@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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +40 -20
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
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 ${
|
|
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
|
-
|
|
2634
|
-
|
|
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,
|