@runcofactor/cli 0.23.0 → 0.24.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/{account-HWN3W3DJ.js → account-LF73VDLD.js} +3 -3
- package/dist/{alert-2XQKWLXV.js → alert-YDCWKNLG.js} +4 -4
- package/dist/{api-catalog-3DNTXLUR.js → api-catalog-SUJ4DUAW.js} +3 -3
- package/dist/{attachment-EKI6FME3.js → attachment-ZOWURGCW.js} +3 -3
- package/dist/{augmentor-WVDFIJVN.js → augmentor-7JFSE344.js} +3 -3
- package/dist/{chunk-5XJARJVW.js → chunk-4O7KUIYE.js} +2 -2
- package/dist/{chunk-UAF5JTY6.js → chunk-EPMDUCEA.js} +2 -2
- package/dist/{chunk-C6GFGMMI.js → chunk-IC3HQSRW.js} +2 -2
- package/dist/{chunk-5H5LE7ZJ.js → chunk-LNX22N2L.js} +2 -2
- package/dist/{chunk-XFESXI7N.js → chunk-LQ5IBELB.js} +2 -2
- package/dist/{chunk-JWPQUDTG.js → chunk-OCT6HQZK.js} +2 -2
- package/dist/{chunk-JNBZKVZE.js → chunk-QX7RZ5CZ.js} +1 -1
- package/dist/chunk-QX7RZ5CZ.js.map +1 -0
- package/dist/{chunk-FZKJERAJ.js → chunk-SL5GDAWL.js} +2 -2
- package/dist/{chunk-AHVGWPO3.js → chunk-SZOPRIBA.js} +18 -2
- package/dist/chunk-SZOPRIBA.js.map +1 -0
- package/dist/{chunk-HXM3JXCX.js → chunk-TVM3VEAP.js} +2 -2
- package/dist/{chunk-2RGTNVYR.js → chunk-YI4LFJFK.js} +36 -11
- package/dist/{chunk-2RGTNVYR.js.map → chunk-YI4LFJFK.js.map} +1 -1
- package/dist/{credential-YCLLYSXW.js → credential-DAK26ZN5.js} +3 -3
- package/dist/{credential-template-ILUYGLUZ.js → credential-template-2S4IKKWH.js} +3 -3
- package/dist/{dist-FC2BFYMX.js → dist-MQMO6WA3.js} +3 -3
- package/dist/{docs-UGJPGZOR.js → docs-SX5RFWWF.js} +3 -3
- package/dist/{environment-TENLRRC3.js → environment-2OAJCTFK.js} +3 -3
- package/dist/{feature-flag-5EBZJD43.js → feature-flag-2HRNJ6Z6.js} +3 -3
- package/dist/index.js +36 -36
- package/dist/{init-YCS4OZFP.js → init-U2Z4ZJCC.js} +3 -3
- package/dist/{install-AVD4R5KA.js → install-VYXTJJVC.js} +3 -3
- package/dist/{issues-TRD5CQ4R.js → issues-QTX3YE5G.js} +3 -3
- package/dist/{knobs-DKAVOTMC.js → knobs-HMONFFHZ.js} +3 -3
- package/dist/{list-W3ONQTUH.js → list-46W2OU5D.js} +3 -3
- package/dist/{local-LLQFYBF3.js → local-4Y75MUYQ.js} +5 -5
- package/dist/{login-X72OFYMF.js → login-GCEIQ5FN.js} +3 -3
- package/dist/{mcp-KGMDCJJO.js → mcp-SLYY6DQL.js} +4 -4
- package/dist/{metrics-IRO7NK4L.js → metrics-3S4ZEA3B.js} +3 -3
- package/dist/{product-FAEOC2BC.js → product-2NW74HNE.js} +3 -3
- package/dist/{property-X6NPV2VK.js → property-E675NNFW.js} +3 -3
- package/dist/{record-CJIEUF2Z.js → record-HELPASIE.js} +4 -4
- package/dist/{release-PGLSKZCM.js → release-ZZIANCH7.js} +3 -3
- package/dist/{sandbox-PBRWVF4Y.js → sandbox-TTFAKM63.js} +82 -7
- package/dist/sandbox-TTFAKM63.js.map +1 -0
- package/dist/{scenarios-5TJ76A5P.js → scenarios-FWFSTCTV.js} +3 -3
- package/dist/{schedule-FRAVS3RH.js → schedule-QIXBH2HF.js} +4 -4
- package/dist/{session-E4PFQPBF.js → session-4RB4TRRU.js} +5 -5
- package/dist/skills/debug-run/SKILL.md +1 -1
- package/dist/{spec-ZNOLW4JR.js → spec-7LGJ4H5W.js} +3 -3
- package/dist/{src-OLQ7ZFHS.js → src-F4UX4PMP.js} +140 -8
- package/dist/src-F4UX4PMP.js.map +1 -0
- package/dist/{src-GEMLVCW4.js → src-FW7TIXEQ.js} +3 -3
- package/dist/{start-EV7J2TEF.js → start-IEUE53KY.js} +3 -3
- package/dist/{tag-UXKWRACA.js → tag-J5SX224K.js} +4 -4
- package/dist/{task-VDLJQKUB.js → task-ONZVZQAQ.js} +3 -3
- package/dist/{test-suite-SOM2MTNH.js → test-suite-STGE6IAV.js} +5 -5
- package/dist/{trace-DZCJO27U.js → trace-O5BVV7ZU.js} +4 -4
- package/dist/{triage-UQAB5HGG.js → triage-VS3S7GXN.js} +4 -4
- package/dist/{utilities-D7EYIATI.js → utilities-ARHQATQ6.js} +3 -3
- package/dist/{workflow-X7QNGJJ2.js → workflow-5FJ4D62D.js} +29 -32
- package/dist/workflow-5FJ4D62D.js.map +1 -0
- package/dist/{workflow-cache-ZMYUGEPB.js → workflow-cache-PDLY7XLW.js} +6 -6
- package/dist/{workflow-debug-LQKP27VI.js → workflow-debug-6IJQ5C3X.js} +6 -6
- package/dist/{workflow-proposals-NEXXWL3O.js → workflow-proposals-MTHYBJGV.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-AHVGWPO3.js.map +0 -1
- package/dist/chunk-JNBZKVZE.js.map +0 -1
- package/dist/sandbox-PBRWVF4Y.js.map +0 -1
- package/dist/src-OLQ7ZFHS.js.map +0 -1
- package/dist/workflow-X7QNGJJ2.js.map +0 -1
- /package/dist/{account-HWN3W3DJ.js.map → account-LF73VDLD.js.map} +0 -0
- /package/dist/{alert-2XQKWLXV.js.map → alert-YDCWKNLG.js.map} +0 -0
- /package/dist/{api-catalog-3DNTXLUR.js.map → api-catalog-SUJ4DUAW.js.map} +0 -0
- /package/dist/{attachment-EKI6FME3.js.map → attachment-ZOWURGCW.js.map} +0 -0
- /package/dist/{augmentor-WVDFIJVN.js.map → augmentor-7JFSE344.js.map} +0 -0
- /package/dist/{chunk-5XJARJVW.js.map → chunk-4O7KUIYE.js.map} +0 -0
- /package/dist/{chunk-UAF5JTY6.js.map → chunk-EPMDUCEA.js.map} +0 -0
- /package/dist/{chunk-C6GFGMMI.js.map → chunk-IC3HQSRW.js.map} +0 -0
- /package/dist/{chunk-5H5LE7ZJ.js.map → chunk-LNX22N2L.js.map} +0 -0
- /package/dist/{chunk-XFESXI7N.js.map → chunk-LQ5IBELB.js.map} +0 -0
- /package/dist/{chunk-JWPQUDTG.js.map → chunk-OCT6HQZK.js.map} +0 -0
- /package/dist/{chunk-FZKJERAJ.js.map → chunk-SL5GDAWL.js.map} +0 -0
- /package/dist/{chunk-HXM3JXCX.js.map → chunk-TVM3VEAP.js.map} +0 -0
- /package/dist/{credential-YCLLYSXW.js.map → credential-DAK26ZN5.js.map} +0 -0
- /package/dist/{credential-template-ILUYGLUZ.js.map → credential-template-2S4IKKWH.js.map} +0 -0
- /package/dist/{dist-FC2BFYMX.js.map → dist-MQMO6WA3.js.map} +0 -0
- /package/dist/{docs-UGJPGZOR.js.map → docs-SX5RFWWF.js.map} +0 -0
- /package/dist/{environment-TENLRRC3.js.map → environment-2OAJCTFK.js.map} +0 -0
- /package/dist/{feature-flag-5EBZJD43.js.map → feature-flag-2HRNJ6Z6.js.map} +0 -0
- /package/dist/{init-YCS4OZFP.js.map → init-U2Z4ZJCC.js.map} +0 -0
- /package/dist/{install-AVD4R5KA.js.map → install-VYXTJJVC.js.map} +0 -0
- /package/dist/{issues-TRD5CQ4R.js.map → issues-QTX3YE5G.js.map} +0 -0
- /package/dist/{knobs-DKAVOTMC.js.map → knobs-HMONFFHZ.js.map} +0 -0
- /package/dist/{list-W3ONQTUH.js.map → list-46W2OU5D.js.map} +0 -0
- /package/dist/{local-LLQFYBF3.js.map → local-4Y75MUYQ.js.map} +0 -0
- /package/dist/{login-X72OFYMF.js.map → login-GCEIQ5FN.js.map} +0 -0
- /package/dist/{mcp-KGMDCJJO.js.map → mcp-SLYY6DQL.js.map} +0 -0
- /package/dist/{metrics-IRO7NK4L.js.map → metrics-3S4ZEA3B.js.map} +0 -0
- /package/dist/{product-FAEOC2BC.js.map → product-2NW74HNE.js.map} +0 -0
- /package/dist/{property-X6NPV2VK.js.map → property-E675NNFW.js.map} +0 -0
- /package/dist/{record-CJIEUF2Z.js.map → record-HELPASIE.js.map} +0 -0
- /package/dist/{release-PGLSKZCM.js.map → release-ZZIANCH7.js.map} +0 -0
- /package/dist/{scenarios-5TJ76A5P.js.map → scenarios-FWFSTCTV.js.map} +0 -0
- /package/dist/{schedule-FRAVS3RH.js.map → schedule-QIXBH2HF.js.map} +0 -0
- /package/dist/{session-E4PFQPBF.js.map → session-4RB4TRRU.js.map} +0 -0
- /package/dist/{spec-ZNOLW4JR.js.map → spec-7LGJ4H5W.js.map} +0 -0
- /package/dist/{src-GEMLVCW4.js.map → src-FW7TIXEQ.js.map} +0 -0
- /package/dist/{start-EV7J2TEF.js.map → start-IEUE53KY.js.map} +0 -0
- /package/dist/{tag-UXKWRACA.js.map → tag-J5SX224K.js.map} +0 -0
- /package/dist/{task-VDLJQKUB.js.map → task-ONZVZQAQ.js.map} +0 -0
- /package/dist/{test-suite-SOM2MTNH.js.map → test-suite-STGE6IAV.js.map} +0 -0
- /package/dist/{trace-DZCJO27U.js.map → trace-O5BVV7ZU.js.map} +0 -0
- /package/dist/{triage-UQAB5HGG.js.map → triage-VS3S7GXN.js.map} +0 -0
- /package/dist/{utilities-D7EYIATI.js.map → utilities-ARHQATQ6.js.map} +0 -0
- /package/dist/{workflow-cache-ZMYUGEPB.js.map → workflow-cache-PDLY7XLW.js.map} +0 -0
- /package/dist/{workflow-debug-LQKP27VI.js.map → workflow-debug-6IJQ5C3X.js.map} +0 -0
- /package/dist/{workflow-proposals-NEXXWL3O.js.map → workflow-proposals-MTHYBJGV.js.map} +0 -0
|
@@ -1211,17 +1211,28 @@ WHAT THE PLATFORM DOES FOR YOU (no action needed)
|
|
|
1211
1211
|
|
|
1212
1212
|
WHAT YOU DECLARE \u2014 cache labels on one-shot build/dep services
|
|
1213
1213
|
Same convention as the sandbox.route/sandbox.port labels. The platform snapshots
|
|
1214
|
-
the declared volumes/paths at verify and restores them at provision
|
|
1215
|
-
|
|
1216
|
-
|
|
1214
|
+
the declared volumes/paths at verify and restores them at provision. How a HIT is
|
|
1215
|
+
reused depends on the service's REUSE MODE (auto-detected, override with
|
|
1216
|
+
sandbox.cache-reuse):
|
|
1217
|
+
- prime (dependency installs \u2014 a service caching a named VOLUME): the restore
|
|
1218
|
+
WARMS the install, but the command STILL RUNS. This is required: a package
|
|
1219
|
+
manager in a workspace monorepo writes a node_modules into EVERY package
|
|
1220
|
+
(apps/web/node_modules, packages/*/node_modules) that lives in the workspace,
|
|
1221
|
+
not in the one cached volume \u2014 so skipping the install on a hit leaves the tree
|
|
1222
|
+
incomplete and the app crashes. Re-running install warm rebuilds the COMPLETE
|
|
1223
|
+
tree. It is fast: the platform auto-provisions and caches the package manager's
|
|
1224
|
+
global store for you (see below).
|
|
1225
|
+
- skip (build outputs \u2014 a service caching only PATHS): the cache fully captures
|
|
1226
|
+
the output, so on a hit a marker file makes the one-shot a no-op (skips the
|
|
1227
|
+
rebuild). A miss runs cold and the next verify re-captures.
|
|
1217
1228
|
|
|
1218
1229
|
services:
|
|
1219
|
-
deps: # dependency install \u2192 cache the volume
|
|
1230
|
+
deps: # dependency install \u2192 cache the volume (prime)
|
|
1220
1231
|
labels:
|
|
1221
1232
|
sandbox.cache: "true"
|
|
1222
1233
|
sandbox.cache-key: "lockfiles" # invalidate when a lockfile changes
|
|
1223
1234
|
sandbox.cache-volumes: "node_modules" # named volume(s) to snapshot
|
|
1224
|
-
web-build: # production build \u2192 cache the output dirs
|
|
1235
|
+
web-build: # production build \u2192 cache the output dirs (skip)
|
|
1225
1236
|
labels:
|
|
1226
1237
|
sandbox.cache: "true"
|
|
1227
1238
|
sandbox.cache-key: "inputs"
|
|
@@ -1245,6 +1256,10 @@ WHAT YOU DECLARE \u2014 cache labels on one-shot build/dep services
|
|
|
1245
1256
|
sandbox.cache-volumes Comma-separated named volumes to snapshot/restore.
|
|
1246
1257
|
sandbox.cache-paths Comma-separated workspace-relative output dirs to
|
|
1247
1258
|
snapshot/restore.
|
|
1259
|
+
sandbox.cache-reuse "prime" | "skip". Optional \u2014 defaults to prime for a
|
|
1260
|
+
service that caches a volume (install) and skip for a
|
|
1261
|
+
paths-only service (build output). Only override if
|
|
1262
|
+
the auto-detection is wrong for your service.
|
|
1248
1263
|
All path/volume values must be plain, relative, traversal-free names \u2014 absolute
|
|
1249
1264
|
paths, ".."/empty segments, or shell metacharacters are rejected (fail-closed).
|
|
1250
1265
|
|
|
@@ -1252,11 +1267,21 @@ WHAT YOU DECLARE \u2014 cache labels on one-shot build/dep services
|
|
|
1252
1267
|
unrelated push (an api-only or docs commit) and the cache would never hit. Key on
|
|
1253
1268
|
the build's actual INPUTS.
|
|
1254
1269
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1270
|
+
PACKAGE-MANAGER STORE IS AUTOMATIC (do not configure it)
|
|
1271
|
+
For a prime install service the platform sets the cache/store env vars for every
|
|
1272
|
+
common package manager (bun, npm, yarn, pnpm) to a shared, cached directory
|
|
1273
|
+
co-located with node_modules, and snapshots it. So the warm re-run of your install
|
|
1274
|
+
is offline and fast for ANY package manager with nothing to declare. Do NOT set
|
|
1275
|
+
BUN_INSTALL_CACHE_DIR / npm_config_cache / YARN_CACHE_FOLDER / store-dir yourself \u2014
|
|
1276
|
+
the platform owns them. Just label the install service sandbox.cache: "true".
|
|
1277
|
+
|
|
1278
|
+
KEEP A SKIP SERVICE'S COMMAND ON ONE LINE
|
|
1279
|
+
For a skip (build-output) service the platform wraps its \`command:\` in a marker
|
|
1280
|
+
gate so a hit skips the build. It rewrites a single-line command \u2014 scalar
|
|
1281
|
+
(\`command: bun run build\`) or flow array (\`command: ["bash","-c","..."]\`). A
|
|
1282
|
+
multi-line block command is left ungated (still gets capture/restore but always
|
|
1283
|
+
runs). Keep it one line. (Prime install commands always run, so this does not
|
|
1284
|
+
apply to them.)
|
|
1260
1285
|
|
|
1261
1286
|
INSTANCE-INDEPENDENCE (required for cached build outputs)
|
|
1262
1287
|
A cached \`dist\` is reused across DIFFERENT sandboxes, so it must not bake any
|
|
@@ -1288,4 +1313,4 @@ export {
|
|
|
1288
1313
|
printGuide,
|
|
1289
1314
|
SANDBOX_GUIDE_SECTIONS
|
|
1290
1315
|
};
|
|
1291
|
-
//# sourceMappingURL=chunk-
|
|
1316
|
+
//# sourceMappingURL=chunk-YI4LFJFK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../content/src/print-guide.ts","../../content/src/sandbox-guide.ts"],"sourcesContent":["import type { GuideSection } from './types.js';\n\nexport function printGuide(\n allSections: GuideSection[],\n section?: string,\n json?: boolean,\n): void {\n const sections = section\n ? allSections.filter((s) => s.slug === section)\n : allSections;\n\n if (section && sections.length === 0) {\n console.error(\n `Unknown guide section: \"${section}\". Available: ${allSections.map((s) => s.slug).join(', ')}`,\n );\n process.exit(1);\n }\n\n if (json) {\n console.log(JSON.stringify(sections, null, 2));\n return;\n }\n\n for (const s of sections) {\n console.log(`\\n${'='.repeat(72)}`);\n console.log(` ${s.title}`);\n console.log(`${'='.repeat(72)}\\n`);\n console.log(s.content);\n console.log('');\n }\n}\n","// Sandbox authoring guide — printed by `canary sandbox guide [section]`\n// and also rendered (in part) in the web UI on the sandbox detail page.\n\nimport type { GuideSection } from './types.js';\n\nconst GOAL: GuideSection = {\n title: 'Goal & Tools',\n slug: 'goal',\n content: `\nYour goal is to turn a git repository into a running, containerized development\nenvironment. The end state is a sandbox URL that loads the app in a browser.\n\nWORKFLOW:\n 1. canary sandbox create <property> Create a template + authoring instance\n 2. Research the app Read the codebase to understand what it needs\n 3. Set config values API keys, secrets → pushed to instance as .canary/.env\n 4. Upload artifacts Dockerfile, docker-compose, setup script, etc. → pushed to instance\n 5. canary sandbox build <instance-id> Build images, start containers, generate nginx\n 6. Check diagnostics & logs Verify the app is running\n 7. Fix artifacts, re-upload, re-build Iterate until the app loads in browser\n 8. Mark checklist items complete Record what you did\n\nEach artifact upload and config set immediately pushes the file to the instance.\nThe \"build\" command triggers the actual rebuild and restart.\nBatch your changes (upload all artifacts, set all config), then build once.\n\nYOUR TOOLS:\n\n Config — set environment variables (pushed to instance .canary/.env immediately):\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n\n SANDBOX_URL is auto-injected — use it in compose for browser-facing URLs.\n\n Artifacts — files that define the sandbox (pushed to instance on upload):\n canary sandbox artifact create <template-id> <type> <file>\n canary sandbox artifact get <template-id> <type>\n\n Types: dockerfile, docker-compose, setup-script, startup-script, seed-script, file\n\n Build — rebuild and start services:\n canary sandbox build <instance-id>\n canary sandbox build <instance-id> --no-wait\n\n Generates nginx routing, injects Docker networking, writes file artifacts,\n runs setup script, then runs compose up, then runs startup script.\n Call this after uploading artifacts.\n\n First builds on large projects may exceed gateway timeouts. The CLI handles\n this automatically — it detects the timeout and polls diagnostics until\n containers are healthy. Use --no-wait to fire-and-forget and poll manually.\n\n Diagnostics & logs — check container health and read service output:\n canary sandbox diagnostics <instance-id>\n canary sandbox logs <instance-id> --service <name>\n\n Run-command — escape hatch for debugging and one-off commands:\n canary sandbox run-command <instance-id> \"<command>\"\n\n Checklist — record your progress:\n canary sandbox checklist <template-id>\n canary sandbox checklist complete <template-id> <item-id> [options]\n canary sandbox checklist skip <template-id> <item-id>\n\nPRINCIPLES:\n - Artifacts are the source of truth. Every fix must update the artifact file and re-upload.\n - Never patch source files. Use Docker env vars, CMD overrides, setup/startup scripts.\n - Code is volume-mounted at /workspace, not baked into the image.\n - The platform handles nginx routing and Docker networking — you don't need to.\n`.trim(),\n};\n\nconst RESEARCH: GuideSection = {\n title: 'Research the App',\n slug: 'research',\n content: `\nBefore writing any artifacts, research the application thoroughly.\n\n1. ENV VAR VALIDATION\n Find the app's environment validation schema (Zod, Joi, dotenv, etc.).\n List ALL required (non-optional) variables. Many apps fail fast on missing vars.\n\n Common locations:\n - src/env.ts, src/config.ts, src/environment.ts\n - .env.example, .env.sample\n\n Classify each var:\n - AUTO-WIRED: Internal service URLs (DATABASE_URL, REDIS_URL) — hardcode in compose\n - PLACEHOLDER: OAuth creds, S3 buckets — use dummy values, app boots without them\n - SENSITIVE: API keys (ANTHROPIC_API_KEY) — goes in template configSchema\n - DISABLED: Worker flags, schedulers — set to false in compose\n\n2. REQUIRED SERVICES\n What databases, caches, queues does the app need?\n Check: docker-compose.yml, package.json deps, infra/ configs, README\n Common: PostgreSQL, Redis, MongoDB, RabbitMQ, Elasticsearch\n\n3. MIGRATION STRATEGY\n Does the migrate script depend on cloud tooling (SST, AWS SDK, Terraform)?\n If yes, use the ORM's CLI directly in the startup script via \"sb-compose exec\":\n - Drizzle: sb-compose exec -T app-api bash -c 'cd /app/packages/db && bun run drizzle-kit migrate'\n - Prisma: sb-compose exec -T app-api bash -c 'bunx prisma migrate deploy'\n - Rails: sb-compose exec -T app-api rails db:migrate\n - Django: sb-compose exec -T app-api python manage.py migrate\n Put migrations in the startup script, NOT as a compose service (see Artifacts).\n Do NOT use bunx for JS migrations — it downloads a fresh copy. Use \"bun run\" instead.\n\n4. PACKAGE BUILD ORDER (monorepos)\n Which workspace packages export from dist/ and must be pre-built?\n Check package.json \"main\" or \"exports\" fields for \"./dist/\" references.\n These builds go in the startup script via sb-compose exec, NOT the Dockerfile.\n When running scripts via sb-compose exec, cd to the package directory\n where the dependency is declared. Bun workspace module resolution won't\n find deps from the monorepo root.\n\n5. PRODUCTION BUILD STRATEGY (frontends)\n Sandboxes serve a PRODUCTION build of the frontend, not a dev server.\n This is faster and far more responsive, ships compressed/hashed/cacheable\n assets, and sidesteps dev-server host-allowlist headaches entirely (a static\n file server has no notion of \"allowed hosts\"). HMR / hot-reload is\n intentionally dropped — a sandbox demos and QAs a BUILT app; it is not a\n live-edit environment.\n\n Identify, from package.json scripts and the framework:\n - The production build command (vite build, next build, ng build,\n webpack --mode production, etc.) and its output directory\n (commonly dist/, build/, or .next/).\n - Whether the framework produces a STATIC bundle (Vite, CRA, Angular,\n plain Webpack → a dist/ of static files) or needs a NODE SERVER at\n runtime (Next.js server mode, Remix, Nuxt SSR). Static bundles are\n served by nginx; server-rendered apps run their production start\n command (next start, etc.) as a normal routed service.\n\n BUILD-TIME ENV VARS:\n Bundlers BAKE build-time public vars into the output, so they must be set\n in the environment BEFORE the build runs — setting them only in compose\n at runtime is too late for a static bundle. Find every build-time-public\n var (VITE_*, NEXT_PUBLIC_*, REACT_APP_*, ng-injected config, etc.),\n especially the browser-facing API base URL, and set them just before the\n build using \\${SANDBOX_URL} for any URL the browser must reach\n (e.g. VITE_API_URL=\\${SANDBOX_URL}/api). See Artifacts → PRODUCTION BUILD\n for where the build runs.\n\n COST: production builds are slower and more memory-hungry than starting a\n dev server. Vite/webpack builds can OOM the default Node heap on large\n apps — be ready to raise it (NODE_OPTIONS=--max-old-space-size=8192) and\n expect the build to add minutes to the build step.\n\n6. WORKERS & DAEMONS TO DISABLE\n Sandbox typically only needs the API server + web frontend.\n Disable: job runners, schedulers, stall detectors, cron, coordinators.\n Look for env flags like WORKER_ENABLED, SCHEDULER_ENABLED, etc.\n`.trim(),\n};\n\nconst ARTIFACTS: GuideSection = {\n title: 'Artifacts',\n slug: 'artifacts',\n content: `\nArtifacts are the files that define your sandbox. Write them locally, then upload:\n canary sandbox artifact create <template-id> <type> <local-file>\n\nIMPORTANT — ARTIFACT SCOPE:\n Artifact uploads are TEMPLATE-level, not instance-level. Uploading an artifact\n updates the template definition and pushes the file to the AUTHORING instance\n only. Running instances provisioned from the template are NOT updated — they\n use whatever was baked in at provision time. To apply artifact changes to an\n existing non-authoring instance, you must rebuild it:\n canary sandbox build <instance-id>\n\nPLATFORM ARCHITECTURE:\n The sandbox filesystem is split into two areas:\n - /workspace checked-out repo and user-owned files\n - /workspace/.canary platform-managed compose, config, and helper scripts\n\n Your compose file is saved as /workspace/.canary/docker-compose.override.yml and\n merged with the platform's base compose at /workspace/.canary/docker-compose.yml,\n which provides:\n - nginx-proxy: reverse proxy on port 80 (routes based on sandbox.route labels)\n - sandbox-agent: platform agent on port 8080\n - minio: S3-compatible object store at http://minio:9000 (creds:\n minioadmin / minioadmin). See MINIO BUCKET below — the platform\n doesn't auto-wire the endpoint env var or create buckets for you.\n\n You do NOT need to declare any of these in your docker-compose.yml — the\n platform injects them. Defining a service with one of these reserved\n names is rejected at artifact-upload time with code\n SANDBOX_RESERVED_SERVICE_NAME. The full reserved list is:\n nginx-proxy\n sandbox-agent\n minio\n minio-init\n docker-socket-proxy\n\n CONTAINER PATHS — the bind mount maps the repo root to /app inside\n containers, while file artifacts are uploaded under /workspace/.canary\n on the host. So the same file has two paths:\n /workspace/.canary/foo.ts ← the path you upload to from the CLI\n /app/.canary/foo.ts ← the path your compose command/services\n reference inside the container\n Reference the in-container path in any compose \\`command\\` array, exec\n call, or script file path that runs inside a service. The host-side\n /workspace path only appears in startup.sh / setup.sh (which run on\n the host, not inside a container).\n\n MINIO BUCKET (S3 emulation):\n The platform provides a MinIO container at http://minio:9000 but does\n NOT inject AWS_ENDPOINT_URL_S3 or create your bucket. You must:\n\n 1. Wire the endpoint into your service env in compose:\n AWS_ENDPOINT_URL_S3: http://minio:9000\n AWS_ACCESS_KEY_ID: minioadmin\n AWS_SECRET_ACCESS_KEY: minioadmin\n AWS_REGION: us-east-1\n S3_BUCKET_NAME: <your-bucket>\n\n 2. Create the bucket on every build via a one-shot service:\n mc-init:\n image: minio/mc\n restart: \"no\"\n entrypoint:\n - sh\n - -c\n - >\n mc alias set local http://minio:9000 minioadmin minioadmin &&\n mc mb -p local/\\${S3_BUCKET_NAME:-app-bucket} ||\n true\n\n Most AWS SDK v3 clients honor AWS_ENDPOINT_URL_S3 natively. If your\n app passes a custom S3 client, also set forcePathStyle: true (MinIO\n doesn't support virtual-host-style bucket addressing).\n\n Compose still runs with /workspace as the project directory. That means user\n compose content keeps the same repo-root-relative behavior for build contexts,\n bind mounts, and referenced files.\n\n COMPOSE INVOCATION: use the pre-installed \"sb-compose\" wrapper on the host.\n It expands to docker compose with the canonical flag set:\n docker compose --project-directory /workspace \\\\\n --env-file /workspace/.canary/.env \\\\\n -f /workspace/.canary/docker-compose.yml \\\\\n -f /workspace/.canary/docker-compose.override.yml \\\\\n \"$@\"\n Call it from startup scripts, action scripts, and run-command the same way:\n sb-compose exec -T app-api bash -c 'bun run build'\n sb-compose ps\n sb-compose logs --tail 50 app-api\n The wrapper is available as /usr/local/bin/sb-compose (symlink) and\n /workspace/.canary/sb-compose. If you invoke raw \"docker compose\" yourself\n you must pass the same flags — otherwise paths and variable interpolation\n (\\${SANDBOX_URL} et al.) will resolve incorrectly.\n\n The platform injects a shared Docker network. On \"build\", the sequence is:\n 1. Write file artifacts to their destination paths\n 2. Run setup script (if present)\n 3. docker compose down (cleanup previous containers)\n 4. docker compose build + up\n 5. Reload nginx\n 6. Wait for long-running services to accept \"sb-compose exec\" before continuing\n 7. Run startup script (if present)\n\n Step 6 is automatic. You don't need to add your own \"wait for postgres\"\n loops at the top of startup.sh — by the time startup.sh runs, every\n non-one-shot service is exec-able. Service healthchecks inside the\n containers (e.g. postgres pg_isready) are still your responsibility if\n your app needs to wait on them.\n\n Do NOT define services named \"nginx-proxy\", \"sandbox-agent\", or \"minio\"\n in your compose.\n\nDOCKERFILE (type: dockerfile)\n A base image that provides the runtime. Source code is volume-mounted, NOT copied in.\n Written to /workspace/.canary/Dockerfile.sandbox (avoids conflicting with the repo's own Dockerfile).\n Reference it in compose as: build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n\n Structure:\n FROM <runtime-base>\n RUN apt-get update && apt-get install -y <system-deps>\n RUN npm install -g <global-tools>\n WORKDIR /app\n\n Rules:\n - NO \"COPY . .\" — code comes via volume mount at /workspace\n - NO \"RUN npm build\" — the production build runs on the instance as a\n compose service (see PRODUCTION BUILD), where \\${SANDBOX_URL} and\n build-time env are available. Baking it into the image would freeze\n build-time env to image-build values.\n - Include ALL system deps (chromium, ffmpeg, python, etc.)\n - For browser automation: use mcr.microsoft.com/playwright as base\n - Do NOT use ENTRYPOINT — it breaks one-shot services (deps, migrate) whose\n command gets passed as args to the entrypoint instead of running directly\n\nDOCKER COMPOSE (type: docker-compose)\n Defines all services. Platform handles networking and nginx routing.\n\n Typical services:\n deps (one-shot) — installs dependencies into named volume, restart: \"no\"\n database — official image with healthcheck (postgres, mysql, etc.)\n cache — redis/memcached with healthcheck\n web-build (one-shot) — runs the production frontend build into the bind-mounted\n repo tree, restart: \"no\" (see PRODUCTION BUILD)\n app-api — application server, PRODUCTION command (NODE_ENV=production,\n no --hot / --watch)\n app-web — static frontend served by nginx:alpine from the build\n output (see PRODUCTION BUILD). SSR frameworks (Next.js\n server mode, etc.) run their production start command\n instead of an nginx static server.\n\n Do NOT use a migrate service in compose. If the migrate container fails once\n (bad SQL, missing env var, wrong path), depends_on with\n service_completed_successfully is permanently unmet — app-api will never\n start, even after you fix the migration. \"docker compose restart\" and\n \"docker compose up\" won't help; only \"docker compose down\" + fresh up works.\n Put migrations in the startup script instead (runs after compose up).\n\n ROUTING LABELS — declare how nginx should route to your services:\n services:\n app-api:\n labels:\n sandbox.route: \"/api\"\n sandbox.port: \"3000\"\n app-web:\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n The platform reads these labels and generates nginx config on \"build\".\n If no labels, all traffic routes to the first non-infrastructure service.\n\n PREFIX STRIPPING: the generated nginx location uses a trailing-slash\n proxy_pass (proxy_pass http://<service>:<port>/), so the matched prefix\n is stripped from the upstream request. With sandbox.route: \"/api\", a\n browser hit to \"/api/health\" is delivered to the upstream as \"/health\".\n Pick VITE_API_URL, OAuth redirect URIs, etc. accordingly.\n\n ENV VAR STRATEGY — use \\${VAR} syntax, values come from config + .env:\n - Auto-wired (hardcode): DATABASE_URL=postgres://user:pass@postgres:5432/db\n - From config: ANTHROPIC_API_KEY=\\${ANTHROPIC_API_KEY}\n - Platform-injected: VITE_API_URL=\\${SANDBOX_URL}/api\n - Disabled: WORKER_ENABLED=false\n\n SANDBOX_URL is auto-injected by the platform (e.g. https://<id>.env.runcofactor.com).\n Use it for any URL a browser needs to reach the sandbox.\n\n Rules:\n - Use named volumes for data persistence (postgres-data, redis-data, etc.)\n - Bind-mount the repo (.:/app) rather than using named volumes for\n node_modules. Per-package node_modules named volumes silently shadow the\n bind mount on any service that doesn't mount them — a common monorepo\n trap where \"bun run build\" fails because tsup is nowhere to be found.\n The sandbox host is Linux EC2; bind mounts are fast.\n - DO NOT define networks — the platform handles networking\n - DO NOT expose ports to the host — nginx handles routing\n - Keep relative paths repo-root-relative; compose runs from /workspace even\n though the platform-managed compose files live in /workspace/.canary\n - Escape shell variables in \"command:\" strings with $$ so compose leaves\n them for the shell (e.g. command: [\"bash\", \"-c\", \"exec foo $$BAR\"]).\n Unescaped \\${VAR} is substituted at compose-time from the env file.\n\nPRODUCTION BUILD (static frontends)\n Serve the frontend as a production build, not a dev server. Two pieces:\n\n 1. BUILD as a one-shot compose service — in its OWN container, NOT inside the\n api/app container. A production build is CPU + disk-I/O heavy; if it runs\n inside the api container it starves the api's /health endpoint and the\n provision health gate fails the whole sandbox (nginx 502). The setup script\n can't do it either (it runs on the HOST, where app runtimes don't exist, and\n before deps installs). So: a dedicated one-shot service that depends on deps.\n\n Build the RIGHT set of packages. In a monorepo, build EVERY workspace\n package that exports from dist/ that is imported by BOTH the frontend AND\n the BACKEND — not just the UI. If the api imports a dist-exporting package\n whose dist is missing, the api crash-loops \"Cannot find module @scope/pkg\"\n and never answers /health. Find them: grep the api's imports for @scope/*\n and cross-check each package.json \"main\"/\"exports\" for \"./dist/\". Build the\n leaf packages first, then the app:\n web-build:\n build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n working_dir: /app\n volumes:\n - .:/app\n environment:\n VITE_API_URL: \\${SANDBOX_URL}/api # build-time public vars FIRST\n NODE_OPTIONS: --max-old-space-size=8192 # raise heap if the build OOMs\n # nice -n 19 keeps the api responsive if anything overlaps. Build the\n # api's dist-dependencies AND the ui, then the app bundle.\n command: [\"bash\",\"-c\",\"nice -n 19 bash -c 'cd /app/packages/foo && bun run build && cd /app/packages/bar && bun run build && cd /app/apps/web && bun run build'\"]\n depends_on:\n deps:\n condition: service_completed_successfully\n restart: \"no\"\n Compose interpolates \\${SANDBOX_URL} at \"up\" time, so the bundle bakes the\n correct browser-facing API URL. The output (dist/) lands in the\n bind-mounted repo tree, where the nginx service below mounts it.\n\n CACHEABILITY: if you want this build cached across sandboxes (see Build\n Caching), the output must NOT bake \\${SANDBOX_URL} — a cached dist is reused on\n other sandboxes. Resolve the API origin at runtime from the browser location\n (web and api are same-origin behind the proxy, so a relative \"/api\" works\n everywhere) instead of baking VITE_API_URL. Then label web-build with\n sandbox.cache + sandbox.cache-inputs/paths.\n\n SERIALIZE the build BEFORE the health gate: give the nginx service a\n depends_on web-build (service_completed_successfully). That makes\n \"docker compose up\" block until the build finishes, so the gate only probes\n the api once the host is idle. Restart the api in the STARTUP script\n afterwards so it boots onto the idle host with all dist packages present.\n\n 2. SERVE the output with a lightweight nginx:alpine service that depends on\n the build completing. Mount the PARENT of the output dir (e.g. apps/web),\n NOT the dist dir directly, and point nginx root at the dist SUBPATH:\n app-web:\n image: nginx:alpine\n volumes:\n - ./apps/web:/srv:ro # mount the PARENT, root is /srv/dist\n - ./.canary/nginx-spa.conf:/etc/nginx/conf.d/default.conf:ro\n depends_on:\n web-build:\n condition: service_completed_successfully\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n WHY mount the parent: bundlers (vite, webpack) DELETE and recreate the\n output dir on build, giving it a NEW inode. Docker creates an empty\n dist bind-source at compose-up time; a direct \"dist:/usr/share/nginx/html\"\n bind pins nginx to that stale empty inode and it serves 403 \"directory\n index forbidden\" forever. Mounting the parent (a stable inode) and letting\n nginx resolve root/<uri> per request always sees the current dist.\n\n Provide an SPA-fallback nginx config as a file artifact so client-side\n routes (deep links / hard refresh) don't 404 — unknown paths serve\n index.html and the client router takes over:\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n nginx-spa.conf:\n server {\n listen 80;\n root /srv/dist; # the dist SUBPATH of the mounted parent\n index index.html;\n gzip on;\n gzip_types text/css application/javascript application/json image/svg+xml;\n location /assets/ {\n try_files $uri =404;\n expires 1y;\n add_header Cache-Control \"public, immutable\";\n }\n location / {\n try_files $uri $uri/ /index.html;\n add_header Cache-Control \"no-cache\";\n }\n }\n\n (Match root to your framework's output dir under the mounted parent — e.g.\n /srv/build for a build/ output. The hashed-asset location block assumes a\n /assets/ dir — adjust to your bundler's asset path.)\n\n WHY: nginx serving static files has no host allowlist (no \"This host is not\n allowed\"), ships compressed/hashed/cacheable assets, and is far more\n responsive than a dev server streaming hundreds of uncompressed modules. The\n trade-off is no hot-reload and a longer, heavier build step — acceptable\n because sandboxes demo a BUILT app.\n\n SSR / server-rendered frameworks (Next.js server mode, Remix, Nuxt SSR):\n skip the nginx static server. Build in the one-shot service, then run the\n framework's production start command (e.g. next start) as a normal routed\n service with sandbox.route \"/\" and its listen port.\n\nSETUP SCRIPT (type: setup-script)\n Runs BEFORE docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/setup.sh.\n\n IMPORTANT: The host is a bare EC2 instance with only docker and basic linux tools.\n App runtimes (node, bun, python, ruby) are NOT available on the host — they live\n inside your Docker images. Do NOT run app commands directly here.\n\n Script authoring rules:\n - Treat /workspace as the repo root for any file access or repo command\n - Do not assume the current directory matches the script's own path\n - Use explicit paths or start with \"cd /workspace\" before repo-relative work\n\n Use for host-level preparation only:\n - Installing system packages (apt-get, yum)\n - Downloading external tools or binaries\n - Generating config files programmatically (prefer file artifacts for static files)\n\n For most apps, you won't need a setup script — file artifacts and the startup\n script cover the common cases. Skip this if you don't need host-level prep.\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Non-zero exit is logged as a warning but does not block the build\n\nSTARTUP SCRIPT (type: startup-script)\n Runs AFTER docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/startup.sh.\n\n This is where most app setup happens. Containers are running, so you can use\n \"sb-compose exec\" to run commands inside them with the app runtime.\n\n Script authoring rules:\n - Start with \"cd /workspace\" or use explicit /workspace/... paths\n - Do not assume the script runs from /workspace/.canary\n - Prefer sb-compose exec for anything that needs the app runtime\n - Use -T with exec (no TTY allocation — startup runs headless)\n\n Use for:\n - DB migrations via ORM CLI (use \"bun run\", NOT bunx — bunx downloads fresh)\n - Building workspace packages that export from dist/\n - Waiting for services to be healthy\n - Triggering initial data loads\n\n POST-MIGRATION RESTART (REQUIRED for many ORMs):\n ORMs that introspect the schema at boot (Drizzle/effect-sql, Prisma with\n prepared statements, SQLAlchemy with autoload, etc.) cache the pre-migration\n schema on the first query. After you run migrations, you MUST restart the\n API service, otherwise you'll see errors like:\n relation \"<table>\" does not exist\n even though psql confirms the table is there. Add \"sb-compose restart\" for\n any service whose client introspects the schema at boot.\n\n Example (Bun monorepo with Drizzle):\n #!/bin/bash\n set -e\n # Build dist-exporting workspace packages the app/web import from\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/ui && bun run build'\n # Run DB migrations inside the API container\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/db && bun run drizzle-kit migrate'\n # Restart services so they pick up the fresh schema + built UI\n sb-compose restart app-api app-web\n # Wait for API to be healthy\n for i in $(seq 1 30); do\n sb-compose exec -T app-api curl -sf http://localhost:3000/health && break\n sleep 2\n done\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Don't pre-wait for services to START — the platform handles that before\n startup.sh runs (see Compose Architecture section). Do wait for service\n *readiness* (app health, schema migrations applied) when your app\n depends on it.\n\nFILE (type: file)\n Arbitrary sandbox-owned files written to the instance at a specific path.\n Use for config files that need to exist before services start (e.g. Vite\n wrapper configs, one-shot scripts that need app dependencies).\n\n !!! File artifacts MUST be written under /workspace/.canary/ !!!\n The repo tree (everything outside .canary/) is user-owned. Sandbox\n helpers live in .canary/ so they never collide with real files and\n are never committed back to the repo. The API will reject any\n --path value outside /workspace/.canary/ with\n SANDBOX_FILE_ARTIFACT_OUTSIDE_CANARY.\n\n If your script needs app dependencies (e.g. \\`postgres\\` from the api\n package), put a tiny package.json next to it in .canary/scripts/ and\n \\`bun install\\` in the setup-script, OR invoke it via\n \\`sb-compose exec -T <svc> ...\\` so it runs inside a container that\n already has them.\n\n Upload with --path to specify the destination:\n canary sandbox artifact create <template-id> file <local-file> --path /workspace/.canary/<dest>\n\n Example — SPA fallback config for the static web server (see PRODUCTION BUILD):\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n ONE ROW PER FILENAME. File artifacts are keyed by (template, filename).\n Uploading a file with the same filename as an earlier upload REPLACES\n the earlier row. The CLI prints \"(replaced)\" on such uploads, and the\n next build removes the previous destination path so the instance\n filesystem mirrors the template set exactly.\n\n If you want to retire a file, just upload something else with a\n different filename — the stale file is cleaned up on next build via\n .canary/.artifact-paths (a manifest the platform maintains).\n\n File artifacts are written before the setup script runs, so they're\n available for both setup and compose services.\n\nSEED SCRIPT (type: seed-script)\n Runs once on first provision to populate demo data. Optional.\n Written to /workspace/.canary/seed.sh.\n Skipped on subsequent builds (checks /workspace/.canary/.seeded).\n\nCONFIG SCHEMA\n Define which env vars need user input. Set when completing the docker-compose checklist item:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[\n {\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"Anthropic API key\"},\n {\"key\": \"OPENAI_API_KEY\", \"required\": false, \"sensitive\": true, \"description\": \"OpenAI API key\"}\n ]'\n\n Set defaults:\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n`.trim(),\n};\n\nconst VERIFICATION: GuideSection = {\n title: 'Verification & Iteration',\n slug: 'verify',\n content: `\nAfter running \"canary sandbox build\", verify the sandbox is working. The iteration loop:\n upload artifacts → build → check diagnostics → read logs → fix → re-upload → rebuild\n\nCOMMANDS:\n # Check all container statuses at a glance\n canary sandbox diagnostics <instance-id>\n\n # Read logs for a specific service\n canary sandbox logs <instance-id> --service app-api\n canary sandbox logs <instance-id> --service app-web\n canary sandbox logs <instance-id> --service postgres\n\n # Run arbitrary commands on the instance (runs on the host VM by default)\n canary sandbox run-command <instance-id> \"docker compose ps\"\n canary sandbox run-command <instance-id> \"echo hello\"\n\n # Run commands inside a specific Docker container\n canary sandbox run-command <instance-id> --service app-api \"bun --version\"\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n\n # Test health endpoints\n canary sandbox run-command <instance-id> \"curl -s http://localhost:3000/health\"\n canary sandbox run-command <instance-id> \"curl -s -o /dev/null -w '%{http_code}' http://localhost:80/\"\n\n # Test with sandbox hostname (for host-restriction issues)\n canary sandbox run-command <instance-id> \\\\\n \"curl -s -H 'Host: <instance-id>.env.runcofactor.com' http://localhost:80/\"\n\nSANDBOX URL:\n https://<instance-id>.env.runcofactor.com\n\nIN-BOX BROWSER VERIFICATION (sandbox agent):\n The agent running inside the sandbox has a headless Chromium and can drive the\n running app directly to confirm setup — no external tester needed. The app is\n in a sibling docker-compose project; target it over the compose network.\n\n docker compose ps # find app service + port\n cofactor session start --headless --url http://<svc>:<port> [--credential <n>]\n cofactor session snapshot # page accessibility tree\n cofactor session click --ref <ref> # act on a ref\n cofactor session type --ref <ref> --text \"...\" --submit\n cofactor session wait --text \"<expected copy>\" # assert it loaded\n cofactor session console --only-errors # surface JS errors\n cofactor session stop\n\n The session daemon idle-exits after ~60s; just start again if a command\n reports no active session. Run this AFTER the app is healthy (get_status /\n diagnostics), then record the result via update_checklist.\n\nIF SOMETHING FAILS:\n 1. Read the logs for the failing service\n 2. Diagnose the root cause (missing env var, failed build, wrong port, etc.)\n 3. Fix the ARTIFACT — not just the running container\n 4. Re-upload: canary sandbox artifact create <template-id> <type> <file>\n 5. Rebuild: canary sandbox build <instance-id>\n 6. Verify again\n\nWHEN A CLI COMMAND TIMES OUT AT 30s:\n Long sandbox operations (build, edit, artifact create on first push,\n occasionally diagnostics) can exceed the gateway window even though\n the work is still running server-side. The CLI prints:\n Error: Request to https://api.trycanary.ai/... timed out after 30000ms.\n\n Don't retry blindly — a retry can spawn duplicate work (e.g. a second\n authoring instance). Instead, poll for the actual state:\n\n canary sandbox template <template-id> # status: provisioning/authoring/verified\n canary sandbox diagnostics <instance-id> # container/app health\n canary sandbox list # running instances\n\n Only retry once you've confirmed via polling that the prior call\n truly didn't take effect.\n\nWHEN VERIFY HANGS:\n \\`canary sandbox verify\\` now streams container state and failing-service\n log tails every ~30s while the probe's app is unreachable. If you see:\n\n >> nginx-proxy is restart-looping because upstream '<service>' (...) is exited.\n Read that service's logs above, not nginx's.\n\n ignore nginx entirely and read the upstream's tail — nginx is the symptom,\n not the cause. Common underlying causes:\n - the upstream service crashed at startup (bad env var, missing file,\n import error)\n - a file artifact was lost because you re-uploaded over its filename\n (shouldn't happen any more — the build-time reconcile handles this —\n but watch for it if you're on an old template)\n\n On failure, the probe instance is preserved by default so you can\n \\`canary sandbox run-command <id>\\` into it. Pass\n --auto-terminate-on-failure for CI.\n\nCOMMON ISSUES:\n\n \"Invalid environment\" / env validation fails\n App has strict env var validation. Read the env schema. Add ALL required vars to compose.\n\n Migration fails with \"SST links not active\" / cloud SDK error\n Migrate script depends on cloud tooling. Use the ORM CLI directly in the startup script\n via sb-compose exec: sb-compose exec -T app-api bash -c 'bun run drizzle-kit migrate'\n DATABASE_URL should be hardcoded in the compose environment section.\n\n \"Cannot find module\" / package not built\n Monorepo package exports from dist/ but hasn't been built.\n Add build step to startup script via sb-compose exec (runs after compose up).\n\n \"Cannot find package 'X'\" from a file-artifact script\n Bun resolves node_modules starting from the script's directory. File\n artifacts live under /workspace/.canary/ which has no node_modules by\n default. Two fixes:\n (a) add a /workspace/.canary/scripts/package.json declaring the\n dep (e.g. \"postgres\") and run \\`bun install\\` from the\n setup-script / inside a sb-compose exec.\n (b) don't run the script on the host at all — invoke it via\n \\`sb-compose exec -T <app-service> bun /app/.canary/scripts/foo.ts\\`\n so it resolves against the workspace's own node_modules. Replace\n <app-service> with your template's actual service name.\n\n Blank page — frontend never renders (served by a dev server)\n The web service is a dev server (Vite/webpack-dev-server) shipping hundreds\n of uncompressed, no-cache modules. On a remote sandbox these trickle in too\n slowly for the SPA to mount, so the page stays blank. Confirm with:\n curl -s <SANDBOX_URL>/ | grep '<script'\n Dev mode references /@vite/client or /src/main.tsx; a production build\n references hashed /assets/*.js. Fix: serve a production build (see\n Artifacts → PRODUCTION BUILD), not a dev server.\n\n Deep links / page refresh 404 (SPA routing)\n nginx serves static files but a client-side route (e.g. /settings) or a\n hard refresh returns 404 — the static server has no SPA fallback. Add\n \"try_files $uri $uri/ /index.html;\" to the nginx config (see Artifacts →\n PRODUCTION BUILD) so unknown paths serve index.html and the client router\n takes over.\n\n Frontend loads but API calls hit the wrong URL / localhost\n The browser-facing API URL was not set as a BUILD-TIME var before the\n build, so the bundle baked a default (often localhost:3000). Static\n bundles read build-time vars at BUILD time, not runtime — setting them\n only in the app-web/runtime compose env is too late. Set the public var on\n the web-build service env (or before the build command), using\n \\${SANDBOX_URL}, then rebuild:\n VITE_API_URL: \\${SANDBOX_URL}/api # or NEXT_PUBLIC_*, REACT_APP_*, etc.\n\n API 502 / \"Cannot find module '@scope/pkg'\" crash-loop after going prod\n The api imports a workspace package that exports from dist/, but that\n package's dist was never built — so the api crash-loops and the health gate\n fails the whole provision with a 502. Easy to miss because the FRONTEND\n build (ui) works. Build EVERY dist-exporting package the api imports in the\n web-build service (see Artifacts → PRODUCTION BUILD), not just the ui ones.\n A warm AMI can hide this (it baked the dist in); a fresh provision exposes it.\n\n 403 \"directory index forbidden\" from the static server\n nginx is bound to the stale empty dist inode the bundler replaced. Mount the\n PARENT dir and set root to the dist subpath (see Artifacts → PRODUCTION\n BUILD), or restart the nginx service after the build.\n\n Build is killed / \"JavaScript heap out of memory\"\n The production build exhausted the Node heap (common on large Vite/webpack\n builds). Raise it on the web-build service before the build:\n NODE_OPTIONS: --max-old-space-size=8192\n If it still OOMs, bump the instance size (canary sandbox set-size) and/or\n the heap further.\n\n Stale page after a rebuild (old dist served)\n nginx is serving a previous build because the build step didn't re-run or\n its output dir wasn't refreshed. Confirm the web-build service ran in this\n build cycle (canary sandbox logs <id> --service web-build), that it wrote\n to the dir nginx mounts, and — if you ever build via the startup script\n instead — that nginx was restarted (sb-compose restart app-web). Hashed\n asset filenames change per build; if index.html still references old\n hashes, the build didn't land.\n\n run-command times out\n Long-running operation exceeds the 55s SSM command timeout. Write a script file and run in background:\n canary sandbox run-command <id> \"cat > /tmp/run.sh << 'SCRIPT'\n #!/bin/bash\n <commands>\n SCRIPT\n chmod +x /tmp/run.sh && nohup /tmp/run.sh > /tmp/run.log 2>&1 & echo started\"\n Then check: canary sandbox run-command <id> \"tail -20 /tmp/run.log\"\n\n \"Resolving dependencies\" hangs in migration container\n bunx/npx downloads a fresh copy instead of using project deps.\n Use \"bun run drizzle-kit migrate\" instead of \"bunx drizzle-kit migrate\".\n\n Containers can't resolve each other by name\n Don't use docker run — use docker compose for everything.\n\nRUNNING TESTS:\n Two approaches depending on your needs:\n\n Option 1 — Install runtime on host (no Docker needed):\n Use the setup script to install the runtime on the host VM, then run\n tests directly. Good for unit tests that don't need services like a database.\n\n Example setup-script artifact:\n #!/bin/bash\n curl -fsSL https://bun.sh/install | bash\n export PATH=\"$HOME/.bun/bin:$PATH\"\n\n Then: canary sandbox run-command <instance-id> \"bun test\"\n\n Option 2 — Run inside a container (--service flag):\n Route commands into a running Docker container. Good for integration\n tests that need access to the database or other services.\n\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n canary sandbox run-command <instance-id> --service app-api \"bun test:integration\"\n\n \\${SANDBOX_URL} is blank / env vars from config not in containers\n The \"build\" command writes /workspace/.canary/.env (with SANDBOX_URL and all config values)\n before running docker compose up. If the .env is missing or stale, re-run:\n canary sandbox build <instance-id>\n You can also trigger a .env refresh by setting any config value:\n canary sandbox config set <template-id> <key> <value>\n\n .env values look empty when I cat the file\n Values are written single-quoted (KEY='value'). Short values can look like\n KEY='' at a glance even when they have content — it's just the quotes. Run\n \"wc -c\" on a line, or \"docker compose exec app-api env | grep KEY\", to\n confirm the actual value landed.\n`.trim(),\n};\n\nconst CHECKLIST: GuideSection = {\n title: 'Checklist',\n slug: 'checklist',\n content: `\nThe checklist tracks your progress toward a working sandbox. View it with:\n canary sandbox checklist <template-id>\n\nEach item has a status: pending, completed, skipped, or failed.\nItems marked with * are actionable (their dependencies are met).\n\nCOMPLETING ITEMS:\n canary sandbox checklist complete <template-id> <item-id> \\\\\n --status <success|failure> \\\\\n --summary \"What you did\"\n\n Example:\n canary sandbox checklist complete <template-id> environment-analyzed \\\\\n --status success \\\\\n --summary \"Bun monorepo: Postgres, Redis. Vite production build served by nginx, API on 3000.\"\n\nSKIPPING ITEMS:\n canary sandbox checklist skip <template-id> <item-id>\n\n Use when an item doesn't apply (e.g., no seed data needed, no integration tests).\n\nSPECIAL: docker-compose-created accepts --config-schema to define user-configurable env vars:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[{\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"API key\"}]'\n\nTYPICAL CHECKLIST ORDER:\n 1. Provisioned — done automatically when you create the template\n 2. Code Cloned — done automatically\n 3. Environment Analyzed — complete after your research phase\n 4. Setup Script Created — complete after uploading setup-script artifact\n 5. Dockerfile Created — complete after uploading dockerfile artifact\n 6. Docker Compose Created — complete after uploading docker-compose artifact (with config-schema)\n 7. Unit Tests Runnable — complete or skip. Install runtime on host via setup script,\n or use --service to run inside a container\n 8. Integration Tests Runnable — complete or skip. Use --service to run inside a\n container with access to database and other services\n 9. Seed Script Created — complete or skip\n 10. Server Running — complete after API responds to health checks\n 11. Server Accessible — complete after nginx routes to the app successfully\n 12. Template Verified — provision a fresh instance and verify it comes up healthy\n\nTEMPLATE VERIFICATION (final step):\n Run one command — it provisions a fresh instance, waits for the app to\n come up, marks template-verified, and terminates the instance:\n canary sandbox verify <template-id>\n\n Pass --keep-instance to leave the instance running afterwards (useful if\n you want to poke at it).\n\n This catches issues that authoring hides:\n - Config values set manually but not in configDefaults\n - SANDBOX_URL or other platform vars not properly referenced\n - Startup scripts that assume manual setup already happened\n\n Template status flow:\n provisioning → authoring → verified\n\n 'verify' is the one transition from authoring to verified, and it is one-way\n — there is no re-authoring path back to 'authoring' from 'verified'. To make\n an AI-assisted edit to a verified template's artifacts, provision a sandbox\n with the agent attached:\n\n canary sandbox provision <template-id> --with-agent\n\n then iterate via in-sandbox agent chat or 'canary sandbox artifact create',\n and re-run 'canary sandbox verify' against the same template id when you're\n happy with the changes. To start over from scratch, use 'regenerate' to\n delete existing artifacts, or create a new template.\n`.trim(),\n};\n\nconst ACTIONS: GuideSection = {\n title: 'Actions',\n slug: 'actions',\n content: `\nActions are template-defined, parameterized operations that run against sandbox\ninstances. Unlike run-command (arbitrary shell), actions have typed input/output\nschemas, are reusable across instances, and produce an auditable execution log.\n\nIN-BOX AGENT: the sandbox agent defines actions with the publish_action tool,\nlists them with list_actions, and RUNS them with execute_action (by slug, with\nparams) — all scoped to its own instance, no instance id needed. The CLI\nequivalents below are for external operators.\n\nUSE CASES:\n - Provision a user credential (username, password, role)\n - Seed demo data for a specific scenario\n - Run database migrations\n - Reset application state\n - Toggle feature flags\n\nMANAGING ACTIONS:\n\n List actions on a template:\n canary sandbox action list <template-id>\n\n View an action definition (including script):\n canary sandbox action get <template-id> <slug>\n\n Create or update an action:\n canary sandbox action create <template-id> <slug> \\\\\n --label \"Provision Credential\" \\\\\n --description \"Create a user account in the sandbox app\" \\\\\n --icon UserPlus \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n --credential-output-config is optional. When set, a successful execution\n also creates/updates a credential in the property's vault — see \"Linking\n actions to Canary credentials\" below.\n\n Delete an action:\n canary sandbox action delete <template-id> <slug>\n\nEXECUTING ACTIONS:\n\n Execute against a running instance:\n canary sandbox action execute <instance-id> <slug> \\\\\n --param username=testuser \\\\\n --param password=s3cret \\\\\n --param role=admin\n\n Or pass all params from a JSON file:\n canary sandbox action execute <instance-id> <slug> \\\\\n --params-file ./params.json\n\n View execution history:\n canary sandbox action executions <instance-id>\n\nSLUG NAMING:\n Slugs must be lowercase alphanumeric with hyphens, starting with a letter:\n provision-credential ✓\n seed-demo-data ✓\n reset-state ✓\n 123-bad ✗\n UPPER_CASE ✗\n\nINPUT SCHEMA:\n A JSON Schema file defining the action's parameters. The schema drives\n validation, CLI prompting, and UI form generation.\n\n Example (provision-user-input.json):\n {\n \"type\": \"object\",\n \"properties\": {\n \"username\": {\n \"type\": \"string\",\n \"description\": \"Username to create\",\n \"minLength\": 1\n },\n \"password\": {\n \"type\": \"string\",\n \"description\": \"Password for the user\",\n \"sensitive\": true\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"Role to assign\",\n \"enum\": [\"admin\", \"user\", \"viewer\"],\n \"default\": \"user\"\n }\n },\n \"required\": [\"username\", \"password\"]\n }\n\n The \"sensitive\" extension marks params that should be masked in UI and\n injected securely (via tempfile, not env var) on the instance.\n\nSCRIPT CONVENTIONS:\n Action scripts are bash, executed on the instance via SSM. Parameters are\n injected as INPUT_<KEY> environment variables, where <KEY> is the param\n name uppercased with non-alphanumeric chars replaced by '_'. There is NO\n camelCase → snake_case conversion. Examples:\n orgName → INPUT_ORGNAME (NOT INPUT_ORG_NAME)\n org_name → INPUT_ORG_NAME\n api-key → INPUT_API_KEY\n If you want INPUT_FOO_BAR, name the input schema key 'foo_bar' or 'foo-bar'.\n\n The last line of stdout that is valid JSON is parsed as the structured result.\n\n EXECUTION ENVIRONMENT — same architecture as startup scripts:\n Scripts run on the HOST (bare EC2 instance). The host has docker, curl,\n and basic linux tools — but NO app runtimes (node, bun, python, ruby).\n Those live inside your Docker containers.\n\n Use \"docker compose exec\" to run commands that need the app runtime:\n sb-compose exec -T app-api node -e \"...\" # Node.js\n sb-compose exec -T app-api bun run seed.ts # Bun\n sb-compose exec -T app-db psql -U app -d db # Database CLI\n\n Use bare host commands for docker/system operations:\n sb-compose restart app-api # Restart a service\n curl -s http://localhost:3000/health # Health check\n cat /workspace/.canary/.env # Read config\n\n The -T flag is required for docker compose exec in non-interactive (SSM)\n contexts. Without it, exec tries to allocate a TTY and hangs.\n\n Working directory is /workspace (the mounted repo root). Keep script logic\n repo-root-relative by starting with \"cd /workspace\" when needed.\n\n AVOID INLINE CODE IN ACTION SCRIPTS:\n Never embed application-language code (JS, Python, etc.) directly in the\n bash script via \"bun -e\" or \"node -e\". Shell escaping of quotes, template\n literals, and variable interpolation is fragile and hard to debug.\n\n Instead, write complex logic as a separate file artifact and execute it:\n 1. Write a script file (e.g. provision-user.ts)\n 2. Upload as a file artifact:\n canary sandbox artifact create <template-id> file provision-user.ts \\\\\n --path /workspace/scripts/provision-user.ts\n 3. Reference it from the action script:\n sb-compose exec -T app-api bun /app/scripts/provision-user.ts\n\n Parameters are available in the action script as INPUT_<KEY> env vars.\n Pass them to the file artifact via env or args:\n sb-compose exec -T -e EMAIL=\"$INPUT_EMAIL\" app-api bun /app/scripts/provision-user.ts\n\n Example — provision a user (two-file pattern):\n\n Place the .ts file under the workspace whose node_modules has the deps\n (e.g. apps/api when only apps/api depends on \\`postgres\\`). Bun resolves\n modules from the script's directory walking up — if the dep isn't hoisted\n to the repo-root node_modules, a script under /workspace/scripts/ will\n fail with \"Cannot find package 'postgres'\". Use \\`-w\\` to set cwd inside\n the right workspace.\n\n Action script (provision-user.sh):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T \\\\\n -w /app/apps/api \\\\\n -e EMAIL=\"$INPUT_EMAIL\" \\\\\n -e PASSWORD=\"$INPUT_PASSWORD\" \\\\\n -e NAME=\"$INPUT_NAME\" \\\\\n app-api bun /app/apps/api/scripts/provision-user.ts\n\n File artifact (uploaded to /workspace/apps/api/scripts/provision-user.ts):\n import postgres from \"postgres\";\n const sql = postgres(process.env.DATABASE_URL!);\n const hash = await Bun.password.hash(process.env.PASSWORD!, { algorithm: \"argon2id\" });\n const email = process.env.EMAIL!.toLowerCase();\n const [org] = await sql\\`INSERT INTO organizations (name) VALUES (\\${email + \"'s Org\"}) RETURNING id\\`;\n const [user] = await sql\\`\n INSERT INTO users (auth_provider, provider_id, primary_email, password_hash, email_verified_at)\n VALUES ('email', \\${email}, \\${email}, \\${hash}, now()) RETURNING id\\`;\n await sql\\`INSERT INTO org_memberships (user_id, org_id, role) VALUES (\\${user.id}, \\${org.id}, 'admin')\\`;\n const loginUrl = \\`\\${process.env.SANDBOX_PUBLIC_URL ?? ''}/login\\`;\n // Echo password back so credentialOutputConfig (below) can resolve $output.password.\n // Output keys referenced by credentialOutputConfig.fieldMapping are auto-redacted\n // from stored stdout/logs by the platform — safe to include.\n console.log(JSON.stringify({ ok: true, email, password: process.env.PASSWORD, userId: user.id, orgId: org.id, loginUrl }));\n await sql.end();\n\nLINKING ACTIONS TO CANARY CREDENTIALS:\n\n When you want a successful action to also produce a credential in the\n property's vault (so it can drive browser logins, seed workflows, etc.),\n attach a credentialOutputConfig. The platform resolves \\`$output.<key>\\`\n tokens against the action's last-line JSON stdout, then upserts a\n credential scoped to (sandboxActionId, environmentId) — re-running the\n action replaces the credential rather than duplicating it.\n\n Provide it with --credential-output-config on action create/update:\n\n canary sandbox action create <template-id> provision-credential \\\\\n --label \"Provision Credential\" \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n Example provision-user-credential.json (matches the script above):\n {\n \"fieldMapping\": {\n \"username\": \"$output.email\",\n \"password\": \"$output.password\"\n },\n \"loginUrl\": \"$output.loginUrl\",\n \"loginInstructions\": \"Sign in via the email/password form.\"\n }\n\n Field-mapping values are either literal strings or \\`$output.<key>\\` tokens\n resolved from the action's parsed JSON output. Output keys referenced by\n fieldMapping/loginUrl are automatically masked in stored stdout and result.\n\n After execution, \\`canary sandbox action execute\\` prints the linked\n credentialId, and \\`canary credential list --property <name>\\` shows the\n vault entry. \\`canary credential test <credId>\\` runs the browser login\n against the sandbox URL.\n\n Example — simple SQL-only action (inline is fine for short commands):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-db psql -U app -d appdb -t --csv -c \\\\\n \"INSERT INTO users (username, password_hash, role)\n VALUES ('$INPUT_USERNAME', crypt('$INPUT_PASSWORD', gen_salt('bf')), '$INPUT_ROLE')\n RETURNING id\" | while read -r user_id; do\n echo \"{\\\\\"userId\\\\\":\\\\\"$user_id\\\\\",\\\\\"username\\\\\":\\\\\"$INPUT_USERNAME\\\\\",\\\\\"loginUrl\\\\\":\\\\\"https://$SANDBOX_PUBLIC_URL/login\\\\\"}\"\n done\n\n Example — seed data via app CLI:\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bun run scripts/seed.ts \\\\\n --scenario \"$INPUT_SCENARIO\" \\\\\n --count \"$INPUT_COUNT\"\n\n Example — run migrations (no params needed):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/db && bun run drizzle-kit migrate'\n echo '{\"status\":\"completed\"}'\n\n Available context variables:\n SANDBOX_PUBLIC_URL — the instance's public URL\n SANDBOX_INSTANCE_ID — the instance UUID\n\nAGENT AUTHORING:\n During template creation, the sandbox agent can publish actions alongside\n artifacts using the publish_action tool. The agent analyzes the codebase\n and generates action definitions for common operations (user creation,\n data seeding, migrations, etc.). These are stored on the template and\n available on all instances provisioned from it.\n\n publish_action accepts an optional credentialOutputConfig — for any action\n that provisions login material (e.g. a user with email/password), the agent\n should set this so the resulting credential lands in the property vault and\n can be exercised by browser automation.\n`.trim(),\n};\n\nconst CACHING: GuideSection = {\n title: 'Build Caching',\n slug: 'caching',\n content: `\nA cold sandbox launch otherwise reruns the slow build work on every provision:\nthe Docker image build (the browser/toolchain install layer), \\`bun install\\`, and\nthe production web build. The platform removes that cost by capturing known-good\noutputs at VERIFICATION time and restoring them on every later provision. Most of\nit is automatic; two things are your job: opt one-shot build services in with\nlabels, and keep their outputs instance-independent.\n\nWHAT THE PLATFORM DOES FOR YOU (no action needed)\n - Prebuilt image: when a template verifies, the built image is pushed to a\n registry and PULLED on later provisions instead of rebuilt. Your services keep\n both \\`build:\\` and an injected \\`image:\\` — \\`--build\\` is skipped on a hit and a\n pull miss falls back to building. Nothing to declare.\n\nWHAT YOU DECLARE — cache labels on one-shot build/dep services\n Same convention as the sandbox.route/sandbox.port labels. The platform snapshots\n the declared volumes/paths at verify and restores them at provision; on a hit a\n marker file makes the one-shot service no-op (skips the build); on a miss it runs\n cold and the next verify re-captures.\n\n services:\n deps: # dependency install → cache the volume\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"lockfiles\" # invalidate when a lockfile changes\n sandbox.cache-volumes: \"node_modules\" # named volume(s) to snapshot\n web-build: # production build → cache the output dirs\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"inputs\"\n sandbox.cache-inputs: \"apps/web,packages/ui,bun.lock\" # source the build reads\n sandbox.cache-paths: \"apps/web/dist,packages/ui/dist\" # output dirs to snapshot\n\n Labels:\n sandbox.cache: \"true\" Opt this service in.\n sandbox.cache-key How the cache is invalidated:\n \"lockfiles\" — hash of the repo's lock manifests (bun.lock, package-lock.json,\n yarn.lock, pnpm-lock.yaml). Use for dependency installs.\n \"inputs\" — content hash of the sandbox.cache-inputs paths (node_modules\n and dist excluded). Use for BUILDS. This is what makes the\n cache useful on a branch that moves: a commit that does not\n touch the build's inputs keeps the key → hit; one that does →\n miss → rebuild. (Default: inputs when cache-inputs is set,\n else lockfiles.)\n sandbox.cache-inputs Comma-separated source paths the build consumes\n (required for \"inputs\"). List the app dir plus every\n workspace package it builds, plus the lockfile.\n sandbox.cache-volumes Comma-separated named volumes to snapshot/restore.\n sandbox.cache-paths Comma-separated workspace-relative output dirs to\n snapshot/restore.\n All path/volume values must be plain, relative, traversal-free names — absolute\n paths, \"..\"/empty segments, or shell metacharacters are rejected (fail-closed).\n\n NEVER key a build on the git commit: a moving branch would invalidate it on every\n unrelated push (an api-only or docs commit) and the cache would never hit. Key on\n the build's actual INPUTS.\n\nKEEP THE COMMAND ON ONE LINE\n The platform wraps a cacheable service's \\`command:\\` in a marker gate so a hit\n skips the build. It rewrites a single-line command — scalar (\\`command: bun run build\\`)\n or flow array (\\`command: [\"bash\",\"-c\",\"...\"]\\`). A multi-line block command is left\n ungated (the service still gets capture/restore but always runs). Keep one line.\n\nINSTANCE-INDEPENDENCE (required for cached build outputs)\n A cached \\`dist\\` is reused across DIFFERENT sandboxes, so it must not bake any\n per-sandbox value. The common trap is baking the API URL at build time\n (VITE_API_URL=\\${SANDBOX_URL}/api). Instead resolve the API origin at RUNTIME from\n the browser location — web and api are the same origin behind the sandbox nginx\n proxy, so a relative \\`/api\\` works on every sandbox. If you must bake a value, bake\n a fixed placeholder and rewrite it at provision; never bake \\${SANDBOX_URL} into a\n cached output. Outputs that embed a per-sandbox value cannot be shared and will\n serve the wrong sandbox's URL.\n\nREFRESHING THE CACHE\n Re-verify the template to re-capture (\\`canary sandbox verify <template-id>\\`).\n Editing artifacts already forces a re-verify. A miss is always safe — it just\n builds cold that one time.\n`,\n};\n\nexport const SANDBOX_GUIDE_SECTIONS: GuideSection[] = [\n GOAL,\n RESEARCH,\n ARTIFACTS,\n CACHING,\n ACTIONS,\n VERIFICATION,\n CHECKLIST,\n];\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../content/src/print-guide.ts","../../content/src/sandbox-guide.ts"],"sourcesContent":["import type { GuideSection } from './types.js';\n\nexport function printGuide(\n allSections: GuideSection[],\n section?: string,\n json?: boolean,\n): void {\n const sections = section\n ? allSections.filter((s) => s.slug === section)\n : allSections;\n\n if (section && sections.length === 0) {\n console.error(\n `Unknown guide section: \"${section}\". Available: ${allSections.map((s) => s.slug).join(', ')}`,\n );\n process.exit(1);\n }\n\n if (json) {\n console.log(JSON.stringify(sections, null, 2));\n return;\n }\n\n for (const s of sections) {\n console.log(`\\n${'='.repeat(72)}`);\n console.log(` ${s.title}`);\n console.log(`${'='.repeat(72)}\\n`);\n console.log(s.content);\n console.log('');\n }\n}\n","// Sandbox authoring guide — printed by `canary sandbox guide [section]`\n// and also rendered (in part) in the web UI on the sandbox detail page.\n\nimport type { GuideSection } from './types.js';\n\nconst GOAL: GuideSection = {\n title: 'Goal & Tools',\n slug: 'goal',\n content: `\nYour goal is to turn a git repository into a running, containerized development\nenvironment. The end state is a sandbox URL that loads the app in a browser.\n\nWORKFLOW:\n 1. canary sandbox create <property> Create a template + authoring instance\n 2. Research the app Read the codebase to understand what it needs\n 3. Set config values API keys, secrets → pushed to instance as .canary/.env\n 4. Upload artifacts Dockerfile, docker-compose, setup script, etc. → pushed to instance\n 5. canary sandbox build <instance-id> Build images, start containers, generate nginx\n 6. Check diagnostics & logs Verify the app is running\n 7. Fix artifacts, re-upload, re-build Iterate until the app loads in browser\n 8. Mark checklist items complete Record what you did\n\nEach artifact upload and config set immediately pushes the file to the instance.\nThe \"build\" command triggers the actual rebuild and restart.\nBatch your changes (upload all artifacts, set all config), then build once.\n\nYOUR TOOLS:\n\n Config — set environment variables (pushed to instance .canary/.env immediately):\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n\n SANDBOX_URL is auto-injected — use it in compose for browser-facing URLs.\n\n Artifacts — files that define the sandbox (pushed to instance on upload):\n canary sandbox artifact create <template-id> <type> <file>\n canary sandbox artifact get <template-id> <type>\n\n Types: dockerfile, docker-compose, setup-script, startup-script, seed-script, file\n\n Build — rebuild and start services:\n canary sandbox build <instance-id>\n canary sandbox build <instance-id> --no-wait\n\n Generates nginx routing, injects Docker networking, writes file artifacts,\n runs setup script, then runs compose up, then runs startup script.\n Call this after uploading artifacts.\n\n First builds on large projects may exceed gateway timeouts. The CLI handles\n this automatically — it detects the timeout and polls diagnostics until\n containers are healthy. Use --no-wait to fire-and-forget and poll manually.\n\n Diagnostics & logs — check container health and read service output:\n canary sandbox diagnostics <instance-id>\n canary sandbox logs <instance-id> --service <name>\n\n Run-command — escape hatch for debugging and one-off commands:\n canary sandbox run-command <instance-id> \"<command>\"\n\n Checklist — record your progress:\n canary sandbox checklist <template-id>\n canary sandbox checklist complete <template-id> <item-id> [options]\n canary sandbox checklist skip <template-id> <item-id>\n\nPRINCIPLES:\n - Artifacts are the source of truth. Every fix must update the artifact file and re-upload.\n - Never patch source files. Use Docker env vars, CMD overrides, setup/startup scripts.\n - Code is volume-mounted at /workspace, not baked into the image.\n - The platform handles nginx routing and Docker networking — you don't need to.\n`.trim(),\n};\n\nconst RESEARCH: GuideSection = {\n title: 'Research the App',\n slug: 'research',\n content: `\nBefore writing any artifacts, research the application thoroughly.\n\n1. ENV VAR VALIDATION\n Find the app's environment validation schema (Zod, Joi, dotenv, etc.).\n List ALL required (non-optional) variables. Many apps fail fast on missing vars.\n\n Common locations:\n - src/env.ts, src/config.ts, src/environment.ts\n - .env.example, .env.sample\n\n Classify each var:\n - AUTO-WIRED: Internal service URLs (DATABASE_URL, REDIS_URL) — hardcode in compose\n - PLACEHOLDER: OAuth creds, S3 buckets — use dummy values, app boots without them\n - SENSITIVE: API keys (ANTHROPIC_API_KEY) — goes in template configSchema\n - DISABLED: Worker flags, schedulers — set to false in compose\n\n2. REQUIRED SERVICES\n What databases, caches, queues does the app need?\n Check: docker-compose.yml, package.json deps, infra/ configs, README\n Common: PostgreSQL, Redis, MongoDB, RabbitMQ, Elasticsearch\n\n3. MIGRATION STRATEGY\n Does the migrate script depend on cloud tooling (SST, AWS SDK, Terraform)?\n If yes, use the ORM's CLI directly in the startup script via \"sb-compose exec\":\n - Drizzle: sb-compose exec -T app-api bash -c 'cd /app/packages/db && bun run drizzle-kit migrate'\n - Prisma: sb-compose exec -T app-api bash -c 'bunx prisma migrate deploy'\n - Rails: sb-compose exec -T app-api rails db:migrate\n - Django: sb-compose exec -T app-api python manage.py migrate\n Put migrations in the startup script, NOT as a compose service (see Artifacts).\n Do NOT use bunx for JS migrations — it downloads a fresh copy. Use \"bun run\" instead.\n\n4. PACKAGE BUILD ORDER (monorepos)\n Which workspace packages export from dist/ and must be pre-built?\n Check package.json \"main\" or \"exports\" fields for \"./dist/\" references.\n These builds go in the startup script via sb-compose exec, NOT the Dockerfile.\n When running scripts via sb-compose exec, cd to the package directory\n where the dependency is declared. Bun workspace module resolution won't\n find deps from the monorepo root.\n\n5. PRODUCTION BUILD STRATEGY (frontends)\n Sandboxes serve a PRODUCTION build of the frontend, not a dev server.\n This is faster and far more responsive, ships compressed/hashed/cacheable\n assets, and sidesteps dev-server host-allowlist headaches entirely (a static\n file server has no notion of \"allowed hosts\"). HMR / hot-reload is\n intentionally dropped — a sandbox demos and QAs a BUILT app; it is not a\n live-edit environment.\n\n Identify, from package.json scripts and the framework:\n - The production build command (vite build, next build, ng build,\n webpack --mode production, etc.) and its output directory\n (commonly dist/, build/, or .next/).\n - Whether the framework produces a STATIC bundle (Vite, CRA, Angular,\n plain Webpack → a dist/ of static files) or needs a NODE SERVER at\n runtime (Next.js server mode, Remix, Nuxt SSR). Static bundles are\n served by nginx; server-rendered apps run their production start\n command (next start, etc.) as a normal routed service.\n\n BUILD-TIME ENV VARS:\n Bundlers BAKE build-time public vars into the output, so they must be set\n in the environment BEFORE the build runs — setting them only in compose\n at runtime is too late for a static bundle. Find every build-time-public\n var (VITE_*, NEXT_PUBLIC_*, REACT_APP_*, ng-injected config, etc.),\n especially the browser-facing API base URL, and set them just before the\n build using \\${SANDBOX_URL} for any URL the browser must reach\n (e.g. VITE_API_URL=\\${SANDBOX_URL}/api). See Artifacts → PRODUCTION BUILD\n for where the build runs.\n\n COST: production builds are slower and more memory-hungry than starting a\n dev server. Vite/webpack builds can OOM the default Node heap on large\n apps — be ready to raise it (NODE_OPTIONS=--max-old-space-size=8192) and\n expect the build to add minutes to the build step.\n\n6. WORKERS & DAEMONS TO DISABLE\n Sandbox typically only needs the API server + web frontend.\n Disable: job runners, schedulers, stall detectors, cron, coordinators.\n Look for env flags like WORKER_ENABLED, SCHEDULER_ENABLED, etc.\n`.trim(),\n};\n\nconst ARTIFACTS: GuideSection = {\n title: 'Artifacts',\n slug: 'artifacts',\n content: `\nArtifacts are the files that define your sandbox. Write them locally, then upload:\n canary sandbox artifact create <template-id> <type> <local-file>\n\nIMPORTANT — ARTIFACT SCOPE:\n Artifact uploads are TEMPLATE-level, not instance-level. Uploading an artifact\n updates the template definition and pushes the file to the AUTHORING instance\n only. Running instances provisioned from the template are NOT updated — they\n use whatever was baked in at provision time. To apply artifact changes to an\n existing non-authoring instance, you must rebuild it:\n canary sandbox build <instance-id>\n\nPLATFORM ARCHITECTURE:\n The sandbox filesystem is split into two areas:\n - /workspace checked-out repo and user-owned files\n - /workspace/.canary platform-managed compose, config, and helper scripts\n\n Your compose file is saved as /workspace/.canary/docker-compose.override.yml and\n merged with the platform's base compose at /workspace/.canary/docker-compose.yml,\n which provides:\n - nginx-proxy: reverse proxy on port 80 (routes based on sandbox.route labels)\n - sandbox-agent: platform agent on port 8080\n - minio: S3-compatible object store at http://minio:9000 (creds:\n minioadmin / minioadmin). See MINIO BUCKET below — the platform\n doesn't auto-wire the endpoint env var or create buckets for you.\n\n You do NOT need to declare any of these in your docker-compose.yml — the\n platform injects them. Defining a service with one of these reserved\n names is rejected at artifact-upload time with code\n SANDBOX_RESERVED_SERVICE_NAME. The full reserved list is:\n nginx-proxy\n sandbox-agent\n minio\n minio-init\n docker-socket-proxy\n\n CONTAINER PATHS — the bind mount maps the repo root to /app inside\n containers, while file artifacts are uploaded under /workspace/.canary\n on the host. So the same file has two paths:\n /workspace/.canary/foo.ts ← the path you upload to from the CLI\n /app/.canary/foo.ts ← the path your compose command/services\n reference inside the container\n Reference the in-container path in any compose \\`command\\` array, exec\n call, or script file path that runs inside a service. The host-side\n /workspace path only appears in startup.sh / setup.sh (which run on\n the host, not inside a container).\n\n MINIO BUCKET (S3 emulation):\n The platform provides a MinIO container at http://minio:9000 but does\n NOT inject AWS_ENDPOINT_URL_S3 or create your bucket. You must:\n\n 1. Wire the endpoint into your service env in compose:\n AWS_ENDPOINT_URL_S3: http://minio:9000\n AWS_ACCESS_KEY_ID: minioadmin\n AWS_SECRET_ACCESS_KEY: minioadmin\n AWS_REGION: us-east-1\n S3_BUCKET_NAME: <your-bucket>\n\n 2. Create the bucket on every build via a one-shot service:\n mc-init:\n image: minio/mc\n restart: \"no\"\n entrypoint:\n - sh\n - -c\n - >\n mc alias set local http://minio:9000 minioadmin minioadmin &&\n mc mb -p local/\\${S3_BUCKET_NAME:-app-bucket} ||\n true\n\n Most AWS SDK v3 clients honor AWS_ENDPOINT_URL_S3 natively. If your\n app passes a custom S3 client, also set forcePathStyle: true (MinIO\n doesn't support virtual-host-style bucket addressing).\n\n Compose still runs with /workspace as the project directory. That means user\n compose content keeps the same repo-root-relative behavior for build contexts,\n bind mounts, and referenced files.\n\n COMPOSE INVOCATION: use the pre-installed \"sb-compose\" wrapper on the host.\n It expands to docker compose with the canonical flag set:\n docker compose --project-directory /workspace \\\\\n --env-file /workspace/.canary/.env \\\\\n -f /workspace/.canary/docker-compose.yml \\\\\n -f /workspace/.canary/docker-compose.override.yml \\\\\n \"$@\"\n Call it from startup scripts, action scripts, and run-command the same way:\n sb-compose exec -T app-api bash -c 'bun run build'\n sb-compose ps\n sb-compose logs --tail 50 app-api\n The wrapper is available as /usr/local/bin/sb-compose (symlink) and\n /workspace/.canary/sb-compose. If you invoke raw \"docker compose\" yourself\n you must pass the same flags — otherwise paths and variable interpolation\n (\\${SANDBOX_URL} et al.) will resolve incorrectly.\n\n The platform injects a shared Docker network. On \"build\", the sequence is:\n 1. Write file artifacts to their destination paths\n 2. Run setup script (if present)\n 3. docker compose down (cleanup previous containers)\n 4. docker compose build + up\n 5. Reload nginx\n 6. Wait for long-running services to accept \"sb-compose exec\" before continuing\n 7. Run startup script (if present)\n\n Step 6 is automatic. You don't need to add your own \"wait for postgres\"\n loops at the top of startup.sh — by the time startup.sh runs, every\n non-one-shot service is exec-able. Service healthchecks inside the\n containers (e.g. postgres pg_isready) are still your responsibility if\n your app needs to wait on them.\n\n Do NOT define services named \"nginx-proxy\", \"sandbox-agent\", or \"minio\"\n in your compose.\n\nDOCKERFILE (type: dockerfile)\n A base image that provides the runtime. Source code is volume-mounted, NOT copied in.\n Written to /workspace/.canary/Dockerfile.sandbox (avoids conflicting with the repo's own Dockerfile).\n Reference it in compose as: build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n\n Structure:\n FROM <runtime-base>\n RUN apt-get update && apt-get install -y <system-deps>\n RUN npm install -g <global-tools>\n WORKDIR /app\n\n Rules:\n - NO \"COPY . .\" — code comes via volume mount at /workspace\n - NO \"RUN npm build\" — the production build runs on the instance as a\n compose service (see PRODUCTION BUILD), where \\${SANDBOX_URL} and\n build-time env are available. Baking it into the image would freeze\n build-time env to image-build values.\n - Include ALL system deps (chromium, ffmpeg, python, etc.)\n - For browser automation: use mcr.microsoft.com/playwright as base\n - Do NOT use ENTRYPOINT — it breaks one-shot services (deps, migrate) whose\n command gets passed as args to the entrypoint instead of running directly\n\nDOCKER COMPOSE (type: docker-compose)\n Defines all services. Platform handles networking and nginx routing.\n\n Typical services:\n deps (one-shot) — installs dependencies into named volume, restart: \"no\"\n database — official image with healthcheck (postgres, mysql, etc.)\n cache — redis/memcached with healthcheck\n web-build (one-shot) — runs the production frontend build into the bind-mounted\n repo tree, restart: \"no\" (see PRODUCTION BUILD)\n app-api — application server, PRODUCTION command (NODE_ENV=production,\n no --hot / --watch)\n app-web — static frontend served by nginx:alpine from the build\n output (see PRODUCTION BUILD). SSR frameworks (Next.js\n server mode, etc.) run their production start command\n instead of an nginx static server.\n\n Do NOT use a migrate service in compose. If the migrate container fails once\n (bad SQL, missing env var, wrong path), depends_on with\n service_completed_successfully is permanently unmet — app-api will never\n start, even after you fix the migration. \"docker compose restart\" and\n \"docker compose up\" won't help; only \"docker compose down\" + fresh up works.\n Put migrations in the startup script instead (runs after compose up).\n\n ROUTING LABELS — declare how nginx should route to your services:\n services:\n app-api:\n labels:\n sandbox.route: \"/api\"\n sandbox.port: \"3000\"\n app-web:\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n The platform reads these labels and generates nginx config on \"build\".\n If no labels, all traffic routes to the first non-infrastructure service.\n\n PREFIX STRIPPING: the generated nginx location uses a trailing-slash\n proxy_pass (proxy_pass http://<service>:<port>/), so the matched prefix\n is stripped from the upstream request. With sandbox.route: \"/api\", a\n browser hit to \"/api/health\" is delivered to the upstream as \"/health\".\n Pick VITE_API_URL, OAuth redirect URIs, etc. accordingly.\n\n ENV VAR STRATEGY — use \\${VAR} syntax, values come from config + .env:\n - Auto-wired (hardcode): DATABASE_URL=postgres://user:pass@postgres:5432/db\n - From config: ANTHROPIC_API_KEY=\\${ANTHROPIC_API_KEY}\n - Platform-injected: VITE_API_URL=\\${SANDBOX_URL}/api\n - Disabled: WORKER_ENABLED=false\n\n SANDBOX_URL is auto-injected by the platform (e.g. https://<id>.env.runcofactor.com).\n Use it for any URL a browser needs to reach the sandbox.\n\n Rules:\n - Use named volumes for data persistence (postgres-data, redis-data, etc.)\n - Bind-mount the repo (.:/app) rather than using named volumes for\n node_modules. Per-package node_modules named volumes silently shadow the\n bind mount on any service that doesn't mount them — a common monorepo\n trap where \"bun run build\" fails because tsup is nowhere to be found.\n The sandbox host is Linux EC2; bind mounts are fast.\n - DO NOT define networks — the platform handles networking\n - DO NOT expose ports to the host — nginx handles routing\n - Keep relative paths repo-root-relative; compose runs from /workspace even\n though the platform-managed compose files live in /workspace/.canary\n - Escape shell variables in \"command:\" strings with $$ so compose leaves\n them for the shell (e.g. command: [\"bash\", \"-c\", \"exec foo $$BAR\"]).\n Unescaped \\${VAR} is substituted at compose-time from the env file.\n\nPRODUCTION BUILD (static frontends)\n Serve the frontend as a production build, not a dev server. Two pieces:\n\n 1. BUILD as a one-shot compose service — in its OWN container, NOT inside the\n api/app container. A production build is CPU + disk-I/O heavy; if it runs\n inside the api container it starves the api's /health endpoint and the\n provision health gate fails the whole sandbox (nginx 502). The setup script\n can't do it either (it runs on the HOST, where app runtimes don't exist, and\n before deps installs). So: a dedicated one-shot service that depends on deps.\n\n Build the RIGHT set of packages. In a monorepo, build EVERY workspace\n package that exports from dist/ that is imported by BOTH the frontend AND\n the BACKEND — not just the UI. If the api imports a dist-exporting package\n whose dist is missing, the api crash-loops \"Cannot find module @scope/pkg\"\n and never answers /health. Find them: grep the api's imports for @scope/*\n and cross-check each package.json \"main\"/\"exports\" for \"./dist/\". Build the\n leaf packages first, then the app:\n web-build:\n build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n working_dir: /app\n volumes:\n - .:/app\n environment:\n VITE_API_URL: \\${SANDBOX_URL}/api # build-time public vars FIRST\n NODE_OPTIONS: --max-old-space-size=8192 # raise heap if the build OOMs\n # nice -n 19 keeps the api responsive if anything overlaps. Build the\n # api's dist-dependencies AND the ui, then the app bundle.\n command: [\"bash\",\"-c\",\"nice -n 19 bash -c 'cd /app/packages/foo && bun run build && cd /app/packages/bar && bun run build && cd /app/apps/web && bun run build'\"]\n depends_on:\n deps:\n condition: service_completed_successfully\n restart: \"no\"\n Compose interpolates \\${SANDBOX_URL} at \"up\" time, so the bundle bakes the\n correct browser-facing API URL. The output (dist/) lands in the\n bind-mounted repo tree, where the nginx service below mounts it.\n\n CACHEABILITY: if you want this build cached across sandboxes (see Build\n Caching), the output must NOT bake \\${SANDBOX_URL} — a cached dist is reused on\n other sandboxes. Resolve the API origin at runtime from the browser location\n (web and api are same-origin behind the proxy, so a relative \"/api\" works\n everywhere) instead of baking VITE_API_URL. Then label web-build with\n sandbox.cache + sandbox.cache-inputs/paths.\n\n SERIALIZE the build BEFORE the health gate: give the nginx service a\n depends_on web-build (service_completed_successfully). That makes\n \"docker compose up\" block until the build finishes, so the gate only probes\n the api once the host is idle. Restart the api in the STARTUP script\n afterwards so it boots onto the idle host with all dist packages present.\n\n 2. SERVE the output with a lightweight nginx:alpine service that depends on\n the build completing. Mount the PARENT of the output dir (e.g. apps/web),\n NOT the dist dir directly, and point nginx root at the dist SUBPATH:\n app-web:\n image: nginx:alpine\n volumes:\n - ./apps/web:/srv:ro # mount the PARENT, root is /srv/dist\n - ./.canary/nginx-spa.conf:/etc/nginx/conf.d/default.conf:ro\n depends_on:\n web-build:\n condition: service_completed_successfully\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n WHY mount the parent: bundlers (vite, webpack) DELETE and recreate the\n output dir on build, giving it a NEW inode. Docker creates an empty\n dist bind-source at compose-up time; a direct \"dist:/usr/share/nginx/html\"\n bind pins nginx to that stale empty inode and it serves 403 \"directory\n index forbidden\" forever. Mounting the parent (a stable inode) and letting\n nginx resolve root/<uri> per request always sees the current dist.\n\n Provide an SPA-fallback nginx config as a file artifact so client-side\n routes (deep links / hard refresh) don't 404 — unknown paths serve\n index.html and the client router takes over:\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n nginx-spa.conf:\n server {\n listen 80;\n root /srv/dist; # the dist SUBPATH of the mounted parent\n index index.html;\n gzip on;\n gzip_types text/css application/javascript application/json image/svg+xml;\n location /assets/ {\n try_files $uri =404;\n expires 1y;\n add_header Cache-Control \"public, immutable\";\n }\n location / {\n try_files $uri $uri/ /index.html;\n add_header Cache-Control \"no-cache\";\n }\n }\n\n (Match root to your framework's output dir under the mounted parent — e.g.\n /srv/build for a build/ output. The hashed-asset location block assumes a\n /assets/ dir — adjust to your bundler's asset path.)\n\n WHY: nginx serving static files has no host allowlist (no \"This host is not\n allowed\"), ships compressed/hashed/cacheable assets, and is far more\n responsive than a dev server streaming hundreds of uncompressed modules. The\n trade-off is no hot-reload and a longer, heavier build step — acceptable\n because sandboxes demo a BUILT app.\n\n SSR / server-rendered frameworks (Next.js server mode, Remix, Nuxt SSR):\n skip the nginx static server. Build in the one-shot service, then run the\n framework's production start command (e.g. next start) as a normal routed\n service with sandbox.route \"/\" and its listen port.\n\nSETUP SCRIPT (type: setup-script)\n Runs BEFORE docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/setup.sh.\n\n IMPORTANT: The host is a bare EC2 instance with only docker and basic linux tools.\n App runtimes (node, bun, python, ruby) are NOT available on the host — they live\n inside your Docker images. Do NOT run app commands directly here.\n\n Script authoring rules:\n - Treat /workspace as the repo root for any file access or repo command\n - Do not assume the current directory matches the script's own path\n - Use explicit paths or start with \"cd /workspace\" before repo-relative work\n\n Use for host-level preparation only:\n - Installing system packages (apt-get, yum)\n - Downloading external tools or binaries\n - Generating config files programmatically (prefer file artifacts for static files)\n\n For most apps, you won't need a setup script — file artifacts and the startup\n script cover the common cases. Skip this if you don't need host-level prep.\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Non-zero exit is logged as a warning but does not block the build\n\nSTARTUP SCRIPT (type: startup-script)\n Runs AFTER docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/startup.sh.\n\n This is where most app setup happens. Containers are running, so you can use\n \"sb-compose exec\" to run commands inside them with the app runtime.\n\n Script authoring rules:\n - Start with \"cd /workspace\" or use explicit /workspace/... paths\n - Do not assume the script runs from /workspace/.canary\n - Prefer sb-compose exec for anything that needs the app runtime\n - Use -T with exec (no TTY allocation — startup runs headless)\n\n Use for:\n - DB migrations via ORM CLI (use \"bun run\", NOT bunx — bunx downloads fresh)\n - Building workspace packages that export from dist/\n - Waiting for services to be healthy\n - Triggering initial data loads\n\n POST-MIGRATION RESTART (REQUIRED for many ORMs):\n ORMs that introspect the schema at boot (Drizzle/effect-sql, Prisma with\n prepared statements, SQLAlchemy with autoload, etc.) cache the pre-migration\n schema on the first query. After you run migrations, you MUST restart the\n API service, otherwise you'll see errors like:\n relation \"<table>\" does not exist\n even though psql confirms the table is there. Add \"sb-compose restart\" for\n any service whose client introspects the schema at boot.\n\n Example (Bun monorepo with Drizzle):\n #!/bin/bash\n set -e\n # Build dist-exporting workspace packages the app/web import from\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/ui && bun run build'\n # Run DB migrations inside the API container\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/db && bun run drizzle-kit migrate'\n # Restart services so they pick up the fresh schema + built UI\n sb-compose restart app-api app-web\n # Wait for API to be healthy\n for i in $(seq 1 30); do\n sb-compose exec -T app-api curl -sf http://localhost:3000/health && break\n sleep 2\n done\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Don't pre-wait for services to START — the platform handles that before\n startup.sh runs (see Compose Architecture section). Do wait for service\n *readiness* (app health, schema migrations applied) when your app\n depends on it.\n\nFILE (type: file)\n Arbitrary sandbox-owned files written to the instance at a specific path.\n Use for config files that need to exist before services start (e.g. Vite\n wrapper configs, one-shot scripts that need app dependencies).\n\n !!! File artifacts MUST be written under /workspace/.canary/ !!!\n The repo tree (everything outside .canary/) is user-owned. Sandbox\n helpers live in .canary/ so they never collide with real files and\n are never committed back to the repo. The API will reject any\n --path value outside /workspace/.canary/ with\n SANDBOX_FILE_ARTIFACT_OUTSIDE_CANARY.\n\n If your script needs app dependencies (e.g. \\`postgres\\` from the api\n package), put a tiny package.json next to it in .canary/scripts/ and\n \\`bun install\\` in the setup-script, OR invoke it via\n \\`sb-compose exec -T <svc> ...\\` so it runs inside a container that\n already has them.\n\n Upload with --path to specify the destination:\n canary sandbox artifact create <template-id> file <local-file> --path /workspace/.canary/<dest>\n\n Example — SPA fallback config for the static web server (see PRODUCTION BUILD):\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n ONE ROW PER FILENAME. File artifacts are keyed by (template, filename).\n Uploading a file with the same filename as an earlier upload REPLACES\n the earlier row. The CLI prints \"(replaced)\" on such uploads, and the\n next build removes the previous destination path so the instance\n filesystem mirrors the template set exactly.\n\n If you want to retire a file, just upload something else with a\n different filename — the stale file is cleaned up on next build via\n .canary/.artifact-paths (a manifest the platform maintains).\n\n File artifacts are written before the setup script runs, so they're\n available for both setup and compose services.\n\nSEED SCRIPT (type: seed-script)\n Runs once on first provision to populate demo data. Optional.\n Written to /workspace/.canary/seed.sh.\n Skipped on subsequent builds (checks /workspace/.canary/.seeded).\n\nCONFIG SCHEMA\n Define which env vars need user input. Set when completing the docker-compose checklist item:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[\n {\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"Anthropic API key\"},\n {\"key\": \"OPENAI_API_KEY\", \"required\": false, \"sensitive\": true, \"description\": \"OpenAI API key\"}\n ]'\n\n Set defaults:\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n`.trim(),\n};\n\nconst VERIFICATION: GuideSection = {\n title: 'Verification & Iteration',\n slug: 'verify',\n content: `\nAfter running \"canary sandbox build\", verify the sandbox is working. The iteration loop:\n upload artifacts → build → check diagnostics → read logs → fix → re-upload → rebuild\n\nCOMMANDS:\n # Check all container statuses at a glance\n canary sandbox diagnostics <instance-id>\n\n # Read logs for a specific service\n canary sandbox logs <instance-id> --service app-api\n canary sandbox logs <instance-id> --service app-web\n canary sandbox logs <instance-id> --service postgres\n\n # Run arbitrary commands on the instance (runs on the host VM by default)\n canary sandbox run-command <instance-id> \"docker compose ps\"\n canary sandbox run-command <instance-id> \"echo hello\"\n\n # Run commands inside a specific Docker container\n canary sandbox run-command <instance-id> --service app-api \"bun --version\"\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n\n # Test health endpoints\n canary sandbox run-command <instance-id> \"curl -s http://localhost:3000/health\"\n canary sandbox run-command <instance-id> \"curl -s -o /dev/null -w '%{http_code}' http://localhost:80/\"\n\n # Test with sandbox hostname (for host-restriction issues)\n canary sandbox run-command <instance-id> \\\\\n \"curl -s -H 'Host: <instance-id>.env.runcofactor.com' http://localhost:80/\"\n\nSANDBOX URL:\n https://<instance-id>.env.runcofactor.com\n\nIN-BOX BROWSER VERIFICATION (sandbox agent):\n The agent running inside the sandbox has a headless Chromium and can drive the\n running app directly to confirm setup — no external tester needed. The app is\n in a sibling docker-compose project; target it over the compose network.\n\n docker compose ps # find app service + port\n cofactor session start --headless --url http://<svc>:<port> [--credential <n>]\n cofactor session snapshot # page accessibility tree\n cofactor session click --ref <ref> # act on a ref\n cofactor session type --ref <ref> --text \"...\" --submit\n cofactor session wait --text \"<expected copy>\" # assert it loaded\n cofactor session console --only-errors # surface JS errors\n cofactor session stop\n\n The session daemon idle-exits after ~60s; just start again if a command\n reports no active session. Run this AFTER the app is healthy (get_status /\n diagnostics), then record the result via update_checklist.\n\nIF SOMETHING FAILS:\n 1. Read the logs for the failing service\n 2. Diagnose the root cause (missing env var, failed build, wrong port, etc.)\n 3. Fix the ARTIFACT — not just the running container\n 4. Re-upload: canary sandbox artifact create <template-id> <type> <file>\n 5. Rebuild: canary sandbox build <instance-id>\n 6. Verify again\n\nWHEN A CLI COMMAND TIMES OUT AT 30s:\n Long sandbox operations (build, edit, artifact create on first push,\n occasionally diagnostics) can exceed the gateway window even though\n the work is still running server-side. The CLI prints:\n Error: Request to https://api.trycanary.ai/... timed out after 30000ms.\n\n Don't retry blindly — a retry can spawn duplicate work (e.g. a second\n authoring instance). Instead, poll for the actual state:\n\n canary sandbox template <template-id> # status: provisioning/authoring/verified\n canary sandbox diagnostics <instance-id> # container/app health\n canary sandbox list # running instances\n\n Only retry once you've confirmed via polling that the prior call\n truly didn't take effect.\n\nWHEN VERIFY HANGS:\n \\`canary sandbox verify\\` now streams container state and failing-service\n log tails every ~30s while the probe's app is unreachable. If you see:\n\n >> nginx-proxy is restart-looping because upstream '<service>' (...) is exited.\n Read that service's logs above, not nginx's.\n\n ignore nginx entirely and read the upstream's tail — nginx is the symptom,\n not the cause. Common underlying causes:\n - the upstream service crashed at startup (bad env var, missing file,\n import error)\n - a file artifact was lost because you re-uploaded over its filename\n (shouldn't happen any more — the build-time reconcile handles this —\n but watch for it if you're on an old template)\n\n On failure, the probe instance is preserved by default so you can\n \\`canary sandbox run-command <id>\\` into it. Pass\n --auto-terminate-on-failure for CI.\n\nCOMMON ISSUES:\n\n \"Invalid environment\" / env validation fails\n App has strict env var validation. Read the env schema. Add ALL required vars to compose.\n\n Migration fails with \"SST links not active\" / cloud SDK error\n Migrate script depends on cloud tooling. Use the ORM CLI directly in the startup script\n via sb-compose exec: sb-compose exec -T app-api bash -c 'bun run drizzle-kit migrate'\n DATABASE_URL should be hardcoded in the compose environment section.\n\n \"Cannot find module\" / package not built\n Monorepo package exports from dist/ but hasn't been built.\n Add build step to startup script via sb-compose exec (runs after compose up).\n\n \"Cannot find package 'X'\" from a file-artifact script\n Bun resolves node_modules starting from the script's directory. File\n artifacts live under /workspace/.canary/ which has no node_modules by\n default. Two fixes:\n (a) add a /workspace/.canary/scripts/package.json declaring the\n dep (e.g. \"postgres\") and run \\`bun install\\` from the\n setup-script / inside a sb-compose exec.\n (b) don't run the script on the host at all — invoke it via\n \\`sb-compose exec -T <app-service> bun /app/.canary/scripts/foo.ts\\`\n so it resolves against the workspace's own node_modules. Replace\n <app-service> with your template's actual service name.\n\n Blank page — frontend never renders (served by a dev server)\n The web service is a dev server (Vite/webpack-dev-server) shipping hundreds\n of uncompressed, no-cache modules. On a remote sandbox these trickle in too\n slowly for the SPA to mount, so the page stays blank. Confirm with:\n curl -s <SANDBOX_URL>/ | grep '<script'\n Dev mode references /@vite/client or /src/main.tsx; a production build\n references hashed /assets/*.js. Fix: serve a production build (see\n Artifacts → PRODUCTION BUILD), not a dev server.\n\n Deep links / page refresh 404 (SPA routing)\n nginx serves static files but a client-side route (e.g. /settings) or a\n hard refresh returns 404 — the static server has no SPA fallback. Add\n \"try_files $uri $uri/ /index.html;\" to the nginx config (see Artifacts →\n PRODUCTION BUILD) so unknown paths serve index.html and the client router\n takes over.\n\n Frontend loads but API calls hit the wrong URL / localhost\n The browser-facing API URL was not set as a BUILD-TIME var before the\n build, so the bundle baked a default (often localhost:3000). Static\n bundles read build-time vars at BUILD time, not runtime — setting them\n only in the app-web/runtime compose env is too late. Set the public var on\n the web-build service env (or before the build command), using\n \\${SANDBOX_URL}, then rebuild:\n VITE_API_URL: \\${SANDBOX_URL}/api # or NEXT_PUBLIC_*, REACT_APP_*, etc.\n\n API 502 / \"Cannot find module '@scope/pkg'\" crash-loop after going prod\n The api imports a workspace package that exports from dist/, but that\n package's dist was never built — so the api crash-loops and the health gate\n fails the whole provision with a 502. Easy to miss because the FRONTEND\n build (ui) works. Build EVERY dist-exporting package the api imports in the\n web-build service (see Artifacts → PRODUCTION BUILD), not just the ui ones.\n A warm AMI can hide this (it baked the dist in); a fresh provision exposes it.\n\n 403 \"directory index forbidden\" from the static server\n nginx is bound to the stale empty dist inode the bundler replaced. Mount the\n PARENT dir and set root to the dist subpath (see Artifacts → PRODUCTION\n BUILD), or restart the nginx service after the build.\n\n Build is killed / \"JavaScript heap out of memory\"\n The production build exhausted the Node heap (common on large Vite/webpack\n builds). Raise it on the web-build service before the build:\n NODE_OPTIONS: --max-old-space-size=8192\n If it still OOMs, bump the instance size (canary sandbox set-size) and/or\n the heap further.\n\n Stale page after a rebuild (old dist served)\n nginx is serving a previous build because the build step didn't re-run or\n its output dir wasn't refreshed. Confirm the web-build service ran in this\n build cycle (canary sandbox logs <id> --service web-build), that it wrote\n to the dir nginx mounts, and — if you ever build via the startup script\n instead — that nginx was restarted (sb-compose restart app-web). Hashed\n asset filenames change per build; if index.html still references old\n hashes, the build didn't land.\n\n run-command times out\n Long-running operation exceeds the 55s SSM command timeout. Write a script file and run in background:\n canary sandbox run-command <id> \"cat > /tmp/run.sh << 'SCRIPT'\n #!/bin/bash\n <commands>\n SCRIPT\n chmod +x /tmp/run.sh && nohup /tmp/run.sh > /tmp/run.log 2>&1 & echo started\"\n Then check: canary sandbox run-command <id> \"tail -20 /tmp/run.log\"\n\n \"Resolving dependencies\" hangs in migration container\n bunx/npx downloads a fresh copy instead of using project deps.\n Use \"bun run drizzle-kit migrate\" instead of \"bunx drizzle-kit migrate\".\n\n Containers can't resolve each other by name\n Don't use docker run — use docker compose for everything.\n\nRUNNING TESTS:\n Two approaches depending on your needs:\n\n Option 1 — Install runtime on host (no Docker needed):\n Use the setup script to install the runtime on the host VM, then run\n tests directly. Good for unit tests that don't need services like a database.\n\n Example setup-script artifact:\n #!/bin/bash\n curl -fsSL https://bun.sh/install | bash\n export PATH=\"$HOME/.bun/bin:$PATH\"\n\n Then: canary sandbox run-command <instance-id> \"bun test\"\n\n Option 2 — Run inside a container (--service flag):\n Route commands into a running Docker container. Good for integration\n tests that need access to the database or other services.\n\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n canary sandbox run-command <instance-id> --service app-api \"bun test:integration\"\n\n \\${SANDBOX_URL} is blank / env vars from config not in containers\n The \"build\" command writes /workspace/.canary/.env (with SANDBOX_URL and all config values)\n before running docker compose up. If the .env is missing or stale, re-run:\n canary sandbox build <instance-id>\n You can also trigger a .env refresh by setting any config value:\n canary sandbox config set <template-id> <key> <value>\n\n .env values look empty when I cat the file\n Values are written single-quoted (KEY='value'). Short values can look like\n KEY='' at a glance even when they have content — it's just the quotes. Run\n \"wc -c\" on a line, or \"docker compose exec app-api env | grep KEY\", to\n confirm the actual value landed.\n`.trim(),\n};\n\nconst CHECKLIST: GuideSection = {\n title: 'Checklist',\n slug: 'checklist',\n content: `\nThe checklist tracks your progress toward a working sandbox. View it with:\n canary sandbox checklist <template-id>\n\nEach item has a status: pending, completed, skipped, or failed.\nItems marked with * are actionable (their dependencies are met).\n\nCOMPLETING ITEMS:\n canary sandbox checklist complete <template-id> <item-id> \\\\\n --status <success|failure> \\\\\n --summary \"What you did\"\n\n Example:\n canary sandbox checklist complete <template-id> environment-analyzed \\\\\n --status success \\\\\n --summary \"Bun monorepo: Postgres, Redis. Vite production build served by nginx, API on 3000.\"\n\nSKIPPING ITEMS:\n canary sandbox checklist skip <template-id> <item-id>\n\n Use when an item doesn't apply (e.g., no seed data needed, no integration tests).\n\nSPECIAL: docker-compose-created accepts --config-schema to define user-configurable env vars:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[{\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"API key\"}]'\n\nTYPICAL CHECKLIST ORDER:\n 1. Provisioned — done automatically when you create the template\n 2. Code Cloned — done automatically\n 3. Environment Analyzed — complete after your research phase\n 4. Setup Script Created — complete after uploading setup-script artifact\n 5. Dockerfile Created — complete after uploading dockerfile artifact\n 6. Docker Compose Created — complete after uploading docker-compose artifact (with config-schema)\n 7. Unit Tests Runnable — complete or skip. Install runtime on host via setup script,\n or use --service to run inside a container\n 8. Integration Tests Runnable — complete or skip. Use --service to run inside a\n container with access to database and other services\n 9. Seed Script Created — complete or skip\n 10. Server Running — complete after API responds to health checks\n 11. Server Accessible — complete after nginx routes to the app successfully\n 12. Template Verified — provision a fresh instance and verify it comes up healthy\n\nTEMPLATE VERIFICATION (final step):\n Run one command — it provisions a fresh instance, waits for the app to\n come up, marks template-verified, and terminates the instance:\n canary sandbox verify <template-id>\n\n Pass --keep-instance to leave the instance running afterwards (useful if\n you want to poke at it).\n\n This catches issues that authoring hides:\n - Config values set manually but not in configDefaults\n - SANDBOX_URL or other platform vars not properly referenced\n - Startup scripts that assume manual setup already happened\n\n Template status flow:\n provisioning → authoring → verified\n\n 'verify' is the one transition from authoring to verified, and it is one-way\n — there is no re-authoring path back to 'authoring' from 'verified'. To make\n an AI-assisted edit to a verified template's artifacts, provision a sandbox\n with the agent attached:\n\n canary sandbox provision <template-id> --with-agent\n\n then iterate via in-sandbox agent chat or 'canary sandbox artifact create',\n and re-run 'canary sandbox verify' against the same template id when you're\n happy with the changes. To start over from scratch, use 'regenerate' to\n delete existing artifacts, or create a new template.\n`.trim(),\n};\n\nconst ACTIONS: GuideSection = {\n title: 'Actions',\n slug: 'actions',\n content: `\nActions are template-defined, parameterized operations that run against sandbox\ninstances. Unlike run-command (arbitrary shell), actions have typed input/output\nschemas, are reusable across instances, and produce an auditable execution log.\n\nIN-BOX AGENT: the sandbox agent defines actions with the publish_action tool,\nlists them with list_actions, and RUNS them with execute_action (by slug, with\nparams) — all scoped to its own instance, no instance id needed. The CLI\nequivalents below are for external operators.\n\nUSE CASES:\n - Provision a user credential (username, password, role)\n - Seed demo data for a specific scenario\n - Run database migrations\n - Reset application state\n - Toggle feature flags\n\nMANAGING ACTIONS:\n\n List actions on a template:\n canary sandbox action list <template-id>\n\n View an action definition (including script):\n canary sandbox action get <template-id> <slug>\n\n Create or update an action:\n canary sandbox action create <template-id> <slug> \\\\\n --label \"Provision Credential\" \\\\\n --description \"Create a user account in the sandbox app\" \\\\\n --icon UserPlus \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n --credential-output-config is optional. When set, a successful execution\n also creates/updates a credential in the property's vault — see \"Linking\n actions to Canary credentials\" below.\n\n Delete an action:\n canary sandbox action delete <template-id> <slug>\n\nEXECUTING ACTIONS:\n\n Execute against a running instance:\n canary sandbox action execute <instance-id> <slug> \\\\\n --param username=testuser \\\\\n --param password=s3cret \\\\\n --param role=admin\n\n Or pass all params from a JSON file:\n canary sandbox action execute <instance-id> <slug> \\\\\n --params-file ./params.json\n\n View execution history:\n canary sandbox action executions <instance-id>\n\nSLUG NAMING:\n Slugs must be lowercase alphanumeric with hyphens, starting with a letter:\n provision-credential ✓\n seed-demo-data ✓\n reset-state ✓\n 123-bad ✗\n UPPER_CASE ✗\n\nINPUT SCHEMA:\n A JSON Schema file defining the action's parameters. The schema drives\n validation, CLI prompting, and UI form generation.\n\n Example (provision-user-input.json):\n {\n \"type\": \"object\",\n \"properties\": {\n \"username\": {\n \"type\": \"string\",\n \"description\": \"Username to create\",\n \"minLength\": 1\n },\n \"password\": {\n \"type\": \"string\",\n \"description\": \"Password for the user\",\n \"sensitive\": true\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"Role to assign\",\n \"enum\": [\"admin\", \"user\", \"viewer\"],\n \"default\": \"user\"\n }\n },\n \"required\": [\"username\", \"password\"]\n }\n\n The \"sensitive\" extension marks params that should be masked in UI and\n injected securely (via tempfile, not env var) on the instance.\n\nSCRIPT CONVENTIONS:\n Action scripts are bash, executed on the instance via SSM. Parameters are\n injected as INPUT_<KEY> environment variables, where <KEY> is the param\n name uppercased with non-alphanumeric chars replaced by '_'. There is NO\n camelCase → snake_case conversion. Examples:\n orgName → INPUT_ORGNAME (NOT INPUT_ORG_NAME)\n org_name → INPUT_ORG_NAME\n api-key → INPUT_API_KEY\n If you want INPUT_FOO_BAR, name the input schema key 'foo_bar' or 'foo-bar'.\n\n The last line of stdout that is valid JSON is parsed as the structured result.\n\n EXECUTION ENVIRONMENT — same architecture as startup scripts:\n Scripts run on the HOST (bare EC2 instance). The host has docker, curl,\n and basic linux tools — but NO app runtimes (node, bun, python, ruby).\n Those live inside your Docker containers.\n\n Use \"docker compose exec\" to run commands that need the app runtime:\n sb-compose exec -T app-api node -e \"...\" # Node.js\n sb-compose exec -T app-api bun run seed.ts # Bun\n sb-compose exec -T app-db psql -U app -d db # Database CLI\n\n Use bare host commands for docker/system operations:\n sb-compose restart app-api # Restart a service\n curl -s http://localhost:3000/health # Health check\n cat /workspace/.canary/.env # Read config\n\n The -T flag is required for docker compose exec in non-interactive (SSM)\n contexts. Without it, exec tries to allocate a TTY and hangs.\n\n Working directory is /workspace (the mounted repo root). Keep script logic\n repo-root-relative by starting with \"cd /workspace\" when needed.\n\n AVOID INLINE CODE IN ACTION SCRIPTS:\n Never embed application-language code (JS, Python, etc.) directly in the\n bash script via \"bun -e\" or \"node -e\". Shell escaping of quotes, template\n literals, and variable interpolation is fragile and hard to debug.\n\n Instead, write complex logic as a separate file artifact and execute it:\n 1. Write a script file (e.g. provision-user.ts)\n 2. Upload as a file artifact:\n canary sandbox artifact create <template-id> file provision-user.ts \\\\\n --path /workspace/scripts/provision-user.ts\n 3. Reference it from the action script:\n sb-compose exec -T app-api bun /app/scripts/provision-user.ts\n\n Parameters are available in the action script as INPUT_<KEY> env vars.\n Pass them to the file artifact via env or args:\n sb-compose exec -T -e EMAIL=\"$INPUT_EMAIL\" app-api bun /app/scripts/provision-user.ts\n\n Example — provision a user (two-file pattern):\n\n Place the .ts file under the workspace whose node_modules has the deps\n (e.g. apps/api when only apps/api depends on \\`postgres\\`). Bun resolves\n modules from the script's directory walking up — if the dep isn't hoisted\n to the repo-root node_modules, a script under /workspace/scripts/ will\n fail with \"Cannot find package 'postgres'\". Use \\`-w\\` to set cwd inside\n the right workspace.\n\n Action script (provision-user.sh):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T \\\\\n -w /app/apps/api \\\\\n -e EMAIL=\"$INPUT_EMAIL\" \\\\\n -e PASSWORD=\"$INPUT_PASSWORD\" \\\\\n -e NAME=\"$INPUT_NAME\" \\\\\n app-api bun /app/apps/api/scripts/provision-user.ts\n\n File artifact (uploaded to /workspace/apps/api/scripts/provision-user.ts):\n import postgres from \"postgres\";\n const sql = postgres(process.env.DATABASE_URL!);\n const hash = await Bun.password.hash(process.env.PASSWORD!, { algorithm: \"argon2id\" });\n const email = process.env.EMAIL!.toLowerCase();\n const [org] = await sql\\`INSERT INTO organizations (name) VALUES (\\${email + \"'s Org\"}) RETURNING id\\`;\n const [user] = await sql\\`\n INSERT INTO users (auth_provider, provider_id, primary_email, password_hash, email_verified_at)\n VALUES ('email', \\${email}, \\${email}, \\${hash}, now()) RETURNING id\\`;\n await sql\\`INSERT INTO org_memberships (user_id, org_id, role) VALUES (\\${user.id}, \\${org.id}, 'admin')\\`;\n const loginUrl = \\`\\${process.env.SANDBOX_PUBLIC_URL ?? ''}/login\\`;\n // Echo password back so credentialOutputConfig (below) can resolve $output.password.\n // Output keys referenced by credentialOutputConfig.fieldMapping are auto-redacted\n // from stored stdout/logs by the platform — safe to include.\n console.log(JSON.stringify({ ok: true, email, password: process.env.PASSWORD, userId: user.id, orgId: org.id, loginUrl }));\n await sql.end();\n\nLINKING ACTIONS TO CANARY CREDENTIALS:\n\n When you want a successful action to also produce a credential in the\n property's vault (so it can drive browser logins, seed workflows, etc.),\n attach a credentialOutputConfig. The platform resolves \\`$output.<key>\\`\n tokens against the action's last-line JSON stdout, then upserts a\n credential scoped to (sandboxActionId, environmentId) — re-running the\n action replaces the credential rather than duplicating it.\n\n Provide it with --credential-output-config on action create/update:\n\n canary sandbox action create <template-id> provision-credential \\\\\n --label \"Provision Credential\" \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n Example provision-user-credential.json (matches the script above):\n {\n \"fieldMapping\": {\n \"username\": \"$output.email\",\n \"password\": \"$output.password\"\n },\n \"loginUrl\": \"$output.loginUrl\",\n \"loginInstructions\": \"Sign in via the email/password form.\"\n }\n\n Field-mapping values are either literal strings or \\`$output.<key>\\` tokens\n resolved from the action's parsed JSON output. Output keys referenced by\n fieldMapping/loginUrl are automatically masked in stored stdout and result.\n\n After execution, \\`canary sandbox action execute\\` prints the linked\n credentialId, and \\`canary credential list --property <name>\\` shows the\n vault entry. \\`canary credential test <credId>\\` runs the browser login\n against the sandbox URL.\n\n Example — simple SQL-only action (inline is fine for short commands):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-db psql -U app -d appdb -t --csv -c \\\\\n \"INSERT INTO users (username, password_hash, role)\n VALUES ('$INPUT_USERNAME', crypt('$INPUT_PASSWORD', gen_salt('bf')), '$INPUT_ROLE')\n RETURNING id\" | while read -r user_id; do\n echo \"{\\\\\"userId\\\\\":\\\\\"$user_id\\\\\",\\\\\"username\\\\\":\\\\\"$INPUT_USERNAME\\\\\",\\\\\"loginUrl\\\\\":\\\\\"https://$SANDBOX_PUBLIC_URL/login\\\\\"}\"\n done\n\n Example — seed data via app CLI:\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bun run scripts/seed.ts \\\\\n --scenario \"$INPUT_SCENARIO\" \\\\\n --count \"$INPUT_COUNT\"\n\n Example — run migrations (no params needed):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/db && bun run drizzle-kit migrate'\n echo '{\"status\":\"completed\"}'\n\n Available context variables:\n SANDBOX_PUBLIC_URL — the instance's public URL\n SANDBOX_INSTANCE_ID — the instance UUID\n\nAGENT AUTHORING:\n During template creation, the sandbox agent can publish actions alongside\n artifacts using the publish_action tool. The agent analyzes the codebase\n and generates action definitions for common operations (user creation,\n data seeding, migrations, etc.). These are stored on the template and\n available on all instances provisioned from it.\n\n publish_action accepts an optional credentialOutputConfig — for any action\n that provisions login material (e.g. a user with email/password), the agent\n should set this so the resulting credential lands in the property vault and\n can be exercised by browser automation.\n`.trim(),\n};\n\nconst CACHING: GuideSection = {\n title: 'Build Caching',\n slug: 'caching',\n content: `\nA cold sandbox launch otherwise reruns the slow build work on every provision:\nthe Docker image build (the browser/toolchain install layer), \\`bun install\\`, and\nthe production web build. The platform removes that cost by capturing known-good\noutputs at VERIFICATION time and restoring them on every later provision. Most of\nit is automatic; two things are your job: opt one-shot build services in with\nlabels, and keep their outputs instance-independent.\n\nWHAT THE PLATFORM DOES FOR YOU (no action needed)\n - Prebuilt image: when a template verifies, the built image is pushed to a\n registry and PULLED on later provisions instead of rebuilt. Your services keep\n both \\`build:\\` and an injected \\`image:\\` — \\`--build\\` is skipped on a hit and a\n pull miss falls back to building. Nothing to declare.\n\nWHAT YOU DECLARE — cache labels on one-shot build/dep services\n Same convention as the sandbox.route/sandbox.port labels. The platform snapshots\n the declared volumes/paths at verify and restores them at provision. How a HIT is\n reused depends on the service's REUSE MODE (auto-detected, override with\n sandbox.cache-reuse):\n - prime (dependency installs — a service caching a named VOLUME): the restore\n WARMS the install, but the command STILL RUNS. This is required: a package\n manager in a workspace monorepo writes a node_modules into EVERY package\n (apps/web/node_modules, packages/*/node_modules) that lives in the workspace,\n not in the one cached volume — so skipping the install on a hit leaves the tree\n incomplete and the app crashes. Re-running install warm rebuilds the COMPLETE\n tree. It is fast: the platform auto-provisions and caches the package manager's\n global store for you (see below).\n - skip (build outputs — a service caching only PATHS): the cache fully captures\n the output, so on a hit a marker file makes the one-shot a no-op (skips the\n rebuild). A miss runs cold and the next verify re-captures.\n\n services:\n deps: # dependency install → cache the volume (prime)\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"lockfiles\" # invalidate when a lockfile changes\n sandbox.cache-volumes: \"node_modules\" # named volume(s) to snapshot\n web-build: # production build → cache the output dirs (skip)\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"inputs\"\n sandbox.cache-inputs: \"apps/web,packages/ui,bun.lock\" # source the build reads\n sandbox.cache-paths: \"apps/web/dist,packages/ui/dist\" # output dirs to snapshot\n\n Labels:\n sandbox.cache: \"true\" Opt this service in.\n sandbox.cache-key How the cache is invalidated:\n \"lockfiles\" — hash of the repo's lock manifests (bun.lock, package-lock.json,\n yarn.lock, pnpm-lock.yaml). Use for dependency installs.\n \"inputs\" — content hash of the sandbox.cache-inputs paths (node_modules\n and dist excluded). Use for BUILDS. This is what makes the\n cache useful on a branch that moves: a commit that does not\n touch the build's inputs keeps the key → hit; one that does →\n miss → rebuild. (Default: inputs when cache-inputs is set,\n else lockfiles.)\n sandbox.cache-inputs Comma-separated source paths the build consumes\n (required for \"inputs\"). List the app dir plus every\n workspace package it builds, plus the lockfile.\n sandbox.cache-volumes Comma-separated named volumes to snapshot/restore.\n sandbox.cache-paths Comma-separated workspace-relative output dirs to\n snapshot/restore.\n sandbox.cache-reuse \"prime\" | \"skip\". Optional — defaults to prime for a\n service that caches a volume (install) and skip for a\n paths-only service (build output). Only override if\n the auto-detection is wrong for your service.\n All path/volume values must be plain, relative, traversal-free names — absolute\n paths, \"..\"/empty segments, or shell metacharacters are rejected (fail-closed).\n\n NEVER key a build on the git commit: a moving branch would invalidate it on every\n unrelated push (an api-only or docs commit) and the cache would never hit. Key on\n the build's actual INPUTS.\n\nPACKAGE-MANAGER STORE IS AUTOMATIC (do not configure it)\n For a prime install service the platform sets the cache/store env vars for every\n common package manager (bun, npm, yarn, pnpm) to a shared, cached directory\n co-located with node_modules, and snapshots it. So the warm re-run of your install\n is offline and fast for ANY package manager with nothing to declare. Do NOT set\n BUN_INSTALL_CACHE_DIR / npm_config_cache / YARN_CACHE_FOLDER / store-dir yourself —\n the platform owns them. Just label the install service sandbox.cache: \"true\".\n\nKEEP A SKIP SERVICE'S COMMAND ON ONE LINE\n For a skip (build-output) service the platform wraps its \\`command:\\` in a marker\n gate so a hit skips the build. It rewrites a single-line command — scalar\n (\\`command: bun run build\\`) or flow array (\\`command: [\"bash\",\"-c\",\"...\"]\\`). A\n multi-line block command is left ungated (still gets capture/restore but always\n runs). Keep it one line. (Prime install commands always run, so this does not\n apply to them.)\n\nINSTANCE-INDEPENDENCE (required for cached build outputs)\n A cached \\`dist\\` is reused across DIFFERENT sandboxes, so it must not bake any\n per-sandbox value. The common trap is baking the API URL at build time\n (VITE_API_URL=\\${SANDBOX_URL}/api). Instead resolve the API origin at RUNTIME from\n the browser location — web and api are the same origin behind the sandbox nginx\n proxy, so a relative \\`/api\\` works on every sandbox. If you must bake a value, bake\n a fixed placeholder and rewrite it at provision; never bake \\${SANDBOX_URL} into a\n cached output. Outputs that embed a per-sandbox value cannot be shared and will\n serve the wrong sandbox's URL.\n\nREFRESHING THE CACHE\n Re-verify the template to re-capture (\\`canary sandbox verify <template-id>\\`).\n Editing artifacts already forces a re-verify. A miss is always safe — it just\n builds cold that one time.\n`,\n};\n\nexport const SANDBOX_GUIDE_SECTIONS: GuideSection[] = [\n GOAL,\n RESEARCH,\n ARTIFACTS,\n CACHING,\n ACTIONS,\n VERIFICATION,\n CHECKLIST,\n];\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchProperties,
|
|
5
5
|
getApplicationArg
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SZOPRIBA.js";
|
|
7
7
|
import {
|
|
8
8
|
apiFetch,
|
|
9
9
|
exitOnFailure
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolveConfig
|
|
15
15
|
} from "./chunk-ZPPG4MGR.js";
|
|
16
16
|
import "./chunk-S3Q7MDCS.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
18
18
|
import "./chunk-ZSOAFMES.js";
|
|
19
19
|
import "./chunk-ICKB3EOT.js";
|
|
20
20
|
|
|
@@ -451,4 +451,4 @@ async function runCredential(argv) {
|
|
|
451
451
|
export {
|
|
452
452
|
runCredential
|
|
453
453
|
};
|
|
454
|
-
//# sourceMappingURL=credential-
|
|
454
|
+
//# sourceMappingURL=credential-DAK26ZN5.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchProperties,
|
|
5
5
|
getApplicationArg
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SZOPRIBA.js";
|
|
7
7
|
import "./chunk-H3LWYSKY.js";
|
|
8
8
|
import {
|
|
9
9
|
getArgValue,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveConfig
|
|
12
12
|
} from "./chunk-ZPPG4MGR.js";
|
|
13
13
|
import "./chunk-S3Q7MDCS.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
15
15
|
import "./chunk-ZSOAFMES.js";
|
|
16
16
|
import "./chunk-ICKB3EOT.js";
|
|
17
17
|
|
|
@@ -290,4 +290,4 @@ async function runCredentialTemplate(argv) {
|
|
|
290
290
|
export {
|
|
291
291
|
runCredentialTemplate
|
|
292
292
|
};
|
|
293
|
-
//# sourceMappingURL=credential-template-
|
|
293
|
+
//# sourceMappingURL=credential-template-2S4IKKWH.js.map
|
|
@@ -149,9 +149,9 @@ import {
|
|
|
149
149
|
withRecoveryNotice,
|
|
150
150
|
withRecoveryNoticeMCP,
|
|
151
151
|
withToolTimeout
|
|
152
|
-
} from "./chunk-
|
|
152
|
+
} from "./chunk-EPMDUCEA.js";
|
|
153
153
|
import "./chunk-S3Q7MDCS.js";
|
|
154
|
-
import "./chunk-
|
|
154
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
155
155
|
import "./chunk-2EHYDUHX.js";
|
|
156
156
|
import "./chunk-ICKB3EOT.js";
|
|
157
157
|
export {
|
|
@@ -306,4 +306,4 @@ export {
|
|
|
306
306
|
withRecoveryNoticeMCP,
|
|
307
307
|
withToolTimeout
|
|
308
308
|
};
|
|
309
|
-
//# sourceMappingURL=dist-
|
|
309
|
+
//# sourceMappingURL=dist-MQMO6WA3.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
apiRequest
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SZOPRIBA.js";
|
|
5
5
|
import "./chunk-H3LWYSKY.js";
|
|
6
6
|
import {
|
|
7
7
|
getArgValue,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
getSubDir
|
|
13
13
|
} from "./chunk-S3Q7MDCS.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
15
15
|
import "./chunk-ZSOAFMES.js";
|
|
16
16
|
import "./chunk-ICKB3EOT.js";
|
|
17
17
|
|
|
@@ -448,4 +448,4 @@ async function runDocs(argv) {
|
|
|
448
448
|
export {
|
|
449
449
|
runDocs
|
|
450
450
|
};
|
|
451
|
-
//# sourceMappingURL=docs-
|
|
451
|
+
//# sourceMappingURL=docs-SX5RFWWF.js.map
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; import { fileURLToPath as __fup
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchProperties
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SZOPRIBA.js";
|
|
6
6
|
import "./chunk-H3LWYSKY.js";
|
|
7
7
|
import {
|
|
8
8
|
getArgValue,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
resolveConfig
|
|
11
11
|
} from "./chunk-ZPPG4MGR.js";
|
|
12
12
|
import "./chunk-S3Q7MDCS.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
14
14
|
import "./chunk-ZSOAFMES.js";
|
|
15
15
|
import "./chunk-ICKB3EOT.js";
|
|
16
16
|
|
|
@@ -218,4 +218,4 @@ async function runEnvironment(argv) {
|
|
|
218
218
|
export {
|
|
219
219
|
runEnvironment
|
|
220
220
|
};
|
|
221
|
-
//# sourceMappingURL=environment-
|
|
221
|
+
//# sourceMappingURL=environment-2OAJCTFK.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
fetchList,
|
|
5
5
|
parseLifecycleStage,
|
|
6
6
|
toLifecycleLabel
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SZOPRIBA.js";
|
|
8
8
|
import "./chunk-H3LWYSKY.js";
|
|
9
9
|
import {
|
|
10
10
|
getArgValue,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
resolveConfig
|
|
13
13
|
} from "./chunk-ZPPG4MGR.js";
|
|
14
14
|
import "./chunk-S3Q7MDCS.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-QX7RZ5CZ.js";
|
|
16
16
|
import "./chunk-ZSOAFMES.js";
|
|
17
17
|
import "./chunk-ICKB3EOT.js";
|
|
18
18
|
|
|
@@ -472,4 +472,4 @@ async function runFeatureFlag(argv) {
|
|
|
472
472
|
export {
|
|
473
473
|
runFeatureFlag
|
|
474
474
|
};
|
|
475
|
-
//# sourceMappingURL=feature-flag-
|
|
475
|
+
//# sourceMappingURL=feature-flag-2HRNJ6Z6.js.map
|