@visorcraft/idlehands 0.9.1
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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/agent.js +2604 -0
- package/dist/agent.js.map +1 -0
- package/dist/anton/controller.js +341 -0
- package/dist/anton/controller.js.map +1 -0
- package/dist/anton/lock.js +110 -0
- package/dist/anton/lock.js.map +1 -0
- package/dist/anton/parser.js +303 -0
- package/dist/anton/parser.js.map +1 -0
- package/dist/anton/prompt.js +203 -0
- package/dist/anton/prompt.js.map +1 -0
- package/dist/anton/reporter.js +119 -0
- package/dist/anton/reporter.js.map +1 -0
- package/dist/anton/session.js +51 -0
- package/dist/anton/session.js.map +1 -0
- package/dist/anton/types.js +7 -0
- package/dist/anton/types.js.map +1 -0
- package/dist/anton/verifier.js +263 -0
- package/dist/anton/verifier.js.map +1 -0
- package/dist/bench/compare.js +239 -0
- package/dist/bench/compare.js.map +1 -0
- package/dist/bench/debug_hooks.js +17 -0
- package/dist/bench/debug_hooks.js.map +1 -0
- package/dist/bench/json_extract.js +22 -0
- package/dist/bench/json_extract.js.map +1 -0
- package/dist/bench/openclaw.js +86 -0
- package/dist/bench/openclaw.js.map +1 -0
- package/dist/bench/report.js +116 -0
- package/dist/bench/report.js.map +1 -0
- package/dist/bench/runner.js +312 -0
- package/dist/bench/runner.js.map +1 -0
- package/dist/bench/types.js +2 -0
- package/dist/bench/types.js.map +1 -0
- package/dist/bot/commands.js +444 -0
- package/dist/bot/commands.js.map +1 -0
- package/dist/bot/confirm-discord.js +133 -0
- package/dist/bot/confirm-discord.js.map +1 -0
- package/dist/bot/confirm-telegram.js +290 -0
- package/dist/bot/confirm-telegram.js.map +1 -0
- package/dist/bot/discord.js +826 -0
- package/dist/bot/discord.js.map +1 -0
- package/dist/bot/format.js +210 -0
- package/dist/bot/format.js.map +1 -0
- package/dist/bot/session-manager.js +270 -0
- package/dist/bot/session-manager.js.map +1 -0
- package/dist/bot/telegram.js +678 -0
- package/dist/bot/telegram.js.map +1 -0
- package/dist/cli/agent-turn.js +45 -0
- package/dist/cli/agent-turn.js.map +1 -0
- package/dist/cli/args.js +236 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bot.js +252 -0
- package/dist/cli/bot.js.map +1 -0
- package/dist/cli/build-repl-context.js +365 -0
- package/dist/cli/build-repl-context.js.map +1 -0
- package/dist/cli/command-registry.js +20 -0
- package/dist/cli/command-registry.js.map +1 -0
- package/dist/cli/commands/anton.js +271 -0
- package/dist/cli/commands/anton.js.map +1 -0
- package/dist/cli/commands/editing.js +328 -0
- package/dist/cli/commands/editing.js.map +1 -0
- package/dist/cli/commands/model.js +274 -0
- package/dist/cli/commands/model.js.map +1 -0
- package/dist/cli/commands/project.js +255 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/runtime.js +63 -0
- package/dist/cli/commands/runtime.js.map +1 -0
- package/dist/cli/commands/session.js +281 -0
- package/dist/cli/commands/session.js.map +1 -0
- package/dist/cli/commands/tools.js +126 -0
- package/dist/cli/commands/tools.js.map +1 -0
- package/dist/cli/commands/trifecta.js +221 -0
- package/dist/cli/commands/trifecta.js.map +1 -0
- package/dist/cli/commands/tui.js +17 -0
- package/dist/cli/commands/tui.js.map +1 -0
- package/dist/cli/init.js +222 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/input.js +360 -0
- package/dist/cli/input.js.map +1 -0
- package/dist/cli/oneshot.js +254 -0
- package/dist/cli/oneshot.js.map +1 -0
- package/dist/cli/repl-context.js +2 -0
- package/dist/cli/repl-context.js.map +1 -0
- package/dist/cli/runtime-cmds.js +811 -0
- package/dist/cli/runtime-cmds.js.map +1 -0
- package/dist/cli/service.js +145 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/cli/session-state.js +130 -0
- package/dist/cli/session-state.js.map +1 -0
- package/dist/cli/setup.js +815 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/shell.js +79 -0
- package/dist/cli/shell.js.map +1 -0
- package/dist/cli/status.js +392 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/watch.js +33 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/client.js +676 -0
- package/dist/client.js.map +1 -0
- package/dist/commands.js +194 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.js +507 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm/auto.js +13 -0
- package/dist/confirm/auto.js.map +1 -0
- package/dist/confirm/headless.js +41 -0
- package/dist/confirm/headless.js.map +1 -0
- package/dist/confirm/terminal.js +90 -0
- package/dist/confirm/terminal.js.map +1 -0
- package/dist/context.js +49 -0
- package/dist/context.js.map +1 -0
- package/dist/git.js +136 -0
- package/dist/git.js.map +1 -0
- package/dist/harnesses.js +171 -0
- package/dist/harnesses.js.map +1 -0
- package/dist/history.js +139 -0
- package/dist/history.js.map +1 -0
- package/dist/index.js +700 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.js +374 -0
- package/dist/indexer.js.map +1 -0
- package/dist/jsonrpc.js +76 -0
- package/dist/jsonrpc.js.map +1 -0
- package/dist/lens.js +525 -0
- package/dist/lens.js.map +1 -0
- package/dist/lsp.js +605 -0
- package/dist/lsp.js.map +1 -0
- package/dist/markdown.js +275 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp.js +554 -0
- package/dist/mcp.js.map +1 -0
- package/dist/recovery.js +178 -0
- package/dist/recovery.js.map +1 -0
- package/dist/replay.js +132 -0
- package/dist/replay.js.map +1 -0
- package/dist/replay_cli.js +24 -0
- package/dist/replay_cli.js.map +1 -0
- package/dist/runtime/executor.js +418 -0
- package/dist/runtime/executor.js.map +1 -0
- package/dist/runtime/planner.js +197 -0
- package/dist/runtime/planner.js.map +1 -0
- package/dist/runtime/store.js +289 -0
- package/dist/runtime/store.js.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/safety.js +446 -0
- package/dist/safety.js.map +1 -0
- package/dist/spinner.js +224 -0
- package/dist/spinner.js.map +1 -0
- package/dist/sys/context.js +124 -0
- package/dist/sys/context.js.map +1 -0
- package/dist/sys/snapshot.sh +97 -0
- package/dist/term.js +61 -0
- package/dist/term.js.map +1 -0
- package/dist/themes.js +135 -0
- package/dist/themes.js.map +1 -0
- package/dist/tools.js +1114 -0
- package/dist/tools.js.map +1 -0
- package/dist/tui/branch-picker.js +65 -0
- package/dist/tui/branch-picker.js.map +1 -0
- package/dist/tui/command-handler.js +108 -0
- package/dist/tui/command-handler.js.map +1 -0
- package/dist/tui/confirm.js +90 -0
- package/dist/tui/confirm.js.map +1 -0
- package/dist/tui/controller.js +463 -0
- package/dist/tui/controller.js.map +1 -0
- package/dist/tui/event-bridge.js +44 -0
- package/dist/tui/event-bridge.js.map +1 -0
- package/dist/tui/events.js +2 -0
- package/dist/tui/events.js.map +1 -0
- package/dist/tui/keymap.js +144 -0
- package/dist/tui/keymap.js.map +1 -0
- package/dist/tui/layout.js +11 -0
- package/dist/tui/layout.js.map +1 -0
- package/dist/tui/render.js +186 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/tui/screen.js +48 -0
- package/dist/tui/screen.js.map +1 -0
- package/dist/tui/state.js +167 -0
- package/dist/tui/state.js.map +1 -0
- package/dist/tui/theme.js +70 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/types.js +2 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upgrade.js +412 -0
- package/dist/upgrade.js.map +1 -0
- package/dist/utils.js +87 -0
- package/dist/utils.js.map +1 -0
- package/dist/vault.js +520 -0
- package/dist/vault.js.map +1 -0
- package/dist/vim.js +160 -0
- package/dist/vim.js.map +1 -0
- package/package.json +67 -0
- package/src/sys/snapshot.sh +97 -0
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI spinner and tool call visualization (Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* Shows an animated spinner while waiting for the first token,
|
|
5
|
+
* then transitions to streaming text. Tool calls get one-line
|
|
6
|
+
* summaries before and after execution.
|
|
7
|
+
*/
|
|
8
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
9
|
+
const INTERVAL = 80; // ms per frame
|
|
10
|
+
export class CliSpinner {
|
|
11
|
+
frame = 0;
|
|
12
|
+
timer = null;
|
|
13
|
+
startTime = Date.now();
|
|
14
|
+
currentTool = null;
|
|
15
|
+
firstDelta = false;
|
|
16
|
+
enabled;
|
|
17
|
+
verbose;
|
|
18
|
+
S;
|
|
19
|
+
constructor(opts) {
|
|
20
|
+
this.S = opts.styler;
|
|
21
|
+
this.enabled = opts.enabled !== false && process.stdout.isTTY === true;
|
|
22
|
+
this.verbose = opts.verbose ?? false;
|
|
23
|
+
}
|
|
24
|
+
/** Start the spinner. Call before ask(). */
|
|
25
|
+
start() {
|
|
26
|
+
if (!this.enabled)
|
|
27
|
+
return;
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
this.frame = 0;
|
|
30
|
+
this.firstDelta = false;
|
|
31
|
+
this.currentTool = null;
|
|
32
|
+
this.timer = setInterval(() => this.render(), INTERVAL);
|
|
33
|
+
}
|
|
34
|
+
/** Called on first token — stop spinner, let streaming begin. */
|
|
35
|
+
onFirstDelta() {
|
|
36
|
+
if (this.firstDelta)
|
|
37
|
+
return;
|
|
38
|
+
this.firstDelta = true;
|
|
39
|
+
this.clearLine();
|
|
40
|
+
this.stopTimer();
|
|
41
|
+
}
|
|
42
|
+
/** Called before a tool executes. */
|
|
43
|
+
onToolCall(event) {
|
|
44
|
+
if (!this.enabled) {
|
|
45
|
+
// Fallback for non-TTY
|
|
46
|
+
process.stderr.write(`◆ ${event.name} ${this.argSummary(event)}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.clearLine();
|
|
50
|
+
this.stopTimer();
|
|
51
|
+
const summary = this.argSummary(event);
|
|
52
|
+
const dim = this.S.dim;
|
|
53
|
+
process.stderr.write(dim(` ◆ ${event.name} ${summary}`));
|
|
54
|
+
process.stderr.write('\n');
|
|
55
|
+
// Restart spinner for tool execution
|
|
56
|
+
this.currentTool = event.name;
|
|
57
|
+
this.startTime = Date.now();
|
|
58
|
+
this.timer = setInterval(() => this.render(), INTERVAL);
|
|
59
|
+
}
|
|
60
|
+
/** Called after a tool completes. */
|
|
61
|
+
onToolResult(event) {
|
|
62
|
+
this.clearLine();
|
|
63
|
+
this.stopTimer();
|
|
64
|
+
this.currentTool = null;
|
|
65
|
+
if (!this.enabled) {
|
|
66
|
+
const icon = event.success ? '✓' : '✗';
|
|
67
|
+
process.stderr.write(` ${icon} ${event.name}: ${event.summary}\n`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const icon = event.success
|
|
71
|
+
? this.S.green('✓')
|
|
72
|
+
: this.S.red('✗');
|
|
73
|
+
const summary = event.summary.length > 120
|
|
74
|
+
? event.summary.slice(0, 117) + '...'
|
|
75
|
+
: event.summary;
|
|
76
|
+
process.stderr.write(` ${icon} ${this.S.dim(`${event.name}: ${summary}`)}\n`);
|
|
77
|
+
// Phase 7: rich display (only in verbose mode or for key outputs)
|
|
78
|
+
if (this.verbose) {
|
|
79
|
+
this.renderRichOutput(event);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// In normal mode, show compact exec output and diffs
|
|
83
|
+
if (event.diff) {
|
|
84
|
+
this.renderDiff(event.diff);
|
|
85
|
+
}
|
|
86
|
+
if (event.execOutput) {
|
|
87
|
+
this.renderExecOutput(event.execOutput, 5);
|
|
88
|
+
}
|
|
89
|
+
if (event.searchMatches?.length) {
|
|
90
|
+
this.renderSearchMatches(event.searchMatches, 5);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Render rich output in verbose mode. */
|
|
95
|
+
renderRichOutput(event) {
|
|
96
|
+
if (event.diff) {
|
|
97
|
+
this.renderDiff(event.diff);
|
|
98
|
+
}
|
|
99
|
+
if (event.execOutput) {
|
|
100
|
+
this.renderExecOutput(event.execOutput, 20);
|
|
101
|
+
}
|
|
102
|
+
if (event.searchMatches?.length) {
|
|
103
|
+
this.renderSearchMatches(event.searchMatches, 20);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Render an inline colored diff (unified format). */
|
|
107
|
+
renderDiff(diff) {
|
|
108
|
+
const lines = diff.split('\n');
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@')) {
|
|
111
|
+
process.stderr.write(` ${this.S.dim(line)}\n`);
|
|
112
|
+
}
|
|
113
|
+
else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
114
|
+
process.stderr.write(` ${this.S.green(line)}\n`);
|
|
115
|
+
}
|
|
116
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
117
|
+
process.stderr.write(` ${this.S.red(line)}\n`);
|
|
118
|
+
}
|
|
119
|
+
else if (line.startsWith('[+')) {
|
|
120
|
+
process.stderr.write(` ${this.S.dim(line)}\n`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
process.stderr.write(` ${this.S.dim(line)}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Render exec stdout (dimmed, limited lines). */
|
|
128
|
+
renderExecOutput(output, maxLines) {
|
|
129
|
+
const lines = output.split('\n');
|
|
130
|
+
const show = lines.slice(0, maxLines);
|
|
131
|
+
for (const line of show) {
|
|
132
|
+
process.stderr.write(` ${this.S.dim(line)}\n`);
|
|
133
|
+
}
|
|
134
|
+
if (lines.length > maxLines) {
|
|
135
|
+
process.stderr.write(` ${this.S.dim(`[+${lines.length - maxLines} more lines]`)}\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Render search matches with highlights. */
|
|
139
|
+
renderSearchMatches(matches, maxLines) {
|
|
140
|
+
const show = matches.slice(0, maxLines);
|
|
141
|
+
for (const match of show) {
|
|
142
|
+
// Format: "file:line:content" — highlight the file:line prefix
|
|
143
|
+
const idx = match.indexOf(':');
|
|
144
|
+
if (idx > 0) {
|
|
145
|
+
const nextIdx = match.indexOf(':', idx + 1);
|
|
146
|
+
if (nextIdx > 0) {
|
|
147
|
+
const prefix = match.slice(0, nextIdx + 1);
|
|
148
|
+
const content = match.slice(nextIdx + 1);
|
|
149
|
+
process.stderr.write(` ${this.S.cyan(prefix)}${this.S.dim(content)}\n`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
process.stderr.write(` ${this.S.dim(match)}\n`);
|
|
154
|
+
}
|
|
155
|
+
if (matches.length > maxLines) {
|
|
156
|
+
process.stderr.write(` ${this.S.dim(`[+${matches.length - maxLines} more matches]`)}\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Stop spinner completely. Call after ask() returns. */
|
|
160
|
+
stop() {
|
|
161
|
+
this.clearLine();
|
|
162
|
+
this.stopTimer();
|
|
163
|
+
}
|
|
164
|
+
render() {
|
|
165
|
+
if (this.firstDelta && !this.currentTool)
|
|
166
|
+
return;
|
|
167
|
+
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
168
|
+
const f = FRAMES[this.frame % FRAMES.length];
|
|
169
|
+
this.frame++;
|
|
170
|
+
let text;
|
|
171
|
+
if (this.currentTool) {
|
|
172
|
+
text = `${f} Running ${this.currentTool}... (${elapsed}s)`;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
text = `${f} Thinking... (${elapsed}s)`;
|
|
176
|
+
}
|
|
177
|
+
process.stderr.write(`\r${this.S.dim(text)}`);
|
|
178
|
+
}
|
|
179
|
+
clearLine() {
|
|
180
|
+
if (!this.enabled)
|
|
181
|
+
return;
|
|
182
|
+
process.stderr.write('\r\x1b[K');
|
|
183
|
+
}
|
|
184
|
+
stopTimer() {
|
|
185
|
+
if (this.timer) {
|
|
186
|
+
clearInterval(this.timer);
|
|
187
|
+
this.timer = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
argSummary(event) {
|
|
191
|
+
const a = event.args;
|
|
192
|
+
switch (event.name) {
|
|
193
|
+
case 'read_file': {
|
|
194
|
+
const p = a.path ?? a.file_path ?? '';
|
|
195
|
+
const range = a.offset && a.limit ? ` (lines ${a.offset}-${Number(a.offset) + Number(a.limit)})` : '';
|
|
196
|
+
const search = a.search ? ` search="${a.search}"` : '';
|
|
197
|
+
return `${p}${range}${search}`;
|
|
198
|
+
}
|
|
199
|
+
case 'write_file':
|
|
200
|
+
return `${a.path ?? a.file_path ?? ''}`;
|
|
201
|
+
case 'edit_file': {
|
|
202
|
+
const p = a.path ?? a.file_path ?? '';
|
|
203
|
+
const old = typeof a.old_string === 'string' ? a.old_string : '';
|
|
204
|
+
const lines = old.split('\n').length;
|
|
205
|
+
return `${p} (replacing ${lines} line${lines !== 1 ? 's' : ''})`;
|
|
206
|
+
}
|
|
207
|
+
case 'insert_file':
|
|
208
|
+
return `${a.path ?? a.file_path ?? ''} at line ${a.line ?? '?'}`;
|
|
209
|
+
case 'exec': {
|
|
210
|
+
const cmd = typeof a.command === 'string' ? a.command : '';
|
|
211
|
+
return cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd;
|
|
212
|
+
}
|
|
213
|
+
case 'list_dir':
|
|
214
|
+
return `${a.path ?? '.'}`;
|
|
215
|
+
case 'search_files':
|
|
216
|
+
return `"${a.pattern ?? ''}" in ${a.path ?? '.'}`;
|
|
217
|
+
case 'undo_path':
|
|
218
|
+
return `${a.path ?? '(last edit)'}`;
|
|
219
|
+
default:
|
|
220
|
+
return Object.keys(a).slice(0, 3).map(k => `${k}=${JSON.stringify(a[k]).slice(0, 30)}`).join(' ');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.js","sourceRoot":"","sources":["../src/spinner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sys_context tool — Scoped system snapshot (Phase 9a).
|
|
3
|
+
*
|
|
4
|
+
* Collects system info on demand via snapshot.sh.
|
|
5
|
+
* Cached for 60s to avoid re-running `df`, `systemctl`, etc. every tool cycle.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
// In dist/, __dirname is dist/sys/ — script is at src/sys/snapshot.sh relative to repo root.
|
|
13
|
+
// In source, __dirname is src/sys/ — script is co-located.
|
|
14
|
+
const SNAPSHOT_SCRIPT = path.join(__dirname, 'snapshot.sh');
|
|
15
|
+
const VALID_SCOPES = ['all', 'services', 'network', 'disk', 'packages'];
|
|
16
|
+
// Cache: scope → { text, timestamp }
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
const CACHE_TTL_MS = 60_000; // 60 seconds
|
|
19
|
+
/**
|
|
20
|
+
* Detect the package manager on this system.
|
|
21
|
+
* Returns: 'apt' | 'dnf' | 'pacman' | 'unknown'
|
|
22
|
+
*/
|
|
23
|
+
export function detectPackageManager() {
|
|
24
|
+
const has = (cmd) => spawnSync('bash', ['-lc', `command -v ${cmd}`], { stdio: 'ignore' }).status === 0;
|
|
25
|
+
if (has('apt'))
|
|
26
|
+
return 'apt';
|
|
27
|
+
if (has('dnf'))
|
|
28
|
+
return 'dnf';
|
|
29
|
+
if (has('pacman'))
|
|
30
|
+
return 'pacman';
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run snapshot.sh with the given scope and return the output.
|
|
35
|
+
* Caches results for 60s per scope.
|
|
36
|
+
*/
|
|
37
|
+
export async function collectSnapshot(scope = 'all') {
|
|
38
|
+
// Check cache
|
|
39
|
+
const cached = cache.get(scope);
|
|
40
|
+
if (cached && (Date.now() - cached.ts) < CACHE_TTL_MS) {
|
|
41
|
+
return cached.text;
|
|
42
|
+
}
|
|
43
|
+
const text = await runSnapshotScript(scope);
|
|
44
|
+
cache.set(scope, { text, ts: Date.now() });
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
/** Clear snapshot cache (for testing). */
|
|
48
|
+
export function clearSnapshotCache() {
|
|
49
|
+
cache.clear();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the snapshot script path. In the built dist, the script lives in
|
|
53
|
+
* src/sys/snapshot.sh relative to the repo root. We try the source path first,
|
|
54
|
+
* then fall back to a co-located path.
|
|
55
|
+
*/
|
|
56
|
+
function resolveSnapshotScript() {
|
|
57
|
+
// Try likely locations in source checkout and installed package.
|
|
58
|
+
const candidates = [
|
|
59
|
+
SNAPSHOT_SCRIPT, // co-located (src/sys/ or dist/sys/)
|
|
60
|
+
path.join(process.cwd(), 'src', 'sys', 'snapshot.sh'), // repo root → source
|
|
61
|
+
path.join(process.cwd(), 'dist', 'sys', 'snapshot.sh'), // repo root → dist
|
|
62
|
+
];
|
|
63
|
+
for (const c of candidates) {
|
|
64
|
+
if (existsSync(c))
|
|
65
|
+
return c;
|
|
66
|
+
}
|
|
67
|
+
// Fall back to the canonical source path for a clear error.
|
|
68
|
+
return SNAPSHOT_SCRIPT;
|
|
69
|
+
}
|
|
70
|
+
async function runSnapshotScript(scope) {
|
|
71
|
+
const scriptPath = resolveSnapshotScript();
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const child = spawn('bash', [scriptPath, scope], {
|
|
74
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
75
|
+
timeout: 5000, // 5s hard limit
|
|
76
|
+
});
|
|
77
|
+
const chunks = [];
|
|
78
|
+
const errChunks = [];
|
|
79
|
+
child.stdout.on('data', (d) => chunks.push(d));
|
|
80
|
+
child.stderr.on('data', (d) => errChunks.push(d));
|
|
81
|
+
child.on('close', (code) => {
|
|
82
|
+
const out = Buffer.concat(chunks).toString('utf8').trim();
|
|
83
|
+
if (code !== 0) {
|
|
84
|
+
const err = Buffer.concat(errChunks).toString('utf8').trim();
|
|
85
|
+
reject(new Error(`snapshot.sh exited ${code}: ${err || out}`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
resolve(out);
|
|
89
|
+
});
|
|
90
|
+
child.on('error', (e) => reject(e));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tool handler for sys_context.
|
|
95
|
+
* Matches the ToolContext pattern used by other tools.
|
|
96
|
+
*/
|
|
97
|
+
export async function sys_context(_ctx, args) {
|
|
98
|
+
const scope = typeof args?.scope === 'string' ? args.scope.toLowerCase() : 'all';
|
|
99
|
+
if (!VALID_SCOPES.includes(scope)) {
|
|
100
|
+
throw new Error(`sys_context: invalid scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
return collectSnapshot(scope);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Tool schema for sys_context (registered only when sys mode is active).
|
|
106
|
+
*/
|
|
107
|
+
export const SYS_CONTEXT_SCHEMA = {
|
|
108
|
+
type: 'function',
|
|
109
|
+
function: {
|
|
110
|
+
name: 'sys_context',
|
|
111
|
+
description: 'Get system information snapshot. Use this when you need to know about the OS, services, network, disk, or packages.',
|
|
112
|
+
parameters: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
scope: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
enum: ['all', 'services', 'network', 'disk', 'packages'],
|
|
118
|
+
description: 'What info to collect. "all" returns everything (~500 tokens). Individual scopes return ~100 tokens each.',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/sys/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# System context snapshot for Idle Hands (Phase 9)
|
|
3
|
+
# Usage: snapshot.sh [scope]
|
|
4
|
+
# Scope: all | services | network | disk | packages
|
|
5
|
+
# Target: <500 tokens for full scope, <100 tokens per individual scope, <2s to collect
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
SCOPE="${1:-all}"
|
|
9
|
+
|
|
10
|
+
collect_os() {
|
|
11
|
+
echo "OS: $(cat /etc/os-release 2>/dev/null | grep '^PRETTY_NAME' | cut -d'"' -f2 || uname -s) ($(uname -m))"
|
|
12
|
+
echo "Kernel: $(uname -r)"
|
|
13
|
+
echo "Hostname: $(hostname)"
|
|
14
|
+
echo "Uptime: $(uptime -p 2>/dev/null | sed 's/^up //' || echo 'unknown')"
|
|
15
|
+
# CPU
|
|
16
|
+
local cpus
|
|
17
|
+
cpus=$(nproc 2>/dev/null || echo '?')
|
|
18
|
+
local model
|
|
19
|
+
model=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2 | xargs || echo 'unknown')
|
|
20
|
+
echo "CPU: ${model} (${cpus} cores)"
|
|
21
|
+
# RAM
|
|
22
|
+
if command -v free &>/dev/null; then
|
|
23
|
+
local total avail
|
|
24
|
+
total=$(free -h | awk '/^Mem:/{print $2}')
|
|
25
|
+
avail=$(free -h | awk '/^Mem:/{print $7}')
|
|
26
|
+
echo "RAM: ${total} (${avail} available)"
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
collect_disk() {
|
|
31
|
+
if command -v df &>/dev/null; then
|
|
32
|
+
echo "Disk:"
|
|
33
|
+
df -h --output=target,size,used,avail,pcent / /home 2>/dev/null | tail -n +2 | while read -r mount size used avail pct; do
|
|
34
|
+
echo " ${mount} ${pct} of ${size} (${avail} free)"
|
|
35
|
+
done
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
collect_network() {
|
|
40
|
+
if command -v ip &>/dev/null; then
|
|
41
|
+
echo "Network:"
|
|
42
|
+
ip -br addr 2>/dev/null | grep -v '^lo ' | while read -r iface state addrs; do
|
|
43
|
+
echo " ${iface} ${state} ${addrs}"
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
collect_services() {
|
|
49
|
+
if command -v systemctl &>/dev/null; then
|
|
50
|
+
echo "Services (active): $(systemctl list-units --state=active --type=service --no-legend --no-pager 2>/dev/null | awk '{print $1}' | sed 's/\.service$//' | head -20 | tr '\n' ', ' | sed 's/,$//')"
|
|
51
|
+
local failed
|
|
52
|
+
failed=$(systemctl list-units --state=failed --type=service --no-legend --no-pager 2>/dev/null | awk '{print $1}' | sed 's/\.service$//' | tr '\n' ', ' | sed 's/,$//')
|
|
53
|
+
echo "Services (failed): ${failed:-none}"
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
collect_packages() {
|
|
58
|
+
if command -v apt &>/dev/null; then
|
|
59
|
+
local installed upgradable
|
|
60
|
+
installed=$(dpkg -l 2>/dev/null | grep -c '^ii' || echo '?')
|
|
61
|
+
upgradable=$(apt list --upgradable 2>/dev/null | grep -c 'upgradable' || echo '0')
|
|
62
|
+
echo "Packages (apt): ${installed} installed, ${upgradable} upgradable"
|
|
63
|
+
elif command -v dnf &>/dev/null; then
|
|
64
|
+
local installed
|
|
65
|
+
installed=$(dnf list installed 2>/dev/null | tail -n +2 | wc -l || echo '?')
|
|
66
|
+
echo "Packages (dnf): ${installed} installed"
|
|
67
|
+
elif command -v pacman &>/dev/null; then
|
|
68
|
+
local installed
|
|
69
|
+
installed=$(pacman -Q 2>/dev/null | wc -l || echo '?')
|
|
70
|
+
echo "Packages (pacman): ${installed} installed"
|
|
71
|
+
fi
|
|
72
|
+
# Last boot
|
|
73
|
+
if command -v who &>/dev/null; then
|
|
74
|
+
echo "Last boot: $(who -b 2>/dev/null | awk '{print $3, $4}' || echo 'unknown')"
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
echo "[System context]"
|
|
79
|
+
case "$SCOPE" in
|
|
80
|
+
all)
|
|
81
|
+
collect_os
|
|
82
|
+
collect_disk
|
|
83
|
+
collect_network
|
|
84
|
+
collect_services
|
|
85
|
+
collect_packages
|
|
86
|
+
;;
|
|
87
|
+
services) collect_services ;;
|
|
88
|
+
network) collect_network ;;
|
|
89
|
+
disk) collect_disk ;;
|
|
90
|
+
packages) collect_packages ;;
|
|
91
|
+
*)
|
|
92
|
+
echo "Unknown scope: $SCOPE"
|
|
93
|
+
echo "Valid scopes: all, services, network, disk, packages"
|
|
94
|
+
exit 1
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
echo "[End system context]"
|
package/dist/term.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export function resolveColorMode(mode) {
|
|
3
|
+
const env = process.env;
|
|
4
|
+
// Standard opt-out
|
|
5
|
+
if ('NO_COLOR' in env)
|
|
6
|
+
return { enabled: false };
|
|
7
|
+
// Explicit force/disable
|
|
8
|
+
if (env.FORCE_COLOR === '0')
|
|
9
|
+
return { enabled: false };
|
|
10
|
+
if (env.FORCE_COLOR && env.FORCE_COLOR !== '0')
|
|
11
|
+
return { enabled: true };
|
|
12
|
+
if (mode === 'always')
|
|
13
|
+
return { enabled: true };
|
|
14
|
+
if (mode === 'never')
|
|
15
|
+
return { enabled: false };
|
|
16
|
+
// auto
|
|
17
|
+
return { enabled: !!process.stdout.isTTY };
|
|
18
|
+
}
|
|
19
|
+
export function makeStyler(enabled, theme) {
|
|
20
|
+
const wrap = (fn) => (s) => (enabled ? fn(s) : s);
|
|
21
|
+
const t = theme;
|
|
22
|
+
return {
|
|
23
|
+
enabled,
|
|
24
|
+
dim: wrap(t?.dim ?? pc.dim),
|
|
25
|
+
bold: wrap(t?.bold ?? pc.bold),
|
|
26
|
+
red: wrap(t?.red ?? pc.red),
|
|
27
|
+
yellow: wrap(t?.yellow ?? pc.yellow),
|
|
28
|
+
green: wrap(t?.green ?? pc.green),
|
|
29
|
+
cyan: wrap(t?.cyan ?? pc.cyan),
|
|
30
|
+
magenta: wrap(t?.magenta ?? pc.magenta),
|
|
31
|
+
blue: wrap(t?.blue ?? pc.blue)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function colorizeUnifiedDiff(diff, s) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const line of diff.split(/\r?\n/)) {
|
|
37
|
+
if (line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@')) {
|
|
38
|
+
out.push(s.dim(line));
|
|
39
|
+
}
|
|
40
|
+
else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
41
|
+
out.push(s.green(line));
|
|
42
|
+
}
|
|
43
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
44
|
+
out.push(s.red(line));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
out.push(line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out.join('\n');
|
|
51
|
+
}
|
|
52
|
+
export function banner(title, s) {
|
|
53
|
+
return s.blue(s.bold(title));
|
|
54
|
+
}
|
|
55
|
+
export function warn(msg, s) {
|
|
56
|
+
return s.yellow('WARN') + s.dim(': ') + msg;
|
|
57
|
+
}
|
|
58
|
+
export function err(msg, s) {
|
|
59
|
+
return s.red('ERROR') + s.dim(': ') + msg;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=term.js.map
|
package/dist/term.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term.js","sourceRoot":"","sources":["../src/term.ts"],"names":[],"mappings":"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"}
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in and custom theme system (Phase 14a).
|
|
3
|
+
*
|
|
4
|
+
* Themes are ANSI color palette mappings applied to the Styler.
|
|
5
|
+
* Each theme remaps the Styler's color slots (red, yellow, green,
|
|
6
|
+
* cyan, magenta, blue, dim, bold) to different picocolors functions.
|
|
7
|
+
*
|
|
8
|
+
* Built-in: default, dark, light, minimal, hacker
|
|
9
|
+
* Custom: ~/.config/idlehands/themes/<name>.json
|
|
10
|
+
*/
|
|
11
|
+
import pc from 'picocolors';
|
|
12
|
+
import fs from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { configDir } from './utils.js';
|
|
15
|
+
// All supported picocolors primitives.
|
|
16
|
+
const PC = {
|
|
17
|
+
dim: pc.dim,
|
|
18
|
+
bold: pc.bold,
|
|
19
|
+
italic: pc.italic,
|
|
20
|
+
underline: pc.underline,
|
|
21
|
+
inverse: pc.inverse,
|
|
22
|
+
red: pc.red,
|
|
23
|
+
yellow: pc.yellow,
|
|
24
|
+
green: pc.green,
|
|
25
|
+
cyan: pc.cyan,
|
|
26
|
+
magenta: pc.magenta,
|
|
27
|
+
blue: pc.blue,
|
|
28
|
+
white: pc.white,
|
|
29
|
+
gray: pc.gray,
|
|
30
|
+
black: pc.black,
|
|
31
|
+
};
|
|
32
|
+
/** Resolve a palette string like "bold+cyan" to a composed function. */
|
|
33
|
+
function resolveColor(spec) {
|
|
34
|
+
const parts = spec.split('+').map(p => p.trim().toLowerCase());
|
|
35
|
+
const fns = parts.map(p => PC[p]).filter(Boolean);
|
|
36
|
+
if (fns.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (fns.length === 1)
|
|
39
|
+
return fns[0];
|
|
40
|
+
return (s) => fns.reduce((acc, fn) => fn(acc), s);
|
|
41
|
+
}
|
|
42
|
+
/** The default (identity) palette — no remapping. */
|
|
43
|
+
const DEFAULT_PALETTE = {
|
|
44
|
+
dim: 'dim', bold: 'bold', red: 'red', yellow: 'yellow',
|
|
45
|
+
green: 'green', cyan: 'cyan', magenta: 'magenta', blue: 'blue',
|
|
46
|
+
};
|
|
47
|
+
const BUILTIN_PALETTES = {
|
|
48
|
+
default: {},
|
|
49
|
+
dark: {
|
|
50
|
+
cyan: 'bold+cyan',
|
|
51
|
+
magenta: 'bold+magenta',
|
|
52
|
+
blue: 'bold+blue',
|
|
53
|
+
},
|
|
54
|
+
light: {
|
|
55
|
+
dim: 'gray',
|
|
56
|
+
cyan: 'blue',
|
|
57
|
+
blue: 'cyan',
|
|
58
|
+
},
|
|
59
|
+
minimal: {
|
|
60
|
+
yellow: 'dim',
|
|
61
|
+
green: 'dim',
|
|
62
|
+
cyan: 'dim',
|
|
63
|
+
magenta: 'dim',
|
|
64
|
+
blue: 'dim',
|
|
65
|
+
},
|
|
66
|
+
hacker: {
|
|
67
|
+
bold: 'bold+green',
|
|
68
|
+
yellow: 'green',
|
|
69
|
+
cyan: 'green',
|
|
70
|
+
magenta: 'green',
|
|
71
|
+
blue: 'green',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
export const BUILTIN_THEME_NAMES = Object.keys(BUILTIN_PALETTES);
|
|
75
|
+
/** Resolve a raw palette to concrete functions, falling back to defaults. */
|
|
76
|
+
export function resolvePalette(palette) {
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const slot of Object.keys(DEFAULT_PALETTE)) {
|
|
79
|
+
const spec = palette[slot] ?? DEFAULT_PALETTE[slot];
|
|
80
|
+
const fn = resolveColor(spec);
|
|
81
|
+
out[slot] = fn ?? PC[DEFAULT_PALETTE[slot]];
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/** Get a built-in theme's resolved functions. */
|
|
86
|
+
export function builtinTheme(name) {
|
|
87
|
+
const p = BUILTIN_PALETTES[name];
|
|
88
|
+
if (!p)
|
|
89
|
+
return undefined;
|
|
90
|
+
return resolvePalette(p);
|
|
91
|
+
}
|
|
92
|
+
/** Custom themes directory. */
|
|
93
|
+
export function customThemesDir() {
|
|
94
|
+
return path.join(configDir(), 'themes');
|
|
95
|
+
}
|
|
96
|
+
/** Load a custom theme from ~/.config/idlehands/themes/<name>.json */
|
|
97
|
+
export async function loadCustomTheme(name) {
|
|
98
|
+
const filePath = path.join(customThemesDir(), `${name}.json`);
|
|
99
|
+
try {
|
|
100
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
if (!parsed || typeof parsed !== 'object')
|
|
103
|
+
return undefined;
|
|
104
|
+
return resolvePalette(parsed);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Resolve a theme by name: built-in first, then custom file. */
|
|
111
|
+
export async function resolveTheme(name) {
|
|
112
|
+
const builtin = builtinTheme(name);
|
|
113
|
+
if (builtin)
|
|
114
|
+
return builtin;
|
|
115
|
+
return loadCustomTheme(name);
|
|
116
|
+
}
|
|
117
|
+
/** List available theme names (built-in + custom). */
|
|
118
|
+
export async function listThemes() {
|
|
119
|
+
const builtin = [...BUILTIN_THEME_NAMES];
|
|
120
|
+
const custom = [];
|
|
121
|
+
try {
|
|
122
|
+
const dir = customThemesDir();
|
|
123
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
124
|
+
for (const e of entries) {
|
|
125
|
+
if (e.isFile() && e.name.endsWith('.json')) {
|
|
126
|
+
custom.push(e.name.replace(/\.json$/, ''));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// no custom themes dir
|
|
132
|
+
}
|
|
133
|
+
return { builtin, custom };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=themes.js.map
|