maqcli 0.9.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.
@@ -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.
@@ -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
- line(" [1] Connect to Mobile — pair your phone as a control surface");
196
- line(" [2] AI Mode — set up the intelligence layer");
197
- line(" [0] Exit to command help");
198
- const path = (await ask(rl, "\n> ")) || "2";
199
- if (path === "0")
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 (path === "1")
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(` Auth key (pairing PIN): ${authKey}`);
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.9.0" });
429
+ const daemon = createDaemon({ token: authKey, version: "0.9.1" });
357
430
  try {
358
431
  const { host, port } = await daemon.listen();
359
- const url = `http://${host}:${port}/`;
360
- line(`\nmaq daemon listening on ${url}`);
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.9.0";
49
+ const VERSION = "0.9.1";
50
50
  async function main(argv) {
51
51
  const [command, ...rest] = argv;
52
52
  switch (command) {
@@ -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.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maqcli",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
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": {