maqcli 0.8.0 → 0.9.1
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.d.ts +10 -0
- package/dist/core/launcher.js +88 -12
- package/dist/index.js +1 -1
- package/dist/server/daemon.js +1 -1
- package/dist/server/webui.d.ts +20 -18
- package/dist/server/webui.js +523 -627
- package/package.json +1 -1
package/dist/core/launcher.d.ts
CHANGED
|
@@ -67,6 +67,16 @@ export interface OnboardingResult {
|
|
|
67
67
|
* knowledge files, so it is directly testable.
|
|
68
68
|
*/
|
|
69
69
|
export declare function applyOnboarding(choices: OnboardingChoices): OnboardingResult;
|
|
70
|
+
/** Render a 9-digit key large and spaced, boxed for terminal visibility (spec: "large terminal display"). */
|
|
71
|
+
export declare function renderKeyBox(key: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Dependency-free arrow-key-navigable menu (spec Phase 1: "arrow-key
|
|
74
|
+
* navigable, styled selection highlight" — no inquirer/blessed). Renders each
|
|
75
|
+
* option, redraws the highlighted one on ↑/↓, and resolves its index on
|
|
76
|
+
* Enter. Falls back to nothing here — callers that need non-TTY behavior
|
|
77
|
+
* should check `process.stdin.isTTY` first, same as the rest of the launcher.
|
|
78
|
+
*/
|
|
79
|
+
export declare function renderMenu(options: string[], selected: number): string;
|
|
70
80
|
/**
|
|
71
81
|
* Run the guided launcher. Returns 0 on success. In a non-interactive context
|
|
72
82
|
* (piped stdin), it prints guidance and returns without blocking.
|
package/dist/core/launcher.js
CHANGED
|
@@ -163,6 +163,68 @@ export function applyOnboarding(choices) {
|
|
|
163
163
|
return { config: cfg, knowledgePath, headroom, authKey: generateAuthKey() };
|
|
164
164
|
}
|
|
165
165
|
/* ----------------------------- interactive ----------------------------- */
|
|
166
|
+
/** Render a 9-digit key large and spaced, boxed for terminal visibility (spec: "large terminal display"). */
|
|
167
|
+
export function renderKeyBox(key) {
|
|
168
|
+
const spaced = key.split("").join(" ");
|
|
169
|
+
const width = spaced.length + 4;
|
|
170
|
+
const bar = "─".repeat(width);
|
|
171
|
+
return [`┌${bar}┐`, `│ ${spaced} │`, `└${bar}┘`].join("\n");
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Dependency-free arrow-key-navigable menu (spec Phase 1: "arrow-key
|
|
175
|
+
* navigable, styled selection highlight" — no inquirer/blessed). Renders each
|
|
176
|
+
* option, redraws the highlighted one on ↑/↓, and resolves its index on
|
|
177
|
+
* Enter. Falls back to nothing here — callers that need non-TTY behavior
|
|
178
|
+
* should check `process.stdin.isTTY` first, same as the rest of the launcher.
|
|
179
|
+
*/
|
|
180
|
+
export function renderMenu(options, selected) {
|
|
181
|
+
return options
|
|
182
|
+
.map((opt, i) => (i === selected ? ` \x1b[38;5;196m❱ ${opt}\x1b[0m` : ` ${opt}`))
|
|
183
|
+
.join("\n");
|
|
184
|
+
}
|
|
185
|
+
async function arrowMenu(options) {
|
|
186
|
+
let selected = 0;
|
|
187
|
+
const render = () => {
|
|
188
|
+
process.stdout.write(renderMenu(options, selected) + "\n");
|
|
189
|
+
};
|
|
190
|
+
render();
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
const stdin = process.stdin;
|
|
193
|
+
const wasRaw = stdin.isRaw;
|
|
194
|
+
stdin.setRawMode?.(true);
|
|
195
|
+
stdin.resume();
|
|
196
|
+
stdin.setEncoding("utf8");
|
|
197
|
+
const redraw = () => {
|
|
198
|
+
process.stdout.write(`\x1b[${options.length}A`); // move cursor back up
|
|
199
|
+
render();
|
|
200
|
+
};
|
|
201
|
+
const onData = (chunk) => {
|
|
202
|
+
if (chunk === "\u0003") {
|
|
203
|
+
// Ctrl-C
|
|
204
|
+
cleanup();
|
|
205
|
+
process.exit(130);
|
|
206
|
+
}
|
|
207
|
+
if (chunk === "\r" || chunk === "\n") {
|
|
208
|
+
cleanup();
|
|
209
|
+
resolve(selected);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (chunk === "\u001b[A" || chunk === "k") {
|
|
213
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
214
|
+
redraw();
|
|
215
|
+
}
|
|
216
|
+
else if (chunk === "\u001b[B" || chunk === "j") {
|
|
217
|
+
selected = (selected + 1) % options.length;
|
|
218
|
+
redraw();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const cleanup = () => {
|
|
222
|
+
stdin.off("data", onData);
|
|
223
|
+
stdin.setRawMode?.(Boolean(wasRaw));
|
|
224
|
+
};
|
|
225
|
+
stdin.on("data", onData);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
166
228
|
function ask(rl, q) {
|
|
167
229
|
return new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
168
230
|
}
|
|
@@ -191,14 +253,22 @@ export async function runLauncher(cwd) {
|
|
|
191
253
|
}
|
|
192
254
|
}
|
|
193
255
|
async function drive(rl, cwd) {
|
|
194
|
-
line("How do you want to start?");
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
256
|
+
line("How do you want to start? (↑/↓ then Enter, or type a number)");
|
|
257
|
+
const options = ["Connect to Mobile — pair your phone as a control surface", "AI Mode — set up the intelligence layer", "Exit to command help"];
|
|
258
|
+
let idx;
|
|
259
|
+
if (typeof process.stdin.setRawMode === "function") {
|
|
260
|
+
idx = await arrowMenu(options);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
line(" [1] " + options[0]);
|
|
264
|
+
line(" [2] " + options[1]);
|
|
265
|
+
line(" [0] " + options[2]);
|
|
266
|
+
const path = (await ask(rl, "\n> ")) || "2";
|
|
267
|
+
idx = path === "0" ? 2 : path === "1" ? 0 : 1;
|
|
268
|
+
}
|
|
269
|
+
if (idx === 2)
|
|
200
270
|
return 1;
|
|
201
|
-
if (
|
|
271
|
+
if (idx === 0)
|
|
202
272
|
return await connectMobile(rl);
|
|
203
273
|
return await aiMode(rl, cwd);
|
|
204
274
|
}
|
|
@@ -256,8 +326,10 @@ async function aiMode(rl, cwd) {
|
|
|
256
326
|
line(` Headroom model : ${result.headroom?.model ?? "heuristic-local"} (${headroomAuto ? "auto" : "manual"})`);
|
|
257
327
|
line(` Permissions : ${permissionMode}`);
|
|
258
328
|
line(` Knowledge doc : ${result.knowledgePath}`);
|
|
259
|
-
line(` Auth key : ${result.authKey}`);
|
|
260
329
|
line("─────────────────────────────────────────────");
|
|
330
|
+
line("");
|
|
331
|
+
line(renderKeyBox(result.authKey));
|
|
332
|
+
line(" ↑ session auth key — auto-passed to the browser, or type it if asked");
|
|
261
333
|
const open = (await ask(rl, "\nLaunch the browser UI now? [Y/n] ")).toLowerCase();
|
|
262
334
|
if (open === "" || open === "y" || open === "yes") {
|
|
263
335
|
return await launchUi(result.authKey);
|
|
@@ -343,7 +415,8 @@ async function connectMobile(rl) {
|
|
|
343
415
|
line(" 1. On this machine, MAQ will run its daemon (loopback + a tunnel/tailnet for remote).");
|
|
344
416
|
line(" 2. In the MAQ phone app, pair using the details below.");
|
|
345
417
|
line("");
|
|
346
|
-
line(
|
|
418
|
+
line(renderKeyBox(authKey));
|
|
419
|
+
line(" ↑ pairing PIN — copy this into the app");
|
|
347
420
|
line("");
|
|
348
421
|
line("Starting the daemon now with: maq serve");
|
|
349
422
|
line(" (the app pairs with host/port + the token the daemon prints)");
|
|
@@ -353,11 +426,14 @@ async function connectMobile(rl) {
|
|
|
353
426
|
async function launchUi(authKey) {
|
|
354
427
|
// Reuse the daemon; open its landing page. Import lazily to avoid a cycle.
|
|
355
428
|
const { createDaemon } = await import("../server/daemon.js");
|
|
356
|
-
const daemon = createDaemon({ token: authKey, version: "0.
|
|
429
|
+
const daemon = createDaemon({ token: authKey, version: "0.9.1" });
|
|
357
430
|
try {
|
|
358
431
|
const { host, port } = await daemon.listen();
|
|
359
|
-
|
|
360
|
-
|
|
432
|
+
// Seamless handoff (spec Phase 3): the key rides in the URL so the
|
|
433
|
+
// browser UI never has to prompt for it. The page reads ?key= once and
|
|
434
|
+
// keeps it in sessionStorage only — it is not persisted or logged again.
|
|
435
|
+
const url = `http://${host}:${port}/?key=${authKey}`;
|
|
436
|
+
line(`\nmaq daemon listening on http://${host}:${port}`);
|
|
361
437
|
line(`auth key/token: ${authKey}`);
|
|
362
438
|
openBrowser(url);
|
|
363
439
|
line("Opened your browser. Press Ctrl-C here to stop the daemon.");
|
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.
|
|
49
|
+
const VERSION = "0.9.1";
|
|
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.
|
|
55
|
+
const version = opts.version ?? "0.9.1";
|
|
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/dist/server/webui.d.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* webui.ts — the self-contained control surface the daemon serves
|
|
3
|
-
* (and `/`). Zero build step, zero dependencies: one HTML document
|
|
4
|
-
* inline CSS + vanilla JS. It renders the SAME normalized event stream the
|
|
2
|
+
* webui.ts — the self-contained "Megalodon" control surface the daemon serves
|
|
3
|
+
* at `/app` (and `/`). Zero build step, zero dependencies: one HTML document
|
|
4
|
+
* with inline CSS + vanilla JS. It renders the SAME normalized event stream the
|
|
5
5
|
* mobile app consumes, so there is no second source of truth.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* This is a from-spec rebuild (maq_cli_ui_spec Phases 4-6). The recommended
|
|
8
|
+
* SPA stack (React + Monaco + Framer + react-resizable-panels) is deliberately
|
|
9
|
+
* NOT used: the daemon ships one static asset with no build pipeline, so the
|
|
10
|
+
* same UX is delivered dependency-free —
|
|
11
|
+
* - real drag-resizable 3-panel shell (custom pointer-drag splitters)
|
|
12
|
+
* - universal preview: JSON tree / code+line-numbers / markdown / sandboxed
|
|
13
|
+
* HTML / images (replaces Monaco + react-markdown + react-json-view)
|
|
14
|
+
* - send button with a toggle-up mode picker popover (Single/Parallel/Loop/
|
|
15
|
+
* Safe), mode icon on the button face, selection persisted
|
|
16
|
+
* - orchestration visualizations derived from the event stream (parallel
|
|
17
|
+
* round dashboard / loop progress / safe split→merge→validate rail)
|
|
18
|
+
* - custom animated cursor follower + micro-interactions (CSS, not Framer)
|
|
15
19
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* ease-out and fully disabled under prefers-reduced-motion.
|
|
20
|
+
* Theme (Megalodon): deep matte black base, controlled blue-white accents (not
|
|
21
|
+
* neon), sparse red for status/danger, white primary text, no gradients except
|
|
22
|
+
* intentional depth cues. Everything references CSS custom-property tokens.
|
|
20
23
|
*
|
|
21
24
|
* Auth: the daemon requires a Bearer token for /v1/*. The page reads it from
|
|
22
|
-
* ?token
|
|
23
|
-
* ReadableStream (
|
|
24
|
-
* URL).
|
|
25
|
+
* ?key= / ?token= or a prompt, keeps it in memory only, and streams SSE via
|
|
26
|
+
* fetch + ReadableStream (token in the Authorization header, never the URL).
|
|
25
27
|
*/
|
|
26
28
|
export declare function webuiHtml(version: string): string;
|