@runtypelabs/cli 2.14.0 → 2.15.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.
Files changed (2) hide show
  1. package/dist/index.js +702 -55
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -35408,15 +35408,17 @@ var FlowInputSchema = external_exports.object({
35408
35408
  id: external_exports.string().min(1).optional(),
35409
35409
  name: external_exports.string().min(1).optional(),
35410
35410
  description: external_exports.string().optional(),
35411
- steps: external_exports.array(external_exports.any()).optional()
35411
+ steps: external_exports.array(external_exports.any()).optional(),
35412
+ contentHash: external_exports.string().length(64).optional()
35412
35413
  }).refine(
35413
35414
  (data) => {
35414
35415
  const hasFlowId = !!data.id;
35415
35416
  const hasSteps = !!(data.name && data.steps && data.steps.length > 0);
35416
- return hasFlowId || hasSteps;
35417
+ const hasPersistedFlow = !!(data.name && data.contentHash);
35418
+ return hasFlowId || hasSteps || hasPersistedFlow;
35417
35419
  },
35418
35420
  {
35419
- message: "Either 'id' or flow definition with 'name' and 'steps' must be provided."
35421
+ message: "Either 'id', flow definition with 'name' and 'steps', or persisted flow with 'name' and 'contentHash' must be provided."
35420
35422
  }
35421
35423
  );
35422
35424
  var AgentLoopConfigSchema = external_exports.object({
@@ -53970,6 +53972,612 @@ gcloud run deploy runtype-deploy \\
53970
53972
  \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
53971
53973
  `;
53972
53974
  }
53975
+ function workerIndexTs(agentSlugs) {
53976
+ const imports = agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n");
53977
+ const agentFiles = agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n");
53978
+ return `/**
53979
+ * Generated by \`runtype deploy --target cloudflare\`.
53980
+ *
53981
+ * Routes:
53982
+ * GET /health \u2014 liveness probe (lists registered agent names)
53983
+ * POST /agent/:name \u2014 SSE stream of the agent event schema
53984
+ *
53985
+ * Agent definitions are statically imported so Wrangler bundles them.
53986
+ * To add a new agent:
53987
+ * 1. Export it with \`runtype deploy --agent <id> --target cloudflare\`
53988
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
53989
+ * 3. Run \`wrangler deploy\`
53990
+ */
53991
+ import {
53992
+ CloudflareAdapter,
53993
+ createRuntime,
53994
+ exportedAgentSchema,
53995
+ type ExportedAgent,
53996
+ } from '@runtypelabs/runtime'
53997
+
53998
+ // ---------------------------------------------------------------------------
53999
+ // Agent registry \u2014 static imports bundled by Wrangler at deploy time
54000
+ // ---------------------------------------------------------------------------
54001
+
54002
+ ${imports}
54003
+
54004
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
54005
+ try {
54006
+ return exportedAgentSchema.parse(json)
54007
+ } catch (err) {
54008
+ throw new Error(
54009
+ \`[runtype] invalid agent JSON in \${filename}: \${
54010
+ err instanceof Error ? err.message : String(err)
54011
+ }\`
54012
+ )
54013
+ }
54014
+ }
54015
+
54016
+ const agentFiles: Array<[string, unknown]> = [
54017
+ ${agentFiles}
54018
+ ]
54019
+
54020
+ const agentRegistry: Record<string, ExportedAgent> = {}
54021
+ for (const [filename, json] of agentFiles) {
54022
+ const agent = parseAgent(json, filename)
54023
+ if (agentRegistry[agent.name]) {
54024
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
54025
+ }
54026
+ agentRegistry[agent.name] = agent
54027
+ }
54028
+
54029
+ // ---------------------------------------------------------------------------
54030
+ // Env \u2014 Wrangler injects secrets + bindings here
54031
+ // ---------------------------------------------------------------------------
54032
+
54033
+ export interface Env {
54034
+ // Secrets \u2014 set via \`wrangler secret put <NAME>\`
54035
+ ANTHROPIC_API_KEY?: string
54036
+ OPENAI_API_KEY?: string
54037
+ GOOGLE_API_KEY?: string
54038
+ MIXLAYER_API_KEY?: string
54039
+ // Optional: narrow CORS from * to specific origins (comma-separated)
54040
+ ALLOWED_ORIGINS?: string
54041
+ // Optional: KV namespace binding for durable cross-request caching
54042
+ CACHE_KV?: KVNamespace
54043
+ }
54044
+
54045
+ // ---------------------------------------------------------------------------
54046
+ // Worker
54047
+ // ---------------------------------------------------------------------------
54048
+
54049
+ export default {
54050
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
54051
+ const url = new URL(request.url)
54052
+ const cors = corsHeaders(request, env.ALLOWED_ORIGINS)
54053
+
54054
+ if (request.method === 'OPTIONS') {
54055
+ return new Response(null, { status: 204, headers: cors })
54056
+ }
54057
+
54058
+ if (request.method === 'GET' && url.pathname === '/health') {
54059
+ return json({ status: 'ok', agents: Object.keys(agentRegistry) }, 200, cors)
54060
+ }
54061
+
54062
+ const agentMatch = url.pathname.match(/^\\/agent\\/([^/]+)$/)
54063
+ if (request.method === 'POST' && agentMatch) {
54064
+ const name = decodeURIComponent(agentMatch[1])
54065
+ if (!agentRegistry[name]) {
54066
+ return json({ error: \`unknown agent: \${name}\` }, 404, cors)
54067
+ }
54068
+
54069
+ const runtime = createRuntime({
54070
+ agents: agentRegistry,
54071
+ keys: { mode: 'own' },
54072
+ adapter: new CloudflareAdapter(env, {
54073
+ kvNamespace: env.CACHE_KV,
54074
+ waitUntil: ctx.waitUntil.bind(ctx),
54075
+ }),
54076
+ })
54077
+
54078
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
54079
+ try {
54080
+ body = (await request.json()) as typeof body
54081
+ } catch {
54082
+ return json({ error: 'request body must be JSON' }, 400, cors)
54083
+ }
54084
+ const messages = Array.isArray(body.messages) ? body.messages : []
54085
+
54086
+ try {
54087
+ const response = await runtime.executeAgent(name, {
54088
+ messages: messages as Array<{
54089
+ role: 'system' | 'user' | 'assistant'
54090
+ content: string
54091
+ }>,
54092
+ signal: request.signal,
54093
+ })
54094
+ const headers = new Headers(response.headers)
54095
+ for (const [k, v] of Object.entries(cors)) headers.set(k, v)
54096
+ return new Response(response.body, { status: response.status, headers })
54097
+ } catch (err) {
54098
+ return json({ error: err instanceof Error ? err.message : 'unknown error' }, 500, cors)
54099
+ }
54100
+ }
54101
+
54102
+ return json({ error: 'not found' }, 404, cors)
54103
+ },
54104
+ }
54105
+
54106
+ // ---------------------------------------------------------------------------
54107
+ // Helpers
54108
+ // ---------------------------------------------------------------------------
54109
+
54110
+ function json(body: unknown, status: number, extra: Record<string, string>): Response {
54111
+ return new Response(JSON.stringify(body), {
54112
+ status,
54113
+ headers: { 'Content-Type': 'application/json', ...extra },
54114
+ })
54115
+ }
54116
+
54117
+ function corsHeaders(request: Request, allowedOriginsEnv?: string): Record<string, string> {
54118
+ const origin = request.headers.get('Origin') ?? ''
54119
+ let allow = '*'
54120
+ if (allowedOriginsEnv) {
54121
+ const list = allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
54122
+ allow = list.includes(origin) ? origin : (list[0] ?? '*')
54123
+ }
54124
+ const headers: Record<string, string> = {
54125
+ 'Access-Control-Allow-Origin': allow,
54126
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
54127
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
54128
+ 'Access-Control-Max-Age': '86400',
54129
+ }
54130
+ if (allow !== '*') headers['Vary'] = 'Origin'
54131
+ return headers
54132
+ }
54133
+ `;
54134
+ }
54135
+ function wranglerToml(projectName) {
54136
+ return `name = "${projectName}"
54137
+ main = "src/index.ts"
54138
+ compatibility_date = "2025-01-01"
54139
+
54140
+ # Secrets \u2014 set via \`wrangler secret put <NAME>\`
54141
+ # Run this for each API key your agents need:
54142
+ # wrangler secret put ANTHROPIC_API_KEY
54143
+ # wrangler secret put OPENAI_API_KEY
54144
+ # wrangler secret put GOOGLE_API_KEY
54145
+ # wrangler secret put MIXLAYER_API_KEY
54146
+
54147
+ # Optional: KV namespace for durable cross-request caching.
54148
+ # Without this the adapter uses a per-isolate in-memory Map (ephemeral).
54149
+ # To enable:
54150
+ # 1. wrangler kv namespace create CACHE_KV
54151
+ # 2. Uncomment the block below and paste the generated id.
54152
+ # [[kv_namespaces]]
54153
+ # binding = "CACHE_KV"
54154
+ # id = "<paste your KV namespace id here>"
54155
+
54156
+ # Optional: Workers AI binding (for @cf/* and workers-ai/* model ids).
54157
+ # [ai]
54158
+ # binding = "AI"
54159
+
54160
+ # CORS: narrow from * to specific origins via ALLOWED_ORIGINS env var.
54161
+ # [vars]
54162
+ # ALLOWED_ORIGINS = "https://yourapp.com,https://persona-chat.dev"
54163
+ `;
54164
+ }
54165
+ function workerPackageJson(name, runtimeDep) {
54166
+ return JSON.stringify(
54167
+ {
54168
+ name,
54169
+ version: "0.0.0",
54170
+ private: true,
54171
+ scripts: {
54172
+ dev: "wrangler dev",
54173
+ deploy: "wrangler deploy",
54174
+ typecheck: "tsc --noEmit"
54175
+ },
54176
+ dependencies: {
54177
+ "@runtypelabs/runtime": runtimeDep
54178
+ },
54179
+ devDependencies: {
54180
+ "@cloudflare/workers-types": "^4.0.0",
54181
+ typescript: "^5.3.3",
54182
+ wrangler: "^4.0.0"
54183
+ }
54184
+ },
54185
+ null,
54186
+ 2
54187
+ );
54188
+ }
54189
+ function workerTsconfigJson() {
54190
+ return JSON.stringify(
54191
+ {
54192
+ compilerOptions: {
54193
+ target: "ES2022",
54194
+ module: "ES2022",
54195
+ moduleResolution: "bundler",
54196
+ strict: true,
54197
+ resolveJsonModule: true,
54198
+ types: ["@cloudflare/workers-types"]
54199
+ },
54200
+ include: ["src"]
54201
+ },
54202
+ null,
54203
+ 2
54204
+ );
54205
+ }
54206
+ function workerSetupSh(monorepoRoot) {
54207
+ return `#!/usr/bin/env bash
54208
+ # Generated by \`runtype deploy --target cloudflare\`.
54209
+ #
54210
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
54211
+ # installs all dependencies so you can run \`wrangler dev\` or deploy with
54212
+ # \`wrangler deploy\`.
54213
+ #
54214
+ # Usage (from this directory):
54215
+ # ./setup.sh
54216
+
54217
+ set -euo pipefail
54218
+
54219
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
54220
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
54221
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
54222
+
54223
+ echo "[setup] building @runtypelabs/runtime..."
54224
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
54225
+
54226
+ mkdir -p "\${TARBALL_DIR}"
54227
+
54228
+ echo "[setup] packing @runtypelabs/runtime tarball..."
54229
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
54230
+ --pack-destination "\${TARBALL_DIR}"
54231
+
54232
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
54233
+ if [ -z "\${RUNTIME_TGZ}" ]; then
54234
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
54235
+ exit 1
54236
+ fi
54237
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
54238
+
54239
+ echo "[setup] updating package.json with file: dependency..."
54240
+ node -e "
54241
+ const fs = require('fs');
54242
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
54243
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
54244
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
54245
+ "
54246
+
54247
+ echo "[setup] installing dependencies..."
54248
+ npm install --no-audit --no-fund
54249
+
54250
+ echo ""
54251
+ echo "[setup] Done! Next steps:"
54252
+ echo " wrangler dev \u2014 preview locally"
54253
+ echo " wrangler deploy \u2014 deploy to Cloudflare Workers"
54254
+ `;
54255
+ }
54256
+ function workerReadme(agentNames, secretNames) {
54257
+ const agentList = agentNames.map((n) => ` - ${n}`).join("\n");
54258
+ const secretList = secretNames.length > 0 ? secretNames.map((s) => ` wrangler secret put ${s}`).join("\n") : " (none detected)";
54259
+ return `# Runtype Cloudflare Workers Deployment
54260
+
54261
+ Generated by \`runtype deploy --target cloudflare\`. Contains the following agents:
54262
+
54263
+ ${agentList}
54264
+
54265
+ ## Setup
54266
+
54267
+ \`\`\`bash
54268
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
54269
+ \`\`\`
54270
+
54271
+ ## Secrets
54272
+
54273
+ Set each API key your agents use:
54274
+
54275
+ \`\`\`bash
54276
+ ${secretList}
54277
+ \`\`\`
54278
+
54279
+ ## Run locally
54280
+
54281
+ \`\`\`bash
54282
+ wrangler dev
54283
+ \`\`\`
54284
+
54285
+ ## Deploy
54286
+
54287
+ \`\`\`bash
54288
+ wrangler deploy
54289
+ \`\`\`
54290
+
54291
+ ## Health check
54292
+
54293
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
54294
+
54295
+ ## Call an agent
54296
+
54297
+ \`\`\`bash
54298
+ curl https://<worker-subdomain>.workers.dev/agent/<name> \\
54299
+ -H 'Content-Type: application/json' \\
54300
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
54301
+ \`\`\`
54302
+ `;
54303
+ }
54304
+ function vercelRouteTs(agentSlugs) {
54305
+ const imports = agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n");
54306
+ const agentFiles = agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n");
54307
+ return `/**
54308
+ * Generated by \`runtype deploy --target vercel\`.
54309
+ *
54310
+ * Catch-all Vercel Function. All traffic is rewritten here by vercel.json.
54311
+ * Routes:
54312
+ * GET /health \u2014 liveness probe (lists registered agent names)
54313
+ * POST /agent/:name \u2014 SSE stream of the agent event schema
54314
+ *
54315
+ * Agent definitions are statically imported and bundled by Vercel.
54316
+ * To add a new agent:
54317
+ * 1. Export it with \`runtype deploy --agent <id> --target vercel\`
54318
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
54319
+ * 3. Run \`vercel deploy\`
54320
+ */
54321
+ import { Hono } from 'hono'
54322
+ import { handle } from 'hono/vercel'
54323
+ import { cors } from 'hono/cors'
54324
+ import {
54325
+ VercelAdapter,
54326
+ createRuntime,
54327
+ exportedAgentSchema,
54328
+ type ExportedAgent,
54329
+ } from '@runtypelabs/runtime'
54330
+
54331
+ // ---------------------------------------------------------------------------
54332
+ // Agent registry \u2014 built at module load, cached across warm invocations
54333
+ // ---------------------------------------------------------------------------
54334
+
54335
+ ${imports}
54336
+
54337
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
54338
+ try {
54339
+ return exportedAgentSchema.parse(json)
54340
+ } catch (err) {
54341
+ throw new Error(
54342
+ \`[runtype] invalid agent JSON in \${filename}: \${
54343
+ err instanceof Error ? err.message : String(err)
54344
+ }\`
54345
+ )
54346
+ }
54347
+ }
54348
+
54349
+ const agentFiles: Array<[string, unknown]> = [
54350
+ ${agentFiles}
54351
+ ]
54352
+
54353
+ const agentRegistry: Record<string, ExportedAgent> = {}
54354
+ for (const [filename, json] of agentFiles) {
54355
+ const agent = parseAgent(json, filename)
54356
+ if (agentRegistry[agent.name]) {
54357
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
54358
+ }
54359
+ agentRegistry[agent.name] = agent
54360
+ }
54361
+
54362
+ // ---------------------------------------------------------------------------
54363
+ // Runtime \u2014 constructed at module scope (warm across invocations)
54364
+ // ---------------------------------------------------------------------------
54365
+
54366
+ const runtime = createRuntime({
54367
+ agents: agentRegistry,
54368
+ keys: { mode: 'own' },
54369
+ adapter: new VercelAdapter(),
54370
+ })
54371
+
54372
+ // ---------------------------------------------------------------------------
54373
+ // Hono app
54374
+ // ---------------------------------------------------------------------------
54375
+
54376
+ const app = new Hono()
54377
+
54378
+ const allowedOriginsEnv = process.env.ALLOWED_ORIGINS?.trim()
54379
+ const allowedOrigins = allowedOriginsEnv
54380
+ ? allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
54381
+ : '*'
54382
+
54383
+ app.use(
54384
+ '*',
54385
+ cors({
54386
+ origin: allowedOrigins,
54387
+ allowMethods: ['GET', 'POST', 'OPTIONS'],
54388
+ allowHeaders: ['Content-Type', 'Authorization'],
54389
+ maxAge: 86400,
54390
+ })
54391
+ )
54392
+
54393
+ app.get('/health', (c) => c.json({ status: 'ok', agents: Object.keys(agentRegistry) }))
54394
+
54395
+ app.post('/agent/:name', async (c) => {
54396
+ const name = c.req.param('name')
54397
+ if (!runtime.getAgent(name)) {
54398
+ return c.json({ error: \`unknown agent: \${name}\` }, 404)
54399
+ }
54400
+
54401
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
54402
+ try {
54403
+ body = (await c.req.json()) as typeof body
54404
+ } catch {
54405
+ return c.json({ error: 'request body must be JSON' }, 400)
54406
+ }
54407
+ const messages = Array.isArray(body.messages) ? body.messages : []
54408
+
54409
+ try {
54410
+ const response = await runtime.executeAgent(name, {
54411
+ messages: messages as Array<{
54412
+ role: 'system' | 'user' | 'assistant'
54413
+ content: string
54414
+ }>,
54415
+ signal: c.req.raw.signal,
54416
+ })
54417
+ return response
54418
+ } catch (err) {
54419
+ return c.json({ error: err instanceof Error ? err.message : 'unknown error' }, 500)
54420
+ }
54421
+ })
54422
+
54423
+ export const GET = handle(app)
54424
+ export const POST = handle(app)
54425
+ `;
54426
+ }
54427
+ function vercelJson() {
54428
+ return JSON.stringify(
54429
+ {
54430
+ rewrites: [{ source: "/(.*)", destination: "/api/[[...route]]" }],
54431
+ functions: {
54432
+ "api/[[...route]].ts": { maxDuration: 60 }
54433
+ }
54434
+ },
54435
+ null,
54436
+ 2
54437
+ );
54438
+ }
54439
+ function vercelPackageJson(name, runtimeDep) {
54440
+ return JSON.stringify(
54441
+ {
54442
+ name,
54443
+ version: "0.0.0",
54444
+ private: true,
54445
+ scripts: {
54446
+ dev: "vercel dev",
54447
+ deploy: "vercel deploy",
54448
+ typecheck: "tsc --noEmit"
54449
+ },
54450
+ dependencies: {
54451
+ "@runtypelabs/runtime": runtimeDep,
54452
+ hono: "^4.12.16"
54453
+ },
54454
+ devDependencies: {
54455
+ "@types/node": "^22.0.0",
54456
+ "@vercel/node": "^5.0.0",
54457
+ typescript: "^5.3.3",
54458
+ vercel: "^44.0.0"
54459
+ }
54460
+ },
54461
+ null,
54462
+ 2
54463
+ );
54464
+ }
54465
+ function vercelTsconfigJson() {
54466
+ return JSON.stringify(
54467
+ {
54468
+ compilerOptions: {
54469
+ target: "ES2022",
54470
+ module: "ESNext",
54471
+ moduleResolution: "bundler",
54472
+ strict: true,
54473
+ resolveJsonModule: true,
54474
+ esModuleInterop: true,
54475
+ skipLibCheck: true
54476
+ },
54477
+ include: ["api"]
54478
+ },
54479
+ null,
54480
+ 2
54481
+ );
54482
+ }
54483
+ function vercelSetupSh(monorepoRoot) {
54484
+ return `#!/usr/bin/env bash
54485
+ # Generated by \`runtype deploy --target vercel\`.
54486
+ #
54487
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
54488
+ # installs all dependencies so you can run \`vercel dev\` or deploy with
54489
+ # \`vercel deploy\`.
54490
+ #
54491
+ # Usage (from this directory):
54492
+ # ./setup.sh
54493
+
54494
+ set -euo pipefail
54495
+
54496
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
54497
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
54498
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
54499
+
54500
+ echo "[setup] building @runtypelabs/runtime..."
54501
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
54502
+
54503
+ mkdir -p "\${TARBALL_DIR}"
54504
+
54505
+ echo "[setup] packing @runtypelabs/runtime tarball..."
54506
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
54507
+ --pack-destination "\${TARBALL_DIR}"
54508
+
54509
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
54510
+ if [ -z "\${RUNTIME_TGZ}" ]; then
54511
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
54512
+ exit 1
54513
+ fi
54514
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
54515
+
54516
+ echo "[setup] updating package.json with file: dependency..."
54517
+ node -e "
54518
+ const fs = require('fs');
54519
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
54520
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
54521
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
54522
+ "
54523
+
54524
+ echo "[setup] installing dependencies..."
54525
+ npm install --no-audit --no-fund
54526
+
54527
+ echo ""
54528
+ echo "[setup] Done! Next steps:"
54529
+ echo " vercel dev \u2014 preview locally"
54530
+ echo " vercel deploy \u2014 deploy to Vercel Functions"
54531
+ `;
54532
+ }
54533
+ function vercelReadme(agentNames, secretNames) {
54534
+ const agentList = agentNames.map((n) => ` - ${n}`).join("\n");
54535
+ const secretList = secretNames.length > 0 ? secretNames.map((s) => ` vercel env add ${s}`).join("\n") : " (none detected)";
54536
+ return `# Runtype Vercel Functions Deployment
54537
+
54538
+ Generated by \`runtype deploy --target vercel\`. Contains the following agents:
54539
+
54540
+ ${agentList}
54541
+
54542
+ ## Setup
54543
+
54544
+ \`\`\`bash
54545
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
54546
+ \`\`\`
54547
+
54548
+ ## Secrets
54549
+
54550
+ Set each API key your agents use via the Vercel dashboard or CLI:
54551
+
54552
+ \`\`\`bash
54553
+ ${secretList}
54554
+ \`\`\`
54555
+
54556
+ ## Run locally
54557
+
54558
+ \`\`\`bash
54559
+ vercel dev
54560
+ \`\`\`
54561
+
54562
+ ## Deploy
54563
+
54564
+ \`\`\`bash
54565
+ vercel deploy
54566
+ \`\`\`
54567
+
54568
+ ## Health check
54569
+
54570
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
54571
+
54572
+ ## Call an agent
54573
+
54574
+ \`\`\`bash
54575
+ curl https://<your-project>.vercel.app/agent/<name> \\
54576
+ -H 'Content-Type: application/json' \\
54577
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
54578
+ \`\`\`
54579
+ `;
54580
+ }
53973
54581
  function collectSecretNames(agentDef) {
53974
54582
  const names = /* @__PURE__ */ new Set();
53975
54583
  const model = agentDef.config?.model?.toLowerCase();
@@ -53992,13 +54600,13 @@ function collectSecretNames(agentDef) {
53992
54600
  function slugify2(s) {
53993
54601
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
53994
54602
  }
53995
- var deployCommand = new Command24("deploy").description("Export an agent or flow and scaffold a Cloud Run deployment").option("--agent <id>", "Agent ID to export (may be repeated)", (v, acc) => {
54603
+ var deployCommand = new Command24("deploy").description("Export an agent or flow and scaffold a deployment (cloud-run, cloudflare, or vercel)").option("--agent <id>", "Agent ID to export (may be repeated)", (v, acc) => {
53996
54604
  acc.push(v);
53997
54605
  return acc;
53998
54606
  }, []).option("--flow <id>", "Flow ID to export (may be repeated)", (v, acc) => {
53999
54607
  acc.push(v);
54000
54608
  return acc;
54001
- }, []).option("--output <dir>", "Output directory for the scaffold (default: ./runtype-deploy)", "./runtype-deploy").option("--name <name>", "Project name used in package.json (default: derived from output dir)").action(
54609
+ }, []).option("--output <dir>", "Output directory for the scaffold (default: ./runtype-deploy)", "./runtype-deploy").option("--name <name>", "Project name used in package.json (default: derived from output dir)").option("--target <target>", "Deployment target: cloud-run, cloudflare, or vercel (default: cloud-run)", "cloud-run").action(
54002
54610
  async (options) => {
54003
54611
  const agentIds = options.agent;
54004
54612
  const flowIds = options.flow;
@@ -54006,13 +54614,18 @@ var deployCommand = new Command24("deploy").description("Export an agent or flow
54006
54614
  console.error(chalk30.red("Error: provide at least one --agent <id> or --flow <id>"));
54007
54615
  process.exit(1);
54008
54616
  }
54617
+ const target = options.target;
54618
+ if (target !== "cloud-run" && target !== "cloudflare" && target !== "vercel") {
54619
+ console.error(chalk30.red(`Error: unknown --target "${target}". Must be one of: cloud-run, cloudflare, vercel`));
54620
+ process.exit(1);
54621
+ }
54009
54622
  const apiKey = await ensureAuth();
54010
54623
  if (!apiKey) return;
54011
54624
  const client = new ApiClient(apiKey);
54012
54625
  const outDir = path14.resolve(options.output);
54013
54626
  const projectName = options.name ?? slugify2(path14.basename(outDir));
54014
54627
  console.log(chalk30.cyan(`
54015
- Scaffolding deployment to ${outDir}
54628
+ Scaffolding ${target} deployment to ${outDir}
54016
54629
  `));
54017
54630
  const agentDefs = [];
54018
54631
  for (const id of agentIds) {
@@ -54063,36 +54676,26 @@ Scaffolding deployment to ${outDir}
54063
54676
  process.exit(1);
54064
54677
  }
54065
54678
  }
54679
+ const agentSlugs = agentDefs.map(({ name }) => slugify2(name));
54680
+ const slugSet = /* @__PURE__ */ new Set();
54681
+ for (let i = 0; i < agentSlugs.length; i++) {
54682
+ const slug = agentSlugs[i];
54683
+ if (slugSet.has(slug)) {
54684
+ const colliders = agentDefs.filter(({ name }) => slugify2(name) === slug).map(({ name }) => `"${name}"`).join(" and ");
54685
+ console.error(chalk30.red(`Error: agents ${colliders} produce the same slug "${slug}".`));
54686
+ console.error(chalk30.yellow(" Rename one of them in the Runtype dashboard to make slugs unique."));
54687
+ process.exit(1);
54688
+ }
54689
+ slugSet.add(slug);
54690
+ }
54066
54691
  fs14.mkdirSync(path14.join(outDir, "agents"), { recursive: true });
54067
54692
  fs14.mkdirSync(path14.join(outDir, "packages"), { recursive: true });
54068
54693
  for (const { name, def } of agentDefs) {
54069
54694
  const filename = `${slugify2(name)}.json`;
54070
- fs14.writeFileSync(
54071
- path14.join(outDir, "agents", filename),
54072
- JSON.stringify(def, null, 2)
54073
- );
54695
+ fs14.writeFileSync(path14.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
54074
54696
  console.log(` Wrote agents/${filename}`);
54075
54697
  }
54076
- fs14.writeFileSync(
54077
- path14.join(outDir, "server.ts"),
54078
- serverTs()
54079
- );
54080
- fs14.writeFileSync(
54081
- path14.join(outDir, "Dockerfile"),
54082
- dockerfile()
54083
- );
54084
- fs14.writeFileSync(
54085
- path14.join(outDir, ".dockerignore"),
54086
- dockerignore()
54087
- );
54088
- fs14.writeFileSync(
54089
- path14.join(outDir, "tsconfig.json"),
54090
- tsconfigJson()
54091
- );
54092
- fs14.writeFileSync(
54093
- path14.join(outDir, "package.json"),
54094
- packageJson(projectName, "workspace:*")
54095
- );
54698
+ const agentNames = agentDefs.map((a) => a.name);
54096
54699
  let monorepoRoot = process.cwd();
54097
54700
  for (let i = 0; i < 8; i++) {
54098
54701
  if (fs14.existsSync(path14.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
@@ -54100,46 +54703,90 @@ Scaffolding deployment to ${outDir}
54100
54703
  if (parent === monorepoRoot) break;
54101
54704
  monorepoRoot = parent;
54102
54705
  }
54103
- const setupScript = setupSh(monorepoRoot);
54104
- const setupPath = path14.join(outDir, "setup.sh");
54105
- fs14.writeFileSync(setupPath, setupScript);
54106
- fs14.chmodSync(setupPath, 493);
54107
54706
  const allSecrets = /* @__PURE__ */ new Set();
54108
54707
  for (const { def } of agentDefs) {
54109
54708
  for (const s of collectSecretNames(def)) allSecrets.add(s);
54110
54709
  }
54111
54710
  const secretNames = Array.from(allSecrets);
54112
- fs14.writeFileSync(
54113
- path14.join(outDir, "README.md"),
54114
- readme(
54115
- agentDefs.map((a) => a.name),
54116
- secretNames
54117
- )
54118
- );
54711
+ if (target === "cloudflare") {
54712
+ fs14.mkdirSync(path14.join(outDir, "src"), { recursive: true });
54713
+ fs14.writeFileSync(path14.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
54714
+ fs14.writeFileSync(path14.join(outDir, "wrangler.toml"), wranglerToml(projectName));
54715
+ fs14.writeFileSync(path14.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
54716
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), workerTsconfigJson());
54717
+ const setupPath = path14.join(outDir, "setup.sh");
54718
+ fs14.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
54719
+ fs14.chmodSync(setupPath, 493);
54720
+ fs14.writeFileSync(path14.join(outDir, "README.md"), workerReadme(agentNames, secretNames));
54721
+ } else if (target === "vercel") {
54722
+ fs14.mkdirSync(path14.join(outDir, "api"), { recursive: true });
54723
+ fs14.writeFileSync(path14.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
54724
+ fs14.writeFileSync(path14.join(outDir, "vercel.json"), vercelJson());
54725
+ fs14.writeFileSync(path14.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
54726
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), vercelTsconfigJson());
54727
+ const setupPath = path14.join(outDir, "setup.sh");
54728
+ fs14.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
54729
+ fs14.chmodSync(setupPath, 493);
54730
+ fs14.writeFileSync(path14.join(outDir, "README.md"), vercelReadme(agentNames, secretNames));
54731
+ } else {
54732
+ fs14.writeFileSync(path14.join(outDir, "server.ts"), serverTs());
54733
+ fs14.writeFileSync(path14.join(outDir, "Dockerfile"), dockerfile());
54734
+ fs14.writeFileSync(path14.join(outDir, ".dockerignore"), dockerignore());
54735
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), tsconfigJson());
54736
+ fs14.writeFileSync(path14.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
54737
+ const setupPath = path14.join(outDir, "setup.sh");
54738
+ fs14.writeFileSync(setupPath, setupSh(monorepoRoot));
54739
+ fs14.chmodSync(setupPath, 493);
54740
+ fs14.writeFileSync(path14.join(outDir, "README.md"), readme(agentNames, secretNames));
54741
+ }
54119
54742
  console.log("");
54120
54743
  console.log(chalk30.green(`\u2713 Scaffold written to ${outDir}`));
54121
54744
  console.log("");
54122
54745
  console.log(chalk30.bold("Next steps:"));
54123
54746
  console.log(` cd ${options.output}`);
54124
54747
  console.log(" chmod +x setup.sh && ./setup.sh # pack runtime + install deps");
54125
- console.log(" npm run dev # test locally");
54126
- console.log("");
54127
- if (secretNames.length > 0) {
54128
- console.log(chalk30.yellow("Required secrets (set before deploying):"));
54129
- for (const s of secretNames) {
54130
- console.log(` ${chalk30.cyan(s)}`);
54748
+ if (target === "cloudflare") {
54749
+ console.log(" wrangler dev # test locally");
54750
+ console.log("");
54751
+ if (secretNames.length > 0) {
54752
+ console.log(chalk30.yellow("Required secrets (set via wrangler):"));
54753
+ for (const s of secretNames) {
54754
+ console.log(` wrangler secret put ${chalk30.cyan(s)}`);
54755
+ }
54756
+ console.log("");
54131
54757
  }
54758
+ console.log("Deploy to Cloudflare Workers:");
54759
+ console.log(" wrangler deploy");
54760
+ } else if (target === "vercel") {
54761
+ console.log(" vercel dev # test locally");
54132
54762
  console.log("");
54133
- console.log(" Set via Cloud Run: gcloud run deploy \u2026 --set-secrets KEY=NAME:latest");
54134
- console.log(" Or export locally: export KEY=<value>");
54763
+ if (secretNames.length > 0) {
54764
+ console.log(chalk30.yellow("Required secrets (set via Vercel):"));
54765
+ for (const s of secretNames) {
54766
+ console.log(` vercel env add ${chalk30.cyan(s)}`);
54767
+ }
54768
+ console.log("");
54769
+ }
54770
+ console.log("Deploy to Vercel Functions:");
54771
+ console.log(" vercel deploy");
54772
+ } else {
54773
+ console.log(" npm run dev # test locally");
54135
54774
  console.log("");
54775
+ if (secretNames.length > 0) {
54776
+ console.log(chalk30.yellow("Required secrets (set before deploying):"));
54777
+ for (const s of secretNames) {
54778
+ console.log(` ${chalk30.cyan(s)}`);
54779
+ }
54780
+ console.log("");
54781
+ console.log(" Set via Cloud Run: gcloud run deploy \u2026 --set-secrets KEY=NAME:latest");
54782
+ console.log(" Or export locally: export KEY=<value>");
54783
+ console.log("");
54784
+ }
54785
+ console.log("Deploy to Cloud Run:");
54786
+ console.log(` gcloud run deploy ${projectName} --source ${options.output} \\`);
54787
+ console.log(" --region us-central1 --platform managed \\");
54788
+ console.log(" --allow-unauthenticated --port 8080");
54136
54789
  }
54137
- console.log("Deploy to Cloud Run:");
54138
- console.log(
54139
- ` gcloud run deploy ${projectName} --source ${options.output} \\`
54140
- );
54141
- console.log(" --region us-central1 --platform managed \\");
54142
- console.log(" --allow-unauthenticated --port 8080");
54143
54790
  }
54144
54791
  );
54145
54792
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/cli",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "description": "Command-line interface for Runtype AI platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "micromatch": "^4.0.8",
22
22
  "yaml": "^2.8.3",
23
23
  "@runtypelabs/ink-components": "0.3.1",
24
- "@runtypelabs/sdk": "1.23.0",
24
+ "@runtypelabs/sdk": "1.24.0",
25
25
  "@runtypelabs/terminal-animations": "0.2.0"
26
26
  },
27
27
  "devDependencies": {