@bastani/atomic 0.5.0-1

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 (68) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +956 -0
  3. package/assets/settings.schema.json +52 -0
  4. package/package.json +68 -0
  5. package/src/cli.ts +197 -0
  6. package/src/commands/cli/chat/client.ts +18 -0
  7. package/src/commands/cli/chat/index.ts +247 -0
  8. package/src/commands/cli/chat.ts +8 -0
  9. package/src/commands/cli/config.ts +55 -0
  10. package/src/commands/cli/init/index.ts +452 -0
  11. package/src/commands/cli/init/onboarding.ts +45 -0
  12. package/src/commands/cli/init/scm.ts +190 -0
  13. package/src/commands/cli/init.ts +8 -0
  14. package/src/commands/cli/update.ts +46 -0
  15. package/src/commands/cli/workflow.ts +164 -0
  16. package/src/lib/merge.ts +65 -0
  17. package/src/lib/path-root-guard.ts +38 -0
  18. package/src/lib/spawn.ts +467 -0
  19. package/src/scripts/bump-version.ts +94 -0
  20. package/src/scripts/constants-base.ts +14 -0
  21. package/src/scripts/constants.ts +34 -0
  22. package/src/sdk/components/color-utils.ts +20 -0
  23. package/src/sdk/components/connectors.test.ts +661 -0
  24. package/src/sdk/components/connectors.ts +156 -0
  25. package/src/sdk/components/edge.tsx +11 -0
  26. package/src/sdk/components/error-boundary.tsx +38 -0
  27. package/src/sdk/components/graph-theme.ts +36 -0
  28. package/src/sdk/components/header.tsx +60 -0
  29. package/src/sdk/components/layout.test.ts +924 -0
  30. package/src/sdk/components/layout.ts +186 -0
  31. package/src/sdk/components/node-card.tsx +68 -0
  32. package/src/sdk/components/orchestrator-panel-contexts.ts +26 -0
  33. package/src/sdk/components/orchestrator-panel-store.test.ts +561 -0
  34. package/src/sdk/components/orchestrator-panel-store.ts +118 -0
  35. package/src/sdk/components/orchestrator-panel-types.ts +21 -0
  36. package/src/sdk/components/orchestrator-panel.tsx +143 -0
  37. package/src/sdk/components/session-graph-panel.tsx +364 -0
  38. package/src/sdk/components/status-helpers.ts +32 -0
  39. package/src/sdk/components/statusline.tsx +63 -0
  40. package/src/sdk/define-workflow.ts +98 -0
  41. package/src/sdk/errors.ts +39 -0
  42. package/src/sdk/index.ts +38 -0
  43. package/src/sdk/providers/claude.ts +316 -0
  44. package/src/sdk/providers/copilot.ts +43 -0
  45. package/src/sdk/providers/opencode.ts +43 -0
  46. package/src/sdk/runtime/discovery.ts +172 -0
  47. package/src/sdk/runtime/executor.test.ts +415 -0
  48. package/src/sdk/runtime/executor.ts +695 -0
  49. package/src/sdk/runtime/loader.ts +372 -0
  50. package/src/sdk/runtime/panel.tsx +9 -0
  51. package/src/sdk/runtime/theme.ts +76 -0
  52. package/src/sdk/runtime/tmux.ts +542 -0
  53. package/src/sdk/types.ts +114 -0
  54. package/src/sdk/workflows.ts +85 -0
  55. package/src/services/config/atomic-config.ts +124 -0
  56. package/src/services/config/atomic-global-config.ts +361 -0
  57. package/src/services/config/config-path.ts +19 -0
  58. package/src/services/config/definitions.ts +176 -0
  59. package/src/services/config/index.ts +7 -0
  60. package/src/services/config/settings-schema.ts +2 -0
  61. package/src/services/config/settings.ts +149 -0
  62. package/src/services/system/copy.ts +381 -0
  63. package/src/services/system/detect.ts +161 -0
  64. package/src/services/system/download.ts +325 -0
  65. package/src/services/system/file-lock.ts +289 -0
  66. package/src/services/system/skills.ts +67 -0
  67. package/src/theme/colors.ts +25 -0
  68. package/src/version.ts +7 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Update command — upgrades atomic to the latest version via bun and
3
+ * reinstalls global skills.
4
+ *
5
+ * Usage:
6
+ * atomic update
7
+ */
8
+
9
+ import { COLORS } from "@/theme/colors.ts";
10
+ import { VERSION } from "@/version.ts";
11
+ import { installGlobalSkills } from "@/services/system/skills.ts";
12
+
13
+ export async function updateCommand(): Promise<number> {
14
+ const bunPath = Bun.which("bun");
15
+ if (!bunPath) {
16
+ console.error(`${COLORS.red}Error: bun is not installed.${COLORS.reset}`);
17
+ console.error("Install bun: https://bun.sh");
18
+ return 1;
19
+ }
20
+
21
+ console.log(`Current version: ${VERSION}`);
22
+ console.log("Updating atomic...\n");
23
+
24
+ // Upgrade the package
25
+ const proc = Bun.spawn([bunPath, "add", "-g", "atomic@latest"], {
26
+ stdio: ["ignore", "inherit", "inherit"],
27
+ });
28
+ const exitCode = await proc.exited;
29
+
30
+ if (exitCode !== 0) {
31
+ console.error(`\n${COLORS.red}Failed to update atomic (exit ${exitCode}).${COLORS.reset}`);
32
+ return 1;
33
+ }
34
+
35
+ // Reinstall global skills
36
+ console.log("\nUpdating skills...");
37
+ try {
38
+ await installGlobalSkills();
39
+ } catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ console.warn(`\n${COLORS.yellow}Warning: failed to install skills: ${message}${COLORS.reset}`);
42
+ }
43
+
44
+ console.log(`\n${COLORS.green}Update complete.${COLORS.reset}`);
45
+ return 0;
46
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Workflow CLI command
3
+ *
4
+ * Usage:
5
+ * atomic workflow -n <name> -a <agent> <prompt>
6
+ * atomic workflow --list
7
+ */
8
+
9
+ import { AGENT_CONFIG, type AgentKey } from "@/services/config/index.ts";
10
+ import { COLORS } from "@/theme/colors.ts";
11
+ import { isCommandInstalled } from "@/services/system/detect.ts";
12
+ import { ensureTmuxInstalled, ensureBunInstalled } from "../../lib/spawn.ts";
13
+ import {
14
+ isTmuxInstalled,
15
+ discoverWorkflows,
16
+ findWorkflow,
17
+ executeWorkflow,
18
+ WorkflowLoader,
19
+ resetMuxBinaryCache,
20
+ } from "@/sdk/workflows.ts";
21
+ import type { AgentType } from "@/sdk/workflows.ts";
22
+
23
+ export async function workflowCommand(options: {
24
+ name?: string;
25
+ agent?: string;
26
+ prompt?: string;
27
+ list?: boolean;
28
+ }): Promise<number> {
29
+ // List mode
30
+ if (options.list) {
31
+ const workflows = await discoverWorkflows(undefined, options.agent as AgentType | undefined);
32
+
33
+ if (workflows.length === 0) {
34
+ console.log("No workflows found.");
35
+ console.log("Create a workflow in .atomic/workflows/<name>/<agent>/index.ts");
36
+ return 0;
37
+ }
38
+
39
+ console.log("Available workflows:\n");
40
+ for (const wf of workflows) {
41
+ const badge = wf.source === "local" ? "(local)" : "(global)";
42
+ console.log(` ${wf.agent}/${wf.name} ${COLORS.dim}${badge}${COLORS.reset}`);
43
+ console.log(` ${COLORS.dim}${wf.path}${COLORS.reset}`);
44
+ }
45
+ return 0;
46
+ }
47
+
48
+ // Run mode — validate inputs
49
+ if (!options.name) {
50
+ console.error(`${COLORS.red}Error: Missing workflow name. Use -n <name>.${COLORS.reset}`);
51
+ return 1;
52
+ }
53
+
54
+ if (!options.agent) {
55
+ console.error(`${COLORS.red}Error: Missing agent. Use -a <agent>.${COLORS.reset}`);
56
+ return 1;
57
+ }
58
+
59
+ const validAgents = Object.keys(AGENT_CONFIG);
60
+ if (!validAgents.includes(options.agent)) {
61
+ console.error(`${COLORS.red}Error: Unknown agent '${options.agent}'.${COLORS.reset}`);
62
+ console.error(`Valid agents: ${validAgents.join(", ")}`);
63
+ return 1;
64
+ }
65
+
66
+ const agent = options.agent as AgentKey;
67
+
68
+ // Check agent CLI is installed
69
+ if (!isCommandInstalled(AGENT_CONFIG[agent].cmd)) {
70
+ console.error(`${COLORS.red}Error: '${AGENT_CONFIG[agent].cmd}' is not installed.${COLORS.reset}`);
71
+ console.error(`Install it from: ${AGENT_CONFIG[agent].install_url}`);
72
+ return 1;
73
+ }
74
+
75
+ // Ensure tmux/psmux is installed
76
+ if (!isTmuxInstalled()) {
77
+ console.log("Terminal multiplexer not found. Installing...");
78
+ try {
79
+ await ensureTmuxInstalled();
80
+ resetMuxBinaryCache();
81
+ } catch {
82
+ // Installation attempt failed — fall through to check below
83
+ }
84
+ if (!isTmuxInstalled()) {
85
+ const isWin = process.platform === "win32";
86
+ console.error(`${COLORS.red}Error: ${isWin ? "psmux" : "tmux"} is not installed.${COLORS.reset}`);
87
+ console.error(
88
+ isWin
89
+ ? "Install psmux: https://github.com/psmux/psmux#installation"
90
+ : "Install tmux: https://github.com/tmux/tmux/wiki/Installing",
91
+ );
92
+ return 1;
93
+ }
94
+ }
95
+
96
+ // Ensure bun is installed (required for workflow execution)
97
+ if (!Bun.which("bun")) {
98
+ console.log("Bun runtime not found. Installing...");
99
+ try {
100
+ await ensureBunInstalled();
101
+ } catch {
102
+ // Installation attempt failed — fall through to check below
103
+ }
104
+ if (!Bun.which("bun")) {
105
+ console.error(`${COLORS.red}Error: bun is not installed.${COLORS.reset}`);
106
+ console.error("Install bun: https://bun.sh");
107
+ return 1;
108
+ }
109
+ }
110
+
111
+ // Find the workflow
112
+ const discovered = await findWorkflow(options.name, agent);
113
+
114
+ if (!discovered) {
115
+ console.error(`${COLORS.red}Error: Workflow '${options.name}' not found for agent '${agent}'.${COLORS.reset}`);
116
+ console.error(`\nExpected location:`);
117
+ console.error(` .atomic/workflows/${options.name}/${agent}/index.ts ${COLORS.dim}(local)${COLORS.reset}`);
118
+ console.error(` ~/.atomic/workflows/${options.name}/${agent}/index.ts ${COLORS.dim}(global)${COLORS.reset}`);
119
+
120
+ const available = await discoverWorkflows(undefined, agent);
121
+ if (available.length > 0) {
122
+ console.error(`\nAvailable ${agent} workflows:`);
123
+ for (const wf of available) {
124
+ console.error(` ${COLORS.dim}•${COLORS.reset} ${wf.name} ${COLORS.dim}(${wf.source})${COLORS.reset}`);
125
+ }
126
+ }
127
+
128
+ return 1;
129
+ }
130
+
131
+ // Load workflow through the pipeline: resolve → validate → load.
132
+ // The loader registers a Bun resolver plugin that maps `atomic/*` and
133
+ // atomic's installed deps onto the running CLI's own module graph, so
134
+ // workflow files don't need their own `package.json` / `node_modules`.
135
+ const result = await WorkflowLoader.loadWorkflow(discovered, {
136
+ warn(warnings) {
137
+ for (const w of warnings) {
138
+ console.warn(`⚠ [${w.rule}] ${w.message}`);
139
+ }
140
+ },
141
+ error(stage, _error, message) {
142
+ console.error(`${COLORS.red}Error (${stage}): ${message}${COLORS.reset}`);
143
+ },
144
+ });
145
+
146
+ if (!result.ok) {
147
+ return 1;
148
+ }
149
+
150
+ // Execute
151
+ try {
152
+ await executeWorkflow({
153
+ definition: result.value.definition,
154
+ agent,
155
+ prompt: options.prompt ?? "",
156
+ workflowFile: discovered.path,
157
+ });
158
+ return 0;
159
+ } catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ console.error(`${COLORS.red}Workflow failed: ${message}${COLORS.reset}`);
162
+ return 1;
163
+ }
164
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Utilities for merging JSON configuration files
3
+ */
4
+
5
+ import { readFile, writeFile } from "fs/promises";
6
+ import { resolve } from "path";
7
+
8
+ interface McpConfig {
9
+ mcpServers?: Record<string, unknown>;
10
+ servers?: Record<string, unknown>;
11
+ lspServers?: Record<string, unknown>;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ function mergeNamedObjectMap(
16
+ destination: Record<string, unknown> | undefined,
17
+ source: Record<string, unknown> | undefined,
18
+ ): Record<string, unknown> | undefined {
19
+ if (!destination && !source) {
20
+ return undefined;
21
+ }
22
+
23
+ return {
24
+ ...destination,
25
+ ...source,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Merge source JSON file into destination JSON file
31
+ * - Preserves all existing keys in destination
32
+ * - Adds/updates keys from source
33
+ * - For MCP server maps: preserves user's servers, adds/updates CLI-managed servers
34
+ *
35
+ * @param srcPath Path to source JSON file
36
+ * @param destPath Path to destination JSON file (will be modified in place)
37
+ */
38
+ export async function mergeJsonFile(
39
+ srcPath: string,
40
+ destPath: string
41
+ ): Promise<void> {
42
+ if (resolve(srcPath) === resolve(destPath)) {
43
+ return;
44
+ }
45
+
46
+ const [srcContent, destContent] = await Promise.all([
47
+ readFile(srcPath, "utf-8"),
48
+ readFile(destPath, "utf-8"),
49
+ ]);
50
+
51
+ const srcConfig: McpConfig = JSON.parse(srcContent);
52
+ const destConfig: McpConfig = JSON.parse(destContent);
53
+
54
+ // Merge top-level config - preserve destination's other keys
55
+ const mergedConfig: McpConfig = {
56
+ ...destConfig,
57
+ ...srcConfig,
58
+ };
59
+
60
+ mergedConfig.mcpServers = mergeNamedObjectMap(destConfig.mcpServers, srcConfig.mcpServers);
61
+ mergedConfig.servers = mergeNamedObjectMap(destConfig.servers, srcConfig.servers);
62
+ mergedConfig.lspServers = mergeNamedObjectMap(destConfig.lspServers, srcConfig.lspServers);
63
+
64
+ await writeFile(destPath, JSON.stringify(mergedConfig, null, 2) + "\n");
65
+ }
@@ -0,0 +1,38 @@
1
+ import { realpath } from "fs/promises";
2
+ import { isAbsolute, relative, resolve } from "path";
3
+
4
+ function isSubPath(rootPath: string, targetPath: string): boolean {
5
+ const rel = relative(rootPath, targetPath);
6
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
7
+ }
8
+
9
+ export function isPathWithinRoot(rootPath: string, candidatePath: string): boolean {
10
+ return isSubPath(resolve(rootPath), resolve(candidatePath));
11
+ }
12
+
13
+ export function assertPathWithinRoot(
14
+ rootPath: string,
15
+ candidatePath: string,
16
+ label: string,
17
+ ): void {
18
+ if (!isPathWithinRoot(rootPath, candidatePath)) {
19
+ throw new Error(`${label} escapes allowed root: ${candidatePath}`);
20
+ }
21
+ }
22
+
23
+ export async function assertRealPathWithinRoot(
24
+ rootPath: string,
25
+ candidatePath: string,
26
+ label: string,
27
+ ): Promise<string> {
28
+ const [resolvedRootPath, resolvedCandidatePath] = await Promise.all([
29
+ realpath(rootPath),
30
+ realpath(candidatePath),
31
+ ]);
32
+
33
+ if (!isSubPath(resolvedRootPath, resolvedCandidatePath)) {
34
+ throw new Error(`${label} resolves outside allowed root: ${candidatePath}`);
35
+ }
36
+
37
+ return resolvedCandidatePath;
38
+ }