ccompactor 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/dist/adapters/claude.js +109 -122
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts +1 -1
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/pi.d.ts +1 -1
- package/dist/adapters/pi.js +8 -4
- package/dist/adapters/pi.js.map +1 -1
- package/dist/adapters/types.d.ts +24 -2
- package/dist/adapters/types.js +33 -5
- package/dist/adapters/types.js.map +1 -1
- package/dist/artifact/render.js +2 -2
- package/dist/artifact/transcript.d.ts +31 -0
- package/dist/artifact/transcript.js +82 -0
- package/dist/artifact/transcript.js.map +1 -0
- package/dist/cli.js +32 -10
- package/dist/cli.js.map +1 -1
- package/dist/discover/index.d.ts +1 -0
- package/dist/discover/index.js.map +1 -1
- package/dist/extract.d.ts +4 -0
- package/dist/extract.js +27 -0
- package/dist/extract.js.map +1 -1
- package/dist/tui/App.d.ts +8 -0
- package/dist/tui/App.js +513 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/details.d.ts +37 -0
- package/dist/tui/details.js +75 -0
- package/dist/tui/details.js.map +1 -0
- package/dist/tui/index.d.ts +3 -2
- package/dist/tui/index.js +10 -135
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/mouse.d.ts +37 -0
- package/dist/tui/mouse.js +41 -0
- package/dist/tui/mouse.js.map +1 -0
- package/dist/tui/theme.d.ts +40 -0
- package/dist/tui/theme.js +54 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readSession } from '../discover/index.js';
|
|
2
|
+
import { buildLedgers } from '../ledgers/index.js';
|
|
3
|
+
/** Load metadata and the newest `previewCount` turns. */
|
|
4
|
+
export async function loadDetails(ref, previewCount = 30) {
|
|
5
|
+
// `light` throughout: nothing here needs the original records, and keeping
|
|
6
|
+
// them for a handful of concurrent 200 MB transcripts is what exhausted the
|
|
7
|
+
// heap.
|
|
8
|
+
const ir = await readSession(ref, { light: true });
|
|
9
|
+
return describe(ir, previewCount);
|
|
10
|
+
}
|
|
11
|
+
export function describe(ir, previewCount) {
|
|
12
|
+
const ledgers = buildLedgers(ir);
|
|
13
|
+
const text = ir.messages.reduce((n, m) => n + (m.text?.length ?? 0), 0);
|
|
14
|
+
const first = ir.messages[0]?.timestamp;
|
|
15
|
+
const last = ir.messages.at(-1)?.timestamp;
|
|
16
|
+
return {
|
|
17
|
+
messages: ir.messages.length,
|
|
18
|
+
userTurns: ledgers.counts.userTurns,
|
|
19
|
+
toolCalls: ledgers.counts.toolCalls,
|
|
20
|
+
tokens: Math.ceil(text / 4),
|
|
21
|
+
...(str(ir.metadata['cwd']) ? { cwd: str(ir.metadata['cwd']) } : {}),
|
|
22
|
+
...(str(ir.metadata['branch']) ? { branch: str(ir.metadata['branch']) } : {}),
|
|
23
|
+
...(str(ir.metadata['model']) ? { model: str(ir.metadata['model']) } : {}),
|
|
24
|
+
...(str(ir.metadata['version']) ? { version: str(ir.metadata['version']) } : {}),
|
|
25
|
+
...(first ? { first } : {}),
|
|
26
|
+
...(last ? { last } : {}),
|
|
27
|
+
compactBoundaries: ir.compactBoundaries.length,
|
|
28
|
+
preview: preview(ir, previewCount),
|
|
29
|
+
diagnostics: ir.diagnostics.length,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The newest turns, oldest first. Tool calls become one line; results are dropped. */
|
|
33
|
+
function preview(ir, count) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const message of ir.messages) {
|
|
36
|
+
const text = (message.text ?? '').trim();
|
|
37
|
+
if (message.isHumanTurn && text) {
|
|
38
|
+
out.push({ role: 'user', text: clip(text, 900), evt: message.eventIndex });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (message.role === 'assistant') {
|
|
42
|
+
if (text) {
|
|
43
|
+
out.push({ role: 'agent', text: clip(text, 700), evt: message.eventIndex });
|
|
44
|
+
}
|
|
45
|
+
else if (message.toolName) {
|
|
46
|
+
out.push({
|
|
47
|
+
role: 'tool',
|
|
48
|
+
text: `${message.toolName} ${clip(stringify(message.toolInput), 90)}`,
|
|
49
|
+
evt: message.eventIndex,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out.slice(-count);
|
|
55
|
+
}
|
|
56
|
+
function str(value) {
|
|
57
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
58
|
+
}
|
|
59
|
+
function stringify(value) {
|
|
60
|
+
if (value === undefined)
|
|
61
|
+
return '';
|
|
62
|
+
if (typeof value === 'string')
|
|
63
|
+
return value;
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(value);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function clip(text, max) {
|
|
72
|
+
const single = text.replace(/\s+/g, ' ').trim();
|
|
73
|
+
return single.length <= max ? single : `${single.slice(0, max - 1)}…`;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=details.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"details.js","sourceRoot":"","sources":["../../src/tui/details.ts"],"names":[],"mappings":"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"}
|
package/dist/tui/index.d.ts
CHANGED
package/dist/tui/index.js
CHANGED
|
@@ -1,146 +1,21 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* `ccompactor --tui`
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
* command — in that order, because that is the order anyone actually works in.
|
|
8
|
-
*
|
|
9
|
-
* It hands the terminal over to the launched agent rather than embedding it:
|
|
10
|
-
* a TUI that keeps the screen while another agent runs in a pty is a TUI that
|
|
11
|
-
* breaks both.
|
|
5
|
+
* The interactive mode, which is the whole product for anyone who does not
|
|
6
|
+
* already know the session id they want.
|
|
12
7
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { listSessions, fuzzyScore } from '../discover/index.js';
|
|
16
|
-
import { extractSession } from '../extract.js';
|
|
17
|
-
import { plan, installed } from '../handoff/index.js';
|
|
18
|
-
function App({ outDir, anyProject, onDone }) {
|
|
19
|
-
const { exit } = useApp();
|
|
20
|
-
const { stdout } = useStdout();
|
|
21
|
-
const [rows, setRows] = useState([]);
|
|
22
|
-
const [loading, setLoading] = useState(true);
|
|
23
|
-
const [query, setQuery] = useState('');
|
|
24
|
-
const [searching, setSearching] = useState(false);
|
|
25
|
-
const [cursor, setCursor] = useState(0);
|
|
26
|
-
const [screen, setScreen] = useState('browse');
|
|
27
|
-
const [status, setStatus] = useState('');
|
|
28
|
-
const [artifact, setArtifact] = useState();
|
|
29
|
-
const [targets, setTargets] = useState([]);
|
|
30
|
-
const height = Math.max(6, (stdout?.rows ?? 30) - 8);
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
void (async () => {
|
|
33
|
-
setRows(await listSessions({ anyProject }));
|
|
34
|
-
setTargets(installed());
|
|
35
|
-
setLoading(false);
|
|
36
|
-
})();
|
|
37
|
-
}, [anyProject]);
|
|
38
|
-
const filtered = useMemo(() => {
|
|
39
|
-
if (query.trim().length === 0)
|
|
40
|
-
return rows;
|
|
41
|
-
return rows
|
|
42
|
-
.map((ref) => ({ ref, score: fuzzyScore(query, `${ref.agent} ${ref.id} ${ref.path}`) }))
|
|
43
|
-
.filter((hit) => hit.score > 0)
|
|
44
|
-
.sort((a, b) => b.score - a.score)
|
|
45
|
-
.map((hit) => hit.ref);
|
|
46
|
-
}, [rows, query]);
|
|
47
|
-
const selected = filtered[Math.min(cursor, Math.max(0, filtered.length - 1))];
|
|
48
|
-
const runExtract = useCallback(async () => {
|
|
49
|
-
if (!selected)
|
|
50
|
-
return;
|
|
51
|
-
setStatus(`extracting ${selected.agent}:${selected.id} …`);
|
|
52
|
-
try {
|
|
53
|
-
const result = await extractSession(`${selected.agent}:${selected.id}`, { outDir, anyProject }, (stage, message) => setStatus(`${stage}: ${message}`));
|
|
54
|
-
setArtifact(result.written[0] ?? `${outDir}/handoff.md`);
|
|
55
|
-
setStatus(`wrote ${result.rendered.tokens} tokens in ${result.elapsedMs} ms (${result.llm}) — h for handoff`);
|
|
56
|
-
setScreen('handoff');
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
setStatus(`error: ${error.message}`);
|
|
60
|
-
}
|
|
61
|
-
}, [selected, outDir, anyProject]);
|
|
62
|
-
const launch = useCallback(async () => {
|
|
63
|
-
if (!artifact)
|
|
64
|
-
return;
|
|
65
|
-
const target = targets[0];
|
|
66
|
-
if (!target) {
|
|
67
|
-
setStatus('no launchable agent found on PATH');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const command = plan(target, artifact, process.cwd());
|
|
71
|
-
// The terminal is handed over rather than shared: the child owns it.
|
|
72
|
-
setStatus(`launching ${command.display}`);
|
|
73
|
-
exit();
|
|
74
|
-
const { spawnSync } = await import('node:child_process');
|
|
75
|
-
const result = spawnSync(command.program, command.argv, { stdio: 'inherit' });
|
|
76
|
-
onDone(result.status ?? 0);
|
|
77
|
-
}, [artifact, targets, exit, onDone]);
|
|
78
|
-
useInput((input, key) => {
|
|
79
|
-
if (key.ctrl && input === 'c') {
|
|
80
|
-
exit();
|
|
81
|
-
onDone(0);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (searching) {
|
|
85
|
-
if (key.return || key.escape) {
|
|
86
|
-
setSearching(false);
|
|
87
|
-
}
|
|
88
|
-
else if (key.backspace || key.delete) {
|
|
89
|
-
setQuery((q) => q.slice(0, -1));
|
|
90
|
-
}
|
|
91
|
-
else if (input && !key.ctrl && !key.meta) {
|
|
92
|
-
setQuery((q) => q + input);
|
|
93
|
-
setCursor(0);
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (input === 'q') {
|
|
98
|
-
exit();
|
|
99
|
-
onDone(0);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (input === '/') {
|
|
103
|
-
setSearching(true);
|
|
104
|
-
setQuery('');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (key.escape) {
|
|
108
|
-
setScreen('browse');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (key.upArrow)
|
|
112
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
113
|
-
if (key.downArrow)
|
|
114
|
-
setCursor((c) => Math.min(filtered.length - 1, c + 1));
|
|
115
|
-
if (key.return && selected)
|
|
116
|
-
setScreen('detail');
|
|
117
|
-
if (input === 'e')
|
|
118
|
-
void runExtract();
|
|
119
|
-
if (input === 'h' && artifact)
|
|
120
|
-
setScreen('handoff');
|
|
121
|
-
});
|
|
122
|
-
if (loading)
|
|
123
|
-
return _jsx(Text, { children: "scanning agent stores \u2026" });
|
|
124
|
-
const visible = filtered.slice(Math.max(0, cursor - height + 3), Math.max(height - 2, cursor + 1));
|
|
125
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "ccompactor" }), _jsxs(Text, { dimColor: true, children: [' ', filtered.length, " of ", rows.length, " session(s)", anyProject ? ' (all projects)' : ''] })] }), searching ? (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "search: " }), _jsx(Text, { children: query }), _jsx(Text, { color: "gray", children: "\u258F" })] })) : (_jsx(Text, { dimColor: true, children: "/ search \u00B7 \u2191\u2193 move \u00B7 enter detail \u00B7 e extract \u00B7 h handoff \u00B7 q quit" })), _jsx(Box, { flexDirection: "column", marginTop: 1, children: visible.map((ref, index) => {
|
|
126
|
-
const active = filtered[cursor]?.id === ref.id;
|
|
127
|
-
return (_jsxs(Text, { inverse: active, children: [active ? '❯ ' : ' ', ref.agent.padEnd(7), ref.id.slice(0, 36).padEnd(38), sizeLabel(ref.bytes).padStart(8), " ", new Date(ref.mtime).toISOString().slice(0, 16).replace('T', ' ')] }, `${ref.agent}:${ref.id}:${index}`));
|
|
128
|
-
}) }), screen !== 'browse' && selected && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Text, { bold: true, children: [selected.agent, ":", selected.id] }), _jsx(Text, { dimColor: true, children: selected.path }), screen === 'detail' && (_jsxs(Text, { children: [sizeLabel(selected.bytes), " \u00B7 modified", ' ', new Date(selected.mtime).toISOString().slice(0, 19).replace('T', ' '), '\n', "Press e to extract."] })), screen === 'handoff' && artifact && (_jsxs(Text, { children: ["artifact: ", artifact, '\n', "targets on PATH: ", targets.join(', ') || 'none', '\n', targets[0] ? plan(targets[0], artifact, process.cwd()).display : '', '\n', "Press h again or enter to launch."] }))] })), status.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", children: status }) }))] }));
|
|
129
|
-
}
|
|
130
|
-
function sizeLabel(bytes) {
|
|
131
|
-
if (bytes === undefined)
|
|
132
|
-
return '—';
|
|
133
|
-
if (bytes < 1024)
|
|
134
|
-
return `${bytes} B`;
|
|
135
|
-
if (bytes < 1024 * 1024)
|
|
136
|
-
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
137
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
138
|
-
}
|
|
8
|
+
import { render } from 'ink';
|
|
9
|
+
import { App } from './App.js';
|
|
139
10
|
export async function runTui(options = {}) {
|
|
11
|
+
if (!process.stdin.isTTY) {
|
|
12
|
+
process.stderr.write('the TUI needs a terminal: stdin is not a tty. Use `ccompactor list` and `ccompactor extract` instead.\n');
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
140
15
|
let code = 0;
|
|
141
16
|
const app = render(_jsx(App, { outDir: options.outDir ?? '.ccompactor', anyProject: options.anyProject ?? true, onDone: (value) => {
|
|
142
17
|
code = value;
|
|
143
|
-
} }));
|
|
18
|
+
} }), { exitOnCtrlC: false });
|
|
144
19
|
await app.waitUntilExit();
|
|
145
20
|
return code;
|
|
146
21
|
}
|
package/dist/tui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.tsx"],"names":[],"mappings":";AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAO9B,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,UAAsB,EAAE;IACnD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yGAAyG,CAC1G,CAAA;QACD,OAAO,CAAC,CAAA;IACV,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,MAAM,GAAG,GAAG,MAAM,CAChB,KAAC,GAAG,IACF,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa,EACvC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI,EACtC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxB,IAAI,GAAG,KAAK,CAAA;QACd,CAAC,GACD,EACF,EAAE,WAAW,EAAE,KAAK,EAAE,CACvB,CAAA;IACD,MAAM,GAAG,CAAC,aAAa,EAAE,CAAA;IACzB,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mouse support, by hand.
|
|
3
|
+
*
|
|
4
|
+
* Ink does not surface mouse events: a click arrives on stdin as an escape
|
|
5
|
+
* sequence that its key parser discards. Verified by sending a click to an Ink
|
|
6
|
+
* app and watching `useInput` stay silent. So the sequences are enabled here,
|
|
7
|
+
* parsed here, and delivered through a callback.
|
|
8
|
+
*
|
|
9
|
+
* SGR mode (`?1006`) is what makes this tractable: the coordinates are plain
|
|
10
|
+
* decimal and there is no 223-column limit, unlike the original X10 encoding.
|
|
11
|
+
* A press is `ESC [ < b ; x ; y M`, a release ends in `m`.
|
|
12
|
+
*/
|
|
13
|
+
import type { Writable } from 'node:stream';
|
|
14
|
+
export type MouseKind = 'click' | 'wheel-up' | 'wheel-down' | 'other';
|
|
15
|
+
export interface MouseEvent {
|
|
16
|
+
kind: MouseKind;
|
|
17
|
+
/** 1-based column, as the terminal reports it. */
|
|
18
|
+
x: number;
|
|
19
|
+
/** 1-based row, as the terminal reports it. */
|
|
20
|
+
y: number;
|
|
21
|
+
/** The raw SGR button code, for anything this does not model. */
|
|
22
|
+
button: number;
|
|
23
|
+
}
|
|
24
|
+
/** Turn on click and wheel reporting, and return a function that turns it off. */
|
|
25
|
+
export declare function enableMouse(out: Writable): () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Pull every mouse event out of a chunk of stdin.
|
|
28
|
+
*
|
|
29
|
+
* Returns the events and the chunk with those sequences removed, so a caller can
|
|
30
|
+
* decide whether anything else is worth passing on. Anything that is not a
|
|
31
|
+
* complete sequence is ignored rather than buffered: a partial escape at the end
|
|
32
|
+
* of a chunk is far more likely to be a keystroke than a click.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readMouse(data: string): {
|
|
35
|
+
events: MouseEvent[];
|
|
36
|
+
rest: string;
|
|
37
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const ENABLE = '\u001b[?1000h\u001b[?1006h';
|
|
2
|
+
const DISABLE = '\u001b[?1000l\u001b[?1006l';
|
|
3
|
+
/** Turn on click and wheel reporting, and return a function that turns it off. */
|
|
4
|
+
export function enableMouse(out) {
|
|
5
|
+
out.write(ENABLE);
|
|
6
|
+
return () => out.write(DISABLE);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Pull every mouse event out of a chunk of stdin.
|
|
10
|
+
*
|
|
11
|
+
* Returns the events and the chunk with those sequences removed, so a caller can
|
|
12
|
+
* decide whether anything else is worth passing on. Anything that is not a
|
|
13
|
+
* complete sequence is ignored rather than buffered: a partial escape at the end
|
|
14
|
+
* of a chunk is far more likely to be a keystroke than a click.
|
|
15
|
+
*/
|
|
16
|
+
export function readMouse(data) {
|
|
17
|
+
const events = [];
|
|
18
|
+
let rest = data;
|
|
19
|
+
const pattern = /\u001b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
20
|
+
rest = rest.replace(pattern, (_match, b, x, y, final) => {
|
|
21
|
+
if (final === 'm')
|
|
22
|
+
return ''; // releases carry nothing we act on
|
|
23
|
+
const button = Number.parseInt(b, 10);
|
|
24
|
+
const kind = (button & 64) !== 0
|
|
25
|
+
? (button & 1) === 0
|
|
26
|
+
? 'wheel-up'
|
|
27
|
+
: 'wheel-down'
|
|
28
|
+
: (button & 3) === 0
|
|
29
|
+
? 'click'
|
|
30
|
+
: 'other';
|
|
31
|
+
events.push({
|
|
32
|
+
kind,
|
|
33
|
+
x: Number.parseInt(x, 10),
|
|
34
|
+
y: Number.parseInt(y, 10),
|
|
35
|
+
button,
|
|
36
|
+
});
|
|
37
|
+
return '';
|
|
38
|
+
});
|
|
39
|
+
return { events, rest };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=mouse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mouse.js","sourceRoot":"","sources":["../../src/tui/mouse.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,GAAG,4BAA4B,CAAA;AAC3C,MAAM,OAAO,GAAG,4BAA4B,CAAA;AAc5C,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,GAAa;IACvC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACjB,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,IAAI,IAAI,GAAG,IAAI,CAAA;IACf,MAAM,OAAO,GAAG,mCAAmC,CAAA;IACnD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,KAAa,EAAE,EAAE;QACtF,IAAI,KAAK,KAAK,GAAG;YAAE,OAAO,EAAE,CAAA,CAAC,mCAAmC;QAChE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QACrC,MAAM,IAAI,GACR,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC;YACjB,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC;gBAClB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,YAAY;YAChB,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC;gBAClB,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,OAAO,CAAA;QACf,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;YACzB,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;YACzB,MAAM;SACP,CAAC,CAAA;QACF,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,CAAA;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AACzB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI palette, matched to the website.
|
|
3
|
+
*
|
|
4
|
+
* Safety yellow on blacktop, the same two colours the site is built from, so
|
|
5
|
+
* the terminal and the browser look like the same product. The yellow is a
|
|
6
|
+
* *surface* and an accent, never a colour for body text: black on `#FFD400` is
|
|
7
|
+
* 13.75:1, and `#FFDD2E` on a dark terminal is 14.7:1, whereas plain `#FFD400`
|
|
8
|
+
* text on white is unreadable.
|
|
9
|
+
*
|
|
10
|
+
* The terminal's own background is left alone. Forcing black would fight a
|
|
11
|
+
* reader's theme, and the selection row carries the brand on its own.
|
|
12
|
+
*/
|
|
13
|
+
export declare const THEME: {
|
|
14
|
+
readonly yellow: "#FFD400";
|
|
15
|
+
/** Yellow as text on a dark terminal. */
|
|
16
|
+
readonly yellowInk: "#FFDD2E";
|
|
17
|
+
/** Black, for text sitting on yellow. */
|
|
18
|
+
readonly onYellow: "#0B0B0B";
|
|
19
|
+
/** Blacktop, the site's surface colour. */
|
|
20
|
+
readonly black: "#141412";
|
|
21
|
+
/** Secondary text. */
|
|
22
|
+
readonly muted: "#8A8880";
|
|
23
|
+
/** The dimmer half of a hazard stripe. */
|
|
24
|
+
readonly stripeDim: "#6B6A63";
|
|
25
|
+
};
|
|
26
|
+
/** The width a full-bleed rule should be. */
|
|
27
|
+
export declare function width(columns: number | undefined): number;
|
|
28
|
+
/**
|
|
29
|
+
* A hazard stripe: the site's signature, in a terminal.
|
|
30
|
+
*
|
|
31
|
+
* Alternating filled and hollow blocks read as a warning tape at any width.
|
|
32
|
+
* Ink has no way to draw a background stripe across a line, so the character
|
|
33
|
+
* itself carries the colour.
|
|
34
|
+
*/
|
|
35
|
+
export declare function hazard(columns: number | undefined): Array<{
|
|
36
|
+
ch: string;
|
|
37
|
+
dim: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
/** A file size a human can read at a glance. */
|
|
40
|
+
export declare function sizeLabel(bytes: number | undefined): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI palette, matched to the website.
|
|
3
|
+
*
|
|
4
|
+
* Safety yellow on blacktop, the same two colours the site is built from, so
|
|
5
|
+
* the terminal and the browser look like the same product. The yellow is a
|
|
6
|
+
* *surface* and an accent, never a colour for body text: black on `#FFD400` is
|
|
7
|
+
* 13.75:1, and `#FFDD2E` on a dark terminal is 14.7:1, whereas plain `#FFD400`
|
|
8
|
+
* text on white is unreadable.
|
|
9
|
+
*
|
|
10
|
+
* The terminal's own background is left alone. Forcing black would fight a
|
|
11
|
+
* reader's theme, and the selection row carries the brand on its own.
|
|
12
|
+
*/
|
|
13
|
+
export const THEME = {
|
|
14
|
+
yellow: '#FFD400',
|
|
15
|
+
/** Yellow as text on a dark terminal. */
|
|
16
|
+
yellowInk: '#FFDD2E',
|
|
17
|
+
/** Black, for text sitting on yellow. */
|
|
18
|
+
onYellow: '#0B0B0B',
|
|
19
|
+
/** Blacktop, the site's surface colour. */
|
|
20
|
+
black: '#141412',
|
|
21
|
+
/** Secondary text. */
|
|
22
|
+
muted: '#8A8880',
|
|
23
|
+
/** The dimmer half of a hazard stripe. */
|
|
24
|
+
stripeDim: '#6B6A63',
|
|
25
|
+
};
|
|
26
|
+
/** The width a full-bleed rule should be. */
|
|
27
|
+
export function width(columns) {
|
|
28
|
+
return Math.max(20, Math.min(columns ?? 80, 120));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A hazard stripe: the site's signature, in a terminal.
|
|
32
|
+
*
|
|
33
|
+
* Alternating filled and hollow blocks read as a warning tape at any width.
|
|
34
|
+
* Ink has no way to draw a background stripe across a line, so the character
|
|
35
|
+
* itself carries the colour.
|
|
36
|
+
*/
|
|
37
|
+
export function hazard(columns) {
|
|
38
|
+
const total = width(columns) - 2;
|
|
39
|
+
return Array.from({ length: total }, (_, i) => ({
|
|
40
|
+
ch: i % 2 === 0 ? '▰' : '▱',
|
|
41
|
+
dim: i % 2 === 1,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** A file size a human can read at a glance. */
|
|
45
|
+
export function sizeLabel(bytes) {
|
|
46
|
+
if (bytes === undefined)
|
|
47
|
+
return '—';
|
|
48
|
+
if (bytes < 1024)
|
|
49
|
+
return `${bytes} B`;
|
|
50
|
+
if (bytes < 1024 * 1024)
|
|
51
|
+
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
52
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/tui/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,MAAM,EAAE,SAAS;IACjB,yCAAyC;IACzC,SAAS,EAAE,SAAS;IACpB,yCAAyC;IACzC,QAAQ,EAAE,SAAS;IACnB,2CAA2C;IAC3C,KAAK,EAAE,SAAS;IAChB,sBAAsB;IACtB,KAAK,EAAE,SAAS;IAChB,0CAA0C;IAC1C,SAAS,EAAE,SAAS;CACZ,CAAA;AAEV,6CAA6C;AAC7C,MAAM,UAAU,KAAK,CAAC,OAA2B;IAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,OAA2B;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QAC3B,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC;KACjB,CAAC,CAAC,CAAA;AACL,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAA;IACnC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAA;IACrC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;IACjE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;AACnD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccompactor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Extract any coding-agent session (Claude Code, Codex, Pi) into a compact, verified, provenance-linked handoff that any other agent can continue from.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc -p tsconfig.json",
|
|
30
|
-
"
|
|
30
|
+
"watch": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
31
|
+
"dev": "npm run build --silent && node dist/cli.js",
|
|
31
32
|
"test": "tsc -p tsconfig.test.json && cd dist-test && node --test",
|
|
32
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
34
|
+
"prepack": "node scripts/guard-vendor.mjs",
|
|
35
|
+
"prepublishOnly": "node scripts/guard-vendor.mjs && npm run build"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"commander": "^13.1.0",
|