showpane 0.4.1 → 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 +14 -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 +873 -166
  106. package/package.json +3 -2
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: portal-upgrade
3
+ description: |
4
+ Self-update the Showpane skill pack to the latest version. Use when asked to "upgrade",
5
+ "update showpane", "update skills", "get latest version", or "check for updates". (showpane)
6
+ allowed-tools: [Bash, Read, Write, Edit, Glob, Grep]
7
+ ---
8
+
9
+ ## Preamble (run first)
10
+
11
+ Before doing anything else, execute this block in a Bash tool call:
12
+
13
+ ```bash
14
+ CONFIG="$HOME/.showpane/config.json"
15
+ if [ ! -f "$CONFIG" ]; then
16
+ echo "Showpane not configured. Run /portal setup first."
17
+ exit 1
18
+ fi
19
+ APP_PATH=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('app_path',''))" 2>/dev/null)
20
+ DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','docker'))" 2>/dev/null)
21
+ ORG_SLUG=$(cat "$CONFIG" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
22
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
23
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
24
+ DATABASE_URL="${DATABASE_URL:-}"
25
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
26
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
27
+ exit 1
28
+ fi
29
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
30
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
31
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
32
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
33
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
34
+
35
+ # Predictive next-skill suggestion
36
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
37
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
38
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
39
+ fi
40
+
41
+ # Search relevant learnings
42
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
43
+ if [ -f "$LEARN_FILE" ]; then
44
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
45
+ echo "LEARNINGS: $_LEARN_COUNT entries"
46
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
47
+ echo "RECENT_LEARNINGS:"
48
+ tail -5 "$LEARN_FILE" 2>/dev/null
49
+ fi
50
+ fi
51
+
52
+ # Track skill execution
53
+ SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
54
+ mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
55
+ echo '{"skill":"portal-upgrade","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
56
+ ```
57
+
58
+ If RECENT_SKILLS is shown, suggest the likely next skill:
59
+ - After portal-create → suggest /portal-preview
60
+ - After portal-preview → suggest /portal-deploy or /portal-share
61
+ - After portal-deploy → suggest /portal-status or /portal-verify
62
+ - After portal-setup → suggest /portal-create
63
+ - After portal-credentials → suggest /portal-share
64
+ - After portal-update → suggest /portal-deploy
65
+
66
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
67
+ relevant warnings or tips for this operation. Apply them where relevant but don't
68
+ mention them unless they directly affect the current task.
69
+
70
+ ## Overview
71
+
72
+ This skill updates both the global Showpane toolchain and the current project using the packaged Showpane CLI. The upgrade is versioned -- it does not depend on the user's project being a git clone of the upstream Showpane repository.
73
+
74
+ The flow is:
75
+ 1. Check the current installed Showpane/toolchain version
76
+ 2. Check the latest published CLI version on npm
77
+ 3. Run a dry-run project upgrade to detect conflicts
78
+ 4. Sync the matching global toolchain
79
+ 5. Apply the project upgrade
80
+
81
+ ## Steps
82
+
83
+ ### Step 1: Check current version
84
+
85
+ Read the current skill pack version from the VERSION file:
86
+
87
+ ```bash
88
+ cat $SKILL_DIR/VERSION
89
+ ```
90
+
91
+ This was already captured by the preamble, but read it explicitly here so you have the full version string (e.g., "1.0.0 (requires app >= 0.1.0)").
92
+
93
+ Display the current version:
94
+
95
+ "Current version: 1.0.0"
96
+
97
+ ### Step 2: Check the latest published Showpane version
98
+
99
+ Run:
100
+
101
+ ```bash
102
+ npm view showpane version
103
+ ```
104
+
105
+ If the command fails, report:
106
+
107
+ "Could not reach the npm registry. Check your network connection and try again."
108
+
109
+ If the returned version matches the current Showpane version, inform the user that no published upgrade is available and stop.
110
+
111
+ ### Step 3: Dry-run the project upgrade
112
+
113
+ Run a dry-run against the user's project before changing anything:
114
+
115
+ ```bash
116
+ npx showpane@latest upgrade --project "$APP_PATH" --dry-run
117
+ ```
118
+
119
+ If the dry-run reports conflicts, stop and show the conflicted managed files. Explain:
120
+
121
+ "Showpane found local edits in framework-managed files. Review or revert those files before running the upgrade."
122
+
123
+ ### Step 4: Sync the latest toolchain
124
+
125
+ Install the matching global Showpane toolchain and Claude Code skill links:
126
+
127
+ ```bash
128
+ npx showpane@latest sync
129
+ ```
130
+
131
+ If sync fails, stop and report the error. Do not continue to the project upgrade.
132
+
133
+ ### Step 5: Apply the project upgrade
134
+
135
+ Run:
136
+
137
+ ```bash
138
+ npx showpane@latest upgrade --project "$APP_PATH"
139
+ ```
140
+
141
+ This applies packaged scaffold updates to managed files only. It does not rely on upstream git history.
142
+
143
+ ### Step 6: Display the resulting version
144
+
145
+ Read the project version file:
146
+
147
+ ```bash
148
+ cat "$APP_PATH/VERSION"
149
+ ```
150
+
151
+ Display a short confirmation:
152
+
153
+ ```
154
+ ════════════════════════════════════════
155
+ Showpane upgraded!
156
+
157
+ Toolchain: latest published version
158
+ App: <APP_PATH/VERSION>
159
+ ════════════════════════════════════════
160
+ ```
161
+
162
+ ## Version Compatibility
163
+
164
+ The VERSION file format remains: `<skill_version> (requires app >= <min_app_version>)`
165
+
166
+ After syncing the toolchain, compare:
167
+ - Read the new minimum app version from `"$SKILL_DIR/skills/VERSION"`
168
+ - Read the current app version from `"$APP_PATH/VERSION"`
169
+ - If the app version is below the minimum, warn the user
170
+
171
+ ## Conventions
172
+
173
+ - Always show the current version before checking for updates. This gives the user a reference point.
174
+ - Always run the dry-run first. No silent updates.
175
+ - Use double-line box drawing (`═`) for the version display boxes.
176
+ - If the user runs upgrade frequently (check learnings), keep output minimal: just the version change and commit count.
177
+ - Do not auto-upgrade on other skill invocations. The upgrade is only triggered explicitly by this skill.
178
+
179
+ ## Edge Cases
180
+
181
+ - **Dry-run conflicts**: Stop and surface the file list. Do not attempt a force-upgrade.
182
+ - **Registry/network failure**: If `npm view` or `npx showpane@latest ...` fails due to connectivity, report it and stop.
183
+ - **Toolchain sync fails**: Do not continue to the project upgrade.
184
+
185
+ ## Error Handling
186
+
187
+ - If the preamble fails, stop and display the error.
188
+ - If the dry-run fails, report the error and stop.
189
+ - If the real upgrade fails after the dry-run succeeded, show the CLI error output and stop.
190
+ - Do not attempt to recover with git commands.
191
+
192
+ ## Changelog Awareness
193
+
194
+ After a successful upgrade, check if a CHANGELOG.md exists in the project root:
195
+
196
+ ```bash
197
+ cat "$APP_PATH/CHANGELOG.md" 2>/dev/null | head -50
198
+ ```
199
+
200
+ If a changelog exists, extract the entries between the previous and current version and display them as a "What's new" section:
201
+
202
+ ```
203
+ What's new in 1.1.0:
204
+ - Portal analytics now shows trend comparisons
205
+ - Fixed credential rotation not bumping version number
206
+ - Preamble now includes telemetry opt-in status
207
+ ```
208
+
209
+ If no changelog exists, the CLI dry-run summary serves as the change summary.
210
+
211
+ ## Upgrade Frequency
212
+
213
+ There is no auto-upgrade mechanism. The user must explicitly run `/portal upgrade` to check for and apply updates.
214
+
215
+ Suggested upgrade frequency: monthly, or when the user encounters an issue that may have been fixed upstream. The skill does not nag or remind.
216
+
217
+ ## Learnings Preservation
218
+
219
+ Upgrading does not affect the user's learnings file (`~/.showpane/learnings.jsonl`). Learnings are stored outside the project and persist across upgrades. Similarly, the config file (`~/.showpane/config.json`) and telemetry data (`~/.showpane/telemetry.jsonl`) are not touched by the upgrade.
220
+
221
+ If a new version introduces changes to the learnings format, the changelog or setup script should handle migration transparently.
222
+
223
+ ## Completion
224
+
225
+ As a final step, log skill completion:
226
+
227
+ ```bash
228
+ echo '{"skill":"portal-upgrade","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
229
+ ```
230
+
231
+ ## Related Skills
232
+
233
+ - `/portal setup` -- re-run setup independently if the upgraded project needs reconfiguration
234
+ - `/portal status` -- check that all portals are healthy after an upgrade
235
+ - `/portal dev` -- restart the dev server to pick up any app changes
@@ -0,0 +1,265 @@
1
+ ---
2
+ name: portal-verify
3
+ description: |
4
+ Verify deployed portal health: DNS, SSL, login page, content rendering, engagement tracking.
5
+ Trigger phrases: "verify portal", "check deployment", "is my portal working", "portal health". (showpane)
6
+ allowed-tools: [Bash, Read, Glob, Grep]
7
+ ---
8
+
9
+ ## Preamble (run first)
10
+
11
+ ```bash
12
+ # Read config
13
+ CONFIG="$HOME/.showpane/config.json"
14
+ if [ ! -f "$CONFIG" ]; then
15
+ echo "Showpane not configured. Run /portal-setup first."
16
+ exit 1
17
+ fi
18
+ APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
19
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
20
+ ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
21
+ 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)
22
+ 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)
23
+ 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)
24
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
25
+ CLOUD_EVENTS_URL="${CLOUD_EVENTS_URL:-$CLOUD_API_BASE}"
26
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
27
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
28
+ DATABASE_URL="${DATABASE_URL:-}"
29
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
30
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
31
+ exit 1
32
+ fi
33
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
34
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
35
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
36
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
37
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
38
+ ```
39
+
40
+ ## Steps
41
+
42
+ ### Step 1: URL Reachability
43
+
44
+ Determine the portal URL based on deploy mode and check that it responds.
45
+
46
+ **For self-hosted (docker/vercel):**
47
+ ```bash
48
+ PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
49
+ echo "Checking portal URL: $PORTAL_URL"
50
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
51
+ echo "HTTP status: $HTTP_CODE"
52
+ if [ "$HTTP_CODE" = "200" ]; then
53
+ URL_STATUS="ok"
54
+ else
55
+ URL_STATUS="unreachable ($HTTP_CODE)"
56
+ fi
57
+ ```
58
+
59
+ **For cloud deploys:**
60
+ ```bash
61
+ if [ "$DEPLOY_MODE" = "cloud" ]; then
62
+ PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
63
+ echo "Checking app URL: $CLOUD_API_BASE"
64
+ APP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$CLOUD_API_BASE/api/health" 2>/dev/null)
65
+ echo "App health: $APP_CODE"
66
+
67
+ echo "Checking org URL: $PORTAL_URL"
68
+ ORG_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
69
+ echo "Org portal: $ORG_CODE"
70
+
71
+ if [ "$ORG_CODE" = "200" ]; then
72
+ URL_STATUS="ok"
73
+ else
74
+ URL_STATUS="unreachable ($ORG_CODE)"
75
+ fi
76
+ fi
77
+ ```
78
+
79
+ If the URL is unreachable, warn the user but continue with remaining checks. DNS propagation or cold starts can cause brief unavailability.
80
+
81
+ ### Step 2: SSL Verification (cloud only)
82
+
83
+ For cloud deploys, verify the SSL certificate is valid and HTTPS redirect works.
84
+
85
+ ```bash
86
+ if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_ORG_SLUG" ]; then
87
+ HTTPS_URL="https://${CLOUD_ORG_SLUG}.showpane.com"
88
+ echo "Checking SSL for $HTTPS_URL..."
89
+
90
+ # Check certificate validity and expiry
91
+ SSL_INFO=$(echo | openssl s_client -servername "${CLOUD_ORG_SLUG}.showpane.com" \
92
+ -connect "${CLOUD_ORG_SLUG}.showpane.com:443" 2>/dev/null \
93
+ | openssl x509 -noout -dates 2>/dev/null)
94
+
95
+ if [ -n "$SSL_INFO" ]; then
96
+ SSL_EXPIRY=$(echo "$SSL_INFO" | grep 'notAfter' | cut -d= -f2)
97
+ echo "SSL valid, expires: $SSL_EXPIRY"
98
+ SSL_STATUS="valid, expires $SSL_EXPIRY"
99
+ else
100
+ echo "SSL certificate not found or invalid"
101
+ SSL_STATUS="not found"
102
+ fi
103
+
104
+ # Check HTTPS redirect from HTTP
105
+ HTTP_URL="http://${CLOUD_ORG_SLUG}.showpane.com"
106
+ REDIRECT_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HTTP_URL" 2>/dev/null)
107
+ if [ "$REDIRECT_CODE" = "301" ] || [ "$REDIRECT_CODE" = "308" ]; then
108
+ echo "HTTP->HTTPS redirect: OK ($REDIRECT_CODE)"
109
+ else
110
+ echo "HTTP->HTTPS redirect: $REDIRECT_CODE (expected 301 or 308)"
111
+ fi
112
+ else
113
+ SSL_STATUS="n/a (self-hosted)"
114
+ fi
115
+ ```
116
+
117
+ ### Step 3: Portal Login Pages
118
+
119
+ List all active portals from the database and verify each login page loads.
120
+
121
+ ```bash
122
+ cd "$APP_PATH" && PORTAL_LIST=$(npx tsx -e "
123
+ const { prisma } = require('./src/lib/db');
124
+ prisma.clientPortal.findMany({ where: { isActive: true }, select: { slug: true, clientName: true } })
125
+ .then(portals => console.log(JSON.stringify(portals)))
126
+ .finally(() => prisma.\$disconnect());
127
+ " 2>/dev/null)
128
+
129
+ echo "Active portals: $PORTAL_LIST"
130
+ ```
131
+
132
+ For each portal, verify the login page returns a response and contains auth-related content:
133
+
134
+ ```bash
135
+ PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
136
+ echo "$PORTAL_LIST" | python3 -c "
137
+ import sys, json
138
+ portals = json.load(sys.stdin)
139
+ for p in portals:
140
+ slug = p['slug']
141
+ name = p.get('clientName', slug)
142
+ print(f' {name} ({slug})')
143
+ print(f'{len(portals)} active portals')
144
+ " 2>/dev/null
145
+ ```
146
+
147
+ Then for each portal slug, check the login page:
148
+
149
+ ```bash
150
+ for SLUG in $(echo "$PORTAL_LIST" | python3 -c "
151
+ import sys, json
152
+ for p in json.load(sys.stdin): print(p['slug'])
153
+ " 2>/dev/null); do
154
+ PAGE_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/client/$SLUG" 2>/dev/null)
155
+ PAGE_BODY=$(curl -s "$PORTAL_URL/client/$SLUG" 2>/dev/null | head -c 5000)
156
+ HAS_AUTH=$(echo "$PAGE_BODY" | grep -ci 'password\|login\|sign.in\|auth' 2>/dev/null || echo "0")
157
+ if [ "$PAGE_CODE" = "200" ] && [ "$HAS_AUTH" -gt 0 ]; then
158
+ echo " $SLUG: Login page OK"
159
+ elif [ "$PAGE_CODE" = "200" ]; then
160
+ echo " $SLUG: Page loads ($PAGE_CODE) but no auth content detected"
161
+ else
162
+ echo " $SLUG: FAILED ($PAGE_CODE)"
163
+ fi
164
+ done
165
+ ```
166
+
167
+ ### Step 4: Engagement Tracking
168
+
169
+ Verify the client events API endpoint responds.
170
+
171
+ ```bash
172
+ PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
173
+ EVENTS_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS "$PORTAL_URL/api/client-events" 2>/dev/null)
174
+ echo "Events endpoint: $EVENTS_CODE"
175
+ ```
176
+
177
+ For cloud deploys, also verify the cloud events URL is reachable:
178
+
179
+ ```bash
180
+ if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_EVENTS_URL" ]; then
181
+ CLOUD_EVENTS_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS "$CLOUD_EVENTS_URL/api/client-events" 2>/dev/null)
182
+ echo "Cloud events endpoint: $CLOUD_EVENTS_CODE"
183
+ fi
184
+ ```
185
+
186
+ The events endpoint should return 200 or 204 for OPTIONS requests. A 404 means the route is not deployed. A 405 (Method Not Allowed) is also acceptable — it means the route exists but only accepts POST.
187
+
188
+ ### Step 5: File Downloads
189
+
190
+ Check if any PortalFile records exist and verify file download endpoints work.
191
+
192
+ ```bash
193
+ cd "$APP_PATH" && FILE_INFO=$(npx tsx -e "
194
+ const { prisma } = require('./src/lib/db');
195
+ prisma.portalFile.findMany({ select: { id: true, filename: true, portalId: true } })
196
+ .then(files => console.log(JSON.stringify({ count: files.length, files: files.slice(0, 5) })))
197
+ .catch(() => console.log(JSON.stringify({ count: 0, files: [] })))
198
+ .finally(() => prisma.\$disconnect());
199
+ " 2>/dev/null)
200
+
201
+ FILE_COUNT=$(echo "$FILE_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null)
202
+ echo "Hosted files: $FILE_COUNT"
203
+ ```
204
+
205
+ If files exist, verify a sample file download endpoint responds:
206
+
207
+ ```bash
208
+ if [ "$FILE_COUNT" -gt 0 ]; then
209
+ SAMPLE_FILE_ID=$(echo "$FILE_INFO" | python3 -c "
210
+ import sys, json
211
+ files = json.load(sys.stdin).get('files', [])
212
+ if files: print(files[0]['id'])
213
+ " 2>/dev/null)
214
+ if [ -n "$SAMPLE_FILE_ID" ]; then
215
+ PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
216
+ DL_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/files/$SAMPLE_FILE_ID" 2>/dev/null)
217
+ echo "File download endpoint: $DL_CODE"
218
+ if [ "$DL_CODE" = "200" ] || [ "$DL_CODE" = "302" ]; then
219
+ FILE_STATUS="accessible"
220
+ else
221
+ FILE_STATUS="endpoint returned $DL_CODE"
222
+ fi
223
+ fi
224
+ else
225
+ FILE_STATUS="none hosted"
226
+ fi
227
+ ```
228
+
229
+ ### Step 6: Report
230
+
231
+ Compile all check results into a health report. Print it in this format:
232
+
233
+ ```
234
+ Portal Health Report
235
+ ====================
236
+ URL: https://orgslug.showpane.com [ok/unreachable]
237
+ SSL: Valid, expires 2027-01-15 [ok/not found/n/a]
238
+ Portals: 3 active
239
+ acme: Login page [ok/failed], Events [ok/failed]
240
+ betacorp: Login page [ok/failed], Events [ok/failed]
241
+ demo: Login page [ok/failed], Events [ok/failed]
242
+ Files: 5 hosted, all accessible [ok/issues]
243
+
244
+ Overall: HEALTHY / DEGRADED / UNHEALTHY
245
+ ```
246
+
247
+ Rules for the overall status:
248
+ - **HEALTHY**: All checks pass (URL reachable, SSL valid for cloud, all portals load, events endpoint responds)
249
+ - **DEGRADED**: URL reachable but some portals fail, or SSL is pending, or events endpoint is down
250
+ - **UNHEALTHY**: URL is unreachable, or no portals load
251
+
252
+ After the report, suggest next steps:
253
+ - If UNHEALTHY: "Run /portal-deploy to redeploy, or /investigate to debug."
254
+ - If DEGRADED: "Check individual portal issues above. Run /portal-status for ongoing monitoring."
255
+ - If HEALTHY: "All systems operational. Run /portal-status for ongoing monitoring."
256
+
257
+ ## Conventions
258
+
259
+ - This skill is read-only — it never modifies the deployment or database
260
+ - All checks use curl with short timeouts to avoid hanging on unreachable endpoints
261
+ - Cloud checks verify both the control plane (app.showpane.com) and the org portal (orgslug.showpane.com)
262
+ - The PortalFile model may not exist in all schema versions — catch errors gracefully
263
+ - If the database is unreachable, skip DB-dependent checks (Steps 3, 5) and note it in the report
264
+ - SSL checks only apply to cloud deploys — self-hosted SSL is the user's responsibility
265
+ - The events endpoint check uses OPTIONS to avoid creating spurious event records
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # Portal guard: warns before destructive portal operations.
3
+ # Used as a PreToolUse hook on Bash, Edit, Write tools.
4
+
5
+ # Read tool input from stdin
6
+ input=$(cat)
7
+
8
+ # Extract the command (for Bash) or file_path (for Edit/Write)
9
+ command=$(echo "$input" | grep -o '"command":"[^"]*"' | sed 's/"command":"//;s/"$//' 2>/dev/null)
10
+ file_path=$(echo "$input" | grep -o '"file_path":"[^"]*"' | sed 's/"file_path":"//;s/"$//' 2>/dev/null)
11
+
12
+ # Check for destructive portal operations in bash commands
13
+ if [ -n "$command" ]; then
14
+ case "$command" in
15
+ *"prisma"*"migrate reset"*|*"prisma"*"db push --force"*)
16
+ echo '{"permissionDecision":"ask","message":"⚠️ This will reset the database and destroy all portal data. Are you sure?"}'
17
+ exit 0
18
+ ;;
19
+ *"rm -rf"*"app/"*|*"rm -r"*"app/"*)
20
+ echo '{"permissionDecision":"ask","message":"⚠️ This will delete the portal application directory."}'
21
+ exit 0
22
+ ;;
23
+ *"DROP TABLE"*|*"drop table"*|*"DELETE FROM"*"ClientPortal"*|*"DELETE FROM"*"Organization"*)
24
+ echo '{"permissionDecision":"ask","message":"⚠️ This SQL command will destroy portal or organization data."}'
25
+ exit 0
26
+ ;;
27
+ *"vercel"*"rm"*|*"vercel"*"remove"*|*"vercel"*"delete"*)
28
+ echo '{"permissionDecision":"ask","message":"⚠️ This will delete a Vercel project. Published portals will go offline."}'
29
+ exit 0
30
+ ;;
31
+ esac
32
+ fi
33
+
34
+ # Check for edits to sensitive files
35
+ if [ -n "$file_path" ]; then
36
+ case "$file_path" in
37
+ *".env"*|*"credentials"*|*"secret"*)
38
+ echo '{"permissionDecision":"ask","message":"⚠️ Modifying credentials/secrets file. Verify this is intentional."}'
39
+ exit 0
40
+ ;;
41
+ *"prisma/schema.prisma"*)
42
+ echo '{"permissionDecision":"ask","message":"⚠️ Modifying the database schema. This may require a migration."}'
43
+ exit 0
44
+ ;;
45
+ esac
46
+ fi
47
+
48
+ # Allow everything else
49
+ echo '{}'
@@ -0,0 +1,33 @@
1
+ # Showpane Platform Constraints
2
+
3
+ Use these only when relevant to the user's request. Do not proactively dump them unless the request would violate one.
4
+
5
+ ## Cloud
6
+
7
+ - Cloud CLI login is intended for workspace owners/admins only.
8
+ - Do not suggest that users can copy reusable cloud API tokens from the dashboard.
9
+
10
+ ## Uploads
11
+
12
+ - Uploaded files are restricted to a safe allowlist.
13
+ - Active web content is not allowed. In particular: `svg`, `html`, `xml`, and JavaScript files should be rejected.
14
+ - Per-file upload limit: `50MB`.
15
+ - Per-portal storage quota: `500MB`.
16
+ - Upload rate limit: `10 uploads/minute` per portal.
17
+
18
+ ## Portal Sharing
19
+
20
+ - Share links are temporary access links.
21
+ - Share-link access should not be described as equivalent to a full operator login.
22
+ - Share-link visitors should not be told they can upload files or generate new share links.
23
+
24
+ ## Analytics
25
+
26
+ - Browser clients should not receive reusable org-wide analytics bearer tokens.
27
+ - Event ingestion is rate-limited.
28
+ - Event metadata must stay small; large blobs should not be suggested as analytics payloads.
29
+
30
+ ## Messaging Guidance
31
+
32
+ - When a request conflicts with one of these rules, explain the limit plainly and suggest the nearest supported path.
33
+ - Prefer: "Showpane currently doesn't allow X" over a long security explanation.