@axlsdk/studio 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createServer
4
- } from "./chunk-KQ3HBRTC.js";
4
+ } from "./chunk-YGLWYDQ3.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { resolve, extname } from "path";
@@ -14,8 +14,58 @@ import { resolveRuntime, pickDefault, pickExport } from "@axlsdk/axl";
14
14
 
15
15
  // src/cli-utils.ts
16
16
  import { CONFIG_CANDIDATES, findConfig, needsTsxLoader, importModule } from "@axlsdk/axl";
17
+ var STUDIO_CLI_HOST = "127.0.0.1";
18
+ function isLoopbackHostname(hostname) {
19
+ return hostname === "localhost" || hostname === "localhost." || hostname === "127.0.0.1";
20
+ }
21
+ function isAllowedStandaloneHost(host) {
22
+ if (!host) return false;
23
+ let url;
24
+ try {
25
+ url = new URL(`http://${host}`);
26
+ } catch {
27
+ return false;
28
+ }
29
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return false;
30
+ return isLoopbackHostname(url.hostname);
31
+ }
32
+ function isAllowedStandaloneOrigin(origin) {
33
+ if (origin === void 0) return true;
34
+ let url;
35
+ try {
36
+ url = new URL(origin);
37
+ } catch {
38
+ return false;
39
+ }
40
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
41
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return false;
42
+ return isLoopbackHostname(url.hostname);
43
+ }
44
+ function isAllowedStandaloneRequest(host, origin) {
45
+ return isAllowedStandaloneHost(host) && isAllowedStandaloneOrigin(origin);
46
+ }
47
+ function withStandaloneRequestGuard(fetch) {
48
+ return (request) => {
49
+ const host = request.headers.get("host") ?? void 0;
50
+ const origin = request.headers.get("origin") ?? void 0;
51
+ if (!isAllowedStandaloneRequest(host, origin)) {
52
+ return Response.json(
53
+ {
54
+ ok: false,
55
+ error: {
56
+ code: "FORBIDDEN_HOST_ORIGIN",
57
+ message: "Standalone Studio accepts requests only through a local host and origin"
58
+ }
59
+ },
60
+ { status: 403 }
61
+ );
62
+ }
63
+ return fetch(request);
64
+ };
65
+ }
17
66
  function parseArgs(argv) {
18
67
  let port = 4400;
68
+ let host = STUDIO_CLI_HOST;
19
69
  let config;
20
70
  let open = false;
21
71
  let help = false;
@@ -26,6 +76,9 @@ function parseArgs(argv) {
26
76
  if (arg === "--port" && argv[i + 1]) {
27
77
  port = parseInt(argv[i + 1], 10);
28
78
  i++;
79
+ } else if (arg === "--dangerously-bind" && argv[i + 1]) {
80
+ host = argv[i + 1];
81
+ i++;
29
82
  } else if (arg === "--config" && argv[i + 1]) {
30
83
  config = argv[i + 1];
31
84
  i++;
@@ -40,7 +93,7 @@ function parseArgs(argv) {
40
93
  help = true;
41
94
  }
42
95
  }
43
- const result = { port, config, open, help, conditions, readOnly };
96
+ const result = { port, host, config, open, help, conditions, readOnly };
44
97
  if (isNaN(port) || port < 1 || port > 65535) {
45
98
  result.portError = `Invalid port: ${port}. Must be between 1 and 65535.`;
46
99
  }
@@ -59,6 +112,7 @@ Usage:
59
112
 
60
113
  Options:
61
114
  --port <number> Server port (default: 4400)
115
+ --dangerously-bind <ip> Bind for a loopback-published container/local-forward tunnel
62
116
  --config <path> Path to config file (default: auto-detect)
63
117
  --conditions <list> Comma-separated Node.js import conditions (e.g., development)
64
118
  --read-only Disable all mutating endpoints (runs, imports, rescore, etc)
@@ -151,24 +205,41 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
151
205
  const { app, createWsHandlers } = createServer({
152
206
  runtime,
153
207
  staticRoot: hasStaticAssets ? staticRoot : void 0,
154
- readOnly: args.readOnly
208
+ readOnly: args.readOnly,
209
+ cors: false
155
210
  });
156
211
  if (args.readOnly) {
157
212
  console.log("[axl-studio] Read-only mode enabled \u2014 mutating endpoints are disabled.");
158
213
  }
159
214
  const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
160
215
  const wsHandlers = createWsHandlers();
216
+ app.use("/ws", async (c, next) => {
217
+ if (!isAllowedStandaloneRequest(c.req.header("Host"), c.req.header("Origin"))) {
218
+ return c.json(
219
+ {
220
+ ok: false,
221
+ error: {
222
+ code: "FORBIDDEN_HOST_ORIGIN",
223
+ message: "Standalone Studio accepts WebSocket connections only locally"
224
+ }
225
+ },
226
+ 403
227
+ );
228
+ }
229
+ await next();
230
+ });
161
231
  app.get(
162
232
  "/ws",
163
233
  upgradeWebSocket(() => wsHandlers)
164
234
  );
165
235
  const server = serve(
166
236
  {
167
- fetch: app.fetch,
168
- port: args.port
237
+ fetch: withStandaloneRequestGuard((request) => app.fetch(request)),
238
+ port: args.port,
239
+ hostname: args.host
169
240
  },
170
241
  (info) => {
171
- console.log(`[axl-studio] Server running at http://localhost:${info.port}`);
242
+ console.log(`[axl-studio] Server running at http://${STUDIO_CLI_HOST}:${info.port}`);
172
243
  console.log(`[axl-studio] Workflows: ${runtime.getWorkflowNames().join(", ") || "(none)"}`);
173
244
  console.log(
174
245
  `[axl-studio] Agents: ${runtime.getAgents().map((a) => a._name).join(", ") || "(none)"}`
@@ -184,8 +255,13 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
184
255
  }
185
256
  );
186
257
  injectWebSocket(server);
258
+ if (args.host !== STUDIO_CLI_HOST) {
259
+ console.warn(
260
+ `[axl-studio] WARNING: binding to ${args.host}. Use this only behind a local-only container port mapping or local-forward tunnel; standalone Studio remains unauthenticated.`
261
+ );
262
+ }
187
263
  if (args.open) {
188
- const url = `http://localhost:${args.port}`;
264
+ const url = `http://${STUDIO_CLI_HOST}:${args.port}`;
189
265
  const { exec } = await import("child_process");
190
266
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
191
267
  exec(`${cmd} ${url}`);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/resolve-runtime.ts","../src/cli-utils.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { resolve, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { createServer } from './server/index.js';\nimport { resolveRuntime } from './resolve-runtime.js';\nimport { parseArgs, findConfig, importModule, CONFIG_CANDIDATES } from './cli-utils.js';\n\n// ── Main ────────────────────────────────────────────────────────────\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n if (args.help) {\n console.log(`\nAxl Studio — Local development UI for Axl agents and workflows\n\nUsage:\n axl-studio [options]\n\nOptions:\n --port <number> Server port (default: 4400)\n --config <path> Path to config file (default: auto-detect)\n --conditions <list> Comma-separated Node.js import conditions (e.g., development)\n --read-only Disable all mutating endpoints (runs, imports, rescore, etc)\n --open Auto-open browser\n -h, --help Show this help message\n\nConfig auto-detection order:\n ${CONFIG_CANDIDATES.join(' → ')}\n\nTip: Use .mts for configs with top-level await or in projects without \"type\": \"module\".\n`);\n process.exit(0);\n }\n\n if (args.portError) {\n console.error(args.portError);\n process.exit(1);\n }\n\n // Resolve config path: explicit --config or auto-detect\n let configPath: string;\n if (args.config) {\n configPath = resolve(process.cwd(), args.config);\n if (!existsSync(configPath)) {\n console.error(`Config file not found: ${configPath}`);\n process.exit(1);\n }\n } else {\n const found = findConfig(process.cwd());\n if (!found) {\n console.error(`No config file found. Searched for: ${CONFIG_CANDIDATES.join(', ')}`);\n console.error(`Create an axl.config.mts that exports a default AxlRuntime instance.`);\n process.exit(1);\n }\n configPath = found;\n }\n\n // Register custom import conditions (e.g., --conditions development).\n // In monorepos, package.json \"exports\" often use the \"development\" condition\n // to point at source (.ts) instead of built dist. Without this, Studio\n // configs that import workspace packages resolve to dist files, which may\n // not exist or be stale.\n if (args.conditions.length > 0) {\n try {\n const nodeModule = await import('node:module');\n const hookCode = [\n `const extra = ${JSON.stringify(args.conditions)};`,\n `export async function resolve(specifier, context, nextResolve) {`,\n ` return nextResolve(specifier, {`,\n ` ...context,`,\n ` conditions: [...new Set([...context.conditions, ...extra])],`,\n ` });`,\n `}`,\n ].join('\\n');\n nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);\n } catch {\n console.warn(`[axl-studio] Warning: --conditions requires Node.js 20.6+`);\n }\n }\n\n console.log(`[axl-studio] Loading config from ${configPath}`);\n\n // Import the user's config via tsImport (handles ESM/CJS correctly for\n // TypeScript files without process-wide side effects)\n let runtime: import('@axlsdk/axl').AxlRuntime;\n const ext = extname(configPath);\n try {\n const mod = await importModule(configPath, import.meta.url);\n // resolveRuntime handles ESM default, CJS-to-ESM interop, and named exports\n runtime = resolveRuntime(mod) as typeof runtime;\n\n if (!runtime || typeof runtime.execute !== 'function') {\n console.error(`Config must export a default AxlRuntime instance.`);\n if (runtime) {\n const keys = Object.keys(runtime as object)\n .slice(0, 5)\n .join(', ');\n console.error(` Got: ${typeof runtime}${keys ? ` with keys: { ${keys} }` : ''}`);\n }\n console.error(\n `Example:\\n import { AxlRuntime } from '@axlsdk/axl';\\n export default new AxlRuntime({ ... });`,\n );\n process.exit(1);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (\n /Cannot use import statement|Unexpected reserved word|top-level await|exports is not defined/.test(\n msg,\n )\n ) {\n console.error(`[axl-studio] Config failed to load due to a CJS/ESM compatibility issue.`);\n if (ext === '.ts' || ext === '.tsx') {\n console.error(\n ` Tip: try renaming to .mts to force ESM format, or ensure tsx is installed and up to date.`,\n );\n } else {\n console.error(` Tip: add \"type\": \"module\" to your package.json.`);\n }\n console.error();\n }\n console.error(`Failed to load config:`, err);\n process.exit(1);\n }\n\n // Determine static root for pre-built SPA\n const staticRoot = resolve(import.meta.dirname ?? __dirname, 'client');\n const hasStaticAssets = existsSync(resolve(staticRoot, 'index.html'));\n\n const { app, createWsHandlers } = createServer({\n runtime,\n staticRoot: hasStaticAssets ? staticRoot : undefined,\n readOnly: args.readOnly,\n });\n\n if (args.readOnly) {\n console.log('[axl-studio] Read-only mode enabled — mutating endpoints are disabled.');\n }\n\n // Set up WebSocket\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n const wsHandlers = createWsHandlers();\n app.get(\n '/ws',\n upgradeWebSocket(() => wsHandlers),\n );\n\n const server = serve(\n {\n fetch: app.fetch,\n port: args.port,\n },\n (info) => {\n console.log(`[axl-studio] Server running at http://localhost:${info.port}`);\n console.log(`[axl-studio] Workflows: ${runtime.getWorkflowNames().join(', ') || '(none)'}`);\n console.log(\n `[axl-studio] Agents: ${\n runtime\n .getAgents()\n .map((a) => a._name)\n .join(', ') || '(none)'\n }`,\n );\n console.log(\n `[axl-studio] Tools: ${\n runtime\n .getTools()\n .map((t) => t.name)\n .join(', ') || '(none)'\n }`,\n );\n\n if (!hasStaticAssets) {\n console.log(\n `[axl-studio] No pre-built UI found. Run 'pnpm build:client' or use Vite dev server on port 4401.`,\n );\n }\n },\n );\n\n injectWebSocket(server);\n\n // Auto-open browser\n if (args.open) {\n const url = `http://localhost:${args.port}`;\n const { exec } = await import('node:child_process');\n const cmd =\n process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';\n exec(`${cmd} ${url}`);\n }\n\n // Graceful shutdown\n const shutdown = async () => {\n console.log('\\n[axl-studio] Shutting down...');\n await runtime.shutdown().catch(() => {});\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((err) => {\n console.error('[axl-studio] Fatal error:', err);\n process.exit(1);\n});\n","/**\n * Re-exports of module-resolution helpers from `@axlsdk/axl`.\n *\n * Studio used to keep its own copy of these (alongside the duplicated copy in\n * `@axlsdk/eval`'s cli-utils). They've been lifted to `@axlsdk/axl` so a fix\n * to the ESM/CJS chain walk applies to all three consumers atomically. Kept\n * as a re-export rather than a deletion so existing imports still work and\n * the test file at `__tests__/resolve-runtime.test.ts` continues to function\n * as a tripwire on this module's path.\n */\nexport { resolveRuntime, pickDefault, pickExport } from '@axlsdk/axl';\n","/**\n * CLI utilities for `axl-studio`.\n *\n * Loader / glob / config-detection helpers now live in `@axlsdk/axl`'s\n * `cli-internals` (shared with `@axlsdk/eval` so fixes apply atomically);\n * this file holds the studio-specific arg parser and re-exports what\n * dependent modules consume.\n */\n\nexport { CONFIG_CANDIDATES, findConfig, needsTsxLoader, importModule } from '@axlsdk/axl';\n\n// ── Parse CLI args ──────────────────────────────────────────────────\n\nexport interface CliArgs {\n port: number;\n config?: string;\n open: boolean;\n conditions: string[];\n readOnly: boolean;\n help: boolean;\n portError?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n let port = 4400;\n let config: string | undefined;\n let open = false;\n let help = false;\n let conditions: string[] = [];\n let readOnly = false;\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--port' && argv[i + 1]) {\n port = parseInt(argv[i + 1], 10);\n i++;\n } else if (arg === '--config' && argv[i + 1]) {\n config = argv[i + 1];\n i++;\n } else if (arg === '--conditions' && argv[i + 1]) {\n conditions = argv[i + 1]\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean);\n i++;\n } else if (arg === '--open') {\n open = true;\n } else if (arg === '--read-only' || arg === '--readonly') {\n readOnly = true;\n } else if (arg === '--help' || arg === '-h') {\n help = true;\n }\n }\n\n const result: CliArgs = { port, config, open, help, conditions, readOnly };\n\n if (isNaN(port) || port < 1 || port > 65535) {\n result.portError = `Invalid port: ${port}. Must be between 1 and 65535.`;\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/resolve-runtime.ts","../src/cli-utils.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { resolve, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { createServer } from './server/index.js';\nimport { resolveRuntime } from './resolve-runtime.js';\nimport {\n parseArgs,\n findConfig,\n importModule,\n CONFIG_CANDIDATES,\n STUDIO_CLI_HOST,\n isAllowedStandaloneRequest,\n withStandaloneRequestGuard,\n} from './cli-utils.js';\n\n// ── Main ────────────────────────────────────────────────────────────\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n if (args.help) {\n console.log(`\nAxl Studio — Local development UI for Axl agents and workflows\n\nUsage:\n axl-studio [options]\n\nOptions:\n --port <number> Server port (default: 4400)\n --dangerously-bind <ip> Bind for a loopback-published container/local-forward tunnel\n --config <path> Path to config file (default: auto-detect)\n --conditions <list> Comma-separated Node.js import conditions (e.g., development)\n --read-only Disable all mutating endpoints (runs, imports, rescore, etc)\n --open Auto-open browser\n -h, --help Show this help message\n\nConfig auto-detection order:\n ${CONFIG_CANDIDATES.join(' → ')}\n\nTip: Use .mts for configs with top-level await or in projects without \"type\": \"module\".\n`);\n process.exit(0);\n }\n\n if (args.portError) {\n console.error(args.portError);\n process.exit(1);\n }\n\n // Resolve config path: explicit --config or auto-detect\n let configPath: string;\n if (args.config) {\n configPath = resolve(process.cwd(), args.config);\n if (!existsSync(configPath)) {\n console.error(`Config file not found: ${configPath}`);\n process.exit(1);\n }\n } else {\n const found = findConfig(process.cwd());\n if (!found) {\n console.error(`No config file found. Searched for: ${CONFIG_CANDIDATES.join(', ')}`);\n console.error(`Create an axl.config.mts that exports a default AxlRuntime instance.`);\n process.exit(1);\n }\n configPath = found;\n }\n\n // Register custom import conditions (e.g., --conditions development).\n // In monorepos, package.json \"exports\" often use the \"development\" condition\n // to point at source (.ts) instead of built dist. Without this, Studio\n // configs that import workspace packages resolve to dist files, which may\n // not exist or be stale.\n if (args.conditions.length > 0) {\n try {\n const nodeModule = await import('node:module');\n const hookCode = [\n `const extra = ${JSON.stringify(args.conditions)};`,\n `export async function resolve(specifier, context, nextResolve) {`,\n ` return nextResolve(specifier, {`,\n ` ...context,`,\n ` conditions: [...new Set([...context.conditions, ...extra])],`,\n ` });`,\n `}`,\n ].join('\\n');\n nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);\n } catch {\n console.warn(`[axl-studio] Warning: --conditions requires Node.js 20.6+`);\n }\n }\n\n console.log(`[axl-studio] Loading config from ${configPath}`);\n\n // Import the user's config via tsImport (handles ESM/CJS correctly for\n // TypeScript files without process-wide side effects)\n let runtime: import('@axlsdk/axl').AxlRuntime;\n const ext = extname(configPath);\n try {\n const mod = await importModule(configPath, import.meta.url);\n // resolveRuntime handles ESM default, CJS-to-ESM interop, and named exports\n runtime = resolveRuntime(mod) as typeof runtime;\n\n if (!runtime || typeof runtime.execute !== 'function') {\n console.error(`Config must export a default AxlRuntime instance.`);\n if (runtime) {\n const keys = Object.keys(runtime as object)\n .slice(0, 5)\n .join(', ');\n console.error(` Got: ${typeof runtime}${keys ? ` with keys: { ${keys} }` : ''}`);\n }\n console.error(\n `Example:\\n import { AxlRuntime } from '@axlsdk/axl';\\n export default new AxlRuntime({ ... });`,\n );\n process.exit(1);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (\n /Cannot use import statement|Unexpected reserved word|top-level await|exports is not defined/.test(\n msg,\n )\n ) {\n console.error(`[axl-studio] Config failed to load due to a CJS/ESM compatibility issue.`);\n if (ext === '.ts' || ext === '.tsx') {\n console.error(\n ` Tip: try renaming to .mts to force ESM format, or ensure tsx is installed and up to date.`,\n );\n } else {\n console.error(` Tip: add \"type\": \"module\" to your package.json.`);\n }\n console.error();\n }\n console.error(`Failed to load config:`, err);\n process.exit(1);\n }\n\n // Determine static root for pre-built SPA\n const staticRoot = resolve(import.meta.dirname ?? __dirname, 'client');\n const hasStaticAssets = existsSync(resolve(staticRoot, 'index.html'));\n\n const { app, createWsHandlers } = createServer({\n runtime,\n staticRoot: hasStaticAssets ? staticRoot : undefined,\n readOnly: args.readOnly,\n cors: false,\n });\n\n if (args.readOnly) {\n console.log('[axl-studio] Read-only mode enabled — mutating endpoints are disabled.');\n }\n\n // Set up WebSocket\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n const wsHandlers = createWsHandlers();\n app.use('/ws', async (c, next) => {\n if (!isAllowedStandaloneRequest(c.req.header('Host'), c.req.header('Origin'))) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'FORBIDDEN_HOST_ORIGIN',\n message: 'Standalone Studio accepts WebSocket connections only locally',\n },\n },\n 403,\n );\n }\n await next();\n });\n app.get(\n '/ws',\n upgradeWebSocket(() => wsHandlers),\n );\n\n const server = serve(\n {\n fetch: withStandaloneRequestGuard((request) => app.fetch(request)),\n port: args.port,\n hostname: args.host,\n },\n (info) => {\n console.log(`[axl-studio] Server running at http://${STUDIO_CLI_HOST}:${info.port}`);\n console.log(`[axl-studio] Workflows: ${runtime.getWorkflowNames().join(', ') || '(none)'}`);\n console.log(\n `[axl-studio] Agents: ${\n runtime\n .getAgents()\n .map((a) => a._name)\n .join(', ') || '(none)'\n }`,\n );\n console.log(\n `[axl-studio] Tools: ${\n runtime\n .getTools()\n .map((t) => t.name)\n .join(', ') || '(none)'\n }`,\n );\n\n if (!hasStaticAssets) {\n console.log(\n `[axl-studio] No pre-built UI found. Run 'pnpm build:client' or use Vite dev server on port 4401.`,\n );\n }\n },\n );\n\n injectWebSocket(server);\n\n if (args.host !== STUDIO_CLI_HOST) {\n console.warn(\n `[axl-studio] WARNING: binding to ${args.host}. Use this only behind a local-only ` +\n 'container port mapping or local-forward tunnel; standalone Studio remains unauthenticated.',\n );\n }\n\n // Auto-open browser\n if (args.open) {\n const url = `http://${STUDIO_CLI_HOST}:${args.port}`;\n const { exec } = await import('node:child_process');\n const cmd =\n process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';\n exec(`${cmd} ${url}`);\n }\n\n // Graceful shutdown\n const shutdown = async () => {\n console.log('\\n[axl-studio] Shutting down...');\n await runtime.shutdown().catch(() => {});\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((err) => {\n console.error('[axl-studio] Fatal error:', err);\n process.exit(1);\n});\n","/**\n * Re-exports of module-resolution helpers from `@axlsdk/axl`.\n *\n * Studio used to keep its own copy of these (alongside the duplicated copy in\n * `@axlsdk/eval`'s cli-utils). They've been lifted to `@axlsdk/axl` so a fix\n * to the ESM/CJS chain walk applies to all three consumers atomically. Kept\n * as a re-export rather than a deletion so existing imports still work and\n * the test file at `__tests__/resolve-runtime.test.ts` continues to function\n * as a tripwire on this module's path.\n */\nexport { resolveRuntime, pickDefault, pickExport } from '@axlsdk/axl';\n","/**\n * CLI utilities for `axl-studio`.\n *\n * Loader / glob / config-detection helpers now live in `@axlsdk/axl`'s\n * `cli-internals` (shared with `@axlsdk/eval` so fixes apply atomically);\n * this file holds the studio-specific arg parser and re-exports what\n * dependent modules consume.\n */\n\nexport { CONFIG_CANDIDATES, findConfig, needsTsxLoader, importModule } from '@axlsdk/axl';\n\n/** Standalone Studio is an unauthenticated local-development surface. */\nexport const STUDIO_CLI_HOST = '127.0.0.1';\n\ntype StandaloneFetch = (request: Request) => Response | Promise<Response>;\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === 'localhost' || hostname === 'localhost.' || hostname === '127.0.0.1';\n}\n\n/** Reject DNS-rebinding Host values before standalone Studio serves admin data. */\nexport function isAllowedStandaloneHost(host: string | undefined): boolean {\n if (!host) return false;\n\n let url: URL;\n try {\n url = new URL(`http://${host}`);\n } catch {\n return false;\n }\n\n if (url.username || url.password || url.pathname !== '/' || url.search || url.hash) return false;\n return isLoopbackHostname(url.hostname);\n}\n\n/**\n * Browser WebSocket handshakes carry an Origin header even though they are not\n * governed by CORS. Standalone Studio accepts only local browser origins; CLI\n * and other non-browser clients may omit Origin.\n */\nexport function isAllowedStandaloneOrigin(origin: string | undefined): boolean {\n if (origin === undefined) return true;\n\n let url: URL;\n try {\n url = new URL(origin);\n } catch {\n return false;\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;\n if (url.username || url.password || url.pathname !== '/' || url.search || url.hash) return false;\n\n return isLoopbackHostname(url.hostname);\n}\n\n/** Standalone Studio accepts only loopback authorities and local/absent origins. */\nexport function isAllowedStandaloneRequest(\n host: string | undefined,\n origin: string | undefined,\n): boolean {\n return isAllowedStandaloneHost(host) && isAllowedStandaloneOrigin(origin);\n}\n\n/**\n * Guard the standalone HTTP boundary before Hono routing. This blocks hostile\n * simple POSTs as well as DNS-rebinding reads, and is intentionally separate\n * from CORS response headers.\n */\nexport function withStandaloneRequestGuard(fetch: StandaloneFetch): StandaloneFetch {\n return (request) => {\n const host = request.headers.get('host') ?? undefined;\n const origin = request.headers.get('origin') ?? undefined;\n if (!isAllowedStandaloneRequest(host, origin)) {\n return Response.json(\n {\n ok: false,\n error: {\n code: 'FORBIDDEN_HOST_ORIGIN',\n message: 'Standalone Studio accepts requests only through a local host and origin',\n },\n },\n { status: 403 },\n );\n }\n return fetch(request);\n };\n}\n\n// ── Parse CLI args ──────────────────────────────────────────────────\n\nexport interface CliArgs {\n port: number;\n host: string;\n config?: string;\n open: boolean;\n conditions: string[];\n readOnly: boolean;\n help: boolean;\n portError?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n let port = 4400;\n let host = STUDIO_CLI_HOST;\n let config: string | undefined;\n let open = false;\n let help = false;\n let conditions: string[] = [];\n let readOnly = false;\n\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--port' && argv[i + 1]) {\n port = parseInt(argv[i + 1], 10);\n i++;\n } else if (arg === '--dangerously-bind' && argv[i + 1]) {\n host = argv[i + 1];\n i++;\n } else if (arg === '--config' && argv[i + 1]) {\n config = argv[i + 1];\n i++;\n } else if (arg === '--conditions' && argv[i + 1]) {\n conditions = argv[i + 1]\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean);\n i++;\n } else if (arg === '--open') {\n open = true;\n } else if (arg === '--read-only' || arg === '--readonly') {\n readOnly = true;\n } else if (arg === '--help' || arg === '-h') {\n help = true;\n }\n }\n\n const result: CliArgs = { port, host, config, open, help, conditions, readOnly };\n\n if (isNaN(port) || port < 1 || port > 65535) {\n result.portError = `Invalid port: ${port}. Must be between 1 and 65535.`;\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -1399,7 +1399,10 @@ function createSessionRoutes(connMgr) {
1399
1399
  const body = await c.req.json();
1400
1400
  const session = runtime.session(id);
1401
1401
  const result = await session.send(body.workflow, body.message);
1402
- return c.json({ ok: true, data: { result } });
1402
+ return c.json({
1403
+ ok: true,
1404
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1405
+ });
1403
1406
  });
1404
1407
  app5.post("/sessions/:id/stream", async (c) => {
1405
1408
  const runtime = c.get("runtime");
@@ -1408,9 +1411,13 @@ function createSessionRoutes(connMgr) {
1408
1411
  const session = runtime.session(id);
1409
1412
  const stream = await session.stream(body.workflow, body.message);
1410
1413
  const executionId = `session-${id}-${Date.now()}`;
1414
+ const redactOn = runtime.isRedactEnabled();
1411
1415
  (async () => {
1412
1416
  for await (const event of stream) {
1413
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
1417
+ connMgr.broadcastWithWildcard(
1418
+ `execution:${executionId}`,
1419
+ redactStreamEvent(event, redactOn)
1420
+ );
1414
1421
  }
1415
1422
  })();
1416
1423
  return c.json({ ok: true, data: { executionId, streaming: true } });
@@ -16116,7 +16123,7 @@ function createServer(options) {
16116
16123
  emptyState: emptyEvalTrendData,
16117
16124
  windows
16118
16125
  });
16119
- if (options.cors !== false) {
16126
+ if (options.cors === true) {
16120
16127
  app5.use("*", (0, import_cors.cors)());
16121
16128
  }
16122
16129
  app5.onError(errorHandler);
@@ -16392,6 +16399,7 @@ function createStudioMiddleware(options) {
16392
16399
  runtime,
16393
16400
  serveClient = true,
16394
16401
  verifyUpgrade,
16402
+ dangerouslyAllowUnauthenticatedWebSockets = false,
16395
16403
  readOnly = false,
16396
16404
  filterTraceEvent
16397
16405
  } = options;
@@ -16417,9 +16425,9 @@ function createStudioMiddleware(options) {
16417
16425
  if (filterTraceEvent) {
16418
16426
  connMgr.setFilter(filterTraceEvent);
16419
16427
  }
16420
- if (process.env.NODE_ENV === "production" && !verifyUpgrade) {
16428
+ if (process.env.NODE_ENV === "production" && !verifyUpgrade && !dangerouslyAllowUnauthenticatedWebSockets) {
16421
16429
  console.warn(
16422
- "[axl-studio] WARNING: Studio middleware mounted in production without verifyUpgrade. WebSocket connections are not authenticated. All registered workflows, tools, and agents are accessible. See https://axlsdk.com/docs/studio/security"
16430
+ "[axl-studio] WARNING: Studio middleware mounted in production without verifyUpgrade. upgradeWebSocket() will refuse to attach. Provide verifyUpgrade or explicitly set dangerouslyAllowUnauthenticatedWebSockets: true only when an outer upgrade gate is guaranteed."
16423
16431
  );
16424
16432
  }
16425
16433
  const listener = (0, import_node_server.getRequestListener)(app5.fetch, {
@@ -16481,6 +16489,11 @@ function createStudioMiddleware(options) {
16481
16489
  let upgradeHandler;
16482
16490
  let serverRef;
16483
16491
  function upgradeWebSocket(server, path) {
16492
+ if (process.env.NODE_ENV === "production" && !verifyUpgrade && !dangerouslyAllowUnauthenticatedWebSockets) {
16493
+ throw new Error(
16494
+ "[axl-studio] Refusing unauthenticated WebSocket upgrades in production. Provide verifyUpgrade or explicitly set dangerouslyAllowUnauthenticatedWebSockets: true."
16495
+ );
16496
+ }
16484
16497
  if (wss) {
16485
16498
  throw new Error(
16486
16499
  "[axl-studio] upgradeWebSocket() has already been called. Call close() first if you need to re-attach."