@maestroai/cli 0.1.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.
@@ -0,0 +1,1172 @@
1
+ // src/index.ts
2
+ import { Command } from "commander";
3
+
4
+ // src/commands/init.ts
5
+ import * as fs from "fs";
6
+ import * as path2 from "path";
7
+ import * as readline from "readline";
8
+ import chalk from "chalk";
9
+ import figures from "figures";
10
+ import {
11
+ Planner,
12
+ TaskParser,
13
+ TaskWriter,
14
+ generateTaskId,
15
+ resetTaskCounter,
16
+ getDefaultSystemPrompt
17
+ } from "@maestroai/core";
18
+
19
+ // src/utils/claude-check.ts
20
+ import { existsSync } from "fs";
21
+ import { execSync } from "child_process";
22
+ import path from "path";
23
+ import os from "os";
24
+ function checkClaudeInstalled() {
25
+ try {
26
+ const claudePath = execSync("command -v claude", { encoding: "utf-8" }).trim();
27
+ if (!claudePath) {
28
+ const homeDir = os.homedir();
29
+ const commonPaths = [
30
+ path.join(homeDir, ".local", "bin", "claude"),
31
+ "/usr/local/bin/claude",
32
+ path.join(homeDir, "bin", "claude")
33
+ ];
34
+ for (const checkPath of commonPaths) {
35
+ if (existsSync(checkPath)) {
36
+ try {
37
+ const version2 = execSync("claude --version", { encoding: "utf-8" }).trim();
38
+ return { installed: true, path: checkPath, version: version2 };
39
+ } catch {
40
+ return { installed: true, path: checkPath };
41
+ }
42
+ }
43
+ }
44
+ return {
45
+ installed: false,
46
+ error: "Claude CLI not found in PATH or common installation directories"
47
+ };
48
+ }
49
+ let version;
50
+ try {
51
+ version = execSync("claude --version", { encoding: "utf-8" }).trim();
52
+ } catch {
53
+ }
54
+ return { installed: true, path: claudePath, version };
55
+ } catch (error) {
56
+ return {
57
+ installed: false,
58
+ error: error instanceof Error ? error.message : "Unknown error"
59
+ };
60
+ }
61
+ }
62
+ function getClaudeInstallInstructions() {
63
+ const platform = os.platform();
64
+ if (platform === "darwin") {
65
+ return `Install Claude CLI for macOS:
66
+ npm install -g @anthropic-ai/claude-code
67
+
68
+ Or using Homebrew:
69
+ brew install anthropic/claude/claude-code`;
70
+ }
71
+ if (platform === "linux") {
72
+ return `Install Claude CLI for Linux:
73
+ npm install -g @anthropic-ai/claude-code`;
74
+ }
75
+ if (platform === "win32") {
76
+ return `Install Claude CLI for Windows:
77
+ npm install -g @anthropic-ai/claude-code`;
78
+ }
79
+ return `Install Claude CLI:
80
+ npm install -g @anthropic-ai/claude-code`;
81
+ }
82
+
83
+ // src/commands/init.ts
84
+ function ask(rl, question, defaultValue) {
85
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
86
+ return new Promise((resolve5) => {
87
+ rl.question(`${question}${suffix}: `, (answer) => {
88
+ resolve5(answer.trim() || defaultValue || "");
89
+ });
90
+ });
91
+ }
92
+ function detectExistingProject(dir) {
93
+ const result = {
94
+ type: "unknown",
95
+ techStack: {},
96
+ hasExistingCode: false,
97
+ configFiles: []
98
+ };
99
+ const pkgJsonPath = path2.resolve(dir, "package.json");
100
+ if (fs.existsSync(pkgJsonPath)) {
101
+ result.hasExistingCode = true;
102
+ result.configFiles.push("package.json");
103
+ try {
104
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
105
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
106
+ if (allDeps["next"]) result.techStack.frontend = "next.js";
107
+ else if (allDeps["react"]) result.techStack.frontend = "react";
108
+ else if (allDeps["vue"]) result.techStack.frontend = "vue";
109
+ else if (allDeps["svelte"]) result.techStack.frontend = "svelte";
110
+ else if (allDeps["angular"]) result.techStack.frontend = "angular";
111
+ if (allDeps["@shadcn/ui"] || allDeps["shadcn-ui"] || allDeps["shadcn"]) result.techStack.uiLibrary = "shadcn";
112
+ else if (allDeps["tailwindcss"]) result.techStack.uiLibrary = "tailwind";
113
+ else if (allDeps["@mui/material"]) result.techStack.uiLibrary = "material-ui";
114
+ if (allDeps["express"]) result.techStack.backend = "node/express";
115
+ else if (allDeps["fastify"]) result.techStack.backend = "node/fastify";
116
+ else if (allDeps["hono"]) result.techStack.backend = "node/hono";
117
+ result.type = "node";
118
+ } catch {
119
+ }
120
+ }
121
+ if (fs.existsSync(path2.resolve(dir, "tsconfig.json"))) {
122
+ result.hasExistingCode = true;
123
+ result.configFiles.push("tsconfig.json");
124
+ }
125
+ if (fs.existsSync(path2.resolve(dir, "pyproject.toml"))) {
126
+ result.hasExistingCode = true;
127
+ result.configFiles.push("pyproject.toml");
128
+ result.type = "python";
129
+ }
130
+ if (fs.existsSync(path2.resolve(dir, "requirements.txt"))) {
131
+ result.hasExistingCode = true;
132
+ result.configFiles.push("requirements.txt");
133
+ if (result.type === "unknown") result.type = "python";
134
+ }
135
+ if (fs.existsSync(path2.resolve(dir, "go.mod"))) {
136
+ result.hasExistingCode = true;
137
+ result.configFiles.push("go.mod");
138
+ result.type = "go";
139
+ }
140
+ if (fs.existsSync(path2.resolve(dir, "Cargo.toml"))) {
141
+ result.hasExistingCode = true;
142
+ result.configFiles.push("Cargo.toml");
143
+ result.type = "rust";
144
+ }
145
+ return result;
146
+ }
147
+ function plannedToTask(planned) {
148
+ const now = (/* @__PURE__ */ new Date()).toISOString();
149
+ return {
150
+ id: generateTaskId(),
151
+ title: planned.title,
152
+ description: planned.description,
153
+ status: "pending",
154
+ priority: planned.priority,
155
+ assignee: null,
156
+ dependencies: planned.dependencies,
157
+ tags: planned.tags,
158
+ createdAt: now,
159
+ updatedAt: now,
160
+ completedAt: null,
161
+ notes: []
162
+ };
163
+ }
164
+ function registerInitCommand(program) {
165
+ program.command("init").description("Initialize a new Maestro project").option("--no-plan", "skip automatic task planning (create empty todo.md)").option("--model <model>", "model to use for planning", "sonnet").option("-d, --dir <path>", "working directory for project context", ".").option("-i, --instructions <path>", "path to an instructions/requirements file for the planner").action(async (options) => {
166
+ const rl = readline.createInterface({
167
+ input: process.stdin,
168
+ output: process.stdout
169
+ });
170
+ try {
171
+ console.log();
172
+ console.log(chalk.bold(" MAESTRO") + " - Project Initialization");
173
+ console.log();
174
+ const claudeCheck = checkClaudeInstalled();
175
+ if (!claudeCheck.installed) {
176
+ console.log(chalk.red(` ${figures.cross} Claude CLI is not installed`));
177
+ console.log();
178
+ console.log(chalk.yellow(" Maestro requires Claude CLI to orchestrate agents."));
179
+ console.log();
180
+ console.log(chalk.bold(" Installation Instructions:"));
181
+ console.log(chalk.dim(getClaudeInstallInstructions()));
182
+ console.log();
183
+ const proceed = await ask(rl, chalk.yellow(" Continue anyway? (not recommended) (y/n)"), "n");
184
+ if (proceed.toLowerCase() !== "y") {
185
+ console.log();
186
+ console.log(chalk.dim(" Initialization cancelled. Install Claude CLI and try again."));
187
+ rl.close();
188
+ process.exit(0);
189
+ }
190
+ console.log();
191
+ } else {
192
+ console.log(chalk.green(` ${figures.tick} Claude CLI detected`));
193
+ if (claudeCheck.version) {
194
+ console.log(chalk.dim(` Version: ${claudeCheck.version}`));
195
+ }
196
+ console.log();
197
+ }
198
+ const targetDir = path2.resolve(options.dir);
199
+ if (targetDir !== process.cwd()) {
200
+ fs.mkdirSync(targetDir, { recursive: true });
201
+ process.chdir(targetDir);
202
+ console.log(chalk.dim(` Changed to directory: ${targetDir}`));
203
+ console.log();
204
+ }
205
+ const detected = detectExistingProject(targetDir);
206
+ if (detected.hasExistingCode) {
207
+ console.log(chalk.cyan(` ${figures.info} Existing ${detected.type} project detected`));
208
+ console.log(chalk.dim(` Found: ${detected.configFiles.join(", ")}`));
209
+ const detectedTech = Object.values(detected.techStack).filter(Boolean);
210
+ if (detectedTech.length > 0) {
211
+ console.log(chalk.dim(` Detected tech: ${detectedTech.join(", ")}`));
212
+ }
213
+ console.log();
214
+ }
215
+ let instructionsContent = "";
216
+ if (options.instructions) {
217
+ const instrPath = path2.resolve(options.instructions);
218
+ if (!fs.existsSync(instrPath)) {
219
+ console.error(chalk.red(` ${figures.cross} Instructions file not found: ${instrPath}`));
220
+ process.exit(1);
221
+ }
222
+ instructionsContent = fs.readFileSync(instrPath, "utf-8").trim();
223
+ console.log(chalk.cyan(` ${figures.info} Loaded instructions from ${chalk.bold(options.instructions)} (${instructionsContent.split("\n").length} lines)`));
224
+ console.log();
225
+ }
226
+ const projectName = await ask(rl, " Project name", path2.basename(targetDir));
227
+ console.log();
228
+ console.log(chalk.dim(" Describe what the agents should work on. This can be:"));
229
+ console.log(chalk.dim(" - A new app to build from scratch"));
230
+ console.log(chalk.dim(" - A feature to add to an existing project"));
231
+ console.log(chalk.dim(" - A bug fix, refactor, or any other work"));
232
+ if (instructionsContent) {
233
+ console.log(chalk.dim(" (Instructions file loaded \u2014 leave blank to use it as the primary goal)"));
234
+ }
235
+ const description = await ask(rl, " Goal / Description", instructionsContent ? "" : "");
236
+ const devCountStr = await ask(rl, " Number of developer agents", "3");
237
+ const devCount = Math.max(1, parseInt(devCountStr, 10) || 3);
238
+ console.log();
239
+ console.log(chalk.dim(" Tech Stack (leave blank to skip):"));
240
+ const frontend = await ask(rl, " Frontend framework (e.g., react, vue, svelte)", detected.techStack.frontend ?? "");
241
+ const uiLibrary = await ask(rl, " UI library (e.g., shadcn, tailwind, material-ui)", detected.techStack.uiLibrary ?? "");
242
+ const backend = await ask(rl, " Backend (e.g., node/express, python/fastapi, go)", detected.techStack.backend ?? "");
243
+ const database = await ask(rl, " Database (e.g., postgres, mongodb, sqlite)", detected.techStack.database ?? "");
244
+ const otherTech = await ask(rl, " Other tools/frameworks", "");
245
+ const techParts = [];
246
+ if (frontend) techParts.push(`Frontend: ${frontend}`);
247
+ if (uiLibrary) techParts.push(`UI: ${uiLibrary}`);
248
+ if (backend) techParts.push(`Backend: ${backend}`);
249
+ if (database) techParts.push(`Database: ${database}`);
250
+ if (otherTech) techParts.push(`Other: ${otherTech}`);
251
+ console.log();
252
+ const workingDirValue = ".";
253
+ const configDescription = description || (instructionsContent ? `See instructions file: ${options.instructions}` : "A Maestro-managed project");
254
+ const configContent = `# Maestro Project Configuration
255
+ name: "${projectName}"
256
+ description: "${configDescription}"
257
+ version: "1.0.0"
258
+
259
+ agents:
260
+ - role: orchestrator
261
+ count: 1
262
+
263
+ - role: project-manager
264
+ count: 1
265
+
266
+ - role: architect
267
+ count: 1
268
+
269
+ - role: developer
270
+ count: ${devCount}
271
+
272
+ - role: designer
273
+ count: 1
274
+
275
+ - role: qa-engineer
276
+ count: 1
277
+
278
+ - role: devops
279
+ count: 1
280
+
281
+ - role: technical-writer
282
+ count: 1
283
+
284
+ - role: code-reviewer
285
+ count: 1
286
+
287
+ settings:
288
+ workingDirectory: "${workingDirValue}"
289
+ todoFile: "todo.md"
290
+ messagesDirectory: ".maestro/messages"
291
+ logsDirectory: ".maestro/logs"
292
+ defaultModel: "claude-sonnet-4-20250514"
293
+ maxConcurrentAgents: 10
294
+ pollIntervalMs: 5000
295
+ maxTotalBudgetUsd: 50.00
296
+
297
+ # Claude CLI configuration
298
+ # NOTE: Agents run with skip-permissions. Process safety (no builds, no Docker,
299
+ # no servers) is enforced via system prompts in .agents/*.md. Edit those files
300
+ # to customise safety rules.
301
+ claudeCommand: claude
302
+ claudeArgs:
303
+ - "--dangerously-skip-permissions"
304
+
305
+ feedback:
306
+ interactionMode: supervised
307
+ requirePlanApproval: true
308
+ progressReportIntervalMs: 60000
309
+ milestonePercentages: [25, 50, 75, 100]
310
+ questionTimeoutMs: 300000
311
+ ${techParts.length > 0 ? `
312
+ techStack:
313
+ ${frontend ? ` frontend: "${frontend}"
314
+ ` : ""}${uiLibrary ? ` uiLibrary: "${uiLibrary}"
315
+ ` : ""}${backend ? ` backend: "${backend}"
316
+ ` : ""}${database ? ` database: "${database}"
317
+ ` : ""}${otherTech ? ` other: "${otherTech}"
318
+ ` : ""}` : ""}`;
319
+ const maestroYamlPath = path2.resolve("maestro.yaml");
320
+ if (fs.existsSync(maestroYamlPath)) {
321
+ const overwrite = await ask(rl, ` ${chalk.yellow("maestro.yaml already exists. Overwrite? (y/n)")}`, "n");
322
+ if (overwrite.toLowerCase() === "y") {
323
+ fs.writeFileSync(maestroYamlPath, configContent, "utf-8");
324
+ console.log(` ${chalk.green(figures.tick)} Updated ${chalk.bold("maestro.yaml")}`);
325
+ } else {
326
+ console.log(chalk.dim(" Skipping maestro.yaml"));
327
+ }
328
+ } else {
329
+ fs.writeFileSync(maestroYamlPath, configContent, "utf-8");
330
+ console.log(` ${chalk.green(figures.tick)} Created ${chalk.bold("maestro.yaml")}`);
331
+ }
332
+ const dirs = [
333
+ ".maestro",
334
+ ".maestro/messages",
335
+ ".maestro/logs",
336
+ ".maestro/sessions"
337
+ ];
338
+ for (const dir of dirs) {
339
+ const dirPath = path2.resolve(dir);
340
+ if (!fs.existsSync(dirPath)) {
341
+ fs.mkdirSync(dirPath, { recursive: true });
342
+ console.log(` ${chalk.green(figures.tick)} Created ${chalk.bold(dir + "/")}`);
343
+ }
344
+ }
345
+ const gitignorePath = path2.resolve(".maestro/.gitignore");
346
+ if (!fs.existsSync(gitignorePath)) {
347
+ fs.writeFileSync(gitignorePath, `logs/
348
+ sessions/
349
+ stop
350
+ `, "utf-8");
351
+ console.log(` ${chalk.green(figures.tick)} Created ${chalk.bold(".maestro/.gitignore")}`);
352
+ }
353
+ const techStackConfig = techParts.length > 0 ? {
354
+ frontend: frontend || void 0,
355
+ uiLibrary: uiLibrary || void 0,
356
+ backend: backend || void 0,
357
+ database: database || void 0,
358
+ other: otherTech || void 0
359
+ } : void 0;
360
+ const agentsDir = path2.resolve(".agents");
361
+ if (!fs.existsSync(agentsDir)) {
362
+ fs.mkdirSync(agentsDir, { recursive: true });
363
+ console.log(` ${chalk.green(figures.tick)} Created ${chalk.bold(".agents/")}`);
364
+ }
365
+ const roles = ["orchestrator", "project-manager", "architect", "developer", "designer", "qa-engineer", "devops", "technical-writer", "code-reviewer"];
366
+ let createdPromptCount = 0;
367
+ for (const role of roles) {
368
+ const promptPath = path2.resolve(agentsDir, `${role}.md`);
369
+ if (!fs.existsSync(promptPath)) {
370
+ const prompt = getDefaultSystemPrompt({
371
+ role,
372
+ agentId: "{{agentId}}",
373
+ projectName: "{{projectName}}",
374
+ todoFilePath: "{{todoFilePath}}",
375
+ inboxPath: "{{inboxPath}}",
376
+ outboxPath: "{{outboxPath}}",
377
+ workingDirectory: "{{workingDirectory}}",
378
+ techStack: techStackConfig
379
+ });
380
+ const header = [
381
+ `<!-- Maestro Agent Prompt: ${role} -->`,
382
+ `<!-- Edit this file to customise the ${role} agent's behaviour. -->`,
383
+ `<!-- Delete this file to fall back to the built-in default prompt. -->`,
384
+ ""
385
+ ].join("\n");
386
+ fs.writeFileSync(promptPath, header + prompt + "\n", "utf-8");
387
+ createdPromptCount++;
388
+ }
389
+ }
390
+ if (createdPromptCount > 0) {
391
+ console.log(` ${chalk.green(figures.tick)} Created ${createdPromptCount} agent prompt files in ${chalk.bold(".agents/")}`);
392
+ }
393
+ const todoPath = path2.resolve("todo.md");
394
+ const hasExistingTodo = fs.existsSync(todoPath);
395
+ let existingTasks = [];
396
+ if (hasExistingTodo) {
397
+ const parser = new TaskParser();
398
+ const existing = parser.parse(fs.readFileSync(todoPath, "utf-8"));
399
+ existingTasks = existing.tasks;
400
+ console.log(chalk.cyan(` ${figures.info} Found existing ${chalk.bold("todo.md")} with ${existingTasks.length} tasks`));
401
+ const doneTasks = existingTasks.filter((t) => t.status === "done").length;
402
+ const inProgress = existingTasks.filter((t) => t.status === "in-progress").length;
403
+ const pending = existingTasks.filter((t) => t.status === "pending").length;
404
+ console.log(chalk.dim(` ${doneTasks} done, ${inProgress} in-progress, ${pending} pending`));
405
+ console.log();
406
+ }
407
+ if (options.plan) {
408
+ console.log();
409
+ const planner = new Planner({
410
+ model: options.model,
411
+ workingDirectory: process.cwd(),
412
+ techStack: techStackConfig
413
+ });
414
+ let planGoal = "";
415
+ if (instructionsContent) {
416
+ planGoal = instructionsContent;
417
+ if (description) {
418
+ planGoal = `${description}
419
+
420
+ --- Detailed Instructions ---
421
+ ${instructionsContent}`;
422
+ }
423
+ } else {
424
+ planGoal = description || `Set up and develop ${projectName}`;
425
+ }
426
+ if (detected.hasExistingCode) {
427
+ planGoal += `
428
+
429
+ This is an existing ${detected.type} project. Found config files: ${detected.configFiles.join(", ")}.`;
430
+ planGoal += `
431
+ Build on the existing codebase rather than creating from scratch.`;
432
+ }
433
+ if (existingTasks.length > 0) {
434
+ planGoal += `
435
+
436
+ EXISTING TASKS (do NOT duplicate these \u2014 only create NEW tasks for work not already covered):`;
437
+ for (const t of existingTasks) {
438
+ planGoal += `
439
+ - [${t.status}] ${t.id}: ${t.title}`;
440
+ }
441
+ }
442
+ const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
443
+ let spinnerIndex = 0;
444
+ let spinnerActive = true;
445
+ const spinnerInterval = setInterval(() => {
446
+ if (!spinnerActive) return;
447
+ const frame = chalk.cyan(spinnerFrames[spinnerIndex]);
448
+ process.stdout.write(`\r ${frame} ${chalk.dim("Planning tasks with Claude...")}`);
449
+ spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
450
+ }, 80);
451
+ try {
452
+ const plannedTasks = await planner.decompose(planGoal);
453
+ spinnerActive = false;
454
+ clearInterval(spinnerInterval);
455
+ process.stdout.write("\r " + chalk.green(figures.tick) + " " + chalk.dim("Planning tasks with Claude... done") + "\n");
456
+ let maxExistingNum = 0;
457
+ for (const t of existingTasks) {
458
+ const match = t.id.match(/^T-(\d+)$/);
459
+ if (match) {
460
+ maxExistingNum = Math.max(maxExistingNum, parseInt(match[1], 10));
461
+ }
462
+ }
463
+ resetTaskCounter(maxExistingNum);
464
+ const titleToId = /* @__PURE__ */ new Map();
465
+ for (const t of existingTasks) {
466
+ titleToId.set(t.title, t.id);
467
+ }
468
+ const newTasks = [];
469
+ for (const planned of plannedTasks) {
470
+ const task = plannedToTask(planned);
471
+ task.dependencies = planned.dependencies.map((depTitle) => titleToId.get(depTitle)).filter((id) => id != null);
472
+ titleToId.set(planned.title, task.id);
473
+ newTasks.push(task);
474
+ }
475
+ const allTasks = [...existingTasks, ...newTasks];
476
+ const writer = new TaskWriter();
477
+ const todoContent = writer.write(projectName, allTasks);
478
+ fs.writeFileSync(todoPath, todoContent, "utf-8");
479
+ if (existingTasks.length > 0) {
480
+ console.log(
481
+ chalk.green(` ${figures.tick} Updated ${chalk.bold("todo.md")}: ${existingTasks.length} existing + ${newTasks.length} new tasks`)
482
+ );
483
+ } else {
484
+ console.log(
485
+ chalk.green(` ${figures.tick} Created ${chalk.bold("todo.md")} with ${newTasks.length} planned tasks`)
486
+ );
487
+ }
488
+ console.log();
489
+ if (newTasks.length > 0) {
490
+ if (existingTasks.length > 0) {
491
+ console.log(chalk.dim(" New tasks:"));
492
+ }
493
+ for (const task of newTasks) {
494
+ const priorityColor = task.priority === "critical" ? chalk.red : task.priority === "high" ? chalk.yellow : task.priority === "medium" ? chalk.cyan : chalk.dim;
495
+ console.log(
496
+ ` ${priorityColor(task.id)} ${task.title}`
497
+ );
498
+ }
499
+ }
500
+ } catch (err) {
501
+ spinnerActive = false;
502
+ clearInterval(spinnerInterval);
503
+ process.stdout.write("\r " + chalk.yellow(figures.warning) + " " + chalk.dim("Planning tasks with Claude... failed") + "\n");
504
+ console.log(
505
+ chalk.yellow(
506
+ ` ${figures.warning} Planning failed: ${err.message}`
507
+ )
508
+ );
509
+ if (!hasExistingTodo) {
510
+ console.log(chalk.dim(" Creating empty todo.md instead"));
511
+ writeEmptyTodo(todoPath, projectName);
512
+ } else {
513
+ console.log(chalk.dim(" Keeping existing todo.md unchanged"));
514
+ }
515
+ }
516
+ } else if (!hasExistingTodo) {
517
+ writeEmptyTodo(todoPath, projectName);
518
+ } else {
519
+ console.log(chalk.dim(` Keeping existing ${chalk.bold("todo.md")} (${existingTasks.length} tasks)`));
520
+ }
521
+ console.log();
522
+ console.log(chalk.green(" Project initialized successfully!"));
523
+ console.log();
524
+ console.log(chalk.bold(" Process Safety"));
525
+ console.log();
526
+ console.log(chalk.dim(" Agents share your machine's CPU and memory. To keep things stable,"));
527
+ console.log(chalk.dim(" they follow built-in process safety rules:"));
528
+ console.log();
529
+ console.log(` ${chalk.green(figures.tick)} ${chalk.white("Agents will")}:`);
530
+ console.log(chalk.dim(" - Write production code, tests, configs, docs, and infrastructure files"));
531
+ console.log(chalk.dim(" - Run lightweight commands (linters, type-checks on single files)"));
532
+ console.log(chalk.dim(" - Run targeted test files (a single spec, not the full suite)"));
533
+ console.log(chalk.dim(" - Install individual packages (e.g. npm install <pkg>)"));
534
+ console.log();
535
+ console.log(` ${chalk.red(figures.cross)} ${chalk.white("Agents will not")}:`);
536
+ console.log(chalk.dim(" - Run builds (npm run build, cargo build, make, etc.)"));
537
+ console.log(chalk.dim(" - Run Docker commands (docker build, docker compose up, etc.)"));
538
+ console.log(chalk.dim(" - Start dev servers (npm run dev, npm start, flask run, etc.)"));
539
+ console.log(chalk.dim(" - Run full dependency installs (npm install with no args)"));
540
+ console.log(chalk.dim(" - Run full test suites (npm test, pytest with no args)"));
541
+ console.log();
542
+ console.log(` ${chalk.cyan(figures.info)} ${chalk.white("You handle")}:`);
543
+ console.log(chalk.dim(" - Building the project when agents finish writing code"));
544
+ console.log(chalk.dim(" - Running Docker and starting dev servers"));
545
+ console.log(chalk.dim(" - Running full test suites and CI pipelines"));
546
+ console.log(chalk.dim(" - Reviewing and approving agent plans (in supervised mode)"));
547
+ console.log();
548
+ console.log(chalk.dim(` Want full autonomy? Edit the process safety section in ${chalk.bold(".agents/*.md")}`));
549
+ console.log(chalk.dim(` to loosen or remove these restrictions per role.`));
550
+ console.log();
551
+ console.log(` Next steps:`);
552
+ console.log(` 1. Review ${chalk.bold("todo.md")} and edit tasks if needed`);
553
+ console.log(` 2. Edit ${chalk.bold("maestro.yaml")} to adjust agents and settings`);
554
+ console.log(` 3. Customize agent prompts in ${chalk.bold(".agents/")} (optional)`);
555
+ console.log(` 4. Run ${chalk.bold("maestro start")} to begin`);
556
+ if (instructionsContent) {
557
+ console.log();
558
+ console.log(chalk.dim(` Instructions from ${chalk.bold(options.instructions)} were used for task planning.`));
559
+ }
560
+ console.log();
561
+ } finally {
562
+ rl.close();
563
+ }
564
+ });
565
+ }
566
+ function writeEmptyTodo(todoPath, projectName) {
567
+ if (!fs.existsSync(todoPath)) {
568
+ const writer = new TaskWriter();
569
+ const todoContent = writer.write(projectName, []);
570
+ fs.writeFileSync(todoPath, todoContent, "utf-8");
571
+ console.log(` ${chalk.green(figures.tick)} Created ${chalk.bold("todo.md")}`);
572
+ }
573
+ }
574
+
575
+ // src/commands/start.ts
576
+ import * as fs3 from "fs";
577
+ import * as path4 from "path";
578
+ import chalk2 from "chalk";
579
+ import figures2 from "figures";
580
+ import {
581
+ loadProjectConfig,
582
+ WorkspaceManager,
583
+ Orchestrator
584
+ } from "@maestroai/core";
585
+
586
+ // src/logging/file-logger.ts
587
+ import * as fs2 from "fs";
588
+ import * as path3 from "path";
589
+ var FileLogger = class {
590
+ logsDir;
591
+ streams = /* @__PURE__ */ new Map();
592
+ constructor(logsDir) {
593
+ this.logsDir = logsDir;
594
+ fs2.mkdirSync(this.logsDir, { recursive: true });
595
+ }
596
+ /**
597
+ * Attach to an orchestrator and start logging all agent output.
598
+ */
599
+ attach(orchestrator) {
600
+ orchestrator.on("agent:output", (event) => {
601
+ this.writeEvent(event);
602
+ });
603
+ orchestrator.on("agent:spawned", (state) => {
604
+ const agentId = state.config.id;
605
+ this.writeLine(
606
+ agentId,
607
+ `[system] Agent spawned: ${state.config.name} (${state.config.role})`
608
+ );
609
+ });
610
+ orchestrator.on("agent:status-changed", (agentId, oldStatus, newStatus) => {
611
+ this.writeLine(agentId, `[system] Status: ${oldStatus} -> ${newStatus}`);
612
+ });
613
+ orchestrator.on("agent:error", (agentId, error) => {
614
+ this.writeLine(agentId, `[error] ${error.message}`);
615
+ });
616
+ orchestrator.on("agent:stopped", (agentId, exitCode) => {
617
+ this.writeLine(
618
+ agentId,
619
+ `[system] Agent stopped (exit code: ${exitCode})`
620
+ );
621
+ this.closeStream(agentId);
622
+ });
623
+ }
624
+ /**
625
+ * Close all open log streams.
626
+ */
627
+ close() {
628
+ for (const [agentId] of this.streams) {
629
+ this.closeStream(agentId);
630
+ }
631
+ }
632
+ writeEvent(event) {
633
+ this.writeLine(event.agentId, `[${event.type}] ${JSON.stringify(event.data)}`);
634
+ }
635
+ writeLine(agentId, line) {
636
+ const stream = this.getOrCreateStream(agentId);
637
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
638
+ stream.write(`${timestamp} ${line}
639
+ `);
640
+ }
641
+ getOrCreateStream(agentId) {
642
+ let stream = this.streams.get(agentId);
643
+ if (!stream) {
644
+ const logFile = path3.join(this.logsDir, `${agentId}.log`);
645
+ stream = fs2.createWriteStream(logFile, { flags: "a" });
646
+ this.streams.set(agentId, stream);
647
+ }
648
+ return stream;
649
+ }
650
+ closeStream(agentId) {
651
+ const stream = this.streams.get(agentId);
652
+ if (stream) {
653
+ stream.end();
654
+ this.streams.delete(agentId);
655
+ }
656
+ }
657
+ };
658
+
659
+ // src/commands/start.ts
660
+ function truncate(str, max) {
661
+ return str.length > max ? str.slice(0, max - 3) + "..." : str;
662
+ }
663
+ function formatDuration(ms) {
664
+ const seconds = Math.floor(ms / 1e3);
665
+ const minutes = Math.floor(seconds / 60);
666
+ const hours = Math.floor(minutes / 60);
667
+ if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
668
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
669
+ return `${seconds}s`;
670
+ }
671
+ function registerStartCommand(program) {
672
+ program.command("start").description("Start the Maestro orchestrator").option("-c, --config <path>", "path to maestro.yaml", "./maestro.yaml").option("-d, --dir <path>", "working directory for project context", ".").option("--no-tui", "run without TUI (log to console)").option("-m, --mode <mode>", "interaction mode (unattended, supervised, interactive)", "supervised").option("-v, --verbose", "show detailed agent output including tool calls", false).action(async (options) => {
673
+ if (options.dir && options.dir !== ".") {
674
+ try {
675
+ process.chdir(path4.resolve(options.dir));
676
+ console.log(chalk2.dim(` Working directory changed to: ${process.cwd()}`));
677
+ } catch (err) {
678
+ console.error(
679
+ chalk2.red(`${figures2.cross} Failed to change directory: ${err.message}`)
680
+ );
681
+ process.exit(1);
682
+ }
683
+ }
684
+ const configPath = path4.resolve(options.config);
685
+ if (!fs3.existsSync(configPath)) {
686
+ console.error(
687
+ chalk2.red(`${figures2.cross} Config file not found: ${configPath}`)
688
+ );
689
+ console.error(
690
+ chalk2.dim(` Run ${chalk2.bold("maestro init")} to create one.`)
691
+ );
692
+ process.exit(1);
693
+ }
694
+ const stopFilePath = path4.resolve(".maestro/stop");
695
+ if (fs3.existsSync(stopFilePath)) {
696
+ fs3.unlinkSync(stopFilePath);
697
+ }
698
+ let projectConfig;
699
+ try {
700
+ projectConfig = await loadProjectConfig(configPath);
701
+ } catch (err) {
702
+ console.error(
703
+ chalk2.red(`${figures2.cross} Failed to load config: ${err.message}`)
704
+ );
705
+ process.exit(1);
706
+ }
707
+ if (options.mode) {
708
+ projectConfig.settings.feedback = {
709
+ progressReportIntervalMs: 6e4,
710
+ milestonePercentages: [25, 50, 75, 100],
711
+ questionTimeoutMs: 3e5,
712
+ requirePlanApproval: true,
713
+ ...projectConfig.settings.feedback,
714
+ interactionMode: options.mode
715
+ };
716
+ }
717
+ const workspace = new WorkspaceManager(process.cwd());
718
+ await workspace.initialize(projectConfig);
719
+ const orchestrator = new Orchestrator(projectConfig);
720
+ const fileLogger = new FileLogger(
721
+ projectConfig.settings.logsDirectory || ".maestro/logs"
722
+ );
723
+ fileLogger.attach(orchestrator);
724
+ let shuttingDown = false;
725
+ const SHUTDOWN_TIMEOUT_MS = 15e3;
726
+ const shutdown = async (signal) => {
727
+ if (shuttingDown) return;
728
+ shuttingDown = true;
729
+ clearInterval(stopWatcher);
730
+ if (!options.tui) {
731
+ console.log();
732
+ console.log(
733
+ chalk2.yellow(`${figures2.warning} Received ${signal}, shutting down gracefully...`)
734
+ );
735
+ }
736
+ const forceExitTimer = setTimeout(() => {
737
+ if (!options.tui) {
738
+ console.log(
739
+ chalk2.red(`${figures2.cross} Shutdown timed out, forcing exit`)
740
+ );
741
+ }
742
+ process.exit(1);
743
+ }, SHUTDOWN_TIMEOUT_MS);
744
+ forceExitTimer.unref();
745
+ try {
746
+ await orchestrator.stop();
747
+ } catch {
748
+ }
749
+ fileLogger.close();
750
+ clearTimeout(forceExitTimer);
751
+ process.exit(0);
752
+ };
753
+ process.on("SIGINT", () => shutdown("SIGINT"));
754
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
755
+ const stopWatcher = setInterval(() => {
756
+ if (fs3.existsSync(stopFilePath)) {
757
+ clearInterval(stopWatcher);
758
+ shutdown("stop-file");
759
+ }
760
+ }, 1e3);
761
+ stopWatcher.unref();
762
+ if (options.tui) {
763
+ const { renderApp } = await import("./src/tui/App.js");
764
+ renderApp(orchestrator, projectConfig);
765
+ await orchestrator.start();
766
+ } else {
767
+ console.log();
768
+ console.log(chalk2.bold(" MAESTRO") + ` - ${projectConfig.name}`);
769
+ console.log(chalk2.dim(` ${projectConfig.description}`));
770
+ console.log();
771
+ const agentNameMap = /* @__PURE__ */ new Map();
772
+ orchestrator.on("agent:spawned", (state) => {
773
+ agentNameMap.set(state.config.id, state.config.name);
774
+ const pid = state.pid ? ` (PID ${state.pid})` : "";
775
+ console.log(
776
+ chalk2.green(`${figures2.play} Agent spawned: ${state.config.name} [${state.config.role}]${pid}`)
777
+ );
778
+ console.log(
779
+ chalk2.dim(` model: ${state.config.model ?? "default"} | mode: ${state.config.permissionMode}`)
780
+ );
781
+ });
782
+ orchestrator.on("agent:status-changed", (agentId, _oldStatus, newStatus) => {
783
+ const name = agentNameMap.get(agentId) ?? agentId;
784
+ const color = newStatus === "running" ? chalk2.green : newStatus === "error" ? chalk2.red : chalk2.yellow;
785
+ console.log(color(`${figures2.pointer} ${name}: ${newStatus}`));
786
+ });
787
+ orchestrator.on("agent:output", (event) => {
788
+ const displayName = agentNameMap.get(event.agentId) ?? event.agentId;
789
+ const prefix = chalk2.dim(` [${displayName}]`);
790
+ if (event.type === "user") {
791
+ const message = event.data.message;
792
+ if (message && typeof message === "object" && Array.isArray(message.content)) {
793
+ for (const block of message.content) {
794
+ if (block.type !== "tool_result") continue;
795
+ let resultText = "";
796
+ if (typeof block.content === "string") {
797
+ resultText = block.content.trim();
798
+ } else if (Array.isArray(block.content)) {
799
+ resultText = block.content.filter((s) => s.type === "text" && typeof s.text === "string").map((s) => s.text.trim()).join(" ");
800
+ }
801
+ if (resultText) {
802
+ const firstLine = resultText.split("\n")[0];
803
+ console.log(`${prefix} ${chalk2.dim(truncate(firstLine, 150))}`);
804
+ }
805
+ }
806
+ }
807
+ return;
808
+ }
809
+ if (event.type === "assistant") {
810
+ let text = "";
811
+ const message = event.data.message;
812
+ if (message && typeof message === "object" && Array.isArray(message.content)) {
813
+ text = message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
814
+ } else if (typeof event.data.content === "string") {
815
+ text = event.data.content;
816
+ } else if (typeof message === "string") {
817
+ text = message;
818
+ }
819
+ if (text) {
820
+ for (const line of text.split("\n")) {
821
+ console.log(`${prefix} ${line}`);
822
+ }
823
+ }
824
+ } else if (event.type === "result") {
825
+ const cost = typeof event.data.cost_usd === "number" ? `$${event.data.cost_usd.toFixed(4)}` : "";
826
+ const turns = typeof event.data.num_turns === "number" ? `${event.data.num_turns} turns` : "";
827
+ const usage = event.data.usage;
828
+ let usageStr = "";
829
+ if (usage && typeof usage === "object") {
830
+ const parts = [];
831
+ if (typeof usage.input_tokens === "number") parts.push(`in:${usage.input_tokens}`);
832
+ if (typeof usage.output_tokens === "number") parts.push(`out:${usage.output_tokens}`);
833
+ if (typeof usage.cache_read_input_tokens === "number" && usage.cache_read_input_tokens > 0) {
834
+ parts.push(`cache:${usage.cache_read_input_tokens}`);
835
+ }
836
+ if (parts.length > 0) usageStr = parts.join(" ");
837
+ }
838
+ console.log(`${prefix} ${chalk2.green("done")} ${[cost, turns, usageStr].filter(Boolean).join(" | ")}`);
839
+ } else if (event.type === "error") {
840
+ const msg = event.data.error ?? JSON.stringify(event.data);
841
+ console.log(`${prefix} ${chalk2.red(`error: ${msg}`)}`);
842
+ } else if (options.verbose) {
843
+ if (event.type === "tool_use") {
844
+ const toolName = event.data.tool ?? event.data.name ?? "tool";
845
+ console.log(`${prefix} ${chalk2.cyan(`[${toolName}]`)} ${chalk2.dim(truncate(JSON.stringify(event.data.input ?? ""), 120))}`);
846
+ } else if (event.type === "system") {
847
+ const msg = event.data.message ?? "";
848
+ if (msg) console.log(`${prefix} ${chalk2.dim(msg)}`);
849
+ }
850
+ }
851
+ });
852
+ orchestrator.on("task:assigned", (task, agentId) => {
853
+ const name = agentNameMap.get(agentId) ?? agentId;
854
+ console.log(
855
+ chalk2.blue(`${figures2.pointer} Task assigned: ${task.id} "${task.title}" \u2192 ${name}`)
856
+ );
857
+ });
858
+ orchestrator.on("task:completed", (task) => {
859
+ console.log(
860
+ chalk2.green(`${figures2.tick} Task completed: ${task.title}`)
861
+ );
862
+ });
863
+ orchestrator.on("orchestrator:cycle", (stats) => {
864
+ const sorted = [...stats.agents].sort((a, b) => {
865
+ const rank = (agent) => {
866
+ if (agent.status === "running" || agent.status === "starting") return 0;
867
+ if (agent.spawned && agent.status === "idle") return 1;
868
+ if (agent.status === "error") return 2;
869
+ if (!agent.spawned) return 4;
870
+ return 3;
871
+ };
872
+ return rank(a) - rank(b);
873
+ });
874
+ const agentTags = sorted.map((a) => {
875
+ const label = a.name;
876
+ if (a.status === "running" || a.status === "starting") {
877
+ return chalk2.green.bold(label);
878
+ } else if (a.status === "error") {
879
+ return chalk2.red(label);
880
+ } else if (!a.spawned) {
881
+ return chalk2.gray(`${label} (Standby)`);
882
+ } else if (a.status === "stopped") {
883
+ return chalk2.dim(label);
884
+ }
885
+ return chalk2.yellow(label);
886
+ });
887
+ const totalTokens = stats.totalInputTokens + stats.totalOutputTokens + stats.totalCacheReadTokens + stats.totalCacheCreationTokens;
888
+ const tokenStr = totalTokens > 0 ? ` | tokens: ${totalTokens.toLocaleString()}` : "";
889
+ console.log(
890
+ chalk2.dim(` [cycle] tasks: ${stats.completedTasks}/${stats.totalTasks} | cost: $${stats.totalCostUsd.toFixed(4)}${tokenStr}`)
891
+ );
892
+ console.log(
893
+ chalk2.dim(" agents: ") + agentTags.join(chalk2.dim(" | "))
894
+ );
895
+ });
896
+ orchestrator.on("rate-limit", (agentId, resetAt) => {
897
+ const name = agentNameMap.get(agentId) ?? agentId;
898
+ const resetInfo = resetAt ? ` Resets at ${resetAt}.` : "";
899
+ console.log(
900
+ chalk2.red.bold(`${figures2.warning} RATE LIMIT: Agent ${name} hit API usage limit.${resetInfo}`)
901
+ );
902
+ });
903
+ orchestrator.on("plan:proposed", (proposal) => {
904
+ console.log(chalk2.cyan(`
905
+ ${figures2.info} Plan proposed with ${proposal.tasks.length} tasks:`));
906
+ for (const task of proposal.tasks) {
907
+ console.log(chalk2.dim(` - [${task.priority}] ${task.title}`));
908
+ }
909
+ orchestrator.decidePlan(proposal.id, "approved");
910
+ console.log(chalk2.green(`${figures2.tick} Plan auto-approved (non-TUI mode)`));
911
+ });
912
+ orchestrator.on("architecture:ready", (archTasks) => {
913
+ console.log();
914
+ console.log(chalk2.bold.cyan(`${figures2.info} Architecture phase complete!`));
915
+ console.log(chalk2.dim(" Review ARCHITECTURE.md and architecture-related files."));
916
+ console.log(chalk2.dim(" Completed architecture tasks:"));
917
+ for (const task of archTasks) {
918
+ console.log(chalk2.dim(` - ${task.title}`));
919
+ }
920
+ orchestrator.approveArchitecture();
921
+ console.log(chalk2.green(`${figures2.tick} Architecture auto-approved, advancing to development`));
922
+ });
923
+ orchestrator.on("phase:changed", (phase) => {
924
+ console.log(chalk2.cyan(`${figures2.pointer} Phase: ${phase}`));
925
+ });
926
+ orchestrator.on("question:asked", (question) => {
927
+ console.log(chalk2.yellow(`
928
+ ${figures2.warning} Agent ${question.fromAgent} asks: ${question.question}`));
929
+ if (question.options) {
930
+ question.options.forEach((opt, i) => console.log(chalk2.dim(` ${i + 1}. ${opt}`)));
931
+ }
932
+ console.log(chalk2.dim(" (auto-skipping in non-TUI mode)"));
933
+ });
934
+ orchestrator.on("orchestrator:activity", (activity) => {
935
+ const kindColors = {
936
+ "task-delegated": chalk2.cyan,
937
+ "task-completed": chalk2.green,
938
+ "task-blocked": chalk2.red,
939
+ "task-failed": chalk2.red,
940
+ "agent-spawned": chalk2.green,
941
+ "agent-stopped": chalk2.gray,
942
+ "agent-error": chalk2.red,
943
+ "phase-changed": chalk2.magenta,
944
+ "pipeline-handoff": chalk2.yellow,
945
+ "plan-created": chalk2.blue,
946
+ "plan-approved": chalk2.green,
947
+ "plan-rejected": chalk2.red,
948
+ "project-completed": chalk2.green,
949
+ "info": chalk2.blue
950
+ };
951
+ const color = kindColors[activity.kind] ?? chalk2.dim;
952
+ const time = new Date(activity.timestamp).toLocaleTimeString("en-US", {
953
+ hour12: false,
954
+ hour: "2-digit",
955
+ minute: "2-digit",
956
+ second: "2-digit"
957
+ });
958
+ console.log(
959
+ chalk2.yellow(` [Orchestrator]`) + chalk2.dim(` ${time} `) + color(activity.message)
960
+ );
961
+ });
962
+ orchestrator.on("progress:report", (report) => {
963
+ console.log(chalk2.cyan(` [progress] ${report.percentComplete.toFixed(0)}% | phase: ${report.phase} | ${report.completedTasks}/${report.totalTasks} tasks`));
964
+ });
965
+ orchestrator.on("milestone:reached", (milestone) => {
966
+ console.log(chalk2.bold.green(`
967
+ ${figures2.star} Milestone: ${milestone.name} - ${milestone.message}`));
968
+ orchestrator.acknowledgeMilestone(milestone.id);
969
+ });
970
+ orchestrator.on("project:completed", (summary) => {
971
+ const totalTokens = summary.totalInputTokens + summary.totalOutputTokens + summary.totalCacheReadTokens + summary.totalCacheCreationTokens;
972
+ console.log();
973
+ console.log(chalk2.bold.green("=".repeat(60)));
974
+ console.log(chalk2.bold.green(` ${figures2.star} PROJECT COMPLETED ${figures2.star}`));
975
+ console.log(chalk2.bold.green("=".repeat(60)));
976
+ console.log();
977
+ console.log(chalk2.bold(" Tasks"));
978
+ console.log(` Completed: ${chalk2.green(String(summary.doneTasks))} / ${summary.totalTasks}`);
979
+ if (summary.cancelledTasks > 0) {
980
+ console.log(` Cancelled: ${chalk2.yellow(String(summary.cancelledTasks))}`);
981
+ }
982
+ console.log();
983
+ console.log(chalk2.bold(" Usage"));
984
+ console.log(` Cost: ${chalk2.cyan("$" + summary.totalCostUsd.toFixed(4))}`);
985
+ if (totalTokens > 0) {
986
+ console.log(` Tokens: ${totalTokens.toLocaleString()} total`);
987
+ console.log(` ${chalk2.dim(`in: ${summary.totalInputTokens.toLocaleString()} | out: ${summary.totalOutputTokens.toLocaleString()} | cache-read: ${summary.totalCacheReadTokens.toLocaleString()} | cache-write: ${summary.totalCacheCreationTokens.toLocaleString()}`)}`);
988
+ }
989
+ console.log(` Duration: ${formatDuration(summary.uptimeMs)}`);
990
+ console.log();
991
+ const activeAgents = summary.agentSummaries.filter((a) => a.tasksCompleted > 0);
992
+ if (activeAgents.length > 0) {
993
+ console.log(chalk2.bold(" Agent Breakdown"));
994
+ for (const agent of activeAgents) {
995
+ const agentTokens = agent.inputTokens + agent.outputTokens;
996
+ const tokenInfo = agentTokens > 0 ? `, ${agentTokens.toLocaleString()} tokens` : "";
997
+ console.log(
998
+ ` ${agent.name} ${chalk2.dim(`(${agent.role})`)}: ${agent.tasksCompleted} tasks, $${agent.costUsd.toFixed(4)}${tokenInfo}`
999
+ );
1000
+ }
1001
+ console.log();
1002
+ }
1003
+ console.log(chalk2.bold(" Next Steps"));
1004
+ console.log(` ${figures2.arrowRight} Review the completed work in your project directory`);
1005
+ console.log(` ${figures2.arrowRight} Run your test suite to verify the changes`);
1006
+ console.log(` ${figures2.arrowRight} Check git diff to review all modifications`);
1007
+ console.log(` ${figures2.arrowRight} Commit and push when ready`);
1008
+ console.log();
1009
+ console.log(chalk2.dim(" " + "=".repeat(56)));
1010
+ console.log();
1011
+ });
1012
+ await orchestrator.start();
1013
+ console.log(
1014
+ chalk2.green(`${figures2.tick} Orchestrator started`)
1015
+ );
1016
+ }
1017
+ });
1018
+ }
1019
+
1020
+ // src/commands/status.ts
1021
+ import * as fs4 from "fs";
1022
+ import * as path5 from "path";
1023
+ import chalk3 from "chalk";
1024
+ import figures3 from "figures";
1025
+ function parseTodoFile(content) {
1026
+ const counts = {
1027
+ pending: 0,
1028
+ inProgress: 0,
1029
+ done: 0,
1030
+ blocked: 0,
1031
+ cancelled: 0,
1032
+ total: 0
1033
+ };
1034
+ const lines = content.split("\n");
1035
+ let currentSection = "";
1036
+ for (const line of lines) {
1037
+ const trimmed = line.trim();
1038
+ if (trimmed.startsWith("## ")) {
1039
+ const heading = trimmed.slice(3).trim().toLowerCase();
1040
+ if (heading.includes("pending")) currentSection = "pending";
1041
+ else if (heading.includes("in progress") || heading.includes("in-progress"))
1042
+ currentSection = "in-progress";
1043
+ else if (heading.includes("done") || heading.includes("completed"))
1044
+ currentSection = "done";
1045
+ else if (heading.includes("blocked")) currentSection = "blocked";
1046
+ else if (heading.includes("cancelled") || heading.includes("canceled"))
1047
+ currentSection = "cancelled";
1048
+ continue;
1049
+ }
1050
+ if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
1051
+ counts.total++;
1052
+ switch (currentSection) {
1053
+ case "pending":
1054
+ counts.pending++;
1055
+ break;
1056
+ case "in-progress":
1057
+ counts.inProgress++;
1058
+ break;
1059
+ case "done":
1060
+ counts.done++;
1061
+ break;
1062
+ case "blocked":
1063
+ counts.blocked++;
1064
+ break;
1065
+ case "cancelled":
1066
+ counts.cancelled++;
1067
+ break;
1068
+ default:
1069
+ if (trimmed.includes("[x]") || trimmed.includes("[X]")) {
1070
+ counts.done++;
1071
+ } else {
1072
+ counts.pending++;
1073
+ }
1074
+ break;
1075
+ }
1076
+ }
1077
+ }
1078
+ return counts;
1079
+ }
1080
+ function registerStatusCommand(program) {
1081
+ program.command("status").description("Show current project status").option("-c, --config <path>", "path to maestro.yaml", "./maestro.yaml").action(async (options) => {
1082
+ const todoPath = path5.resolve("todo.md");
1083
+ const stopFilePath = path5.resolve(".maestro/stop");
1084
+ const isRunning = !fs4.existsSync(stopFilePath) && fs4.existsSync(path5.resolve(".maestro"));
1085
+ console.log();
1086
+ console.log(chalk3.bold(" MAESTRO") + " - Project Status");
1087
+ console.log();
1088
+ console.log(
1089
+ ` Orchestrator: ${isRunning ? chalk3.green("possibly running") : chalk3.dim("stopped")}`
1090
+ );
1091
+ console.log();
1092
+ if (!fs4.existsSync(todoPath)) {
1093
+ console.log(chalk3.yellow(` ${figures3.warning} No todo.md found`));
1094
+ console.log(chalk3.dim(` Run ${chalk3.bold("maestro init")} to set up your project.`));
1095
+ console.log();
1096
+ return;
1097
+ }
1098
+ const content = fs4.readFileSync(todoPath, "utf-8");
1099
+ const counts = parseTodoFile(content);
1100
+ if (counts.total === 0) {
1101
+ console.log(chalk3.dim(" No tasks found in todo.md"));
1102
+ console.log();
1103
+ return;
1104
+ }
1105
+ const barWidth = 30;
1106
+ const doneRatio = counts.done / counts.total;
1107
+ const filledWidth = Math.round(doneRatio * barWidth);
1108
+ const bar = chalk3.green("\u2588".repeat(filledWidth)) + chalk3.dim("\u2591".repeat(barWidth - filledWidth));
1109
+ console.log(` Progress: ${bar} ${Math.round(doneRatio * 100)}%`);
1110
+ console.log();
1111
+ console.log(` ${chalk3.green(figures3.tick)} Done: ${counts.done}`);
1112
+ console.log(` ${chalk3.yellow(figures3.play)} In Progress: ${counts.inProgress}`);
1113
+ console.log(` ${chalk3.dim(figures3.circle)} Pending: ${counts.pending}`);
1114
+ if (counts.blocked > 0) {
1115
+ console.log(` ${chalk3.red(figures3.cross)} Blocked: ${counts.blocked}`);
1116
+ }
1117
+ if (counts.cancelled > 0) {
1118
+ console.log(` ${chalk3.dim(figures3.cross)} Cancelled: ${counts.cancelled}`);
1119
+ }
1120
+ console.log(` ${"\u2500".repeat(28)}`);
1121
+ console.log(` Total: ${counts.total}`);
1122
+ console.log();
1123
+ });
1124
+ }
1125
+
1126
+ // src/commands/stop.ts
1127
+ import * as fs5 from "fs";
1128
+ import * as path6 from "path";
1129
+ import chalk4 from "chalk";
1130
+ import figures4 from "figures";
1131
+ function registerStopCommand(program) {
1132
+ program.command("stop").description("Stop the running Maestro orchestrator").action(async () => {
1133
+ const maestroDir = path6.resolve(".maestro");
1134
+ const stopFilePath = path6.resolve(".maestro/stop");
1135
+ if (!fs5.existsSync(maestroDir)) {
1136
+ console.error(
1137
+ chalk4.red(
1138
+ `${figures4.cross} No .maestro directory found. Is this a Maestro project?`
1139
+ )
1140
+ );
1141
+ process.exit(1);
1142
+ }
1143
+ if (fs5.existsSync(stopFilePath)) {
1144
+ console.log(
1145
+ chalk4.yellow(`${figures4.warning} Stop signal already sent.`)
1146
+ );
1147
+ return;
1148
+ }
1149
+ fs5.writeFileSync(stopFilePath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
1150
+ console.log(
1151
+ chalk4.green(
1152
+ `${figures4.tick} Stop signal sent. The orchestrator will shut down gracefully.`
1153
+ )
1154
+ );
1155
+ });
1156
+ }
1157
+
1158
+ // src/index.ts
1159
+ function createProgram() {
1160
+ const program = new Command();
1161
+ program.name("maestro").description("Orchestrate multiple Claude Code agents working as a team").version("0.1.0");
1162
+ registerInitCommand(program);
1163
+ registerStartCommand(program);
1164
+ registerStatusCommand(program);
1165
+ registerStopCommand(program);
1166
+ return program;
1167
+ }
1168
+
1169
+ export {
1170
+ createProgram
1171
+ };
1172
+ //# sourceMappingURL=chunk-5ZDNPLKN.js.map