march-cli 0.1.34 → 0.1.35

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.
Files changed (52) hide show
  1. package/package.json +12 -1
  2. package/src/agent/lifecycle/runner-lifecycle.mjs +16 -0
  3. package/src/agent/lifecycle/runtime-restart-tool.mjs +22 -0
  4. package/src/agent/runner.mjs +9 -14
  5. package/src/agent/runtime/remote-runner-client.mjs +7 -15
  6. package/src/agent/runtime/runner-ipc-target.mjs +3 -22
  7. package/src/agent/runtime/runner-process-client.mjs +101 -24
  8. package/src/agent/runtime/runner-runtime-host.mjs +2 -0
  9. package/src/agent/runtime/state/runner-state.mjs +80 -0
  10. package/src/agent/session/session-options.mjs +2 -1
  11. package/src/agent/tools.mjs +3 -1
  12. package/src/cli/args.mjs +14 -3
  13. package/src/cli/commands/catalog/visible-commands.mjs +5 -0
  14. package/src/cli/commands/help-command.mjs +1 -7
  15. package/src/cli/commands/registry/slash-command-registry.mjs +293 -0
  16. package/src/cli/input/autocomplete.mjs +2 -25
  17. package/src/cli/repl-loop.mjs +24 -41
  18. package/src/cli/slash-commands.mjs +19 -185
  19. package/src/cli/startup/app-runtime.mjs +201 -0
  20. package/src/cli/startup/configured-command.mjs +9 -0
  21. package/src/cli/startup/early-command.mjs +29 -0
  22. package/src/cli/turn/turn-input-preparer.mjs +41 -0
  23. package/src/main.mjs +47 -242
  24. package/src/web-ui/command.mjs +112 -0
  25. package/src/web-ui/dist/assets/index-BUmhnID4.css +1 -0
  26. package/src/web-ui/dist/assets/index-CtuqTjcB.js +1845 -0
  27. package/src/web-ui/dist/index.html +13 -0
  28. package/src/web-ui/index.html +12 -0
  29. package/src/web-ui/runtime-host.mjs +185 -0
  30. package/src/web-ui/server.mjs +139 -0
  31. package/src/web-ui/session-manager.mjs +109 -0
  32. package/src/web-ui/src/App.tsx +7 -0
  33. package/src/web-ui/src/components/AppShell.tsx +47 -0
  34. package/src/web-ui/src/components/Composer.tsx +47 -0
  35. package/src/web-ui/src/components/FileExplorer.tsx +46 -0
  36. package/src/web-ui/src/components/RightSidebar.tsx +70 -0
  37. package/src/web-ui/src/components/SessionTimeline.tsx +31 -0
  38. package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +109 -0
  39. package/src/web-ui/src/components/timeline/TimelineList.tsx +14 -0
  40. package/src/web-ui/src/fileTreeAdapter.ts +51 -0
  41. package/src/web-ui/src/main.tsx +11 -0
  42. package/src/web-ui/src/mockData.ts +87 -0
  43. package/src/web-ui/src/model.ts +62 -0
  44. package/src/web-ui/src/runtime/client.ts +74 -0
  45. package/src/web-ui/src/runtime/runtimeTimeline.ts +88 -0
  46. package/src/web-ui/src/runtime/useWebRuntime.ts +132 -0
  47. package/src/web-ui/src/styles/shell.css +156 -0
  48. package/src/web-ui/src/styles/tokens.css +116 -0
  49. package/src/web-ui/src/timelineAdapter.ts +43 -0
  50. package/src/web-ui/src/vite-env.d.ts +1 -0
  51. package/src/web-ui/tsconfig.json +20 -0
  52. package/src/web-ui/vite.config.mjs +11 -0
@@ -0,0 +1,201 @@
1
+ import { basename, join, resolve } from "node:path";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { createUI } from "../ui.mjs";
4
+ import { createPermissionController, MODE } from "../permissions.mjs";
5
+ import { loadKeybindings } from "../input/keybindings.mjs";
6
+ import { createInputHistoryStore } from "../input/history-store.mjs";
7
+ import { createModeState } from "../input/mode-state.mjs";
8
+ import { loadPromptTemplates } from "../input/prompt-templates.mjs";
9
+ import { createStatusLineUpdater } from "../status-line-updater.mjs";
10
+ import { wireTuiHandlers } from "../tui/tui-handlers.mjs";
11
+ import { createMarchAuthStorage } from "../../auth/storage.mjs";
12
+ import { createRuntimeRunner } from "./create-runtime-runner.mjs";
13
+ import { createCliShellRuntime } from "../../shell/cli-runtime.mjs";
14
+ import { MarkdownMemoryStore } from "../../memory/markdown-store.mjs";
15
+ import { createMarkdownMemoryTools } from "../../memory/markdown-tools.mjs";
16
+ import { discoverProjectExtensionPaths } from "../../extensions/discovery.mjs";
17
+ import { loadProjectLifecycleHookManifests } from "../../extensions/lifecycle-manifest.mjs";
18
+ import { loadOrCreateProjectId, resumeStartupSession } from "./startup-session.mjs";
19
+ import { initializeMcp } from "../../mcp/index.mjs";
20
+ import { createWebToolsFromConfig } from "../../web/tools.mjs";
21
+ import { createModelContextDumper } from "../../debug/model-context-dumper.mjs";
22
+ import { createLogger, installProcessLogHandlers } from "../../debug/logger.mjs";
23
+ import { defaultProfilePaths, ensureProfileFiles } from "../../context/profiles.mjs";
24
+ import { createDesktopTurnNotifier } from "../../notification/desktop-notifier.mjs";
25
+ import { normalizeRemoteMemorySources } from "../../memory/remote/config.mjs";
26
+ import { resolveMemoryRoot } from "../../memory/root.mjs";
27
+ import { ensureBrowserDaemon } from "../../browser/client/lifecycle.mjs";
28
+
29
+ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot, useRuntimeProcess } = {}) {
30
+ if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
31
+ await ensureBrowserDaemon({ stateRoot }).catch(() => {});
32
+
33
+ const logger = createLogger({ logDir: join(stateRoot, "logs") });
34
+ installProcessLogHandlers(logger);
35
+ logger.event("process.start", {
36
+ cwd,
37
+ argv,
38
+ version: process.version,
39
+ platform: process.platform,
40
+ logPath: logger.path,
41
+ });
42
+
43
+ const provider = args.provider ?? config.provider ?? null;
44
+ const serviceTier = config.serviceTier ?? null;
45
+ const model = args.model ?? config.model ?? null;
46
+ const extensionPaths = [
47
+ ...discoverProjectExtensionPaths(cwd),
48
+ ...args.extensions.map((extensionPath) => resolve(cwd, extensionPath)),
49
+ ];
50
+ const lifecycleManifests = loadProjectLifecycleHookManifests(cwd);
51
+ const keybindingConfig = loadKeybindings(cwd);
52
+ const promptTemplateConfig = loadPromptTemplates(cwd);
53
+ const authConfig = createMarchAuthStorage({ provider: provider ?? "deepseek", providers: config.providers, cwd });
54
+
55
+ if (!authConfig.hasAuth) {
56
+ process.stderr.write("Error: no providers configured. Run: march provider --config\n");
57
+ return { ok: false, code: 1, logger };
58
+ }
59
+
60
+ const projectMarchDir = resolve(cwd, ".march");
61
+ if (!existsSync(projectMarchDir)) mkdirSync(projectMarchDir, { recursive: true });
62
+ const inputHistoryStore = createInputHistoryStore({ path: join(projectMarchDir, "input-history.json") });
63
+ const modeState = createModeState();
64
+ const namespace = loadOrCreateProjectId(projectMarchDir);
65
+ const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
66
+ const profilePaths = defaultProfilePaths();
67
+ ensureProfileFiles(profilePaths);
68
+ const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
69
+ const remoteMemorySources = normalizeRemoteMemorySources(config);
70
+ const memoryTools = createMarkdownMemoryTools(memoryStore, { remoteSources: remoteMemorySources });
71
+ const currentProject = basename(cwd);
72
+ const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
73
+
74
+ const mcpInit = useRuntimeProcess
75
+ ? { clientManager: null, mcpTools: [], mcpInjections: [], errors: [] }
76
+ : await initializeMcp({ projectDir: cwd });
77
+ for (const { server, error } of mcpInit.errors) {
78
+ if (!args.json) process.stderr.write(`[mcp] ${server}: ${error}\n`);
79
+ }
80
+
81
+ const webTools = createWebToolsFromConfig(config);
82
+ const turnNotifier = createDesktopTurnNotifier({ enabled: Boolean(config.notifications?.turnEnd), config: config.notifications });
83
+ const permissionMode = args.permissionMode ?? MODE.BYPASS;
84
+ const permissionController = createPermissionController({ mode: permissionMode });
85
+ const usePiSessions = true;
86
+ const usePiRuntimeHost = true;
87
+ const sessionSource = "pi";
88
+ const sessionsRoot = join(projectMarchDir, "sessions");
89
+ const sessionState = {
90
+ sessionId: args.resume ?? Date.now().toString(36),
91
+ sessionDir: null,
92
+ };
93
+ sessionState.sessionDir = join(sessionsRoot, sessionState.sessionId);
94
+ const contextDumpRoot = resolve(projectMarchDir, "context-dumps", sessionState.sessionId);
95
+ const modelContextDumper = createModelContextDumper({
96
+ enabled: args.dumpContext,
97
+ rootDir: contextDumpRoot,
98
+ });
99
+
100
+ const ui = createUI({
101
+ json: args.json,
102
+ cwd,
103
+ keybindings: keybindingConfig.keybindings,
104
+ promptTemplates: promptTemplateConfig.templates,
105
+ shellRuntime,
106
+ historyStore: inputHistoryStore,
107
+ });
108
+
109
+ let turnRunning = false;
110
+ let refreshStatusBar = null;
111
+ const runnerOptions = {
112
+ cwd,
113
+ modelId: model,
114
+ provider,
115
+ serviceTier,
116
+ providers: config.providers,
117
+ config,
118
+ stateRoot,
119
+ memoryRoot,
120
+ profilePaths,
121
+ namespace,
122
+ projectMarchDir,
123
+ extensionPaths,
124
+ permissionMode,
125
+ shellRuntime: Boolean(shellRuntime),
126
+ lifecycleHooks: lifecycleManifests.hooks,
127
+ lifecycleDiagnostics: lifecycleManifests.diagnostics,
128
+ modelContextDumper: { enabled: args.dumpContext, rootDir: contextDumpRoot },
129
+ remoteMemorySources,
130
+ };
131
+
132
+ let runner;
133
+ try {
134
+ runner = await createRuntimeRunner({
135
+ useRuntimeProcess, runnerOptions, ui, memoryStore, memoryTools, shellRuntime,
136
+ mcpTools: mcpInit.mcpTools, mcpInjections: mcpInit.mcpInjections, mcpClientManager: mcpInit.clientManager, webTools,
137
+ usePiSessions, usePiRuntimeHost, authStorage: authConfig.authStorage,
138
+ permissionController, modelContextDumper, turnNotifier, logger,
139
+ refreshStatusBar: (...args) => refreshStatusBar?.(...args),
140
+ });
141
+ } catch (err) {
142
+ process.stderr.write(`Error: ${err.message}\n`);
143
+ logger.error("runtime.start_failed", { error: err });
144
+ memoryStore.close?.();
145
+ return { ok: false, code: 1, logger };
146
+ }
147
+
148
+ refreshStatusBar = createStatusLineUpdater({
149
+ ui,
150
+ runner,
151
+ sessionState,
152
+ sessionSource,
153
+ getMode: () => modeState.get(),
154
+ });
155
+ const initialContextTokens = typeof runner.estimateContextTokens === "function"
156
+ ? await runner.estimateContextTokens("")
157
+ : null;
158
+ refreshStatusBar(initialContextTokens ? { contextTokens: initialContextTokens } : undefined);
159
+
160
+ wireTuiHandlers({
161
+ ui,
162
+ runner,
163
+ sessionState,
164
+ projectMarchDir,
165
+ refreshStatusBar,
166
+ isTurnRunning: () => turnRunning,
167
+ modeState,
168
+ });
169
+
170
+ const startupResume = await resumeStartupSession({
171
+ resumeId: args.resume,
172
+ runner,
173
+ sessionState,
174
+ projectMarchDir,
175
+ ui,
176
+ });
177
+ refreshStatusBar();
178
+
179
+ return {
180
+ ok: true,
181
+ args,
182
+ cwd,
183
+ ui,
184
+ runner,
185
+ memoryStore,
186
+ currentProject,
187
+ sessionState,
188
+ sessionsRoot,
189
+ projectMarchDir,
190
+ sessionSource,
191
+ extensionPaths,
192
+ keybindingConfig,
193
+ promptTemplateConfig,
194
+ startupResume,
195
+ contextDumpRoot,
196
+ logger,
197
+ modeState,
198
+ refreshStatusBar,
199
+ setTurnRunning(value) { turnRunning = value; },
200
+ };
201
+ }
@@ -1,5 +1,6 @@
1
1
  import { runBrowserCommand } from "../../browser/cli/command.mjs";
2
2
  import { runGatewayCommand } from "../../gateway/command.mjs";
3
+ import { runWebUiCommand } from "../../web-ui/command.mjs";
3
4
 
4
5
  export async function runConfiguredCliCommand(args, { config, cwd, stateRoot }) {
5
6
  if (args.command?.name === "browser") {
@@ -13,5 +14,13 @@ export async function runConfiguredCliCommand(args, { config, cwd, stateRoot })
13
14
  if (args.command?.name === "gateway" && args.command.args?.[0] !== "run") {
14
15
  return { handled: true, code: await runGatewayCommand(args, { config, cwd }) };
15
16
  }
17
+ if (args.command?.name === "web") {
18
+ try {
19
+ return { handled: true, code: await runWebUiCommand(args, { config, cwd, stateRoot }) };
20
+ } catch (err) {
21
+ process.stderr.write(`Error: ${err.message}\n`);
22
+ return { handled: true, code: 1 };
23
+ }
24
+ }
16
25
  return { handled: false, code: null };
17
26
  }
@@ -0,0 +1,29 @@
1
+ import { homedir } from "node:os";
2
+ import { runLoginCommand } from "../../auth/login-command.mjs";
3
+ import { runProviderCommand } from "../../provider/command.mjs";
4
+ import { runWebSearchConfigCommand } from "../../web/config-command.mjs";
5
+ import { runMemoryCommand } from "../../memory/command.mjs";
6
+ import { resolveMemoryRoot } from "../../memory/root.mjs";
7
+ import { runConfiguredCliCommand } from "./configured-command.mjs";
8
+
9
+ export async function runEarlyCliCommand(args, { config, cwd, stateRoot }) {
10
+ if (args.command?.name === "login") {
11
+ try {
12
+ return { handled: true, code: await runLoginCommand({ providerId: args.command.args[0] ?? args.provider }) };
13
+ } catch (err) {
14
+ process.stderr.write(`Error: ${err.message}\n`);
15
+ return { handled: true, code: 1 };
16
+ }
17
+ }
18
+ if (args.command?.name === "provider") return { handled: true, code: await runProviderCommand(args) };
19
+ if (args.command?.name === "websearch") {
20
+ if (args.providerConfig) return { handled: true, code: await runWebSearchConfigCommand({ homeDir: homedir() }) };
21
+ process.stderr.write("Usage: march websearch --config\n");
22
+ return { handled: true, code: 1 };
23
+ }
24
+ if (args.command?.name === "memory") {
25
+ args.memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
26
+ return { handled: true, code: await runMemoryCommand(args, { homeDir: homedir() }) };
27
+ }
28
+ return await runConfiguredCliCommand(args, { config, cwd, stateRoot });
29
+ }
@@ -0,0 +1,41 @@
1
+ import { appendModeReminder } from "../input/mode-state.mjs";
2
+ import { inverse } from "../tui/ui-theme.mjs";
3
+ import { formatRecallHints } from "../../memory/markdown-store.mjs";
4
+ import { formatMessageAttachmentsForDisplay } from "../../session/attachment-display.mjs";
5
+ import { formatShellHints } from "../../shell/hints.mjs";
6
+
7
+ export function prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState = null }) {
8
+ memoryStore.beginTurn();
9
+ const engine = runner.engine ?? {};
10
+ const carryoverAlreadyRendered = engine.hasRenderedPendingAssistantRecallHints?.() ?? false;
11
+ const carryoverRecallHints = engine.takePendingAssistantRecallHints?.() ?? [];
12
+ const userRecallHints = memoryStore.recallForUser(prompt, {
13
+ currentProject,
14
+ excludedIds: engine.getRecentRecallMemoryIds?.() ?? [],
15
+ });
16
+ const modePrompt = appendModeReminder(prompt, modeState?.get?.());
17
+ const fullPrompt = appendPromptBlocks(
18
+ modePrompt,
19
+ formatRecallHints("user", userRecallHints),
20
+ formatRecallHints("assistant", carryoverRecallHints),
21
+ formatShellHints(runner.shellRuntime),
22
+ );
23
+
24
+ return {
25
+ fullPrompt,
26
+ userMessage: prompt,
27
+ runOptions: { userRecallHints, currentProject },
28
+ displayMessage: formatUserDisplayMessage(prompt),
29
+ userRecallHints,
30
+ carryoverRecallHints,
31
+ shouldRenderCarryoverRecall: carryoverRecallHints.length > 0 && !carryoverAlreadyRendered,
32
+ };
33
+ }
34
+
35
+ export function formatUserDisplayMessage(prompt) {
36
+ return `${inverse(" USER ")} ${formatMessageAttachmentsForDisplay(prompt)}`;
37
+ }
38
+
39
+ function appendPromptBlocks(prompt, ...blocks) {
40
+ return [prompt, ...blocks.filter(Boolean)].join("\n\n");
41
+ }
package/src/main.mjs CHANGED
@@ -1,46 +1,18 @@
1
1
  import { homedir } from "node:os";
2
- import { join, resolve, basename, relative } from "node:path";
3
- import { existsSync, mkdirSync } from "node:fs";
2
+ import { join, relative } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import { parseCliArgs, showHelp } from "./cli/args.mjs";
6
- import { createUI } from "./cli/ui.mjs";
7
- import { createPermissionController, MODE } from "./cli/permissions.mjs";
8
- import { loadKeybindings } from "./cli/input/keybindings.mjs";
9
- import { createInputHistoryStore } from "./cli/input/history-store.mjs";
10
- import { createModeState } from "./cli/input/mode-state.mjs";
11
- import { loadPromptTemplates } from "./cli/input/prompt-templates.mjs";
12
5
  import { runInteractiveRepl, runSingleShotPrompt } from "./cli/repl-loop.mjs";
13
6
  import { closeMarchRuntime } from "./cli/startup/runtime-close.mjs";
14
- import { createStatusLineUpdater } from "./cli/status-line-updater.mjs";
15
- import { wireTuiHandlers } from "./cli/tui/tui-handlers.mjs";
16
- import { createMarchAuthStorage } from "./auth/storage.mjs";
17
- import { runLoginCommand } from "./auth/login-command.mjs";
18
- import { createRuntimeRunner } from "./cli/startup/create-runtime-runner.mjs";
19
- import { createCliShellRuntime } from "./shell/cli-runtime.mjs";
20
- import { MarkdownMemoryStore } from "./memory/markdown-store.mjs";
21
- import { createMarkdownMemoryTools } from "./memory/markdown-tools.mjs";
7
+ import { createCliAppRuntime } from "./cli/startup/app-runtime.mjs";
8
+ import { formatStartupBanner } from "./cli/startup/startup-banner.mjs";
22
9
  import { loadDotEnv } from "./config/dotenv.mjs";
23
10
  import { loadConfig } from "./config/loader.mjs";
24
- import { discoverProjectExtensionPaths } from "./extensions/discovery.mjs";
25
- import { loadProjectLifecycleHookManifests } from "./extensions/lifecycle-manifest.mjs";
26
- import { loadOrCreateProjectId, resumeStartupSession } from "./cli/startup/startup-session.mjs";
27
- import { formatStartupBanner } from "./cli/startup/startup-banner.mjs";
28
- import { initializeMcp } from "./mcp/index.mjs";
29
- import { createWebToolsFromConfig } from "./web/tools.mjs";
30
- import { createModelContextDumper } from "./debug/model-context-dumper.mjs";
31
- import { createLogger, installProcessLogHandlers } from "./debug/logger.mjs";
32
- import { defaultProfilePaths, ensureProfileFiles } from "./context/profiles.mjs";
33
- import { runProviderCommand } from "./provider/command.mjs";
34
- import { runWebSearchConfigCommand } from "./web/config-command.mjs";
35
- import { createDesktopTurnNotifier } from "./notification/desktop-notifier.mjs";
36
11
  import { registerSuperGrokOAuthProvider } from "./supergrok/oauth-provider.mjs";
37
12
  import { installNetworkEnvironment } from "./network/environment.mjs";
38
- import { runMemoryCommand } from "./memory/command.mjs";
39
- import { normalizeRemoteMemorySources } from "./memory/remote/config.mjs";
40
- import { resolveMemoryRoot } from "./memory/root.mjs";
41
- import { runConfiguredCliCommand } from "./cli/startup/configured-command.mjs";
13
+ import { runEarlyCliCommand } from "./cli/startup/early-command.mjs";
42
14
  import { maybeRunGatewayDaemonCommand } from "./cli/startup/gateway-daemon-command.mjs";
43
- import { ensureBrowserDaemon } from "./browser/client/lifecycle.mjs";
15
+
44
16
  export async function run(argv) {
45
17
  const cwd = process.cwd();
46
18
  loadDotEnv(cwd);
@@ -56,239 +28,72 @@ export async function run(argv) {
56
28
  const stateRoot = join(homedir(), ".march");
57
29
  const useRuntimeProcess = process.env.MARCH_RUNTIME_PROCESS !== "0";
58
30
  installNetworkEnvironment(config.network);
59
- if (args.command?.name === "login") {
60
- try {
61
- return await runLoginCommand({
62
- providerId: args.command.args[0] ?? args.provider,
63
- });
64
- } catch (err) {
65
- process.stderr.write(`Error: ${err.message}\n`);
66
- return 1;
67
- }
68
- }
69
- if (args.command?.name === "provider") {
70
- return await runProviderCommand(args);
71
- }
72
- if (args.command?.name === "websearch") {
73
- if (args.providerConfig) return await runWebSearchConfigCommand({ homeDir: homedir() });
74
- process.stderr.write("Usage: march websearch --config\n");
75
- return 1;
76
- }
77
- if (args.command?.name === "memory") {
78
- args.memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
79
- return await runMemoryCommand(args, { homeDir: homedir() });
80
- }
81
- const configuredCommand = await runConfiguredCliCommand(args, { config, cwd, stateRoot });
82
- if (configuredCommand.handled) return configuredCommand.code;
83
- if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
84
- await ensureBrowserDaemon({ stateRoot }).catch(() => {});
85
- const logger = createLogger({ logDir: join(stateRoot, "logs") });
86
- installProcessLogHandlers(logger);
87
- logger.event("process.start", {
88
- cwd,
89
- argv,
90
- version: process.version,
91
- platform: process.platform,
92
- logPath: logger.path,
93
- });
94
31
 
95
- const provider = args.provider ?? config.provider ?? null;
96
- const serviceTier = config.serviceTier ?? null;
97
- const model = args.model ?? config.model ?? null;
98
- const extensionPaths = [
99
- ...discoverProjectExtensionPaths(cwd),
100
- ...args.extensions.map((extensionPath) => resolve(cwd, extensionPath)),
101
- ];
102
- const lifecycleManifests = loadProjectLifecycleHookManifests(cwd);
103
- const keybindingConfig = loadKeybindings(cwd);
104
- const promptTemplateConfig = loadPromptTemplates(cwd);
105
- const authConfig = createMarchAuthStorage({ provider: provider ?? "deepseek", providers: config.providers, cwd });
32
+ const earlyCommand = await runEarlyCliCommand(args, { config, cwd, stateRoot });
33
+ if (earlyCommand.handled) return earlyCommand.code;
106
34
 
107
- if (!authConfig.hasAuth) {
108
- process.stderr.write("Error: no providers configured. Run: march provider --config\n");
109
- return 1;
110
- }
111
-
112
- const projectMarchDir = resolve(cwd, ".march");
113
- if (!existsSync(projectMarchDir)) mkdirSync(projectMarchDir, { recursive: true });
114
- const inputHistoryStore = createInputHistoryStore({ path: join(projectMarchDir, "input-history.json") });
115
- const modeState = createModeState();
116
- const namespace = loadOrCreateProjectId(projectMarchDir);
117
- const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
118
- const profilePaths = defaultProfilePaths();
119
- ensureProfileFiles(profilePaths);
120
- const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
121
- const remoteMemorySources = normalizeRemoteMemorySources(config);
122
- const memoryTools = createMarkdownMemoryTools(memoryStore, { remoteSources: remoteMemorySources });
123
- const currentProject = basename(cwd);
124
- const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
35
+ const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot, useRuntimeProcess });
36
+ if (!app.ok) return app.code;
125
37
 
126
- const mcpInit = useRuntimeProcess
127
- ? { clientManager: null, mcpTools: [], mcpInjections: [], errors: [] }
128
- : await initializeMcp({ projectDir: cwd });
129
- const { clientManager: mcpClientManager, mcpTools, mcpInjections } = mcpInit;
130
- for (const { server, error } of mcpInit.errors) {
131
- if (args.json) {
132
- // errors will be surfaced in diagnostics via runner status
133
- } else {
134
- process.stderr.write(`[mcp] ${server}: ${error}\n`);
135
- }
136
- }
137
-
138
- const webTools = createWebToolsFromConfig(config);
139
- const turnNotifier = createDesktopTurnNotifier({ enabled: Boolean(config.notifications?.turnEnd), config: config.notifications });
140
- const permissionMode = args.permissionMode ?? MODE.BYPASS;
141
- const permissionController = createPermissionController({ mode: permissionMode });
142
- const usePiSessions = true;
143
- const usePiRuntimeHost = true;
144
- const sessionSource = "pi";
145
- const sessionsRoot = join(projectMarchDir, "sessions");
146
- const sessionState = {
147
- sessionId: args.resume ?? Date.now().toString(36),
148
- sessionDir: null,
149
- };
150
- sessionState.sessionDir = join(sessionsRoot, sessionState.sessionId);
151
- const contextDumpRoot = resolve(projectMarchDir, "context-dumps", sessionState.sessionId);
152
- const modelContextDumper = createModelContextDumper({
153
- enabled: args.dumpContext,
154
- rootDir: contextDumpRoot,
155
- });
156
-
157
- const ui = createUI({
158
- json: args.json,
159
- cwd,
160
- keybindings: keybindingConfig.keybindings,
161
- promptTemplates: promptTemplateConfig.templates,
162
- shellRuntime,
163
- historyStore: inputHistoryStore,
164
- });
165
-
166
- // Esc to abort current turn
167
- let turnRunning = false;
168
- let refreshStatusBar = null;
169
-
170
- const runnerOptions = {
171
- cwd,
172
- modelId: model,
173
- provider,
174
- serviceTier,
175
- providers: config.providers,
38
+ const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, {
176
39
  config,
177
- stateRoot,
178
- memoryRoot,
179
- profilePaths,
180
- namespace,
181
- projectMarchDir,
182
- extensionPaths,
183
- permissionMode,
184
- shellRuntime: Boolean(shellRuntime),
185
- lifecycleHooks: lifecycleManifests.hooks,
186
- lifecycleDiagnostics: lifecycleManifests.diagnostics,
187
- modelContextDumper: { enabled: args.dumpContext, rootDir: contextDumpRoot },
188
- remoteMemorySources,
189
- };
190
- const runner = await createRuntimeRunner({
191
- useRuntimeProcess,
192
- runnerOptions,
193
- ui,
194
- memoryStore,
195
- memoryTools,
196
- shellRuntime,
197
- mcpTools,
198
- mcpInjections,
199
- mcpClientManager,
200
- webTools,
201
- usePiSessions,
202
- usePiRuntimeHost,
203
- authStorage: authConfig.authStorage,
204
- permissionController,
205
- modelContextDumper,
206
- turnNotifier,
207
- logger,
208
- refreshStatusBar: (...args) => refreshStatusBar?.(...args),
209
- });
210
-
211
- refreshStatusBar = createStatusLineUpdater({
212
- ui,
213
- runner,
214
- sessionState,
215
- sessionSource,
216
- getMode: () => modeState.get(),
217
- });
218
- const initialContextTokens = typeof runner.estimateContextTokens === "function"
219
- ? await runner.estimateContextTokens("")
220
- : null;
221
- refreshStatusBar(initialContextTokens ? { contextTokens: initialContextTokens } : undefined);
222
-
223
- wireTuiHandlers({
224
- ui,
225
- runner,
226
- sessionState,
227
- projectMarchDir,
228
- refreshStatusBar,
229
- isTurnRunning: () => turnRunning,
230
- modeState,
231
- });
232
-
233
- const startupResume = await resumeStartupSession({
234
- resumeId: args.resume,
235
- runner,
236
- sessionState,
237
- projectMarchDir,
238
- ui,
40
+ cwd,
41
+ runner: app.runner,
42
+ currentProject: app.currentProject,
43
+ memoryStore: app.memoryStore,
44
+ ui: app.ui,
45
+ logger: app.logger,
239
46
  });
240
- refreshStatusBar();
241
- const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, { config, cwd, runner, currentProject, memoryStore, ui, logger });
242
47
  if (gatewayDaemonCommand.handled) return gatewayDaemonCommand.code;
243
48
 
244
49
  if (args.prompt) {
245
- turnRunning = true;
50
+ app.setTurnRunning(true);
246
51
  try {
247
52
  await runSingleShotPrompt({
248
53
  prompt: args.prompt,
249
- runner,
250
- memoryStore,
251
- currentProject,
252
- ui,
253
- sessionState,
254
- refreshStatusBar,
255
- modeState,
54
+ runner: app.runner,
55
+ memoryStore: app.memoryStore,
56
+ currentProject: app.currentProject,
57
+ ui: app.ui,
58
+ sessionState: app.sessionState,
59
+ refreshStatusBar: app.refreshStatusBar,
60
+ modeState: app.modeState,
256
61
  });
257
62
  } finally {
258
- turnRunning = false;
259
- await closeMarchRuntime({ runner, memoryStore, ui, logger, blankLine: true });
63
+ app.setTurnRunning(false);
64
+ await closeMarchRuntime({ runner: app.runner, memoryStore: app.memoryStore, ui: app.ui, logger: app.logger, blankLine: true });
260
65
  }
261
- logger.event("process.exit", { code: 0 });
66
+ app.logger.event("process.exit", { code: 0 });
262
67
  return 0;
263
68
  }
264
69
 
265
- const dumpContextPath = args.dumpContext ? relative(cwd, contextDumpRoot) : null;
266
- if (startupResume.transcriptTurns?.length > 0) ui.restoreTranscript?.(startupResume.transcriptTurns);
267
- for (const line of formatStartupBanner({ cwd, modelId: runner.engine.modelId, thinkingLevel: runner.engine.thinkingLevel, mode: modeState.get(), dumpContextPath })) ui.writeln(line);
70
+ const dumpContextPath = args.dumpContext ? relative(cwd, app.contextDumpRoot) : null;
71
+ if (app.startupResume.transcriptTurns?.length > 0) app.ui.restoreTranscript?.(app.startupResume.transcriptTurns);
72
+ for (const line of formatStartupBanner({ cwd, modelId: app.runner.engine.modelId, thinkingLevel: app.runner.engine.thinkingLevel, mode: app.modeState.get(), dumpContextPath })) app.ui.writeln(line);
268
73
  try {
269
74
  await runInteractiveRepl({
270
75
  cwd,
271
76
  args,
272
- ui,
273
- runner,
274
- memoryStore,
275
- currentProject,
276
- sessionState,
277
- sessionsRoot,
278
- projectMarchDir,
279
- sessionSource,
280
- extensionPaths,
281
- keybindingConfig,
282
- promptTemplateConfig,
283
- renderStartupBanner: () => formatStartupBanner({ cwd, modelId: runner.engine.modelId, thinkingLevel: runner.engine.thinkingLevel, mode: modeState.get(), dumpContextPath }),
284
- refreshStatusBar,
285
- setTurnRunning: (value) => { turnRunning = value; },
286
- modeState,
77
+ ui: app.ui,
78
+ runner: app.runner,
79
+ memoryStore: app.memoryStore,
80
+ currentProject: app.currentProject,
81
+ sessionState: app.sessionState,
82
+ sessionsRoot: app.sessionsRoot,
83
+ projectMarchDir: app.projectMarchDir,
84
+ sessionSource: app.sessionSource,
85
+ extensionPaths: app.extensionPaths,
86
+ keybindingConfig: app.keybindingConfig,
87
+ promptTemplateConfig: app.promptTemplateConfig,
88
+ renderStartupBanner: () => formatStartupBanner({ cwd, modelId: app.runner.engine.modelId, thinkingLevel: app.runner.engine.thinkingLevel, mode: app.modeState.get(), dumpContextPath }),
89
+ refreshStatusBar: app.refreshStatusBar,
90
+ setTurnRunning: app.setTurnRunning,
91
+ modeState: app.modeState,
287
92
  });
288
93
  } finally {
289
- await closeMarchRuntime({ runner, memoryStore, ui, logger });
94
+ await closeMarchRuntime({ runner: app.runner, memoryStore: app.memoryStore, ui: app.ui, logger: app.logger });
290
95
  }
291
- logger.event("process.exit", { code: 0 });
96
+ app.logger.event("process.exit", { code: 0 });
292
97
  return 0;
293
98
  }
294
99