claude-term-tabs 2.1.1 → 2.1.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 +17 -7
- package/dist/cache.js +1 -1
- package/dist/handoff.js +2 -1
- package/dist/terminals/wezterm.js +13 -2
- package/dist/transcript.js +8 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# claude-term-tabs
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/claude-term-tabs)
|
|
4
|
+
[](https://github.com/ecoutu/claude-term-tabs/actions/workflows/release.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Find a running Claude Code session by what you remember about it, and switch to its terminal tab.
|
|
4
8
|
|
|
9
|
+

|
|
10
|
+
|
|
5
11
|
```bash
|
|
6
12
|
ctt working on pause in claude-term-tabs
|
|
7
13
|
# → Pause sessions before a restart · claude-term-tabs · tab 4
|
|
8
14
|
```
|
|
9
15
|
|
|
10
|
-
Works with iTerm2 and WezTerm on macOS.
|
|
16
|
+
Works with iTerm2 on macOS, and WezTerm on macOS and Linux.
|
|
11
17
|
|
|
12
18
|
## Install
|
|
13
19
|
|
|
@@ -142,16 +148,20 @@ npm test
|
|
|
142
148
|
npm run test:package # packs, installs and runs the npm package
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
No runtime dependencies
|
|
146
|
-
pull request rules.
|
|
151
|
+
No runtime dependencies. A clone runs the TypeScript directly; the npm package ships it built to JavaScript.
|
|
152
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) has the commit and pull request rules.
|
|
153
|
+
|
|
154
|
+
`scripts/record-demo.sh` re-records `docs/demo.gif`. It runs Claude Code sessions in a scratch HOME on a hidden
|
|
155
|
+
display, so it needs Linux, Xvfb, ffmpeg and a token from `claude setup-token`; its header lists the rest.
|
|
147
156
|
|
|
148
157
|
### Releases
|
|
149
158
|
|
|
150
159
|
Every push to `main` runs CI, then semantic-release reads the conventional commit
|
|
151
|
-
messages since the last tag and, when any warrant it, tags the next version (`fix:`
|
|
152
|
-
`feat:` minor, `BREAKING CHANGE:` major), adds the notes to
|
|
153
|
-
|
|
154
|
-
version; the git tag is the version.
|
|
160
|
+
messages since the last tag and, when any warrant it, tags the next version (`fix:`, `perf:` or
|
|
161
|
+
`docs(readme):` patch, `feat:` minor, `!` or `BREAKING CHANGE:` major), adds the notes to
|
|
162
|
+
[CHANGELOG.md](CHANGELOG.md), publishes the package to npm with provenance and creates a GitHub
|
|
163
|
+
release. `package.json` keeps a placeholder version; the git tag is the version.
|
|
164
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) explains how the npm publish works.
|
|
155
165
|
|
|
156
166
|
### Manual checks
|
|
157
167
|
|
package/dist/cache.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { writeOwnerOnly } from "./fsutil.js";
|
|
3
|
-
const CACHE_VERSION =
|
|
3
|
+
const CACHE_VERSION = 2;
|
|
4
4
|
/** Digest cache keyed by transcript path, valid while the file's size and mtime are unchanged. */
|
|
5
5
|
export class DigestCache {
|
|
6
6
|
file;
|
package/dist/handoff.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
|
|
2
|
+
import { withoutPasteTags } from "./transcript.js";
|
|
2
3
|
const DEFAULT_TAIL_BYTES = 128 * 1024;
|
|
3
4
|
const HANDOFF_LINE = /^\s*(?:#+\s*|\*\*)?HANDOFF\b(?!S)/m;
|
|
4
5
|
/**
|
|
@@ -94,7 +95,7 @@ export function latestAssistantAfterPrompt(path, promptStart, sinceMs, opts = {}
|
|
|
94
95
|
const start = Math.max(0, size - window);
|
|
95
96
|
const lines = readRange(fd, start, size - start).split('\n');
|
|
96
97
|
const entries = (start > 0 ? lines.slice(1) : lines).flatMap(l => parse(l) ?? []);
|
|
97
|
-
const promptAt = findLastIndex(entries, e => e.type === 'user' && Date.parse(String(e.timestamp)) >= sinceMs && (textOf(e.message) ?? '').trimStart().startsWith(promptStart));
|
|
98
|
+
const promptAt = findLastIndex(entries, e => e.type === 'user' && Date.parse(String(e.timestamp)) >= sinceMs && withoutPasteTags(textOf(e.message) ?? '').trimStart().startsWith(promptStart));
|
|
98
99
|
if (promptAt >= 0) {
|
|
99
100
|
const replies = entries.slice(promptAt + 1).filter(e => e.type === 'assistant').map(e => textOf(e.message)).filter((t) => t !== undefined);
|
|
100
101
|
return replies.at(-1);
|
|
@@ -6,9 +6,12 @@ export function weztermAdapter(exec, shell = process.env.SHELL ?? '/bin/sh') {
|
|
|
6
6
|
throw new Error(`wezterm cli ${args[0]} failed (${r.status}): ${r.stderr.trim()}`);
|
|
7
7
|
return r.stdout;
|
|
8
8
|
};
|
|
9
|
+
const list = () => JSON.parse(cli(['list', '--format', 'json']));
|
|
10
|
+
// A spawn without --window-id lands in the caller's window, so tabs that follow a new window go there explicitly.
|
|
11
|
+
let newWindowPane;
|
|
9
12
|
return {
|
|
10
13
|
name: 'wezterm',
|
|
11
|
-
listPanes: () => toPanes(
|
|
14
|
+
listPanes: () => toPanes(list()),
|
|
12
15
|
focus: (pane) => { cli(['activate-pane', '--pane-id', pane.id]); },
|
|
13
16
|
sendText: (pane, text) => {
|
|
14
17
|
cli(['send-text', '--pane-id', pane.id, text]);
|
|
@@ -16,7 +19,15 @@ export function weztermAdapter(exec, shell = process.env.SHELL ?? '/bin/sh') {
|
|
|
16
19
|
},
|
|
17
20
|
closePane: (pane) => { cli(['kill-pane', '--pane-id', pane.id]); },
|
|
18
21
|
openPane: (cwd, command, opts) => {
|
|
19
|
-
|
|
22
|
+
const newWindow = opts?.newWindow === true;
|
|
23
|
+
const windowId = newWindow || newWindowPane === undefined ? undefined : list().find(p => String(p.pane_id) === newWindowPane)?.window_id;
|
|
24
|
+
const where = newWindow ? ['--new-window'] : windowId !== undefined ? ['--window-id', String(windowId)] : [];
|
|
25
|
+
if (newWindow)
|
|
26
|
+
newWindowPane = undefined;
|
|
27
|
+
const id = cli(['spawn', ...where, '--cwd', cwd, '--', shell, '-lc', `${command}; exec ${shell} -l`]).trim();
|
|
28
|
+
if (newWindow)
|
|
29
|
+
newWindowPane = id;
|
|
30
|
+
return id;
|
|
20
31
|
}
|
|
21
32
|
};
|
|
22
33
|
}
|
package/dist/transcript.js
CHANGED
|
@@ -4,6 +4,8 @@ const HEAD_BYTES = 64 * 1024;
|
|
|
4
4
|
const DEFAULT_TAIL_BYTES = 256 * 1024;
|
|
5
5
|
const PROMPT_CHARS = 200;
|
|
6
6
|
const WRAPPER_PREFIXES = ['<command', '<local-command', '<system', '<user-prompt-submit-hook', 'Caveat:'];
|
|
7
|
+
/** Claude Code records pasted text between `<pasted_content id="…">` and `</pasted_content id="…">` tags. */
|
|
8
|
+
const PASTE_TAG = /<\/?pasted_content\b[^>]*>/g;
|
|
7
9
|
/**
|
|
8
10
|
* Build a Digest from a transcript by reading its head (first prompt, cwd) and a tail window
|
|
9
11
|
* (latest title, last prompt, branch). A missing or unreadable file yields the fallback fields
|
|
@@ -120,7 +122,12 @@ function userText(message) {
|
|
|
120
122
|
function trimPrompt(text) {
|
|
121
123
|
if (text === undefined)
|
|
122
124
|
return undefined;
|
|
123
|
-
|
|
125
|
+
const prompt = withoutPasteTags(text).replace(/\s+/g, ' ').trim().slice(0, PROMPT_CHARS);
|
|
126
|
+
return prompt === '' ? undefined : prompt;
|
|
127
|
+
}
|
|
128
|
+
/** A prompt as it was typed, without the tags Claude Code puts around pasted text. */
|
|
129
|
+
export function withoutPasteTags(text) {
|
|
130
|
+
return text.replace(PASTE_TAG, '');
|
|
124
131
|
}
|
|
125
132
|
function str(v) {
|
|
126
133
|
return typeof v === 'string' && v !== '' ? v : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-term-tabs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "Find a running Claude Code session by what you remember, and jump to its terminal tab",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@semantic-release/changelog": "7.0.0",
|
|
48
48
|
"@semantic-release/git": "11.0.1",
|
|
49
49
|
"@types/node": "24.19.2",
|
|
50
|
+
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
50
51
|
"husky": "9.1.7",
|
|
51
52
|
"semantic-release": "25.0.9",
|
|
52
53
|
"typescript": "5.9.3"
|