aicodeman 1.20.1 → 1.22.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/README.md +19 -15
- package/README.zh-CN.md +6 -20
- package/dist/cli-style.d.ts +115 -0
- package/dist/cli-style.d.ts.map +1 -0
- package/dist/cli-style.js +245 -0
- package/dist/cli-style.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +237 -186
- package/dist/cli.js.map +1 -1
- package/dist/config/dependency-registry.d.ts.map +1 -1
- package/dist/config/dependency-registry.js +24 -0
- package/dist/config/dependency-registry.js.map +1 -1
- package/dist/config/instance.d.ts +9 -0
- package/dist/config/instance.d.ts.map +1 -1
- package/dist/config/instance.js +14 -0
- package/dist/config/instance.js.map +1 -1
- package/dist/cron/cron-service.d.ts +2 -1
- package/dist/cron/cron-service.d.ts.map +1 -1
- package/dist/cron/cron-service.js +3 -2
- package/dist/cron/cron-service.js.map +1 -1
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +11 -0
- package/dist/docker-hosts.js.map +1 -1
- package/dist/mux-interface.d.ts +3 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +1 -0
- package/dist/remote-hosts.js.map +1 -1
- package/dist/session.d.ts +9 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +29 -4
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +22 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +45 -13
- package/dist/state-store.js.map +1 -1
- package/dist/tab-layout-legacy-order.d.ts +12 -0
- package/dist/tab-layout-legacy-order.d.ts.map +1 -0
- package/dist/tab-layout-legacy-order.js +58 -0
- package/dist/tab-layout-legacy-order.js.map +1 -0
- package/dist/tab-layout-persistence.d.ts +43 -0
- package/dist/tab-layout-persistence.d.ts.map +1 -0
- package/dist/tab-layout-persistence.js +100 -0
- package/dist/tab-layout-persistence.js.map +1 -0
- package/dist/tab-layout-service.d.ts +96 -0
- package/dist/tab-layout-service.d.ts.map +1 -0
- package/dist/tab-layout-service.js +535 -0
- package/dist/tab-layout-service.js.map +1 -0
- package/dist/tab-layout.d.ts +83 -0
- package/dist/tab-layout.d.ts.map +1 -0
- package/dist/tab-layout.js +457 -0
- package/dist/tab-layout.js.map +1 -0
- package/dist/tmux-manager.d.ts +2 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +60 -10
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tui/tui-ansi.d.ts +71 -0
- package/dist/tui/tui-ansi.d.ts.map +1 -0
- package/dist/tui/tui-ansi.js +576 -0
- package/dist/tui/tui-ansi.js.map +1 -0
- package/dist/tui/tui-app.d.ts +424 -0
- package/dist/tui/tui-app.d.ts.map +1 -0
- package/dist/tui/tui-app.js +2424 -0
- package/dist/tui/tui-app.js.map +1 -0
- package/dist/tui/tui-approvals.d.ts +71 -0
- package/dist/tui/tui-approvals.d.ts.map +1 -0
- package/dist/tui/tui-approvals.js +114 -0
- package/dist/tui/tui-approvals.js.map +1 -0
- package/dist/tui/tui-client.d.ts +581 -0
- package/dist/tui/tui-client.d.ts.map +1 -0
- package/dist/tui/tui-client.js +1359 -0
- package/dist/tui/tui-client.js.map +1 -0
- package/dist/tui/tui-composer.d.ts +83 -0
- package/dist/tui/tui-composer.d.ts.map +1 -0
- package/dist/tui/tui-composer.js +174 -0
- package/dist/tui/tui-composer.js.map +1 -0
- package/dist/tui/tui-digest.d.ts +27 -0
- package/dist/tui/tui-digest.d.ts.map +1 -0
- package/dist/tui/tui-digest.js +79 -0
- package/dist/tui/tui-digest.js.map +1 -0
- package/dist/tui/tui-keys.d.ts +57 -0
- package/dist/tui/tui-keys.d.ts.map +1 -0
- package/dist/tui/tui-keys.js +204 -0
- package/dist/tui/tui-keys.js.map +1 -0
- package/dist/tui/tui-layout.d.ts +70 -0
- package/dist/tui/tui-layout.d.ts.map +1 -0
- package/dist/tui/tui-layout.js +87 -0
- package/dist/tui/tui-layout.js.map +1 -0
- package/dist/tui/tui-model.d.ts +178 -0
- package/dist/tui/tui-model.d.ts.map +1 -0
- package/dist/tui/tui-model.js +502 -0
- package/dist/tui/tui-model.js.map +1 -0
- package/dist/tui/tui-render.d.ts +150 -0
- package/dist/tui/tui-render.d.ts.map +1 -0
- package/dist/tui/tui-render.js +784 -0
- package/dist/tui/tui-render.js.map +1 -0
- package/dist/tui/tui-sse.d.ts +79 -0
- package/dist/tui/tui-sse.d.ts.map +1 -0
- package/dist/tui/tui-sse.js +200 -0
- package/dist/tui/tui-sse.js.map +1 -0
- package/dist/tui/tui-types.d.ts +190 -0
- package/dist/tui/tui-types.d.ts.map +1 -0
- package/dist/tui/tui-types.js +16 -0
- package/dist/tui/tui-types.js.map +1 -0
- package/dist/types/app-state.d.ts +3 -0
- package/dist/types/app-state.d.ts.map +1 -1
- package/dist/types/app-state.js.map +1 -1
- package/dist/types/session.d.ts +30 -4
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +2 -1
- package/dist/types/session.js.map +1 -1
- package/dist/utils/cli-executable-resolver.d.ts +2 -2
- package/dist/utils/cli-executable-resolver.js +2 -2
- package/dist/utils/dependency-report.d.ts +21 -3
- package/dist/utils/dependency-report.d.ts.map +1 -1
- package/dist/utils/dependency-report.js +29 -10
- package/dist/utils/dependency-report.js.map +1 -1
- package/dist/utils/grok-cli-resolver.d.ts +61 -0
- package/dist/utils/grok-cli-resolver.d.ts.map +1 -0
- package/dist/utils/grok-cli-resolver.js +130 -0
- package/dist/utils/grok-cli-resolver.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/web/ports/index.d.ts +1 -0
- package/dist/web/ports/index.d.ts.map +1 -1
- package/dist/web/ports/session-port.d.ts +1 -1
- package/dist/web/ports/session-port.d.ts.map +1 -1
- package/dist/web/ports/tab-layout-port.d.ts +7 -0
- package/dist/web/ports/tab-layout-port.d.ts.map +1 -0
- package/dist/web/ports/tab-layout-port.js +2 -0
- package/dist/web/ports/tab-layout-port.js.map +1 -0
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.84bf8955.js +36 -0
- package/dist/web/public/app.84bf8955.js.br +0 -0
- package/dist/web/public/app.84bf8955.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.5abbd823.js → constants.189c7bff.js} +82 -8
- package/dist/web/public/constants.189c7bff.js.br +0 -0
- package/dist/web/public/constants.189c7bff.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js +5 -2
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.34016b89.js +1 -0
- package/dist/web/public/i18n.34016b89.js.br +0 -0
- package/dist/web/public/i18n.34016b89.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +74 -13
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
- package/dist/web/public/mobile-overview.js +1 -0
- package/dist/web/public/mobile-overview.js.br +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.983f064e.css +1 -0
- package/dist/web/public/mobile.983f064e.css.br +0 -0
- package/dist/web/public/mobile.983f064e.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.5e97a36e.js → panels-ui.7c667123.js} +77 -77
- package/dist/web/public/panels-ui.7c667123.js.br +0 -0
- package/dist/web/public/panels-ui.7c667123.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js +15 -5
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.acd79900.js +68 -0
- package/dist/web/public/session-ui.acd79900.js.br +0 -0
- package/dist/web/public/session-ui.acd79900.js.gz +0 -0
- package/dist/web/public/settings-ui.115a2d1e.js +67 -0
- package/dist/web/public/settings-ui.115a2d1e.js.br +0 -0
- package/dist/web/public/settings-ui.115a2d1e.js.gz +0 -0
- package/dist/web/public/styles.64369ebd.css +1 -0
- package/dist/web/public/styles.64369ebd.css.br +0 -0
- package/dist/web/public/styles.64369ebd.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.f3465ce1.js +1 -0
- package/dist/web/public/tab-rail-resize.f3465ce1.js.br +0 -0
- package/dist/web/public/tab-rail-resize.f3465ce1.js.gz +0 -0
- package/dist/web/public/{terminal-ui.f70d681f.js → terminal-ui.ef68a666.js} +1 -1
- package/dist/web/public/terminal-ui.ef68a666.js.br +0 -0
- package/dist/web/public/terminal-ui.ef68a666.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/response-viewer-transcript.js +1 -1
- package/dist/web/response-viewer-transcript.js.map +1 -1
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/ralph-routes.js +1 -1
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +9 -4
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +68 -27
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts +2 -2
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +15 -2
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/routes/tab-layout-routes.d.ts +6 -0
- package/dist/web/routes/tab-layout-routes.d.ts.map +1 -0
- package/dist/web/routes/tab-layout-routes.js +45 -0
- package/dist/web/routes/tab-layout-routes.js.map +1 -0
- package/dist/web/routes/webview-routes.d.ts +2 -2
- package/dist/web/routes/webview-routes.d.ts.map +1 -1
- package/dist/web/routes/webview-routes.js +29 -1
- package/dist/web/routes/webview-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +25 -1
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +47 -5
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +101 -36
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-order-sse.d.ts +7 -0
- package/dist/web/session-order-sse.d.ts.map +1 -0
- package/dist/web/session-order-sse.js +10 -0
- package/dist/web/session-order-sse.js.map +1 -0
- package/dist/web/sse-events.d.ts +5 -2
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +5 -2
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/sse-stream-manager.d.ts +9 -0
- package/dist/web/sse-stream-manager.d.ts.map +1 -1
- package/dist/web/sse-stream-manager.js +86 -27
- package/dist/web/sse-stream-manager.js.map +1 -1
- package/dist/web/tab-layout-sse.d.ts +4 -0
- package/dist/web/tab-layout-sse.d.ts.map +1 -0
- package/dist/web/tab-layout-sse.js +4 -0
- package/dist/web/tab-layout-sse.js.map +1 -0
- package/package.json +2 -1
- package/skills/codeman/reference/endpoints.md +12 -11
- package/skills/codeman/reference/messaging.md +2 -2
- package/skills/codeman/reference/recipes.md +1 -1
- package/skills/codeman/reference/verbs.md +3 -3
- package/dist/web/public/app.202140a2.js +0 -35
- package/dist/web/public/app.202140a2.js.br +0 -0
- package/dist/web/public/app.202140a2.js.gz +0 -0
- package/dist/web/public/constants.5abbd823.js.br +0 -0
- package/dist/web/public/constants.5abbd823.js.gz +0 -0
- package/dist/web/public/i18n.196a992e.js +0 -1
- package/dist/web/public/i18n.196a992e.js.br +0 -0
- package/dist/web/public/i18n.196a992e.js.gz +0 -0
- package/dist/web/public/mobile.49aeb17e.css +0 -1
- package/dist/web/public/mobile.49aeb17e.css.br +0 -0
- package/dist/web/public/mobile.49aeb17e.css.gz +0 -0
- package/dist/web/public/panels-ui.5e97a36e.js.br +0 -0
- package/dist/web/public/panels-ui.5e97a36e.js.gz +0 -0
- package/dist/web/public/session-ui.71a0ce9a.js +0 -68
- package/dist/web/public/session-ui.71a0ce9a.js.br +0 -0
- package/dist/web/public/session-ui.71a0ce9a.js.gz +0 -0
- package/dist/web/public/settings-ui.1a62e935.js +0 -67
- package/dist/web/public/settings-ui.1a62e935.js.br +0 -0
- package/dist/web/public/settings-ui.1a62e935.js.gz +0 -0
- package/dist/web/public/styles.68f9e118.css +0 -1
- package/dist/web/public/styles.68f9e118.css.br +0 -0
- package/dist/web/public/styles.68f9e118.css.gz +0 -0
- package/dist/web/public/terminal-ui.f70d681f.js.br +0 -0
- package/dist/web/public/terminal-ui.f70d681f.js.gz +0 -0
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* @module cli
|
|
8
8
|
*/
|
|
9
9
|
import { Command } from 'commander';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
10
|
import { createRequire } from 'module';
|
|
12
11
|
import http from 'node:http';
|
|
13
12
|
import https from 'node:https';
|
|
@@ -25,6 +24,7 @@ import { isSupportedAttachmentExtension } from './attachment-registry.js';
|
|
|
25
24
|
import { daemonStatus, startDaemon, stopDaemon } from './daemon-control.js';
|
|
26
25
|
import { installService, serviceStatus, uninstallService } from './service-installer.js';
|
|
27
26
|
import { isLoopbackBindHost, isUnauthenticatedNetworkAcknowledged } from './web/network-auth-policy.js';
|
|
27
|
+
import { confirm, heading, isInteractive, kv, palette, rule, tint, withSpinner } from './cli-style.js';
|
|
28
28
|
const require = createRequire(import.meta.url);
|
|
29
29
|
const pkg = require('../package.json');
|
|
30
30
|
const program = new Command();
|
|
@@ -97,13 +97,13 @@ program
|
|
|
97
97
|
.action(async (filePath, options) => {
|
|
98
98
|
const extension = String(filePath).split('.').pop()?.toLowerCase() || '';
|
|
99
99
|
if (!isAbsolute(filePath) || !isSupportedAttachmentExtension(extension)) {
|
|
100
|
-
console.error(
|
|
100
|
+
console.error(palette.err('✗ attach requires an absolute path to a png, pdf, docx, pptx, md, or txt file'));
|
|
101
101
|
process.exit(1);
|
|
102
102
|
}
|
|
103
103
|
const sessionId = options.session || process.env.CODEMAN_SESSION_ID;
|
|
104
104
|
const apiUrl = options.url || process.env.CODEMAN_API_URL || 'https://127.0.0.1:3000';
|
|
105
105
|
if (sessionId && (await postAttachment(apiUrl, sessionId, filePath))) {
|
|
106
|
-
console.log(
|
|
106
|
+
console.log(palette.ok('✓ Attachment card requested'));
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
console.log(makeAttachmentMagicLink(filePath));
|
|
@@ -158,7 +158,7 @@ export function resolveSkillTargetPath(options) {
|
|
|
158
158
|
function resolveSkillTarget(options) {
|
|
159
159
|
const resolved = resolveSkillTargetPath(options);
|
|
160
160
|
if (resolved.missingCase !== undefined) {
|
|
161
|
-
console.error(
|
|
161
|
+
console.error(palette.err(`✗ Case not found: ${resolved.missingCase}`));
|
|
162
162
|
process.exit(1);
|
|
163
163
|
}
|
|
164
164
|
return resolved.target;
|
|
@@ -182,10 +182,10 @@ function reportSkillResult(result, target) {
|
|
|
182
182
|
};
|
|
183
183
|
const message = messages[result];
|
|
184
184
|
if (message.ok) {
|
|
185
|
-
console.log(
|
|
185
|
+
console.log(palette.ok(`✓ ${message.text}`));
|
|
186
186
|
}
|
|
187
187
|
else {
|
|
188
|
-
console.error(
|
|
188
|
+
console.error(palette.err(`✗ ${message.text}`));
|
|
189
189
|
process.exit(1);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
@@ -203,7 +203,7 @@ skillCmd
|
|
|
203
203
|
reportSkillResult(await installAgentSkillInto(target), target);
|
|
204
204
|
}
|
|
205
205
|
catch (err) {
|
|
206
|
-
console.error(
|
|
206
|
+
console.error(palette.err(`✗ Failed to install agent skill: ${getErrorMessage(err)}`));
|
|
207
207
|
process.exit(1);
|
|
208
208
|
}
|
|
209
209
|
});
|
|
@@ -218,7 +218,7 @@ skillCmd
|
|
|
218
218
|
reportSkillResult(await removeAgentSkillFrom(target), target);
|
|
219
219
|
}
|
|
220
220
|
catch (err) {
|
|
221
|
-
console.error(
|
|
221
|
+
console.error(palette.err(`✗ Failed to remove agent skill: ${getErrorMessage(err)}`));
|
|
222
222
|
process.exit(1);
|
|
223
223
|
}
|
|
224
224
|
});
|
|
@@ -232,12 +232,12 @@ sessionCmd
|
|
|
232
232
|
try {
|
|
233
233
|
const manager = getSessionManager();
|
|
234
234
|
const session = await manager.createSession(options.dir);
|
|
235
|
-
console.log(
|
|
235
|
+
console.log(palette.ok(`✓ Session started: ${session.id}`));
|
|
236
236
|
console.log(` Working directory: ${session.workingDir}`);
|
|
237
237
|
console.log(` PID: ${session.pid}`);
|
|
238
238
|
}
|
|
239
239
|
catch (err) {
|
|
240
|
-
console.error(
|
|
240
|
+
console.error(palette.err(`✗ Failed to start session: ${getErrorMessage(err)}`));
|
|
241
241
|
process.exit(1);
|
|
242
242
|
}
|
|
243
243
|
});
|
|
@@ -248,65 +248,72 @@ sessionCmd
|
|
|
248
248
|
try {
|
|
249
249
|
const manager = getSessionManager();
|
|
250
250
|
await manager.stopSession(id);
|
|
251
|
-
console.log(
|
|
251
|
+
console.log(palette.ok(`✓ Session stopped: ${id}`));
|
|
252
252
|
}
|
|
253
253
|
catch (err) {
|
|
254
|
-
console.error(
|
|
254
|
+
console.error(palette.err(`✗ Failed to stop session: ${getErrorMessage(err)}`));
|
|
255
255
|
process.exit(1);
|
|
256
256
|
}
|
|
257
257
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
258
|
+
/** Session status in the shared vocabulary: idle is fine, busy is working, anything else is a problem. */
|
|
259
|
+
function sessionStatusLabel(status) {
|
|
260
|
+
if (status === 'idle')
|
|
261
|
+
return palette.ok('idle');
|
|
262
|
+
if (status === 'busy')
|
|
263
|
+
return palette.warn('busy');
|
|
264
|
+
return palette.err(status);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The one session listing. `codeman list` used to be a copy of this that had
|
|
268
|
+
* drifted (it lost the stopped and web-server sections), so it now calls the
|
|
269
|
+
* same renderer and only opts out of those two sections.
|
|
270
|
+
*/
|
|
271
|
+
function printSessionList(options) {
|
|
263
272
|
const manager = getSessionManager();
|
|
264
273
|
const sessions = manager.getAllSessions();
|
|
265
274
|
const stored = manager.getStoredSessions();
|
|
266
275
|
if (sessions.length === 0 && Object.keys(stored).length === 0) {
|
|
267
|
-
console.log(
|
|
276
|
+
console.log(palette.warn('No sessions found'));
|
|
268
277
|
return;
|
|
269
278
|
}
|
|
270
|
-
console.log(
|
|
279
|
+
console.log(heading('Active Sessions:'));
|
|
271
280
|
if (sessions.length === 0) {
|
|
272
281
|
console.log(' (none)');
|
|
273
282
|
}
|
|
274
283
|
else {
|
|
275
284
|
for (const session of sessions) {
|
|
276
|
-
|
|
277
|
-
? chalk.green('idle')
|
|
278
|
-
: session.status === 'busy'
|
|
279
|
-
? chalk.yellow('busy')
|
|
280
|
-
: chalk.red(session.status);
|
|
281
|
-
console.log(` ${chalk.cyan(session.id.slice(0, 8))} ${status} ${session.workingDir}`);
|
|
285
|
+
console.log(` ${palette.info(session.id.slice(0, 8))} ${sessionStatusLabel(session.status)} ${session.workingDir}`);
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
288
|
+
if (options.includeStored) {
|
|
289
|
+
const stoppedSessions = Object.values(stored).filter((s) => s.status === 'stopped');
|
|
290
|
+
if (stoppedSessions.length > 0) {
|
|
291
|
+
console.log(heading('Stopped Sessions:'));
|
|
292
|
+
for (const session of stoppedSessions) {
|
|
293
|
+
const name = session.name ? ` (${session.name})` : '';
|
|
294
|
+
console.log(` ${palette.muted(session.id.slice(0, 8))} ${palette.muted('stopped')}${name} ${session.workingDir}`);
|
|
295
|
+
}
|
|
290
296
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
?
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const name = session.name ? ` (${session.name})` : '';
|
|
303
|
-
const mode = session.mode === 'shell' ? chalk.gray(' [shell]') : '';
|
|
304
|
-
const cost = session.totalCost ? chalk.gray(` $${session.totalCost.toFixed(4)}`) : '';
|
|
305
|
-
console.log(` ${chalk.cyan(session.id.slice(0, 8))} ${status}${name}${mode}${cost} ${session.workingDir}`);
|
|
297
|
+
// Sessions the web server owns: this process has no PTY for them, so they
|
|
298
|
+
// only exist in the shared state file.
|
|
299
|
+
const activeSessions = Object.values(stored).filter((s) => s.status !== 'stopped');
|
|
300
|
+
if (sessions.length === 0 && activeSessions.length > 0) {
|
|
301
|
+
console.log(heading('Active Sessions (from web server):'));
|
|
302
|
+
for (const session of activeSessions) {
|
|
303
|
+
const name = session.name ? ` (${session.name})` : '';
|
|
304
|
+
const mode = session.mode === 'shell' ? palette.muted(' [shell]') : '';
|
|
305
|
+
const cost = session.totalCost ? palette.muted(` $${session.totalCost.toFixed(4)}`) : '';
|
|
306
|
+
console.log(` ${palette.info(session.id.slice(0, 8))} ${sessionStatusLabel(session.status)}${name}${mode}${cost} ${session.workingDir}`);
|
|
307
|
+
}
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
console.log('');
|
|
309
|
-
}
|
|
311
|
+
}
|
|
312
|
+
sessionCmd
|
|
313
|
+
.command('list')
|
|
314
|
+
.alias('ls')
|
|
315
|
+
.description('List all sessions')
|
|
316
|
+
.action(() => printSessionList({ includeStored: true }));
|
|
310
317
|
sessionCmd
|
|
311
318
|
.command('logs <id>')
|
|
312
319
|
.description('View session output')
|
|
@@ -315,11 +322,11 @@ sessionCmd
|
|
|
315
322
|
const manager = getSessionManager();
|
|
316
323
|
const output = options.errors ? manager.getSessionError(id) : manager.getSessionOutput(id);
|
|
317
324
|
if (output === null) {
|
|
318
|
-
console.log(
|
|
325
|
+
console.log(palette.warn(`Session ${id} not found or not active`));
|
|
319
326
|
return;
|
|
320
327
|
}
|
|
321
328
|
if (output === '') {
|
|
322
|
-
console.log(
|
|
329
|
+
console.log(palette.muted('(no output)'));
|
|
323
330
|
return;
|
|
324
331
|
}
|
|
325
332
|
console.log(output);
|
|
@@ -342,7 +349,7 @@ taskCmd
|
|
|
342
349
|
completionPhrase: options.completion,
|
|
343
350
|
timeoutMs: options.timeout ? parseInt(options.timeout, 10) : undefined,
|
|
344
351
|
});
|
|
345
|
-
console.log(
|
|
352
|
+
console.log(palette.ok(`✓ Task added: ${task.id}`));
|
|
346
353
|
console.log(` Prompt: ${prompt.slice(0, 50)}${prompt.length > 50 ? '...' : ''}`);
|
|
347
354
|
console.log(` Priority: ${task.priority}`);
|
|
348
355
|
});
|
|
@@ -358,23 +365,23 @@ taskCmd
|
|
|
358
365
|
tasks = tasks.filter((t) => t.status === options.status);
|
|
359
366
|
}
|
|
360
367
|
if (tasks.length === 0) {
|
|
361
|
-
console.log(
|
|
368
|
+
console.log(palette.warn('No tasks found'));
|
|
362
369
|
return;
|
|
363
370
|
}
|
|
364
371
|
const statusColors = {
|
|
365
|
-
pending:
|
|
366
|
-
running:
|
|
367
|
-
completed:
|
|
368
|
-
failed:
|
|
372
|
+
pending: palette.muted,
|
|
373
|
+
running: palette.warn,
|
|
374
|
+
completed: palette.ok,
|
|
375
|
+
failed: palette.err,
|
|
369
376
|
};
|
|
370
|
-
console.log(
|
|
377
|
+
console.log(palette.emph('\nTasks:'));
|
|
371
378
|
for (const task of tasks) {
|
|
372
379
|
const color = statusColors[task.status];
|
|
373
380
|
const prompt = task.prompt.slice(0, 40) + (task.prompt.length > 40 ? '...' : '');
|
|
374
|
-
console.log(` ${
|
|
381
|
+
console.log(` ${palette.info(task.id.slice(0, 8))} ${color(task.status.padEnd(10))} [${task.priority}] ${prompt}`);
|
|
375
382
|
}
|
|
376
383
|
const counts = queue.getCount();
|
|
377
|
-
console.log(
|
|
384
|
+
console.log(palette.emph('\nSummary:'));
|
|
378
385
|
console.log(` Pending: ${counts.pending}, Running: ${counts.running}, Completed: ${counts.completed}, Failed: ${counts.failed}`);
|
|
379
386
|
console.log('');
|
|
380
387
|
});
|
|
@@ -385,10 +392,10 @@ taskCmd
|
|
|
385
392
|
const queue = getTaskQueue();
|
|
386
393
|
const task = queue.getTask(id);
|
|
387
394
|
if (!task) {
|
|
388
|
-
console.log(
|
|
395
|
+
console.log(palette.err(`Task ${id} not found`));
|
|
389
396
|
return;
|
|
390
397
|
}
|
|
391
|
-
console.log(
|
|
398
|
+
console.log(palette.emph('\nTask Details:'));
|
|
392
399
|
console.log(` ID: ${task.id}`);
|
|
393
400
|
console.log(` Status: ${task.status}`);
|
|
394
401
|
console.log(` Priority: ${task.priority}`);
|
|
@@ -398,10 +405,10 @@ taskCmd
|
|
|
398
405
|
console.log(` Session: ${task.assignedSessionId}`);
|
|
399
406
|
}
|
|
400
407
|
if (task.error) {
|
|
401
|
-
console.log(` Error: ${
|
|
408
|
+
console.log(` Error: ${palette.err(task.error)}`);
|
|
402
409
|
}
|
|
403
410
|
if (task.output) {
|
|
404
|
-
console.log(
|
|
411
|
+
console.log(palette.emph('\nOutput:'));
|
|
405
412
|
console.log(task.output.slice(0, 500) + (task.output.length > 500 ? '...' : ''));
|
|
406
413
|
}
|
|
407
414
|
console.log('');
|
|
@@ -413,10 +420,10 @@ taskCmd
|
|
|
413
420
|
.action((id) => {
|
|
414
421
|
const queue = getTaskQueue();
|
|
415
422
|
if (queue.removeTask(id)) {
|
|
416
|
-
console.log(
|
|
423
|
+
console.log(palette.ok(`✓ Task removed: ${id}`));
|
|
417
424
|
}
|
|
418
425
|
else {
|
|
419
|
-
console.log(
|
|
426
|
+
console.log(palette.err(`Task ${id} not found`));
|
|
420
427
|
}
|
|
421
428
|
});
|
|
422
429
|
taskCmd
|
|
@@ -429,15 +436,15 @@ taskCmd
|
|
|
429
436
|
let count;
|
|
430
437
|
if (options.all) {
|
|
431
438
|
count = queue.clearAll();
|
|
432
|
-
console.log(
|
|
439
|
+
console.log(palette.ok(`✓ Cleared ${count} tasks`));
|
|
433
440
|
}
|
|
434
441
|
else if (options.failed) {
|
|
435
442
|
count = queue.clearFailed();
|
|
436
|
-
console.log(
|
|
443
|
+
console.log(palette.ok(`✓ Cleared ${count} failed tasks`));
|
|
437
444
|
}
|
|
438
445
|
else {
|
|
439
446
|
count = queue.clearCompleted();
|
|
440
|
-
console.log(
|
|
447
|
+
console.log(palette.ok(`✓ Cleared ${count} completed tasks`));
|
|
441
448
|
}
|
|
442
449
|
});
|
|
443
450
|
// ============ Ralph Loop Commands ============
|
|
@@ -455,32 +462,32 @@ ralphCmd
|
|
|
455
462
|
loop.setMinDuration(parseFloat(options.minHours));
|
|
456
463
|
}
|
|
457
464
|
if (loop.isRunning()) {
|
|
458
|
-
console.log(
|
|
465
|
+
console.log(palette.warn('Ralph loop is already running'));
|
|
459
466
|
return;
|
|
460
467
|
}
|
|
461
468
|
loop.on('taskAssigned', (taskId, sessionId) => {
|
|
462
|
-
console.log(
|
|
469
|
+
console.log(palette.info(`→ Task ${taskId.slice(0, 8)} assigned to session ${sessionId.slice(0, 8)}`));
|
|
463
470
|
});
|
|
464
471
|
loop.on('taskCompleted', (taskId) => {
|
|
465
|
-
console.log(
|
|
472
|
+
console.log(palette.ok(`✓ Task ${taskId.slice(0, 8)} completed`));
|
|
466
473
|
});
|
|
467
474
|
loop.on('taskFailed', (taskId, error) => {
|
|
468
|
-
console.log(
|
|
475
|
+
console.log(palette.err(`✗ Task ${taskId.slice(0, 8)} failed: ${error}`));
|
|
469
476
|
});
|
|
470
477
|
loop.on('stopped', () => {
|
|
471
|
-
console.log(
|
|
478
|
+
console.log(palette.warn('\nRalph loop stopped'));
|
|
472
479
|
printStats(loop.getStats());
|
|
473
480
|
process.exit(0);
|
|
474
481
|
});
|
|
475
482
|
await loop.start();
|
|
476
|
-
console.log(
|
|
483
|
+
console.log(palette.ok('✓ Ralph loop started'));
|
|
477
484
|
if (options.minHours) {
|
|
478
485
|
console.log(` Minimum duration: ${options.minHours} hours`);
|
|
479
486
|
}
|
|
480
|
-
console.log(
|
|
487
|
+
console.log(palette.muted(' Press Ctrl+C to stop\n'));
|
|
481
488
|
// Keep process running
|
|
482
489
|
process.on('SIGINT', () => {
|
|
483
|
-
console.log(
|
|
490
|
+
console.log(palette.warn('\nStopping Ralph loop...'));
|
|
484
491
|
loop.stop();
|
|
485
492
|
});
|
|
486
493
|
});
|
|
@@ -490,11 +497,11 @@ ralphCmd
|
|
|
490
497
|
.action(() => {
|
|
491
498
|
const loop = getRalphLoop();
|
|
492
499
|
if (!loop.isRunning()) {
|
|
493
|
-
console.log(
|
|
500
|
+
console.log(palette.warn('Ralph loop is not running'));
|
|
494
501
|
return;
|
|
495
502
|
}
|
|
496
503
|
loop.stop();
|
|
497
|
-
console.log(
|
|
504
|
+
console.log(palette.ok('✓ Ralph loop stopped'));
|
|
498
505
|
});
|
|
499
506
|
ralphCmd
|
|
500
507
|
.command('status')
|
|
@@ -505,21 +512,21 @@ ralphCmd
|
|
|
505
512
|
printStats(stats);
|
|
506
513
|
});
|
|
507
514
|
function printStats(stats) {
|
|
508
|
-
const statusColor = stats.status === 'running' ?
|
|
509
|
-
console.log(
|
|
515
|
+
const statusColor = stats.status === 'running' ? palette.ok : stats.status === 'paused' ? palette.warn : palette.muted;
|
|
516
|
+
console.log(palette.emph('\nRalph Loop Status:'));
|
|
510
517
|
console.log(` Status: ${statusColor(stats.status)}`);
|
|
511
518
|
console.log(` Elapsed: ${stats.elapsedHours.toFixed(2)} hours`);
|
|
512
519
|
if (stats.minDurationMs) {
|
|
513
520
|
const minHours = stats.minDurationMs / (1000 * 60 * 60);
|
|
514
521
|
console.log(` Min Duration: ${minHours.toFixed(2)} hours (${stats.minDurationReached ? 'reached' : 'not reached'})`);
|
|
515
522
|
}
|
|
516
|
-
console.log(
|
|
523
|
+
console.log(palette.emph('\nTasks:'));
|
|
517
524
|
console.log(` Pending: ${stats.pending}`);
|
|
518
525
|
console.log(` Running: ${stats.running}`);
|
|
519
526
|
console.log(` Completed: ${stats.completed} (${stats.tasksCompleted} this session)`);
|
|
520
527
|
console.log(` Failed: ${stats.failed}`);
|
|
521
528
|
console.log(` Generated: ${stats.tasksGenerated}`);
|
|
522
|
-
console.log(
|
|
529
|
+
console.log(palette.emph('\nSessions:'));
|
|
523
530
|
console.log(` Active: ${stats.activeSessions}`);
|
|
524
531
|
console.log(` Idle: ${stats.idleSessions}`);
|
|
525
532
|
console.log(` Busy: ${stats.busySessions}`);
|
|
@@ -615,44 +622,44 @@ program
|
|
|
615
622
|
break;
|
|
616
623
|
}
|
|
617
624
|
}
|
|
618
|
-
console.log(
|
|
619
|
-
console.log(
|
|
620
|
-
console.log(
|
|
625
|
+
console.log(heading('Codeman Status'));
|
|
626
|
+
console.log(rule(40));
|
|
627
|
+
console.log(heading('Web Server:'));
|
|
621
628
|
if (probe.reachable) {
|
|
622
629
|
const version = probe.version ? ` (v${probe.version})` : '';
|
|
623
|
-
console.log(
|
|
630
|
+
console.log(kv('Status', `${palette.ok('running')}${version} at ${probe.url}`));
|
|
624
631
|
if (probe.authRequired) {
|
|
625
|
-
console.log(
|
|
632
|
+
console.log(palette.muted(' (answers 401: set CODEMAN_PASSWORD/CODEMAN_USERNAME to see session details)'));
|
|
626
633
|
}
|
|
627
634
|
}
|
|
628
635
|
else {
|
|
629
|
-
console.log(
|
|
630
|
-
console.log(
|
|
636
|
+
console.log(kv('Status', `${palette.err('not reachable')} at ${candidates.join(' or ')}`));
|
|
637
|
+
console.log(palette.muted(' (start it with `codeman web`, or check your service: systemctl --user status codeman-web)'));
|
|
631
638
|
}
|
|
632
639
|
// Prefer the server's live view; fall back to the shared saved state, labeled
|
|
633
640
|
// as such, so the numbers are never silently a different thing.
|
|
634
641
|
if (probe.sessions) {
|
|
635
642
|
const live = probe.sessions;
|
|
636
|
-
console.log(
|
|
637
|
-
console.log(
|
|
638
|
-
console.log(
|
|
639
|
-
console.log(
|
|
643
|
+
console.log(heading('Sessions (live, from the server):'));
|
|
644
|
+
console.log(kv('Total', String(live.length)));
|
|
645
|
+
console.log(kv('Idle', String(live.filter((s) => s.status === 'idle').length)));
|
|
646
|
+
console.log(kv('Busy', String(live.filter((s) => s.status === 'busy').length)));
|
|
640
647
|
}
|
|
641
648
|
else {
|
|
642
649
|
const manager = getSessionManager();
|
|
643
650
|
const storedValues = Object.values(manager.getStoredSessions());
|
|
644
|
-
console.log(
|
|
645
|
-
console.log(
|
|
646
|
-
console.log(
|
|
647
|
-
console.log(
|
|
651
|
+
console.log(heading('Sessions (from saved state):'));
|
|
652
|
+
console.log(kv('Active', String(storedValues.filter((s) => s.status !== 'stopped').length)));
|
|
653
|
+
console.log(kv('Idle', String(storedValues.filter((s) => s.status === 'idle').length)));
|
|
654
|
+
console.log(kv('Busy', String(storedValues.filter((s) => s.status === 'busy').length)));
|
|
648
655
|
}
|
|
649
656
|
const taskCounts = getTaskQueue().getCount();
|
|
650
|
-
console.log(
|
|
651
|
-
console.log(
|
|
652
|
-
console.log(
|
|
653
|
-
console.log(
|
|
654
|
-
console.log(
|
|
655
|
-
console.log(
|
|
657
|
+
console.log(heading('Tasks:'));
|
|
658
|
+
console.log(kv('Total', String(taskCounts.total)));
|
|
659
|
+
console.log(kv('Pending', String(taskCounts.pending)));
|
|
660
|
+
console.log(kv('Running', String(taskCounts.running)));
|
|
661
|
+
console.log(kv('Completed', String(taskCounts.completed)));
|
|
662
|
+
console.log(kv('Failed', String(taskCounts.failed)));
|
|
656
663
|
console.log('');
|
|
657
664
|
});
|
|
658
665
|
program
|
|
@@ -661,15 +668,23 @@ program
|
|
|
661
668
|
.option('-f, --force', 'Skip confirmation')
|
|
662
669
|
.action(async (options) => {
|
|
663
670
|
if (!options.force) {
|
|
664
|
-
console.log(
|
|
665
|
-
|
|
666
|
-
|
|
671
|
+
console.log(palette.warn('This will stop all sessions and clear all state.'));
|
|
672
|
+
// Non-interactive callers keep the old refusal: a script piping into the
|
|
673
|
+
// CLI must never be able to reset state by hanging on an unseen question.
|
|
674
|
+
if (!isInteractive()) {
|
|
675
|
+
console.log(palette.warn('Use --force to confirm.'));
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (!(await confirm('Reset all Codeman state?'))) {
|
|
679
|
+
console.log(palette.muted('○ Cancelled, nothing was changed'));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
667
682
|
}
|
|
668
683
|
const manager = getSessionManager();
|
|
669
684
|
const store = getStore();
|
|
670
685
|
await manager.stopAllSessions();
|
|
671
686
|
store.reset();
|
|
672
|
-
console.log(
|
|
687
|
+
console.log(palette.ok('✓ All state reset'));
|
|
673
688
|
});
|
|
674
689
|
// Shorthand commands at root level
|
|
675
690
|
program
|
|
@@ -679,35 +694,41 @@ program
|
|
|
679
694
|
.action(async (options) => {
|
|
680
695
|
const manager = getSessionManager();
|
|
681
696
|
const session = await manager.createSession(options.dir);
|
|
682
|
-
console.log(
|
|
697
|
+
console.log(palette.ok(`✓ Session started: ${session.id}`));
|
|
683
698
|
});
|
|
684
699
|
program
|
|
685
700
|
.command('list')
|
|
686
701
|
.alias('ls')
|
|
687
|
-
.description('List
|
|
688
|
-
.action(() => {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
702
|
+
.description('List active sessions (shorthand; `codeman session list` also shows stopped ones)')
|
|
703
|
+
.action(() => printSessionList({ includeStored: false }));
|
|
704
|
+
// ============ TUI ============
|
|
705
|
+
program
|
|
706
|
+
.command('tui')
|
|
707
|
+
.argument('[n]', 'attach straight to the nth session of `codeman tui --list`')
|
|
708
|
+
.description('Terminal dashboard for your sessions (the web UI remains the primary surface)')
|
|
709
|
+
.option('-l, --list', 'Print the numbered session list and exit, instead of opening the dashboard')
|
|
710
|
+
.action(async (position, options) => {
|
|
711
|
+
// Imported here, not at the top: the dashboard pulls in the whole TUI core,
|
|
712
|
+
// and every other command would pay for it at startup.
|
|
713
|
+
const { runTui, runTuiAttach, runTuiList } = await import('./tui/tui-app.js');
|
|
714
|
+
if (options.list) {
|
|
715
|
+
process.exitCode = await runTuiList();
|
|
694
716
|
return;
|
|
695
717
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
? chalk.green('idle')
|
|
704
|
-
: session.status === 'busy'
|
|
705
|
-
? chalk.yellow('busy')
|
|
706
|
-
: chalk.red(session.status);
|
|
707
|
-
console.log(` ${chalk.cyan(session.id.slice(0, 8))} ${status} ${session.workingDir}`);
|
|
718
|
+
if (position !== undefined) {
|
|
719
|
+
const n = Number.parseInt(position, 10);
|
|
720
|
+
if (!Number.isSafeInteger(n) || n < 1) {
|
|
721
|
+
console.error(palette.err(`"${position}" is not a session number.`));
|
|
722
|
+
console.error(`Run ${palette.info('codeman tui --list')} to see them.`);
|
|
723
|
+
process.exitCode = 1;
|
|
724
|
+
return;
|
|
708
725
|
}
|
|
726
|
+
process.exitCode = await runTuiAttach(n);
|
|
727
|
+
return;
|
|
709
728
|
}
|
|
710
|
-
|
|
729
|
+
// The dashboard owns the terminal until it quits; exiting explicitly keeps a
|
|
730
|
+
// stray handle (a socket mid-close) from stranding the user's shell.
|
|
731
|
+
process.exit(await runTui());
|
|
711
732
|
});
|
|
712
733
|
// ============ Web / daemon / service Commands ============
|
|
713
734
|
/** Shared option set for the commands that can launch a web server. */
|
|
@@ -724,7 +745,7 @@ function addWebLaunchOptions(cmd) {
|
|
|
724
745
|
function toWebLaunchOptions(options) {
|
|
725
746
|
const port = parseInt(options.port, 10);
|
|
726
747
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
727
|
-
console.error(
|
|
748
|
+
console.error(palette.err(`✗ Invalid port: ${options.port}`));
|
|
728
749
|
process.exit(1);
|
|
729
750
|
}
|
|
730
751
|
return {
|
|
@@ -745,8 +766,8 @@ function warnIfUnauthenticatedNetwork(launch) {
|
|
|
745
766
|
return;
|
|
746
767
|
if (isUnauthenticatedNetworkAcknowledged(launch.allowUnauthenticatedNetwork))
|
|
747
768
|
return;
|
|
748
|
-
console.log(
|
|
749
|
-
console.log(
|
|
769
|
+
console.log(palette.warn(`⚠ Binding ${launch.host} without CODEMAN_PASSWORD: anyone who can reach this port gets terminal control.`));
|
|
770
|
+
console.log(palette.warn(' Set CODEMAN_PASSWORD, or bind 127.0.0.1 and front it with tailscale serve.'));
|
|
750
771
|
}
|
|
751
772
|
// Web interface command
|
|
752
773
|
const webCmd = addWebLaunchOptions(program.command('web').description('Start the web interface'))
|
|
@@ -760,48 +781,49 @@ webCmd.action(async (options) => {
|
|
|
760
781
|
process.env.CODEMAN_MULTIUSER = '1';
|
|
761
782
|
const launch = toWebLaunchOptions(options);
|
|
762
783
|
if (options.stop) {
|
|
763
|
-
|
|
784
|
+
// stopDaemon waits for the process to actually exit (up to 15s).
|
|
785
|
+
const result = await withSpinner('Stopping Codeman...', () => stopDaemon(launch));
|
|
764
786
|
if (result.ok && result.reason === 'not-running') {
|
|
765
|
-
console.log(
|
|
787
|
+
console.log(palette.muted(`○ ${result.message}`));
|
|
766
788
|
return;
|
|
767
789
|
}
|
|
768
790
|
if (result.ok) {
|
|
769
|
-
console.log(
|
|
770
|
-
console.log(
|
|
791
|
+
console.log(palette.ok(`✓ ${result.message ?? `Stopped Codeman (pid ${result.pid})`}`));
|
|
792
|
+
console.log(palette.muted(' Your agents keep running in tmux.'));
|
|
771
793
|
return;
|
|
772
794
|
}
|
|
773
|
-
console.error(
|
|
795
|
+
console.error(palette.err(`✗ ${result.message ?? 'Could not stop the server'}`));
|
|
774
796
|
process.exit(1);
|
|
775
797
|
}
|
|
776
798
|
if (options.status) {
|
|
777
799
|
const status = await daemonStatus(launch);
|
|
778
800
|
if (status.responding) {
|
|
779
801
|
const version = status.version ? ` (v${status.version})` : '';
|
|
780
|
-
console.log(
|
|
802
|
+
console.log(palette.ok(`✓ Responding at ${status.url}${version}`));
|
|
781
803
|
}
|
|
782
804
|
else {
|
|
783
|
-
console.log(
|
|
805
|
+
console.log(palette.warn(`○ Nothing answering at ${status.url}`));
|
|
784
806
|
}
|
|
785
|
-
console.log(
|
|
786
|
-
console.log(
|
|
787
|
-
console.log(
|
|
807
|
+
console.log(kv('Daemon pid', status.running ? palette.ok(String(status.pid)) : palette.muted('not running'), 11));
|
|
808
|
+
console.log(palette.muted(kv('Pidfile', status.pidFile, 11)));
|
|
809
|
+
console.log(palette.muted(kv('Log', status.logPath, 11)));
|
|
788
810
|
if (!status.running && status.responding) {
|
|
789
|
-
console.log(
|
|
811
|
+
console.log(palette.muted(' (running, but not started with --daemon: probably a service or a foreground run)'));
|
|
790
812
|
}
|
|
791
813
|
return;
|
|
792
814
|
}
|
|
793
815
|
if (options.daemon) {
|
|
794
816
|
warnIfUnauthenticatedNetwork(launch);
|
|
795
|
-
|
|
796
|
-
const result = await startDaemon(launch);
|
|
817
|
+
// The start polls /api/status for up to 30s; without this the shell just sits there.
|
|
818
|
+
const result = await withSpinner('Starting Codeman in the background, waiting for it to answer...', () => startDaemon(launch));
|
|
797
819
|
if (result.ok) {
|
|
798
|
-
console.log(
|
|
799
|
-
console.log(
|
|
800
|
-
console.log(
|
|
801
|
-
console.log(
|
|
820
|
+
console.log(palette.ok(`\n✓ Codeman is running at ${result.url} (pid ${result.pid})`));
|
|
821
|
+
console.log(palette.muted(` Logs: ${result.logPath}`));
|
|
822
|
+
console.log(palette.muted(' Stop it with: codeman web --stop'));
|
|
823
|
+
console.log(palette.muted(' Want it back after a reboot? codeman service install'));
|
|
802
824
|
return;
|
|
803
825
|
}
|
|
804
|
-
console.error(
|
|
826
|
+
console.error(palette.err(`\n✗ ${result.message ?? 'Failed to start'}`));
|
|
805
827
|
process.exit(1);
|
|
806
828
|
}
|
|
807
829
|
const { startWebServer } = await import('./web/server.js');
|
|
@@ -810,28 +832,28 @@ webCmd.action(async (options) => {
|
|
|
810
832
|
const https = launch.https;
|
|
811
833
|
const titleHostname = options.titleHostname;
|
|
812
834
|
const allowUnauthenticatedNetwork = launch.allowUnauthenticatedNetwork ?? false;
|
|
813
|
-
const protocol = https ? 'https' : 'http';
|
|
814
835
|
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
815
|
-
console.log(
|
|
836
|
+
console.log(palette.info(`Starting Codeman web interface on ${displayHost}:${port}${https ? ' (HTTPS)' : ''}...`));
|
|
816
837
|
try {
|
|
838
|
+
// The server prints its own "running at" line (it also covers the daemon and
|
|
839
|
+
// service launch paths), so this one used to be a duplicate of it.
|
|
817
840
|
const server = await startWebServer(port, https, false, host, titleHostname, allowUnauthenticatedNetwork);
|
|
818
|
-
console.log(chalk.green(`\n✓ Web interface running at ${protocol}://${displayHost}:${port}`));
|
|
819
841
|
if (https) {
|
|
820
|
-
console.log(
|
|
842
|
+
console.log(palette.warn(' Note: Accept the self-signed certificate in your browser on first visit'));
|
|
821
843
|
}
|
|
822
|
-
console.log(
|
|
844
|
+
console.log(palette.muted(' Press Ctrl+C to stop\n'));
|
|
823
845
|
// Graceful shutdown handler — flush state and clean up on SIGTERM/SIGINT
|
|
824
846
|
let shuttingDown = false;
|
|
825
847
|
const shutdown = async (signal) => {
|
|
826
848
|
if (shuttingDown)
|
|
827
849
|
return;
|
|
828
850
|
shuttingDown = true;
|
|
829
|
-
console.log(
|
|
851
|
+
console.log(palette.warn(`\n${signal} received, shutting down gracefully...`));
|
|
830
852
|
try {
|
|
831
853
|
await server.stop();
|
|
832
854
|
}
|
|
833
855
|
catch (err) {
|
|
834
|
-
console.error(
|
|
856
|
+
console.error(palette.err(`Error during shutdown: ${getErrorMessage(err)}`));
|
|
835
857
|
}
|
|
836
858
|
process.exit(0);
|
|
837
859
|
};
|
|
@@ -840,7 +862,7 @@ webCmd.action(async (options) => {
|
|
|
840
862
|
process.on('SIGHUP', () => shutdown('SIGHUP'));
|
|
841
863
|
}
|
|
842
864
|
catch (err) {
|
|
843
|
-
console.error(
|
|
865
|
+
console.error(palette.err(`✗ Failed to start web server: ${getErrorMessage(err)}`));
|
|
844
866
|
process.exit(1);
|
|
845
867
|
}
|
|
846
868
|
});
|
|
@@ -852,18 +874,19 @@ const serviceCmd = program
|
|
|
852
874
|
addWebLaunchOptions(serviceCmd.command('install').description('Install and start the service, then verify it answers')).action(async (options) => {
|
|
853
875
|
const launch = toWebLaunchOptions(options);
|
|
854
876
|
warnIfUnauthenticatedNetwork(launch);
|
|
855
|
-
|
|
856
|
-
|
|
877
|
+
// Install polls the new unit's /api/status for up to 30s before it can honestly
|
|
878
|
+
// report success, so the wait needs a visible heartbeat.
|
|
879
|
+
const result = await withSpinner('Installing the Codeman service, waiting for it to answer...', () => installService(launch));
|
|
857
880
|
for (const warning of result.warnings ?? [])
|
|
858
|
-
console.log(
|
|
881
|
+
console.log(palette.warn(`⚠ ${warning}`));
|
|
859
882
|
if (!result.ok) {
|
|
860
|
-
console.error(
|
|
883
|
+
console.error(palette.err(`✗ ${result.message}`));
|
|
861
884
|
process.exit(1);
|
|
862
885
|
}
|
|
863
|
-
console.log(
|
|
864
|
-
console.log(
|
|
886
|
+
console.log(palette.ok(`✓ ${result.message}`));
|
|
887
|
+
console.log(palette.muted(` Unit: ${result.unitPath}`));
|
|
865
888
|
if (process.env.CODEMAN_PASSWORD) {
|
|
866
|
-
console.log(
|
|
889
|
+
console.log(palette.warn(' Note: CODEMAN_PASSWORD was NOT copied into the unit file. Add it there yourself if the service needs auth.'));
|
|
867
890
|
}
|
|
868
891
|
});
|
|
869
892
|
serviceCmd
|
|
@@ -872,22 +895,22 @@ serviceCmd
|
|
|
872
895
|
.action(() => {
|
|
873
896
|
const result = uninstallService();
|
|
874
897
|
if (!result.ok) {
|
|
875
|
-
console.error(
|
|
898
|
+
console.error(palette.err(`✗ ${result.message}`));
|
|
876
899
|
process.exit(1);
|
|
877
900
|
}
|
|
878
|
-
console.log(
|
|
901
|
+
console.log(palette.ok(`✓ ${result.message}`));
|
|
879
902
|
});
|
|
880
903
|
addWebLaunchOptions(serviceCmd.command('status').description('Show whether the service is installed and running')).action(async (options) => {
|
|
881
904
|
const status = await serviceStatus(toWebLaunchOptions(options));
|
|
882
905
|
if (!status.kind) {
|
|
883
|
-
console.log(
|
|
906
|
+
console.log(palette.warn(`No supported supervisor on ${process.platform}. Use \`codeman web -d\` instead.`));
|
|
884
907
|
return;
|
|
885
908
|
}
|
|
886
909
|
console.log(` Supervisor: ${status.kind} (${status.name})`);
|
|
887
|
-
console.log(` Unit file: ${status.installed ?
|
|
888
|
-
console.log(` Loaded: ${status.loaded ?
|
|
910
|
+
console.log(` Unit file: ${status.installed ? palette.ok(status.unitPath) : palette.muted('not installed')}`);
|
|
911
|
+
console.log(` Loaded: ${status.loaded ? palette.ok('yes') : palette.muted('no')}`);
|
|
889
912
|
const version = status.version ? ` (v${status.version})` : '';
|
|
890
|
-
console.log(` Responding: ${status.responding ?
|
|
913
|
+
console.log(` Responding: ${status.responding ? palette.ok(`yes at ${status.url}${version}`) : palette.muted(`no at ${status.url}`)}`);
|
|
891
914
|
});
|
|
892
915
|
// ============ Multi-user Commands ============
|
|
893
916
|
//
|
|
@@ -954,7 +977,7 @@ usersCmd
|
|
|
954
977
|
.action(async (name, options) => {
|
|
955
978
|
const { createUser, isValidUsername } = await import('./user-store.js');
|
|
956
979
|
if (!isValidUsername(name)) {
|
|
957
|
-
console.error(
|
|
980
|
+
console.error(palette.err('✗ Username must be lowercase, start alphanumeric, 2-32 chars ([a-z0-9_-])'));
|
|
958
981
|
process.exit(1);
|
|
959
982
|
}
|
|
960
983
|
try {
|
|
@@ -966,19 +989,19 @@ usersCmd
|
|
|
966
989
|
password = await promptHiddenPassword('New password: ');
|
|
967
990
|
const confirm = await promptHiddenPassword('Confirm password: ');
|
|
968
991
|
if (password !== confirm) {
|
|
969
|
-
console.error(
|
|
992
|
+
console.error(palette.err('✗ Passwords do not match'));
|
|
970
993
|
process.exit(1);
|
|
971
994
|
}
|
|
972
995
|
}
|
|
973
996
|
if (!password || password.length < 8) {
|
|
974
|
-
console.error(
|
|
997
|
+
console.error(palette.err('✗ Password must be at least 8 characters'));
|
|
975
998
|
process.exit(1);
|
|
976
999
|
}
|
|
977
1000
|
const user = await createUser({ username: name, role: options.admin ? 'admin' : 'user', password });
|
|
978
|
-
console.log(
|
|
1001
|
+
console.log(palette.ok(`✓ Created ${user.role} "${user.username}"`));
|
|
979
1002
|
}
|
|
980
1003
|
catch (err) {
|
|
981
|
-
console.error(
|
|
1004
|
+
console.error(palette.err(`✗ ${getErrorMessage(err)}`));
|
|
982
1005
|
process.exit(1);
|
|
983
1006
|
}
|
|
984
1007
|
});
|
|
@@ -997,15 +1020,15 @@ usersCmd
|
|
|
997
1020
|
password = await promptHiddenPassword('New password: ');
|
|
998
1021
|
const confirm = await promptHiddenPassword('Confirm password: ');
|
|
999
1022
|
if (password !== confirm) {
|
|
1000
|
-
console.error(
|
|
1023
|
+
console.error(palette.err('✗ Passwords do not match'));
|
|
1001
1024
|
process.exit(1);
|
|
1002
1025
|
}
|
|
1003
1026
|
}
|
|
1004
1027
|
await setPassword(name, password, { mustChangePassword: false });
|
|
1005
|
-
console.log(
|
|
1028
|
+
console.log(palette.ok(`✓ Password updated for "${name}"`));
|
|
1006
1029
|
}
|
|
1007
1030
|
catch (err) {
|
|
1008
|
-
console.error(
|
|
1031
|
+
console.error(palette.err(`✗ ${getErrorMessage(err)}`));
|
|
1009
1032
|
process.exit(1);
|
|
1010
1033
|
}
|
|
1011
1034
|
});
|
|
@@ -1017,17 +1040,17 @@ usersCmd
|
|
|
1017
1040
|
const { readUsers } = await import('./user-store.js');
|
|
1018
1041
|
const users = await readUsers(true);
|
|
1019
1042
|
if (users.length === 0) {
|
|
1020
|
-
console.log(
|
|
1043
|
+
console.log(palette.warn('No users defined (run: codeman users add <name> --admin)'));
|
|
1021
1044
|
return;
|
|
1022
1045
|
}
|
|
1023
|
-
console.log(
|
|
1046
|
+
console.log(palette.emph('\nUsers:'));
|
|
1024
1047
|
for (const u of users) {
|
|
1025
|
-
const role = u.role === 'admin' ?
|
|
1026
|
-
const state = u.disabled ?
|
|
1048
|
+
const role = u.role === 'admin' ? palette.accent('admin') : palette.info('user ');
|
|
1049
|
+
const state = u.disabled ? palette.err('disabled') : palette.ok('enabled ');
|
|
1027
1050
|
const flags = [u.mustChangePassword ? 'must-change-pw' : '', u.canBypassPermissions ? 'can-bypass' : '']
|
|
1028
1051
|
.filter(Boolean)
|
|
1029
1052
|
.join(' ');
|
|
1030
|
-
console.log(` ${role} ${state} ${u.username}${flags ?
|
|
1053
|
+
console.log(` ${role} ${state} ${u.username}${flags ? palette.muted(` [${flags}]`) : ''}`);
|
|
1031
1054
|
}
|
|
1032
1055
|
console.log('');
|
|
1033
1056
|
});
|
|
@@ -1041,17 +1064,44 @@ usersCmd
|
|
|
1041
1064
|
await deleteUser(name);
|
|
1042
1065
|
if (options.deleteSpace) {
|
|
1043
1066
|
await deleteUserSpace(name);
|
|
1044
|
-
console.log(
|
|
1067
|
+
console.log(palette.ok(`✓ Deleted user "${name}" and their space`));
|
|
1045
1068
|
}
|
|
1046
1069
|
else {
|
|
1047
|
-
console.log(
|
|
1070
|
+
console.log(palette.ok(`✓ Deleted user "${name}" (space left on disk)`));
|
|
1048
1071
|
}
|
|
1049
1072
|
}
|
|
1050
1073
|
catch (err) {
|
|
1051
|
-
console.error(
|
|
1074
|
+
console.error(palette.err(`✗ ${getErrorMessage(err)}`));
|
|
1052
1075
|
process.exit(1);
|
|
1053
1076
|
}
|
|
1054
1077
|
});
|
|
1078
|
+
/**
|
|
1079
|
+
* Missing REQUIRED tools are failures; a missing optional one or a skipped check
|
|
1080
|
+
* is just absence, so it stays muted rather than shouting red at everyone
|
|
1081
|
+
* without LibreOffice installed.
|
|
1082
|
+
*/
|
|
1083
|
+
function dependencyTone(result) {
|
|
1084
|
+
if (result.status === 'ok')
|
|
1085
|
+
return 'ok';
|
|
1086
|
+
if (result.status === 'skipped')
|
|
1087
|
+
return 'idle';
|
|
1088
|
+
return result.required ? 'err' : 'idle';
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* The colorize hook `dependency-report.ts` was written for. Versions stay in the
|
|
1092
|
+
* default color (they are data, not a verdict); everything that IS a verdict is
|
|
1093
|
+
* painted, and the supporting detail is muted so the glyph column reads first.
|
|
1094
|
+
*/
|
|
1095
|
+
const DOCTOR_STYLE = {
|
|
1096
|
+
title: (text) => palette.emph(text),
|
|
1097
|
+
heading: (text) => palette.emph(palette.info(text)),
|
|
1098
|
+
glyph: (result, glyph) => tint(dependencyTone(result), glyph),
|
|
1099
|
+
label: (text) => text,
|
|
1100
|
+
status: (result, text) => (result.status === 'ok' ? text : tint(dependencyTone(result), text)),
|
|
1101
|
+
path: (text) => palette.muted(text),
|
|
1102
|
+
meta: (text) => palette.muted(text),
|
|
1103
|
+
summary: (text) => palette.emph(text),
|
|
1104
|
+
};
|
|
1055
1105
|
program
|
|
1056
1106
|
.command('doctor')
|
|
1057
1107
|
.alias('check-deps')
|
|
@@ -1072,10 +1122,11 @@ program
|
|
|
1072
1122
|
: DEPENDENCY_REGISTRY;
|
|
1073
1123
|
const results = checkAll(registry, host);
|
|
1074
1124
|
if (options.json) {
|
|
1125
|
+
// Raw JSON, never styled: this output is parsed, not read.
|
|
1075
1126
|
console.log(JSON.stringify(renderJson(results, host.environment), null, 2));
|
|
1076
1127
|
}
|
|
1077
1128
|
else {
|
|
1078
|
-
console.log(renderTable(results, host.environment));
|
|
1129
|
+
console.log(renderTable(results, host.environment, DOCTOR_STYLE));
|
|
1079
1130
|
}
|
|
1080
1131
|
process.exit(computeExitCode(results));
|
|
1081
1132
|
});
|