agim-cli 1.2.75 → 1.2.83
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/CHANGELOG.md +208 -0
- package/dist/cli-ui/tui/app.d.ts +22 -0
- package/dist/cli-ui/tui/app.d.ts.map +1 -0
- package/dist/cli-ui/tui/app.js +578 -0
- package/dist/cli-ui/tui/app.js.map +1 -0
- package/dist/cli-ui/tui/index.d.ts +7 -0
- package/dist/cli-ui/tui/index.d.ts.map +1 -0
- package/dist/cli-ui/tui/index.js +125 -0
- package/dist/cli-ui/tui/index.js.map +1 -0
- package/dist/cli-ui/tui/markdown.d.ts +5 -0
- package/dist/cli-ui/tui/markdown.d.ts.map +1 -0
- package/dist/cli-ui/tui/markdown.js +145 -0
- package/dist/cli-ui/tui/markdown.js.map +1 -0
- package/dist/cli-ui/tui/sessions.d.ts +31 -0
- package/dist/cli-ui/tui/sessions.d.ts.map +1 -0
- package/dist/cli-ui/tui/sessions.js +107 -0
- package/dist/cli-ui/tui/sessions.js.map +1 -0
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// agim TUI entry — wired into `agim tui` subcommand.
|
|
2
|
+
//
|
|
3
|
+
// Modes:
|
|
4
|
+
// * fresh: agim tui [--agent <name>]
|
|
5
|
+
// * resume: agim tui --resume <session-id> (auto-picks last if --resume=)
|
|
6
|
+
// * list: agim tui --list-sessions
|
|
7
|
+
//
|
|
8
|
+
// Sessions persist to <AGIM_HOME>/tui-sessions/<id>.json after each
|
|
9
|
+
// agent turn and on graceful exit. On exit we print the resume hint
|
|
10
|
+
// + a trailing newline so the next shell prompt doesn't collide with
|
|
11
|
+
// the last rendered frame.
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { render } from 'ink';
|
|
14
|
+
import { TuiApp } from './app.js';
|
|
15
|
+
import { registry } from '../../core/registry.js';
|
|
16
|
+
import { loadConfig } from '../../core/onboarding.js';
|
|
17
|
+
import { logger as rootLogger } from '../../core/logger.js';
|
|
18
|
+
import { generateSessionId, listSessions, loadSession, } from './sessions.js';
|
|
19
|
+
const log = rootLogger.child({ component: 'tui' });
|
|
20
|
+
function formatSessionLine(s) {
|
|
21
|
+
const ts = s.updatedAt.replace('T', ' ').slice(0, 19);
|
|
22
|
+
const ag = s.agent ? ` · ${s.agent}` : '';
|
|
23
|
+
const msgs = s.messages > 0 ? ` · ${s.messages} 条` : '';
|
|
24
|
+
return `${s.id} ${ts}${ag}${msgs}`;
|
|
25
|
+
}
|
|
26
|
+
export async function runTui(opts = {}) {
|
|
27
|
+
// --list-sessions: print and exit (no TTY required for this).
|
|
28
|
+
if (opts.listSessions) {
|
|
29
|
+
const sessions = listSessions(50, true);
|
|
30
|
+
if (sessions.length === 0) {
|
|
31
|
+
console.log('(没有任何已保存的 TUI 会话)');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(`找到 ${sessions.length} 个 TUI 会话(最新在前):\n`);
|
|
35
|
+
for (const s of sessions)
|
|
36
|
+
console.log(' ' + formatSessionLine(s));
|
|
37
|
+
console.log('\n用法:agim tui --resume <id>');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!process.stdout.isTTY) {
|
|
41
|
+
console.error('agim tui: stdout is not a TTY — TUI requires an interactive terminal.');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
// Resolve --resume: empty string / "last" means pick most recent;
|
|
45
|
+
// explicit id means look that one up. Either way, returns null if
|
|
46
|
+
// we couldn't find it and we treat that as a fresh start with a
|
|
47
|
+
// warning.
|
|
48
|
+
let resumed;
|
|
49
|
+
let sessionId = generateSessionId();
|
|
50
|
+
let resumeWarning;
|
|
51
|
+
if (opts.resume !== undefined) {
|
|
52
|
+
const wantedId = opts.resume.trim();
|
|
53
|
+
if (!wantedId || wantedId === 'last') {
|
|
54
|
+
const sessions = listSessions(1, true);
|
|
55
|
+
if (sessions.length === 0) {
|
|
56
|
+
resumeWarning = '没有可恢复的会话,按新会话启动。';
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const s = loadSession(sessions[0].id);
|
|
60
|
+
if (s) {
|
|
61
|
+
resumed = { id: s.id, agent: s.agent, messages: s.messages };
|
|
62
|
+
sessionId = s.id;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
resumeWarning = `读取最新会话 ${sessions[0].id} 失败,按新会话启动。`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const s = loadSession(wantedId);
|
|
71
|
+
if (s) {
|
|
72
|
+
resumed = { id: s.id, agent: s.agent, messages: s.messages };
|
|
73
|
+
sessionId = s.id;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
resumeWarning = `没找到会话 "${wantedId}",按新会话启动。`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const wantVerbose = process.env.IMHUB_TUI_VERBOSE === '1';
|
|
81
|
+
const savedLevel = rootLogger.level;
|
|
82
|
+
if (!wantVerbose)
|
|
83
|
+
rootLogger.level = 'silent';
|
|
84
|
+
// Clear screen + park cursor at top-left.
|
|
85
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
86
|
+
try {
|
|
87
|
+
await registry.loadBuiltInPlugins();
|
|
88
|
+
const cfg = await loadConfig();
|
|
89
|
+
if (cfg.acpAgents && cfg.acpAgents.length > 0) {
|
|
90
|
+
await registry.loadACPAgents(cfg.acpAgents);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
log.warn({ event: 'tui.registry.load_failed', err: err instanceof Error ? err.message : String(err) }, 'registry / config load failed — proceeding best-effort');
|
|
95
|
+
}
|
|
96
|
+
let initialAgent = opts.agent ?? resumed?.agent;
|
|
97
|
+
if (initialAgent) {
|
|
98
|
+
const found = registry.findAgent(initialAgent);
|
|
99
|
+
if (!found) {
|
|
100
|
+
const list = registry.listAgents();
|
|
101
|
+
console.error(`agim tui: agent "${initialAgent}" not found. Available: ${list.join(', ') || '<none>'}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
initialAgent = found.name;
|
|
105
|
+
}
|
|
106
|
+
const { waitUntilExit } = render(React.createElement(TuiApp, { initialAgent, resumed, sessionId }), { patchConsole: true, exitOnCtrlC: true });
|
|
107
|
+
try {
|
|
108
|
+
await waitUntilExit();
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
rootLogger.level = savedLevel;
|
|
112
|
+
// v1.2.78 — drop a clean newline and print the resume hint AFTER
|
|
113
|
+
// Ink relinquishes the screen. Without these the next shell
|
|
114
|
+
// prompt collides with the last rendered frame, and the user has
|
|
115
|
+
// no way to know they can pick up where they left off.
|
|
116
|
+
process.stdout.write('\n');
|
|
117
|
+
if (resumeWarning) {
|
|
118
|
+
console.warn('⚠ ' + resumeWarning);
|
|
119
|
+
}
|
|
120
|
+
console.log(`会话 ID:${sessionId}`);
|
|
121
|
+
console.log(`恢复命令:agim tui --resume ${sessionId}`);
|
|
122
|
+
console.log(`查看所有会话:agim tui --list-sessions`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli-ui/tui/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/cli-ui/tui/markdown.tsx"],"names":[],"mappings":"AAmMA,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAYpE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
const HEADING_COLORS = ['cyan', 'green', 'yellow', 'magenta', 'blue', 'red'];
|
|
5
|
+
// Tiny, dependency-free token-class colorizer for common languages.
|
|
6
|
+
// Goal: useful visual separation, NOT correctness. Anything we don't
|
|
7
|
+
// recognise renders unstyled.
|
|
8
|
+
const KEYWORDS = {
|
|
9
|
+
js: /\b(const|let|var|function|return|if|else|for|while|class|new|await|async|import|export|from|as|of|in|typeof|instanceof|this|super|extends|throw|try|catch|finally|switch|case|break|continue|null|undefined|true|false)\b/g,
|
|
10
|
+
ts: /\b(const|let|var|function|return|if|else|for|while|class|new|await|async|import|export|from|as|of|in|typeof|instanceof|this|super|extends|throw|try|catch|finally|switch|case|break|continue|null|undefined|true|false|interface|type|enum|public|private|protected|readonly|implements|namespace)\b/g,
|
|
11
|
+
py: /\b(def|class|return|if|elif|else|for|while|in|not|and|or|None|True|False|import|from|as|try|except|finally|raise|with|lambda|yield|global|nonlocal|pass|break|continue|async|await)\b/g,
|
|
12
|
+
sh: /\b(if|then|fi|else|elif|for|while|do|done|case|esac|in|function|return|exit|break|continue|local|export|readonly|set|unset)\b/g,
|
|
13
|
+
bash: /\b(if|then|fi|else|elif|for|while|do|done|case|esac|in|function|return|exit|break|continue|local|export|readonly|set|unset)\b/g,
|
|
14
|
+
go: /\b(func|var|const|type|struct|interface|return|if|else|for|range|switch|case|default|break|continue|go|defer|chan|select|package|import|map|nil|true|false)\b/g,
|
|
15
|
+
};
|
|
16
|
+
// marked emits `language-foo` from fences; common spellings to canonical
|
|
17
|
+
// keys above. Without this, ```typescript ``` blocks didn't colour
|
|
18
|
+
// because the lookup was for `typescript` not `ts`.
|
|
19
|
+
const LANG_ALIASES = {
|
|
20
|
+
javascript: 'js', jsx: 'js', node: 'js',
|
|
21
|
+
typescript: 'ts', tsx: 'ts',
|
|
22
|
+
python: 'py', python3: 'py',
|
|
23
|
+
shell: 'sh', bash: 'sh', zsh: 'sh',
|
|
24
|
+
golang: 'go',
|
|
25
|
+
};
|
|
26
|
+
function highlightCode(code, lang) {
|
|
27
|
+
const lc = lang?.toLowerCase();
|
|
28
|
+
const canon = lc ? (LANG_ALIASES[lc] ?? lc) : undefined;
|
|
29
|
+
const kw = canon ? KEYWORDS[canon] : undefined;
|
|
30
|
+
if (!kw)
|
|
31
|
+
return [{ text: code }];
|
|
32
|
+
const spans = [];
|
|
33
|
+
let last = 0;
|
|
34
|
+
// Reset lastIndex since we use the same RegExp instance.
|
|
35
|
+
kw.lastIndex = 0;
|
|
36
|
+
let m;
|
|
37
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: standard regex.exec loop
|
|
38
|
+
while ((m = kw.exec(code)) !== null) {
|
|
39
|
+
if (m.index > last)
|
|
40
|
+
spans.push({ text: code.slice(last, m.index) });
|
|
41
|
+
spans.push({ text: m[0], kind: 'keyword' });
|
|
42
|
+
last = m.index + m[0].length;
|
|
43
|
+
}
|
|
44
|
+
if (last < code.length)
|
|
45
|
+
spans.push({ text: code.slice(last) });
|
|
46
|
+
return spans;
|
|
47
|
+
}
|
|
48
|
+
function spanColor(kind) {
|
|
49
|
+
if (kind === 'keyword')
|
|
50
|
+
return 'magenta';
|
|
51
|
+
if (kind === 'string')
|
|
52
|
+
return 'green';
|
|
53
|
+
if (kind === 'comment')
|
|
54
|
+
return 'gray';
|
|
55
|
+
if (kind === 'number')
|
|
56
|
+
return 'yellow';
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function renderInline(tokens, keyPrefix) {
|
|
60
|
+
if (!tokens)
|
|
61
|
+
return [];
|
|
62
|
+
const out = [];
|
|
63
|
+
tokens.forEach((tok, i) => {
|
|
64
|
+
const k = `${keyPrefix}-${i}`;
|
|
65
|
+
switch (tok.type) {
|
|
66
|
+
case 'text':
|
|
67
|
+
out.push(_jsx(Text, { children: tok.text ?? '' }, k));
|
|
68
|
+
break;
|
|
69
|
+
case 'strong':
|
|
70
|
+
out.push(_jsx(Text, { bold: true, children: renderInline(tok.tokens, k) }, k));
|
|
71
|
+
break;
|
|
72
|
+
case 'em':
|
|
73
|
+
out.push(_jsx(Text, { italic: true, children: renderInline(tok.tokens, k) }, k));
|
|
74
|
+
break;
|
|
75
|
+
case 'del':
|
|
76
|
+
out.push(_jsx(Text, { strikethrough: true, children: renderInline(tok.tokens, k) }, k));
|
|
77
|
+
break;
|
|
78
|
+
case 'codespan':
|
|
79
|
+
out.push(_jsxs(Text, { color: "cyan", children: ["`", tok.text ?? '', "`"] }, k));
|
|
80
|
+
break;
|
|
81
|
+
case 'link':
|
|
82
|
+
out.push(_jsxs(Text, { children: [_jsx(Text, { underline: true, children: renderInline(tok.tokens, k) }), _jsxs(Text, { dimColor: true, children: [" (", tok.href ?? '', ")"] })] }, k));
|
|
83
|
+
break;
|
|
84
|
+
case 'br':
|
|
85
|
+
out.push(_jsx(Text, { children: '\n' }, k));
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
out.push(_jsx(Text, { children: tok.text ?? '' }, k));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function renderBlock(tok, key) {
|
|
94
|
+
switch (tok.type) {
|
|
95
|
+
case 'heading': {
|
|
96
|
+
const depth = Math.min((tok.depth ?? 1) - 1, HEADING_COLORS.length - 1);
|
|
97
|
+
const prefix = '#'.repeat(tok.depth ?? 1) + ' ';
|
|
98
|
+
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: HEADING_COLORS[depth], children: prefix }), _jsx(Text, { bold: true, color: HEADING_COLORS[depth], children: renderInline(tok.tokens, key) })] }, key));
|
|
99
|
+
}
|
|
100
|
+
case 'paragraph':
|
|
101
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: renderInline(tok.tokens, key) }) }, key));
|
|
102
|
+
case 'code': {
|
|
103
|
+
const lang = tok.lang || '';
|
|
104
|
+
const spans = highlightCode(tok.text ?? '', lang);
|
|
105
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [lang && _jsx(Text, { dimColor: true, children: lang }), _jsx(Text, { children: spans.map((s, i) => (_jsx(Text, { color: spanColor(s.kind), children: s.text }, i))) })] }, key));
|
|
106
|
+
}
|
|
107
|
+
case 'blockquote':
|
|
108
|
+
return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: (tok.tokens ?? []).map((inner, i) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2502 " }), _jsx(Text, { dimColor: true, children: renderInline(inner.tokens, `${key}-${i}`) })] }, i))) }, key));
|
|
109
|
+
case 'list': {
|
|
110
|
+
const ordered = !!tok.ordered;
|
|
111
|
+
const items = (tok.items ?? []);
|
|
112
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: items.map((item, i) => {
|
|
113
|
+
const marker = ordered ? `${i + 1}. ` : '• ';
|
|
114
|
+
// list_item tokens have nested tokens; flatten to inline text.
|
|
115
|
+
const itemTokens = item.tokens ?? [];
|
|
116
|
+
return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: marker }), _jsx(Text, { children: renderInline(itemTokens, `${key}-${i}`) })] }, i));
|
|
117
|
+
}) }, key));
|
|
118
|
+
}
|
|
119
|
+
case 'hr':
|
|
120
|
+
return _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: '─'.repeat(40) }) }, key);
|
|
121
|
+
case 'space':
|
|
122
|
+
return null;
|
|
123
|
+
default:
|
|
124
|
+
// Tables / html / footnotes / etc. — fall back to raw text so
|
|
125
|
+
// nothing is silently dropped.
|
|
126
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: tok.text ?? '' }) }, key));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Render arbitrary markdown source as Ink components. */
|
|
130
|
+
export function Markdown({ source }) {
|
|
131
|
+
// Empty body: just nothing.
|
|
132
|
+
if (!source)
|
|
133
|
+
return _jsx(Text, {});
|
|
134
|
+
let tokens;
|
|
135
|
+
try {
|
|
136
|
+
tokens = marked.lexer(source);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Fallback: render as plain text if the lexer chokes (e.g. partial
|
|
140
|
+
// mid-stream content with unbalanced backticks).
|
|
141
|
+
return _jsx(Text, { children: source });
|
|
142
|
+
}
|
|
143
|
+
return _jsx(_Fragment, { children: tokens.map((t, i) => renderBlock(t, `b-${i}`)) });
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/cli-ui/tui/markdown.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const TUI_SESSIONS_DIR: string;
|
|
2
|
+
export interface StoredMessage {
|
|
3
|
+
role: 'user' | 'assistant' | 'system';
|
|
4
|
+
content: string;
|
|
5
|
+
elapsedMs?: number;
|
|
6
|
+
agentName?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StoredSession {
|
|
9
|
+
id: string;
|
|
10
|
+
agent: string | undefined;
|
|
11
|
+
cwd: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
messages: StoredMessage[];
|
|
15
|
+
}
|
|
16
|
+
/** Pretty timestamp prefix + 6 hex bytes — sortable by mtime AND id. */
|
|
17
|
+
export declare function generateSessionId(): string;
|
|
18
|
+
export declare function saveSession(s: StoredSession): void;
|
|
19
|
+
export declare function loadSession(id: string): StoredSession | null;
|
|
20
|
+
export interface SessionMeta {
|
|
21
|
+
id: string;
|
|
22
|
+
agent: string | undefined;
|
|
23
|
+
cwd: string;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
messages: number;
|
|
26
|
+
bytes: number;
|
|
27
|
+
}
|
|
28
|
+
/** Latest first. Cheap: only reads filenames + stat; doesn't open JSON.
|
|
29
|
+
* For richer info we open per-file (used by --list-sessions full path). */
|
|
30
|
+
export declare function listSessions(limit?: number, full?: boolean): SessionMeta[];
|
|
31
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/cli-ui/tui/sessions.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,gBAAgB,QAAkC,CAAA;AAE/D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,aAAa,EAAE,CAAA;CAC1B;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,IAAI,MAAM,CAM1C;AAQD,wBAAgB,WAAW,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAYlD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAS5D;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;4EAC4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,SAAK,EAAE,IAAI,UAAQ,GAAG,WAAW,EAAE,CAgCpE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// agim TUI session persistence — saves chat transcript so the user
|
|
2
|
+
// can pick up where they left off with `agim tui --resume <id>`.
|
|
3
|
+
//
|
|
4
|
+
// Storage: <AGIM_HOME>/tui-sessions/<id>.json
|
|
5
|
+
// File shape: { id, agent, cwd, createdAt, updatedAt, messages: [...] }
|
|
6
|
+
//
|
|
7
|
+
// We save on:
|
|
8
|
+
// - graceful exit (TuiApp's onExit handler)
|
|
9
|
+
// - end of each agent turn (so SIGKILL still preserves the last
|
|
10
|
+
// full exchange)
|
|
11
|
+
//
|
|
12
|
+
// Failures are warned to the logger but never propagate — losing a
|
|
13
|
+
// session is a quality-of-life regression, not data corruption.
|
|
14
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { randomBytes } from 'node:crypto';
|
|
17
|
+
import { AGIM_HOME } from '../../core/agim-paths.js';
|
|
18
|
+
import { logger as rootLogger } from '../../core/logger.js';
|
|
19
|
+
const log = rootLogger.child({ component: 'tui.sessions' });
|
|
20
|
+
export const TUI_SESSIONS_DIR = join(AGIM_HOME, 'tui-sessions');
|
|
21
|
+
/** Pretty timestamp prefix + 6 hex bytes — sortable by mtime AND id. */
|
|
22
|
+
export function generateSessionId() {
|
|
23
|
+
const d = new Date();
|
|
24
|
+
const pad = (n, w = 2) => String(n).padStart(w, '0');
|
|
25
|
+
const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
26
|
+
const rnd = randomBytes(3).toString('hex');
|
|
27
|
+
return `tui-${stamp}-${rnd}`;
|
|
28
|
+
}
|
|
29
|
+
function ensureDir() {
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(TUI_SESSIONS_DIR, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
log.warn({ event: 'tui.sessions.mkdir_failed', err: err instanceof Error ? err.message : String(err) });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function saveSession(s) {
|
|
38
|
+
// Empty + system-only sessions aren't worth a file — saves clutter
|
|
39
|
+
// when the user just launched and exited without chatting.
|
|
40
|
+
const meaningful = s.messages.filter((m) => m.role !== 'system').length > 0;
|
|
41
|
+
if (!meaningful)
|
|
42
|
+
return;
|
|
43
|
+
ensureDir();
|
|
44
|
+
const file = join(TUI_SESSIONS_DIR, `${s.id}.json`);
|
|
45
|
+
try {
|
|
46
|
+
writeFileSync(file, JSON.stringify(s, null, 2), { mode: 0o600 });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
log.warn({ event: 'tui.sessions.save_failed', file, err: err instanceof Error ? err.message : String(err) });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function loadSession(id) {
|
|
53
|
+
const file = join(TUI_SESSIONS_DIR, `${id}.json`);
|
|
54
|
+
if (!existsSync(file))
|
|
55
|
+
return null;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(readFileSync(file, 'utf-8'));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
log.warn({ event: 'tui.sessions.load_failed', file, err: err instanceof Error ? err.message : String(err) });
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Latest first. Cheap: only reads filenames + stat; doesn't open JSON.
|
|
65
|
+
* For richer info we open per-file (used by --list-sessions full path). */
|
|
66
|
+
export function listSessions(limit = 20, full = false) {
|
|
67
|
+
if (!existsSync(TUI_SESSIONS_DIR))
|
|
68
|
+
return [];
|
|
69
|
+
let names = [];
|
|
70
|
+
try {
|
|
71
|
+
names = readdirSync(TUI_SESSIONS_DIR).filter((n) => n.endsWith('.json'));
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
log.warn({ event: 'tui.sessions.list_failed', err: err instanceof Error ? err.message : String(err) });
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const n of names) {
|
|
79
|
+
const file = join(TUI_SESSIONS_DIR, n);
|
|
80
|
+
let st;
|
|
81
|
+
try {
|
|
82
|
+
st = statSync(file);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const id = n.replace(/\.json$/, '');
|
|
88
|
+
let agent;
|
|
89
|
+
let cwd = '';
|
|
90
|
+
let updatedAt = st.mtime.toISOString();
|
|
91
|
+
let msgCount = 0;
|
|
92
|
+
if (full) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(readFileSync(file, 'utf-8'));
|
|
95
|
+
agent = parsed.agent;
|
|
96
|
+
cwd = parsed.cwd;
|
|
97
|
+
updatedAt = parsed.updatedAt || updatedAt;
|
|
98
|
+
msgCount = parsed.messages.length;
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
}
|
|
102
|
+
out.push({ id, agent, cwd, updatedAt, messages: msgCount, bytes: st.size });
|
|
103
|
+
}
|
|
104
|
+
out.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
|
105
|
+
return out.slice(0, limit);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/cli-ui/tui/sessions.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1809,6 +1809,18 @@ program
|
|
|
1809
1809
|
await saveConfig(config);
|
|
1810
1810
|
console.log(`\n✅ Configuration saved to ${CONFIG_FILE}`);
|
|
1811
1811
|
});
|
|
1812
|
+
program
|
|
1813
|
+
.command('tui')
|
|
1814
|
+
.description('Open the terminal chat UI (Ink-based). Pick an agent + chat in-process.')
|
|
1815
|
+
.option('-a, --agent <name>', 'Pre-select an agent by name (e.g. claude-code, codex, native)')
|
|
1816
|
+
.option('-r, --resume [id]', 'Resume a saved session by id; "last" or empty picks the most recent')
|
|
1817
|
+
.option('--list-sessions', 'Print saved TUI sessions (latest first) and exit')
|
|
1818
|
+
.action(async (opts) => {
|
|
1819
|
+
const { runTui } = await import('./cli-ui/tui/index.js');
|
|
1820
|
+
// commander gives `--resume` (no arg) as `true`; normalise to empty string
|
|
1821
|
+
const resume = opts.resume === true ? '' : opts.resume;
|
|
1822
|
+
await runTui({ agent: opts.agent, resume, listSessions: opts.listSessions });
|
|
1823
|
+
});
|
|
1812
1824
|
program
|
|
1813
1825
|
.command('agents')
|
|
1814
1826
|
.description('List available agents')
|