@astroanywhere/agent 0.1.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 (203) hide show
  1. package/LICENSE +76 -0
  2. package/README.md +178 -0
  3. package/dist/cli.d.ts +15 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +401 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/index.d.ts +9 -0
  8. package/dist/commands/index.d.ts.map +1 -0
  9. package/dist/commands/index.js +9 -0
  10. package/dist/commands/index.js.map +1 -0
  11. package/dist/commands/mcp.d.ts +16 -0
  12. package/dist/commands/mcp.d.ts.map +1 -0
  13. package/dist/commands/mcp.js +19 -0
  14. package/dist/commands/mcp.js.map +1 -0
  15. package/dist/commands/setup.d.ts +20 -0
  16. package/dist/commands/setup.d.ts.map +1 -0
  17. package/dist/commands/setup.js +585 -0
  18. package/dist/commands/setup.js.map +1 -0
  19. package/dist/commands/start.d.ts +16 -0
  20. package/dist/commands/start.d.ts.map +1 -0
  21. package/dist/commands/start.js +638 -0
  22. package/dist/commands/start.js.map +1 -0
  23. package/dist/commands/status.d.ts +5 -0
  24. package/dist/commands/status.d.ts.map +1 -0
  25. package/dist/commands/status.js +63 -0
  26. package/dist/commands/status.js.map +1 -0
  27. package/dist/commands/stop.d.ts +5 -0
  28. package/dist/commands/stop.d.ts.map +1 -0
  29. package/dist/commands/stop.js +85 -0
  30. package/dist/commands/stop.js.map +1 -0
  31. package/dist/execution/direct-strategy.d.ts +18 -0
  32. package/dist/execution/direct-strategy.d.ts.map +1 -0
  33. package/dist/execution/direct-strategy.js +156 -0
  34. package/dist/execution/direct-strategy.js.map +1 -0
  35. package/dist/execution/docker-strategy.d.ts +26 -0
  36. package/dist/execution/docker-strategy.d.ts.map +1 -0
  37. package/dist/execution/docker-strategy.js +222 -0
  38. package/dist/execution/docker-strategy.js.map +1 -0
  39. package/dist/execution/index.d.ts +14 -0
  40. package/dist/execution/index.d.ts.map +1 -0
  41. package/dist/execution/index.js +13 -0
  42. package/dist/execution/index.js.map +1 -0
  43. package/dist/execution/kubernetes-exec-strategy.d.ts +23 -0
  44. package/dist/execution/kubernetes-exec-strategy.d.ts.map +1 -0
  45. package/dist/execution/kubernetes-exec-strategy.js +232 -0
  46. package/dist/execution/kubernetes-exec-strategy.js.map +1 -0
  47. package/dist/execution/registry.d.ts +41 -0
  48. package/dist/execution/registry.d.ts.map +1 -0
  49. package/dist/execution/registry.js +84 -0
  50. package/dist/execution/registry.js.map +1 -0
  51. package/dist/execution/slurm-strategy.d.ts +22 -0
  52. package/dist/execution/slurm-strategy.d.ts.map +1 -0
  53. package/dist/execution/slurm-strategy.js +219 -0
  54. package/dist/execution/slurm-strategy.js.map +1 -0
  55. package/dist/execution/types.d.ts +72 -0
  56. package/dist/execution/types.d.ts.map +1 -0
  57. package/dist/execution/types.js +10 -0
  58. package/dist/execution/types.js.map +1 -0
  59. package/dist/index.d.ts +22 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +22 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/lib/api-client.d.ts +35 -0
  64. package/dist/lib/api-client.d.ts.map +1 -0
  65. package/dist/lib/api-client.js +126 -0
  66. package/dist/lib/api-client.js.map +1 -0
  67. package/dist/lib/config.d.ts +174 -0
  68. package/dist/lib/config.d.ts.map +1 -0
  69. package/dist/lib/config.js +399 -0
  70. package/dist/lib/config.js.map +1 -0
  71. package/dist/lib/copy-worktree.d.ts +73 -0
  72. package/dist/lib/copy-worktree.d.ts.map +1 -0
  73. package/dist/lib/copy-worktree.js +374 -0
  74. package/dist/lib/copy-worktree.js.map +1 -0
  75. package/dist/lib/git-pr.d.ts +63 -0
  76. package/dist/lib/git-pr.d.ts.map +1 -0
  77. package/dist/lib/git-pr.js +224 -0
  78. package/dist/lib/git-pr.js.map +1 -0
  79. package/dist/lib/hardware-id.d.ts +25 -0
  80. package/dist/lib/hardware-id.d.ts.map +1 -0
  81. package/dist/lib/hardware-id.js +186 -0
  82. package/dist/lib/hardware-id.js.map +1 -0
  83. package/dist/lib/hpc-context.d.ts +35 -0
  84. package/dist/lib/hpc-context.d.ts.map +1 -0
  85. package/dist/lib/hpc-context.js +167 -0
  86. package/dist/lib/hpc-context.js.map +1 -0
  87. package/dist/lib/prompt-templates.d.ts +195 -0
  88. package/dist/lib/prompt-templates.d.ts.map +1 -0
  89. package/dist/lib/prompt-templates.js +353 -0
  90. package/dist/lib/prompt-templates.js.map +1 -0
  91. package/dist/lib/providers.d.ts +27 -0
  92. package/dist/lib/providers.d.ts.map +1 -0
  93. package/dist/lib/providers.js +372 -0
  94. package/dist/lib/providers.js.map +1 -0
  95. package/dist/lib/repo-context.d.ts +18 -0
  96. package/dist/lib/repo-context.d.ts.map +1 -0
  97. package/dist/lib/repo-context.js +61 -0
  98. package/dist/lib/repo-context.js.map +1 -0
  99. package/dist/lib/repo-utils.d.ts +35 -0
  100. package/dist/lib/repo-utils.d.ts.map +1 -0
  101. package/dist/lib/repo-utils.js +222 -0
  102. package/dist/lib/repo-utils.js.map +1 -0
  103. package/dist/lib/resources.d.ts +17 -0
  104. package/dist/lib/resources.d.ts.map +1 -0
  105. package/dist/lib/resources.js +227 -0
  106. package/dist/lib/resources.js.map +1 -0
  107. package/dist/lib/slurm-detect.d.ts +15 -0
  108. package/dist/lib/slurm-detect.d.ts.map +1 -0
  109. package/dist/lib/slurm-detect.js +148 -0
  110. package/dist/lib/slurm-detect.js.map +1 -0
  111. package/dist/lib/slurm-executor.d.ts +70 -0
  112. package/dist/lib/slurm-executor.d.ts.map +1 -0
  113. package/dist/lib/slurm-executor.js +402 -0
  114. package/dist/lib/slurm-executor.js.map +1 -0
  115. package/dist/lib/slurm-job-monitor.d.ts +52 -0
  116. package/dist/lib/slurm-job-monitor.d.ts.map +1 -0
  117. package/dist/lib/slurm-job-monitor.js +212 -0
  118. package/dist/lib/slurm-job-monitor.js.map +1 -0
  119. package/dist/lib/ssh-discovery.d.ts +17 -0
  120. package/dist/lib/ssh-discovery.d.ts.map +1 -0
  121. package/dist/lib/ssh-discovery.js +287 -0
  122. package/dist/lib/ssh-discovery.js.map +1 -0
  123. package/dist/lib/ssh-installer.d.ts +69 -0
  124. package/dist/lib/ssh-installer.d.ts.map +1 -0
  125. package/dist/lib/ssh-installer.js +230 -0
  126. package/dist/lib/ssh-installer.js.map +1 -0
  127. package/dist/lib/streaming-prompt.d.ts +48 -0
  128. package/dist/lib/streaming-prompt.d.ts.map +1 -0
  129. package/dist/lib/streaming-prompt.js +91 -0
  130. package/dist/lib/streaming-prompt.js.map +1 -0
  131. package/dist/lib/task-executor.d.ts +114 -0
  132. package/dist/lib/task-executor.d.ts.map +1 -0
  133. package/dist/lib/task-executor.js +753 -0
  134. package/dist/lib/task-executor.js.map +1 -0
  135. package/dist/lib/websocket-client.d.ts +200 -0
  136. package/dist/lib/websocket-client.d.ts.map +1 -0
  137. package/dist/lib/websocket-client.js +781 -0
  138. package/dist/lib/websocket-client.js.map +1 -0
  139. package/dist/lib/workdir-safety.d.ts +63 -0
  140. package/dist/lib/workdir-safety.d.ts.map +1 -0
  141. package/dist/lib/workdir-safety.js +247 -0
  142. package/dist/lib/workdir-safety.js.map +1 -0
  143. package/dist/lib/worktree-include.d.ts +14 -0
  144. package/dist/lib/worktree-include.d.ts.map +1 -0
  145. package/dist/lib/worktree-include.js +68 -0
  146. package/dist/lib/worktree-include.js.map +1 -0
  147. package/dist/lib/worktree-setup.d.ts +18 -0
  148. package/dist/lib/worktree-setup.d.ts.map +1 -0
  149. package/dist/lib/worktree-setup.js +60 -0
  150. package/dist/lib/worktree-setup.js.map +1 -0
  151. package/dist/lib/worktree.d.ts +37 -0
  152. package/dist/lib/worktree.d.ts.map +1 -0
  153. package/dist/lib/worktree.js +411 -0
  154. package/dist/lib/worktree.js.map +1 -0
  155. package/dist/mcp/index.d.ts +8 -0
  156. package/dist/mcp/index.d.ts.map +1 -0
  157. package/dist/mcp/index.js +8 -0
  158. package/dist/mcp/index.js.map +1 -0
  159. package/dist/mcp/server.d.ts +45 -0
  160. package/dist/mcp/server.d.ts.map +1 -0
  161. package/dist/mcp/server.js +153 -0
  162. package/dist/mcp/server.js.map +1 -0
  163. package/dist/mcp/session-bridge.d.ts +87 -0
  164. package/dist/mcp/session-bridge.d.ts.map +1 -0
  165. package/dist/mcp/session-bridge.js +317 -0
  166. package/dist/mcp/session-bridge.js.map +1 -0
  167. package/dist/mcp/tools.d.ts +70 -0
  168. package/dist/mcp/tools.d.ts.map +1 -0
  169. package/dist/mcp/tools.js +234 -0
  170. package/dist/mcp/tools.js.map +1 -0
  171. package/dist/mcp/types.d.ts +197 -0
  172. package/dist/mcp/types.d.ts.map +1 -0
  173. package/dist/mcp/types.js +16 -0
  174. package/dist/mcp/types.js.map +1 -0
  175. package/dist/providers/base-adapter.d.ts +56 -0
  176. package/dist/providers/base-adapter.d.ts.map +1 -0
  177. package/dist/providers/base-adapter.js +5 -0
  178. package/dist/providers/base-adapter.js.map +1 -0
  179. package/dist/providers/claude-code-adapter.d.ts +27 -0
  180. package/dist/providers/claude-code-adapter.d.ts.map +1 -0
  181. package/dist/providers/claude-code-adapter.js +298 -0
  182. package/dist/providers/claude-code-adapter.js.map +1 -0
  183. package/dist/providers/claude-sdk-adapter.d.ts +60 -0
  184. package/dist/providers/claude-sdk-adapter.d.ts.map +1 -0
  185. package/dist/providers/claude-sdk-adapter.js +632 -0
  186. package/dist/providers/claude-sdk-adapter.js.map +1 -0
  187. package/dist/providers/codex-adapter.d.ts +21 -0
  188. package/dist/providers/codex-adapter.d.ts.map +1 -0
  189. package/dist/providers/codex-adapter.js +197 -0
  190. package/dist/providers/codex-adapter.js.map +1 -0
  191. package/dist/providers/index.d.ts +26 -0
  192. package/dist/providers/index.d.ts.map +1 -0
  193. package/dist/providers/index.js +58 -0
  194. package/dist/providers/index.js.map +1 -0
  195. package/dist/providers/slurm-adapter.d.ts +26 -0
  196. package/dist/providers/slurm-adapter.d.ts.map +1 -0
  197. package/dist/providers/slurm-adapter.js +146 -0
  198. package/dist/providers/slurm-adapter.js.map +1 -0
  199. package/dist/types.d.ts +592 -0
  200. package/dist/types.d.ts.map +1 -0
  201. package/dist/types.js +5 -0
  202. package/dist/types.js.map +1 -0
  203. package/package.json +77 -0
@@ -0,0 +1,411 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { mkdir, readFile, appendFile, rm, copyFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { basename, isAbsolute, join, relative, resolve } from 'node:path';
7
+ import { applyWorktreeInclude } from './worktree-include.js';
8
+ import { runSetupScript } from './worktree-setup.js';
9
+ const execFileAsync = promisify(execFile);
10
+ export async function createWorktree(options) {
11
+ const { workingDirectory, taskId, rootOverride, projectId, nodeId, agentDir, stdout, stderr, } = options;
12
+ // Validate taskId format to prevent command injection
13
+ validateTaskId(taskId);
14
+ const resolvedWorkingDirectory = resolve(workingDirectory);
15
+ const gitRoot = await getGitRoot(resolvedWorkingDirectory);
16
+ // Require git repository to exist
17
+ // Safety checks in task-executor.ts ensure git is initialized before reaching here
18
+ if (!gitRoot) {
19
+ throw new Error(`Not a git repository: ${resolvedWorkingDirectory}. Initialize git first.`);
20
+ }
21
+ const hasHead = await hasCommits(gitRoot);
22
+ if (!hasHead) {
23
+ throw new Error(`Git repository has no commits: ${gitRoot}. Create an initial commit first.`);
24
+ }
25
+ const agentDirName = agentDir ?? '.astro';
26
+ const baseRoot = rootOverride ?? await resolveWorktreeRoot(gitRoot, agentDirName);
27
+ const branchPrefix = await readBranchPrefix(gitRoot, agentDirName);
28
+ const branchName = `${branchPrefix}${sanitize(taskId)}`;
29
+ const worktreePath = join(baseRoot, sanitize(taskId));
30
+ await rm(worktreePath, { recursive: true, force: true });
31
+ await pruneWorktrees(gitRoot);
32
+ // Find and remove any existing worktree that uses this branch (porcelain parsing).
33
+ // Without this, `git branch -D` fails because the branch is checked out
34
+ // by a lingering worktree from a previous execution.
35
+ await removeLingeringWorktrees(gitRoot, branchName);
36
+ await ensureBranchAvailable(gitRoot, branchName);
37
+ // Delete remote branch if it exists — prevents non-fast-forward push
38
+ // failures when re-executing a task whose previous branch was already pushed
39
+ await deleteRemoteBranch(gitRoot, branchName);
40
+ // Fetch latest so we branch from up-to-date origin
41
+ try {
42
+ await execFileAsync('git', ['-C', gitRoot, '-c', 'core.hooksPath=/dev/null', 'fetch', 'origin'], { env: withGitEnv(), timeout: 30_000 });
43
+ }
44
+ catch {
45
+ // Non-fatal: proceed with potentially stale refs
46
+ }
47
+ // Detect the default branch to use as start point
48
+ const defaultBranch = await getDefaultBranch(gitRoot);
49
+ // Create worktree from origin/<defaultBranch> — NOT from HEAD.
50
+ // Using HEAD is dangerous because it could be on a stale task branch
51
+ // (from a previous execution that fell back to the repo directly),
52
+ // which would cause cross-task commit contamination.
53
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'add', '-b', branchName, worktreePath, `origin/${defaultBranch}`], { env: withGitEnv(), timeout: 30_000 });
54
+ // Initialize submodules if the repo uses them (non-fatal)
55
+ try {
56
+ await initSubmodules(worktreePath, stderr);
57
+ }
58
+ catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ stderr?.(`submodule init failed: ${msg}`);
61
+ }
62
+ // Orchestration: include files + setup script (both non-fatal)
63
+ const log = stdout;
64
+ try {
65
+ await applyWorktreeInclude({ gitRoot, worktreePath, log });
66
+ }
67
+ catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ stderr?.(`worktree-include failed: ${msg}`);
70
+ }
71
+ try {
72
+ await runSetupScript({
73
+ gitRoot,
74
+ worktreePath,
75
+ taskId,
76
+ projectId,
77
+ nodeId,
78
+ stdout,
79
+ stderr,
80
+ });
81
+ }
82
+ catch (err) {
83
+ const msg = err instanceof Error ? err.message : String(err);
84
+ stderr?.(`setup script failed: ${msg}`);
85
+ }
86
+ // Copy CLAUDE.md if it exists in the git root but isn't tracked (non-fatal)
87
+ try {
88
+ await ensureClaudeMdInWorktree(gitRoot, worktreePath);
89
+ }
90
+ catch (err) {
91
+ const msg = err instanceof Error ? err.message : String(err);
92
+ stderr?.(`CLAUDE.md copy failed: ${msg}`);
93
+ }
94
+ const relativePath = relative(gitRoot, resolvedWorkingDirectory);
95
+ const useRelativePath = relativePath && !relativePath.startsWith('..') && !isAbsolute(relativePath) && relativePath !== '.';
96
+ const worktreeWorkingDirectory = useRelativePath
97
+ ? join(worktreePath, relativePath)
98
+ : worktreePath;
99
+ return {
100
+ workingDirectory: worktreeWorkingDirectory,
101
+ branchName,
102
+ cleanup: async (options) => {
103
+ await cleanupWorktree(gitRoot, worktreePath, branchName, options?.keepBranch);
104
+ },
105
+ };
106
+ }
107
+ /**
108
+ * Parse `git worktree list --porcelain` and remove any worktree
109
+ * that has the target branch checked out. This prevents
110
+ * "branch already checked out" errors on re-execution.
111
+ */
112
+ export async function removeLingeringWorktrees(gitRoot, branchName) {
113
+ let wtList;
114
+ try {
115
+ const result = await execFileAsync('git', ['-C', gitRoot, 'worktree', 'list', '--porcelain'], { env: withGitEnv(), timeout: 10_000 });
116
+ wtList = result.stdout;
117
+ }
118
+ catch {
119
+ return; // Can't list — nothing to clean
120
+ }
121
+ // Parse porcelain output: blocks separated by blank lines,
122
+ // each block has "worktree <path>" and "branch refs/heads/<name>"
123
+ let currentWorktreePath = null;
124
+ for (const line of wtList.split('\n')) {
125
+ if (line.startsWith('worktree ')) {
126
+ currentWorktreePath = line.slice('worktree '.length);
127
+ }
128
+ else if (line.startsWith('branch ') && currentWorktreePath) {
129
+ const ref = line.slice('branch '.length);
130
+ if (ref === `refs/heads/${branchName}`) {
131
+ console.log(`[worktree] Removing lingering worktree at ${currentWorktreePath} (branch: ${branchName})`);
132
+ // Try git worktree remove first, fall back to force-removing the directory + prune
133
+ try {
134
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'remove', '--force', currentWorktreePath], { env: withGitEnv(), timeout: 30_000 });
135
+ }
136
+ catch {
137
+ // Fallback: force-remove the directory and prune to deregister
138
+ await rm(currentWorktreePath, { recursive: true, force: true });
139
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'prune'], {
140
+ env: withGitEnv(),
141
+ timeout: 30_000,
142
+ });
143
+ }
144
+ // Clean up directory if it still exists after either path
145
+ if (existsSync(currentWorktreePath)) {
146
+ await rm(currentWorktreePath, { recursive: true, force: true });
147
+ }
148
+ }
149
+ }
150
+ else if (line.trim() === '') {
151
+ currentWorktreePath = null;
152
+ }
153
+ }
154
+ }
155
+ /**
156
+ * Delete remote branch if it exists.
157
+ * Prevents non-fast-forward push failures when re-executing a task
158
+ * whose previous branch was already pushed.
159
+ */
160
+ async function deleteRemoteBranch(gitRoot, branchName) {
161
+ try {
162
+ await execFileAsync('git', ['-C', gitRoot, 'push', 'origin', '--delete', branchName], { env: withGitEnv(), timeout: 15_000 });
163
+ console.log(`[worktree] Deleted remote branch ${branchName}`);
164
+ }
165
+ catch {
166
+ // Remote branch didn't exist — that's fine
167
+ }
168
+ }
169
+ /**
170
+ * Detect the default branch (main/master) for the repo.
171
+ */
172
+ async function getDefaultBranch(gitRoot) {
173
+ try {
174
+ const { stdout } = await execFileAsync('git', ['-C', gitRoot, 'symbolic-ref', 'refs/remotes/origin/HEAD'], { env: withGitEnv(), timeout: 5_000 });
175
+ const parts = stdout.trim().split('/');
176
+ return parts[parts.length - 1];
177
+ }
178
+ catch {
179
+ // Fallback
180
+ }
181
+ try {
182
+ const { stdout } = await execFileAsync('git', ['-C', gitRoot, 'branch', '-r', '--list', 'origin/main', 'origin/master'], { env: withGitEnv(), timeout: 5_000 });
183
+ const branches = stdout.trim().split('\n').map((b) => b.trim());
184
+ if (branches.includes('origin/main'))
185
+ return 'main';
186
+ if (branches.includes('origin/master'))
187
+ return 'master';
188
+ }
189
+ catch {
190
+ // Fallback
191
+ }
192
+ return 'main';
193
+ }
194
+ /**
195
+ * Read the branch prefix from the agent directory config.
196
+ * Falls back to 'astro/' if config doesn't exist or is invalid.
197
+ */
198
+ async function readBranchPrefix(gitRoot, agentDirName) {
199
+ try {
200
+ const configPath = join(gitRoot, agentDirName, 'config.json');
201
+ const content = await readFile(configPath, 'utf-8');
202
+ const config = JSON.parse(content);
203
+ if (config.branchPrefix && typeof config.branchPrefix === 'string') {
204
+ return config.branchPrefix;
205
+ }
206
+ }
207
+ catch {
208
+ // Config doesn't exist or is invalid — use default
209
+ }
210
+ return 'astro/';
211
+ }
212
+ /**
213
+ * Resolve the worktree root directory.
214
+ *
215
+ * Creates `{agentDirName}/worktrees/` inside the git root so worktrees live
216
+ * alongside the project (easy access to untracked data files, no bloat in ~/.astro/).
217
+ *
218
+ * Automatically adds `{agentDirName}/worktrees/` and `{agentDirName}/cache/`
219
+ * to the repo's `.gitignore` if missing.
220
+ *
221
+ * Falls back to `~/.astro/worktrees/{repoName}/` if the git root is read-only.
222
+ */
223
+ async function resolveWorktreeRoot(gitRoot, agentDirName) {
224
+ const worktreesDir = join(gitRoot, agentDirName, 'worktrees');
225
+ try {
226
+ await mkdir(worktreesDir, { recursive: true });
227
+ await ensureGitignoreEntry(gitRoot, `${agentDirName}/worktrees/`);
228
+ await ensureGitignoreEntry(gitRoot, `${agentDirName}/cache/`);
229
+ return worktreesDir;
230
+ }
231
+ catch {
232
+ // Git root is read-only — fall back to home dir
233
+ const repoName = sanitize(basename(gitRoot));
234
+ const fallback = join(homedir(), '.astro', 'worktrees', repoName);
235
+ await mkdir(fallback, { recursive: true });
236
+ console.log(`[worktree] Git root read-only, using fallback: ${fallback}`);
237
+ return fallback;
238
+ }
239
+ }
240
+ /**
241
+ * Ensure a pattern is present in the repo's .gitignore.
242
+ * Appends if missing; creates the file if it doesn't exist.
243
+ */
244
+ async function ensureGitignoreEntry(gitRoot, pattern) {
245
+ const gitignorePath = join(gitRoot, '.gitignore');
246
+ try {
247
+ if (existsSync(gitignorePath)) {
248
+ const content = await readFile(gitignorePath, 'utf-8');
249
+ // Check if already present (exact line match)
250
+ const lines = content.split('\n').map((l) => l.trim());
251
+ if (lines.includes(pattern))
252
+ return;
253
+ // Append with a preceding newline if the file doesn't end with one
254
+ // Only add comment header if not already present in the file
255
+ const prefix = content.endsWith('\n') ? '' : '\n';
256
+ const hasComment = lines.includes('# Astro agent directory');
257
+ const entry = hasComment ? `${prefix}${pattern}\n` : `${prefix}\n# Astro agent directory\n${pattern}\n`;
258
+ await appendFile(gitignorePath, entry);
259
+ }
260
+ else {
261
+ await appendFile(gitignorePath, `# Astro agent directory\n${pattern}\n`);
262
+ }
263
+ }
264
+ catch {
265
+ // Non-fatal: .gitignore update failed (e.g., permissions)
266
+ console.log(`[worktree] Could not update .gitignore at ${gitignorePath}`);
267
+ }
268
+ }
269
+ /**
270
+ * Ensure CLAUDE.md is available in the worktree.
271
+ *
272
+ * If CLAUDE.md is tracked by git it will already appear in the worktree
273
+ * automatically. But if it is untracked or gitignored in the source repo
274
+ * we need to copy it explicitly so the agent has access to project
275
+ * instructions inside the isolated worktree.
276
+ *
277
+ * This is intentionally non-fatal: a missing CLAUDE.md should never block
278
+ * task execution.
279
+ */
280
+ export async function ensureClaudeMdInWorktree(gitRoot, worktreePath) {
281
+ try {
282
+ const sourcePath = join(gitRoot, 'CLAUDE.md');
283
+ const destPath = join(worktreePath, 'CLAUDE.md');
284
+ // Nothing to copy if the source repo doesn't have a CLAUDE.md
285
+ if (!existsSync(sourcePath)) {
286
+ return;
287
+ }
288
+ // If it already exists in the worktree (i.e. it's tracked by git),
289
+ // there's nothing to do
290
+ if (existsSync(destPath)) {
291
+ return;
292
+ }
293
+ await copyFile(sourcePath, destPath);
294
+ console.log(`[worktree] Copied CLAUDE.md from ${gitRoot} to ${worktreePath}`);
295
+ }
296
+ catch (err) {
297
+ const msg = err instanceof Error ? err.message : String(err);
298
+ console.warn(`[worktree] Failed to copy CLAUDE.md: ${msg}`);
299
+ }
300
+ }
301
+ /**
302
+ * Initialize and update git submodules in a worktree.
303
+ *
304
+ * Git worktrees don't automatically init submodules — the directories
305
+ * exist but are empty. We run `git submodule update --init --recursive`
306
+ * to populate them. This is skipped if no .gitmodules file exists.
307
+ */
308
+ async function initSubmodules(worktreePath, stderr) {
309
+ if (!existsSync(join(worktreePath, '.gitmodules'))) {
310
+ return;
311
+ }
312
+ console.log(`[worktree] Initializing submodules in ${worktreePath}`);
313
+ try {
314
+ await execFileAsync('git', ['-C', worktreePath, 'submodule', 'update', '--init', '--recursive'], { env: withGitEnv(), timeout: 120_000 });
315
+ }
316
+ catch (err) {
317
+ const msg = err instanceof Error ? err.message : String(err);
318
+ stderr?.(`[worktree] Submodule init failed: ${msg}`);
319
+ }
320
+ }
321
+ async function getGitRoot(workingDirectory) {
322
+ try {
323
+ const { stdout } = await execFileAsync('git', ['-C', workingDirectory, 'rev-parse', '--show-toplevel'], { env: withGitEnv(), timeout: 5_000 });
324
+ const root = stdout.trim();
325
+ return root ? resolve(root) : null;
326
+ }
327
+ catch {
328
+ return null;
329
+ }
330
+ }
331
+ async function ensureBranchAvailable(gitRoot, branchName) {
332
+ try {
333
+ await execFileAsync('git', ['-C', gitRoot, 'branch', '-D', branchName], { env: withGitEnv(), timeout: 10_000 });
334
+ }
335
+ catch {
336
+ // Branch didn't exist — that's fine
337
+ }
338
+ }
339
+ async function cleanupWorktree(gitRoot, worktreePath, branchName, keepBranch) {
340
+ try {
341
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'remove', '--force', worktreePath], { env: withGitEnv(), timeout: 30_000 });
342
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'prune'], {
343
+ env: withGitEnv(),
344
+ timeout: 30_000,
345
+ });
346
+ }
347
+ catch {
348
+ // Ignore - worktree may already be removed
349
+ }
350
+ try {
351
+ await rm(worktreePath, { recursive: true, force: true });
352
+ }
353
+ catch {
354
+ // Ignore - directory may not exist
355
+ }
356
+ // Skip branch deletion when keepBranch is true (e.g., PR was created)
357
+ if (!keepBranch) {
358
+ try {
359
+ await execFileAsync('git', ['-C', gitRoot, 'branch', '-D', branchName], { env: withGitEnv(), timeout: 10_000 });
360
+ }
361
+ catch {
362
+ // Ignore - branch may not exist or already deleted
363
+ }
364
+ }
365
+ }
366
+ async function pruneWorktrees(gitRoot) {
367
+ try {
368
+ await execFileAsync('git', ['-C', gitRoot, 'worktree', 'prune'], {
369
+ env: withGitEnv(),
370
+ timeout: 30_000,
371
+ });
372
+ }
373
+ catch {
374
+ // Ignore prune errors
375
+ }
376
+ }
377
+ async function hasCommits(gitRoot) {
378
+ try {
379
+ await execFileAsync('git', ['-C', gitRoot, 'rev-parse', '--verify', 'HEAD'], {
380
+ env: withGitEnv(),
381
+ timeout: 5_000,
382
+ });
383
+ return true;
384
+ }
385
+ catch {
386
+ return false;
387
+ }
388
+ }
389
+ /**
390
+ * Validate that taskId is safe for use in paths.
391
+ * Accepts both UUID format and execution ID format (exec-{uuid-prefix}-{nodeId}-{timestamp}).
392
+ * Command injection is not a risk because we use execFileAsync (not shell),
393
+ * and the sanitize() function handles path safety.
394
+ */
395
+ function validateTaskId(taskId) {
396
+ // Allow: alphanumeric, hyphens, underscores, dots (same chars as sanitize keeps)
397
+ const safePattern = /^[a-zA-Z0-9._-]+$/;
398
+ if (!safePattern.test(taskId) || taskId.length > 200) {
399
+ throw new Error(`Invalid taskId format: ${taskId}. Must be alphanumeric with hyphens/underscores/dots, max 200 chars.`);
400
+ }
401
+ }
402
+ function sanitize(value) {
403
+ return value.replace(/[^a-zA-Z0-9._-]/g, '_');
404
+ }
405
+ function withGitEnv() {
406
+ return {
407
+ ...process.env,
408
+ GIT_TERMINAL_PROMPT: '0',
409
+ };
410
+ }
411
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/lib/worktree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MCP Bridge module exports
3
+ */
4
+ export { SessionBridge } from './session-bridge.js';
5
+ export { runMcpServer, getClaudeCodeMcpConfig, getClaudeCodeConfigPath } from './server.js';
6
+ export { ToolHandlers, toolDefinitions } from './tools.js';
7
+ export * from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,cAAc,YAAY,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MCP Bridge module exports
3
+ */
4
+ export { SessionBridge } from './session-bridge.js';
5
+ export { runMcpServer, getClaudeCodeMcpConfig, getClaudeCodeConfigPath } from './server.js';
6
+ export { ToolHandlers, toolDefinitions } from './tools.js';
7
+ export * from './types.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,cAAc,YAAY,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Astro MCP Server
3
+ *
4
+ * This MCP server allows existing Claude Code sessions to connect to Astro
5
+ * and link their activity to specific tasks. It uses the stdio transport
6
+ * to communicate with Claude Code.
7
+ *
8
+ * Tools provided:
9
+ * - astro_attach: Attach session to an Astro task
10
+ * - astro_detach: Detach from the current task
11
+ * - astro_status: Check attachment status
12
+ * - astro_send: Send a message/event to Astro
13
+ */
14
+ export interface McpServerOptions {
15
+ /** Override relay URL (defaults to config) */
16
+ relayUrl?: string;
17
+ /** Override machine ID (defaults to config) */
18
+ machineId?: string;
19
+ /** Override WS token (defaults to config) */
20
+ wsToken?: string;
21
+ /** Log level for console output */
22
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
23
+ }
24
+ /**
25
+ * Create and run the Astro MCP server
26
+ */
27
+ export declare function runMcpServer(options?: McpServerOptions): Promise<void>;
28
+ /**
29
+ * Get Claude Code MCP configuration for this server
30
+ */
31
+ export declare function getClaudeCodeMcpConfig(options?: {
32
+ execPath?: string;
33
+ }): {
34
+ mcpServers: {
35
+ astro: {
36
+ command: string;
37
+ args: string[];
38
+ };
39
+ };
40
+ };
41
+ /**
42
+ * Get the path to Claude Code's MCP configuration file
43
+ */
44
+ export declare function getClaudeCodeConfigPath(): string;
45
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6HhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG;IAC3E,UAAU,EAAE;QACV,KAAK,EAAE;YACL,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,EAAE,CAAC;SAChB,CAAC;KACH,CAAC;CACH,CAaA;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAGhD"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Astro MCP Server
3
+ *
4
+ * This MCP server allows existing Claude Code sessions to connect to Astro
5
+ * and link their activity to specific tasks. It uses the stdio transport
6
+ * to communicate with Claude Code.
7
+ *
8
+ * Tools provided:
9
+ * - astro_attach: Attach session to an Astro task
10
+ * - astro_detach: Detach from the current task
11
+ * - astro_status: Check attachment status
12
+ * - astro_send: Send a message/event to Astro
13
+ */
14
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
17
+ import { SessionBridge } from './session-bridge.js';
18
+ import { ToolHandlers, toolDefinitions } from './tools.js';
19
+ import { config as appConfig } from '../lib/config.js';
20
+ /**
21
+ * Create and run the Astro MCP server
22
+ */
23
+ export async function runMcpServer(options = {}) {
24
+ const logLevel = options.logLevel ?? 'info';
25
+ const log = (level, ...args) => {
26
+ const levels = { debug: 0, info: 1, warn: 2, error: 3 };
27
+ if (levels[level] >= levels[logLevel]) {
28
+ console.error(`[astro-mcp] [${level}]`, ...args);
29
+ }
30
+ };
31
+ // Get configuration from stored config or options
32
+ const bridgeConfig = {
33
+ relayUrl: options.relayUrl ?? appConfig.getRelayUrl(),
34
+ machineId: options.machineId ?? appConfig.getMachineId(),
35
+ wsToken: options.wsToken ?? appConfig.getWsToken(),
36
+ autoReconnect: true,
37
+ };
38
+ log('info', 'Starting Astro MCP server...');
39
+ log('debug', 'Config:', {
40
+ relayUrl: bridgeConfig.relayUrl,
41
+ machineId: bridgeConfig.machineId,
42
+ hasWsToken: !!bridgeConfig.wsToken,
43
+ });
44
+ // Create session bridge
45
+ const bridge = new SessionBridge(bridgeConfig);
46
+ // Set up bridge event logging
47
+ bridge.onEvent((event) => {
48
+ switch (event.type) {
49
+ case 'connected':
50
+ log('info', 'Connected to Astro relay');
51
+ break;
52
+ case 'disconnected':
53
+ log('info', 'Disconnected from Astro relay:', event.data);
54
+ break;
55
+ case 'attached':
56
+ log('info', 'Attached to task:', event.data);
57
+ break;
58
+ case 'detached':
59
+ log('info', 'Detached from task:', event.data);
60
+ break;
61
+ case 'event_sent':
62
+ log('debug', 'Event sent:', event.data);
63
+ break;
64
+ case 'error':
65
+ log('error', 'Bridge error:', event.error?.message);
66
+ break;
67
+ }
68
+ });
69
+ // Create tool handlers
70
+ const toolHandlers = new ToolHandlers(bridge);
71
+ // Create MCP server
72
+ const server = new Server({
73
+ name: 'astro-mcp-bridge',
74
+ version: '0.1.0',
75
+ }, {
76
+ capabilities: {
77
+ tools: {},
78
+ },
79
+ });
80
+ // Handle ListTools request
81
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
82
+ return {
83
+ tools: toolDefinitions,
84
+ };
85
+ });
86
+ // Handle CallTool request
87
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
88
+ const { name, arguments: args } = request.params;
89
+ log('debug', `Tool called: ${name}`, args);
90
+ try {
91
+ const result = await toolHandlers.handleToolCall(name, args ?? {});
92
+ return {
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: JSON.stringify(result, null, 2),
97
+ },
98
+ ],
99
+ };
100
+ }
101
+ catch (error) {
102
+ log('error', `Tool ${name} error:`, error);
103
+ return {
104
+ content: [
105
+ {
106
+ type: 'text',
107
+ text: JSON.stringify({
108
+ success: false,
109
+ message: `Error: ${error instanceof Error ? error.message : String(error)}`,
110
+ }),
111
+ },
112
+ ],
113
+ isError: true,
114
+ };
115
+ }
116
+ });
117
+ // Handle graceful shutdown
118
+ const cleanup = () => {
119
+ log('info', 'Shutting down...');
120
+ bridge.disconnect();
121
+ process.exit(0);
122
+ };
123
+ process.on('SIGINT', cleanup);
124
+ process.on('SIGTERM', cleanup);
125
+ // Start the server using stdio transport
126
+ const transport = new StdioServerTransport();
127
+ await server.connect(transport);
128
+ log('info', 'Astro MCP server started on stdio');
129
+ }
130
+ /**
131
+ * Get Claude Code MCP configuration for this server
132
+ */
133
+ export function getClaudeCodeMcpConfig(options = {}) {
134
+ // Default to using npx to run the CLI
135
+ const command = options.execPath ?? 'npx';
136
+ const args = options.execPath ? ['mcp'] : ['@astro/agent', 'mcp'];
137
+ return {
138
+ mcpServers: {
139
+ astro: {
140
+ command,
141
+ args,
142
+ },
143
+ },
144
+ };
145
+ }
146
+ /**
147
+ * Get the path to Claude Code's MCP configuration file
148
+ */
149
+ export function getClaudeCodeConfigPath() {
150
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
151
+ return `${home}/.claude/mcp_servers.json`;
152
+ }
153
+ //# sourceMappingURL=server.js.map