agentic-dev 0.2.17 → 0.2.19

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.
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import process from "node:process";
5
- import * as readline from "node:readline";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { DEFAULT_TEMPLATE_OWNER, ensureTargetDir, fetchTemplateRepos, finalizeRepositoryGit, installTemplateRepo, parseArgs, resolveGitHubOrchestration, resolveTemplateRepo, usage, } from "../lib/scaffold.js";
8
8
  const DEFAULT_TARGET_DIR = ".";
9
9
  const DEFAULT_AI_PROVIDERS = ["codex", "claude"];
10
- const DEFAULT_PROVIDER_PROFILES = ["codex-subscription", "claude-subscription"];
10
+ const DEFAULT_PROVIDER_PROFILES = ["codex-cli", "claude-cli"];
11
+ const DASHBOARD_TABS = ["Overview", "Agents", "Tasks", "Activity"];
11
12
  const GITHUB_AUTH_CHOICES = [
12
13
  {
13
14
  label: "gh-session",
@@ -43,12 +44,11 @@ const AI_PROVIDER_CHOICES = [
43
44
  },
44
45
  ];
45
46
  const PROVIDER_PROFILE_CHOICES = [
46
- { label: "codex-subscription", value: "codex-subscription", description: "Codex subscription/runtime access" },
47
- { label: "codex-api", value: "codex-api", description: "Codex API access" },
48
- { label: "claude-subscription", value: "claude-subscription", description: "Claude subscription/runtime access" },
49
- { label: "claude-api", value: "claude-api", description: "Claude API access" },
50
- { label: "openai-api", value: "openai-api", description: "OpenAI API access" },
51
- { label: "ollama-self-hosted", value: "ollama-self-hosted", description: "Local Ollama runtime" },
47
+ { label: "codex-cli", value: "codex-cli", description: "Run tasks through the local Codex CLI session." },
48
+ { label: "openai-api", value: "openai-api", description: "Call the OpenAI Responses API directly for coding tasks." },
49
+ { label: "claude-cli", value: "claude-cli", description: "Run tasks through the local Claude Code CLI session." },
50
+ { label: "anthropic-api", value: "anthropic-api", description: "Call the Anthropic Messages API directly for Claude tasks." },
51
+ { label: "ollama-self-hosted", value: "ollama-self-hosted", description: "Use the local Ollama server/runtime." },
52
52
  ];
53
53
  const GITHUB_PROJECT_MODE_CHOICES = [
54
54
  {
@@ -74,13 +74,6 @@ const CONFIRM_CHOICES = [
74
74
  description: "Stop without writing files",
75
75
  },
76
76
  ];
77
- function clearMenu(lines) {
78
- if (lines <= 0) {
79
- return;
80
- }
81
- readline.moveCursor(process.stdout, 0, -lines);
82
- readline.clearScreenDown(process.stdout);
83
- }
84
77
  function inferProjectName(targetDir) {
85
78
  const normalized = targetDir.trim() || DEFAULT_TARGET_DIR;
86
79
  const resolved = path.resolve(process.cwd(), normalized);
@@ -101,8 +94,42 @@ function uniqueStrings(values) {
101
94
  function normalizeProviders(providers) {
102
95
  return providers.length > 0 ? uniqueStrings(providers) : [...DEFAULT_AI_PROVIDERS];
103
96
  }
97
+ function normalizeProviderProfile(value) {
98
+ const normalized = value.trim().toLowerCase();
99
+ switch (normalized) {
100
+ case "codex-subscription":
101
+ case "codex-cli":
102
+ return "codex-cli";
103
+ case "codex-api":
104
+ case "openai-api":
105
+ return "openai-api";
106
+ case "claude-subscription":
107
+ case "claude-cli":
108
+ return "claude-cli";
109
+ case "claude-api":
110
+ case "anthropic-api":
111
+ return "anthropic-api";
112
+ case "ollama-self-hosted":
113
+ return "ollama-self-hosted";
114
+ default:
115
+ return "";
116
+ }
117
+ }
104
118
  function normalizeProviderProfiles(providerProfiles) {
105
- return providerProfiles.length > 0 ? uniqueStrings(providerProfiles) : [...DEFAULT_PROVIDER_PROFILES];
119
+ const normalized = providerProfiles
120
+ .map((profile) => normalizeProviderProfile(profile))
121
+ .filter(Boolean);
122
+ return normalized.length > 0 ? uniqueStrings(normalized) : [...DEFAULT_PROVIDER_PROFILES];
123
+ }
124
+ function normalizeGithubAuthMode(value) {
125
+ const normalized = value.trim().toLowerCase();
126
+ if (normalized === "public" || normalized === "gh-session") {
127
+ return "gh";
128
+ }
129
+ if (normalized === "gh" || normalized === "env" || normalized === "pat") {
130
+ return normalized;
131
+ }
132
+ return "gh";
106
133
  }
107
134
  function hydrateOptions(options) {
108
135
  const state = {
@@ -131,16 +158,6 @@ function hydrateOptions(options) {
131
158
  }
132
159
  return state;
133
160
  }
134
- function normalizeGithubAuthMode(value) {
135
- const normalized = value.trim().toLowerCase();
136
- if (normalized === "public" || normalized === "gh-session") {
137
- return "gh";
138
- }
139
- if (normalized === "gh" || normalized === "env" || normalized === "pat") {
140
- return normalized;
141
- }
142
- return "gh";
143
- }
144
161
  function applyRuntimeGitHubAuth(state) {
145
162
  if (state.githubAuthMode === "pat" && state.githubPat) {
146
163
  process.env.AGENTIC_GITHUB_TOKEN = state.githubPat;
@@ -179,21 +196,21 @@ function buildSteps(state, repos) {
179
196
  key: "projectName",
180
197
  type: "text",
181
198
  label: "Project name",
182
- description: "Written into scaffold metadata and Claude workspace nickname.",
199
+ description: "Written into scaffold metadata and workspace labels.",
183
200
  placeholder: inferProjectName(state.targetDir),
184
201
  },
185
202
  {
186
203
  key: "githubRepositoryInput",
187
204
  type: "text",
188
205
  label: "GitHub repository",
189
- description: "Use owner/name, a GitHub URL, or just a repo name to create/use the repository.",
206
+ description: "Use owner/name, a GitHub URL, or just a repo name to create or reuse the repository.",
190
207
  placeholder: state.projectName,
191
208
  },
192
209
  {
193
210
  key: "githubAuthMode",
194
211
  type: "single",
195
212
  label: "GitHub credential source",
196
- description: "Choose which GitHub credentials this run should use for repo/project orchestration. say828 template-* selection is always available.",
213
+ description: "Choose which credentials this run should use for repo/project orchestration. say828 template-* selection is always available.",
197
214
  choices: GITHUB_AUTH_CHOICES,
198
215
  },
199
216
  ];
@@ -210,7 +227,7 @@ function buildSteps(state, repos) {
210
227
  key: "githubProjectMode",
211
228
  type: "single",
212
229
  label: "GitHub project mode",
213
- description: "Choose whether the orchestration must reuse an existing GitHub Project or create one if needed.",
230
+ description: "Choose whether orchestration must reuse an existing GitHub Project or create one if needed.",
214
231
  choices: GITHUB_PROJECT_MODE_CHOICES,
215
232
  }, {
216
233
  key: "githubProjectTitle",
@@ -227,14 +244,14 @@ function buildSteps(state, repos) {
227
244
  }, {
228
245
  key: "providerProfiles",
229
246
  type: "multi",
230
- label: "AI runtime profiles",
231
- description: "Use Space to toggle provider/runtime profiles.",
247
+ label: "AI execution adapters",
248
+ description: "Toggle the runtime adapters this repo may use during orchestration.",
232
249
  choices: PROVIDER_PROFILE_CHOICES,
233
250
  }, {
234
251
  key: "aiProviders",
235
252
  type: "multi",
236
253
  label: "Workspace agent surfaces",
237
- description: "Use Space to toggle Codex/Claude workspace assets.",
254
+ description: "Toggle Codex, Claude, and Ollama metadata in the generated repo.",
238
255
  choices: AI_PROVIDER_CHOICES,
239
256
  });
240
257
  if (!state.force && directoryHasUserFiles(state.targetDir)) {
@@ -261,7 +278,7 @@ function buildSteps(state, repos) {
261
278
  key: "confirm",
262
279
  type: "confirm",
263
280
  label: "Review and run",
264
- description: "Confirm every choice before the CLI starts cloning or installing.",
281
+ description: "All selections are collected first. Commands run only after final confirmation.",
265
282
  choices: CONFIRM_CHOICES,
266
283
  });
267
284
  return steps;
@@ -284,29 +301,67 @@ function stepValue(state, step) {
284
301
  }
285
302
  return state[step.key];
286
303
  }
287
- function renderHeader(step, index, total) {
288
- return [`Agentic Dev Setup ${index + 1}/${total}`, step.label, step.description, ""];
304
+ function renderHeader(title, subtitle, modeHint) {
305
+ return [
306
+ title,
307
+ subtitle,
308
+ "",
309
+ modeHint,
310
+ "",
311
+ ];
312
+ }
313
+ function executionPreview(state, selectedRepo) {
314
+ const lines = [
315
+ `Project directory: ${path.resolve(process.cwd(), state.targetDir)}`,
316
+ `Project name: ${state.projectName}`,
317
+ `GitHub repository: ${state.githubRepositoryInput}`,
318
+ `GitHub project mode: ${state.githubProjectMode}`,
319
+ `GitHub project title: ${state.githubProjectTitle}`,
320
+ `GitHub credential source: ${state.githubAuthMode}`,
321
+ `Template repo: ${selectedRepo?.name || state.template}`,
322
+ `Workspace surfaces: ${state.aiProviders.join(", ")}`,
323
+ `Execution adapters: ${state.providerProfiles.join(", ")}`,
324
+ `Allow non-empty directory: ${state.force ? "yes" : "no"}`,
325
+ ];
326
+ if (state.githubAuthMode === "pat") {
327
+ lines.push(`GitHub PAT supplied: ${state.githubPat ? "yes" : "no"}`);
328
+ }
329
+ lines.push("");
330
+ lines.push("Planned execution:");
331
+ lines.push(` 1. Ensure GitHub repo ${state.githubRepositoryInput}`);
332
+ lines.push(` 2. Ensure GitHub Project ${state.githubProjectTitle}`);
333
+ lines.push(` 3. Clone ${selectedRepo?.name || state.template}`);
334
+ lines.push(" 4. Install shared sub-agent assets and orchestration workflow");
335
+ lines.push(" 5. Copy .env.example to .env if needed");
336
+ lines.push(" 6. Run pnpm install");
337
+ lines.push(" 7. If the selected template has a default frontend target, install Playwright Chromium");
338
+ lines.push(" 8. If the selected template has a default frontend target and bootstrap is not skipped, run frontend parity bootstrap");
339
+ return lines;
289
340
  }
290
341
  function renderTextStep(step, state, buffers, index, total) {
291
- const lines = renderHeader(step, index, total);
292
342
  if (!buffers.has(step.key)) {
293
343
  buffers.set(step.key, String(stepValue(state, step) || ""));
294
344
  }
295
345
  const rawValue = buffers.get(step.key) || "";
296
346
  const displayValue = step.type === "password" ? "*".repeat(rawValue.length) : rawValue || step.placeholder || "";
297
- lines.push(`Value: ${displayValue}`);
298
- lines.push("");
299
- lines.push("Controls: type text, Backspace to edit, Enter or → to continue, ← to go back.");
300
- process.stdout.write(`${lines.join("\n")}\n`);
301
- return lines.length;
347
+ return [
348
+ ...renderHeader(`Agentic Dev Init ${index + 1}/${total}`, step.label, step.description),
349
+ `Value: ${displayValue}`,
350
+ "",
351
+ "Controls",
352
+ " ← previous screen",
353
+ " → or Enter next screen",
354
+ " Backspace edit",
355
+ " Ctrl+C cancel",
356
+ ];
302
357
  }
303
358
  function renderSingleChoiceStep(step, state, cursors, index, total) {
304
- const lines = renderHeader(step, index, total);
305
359
  if (!cursors.has(step.key)) {
306
360
  const currentValue = stepValue(state, step);
307
361
  const currentIndex = Math.max(0, step.choices.findIndex((choice) => choice.value === currentValue));
308
362
  cursors.set(step.key, currentIndex >= 0 ? currentIndex : 0);
309
363
  }
364
+ const lines = renderHeader(`Agentic Dev Init ${index + 1}/${total}`, step.label, step.description);
310
365
  const cursor = cursors.get(step.key) || 0;
311
366
  step.choices.forEach((choice, choiceIndex) => {
312
367
  const pointer = choiceIndex === cursor ? ">" : " ";
@@ -314,15 +369,18 @@ function renderSingleChoiceStep(step, state, cursors, index, total) {
314
369
  lines.push(`${pointer} ${choice.label}${suffix}`);
315
370
  });
316
371
  lines.push("");
317
- lines.push("Controls: ↑/↓ to choose, Enter or → to continue, ← to go back.");
318
- process.stdout.write(`${lines.join("\n")}\n`);
319
- return lines.length;
372
+ lines.push("Controls");
373
+ lines.push(" ↑/↓ choose");
374
+ lines.push(" ← previous screen");
375
+ lines.push(" → or Enter next screen");
376
+ lines.push(" Ctrl+C cancel");
377
+ return lines;
320
378
  }
321
379
  function renderMultiChoiceStep(step, state, cursors, index, total) {
322
- const lines = renderHeader(step, index, total);
323
380
  if (!cursors.has(step.key)) {
324
381
  cursors.set(step.key, 0);
325
382
  }
383
+ const lines = renderHeader(`Agentic Dev Init ${index + 1}/${total}`, step.label, step.description);
326
384
  const selected = new Set(uniqueStrings(stepValue(state, step) || []));
327
385
  const cursor = cursors.get(step.key) || 0;
328
386
  step.choices.forEach((choice, choiceIndex) => {
@@ -332,39 +390,16 @@ function renderMultiChoiceStep(step, state, cursors, index, total) {
332
390
  lines.push(`${pointer} ${checked} ${choice.label}${suffix}`);
333
391
  });
334
392
  lines.push("");
335
- lines.push("Controls: ↑/↓ to move, Space to toggle, Enter or → to continue, ← to go back.");
336
- process.stdout.write(`${lines.join("\n")}\n`);
337
- return lines.length;
338
- }
339
- function executionPreview(state, selectedRepo) {
340
- const lines = [
341
- `Project directory: ${path.resolve(process.cwd(), state.targetDir)}`,
342
- `Project name: ${state.projectName}`,
343
- `GitHub repository: ${state.githubRepositoryInput}`,
344
- `GitHub project mode: ${state.githubProjectMode}`,
345
- `GitHub project title: ${state.githubProjectTitle}`,
346
- `GitHub credential source: ${state.githubAuthMode}`,
347
- `Template repo: ${selectedRepo?.name || state.template}`,
348
- `AI providers: ${state.aiProviders.join(", ")}`,
349
- `Provider profiles: ${state.providerProfiles.join(", ")}`,
350
- `Allow non-empty directory: ${state.force ? "yes" : "no"}`,
351
- ];
352
- if (state.githubAuthMode === "pat") {
353
- lines.push(`GitHub PAT supplied: ${state.githubPat ? "yes" : "no"}`);
354
- }
355
- lines.push("Run plan:");
356
- lines.push(` 1. Ensure GitHub repo ${state.githubRepositoryInput}`);
357
- lines.push(` 2. Ensure GitHub Project ${state.githubProjectTitle}`);
358
- lines.push(` 3. Clone ${selectedRepo?.name || state.template}`);
359
- lines.push(" 4. Install shared sub-agent assets and orchestration workflow");
360
- lines.push(" 5. Copy .env.example to .env if needed");
361
- lines.push(" 6. Run pnpm install");
362
- lines.push(" 7. If the selected template has a default frontend target, install Playwright Chromium");
363
- lines.push(" 8. If the selected template has a default frontend target and bootstrap is not skipped, run frontend parity bootstrap");
393
+ lines.push("Controls");
394
+ lines.push(" ↑/↓ choose");
395
+ lines.push(" Space toggle");
396
+ lines.push(" ← previous screen");
397
+ lines.push(" → or Enter next screen");
398
+ lines.push(" Ctrl+C cancel");
364
399
  return lines;
365
400
  }
366
401
  function renderConfirmStep(step, state, repos, cursors, index, total) {
367
- const lines = renderHeader(step, index, total);
402
+ const lines = renderHeader(`Agentic Dev Init ${index + 1}/${total}`, step.label, step.description);
368
403
  const selectedRepo = resolveTemplateRepo(state.template, repos);
369
404
  executionPreview(state, selectedRepo).forEach((line) => lines.push(line));
370
405
  lines.push("");
@@ -378,48 +413,73 @@ function renderConfirmStep(step, state, repos, cursors, index, total) {
378
413
  lines.push(`${pointer} ${choice.label}${suffix}`);
379
414
  });
380
415
  lines.push("");
381
- lines.push("Controls: ↑/↓ to choose, Enter or → to confirm, ← to go back.");
382
- process.stdout.write(`${lines.join("\n")}\n`);
383
- return lines.length;
416
+ lines.push("Controls");
417
+ lines.push(" ↑/↓ choose");
418
+ lines.push(" ← previous screen");
419
+ lines.push(" → or Enter confirm");
420
+ lines.push(" Ctrl+C cancel");
421
+ return lines;
422
+ }
423
+ function truncateLine(line, width) {
424
+ if (width <= 0) {
425
+ return "";
426
+ }
427
+ if (line.length <= width) {
428
+ return line;
429
+ }
430
+ if (width <= 1) {
431
+ return line.slice(0, width);
432
+ }
433
+ return `${line.slice(0, width - 1)}…`;
384
434
  }
385
- async function runInteractiveSession(render, onInput) {
435
+ function renderFullScreen(lines) {
436
+ const width = process.stdout.columns || 120;
437
+ const height = process.stdout.rows || 40;
438
+ const clipped = lines.map((line) => truncateLine(line, width)).slice(0, height);
439
+ while (clipped.length < height) {
440
+ clipped.push("");
441
+ }
442
+ process.stdout.write("\u001b[H\u001b[2J");
443
+ process.stdout.write(clipped.join("\n"));
444
+ }
445
+ async function runFullScreenSession(render, onInput) {
386
446
  const stdin = process.stdin;
387
447
  const stdout = process.stdout;
388
448
  const previousRawMode = typeof stdin.setRawMode === "function" ? stdin.isRaw : undefined;
449
+ const rerender = () => {
450
+ renderFullScreen(render());
451
+ };
452
+ stdout.write("\u001b[?1049h\u001b[?25l");
389
453
  if (typeof stdin.setRawMode === "function") {
390
454
  stdin.setRawMode(true);
391
455
  }
392
456
  stdin.resume();
393
457
  stdin.setEncoding("utf8");
394
458
  return new Promise((resolve, reject) => {
395
- let renderedLines = render();
396
459
  const cleanup = () => {
397
460
  stdin.removeListener("data", handleData);
461
+ stdout.removeListener("resize", rerender);
398
462
  if (typeof stdin.setRawMode === "function") {
399
463
  stdin.setRawMode(Boolean(previousRawMode));
400
464
  }
401
- stdout.write("\n");
402
- };
403
- const rerender = () => {
404
- clearMenu(renderedLines);
405
- renderedLines = render();
465
+ stdout.write("\u001b[?25h\u001b[?1049l");
406
466
  };
407
467
  const handleData = (chunk) => {
408
468
  try {
409
469
  const result = onInput(chunk, rerender);
410
470
  if (result !== undefined) {
411
- clearMenu(renderedLines);
412
471
  cleanup();
413
472
  resolve(result);
414
473
  }
415
474
  }
416
475
  catch (error) {
417
- clearMenu(renderedLines);
418
476
  cleanup();
419
477
  reject(error);
420
478
  }
421
479
  };
480
+ stdout.on("resize", rerender);
422
481
  stdin.on("data", handleData);
482
+ rerender();
423
483
  });
424
484
  }
425
485
  function validateStepValue(step, state, value) {
@@ -464,7 +524,7 @@ function validateStepValue(step, state, value) {
464
524
  if (step.key === "providerProfiles") {
465
525
  const profiles = normalizeProviderProfiles(Array.isArray(value) ? value : []);
466
526
  if (profiles.length === 0) {
467
- throw new Error("Select at least one provider profile.");
527
+ throw new Error("Select at least one execution adapter.");
468
528
  }
469
529
  return profiles;
470
530
  }
@@ -489,13 +549,12 @@ async function runWizard(initialState, repos) {
489
549
  const buffers = new Map();
490
550
  const cursors = new Map();
491
551
  let stepIndex = 0;
492
- return runInteractiveSession(() => {
552
+ return runFullScreenSession(() => {
493
553
  const steps = buildSteps(state, repos);
494
554
  if (stepIndex >= steps.length) {
495
555
  stepIndex = steps.length - 1;
496
556
  }
497
557
  const step = steps[stepIndex];
498
- process.stdout.write("\n");
499
558
  if (step.type === "text" || step.type === "password") {
500
559
  return renderTextStep(step, state, buffers, stepIndex, steps.length);
501
560
  }
@@ -684,7 +743,7 @@ async function promptLinearly(initialState, repos) {
684
743
  const projectTitleAnswer = await rl.question(`GitHub project title [${state.githubProjectTitle || `${state.projectName} Delivery`}]: `);
685
744
  state.githubProjectTitle = projectTitleAnswer.trim() || state.githubProjectTitle || `${state.projectName} Delivery`;
686
745
  state.template = await promptForChoice(rl, `Template repo from ${state.owner}`, buildTemplateChoices(repos));
687
- state.providerProfiles = await promptForMultiChoice(rl, "AI runtime profiles", PROVIDER_PROFILE_CHOICES, state.providerProfiles);
746
+ state.providerProfiles = await promptForMultiChoice(rl, "AI execution adapters", PROVIDER_PROFILE_CHOICES, state.providerProfiles);
688
747
  state.aiProviders = await promptForMultiChoice(rl, "AI providers", AI_PROVIDER_CHOICES, state.aiProviders);
689
748
  if (!state.force && directoryHasUserFiles(state.targetDir)) {
690
749
  state.force = await promptForChoice(rl, "Target directory is not empty", [
@@ -758,6 +817,356 @@ function validateNonInteractiveState(state) {
758
817
  }
759
818
  return state;
760
819
  }
820
+ function readJson(filePath) {
821
+ if (!fs.existsSync(filePath)) {
822
+ return null;
823
+ }
824
+ try {
825
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
826
+ }
827
+ catch {
828
+ return null;
829
+ }
830
+ }
831
+ function readTaskList(snapshotRoot) {
832
+ const taskIndex = readJson(path.join(snapshotRoot, ".agentic-dev/generated/task-index.json"));
833
+ const indexed = Array.isArray(taskIndex?.tasks) ? taskIndex?.tasks : null;
834
+ if (indexed) {
835
+ return indexed.map((task) => ({
836
+ id: String(task.id || ""),
837
+ title: String(task.title || ""),
838
+ status: String(task.status || "open"),
839
+ source: String(task.source || ""),
840
+ issue_number: task.issue_number ?? null,
841
+ issue_url: task.issue_url ?? null,
842
+ }));
843
+ }
844
+ const taskIr = readJson(path.join(snapshotRoot, ".agentic-dev/generated/task-ir.json"));
845
+ const irTasks = Array.isArray(taskIr?.tasks) ? taskIr?.tasks : [];
846
+ return irTasks.map((task) => ({
847
+ id: String(task.id || ""),
848
+ title: String(task.title || ""),
849
+ status: String(task.status || "open"),
850
+ source: String(task.source || ""),
851
+ issue_number: null,
852
+ issue_url: null,
853
+ }));
854
+ }
855
+ function loadDashboardSnapshot(cwd) {
856
+ const setup = readJson(path.join(cwd, ".agentic-dev/setup.json"));
857
+ const orchestration = readJson(path.join(cwd, ".agentic-dev/orchestration.json"));
858
+ const queue = readJson(path.join(cwd, ".agentic-dev/generated/agent-queue.json"));
859
+ const dispatch = readJson(path.join(cwd, ".agentic-dev/generated/dispatch-plan.json"));
860
+ const journal = readJson(path.join(cwd, ".agentic-dev/generated/execution-journal.json"));
861
+ const closed = readJson(path.join(cwd, ".agentic-dev/generated/closed-tasks.json"));
862
+ const tasks = readTaskList(cwd);
863
+ const agents = Array.isArray(orchestration?.specialized_agents) ? orchestration?.specialized_agents : [];
864
+ const recentExecutions = Array.isArray(journal?.executions) ? journal?.executions : [];
865
+ return {
866
+ installed: Boolean(setup && orchestration),
867
+ cwd,
868
+ projectName: String(setup?.project_name || orchestration?.project_name || path.basename(cwd)),
869
+ templateRepo: String(setup?.template_repo || ""),
870
+ repositorySlug: String(orchestration?.github?.repository
871
+ ? (orchestration?.github).repository.slug || ""
872
+ : ""),
873
+ projectTitle: String(orchestration?.github?.project
874
+ ? (orchestration?.github).project.title || ""
875
+ : ""),
876
+ agentSurfaces: Array.isArray(setup?.ai_providers) ? setup?.ai_providers : [],
877
+ providers: Array.isArray(orchestration?.providers) ? orchestration?.providers : [],
878
+ agents: agents.map((agent) => ({
879
+ id: String(agent.id || ""),
880
+ description: String(agent.description || ""),
881
+ })),
882
+ tasks,
883
+ queueCount: Array.isArray(queue?.queue) ? queue.queue.length : 0,
884
+ dispatchCount: Array.isArray(dispatch?.dispatch) ? dispatch.dispatch.length : 0,
885
+ executionCount: recentExecutions.length,
886
+ closedCount: Array.isArray(closed?.closed) ? closed.closed.length : 0,
887
+ recentExecutions: recentExecutions
888
+ .map((item) => ({
889
+ task_id: String(item.task_id || ""),
890
+ title: String(item.title || ""),
891
+ provider: String(item.provider || ""),
892
+ adapter: String(item.adapter || ""),
893
+ execution_state: String(item.execution_state || ""),
894
+ started_at: item.started_at || "",
895
+ completed_at: item.completed_at || "",
896
+ issue_number: item.issue_number ?? null,
897
+ stdout: String(item.stdout || ""),
898
+ stderr: String(item.stderr || ""),
899
+ }))
900
+ .reverse(),
901
+ };
902
+ }
903
+ function selectedTab(tabIndex) {
904
+ return DASHBOARD_TABS[Math.max(0, Math.min(DASHBOARD_TABS.length - 1, tabIndex))];
905
+ }
906
+ function renderDashboardTabs(tabIndex) {
907
+ return DASHBOARD_TABS.map((tab, index) => (index === tabIndex ? `[${tab}]` : ` ${tab} `)).join(" ");
908
+ }
909
+ function dashboardList(state) {
910
+ const tab = selectedTab(state.tabIndex);
911
+ if (tab === "Agents") {
912
+ return state.snapshot.agents.map((agent) => `${agent.id} - ${agent.description}`);
913
+ }
914
+ if (tab === "Tasks") {
915
+ return state.snapshot.tasks.map((task) => {
916
+ const issue = task.issue_number ? ` #${task.issue_number}` : "";
917
+ return `${task.status.toUpperCase()} ${task.id}${issue} - ${task.title}`;
918
+ });
919
+ }
920
+ if (tab === "Activity") {
921
+ return state.snapshot.recentExecutions.map((execution) => {
922
+ return `${execution.execution_state.toUpperCase()} ${execution.task_id} via ${execution.adapter || execution.provider}`;
923
+ });
924
+ }
925
+ return [];
926
+ }
927
+ function clampDashboardCursor(state) {
928
+ const items = dashboardList(state);
929
+ if (items.length === 0) {
930
+ state.listCursor = 0;
931
+ return;
932
+ }
933
+ state.listCursor = Math.max(0, Math.min(state.listCursor, items.length - 1));
934
+ }
935
+ function renderOverview(snapshot) {
936
+ const openTasks = snapshot.tasks.filter((task) => task.status !== "closed").length;
937
+ const closedTasks = snapshot.tasks.filter((task) => task.status === "closed").length;
938
+ return [
939
+ `Project: ${snapshot.projectName}`,
940
+ `Template: ${snapshot.templateRepo || "(not recorded)"}`,
941
+ `Repository: ${snapshot.repositorySlug || "(not bound)"}`,
942
+ `GitHub Project: ${snapshot.projectTitle || "(not bound)"}`,
943
+ `Workspace surfaces: ${snapshot.agentSurfaces.join(", ") || "(none)"}`,
944
+ `Execution adapters: ${snapshot.providers.join(", ") || "(none)"}`,
945
+ "",
946
+ `Tasks: ${snapshot.tasks.length} total | ${openTasks} open | ${closedTasks} closed`,
947
+ `Queue: ${snapshot.queueCount} planned agent items`,
948
+ `Dispatch: ${snapshot.dispatchCount} dispatch items`,
949
+ `Executions: ${snapshot.executionCount} records`,
950
+ `Closed by close pass: ${snapshot.closedCount}`,
951
+ ];
952
+ }
953
+ function renderAgents(state) {
954
+ const items = dashboardList(state);
955
+ if (items.length === 0) {
956
+ return ["No specialized agents recorded in .agentic-dev/orchestration.json."];
957
+ }
958
+ return items.map((item, index) => `${index === state.listCursor ? ">" : " "} ${item}`);
959
+ }
960
+ function renderTasks(state) {
961
+ if (state.snapshot.tasks.length === 0) {
962
+ return ["No task IR or task index found yet."];
963
+ }
964
+ const selected = state.snapshot.tasks[state.listCursor];
965
+ const lines = state.snapshot.tasks.slice(0, 12).map((task, index) => `${index === state.listCursor ? ">" : " "} ${task.status.toUpperCase()} ${task.id} - ${task.title}`);
966
+ if (selected) {
967
+ lines.push("");
968
+ lines.push(`Selected source: ${selected.source || "(unknown)"}`);
969
+ lines.push(`Selected issue: ${selected.issue_number ? `#${selected.issue_number}` : "(not mirrored yet)"}`);
970
+ }
971
+ return lines;
972
+ }
973
+ function renderActivity(state) {
974
+ if (state.snapshot.recentExecutions.length === 0) {
975
+ return ["No execution journal found yet."];
976
+ }
977
+ const selected = state.snapshot.recentExecutions[state.listCursor];
978
+ const lines = state.snapshot.recentExecutions
979
+ .slice(0, 12)
980
+ .map((execution, index) => `${index === state.listCursor ? ">" : " "} ${execution.execution_state.toUpperCase()} ${execution.task_id} via ${execution.adapter || execution.provider}`);
981
+ if (selected) {
982
+ lines.push("");
983
+ lines.push(`Task: ${selected.title}`);
984
+ lines.push(`Started: ${selected.started_at || "(unknown)"}`);
985
+ lines.push(`Completed: ${selected.completed_at || "(unknown)"}`);
986
+ if (selected.stderr) {
987
+ lines.push(`stderr: ${selected.stderr}`);
988
+ }
989
+ else if (selected.stdout) {
990
+ lines.push(`stdout: ${selected.stdout.split(/\r?\n/)[0] || "(empty)"}`);
991
+ }
992
+ }
993
+ return lines;
994
+ }
995
+ function renderDashboard(state) {
996
+ const snapshot = state.snapshot;
997
+ const lines = renderHeader("Agentic Dev Dashboard", snapshot.installed
998
+ ? "Installed orchestration repo detected."
999
+ : "No .agentic-dev install found in the current working directory.", `cwd: ${snapshot.cwd}`);
1000
+ lines.push(renderDashboardTabs(state.tabIndex));
1001
+ lines.push("");
1002
+ if (!snapshot.installed) {
1003
+ lines.push("This command is the management TUI.");
1004
+ lines.push("Initialize a repo first with:");
1005
+ lines.push(" agentic-dev init");
1006
+ lines.push("");
1007
+ lines.push("Controls");
1008
+ lines.push(" q quit");
1009
+ lines.push(" r refresh");
1010
+ return lines;
1011
+ }
1012
+ const tab = selectedTab(state.tabIndex);
1013
+ if (tab === "Overview") {
1014
+ lines.push(...renderOverview(snapshot));
1015
+ }
1016
+ else if (tab === "Agents") {
1017
+ lines.push(...renderAgents(state));
1018
+ }
1019
+ else if (tab === "Tasks") {
1020
+ lines.push(...renderTasks(state));
1021
+ }
1022
+ else {
1023
+ lines.push(...renderActivity(state));
1024
+ }
1025
+ lines.push("");
1026
+ lines.push(`Status: ${state.statusMessage}`);
1027
+ lines.push("");
1028
+ lines.push("Controls");
1029
+ lines.push(" ←/→ switch tabs");
1030
+ lines.push(" ↑/↓ move selection");
1031
+ lines.push(" r refresh");
1032
+ lines.push(" i build IR");
1033
+ lines.push(" t sync GitHub tasks");
1034
+ lines.push(" p plan queue");
1035
+ lines.push(" d build dispatch");
1036
+ lines.push(" e execute dispatch");
1037
+ lines.push(" c close completed tasks");
1038
+ lines.push(" q quit");
1039
+ return lines;
1040
+ }
1041
+ function runDashboardAction(cwd, key) {
1042
+ const scriptMap = {
1043
+ i: ".agentic-dev/runtime/sdd_to_ir.ts",
1044
+ t: ".agentic-dev/runtime/sync_project_tasks.ts",
1045
+ p: ".agentic-dev/runtime/run_multi_agent_queue.ts",
1046
+ d: ".agentic-dev/runtime/dispatch_agents.ts",
1047
+ e: ".agentic-dev/runtime/dispatch_execute.ts",
1048
+ c: ".agentic-dev/runtime/close_completed_tasks.ts",
1049
+ };
1050
+ const script = scriptMap[key];
1051
+ if (!script) {
1052
+ return "No action.";
1053
+ }
1054
+ if (!fs.existsSync(path.join(cwd, script))) {
1055
+ throw new Error(`Missing runtime script: ${script}`);
1056
+ }
1057
+ const result = spawnSync("npx", ["--yes", "tsx", script], {
1058
+ cwd,
1059
+ encoding: "utf-8",
1060
+ maxBuffer: 1024 * 1024 * 20,
1061
+ });
1062
+ if (result.status !== 0) {
1063
+ throw new Error((result.stderr || result.stdout || `Failed to run ${script}`).trim());
1064
+ }
1065
+ return (result.stdout || `${script} ok`).trim().split(/\r?\n/).join(" | ");
1066
+ }
1067
+ async function runDashboard() {
1068
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1069
+ throw new Error("Dashboard mode requires a TTY. Use `agentic-dev init` for non-interactive setup.");
1070
+ }
1071
+ const state = {
1072
+ tabIndex: 0,
1073
+ listCursor: 0,
1074
+ snapshot: loadDashboardSnapshot(process.cwd()),
1075
+ statusMessage: "Ready.",
1076
+ };
1077
+ clampDashboardCursor(state);
1078
+ await runFullScreenSession(() => renderDashboard(state), (chunk, rerender) => {
1079
+ if (chunk === "\u0003" || chunk.toLowerCase() === "q") {
1080
+ return true;
1081
+ }
1082
+ if (chunk === "\u001b[C") {
1083
+ state.tabIndex = (state.tabIndex + 1) % DASHBOARD_TABS.length;
1084
+ clampDashboardCursor(state);
1085
+ rerender();
1086
+ return undefined;
1087
+ }
1088
+ if (chunk === "\u001b[D") {
1089
+ state.tabIndex = (state.tabIndex - 1 + DASHBOARD_TABS.length) % DASHBOARD_TABS.length;
1090
+ clampDashboardCursor(state);
1091
+ rerender();
1092
+ return undefined;
1093
+ }
1094
+ if (chunk === "\u001b[A") {
1095
+ state.listCursor = Math.max(0, state.listCursor - 1);
1096
+ rerender();
1097
+ return undefined;
1098
+ }
1099
+ if (chunk === "\u001b[B") {
1100
+ state.listCursor += 1;
1101
+ clampDashboardCursor(state);
1102
+ rerender();
1103
+ return undefined;
1104
+ }
1105
+ const lower = chunk.toLowerCase();
1106
+ if (lower === "r") {
1107
+ state.snapshot = loadDashboardSnapshot(process.cwd());
1108
+ clampDashboardCursor(state);
1109
+ state.statusMessage = "Refreshed snapshot.";
1110
+ rerender();
1111
+ return undefined;
1112
+ }
1113
+ if ("itpdec".includes(lower)) {
1114
+ if (!state.snapshot.installed) {
1115
+ state.statusMessage = "Initialize the repo first with `agentic-dev init`.";
1116
+ rerender();
1117
+ return undefined;
1118
+ }
1119
+ try {
1120
+ const actionOutput = runDashboardAction(process.cwd(), lower);
1121
+ state.snapshot = loadDashboardSnapshot(process.cwd());
1122
+ clampDashboardCursor(state);
1123
+ state.statusMessage = actionOutput || "Action completed.";
1124
+ }
1125
+ catch (error) {
1126
+ state.statusMessage = error instanceof Error ? error.message : String(error);
1127
+ }
1128
+ rerender();
1129
+ return undefined;
1130
+ }
1131
+ return undefined;
1132
+ });
1133
+ }
1134
+ async function runInitCommand(options) {
1135
+ const state = hydrateOptions(options);
1136
+ applyRuntimeGitHubAuth(state);
1137
+ const repos = await fetchTemplateRepos({ owner: state.owner });
1138
+ if (repos.length === 0) {
1139
+ throw new Error(`No public template-* repos found for ${state.owner}.`);
1140
+ }
1141
+ const resolvedState = options.yes
1142
+ ? validateNonInteractiveState(sanitizeStateForInstall(state))
1143
+ : process.stdin.isTTY && process.stdout.isTTY
1144
+ ? await runWizard(state, repos)
1145
+ : await promptLinearly(state, repos);
1146
+ applyRuntimeGitHubAuth(resolvedState);
1147
+ const orchestration = await resolveGitHubOrchestration(resolvedState);
1148
+ const selectedRepo = resolveTemplateRepo(resolvedState.template, repos);
1149
+ if (!selectedRepo) {
1150
+ throw new Error(`Template repo not found: ${resolvedState.template}`);
1151
+ }
1152
+ const destinationRoot = ensureTargetDir(resolvedState.targetDir, {
1153
+ force: resolvedState.force,
1154
+ });
1155
+ const result = installTemplateRepo({
1156
+ destinationRoot: path.resolve(destinationRoot),
1157
+ templateRepo: selectedRepo,
1158
+ setupSelections: {
1159
+ ...resolvedState,
1160
+ ...orchestration,
1161
+ },
1162
+ skipBootstrap: resolvedState.skipBootstrap,
1163
+ });
1164
+ finalizeRepositoryGit(path.resolve(destinationRoot), {
1165
+ ...resolvedState,
1166
+ ...orchestration,
1167
+ });
1168
+ printSuccess(result);
1169
+ }
761
1170
  async function main() {
762
1171
  let options;
763
1172
  try {
@@ -774,10 +1183,11 @@ async function main() {
774
1183
  console.log(usage());
775
1184
  return;
776
1185
  }
777
- if (options.command !== "init") {
1186
+ let command = options.command;
1187
+ if (!["dashboard", "init"].includes(command)) {
778
1188
  if (!options.targetDir) {
779
1189
  options.targetDir = options.command;
780
- options.command = "init";
1190
+ command = "init";
781
1191
  }
782
1192
  else {
783
1193
  console.error(`Unsupported command: ${options.command}`);
@@ -788,40 +1198,11 @@ async function main() {
788
1198
  }
789
1199
  }
790
1200
  try {
791
- const state = hydrateOptions(options);
792
- applyRuntimeGitHubAuth(state);
793
- const repos = await fetchTemplateRepos({ owner: state.owner });
794
- if (repos.length === 0) {
795
- throw new Error(`No public template-* repos found for ${state.owner}.`);
796
- }
797
- const resolvedState = options.yes
798
- ? validateNonInteractiveState(sanitizeStateForInstall(state))
799
- : process.stdin.isTTY && process.stdout.isTTY
800
- ? await runWizard(state, repos)
801
- : await promptLinearly(state, repos);
802
- applyRuntimeGitHubAuth(resolvedState);
803
- const orchestration = await resolveGitHubOrchestration(resolvedState);
804
- const selectedRepo = resolveTemplateRepo(resolvedState.template, repos);
805
- if (!selectedRepo) {
806
- throw new Error(`Template repo not found: ${resolvedState.template}`);
807
- }
808
- const destinationRoot = ensureTargetDir(resolvedState.targetDir, {
809
- force: resolvedState.force,
810
- });
811
- const result = installTemplateRepo({
812
- destinationRoot: path.resolve(destinationRoot),
813
- templateRepo: selectedRepo,
814
- setupSelections: {
815
- ...resolvedState,
816
- ...orchestration,
817
- },
818
- skipBootstrap: resolvedState.skipBootstrap,
819
- });
820
- finalizeRepositoryGit(path.resolve(destinationRoot), {
821
- ...resolvedState,
822
- ...orchestration,
823
- });
824
- printSuccess(result);
1201
+ if (command === "dashboard") {
1202
+ await runDashboard();
1203
+ return;
1204
+ }
1205
+ await runInitCommand(options);
825
1206
  }
826
1207
  catch (error) {
827
1208
  console.error(error instanceof Error ? error.message : String(error));