@skillfm/local 2.3.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.
Files changed (100) hide show
  1. package/dist/doctor.js.map +1 -1
  2. package/dist/guard/cli.d.ts.map +1 -1
  3. package/dist/guard/cli.js.map +1 -1
  4. package/dist/harness/kernels/deny-pipeline.js +1 -1
  5. package/dist/harness/kernels/deny-pipeline.js.map +1 -1
  6. package/dist/harness/writers.d.ts.map +1 -1
  7. package/dist/harness/writers.js +2 -0
  8. package/dist/harness/writers.js.map +1 -1
  9. package/dist/index.js +29 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp-stdio/api-client.d.ts.map +1 -1
  12. package/dist/mcp-stdio/api-client.js +3 -0
  13. package/dist/mcp-stdio/api-client.js.map +1 -1
  14. package/dist/mcp-stdio/render-flow.d.ts.map +1 -1
  15. package/dist/mcp-stdio/render-flow.js.map +1 -1
  16. package/dist/mcp-stdio/request-context.d.ts.map +1 -1
  17. package/dist/mcp-stdio/request-context.js +1 -0
  18. package/dist/mcp-stdio/request-context.js.map +1 -1
  19. package/dist/mcp-stdio/server.d.ts.map +1 -1
  20. package/dist/mcp-stdio/server.js +75 -3
  21. package/dist/mcp-stdio/server.js.map +1 -1
  22. package/dist/mcp-stdio/sse-progress-client.js.map +1 -1
  23. package/dist/skill-installer/bundle-fetcher.d.ts +40 -0
  24. package/dist/skill-installer/bundle-fetcher.d.ts.map +1 -0
  25. package/dist/skill-installer/bundle-fetcher.js +133 -0
  26. package/dist/skill-installer/bundle-fetcher.js.map +1 -0
  27. package/dist/skill-installer/errors.d.ts +12 -0
  28. package/dist/skill-installer/errors.d.ts.map +1 -0
  29. package/dist/skill-installer/errors.js +42 -0
  30. package/dist/skill-installer/errors.js.map +1 -0
  31. package/dist/skill-installer/index.d.ts +20 -0
  32. package/dist/skill-installer/index.d.ts.map +1 -0
  33. package/dist/skill-installer/index.js +193 -0
  34. package/dist/skill-installer/index.js.map +1 -0
  35. package/dist/skill-installer/lockfile.d.ts +8 -0
  36. package/dist/skill-installer/lockfile.d.ts.map +1 -0
  37. package/dist/skill-installer/lockfile.js +52 -0
  38. package/dist/skill-installer/lockfile.js.map +1 -0
  39. package/dist/skill-installer/npm-installer.d.ts +16 -0
  40. package/dist/skill-installer/npm-installer.d.ts.map +1 -0
  41. package/dist/skill-installer/npm-installer.js +83 -0
  42. package/dist/skill-installer/npm-installer.js.map +1 -0
  43. package/dist/skill-installer/paths.d.ts +4 -0
  44. package/dist/skill-installer/paths.d.ts.map +1 -0
  45. package/dist/skill-installer/paths.js +16 -0
  46. package/dist/skill-installer/paths.js.map +1 -0
  47. package/dist/skill-installer/tar-extractor.d.ts +15 -0
  48. package/dist/skill-installer/tar-extractor.d.ts.map +1 -0
  49. package/dist/skill-installer/tar-extractor.js +56 -0
  50. package/dist/skill-installer/tar-extractor.js.map +1 -0
  51. package/dist/skill-md/template.js +2 -2
  52. package/dist/skill-runner/cli.d.ts +4 -0
  53. package/dist/skill-runner/cli.d.ts.map +1 -0
  54. package/dist/skill-runner/cli.js +81 -0
  55. package/dist/skill-runner/cli.js.map +1 -0
  56. package/dist/skill-runner/discovery.d.ts +3 -0
  57. package/dist/skill-runner/discovery.d.ts.map +1 -0
  58. package/dist/skill-runner/discovery.js +108 -0
  59. package/dist/skill-runner/discovery.js.map +1 -0
  60. package/dist/skill-runner/index.d.ts +9 -0
  61. package/dist/skill-runner/index.d.ts.map +1 -0
  62. package/dist/skill-runner/index.js +100 -0
  63. package/dist/skill-runner/index.js.map +1 -0
  64. package/dist/skill-runner/registry.d.ts +11 -0
  65. package/dist/skill-runner/registry.d.ts.map +1 -0
  66. package/dist/skill-runner/registry.js +79 -0
  67. package/dist/skill-runner/registry.js.map +1 -0
  68. package/dist/skill-runner/spawner.d.ts +14 -0
  69. package/dist/skill-runner/spawner.d.ts.map +1 -0
  70. package/dist/skill-runner/spawner.js +85 -0
  71. package/dist/skill-runner/spawner.js.map +1 -0
  72. package/dist/skill-runner/types.d.ts +62 -0
  73. package/dist/skill-runner/types.d.ts.map +1 -0
  74. package/dist/skill-runner/types.js +6 -0
  75. package/dist/skill-runner/types.js.map +1 -0
  76. package/dist/skill-tunnel/cli.d.ts +5 -0
  77. package/dist/skill-tunnel/cli.d.ts.map +1 -0
  78. package/dist/skill-tunnel/cli.js +205 -0
  79. package/dist/skill-tunnel/cli.js.map +1 -0
  80. package/dist/skill-tunnel/client.d.ts +56 -0
  81. package/dist/skill-tunnel/client.d.ts.map +1 -0
  82. package/dist/skill-tunnel/client.js +260 -0
  83. package/dist/skill-tunnel/client.js.map +1 -0
  84. package/dist/skill-tunnel/handshake.d.ts +35 -0
  85. package/dist/skill-tunnel/handshake.d.ts.map +1 -0
  86. package/dist/skill-tunnel/handshake.js +61 -0
  87. package/dist/skill-tunnel/handshake.js.map +1 -0
  88. package/dist/skill-tunnel/heartbeat.d.ts +34 -0
  89. package/dist/skill-tunnel/heartbeat.d.ts.map +1 -0
  90. package/dist/skill-tunnel/heartbeat.js +86 -0
  91. package/dist/skill-tunnel/heartbeat.js.map +1 -0
  92. package/dist/skill-tunnel/local-bridge.d.ts +30 -0
  93. package/dist/skill-tunnel/local-bridge.d.ts.map +1 -0
  94. package/dist/skill-tunnel/local-bridge.js +224 -0
  95. package/dist/skill-tunnel/local-bridge.js.map +1 -0
  96. package/dist/skill-tunnel/reconnect.d.ts +21 -0
  97. package/dist/skill-tunnel/reconnect.d.ts.map +1 -0
  98. package/dist/skill-tunnel/reconnect.js +72 -0
  99. package/dist/skill-tunnel/reconnect.js.map +1 -0
  100. package/package.json +5 -2
@@ -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":"AAAA,2BAA2B;AAC3B,gDAAgD;AAChD,8CAA8C;AAE9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EACL,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAclD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAyB;IAC1D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE3C,oBAAoB;IACpB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CACd,yBAAyB,CAAC,cAAc,EACxC,iBAAiB,IAAI,oCAAoC,CAC1D,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC;IAEtD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,SAAS,CACd,yBAAyB,CAAC,eAAe,EACzC,2DAA2D,CAC5D,CAAC;IACJ,CAAC;IAED,MAAM,kBAAkB,GAAG,UAAU,IAAI,mBAAmB,CAAC;IAE7D,4DAA4D;IAC5D,wEAAwE;IACxE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,EAAE,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAEpE,mBAAmB;IACnB,IAAI,WAAW,GAAwB,IAAI,CAAC;IAC5C,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,IAAI,CAAC;QACH,IAAI,CAAC;YACH,WAAW,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAA4B,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAClC,OAAO,SAAS,CACd,yBAAyB,CAAC,gBAAgB,EAC1C,uBAAuB,IAAI,gCAAgC,CAC5D,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,sCAAsC;QACtC,IAAI,WAAW,CAAC;QAChB,IAAI,CAAC;YACH,oDAAoD;YACpD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,EAAE,kBAAkB,CAAC,CAAC;YAC/E,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC;YAE9B,WAAW,GAAG,MAAM,WAAW,CAAC;gBAC9B,UAAU;gBACV,UAAU;gBACV,IAAI;gBACJ,OAAO;gBACP,OAAO;aACR,CAAC,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;QAChC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAGT,CAAC;YACF,OAAO,SAAS,CACd,CAAC,CAAC,WAAW,IAAI,yBAAyB,CAAC,QAAQ,EACnD,CAAC,CAAC,OAAO,EACT,CAAC,CAAC,cAAc,CACjB,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,WAAW,CAAC;QAEjE,kDAAkD;QAClD,IAAI,UAAU,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAC/D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,uBAAuB;YACvB,aAAa,CAAC,OAAO,CAAC,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC;gBAChF,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC;gBAChC,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC;oBACpC,UAAU;oBACV,UAAU;oBACV,IAAI;oBACJ,OAAO,EAAE,eAAe;oBACxB,OAAO;iBACR,CAAC,CAAC;gBACH,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;gBAC9B,UAAU,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;YACzE,CAAC;YAAC,OAAO,QAAiB,EAAE,CAAC;gBAC3B,MAAM,CAAC,GAAG,QAA2D,CAAC;gBACtE,OAAO,SAAS,CACd,CAAC,CAAC,WAAW,IAAI,yBAAyB,CAAC,QAAQ,EACnD,CAAC,CAAC,OAAO,CACV,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO,SAAS,CACd,yBAAyB,CAAC,wBAAwB,EAClD,iCAAiC,IAAI,IAAI,eAAe,eAAe,CACxE,CAAC;YACJ,CAAC;QACH,CAAC;QAED,0CAA0C;QAC1C,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC;QAC3E,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;QAE9C,mDAAmD;QACnD,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAAsD,CAAC;YACjE,OAAO,SAAS,CACd,CAAC,CAAC,WAAW,IAAI,yBAAyB,CAAC,qBAAqB,EAChE,CAAC,CAAC,OAAO,CACV,CAAC;QACJ,CAAC;QAED,aAAa;QACb,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAGT,CAAC;YACF,OAAO,SAAS,CACd,CAAC,CAAC,WAAW,IAAI,yBAAyB,CAAC,oBAAoB,EAC/D,CAAC,CAAC,OAAO,EACT,CAAC,CAAC,OAAO,CACV,CAAC;QACJ,CAAC;QAED,6CAA6C;QAC7C,IAAI,CAAC;YACH,cAAc,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAAsD,CAAC;YACjE,OAAO,SAAS,CACd,CAAC,CAAC,WAAW,IAAI,yBAAyB,CAAC,wBAAwB,EACnE,CAAC,CAAC,OAAO,CACV,CAAC;QACJ,CAAC;QAED,yCAAyC;QACzC,+CAA+C;QAC/C,IAAI,CAAC;YACH,MAAM,qBAAqB,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,OAAO,EAAE,CAAC;YACjB,mDAAmD;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6DAA6D,eAAe,KAAM,OAAiB,CAAC,OAAO,IAAI,CAChH,CAAC;QACJ,CAAC;QAED,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI;YACJ,OAAO,EAAE,eAAe;YACxB,WAAW,EAAE,UAAU;YACvB,cAAc,EAAE,kCAAkC;SACnD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,4BAA4B;QAC5B,IAAI,OAAO;YAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,WAAW;YAAE,WAAW,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,SAAS,SAAS,CAChB,IAA2B,EAC3B,OAAe,EACf,OAAiC;IAEjC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI;QACX,OAAO;QACP,OAAO;QACP,cAAc,EAAE,mBAAmB,CAAC,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,qBAAqB,CAClC,eAAuB,EACvB,UAAkB;IAElB,sCAAsC;IACtC,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAG,SAAS,CAAC,eAAe,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBAChD,MAAM,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,WAAW,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACzC,OAAO;IACT,CAAC;IAED,yBAAyB;IACzB,IAAI,CAAC;QACH,WAAW,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;QACjD,MAAM,CAAC,UAAU,EAAE,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/skill-installer/paths.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,qEAAqE;AAErE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAEzE,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,OAAe,EACf,aAAqB,mBAAmB;IAExC,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;QAClD,eAAe,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QACvC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC;QACpD,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI,SAAS,CAAC;KAC5E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { SkillInstallErrorCode } from '@skillfm/contracts/skill-distribution';
2
+ export interface ExtractError {
3
+ installCode: SkillInstallErrorCode;
4
+ message: string;
5
+ }
6
+ /**
7
+ * Extract tarball at tmpPath into installDir.
8
+ * Validates that BUNDLE_REQUIRED_ENTRY_FILES are present after extraction.
9
+ * On failure: cleans up installDir, returns error.
10
+ */
11
+ export declare function extractBundle(opts: {
12
+ tmpPath: string;
13
+ installDir: string;
14
+ }): Promise<void>;
15
+ //# sourceMappingURL=tar-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar-extractor.d.ts","sourceRoot":"","sources":["../../src/skill-installer/tar-extractor.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAEnF,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,qBAAqB,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoChB"}
@@ -0,0 +1,56 @@
1
+ // skill-installer/tar-extractor.ts
2
+ // Extracts skill bundle tarball and validates required entry files.
3
+ import { mkdirSync, existsSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { x as tarExtract } from 'tar';
6
+ import { BUNDLE_REQUIRED_ENTRY_FILES, SKILL_INSTALL_ERROR_CODES, } from '@skillfm/contracts/skill-distribution';
7
+ /**
8
+ * Extract tarball at tmpPath into installDir.
9
+ * Validates that BUNDLE_REQUIRED_ENTRY_FILES are present after extraction.
10
+ * On failure: cleans up installDir, returns error.
11
+ */
12
+ export async function extractBundle(opts) {
13
+ const { tmpPath, installDir } = opts;
14
+ // Create install dir
15
+ mkdirSync(installDir, { recursive: true });
16
+ try {
17
+ // Use tar npm package (ESM-compatible v7)
18
+ await tarExtract({
19
+ file: tmpPath,
20
+ cwd: installDir,
21
+ // Don't strip components — tarball root is the skill dir contents
22
+ });
23
+ }
24
+ catch (err) {
25
+ // Clean up partially extracted dir, but preserve any pre-existing .storage/
26
+ cleanInstallDir(installDir);
27
+ const extractErr = new Error(`Failed to extract bundle: ${err.message}`);
28
+ extractErr.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_EXTRACT_FAILED;
29
+ throw extractErr;
30
+ }
31
+ // Validate required entry files
32
+ for (const requiredFile of BUNDLE_REQUIRED_ENTRY_FILES) {
33
+ const fullPath = join(installDir, requiredFile);
34
+ if (!existsSync(fullPath)) {
35
+ cleanInstallDir(installDir);
36
+ const missingErr = new Error(`Bundle missing required file: ${requiredFile}`);
37
+ missingErr.installCode = SKILL_INSTALL_ERROR_CODES.BUNDLE_MISSING_SERVER_JS;
38
+ throw missingErr;
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Clean install dir — removes everything except .storage/ (user identity state).
44
+ */
45
+ function cleanInstallDir(installDir) {
46
+ try {
47
+ if (!existsSync(installDir))
48
+ return;
49
+ // Remove the whole dir (if brand new, no .storage to preserve)
50
+ rmSync(installDir, { recursive: true, force: true });
51
+ }
52
+ catch {
53
+ // Ignore cleanup errors
54
+ }
55
+ }
56
+ //# sourceMappingURL=tar-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar-extractor.js","sourceRoot":"","sources":["../../src/skill-installer/tar-extractor.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,oEAAoE;AAEpE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,IAAI,UAAU,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,uCAAuC,CAAC;AAQ/C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAGnC;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAErC,qBAAqB;IACrB,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,IAAI,CAAC;QACH,0CAA0C;QAC1C,MAAM,UAAU,CAAC;YACf,IAAI,EAAE,OAAO;YACb,GAAG,EAAE,UAAU;YACf,kEAAkE;SACnE,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,4EAA4E;QAC5E,eAAe,CAAC,UAAU,CAAC,CAAC;QAE5B,MAAM,UAAU,GAAG,IAAI,KAAK,CAC1B,6BAA8B,GAAa,CAAC,OAAO,EAAE,CACJ,CAAC;QACpD,UAAU,CAAC,WAAW,GAAG,yBAAyB,CAAC,qBAAqB,CAAC;QACzE,MAAM,UAAU,CAAC;IACnB,CAAC;IAED,gCAAgC;IAChC,KAAK,MAAM,YAAY,IAAI,2BAA2B,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,eAAe,CAAC,UAAU,CAAC,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,KAAK,CAC1B,iCAAiC,YAAY,EAAE,CACE,CAAC;YACpD,UAAU,CAAC,WAAW,GAAG,yBAAyB,CAAC,wBAAwB,CAAC;YAC5E,MAAM,UAAU,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,UAAkB;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO;QACpC,+DAA+D;QAC/D,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;AACH,CAAC"}
@@ -178,7 +178,7 @@ ScriptCard, 让你的产出更专业。
178
178
 
179
179
  > "你刚用 SkillFM 的 **内容工厂** 完成了 3 篇小红书文案, 这是免费额度的
180
180
  > 最后一次。基于你在做的副业方向 (养护用品), 如果想持续一周产出 3-5 篇,
181
- > Broadcaster 订阅 (\$9 / 月) 解锁全 skill 常态额度, 比重新来过划算很多。
181
+ > Broadcaster 订阅 ($9 / 月) 解锁全 skill 常态额度, 比重新来过划算很多。
182
182
  > 想试的话直接告诉我'订阅'就行。"
183
183
 
184
184
  **不要说**:
@@ -306,7 +306,7 @@ Core principles:
306
306
  > "You just produced 3 Xiaohongshu posts with SkillFM's **content factory** —
307
307
  > that was your last free trial for this skill. For your side-business
308
308
  > direction (pet care products), if you want to keep publishing 3-5 posts
309
- > a week, a Broadcaster subscription (\$9/mo) unlocks the full quota across
309
+ > a week, a Broadcaster subscription ($9/mo) unlocks the full quota across
310
310
  > all skills — worth it vs. starting from scratch each time. Just tell me
311
311
  > 'subscribe' if you want to try."
312
312
 
@@ -0,0 +1,4 @@
1
+ export declare function cmdRunSkill(): Promise<void>;
2
+ export declare function cmdStopSkill(): Promise<void>;
3
+ export declare function cmdListSkills(): void;
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/skill-runner/cli.ts"],"names":[],"mappings":"AAqCA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA4BjD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBlD;AAED,wBAAgB,aAAa,IAAI,IAAI,CAapC"}