@skillfm/local 2.2.0 → 2.5.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 +12 -0
- package/dist/doctor.js.map +1 -1
- package/dist/guard/cli.d.ts.map +1 -1
- package/dist/guard/cli.js.map +1 -1
- package/dist/harness/kernels/deny-pipeline.js +1 -1
- package/dist/harness/kernels/deny-pipeline.js.map +1 -1
- package/dist/harness/writers.d.ts.map +1 -1
- package/dist/harness/writers.js +2 -0
- package/dist/harness/writers.js.map +1 -1
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-stdio/api-client.d.ts.map +1 -1
- package/dist/mcp-stdio/api-client.js +3 -0
- package/dist/mcp-stdio/api-client.js.map +1 -1
- package/dist/mcp-stdio/render-flow.d.ts.map +1 -1
- package/dist/mcp-stdio/render-flow.js.map +1 -1
- package/dist/mcp-stdio/request-context.d.ts.map +1 -1
- package/dist/mcp-stdio/request-context.js +1 -0
- package/dist/mcp-stdio/request-context.js.map +1 -1
- package/dist/mcp-stdio/server.d.ts.map +1 -1
- package/dist/mcp-stdio/server.js +118 -17
- package/dist/mcp-stdio/server.js.map +1 -1
- package/dist/mcp-stdio/sse-progress-client.js.map +1 -1
- package/dist/skill-installer/bundle-fetcher.d.ts +40 -0
- package/dist/skill-installer/bundle-fetcher.d.ts.map +1 -0
- package/dist/skill-installer/bundle-fetcher.js +133 -0
- package/dist/skill-installer/bundle-fetcher.js.map +1 -0
- package/dist/skill-installer/errors.d.ts +12 -0
- package/dist/skill-installer/errors.d.ts.map +1 -0
- package/dist/skill-installer/errors.js +42 -0
- package/dist/skill-installer/errors.js.map +1 -0
- package/dist/skill-installer/index.d.ts +20 -0
- package/dist/skill-installer/index.d.ts.map +1 -0
- package/dist/skill-installer/index.js +193 -0
- package/dist/skill-installer/index.js.map +1 -0
- package/dist/skill-installer/lockfile.d.ts +8 -0
- package/dist/skill-installer/lockfile.d.ts.map +1 -0
- package/dist/skill-installer/lockfile.js +52 -0
- package/dist/skill-installer/lockfile.js.map +1 -0
- package/dist/skill-installer/npm-installer.d.ts +16 -0
- package/dist/skill-installer/npm-installer.d.ts.map +1 -0
- package/dist/skill-installer/npm-installer.js +83 -0
- package/dist/skill-installer/npm-installer.js.map +1 -0
- package/dist/skill-installer/paths.d.ts +4 -0
- package/dist/skill-installer/paths.d.ts.map +1 -0
- package/dist/skill-installer/paths.js +16 -0
- package/dist/skill-installer/paths.js.map +1 -0
- package/dist/skill-installer/tar-extractor.d.ts +15 -0
- package/dist/skill-installer/tar-extractor.d.ts.map +1 -0
- package/dist/skill-installer/tar-extractor.js +56 -0
- package/dist/skill-installer/tar-extractor.js.map +1 -0
- package/dist/skill-md/template.js +2 -2
- package/dist/skill-runner/cli.d.ts +4 -0
- package/dist/skill-runner/cli.d.ts.map +1 -0
- package/dist/skill-runner/cli.js +81 -0
- package/dist/skill-runner/cli.js.map +1 -0
- package/dist/skill-runner/discovery.d.ts +3 -0
- package/dist/skill-runner/discovery.d.ts.map +1 -0
- package/dist/skill-runner/discovery.js +108 -0
- package/dist/skill-runner/discovery.js.map +1 -0
- package/dist/skill-runner/index.d.ts +9 -0
- package/dist/skill-runner/index.d.ts.map +1 -0
- package/dist/skill-runner/index.js +100 -0
- package/dist/skill-runner/index.js.map +1 -0
- package/dist/skill-runner/registry.d.ts +11 -0
- package/dist/skill-runner/registry.d.ts.map +1 -0
- package/dist/skill-runner/registry.js +79 -0
- package/dist/skill-runner/registry.js.map +1 -0
- package/dist/skill-runner/spawner.d.ts +14 -0
- package/dist/skill-runner/spawner.d.ts.map +1 -0
- package/dist/skill-runner/spawner.js +85 -0
- package/dist/skill-runner/spawner.js.map +1 -0
- package/dist/skill-runner/types.d.ts +62 -0
- package/dist/skill-runner/types.d.ts.map +1 -0
- package/dist/skill-runner/types.js +6 -0
- package/dist/skill-runner/types.js.map +1 -0
- package/dist/skill-tunnel/cli.d.ts +5 -0
- package/dist/skill-tunnel/cli.d.ts.map +1 -0
- package/dist/skill-tunnel/cli.js +205 -0
- package/dist/skill-tunnel/cli.js.map +1 -0
- package/dist/skill-tunnel/client.d.ts +56 -0
- package/dist/skill-tunnel/client.d.ts.map +1 -0
- package/dist/skill-tunnel/client.js +260 -0
- package/dist/skill-tunnel/client.js.map +1 -0
- package/dist/skill-tunnel/handshake.d.ts +35 -0
- package/dist/skill-tunnel/handshake.d.ts.map +1 -0
- package/dist/skill-tunnel/handshake.js +61 -0
- package/dist/skill-tunnel/handshake.js.map +1 -0
- package/dist/skill-tunnel/heartbeat.d.ts +34 -0
- package/dist/skill-tunnel/heartbeat.d.ts.map +1 -0
- package/dist/skill-tunnel/heartbeat.js +86 -0
- package/dist/skill-tunnel/heartbeat.js.map +1 -0
- package/dist/skill-tunnel/local-bridge.d.ts +30 -0
- package/dist/skill-tunnel/local-bridge.d.ts.map +1 -0
- package/dist/skill-tunnel/local-bridge.js +224 -0
- package/dist/skill-tunnel/local-bridge.js.map +1 -0
- package/dist/skill-tunnel/reconnect.d.ts +21 -0
- package/dist/skill-tunnel/reconnect.d.ts.map +1 -0
- package/dist/skill-tunnel/reconnect.js +72 -0
- package/dist/skill-tunnel/reconnect.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// skill-installer/bundle-fetcher.ts
|
|
2
|
+
// Fetches the skill bundle tarball from brain, writes to a tmp file, verifies sha256.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createWriteStream, mkdirSync, unlinkSync, existsSync } from 'node:fs';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
|
+
import { SKILL_BUNDLE_ERROR_CODES, SKILL_INSTALL_ERROR_CODES, mapServerErrorToInstallError, } from '@skillfm/contracts/skill-distribution';
|
|
9
|
+
import { classifyNetworkError } from './errors.js';
|
|
10
|
+
const FETCH_TIMEOUT_MS = 60_000; // B4 step: 60s timeout
|
|
11
|
+
/**
|
|
12
|
+
* Fetch bundle tarball from brain and stream to tmpPath.
|
|
13
|
+
* Returns version + checksum from response headers.
|
|
14
|
+
* Throws FetchError-shaped object on failure.
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchBundle(opts) {
|
|
17
|
+
const { apiBaseUrl, agentToken, slug, version, tmpPath } = opts;
|
|
18
|
+
// Ensure tmp dir exists
|
|
19
|
+
mkdirSync(dirname(tmpPath), { recursive: true });
|
|
20
|
+
const query = version ? `?version=${encodeURIComponent(version)}` : '';
|
|
21
|
+
const url = `${apiBaseUrl}/v1/skills/${encodeURIComponent(slug)}/bundle.tar.gz${query}`;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
headers: {
|
|
28
|
+
'X-Agent-Token': agentToken,
|
|
29
|
+
'X-Skillfm-Local-Version': '2.5.0',
|
|
30
|
+
Accept: 'application/gzip',
|
|
31
|
+
},
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
});
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
// Try to parse error body
|
|
37
|
+
let code = SKILL_INSTALL_ERROR_CODES.INTERNAL;
|
|
38
|
+
let message = `HTTP ${res.status}`;
|
|
39
|
+
let details;
|
|
40
|
+
try {
|
|
41
|
+
const body = (await res.json());
|
|
42
|
+
if (body?.error?.code) {
|
|
43
|
+
code = mapServerErrorToInstallError(body.error.code);
|
|
44
|
+
message = body.error.message || message;
|
|
45
|
+
details = body.error.details;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Body not parseable
|
|
50
|
+
}
|
|
51
|
+
const err = new Error(message);
|
|
52
|
+
err.installCode = code;
|
|
53
|
+
err.installDetails = details;
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
// Read required response headers
|
|
57
|
+
const skillVersion = res.headers.get('x-skill-version');
|
|
58
|
+
const checksumSha256 = res.headers.get('x-skill-checksum-sha256');
|
|
59
|
+
const manifestVersion = res.headers.get('x-skill-manifest-version') || 'v0.2';
|
|
60
|
+
if (!skillVersion) {
|
|
61
|
+
const err = new Error('Missing X-Skill-Version header from brain');
|
|
62
|
+
err.installCode = SKILL_INSTALL_ERROR_CODES.INTERNAL;
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
if (!checksumSha256) {
|
|
66
|
+
const err = new Error('Missing X-Skill-Checksum-Sha256 header from brain');
|
|
67
|
+
err.installCode = SKILL_INSTALL_ERROR_CODES.INTERNAL;
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
// Stream body to tmp file
|
|
71
|
+
if (!res.body) {
|
|
72
|
+
const err = new Error('No response body');
|
|
73
|
+
err.installCode = SKILL_INSTALL_ERROR_CODES.INTERNAL;
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
const writer = createWriteStream(tmpPath);
|
|
77
|
+
await pipeline(Readable.fromWeb(res.body), writer);
|
|
78
|
+
return {
|
|
79
|
+
tmpPath,
|
|
80
|
+
version: skillVersion,
|
|
81
|
+
checksumSha256,
|
|
82
|
+
manifestVersion,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
// Already classified
|
|
88
|
+
if (err.installCode) {
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
// Network error
|
|
92
|
+
const code = classifyNetworkError(err);
|
|
93
|
+
const netErr = new Error(`Network error fetching bundle: ${err.message}`);
|
|
94
|
+
netErr.installCode = code;
|
|
95
|
+
throw netErr;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compute SHA-256 hex of a file.
|
|
100
|
+
*/
|
|
101
|
+
export async function computeFileSha256(filePath) {
|
|
102
|
+
const { createReadStream } = await import('node:fs');
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const hash = createHash('sha256');
|
|
105
|
+
const stream = createReadStream(filePath);
|
|
106
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
107
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
108
|
+
stream.on('error', reject);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Verify sha256 of tmpPath matches expected.
|
|
113
|
+
* Returns true if match, false if mismatch.
|
|
114
|
+
*/
|
|
115
|
+
export async function verifyChecksum(filePath, expected) {
|
|
116
|
+
const actual = await computeFileSha256(filePath);
|
|
117
|
+
return actual.toLowerCase() === expected.toLowerCase();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Delete tmp file silently (cleanup).
|
|
121
|
+
*/
|
|
122
|
+
export function deleteTmpFile(filePath) {
|
|
123
|
+
try {
|
|
124
|
+
if (existsSync(filePath))
|
|
125
|
+
unlinkSync(filePath);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Ignore
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Re-export for convenience
|
|
132
|
+
export { SKILL_BUNDLE_ERROR_CODES };
|
|
133
|
+
//# sourceMappingURL=bundle-fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-fetcher.js","sourceRoot":"","sources":["../../src/skill-installer/bundle-fetcher.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,sFAAsF;AAEtF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,uCAAuC,CAAC;AAK/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,uBAAuB;AAexD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAMjC;IACC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEhE,wBAAwB;IACxB,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjD,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,GAAG,GAAG,GAAG,UAAU,cAAc,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,KAAK,EAAE,CAAC;IAExF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAErE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,eAAe,EAAE,UAAU;gBAC3B,yBAAyB,EAAE,OAAO;gBAClC,MAAM,EAAE,kBAAkB;aAC3B;YACD,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,0BAA0B;YAC1B,IAAI,IAAI,GAA0B,yBAAyB,CAAC,QAAQ,CAAC;YACrE,IAAI,OAAO,GAAG,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnC,IAAI,OAA4C,CAAC;YAEjD,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;gBAC3D,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;oBACtB,IAAI,GAAG,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACrD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,OAAO,CAAC;oBACxC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;gBAC/B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,OAAO,CAG5B,CAAC;YACF,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC;YACvB,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC;YAC7B,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,iCAAiC;QACjC,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACxD,MAAM,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAClE,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,MAAM,CAAC;QAE9E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,2CAA2C,CAEhE,CAAC;YACF,GAAG,CAAC,WAAW,GAAG,yBAAyB,CAAC,QAAQ,CAAC;YACrD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,mDAAmD,CAExE,CAAC;YACF,GAAG,CAAC,WAAW,GAAG,yBAAyB,CAAC,QAAQ,CAAC;YACrD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,0BAA0B;QAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAEvC,CAAC;YACF,GAAG,CAAC,WAAW,GAAG,yBAAyB,CAAC,QAAQ,CAAC;YACrD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAA8C,CAAC,EAAE,MAAM,CAAC,CAAC;QAE7F,OAAO;YACL,OAAO;YACP,OAAO,EAAE,YAAY;YACrB,cAAc;YACd,eAAe;SAChB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,qBAAqB;QACrB,IAAK,GAAgC,CAAC,WAAW,EAAE,CAAC;YAClD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,gBAAgB;QAChB,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,KAAK,CACtB,kCAAmC,GAAa,CAAC,OAAO,EAAE,CACT,CAAC;QACpD,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,MAAM,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,QAAgB;IACrE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED,4BAA4B;AAC5B,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mapServerErrorToInstallError } from '@skillfm/contracts/skill-distribution';
|
|
2
|
+
import type { SkillInstallErrorCode } from '@skillfm/contracts/skill-distribution';
|
|
3
|
+
export { mapServerErrorToInstallError };
|
|
4
|
+
/**
|
|
5
|
+
* Classify a caught network/fetch error into a SkillInstallErrorCode.
|
|
6
|
+
* Network-layer errors (ENETUNREACH, ECONNRESET, ETIMEDOUT, AbortError, fetch failed)
|
|
7
|
+
* all map to NETWORK.UNREACHABLE.
|
|
8
|
+
*/
|
|
9
|
+
export declare function classifyNetworkError(err: unknown): SkillInstallErrorCode;
|
|
10
|
+
/** Hints shown to the agent for each error code */
|
|
11
|
+
export declare const INSTALL_ERROR_HINTS: Partial<Record<SkillInstallErrorCode, string>>;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/skill-installer/errors.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,4BAA4B,EAE7B,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAgBxE;AAED,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAc9E,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// skill-installer/errors.ts
|
|
2
|
+
// Maps server SKILL.BUNDLE.* codes + network errors to SkillInstallErrorCode.
|
|
3
|
+
// Directly re-exports + extends mapServerErrorToInstallError from contracts.
|
|
4
|
+
import { mapServerErrorToInstallError, SKILL_INSTALL_ERROR_CODES, } from '@skillfm/contracts/skill-distribution';
|
|
5
|
+
export { mapServerErrorToInstallError };
|
|
6
|
+
/**
|
|
7
|
+
* Classify a caught network/fetch error into a SkillInstallErrorCode.
|
|
8
|
+
* Network-layer errors (ENETUNREACH, ECONNRESET, ETIMEDOUT, AbortError, fetch failed)
|
|
9
|
+
* all map to NETWORK.UNREACHABLE.
|
|
10
|
+
*/
|
|
11
|
+
export function classifyNetworkError(err) {
|
|
12
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
13
|
+
if (msg.includes('enetunreach') ||
|
|
14
|
+
msg.includes('econnreset') ||
|
|
15
|
+
msg.includes('econnrefused') ||
|
|
16
|
+
msg.includes('enotfound') ||
|
|
17
|
+
msg.includes('etimedout') ||
|
|
18
|
+
msg.includes('abort') ||
|
|
19
|
+
msg.includes('fetch failed') ||
|
|
20
|
+
msg.includes('network') ||
|
|
21
|
+
msg.includes('timeout')) {
|
|
22
|
+
return SKILL_INSTALL_ERROR_CODES.NETWORK_UNREACHABLE;
|
|
23
|
+
}
|
|
24
|
+
return SKILL_INSTALL_ERROR_CODES.INTERNAL;
|
|
25
|
+
}
|
|
26
|
+
/** Hints shown to the agent for each error code */
|
|
27
|
+
export const INSTALL_ERROR_HINTS = {
|
|
28
|
+
[SKILL_INSTALL_ERROR_CODES.NETWORK_UNREACHABLE]: '网络异常,待会儿再试',
|
|
29
|
+
[SKILL_INSTALL_ERROR_CODES.AGENT_NOT_BOUND]: "先跑 `skillfm-local activate`",
|
|
30
|
+
[SKILL_INSTALL_ERROR_CODES.SKILL_NOT_FOUND]: 'slug 不存在,是不是拼错了',
|
|
31
|
+
[SKILL_INSTALL_ERROR_CODES.SKILL_ACCESS_DENIED]: '你的订阅不允许装这个 skill',
|
|
32
|
+
[SKILL_INSTALL_ERROR_CODES.SKILL_NOT_READY]: 'skill 暂未发布,稍后重试',
|
|
33
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_CHECKSUM_MISMATCH]: '下载损坏,已自动重试 1 次',
|
|
34
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_EXTRACT_FAILED]: '解压失败,bundle 可能损坏',
|
|
35
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_NPM_CI_FAILED]: 'skill 依赖装不上',
|
|
36
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_MISSING_SERVER_JS]: 'bundle 损坏,请联系 SkillFM',
|
|
37
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_DISK_FULL]: '磁盘空间不足',
|
|
38
|
+
[SKILL_INSTALL_ERROR_CODES.BUNDLE_LOCK_BUSY]: '同一 skill 正在安装中,请稍候',
|
|
39
|
+
[SKILL_INSTALL_ERROR_CODES.INPUT_BAD_SLUG]: 'slug 格式无效,应为小写字母/数字/短横线,2-64 字符',
|
|
40
|
+
[SKILL_INSTALL_ERROR_CODES.INTERNAL]: '内部错误,请重试',
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/skill-installer/errors.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,8EAA8E;AAC9E,6EAA6E;AAE7E,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,MAAM,GAAG,GAAG,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7E,IACE,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC3B,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC1B,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC5B,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzB,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzB,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QACrB,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC5B,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;QACvB,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EACvB,CAAC;QACD,OAAO,yBAAyB,CAAC,mBAAmB,CAAC;IACvD,CAAC;IACD,OAAO,yBAAyB,CAAC,QAAQ,CAAC;AAC5C,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,mBAAmB,GAAmD;IACjF,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,EAAE,YAAY;IAC7D,CAAC,yBAAyB,CAAC,eAAe,CAAC,EAAE,6BAA6B;IAC1E,CAAC,yBAAyB,CAAC,eAAe,CAAC,EAAE,iBAAiB;IAC9D,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,EAAE,kBAAkB;IACnE,CAAC,yBAAyB,CAAC,eAAe,CAAC,EAAE,iBAAiB;IAC9D,CAAC,yBAAyB,CAAC,wBAAwB,CAAC,EAAE,gBAAgB;IACtE,CAAC,yBAAyB,CAAC,qBAAqB,CAAC,EAAE,kBAAkB;IACrE,CAAC,yBAAyB,CAAC,oBAAoB,CAAC,EAAE,aAAa;IAC/D,CAAC,yBAAyB,CAAC,wBAAwB,CAAC,EAAE,uBAAuB;IAC7E,CAAC,yBAAyB,CAAC,gBAAgB,CAAC,EAAE,QAAQ;IACtD,CAAC,yBAAyB,CAAC,gBAAgB,CAAC,EAAE,oBAAoB;IAClE,CAAC,yBAAyB,CAAC,cAAc,CAAC,EAAE,iCAAiC;IAC7E,CAAC,yBAAyB,CAAC,QAAQ,CAAC,EAAE,UAAU;CACjD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SkillInstallResult } from '@skillfm/contracts/skill-distribution';
|
|
2
|
+
export interface InstallSkillOptions {
|
|
3
|
+
slug: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
/** Override api base url (defaults to config.apiBaseUrl) */
|
|
6
|
+
apiBaseUrl?: string;
|
|
7
|
+
/** Override agent token (defaults to config.brainKey) */
|
|
8
|
+
agentToken?: string;
|
|
9
|
+
/** Override skills root directory (defaults to ~/.skillfm/skills/) — useful in tests */
|
|
10
|
+
skillsRoot?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Install a SkillFM official skill bundle.
|
|
14
|
+
* Implements B1-B12 from SKILL-INSTALL-CALL-SEQUENCE.md §2.2.
|
|
15
|
+
*
|
|
16
|
+
* B12 (cleanup) is guaranteed via try/finally.
|
|
17
|
+
* Returns SkillInstallResult.
|
|
18
|
+
*/
|
|
19
|
+
export declare function installSkill(opts: InstallSkillOptions): Promise<SkillInstallResult>;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skill-installer/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAShF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA8KzF"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// skill-installer/index.ts
|
|
2
|
+
// installSkill() — main orchestration function.
|
|
3
|
+
// Implements CALL-SEQUENCE §2.2 steps B1-B12.
|
|
4
|
+
import { existsSync, symlinkSync, rmSync, cpSync } from 'node:fs';
|
|
5
|
+
import { SKILL_SLUG_REGEX, SKILL_INSTALL_ERROR_CODES, } from '@skillfm/contracts/skill-distribution';
|
|
6
|
+
import { acquireLock } from './lockfile.js';
|
|
7
|
+
import { fetchBundle, verifyChecksum, deleteTmpFile } from './bundle-fetcher.js';
|
|
8
|
+
import { extractBundle } from './tar-extractor.js';
|
|
9
|
+
import { runNpmCi, verifyServerJs } from './npm-installer.js';
|
|
10
|
+
import { buildInstallPaths, DEFAULT_SKILLS_ROOT } from './paths.js';
|
|
11
|
+
import { INSTALL_ERROR_HINTS } from './errors.js';
|
|
12
|
+
/**
|
|
13
|
+
* Install a SkillFM official skill bundle.
|
|
14
|
+
* Implements B1-B12 from SKILL-INSTALL-CALL-SEQUENCE.md §2.2.
|
|
15
|
+
*
|
|
16
|
+
* B12 (cleanup) is guaranteed via try/finally.
|
|
17
|
+
* Returns SkillInstallResult.
|
|
18
|
+
*/
|
|
19
|
+
export async function installSkill(opts) {
|
|
20
|
+
const { slug, version, skillsRoot } = opts;
|
|
21
|
+
// B1: Validate slug
|
|
22
|
+
if (!SKILL_SLUG_REGEX.test(slug)) {
|
|
23
|
+
return makeError(SKILL_INSTALL_ERROR_CODES.INPUT_BAD_SLUG, `Invalid slug "${slug}" — must match /^[a-z0-9-]{2,64}$/`);
|
|
24
|
+
}
|
|
25
|
+
// B2: Read config for agent_token + api_base_url
|
|
26
|
+
const { config } = await import('../mcp-stdio/config.js');
|
|
27
|
+
const apiBaseUrl = opts.apiBaseUrl ?? config.apiBaseUrl;
|
|
28
|
+
const agentToken = opts.agentToken ?? config.brainKey;
|
|
29
|
+
if (!agentToken) {
|
|
30
|
+
return makeError(SKILL_INSTALL_ERROR_CODES.AGENT_NOT_BOUND, 'No agent_token found. Run `skillfm-local activate` first.');
|
|
31
|
+
}
|
|
32
|
+
const resolvedSkillsRoot = skillsRoot ?? DEFAULT_SKILLS_ROOT;
|
|
33
|
+
// We need a preliminary paths object to build the lock path
|
|
34
|
+
// (version is unknown until after fetch, but lock path only needs slug)
|
|
35
|
+
const { join } = await import('node:path');
|
|
36
|
+
const lockPath = join(resolvedSkillsRoot, '.locks', `${slug}.lock`);
|
|
37
|
+
// B3: Acquire lock
|
|
38
|
+
let releaseLock = null;
|
|
39
|
+
let tmpPath = null;
|
|
40
|
+
try {
|
|
41
|
+
try {
|
|
42
|
+
releaseLock = await acquireLock(lockPath);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const e = err;
|
|
46
|
+
if (e.code === 'BUNDLE.LOCK_BUSY') {
|
|
47
|
+
return makeError(SKILL_INSTALL_ERROR_CODES.BUNDLE_LOCK_BUSY, `Another install of "${slug}" is in progress. Please wait.`);
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
// B4+B5: Fetch bundle + stream to tmp
|
|
52
|
+
let fetchResult;
|
|
53
|
+
try {
|
|
54
|
+
// Build a preliminary tmpPath (version placeholder)
|
|
55
|
+
const tmpPaths = buildInstallPaths(slug, version ?? 'tmp', resolvedSkillsRoot);
|
|
56
|
+
tmpPath = tmpPaths.tmpTarball;
|
|
57
|
+
fetchResult = await fetchBundle({
|
|
58
|
+
apiBaseUrl,
|
|
59
|
+
agentToken,
|
|
60
|
+
slug,
|
|
61
|
+
version,
|
|
62
|
+
tmpPath,
|
|
63
|
+
});
|
|
64
|
+
tmpPath = fetchResult.tmpPath;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const e = err;
|
|
68
|
+
return makeError(e.installCode ?? SKILL_INSTALL_ERROR_CODES.INTERNAL, e.message, e.installDetails);
|
|
69
|
+
}
|
|
70
|
+
const { version: resolvedVersion, checksumSha256 } = fetchResult;
|
|
71
|
+
// B6: Verify sha256 (auto-retry once on mismatch)
|
|
72
|
+
let checksumOk = await verifyChecksum(tmpPath, checksumSha256);
|
|
73
|
+
if (!checksumOk) {
|
|
74
|
+
// Retry once: re-fetch
|
|
75
|
+
deleteTmpFile(tmpPath);
|
|
76
|
+
try {
|
|
77
|
+
const retryPaths = buildInstallPaths(slug, resolvedVersion, resolvedSkillsRoot);
|
|
78
|
+
tmpPath = retryPaths.tmpTarball;
|
|
79
|
+
const retryResult = await fetchBundle({
|
|
80
|
+
apiBaseUrl,
|
|
81
|
+
agentToken,
|
|
82
|
+
slug,
|
|
83
|
+
version: resolvedVersion,
|
|
84
|
+
tmpPath,
|
|
85
|
+
});
|
|
86
|
+
tmpPath = retryResult.tmpPath;
|
|
87
|
+
checksumOk = await verifyChecksum(tmpPath, retryResult.checksumSha256);
|
|
88
|
+
}
|
|
89
|
+
catch (retryErr) {
|
|
90
|
+
const e = retryErr;
|
|
91
|
+
return makeError(e.installCode ?? SKILL_INSTALL_ERROR_CODES.INTERNAL, e.message);
|
|
92
|
+
}
|
|
93
|
+
if (!checksumOk) {
|
|
94
|
+
return makeError(SKILL_INSTALL_ERROR_CODES.BUNDLE_CHECKSUM_MISMATCH, `Bundle checksum mismatch for "${slug}@${resolvedVersion}" after retry`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Build final paths with resolved version
|
|
98
|
+
const paths = buildInstallPaths(slug, resolvedVersion, resolvedSkillsRoot);
|
|
99
|
+
const { installDir, versionlessLink } = paths;
|
|
100
|
+
// B7+B8: Extract tarball + validate required files
|
|
101
|
+
try {
|
|
102
|
+
await extractBundle({ tmpPath, installDir });
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const e = err;
|
|
106
|
+
return makeError(e.installCode ?? SKILL_INSTALL_ERROR_CODES.BUNDLE_EXTRACT_FAILED, e.message);
|
|
107
|
+
}
|
|
108
|
+
// B9: npm ci
|
|
109
|
+
try {
|
|
110
|
+
await runNpmCi(installDir);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const e = err;
|
|
114
|
+
return makeError(e.installCode ?? SKILL_INSTALL_ERROR_CODES.BUNDLE_NPM_CI_FAILED, e.message, e.details);
|
|
115
|
+
}
|
|
116
|
+
// B10: Verify runtime/server.js still exists
|
|
117
|
+
try {
|
|
118
|
+
verifyServerJs(installDir);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
const e = err;
|
|
122
|
+
return makeError(e.installCode ?? SKILL_INSTALL_ERROR_CODES.BUNDLE_MISSING_SERVER_JS, e.message);
|
|
123
|
+
}
|
|
124
|
+
// B11: Create/update versionless symlink
|
|
125
|
+
// macOS/Linux: symlink; Windows fallback: copy
|
|
126
|
+
try {
|
|
127
|
+
await updateVersionlessLink(versionlessLink, installDir);
|
|
128
|
+
}
|
|
129
|
+
catch (linkErr) {
|
|
130
|
+
// Not fatal — log a warning but don't fail install
|
|
131
|
+
process.stderr.write(`[skill-installer] warn: failed to create versionless link ${versionlessLink}: ${linkErr.message}\n`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
slug,
|
|
136
|
+
version: resolvedVersion,
|
|
137
|
+
install_dir: installDir,
|
|
138
|
+
hint_for_agent: 'skill 已就绪。下一步 run-skill 起 daemon',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
// B12: Cleanup — always run
|
|
143
|
+
if (tmpPath)
|
|
144
|
+
deleteTmpFile(tmpPath);
|
|
145
|
+
if (releaseLock)
|
|
146
|
+
releaseLock();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Helpers
|
|
151
|
+
// ============================================================================
|
|
152
|
+
function makeError(code, message, details) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: code,
|
|
156
|
+
message,
|
|
157
|
+
details,
|
|
158
|
+
hint_for_agent: INSTALL_ERROR_HINTS[code],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Update the versionless symlink <slug> → <slug>@<version>.
|
|
163
|
+
* On Windows (no symlink privilege), fall back to recursive copy.
|
|
164
|
+
*/
|
|
165
|
+
async function updateVersionlessLink(versionlessLink, installDir) {
|
|
166
|
+
// Remove existing link/dir if present
|
|
167
|
+
if (existsSync(versionlessLink)) {
|
|
168
|
+
try {
|
|
169
|
+
const { lstatSync } = await import('node:fs');
|
|
170
|
+
const stat = lstatSync(versionlessLink);
|
|
171
|
+
if (stat.isSymbolicLink() || stat.isDirectory()) {
|
|
172
|
+
rmSync(versionlessLink, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Ignore
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Try symlink first
|
|
180
|
+
if (process.platform !== 'win32') {
|
|
181
|
+
symlinkSync(installDir, versionlessLink);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Windows fallback: copy
|
|
185
|
+
try {
|
|
186
|
+
symlinkSync(installDir, versionlessLink);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Symlink failed (no admin) — use recursive copy
|
|
190
|
+
cpSync(installDir, versionlessLink, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skill-installer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire a lock for the given lockFile path.
|
|
3
|
+
* Returns a release function.
|
|
4
|
+
* Throws with code BUNDLE.LOCK_BUSY if lock is not acquired within timeout.
|
|
5
|
+
*/
|
|
6
|
+
export declare function acquireLock(lockPath: string): Promise<() => void>;
|
|
7
|
+
export declare function releaseLock(lockPath: string): void;
|
|
8
|
+
//# sourceMappingURL=lockfile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile.d.ts","sourceRoot":"","sources":["../../src/skill-installer/lockfile.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CA0BvE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQlD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// skill-installer/lockfile.ts
|
|
2
|
+
// Provides mutex locking via mkdir atomicity (POSIX mkdir is atomic).
|
|
3
|
+
// ~/.skillfm/skills/.locks/<slug>.lock is a directory (not a file).
|
|
4
|
+
// mkdir succeeds only for the first caller; subsequent callers get EEXIST → busy.
|
|
5
|
+
import { mkdirSync, rmdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { dirname } from 'node:path';
|
|
7
|
+
const LOCK_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
|
|
8
|
+
const POLL_INTERVAL_MS = 500;
|
|
9
|
+
/**
|
|
10
|
+
* Acquire a lock for the given lockFile path.
|
|
11
|
+
* Returns a release function.
|
|
12
|
+
* Throws with code BUNDLE.LOCK_BUSY if lock is not acquired within timeout.
|
|
13
|
+
*/
|
|
14
|
+
export async function acquireLock(lockPath) {
|
|
15
|
+
// Ensure parent dir exists
|
|
16
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
17
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
18
|
+
while (Date.now() < deadline) {
|
|
19
|
+
try {
|
|
20
|
+
// mkdir is atomic on POSIX — only one caller can succeed
|
|
21
|
+
mkdirSync(lockPath, { recursive: false });
|
|
22
|
+
// Acquired
|
|
23
|
+
return () => releaseLock(lockPath);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const e = err;
|
|
27
|
+
if (e.code !== 'EEXIST') {
|
|
28
|
+
// Unexpected error
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
// Lock is held — wait and retry
|
|
32
|
+
await sleep(POLL_INTERVAL_MS);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const err = new Error(`lock busy for more than ${LOCK_TIMEOUT_MS / 1000}s: ${lockPath}`);
|
|
36
|
+
err.code = 'BUNDLE.LOCK_BUSY';
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
export function releaseLock(lockPath) {
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(lockPath)) {
|
|
42
|
+
rmdirSync(lockPath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Ignore errors during cleanup — lock may have been force-released
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=lockfile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile.js","sourceRoot":"","sources":["../../src/skill-installer/lockfile.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,sEAAsE;AACtE,oEAAoE;AACpE,kFAAkF;AAElF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AACnD,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB;IAChD,2BAA2B;IAC3B,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;IAE9C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,yDAAyD;YACzD,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1C,WAAW;YACX,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAA4B,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxB,mBAAmB;gBACnB,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,gCAAgC;YAChC,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,2BAA2B,eAAe,GAAG,IAAI,MAAM,QAAQ,EAAE,CAAC,CAAC;IACxF,GAA6B,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACzD,MAAM,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;IACrE,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface NpmInstallResult {
|
|
2
|
+
exitCode: number;
|
|
3
|
+
stderr: string;
|
|
4
|
+
stdout: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Run `npm ci --omit=dev` in installDir.
|
|
8
|
+
* Returns exitCode + collected stderr on failure.
|
|
9
|
+
* Throws with installCode = BUNDLE.NPM_CI_FAILED on non-zero exit or timeout.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runNpmCi(installDir: string): Promise<NpmInstallResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Verify that runtime/server.js still exists after npm ci.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verifyServerJs(installDir: string): void;
|
|
16
|
+
//# sourceMappingURL=npm-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-installer.d.ts","sourceRoot":"","sources":["../../src/skill-installer/npm-installer.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiE5E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CASvD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// skill-installer/npm-installer.ts
|
|
2
|
+
// Runs `npm ci --omit=dev` in the install directory with a configurable timeout.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { SKILL_INSTALL_ERROR_CODES } from '@skillfm/contracts/skill-distribution';
|
|
7
|
+
/** Default npm ci timeout in ms. Override with SKILLFM_NPM_CI_TIMEOUT_MS env. */
|
|
8
|
+
const DEFAULT_NPM_CI_TIMEOUT_MS = 180_000;
|
|
9
|
+
function getNpmCiTimeoutMs() {
|
|
10
|
+
const envVal = process.env.SKILLFM_NPM_CI_TIMEOUT_MS;
|
|
11
|
+
if (envVal) {
|
|
12
|
+
const parsed = parseInt(envVal, 10);
|
|
13
|
+
if (!isNaN(parsed) && parsed > 0)
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
return DEFAULT_NPM_CI_TIMEOUT_MS;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run `npm ci --omit=dev` in installDir.
|
|
20
|
+
* Returns exitCode + collected stderr on failure.
|
|
21
|
+
* Throws with installCode = BUNDLE.NPM_CI_FAILED on non-zero exit or timeout.
|
|
22
|
+
*/
|
|
23
|
+
export async function runNpmCi(installDir) {
|
|
24
|
+
const timeoutMs = getNpmCiTimeoutMs();
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const stderrChunks = [];
|
|
27
|
+
const stdoutChunks = [];
|
|
28
|
+
const child = spawn('npm', ['ci', '--omit=dev'], {
|
|
29
|
+
cwd: installDir,
|
|
30
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
// Reduce npm output noise
|
|
34
|
+
npm_config_loglevel: 'warn',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
child.stdout.on('data', (chunk) => stdoutChunks.push(chunk.toString()));
|
|
38
|
+
child.stderr.on('data', (chunk) => stderrChunks.push(chunk.toString()));
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
child.kill('SIGTERM');
|
|
41
|
+
const timeoutErr = new Error(`npm ci timed out after ${timeoutMs / 1000}s in ${installDir}. ` +
|
|
42
|
+
`Consider setting SKILLFM_NPM_CI_TIMEOUT_MS to a higher value.`);
|
|
43
|
+
timeoutErr.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_NPM_CI_FAILED;
|
|
44
|
+
reject(timeoutErr);
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
child.on('close', (code) => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
const exitCode = code ?? 1;
|
|
49
|
+
const stderr = stderrChunks.join('');
|
|
50
|
+
const stdout = stdoutChunks.join('');
|
|
51
|
+
if (exitCode !== 0) {
|
|
52
|
+
const failErr = new Error(`npm ci failed with exit code ${exitCode}: ${stderr.slice(0, 500)}`);
|
|
53
|
+
failErr.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_NPM_CI_FAILED;
|
|
54
|
+
failErr.details = {
|
|
55
|
+
exit_code: exitCode,
|
|
56
|
+
stderr: stderr.slice(0, 1000),
|
|
57
|
+
install_dir: installDir,
|
|
58
|
+
};
|
|
59
|
+
reject(failErr);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
resolve({ exitCode, stderr, stdout });
|
|
63
|
+
});
|
|
64
|
+
child.on('error', (spawnErr) => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
const err = new Error(`Failed to spawn npm: ${spawnErr.message}`);
|
|
67
|
+
err.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_NPM_CI_FAILED;
|
|
68
|
+
reject(err);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Verify that runtime/server.js still exists after npm ci.
|
|
74
|
+
*/
|
|
75
|
+
export function verifyServerJs(installDir) {
|
|
76
|
+
const serverJs = join(installDir, 'runtime', 'server.js');
|
|
77
|
+
if (!existsSync(serverJs)) {
|
|
78
|
+
const err = new Error(`runtime/server.js missing after npm ci in ${installDir}`);
|
|
79
|
+
err.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_MISSING_SERVER_JS;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=npm-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-installer.js","sourceRoot":"","sources":["../../src/skill-installer/npm-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SkillInstallPaths } from '@skillfm/contracts/skill-distribution';
|
|
2
|
+
export declare const DEFAULT_SKILLS_ROOT: string;
|
|
3
|
+
export declare function buildInstallPaths(slug: string, version: string, skillsRoot?: string): SkillInstallPaths;
|
|
4
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/skill-installer/paths.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE/E,eAAO,MAAM,mBAAmB,QAAwC,CAAC;AAEzE,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAA4B,GACvC,iBAAiB,CAQnB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// skill-installer/paths.ts
|
|
2
|
+
// Construct SkillInstallPaths (§7 of skill-distribution.ts contract)
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
export const DEFAULT_SKILLS_ROOT = join(homedir(), '.skillfm', 'skills');
|
|
7
|
+
export function buildInstallPaths(slug, version, skillsRoot = DEFAULT_SKILLS_ROOT) {
|
|
8
|
+
const rand = randomBytes(4).toString('hex');
|
|
9
|
+
return {
|
|
10
|
+
installDir: join(skillsRoot, `${slug}@${version}`),
|
|
11
|
+
versionlessLink: join(skillsRoot, slug),
|
|
12
|
+
lockFile: join(skillsRoot, '.locks', `${slug}.lock`),
|
|
13
|
+
tmpTarball: join(skillsRoot, '.cache', `${slug}-${version}-${rand}.tar.gz`),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=paths.js.map
|