march-cli 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "March CLI — terminal-native coding agent with context reconstruction",
5
5
  "type": "module",
6
6
  "main": "./src/main.mjs",
@@ -32,6 +32,7 @@
32
32
  "node-notifier": "^10.0.1",
33
33
  "node-pty": "^1.1.0",
34
34
  "typebox": "^1.0.58",
35
+ "undici": "^7.25.0",
35
36
  "web-tree-sitter": "^0.26.8"
36
37
  },
37
38
  "optionalDependencies": {
@@ -45,7 +45,12 @@ export async function selectWithKeyboard({ input = process.stdin, output = proce
45
45
  input.off("data", onData);
46
46
  input.setRawMode(false);
47
47
  input.pause();
48
- if (renderedLines > 0) output.write(`\x1b[${renderedLines}F`);
48
+ if (renderedLines > 0) {
49
+ output.write(`\x1b[${renderedLines}F`);
50
+ // clear all previously rendered lines
51
+ for (let i = 0; i < renderedLines; i++) output.write("\x1b[2K\r\n");
52
+ output.write(`\x1b[${renderedLines}F`);
53
+ }
49
54
  // final render shows just the selected item
50
55
  const lines = formatSelectionList({ message, items, selected, viewportStart: 0, viewportEnd: items.length, done: value != null });
51
56
  output.write(`\x1b[2K\r${lines[0]}\n`);
@@ -46,6 +46,7 @@ function mergeLayers(layers) {
46
46
  serviceTier: null,
47
47
  providers: {},
48
48
  webSearch: { provider: null, providers: {} },
49
+ network: { proxy: "system", ca: "system" },
49
50
  maxTurns: null,
50
51
  trimBatch: null,
51
52
  memoryRoot: null,
@@ -69,6 +70,12 @@ function mergeLayers(layers) {
69
70
  ...layer.notifications,
70
71
  };
71
72
  }
73
+ if (layer.network && typeof layer.network === "object" && !Array.isArray(layer.network)) {
74
+ result.network = {
75
+ ...result.network,
76
+ ...layer.network,
77
+ };
78
+ }
72
79
  if (layer.memoryRoot) result.memoryRoot = layer.memoryRoot;
73
80
  }
74
81
 
package/src/main.mjs CHANGED
@@ -33,6 +33,7 @@ import { runProviderConfigCommand } from "./provider/config-command.mjs";
33
33
  import { runWebSearchConfigCommand } from "./web/config-command.mjs";
34
34
  import { createDesktopTurnNotifier } from "./notification/desktop-notifier.mjs";
35
35
  import { registerSuperGrokOAuthProvider } from "./supergrok/oauth-provider.mjs";
36
+ import { installNetworkEnvironment } from "./network/environment.mjs";
36
37
 
37
38
  export async function run(argv) {
38
39
  const cwd = process.cwd();
@@ -40,12 +41,13 @@ export async function run(argv) {
40
41
  registerSuperGrokOAuthProvider();
41
42
 
42
43
  const args = parseCliArgs(argv);
43
-
44
44
  if (args.help) {
45
45
  showHelp();
46
46
  return 0;
47
47
  }
48
48
 
49
+ const config = loadConfig(cwd);
50
+ installNetworkEnvironment(config.network);
49
51
  if (args.command?.name === "login") {
50
52
  try {
51
53
  return await runLoginCommand({
@@ -67,8 +69,6 @@ export async function run(argv) {
67
69
  const stateRoot = join(homedir(), ".march");
68
70
  if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
69
71
 
70
- // Load config (CLI args override config file values)
71
- const config = loadConfig(cwd);
72
72
  const provider = args.provider ?? config.provider ?? null;
73
73
  const serviceTier = config.serviceTier ?? null;
74
74
  const model = args.model ?? config.model ?? null;
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import tls from "node:tls";
4
+ import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
5
+
6
+ export function installNetworkEnvironment(network = {}) {
7
+ const proxy = resolveProxySettings(network);
8
+ installProxyDispatcher(proxy);
9
+ const ca = installDefaultCertificates(network.ca ?? "system");
10
+ return { proxy, ca };
11
+ }
12
+
13
+ export function resolveProxySettings(network = {}, { env = process.env, platform = process.platform } = {}) {
14
+ const proxyMode = network.proxy ?? "system";
15
+ const explicitNoProxy = formatNoProxy(network.noProxy);
16
+
17
+ if (proxyMode === false || proxyMode === "none" || proxyMode === "direct") {
18
+ return { mode: "direct", httpProxy: null, httpsProxy: null, noProxy: explicitNoProxy ?? env.NO_PROXY ?? env.no_proxy ?? null };
19
+ }
20
+
21
+ if (typeof proxyMode === "string" && proxyMode.trim() && proxyMode !== "system") {
22
+ const proxy = normalizeProxyUrl(proxyMode.trim());
23
+ return { mode: "config", httpProxy: proxy, httpsProxy: proxy, noProxy: explicitNoProxy ?? env.NO_PROXY ?? env.no_proxy ?? null };
24
+ }
25
+
26
+ const envProxy = proxyFromEnv(env, explicitNoProxy);
27
+ if (envProxy.httpProxy || envProxy.httpsProxy) return { mode: "env", ...envProxy };
28
+
29
+ const systemProxy = platform === "win32" ? detectWindowsProxy() : null;
30
+ if (systemProxy?.httpProxy || systemProxy?.httpsProxy) {
31
+ return {
32
+ mode: "system",
33
+ httpProxy: systemProxy.httpProxy,
34
+ httpsProxy: systemProxy.httpsProxy,
35
+ noProxy: explicitNoProxy ?? systemProxy.noProxy ?? null,
36
+ };
37
+ }
38
+
39
+ return { mode: "direct", httpProxy: null, httpsProxy: null, noProxy: explicitNoProxy ?? null };
40
+ }
41
+
42
+ function installProxyDispatcher(proxy) {
43
+ setGlobalDispatcher(new EnvHttpProxyAgent({
44
+ httpProxy: proxy.httpProxy ?? "",
45
+ httpsProxy: proxy.httpsProxy ?? "",
46
+ noProxy: proxy.noProxy ?? "",
47
+ bodyTimeout: 0,
48
+ headersTimeout: 0,
49
+ }));
50
+ }
51
+
52
+ export function installDefaultCertificates(caConfig = "system") {
53
+ const entries = Array.isArray(caConfig) ? caConfig : [caConfig];
54
+ const wantsSystem = entries.includes("system");
55
+ const pemPaths = entries.filter((entry) => typeof entry === "string" && entry && entry !== "system");
56
+
57
+ if (!wantsSystem && pemPaths.length === 0) return { mode: "default", system: false, extraFiles: [] };
58
+ if (typeof tls.setDefaultCACertificates !== "function" || typeof tls.getCACertificates !== "function") {
59
+ return { mode: "unsupported", system: false, extraFiles: [] };
60
+ }
61
+
62
+ const certificates = [
63
+ ...tls.getCACertificates("default"),
64
+ ...(wantsSystem ? tls.getCACertificates("system") : []),
65
+ ...pemPaths.map((path) => readFileSync(path, "utf8")),
66
+ ];
67
+ tls.setDefaultCACertificates([...new Set(certificates)]);
68
+ return { mode: "installed", system: wantsSystem, extraFiles: pemPaths };
69
+ }
70
+
71
+ function proxyFromEnv(env, explicitNoProxy) {
72
+ const httpsProxy = env.HTTPS_PROXY ?? env.https_proxy ?? env.ALL_PROXY ?? env.all_proxy ?? null;
73
+ const httpProxy = env.HTTP_PROXY ?? env.http_proxy ?? env.ALL_PROXY ?? env.all_proxy ?? null;
74
+ const normalizedHttp = httpProxy ? normalizeProxyUrl(httpProxy) : null;
75
+ return {
76
+ httpProxy: normalizedHttp,
77
+ httpsProxy: httpsProxy ? normalizeProxyUrl(httpsProxy) : normalizedHttp,
78
+ noProxy: explicitNoProxy ?? env.NO_PROXY ?? env.no_proxy ?? null,
79
+ };
80
+ }
81
+
82
+ function detectWindowsProxy() {
83
+ try {
84
+ const output = execFileSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"], { encoding: "utf8", windowsHide: true });
85
+ const values = parseRegQuery(output);
86
+ if (values.ProxyEnable !== "0x1") return null;
87
+ return parseWindowsProxyServer(values.ProxyServer, values.ProxyOverride);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ function parseRegQuery(output) {
94
+ const values = {};
95
+ for (const line of output.split(/\r?\n/)) {
96
+ const match = line.trim().match(/^(\S+)\s+REG_\S+\s+(.+)$/);
97
+ if (match) values[match[1]] = match[2].trim();
98
+ }
99
+ return values;
100
+ }
101
+
102
+ function parseWindowsProxyServer(proxyServer, proxyOverride) {
103
+ if (!proxyServer) return null;
104
+ const entries = Object.fromEntries(proxyServer.split(";").map((part) => part.split("=")).filter((part) => part.length === 2));
105
+ const fallback = proxyServer.includes("=") ? null : proxyServer;
106
+ return {
107
+ httpProxy: normalizeProxyUrl(entries.http ?? fallback),
108
+ httpsProxy: normalizeProxyUrl(entries.https ?? entries.http ?? fallback),
109
+ noProxy: formatWindowsProxyOverride(proxyOverride),
110
+ };
111
+ }
112
+
113
+ function normalizeProxyUrl(value) {
114
+ if (!value) return null;
115
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `http://${value}`;
116
+ }
117
+
118
+ function formatNoProxy(value) {
119
+ if (Array.isArray(value)) return value.join(",");
120
+ if (typeof value === "string") return value;
121
+ return null;
122
+ }
123
+
124
+ function formatWindowsProxyOverride(value) {
125
+ if (!value) return null;
126
+ return value
127
+ .split(";")
128
+ .map((entry) => entry.trim())
129
+ .filter((entry) => entry && entry !== "<local>")
130
+ .join(",") || null;
131
+ }