@schoolai/shipyard 3.4.0 → 3.5.0-rc.20260503.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 +1 -2
- package/dist/capabilities-NYAJEDLN.js +154 -0
- package/dist/{chunk-VJQY42NE.js → chunk-36ZIIVQI.js} +58 -10
- package/dist/chunk-36ZIIVQI.js.map +1 -0
- package/dist/{chunk-MEJ6OUJY.js → chunk-3NMNYZN7.js} +3 -3
- package/dist/{chunk-CCW5QAUH.js → chunk-3TB4VNFG.js} +2 -2
- package/dist/{chunk-DR5BPAKZ.js → chunk-5LIPEC7P.js} +19 -3
- package/dist/chunk-5LIPEC7P.js.map +1 -0
- package/dist/chunk-GUXFYNLJ.js +23753 -0
- package/dist/chunk-GUXFYNLJ.js.map +1 -0
- package/dist/{chunk-LWP7FT4Q.js → chunk-XXTIKBCU.js} +18 -2
- package/dist/chunk-XXTIKBCU.js.map +1 -0
- package/dist/{chunk-DNIC3FOH.js → chunk-YUG27SAR.js} +1 -1
- package/dist/{chunk-BRW34JMG.js → chunk-ZSBE4P76.js} +42 -3
- package/dist/chunk-ZSBE4P76.js.map +1 -0
- package/dist/{git-repo-LG3LCOWF.js → git-repo-WGZ7Q3D5.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/logger-7XW3I4XN.js +16 -0
- package/dist/{login-JTMNYTC5.js → login-XTDSLC6O.js} +5 -5
- package/dist/{logout-M7F7HXUU.js → logout-CUAAF5IK.js} +3 -3
- package/dist/{mcp-servers-T7OJWREP.js → mcp-servers-3SHS2PEJ.js} +3 -3
- package/dist/mcp-servers-3SHS2PEJ.js.map +1 -0
- package/dist/{roi-ZCVNBSTO.js → roi-LN7MMRH7.js} +96 -8
- package/dist/roi-LN7MMRH7.js.map +1 -0
- package/dist/{serve-QENWMCVP.js → serve-QKXME3DV.js} +15319 -35936
- package/dist/serve-QKXME3DV.js.map +1 -0
- package/dist/{skills-ULFXYA7N.js → skills-OMDIMU7D.js} +2 -2
- package/dist/skills-OMDIMU7D.js.map +1 -0
- package/dist/{start-6AQMLAOX.js → start-JDVIIDYK.js} +6 -6
- package/package.json +3 -8
- package/dist/chunk-BRW34JMG.js.map +0 -1
- package/dist/chunk-DR5BPAKZ.js.map +0 -1
- package/dist/chunk-LWP7FT4Q.js.map +0 -1
- package/dist/chunk-VJQY42NE.js.map +0 -1
- package/dist/roi-ZCVNBSTO.js.map +0 -1
- package/dist/serve-QENWMCVP.js.map +0 -1
- /package/dist/{git-repo-LG3LCOWF.js.map → capabilities-NYAJEDLN.js.map} +0 -0
- /package/dist/{chunk-MEJ6OUJY.js.map → chunk-3NMNYZN7.js.map} +0 -0
- /package/dist/{chunk-CCW5QAUH.js.map → chunk-3TB4VNFG.js.map} +0 -0
- /package/dist/{chunk-DNIC3FOH.js.map → chunk-YUG27SAR.js.map} +0 -0
- /package/dist/{login-JTMNYTC5.js.map → git-repo-WGZ7Q3D5.js.map} +0 -0
- /package/dist/{mcp-servers-T7OJWREP.js.map → logger-7XW3I4XN.js.map} +0 -0
- /package/dist/{skills-ULFXYA7N.js.map → login-XTDSLC6O.js.map} +0 -0
- /package/dist/{logout-M7F7HXUU.js.map → logout-CUAAF5IK.js.map} +0 -0
- /package/dist/{start-6AQMLAOX.js.map → start-JDVIIDYK.js.map} +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
<p>
|
|
7
7
|
<a href="https://www.npmjs.com/package/@schoolai/shipyard"><img src="https://img.shields.io/npm/v/@schoolai/shipyard" alt="npm"></a>
|
|
8
|
-
<a href="https://img.shields.io/badge/license-FSL--1.1-blue"><img src="https://img.shields.io/badge/license-FSL--1.1-blue" alt="License"></a>
|
|
9
8
|
</p>
|
|
10
9
|
</div>
|
|
11
10
|
|
|
@@ -144,4 +143,4 @@ That's it. The daemon handles authentication via GitHub device flow, connects to
|
|
|
144
143
|
|
|
145
144
|
## License
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
Proprietary. All rights reserved. © SchoolAI. Distributed for use with the Shipyard service; not for redistribution or modification.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addAssignees,
|
|
4
|
+
addLabels,
|
|
5
|
+
addReviewers,
|
|
6
|
+
appendShipyardBodyMarker,
|
|
7
|
+
approvePR,
|
|
8
|
+
buildShipyardBodyMarker,
|
|
9
|
+
captureTreeSnapshot,
|
|
10
|
+
closePR,
|
|
11
|
+
commentOnPR,
|
|
12
|
+
convertToDraft,
|
|
13
|
+
deduplicatePRs,
|
|
14
|
+
detectAgentProviders,
|
|
15
|
+
detectAnthropicAuth,
|
|
16
|
+
detectCapabilities,
|
|
17
|
+
detectCapabilitiesWithInitialRetry,
|
|
18
|
+
detectMarketplacePlugins,
|
|
19
|
+
detectModels,
|
|
20
|
+
ensureLabel,
|
|
21
|
+
fetchRemoteBranch,
|
|
22
|
+
findRenames,
|
|
23
|
+
getAssignedReviews,
|
|
24
|
+
getBranchDiff,
|
|
25
|
+
getBranchFiles,
|
|
26
|
+
getChangedFiles,
|
|
27
|
+
getConfiguredEnvironments,
|
|
28
|
+
getDefaultBranch,
|
|
29
|
+
getFileAtRef,
|
|
30
|
+
getGitRefExists,
|
|
31
|
+
getLatestDeployments,
|
|
32
|
+
getMergeBase,
|
|
33
|
+
getPRByBranch,
|
|
34
|
+
getPRDiff,
|
|
35
|
+
getPRFiles,
|
|
36
|
+
getPRForCurrentBranch,
|
|
37
|
+
getPRReviewComments,
|
|
38
|
+
getRequiredChecks,
|
|
39
|
+
getSnapshotDiff,
|
|
40
|
+
getSnapshotFiles,
|
|
41
|
+
getStackDiff,
|
|
42
|
+
getStackFiles,
|
|
43
|
+
getStagedDiff,
|
|
44
|
+
getUnstagedDiff,
|
|
45
|
+
getUserPRs,
|
|
46
|
+
getWorkflowRuns,
|
|
47
|
+
mapGhPRToPRData,
|
|
48
|
+
markPRReady,
|
|
49
|
+
mergePR,
|
|
50
|
+
pickLastKnownCapabilities,
|
|
51
|
+
removeAssignees,
|
|
52
|
+
removeLabels,
|
|
53
|
+
reopenPR,
|
|
54
|
+
replyToReviewComment,
|
|
55
|
+
requestChanges,
|
|
56
|
+
rerunChecks,
|
|
57
|
+
resolveReviewThread,
|
|
58
|
+
unresolveReviewThread
|
|
59
|
+
} from "./chunk-GUXFYNLJ.js";
|
|
60
|
+
import {
|
|
61
|
+
detectMCPServers,
|
|
62
|
+
redactArgs,
|
|
63
|
+
redactEnv
|
|
64
|
+
} from "./chunk-5LIPEC7P.js";
|
|
65
|
+
import {
|
|
66
|
+
detectSkills
|
|
67
|
+
} from "./chunk-XXTIKBCU.js";
|
|
68
|
+
import "./chunk-ZSBE4P76.js";
|
|
69
|
+
import "./chunk-EHQITHQX.js";
|
|
70
|
+
import {
|
|
71
|
+
detectEnvironments,
|
|
72
|
+
findGitRepos,
|
|
73
|
+
getGitTopLevel,
|
|
74
|
+
getRepoMetadata,
|
|
75
|
+
invalidateRepoPaths,
|
|
76
|
+
isAncestor,
|
|
77
|
+
isGhAvailable,
|
|
78
|
+
parseOwnerRepo
|
|
79
|
+
} from "./chunk-36ZIIVQI.js";
|
|
80
|
+
import "./chunk-YUG27SAR.js";
|
|
81
|
+
import "./chunk-M5M6VC5F.js";
|
|
82
|
+
import "./chunk-KYLY4DJF.js";
|
|
83
|
+
import "./chunk-2H7UOFLK.js";
|
|
84
|
+
export {
|
|
85
|
+
addAssignees,
|
|
86
|
+
addLabels,
|
|
87
|
+
addReviewers,
|
|
88
|
+
appendShipyardBodyMarker,
|
|
89
|
+
approvePR,
|
|
90
|
+
buildShipyardBodyMarker,
|
|
91
|
+
captureTreeSnapshot,
|
|
92
|
+
closePR,
|
|
93
|
+
commentOnPR,
|
|
94
|
+
convertToDraft,
|
|
95
|
+
deduplicatePRs,
|
|
96
|
+
detectAgentProviders,
|
|
97
|
+
detectAnthropicAuth,
|
|
98
|
+
detectCapabilities,
|
|
99
|
+
detectCapabilitiesWithInitialRetry,
|
|
100
|
+
detectEnvironments,
|
|
101
|
+
detectMCPServers,
|
|
102
|
+
detectMarketplacePlugins,
|
|
103
|
+
detectModels,
|
|
104
|
+
detectSkills,
|
|
105
|
+
ensureLabel,
|
|
106
|
+
fetchRemoteBranch,
|
|
107
|
+
findGitRepos,
|
|
108
|
+
findRenames,
|
|
109
|
+
getAssignedReviews,
|
|
110
|
+
getBranchDiff,
|
|
111
|
+
getBranchFiles,
|
|
112
|
+
getChangedFiles,
|
|
113
|
+
getConfiguredEnvironments,
|
|
114
|
+
getDefaultBranch,
|
|
115
|
+
getFileAtRef,
|
|
116
|
+
getGitRefExists,
|
|
117
|
+
getGitTopLevel,
|
|
118
|
+
getLatestDeployments,
|
|
119
|
+
getMergeBase,
|
|
120
|
+
getPRByBranch,
|
|
121
|
+
getPRDiff,
|
|
122
|
+
getPRFiles,
|
|
123
|
+
getPRForCurrentBranch,
|
|
124
|
+
getPRReviewComments,
|
|
125
|
+
getRepoMetadata,
|
|
126
|
+
getRequiredChecks,
|
|
127
|
+
getSnapshotDiff,
|
|
128
|
+
getSnapshotFiles,
|
|
129
|
+
getStackDiff,
|
|
130
|
+
getStackFiles,
|
|
131
|
+
getStagedDiff,
|
|
132
|
+
getUnstagedDiff,
|
|
133
|
+
getUserPRs,
|
|
134
|
+
getWorkflowRuns,
|
|
135
|
+
invalidateRepoPaths,
|
|
136
|
+
isAncestor,
|
|
137
|
+
isGhAvailable,
|
|
138
|
+
mapGhPRToPRData,
|
|
139
|
+
markPRReady,
|
|
140
|
+
mergePR,
|
|
141
|
+
parseOwnerRepo,
|
|
142
|
+
pickLastKnownCapabilities,
|
|
143
|
+
redactArgs,
|
|
144
|
+
redactEnv,
|
|
145
|
+
removeAssignees,
|
|
146
|
+
removeLabels,
|
|
147
|
+
reopenPR,
|
|
148
|
+
replyToReviewComment,
|
|
149
|
+
requestChanges,
|
|
150
|
+
rerunChecks,
|
|
151
|
+
resolveReviewThread,
|
|
152
|
+
unresolveReviewThread
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=capabilities-NYAJEDLN.js.map
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
TIMEOUT_MS,
|
|
4
|
-
run,
|
|
5
|
-
runWithTimeout
|
|
6
|
-
} from "./chunk-KYLY4DJF.js";
|
|
7
2
|
import {
|
|
8
3
|
logger
|
|
9
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YUG27SAR.js";
|
|
10
5
|
import {
|
|
11
6
|
external_exports,
|
|
12
7
|
getShipyardHome
|
|
13
8
|
} from "./chunk-M5M6VC5F.js";
|
|
9
|
+
import {
|
|
10
|
+
TIMEOUT_MS,
|
|
11
|
+
run,
|
|
12
|
+
runWithTimeout
|
|
13
|
+
} from "./chunk-KYLY4DJF.js";
|
|
14
14
|
|
|
15
15
|
// src/shared/capabilities/git-repo.ts
|
|
16
16
|
import { readdir } from "fs/promises";
|
|
@@ -137,6 +137,24 @@ async function saveCache(entries) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// src/shared/capabilities/repo-paths-cache.ts
|
|
141
|
+
var REPO_PATHS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
142
|
+
var cache = null;
|
|
143
|
+
function getCachedRepoPaths(now = Date.now()) {
|
|
144
|
+
if (!cache) return null;
|
|
145
|
+
if (now - cache.populatedAt >= REPO_PATHS_CACHE_TTL_MS) {
|
|
146
|
+
cache = null;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return { repos: cache.repos, worktrees: cache.worktrees };
|
|
150
|
+
}
|
|
151
|
+
function setCachedRepoPaths(repos, worktrees, now = Date.now()) {
|
|
152
|
+
cache = { repos, worktrees, populatedAt: now };
|
|
153
|
+
}
|
|
154
|
+
function invalidateRepoPaths() {
|
|
155
|
+
cache = null;
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
// src/shared/capabilities/git-repo.ts
|
|
141
159
|
var gitRepoCache = /* @__PURE__ */ new Map();
|
|
142
160
|
async function isGitRepo(cwd) {
|
|
@@ -257,10 +275,39 @@ async function getRepoMetadata(repoPath, cached) {
|
|
|
257
275
|
return null;
|
|
258
276
|
}
|
|
259
277
|
}
|
|
260
|
-
async function detectEnvironments() {
|
|
278
|
+
async function detectEnvironments(lastKnown) {
|
|
261
279
|
const { homedir } = await import("os");
|
|
262
|
-
const
|
|
263
|
-
|
|
280
|
+
const cachedPaths = getCachedRepoPaths();
|
|
281
|
+
let repoPaths;
|
|
282
|
+
let worktreePaths;
|
|
283
|
+
if (cachedPaths) {
|
|
284
|
+
repoPaths = cachedPaths.repos;
|
|
285
|
+
worktreePaths = cachedPaths.worktrees;
|
|
286
|
+
} else {
|
|
287
|
+
try {
|
|
288
|
+
repoPaths = await findGitRepos(homedir());
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (lastKnown && lastKnown.length > 0) {
|
|
291
|
+
const { logger: logger2 } = await import("./logger-7XW3I4XN.js");
|
|
292
|
+
logger2.warn(
|
|
293
|
+
{ err, lastKnownCount: lastKnown.length },
|
|
294
|
+
"detectEnvironments findGitRepos threw \u2014 preserving lastKnown"
|
|
295
|
+
);
|
|
296
|
+
return lastKnown;
|
|
297
|
+
}
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {
|
|
301
|
+
const { logger: logger2 } = await import("./logger-7XW3I4XN.js");
|
|
302
|
+
logger2.warn(
|
|
303
|
+
{ lastKnownCount: lastKnown.length },
|
|
304
|
+
"detectEnvironments walk empty but lastKnown non-empty \u2014 preserving lastKnown"
|
|
305
|
+
);
|
|
306
|
+
return lastKnown;
|
|
307
|
+
}
|
|
308
|
+
worktreePaths = await discoverWorktrees(repoPaths);
|
|
309
|
+
setCachedRepoPaths(repoPaths, worktreePaths);
|
|
310
|
+
}
|
|
264
311
|
const allPaths = [.../* @__PURE__ */ new Set([...repoPaths, ...worktreePaths])];
|
|
265
312
|
const prevCache = await loadCache();
|
|
266
313
|
const headMtimes = await Promise.all(allPaths.map(getHeadMtime));
|
|
@@ -325,6 +372,7 @@ export {
|
|
|
325
372
|
isEexist,
|
|
326
373
|
isCorruptionError,
|
|
327
374
|
findProjectRoot,
|
|
375
|
+
invalidateRepoPaths,
|
|
328
376
|
gitRepoCache,
|
|
329
377
|
isGitRepo,
|
|
330
378
|
isGhAvailable,
|
|
@@ -337,4 +385,4 @@ export {
|
|
|
337
385
|
detectEnvironments,
|
|
338
386
|
_testing
|
|
339
387
|
};
|
|
340
|
-
//# sourceMappingURL=chunk-
|
|
388
|
+
//# sourceMappingURL=chunk-36ZIIVQI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/git-repo.ts","../src/shared/capabilities/environments-cache.ts","../src/shared/fs-utils.ts","../src/shared/capabilities/repo-paths-cache.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { GitRepoInfo } from '@shipyard/session';\nimport {\n type CacheEntry,\n getHeadMtime,\n isCacheValid,\n loadCache,\n saveCache,\n} from './environments-cache.js';\nimport { getCachedRepoPaths, setCachedRepoPaths } from './repo-paths-cache.js';\nimport { run, runWithTimeout, TIMEOUT_MS } from './shell.js';\n\n/** Cache of directory paths to whether they are inside a git repository. */\nexport const gitRepoCache = new Map<string, boolean>();\n\n/**\n * Check whether the given directory is inside a git work-tree.\n * Results are cached per directory so repeated calls (e.g. during\n * debounced diff captures) do not re-spawn git processes.\n */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n const cached = gitRepoCache.get(cwd);\n if (cached !== undefined) return cached;\n try {\n await runWithTimeout('git', ['rev-parse', '--git-dir'], cwd, TIMEOUT_MS);\n gitRepoCache.set(cwd, true);\n return true;\n } catch {\n gitRepoCache.set(cwd, false);\n return false;\n }\n}\n\nlet ghAvailableCache = false;\n\nexport async function isGhAvailable(): Promise<boolean> {\n if (ghAvailableCache) return true;\n try {\n await run('which', ['gh']);\n ghAvailableCache = true;\n return true;\n } catch {\n return false;\n }\n}\n\nconst topLevelCache = new Map<string, string>();\n\nexport async function getGitTopLevel(cwd: string): Promise<string> {\n const cached = topLevelCache.get(cwd);\n if (cached !== undefined) return cached;\n const topLevel = (\n await runWithTimeout('git', ['rev-parse', '--show-toplevel'], cwd, TIMEOUT_MS)\n ).trim();\n topLevelCache.set(cwd, topLevel);\n return topLevel;\n}\n\nexport function parseOwnerRepo(remoteUrl: string): { owner: string; repo: string } | null {\n const match = remoteUrl.match(/github\\.com[:/]([^/]+)\\/([^/.]+)/);\n if (!match?.[1] || !match[2]) return null;\n return { owner: match[1], repo: match[2] };\n}\n\n/**\n * Look up the repo's default branch via the `origin/HEAD` symbolic ref.\n * Returns the branch name (e.g. `main`) or null when the symbolic ref\n * isn't set (rare — happens for repos cloned without `git remote\n * set-head` or local-only repos).\n */\nexport async function getRepoDefaultBranch(cwd: string): Promise<string | null> {\n try {\n const head = (\n await runWithTimeout(\n 'git',\n ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'],\n cwd,\n TIMEOUT_MS\n )\n ).trim();\n if (!head) return null;\n return head.replace(/^origin\\//, '') || null;\n } catch {\n return null;\n }\n}\n\nexport async function isAncestor(\n ancestor: string,\n descendant: string,\n cwd: string\n): Promise<boolean> {\n try {\n await runWithTimeout('git', ['merge-base', '--is-ancestor', ancestor, descendant], cwd, 5_000);\n return true;\n } catch {\n return false;\n }\n}\n\nconst EXCLUDE_DIRS = new Set([\n 'node_modules',\n 'Library',\n 'Applications',\n 'Pictures',\n 'Music',\n 'Movies',\n 'go',\n '.Trash',\n]);\n\nconst MAX_DEPTH = 4;\n\nexport async function findGitRepos(dir: string, depth = 0): Promise<string[]> {\n if (depth > MAX_DEPTH) return [];\n\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name === '.git') {\n return [dir];\n }\n }\n\n if (depth >= MAX_DEPTH) return [];\n\n const promises: Promise<string[]>[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.')) continue;\n if (EXCLUDE_DIRS.has(entry.name)) continue;\n promises.push(findGitRepos(join(dir, entry.name), depth + 1));\n }\n\n const results = await Promise.all(promises);\n return results.flat();\n } catch {\n return [];\n }\n}\n\nexport async function getRepoMetadata(\n repoPath: string,\n cached?: CacheEntry\n): Promise<GitRepoInfo | null> {\n try {\n if (cached) {\n return {\n path: repoPath,\n name: basename(repoPath),\n branch: cached.branch,\n ...(cached.remote !== null && { remote: cached.remote }),\n };\n }\n\n const [branchResult, remoteResult] = await Promise.allSettled([\n run('git', ['branch', '--show-current'], repoPath),\n run('git', ['remote', 'get-url', 'origin'], repoPath),\n ]);\n\n const branch = branchResult.status === 'fulfilled' ? branchResult.value || 'HEAD' : 'HEAD';\n const remote =\n remoteResult.status === 'fulfilled' ? remoteResult.value || undefined : undefined;\n\n return {\n path: repoPath,\n name: basename(repoPath),\n branch,\n ...(remote && { remote }),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Detect git environments under $HOME.\n *\n * `lastKnown` preserves the previously-detected environment list when the\n * underlying `findGitRepos` walk returns `[]` for a non-authoritative reason\n * (root readdir failure: EMFILE, EACCES, EBUSY). Without this, a transient\n * filesystem hiccup in the 30s capability-refresh tick collapses environments\n * to `[]`, which `applyFreshCapabilities` writes wholesale, and the browser\n * briefly renders \"No environment\" until the next successful tick. Mirror of\n * the `lastKnownAgents` pattern in `agents.ts`.\n *\n * Discrimination: a successful walk that genuinely finds zero repos is\n * indistinguishable from a swallowed-error walk in the current shape (the\n * inner `try/catch` in `findGitRepos` returns `[]` on readdir failure). The\n * `lastKnown.length > 0` guard means we only preserve when there's something\n * worth preserving; a clean machine that genuinely has zero repos still gets\n * `[]`.\n */\nexport async function detectEnvironments(lastKnown?: GitRepoInfo[]): Promise<GitRepoInfo[]> {\n const { homedir } = await import('node:os');\n\n /**\n * The repo-paths cache (`repo-paths-cache.ts`) holds the result of the\n * `findGitRepos(homedir())` walk and the N parallel `git worktree list`\n * spawns — together ~12-15s on a busy machine. Per-repo branch info is\n * still resolved fresh below via `getRepoMetadata` (cached separately by\n * `.git/HEAD` mtime in `environments-cache.ts`). The list cache lives\n * 24h or until `invalidateRepoPaths()` is called (worktree create/remove,\n * explicit refresh).\n */\n const cachedPaths = getCachedRepoPaths();\n let repoPaths: string[];\n let worktreePaths: string[];\n\n if (cachedPaths) {\n repoPaths = cachedPaths.repos;\n worktreePaths = cachedPaths.worktrees;\n } else {\n try {\n repoPaths = await findGitRepos(homedir());\n } catch (err) {\n if (lastKnown && lastKnown.length > 0) {\n const { logger } = await import('../logger.js');\n logger.warn(\n { err, lastKnownCount: lastKnown.length },\n 'detectEnvironments findGitRepos threw — preserving lastKnown'\n );\n return lastKnown;\n }\n return [];\n }\n\n if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {\n /**\n * Walk returned empty but we know there used to be repos — almost\n * certainly a transient root-readdir error swallowed inside\n * `findGitRepos`. Preserve rather than wipe.\n */\n const { logger } = await import('../logger.js');\n logger.warn(\n { lastKnownCount: lastKnown.length },\n 'detectEnvironments walk empty but lastKnown non-empty — preserving lastKnown'\n );\n return lastKnown;\n }\n\n worktreePaths = await discoverWorktrees(repoPaths);\n setCachedRepoPaths(repoPaths, worktreePaths);\n }\n\n const allPaths = [...new Set([...repoPaths, ...worktreePaths])];\n\n const prevCache = await loadCache();\n const headMtimes = await Promise.all(allPaths.map(getHeadMtime));\n const nextCache = new Map<string, CacheEntry>();\n\n const resolved = await Promise.all(\n allPaths.map(async (path, idx) => {\n const currentMtime = headMtimes[idx] ?? null;\n const prev = prevCache.get(path);\n const cacheHit =\n prev !== undefined && currentMtime !== null && isCacheValid(prev, currentMtime);\n const info = await getRepoMetadata(path, cacheHit ? prev : undefined);\n if (info !== null && currentMtime !== null) {\n nextCache.set(path, {\n path,\n branch: info.branch,\n remote: info.remote ?? null,\n headMtimeMs: currentMtime,\n cachedAt: cacheHit && prev ? prev.cachedAt : Date.now(),\n });\n }\n return info;\n })\n );\n\n await saveCache(nextCache);\n\n return resolved.filter((info): info is GitRepoInfo => info !== null);\n}\n\n/**\n * For each repo, run `git worktree list --porcelain` to discover\n * linked worktrees (which may live in dot-directories like .claude/worktrees/).\n */\nasync function discoverWorktrees(repoPaths: string[]): Promise<string[]> {\n const results = await Promise.allSettled(repoPaths.map(listWorktreePaths));\n return results.flatMap((r) => (r.status === 'fulfilled' ? r.value : []));\n}\n\nasync function listWorktreePaths(repoPath: string): Promise<string[]> {\n try {\n const output = await runWithTimeout(\n 'git',\n ['worktree', 'list', '--porcelain'],\n repoPath,\n TIMEOUT_MS\n );\n return parseWorktreeListOutput(output, repoPath);\n } catch {\n return [];\n }\n}\n\nfunction parseWorktreeListOutput(output: string, repoPath: string): string[] {\n const paths: string[] = [];\n for (const line of output.split('\\n')) {\n if (line.startsWith('worktree ')) {\n const wt = line.slice('worktree '.length).trim();\n if (wt && wt !== repoPath) paths.push(wt);\n }\n }\n return paths;\n}\n\nexport const _testing = {\n parseWorktreeListOutput,\n resetGhAvailableCache: () => {\n ghAvailableCache = false;\n },\n};\n","import { mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { z } from 'zod';\nimport { getShipyardHome } from '../env.js';\nimport { isEnoent } from '../fs-utils.js';\nimport { logger } from '../logger.js';\n\nconst CacheEntrySchema = z.object({\n path: z.string(),\n branch: z.string(),\n remote: z.string().nullable(),\n headMtimeMs: z.number(),\n cachedAt: z.number(),\n});\n\nconst CacheFileSchema = z.object({\n entries: z.record(z.string(), CacheEntrySchema),\n});\n\nexport type CacheEntry = z.infer<typeof CacheEntrySchema>;\ntype CacheFile = z.infer<typeof CacheFileSchema>;\n\nexport const CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction cacheFilePath(): string {\n return join(getShipyardHome(), 'data', 'environments-cache.json');\n}\n\nexport async function loadCache(): Promise<Map<string, CacheEntry>> {\n const filePath = cacheFilePath();\n try {\n const raw = await readFile(filePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n const result = CacheFileSchema.safeParse(parsed);\n if (!result.success) {\n logger.debug({ path: filePath }, 'environments-cache: malformed, treating as empty');\n return new Map();\n }\n return new Map(Object.entries(result.data.entries));\n } catch (err) {\n if (isEnoent(err)) {\n return new Map();\n }\n logger.debug({ err }, 'environments-cache: read failed, treating as empty');\n return new Map();\n }\n}\n\n/**\n * Resolve the actual path of HEAD for the given worktree.\n *\n * - If `<path>/.git` is a directory, HEAD lives at `<path>/.git/HEAD`.\n * - If `<path>/.git` is a file (linked worktree), it contains `gitdir: <path>`;\n * HEAD lives at `<gitdir>/HEAD`. The gitdir may be relative to `<path>`.\n * - Returns null on any error (missing, unreadable, malformed).\n */\nexport async function resolveHeadPath(worktreePath: string): Promise<string | null> {\n const dotGitPath = join(worktreePath, '.git');\n try {\n const dotGitStat = await stat(dotGitPath);\n if (dotGitStat.isDirectory()) {\n return join(dotGitPath, 'HEAD');\n }\n if (dotGitStat.isFile()) {\n const contents = await readFile(dotGitPath, 'utf8');\n const match = contents.match(/^gitdir:\\s*(.+)\\s*$/m);\n if (!match?.[1]) return null;\n const gitdirRaw = match[1].trim();\n const gitdir = isAbsolute(gitdirRaw) ? gitdirRaw : resolve(dirname(dotGitPath), gitdirRaw);\n return join(gitdir, 'HEAD');\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function getHeadMtime(worktreePath: string): Promise<number | null> {\n const headPath = await resolveHeadPath(worktreePath);\n if (!headPath) return null;\n try {\n const headStat = await stat(headPath);\n return headStat.mtimeMs;\n } catch {\n return null;\n }\n}\n\nexport function isCacheValid(entry: CacheEntry, currentMtimeMs: number): boolean {\n if (entry.headMtimeMs !== currentMtimeMs) return false;\n if (Date.now() - entry.cachedAt >= CACHE_TTL_MS) return false;\n return true;\n}\n\nexport async function saveCache(entries: Map<string, CacheEntry>): Promise<void> {\n const filePath = cacheFilePath();\n const tmpPath = `${filePath}.tmp`;\n try {\n await mkdir(dirname(filePath), { recursive: true });\n /**\n * Merge-on-save: re-read disk immediately before writing so a concurrent\n * saveCache call's entries are preserved rather than erased (new entries\n * win for paths present in both — they are fresher).\n */\n const current = await loadCache();\n for (const [path, entry] of entries) {\n current.set(path, entry);\n }\n const payload: CacheFile = { entries: Object.fromEntries(current) };\n await writeFile(tmpPath, JSON.stringify(payload), 'utf8');\n await rename(tmpPath, filePath);\n } catch (err) {\n logger.debug({ err, filePath }, 'environments-cache: save failed');\n void unlink(tmpPath).catch(() => {});\n }\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\n\nexport function isEnoent(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nexport function isEexist(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'EEXIST';\n}\n\n/**\n * Detect JSON parse errors or Zod validation failures that indicate\n * a corrupt store file. Shared across all file-backed stores.\n */\nexport function isCorruptionError(err: unknown): boolean {\n if (err instanceof SyntaxError) return true;\n if (typeof err === 'object' && err !== null && 'name' in err && err.name === 'ZodError')\n return true;\n return false;\n}\n\n/**\n * Walk up the directory tree from `startDir` to find the project root.\n *\n * Looks for `.git` (directory or file — worktrees use a `.git` file) or\n * `pnpm-workspace.yaml` as root markers.\n *\n * Returns a discriminated result so callers can distinguish \"marker found\"\n * (genuinely scoped, even if the marker is in startDir itself) from\n * \"no marker anywhere\" (genuinely unscoped). The bare-string return shape\n * conflated these, since both cases produced `startDir` — breaking the\n * unscoped detection in `serve.ts` whenever the daemon was launched from\n * a project's own root directory.\n *\n * In dev: `startDir` is typically `apps/daemon/` (pnpm runs from package dir),\n * so this walks up to find the monorepo root. In prod install: `startDir` is\n * the user's project directory where the daemon is launched, and `.git` is\n * found immediately. Called once at daemon startup — the result is used as\n * the workspace root for file-io, LSP, and file watching.\n */\nexport function findProjectRoot(startDir: string): { root: string; foundMarker: boolean } {\n let dir = startDir;\n while (true) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml')) || existsSync(join(dir, '.git'))) {\n return { root: dir, foundMarker: true };\n }\n const parent = dirname(dir);\n if (parent === dir || parent === parse(dir).root) {\n return { root: startDir, foundMarker: false };\n }\n dir = parent;\n }\n}\n","/**\n * In-memory cache of the list of git repo paths under $HOME and the linked\n * worktree paths discovered via `git worktree list --porcelain` for each.\n *\n * Distinct from `environments-cache.ts` (which caches per-repo branch info on\n * disk, keyed by `.git/HEAD` mtime). This one caches the LIST of repos and\n * worktrees — i.e. the `findGitRepos(homedir())` walk and the N parallel\n * `git worktree list` spawns. Those are the ~12-15s cost on a busy machine.\n *\n * Invalidation:\n * - 24h TTL — covers a multi-day daemon uptime well; new repos created\n * outside the daemon (`git clone` from a terminal) take up to 24h to\n * appear in the picker. Acceptable trade-off; the user can hit the\n * manual Refresh button to force-invalidate.\n * - `invalidateRepoPaths()` — called by:\n * * worktree-service (after add/remove)\n * * explicit refresh requests\n *\n * The walk + worktree spawns ARE NOT triggered by the capability-watcher's\n * normal events: scoped re-detect (`refreshMcpServers` etc.) skips\n * `detectEnvironments` entirely and reuses the existing `daemon.capabilities\n * ?.environments`. The cache is only consulted by full `detectCapabilities`\n * runs (5-min safety-net tick, manual Refresh button, boot, cwd-changed).\n */\n\nexport { getCachedRepoPaths, setCachedRepoPaths, invalidateRepoPaths, REPO_PATHS_CACHE_TTL_MS };\n\nconst REPO_PATHS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\ninterface CachedEntry {\n repos: string[];\n worktrees: string[];\n populatedAt: number;\n}\n\nlet cache: CachedEntry | null = null;\n\nfunction getCachedRepoPaths(\n now: number = Date.now()\n): { repos: string[]; worktrees: string[] } | null {\n if (!cache) return null;\n if (now - cache.populatedAt >= REPO_PATHS_CACHE_TTL_MS) {\n cache = null;\n return null;\n }\n return { repos: cache.repos, worktrees: cache.worktrees };\n}\n\nfunction setCachedRepoPaths(repos: string[], worktrees: string[], now: number = Date.now()): void {\n cache = { repos, worktrees, populatedAt: now };\n}\n\nfunction invalidateRepoPaths(): void {\n cache = null;\n}\n\nexport const _testing = {\n reset: invalidateRepoPaths,\n inspect: () => cache,\n};\n"],"mappings":"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","names":["join","dirname","join","join","dirname","join","logger"]}
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
DevicePollResponseSchema,
|
|
6
6
|
DeviceStartResponseSchema,
|
|
7
7
|
ROUTES
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZSBE4P76.js";
|
|
9
9
|
import {
|
|
10
10
|
print,
|
|
11
11
|
printError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-3TB4VNFG.js";
|
|
13
13
|
import {
|
|
14
14
|
getConfigPath,
|
|
15
15
|
loadAuthToken,
|
|
@@ -217,4 +217,4 @@ export {
|
|
|
217
217
|
ensureAuthenticated,
|
|
218
218
|
loginCommand
|
|
219
219
|
};
|
|
220
|
-
//# sourceMappingURL=chunk-
|
|
220
|
+
//# sourceMappingURL=chunk-3NMNYZN7.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getLogFilePath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YUG27SAR.js";
|
|
5
5
|
|
|
6
6
|
// src/shared/commands/output.ts
|
|
7
7
|
import { appendFileSync } from "fs";
|
|
@@ -40,4 +40,4 @@ export {
|
|
|
40
40
|
print,
|
|
41
41
|
printError
|
|
42
42
|
};
|
|
43
|
-
//# sourceMappingURL=chunk-
|
|
43
|
+
//# sourceMappingURL=chunk-3TB4VNFG.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
logger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YUG27SAR.js";
|
|
5
5
|
import {
|
|
6
6
|
external_exports
|
|
7
7
|
} from "./chunk-M5M6VC5F.js";
|
|
@@ -525,7 +525,23 @@ async function resolveAuthStatuses(servers, tokenStore) {
|
|
|
525
525
|
}
|
|
526
526
|
await resolveClaudeCodeAuthStatuses(servers);
|
|
527
527
|
}
|
|
528
|
-
async function detectMCPServers(environments, tokenStore, log) {
|
|
528
|
+
async function detectMCPServers(environments, tokenStore, log, lastKnown) {
|
|
529
|
+
try {
|
|
530
|
+
return await detectMCPServersInner(environments, tokenStore, log);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
const { logger: logger2 } = await import("./logger-7XW3I4XN.js");
|
|
533
|
+
if (lastKnown && lastKnown.length > 0) {
|
|
534
|
+
logger2.warn(
|
|
535
|
+
{ err, lastKnownCount: lastKnown.length },
|
|
536
|
+
"detectMCPServers threw \u2014 preserving lastKnown"
|
|
537
|
+
);
|
|
538
|
+
return lastKnown;
|
|
539
|
+
}
|
|
540
|
+
logger2.debug({ err }, "detectMCPServers threw with no lastKnown \u2014 returning []");
|
|
541
|
+
return [];
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function detectMCPServersInner(environments, tokenStore, log) {
|
|
529
545
|
const allServers = [];
|
|
530
546
|
const userSettingsPath = join2(homedir2(), ".claude", "settings.json");
|
|
531
547
|
const userLocalSettingsPath = join2(homedir2(), ".claude", "settings.local.json");
|
|
@@ -593,4 +609,4 @@ export {
|
|
|
593
609
|
redactEnv,
|
|
594
610
|
detectMCPServers
|
|
595
611
|
};
|
|
596
|
-
//# sourceMappingURL=chunk-
|
|
612
|
+
//# sourceMappingURL=chunk-5LIPEC7P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/mcp-servers.ts","../src/shared/mcp/claude-code-credentials.ts","../src/shared/mcp/schemas.ts","../src/shared/mcp/resolve-servers.ts","../src/shared/capabilities/account-integrations.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { GitRepoInfo, MCPServerInfo, MCPServerSource } from '@shipyard/session';\nimport { z } from 'zod';\nimport { readClaudeCodeCredentials } from '../mcp/claude-code-credentials.js';\nimport { VAULT_REF_PATTERN } from '../mcp/resolve-servers.js';\nimport { fetchClaudeAiIntegrations } from './account-integrations.js';\n\nconst MCPStdioEntrySchema = z\n .object({\n type: z.literal('stdio').optional(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .passthrough();\n\nconst MCPOAuthConfigSchema = z\n .object({\n clientId: z.string().optional(),\n callbackPort: z.number().optional(),\n })\n .passthrough();\n\nconst MCPHttpEntrySchema = z\n .object({\n type: z.literal('http'),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n oauth: MCPOAuthConfigSchema.optional(),\n })\n .passthrough();\n\nconst MCPSSEEntrySchema = z\n .object({\n type: z.literal('sse'),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n })\n .passthrough();\n\nconst MCPServerEntrySchema = z.union([MCPStdioEntrySchema, MCPHttpEntrySchema, MCPSSEEntrySchema]);\n\nconst SECRET_PATTERNS = /^(sk-|ghp_|gho_|glpat-|xoxb-|xoxp-|Bearer\\s|token\\s)/i;\nconst SECRET_FLAGS = new Set(['--api-key', '--token', '--secret', '--password', '--key', '-k']);\n\nexport function redactArgs(args: string[]): string[] {\n return args.map((arg, i) => {\n if (SECRET_PATTERNS.test(arg)) return '***';\n const prevArg = i > 0 ? args[i - 1] : undefined;\n if (prevArg && SECRET_FLAGS.has(prevArg)) return '***';\n const eqIdx = arg.indexOf('=');\n if (eqIdx > 0 && SECRET_FLAGS.has(arg.slice(0, eqIdx))) {\n return `${arg.slice(0, eqIdx + 1)}***`;\n }\n return arg;\n });\n}\n\n/**\n * Redact all environment variable values. Env vars passed to MCP servers\n * frequently contain secrets (API keys, tokens) and should never be\n * exposed to the browser.\n */\nexport function redactEnv(env: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const key of Object.keys(env)) {\n result[key] = '***';\n }\n return result;\n}\n\ntype MCPStdioEntry = z.infer<typeof MCPStdioEntrySchema>;\ntype MCPHttpEntry = z.infer<typeof MCPHttpEntrySchema>;\ntype MCPSSEEntry = z.infer<typeof MCPSSEEntrySchema>;\n\nfunction isHttpEntry(data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry): data is MCPHttpEntry {\n return 'type' in data && data.type === 'http';\n}\n\nfunction isSSEEntry(data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry): data is MCPSSEEntry {\n return 'type' in data && data.type === 'sse';\n}\n\nfunction entryToServerInfo(\n name: string,\n data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry,\n source: MCPServerSource\n): MCPServerInfo {\n if (isHttpEntry(data)) {\n return {\n name,\n type: 'http',\n url: data.url,\n headers: data.headers,\n oauth: data.oauth,\n enabled: true,\n source,\n authStatus: 'unknown' as const,\n };\n }\n if (isSSEEntry(data)) {\n return {\n name,\n type: 'sse',\n url: data.url,\n headers: data.headers,\n enabled: true,\n source,\n authStatus: 'unknown' as const,\n };\n }\n return {\n name,\n type: 'stdio',\n command: data.command,\n args: data.args,\n env: data.env,\n enabled: true,\n source,\n authStatus: 'unknown' as const,\n };\n}\n\nasync function readMCPConfig(\n filePath: string,\n source: MCPServerSource,\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): Promise<MCPServerInfo[]> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; validated per-entry below\n const json = JSON.parse(raw) as Record<string, unknown>;\n\n const entries =\n typeof json.mcpServers === 'object' && json.mcpServers !== null ? json.mcpServers : json;\n\n const servers: MCPServerInfo[] = [];\n for (const [name, value] of Object.entries(entries)) {\n if (name === 'mcpServers') continue;\n /** Skip non-object values — settings files may have non-MCP keys (e.g. enabledMcpjsonServers: []). */\n if (typeof value !== 'object' || value === null || Array.isArray(value)) continue;\n const result = MCPServerEntrySchema.safeParse(value);\n if (!result.success) {\n const entry = {\n event: 'mcp_config_entry_invalid',\n name,\n source,\n filePath,\n error: result.error.message,\n };\n if (log) log(entry);\n // biome-ignore lint/suspicious/noConsole: diagnostic fallback when no structured logger is injected\n else console.warn('[mcp-servers]', entry.event, entry.name, entry.error);\n continue;\n }\n servers.push(entryToServerInfo(name, result.data, source));\n }\n return servers;\n } catch {\n return [];\n }\n}\n\nasync function readPluginMCPServers(): Promise<MCPServerInfo[]> {\n const servers: MCPServerInfo[] = [];\n try {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n const settingsRaw = await readFile(settingsPath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; checking enabledPlugins field only\n const settings = JSON.parse(settingsRaw) as { enabledPlugins?: Record<string, boolean> };\n if (!settings.enabledPlugins) return [];\n\n const installedPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');\n const installedRaw = await readFile(installedPath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; checking plugins field only\n const installed = JSON.parse(installedRaw) as {\n plugins?: Record<string, Array<{ installPath?: string }>>;\n };\n if (!installed.plugins) return [];\n\n for (const [pluginId, enabled] of Object.entries(settings.enabledPlugins)) {\n if (!enabled) continue;\n const installs = installed.plugins[pluginId];\n if (!installs || installs.length === 0) continue;\n const installPath = installs[0]?.installPath;\n if (!installPath) continue;\n\n const mcpJsonPath = join(installPath, '.mcp.json');\n const pluginServers = await readMCPConfig(mcpJsonPath, 'plugin');\n servers.push(...pluginServers);\n }\n } catch {}\n return servers;\n}\n\n/** Priority order for source-based deduplication (higher index wins). */\nconst SOURCE_PRIORITY: readonly MCPServerSource[] = [\n 'claudeai',\n 'plugin',\n 'user',\n 'project',\n 'local',\n 'mcp-json',\n];\n\n/**\n * Check Claude Code's credential store for servers that still have\n * `authStatus === 'unknown'` after checking Shipyard's own token store.\n * If Claude Code has a valid (non-expired) token, mark the server as authenticated.\n */\nasync function resolveClaudeCodeAuthStatuses(servers: Map<string, MCPServerInfo>): Promise<void> {\n for (const server of servers.values()) {\n if (server.authStatus !== 'unknown') continue;\n if (server.type === 'stdio') continue;\n const ccCreds = await readClaudeCodeCredentials(server.name, server.url ?? '');\n if (ccCreds?.accessToken && ccCreds.expiresAt && ccCreds.expiresAt > Date.now()) {\n server.authStatus = 'authenticated';\n }\n }\n}\n\nfunction resolveStdioAuthStatus(\n server: MCPServerInfo,\n tokens: Record<string, import('../mcp/token-store.js').MCPOAuthToken>,\n tokenStore: import('../mcp/token-store.js').MCPTokenStore\n): void {\n if (!server.env) return;\n for (const value of Object.values(server.env)) {\n const match = VAULT_REF_PATTERN.exec(value);\n if (!match) continue;\n const vaultKey = match[1] ?? '';\n const token = tokens[vaultKey];\n if (token) {\n server.authStatus = tokenStore.isExpired(token) ? 'unauthenticated' : 'authenticated';\n return;\n }\n }\n}\n\nasync function resolveAuthStatuses(\n servers: Map<string, MCPServerInfo>,\n tokenStore: import('../mcp/token-store.js').MCPTokenStore\n): Promise<void> {\n const tokens = await tokenStore.getAllTokens();\n for (const server of servers.values()) {\n if (server.type === 'stdio') {\n resolveStdioAuthStatus(server, tokens, tokenStore);\n continue;\n }\n const token = tokens[server.name];\n if (token) {\n server.authStatus = tokenStore.isExpired(token) ? 'unauthenticated' : 'authenticated';\n }\n }\n\n await resolveClaudeCodeAuthStatuses(servers);\n}\n\n/**\n * Detect MCP servers across user/project/plugin/claude.ai sources.\n *\n * `lastKnown` preserves the previously-detected MCP server list when the\n * detection itself throws (rare — readMCPConfig swallows its own errors —\n * but defense in depth against future regressions and unhandled rejections\n * inside `fetchClaudeAiIntegrations`). Mirror of the `lastKnownAgents`\n * pattern in `agents.ts`.\n *\n * Note: under normal operation, errors are swallowed inside the per-file\n * readers, so this function reaches the dedupe step with a possibly-empty\n * but well-shaped `allServers`. The `try/catch` here is the chokepoint that\n * catches anything escaping that — particularly account-integrations\n * fetches that hit the network.\n */\nexport async function detectMCPServers(\n environments: GitRepoInfo[],\n tokenStore?: import('../mcp/token-store.js').MCPTokenStore,\n log?: (entry: { event: string; [key: string]: unknown }) => void,\n lastKnown?: MCPServerInfo[]\n): Promise<MCPServerInfo[]> {\n try {\n return await detectMCPServersInner(environments, tokenStore, log);\n } catch (err) {\n /**\n * Preserve the original \"always resolves\" contract: inner readers\n * already swallow per-file errors and return [], so this catch only\n * fires for unhandled rejections (network failure in\n * `fetchClaudeAiIntegrations`, future regressions). Returning\n * lastKnown if available, otherwise [] — never rethrow, because the\n * Promise.all in detectCapabilities would tear down the whole refresh\n * for a single slice's failure.\n */\n const { logger } = await import('../logger.js');\n if (lastKnown && lastKnown.length > 0) {\n logger.warn(\n { err, lastKnownCount: lastKnown.length },\n 'detectMCPServers threw — preserving lastKnown'\n );\n return lastKnown;\n }\n logger.debug({ err }, 'detectMCPServers threw with no lastKnown — returning []');\n return [];\n }\n}\n\nasync function detectMCPServersInner(\n environments: GitRepoInfo[],\n tokenStore?: import('../mcp/token-store.js').MCPTokenStore,\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): Promise<MCPServerInfo[]> {\n const allServers: MCPServerInfo[] = [];\n\n const userSettingsPath = join(homedir(), '.claude', 'settings.json');\n const userLocalSettingsPath = join(homedir(), '.claude', 'settings.local.json');\n const userMcpJsonPath = join(homedir(), '.mcp.json');\n const userClaudeJsonPath = join(homedir(), '.claude.json');\n\n const [\n userServers,\n userLocalServers,\n userMcpJsonServers,\n userClaudeJsonServers,\n pluginServers,\n claudeAiServers,\n ] = await Promise.all([\n readMCPConfig(userSettingsPath, 'user', log),\n readMCPConfig(userLocalSettingsPath, 'user', log),\n readMCPConfig(userMcpJsonPath, 'user', log),\n readMCPConfig(userClaudeJsonPath, 'user', log),\n readPluginMCPServers(),\n fetchClaudeAiIntegrations(log),\n ]);\n allServers.push(\n ...claudeAiServers,\n ...pluginServers,\n ...userServers,\n ...userLocalServers,\n ...userMcpJsonServers,\n ...userClaudeJsonServers\n );\n\n const envResults = await Promise.all(\n environments.map((env) => {\n const projectPath = join(env.path, '.claude', 'settings.json');\n const localPath = join(env.path, '.claude', 'settings.local.json');\n const mcpJsonPath = join(env.path, '.mcp.json');\n return Promise.all([\n readMCPConfig(projectPath, 'project', log),\n readMCPConfig(localPath, 'local', log),\n readMCPConfig(mcpJsonPath, 'mcp-json', log),\n ]);\n })\n );\n for (const [projectServers, localServers, mcpJsonServers] of envResults) {\n allServers.push(...projectServers, ...localServers, ...mcpJsonServers);\n }\n\n const deduped = new Map<string, MCPServerInfo>();\n for (const server of allServers) {\n const existing = deduped.get(server.name);\n if (\n !existing ||\n SOURCE_PRIORITY.indexOf(server.source) >= SOURCE_PRIORITY.indexOf(existing.source)\n ) {\n deduped.set(server.name, server);\n }\n }\n\n if (tokenStore) {\n await resolveAuthStatuses(deduped, tokenStore);\n }\n\n return [...deduped.values()];\n}\n","import { execFile as execFileCb } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js';\nimport { z } from 'zod';\n\nimport { logger } from '../logger.js';\nimport { DiscoveryStateSchema } from './schemas.js';\n\nconst execFile = promisify(execFileCb);\n\n/**\n * Represents a single OAuth entry from Claude Code's credential store.\n *\n * Claude Code v2.1.81+ stores credentials in macOS Keychain under\n * service name \"Claude Code-credentials\". Older versions used\n * `~/.claude/.credentials.json`.\n *\n * The format is owned by Claude Code and may change without notice.\n * All fields are optional to tolerate schema drift.\n */\nexport interface ClaudeCodeOAuthEntry {\n serverName: string;\n serverUrl: string;\n clientId?: string;\n clientSecret?: string;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n discoveryState?: OAuthDiscoveryState;\n}\n\n/**\n * Schema for a single entry in `mcpOAuth`. We use `.passthrough()` so\n * unknown future fields are preserved.\n *\n * CC stores `null` for cleared fields (e.g., `accessToken: null` after\n * logout). Zod's `.optional()` only accepts `undefined`, not `null`,\n * so we must use `.nullable().optional()` to tolerate both.\n */\nconst ClaudeCodeOAuthEntrySchema = z\n .object({\n serverName: z.string().nullable().optional(),\n serverUrl: z.string().nullable().optional(),\n clientId: z.string().nullable().optional(),\n clientSecret: z.string().nullable().optional(),\n accessToken: z.string().nullable().optional(),\n refreshToken: z.string().nullable().optional(),\n expiresAt: z.number().nullable().optional(),\n discoveryState: DiscoveryStateSchema.nullable().optional(),\n })\n .passthrough();\n\nconst CredentialsFileSchema = z\n .object({\n mcpOAuth: z.record(z.string(), z.unknown()).optional(),\n })\n .passthrough();\n\nconst CREDENTIALS_PATH = join(homedir(), '.claude', '.credentials.json');\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\nconst PluginMcpJsonSchema = z.record(\n z.string(),\n z\n .object({\n url: z.string().optional(),\n oauth: z.object({ clientId: z.string(), callbackPort: z.number().optional() }).optional(),\n })\n .passthrough()\n);\n\n/**\n * Read OAuth clientIds from CC plugin `.mcp.json` files.\n * Returns a Map from server URL to clientId.\n */\nasync function extractClientIdsFromPlugin(\n installPath: string,\n result: Map<string, string>\n): Promise<void> {\n const raw = await readFile(join(installPath, '.mcp.json'), 'utf-8');\n const json: unknown = JSON.parse(raw);\n const outer = z.record(z.string(), z.unknown()).safeParse(json);\n if (!outer.success) return;\n const servers = outer.data.mcpServers ?? outer.data;\n const validated = PluginMcpJsonSchema.safeParse(servers);\n if (!validated.success) return;\n\n for (const config of Object.values(validated.data)) {\n if (config.url && config.oauth?.clientId) {\n result.set(config.url, config.oauth.clientId);\n }\n }\n}\n\nexport async function readPluginClientIds(): Promise<Map<string, string>> {\n const result = new Map<string, string>();\n try {\n const installedPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');\n const InstalledPluginsSchema = z.object({\n plugins: z\n .record(z.string(), z.array(z.object({ installPath: z.string().optional() })))\n .optional(),\n });\n const parsed = InstalledPluginsSchema.safeParse(\n JSON.parse(await readFile(installedPath, 'utf-8'))\n );\n if (!parsed.success || !parsed.data.plugins) return result;\n\n for (const installs of Object.values(parsed.data.plugins)) {\n const installPath = installs?.[0]?.installPath;\n if (!installPath) continue;\n await extractClientIdsFromPlugin(installPath, result).catch(() => {});\n }\n } catch {}\n return result;\n}\n\n/**\n * Attempt to read Claude Code's credential blob from macOS Keychain.\n * Returns the parsed JSON object or null if unavailable.\n */\nasync function readKeychainCredentials(): Promise<Record<string, unknown> | null> {\n if (process.platform !== 'darwin') return null;\n\n try {\n const { stdout } = await execFile(\n 'security',\n ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'],\n { timeout: 5_000 }\n );\n const result = z.record(z.string(), z.unknown()).safeParse(JSON.parse(stdout.trim()));\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Search a parsed credentials object for the best MCP OAuth entry\n * matching the given `serverUrl`. Prefers entries with an access token;\n * separately gathers clientId from any matching entry.\n *\n * CC stores entries under both bare (`slack`) and plugin-prefixed\n * (`plugin:slack:slack`) keys. The prefixed entries typically have\n * tokens while the bare entries may only have clientId. We merge\n * across all matching entries to get the most complete picture.\n */\n/** Scan a flat mcpOAuth record for entries matching serverUrl. */\nfunction scanEntries(\n mcpOAuth: Record<string, unknown>,\n serverUrl: string\n): {\n bestWithToken: z.infer<typeof ClaudeCodeOAuthEntrySchema> | null;\n clientId?: string;\n clientSecret?: string;\n discoveryState?: OAuthDiscoveryState;\n} {\n let bestWithToken: z.infer<typeof ClaudeCodeOAuthEntrySchema> | null = null;\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n let discoveryState: OAuthDiscoveryState | undefined;\n\n for (const value of Object.values(mcpOAuth)) {\n const entryResult = ClaudeCodeOAuthEntrySchema.safeParse(value);\n if (!entryResult.success) continue;\n\n const entry = entryResult.data;\n if (entry.serverUrl !== serverUrl) continue;\n\n if (entry.clientId && !clientId) {\n clientId = entry.clientId;\n clientSecret = entry.clientSecret || undefined;\n }\n if (entry.discoveryState && !discoveryState) {\n discoveryState = entry.discoveryState;\n }\n if (entry.accessToken && !bestWithToken) {\n bestWithToken = entry;\n }\n }\n\n return { bestWithToken, clientId, clientSecret, discoveryState };\n}\n\nfunction findMatchingEntry(\n credentialsData: Record<string, unknown>,\n serverName: string,\n serverUrl: string\n): ClaudeCodeOAuthEntry | null {\n const fileResult = CredentialsFileSchema.safeParse(credentialsData);\n if (!fileResult.success) return null;\n\n const mcpOAuth = fileResult.data.mcpOAuth;\n if (!mcpOAuth) return null;\n\n const { bestWithToken, clientId, clientSecret, discoveryState } = scanEntries(\n mcpOAuth,\n serverUrl\n );\n if (!bestWithToken && !clientId) return null;\n\n return {\n serverName: bestWithToken?.serverName ?? serverName,\n serverUrl,\n clientId: bestWithToken?.clientId || clientId,\n clientSecret: bestWithToken?.clientSecret || clientSecret || undefined,\n accessToken: bestWithToken?.accessToken || undefined,\n refreshToken: bestWithToken?.refreshToken || undefined,\n expiresAt: bestWithToken?.expiresAt ?? undefined,\n discoveryState: bestWithToken?.discoveryState ?? discoveryState ?? undefined,\n };\n}\n\n/**\n * Search Claude Code's credential store for an MCP OAuth entry\n * matching the given `serverUrl`.\n *\n * Tries macOS Keychain first (Claude Code v2.1.81+), then falls back\n * to the legacy file at `~/.claude/.credentials.json`.\n *\n * Keys in the store use hashes we cannot reproduce, so we match\n * by `serverUrl` instead. Returns the first entry whose `serverUrl`\n * matches and that has a non-empty `clientId`.\n */\nexport async function readClaudeCodeCredentials(\n serverName: string,\n serverUrl: string\n): Promise<ClaudeCodeOAuthEntry | null> {\n if (!serverUrl) return null;\n\n try {\n const keychainData = await readKeychainCredentials();\n if (keychainData) {\n const entry = findMatchingEntry(keychainData, serverName, serverUrl);\n if (entry) {\n logger.debug({ serverUrl }, 'Found Claude Code credentials in macOS Keychain');\n return entry;\n }\n }\n } catch (err: unknown) {\n logger.debug({ err }, 'Failed to read Claude Code credentials from Keychain');\n }\n\n try {\n const raw = await readFile(CREDENTIALS_PATH, 'utf-8');\n const fileResult = CredentialsFileSchema.safeParse(JSON.parse(raw));\n if (fileResult.success) {\n const entry = findMatchingEntry(fileResult.data, serverName, serverUrl);\n if (entry) {\n logger.debug({ serverUrl }, 'Found Claude Code credentials in legacy file');\n return entry;\n }\n }\n\n return null;\n } catch (err: unknown) {\n logger.debug({ err }, 'Failed to read Claude Code credentials from file');\n return null;\n }\n}\n","import {\n OAuthMetadataSchema,\n OAuthProtectedResourceMetadataSchema,\n OpenIdProviderDiscoveryMetadataSchema,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\nimport { z } from 'zod';\n\nexport const AuthorizationServerMetadataSchema = z.union([\n OAuthMetadataSchema,\n OpenIdProviderDiscoveryMetadataSchema,\n]);\n\nexport const DiscoveryStateSchema = z\n .object({\n authorizationServerUrl: z.string(),\n authorizationServerMetadata: AuthorizationServerMetadataSchema.optional(),\n resourceMetadata: OAuthProtectedResourceMetadataSchema.optional(),\n resourceMetadataUrl: z.string().optional(),\n })\n .passthrough();\n","import type { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';\nimport type { MCPServerInfo } from '@shipyard/session';\n\nimport type { MCPTokenStore } from './token-store.js';\n\nexport const ENV_VAR_PATTERN = /\\$\\{([^}]+)\\}/g;\nexport const VAULT_REF_PATTERN = /^\\$\\{vault:([^}]+)\\}$/;\n\nexport function interpolateEnvVars(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n result[key] = value.replace(\n ENV_VAR_PATTERN,\n (_, varName: string) => process.env[varName] ?? ''\n );\n }\n return result;\n}\n\n/**\n * Pass through user-configured static headers only. OAuth tokens for HTTP\n * servers are managed by the SDK subprocess via CC's Keychain — injecting\n * Shipyard's own tokens here would conflict with the SDK's OAuth transport.\n * Token store injection is only used for stdio env vars (resolveStdioEnv).\n */\nexport function resolveHeaders(server: MCPServerInfo): Record<string, string> | undefined {\n if (!server.headers) return undefined;\n return interpolateEnvVars(server.headers);\n}\n\nexport function resolveStdioEnv(\n env: Record<string, string> | undefined,\n store?: MCPTokenStore,\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): Record<string, string> | undefined {\n if (!env) return undefined;\n if (!store) return env;\n\n const result: Record<string, string> = {};\n let changed = false;\n\n for (const [key, value] of Object.entries(env)) {\n const match = VAULT_REF_PATTERN.exec(value);\n if (match) {\n const vaultKey = match[1] ?? '';\n const token = store.getTokenSync(vaultKey);\n if (token && !store.isExpired(token)) {\n result[key] = token.accessToken;\n changed = true;\n log?.({\n event: 'vault_ref_resolved',\n envKey: key,\n vaultKey,\n tokenType: token.tokenType,\n hasExpiry: token.expiresAt !== undefined,\n });\n continue;\n }\n log?.({\n event: 'vault_ref_unresolved',\n envKey: key,\n vaultKey,\n tokenFound: token !== null,\n tokenExpired: token ? store.isExpired(token) : false,\n });\n }\n result[key] = value;\n }\n\n return changed ? result : env;\n}\n\nexport function resolveEnabledMcpServers(\n overrides: Array<{ name: string; enabled: boolean }> | undefined,\n available: MCPServerInfo[] | undefined,\n mcpTokenStore?: MCPTokenStore\n): Record<string, McpServerConfig> | undefined {\n if (!overrides || !available) return undefined;\n const enabledNames = new Set(overrides.filter((o) => o.enabled).map((o) => o.name));\n if (enabledNames.size === 0) return {};\n const result: Record<string, McpServerConfig> = {};\n for (const server of available) {\n if (!enabledNames.has(server.name)) continue;\n if (server.type === 'http' && server.url) {\n result[server.name] = {\n type: 'http',\n url: server.url,\n headers: resolveHeaders(server),\n };\n } else if (server.type === 'sse' && server.url) {\n result[server.name] = {\n type: 'sse',\n url: server.url,\n headers: resolveHeaders(server),\n };\n } else if (server.command) {\n result[server.name] = {\n command: server.command,\n args: server.args ?? undefined,\n env: resolveStdioEnv(server.env, mcpTokenStore),\n };\n }\n }\n return result;\n}\n","import { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { MCPServerInfo } from '@shipyard/session';\n\nimport { logger } from '../logger.js';\n\nconst execFile = promisify(execFileCb);\n\n/**\n * Read the Anthropic OAuth token from macOS Keychain (\"Claude Code\" service).\n * Returns null if the platform is not macOS, the entry does not exist,\n * or the stored value is an API key (starts with `sk-`) rather than an OAuth token.\n */\nexport async function readAnthropicOAuthToken(): Promise<string | null> {\n if (process.platform !== 'darwin') return null;\n\n try {\n const { stdout } = await execFile(\n 'security',\n ['find-generic-password', '-s', 'Claude Code', '-w'],\n { timeout: 5_000 }\n );\n const token = stdout.trim();\n if (!token || token.startsWith('sk-')) return null;\n return token;\n } catch {\n return null;\n }\n}\n\n/**\n * Minimal shape we expect from the Anthropic MCP servers API response.\n * Hand-rolled instead of Zod to keep best-effort parsing lightweight.\n */\nfunction parseClaudeAiServers(data: unknown): Array<{ name: string; url: string }> {\n if (typeof data !== 'object' || data === null) return [];\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; manually validated below\n const obj = data as Record<string, unknown>;\n if (!Array.isArray(obj.data)) return [];\n const results: Array<{ name: string; url: string }> = [];\n for (const entry of obj.data) {\n if (typeof entry !== 'object' || entry === null) continue;\n // eslint-disable-next-line no-restricted-syntax -- narrowing unknown entry fields\n const rec = entry as Record<string, unknown>;\n if (typeof rec.name === 'string' && typeof rec.url === 'string') {\n results.push({ name: rec.name, url: rec.url });\n }\n }\n return results;\n}\n\n/**\n * Fetch MCP server integrations from the user's Claude.ai account.\n * Best-effort: returns [] on any failure.\n */\nexport async function fetchClaudeAiIntegrations(\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): Promise<MCPServerInfo[]> {\n try {\n const token = await readAnthropicOAuthToken();\n if (!token) return [];\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n\n try {\n const response = await fetch('https://api.anthropic.com/v1/mcp_servers?limit=1000', {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const debugLog = log ?? ((entry: { event: string }) => logger.debug(entry, entry.event));\n debugLog({\n event: 'claudeai_integrations_fetch_failed',\n status: response.status,\n });\n return [];\n }\n\n const body: unknown = await response.json();\n const servers = parseClaudeAiServers(body);\n\n return servers.map((s) => ({\n name: s.name,\n type: 'http' as const,\n url: s.url,\n enabled: true,\n source: 'claudeai' as const,\n authStatus: 'unknown' as const,\n }));\n } finally {\n clearTimeout(timeout);\n }\n } catch (err: unknown) {\n const debugLog = log ?? ((entry: { event: string }) => logger.debug(entry, entry.event));\n debugLog({\n event: 'claudeai_integrations_fetch_error',\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n}\n"],"mappings":"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","names":["readFile","homedir","join","execFileCb","promisify","execFile","promisify","execFileCb","readFile","join","homedir","logger"]}
|