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.
- package/README.md +14 -1
- package/bundle/meta/scaffold-manifest.json +73 -0
- package/bundle/scaffold/VERSION +1 -0
- package/bundle/scaffold/__dot__env.example +24 -0
- package/bundle/scaffold/__dot__gitignore +41 -0
- package/bundle/scaffold/docker/Caddyfile +3 -0
- package/bundle/scaffold/docker/Dockerfile +30 -0
- package/bundle/scaffold/docker-compose.yml +53 -0
- package/bundle/scaffold/next.config.ts +20 -0
- package/bundle/scaffold/package-lock.json +5843 -0
- package/bundle/scaffold/package.json +42 -0
- package/bundle/scaffold/postcss.config.js +6 -0
- package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
- package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
- package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
- package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
- package/bundle/scaffold/prisma/schema.local.prisma +131 -0
- package/bundle/scaffold/prisma/schema.prisma +128 -0
- package/bundle/scaffold/prisma/seed.ts +49 -0
- package/bundle/scaffold/public/example-avatar.svg +4 -0
- package/bundle/scaffold/public/example-logo.svg +4 -0
- package/bundle/scaffold/public/robots.txt +2 -0
- package/bundle/scaffold/scripts/backup.sh +19 -0
- package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
- package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
- package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
- package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
- package/bundle/scaffold/scripts/restore.sh +31 -0
- package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
- package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
- package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
- package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
- package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
- package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
- package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
- package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
- package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
- package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
- package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
- package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
- package/bundle/scaffold/src/app/api/health/route.ts +19 -0
- package/bundle/scaffold/src/app/globals.css +7 -0
- package/bundle/scaffold/src/app/layout.tsx +25 -0
- package/bundle/scaffold/src/app/page.tsx +171 -0
- package/bundle/scaffold/src/components/portal-login.tsx +169 -0
- package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
- package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
- package/bundle/scaffold/src/lib/branding.ts +50 -0
- package/bundle/scaffold/src/lib/client-auth.ts +98 -0
- package/bundle/scaffold/src/lib/client-portals.ts +134 -0
- package/bundle/scaffold/src/lib/control-plane.ts +100 -0
- package/bundle/scaffold/src/lib/db.ts +7 -0
- package/bundle/scaffold/src/lib/files.ts +124 -0
- package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
- package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
- package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
- package/bundle/scaffold/src/lib/storage.ts +204 -0
- package/bundle/scaffold/src/lib/token.ts +186 -0
- package/bundle/scaffold/src/lib/utils.ts +6 -0
- package/bundle/scaffold/src/middleware.ts +61 -0
- package/bundle/scaffold/tailwind.config.ts +15 -0
- package/bundle/scaffold/tests/__dot__gitkeep +0 -0
- package/bundle/scaffold/tsconfig.json +23 -0
- package/bundle/scaffold/vitest.config.ts +13 -0
- package/bundle/toolchain/VERSION +1 -0
- package/bundle/toolchain/bin/check-slug.ts +59 -0
- package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
- package/bundle/toolchain/bin/create-portal.ts +71 -0
- package/bundle/toolchain/bin/delete-portal.ts +48 -0
- package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
- package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
- package/bundle/toolchain/bin/generate-share-link.ts +68 -0
- package/bundle/toolchain/bin/list-portals.ts +53 -0
- package/bundle/toolchain/bin/materialize-file.ts +35 -0
- package/bundle/toolchain/bin/query-analytics.ts +88 -0
- package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
- package/bundle/toolchain/bin/showpane-config +63 -0
- package/bundle/toolchain/bin/tsconfig.json +13 -0
- package/bundle/toolchain/skills/VERSION +1 -0
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
- package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
- package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
- package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
- package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
- package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
- package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
- package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
- package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
- package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
- package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
- package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
- package/bundle/toolchain/skills/shared/preamble.md +137 -0
- package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
- package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
- package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
- package/dist/index.js +873 -166
- 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.
|