showpane 0.4.25 → 0.4.27

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 (32) hide show
  1. package/bundle/meta/scaffold-manifest.json +6 -5
  2. package/bundle/scaffold/prisma/schema.local.prisma +1 -0
  3. package/bundle/scaffold/src/__tests__/branding.test.ts +43 -0
  4. package/bundle/scaffold/src/app/(portal)/client/page.tsx +42 -15
  5. package/bundle/scaffold/src/lib/branding.ts +47 -0
  6. package/bundle/scaffold/src/lib/portal-contracts.ts +1 -0
  7. package/bundle/toolchain/CLI_VERSION +1 -1
  8. package/bundle/toolchain/bin/create-portal.ts +50 -11
  9. package/bundle/toolchain/bin/export-runtime-state.ts +1 -0
  10. package/bundle/toolchain/skills/portal-analytics/SKILL.md +2 -2
  11. package/bundle/toolchain/skills/portal-create/SKILL.md +17 -11
  12. package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +15 -9
  13. package/bundle/toolchain/skills/portal-credentials/SKILL.md +2 -2
  14. package/bundle/toolchain/skills/portal-delete/SKILL.md +2 -2
  15. package/bundle/toolchain/skills/portal-deploy/SKILL.md +6 -3
  16. package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +4 -1
  17. package/bundle/toolchain/skills/portal-dev/SKILL.md +2 -2
  18. package/bundle/toolchain/skills/portal-list/SKILL.md +2 -2
  19. package/bundle/toolchain/skills/portal-onboard/SKILL.md +33 -29
  20. package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +31 -27
  21. package/bundle/toolchain/skills/portal-preview/SKILL.md +9 -6
  22. package/bundle/toolchain/skills/portal-preview/SKILL.md.tmpl +7 -4
  23. package/bundle/toolchain/skills/portal-setup/SKILL.md +8 -12
  24. package/bundle/toolchain/skills/portal-setup/SKILL.md.tmpl +6 -10
  25. package/bundle/toolchain/skills/portal-share/SKILL.md +6 -7
  26. package/bundle/toolchain/skills/portal-share/SKILL.md.tmpl +4 -5
  27. package/bundle/toolchain/skills/portal-status/SKILL.md +2 -2
  28. package/bundle/toolchain/skills/portal-update/SKILL.md +2 -2
  29. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +2 -2
  30. package/bundle/toolchain/skills/portal-verify/SKILL.md +2 -2
  31. package/dist/index.js +6 -2
  32. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-13T22:00:52.340Z",
3
+ "generatedAt": "2026-04-13T22:57:26.230Z",
4
4
  "scaffoldVersion": "0.2.7",
5
5
  "files": {
6
6
  ".env.example": "ed105f2bdcd1888a98181d55e3c9f7d6eff3ae9c3e2366c2e777a12e3caddfa7",
@@ -11,7 +11,7 @@
11
11
  "package.json": "b095e17e7fc181c630e87fe9f473c5a4ef969afcd4b110f9f9c6d6a6d93f1c0b",
12
12
  "postcss.config.js": "fa650b380adfabb151a0b352f7135e107e6352345f899060f1c5c231228f94bf",
13
13
  "prisma.config.ts": "36f56fd74eae70632e484443e38d08665158d72d5c978dc456651d8d5e1a636e",
14
- "prisma/schema.local.prisma": "f5d6f3cb17d6d229f46ef82eef7c0ff4261596924f0173fef075ac394f423073",
14
+ "prisma/schema.local.prisma": "90b9bc60e31a968137a574f156ad83ed5e46a45a8d6080b40126118806b9cd34",
15
15
  "prisma/seed.ts": "01b4295296676ab415ab2b30b4e39e13c3fdd30c3bab2b22a7cca672d1c2ea90",
16
16
  "public/example-avatar.svg": "0edeb0d3fbefa89cc27ffe6564d20e3ee0fd073cb6d9f2a025248ef3b3f277fd",
17
17
  "public/example-logo.svg": "bc5cd933aff2a17698dee66a7b4ea940ad12238e9d813474d643b459b1e8d6da",
@@ -19,6 +19,7 @@
19
19
  "scripts/prisma-db-push.mjs": "76ac85fe65b5dc3d9cc7432e44618fcc84b7443574c8d88198d01f13ac23c040",
20
20
  "scripts/prisma-generate.mjs": "d371e63388fa39f963b7c3c7cb8f87e0d9cd43cbf69d254b999108e29b8738c8",
21
21
  "scripts/prisma-schema.mjs": "0a86cc1b5f84120948aed8f97a84f2d5b173f91a43ea34ad6767441894121d83",
22
+ "src/__tests__/branding.test.ts": "dffbb3744705bc378ad91b9d7b8868004ffeaa45623f4748e1fed951096b9e3f",
22
23
  "src/__tests__/client-portals.test.ts": "9c3236bf0f7190b7d5ba9082287dcb29bc00d28dd63782a89505125ead06c624",
23
24
  "src/__tests__/deploy-bundle.test.ts": "abd3216170f306c09df6abb0d2afad966a5741e8859f25a310a0a09693d37609",
24
25
  "src/__tests__/portal-contracts.test.ts": "a3ecf29c460a14f22ef48449fc9de5b945ce5f03423f12380d05e01159d1e3b4",
@@ -27,7 +28,7 @@
27
28
  "src/app/(portal)/client/example/example-client.tsx": "ed32b111acea861f448d865338f8841d47c6ca7c2f87ed30d85bb0804940d4ec",
28
29
  "src/app/(portal)/client/example/page.tsx": "f330864f63c9feea76c8a62c3eba3ce57578627e0d4abd929fd7fefdfc7af058",
29
30
  "src/app/(portal)/client/layout.tsx": "4f43871510408a81da229d48ae316ec1d1c1beda93121922246300a2c8fd0999",
30
- "src/app/(portal)/client/page.tsx": "7efe97c047e9ad8f5d9034fe616c0c915277a9cb92fa4ce57fe35af672c80320",
31
+ "src/app/(portal)/client/page.tsx": "cf0d1b0517aadf1bfbfc91552d0c13201a140c31d2df9e08e1c62a5d48e59189",
31
32
  "src/app/api/client-auth/route.ts": "ce1858559b1e944d5b1dc719d1f03bebf66286671700b1b5397382109f0f1e0d",
32
33
  "src/app/api/client-auth/share/route.ts": "ed82414212dcd26af8c6c0f2bd44d9d79a727ed35cfedbac8c4077a6220aad14",
33
34
  "src/app/api/client-events/route.ts": "13d545537b7e8ce421e6169d25c105adf2a2de3d978ae0a2c6751ff5f7d2eb33",
@@ -43,7 +44,7 @@
43
44
  "src/components/portal-login.tsx": "8b0d91bb28674e1102fd2e5b5ddcc3a93755dd806fbd3d1b2dbea2646cffca5e",
44
45
  "src/components/portal-shell.tsx": "f46a0f753a4a0318f06c8b4e46295febb84b03ea082c95057a6da50c737b4e21",
45
46
  "src/lib/abuse-controls.ts": "d79d58d93267aca48ad0b7b9b91f753c9a3c27263e4e98daf768a950c44a6fc6",
46
- "src/lib/branding.ts": "cc55f40e02bc3e486b227988f95739ca1cda8012c97b591295995eb4465efd57",
47
+ "src/lib/branding.ts": "f0fa242c285610105c27fcaf76828f1892c8051fc2db5c36bdbe92ea916aa4bd",
47
48
  "src/lib/client-auth.ts": "b9bdfe77dbe5d6ec6c6a930627fc43d3253f0d76fd8fc4093af5a75742bebe42",
48
49
  "src/lib/client-portals.ts": "9b531f9a9ea459b4ab85257b9dd282874fa1422838fe89d511940e417114216a",
49
50
  "src/lib/control-plane.ts": "e0cf39f28ec7de715fd5cfbb5f4240773fcd3d775cd1677588dd749fff740a0e",
@@ -51,7 +52,7 @@
51
52
  "src/lib/deploy-bundle.ts": "e9675cccb2c802e408639481986c6b629737541853e1c93f322c08a5b9dfc5f9",
52
53
  "src/lib/files.ts": "24fd8d1d53c180d62441019395fb140ba3baa28311918ac488284adcdda8eb9a",
53
54
  "src/lib/load-app-env.ts": "78b80e17d896885f0d72315ee9a6cf7a0a8c6c08171f26e3d599bb9b2e8afeee",
54
- "src/lib/portal-contracts.ts": "e3521b117239d0b0f9bc86a08607c6bb26f93dc1c47cd2dcf0485c2ca7358835",
55
+ "src/lib/portal-contracts.ts": "927695c64d4f7b56be9e7c0bc37fac105d37558404df8eac09c6f0d1a8f27669",
55
56
  "src/lib/prisma-client.ts": "28cd100129a0178a6c8fdfe49e6997b19983fcc427b9fa7caee3ac26226e5eb3",
56
57
  "src/lib/runtime-state.ts": "3d30de7dfeaaa48d8b6fd5d29976ecd001408172100c95b063d5d804fdce0a2e",
57
58
  "src/lib/storage.ts": "ae3b85fc6cccd39d4174a391dcbe6e91fb9460eb407ec9dbfedd63594a441d08",
@@ -69,6 +69,7 @@ model ClientPortal {
69
69
  organizationId String
70
70
  slug String
71
71
  companyName String
72
+ websiteUrl String?
72
73
  logoUrl String?
73
74
 
74
75
  // Auth
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ getBrandLogoUrl,
5
+ getDomainFromWebsite,
6
+ getLogoUrl,
7
+ normalizeWebsiteUrl,
8
+ } from "@/lib/branding";
9
+
10
+ describe("branding helpers", () => {
11
+ it("normalizes bare domains into https URLs", () => {
12
+ expect(normalizeWebsiteUrl("bidgen.io")).toBe("https://bidgen.io/");
13
+ expect(normalizeWebsiteUrl("https://bidgen.io")).toBe("https://bidgen.io/");
14
+ });
15
+
16
+ it("extracts hostnames from bare domains and full URLs", () => {
17
+ expect(getDomainFromWebsite("bidgen.io")).toBe("bidgen.io");
18
+ expect(getDomainFromWebsite("https://www.bidgen.io/path")).toBe("www.bidgen.io");
19
+ });
20
+
21
+ it("prefers a stored logo URL, then website-derived logos, then initials", () => {
22
+ expect(
23
+ getBrandLogoUrl({
24
+ logoUrl: "https://cdn.example/logo.png",
25
+ websiteUrl: "bidgen.io",
26
+ fallbackName: "Bidgen",
27
+ }),
28
+ ).toBe("https://cdn.example/logo.png");
29
+
30
+ expect(
31
+ getBrandLogoUrl({
32
+ websiteUrl: "bidgen.io",
33
+ fallbackName: "Bidgen",
34
+ }),
35
+ ).toBe(getLogoUrl("bidgen.io", "Bidgen"));
36
+
37
+ expect(
38
+ getBrandLogoUrl({
39
+ fallbackName: "Bidgen",
40
+ }),
41
+ ).toBe(getLogoUrl("", "Bidgen"));
42
+ });
43
+ });
@@ -1,5 +1,5 @@
1
1
  import { PortalLogin } from "@/components/portal-login";
2
- import { getInitialLogo } from "@/lib/branding";
2
+ import { getBrandLogoUrl } from "@/lib/branding";
3
3
  import { prisma } from "@/lib/db";
4
4
  import { resolveDefaultOrganizationId } from "@/lib/client-portals";
5
5
  import { getRuntimePortalBySlug, getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
@@ -12,12 +12,13 @@ export default async function ClientLogin({
12
12
  const params = (await searchParams) ?? {};
13
13
  const portalSlug = params.portal?.trim() || null;
14
14
 
15
- let companyName = "Your Portal";
15
+ let companyName = "Showpane";
16
16
  let companyUrl = "https://showpane.com";
17
17
  let supportEmail = "support@showpane.com";
18
18
  let portalLabel = "Client Portal";
19
19
  let description = "Private portal access. Sign in with the credentials you were sent.";
20
- let companyInitial = "P";
20
+ let companyLogoSrc: string | null = null;
21
+ let companyLogoAlt = companyName;
21
22
 
22
23
  if (portalSlug) {
23
24
  try {
@@ -25,10 +26,16 @@ export default async function ClientLogin({
25
26
  const state = await getRuntimeState();
26
27
  const portal = await getRuntimePortalBySlug(portalSlug);
27
28
  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";
29
+ const orgName = state?.organization?.name || companyName;
30
+ companyName = orgName;
31
+ companyLogoAlt = orgName;
32
+ portalLabel = state?.organization?.portalLabel || `${orgName} Portal`;
33
+ description = `Private portal created by ${orgName} for ${portal.companyName}. Sign in with the credentials you were sent.`;
34
+ companyLogoSrc = getBrandLogoUrl({
35
+ logoUrl: state?.organization?.logoUrl,
36
+ websiteUrl: state?.organization?.websiteUrl,
37
+ fallbackName: orgName,
38
+ });
32
39
  if (state?.organization?.websiteUrl) {
33
40
  companyUrl = state.organization.websiteUrl;
34
41
  }
@@ -45,11 +52,15 @@ export default async function ClientLogin({
45
52
  slug: portalSlug,
46
53
  isActive: true,
47
54
  },
48
- select: { companyName: true },
55
+ select: {
56
+ companyName: true,
57
+ },
49
58
  });
50
59
  const organization = await prisma.organization.findUnique({
51
60
  where: { id: organizationId },
52
61
  select: {
62
+ name: true,
63
+ logoUrl: true,
53
64
  websiteUrl: true,
54
65
  supportEmail: true,
55
66
  portalLabel: true,
@@ -57,10 +68,16 @@ export default async function ClientLogin({
57
68
  });
58
69
 
59
70
  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";
71
+ const orgName = organization?.name || companyName;
72
+ companyName = orgName;
73
+ companyLogoAlt = orgName;
74
+ portalLabel = organization?.portalLabel || `${orgName} Portal`;
75
+ description = `Private portal created by ${orgName} for ${portal.companyName}. Sign in with the credentials you were sent.`;
76
+ companyLogoSrc = getBrandLogoUrl({
77
+ logoUrl: organization?.logoUrl,
78
+ websiteUrl: organization?.websiteUrl,
79
+ fallbackName: orgName,
80
+ });
64
81
  }
65
82
  if (organization?.websiteUrl) {
66
83
  companyUrl = organization.websiteUrl;
@@ -79,9 +96,19 @@ export default async function ClientLogin({
79
96
  <PortalLogin
80
97
  companyName={companyName}
81
98
  companyLogo={
82
- <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-gray-900">
83
- <span className="text-xs font-bold text-white">{companyInitial}</span>
84
- </div>
99
+ companyLogoSrc ? (
100
+ <img
101
+ src={companyLogoSrc}
102
+ alt={companyLogoAlt}
103
+ className="h-7 w-7 rounded-lg object-cover"
104
+ />
105
+ ) : (
106
+ <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-gray-900">
107
+ <span className="text-xs font-bold text-white">
108
+ {companyName[0]?.toUpperCase() || "S"}
109
+ </span>
110
+ </div>
111
+ )
85
112
  }
86
113
  companyUrl={companyUrl}
87
114
  supportEmail={supportEmail}
@@ -4,6 +4,36 @@
4
4
  * All functions return sensible defaults on failure.
5
5
  */
6
6
 
7
+ /**
8
+ * Normalize a website or bare domain into an https URL.
9
+ */
10
+ export function normalizeWebsiteUrl(value?: string | null): string | null {
11
+ if (!value) return null;
12
+ const trimmed = value.trim();
13
+ if (!trimmed) return null;
14
+
15
+ const candidate = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
16
+ try {
17
+ return new URL(candidate).toString();
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Extract a hostname from a website URL or bare domain.
25
+ */
26
+ export function getDomainFromWebsite(value?: string | null): string | null {
27
+ const normalized = normalizeWebsiteUrl(value);
28
+ if (!normalized) return null;
29
+
30
+ try {
31
+ return new URL(normalized).hostname;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
7
37
  /**
8
38
  * Fetch a company logo URL from a domain.
9
39
  * Uses Clearbit Logo API (free, no key required).
@@ -19,6 +49,23 @@ export function getLogoUrl(domain: string, fallbackName?: string): string {
19
49
  return `https://ui-avatars.com/api/?name=${initial}&background=111827&color=fff&size=128&bold=true`;
20
50
  }
21
51
 
52
+ /**
53
+ * Resolve the best available logo source for a brand.
54
+ * Prefers a stored logo URL, then a website-derived domain, then initials.
55
+ */
56
+ export function getBrandLogoUrl(options: {
57
+ logoUrl?: string | null;
58
+ websiteUrl?: string | null;
59
+ fallbackName: string;
60
+ }): string {
61
+ if (options.logoUrl) {
62
+ return options.logoUrl;
63
+ }
64
+
65
+ const domain = getDomainFromWebsite(options.websiteUrl);
66
+ return getLogoUrl(domain ?? "", options.fallbackName);
67
+ }
68
+
22
69
  /**
23
70
  * Fetch a Gravatar URL for an email address.
24
71
  * Falls back to UI Avatars if no Gravatar exists.
@@ -87,6 +87,7 @@ export interface PortalFileSyncManifestPayload {
87
87
  export interface RuntimePortalSnapshot {
88
88
  slug: string;
89
89
  companyName: string;
90
+ websiteUrl?: string | null;
90
91
  logoUrl?: string | null;
91
92
  username: string;
92
93
  passwordHash: string;
@@ -1 +1 @@
1
- 0.4.25
1
+ 0.4.27
@@ -1,4 +1,5 @@
1
1
  import { PrismaClient } from "@/lib/prisma-client";
2
+ import { getBrandLogoUrl, normalizeWebsiteUrl } from "@/lib/branding";
2
3
  import bcrypt from "bcryptjs";
3
4
  import crypto from "node:crypto";
4
5
 
@@ -16,7 +17,7 @@ async function main() {
16
17
  const args = process.argv.slice(2);
17
18
 
18
19
  if (args.includes("--help")) {
19
- console.log("Usage: create-portal --slug <slug> --company <name> --org-id <orgId>");
20
+ console.log("Usage: create-portal --slug <slug> --company <name> --org-id <orgId> [--website <domain-or-url>]");
20
21
  console.log("Creates a new client portal with auto-generated credentials.");
21
22
  process.exit(0);
22
23
  }
@@ -25,6 +26,7 @@ async function main() {
25
26
  const slug = getArg("--slug");
26
27
  const company = getArg("--company");
27
28
  const orgId = getArg("--org-id");
29
+ const website = getArg("--website");
28
30
 
29
31
  if (!slug || !company || !orgId) fail("Missing --slug, --company, or --org-id");
30
32
 
@@ -37,6 +39,11 @@ async function main() {
37
39
  const username = slug;
38
40
  const password = crypto.randomBytes(16).toString("base64url");
39
41
  const passwordHash = await bcrypt.hash(password, 10);
42
+ const websiteUrl = normalizeWebsiteUrl(website);
43
+ const logoUrl = getBrandLogoUrl({
44
+ websiteUrl,
45
+ fallbackName: company,
46
+ });
40
47
 
41
48
  const prisma = new PrismaClient();
42
49
  try {
@@ -46,19 +53,51 @@ async function main() {
46
53
  });
47
54
  if (existing) fail(`Slug "${slug}" already exists`);
48
55
 
49
- const portal = await prisma.clientPortal.create({
50
- data: {
51
- organizationId: orgId,
52
- slug,
53
- companyName: company,
54
- username,
55
- passwordHash,
56
- },
57
- });
56
+ const createData: Record<string, unknown> = {
57
+ organizationId: orgId,
58
+ slug,
59
+ companyName: company,
60
+ logoUrl,
61
+ username,
62
+ passwordHash,
63
+ };
64
+
65
+ // Older scaffolded apps may not have the ClientPortal.websiteUrl column yet.
66
+ if (websiteUrl) {
67
+ createData.websiteUrl = websiteUrl;
68
+ }
69
+
70
+ let portal;
71
+ try {
72
+ portal = await prisma.clientPortal.create({
73
+ data: createData,
74
+ });
75
+ } catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ if (
78
+ websiteUrl &&
79
+ (message.includes("Unknown arg `websiteUrl`") ||
80
+ message.includes("Unknown argument `websiteUrl`"))
81
+ ) {
82
+ delete createData.websiteUrl;
83
+ portal = await prisma.clientPortal.create({
84
+ data: createData,
85
+ });
86
+ } else {
87
+ throw error;
88
+ }
89
+ }
58
90
 
59
91
  console.log(JSON.stringify({
60
92
  ok: true,
61
- portal: { id: portal.id, slug: portal.slug, username, password },
93
+ portal: {
94
+ id: portal.id,
95
+ slug: portal.slug,
96
+ username,
97
+ password,
98
+ websiteUrl: portal.websiteUrl,
99
+ logoUrl: portal.logoUrl,
100
+ },
62
101
  }));
63
102
  } finally {
64
103
  await prisma.$disconnect();
@@ -51,6 +51,7 @@ async function main() {
51
51
  select: {
52
52
  slug: true,
53
53
  companyName: true,
54
+ websiteUrl: true,
54
55
  logoUrl: true,
55
56
  username: true,
56
57
  passwordHash: true,
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -104,9 +104,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
104
104
 
105
105
  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.
106
106
 
107
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
107
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
108
108
 
109
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
109
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
110
110
 
111
111
  ## Steps
112
112
 
@@ -168,9 +168,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
168
168
 
169
169
  If invalid, explain the issue and ask for a different slug.
170
170
 
171
- Also ask for the client's website domain (e.g., "acme-health.com"). This is optional but enables auto-branding:
172
- - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
173
- - If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
171
+ Also determine the client's website domain. This enables auto-branding and is worth a quick best-effort pass:
172
+ - if the domain is explicit in the transcript or user prompt, use it
173
+ - otherwise make one quick best-effort lookup for the official site
174
+ - if one clear candidate stands out, suggest it briefly: `I found <domain> — I'll use that for the logo unless you want a different one`
175
+ - if confidence is low, ask directly
176
+
177
+ Store the confirmed value in `ClientPortal.websiteUrl`.
178
+ Use it to derive `ClientPortal.logoUrl` via `getBrandLogoUrl(...)`.
179
+ If no domain is confirmed, fall back to an initial-based logo.
174
180
 
175
181
  ### Step 3: Granola MCP integration (optional)
176
182
 
@@ -284,10 +290,10 @@ import { PortalShell } from "@/components/portal-shell";
284
290
  - The exported component returns `<PortalShell>` with all required props
285
291
 
286
292
  **PortalShell props (all required):**
287
- - `companyName` — the org's company name (from `ORG_NAME`)
288
- - `companyLogo` — a `<span>` with the first letter of the company name, white text
293
+ - `companyName` — the org's company name (from `get-org.ts`)
294
+ - `companyLogo` — prefer the org logo via `getBrandLogoUrl({ logoUrl: ORG.logoUrl, websiteUrl: ORG.websiteUrl, fallbackName: ORG.name })`; fall back to initials only if no better source exists
289
295
  - `clientName` — the client's company name (from transcript or user input)
290
- - `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
296
+ - `clientLogoSrc` — use `getBrandLogoUrl({ websiteUrl: <confirmed client website>, fallbackName: clientName })`; store the chosen URL in the `ClientPortal.logoUrl` field
291
297
  - `clientLogoAlt` — the client company name
292
298
  - `lastUpdated` — today's date formatted as "7 April 2026"
293
299
  - `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
@@ -335,12 +341,12 @@ Import only the icons you need from `lucide-react`. Common choices:
335
341
  Run the create-portal script to register the portal in the database:
336
342
 
337
343
  ```bash
338
- 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"
344
+ 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" --website "<confirmed_client_website_if_any>"
339
345
  ```
340
346
 
341
347
  This creates the `ClientPortal` record with the slug and company name, links it to
342
- the Organization, and currently auto-generates initial credentials. The script
343
- returns `username` and `password`.
348
+ the Organization, stores the confirmed client website/logo if available, and
349
+ currently auto-generates initial credentials. The script returns `username` and `password`.
344
350
 
345
351
  Do not dump those credentials into the middle of the flow unless the user asked.
346
352
  For onboarding, carry them forward quietly and show them at the access phase.
@@ -73,9 +73,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
73
73
 
74
74
  If invalid, explain the issue and ask for a different slug.
75
75
 
76
- Also ask for the client's website domain (e.g., "acme-health.com"). This is optional but enables auto-branding:
77
- - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
78
- - If not provided, an initial-based logo is generated via `getInitialLogo(companyName)` and stored as a data URI
76
+ Also determine the client's website domain. This enables auto-branding and is worth a quick best-effort pass:
77
+ - if the domain is explicit in the transcript or user prompt, use it
78
+ - otherwise make one quick best-effort lookup for the official site
79
+ - if one clear candidate stands out, suggest it briefly: `I found <domain> — I'll use that for the logo unless you want a different one`
80
+ - if confidence is low, ask directly
81
+
82
+ Store the confirmed value in `ClientPortal.websiteUrl`.
83
+ Use it to derive `ClientPortal.logoUrl` via `getBrandLogoUrl(...)`.
84
+ If no domain is confirmed, fall back to an initial-based logo.
79
85
 
80
86
  ### Step 3: Granola MCP integration (optional)
81
87
 
@@ -189,10 +195,10 @@ import { PortalShell } from "@/components/portal-shell";
189
195
  - The exported component returns `<PortalShell>` with all required props
190
196
 
191
197
  **PortalShell props (all required):**
192
- - `companyName` — the org's company name (from `ORG_NAME`)
193
- - `companyLogo` — a `<span>` with the first letter of the company name, white text
198
+ - `companyName` — the org's company name (from `get-org.ts`)
199
+ - `companyLogo` — prefer the org logo via `getBrandLogoUrl({ logoUrl: ORG.logoUrl, websiteUrl: ORG.websiteUrl, fallbackName: ORG.name })`; fall back to initials only if no better source exists
194
200
  - `clientName` — the client's company name (from transcript or user input)
195
- - `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
201
+ - `clientLogoSrc` — use `getBrandLogoUrl({ websiteUrl: <confirmed client website>, fallbackName: clientName })`; store the chosen URL in the `ClientPortal.logoUrl` field
196
202
  - `clientLogoAlt` — the client company name
197
203
  - `lastUpdated` — today's date formatted as "7 April 2026"
198
204
  - `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
@@ -240,12 +246,12 @@ Import only the icons you need from `lucide-react`. Common choices:
240
246
  Run the create-portal script to register the portal in the database:
241
247
 
242
248
  ```bash
243
- 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"
249
+ 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" --website "<confirmed_client_website_if_any>"
244
250
  ```
245
251
 
246
252
  This creates the `ClientPortal` record with the slug and company name, links it to
247
- the Organization, and currently auto-generates initial credentials. The script
248
- returns `username` and `password`.
253
+ the Organization, stores the confirmed client website/logo if available, and
254
+ currently auto-generates initial credentials. The script returns `username` and `password`.
249
255
 
250
256
  Do not dump those credentials into the middle of the flow unless the user asked.
251
257
  For onboarding, carry them forward quietly and show them at the access phase.
@@ -109,9 +109,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
109
109
 
110
110
  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.
111
111
 
112
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
112
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
113
113
 
114
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
114
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
115
115
 
116
116
  ## Steps
117
117
 
@@ -109,9 +109,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
109
109
 
110
110
  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.
111
111
 
112
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
112
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
113
113
 
114
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
114
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
115
115
 
116
116
  ## Safety Guard
117
117
 
@@ -102,9 +102,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
102
102
 
103
103
  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.
104
104
 
105
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
105
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
106
106
 
107
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
107
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
108
108
 
109
109
  ## Steps
110
110
 
@@ -168,12 +168,15 @@ Run list-portals and warn about portals missing credentials. This is a warning,
168
168
  ### Cloud Step 2: Run the canonical deploy command
169
169
 
170
170
  Use the built-in deploy command instead of reimplementing the staged cloud protocol in shell.
171
+ Run it directly so progress lines stay visible while the build and publish are in flight.
171
172
 
172
173
  ```bash
173
- DEPLOY_JSON=$(cd "$APP_PATH" && SHOWPANE_APP_PATH="$APP_PATH" NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$SKILL_DIR/bin/tsconfig.json" "$SKILL_DIR/bin/deploy-to-cloud.ts" --app-path "$APP_PATH" --wait --json)
174
+ DEPLOY_JSON=$(cd "$APP_PATH" && SHOWPANE_APP_PATH="$APP_PATH" "$SHOWPANE_BIN/showpane" deploy --wait --json)
174
175
  echo "$DEPLOY_JSON"
175
176
  ```
176
177
 
178
+ Do not wrap this command in `tail` or another truncating pipe. If you need a shorter summary later, capture the output after the command finishes or read from a temp log separately.
179
+
177
180
  That command already owns:
178
181
  - type check
179
182
  - project-link bootstrap
@@ -73,12 +73,15 @@ Run list-portals and warn about portals missing credentials. This is a warning,
73
73
  ### Cloud Step 2: Run the canonical deploy command
74
74
 
75
75
  Use the built-in deploy command instead of reimplementing the staged cloud protocol in shell.
76
+ Run it directly so progress lines stay visible while the build and publish are in flight.
76
77
 
77
78
  ```bash
78
- DEPLOY_JSON=$(cd "$APP_PATH" && SHOWPANE_APP_PATH="$APP_PATH" NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$SKILL_DIR/bin/tsconfig.json" "$SKILL_DIR/bin/deploy-to-cloud.ts" --app-path "$APP_PATH" --wait --json)
79
+ DEPLOY_JSON=$(cd "$APP_PATH" && SHOWPANE_APP_PATH="$APP_PATH" "$SHOWPANE_BIN/showpane" deploy --wait --json)
79
80
  echo "$DEPLOY_JSON"
80
81
  ```
81
82
 
83
+ Do not wrap this command in `tail` or another truncating pipe. If you need a shorter summary later, capture the output after the command finishes or read from a temp log separately.
84
+
82
85
  That command already owns:
83
86
  - type check
84
87
  - project-link bootstrap
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Steps
107
107
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -154,20 +154,12 @@ If a checkpoint exists:
154
154
 
155
155
  ### Phase 1: Orientation
156
156
 
157
- Start with a compact welcome:
158
-
159
- ```
160
- ╔══════════════════════════════════════════╗
161
- ║ SHOWPANE — First Portal Wizard ║
162
- ╚══════════════════════════════════════════╝
163
- ```
164
-
165
- Then use one short, friendly paragraph. Keep it concise and informal.
157
+ Start with one short setup confirmation and one sharp ask. Keep it concise and informal.
166
158
 
167
159
  Suggested shape:
168
160
 
169
- - "Let's get your first portal started."
170
- - "Who's it for, and what's the context? If you've got a call transcript, paste it in and I'll use that too."
161
+ - "Everything's set up. Let's make your first portal."
162
+ - "Whos it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
171
163
 
172
164
  Keep the opening focused on the user's first portal. This skill is the first-run default, so the top of the flow should feel fast, calm, and immediately useful.
173
165
 
@@ -222,7 +214,7 @@ Start with one short source question, not a menu.
222
214
 
223
215
  Recommended opening:
224
216
 
225
- - "Who's it for, and what's the context? If you've got a call transcript, paste it in and I'll use that too."
217
+ - "Whos it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
226
218
 
227
219
  Only branch after the user answers:
228
220
 
@@ -278,6 +270,7 @@ During this phase:
278
270
 
279
271
  - suggest a slug if needed
280
272
  - prefer a real company/client name in the portal
273
+ - if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
281
274
  - prefer useful structure over completeness
282
275
  - aim for a credible first draft, not a perfect final artifact
283
276
  - do not re-ask for the company name or template if the user already chose them
@@ -329,25 +322,30 @@ If no dev server is running, start it using the `portal-dev` instructions first.
329
322
  If the create step already generated local credentials, show them before opening
330
323
  the preview link. Keep it simple:
331
324
 
325
+ - preview URL
332
326
  - username
333
327
  - password
334
- - one sentence saying these are for local preview right now
335
328
 
336
329
  Do this before opening the browser so the user is not dropped onto a login screen
337
330
  without the credentials they need.
338
331
 
339
- After that, open the local preview immediately. Do not ask a separate
340
- "do you want refinements first?" question unless the user already asked for that.
332
+ After that, open the local preview immediately.
341
333
 
342
- Tell the user exactly what to inspect:
334
+ Once the preview is open, keep the handoff short and decision-oriented. Tell the
335
+ user exactly what to inspect:
343
336
 
344
337
  - does the overall story feel right?
345
- - are next steps clear?
346
- - are obvious documents or dates missing?
338
+ - anything factually wrong?
347
339
 
348
340
  If the user requests a small content fix after preview, make it and preview again.
349
341
  Keep this loop tight. Do not let it become an open-ended editing session.
350
342
 
343
+ Bias the next step toward publish:
344
+
345
+ - `If it looks right, say publish.`
346
+ - `If not, tell me what to change.`
347
+ - `If it looks right, I can publish it to Showpane Cloud so you get a hosted link and built-in analytics without having to self-host. Free for 7 days, then $29/mo.`
348
+
351
349
  Save checkpoint with phase `previewed`.
352
350
 
353
351
  ### Phase 7: Access setup
@@ -380,6 +378,7 @@ Save checkpoint with phase `access-ready` and `accessMode`.
380
378
  ### Phase 8: Publish to Showpane Cloud
381
379
 
382
380
  Run the `portal-deploy` flow inline.
381
+ Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
383
382
 
384
383
  This phase is part of onboarding by default. Do not treat publish as an optional
385
384
  afterthought unless the user explicitly says they want to stop at local preview.
@@ -414,27 +413,32 @@ Save checkpoint with phase `published` once the hosted URL is live or clearly ac
414
413
 
415
414
  ### Phase 9: Final summary
416
415
 
417
- Show a compact final reference card:
416
+ Show a compact final handoff card. At this point, prioritize the hosted link,
417
+ the active access credentials, and one or two clear next actions.
418
418
 
419
419
  ```
420
420
  ════════════════════════════════════════
421
- First portal complete
421
+ First portal live
422
422
 
423
423
  Portal: <slug> (<company>)
424
- Local: http://localhost:3000/client/<slug>
425
- Cloud: <hosted-url-or-publishing-status>
426
- Access: <hosted login / hosted share link / both>
424
+ Cloud: <hosted login URL or share URL>
425
+ Login: <username if login is the active access mode>
426
+ Pass: <password if login is the active access mode>
427
427
 
428
- Next: /portal-update <slug>
429
- Help: /portal-list, /portal-status
428
+ Next: /portal-update <slug>
429
+ /portal-share <slug> (if a direct hosted link would help)
430
430
  ════════════════════════════════════════
431
431
  ```
432
432
 
433
433
  Rules:
434
434
 
435
- - never include the password in the final summary
436
- - include the hosted share URL only if it exists and the user explicitly asked for it after publish
435
+ - if login is the active access mode, include the username and password here so the user does not have to scroll back
436
+ - if a hosted share URL exists because the user asked for one after publish, show that as the primary cloud link
437
+ - otherwise show the hosted login URL as the cloud link
438
+ - keep the summary short; avoid extra explanation once the portal is live
437
439
  - if publish is still finalizing, say so plainly instead of pretending it is live
440
+ In that case, show the local preview link and point the user to `/portal-status`
441
+ instead of pretending the hosted link is ready.
438
442
 
439
443
  After the final summary, delete the checkpoint:
440
444
 
@@ -61,20 +61,12 @@ If a checkpoint exists:
61
61
 
62
62
  ### Phase 1: Orientation
63
63
 
64
- Start with a compact welcome:
65
-
66
- ```
67
- ╔══════════════════════════════════════════╗
68
- ║ SHOWPANE — First Portal Wizard ║
69
- ╚══════════════════════════════════════════╝
70
- ```
71
-
72
- Then use one short, friendly paragraph. Keep it concise and informal.
64
+ Start with one short setup confirmation and one sharp ask. Keep it concise and informal.
73
65
 
74
66
  Suggested shape:
75
67
 
76
- - "Let's get your first portal started."
77
- - "Who's it for, and what's the context? If you've got a call transcript, paste it in and I'll use that too."
68
+ - "Everything's set up. Let's make your first portal."
69
+ - "Whos it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
78
70
 
79
71
  Keep the opening focused on the user's first portal. This skill is the first-run default, so the top of the flow should feel fast, calm, and immediately useful.
80
72
 
@@ -129,7 +121,7 @@ Start with one short source question, not a menu.
129
121
 
130
122
  Recommended opening:
131
123
 
132
- - "Who's it for, and what's the context? If you've got a call transcript, paste it in and I'll use that too."
124
+ - "Whos it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
133
125
 
134
126
  Only branch after the user answers:
135
127
 
@@ -185,6 +177,7 @@ During this phase:
185
177
 
186
178
  - suggest a slug if needed
187
179
  - prefer a real company/client name in the portal
180
+ - if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
188
181
  - prefer useful structure over completeness
189
182
  - aim for a credible first draft, not a perfect final artifact
190
183
  - do not re-ask for the company name or template if the user already chose them
@@ -236,25 +229,30 @@ If no dev server is running, start it using the `portal-dev` instructions first.
236
229
  If the create step already generated local credentials, show them before opening
237
230
  the preview link. Keep it simple:
238
231
 
232
+ - preview URL
239
233
  - username
240
234
  - password
241
- - one sentence saying these are for local preview right now
242
235
 
243
236
  Do this before opening the browser so the user is not dropped onto a login screen
244
237
  without the credentials they need.
245
238
 
246
- After that, open the local preview immediately. Do not ask a separate
247
- "do you want refinements first?" question unless the user already asked for that.
239
+ After that, open the local preview immediately.
248
240
 
249
- Tell the user exactly what to inspect:
241
+ Once the preview is open, keep the handoff short and decision-oriented. Tell the
242
+ user exactly what to inspect:
250
243
 
251
244
  - does the overall story feel right?
252
- - are next steps clear?
253
- - are obvious documents or dates missing?
245
+ - anything factually wrong?
254
246
 
255
247
  If the user requests a small content fix after preview, make it and preview again.
256
248
  Keep this loop tight. Do not let it become an open-ended editing session.
257
249
 
250
+ Bias the next step toward publish:
251
+
252
+ - `If it looks right, say publish.`
253
+ - `If not, tell me what to change.`
254
+ - `If it looks right, I can publish it to Showpane Cloud so you get a hosted link and built-in analytics without having to self-host. Free for 7 days, then $29/mo.`
255
+
258
256
  Save checkpoint with phase `previewed`.
259
257
 
260
258
  ### Phase 7: Access setup
@@ -287,6 +285,7 @@ Save checkpoint with phase `access-ready` and `accessMode`.
287
285
  ### Phase 8: Publish to Showpane Cloud
288
286
 
289
287
  Run the `portal-deploy` flow inline.
288
+ Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
290
289
 
291
290
  This phase is part of onboarding by default. Do not treat publish as an optional
292
291
  afterthought unless the user explicitly says they want to stop at local preview.
@@ -321,27 +320,32 @@ Save checkpoint with phase `published` once the hosted URL is live or clearly ac
321
320
 
322
321
  ### Phase 9: Final summary
323
322
 
324
- Show a compact final reference card:
323
+ Show a compact final handoff card. At this point, prioritize the hosted link,
324
+ the active access credentials, and one or two clear next actions.
325
325
 
326
326
  ```
327
327
  ════════════════════════════════════════
328
- First portal complete
328
+ First portal live
329
329
 
330
330
  Portal: <slug> (<company>)
331
- Local: http://localhost:3000/client/<slug>
332
- Cloud: <hosted-url-or-publishing-status>
333
- Access: <hosted login / hosted share link / both>
331
+ Cloud: <hosted login URL or share URL>
332
+ Login: <username if login is the active access mode>
333
+ Pass: <password if login is the active access mode>
334
334
 
335
- Next: /portal-update <slug>
336
- Help: /portal-list, /portal-status
335
+ Next: /portal-update <slug>
336
+ /portal-share <slug> (if a direct hosted link would help)
337
337
  ════════════════════════════════════════
338
338
  ```
339
339
 
340
340
  Rules:
341
341
 
342
- - never include the password in the final summary
343
- - include the hosted share URL only if it exists and the user explicitly asked for it after publish
342
+ - if login is the active access mode, include the username and password here so the user does not have to scroll back
343
+ - if a hosted share URL exists because the user asked for one after publish, show that as the primary cloud link
344
+ - otherwise show the hosted login URL as the cloud link
345
+ - keep the summary short; avoid extra explanation once the portal is live
344
346
  - if publish is still finalizing, say so plainly instead of pretending it is live
347
+ In that case, show the local preview link and point the user to `/portal-status`
348
+ instead of pretending the hosted link is ready.
345
349
 
346
350
  After the final summary, delete the checkpoint:
347
351
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -183,12 +183,15 @@ Run this in a single Bash tool call so the browser opens immediately.
183
183
  After opening, display:
184
184
 
185
185
  ```
186
- Opened portal for <slug> in browser
187
- URL: http://localhost:3000/client/<slug>
186
+ Preview ready
187
+
188
+ URL: http://localhost:3000/client/<slug>
189
+ Login: /portal-credentials <slug>
188
190
  ```
189
191
 
190
- If the portal has credentials set up, remind the user:
191
- "Login with the credentials from /portal-credentials <slug>. For external access, publish first with /portal-deploy."
192
+ Then add one short next step:
193
+
194
+ - `If it looks right, publish it with /portal-deploy.`
192
195
 
193
196
  If the portal is the example portal (slug is "example"), no credentials are needed -- it is publicly accessible by design.
194
197
 
@@ -88,12 +88,15 @@ Run this in a single Bash tool call so the browser opens immediately.
88
88
  After opening, display:
89
89
 
90
90
  ```
91
- Opened portal for <slug> in browser
92
- URL: http://localhost:3000/client/<slug>
91
+ Preview ready
92
+
93
+ URL: http://localhost:3000/client/<slug>
94
+ Login: /portal-credentials <slug>
93
95
  ```
94
96
 
95
- If the portal has credentials set up, remind the user:
96
- "Login with the credentials from /portal-credentials <slug>. For external access, publish first with /portal-deploy."
97
+ Then add one short next step:
98
+
99
+ - `If it looks right, publish it with /portal-deploy.`
97
100
 
98
101
  If the portal is the example portal (slug is "example"), no credentials are needed -- it is publicly accessible by design.
99
102
 
@@ -93,9 +93,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
93
93
 
94
94
  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.
95
95
 
96
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
96
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
97
97
 
98
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
98
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
99
99
 
100
100
  ## Steps
101
101
 
@@ -269,21 +269,17 @@ Replace the placeholder values with the actual values collected. Use `chmod 600`
269
269
 
270
270
  ### Step 10: Print success summary
271
271
 
272
- Display a clear summary:
272
+ Display a short success handoff:
273
273
 
274
274
  ```
275
275
  Showpane setup complete!
276
276
 
277
- App path: /path/to/showpane-project
278
- Deploy mode: local
279
- Organization: Acme Consulting (acme-consulting)
280
- Brand color: #2563eb
281
- Telemetry: anonymous
277
+ App: /path/to/showpane-project
278
+ Org: Acme Consulting (acme-consulting)
282
279
 
283
- Next steps:
284
- 1. Recommended first run: /portal-onboard
285
- 2. Fast repeat-user path: /portal-create <slug>
286
- 3. View the example portal: open http://localhost:3000/client/example
280
+ Next:
281
+ /portal-onboard
282
+ /portal-create <slug>
287
283
  ```
288
284
 
289
285
  ### Step 11: Record learning (optional)
@@ -186,21 +186,17 @@ Replace the placeholder values with the actual values collected. Use `chmod 600`
186
186
 
187
187
  ### Step 10: Print success summary
188
188
 
189
- Display a clear summary:
189
+ Display a short success handoff:
190
190
 
191
191
  ```
192
192
  Showpane setup complete!
193
193
 
194
- App path: /path/to/showpane-project
195
- Deploy mode: local
196
- Organization: Acme Consulting (acme-consulting)
197
- Brand color: #2563eb
198
- Telemetry: anonymous
194
+ App: /path/to/showpane-project
195
+ Org: Acme Consulting (acme-consulting)
199
196
 
200
- Next steps:
201
- 1. Recommended first run: /portal-onboard
202
- 2. Fast repeat-user path: /portal-create <slug>
203
- 3. View the example portal: open http://localhost:3000/client/example
197
+ Next:
198
+ /portal-onboard
199
+ /portal-create <slug>
204
200
  ```
205
201
 
206
202
  ### Step 11: Record learning (optional)
@@ -102,9 +102,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
102
102
 
103
103
  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.
104
104
 
105
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
105
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
106
106
 
107
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
107
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
108
108
 
109
109
  ## Overview
110
110
 
@@ -153,17 +153,16 @@ Present the link in a clear, copy-friendly ASCII box:
153
153
 
154
154
  ```
155
155
  ════════════════════════════════════════
156
- Share link for: whzan
157
- https://portal.example.com/client/whzan/s/eyJ...
158
- Expires: never (until credentials rotate)
156
+ Share link: https://portal.example.com/client/whzan/s/eyJ...
157
+ Revoke: rotate credentials or deactivate the portal
159
158
  ════════════════════════════════════════
160
159
  ```
161
160
 
162
161
  ### Step 4: Provide usage guidance
163
162
 
164
- After displaying the link, add this note:
163
+ After displaying the link, add one short note:
165
164
 
166
- "This link is meant for the hosted portal. The link stays valid until the portal credentials are rotated or the portal is deactivated."
165
+ `This is for the hosted portal. It stays valid until credentials rotate or the portal is deactivated.`
167
166
 
168
167
  If the user has previously generated share links (check learnings for patterns), you can skip the explanation and just show the link.
169
168
 
@@ -58,17 +58,16 @@ Present the link in a clear, copy-friendly ASCII box:
58
58
 
59
59
  ```
60
60
  ════════════════════════════════════════
61
- Share link for: whzan
62
- https://portal.example.com/client/whzan/s/eyJ...
63
- Expires: never (until credentials rotate)
61
+ Share link: https://portal.example.com/client/whzan/s/eyJ...
62
+ Revoke: rotate credentials or deactivate the portal
64
63
  ════════════════════════════════════════
65
64
  ```
66
65
 
67
66
  ### Step 4: Provide usage guidance
68
67
 
69
- After displaying the link, add this note:
68
+ After displaying the link, add one short note:
70
69
 
71
- "This link is meant for the hosted portal. The link stays valid until the portal credentials are rotated or the portal is deactivated."
70
+ `This is for the hosted portal. It stays valid until credentials rotate or the portal is deactivated.`
72
71
 
73
72
  If the user has previously generated share links (check learnings for patterns), you can skip the explanation and just show the link.
74
73
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Steps
107
107
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Overview
107
107
 
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
99
99
 
100
100
  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.
101
101
 
102
- Read `skills/shared/runtime-principles.md` once near the start of the skill and apply the relevant product defaults.
102
+ Read `skills/shared/runtime-principles.md` directly from that exact path near the start of the skill and apply the relevant product defaults.
103
103
 
104
- If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill and apply only the relevant limits.
104
+ If `skills/shared/platform-constraints.md` exists, read it directly from that exact path near the start of the skill and apply only the relevant limits. No directory listing is needed first.
105
105
 
106
106
  ## Steps
107
107
 
package/dist/index.js CHANGED
@@ -671,8 +671,12 @@ async function syncCloudProjectLink(projectRoot, accessToken) {
671
671
  writeCloudProjectLink(projectRoot, projectLink);
672
672
  }
673
673
  function removePath(targetPath) {
674
- if (!existsSync(targetPath)) return;
675
- const stat = lstatSync(targetPath);
674
+ let stat;
675
+ try {
676
+ stat = lstatSync(targetPath);
677
+ } catch {
678
+ return;
679
+ }
676
680
  if (stat.isSymbolicLink() || stat.isFile()) {
677
681
  unlinkSync(targetPath);
678
682
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {