@bastani/atomic 0.5.3-1 → 0.5.4-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +110 -11
  2. package/dist/{chunk-mn870nrv.js → chunk-xkxndz5g.js} +213 -154
  3. package/dist/sdk/components/workflow-picker-panel.d.ts +120 -0
  4. package/dist/sdk/define-workflow.d.ts +1 -1
  5. package/dist/sdk/index.js +1 -1
  6. package/dist/sdk/runtime/discovery.d.ts +57 -3
  7. package/dist/sdk/runtime/executor.d.ts +15 -2
  8. package/dist/sdk/runtime/tmux.d.ts +9 -0
  9. package/dist/sdk/types.d.ts +63 -4
  10. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +61 -0
  11. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +48 -0
  12. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +25 -0
  13. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +91 -0
  14. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +56 -0
  15. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +48 -0
  16. package/dist/sdk/workflows/builtin/ralph/claude/index.js +6 -5
  17. package/dist/sdk/workflows/builtin/ralph/copilot/index.js +6 -5
  18. package/dist/sdk/workflows/builtin/ralph/opencode/index.js +6 -5
  19. package/dist/sdk/workflows/index.d.ts +4 -4
  20. package/dist/sdk/workflows/index.js +7 -1
  21. package/package.json +1 -1
  22. package/src/cli.ts +25 -3
  23. package/src/commands/cli/chat/index.ts +5 -5
  24. package/src/commands/cli/init/index.ts +79 -77
  25. package/src/commands/cli/workflow-command.test.ts +757 -0
  26. package/src/commands/cli/workflow.test.ts +310 -0
  27. package/src/commands/cli/workflow.ts +445 -105
  28. package/src/sdk/components/workflow-picker-panel.tsx +1462 -0
  29. package/src/sdk/define-workflow.test.ts +101 -0
  30. package/src/sdk/define-workflow.ts +62 -2
  31. package/src/sdk/runtime/discovery.ts +111 -8
  32. package/src/sdk/runtime/executor.ts +89 -32
  33. package/src/sdk/runtime/tmux.conf +55 -0
  34. package/src/sdk/runtime/tmux.ts +34 -10
  35. package/src/sdk/types.ts +67 -4
  36. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +294 -0
  37. package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +276 -0
  38. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +38 -0
  39. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +816 -0
  40. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +334 -0
  41. package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +284 -0
  42. package/src/sdk/workflows/builtin/ralph/claude/index.ts +8 -4
  43. package/src/sdk/workflows/builtin/ralph/copilot/index.ts +10 -4
  44. package/src/sdk/workflows/builtin/ralph/opencode/index.ts +8 -4
  45. package/src/sdk/workflows/index.ts +9 -1
  46. package/src/services/system/auto-sync.ts +1 -1
  47. package/src/services/system/install-ui.ts +109 -39
  48. package/src/theme/colors.ts +65 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * deep-research-codebase / opencode
3
+ *
4
+ * OpenCode replica of the Claude deep-research-codebase workflow. The Claude
5
+ * version dispatches specialist sub-agents (codebase-locator, codebase-
6
+ * analyzer, etc.) inside a single explorer session via `@"name (agent)"`
7
+ * syntax — a Claude-specific feature. OpenCode sessions are bound to a
8
+ * single agent for their lifetime, so we keep the SAME graph topology
9
+ * (scout ∥ history → explorer-1..N → aggregator) but drive each explorer
10
+ * through the locate → analyze → patterns → synthesize sequence inline using
11
+ * the default agent's built-in file tools.
12
+ *
13
+ * Topology (identical to Claude version):
14
+ *
15
+ * ┌─→ codebase-scout
16
+ * parent ─┤
17
+ * └─→ research-history
18
+ * │
19
+ * ▼
20
+ * ┌──────────────────────────────────────────────────┐
21
+ * │ explorer-1 explorer-2 ... explorer-N │ (Promise.all)
22
+ * └──────────────────────────────────────────────────┘
23
+ * │
24
+ * ▼
25
+ * aggregator
26
+ *
27
+ * OpenCode-specific concerns baked in:
28
+ *
29
+ * • F5 — every `ctx.stage()` call is a FRESH session with no memory of
30
+ * prior stages. We forward the scout overview, history overview, and
31
+ * partition assignment explicitly into each explorer's first prompt.
32
+ *
33
+ * • F9 — `s.save()` receives the `{ info, parts }` payload from
34
+ * `s.client.session.prompt()` via `result.data!`. Passing the full
35
+ * `result` (with its wrapping) or raw `result.data.parts` breaks
36
+ * downstream `transcript()` reads.
37
+ *
38
+ * • F6 — every prompt explicitly requires trailing prose AFTER any tool
39
+ * call so the rendered transcript has content. OpenCode's `parts` array
40
+ * mixes text/tool/reasoning/file parts; without trailing text the
41
+ * transcript extractor returns an empty string.
42
+ *
43
+ * • F3 — transcript extraction relies on the runtime's text-only rendering
44
+ * of `result.data.parts`. The helpers call `ctx.transcript(handle)` which
45
+ * returns `{ path, content }` where content is already text-filtered.
46
+ */
47
+ declare const _default: import("../../../index.ts").WorkflowDefinition<"opencode">;
48
+ export default _default;
@@ -12,7 +12,7 @@ import {
12
12
  import"../../../../../chunk-1gb5qxz9.js";
13
13
  import {
14
14
  defineWorkflow
15
- } from "../../../../../chunk-mn870nrv.js";
15
+ } from "../../../../../chunk-xkxndz5g.js";
16
16
 
17
17
  // src/sdk/workflows/builtin/ralph/claude/index.ts
18
18
  var MAX_LOOPS = 10;
@@ -24,12 +24,13 @@ var claude_default = defineWorkflow({
24
24
  name: "ralph",
25
25
  description: "Plan \u2192 orchestrate \u2192 review \u2192 debug loop with bounded iteration"
26
26
  }).run(async (ctx) => {
27
+ const prompt = ctx.inputs.prompt ?? "";
27
28
  let consecutiveClean = 0;
28
29
  let debuggerReport = "";
29
30
  for (let iteration = 1;iteration <= MAX_LOOPS; iteration++) {
30
31
  const plannerName = `planner-${iteration}`;
31
32
  await ctx.stage({ name: plannerName }, {}, {}, async (s) => {
32
- await s.session.query(asAgentCall("planner", buildPlannerPrompt(s.userPrompt, {
33
+ await s.session.query(asAgentCall("planner", buildPlannerPrompt(prompt, {
33
34
  iteration,
34
35
  debuggerReport: debuggerReport || undefined
35
36
  })));
@@ -37,13 +38,13 @@ var claude_default = defineWorkflow({
37
38
  });
38
39
  const orchName = `orchestrator-${iteration}`;
39
40
  await ctx.stage({ name: orchName }, {}, {}, async (s) => {
40
- await s.session.query(asAgentCall("orchestrator", buildOrchestratorPrompt(s.userPrompt)));
41
+ await s.session.query(asAgentCall("orchestrator", buildOrchestratorPrompt(prompt)));
41
42
  s.save(s.sessionId);
42
43
  });
43
44
  let gitStatus = await safeGitStatusS();
44
45
  const reviewerName = `reviewer-${iteration}`;
45
46
  const review = await ctx.stage({ name: reviewerName }, {}, {}, async (s) => {
46
- const result = await s.session.query(asAgentCall("reviewer", buildReviewPrompt(s.userPrompt, { gitStatus, iteration })));
47
+ const result = await s.session.query(asAgentCall("reviewer", buildReviewPrompt(prompt, { gitStatus, iteration })));
47
48
  s.save(s.sessionId);
48
49
  return result.output;
49
50
  });
@@ -56,7 +57,7 @@ var claude_default = defineWorkflow({
56
57
  gitStatus = await safeGitStatusS();
57
58
  const confirmName = `reviewer-${iteration}-confirm`;
58
59
  const confirm = await ctx.stage({ name: confirmName }, {}, {}, async (s) => {
59
- const result = await s.session.query(asAgentCall("reviewer", buildReviewPrompt(s.userPrompt, {
60
+ const result = await s.session.query(asAgentCall("reviewer", buildReviewPrompt(prompt, {
60
61
  gitStatus,
61
62
  iteration,
62
63
  isConfirmationPass: true
@@ -12,7 +12,7 @@ import {
12
12
  import"../../../../../chunk-1gb5qxz9.js";
13
13
  import {
14
14
  defineWorkflow
15
- } from "../../../../../chunk-mn870nrv.js";
15
+ } from "../../../../../chunk-xkxndz5g.js";
16
16
 
17
17
  // src/sdk/workflows/builtin/ralph/copilot/index.ts
18
18
  var MAX_LOOPS = 10;
@@ -27,13 +27,14 @@ var copilot_default = defineWorkflow({
27
27
  name: "ralph",
28
28
  description: "Plan \u2192 orchestrate \u2192 review \u2192 debug loop with bounded iteration"
29
29
  }).run(async (ctx) => {
30
+ const userPromptText = ctx.inputs.prompt ?? "";
30
31
  let consecutiveClean = 0;
31
32
  let debuggerReport = "";
32
33
  for (let iteration = 1;iteration <= MAX_LOOPS; iteration++) {
33
34
  const plannerName = `planner-${iteration}`;
34
35
  const planner = await ctx.stage({ name: plannerName }, {}, { agent: "planner" }, async (s) => {
35
36
  await s.session.sendAndWait({
36
- prompt: buildPlannerPrompt(s.userPrompt, {
37
+ prompt: buildPlannerPrompt(userPromptText, {
37
38
  iteration,
38
39
  debuggerReport: debuggerReport || undefined
39
40
  })
@@ -45,7 +46,7 @@ var copilot_default = defineWorkflow({
45
46
  const orchName = `orchestrator-${iteration}`;
46
47
  await ctx.stage({ name: orchName }, {}, { agent: "orchestrator" }, async (s) => {
47
48
  await s.session.sendAndWait({
48
- prompt: buildOrchestratorPrompt(s.userPrompt, {
49
+ prompt: buildOrchestratorPrompt(userPromptText, {
49
50
  plannerNotes: planner.result
50
51
  })
51
52
  }, AGENT_SEND_TIMEOUT_MS);
@@ -55,7 +56,7 @@ var copilot_default = defineWorkflow({
55
56
  const reviewerName = `reviewer-${iteration}`;
56
57
  const review = await ctx.stage({ name: reviewerName }, {}, { agent: "reviewer" }, async (s) => {
57
58
  await s.session.sendAndWait({
58
- prompt: buildReviewPrompt(s.userPrompt, {
59
+ prompt: buildReviewPrompt(userPromptText, {
59
60
  gitStatus,
60
61
  iteration
61
62
  })
@@ -74,7 +75,7 @@ var copilot_default = defineWorkflow({
74
75
  const confirmName = `reviewer-${iteration}-confirm`;
75
76
  const confirm = await ctx.stage({ name: confirmName }, {}, { agent: "reviewer" }, async (s) => {
76
77
  await s.session.sendAndWait({
77
- prompt: buildReviewPrompt(s.userPrompt, {
78
+ prompt: buildReviewPrompt(userPromptText, {
78
79
  gitStatus,
79
80
  iteration,
80
81
  isConfirmationPass: true
@@ -12,7 +12,7 @@ import {
12
12
  import"../../../../../chunk-1gb5qxz9.js";
13
13
  import {
14
14
  defineWorkflow
15
- } from "../../../../../chunk-mn870nrv.js";
15
+ } from "../../../../../chunk-xkxndz5g.js";
16
16
 
17
17
  // src/sdk/workflows/builtin/ralph/opencode/index.ts
18
18
  var MAX_LOOPS = 10;
@@ -25,6 +25,7 @@ var opencode_default = defineWorkflow({
25
25
  name: "ralph",
26
26
  description: "Plan \u2192 orchestrate \u2192 review \u2192 debug loop with bounded iteration"
27
27
  }).run(async (ctx) => {
28
+ const prompt = ctx.inputs.prompt ?? "";
28
29
  let consecutiveClean = 0;
29
30
  let debuggerReport = "";
30
31
  for (let iteration = 1;iteration <= MAX_LOOPS; iteration++) {
@@ -35,7 +36,7 @@ var opencode_default = defineWorkflow({
35
36
  parts: [
36
37
  {
37
38
  type: "text",
38
- text: buildPlannerPrompt(s.userPrompt, {
39
+ text: buildPlannerPrompt(prompt, {
39
40
  iteration,
40
41
  debuggerReport: debuggerReport || undefined
41
42
  })
@@ -53,7 +54,7 @@ var opencode_default = defineWorkflow({
53
54
  parts: [
54
55
  {
55
56
  type: "text",
56
- text: buildOrchestratorPrompt(s.userPrompt, {
57
+ text: buildOrchestratorPrompt(prompt, {
57
58
  plannerNotes: planner.result
58
59
  })
59
60
  }
@@ -70,7 +71,7 @@ var opencode_default = defineWorkflow({
70
71
  parts: [
71
72
  {
72
73
  type: "text",
73
- text: buildReviewPrompt(s.userPrompt, {
74
+ text: buildReviewPrompt(prompt, {
74
75
  gitStatus,
75
76
  iteration
76
77
  })
@@ -95,7 +96,7 @@ var opencode_default = defineWorkflow({
95
96
  parts: [
96
97
  {
97
98
  type: "text",
98
- text: buildReviewPrompt(s.userPrompt, {
99
+ text: buildReviewPrompt(prompt, {
99
100
  gitStatus,
100
101
  iteration,
101
102
  isConfirmationPass: true
@@ -6,7 +6,7 @@
6
6
  * for spawning agent sessions using native TypeScript control flow.
7
7
  */
8
8
  export { defineWorkflow, WorkflowBuilder } from "../define-workflow.js";
9
- export type { AgentType, Transcript, SavedMessage, SaveTranscript, SessionContext, SessionRef, SessionHandle, SessionRunOptions, WorkflowContext, WorkflowOptions, WorkflowDefinition, StageClientOptions, StageSessionOptions, ProviderClient, ProviderSession, CopilotClient, CopilotClientOptions, CopilotSession, CopilotSessionConfig, OpencodeClient, OpencodeSession, ClaudeClientWrapper, ClaudeSessionWrapper, ClaudeQueryDefaults, } from "../types.js";
9
+ export type { AgentType, Transcript, SavedMessage, SaveTranscript, SessionContext, SessionRef, SessionHandle, SessionRunOptions, WorkflowContext, WorkflowOptions, WorkflowDefinition, WorkflowInput, WorkflowInputType, StageClientOptions, StageSessionOptions, ProviderClient, ProviderSession, CopilotClient, CopilotClientOptions, CopilotSession, CopilotSessionConfig, OpencodeClient, OpencodeSession, ClaudeClientWrapper, ClaudeSessionWrapper, ClaudeQueryDefaults, } from "../types.js";
10
10
  export type { SessionEvent as CopilotSessionEvent } from "@github/copilot-sdk";
11
11
  export type { SessionPromptResponse as OpenCodePromptResponse } from "@opencode-ai/sdk/v2";
12
12
  export type { SessionMessage as ClaudeSessionMessage } from "@anthropic-ai/claude-agent-sdk";
@@ -16,9 +16,9 @@ export { validateCopilotWorkflow } from "../providers/copilot.js";
16
16
  export type { CopilotValidationWarning } from "../providers/copilot.js";
17
17
  export { validateOpenCodeWorkflow } from "../providers/opencode.js";
18
18
  export type { OpenCodeValidationWarning } from "../providers/opencode.js";
19
- export { isTmuxInstalled, getMuxBinary, resetMuxBinaryCache, isInsideTmux, createSession, createWindow, createPane, sendLiteralText, sendSpecialKey, sendKeysAndSubmit, capturePane, capturePaneVisible, capturePaneScrollback, killSession, killWindow, sessionExists, attachSession, switchClient, getCurrentSession, attachOrSwitch, selectWindow, waitForOutput, tmuxRun, normalizeTmuxCapture, normalizeTmuxLines, paneLooksReady, paneHasActiveTask, paneIsIdle, waitForPaneReady, attemptSubmitRounds, } from "../runtime/tmux.js";
20
- export { AGENTS, discoverWorkflows, findWorkflow, WORKFLOWS_GITIGNORE, } from "../runtime/discovery.js";
21
- export type { DiscoveredWorkflow } from "../runtime/discovery.js";
19
+ export { SOCKET_NAME, isTmuxInstalled, getMuxBinary, resetMuxBinaryCache, isInsideTmux, createSession, createWindow, createPane, sendLiteralText, sendSpecialKey, sendKeysAndSubmit, capturePane, capturePaneVisible, capturePaneScrollback, killSession, killWindow, sessionExists, attachSession, spawnMuxAttach, switchClient, getCurrentSession, attachOrSwitch, selectWindow, waitForOutput, tmuxRun, normalizeTmuxCapture, normalizeTmuxLines, paneLooksReady, paneHasActiveTask, paneIsIdle, waitForPaneReady, attemptSubmitRounds, } from "../runtime/tmux.js";
20
+ export { AGENTS, discoverWorkflows, findWorkflow, loadWorkflowsMetadata, WORKFLOWS_GITIGNORE, } from "../runtime/discovery.js";
21
+ export type { DiscoveredWorkflow, WorkflowWithMetadata, } from "../runtime/discovery.js";
22
22
  export { WorkflowLoader } from "../runtime/loader.js";
23
23
  export { executeWorkflow } from "../runtime/executor.js";
24
24
  export type { WorkflowRunOptions } from "../runtime/executor.js";
@@ -2,6 +2,7 @@
2
2
  import"../../chunk-1gb5qxz9.js";
3
3
  import {
4
4
  AGENTS,
5
+ SOCKET_NAME,
5
6
  WORKFLOWS_GITIGNORE,
6
7
  WorkflowBuilder,
7
8
  WorkflowLoader,
@@ -27,6 +28,7 @@ import {
27
28
  isTmuxInstalled,
28
29
  killSession,
29
30
  killWindow,
31
+ loadWorkflowsMetadata,
30
32
  normalizeTmuxCapture,
31
33
  normalizeTmuxLines,
32
34
  paneHasActiveTask,
@@ -38,6 +40,7 @@ import {
38
40
  sendLiteralText,
39
41
  sendSpecialKey,
40
42
  sessionExists,
43
+ spawnMuxAttach,
41
44
  switchClient,
42
45
  tmuxRun,
43
46
  validateClaudeWorkflow,
@@ -45,7 +48,7 @@ import {
45
48
  validateOpenCodeWorkflow,
46
49
  waitForOutput,
47
50
  waitForPaneReady
48
- } from "../../chunk-mn870nrv.js";
51
+ } from "../../chunk-xkxndz5g.js";
49
52
  export {
50
53
  waitForPaneReady,
51
54
  waitForOutput,
@@ -54,6 +57,7 @@ export {
54
57
  validateClaudeWorkflow,
55
58
  tmuxRun,
56
59
  switchClient,
60
+ spawnMuxAttach,
57
61
  sessionExists,
58
62
  sendSpecialKey,
59
63
  sendLiteralText,
@@ -65,6 +69,7 @@ export {
65
69
  paneHasActiveTask,
66
70
  normalizeTmuxLines,
67
71
  normalizeTmuxCapture,
72
+ loadWorkflowsMetadata,
68
73
  killWindow,
69
74
  killSession,
70
75
  isTmuxInstalled,
@@ -90,5 +95,6 @@ export {
90
95
  WorkflowLoader,
91
96
  WorkflowBuilder,
92
97
  WORKFLOWS_GITIGNORE,
98
+ SOCKET_NAME,
93
99
  AGENTS
94
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.5.3-1",
3
+ "version": "0.5.4-0",
4
4
  "description": "Configuration management CLI and SDK for coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli.ts CHANGED
@@ -127,20 +127,42 @@ Examples:
127
127
  });
128
128
 
129
129
  // Add workflow command
130
+ //
131
+ // Two shapes are supported behind a single command:
132
+ // 1. `atomic workflow -a <agent>` — interactive picker
133
+ // 2. `atomic workflow -n <name> -a <agent> ...` — named run with
134
+ // either a positional prompt (free-form workflows) or
135
+ // `--<field>=<value>` flags (structured-input workflows).
136
+ //
137
+ // `allowUnknownOption` + `allowExcessArguments` give us both: unknown
138
+ // flags and positional tokens land in `cmd.args`, which we forward
139
+ // as `passthroughArgs` so the command layer can parse them against
140
+ // the workflow's declared schema.
130
141
  program
131
142
  .command("workflow")
132
143
  .description("Run a multi-session agent workflow")
133
144
  .option("-n, --name <name>", "Workflow name (matches directory under .atomic/workflows/<name>/)")
134
145
  .option("-a, --agent <name>", `Agent to use (${agentChoices})`)
135
146
  .option("-l, --list", "List available workflows")
136
- .argument("[prompt...]", "Prompt for the workflow")
137
- .action(async (promptParts, localOpts) => {
147
+ .allowUnknownOption()
148
+ .allowExcessArguments(true)
149
+ .addHelpText(
150
+ "after",
151
+ `
152
+ Examples:
153
+ $ atomic workflow -l List available workflows
154
+ $ atomic workflow -a claude Open the interactive picker
155
+ $ atomic workflow -n ralph -a claude "fix bug" Run a free-form workflow
156
+ $ atomic workflow -n gen-spec -a claude --research_doc=notes.md --focus=standard
157
+ Run a structured-input workflow`,
158
+ )
159
+ .action(async (localOpts, cmd) => {
138
160
  const { workflowCommand } = await import("@/commands/cli/workflow.ts");
139
161
  const exitCode = await workflowCommand({
140
162
  name: localOpts.name,
141
163
  agent: localOpts.agent,
142
- prompt: promptParts.length > 0 ? promptParts.join(" ") : undefined,
143
164
  list: localOpts.list,
165
+ passthroughArgs: cmd.args,
144
166
  });
145
167
  process.exit(exitCode);
146
168
  });
@@ -31,7 +31,8 @@ import {
31
31
  import {
32
32
  createSession,
33
33
  killSession,
34
- getMuxBinary,
34
+ spawnMuxAttach,
35
+ SOCKET_NAME,
35
36
  } from "@/sdk/workflows/index.ts";
36
37
  import { ensureTmuxInstalled } from "@/lib/spawn.ts";
37
38
 
@@ -222,10 +223,9 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise<num
222
223
  try {
223
224
  createSession(windowName, shellCmd, undefined, projectRoot);
224
225
 
225
- const muxBinary = getMuxBinary() ?? "tmux";
226
- const attachProc = Bun.spawn([muxBinary, "attach-session", "-t", windowName], {
227
- stdio: ["inherit", "inherit", "inherit"],
228
- });
226
+ console.log(`[atomic] Session: ${windowName} (FYI all atomic sessions run on tmux -L ${SOCKET_NAME})`);
227
+
228
+ const attachProc = spawnMuxAttach(windowName);
229
229
  const exitCode = await attachProc.exited;
230
230
 
231
231
  // Clean up launcher
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * Init command - Interactive setup flow for atomic CLI
3
+ *
4
+ * Uses Catppuccin Mocha palette for visual hierarchy and brand alignment.
5
+ * All color output respects the NO_COLOR environment variable.
3
6
  */
4
7
 
5
8
  import {
@@ -45,6 +48,7 @@ import {
45
48
  hasProjectOnboardingFiles,
46
49
  } from "./onboarding.ts";
47
50
  import { displayBlockBanner } from "@/theme/logo.ts";
51
+ import { createPainter } from "@/theme/colors.ts";
48
52
 
49
53
  /**
50
54
  * Thrown when the user cancels an interactive prompt during init.
@@ -91,6 +95,7 @@ export {
91
95
  */
92
96
  export async function initCommand(options: InitOptions = {}): Promise<void> {
93
97
  const { showBanner = true, configNotFoundMessage, callerHandlesExit = false } = options;
98
+ const paint = createPainter();
94
99
 
95
100
  /** Exit-or-throw helper: when a caller (e.g. chatCommand auto-init) sets
96
101
  * `callerHandlesExit`, we throw so the caller can handle the cancellation.
@@ -107,30 +112,23 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
107
112
  displayBlockBanner();
108
113
  }
109
114
 
110
- // Show intro
111
- intro("Atomic: Automated Procedures and Memory for AI Coding Agents");
112
- log.message(
113
- "Enable multi-hour autonomous coding sessions with the Ralph Wiggum\nMethod using research, plan, implement methodology."
114
- );
115
+ intro(paint("accent", "Configure agent skills & source control", { bold: true }));
115
116
 
116
- // Show config not found message if provided (after intro, before agent selection)
117
117
  if (configNotFoundMessage) {
118
118
  log.info(configNotFoundMessage);
119
119
  }
120
120
 
121
- // Select agent
121
+ // ── Agent selection ────────────────────────────────────────────────
122
122
  let agentKey: AgentKey;
123
123
 
124
124
  if (options.preSelectedAgent) {
125
- // Pre-selected agent - validate and skip selection prompt
126
125
  if (!isValidAgent(options.preSelectedAgent)) {
127
126
  cancel(`Unknown agent: ${options.preSelectedAgent}`);
128
127
  exitOrThrow(1, `Unknown agent: ${options.preSelectedAgent}`);
129
128
  }
130
129
  agentKey = options.preSelectedAgent;
131
- log.info(`Configuring ${AGENT_CONFIG[agentKey].name}...`);
130
+ log.info(`${paint("accent", "→")} Agent: ${paint("text", AGENT_CONFIG[agentKey].name, { bold: true })}`);
132
131
  } else {
133
- // Interactive selection
134
132
  const agentKeys = getAgentKeys();
135
133
  const agentOptions = agentKeys.map((key) => ({
136
134
  value: key,
@@ -139,12 +137,12 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
139
137
  }));
140
138
 
141
139
  const selectedAgent = await select({
142
- message: "Select a coding agent to configure:",
140
+ message: "Which coding agent?",
143
141
  options: agentOptions,
144
142
  });
145
143
 
146
144
  if (isCancel(selectedAgent)) {
147
- cancel("Operation cancelled.");
145
+ cancel("Cancelled.");
148
146
  exitOrThrow(0);
149
147
  }
150
148
 
@@ -152,104 +150,87 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
152
150
  }
153
151
  const agent = AGENT_CONFIG[agentKey];
154
152
  const targetDir = process.cwd();
155
-
156
- // Auto-confirm mode for CI/testing
157
153
  const autoConfirm = options.yes ?? false;
158
154
 
159
- // Select source control type (after agent selection)
155
+ // ── SCM selection ──────────────────────────────────────────────────
160
156
  let scmType: SourceControlType;
161
157
 
162
158
  if (options.preSelectedScm) {
163
- // Pre-selected SCM - validate and skip selection prompt
164
159
  if (!isValidScm(options.preSelectedScm)) {
165
160
  cancel(`Unknown source control: ${options.preSelectedScm}`);
166
161
  exitOrThrow(1, `Unknown source control: ${options.preSelectedScm}`);
167
162
  }
168
163
  scmType = options.preSelectedScm;
169
- log.info(`Using ${SCM_CONFIG[scmType].displayName} for source control...`);
164
+ log.info(`${paint("accent", "→")} SCM: ${paint("text", SCM_CONFIG[scmType].displayName, { bold: true })}`);
170
165
  } else if (autoConfirm) {
171
- // Auto-confirm mode defaults to GitHub
172
166
  scmType = "github";
173
- log.info("Defaulting to GitHub/Git for source control...");
167
+ log.info(`${paint("accent", "→")} SCM: ${paint("text", "GitHub / Git", { bold: true })} ${paint("dim", "(default)")}`);
174
168
  } else {
175
- // Interactive selection
176
169
  const scmOptions = getScmKeys().map((key) => ({
177
170
  value: key,
178
171
  label: SCM_CONFIG[key].displayName,
179
- hint: `Uses ${SCM_CONFIG[key].cliTool} + ${SCM_CONFIG[key].reviewSystem}`,
172
+ hint: `${SCM_CONFIG[key].cliTool} + ${SCM_CONFIG[key].reviewSystem}`,
180
173
  }));
181
174
 
182
175
  const selectedScm = await select({
183
- message: "Select your source control system:",
176
+ message: "Which source control?",
184
177
  options: scmOptions,
185
178
  });
186
179
 
187
180
  if (isCancel(selectedScm)) {
188
- cancel("Operation cancelled.");
181
+ cancel("Cancelled.");
189
182
  exitOrThrow(0);
190
183
  }
191
184
 
192
185
  scmType = selectedScm as SourceControlType;
193
186
  }
194
187
 
195
- // Show Phabricator configuration warning if Sapling is selected
188
+ // Sapling-specific warning
196
189
  if (scmType === "sapling") {
197
190
  const arcconfigPath = join(targetDir, ".arcconfig");
198
191
  const hasArcconfig = await pathExists(arcconfigPath);
199
192
 
200
193
  if (!hasArcconfig) {
201
194
  log.warn(
202
- "Note: Sapling + Phabricator requires .arcconfig in your repository root.\n" +
203
- "See: https://www.phacility.com/phabricator/ for Phabricator setup."
195
+ `Sapling + Phabricator requires ${paint("text", ".arcconfig", { bold: true })} in your repo root.\n` +
196
+ `${paint("dim", "See: https://www.phacility.com/phabricator/")}`
204
197
  );
205
198
  }
206
199
  }
207
200
 
208
- // Confirm directory
209
- let confirmDir: boolean | symbol = true;
210
- if (!autoConfirm) {
211
- confirmDir = await confirm({
212
- message: `Configure ${agent.name} source control skills in ${targetDir}?`,
213
- initialValue: true,
214
- });
215
-
216
- if (isCancel(confirmDir)) {
217
- cancel("Operation cancelled.");
218
- exitOrThrow(0);
219
- }
220
-
221
- if (!confirmDir) {
222
- cancel("Operation cancelled.");
223
- exitOrThrow(0);
224
- }
225
- }
226
-
227
- // Check if folder already exists
201
+ // ── Preflight summary ──────────────────────────────────────────────
228
202
  const targetFolder = join(targetDir, agent.folder);
229
203
  const folderExists = await pathExists(targetFolder);
204
+ const configAction = folderExists ? "update" : "create";
230
205
 
231
- if (folderExists && !autoConfirm) {
232
- const update = await confirm({
233
- message: `${agent.folder} already exists. Update source control skills?`,
206
+ if (!autoConfirm) {
207
+ const summaryLines = [
208
+ `${paint("dim", "Agent")} ${paint("text", agent.name, { bold: true })}`,
209
+ `${paint("dim", "SCM")} ${paint("text", SCM_CONFIG[scmType].displayName, { bold: true })}`,
210
+ `${paint("dim", "Target")} ${paint("text", targetDir)}`,
211
+ `${paint("dim", "Action")} ${paint(folderExists ? "warning" : "success", configAction)}`,
212
+ ];
213
+ note(summaryLines.join("\n"), paint("accent", "Setup", { bold: true }));
214
+
215
+ const shouldProceed = await confirm({
216
+ message: folderExists
217
+ ? `${agent.folder} exists — update source control skills?`
218
+ : "Proceed with setup?",
234
219
  initialValue: true,
235
- active: "Yes, update",
236
- inactive: "No, cancel",
237
220
  });
238
221
 
239
- if (isCancel(update)) {
240
- cancel("Operation cancelled.");
241
- exitOrThrow(0);
242
- }
243
-
244
- if (!update) {
245
- cancel("Operation cancelled. Existing config preserved.");
222
+ if (isCancel(shouldProceed) || !shouldProceed) {
223
+ cancel("Cancelled.");
246
224
  exitOrThrow(0);
247
225
  }
248
226
  }
249
227
 
250
- // Configure source control skills with spinner
228
+ // ── Configure ──────────────────────────────────────────────────────
251
229
  const s = spinner();
252
- s.start("Configuring source control skills...");
230
+ s.start("Configuring skills");
231
+
232
+ let skillsInstalled = false;
233
+ let skillsSkipReason = "";
253
234
 
254
235
  try {
255
236
  const configRoot = getConfigRoot();
@@ -289,7 +270,7 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
289
270
  });
290
271
  upsertTrustedWorkspacePath(resolve(targetDir), agentKey);
291
272
 
292
- s.stop("Source control skills configured successfully!");
273
+ s.stop(paint("success", "✓", { bold: true }) + " Skills configured");
293
274
 
294
275
  // Install SCM-specific skill variants locally for the active agent via
295
276
  // `npx skills add` (best-effort: a failure is surfaced as a warning).
@@ -303,7 +284,7 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
303
284
  const skillsLabel = skillsToInstall.join(", ");
304
285
  const skillsSpinner = spinner();
305
286
  skillsSpinner.start(
306
- `Installing ${skillsLabel} locally for ${agent.name}...`,
287
+ `Installing ${paint("text", skillsLabel, { bold: true })}…`,
307
288
  );
308
289
  const skillsResult = await installLocalScmSkills({
309
290
  scmType,
@@ -311,39 +292,60 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
311
292
  cwd: targetDir,
312
293
  });
313
294
  if (skillsResult.success) {
295
+ skillsInstalled = true;
314
296
  skillsSpinner.stop(
315
- `Installed ${skillsLabel} locally for ${agent.name}`,
297
+ paint("success", "✓", { bold: true }) + ` ${skillsLabel} installed`,
316
298
  );
317
299
  } else {
300
+ skillsSkipReason = skillsResult.details;
318
301
  skillsSpinner.stop(
319
- `Skipped local ${skillsLabel} install (${skillsResult.details})`,
302
+ paint("warning", "○") + ` ${skillsLabel} skipped ${paint("dim", `(${skillsResult.details})`)}`,
320
303
  );
321
304
  }
322
305
  }
323
306
  } catch (error) {
324
- s.stop("Failed to configure source control skills");
307
+ s.stop(paint("error", "✗", { bold: true }) + " Configuration failed");
325
308
  console.error(
326
309
  error instanceof Error ? error.message : "Unknown error occurred"
327
310
  );
328
311
  exitOrThrow(1, error instanceof Error ? error.message : "Unknown error occurred");
329
312
  }
330
313
 
331
- // Check for WSL on Windows
314
+ // ── WSL warning ────────────────────────────────────────────────────
332
315
  if (isWindows() && !isWslInstalled()) {
333
- note(
334
- `WSL is not installed. Some scripts may require WSL.\n` +
335
- `Install WSL: ${WSL_INSTALL_URL}`,
336
- "Warning"
316
+ log.warn(
317
+ `WSL not detected. Some scripts may require it.\n` +
318
+ `${paint("dim", WSL_INSTALL_URL)}`
337
319
  );
338
320
  }
339
321
 
340
- // Success message
341
- note(
342
- `${agent.name} source control skills configured in ${agent.folder}/skills\n\n` +
343
- `Selected workflow: ${SCM_CONFIG[scmType].displayName}\n\n` +
344
- `Run '${agent.cmd}' to start the agent.`,
345
- "Success"
322
+ // ── Summary ────────────────────────────────────────────────────────
323
+ const resultLines: string[] = [];
324
+ resultLines.push(
325
+ `${paint("success", "✓")} ${agent.name} skills ${paint("dim", "→")} ${paint("text", agent.folder + "/skills")}`,
326
+ );
327
+ resultLines.push(
328
+ `${paint("success", "✓")} SCM workflow ${paint("dim", "→")} ${paint("text", SCM_CONFIG[scmType].displayName)}`,
329
+ );
330
+
331
+ if (import.meta.dir.includes("node_modules")) {
332
+ if (skillsInstalled) {
333
+ resultLines.push(
334
+ `${paint("success", "✓")} Local skills installed`,
335
+ );
336
+ } else {
337
+ resultLines.push(
338
+ `${paint("warning", "○")} Local skills skipped ${paint("dim", skillsSkipReason ? `(${skillsSkipReason})` : "")}`,
339
+ );
340
+ }
341
+ }
342
+
343
+ resultLines.push("");
344
+ resultLines.push(
345
+ `${paint("accent", "→")} Run ${paint("text", agent.cmd, { bold: true })} to start the agent`,
346
346
  );
347
347
 
348
- outro("You're all set!");
348
+ note(resultLines.join("\n"), paint("success", "Ready", { bold: true }));
349
+
350
+ outro(paint("dim", "Happy coding ⚛"));
349
351
  }