-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Local Playground #33
Local Playground #33
Changes from 15 commits
e30f9ea
1ade0f4
a6e6286
60b6e07
a0f3cf9
a024cf0
4eb3d1c
2ca4822
f2eb50a
ef03da8
648f9c3
48100cb
d7ed5ea
2b45336
6eb086d
e8c782e
a8a615c
a3a9242
db1a202
055ecab
a96ef0a
c5aba60
2c414b1
63316c1
f8a8bd5
1592a3d
c7e324b
17e46fb
1ab354b
a087f6f
7f615ad
a0a5603
d822397
21c3623
9895c6a
9098291
1beb9a8
01104b3
6a9b5dd
562c5e0
d1c4b53
6496d40
bac96e2
25251c8
bc1ec36
70a75b9
34fae23
804f847
8a43b52
edd46ee
4c9c9e2
4a0c009
43a10b8
55ad837
8ae2571
aa0449f
d8b1a44
08982f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Command } from "commander"; | ||
|
||
import { TunnelService } from "../services/tunnel"; | ||
import { detectPort } from "../utils/port-detector"; | ||
|
||
export const devDeprecatedCommand = new Command() | ||
.name("dev-deprecated") | ||
.description("Make your AI agent discoverable and register the plugin") | ||
.option("-p, --port <number>", "Local port to expose", parseInt) | ||
.option("-s, --serveo", "Use Serveo instead of Localtunnel", false) | ||
.option("-t, --testnet", "Use Testnet instead of Mainnet", false) | ||
.action(async (options) => { | ||
let port = options.port; | ||
if (!port) { | ||
port = await detectPort(); | ||
if (!port) { | ||
console.error( | ||
"Unable to detect the port automatically. Please specify a port using the -p or --port option.", | ||
); | ||
process.exit(1); | ||
} | ||
console.log(`Detected port: ${port}`); | ||
} | ||
const tunnelService = new TunnelService({ | ||
port, | ||
useServeo: options.serveo, | ||
useTestnet: options.testnet, | ||
}); | ||
await tunnelService.start(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,128 @@ | ||
import { Command } from "commander"; | ||
import dotenv from "dotenv"; | ||
import isPortReachable from "is-port-reachable"; | ||
|
||
import { TunnelService } from "../services/tunnel"; | ||
import { startApiServer } from "../services/proxy"; | ||
import { getDeployedUrl } from "../utils/deployed-url"; | ||
import { validateEnv } from "../utils/env"; | ||
import { validateAndParseOpenApiSpec } from "../utils/openapi"; | ||
import { detectPort } from "../utils/port-detector"; | ||
import { getHostname, getSpecUrl } from "../utils/url-utils"; | ||
|
||
dotenv.config(); | ||
validateEnv(); | ||
|
||
interface ApiConfig { | ||
key: string; | ||
url: string; | ||
serverPort: number; | ||
} | ||
|
||
interface ValidationResult { | ||
pluginId: string; | ||
accountId: string; | ||
spec: unknown; | ||
} | ||
|
||
const DEFAULT_PORTS = { | ||
SERVER: 3010, | ||
} as const; | ||
|
||
async function findAvailablePort(startPort: number): Promise<number> { | ||
let port = startPort; | ||
while (await isPortReachable(port, { host: "localhost" })) { | ||
port++; | ||
} | ||
return port; | ||
} | ||
|
||
const API_CONFIG: ApiConfig = { | ||
key: process.env.BITTE_API_KEY!, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is likely the culprit of the following dirty error which I will paste in entirety to demonstrate how gross it is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specifically the "dev" command is the only one no longer subject to the check with the warning. Search the project for |
||
url: process.env.BITTE_API_URL!, | ||
serverPort: DEFAULT_PORTS.SERVER, | ||
}; | ||
|
||
async function fetchAndValidateSpec(url: string): Promise<ValidationResult> { | ||
const pluginId = getHostname(url); | ||
const specUrl = getSpecUrl(url); | ||
|
||
const validation = await validateAndParseOpenApiSpec(specUrl); | ||
const { isValid, accountId } = validation; | ||
|
||
const specContent = await fetch(specUrl).then((res) => res.text()); | ||
let spec = JSON.parse(specContent); | ||
|
||
if (!isValid) { | ||
spec = { | ||
...spec, | ||
servers: [{ url }], | ||
"x-mb": { | ||
...spec["x-mb"], | ||
"account-id": accountId || "anon", | ||
}, | ||
}; | ||
} | ||
|
||
return { | ||
pluginId, | ||
accountId: accountId || "anon", | ||
spec, | ||
}; | ||
} | ||
|
||
async function setupPorts(options: { | ||
port?: string; | ||
}): Promise<{ port: number; serverPort: number }> { | ||
let port = parseInt(options.port || "") || 0; | ||
|
||
if (port === 0) { | ||
const detectedPort = await detectPort(); | ||
if (detectedPort) { | ||
port = detectedPort; | ||
} else { | ||
port = await findAvailablePort(3000); | ||
} | ||
} | ||
|
||
const serverPort = await findAvailablePort(DEFAULT_PORTS.SERVER); | ||
|
||
return { port, serverPort }; | ||
} | ||
|
||
export const devCommand = new Command() | ||
.name("dev") | ||
.description("Make your AI agent discoverable and register the plugin") | ||
.option("-p, --port <number>", "Local port to expose", parseInt) | ||
.option("-s, --serveo", "Use Serveo instead of Localtunnel", false) | ||
.description("Start a local playground for your AI agent") | ||
.option("-p, --port <port>", "Port to run playground on", "3000") | ||
.option("-t, --testnet", "Use Testnet instead of Mainnet", false) | ||
.action(async (options) => { | ||
let port = options.port; | ||
if (!port) { | ||
port = await detectPort(); | ||
if (!port) { | ||
console.error( | ||
"Unable to detect the port automatically. Please specify a port using the -p or --port option.", | ||
try { | ||
const { port, serverPort } = await setupPorts(options); | ||
|
||
API_CONFIG.serverPort = serverPort; | ||
const server = await startApiServer(API_CONFIG); | ||
|
||
const deployedUrl = getDeployedUrl(port); | ||
if (!deployedUrl) { | ||
throw new Error("Deployed URL could not be determined."); | ||
} | ||
|
||
try { | ||
console.log( | ||
"[Dev] Fetching and validating OpenAPI spec from:", | ||
deployedUrl, | ||
); | ||
process.exit(1); | ||
await fetchAndValidateSpec(deployedUrl); | ||
console.log("[Dev] OpenAPI spec validation successful"); | ||
} catch (error) { | ||
console.error("[Dev] Error validating OpenAPI spec:", error); | ||
throw error; | ||
} | ||
console.log(`Detected port: ${port}`); | ||
|
||
process.on("SIGINT", async () => { | ||
server.close(); | ||
process.exit(0); | ||
}); | ||
} catch (error) { | ||
process.exit(1); | ||
} | ||
const tunnelService = new TunnelService({ | ||
port, | ||
useServeo: options.serveo, | ||
useTestnet: options.testnet, | ||
}); | ||
await tunnelService.start(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# React + TypeScript + Vite | ||
|
||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | ||
|
||
Currently, two official plugins are available: | ||
|
||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | ||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | ||
|
||
## Expanding the ESLint configuration | ||
|
||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: | ||
|
||
- Configure the top-level `parserOptions` property like this: | ||
|
||
```js | ||
export default tseslint.config({ | ||
languageOptions: { | ||
// other options... | ||
parserOptions: { | ||
project: ['./tsconfig.node.json', './tsconfig.app.json'], | ||
tsconfigRootDir: import.meta.dirname, | ||
}, | ||
}, | ||
}) | ||
``` | ||
|
||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` | ||
- Optionally add `...tseslint.configs.stylisticTypeChecked` | ||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: | ||
|
||
```js | ||
// eslint.config.js | ||
import react from 'eslint-plugin-react' | ||
|
||
export default tseslint.config({ | ||
// Set the react version | ||
settings: { react: { version: '18.3' } }, | ||
plugins: { | ||
// Add the react plugin | ||
react, | ||
}, | ||
rules: { | ||
// other rules... | ||
// Enable its recommended rules | ||
...react.configs.recommended.rules, | ||
...react.configs['jsx-runtime'].rules, | ||
}, | ||
}) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import js from "@eslint/js"; | ||
import globals from "globals"; | ||
import reactHooks from "eslint-plugin-react-hooks"; | ||
import reactRefresh from "eslint-plugin-react-refresh"; | ||
import tseslint from "typescript-eslint"; | ||
|
||
export default tseslint.config( | ||
{ ignores: ["dist"] }, | ||
{ | ||
extends: [js.configs.recommended, ...tseslint.configs.recommended], | ||
files: ["**/*.{ts,tsx}"], | ||
languageOptions: { | ||
ecmaVersion: 2020, | ||
globals: globals.browser, | ||
}, | ||
plugins: { | ||
"react-hooks": reactHooks, | ||
"react-refresh": reactRefresh, | ||
}, | ||
rules: { | ||
...reactHooks.configs.recommended.rules, | ||
"react-refresh/only-export-components": [ | ||
"warn", | ||
{ allowConstantExport: true }, | ||
], | ||
}, | ||
}, | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React + TS</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe that either of these dependencies are used in this project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
closed by #54