showpane 0.4.4 → 0.4.6

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-09T13:24:17.013Z",
4
- "scaffoldVersion": "0.2.1",
3
+ "generatedAt": "2026-04-09T14:26:32.270Z",
4
+ "scaffoldVersion": "0.2.3",
5
5
  "files": {
6
6
  ".env.example": "0dd692f1c7e6bcabdf5dbdfe9abb73797d79d8e90da150d6098b63ddc695dc29",
7
7
  ".gitignore": "998e5f43865ea56ac79a05acfd5d4b0d696f310bd5325a1ed458c3d40154d437",
8
- "VERSION": "71015c979ccb0fc8a0be7ca0ae83046ab045cdc2c8faa09fb2f0f7e440f9b4a6",
8
+ "VERSION": "3ab94c04d24986f3af288ba1cda2c0bbddbc5a89dff097182805f54578e1ea75",
9
9
  "docker-compose.yml": "420fd123da019c22f03662933537e24779b4c2c91f90c23abfec5965cd0f35ce",
10
10
  "docker/Caddyfile": "d9c58086986795f5b3e42ff9b5942e60b8df946a1a0c40351381616c0b4d2bed",
11
11
  "docker/Dockerfile": "340470e3735ea53b2c03003a13a91361652291add33c40a2bf13e6af2a8cb73a",
@@ -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": "1c48a37632621373db7730756aacde708e29d6f1bd14062b63736fe8057ce842",
50
+ "src/app/page.tsx": "732ea54f313386b65bce1170785379b27bb26b5da26b833b1e50c3713b87be1a",
51
51
  "src/components/copy-button.tsx": "2f3d1d8a6a0a570c8d78e19c3c15519c44af17b5d8893ae5a5f57db5ecce7077",
52
52
  "src/components/portal-login.tsx": "8b0d91bb28674e1102fd2e5b5ddcc3a93755dd806fbd3d1b2dbea2646cffca5e",
53
53
  "src/components/portal-shell.tsx": "a4e16e118ef93f79e71fb69e80f1fac6e6fff90f0fbdacdf8deb821a57656877",
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.3
@@ -4,6 +4,7 @@ import { prisma } from "@/lib/db";
4
4
  import { getRuntimeState, isRuntimeSnapshotMode } from "@/lib/runtime-state";
5
5
  import { ArrowUpRight, BookOpen, Command, MessageSquareQuote } from "lucide-react";
6
6
  import Link from "next/link";
7
+ import { existsSync, readFileSync } from "node:fs";
7
8
  import os from "node:os";
8
9
  import path from "node:path";
9
10
 
@@ -16,9 +17,22 @@ const PROMPT_EXAMPLES = [
16
17
  export default async function Home() {
17
18
  let portalCount = 0;
18
19
  const showpaneBinDir = path.join(os.homedir(), ".showpane", "bin");
19
- const resumeCommand = (process.env.PATH ?? "").split(path.delimiter).includes(showpaneBinDir)
20
- ? "showpane claude"
21
- : "npx showpane claude";
20
+ const configPath = path.join(os.homedir(), ".showpane", "config.json");
21
+ const configShellPathConfigured = existsSync(configPath)
22
+ ? (() => {
23
+ try {
24
+ const raw = readFileSync(configPath, "utf8");
25
+ return Boolean((JSON.parse(raw) as { shellPathConfigured?: boolean }).shellPathConfigured);
26
+ } catch {
27
+ return false;
28
+ }
29
+ })()
30
+ : false;
31
+ const prefersCanonicalCommand =
32
+ configShellPathConfigured ||
33
+ (process.env.PATH ?? "").split(path.delimiter).includes(showpaneBinDir);
34
+ const primaryCommand = "showpane claude";
35
+ const fallbackCommand = "npx showpane claude";
22
36
  try {
23
37
  if (isRuntimeSnapshotMode()) {
24
38
  const state = await getRuntimeState();
@@ -46,7 +60,7 @@ export default async function Home() {
46
60
  Your Showpane workspace is ready
47
61
  </h1>
48
62
  <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.
63
+ Open a new terminal window, run Showpane with Claude, and create your first client portal.
50
64
  </p>
51
65
  </div>
52
66
  </div>
@@ -61,15 +75,21 @@ export default async function Home() {
61
75
  Start with Claude
62
76
  </div>
63
77
  <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.
78
+ Open a new terminal window and run this command there. Your current terminal is running the local app, so this command belongs in a fresh one.
65
79
  </p>
80
+ {!prefersCanonicalCommand && (
81
+ <p className="mt-2 text-xs leading-5 text-white/60">
82
+ If <code className="font-mono text-white">{primaryCommand}</code> isn&apos;t available in your shell yet, use{" "}
83
+ <code className="font-mono text-white">{fallbackCommand}</code>.
84
+ </p>
85
+ )}
66
86
  </div>
67
- <CopyButton text={resumeCommand} invert />
87
+ <CopyButton text={primaryCommand} invert />
68
88
  </div>
69
89
 
70
90
  <div className="mt-5 rounded-2xl border border-white/10 bg-white/5 p-4">
71
91
  <code className="block overflow-x-auto font-mono text-sm text-white sm:text-[15px]">
72
- {resumeCommand}
92
+ {primaryCommand}
73
93
  </code>
74
94
  </div>
75
95
  </section>
@@ -1 +1 @@
1
- 1.1.1 (requires app >= 0.2.1)
1
+ 1.1.3 (requires app >= 0.2.3)
package/dist/index.js CHANGED
@@ -243,35 +243,146 @@ function getResumeCommand() {
243
243
  function getResumeHint() {
244
244
  return isShowpaneShimOnPath() ? null : `Optional: add ${SHOWPANE_BIN_DIR} to your PATH to use ${BOLD}showpane${RESET} directly.`;
245
245
  }
246
+ function shellPathExportLine() {
247
+ return `export PATH="$HOME/.showpane/bin:$PATH"`;
248
+ }
249
+ function detectShellProfile() {
250
+ if (process.platform === "win32") {
251
+ return null;
252
+ }
253
+ const shell = basename(process.env.SHELL || "");
254
+ if (shell === "zsh") {
255
+ return join(homedir(), ".zshrc");
256
+ }
257
+ if (shell === "bash") {
258
+ return process.platform === "darwin" ? join(homedir(), ".bash_profile") : join(homedir(), ".bashrc");
259
+ }
260
+ if (shell === "fish") {
261
+ return join(homedir(), ".config", "fish", "config.fish");
262
+ }
263
+ return null;
264
+ }
265
+ function ensureShellPathEntry(profilePath) {
266
+ ensureDir(dirname(profilePath));
267
+ const exportLine = shellPathExportLine();
268
+ const contents = existsSync(profilePath) ? readFileSync(profilePath, "utf8") : "";
269
+ if (contents.includes(exportLine) || contents.includes(SHOWPANE_BIN_DIR)) {
270
+ return false;
271
+ }
272
+ const prefix = contents.length > 0 && !contents.endsWith("\n") ? "\n" : "";
273
+ writeFileSync(profilePath, `${contents}${prefix}${exportLine}
274
+ `);
275
+ return true;
276
+ }
277
+ async function maybeConfigureShellPath(config, options) {
278
+ const configuredProfile = typeof config.shellPathConfiguredProfile === "string" ? config.shellPathConfiguredProfile : null;
279
+ if (isShowpaneShimOnPath()) {
280
+ config.shellPathConfigured = true;
281
+ config.shellPathPrompted = true;
282
+ return {
283
+ command: "showpane claude",
284
+ configured: true,
285
+ profilePath: configuredProfile
286
+ };
287
+ }
288
+ if (config.shellPathConfigured) {
289
+ return {
290
+ command: "showpane claude",
291
+ configured: true,
292
+ profilePath: configuredProfile
293
+ };
294
+ }
295
+ if (options.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
296
+ config.shellPathPrompted = true;
297
+ config.shellPathConfigured = false;
298
+ return {
299
+ command: "npx showpane claude",
300
+ configured: false,
301
+ profilePath: configuredProfile
302
+ };
303
+ }
304
+ const profilePath = detectShellProfile();
305
+ if (!profilePath) {
306
+ config.shellPathPrompted = true;
307
+ config.shellPathConfigured = false;
308
+ return {
309
+ command: "npx showpane claude",
310
+ configured: false,
311
+ profilePath: null
312
+ };
313
+ }
314
+ const answer = await ask(
315
+ ` ${BOLD}Add Showpane to your PATH so 'showpane' works in future terminals?${RESET} ${DIM}(recommended) [Y/n]${RESET} `
316
+ );
317
+ if (answer && !["y", "yes"].includes(answer.toLowerCase())) {
318
+ config.shellPathPrompted = true;
319
+ config.shellPathConfigured = false;
320
+ config.shellPathConfiguredProfile = profilePath;
321
+ return {
322
+ command: "npx showpane claude",
323
+ configured: false,
324
+ profilePath
325
+ };
326
+ }
327
+ ensureShellPathEntry(profilePath);
328
+ config.shellPathPrompted = true;
329
+ config.shellPathConfigured = true;
330
+ config.shellPathConfiguredProfile = profilePath;
331
+ console.log();
332
+ green(`Added Showpane to your PATH in ${DIM}${profilePath}${RESET}`);
333
+ return {
334
+ command: "showpane claude",
335
+ configured: true,
336
+ profilePath
337
+ };
338
+ }
246
339
  function getCommandOutput(errorLike) {
247
340
  const error2 = errorLike;
248
341
  const stdout = typeof error2?.stdout === "string" ? error2.stdout : error2?.stdout?.toString() ?? "";
249
342
  const stderr = typeof error2?.stderr === "string" ? error2.stderr : error2?.stderr?.toString() ?? "";
250
343
  return [stdout, stderr].filter(Boolean).join("\n").trim();
251
344
  }
252
- function runQuiet(command2, cwd, env) {
253
- try {
254
- execSync(command2, {
255
- cwd,
256
- env: env ? { ...process.env, ...env } : process.env,
257
- encoding: "utf8",
258
- stdio: ["ignore", "pipe", "pipe"],
259
- maxBuffer: 20 * 1024 * 1024
345
+ async function runQuietAsync(command2, cwd, env) {
346
+ const child = spawn(command2, {
347
+ cwd,
348
+ env: env ? { ...process.env, ...env } : process.env,
349
+ shell: true,
350
+ stdio: ["ignore", "pipe", "pipe"]
351
+ });
352
+ let output = "";
353
+ const appendOutput = (chunk) => {
354
+ output += chunk.toString();
355
+ if (output.length > 20 * 1024 * 1024) {
356
+ output = output.slice(-20 * 1024 * 1024);
357
+ }
358
+ };
359
+ child.stdout?.on("data", appendOutput);
360
+ child.stderr?.on("data", appendOutput);
361
+ await new Promise((resolvePromise, rejectPromise) => {
362
+ child.on("error", (errorLike) => {
363
+ rejectPromise(
364
+ new StepCommandError(
365
+ errorLike instanceof Error ? errorLike.message : String(errorLike),
366
+ output.trim()
367
+ )
368
+ );
260
369
  });
261
- } catch (errorLike) {
262
- const output = getCommandOutput(errorLike);
263
- throw new StepCommandError(
264
- errorLike instanceof Error ? errorLike.message : String(errorLike),
265
- output
266
- );
267
- }
370
+ child.on("close", (code, signal) => {
371
+ if (code === 0) {
372
+ resolvePromise();
373
+ return;
374
+ }
375
+ const reason = signal ? `Command terminated with signal ${signal}` : `Command exited with code ${code ?? "unknown"}`;
376
+ rejectPromise(new StepCommandError(reason, output.trim()));
377
+ });
378
+ });
268
379
  }
269
- function runInstallerCommand(command2, cwd, env, verbose) {
380
+ async function runInstallerCommand(command2, cwd, env, verbose) {
270
381
  if (verbose) {
271
382
  run(command2, cwd, env);
272
383
  return;
273
384
  }
274
- runQuiet(command2, cwd, env);
385
+ await runQuietAsync(command2, cwd, env);
275
386
  }
276
387
  var activeSpinner = null;
277
388
  function renderSpinner(label, frame, startedAt) {
@@ -331,6 +442,18 @@ function stepFailure(label, errorLike, hint) {
331
442
  }
332
443
  process.exit(1);
333
444
  }
445
+ function shouldUseSpinner(verbose) {
446
+ return process.stdout.isTTY && !verbose;
447
+ }
448
+ function stepStartForCreate(label, options) {
449
+ stepStart(label, shouldUseSpinner(options.verbose));
450
+ }
451
+ function stepSuccessForCreate(label) {
452
+ stepSuccess(label);
453
+ }
454
+ function stepFailureForCreate(label, errorLike, hint) {
455
+ stepFailure(label, errorLike, hint);
456
+ }
334
457
  function attachSpinnerCleanup() {
335
458
  const cleanup = () => stopSpinner();
336
459
  process.on("exit", cleanup);
@@ -679,39 +802,39 @@ function applyUpgradePlan(projectRoot, scaffoldSource, plan) {
679
802
  cpSync(sourcePath, targetPath);
680
803
  }
681
804
  }
682
- function installDependencies(projectRoot, verbose) {
805
+ async function installDependencies(projectRoot, verbose) {
683
806
  if (existsSync(join(projectRoot, "package-lock.json"))) {
684
807
  if (verbose === void 0) {
685
808
  run("npm ci", projectRoot, getInstallerEnv());
686
809
  } else {
687
- runInstallerCommand("npm ci", projectRoot, getInstallerEnv(), verbose);
810
+ await runInstallerCommand("npm ci", projectRoot, getInstallerEnv(), verbose);
688
811
  }
689
812
  } else {
690
813
  if (verbose === void 0) {
691
814
  run("npm install", projectRoot, getInstallerEnv());
692
815
  } else {
693
- runInstallerCommand("npm install", projectRoot, getInstallerEnv(), verbose);
816
+ await runInstallerCommand("npm install", projectRoot, getInstallerEnv(), verbose);
694
817
  }
695
818
  }
696
819
  }
697
- function generateLocalDatabase(projectRoot, databaseUrl, verbose) {
820
+ async function generateLocalDatabase(projectRoot, databaseUrl, verbose) {
698
821
  const env = getInstallerEnv({
699
822
  DATABASE_URL: databaseUrl
700
823
  });
701
824
  if (verbose === void 0) {
702
825
  run("npm run prisma:db-push", projectRoot, env);
703
826
  } else {
704
- runInstallerCommand("npm run prisma:db-push", projectRoot, env, verbose);
827
+ await runInstallerCommand("npm run prisma:db-push", projectRoot, env, verbose);
705
828
  }
706
829
  }
707
- function seedProject(projectRoot, databaseUrl, verbose) {
830
+ async function seedProject(projectRoot, databaseUrl, verbose) {
708
831
  const env = getInstallerEnv({
709
832
  DATABASE_URL: databaseUrl
710
833
  });
711
834
  if (verbose === void 0) {
712
835
  run("npx tsx prisma/seed.ts", projectRoot, env);
713
836
  } else {
714
- runInstallerCommand("npx tsx prisma/seed.ts", projectRoot, env, verbose);
837
+ await runInstallerCommand("npx tsx prisma/seed.ts", projectRoot, env, verbose);
715
838
  }
716
839
  }
717
840
  function maybeRunPostUpgradeSteps(projectRoot, changedPaths) {
@@ -782,9 +905,8 @@ function installSharedSkillProjection(toolchainRoot) {
782
905
  process.platform === "win32" ? "junction" : "dir"
783
906
  );
784
907
  }
785
- function printCreateSuccessCard(projectRoot, url) {
786
- const resumeCommand = getResumeCommand();
787
- const resumeHint = getResumeHint();
908
+ function printCreateSuccessCard(projectRoot, url, pathSetup) {
909
+ const resumeCommand = pathSetup.command;
788
910
  console.log();
789
911
  console.log(` ${GREEN}Showpane is ready${RESET}`);
790
912
  console.log();
@@ -792,14 +914,20 @@ function printCreateSuccessCard(projectRoot, url) {
792
914
  console.log(` ${BOLD}App:${RESET} ${url}`);
793
915
  console.log(` ${BOLD}Demo:${RESET} example / demo-only-password`);
794
916
  console.log();
795
- console.log(` ${BOLD}Next:${RESET}`);
917
+ console.log(` ${BOLD}Next (in a new terminal window):${RESET}`);
796
918
  console.log(` ${DIM}${resumeCommand}${RESET}`);
797
919
  console.log();
920
+ if (pathSetup.configured && pathSetup.profilePath) {
921
+ console.log(` ${DIM}Your current terminal is running the local app logs. Open a fresh terminal so ${BOLD}showpane${RESET}${DIM} is available from ${pathSetup.profilePath}.${RESET}`);
922
+ } else {
923
+ console.log(` ${DIM}Your current terminal is running the local app logs, so open a fresh terminal before you run that command.${RESET}`);
924
+ }
925
+ console.log();
798
926
  console.log(` ${BOLD}Try:${RESET}`);
799
927
  console.log(` ${DIM}Create a portal for my call with Acme Health${RESET}`);
800
- if (resumeHint) {
928
+ if (!pathSetup.configured) {
801
929
  console.log();
802
- console.log(` ${DIM}${resumeHint}${RESET}`);
930
+ console.log(` ${DIM}${getResumeHint()}${RESET}`);
803
931
  }
804
932
  console.log();
805
933
  }
@@ -1031,6 +1159,8 @@ async function createProject(args) {
1031
1159
  }
1032
1160
  printBanner();
1033
1161
  ensureShowpaneShim();
1162
+ const config = readShowpaneConfig();
1163
+ const pathSetup = await maybeConfigureShellPath(config, options);
1034
1164
  const companyName = options.companyName ?? await ask(` ${BOLD}What's your company name?${RESET} `);
1035
1165
  if (!companyName) {
1036
1166
  error("Company name is required.");
@@ -1046,20 +1176,20 @@ async function createProject(args) {
1046
1176
  console.log();
1047
1177
  blue(`Setting up ${BOLD}${companyName}${RESET} portal as ${DIM}${dirName}/${RESET}`);
1048
1178
  console.log();
1049
- stepStart("Create project");
1179
+ stepStartForCreate("Creating project", options);
1050
1180
  try {
1051
1181
  copyScaffoldFiles(join(bundleRoot, "scaffold"), projectRoot, scaffoldManifest);
1052
- stepSuccess("Project created");
1182
+ stepSuccessForCreate("Project created");
1053
1183
  } catch (errorLike) {
1054
- stepFailure("Create project", errorLike);
1184
+ stepFailureForCreate("Creating project", errorLike);
1055
1185
  }
1056
- stepStart("Install dependencies");
1186
+ stepStartForCreate("Installing dependencies", options);
1057
1187
  try {
1058
- installDependencies(projectRoot, options.verbose);
1059
- stepSuccess("Dependencies installed");
1188
+ await installDependencies(projectRoot, options.verbose);
1189
+ stepSuccessForCreate("Dependencies installed");
1060
1190
  } catch (errorLike) {
1061
- stepFailure(
1062
- "Install dependencies",
1191
+ stepFailureForCreate(
1192
+ "Installing dependencies",
1063
1193
  errorLike,
1064
1194
  "Check your Node.js version and network connection, then try again."
1065
1195
  );
@@ -1072,23 +1202,22 @@ async function createProject(args) {
1072
1202
  AUTH_SECRET="${authSecret}"
1073
1203
  `
1074
1204
  );
1075
- stepStart("Configure database");
1205
+ stepStartForCreate("Configuring database", options);
1076
1206
  try {
1077
- generateLocalDatabase(projectRoot, databaseUrl, options.verbose);
1078
- seedProject(projectRoot, databaseUrl, options.verbose);
1079
- stepSuccess("Database configured");
1207
+ await generateLocalDatabase(projectRoot, databaseUrl, options.verbose);
1208
+ await seedProject(projectRoot, databaseUrl, options.verbose);
1209
+ stepSuccessForCreate("Database configured");
1080
1210
  } catch (errorLike) {
1081
- stepFailure(
1082
- "Configure database",
1211
+ stepFailureForCreate(
1212
+ "Configuring database",
1083
1213
  errorLike,
1084
1214
  "Check Prisma setup and the generated .env file, then retry the install."
1085
1215
  );
1086
1216
  }
1087
- stepStart("Install Claude skills");
1217
+ stepStartForCreate("Installing Claude skills", options);
1088
1218
  let toolchainInfo;
1089
1219
  try {
1090
1220
  toolchainInfo = syncToolchain(bundleRoot, showpaneVersion, false);
1091
- const config = readShowpaneConfig();
1092
1221
  updateWorkspaceFromConfig(config, projectRoot, {
1093
1222
  name: dirName,
1094
1223
  deployMode: "local",
@@ -1102,15 +1231,15 @@ AUTH_SECRET="${authSecret}"
1102
1231
  toolchainInfo.toolchainVersion
1103
1232
  );
1104
1233
  tryInitializeGitRepo(projectRoot, false);
1105
- stepSuccess("Claude skills installed");
1234
+ stepSuccessForCreate("Claude skills installed");
1106
1235
  } catch (errorLike) {
1107
- stepFailure(
1108
- "Install Claude skills",
1236
+ stepFailureForCreate(
1237
+ "Installing Claude skills",
1109
1238
  errorLike,
1110
1239
  "Check permissions for ~/.showpane and ~/.claude/skills, then try again."
1111
1240
  );
1112
1241
  }
1113
- stepStart("Start app");
1242
+ stepStartForCreate("Starting app", options);
1114
1243
  let serverStart;
1115
1244
  try {
1116
1245
  serverStart = await startDevServer(
@@ -1120,13 +1249,13 @@ AUTH_SECRET="${authSecret}"
1120
1249
  options.verbose
1121
1250
  );
1122
1251
  } catch (errorLike) {
1123
- stepFailure(
1124
- "Start app",
1252
+ stepFailureForCreate(
1253
+ "Starting app",
1125
1254
  errorLike,
1126
1255
  `Run ${BOLD}cd ${dirName} && npm run dev${RESET} for more detail.`
1127
1256
  );
1128
1257
  }
1129
- printCreateSuccessCard(projectRoot, serverStart.url);
1258
+ printCreateSuccessCard(projectRoot, serverStart.url, pathSetup);
1130
1259
  serverStart.devServer.on("close", (code) => {
1131
1260
  if (code !== 0) {
1132
1261
  error(`Dev server exited with code ${code}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {