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.
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-09T11:52:07.132Z",
4
- "scaffoldVersion": "0.2.0",
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": "1f930dd1f133c1f97a94fe3acb8db34372cf4c01ffdb2b3ff4ca72f9494121e9",
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": "a0778e98016957ce4ccdb58fae9be0a4a71e1961fd39a2dfca1612914179b1ae",
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",
@@ -1 +1 @@
1
- 0.2.0
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 templates = [
9
- {
10
- name: "Sales Follow-up",
11
- description: "Meeting notes, next steps, documents",
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
- portalCount = await prisma.clientPortal.count();
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 flex flex-col">
45
- {/* Hero zone */}
46
- <div className="bg-gradient-to-b from-[#2C5278] to-[#5A8BB5] px-4 py-16 md:py-24 text-center relative overflow-hidden">
47
- <div
48
- className="absolute inset-0 opacity-[0.07]"
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
- <p className="mt-6 text-white/90 text-lg">
69
- Create professional client portals with Claude Code.
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
- {/* Action zone */}
75
- <div className="flex-1 bg-[#FDFBF7] px-4 py-12 md:py-16">
76
- <div className="max-w-lg mx-auto">
77
- {/* Steps */}
78
- <ol className="space-y-4">
79
- <li className="border border-gray-200 rounded-lg p-5 bg-white">
80
- <div className="flex items-start gap-4">
81
- <span className="flex-shrink-0 w-7 h-7 rounded-full bg-gray-900 text-white text-sm font-medium flex items-center justify-center">
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
- </li>
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
- <li className="border border-gray-200 rounded-lg p-5 bg-white">
96
- <div className="flex items-start gap-4">
97
- <span className="flex-shrink-0 w-7 h-7 rounded-full bg-gray-900 text-white text-sm font-medium flex items-center justify-center">
98
- 2
99
- </span>
100
- <div className="min-w-0">
101
- <p className="text-gray-900 font-medium mb-2">
102
- Use the fast path slash command
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
- </div>
109
- </li>
99
+ ))}
100
+ </div>
101
+ </section>
110
102
 
111
- <li className="border border-gray-200 rounded-lg p-5 bg-white">
112
- <div className="flex items-start gap-4">
113
- <span className="flex-shrink-0 w-7 h-7 rounded-full bg-gray-900 text-white text-sm font-medium flex items-center justify-center">
114
- 3
115
- </span>
116
- <div className="min-w-0">
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
- </li>
126
- </ol>
127
-
128
- <p className="mt-4 text-xs text-gray-400 text-center">
129
- Don&apos;t have Claude Code?{" "}
130
- <a
131
- href="https://claude.ai/code"
132
- className="text-blue-600 hover:underline"
133
- target="_blank"
134
- rel="noopener noreferrer"
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
- {/* Template previews */}
141
- <div className="mt-12">
142
- <p className="text-sm font-medium text-gray-500 text-center mb-4">
143
- Claude Code generates portals from templates
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
- <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
146
- {templates.map((t) => (
147
- <div
148
- key={t.name}
149
- className="border border-gray-200 rounded-lg p-4 bg-white text-center"
150
- >
151
- <t.icon className="h-5 w-5 text-gray-400 mx-auto mb-2" />
152
- <p className="text-sm font-medium text-gray-900">{t.name}</p>
153
- <p className="text-xs text-gray-500 mt-1">{t.description}</p>
154
- </div>
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
+ }
@@ -1 +1 @@
1
- 1.1.0 (requires app >= 0.2.0)
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 --org-id omitted.");
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
- let orgId = getArg("--org-id");
20
+ const orgId = getArg("--org-id");
21
+ const orgSlug = getArg("--org-slug");
21
22
 
22
- if (!orgId) {
23
- const firstOrg = await prisma.organization.findFirst({ select: { id: true } });
24
- if (!firstOrg) fail("No organizations found");
25
- orgId = firstOrg.id;
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: orgId },
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {