maqcli 0.6.4 → 0.6.6
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 +21 -5
- package/dist/core/update.js +54 -9
- 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.6" });
|
|
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
|
@@ -66,22 +66,37 @@ export declare function runNpm(args: string[], opts?: {
|
|
|
66
66
|
}>;
|
|
67
67
|
/**
|
|
68
68
|
* Build the OS-appropriate command to run `npm install -g maqcli@<version>`
|
|
69
|
-
* fully detached from the current process (see module doc for why).
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* the
|
|
69
|
+
* fully detached from the current process (see module doc for why).
|
|
70
|
+
*
|
|
71
|
+
* On Windows, `cmd.exe /c "npm install..."` alone is NOT enough: even with
|
|
72
|
+
* `detached: true` + `.unref()` on the Node side, the child stays tied to the
|
|
73
|
+
* parent's console/job object, and it dies the moment the parent's console
|
|
74
|
+
* closes (a long-standing Node behavior on Windows — nodejs/node#5614). The
|
|
75
|
+
* fix is to have cmd.exe launch the real work via the `start` builtin, which
|
|
76
|
+
* creates a genuinely independent process outside the parent's job object.
|
|
77
|
+
*
|
|
78
|
+
* `start`'s own command-line parsing is notoriously fragile with nested
|
|
79
|
+
* quotes (redirection operators and quoted paths inside a quoted inner
|
|
80
|
+
* command reliably break it). Rather than fight cmd.exe's quoting rules, we
|
|
81
|
+
* write the actual work to a small, disposable .bat/.sh script on disk and
|
|
82
|
+
* have `start`/the shell launch THAT — no nested quoting at all.
|
|
73
83
|
*/
|
|
74
84
|
export declare function detachedInstallCommand(version: string, logPath: string, platform?: NodeJS.Platform): {
|
|
75
85
|
cmd: string;
|
|
76
86
|
args: string[];
|
|
87
|
+
scriptPath: string;
|
|
88
|
+
scriptContent: string;
|
|
89
|
+
cleanupPath?: string;
|
|
90
|
+
cleanupContent?: string;
|
|
77
91
|
};
|
|
78
92
|
/**
|
|
79
93
|
* Launch the update fully detached and return immediately — the caller
|
|
80
94
|
* (cmdUpdate) should print guidance and exit right away rather than await
|
|
81
95
|
* anything else, which is what avoids the Windows file-lock failure mode.
|
|
82
96
|
* `spawner` is injectable for tests so nothing is actually spawned there.
|
|
97
|
+
* `writer` is injectable so tests don't touch the real filesystem either.
|
|
83
98
|
*/
|
|
84
|
-
export declare function launchDetachedUpdate(version: string, logPath: string, spawner?: typeof spawn): number | undefined;
|
|
99
|
+
export declare function launchDetachedUpdate(version: string, logPath: string, spawner?: typeof spawn, writer?: (path: string, content: string) => void): number | undefined;
|
|
85
100
|
export interface UpdateOutcome {
|
|
86
101
|
ok: boolean;
|
|
87
102
|
message: string;
|
|
@@ -116,6 +131,7 @@ export declare function performUpdate(currentVersion: string, opts?: {
|
|
|
116
131
|
stderr: string;
|
|
117
132
|
}>;
|
|
118
133
|
spawner?: typeof spawn;
|
|
134
|
+
writer?: (path: string, content: string) => void;
|
|
119
135
|
logPath?: string;
|
|
120
136
|
}): Promise<UpdateOutcome>;
|
|
121
137
|
/**
|
package/dist/core/update.js
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
import { spawn } from "node:child_process";
|
|
44
44
|
import { tmpdir } from "node:os";
|
|
45
45
|
import { join } from "node:path";
|
|
46
|
+
import { writeFileSync, chmodSync } from "node:fs";
|
|
46
47
|
import { execSafe } from "./exec.js";
|
|
47
48
|
/** Parse "x.y.z" into a comparable tuple; non-numeric parts sort as 0. */
|
|
48
49
|
function parseSemver(v) {
|
|
@@ -97,26 +98,68 @@ export async function runNpm(args, opts = {}) {
|
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
100
|
* Build the OS-appropriate command to run `npm install -g maqcli@<version>`
|
|
100
|
-
* fully detached from the current process (see module doc for why).
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* the
|
|
101
|
+
* fully detached from the current process (see module doc for why).
|
|
102
|
+
*
|
|
103
|
+
* On Windows, `cmd.exe /c "npm install..."` alone is NOT enough: even with
|
|
104
|
+
* `detached: true` + `.unref()` on the Node side, the child stays tied to the
|
|
105
|
+
* parent's console/job object, and it dies the moment the parent's console
|
|
106
|
+
* closes (a long-standing Node behavior on Windows — nodejs/node#5614). The
|
|
107
|
+
* fix is to have cmd.exe launch the real work via the `start` builtin, which
|
|
108
|
+
* creates a genuinely independent process outside the parent's job object.
|
|
109
|
+
*
|
|
110
|
+
* `start`'s own command-line parsing is notoriously fragile with nested
|
|
111
|
+
* quotes (redirection operators and quoted paths inside a quoted inner
|
|
112
|
+
* command reliably break it). Rather than fight cmd.exe's quoting rules, we
|
|
113
|
+
* write the actual work to a small, disposable .bat/.sh script on disk and
|
|
114
|
+
* have `start`/the shell launch THAT — no nested quoting at all.
|
|
104
115
|
*/
|
|
105
116
|
export function detachedInstallCommand(version, logPath, platform = process.platform) {
|
|
106
117
|
const pkg = `maqcli@${version}`;
|
|
107
118
|
if (platform === "win32") {
|
|
108
|
-
|
|
119
|
+
const scriptPath = join(tmpdir(), `maq-update-${Date.now()}.bat`);
|
|
120
|
+
const cleanupPath = join(tmpdir(), `maq-update-cleanup-${Date.now()}.bat`);
|
|
121
|
+
// The install script does NOT try to delete itself — a .bat can't
|
|
122
|
+
// reliably remove the file cmd.exe is actively interpreting. Instead it
|
|
123
|
+
// launches this second, separate cleanup script (detached, its own
|
|
124
|
+
// process) once npm finishes; THAT script deletes both files after a
|
|
125
|
+
// short delay, by which point neither is open by anything.
|
|
126
|
+
const scriptContent = [
|
|
127
|
+
"@echo off",
|
|
128
|
+
`npm install -g ${pkg} > "${logPath}" 2>&1`,
|
|
129
|
+
`start "" /min "${cleanupPath}"`,
|
|
130
|
+
].join("\r\n");
|
|
131
|
+
const cleanupContent = [
|
|
132
|
+
"@echo off",
|
|
133
|
+
"ping -n 2 127.0.0.1 >nul",
|
|
134
|
+
`del "${scriptPath}"`,
|
|
135
|
+
`del "%~f0"`,
|
|
136
|
+
].join("\r\n");
|
|
137
|
+
return { cmd: "cmd.exe", args: ["/c", "start", "", "/min", scriptPath], scriptPath, scriptContent, cleanupPath, cleanupContent };
|
|
109
138
|
}
|
|
110
|
-
|
|
139
|
+
const scriptPath = join(tmpdir(), `maq-update-${Date.now()}.sh`);
|
|
140
|
+
const scriptContent = ["#!/bin/sh", `npm install -g ${pkg} > '${logPath}' 2>&1`, `rm -f "$0"`].join("\n");
|
|
141
|
+
return { cmd: "sh", args: [scriptPath], scriptPath, scriptContent };
|
|
111
142
|
}
|
|
112
143
|
/**
|
|
113
144
|
* Launch the update fully detached and return immediately — the caller
|
|
114
145
|
* (cmdUpdate) should print guidance and exit right away rather than await
|
|
115
146
|
* anything else, which is what avoids the Windows file-lock failure mode.
|
|
116
147
|
* `spawner` is injectable for tests so nothing is actually spawned there.
|
|
148
|
+
* `writer` is injectable so tests don't touch the real filesystem either.
|
|
117
149
|
*/
|
|
118
|
-
export function launchDetachedUpdate(version, logPath, spawner = spawn) {
|
|
119
|
-
const { cmd, args } = detachedInstallCommand(version, logPath);
|
|
150
|
+
export function launchDetachedUpdate(version, logPath, spawner = spawn, writer = (path, content) => writeFileSync(path, content, "utf8")) {
|
|
151
|
+
const { cmd, args, scriptPath, scriptContent, cleanupPath, cleanupContent } = detachedInstallCommand(version, logPath);
|
|
152
|
+
writer(scriptPath, scriptContent);
|
|
153
|
+
if (cleanupPath && cleanupContent)
|
|
154
|
+
writer(cleanupPath, cleanupContent);
|
|
155
|
+
if (process.platform !== "win32") {
|
|
156
|
+
try {
|
|
157
|
+
chmodSync(scriptPath, 0o755);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* best-effort */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
120
163
|
const child = spawner(cmd, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
121
164
|
child.on("error", () => {
|
|
122
165
|
/* best-effort; the log file (or its absence) is the source of truth */
|
|
@@ -153,7 +196,9 @@ export async function performUpdate(currentVersion, opts = {}) {
|
|
|
153
196
|
const latest = version.latest;
|
|
154
197
|
if (!opts.wait) {
|
|
155
198
|
const logPath = opts.logPath ?? defaultLogPath();
|
|
156
|
-
const pid =
|
|
199
|
+
const pid = opts.writer
|
|
200
|
+
? launchDetachedUpdate(latest, logPath, opts.spawner, opts.writer)
|
|
201
|
+
: launchDetachedUpdate(latest, logPath, opts.spawner);
|
|
157
202
|
return {
|
|
158
203
|
ok: true,
|
|
159
204
|
message: `update to v${latest} launched in the background (detached, avoids file locks). Open a new terminal in a few seconds to pick it up.`,
|
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.6";
|
|
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.6";
|
|
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.6",
|
|
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": {
|