showpane 0.4.3 → 0.4.4
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 +5 -4
- package/bundle/scaffold/VERSION +1 -1
- package/bundle/scaffold/src/app/page.tsx +108 -130
- package/bundle/scaffold/src/components/copy-button.tsx +49 -0
- package/bundle/toolchain/VERSION +1 -1
- package/bundle/toolchain/bin/list-portals.ts +25 -9
- package/bundle/toolchain/skills/portal-create/SKILL.md +18 -0
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-
|
|
4
|
-
"scaffoldVersion": "0.2.
|
|
3
|
+
"generatedAt": "2026-04-09T13:24:17.013Z",
|
|
4
|
+
"scaffoldVersion": "0.2.1",
|
|
5
5
|
"files": {
|
|
6
6
|
".env.example": "0dd692f1c7e6bcabdf5dbdfe9abb73797d79d8e90da150d6098b63ddc695dc29",
|
|
7
7
|
".gitignore": "998e5f43865ea56ac79a05acfd5d4b0d696f310bd5325a1ed458c3d40154d437",
|
|
8
|
-
"VERSION": "
|
|
8
|
+
"VERSION": "71015c979ccb0fc8a0be7ca0ae83046ab045cdc2c8faa09fb2f0f7e440f9b4a6",
|
|
9
9
|
"docker-compose.yml": "420fd123da019c22f03662933537e24779b4c2c91f90c23abfec5965cd0f35ce",
|
|
10
10
|
"docker/Caddyfile": "d9c58086986795f5b3e42ff9b5942e60b8df946a1a0c40351381616c0b4d2bed",
|
|
11
11
|
"docker/Dockerfile": "340470e3735ea53b2c03003a13a91361652291add33c40a2bf13e6af2a8cb73a",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"src/app/api/health/route.ts": "78fff55707372ce0cd6e9e49ef4f049622bc43cc42916d3f83e0162409d678b1",
|
|
48
48
|
"src/app/globals.css": "28dcda76006d0e6af01b6dcf1a315dc5b5b6931c880fc53fd6565ff09d5dd13a",
|
|
49
49
|
"src/app/layout.tsx": "c17aabeb2b486f023e777230343ace6cc06840f641a10b9dd9f65e092018f82f",
|
|
50
|
-
"src/app/page.tsx": "
|
|
50
|
+
"src/app/page.tsx": "1c48a37632621373db7730756aacde708e29d6f1bd14062b63736fe8057ce842",
|
|
51
|
+
"src/components/copy-button.tsx": "2f3d1d8a6a0a570c8d78e19c3c15519c44af17b5d8893ae5a5f57db5ecce7077",
|
|
51
52
|
"src/components/portal-login.tsx": "8b0d91bb28674e1102fd2e5b5ddcc3a93755dd806fbd3d1b2dbea2646cffca5e",
|
|
52
53
|
"src/components/portal-shell.tsx": "a4e16e118ef93f79e71fb69e80f1fac6e6fff90f0fbdacdf8deb821a57656877",
|
|
53
54
|
"src/lib/abuse-controls.ts": "d79d58d93267aca48ad0b7b9b91f753c9a3c27263e4e98daf768a950c44a6fc6",
|
package/bundle/scaffold/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.1
|
|
@@ -1,26 +1,16 @@
|
|
|
1
|
+
import { CopyButton } from "@/components/copy-button";
|
|
2
|
+
import { resolveDefaultOrganizationId } from "@/lib/client-portals";
|
|
1
3
|
import { prisma } from "@/lib/db";
|
|
2
4
|
import { getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
|
|
5
|
+
import { ArrowUpRight, BookOpen, Command, MessageSquareQuote } from "lucide-react";
|
|
3
6
|
import Link from "next/link";
|
|
4
|
-
import { Presentation, Briefcase, UserPlus } from "lucide-react";
|
|
5
7
|
import os from "node:os";
|
|
6
8
|
import path from "node:path";
|
|
7
9
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
icon: Presentation,
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: "Consulting",
|
|
16
|
-
description: "Project overview, deliverables, timeline",
|
|
17
|
-
icon: Briefcase,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: "Onboarding",
|
|
21
|
-
description: "Welcome, setup steps, resources",
|
|
22
|
-
icon: UserPlus,
|
|
23
|
-
},
|
|
10
|
+
const GUIDE_URL = "https://app.showpane.com/docs/first-portal";
|
|
11
|
+
const PROMPT_EXAMPLES = [
|
|
12
|
+
"Create a portal for my client Acme based on my call transcript, which is here: [paste it]",
|
|
13
|
+
"Create a portal for my client Acme based on my call from earlier today. Use the Granola MCP to grab the transcript.",
|
|
24
14
|
];
|
|
25
15
|
|
|
26
16
|
export default async function Home() {
|
|
@@ -34,144 +24,132 @@ export default async function Home() {
|
|
|
34
24
|
const state = await getRuntimeState();
|
|
35
25
|
portalCount = state?.portals.length ?? 0;
|
|
36
26
|
} else {
|
|
37
|
-
|
|
27
|
+
const organizationId = await resolveDefaultOrganizationId();
|
|
28
|
+
if (organizationId) {
|
|
29
|
+
portalCount = await prisma.clientPortal.count({
|
|
30
|
+
where: { organizationId },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
38
33
|
}
|
|
39
34
|
} catch {
|
|
40
35
|
// DB not ready yet — show welcome page with 0 portals
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
return (
|
|
44
|
-
<main className="min-h-screen
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
style={{
|
|
50
|
-
backgroundImage: "radial-gradient(circle, white 1px, transparent 1px)",
|
|
51
|
-
backgroundSize: "24px 24px",
|
|
52
|
-
}}
|
|
53
|
-
/>
|
|
54
|
-
<div className="relative">
|
|
55
|
-
<h1 className="sr-only">SHOWPANE</h1>
|
|
56
|
-
<div
|
|
57
|
-
role="img"
|
|
58
|
-
aria-label="SHOWPANE"
|
|
59
|
-
className="font-mono text-white text-[0.45rem] leading-[1.1] sm:text-[0.55rem] md:text-xs whitespace-pre select-none mx-auto w-fit"
|
|
60
|
-
>
|
|
61
|
-
{`███████╗██╗ ██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ███╗ ██╗███████╗
|
|
62
|
-
██╔════╝██║ ██║██╔═══██╗██║ ██║██╔══██╗██╔══██╗████╗ ██║██╔════╝
|
|
63
|
-
███████╗███████║██║ ██║██║ █╗ ██║██████╔╝███████║██╔██╗ ██║█████╗
|
|
64
|
-
╚════██║██╔══██║██║ ██║██║███╗██║██╔═══╝ ██╔══██║██║╚██╗██║██╔══╝
|
|
65
|
-
███████║██║ ██║╚██████╔╝╚███╔███╔╝██║ ██║ ██║██║ ╚████║███████╗
|
|
66
|
-
╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝`}
|
|
39
|
+
<main className="min-h-screen bg-[#f6f1e8] text-slate-900">
|
|
40
|
+
<div className="bg-[radial-gradient(circle_at_top,_rgba(255,255,255,0.24),_transparent_48%),linear-gradient(180deg,_#214668_0%,_#3d6f9c_100%)] px-4 py-16 text-white sm:py-20">
|
|
41
|
+
<div className="mx-auto max-w-3xl">
|
|
42
|
+
<div className="inline-flex items-center rounded-full border border-white/20 bg-white/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-white/80">
|
|
43
|
+
Local app running
|
|
67
44
|
</div>
|
|
68
|
-
<
|
|
69
|
-
|
|
45
|
+
<h1 className="mt-6 text-4xl font-semibold tracking-tight sm:text-5xl">
|
|
46
|
+
Your Showpane workspace is ready
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="mt-4 max-w-2xl text-base leading-7 text-white/82 sm:text-lg">
|
|
49
|
+
Open Claude in your Showpane workspace and create your first client portal.
|
|
70
50
|
</p>
|
|
71
51
|
</div>
|
|
72
52
|
</div>
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
1
|
|
83
|
-
</span>
|
|
84
|
-
<div className="min-w-0">
|
|
85
|
-
<p className="text-gray-900 font-medium mb-2">
|
|
86
|
-
In a terminal, reopen your Showpane workspace
|
|
87
|
-
</p>
|
|
88
|
-
<code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded overflow-x-auto">
|
|
89
|
-
{resumeCommand}
|
|
90
|
-
</code>
|
|
54
|
+
<div className="px-4 py-10 sm:py-12">
|
|
55
|
+
<div className="mx-auto max-w-3xl space-y-5">
|
|
56
|
+
<section className="rounded-[28px] border border-slate-200 bg-slate-950 p-6 text-white shadow-[0_24px_80px_rgba(15,23,42,0.16)] sm:p-7">
|
|
57
|
+
<div className="flex items-start justify-between gap-4">
|
|
58
|
+
<div>
|
|
59
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-white/80">
|
|
60
|
+
<Command className="h-4 w-4" />
|
|
61
|
+
Start with Claude
|
|
91
62
|
</div>
|
|
63
|
+
<p className="mt-3 text-sm leading-6 text-white/72">
|
|
64
|
+
This opens Claude in the right Showpane workspace so you can start creating portals immediately.
|
|
65
|
+
</p>
|
|
92
66
|
</div>
|
|
93
|
-
|
|
67
|
+
<CopyButton text={resumeCommand} invert />
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="mt-5 rounded-2xl border border-white/10 bg-white/5 p-4">
|
|
71
|
+
<code className="block overflow-x-auto font-mono text-sm text-white sm:text-[15px]">
|
|
72
|
+
{resumeCommand}
|
|
73
|
+
</code>
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<section className="rounded-[28px] border border-slate-200 bg-white p-6 shadow-[0_20px_70px_rgba(15,23,42,0.07)] sm:p-7">
|
|
78
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700">
|
|
79
|
+
<MessageSquareQuote className="h-4 w-4" />
|
|
80
|
+
What to say to Claude
|
|
81
|
+
</div>
|
|
94
82
|
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
83
|
+
<div className="mt-5 space-y-3">
|
|
84
|
+
{PROMPT_EXAMPLES.map((example, index) => (
|
|
85
|
+
<div
|
|
86
|
+
key={example}
|
|
87
|
+
className="rounded-2xl border border-slate-200 bg-slate-50 p-4"
|
|
88
|
+
>
|
|
89
|
+
<div className="flex items-start justify-between gap-3">
|
|
90
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">
|
|
91
|
+
Example {index + 1}
|
|
92
|
+
</p>
|
|
93
|
+
<CopyButton text={example} />
|
|
94
|
+
</div>
|
|
95
|
+
<p className="mt-3 whitespace-pre-wrap pr-2 font-mono text-sm leading-6 text-slate-700">
|
|
96
|
+
{example}
|
|
103
97
|
</p>
|
|
104
|
-
<code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded">
|
|
105
|
-
/portal create acme-health
|
|
106
|
-
</code>
|
|
107
98
|
</div>
|
|
108
|
-
|
|
109
|
-
</
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
</section>
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<p className="text-gray-900 font-medium mb-2">
|
|
118
|
-
Or tell it what to create
|
|
119
|
-
</p>
|
|
120
|
-
<code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded overflow-x-auto">
|
|
121
|
-
Create a portal for my call with [client name]
|
|
122
|
-
</code>
|
|
103
|
+
<section className="rounded-[32px] border border-[#b8d2ee] bg-[linear-gradient(135deg,_#f8fcff_0%,_#d8ebfb_55%,_#bed8f1_100%)] p-6 shadow-[0_24px_90px_rgba(61,111,156,0.18)] sm:p-8">
|
|
104
|
+
<div className="flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between">
|
|
105
|
+
<div className="max-w-xl">
|
|
106
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-[#214668]">
|
|
107
|
+
<BookOpen className="h-4 w-4" />
|
|
108
|
+
Need help creating your first portal?
|
|
123
109
|
</div>
|
|
110
|
+
<p className="mt-3 text-base leading-7 text-[#284f74]">
|
|
111
|
+
Follow the step-by-step guide with examples, best practices, and a walkthrough video.
|
|
112
|
+
</p>
|
|
124
113
|
</div>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Install it here
|
|
137
|
-
</a>
|
|
138
|
-
</p>
|
|
114
|
+
<a
|
|
115
|
+
href={GUIDE_URL}
|
|
116
|
+
className="inline-flex items-center justify-center gap-2 rounded-full bg-[#214668] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#18344d]"
|
|
117
|
+
target="_blank"
|
|
118
|
+
rel="noopener noreferrer"
|
|
119
|
+
>
|
|
120
|
+
Open Creating Your First Portal
|
|
121
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
122
|
+
</a>
|
|
123
|
+
</div>
|
|
124
|
+
</section>
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
<div className="space-y-3 pt-2 text-center">
|
|
127
|
+
<p className="text-xs text-slate-500">
|
|
128
|
+
Need Claude Code first?{" "}
|
|
129
|
+
<a
|
|
130
|
+
href="https://claude.ai/code"
|
|
131
|
+
className="font-medium text-[#214668] underline decoration-slate-300 underline-offset-4 hover:decoration-[#214668]"
|
|
132
|
+
target="_blank"
|
|
133
|
+
rel="noopener noreferrer"
|
|
134
|
+
>
|
|
135
|
+
Install it here
|
|
136
|
+
</a>
|
|
144
137
|
</p>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
|
|
139
|
+
<div className="space-y-1 text-xs text-slate-400">
|
|
140
|
+
{portalCount > 0 && (
|
|
141
|
+
<p>
|
|
142
|
+
You have {portalCount} portal{portalCount !== 1 ? "s" : ""}.{" "}
|
|
143
|
+
<Link href="/client" className="text-[#214668] hover:underline">
|
|
144
|
+
Go to login
|
|
145
|
+
</Link>
|
|
146
|
+
</p>
|
|
147
|
+
)}
|
|
148
|
+
<p>Powered by Claude Code</p>
|
|
156
149
|
</div>
|
|
157
150
|
</div>
|
|
158
151
|
</div>
|
|
159
152
|
</div>
|
|
160
|
-
|
|
161
|
-
{/* Footer zone */}
|
|
162
|
-
<footer className="bg-[#FDFBF7] px-4 pb-8 text-center space-y-2">
|
|
163
|
-
{portalCount > 0 && (
|
|
164
|
-
<p className="text-sm text-gray-500">
|
|
165
|
-
You have {portalCount} portal{portalCount !== 1 ? "s" : ""}.{" "}
|
|
166
|
-
<Link href="/client" className="text-blue-600 hover:underline">
|
|
167
|
-
Go to login
|
|
168
|
-
</Link>
|
|
169
|
-
</p>
|
|
170
|
-
)}
|
|
171
|
-
<p className="text-xs text-gray-400">
|
|
172
|
-
Powered by Claude Code
|
|
173
|
-
</p>
|
|
174
|
-
</footer>
|
|
175
153
|
</main>
|
|
176
154
|
);
|
|
177
155
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Check, Copy } from "lucide-react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
type CopyButtonProps = {
|
|
7
|
+
text: string;
|
|
8
|
+
invert?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function CopyButton({ text, invert = false }: CopyButtonProps) {
|
|
12
|
+
const [copied, setCopied] = useState(false);
|
|
13
|
+
const [copyError, setCopyError] = useState(false);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!copied && !copyError) return;
|
|
17
|
+
|
|
18
|
+
const timeout = window.setTimeout(() => {
|
|
19
|
+
setCopied(false);
|
|
20
|
+
setCopyError(false);
|
|
21
|
+
}, 2000);
|
|
22
|
+
|
|
23
|
+
return () => window.clearTimeout(timeout);
|
|
24
|
+
}, [copied, copyError]);
|
|
25
|
+
|
|
26
|
+
async function handleCopy() {
|
|
27
|
+
try {
|
|
28
|
+
await navigator.clipboard.writeText(text);
|
|
29
|
+
setCopied(true);
|
|
30
|
+
} catch {
|
|
31
|
+
setCopyError(true);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={handleCopy}
|
|
39
|
+
className={
|
|
40
|
+
invert
|
|
41
|
+
? "inline-flex items-center gap-1.5 rounded-full border border-white/20 bg-white/10 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-white/20"
|
|
42
|
+
: "inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs font-semibold text-slate-700 transition hover:border-slate-300 hover:bg-slate-50"
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
46
|
+
{copied ? "Copied!" : copyError ? "Failed" : "Copy"}
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
}
|
package/bundle/toolchain/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.1 (requires app >= 0.2.1)
|
|
@@ -9,30 +9,46 @@ async function main() {
|
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
10
|
|
|
11
11
|
if (args.includes("--help")) {
|
|
12
|
-
console.log("Usage: list-portals [--org-id <orgId>]");
|
|
13
|
-
console.log("Lists all client portals. Defaults to first organization if
|
|
12
|
+
console.log("Usage: list-portals [--org-id <orgId>] [--org-slug <slug>]");
|
|
13
|
+
console.log("Lists all client portals. Defaults to first organization if no org filter is provided.");
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const prisma = new PrismaClient();
|
|
18
18
|
try {
|
|
19
19
|
const getArg = (flag: string) => { const i = args.indexOf(flag); return i !== -1 ? args[i + 1] : undefined; };
|
|
20
|
-
|
|
20
|
+
const orgId = getArg("--org-id");
|
|
21
|
+
const orgSlug = getArg("--org-slug");
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const organization = orgId
|
|
24
|
+
? await prisma.organization.findUnique({
|
|
25
|
+
where: { id: orgId },
|
|
26
|
+
select: { id: true, name: true, slug: true },
|
|
27
|
+
})
|
|
28
|
+
: orgSlug
|
|
29
|
+
? await prisma.organization.findUnique({
|
|
30
|
+
where: { slug: orgSlug },
|
|
31
|
+
select: { id: true, name: true, slug: true },
|
|
32
|
+
})
|
|
33
|
+
: await prisma.organization.findFirst({
|
|
34
|
+
orderBy: { createdAt: "asc" },
|
|
35
|
+
select: { id: true, name: true, slug: true },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!organization) fail("No organizations found");
|
|
27
39
|
|
|
28
40
|
const portals = await prisma.clientPortal.findMany({
|
|
29
|
-
where: { organizationId:
|
|
41
|
+
where: { organizationId: organization.id },
|
|
30
42
|
include: { organization: { select: { name: true } } },
|
|
31
43
|
orderBy: { createdAt: "desc" },
|
|
32
44
|
});
|
|
33
45
|
|
|
34
46
|
console.log(JSON.stringify({
|
|
35
47
|
ok: true,
|
|
48
|
+
orgId: organization.id,
|
|
49
|
+
orgSlug: organization.slug,
|
|
50
|
+
orgName: organization.name,
|
|
51
|
+
total: portals.length,
|
|
36
52
|
portals: portals.map((p) => ({
|
|
37
53
|
slug: p.slug,
|
|
38
54
|
companyName: p.companyName,
|
|
@@ -71,6 +71,24 @@ mention them unless they directly affect the current task.
|
|
|
71
71
|
|
|
72
72
|
## Steps
|
|
73
73
|
|
|
74
|
+
### Step 0: Check first-portal guide eligibility
|
|
75
|
+
|
|
76
|
+
Before asking for the slug or generating anything, determine the existing portal count
|
|
77
|
+
for the active organization:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" --org-slug "$ORG_SLUG"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The script returns JSON with `total` and `orgId` for the active org. Reuse that
|
|
84
|
+
`orgId` for the slug validation and portal creation commands below.
|
|
85
|
+
|
|
86
|
+
- If `total` is `0`, `1`, or `2`, say this line exactly once:
|
|
87
|
+
`If helpful, the first-portal guide has examples and best practices: https://app.showpane.com/docs/first-portal`
|
|
88
|
+
- If `total` is `3` or higher, do not mention the guide.
|
|
89
|
+
- Mention it once per invocation only. Do not repeat it later in the same flow.
|
|
90
|
+
- Use the org-scoped portal count directly. Do not use learnings or timeline heuristics.
|
|
91
|
+
|
|
74
92
|
### Step 1: Determine the portal slug
|
|
75
93
|
|
|
76
94
|
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.
|