march-cli 0.1.21 → 0.1.22
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/bin/march.mjs +13 -13
- package/package.json +43 -43
- package/src/agent/command-exec-tool.mjs +172 -168
- package/src/agent/context-stats-tool.mjs +57 -57
- package/src/agent/editing/diff-apply.mjs +28 -28
- package/src/agent/editing/diff-format.mjs +57 -57
- package/src/agent/editing/lsp-report.mjs +69 -69
- package/src/agent/file-edit-tool.mjs +262 -262
- package/src/agent/file-tools/read-file-tool.mjs +112 -112
- package/src/agent/file-tools/read-image-tool.mjs +76 -76
- package/src/agent/model-payload-dumper.mjs +208 -208
- package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
- package/src/agent/provider/payload-messages.mjs +138 -138
- package/src/agent/runner/codex-large-context-guard.mjs +87 -87
- package/src/agent/runner/codex-transport-compression.mjs +180 -180
- package/src/agent/runner/codex-transport-debug.mjs +113 -113
- package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
- package/src/agent/runner/fast-model.mjs +36 -36
- package/src/agent/runner/runner-cleanup.mjs +12 -12
- package/src/agent/runner/runner-init.mjs +15 -15
- package/src/agent/runner/runner-session-state.mjs +40 -40
- package/src/agent/runner/runner-utils.mjs +24 -24
- package/src/agent/runner.mjs +299 -299
- package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
- package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
- package/src/agent/runtime/remote-runner-client.mjs +73 -73
- package/src/agent/runtime/remote-ui-client.mjs +20 -20
- package/src/agent/runtime/runner-ipc-target.mjs +125 -125
- package/src/agent/runtime/runner-process-client.mjs +47 -47
- package/src/agent/runtime/runner-process-entry.mjs +11 -11
- package/src/agent/runtime/runner-process-factory.mjs +108 -108
- package/src/agent/runtime/runner-runtime-host.mjs +79 -79
- package/src/agent/runtime/runtime-factory.mjs +42 -42
- package/src/agent/runtime/runtime-host.mjs +34 -34
- package/src/agent/runtime/ui-event-bridge.mjs +95 -95
- package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
- package/src/agent/screen-tools/screen-tool.mjs +49 -49
- package/src/agent/screen-tools/windows-screen.mjs +133 -133
- package/src/agent/session/session-auto-name.mjs +41 -41
- package/src/agent/session/session-binding.mjs +12 -12
- package/src/agent/session/session-options.mjs +47 -47
- package/src/agent/tool-names.mjs +1 -1
- package/src/agent/tool-result.mjs +3 -3
- package/src/agent/tool-summary.mjs +112 -112
- package/src/agent/tools.mjs +58 -58
- package/src/agent/turn/turn-events.mjs +111 -111
- package/src/agent/turn/turn-logging.mjs +30 -30
- package/src/agent/turn/turn-runner.mjs +196 -196
- package/src/agent/vision-capability.mjs +14 -14
- package/src/auth/login-command.mjs +90 -90
- package/src/auth/storage.mjs +34 -34
- package/src/cli/args.mjs +79 -79
- package/src/cli/commands/copy-command.mjs +87 -87
- package/src/cli/commands/export-command.mjs +206 -206
- package/src/cli/commands/extensions-command.mjs +53 -53
- package/src/cli/commands/help-command.mjs +7 -7
- package/src/cli/commands/model-command.mjs +141 -141
- package/src/cli/commands/paste-image-command.mjs +43 -43
- package/src/cli/commands/provider-command.mjs +59 -59
- package/src/cli/commands/status-command.mjs +194 -194
- package/src/cli/commands/thinking-command.mjs +87 -87
- package/src/cli/fallback-ui.mjs +156 -156
- package/src/cli/input/attachment-tokens.mjs +20 -20
- package/src/cli/input/autocomplete.mjs +74 -106
- package/src/cli/input/external-editor.mjs +39 -39
- package/src/cli/input/file-search/index.mjs +160 -0
- package/src/cli/input/history-store.mjs +35 -35
- package/src/cli/input/image-clipboard.mjs +55 -55
- package/src/cli/input/keybinding-dispatch.mjs +76 -76
- package/src/cli/input/keybindings.mjs +96 -96
- package/src/cli/input/mode-state.mjs +43 -43
- package/src/cli/input/prompt-templates.mjs +84 -84
- package/src/cli/input/select-with-keyboard.mjs +86 -86
- package/src/cli/permissions.mjs +103 -103
- package/src/cli/repl-commands.mjs +86 -86
- package/src/cli/repl-loop.mjs +183 -183
- package/src/cli/selector-list.mjs +21 -21
- package/src/cli/session/pi-session-switch-command.mjs +41 -41
- package/src/cli/session/session-command.mjs +23 -23
- package/src/cli/session/session-list-command.mjs +68 -68
- package/src/cli/session/session-name-command.mjs +26 -26
- package/src/cli/session/session-source-command.mjs +89 -89
- package/src/cli/session/session-switch-command.mjs +1 -1
- package/src/cli/shell/shell-command.mjs +55 -55
- package/src/cli/shell/shell-drawer-controls.mjs +33 -33
- package/src/cli/shell/shell-drawer.mjs +192 -192
- package/src/cli/shell/shell-split-layout.mjs +70 -70
- package/src/cli/slash-commands.mjs +192 -192
- package/src/cli/startup/create-runtime-runner.mjs +61 -61
- package/src/cli/startup/runtime-close.mjs +23 -23
- package/src/cli/startup/startup-banner.mjs +71 -71
- package/src/cli/startup/startup-session.mjs +51 -51
- package/src/cli/status-line-updater.mjs +75 -75
- package/src/cli/tool-output.mjs +9 -9
- package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
- package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
- package/src/cli/tui/input/mouse-tracking.mjs +20 -20
- package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
- package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
- package/src/cli/tui/markdown-renderer.mjs +285 -285
- package/src/cli/tui/output/scroll-state.mjs +79 -79
- package/src/cli/tui/output/text-line-renderer.mjs +50 -50
- package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
- package/src/cli/tui/output/visible-lines.mjs +8 -8
- package/src/cli/tui/output-buffer.mjs +293 -293
- package/src/cli/tui/permission-request-ui.mjs +18 -18
- package/src/cli/tui/recall-rendering.mjs +25 -25
- package/src/cli/tui/render/render-scheduler.mjs +26 -26
- package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
- package/src/cli/tui/select/editor-select-list.mjs +111 -111
- package/src/cli/tui/selection-screen.mjs +269 -269
- package/src/cli/tui/status/retry-status.mjs +72 -72
- package/src/cli/tui/status/spinner-status.mjs +42 -42
- package/src/cli/tui/status/status-bar.mjs +225 -225
- package/src/cli/tui/syntax/highlighting.mjs +260 -260
- package/src/cli/tui/syntax/languages.mjs +91 -91
- package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
- package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
- package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
- package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
- package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
- package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
- package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
- package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
- package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
- package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
- package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
- package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
- package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
- package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
- package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
- package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
- package/src/cli/tui/tool-rendering.mjs +87 -87
- package/src/cli/tui/tui-diff-rendering.mjs +157 -157
- package/src/cli/tui/tui-handlers.mjs +111 -111
- package/src/cli/tui/tui-input-controller.mjs +61 -61
- package/src/cli/tui/ui-theme.mjs +157 -157
- package/src/cli/ui.mjs +297 -297
- package/src/config/config-json.mjs +84 -84
- package/src/config/dotenv.mjs +20 -20
- package/src/config/features.mjs +75 -75
- package/src/config/loader.mjs +143 -143
- package/src/config/settings-command.mjs +97 -97
- package/src/context/engine.mjs +198 -198
- package/src/context/injections.mjs +26 -26
- package/src/context/profiles.mjs +39 -39
- package/src/context/project-context.mjs +20 -20
- package/src/context/session-status.mjs +17 -17
- package/src/context/shell-layers.mjs +23 -23
- package/src/context/system-core/base.md +65 -65
- package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
- package/src/context/system-core/prompts/default.md +3 -3
- package/src/context/system-core.mjs +35 -35
- package/src/debug/logger.mjs +141 -141
- package/src/debug/model-context-dumper.mjs +52 -52
- package/src/extensions/discovery.mjs +40 -40
- package/src/extensions/lifecycle-adapter.mjs +210 -210
- package/src/extensions/lifecycle-manifest.mjs +69 -69
- package/src/image-gen/index.mjs +7 -7
- package/src/image-gen/provider.mjs +231 -231
- package/src/image-gen/tool.mjs +84 -84
- package/src/lsp/client.mjs +257 -257
- package/src/lsp/diagnostic-store.mjs +42 -42
- package/src/lsp/diagnostics-format.mjs +72 -72
- package/src/lsp/managed-node-server.mjs +99 -99
- package/src/lsp/path-match.mjs +10 -10
- package/src/lsp/server-definitions.mjs +188 -188
- package/src/lsp/servers.mjs +165 -165
- package/src/lsp/service.mjs +110 -110
- package/src/lsp/status-message.mjs +9 -9
- package/src/lsp/typescript-project-resolver.mjs +186 -186
- package/src/main.mjs +299 -299
- package/src/mcp/client.mjs +195 -195
- package/src/mcp/config.mjs +130 -130
- package/src/mcp/index.mjs +48 -48
- package/src/mcp/tools.mjs +98 -98
- package/src/memory/database.mjs +219 -219
- package/src/memory/glossary.mjs +124 -124
- package/src/memory/graph/graph-cascades.mjs +109 -109
- package/src/memory/graph/graph-diagnostics.mjs +73 -73
- package/src/memory/graph/graph-path-removal.mjs +50 -50
- package/src/memory/graph/graph-path-utils.mjs +17 -17
- package/src/memory/graph/graph-primitives.mjs +103 -103
- package/src/memory/graph/graph-read.mjs +159 -159
- package/src/memory/graph.mjs +282 -282
- package/src/memory/markdown/markdown-delete.mjs +23 -23
- package/src/memory/markdown/markdown-format.mjs +128 -128
- package/src/memory/markdown/markdown-recall.mjs +28 -28
- package/src/memory/markdown/ripgrep.mjs +16 -16
- package/src/memory/markdown/sqlite-index.mjs +87 -87
- package/src/memory/markdown-store.mjs +286 -286
- package/src/memory/markdown-tools.mjs +103 -103
- package/src/memory/search.mjs +142 -142
- package/src/memory/snapshot.mjs +86 -86
- package/src/memory/system-views.mjs +120 -120
- package/src/memory/tools.mjs +282 -282
- package/src/network/environment.mjs +131 -131
- package/src/notification/desktop-notifier.mjs +262 -262
- package/src/platform/open-file.mjs +28 -28
- package/src/platform/spawn-command.mjs +27 -27
- package/src/provider/accept-command.mjs +89 -89
- package/src/provider/command.mjs +21 -21
- package/src/provider/config-command.mjs +129 -129
- package/src/provider/custom-provider.mjs +113 -113
- package/src/provider/hosted-tools.mjs +111 -111
- package/src/provider/presets.mjs +72 -72
- package/src/provider/share-command.mjs +79 -79
- package/src/provider/share-payload.mjs +52 -52
- package/src/session/attachment-display.mjs +16 -16
- package/src/session/attachment-references.mjs +65 -65
- package/src/session/attachments.mjs +140 -140
- package/src/session/persist.mjs +1 -1
- package/src/session/pi-manager.mjs +34 -34
- package/src/session/session-utils.mjs +16 -16
- package/src/session/sidecar-sync.mjs +19 -19
- package/src/session/sidecar.mjs +69 -69
- package/src/session/transcript.mjs +83 -83
- package/src/session/tree.mjs +42 -42
- package/src/shell/cli-runtime.mjs +11 -11
- package/src/shell/hints.mjs +12 -12
- package/src/shell/node-pty-adapter.mjs +81 -81
- package/src/shell/runtime-state.mjs +126 -126
- package/src/shell/runtime.mjs +252 -252
- package/src/shell/screen-buffer.mjs +136 -136
- package/src/shell/tool-read.mjs +74 -74
- package/src/shell/tools.mjs +299 -299
- package/src/supergrok/actions/image-generate.mjs +60 -60
- package/src/supergrok/actions/search.mjs +78 -78
- package/src/supergrok/auth.mjs +36 -36
- package/src/supergrok/constants.mjs +18 -18
- package/src/supergrok/oauth-provider.mjs +278 -278
- package/src/supergrok/provider.mjs +35 -35
- package/src/supergrok/response.mjs +76 -76
- package/src/supergrok/tool.mjs +61 -61
- package/src/text/ansi.mjs +3 -3
- package/src/web/config-command.mjs +43 -43
- package/src/web/fetch.mjs +78 -78
- package/src/web/presets.mjs +16 -16
- package/src/web/search.mjs +83 -83
- package/src/web/tools.mjs +107 -107
package/src/debug/logger.mjs
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
const LEVELS = Object.freeze({ debug: 10, info: 20, warn: 30, error: 40, silent: 99 });
|
|
6
|
-
const REDACTED = "[redacted]";
|
|
7
|
-
const MAX_STRING_LENGTH = 2000;
|
|
8
|
-
const MAX_ARRAY_LENGTH = 50;
|
|
9
|
-
const MAX_OBJECT_KEYS = 80;
|
|
10
|
-
const SENSITIVE_KEY = /(api[-_]?key|authorization|auth|token|secret|password|cookie|credential|b64|base64|image)/i;
|
|
11
|
-
|
|
12
|
-
export function createLogger({
|
|
13
|
-
enabled = process.env.MARCH_LOG !== "0",
|
|
14
|
-
level = process.env.MARCH_LOG_LEVEL ?? "info",
|
|
15
|
-
logDir = defaultLogDir(),
|
|
16
|
-
now = () => new Date(),
|
|
17
|
-
pid = process.pid,
|
|
18
|
-
} = {}) {
|
|
19
|
-
const threshold = normalizeLevel(level);
|
|
20
|
-
const path = join(logDir, `${dateStamp(now())}-march-${pid}.jsonl`);
|
|
21
|
-
const base = { enabled: Boolean(enabled), level: levelName(threshold), path };
|
|
22
|
-
|
|
23
|
-
function write(levelNameValue, event, fields = {}) {
|
|
24
|
-
if (!base.enabled || normalizeLevel(levelNameValue) < threshold) return;
|
|
25
|
-
const entry = {
|
|
26
|
-
ts: now().toISOString(),
|
|
27
|
-
level: levelNameValue,
|
|
28
|
-
event,
|
|
29
|
-
pid,
|
|
30
|
-
...sanitize(fields),
|
|
31
|
-
};
|
|
32
|
-
try {
|
|
33
|
-
mkdirSync(logDir, { recursive: true });
|
|
34
|
-
appendFileSync(path, `${JSON.stringify(entry)}\n`, "utf8");
|
|
35
|
-
} catch {
|
|
36
|
-
// Logging must never change CLI behavior.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const logger = {
|
|
41
|
-
...base,
|
|
42
|
-
event: (event, fields) => write("info", event, fields),
|
|
43
|
-
debug: (event, fields) => write("debug", event, fields),
|
|
44
|
-
warn: (event, fields) => write("warn", event, fields),
|
|
45
|
-
error: (event, fields) => write("error", event, fields),
|
|
46
|
-
child(extraFields = {}) {
|
|
47
|
-
return createChildLogger(logger, sanitize(extraFields));
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
return logger;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function createHeartbeat({ logger, event = "heartbeat", intervalMs = 10_000, getFields = () => ({}) } = {}) {
|
|
54
|
-
if (!logger?.enabled || intervalMs <= 0) return { stop() {} };
|
|
55
|
-
const timer = setInterval(() => logger.event(event, getFields()), intervalMs);
|
|
56
|
-
timer.unref?.();
|
|
57
|
-
return {
|
|
58
|
-
stop() { clearInterval(timer); },
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function installProcessLogHandlers(logger) {
|
|
63
|
-
if (!logger?.enabled) return;
|
|
64
|
-
process.once("uncaughtException", (err) => {
|
|
65
|
-
logger.error("process.uncaughtException", { error: formatError(err) });
|
|
66
|
-
});
|
|
67
|
-
process.once("unhandledRejection", (reason) => {
|
|
68
|
-
logger.error("process.unhandledRejection", { error: formatError(reason) });
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function formatError(err) {
|
|
73
|
-
if (err instanceof Error) {
|
|
74
|
-
return {
|
|
75
|
-
name: err.name,
|
|
76
|
-
message: err.message,
|
|
77
|
-
stack: err.stack,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return { message: String(err) };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function sanitize(value, seen = new WeakSet(), key = "") {
|
|
84
|
-
if (SENSITIVE_KEY.test(key)) return REDACTED;
|
|
85
|
-
if (value == null || typeof value === "number" || typeof value === "boolean") return value;
|
|
86
|
-
if (typeof value === "string") return truncateString(value);
|
|
87
|
-
if (typeof value === "bigint") return String(value);
|
|
88
|
-
if (typeof value === "function" || typeof value === "symbol") return `[${typeof value}]`;
|
|
89
|
-
if (value instanceof Error) return sanitize(formatError(value), seen);
|
|
90
|
-
if (typeof value !== "object") return String(value);
|
|
91
|
-
if (seen.has(value)) return "[circular]";
|
|
92
|
-
seen.add(value);
|
|
93
|
-
if (Array.isArray(value)) {
|
|
94
|
-
const items = value.slice(0, MAX_ARRAY_LENGTH).map((item) => sanitize(item, seen));
|
|
95
|
-
if (value.length > MAX_ARRAY_LENGTH) items.push(`[${value.length - MAX_ARRAY_LENGTH} more items]`);
|
|
96
|
-
return items;
|
|
97
|
-
}
|
|
98
|
-
const entries = Object.entries(value).slice(0, MAX_OBJECT_KEYS);
|
|
99
|
-
const out = {};
|
|
100
|
-
for (const [entryKey, entryValue] of entries) out[entryKey] = sanitize(entryValue, seen, entryKey);
|
|
101
|
-
const omitted = Object.keys(value).length - entries.length;
|
|
102
|
-
if (omitted > 0) out.__omittedKeys = omitted;
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function createChildLogger(parent, extraFields) {
|
|
107
|
-
function withFields(fields) {
|
|
108
|
-
return { ...extraFields, ...(fields ?? {}) };
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
enabled: parent.enabled,
|
|
112
|
-
level: parent.level,
|
|
113
|
-
path: parent.path,
|
|
114
|
-
event: (event, fields) => parent.event(event, withFields(fields)),
|
|
115
|
-
debug: (event, fields) => parent.debug(event, withFields(fields)),
|
|
116
|
-
warn: (event, fields) => parent.warn(event, withFields(fields)),
|
|
117
|
-
error: (event, fields) => parent.error(event, withFields(fields)),
|
|
118
|
-
child: (fields = {}) => createChildLogger(parent, withFields(sanitize(fields))),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function defaultLogDir() {
|
|
123
|
-
return join(homedir(), ".march", "logs");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function dateStamp(now) {
|
|
127
|
-
return now.toISOString().slice(0, 10);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function truncateString(value) {
|
|
131
|
-
if (value.length <= MAX_STRING_LENGTH) return value;
|
|
132
|
-
return `${value.slice(0, MAX_STRING_LENGTH)}...[truncated ${value.length - MAX_STRING_LENGTH} chars]`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function normalizeLevel(level) {
|
|
136
|
-
return LEVELS[String(level).toLowerCase()] ?? LEVELS.info;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function levelName(value) {
|
|
140
|
-
return Object.entries(LEVELS).find(([, level]) => level === value)?.[0] ?? "info";
|
|
141
|
-
}
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const LEVELS = Object.freeze({ debug: 10, info: 20, warn: 30, error: 40, silent: 99 });
|
|
6
|
+
const REDACTED = "[redacted]";
|
|
7
|
+
const MAX_STRING_LENGTH = 2000;
|
|
8
|
+
const MAX_ARRAY_LENGTH = 50;
|
|
9
|
+
const MAX_OBJECT_KEYS = 80;
|
|
10
|
+
const SENSITIVE_KEY = /(api[-_]?key|authorization|auth|token|secret|password|cookie|credential|b64|base64|image)/i;
|
|
11
|
+
|
|
12
|
+
export function createLogger({
|
|
13
|
+
enabled = process.env.MARCH_LOG !== "0",
|
|
14
|
+
level = process.env.MARCH_LOG_LEVEL ?? "info",
|
|
15
|
+
logDir = defaultLogDir(),
|
|
16
|
+
now = () => new Date(),
|
|
17
|
+
pid = process.pid,
|
|
18
|
+
} = {}) {
|
|
19
|
+
const threshold = normalizeLevel(level);
|
|
20
|
+
const path = join(logDir, `${dateStamp(now())}-march-${pid}.jsonl`);
|
|
21
|
+
const base = { enabled: Boolean(enabled), level: levelName(threshold), path };
|
|
22
|
+
|
|
23
|
+
function write(levelNameValue, event, fields = {}) {
|
|
24
|
+
if (!base.enabled || normalizeLevel(levelNameValue) < threshold) return;
|
|
25
|
+
const entry = {
|
|
26
|
+
ts: now().toISOString(),
|
|
27
|
+
level: levelNameValue,
|
|
28
|
+
event,
|
|
29
|
+
pid,
|
|
30
|
+
...sanitize(fields),
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(logDir, { recursive: true });
|
|
34
|
+
appendFileSync(path, `${JSON.stringify(entry)}\n`, "utf8");
|
|
35
|
+
} catch {
|
|
36
|
+
// Logging must never change CLI behavior.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const logger = {
|
|
41
|
+
...base,
|
|
42
|
+
event: (event, fields) => write("info", event, fields),
|
|
43
|
+
debug: (event, fields) => write("debug", event, fields),
|
|
44
|
+
warn: (event, fields) => write("warn", event, fields),
|
|
45
|
+
error: (event, fields) => write("error", event, fields),
|
|
46
|
+
child(extraFields = {}) {
|
|
47
|
+
return createChildLogger(logger, sanitize(extraFields));
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return logger;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createHeartbeat({ logger, event = "heartbeat", intervalMs = 10_000, getFields = () => ({}) } = {}) {
|
|
54
|
+
if (!logger?.enabled || intervalMs <= 0) return { stop() {} };
|
|
55
|
+
const timer = setInterval(() => logger.event(event, getFields()), intervalMs);
|
|
56
|
+
timer.unref?.();
|
|
57
|
+
return {
|
|
58
|
+
stop() { clearInterval(timer); },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function installProcessLogHandlers(logger) {
|
|
63
|
+
if (!logger?.enabled) return;
|
|
64
|
+
process.once("uncaughtException", (err) => {
|
|
65
|
+
logger.error("process.uncaughtException", { error: formatError(err) });
|
|
66
|
+
});
|
|
67
|
+
process.once("unhandledRejection", (reason) => {
|
|
68
|
+
logger.error("process.unhandledRejection", { error: formatError(reason) });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function formatError(err) {
|
|
73
|
+
if (err instanceof Error) {
|
|
74
|
+
return {
|
|
75
|
+
name: err.name,
|
|
76
|
+
message: err.message,
|
|
77
|
+
stack: err.stack,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { message: String(err) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function sanitize(value, seen = new WeakSet(), key = "") {
|
|
84
|
+
if (SENSITIVE_KEY.test(key)) return REDACTED;
|
|
85
|
+
if (value == null || typeof value === "number" || typeof value === "boolean") return value;
|
|
86
|
+
if (typeof value === "string") return truncateString(value);
|
|
87
|
+
if (typeof value === "bigint") return String(value);
|
|
88
|
+
if (typeof value === "function" || typeof value === "symbol") return `[${typeof value}]`;
|
|
89
|
+
if (value instanceof Error) return sanitize(formatError(value), seen);
|
|
90
|
+
if (typeof value !== "object") return String(value);
|
|
91
|
+
if (seen.has(value)) return "[circular]";
|
|
92
|
+
seen.add(value);
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
const items = value.slice(0, MAX_ARRAY_LENGTH).map((item) => sanitize(item, seen));
|
|
95
|
+
if (value.length > MAX_ARRAY_LENGTH) items.push(`[${value.length - MAX_ARRAY_LENGTH} more items]`);
|
|
96
|
+
return items;
|
|
97
|
+
}
|
|
98
|
+
const entries = Object.entries(value).slice(0, MAX_OBJECT_KEYS);
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [entryKey, entryValue] of entries) out[entryKey] = sanitize(entryValue, seen, entryKey);
|
|
101
|
+
const omitted = Object.keys(value).length - entries.length;
|
|
102
|
+
if (omitted > 0) out.__omittedKeys = omitted;
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createChildLogger(parent, extraFields) {
|
|
107
|
+
function withFields(fields) {
|
|
108
|
+
return { ...extraFields, ...(fields ?? {}) };
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
enabled: parent.enabled,
|
|
112
|
+
level: parent.level,
|
|
113
|
+
path: parent.path,
|
|
114
|
+
event: (event, fields) => parent.event(event, withFields(fields)),
|
|
115
|
+
debug: (event, fields) => parent.debug(event, withFields(fields)),
|
|
116
|
+
warn: (event, fields) => parent.warn(event, withFields(fields)),
|
|
117
|
+
error: (event, fields) => parent.error(event, withFields(fields)),
|
|
118
|
+
child: (fields = {}) => createChildLogger(parent, withFields(sanitize(fields))),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function defaultLogDir() {
|
|
123
|
+
return join(homedir(), ".march", "logs");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function dateStamp(now) {
|
|
127
|
+
return now.toISOString().slice(0, 10);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function truncateString(value) {
|
|
131
|
+
if (value.length <= MAX_STRING_LENGTH) return value;
|
|
132
|
+
return `${value.slice(0, MAX_STRING_LENGTH)}...[truncated ${value.length - MAX_STRING_LENGTH} chars]`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeLevel(level) {
|
|
136
|
+
return LEVELS[String(level).toLowerCase()] ?? LEVELS.info;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function levelName(value) {
|
|
140
|
+
return Object.entries(LEVELS).find(([, level]) => level === value)?.[0] ?? "info";
|
|
141
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, extname, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
export function createModelContextDumper({ enabled = false, rootDir = "", now = () => new Date() } = {}) {
|
|
5
|
-
let sequence = 0;
|
|
6
|
-
return {
|
|
7
|
-
enabled: Boolean(enabled),
|
|
8
|
-
rootDir,
|
|
9
|
-
dump({ kind = "model", prompt = "", metadata = {} } = {}) {
|
|
10
|
-
if (!enabled) return null;
|
|
11
|
-
sequence += 1;
|
|
12
|
-
mkdirSync(rootDir, { recursive: true });
|
|
13
|
-
const timestamp = now().toISOString();
|
|
14
|
-
const filename = `${sanitizeTimestamp(timestamp)}-${String(sequence).padStart(4, "0")}-${sanitizeKind(kind)}.md`;
|
|
15
|
-
const path = join(rootDir, filename);
|
|
16
|
-
writeFileSync(path, `${formatHeader({ kind, timestamp, ...metadata })}\n\n${prompt}`, "utf8");
|
|
17
|
-
return path;
|
|
18
|
-
},
|
|
19
|
-
dumpSidecar({ sourcePath, suffix, value } = {}) {
|
|
20
|
-
if (!enabled || !sourcePath || !suffix) return null;
|
|
21
|
-
const ext = extname(sourcePath);
|
|
22
|
-
const stem = basename(sourcePath, ext);
|
|
23
|
-
const path = join(rootDir, `${stem}-${sanitizeKind(suffix)}.json`);
|
|
24
|
-
mkdirSync(rootDir, { recursive: true });
|
|
25
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
26
|
-
return path;
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function formatHeader(metadata) {
|
|
32
|
-
const lines = ["---"];
|
|
33
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
34
|
-
if (value == null || value === "") continue;
|
|
35
|
-
lines.push(`${key}: ${formatValue(value)}`);
|
|
36
|
-
}
|
|
37
|
-
lines.push("---");
|
|
38
|
-
return lines.join("\n");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function formatValue(value) {
|
|
42
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
43
|
-
return JSON.stringify(String(value));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function sanitizeTimestamp(timestamp) {
|
|
47
|
-
return timestamp.replace(/[:.]/g, "-");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function sanitizeKind(kind) {
|
|
51
|
-
return String(kind).replace(/[^a-z0-9_-]+/gi, "-").replace(/^-+|-+$/g, "") || "model";
|
|
52
|
-
}
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, extname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function createModelContextDumper({ enabled = false, rootDir = "", now = () => new Date() } = {}) {
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
return {
|
|
7
|
+
enabled: Boolean(enabled),
|
|
8
|
+
rootDir,
|
|
9
|
+
dump({ kind = "model", prompt = "", metadata = {} } = {}) {
|
|
10
|
+
if (!enabled) return null;
|
|
11
|
+
sequence += 1;
|
|
12
|
+
mkdirSync(rootDir, { recursive: true });
|
|
13
|
+
const timestamp = now().toISOString();
|
|
14
|
+
const filename = `${sanitizeTimestamp(timestamp)}-${String(sequence).padStart(4, "0")}-${sanitizeKind(kind)}.md`;
|
|
15
|
+
const path = join(rootDir, filename);
|
|
16
|
+
writeFileSync(path, `${formatHeader({ kind, timestamp, ...metadata })}\n\n${prompt}`, "utf8");
|
|
17
|
+
return path;
|
|
18
|
+
},
|
|
19
|
+
dumpSidecar({ sourcePath, suffix, value } = {}) {
|
|
20
|
+
if (!enabled || !sourcePath || !suffix) return null;
|
|
21
|
+
const ext = extname(sourcePath);
|
|
22
|
+
const stem = basename(sourcePath, ext);
|
|
23
|
+
const path = join(rootDir, `${stem}-${sanitizeKind(suffix)}.json`);
|
|
24
|
+
mkdirSync(rootDir, { recursive: true });
|
|
25
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
26
|
+
return path;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatHeader(metadata) {
|
|
32
|
+
const lines = ["---"];
|
|
33
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
34
|
+
if (value == null || value === "") continue;
|
|
35
|
+
lines.push(`${key}: ${formatValue(value)}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push("---");
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatValue(value) {
|
|
42
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
43
|
+
return JSON.stringify(String(value));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sanitizeTimestamp(timestamp) {
|
|
47
|
+
return timestamp.replace(/[:.]/g, "-");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sanitizeKind(kind) {
|
|
51
|
+
return String(kind).replace(/[^a-z0-9_-]+/gi, "-").replace(/^-+|-+$/g, "") || "model";
|
|
52
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
const EXTENSION_FILE_NAMES = new Set(["index.js", "index.ts", "index.mjs", "index.cjs"]);
|
|
5
|
-
const EXTENSION_FILE_EXTENSIONS = new Set([".js", ".ts", ".mjs", ".cjs"]);
|
|
6
|
-
|
|
7
|
-
export function discoverProjectExtensionPaths(cwd) {
|
|
8
|
-
const extensionsDir = resolve(cwd, ".march", "extensions");
|
|
9
|
-
if (!existsSync(extensionsDir)) return [];
|
|
10
|
-
|
|
11
|
-
const entries = readdirSync(extensionsDir, { withFileTypes: true })
|
|
12
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
13
|
-
const paths = [];
|
|
14
|
-
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
const entryPath = join(extensionsDir, entry.name);
|
|
17
|
-
if (entry.isFile() && hasExtensionFileExtension(entry.name)) {
|
|
18
|
-
paths.push(entryPath);
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (!entry.isDirectory()) continue;
|
|
22
|
-
|
|
23
|
-
const indexPath = findExtensionIndex(entryPath);
|
|
24
|
-
if (indexPath) paths.push(indexPath);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return paths;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function findExtensionIndex(dir) {
|
|
31
|
-
for (const fileName of EXTENSION_FILE_NAMES) {
|
|
32
|
-
const filePath = join(dir, fileName);
|
|
33
|
-
if (existsSync(filePath)) return filePath;
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function hasExtensionFileExtension(fileName) {
|
|
39
|
-
return EXTENSION_FILE_EXTENSIONS.has(fileName.slice(fileName.lastIndexOf(".")));
|
|
40
|
-
}
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const EXTENSION_FILE_NAMES = new Set(["index.js", "index.ts", "index.mjs", "index.cjs"]);
|
|
5
|
+
const EXTENSION_FILE_EXTENSIONS = new Set([".js", ".ts", ".mjs", ".cjs"]);
|
|
6
|
+
|
|
7
|
+
export function discoverProjectExtensionPaths(cwd) {
|
|
8
|
+
const extensionsDir = resolve(cwd, ".march", "extensions");
|
|
9
|
+
if (!existsSync(extensionsDir)) return [];
|
|
10
|
+
|
|
11
|
+
const entries = readdirSync(extensionsDir, { withFileTypes: true })
|
|
12
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
13
|
+
const paths = [];
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const entryPath = join(extensionsDir, entry.name);
|
|
17
|
+
if (entry.isFile() && hasExtensionFileExtension(entry.name)) {
|
|
18
|
+
paths.push(entryPath);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!entry.isDirectory()) continue;
|
|
22
|
+
|
|
23
|
+
const indexPath = findExtensionIndex(entryPath);
|
|
24
|
+
if (indexPath) paths.push(indexPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return paths;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findExtensionIndex(dir) {
|
|
31
|
+
for (const fileName of EXTENSION_FILE_NAMES) {
|
|
32
|
+
const filePath = join(dir, fileName);
|
|
33
|
+
if (existsSync(filePath)) return filePath;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasExtensionFileExtension(fileName) {
|
|
39
|
+
return EXTENSION_FILE_EXTENSIONS.has(fileName.slice(fileName.lastIndexOf(".")));
|
|
40
|
+
}
|