showpane 0.4.17 → 0.4.19

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 (27) hide show
  1. package/bundle/meta/scaffold-manifest.json +5 -5
  2. package/bundle/scaffold/VERSION +1 -1
  3. package/bundle/scaffold/src/app/(portal)/client/page.tsx +81 -8
  4. package/bundle/scaffold/src/middleware.ts +13 -3
  5. package/bundle/toolchain/CLI_VERSION +1 -1
  6. package/bundle/toolchain/VERSION +1 -1
  7. package/bundle/toolchain/bin/get-org.ts +46 -0
  8. package/bundle/toolchain/skills/VERSION +1 -1
  9. package/bundle/toolchain/skills/portal-analytics/SKILL.md +11 -0
  10. package/bundle/toolchain/skills/portal-create/SKILL.md +71 -18
  11. package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +60 -18
  12. package/bundle/toolchain/skills/portal-credentials/SKILL.md +11 -0
  13. package/bundle/toolchain/skills/portal-delete/SKILL.md +11 -0
  14. package/bundle/toolchain/skills/portal-deploy/SKILL.md +27 -2
  15. package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +16 -2
  16. package/bundle/toolchain/skills/portal-dev/SKILL.md +11 -0
  17. package/bundle/toolchain/skills/portal-list/SKILL.md +11 -0
  18. package/bundle/toolchain/skills/portal-onboard/SKILL.md +41 -10
  19. package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +37 -2
  20. package/bundle/toolchain/skills/portal-preview/SKILL.md +11 -0
  21. package/bundle/toolchain/skills/portal-share/SKILL.md +11 -0
  22. package/bundle/toolchain/skills/portal-status/SKILL.md +11 -0
  23. package/bundle/toolchain/skills/portal-update/SKILL.md +11 -0
  24. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +11 -0
  25. package/bundle/toolchain/skills/portal-verify/SKILL.md +11 -0
  26. package/dist/index.js +53 -9
  27. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-11T22:10:49.959Z",
4
- "scaffoldVersion": "0.2.6",
3
+ "generatedAt": "2026-04-11T23:18:05.246Z",
4
+ "scaffoldVersion": "0.2.7",
5
5
  "files": {
6
6
  ".env.example": "ed105f2bdcd1888a98181d55e3c9f7d6eff3ae9c3e2366c2e777a12e3caddfa7",
7
7
  ".gitignore": "998e5f43865ea56ac79a05acfd5d4b0d696f310bd5325a1ed458c3d40154d437",
8
- "VERSION": "be3c6d2c6c406a64d44f0b6464a887e290416dd90c524094485b1be00936d6d7",
8
+ "VERSION": "5b57e3b8c153d1d33b0c0f1ee29d3de1bf93232ed95aa78b80fe885e99faa915",
9
9
  "next.config.ts": "cf27999cc274cce79bc4c8df11789807719abf40752b60e4b4967a3d2f0ed013",
10
10
  "package-lock.json": "d8e30eb86f08e70787d4459a084b4ab2a9f119696bbd3146ec4ba5675fffd3c2",
11
11
  "package.json": "b095e17e7fc181c630e87fe9f473c5a4ef969afcd4b110f9f9c6d6a6d93f1c0b",
@@ -27,7 +27,7 @@
27
27
  "src/app/(portal)/client/example/example-client.tsx": "ed32b111acea861f448d865338f8841d47c6ca7c2f87ed30d85bb0804940d4ec",
28
28
  "src/app/(portal)/client/example/page.tsx": "f330864f63c9feea76c8a62c3eba3ce57578627e0d4abd929fd7fefdfc7af058",
29
29
  "src/app/(portal)/client/layout.tsx": "4f43871510408a81da229d48ae316ec1d1c1beda93121922246300a2c8fd0999",
30
- "src/app/(portal)/client/page.tsx": "7b4e3e5b286672f83581e24349e98ac930d8bbf5dfed02793754be2ddadf430f",
30
+ "src/app/(portal)/client/page.tsx": "7efe97c047e9ad8f5d9034fe616c0c915277a9cb92fa4ce57fe35af672c80320",
31
31
  "src/app/api/client-auth/route.ts": "ce1858559b1e944d5b1dc719d1f03bebf66286671700b1b5397382109f0f1e0d",
32
32
  "src/app/api/client-auth/share/route.ts": "ed82414212dcd26af8c6c0f2bd44d9d79a727ed35cfedbac8c4077a6220aad14",
33
33
  "src/app/api/client-events/route.ts": "13d545537b7e8ce421e6169d25c105adf2a2de3d978ae0a2c6751ff5f7d2eb33",
@@ -57,7 +57,7 @@
57
57
  "src/lib/storage.ts": "ae3b85fc6cccd39d4174a391dcbe6e91fb9460eb407ec9dbfedd63594a441d08",
58
58
  "src/lib/token.ts": "518898ca3cbba069f507f736ed80f5afa0c5af07b0b02fdd2d682e466598c803",
59
59
  "src/lib/utils.ts": "d1f1e0d62cb8d8d1e04c26e14de842d8a151f75812d81b046c65b5d1fe8e4b27",
60
- "src/middleware.ts": "5623ef170d9327e47373943eae33a8b07a6dda970525d0833e0865e4429df094",
60
+ "src/middleware.ts": "5925c0b1ad801a201e82c26ccd6c0642717670446d1503e9ed82dcf3ea03c825",
61
61
  "src/types/adm-zip.d.ts": "a9a32ea84d6d6cd89626ba5cd6f5519158a652362abbe5647474114c30ecc3c4",
62
62
  "tailwind.config.ts": "dc0dec22249b290b857190495ce7140327a6f4d94fedd5dffcdb298fa3928071",
63
63
  "tests/.gitkeep": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
@@ -1 +1 @@
1
- 0.2.6
1
+ 0.2.7
@@ -1,19 +1,92 @@
1
- "use client";
2
-
3
1
  import { PortalLogin } from "@/components/portal-login";
2
+ import { getInitialLogo } from "@/lib/branding";
3
+ import { prisma } from "@/lib/db";
4
+ import { resolveDefaultOrganizationId } from "@/lib/client-portals";
5
+ import { getRuntimePortalBySlug, getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
6
+
7
+ export default async function ClientLogin({
8
+ searchParams,
9
+ }: {
10
+ searchParams?: Promise<{ portal?: string }>;
11
+ }) {
12
+ const params = (await searchParams) ?? {};
13
+ const portalSlug = params.portal?.trim() || null;
14
+
15
+ let companyName = "Your Portal";
16
+ let companyUrl = "https://showpane.com";
17
+ let supportEmail = "support@showpane.com";
18
+ let portalLabel = "Client Portal";
19
+ let description = "Private portal access. Sign in with the credentials you were sent.";
20
+ let companyInitial = "P";
21
+
22
+ if (portalSlug) {
23
+ try {
24
+ if (isRuntimeSnapshotMode()) {
25
+ const state = await getRuntimeState();
26
+ const portal = await getRuntimePortalBySlug(portalSlug);
27
+ if (portal) {
28
+ companyName = portal.companyName;
29
+ portalLabel = `${portal.companyName} Portal`;
30
+ description = `Private portal for ${portal.companyName}. Sign in with the credentials you were sent.`;
31
+ companyInitial = portal.companyName[0]?.toUpperCase() || "P";
32
+ if (state?.organization?.websiteUrl) {
33
+ companyUrl = state.organization.websiteUrl;
34
+ }
35
+ if (state?.organization?.supportEmail) {
36
+ supportEmail = state.organization.supportEmail;
37
+ }
38
+ }
39
+ } else {
40
+ const organizationId = await resolveDefaultOrganizationId();
41
+ if (organizationId) {
42
+ const portal = await prisma.clientPortal.findFirst({
43
+ where: {
44
+ organizationId,
45
+ slug: portalSlug,
46
+ isActive: true,
47
+ },
48
+ select: { companyName: true },
49
+ });
50
+ const organization = await prisma.organization.findUnique({
51
+ where: { id: organizationId },
52
+ select: {
53
+ websiteUrl: true,
54
+ supportEmail: true,
55
+ portalLabel: true,
56
+ },
57
+ });
58
+
59
+ if (portal) {
60
+ companyName = portal.companyName;
61
+ portalLabel = organization?.portalLabel || `${portal.companyName} Portal`;
62
+ description = `Private portal for ${portal.companyName}. Sign in with the credentials you were sent.`;
63
+ companyInitial = portal.companyName[0]?.toUpperCase() || "P";
64
+ }
65
+ if (organization?.websiteUrl) {
66
+ companyUrl = organization.websiteUrl;
67
+ }
68
+ if (organization?.supportEmail) {
69
+ supportEmail = organization.supportEmail;
70
+ }
71
+ }
72
+ }
73
+ } catch {
74
+ // Fall back to the generic login shell if portal-aware context cannot be resolved.
75
+ }
76
+ }
4
77
 
5
- export default function ClientLogin() {
6
78
  return (
7
79
  <PortalLogin
8
- companyName="Your Portal"
80
+ companyName={companyName}
9
81
  companyLogo={
10
82
  <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-gray-900">
11
- <span className="text-xs font-bold text-white">P</span>
83
+ <span className="text-xs font-bold text-white">{companyInitial}</span>
12
84
  </div>
13
85
  }
14
- companyUrl="https://showpane.com"
15
- supportEmail="support@showpane.com"
16
- description="Private portal access. Sign in with the credentials you were sent."
86
+ companyUrl={companyUrl}
87
+ supportEmail={supportEmail}
88
+ portalLabel={portalLabel}
89
+ description={description}
17
90
  />
18
91
  );
19
92
  }
@@ -35,18 +35,28 @@ export async function middleware(req: NextRequest) {
35
35
  try {
36
36
  const portal = await getAuthenticatedPortal(req);
37
37
  if (!portal) {
38
- return NextResponse.redirect(new URL("/client", req.url));
38
+ const loginUrl = new URL("/client", req.url);
39
+ const requestedSlug = pathname.split("/")[2];
40
+ if (requestedSlug) {
41
+ loginUrl.searchParams.set("portal", requestedSlug);
42
+ }
43
+ return NextResponse.redirect(loginUrl);
39
44
  }
40
45
  // Ensure the URL slug matches the authenticated slug
41
46
  const urlSlug = pathname.split("/")[2];
42
47
  if (urlSlug !== portal.slug) {
43
- return NextResponse.redirect(new URL("/client", req.url));
48
+ return NextResponse.redirect(new URL(`/client/${portal.slug}`, req.url));
44
49
  }
45
50
  return NextResponse.next();
46
51
  } catch (e) {
47
52
  console.error("Middleware: DB error checking auth on portal page", e);
48
53
  // Fail closed — redirect to login
49
- return NextResponse.redirect(new URL("/client", req.url));
54
+ const loginUrl = new URL("/client", req.url);
55
+ const requestedSlug = pathname.split("/")[2];
56
+ if (requestedSlug) {
57
+ loginUrl.searchParams.set("portal", requestedSlug);
58
+ }
59
+ return NextResponse.redirect(loginUrl);
50
60
  }
51
61
  }
52
62
 
@@ -1 +1 @@
1
- 0.4.17
1
+ 0.4.19
@@ -1 +1 @@
1
- 1.1.6 (requires app >= 0.2.6)
1
+ 1.1.7 (requires app >= 0.2.7)
@@ -0,0 +1,46 @@
1
+ import { PrismaClient } from "@/lib/prisma-client";
2
+
3
+ function fail(message: string): never {
4
+ console.error(JSON.stringify({ ok: false, error: message }));
5
+ process.exit(1);
6
+ }
7
+
8
+ async function main() {
9
+ const args = process.argv.slice(2);
10
+
11
+ if (args.includes("--help")) {
12
+ console.log("Usage: get-org --slug <orgSlug>");
13
+ console.log("Looks up a local Showpane organization by slug.");
14
+ process.exit(0);
15
+ }
16
+
17
+ const getArg = (flag: string) => {
18
+ const index = args.indexOf(flag);
19
+ return index !== -1 ? args[index + 1] : undefined;
20
+ };
21
+
22
+ const slug = getArg("--slug");
23
+ if (!slug) {
24
+ fail("Missing --slug");
25
+ }
26
+
27
+ const prisma = new PrismaClient();
28
+ try {
29
+ const org = await prisma.organization.findUnique({
30
+ where: { slug },
31
+ select: { id: true, slug: true, name: true },
32
+ });
33
+
34
+ if (!org) {
35
+ fail(`Organization "${slug}" not found`);
36
+ }
37
+
38
+ console.log(JSON.stringify({ ok: true, org }));
39
+ } finally {
40
+ await prisma.$disconnect();
41
+ }
42
+ }
43
+
44
+ main().catch((error) => {
45
+ fail(String(error));
46
+ });
@@ -1 +1 @@
1
- 1.1.6 (requires app >= 0.2.6)
1
+ 1.1.7 (requires app >= 0.2.7)
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-analytics","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-analytics" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -27,6 +27,10 @@ fi
27
27
  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)
28
28
  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")
29
29
  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)
30
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
31
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
32
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
33
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
30
34
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
31
35
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
32
36
  DATABASE_URL="${DATABASE_URL:-}"
@@ -67,6 +71,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
67
71
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
68
72
  echo '{"skill":"portal-create","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
69
73
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
74
+ if [ "portal-create" = "portal-deploy" ]; then
75
+ echo "ORG_SLUG: $ORG_SLUG"
76
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
77
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
78
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
79
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
80
+ fi
70
81
  echo "TELEMETRY: $TEL"
71
82
  echo "TEL_PROMPTED: $TEL_PROMPTED"
72
83
  ```
@@ -99,14 +110,48 @@ If `skills/shared/platform-constraints.md` exists, read it once near the start o
99
110
 
100
111
  ## Steps
101
112
 
102
- ### Step 1: Determine the portal slug
113
+ ### Step 1: Resolve the local organization
114
+
115
+ Resolve the organization from the configured `ORG_SLUG` before doing anything else:
116
+
117
+ ```bash
118
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
119
+ ```
120
+
121
+ The helper returns:
122
+
123
+ ```json
124
+ {"ok":true,"org":{"id":"...","slug":"...","name":"..."}}
125
+ ```
126
+
127
+ Store:
128
+
129
+ - `ORG_ID`
130
+ - `ORG_NAME`
131
+
132
+ If the helper fails, stop and tell the user to run `/portal-setup` again instead
133
+ of guessing with ad-hoc SQL.
134
+
135
+ Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
136
+ directories just to understand the project. Do not call `check-slug.ts` with
137
+ anything except `--org-id`.
138
+
139
+ The canonical references for this skill are:
140
+
141
+ - the configured `APP_PATH`
142
+ - the configured `ORG_SLUG`
143
+ - this skill file
144
+ - `$SKILL_DIR/templates/<chosen-template>/...`
145
+ - `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
146
+
147
+ ### Step 2: Determine the portal slug
103
148
 
104
149
  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.
105
150
 
106
151
  Validate the slug by running:
107
152
 
108
153
  ```bash
109
- 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>
154
+ 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"
110
155
  ```
111
156
 
112
157
  The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
@@ -120,7 +165,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
120
165
  - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
121
166
  - If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
122
167
 
123
- ### Step 2: Granola MCP integration (optional)
168
+ ### Step 3: Granola MCP integration (optional)
124
169
 
125
170
  Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
126
171
 
@@ -139,7 +184,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
139
184
 
140
185
  Never fail or block because Granola is unavailable. It is purely additive.
141
186
 
142
- ### Step 3: Template selection
187
+ ### Step 4: Template selection
143
188
 
144
189
  Ask which template to use as a starting point. Keep this brief and practical —
145
190
  the user chose `/portal-create` because they want the fast path, not a wizard.
@@ -149,10 +194,10 @@ the user chose `/portal-create` because they want the fast path, not a wizard.
149
194
  3. **onboarding** — Welcome, setup steps, resources. Best for new client onboarding.
150
195
  4. **blank** — Start from scratch with just an overview tab.
151
196
 
152
- Read the chosen template file from `$SKILL_DIR/templates/` for structural inspiration:
197
+ Read the chosen template file from the exact toolchain path for structural inspiration:
153
198
 
154
199
  ```bash
155
- cat "$SKILL_DIR/templates/sales-followup/sales-followup-client.tsx"
200
+ cat "$SKILL_DIR/templates/<chosen-template>/<chosen-template>-client.tsx"
156
201
  ```
157
202
 
158
203
  Always also read the example portal as your quality and style reference:
@@ -163,7 +208,10 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
163
208
 
164
209
  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.
165
210
 
166
- ### Step 4: Analyze transcript (if available)
211
+ Do not search the repo for templates or ask the filesystem where templates live.
212
+ Use the selected template and the exact `SKILL_DIR` path above.
213
+
214
+ ### Step 5: Analyze transcript (if available)
167
215
 
168
216
  If a transcript was provided (from Granola or pasted), analyze it to extract:
169
217
 
@@ -189,7 +237,7 @@ Extract from the transcript:
189
237
  - **Mentioned documents** (for documents tab)
190
238
  - **Services or products discussed** (for services/overview content)
191
239
 
192
- ### Step 5: Generate the portal files
240
+ ### Step 6: Generate the portal files
193
241
 
194
242
  Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
195
243
 
@@ -275,17 +323,22 @@ Import only the icons you need from `lucide-react`. Common choices:
275
323
  </details>
276
324
  ```
277
325
 
278
- ### Step 6: Create database record
326
+ ### Step 7: Create database record
279
327
 
280
328
  Run the create-portal script to register the portal in the database:
281
329
 
282
330
  ```bash
283
- 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>
331
+ 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"
284
332
  ```
285
333
 
286
- 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`.
334
+ This creates the `ClientPortal` record with the slug and company name, links it to
335
+ the Organization, and currently auto-generates initial credentials. The script
336
+ returns `username` and `password`.
337
+
338
+ Do not dump those credentials into the middle of the flow unless the user asked.
339
+ For onboarding, carry them forward quietly and show them at the access phase.
287
340
 
288
- ### Step 7: Self-review
341
+ ### Step 8: Self-review
289
342
 
290
343
  After generating the files, read them back and verify:
291
344
 
@@ -300,7 +353,7 @@ After generating the files, read them back and verify:
300
353
 
301
354
  If any check fails, fix the issue before proceeding.
302
355
 
303
- ### Step 8: Open preview
356
+ ### Step 9: Open preview
304
357
 
305
358
  Check if the dev server is running:
306
359
 
@@ -318,7 +371,7 @@ If not running, suggest:
318
371
 
319
372
  > "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
320
373
 
321
- ### Step 9: Summary and next steps
374
+ ### Step 10: Summary and next steps
322
375
 
323
376
  Print a summary:
324
377
 
@@ -331,13 +384,13 @@ Portal created: <slug>
331
384
  src/app/(portal)/client/<slug>/<slug>-client.tsx
332
385
 
333
386
  Next steps:
334
- 1. Create login credentials: /portal-credentials <slug>
335
- 2. Preview the portal: /portal-preview <slug>
336
- 3. Edit content: /portal-update <slug>
387
+ 1. Preview the portal: /portal-preview <slug>
388
+ 2. Edit content: /portal-update <slug>
389
+ 3. Rotate credentials: /portal-credentials <slug>
337
390
  4. Deploy: /portal-deploy
338
391
  ```
339
392
 
340
- ### Step 10: Record learning
393
+ ### Step 11: Record learning
341
394
 
342
395
  Append a learning about the portal creation for future reference:
343
396
 
@@ -15,14 +15,48 @@ capabilities, read `skills/shared/platform-constraints.md` and apply the relevan
15
15
 
16
16
  ## Steps
17
17
 
18
- ### Step 1: Determine the portal slug
18
+ ### Step 1: Resolve the local organization
19
+
20
+ Resolve the organization from the configured `ORG_SLUG` before doing anything else:
21
+
22
+ ```bash
23
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
24
+ ```
25
+
26
+ The helper returns:
27
+
28
+ ```json
29
+ {"ok":true,"org":{"id":"...","slug":"...","name":"..."}}
30
+ ```
31
+
32
+ Store:
33
+
34
+ - `ORG_ID`
35
+ - `ORG_NAME`
36
+
37
+ If the helper fails, stop and tell the user to run `/portal-setup` again instead
38
+ of guessing with ad-hoc SQL.
39
+
40
+ Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
41
+ directories just to understand the project. Do not call `check-slug.ts` with
42
+ anything except `--org-id`.
43
+
44
+ The canonical references for this skill are:
45
+
46
+ - the configured `APP_PATH`
47
+ - the configured `ORG_SLUG`
48
+ - this skill file
49
+ - `$SKILL_DIR/templates/<chosen-template>/...`
50
+ - `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
51
+
52
+ ### Step 2: Determine the portal slug
19
53
 
20
54
  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
55
 
22
56
  Validate the slug by running:
23
57
 
24
58
  ```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>
59
+ 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
60
  ```
27
61
 
28
62
  The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":"..."}`. If invalid:
@@ -36,7 +70,7 @@ Also ask for the client's website domain (e.g., "acme-health.com"). This is opti
36
70
  - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
37
71
  - If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
38
72
 
39
- ### Step 2: Granola MCP integration (optional)
73
+ ### Step 3: Granola MCP integration (optional)
40
74
 
41
75
  Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This is a convenience, not a requirement.
42
76
 
@@ -55,7 +89,7 @@ Try to use the Granola MCP `list_meetings` tool to fetch recent meetings. This i
55
89
 
56
90
  Never fail or block because Granola is unavailable. It is purely additive.
57
91
 
58
- ### Step 3: Template selection
92
+ ### Step 4: Template selection
59
93
 
60
94
  Ask which template to use as a starting point. Keep this brief and practical —
61
95
  the user chose `/portal-create` because they want the fast path, not a wizard.
@@ -65,10 +99,10 @@ the user chose `/portal-create` because they want the fast path, not a wizard.
65
99
  3. **onboarding** — Welcome, setup steps, resources. Best for new client onboarding.
66
100
  4. **blank** — Start from scratch with just an overview tab.
67
101
 
68
- Read the chosen template file from `$SKILL_DIR/templates/` for structural inspiration:
102
+ Read the chosen template file from the exact toolchain path for structural inspiration:
69
103
 
70
104
  ```bash
71
- cat "$SKILL_DIR/templates/sales-followup/sales-followup-client.tsx"
105
+ cat "$SKILL_DIR/templates/<chosen-template>/<chosen-template>-client.tsx"
72
106
  ```
73
107
 
74
108
  Always also read the example portal as your quality and style reference:
@@ -79,7 +113,10 @@ cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
79
113
 
80
114
  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
115
 
82
- ### Step 4: Analyze transcript (if available)
116
+ Do not search the repo for templates or ask the filesystem where templates live.
117
+ Use the selected template and the exact `SKILL_DIR` path above.
118
+
119
+ ### Step 5: Analyze transcript (if available)
83
120
 
84
121
  If a transcript was provided (from Granola or pasted), analyze it to extract:
85
122
 
@@ -105,7 +142,7 @@ Extract from the transcript:
105
142
  - **Mentioned documents** (for documents tab)
106
143
  - **Services or products discussed** (for services/overview content)
107
144
 
108
- ### Step 5: Generate the portal files
145
+ ### Step 6: Generate the portal files
109
146
 
110
147
  Create two files in `$APP_PATH/src/app/(portal)/client/<slug>/`:
111
148
 
@@ -191,17 +228,22 @@ Import only the icons you need from `lucide-react`. Common choices:
191
228
  </details>
192
229
  ```
193
230
 
194
- ### Step 6: Create database record
231
+ ### Step 7: Create database record
195
232
 
196
233
  Run the create-portal script to register the portal in the database:
197
234
 
198
235
  ```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>
236
+ 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
237
  ```
201
238
 
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`.
239
+ This creates the `ClientPortal` record with the slug and company name, links it to
240
+ the Organization, and currently auto-generates initial credentials. The script
241
+ returns `username` and `password`.
242
+
243
+ Do not dump those credentials into the middle of the flow unless the user asked.
244
+ For onboarding, carry them forward quietly and show them at the access phase.
203
245
 
204
- ### Step 7: Self-review
246
+ ### Step 8: Self-review
205
247
 
206
248
  After generating the files, read them back and verify:
207
249
 
@@ -216,7 +258,7 @@ After generating the files, read them back and verify:
216
258
 
217
259
  If any check fails, fix the issue before proceeding.
218
260
 
219
- ### Step 8: Open preview
261
+ ### Step 9: Open preview
220
262
 
221
263
  Check if the dev server is running:
222
264
 
@@ -234,7 +276,7 @@ If not running, suggest:
234
276
 
235
277
  > "Start the dev server with `/portal-dev` to preview your portal at http://localhost:3000/client/<slug>"
236
278
 
237
- ### Step 9: Summary and next steps
279
+ ### Step 10: Summary and next steps
238
280
 
239
281
  Print a summary:
240
282
 
@@ -247,13 +289,13 @@ Portal created: <slug>
247
289
  src/app/(portal)/client/<slug>/<slug>-client.tsx
248
290
 
249
291
  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>
292
+ 1. Preview the portal: /portal-preview <slug>
293
+ 2. Edit content: /portal-update <slug>
294
+ 3. Rotate credentials: /portal-credentials <slug>
253
295
  4. Deploy: /portal-deploy
254
296
  ```
255
297
 
256
- ### Step 10: Record learning
298
+ ### Step 11: Record learning
257
299
 
258
300
  Append a learning about the portal creation for future reference:
259
301
 
@@ -32,6 +32,10 @@ fi
32
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
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
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)
35
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
36
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
37
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
38
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
35
39
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
36
40
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
37
41
  DATABASE_URL="${DATABASE_URL:-}"
@@ -72,6 +76,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
72
76
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
73
77
  echo '{"skill":"portal-credentials","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
74
78
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
79
+ if [ "portal-credentials" = "portal-deploy" ]; then
80
+ echo "ORG_SLUG: $ORG_SLUG"
81
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
82
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
83
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
84
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
85
+ fi
75
86
  echo "TELEMETRY: $TEL"
76
87
  echo "TEL_PROMPTED: $TEL_PROMPTED"
77
88
  ```
@@ -32,6 +32,10 @@ fi
32
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
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
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)
35
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
36
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
37
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
38
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
35
39
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
36
40
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
37
41
  DATABASE_URL="${DATABASE_URL:-}"
@@ -72,6 +76,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
72
76
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
73
77
  echo '{"skill":"portal-delete","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
74
78
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
79
+ if [ "portal-delete" = "portal-deploy" ]; then
80
+ echo "ORG_SLUG: $ORG_SLUG"
81
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
82
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
83
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
84
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
85
+ fi
75
86
  echo "TELEMETRY: $TEL"
76
87
  echo "TEL_PROMPTED: $TEL_PROMPTED"
77
88
  ```
@@ -25,6 +25,10 @@ fi
25
25
  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)
26
26
  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")
27
27
  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)
28
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
29
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
30
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
31
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
28
32
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
29
33
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
30
34
  DATABASE_URL="${DATABASE_URL:-}"
@@ -65,6 +69,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
65
69
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
66
70
  echo '{"skill":"portal-deploy","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
67
71
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
72
+ if [ "portal-deploy" = "portal-deploy" ]; then
73
+ echo "ORG_SLUG: $ORG_SLUG"
74
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
75
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
76
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
77
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
78
+ fi
68
79
  echo "TELEMETRY: $TEL"
69
80
  echo "TEL_PROMPTED: $TEL_PROMPTED"
70
81
  ```
@@ -112,12 +123,26 @@ Check that the preamble successfully read cloud config values:
112
123
 
113
124
  ```bash
114
125
  if [ -z "$CLOUD_API_TOKEN" ]; then
115
- echo "ERROR: No API token found. Run 'showpane login' to authenticate."
126
+ echo "ERROR: No cloud access token found. Run 'showpane login' to authenticate."
116
127
  exit 1
117
128
  fi
118
129
  ```
119
130
 
120
- If the token is missing, stop and tell the user to run `showpane login`.
131
+ If the token is missing, stop and tell the user:
132
+
133
+ - `showpane login` is the right next step
134
+ - it opens browser auth for Showpane Cloud automatically
135
+ - if they need an account first:
136
+ - sign up: `https://app.showpane.com/sign-up`
137
+ - sign in: `https://app.showpane.com/sign-in`
138
+
139
+ If they are already inside Claude Code, explicitly suggest:
140
+
141
+ ```text
142
+ ! showpane login
143
+ ```
144
+
145
+ Then resume deploy after login finishes.
121
146
 
122
147
  #### 1b. Verify Showpane Cloud is reachable
123
148
 
@@ -28,12 +28,26 @@ Check that the preamble successfully read cloud config values:
28
28
 
29
29
  ```bash
30
30
  if [ -z "$CLOUD_API_TOKEN" ]; then
31
- echo "ERROR: No API token found. Run 'showpane login' to authenticate."
31
+ echo "ERROR: No cloud access token found. Run 'showpane login' to authenticate."
32
32
  exit 1
33
33
  fi
34
34
  ```
35
35
 
36
- If the token is missing, stop and tell the user to run `showpane login`.
36
+ If the token is missing, stop and tell the user:
37
+
38
+ - `showpane login` is the right next step
39
+ - it opens browser auth for Showpane Cloud automatically
40
+ - if they need an account first:
41
+ - sign up: `https://app.showpane.com/sign-up`
42
+ - sign in: `https://app.showpane.com/sign-in`
43
+
44
+ If they are already inside Claude Code, explicitly suggest:
45
+
46
+ ```text
47
+ ! showpane login
48
+ ```
49
+
50
+ Then resume deploy after login finishes.
37
51
 
38
52
  #### 1b. Verify Showpane Cloud is reachable
39
53
 
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-dev","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-dev" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-list","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-list" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -67,17 +67,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
67
67
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
68
68
  echo '{"skill":"portal-onboard","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
69
69
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE"
70
- echo "CONFIG_PRESENT: $CONFIG_PRESENT"
71
- echo "APP_PATH: ${APP_PATH:-missing}"
72
- echo "ORG_SLUG: ${ORG_SLUG:-missing}"
73
- echo "TELEMETRY: $TEL"
74
- echo "TEL_PROMPTED: $TEL_PROMPTED"
70
+ echo "WORKSPACE: ${APP_PATH:+ready}${APP_PATH:-missing}"
71
+ echo "ORG: ${ORG_SLUG:-missing}"
75
72
  if [ -f "$CHECKPOINT" ]; then
76
- echo "CHECKPOINT_PRESENT: true"
77
- echo "CHECKPOINT_DATA:"
73
+ echo "CHECKPOINT: present"
78
74
  cat "$CHECKPOINT"
79
75
  else
80
- echo "CHECKPOINT_PRESENT: false"
76
+ echo "CHECKPOINT: missing"
81
77
  fi
82
78
  ```
83
79
 
@@ -256,12 +252,33 @@ Save checkpoint with:
256
252
  Run the `portal-create` flow inline, but frame it as part of the wizard, not as
257
253
  a separate command.
258
254
 
255
+ Before you do, resolve the local organization with the canonical helper and keep
256
+ that result in hand:
257
+
258
+ ```bash
259
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
260
+ ```
261
+
262
+ Use the returned `org.id` as the canonical org id for the rest of the create flow.
263
+ Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
264
+
265
+ Also: do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or
266
+ template directories just to reorient yourself. At this point the canonical inputs
267
+ are already known:
268
+
269
+ - workspace app path
270
+ - org slug
271
+ - selected client company name
272
+ - selected template
273
+ - optional transcript source
274
+
259
275
  During this phase:
260
276
 
261
277
  - suggest a slug if needed
262
278
  - prefer a real company/client name in the portal
263
279
  - prefer useful structure over completeness
264
280
  - aim for a credible first draft, not a perfect final artifact
281
+ - do not re-ask for the company name or template if the user already chose them
265
282
 
266
283
  If the slug is taken, treat that as a soft retry, not a failure.
267
284
 
@@ -293,6 +310,16 @@ Run the `portal-preview` flow inline.
293
310
 
294
311
  If no dev server is running, start it using the `portal-dev` instructions first.
295
312
 
313
+ If the create step already generated local credentials, show them before opening
314
+ the preview link. Keep it simple:
315
+
316
+ - username
317
+ - password
318
+ - one sentence saying these are for local preview right now
319
+
320
+ Do this before opening the browser so the user is not dropped onto a login screen
321
+ without the credentials they need.
322
+
296
323
  Tell the user exactly what to inspect:
297
324
 
298
325
  - does the overall story feel right?
@@ -316,10 +343,14 @@ Recommended first-run choice:
316
343
  Important rule: share links still require portal credentials to exist, because
317
344
  share links are tied to credential versioning.
318
345
 
346
+ The `create-portal` flow currently creates initial credentials automatically. If
347
+ you already have a generated username/password from that step, do not immediately
348
+ re-run `portal-credentials` unless the user wants to rotate or replace them.
349
+
319
350
  So the flow is:
320
351
 
321
- 1. run `portal-credentials` inline
322
- 2. show the credentials once
352
+ 1. if credentials were already generated during create, show them once here
353
+ 2. if credentials do not exist or need changing, run `portal-credentials` inline
323
354
  3. explain that external sharing starts with `portal-deploy`
324
355
  4. if the user wants a direct hosted access link after publish, plan to run `portal-share`
325
356
 
@@ -159,12 +159,33 @@ Save checkpoint with:
159
159
  Run the `portal-create` flow inline, but frame it as part of the wizard, not as
160
160
  a separate command.
161
161
 
162
+ Before you do, resolve the local organization with the canonical helper and keep
163
+ that result in hand:
164
+
165
+ ```bash
166
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/get-org.ts" --slug "$ORG_SLUG"
167
+ ```
168
+
169
+ Use the returned `org.id` as the canonical org id for the rest of the create flow.
170
+ Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
171
+
172
+ Also: do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or
173
+ template directories just to reorient yourself. At this point the canonical inputs
174
+ are already known:
175
+
176
+ - workspace app path
177
+ - org slug
178
+ - selected client company name
179
+ - selected template
180
+ - optional transcript source
181
+
162
182
  During this phase:
163
183
 
164
184
  - suggest a slug if needed
165
185
  - prefer a real company/client name in the portal
166
186
  - prefer useful structure over completeness
167
187
  - aim for a credible first draft, not a perfect final artifact
188
+ - do not re-ask for the company name or template if the user already chose them
168
189
 
169
190
  If the slug is taken, treat that as a soft retry, not a failure.
170
191
 
@@ -196,6 +217,16 @@ Run the `portal-preview` flow inline.
196
217
 
197
218
  If no dev server is running, start it using the `portal-dev` instructions first.
198
219
 
220
+ If the create step already generated local credentials, show them before opening
221
+ the preview link. Keep it simple:
222
+
223
+ - username
224
+ - password
225
+ - one sentence saying these are for local preview right now
226
+
227
+ Do this before opening the browser so the user is not dropped onto a login screen
228
+ without the credentials they need.
229
+
199
230
  Tell the user exactly what to inspect:
200
231
 
201
232
  - does the overall story feel right?
@@ -219,10 +250,14 @@ Recommended first-run choice:
219
250
  Important rule: share links still require portal credentials to exist, because
220
251
  share links are tied to credential versioning.
221
252
 
253
+ The `create-portal` flow currently creates initial credentials automatically. If
254
+ you already have a generated username/password from that step, do not immediately
255
+ re-run `portal-credentials` unless the user wants to rotate or replace them.
256
+
222
257
  So the flow is:
223
258
 
224
- 1. run `portal-credentials` inline
225
- 2. show the credentials once
259
+ 1. if credentials were already generated during create, show them once here
260
+ 2. if credentials do not exist or need changing, run `portal-credentials` inline
226
261
  3. explain that external sharing starts with `portal-deploy`
227
262
  4. if the user wants a direct hosted access link after publish, plan to run `portal-share`
228
263
 
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-preview","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-preview" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -25,6 +25,10 @@ fi
25
25
  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)
26
26
  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")
27
27
  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)
28
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
29
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
30
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
31
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
28
32
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
29
33
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
30
34
  DATABASE_URL="${DATABASE_URL:-}"
@@ -65,6 +69,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
65
69
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
66
70
  echo '{"skill":"portal-share","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
67
71
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
72
+ if [ "portal-share" = "portal-deploy" ]; then
73
+ echo "ORG_SLUG: $ORG_SLUG"
74
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
75
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
76
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
77
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
78
+ fi
68
79
  echo "TELEMETRY: $TEL"
69
80
  echo "TEL_PROMPTED: $TEL_PROMPTED"
70
81
  ```
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-status","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-status" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-update","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-update" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-upgrade","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-upgrade" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
@@ -22,6 +22,10 @@ fi
22
22
  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)
23
23
  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")
24
24
  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)
25
+ CLOUD_API_TOKEN=$("$SHOWPANE_BIN/showpane-config" get accessToken 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('accessToken',''))" 2>/dev/null || true)
26
+ CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
27
+ CLOUD_ORG_SLUG="${ORG_SLUG:-}"
28
+ CLOUD_PORTAL_URL=$("$SHOWPANE_BIN/showpane-config" get portalUrl 2>/dev/null || python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('portalUrl',''))" 2>/dev/null || true)
25
29
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
26
30
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
27
31
  DATABASE_URL="${DATABASE_URL:-}"
@@ -62,6 +66,13 @@ SHOWPANE_TIMELINE="$SHOWPANE_HOME/timeline.jsonl"
62
66
  mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
63
67
  echo '{"skill":"portal-verify","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
64
68
  echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
69
+ if [ "portal-verify" = "portal-deploy" ]; then
70
+ echo "ORG_SLUG: $ORG_SLUG"
71
+ echo "CLOUD_API_TOKEN: ${CLOUD_API_TOKEN:+present}${CLOUD_API_TOKEN:-missing}"
72
+ echo "CLOUD_API_BASE: ${CLOUD_API_BASE:-missing}"
73
+ echo "CLOUD_ORG_SLUG: ${CLOUD_ORG_SLUG:-missing}"
74
+ echo "CLOUD_PORTAL_URL: ${CLOUD_PORTAL_URL:-missing}"
75
+ fi
65
76
  echo "TELEMETRY: $TEL"
66
77
  echo "TEL_PROMPTED: $TEL_PROMPTED"
67
78
  ```
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ var GREEN = "\x1B[32m";
32
32
  var BLUE = "\x1B[34m";
33
33
  var WHITE = "\x1B[37m";
34
34
  var RED = "\x1B[31m";
35
- var API_BASE = "https://app.showpane.com";
35
+ var API_BASE = process.env.SHOWPANE_CLOUD_URL || "https://app.showpane.com";
36
36
  var ORGANIZATION_REQUIRED_ERROR = "organization_required";
37
37
  var ORGANIZATION_NOT_READY_ERROR = "organization_not_ready";
38
38
  var SHOWPANE_HOME = join(homedir(), ".showpane");
@@ -62,10 +62,10 @@ function error(message) {
62
62
  console.error(` ${RED}\u2717${RESET} ${message}`);
63
63
  }
64
64
  function printCreateUsage() {
65
- console.log("Usage: showpane [--yes --name <company>] [--no-open] [--verbose]");
65
+ console.log("Usage: showpane [--yes --name <company> --full-name <name> --work-email <email> [--website <domain>]] [--no-open] [--verbose]");
66
66
  }
67
67
  function printClaudeUsage() {
68
- console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company>] [--verbose]");
68
+ console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company> --full-name <name> --work-email <email> [--website <domain>]] [--verbose]");
69
69
  }
70
70
  function printBanner() {
71
71
  const banner = `
@@ -120,10 +120,37 @@ function parseCreateArgs(args) {
120
120
  index += 1;
121
121
  continue;
122
122
  }
123
+ if (arg === "--full-name") {
124
+ const value = args[index + 1];
125
+ if (!value || value.startsWith("--")) {
126
+ throw new Error("Missing value for --full-name.");
127
+ }
128
+ options.contactName = value.trim();
129
+ index += 1;
130
+ continue;
131
+ }
132
+ if (arg === "--work-email") {
133
+ const value = args[index + 1];
134
+ if (!value || value.startsWith("--")) {
135
+ throw new Error("Missing value for --work-email.");
136
+ }
137
+ options.contactEmail = value.trim();
138
+ index += 1;
139
+ continue;
140
+ }
141
+ if (arg === "--website") {
142
+ const value = args[index + 1];
143
+ if (!value || value.startsWith("--")) {
144
+ throw new Error("Missing value for --website.");
145
+ }
146
+ options.websiteUrl = value.trim();
147
+ index += 1;
148
+ continue;
149
+ }
123
150
  throw new Error(`Unknown argument: ${arg}`);
124
151
  }
125
- if (options.yes && !options.companyName) {
126
- throw new Error("`--yes` requires `--name <company>` for a non-interactive install.");
152
+ if (options.yes && (!options.companyName || !options.contactName || !options.contactEmail)) {
153
+ throw new Error("`--yes` requires `--name <company> --full-name <name> --work-email <email>` for a non-interactive install.");
127
154
  }
128
155
  return options;
129
156
  }
@@ -1257,9 +1284,9 @@ async function createProject(args) {
1257
1284
  blue("Let's get some basic info to set up your account.");
1258
1285
  console.log();
1259
1286
  }
1260
- const contactName = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your full name?${RESET} `);
1261
- const contactEmail = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your work email?${RESET} `);
1262
- const websiteUrl = options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your company website or domain?${RESET} `);
1287
+ const contactName = options.contactName ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your full name?${RESET} `));
1288
+ const contactEmail = options.contactEmail ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your work email?${RESET} `));
1289
+ const websiteUrl = options.websiteUrl ?? (options.yes || !process.stdin.isTTY || !process.stdout.isTTY ? "" : await ask(` ${BOLD}What's your company website or domain?${RESET} `));
1263
1290
  const slug = toSlug(companyName);
1264
1291
  const dirName = `showpane-${slug}`;
1265
1292
  const projectRoot = resolve(process.cwd(), dirName);
@@ -1529,7 +1556,15 @@ async function login() {
1529
1556
  ensureShowpaneShim();
1530
1557
  blue("Authenticating with Showpane...");
1531
1558
  console.log();
1532
- const initRes = await fetch(`${API_BASE}/api/cli/init`, { method: "POST" });
1559
+ let initRes;
1560
+ try {
1561
+ initRes = await fetch(`${API_BASE}/api/cli/init`, { method: "POST" });
1562
+ } catch (errorLike) {
1563
+ const message = errorLike instanceof Error ? errorLike.message : String(errorLike);
1564
+ throw new Error(
1565
+ `Could not reach Showpane Cloud at ${API_BASE}. Check your internet connection, VPN/proxy settings, or try again in a moment. (${message})`
1566
+ );
1567
+ }
1533
1568
  if (!initRes.ok) {
1534
1569
  throw new Error(`Failed to start auth flow (${initRes.status})`);
1535
1570
  }
@@ -1589,12 +1624,21 @@ if (process.argv.includes("--version")) {
1589
1624
  console.log(getPackageVersion(packageRoot));
1590
1625
  process.exit(0);
1591
1626
  }
1627
+ if (process.argv.length <= 2 && process.argv.includes("--help")) {
1628
+ printCreateUsage();
1629
+ console.log("Commands: claude, login, projects, sync, upgrade");
1630
+ process.exit(0);
1631
+ }
1592
1632
  if (command === "login") {
1593
1633
  login().catch((err) => {
1594
1634
  error(String(err));
1595
1635
  process.exit(1);
1596
1636
  });
1597
1637
  } else if (command === "claude") {
1638
+ if (process.argv.includes("--help")) {
1639
+ printClaudeUsage();
1640
+ process.exit(0);
1641
+ }
1598
1642
  openClaude(process.argv.slice(3)).catch((err) => {
1599
1643
  error(String(err));
1600
1644
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {