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.
- package/README.md +223 -0
- package/dist/core/audit.d.ts +43 -0
- package/dist/core/audit.js +77 -0
- package/dist/core/board.d.ts +78 -0
- package/dist/core/board.js +256 -0
- package/dist/core/catalog.d.ts +50 -0
- package/dist/core/catalog.js +103 -0
- package/dist/core/command-catalog.d.ts +44 -0
- package/dist/core/command-catalog.js +86 -0
- package/dist/core/completion.d.ts +24 -0
- package/dist/core/completion.js +309 -0
- package/dist/core/complexity.d.ts +17 -0
- package/dist/core/complexity.js +87 -0
- package/dist/core/config-store.d.ts +33 -0
- package/dist/core/config-store.js +61 -0
- package/dist/core/connectivity.d.ts +34 -0
- package/dist/core/connectivity.js +49 -0
- package/dist/core/cost-tracker.d.ts +89 -0
- package/dist/core/cost-tracker.js +189 -0
- package/dist/core/cost.d.ts +35 -0
- package/dist/core/cost.js +89 -0
- package/dist/core/exec.d.ts +43 -0
- package/dist/core/exec.js +154 -0
- package/dist/core/flows.d.ts +36 -0
- package/dist/core/flows.js +96 -0
- package/dist/core/headroom.d.ts +36 -0
- package/dist/core/headroom.js +88 -0
- package/dist/core/help-topics.d.ts +26 -0
- package/dist/core/help-topics.js +294 -0
- package/dist/core/init-wizard.d.ts +26 -0
- package/dist/core/init-wizard.js +168 -0
- package/dist/core/interactive-registry.d.ts +50 -0
- package/dist/core/interactive-registry.js +86 -0
- package/dist/core/interactive.d.ts +48 -0
- package/dist/core/interactive.js +137 -0
- package/dist/core/logger.d.ts +16 -0
- package/dist/core/logger.js +46 -0
- package/dist/core/memory.d.ts +28 -0
- package/dist/core/memory.js +70 -0
- package/dist/core/metered.d.ts +9 -0
- package/dist/core/metered.js +16 -0
- package/dist/core/model.d.ts +74 -0
- package/dist/core/model.js +199 -0
- package/dist/core/pipeline.d.ts +33 -0
- package/dist/core/pipeline.js +223 -0
- package/dist/core/plugins.d.ts +21 -0
- package/dist/core/plugins.js +38 -0
- package/dist/core/probe.d.ts +48 -0
- package/dist/core/probe.js +156 -0
- package/dist/core/profiles.d.ts +42 -0
- package/dist/core/profiles.js +153 -0
- package/dist/core/providers.d.ts +84 -0
- package/dist/core/providers.js +275 -0
- package/dist/core/recall.d.ts +29 -0
- package/dist/core/recall.js +83 -0
- package/dist/core/registry.d.ts +41 -0
- package/dist/core/registry.js +162 -0
- package/dist/core/router.d.ts +33 -0
- package/dist/core/router.js +40 -0
- package/dist/core/sandbox.d.ts +78 -0
- package/dist/core/sandbox.js +268 -0
- package/dist/core/session.d.ts +105 -0
- package/dist/core/session.js +252 -0
- package/dist/core/skills.d.ts +56 -0
- package/dist/core/skills.js +289 -0
- package/dist/core/subagent.d.ts +40 -0
- package/dist/core/subagent.js +55 -0
- package/dist/core/supervisor.d.ts +37 -0
- package/dist/core/supervisor.js +40 -0
- package/dist/core/tools.d.ts +39 -0
- package/dist/core/tools.js +159 -0
- package/dist/core/types.d.ts +87 -0
- package/dist/core/types.js +10 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1032 -0
- package/dist/phases/execute.d.ts +39 -0
- package/dist/phases/execute.js +166 -0
- package/dist/phases/plan.d.ts +11 -0
- package/dist/phases/plan.js +118 -0
- package/dist/phases/scout.d.ts +10 -0
- package/dist/phases/scout.js +113 -0
- package/dist/phases/verify.d.ts +22 -0
- package/dist/phases/verify.js +81 -0
- package/dist/server/daemon.d.ts +50 -0
- package/dist/server/daemon.js +377 -0
- package/dist/server/relay-bridge.d.ts +44 -0
- package/dist/server/relay-bridge.js +175 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1032 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* maqcli entry point.
|
|
4
|
+
*
|
|
5
|
+
* MAQ master orchestrator: a token-efficient, agent-agnostic supervisor that
|
|
6
|
+
* sits on top of any worker CLI (AI or not) via a Scout -> Plan -> Execute ->
|
|
7
|
+
* Verify pipeline. Zero runtime dependencies; uses only Node built-ins.
|
|
8
|
+
*
|
|
9
|
+
* Commands: detect | config | scout | plan | run | verify | help | version
|
|
10
|
+
*/
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
import { logger } from "./core/logger.js";
|
|
13
|
+
import { detectAgents, resolveTarget } from "./core/registry.js";
|
|
14
|
+
import { loadConfig, saveConfig, setConfigKey, configPath } from "./core/config-store.js";
|
|
15
|
+
import { runScout } from "./phases/scout.js";
|
|
16
|
+
import { getProvider } from "./core/model.js";
|
|
17
|
+
import { runPlan } from "./phases/plan.js";
|
|
18
|
+
import { detectTestCommand, runVerify } from "./phases/verify.js";
|
|
19
|
+
import { runPipeline } from "./core/pipeline.js";
|
|
20
|
+
import { createDaemon } from "./server/daemon.js";
|
|
21
|
+
import { RelayBridge } from "./server/relay-bridge.js";
|
|
22
|
+
import { probeConnectivity } from "./core/probe.js";
|
|
23
|
+
import { priceFor } from "./core/cost.js";
|
|
24
|
+
import { SkillsManager, initSkills } from "./core/skills.js";
|
|
25
|
+
import { runSubagent } from "./core/subagent.js";
|
|
26
|
+
import { createToolRegistry } from "./core/tools.js";
|
|
27
|
+
import { Router } from "./core/router.js";
|
|
28
|
+
import { masterOptions, cheapestMaster } from "./core/catalog.js";
|
|
29
|
+
import { verifyAuditLog } from "./core/audit.js";
|
|
30
|
+
import { ProfileManager } from "./core/profiles.js";
|
|
31
|
+
import { RecallMemory } from "./core/recall.js";
|
|
32
|
+
import { runSwarm } from "./core/supervisor.js";
|
|
33
|
+
import { SessionRegistry } from "./core/session.js";
|
|
34
|
+
import { InteractiveWorker } from "./core/interactive.js";
|
|
35
|
+
import { loadFlows, addFlow, removeFlow, scheduleMs, Scheduler } from "./core/flows.js";
|
|
36
|
+
import { attachWebhook } from "./core/plugins.js";
|
|
37
|
+
import { agentSpec } from "./core/registry.js";
|
|
38
|
+
import { authAdvisory } from "./core/registry.js";
|
|
39
|
+
import { Board } from "./core/board.js";
|
|
40
|
+
import { getTopic, listTopics, renderTopic } from "./core/help-topics.js";
|
|
41
|
+
import { generateCompletion, detectShell } from "./core/completion.js";
|
|
42
|
+
import { runInit } from "./core/init-wizard.js";
|
|
43
|
+
import { CostTracker } from "./core/cost-tracker.js";
|
|
44
|
+
const VERSION = "0.2.0";
|
|
45
|
+
async function main(argv) {
|
|
46
|
+
const [command, ...rest] = argv;
|
|
47
|
+
switch (command) {
|
|
48
|
+
case undefined:
|
|
49
|
+
case "-h":
|
|
50
|
+
case "--help":
|
|
51
|
+
printHelp();
|
|
52
|
+
return 0;
|
|
53
|
+
case "help":
|
|
54
|
+
return cmdHelp(rest);
|
|
55
|
+
case "version":
|
|
56
|
+
case "-v":
|
|
57
|
+
case "--version":
|
|
58
|
+
logger.out(VERSION);
|
|
59
|
+
return 0;
|
|
60
|
+
case "detect":
|
|
61
|
+
return cmdDetect(rest);
|
|
62
|
+
case "config":
|
|
63
|
+
return cmdConfig(rest);
|
|
64
|
+
case "scout":
|
|
65
|
+
return cmdScout(rest);
|
|
66
|
+
case "plan":
|
|
67
|
+
return cmdPlan(rest);
|
|
68
|
+
case "run":
|
|
69
|
+
return cmdRun(rest);
|
|
70
|
+
case "verify":
|
|
71
|
+
return cmdVerify(rest);
|
|
72
|
+
case "serve":
|
|
73
|
+
return cmdServe(rest);
|
|
74
|
+
case "sessions":
|
|
75
|
+
return cmdSessions(rest);
|
|
76
|
+
case "models":
|
|
77
|
+
return cmdModels(rest);
|
|
78
|
+
case "probe":
|
|
79
|
+
return cmdProbe(rest);
|
|
80
|
+
case "skills":
|
|
81
|
+
return cmdSkills(rest);
|
|
82
|
+
case "subagent":
|
|
83
|
+
return cmdSubagent(rest);
|
|
84
|
+
case "tools":
|
|
85
|
+
return cmdTools(rest);
|
|
86
|
+
case "doctor":
|
|
87
|
+
return cmdDoctor(rest);
|
|
88
|
+
case "audit":
|
|
89
|
+
return cmdAudit(rest);
|
|
90
|
+
case "agents":
|
|
91
|
+
return cmdAgents(rest);
|
|
92
|
+
case "memory":
|
|
93
|
+
return cmdMemory(rest);
|
|
94
|
+
case "swarm":
|
|
95
|
+
return cmdSwarm(rest);
|
|
96
|
+
case "interactive":
|
|
97
|
+
return cmdInteractive(rest);
|
|
98
|
+
case "flow":
|
|
99
|
+
return cmdFlow(rest);
|
|
100
|
+
case "board":
|
|
101
|
+
return cmdBoard(rest);
|
|
102
|
+
case "init":
|
|
103
|
+
return cmdInit(rest);
|
|
104
|
+
case "logs":
|
|
105
|
+
return cmdLogs(rest);
|
|
106
|
+
case "stop":
|
|
107
|
+
return cmdStop(rest);
|
|
108
|
+
case "cost":
|
|
109
|
+
return cmdCost(rest);
|
|
110
|
+
case "completion":
|
|
111
|
+
return cmdCompletion(rest);
|
|
112
|
+
default:
|
|
113
|
+
logger.error(`unknown command: ${command}`);
|
|
114
|
+
printHelp();
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function commonFlags() {
|
|
119
|
+
return {
|
|
120
|
+
json: { type: "boolean", default: false },
|
|
121
|
+
cwd: { type: "string" },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function cmdDetect(args) {
|
|
125
|
+
const { values } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
126
|
+
const agents = detectAgents();
|
|
127
|
+
if (values.json) {
|
|
128
|
+
logger.out(JSON.stringify(agents, null, 2));
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
logger.out("Detected worker CLIs:");
|
|
132
|
+
for (const a of agents) {
|
|
133
|
+
const status = a.installed
|
|
134
|
+
? `installed${a.authenticated ? ", authenticated" : ""}${a.stableJsonStream ? ", json-stream" : ""}`
|
|
135
|
+
: "not found";
|
|
136
|
+
logger.out(` ${a.name.padEnd(14)} ${status}${a.binPath ? " (" + a.binPath + ")" : ""}`);
|
|
137
|
+
}
|
|
138
|
+
const { target, ambiguous } = resolveTarget("auto", agents);
|
|
139
|
+
logger.out(`\nauto target -> ${target}${ambiguous.length > 1 ? " (ambiguous: " + ambiguous.join(", ") + ")" : ""}`);
|
|
140
|
+
const advisory = authAdvisory(agents);
|
|
141
|
+
if (advisory)
|
|
142
|
+
logger.out(`\n! ${advisory}`);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
function cmdConfig(args) {
|
|
146
|
+
const sub = args[0];
|
|
147
|
+
if (sub === "get" || sub === undefined) {
|
|
148
|
+
logger.out(JSON.stringify(loadConfig(), null, 2));
|
|
149
|
+
logger.out(`\n(config file: ${configPath()})`);
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
if (sub === "set") {
|
|
153
|
+
const key = args[1];
|
|
154
|
+
const value = args[2];
|
|
155
|
+
if (!key || value === undefined) {
|
|
156
|
+
logger.error("usage: maq config set <key> <value>");
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const cfg = setConfigKey(key, value);
|
|
161
|
+
logger.out(`set ${key} = ${String(cfg[key])}`);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
logger.error(e.message);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (sub === "path") {
|
|
170
|
+
logger.out(configPath());
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
if (sub === "reset") {
|
|
174
|
+
saveConfig(loadConfig());
|
|
175
|
+
logger.out("config written with defaults merged");
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
logger.error(`unknown config subcommand: ${sub}`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
async function cmdScout(args) {
|
|
182
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
183
|
+
const task = positionals.join(" ").trim();
|
|
184
|
+
if (!task) {
|
|
185
|
+
logger.error('usage: maq scout "<task>"');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
const cwd = values.cwd ?? process.cwd();
|
|
189
|
+
const findings = await runScout(task, cwd);
|
|
190
|
+
logger.out(JSON.stringify(findings, null, values.json ? 0 : 2));
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
async function cmdPlan(args) {
|
|
194
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
195
|
+
const task = positionals.join(" ").trim();
|
|
196
|
+
if (!task) {
|
|
197
|
+
logger.error('usage: maq plan "<task>"');
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
const cwd = values.cwd ?? process.cwd();
|
|
201
|
+
const cfg = loadConfig();
|
|
202
|
+
const findings = await runScout(task, cwd);
|
|
203
|
+
const plan = await runPlan(findings, getProvider(cfg.provider), cfg.masterModel);
|
|
204
|
+
logger.out(JSON.stringify(plan, null, values.json ? 0 : 2));
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
async function cmdRun(args) {
|
|
208
|
+
const { values, positionals } = parseArgs({
|
|
209
|
+
args,
|
|
210
|
+
options: {
|
|
211
|
+
...commonFlags(),
|
|
212
|
+
target: { type: "string", short: "t" },
|
|
213
|
+
"dry-run": { type: "boolean", default: false },
|
|
214
|
+
provider: { type: "string" },
|
|
215
|
+
model: { type: "string" },
|
|
216
|
+
audit: { type: "boolean", default: false },
|
|
217
|
+
},
|
|
218
|
+
allowPositionals: true,
|
|
219
|
+
});
|
|
220
|
+
const task = positionals.join(" ").trim();
|
|
221
|
+
if (!task) {
|
|
222
|
+
logger.error('usage: maq run "<task>" [--target auto|claude|codex|gemini|none] [--dry-run] [--provider p] [--model m] [--audit]');
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
const cwd = values.cwd ?? process.cwd();
|
|
226
|
+
const streamJson = values.json;
|
|
227
|
+
const onEvent = (e) => {
|
|
228
|
+
if (streamJson) {
|
|
229
|
+
logger.out(JSON.stringify(e));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
renderEvent(e);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const result = await runPipeline(task, {
|
|
236
|
+
cwd,
|
|
237
|
+
target: values.target,
|
|
238
|
+
dryRun: values["dry-run"],
|
|
239
|
+
provider: values.provider,
|
|
240
|
+
model: values.model,
|
|
241
|
+
audit: values.audit,
|
|
242
|
+
onEvent,
|
|
243
|
+
});
|
|
244
|
+
if (!streamJson) {
|
|
245
|
+
logger.out("");
|
|
246
|
+
logger.out(`result: ${result.execute.status}, verified: ${result.verify.verified} (${result.verify.method})`);
|
|
247
|
+
if (result.cost) {
|
|
248
|
+
logger.out(`master cost: ${result.cost.calls} calls, ${result.cost.totalTokens} tok, $${result.cost.usd.toFixed(4)}`);
|
|
249
|
+
}
|
|
250
|
+
if (result.runId) {
|
|
251
|
+
logger.out(`audit: .maq/runs/${result.runId}/ (verify with: maq audit verify .maq/runs/${result.runId})`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result.verify.verified && result.execute.status !== "failed" ? 0 : 3;
|
|
255
|
+
}
|
|
256
|
+
async function cmdVerify(args) {
|
|
257
|
+
const { values } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
258
|
+
const cwd = values.cwd ?? process.cwd();
|
|
259
|
+
const testCmd = detectTestCommand(cwd);
|
|
260
|
+
const cfg = loadConfig();
|
|
261
|
+
const result = await runVerify({ status: "success", target: "none", command: null, exitCode: 0, stdout: "", stderr: "", filesChanged: [], errors: [] }, { cwd, provider: getProvider(cfg.provider), model: cfg.masterModel });
|
|
262
|
+
logger.out(JSON.stringify({ detectedTestCommand: testCmd, verify: result }, null, values.json ? 0 : 2));
|
|
263
|
+
return result.verified ? 0 : 3;
|
|
264
|
+
}
|
|
265
|
+
async function cmdServe(args) {
|
|
266
|
+
const { values } = parseArgs({
|
|
267
|
+
args,
|
|
268
|
+
options: {
|
|
269
|
+
host: { type: "string" },
|
|
270
|
+
port: { type: "string" },
|
|
271
|
+
token: { type: "string" },
|
|
272
|
+
cors: { type: "string" },
|
|
273
|
+
relay: { type: "string" },
|
|
274
|
+
device: { type: "string" },
|
|
275
|
+
"relay-token": { type: "string" },
|
|
276
|
+
},
|
|
277
|
+
allowPositionals: true,
|
|
278
|
+
});
|
|
279
|
+
const daemon = createDaemon({
|
|
280
|
+
host: values.host,
|
|
281
|
+
port: values.port ? Number(values.port) : undefined,
|
|
282
|
+
token: values.token,
|
|
283
|
+
corsOrigin: values.cors,
|
|
284
|
+
version: VERSION,
|
|
285
|
+
});
|
|
286
|
+
try {
|
|
287
|
+
const { host, port } = await daemon.listen();
|
|
288
|
+
logger.out(`maq daemon listening on http://${host}:${port}`);
|
|
289
|
+
logger.out(`auth token: ${daemon.token}`);
|
|
290
|
+
logger.out(` export MAQ_TOKEN=${daemon.token} # for 'maq sessions'`);
|
|
291
|
+
if (host === "0.0.0.0" || host === "::") {
|
|
292
|
+
logger.error("! SECURITY: bound to all interfaces — this exposes device control to the network. Prefer 127.0.0.1 + a tunnel/tailnet.");
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
logger.out("(loopback only; put a tunnel/tailnet in front for remote access)");
|
|
296
|
+
}
|
|
297
|
+
logger.out("endpoints: GET /health GET /v1/agents GET /v1/connectivity GET|POST /v1/sessions GET /v1/sessions/:id/events (SSE)");
|
|
298
|
+
// Optional: dial OUT to a relay so a phone can reach this daemon with no
|
|
299
|
+
// inbound ports (Tier 2). Pairing string for the app:
|
|
300
|
+
// maq://relay?url=<relay>&device=<id>&token=<relay-token>
|
|
301
|
+
let bridge;
|
|
302
|
+
const relayUrl = values.relay ?? process.env.MAQ_RELAY_URL;
|
|
303
|
+
if (relayUrl) {
|
|
304
|
+
const deviceId = values.device ?? process.env.MAQ_DEVICE_ID ?? daemon.token.slice(0, 12);
|
|
305
|
+
bridge = new RelayBridge({
|
|
306
|
+
url: relayUrl,
|
|
307
|
+
deviceId,
|
|
308
|
+
token: values["relay-token"] ?? process.env.RELAY_TOKEN,
|
|
309
|
+
registry: daemon.registry,
|
|
310
|
+
log: (m) => logger.out(m),
|
|
311
|
+
});
|
|
312
|
+
bridge.start();
|
|
313
|
+
logger.out(`relay bridge: dialing ${relayUrl} as device '${deviceId}'`);
|
|
314
|
+
logger.out(` app pairing: maq://relay?url=${encodeURIComponent(relayUrl)}&device=${deviceId}&token=<relay-token>`);
|
|
315
|
+
}
|
|
316
|
+
// Optional: outbound webhook forwarding (plugins).
|
|
317
|
+
const cfg = loadConfig();
|
|
318
|
+
if (cfg.webhookUrl) {
|
|
319
|
+
attachWebhook(daemon.registry, { url: cfg.webhookUrl, log: (m) => logger.error(m) });
|
|
320
|
+
logger.out(`webhook: forwarding lifecycle events to ${cfg.webhookUrl}`);
|
|
321
|
+
}
|
|
322
|
+
// Optional: scheduled flows (cron-style) run against this daemon.
|
|
323
|
+
let scheduler;
|
|
324
|
+
const flows = loadFlows().filter((f) => f.enabled);
|
|
325
|
+
if (flows.length) {
|
|
326
|
+
scheduler = new Scheduler((flow) => {
|
|
327
|
+
logger.out(`flow '${flow.name}' firing: ${flow.task}`);
|
|
328
|
+
daemon.registry.assign(flow.task, { target: flow.target, profile: flow.profile, cwd: flow.cwd });
|
|
329
|
+
}, (m) => logger.out(m));
|
|
330
|
+
scheduler.start(flows);
|
|
331
|
+
}
|
|
332
|
+
// Keep the process alive until interrupted.
|
|
333
|
+
await new Promise((resolve) => {
|
|
334
|
+
const stop = () => {
|
|
335
|
+
logger.out("\nshutting down...");
|
|
336
|
+
bridge?.stop();
|
|
337
|
+
scheduler?.stop();
|
|
338
|
+
daemon.close().then(resolve);
|
|
339
|
+
};
|
|
340
|
+
process.on("SIGINT", stop);
|
|
341
|
+
process.on("SIGTERM", stop);
|
|
342
|
+
});
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
logger.error(`failed to start daemon: ${e.message}`);
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function daemonBase(values) {
|
|
351
|
+
const host = values.host ?? process.env.MAQ_HOST ?? "127.0.0.1";
|
|
352
|
+
const port = values.port ? Number(values.port) : Number(process.env.MAQ_PORT ?? 7717);
|
|
353
|
+
const token = values.token ?? process.env.MAQ_TOKEN ?? "";
|
|
354
|
+
return { base: `http://${host}:${port}`, token };
|
|
355
|
+
}
|
|
356
|
+
async function cmdSessions(args) {
|
|
357
|
+
const { values, positionals } = parseArgs({
|
|
358
|
+
args,
|
|
359
|
+
options: { ...commonFlags(), host: { type: "string" }, port: { type: "string" }, token: { type: "string" } },
|
|
360
|
+
allowPositionals: true,
|
|
361
|
+
});
|
|
362
|
+
const { base, token } = daemonBase(values);
|
|
363
|
+
if (!token) {
|
|
364
|
+
logger.error("no token: set MAQ_TOKEN (printed by 'maq serve') or pass --token");
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
const id = positionals[0];
|
|
368
|
+
const action = positionals[1];
|
|
369
|
+
if (id && action) {
|
|
370
|
+
if (!["pause", "resume", "cancel"].includes(action)) {
|
|
371
|
+
logger.error("action must be pause|resume|cancel");
|
|
372
|
+
return 1;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const res = await fetch(`${base}/v1/sessions/${encodeURIComponent(id)}/control`, {
|
|
376
|
+
method: "POST",
|
|
377
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
378
|
+
body: JSON.stringify({ action }),
|
|
379
|
+
});
|
|
380
|
+
const body = await res.json();
|
|
381
|
+
logger.out(JSON.stringify(body, null, values.json ? 0 : 2));
|
|
382
|
+
return res.ok ? 0 : 1;
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
logger.error(`cannot reach daemon at ${base}: ${e.message} (is 'maq serve' running?)`);
|
|
386
|
+
return 1;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const path = id ? `/v1/sessions/${encodeURIComponent(id)}` : "/v1/sessions";
|
|
390
|
+
try {
|
|
391
|
+
const res = await fetch(base + path, { headers: { authorization: `Bearer ${token}` } });
|
|
392
|
+
const body = await res.json();
|
|
393
|
+
if (!res.ok) {
|
|
394
|
+
logger.error(`daemon ${res.status}: ${JSON.stringify(body)}`);
|
|
395
|
+
return 1;
|
|
396
|
+
}
|
|
397
|
+
logger.out(JSON.stringify(body, null, values.json ? 0 : 2));
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
catch (e) {
|
|
401
|
+
logger.error(`cannot reach daemon at ${base}: ${e.message} (is 'maq serve' running?)`);
|
|
402
|
+
return 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function cmdModels(args) {
|
|
406
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
407
|
+
const sub = positionals[0];
|
|
408
|
+
const pretty = values.json ? 0 : 2;
|
|
409
|
+
if (sub === "list") {
|
|
410
|
+
logger.out(JSON.stringify(masterOptions(), null, pretty));
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
if (sub === "cheapest") {
|
|
414
|
+
const best = cheapestMaster();
|
|
415
|
+
logger.out(JSON.stringify(best, null, pretty));
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
if (sub === "auto") {
|
|
419
|
+
const best = cheapestMaster();
|
|
420
|
+
setConfigKey("provider", best.provider);
|
|
421
|
+
setConfigKey("cheapModel", best.model);
|
|
422
|
+
setConfigKey("masterModel", best.model);
|
|
423
|
+
const cfg = loadConfig();
|
|
424
|
+
// Only overwrite the strong tier if it's still the default placeholder.
|
|
425
|
+
if (cfg.strongModel === "heuristic-local")
|
|
426
|
+
setConfigKey("strongModel", best.model);
|
|
427
|
+
logger.out(`master auto-configured for lowest cost:`);
|
|
428
|
+
logger.out(` provider = ${best.provider}`);
|
|
429
|
+
logger.out(` cheapModel = ${best.model} (${best.label}, ${best.costClass})`);
|
|
430
|
+
logger.out(` reason: ${best.reason}`);
|
|
431
|
+
logger.out(`\ntip: set a stronger 'strongModel' for the hardest Plan steps: maq config set strongModel <model>`);
|
|
432
|
+
return 0;
|
|
433
|
+
}
|
|
434
|
+
const cfg = loadConfig();
|
|
435
|
+
const info = {
|
|
436
|
+
provider: cfg.provider,
|
|
437
|
+
tiers: {
|
|
438
|
+
cheap: { model: cfg.cheapModel, price: priceFor(cfg.cheapModel) },
|
|
439
|
+
strong: { model: cfg.strongModel, price: priceFor(cfg.strongModel) },
|
|
440
|
+
},
|
|
441
|
+
masterModel: cfg.masterModel,
|
|
442
|
+
available: providerAvailable(cfg.provider),
|
|
443
|
+
envHints: providerEnvHint(cfg.provider),
|
|
444
|
+
cheapestAvailable: cheapestMaster().label,
|
|
445
|
+
};
|
|
446
|
+
logger.out(JSON.stringify(info, null, pretty));
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
function providerAvailable(name) {
|
|
450
|
+
try {
|
|
451
|
+
getProvider(name, { strict: true });
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function providerEnvHint(name) {
|
|
459
|
+
if (name.toLowerCase().startsWith("cli"))
|
|
460
|
+
return "reuses an authenticated worker CLI as the master ($0 marginal)";
|
|
461
|
+
switch (name.toLowerCase()) {
|
|
462
|
+
case "openai":
|
|
463
|
+
return "set OPENAI_API_KEY (optional OPENAI_BASE_URL)";
|
|
464
|
+
case "anthropic":
|
|
465
|
+
return "set ANTHROPIC_API_KEY";
|
|
466
|
+
case "groq":
|
|
467
|
+
return "set GROQ_API_KEY";
|
|
468
|
+
case "ollama":
|
|
469
|
+
return "run Ollama locally (optional OLLAMA_HOST)";
|
|
470
|
+
case "openai-compatible":
|
|
471
|
+
case "litellm":
|
|
472
|
+
return "set MAQ_PROVIDER_BASE_URL (+ MAQ_PROVIDER_API_KEY)";
|
|
473
|
+
default:
|
|
474
|
+
return "offline; no key required";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function cmdProbe(args) {
|
|
478
|
+
const { values } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
479
|
+
const result = await probeConnectivity();
|
|
480
|
+
logger.out(JSON.stringify(result, null, values.json ? 0 : 2));
|
|
481
|
+
return result.decision.tier === "blocked" ? 2 : 0;
|
|
482
|
+
}
|
|
483
|
+
function cmdSkills(args) {
|
|
484
|
+
const { values, positionals } = parseArgs({
|
|
485
|
+
args,
|
|
486
|
+
options: { ...commonFlags(), tier: { type: "string" } },
|
|
487
|
+
allowPositionals: true,
|
|
488
|
+
});
|
|
489
|
+
const cwd = values.cwd ?? process.cwd();
|
|
490
|
+
const cfg = loadConfig();
|
|
491
|
+
const sub = positionals[0];
|
|
492
|
+
const mgr = new SkillsManager(cwd, { skillsDir: cfg.skillsDir });
|
|
493
|
+
if (sub === "init") {
|
|
494
|
+
const written = initSkills(cwd, cfg.skillsDir);
|
|
495
|
+
logger.out(written.length ? `wrote:\n ${written.join("\n ")}` : "nothing to write (files already exist)");
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
if (sub === "path") {
|
|
499
|
+
logger.out(`${cwd}/${cfg.skillsDir}`);
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
const tier = values.tier === "strong" ? "strong" : "cheap";
|
|
503
|
+
const skills = mgr.forTier(tier);
|
|
504
|
+
const out = {
|
|
505
|
+
tier,
|
|
506
|
+
skillsDir: cfg.skillsDir,
|
|
507
|
+
count: skills.length,
|
|
508
|
+
skills: skills.map((s) => ({ name: s.name, tier: s.tier, kind: s.kind, source: s.path ?? "built-in" })),
|
|
509
|
+
contextBlockChars: mgr.contextBlock(tier).length,
|
|
510
|
+
};
|
|
511
|
+
logger.out(JSON.stringify(out, null, values.json ? 0 : 2));
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
async function cmdSubagent(args) {
|
|
515
|
+
const { values, positionals } = parseArgs({
|
|
516
|
+
args,
|
|
517
|
+
options: { ...commonFlags(), context: { type: "string" }, tokens: { type: "string" } },
|
|
518
|
+
allowPositionals: true,
|
|
519
|
+
});
|
|
520
|
+
const task = positionals.join(" ").trim();
|
|
521
|
+
if (!task) {
|
|
522
|
+
logger.error('usage: maq subagent "<task>" [--context <text>] [--tokens 1500]');
|
|
523
|
+
return 1;
|
|
524
|
+
}
|
|
525
|
+
const cfg = loadConfig();
|
|
526
|
+
const route = new Router(cfg).route("cheap");
|
|
527
|
+
const result = await runSubagent({ task, context: values.context, summaryTokens: values.tokens ? Number(values.tokens) : undefined }, route.provider, route.model);
|
|
528
|
+
logger.out(JSON.stringify(result, null, values.json ? 0 : 2));
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
async function cmdTools(args) {
|
|
532
|
+
const { values, positionals } = parseArgs({
|
|
533
|
+
args,
|
|
534
|
+
options: { ...commonFlags(), args: { type: "string" } },
|
|
535
|
+
allowPositionals: true,
|
|
536
|
+
});
|
|
537
|
+
const cwd = values.cwd ?? process.cwd();
|
|
538
|
+
const registry = createToolRegistry({ cwd });
|
|
539
|
+
const name = positionals[0];
|
|
540
|
+
if (!name) {
|
|
541
|
+
logger.out(JSON.stringify({ netEnabled: registry.netEnabled, tools: registry.schemas() }, null, values.json ? 0 : 2));
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
544
|
+
let toolArgs = {};
|
|
545
|
+
if (values.args) {
|
|
546
|
+
try {
|
|
547
|
+
toolArgs = JSON.parse(values.args);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
logger.error("--args must be JSON");
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const result = await registry.run(name, toolArgs);
|
|
556
|
+
logger.out(typeof result === "string" ? result : JSON.stringify(result, null, values.json ? 0 : 2));
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
logger.error(e.message);
|
|
561
|
+
return 1;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async function cmdDoctor(args) {
|
|
565
|
+
const { values } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
566
|
+
const cfg = loadConfig();
|
|
567
|
+
const agents = detectAgents();
|
|
568
|
+
const best = cheapestMaster();
|
|
569
|
+
const probe = await probeConnectivity();
|
|
570
|
+
const report = {
|
|
571
|
+
config: {
|
|
572
|
+
provider: cfg.provider,
|
|
573
|
+
cheapModel: cfg.cheapModel,
|
|
574
|
+
strongModel: cfg.strongModel,
|
|
575
|
+
providerAvailable: providerAvailable(cfg.provider),
|
|
576
|
+
},
|
|
577
|
+
workerAgents: agents.filter((a) => a.installed).map((a) => ({ name: a.name, authenticated: a.authenticated })),
|
|
578
|
+
masterAuto: { pick: best.label, provider: best.provider, model: best.model, costClass: best.costClass },
|
|
579
|
+
connectivity: { tier: probe.decision.tier, reason: probe.decision.reason, stun: probe.details.stun.ok, relay: probe.details.relay.ok },
|
|
580
|
+
skills: new SkillsManager(process.cwd(), { skillsDir: cfg.skillsDir }).load().length,
|
|
581
|
+
aiAdvisory: authAdvisory(agents),
|
|
582
|
+
};
|
|
583
|
+
const healthy = report.config.providerAvailable || best.available;
|
|
584
|
+
if (values.json) {
|
|
585
|
+
logger.out(JSON.stringify({ healthy, ...report }, null, 0));
|
|
586
|
+
return healthy ? 0 : 1;
|
|
587
|
+
}
|
|
588
|
+
logger.out("MAQ doctor");
|
|
589
|
+
logger.out(` provider: ${report.config.provider} (${report.config.providerAvailable ? "available" : "unavailable"})`);
|
|
590
|
+
logger.out(` tiers: cheap=${report.config.cheapModel} strong=${report.config.strongModel}`);
|
|
591
|
+
logger.out(` master auto: ${report.masterAuto.pick} [${report.masterAuto.costClass}]`);
|
|
592
|
+
logger.out(` worker CLIs: ${report.workerAgents.map((a) => a.name + (a.authenticated ? "*" : "")).join(", ") || "none"} (*=authenticated)`);
|
|
593
|
+
logger.out(` connectivity: tier=${report.connectivity.tier} stun=${report.connectivity.stun} relay=${report.connectivity.relay}`);
|
|
594
|
+
logger.out(` skills: ${report.skills} loaded`);
|
|
595
|
+
if (report.aiAdvisory)
|
|
596
|
+
logger.out(` ! AI: ${report.aiAdvisory}`);
|
|
597
|
+
logger.out(` status: ${healthy ? "OK" : "no usable master provider — run 'maq models auto'"}`);
|
|
598
|
+
return healthy ? 0 : 1;
|
|
599
|
+
}
|
|
600
|
+
function cmdAudit(args) {
|
|
601
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
602
|
+
const sub = positionals[0];
|
|
603
|
+
if (sub !== "verify") {
|
|
604
|
+
logger.error("usage: maq audit verify <run-dir>");
|
|
605
|
+
return 1;
|
|
606
|
+
}
|
|
607
|
+
const dir = positionals[1];
|
|
608
|
+
if (!dir) {
|
|
609
|
+
logger.error("usage: maq audit verify <run-dir> (e.g. .maq/runs/<id>)");
|
|
610
|
+
return 1;
|
|
611
|
+
}
|
|
612
|
+
const res = verifyAuditLog(dir);
|
|
613
|
+
logger.out(JSON.stringify(res, null, values.json ? 0 : 2));
|
|
614
|
+
return res.ok ? 0 : 2;
|
|
615
|
+
}
|
|
616
|
+
function cmdAgents(args) {
|
|
617
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
618
|
+
const cwd = values.cwd ?? process.cwd();
|
|
619
|
+
const mgr = new ProfileManager(cwd);
|
|
620
|
+
if (positionals[0] === "init") {
|
|
621
|
+
const written = mgr.init();
|
|
622
|
+
logger.out(written.length ? `wrote:\n ${written.join("\n ")}` : "nothing to write (files already exist)");
|
|
623
|
+
return 0;
|
|
624
|
+
}
|
|
625
|
+
const profiles = mgr.load();
|
|
626
|
+
if (positionals[0] === "show" && positionals[1]) {
|
|
627
|
+
const p = mgr.get(positionals[1]);
|
|
628
|
+
if (!p) {
|
|
629
|
+
logger.error(`no such profile: ${positionals[1]}`);
|
|
630
|
+
return 1;
|
|
631
|
+
}
|
|
632
|
+
logger.out(JSON.stringify(p, null, values.json ? 0 : 2));
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
logger.out(JSON.stringify(profiles.map((p) => ({ name: p.name, role: p.role, target: p.target, provider: p.provider, allowedTools: p.allowedTools, source: p.path ?? "built-in" })), null, values.json ? 0 : 2));
|
|
636
|
+
return 0;
|
|
637
|
+
}
|
|
638
|
+
function cmdMemory(args) {
|
|
639
|
+
const { values, positionals } = parseArgs({
|
|
640
|
+
args,
|
|
641
|
+
options: { ...commonFlags(), tags: { type: "string" }, limit: { type: "string" } },
|
|
642
|
+
allowPositionals: true,
|
|
643
|
+
});
|
|
644
|
+
const cwd = values.cwd ?? process.cwd();
|
|
645
|
+
const mem = new RecallMemory(cwd);
|
|
646
|
+
const sub = positionals[0];
|
|
647
|
+
if (sub === "store") {
|
|
648
|
+
const key = positionals[1];
|
|
649
|
+
const text = positionals.slice(2).join(" ");
|
|
650
|
+
if (!key || !text) {
|
|
651
|
+
logger.error('usage: maq memory store <key> "<text>" [--tags a,b]');
|
|
652
|
+
return 1;
|
|
653
|
+
}
|
|
654
|
+
const tags = values.tags?.split(/[,\s]+/).filter(Boolean) ?? [];
|
|
655
|
+
const note = mem.store(key, text, tags);
|
|
656
|
+
logger.out(`stored: ${note.key}`);
|
|
657
|
+
return 0;
|
|
658
|
+
}
|
|
659
|
+
if (sub === "recall") {
|
|
660
|
+
const q = positionals.slice(1).join(" ");
|
|
661
|
+
const limit = values.limit ? Number(values.limit) : 5;
|
|
662
|
+
logger.out(JSON.stringify(mem.recall(q, limit), null, values.json ? 0 : 2));
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
if (sub === "list") {
|
|
666
|
+
logger.out(JSON.stringify(mem.all(), null, values.json ? 0 : 2));
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
669
|
+
logger.error("usage: maq memory [store <key> \"<text>\" | recall <query> | list]");
|
|
670
|
+
return 1;
|
|
671
|
+
}
|
|
672
|
+
async function cmdSwarm(args) {
|
|
673
|
+
const { values, positionals } = parseArgs({
|
|
674
|
+
args,
|
|
675
|
+
options: { ...commonFlags(), target: { type: "string", short: "t" }, concurrency: { type: "string" }, "dry-run": { type: "boolean", default: false } },
|
|
676
|
+
allowPositionals: true,
|
|
677
|
+
});
|
|
678
|
+
const tasks = positionals.map((t) => t.trim()).filter(Boolean);
|
|
679
|
+
if (tasks.length === 0) {
|
|
680
|
+
logger.error('usage: maq swarm "<task1>" "<task2>" ... [--target none] [--concurrency N] [--dry-run]');
|
|
681
|
+
return 1;
|
|
682
|
+
}
|
|
683
|
+
const cwd = values.cwd ?? process.cwd();
|
|
684
|
+
const registry = new SessionRegistry();
|
|
685
|
+
const result = await runSwarm(registry, tasks.map((task) => ({ task, target: values.target, cwd, dryRun: values["dry-run"] })), { maxConcurrency: values.concurrency ? Number(values.concurrency) : undefined });
|
|
686
|
+
logger.out(JSON.stringify(result, null, values.json ? 0 : 2));
|
|
687
|
+
return result.allVerified ? 0 : 3;
|
|
688
|
+
}
|
|
689
|
+
async function cmdInteractive(args) {
|
|
690
|
+
const { values, positionals } = parseArgs({
|
|
691
|
+
args,
|
|
692
|
+
options: { ...commonFlags(), target: { type: "string", short: "t" } },
|
|
693
|
+
allowPositionals: true,
|
|
694
|
+
});
|
|
695
|
+
const task = positionals.join(" ").trim();
|
|
696
|
+
const requested = values.target ?? "auto";
|
|
697
|
+
const agents = detectAgents();
|
|
698
|
+
const { target } = resolveTarget(requested, agents);
|
|
699
|
+
const spec = agentSpec(target);
|
|
700
|
+
const binPath = agents.find((a) => a.name === target)?.binPath ?? null;
|
|
701
|
+
if (!spec || !spec.headless || !binPath) {
|
|
702
|
+
logger.error(`interactive needs a runnable agent CLI; '${target}' is not available. Try: maq detect`);
|
|
703
|
+
return 1;
|
|
704
|
+
}
|
|
705
|
+
const cwd = values.cwd ?? process.cwd();
|
|
706
|
+
const argv = spec.headless.map((a) => (a === "{task}" ? task : a));
|
|
707
|
+
const worker = new InteractiveWorker(binPath, argv, {
|
|
708
|
+
cwd,
|
|
709
|
+
onEvent: (e) => renderEvent(e),
|
|
710
|
+
onStatus: (s) => logger.out(` [status: ${s}]`),
|
|
711
|
+
});
|
|
712
|
+
worker.start();
|
|
713
|
+
logger.out(`interactive ${target} started; type to steer, Ctrl-D to end.`);
|
|
714
|
+
// Forward this terminal's stdin to the worker (inbox → stdin steering).
|
|
715
|
+
process.stdin.setEncoding("utf8");
|
|
716
|
+
process.stdin.on("data", (chunk) => worker.write(String(chunk).replace(/\r?\n$/, "")));
|
|
717
|
+
process.stdin.on("end", () => worker.end());
|
|
718
|
+
const code = await worker.wait();
|
|
719
|
+
return code === 0 ? 0 : 2;
|
|
720
|
+
}
|
|
721
|
+
function cmdFlow(args) {
|
|
722
|
+
const { values, positionals } = parseArgs({
|
|
723
|
+
args,
|
|
724
|
+
options: { ...commonFlags(), schedule: { type: "string" }, target: { type: "string", short: "t" }, profile: { type: "string" } },
|
|
725
|
+
allowPositionals: true,
|
|
726
|
+
});
|
|
727
|
+
const sub = positionals[0];
|
|
728
|
+
if (sub === "list") {
|
|
729
|
+
logger.out(JSON.stringify(loadFlows(), null, values.json ? 0 : 2));
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
if (sub === "add") {
|
|
733
|
+
const name = positionals[1];
|
|
734
|
+
const task = positionals.slice(2).join(" ");
|
|
735
|
+
const schedule = values.schedule;
|
|
736
|
+
if (!name || !task || !schedule) {
|
|
737
|
+
logger.error('usage: maq flow add <name> "<task>" --schedule "@daily|every 30m" [--target t] [--profile p]');
|
|
738
|
+
return 1;
|
|
739
|
+
}
|
|
740
|
+
if (scheduleMs(schedule) === null) {
|
|
741
|
+
logger.error(`invalid schedule '${schedule}'. Use @hourly|@daily|@weekly or "every 30m|2h|45s".`);
|
|
742
|
+
return 1;
|
|
743
|
+
}
|
|
744
|
+
addFlow({ name, task, schedule, target: values.target, profile: values.profile, enabled: true });
|
|
745
|
+
logger.out(`flow '${name}' added (${schedule}). Runs when 'maq serve' is active.`);
|
|
746
|
+
return 0;
|
|
747
|
+
}
|
|
748
|
+
if (sub === "remove" || sub === "rm") {
|
|
749
|
+
const name = positionals[1];
|
|
750
|
+
if (!name) {
|
|
751
|
+
logger.error("usage: maq flow remove <name>");
|
|
752
|
+
return 1;
|
|
753
|
+
}
|
|
754
|
+
removeFlow(name);
|
|
755
|
+
logger.out(`flow '${name}' removed`);
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
logger.error("usage: maq flow [list | add <name> \"<task>\" --schedule <s> | remove <name>]");
|
|
759
|
+
return 1;
|
|
760
|
+
}
|
|
761
|
+
function cmdHelp(args) {
|
|
762
|
+
const topic = args[0];
|
|
763
|
+
if (!topic) {
|
|
764
|
+
printHelp();
|
|
765
|
+
const all = listTopics();
|
|
766
|
+
logger.out(`\ntopics: ${all.map((t) => t.name).join(", ")}`);
|
|
767
|
+
logger.out(" run: maq help <topic>");
|
|
768
|
+
return 0;
|
|
769
|
+
}
|
|
770
|
+
const found = getTopic(topic);
|
|
771
|
+
if (!found) {
|
|
772
|
+
const all = listTopics();
|
|
773
|
+
logger.error(`unknown topic: ${topic}`);
|
|
774
|
+
logger.out(`\navailable topics: ${all.map((t) => t.name).join(", ")}`);
|
|
775
|
+
return 2;
|
|
776
|
+
}
|
|
777
|
+
logger.out(renderTopic(found));
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
function cmdBoard(args) {
|
|
781
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
782
|
+
const cwd = values.cwd ?? process.cwd();
|
|
783
|
+
const board = new Board(cwd);
|
|
784
|
+
const sub = positionals[0];
|
|
785
|
+
if (sub === "show") {
|
|
786
|
+
const taskId = positionals[1];
|
|
787
|
+
if (!taskId) {
|
|
788
|
+
logger.error("usage: maq board show <task-id>");
|
|
789
|
+
return 2;
|
|
790
|
+
}
|
|
791
|
+
const result = board.showTask(taskId);
|
|
792
|
+
if (!result) {
|
|
793
|
+
logger.error(`no task found: ${taskId}`);
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
796
|
+
logger.out(JSON.stringify(result, null, values.json ? 0 : 2));
|
|
797
|
+
return 0;
|
|
798
|
+
}
|
|
799
|
+
if (sub === "tail") {
|
|
800
|
+
const limit = positionals[1] ? Number(positionals[1]) : 5;
|
|
801
|
+
const tasks = board.listTasks(limit);
|
|
802
|
+
if (values.json) {
|
|
803
|
+
logger.out(JSON.stringify(tasks, null, 0));
|
|
804
|
+
return 0;
|
|
805
|
+
}
|
|
806
|
+
if (tasks.length === 0) {
|
|
807
|
+
logger.out("no tasks recorded yet");
|
|
808
|
+
return 0;
|
|
809
|
+
}
|
|
810
|
+
for (const t of tasks) {
|
|
811
|
+
const phases = t.phases.map((p) => `${p.phase}:${p.status}`).join(" \u2192 ");
|
|
812
|
+
logger.out(` ${t.taskId} ${t.status.padEnd(7)} ${phases} "${t.task.slice(0, 60)}"`);
|
|
813
|
+
}
|
|
814
|
+
return 0;
|
|
815
|
+
}
|
|
816
|
+
// Default: list
|
|
817
|
+
const limit = positionals[1] ? Number(positionals[1]) : 20;
|
|
818
|
+
const tasks = board.listTasks(limit);
|
|
819
|
+
logger.out(JSON.stringify(tasks, null, values.json ? 0 : 2));
|
|
820
|
+
return 0;
|
|
821
|
+
}
|
|
822
|
+
async function cmdInit(args) {
|
|
823
|
+
const { values } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
824
|
+
const cwd = values.cwd ?? process.cwd();
|
|
825
|
+
const result = await runInit(cwd);
|
|
826
|
+
return result.configured ? 0 : 1;
|
|
827
|
+
}
|
|
828
|
+
function cmdLogs(args) {
|
|
829
|
+
const { values, positionals } = parseArgs({
|
|
830
|
+
args,
|
|
831
|
+
options: { ...commonFlags(), limit: { type: "string" } },
|
|
832
|
+
allowPositionals: true,
|
|
833
|
+
});
|
|
834
|
+
const cwd = values.cwd ?? process.cwd();
|
|
835
|
+
const board = new Board(cwd);
|
|
836
|
+
const taskId = positionals[0];
|
|
837
|
+
if (taskId) {
|
|
838
|
+
const entries = board.getEntries(taskId);
|
|
839
|
+
if (entries.length === 0) {
|
|
840
|
+
logger.error(`no log entries for task: ${taskId}`);
|
|
841
|
+
return 1;
|
|
842
|
+
}
|
|
843
|
+
if (values.json) {
|
|
844
|
+
logger.out(JSON.stringify(entries, null, 0));
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
for (const e of entries) {
|
|
848
|
+
const phase = e.phase ? ` [${e.phase}]` : "";
|
|
849
|
+
logger.out(` ${e.ts} ${e.type}${phase} ${JSON.stringify(e.data)}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return 0;
|
|
853
|
+
}
|
|
854
|
+
const limit = values.limit ? Number(values.limit) : 10;
|
|
855
|
+
const tasks = board.listTasks(limit);
|
|
856
|
+
if (values.json) {
|
|
857
|
+
logger.out(JSON.stringify(tasks, null, 0));
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
if (tasks.length === 0) {
|
|
861
|
+
logger.out("no tasks recorded yet. run: maq run \"<task>\"");
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
logger.out("Recent tasks (use 'maq logs <task-id>' for details):\n");
|
|
865
|
+
for (const t of tasks) {
|
|
866
|
+
const dur = t.finishedAt ? ` (${timeDiff(t.startedAt, t.finishedAt)})` : "";
|
|
867
|
+
logger.out(` ${t.taskId} ${t.status.padEnd(7)}${dur} "${t.task.slice(0, 60)}"`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return 0;
|
|
871
|
+
}
|
|
872
|
+
function timeDiff(a, b) {
|
|
873
|
+
const ms = new Date(b).getTime() - new Date(a).getTime();
|
|
874
|
+
if (ms < 1000)
|
|
875
|
+
return `${ms}ms`;
|
|
876
|
+
const s = Math.round(ms / 1000);
|
|
877
|
+
if (s < 60)
|
|
878
|
+
return `${s}s`;
|
|
879
|
+
const m = Math.floor(s / 60);
|
|
880
|
+
return `${m}m ${s % 60}s`;
|
|
881
|
+
}
|
|
882
|
+
function cmdStop(args) {
|
|
883
|
+
const { values, positionals } = parseArgs({
|
|
884
|
+
args,
|
|
885
|
+
options: commonFlags(),
|
|
886
|
+
allowPositionals: true,
|
|
887
|
+
});
|
|
888
|
+
const taskId = positionals[0];
|
|
889
|
+
if (!taskId) {
|
|
890
|
+
logger.error("usage: maq stop <task-id>");
|
|
891
|
+
return 2;
|
|
892
|
+
}
|
|
893
|
+
const cwd = values.cwd ?? process.cwd();
|
|
894
|
+
const board = new Board(cwd);
|
|
895
|
+
board.taskAbort(taskId, "stopped by user (maq stop)");
|
|
896
|
+
logger.out(`task ${taskId} marked as aborted`);
|
|
897
|
+
return 0;
|
|
898
|
+
}
|
|
899
|
+
function cmdCost(args) {
|
|
900
|
+
const { values, positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
901
|
+
const cwd = values.cwd ?? process.cwd();
|
|
902
|
+
const tracker = new CostTracker(cwd);
|
|
903
|
+
const sub = positionals[0];
|
|
904
|
+
if (sub === "reset") {
|
|
905
|
+
tracker.reset();
|
|
906
|
+
logger.out("cost history cleared");
|
|
907
|
+
return 0;
|
|
908
|
+
}
|
|
909
|
+
const report = tracker.report();
|
|
910
|
+
if (values.json) {
|
|
911
|
+
logger.out(JSON.stringify(report, null, 0));
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
logger.out(CostTracker.renderReport(report));
|
|
915
|
+
}
|
|
916
|
+
return 0;
|
|
917
|
+
}
|
|
918
|
+
function cmdCompletion(args) {
|
|
919
|
+
const { positionals } = parseArgs({ args, options: commonFlags(), allowPositionals: true });
|
|
920
|
+
const shell = positionals[0] ?? detectShell();
|
|
921
|
+
if (!shell || !["bash", "zsh", "fish", "powershell"].includes(shell)) {
|
|
922
|
+
logger.error("usage: maq completion <bash|zsh|fish|powershell>");
|
|
923
|
+
logger.out("\nadd to your shell profile:");
|
|
924
|
+
logger.out(' bash: eval "$(maq completion bash)"');
|
|
925
|
+
logger.out(' zsh: eval "$(maq completion zsh)"');
|
|
926
|
+
logger.out(" fish: maq completion fish | source");
|
|
927
|
+
logger.out(" powershell: maq completion powershell | Invoke-Expression");
|
|
928
|
+
return 2;
|
|
929
|
+
}
|
|
930
|
+
logger.out(generateCompletion(shell));
|
|
931
|
+
return 0;
|
|
932
|
+
}
|
|
933
|
+
function renderEvent(e) {
|
|
934
|
+
switch (e.type) {
|
|
935
|
+
case "task.started":
|
|
936
|
+
logger.out(`> task: ${String(e.data.task)} [provider=${String(e.data.provider)} cheap=${String(e.data.cheapModel)} strong=${String(e.data.strongModel)}]`);
|
|
937
|
+
break;
|
|
938
|
+
case "phase.started":
|
|
939
|
+
logger.out(` - ${String(e.data.phase)} ...`);
|
|
940
|
+
break;
|
|
941
|
+
case "phase.done":
|
|
942
|
+
logger.out(` = ${String(e.data.phase)} done ${summarize(e.data)}`);
|
|
943
|
+
break;
|
|
944
|
+
case "task.done":
|
|
945
|
+
logger.out(`> done (verified=${String(e.data.verified)}, status=${String(e.data.status)})`);
|
|
946
|
+
break;
|
|
947
|
+
case "agent.stdout":
|
|
948
|
+
logger.out(` | ${String(e.data.text)}`);
|
|
949
|
+
break;
|
|
950
|
+
case "agent.stderr":
|
|
951
|
+
logger.out(` ! ${String(e.data.text)}`);
|
|
952
|
+
break;
|
|
953
|
+
case "tool.call":
|
|
954
|
+
logger.out(` · tool: ${e.data.tool ? String(e.data.tool) : JSON.stringify(e.data.command ?? e.data.raw ?? {})}`);
|
|
955
|
+
break;
|
|
956
|
+
case "task.paused":
|
|
957
|
+
logger.out("> paused");
|
|
958
|
+
break;
|
|
959
|
+
case "task.resumed":
|
|
960
|
+
logger.out("> resumed");
|
|
961
|
+
break;
|
|
962
|
+
case "task.cancelled":
|
|
963
|
+
logger.out("> cancelled");
|
|
964
|
+
break;
|
|
965
|
+
case "task.error":
|
|
966
|
+
logger.out(`> error: ${String(e.data.message)}`);
|
|
967
|
+
break;
|
|
968
|
+
default:
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function summarize(data) {
|
|
973
|
+
const drop = new Set(["phase"]);
|
|
974
|
+
const parts = Object.entries(data)
|
|
975
|
+
.filter(([k]) => !drop.has(k))
|
|
976
|
+
.map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
977
|
+
return parts.length ? `(${parts.join(", ")})` : "";
|
|
978
|
+
}
|
|
979
|
+
function printHelp() {
|
|
980
|
+
logger.out([
|
|
981
|
+
"maqcli — MAQ master orchestrator (Scout -> Plan -> Execute -> Verify)",
|
|
982
|
+
"",
|
|
983
|
+
"Usage: maq <command> [options]",
|
|
984
|
+
"",
|
|
985
|
+
"Commands:",
|
|
986
|
+
" detect Scan PATH + auth dirs for worker CLIs",
|
|
987
|
+
' scout "<task>" Read-only recon; prints structured findings',
|
|
988
|
+
' plan "<task>" Verifier-gated candidate plan (scout + plan)',
|
|
989
|
+
' run "<task>" [opts] Full pipeline; dispatch to a worker or raw',
|
|
990
|
+
" verify [--cwd <dir>] Run project tests / cross-model review",
|
|
991
|
+
" serve [--host --port --token] Start the HTTP+SSE daemon (app seam)",
|
|
992
|
+
" sessions [<id>] [pause|resume|cancel] List/inspect/control daemon sessions (needs MAQ_TOKEN)",
|
|
993
|
+
" models [list|cheapest|auto] Show/optimize the master model ($0 intelligence layer)",
|
|
994
|
+
" probe Probe connectivity tier (STUN/UDP + TCP + LAN)",
|
|
995
|
+
" skills [init|path] [--tier] List/scaffold the skills/rules layer (tier-aware)",
|
|
996
|
+
' subagent "<task>" [--context] Run an isolated sub-agent; returns a concise summary',
|
|
997
|
+
" tools [<name>] [--args JSON] List safe tools, or run one (read_file/list_dir/grep_text/...)",
|
|
998
|
+
" doctor One-shot health check (provider, agents, connectivity, skills)",
|
|
999
|
+
" agents [list|init|show <n>] Named agent profiles + tool restrictions",
|
|
1000
|
+
' memory [store|recall|list] Recall memory store (injected into planning)',
|
|
1001
|
+
' swarm "<t1>" "<t2>" … Run tasks across parallel workers (supervisor join)',
|
|
1002
|
+
' interactive [-t <agent>] Attach a steerable interactive worker (stdin steering)',
|
|
1003
|
+
' flow [list|add|remove] Scheduled agent sessions (run under maq serve)',
|
|
1004
|
+
" board [show|list|tail] Inspect the shared state board",
|
|
1005
|
+
" init First-time setup wizard",
|
|
1006
|
+
" logs [<task-id>] View structured run logs",
|
|
1007
|
+
" stop <task-id> Abort a running task (kill switch)",
|
|
1008
|
+
" cost [report|reset] Aggregated cost tracking",
|
|
1009
|
+
' completion <shell> Shell completions (bash/zsh/fish/powershell)',
|
|
1010
|
+
" audit verify <run-dir> Verify a run's hash-chained audit log",
|
|
1011
|
+
" config [get|set|path|reset] Read or update ~/.maqcli/config.json",
|
|
1012
|
+
" version | help [<topic>]",
|
|
1013
|
+
"",
|
|
1014
|
+
"run options:",
|
|
1015
|
+
" -t, --target <t> auto | claude-code | codex | gemini | none",
|
|
1016
|
+
" --dry-run plan and show commands without executing",
|
|
1017
|
+
" --provider <p> override provider for this run (openai|anthropic|ollama|groq|cli:<agent>|…)",
|
|
1018
|
+
" --model <m> override the tier model(s) for this run",
|
|
1019
|
+
" --audit write run artifacts + a hash-chained audit log under .maq/runs/",
|
|
1020
|
+
" --json stream normalized events / emit JSON",
|
|
1021
|
+
" --cwd <dir> working directory",
|
|
1022
|
+
"",
|
|
1023
|
+
"providers (set 'provider' via config; keys from env only):",
|
|
1024
|
+
" heuristic (offline, $0) | openai | anthropic | ollama | groq | openai-compatible/litellm",
|
|
1025
|
+
].join("\n"));
|
|
1026
|
+
}
|
|
1027
|
+
main(process.argv.slice(2))
|
|
1028
|
+
.then((code) => process.exit(code))
|
|
1029
|
+
.catch((err) => {
|
|
1030
|
+
logger.error(err instanceof Error ? err.stack ?? err.message : String(err));
|
|
1031
|
+
process.exit(1);
|
|
1032
|
+
});
|