claude-term-tabs 0.0.0-bootstrap

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.
@@ -0,0 +1,127 @@
1
+ import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
2
+ import { repoName } from "./paths.js";
3
+ const HEAD_BYTES = 64 * 1024;
4
+ const DEFAULT_TAIL_BYTES = 256 * 1024;
5
+ const PROMPT_CHARS = 200;
6
+ const WRAPPER_PREFIXES = ['<command', '<local-command', '<system', '<user-prompt-submit-hook', 'Caveat:'];
7
+ /**
8
+ * Build a Digest from a transcript by reading its head (first prompt, cwd) and a tail window
9
+ * (latest title, last prompt, branch). A missing or unreadable file yields the fallback fields
10
+ * plus a `note`; it never throws.
11
+ */
12
+ export function extractDigest(path, fallback, opts = {}) {
13
+ let fd;
14
+ try {
15
+ fd = openSync(path, 'r');
16
+ }
17
+ catch (err) {
18
+ return fallbackDigest(fallback, `transcript unreadable: ${err.message}`);
19
+ }
20
+ try {
21
+ const { size, mtimeMs } = fstatSync(fd);
22
+ const headEntries = parseLines(readRange(fd, 0, Math.min(size, HEAD_BYTES)), false);
23
+ const tail = readTail(fd, size, opts.tailBytes ?? DEFAULT_TAIL_BYTES);
24
+ const cwd = tail.cwd ?? firstString(headEntries, 'cwd') ?? fallback.cwd ?? '';
25
+ return {
26
+ sessionId: fallback.sessionId,
27
+ cwd,
28
+ ...repoName(cwd),
29
+ title: tail.customTitle ?? tail.aiTitle ?? tail.summary,
30
+ firstPrompt: firstRealPrompt(headEntries),
31
+ lastPrompt: trimPrompt(tail.lastPrompt),
32
+ branch: tail.branch ?? firstString(headEntries, 'gitBranch'),
33
+ lastActive: Math.round(mtimeMs)
34
+ };
35
+ }
36
+ finally {
37
+ closeSync(fd);
38
+ }
39
+ }
40
+ function fallbackDigest(fallback, note) {
41
+ const cwd = fallback.cwd ?? '';
42
+ return { sessionId: fallback.sessionId, cwd, ...repoName(cwd), lastActive: 0, note };
43
+ }
44
+ function readRange(fd, start, length) {
45
+ const buf = Buffer.alloc(length);
46
+ const read = readSync(fd, buf, 0, length, start);
47
+ return buf.subarray(0, read).toString('utf8');
48
+ }
49
+ function readTail(fd, size, initial) {
50
+ let window = Math.max(1, initial);
51
+ for (;;) {
52
+ const start = Math.max(0, size - window);
53
+ const entries = parseLines(readRange(fd, start, size - start), start > 0);
54
+ const fields = collectTail(entries);
55
+ const titled = fields.customTitle ?? fields.aiTitle ?? fields.summary;
56
+ if (titled !== undefined || start === 0)
57
+ return fields;
58
+ window *= 2;
59
+ }
60
+ }
61
+ function parseLines(text, dropFirst) {
62
+ const lines = text.split('\n');
63
+ const usable = dropFirst ? lines.slice(1) : lines;
64
+ return usable.flatMap(line => {
65
+ if (line.trim() === '')
66
+ return [];
67
+ try {
68
+ const parsed = JSON.parse(line);
69
+ return isEntry(parsed) ? [parsed] : [];
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ });
75
+ }
76
+ function isEntry(v) {
77
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
78
+ }
79
+ function collectTail(entries) {
80
+ return entries.reduce((acc, e) => ({
81
+ customTitle: e.type === 'custom-title' ? str(e.customTitle) ?? acc.customTitle : acc.customTitle,
82
+ aiTitle: e.type === 'ai-title' ? str(e.aiTitle) ?? acc.aiTitle : acc.aiTitle,
83
+ summary: e.type === 'summary' ? str(e.summary) ?? acc.summary : acc.summary,
84
+ lastPrompt: e.type === 'last-prompt' ? str(e.lastPrompt) ?? acc.lastPrompt : acc.lastPrompt,
85
+ branch: str(e.gitBranch) ?? acc.branch,
86
+ cwd: str(e.cwd) ?? acc.cwd
87
+ }), {});
88
+ }
89
+ function firstString(entries, key) {
90
+ for (const e of entries) {
91
+ const v = str(e[key]);
92
+ if (v !== undefined)
93
+ return v;
94
+ }
95
+ return undefined;
96
+ }
97
+ function firstRealPrompt(entries) {
98
+ for (const e of entries) {
99
+ if (e.type !== 'user' || e.isMeta === true)
100
+ continue;
101
+ const text = userText(e.message);
102
+ if (text !== undefined && !WRAPPER_PREFIXES.some(p => text.trimStart().startsWith(p))) {
103
+ return trimPrompt(text);
104
+ }
105
+ }
106
+ return undefined;
107
+ }
108
+ function userText(message) {
109
+ if (!isEntry(message))
110
+ return undefined;
111
+ const content = message.content;
112
+ if (typeof content === 'string')
113
+ return content;
114
+ if (!Array.isArray(content))
115
+ return undefined;
116
+ const texts = content.filter(isEntry).filter(b => b.type === 'text').map(b => str(b.text) ?? '');
117
+ const joined = texts.join(' ').trim();
118
+ return joined === '' ? undefined : joined;
119
+ }
120
+ function trimPrompt(text) {
121
+ if (text === undefined)
122
+ return undefined;
123
+ return text.replace(/\s+/g, ' ').trim().slice(0, PROMPT_CHARS);
124
+ }
125
+ function str(v) {
126
+ return typeof v === 'string' && v !== '' ? v : undefined;
127
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "claude-term-tabs",
3
+ "version": "0.0.0-bootstrap",
4
+ "description": "Find a running Claude Code session by what you remember, and jump to its terminal tab",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Emma Coutu",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ecoutu/claude-term-tabs.git"
11
+ },
12
+ "homepage": "https://github.com/ecoutu/claude-term-tabs#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ecoutu/claude-term-tabs/issues"
15
+ },
16
+ "keywords": [
17
+ "claude",
18
+ "claude-code",
19
+ "iterm2",
20
+ "wezterm",
21
+ "terminal",
22
+ "tabs",
23
+ "cli"
24
+ ],
25
+ "bin": {
26
+ "claude-term-tabs": "bin/claude-term-tabs",
27
+ "ctt": "bin/claude-term-tabs"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "dist"
32
+ ],
33
+ "engines": {
34
+ "node": ">=24"
35
+ },
36
+ "scripts": {
37
+ "test": "CTT_NO_REAL_EXEC=1 node --test 'tests/**/*.test.ts'",
38
+ "check": "tsc",
39
+ "prepare": "husky",
40
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
41
+ "prepack": "npm run build"
42
+ },
43
+ "devDependencies": {
44
+ "@commitlint/cli": "21.2.4",
45
+ "@commitlint/config-conventional": "21.2.4",
46
+ "@semantic-release/changelog": "7.0.0",
47
+ "@semantic-release/git": "11.0.1",
48
+ "@types/node": "24.19.2",
49
+ "husky": "9.1.7",
50
+ "semantic-release": "25.0.9",
51
+ "typescript": "5.9.3"
52
+ }
53
+ }