@generativereality/cctabs 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,7 +1,7 @@
1
1
  {
2
2
  "name": "cctabs",
3
3
  "description": "Claude Code tab manager. Terminal tabs as the UI, no tmux. Claude can orchestrate its own sibling sessions.",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "author": {
6
6
  "name": "generativereality",
7
7
  "url": "https://cctabs.com"
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { consola } from "consola";
11
11
  import * as p from "@clack/prompts";
12
12
  //#region package.json
13
13
  var name = "@generativereality/cctabs";
14
- var version = "0.4.3";
14
+ var version = "0.4.4";
15
15
  var description = "Claude Code tab manager. Terminal tabs as the UI, no tmux.";
16
16
  var package_default = {
17
17
  name,
@@ -1263,24 +1263,26 @@ function ensureConfigExists() {
1263
1263
  return CONFIG_PATH;
1264
1264
  }
1265
1265
  //#endregion
1266
+ //#region src/core/shell.ts
1267
+ /**
1268
+ * POSIX single-quote escape one argv token. Several commands join the
1269
+ * configured `claude.flags` into a raw shell string and send it as terminal
1270
+ * input, so any value with shell metacharacters must be quoted or the shell
1271
+ * mangles it before `claude` sees it — e.g. a `--model opus[1m]` flag
1272
+ * glob-expands under zsh ("no matches found: opus[1m]") and the launch
1273
+ * silently falls back to the default model. Single quotes are inert in every
1274
+ * POSIX shell; embedded single quotes are closed, escaped, and reopened ('\'').
1275
+ */
1276
+ function shellQuoteArg(arg) {
1277
+ return `'${arg.replace(/'/g, "'\\''")}'`;
1278
+ }
1279
+ //#endregion
1266
1280
  //#region src/core/open-session.ts
1267
1281
  function shellQuoteEnv$1(env) {
1268
1282
  const entries = Object.entries(env);
1269
1283
  if (!entries.length) return "";
1270
1284
  return entries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ") + " ";
1271
1285
  }
1272
- /**
1273
- * POSIX single-quote escape one argv token. The configured `claude.flags` are
1274
- * joined into a raw shell string and sent as terminal input, so any value with
1275
- * shell metacharacters must be quoted or the shell mangles it before `claude`
1276
- * sees it — e.g. a `--model opus[1m]` flag glob-expands under zsh ("no matches
1277
- * found: opus[1m]") and the launch silently falls back to the default model.
1278
- * Single quotes are inert in every POSIX shell; embedded single quotes are
1279
- * closed, escaped, and reopened ('\'').
1280
- */
1281
- function shellQuoteArg(arg) {
1282
- return `'${arg.replace(/'/g, "'\\''")}'`;
1283
- }
1284
1286
  /** Poll scrollback until a pattern is visible, then return. Rejects on timeout. */
1285
1287
  async function waitForScrollbackMatch(adapter, blockId, pattern, label, timeoutMs, pollInterval = 1e3) {
1286
1288
  const deadline = Date.now() + timeoutMs;
@@ -1327,6 +1329,12 @@ async function sendInitialPrompt(adapter, blockId, initialPromptFile) {
1327
1329
  adapter.closeSocket();
1328
1330
  throw new Error("Claude prompt (❯) never appeared — not sending initial prompt. Check that claude started successfully.");
1329
1331
  }
1332
+ if (/trustthisfolder|Yes,?Itrustthis|Isthisaproject/i.test(adapter.scrollback(blockId, 40).replace(/\s+/g, ""))) for (let attempt = 0; attempt < 18; attempt++) {
1333
+ const screen = adapter.scrollback(blockId, 14).replace(/\s+/g, "");
1334
+ if (/automode|foragents|Try["'“]/i.test(screen)) break;
1335
+ await adapter.sendInput(blockId, "\r");
1336
+ await sleep(800);
1337
+ }
1330
1338
  const prompt = readFileSync(initialPromptFile, "utf-8").trimEnd();
1331
1339
  const sentinel = prompt.replace(/\s+/g, "").slice(0, 24);
1332
1340
  const landed = () => {
@@ -1382,7 +1390,7 @@ async function openSession(opts) {
1382
1390
  const envPrefix = envVars ? shellQuoteEnv$1(envVars) : "";
1383
1391
  const claudeCore = `claude${extraFlags ? " " + extraFlags : ""} ${claudeCmd.replace(/^claude\s*/, "")}${namePart}${modelPart}`.replace(/\s+/g, " ").trim();
1384
1392
  const shell = process.env.SHELL ?? "/bin/zsh";
1385
- const launch = `${envPrefix}exec ${claudeCore}`;
1393
+ const launch = `${envPrefix}${claudeCore}; exec ${shell} -l -i`;
1386
1394
  const { blockId, tabId } = await adapter.openTabDirect({
1387
1395
  cwd: dir,
1388
1396
  title: tabName,
@@ -1869,7 +1877,7 @@ const resumeCommand = define({
1869
1877
  return;
1870
1878
  }
1871
1879
  }
1872
- const extraFlags = loadConfig().claude.flags.join(" ");
1880
+ const extraFlags = loadConfig().claude.flags.map(shellQuoteArg).join(" ");
1873
1881
  const envPrefix = envVars ? shellQuoteEnv(envVars) : "";
1874
1882
  const modelPart = resolvedModel ? ` --model ${JSON.stringify(resolvedModel)}` : "";
1875
1883
  const cmd = `cd ${JSON.stringify(dir)} && ${envPrefix}claude${extraFlags ? " " + extraFlags : ""} --resume ${sessionId} --name ${JSON.stringify(name)}${modelPart}\r`;
@@ -2240,6 +2248,15 @@ const configCommand = define({
2240
2248
  });
2241
2249
  //#endregion
2242
2250
  //#region src/commands/restore.ts
2251
+ /**
2252
+ * Settle after each direct-spawn (Tabby) recreate, before creating the next
2253
+ * tab. A freshly created tab spawns its PTY only once it becomes the active
2254
+ * tab, and each new tab steals activation from the previous one — so without
2255
+ * this gap only the last-created tab actually launches Claude. One second is
2256
+ * comfortably longer than a PTY fork + shell exec, while keeping a full restore
2257
+ * snappy.
2258
+ */
2259
+ const SPAWN_SETTLE_MS = 1e3;
2243
2260
  function readStdinSync() {
2244
2261
  if (process.stdin.isTTY) return "";
2245
2262
  try {
@@ -2347,7 +2364,7 @@ async function runManifestMode(manifestPath, createMissing, dryRun) {
2347
2364
  const wsTabIds = currentWsData ? new Set(currentWsData.workspacedata.tabids) : new Set(tabsById.keys());
2348
2365
  const results = [];
2349
2366
  const toSpawn = [];
2350
- const extraFlags = loadConfig().claude.flags.join(" ");
2367
+ const extraFlags = loadConfig().claude.flags.map(shellQuoteArg).join(" ");
2351
2368
  for (const entry of entries) {
2352
2369
  let resolvedSessionId = entry.session_id;
2353
2370
  if (entry.session_id) {
@@ -2508,7 +2525,7 @@ async function runLegacyMode(rawDir, dryRun) {
2508
2525
  return;
2509
2526
  }
2510
2527
  consola.info(`Found ${toResume.length} tab(s) to restore:`);
2511
- const extraFlags = loadConfig().claude.flags.join(" ");
2528
+ const extraFlags = loadConfig().claude.flags.map(shellQuoteArg).join(" ");
2512
2529
  const results = [];
2513
2530
  const toRecreate = [];
2514
2531
  const resolved = [];
@@ -2630,8 +2647,11 @@ async function runLegacyMode(rawDir, dryRun) {
2630
2647
  r.result = `✘ recreate failed: ${err.message}`;
2631
2648
  }
2632
2649
  };
2633
- if (typeof adapter.openTabDirect === "function") await Promise.all(toRecreate.map(recreateOne));
2634
- else for (const t of toRecreate) await recreateOne(t);
2650
+ const usesDirectSpawn = typeof adapter.openTabDirect === "function";
2651
+ for (const t of toRecreate) {
2652
+ await recreateOne(t);
2653
+ if (usesDirectSpawn) await new Promise((r) => setTimeout(r, SPAWN_SETTLE_MS));
2654
+ }
2635
2655
  } else adapter.closeSocket();
2636
2656
  console.log("\nRestore summary:");
2637
2657
  for (const r of results) console.log(` ${r.name}: ${r.result}`);
@@ -3372,7 +3392,7 @@ const importCommand = define({
3372
3392
  mkdirSync(targetProjectDir, { recursive: true });
3373
3393
  copyFileSync(srcJsonl, targetJsonl);
3374
3394
  const config = loadConfig();
3375
- const claudeCmd = `claude${config.claude.flags.length ? " " + config.claude.flags.join(" ") : ""} --resume ${entry.sessionId} --name ${JSON.stringify(entry.name)}`;
3395
+ const claudeCmd = `claude${config.claude.flags.length ? " " + config.claude.flags.map(shellQuoteArg).join(" ") : ""} --resume ${entry.sessionId} --name ${JSON.stringify(entry.name)}`;
3376
3396
  try {
3377
3397
  await openSession({
3378
3398
  tabName: entry.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@generativereality/cctabs",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Claude Code tab manager. Terminal tabs as the UI, no tmux.",
5
5
  "type": "module",
6
6
  "bin": {