march-cli 0.1.6 → 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.
|
|
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": {
|
|
@@ -12,9 +12,19 @@ export async function selectWithKeyboard({ input = process.stdin, output = proce
|
|
|
12
12
|
|
|
13
13
|
let selected = 0;
|
|
14
14
|
let renderedLines = 0;
|
|
15
|
+
const maxViewport = Math.max(4, (output.rows || 24) - 2);
|
|
16
|
+
let viewportStart = 0;
|
|
17
|
+
|
|
18
|
+
const viewportEnd = () => Math.min(viewportStart + maxViewport, items.length);
|
|
19
|
+
|
|
20
|
+
const adjustViewport = () => {
|
|
21
|
+
if (selected < viewportStart) viewportStart = selected;
|
|
22
|
+
else if (selected >= viewportStart + maxViewport) viewportStart = selected - maxViewport + 1;
|
|
23
|
+
};
|
|
24
|
+
|
|
15
25
|
const render = () => {
|
|
16
26
|
if (renderedLines > 0) output.write(`\x1b[${renderedLines}F`);
|
|
17
|
-
const lines = formatSelectionList({ message, items, selected });
|
|
27
|
+
const lines = formatSelectionList({ message, items, selected, viewportStart, viewportEnd: viewportEnd(), done: false });
|
|
18
28
|
for (const line of lines) output.write(`\x1b[2K\r${line}\n`);
|
|
19
29
|
renderedLines = lines.length;
|
|
20
30
|
};
|
|
@@ -26,8 +36,8 @@ export async function selectWithKeyboard({ input = process.stdin, output = proce
|
|
|
26
36
|
if (finished) return;
|
|
27
37
|
if (key === "\u0003" || key === "\u001b") finish(null);
|
|
28
38
|
else if (key === "\r" || key === "\n") finish(items[selected].value);
|
|
29
|
-
else if (key === "\u001b[A") { selected = (selected - 1 + items.length) % items.length; render(); }
|
|
30
|
-
else if (key === "\u001b[B") { selected = (selected + 1) % items.length; render(); }
|
|
39
|
+
else if (key === "\u001b[A") { selected = (selected - 1 + items.length) % items.length; adjustViewport(); render(); }
|
|
40
|
+
else if (key === "\u001b[B") { selected = (selected + 1) % items.length; adjustViewport(); render(); }
|
|
31
41
|
}
|
|
32
42
|
};
|
|
33
43
|
const finish = (value) => {
|
|
@@ -35,26 +45,36 @@ export async function selectWithKeyboard({ input = process.stdin, output = proce
|
|
|
35
45
|
input.off("data", onData);
|
|
36
46
|
input.setRawMode(false);
|
|
37
47
|
input.pause();
|
|
38
|
-
if (renderedLines > 0)
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
|
54
|
+
// final render shows just the selected item
|
|
55
|
+
const lines = formatSelectionList({ message, items, selected, viewportStart: 0, viewportEnd: items.length, done: value != null });
|
|
56
|
+
output.write(`\x1b[2K\r${lines[0]}\n`);
|
|
57
|
+
output.write(`\x1b[2K\r ${items[selected].label}\n`);
|
|
41
58
|
resolve(value);
|
|
42
59
|
};
|
|
43
60
|
input.setRawMode(true);
|
|
44
61
|
input.resume();
|
|
45
62
|
input.on("data", onData);
|
|
63
|
+
adjustViewport();
|
|
46
64
|
render();
|
|
47
65
|
});
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
export function formatSelectionList({ message, items, selected, done = false }) {
|
|
68
|
+
export function formatSelectionList({ message, items, selected, viewportStart = 0, viewportEnd = items.length, done = false }) {
|
|
51
69
|
const hint = done ? "selected" : "↑/↓, Enter";
|
|
52
70
|
const lines = [`${message} (${hint})`];
|
|
53
|
-
|
|
71
|
+
if (viewportStart > 0) lines.push(" …");
|
|
72
|
+
for (let i = viewportStart; i < viewportEnd; i++) {
|
|
54
73
|
const marker = i === selected ? "›" : " ";
|
|
55
74
|
const label = `${marker} ${items[i].label}`;
|
|
56
75
|
lines.push(i === selected ? `\x1b[7m${label}\x1b[0m` : label);
|
|
57
76
|
}
|
|
77
|
+
if (viewportEnd < items.length) lines.push(" …");
|
|
58
78
|
return lines;
|
|
59
79
|
}
|
|
60
80
|
|
|
@@ -64,4 +84,4 @@ function readLine({ input = process.stdin, output = process.stdout, prompt }) {
|
|
|
64
84
|
rl.close();
|
|
65
85
|
resolve(answer);
|
|
66
86
|
}));
|
|
67
|
-
}
|
|
87
|
+
}
|
package/src/config/loader.mjs
CHANGED
|
@@ -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
|
+
}
|