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.
@@ -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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"}
@@ -1,4 +1,5 @@
1
- export declare function runTui(options?: {
1
+ export interface TuiOptions {
2
2
  outDir?: string;
3
3
  anyProject?: boolean;
4
- }): Promise<number>;
4
+ }
5
+ export declare function runTui(options?: TuiOptions): Promise<number>;
package/dist/tui/index.js CHANGED
@@ -1,146 +1,21 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * `ccompactor --tui`
4
4
  *
5
- * The same work as the flags, without having to know them. A session browser
6
- * that filters, shows what a session is, extracts it, and offers the handoff
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 { Box, Text, render, useApp, useInput, useStdout } from 'ink';
14
- import { useCallback, useEffect, useMemo, useState } from 'react';
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tui/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AACpE,OAAc,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAe,MAAM,qBAAqB,CAAA;AAWlE,SAAS,GAAG,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAY;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAA;IAC9B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAe,EAAE,CAAC,CAAA;IAClD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAS,QAAQ,CAAC,CAAA;IACtD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACxC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,EAAsB,CAAA;IAC9D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAW,EAAE,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,OAAO,CAAC,MAAM,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;YAC3C,UAAU,CAAC,SAAS,EAAE,CAAC,CAAA;YACvB,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC,CAAC,EAAE,CAAA;IACN,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAC1C,OAAO,IAAI;aACR,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;aACvF,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;aAC9B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;aACjC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;IAEjB,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAE7E,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC,QAAQ;YAAE,OAAM;QACrB,SAAS,CAAC,cAAc,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAA;QAC1D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,cAAc,CACjC,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,EAAE,EAAE,EAClC,EAAE,MAAM,EAAE,UAAU,EAAE,EACtB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,KAAK,OAAO,EAAE,CAAC,CACtD,CAAA;YACD,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,aAAa,CAAC,CAAA;YACxD,SAAS,CACP,SAAS,MAAM,CAAC,QAAQ,CAAC,MAAM,cAAc,MAAM,CAAC,SAAS,QAAQ,MAAM,CAAC,GAAG,mBAAmB,CACnG,CAAA;YACD,SAAS,CAAC,SAAS,CAAC,CAAA;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,CAAC,UAAW,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;QACjD,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAElC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACpC,IAAI,CAAC,QAAQ;YAAE,OAAM;QACrB,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,SAAS,CAAC,mCAAmC,CAAC,CAAA;YAC9C,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACrD,qEAAqE;QACrE,SAAS,CAAC,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QACzC,IAAI,EAAE,CAAA;QACN,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAA;QACxD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;QAC7E,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAA;IAC5B,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAErC,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtB,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAC9B,IAAI,EAAE,CAAA;YACN,MAAM,CAAC,CAAC,CAAC,CAAA;YACT,OAAM;QACR,CAAC;QACD,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC7B,YAAY,CAAC,KAAK,CAAC,CAAA;YACrB,CAAC;iBAAM,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACvC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YACjC,CAAC;iBAAM,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC3C,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAA;gBAC1B,SAAS,CAAC,CAAC,CAAC,CAAA;YACd,CAAC;YACD,OAAM;QACR,CAAC;QACD,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;YACN,MAAM,CAAC,CAAC,CAAC,CAAA;YACT,OAAM;QACR,CAAC;QACD,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAClB,YAAY,CAAC,IAAI,CAAC,CAAA;YAClB,QAAQ,CAAC,EAAE,CAAC,CAAA;YACZ,OAAM;QACR,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,SAAS,CAAC,QAAQ,CAAC,CAAA;YACnB,OAAM;QACR,CAAC;QACD,IAAI,GAAG,CAAC,OAAO;YAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACrD,IAAI,GAAG,CAAC,SAAS;YAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACzE,IAAI,GAAG,CAAC,MAAM,IAAI,QAAQ;YAAE,SAAS,CAAC,QAAQ,CAAC,CAAA;QAC/C,IAAI,KAAK,KAAK,GAAG;YAAE,KAAK,UAAU,EAAE,CAAA;QACpC,IAAI,KAAK,KAAK,GAAG,IAAI,QAAQ;YAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,IAAI,OAAO;QAAE,OAAO,KAAC,IAAI,+CAA+B,CAAA;IAExD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;IAElG,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,eACF,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,2BAEhB,EACP,MAAC,IAAI,IAAC,QAAQ,mBACX,IAAI,EACJ,QAAQ,CAAC,MAAM,UAAM,IAAI,CAAC,MAAM,iBAChC,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,IAC/B,IACH,EAEL,SAAS,CAAC,CAAC,CAAC,CACX,MAAC,GAAG,eACF,KAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,yBAAgB,EACpC,KAAC,IAAI,cAAE,KAAK,GAAQ,EACpB,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,uBAAS,IACvB,CACP,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,QAAQ,4HAA0E,CACzF,EAED,KAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,YACrC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;oBAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,CAAA;oBAC9C,OAAO,CACL,MAAC,IAAI,IAAyC,OAAO,EAAE,MAAM,aAC1D,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EACpB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EACnB,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAC9B,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAG,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,KAJ3F,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,EAAE,IAAI,KAAK,EAAE,CAKrC,CACR,CAAA;gBACH,CAAC,CAAC,GACE,EAEL,MAAM,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAClC,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,EAAE,WAAW,EAAC,OAAO,EAAC,WAAW,EAAC,MAAM,EAAC,QAAQ,EAAE,CAAC,aAC1F,MAAC,IAAI,IAAC,IAAI,mBACP,QAAQ,CAAC,KAAK,OAAG,QAAQ,CAAC,EAAE,IACxB,EACP,KAAC,IAAI,IAAC,QAAQ,kBAAE,QAAQ,CAAC,IAAI,GAAQ,EACpC,MAAM,KAAK,QAAQ,IAAI,CACtB,MAAC,IAAI,eACF,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,sBAAa,GAAG,EACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EACrE,IAAI,2BACA,CACR,EACA,MAAM,KAAK,SAAS,IAAI,QAAQ,IAAI,CACnC,MAAC,IAAI,6BACQ,QAAQ,EAClB,IAAI,uBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EACnD,IAAI,EACJ,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EACnE,IAAI,yCACA,CACR,IACG,CACP,EAEA,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CACpB,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,YACf,KAAC,IAAI,IAAC,KAAK,EAAC,OAAO,YAAE,MAAM,GAAQ,GAC/B,CACP,IACG,CACP,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAyB;IAC1C,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;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,UAAqD,EAAE;IAClF,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,CACH,CAAA;IACD,MAAM,GAAG,CAAC,aAAa,EAAE,CAAA;IACzB,OAAO,IAAI,CAAA;AACb,CAAC"}
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.0",
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
- "dev": "node --experimental-strip-types src/cli.ts",
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
- "prepublishOnly": "node scripts/guard-vendor.mjs && npm run build",
34
- "prepack": "node scripts/guard-vendor.mjs"
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",