showpane 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +22 -1
  2. package/bundle/meta/scaffold-manifest.json +73 -0
  3. package/bundle/scaffold/VERSION +1 -0
  4. package/bundle/scaffold/__dot__env.example +24 -0
  5. package/bundle/scaffold/__dot__gitignore +41 -0
  6. package/bundle/scaffold/docker/Caddyfile +3 -0
  7. package/bundle/scaffold/docker/Dockerfile +30 -0
  8. package/bundle/scaffold/docker-compose.yml +53 -0
  9. package/bundle/scaffold/next.config.ts +20 -0
  10. package/bundle/scaffold/package-lock.json +5843 -0
  11. package/bundle/scaffold/package.json +42 -0
  12. package/bundle/scaffold/postcss.config.js +6 -0
  13. package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
  14. package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
  15. package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
  16. package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
  17. package/bundle/scaffold/prisma/schema.local.prisma +131 -0
  18. package/bundle/scaffold/prisma/schema.prisma +128 -0
  19. package/bundle/scaffold/prisma/seed.ts +49 -0
  20. package/bundle/scaffold/public/example-avatar.svg +4 -0
  21. package/bundle/scaffold/public/example-logo.svg +4 -0
  22. package/bundle/scaffold/public/robots.txt +2 -0
  23. package/bundle/scaffold/scripts/backup.sh +19 -0
  24. package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
  25. package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
  26. package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
  27. package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
  28. package/bundle/scaffold/scripts/restore.sh +31 -0
  29. package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
  30. package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
  31. package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
  32. package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
  33. package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
  34. package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
  35. package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
  36. package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
  37. package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
  38. package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
  39. package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
  40. package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
  41. package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
  42. package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
  43. package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
  44. package/bundle/scaffold/src/app/api/health/route.ts +19 -0
  45. package/bundle/scaffold/src/app/globals.css +7 -0
  46. package/bundle/scaffold/src/app/layout.tsx +25 -0
  47. package/bundle/scaffold/src/app/page.tsx +171 -0
  48. package/bundle/scaffold/src/components/portal-login.tsx +169 -0
  49. package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
  50. package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
  51. package/bundle/scaffold/src/lib/branding.ts +50 -0
  52. package/bundle/scaffold/src/lib/client-auth.ts +98 -0
  53. package/bundle/scaffold/src/lib/client-portals.ts +134 -0
  54. package/bundle/scaffold/src/lib/control-plane.ts +100 -0
  55. package/bundle/scaffold/src/lib/db.ts +7 -0
  56. package/bundle/scaffold/src/lib/files.ts +124 -0
  57. package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
  58. package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
  59. package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
  60. package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
  61. package/bundle/scaffold/src/lib/storage.ts +204 -0
  62. package/bundle/scaffold/src/lib/token.ts +186 -0
  63. package/bundle/scaffold/src/lib/utils.ts +6 -0
  64. package/bundle/scaffold/src/middleware.ts +61 -0
  65. package/bundle/scaffold/tailwind.config.ts +15 -0
  66. package/bundle/scaffold/tests/__dot__gitkeep +0 -0
  67. package/bundle/scaffold/tsconfig.json +23 -0
  68. package/bundle/scaffold/vitest.config.ts +13 -0
  69. package/bundle/toolchain/VERSION +1 -0
  70. package/bundle/toolchain/bin/check-slug.ts +59 -0
  71. package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
  72. package/bundle/toolchain/bin/create-portal.ts +71 -0
  73. package/bundle/toolchain/bin/delete-portal.ts +48 -0
  74. package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
  75. package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
  76. package/bundle/toolchain/bin/generate-share-link.ts +68 -0
  77. package/bundle/toolchain/bin/list-portals.ts +53 -0
  78. package/bundle/toolchain/bin/materialize-file.ts +35 -0
  79. package/bundle/toolchain/bin/query-analytics.ts +88 -0
  80. package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
  81. package/bundle/toolchain/bin/showpane-config +63 -0
  82. package/bundle/toolchain/bin/tsconfig.json +13 -0
  83. package/bundle/toolchain/skills/VERSION +1 -0
  84. package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
  85. package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
  86. package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
  87. package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
  88. package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
  89. package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
  90. package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
  91. package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
  92. package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
  93. package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
  94. package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
  95. package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
  96. package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
  97. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
  98. package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
  99. package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
  100. package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
  101. package/bundle/toolchain/skills/shared/preamble.md +137 -0
  102. package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
  103. package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
  104. package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
  105. package/dist/index.js +875 -159
  106. package/package.json +4 -2
@@ -0,0 +1,268 @@
1
+ ---
2
+ name: portal-status
3
+ description: |
4
+ Terminal dashboard showing all portals with health scores. Use when asked to "portal status",
5
+ "dashboard", "health check", "how are my portals", or "overview of all portals". (showpane)
6
+ allowed-tools: [Bash, Read, 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-status","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 produces a terminal-based status dashboard showing all portals with health scores, recent activity, and attention flags. It combines data from the portal list and analytics queries into a single at-a-glance view. Think of it as the "home screen" for a Showpane operator -- one command to understand the state of all client portals.
73
+
74
+ The health score is a composite 0-10 rating that weights recent activity, credential freshness, content recency, and file completeness. Portals scoring below 5 are flagged with suggestions for improvement.
75
+
76
+ ## Steps
77
+
78
+ ### Step 1: Fetch the portal list
79
+
80
+ Run the list script to get all active portals:
81
+
82
+ ```bash
83
+ cd $APP_PATH && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig $APP_PATH/tsconfig.json $SKILL_DIR/bin/list-portals.ts --org-id <org_id>
84
+ ```
85
+
86
+ This returns the list of portals with their metadata (slug, company name, status, last updated, credential info).
87
+
88
+ If the org has zero portals, display:
89
+
90
+ ```
91
+ SHOWPANE STATUS DASHBOARD
92
+ ════════════════════════════════════════════════════════════
93
+ No portals yet. Run /portal create to get started.
94
+ ════════════════════════════════════════════════════════════
95
+ ```
96
+
97
+ ### Step 2: Fetch analytics for each portal
98
+
99
+ For each portal in the list, query analytics:
100
+
101
+ ```bash
102
+ cd $APP_PATH && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig $APP_PATH/tsconfig.json $SKILL_DIR/bin/query-analytics.ts --slug <slug> --org-id <org_id>
103
+ ```
104
+
105
+ If you have many portals (5+), you can batch the queries. For a small number, sequential calls are fine. The script should return quickly for each portal.
106
+
107
+ Collect the following data points per portal:
108
+ - `views7d`: Total views in the last 7 days
109
+ - `lastActivity`: Timestamp of most recent event
110
+ - `credentialCreatedAt`: When credentials were first set up
111
+ - `lastUpdated`: When the portal content was last modified
112
+ - `hasFiles`: Whether the portal has at least one uploaded/attached file
113
+
114
+ ### Step 3: Calculate health scores
115
+
116
+ Score each portal on a 0-10 scale using four weighted dimensions:
117
+
118
+ #### Recent Activity (30% weight)
119
+ - Views in last 7 days > 0: score 10
120
+ - No views in last 7 days but views in last 30 days > 0: score 5
121
+ - No views in last 30 days: score 0
122
+
123
+ #### Credential Freshness (20% weight)
124
+ - Credentials created less than 30 days ago: score 10
125
+ - Credentials created less than 90 days ago: score 7
126
+ - Credentials older than 90 days: score 3
127
+ - No credentials at all: score 0
128
+
129
+ #### Content Recency (30% weight)
130
+ - `lastUpdated` within 30 days: score 10
131
+ - `lastUpdated` within 90 days: score 7
132
+ - `lastUpdated` older than 90 days: score 3
133
+
134
+ #### Has Files (20% weight)
135
+ - At least 1 file/document attached: score 10
136
+ - No files: score 5
137
+
138
+ **Composite score** = (activity * 0.3) + (credentials * 0.2) + (content * 0.3) + (files * 0.2)
139
+
140
+ Round to one decimal place.
141
+
142
+ ### Step 4: Display the dashboard
143
+
144
+ Format the output as a rich ASCII table:
145
+
146
+ ```
147
+ SHOWPANE STATUS DASHBOARD
148
+ ════════════════════════════════════════════════════════════
149
+ Portal Company Health Views(7d) Last Login
150
+ ───────── ──────────────────── ─────── ────────── ──────────
151
+ whzan Whzan Digital Health 8.5/10 12 2 days ago
152
+ acme Acme Corp 6.0/10 0 15 days ago
153
+ example Example Portal 3.0/10 0 Never
154
+ ════════════════════════════════════════════════════════════
155
+ 3 portals | Avg health: 5.8/10 | 1 needs attention
156
+ ```
157
+
158
+ Column definitions:
159
+
160
+ - **Portal**: The slug. Keep it short.
161
+ - **Company**: Client company name, truncated to 20 characters if needed.
162
+ - **Health**: Composite score out of 10, one decimal place.
163
+ - **Views(7d)**: View count in the last 7 days. "0" if none.
164
+ - **Last Login**: Human-readable relative time. "2 days ago", "15 days ago", "Never". Use "Never" if there are zero recorded events.
165
+
166
+ ### Step 5: Flag portals needing attention
167
+
168
+ Any portal with a health score below 5.0 gets flagged. After the table, list specific, actionable suggestions:
169
+
170
+ ```
171
+ NEEDS ATTENTION
172
+ ────────────────────────────────────────
173
+ example (3.0/10):
174
+ - No views in 30 days. Check if credentials were shared with the client.
175
+ - Content last updated 4 months ago. Consider refreshing.
176
+ - No files attached. Add documents to increase engagement.
177
+
178
+ acme (6.0/10):
179
+ - No views in 7 days. Consider a follow-up email.
180
+ ────────────────────────────────────────
181
+ ```
182
+
183
+ Guidelines for suggestions:
184
+
185
+ - **No views, has credentials**: "Check if credentials were shared with the client."
186
+ - **No views, no credentials**: "Set up credentials with /portal credentials <slug> and share them."
187
+ - **Stale content (> 90 days)**: "Content last updated X months ago. Consider refreshing."
188
+ - **No files**: "Add documents to increase engagement."
189
+ - **Declining activity (had views before, none now)**: "Activity has dropped off. Consider a follow-up."
190
+ - **Old credentials (> 90 days)**: "Credentials are X months old. Consider rotating with /portal credentials <slug>."
191
+
192
+ Limit to 3 suggestions per portal maximum. Prioritize by impact.
193
+
194
+ ### Step 6: Summarize
195
+
196
+ The footer line provides the aggregate view:
197
+ - Total portals count
198
+ - Average health score across all portals
199
+ - Count of portals needing attention (health < 5.0)
200
+
201
+ If all portals are healthy (score >= 7.0), add a positive note: "All portals healthy. Nice work."
202
+
203
+ If average health is below 5.0, flag it: "Overall health is low. Focus on the flagged portals above."
204
+
205
+ ## Health Score Interpretation
206
+
207
+ Provide this legend if the user asks what the scores mean or on first use:
208
+
209
+ - **8-10**: Excellent. Portal is active, content is fresh, credentials are current.
210
+ - **5-7.9**: Okay. Some areas could use attention but the portal is functional.
211
+ - **Below 5**: Needs attention. The portal may be stale, unused, or missing key components.
212
+
213
+ ## Conventions
214
+
215
+ - Always show all active portals. Do not filter or paginate -- the dashboard should be a complete picture.
216
+ - Sort by health score ascending (worst first) so the portals needing attention are at the top.
217
+ - Use relative time for "Last Login" column ("2 days ago", "3 weeks ago", "Never") for quick scanning.
218
+ - Use double-line box drawing (`═`) for outer borders and single-line (`─`) for internal dividers and the "needs attention" section.
219
+ - The dashboard should fit within 80 columns if possible. Truncate company names if needed.
220
+ - If learnings indicate the user checks status regularly, skip the health score legend and just show the dashboard.
221
+
222
+ ## Error Handling
223
+
224
+ - If the preamble fails, stop and display the error.
225
+ - If the list query succeeds but analytics queries fail for some portals, still show the dashboard with available data. Mark missing analytics as "N/A" rather than failing the whole dashboard.
226
+ - If all queries fail, show the error and suggest checking the database connection.
227
+
228
+ ## Dashboard Refresh
229
+
230
+ The status dashboard is a point-in-time snapshot. It does not auto-refresh or watch for changes. If the user wants to monitor portal health over time, suggest running `/portal status` periodically (e.g., weekly) or setting up a scheduled task.
231
+
232
+ If learnings indicate the user runs status checks regularly, track the trend: "Average health this week: 6.2/10 (up from 5.5/10 last week)." This requires comparing against previous status check results stored in learnings.
233
+
234
+ ## Health Score Calibration
235
+
236
+ The health scoring weights are intentionally opinionated. They reflect what matters for a client portal product:
237
+
238
+ - **Activity (30%)** is weighted highest because the primary purpose of a portal is client engagement. A portal with no views is not serving its purpose.
239
+ - **Content recency (30%)** is equally weighted because stale content erodes trust. If a client sees the same content from months ago, they stop checking.
240
+ - **Credential freshness (20%)** matters for security hygiene. Old credentials are more likely to be compromised or forgotten.
241
+ - **Has files (20%)** is a completeness signal. Portals with documents tend to have higher engagement because they give clients a reason to return.
242
+
243
+ If the user disagrees with the weighting (e.g., "files aren't important for my portals"), note it as a learning for future reference but do not change the scoring algorithm at runtime. The scoring is consistent across all portals for comparability.
244
+
245
+ ## Telemetry
246
+
247
+ If telemetry is enabled, the status skill records:
248
+
249
+ ```json
250
+ {"skill":"portal-status","ts":"2026-04-07T12:00:00Z","duration_s":3,"outcome":"success","portal_count":3,"avg_health":5.8}
251
+ ```
252
+
253
+ This helps track how often the user monitors their portals and whether overall health is trending up or down. No portal-specific data (slugs, company names) is included in telemetry.
254
+
255
+ ## Completion
256
+
257
+ As a final step, log skill completion:
258
+
259
+ ```bash
260
+ echo '{"skill":"portal-status","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
261
+ ```
262
+
263
+ ## Related Skills
264
+
265
+ - `/portal analytics <slug>` -- deep dive into a specific portal's analytics
266
+ - `/portal list` -- simpler list without health scores
267
+ - `/portal preview <slug>` -- open a portal to verify its content
268
+ - `/portal credentials <slug>` -- set up or rotate credentials for a flagged portal
@@ -0,0 +1,348 @@
1
+ ---
2
+ name: portal-update
3
+ description: |
4
+ Edit existing portal content — add tabs, update text, change structure, or refresh after a new meeting.
5
+ Trigger phrases: "update portal", "edit portal", "change the portal", "add a tab to", "update content for". (showpane)
6
+ allowed-tools: [Bash, Read, Write, Edit, 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
+ APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
22
+ if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
23
+ DATABASE_URL="${DATABASE_URL:-}"
24
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
25
+ echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
26
+ exit 1
27
+ fi
28
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
29
+ SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
30
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
31
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
32
+ [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
33
+
34
+ # Predictive next-skill suggestion
35
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
36
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
37
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
38
+ fi
39
+
40
+ # Search relevant learnings
41
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
42
+ if [ -f "$LEARN_FILE" ]; then
43
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
44
+ echo "LEARNINGS: $_LEARN_COUNT entries"
45
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
46
+ echo "RECENT_LEARNINGS:"
47
+ tail -5 "$LEARN_FILE" 2>/dev/null
48
+ fi
49
+ fi
50
+
51
+ # Track skill execution
52
+ SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
53
+ mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
54
+ echo '{"skill":"portal-update","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
55
+ ```
56
+
57
+ If RECENT_SKILLS is shown, suggest the likely next skill:
58
+ - After portal-create → suggest /portal-preview
59
+ - After portal-preview → suggest /portal-deploy or /portal-share
60
+ - After portal-deploy → suggest /portal-status or /portal-verify
61
+ - After portal-setup → suggest /portal-create
62
+ - After portal-credentials → suggest /portal-share
63
+ - After portal-update → suggest /portal-deploy
64
+
65
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
66
+ relevant warnings or tips for this operation. Apply them where relevant but don't
67
+ mention them unless they directly affect the current task.
68
+
69
+ ## Steps
70
+
71
+ ### Step 1: Identify the portal to edit
72
+
73
+ If the user provided a slug (e.g., `/portal update acme-health`), use it. Otherwise, ask which portal to update.
74
+
75
+ Verify the portal exists by checking for the client component file:
76
+
77
+ ```bash
78
+ ls "$APP_PATH/src/app/(portal)/client/<slug>/<slug>-client.tsx" 2>/dev/null
79
+ ```
80
+
81
+ If the file doesn't exist, inform the user and suggest `/portal create <slug>` instead. If the slug directory exists but the client file has a different name, list the directory contents to find the correct file.
82
+
83
+ ### Step 2: Read the existing portal
84
+
85
+ Read the full client component file:
86
+
87
+ ```bash
88
+ cat "$APP_PATH/src/app/(portal)/client/<slug>/<slug>-client.tsx"
89
+ ```
90
+
91
+ Also read the page.tsx for completeness:
92
+
93
+ ```bash
94
+ cat "$APP_PATH/src/app/(portal)/client/<slug>/page.tsx"
95
+ ```
96
+
97
+ Parse and understand the current portal structure:
98
+ - How many tabs exist and what are their IDs and labels?
99
+ - What content is in each tab?
100
+ - What PortalShell props are set?
101
+ - What icons are imported?
102
+
103
+ ### Step 3: Present current structure
104
+
105
+ Show the user a concise summary of the portal's current state:
106
+
107
+ ```
108
+ Portal: acme-health (Acme Health)
109
+
110
+ Tabs:
111
+ 1. overview — "Services overview" (Presentation icon)
112
+ 2. meetings — "Meetings" (CalendarDays icon)
113
+ 3. documents — "Documents" (FileText icon, amber badge)
114
+
115
+ Contact: Jane Smith (Account Manager)
116
+ Last updated: 2 April 2026
117
+ ```
118
+
119
+ This gives the user context before they describe what to change.
120
+
121
+ ### Step 4: Scope lock
122
+
123
+ **CRITICAL: Only edit files within the portal's own directory.**
124
+
125
+ Allowed files:
126
+ - `$APP_PATH/src/app/(portal)/client/<slug>/page.tsx`
127
+ - `$APP_PATH/src/app/(portal)/client/<slug>/<slug>-client.tsx`
128
+ - Any other files within `$APP_PATH/src/app/(portal)/client/<slug>/`
129
+
130
+ Do NOT modify:
131
+ - `$APP_PATH/src/components/portal-shell.tsx` or any shared components
132
+ - Other portals' directories
133
+ - Files in `$APP_PATH/src/lib/`
134
+ - Any files outside the portal's directory
135
+
136
+ If the user's request would require changes to shared components, explain that this is outside the scope of a portal update and suggest they make those changes separately.
137
+
138
+ ### Step 5: Check learnings for user preferences
139
+
140
+ Before making changes, read any relevant learnings from the learnings file:
141
+
142
+ ```bash
143
+ if [ -f "$HOME/.showpane/learnings.jsonl" ]; then
144
+ grep '"portal-update"\|"portal-create"' "$HOME/.showpane/learnings.jsonl" | tail -10
145
+ fi
146
+ ```
147
+
148
+ Look for patterns that inform how to make edits:
149
+ - Tab preferences (how many tabs the user typically wants)
150
+ - Content density preferences (sparse vs. detailed)
151
+ - Styling preferences (color accents, badge usage)
152
+ - Structural patterns (timeline vs. bullet list for next steps)
153
+
154
+ Apply these preferences when generating new content, but do not mention them to the user unless relevant.
155
+
156
+ ### Step 6: Understand the requested changes
157
+
158
+ Ask the user what they want to change. Common requests and how to handle them:
159
+
160
+ **Adding a new tab:**
161
+ 1. Ask for the tab name and what content it should contain
162
+ 2. Create a new function component for the tab content (e.g., `function PricingTab()`)
163
+ 3. Add the tab to the `tabs` array in the exported component
164
+ 4. Import any new icons needed
165
+ 5. Keep the tab count at or below 6
166
+
167
+ **Updating existing content:**
168
+ 1. Identify which tab and section to update
169
+ 2. Make the targeted edit
170
+ 3. Preserve the existing structure and styling
171
+
172
+ **Adding meeting notes from a new meeting:**
173
+ 1. If a transcript is provided (from Granola or pasted), analyze it for new discussion points, action items, and documents
174
+ 2. Add a new meeting section to the Meetings tab using the collapsible `<details>` pattern
175
+ 3. Update the Next Steps tab if new action items were discussed
176
+ 4. Update `lastUpdated` to today's date
177
+
178
+ **Changing the tab order:**
179
+ 1. Reorder the `tabs` array — but keep overview/welcome as the first tab
180
+ 2. Update the `hideFooterOnTab` prop if the first tab changes
181
+
182
+ **Removing a tab:**
183
+ 1. Remove the tab from the `tabs` array
184
+ 2. Remove the corresponding function component
185
+ 3. Clean up unused icon imports
186
+ 4. Ensure at least 2 tabs remain
187
+
188
+ **Updating contact info or metadata:**
189
+ 1. Update the relevant PortalShell props
190
+ 2. Update any inline mentions of the contact in tab content
191
+
192
+ ### Step 7: Make the edits
193
+
194
+ Apply the changes to the client component file. Follow the same conventions as `/portal create`:
195
+
196
+ - Cards: `rounded-2xl border bg-white shadow-sm`
197
+ - Card padding: `p-5 sm:p-6`
198
+ - Section headings: `text-base font-bold tracking-tight text-gray-900`
199
+ - Body text: `text-sm leading-relaxed text-gray-600`
200
+ - Small text: `text-xs text-gray-500`
201
+ - Responsive: use `sm:` breakpoints
202
+ - Icons from `lucide-react` only
203
+
204
+ When adding new content, match the visual weight and density of existing tabs. A portal with sparse, clean tabs should get sparse, clean new content — not suddenly dense data tables.
205
+
206
+ ### Step 8: Show diff before confirming
207
+
208
+ After making edits, show the user a summary of what changed:
209
+
210
+ - Which tabs were added, removed, or modified
211
+ - What content was updated
212
+ - Any prop changes on PortalShell
213
+
214
+ If using git, show the actual diff:
215
+
216
+ ```bash
217
+ cd "$APP_PATH" && git diff "src/app/(portal)/client/<slug>/"
218
+ ```
219
+
220
+ Ask the user to confirm the changes look correct before considering the update complete.
221
+
222
+ ### Step 9: Update lastUpdated
223
+
224
+ Update the `lastUpdated` prop on PortalShell to today's date in the format "7 April 2026". This signals to the client that the portal has fresh content.
225
+
226
+ ### Step 10: Open preview
227
+
228
+ Check if the dev server is running:
229
+
230
+ ```bash
231
+ lsof -i :3000 -sTCP:LISTEN -t 2>/dev/null
232
+ ```
233
+
234
+ If running, open the portal:
235
+
236
+ ```bash
237
+ open "http://localhost:3000/client/<slug>"
238
+ ```
239
+
240
+ If a specific tab was updated, deep-link to it:
241
+
242
+ ```bash
243
+ open "http://localhost:3000/client/<slug>#<tab_id>"
244
+ ```
245
+
246
+ If not running, suggest starting the dev server with `/portal dev`.
247
+
248
+ ### Step 11: Record learning
249
+
250
+ If the update reveals a pattern or preference, record it:
251
+
252
+ ```bash
253
+ echo '{"skill":"portal-update","key":"<pattern>","insight":"<observation>","confidence":7,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
254
+ ```
255
+
256
+ Examples of useful learnings:
257
+ - "User prefers 3-tab portals over 5-tab"
258
+ - "User always includes a Documents tab"
259
+ - "User prefers numbered next steps over bullet points"
260
+ - "User likes amber badges on action-required tabs"
261
+
262
+ ## Granola Enrichment for Updates
263
+
264
+ When the user says something like "update the portal with the latest meeting" or "add notes from today's call", attempt Granola MCP integration:
265
+
266
+ 1. Try `list_meetings` to get recent meetings
267
+ 2. If available, show recent meetings and ask which one to add
268
+ 3. If selected, pull the transcript with `get_meeting_transcript`
269
+ 4. Analyze the transcript for new discussion points, action items, and documents
270
+ 5. Add a new meeting section to the Meetings tab (or create the tab if it doesn't exist)
271
+ 6. Update the Next Steps tab with any new action items
272
+ 7. Update `lastUpdated` to today's date
273
+
274
+ If Granola is not available, ask the user to paste the transcript or describe the changes manually. Never fail because Granola is unavailable.
275
+
276
+ ## Content Pattern Reference
277
+
278
+ When adding new content, use these patterns to match the existing portal style:
279
+
280
+ **Meeting notes section** (collapsible):
281
+ ```tsx
282
+ <MeetingSection title="Follow-up Call" defaultOpen={true}>
283
+ <ul className="space-y-2 text-sm text-gray-600">
284
+ <li className="flex gap-2.5">
285
+ <span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary/60" />
286
+ Discussion point text here
287
+ </li>
288
+ </ul>
289
+ </MeetingSection>
290
+ ```
291
+
292
+ **Action item with status**:
293
+ ```tsx
294
+ <li className="flex items-stretch gap-3 sm:gap-4">
295
+ <div className="flex flex-col items-center">
296
+ <span className={cn(
297
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold",
298
+ done ? "bg-green-500 text-white" : "bg-gray-900 text-white"
299
+ )}>
300
+ {done ? "✓" : index + 1}
301
+ </span>
302
+ </div>
303
+ <div className="pb-5">
304
+ <p className="text-sm font-semibold text-gray-900">Action title</p>
305
+ <p className="mt-0.5 text-sm leading-relaxed text-gray-500">Details here</p>
306
+ </div>
307
+ </li>
308
+ ```
309
+
310
+ **Document download card**:
311
+ ```tsx
312
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
313
+ <div className="flex items-start gap-3">
314
+ <FileText className="mt-0.5 h-5 w-5 shrink-0 text-gray-400" />
315
+ <div>
316
+ <p className="text-sm font-medium text-gray-900">Document title</p>
317
+ <p className="mt-1 text-sm text-gray-500">Description or instructions</p>
318
+ </div>
319
+ </div>
320
+ <button type="button" className="flex w-full items-center justify-center gap-1.5 rounded-lg bg-gray-900 px-5 py-2 text-xs font-semibold text-white transition-colors hover:bg-gray-800 sm:w-auto">
321
+ <Download className="h-3.5 w-3.5" />
322
+ Download PDF
323
+ </button>
324
+ </div>
325
+ ```
326
+
327
+ ## Completion
328
+
329
+ As a final step, log skill completion:
330
+
331
+ ```bash
332
+ echo '{"skill":"portal-update","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
333
+ ```
334
+
335
+ ## Conventions
336
+
337
+ - Scope lock: ONLY edit files in `$APP_PATH/src/app/(portal)/client/<slug>/`
338
+ - Never modify shared components, other portals, or lib files
339
+ - Always show the diff to the user before considering the update complete
340
+ - Preserve existing styling patterns — match the visual density of existing tabs
341
+ - Update `lastUpdated` on every content change
342
+ - Keep tab count between 2 and 6
343
+ - First tab must always be overview/welcome
344
+ - Follow the same Tailwind class conventions as `/portal create`
345
+ - If the user's request is ambiguous, ask for clarification rather than guessing
346
+ - When adding meeting notes, use the collapsible `<details>` pattern from the example portal
347
+ - When the user asks to "refresh" a portal, they typically mean update `lastUpdated` and add new content from a recent meeting — treat this as a meeting enrichment flow
348
+ - If a portal has grown to 6 tabs and the user wants to add more, suggest consolidating related tabs instead of exceeding the maximum