maqcli 0.2.0

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.
Files changed (88) hide show
  1. package/README.md +223 -0
  2. package/dist/core/audit.d.ts +43 -0
  3. package/dist/core/audit.js +77 -0
  4. package/dist/core/board.d.ts +78 -0
  5. package/dist/core/board.js +256 -0
  6. package/dist/core/catalog.d.ts +50 -0
  7. package/dist/core/catalog.js +103 -0
  8. package/dist/core/command-catalog.d.ts +44 -0
  9. package/dist/core/command-catalog.js +86 -0
  10. package/dist/core/completion.d.ts +24 -0
  11. package/dist/core/completion.js +309 -0
  12. package/dist/core/complexity.d.ts +17 -0
  13. package/dist/core/complexity.js +87 -0
  14. package/dist/core/config-store.d.ts +33 -0
  15. package/dist/core/config-store.js +61 -0
  16. package/dist/core/connectivity.d.ts +34 -0
  17. package/dist/core/connectivity.js +49 -0
  18. package/dist/core/cost-tracker.d.ts +89 -0
  19. package/dist/core/cost-tracker.js +189 -0
  20. package/dist/core/cost.d.ts +35 -0
  21. package/dist/core/cost.js +89 -0
  22. package/dist/core/exec.d.ts +43 -0
  23. package/dist/core/exec.js +154 -0
  24. package/dist/core/flows.d.ts +36 -0
  25. package/dist/core/flows.js +96 -0
  26. package/dist/core/headroom.d.ts +36 -0
  27. package/dist/core/headroom.js +88 -0
  28. package/dist/core/help-topics.d.ts +26 -0
  29. package/dist/core/help-topics.js +294 -0
  30. package/dist/core/init-wizard.d.ts +26 -0
  31. package/dist/core/init-wizard.js +168 -0
  32. package/dist/core/interactive-registry.d.ts +50 -0
  33. package/dist/core/interactive-registry.js +86 -0
  34. package/dist/core/interactive.d.ts +48 -0
  35. package/dist/core/interactive.js +137 -0
  36. package/dist/core/logger.d.ts +16 -0
  37. package/dist/core/logger.js +46 -0
  38. package/dist/core/memory.d.ts +28 -0
  39. package/dist/core/memory.js +70 -0
  40. package/dist/core/metered.d.ts +9 -0
  41. package/dist/core/metered.js +16 -0
  42. package/dist/core/model.d.ts +74 -0
  43. package/dist/core/model.js +199 -0
  44. package/dist/core/pipeline.d.ts +33 -0
  45. package/dist/core/pipeline.js +223 -0
  46. package/dist/core/plugins.d.ts +21 -0
  47. package/dist/core/plugins.js +38 -0
  48. package/dist/core/probe.d.ts +48 -0
  49. package/dist/core/probe.js +156 -0
  50. package/dist/core/profiles.d.ts +42 -0
  51. package/dist/core/profiles.js +153 -0
  52. package/dist/core/providers.d.ts +84 -0
  53. package/dist/core/providers.js +275 -0
  54. package/dist/core/recall.d.ts +29 -0
  55. package/dist/core/recall.js +83 -0
  56. package/dist/core/registry.d.ts +41 -0
  57. package/dist/core/registry.js +162 -0
  58. package/dist/core/router.d.ts +33 -0
  59. package/dist/core/router.js +40 -0
  60. package/dist/core/sandbox.d.ts +78 -0
  61. package/dist/core/sandbox.js +268 -0
  62. package/dist/core/session.d.ts +105 -0
  63. package/dist/core/session.js +252 -0
  64. package/dist/core/skills.d.ts +56 -0
  65. package/dist/core/skills.js +289 -0
  66. package/dist/core/subagent.d.ts +40 -0
  67. package/dist/core/subagent.js +55 -0
  68. package/dist/core/supervisor.d.ts +37 -0
  69. package/dist/core/supervisor.js +40 -0
  70. package/dist/core/tools.d.ts +39 -0
  71. package/dist/core/tools.js +159 -0
  72. package/dist/core/types.d.ts +87 -0
  73. package/dist/core/types.js +10 -0
  74. package/dist/index.d.ts +11 -0
  75. package/dist/index.js +1032 -0
  76. package/dist/phases/execute.d.ts +39 -0
  77. package/dist/phases/execute.js +166 -0
  78. package/dist/phases/plan.d.ts +11 -0
  79. package/dist/phases/plan.js +118 -0
  80. package/dist/phases/scout.d.ts +10 -0
  81. package/dist/phases/scout.js +113 -0
  82. package/dist/phases/verify.d.ts +22 -0
  83. package/dist/phases/verify.js +81 -0
  84. package/dist/server/daemon.d.ts +50 -0
  85. package/dist/server/daemon.js +377 -0
  86. package/dist/server/relay-bridge.d.ts +44 -0
  87. package/dist/server/relay-bridge.js +175 -0
  88. package/package.json +39 -0
@@ -0,0 +1,294 @@
1
+ /**
2
+ * @module help-topics
3
+ *
4
+ * Conceptual help system for `maq help <topic>`.
5
+ * Provides detailed reference content for core maq concepts
6
+ * including the pipeline, permissions, routing, board, skills,
7
+ * providers, safety, and audit subsystems.
8
+ *
9
+ * Zero npm dependencies. ESM module.
10
+ */
11
+ // ── Topic Definitions ───────────────────────────────────────────────
12
+ const WRAP_WIDTH = 78;
13
+ const topics = [
14
+ {
15
+ name: "pipeline",
16
+ title: "The Scout → Plan → Execute → Verify Pipeline",
17
+ content: [
18
+ "Every maq task flows through a four-stage pipeline that separates",
19
+ "reasoning from action.",
20
+ "",
21
+ "1. Scout — A lightweight model scans the repository, gathers context,",
22
+ " and produces a structured summary of the codebase topology, relevant",
23
+ " files, and potential risks. Scout never writes files.",
24
+ "",
25
+ "2. Plan — A mid-tier model receives the scout report and the user's",
26
+ " intent, then emits an ordered list of concrete steps with expected",
27
+ " outcomes. Each step is tagged with the permission tier it requires.",
28
+ "",
29
+ "3. Execute — A capable model (or the same planner) carries out each",
30
+ " step sequentially. File writes, shell commands, and network calls",
31
+ " happen here, gated by the permission tier granted at launch.",
32
+ "",
33
+ "4. Verify — An independent model reviews the diff, runs any declared",
34
+ " checks (tests, linters, type-checks), and produces a pass/fail",
35
+ " verdict. Failed verification triggers an automatic retry loop",
36
+ " (up to the configured retry limit).",
37
+ "",
38
+ "The pipeline is recorded end-to-end in the audit log, so every",
39
+ "decision and mutation can be replayed or inspected after the fact.",
40
+ ].join("\n"),
41
+ },
42
+ {
43
+ name: "permissions",
44
+ title: "Permission Tiers & Safety Boundary",
45
+ content: [
46
+ "Maq enforces a three-tier permission model that limits what each",
47
+ "pipeline stage is allowed to do.",
48
+ "",
49
+ "Tier 1 — Read-only. The agent can read files, list directories,",
50
+ "and query environment variables. No writes, no shell commands,",
51
+ "no network calls beyond API routing. Scout always runs at Tier 1.",
52
+ "",
53
+ "Tier 2 — Write-local. The agent may create and modify files inside",
54
+ "the project directory, run allow-listed shell commands (e.g. npm",
55
+ "test, cargo build), and access localhost services. It cannot touch",
56
+ "files outside the project root or make arbitrary network requests.",
57
+ "",
58
+ "Tier 3 — Full access. The agent may execute arbitrary commands,",
59
+ "access the network, and modify files anywhere the invoking user",
60
+ "has OS-level permission. Tier 3 requires explicit --tier=3 flag",
61
+ "and records every action in a tamper-evident audit log.",
62
+ "",
63
+ "The tier is set at invocation time and cannot be escalated during",
64
+ "a run. Plan steps tagged above the granted tier are skipped with",
65
+ "a warning, ensuring the user always controls the blast radius.",
66
+ ].join("\n"),
67
+ },
68
+ {
69
+ name: "routing",
70
+ title: "Hybrid API Routing",
71
+ content: [
72
+ "Maq uses a hybrid routing strategy to balance cost, latency, and",
73
+ "capability across pipeline stages.",
74
+ "",
75
+ "Broker routing (Scout & Plan) — These stages send requests through",
76
+ "the maq broker, which selects the cheapest model that meets the",
77
+ "stage's capability floor. The broker tracks per-model cost rates,",
78
+ "context-window sizes, and historical accuracy to make routing",
79
+ "decisions. Users can pin a specific model with --model to override.",
80
+ "",
81
+ "Native routing (Execute) — Execution-stage calls go directly to",
82
+ "the provider's API endpoint, bypassing the broker. This avoids",
83
+ "double-hop latency for the most token-intensive stage and lets",
84
+ "the agent stream tool-call results in real time.",
85
+ "",
86
+ "Provider fallback — If the primary provider returns a 5xx or a",
87
+ "rate-limit error, the broker automatically retries with the next",
88
+ "cheapest compatible model. The fallback chain is configurable via",
89
+ "`maq config set routing.fallback`.",
90
+ "",
91
+ "All routing decisions are logged with the model used, token counts,",
92
+ "and wall-clock latency so you can audit cost after every run.",
93
+ ].join("\n"),
94
+ },
95
+ {
96
+ name: "board",
97
+ title: "The Shared Board (.maq/board.jsonl)",
98
+ content: [
99
+ "The board is a local append-only JSONL file at .maq/board.jsonl",
100
+ "that acts as a stigmergy layer between agents and pipeline stages.",
101
+ "",
102
+ "Each line is a JSON object with a timestamp, author (stage name",
103
+ "or agent ID), type tag, and a freeform payload. Common types:",
104
+ "",
105
+ " context — Scout writes discovered files, symbols, dependencies.",
106
+ " step — Plan writes each planned step with expected outcome.",
107
+ " result — Execute writes the actual outcome of each step.",
108
+ " verdict — Verify writes pass/fail plus evidence.",
109
+ " note — Any stage can leave advisory notes for later stages.",
110
+ "",
111
+ "Because the board is append-only, no stage can erase another's",
112
+ "observations. Downstream stages read the full board to build",
113
+ "situational awareness without direct inter-process communication.",
114
+ "",
115
+ "Use `maq board show` to pretty-print the current board, or",
116
+ "`maq board tail` to stream new entries in real time. The board",
117
+ "is reset at the start of each new session unless --resume is set.",
118
+ "",
119
+ "The board format is intentionally simple so external tools can",
120
+ "parse it with any JSON library or even plain grep.",
121
+ ].join("\n"),
122
+ },
123
+ {
124
+ name: "skills",
125
+ title: "Skills & Rules Layer",
126
+ content: [
127
+ "Skills are reusable instruction sets that teach maq how to handle",
128
+ "specific tasks — for example, writing tests in a particular style",
129
+ "or deploying to a specific cloud provider.",
130
+ "",
131
+ "A skill is a Markdown file stored under .maq/skills/ with YAML",
132
+ "front-matter declaring its name, description, and required tier.",
133
+ "During planning, the planner searches the skills directory for",
134
+ "skills whose description matches the current intent and injects",
135
+ "their instructions into the plan context.",
136
+ "",
137
+ "Skills are tier-aware: a Tier 1 skill can only contain read",
138
+ "operations, while a Tier 3 skill may include deployment commands.",
139
+ "If the current run's tier is lower than the skill's requirement,",
140
+ "the skill is silently skipped.",
141
+ "",
142
+ "Auto-learning: when a verify stage passes, maq can optionally",
143
+ "distill the successful plan into a new skill and write it to",
144
+ ".maq/skills/ so future runs benefit automatically. This behavior",
145
+ "is controlled by `maq config set skills.autoLearn true`.",
146
+ "",
147
+ "Rules are simpler: plain text constraints (e.g. \"never delete",
148
+ "migration files\") stored in .maq/rules.md and prepended to every",
149
+ "plan context unconditionally.",
150
+ ].join("\n"),
151
+ },
152
+ {
153
+ name: "providers",
154
+ title: "Model Providers & Cost Tiers",
155
+ content: [
156
+ "Maq supports multiple LLM providers and organizes them into cost",
157
+ "tiers to help you control spend.",
158
+ "",
159
+ "Built-in providers: OpenAI, Anthropic, Google (Gemini), Mistral,",
160
+ "Groq, and any OpenAI-compatible endpoint via a custom base URL.",
161
+ "Each provider is configured with an API key stored in your OS",
162
+ "keychain or in .maq/config.json (encrypted at rest).",
163
+ "",
164
+ "Cost tiers:",
165
+ " Tier A — Economy models (e.g. gpt-4.1-mini, gemini-2.5-flash).",
166
+ " Used by default for Scout and quick Plan tasks.",
167
+ " Tier B — Standard models (e.g. gpt-4.1, claude-sonnet-4).",
168
+ " Default for Execute when the plan is non-trivial.",
169
+ " Tier C — Frontier models (e.g. o3, claude-opus-4).",
170
+ " Opt-in only via --model or per-stage config.",
171
+ "",
172
+ "The broker picks the cheapest model in the allowed tier that",
173
+ "satisfies the stage's context-window and tool-use requirements.",
174
+ "Use `maq models list` to see available models, their tiers, and",
175
+ "current per-token pricing. Use `maq models cheapest` to find the",
176
+ "cheapest model that can handle a given context size.",
177
+ ].join("\n"),
178
+ },
179
+ {
180
+ name: "safety",
181
+ title: "Safety & Trust Model",
182
+ content: [
183
+ "Maq is designed so that the user is always in control and no",
184
+ "single model failure can cause irreversible damage.",
185
+ "",
186
+ "Defense in depth:",
187
+ " 1. Tier gating prevents Execute from exceeding the permission",
188
+ " level the user granted at invocation.",
189
+ " 2. Plan review — in interactive mode the plan is displayed for",
190
+ " approval before execution begins. In CI mode the plan is",
191
+ " logged and a --yes flag is required to skip approval.",
192
+ " 3. Verify independence — the verification model is deliberately",
193
+ " a different model (or temperature) than the executor to avoid",
194
+ " self-confirming bias.",
195
+ " 4. Rollback snapshots — before any write, maq snapshots the",
196
+ " target file. If verification fails and retries are exhausted,",
197
+ " all changes are rolled back automatically.",
198
+ "",
199
+ "Network sandboxing: at Tier 1 and Tier 2, outbound network access",
200
+ "is blocked except for LLM API calls and localhost. Tier 3 lifts",
201
+ "this restriction but logs every outbound connection.",
202
+ "",
203
+ "All safety events (tier violations, rollbacks, blocked commands)",
204
+ "are written to the audit log with full context for post-hoc review.",
205
+ ].join("\n"),
206
+ },
207
+ {
208
+ name: "audit",
209
+ title: "Audit Logs & Replay",
210
+ content: [
211
+ "Every maq session produces a structured audit log under",
212
+ ".maq/sessions/<id>/audit.jsonl. The log captures every decision",
213
+ "and side-effect so you can understand exactly what happened.",
214
+ "",
215
+ "Each audit entry contains:",
216
+ " • timestamp (ISO-8601 with milliseconds)",
217
+ " • stage (scout, plan, execute, verify)",
218
+ " • action type (file_read, file_write, shell_exec, api_call, ...)",
219
+ " • input arguments and output summary",
220
+ " • model used, token counts, and latency",
221
+ " • permission tier at the time of the action",
222
+ "",
223
+ "Replay: use `maq audit <session-id>` to pretty-print the log or",
224
+ "pipe it to jq for ad-hoc queries. The --replay flag re-executes",
225
+ "the logged plan against the current codebase, which is useful for",
226
+ "regression testing your skills and rules.",
227
+ "",
228
+ "Retention: audit logs are kept for 30 days by default. Change the",
229
+ "retention window with `maq config set audit.retentionDays <n>`.",
230
+ "",
231
+ "Compliance: for regulated environments, enable tamper-evident mode",
232
+ "with `maq config set audit.signed true`, which appends an HMAC to",
233
+ "each entry using a key stored in your OS keychain.",
234
+ ].join("\n"),
235
+ },
236
+ ];
237
+ // ── Public API ──────────────────────────────────────────────────────
238
+ /** Returns a shallow copy of all available help topics. */
239
+ export function listTopics() {
240
+ return [...topics];
241
+ }
242
+ /** Looks up a topic by name (case-insensitive). Returns `null` if not found. */
243
+ export function getTopic(name) {
244
+ const lower = name.toLowerCase();
245
+ return topics.find((t) => t.name === lower) ?? null;
246
+ }
247
+ /**
248
+ * Renders a topic as a formatted string suitable for terminal output.
249
+ * The title is displayed with an underline, followed by word-wrapped
250
+ * paragraphs of the content body.
251
+ */
252
+ export function renderTopic(topic) {
253
+ const underline = "─".repeat(topic.title.length);
254
+ const wrapped = wordWrap(topic.content, WRAP_WIDTH);
255
+ return `${topic.title}\n${underline}\n\n${wrapped}\n`;
256
+ }
257
+ // ── Internals ───────────────────────────────────────────────────────
258
+ /**
259
+ * Word-wraps text to `width` columns, preserving existing blank lines
260
+ * as paragraph breaks and respecting leading whitespace for indented lines.
261
+ */
262
+ function wordWrap(text, width) {
263
+ const lines = text.split("\n");
264
+ const out = [];
265
+ for (const line of lines) {
266
+ if (line.trim() === "") {
267
+ out.push("");
268
+ continue;
269
+ }
270
+ // Detect leading whitespace to preserve indentation.
271
+ const indent = line.match(/^(\s*)/)?.[1] ?? "";
272
+ const effectiveWidth = width - indent.length;
273
+ if (effectiveWidth <= 20) {
274
+ // Line is too indented to wrap meaningfully; keep as-is.
275
+ out.push(line);
276
+ continue;
277
+ }
278
+ const words = line.trimStart().split(/\s+/);
279
+ let current = indent;
280
+ for (const word of words) {
281
+ if (current.length > indent.length && current.length + 1 + word.length > width) {
282
+ out.push(current);
283
+ current = indent + word;
284
+ }
285
+ else {
286
+ current += (current.length > indent.length ? " " : "") + word;
287
+ }
288
+ }
289
+ if (current.length > 0) {
290
+ out.push(current);
291
+ }
292
+ }
293
+ return out.join("\n");
294
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @module init-wizard
3
+ * @description Interactive onboarding wizard for `maq init`.
4
+ *
5
+ * Walks the user through first-time setup:
6
+ * 1. Welcome banner
7
+ * 2. Agent auto-detection
8
+ * 3. LLM provider selection
9
+ * 4. Permission-default selection
10
+ * 5. Skill scaffolding
11
+ * 6. `.maq/` directory creation
12
+ * 7. Config persistence
13
+ * 8. Summary & next-step tips
14
+ *
15
+ * Zero npm dependencies — uses only Node built-ins.
16
+ */
17
+ /**
18
+ * Runs the interactive `maq init` wizard.
19
+ *
20
+ * @param cwd - The working directory (project root) in which `.maq/` will be created.
21
+ * @returns An object indicating whether configuration was written and a human-readable summary.
22
+ */
23
+ export declare function runInit(cwd: string): Promise<{
24
+ configured: boolean;
25
+ summary: string[];
26
+ }>;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @module init-wizard
3
+ * @description Interactive onboarding wizard for `maq init`.
4
+ *
5
+ * Walks the user through first-time setup:
6
+ * 1. Welcome banner
7
+ * 2. Agent auto-detection
8
+ * 3. LLM provider selection
9
+ * 4. Permission-default selection
10
+ * 5. Skill scaffolding
11
+ * 6. `.maq/` directory creation
12
+ * 7. Config persistence
13
+ * 8. Summary & next-step tips
14
+ *
15
+ * Zero npm dependencies — uses only Node built-ins.
16
+ */
17
+ import { createInterface } from "node:readline";
18
+ import { mkdir } from "node:fs/promises";
19
+ import { join } from "node:path";
20
+ import { loadConfig, saveConfig, configPath } from "./config-store.js";
21
+ import { initSkills } from "./skills.js";
22
+ import { detectAgents } from "./registry.js";
23
+ /* ------------------------------------------------------------------ */
24
+ /* Constants */
25
+ /* ------------------------------------------------------------------ */
26
+ const PROVIDERS = [
27
+ { key: "1", label: "heuristic (offline, $0)" },
28
+ { key: "2", label: "openai" },
29
+ { key: "3", label: "anthropic" },
30
+ { key: "4", label: "ollama" },
31
+ { key: "5", label: "groq" },
32
+ { key: "0", label: "skip" },
33
+ ];
34
+ const PROVIDER_MAP = {
35
+ "1": "heuristic",
36
+ "2": "openai",
37
+ "3": "anthropic",
38
+ "4": "ollama",
39
+ "5": "groq",
40
+ };
41
+ const PERMISSIONS = [
42
+ { key: "1", label: "strict (read-only preview first)" },
43
+ { key: "2", label: "standard (scoped writes)" },
44
+ { key: "0", label: "skip" },
45
+ ];
46
+ const PERMISSION_MAP = {
47
+ "1": "strict",
48
+ "2": "standard",
49
+ };
50
+ /* ------------------------------------------------------------------ */
51
+ /* Helpers */
52
+ /* ------------------------------------------------------------------ */
53
+ /**
54
+ * Prompts the user with a question and resolves with their trimmed answer.
55
+ */
56
+ function ask(rl, question) {
57
+ return new Promise((resolve) => {
58
+ rl.question(question, (answer) => {
59
+ resolve(answer.trim());
60
+ });
61
+ });
62
+ }
63
+ /* ------------------------------------------------------------------ */
64
+ /* Public API */
65
+ /* ------------------------------------------------------------------ */
66
+ /**
67
+ * Runs the interactive `maq init` wizard.
68
+ *
69
+ * @param cwd - The working directory (project root) in which `.maq/` will be created.
70
+ * @returns An object indicating whether configuration was written and a human-readable summary.
71
+ */
72
+ export async function runInit(cwd) {
73
+ const summary = [];
74
+ const rl = createInterface({
75
+ input: process.stdin,
76
+ output: process.stdout,
77
+ });
78
+ try {
79
+ return await _runWizard(rl, cwd, summary);
80
+ }
81
+ finally {
82
+ rl.close();
83
+ }
84
+ }
85
+ /* ------------------------------------------------------------------ */
86
+ /* Internal wizard flow */
87
+ /* ------------------------------------------------------------------ */
88
+ async function _runWizard(rl, cwd, summary) {
89
+ /* ── 1. Welcome ─────────────────────────────────────────────── */
90
+ console.log("");
91
+ console.log(" maq init — first-time setup");
92
+ console.log(" ─────────────────────────────");
93
+ console.log("");
94
+ /* ── 2. Auto-detect agents ──────────────────────────────────── */
95
+ const agents = await detectAgents();
96
+ if (agents.length > 0) {
97
+ console.log(" Detected agents:");
98
+ for (const agent of agents) {
99
+ console.log(` • ${agent}`);
100
+ }
101
+ summary.push(`Detected ${agents.length} agent(s): ${agents.join(", ")}`);
102
+ }
103
+ else {
104
+ console.log(" No agents detected.");
105
+ summary.push("No agents detected");
106
+ }
107
+ console.log("");
108
+ /* ── 3. Provider selection ──────────────────────────────────── */
109
+ console.log(" Choose a default LLM provider:");
110
+ for (const p of PROVIDERS) {
111
+ console.log(` [${p.key}] ${p.label}`);
112
+ }
113
+ const providerChoice = await ask(rl, "\n Provider [1]: ");
114
+ const providerKey = providerChoice === "" ? "1" : providerChoice;
115
+ const provider = PROVIDER_MAP[providerKey] ?? null;
116
+ if (provider) {
117
+ summary.push(`Provider: ${provider}`);
118
+ }
119
+ else {
120
+ summary.push("Provider: skipped");
121
+ }
122
+ console.log("");
123
+ /* ── 4. Permission default ──────────────────────────────────── */
124
+ console.log(" Choose a default permission level:");
125
+ for (const p of PERMISSIONS) {
126
+ console.log(` [${p.key}] ${p.label}`);
127
+ }
128
+ const permChoice = await ask(rl, "\n Permission [1]: ");
129
+ const permKey = permChoice === "" ? "1" : permChoice;
130
+ const permission = PERMISSION_MAP[permKey] ?? null;
131
+ if (permission) {
132
+ summary.push(`Permissions: ${permission}`);
133
+ }
134
+ else {
135
+ summary.push("Permissions: skipped");
136
+ }
137
+ console.log("");
138
+ /* ── 5. Scaffold skills ─────────────────────────────────────── */
139
+ await initSkills(cwd);
140
+ summary.push("Skills scaffolded");
141
+ /* ── 6. Create .maq/ dir ────────────────────────────────────── */
142
+ const maqDir = join(cwd, ".maq");
143
+ await mkdir(maqDir, { recursive: true });
144
+ summary.push(`Directory: ${maqDir}`);
145
+ /* ── 7. Save config ─────────────────────────────────────────── */
146
+ const config = loadConfig();
147
+ if (provider) {
148
+ config.provider = provider;
149
+ }
150
+ if (permission) {
151
+ config.defaultPermission = permission;
152
+ }
153
+ saveConfig(config);
154
+ summary.push(`Config saved → ${configPath()}`);
155
+ /* ── 8. Summary & tips ──────────────────────────────────────── */
156
+ console.log(" ✔ Setup complete!");
157
+ console.log("");
158
+ console.log(" Summary:");
159
+ for (const line of summary) {
160
+ console.log(` • ${line}`);
161
+ }
162
+ console.log("");
163
+ console.log(" Next steps:");
164
+ console.log(' Try: maq doctor');
165
+ console.log(' Try: maq run "<task>"');
166
+ console.log("");
167
+ return { configured: true, summary };
168
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Interactive registry — long-lived interactive agent terminals the app drives.
3
+ *
4
+ * The Master edition is multi-panel: panel 1 is the Headroom master (maq
5
+ * commands via /v1/exec); the other panels are AI worker CLIs the user starts
6
+ * themselves (for when an agent isn't the auto-detected default, or to run
7
+ * several at once). Each is an InteractiveWorker (piped stdin steering + live
8
+ * status) whose events the daemon streams over SSE and whose stdin the app
9
+ * writes — the same connection method for every agent terminal.
10
+ */
11
+ import type { MaqEvent } from "./types.js";
12
+ import { InteractiveWorker, type WorkerStatus } from "./interactive.js";
13
+ export interface InteractiveInfo {
14
+ id: string;
15
+ target: string;
16
+ status: WorkerStatus;
17
+ createdAt: string;
18
+ /** Whether the resolved agent looked authenticated at start time. */
19
+ authenticated?: boolean;
20
+ /** Advisory if the agent is installed but not authenticated (may prompt login). */
21
+ warning?: string;
22
+ }
23
+ interface Entry {
24
+ worker: InteractiveWorker;
25
+ info: InteractiveInfo;
26
+ events: MaqEvent[];
27
+ }
28
+ export declare class InteractiveRegistry {
29
+ private entries;
30
+ private bus;
31
+ constructor();
32
+ /** Start an AI CLI as an interactive agent terminal. Throws if unresolved. */
33
+ start(target: string, opts?: {
34
+ cwd?: string;
35
+ task?: string;
36
+ }): InteractiveInfo;
37
+ /** Lower-level spawn (also used by tests with an arbitrary bin). */
38
+ spawnRaw(bin: string, args: string[], meta: {
39
+ target: string;
40
+ cwd?: string;
41
+ }): InteractiveInfo;
42
+ get(id: string): Entry | undefined;
43
+ list(): InteractiveInfo[];
44
+ /** Steer: write a line to the agent's stdin. */
45
+ input(id: string, text: string): boolean;
46
+ close(id: string): boolean;
47
+ subscribe(id: string, listener: (e: MaqEvent) => void): () => void;
48
+ private push;
49
+ }
50
+ export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Interactive registry — long-lived interactive agent terminals the app drives.
3
+ *
4
+ * The Master edition is multi-panel: panel 1 is the Headroom master (maq
5
+ * commands via /v1/exec); the other panels are AI worker CLIs the user starts
6
+ * themselves (for when an agent isn't the auto-detected default, or to run
7
+ * several at once). Each is an InteractiveWorker (piped stdin steering + live
8
+ * status) whose events the daemon streams over SSE and whose stdin the app
9
+ * writes — the same connection method for every agent terminal.
10
+ */
11
+ import { EventEmitter } from "node:events";
12
+ import { randomUUID } from "node:crypto";
13
+ import { makeEvent } from "./types.js";
14
+ import { InteractiveWorker } from "./interactive.js";
15
+ import { detectAgents, agentSpec, resolveTarget } from "./registry.js";
16
+ export class InteractiveRegistry {
17
+ entries = new Map();
18
+ bus = new EventEmitter();
19
+ constructor() {
20
+ this.bus.setMaxListeners(0);
21
+ }
22
+ /** Start an AI CLI as an interactive agent terminal. Throws if unresolved. */
23
+ start(target, opts = {}) {
24
+ const agents = detectAgents();
25
+ const { target: resolved } = resolveTarget(target || "auto", agents);
26
+ const spec = agentSpec(resolved);
27
+ const binPath = agents.find((a) => a.name === resolved)?.binPath ?? null;
28
+ if (!spec || !spec.headless || !binPath) {
29
+ throw new Error(`agent '${resolved}' is not installed/runnable (try 'maq detect')`);
30
+ }
31
+ const args = spec.headless.map((a) => (a === "{task}" ? (opts.task ?? "") : a));
32
+ const authed = agents.find((a) => a.name === resolved)?.authenticated ?? false;
33
+ const info = this.spawnRaw(binPath, args, { target: resolved, cwd: opts.cwd });
34
+ info.authenticated = authed;
35
+ if (!authed) {
36
+ info.warning = `'${resolved}' is installed but not authenticated — it may prompt you to log in. Complete login in the terminal panel, then retry.`;
37
+ }
38
+ return info;
39
+ }
40
+ /** Lower-level spawn (also used by tests with an arbitrary bin). */
41
+ spawnRaw(bin, args, meta) {
42
+ const id = randomUUID();
43
+ const info = { id, target: meta.target, status: "starting", createdAt: new Date().toISOString() };
44
+ const worker = new InteractiveWorker(bin, args, {
45
+ cwd: meta.cwd,
46
+ onEvent: (e) => this.push(id, e),
47
+ onStatus: (s) => {
48
+ info.status = s;
49
+ this.push(id, makeEvent("agent.event", { status: s }));
50
+ },
51
+ });
52
+ this.entries.set(id, { worker, info, events: [] });
53
+ worker.start();
54
+ return info;
55
+ }
56
+ get(id) {
57
+ return this.entries.get(id);
58
+ }
59
+ list() {
60
+ return [...this.entries.values()].map((e) => e.info);
61
+ }
62
+ /** Steer: write a line to the agent's stdin. */
63
+ input(id, text) {
64
+ return this.entries.get(id)?.worker.write(text) ?? false;
65
+ }
66
+ close(id) {
67
+ const e = this.entries.get(id);
68
+ if (!e)
69
+ return false;
70
+ e.worker.end();
71
+ e.worker.kill();
72
+ return true;
73
+ }
74
+ subscribe(id, listener) {
75
+ const evt = `event:${id}`;
76
+ this.bus.on(evt, listener);
77
+ return () => this.bus.off(evt, listener);
78
+ }
79
+ push(id, e) {
80
+ const entry = this.entries.get(id);
81
+ if (!entry)
82
+ return;
83
+ entry.events.push(e);
84
+ this.bus.emit(`event:${id}`, e);
85
+ }
86
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Interactive worker — the "steer a running worker" capability (CAO's live-PTY
3
+ * value) WITHOUT a mandatory native PTY dependency. It spawns a worker with a
4
+ * kept-open stdin pipe, streams stdout/stderr line-by-line as normalized
5
+ * events, infers a live status (starting → processing ⇄ idle → exited), and
6
+ * lets a caller `write()` follow-up input mid-task (inbox → stdin steering).
7
+ *
8
+ * Zero runtime deps (node:child_process). A full terminal-emulation PTY backend
9
+ * (node-pty) can be added later as an opt-in for CLIs that require a real TTY;
10
+ * this covers CLIs that read line-based stdin.
11
+ */
12
+ import { type MaqEvent } from "./types.js";
13
+ export type WorkerStatus = "starting" | "processing" | "idle" | "exited";
14
+ export interface InteractiveOptions {
15
+ cwd?: string;
16
+ env?: NodeJS.ProcessEnv;
17
+ /** Quiet period (ms) after which a busy worker is considered idle. */
18
+ idleMs?: number;
19
+ onEvent?: (e: MaqEvent) => void;
20
+ onStatus?: (s: WorkerStatus) => void;
21
+ }
22
+ export declare class InteractiveWorker {
23
+ private bin;
24
+ private args;
25
+ private child;
26
+ private _status;
27
+ private idleTimer;
28
+ private outBuf;
29
+ private errBuf;
30
+ private idleMs;
31
+ private opts;
32
+ private exitResolve;
33
+ constructor(bin: string, args?: string[], opts?: InteractiveOptions);
34
+ get status(): WorkerStatus;
35
+ start(): void;
36
+ /** Steer the worker: send a line of input to its stdin. */
37
+ write(text: string): boolean;
38
+ /** Signal end-of-input (close stdin). */
39
+ end(): void;
40
+ kill(): void;
41
+ /** Resolve when the process exits. */
42
+ wait(): Promise<number | null>;
43
+ private pump;
44
+ private emitLine;
45
+ private armIdle;
46
+ private setStatus;
47
+ private emit;
48
+ }