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,277 @@
1
+ ---
2
+ name: portal-onboard
3
+ description: |
4
+ Guided first-run experience that chains setup, create, credentials, and preview.
5
+ Use when asked to "get started", "onboard", "first time setup", "walk me through",
6
+ or "set up showpane from scratch". (showpane)
7
+ allowed-tools: [Bash, Read, Write, Edit, Glob, Grep]
8
+ ---
9
+
10
+ ## Preamble (run first)
11
+
12
+ Before doing anything else, execute this block in a Bash tool call:
13
+
14
+ ```bash
15
+ CONFIG="$HOME/.showpane/config.json"
16
+ if [ ! -f "$CONFIG" ]; then
17
+ echo "Showpane not configured. Run /portal setup first."
18
+ exit 1
19
+ fi
20
+ APP_PATH=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('app_path',''))" 2>/dev/null)
21
+ DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','docker'))" 2>/dev/null)
22
+ 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)
23
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
24
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
25
+ DATABASE_URL="${DATABASE_URL:-}"
26
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
27
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
28
+ exit 1
29
+ fi
30
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
31
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
32
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
33
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
34
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
35
+
36
+ # Predictive next-skill suggestion
37
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
38
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
39
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
40
+ fi
41
+
42
+ # Search relevant learnings
43
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
44
+ if [ -f "$LEARN_FILE" ]; then
45
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
46
+ echo "LEARNINGS: $_LEARN_COUNT entries"
47
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
48
+ echo "RECENT_LEARNINGS:"
49
+ tail -5 "$LEARN_FILE" 2>/dev/null
50
+ fi
51
+ fi
52
+
53
+ # Track skill execution
54
+ SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
55
+ mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
56
+ echo '{"skill":"portal-onboard","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
57
+ ```
58
+
59
+ If RECENT_SKILLS is shown, suggest the likely next skill:
60
+ - After portal-create → suggest /portal-preview
61
+ - After portal-preview → suggest /portal-deploy or /portal-share
62
+ - After portal-deploy → suggest /portal-status or /portal-verify
63
+ - After portal-setup → suggest /portal-create
64
+ - After portal-credentials → suggest /portal-share
65
+ - After portal-update → suggest /portal-deploy
66
+
67
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
68
+ relevant warnings or tips for this operation. Apply them where relevant but don't
69
+ mention them unless they directly affect the current task.
70
+
71
+ ## Overview
72
+
73
+ This skill is the guided first-run experience for Showpane. It chains four other skills together in sequence -- setup, create, credentials, and preview -- walking the user through each step with clear transitions. The goal is to go from zero to a working portal in the browser in under 5 minutes.
74
+
75
+ This is specifically designed for users who have just installed Showpane and want to see it working end-to-end. It is opinionated about the order of operations and provides more context and encouragement than the individual skills would on their own.
76
+
77
+ The onboard flow is linear. Each step depends on the previous one completing successfully. If any step fails, the flow stops and provides recovery instructions rather than skipping ahead.
78
+
79
+ ## Steps
80
+
81
+ ### Step 1: Welcome message
82
+
83
+ Start with a welcome that sets expectations. First, display the ASCII welcome banner:
84
+
85
+ ```
86
+ ╔══════════════════════════════════════════╗
87
+ ║ SHOWPANE — Client Portal Generator ║
88
+ ╚══════════════════════════════════════════╝
89
+ ```
90
+
91
+ Then continue with:
92
+
93
+ "Welcome to Showpane! Let's set up your first client portal. This will take about 5 minutes and we'll go through four steps:
94
+
95
+ 1. Configure Showpane (app path, database, organization)
96
+ 2. Create your first portal
97
+ 3. Set up login credentials
98
+ 4. Open it in your browser
99
+
100
+ Let's get started."
101
+
102
+ This framing is important. It tells the user what to expect and roughly how long it will take. Uncertainty causes drop-off.
103
+
104
+ ### Step 2: Run the setup flow
105
+
106
+ Read the instructions from `skills/portal-setup/SKILL.md` and execute the setup flow inline. This means you follow the setup skill's instructions as if the user had run `/portal setup` directly.
107
+
108
+ The setup flow will:
109
+ - Detect the app path (or ask the user)
110
+ - Verify the database connection
111
+ - Create or select an organization
112
+ - Write the config to `~/.showpane/config.json`
113
+ - Set file permissions
114
+
115
+ During setup, also ask for the company's website URL (e.g., "acme.com"). This is used for auto-branding:
116
+ - Fetch the company logo via `getLogoUrl(domain)` from `app/src/lib/branding.ts`
117
+ - The logo URL will be stored in the Organization record's `logoUrl` field
118
+ - If the user doesn't have a website, skip — the initial-based fallback works fine
119
+
120
+ Also ask for the contact's email address and use `getAvatarUrl(email, contactName)` to auto-populate the contact avatar.
121
+
122
+ **Important**: If setup detects that Showpane is already configured (config file exists, database is connected, org exists), acknowledge it and skip to the next step: "Showpane is already configured. Skipping setup."
123
+
124
+ If setup fails at any point (database not reachable, app not found), stop the onboard flow and provide clear instructions: "Setup needs to be completed before we can continue. Fix the issue above and run /portal onboard again."
125
+
126
+ After successful setup, transition:
127
+
128
+ "Setup complete! Your Showpane instance is configured. Let's create your first portal."
129
+
130
+ ### Granola MCP Integration
131
+
132
+ After setup, check if Granola MCP tools are available by attempting to call `list_meetings`.
133
+
134
+ If available:
135
+ - Show: "I found your Granola meetings. Want to use a recent call as the source for your first portal?"
136
+ - List recent meetings with date + title
137
+ - If selected, use the transcript to pre-populate portal content
138
+
139
+ If not available:
140
+ - Skip silently. Do not mention Granola or show an error.
141
+
142
+ ### Step 3: Run the create flow
143
+
144
+ Read the instructions from `skills/portal-create/SKILL.md` and execute the create flow inline. This creates the portal page files and database record.
145
+
146
+ During onboarding, provide a bit more guidance than the standalone create skill would:
147
+
148
+ - Suggest a slug if the user is unsure: "Pick a short name for the portal URL, like your client's company name in lowercase. For example: 'acme' or 'whzan'."
149
+ - If the user has Granola MCP connected, offer to pull a recent meeting as the source material. If not, prompt for a brief description of the client and what the portal should contain.
150
+ - Use the sales-followup template as the default suggestion for first-time users: "We'll use the sales follow-up template as a starting point. You can customize everything after."
151
+
152
+ After successful creation, transition:
153
+
154
+ "Great! Portal '<slug>' created for <company>. Now let's set up credentials so your client can log in."
155
+
156
+ If creation fails (e.g., slug already taken), help the user pick a different slug and retry. Do not abort the entire onboard flow for a recoverable error.
157
+
158
+ ### Step 4: Run the credentials flow
159
+
160
+ Read the instructions from `skills/portal-credentials/SKILL.md` and execute the credentials flow inline. This creates a username and password for the portal.
161
+
162
+ During onboarding, the credentials flow is straightforward -- generate initial credentials. There is no rotation to handle on first run.
163
+
164
+ After credentials are generated, display them clearly:
165
+
166
+ "Credentials created. Save these -- they are shown once:
167
+
168
+ Username: <username>
169
+ Password: <password>
170
+
171
+ You'll share these with your client (or use /portal share to generate a link instead)."
172
+
173
+ Transition to the final step:
174
+
175
+ "Almost done! Let's open your portal in the browser."
176
+
177
+ ### Step 5: Run the preview flow
178
+
179
+ Read the instructions from `skills/portal-preview/SKILL.md` and execute the preview flow inline. This opens the portal in the user's default browser.
180
+
181
+ During onboarding, add a note about what the user will see:
182
+
183
+ "Opening your portal now. You'll see a login page. Use the credentials above to log in, or just review the page to make sure everything looks right."
184
+
185
+ If no dev server is running, start one: "The dev server isn't running. Let me start it for you." Then run the dev server startup (from `/portal dev` instructions) and wait for it to be ready before opening the browser.
186
+
187
+ After the browser opens, transition to the final summary.
188
+
189
+ ### Step 6: Final summary
190
+
191
+ Display the completion summary in an ASCII box:
192
+
193
+ ```
194
+ ════════════════════════════════════════
195
+ Showpane is ready!
196
+
197
+ Portal: <slug> (<company>)
198
+ URL: http://localhost:3000/client/<slug>
199
+ Username: <username>
200
+
201
+ Next: /portal deploy to go live
202
+ Help: /portal list, /portal status
203
+ ════════════════════════════════════════
204
+ ```
205
+
206
+ Note: Do NOT include the password in the final summary. It was shown once during Step 4. The summary is a reference card the user might screenshot or scroll back to, so it should not contain the password.
207
+
208
+ After the summary, provide a brief "what's next" orientation:
209
+
210
+ "Your portal is live locally. Here's what you can do next:
211
+
212
+ - `/portal deploy` -- push to production so your client can access it
213
+ - `/portal share <slug>` -- generate a share link (no login needed)
214
+ - `/portal update <slug>` -- edit the portal content
215
+ - `/portal analytics <slug>` -- track client engagement
216
+ - `/portal status` -- see a dashboard of all your portals"
217
+
218
+ ## Flow Control
219
+
220
+ This skill is a sequential orchestrator. Each step depends on the previous one:
221
+
222
+ ```
223
+ setup -> create -> credentials -> preview -> summary
224
+ ```
225
+
226
+ Rules:
227
+ - **Never skip a step.** Even if the user says "I already have credentials", verify by checking the database. If credentials exist, acknowledge and move on. But do not skip the check.
228
+ - **Stop on hard failures.** If setup cannot connect to the database, or create fails due to a missing dependency, stop and explain. Do not try to continue with a broken foundation.
229
+ - **Retry on soft failures.** If the user picks a slug that is taken, help them pick another. If the dev server takes a moment to start, wait. These are not reasons to abort.
230
+ - **Respect existing state.** If the user already has a configured Showpane, an existing portal, and credentials, acknowledge each one and skip to the next incomplete step. The onboard flow should work for both brand-new and partially-set-up installations.
231
+
232
+ ## Detecting Existing State
233
+
234
+ Before each step, check whether it has already been completed:
235
+
236
+ 1. **Setup**: Check if `~/.showpane/config.json` exists and is valid. If yes, skip setup.
237
+ 2. **Create**: Check if the org has at least one portal (via `list-portals.ts`). If yes, ask: "You already have portals. Want to create another, or skip to credentials?"
238
+ 3. **Credentials**: Check if the selected portal has credentials (from list response `hasCredentials` field). If yes, skip credentials.
239
+ 4. **Preview**: Always run. Opening the browser is always useful.
240
+
241
+ ## Conventions
242
+
243
+ - Use encouraging but not patronizing language. "Great!" and "Let's move on" are fine. "Wow, amazing job!" is too much.
244
+ - Keep transitions between steps to one sentence. Do not recap what just happened -- the user can see it.
245
+ - Use double-line box drawing (`═`) for the final summary box only. Keep step transitions as plain text.
246
+ - If the total onboard time exceeds 10 minutes (e.g., due to long create flow with Granola), do not comment on it. The user is working, not racing.
247
+ - If learnings exist from a previous session, load them silently. The onboard flow benefits from prior context without mentioning it.
248
+
249
+ ## Error Handling
250
+
251
+ - If the preamble itself fails (no config), this is expected for first-run. In this specific case, do NOT abort. Instead, proceed directly to the setup step: "Looks like Showpane isn't configured yet. Let's fix that."
252
+ - If any bin/ script returns a non-zero exit, read the error from stderr, display it, and suggest a fix.
253
+ - If the user wants to bail out mid-flow, respect it: "No problem. You can pick up where you left off by running the individual skills: /portal setup, /portal create, /portal credentials, /portal preview."
254
+
255
+ ## Special Case: Preamble Failure
256
+
257
+ Unlike other skills, `/portal onboard` should NOT hard-fail if the preamble reports "Showpane not configured." The entire point of this skill is to handle first-run. If the config file does not exist, catch the preamble error and proceed to Step 2 (setup) which will create the config.
258
+
259
+ To handle this: Run the preamble, capture the output. If it includes "not configured", note that setup is needed and continue. Do not exit.
260
+
261
+ ## Completion
262
+
263
+ As a final step, log skill completion:
264
+
265
+ ```bash
266
+ echo '{"skill":"portal-onboard","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
267
+ ```
268
+
269
+ ## Related Skills
270
+
271
+ This skill chains the following skills inline (read their SKILL.md files for detailed instructions):
272
+ - `/portal setup` -- Step 2
273
+ - `/portal create` -- Step 3
274
+ - `/portal credentials` -- Step 4
275
+ - `/portal preview` -- Step 5
276
+ - `/portal dev` -- called within Step 5 if needed
277
+ - `/portal deploy` -- suggested as next step in the summary
@@ -0,0 +1,257 @@
1
+ ---
2
+ name: portal-preview
3
+ description: |
4
+ Open a portal in the browser for preview. Use when asked to "preview portal",
5
+ "open portal", "view portal", "show me the portal", or "open in browser". (showpane)
6
+ allowed-tools: [Bash, Read]
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-preview","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 opens a portal in the user's default web browser. It is the fastest way to see what a portal looks like after creating or updating it. The skill determines the correct URL based on the deployment mode and whether a local dev server is running, then opens it using the platform's native command.
73
+
74
+ This is a lightweight skill -- it does not start a dev server, build the app, or modify anything. It just opens a URL. If the dev server is not running, it tells the user how to start it.
75
+
76
+ ## Steps
77
+
78
+ ### Step 1: Identify the target portal
79
+
80
+ If the user specified a slug (e.g., "preview whzan"), use that slug directly.
81
+
82
+ If no slug is provided, check how many portals exist:
83
+ - If there is exactly one portal, use that one and mention it: "Opening your only portal: <slug>"
84
+ - If there are multiple portals, ask: "Which portal do you want to preview? Run /portal list to see your portals."
85
+ - If there are zero portals, say: "No portals found. Create one first with /portal create."
86
+
87
+ For single-portal organizations, auto-selecting saves a round trip.
88
+
89
+ ### Step 2: Determine the correct URL
90
+
91
+ The URL depends on the environment. Check in this order of priority:
92
+
93
+ **Option A: Local dev server running (check port 3000)**
94
+
95
+ ```bash
96
+ lsof -i :3000 -sTCP:LISTEN -t >/dev/null 2>&1 && echo "DEV_RUNNING=true" || echo "DEV_RUNNING=false"
97
+ ```
98
+
99
+ If a process is listening on port 3000, the URL is:
100
+ ```
101
+ http://localhost:3000/client/<slug>
102
+ ```
103
+
104
+ **Option B: Docker deploy mode (check port 8080)**
105
+
106
+ If `DEPLOY_MODE` is `docker`, check if the container is running:
107
+
108
+ ```bash
109
+ lsof -i :8080 -sTCP:LISTEN -t >/dev/null 2>&1 && echo "DOCKER_RUNNING=true" || echo "DOCKER_RUNNING=false"
110
+ ```
111
+
112
+ If running, the URL is:
113
+ ```
114
+ http://localhost:8080/client/<slug>
115
+ ```
116
+
117
+ **Option C: NEXT_PUBLIC_APP_URL is set**
118
+
119
+ If the environment variable `NEXT_PUBLIC_APP_URL` is set (sourced from `.env` by the preamble), use it:
120
+ ```
121
+ ${NEXT_PUBLIC_APP_URL}/client/<slug>
122
+ ```
123
+
124
+ This handles production/staging URLs, custom domains, and any other deployment target.
125
+
126
+ **Fallback: Nothing running**
127
+
128
+ If no server is detected and no app URL is configured, do not open the browser. Instead, inform the user:
129
+
130
+ "No running server detected. Start the dev server with /portal dev, or set NEXT_PUBLIC_APP_URL in your .env file."
131
+
132
+ ### Step 3: Open the URL in the browser
133
+
134
+ Use the platform-appropriate command:
135
+
136
+ ```bash
137
+ # macOS
138
+ open "<url>"
139
+
140
+ # Linux
141
+ xdg-open "<url>"
142
+ ```
143
+
144
+ Detect the platform:
145
+
146
+ ```bash
147
+ if [[ "$OSTYPE" == "darwin"* ]]; then
148
+ open "<url>"
149
+ elif command -v xdg-open >/dev/null 2>&1; then
150
+ xdg-open "<url>"
151
+ else
152
+ echo "Cannot detect browser opener. Visit: <url>"
153
+ fi
154
+ ```
155
+
156
+ Run this in a single Bash tool call so the browser opens immediately.
157
+
158
+ ### Step 4: Print confirmation
159
+
160
+ After opening, display:
161
+
162
+ ```
163
+ Opened portal for <slug> in browser
164
+ URL: http://localhost:3000/client/<slug>
165
+ ```
166
+
167
+ If the portal has credentials set up, remind the user:
168
+ "Login with the credentials from /portal credentials <slug>. Or generate a share link with /portal share <slug> to bypass login."
169
+
170
+ If the portal is the example portal (slug is "example"), no credentials are needed -- it is publicly accessible by design.
171
+
172
+ ## URL Priority Order
173
+
174
+ To be explicit about resolution order:
175
+
176
+ 1. **Dev server on port 3000** -- always preferred for local development, regardless of other settings.
177
+ 2. **Docker on port 8080** -- used when deploy_mode is docker and the container is running.
178
+ 3. **NEXT_PUBLIC_APP_URL** -- used for production or staging environments, or when no local server is detected.
179
+ 4. **Fallback** -- no URL available, prompt the user to start a server.
180
+
181
+ This order ensures that during development, the user always sees the latest local version, even if NEXT_PUBLIC_APP_URL points to production.
182
+
183
+ ## Conventions
184
+
185
+ - Always check for a running server before attempting to open. Opening a URL with no server just shows a browser error page -- a poor experience.
186
+ - Print the full URL so the user can copy-paste it if needed (e.g., to share with a colleague looking at the same screen).
187
+ - Do not open multiple browser tabs. One call to `open` per invocation.
188
+ - If the slug is "example", mention that this is the built-in example portal and does not require authentication.
189
+ - If learnings indicate the user prefers a specific browser or has a custom port, adapt the URL accordingly.
190
+
191
+ ## Edge Cases
192
+
193
+ - **Port conflict**: If port 3000 has a non-Showpane process running, `lsof` will still detect it and the URL will be wrong. This is unlikely in practice but worth noting. The user will see a different app in the browser and can correct by specifying the URL manually.
194
+ - **WSL (Windows Subsystem for Linux)**: `xdg-open` may not work. On WSL, use `wslview` or `explorer.exe` instead. Detect WSL via `grep -qi microsoft /proc/version`.
195
+ - **SSH/remote session**: If the user is connected via SSH, opening a browser on the remote machine does nothing useful. Detect this via the `SSH_CONNECTION` environment variable and print the URL instead: "You appear to be in an SSH session. Visit this URL on your local machine: <url>"
196
+ - **Inactive portal**: If the portal is deactivated (`isActive: false`), the preview will show a "not found" page. Warn the user: "Portal '<slug>' is inactive. You'll see a not-found page. Reactivate it first or use /portal list to check status."
197
+
198
+ ## Error Handling
199
+
200
+ - If the preamble fails, stop and display the error.
201
+ - If no server is running and no URL is configured, provide clear instructions rather than opening a dead URL.
202
+ - If the `open` or `xdg-open` command fails, print the URL as a fallback: "Could not open browser automatically. Visit: <url>"
203
+
204
+ ## Login Context
205
+
206
+ When the portal opens in the browser, the client will see a login page (unless they have an active session or use a share link). Provide context about how to access the portal:
207
+
208
+ - **Has credentials**: Remind the user of the username (derived from the slug). Do not display the password -- they can get it from `/portal credentials <slug>` if they need it.
209
+ - **No credentials**: Warn that the portal will show a login page but there are no credentials to enter. Suggest running `/portal credentials <slug>` first.
210
+ - **Example portal**: The built-in example at `/client/example` is publicly accessible and does not require login. No credentials needed.
211
+
212
+ If the user is previewing to check content before sharing with a client, suggest: "To see exactly what the client will see, open an incognito/private window. Your existing session cookies may affect the view."
213
+
214
+ ## Previewing After Changes
215
+
216
+ A common workflow is: edit portal content with `/portal update`, then preview to verify. If the dev server is running with Next.js hot reload, changes to the client component files will appear immediately without a page refresh. If the user is running a production build (docker mode), they may need to rebuild first.
217
+
218
+ If the user just ran `/portal update` and then `/portal preview`, mention: "If you don't see your changes, make sure the dev server is running (hot reload) or rebuild with /portal deploy for docker mode."
219
+
220
+ ## Multiple Portals Preview
221
+
222
+ If the user asks to "preview all portals" or "open all my portals", open each one in a separate browser tab. However, limit to 5 tabs maximum to avoid browser overload. If there are more than 5 portals, open the 5 most recently updated and note: "Opened the 5 most recently updated portals. Use /portal preview <slug> for specific portals."
223
+
224
+ To open multiple tabs, run the open command for each URL sequentially:
225
+
226
+ ```bash
227
+ open "http://localhost:3000/client/whzan"
228
+ open "http://localhost:3000/client/acme"
229
+ open "http://localhost:3000/client/example"
230
+ ```
231
+
232
+ Each `open` call creates a new tab in the default browser.
233
+
234
+ ## Telemetry
235
+
236
+ If telemetry is enabled, the preview skill records a minimal event:
237
+
238
+ ```json
239
+ {"skill":"portal-preview","ts":"2026-04-07T12:00:00Z","duration_s":1,"outcome":"success"}
240
+ ```
241
+
242
+ No portal slug or URL is included in telemetry. The event only records that a preview was triggered.
243
+
244
+ ## Completion
245
+
246
+ As a final step, log skill completion:
247
+
248
+ ```bash
249
+ echo '{"skill":"portal-preview","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
250
+ ```
251
+
252
+ ## Related Skills
253
+
254
+ - `/portal dev` -- start the local development server
255
+ - `/portal share` -- generate a link that works for anyone, not just the local machine
256
+ - `/portal deploy` -- deploy to production so the portal is accessible externally
257
+ - `/portal credentials` -- get or rotate login credentials for the portal