showpane 0.4.0 → 0.4.2

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 (106) hide show
  1. package/README.md +22 -1
  2. package/bundle/meta/scaffold-manifest.json +73 -0
  3. package/bundle/scaffold/VERSION +1 -0
  4. package/bundle/scaffold/__dot__env.example +24 -0
  5. package/bundle/scaffold/__dot__gitignore +41 -0
  6. package/bundle/scaffold/docker/Caddyfile +3 -0
  7. package/bundle/scaffold/docker/Dockerfile +30 -0
  8. package/bundle/scaffold/docker-compose.yml +53 -0
  9. package/bundle/scaffold/next.config.ts +20 -0
  10. package/bundle/scaffold/package-lock.json +5843 -0
  11. package/bundle/scaffold/package.json +42 -0
  12. package/bundle/scaffold/postcss.config.js +6 -0
  13. package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
  14. package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
  15. package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
  16. package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
  17. package/bundle/scaffold/prisma/schema.local.prisma +131 -0
  18. package/bundle/scaffold/prisma/schema.prisma +128 -0
  19. package/bundle/scaffold/prisma/seed.ts +49 -0
  20. package/bundle/scaffold/public/example-avatar.svg +4 -0
  21. package/bundle/scaffold/public/example-logo.svg +4 -0
  22. package/bundle/scaffold/public/robots.txt +2 -0
  23. package/bundle/scaffold/scripts/backup.sh +19 -0
  24. package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
  25. package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
  26. package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
  27. package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
  28. package/bundle/scaffold/scripts/restore.sh +31 -0
  29. package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
  30. package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
  31. package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
  32. package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
  33. package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
  34. package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
  35. package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
  36. package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
  37. package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
  38. package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
  39. package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
  40. package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
  41. package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
  42. package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
  43. package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
  44. package/bundle/scaffold/src/app/api/health/route.ts +19 -0
  45. package/bundle/scaffold/src/app/globals.css +7 -0
  46. package/bundle/scaffold/src/app/layout.tsx +25 -0
  47. package/bundle/scaffold/src/app/page.tsx +171 -0
  48. package/bundle/scaffold/src/components/portal-login.tsx +169 -0
  49. package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
  50. package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
  51. package/bundle/scaffold/src/lib/branding.ts +50 -0
  52. package/bundle/scaffold/src/lib/client-auth.ts +98 -0
  53. package/bundle/scaffold/src/lib/client-portals.ts +134 -0
  54. package/bundle/scaffold/src/lib/control-plane.ts +100 -0
  55. package/bundle/scaffold/src/lib/db.ts +7 -0
  56. package/bundle/scaffold/src/lib/files.ts +124 -0
  57. package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
  58. package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
  59. package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
  60. package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
  61. package/bundle/scaffold/src/lib/storage.ts +204 -0
  62. package/bundle/scaffold/src/lib/token.ts +186 -0
  63. package/bundle/scaffold/src/lib/utils.ts +6 -0
  64. package/bundle/scaffold/src/middleware.ts +61 -0
  65. package/bundle/scaffold/tailwind.config.ts +15 -0
  66. package/bundle/scaffold/tests/__dot__gitkeep +0 -0
  67. package/bundle/scaffold/tsconfig.json +23 -0
  68. package/bundle/scaffold/vitest.config.ts +13 -0
  69. package/bundle/toolchain/VERSION +1 -0
  70. package/bundle/toolchain/bin/check-slug.ts +59 -0
  71. package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
  72. package/bundle/toolchain/bin/create-portal.ts +71 -0
  73. package/bundle/toolchain/bin/delete-portal.ts +48 -0
  74. package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
  75. package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
  76. package/bundle/toolchain/bin/generate-share-link.ts +68 -0
  77. package/bundle/toolchain/bin/list-portals.ts +53 -0
  78. package/bundle/toolchain/bin/materialize-file.ts +35 -0
  79. package/bundle/toolchain/bin/query-analytics.ts +88 -0
  80. package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
  81. package/bundle/toolchain/bin/showpane-config +63 -0
  82. package/bundle/toolchain/bin/tsconfig.json +13 -0
  83. package/bundle/toolchain/skills/VERSION +1 -0
  84. package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
  85. package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
  86. package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
  87. package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
  88. package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
  89. package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
  90. package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
  91. package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
  92. package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
  93. package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
  94. package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
  95. package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
  96. package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
  97. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
  98. package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
  99. package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
  100. package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
  101. package/bundle/toolchain/skills/shared/preamble.md +137 -0
  102. package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
  103. package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
  104. package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
  105. package/dist/index.js +875 -159
  106. package/package.json +4 -2
@@ -0,0 +1,721 @@
1
+ ---
2
+ name: portal-deploy
3
+ description: |
4
+ Deploy the Showpane portal app. Runs pre-flight checks, applies migrations, and deploys via Docker or Vercel.
5
+ Trigger phrases: "portal deploy", "deploy portals", "push to production", "ship the portals". (showpane)
6
+ allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, WebSearch]
7
+ ---
8
+
9
+ Before proposing or executing cloud deploy behavior, read `skills/shared/platform-constraints.md`
10
+ and apply any relevant current platform limits.
11
+
12
+ ## Preamble (run first)
13
+
14
+ ```bash
15
+ # Read config
16
+ CONFIG="$HOME/.showpane/config.json"
17
+ if [ ! -f "$CONFIG" ]; then
18
+ echo "Showpane not configured. Run /portal setup first."
19
+ exit 1
20
+ fi
21
+ APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
22
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
23
+ ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
24
+ CLOUD_API_TOKEN=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('api_token', d.get('accessToken','')))" 2>/dev/null)
25
+ CLOUD_ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('org_slug', d.get('orgSlug','')))" 2>/dev/null)
26
+ CLOUD_PORTAL_URL=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('portal_url', d.get('portalUrl','')))" 2>/dev/null)
27
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
28
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
29
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
30
+ DATABASE_URL="${DATABASE_URL:-}"
31
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
32
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
33
+ exit 1
34
+ fi
35
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
36
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
37
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
38
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
39
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
40
+
41
+ # Predictive next-skill suggestion
42
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
43
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
44
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
45
+ fi
46
+
47
+ # Search relevant learnings
48
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
49
+ if [ -f "$LEARN_FILE" ]; then
50
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
51
+ echo "LEARNINGS: $_LEARN_COUNT entries"
52
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
53
+ echo "RECENT_LEARNINGS:"
54
+ tail -5 "$LEARN_FILE" 2>/dev/null
55
+ fi
56
+ fi
57
+
58
+ # Track skill execution
59
+ SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
60
+ mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
61
+ echo '{"skill":"portal-deploy","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
62
+ ```
63
+
64
+ If RECENT_SKILLS is shown, suggest the likely next skill:
65
+ - After portal-create → suggest /portal-preview
66
+ - After portal-preview → suggest /portal-deploy or /portal-share
67
+ - After portal-deploy → suggest /portal-status or /portal-verify
68
+ - After portal-setup → suggest /portal-create
69
+ - After portal-credentials → suggest /portal-share
70
+ - After portal-update → suggest /portal-deploy
71
+
72
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
73
+ relevant warnings or tips for this operation. Apply them where relevant but don't
74
+ mention them unless they directly affect the current task.
75
+
76
+ ## Steps
77
+
78
+ ### Step 0: Choose deployment target
79
+
80
+ Check the `DEPLOY_MODE` variable from the preamble. Route accordingly:
81
+
82
+ - If `DEPLOY_MODE` is `"cloud"` — skip to **Cloud Deploy Flow** (after Step 6)
83
+ - If `DEPLOY_MODE` is `"docker"` or `"vercel"` or unset — continue with the existing self-hosted deploy flow (Steps 1-6)
84
+
85
+ If no deploy mode is configured, present the options:
86
+
87
+ 1. **Self-host with Docker** (free) — Deploy with Docker Compose on your own server
88
+ 2. **Showpane Cloud** ($29/month, 7-day free trial) — Managed hosting at {org}.showpane.com
89
+
90
+ If the user chooses Cloud but has not run `showpane login` yet (no `CLOUD_API_TOKEN`), tell them:
91
+
92
+ > "Run `showpane login` first to authenticate with Showpane Cloud, then re-run this deploy."
93
+
94
+ ### Step 1: Pre-flight checks
95
+
96
+ Run all checks before deploying. Any failure here should block the deploy.
97
+
98
+ #### 1a. TypeScript type check
99
+
100
+ ```bash
101
+ cd "$APP_PATH" && npx tsc --noEmit 2>&1
102
+ ```
103
+
104
+ If type errors are found, display them clearly and stop. Do not deploy with type errors. Common type errors during portal development:
105
+
106
+ - Missing imports (forgot to import an icon from lucide-react)
107
+ - PortalShell prop mismatch (wrong type for contact, missing required prop)
108
+ - Unused variables in tab content functions
109
+
110
+ For simple issues (missing import, typo in a prop name), offer to fix them before retrying the deploy. For complex type errors in shared code, suggest the user investigate and fix manually.
111
+
112
+ The type check typically takes 10-30 seconds depending on project size. If it takes longer than 60 seconds, it may indicate a problem with the TypeScript configuration.
113
+
114
+ #### 1b. Verify all portals have credentials
115
+
116
+ Run the list-portals script to check portal status:
117
+
118
+ ```bash
119
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" --org-id <org_id>
120
+ ```
121
+
122
+ The output is a JSON array of portals. Check each portal for a `username` field. If any active portal lacks credentials, warn the user:
123
+
124
+ ```
125
+ WARNING: The following portals have no login credentials:
126
+ - acme-health
127
+ - new-client
128
+
129
+ Clients won't be able to log in to these portals.
130
+ Run /portal credentials <slug> to create credentials, or continue anyway?
131
+ ```
132
+
133
+ This is a warning, not a blocker — the user can choose to continue. Some portals may be works in progress that don't need credentials yet.
134
+
135
+ #### 1c. Verify deployment config exists
136
+
137
+ **For Docker mode:**
138
+ ```bash
139
+ ls "$APP_PATH/docker-compose.yml" 2>/dev/null || ls "$APP_PATH/compose.yml" 2>/dev/null
140
+ ```
141
+
142
+ **For Vercel mode:**
143
+ ```bash
144
+ ls "$APP_PATH/.vercel/project.json" 2>/dev/null || ls "$APP_PATH/vercel.json" 2>/dev/null
145
+ ```
146
+
147
+ If the config doesn't exist, inform the user and provide setup guidance:
148
+ - Docker: "No docker-compose.yml found. Create one or switch deploy mode to vercel."
149
+ - Vercel: "No Vercel config found. Run `npx vercel link` to connect your project."
150
+
151
+ #### 1d. Check for uncommitted changes (Vercel mode only)
152
+
153
+ For Vercel deploys, check if there are uncommitted changes:
154
+
155
+ ```bash
156
+ cd "$APP_PATH" && git status --porcelain
157
+ ```
158
+
159
+ If there are changes, show the user what will be committed as part of the deploy:
160
+
161
+ ```bash
162
+ cd "$APP_PATH" && git diff --stat
163
+ ```
164
+
165
+ List the changed files and ask the user to confirm. Pay attention to:
166
+ - `.env` files — these should NEVER be committed. If `.env` appears in the changes, warn the user and exclude it.
167
+ - Large binary files — these will slow down the deploy. Suggest adding them to `.gitignore`.
168
+ - Files outside the portal directories — these may be unintended changes.
169
+
170
+ If there are no changes to commit and the current branch is up to date with the remote, inform the user that there is nothing new to deploy.
171
+
172
+ ### Step 2: Apply database migrations
173
+
174
+ Before deploying, apply any pending migrations:
175
+
176
+ ```bash
177
+ cd "$APP_PATH" && npx prisma migrate deploy
178
+ ```
179
+
180
+ This runs in production mode — it only applies pending migrations, never creates new ones. If this fails, stop the deploy and show the error. Common issues:
181
+ - DATABASE_URL not set or incorrect
182
+ - Database server unreachable
183
+ - Migration conflicts (rare, requires manual resolution)
184
+
185
+ ### Step 3: Deploy
186
+
187
+ #### Docker mode
188
+
189
+ Build and restart the containers:
190
+
191
+ ```bash
192
+ cd "$APP_PATH" && docker compose build && docker compose up -d
193
+ ```
194
+
195
+ If `docker compose` is not available, try `docker-compose` (older syntax):
196
+
197
+ ```bash
198
+ cd "$APP_PATH" && docker-compose build && docker-compose up -d
199
+ ```
200
+
201
+ Wait for the containers to start. Check container status:
202
+
203
+ ```bash
204
+ cd "$APP_PATH" && docker compose ps
205
+ ```
206
+
207
+ All containers should show status "Up" or "running".
208
+
209
+ #### Vercel mode
210
+
211
+ Stage, commit, and push:
212
+
213
+ ```bash
214
+ cd "$APP_PATH" && git add -A && git commit -m "Deploy portal updates" && git push
215
+ ```
216
+
217
+ If there are no changes to commit, just push:
218
+
219
+ ```bash
220
+ cd "$APP_PATH" && git push
221
+ ```
222
+
223
+ The push triggers Vercel's automatic deployment pipeline. Note that the deploy is async — the push returns immediately, but the actual deployment takes 1-3 minutes.
224
+
225
+ For Vercel, inform the user:
226
+
227
+ > "Pushed to remote. Vercel will build and deploy automatically. Check your Vercel dashboard for deploy status."
228
+
229
+ ### Step 4: Post-deploy verification
230
+
231
+ #### Docker mode
232
+
233
+ Wait a few seconds for the app to start, then hit the health endpoint:
234
+
235
+ ```bash
236
+ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health
237
+ ```
238
+
239
+ Expected: HTTP 200. Then check the response body:
240
+
241
+ ```bash
242
+ curl -s http://localhost:8080/api/health
243
+ ```
244
+
245
+ Expected: `{"status":"ok"}` or similar health response.
246
+
247
+ If the health check fails:
248
+ 1. Check container logs: `cd "$APP_PATH" && docker compose logs --tail=50`
249
+ 2. Check if the port is correct (might be 3000, 8080, or custom)
250
+ 3. Report the error and suggest debugging steps
251
+
252
+ #### Vercel mode
253
+
254
+ If a production URL is known (from config or Vercel project settings), check it:
255
+
256
+ ```bash
257
+ curl -s -o /dev/null -w "%{http_code}" https://<production_url>/api/health
258
+ ```
259
+
260
+ If the production URL is not known, skip the health check and inform the user:
261
+
262
+ > "Deploy triggered. Verify at your Vercel production URL once the build completes."
263
+
264
+ ### Step 5: Deployment summary
265
+
266
+ Print a clear summary of what happened:
267
+
268
+ ```
269
+ Deploy complete!
270
+
271
+ Mode: docker
272
+ Migrations: 2 applied (or: up to date)
273
+ Type check: passed
274
+ Portals: 5 active (3 with credentials)
275
+ Health: OK (200)
276
+
277
+ App URL: http://localhost:8080
278
+ Login: http://localhost:8080/client
279
+ ```
280
+
281
+ For Vercel:
282
+
283
+ ```
284
+ Deploy triggered!
285
+
286
+ Mode: vercel
287
+ Migrations: up to date
288
+ Type check: passed
289
+ Commit: abc1234 "Deploy portal updates"
290
+ Portals: 5 active (3 with credentials)
291
+
292
+ Status: Building (check Vercel dashboard)
293
+ URL: https://your-app.vercel.app
294
+ ```
295
+
296
+ ### Step 6: Record deployment
297
+
298
+ Log the deployment for operational memory:
299
+
300
+ ```bash
301
+ echo '{"skill":"portal-deploy","key":"deploy","insight":"Deployed via <mode>. Migrations: <count>. Portals: <count> active.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Cloud Deploy Flow (when DEPLOY_MODE is "cloud")
307
+
308
+ This section runs instead of Steps 1-6 when `DEPLOY_MODE` is `"cloud"`. The cloud flow builds the app locally, packages the `.vercel/output` artifact, uploads that artifact to Showpane Cloud, and lets the control plane publish the hosted app. The OSS app should never need to reason about Vercel project IDs or call the Vercel API directly.
309
+
310
+ ### Cloud Step 1: Pre-flight checks
311
+
312
+ #### 1a. Verify cloud credentials
313
+
314
+ Check that the preamble successfully read cloud config values:
315
+
316
+ ```bash
317
+ if [ -z "$CLOUD_API_TOKEN" ]; then
318
+ echo "ERROR: No API token found. Run 'showpane login' to authenticate."
319
+ exit 1
320
+ fi
321
+ ```
322
+
323
+ If the token is missing, stop and tell the user to run `showpane login`.
324
+
325
+ #### 1b. Verify Showpane Cloud is reachable
326
+
327
+ ```bash
328
+ curl -s -o /dev/null -w "%{http_code}" "$CLOUD_API_BASE/api/health"
329
+ ```
330
+
331
+ Expected: HTTP 200. If Showpane Cloud is unreachable, stop and show the error. Token validity is checked by the deploy API itself — OSS should not call Vercel or depend on provider internals.
332
+
333
+ #### 1c. TypeScript type check
334
+
335
+ Same as the self-hosted flow:
336
+
337
+ ```bash
338
+ cd "$APP_PATH" && npx tsc --noEmit 2>&1
339
+ ```
340
+
341
+ If type errors are found, display them and stop. Offer to fix simple issues (missing imports, typos).
342
+
343
+ #### 1d. Verify portals have credentials
344
+
345
+ Same as self-hosted Step 1b — run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
346
+
347
+ ### Cloud Step 2: Build the app
348
+
349
+ Run a Next.js production build:
350
+
351
+ ```bash
352
+ cd "$APP_PATH" && npx next build
353
+ ```
354
+
355
+ After the build completes, verify the output directory was created:
356
+
357
+ ```bash
358
+ ls -la "$APP_PATH/.vercel/output/"
359
+ ```
360
+
361
+ Expected: the `.vercel/output/` directory exists and contains `config.json`, `static/`, and optionally `functions/`. If the build fails or the output directory is missing, show the build errors and stop.
362
+
363
+ The build typically takes 30-90 seconds depending on the number of portals.
364
+
365
+ ### Cloud Step 3: Package the deploy artifact
366
+
367
+ Package the prebuilt output plus the traced runtime files as a single zip artifact that can be handed to Showpane Cloud. This bundle must include:
368
+ - `.vercel/output/**`
369
+ - traced `.next/server/**` and `node_modules/**` files referenced by the build output
370
+ - a sanitized `.env` stub so the traced runtime can resolve its expected path without leaking local secrets
371
+
372
+ ```bash
373
+ ARTIFACT_PATH="/tmp/showpane-deploy-${CLOUD_ORG_SLUG:-portal}.zip"
374
+ rm -f "$ARTIFACT_PATH"
375
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-deploy-bundle.ts" --output "$ARTIFACT_PATH"
376
+ test -f "$ARTIFACT_PATH" || { echo "ERROR: Artifact zip was not created"; exit 1; }
377
+ echo "Artifact ready: $ARTIFACT_PATH"
378
+ ```
379
+
380
+ ### Cloud Step 4: Export runtime data
381
+
382
+ Export the current local portal-runtime state so Showpane Cloud can sync credentials and portal metadata before publishing:
383
+
384
+ ```bash
385
+ RUNTIME_DATA_PATH="/tmp/showpane-runtime-${CLOUD_ORG_SLUG:-portal}.json"
386
+ rm -f "$RUNTIME_DATA_PATH"
387
+ if [ -n "$ORG_SLUG" ]; then
388
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-runtime-state.ts" --org-slug "$ORG_SLUG" > "$RUNTIME_DATA_PATH"
389
+ else
390
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-runtime-state.ts" > "$RUNTIME_DATA_PATH"
391
+ fi
392
+ test -f "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
393
+ echo "Runtime payload ready: $RUNTIME_DATA_PATH"
394
+ ```
395
+
396
+ ### Cloud Step 5: Export file manifest
397
+
398
+ Export uploaded document metadata and checksums so Showpane Cloud can determine which files need syncing:
399
+
400
+ ```bash
401
+ FILE_MANIFEST_PATH="/tmp/showpane-files-${CLOUD_ORG_SLUG:-portal}.json"
402
+ rm -f "$FILE_MANIFEST_PATH"
403
+ if [ -n "$ORG_SLUG" ]; then
404
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-file-manifest.ts" --org-slug "$ORG_SLUG" > "$FILE_MANIFEST_PATH"
405
+ else
406
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-file-manifest.ts" > "$FILE_MANIFEST_PATH"
407
+ fi
408
+ test -f "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
409
+ echo "File manifest ready: $FILE_MANIFEST_PATH"
410
+ ```
411
+
412
+ ### Cloud Step 6: Sync uploaded files
413
+
414
+ Ask Showpane Cloud which files are missing or stale, then upload only those file bytes:
415
+
416
+ ```bash
417
+ SYNC_PLAN_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/files/plan" \
418
+ -H "Authorization: Bearer $CLOUD_API_TOKEN" \
419
+ -H "Content-Type: application/json" \
420
+ --data-binary @"$FILE_MANIFEST_PATH")
421
+
422
+ MISSING_COUNT=$(echo "$SYNC_PLAN_RESPONSE" | python3 -c "
423
+ import sys, json
424
+ d = json.load(sys.stdin)
425
+ if 'error' in d:
426
+ print('ERROR: ' + d['error'])
427
+ sys.exit(1)
428
+ print(len(d.get('missing', [])))
429
+ ")
430
+
431
+ echo \"Files to sync: $MISSING_COUNT\"
432
+
433
+ if [ \"$MISSING_COUNT\" -gt 0 ]; then
434
+ TMP_SYNC_DIR=$(mktemp -d /tmp/showpane-file-sync.XXXXXX)
435
+ echo \"$SYNC_PLAN_RESPONSE\" | python3 -c '
436
+ import json, sys
437
+ for item in json.load(sys.stdin).get(\"missing\", []):
438
+ print(\"\\t\".join([
439
+ item[\"storagePath\"],
440
+ item[\"portalSlug\"],
441
+ item[\"filename\"],
442
+ item[\"mimeType\"],
443
+ str(item[\"size\"]),
444
+ item[\"uploadedBy\"],
445
+ item[\"uploadedAt\"],
446
+ item[\"checksum\"],
447
+ ]))
448
+ ' | while IFS=$'\t' read -r STORAGE_PATH PORTAL_SLUG FILE_NAME MIME_TYPE FILE_SIZE UPLOADED_BY UPLOADED_AT CHECKSUM; do
449
+ TMP_FILE="$TMP_SYNC_DIR/$CHECKSUM"
450
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/materialize-file.ts" --storage-path "$STORAGE_PATH" --output "$TMP_FILE"
451
+ curl -s -X POST "$CLOUD_API_BASE/api/files/upload" \
452
+ -H "Authorization: Bearer $CLOUD_API_TOKEN" \
453
+ -F "file=@$TMP_FILE;type=$MIME_TYPE" \
454
+ -F "storagePath=$STORAGE_PATH" \
455
+ -F "portalSlug=$PORTAL_SLUG" \
456
+ -F "filename=$FILE_NAME" \
457
+ -F "mimeType=$MIME_TYPE" \
458
+ -F "size=$FILE_SIZE" \
459
+ -F "uploadedBy=$UPLOADED_BY" \
460
+ -F "uploadedAt=$UPLOADED_AT" \
461
+ -F "checksum=$CHECKSUM" \
462
+ >/dev/null || exit 1
463
+ done
464
+ fi
465
+ ```
466
+
467
+ ### Cloud Step 7: Upload the artifact to Showpane Cloud
468
+
469
+ ```bash
470
+ PORTAL_COUNT=$(cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" \
471
+ | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('portals', [])))")
472
+
473
+ DEPLOY_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments" \
474
+ -H "Authorization: Bearer $CLOUD_API_TOKEN" \
475
+ -F "artifact=@$ARTIFACT_PATH" \
476
+ -F "source=claude-portal-deploy" \
477
+ -F "app_path=$APP_PATH" \
478
+ -F "portalCount=$PORTAL_COUNT" \
479
+ -F "runtimeData=@$RUNTIME_DATA_PATH;type=application/json")
480
+
481
+ echo "$DEPLOY_RESPONSE" | python3 -c "
482
+ import sys, json
483
+ d = json.load(sys.stdin)
484
+ if 'error' in d:
485
+ print('ERROR: ' + str(d['error']))
486
+ sys.exit(1)
487
+ print('Deployment accepted')
488
+ print('ID: ' + d.get('deploymentId', d.get('id', 'unknown')))
489
+ print('Status: ' + d.get('status', 'unknown'))
490
+ "
491
+ ```
492
+
493
+ Extract the deployment ID from the response. If the API returns an error, show it and stop.
494
+
495
+ ### Cloud Step 8: Wait for cloud publish to finish
496
+
497
+ Poll Showpane Cloud until the deployment reaches `live` or `failed`:
498
+
499
+ ```bash
500
+ DEPLOY_ID=$(echo "$DEPLOY_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deploymentId', d.get('id','')))")
501
+
502
+ echo "Waiting for deployment to go live..."
503
+ for i in $(seq 1 60); do
504
+ sleep 5
505
+ STATUS=$(curl -s \
506
+ -H "Authorization: Bearer $CLOUD_API_TOKEN" \
507
+ "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID" \
508
+ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))")
509
+
510
+ echo " Status: $STATUS"
511
+
512
+ if [ "$STATUS" = "live" ]; then
513
+ echo "Deployment is live!"
514
+ break
515
+ fi
516
+
517
+ if [ "$STATUS" = "failed" ] || [ "$STATUS" = "unhealthy" ]; then
518
+ echo "ERROR: Deployment failed with status: $STATUS"
519
+ curl -s \
520
+ -H "Authorization: Bearer $CLOUD_API_TOKEN" \
521
+ "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID"
522
+ exit 1
523
+ fi
524
+ done
525
+
526
+ if [ "$STATUS" != "live" ]; then
527
+ echo "WARNING: Deployment did not become ready within 5 minutes."
528
+ echo "Check Showpane Cloud for status."
529
+ fi
530
+ ```
531
+
532
+ The publish typically takes 15-60 seconds. Poll every 5 seconds for up to 5 minutes.
533
+
534
+ ### Cloud Step 9: Post-deploy verification
535
+
536
+ Once the deployment is live, verify the portal is accessible:
537
+
538
+ ```bash
539
+ PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
540
+
541
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL")
542
+ echo "Health check: $PORTAL_URL -> HTTP $HTTP_CODE"
543
+ ```
544
+
545
+ Expected: HTTP 200. If not 200, warn the user but do not treat it as a fatal error — DNS propagation or edge caching may cause a brief delay.
546
+
547
+ Also check the API health endpoint:
548
+
549
+ ```bash
550
+ curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/health"
551
+ ```
552
+
553
+ ### Cloud Step 10: Deployment summary
554
+
555
+ Print a clear summary:
556
+
557
+ ```
558
+ Cloud deploy complete!
559
+
560
+ Mode: cloud
561
+ Type check: passed
562
+ Build: .vercel/output/ (N files)
563
+ Portals: N active
564
+ Status: live
565
+ Health: OK (200)
566
+
567
+ Portal URL: https://orgslug.showpane.com
568
+ Deploy ID: dep_xxxxxxxxxxxx
569
+ ```
570
+
571
+ ### Cloud Step 11: Record deployment
572
+
573
+ Log the cloud deployment for operational memory:
574
+
575
+ ```bash
576
+ echo '{"skill":"portal-deploy","key":"deploy","insight":"Cloud deploy to '$CLOUD_ORG_SLUG'.showpane.com. Migrations: <count>. Portals: <count> active. Deploy ID: '$DEPLOY_ID'.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
577
+ ```
578
+
579
+ ### Cloud Step 12: Clean up
580
+
581
+ Remove the temporary artifact:
582
+
583
+ ```bash
584
+ rm -f "$ARTIFACT_PATH"
585
+ rm -f "$RUNTIME_DATA_PATH"
586
+ rm -f "$FILE_MANIFEST_PATH"
587
+ [ -n "${TMP_SYNC_DIR:-}" ] && rm -rf "$TMP_SYNC_DIR"
588
+ ```
589
+
590
+ ---
591
+
592
+ ## Post-Deploy Verification
593
+
594
+ After deployment succeeds (either self-hosted Steps 1-6 or Cloud Steps 1-12), automatically run these verification steps. Do not ask the user — just do them.
595
+
596
+ ### Step V1: DNS/URL Check
597
+ Fetch the portal URL and verify it returns 200:
598
+ ```bash
599
+ PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
600
+ echo "Verifying $PORTAL_URL..."
601
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
602
+ echo "HTTP status: $HTTP_CODE"
603
+ ```
604
+ If not 200, warn but don't fail — DNS propagation may take time for cloud deploys.
605
+
606
+ ### Step V2: Portal Content Check
607
+ If portals exist in the database, verify at least one portal login page loads:
608
+ ```bash
609
+ # Get the first active portal slug
610
+ FIRST_SLUG=$(cd "$APP_PATH" && npx tsx -e "
611
+ const { prisma } = require('./src/lib/db');
612
+ prisma.clientPortal.findFirst({ where: { isActive: true }, select: { slug: true } })
613
+ .then(p => console.log(p?.slug || ''))
614
+ .finally(() => prisma.\$disconnect());
615
+ " 2>/dev/null)
616
+ if [ -n "$FIRST_SLUG" ]; then
617
+ echo "Checking portal: $PORTAL_URL/client/$FIRST_SLUG"
618
+ curl -s -o /dev/null -w "Portal login page: %{http_code}\n" "$PORTAL_URL/client/$FIRST_SLUG" 2>/dev/null
619
+ fi
620
+ ```
621
+
622
+ ### Step V3: SSL Check (cloud only)
623
+ For cloud deploys, verify SSL is working:
624
+ ```bash
625
+ if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_PORTAL_URL" ]; then
626
+ echo "Checking SSL..."
627
+ SSL_OK=$(curl -s -o /dev/null -w "%{http_code}" "https://${CLOUD_ORG_SLUG}.showpane.com" 2>/dev/null)
628
+ echo "SSL status: $SSL_OK"
629
+ fi
630
+ ```
631
+
632
+ ### Step V4: Summary
633
+ Print a deployment summary:
634
+ ```
635
+ ✓ Deployed successfully
636
+ URL: [portal URL]
637
+ Portals: [count]
638
+ SSL: [ok/pending]
639
+
640
+ Next: /portal-status for ongoing monitoring
641
+ ```
642
+
643
+ ---
644
+
645
+ ## Error Recovery
646
+
647
+ Deployments can fail at multiple points. Here is how to recover from each:
648
+
649
+ ### Docker build failure
650
+ If `docker compose build` fails:
651
+ - Check the Dockerfile for syntax errors
652
+ - Check that all required files are present (especially `.env` and `prisma/schema.prisma`)
653
+ - Check available disk space — Docker builds can require significant space
654
+ - Try `docker compose build --no-cache` if a cached layer is stale
655
+
656
+ ### Vercel push failure
657
+ If `git push` fails:
658
+ - **Authentication**: Check git credentials or SSH keys
659
+ - **Remote rejection**: The remote may have branch protections. Check if pushing to the correct branch.
660
+ - **Diverged history**: Someone else pushed since your last pull. Run `git pull --rebase` first.
661
+
662
+ ### Health check failure
663
+ If the health endpoint returns non-200 after deploy:
664
+ - Check application logs: `docker compose logs --tail=100` (Docker) or Vercel function logs
665
+ - Common causes: missing environment variables, database connection issues, port conflicts
666
+ - For Docker, check if the container is actually running: `docker compose ps`
667
+
668
+ ### Cloud: Token invalid or auth expired
669
+ If the Showpane Cloud API returns 401/403 during pre-flight or publish:
670
+ - **401 Unauthorized**: The access token is expired or revoked. Run `showpane login` to re-authenticate.
671
+ - **403 Forbidden**: The token does not belong to an org that can publish. Re-authenticate or finish cloud onboarding.
672
+ - **409 organization_required**: The user authenticated but has no org yet. Send them to Showpane Cloud checkout to start the trial, then re-run `showpane login`.
673
+
674
+ ### Cloud: Artifact upload failure
675
+ If the POST to `/api/deployments` fails:
676
+ - **400 Bad Request**: The artifact is missing or malformed. Rebuild locally and retry.
677
+ - **413 Payload Too Large**: The build artifact is too large. Remove oversized static assets from the portal app.
678
+ - **422 Validation Error**: The cloud control plane rejected the deploy metadata. Show the response body directly.
679
+
680
+ ### Cloud: File sync failure
681
+ If `POST /api/files/plan` or `POST /api/files/upload` fails:
682
+ - **409 Conflict**: Hosted file sync needs object storage configured in Showpane Cloud. Configure S3/R2-backed storage before deploying documents.
683
+ - **404 Not Found**: The target portal slug does not exist in the synced runtime state. Re-export runtime data and retry the deploy.
684
+ - **413 Payload Too Large**: A single uploaded file is too large for multipart upload. Move that document to shared storage and link it instead of embedding it in the portal.
685
+ - **5xx**: The cloud storage layer failed. Retry after verifying storage credentials.
686
+
687
+ ### Cloud: Publish stuck or failed
688
+ If the deployment never reaches `live`:
689
+ - Check the response from `GET /api/deployments/$DEPLOY_ID` for an `error` field.
690
+ - Report the failure exactly as returned by Showpane Cloud.
691
+ - Do not tell the user to debug Vercel directly unless the cloud response explicitly points there.
692
+
693
+ ## Completion
694
+
695
+ As a final step, log skill completion:
696
+
697
+ ```bash
698
+ echo '{"skill":"portal-deploy","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
699
+ ```
700
+
701
+ ## Conventions
702
+
703
+ - Always run pre-flight checks before deploying — never skip the type check
704
+ - Credential warnings are non-blocking — the user decides whether to continue
705
+ - Migrations run before the deploy, not after
706
+ - For Docker: verify the health endpoint after deploy
707
+ - For Vercel: the deploy is async — inform the user to check the dashboard
708
+ - Never force-push or reset git history during Vercel deploys
709
+ - If any pre-flight check fails (type errors, missing deploy config), stop and explain
710
+ - Show the full deployment summary with portal count, migration status, and health
711
+ - The deploy commit message is always "Deploy portal updates" — keep it simple and consistent
712
+ - If the user wants a custom commit message, they should commit manually before running deploy
713
+ - For Vercel deploys, the build typically takes 1-3 minutes — do not poll or wait, just inform the user
714
+ - Always run migrations before the build/push step, never after — the app code expects the latest schema
715
+ - If this is the first deploy, suggest running `/portal credentials` for all portals before deploying so clients can actually log in
716
+ - For Cloud deploys: build locally, upload the artifact to Showpane Cloud, and let the control plane publish it
717
+ - For Cloud deploys: always wait for the deployment to reach `live` before declaring success
718
+ - For Cloud deploys: the portal URL is `https://{org}.showpane.com` — verify it returns 200 after deploy
719
+ - For Cloud deploys: clean up the temporary artifact after deploy completes
720
+ - The CLI `showpane login` is auth only — org creation and billing live in Showpane Cloud checkout
721
+ - For Cloud deploys: OSS should never call Vercel directly or require project/provider details from the user