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