showpane 0.4.25 → 0.4.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +17 -11
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +15 -9
- 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 +33 -29
- package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +31 -27
- package/bundle/toolchain/skills/portal-preview/SKILL.md +9 -6
- package/bundle/toolchain/skills/portal-preview/SKILL.md.tmpl +7 -4
- package/bundle/toolchain/skills/portal-setup/SKILL.md +8 -12
- package/bundle/toolchain/skills/portal-setup/SKILL.md.tmpl +6 -10
- package/bundle/toolchain/skills/portal-share/SKILL.md +6 -7
- package/bundle/toolchain/skills/portal-share/SKILL.md.tmpl +4 -5
- 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-13T22:
|
|
3
|
+
"generatedAt": "2026-04-13T22:57:26.230Z",
|
|
4
4
|
"scaffoldVersion": "0.2.7",
|
|
5
5
|
"files": {
|
|
6
6
|
".env.example": "ed105f2bdcd1888a98181d55e3c9f7d6eff3ae9c3e2366c2e777a12e3caddfa7",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"package.json": "b095e17e7fc181c630e87fe9f473c5a4ef969afcd4b110f9f9c6d6a6d93f1c0b",
|
|
12
12
|
"postcss.config.js": "fa650b380adfabb151a0b352f7135e107e6352345f899060f1c5c231228f94bf",
|
|
13
13
|
"prisma.config.ts": "36f56fd74eae70632e484443e38d08665158d72d5c978dc456651d8d5e1a636e",
|
|
14
|
-
"prisma/schema.local.prisma": "
|
|
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.27
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PrismaClient } from "@/lib/prisma-client";
|
|
2
|
+
import { getBrandLogoUrl, normalizeWebsiteUrl } from "@/lib/branding";
|
|
2
3
|
import bcrypt from "bcryptjs";
|
|
3
4
|
import crypto from "node:crypto";
|
|
4
5
|
|
|
@@ -16,7 +17,7 @@ async function main() {
|
|
|
16
17
|
const args = process.argv.slice(2);
|
|
17
18
|
|
|
18
19
|
if (args.includes("--help")) {
|
|
19
|
-
console.log("Usage: create-portal --slug <slug> --company <name> --org-id <orgId>");
|
|
20
|
+
console.log("Usage: create-portal --slug <slug> --company <name> --org-id <orgId> [--website <domain-or-url>]");
|
|
20
21
|
console.log("Creates a new client portal with auto-generated credentials.");
|
|
21
22
|
process.exit(0);
|
|
22
23
|
}
|
|
@@ -25,6 +26,7 @@ async function main() {
|
|
|
25
26
|
const slug = getArg("--slug");
|
|
26
27
|
const company = getArg("--company");
|
|
27
28
|
const orgId = getArg("--org-id");
|
|
29
|
+
const website = getArg("--website");
|
|
28
30
|
|
|
29
31
|
if (!slug || !company || !orgId) fail("Missing --slug, --company, or --org-id");
|
|
30
32
|
|
|
@@ -37,6 +39,11 @@ async function main() {
|
|
|
37
39
|
const username = slug;
|
|
38
40
|
const password = crypto.randomBytes(16).toString("base64url");
|
|
39
41
|
const passwordHash = await bcrypt.hash(password, 10);
|
|
42
|
+
const websiteUrl = normalizeWebsiteUrl(website);
|
|
43
|
+
const logoUrl = getBrandLogoUrl({
|
|
44
|
+
websiteUrl,
|
|
45
|
+
fallbackName: company,
|
|
46
|
+
});
|
|
40
47
|
|
|
41
48
|
const prisma = new PrismaClient();
|
|
42
49
|
try {
|
|
@@ -46,19 +53,51 @@ async function main() {
|
|
|
46
53
|
});
|
|
47
54
|
if (existing) fail(`Slug "${slug}" already exists`);
|
|
48
55
|
|
|
49
|
-
const
|
|
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
|
|
|
@@ -168,9 +168,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
|
|
|
168
168
|
|
|
169
169
|
If invalid, explain the issue and ask for a different slug.
|
|
170
170
|
|
|
171
|
-
Also
|
|
172
|
-
-
|
|
173
|
-
-
|
|
171
|
+
Also determine the client's website domain. This enables auto-branding and is worth a quick best-effort pass:
|
|
172
|
+
- if the domain is explicit in the transcript or user prompt, use it
|
|
173
|
+
- otherwise make one quick best-effort lookup for the official site
|
|
174
|
+
- if one clear candidate stands out, suggest it briefly: `I found <domain> — I'll use that for the logo unless you want a different one`
|
|
175
|
+
- if confidence is low, ask directly
|
|
176
|
+
|
|
177
|
+
Store the confirmed value in `ClientPortal.websiteUrl`.
|
|
178
|
+
Use it to derive `ClientPortal.logoUrl` via `getBrandLogoUrl(...)`.
|
|
179
|
+
If no domain is confirmed, fall back to an initial-based logo.
|
|
174
180
|
|
|
175
181
|
### Step 3: Granola MCP integration (optional)
|
|
176
182
|
|
|
@@ -284,10 +290,10 @@ import { PortalShell } from "@/components/portal-shell";
|
|
|
284
290
|
- The exported component returns `<PortalShell>` with all required props
|
|
285
291
|
|
|
286
292
|
**PortalShell props (all required):**
|
|
287
|
-
- `companyName` — the org's company name (from `
|
|
288
|
-
- `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
|
|
289
295
|
- `clientName` — the client's company name (from transcript or user input)
|
|
290
|
-
- `clientLogoSrc` —
|
|
296
|
+
- `clientLogoSrc` — use `getBrandLogoUrl({ websiteUrl: <confirmed client website>, fallbackName: clientName })`; store the chosen URL in the `ClientPortal.logoUrl` field
|
|
291
297
|
- `clientLogoAlt` — the client company name
|
|
292
298
|
- `lastUpdated` — today's date formatted as "7 April 2026"
|
|
293
299
|
- `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
|
|
@@ -335,12 +341,12 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
335
341
|
Run the create-portal script to register the portal in the database:
|
|
336
342
|
|
|
337
343
|
```bash
|
|
338
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID"
|
|
344
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID" --website "<confirmed_client_website_if_any>"
|
|
339
345
|
```
|
|
340
346
|
|
|
341
347
|
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
342
|
-
the Organization,
|
|
343
|
-
returns `username` and `password`.
|
|
348
|
+
the Organization, stores the confirmed client website/logo if available, and
|
|
349
|
+
currently auto-generates initial credentials. The script returns `username` and `password`.
|
|
344
350
|
|
|
345
351
|
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
346
352
|
For onboarding, carry them forward quietly and show them at the access phase.
|
|
@@ -73,9 +73,15 @@ The script returns `{"valid":true}` or `{"valid":false,"reason":"...","message":
|
|
|
73
73
|
|
|
74
74
|
If invalid, explain the issue and ask for a different slug.
|
|
75
75
|
|
|
76
|
-
Also
|
|
77
|
-
-
|
|
78
|
-
-
|
|
76
|
+
Also determine the client's website domain. This enables auto-branding and is worth a quick best-effort pass:
|
|
77
|
+
- if the domain is explicit in the transcript or user prompt, use it
|
|
78
|
+
- otherwise make one quick best-effort lookup for the official site
|
|
79
|
+
- if one clear candidate stands out, suggest it briefly: `I found <domain> — I'll use that for the logo unless you want a different one`
|
|
80
|
+
- if confidence is low, ask directly
|
|
81
|
+
|
|
82
|
+
Store the confirmed value in `ClientPortal.websiteUrl`.
|
|
83
|
+
Use it to derive `ClientPortal.logoUrl` via `getBrandLogoUrl(...)`.
|
|
84
|
+
If no domain is confirmed, fall back to an initial-based logo.
|
|
79
85
|
|
|
80
86
|
### Step 3: Granola MCP integration (optional)
|
|
81
87
|
|
|
@@ -189,10 +195,10 @@ import { PortalShell } from "@/components/portal-shell";
|
|
|
189
195
|
- The exported component returns `<PortalShell>` with all required props
|
|
190
196
|
|
|
191
197
|
**PortalShell props (all required):**
|
|
192
|
-
- `companyName` — the org's company name (from `
|
|
193
|
-
- `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
|
|
194
200
|
- `clientName` — the client's company name (from transcript or user input)
|
|
195
|
-
- `clientLogoSrc` —
|
|
201
|
+
- `clientLogoSrc` — use `getBrandLogoUrl({ websiteUrl: <confirmed client website>, fallbackName: clientName })`; store the chosen URL in the `ClientPortal.logoUrl` field
|
|
196
202
|
- `clientLogoAlt` — the client company name
|
|
197
203
|
- `lastUpdated` — today's date formatted as "7 April 2026"
|
|
198
204
|
- `contact` — object with `name`, `title`, `avatarSrc`, `email` (from the `get-org.ts` result, not from ad-hoc config or DB probing)
|
|
@@ -240,12 +246,12 @@ Import only the icons you need from `lucide-react`. Common choices:
|
|
|
240
246
|
Run the create-portal script to register the portal in the database:
|
|
241
247
|
|
|
242
248
|
```bash
|
|
243
|
-
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID"
|
|
249
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-portal.ts" --slug <slug> --company "<client_company_name>" --org-id "$ORG_ID" --website "<confirmed_client_website_if_any>"
|
|
244
250
|
```
|
|
245
251
|
|
|
246
252
|
This creates the `ClientPortal` record with the slug and company name, links it to
|
|
247
|
-
the Organization,
|
|
248
|
-
returns `username` and `password`.
|
|
253
|
+
the Organization, stores the confirmed client website/logo if available, and
|
|
254
|
+
currently auto-generates initial credentials. The script returns `username` and `password`.
|
|
249
255
|
|
|
250
256
|
Do not dump those credentials into the middle of the flow unless the user asked.
|
|
251
257
|
For onboarding, carry them forward quietly and show them at the access phase.
|
|
@@ -109,9 +109,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
|
|
|
109
109
|
|
|
110
110
|
If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
|
|
111
111
|
|
|
112
|
-
Read `skills/shared/runtime-principles.md`
|
|
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
|
|
|
@@ -154,20 +154,12 @@ If a checkpoint exists:
|
|
|
154
154
|
|
|
155
155
|
### Phase 1: Orientation
|
|
156
156
|
|
|
157
|
-
Start with
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
╔══════════════════════════════════════════╗
|
|
161
|
-
║ SHOWPANE — First Portal Wizard ║
|
|
162
|
-
╚══════════════════════════════════════════╝
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Then use one short, friendly paragraph. Keep it concise and informal.
|
|
157
|
+
Start with one short setup confirmation and one sharp ask. Keep it concise and informal.
|
|
166
158
|
|
|
167
159
|
Suggested shape:
|
|
168
160
|
|
|
169
|
-
- "Let's
|
|
170
|
-
- "Who
|
|
161
|
+
- "Everything's set up. Let's make your first portal."
|
|
162
|
+
- "Who’s it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
|
|
171
163
|
|
|
172
164
|
Keep the opening focused on the user's first portal. This skill is the first-run default, so the top of the flow should feel fast, calm, and immediately useful.
|
|
173
165
|
|
|
@@ -222,7 +214,7 @@ Start with one short source question, not a menu.
|
|
|
222
214
|
|
|
223
215
|
Recommended opening:
|
|
224
216
|
|
|
225
|
-
- "Who
|
|
217
|
+
- "Who’s it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
|
|
226
218
|
|
|
227
219
|
Only branch after the user answers:
|
|
228
220
|
|
|
@@ -278,6 +270,7 @@ During this phase:
|
|
|
278
270
|
|
|
279
271
|
- suggest a slug if needed
|
|
280
272
|
- prefer a real company/client name in the portal
|
|
273
|
+
- if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
|
|
281
274
|
- prefer useful structure over completeness
|
|
282
275
|
- aim for a credible first draft, not a perfect final artifact
|
|
283
276
|
- do not re-ask for the company name or template if the user already chose them
|
|
@@ -329,25 +322,30 @@ If no dev server is running, start it using the `portal-dev` instructions first.
|
|
|
329
322
|
If the create step already generated local credentials, show them before opening
|
|
330
323
|
the preview link. Keep it simple:
|
|
331
324
|
|
|
325
|
+
- preview URL
|
|
332
326
|
- username
|
|
333
327
|
- password
|
|
334
|
-
- one sentence saying these are for local preview right now
|
|
335
328
|
|
|
336
329
|
Do this before opening the browser so the user is not dropped onto a login screen
|
|
337
330
|
without the credentials they need.
|
|
338
331
|
|
|
339
|
-
After that, open the local preview immediately.
|
|
340
|
-
"do you want refinements first?" question unless the user already asked for that.
|
|
332
|
+
After that, open the local preview immediately.
|
|
341
333
|
|
|
342
|
-
|
|
334
|
+
Once the preview is open, keep the handoff short and decision-oriented. Tell the
|
|
335
|
+
user exactly what to inspect:
|
|
343
336
|
|
|
344
337
|
- does the overall story feel right?
|
|
345
|
-
-
|
|
346
|
-
- are obvious documents or dates missing?
|
|
338
|
+
- anything factually wrong?
|
|
347
339
|
|
|
348
340
|
If the user requests a small content fix after preview, make it and preview again.
|
|
349
341
|
Keep this loop tight. Do not let it become an open-ended editing session.
|
|
350
342
|
|
|
343
|
+
Bias the next step toward publish:
|
|
344
|
+
|
|
345
|
+
- `If it looks right, say publish.`
|
|
346
|
+
- `If not, tell me what to change.`
|
|
347
|
+
- `If it looks right, I can publish it to Showpane Cloud so you get a hosted link and built-in analytics without having to self-host. Free for 7 days, then $29/mo.`
|
|
348
|
+
|
|
351
349
|
Save checkpoint with phase `previewed`.
|
|
352
350
|
|
|
353
351
|
### Phase 7: Access setup
|
|
@@ -380,6 +378,7 @@ Save checkpoint with phase `access-ready` and `accessMode`.
|
|
|
380
378
|
### Phase 8: Publish to Showpane Cloud
|
|
381
379
|
|
|
382
380
|
Run the `portal-deploy` flow inline.
|
|
381
|
+
Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
|
|
383
382
|
|
|
384
383
|
This phase is part of onboarding by default. Do not treat publish as an optional
|
|
385
384
|
afterthought unless the user explicitly says they want to stop at local preview.
|
|
@@ -414,27 +413,32 @@ Save checkpoint with phase `published` once the hosted URL is live or clearly ac
|
|
|
414
413
|
|
|
415
414
|
### Phase 9: Final summary
|
|
416
415
|
|
|
417
|
-
Show a compact final
|
|
416
|
+
Show a compact final handoff card. At this point, prioritize the hosted link,
|
|
417
|
+
the active access credentials, and one or two clear next actions.
|
|
418
418
|
|
|
419
419
|
```
|
|
420
420
|
════════════════════════════════════════
|
|
421
|
-
First portal
|
|
421
|
+
First portal live
|
|
422
422
|
|
|
423
423
|
Portal: <slug> (<company>)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
Cloud: <hosted login URL or share URL>
|
|
425
|
+
Login: <username if login is the active access mode>
|
|
426
|
+
Pass: <password if login is the active access mode>
|
|
427
427
|
|
|
428
|
-
Next:
|
|
429
|
-
|
|
428
|
+
Next: /portal-update <slug>
|
|
429
|
+
/portal-share <slug> (if a direct hosted link would help)
|
|
430
430
|
════════════════════════════════════════
|
|
431
431
|
```
|
|
432
432
|
|
|
433
433
|
Rules:
|
|
434
434
|
|
|
435
|
-
-
|
|
436
|
-
-
|
|
435
|
+
- if login is the active access mode, include the username and password here so the user does not have to scroll back
|
|
436
|
+
- if a hosted share URL exists because the user asked for one after publish, show that as the primary cloud link
|
|
437
|
+
- otherwise show the hosted login URL as the cloud link
|
|
438
|
+
- keep the summary short; avoid extra explanation once the portal is live
|
|
437
439
|
- if publish is still finalizing, say so plainly instead of pretending it is live
|
|
440
|
+
In that case, show the local preview link and point the user to `/portal-status`
|
|
441
|
+
instead of pretending the hosted link is ready.
|
|
438
442
|
|
|
439
443
|
After the final summary, delete the checkpoint:
|
|
440
444
|
|
|
@@ -61,20 +61,12 @@ If a checkpoint exists:
|
|
|
61
61
|
|
|
62
62
|
### Phase 1: Orientation
|
|
63
63
|
|
|
64
|
-
Start with
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
╔══════════════════════════════════════════╗
|
|
68
|
-
║ SHOWPANE — First Portal Wizard ║
|
|
69
|
-
╚══════════════════════════════════════════╝
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Then use one short, friendly paragraph. Keep it concise and informal.
|
|
64
|
+
Start with one short setup confirmation and one sharp ask. Keep it concise and informal.
|
|
73
65
|
|
|
74
66
|
Suggested shape:
|
|
75
67
|
|
|
76
|
-
- "Let's
|
|
77
|
-
- "Who
|
|
68
|
+
- "Everything's set up. Let's make your first portal."
|
|
69
|
+
- "Who’s it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
|
|
78
70
|
|
|
79
71
|
Keep the opening focused on the user's first portal. This skill is the first-run default, so the top of the flow should feel fast, calm, and immediately useful.
|
|
80
72
|
|
|
@@ -129,7 +121,7 @@ Start with one short source question, not a menu.
|
|
|
129
121
|
|
|
130
122
|
Recommended opening:
|
|
131
123
|
|
|
132
|
-
- "Who
|
|
124
|
+
- "Who’s it for? Paste a call transcript or meeting notes, or give me a short brief on the client and what you want the portal to do."
|
|
133
125
|
|
|
134
126
|
Only branch after the user answers:
|
|
135
127
|
|
|
@@ -185,6 +177,7 @@ During this phase:
|
|
|
185
177
|
|
|
186
178
|
- suggest a slug if needed
|
|
187
179
|
- prefer a real company/client name in the portal
|
|
180
|
+
- if the client website is not already obvious, do one quick best-effort domain lookup and suggest the result briefly before drafting
|
|
188
181
|
- prefer useful structure over completeness
|
|
189
182
|
- aim for a credible first draft, not a perfect final artifact
|
|
190
183
|
- do not re-ask for the company name or template if the user already chose them
|
|
@@ -236,25 +229,30 @@ If no dev server is running, start it using the `portal-dev` instructions first.
|
|
|
236
229
|
If the create step already generated local credentials, show them before opening
|
|
237
230
|
the preview link. Keep it simple:
|
|
238
231
|
|
|
232
|
+
- preview URL
|
|
239
233
|
- username
|
|
240
234
|
- password
|
|
241
|
-
- one sentence saying these are for local preview right now
|
|
242
235
|
|
|
243
236
|
Do this before opening the browser so the user is not dropped onto a login screen
|
|
244
237
|
without the credentials they need.
|
|
245
238
|
|
|
246
|
-
After that, open the local preview immediately.
|
|
247
|
-
"do you want refinements first?" question unless the user already asked for that.
|
|
239
|
+
After that, open the local preview immediately.
|
|
248
240
|
|
|
249
|
-
|
|
241
|
+
Once the preview is open, keep the handoff short and decision-oriented. Tell the
|
|
242
|
+
user exactly what to inspect:
|
|
250
243
|
|
|
251
244
|
- does the overall story feel right?
|
|
252
|
-
-
|
|
253
|
-
- are obvious documents or dates missing?
|
|
245
|
+
- anything factually wrong?
|
|
254
246
|
|
|
255
247
|
If the user requests a small content fix after preview, make it and preview again.
|
|
256
248
|
Keep this loop tight. Do not let it become an open-ended editing session.
|
|
257
249
|
|
|
250
|
+
Bias the next step toward publish:
|
|
251
|
+
|
|
252
|
+
- `If it looks right, say publish.`
|
|
253
|
+
- `If not, tell me what to change.`
|
|
254
|
+
- `If it looks right, I can publish it to Showpane Cloud so you get a hosted link and built-in analytics without having to self-host. Free for 7 days, then $29/mo.`
|
|
255
|
+
|
|
258
256
|
Save checkpoint with phase `previewed`.
|
|
259
257
|
|
|
260
258
|
### Phase 7: Access setup
|
|
@@ -287,6 +285,7 @@ Save checkpoint with phase `access-ready` and `accessMode`.
|
|
|
287
285
|
### Phase 8: Publish to Showpane Cloud
|
|
288
286
|
|
|
289
287
|
Run the `portal-deploy` flow inline.
|
|
288
|
+
Let the deploy command stream its own progress. Do not wrap the long-running publish step in `tail`.
|
|
290
289
|
|
|
291
290
|
This phase is part of onboarding by default. Do not treat publish as an optional
|
|
292
291
|
afterthought unless the user explicitly says they want to stop at local preview.
|
|
@@ -321,27 +320,32 @@ Save checkpoint with phase `published` once the hosted URL is live or clearly ac
|
|
|
321
320
|
|
|
322
321
|
### Phase 9: Final summary
|
|
323
322
|
|
|
324
|
-
Show a compact final
|
|
323
|
+
Show a compact final handoff card. At this point, prioritize the hosted link,
|
|
324
|
+
the active access credentials, and one or two clear next actions.
|
|
325
325
|
|
|
326
326
|
```
|
|
327
327
|
════════════════════════════════════════
|
|
328
|
-
First portal
|
|
328
|
+
First portal live
|
|
329
329
|
|
|
330
330
|
Portal: <slug> (<company>)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
Cloud: <hosted login URL or share URL>
|
|
332
|
+
Login: <username if login is the active access mode>
|
|
333
|
+
Pass: <password if login is the active access mode>
|
|
334
334
|
|
|
335
|
-
Next:
|
|
336
|
-
|
|
335
|
+
Next: /portal-update <slug>
|
|
336
|
+
/portal-share <slug> (if a direct hosted link would help)
|
|
337
337
|
════════════════════════════════════════
|
|
338
338
|
```
|
|
339
339
|
|
|
340
340
|
Rules:
|
|
341
341
|
|
|
342
|
-
-
|
|
343
|
-
-
|
|
342
|
+
- if login is the active access mode, include the username and password here so the user does not have to scroll back
|
|
343
|
+
- if a hosted share URL exists because the user asked for one after publish, show that as the primary cloud link
|
|
344
|
+
- otherwise show the hosted login URL as the cloud link
|
|
345
|
+
- keep the summary short; avoid extra explanation once the portal is live
|
|
344
346
|
- if publish is still finalizing, say so plainly instead of pretending it is live
|
|
347
|
+
In that case, show the local preview link and point the user to `/portal-status`
|
|
348
|
+
instead of pretending the hosted link is ready.
|
|
345
349
|
|
|
346
350
|
After the final summary, delete the checkpoint:
|
|
347
351
|
|
|
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
|
|
|
99
99
|
|
|
100
100
|
If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
|
|
101
101
|
|
|
102
|
-
Read `skills/shared/runtime-principles.md`
|
|
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
|
|
|
@@ -183,12 +183,15 @@ Run this in a single Bash tool call so the browser opens immediately.
|
|
|
183
183
|
After opening, display:
|
|
184
184
|
|
|
185
185
|
```
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
Preview ready
|
|
187
|
+
|
|
188
|
+
URL: http://localhost:3000/client/<slug>
|
|
189
|
+
Login: /portal-credentials <slug>
|
|
188
190
|
```
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
Then add one short next step:
|
|
193
|
+
|
|
194
|
+
- `If it looks right, publish it with /portal-deploy.`
|
|
192
195
|
|
|
193
196
|
If the portal is the example portal (slug is "example"), no credentials are needed -- it is publicly accessible by design.
|
|
194
197
|
|
|
@@ -88,12 +88,15 @@ Run this in a single Bash tool call so the browser opens immediately.
|
|
|
88
88
|
After opening, display:
|
|
89
89
|
|
|
90
90
|
```
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
Preview ready
|
|
92
|
+
|
|
93
|
+
URL: http://localhost:3000/client/<slug>
|
|
94
|
+
Login: /portal-credentials <slug>
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
Then add one short next step:
|
|
98
|
+
|
|
99
|
+
- `If it looks right, publish it with /portal-deploy.`
|
|
97
100
|
|
|
98
101
|
If the portal is the example portal (slug is "example"), no credentials are needed -- it is publicly accessible by design.
|
|
99
102
|
|
|
@@ -93,9 +93,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
|
|
|
93
93
|
|
|
94
94
|
If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
|
|
95
95
|
|
|
96
|
-
Read `skills/shared/runtime-principles.md`
|
|
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
|
|
|
@@ -269,21 +269,17 @@ Replace the placeholder values with the actual values collected. Use `chmod 600`
|
|
|
269
269
|
|
|
270
270
|
### Step 10: Print success summary
|
|
271
271
|
|
|
272
|
-
Display a
|
|
272
|
+
Display a short success handoff:
|
|
273
273
|
|
|
274
274
|
```
|
|
275
275
|
Showpane setup complete!
|
|
276
276
|
|
|
277
|
-
App
|
|
278
|
-
|
|
279
|
-
Organization: Acme Consulting (acme-consulting)
|
|
280
|
-
Brand color: #2563eb
|
|
281
|
-
Telemetry: anonymous
|
|
277
|
+
App: /path/to/showpane-project
|
|
278
|
+
Org: Acme Consulting (acme-consulting)
|
|
282
279
|
|
|
283
|
-
Next
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
3. View the example portal: open http://localhost:3000/client/example
|
|
280
|
+
Next:
|
|
281
|
+
/portal-onboard
|
|
282
|
+
/portal-create <slug>
|
|
287
283
|
```
|
|
288
284
|
|
|
289
285
|
### Step 11: Record learning (optional)
|
|
@@ -186,21 +186,17 @@ Replace the placeholder values with the actual values collected. Use `chmod 600`
|
|
|
186
186
|
|
|
187
187
|
### Step 10: Print success summary
|
|
188
188
|
|
|
189
|
-
Display a
|
|
189
|
+
Display a short success handoff:
|
|
190
190
|
|
|
191
191
|
```
|
|
192
192
|
Showpane setup complete!
|
|
193
193
|
|
|
194
|
-
App
|
|
195
|
-
|
|
196
|
-
Organization: Acme Consulting (acme-consulting)
|
|
197
|
-
Brand color: #2563eb
|
|
198
|
-
Telemetry: anonymous
|
|
194
|
+
App: /path/to/showpane-project
|
|
195
|
+
Org: Acme Consulting (acme-consulting)
|
|
199
196
|
|
|
200
|
-
Next
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
3. View the example portal: open http://localhost:3000/client/example
|
|
197
|
+
Next:
|
|
198
|
+
/portal-onboard
|
|
199
|
+
/portal-create <slug>
|
|
204
200
|
```
|
|
205
201
|
|
|
206
202
|
### Step 11: Record learning (optional)
|
|
@@ -102,9 +102,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
|
|
|
102
102
|
|
|
103
103
|
If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
|
|
104
104
|
|
|
105
|
-
Read `skills/shared/runtime-principles.md`
|
|
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
|
|
|
@@ -153,17 +153,16 @@ Present the link in a clear, copy-friendly ASCII box:
|
|
|
153
153
|
|
|
154
154
|
```
|
|
155
155
|
════════════════════════════════════════
|
|
156
|
-
Share link
|
|
157
|
-
|
|
158
|
-
Expires: never (until credentials rotate)
|
|
156
|
+
Share link: https://portal.example.com/client/whzan/s/eyJ...
|
|
157
|
+
Revoke: rotate credentials or deactivate the portal
|
|
159
158
|
════════════════════════════════════════
|
|
160
159
|
```
|
|
161
160
|
|
|
162
161
|
### Step 4: Provide usage guidance
|
|
163
162
|
|
|
164
|
-
After displaying the link, add
|
|
163
|
+
After displaying the link, add one short note:
|
|
165
164
|
|
|
166
|
-
|
|
165
|
+
`This is for the hosted portal. It stays valid until credentials rotate or the portal is deactivated.`
|
|
167
166
|
|
|
168
167
|
If the user has previously generated share links (check learnings for patterns), you can skip the explanation and just show the link.
|
|
169
168
|
|
|
@@ -58,17 +58,16 @@ Present the link in a clear, copy-friendly ASCII box:
|
|
|
58
58
|
|
|
59
59
|
```
|
|
60
60
|
════════════════════════════════════════
|
|
61
|
-
Share link
|
|
62
|
-
|
|
63
|
-
Expires: never (until credentials rotate)
|
|
61
|
+
Share link: https://portal.example.com/client/whzan/s/eyJ...
|
|
62
|
+
Revoke: rotate credentials or deactivate the portal
|
|
64
63
|
════════════════════════════════════════
|
|
65
64
|
```
|
|
66
65
|
|
|
67
66
|
### Step 4: Provide usage guidance
|
|
68
67
|
|
|
69
|
-
After displaying the link, add
|
|
68
|
+
After displaying the link, add one short note:
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
`This is for the hosted portal. It stays valid until credentials rotate or the portal is deactivated.`
|
|
72
71
|
|
|
73
72
|
If the user has previously generated share links (check learnings for patterns), you can skip the explanation and just show the link.
|
|
74
73
|
|
|
@@ -99,9 +99,9 @@ If `RECENT_SKILLS` is shown, suggest the likely next skill:
|
|
|
99
99
|
|
|
100
100
|
If `RECENT_LEARNINGS` is shown, review them before proceeding. Apply them where relevant but do not mention them unless they materially affect the current task.
|
|
101
101
|
|
|
102
|
-
Read `skills/shared/runtime-principles.md`
|
|
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;
|