@moxxy/cli 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1131 @@
1
+ import {
2
+ PIPELINE_STAGES,
3
+ moxxyConfigSchema
4
+ } from "./chunk-GSNMMI3H.mjs";
5
+
6
+ // src/cli.ts
7
+ import { Command } from "commander";
8
+
9
+ // src/commands/agent.ts
10
+ import prompts from "prompts";
11
+
12
+ // src/config/config-manager.ts
13
+ import { readFile, writeFile, mkdir } from "fs/promises";
14
+ import { existsSync } from "fs";
15
+ import { join } from "path";
16
+ import { homedir } from "os";
17
+
18
+ // src/config/config-defaults.ts
19
+ var DEFAULT_CONFIG = {
20
+ version: 1,
21
+ webhook: { port: 3456, host: "0.0.0.0", path: "/webhook/github" },
22
+ repos: []
23
+ };
24
+ var CONFIG_DIR = ".moxxy";
25
+ var CONFIG_FILE = "config.json";
26
+
27
+ // src/config/config-manager.ts
28
+ var ConfigManager = class {
29
+ configPath;
30
+ config = null;
31
+ constructor(configDir) {
32
+ const base = configDir || join(homedir(), CONFIG_DIR);
33
+ this.configPath = join(base, CONFIG_FILE);
34
+ }
35
+ async load() {
36
+ if (!existsSync(this.configPath)) {
37
+ throw new Error(`Config not found at ${this.configPath}. Run 'moxxy config init' first.`);
38
+ }
39
+ const raw = await readFile(this.configPath, "utf-8");
40
+ const parsed = JSON.parse(raw);
41
+ this.config = moxxyConfigSchema.parse(parsed);
42
+ return this.config;
43
+ }
44
+ async save(config) {
45
+ const dir = join(this.configPath, "..");
46
+ if (!existsSync(dir)) {
47
+ await mkdir(dir, { recursive: true });
48
+ }
49
+ this.config = moxxyConfigSchema.parse(config);
50
+ await writeFile(this.configPath, JSON.stringify(this.config, null, 2) + "\n");
51
+ }
52
+ async init(config) {
53
+ const merged = { ...DEFAULT_CONFIG, ...config };
54
+ await this.save(merged);
55
+ return merged;
56
+ }
57
+ getConfig() {
58
+ if (!this.config) throw new Error("Config not loaded. Call load() first.");
59
+ return this.config;
60
+ }
61
+ async addRepo(repo) {
62
+ const config = this.getConfig();
63
+ const exists = config.repos.find((r) => r.owner === repo.owner && r.repo === repo.repo);
64
+ if (exists) throw new Error(`Repo ${repo.owner}/${repo.repo} already configured.`);
65
+ config.repos.push(repo);
66
+ await this.save(config);
67
+ }
68
+ async removeRepo(owner, repo) {
69
+ const config = this.getConfig();
70
+ config.repos = config.repos.filter((r) => !(r.owner === owner && r.repo === repo));
71
+ await this.save(config);
72
+ }
73
+ async set(key, value) {
74
+ const config = this.getConfig();
75
+ const keys = key.split(".");
76
+ let obj = config;
77
+ for (let i = 0; i < keys.length - 1; i++) {
78
+ obj = obj[keys[i]] ??= {};
79
+ }
80
+ obj[keys[keys.length - 1]] = value;
81
+ await this.save(config);
82
+ }
83
+ get(key) {
84
+ const config = this.getConfig();
85
+ const keys = key.split(".");
86
+ let obj = config;
87
+ for (const k of keys) {
88
+ obj = obj?.[k];
89
+ }
90
+ return obj;
91
+ }
92
+ get path() {
93
+ return this.configPath;
94
+ }
95
+ };
96
+
97
+ // src/utils/logger.ts
98
+ import chalk from "chalk";
99
+ import ora from "ora";
100
+ function info(message) {
101
+ console.log(`${chalk.cyan("i")} ${message}`);
102
+ }
103
+ function success(message) {
104
+ console.log(`${chalk.green("\u2714")} ${message}`);
105
+ }
106
+ function error(message) {
107
+ console.error(`${chalk.red("\u2716")} ${message}`);
108
+ }
109
+ function warn(message) {
110
+ console.warn(`${chalk.yellow("\u26A0")} ${message}`);
111
+ }
112
+ function spinner(text) {
113
+ return ora({ text, color: "cyan" }).start();
114
+ }
115
+ function printBanner() {
116
+ console.log();
117
+ console.log(chalk.bold.cyan(" Moxxy"));
118
+ console.log(chalk.dim(" Agent orchestration platform"));
119
+ console.log();
120
+ }
121
+ function section(title) {
122
+ console.log();
123
+ console.log(chalk.bold(title));
124
+ console.log(chalk.dim("\u2500".repeat(title.length)));
125
+ }
126
+ function keyValue(key, value) {
127
+ console.log(` ${chalk.dim(key + ":")} ${value}`);
128
+ }
129
+ function listItem(text) {
130
+ console.log(` ${chalk.dim("\u2022")} ${text}`);
131
+ }
132
+
133
+ // src/commands/agent.ts
134
+ var SDK_NAMES = {
135
+ molt: "Molt Gateway",
136
+ claude: "Claude CLI"
137
+ };
138
+ async function createSDK(config) {
139
+ const provider = config.sdk || "molt";
140
+ const sdkName = SDK_NAMES[provider] || provider;
141
+ if (provider === "claude") {
142
+ const { ClaudeSDK } = await import("./src-D5HMDDVE.mjs");
143
+ const claudeConfig = config.claude || {};
144
+ const sdk2 = await ClaudeSDK.create({
145
+ cliPath: claudeConfig.cliPath,
146
+ model: claudeConfig.model,
147
+ timeout: claudeConfig.timeout,
148
+ permissionMode: claudeConfig.permissionMode
149
+ });
150
+ return { sdk: sdk2, sdkName };
151
+ }
152
+ if (!config.gateway) {
153
+ throw new Error(
154
+ 'Gateway configuration is required for the molt SDK. Run "moxxy config init --sdk molt --gateway-url <url>".'
155
+ );
156
+ }
157
+ const { MoltSDK } = await import("./dist-UYA4RJUH.mjs");
158
+ const sdk = await MoltSDK.create({
159
+ gatewayUrl: config.gateway.url,
160
+ authToken: config.gateway.authToken,
161
+ deviceId: config.gateway.deviceId,
162
+ devicePrivateKey: config.gateway.devicePrivateKey
163
+ });
164
+ return { sdk, sdkName };
165
+ }
166
+ async function loadConfig() {
167
+ const manager = new ConfigManager();
168
+ return manager.load();
169
+ }
170
+ function registerAgentCommand(program) {
171
+ const agent = program.command("agent").description("Manage Moxxy agents");
172
+ agent.command("list").description("List all agents").action(async () => {
173
+ try {
174
+ const config = await loadConfig();
175
+ const connectSpinner = spinner("Connecting to SDK");
176
+ const { sdk, sdkName } = await createSDK(config);
177
+ connectSpinner.succeed(`Connected to ${sdkName}`);
178
+ const listSpinner = spinner("Fetching agents");
179
+ const result = await sdk.agents.list();
180
+ listSpinner.stop();
181
+ const agents = result.agents ?? [];
182
+ if (agents.length === 0) {
183
+ console.log();
184
+ warn("No agents found.");
185
+ info(`Run ${chalk.cyan("moxxy agent create <id>")} to create one.`);
186
+ sdk.disconnect();
187
+ return;
188
+ }
189
+ const configuredId = config.agent?.moltAgentId;
190
+ section(`Agents (${agents.length})`);
191
+ for (const a of agents) {
192
+ const id = a.agentId || a.id;
193
+ const tags = [];
194
+ if (a.isDefault) tags.push(chalk.cyan("default"));
195
+ if (id === configuredId) tags.push(chalk.green("configured"));
196
+ if (a.heartbeat?.enabled) tags.push(chalk.dim("heartbeat"));
197
+ const sessionCount = a.sessions?.count ?? 0;
198
+ const sessionsInfo = chalk.dim(
199
+ `${sessionCount} session${sessionCount !== 1 ? "s" : ""}`
200
+ );
201
+ const tagStr = tags.length > 0 ? ` ${tags.join(" ")}` : "";
202
+ listItem(`${chalk.bold(id)}${tagStr} ${sessionsInfo}`);
203
+ }
204
+ if (result.defaultAgentId) {
205
+ console.log();
206
+ keyValue("Default", chalk.cyan(result.defaultAgentId));
207
+ }
208
+ console.log();
209
+ sdk.disconnect();
210
+ } catch (err) {
211
+ error(`${err instanceof Error ? err.message : err}`);
212
+ process.exitCode = 1;
213
+ }
214
+ });
215
+ agent.command("create").description("Create a new agent (interactive when no flags provided)").argument("[id]", "Agent ID (unique name)").option("-w, --workspace <path>", "Workspace directory").option("-m, --model <model>", "Primary model (e.g. anthropic/claude-sonnet-4-5)").option("--default", "Set as the default agent").option("--name <name>", "Display name").option("--theme <theme>", "Persona / theme description").action(async (id, options) => {
216
+ try {
217
+ const config = await loadConfig();
218
+ if (!id) {
219
+ console.log();
220
+ console.log(chalk.bold.cyan(" Create Agent"));
221
+ console.log(chalk.dim(" Fill in the details below. Press Ctrl+C to cancel.\n"));
222
+ const answers = await prompts([
223
+ {
224
+ type: "text",
225
+ name: "agentId",
226
+ message: "Agent ID",
227
+ validate: (v) => v.length > 0 ? true : "Agent ID is required"
228
+ },
229
+ {
230
+ type: "text",
231
+ name: "workspace",
232
+ message: "Workspace directory (optional, press Enter to skip)"
233
+ },
234
+ {
235
+ type: "text",
236
+ name: "model",
237
+ message: "Primary model",
238
+ initial: "anthropic/claude-sonnet-4-5"
239
+ },
240
+ {
241
+ type: "text",
242
+ name: "name",
243
+ message: "Display name (optional)"
244
+ },
245
+ {
246
+ type: "text",
247
+ name: "theme",
248
+ message: "Persona / theme (optional)"
249
+ },
250
+ {
251
+ type: "confirm",
252
+ name: "isDefault",
253
+ message: "Set as default agent?",
254
+ initial: false
255
+ }
256
+ ]);
257
+ if (!answers.agentId) {
258
+ warn("Agent creation cancelled.");
259
+ return;
260
+ }
261
+ id = answers.agentId;
262
+ options.workspace = answers.workspace || void 0;
263
+ options.model = answers.model || void 0;
264
+ options.name = answers.name || void 0;
265
+ options.theme = answers.theme || void 0;
266
+ options.default = answers.isDefault ?? false;
267
+ }
268
+ const connectSpinner = spinner("Connecting to SDK");
269
+ const { sdk, sdkName } = await createSDK(config);
270
+ connectSpinner.succeed(`Connected to ${sdkName}`);
271
+ const createSpinner = spinner(`Creating agent ${chalk.bold(id)}`);
272
+ try {
273
+ const created = await sdk.agents.create({
274
+ agentId: id,
275
+ workspace: options.workspace,
276
+ isDefault: options.default ?? false,
277
+ identity: options.name || options.theme ? { name: options.name, theme: options.theme } : void 0,
278
+ model: options.model ? { primary: options.model } : void 0
279
+ });
280
+ createSpinner.succeed(`Agent ${chalk.bold(created.agentId || id)} created`);
281
+ } catch (err) {
282
+ createSpinner.fail(`Failed to create agent: ${err?.message || err}`);
283
+ sdk.disconnect();
284
+ process.exitCode = 1;
285
+ return;
286
+ }
287
+ sdk.disconnect();
288
+ } catch (err) {
289
+ error(`${err instanceof Error ? err.message : err}`);
290
+ process.exitCode = 1;
291
+ }
292
+ });
293
+ agent.command("remove").description("Remove an agent").argument("<id>", "Agent ID to remove").action(async (id) => {
294
+ try {
295
+ const config = await loadConfig();
296
+ const connectSpinner = spinner("Connecting to SDK");
297
+ const { sdk, sdkName } = await createSDK(config);
298
+ connectSpinner.succeed(`Connected to ${sdkName}`);
299
+ const removeSpinner = spinner(`Removing agent ${chalk.bold(id)}`);
300
+ try {
301
+ await sdk.agents.delete(id);
302
+ removeSpinner.succeed(`Agent ${chalk.bold(id)} removed`);
303
+ } catch (err) {
304
+ removeSpinner.fail(`Failed to remove agent: ${err?.message || err}`);
305
+ sdk.disconnect();
306
+ process.exitCode = 1;
307
+ return;
308
+ }
309
+ if (config.agent?.moltAgentId === id) {
310
+ const manager = new ConfigManager();
311
+ await manager.load();
312
+ await manager.set("agent.moltAgentId", "");
313
+ info("Cleared configured agent ID (was the removed agent).");
314
+ }
315
+ sdk.disconnect();
316
+ } catch (err) {
317
+ error(`${err instanceof Error ? err.message : err}`);
318
+ process.exitCode = 1;
319
+ }
320
+ });
321
+ agent.command("stop").description("Stop an agent (disables heartbeat)").argument("<id>", "Agent ID to stop").action(async (id) => {
322
+ try {
323
+ const config = await loadConfig();
324
+ const connectSpinner = spinner("Connecting to SDK");
325
+ const { sdk, sdkName } = await createSDK(config);
326
+ connectSpinner.succeed(`Connected to ${sdkName}`);
327
+ const stopSpinner = spinner(`Stopping agent ${chalk.bold(id)}`);
328
+ try {
329
+ await sdk.agents.configureHeartbeat(id, { enabled: false });
330
+ stopSpinner.succeed(`Agent ${chalk.bold(id)} stopped (heartbeat disabled)`);
331
+ } catch (err) {
332
+ stopSpinner.fail(`Failed to stop agent: ${err?.message || err}`);
333
+ sdk.disconnect();
334
+ process.exitCode = 1;
335
+ return;
336
+ }
337
+ sdk.disconnect();
338
+ } catch (err) {
339
+ error(`${err instanceof Error ? err.message : err}`);
340
+ process.exitCode = 1;
341
+ }
342
+ });
343
+ agent.command("assign").description("Assign an available agent for task execution").argument("[id]", "Specific agent ID to assign (auto-selects if omitted)").option("-r, --role <role>", "Role to assign", "developer").action(async (id, options) => {
344
+ try {
345
+ const config = await loadConfig();
346
+ const connectSpinner = spinner("Connecting to SDK");
347
+ const { sdk, sdkName } = await createSDK(config);
348
+ connectSpinner.succeed(`Connected to ${sdkName}`);
349
+ const listSpinner = spinner("Fetching agents");
350
+ const result = await sdk.agents.list();
351
+ listSpinner.stop();
352
+ const agents = result.agents ?? [];
353
+ if (agents.length === 0) {
354
+ error("No agents available.");
355
+ info(`Run ${chalk.cyan("moxxy agent create <id>")} to create one first.`);
356
+ sdk.disconnect();
357
+ process.exitCode = 1;
358
+ return;
359
+ }
360
+ section(`Available agents (${agents.length})`);
361
+ for (const a of agents) {
362
+ const agentId = a.agentId || a.id;
363
+ const tags = [];
364
+ if (a.isDefault) tags.push(chalk.cyan("default"));
365
+ const tagStr = tags.length > 0 ? ` ${tags.join(" ")}` : "";
366
+ listItem(`${chalk.bold(agentId)}${tagStr}`);
367
+ }
368
+ console.log();
369
+ let targetId;
370
+ if (id) {
371
+ const found = agents.find((a) => (a.agentId || a.id) === id);
372
+ if (!found) {
373
+ error(`Agent '${id}' not found.`);
374
+ sdk.disconnect();
375
+ process.exitCode = 1;
376
+ return;
377
+ }
378
+ targetId = id;
379
+ } else {
380
+ const defaultAgent = result.defaultAgentId ? agents.find((a) => (a.agentId || a.id) === result.defaultAgentId) : null;
381
+ if (defaultAgent) {
382
+ targetId = defaultAgent.agentId || defaultAgent.id;
383
+ } else {
384
+ targetId = agents[0].agentId || agents[0].id;
385
+ }
386
+ }
387
+ const { MoltAgentSupervisor } = await import("./dist-7LTHRYKA.mjs");
388
+ const supervisor = new MoltAgentSupervisor(sdk);
389
+ const assignSpinner = spinner(
390
+ `Assigning agent ${chalk.bold(targetId)} as ${chalk.cyan(options.role)}`
391
+ );
392
+ try {
393
+ const handle = await supervisor.assignAgent(targetId, options.role);
394
+ if (handle.status === "busy") {
395
+ assignSpinner.warn(
396
+ `Agent ${chalk.bold(targetId)} is currently busy. It will be used when it becomes free.`
397
+ );
398
+ } else {
399
+ assignSpinner.succeed(
400
+ `Agent ${chalk.bold(targetId)} assigned (${chalk.green(handle.status)})`
401
+ );
402
+ }
403
+ } catch (err) {
404
+ assignSpinner.fail(`Failed to assign agent: ${err?.message || err}`);
405
+ sdk.disconnect();
406
+ process.exitCode = 1;
407
+ return;
408
+ }
409
+ const manager = new ConfigManager();
410
+ await manager.load();
411
+ await manager.set("agent", {
412
+ moltAgentId: targetId,
413
+ maxConcurrentTasks: config.agent?.maxConcurrentTasks || 1
414
+ });
415
+ success(
416
+ `Config updated: ${chalk.bold("agent.moltAgentId")} = ${chalk.cyan(targetId)}`
417
+ );
418
+ await supervisor.releaseAgent(targetId);
419
+ sdk.disconnect();
420
+ } catch (err) {
421
+ error(`${err instanceof Error ? err.message : err}`);
422
+ process.exitCode = 1;
423
+ }
424
+ });
425
+ }
426
+
427
+ // src/utils/prompt.ts
428
+ import chalk2 from "chalk";
429
+ import prompts2 from "prompts";
430
+ async function interactiveInit() {
431
+ console.log();
432
+ console.log(chalk2.bold.cyan(" Moxxy Config Setup"));
433
+ console.log(chalk2.dim(" Answer the prompts below to initialize your configuration.\n"));
434
+ const { sdk } = await prompts2({
435
+ type: "select",
436
+ name: "sdk",
437
+ message: "SDK provider",
438
+ choices: [
439
+ { title: "Molt", value: "molt" },
440
+ { title: "Claude", value: "claude" }
441
+ ]
442
+ });
443
+ if (!sdk) return null;
444
+ let gatewayUrl;
445
+ let authToken;
446
+ let claudeApiKey;
447
+ let claudeModel;
448
+ let claudeCliPath;
449
+ if (sdk === "molt") {
450
+ const molt = await prompts2([
451
+ {
452
+ type: "text",
453
+ name: "gatewayUrl",
454
+ message: "Gateway URL",
455
+ validate: (v) => v.length > 0 ? true : "Gateway URL is required"
456
+ },
457
+ {
458
+ type: "text",
459
+ name: "authToken",
460
+ message: "Auth token (optional, press Enter to skip)"
461
+ }
462
+ ]);
463
+ if (!molt.gatewayUrl) return null;
464
+ gatewayUrl = molt.gatewayUrl;
465
+ authToken = molt.authToken || void 0;
466
+ } else {
467
+ const claude = await prompts2([
468
+ {
469
+ type: "text",
470
+ name: "apiKey",
471
+ message: "Anthropic API key (optional, press Enter to skip)"
472
+ },
473
+ {
474
+ type: "text",
475
+ name: "model",
476
+ message: "Claude model",
477
+ initial: "claude-sonnet-4-5"
478
+ },
479
+ {
480
+ type: "text",
481
+ name: "cliPath",
482
+ message: "Claude CLI path",
483
+ initial: "claude"
484
+ }
485
+ ]);
486
+ if (claude.model === void 0) return null;
487
+ claudeApiKey = claude.apiKey || void 0;
488
+ claudeModel = claude.model;
489
+ claudeCliPath = claude.cliPath;
490
+ }
491
+ const shared = await prompts2([
492
+ {
493
+ type: "password",
494
+ name: "githubToken",
495
+ message: "GitHub personal access token",
496
+ validate: (v) => v.length > 0 ? true : "GitHub token is required"
497
+ },
498
+ {
499
+ type: "password",
500
+ name: "webhookSecret",
501
+ message: "Webhook secret (optional, press Enter to skip)"
502
+ },
503
+ {
504
+ type: "number",
505
+ name: "webhookPort",
506
+ message: "Webhook port",
507
+ initial: 3456
508
+ },
509
+ {
510
+ type: "text",
511
+ name: "agentId",
512
+ message: "Agent ID (optional, press Enter to skip)"
513
+ }
514
+ ]);
515
+ if (!shared.githubToken) return null;
516
+ const { pipelineStages } = await prompts2({
517
+ type: "multiselect",
518
+ name: "pipelineStages",
519
+ message: "Pipeline stages to enable",
520
+ choices: [
521
+ { title: "Triage", value: "triage", selected: true },
522
+ { title: "Research", value: "research", selected: true },
523
+ { title: "Inspect", value: "inspect", selected: true },
524
+ { title: "Plan", value: "plan", selected: true },
525
+ { title: "Implement", value: "implement", selected: true }
526
+ ],
527
+ hint: "- Space to toggle. Plan & Implement are always on."
528
+ });
529
+ return {
530
+ sdk,
531
+ gatewayUrl,
532
+ authToken,
533
+ claudeApiKey,
534
+ claudeModel,
535
+ claudeCliPath,
536
+ githubToken: shared.githubToken,
537
+ webhookSecret: shared.webhookSecret || void 0,
538
+ webhookPort: String(shared.webhookPort ?? 3456),
539
+ agentId: shared.agentId || void 0,
540
+ pipelineStages: pipelineStages?.length ? pipelineStages : void 0
541
+ };
542
+ }
543
+ var CONFIG_GROUPS = [
544
+ { title: "SDK Provider", value: "sdk" },
545
+ { title: "GitHub", value: "github" },
546
+ { title: "Webhook", value: "webhook" },
547
+ { title: "Pipeline Stages", value: "pipeline" },
548
+ { title: "Agent", value: "agent" },
549
+ { title: "Done", value: "done" }
550
+ ];
551
+ async function interactiveConfig(manager) {
552
+ const config = manager.getConfig();
553
+ console.log();
554
+ console.log(chalk2.bold.cyan(" Moxxy Config Editor"));
555
+ console.log(chalk2.dim(' Select a section to edit. Ctrl+C or "Done" to exit.\n'));
556
+ while (true) {
557
+ const { group } = await prompts2({
558
+ type: "select",
559
+ name: "group",
560
+ message: "Config section",
561
+ choices: [...CONFIG_GROUPS]
562
+ });
563
+ if (!group || group === "done") break;
564
+ switch (group) {
565
+ case "sdk":
566
+ await editSDK(manager, config);
567
+ break;
568
+ case "github":
569
+ await editGitHub(manager, config);
570
+ break;
571
+ case "webhook":
572
+ await editWebhook(manager, config);
573
+ break;
574
+ case "pipeline":
575
+ await editPipeline(manager, config);
576
+ break;
577
+ case "agent":
578
+ await editAgent(manager, config);
579
+ break;
580
+ }
581
+ }
582
+ }
583
+ async function editSDK(manager, config) {
584
+ const { provider } = await prompts2({
585
+ type: "select",
586
+ name: "provider",
587
+ message: "SDK provider",
588
+ choices: [
589
+ { title: "Molt", value: "molt" },
590
+ { title: "Claude", value: "claude" }
591
+ ],
592
+ initial: config.sdk === "claude" ? 1 : 0
593
+ });
594
+ if (!provider) return;
595
+ if (provider === "molt") {
596
+ const result = await prompts2([
597
+ {
598
+ type: "text",
599
+ name: "gatewayUrl",
600
+ message: "Gateway URL",
601
+ initial: config.gateway?.url || ""
602
+ },
603
+ {
604
+ type: "text",
605
+ name: "authToken",
606
+ message: "Auth token",
607
+ initial: config.gateway?.authToken || ""
608
+ }
609
+ ]);
610
+ if (result.gatewayUrl === void 0) return;
611
+ await manager.set("sdk", "molt");
612
+ await manager.set("gateway", {
613
+ url: result.gatewayUrl,
614
+ authToken: result.authToken || void 0
615
+ });
616
+ } else {
617
+ const result = await prompts2([
618
+ {
619
+ type: "text",
620
+ name: "apiKey",
621
+ message: "API key",
622
+ initial: config.claude?.apiKey || ""
623
+ },
624
+ {
625
+ type: "text",
626
+ name: "model",
627
+ message: "Model",
628
+ initial: config.claude?.model || "claude-sonnet-4-5"
629
+ },
630
+ {
631
+ type: "text",
632
+ name: "cliPath",
633
+ message: "CLI path",
634
+ initial: config.claude?.cliPath || "claude"
635
+ }
636
+ ]);
637
+ if (result.model === void 0) return;
638
+ await manager.set("sdk", "claude");
639
+ await manager.set("claude", {
640
+ apiKey: result.apiKey || void 0,
641
+ model: result.model,
642
+ cliPath: result.cliPath
643
+ });
644
+ }
645
+ console.log(chalk2.green(" SDK config updated.\n"));
646
+ }
647
+ async function editGitHub(manager, config) {
648
+ const result = await prompts2([
649
+ {
650
+ type: "password",
651
+ name: "token",
652
+ message: "GitHub token",
653
+ initial: config.github?.token || ""
654
+ },
655
+ {
656
+ type: "password",
657
+ name: "webhookSecret",
658
+ message: "Webhook secret",
659
+ initial: config.github?.webhookSecret || ""
660
+ }
661
+ ]);
662
+ if (result.token === void 0) return;
663
+ await manager.set("github", {
664
+ token: result.token,
665
+ webhookSecret: result.webhookSecret || void 0
666
+ });
667
+ console.log(chalk2.green(" GitHub config updated.\n"));
668
+ }
669
+ async function editWebhook(manager, config) {
670
+ const webhook = config.webhook || { port: 3456, host: "0.0.0.0", path: "/webhook/github" };
671
+ const result = await prompts2([
672
+ {
673
+ type: "number",
674
+ name: "port",
675
+ message: "Port",
676
+ initial: webhook.port
677
+ },
678
+ {
679
+ type: "text",
680
+ name: "host",
681
+ message: "Host",
682
+ initial: webhook.host
683
+ },
684
+ {
685
+ type: "text",
686
+ name: "path",
687
+ message: "Path",
688
+ initial: webhook.path
689
+ }
690
+ ]);
691
+ if (result.port === void 0) return;
692
+ await manager.set("webhook", {
693
+ port: result.port,
694
+ host: result.host,
695
+ path: result.path
696
+ });
697
+ console.log(chalk2.green(" Webhook config updated.\n"));
698
+ }
699
+ async function editPipeline(manager, config) {
700
+ const currentStages = config.pipeline?.enabledStages ?? [...PIPELINE_STAGES];
701
+ const currentSet = new Set(currentStages);
702
+ const { stages } = await prompts2({
703
+ type: "multiselect",
704
+ name: "stages",
705
+ message: "Enabled pipeline stages",
706
+ choices: PIPELINE_STAGES.map((s) => ({
707
+ title: s.charAt(0).toUpperCase() + s.slice(1),
708
+ value: s,
709
+ selected: currentSet.has(s)
710
+ })),
711
+ hint: "- Space to toggle. Plan & Implement are always on."
712
+ });
713
+ if (!stages) return;
714
+ const final = [...stages];
715
+ if (!final.includes("plan")) final.push("plan");
716
+ if (!final.includes("implement")) final.push("implement");
717
+ await manager.set("pipeline", { enabledStages: final });
718
+ console.log(chalk2.green(" Pipeline config updated.\n"));
719
+ }
720
+ async function editAgent(manager, config) {
721
+ const agent = config.agent || {};
722
+ const result = await prompts2([
723
+ {
724
+ type: "text",
725
+ name: "moltAgentId",
726
+ message: "Agent ID",
727
+ initial: agent.moltAgentId || ""
728
+ },
729
+ {
730
+ type: "number",
731
+ name: "maxConcurrentTasks",
732
+ message: "Max concurrent tasks",
733
+ initial: agent.maxConcurrentTasks || 1
734
+ }
735
+ ]);
736
+ if (result.moltAgentId === void 0) return;
737
+ await manager.set("agent", {
738
+ moltAgentId: result.moltAgentId || void 0,
739
+ maxConcurrentTasks: result.maxConcurrentTasks || 1
740
+ });
741
+ console.log(chalk2.green(" Agent config updated.\n"));
742
+ }
743
+
744
+ // src/commands/config.ts
745
+ function registerConfigCommand(program) {
746
+ const config = program.command("config").description("Manage Moxxy configuration");
747
+ config.action(async () => {
748
+ const manager = new ConfigManager();
749
+ try {
750
+ await manager.load();
751
+ } catch {
752
+ error("No config found. Run 'moxxy config init' first.");
753
+ process.exitCode = 1;
754
+ return;
755
+ }
756
+ await interactiveConfig(manager);
757
+ });
758
+ config.command("init").description("Initialize Moxxy configuration").option("--sdk <provider>", "SDK provider (molt or claude)").option("--github-token <token>", "GitHub personal access token").option("--gateway-url <url>", "Gateway URL (molt SDK)").option("--auth-token <token>", "Gateway auth token (molt SDK)").option("--claude-api-key <key>", "Anthropic API key (claude SDK)").option("--claude-model <model>", "Claude model (claude SDK)", "claude-sonnet-4-5").option("--claude-cli-path <path>", "Claude CLI path (claude SDK)", "claude").option("--webhook-secret <secret>", "GitHub webhook secret").option("--webhook-port <port>", "Webhook server port", "3456").option("--agent-id <id>", "Agent ID").option(
759
+ "--pipeline-stages <stages>",
760
+ "Comma-separated pipeline stages (triage,research,inspect,plan,implement)"
761
+ ).action(async (options) => {
762
+ if (!options.sdk) {
763
+ const result = await interactiveInit();
764
+ if (!result) {
765
+ warn("Config init cancelled.");
766
+ return;
767
+ }
768
+ options = { ...options, ...result };
769
+ }
770
+ const sdk = options.sdk;
771
+ if (sdk !== "molt" && sdk !== "claude") {
772
+ error('--sdk must be "molt" or "claude"');
773
+ process.exitCode = 1;
774
+ return;
775
+ }
776
+ if (!options.githubToken) {
777
+ error("--github-token is required");
778
+ process.exitCode = 1;
779
+ return;
780
+ }
781
+ if (sdk === "molt" && !options.gatewayUrl) {
782
+ error("--gateway-url is required when using the molt SDK");
783
+ process.exitCode = 1;
784
+ return;
785
+ }
786
+ let enabledStages;
787
+ if (options.pipelineStages) {
788
+ if (typeof options.pipelineStages === "string") {
789
+ enabledStages = options.pipelineStages.split(",").map((s) => s.trim());
790
+ } else if (Array.isArray(options.pipelineStages)) {
791
+ enabledStages = options.pipelineStages;
792
+ }
793
+ }
794
+ const manager = new ConfigManager();
795
+ try {
796
+ await manager.init({
797
+ version: 1,
798
+ sdk,
799
+ gateway: sdk === "molt" ? { url: options.gatewayUrl, authToken: options.authToken } : void 0,
800
+ github: { token: options.githubToken, webhookSecret: options.webhookSecret },
801
+ webhook: {
802
+ port: parseInt(options.webhookPort, 10),
803
+ host: "0.0.0.0",
804
+ path: "/webhook/github"
805
+ },
806
+ repos: [],
807
+ agent: options.agentId ? { moltAgentId: options.agentId } : void 0,
808
+ pipeline: enabledStages ? { enabledStages } : void 0,
809
+ claude: sdk === "claude" ? {
810
+ apiKey: options.claudeApiKey,
811
+ cliPath: options.claudeCliPath,
812
+ model: options.claudeModel
813
+ } : void 0
814
+ });
815
+ success(`Config created at ${chalk.dim(manager.path)}`);
816
+ } catch (err) {
817
+ error(`Failed to init config: ${err instanceof Error ? err.message : err}`);
818
+ process.exitCode = 1;
819
+ }
820
+ });
821
+ config.command("set").description("Set a config value").argument("<key>", "Config key (dot notation)").argument("<value>", "Config value").action(async (key, value) => {
822
+ const manager = new ConfigManager();
823
+ try {
824
+ await manager.load();
825
+ let parsed = value;
826
+ try {
827
+ parsed = JSON.parse(value);
828
+ } catch {
829
+ }
830
+ await manager.set(key, parsed);
831
+ success(`Set ${chalk.bold(key)}`);
832
+ } catch (err) {
833
+ error(`Failed: ${err instanceof Error ? err.message : err}`);
834
+ process.exitCode = 1;
835
+ }
836
+ });
837
+ config.command("get").description("Get a config value").argument("<key>", "Config key (dot notation)").action(async (key) => {
838
+ const manager = new ConfigManager();
839
+ try {
840
+ await manager.load();
841
+ const value = manager.get(key);
842
+ console.log(chalk.cyan(JSON.stringify(value, null, 2)));
843
+ } catch (err) {
844
+ error(`Failed: ${err instanceof Error ? err.message : err}`);
845
+ process.exitCode = 1;
846
+ }
847
+ });
848
+ }
849
+
850
+ // src/commands/repo.ts
851
+ function registerRepoCommand(program) {
852
+ const repo = program.command("repo").description("Manage watched repositories");
853
+ repo.command("add").description("Add a repository to watch").argument("<repo>", "Repository in owner/repo format").option("--branch <branch>", "Default branch", "main").option("--events <events>", "Comma-separated events to watch", "issues.opened,issues.labeled").action(async (repoStr, options) => {
854
+ const [owner, name] = repoStr.split("/");
855
+ if (!owner || !name) {
856
+ error("Repository must be in owner/repo format");
857
+ process.exitCode = 1;
858
+ return;
859
+ }
860
+ const manager = new ConfigManager();
861
+ try {
862
+ await manager.load();
863
+ await manager.addRepo({
864
+ owner,
865
+ repo: name,
866
+ defaultBranch: options.branch,
867
+ enabledEvents: options.events.split(",")
868
+ });
869
+ success(`Added ${chalk.bold(`${owner}/${name}`)}`);
870
+ } catch (err) {
871
+ error(`Failed: ${err instanceof Error ? err.message : err}`);
872
+ process.exitCode = 1;
873
+ }
874
+ });
875
+ repo.command("list").description("List watched repositories").action(async () => {
876
+ const manager = new ConfigManager();
877
+ try {
878
+ await manager.load();
879
+ const config = manager.getConfig();
880
+ if (config.repos.length === 0) {
881
+ info("No repositories configured.");
882
+ return;
883
+ }
884
+ section(`Repositories (${config.repos.length})`);
885
+ for (const r of config.repos) {
886
+ listItem(
887
+ `${chalk.bold(`${r.owner}/${r.repo}`)} ${chalk.dim(`branch: ${r.defaultBranch}`)} ${chalk.dim(`events: ${r.enabledEvents.join(", ")}`)}`
888
+ );
889
+ }
890
+ console.log();
891
+ } catch (err) {
892
+ error(`Failed: ${err instanceof Error ? err.message : err}`);
893
+ process.exitCode = 1;
894
+ }
895
+ });
896
+ repo.command("remove").description("Remove a watched repository").argument("<repo>", "Repository in owner/repo format").action(async (repoStr) => {
897
+ const [owner, name] = repoStr.split("/");
898
+ if (!owner || !name) {
899
+ error("Repository must be in owner/repo format");
900
+ process.exitCode = 1;
901
+ return;
902
+ }
903
+ const manager = new ConfigManager();
904
+ try {
905
+ await manager.load();
906
+ await manager.removeRepo(owner, name);
907
+ success(`Removed ${chalk.bold(`${owner}/${name}`)}`);
908
+ } catch (err) {
909
+ error(`Failed: ${err instanceof Error ? err.message : err}`);
910
+ process.exitCode = 1;
911
+ }
912
+ });
913
+ }
914
+
915
+ // src/commands/start.ts
916
+ var SDK_NAMES2 = {
917
+ molt: "Molt Gateway",
918
+ claude: "Claude CLI"
919
+ };
920
+ async function createSDK2(config) {
921
+ const provider = config.sdk || "molt";
922
+ const sdkName = SDK_NAMES2[provider] || provider;
923
+ if (provider === "claude") {
924
+ const { ClaudeSDK } = await import("./src-D5HMDDVE.mjs");
925
+ const claudeConfig = config.claude || {};
926
+ const sdk2 = await ClaudeSDK.create({
927
+ cliPath: claudeConfig.cliPath,
928
+ model: claudeConfig.model,
929
+ timeout: claudeConfig.timeout,
930
+ permissionMode: claudeConfig.permissionMode
931
+ });
932
+ return { sdk: sdk2, sdkName };
933
+ }
934
+ if (!config.gateway) {
935
+ throw new Error(
936
+ 'Gateway configuration is required for the molt SDK. Run "moxxy config init --sdk molt --gateway-url <url>".'
937
+ );
938
+ }
939
+ const { MoltSDK } = await import("./dist-UYA4RJUH.mjs");
940
+ const sdk = await MoltSDK.create({
941
+ gatewayUrl: config.gateway.url,
942
+ authToken: config.gateway.authToken,
943
+ deviceId: config.gateway.deviceId,
944
+ devicePrivateKey: config.gateway.devicePrivateKey
945
+ });
946
+ return { sdk, sdkName };
947
+ }
948
+ function registerStartCommand(program) {
949
+ program.command("start").description("Start Moxxy orchestration").action(async () => {
950
+ const configManager = new ConfigManager();
951
+ try {
952
+ const config = await configManager.load();
953
+ printBanner();
954
+ const { MoltAgentSupervisor } = await import("./dist-7LTHRYKA.mjs");
955
+ const { IntegrationManager } = await import("./dist-LKIOZQ42.mjs");
956
+ const { GitHubIntegration } = await import("./dist-KLSB6YHV.mjs");
957
+ const provider = config.sdk || "molt";
958
+ const sdkName = SDK_NAMES2[provider] || provider;
959
+ const connectSpinner = spinner(`Connecting to ${sdkName}`);
960
+ try {
961
+ const { sdk } = await createSDK2(config);
962
+ connectSpinner.succeed(`Connected to ${sdkName}`);
963
+ const supervisorSpinner = spinner("Initializing agent supervisor");
964
+ const supervisor = new MoltAgentSupervisor(sdk);
965
+ let agentId = config.agent?.moltAgentId;
966
+ if (!agentId) {
967
+ const agentList = await sdk.agents.list();
968
+ const agents = agentList.agents ?? [];
969
+ if (agents.length > 0) {
970
+ const picked = agentList.defaultAgentId && agents.find((a) => (a.agentId || a.id) === agentList.defaultAgentId) || agents[0];
971
+ agentId = picked.agentId || picked.id;
972
+ }
973
+ }
974
+ if (agentId) {
975
+ await supervisor.assignAgent(agentId, "developer");
976
+ supervisorSpinner.succeed(`Agent ${chalk.bold(agentId)} assigned`);
977
+ } else {
978
+ supervisorSpinner.warn(
979
+ `No agents found. Run ${chalk.cyan("moxxy agent create <id>")} to create one.`
980
+ );
981
+ }
982
+ const integrationManager = new IntegrationManager();
983
+ const github = new GitHubIntegration();
984
+ integrationManager.register(github);
985
+ integrationManager.on("*", (event) => {
986
+ info(
987
+ `${chalk.dim(`[${event.integration}]`)} ${event.type}: ${JSON.stringify(event.data).slice(0, 200)}`
988
+ );
989
+ });
990
+ const webhookConfig = config.webhook || {
991
+ port: 3456,
992
+ host: "0.0.0.0",
993
+ path: "/webhook/github"
994
+ };
995
+ const webhookSpinner = spinner("Starting webhook server");
996
+ await github.connect({
997
+ github: config.github,
998
+ webhook: webhookConfig,
999
+ sdk,
1000
+ supervisor,
1001
+ moltAgentId: agentId || "",
1002
+ enabledStages: config.pipeline?.enabledStages
1003
+ });
1004
+ webhookSpinner.succeed(
1005
+ `Webhook server listening on ${chalk.cyan(`${webhookConfig.host}:${webhookConfig.port}${webhookConfig.path}`)}`
1006
+ );
1007
+ if (config.repos.length > 0) {
1008
+ const watchSpinner = spinner(`Watching ${config.repos.length} repositories`);
1009
+ await github.watchRepos(config.repos);
1010
+ const targets = config.repos.map((r) => `${r.owner}/${r.repo}`);
1011
+ watchSpinner.succeed(
1012
+ `Watching ${chalk.bold(String(targets.length))} repositories: ${targets.join(", ")}`
1013
+ );
1014
+ }
1015
+ console.log();
1016
+ success(chalk.bold("Moxxy is running.") + " Press Ctrl+C to stop.");
1017
+ const shutdown = async () => {
1018
+ console.log();
1019
+ info("Shutting down...");
1020
+ await integrationManager.stopAll();
1021
+ sdk.disconnect();
1022
+ success("Shutdown complete.");
1023
+ process.exit(0);
1024
+ };
1025
+ process.on("SIGINT", shutdown);
1026
+ process.on("SIGTERM", shutdown);
1027
+ await new Promise(() => {
1028
+ });
1029
+ } catch (innerErr) {
1030
+ connectSpinner.fail(
1031
+ `Failed to start: ${innerErr instanceof Error ? innerErr.message : innerErr}`
1032
+ );
1033
+ process.exitCode = 1;
1034
+ }
1035
+ } catch (err) {
1036
+ error(`Failed to start: ${err instanceof Error ? err.message : err}`);
1037
+ process.exitCode = 1;
1038
+ }
1039
+ });
1040
+ }
1041
+
1042
+ // src/commands/status.ts
1043
+ function registerStatusCommand(program) {
1044
+ program.command("status").description("Show Moxxy configuration status").action(async () => {
1045
+ const configManager = new ConfigManager();
1046
+ try {
1047
+ const config = await configManager.load();
1048
+ printBanner();
1049
+ const provider = config.sdk || "molt";
1050
+ section("SDK");
1051
+ keyValue("Provider", chalk.bold(provider));
1052
+ if (provider === "molt" && config.gateway) {
1053
+ section("Gateway");
1054
+ keyValue("URL", chalk.cyan(config.gateway.url));
1055
+ keyValue(
1056
+ "Auth",
1057
+ config.gateway.authToken ? chalk.green("configured") : chalk.dim("not set")
1058
+ );
1059
+ keyValue(
1060
+ "Device",
1061
+ config.gateway.deviceId || chalk.dim("not set")
1062
+ );
1063
+ }
1064
+ if (provider === "claude" && config.claude) {
1065
+ section("Claude");
1066
+ keyValue(
1067
+ "API Key",
1068
+ config.claude.apiKey ? chalk.yellow("***" + config.claude.apiKey.slice(-4)) : chalk.dim("not set")
1069
+ );
1070
+ keyValue("CLI Path", chalk.cyan(config.claude.cliPath || "claude"));
1071
+ keyValue("Model", chalk.cyan(config.claude.model || "claude-sonnet-4-5"));
1072
+ }
1073
+ section("GitHub");
1074
+ keyValue(
1075
+ "Token",
1076
+ config.github.token ? chalk.yellow("***" + config.github.token.slice(-4)) : chalk.dim("not set")
1077
+ );
1078
+ keyValue(
1079
+ "Webhook Secret",
1080
+ config.github.webhookSecret ? chalk.green("configured") : chalk.dim("not set")
1081
+ );
1082
+ if (config.webhook) {
1083
+ section("Webhook Server");
1084
+ keyValue("Port", chalk.cyan(String(config.webhook.port)));
1085
+ keyValue("Path", chalk.cyan(config.webhook.path));
1086
+ }
1087
+ section(`Repositories (${config.repos.length})`);
1088
+ if (config.repos.length === 0) {
1089
+ keyValue("", chalk.dim("None configured"));
1090
+ } else {
1091
+ for (const r of config.repos) {
1092
+ listItem(
1093
+ `${chalk.bold(`${r.owner}/${r.repo}`)} ${chalk.dim(`(${r.defaultBranch})`)}`
1094
+ );
1095
+ }
1096
+ }
1097
+ if (config.agent) {
1098
+ section("Agent");
1099
+ keyValue(
1100
+ "Agent ID",
1101
+ config.agent.moltAgentId || chalk.dim("not set")
1102
+ );
1103
+ keyValue(
1104
+ "Max Concurrent",
1105
+ String(config.agent.maxConcurrentTasks || 1)
1106
+ );
1107
+ }
1108
+ console.log();
1109
+ } catch (err) {
1110
+ error(`${err instanceof Error ? err.message : err}`);
1111
+ process.exitCode = 1;
1112
+ }
1113
+ });
1114
+ }
1115
+
1116
+ // src/cli.ts
1117
+ function createProgram() {
1118
+ const program = new Command();
1119
+ program.name("moxxy").description("Moxxy - Agent orchestration platform").version("0.0.6");
1120
+ registerStartCommand(program);
1121
+ registerRepoCommand(program);
1122
+ registerConfigCommand(program);
1123
+ registerStatusCommand(program);
1124
+ registerAgentCommand(program);
1125
+ return program;
1126
+ }
1127
+
1128
+ export {
1129
+ ConfigManager,
1130
+ createProgram
1131
+ };