@runcofactor/cli 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +125 -0
  2. package/dist/api-catalog-GFGCLVS4.js +401 -0
  3. package/dist/api-catalog-GFGCLVS4.js.map +1 -0
  4. package/dist/attachment-SJC7D7FY.js +276 -0
  5. package/dist/attachment-SJC7D7FY.js.map +1 -0
  6. package/dist/auth-EN6NQCJY.js +31 -0
  7. package/dist/auth-EN6NQCJY.js.map +1 -0
  8. package/dist/bin.js +7 -0
  9. package/dist/bin.js.map +1 -0
  10. package/dist/chunk-3CZU7XCS.js +272 -0
  11. package/dist/chunk-3CZU7XCS.js.map +1 -0
  12. package/dist/chunk-4NRDONSC.js +1048 -0
  13. package/dist/chunk-4NRDONSC.js.map +1 -0
  14. package/dist/chunk-4PNOV7OV.js +51980 -0
  15. package/dist/chunk-4PNOV7OV.js.map +1 -0
  16. package/dist/chunk-52IO7ACJ.js +2941 -0
  17. package/dist/chunk-52IO7ACJ.js.map +1 -0
  18. package/dist/chunk-6LEZCKKV.js +663 -0
  19. package/dist/chunk-6LEZCKKV.js.map +1 -0
  20. package/dist/chunk-A4OFTUVV.js +170 -0
  21. package/dist/chunk-A4OFTUVV.js.map +1 -0
  22. package/dist/chunk-B2VISN2V.js +2459 -0
  23. package/dist/chunk-B2VISN2V.js.map +1 -0
  24. package/dist/chunk-CLAWT7BJ.js +271 -0
  25. package/dist/chunk-CLAWT7BJ.js.map +1 -0
  26. package/dist/chunk-GKY2KXO5.js +281 -0
  27. package/dist/chunk-GKY2KXO5.js.map +1 -0
  28. package/dist/chunk-I4QLI3JO.js +413 -0
  29. package/dist/chunk-I4QLI3JO.js.map +1 -0
  30. package/dist/chunk-IMPZAX6L.js +180 -0
  31. package/dist/chunk-IMPZAX6L.js.map +1 -0
  32. package/dist/chunk-J6SH7GQT.js +241 -0
  33. package/dist/chunk-J6SH7GQT.js.map +1 -0
  34. package/dist/chunk-JDBCZ3Z2.js +298 -0
  35. package/dist/chunk-JDBCZ3Z2.js.map +1 -0
  36. package/dist/chunk-JICSYFRJ.js +46 -0
  37. package/dist/chunk-JICSYFRJ.js.map +1 -0
  38. package/dist/chunk-JWHYXEA2.js +32 -0
  39. package/dist/chunk-JWHYXEA2.js.map +1 -0
  40. package/dist/chunk-N7HFALX3.js +88 -0
  41. package/dist/chunk-N7HFALX3.js.map +1 -0
  42. package/dist/chunk-Q4BO4Z5V.js +178 -0
  43. package/dist/chunk-Q4BO4Z5V.js.map +1 -0
  44. package/dist/chunk-QFDOWABX.js +195 -0
  45. package/dist/chunk-QFDOWABX.js.map +1 -0
  46. package/dist/chunk-SJJW77WN.js +605 -0
  47. package/dist/chunk-SJJW77WN.js.map +1 -0
  48. package/dist/chunk-VKVL7WBN.js +50 -0
  49. package/dist/chunk-VKVL7WBN.js.map +1 -0
  50. package/dist/chunk-WOHL4FTB.js +353 -0
  51. package/dist/chunk-WOHL4FTB.js.map +1 -0
  52. package/dist/chunk-XQXVG7RA.js +426 -0
  53. package/dist/chunk-XQXVG7RA.js.map +1 -0
  54. package/dist/chunk-ZD3KHESI.js +94 -0
  55. package/dist/chunk-ZD3KHESI.js.map +1 -0
  56. package/dist/credential-COLTBV6D.js +450 -0
  57. package/dist/credential-COLTBV6D.js.map +1 -0
  58. package/dist/credential-template-EEPYFG7R.js +291 -0
  59. package/dist/credential-template-EEPYFG7R.js.map +1 -0
  60. package/dist/debug-workflow-KOVH5GOP.js +259 -0
  61. package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
  62. package/dist/dist-44PZR7BQ.js +423 -0
  63. package/dist/dist-44PZR7BQ.js.map +1 -0
  64. package/dist/docs-3SSORHB5.js +366 -0
  65. package/dist/docs-3SSORHB5.js.map +1 -0
  66. package/dist/doctor-OTSIWV6G.js +132 -0
  67. package/dist/doctor-OTSIWV6G.js.map +1 -0
  68. package/dist/feature-flag-T2LRZME3.js +283 -0
  69. package/dist/feature-flag-T2LRZME3.js.map +1 -0
  70. package/dist/file-type-S3UUBVCT.js +4205 -0
  71. package/dist/file-type-S3UUBVCT.js.map +1 -0
  72. package/dist/index.d.ts +3 -0
  73. package/dist/index.js +982 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/init-TGWRECT2.js +208 -0
  76. package/dist/init-TGWRECT2.js.map +1 -0
  77. package/dist/install-DXSNWZKO.js +295 -0
  78. package/dist/install-DXSNWZKO.js.map +1 -0
  79. package/dist/issues-XIZOPX6T.js +564 -0
  80. package/dist/issues-XIZOPX6T.js.map +1 -0
  81. package/dist/knobs-NBINZIUB.js +295 -0
  82. package/dist/knobs-NBINZIUB.js.map +1 -0
  83. package/dist/list-3TDHK637.js +59 -0
  84. package/dist/list-3TDHK637.js.map +1 -0
  85. package/dist/local-HICVD6PU.js +100 -0
  86. package/dist/local-HICVD6PU.js.map +1 -0
  87. package/dist/login-UOIUXVUK.js +315 -0
  88. package/dist/login-UOIUXVUK.js.map +1 -0
  89. package/dist/mcp-54IJEOUA.js +1956 -0
  90. package/dist/mcp-54IJEOUA.js.map +1 -0
  91. package/dist/pdf-extract-ABWV3L5X.js +13 -0
  92. package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
  93. package/dist/pdfjs-DQ2YA5S3.js +41561 -0
  94. package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
  95. package/dist/property-UXLOLPTD.js +701 -0
  96. package/dist/property-UXLOLPTD.js.map +1 -0
  97. package/dist/property-skill-guide.md +189 -0
  98. package/dist/psql-BYUUNLGG.js +115 -0
  99. package/dist/psql-BYUUNLGG.js.map +1 -0
  100. package/dist/record-4TR2UIIS.js +233 -0
  101. package/dist/record-4TR2UIIS.js.map +1 -0
  102. package/dist/redis-35VBUYW7.js +116 -0
  103. package/dist/redis-35VBUYW7.js.map +1 -0
  104. package/dist/release-OOLTUBUT.js +354 -0
  105. package/dist/release-OOLTUBUT.js.map +1 -0
  106. package/dist/sandbox-XGSUU6G7.js +2046 -0
  107. package/dist/sandbox-XGSUU6G7.js.map +1 -0
  108. package/dist/scenarios-GWLJEGHA.js +280 -0
  109. package/dist/scenarios-GWLJEGHA.js.map +1 -0
  110. package/dist/session-MYFNZPRP.js +2414 -0
  111. package/dist/session-MYFNZPRP.js.map +1 -0
  112. package/dist/skill-KM34LDMO.js +1839 -0
  113. package/dist/skill-KM34LDMO.js.map +1 -0
  114. package/dist/skills/debug-run/SKILL.md +262 -0
  115. package/dist/skills/debug-run/history/1.0.0.md +244 -0
  116. package/dist/skills/debug-run/history/1.0.1.md +245 -0
  117. package/dist/skills/debug-run/history/1.0.2.md +254 -0
  118. package/dist/skills/debug-run/history/1.1.0.md +262 -0
  119. package/dist/skills/debug-run/version.json +3 -0
  120. package/dist/spec-RA3CFEKG.js +951 -0
  121. package/dist/spec-RA3CFEKG.js.map +1 -0
  122. package/dist/src-LB4HBTJQ.js +1516 -0
  123. package/dist/src-LB4HBTJQ.js.map +1 -0
  124. package/dist/src-N3TPKGTY.js +244 -0
  125. package/dist/src-N3TPKGTY.js.map +1 -0
  126. package/dist/src-UVDX5UP6.js +17 -0
  127. package/dist/src-UVDX5UP6.js.map +1 -0
  128. package/dist/start-PSEQSQA3.js +151 -0
  129. package/dist/start-PSEQSQA3.js.map +1 -0
  130. package/dist/tag-YY6YAABQ.js +22 -0
  131. package/dist/tag-YY6YAABQ.js.map +1 -0
  132. package/dist/task-IQC3YR5K.js +922 -0
  133. package/dist/task-IQC3YR5K.js.map +1 -0
  134. package/dist/test-suite-DZNJFXDW.js +394 -0
  135. package/dist/test-suite-DZNJFXDW.js.map +1 -0
  136. package/dist/trace-HXIW7U2D.js +14 -0
  137. package/dist/trace-HXIW7U2D.js.map +1 -0
  138. package/dist/triage-ORG45R5W.js +16 -0
  139. package/dist/triage-ORG45R5W.js.map +1 -0
  140. package/dist/upgrade-L2VYMP2V.js +61 -0
  141. package/dist/upgrade-L2VYMP2V.js.map +1 -0
  142. package/dist/workflow-XYFH5LTP.js +3708 -0
  143. package/dist/workflow-XYFH5LTP.js.map +1 -0
  144. package/dist/workflow-cache-NQJQFWMV.js +253 -0
  145. package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
  146. package/dist/workflow-debug-BGD4UIXU.js +927 -0
  147. package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
  148. package/dist/workflow-proposals-MTUDX46O.js +382 -0
  149. package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
  150. package/package.json +61 -0
@@ -0,0 +1,1048 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+
3
+ // ../content/src/print-guide.ts
4
+ function printGuide(allSections, section, json) {
5
+ const sections = section ? allSections.filter((s) => s.slug === section) : allSections;
6
+ if (section && sections.length === 0) {
7
+ console.error(
8
+ `Unknown guide section: "${section}". Available: ${allSections.map((s) => s.slug).join(", ")}`
9
+ );
10
+ process.exit(1);
11
+ }
12
+ if (json) {
13
+ console.log(JSON.stringify(sections, null, 2));
14
+ return;
15
+ }
16
+ for (const s of sections) {
17
+ console.log(`
18
+ ${"=".repeat(72)}`);
19
+ console.log(` ${s.title}`);
20
+ console.log(`${"=".repeat(72)}
21
+ `);
22
+ console.log(s.content);
23
+ console.log("");
24
+ }
25
+ }
26
+
27
+ // ../content/src/sandbox-guide.ts
28
+ var GOAL = {
29
+ title: "Goal & Tools",
30
+ slug: "goal",
31
+ content: `
32
+ Your goal is to turn a git repository into a running, containerized development
33
+ environment. The end state is a sandbox URL that loads the app in a browser.
34
+
35
+ WORKFLOW:
36
+ 1. canary sandbox create <property> Create a template + authoring instance
37
+ 2. Research the app Read the codebase to understand what it needs
38
+ 3. Set config values API keys, secrets \u2192 pushed to instance as .canary/.env
39
+ 4. Upload artifacts Dockerfile, docker-compose, setup script, etc. \u2192 pushed to instance
40
+ 5. canary sandbox build <instance-id> Build images, start containers, generate nginx
41
+ 6. Check diagnostics & logs Verify the app is running
42
+ 7. Fix artifacts, re-upload, re-build Iterate until the app loads in browser
43
+ 8. Mark checklist items complete Record what you did
44
+
45
+ Each artifact upload and config set immediately pushes the file to the instance.
46
+ The "build" command triggers the actual rebuild and restart.
47
+ Batch your changes (upload all artifacts, set all config), then build once.
48
+
49
+ YOUR TOOLS:
50
+
51
+ Config \u2014 set environment variables (pushed to instance .canary/.env immediately):
52
+ canary sandbox config set <template-id> <key> <value>
53
+ canary sandbox config list <template-id>
54
+
55
+ SANDBOX_URL is auto-injected \u2014 use it in compose for browser-facing URLs.
56
+
57
+ Artifacts \u2014 files that define the sandbox (pushed to instance on upload):
58
+ canary sandbox artifact create <template-id> <type> <file>
59
+ canary sandbox artifact get <template-id> <type>
60
+
61
+ Types: dockerfile, docker-compose, setup-script, startup-script, seed-script, file
62
+
63
+ Build \u2014 rebuild and start services:
64
+ canary sandbox build <instance-id>
65
+ canary sandbox build <instance-id> --no-wait
66
+
67
+ Generates nginx routing, injects Docker networking, writes file artifacts,
68
+ runs setup script, then runs compose up, then runs startup script.
69
+ Call this after uploading artifacts.
70
+
71
+ First builds on large projects may exceed gateway timeouts. The CLI handles
72
+ this automatically \u2014 it detects the timeout and polls diagnostics until
73
+ containers are healthy. Use --no-wait to fire-and-forget and poll manually.
74
+
75
+ Diagnostics & logs \u2014 check container health and read service output:
76
+ canary sandbox diagnostics <instance-id>
77
+ canary sandbox logs <instance-id> --service <name>
78
+
79
+ Run-command \u2014 escape hatch for debugging and one-off commands:
80
+ canary sandbox run-command <instance-id> "<command>"
81
+
82
+ Checklist \u2014 record your progress:
83
+ canary sandbox checklist <template-id>
84
+ canary sandbox checklist complete <template-id> <item-id> [options]
85
+ canary sandbox checklist skip <template-id> <item-id>
86
+
87
+ PRINCIPLES:
88
+ - Artifacts are the source of truth. Every fix must update the artifact file and re-upload.
89
+ - Never patch source files. Use Docker env vars, CMD overrides, setup/startup scripts.
90
+ - Code is volume-mounted at /workspace, not baked into the image.
91
+ - The platform handles nginx routing and Docker networking \u2014 you don't need to.
92
+ `.trim()
93
+ };
94
+ var RESEARCH = {
95
+ title: "Research the App",
96
+ slug: "research",
97
+ content: `
98
+ Before writing any artifacts, research the application thoroughly.
99
+
100
+ 1. ENV VAR VALIDATION
101
+ Find the app's environment validation schema (Zod, Joi, dotenv, etc.).
102
+ List ALL required (non-optional) variables. Many apps fail fast on missing vars.
103
+
104
+ Common locations:
105
+ - src/env.ts, src/config.ts, src/environment.ts
106
+ - .env.example, .env.sample
107
+
108
+ Classify each var:
109
+ - AUTO-WIRED: Internal service URLs (DATABASE_URL, REDIS_URL) \u2014 hardcode in compose
110
+ - PLACEHOLDER: OAuth creds, S3 buckets \u2014 use dummy values, app boots without them
111
+ - SENSITIVE: API keys (ANTHROPIC_API_KEY) \u2014 goes in template configSchema
112
+ - DISABLED: Worker flags, schedulers \u2014 set to false in compose
113
+
114
+ 2. REQUIRED SERVICES
115
+ What databases, caches, queues does the app need?
116
+ Check: docker-compose.yml, package.json deps, infra/ configs, README
117
+ Common: PostgreSQL, Redis, MongoDB, RabbitMQ, Elasticsearch
118
+
119
+ 3. MIGRATION STRATEGY
120
+ Does the migrate script depend on cloud tooling (SST, AWS SDK, Terraform)?
121
+ If yes, use the ORM's CLI directly in the startup script via "sb-compose exec":
122
+ - Drizzle: sb-compose exec -T app-api bash -c 'cd /app/packages/db && bun run drizzle-kit migrate'
123
+ - Prisma: sb-compose exec -T app-api bash -c 'bunx prisma migrate deploy'
124
+ - Rails: sb-compose exec -T app-api rails db:migrate
125
+ - Django: sb-compose exec -T app-api python manage.py migrate
126
+ Put migrations in the startup script, NOT as a compose service (see Artifacts).
127
+ Do NOT use bunx for JS migrations \u2014 it downloads a fresh copy. Use "bun run" instead.
128
+
129
+ 4. PACKAGE BUILD ORDER (monorepos)
130
+ Which workspace packages export from dist/ and must be pre-built?
131
+ Check package.json "main" or "exports" fields for "./dist/" references.
132
+ These builds go in the startup script via sb-compose exec, NOT the Dockerfile.
133
+ When running scripts via sb-compose exec, cd to the package directory
134
+ where the dependency is declared. Bun workspace module resolution won't
135
+ find deps from the monorepo root.
136
+
137
+ 5. DEV SERVER HOST RESTRICTIONS
138
+ Modern dev servers block unknown hostnames:
139
+ - Vite 5.4+: upload a wrapper config via "file" artifact with allowedHosts: true
140
+ - Vite 6+: --allowedHosts all works as a CLI flag in compose command
141
+ - Next.js: hostname config in next.config.js
142
+ - Webpack: allowedHosts in devServer config
143
+ Fix via file artifact or compose CMD override \u2014 never patch the config file.
144
+ Check the Vite version in package.json \u2014 if >= 5.4, create the allowedHosts
145
+ file artifact PROACTIVELY before the first build, not after a browser error.
146
+
147
+ GOTCHA \u2014 relative imports from the wrapper config:
148
+ File artifacts can only land under /workspace/.canary/ (see Artifacts \u2192
149
+ PLATFORM ARCHITECTURE). But Vite resolves esbuild imports inside a
150
+ --config file relative to the config file's *own location*, not the
151
+ working_dir. So if your wrapper does
152
+ import baseConfig from "./vite.config";
153
+ and you point vite at /app/.canary/vite.sandbox.config.ts, esbuild
154
+ looks for /app/.canary/vite.config.ts and fails.
155
+ Fix: stage the wrapper into the repo tree at setup time. Add a
156
+ setup-script artifact that does
157
+ cp /workspace/.canary/vite.sandbox.config.ts \\
158
+ /workspace/apps/web/vite.sandbox.config.ts
159
+ and reference the file via its repo-tree name in compose
160
+ (\`--config vite.sandbox.config.ts\` with working_dir /app/apps/web).
161
+ The .canary/ copy stays the source of truth; the staged copy is just
162
+ a build-time stamp.
163
+
164
+ 6. WORKERS & DAEMONS TO DISABLE
165
+ Sandbox typically only needs the API server + web frontend.
166
+ Disable: job runners, schedulers, stall detectors, cron, coordinators.
167
+ Look for env flags like WORKER_ENABLED, SCHEDULER_ENABLED, etc.
168
+ `.trim()
169
+ };
170
+ var ARTIFACTS = {
171
+ title: "Artifacts",
172
+ slug: "artifacts",
173
+ content: `
174
+ Artifacts are the files that define your sandbox. Write them locally, then upload:
175
+ canary sandbox artifact create <template-id> <type> <local-file>
176
+
177
+ IMPORTANT \u2014 ARTIFACT SCOPE:
178
+ Artifact uploads are TEMPLATE-level, not instance-level. Uploading an artifact
179
+ updates the template definition and pushes the file to the AUTHORING instance
180
+ only. Running instances provisioned from the template are NOT updated \u2014 they
181
+ use whatever was baked in at provision time. To apply artifact changes to an
182
+ existing non-authoring instance, you must rebuild it:
183
+ canary sandbox build <instance-id>
184
+
185
+ PLATFORM ARCHITECTURE:
186
+ The sandbox filesystem is split into two areas:
187
+ - /workspace checked-out repo and user-owned files
188
+ - /workspace/.canary platform-managed compose, config, and helper scripts
189
+
190
+ Your compose file is saved as /workspace/.canary/docker-compose.override.yml and
191
+ merged with the platform's base compose at /workspace/.canary/docker-compose.yml,
192
+ which provides:
193
+ - nginx-proxy: reverse proxy on port 80 (routes based on sandbox.route labels)
194
+ - sandbox-agent: platform agent on port 8080
195
+ - minio: S3-compatible object store at http://minio:9000 (creds:
196
+ minioadmin / minioadmin). See MINIO BUCKET below \u2014 the platform
197
+ doesn't auto-wire the endpoint env var or create buckets for you.
198
+
199
+ You do NOT need to declare any of these in your docker-compose.yml \u2014 the
200
+ platform injects them. Defining a service with one of these reserved
201
+ names is rejected at artifact-upload time with code
202
+ SANDBOX_RESERVED_SERVICE_NAME. The full reserved list is:
203
+ nginx-proxy
204
+ sandbox-agent
205
+ minio
206
+ minio-init
207
+ docker-socket-proxy
208
+
209
+ CONTAINER PATHS \u2014 the bind mount maps the repo root to /app inside
210
+ containers, while file artifacts are uploaded under /workspace/.canary
211
+ on the host. So the same file has two paths:
212
+ /workspace/.canary/foo.ts \u2190 the path you upload to from the CLI
213
+ /app/.canary/foo.ts \u2190 the path your compose command/services
214
+ reference inside the container
215
+ Reference the in-container path in any compose \`command\` array, exec
216
+ call, or script file path that runs inside a service. The host-side
217
+ /workspace path only appears in startup.sh / setup.sh (which run on
218
+ the host, not inside a container).
219
+
220
+ MINIO BUCKET (S3 emulation):
221
+ The platform provides a MinIO container at http://minio:9000 but does
222
+ NOT inject AWS_ENDPOINT_URL_S3 or create your bucket. You must:
223
+
224
+ 1. Wire the endpoint into your service env in compose:
225
+ AWS_ENDPOINT_URL_S3: http://minio:9000
226
+ AWS_ACCESS_KEY_ID: minioadmin
227
+ AWS_SECRET_ACCESS_KEY: minioadmin
228
+ AWS_REGION: us-east-1
229
+ S3_BUCKET_NAME: <your-bucket>
230
+
231
+ 2. Create the bucket on every build via a one-shot service:
232
+ mc-init:
233
+ image: minio/mc
234
+ restart: "no"
235
+ entrypoint:
236
+ - sh
237
+ - -c
238
+ - >
239
+ mc alias set local http://minio:9000 minioadmin minioadmin &&
240
+ mc mb -p local/\${S3_BUCKET_NAME:-app-bucket} ||
241
+ true
242
+
243
+ Most AWS SDK v3 clients honor AWS_ENDPOINT_URL_S3 natively. If your
244
+ app passes a custom S3 client, also set forcePathStyle: true (MinIO
245
+ doesn't support virtual-host-style bucket addressing).
246
+
247
+ Compose still runs with /workspace as the project directory. That means user
248
+ compose content keeps the same repo-root-relative behavior for build contexts,
249
+ bind mounts, and referenced files.
250
+
251
+ COMPOSE INVOCATION: use the pre-installed "sb-compose" wrapper on the host.
252
+ It expands to docker compose with the canonical flag set:
253
+ docker compose --project-directory /workspace \\
254
+ --env-file /workspace/.canary/.env \\
255
+ -f /workspace/.canary/docker-compose.yml \\
256
+ -f /workspace/.canary/docker-compose.override.yml \\
257
+ "$@"
258
+ Call it from startup scripts, action scripts, and run-command the same way:
259
+ sb-compose exec -T app-api bash -c 'bun run build'
260
+ sb-compose ps
261
+ sb-compose logs --tail 50 app-api
262
+ The wrapper is available as /usr/local/bin/sb-compose (symlink) and
263
+ /workspace/.canary/sb-compose. If you invoke raw "docker compose" yourself
264
+ you must pass the same flags \u2014 otherwise paths and variable interpolation
265
+ (\${SANDBOX_URL} et al.) will resolve incorrectly.
266
+
267
+ The platform injects a shared Docker network. On "build", the sequence is:
268
+ 1. Write file artifacts to their destination paths
269
+ 2. Run setup script (if present)
270
+ 3. docker compose down (cleanup previous containers)
271
+ 4. docker compose build + up
272
+ 5. Reload nginx
273
+ 6. Wait for long-running services to accept "sb-compose exec" before continuing
274
+ 7. Run startup script (if present)
275
+
276
+ Step 6 is automatic. You don't need to add your own "wait for postgres"
277
+ loops at the top of startup.sh \u2014 by the time startup.sh runs, every
278
+ non-one-shot service is exec-able. Service healthchecks inside the
279
+ containers (e.g. postgres pg_isready) are still your responsibility if
280
+ your app needs to wait on them.
281
+
282
+ Do NOT define services named "nginx-proxy", "sandbox-agent", or "minio"
283
+ in your compose.
284
+
285
+ DOCKERFILE (type: dockerfile)
286
+ A base image that provides the runtime. Source code is volume-mounted, NOT copied in.
287
+ Written to /workspace/.canary/Dockerfile.sandbox (avoids conflicting with the repo's own Dockerfile).
288
+ Reference it in compose as: build: { context: ., dockerfile: .canary/Dockerfile.sandbox }
289
+
290
+ Structure:
291
+ FROM <runtime-base>
292
+ RUN apt-get update && apt-get install -y <system-deps>
293
+ RUN npm install -g <global-tools>
294
+ WORKDIR /app
295
+
296
+ Rules:
297
+ - NO "COPY . ." \u2014 code comes via volume mount at /workspace
298
+ - NO "RUN npm build" \u2014 builds happen in the setup script
299
+ - Include ALL system deps (chromium, ffmpeg, python, etc.)
300
+ - For browser automation: use mcr.microsoft.com/playwright as base
301
+ - Do NOT use ENTRYPOINT \u2014 it breaks one-shot services (deps, migrate) whose
302
+ command gets passed as args to the entrypoint instead of running directly
303
+
304
+ DOCKER COMPOSE (type: docker-compose)
305
+ Defines all services. Platform handles networking and nginx routing.
306
+
307
+ Typical services:
308
+ deps (one-shot) \u2014 installs dependencies into named volume, restart: "no"
309
+ database \u2014 official image with healthcheck (postgres, mysql, etc.)
310
+ cache \u2014 redis/memcached with healthcheck
311
+ app-api \u2014 application server in dev mode (hot reload)
312
+ app-web \u2014 frontend dev server bound to 0.0.0.0
313
+
314
+ Do NOT use a migrate service in compose. If the migrate container fails once
315
+ (bad SQL, missing env var, wrong path), depends_on with
316
+ service_completed_successfully is permanently unmet \u2014 app-api will never
317
+ start, even after you fix the migration. "docker compose restart" and
318
+ "docker compose up" won't help; only "docker compose down" + fresh up works.
319
+ Put migrations in the startup script instead (runs after compose up).
320
+
321
+ ROUTING LABELS \u2014 declare how nginx should route to your services:
322
+ services:
323
+ app-api:
324
+ labels:
325
+ sandbox.route: "/api"
326
+ sandbox.port: "3000"
327
+ app-web:
328
+ labels:
329
+ sandbox.route: "/"
330
+ sandbox.port: "5173"
331
+
332
+ The platform reads these labels and generates nginx config on "build".
333
+ If no labels, all traffic routes to the first non-infrastructure service.
334
+
335
+ PREFIX STRIPPING: the generated nginx location uses a trailing-slash
336
+ proxy_pass (proxy_pass http://<service>:<port>/), so the matched prefix
337
+ is stripped from the upstream request. With sandbox.route: "/api", a
338
+ browser hit to "/api/health" is delivered to the upstream as "/health".
339
+ Pick VITE_API_URL, OAuth redirect URIs, etc. accordingly.
340
+
341
+ ENV VAR STRATEGY \u2014 use \${VAR} syntax, values come from config + .env:
342
+ - Auto-wired (hardcode): DATABASE_URL=postgres://user:pass@postgres:5432/db
343
+ - From config: ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
344
+ - Platform-injected: VITE_API_URL=\${SANDBOX_URL}/api
345
+ - Disabled: WORKER_ENABLED=false
346
+
347
+ SANDBOX_URL is auto-injected by the platform (e.g. https://<id>.env.trycanary.ai).
348
+ Use it for any URL a browser needs to reach the sandbox.
349
+
350
+ Rules:
351
+ - Use named volumes for data persistence (postgres-data, redis-data, etc.)
352
+ - Bind-mount the repo (.:/app) rather than using named volumes for
353
+ node_modules. Per-package node_modules named volumes silently shadow the
354
+ bind mount on any service that doesn't mount them \u2014 a common monorepo
355
+ trap where "bun run build" fails because tsup is nowhere to be found.
356
+ The sandbox host is Linux EC2; bind mounts are fast.
357
+ - DO NOT define networks \u2014 the platform handles networking
358
+ - DO NOT expose ports to the host \u2014 nginx handles routing
359
+ - Keep relative paths repo-root-relative; compose runs from /workspace even
360
+ though the platform-managed compose files live in /workspace/.canary
361
+ - Escape shell variables in "command:" strings with $$ so compose leaves
362
+ them for the shell (e.g. command: ["bash", "-c", "exec foo $$BAR"]).
363
+ Unescaped \${VAR} is substituted at compose-time from the env file.
364
+
365
+ SETUP SCRIPT (type: setup-script)
366
+ Runs BEFORE docker compose up on every build. Runs on the HOST, not in a container.
367
+ Written to /workspace/.canary/setup.sh.
368
+
369
+ IMPORTANT: The host is a bare EC2 instance with only docker and basic linux tools.
370
+ App runtimes (node, bun, python, ruby) are NOT available on the host \u2014 they live
371
+ inside your Docker images. Do NOT run app commands directly here.
372
+
373
+ Script authoring rules:
374
+ - Treat /workspace as the repo root for any file access or repo command
375
+ - Do not assume the current directory matches the script's own path
376
+ - Use explicit paths or start with "cd /workspace" before repo-relative work
377
+
378
+ Use for host-level preparation only:
379
+ - Installing system packages (apt-get, yum)
380
+ - Downloading external tools or binaries
381
+ - Generating config files programmatically (prefer file artifacts for static files)
382
+
383
+ For most apps, you won't need a setup script \u2014 file artifacts and the startup
384
+ script cover the common cases. Skip this if you don't need host-level prep.
385
+
386
+ Rules:
387
+ - Must be idempotent \u2014 safe to run multiple times
388
+ - Never patch source files
389
+ - Non-zero exit is logged as a warning but does not block the build
390
+
391
+ STARTUP SCRIPT (type: startup-script)
392
+ Runs AFTER docker compose up on every build. Runs on the HOST, not in a container.
393
+ Written to /workspace/.canary/startup.sh.
394
+
395
+ This is where most app setup happens. Containers are running, so you can use
396
+ "sb-compose exec" to run commands inside them with the app runtime.
397
+
398
+ Script authoring rules:
399
+ - Start with "cd /workspace" or use explicit /workspace/... paths
400
+ - Do not assume the script runs from /workspace/.canary
401
+ - Prefer sb-compose exec for anything that needs the app runtime
402
+ - Use -T with exec (no TTY allocation \u2014 startup runs headless)
403
+
404
+ Use for:
405
+ - DB migrations via ORM CLI (use "bun run", NOT bunx \u2014 bunx downloads fresh)
406
+ - Building workspace packages that export from dist/
407
+ - Waiting for services to be healthy
408
+ - Triggering initial data loads
409
+
410
+ POST-MIGRATION RESTART (REQUIRED for many ORMs):
411
+ ORMs that introspect the schema at boot (Drizzle/effect-sql, Prisma with
412
+ prepared statements, SQLAlchemy with autoload, etc.) cache the pre-migration
413
+ schema on the first query. After you run migrations, you MUST restart the
414
+ API service, otherwise you'll see errors like:
415
+ relation "<table>" does not exist
416
+ even though psql confirms the table is there. Add "sb-compose restart" for
417
+ any service whose client introspects the schema at boot.
418
+
419
+ Example (Bun monorepo with Drizzle):
420
+ #!/bin/bash
421
+ set -e
422
+ # Build dist-exporting workspace packages the app/web import from
423
+ sb-compose exec -T app-api bash -c \\
424
+ 'cd /app/packages/ui && bun run build'
425
+ # Run DB migrations inside the API container
426
+ sb-compose exec -T app-api bash -c \\
427
+ 'cd /app/packages/db && bun run drizzle-kit migrate'
428
+ # Restart services so they pick up the fresh schema + built UI
429
+ sb-compose restart app-api app-web
430
+ # Wait for API to be healthy
431
+ for i in $(seq 1 30); do
432
+ sb-compose exec -T app-api curl -sf http://localhost:3000/health && break
433
+ sleep 2
434
+ done
435
+
436
+ Rules:
437
+ - Must be idempotent \u2014 safe to run multiple times
438
+ - Never patch source files
439
+ - Don't pre-wait for services to START \u2014 the platform handles that before
440
+ startup.sh runs (see Compose Architecture section). Do wait for service
441
+ *readiness* (app health, schema migrations applied) when your app
442
+ depends on it.
443
+
444
+ FILE (type: file)
445
+ Arbitrary sandbox-owned files written to the instance at a specific path.
446
+ Use for config files that need to exist before services start (e.g. Vite
447
+ wrapper configs, one-shot scripts that need app dependencies).
448
+
449
+ !!! File artifacts MUST be written under /workspace/.canary/ !!!
450
+ The repo tree (everything outside .canary/) is user-owned. Sandbox
451
+ helpers live in .canary/ so they never collide with real files and
452
+ are never committed back to the repo. The API will reject any
453
+ --path value outside /workspace/.canary/ with
454
+ SANDBOX_FILE_ARTIFACT_OUTSIDE_CANARY.
455
+
456
+ If your script needs app dependencies (e.g. \`postgres\` from the api
457
+ package), put a tiny package.json next to it in .canary/scripts/ and
458
+ \`bun install\` in the setup-script, OR invoke it via
459
+ \`sb-compose exec -T <svc> ...\` so it runs inside a container that
460
+ already has them.
461
+
462
+ Upload with --path to specify the destination:
463
+ canary sandbox artifact create <template-id> file <local-file> --path /workspace/.canary/<dest>
464
+
465
+ Example \u2014 Vite 5.x allowedHosts wrapper:
466
+ canary sandbox artifact create <template-id> file vite.sandbox.config.ts \\
467
+ --path /workspace/.canary/vite.sandbox.config.ts
468
+
469
+ ONE ROW PER FILENAME. File artifacts are keyed by (template, filename).
470
+ Uploading a file with the same filename as an earlier upload REPLACES
471
+ the earlier row. The CLI prints "(replaced)" on such uploads, and the
472
+ next build removes the previous destination path so the instance
473
+ filesystem mirrors the template set exactly.
474
+
475
+ If you want to retire a file, just upload something else with a
476
+ different filename \u2014 the stale file is cleaned up on next build via
477
+ .canary/.artifact-paths (a manifest the platform maintains).
478
+
479
+ File artifacts are written before the setup script runs, so they're
480
+ available for both setup and compose services.
481
+
482
+ SEED SCRIPT (type: seed-script)
483
+ Runs once on first provision to populate demo data. Optional.
484
+ Written to /workspace/.canary/seed.sh.
485
+ Skipped on subsequent builds (checks /workspace/.canary/.seeded).
486
+
487
+ CONFIG SCHEMA
488
+ Define which env vars need user input. Set when completing the docker-compose checklist item:
489
+ canary sandbox checklist complete <template-id> docker-compose-created \\
490
+ --status success \\
491
+ --summary "Compose with postgres, redis, api, web" \\
492
+ --config-schema '[
493
+ {"key": "ANTHROPIC_API_KEY", "required": true, "sensitive": true, "description": "Anthropic API key"},
494
+ {"key": "OPENAI_API_KEY", "required": false, "sensitive": true, "description": "OpenAI API key"}
495
+ ]'
496
+
497
+ Set defaults:
498
+ canary sandbox config set <template-id> <key> <value>
499
+ canary sandbox config list <template-id>
500
+ `.trim()
501
+ };
502
+ var VERIFICATION = {
503
+ title: "Verification & Iteration",
504
+ slug: "verify",
505
+ content: `
506
+ After running "canary sandbox build", verify the sandbox is working. The iteration loop:
507
+ upload artifacts \u2192 build \u2192 check diagnostics \u2192 read logs \u2192 fix \u2192 re-upload \u2192 rebuild
508
+
509
+ COMMANDS:
510
+ # Check all container statuses at a glance
511
+ canary sandbox diagnostics <instance-id>
512
+
513
+ # Read logs for a specific service
514
+ canary sandbox logs <instance-id> --service app-api
515
+ canary sandbox logs <instance-id> --service app-web
516
+ canary sandbox logs <instance-id> --service postgres
517
+
518
+ # Run arbitrary commands on the instance (runs on the host VM by default)
519
+ canary sandbox run-command <instance-id> "docker compose ps"
520
+ canary sandbox run-command <instance-id> "echo hello"
521
+
522
+ # Run commands inside a specific Docker container
523
+ canary sandbox run-command <instance-id> --service app-api "bun --version"
524
+ canary sandbox run-command <instance-id> --service app-api "bun test"
525
+
526
+ # Test health endpoints
527
+ canary sandbox run-command <instance-id> "curl -s http://localhost:3000/health"
528
+ canary sandbox run-command <instance-id> "curl -s -o /dev/null -w '%{http_code}' http://localhost:80/"
529
+
530
+ # Test with sandbox hostname (for host-restriction issues)
531
+ canary sandbox run-command <instance-id> \\
532
+ "curl -s -H 'Host: <instance-id>.env.trycanary.ai' http://localhost:80/"
533
+
534
+ SANDBOX URL:
535
+ https://<instance-id>.env.trycanary.ai
536
+
537
+ IF SOMETHING FAILS:
538
+ 1. Read the logs for the failing service
539
+ 2. Diagnose the root cause (missing env var, failed build, wrong port, etc.)
540
+ 3. Fix the ARTIFACT \u2014 not just the running container
541
+ 4. Re-upload: canary sandbox artifact create <template-id> <type> <file>
542
+ 5. Rebuild: canary sandbox build <instance-id>
543
+ 6. Verify again
544
+
545
+ WHEN A CLI COMMAND TIMES OUT AT 30s:
546
+ Long sandbox operations (build, edit, artifact create on first push,
547
+ occasionally diagnostics) can exceed the gateway window even though
548
+ the work is still running server-side. The CLI prints:
549
+ Error: Request to https://api.trycanary.ai/... timed out after 30000ms.
550
+
551
+ Don't retry blindly \u2014 a retry can spawn duplicate work (e.g. a second
552
+ authoring instance). Instead, poll for the actual state:
553
+
554
+ canary sandbox template <template-id> # status: provisioning/authoring/verified
555
+ canary sandbox diagnostics <instance-id> # container/app health
556
+ canary sandbox list # running instances
557
+
558
+ Only retry once you've confirmed via polling that the prior call
559
+ truly didn't take effect.
560
+
561
+ WHEN VERIFY HANGS:
562
+ \`canary sandbox verify\` now streams container state and failing-service
563
+ log tails every ~30s while the probe's app is unreachable. If you see:
564
+
565
+ >> nginx-proxy is restart-looping because upstream '<service>' (...) is exited.
566
+ Read that service's logs above, not nginx's.
567
+
568
+ ignore nginx entirely and read the upstream's tail \u2014 nginx is the symptom,
569
+ not the cause. Common underlying causes:
570
+ - the upstream service crashed at startup (bad env var, missing file,
571
+ import error)
572
+ - a file artifact was lost because you re-uploaded over its filename
573
+ (shouldn't happen any more \u2014 the build-time reconcile handles this \u2014
574
+ but watch for it if you're on an old template)
575
+
576
+ On failure, the probe instance is preserved by default so you can
577
+ \`canary sandbox run-command <id>\` into it. Pass
578
+ --auto-terminate-on-failure for CI.
579
+
580
+ COMMON ISSUES:
581
+
582
+ "Invalid environment" / env validation fails
583
+ App has strict env var validation. Read the env schema. Add ALL required vars to compose.
584
+
585
+ Migration fails with "SST links not active" / cloud SDK error
586
+ Migrate script depends on cloud tooling. Use the ORM CLI directly in the startup script
587
+ via sb-compose exec: sb-compose exec -T app-api bash -c 'bun run drizzle-kit migrate'
588
+ DATABASE_URL should be hardcoded in the compose environment section.
589
+
590
+ "Cannot find module" / package not built
591
+ Monorepo package exports from dist/ but hasn't been built.
592
+ Add build step to startup script via sb-compose exec (runs after compose up).
593
+
594
+ "Cannot find package 'X'" from a file-artifact script
595
+ Bun resolves node_modules starting from the script's directory. File
596
+ artifacts live under /workspace/.canary/ which has no node_modules by
597
+ default. Two fixes:
598
+ (a) add a /workspace/.canary/scripts/package.json declaring the
599
+ dep (e.g. "postgres") and run \`bun install\` from the
600
+ setup-script / inside a sb-compose exec.
601
+ (b) don't run the script on the host at all \u2014 invoke it via
602
+ \`sb-compose exec -T <app-service> bun /app/.canary/scripts/foo.ts\`
603
+ so it resolves against the workspace's own node_modules. Replace
604
+ <app-service> with your template's actual service name.
605
+
606
+ "This host is not allowed" (Vite, Next.js)
607
+ Dev server blocks requests from sandbox subdomain.
608
+ For Vite 5.x, upload a wrapper config as a file artifact (into .canary/).
609
+
610
+ IMPORTANT: the wrapper must be self-contained \u2014 DO NOT import 'vite' or
611
+ a sibling config file. Vite evaluates the config via Node CJS bundled
612
+ by esbuild; from /workspace/.canary/ Node can't resolve packages
613
+ through Bun's hoisted .bun/<pkg>@<ver>/... layout, so any 'import ... from
614
+ "vite"' in the wrapper fails with "Cannot find module 'vite'". The
615
+ 'defineConfig' helper is just a typed identity, so plain object export
616
+ works fine.
617
+
618
+ Note: the file artifact is at /workspace/.canary/... but the bind mount
619
+ inside containers is /app, so reference it as /app/.canary/... in your
620
+ compose command.
621
+
622
+ # Create the wrapper locally \u2014 no imports
623
+ cat > vite.sandbox.config.ts <<'EOF'
624
+ // Mirror your apps/web/vite.config.ts settings inline here.
625
+ // Reference paths use the in-container path (/app/...), not /workspace.
626
+ export default {
627
+ root: '/app/apps/web',
628
+ server: {
629
+ host: '0.0.0.0',
630
+ port: 5173,
631
+ allowedHosts: true,
632
+ },
633
+ // ...copy 'define', 'optimizeDeps', 'resolve' from your base config
634
+ };
635
+ EOF
636
+ # Upload it into .canary/
637
+ canary sandbox artifact create <template-id> file vite.sandbox.config.ts \\
638
+ --path /workspace/.canary/vite.sandbox.config.ts
639
+ Then in compose:
640
+ command: ["bash","-c","cd /app/apps/web && bunx vite --host 0.0.0.0 --port 5173 --config /app/.canary/vite.sandbox.config.ts"]
641
+ For Vite 6+: --allowedHosts all works as a CLI flag (no wrapper needed).
642
+
643
+ Browser gets CORS / "loopback" error when frontend calls API
644
+ Frontend is trying to reach localhost:3000 instead of going through nginx.
645
+ Use \${SANDBOX_URL}/api as the API URL in compose env vars:
646
+ VITE_API_URL: \${SANDBOX_URL}/api
647
+
648
+ run-command times out
649
+ Long-running operation exceeds the 55s SSM command timeout. Write a script file and run in background:
650
+ canary sandbox run-command <id> "cat > /tmp/run.sh << 'SCRIPT'
651
+ #!/bin/bash
652
+ <commands>
653
+ SCRIPT
654
+ chmod +x /tmp/run.sh && nohup /tmp/run.sh > /tmp/run.log 2>&1 & echo started"
655
+ Then check: canary sandbox run-command <id> "tail -20 /tmp/run.log"
656
+
657
+ "Resolving dependencies" hangs in migration container
658
+ bunx/npx downloads a fresh copy instead of using project deps.
659
+ Use "bun run drizzle-kit migrate" instead of "bunx drizzle-kit migrate".
660
+
661
+ Containers can't resolve each other by name
662
+ Don't use docker run \u2014 use docker compose for everything.
663
+
664
+ RUNNING TESTS:
665
+ Two approaches depending on your needs:
666
+
667
+ Option 1 \u2014 Install runtime on host (no Docker needed):
668
+ Use the setup script to install the runtime on the host VM, then run
669
+ tests directly. Good for unit tests that don't need services like a database.
670
+
671
+ Example setup-script artifact:
672
+ #!/bin/bash
673
+ curl -fsSL https://bun.sh/install | bash
674
+ export PATH="$HOME/.bun/bin:$PATH"
675
+
676
+ Then: canary sandbox run-command <instance-id> "bun test"
677
+
678
+ Option 2 \u2014 Run inside a container (--service flag):
679
+ Route commands into a running Docker container. Good for integration
680
+ tests that need access to the database or other services.
681
+
682
+ canary sandbox run-command <instance-id> --service app-api "bun test"
683
+ canary sandbox run-command <instance-id> --service app-api "bun test:integration"
684
+
685
+ \${SANDBOX_URL} is blank / env vars from config not in containers
686
+ The "build" command writes /workspace/.canary/.env (with SANDBOX_URL and all config values)
687
+ before running docker compose up. If the .env is missing or stale, re-run:
688
+ canary sandbox build <instance-id>
689
+ You can also trigger a .env refresh by setting any config value:
690
+ canary sandbox config set <template-id> <key> <value>
691
+
692
+ .env values look empty when I cat the file
693
+ Values are written single-quoted (KEY='value'). Short values can look like
694
+ KEY='' at a glance even when they have content \u2014 it's just the quotes. Run
695
+ "wc -c" on a line, or "docker compose exec app-api env | grep KEY", to
696
+ confirm the actual value landed.
697
+ `.trim()
698
+ };
699
+ var CHECKLIST = {
700
+ title: "Checklist",
701
+ slug: "checklist",
702
+ content: `
703
+ The checklist tracks your progress toward a working sandbox. View it with:
704
+ canary sandbox checklist <template-id>
705
+
706
+ Each item has a status: pending, completed, skipped, or failed.
707
+ Items marked with * are actionable (their dependencies are met).
708
+
709
+ COMPLETING ITEMS:
710
+ canary sandbox checklist complete <template-id> <item-id> \\
711
+ --status <success|failure> \\
712
+ --summary "What you did"
713
+
714
+ Example:
715
+ canary sandbox checklist complete <template-id> environment-analyzed \\
716
+ --status success \\
717
+ --summary "Bun monorepo: Postgres, Redis. Vite frontend on 5173, API on 3000."
718
+
719
+ SKIPPING ITEMS:
720
+ canary sandbox checklist skip <template-id> <item-id>
721
+
722
+ Use when an item doesn't apply (e.g., no seed data needed, no integration tests).
723
+
724
+ SPECIAL: docker-compose-created accepts --config-schema to define user-configurable env vars:
725
+ canary sandbox checklist complete <template-id> docker-compose-created \\
726
+ --status success \\
727
+ --summary "Compose with postgres, redis, api, web" \\
728
+ --config-schema '[{"key": "ANTHROPIC_API_KEY", "required": true, "sensitive": true, "description": "API key"}]'
729
+
730
+ TYPICAL CHECKLIST ORDER:
731
+ 1. Provisioned \u2014 done automatically when you create the template
732
+ 2. Code Cloned \u2014 done automatically
733
+ 3. Environment Analyzed \u2014 complete after your research phase
734
+ 4. Setup Script Created \u2014 complete after uploading setup-script artifact
735
+ 5. Dockerfile Created \u2014 complete after uploading dockerfile artifact
736
+ 6. Docker Compose Created \u2014 complete after uploading docker-compose artifact (with config-schema)
737
+ 7. Unit Tests Runnable \u2014 complete or skip. Install runtime on host via setup script,
738
+ or use --service to run inside a container
739
+ 8. Integration Tests Runnable \u2014 complete or skip. Use --service to run inside a
740
+ container with access to database and other services
741
+ 9. Seed Script Created \u2014 complete or skip
742
+ 10. Server Running \u2014 complete after API responds to health checks
743
+ 11. Server Accessible \u2014 complete after nginx routes to the app successfully
744
+ 12. Template Verified \u2014 provision a fresh instance and verify it comes up healthy
745
+
746
+ TEMPLATE VERIFICATION (final step):
747
+ Run one command \u2014 it provisions a fresh instance, waits for the app to
748
+ come up, marks template-verified, and terminates the instance:
749
+ canary sandbox verify <template-id>
750
+
751
+ Pass --keep-instance to leave the instance running afterwards (useful if
752
+ you want to poke at it).
753
+
754
+ This catches issues that authoring hides:
755
+ - Config values set manually but not in configDefaults
756
+ - SANDBOX_URL or other platform vars not properly referenced
757
+ - Startup scripts that assume manual setup already happened
758
+
759
+ Template status flow:
760
+ provisioning \u2192 authoring \u2192 verified
761
+
762
+ 'verify' is the one transition from authoring to verified, and it is one-way
763
+ \u2014 there is no re-authoring path back to 'authoring' from 'verified'. To make
764
+ an AI-assisted edit to a verified template's artifacts, provision a sandbox
765
+ with the agent attached:
766
+
767
+ canary sandbox provision <template-id> --with-agent
768
+
769
+ then iterate via in-sandbox agent chat or 'canary sandbox artifact create',
770
+ and re-run 'canary sandbox verify' against the same template id when you're
771
+ happy with the changes. To start over from scratch, use 'regenerate' to
772
+ delete existing artifacts, or create a new template.
773
+ `.trim()
774
+ };
775
+ var ACTIONS = {
776
+ title: "Actions",
777
+ slug: "actions",
778
+ content: `
779
+ Actions are template-defined, parameterized operations that run against sandbox
780
+ instances. Unlike run-command (arbitrary shell), actions have typed input/output
781
+ schemas, are reusable across instances, and produce an auditable execution log.
782
+
783
+ USE CASES:
784
+ - Provision a user credential (username, password, role)
785
+ - Seed demo data for a specific scenario
786
+ - Run database migrations
787
+ - Reset application state
788
+ - Toggle feature flags
789
+
790
+ MANAGING ACTIONS:
791
+
792
+ List actions on a template:
793
+ canary sandbox action list <template-id>
794
+
795
+ View an action definition (including script):
796
+ canary sandbox action get <template-id> <slug>
797
+
798
+ Create or update an action:
799
+ canary sandbox action create <template-id> <slug> \\
800
+ --label "Provision Credential" \\
801
+ --description "Create a user account in the sandbox app" \\
802
+ --icon UserPlus \\
803
+ --script ./scripts/provision-user.sh \\
804
+ --input-schema ./schemas/provision-user-input.json \\
805
+ --credential-output-config ./schemas/provision-user-credential.json
806
+
807
+ --credential-output-config is optional. When set, a successful execution
808
+ also creates/updates a credential in the property's vault \u2014 see "Linking
809
+ actions to Canary credentials" below.
810
+
811
+ Delete an action:
812
+ canary sandbox action delete <template-id> <slug>
813
+
814
+ EXECUTING ACTIONS:
815
+
816
+ Execute against a running instance:
817
+ canary sandbox action execute <instance-id> <slug> \\
818
+ --param username=testuser \\
819
+ --param password=s3cret \\
820
+ --param role=admin
821
+
822
+ Or pass all params from a JSON file:
823
+ canary sandbox action execute <instance-id> <slug> \\
824
+ --params-file ./params.json
825
+
826
+ View execution history:
827
+ canary sandbox action executions <instance-id>
828
+
829
+ SLUG NAMING:
830
+ Slugs must be lowercase alphanumeric with hyphens, starting with a letter:
831
+ provision-credential \u2713
832
+ seed-demo-data \u2713
833
+ reset-state \u2713
834
+ 123-bad \u2717
835
+ UPPER_CASE \u2717
836
+
837
+ INPUT SCHEMA:
838
+ A JSON Schema file defining the action's parameters. The schema drives
839
+ validation, CLI prompting, and UI form generation.
840
+
841
+ Example (provision-user-input.json):
842
+ {
843
+ "type": "object",
844
+ "properties": {
845
+ "username": {
846
+ "type": "string",
847
+ "description": "Username to create",
848
+ "minLength": 1
849
+ },
850
+ "password": {
851
+ "type": "string",
852
+ "description": "Password for the user",
853
+ "sensitive": true
854
+ },
855
+ "role": {
856
+ "type": "string",
857
+ "description": "Role to assign",
858
+ "enum": ["admin", "user", "viewer"],
859
+ "default": "user"
860
+ }
861
+ },
862
+ "required": ["username", "password"]
863
+ }
864
+
865
+ The "sensitive" extension marks params that should be masked in UI and
866
+ injected securely (via tempfile, not env var) on the instance.
867
+
868
+ SCRIPT CONVENTIONS:
869
+ Action scripts are bash, executed on the instance via SSM. Parameters are
870
+ injected as INPUT_<KEY> environment variables, where <KEY> is the param
871
+ name uppercased with non-alphanumeric chars replaced by '_'. There is NO
872
+ camelCase \u2192 snake_case conversion. Examples:
873
+ orgName \u2192 INPUT_ORGNAME (NOT INPUT_ORG_NAME)
874
+ org_name \u2192 INPUT_ORG_NAME
875
+ api-key \u2192 INPUT_API_KEY
876
+ If you want INPUT_FOO_BAR, name the input schema key 'foo_bar' or 'foo-bar'.
877
+
878
+ The last line of stdout that is valid JSON is parsed as the structured result.
879
+
880
+ EXECUTION ENVIRONMENT \u2014 same architecture as startup scripts:
881
+ Scripts run on the HOST (bare EC2 instance). The host has docker, curl,
882
+ and basic linux tools \u2014 but NO app runtimes (node, bun, python, ruby).
883
+ Those live inside your Docker containers.
884
+
885
+ Use "docker compose exec" to run commands that need the app runtime:
886
+ sb-compose exec -T app-api node -e "..." # Node.js
887
+ sb-compose exec -T app-api bun run seed.ts # Bun
888
+ sb-compose exec -T app-db psql -U app -d db # Database CLI
889
+
890
+ Use bare host commands for docker/system operations:
891
+ sb-compose restart app-api # Restart a service
892
+ curl -s http://localhost:3000/health # Health check
893
+ cat /workspace/.canary/.env # Read config
894
+
895
+ The -T flag is required for docker compose exec in non-interactive (SSM)
896
+ contexts. Without it, exec tries to allocate a TTY and hangs.
897
+
898
+ Working directory is /workspace (the mounted repo root). Keep script logic
899
+ repo-root-relative by starting with "cd /workspace" when needed.
900
+
901
+ AVOID INLINE CODE IN ACTION SCRIPTS:
902
+ Never embed application-language code (JS, Python, etc.) directly in the
903
+ bash script via "bun -e" or "node -e". Shell escaping of quotes, template
904
+ literals, and variable interpolation is fragile and hard to debug.
905
+
906
+ Instead, write complex logic as a separate file artifact and execute it:
907
+ 1. Write a script file (e.g. provision-user.ts)
908
+ 2. Upload as a file artifact:
909
+ canary sandbox artifact create <template-id> file provision-user.ts \\
910
+ --path /workspace/scripts/provision-user.ts
911
+ 3. Reference it from the action script:
912
+ sb-compose exec -T app-api bun /app/scripts/provision-user.ts
913
+
914
+ Parameters are available in the action script as INPUT_<KEY> env vars.
915
+ Pass them to the file artifact via env or args:
916
+ sb-compose exec -T -e EMAIL="$INPUT_EMAIL" app-api bun /app/scripts/provision-user.ts
917
+
918
+ Example \u2014 provision a user (two-file pattern):
919
+
920
+ Place the .ts file under the workspace whose node_modules has the deps
921
+ (e.g. apps/api when only apps/api depends on \`postgres\`). Bun resolves
922
+ modules from the script's directory walking up \u2014 if the dep isn't hoisted
923
+ to the repo-root node_modules, a script under /workspace/scripts/ will
924
+ fail with "Cannot find package 'postgres'". Use \`-w\` to set cwd inside
925
+ the right workspace.
926
+
927
+ Action script (provision-user.sh):
928
+ #!/bin/bash
929
+ set -euo pipefail
930
+ cd /workspace
931
+ sb-compose exec -T \\
932
+ -w /app/apps/api \\
933
+ -e EMAIL="$INPUT_EMAIL" \\
934
+ -e PASSWORD="$INPUT_PASSWORD" \\
935
+ -e NAME="$INPUT_NAME" \\
936
+ app-api bun /app/apps/api/scripts/provision-user.ts
937
+
938
+ File artifact (uploaded to /workspace/apps/api/scripts/provision-user.ts):
939
+ import postgres from "postgres";
940
+ const sql = postgres(process.env.DATABASE_URL!);
941
+ const hash = await Bun.password.hash(process.env.PASSWORD!, { algorithm: "argon2id" });
942
+ const email = process.env.EMAIL!.toLowerCase();
943
+ const [org] = await sql\`INSERT INTO organizations (name) VALUES (\${email + "'s Org"}) RETURNING id\`;
944
+ const [user] = await sql\`
945
+ INSERT INTO users (auth_provider, provider_id, primary_email, password_hash, email_verified_at)
946
+ VALUES ('email', \${email}, \${email}, \${hash}, now()) RETURNING id\`;
947
+ await sql\`INSERT INTO org_memberships (user_id, org_id, role) VALUES (\${user.id}, \${org.id}, 'admin')\`;
948
+ const loginUrl = \`\${process.env.SANDBOX_PUBLIC_URL ?? ''}/login\`;
949
+ // Echo password back so credentialOutputConfig (below) can resolve $output.password.
950
+ // Output keys referenced by credentialOutputConfig.fieldMapping are auto-redacted
951
+ // from stored stdout/logs by the platform \u2014 safe to include.
952
+ console.log(JSON.stringify({ ok: true, email, password: process.env.PASSWORD, userId: user.id, orgId: org.id, loginUrl }));
953
+ await sql.end();
954
+
955
+ LINKING ACTIONS TO CANARY CREDENTIALS:
956
+
957
+ When you want a successful action to also produce a credential in the
958
+ property's vault (so it can drive browser logins, seed workflows, etc.),
959
+ attach a credentialOutputConfig. The platform resolves \`$output.<key>\`
960
+ tokens against the action's last-line JSON stdout, then upserts a
961
+ credential scoped to (sandboxActionId, environmentId) \u2014 re-running the
962
+ action replaces the credential rather than duplicating it.
963
+
964
+ Provide it with --credential-output-config on action create/update:
965
+
966
+ canary sandbox action create <template-id> provision-credential \\
967
+ --label "Provision Credential" \\
968
+ --script ./scripts/provision-user.sh \\
969
+ --input-schema ./schemas/provision-user-input.json \\
970
+ --credential-output-config ./schemas/provision-user-credential.json
971
+
972
+ Example provision-user-credential.json (matches the script above):
973
+ {
974
+ "fieldMapping": {
975
+ "username": "$output.email",
976
+ "password": "$output.password"
977
+ },
978
+ "loginUrl": "$output.loginUrl",
979
+ "loginInstructions": "Sign in via the email/password form."
980
+ }
981
+
982
+ Field-mapping values are either literal strings or \`$output.<key>\` tokens
983
+ resolved from the action's parsed JSON output. Output keys referenced by
984
+ fieldMapping/loginUrl are automatically masked in stored stdout and result.
985
+
986
+ After execution, \`canary sandbox action execute\` prints the linked
987
+ credentialId, and \`canary credential list --property <name>\` shows the
988
+ vault entry. \`canary credential test <credId>\` runs the browser login
989
+ against the sandbox URL.
990
+
991
+ Example \u2014 simple SQL-only action (inline is fine for short commands):
992
+ #!/bin/bash
993
+ set -euo pipefail
994
+ cd /workspace
995
+ sb-compose exec -T app-db psql -U app -d appdb -t --csv -c \\
996
+ "INSERT INTO users (username, password_hash, role)
997
+ VALUES ('$INPUT_USERNAME', crypt('$INPUT_PASSWORD', gen_salt('bf')), '$INPUT_ROLE')
998
+ RETURNING id" | while read -r user_id; do
999
+ echo "{\\"userId\\":\\"$user_id\\",\\"username\\":\\"$INPUT_USERNAME\\",\\"loginUrl\\":\\"https://$SANDBOX_PUBLIC_URL/login\\"}"
1000
+ done
1001
+
1002
+ Example \u2014 seed data via app CLI:
1003
+ #!/bin/bash
1004
+ set -euo pipefail
1005
+ cd /workspace
1006
+ sb-compose exec -T app-api bun run scripts/seed.ts \\
1007
+ --scenario "$INPUT_SCENARIO" \\
1008
+ --count "$INPUT_COUNT"
1009
+
1010
+ Example \u2014 run migrations (no params needed):
1011
+ #!/bin/bash
1012
+ set -euo pipefail
1013
+ cd /workspace
1014
+ sb-compose exec -T app-api bash -c \\
1015
+ 'cd /app/packages/db && bun run drizzle-kit migrate'
1016
+ echo '{"status":"completed"}'
1017
+
1018
+ Available context variables:
1019
+ SANDBOX_PUBLIC_URL \u2014 the instance's public URL
1020
+ SANDBOX_INSTANCE_ID \u2014 the instance UUID
1021
+
1022
+ AGENT AUTHORING:
1023
+ During template creation, the sandbox agent can publish actions alongside
1024
+ artifacts using the publish_action tool. The agent analyzes the codebase
1025
+ and generates action definitions for common operations (user creation,
1026
+ data seeding, migrations, etc.). These are stored on the template and
1027
+ available on all instances provisioned from it.
1028
+
1029
+ publish_action accepts an optional credentialOutputConfig \u2014 for any action
1030
+ that provisions login material (e.g. a user with email/password), the agent
1031
+ should set this so the resulting credential lands in the property vault and
1032
+ can be exercised by browser automation.
1033
+ `.trim()
1034
+ };
1035
+ var SANDBOX_GUIDE_SECTIONS = [
1036
+ GOAL,
1037
+ RESEARCH,
1038
+ ARTIFACTS,
1039
+ ACTIONS,
1040
+ VERIFICATION,
1041
+ CHECKLIST
1042
+ ];
1043
+
1044
+ export {
1045
+ printGuide,
1046
+ SANDBOX_GUIDE_SECTIONS
1047
+ };
1048
+ //# sourceMappingURL=chunk-4NRDONSC.js.map