showpane 0.4.24 → 0.4.26

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 (29) 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 +26 -22
  12. package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +24 -20
  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 +13 -7
  20. package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +11 -5
  21. package/bundle/toolchain/skills/portal-preview/SKILL.md +2 -2
  22. package/bundle/toolchain/skills/portal-setup/SKILL.md +2 -2
  23. package/bundle/toolchain/skills/portal-share/SKILL.md +2 -2
  24. package/bundle/toolchain/skills/portal-status/SKILL.md +2 -2
  25. package/bundle/toolchain/skills/portal-update/SKILL.md +2 -2
  26. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +2 -2
  27. package/bundle/toolchain/skills/portal-verify/SKILL.md +2 -2
  28. package/dist/index.js +6 -2
  29. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-13T21:26:42.638Z",
3
+ "generatedAt": "2026-04-13T22:42:22.971Z",
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.24
1
+ 0.4.26
@@ -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
 
@@ -138,13 +138,8 @@ Store:
138
138
  If the helper fails, stop and tell the user to run `/portal-setup` again instead
139
139
  of guessing with ad-hoc SQL.
140
140
 
141
- Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
142
- directories just to understand the project. Do not call `check-slug.ts` with
143
- anything except `--org-id`.
144
- Do not re-read config, Prisma, or SQLite to rediscover org fields that were already
145
- returned by `get-org.ts`.
146
-
147
- The canonical references for this skill are:
141
+ Once `get-org.ts` succeeds, extra project probing rarely improves the draft.
142
+ Use that result plus the selected template/example as the canonical references:
148
143
 
149
144
  - the configured `APP_PATH`
150
145
  - the configured `ORG_SLUG`
@@ -153,6 +148,9 @@ The canonical references for this skill are:
153
148
  - `$SKILL_DIR/templates/<chosen-template>/...`
154
149
  - `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
155
150
 
151
+ For slug checks, use `check-slug.ts` with `--org-id`. Re-reading config, Prisma,
152
+ or SQLite usually just burns time without changing the correct org context.
153
+
156
154
  ### Step 2: Determine the portal slug
157
155
 
158
156
  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.
@@ -170,9 +168,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
170
168
 
171
169
  If invalid, explain the issue and ask for a different slug.
172
170
 
173
- Also ask for the client's website domain (e.g., "acme-health.com"). This is optional but enables auto-branding:
174
- - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
175
- - 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.
176
180
 
177
181
  ### Step 3: Granola MCP integration (optional)
178
182
 
@@ -215,10 +219,10 @@ Always also read the example portal as your quality and style reference:
215
219
  cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
216
220
  ```
217
221
 
218
- 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.
219
-
220
- Do not search the repo for templates or ask the filesystem where templates live.
221
- Use the selected template and the exact `SKILL_DIR` path above.
222
+ The template provides structure. The example provides quality and styling.
223
+ Read those directly from the known paths above, then match the example's card
224
+ styles, typography, spacing, and responsive breakpoints. Templates are
225
+ inspiration, not rigid scaffolds.
222
226
 
223
227
  ### Step 5: Analyze transcript (if available)
224
228
 
@@ -286,10 +290,10 @@ import { PortalShell } from "@/components/portal-shell";
286
290
  - The exported component returns `<PortalShell>` with all required props
287
291
 
288
292
  **PortalShell props (all required):**
289
- - `companyName` — the org's company name (from `ORG_NAME`)
290
- - `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
291
295
  - `clientName` — the client's company name (from transcript or user input)
292
- - `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
293
297
  - `clientLogoAlt` — the client company name
294
298
  - `lastUpdated` — today's date formatted as "7 April 2026"
295
299
  - `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
@@ -337,12 +341,12 @@ Import only the icons you need from `lucide-react`. Common choices:
337
341
  Run the create-portal script to register the portal in the database:
338
342
 
339
343
  ```bash
340
- 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>"
341
345
  ```
342
346
 
343
347
  This creates the `ClientPortal` record with the slug and company name, links it to
344
- the Organization, and currently auto-generates initial credentials. The script
345
- 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`.
346
350
 
347
351
  Do not dump those credentials into the middle of the flow unless the user asked.
348
352
  For onboarding, carry them forward quietly and show them at the access phase.
@@ -43,13 +43,8 @@ Store:
43
43
  If the helper fails, stop and tell the user to run `/portal-setup` again instead
44
44
  of guessing with ad-hoc SQL.
45
45
 
46
- Do not probe `showpane --help`, `package.json`, `scripts/`, `prisma/`, or template
47
- directories just to understand the project. Do not call `check-slug.ts` with
48
- anything except `--org-id`.
49
- Do not re-read config, Prisma, or SQLite to rediscover org fields that were already
50
- returned by `get-org.ts`.
51
-
52
- The canonical references for this skill are:
46
+ Once `get-org.ts` succeeds, extra project probing rarely improves the draft.
47
+ Use that result plus the selected template/example as the canonical references:
53
48
 
54
49
  - the configured `APP_PATH`
55
50
  - the configured `ORG_SLUG`
@@ -58,6 +53,9 @@ The canonical references for this skill are:
58
53
  - `$SKILL_DIR/templates/<chosen-template>/...`
59
54
  - `$APP_PATH/src/app/(portal)/client/example/example-client.tsx`
60
55
 
56
+ For slug checks, use `check-slug.ts` with `--org-id`. Re-reading config, Prisma,
57
+ or SQLite usually just burns time without changing the correct org context.
58
+
61
59
  ### Step 2: Determine the portal slug
62
60
 
63
61
  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.
@@ -75,9 +73,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
75
73
 
76
74
  If invalid, explain the issue and ask for a different slug.
77
75
 
78
- Also ask for the client's website domain (e.g., "acme-health.com"). This is optional but enables auto-branding:
79
- - If provided, the client logo will be fetched via `getLogoUrl(domain)` and stored in `ClientPortal.logoUrl`
80
- - 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.
81
85
 
82
86
  ### Step 3: Granola MCP integration (optional)
83
87
 
@@ -120,10 +124,10 @@ Always also read the example portal as your quality and style reference:
120
124
  cat "$APP_PATH/src/app/(portal)/client/example/example-client.tsx"
121
125
  ```
122
126
 
123
- 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.
124
-
125
- Do not search the repo for templates or ask the filesystem where templates live.
126
- Use the selected template and the exact `SKILL_DIR` path above.
127
+ The template provides structure. The example provides quality and styling.
128
+ Read those directly from the known paths above, then match the example's card
129
+ styles, typography, spacing, and responsive breakpoints. Templates are
130
+ inspiration, not rigid scaffolds.
127
131
 
128
132
  ### Step 5: Analyze transcript (if available)
129
133
 
@@ -191,10 +195,10 @@ import { PortalShell } from "@/components/portal-shell";
191
195
  - The exported component returns `<PortalShell>` with all required props
192
196
 
193
197
  **PortalShell props (all required):**
194
- - `companyName` — the org's company name (from `ORG_NAME`)
195
- - `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
196
200
  - `clientName` — the client's company name (from transcript or user input)
197
- - `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
198
202
  - `clientLogoAlt` — the client company name
199
203
  - `lastUpdated` — today's date formatted as "7 April 2026"
200
204
  - `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
@@ -242,12 +246,12 @@ Import only the icons you need from `lucide-react`. Common choices:
242
246
  Run the create-portal script to register the portal in the database:
243
247
 
244
248
  ```bash
245
- 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>"
246
250
  ```
247
251
 
248
252
  This creates the `ClientPortal` record with the slug and company name, links it to
249
- the Organization, and currently auto-generates initial credentials. The script
250
- 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`.
251
255
 
252
256
  Do not dump those credentials into the middle of the flow unless the user asked.
253
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
 
@@ -262,9 +262,8 @@ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PA
262
262
  Use the returned `org.id` as the canonical org id for the rest of the create flow.
263
263
  Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
264
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:
265
+ At this point, extra repo reorientation rarely changes the first draft. Use the
266
+ known inputs below and spend the time shaping the portal instead:
268
267
 
269
268
  - workspace app path
270
269
  - org slug
@@ -272,10 +271,14 @@ are already known:
272
271
  - selected template
273
272
  - optional transcript source
274
273
 
274
+ If you need structure or style, read only the selected template and the example
275
+ portal. Other portal skills are for later phases and usually just add latency here.
276
+
275
277
  During this phase:
276
278
 
277
279
  - suggest a slug if needed
278
280
  - prefer a real company/client name in the portal
281
+ - if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
279
282
  - prefer useful structure over completeness
280
283
  - aim for a credible first draft, not a perfect final artifact
281
284
  - do not re-ask for the company name or template if the user already chose them
@@ -378,14 +381,17 @@ Save checkpoint with phase `access-ready` and `accessMode`.
378
381
  ### Phase 8: Publish to Showpane Cloud
379
382
 
380
383
  Run the `portal-deploy` flow inline.
384
+ Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
381
385
 
382
386
  This phase is part of onboarding by default. Do not treat publish as an optional
383
387
  afterthought unless the user explicitly says they want to stop at local preview.
384
388
 
385
389
  If cloud auth is missing:
386
390
 
387
- - tell the user to run `showpane login`
388
- - after login completes, resume from the checkpoint
391
+ - run `showpane login` inline via Bash in the current session
392
+ - if you need to hand control to the user, tell them to run `! showpane login`
393
+ - let it handle sign-in, sign-up, and checkout if needed
394
+ - then resume publish from the checkpoint
389
395
 
390
396
  If deploy returns `organization_required`:
391
397
 
@@ -169,9 +169,8 @@ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PA
169
169
  Use the returned `org.id` as the canonical org id for the rest of the create flow.
170
170
  Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
171
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:
172
+ At this point, extra repo reorientation rarely changes the first draft. Use the
173
+ known inputs below and spend the time shaping the portal instead:
175
174
 
176
175
  - workspace app path
177
176
  - org slug
@@ -179,10 +178,14 @@ are already known:
179
178
  - selected template
180
179
  - optional transcript source
181
180
 
181
+ If you need structure or style, read only the selected template and the example
182
+ portal. Other portal skills are for later phases and usually just add latency here.
183
+
182
184
  During this phase:
183
185
 
184
186
  - suggest a slug if needed
185
187
  - prefer a real company/client name in the portal
188
+ - if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
186
189
  - prefer useful structure over completeness
187
190
  - aim for a credible first draft, not a perfect final artifact
188
191
  - do not re-ask for the company name or template if the user already chose them
@@ -285,14 +288,17 @@ Save checkpoint with phase `access-ready` and `accessMode`.
285
288
  ### Phase 8: Publish to Showpane Cloud
286
289
 
287
290
  Run the `portal-deploy` flow inline.
291
+ Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
288
292
 
289
293
  This phase is part of onboarding by default. Do not treat publish as an optional
290
294
  afterthought unless the user explicitly says they want to stop at local preview.
291
295
 
292
296
  If cloud auth is missing:
293
297
 
294
- - tell the user to run `showpane login`
295
- - after login completes, resume from the checkpoint
298
+ - run `showpane login` inline via Bash in the current session
299
+ - if you need to hand control to the user, tell them to run `! showpane login`
300
+ - let it handle sign-in, sign-up, and checkout if needed
301
+ - then resume publish from the checkpoint
296
302
 
297
303
  If deploy returns `organization_required`:
298
304
 
@@ -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
 
@@ -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
 
@@ -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
 
@@ -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.24",
3
+ "version": "0.4.26",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {