showpane 0.4.13 → 0.4.15

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 (55) hide show
  1. package/README.md +2 -1
  2. package/bundle/meta/scaffold-manifest.json +10 -10
  3. package/bundle/scaffold/VERSION +1 -1
  4. package/bundle/scaffold/prisma/seed.ts +40 -35
  5. package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +7 -0
  6. package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +1 -2
  7. package/bundle/scaffold/src/app/(portal)/client/page.tsx +5 -4
  8. package/bundle/scaffold/src/app/page.tsx +43 -6
  9. package/bundle/scaffold/src/components/portal-shell.tsx +23 -0
  10. package/bundle/scaffold/src/lib/portal-contracts.ts +33 -0
  11. package/bundle/toolchain/CLI_VERSION +1 -0
  12. package/bundle/toolchain/TELEMETRY_CONFIG.json +4 -0
  13. package/bundle/toolchain/VERSION +1 -1
  14. package/bundle/toolchain/bin/ensure-cloud-project-link.ts +34 -1
  15. package/bundle/toolchain/bin/showpane-config +108 -29
  16. package/bundle/toolchain/bin/showpane-telemetry-log +84 -0
  17. package/bundle/toolchain/bin/showpane-telemetry-sync +212 -0
  18. package/bundle/toolchain/bin/showpane-update-check +130 -0
  19. package/bundle/toolchain/skills/SKILL.md.tmpl +13 -0
  20. package/bundle/toolchain/skills/VERSION +1 -1
  21. package/bundle/toolchain/skills/portal-analytics/SKILL.md +60 -38
  22. package/bundle/toolchain/skills/portal-analytics/SKILL.md.tmpl +192 -0
  23. package/bundle/toolchain/skills/portal-create/SKILL.md +65 -67
  24. package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +264 -0
  25. package/bundle/toolchain/skills/portal-credentials/SKILL.md +66 -49
  26. package/bundle/toolchain/skills/portal-credentials/SKILL.md.tmpl +198 -0
  27. package/bundle/toolchain/skills/portal-delete/SKILL.md +63 -41
  28. package/bundle/toolchain/skills/portal-delete/SKILL.md.tmpl +194 -0
  29. package/bundle/toolchain/skills/portal-deploy/SKILL.md +57 -47
  30. package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +452 -0
  31. package/bundle/toolchain/skills/portal-dev/SKILL.md +65 -47
  32. package/bundle/toolchain/skills/portal-dev/SKILL.md.tmpl +228 -0
  33. package/bundle/toolchain/skills/portal-list/SKILL.md +64 -43
  34. package/bundle/toolchain/skills/portal-list/SKILL.md.tmpl +181 -0
  35. package/bundle/toolchain/skills/portal-onboard/SKILL.md +331 -162
  36. package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +340 -0
  37. package/bundle/toolchain/skills/portal-preview/SKILL.md +65 -44
  38. package/bundle/toolchain/skills/portal-preview/SKILL.md.tmpl +171 -0
  39. package/bundle/toolchain/skills/portal-setup/SKILL.md +79 -60
  40. package/bundle/toolchain/skills/portal-setup/SKILL.md.tmpl +227 -0
  41. package/bundle/toolchain/skills/portal-share/SKILL.md +69 -47
  42. package/bundle/toolchain/skills/portal-share/SKILL.md.tmpl +162 -0
  43. package/bundle/toolchain/skills/portal-status/SKILL.md +58 -37
  44. package/bundle/toolchain/skills/portal-status/SKILL.md.tmpl +196 -0
  45. package/bundle/toolchain/skills/portal-update/SKILL.md +60 -46
  46. package/bundle/toolchain/skills/portal-update/SKILL.md.tmpl +269 -0
  47. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +55 -33
  48. package/bundle/toolchain/skills/portal-upgrade/SKILL.md.tmpl +164 -0
  49. package/bundle/toolchain/skills/portal-verify/SKILL.md +69 -14
  50. package/bundle/toolchain/skills/portal-verify/SKILL.md.tmpl +224 -0
  51. package/bundle/toolchain/skills/shared/preamble.md +30 -126
  52. package/bundle/toolchain/skills/shared/runtime-principles.md +25 -0
  53. package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +1 -1
  54. package/dist/index.js +79 -14
  55. package/package.json +5 -2
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: portal-create
3
+ description: |
4
+ Scaffold a new client portal from a meeting transcript, template, or description.
5
+ This is the faster repeat-user path once Showpane is already configured and the
6
+ user does not need the guided first-run wizard.
7
+ Trigger phrases: "create a portal", "new portal", "set up a client page", "make a portal for". (showpane)
8
+ allowed-tools: [Bash, Read, Write, Edit, Glob, Grep]
9
+ ---
10
+
11
+ If the user asks for unsupported hosted behavior, risky upload types, or cloud-specific
12
+ capabilities, read `skills/shared/platform-constraints.md` and apply the relevant limits.
13
+
14
+ {{PREAMBLE}}
15
+
16
+ ## Steps
17
+
18
+ ### Step 1: Determine the portal slug
19
+
20
+ If the user provided a slug (e.g., `/portal-create acme-health`), use it. Otherwise, infer from context — the company name mentioned in conversation, a meeting transcript, or ask the user directly.
21
+
22
+ Validate the slug by running:
23
+
24
+ ```bash
25
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/check-slug.ts" --slug <slug> --org-id <org_id>
26
+ ```
27
+
28
+ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
29
+ - `reason: "format"` — slug must be lowercase alphanumeric + hyphens, 2-50 chars, no leading/trailing hyphens
30
+ - `reason: "reserved"` — reserved names: api, client, s, admin, static, _next, health, example
31
+ - `reason: "taken"` — a portal with this slug already exists for this org
32
+
33
+ If invalid, explain the issue and ask for a different slug.
34
+
35
+ Also ask for the client's website domain (e.g., "acme-health.com"). This is optional but enables auto-branding:
36
+ - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
37
+ - If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
38
+
39
+ ### Step 2: Granola MCP integration (optional)
40
+
41
+ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
42
+
43
+ **If Granola MCP is available:**
44
+ 1. Call `list_meetings` to get recent meetings
45
+ 2. Present the list to the user: date, title, participants
46
+ 3. Ask which meeting to use (or "none — I'll describe the portal manually")
47
+ 4. If a meeting is selected, call `get_meeting_transcript` to retrieve the full transcript
48
+ 5. Store the transcript for content analysis in Step 4
49
+
50
+ **If Granola MCP is NOT available** (tool-not-found error):
51
+ - Skip gracefully. Do not mention Granola or show an error.
52
+ - Ask: "Do you have a meeting transcript to paste, or shall I work from a description?"
53
+ - If the user pastes a transcript, store it for analysis
54
+ - If no transcript, proceed to template selection with manual content
55
+
56
+ Never fail or block because Granola is unavailable. It is purely additive.
57
+
58
+ ### Step 3: Template selection
59
+
60
+ Ask which template to use as a starting point. Keep this brief and practical —
61
+ the user chose `/portal-create` because they want the fast path, not a wizard.
62
+
63
+ 1. **sales-followup** — Meeting notes, next steps, documents. Best after a sales call.
64
+ 2. **consulting** — Project overview, deliverables, timeline. Best for ongoing engagements.
65
+ 3. **onboarding** — Welcome, setup steps, resources. Best for new client onboarding.
66
+ 4. **blank** — Start from scratch with just an overview tab.
67
+
68
+ Read the chosen template file from `$SKILL_DIR/templates/` for structural inspiration:
69
+
70
+ ```bash
71
+ cat "$SKILL_DIR/templates/sales-followup/sales-followup-client.tsx"
72
+ ```
73
+
74
+ Always also read the example portal as your quality and style reference:
75
+
76
+ ```bash
77
+ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
78
+ ```
79
+
80
+ The template provides content structure. The example provides quality and styling. Match the example's patterns: card styles, typography, spacing, responsive breakpoints. Templates are inspiration, not rigid scaffolds. Adapt the structure to fit the actual content.
81
+
82
+ ### Step 4: Analyze transcript (if available)
83
+
84
+ If a transcript was provided (from Granola or pasted), analyze it to extract:
85
+
86
+ | Signal Found | Tab to Generate | Content Pattern |
87
+ |---|---|---|
88
+ | Meeting discussion topics | "Meetings" | Collapsible `<details>` sections per meeting with bullet points |
89
+ | Action items, next steps, follow-ups | "Next Steps" | Numbered timeline with status indicators (done/pending) |
90
+ | Documents mentioned (contracts, NDAs, proposals) | "Documents" | Download cards with file type icons from lucide-react |
91
+ | Service descriptions, capabilities discussed | "Services" | Grid of cards with title and description |
92
+ | Pricing, costs, tiers discussed | "Pricing" | Comparison table or tier cards |
93
+ | Project phases, milestones | "Timeline" | Vertical timeline with phase markers |
94
+
95
+ Always generate at minimum:
96
+ - An **overview/welcome tab** (first tab, always)
97
+ - At least **one additional tab** based on content
98
+
99
+ If the transcript is rich, generate up to 5-6 tabs. Do not exceed 6 tabs total.
100
+
101
+ Extract from the transcript:
102
+ - **Company name** and contact details (for PortalShell props)
103
+ - **Key discussion points** (for meeting notes)
104
+ - **Agreed actions** (for next steps timeline)
105
+ - **Mentioned documents** (for documents tab)
106
+ - **Services or products discussed** (for services/overview content)
107
+
108
+ ### Step 5: Generate the portal files
109
+
110
+ Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
111
+
112
+ #### File 1: `page.tsx` (server component)
113
+
114
+ ```tsx
115
+ import { <SlugName>PortalClient } from "./<slug>-client";
116
+
117
+ export const metadata = {
118
+ title: "<Company Name> | Portal",
119
+ };
120
+
121
+ export default function <SlugName>Portal() {
122
+ return <<SlugName>PortalClient />;
123
+ }
124
+ ```
125
+
126
+ Convert the slug to PascalCase for the component name (e.g., `acme-health` becomes `AcmeHealth`).
127
+
128
+ #### File 2: `<slug>-client.tsx` (client component)
129
+
130
+ This is the main file. Follow these conventions exactly:
131
+
132
+ **Imports:**
133
+ ```tsx
134
+ "use client";
135
+
136
+ import { type ReactNode } from "react";
137
+ import { /* icons from lucide-react */ } from "lucide-react";
138
+ import { cn } from "@/lib/utils";
139
+ import { PortalShell } from "@/components/portal-shell";
140
+ ```
141
+
142
+ **Structure:**
143
+ - Define each tab's content as a separate function component within the file (e.g., `function OverviewTab()`, `function DocumentsTab()`)
144
+ - Export a single named component: `export function <SlugName>PortalClient()`
145
+ - The exported component returns `<PortalShell>` with all required props
146
+
147
+ **PortalShell props (all required):**
148
+ - `companyName` — the org's company name (from config/DB)
149
+ - `companyLogo` — a `<span>` with the first letter of the company name, white text
150
+ - `clientName` — the client's company name (from transcript or user input)
151
+ - `clientLogoSrc` — if client domain was provided: use `getLogoUrl(domain)` from `app/src/lib/branding.ts`. If not: use `getInitialLogo(clientName)` to generate an SVG data URI. Store the chosen URL in the ClientPortal record's `logoUrl` field
152
+ - `clientLogoAlt` — the client company name
153
+ - `lastUpdated` — today's date formatted as "7 April 2026"
154
+ - `contact` — object with `name`, `title`, `avatarSrc`, `email` (from org config)
155
+ - `tabs` — array of tab objects with `id`, `label`, `icon`, `content`, and optional `badge`
156
+ - `hideFooterOnTab` — set to `"overview"` (hides the contact footer on the first tab since it typically has contact info inline)
157
+
158
+ **Styling conventions (match the example portal exactly):**
159
+ - Cards: `rounded-2xl border bg-white shadow-sm`
160
+ - Card padding: `p-5 sm:p-6`
161
+ - Section headings: `text-base font-bold tracking-tight text-gray-900`
162
+ - Body text: `text-sm leading-relaxed text-gray-600`
163
+ - Small text: `text-xs text-gray-500`
164
+ - Bullet points: use `<span>` dots with `h-1.5 w-1.5 rounded-full` for bullet markers
165
+ - Status badges: `rounded-full px-2 py-0.5 text-[11px] font-medium` with color variants
166
+ - Buttons: `rounded-lg bg-gray-900 px-5 py-2 text-xs font-semibold text-white`
167
+ - Grid layouts: `grid gap-3 sm:grid-cols-2` for card grids
168
+ - Spacing between sections: `mt-6` with `mb-4` for section headings
169
+ - Responsive: mobile-first, use `sm:` breakpoints for wider layouts
170
+
171
+ **Icon usage:**
172
+ Import only the icons you need from `lucide-react`. Common choices:
173
+ - `Presentation` for overview/services
174
+ - `CalendarDays` for meetings
175
+ - `FileText` for documents
176
+ - `BarChart3` for analytics/strategy
177
+ - `ListChecks` for next steps
178
+ - `Download` for download buttons
179
+ - `ChevronDown` for collapsible sections
180
+ - `Clock` for timeline
181
+ - `DollarSign` for pricing
182
+
183
+ **For collapsible meeting sections**, use the same `<details>` pattern as the example:
184
+ ```tsx
185
+ <details open={defaultOpen} className="group">
186
+ <summary className="flex cursor-pointer list-none items-center gap-1.5 text-left">
187
+ <ChevronDown className="h-3.5 w-3.5 shrink-0 text-gray-400 transition-transform group-open:rotate-180" />
188
+ <h4 className="text-sm font-semibold text-gray-900">{title}</h4>
189
+ </summary>
190
+ <div className="mt-2 pl-5">{children}</div>
191
+ </details>
192
+ ```
193
+
194
+ ### Step 6: Create database record
195
+
196
+ Run the create-portal script to register the portal in the database:
197
+
198
+ ```bash
199
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id <org_id>
200
+ ```
201
+
202
+ This creates the `ClientPortal` record with the slug, company name, and links it to the Organization. It does NOT create credentials — that is a separate step via `/portal-credentials`.
203
+
204
+ ### Step 7: Self-review
205
+
206
+ After generating the files, read them back and verify:
207
+
208
+ 1. **PortalShell used?** The client component must use `<PortalShell>` as its root element.
209
+ 2. **Minimum 2 tabs?** Check the `tabs` array has at least 2 entries.
210
+ 3. **Contact info in props?** The `contact` prop must have `name`, `title`, `avatarSrc`, `email`.
211
+ 4. **"use client" directive?** Must be the first line of the client component.
212
+ 5. **Imports correct?** `cn` from `@/lib/utils`, `PortalShell` from `@/components/portal-shell`.
213
+ 6. **No hardcoded localhost URLs?** Links should be relative or use placeholders.
214
+ 7. **Responsive patterns?** Check for `sm:` breakpoints on grids and padding.
215
+ 8. **Tab content functions?** Each tab should have its own function, not inline JSX.
216
+
217
+ If any check fails, fix the issue before proceeding.
218
+
219
+ ### Step 8: Open preview
220
+
221
+ Check if the dev server is running:
222
+
223
+ ```bash
224
+ lsof -i :3000 -sTCP:LISTEN -t 2>/dev/null
225
+ ```
226
+
227
+ If running, open the portal in the browser:
228
+
229
+ ```bash
230
+ open "http://localhost:3000/client/<slug>"
231
+ ```
232
+
233
+ If not running, suggest:
234
+
235
+ > "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
236
+
237
+ ### Step 9: Summary and next steps
238
+
239
+ Print a summary:
240
+
241
+ ```
242
+ Portal created: <slug>
243
+
244
+ Client: <company_name>
245
+ Tabs: Overview, Next Steps, Documents (3 tabs)
246
+ Files: src/app/(portal)/client/<slug>/page.tsx
247
+ src/app/(portal)/client/<slug>/<slug>-client.tsx
248
+
249
+ Next steps:
250
+ 1. Create login credentials: /portal-credentials <slug>
251
+ 2. Preview the portal: /portal-preview <slug>
252
+ 3. Edit content: /portal-update <slug>
253
+ 4. Deploy: /portal-deploy
254
+ ```
255
+
256
+ ### Step 10: Record learning
257
+
258
+ Append a learning about the portal creation for future reference:
259
+
260
+ ```bash
261
+ echo '{"skill":"portal-create","key":"portal-created","insight":"Created portal <slug> for <company>. Template: <template>. Tabs: <tab_list>.","confidence":8,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
262
+ ```
263
+
264
+ {{COMPLETION}}
@@ -18,16 +18,20 @@ hooks:
18
18
 
19
19
  ## Preamble (run first)
20
20
 
21
+ Before doing anything else, execute this block in a Bash tool call:
22
+
21
23
  ```bash
22
- # Read config
23
- CONFIG="$HOME/.showpane/config.json"
24
+ SHOWPANE_HOME="$HOME/.showpane"
25
+ SHOWPANE_BIN="$SHOWPANE_HOME/bin"
26
+ CONFIG="$SHOWPANE_HOME/config.json"
24
27
  if [ ! -f "$CONFIG" ]; then
25
- echo "Showpane not configured. Run /portal setup first."
28
+ echo "Showpane not configured. Run /portal-setup first."
26
29
  exit 1
27
30
  fi
28
- APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
29
- DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null)
30
- ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
31
+
32
+ APP_PATH=$("$SHOWPANE_BIN/showpane-config" get app_path 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
33
+ DEPLOY_MODE=$("$SHOWPANE_BIN/showpane-config" get deploy_mode 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null || echo "local")
34
+ ORG_SLUG=$("$SHOWPANE_BIN/showpane-config" get orgSlug 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
31
35
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
32
36
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
33
37
  DATABASE_URL="${DATABASE_URL:-}"
@@ -35,20 +39,26 @@ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
35
39
  echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
36
40
  exit 1
37
41
  fi
38
- SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
42
+
43
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$SHOWPANE_HOME/current}"
39
44
  SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
40
- echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
41
- LEARN_FILE="$HOME/.showpane/learnings.jsonl"
45
+ _UPD=$("$SHOWPANE_BIN/showpane-update-check" 2>/dev/null || true)
46
+ [ -n "$_UPD" ] && echo "$_UPD" || true
47
+ mkdir -p "$SHOWPANE_HOME/sessions" "$SHOWPANE_HOME/analytics" "$SHOWPANE_HOME/checkpoints"
48
+ touch "$SHOWPANE_HOME/sessions/$PPID"
49
+ find "$SHOWPANE_HOME/sessions" -mmin +120 -type f -delete 2>/dev/null || true
50
+ TEL=$("$SHOWPANE_BIN/showpane-config" get telemetry 2>/dev/null || echo "anonymous")
51
+ TEL_PROMPTED=$([ -f "$SHOWPANE_HOME/.telemetry-prompted" ] && echo "yes" || echo "no")
52
+ _TEL_START=$(date +%s)
53
+ _SESSION_ID="${PPID:-0}-$(date +%s)"
54
+
55
+ LEARN_FILE="$SHOWPANE_HOME/learnings.jsonl"
42
56
  [ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
43
-
44
- # Predictive next-skill suggestion
45
- if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
46
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
57
+ if [ -f "$SHOWPANE_HOME/timeline.jsonl" ]; then
58
+ _RECENT=$(grep '"event":"completed"' "$SHOWPANE_HOME/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '
59
+ ' ',' | sed 's/,$//' || true)
47
60
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
48
61
  fi
49
-
50
- # Search relevant learnings
51
- LEARN_FILE="$HOME/.showpane/learnings.jsonl"
52
62
  if [ -f "$LEARN_FILE" ]; then
53
63
  _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
54
64
  echo "LEARNINGS: $_LEARN_COUNT entries"
@@ -58,29 +68,45 @@ if [ -f "$LEARN_FILE" ]; then
58
68
  fi
59
69
  fi
60
70
 
61
- # Track skill execution
62
- SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
71
+ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
63
72
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
64
73
  echo '{"skill":"portal-credentials","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
74
+ echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
75
+ echo "TELEMETRY: $TEL"
76
+ echo "TEL_PROMPTED: $TEL_PROMPTED"
77
+ ```
78
+
79
+ If output shows `JUST_UPGRADED <from> <to>`, tell the user Showpane was just upgraded and continue.
80
+
81
+ If output shows `UPGRADE_AVAILABLE <old> <new>`, tell the user a newer Showpane toolchain is available and recommend `/portal-upgrade`.
82
+
83
+ If `TEL_PROMPTED` is `no`, default telemetry to `anonymous` without interrupting the flow. Do not mention telemetry unless the user asks.
84
+
85
+ Run:
86
+ ```bash
87
+ "$SHOWPANE_BIN/showpane-config" set telemetry anonymous
88
+ touch "$SHOWPANE_HOME/.telemetry-prompted"
65
89
  ```
66
90
 
67
- If RECENT_SKILLS is shown, suggest the likely next skill:
91
+ If `RECENT_SKILLS` is shown, suggest the likely next skill:
68
92
  - After portal-create → suggest /portal-preview
69
- - After portal-preview → suggest /portal-deploy or /portal-share
93
+ - After portal-preview → suggest /portal-deploy
70
94
  - After portal-deploy → suggest /portal-status or /portal-verify
71
- - After portal-setup → suggest /portal-create
72
- - After portal-credentials → suggest /portal-share
73
- - After portal-update → suggest /portal-deploy
95
+ - After portal-setup → suggest /portal-onboard for a first run, or /portal-create for the fast path
96
+ - After portal-credentials → suggest /portal-deploy before external sharing
97
+ - After portal-update → suggest /portal-preview or /portal-deploy
98
+
99
+ If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
74
100
 
75
- If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
76
- relevant warnings or tips for this operation. Apply them where relevant but don't
77
- mention them unless they directly affect the current task.
101
+ Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+
103
+ If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
78
104
 
79
105
  ## Steps
80
106
 
81
107
  ### Step 1: Identify the portal
82
108
 
83
- If the user provided a slug (e.g., `/portal credentials acme-health`), use it. Otherwise, list available portals to help the user choose:
109
+ If the user provided a slug (e.g., `/portal-credentials acme-health`), use it. Otherwise, list available portals to help the user choose:
84
110
 
85
111
  ```bash
86
112
  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>
@@ -88,9 +114,9 @@ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PA
88
114
 
89
115
  Present the list and ask which portal needs credentials. If there is only one portal, confirm it rather than asking.
90
116
 
91
- Verify the portal exists by checking the database. The `create-portal.ts` script should have been run during `/portal create` to register the portal. If no DB record exists, inform the user:
117
+ Verify the portal exists by checking the database. The `create-portal.ts` script should have been run during `/portal-create` to register the portal. If no DB record exists, inform the user:
92
118
 
93
- > "No portal record found for '<slug>'. Run `/portal create <slug>` first to register it."
119
+ > "No portal record found for '<slug>'. Run `/portal-create <slug>` first to register it."
94
120
 
95
121
  Also check the portal's current credential status from the list output. If credentials already exist, inform the user before proceeding:
96
122
 
@@ -127,7 +153,7 @@ The script returns JSON on stdout:
127
153
 
128
154
  If the script returns an error, handle it based on the error type:
129
155
 
130
- - `portal_not_found` — The portal slug doesn't exist in the database for this org. Suggest running `/portal create <slug>` first.
156
+ - `portal_not_found` — The portal slug doesn't exist in the database for this org. Suggest running `/portal-create <slug>` first.
131
157
  - `database_error` — Connection or query failure. Check DATABASE_URL and re-apply the local schema with `cd $APP_PATH && npm run prisma:db-push`.
132
158
  - `auth_secret_missing` — The AUTH_SECRET environment variable is not set in `$APP_PATH/.env`. The rotate-credentials script needs this to function. Suggest adding `AUTH_SECRET=<random-string>` to the .env file.
133
159
 
@@ -154,7 +180,7 @@ The ASCII box ensures the credentials stand out clearly in terminal output. Do n
154
180
 
155
181
  Immediately after displaying credentials, show this warning:
156
182
 
157
- > **Save these credentials now.** They will not be shown again. The password is hashed in the database and cannot be retrieved. If lost, run `/portal credentials <slug>` again to rotate to a new password.
183
+ > **Save these credentials now.** They will not be shown again. The password is hashed in the database and cannot be retrieved. If lost, run `/portal-credentials <slug>` again to rotate to a new password.
158
184
 
159
185
  If this was a rotation (`"rotated": true`), add:
160
186
 
@@ -167,7 +193,8 @@ Provide guidance on how to share the credentials with the client:
167
193
  > **How to share with your client:**
168
194
  > - Send the username and password via a secure channel (encrypted email, Signal, etc.)
169
195
  > - The login page is at `/client` — the client enters the portal slug as the "company" field, then the username and password
170
- > - For a direct link without credentials, use `/portal share <slug>` to generate a reusable share URL that bypasses the login entirely
196
+ > - For external access, publish the portal first with `/portal-deploy`
197
+ > - After publish, you can use `/portal-share <slug>` if you want a direct hosted link instead of asking the client to log in
171
198
 
172
199
  Recommend against sharing credentials via:
173
200
  - Unencrypted email (can be intercepted)
@@ -206,8 +233,8 @@ Credentials created for: acme-health
206
233
 
207
234
  Next steps:
208
235
  1. Send credentials to the client via a secure channel
209
- 2. Generate a share link: /portal share <slug>
210
- 3. Deploy if not already live: /portal deploy
236
+ 2. Deploy if not already live: /portal-deploy
237
+ 3. Optional hosted direct link after publish: /portal-share <slug>
211
238
  ```
212
239
 
213
240
  ### Step 9: Record credential event
@@ -229,7 +256,7 @@ echo '{"skill":"portal-credentials","key":"credentials-created","insight":"Creat
229
256
 
230
257
  ## Error Handling
231
258
 
232
- - **Script not found**: If `$SKILL_DIR/bin/rotate-credentials.ts` does not exist, the skill pack may not be fully installed. Suggest running `/portal upgrade` or checking the Showpane installation.
259
+ - **Script not found**: If `$SKILL_DIR/bin/rotate-credentials.ts` does not exist, the skill pack may not be fully installed. Suggest running `/portal-upgrade` or checking the Showpane installation.
233
260
  - **Prisma connection error**: DATABASE_URL may be wrong or the database may be down. Check the .env file and database status.
234
261
  - **Permission denied on .env**: The preamble sources `$APP_PATH/.env`. If this file is not readable, the DATABASE_URL will not be set. Check file permissions.
235
262
  - **Script timeout**: If the script takes more than 30 seconds, something is wrong. The most common cause is a database connection timeout — check network connectivity to the database host.
@@ -254,21 +281,11 @@ Even in bulk mode, show the security warning once after all credentials are disp
254
281
 
255
282
  ## Completion
256
283
 
257
- As a final step, log skill completion:
284
+ As a final step, log skill completion and telemetry:
258
285
 
259
286
  ```bash
260
287
  echo '{"skill":"portal-credentials","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
288
+ _TEL_END=$(date +%s)
289
+ _TEL_DUR=$(( _TEL_END - ${_TEL_START:-_TEL_END} ))
290
+ "$HOME/.showpane/bin/showpane-telemetry-log" --skill "portal-credentials" --duration "$_TEL_DUR" --outcome success --session-id "${_SESSION_ID:-}" 2>/dev/null || true
261
291
  ```
262
-
263
- ## Conventions
264
-
265
- - Always run the bin script rather than manipulating the database directly — never generate passwords, hash them, or write to the database from the skill
266
- - Show credentials in an ASCII box for clear visibility in the terminal
267
- - Warn about credential impermanence immediately after displaying them
268
- - If the portal has no DB record, direct the user to `/portal create` first
269
- - Never store, log, or record the plaintext password anywhere — not in learnings, not in telemetry, not in config
270
- - Confirm before rotating existing credentials (default to "no") since rotation invalidates sessions
271
- - The username is always the same as the portal slug — this is by design for simplicity
272
- - If the user asks "what is the password for X portal", explain that passwords are one-way hashed and cannot be retrieved — offer to rotate to a new one instead
273
- - After creating credentials, always suggest the next step: either share with the client or deploy if not already live
274
- - If the app is not deployed yet, credentials still work — they are stored in the database regardless of whether the app is running in production