@generativereality/cctabs 0.1.0 → 0.1.2
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/README.md +3 -3
- package/dist/index.js +87 -21
- package/package.json +3 -3
- package/skills/cctabs/SKILL.md +297 -0
- package/skills/herd/SKILL.md +0 -202
|
@@ -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.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "generativereality",
|
|
7
7
|
"url": "https://cctabs.com"
|
package/README.md
CHANGED
|
@@ -55,9 +55,9 @@ npm install -g cctabs
|
|
|
55
55
|
**Skill only** (if you already have the CLI installed via npm):
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
mkdir -p .claude/skills/
|
|
59
|
-
curl -fsSL https://raw.githubusercontent.com/generativereality/cctabs/main/skills/
|
|
60
|
-
-o .claude/skills/
|
|
58
|
+
mkdir -p .claude/skills/cctabs
|
|
59
|
+
curl -fsSL https://raw.githubusercontent.com/generativereality/cctabs/main/skills/cctabs/SKILL.md \
|
|
60
|
+
-o .claude/skills/cctabs/SKILL.md
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
**Requirements:** [Wave Terminal](https://waveterm.dev) · macOS · Node.js 20+
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { consola } from "consola";
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
11
11
|
//#region package.json
|
|
12
12
|
var name = "@generativereality/cctabs";
|
|
13
|
-
var version = "0.1.
|
|
13
|
+
var version = "0.1.2";
|
|
14
14
|
var description = "Claude Code tab manager. Terminal tabs as the UI, no tmux.";
|
|
15
15
|
var package_default = {
|
|
16
16
|
name,
|
|
@@ -28,10 +28,10 @@ var package_default = {
|
|
|
28
28
|
"build": "tsdown",
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
|
30
30
|
"lint": "eslint src/",
|
|
31
|
-
"check": "
|
|
31
|
+
"check": "npm run typecheck && npm run build",
|
|
32
32
|
"release": "bumpp && npm publish",
|
|
33
33
|
"sync-plugin": "bash scripts/sync-plugin.sh",
|
|
34
|
-
"prepack": "bash scripts/sync-plugin.sh --check &&
|
|
34
|
+
"prepack": "bash scripts/sync-plugin.sh --check && npm run build"
|
|
35
35
|
},
|
|
36
36
|
keywords: [
|
|
37
37
|
"claude-code",
|
|
@@ -206,7 +206,9 @@ var WaveAdapter = class {
|
|
|
206
206
|
const out = execFileSync("wsh", [
|
|
207
207
|
"blocks",
|
|
208
208
|
"list",
|
|
209
|
-
"--json"
|
|
209
|
+
"--json",
|
|
210
|
+
"--timeout",
|
|
211
|
+
"15000"
|
|
210
212
|
], { encoding: "utf-8" });
|
|
211
213
|
return JSON.parse(out);
|
|
212
214
|
} catch {
|
|
@@ -222,6 +224,16 @@ var WaveAdapter = class {
|
|
|
222
224
|
`-${lastN}`
|
|
223
225
|
], { encoding: "utf-8" }).stdout ?? "";
|
|
224
226
|
}
|
|
227
|
+
/** Poll scrollback to confirm it really is empty (block has no live shell).
|
|
228
|
+
* A freshly-opened tab may briefly have empty scrollback before the prompt
|
|
229
|
+
* renders, so poll a few times before declaring the block dead. */
|
|
230
|
+
async confirmScrollbackEmpty(blockId, attempts = 3, intervalMs = 500) {
|
|
231
|
+
for (let i = 0; i < attempts; i++) {
|
|
232
|
+
if (this.scrollback(blockId, 10).trim()) return false;
|
|
233
|
+
if (i < attempts - 1) await sleep(intervalMs);
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
225
237
|
/** Detect whether a Claude session is running in a terminal block */
|
|
226
238
|
detectSessionStatus(blockId) {
|
|
227
239
|
const tail = this.scrollback(blockId, 10);
|
|
@@ -261,7 +273,7 @@ var WaveAdapter = class {
|
|
|
261
273
|
}
|
|
262
274
|
return true;
|
|
263
275
|
}
|
|
264
|
-
async waitForNewBlock(beforeIds, timeoutMs =
|
|
276
|
+
async waitForNewBlock(beforeIds, timeoutMs = 15e3) {
|
|
265
277
|
const deadline = Date.now() + timeoutMs;
|
|
266
278
|
while (Date.now() < deadline) {
|
|
267
279
|
await sleep(250);
|
|
@@ -343,9 +355,12 @@ var WaveAdapter = class {
|
|
|
343
355
|
}
|
|
344
356
|
resolveTab(query, tabsById, tabNames) {
|
|
345
357
|
const q = query.toLowerCase();
|
|
346
|
-
|
|
358
|
+
const ids = [...tabsById.keys()];
|
|
359
|
+
const exact = ids.filter((tid) => (tabNames.get(tid) ?? "").toLowerCase() === q);
|
|
360
|
+
if (exact.length > 0) return exact;
|
|
361
|
+
return ids.filter((tid) => {
|
|
347
362
|
const name = tabNames.get(tid) ?? "";
|
|
348
|
-
return
|
|
363
|
+
return tid.startsWith(query) || name.toLowerCase().startsWith(q);
|
|
349
364
|
});
|
|
350
365
|
}
|
|
351
366
|
resolveBlock(query, blocks) {
|
|
@@ -353,10 +368,11 @@ var WaveAdapter = class {
|
|
|
353
368
|
}
|
|
354
369
|
resolveWorkspace(workspaces, query) {
|
|
355
370
|
const q = query.toLowerCase();
|
|
356
|
-
|
|
371
|
+
const exact = workspaces.filter(({ workspacedata: wd }) => (wd.name ?? "").toLowerCase() === q);
|
|
372
|
+
return (exact.length > 0 ? exact : workspaces.filter(({ workspacedata: wd }) => {
|
|
357
373
|
const name = wd.name ?? "";
|
|
358
|
-
return
|
|
359
|
-
}).map((w) => ({
|
|
374
|
+
return wd.oid.startsWith(query) || name.toLowerCase().startsWith(q);
|
|
375
|
+
})).map((w) => ({
|
|
360
376
|
data: w.workspacedata,
|
|
361
377
|
windowId: w.windowid
|
|
362
378
|
}));
|
|
@@ -633,7 +649,7 @@ const newCommand = define({
|
|
|
633
649
|
}
|
|
634
650
|
let initialPromptFile;
|
|
635
651
|
if (promptText) {
|
|
636
|
-
initialPromptFile = join(tmpdir(), `
|
|
652
|
+
initialPromptFile = join(tmpdir(), `cctabs-prompt-${Date.now()}.txt`);
|
|
637
653
|
writeFileSync(initialPromptFile, promptText);
|
|
638
654
|
} else if (promptFile) initialPromptFile = promptFile;
|
|
639
655
|
const tabId = await openSession({
|
|
@@ -649,9 +665,11 @@ const newCommand = define({
|
|
|
649
665
|
});
|
|
650
666
|
//#endregion
|
|
651
667
|
//#region src/core/session.ts
|
|
652
|
-
/** Convert an absolute path to Claude's project slug
|
|
668
|
+
/** Convert an absolute path to Claude Code's project slug.
|
|
669
|
+
* Claude Code replaces any non-alphanumeric character (spaces, /, ., etc.) with '-'.
|
|
670
|
+
* Hyphens are preserved. Example: "/Users/me/Remember This" → "-Users-me-Remember-This". */
|
|
653
671
|
function pathToProjectSlug(dir) {
|
|
654
|
-
return resolve(dir).replace(/[
|
|
672
|
+
return resolve(dir).replace(/[^A-Za-z0-9-]/g, "-");
|
|
655
673
|
}
|
|
656
674
|
/** Find the most recent .jsonl session file in a Claude project directory */
|
|
657
675
|
function latestJsonlIn(projectDir) {
|
|
@@ -849,7 +867,8 @@ const resumeCommand = define({
|
|
|
849
867
|
process.exit(1);
|
|
850
868
|
}
|
|
851
869
|
const tabId = matchingTabs[0];
|
|
852
|
-
const
|
|
870
|
+
const blocks = tabsById.get(tabId) ?? [];
|
|
871
|
+
const termBlock = blocks.find((b) => b.view === "term");
|
|
853
872
|
if (!termBlock) {
|
|
854
873
|
consola.error(`No terminal block found in tab '${name}'`);
|
|
855
874
|
process.exit(1);
|
|
@@ -860,7 +879,20 @@ const resumeCommand = define({
|
|
|
860
879
|
consola.warn(`Claude is already running in tab "${name}" (${status}) — skipping resume`);
|
|
861
880
|
process.exit(0);
|
|
862
881
|
}
|
|
863
|
-
if (status === "unknown")
|
|
882
|
+
if (status === "unknown") {
|
|
883
|
+
if (await adapter.confirmScrollbackEmpty(termBlock.blockid)) {
|
|
884
|
+
consola.info(`Tab "${name}" has no live shell (empty scrollback) — recreating`);
|
|
885
|
+
for (const b of blocks) adapter.deleteBlock(b.blockid);
|
|
886
|
+
adapter.closeSocket();
|
|
887
|
+
const newTabId = await openSession({
|
|
888
|
+
tabName: name,
|
|
889
|
+
dir,
|
|
890
|
+
claudeCmd: `claude --resume ${sessionId} --name ${JSON.stringify(name)}`
|
|
891
|
+
});
|
|
892
|
+
consola.success(`Tab "${name}" [${newTabId.slice(0, 8)}] → claude --resume ${sessionId.slice(0, 8)}… at ${dir} (recreated)`);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
864
896
|
const extraFlags = loadConfig().claude.flags.join(" ");
|
|
865
897
|
const cmd = `cd ${JSON.stringify(dir)} && claude${extraFlags ? " " + extraFlags : ""} --resume ${sessionId} --name ${JSON.stringify(name)}\r`;
|
|
866
898
|
await adapter.sendInput(termBlock.blockid, cmd);
|
|
@@ -1024,7 +1056,7 @@ const renameCommand = define({
|
|
|
1024
1056
|
const query = ctx.positionals[1];
|
|
1025
1057
|
const newName = ctx.positionals[2];
|
|
1026
1058
|
if (!query || !newName) {
|
|
1027
|
-
consola.error("Usage:
|
|
1059
|
+
consola.error("Usage: cctabs rename <tab> <new-name>");
|
|
1028
1060
|
process.exit(1);
|
|
1029
1061
|
}
|
|
1030
1062
|
const adapter = requireWaveAdapter();
|
|
@@ -1134,7 +1166,7 @@ const sendCommand = define({
|
|
|
1134
1166
|
const filePath = ctx.values.file;
|
|
1135
1167
|
const appendEnter = ctx.values.enter ?? true;
|
|
1136
1168
|
if (!query) {
|
|
1137
|
-
consola.error("Usage:
|
|
1169
|
+
consola.error("Usage: cctabs send <tab-or-block> [text]");
|
|
1138
1170
|
process.exit(1);
|
|
1139
1171
|
}
|
|
1140
1172
|
let rawText;
|
|
@@ -1243,6 +1275,7 @@ const restoreCommand = define({
|
|
|
1243
1275
|
consola.info(`Found ${toResume.length} tab(s) to restore:`);
|
|
1244
1276
|
const extraFlags = loadConfig().claude.flags.join(" ");
|
|
1245
1277
|
const results = [];
|
|
1278
|
+
const toRecreate = [];
|
|
1246
1279
|
for (const tab of toResume) {
|
|
1247
1280
|
const sessions = findSessionsByName(dir, tab.name);
|
|
1248
1281
|
if (sessions.length === 0) {
|
|
@@ -1254,7 +1287,7 @@ const restoreCommand = define({
|
|
|
1254
1287
|
continue;
|
|
1255
1288
|
}
|
|
1256
1289
|
if (sessions.length > 1) {
|
|
1257
|
-
consola.log(` ${tab.name} — multiple sessions found, skipping (use
|
|
1290
|
+
consola.log(` ${tab.name} — multiple sessions found, skipping (use cctabs resume --session to pick one)`);
|
|
1258
1291
|
results.push({
|
|
1259
1292
|
name: tab.name,
|
|
1260
1293
|
result: "ambiguous (multiple sessions)"
|
|
@@ -1263,14 +1296,31 @@ const restoreCommand = define({
|
|
|
1263
1296
|
}
|
|
1264
1297
|
const sessionId = sessions[0].id;
|
|
1265
1298
|
if (dryRun) {
|
|
1266
|
-
|
|
1299
|
+
const mode = tab.status === "unknown" ? "recreate" : "send";
|
|
1300
|
+
consola.log(` ${tab.name} → would ${mode} session ${sessionId.slice(0, 8)}…`);
|
|
1267
1301
|
results.push({
|
|
1268
1302
|
name: tab.name,
|
|
1269
1303
|
result: `dry run: ${sessionId.slice(0, 8)}…`
|
|
1270
1304
|
});
|
|
1271
1305
|
continue;
|
|
1272
1306
|
}
|
|
1273
|
-
|
|
1307
|
+
if (tab.status === "unknown") {
|
|
1308
|
+
if (await adapter.confirmScrollbackEmpty(tab.blockId)) {
|
|
1309
|
+
const blockIds = (tabsById.get(tab.tabId) ?? []).map((b) => b.blockid);
|
|
1310
|
+
toRecreate.push({
|
|
1311
|
+
name: tab.name,
|
|
1312
|
+
sessionId,
|
|
1313
|
+
blockIds,
|
|
1314
|
+
tabId: tab.tabId
|
|
1315
|
+
});
|
|
1316
|
+
results.push({
|
|
1317
|
+
name: tab.name,
|
|
1318
|
+
result: "queued for recreate"
|
|
1319
|
+
});
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
consola.log(` ${tab.name} → resuming session ${sessionId.slice(0, 8)}… (send)`);
|
|
1274
1324
|
const cmd = `cd ${JSON.stringify(dir)} && claude${extraFlags ? " " + extraFlags : ""} --resume ${sessionId} --name ${JSON.stringify(tab.name)}\r`;
|
|
1275
1325
|
await adapter.sendInput(tab.blockId, cmd);
|
|
1276
1326
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -1293,7 +1343,23 @@ const restoreCommand = define({
|
|
|
1293
1343
|
}
|
|
1294
1344
|
}
|
|
1295
1345
|
}
|
|
1296
|
-
|
|
1346
|
+
if (!dryRun && toRecreate.length) {
|
|
1347
|
+
for (const t of toRecreate) for (const bid of t.blockIds) adapter.deleteBlock(bid);
|
|
1348
|
+
adapter.closeSocket();
|
|
1349
|
+
consola.info(`Recreating ${toRecreate.length} dead tab(s)…`);
|
|
1350
|
+
for (const t of toRecreate) try {
|
|
1351
|
+
const newTabId = await openSession({
|
|
1352
|
+
tabName: t.name,
|
|
1353
|
+
dir,
|
|
1354
|
+
claudeCmd: `claude --resume ${t.sessionId} --name ${JSON.stringify(t.name)}`
|
|
1355
|
+
});
|
|
1356
|
+
const r = results.find((x) => x.name === t.name);
|
|
1357
|
+
r.result = `✔ recreated [${newTabId.slice(0, 8)}]`;
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
const r = results.find((x) => x.name === t.name);
|
|
1360
|
+
r.result = `✘ recreate failed: ${err.message}`;
|
|
1361
|
+
}
|
|
1362
|
+
} else adapter.closeSocket();
|
|
1297
1363
|
console.log("\nRestore summary:");
|
|
1298
1364
|
for (const r of results) console.log(` ${r.name}: ${r.result}`);
|
|
1299
1365
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@generativereality/cctabs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Claude Code tab manager. Terminal tabs as the UI, no tmux.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"build": "tsdown",
|
|
17
17
|
"typecheck": "tsc --noEmit",
|
|
18
18
|
"lint": "eslint src/",
|
|
19
|
-
"check": "
|
|
19
|
+
"check": "npm run typecheck && npm run build",
|
|
20
20
|
"release": "bumpp && npm publish",
|
|
21
21
|
"sync-plugin": "bash scripts/sync-plugin.sh",
|
|
22
|
-
"prepack": "bash scripts/sync-plugin.sh --check &&
|
|
22
|
+
"prepack": "bash scripts/sync-plugin.sh --check && npm run build"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"claude-code",
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cctabs
|
|
3
|
+
description: Manage Claude Code sessions across terminal tabs (NOT browser tabs) — list running sessions, open new ones, fork, close, inspect output, and send input. Use this when working with multiple parallel Claude Code sessions in terminal tabs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are managing Claude Code sessions using the `cctabs` CLI.
|
|
7
|
+
|
|
8
|
+
**Important:** "tabs" here means **terminal tabs** (e.g. Wave Terminal tabs), NOT browser tabs. Each terminal tab runs its own Claude Code session. This skill is for managing those terminal-based Claude Code sessions — not for browser automation.
|
|
9
|
+
|
|
10
|
+
## Before you spawn anything: is cctabs the right tool?
|
|
11
|
+
|
|
12
|
+
cctabs is excellent for:
|
|
13
|
+
- **Multiple human-driven sessions** on unrelated projects (check on a deploy here, draft a blog post there, monitor a long-running task somewhere else).
|
|
14
|
+
- **Genuinely orthogonal parallel work** where each tab touches a disjoint file set (e.g. each tab writes to its own new directory, or each tab works on a different repo).
|
|
15
|
+
- **Long-running background sessions** that the user wants to check on later (builds, scrapes, benchmarks).
|
|
16
|
+
|
|
17
|
+
cctabs is the WRONG tool for:
|
|
18
|
+
- **Interconnected parallel work within one session.** If you're orchestrating and farming out subtasks that all modify the same evolving codebase, tabs hide each other's commits from each other. By the time they're done, you have three diverged branches that need manual merge, and any intervening change on `main`/`next` can make the merge structurally painful. **Use the Agent tool instead** — subagents share your filesystem and git state, commit in place, and surface their result back to you.
|
|
19
|
+
- **Sequential dependencies.** If B depends on A's commits landing, don't parallelize — run A to completion first, then B.
|
|
20
|
+
- **Work that touches the same files as the current orchestrator session.** Commits race, branches diverge, conflicts multiply.
|
|
21
|
+
|
|
22
|
+
A good test: *"If both tabs finish successfully, will merging their output be trivial?"* If yes, cctabs is fine. If no (or you can't tell), do it sequentially or use subagents.
|
|
23
|
+
|
|
24
|
+
## First: Ensure cctabs is available
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
which cctabs || ls "$(npm prefix -g)/bin/cctabs" 2>/dev/null
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If found, use whichever path works. If `cctabs` is on PATH, use it directly. Otherwise use the full path from `npm prefix -g`.
|
|
31
|
+
|
|
32
|
+
If not found, ask the user: "cctabs isn't installed yet — want me to install it globally with npm?" If they agree, run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @generativereality/cctabs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Do not modify PATH or npm configuration beyond this.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
Each Claude Code session runs in its own **terminal tab**. `cctabs` lets you — and other Claude Code sessions — introspect and orchestrate the full session fleet.
|
|
43
|
+
|
|
44
|
+
## When to Use Worktrees
|
|
45
|
+
|
|
46
|
+
**Use `--worktree` whenever a tab will edit code on a branch that differs from the main working tree.** This includes:
|
|
47
|
+
- Fixing CI on a PR (`cctabs new fix-1789 ~/Dev/myapp --worktree`)
|
|
48
|
+
- Working on a feature branch while the main checkout runs a dev server
|
|
49
|
+
- Any task where multiple tabs might checkout different branches
|
|
50
|
+
|
|
51
|
+
Without `--worktree`, all tabs share the same working directory. If two tabs checkout different branches, they stomp on each other's files — causing silent conflicts, lost changes, and broken dev servers.
|
|
52
|
+
|
|
53
|
+
**Rule of thumb:**
|
|
54
|
+
- **Read-only / docs / coordination** → no worktree needed (stays on current branch)
|
|
55
|
+
- **Editing code on a different branch** → always `--worktree`
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# ❌ WRONG — two tabs checking out different branches in the same directory
|
|
59
|
+
cctabs new fix-auth ~/Dev/myapp --prompt "checkout PR #101 and fix lint"
|
|
60
|
+
cctabs new fix-api ~/Dev/myapp --prompt "checkout PR #102 and fix tests"
|
|
61
|
+
|
|
62
|
+
# ✅ RIGHT — each gets its own isolated copy
|
|
63
|
+
cctabs new fix-auth ~/Dev/myapp --worktree --prompt "checkout PR #101 and fix lint"
|
|
64
|
+
cctabs new fix-api ~/Dev/myapp --worktree --prompt "checkout PR #102 and fix tests"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick Reference
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cctabs sessions # list all tabs with session status
|
|
71
|
+
cctabs list # list all workspaces, tabs, and blocks
|
|
72
|
+
cctabs new <name> [dir] [-w workspace] [-p "prompt"] [-f file] # new tab + claude
|
|
73
|
+
cctabs resume <name> [dir] # resume last session (reuses tab or creates one)
|
|
74
|
+
cctabs fork <tab-name> [-n new-name] # fork session into new tab (--resume <id> --fork-session)
|
|
75
|
+
cctabs close <name-or-id> # close a tab
|
|
76
|
+
cctabs rename <name-or-id> <new-name> # rename a tab
|
|
77
|
+
cctabs scrollback <tab-or-block> [n] # read terminal output (default: 50 lines)
|
|
78
|
+
cctabs send <tab-or-block> [text] # send input — arg, --file, or stdin pipe
|
|
79
|
+
cctabs config # show config and path
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Workflow: Checking What's Running
|
|
83
|
+
|
|
84
|
+
Before starting new sessions, always check what's already active:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cctabs sessions
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Output example:
|
|
91
|
+
```
|
|
92
|
+
Sessions
|
|
93
|
+
==================================================
|
|
94
|
+
|
|
95
|
+
Workspace: work (current)
|
|
96
|
+
|
|
97
|
+
[a1b2c3d4] "auth" ◄ ~/Dev/myapp
|
|
98
|
+
● active
|
|
99
|
+
[e5f6a7b8] "api" ~/Dev/myapp
|
|
100
|
+
○ idle
|
|
101
|
+
[c9d0e1f2] "infra" ~/Dev/myapp
|
|
102
|
+
terminal
|
|
103
|
+
last: $ git status
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Workflow: Opening a Session Batch
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
cctabs new auth ~/Dev/myapp
|
|
110
|
+
cctabs new api ~/Dev/myapp
|
|
111
|
+
cctabs new infra ~/Dev/myapp
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each tab is automatically named and the claude session name is synced to the tab title.
|
|
115
|
+
|
|
116
|
+
## Workflow: Resuming a Session
|
|
117
|
+
|
|
118
|
+
`cctabs resume` finds the latest session ID for the directory and runs `claude --resume <id>`.
|
|
119
|
+
If the named tab still exists, it reuses it. If not, it creates a new tab.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cctabs resume auth ~/Dev/myapp # reuses "auth" tab if it exists, otherwise creates one
|
|
123
|
+
cctabs resume api ~/Dev/myapp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Use `cctabs resume` instead of `cctabs new` when you want to continue a previous conversation.**
|
|
127
|
+
`cctabs new` always starts a fresh Claude session. `cctabs resume` picks up where the last session left off.
|
|
128
|
+
|
|
129
|
+
## Workflow: Forking a Session
|
|
130
|
+
|
|
131
|
+
Use `fork` when you want to explore an alternative approach without disrupting the original.
|
|
132
|
+
`cctabs fork` finds the latest session ID for the source tab and opens a new tab with
|
|
133
|
+
`claude --resume <id> --fork-session`. The source tab is not modified.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
cctabs fork auth # creates "auth-fork" tab
|
|
137
|
+
cctabs fork auth -n "auth-v2" # creates "auth-v2" tab
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The forked session shares full conversation history up to the fork point, then diverges independently.
|
|
141
|
+
|
|
142
|
+
## Workflow: Spawning a Parallel Agent
|
|
143
|
+
|
|
144
|
+
**Before spawning, re-read "is cctabs the right tool?" above.** If the task is interconnected with your current work, use the Agent tool (subagents) instead — they share your filesystem and commits.
|
|
145
|
+
|
|
146
|
+
As a Claude Code session, you can spawn a sibling session for a **genuinely independent** parallel task:
|
|
147
|
+
|
|
148
|
+
**Preferred: pass the initial task directly to `cctabs new`** using `--prompt` or `--file`. This polls internally until Claude's `❯` prompt appears before sending — no race condition:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cctabs new payments ~/Dev/myapp --prompt "implement the billing endpoint"
|
|
152
|
+
cctabs new payments ~/Dev/myapp --file /tmp/task.txt
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If you need to send a task after the fact, poll first:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
cctabs new payments ~/Dev/myapp
|
|
159
|
+
# Poll until ❯ appears (typically 10-15s with MCP servers)
|
|
160
|
+
cctabs scrollback payments 5 # repeat until you see ❯
|
|
161
|
+
cctabs send payments --file /tmp/task.txt
|
|
162
|
+
cctabs send payments "yes\n" # quick replies
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Do NOT call `cctabs send` immediately after `cctabs new`** — Claude is still starting up and the text will land as raw shell commands.
|
|
166
|
+
|
|
167
|
+
### Spawning gotchas (hard-won)
|
|
168
|
+
|
|
169
|
+
1. **Verify the worktree base immediately after spawn.** `--worktree` does not always branch from your current HEAD — if you have local un-pushed commits, the child session may branch from an older commit (whatever the remote tracking branch points at). Always check:
|
|
170
|
+
```bash
|
|
171
|
+
cctabs new kid ~/Dev/myapp --worktree -p "..."
|
|
172
|
+
# Then in the ORCHESTRATOR tab:
|
|
173
|
+
git -C ~/Dev/myapp/.claude/worktrees/kid log --oneline -1
|
|
174
|
+
```
|
|
175
|
+
If the base is not what you expected, abort and fix: either push your commits to the tracking branch first, or spawn without `--worktree` and let the subagent work on your branch directly.
|
|
176
|
+
|
|
177
|
+
2. **Never instruct a subagent to "rebase your branch on main/next."** Subagents interpret this liberally. A common failure mode: the subagent does `git reset --hard <remote>` and throws away its own completed commits, trying to redo the work from scratch. Instead:
|
|
178
|
+
- Have the orchestrator handle rebases after the subagent is done.
|
|
179
|
+
- Or send a precise patch/diff rather than a verbal rebase instruction.
|
|
180
|
+
- Or tell the subagent explicitly: *"do not rebase, do not reset; make fixup commits on top of your existing branch."*
|
|
181
|
+
|
|
182
|
+
3. **Subagents won't see each other's commits.** Each tab has its own working tree. If ws-A commits a schema, ws-B cannot consume it until you merge A → main → rebase B. This is a fundamental property, not a bug. Only parallelize when this limitation doesn't matter.
|
|
183
|
+
|
|
184
|
+
4. **Don't delegate rebases or merges to subagents.** Those are orchestrator work. Subagents produce content; orchestrator integrates.
|
|
185
|
+
|
|
186
|
+
## Workflow: Monitoring Another Session
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cctabs scrollback auth # last 50 lines
|
|
190
|
+
cctabs scrollback auth 200 # last 200 lines
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Workflow: Sending Input to a Session
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
cctabs send auth "yes\n" # approve a tool call
|
|
197
|
+
cctabs send auth "\n" # press enter (confirm a prompt)
|
|
198
|
+
cctabs send auth "/clear\n" # send a slash command
|
|
199
|
+
cctabs send auth --file ~/prompts/task.txt # send a full prompt from file
|
|
200
|
+
echo "do the thing" | cctabs send auth # pipe via stdin
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Workflow: Worktrees
|
|
204
|
+
|
|
205
|
+
**Always point tabs at the repo root — never at a manually-created worktree directory.** Claude Code manages worktrees itself via `claude --worktree <name>`, which creates `.claude/worktrees/<name>/` inside the repo and handles branch creation and cleanup automatically.
|
|
206
|
+
|
|
207
|
+
### New isolated session (new branch, Claude manages everything)
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
cctabs new feature-name ~/Dev/myapp --worktree
|
|
211
|
+
# Equivalent to: cd ~/Dev/myapp && claude --worktree "feature-name" --name "feature-name"
|
|
212
|
+
# Claude creates: ~/Dev/myapp/.claude/worktrees/feature-name/
|
|
213
|
+
# Claude creates branch: worktree-feature-name
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Existing branch — ask Claude to enter the worktree mid-session
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
cctabs new hiring ~/Dev/myapp # open tab at repo root
|
|
220
|
+
cctabs send hiring "Enter a worktree for branch z.old/new-hire-ad and ..."
|
|
221
|
+
# Claude will use EnterWorktree tool to set up isolation
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Do NOT manage git worktrees manually
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# ❌ WRONG — do not create worktree dirs yourself and pass them to cctabs new
|
|
228
|
+
git worktree add ~/Dev/myapp-feature branch
|
|
229
|
+
cctabs new feature ~/Dev/myapp-feature
|
|
230
|
+
|
|
231
|
+
# ✅ RIGHT — always use repo root; let Claude Code manage the worktree
|
|
232
|
+
cctabs new feature ~/Dev/myapp --worktree
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Why:** Manually created worktree dirs placed outside the repo confuse Claude Code's session tracking, project memory lookup (`.claude/` is in the main repo), and CLAUDE.md resolution. Claude Code's built-in worktree support keeps everything co-located under `.claude/worktrees/` and handles cleanup on session exit.
|
|
236
|
+
|
|
237
|
+
**Worktree base-commit caveat:** after spawning with `--worktree`, verify the branch base matches your expectation (see "Spawning gotchas" above). If your orchestrator has local commits that haven't been pushed, the worktree may branch from the stale remote tip instead of HEAD. This bites hardest when parallel tabs need to share schema/types your orchestrator has been working on — they won't see those changes if they branched before the commits landed upstream.
|
|
238
|
+
|
|
239
|
+
## Handling `cctabs new` Timeout Errors
|
|
240
|
+
|
|
241
|
+
`cctabs new` may occasionally fail with "Timed out waiting for new terminal block". This does **NOT** mean you have too many tabs or that Wave Terminal has hit a limit.
|
|
242
|
+
|
|
243
|
+
**Possible causes** (root cause not yet confirmed):
|
|
244
|
+
- Wave Terminal may need to be in focus / foreground for tab creation to register
|
|
245
|
+
- The internal timeout may be slightly too short for the current system load
|
|
246
|
+
- Transient IPC timing issue between cctabs and Wave
|
|
247
|
+
|
|
248
|
+
**What to do:**
|
|
249
|
+
1. **Retry the same command** — it often works on the second attempt
|
|
250
|
+
2. If it fails again, wait a few seconds and retry once more
|
|
251
|
+
3. If it keeps failing, ask the user to bring Wave Terminal to the foreground and try again
|
|
252
|
+
|
|
253
|
+
**What NOT to do:**
|
|
254
|
+
- ❌ Do NOT assume there is a "tab limit" — there isn't one
|
|
255
|
+
- ❌ Do NOT close other tabs to "make room" — this destroys the user's sessions
|
|
256
|
+
- ❌ Do NOT suggest the user has too many tabs open
|
|
257
|
+
|
|
258
|
+
## Workflow: Cleanup
|
|
259
|
+
|
|
260
|
+
**⚠️ NEVER close tabs without explicit user approval.** Each tab may contain an active session with important context, uncommitted work, or in-progress tasks. Closing a tab is destructive and irreversible.
|
|
261
|
+
|
|
262
|
+
**Always ask first:**
|
|
263
|
+
> "These tabs look idle: `old-feature`, `fix-1234`. Want me to close any of them?"
|
|
264
|
+
|
|
265
|
+
Only after the user confirms:
|
|
266
|
+
```bash
|
|
267
|
+
cctabs close old-feature # close by name (prefix match)
|
|
268
|
+
cctabs close e5f6a7b8 # close by block ID prefix
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Tab Naming Conventions
|
|
272
|
+
|
|
273
|
+
Name tabs after the **project or task**:
|
|
274
|
+
- `auth` — authentication work
|
|
275
|
+
- `api` — API service
|
|
276
|
+
- `infra` — infrastructure
|
|
277
|
+
- `pr-1234` — specific PR work
|
|
278
|
+
- `auth-v2` — forked attempt
|
|
279
|
+
|
|
280
|
+
## Notes
|
|
281
|
+
|
|
282
|
+
- Tab names are matched by exact name or prefix (case-insensitive)
|
|
283
|
+
- Block IDs can be abbreviated to the first 8 characters
|
|
284
|
+
- `cctabs new` and `cctabs resume` automatically pass `--name <tab-name>` to claude, syncing the session display name with the tab title
|
|
285
|
+
- Configured `claude.flags` in `~/.config/cctabs/config.toml` are applied to every session
|
|
286
|
+
- `cctabs send` resolves tab names to their terminal block automatically
|
|
287
|
+
|
|
288
|
+
## Lesson: the common failure mode
|
|
289
|
+
|
|
290
|
+
A pattern that wastes the most tokens: an orchestrator spawns three tabs for "parallel workstreams" on the same feature, each tab diverges from the base and from each other, the orchestrator loses visibility into what each is doing, one tab misinterprets a course-correct and resets its own work, and finally the orchestrator spends hours hand-merging commits that don't apply cleanly against an intervening refactor.
|
|
291
|
+
|
|
292
|
+
The fix is upstream: before spawning, ask *"are these workstreams actually independent?"* If the answer is "mostly, but they share a common data model / schema / utility module" — they are **not** independent for cctabs purposes. Either:
|
|
293
|
+
- Do them sequentially in one tab (cheapest).
|
|
294
|
+
- Use the Agent tool for subtasks that share orchestrator state.
|
|
295
|
+
- Land the shared pieces first on `main`/`next`, push, then spawn tabs (each branches cleanly off the new tip and work is truly orthogonal from there).
|
|
296
|
+
|
|
297
|
+
Parallel tabs earn their keep when the work is genuinely orthogonal (separate repos, separate brand-new directories, independent features) and when you'd otherwise be idle waiting for one long-running task to finish.
|
package/skills/herd/SKILL.md
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: herd
|
|
3
|
-
description: Manage Claude Code sessions across terminal tabs (NOT browser tabs) — list running sessions, open new ones, fork, close, inspect output, and send input. Use this when working with multiple parallel Claude Code sessions in terminal tabs.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are managing Claude Code sessions using the `cctabs` CLI.
|
|
7
|
-
|
|
8
|
-
**Important:** "tabs" here means **terminal tabs** (e.g. Wave Terminal tabs), NOT browser tabs. Each terminal tab runs its own Claude Code session. This skill is for managing those terminal-based Claude Code sessions — not for browser automation.
|
|
9
|
-
|
|
10
|
-
## First: Ensure cctabs is available
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
which cctabs || ls "$(npm prefix -g)/bin/cctabs" 2>/dev/null
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
If found, use whichever path works. If `cctabs` is on PATH, use it directly. Otherwise use the full path from `npm prefix -g`.
|
|
17
|
-
|
|
18
|
-
If not found, ask the user: "cctabs isn't installed yet — want me to install it globally with npm?" If they agree, run:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install -g @generativereality/cctabs
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Do not modify PATH or npm configuration beyond this.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
Each Claude Code session runs in its own **terminal tab**. `cctabs` lets you — and other Claude Code sessions — introspect and orchestrate the full session fleet.
|
|
29
|
-
|
|
30
|
-
## Quick Reference
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
cctabs sessions # list all tabs with session status
|
|
34
|
-
cctabs list # list all workspaces, tabs, and blocks
|
|
35
|
-
cctabs new <name> [dir] [-w workspace] [-p "prompt"] [-f file] # new tab + claude
|
|
36
|
-
cctabs resume <name> [dir] # resume last session (reuses tab or creates one)
|
|
37
|
-
cctabs fork <tab-name> [-n new-name] # fork session into new tab (--resume <id> --fork-session)
|
|
38
|
-
cctabs close <name-or-id> # close a tab
|
|
39
|
-
cctabs rename <name-or-id> <new-name> # rename a tab
|
|
40
|
-
cctabs scrollback <tab-or-block> [n] # read terminal output (default: 50 lines)
|
|
41
|
-
cctabs send <tab-or-block> [text] # send input — arg, --file, or stdin pipe
|
|
42
|
-
cctabs config # show config and path
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Workflow: Checking What's Running
|
|
46
|
-
|
|
47
|
-
Before starting new sessions, always check what's already active:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
cctabs sessions
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Output example:
|
|
54
|
-
```
|
|
55
|
-
Sessions
|
|
56
|
-
==================================================
|
|
57
|
-
|
|
58
|
-
Workspace: work (current)
|
|
59
|
-
|
|
60
|
-
[a1b2c3d4] "auth" ◄ ~/Dev/myapp
|
|
61
|
-
● active
|
|
62
|
-
[e5f6a7b8] "api" ~/Dev/myapp
|
|
63
|
-
○ idle
|
|
64
|
-
[c9d0e1f2] "infra" ~/Dev/myapp
|
|
65
|
-
terminal
|
|
66
|
-
last: $ git status
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Workflow: Opening a Session Batch
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
cctabs new auth ~/Dev/myapp
|
|
73
|
-
cctabs new api ~/Dev/myapp
|
|
74
|
-
cctabs new infra ~/Dev/myapp
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Each tab is automatically named and the claude session name is synced to the tab title.
|
|
78
|
-
|
|
79
|
-
## Workflow: Resuming a Session
|
|
80
|
-
|
|
81
|
-
`cctabs resume` finds the latest session ID for the directory and runs `claude --resume <id>`.
|
|
82
|
-
If the named tab still exists, it reuses it. If not, it creates a new tab.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
cctabs resume auth ~/Dev/myapp # reuses "auth" tab if it exists, otherwise creates one
|
|
86
|
-
cctabs resume api ~/Dev/myapp
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Use `cctabs resume` instead of `cctabs new` when you want to continue a previous conversation.**
|
|
90
|
-
`cctabs new` always starts a fresh Claude session. `cctabs resume` picks up where the last session left off.
|
|
91
|
-
|
|
92
|
-
## Workflow: Forking a Session
|
|
93
|
-
|
|
94
|
-
Use `fork` when you want to explore an alternative approach without disrupting the original.
|
|
95
|
-
`cctabs fork` finds the latest session ID for the source tab and opens a new tab with
|
|
96
|
-
`claude --resume <id> --fork-session`. The source tab is not modified.
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
cctabs fork auth # creates "auth-fork" tab
|
|
100
|
-
cctabs fork auth -n "auth-v2" # creates "auth-v2" tab
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
The forked session shares full conversation history up to the fork point, then diverges independently.
|
|
104
|
-
|
|
105
|
-
## Workflow: Spawning a Parallel Agent
|
|
106
|
-
|
|
107
|
-
As a Claude Code session, you can spawn a sibling session to work on a parallel task:
|
|
108
|
-
|
|
109
|
-
**Preferred: pass the initial task directly to `cctabs new`** using `--prompt` or `--file`. This polls internally until Claude's `❯` prompt appears before sending — no race condition:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
cctabs new payments ~/Dev/myapp --prompt "implement the billing endpoint"
|
|
113
|
-
cctabs new payments ~/Dev/myapp --file /tmp/task.txt
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
If you need to send a task after the fact, poll first:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
cctabs new payments ~/Dev/myapp
|
|
120
|
-
# Poll until ❯ appears (typically 10-15s with MCP servers)
|
|
121
|
-
cctabs scrollback payments 5 # repeat until you see ❯
|
|
122
|
-
cctabs send payments --file /tmp/task.txt
|
|
123
|
-
cctabs send payments "yes\n" # quick replies
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**Do NOT call `cctabs send` immediately after `cctabs new`** — Claude is still starting up and the text will land as raw shell commands.
|
|
127
|
-
|
|
128
|
-
## Workflow: Monitoring Another Session
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
cctabs scrollback auth # last 50 lines
|
|
132
|
-
cctabs scrollback auth 200 # last 200 lines
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Workflow: Sending Input to a Session
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
cctabs send auth "yes\n" # approve a tool call
|
|
139
|
-
cctabs send auth "\n" # press enter (confirm a prompt)
|
|
140
|
-
cctabs send auth "/clear\n" # send a slash command
|
|
141
|
-
cctabs send auth --file ~/prompts/task.txt # send a full prompt from file
|
|
142
|
-
echo "do the thing" | cctabs send auth # pipe via stdin
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Workflow: Worktrees
|
|
146
|
-
|
|
147
|
-
**Always point tabs at the repo root — never at a manually-created worktree directory.** Claude Code manages worktrees itself via `claude --worktree <name>`, which creates `.claude/worktrees/<name>/` inside the repo and handles branch creation and cleanup automatically.
|
|
148
|
-
|
|
149
|
-
### New isolated session (new branch, Claude manages everything)
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
cctabs new feature-name ~/Dev/myapp --worktree
|
|
153
|
-
# Equivalent to: cd ~/Dev/myapp && claude --worktree "feature-name" --name "feature-name"
|
|
154
|
-
# Claude creates: ~/Dev/myapp/.claude/worktrees/feature-name/
|
|
155
|
-
# Claude creates branch: worktree-feature-name
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Existing branch — ask Claude to enter the worktree mid-session
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
cctabs new hiring ~/Dev/myapp # open tab at repo root
|
|
162
|
-
cctabs send hiring "Enter a worktree for branch z.old/new-hire-ad and ..."
|
|
163
|
-
# Claude will use EnterWorktree tool to set up isolation
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Do NOT manage git worktrees manually
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
# ❌ WRONG — do not create worktree dirs yourself and pass them to cctabs new
|
|
170
|
-
git worktree add ~/Dev/myapp-feature branch
|
|
171
|
-
cctabs new feature ~/Dev/myapp-feature
|
|
172
|
-
|
|
173
|
-
# ✅ RIGHT — always use repo root; let Claude Code manage the worktree
|
|
174
|
-
cctabs new feature ~/Dev/myapp --worktree
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Why:** Manually created worktree dirs placed outside the repo confuse Claude Code's session tracking, project memory lookup (`.claude/` is in the main repo), and CLAUDE.md resolution. Claude Code's built-in worktree support keeps everything co-located under `.claude/worktrees/` and handles cleanup on session exit.
|
|
178
|
-
|
|
179
|
-
## Workflow: Cleanup
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
cctabs sessions # find idle/terminal tabs
|
|
183
|
-
cctabs close old-feature # close by name (prefix match)
|
|
184
|
-
cctabs close e5f6a7b8 # close by block ID prefix
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## Tab Naming Conventions
|
|
188
|
-
|
|
189
|
-
Name tabs after the **project or task**:
|
|
190
|
-
- `auth` — authentication work
|
|
191
|
-
- `api` — API service
|
|
192
|
-
- `infra` — infrastructure
|
|
193
|
-
- `pr-1234` — specific PR work
|
|
194
|
-
- `auth-v2` — forked attempt
|
|
195
|
-
|
|
196
|
-
## Notes
|
|
197
|
-
|
|
198
|
-
- Tab names are matched by exact name or prefix (case-insensitive)
|
|
199
|
-
- Block IDs can be abbreviated to the first 8 characters
|
|
200
|
-
- `cctabs new` and `cctabs resume` automatically pass `--name <tab-name>` to claude, syncing the session display name with the tab title
|
|
201
|
-
- Configured `claude.flags` in `~/.config/cctabs/config.toml` are applied to every session
|
|
202
|
-
- `cctabs send` resolves tab names to their terminal block automatically
|