@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.
- package/README.md +110 -11
- package/dist/{chunk-mn870nrv.js → chunk-xkxndz5g.js} +213 -154
- package/dist/sdk/components/workflow-picker-panel.d.ts +120 -0
- package/dist/sdk/define-workflow.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/runtime/discovery.d.ts +57 -3
- package/dist/sdk/runtime/executor.d.ts +15 -2
- package/dist/sdk/runtime/tmux.d.ts +9 -0
- package/dist/sdk/types.d.ts +63 -4
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +61 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +25 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +91 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +56 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/ralph/claude/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/copilot/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/opencode/index.js +6 -5
- package/dist/sdk/workflows/index.d.ts +4 -4
- package/dist/sdk/workflows/index.js +7 -1
- package/package.json +1 -1
- package/src/cli.ts +25 -3
- package/src/commands/cli/chat/index.ts +5 -5
- package/src/commands/cli/init/index.ts +79 -77
- package/src/commands/cli/workflow-command.test.ts +757 -0
- package/src/commands/cli/workflow.test.ts +310 -0
- package/src/commands/cli/workflow.ts +445 -105
- package/src/sdk/components/workflow-picker-panel.tsx +1462 -0
- package/src/sdk/define-workflow.test.ts +101 -0
- package/src/sdk/define-workflow.ts +62 -2
- package/src/sdk/runtime/discovery.ts +111 -8
- package/src/sdk/runtime/executor.ts +89 -32
- package/src/sdk/runtime/tmux.conf +55 -0
- package/src/sdk/runtime/tmux.ts +34 -10
- package/src/sdk/types.ts +67 -4
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +294 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +276 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +38 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +816 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +334 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +284 -0
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +8 -4
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +10 -4
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +8 -4
- package/src/sdk/workflows/index.ts +9 -1
- package/src/services/system/auto-sync.ts +1 -1
- package/src/services/system/install-ui.ts +109 -39
- 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-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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-
|
|
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
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
|
-
.
|
|
137
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
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: "
|
|
140
|
+
message: "Which coding agent?",
|
|
143
141
|
options: agentOptions,
|
|
144
142
|
});
|
|
145
143
|
|
|
146
144
|
if (isCancel(selectedAgent)) {
|
|
147
|
-
cancel("
|
|
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
|
-
//
|
|
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(
|
|
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("
|
|
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:
|
|
172
|
+
hint: `${SCM_CONFIG[key].cliTool} + ${SCM_CONFIG[key].reviewSystem}`,
|
|
180
173
|
}));
|
|
181
174
|
|
|
182
175
|
const selectedScm = await select({
|
|
183
|
-
message: "
|
|
176
|
+
message: "Which source control?",
|
|
184
177
|
options: scmOptions,
|
|
185
178
|
});
|
|
186
179
|
|
|
187
180
|
if (isCancel(selectedScm)) {
|
|
188
|
-
cancel("
|
|
181
|
+
cancel("Cancelled.");
|
|
189
182
|
exitOrThrow(0);
|
|
190
183
|
}
|
|
191
184
|
|
|
192
185
|
scmType = selectedScm as SourceControlType;
|
|
193
186
|
}
|
|
194
187
|
|
|
195
|
-
//
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
//
|
|
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 (
|
|
232
|
-
const
|
|
233
|
-
|
|
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(
|
|
240
|
-
cancel("
|
|
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
|
|
228
|
+
// ── Configure ──────────────────────────────────────────────────────
|
|
251
229
|
const s = spinner();
|
|
252
|
-
s.start("Configuring
|
|
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("
|
|
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
|
|
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
|
-
|
|
297
|
+
paint("success", "✓", { bold: true }) + ` ${skillsLabel} installed`,
|
|
316
298
|
);
|
|
317
299
|
} else {
|
|
300
|
+
skillsSkipReason = skillsResult.details;
|
|
318
301
|
skillsSpinner.stop(
|
|
319
|
-
`
|
|
302
|
+
paint("warning", "○") + ` ${skillsLabel} skipped ${paint("dim", `(${skillsResult.details})`)}`,
|
|
320
303
|
);
|
|
321
304
|
}
|
|
322
305
|
}
|
|
323
306
|
} catch (error) {
|
|
324
|
-
s.stop("
|
|
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
|
-
//
|
|
314
|
+
// ── WSL warning ────────────────────────────────────────────────────
|
|
332
315
|
if (isWindows() && !isWslInstalled()) {
|
|
333
|
-
|
|
334
|
-
`WSL
|
|
335
|
-
|
|
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
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
348
|
+
note(resultLines.join("\n"), paint("success", "Ready", { bold: true }));
|
|
349
|
+
|
|
350
|
+
outro(paint("dim", "Happy coding ⚛"));
|
|
349
351
|
}
|