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.
- package/bundle/meta/scaffold-manifest.json +6 -5
- package/bundle/scaffold/prisma/schema.local.prisma +1 -0
- package/bundle/scaffold/src/__tests__/branding.test.ts +43 -0
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +42 -15
- package/bundle/scaffold/src/lib/branding.ts +47 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +1 -0
- package/bundle/toolchain/CLI_VERSION +1 -1
- package/bundle/toolchain/bin/create-portal.ts +50 -11
- package/bundle/toolchain/bin/export-runtime-state.ts +1 -0
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-create/SKILL.md +26 -22
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +24 -20
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-delete/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +6 -3
- package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +4 -1
- package/bundle/toolchain/skills/portal-dev/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-list/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +13 -7
- package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +11 -5
- package/bundle/toolchain/skills/portal-preview/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-setup/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-share/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-status/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-update/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +2 -2
- package/bundle/toolchain/skills/portal-verify/SKILL.md +2 -2
- package/dist/index.js +6 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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",
|
|
@@ -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 {
|
|
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 = "
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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: {
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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: {
|
|
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();
|
|
@@ -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`
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
174
|
-
-
|
|
175
|
-
-
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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 `
|
|
290
|
-
- `companyLogo` —
|
|
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` —
|
|
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,
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
79
|
-
-
|
|
80
|
-
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 `
|
|
195
|
-
- `companyLogo` —
|
|
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` —
|
|
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,
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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"
|
|
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"
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
-
|
|
388
|
-
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
-
|
|
295
|
-
-
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
675
|
-
|
|
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;
|