maqcli 0.6.2 → 0.6.3
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/dist/core/launcher.js +1 -1
- package/dist/core/update.d.ts +32 -15
- package/dist/core/update.js +41 -12
- package/dist/index.js +1 -1
- package/dist/server/daemon.js +1 -1
- package/package.json +1 -1
package/dist/core/launcher.js
CHANGED
|
@@ -353,7 +353,7 @@ async function connectMobile(rl) {
|
|
|
353
353
|
async function launchUi(authKey) {
|
|
354
354
|
// Reuse the daemon; open its landing page. Import lazily to avoid a cycle.
|
|
355
355
|
const { createDaemon } = await import("../server/daemon.js");
|
|
356
|
-
const daemon = createDaemon({ token: authKey, version: "0.6.
|
|
356
|
+
const daemon = createDaemon({ token: authKey, version: "0.6.3" });
|
|
357
357
|
try {
|
|
358
358
|
const { host, port } = await daemon.listen();
|
|
359
359
|
const url = `http://${host}:${port}/`;
|
package/dist/core/update.d.ts
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
* instead of waiting on it. By the time npm actually touches the shim files,
|
|
25
25
|
* the process that used to hold them has already terminated. This is the same
|
|
26
26
|
* pattern self-updating CLIs (rustup, deno, bun) use on Windows.
|
|
27
|
+
*
|
|
28
|
+
* THE SEPARATE "CAN'T EVEN LAUNCH NPM" PROBLEM AND ITS FIX
|
|
29
|
+
* ------------------------------------------------------------
|
|
30
|
+
* `child_process.spawn(cmd, args, {shell:false})` — which execSafe always
|
|
31
|
+
* uses, deliberately, to avoid shell-injection — cannot execute `npm`
|
|
32
|
+
* directly on Windows AT ALL, regardless of which name you pass. The `npm` on
|
|
33
|
+
* PATH there is `npm.cmd`, a batch-file shim, and Windows can only run batch
|
|
34
|
+
* files through a command interpreter; spawning it as a bare child process
|
|
35
|
+
* throws ENOENT (name not found as-is) or EINVAL (found but not directly
|
|
36
|
+
* executable), never succeeds. The fix is `runNpm()` below: on Windows it
|
|
37
|
+
* routes through `cmd.exe /c "npm ..."` (still shell:false at the
|
|
38
|
+
* child_process level — cmd.exe is the fixed, trusted executable being
|
|
39
|
+
* spawned; the args passed to IT are our own literal strings, never
|
|
40
|
+
* unsanitized input, so this does not reopen the shell-injection risk
|
|
41
|
+
* execSafe exists to close).
|
|
27
42
|
*/
|
|
28
43
|
import { spawn } from "node:child_process";
|
|
29
44
|
export interface VersionInfo {
|
|
@@ -38,21 +53,23 @@ export declare function isNewer(current: string, latest: string): boolean;
|
|
|
38
53
|
export declare function fetchLatestVersion(registryUrl?: string, timeoutMs?: number): Promise<string>;
|
|
39
54
|
/** Compare the running version against the registry; never throws. */
|
|
40
55
|
export declare function checkForUpdate(currentVersion: string): Promise<VersionInfo>;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Run an npm subcommand safely and portably. See the module doc's second
|
|
58
|
+
* section for why this can't just be `execSafe("npm", args)` on Windows.
|
|
59
|
+
*/
|
|
60
|
+
export declare function runNpm(args: string[], opts?: {
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
code: number | null;
|
|
64
|
+
stdout: string;
|
|
65
|
+
stderr: string;
|
|
66
|
+
}>;
|
|
50
67
|
/**
|
|
51
68
|
* Build the OS-appropriate command to run `npm install -g maqcli@<version>`
|
|
52
69
|
* fully detached from the current process (see module doc for why). On
|
|
53
|
-
* Windows this wraps npm in a new cmd.exe
|
|
54
|
-
*
|
|
55
|
-
*
|
|
70
|
+
* Windows this wraps npm in a new cmd.exe (which is also how the shim gets
|
|
71
|
+
* resolved at all — see runNpm above); on POSIX, a `sh -c` one-liner gives us
|
|
72
|
+
* the same redirection-to-a-log-file behavior.
|
|
56
73
|
*/
|
|
57
74
|
export declare function detachedInstallCommand(version: string, logPath: string, platform?: NodeJS.Platform): {
|
|
58
75
|
cmd: string;
|
|
@@ -69,7 +86,7 @@ export interface UpdateOutcome {
|
|
|
69
86
|
ok: boolean;
|
|
70
87
|
message: string;
|
|
71
88
|
version?: VersionInfo;
|
|
72
|
-
/** Set when the update was launched detached
|
|
89
|
+
/** Set when the update was launched detached (the default path). */
|
|
73
90
|
detachedPid?: number;
|
|
74
91
|
/** Path a caller can inspect afterward to see the detached install's own npm output. */
|
|
75
92
|
logPath?: string;
|
|
@@ -86,8 +103,8 @@ export interface UpdateOutcome {
|
|
|
86
103
|
* alive, still holding the lock, for the whole duration).
|
|
87
104
|
*
|
|
88
105
|
* `opts.wait: true` (used by tests, and by --wait for a CI/non-interactive
|
|
89
|
-
* caller that can tolerate the lock risk) runs synchronously
|
|
90
|
-
*
|
|
106
|
+
* caller that can tolerate the lock risk) runs synchronously instead, so the
|
|
107
|
+
* outcome is available in the same process.
|
|
91
108
|
*/
|
|
92
109
|
export declare function performUpdate(currentVersion: string, opts?: {
|
|
93
110
|
checkOnly?: boolean;
|
package/dist/core/update.js
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
* instead of waiting on it. By the time npm actually touches the shim files,
|
|
25
25
|
* the process that used to hold them has already terminated. This is the same
|
|
26
26
|
* pattern self-updating CLIs (rustup, deno, bun) use on Windows.
|
|
27
|
+
*
|
|
28
|
+
* THE SEPARATE "CAN'T EVEN LAUNCH NPM" PROBLEM AND ITS FIX
|
|
29
|
+
* ------------------------------------------------------------
|
|
30
|
+
* `child_process.spawn(cmd, args, {shell:false})` — which execSafe always
|
|
31
|
+
* uses, deliberately, to avoid shell-injection — cannot execute `npm`
|
|
32
|
+
* directly on Windows AT ALL, regardless of which name you pass. The `npm` on
|
|
33
|
+
* PATH there is `npm.cmd`, a batch-file shim, and Windows can only run batch
|
|
34
|
+
* files through a command interpreter; spawning it as a bare child process
|
|
35
|
+
* throws ENOENT (name not found as-is) or EINVAL (found but not directly
|
|
36
|
+
* executable), never succeeds. The fix is `runNpm()` below: on Windows it
|
|
37
|
+
* routes through `cmd.exe /c "npm ..."` (still shell:false at the
|
|
38
|
+
* child_process level — cmd.exe is the fixed, trusted executable being
|
|
39
|
+
* spawned; the args passed to IT are our own literal strings, never
|
|
40
|
+
* unsanitized input, so this does not reopen the shell-injection risk
|
|
41
|
+
* execSafe exists to close).
|
|
27
42
|
*/
|
|
28
43
|
import { spawn } from "node:child_process";
|
|
29
44
|
import { tmpdir } from "node:os";
|
|
@@ -66,17 +81,30 @@ export async function checkForUpdate(currentVersion) {
|
|
|
66
81
|
return { current: currentVersion, latest: null, upToDate: true, error: e.message };
|
|
67
82
|
}
|
|
68
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Run an npm subcommand safely and portably. See the module doc's second
|
|
86
|
+
* section for why this can't just be `execSafe("npm", args)` on Windows.
|
|
87
|
+
*/
|
|
88
|
+
export async function runNpm(args, opts = {}) {
|
|
89
|
+
if (process.platform === "win32") {
|
|
90
|
+
// Every element here is our own literal string (package name, flags) —
|
|
91
|
+
// never unsanitized user input — so building one cmd.exe command line is
|
|
92
|
+
// safe. Defensively quote anything with whitespace anyway.
|
|
93
|
+
const cmdLine = ["npm", ...args].map((a) => (/\s/.test(a) ? `"${a}"` : a)).join(" ");
|
|
94
|
+
return execSafe("cmd.exe", ["/c", cmdLine], opts);
|
|
95
|
+
}
|
|
96
|
+
return execSafe("npm", args, opts);
|
|
97
|
+
}
|
|
69
98
|
/**
|
|
70
99
|
* Build the OS-appropriate command to run `npm install -g maqcli@<version>`
|
|
71
100
|
* fully detached from the current process (see module doc for why). On
|
|
72
|
-
* Windows this wraps npm in a new cmd.exe
|
|
73
|
-
*
|
|
74
|
-
*
|
|
101
|
+
* Windows this wraps npm in a new cmd.exe (which is also how the shim gets
|
|
102
|
+
* resolved at all — see runNpm above); on POSIX, a `sh -c` one-liner gives us
|
|
103
|
+
* the same redirection-to-a-log-file behavior.
|
|
75
104
|
*/
|
|
76
105
|
export function detachedInstallCommand(version, logPath, platform = process.platform) {
|
|
77
106
|
const pkg = `maqcli@${version}`;
|
|
78
107
|
if (platform === "win32") {
|
|
79
|
-
// /c runs and closes; redirection captures npm's own output for later inspection.
|
|
80
108
|
return { cmd: "cmd.exe", args: ["/c", `npm install -g ${pkg} > "${logPath}" 2>&1`] };
|
|
81
109
|
}
|
|
82
110
|
return { cmd: "sh", args: ["-c", `npm install -g ${pkg} > '${logPath}' 2>&1`] };
|
|
@@ -108,8 +136,8 @@ export function launchDetachedUpdate(version, logPath, spawner = spawn) {
|
|
|
108
136
|
* alive, still holding the lock, for the whole duration).
|
|
109
137
|
*
|
|
110
138
|
* `opts.wait: true` (used by tests, and by --wait for a CI/non-interactive
|
|
111
|
-
* caller that can tolerate the lock risk) runs synchronously
|
|
112
|
-
*
|
|
139
|
+
* caller that can tolerate the lock risk) runs synchronously instead, so the
|
|
140
|
+
* outcome is available in the same process.
|
|
113
141
|
*/
|
|
114
142
|
export async function performUpdate(currentVersion, opts = {}) {
|
|
115
143
|
const version = await checkForUpdate(currentVersion);
|
|
@@ -134,11 +162,12 @@ export async function performUpdate(currentVersion, opts = {}) {
|
|
|
134
162
|
logPath,
|
|
135
163
|
};
|
|
136
164
|
}
|
|
137
|
-
// --wait / test path: run synchronously in this process
|
|
138
|
-
// can hit the file-lock failure
|
|
139
|
-
// the thing being replaced; it's
|
|
140
|
-
// runner, not as the default
|
|
141
|
-
|
|
165
|
+
// --wait / test path: run synchronously in this process via runNpm (or an
|
|
166
|
+
// injected runner). On Windows this can still hit the file-lock failure
|
|
167
|
+
// mode described above if `maq` itself is the thing being replaced; it's
|
|
168
|
+
// kept for CI and for tests with a stubbed runner, not as the default
|
|
169
|
+
// interactive experience.
|
|
170
|
+
const runner = opts.runner ?? ((_cmd, args) => runNpm(args, { timeoutMs: 120000 }));
|
|
142
171
|
const outcome = await runner("npm", ["install", "-g", `maqcli@${latest}`]);
|
|
143
172
|
if (outcome.code !== 0) {
|
|
144
173
|
return {
|
|
@@ -171,7 +200,7 @@ export async function readUpdateLog(logPath) {
|
|
|
171
200
|
* "what did we ask for") — this is what confirms a detached update landed,
|
|
172
201
|
* independent of which process/terminal you're asking from.
|
|
173
202
|
*/
|
|
174
|
-
export async function installedGlobalVersion(runner = (
|
|
203
|
+
export async function installedGlobalVersion(runner = (_cmd, args) => runNpm(args, { timeoutMs: 15000 })) {
|
|
175
204
|
const outcome = await runner("npm", ["list", "-g", "maqcli", "--depth=0", "--json"]);
|
|
176
205
|
if (outcome.code !== 0)
|
|
177
206
|
return null;
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import { runOrchestration } from "./core/orchestrator.js";
|
|
|
46
46
|
import { performUpdate, installedGlobalVersion } from "./core/update.js";
|
|
47
47
|
import { checkProtectedPath, scanForInjection, securityLog, securityRules, } from "./core/security.js";
|
|
48
48
|
import { readFileSync, statSync } from "node:fs";
|
|
49
|
-
const VERSION = "0.6.
|
|
49
|
+
const VERSION = "0.6.3";
|
|
50
50
|
async function main(argv) {
|
|
51
51
|
const [command, ...rest] = argv;
|
|
52
52
|
switch (command) {
|
package/dist/server/daemon.js
CHANGED
|
@@ -52,7 +52,7 @@ export function createDaemon(opts = {}) {
|
|
|
52
52
|
const host = opts.host ?? process.env.MAQ_HOST ?? "127.0.0.1";
|
|
53
53
|
const port = opts.port ?? Number(process.env.MAQ_PORT ?? 7717);
|
|
54
54
|
const token = opts.token ?? process.env.MAQ_TOKEN ?? generateToken();
|
|
55
|
-
const version = opts.version ?? "0.6.
|
|
55
|
+
const version = opts.version ?? "0.6.3";
|
|
56
56
|
const corsOrigin = opts.corsOrigin ?? process.env.MAQ_CORS_ORIGIN;
|
|
57
57
|
const registry = opts.registry ?? new SessionRegistry();
|
|
58
58
|
const interactive = new InteractiveRegistry();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maqcli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "MAQ master orchestrator — a token-efficient, agent-agnostic supervisor CLI that sits on top of any worker CLI (AI or not) via a Scout -> Plan -> Execute -> Verify pipeline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|