@agentprojectcontext/apx 1.15.5 → 1.16.0
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/package.json +40 -5
- package/src/cli/commands/log.js +113 -0
- package/src/cli/commands/overlay.js +253 -0
- package/src/cli/commands/sys.js +88 -16
- package/src/cli/index.js +23 -1
- package/src/cli/terminal-chat/renderer.js +71 -56
- package/src/cli-ts/commands/agent.ts +173 -0
- package/src/cli-ts/commands/chat.ts +119 -0
- package/src/cli-ts/commands/daemon.ts +112 -0
- package/src/cli-ts/commands/exec.ts +109 -0
- package/src/cli-ts/commands/mcp.ts +235 -0
- package/src/cli-ts/commands/session.ts +224 -0
- package/src/cli-ts/commands/status.ts +61 -0
- package/src/cli-ts/http.ts +36 -0
- package/src/cli-ts/index.ts +73 -0
- package/src/cli-ts/ui.ts +107 -0
- package/src/core/logging.js +81 -0
- package/src/daemon/api.js +58 -0
- package/src/daemon/engines/anthropic.js +60 -1
- package/src/daemon/engines/index.js +2 -1
- package/src/daemon/engines/ollama.js +70 -3
- package/src/daemon/index.js +58 -0
- package/src/daemon/overlay-ws.js +40 -0
- package/src/daemon/plugins/index.js +2 -1
- package/src/daemon/plugins/overlay.js +177 -0
- package/src/daemon/plugins/telegram.js +15 -3
- package/src/daemon/super-agent.js +102 -19
- package/src/daemon/transcription.js +262 -59
- package/src/daemon/wakeup.js +14 -19
- package/src/daemon/whisper-server.py +57 -6
- package/src/overlay/index.html +44 -0
- package/src/overlay/main.js +480 -0
- package/src/overlay/package.json +3 -0
- package/src/overlay/preload.js +34 -0
- package/src/overlay/renderer.js +371 -0
- package/src/overlay/style.css +250 -0
- package/src/tui/_shims/cli-error.ts +6 -0
- package/src/tui/_shims/cli-logo.ts +18 -0
- package/src/tui/_shims/cli-ui.ts +1 -0
- package/src/tui/_shims/config-console-state.ts +7 -0
- package/src/tui/_shims/core-any.ts +30 -0
- package/src/tui/_shims/core-binary.ts +13 -0
- package/src/tui/_shims/core-flag.ts +3 -0
- package/src/tui/_shims/core-log.ts +14 -0
- package/src/tui/_shims/lsp-language.ts +1 -0
- package/src/tui/_shims/opencode-any.ts +135 -0
- package/src/tui/_shims/opencode-sdk-v2.ts +48 -0
- package/src/tui/_shims/plugin-tui.ts +13 -0
- package/src/tui/_shims/provider-provider.ts +10 -0
- package/src/tui/_shims/session-retry.ts +1 -0
- package/src/tui/_shims/session-schema.ts +15 -0
- package/src/tui/_shims/session-session.ts +3 -0
- package/src/tui/_shims/snapshot.ts +4 -0
- package/src/tui/_shims/tool-any.ts +18 -0
- package/src/tui/_shims/util-error.ts +7 -0
- package/src/tui/_shims/util-filesystem.ts +79 -0
- package/src/tui/_shims/util-format.ts +7 -0
- package/src/tui/_shims/util-iife.ts +3 -0
- package/src/tui/_shims/util-locale.ts +10 -0
- package/src/tui/_shims/util-process.ts +38 -0
- package/src/tui/app.tsx +783 -0
- package/src/tui/asset/charge.wav +0 -0
- package/src/tui/asset/pulse-a.wav +0 -0
- package/src/tui/asset/pulse-b.wav +0 -0
- package/src/tui/asset/pulse-c.wav +0 -0
- package/src/tui/attach.ts +100 -0
- package/src/tui/component/bg-pulse-render.ts +436 -0
- package/src/tui/component/bg-pulse.tsx +99 -0
- package/src/tui/component/border.tsx +21 -0
- package/src/tui/component/dialog-agent.tsx +31 -0
- package/src/tui/component/dialog-console-org.tsx +103 -0
- package/src/tui/component/dialog-mcp.tsx +85 -0
- package/src/tui/component/dialog-model.tsx +175 -0
- package/src/tui/component/dialog-provider.tsx +456 -0
- package/src/tui/component/dialog-retry-action.tsx +160 -0
- package/src/tui/component/dialog-session-delete-failed.tsx +99 -0
- package/src/tui/component/dialog-session-list.tsx +323 -0
- package/src/tui/component/dialog-session-rename.tsx +31 -0
- package/src/tui/component/dialog-skill.tsx +36 -0
- package/src/tui/component/dialog-stash.tsx +87 -0
- package/src/tui/component/dialog-status.tsx +168 -0
- package/src/tui/component/dialog-tag.tsx +44 -0
- package/src/tui/component/dialog-theme-list.tsx +50 -0
- package/src/tui/component/dialog-variant.tsx +39 -0
- package/src/tui/component/dialog-workspace-create.tsx +302 -0
- package/src/tui/component/dialog-workspace-file-changes.tsx +138 -0
- package/src/tui/component/dialog-workspace-unavailable.tsx +69 -0
- package/src/tui/component/error-component.tsx +92 -0
- package/src/tui/component/logo.tsx +896 -0
- package/src/tui/component/plugin-route-missing.tsx +14 -0
- package/src/tui/component/prompt/autocomplete.tsx +869 -0
- package/src/tui/component/prompt/cwd.ts +0 -0
- package/src/tui/component/prompt/frecency.tsx +90 -0
- package/src/tui/component/prompt/history.tsx +108 -0
- package/src/tui/component/prompt/index.tsx +1809 -0
- package/src/tui/component/prompt/part.ts +16 -0
- package/src/tui/component/prompt/stash.tsx +101 -0
- package/src/tui/component/prompt/traits.ts +35 -0
- package/src/tui/component/spinner.tsx +24 -0
- package/src/tui/component/startup-loading.tsx +63 -0
- package/src/tui/component/todo-item.tsx +32 -0
- package/src/tui/component/use-connected.tsx +9 -0
- package/src/tui/component/workspace-label.tsx +19 -0
- package/src/tui/config/cwd.ts +5 -0
- package/src/tui/config/keybind.ts +432 -0
- package/src/tui/config/tui-migrate.ts +154 -0
- package/src/tui/config/tui-schema.ts +34 -0
- package/src/tui/config/tui.ts +46 -0
- package/src/tui/context/aggregate-failures.ts +34 -0
- package/src/tui/context/args.tsx +15 -0
- package/src/tui/context/command-palette.tsx +163 -0
- package/src/tui/context/directory.ts +15 -0
- package/src/tui/context/editor-zed.ts +283 -0
- package/src/tui/context/editor.ts +468 -0
- package/src/tui/context/event-apx.ts +22 -0
- package/src/tui/context/event.ts +6 -0
- package/src/tui/context/exit.tsx +60 -0
- package/src/tui/context/helper.tsx +25 -0
- package/src/tui/context/kv.tsx +81 -0
- package/src/tui/context/local.tsx +608 -0
- package/src/tui/context/path-format.tsx +39 -0
- package/src/tui/context/project-apx.tsx +48 -0
- package/src/tui/context/project.tsx +7 -0
- package/src/tui/context/prompt.tsx +18 -0
- package/src/tui/context/route.tsx +52 -0
- package/src/tui/context/sdk-apx.tsx +185 -0
- package/src/tui/context/sdk.tsx +6 -0
- package/src/tui/context/sync-apx.tsx +178 -0
- package/src/tui/context/sync-v2.tsx +16 -0
- package/src/tui/context/sync.tsx +118 -0
- package/src/tui/context/theme/aura.json +69 -0
- package/src/tui/context/theme/ayu.json +80 -0
- package/src/tui/context/theme/carbonfox.json +248 -0
- package/src/tui/context/theme/catppuccin-frappe.json +230 -0
- package/src/tui/context/theme/catppuccin-macchiato.json +230 -0
- package/src/tui/context/theme/catppuccin.json +112 -0
- package/src/tui/context/theme/cobalt2.json +225 -0
- package/src/tui/context/theme/cursor.json +249 -0
- package/src/tui/context/theme/dracula.json +219 -0
- package/src/tui/context/theme/everforest.json +241 -0
- package/src/tui/context/theme/flexoki.json +237 -0
- package/src/tui/context/theme/github.json +233 -0
- package/src/tui/context/theme/gruvbox.json +242 -0
- package/src/tui/context/theme/kanagawa.json +77 -0
- package/src/tui/context/theme/lucent-orng.json +234 -0
- package/src/tui/context/theme/material.json +235 -0
- package/src/tui/context/theme/matrix.json +77 -0
- package/src/tui/context/theme/mercury.json +252 -0
- package/src/tui/context/theme/monokai.json +221 -0
- package/src/tui/context/theme/nightowl.json +221 -0
- package/src/tui/context/theme/nord.json +223 -0
- package/src/tui/context/theme/one-dark.json +84 -0
- package/src/tui/context/theme/opencode.json +245 -0
- package/src/tui/context/theme/orng.json +249 -0
- package/src/tui/context/theme/osaka-jade.json +93 -0
- package/src/tui/context/theme/palenight.json +222 -0
- package/src/tui/context/theme/rosepine.json +234 -0
- package/src/tui/context/theme/solarized.json +223 -0
- package/src/tui/context/theme/synthwave84.json +226 -0
- package/src/tui/context/theme/tokyonight.json +243 -0
- package/src/tui/context/theme/vercel.json +245 -0
- package/src/tui/context/theme/vesper.json +218 -0
- package/src/tui/context/theme/zenburn.json +223 -0
- package/src/tui/context/theme.tsx +1247 -0
- package/src/tui/context/tui-config.tsx +9 -0
- package/src/tui/event.ts +16 -0
- package/src/tui/feature-plugins/home/footer.tsx +94 -0
- package/src/tui/feature-plugins/home/tips-view.tsx +166 -0
- package/src/tui/feature-plugins/home/tips.tsx +59 -0
- package/src/tui/feature-plugins/sidebar/context.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/files.tsx +63 -0
- package/src/tui/feature-plugins/sidebar/footer.tsx +94 -0
- package/src/tui/feature-plugins/sidebar/lsp.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/mcp.tsx +97 -0
- package/src/tui/feature-plugins/sidebar/todo.tsx +49 -0
- package/src/tui/feature-plugins/system/plugins.tsx +269 -0
- package/src/tui/feature-plugins/system/session-v2.tsx +1143 -0
- package/src/tui/feature-plugins/system/which-key.tsx +608 -0
- package/src/tui/keymap.tsx +166 -0
- package/src/tui/layer.ts +6 -0
- package/src/tui/plugin/api.tsx +381 -0
- package/src/tui/plugin/command-shim.ts +109 -0
- package/src/tui/plugin/internal.ts +33 -0
- package/src/tui/plugin/runtime.ts +1069 -0
- package/src/tui/plugin/slots.tsx +60 -0
- package/src/tui/routes/home.tsx +96 -0
- package/src/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
- package/src/tui/routes/session/dialog-message.tsx +108 -0
- package/src/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/tui/routes/session/footer.tsx +91 -0
- package/src/tui/routes/session/index.tsx +188 -0
- package/src/tui/routes/session/permission.tsx +722 -0
- package/src/tui/routes/session/question.tsx +490 -0
- package/src/tui/routes/session/sidebar.tsx +102 -0
- package/src/tui/routes/session/subagent-footer.tsx +133 -0
- package/src/tui/run.ts +84 -0
- package/src/tui/thread.ts +261 -0
- package/src/tui/tsconfig.json +40 -0
- package/src/tui/ui/dialog-alert.tsx +66 -0
- package/src/tui/ui/dialog-confirm.tsx +108 -0
- package/src/tui/ui/dialog-export-options.tsx +217 -0
- package/src/tui/ui/dialog-help.tsx +40 -0
- package/src/tui/ui/dialog-prompt.tsx +101 -0
- package/src/tui/ui/dialog-select.tsx +553 -0
- package/src/tui/ui/dialog.tsx +211 -0
- package/src/tui/ui/link.tsx +34 -0
- package/src/tui/ui/spinner.ts +368 -0
- package/src/tui/ui/toast.tsx +111 -0
- package/src/tui/util/clipboard.ts +217 -0
- package/src/tui/util/editor.ts +37 -0
- package/src/tui/util/model.ts +23 -0
- package/src/tui/util/provider-origin.ts +7 -0
- package/src/tui/util/revert-diff.ts +18 -0
- package/src/tui/util/scroll.ts +25 -0
- package/src/tui/util/selection.ts +65 -0
- package/src/tui/util/signal.ts +41 -0
- package/src/tui/util/sound.ts +156 -0
- package/src/tui/util/transcript.ts +112 -0
- package/src/tui/validate-session.ts +29 -0
- package/src/tui/win32.ts +130 -0
- package/src/tui/worker.ts +104 -0
|
@@ -9,6 +9,7 @@ export const C = {
|
|
|
9
9
|
altOn: "\x1b[?1049h",
|
|
10
10
|
altOff: "\x1b[?1049l",
|
|
11
11
|
showCursor: "\x1b[?25h",
|
|
12
|
+
hideCursor: "\x1b[?25l",
|
|
12
13
|
setBgBlack: "\x1b]11;#000000\x07",
|
|
13
14
|
resetBg: "\x1b]111\x07",
|
|
14
15
|
bg: "\x1b[48;2;0;0;0m",
|
|
@@ -27,6 +28,27 @@ export const C = {
|
|
|
27
28
|
noItalic: "\x1b[23m",
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Output buffer — all rendering writes here; flushed once per frame.
|
|
33
|
+
// This eliminates flash: the terminal sees clear+draw as a single atomic op.
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
let _buf = "";
|
|
36
|
+
|
|
37
|
+
function _w(s) {
|
|
38
|
+
_buf += s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _moveTo(row, col) {
|
|
42
|
+
_buf += `\x1b[${Math.max(1, row + 1)};${Math.max(1, col + 1)}H`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _writeAt(row, col, text, width, bg = C.bg) {
|
|
46
|
+
_moveTo(row, col);
|
|
47
|
+
_buf += bg + padAnsi(text, width) + C.bg;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
30
52
|
export function titlecase(value) {
|
|
31
53
|
const clean = String(value || "").trim();
|
|
32
54
|
if (!clean) return "";
|
|
@@ -66,25 +88,11 @@ function terminalSize() {
|
|
|
66
88
|
};
|
|
67
89
|
}
|
|
68
90
|
|
|
91
|
+
// Legacy direct-write moveTo used for final cursor positioning after flush
|
|
69
92
|
function moveTo(row, col) {
|
|
70
93
|
readline.cursorTo(process.stdout, Math.max(0, col), Math.max(0, row));
|
|
71
94
|
}
|
|
72
95
|
|
|
73
|
-
function writeAt(row, col, text, width, bg = C.bg) {
|
|
74
|
-
moveTo(row, col);
|
|
75
|
-
process.stdout.write(bg + padAnsi(text, width) + C.bg);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function clearFull() {
|
|
79
|
-
const { width, height } = terminalSize();
|
|
80
|
-
process.stdout.write(C.bg + "\x1b[2J\x1b[3J\x1b[H");
|
|
81
|
-
for (let row = 0; row < height; row++) {
|
|
82
|
-
process.stdout.write(C.bg + " ".repeat(width));
|
|
83
|
-
if (row < height - 1) process.stdout.write("\n");
|
|
84
|
-
}
|
|
85
|
-
process.stdout.write("\x1b[H" + C.bg);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
96
|
function centerLeft(width, contentWidth) {
|
|
89
97
|
return Math.max(0, Math.floor((width - contentWidth) / 2));
|
|
90
98
|
}
|
|
@@ -122,7 +130,7 @@ function renderLogo(termWidth, top) {
|
|
|
122
130
|
const line = lines[i];
|
|
123
131
|
const left = centerLeft(termWidth, visible(line));
|
|
124
132
|
const color = i < 2 ? C.dim : i < 4 ? C.muted : C.text;
|
|
125
|
-
|
|
133
|
+
_writeAt(top + i, left, C.bold + color + line + C.normal, visible(line));
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
|
@@ -164,11 +172,11 @@ function renderPromptBlock(state, chatWidth) {
|
|
|
164
172
|
: C.muted + C.italic + fit(placeholder, contentWidth) + C.noItalic;
|
|
165
173
|
const metaLine = renderModeMeta(currentModeIdx, activeAgent, activeModel, contentWidth);
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
_writeAt(top, left, C.primary + "┃" + C.panel + " " + " ".repeat(contentWidth), boxWidth, C.bg);
|
|
176
|
+
_writeAt(top + 1, left, C.primary + "┃" + C.panel + " " + padAnsi(promptLine, contentWidth), boxWidth, C.bg);
|
|
177
|
+
_writeAt(top + 2, left, C.primary + "┃" + C.panel + " " + " ".repeat(contentWidth), boxWidth, C.bg);
|
|
178
|
+
_writeAt(top + 3, left, C.primary + "┃" + C.panel + " " + padAnsi(metaLine, contentWidth), boxWidth, C.bg);
|
|
179
|
+
_writeAt(top + 4, left, C.primary + "╹" + C.panel + " " + " ".repeat(contentWidth), boxWidth, C.bg);
|
|
172
180
|
|
|
173
181
|
const hotkeys =
|
|
174
182
|
C.bold + C.text + "tab" + C.normal + C.muted + " agents " +
|
|
@@ -181,7 +189,7 @@ function renderPromptBlock(state, chatWidth) {
|
|
|
181
189
|
: "") +
|
|
182
190
|
C.bold + C.text + "enter" + C.normal + C.muted + " send";
|
|
183
191
|
const hotkeyLeft = Math.max(left, left + boxWidth - visible(hotkeys));
|
|
184
|
-
|
|
192
|
+
_writeAt(top + 5, hotkeyLeft, hotkeys, visible(hotkeys), C.bg);
|
|
185
193
|
|
|
186
194
|
return { row: top + 1, col: left + 2 + visible(beforeCursor) };
|
|
187
195
|
}
|
|
@@ -282,7 +290,6 @@ function transcriptLines(transcript, width) {
|
|
|
282
290
|
addLine(lines, margin + C.primary + "┃" + C.panel + " " + C.text + padAnsi(chunk, inner), C.bg);
|
|
283
291
|
}
|
|
284
292
|
if (item.meta) {
|
|
285
|
-
// Show QUEUED badge with distinct highlight color
|
|
286
293
|
const badgeText = item.meta === "queued"
|
|
287
294
|
? C.warning + C.bold + "QUEUED" + C.normal + C.muted + " click to send/remove"
|
|
288
295
|
: C.muted + item.meta;
|
|
@@ -315,7 +322,7 @@ function transcriptLines(transcript, width) {
|
|
|
315
322
|
const isQuestion = trace.tool === "ask_questions";
|
|
316
323
|
const label = isQuestion ? C.warning + C.bold + "QUESTION" : C.muted + "TOOL";
|
|
317
324
|
const name = isQuestion ? "" : C.text + trace.tool;
|
|
318
|
-
|
|
325
|
+
|
|
319
326
|
if (isQuestion && trace.args?.questions) {
|
|
320
327
|
addLine(lines, "", C.bg);
|
|
321
328
|
addLine(lines, margin + label + C.muted + " (…)" + C.bg, C.bg);
|
|
@@ -354,11 +361,11 @@ function renderChat(state, chatWidth, height, promptTop) {
|
|
|
354
361
|
const slice = lines.slice(start, start + maxRows);
|
|
355
362
|
|
|
356
363
|
for (let i = 0; i < slice.length && i < maxRows; i++) {
|
|
357
|
-
|
|
364
|
+
_writeAt(i, 0, slice[i].text, chatWidth - 1, slice[i].bg);
|
|
358
365
|
}
|
|
359
366
|
|
|
360
367
|
if (offset > 0 && maxRows > 1) {
|
|
361
|
-
|
|
368
|
+
_writeAt(0, 0, C.muted + `↑ ${offset} lines above bottom`, chatWidth - 1, C.bg);
|
|
362
369
|
}
|
|
363
370
|
}
|
|
364
371
|
|
|
@@ -369,37 +376,37 @@ function renderSidebar(state) {
|
|
|
369
376
|
const sideWidth = Math.min(34, Math.max(28, Math.floor(width * 0.3)));
|
|
370
377
|
const left = width - sideWidth;
|
|
371
378
|
for (let row = 0; row < height; row++) {
|
|
372
|
-
|
|
379
|
+
_writeAt(row, left, "", sideWidth, C.panel);
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
const contentWidth = sideWidth - 4;
|
|
376
383
|
const totalTokens = state.usage.input + state.usage.output;
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
385
|
+
_writeAt(1, left + 2, C.text + C.bold + "Sesión" + C.normal, contentWidth, C.panel);
|
|
386
|
+
_writeAt(2, left + 2, C.muted + fit(state.sessionTitle || "chat local", contentWidth), contentWidth, C.panel);
|
|
387
|
+
_writeAt(3, left + 2, C.muted + "agent " + C.text + state.activeAgent, contentWidth, C.panel);
|
|
388
|
+
_writeAt(4, left + 2, C.muted + "app " + C.text + `APX ${state.version}`, contentWidth, C.panel);
|
|
382
389
|
|
|
383
|
-
|
|
390
|
+
_writeAt(6, left + 2, C.text + C.bold + "Modelo" + C.normal, contentWidth, C.panel);
|
|
384
391
|
const modelLines = wrapText(state.activeModel || "(none)", contentWidth).slice(0, 2);
|
|
385
392
|
modelLines.forEach((line, index) => {
|
|
386
|
-
|
|
393
|
+
_writeAt(7 + index, left + 2, C.muted + line, contentWidth, C.panel);
|
|
387
394
|
});
|
|
388
395
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
396
|
+
_writeAt(10, left + 2, C.text + C.bold + "Contexto" + C.normal, contentWidth, C.panel);
|
|
397
|
+
_writeAt(11, left + 2, C.muted + `${totalTokens.toLocaleString()} tokens total`, contentWidth, C.panel);
|
|
398
|
+
_writeAt(12, left + 2, C.muted + `${state.usage.input.toLocaleString()} in · ${state.usage.output.toLocaleString()} out`, contentWidth, C.panel);
|
|
399
|
+
_writeAt(13, left + 2, C.muted + `${state.usage.percent}% usado`, contentWidth, C.panel);
|
|
400
|
+
_writeAt(14, left + 2, C.muted + "$0.00 spent", contentWidth, C.panel);
|
|
394
401
|
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
_writeAt(16, left + 2, C.text + C.bold + "LSP" + C.normal, contentWidth, C.panel);
|
|
403
|
+
_writeAt(17, left + 2, C.muted + "LSPs are disabled", contentWidth, C.panel);
|
|
397
404
|
|
|
398
405
|
const cwdLines = wrapText(process.cwd(), contentWidth).slice(-4);
|
|
399
406
|
let row = Math.max(19, height - cwdLines.length - 4);
|
|
400
|
-
|
|
401
|
-
for (const line of cwdLines)
|
|
402
|
-
|
|
407
|
+
_writeAt(row++, left + 2, C.text + C.bold + "Directorio" + C.normal, contentWidth, C.panel);
|
|
408
|
+
for (const line of cwdLines) _writeAt(row++, left + 2, C.muted + line, contentWidth, C.panel);
|
|
409
|
+
_writeAt(height - 1, left + 2, C.success + "• " + C.text + "APX" + C.muted + ` ${state.version}`, contentWidth, C.panel);
|
|
403
410
|
|
|
404
411
|
return { left, width: sideWidth };
|
|
405
412
|
}
|
|
@@ -415,17 +422,17 @@ function renderPaletteOverlay(state) {
|
|
|
415
422
|
const left = centerLeft(width, boxWidth);
|
|
416
423
|
const top = Math.max(1, Math.floor((height - boxHeight) / 2));
|
|
417
424
|
|
|
418
|
-
|
|
419
|
-
|
|
425
|
+
_writeAt(top, left, C.text + C.bold + " " + title + C.normal, boxWidth, C.panel);
|
|
426
|
+
_writeAt(top + 1, left, C.dim + "▀".repeat(boxWidth), boxWidth, C.panel);
|
|
420
427
|
for (let i = 0; i < state.paletteOptions.length; i++) {
|
|
421
428
|
const active = i === state.paletteSelection;
|
|
422
429
|
const marker = active ? "›" : " ";
|
|
423
430
|
const bg = active ? C.panel2 : C.panel;
|
|
424
431
|
const fg = active ? C.primary + C.bold : C.text;
|
|
425
|
-
|
|
432
|
+
_writeAt(top + 2 + i, left, fg + ` ${marker} ${state.paletteOptions[i]}` + C.normal, boxWidth, bg);
|
|
426
433
|
}
|
|
427
|
-
|
|
428
|
-
|
|
434
|
+
_writeAt(top + 2 + state.paletteOptions.length, left, C.dim + "▄".repeat(boxWidth), boxWidth, C.panel);
|
|
435
|
+
_writeAt(
|
|
429
436
|
top + 3 + state.paletteOptions.length,
|
|
430
437
|
left,
|
|
431
438
|
C.muted + "↑↓ select " + C.text + C.bold + "enter" + C.normal + C.muted + " choose " + C.text + C.bold + "esc" + C.normal + C.muted + " close",
|
|
@@ -444,18 +451,18 @@ function renderMsgActionsOverlay(state) {
|
|
|
444
451
|
const left = centerLeft(width, boxWidth);
|
|
445
452
|
const top = Math.max(1, Math.floor((height - boxHeight) / 2));
|
|
446
453
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
454
|
+
_writeAt(top, left, C.text + C.bold + " " + title + C.normal, boxWidth, C.panel);
|
|
455
|
+
_writeAt(top + 1, left, C.muted + " " + C.italic + preview + C.noItalic, boxWidth, C.panel);
|
|
456
|
+
_writeAt(top + 2, left, C.dim + "▀".repeat(boxWidth), boxWidth, C.panel);
|
|
450
457
|
for (let i = 0; i < opts.length; i++) {
|
|
451
458
|
const active = i === state.msgActionsSelection;
|
|
452
459
|
const marker = active ? "›" : " ";
|
|
453
460
|
const bg = active ? C.panel2 : C.panel;
|
|
454
461
|
const fg = active ? C.primary + C.bold : C.text;
|
|
455
|
-
|
|
462
|
+
_writeAt(top + 3 + i, left, fg + ` ${marker} ${opts[i]}` + C.normal, boxWidth, bg);
|
|
456
463
|
}
|
|
457
|
-
|
|
458
|
-
|
|
464
|
+
_writeAt(top + 3 + opts.length, left, C.dim + "▄".repeat(boxWidth), boxWidth, C.panel);
|
|
465
|
+
_writeAt(
|
|
459
466
|
top + 4 + opts.length,
|
|
460
467
|
left,
|
|
461
468
|
C.muted + "↑↓ select " + C.text + C.bold + "enter" + C.normal + C.muted + " choose " + C.text + C.bold + "esc" + C.normal + C.muted + " close",
|
|
@@ -465,7 +472,9 @@ function renderMsgActionsOverlay(state) {
|
|
|
465
472
|
}
|
|
466
473
|
|
|
467
474
|
export function renderTerminalChat(state) {
|
|
468
|
-
|
|
475
|
+
// Reset buffer — clear screen and home cursor in ONE atomic write (no flash)
|
|
476
|
+
_buf = C.bg + "\x1b[2J\x1b[H";
|
|
477
|
+
|
|
469
478
|
const { width, height } = terminalSize();
|
|
470
479
|
const sidebar = state.hasStarted ? renderSidebar(state) : null;
|
|
471
480
|
const chatWidth = sidebar ? sidebar.left : width;
|
|
@@ -481,5 +490,11 @@ export function renderTerminalChat(state) {
|
|
|
481
490
|
|
|
482
491
|
if (state.inCommandPalette) renderPaletteOverlay(state);
|
|
483
492
|
if (state.inMsgActions) renderMsgActionsOverlay(state);
|
|
484
|
-
|
|
493
|
+
|
|
494
|
+
// Position cursor in buffer before final flush
|
|
495
|
+
_buf += `\x1b[${Math.max(1, cursor.row + 1)};${Math.max(1, cursor.col + 1)}H`;
|
|
496
|
+
|
|
497
|
+
// Single atomic write — terminal renders entire frame at once, no flash
|
|
498
|
+
process.stdout.write(_buf);
|
|
499
|
+
_buf = "";
|
|
485
500
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { CommandModule, Argv, ArgumentsCamelCase } from "yargs";
|
|
2
|
+
import { getHttp } from "../http.js";
|
|
3
|
+
import { println, error, success, dim, highlight, table } from "../ui.js";
|
|
4
|
+
|
|
5
|
+
interface GlobalArgs { project?: string }
|
|
6
|
+
|
|
7
|
+
async function resolveProjectId(project?: string): Promise<string> {
|
|
8
|
+
const http = await getHttp();
|
|
9
|
+
const projects = (await http.get("/projects")) as Array<{ id: string; name: string; path: string }>;
|
|
10
|
+
if (!projects?.length) throw new Error("No projects registered. Run: apx init");
|
|
11
|
+
if (project) {
|
|
12
|
+
const match = projects.find(
|
|
13
|
+
(p) => p.id === project || p.name === project || p.path === project || p.path?.endsWith("/" + project),
|
|
14
|
+
);
|
|
15
|
+
if (!match) throw new Error(`Project not found: ${project}`);
|
|
16
|
+
return match.id;
|
|
17
|
+
}
|
|
18
|
+
return projects[0]!.id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---------- list ----------
|
|
22
|
+
|
|
23
|
+
const listCmd: CommandModule = {
|
|
24
|
+
command: "list",
|
|
25
|
+
aliases: ["ls"],
|
|
26
|
+
describe: "List agents in the current project",
|
|
27
|
+
handler: async (args: ArgumentsCamelCase<GlobalArgs>) => {
|
|
28
|
+
try {
|
|
29
|
+
const http = await getHttp();
|
|
30
|
+
const pid = await resolveProjectId(args.project as string | undefined);
|
|
31
|
+
const agents = (await http.get(`/projects/${pid}/agents`)) as Array<{
|
|
32
|
+
slug: string; role?: string; model?: string; description?: string;
|
|
33
|
+
}>;
|
|
34
|
+
if (!agents?.length) { println(dim("No agents found.")); return; }
|
|
35
|
+
table(
|
|
36
|
+
agents.map((a) => ({
|
|
37
|
+
Slug: a.slug,
|
|
38
|
+
Role: a.role || "-",
|
|
39
|
+
Model: a.model || "-",
|
|
40
|
+
Description: a.description ? a.description.slice(0, 50) : "-",
|
|
41
|
+
})),
|
|
42
|
+
["Slug", "Role", "Model", "Description"],
|
|
43
|
+
);
|
|
44
|
+
} catch (err: unknown) {
|
|
45
|
+
error(err instanceof Error ? err.message : String(err));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ---------- get / show ----------
|
|
52
|
+
|
|
53
|
+
const getCmd: CommandModule = {
|
|
54
|
+
command: "get <slug>",
|
|
55
|
+
aliases: ["show"],
|
|
56
|
+
describe: "Show agent details and memory",
|
|
57
|
+
builder: (yargs: Argv) =>
|
|
58
|
+
yargs.positional("slug", { type: "string", demandOption: true, describe: "Agent slug" }),
|
|
59
|
+
handler: async (args: ArgumentsCamelCase<Record<string, unknown>>) => {
|
|
60
|
+
try {
|
|
61
|
+
const project = args.project as string | undefined;
|
|
62
|
+
const slug = args.slug as string;
|
|
63
|
+
const http = await getHttp();
|
|
64
|
+
const pid = await resolveProjectId(project);
|
|
65
|
+
const agent = (await http.get(`/projects/${pid}/agents/${slug}`)) as Record<string, unknown>;
|
|
66
|
+
process.stdout.write(JSON.stringify(agent, null, 2) + "\n");
|
|
67
|
+
} catch (err: unknown) {
|
|
68
|
+
error(err instanceof Error ? err.message : String(err));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ---------- add / create ----------
|
|
75
|
+
|
|
76
|
+
const addCmd: CommandModule = {
|
|
77
|
+
command: "add <slug>",
|
|
78
|
+
aliases: ["create"],
|
|
79
|
+
describe: "Create a new agent",
|
|
80
|
+
builder: (yargs: Argv) =>
|
|
81
|
+
yargs
|
|
82
|
+
.positional("slug", { type: "string", demandOption: true, describe: "Agent slug (identifier)" })
|
|
83
|
+
.option("role", { type: "string", describe: "Agent role (system prompt)" })
|
|
84
|
+
.option("model", { type: "string", describe: "LLM model (e.g. claude-sonnet-4-6)" })
|
|
85
|
+
.option("description", { alias: "d", type: "string", describe: "Short description" })
|
|
86
|
+
.option("skills", { type: "string", describe: "Comma-separated skill list" })
|
|
87
|
+
.option("language", { type: "string", describe: "Language code (e.g. en, es)" })
|
|
88
|
+
.option("tools", { type: "string", describe: "Comma-separated allowed tools" }),
|
|
89
|
+
handler: async (args: ArgumentsCamelCase<Record<string, unknown>>) => {
|
|
90
|
+
try {
|
|
91
|
+
const project = args.project as string | undefined;
|
|
92
|
+
const slug = args.slug as string;
|
|
93
|
+
const role = args.role as string | undefined;
|
|
94
|
+
const model = args.model as string | undefined;
|
|
95
|
+
const description = args.description as string | undefined;
|
|
96
|
+
const skills = args.skills as string | undefined;
|
|
97
|
+
const language = args.language as string | undefined;
|
|
98
|
+
const tools = args.tools as string | undefined;
|
|
99
|
+
const http = await getHttp();
|
|
100
|
+
const pid = await resolveProjectId(project);
|
|
101
|
+
const agent = await http.post(`/projects/${pid}/agents`, {
|
|
102
|
+
slug,
|
|
103
|
+
role,
|
|
104
|
+
model,
|
|
105
|
+
description,
|
|
106
|
+
skills: skills?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
107
|
+
language,
|
|
108
|
+
tools: tools?.split(",").map((t) => t.trim()).filter(Boolean),
|
|
109
|
+
});
|
|
110
|
+
success(`Agent created: ${highlight(slug)}`);
|
|
111
|
+
process.stdout.write(JSON.stringify(agent, null, 2) + "\n");
|
|
112
|
+
} catch (err: unknown) {
|
|
113
|
+
error(err instanceof Error ? err.message : String(err));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// ---------- memory ----------
|
|
120
|
+
|
|
121
|
+
const memoryCmd: CommandModule = {
|
|
122
|
+
command: "memory <slug>",
|
|
123
|
+
describe: "Read or write agent memory",
|
|
124
|
+
builder: (yargs: Argv) =>
|
|
125
|
+
yargs
|
|
126
|
+
.positional("slug", { type: "string", demandOption: true, describe: "Agent slug" })
|
|
127
|
+
.option("append", { type: "boolean", default: false, describe: "Append stdin to memory" })
|
|
128
|
+
.option("replace", { type: "boolean", default: false, describe: "Replace memory with stdin" }),
|
|
129
|
+
handler: async (args: ArgumentsCamelCase<Record<string, unknown>>) => {
|
|
130
|
+
try {
|
|
131
|
+
const project = args.project as string | undefined;
|
|
132
|
+
const slug = args.slug as string;
|
|
133
|
+
const append = args.append as boolean;
|
|
134
|
+
const replace = args.replace as boolean;
|
|
135
|
+
const http = await getHttp();
|
|
136
|
+
const pid = await resolveProjectId(project);
|
|
137
|
+
if (append || replace) {
|
|
138
|
+
const chunks: Buffer[] = [];
|
|
139
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
140
|
+
const body = Buffer.concat(chunks).toString();
|
|
141
|
+
if (append) {
|
|
142
|
+
const existing = (await http.get(`/projects/${pid}/agents/${slug}/memory`)) as { body: string };
|
|
143
|
+
await http.put(`/projects/${pid}/agents/${slug}/memory`, { body: (existing.body || "") + "\n" + body });
|
|
144
|
+
} else {
|
|
145
|
+
await http.put(`/projects/${pid}/agents/${slug}/memory`, { body });
|
|
146
|
+
}
|
|
147
|
+
success("Memory updated.");
|
|
148
|
+
} else {
|
|
149
|
+
const mem = (await http.get(`/projects/${pid}/agents/${slug}/memory`)) as { body: string };
|
|
150
|
+
process.stdout.write(mem.body || "");
|
|
151
|
+
}
|
|
152
|
+
} catch (err: unknown) {
|
|
153
|
+
error(err instanceof Error ? err.message : String(err));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ---------- parent ----------
|
|
160
|
+
|
|
161
|
+
export const agentCmd: CommandModule = {
|
|
162
|
+
command: "agent",
|
|
163
|
+
aliases: ["agents"],
|
|
164
|
+
describe: "Manage APC agents",
|
|
165
|
+
builder: (yargs: Argv) =>
|
|
166
|
+
yargs
|
|
167
|
+
.command(listCmd)
|
|
168
|
+
.command(getCmd)
|
|
169
|
+
.command(addCmd)
|
|
170
|
+
.command(memoryCmd)
|
|
171
|
+
.demandCommand(1, "Specify an agent subcommand"),
|
|
172
|
+
handler: () => {},
|
|
173
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { CommandModule, Argv, ArgumentsCamelCase } from "yargs";
|
|
2
|
+
import { getHttp, type StreamEvent } from "../http.js";
|
|
3
|
+
import { error, println, dim } from "../ui.js";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
|
|
6
|
+
interface GlobalArgs { project?: string }
|
|
7
|
+
|
|
8
|
+
async function resolveProjectId(project?: string): Promise<string> {
|
|
9
|
+
const http = await getHttp();
|
|
10
|
+
const projects = (await http.get("/projects")) as Array<{ id: string; name: string; path: string }>;
|
|
11
|
+
if (!projects?.length) throw new Error("No projects registered. Run: apx init");
|
|
12
|
+
if (project) {
|
|
13
|
+
const match = projects.find(
|
|
14
|
+
(p) => p.id === project || p.name === project || p.path === project || p.path?.endsWith("/" + project),
|
|
15
|
+
);
|
|
16
|
+
if (!match) throw new Error(`Project not found: ${project}`);
|
|
17
|
+
return match.id;
|
|
18
|
+
}
|
|
19
|
+
return projects[0]!.id;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const chatCmd: CommandModule = {
|
|
23
|
+
command: "chat [agent]",
|
|
24
|
+
describe: "Start an interactive multi-turn chat with an agent",
|
|
25
|
+
builder: (yargs: Argv) =>
|
|
26
|
+
yargs
|
|
27
|
+
.positional("agent", {
|
|
28
|
+
type: "string",
|
|
29
|
+
default: "default",
|
|
30
|
+
describe: "Agent slug (defaults to super-agent)",
|
|
31
|
+
})
|
|
32
|
+
.option("model", { type: "string", describe: "Override model" })
|
|
33
|
+
.option("conversation", {
|
|
34
|
+
alias: "c",
|
|
35
|
+
type: "string",
|
|
36
|
+
describe: "Continue an existing conversation ID",
|
|
37
|
+
}),
|
|
38
|
+
handler: async (
|
|
39
|
+
args: ArgumentsCamelCase<Record<string, unknown>>,
|
|
40
|
+
) => {
|
|
41
|
+
try {
|
|
42
|
+
const project = args.project as string | undefined;
|
|
43
|
+
const agent = args.agent as string;
|
|
44
|
+
const model = args.model as string | undefined;
|
|
45
|
+
const http = await getHttp();
|
|
46
|
+
const pid = await resolveProjectId(project);
|
|
47
|
+
let conversationId = args.conversation as string | undefined;
|
|
48
|
+
|
|
49
|
+
if (!process.stdin.isTTY) {
|
|
50
|
+
// Non-interactive: read one prompt from stdin
|
|
51
|
+
const chunks: Buffer[] = [];
|
|
52
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
53
|
+
const prompt = Buffer.concat(chunks).toString().trim();
|
|
54
|
+
if (!prompt) throw new Error("No prompt provided via stdin.");
|
|
55
|
+
const result = (await http.post(`/projects/${pid}/agents/${agent}/chat`, {
|
|
56
|
+
prompt,
|
|
57
|
+
model,
|
|
58
|
+
conversation_id: conversationId,
|
|
59
|
+
})) as { conversation_id: string; text: string };
|
|
60
|
+
process.stdout.write(result.text + "\n");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Interactive REPL
|
|
65
|
+
println(dim(`APX Chat — agent: ${agent} (type /exit or ctrl+c to quit)`));
|
|
66
|
+
|
|
67
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
|
|
68
|
+
rl.setPrompt("\x1b[96m> \x1b[0m");
|
|
69
|
+
rl.prompt();
|
|
70
|
+
|
|
71
|
+
rl.on("line", async (line) => {
|
|
72
|
+
const text = line.trim();
|
|
73
|
+
if (!text) { rl.prompt(); return; }
|
|
74
|
+
if (text === "/exit" || text === "/quit") { rl.close(); return; }
|
|
75
|
+
|
|
76
|
+
rl.pause();
|
|
77
|
+
try {
|
|
78
|
+
// Try streaming first
|
|
79
|
+
let responded = false;
|
|
80
|
+
try {
|
|
81
|
+
const result = await http.streamPost(
|
|
82
|
+
`/projects/${pid}/super-agent/chat/stream`,
|
|
83
|
+
{ prompt: text, model, previousMessages: [] },
|
|
84
|
+
(ev: StreamEvent) => {
|
|
85
|
+
if (ev.type === "chunk" && typeof ev.chunk === "string") {
|
|
86
|
+
process.stdout.write(ev.chunk);
|
|
87
|
+
responded = true;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
) as { text?: string };
|
|
91
|
+
if (!responded && result?.text) process.stdout.write(result.text);
|
|
92
|
+
process.stdout.write("\n");
|
|
93
|
+
} catch {
|
|
94
|
+
// Fall back to non-streaming agent chat
|
|
95
|
+
const result = (await http.post(`/projects/${pid}/agents/${agent}/chat`, {
|
|
96
|
+
prompt: text,
|
|
97
|
+
model,
|
|
98
|
+
conversation_id: conversationId,
|
|
99
|
+
})) as { conversation_id: string; text: string };
|
|
100
|
+
conversationId = result.conversation_id;
|
|
101
|
+
process.stdout.write(result.text + "\n");
|
|
102
|
+
}
|
|
103
|
+
} catch (err: unknown) {
|
|
104
|
+
error(err instanceof Error ? err.message : String(err));
|
|
105
|
+
}
|
|
106
|
+
rl.resume();
|
|
107
|
+
rl.prompt();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
rl.on("close", () => {
|
|
111
|
+
println(dim("\nGoodbye."));
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
114
|
+
} catch (err: unknown) {
|
|
115
|
+
error(err instanceof Error ? err.message : String(err));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { CommandModule, Argv, ArgumentsCamelCase } from "yargs";
|
|
2
|
+
import { getHttp } from "../http.js";
|
|
3
|
+
import { println, error, success, dim } from "../ui.js";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// ---------- status ----------
|
|
7
|
+
|
|
8
|
+
const statusCmd: CommandModule = {
|
|
9
|
+
command: "status",
|
|
10
|
+
describe: "Show daemon status",
|
|
11
|
+
handler: async () => {
|
|
12
|
+
try {
|
|
13
|
+
const http = await getHttp();
|
|
14
|
+
const health = (await http.get("/health")) as {
|
|
15
|
+
status: string; version?: string; uptime_s?: number;
|
|
16
|
+
};
|
|
17
|
+
success(`Daemon running version=${health.version ?? "?"} uptime=${health.uptime_s ?? "?"}s`);
|
|
18
|
+
} catch {
|
|
19
|
+
error("Daemon is not running.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ---------- start ----------
|
|
26
|
+
|
|
27
|
+
const startCmd: CommandModule = {
|
|
28
|
+
command: "start",
|
|
29
|
+
describe: "Start the APX daemon in the background",
|
|
30
|
+
handler: async () => {
|
|
31
|
+
try {
|
|
32
|
+
const http = await getHttp();
|
|
33
|
+
await http.get("/health");
|
|
34
|
+
println(dim("Daemon is already running."));
|
|
35
|
+
} catch {
|
|
36
|
+
const daemon = spawn("apx-daemon", [], {
|
|
37
|
+
detached: true,
|
|
38
|
+
stdio: "ignore",
|
|
39
|
+
});
|
|
40
|
+
daemon.unref();
|
|
41
|
+
success("Daemon started.");
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ---------- stop ----------
|
|
47
|
+
|
|
48
|
+
const stopCmd: CommandModule = {
|
|
49
|
+
command: "stop",
|
|
50
|
+
describe: "Gracefully stop the APX daemon",
|
|
51
|
+
handler: async () => {
|
|
52
|
+
try {
|
|
53
|
+
const http = await getHttp();
|
|
54
|
+
await http.post("/admin/shutdown", {});
|
|
55
|
+
success("Daemon stopped.");
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
error(err instanceof Error ? err.message : String(err));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ---------- logs ----------
|
|
64
|
+
|
|
65
|
+
const logsCmd: CommandModule = {
|
|
66
|
+
command: "logs",
|
|
67
|
+
describe: "Stream daemon logs",
|
|
68
|
+
builder: (yargs: Argv) =>
|
|
69
|
+
yargs.option("tail", {
|
|
70
|
+
alias: "n",
|
|
71
|
+
type: "number",
|
|
72
|
+
default: 50,
|
|
73
|
+
describe: "Number of lines to show",
|
|
74
|
+
}),
|
|
75
|
+
handler: async (args: ArgumentsCamelCase<Record<string, unknown>>) => {
|
|
76
|
+
try {
|
|
77
|
+
const tail = (args.tail as number) ?? 50;
|
|
78
|
+
const { homedir } = await import("node:os");
|
|
79
|
+
const { createReadStream } = await import("node:fs");
|
|
80
|
+
const { createInterface } = await import("node:readline");
|
|
81
|
+
const logPath = `${homedir()}/.apx/daemon.log`;
|
|
82
|
+
try {
|
|
83
|
+
const rl = createInterface({ input: createReadStream(logPath), crlfDelay: Infinity });
|
|
84
|
+
const lines: string[] = [];
|
|
85
|
+
for await (const line of rl) lines.push(line);
|
|
86
|
+
const tailLines = lines.slice(-tail);
|
|
87
|
+
tailLines.forEach((l) => println(l));
|
|
88
|
+
} catch {
|
|
89
|
+
error(`Log file not found: ${logPath}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
} catch (err: unknown) {
|
|
93
|
+
error(err instanceof Error ? err.message : String(err));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ---------- parent ----------
|
|
100
|
+
|
|
101
|
+
export const daemonCmd: CommandModule = {
|
|
102
|
+
command: "daemon",
|
|
103
|
+
describe: "Manage the APX background daemon",
|
|
104
|
+
builder: (yargs: Argv) =>
|
|
105
|
+
yargs
|
|
106
|
+
.command(statusCmd)
|
|
107
|
+
.command(startCmd)
|
|
108
|
+
.command(stopCmd)
|
|
109
|
+
.command(logsCmd)
|
|
110
|
+
.demandCommand(1, "Specify a daemon subcommand"),
|
|
111
|
+
handler: () => {},
|
|
112
|
+
};
|