@schoolai/shipyard 1.2.0 → 2.0.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/dist/{auth-XINVA3ZW.js → auth-V6KVU7VA.js} +4 -3
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/{chunk-FY3DRRGT.js → chunk-5OHYOIOG.js} +3 -3
- package/dist/chunk-5OHYOIOG.js.map +1 -0
- package/dist/{chunk-HS57GMAL.js → chunk-6AQKMCGK.js} +15 -9
- package/dist/chunk-6AQKMCGK.js.map +1 -0
- package/dist/chunk-BYFLMOF7.js +591 -0
- package/dist/chunk-BYFLMOF7.js.map +1 -0
- package/dist/{login-Q6PDH6HF.js → chunk-C4SKAWEG.js} +104 -40
- package/dist/chunk-C4SKAWEG.js.map +1 -0
- package/dist/chunk-DPMRSLYJ.js +109 -0
- package/dist/chunk-DPMRSLYJ.js.map +1 -0
- package/dist/{chunk-K3GFMEBF.js → chunk-JFEQEK53.js} +3 -3
- package/dist/chunk-JFEQEK53.js.map +1 -0
- package/dist/{chunk-U647WKG5.js → chunk-L6BFDLLZ.js} +264 -48
- package/dist/chunk-L6BFDLLZ.js.map +1 -0
- package/dist/{chunk-6VAYVPEL.js → chunk-ZU4YUN33.js} +8 -8
- package/dist/chunk-ZU4YUN33.js.map +1 -0
- package/dist/index.js +41 -49812
- package/dist/index.js.map +1 -1
- package/dist/login-JWAG3GPR.js +18 -0
- package/dist/login-JWAG3GPR.js.map +1 -0
- package/dist/{logout-XX5ULFHB.js → logout-PIKY2YCJ.js} +7 -6
- package/dist/logout-PIKY2YCJ.js.map +1 -0
- package/dist/mcp-servers-2MAQ6WKL.js +15 -0
- package/dist/mcp-servers-2MAQ6WKL.js.map +1 -0
- package/dist/plugin-mcp-linear.d.ts +2 -0
- package/dist/plugin-mcp-linear.js +1683 -0
- package/dist/plugin-mcp-linear.js.map +1 -0
- package/dist/serve-2FNONIDL.js +69583 -0
- package/dist/serve-2FNONIDL.js.map +1 -0
- package/dist/skills-NCKYNLUS.js +9 -0
- package/dist/skills-NCKYNLUS.js.map +1 -0
- package/dist/start-CQC22BQF.js +35 -0
- package/dist/start-CQC22BQF.js.map +1 -0
- package/package.json +8 -3
- package/dist/chunk-6VAYVPEL.js.map +0 -1
- package/dist/chunk-FY3DRRGT.js.map +0 -1
- package/dist/chunk-HS57GMAL.js.map +0 -1
- package/dist/chunk-K3GFMEBF.js.map +0 -1
- package/dist/chunk-U647WKG5.js.map +0 -1
- package/dist/login-Q6PDH6HF.js.map +0 -1
- package/dist/logout-XX5ULFHB.js.map +0 -1
- /package/dist/{auth-XINVA3ZW.js.map → auth-V6KVU7VA.js.map} +0 -0
|
@@ -1,48 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ROUTES
|
|
4
|
-
} from "./chunk-U647WKG5.js";
|
|
5
2
|
import {
|
|
6
3
|
print,
|
|
7
4
|
printError
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import
|
|
5
|
+
} from "./chunk-JFEQEK53.js";
|
|
6
|
+
import {
|
|
7
|
+
DeviceExchangeCodeResponseSchema,
|
|
8
|
+
DevicePollPendingSchema,
|
|
9
|
+
DevicePollResponseSchema,
|
|
10
|
+
DeviceStartResponseSchema,
|
|
11
|
+
ROUTES
|
|
12
|
+
} from "./chunk-L6BFDLLZ.js";
|
|
10
13
|
import {
|
|
11
14
|
getConfigPath,
|
|
15
|
+
loadAuthToken,
|
|
12
16
|
readConfig,
|
|
13
17
|
writeConfig
|
|
14
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ZU4YUN33.js";
|
|
15
19
|
import {
|
|
16
20
|
external_exports,
|
|
17
21
|
isDevMode
|
|
18
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-6AQKMCGK.js";
|
|
19
23
|
|
|
20
|
-
// src/commands/login.ts
|
|
24
|
+
// src/shared/commands/login.ts
|
|
21
25
|
var DEFAULT_SIGNALING_URL = "https://shipyard-session-server.jacob-191.workers.dev";
|
|
22
26
|
var DEFAULT_DEV_SIGNALING_URL = "http://localhost:4444";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
});
|
|
38
|
-
var DevicePollErrorSchema = external_exports.object({
|
|
39
|
-
error: external_exports.string()
|
|
40
|
-
});
|
|
41
|
-
async function loginCommand(options) {
|
|
42
|
-
if (options.check) {
|
|
43
|
-
return checkLogin();
|
|
27
|
+
function getSignalingUrl() {
|
|
28
|
+
return process.env.SHIPYARD_SIGNALING_URL ?? (isDevMode() ? DEFAULT_DEV_SIGNALING_URL : DEFAULT_SIGNALING_URL);
|
|
29
|
+
}
|
|
30
|
+
async function ensureAuthenticated(opts) {
|
|
31
|
+
const existing = await loadAuthToken();
|
|
32
|
+
if (existing.status === "ok") {
|
|
33
|
+
return {
|
|
34
|
+
token: existing.token,
|
|
35
|
+
userId: existing.userId,
|
|
36
|
+
displayName: existing.displayName,
|
|
37
|
+
signalingUrl: existing.signalingUrl ?? opts.signalingUrl,
|
|
38
|
+
deviceFlowRan: false
|
|
39
|
+
};
|
|
44
40
|
}
|
|
45
|
-
|
|
41
|
+
if (existing.status === "expired") {
|
|
42
|
+
print(" Token expired. Starting re-authentication...\n");
|
|
43
|
+
}
|
|
44
|
+
if (opts.webCode) {
|
|
45
|
+
try {
|
|
46
|
+
return await runWebCodeFlow(opts.signalingUrl, opts.webCode);
|
|
47
|
+
} catch {
|
|
48
|
+
print(" Web code expired or invalid. Starting standard login...\n");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return runDeviceFlow(opts.signalingUrl);
|
|
52
|
+
}
|
|
53
|
+
async function runDeviceFlow(signalingUrl) {
|
|
46
54
|
const startData = await startDeviceFlow(signalingUrl);
|
|
47
55
|
print(` Open this URL in your browser:
|
|
48
56
|
`);
|
|
@@ -51,23 +59,69 @@ async function loginCommand(options) {
|
|
|
51
59
|
print(` Your code: ${startData.userCode}
|
|
52
60
|
`);
|
|
53
61
|
print(" Waiting for authorization...");
|
|
62
|
+
if (!process.env.SHIPYARD_NO_OPEN) {
|
|
63
|
+
import("open").then(({ default: open }) => open(startData.verificationUri)).catch(() => {
|
|
64
|
+
});
|
|
65
|
+
}
|
|
54
66
|
const interval = (startData.interval ?? 5) * 1e3;
|
|
55
67
|
const deadline = Date.now() + startData.expiresIn * 1e3;
|
|
56
68
|
while (Date.now() < deadline) {
|
|
57
69
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
58
|
-
|
|
70
|
+
try {
|
|
71
|
+
const pollRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_POLL}`, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "Content-Type": "application/json" },
|
|
74
|
+
body: JSON.stringify({ deviceCode: startData.deviceCode })
|
|
75
|
+
});
|
|
76
|
+
if (pollRes.ok) {
|
|
77
|
+
const result = await handleSuccessfulPoll(pollRes, signalingUrl);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
await handlePollError(pollRes, interval);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
printError("\n Authorization timed out. Run `shipyard login` again.");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
async function runWebCodeFlow(signalingUrl, userCode) {
|
|
88
|
+
print(" Authenticating via web app code...\n");
|
|
89
|
+
let deviceCode;
|
|
90
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
91
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3));
|
|
92
|
+
const exchangeRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_EXCHANGE_CODE}`, {
|
|
59
93
|
method: "POST",
|
|
60
94
|
headers: { "Content-Type": "application/json" },
|
|
61
|
-
body: JSON.stringify({
|
|
95
|
+
body: JSON.stringify({ userCode })
|
|
62
96
|
});
|
|
63
|
-
if (
|
|
64
|
-
await
|
|
65
|
-
|
|
97
|
+
if (exchangeRes.ok) {
|
|
98
|
+
const parsed = DeviceExchangeCodeResponseSchema.parse(await exchangeRes.json());
|
|
99
|
+
deviceCode = parsed.deviceCode;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
if (attempt === 2) {
|
|
103
|
+
throw new Error(`Exchange failed after 3 attempts: ${exchangeRes.status}`);
|
|
66
104
|
}
|
|
67
|
-
await handlePollError(pollRes, interval);
|
|
68
105
|
}
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
if (!deviceCode) throw new Error("Exchange failed: no device code");
|
|
107
|
+
const pollRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_POLL}`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { "Content-Type": "application/json" },
|
|
110
|
+
body: JSON.stringify({ deviceCode })
|
|
111
|
+
});
|
|
112
|
+
if (!pollRes.ok) {
|
|
113
|
+
throw new Error(`Poll failed: ${pollRes.status}`);
|
|
114
|
+
}
|
|
115
|
+
const result = await handleSuccessfulPoll(pollRes, signalingUrl);
|
|
116
|
+
print(" Authenticated via web app\n");
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
async function loginCommand(options) {
|
|
120
|
+
if (options.check) {
|
|
121
|
+
return checkLogin();
|
|
122
|
+
}
|
|
123
|
+
const signalingUrl = getSignalingUrl();
|
|
124
|
+
await runDeviceFlow(signalingUrl);
|
|
71
125
|
}
|
|
72
126
|
async function startDeviceFlow(signalingUrl) {
|
|
73
127
|
print("Starting device authorization...\n");
|
|
@@ -117,10 +171,17 @@ async function handleSuccessfulPoll(pollRes, signalingUrl) {
|
|
|
117
171
|
print(`
|
|
118
172
|
Logged in as ${pollData.user.displayName} (${pollData.user.providers.join(", ")})`);
|
|
119
173
|
print(` Token saved to ${getConfigPath()}`);
|
|
174
|
+
return {
|
|
175
|
+
token: pollData.token,
|
|
176
|
+
userId: pollData.user.id,
|
|
177
|
+
displayName: pollData.user.displayName,
|
|
178
|
+
signalingUrl,
|
|
179
|
+
deviceFlowRan: true
|
|
180
|
+
};
|
|
120
181
|
}
|
|
121
182
|
async function handlePollError(pollRes, interval) {
|
|
122
183
|
try {
|
|
123
|
-
const errorData =
|
|
184
|
+
const errorData = DevicePollPendingSchema.parse(await pollRes.json());
|
|
124
185
|
if (errorData.error === "expired_token") {
|
|
125
186
|
printError("\n Authorization expired. Run `shipyard login` again.");
|
|
126
187
|
process.exit(1);
|
|
@@ -145,7 +206,10 @@ async function checkLogin() {
|
|
|
145
206
|
print(`Logged in as ${config.auth.displayName} (${config.auth.providers.join(", ")})`);
|
|
146
207
|
print(`Token expires in ${daysLeft} days`);
|
|
147
208
|
}
|
|
209
|
+
|
|
148
210
|
export {
|
|
211
|
+
getSignalingUrl,
|
|
212
|
+
ensureAuthenticated,
|
|
149
213
|
loginCommand
|
|
150
214
|
};
|
|
151
|
-
//# sourceMappingURL=
|
|
215
|
+
//# sourceMappingURL=chunk-C4SKAWEG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/commands/login.ts"],"sourcesContent":["import {\n DeviceExchangeCodeResponseSchema,\n DevicePollPendingSchema,\n DevicePollResponseSchema,\n DeviceStartResponseSchema,\n ROUTES,\n} from '@shipyard/session';\nimport { z } from 'zod';\nimport {\n getConfigPath,\n loadAuthToken,\n readConfig,\n type ShipyardConfig,\n writeConfig,\n} from '../../services/bootstrap/auth.js';\nimport { isDevMode } from '../env.js';\nimport { print, printError } from './output.js';\n\nconst DEFAULT_SIGNALING_URL = 'https://shipyard-session-server.jacob-191.workers.dev';\nconst DEFAULT_DEV_SIGNALING_URL = 'http://localhost:4444';\n\nexport function getSignalingUrl(): string {\n return (\n process.env.SHIPYARD_SIGNALING_URL ??\n (isDevMode() ? DEFAULT_DEV_SIGNALING_URL : DEFAULT_SIGNALING_URL)\n );\n}\n\nexport interface AuthConfig {\n token: string;\n userId: string;\n displayName: string;\n signalingUrl: string;\n}\n\nexport interface AuthResult extends AuthConfig {\n deviceFlowRan: boolean;\n}\n\n/**\n * Ensure the user is authenticated, running the device flow interactively if needed.\n * Returns the auth config on success, or calls process.exit(1) on failure.\n */\nexport async function ensureAuthenticated(opts: {\n signalingUrl: string;\n webCode?: string;\n}): Promise<AuthResult> {\n const existing = await loadAuthToken();\n\n if (existing.status === 'ok') {\n return {\n token: existing.token,\n userId: existing.userId,\n displayName: existing.displayName,\n signalingUrl: existing.signalingUrl ?? opts.signalingUrl,\n deviceFlowRan: false,\n };\n }\n\n if (existing.status === 'expired') {\n print(' Token expired. Starting re-authentication...\\n');\n }\n\n if (opts.webCode) {\n try {\n return await runWebCodeFlow(opts.signalingUrl, opts.webCode);\n } catch {\n print(' Web code expired or invalid. Starting standard login...\\n');\n }\n }\n\n return runDeviceFlow(opts.signalingUrl);\n}\n\nasync function runDeviceFlow(signalingUrl: string): Promise<AuthResult> {\n const startData = await startDeviceFlow(signalingUrl);\n\n print(` Open this URL in your browser:\\n`);\n print(` ${startData.verificationUri}\\n`);\n print(` Your code: ${startData.userCode}\\n`);\n print(' Waiting for authorization...');\n\n if (!process.env.SHIPYARD_NO_OPEN) {\n import('open').then(({ default: open }) => open(startData.verificationUri)).catch(() => {});\n }\n\n const interval = (startData.interval ?? 5) * 1000;\n const deadline = Date.now() + startData.expiresIn * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, interval));\n\n try {\n const pollRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_POLL}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ deviceCode: startData.deviceCode }),\n });\n\n if (pollRes.ok) {\n const result = await handleSuccessfulPoll(pollRes, signalingUrl);\n return result;\n }\n\n await handlePollError(pollRes, interval);\n } catch {}\n }\n\n printError('\\n Authorization timed out. Run `shipyard login` again.');\n process.exit(1);\n}\n\nasync function runWebCodeFlow(signalingUrl: string, userCode: string): Promise<AuthResult> {\n print(' Authenticating via web app code...\\n');\n\n let deviceCode: string | undefined;\n for (let attempt = 0; attempt < 3; attempt++) {\n if (attempt > 0) await new Promise((r) => setTimeout(r, 1000));\n\n const exchangeRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_EXCHANGE_CODE}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ userCode }),\n });\n\n if (exchangeRes.ok) {\n const parsed = DeviceExchangeCodeResponseSchema.parse(await exchangeRes.json());\n deviceCode = parsed.deviceCode;\n break;\n }\n\n if (attempt === 2) {\n throw new Error(`Exchange failed after 3 attempts: ${exchangeRes.status}`);\n }\n }\n\n if (!deviceCode) throw new Error('Exchange failed: no device code');\n\n const pollRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_POLL}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ deviceCode }),\n });\n\n if (!pollRes.ok) {\n throw new Error(`Poll failed: ${pollRes.status}`);\n }\n\n const result = await handleSuccessfulPoll(pollRes, signalingUrl);\n print(' Authenticated via web app\\n');\n return result;\n}\n\nexport async function loginCommand(options: { check?: boolean }): Promise<void> {\n if (options.check) {\n return checkLogin();\n }\n\n const signalingUrl = getSignalingUrl();\n await runDeviceFlow(signalingUrl);\n}\n\nasync function startDeviceFlow(signalingUrl: string) {\n print('Starting device authorization...\\n');\n\n let startRes: Response;\n try {\n startRes = await fetch(`${signalingUrl}${ROUTES.AUTH_DEVICE_START}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n });\n } catch {\n printError(`Could not reach signaling server at ${signalingUrl}`);\n printError('Check your internet connection or set SHIPYARD_SIGNALING_URL.');\n process.exit(1);\n }\n\n if (!startRes.ok) {\n printError(`Failed to start device flow: ${startRes.status}`);\n process.exit(1);\n }\n\n return DeviceStartResponseSchema.parse(await startRes.json());\n}\n\nfunction extractTokenExpiry(token: string): number {\n const fallback = Date.now() + 30 * 24 * 60 * 60 * 1000;\n try {\n const payloadB64 = token.split('.')[1];\n if (!payloadB64) return fallback;\n const payload: unknown = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());\n const exp = z.object({ exp: z.number() }).safeParse(payload);\n return exp.success ? exp.data.exp * 1000 : fallback;\n } catch {\n return fallback;\n }\n}\n\nasync function handleSuccessfulPoll(pollRes: Response, signalingUrl: string): Promise<AuthResult> {\n const pollData = DevicePollResponseSchema.parse(await pollRes.json());\n const expiresAt = extractTokenExpiry(pollData.token);\n\n const config: ShipyardConfig = {\n auth: {\n token: pollData.token,\n userId: pollData.user.id,\n displayName: pollData.user.displayName,\n providers: pollData.user.providers,\n expiresAt,\n signalingUrl,\n },\n };\n\n await writeConfig(config);\n\n print(`\\n Logged in as ${pollData.user.displayName} (${pollData.user.providers.join(', ')})`);\n print(` Token saved to ${getConfigPath()}`);\n\n return {\n token: pollData.token,\n userId: pollData.user.id,\n displayName: pollData.user.displayName,\n signalingUrl,\n deviceFlowRan: true,\n };\n}\n\nasync function handlePollError(pollRes: Response, interval: number): Promise<void> {\n try {\n const errorData = DevicePollPendingSchema.parse(await pollRes.json());\n\n if (errorData.error === 'expired_token') {\n printError('\\n Authorization expired. Run `shipyard login` again.');\n process.exit(1);\n }\n\n if (errorData.error === 'slow_down') {\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n } catch {}\n}\n\nasync function checkLogin(): Promise<void> {\n const config = await readConfig();\n\n if (!config?.auth?.token) {\n print('Not logged in. Run `shipyard login` to authenticate.');\n process.exit(1);\n }\n\n if (config.auth.expiresAt < Date.now()) {\n print('Token expired. Run `shipyard login` to re-authenticate.');\n process.exit(1);\n }\n\n const daysLeft = Math.ceil((config.auth.expiresAt - Date.now()) / (24 * 60 * 60 * 1000));\n print(`Logged in as ${config.auth.displayName} (${config.auth.providers.join(', ')})`);\n print(`Token expires in ${daysLeft} days`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/shared/capabilities/skills.ts
|
|
4
|
+
import { readdir, readFile } from "fs/promises";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
function parseSkillFrontmatter(content, fallbackName) {
|
|
8
|
+
const match = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
9
|
+
if (!match?.[1]) return { name: fallbackName, description: "" };
|
|
10
|
+
const frontmatter = match[1];
|
|
11
|
+
const nameMatch = /^name:\s*["']?(.+?)["']?\s*$/m.exec(frontmatter);
|
|
12
|
+
const descMatch = /^description:\s*["']?(.+?)["']?\s*$/m.exec(frontmatter);
|
|
13
|
+
const name = nameMatch?.[1]?.trim() ?? fallbackName;
|
|
14
|
+
let description = descMatch?.[1]?.trim() ?? "";
|
|
15
|
+
if (description.length > 200) description = `${description.slice(0, 197)}...`;
|
|
16
|
+
return { name, description };
|
|
17
|
+
}
|
|
18
|
+
async function readSkillsFromDir(dirPath) {
|
|
19
|
+
try {
|
|
20
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
21
|
+
const skills = [];
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (!entry.isDirectory()) continue;
|
|
24
|
+
const skillDir = join(dirPath, entry.name);
|
|
25
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
26
|
+
try {
|
|
27
|
+
const content = await readFile(skillFile, "utf-8");
|
|
28
|
+
const parsed = parseSkillFrontmatter(content, entry.name);
|
|
29
|
+
if (parsed) skills.push(parsed);
|
|
30
|
+
} catch {
|
|
31
|
+
skills.push({ name: entry.name, description: "" });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return skills;
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function readSkillsRecursive(dirPath) {
|
|
40
|
+
const skills = [];
|
|
41
|
+
try {
|
|
42
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (!entry.isDirectory()) continue;
|
|
45
|
+
const subDir = join(dirPath, entry.name);
|
|
46
|
+
const skillFile = join(subDir, "SKILL.md");
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(skillFile, "utf-8");
|
|
49
|
+
const parsed = parseSkillFrontmatter(content, entry.name);
|
|
50
|
+
if (parsed) skills.push(parsed);
|
|
51
|
+
} catch {
|
|
52
|
+
const nested = await readSkillsRecursive(subDir);
|
|
53
|
+
skills.push(...nested);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return skills;
|
|
59
|
+
}
|
|
60
|
+
async function readPluginSkills() {
|
|
61
|
+
const skills = [];
|
|
62
|
+
try {
|
|
63
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
64
|
+
const settingsRaw = await readFile(settingsPath, "utf-8");
|
|
65
|
+
const settings = JSON.parse(settingsRaw);
|
|
66
|
+
if (!settings.enabledPlugins) return [];
|
|
67
|
+
const installedPath = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
68
|
+
const installedRaw = await readFile(installedPath, "utf-8");
|
|
69
|
+
const installed = JSON.parse(installedRaw);
|
|
70
|
+
if (!installed.plugins) return [];
|
|
71
|
+
for (const [pluginId, enabled] of Object.entries(settings.enabledPlugins)) {
|
|
72
|
+
if (!enabled) continue;
|
|
73
|
+
const installs = installed.plugins[pluginId];
|
|
74
|
+
if (!installs || installs.length === 0) continue;
|
|
75
|
+
const installPath = installs[0]?.installPath;
|
|
76
|
+
if (!installPath) continue;
|
|
77
|
+
const skillsDir = join(installPath, "skills");
|
|
78
|
+
const pluginSkills = await readSkillsRecursive(skillsDir);
|
|
79
|
+
skills.push(...pluginSkills);
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
return skills;
|
|
84
|
+
}
|
|
85
|
+
async function detectSkills(environments) {
|
|
86
|
+
const userSkillsDir = join(homedir(), ".claude", "skills");
|
|
87
|
+
const [userSkills, pluginSkills] = await Promise.all([
|
|
88
|
+
readSkillsFromDir(userSkillsDir),
|
|
89
|
+
readPluginSkills()
|
|
90
|
+
]);
|
|
91
|
+
const allSkills = [...pluginSkills, ...userSkills];
|
|
92
|
+
for (const env of environments) {
|
|
93
|
+
const projectSkillsDir = join(env.path, ".claude", "skills");
|
|
94
|
+
const projectSkills = await readSkillsFromDir(projectSkillsDir);
|
|
95
|
+
allSkills.push(...projectSkills);
|
|
96
|
+
}
|
|
97
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const skill of allSkills) {
|
|
99
|
+
if (!deduped.has(skill.name)) {
|
|
100
|
+
deduped.set(skill.name, skill);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [...deduped.values()];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
detectSkills
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=chunk-DPMRSLYJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/skills.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { GitRepoInfo, SkillInfo } from '@shipyard/session';\n\n/**\n * Extract name and description from a SKILL.md YAML frontmatter.\n * Expects `---\\nname: ...\\ndescription: \"...\"\\n---` at the top.\n */\nfunction parseSkillFrontmatter(\n content: string,\n fallbackName: string\n): { name: string; description: string } | null {\n const match = /^---\\n([\\s\\S]*?)\\n---/.exec(content);\n if (!match?.[1]) return { name: fallbackName, description: '' };\n\n const frontmatter = match[1];\n const nameMatch = /^name:\\s*[\"']?(.+?)[\"']?\\s*$/m.exec(frontmatter);\n const descMatch = /^description:\\s*[\"']?(.+?)[\"']?\\s*$/m.exec(frontmatter);\n\n const name = nameMatch?.[1]?.trim() ?? fallbackName;\n let description = descMatch?.[1]?.trim() ?? '';\n if (description.length > 200) description = `${description.slice(0, 197)}...`;\n\n return { name, description };\n}\n\nasync function readSkillsFromDir(dirPath: string): Promise<SkillInfo[]> {\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n const skills: SkillInfo[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const skillDir = join(dirPath, entry.name);\n const skillFile = join(skillDir, 'SKILL.md');\n try {\n const content = await readFile(skillFile, 'utf-8');\n const parsed = parseSkillFrontmatter(content, entry.name);\n if (parsed) skills.push(parsed);\n } catch {\n skills.push({ name: entry.name, description: '' });\n }\n }\n\n return skills;\n } catch {\n return [];\n }\n}\n\nasync function readSkillsRecursive(dirPath: string): Promise<SkillInfo[]> {\n const skills: SkillInfo[] = [];\n try {\n const entries = await readdir(dirPath, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const subDir = join(dirPath, entry.name);\n const skillFile = join(subDir, 'SKILL.md');\n try {\n const content = await readFile(skillFile, 'utf-8');\n const parsed = parseSkillFrontmatter(content, entry.name);\n if (parsed) skills.push(parsed);\n } catch {\n const nested = await readSkillsRecursive(subDir);\n skills.push(...nested);\n }\n }\n } catch {}\n return skills;\n}\n\nasync function readPluginSkills(): Promise<SkillInfo[]> {\n const skills: SkillInfo[] = [];\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 skillsDir = join(installPath, 'skills');\n const pluginSkills = await readSkillsRecursive(skillsDir);\n skills.push(...pluginSkills);\n }\n } catch {}\n return skills;\n}\n\nexport async function detectSkills(environments: GitRepoInfo[]): Promise<SkillInfo[]> {\n const userSkillsDir = join(homedir(), '.claude', 'skills');\n const [userSkills, pluginSkills] = await Promise.all([\n readSkillsFromDir(userSkillsDir),\n readPluginSkills(),\n ]);\n const allSkills: SkillInfo[] = [...pluginSkills, ...userSkills];\n\n for (const env of environments) {\n const projectSkillsDir = join(env.path, '.claude', 'skills');\n const projectSkills = await readSkillsFromDir(projectSkillsDir);\n allSkills.push(...projectSkills);\n }\n\n const deduped = new Map<string, SkillInfo>();\n for (const skill of allSkills) {\n if (!deduped.has(skill.name)) {\n deduped.set(skill.name, skill);\n }\n }\n\n return [...deduped.values()];\n}\n"],"mappings":";;;AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,eAAe;AACxB,SAAS,YAAY;AAOrB,SAAS,sBACP,SACA,cAC8C;AAC9C,QAAM,QAAQ,wBAAwB,KAAK,OAAO;AAClD,MAAI,CAAC,QAAQ,CAAC,EAAG,QAAO,EAAE,MAAM,cAAc,aAAa,GAAG;AAE9D,QAAM,cAAc,MAAM,CAAC;AAC3B,QAAM,YAAY,gCAAgC,KAAK,WAAW;AAClE,QAAM,YAAY,uCAAuC,KAAK,WAAW;AAEzE,QAAM,OAAO,YAAY,CAAC,GAAG,KAAK,KAAK;AACvC,MAAI,cAAc,YAAY,CAAC,GAAG,KAAK,KAAK;AAC5C,MAAI,YAAY,SAAS,IAAK,eAAc,GAAG,YAAY,MAAM,GAAG,GAAG,CAAC;AAExE,SAAO,EAAE,MAAM,YAAY;AAC7B;AAEA,eAAe,kBAAkB,SAAuC;AACtE,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AAC9D,UAAM,SAAsB,CAAC;AAE7B,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,YAAM,WAAW,KAAK,SAAS,MAAM,IAAI;AACzC,YAAM,YAAY,KAAK,UAAU,UAAU;AAC3C,UAAI;AACF,cAAM,UAAU,MAAM,SAAS,WAAW,OAAO;AACjD,cAAM,SAAS,sBAAsB,SAAS,MAAM,IAAI;AACxD,YAAI,OAAQ,QAAO,KAAK,MAAM;AAAA,MAChC,QAAQ;AACN,eAAO,KAAK,EAAE,MAAM,MAAM,MAAM,aAAa,GAAG,CAAC;AAAA,MACnD;AAAA,IACF;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,oBAAoB,SAAuC;AACxE,QAAM,SAAsB,CAAC;AAC7B,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AAC9D,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,YAAM,SAAS,KAAK,SAAS,MAAM,IAAI;AACvC,YAAM,YAAY,KAAK,QAAQ,UAAU;AACzC,UAAI;AACF,cAAM,UAAU,MAAM,SAAS,WAAW,OAAO;AACjD,cAAM,SAAS,sBAAsB,SAAS,MAAM,IAAI;AACxD,YAAI,OAAQ,QAAO,KAAK,MAAM;AAAA,MAChC,QAAQ;AACN,cAAM,SAAS,MAAM,oBAAoB,MAAM;AAC/C,eAAO,KAAK,GAAG,MAAM;AAAA,MACvB;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,eAAe,mBAAyC;AACtD,QAAM,SAAsB,CAAC;AAC7B,MAAI;AACF,UAAM,eAAe,KAAK,QAAQ,GAAG,WAAW,eAAe;AAC/D,UAAM,cAAc,MAAM,SAAS,cAAc,OAAO;AAExD,UAAM,WAAW,KAAK,MAAM,WAAW;AACvC,QAAI,CAAC,SAAS,eAAgB,QAAO,CAAC;AAEtC,UAAM,gBAAgB,KAAK,QAAQ,GAAG,WAAW,WAAW,wBAAwB;AACpF,UAAM,eAAe,MAAM,SAAS,eAAe,OAAO;AAE1D,UAAM,YAAY,KAAK,MAAM,YAAY;AAGzC,QAAI,CAAC,UAAU,QAAS,QAAO,CAAC;AAEhC,eAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,SAAS,cAAc,GAAG;AACzE,UAAI,CAAC,QAAS;AACd,YAAM,WAAW,UAAU,QAAQ,QAAQ;AAC3C,UAAI,CAAC,YAAY,SAAS,WAAW,EAAG;AACxC,YAAM,cAAc,SAAS,CAAC,GAAG;AACjC,UAAI,CAAC,YAAa;AAElB,YAAM,YAAY,KAAK,aAAa,QAAQ;AAC5C,YAAM,eAAe,MAAM,oBAAoB,SAAS;AACxD,aAAO,KAAK,GAAG,YAAY;AAAA,IAC7B;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,eAAsB,aAAa,cAAmD;AACpF,QAAM,gBAAgB,KAAK,QAAQ,GAAG,WAAW,QAAQ;AACzD,QAAM,CAAC,YAAY,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IACnD,kBAAkB,aAAa;AAAA,IAC/B,iBAAiB;AAAA,EACnB,CAAC;AACD,QAAM,YAAyB,CAAC,GAAG,cAAc,GAAG,UAAU;AAE9D,aAAW,OAAO,cAAc;AAC9B,UAAM,mBAAmB,KAAK,IAAI,MAAM,WAAW,QAAQ;AAC3D,UAAM,gBAAgB,MAAM,kBAAkB,gBAAgB;AAC9D,cAAU,KAAK,GAAG,aAAa;AAAA,EACjC;AAEA,QAAM,UAAU,oBAAI,IAAuB;AAC3C,aAAW,SAAS,WAAW;AAC7B,QAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,GAAG;AAC5B,cAAQ,IAAI,MAAM,MAAM,KAAK;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,QAAQ,OAAO,CAAC;AAC7B;","names":[]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getLogFilePath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5OHYOIOG.js";
|
|
5
5
|
|
|
6
|
-
// src/commands/output.ts
|
|
6
|
+
// src/shared/commands/output.ts
|
|
7
7
|
import { appendFileSync } from "fs";
|
|
8
8
|
function appendToLogFile(level, message) {
|
|
9
9
|
try {
|
|
@@ -33,4 +33,4 @@ export {
|
|
|
33
33
|
print,
|
|
34
34
|
printError
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
36
|
+
//# sourceMappingURL=chunk-JFEQEK53.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/commands/output.ts"],"sourcesContent":["import { appendFileSync } from 'node:fs';\nimport { getLogFilePath } from '../logger.js';\n\n/**\n * CLI output helpers for interactive commands (login, logout).\n * Uses process.stdout/stderr directly to avoid pino JSON formatting.\n * Also appends a structured JSON line to the daemon log file for forensics.\n */\n\nfunction appendToLogFile(level: 'info' | 'error', message: string): void {\n try {\n const entry = JSON.stringify({\n level: level === 'error' ? 50 : 30,\n time: Date.now(),\n source: 'cli',\n msg: message,\n });\n appendFileSync(getLogFilePath(), `${entry}\\n`);\n } catch {\n /** Log file write failure must never break CLI output */\n }\n}\n\nexport function print(message: string): void {\n process.stdout.write(`${message}\\n`);\n appendToLogFile('info', message);\n}\n\nexport function printError(message: string): void {\n process.stderr.write(`${message}\\n`);\n appendToLogFile('error', message);\n}\n"],"mappings":";;;;;;AAAA,SAAS,sBAAsB;AAS/B,SAAS,gBAAgB,OAAyB,SAAuB;AACvE,MAAI;AACF,UAAM,QAAQ,KAAK,UAAU;AAAA,MAC3B,OAAO,UAAU,UAAU,KAAK;AAAA,MAChC,MAAM,KAAK,IAAI;AAAA,MACf,QAAQ;AAAA,MACR,KAAK;AAAA,IACP,CAAC;AACD,mBAAe,eAAe,GAAG,GAAG,KAAK;AAAA,CAAI;AAAA,EAC/C,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,MAAM,SAAuB;AAC3C,UAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AACnC,kBAAgB,QAAQ,OAAO;AACjC;AAEO,SAAS,WAAW,SAAuB;AAChD,UAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AACnC,kBAAgB,SAAS,OAAO;AAClC;","names":[]}
|