@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,52 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Atomic Settings",
4
+ "description": "Configuration file for the Atomic CLI (@bastani/atomic). Stored at ~/.atomic/settings.json (global) or .atomic/settings.json (project-local).",
5
+ "type": "object",
6
+ "properties": {
7
+ "$schema": {
8
+ "type": "string",
9
+ "description": "JSON Schema URL for editor intellisense."
10
+ },
11
+ "version": {
12
+ "type": "number",
13
+ "description": "Config schema version."
14
+ },
15
+ "scm": {
16
+ "type": "string",
17
+ "enum": ["github", "sapling"],
18
+ "description": "Selected source control management system."
19
+ },
20
+ "lastUpdated": {
21
+ "type": "string",
22
+ "format": "date-time",
23
+ "description": "ISO 8601 timestamp of the last configuration update."
24
+ },
25
+ "prerelease": {
26
+ "type": "boolean",
27
+ "description": "When true, 'atomic update' fetches the latest prerelease instead of the latest stable release. Set automatically by the installer when using the --prerelease flag.",
28
+ "default": false
29
+ },
30
+ "trustedPaths": {
31
+ "type": "array",
32
+ "description": "Globally trusted workspaces that have completed provider onboarding through 'atomic init'.",
33
+ "items": {
34
+ "type": "object",
35
+ "required": ["workspacePath", "provider"],
36
+ "properties": {
37
+ "workspacePath": {
38
+ "type": "string",
39
+ "description": "Absolute path to the initialized workspace."
40
+ },
41
+ "provider": {
42
+ "type": "string",
43
+ "enum": ["claude", "opencode", "copilot"],
44
+ "description": "Provider initialized for the trusted workspace."
45
+ }
46
+ },
47
+ "additionalProperties": false
48
+ }
49
+ }
50
+ },
51
+ "additionalProperties": false
52
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@bastani/atomic",
3
+ "version": "0.5.0-1",
4
+ "description": "Configuration management CLI and SDK for coding agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/flora131/atomic.git"
10
+ },
11
+ "keywords": [
12
+ "cli",
13
+ "sdk",
14
+ "coding-agents",
15
+ "agents",
16
+ "ai-engineering",
17
+ "harness-engineering",
18
+ "claude-code",
19
+ "copilot-cli",
20
+ "opencode"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "bin": {
26
+ "atomic": "src/cli.ts"
27
+ },
28
+ "exports": {
29
+ ".": "./src/sdk/index.ts",
30
+ "./workflows": "./src/sdk/workflows.ts"
31
+ },
32
+ "files": [
33
+ "src",
34
+ "assets/settings.schema.json"
35
+ ],
36
+ "scripts": {
37
+ "dev": "bun run src/cli.ts",
38
+ "build": "bun run build.ts",
39
+ "test": "bun test ./src ./tests",
40
+ "test:coverage": "bun test --coverage ./src ./tests",
41
+ "typecheck": "bunx tsc --noEmit",
42
+ "lint": "oxlint --config=oxlint.json src",
43
+ "lint:fix": "oxlint --config=oxlint.json --fix src",
44
+ "prepare": "lefthook install || true"
45
+ },
46
+ "devDependencies": {
47
+ "@types/bun": "^1.3.11",
48
+ "@types/react": "^19.2.14",
49
+ "lefthook": "^2.1.4",
50
+ "oxlint": "^1.59.0",
51
+ "typescript": "^6.0.2",
52
+ "typescript-language-server": "^5.1.3"
53
+ },
54
+ "dependencies": {
55
+ "@anthropic-ai/claude-agent-sdk": "^0.2.92",
56
+ "@clack/prompts": "^1.2.0",
57
+ "@commander-js/extra-typings": "^14.0.0",
58
+ "@github/copilot-sdk": "^0.2.1",
59
+ "@opencode-ai/sdk": "^1.3.17",
60
+ "@opentui/core": "^0.1.97",
61
+ "@opentui/react": "^0.1.97",
62
+ "commander": "^14.0.3",
63
+ "ignore": "^7.0.5",
64
+ "react": "19",
65
+ "yaml": "^2.8.3",
66
+ "zod": "^4.3.6"
67
+ }
68
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Atomic CLI - Configuration management for coding agents
4
+ *
5
+ * Built with Commander.js for robust argument parsing and type-safe options.
6
+ *
7
+ * Usage:
8
+ * atomic Interactive setup (same as 'atomic init')
9
+ * atomic init Interactive setup with agent selection
10
+ * atomic init -a <agent> Setup specific agent (skip selection)
11
+ * atomic init -s <scm> Setup specific SCM (github, sapling)
12
+ * atomic chat -a <agent> Start interactive chat with an agent
13
+ * atomic config set <key> <value> Set configuration value
14
+ * atomic --version Show version
15
+ * atomic --help Show help
16
+ */
17
+
18
+ import { Command } from "@commander-js/extra-typings";
19
+ import { VERSION } from "@/version.ts";
20
+ import { COLORS } from "@/theme/colors.ts";
21
+ import { AGENT_CONFIG, type AgentKey, SCM_CONFIG, type SourceControlType } from "@/services/config/index.ts";
22
+
23
+ /**
24
+ * Create and configure the main CLI program
25
+ */
26
+ export function createProgram() {
27
+ const program = new Command()
28
+ .name("atomic")
29
+ .description("Configuration management CLI for coding agents")
30
+ .version(VERSION, "-v, --version", "Show version number")
31
+
32
+ // Global options available to all commands
33
+ .option("-f, --force", "Overwrite all config files")
34
+ .option("-y, --yes", "Auto-confirm all prompts (non-interactive mode)")
35
+ .option("--no-banner", "Skip ASCII banner display")
36
+
37
+ // Configure error output with colors
38
+ .configureOutput({
39
+ writeErr: (str) => {
40
+ process.stderr.write(`${COLORS.red}${str}${COLORS.reset}`);
41
+ },
42
+ outputError: (str, write) => {
43
+ write(`${COLORS.red}${str}${COLORS.reset}`);
44
+ },
45
+ });
46
+
47
+ // Build agent choices string for help text
48
+ const agentChoices = Object.keys(AGENT_CONFIG).join(", ");
49
+ const scmChoices = Object.keys(SCM_CONFIG).join(", ");
50
+
51
+ // Add init command
52
+ program
53
+ .command("init")
54
+ .description("Interactive setup with agent selection")
55
+ .option(
56
+ "-a, --agent <name>",
57
+ `Pre-select agent to configure (${agentChoices})`,
58
+ )
59
+ .option(
60
+ "-s, --scm <name>",
61
+ `Pre-select source control system (${scmChoices})`,
62
+ )
63
+ .action(async (localOpts) => {
64
+ const globalOpts = program.opts();
65
+ const { initCommand } = await import("@/commands/cli/init.ts");
66
+
67
+ await initCommand({
68
+ showBanner: globalOpts.banner !== false,
69
+ preSelectedAgent: localOpts.agent as AgentKey | undefined,
70
+ preSelectedScm: localOpts.scm as SourceControlType | undefined,
71
+ force: globalOpts.force,
72
+ yes: globalOpts.yes,
73
+ });
74
+ });
75
+
76
+ // Add chat command (default command when no subcommand is provided)
77
+ program
78
+ .command("chat", { isDefault: true })
79
+ .description("Start an interactive chat session with a coding agent")
80
+ .option("-a, --agent <name>", `Agent to chat with (${agentChoices})`)
81
+ .allowUnknownOption()
82
+ .allowExcessArguments(true)
83
+ .addHelpText(
84
+ "after",
85
+ `
86
+ All arguments after -a <agent> are forwarded to the native agent CLI.
87
+
88
+ Examples:
89
+ $ atomic chat -a claude Start Claude interactively
90
+ $ atomic chat -a copilot Start Copilot interactively
91
+ $ atomic chat -a opencode Start OpenCode interactively
92
+ $ atomic chat -a claude "fix the bug" Claude with initial prompt
93
+ $ atomic chat -a copilot --model gpt-4o Copilot with custom model
94
+ $ atomic chat -a claude --verbose Forward --verbose to claude`,
95
+ )
96
+ .action(async (localOpts, cmd) => {
97
+ const validAgents = Object.keys(AGENT_CONFIG);
98
+ const agentType = localOpts.agent;
99
+
100
+ if (!agentType) {
101
+ console.error(
102
+ `${COLORS.red}Error: Missing agent.${COLORS.reset}`,
103
+ );
104
+ console.error(
105
+ "Start chat with an explicit provider, for example: atomic chat -a claude",
106
+ );
107
+ process.exit(1);
108
+ }
109
+
110
+ // Validate agent choice
111
+ if (!validAgents.includes(agentType)) {
112
+ console.error(
113
+ `${COLORS.red}Error: Unknown agent '${agentType}'${COLORS.reset}`,
114
+ );
115
+ console.error(`Valid agents: ${agentChoices}`);
116
+ process.exit(1);
117
+ }
118
+
119
+ // Collect extra args/options to forward to the native CLI
120
+ const passthroughArgs = cmd.args;
121
+
122
+ const { chatCommand } = await import("@/commands/cli/chat.ts");
123
+ const exitCode = await chatCommand({
124
+ agentType: agentType as "claude" | "opencode" | "copilot",
125
+ passthroughArgs,
126
+ });
127
+
128
+ process.exit(exitCode);
129
+ });
130
+
131
+ // Add workflow command
132
+ program
133
+ .command("workflow")
134
+ .description("Run a multi-session agent workflow")
135
+ .option("-n, --name <name>", "Workflow name (matches directory under .atomic/workflows/<name>/)")
136
+ .option("-a, --agent <name>", `Agent to use (${agentChoices})`)
137
+ .option("-l, --list", "List available workflows")
138
+ .argument("[prompt...]", "Prompt for the workflow")
139
+ .action(async (promptParts, localOpts) => {
140
+ const { workflowCommand } = await import("@/commands/cli/workflow.ts");
141
+ const exitCode = await workflowCommand({
142
+ name: localOpts.name,
143
+ agent: localOpts.agent,
144
+ prompt: promptParts.length > 0 ? promptParts.join(" ") : undefined,
145
+ list: localOpts.list,
146
+ });
147
+ process.exit(exitCode);
148
+ });
149
+
150
+ program
151
+ .command("update")
152
+ .description("Update atomic to the latest version and reinstall skills")
153
+ .action(async () => {
154
+ const { updateCommand } = await import("@/commands/cli/update.ts");
155
+ process.exit(await updateCommand());
156
+ });
157
+
158
+ // Add config command for managing CLI settings
159
+ const configCmd = program
160
+ .command("config")
161
+ .description("Manage atomic configuration");
162
+
163
+ // Add 'set' subcommand to config
164
+ configCmd
165
+ .command("set")
166
+ .description("Set a configuration value")
167
+ .argument("<key>", "Configuration key (e.g., telemetry)")
168
+ .argument("<value>", "Value to set (e.g., true, false)")
169
+ .action(async (key: string, value: string) => {
170
+ const { configCommand } = await import("@/commands/cli/config.ts");
171
+ await configCommand("set", key, value);
172
+ });
173
+
174
+ return program;
175
+ }
176
+
177
+ // Create the program instance for use by main() and tests
178
+ export const program = createProgram();
179
+
180
+ /**
181
+ * Main entry point for the CLI
182
+ */
183
+ async function main(): Promise<void> {
184
+ try {
185
+ // Parse and execute the command
186
+ await program.parseAsync();
187
+ } catch (error) {
188
+ const message = error instanceof Error ? error.message : String(error);
189
+ console.error(`${COLORS.red}Error: ${message}${COLORS.reset}`);
190
+ process.exit(1);
191
+ }
192
+ }
193
+
194
+ // Run the CLI
195
+ if (import.meta.main) {
196
+ await main();
197
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Chat client utilities
3
+ *
4
+ * Provides agent display name resolution for the chat command.
5
+ * SDK client creation has been removed — the chat command now spawns
6
+ * native agent CLIs directly.
7
+ */
8
+
9
+ import type { AgentKey } from "@/services/config/index.ts";
10
+
11
+ export function getAgentDisplayName(agentType: AgentKey): string {
12
+ const names: Record<AgentKey, string> = {
13
+ claude: "Claude",
14
+ opencode: "OpenCode",
15
+ copilot: "Copilot",
16
+ };
17
+ return names[agentType] ?? agentType;
18
+ }
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Chat CLI command for atomic
4
+ *
5
+ * Spawns the native agent CLI as an interactive subprocess.
6
+ * When already inside a tmux/psmux session, the agent spawns inline
7
+ * in the current pane. When outside tmux, it creates a new tmux
8
+ * session and attaches to it.
9
+ *
10
+ * All extra arguments after `-a <agent>` are forwarded to the native CLI.
11
+ *
12
+ * Usage:
13
+ * atomic chat -a <agent> [native-args...]
14
+ */
15
+
16
+ import { join } from "path";
17
+ import { homedir } from "os";
18
+ import { mkdir, writeFile, rm } from "fs/promises";
19
+ import { AGENT_CONFIG, type AgentKey } from "@/services/config/index.ts";
20
+ import { COLORS } from "@/theme/colors.ts";
21
+ import { isCommandInstalled } from "@/services/system/detect.ts";
22
+ import {
23
+ ensureAtomicGlobalAgentConfigs,
24
+ } from "@/services/config/atomic-global-config.ts";
25
+ import { getConfigRoot } from "@/services/config/config-path.ts";
26
+ import {
27
+ isInsideTmux,
28
+ isTmuxInstalled,
29
+ resetMuxBinaryCache,
30
+ } from "@/sdk/workflows.ts";
31
+ import {
32
+ createSession,
33
+ killSession,
34
+ getMuxBinary,
35
+ } from "@/sdk/workflows.ts";
36
+ import { ensureTmuxInstalled } from "@/lib/spawn.ts";
37
+
38
+ // ============================================================================
39
+ // Types
40
+ // ============================================================================
41
+
42
+ export type AgentType = AgentKey;
43
+
44
+ /**
45
+ * Options for the chat command.
46
+ */
47
+ export interface ChatCommandOptions {
48
+ /** Agent type to use (claude, opencode, copilot) */
49
+ agentType?: AgentType;
50
+ /** Extra args/options forwarded verbatim to the native agent CLI */
51
+ passthroughArgs?: string[];
52
+ }
53
+
54
+ // ============================================================================
55
+ // Helpers
56
+ // ============================================================================
57
+
58
+ export function getAgentDisplayName(agentType: AgentType): string {
59
+ return AGENT_CONFIG[agentType].name;
60
+ }
61
+
62
+ /**
63
+ * Build the argv array for spawning the agent CLI.
64
+ *
65
+ * Starts with the agent's default chat_flags, then appends any
66
+ * extra args the user passed after `-a <agent>`.
67
+ */
68
+ export function buildAgentArgs(agentType: AgentType, passthroughArgs: string[] = []): string[] {
69
+ const config = AGENT_CONFIG[agentType];
70
+ return [...config.chat_flags, ...passthroughArgs];
71
+ }
72
+
73
+ function generateChatId(): string {
74
+ return crypto.randomUUID().slice(0, 8);
75
+ }
76
+
77
+ /** Escape a string for safe interpolation inside a bash double-quoted string. */
78
+ function escBash(s: string): string {
79
+ return s.replace(/[\\"$`!]/g, "\\$&");
80
+ }
81
+
82
+ /** Escape a string for safe interpolation inside a PowerShell double-quoted string. */
83
+ function escPwsh(s: string): string {
84
+ return s.replace(/[`"$]/g, "`$&");
85
+ }
86
+
87
+ /**
88
+ * Build a launcher script that preserves cwd and properly quotes args.
89
+ * This avoids shell-injection risks from passthrough args.
90
+ */
91
+ function buildLauncherScript(
92
+ cmd: string,
93
+ args: string[],
94
+ projectRoot: string,
95
+ ): { script: string; ext: string } {
96
+ const isWin = process.platform === "win32";
97
+
98
+ if (isWin) {
99
+ // PowerShell: use array splatting for safe arg passing
100
+ const argList = args.map((a) => `"${escPwsh(a)}"`).join(", ");
101
+ const script = [
102
+ `Set-Location "${escPwsh(projectRoot)}"`,
103
+ argList.length > 0
104
+ ? `& "${escPwsh(cmd)}" @(${argList})`
105
+ : `& "${escPwsh(cmd)}"`,
106
+ ].join("\n");
107
+ return { script, ext: "ps1" };
108
+ }
109
+
110
+ // Bash: use proper quoting for each arg
111
+ const quotedArgs = args
112
+ .map((a) => `"${escBash(a)}"`)
113
+ .join(" ");
114
+ const script = [
115
+ "#!/bin/bash",
116
+ `cd "${escBash(projectRoot)}"`,
117
+ `exec "${escBash(cmd)}" ${quotedArgs}`,
118
+ ].join("\n");
119
+ return { script, ext: "sh" };
120
+ }
121
+
122
+ // ============================================================================
123
+ // Chat Command Implementation
124
+ // ============================================================================
125
+
126
+ /**
127
+ * Spawn the native agent CLI as an interactive subprocess.
128
+ *
129
+ * When running inside a tmux/psmux session, the agent spawns inline
130
+ * in the current pane with inherited stdio.
131
+ *
132
+ * When running outside tmux, a new tmux session is created and
133
+ * attached so the agent benefits from multiplexer features.
134
+ *
135
+ * @param options - Chat command configuration options
136
+ * @returns Exit code from the agent process
137
+ */
138
+ export async function chatCommand(options: ChatCommandOptions = {}): Promise<number> {
139
+ const { agentType, passthroughArgs } = options;
140
+
141
+ if (!agentType) {
142
+ throw new Error("agentType is required. Start chat with `atomic chat -a <agent>`.");
143
+ }
144
+
145
+ const config = AGENT_CONFIG[agentType];
146
+
147
+ // Check the agent CLI is installed
148
+ if (!isCommandInstalled(config.cmd)) {
149
+ console.error(
150
+ `${COLORS.red}Error: '${config.cmd}' is not installed or not in PATH.${COLORS.reset}`
151
+ );
152
+ console.error(`Install it from: ${config.install_url}`);
153
+ return 1;
154
+ }
155
+
156
+ // ── Preflight: global config sync ──
157
+ const projectRoot = process.cwd();
158
+ const configRoot = getConfigRoot();
159
+
160
+ await ensureAtomicGlobalAgentConfigs(configRoot);
161
+
162
+ // ── Build argv ──
163
+ const args = buildAgentArgs(agentType, passthroughArgs);
164
+ const cmd = [config.cmd, ...args];
165
+
166
+ // ── Inside tmux: spawn inline in the current pane ──
167
+ if (isInsideTmux()) {
168
+ return spawnDirect(cmd, projectRoot);
169
+ }
170
+
171
+ // ── No TTY: tmux attach requires a real terminal ──
172
+ if (!process.stdin.isTTY) {
173
+ return spawnDirect(cmd, projectRoot);
174
+ }
175
+
176
+ // ── Ensure tmux is available ──
177
+ if (!isTmuxInstalled()) {
178
+ console.log("Terminal multiplexer not found. Installing...");
179
+ try {
180
+ await ensureTmuxInstalled();
181
+ resetMuxBinaryCache();
182
+ } catch {
183
+ // Fall through to check below
184
+ }
185
+ if (!isTmuxInstalled()) {
186
+ // No tmux available — fall back to direct spawn
187
+ return spawnDirect(cmd, projectRoot);
188
+ }
189
+ }
190
+
191
+ // ── Build launcher script for safe arg/cwd handling ──
192
+ const chatId = generateChatId();
193
+ const windowName = `atomic-chat-${chatId}`;
194
+
195
+ const sessionsDir = join(homedir(), ".atomic", "sessions", "chat");
196
+ await mkdir(sessionsDir, { recursive: true });
197
+ const { script, ext } = buildLauncherScript(config.cmd, args, projectRoot);
198
+ const launcherPath = join(sessionsDir, `${windowName}.${ext}`);
199
+ await writeFile(launcherPath, script, { mode: 0o755 });
200
+
201
+ const shellCmd = process.platform === "win32"
202
+ ? `pwsh -NoProfile -File "${launcherPath}"`
203
+ : `bash "${launcherPath}"`;
204
+
205
+ // ── Outside tmux: create a new session and attach ──
206
+ try {
207
+ createSession(windowName, shellCmd, undefined, projectRoot);
208
+
209
+ const muxBinary = getMuxBinary() ?? "tmux";
210
+ const attachProc = Bun.spawn([muxBinary, "attach-session", "-t", windowName], {
211
+ stdio: ["inherit", "inherit", "inherit"],
212
+ });
213
+ const exitCode = await attachProc.exited;
214
+
215
+ // Clean up launcher
216
+ try { await rm(launcherPath, { force: true }); } catch {}
217
+
218
+ // If tmux attach itself failed (e.g. lost TTY), clean up and fall back
219
+ if (exitCode !== 0) {
220
+ try { killSession(windowName); } catch {}
221
+ return spawnDirect(cmd, projectRoot);
222
+ }
223
+
224
+ return exitCode;
225
+ } catch (error) {
226
+ try { await rm(launcherPath, { force: true }); } catch {}
227
+ const message = error instanceof Error ? error.message : String(error);
228
+ console.error(
229
+ `${COLORS.yellow}Warning: Failed to create tmux session (${message}). Falling back to direct spawn.${COLORS.reset}`
230
+ );
231
+ return spawnDirect(cmd, projectRoot);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Spawn the agent CLI directly with inherited stdio.
237
+ * Used when not inside tmux.
238
+ */
239
+ async function spawnDirect(cmd: string[], projectRoot: string): Promise<number> {
240
+ const proc = Bun.spawn(cmd, {
241
+ stdio: ["inherit", "inherit", "inherit"],
242
+ cwd: projectRoot,
243
+ env: { ...process.env },
244
+ });
245
+
246
+ return await proc.exited;
247
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Compatibility barrel for the chat CLI command.
4
+ *
5
+ * The implementation lives under `commands/cli/chat/`.
6
+ */
7
+
8
+ export * from "@/commands/cli/chat/index.ts";
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Config command - Manage Atomic CLI configuration
3
+ *
4
+ * Usage: atomic config set <key> <value>
5
+ *
6
+ * Currently supported:
7
+ * atomic config set telemetry true|false
8
+ */
9
+
10
+ import { log } from "@clack/prompts";
11
+ import { setTelemetryEnabled } from "@/services/config/settings.ts";
12
+
13
+ /**
14
+ * Execute the config command
15
+ */
16
+ export async function configCommand(
17
+ subcommand: string | undefined,
18
+ key: string | undefined,
19
+ value: string | undefined
20
+ ): Promise<void> {
21
+ if (!subcommand) {
22
+ log.error("Missing subcommand. Usage: atomic config set <key> <value>");
23
+ process.exit(1);
24
+ }
25
+
26
+ if (subcommand !== "set") {
27
+ log.error(`Unknown subcommand: ${subcommand}. Only 'set' is supported.`);
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!key) {
32
+ log.error("Missing key. Usage: atomic config set <key> <value>");
33
+ process.exit(1);
34
+ }
35
+
36
+ if (key !== "telemetry") {
37
+ log.error(`Unknown config key: ${key}. Only 'telemetry' is supported.`);
38
+ process.exit(1);
39
+ }
40
+
41
+ if (!value) {
42
+ log.error("Missing value. Usage: atomic config set telemetry <true|false>");
43
+ process.exit(1);
44
+ }
45
+
46
+ if (value !== "true" && value !== "false") {
47
+ log.error(`Invalid value: ${value}. Must be 'true' or 'false'.`);
48
+ process.exit(1);
49
+ }
50
+
51
+ const enabled = value === "true";
52
+ setTelemetryEnabled(enabled);
53
+
54
+ log.success(`Telemetry has been ${enabled ? "enabled" : "disabled"}.`);
55
+ }