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.
- package/README.md +2 -1
- package/bundle/meta/scaffold-manifest.json +10 -10
- package/bundle/scaffold/VERSION +1 -1
- package/bundle/scaffold/prisma/seed.ts +40 -35
- package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +7 -0
- package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +1 -2
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +5 -4
- package/bundle/scaffold/src/app/page.tsx +43 -6
- package/bundle/scaffold/src/components/portal-shell.tsx +23 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +33 -0
- package/bundle/toolchain/CLI_VERSION +1 -0
- package/bundle/toolchain/TELEMETRY_CONFIG.json +4 -0
- package/bundle/toolchain/VERSION +1 -1
- package/bundle/toolchain/bin/ensure-cloud-project-link.ts +34 -1
- package/bundle/toolchain/bin/showpane-config +108 -29
- package/bundle/toolchain/bin/showpane-telemetry-log +84 -0
- package/bundle/toolchain/bin/showpane-telemetry-sync +212 -0
- package/bundle/toolchain/bin/showpane-update-check +130 -0
- package/bundle/toolchain/skills/SKILL.md.tmpl +13 -0
- package/bundle/toolchain/skills/VERSION +1 -1
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +60 -38
- package/bundle/toolchain/skills/portal-analytics/SKILL.md.tmpl +192 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +65 -67
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +264 -0
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +66 -49
- package/bundle/toolchain/skills/portal-credentials/SKILL.md.tmpl +198 -0
- package/bundle/toolchain/skills/portal-delete/SKILL.md +63 -41
- package/bundle/toolchain/skills/portal-delete/SKILL.md.tmpl +194 -0
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +57 -47
- package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +452 -0
- package/bundle/toolchain/skills/portal-dev/SKILL.md +65 -47
- package/bundle/toolchain/skills/portal-dev/SKILL.md.tmpl +228 -0
- package/bundle/toolchain/skills/portal-list/SKILL.md +64 -43
- package/bundle/toolchain/skills/portal-list/SKILL.md.tmpl +181 -0
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +331 -162
- package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +340 -0
- package/bundle/toolchain/skills/portal-preview/SKILL.md +65 -44
- package/bundle/toolchain/skills/portal-preview/SKILL.md.tmpl +171 -0
- package/bundle/toolchain/skills/portal-setup/SKILL.md +79 -60
- package/bundle/toolchain/skills/portal-setup/SKILL.md.tmpl +227 -0
- package/bundle/toolchain/skills/portal-share/SKILL.md +69 -47
- package/bundle/toolchain/skills/portal-share/SKILL.md.tmpl +162 -0
- package/bundle/toolchain/skills/portal-status/SKILL.md +58 -37
- package/bundle/toolchain/skills/portal-status/SKILL.md.tmpl +196 -0
- package/bundle/toolchain/skills/portal-update/SKILL.md +60 -46
- package/bundle/toolchain/skills/portal-update/SKILL.md.tmpl +269 -0
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +55 -33
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md.tmpl +164 -0
- package/bundle/toolchain/skills/portal-verify/SKILL.md +69 -14
- package/bundle/toolchain/skills/portal-verify/SKILL.md.tmpl +224 -0
- package/bundle/toolchain/skills/shared/preamble.md +30 -126
- package/bundle/toolchain/skills/shared/runtime-principles.md +25 -0
- package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +1 -1
- package/dist/index.js +79 -14
- 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
|
-
|
|
23
|
-
|
|
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
|
|
28
|
+
echo "Showpane not configured. Run /portal-setup first."
|
|
26
29
|
exit 1
|
|
27
30
|
fi
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
42
|
+
|
|
43
|
+
SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$SHOWPANE_HOME/current}"
|
|
39
44
|
SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
210
|
-
3.
|
|
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
|
|
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
|