showpane 0.4.2 → 0.4.3

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/README.md CHANGED
@@ -24,6 +24,18 @@ Flags:
24
24
  Authenticate with Showpane Cloud for hosted portal deployment.
25
25
  This is auth only — org creation and billing happen in the Showpane Cloud web app.
26
26
 
27
+ ### `showpane claude`
28
+ Resume your Showpane workspace by launching Claude Code in the right project directory.
29
+
30
+ Flags:
31
+
32
+ - `--project <name-or-path>` — open a specific remembered workspace
33
+ - `--yes` / `--name <company>` — only used when no workspace exists yet and Showpane needs to create the first one
34
+ - `--verbose` — show raw setup logs during first-time onboarding
35
+
36
+ ### `showpane projects`
37
+ List remembered Showpane workspaces and show which one is currently active for global skills.
38
+
27
39
  ### `showpane sync`
28
40
  Install or refresh the global Showpane toolchain and Claude Code skills for the current CLI version.
29
41
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-09T11:11:24.207Z",
3
+ "generatedAt": "2026-04-09T11:52:07.132Z",
4
4
  "scaffoldVersion": "0.2.0",
5
5
  "files": {
6
6
  ".env.example": "0dd692f1c7e6bcabdf5dbdfe9abb73797d79d8e90da150d6098b63ddc695dc29",
@@ -47,7 +47,7 @@
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": "f5c88e28ee73acfdde347ceae6602717923a01357f56375a66e9cdd403b06992",
50
+ "src/app/page.tsx": "a0778e98016957ce4ccdb58fae9be0a4a71e1961fd39a2dfca1612914179b1ae",
51
51
  "src/components/portal-login.tsx": "8b0d91bb28674e1102fd2e5b5ddcc3a93755dd806fbd3d1b2dbea2646cffca5e",
52
52
  "src/components/portal-shell.tsx": "a4e16e118ef93f79e71fb69e80f1fac6e6fff90f0fbdacdf8deb821a57656877",
53
53
  "src/lib/abuse-controls.ts": "d79d58d93267aca48ad0b7b9b91f753c9a3c27263e4e98daf768a950c44a6fc6",
@@ -2,6 +2,8 @@ import { prisma } from "@/lib/db";
2
2
  import { getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
3
3
  import Link from "next/link";
4
4
  import { Presentation, Briefcase, UserPlus } from "lucide-react";
5
+ import os from "node:os";
6
+ import path from "node:path";
5
7
 
6
8
  const templates = [
7
9
  {
@@ -23,6 +25,10 @@ const templates = [
23
25
 
24
26
  export default async function Home() {
25
27
  let portalCount = 0;
28
+ const showpaneBinDir = path.join(os.homedir(), ".showpane", "bin");
29
+ const resumeCommand = (process.env.PATH ?? "").split(path.delimiter).includes(showpaneBinDir)
30
+ ? "showpane claude"
31
+ : "npx showpane claude";
26
32
  try {
27
33
  if (isRuntimeSnapshotMode()) {
28
34
  const state = await getRuntimeState();
@@ -77,10 +83,10 @@ export default async function Home() {
77
83
  </span>
78
84
  <div className="min-w-0">
79
85
  <p className="text-gray-900 font-medium mb-2">
80
- Open your terminal in this directory
86
+ In a terminal, reopen your Showpane workspace
81
87
  </p>
82
88
  <code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded overflow-x-auto">
83
- cd app
89
+ {resumeCommand}
84
90
  </code>
85
91
  </div>
86
92
  </div>
@@ -93,10 +99,10 @@ export default async function Home() {
93
99
  </span>
94
100
  <div className="min-w-0">
95
101
  <p className="text-gray-900 font-medium mb-2">
96
- Launch Claude Code
102
+ Use the fast path slash command
97
103
  </p>
98
104
  <code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded">
99
- claude
105
+ /portal create acme-health
100
106
  </code>
101
107
  </div>
102
108
  </div>
@@ -109,7 +115,7 @@ export default async function Home() {
109
115
  </span>
110
116
  <div className="min-w-0">
111
117
  <p className="text-gray-900 font-medium mb-2">
112
- Tell it what to create
118
+ Or tell it what to create
113
119
  </p>
114
120
  <code className="block text-sm text-gray-300 font-mono bg-[#111827] px-3 py-2 rounded overflow-x-auto">
115
121
  Create a portal for my call with [client name]
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ var {
23
23
  unlinkSync,
24
24
  writeFileSync
25
25
  } = fs;
26
- var { dirname, join, resolve } = path;
26
+ var { basename, dirname, join, resolve } = path;
27
27
  var { homedir } = os;
28
28
  var RESET = "\x1B[0m";
29
29
  var BOLD = "\x1B[1m";
@@ -34,6 +34,7 @@ var WHITE = "\x1B[37m";
34
34
  var RED = "\x1B[31m";
35
35
  var API_BASE = "https://app.showpane.com";
36
36
  var SHOWPANE_HOME = join(homedir(), ".showpane");
37
+ var SHOWPANE_BIN_DIR = join(SHOWPANE_HOME, "bin");
37
38
  var TOOLCHAIN_DIR = join(SHOWPANE_HOME, "toolchains");
38
39
  var CURRENT_TOOLCHAIN_LINK = join(SHOWPANE_HOME, "current");
39
40
  var CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
@@ -61,6 +62,9 @@ function error(message) {
61
62
  function printCreateUsage() {
62
63
  console.log("Usage: showpane [--yes --name <company>] [--no-open] [--verbose]");
63
64
  }
65
+ function printClaudeUsage() {
66
+ console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company>] [--verbose]");
67
+ }
64
68
  function printBanner() {
65
69
  const banner = `
66
70
  ${BOLD}${WHITE} \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
@@ -121,6 +125,51 @@ function parseCreateArgs(args) {
121
125
  }
122
126
  return options;
123
127
  }
128
+ function parseClaudeArgs(args) {
129
+ const options = {
130
+ noOpen: false,
131
+ verbose: false,
132
+ yes: false
133
+ };
134
+ for (let index = 0; index < args.length; index += 1) {
135
+ const arg = args[index];
136
+ if (arg === "--yes") {
137
+ options.yes = true;
138
+ continue;
139
+ }
140
+ if (arg === "--no-open") {
141
+ options.noOpen = true;
142
+ continue;
143
+ }
144
+ if (arg === "--verbose") {
145
+ options.verbose = true;
146
+ continue;
147
+ }
148
+ if (arg === "--name") {
149
+ const value = args[index + 1];
150
+ if (!value || value.startsWith("--")) {
151
+ throw new Error("Missing value for --name.");
152
+ }
153
+ options.companyName = value.trim();
154
+ index += 1;
155
+ continue;
156
+ }
157
+ if (arg === "--project") {
158
+ const value = args[index + 1];
159
+ if (!value || value.startsWith("--")) {
160
+ throw new Error("Missing value for --project.");
161
+ }
162
+ options.project = value.trim();
163
+ index += 1;
164
+ continue;
165
+ }
166
+ throw new Error(`Unknown argument: ${arg}`);
167
+ }
168
+ if (options.project && options.companyName) {
169
+ throw new Error("`--project` can not be combined with `--name`.");
170
+ }
171
+ return options;
172
+ }
124
173
  function toSlug(name) {
125
174
  return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
126
175
  }
@@ -179,6 +228,21 @@ function maybePrintShowpaneUpdateMessage(currentVersion) {
179
228
  } catch {
180
229
  }
181
230
  }
231
+ function normalizePathForComparison(targetPath) {
232
+ const normalized = path.normalize(resolve(targetPath));
233
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
234
+ }
235
+ function isShowpaneShimOnPath() {
236
+ const pathValue = process.env.PATH ?? "";
237
+ const binDir = normalizePathForComparison(SHOWPANE_BIN_DIR);
238
+ return pathValue.split(path.delimiter).filter(Boolean).some((entry) => normalizePathForComparison(entry) === binDir);
239
+ }
240
+ function getResumeCommand() {
241
+ return isShowpaneShimOnPath() ? "showpane claude" : "npx showpane claude";
242
+ }
243
+ function getResumeHint() {
244
+ return isShowpaneShimOnPath() ? null : `Optional: add ${SHOWPANE_BIN_DIR} to your PATH to use ${BOLD}showpane${RESET} directly.`;
245
+ }
182
246
  function getCommandOutput(errorLike) {
183
247
  const error2 = errorLike;
184
248
  const stdout = typeof error2?.stdout === "string" ? error2.stdout : error2?.stdout?.toString() ?? "";
@@ -209,13 +273,48 @@ function runInstallerCommand(command2, cwd, env, verbose) {
209
273
  }
210
274
  runQuiet(command2, cwd, env);
211
275
  }
212
- function stepStart(label) {
213
- blue(label);
276
+ var activeSpinner = null;
277
+ function renderSpinner(label, frame, startedAt) {
278
+ const elapsedSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
279
+ const elapsed = elapsedSeconds > 0 ? ` ${DIM}${elapsedSeconds}s${RESET}` : "";
280
+ process.stdout.write(`\r ${BLUE}${frame}${RESET} ${label}...${elapsed}\x1B[K`);
281
+ }
282
+ function stopSpinner(clearLine = true) {
283
+ if (!activeSpinner) return;
284
+ clearInterval(activeSpinner.interval);
285
+ if (clearLine) {
286
+ process.stdout.write("\r\x1B[K");
287
+ } else {
288
+ process.stdout.write("\n");
289
+ }
290
+ activeSpinner = null;
291
+ }
292
+ function stepStart(label, spinner = false) {
293
+ stopSpinner();
294
+ if (!spinner) {
295
+ blue(label);
296
+ return;
297
+ }
298
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
299
+ let frameIndex = 0;
300
+ const startedAt = Date.now();
301
+ renderSpinner(label, frames[frameIndex], startedAt);
302
+ const interval = setInterval(() => {
303
+ frameIndex = (frameIndex + 1) % frames.length;
304
+ renderSpinner(label, frames[frameIndex], startedAt);
305
+ }, 80);
306
+ activeSpinner = {
307
+ interval,
308
+ label,
309
+ startedAt
310
+ };
214
311
  }
215
312
  function stepSuccess(label) {
313
+ stopSpinner();
216
314
  green(label);
217
315
  }
218
316
  function stepFailure(label, errorLike, hint) {
317
+ stopSpinner();
219
318
  error(`${label} failed.`);
220
319
  const message = errorLike instanceof Error ? errorLike.message : String(errorLike);
221
320
  const output = errorLike instanceof StepCommandError ? errorLike.output : getCommandOutput(errorLike);
@@ -232,6 +331,13 @@ function stepFailure(label, errorLike, hint) {
232
331
  }
233
332
  process.exit(1);
234
333
  }
334
+ function attachSpinnerCleanup() {
335
+ const cleanup = () => stopSpinner();
336
+ process.on("exit", cleanup);
337
+ process.on("SIGINT", cleanup);
338
+ process.on("SIGTERM", cleanup);
339
+ }
340
+ attachSpinnerCleanup();
235
341
  function openBrowser(url) {
236
342
  const platform = process.platform;
237
343
  const command2 = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
@@ -245,6 +351,101 @@ function writeJson(filePath, value) {
245
351
  writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
246
352
  `);
247
353
  }
354
+ function getShowpaneConfigPath() {
355
+ return join(SHOWPANE_HOME, "config.json");
356
+ }
357
+ function readShowpaneConfig() {
358
+ const configPath = getShowpaneConfigPath();
359
+ if (!existsSync(configPath)) {
360
+ return {};
361
+ }
362
+ return readJson(configPath);
363
+ }
364
+ function writeShowpaneConfig(config) {
365
+ ensureDir(SHOWPANE_HOME);
366
+ const configPath = getShowpaneConfigPath();
367
+ writeJson(configPath, config);
368
+ chmodSync(configPath, 384);
369
+ }
370
+ function findWorkspaceRoot(startPath) {
371
+ let currentPath = resolve(startPath);
372
+ while (true) {
373
+ if (existsSync(join(currentPath, "package.json")) && existsSync(join(currentPath, "prisma", "schema.prisma")) && existsSync(getProjectMetadataPath(currentPath))) {
374
+ return currentPath;
375
+ }
376
+ const parentPath = dirname(currentPath);
377
+ if (parentPath === currentPath) {
378
+ return null;
379
+ }
380
+ currentPath = parentPath;
381
+ }
382
+ }
383
+ function defaultWorkspaceEntry(projectPath, overrides) {
384
+ return {
385
+ name: basename(projectPath),
386
+ path: resolve(projectPath),
387
+ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString(),
388
+ deployMode: "local",
389
+ orgSlug: "",
390
+ ...overrides
391
+ };
392
+ }
393
+ function getWorkspaceEntries(config) {
394
+ const workspaces = [...config.workspaces ?? []];
395
+ const activePath = config.app_path ? resolve(config.app_path) : null;
396
+ if (activePath && !workspaces.some((workspace) => normalizePathForComparison(workspace.path) === normalizePathForComparison(activePath))) {
397
+ workspaces.push(defaultWorkspaceEntry(activePath, {
398
+ deployMode: typeof config.deploy_mode === "string" ? config.deploy_mode : "local",
399
+ orgSlug: typeof config.orgSlug === "string" ? config.orgSlug : ""
400
+ }));
401
+ }
402
+ return workspaces.map((workspace) => ({
403
+ ...workspace,
404
+ path: resolve(workspace.path),
405
+ lastUsedAt: workspace.lastUsedAt || (/* @__PURE__ */ new Date(0)).toISOString(),
406
+ deployMode: workspace.deployMode || "local",
407
+ orgSlug: workspace.orgSlug || ""
408
+ })).sort((left, right) => right.lastUsedAt.localeCompare(left.lastUsedAt));
409
+ }
410
+ function setActiveWorkspace(config, workspace) {
411
+ config.app_path = workspace.path;
412
+ config.deploy_mode = workspace.deployMode;
413
+ config.orgSlug = workspace.orgSlug;
414
+ }
415
+ function upsertWorkspace(config, workspace, makeActive = true) {
416
+ const workspaces = getWorkspaceEntries(config).filter(
417
+ (entry) => normalizePathForComparison(entry.path) !== normalizePathForComparison(workspace.path)
418
+ );
419
+ workspaces.push(workspace);
420
+ config.workspaces = workspaces.sort((left, right) => right.lastUsedAt.localeCompare(left.lastUsedAt));
421
+ if (makeActive) {
422
+ setActiveWorkspace(config, workspace);
423
+ }
424
+ }
425
+ function updateWorkspaceFromConfig(config, projectPath, overrides) {
426
+ const workspace = defaultWorkspaceEntry(projectPath, {
427
+ deployMode: typeof config.deploy_mode === "string" ? config.deploy_mode : "local",
428
+ orgSlug: typeof config.orgSlug === "string" ? config.orgSlug : "",
429
+ ...overrides
430
+ });
431
+ upsertWorkspace(config, workspace, true);
432
+ return workspace;
433
+ }
434
+ function ensureShowpaneShim() {
435
+ ensureDir(SHOWPANE_BIN_DIR);
436
+ const shellShim = join(SHOWPANE_BIN_DIR, "showpane");
437
+ writeFileSync(
438
+ shellShim,
439
+ '#!/bin/sh\nexec npx --yes showpane "$@"\n'
440
+ );
441
+ chmodSync(shellShim, 493);
442
+ if (process.platform === "win32") {
443
+ writeFileSync(
444
+ join(SHOWPANE_BIN_DIR, "showpane.cmd"),
445
+ "@echo off\r\nnpx --yes showpane %*\r\n"
446
+ );
447
+ }
448
+ }
248
449
  function ensureDir(dirPath) {
249
450
  mkdirSync(dirPath, { recursive: true });
250
451
  }
@@ -581,7 +782,9 @@ function installSharedSkillProjection(toolchainRoot) {
581
782
  process.platform === "win32" ? "junction" : "dir"
582
783
  );
583
784
  }
584
- function printCreateSuccessCard(projectRoot, projectName, url) {
785
+ function printCreateSuccessCard(projectRoot, url) {
786
+ const resumeCommand = getResumeCommand();
787
+ const resumeHint = getResumeHint();
585
788
  console.log();
586
789
  console.log(` ${GREEN}Showpane is ready${RESET}`);
587
790
  console.log();
@@ -590,10 +793,14 @@ function printCreateSuccessCard(projectRoot, projectName, url) {
590
793
  console.log(` ${BOLD}Demo:${RESET} example / demo-only-password`);
591
794
  console.log();
592
795
  console.log(` ${BOLD}Next:${RESET}`);
593
- console.log(` ${DIM}cd ${projectName} && claude${RESET}`);
796
+ console.log(` ${DIM}${resumeCommand}${RESET}`);
594
797
  console.log();
595
798
  console.log(` ${BOLD}Try:${RESET}`);
596
799
  console.log(` ${DIM}Create a portal for my call with Acme Health${RESET}`);
800
+ if (resumeHint) {
801
+ console.log();
802
+ console.log(` ${DIM}${resumeHint}${RESET}`);
803
+ }
597
804
  console.log();
598
805
  }
599
806
  function startDevServer(projectRoot, databaseUrl, noOpen, verbose) {
@@ -650,6 +857,93 @@ function startDevServer(projectRoot, databaseUrl, noOpen, verbose) {
650
857
  });
651
858
  });
652
859
  }
860
+ function resolveWorkspaceSelection(config, specifier) {
861
+ const workspaces = getWorkspaceEntries(config);
862
+ if (!specifier) {
863
+ return workspaces;
864
+ }
865
+ const asPathRoot = findWorkspaceRoot(specifier);
866
+ if (asPathRoot) {
867
+ return [defaultWorkspaceEntry(asPathRoot)];
868
+ }
869
+ const normalizedSpecifier = normalizePathForComparison(specifier);
870
+ const pathMatches = workspaces.filter(
871
+ (workspace) => normalizePathForComparison(workspace.path) === normalizedSpecifier
872
+ );
873
+ if (pathMatches.length > 0) {
874
+ return pathMatches;
875
+ }
876
+ const nameMatches = workspaces.filter((workspace) => workspace.name === specifier);
877
+ return nameMatches;
878
+ }
879
+ async function promptWorkspaceSelection(workspaces) {
880
+ console.log();
881
+ console.log(` ${BOLD}Select a Showpane workspace${RESET}`);
882
+ console.log();
883
+ for (const [index, workspace] of workspaces.entries()) {
884
+ console.log(` ${index + 1}. ${workspace.name}`);
885
+ console.log(` ${DIM}${workspace.path}${RESET}`);
886
+ console.log(` ${DIM}Last used: ${workspace.lastUsedAt}${RESET}`);
887
+ }
888
+ console.log();
889
+ while (true) {
890
+ const answer = await ask(` ${BOLD}Choose a workspace [1-${workspaces.length}] or q to cancel:${RESET} `);
891
+ if (!answer) continue;
892
+ if (answer.toLowerCase() === "q") {
893
+ process.exit(0);
894
+ }
895
+ const selectedIndex = Number.parseInt(answer, 10);
896
+ if (Number.isInteger(selectedIndex) && selectedIndex >= 1 && selectedIndex <= workspaces.length) {
897
+ return workspaces[selectedIndex - 1];
898
+ }
899
+ }
900
+ }
901
+ function printWorkspaceList(config) {
902
+ printBanner();
903
+ const workspaces = getWorkspaceEntries(config);
904
+ if (workspaces.length === 0) {
905
+ console.log();
906
+ blue("No Showpane workspaces found");
907
+ console.log(` ${DIM}Run: npx showpane${RESET}`);
908
+ console.log();
909
+ return;
910
+ }
911
+ const activePath = config.app_path ? normalizePathForComparison(config.app_path) : null;
912
+ console.log();
913
+ console.log(` ${BOLD}Showpane workspaces${RESET}`);
914
+ console.log();
915
+ for (const [index, workspace] of workspaces.entries()) {
916
+ const isActive = activePath !== null && normalizePathForComparison(workspace.path) === activePath;
917
+ const marker = isActive ? "*" : " ";
918
+ console.log(` ${marker} ${index + 1}. ${workspace.name}`);
919
+ console.log(` ${workspace.path}`);
920
+ console.log(` ${DIM}Last used: ${workspace.lastUsedAt}${RESET}`);
921
+ }
922
+ console.log();
923
+ }
924
+ async function openClaudeInWorkspace(workspace) {
925
+ if (!commandExists("claude")) {
926
+ throw new Error("Claude Code is not installed or not on PATH.");
927
+ }
928
+ blue(`Opening ${workspace.name} workspace`);
929
+ console.log(` ${DIM}${workspace.path}${RESET}`);
930
+ console.log();
931
+ await new Promise((resolveLaunch, rejectLaunch) => {
932
+ const child = spawn("claude", [], {
933
+ cwd: workspace.path,
934
+ stdio: "inherit",
935
+ env: {
936
+ ...process.env,
937
+ SHOWPANE_APP_PATH: workspace.path,
938
+ SHOWPANE_TOOLCHAIN_DIR: CURRENT_TOOLCHAIN_LINK
939
+ }
940
+ });
941
+ child.on("error", rejectLaunch);
942
+ child.on("close", (code) => {
943
+ process.exit(code ?? 0);
944
+ });
945
+ });
946
+ }
653
947
  function installSkillProjection(toolchainRoot) {
654
948
  removePath(join(CLAUDE_SKILLS_DIR, "showpane"));
655
949
  installSharedSkillProjection(toolchainRoot);
@@ -736,6 +1030,7 @@ async function createProject(args) {
736
1030
  process.exit(1);
737
1031
  }
738
1032
  printBanner();
1033
+ ensureShowpaneShim();
739
1034
  const companyName = options.companyName ?? await ask(` ${BOLD}What's your company name?${RESET} `);
740
1035
  if (!companyName) {
741
1036
  error("Company name is required.");
@@ -793,6 +1088,13 @@ AUTH_SECRET="${authSecret}"
793
1088
  let toolchainInfo;
794
1089
  try {
795
1090
  toolchainInfo = syncToolchain(bundleRoot, showpaneVersion, false);
1091
+ const config = readShowpaneConfig();
1092
+ updateWorkspaceFromConfig(config, projectRoot, {
1093
+ name: dirName,
1094
+ deployMode: "local",
1095
+ orgSlug: ""
1096
+ });
1097
+ writeShowpaneConfig(config);
796
1098
  writeProjectState(
797
1099
  projectRoot,
798
1100
  showpaneVersion,
@@ -824,7 +1126,7 @@ AUTH_SECRET="${authSecret}"
824
1126
  `Run ${BOLD}cd ${dirName} && npm run dev${RESET} for more detail.`
825
1127
  );
826
1128
  }
827
- printCreateSuccessCard(projectRoot, dirName, serverStart.url);
1129
+ printCreateSuccessCard(projectRoot, serverStart.url);
828
1130
  serverStart.devServer.on("close", (code) => {
829
1131
  if (code !== 0) {
830
1132
  error(`Dev server exited with code ${code}`);
@@ -842,6 +1144,7 @@ async function syncCurrentToolchain() {
842
1144
  const packageRoot2 = getPackageRoot();
843
1145
  const bundleRoot = getLocalBundleRoot(packageRoot2);
844
1146
  const showpaneVersion = getPackageVersion(packageRoot2);
1147
+ ensureShowpaneShim();
845
1148
  printBanner();
846
1149
  maybePrintShowpaneUpdateMessage(showpaneVersion);
847
1150
  console.log();
@@ -914,8 +1217,73 @@ async function upgradeProject(args) {
914
1217
  bundleSource.cleanup();
915
1218
  }
916
1219
  }
1220
+ async function openClaude(args) {
1221
+ let options;
1222
+ try {
1223
+ options = parseClaudeArgs(args);
1224
+ } catch (errorLike) {
1225
+ printBanner();
1226
+ console.log();
1227
+ error(errorLike instanceof Error ? errorLike.message : String(errorLike));
1228
+ printClaudeUsage();
1229
+ process.exit(1);
1230
+ }
1231
+ ensureShowpaneShim();
1232
+ const config = readShowpaneConfig();
1233
+ const workspaces = getWorkspaceEntries(config);
1234
+ if (workspaces.length === 0 && !options.project) {
1235
+ blue("No Showpane workspace found. Let's create one first.");
1236
+ console.log();
1237
+ await createProject(args);
1238
+ return;
1239
+ }
1240
+ if (workspaces.length > 0 && (options.companyName || options.yes)) {
1241
+ error("`--yes` and `--name` only apply when creating the first workspace.");
1242
+ printClaudeUsage();
1243
+ process.exit(1);
1244
+ }
1245
+ let workspace;
1246
+ if (options.project) {
1247
+ const matches = resolveWorkspaceSelection(config, options.project);
1248
+ if (matches.length === 0) {
1249
+ error(`Could not find a Showpane workspace matching: ${options.project}`);
1250
+ process.exit(1);
1251
+ }
1252
+ if (matches.length > 1) {
1253
+ error(`Multiple workspaces matched: ${options.project}`);
1254
+ for (const match of matches) {
1255
+ console.error(` ${match.name} \u2014 ${match.path}`);
1256
+ }
1257
+ process.exit(1);
1258
+ }
1259
+ workspace = matches[0];
1260
+ } else if (workspaces.length === 1) {
1261
+ workspace = workspaces[0];
1262
+ } else if (!process.stdout.isTTY) {
1263
+ error("Multiple Showpane workspaces found. Use `showpane claude --project <name-or-path>`.");
1264
+ process.exit(1);
1265
+ } else {
1266
+ workspace = await promptWorkspaceSelection(workspaces);
1267
+ }
1268
+ const workspaceRoot = findWorkspaceRoot(workspace.path) ?? resolve(workspace.path);
1269
+ const selectedWorkspace = defaultWorkspaceEntry(workspaceRoot, {
1270
+ name: workspace.name || basename(workspaceRoot),
1271
+ deployMode: workspace.deployMode || "local",
1272
+ orgSlug: workspace.orgSlug || ""
1273
+ });
1274
+ upsertWorkspace(config, selectedWorkspace, true);
1275
+ writeShowpaneConfig(config);
1276
+ try {
1277
+ await openClaudeInWorkspace(selectedWorkspace);
1278
+ } catch (errorLike) {
1279
+ error(errorLike instanceof Error ? errorLike.message : String(errorLike));
1280
+ console.error(`Hint: Install Claude Code first, or use ${getResumeCommand()} later.`);
1281
+ process.exit(1);
1282
+ }
1283
+ }
917
1284
  async function login() {
918
1285
  printBanner();
1286
+ ensureShowpaneShim();
919
1287
  blue("Authenticating with Showpane...");
920
1288
  console.log();
921
1289
  const initRes = await fetch(`${API_BASE}/api/cli/init`, { method: "POST" });
@@ -944,26 +1312,23 @@ async function login() {
944
1312
  }
945
1313
  const data = await pollRes.json();
946
1314
  if (data.status !== "approved") continue;
947
- const configDir = join(homedir(), ".showpane");
948
- mkdirSync(configDir, { recursive: true });
949
- const configPath = join(configDir, "config.json");
950
- writeFileSync(
951
- configPath,
952
- JSON.stringify(
953
- {
954
- accessToken: data.accessToken,
955
- accessTokenExpiresAt: data.tokenExpiresAt,
956
- orgSlug: data.orgSlug,
957
- portalUrl: data.portalUrl,
958
- vercelProjectId: data.vercelProjectId,
959
- app_path: process.cwd(),
960
- deploy_mode: "cloud"
961
- },
962
- null,
963
- 2
964
- )
965
- );
966
- chmodSync(configPath, 384);
1315
+ const config = readShowpaneConfig();
1316
+ config.accessToken = data.accessToken;
1317
+ config.accessTokenExpiresAt = data.tokenExpiresAt;
1318
+ config.orgSlug = data.orgSlug;
1319
+ config.portalUrl = data.portalUrl;
1320
+ config.vercelProjectId = data.vercelProjectId;
1321
+ const currentWorkspace = findWorkspaceRoot(process.cwd()) ?? (config.app_path ? findWorkspaceRoot(config.app_path) ?? resolve(config.app_path) : null);
1322
+ if (currentWorkspace) {
1323
+ updateWorkspaceFromConfig(config, currentWorkspace, {
1324
+ name: basename(currentWorkspace),
1325
+ deployMode: "cloud",
1326
+ orgSlug: data.orgSlug
1327
+ });
1328
+ } else {
1329
+ config.deploy_mode = "cloud";
1330
+ }
1331
+ writeShowpaneConfig(config);
967
1332
  console.log();
968
1333
  green(`Authenticated! Connected to ${BOLD}${data.orgSlug}${RESET}`);
969
1334
  console.log();
@@ -983,6 +1348,18 @@ if (command === "login") {
983
1348
  error(String(err));
984
1349
  process.exit(1);
985
1350
  });
1351
+ } else if (command === "claude") {
1352
+ openClaude(process.argv.slice(3)).catch((err) => {
1353
+ error(String(err));
1354
+ process.exit(1);
1355
+ });
1356
+ } else if (command === "projects") {
1357
+ try {
1358
+ printWorkspaceList(readShowpaneConfig());
1359
+ } catch (err) {
1360
+ error(String(err));
1361
+ process.exit(1);
1362
+ }
986
1363
  } else if (command === "sync") {
987
1364
  syncCurrentToolchain().catch((err) => {
988
1365
  error(String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {