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,309 @@
1
+ ---
2
+ name: portal-setup
3
+ description: |
4
+ Interactive setup wizard for Showpane. Creates config, installs deps, runs migrations, and creates the organization.
5
+ Trigger phrases: "portal setup", "configure showpane", "set up showpane", "initialize showpane". (showpane)
6
+ allowed-tools: [Bash, Read, Write, Edit, Glob, Grep]
7
+ hooks:
8
+ PreToolUse:
9
+ - matcher: "Bash"
10
+ hooks:
11
+ - type: command
12
+ command: "bash ${CLAUDE_SKILL_DIR}/../showpane-shared/bin/check-portal-guard.sh"
13
+ ---
14
+
15
+ ## Preamble (run first)
16
+
17
+ This skill's preamble is different from other skills — it does NOT require config.json to exist, because this skill creates it.
18
+
19
+ ```bash
20
+ # --- Portal Setup Preamble ---
21
+ CONFIG="$HOME/.showpane/config.json"
22
+ EXISTING_CONFIG=false
23
+ if [ -f "$CONFIG" ]; then
24
+ EXISTING_CONFIG=true
25
+ APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null || true)
26
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode',''))" 2>/dev/null || true)
27
+ ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
28
+ echo "EXISTING_CONFIG: true"
29
+ echo "APP_PATH: $APP_PATH"
30
+ echo "DEPLOY_MODE: $DEPLOY_MODE"
31
+ echo "ORG_SLUG: $ORG_SLUG"
32
+ else
33
+ echo "EXISTING_CONFIG: false"
34
+ echo "No config found. Starting fresh setup."
35
+ fi
36
+
37
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
38
+ SKILL_VERSION=$(head -1 "$SKILL_DIR/skills/VERSION" 2>/dev/null | cut -d' ' -f1 || echo "unknown")
39
+ echo "SHOWPANE: v$SKILL_VERSION | SETUP MODE"
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-setup","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
62
+ # --- End Preamble ---
63
+ ```
64
+
65
+ If RECENT_SKILLS is shown, suggest the likely next skill:
66
+ - After portal-create → suggest /portal-preview
67
+ - After portal-preview → suggest /portal-deploy or /portal-share
68
+ - After portal-deploy → suggest /portal-status or /portal-verify
69
+ - After portal-setup → suggest /portal-create
70
+ - After portal-credentials → suggest /portal-share
71
+ - After portal-update → suggest /portal-deploy
72
+
73
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
74
+ relevant warnings or tips for this operation. Apply them where relevant but don't
75
+ mention them unless they directly affect the current task.
76
+
77
+ ## Steps
78
+
79
+ ### Step 1: Check for existing configuration
80
+
81
+ If the preamble output shows `EXISTING_CONFIG: true`, inform the user that Showpane is already configured and show the current settings. Ask if they want to reconfigure. If they say no, exit gracefully. If they say yes, continue with the setup — existing values become defaults the user can accept or change.
82
+
83
+ ### Step 2: Detect or ask for app_path
84
+
85
+ Try to find the Showpane app automatically by checking these locations in order:
86
+
87
+ 1. Current working directory — look for `package.json` containing `"name"` with "showpane" in it
88
+ 2. Parent directory — check `../app/` for the same
89
+ 3. Common locations: `~/git/showpane/`, `~/showpane/`
90
+ 4. The `SHOWPANE_APP_PATH` environment variable
91
+
92
+ Run the detection:
93
+
94
+ ```bash
95
+ for candidate in "$(pwd)" "$(pwd)/../app" "$HOME/git/showpane/app" "$HOME/showpane/app"; do
96
+ if [ -f "$candidate/package.json" ] && [ -f "$candidate/prisma/schema.prisma" ]; then
97
+ echo "FOUND: $(cd "$candidate" && pwd)"
98
+ break
99
+ fi
100
+ done
101
+ ```
102
+
103
+ If found, confirm with the user: "Found Showpane app at /path/to/app. Use this? (Y/n)"
104
+
105
+ If not found, ask the user to provide the path. Validate that the path exists and contains both `package.json` and `prisma/schema.prisma`. If either file is missing, the path is not a valid Showpane app directory — explain which file is missing and ask again.
106
+
107
+ Resolve the path to an absolute path (no `~` or relative components) before storing it. Use `cd "$path" && pwd` to resolve.
108
+
109
+ Store the resolved absolute path as `APP_PATH`.
110
+
111
+ #### Packaged Installer Mode
112
+
113
+ When the user is inside a freshly generated Showpane project, the setup should:
114
+ - Prefer the current working directory as `APP_PATH`
115
+ - Skip any suggestion to clone the upstream Showpane repository
116
+ - Auto-detect SQLite from DATABASE_URL (if it starts with "file:" it's SQLite)
117
+ - Still ask for org name, contact details, and website URL
118
+ - Be concise — the user just ran `npx showpane` and wants to get going fast
119
+
120
+ ### Step 3: Ask for deploy mode
121
+
122
+ Present the options:
123
+
124
+ - **docker** — Self-hosted with Docker Compose. Best for VPS, dedicated servers, or local development.
125
+ - **vercel** — Deployed via Vercel. Pushes to git trigger automatic deploys.
126
+
127
+ Default to `docker` if the user doesn't have a preference. Store as `DEPLOY_MODE`.
128
+
129
+ ### Step 4: Install dependencies
130
+
131
+ Check if `$APP_PATH/node_modules` exists. If not, run:
132
+
133
+ ```bash
134
+ cd "$APP_PATH" && npm install
135
+ ```
136
+
137
+ If node_modules exists but `.prisma` client is missing, run:
138
+
139
+ ```bash
140
+ cd "$APP_PATH" && npx prisma generate
141
+ ```
142
+
143
+ Wait for installation to complete before proceeding.
144
+
145
+ ### Step 5: Run database migrations
146
+
147
+ Source the app's `.env` file to get `DATABASE_URL`:
148
+
149
+ ```bash
150
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
151
+ ```
152
+
153
+ Check if the database has existing tables. For a fresh database:
154
+
155
+ ```bash
156
+ cd "$APP_PATH" && npx prisma migrate dev
157
+ ```
158
+
159
+ For an existing database with pending migrations:
160
+
161
+ ```bash
162
+ cd "$APP_PATH" && npx prisma migrate deploy
163
+ ```
164
+
165
+ If `DATABASE_URL` is not set in `.env`, inform the user they need to configure it. Provide guidance:
166
+ - For local development: `postgresql://postgres:postgres@localhost:5432/showpane`
167
+ - For Docker: the docker-compose.yml should provide it
168
+ - For Vercel: set it in the Vercel dashboard environment variables
169
+
170
+ Do NOT proceed until migrations complete successfully.
171
+
172
+ ### Step 6: Create or find Organization record
173
+
174
+ Ask the user for their organization details one at a time. Do not present all questions at once — guide them through the process conversationally.
175
+
176
+ 1. **Organization name** (required) — e.g., "Acme Consulting". This is the name that appears in portal headers alongside the client name.
177
+ 2. **Contact name** (required) — the person who appears on portal footers as the point of contact, e.g., "Jane Smith". This is typically the account manager or sales rep.
178
+ 3. **Contact email** (required) — e.g., "jane@acme.com". Displayed in the portal footer as a mailto link.
179
+ 4. **Contact title** (optional, default: "Account Manager") — e.g., "Director", "Partner", "Client Success Lead". Shown next to the contact name in the portal footer.
180
+ 5. **Contact phone** (optional) — e.g., "+44 7700 900000". If provided, displayed alongside email in the portal footer.
181
+ 6. **Company website URL** (optional) — e.g., "acme.com". Used to auto-fetch the company logo via Clearbit.
182
+ - If provided, fetch logo URL: `https://logo.clearbit.com/{domain}` and store in `Organization.logoUrl`
183
+ - Also store the URL in `Organization.websiteUrl`
184
+ 7. **Contact avatar**: Auto-populated from the contact email via Gravatar. No need to ask — just use `getAvatarUrl(email, contactName)` from `app/src/lib/branding.ts` and store in `Organization.contactAvatar`
185
+
186
+ Generate an org slug from the organization name: lowercase, replace spaces with hyphens, strip non-alphanumeric characters except hyphens, remove consecutive hyphens. For example, "Acme Consulting Ltd." becomes `acme-consulting-ltd`. Confirm the generated slug with the user and allow them to override it.
187
+
188
+ Create the organization record in the database using the app's Prisma client. The Organization model stores:
189
+ - `name`, `slug`, `contactName`, `contactEmail`, `contactTitle`, `contactPhone`
190
+
191
+ If an Organization with that slug already exists in the database, present two options:
192
+ 1. Use the existing organization (show its current details so the user can verify)
193
+ 2. Create a new organization with a different slug
194
+
195
+ If the user chooses to use the existing org, skip creation and proceed with that org's slug. This is common when reconfiguring or when multiple people set up the same Showpane instance.
196
+
197
+ ### Step 7: Theme configuration
198
+
199
+ Ask the user for their primary brand color as a hex value. This color is used for active tab indicators, bullet point accents, and interactive elements throughout the portal. Provide suggestions:
200
+
201
+ - Default: `#1a1a1a` (near-black, professional — works well for most brands)
202
+ - Common choices: `#2563eb` (blue), `#059669` (green), `#dc2626` (red), `#7c3aed` (purple)
203
+ - Tip: if the user has a brand guidelines document or website, suggest pulling the primary color from there
204
+
205
+ Validate the input is a valid hex color:
206
+ - Accept with or without `#` prefix (add `#` if missing)
207
+ - Accept 3-digit shorthand (`#abc` expands to `#aabbcc`)
208
+ - Accept 6-digit full form (`#2563eb`)
209
+ - Reject anything else with a clear message: "Please enter a valid hex color like #2563eb"
210
+
211
+ Update the Organization record with `primaryColor` set to the validated hex value. The portal app reads this value and applies it via CSS custom properties (the `--primary` variable in the Tailwind theme).
212
+
213
+ If the user says "skip" or "default", use `#1a1a1a`. Brand color can always be changed later by running `/portal setup` again.
214
+
215
+ ### Step 8: Telemetry opt-in
216
+
217
+ Ask the user about anonymous usage telemetry:
218
+
219
+ > "Help Showpane improve! Share anonymous usage data (which skills you use, how long they take). No code, file paths, or portal content is ever sent."
220
+
221
+ Options:
222
+ - **community** — anonymous usage stats with a stable device ID for deduplication
223
+ - **anonymous** — anonymous usage stats with no device ID
224
+ - **off** — no telemetry (default)
225
+
226
+ Store the choice in config.
227
+
228
+ ### Step 9: Save configuration
229
+
230
+ Create the `~/.showpane/` directory if it doesn't exist:
231
+
232
+ ```bash
233
+ mkdir -p "$HOME/.showpane"
234
+ ```
235
+
236
+ Write the config file:
237
+
238
+ ```bash
239
+ cat > "$HOME/.showpane/config.json" << 'CONFIGEOF'
240
+ {
241
+ "app_path": "<resolved_absolute_path>",
242
+ "deploy_mode": "<docker|vercel>",
243
+ "orgSlug": "<org_slug>",
244
+ "telemetry": "<community|anonymous|off>"
245
+ }
246
+ CONFIGEOF
247
+ chmod 600 "$HOME/.showpane/config.json"
248
+ ```
249
+
250
+ Replace the placeholder values with the actual values collected. Use `chmod 600` so only the owner can read/write the config (it may contain sensitive paths).
251
+
252
+ ### Step 10: Print success summary
253
+
254
+ Display a clear summary:
255
+
256
+ ```
257
+ Showpane setup complete!
258
+
259
+ App path: /path/to/showpane-project
260
+ Deploy mode: docker
261
+ Organization: Acme Consulting (acme-consulting)
262
+ Brand color: #2563eb
263
+ Telemetry: off
264
+
265
+ Next steps:
266
+ 1. Start the dev server: /portal dev
267
+ 2. Create your first portal: /portal create <slug>
268
+ 3. View the example portal: open http://localhost:3000/client/example
269
+ ```
270
+
271
+ ### Step 11: Record learning (optional)
272
+
273
+ If this is the first setup, create an initial learning entry:
274
+
275
+ ```bash
276
+ mkdir -p "$HOME/.showpane"
277
+ echo '{"skill":"portal-setup","key":"initial-setup","insight":"Setup completed. Deploy mode: <mode>. Org: <name>.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
278
+ ```
279
+
280
+ ## Error Handling
281
+
282
+ Each step can fail independently. Handle failures gracefully:
283
+
284
+ - **App path not found**: Ask the user to run `npx showpane` first or point setup at an existing generated Showpane project
285
+ - **npm install fails**: Check Node.js version (requires 18+), check internet connectivity, suggest clearing `node_modules` and retrying
286
+ - **Prisma migrate fails**: Check DATABASE_URL is correct and the database server is running. For local dev, suggest `docker compose up -d db` if a database service exists in docker-compose.yml
287
+ - **Organization creation fails**: Check database connectivity. If the Prisma client throws a connection error, verify DATABASE_URL in `.env`
288
+ - **Config write fails**: Check that `$HOME/.showpane/` is writable. On some systems, home directory permissions may block directory creation
289
+
290
+ Never silently continue past a failure. Each step depends on the previous step succeeding.
291
+
292
+ ## Completion
293
+
294
+ As a final step, log skill completion:
295
+
296
+ ```bash
297
+ echo '{"skill":"portal-setup","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
298
+ ```
299
+
300
+ ## Conventions
301
+
302
+ - Never store DATABASE_URL or AUTH_SECRET in config.json — these live in the app's `.env` file
303
+ - Config file permissions must be 600 (owner read/write only)
304
+ - The orgSlug in config.json determines which Organization record is used by all other skills
305
+ - All user-facing output should be concise and scannable — use indented key-value pairs, not paragraphs
306
+ - If any step fails, provide a clear error message and suggest how to fix it, but do not silently continue
307
+ - The setup wizard is interactive — ask one question at a time, don't dump all questions at once
308
+ - When reconfiguring, show current values as defaults so the user can press enter to keep them
309
+ - The setup wizard should complete in under 5 minutes for a user with all prerequisites (Node.js, Postgres, the repo cloned)
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: portal-share
3
+ description: |
4
+ Generate a reusable secure share link for a client portal. Use when asked to "share portal",
5
+ "send link", "generate share link", "share URL", or "create access link". (showpane)
6
+ allowed-tools: [Bash, Read]
7
+ ---
8
+
9
+ If the user asks for broader share-link capabilities than Showpane supports, read
10
+ `skills/shared/platform-constraints.md` and apply the relevant limits.
11
+
12
+ ## Preamble (run first)
13
+
14
+ Before doing anything else, execute this block in a Bash tool call:
15
+
16
+ ```bash
17
+ CONFIG="$HOME/.showpane/config.json"
18
+ if [ ! -f "$CONFIG" ]; then
19
+ echo "Showpane not configured. Run /portal setup first."
20
+ exit 1
21
+ fi
22
+ APP_PATH=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('app_path',''))" 2>/dev/null)
23
+ DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','docker'))" 2>/dev/null)
24
+ 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)
25
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
+ DATABASE_URL="${DATABASE_URL:-}"
28
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
29
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
30
+ exit 1
31
+ fi
32
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
33
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
34
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
35
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
36
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
37
+
38
+ # Predictive next-skill suggestion
39
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
40
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
41
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
42
+ fi
43
+
44
+ # Search relevant learnings
45
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
46
+ if [ -f "$LEARN_FILE" ]; then
47
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
48
+ echo "LEARNINGS: $_LEARN_COUNT entries"
49
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
50
+ echo "RECENT_LEARNINGS:"
51
+ tail -5 "$LEARN_FILE" 2>/dev/null
52
+ fi
53
+ fi
54
+
55
+ # Track skill execution
56
+ SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
57
+ mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
58
+ echo '{"skill":"portal-share","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
59
+ ```
60
+
61
+ If RECENT_SKILLS is shown, suggest the likely next skill:
62
+ - After portal-create → suggest /portal-preview
63
+ - After portal-preview → suggest /portal-deploy or /portal-share
64
+ - After portal-deploy → suggest /portal-status or /portal-verify
65
+ - After portal-setup → suggest /portal-create
66
+ - After portal-credentials → suggest /portal-share
67
+ - After portal-update → suggest /portal-deploy
68
+
69
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
70
+ relevant warnings or tips for this operation. Apply them where relevant but don't
71
+ mention them unless they directly affect the current task.
72
+
73
+ ## Overview
74
+
75
+ This skill generates a signed share link that allows a client to access their portal without entering a username and password. The link is useful for initial onboarding, sending to a client after a meeting, or providing access to someone who does not have the portal credentials.
76
+
77
+ Share links use HMAC-SHA256 signed tokens. The token encodes the portal slug, a share scope, and the current credential version. Share links do not expire automatically. If credentials are rotated after the link is generated, the link becomes invalid automatically -- this is by design.
78
+
79
+ ## Steps
80
+
81
+ ### Step 1: Identify the target portal
82
+
83
+ The user must specify which portal to share. If no slug is provided, ask: "Which portal do you want to share? Run /portal list to see your portals."
84
+
85
+ Do not proceed without a slug. Share links are portal-specific.
86
+
87
+ ### Step 2: Generate the share link
88
+
89
+ Run the share link generator:
90
+
91
+ ```bash
92
+ cd $APP_PATH && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig $APP_PATH/tsconfig.json $SKILL_DIR/bin/generate-share-link.ts --slug <slug> --org-id <org_id>
93
+ ```
94
+
95
+ The script reads AUTH_SECRET from the app's `.env` file (sourced by the preamble) and uses the `signShareToken` function from the app's `client-auth.ts` module. It constructs a full URL using NEXT_PUBLIC_APP_URL (also from `.env`).
96
+
97
+ Expected success response:
98
+
99
+ ```json
100
+ {
101
+ "ok": true,
102
+ "shareUrl": "https://portal.example.com/client/whzan/s/eyJ...",
103
+ "expiresIn": "never"
104
+ }
105
+ ```
106
+
107
+ Expected error responses:
108
+
109
+ - `portal_not_found`: The slug does not exist. Suggest `/portal list`.
110
+ - `no_credentials`: The portal has no credentials set up. Share links require credentials because the token's validity is tied to the credential version. Suggest running `/portal credentials <slug>` first.
111
+ - `no_auth_secret`: AUTH_SECRET is not set in the app's `.env`. The user needs to add one. Suggest: `openssl rand -base64 32` to generate a secret.
112
+ - `no_app_url`: NEXT_PUBLIC_APP_URL is not set. The script cannot construct the full share URL. Ask the user what their portal's public URL is.
113
+
114
+ ### Step 3: Display the share link
115
+
116
+ Present the link in a clear, copy-friendly ASCII box:
117
+
118
+ ```
119
+ ════════════════════════════════════════
120
+ Share link for: whzan
121
+ https://portal.example.com/client/whzan/s/eyJ...
122
+ Expires: never (until credentials rotate)
123
+ ════════════════════════════════════════
124
+ ```
125
+
126
+ ### Step 4: Provide usage guidance
127
+
128
+ After displaying the link, add this note:
129
+
130
+ "Send this link to the client. They can access the portal without logging in. The link stays valid until the portal credentials are rotated or the portal is deactivated."
131
+
132
+ If the user has previously generated share links (check learnings for patterns), you can skip the explanation and just show the link.
133
+
134
+ ## Security Considerations
135
+
136
+ Share links are a convenience feature with intentional security trade-offs:
137
+
138
+ - **No automatic expiry**: Links remain valid until credentials are rotated, the portal is deactivated, or AUTH_SECRET changes.
139
+ - **Credential version binding**: Rotating credentials invalidates all outstanding share links. This is the revocation mechanism -- if a link is compromised, rotate credentials with `/portal credentials <slug>`.
140
+ - **Single portal scope**: Each link grants access to exactly one portal. A share link for "whzan" cannot be used to access "acme".
141
+ - **No re-use tracking**: The link can be used multiple times by anyone who has it. There is no per-user tracking on share links.
142
+
143
+ Do NOT log the share URL to learnings or telemetry. The URL contains the signed token which is an access credential. Print it to stdout only.
144
+
145
+ ## Conventions
146
+
147
+ - Always display the full URL, never truncate or abbreviate it. The user needs to copy-paste it.
148
+ - Make it clear that the link does not expire automatically and is revoked by credential rotation or portal deactivation.
149
+ - Use double-line box drawing (`═`) for the border around the link.
150
+ - If NEXT_PUBLIC_APP_URL is `http://localhost:3000`, warn the user: "This is a local development URL. The client won't be able to access it remotely. Deploy the app first with /portal deploy, then generate the share link."
151
+ - If the deploy mode is `docker` and the URL looks like localhost, same warning applies.
152
+
153
+ ## Error Handling
154
+
155
+ - If AUTH_SECRET is missing, this is a hard blocker. Explain that share links require a signing secret and provide the generation command: `openssl rand -base64 32`.
156
+ - If the portal has no credentials, explain that share links are tied to credential versions and the user needs to set up credentials first.
157
+ - If the script fails for any other reason, show the error message from stderr and suggest the user check their configuration with `/portal status`.
158
+
159
+ ## Token Anatomy
160
+
161
+ For troubleshooting and understanding, here is what a share token contains. You do not need to decode tokens manually, but knowing the structure helps when debugging access issues.
162
+
163
+ The token is a base64url-encoded JSON payload with an HMAC-SHA256 signature appended:
164
+
165
+ ```json
166
+ {
167
+ "v": 1,
168
+ "slug": "whzan",
169
+ "scope": "share",
170
+ "exp": 1712588400,
171
+ "ver": 3,
172
+ "jti": "random-unique-id"
173
+ }
174
+ ```
175
+
176
+ Fields:
177
+ - `v`: Token format version. Always 1 for now.
178
+ - `slug`: The portal this token grants access to.
179
+ - `scope`: Always "share" for share links. Distinguishes from session tokens.
180
+ - `exp`: `null` for non-expiring share links.
181
+ - `ver`: The credential version at time of signing. If credentials are rotated (bumping the version), this token becomes invalid even before expiry.
182
+ - `jti`: A random unique identifier for this specific token. Not currently used for revocation but available for future per-token revocation.
183
+
184
+ The signature is computed using AUTH_SECRET from the app's `.env`. If AUTH_SECRET changes (e.g., during a key rotation), all outstanding share links are invalidated.
185
+
186
+ ## Workflow Patterns
187
+
188
+ Common patterns for using share links in practice:
189
+
190
+ **After a meeting**: Create the portal with `/portal create`, set up credentials with `/portal credentials`, then immediately generate a share link. Send the link in the follow-up email. The client can access the portal instantly without needing to remember a password.
191
+
192
+ **For quick reviews**: If a colleague or stakeholder needs to see the portal but should not have permanent credentials, a share link is ideal. It can be reused and does not create a full operator login.
193
+
194
+ **Re-sharing after content update**: If you update portal content with `/portal update` and want the client to see the changes, generate a fresh share link and send it. This is often easier than asking them to log in again.
195
+
196
+ ## Learnings Integration
197
+
198
+ After generating a share link, consider recording a learning entry if this is the first time sharing this portal. The learning helps other skills (like `/portal analytics`) provide better context:
199
+
200
+ ```json
201
+ {"skill":"portal-share","key":"share-event","insight":"whzan shared via link on 2026-04-07","confidence":10,"ts":"2026-04-07T14:30:00Z"}
202
+ ```
203
+
204
+ Do not record the URL itself in learnings. Only record the fact that a share event occurred and when.
205
+
206
+ ## Expiry Duration
207
+
208
+ Share links do not expire automatically in this version.
209
+
210
+ The revocation mechanisms are:
211
+
212
+ - rotate credentials with `/portal credentials <slug>`
213
+ - deactivate the portal with `/portal delete <slug>`
214
+ - rotate `AUTH_SECRET`
215
+
216
+ ## Multiple Share Links
217
+
218
+ Generating a new share link does not invalidate the previous one. Both links remain valid until credentials are rotated, the portal is deactivated, or AUTH_SECRET changes. This means the user can safely generate multiple links for the same portal (e.g., one for the client contact, one for their colleague) without affecting each other.
219
+
220
+ If the user wants to revoke all outstanding share links immediately, the mechanism is credential rotation: `/portal credentials <slug>`. This bumps the credential version, which invalidates all tokens signed against the previous version.
221
+
222
+ ## Completion
223
+
224
+ As a final step, log skill completion:
225
+
226
+ ```bash
227
+ echo '{"skill":"portal-share","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
228
+ ```
229
+
230
+ ## Related Skills
231
+
232
+ - `/portal credentials` -- set up or rotate credentials (required before sharing)
233
+ - `/portal preview` -- open the portal locally to verify content before sharing
234
+ - `/portal analytics` -- check engagement after sharing to see if the client accessed the portal