@myagentroam/node 0.1.6 → 0.1.8

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.
@@ -0,0 +1,504 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { skillInstallMetadataSchema } from '@myagentroam/protocol';
7
+ export class SkillInstallService {
8
+ async recoverNodeHome(home) {
9
+ const agentsRoot = path.join(home, '.agents', 'skills');
10
+ const claudeRoot = path.join(home, '.claude', 'skills');
11
+ await assertExistingContainedDirectory(home, agentsRoot);
12
+ const roots = [agentsRoot];
13
+ if (await exists(claudeRoot)) {
14
+ const stat = await lstat(claudeRoot);
15
+ if (stat.isSymbolicLink()) {
16
+ if (!(await exists(agentsRoot)) ||
17
+ (await realpath(claudeRoot)) !== (await realpath(agentsRoot)))
18
+ throw new Error('NODE_SKILL_LINK_UNAVAILABLE');
19
+ }
20
+ else {
21
+ await assertContainedDirectory(home, claudeRoot);
22
+ roots.push(claudeRoot);
23
+ }
24
+ }
25
+ await recoverTransactions(roots);
26
+ }
27
+ async recoverWorkspace(workspace) {
28
+ const roots = [
29
+ path.join(workspace, '.agents', 'skills'),
30
+ path.join(workspace, '.claude', 'skills')
31
+ ];
32
+ for (const root of roots)
33
+ await assertExistingContainedDirectory(workspace, root);
34
+ await recoverTransactions(roots);
35
+ }
36
+ async installNodeHome(home, name, bundle, metadata) {
37
+ assertName(name);
38
+ skillInstallMetadataSchema.parse(metadata);
39
+ const agentsRoot = path.join(home, '.agents', 'skills');
40
+ await mkdir(agentsRoot, { recursive: true });
41
+ await assertContainedDirectory(home, agentsRoot);
42
+ const claudeRoot = path.join(home, '.claude', 'skills');
43
+ const mode = await ensureClaudeRoot(agentsRoot, claudeRoot);
44
+ if (mode === 'MANAGED_COPY')
45
+ await assertContainedDirectory(home, claudeRoot);
46
+ await replaceDirectory(agentsRoot, name, bundle, metadata);
47
+ if (mode === 'MANAGED_COPY')
48
+ await replaceDirectory(claudeRoot, name, bundle, metadata);
49
+ }
50
+ async installWorkspace(workspace, name, bundle, metadata, options = {}) {
51
+ assertName(name);
52
+ skillInstallMetadataSchema.parse(metadata);
53
+ if (!options.confirmTracked && (await workspaceSkillTracked(workspace, name)))
54
+ throw new Error('WORKSPACE_SKILL_TRACKED_CONFIRM_REQUIRED');
55
+ const agentsRoot = path.join(workspace, '.agents', 'skills');
56
+ const claudeRoot = path.join(workspace, '.claude', 'skills');
57
+ await mkdir(agentsRoot, { recursive: true });
58
+ await mkdir(claudeRoot, { recursive: true });
59
+ await assertContainedDirectory(workspace, agentsRoot);
60
+ await assertContainedDirectory(workspace, claudeRoot);
61
+ await replacePair(agentsRoot, claudeRoot, name, bundle, metadata, () => updateGitExclude(workspace, name, true), () => updateGitExclude(workspace, name, false));
62
+ }
63
+ async removeWorkspace(workspace, name) {
64
+ assertName(name);
65
+ const agentsRoot = path.join(workspace, '.agents', 'skills');
66
+ const claudeRoot = path.join(workspace, '.claude', 'skills');
67
+ await assertExistingContainedDirectory(workspace, agentsRoot);
68
+ await assertExistingContainedDirectory(workspace, claudeRoot);
69
+ await removePair(agentsRoot, claudeRoot, name, () => updateGitExclude(workspace, name, false), () => updateGitExclude(workspace, name, true));
70
+ }
71
+ async removeNodeHome(home, name) {
72
+ assertName(name);
73
+ const agentsRoot = path.join(home, '.agents', 'skills');
74
+ const claudeRoot = path.join(home, '.claude', 'skills');
75
+ const mode = await ensureClaudeRoot(agentsRoot, claudeRoot);
76
+ if (mode === 'MANAGED_COPY')
77
+ await assertContainedDirectory(home, claudeRoot);
78
+ if (mode === 'MANAGED_COPY')
79
+ await removePair(agentsRoot, claudeRoot, name);
80
+ else
81
+ await removeSingle(agentsRoot, name);
82
+ }
83
+ async repairWorkspaceGitExclude(workspace) {
84
+ const root = path.join(workspace, '.agents', 'skills');
85
+ const names = new Set();
86
+ try {
87
+ for (const entry of await readdir(root, { withFileTypes: true })) {
88
+ if (!entry.isDirectory() || entry.isSymbolicLink())
89
+ continue;
90
+ try {
91
+ assertName(entry.name);
92
+ names.add(entry.name);
93
+ }
94
+ catch {
95
+ // Invalid directory names remain visible to inspection but are never written as patterns.
96
+ }
97
+ }
98
+ }
99
+ catch (error) {
100
+ if (!isMissing(error))
101
+ throw error;
102
+ }
103
+ await rewriteGitExclude(workspace, names);
104
+ }
105
+ }
106
+ export async function workspaceSkillTracked(workspace, name) {
107
+ assertName(name);
108
+ if (!(await exists(path.join(workspace, '.git'))))
109
+ return false;
110
+ try {
111
+ const result = await promisify(execFile)('git', ['-C', workspace, 'ls-files', '--', `.agents/skills/${name}`, `.claude/skills/${name}`], { timeout: 5_000, windowsHide: true });
112
+ return result.stdout.trim() !== '';
113
+ }
114
+ catch {
115
+ throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
116
+ }
117
+ }
118
+ async function replacePair(firstRoot, secondRoot, name, bundle, metadata, beforeCommit, rollbackSideEffect) {
119
+ const transactionId = randomUUID();
120
+ const first = await prepare(firstRoot, name, bundle, metadata);
121
+ let second;
122
+ let sideEffectApplied = false;
123
+ try {
124
+ second = await prepare(secondRoot, name, bundle, metadata);
125
+ await writeTransaction([firstRoot, secondRoot], transactionId, 'PREPARED', [first, second]);
126
+ await commitPrepared(first);
127
+ await commitPrepared(second);
128
+ await beforeCommit?.();
129
+ sideEffectApplied = beforeCommit !== undefined;
130
+ await writeTransaction([firstRoot, secondRoot], transactionId, 'COMMITTED', [first, second]);
131
+ }
132
+ catch (error) {
133
+ if (sideEffectApplied)
134
+ await rollbackSideEffect?.();
135
+ if (second !== undefined)
136
+ await rollbackPrepared(second);
137
+ await rollbackPrepared(first);
138
+ await removeTransaction([firstRoot, secondRoot], transactionId);
139
+ throw error;
140
+ }
141
+ try {
142
+ await cleanupPrepared(first);
143
+ await cleanupPrepared(second);
144
+ await removeTransaction([firstRoot, secondRoot], transactionId);
145
+ }
146
+ catch {
147
+ // COMMITTED journal remains authoritative and the next inspect/install finishes cleanup.
148
+ }
149
+ }
150
+ async function replaceDirectory(root, name, bundle, metadata) {
151
+ const transactionId = randomUUID();
152
+ const prepared = await prepare(root, name, bundle, metadata);
153
+ try {
154
+ await writeTransaction([root], transactionId, 'PREPARED', [prepared]);
155
+ await commitPrepared(prepared);
156
+ await writeTransaction([root], transactionId, 'COMMITTED', [prepared]);
157
+ }
158
+ catch (error) {
159
+ await rollbackPrepared(prepared);
160
+ await removeTransaction([root], transactionId);
161
+ throw error;
162
+ }
163
+ try {
164
+ await cleanupPrepared(prepared);
165
+ await removeTransaction([root], transactionId);
166
+ }
167
+ catch {
168
+ // COMMITTED journal remains authoritative and the next inspect/install finishes cleanup.
169
+ }
170
+ }
171
+ async function writeTransaction(roots, transactionId, state, items) {
172
+ const record = {
173
+ schemaVersion: 1,
174
+ transactionId,
175
+ state,
176
+ roots,
177
+ items: items.map(({ target, staging, backup, hadTarget }) => ({
178
+ target,
179
+ staging,
180
+ backup,
181
+ hadTarget
182
+ }))
183
+ };
184
+ for (const root of roots) {
185
+ await mkdir(root, { recursive: true });
186
+ const destination = transactionPath(root, transactionId);
187
+ const staging = `${destination}.tmp`;
188
+ await writeFile(staging, JSON.stringify(record), { flag: 'w' });
189
+ await rename(staging, destination);
190
+ }
191
+ }
192
+ async function removeTransaction(roots, transactionId) {
193
+ await Promise.all(roots.map((root) => rm(transactionPath(root, transactionId), { force: true })));
194
+ }
195
+ async function recoverTransactions(roots) {
196
+ const normalizedRoots = roots.map((root) => path.resolve(root));
197
+ const records = new Map();
198
+ for (const root of normalizedRoots) {
199
+ let entries;
200
+ try {
201
+ entries = await readdir(root);
202
+ }
203
+ catch (error) {
204
+ if (isMissing(error))
205
+ continue;
206
+ throw error;
207
+ }
208
+ for (const entry of entries.filter((name) => /^\.mar-transaction-[0-9a-f-]+\.json$/u.test(name))) {
209
+ let record;
210
+ try {
211
+ record = JSON.parse(await readFile(path.join(root, entry), 'utf8'));
212
+ validateTransaction(record, normalizedRoots);
213
+ }
214
+ catch {
215
+ throw new Error('SKILL_INSTALL_RECOVERY_REQUIRED');
216
+ }
217
+ records.set(record.transactionId, record);
218
+ }
219
+ }
220
+ for (const record of records.values()) {
221
+ if (record.state === 'COMMITTED') {
222
+ for (const item of record.items) {
223
+ await rm(item.backup, { recursive: true, force: true });
224
+ await rm(item.staging, { recursive: true, force: true });
225
+ }
226
+ }
227
+ else {
228
+ for (const item of [...record.items].reverse())
229
+ await recoverPrepared(item);
230
+ }
231
+ await removeTransaction(record.roots, record.transactionId);
232
+ }
233
+ }
234
+ async function recoverPrepared(item) {
235
+ if (await exists(item.backup)) {
236
+ await rm(item.target, { recursive: true, force: true });
237
+ await rename(item.backup, item.target);
238
+ }
239
+ else if (!item.hadTarget && (await exists(item.target)) && !(await exists(item.staging))) {
240
+ await rm(item.target, { recursive: true, force: true });
241
+ }
242
+ await rm(item.staging, { recursive: true, force: true });
243
+ }
244
+ function validateTransaction(record, roots) {
245
+ if (record.schemaVersion !== 1 ||
246
+ !/^[0-9a-f-]{36}$/u.test(record.transactionId) ||
247
+ !['PREPARED', 'COMMITTED'].includes(record.state) ||
248
+ !Array.isArray(record.roots) ||
249
+ record.roots.some((root) => !roots.includes(path.resolve(root))) ||
250
+ !Array.isArray(record.items) ||
251
+ record.items.length < 1 ||
252
+ record.items.length > 2)
253
+ throw new Error('SKILL_INSTALL_RECOVERY_REQUIRED');
254
+ for (const item of record.items) {
255
+ const root = path.dirname(item.target);
256
+ if (!roots.includes(path.resolve(root)) ||
257
+ path.dirname(item.staging) !== root ||
258
+ path.dirname(item.backup) !== root ||
259
+ !path.basename(item.staging).startsWith('.mar-staging-') ||
260
+ !path.basename(item.backup).startsWith('.mar-backup-'))
261
+ throw new Error('SKILL_INSTALL_RECOVERY_REQUIRED');
262
+ }
263
+ }
264
+ function transactionPath(root, transactionId) {
265
+ return path.join(root, `.mar-transaction-${transactionId}.json`);
266
+ }
267
+ async function prepare(root, name, bundle, metadata) {
268
+ await mkdir(root, { recursive: true });
269
+ const caseCollision = (await readdir(root)).find((entry) => entry.toLowerCase() === name && entry !== name);
270
+ if (caseCollision !== undefined)
271
+ throw new Error('SKILL_NAME_CONFLICT');
272
+ const target = path.join(root, name);
273
+ const staging = path.join(root, `.mar-staging-${randomUUID()}`);
274
+ const backup = path.join(root, `.mar-backup-${randomUUID()}`);
275
+ const hadTarget = await exists(target);
276
+ if (hadTarget) {
277
+ const targetStat = await lstat(target);
278
+ if (!targetStat.isDirectory() && !targetStat.isSymbolicLink())
279
+ throw new Error('SKILL_INSTALL_INVALID_LINK');
280
+ }
281
+ await mkdir(staging, { recursive: false });
282
+ try {
283
+ if (bundle.schemaVersion !== 1 || bundle.files.length === 0 || bundle.files.length > 1_000)
284
+ throw new Error('SKILL_CONTENT_INVALID');
285
+ const paths = new Set();
286
+ for (const file of bundle.files) {
287
+ assertRelative(file.path);
288
+ if (paths.has(file.path))
289
+ throw new Error('SKILL_CONTENT_INVALID');
290
+ paths.add(file.path);
291
+ const destination = path.join(staging, ...file.path.split('/'));
292
+ await mkdir(path.dirname(destination), { recursive: true });
293
+ await writeFile(destination, Buffer.from(file.contentBase64, 'base64'), { flag: 'wx' });
294
+ }
295
+ await writeFile(path.join(staging, '.mar-skill-install.json'), JSON.stringify(metadata), {
296
+ flag: 'wx'
297
+ });
298
+ }
299
+ catch (error) {
300
+ await rm(staging, { recursive: true, force: true });
301
+ throw error;
302
+ }
303
+ return { target, staging, backup, hadTarget, committed: false };
304
+ }
305
+ async function assertContainedDirectory(container, directory) {
306
+ const [containerReal, directoryReal] = await Promise.all([
307
+ realpath(container),
308
+ realpath(directory)
309
+ ]);
310
+ const relative = path.relative(containerReal, directoryReal);
311
+ if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative))
312
+ throw new Error('SKILL_PATH_UNSAFE');
313
+ }
314
+ async function assertExistingContainedDirectory(container, directory) {
315
+ if (await exists(directory))
316
+ await assertContainedDirectory(container, directory);
317
+ }
318
+ async function removeSingle(root, name) {
319
+ const target = path.join(root, name);
320
+ if (!(await exists(target)))
321
+ return;
322
+ const stat = await lstat(target);
323
+ if (!stat.isDirectory() || stat.isSymbolicLink())
324
+ throw new Error('SKILL_INSTALL_INVALID_LINK');
325
+ const backup = path.join(root, `.mar-delete-${randomUUID()}`);
326
+ await rename(target, backup);
327
+ await rm(backup, { recursive: true, force: true });
328
+ }
329
+ async function commitPrepared(item) {
330
+ if (item.hadTarget)
331
+ await rename(item.target, item.backup);
332
+ await rename(item.staging, item.target);
333
+ item.committed = true;
334
+ }
335
+ async function rollbackPrepared(item) {
336
+ if (item.committed)
337
+ await rm(item.target, { recursive: true, force: true });
338
+ if (await exists(item.backup))
339
+ await rename(item.backup, item.target);
340
+ await rm(item.staging, { recursive: true, force: true });
341
+ }
342
+ async function cleanupPrepared(item) {
343
+ await rm(item.backup, { recursive: true, force: true });
344
+ }
345
+ async function removePair(firstRoot, secondRoot, name, beforeCommit, rollbackSideEffect) {
346
+ const first = path.join(firstRoot, name);
347
+ const second = path.join(secondRoot, name);
348
+ for (const target of [first, second]) {
349
+ if (!(await exists(target)))
350
+ continue;
351
+ const stat = await lstat(target);
352
+ if (!stat.isDirectory() || stat.isSymbolicLink())
353
+ throw new Error('SKILL_INSTALL_INVALID_LINK');
354
+ }
355
+ const firstBackup = `${first}.mar-delete-${randomUUID()}`;
356
+ const secondBackup = `${second}.mar-delete-${randomUUID()}`;
357
+ let sideEffectApplied = false;
358
+ try {
359
+ if (await exists(first))
360
+ await rename(first, firstBackup);
361
+ if (await exists(second))
362
+ await rename(second, secondBackup);
363
+ await beforeCommit?.();
364
+ sideEffectApplied = beforeCommit !== undefined;
365
+ await rm(firstBackup, { recursive: true, force: true });
366
+ await rm(secondBackup, { recursive: true, force: true });
367
+ }
368
+ catch (error) {
369
+ if (sideEffectApplied)
370
+ await rollbackSideEffect?.();
371
+ if (await exists(firstBackup))
372
+ await rename(firstBackup, first);
373
+ if (await exists(secondBackup))
374
+ await rename(secondBackup, second);
375
+ throw error;
376
+ }
377
+ }
378
+ async function ensureClaudeRoot(agentsRoot, claudeRoot) {
379
+ try {
380
+ const stat = await lstat(claudeRoot);
381
+ if (stat.isSymbolicLink()) {
382
+ const linked = await realpath(path.resolve(path.dirname(claudeRoot), await readlink(claudeRoot)));
383
+ const agents = await realpath(agentsRoot);
384
+ if (linked !== agents)
385
+ throw new Error('NODE_SKILL_LINK_UNAVAILABLE');
386
+ return 'LINK';
387
+ }
388
+ if (stat.isDirectory())
389
+ return 'MANAGED_COPY';
390
+ throw new Error('NODE_SKILL_LINK_UNAVAILABLE');
391
+ }
392
+ catch (error) {
393
+ if (!isMissing(error))
394
+ throw error;
395
+ await mkdir(path.dirname(claudeRoot), { recursive: true });
396
+ try {
397
+ await symlink(agentsRoot, claudeRoot, process.platform === 'win32' ? 'junction' : 'dir');
398
+ return 'LINK';
399
+ }
400
+ catch (linkError) {
401
+ if (process.platform !== 'win32')
402
+ throw linkError;
403
+ await mkdir(claudeRoot, { recursive: true });
404
+ return 'MANAGED_COPY';
405
+ }
406
+ }
407
+ }
408
+ async function updateGitExclude(workspace, name, add) {
409
+ await mutateGitExclude(workspace, (entries) => {
410
+ for (const root of ['.agents', '.claude']) {
411
+ const line = `/${root}/skills/${name}/`;
412
+ if (add)
413
+ entries.add(line);
414
+ else
415
+ entries.delete(line);
416
+ }
417
+ });
418
+ }
419
+ async function rewriteGitExclude(workspace, names) {
420
+ await mutateGitExclude(workspace, (entries) => {
421
+ entries.clear();
422
+ for (const name of names)
423
+ for (const root of ['.agents', '.claude'])
424
+ entries.add(`/${root}/skills/${name}/`);
425
+ });
426
+ }
427
+ async function mutateGitExclude(workspace, mutate) {
428
+ const git = path.join(workspace, '.git');
429
+ if (!(await exists(git)))
430
+ return;
431
+ let exclude;
432
+ try {
433
+ const result = await promisify(execFile)('git', ['-C', workspace, 'rev-parse', '--git-path', 'info/exclude', '--git-common-dir'], { timeout: 5_000, windowsHide: true });
434
+ const [excludeOutput, commonOutput] = result.stdout.trim().split(/\r?\n/u);
435
+ if (!excludeOutput || !commonOutput)
436
+ throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
437
+ exclude = path.resolve(workspace, excludeOutput);
438
+ const common = path.resolve(workspace, commonOutput);
439
+ const relative = path.relative(common, exclude);
440
+ if (relative.startsWith('..') || path.isAbsolute(relative))
441
+ throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
442
+ }
443
+ catch {
444
+ throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
445
+ }
446
+ await mkdir(path.dirname(exclude), { recursive: true });
447
+ const begin = '# BEGIN MyAgentRoam Skills';
448
+ const end = '# END MyAgentRoam Skills';
449
+ let content = '';
450
+ try {
451
+ const excludeStat = await lstat(exclude);
452
+ if (!excludeStat.isFile() || excludeStat.isSymbolicLink())
453
+ throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
454
+ content = await readFile(exclude, 'utf8');
455
+ }
456
+ catch (error) {
457
+ if (!isMissing(error))
458
+ throw error;
459
+ }
460
+ const pattern = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}\\n?`, 'u');
461
+ const existing = pattern.exec(content)?.[0] ?? '';
462
+ const entries = new Set(existing.split(/\r?\n/u).filter((line) => line.startsWith('/.')));
463
+ mutate(entries);
464
+ const block = entries.size === 0 ? '' : `${begin}\n${[...entries].sort().join('\n')}\n${end}\n`;
465
+ const next = content.replace(pattern, '').replace(/\s*$/u, '\n') + block;
466
+ const staging = path.join(path.dirname(exclude), `.mar-git-exclude-${randomUUID()}.tmp`);
467
+ try {
468
+ await writeFile(staging, next, { encoding: 'utf8', flag: 'wx' });
469
+ await rename(staging, exclude);
470
+ }
471
+ catch (error) {
472
+ await rm(staging, { force: true });
473
+ throw error;
474
+ }
475
+ }
476
+ function assertName(name) {
477
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) ||
478
+ /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u.test(name))
479
+ throw new Error('SKILL_NAME_CONFLICT');
480
+ }
481
+ function assertRelative(value) {
482
+ if (value === '' ||
483
+ value.startsWith('/') ||
484
+ value.includes('\\') ||
485
+ value.split('/').some((part) => part === '' || part === '.' || part === '..'))
486
+ throw new Error('SKILL_PATH_UNSAFE');
487
+ }
488
+ async function exists(value) {
489
+ try {
490
+ await lstat(value);
491
+ return true;
492
+ }
493
+ catch (error) {
494
+ if (isMissing(error))
495
+ return false;
496
+ throw error;
497
+ }
498
+ }
499
+ function isMissing(error) {
500
+ return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
501
+ }
502
+ function escapeRegExp(value) {
503
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
504
+ }
@@ -0,0 +1,44 @@
1
+ import type { NodeConfig } from '../config.js';
2
+ import type { NodeDatabase } from '../database.js';
3
+ export declare class SkillNodeOperationService {
4
+ private readonly database;
5
+ private readonly config;
6
+ private readonly directories;
7
+ private readonly installer;
8
+ private readonly activeTargets;
9
+ private readonly jobs;
10
+ constructor(database: () => NodeDatabase, config: () => NodeConfig);
11
+ operations(): {
12
+ 'node.skills.inspect': () => Promise<import("./skill-directory-service.js").LocalSkillTargetInspection>;
13
+ 'node.skills.install': (data: unknown) => Promise<{
14
+ accepted: boolean;
15
+ installJobId: `${string}-${string}-${string}-${string}-${string}`;
16
+ }>;
17
+ 'node.skills.remove': (data: unknown) => Promise<{
18
+ removed: boolean;
19
+ }>;
20
+ 'workspace.skills.inspect': (data: unknown) => Promise<import("./skill-directory-service.js").LocalSkillTargetInspection>;
21
+ 'workspace.skills.install': (data: unknown) => Promise<{
22
+ accepted: boolean;
23
+ installJobId: `${string}-${string}-${string}-${string}-${string}`;
24
+ }>;
25
+ 'workspace.skills.remove': (data: unknown) => Promise<{
26
+ removed: boolean;
27
+ }>;
28
+ 'workspace.skills.git-exclude-repair': (data: unknown) => Promise<{
29
+ repaired: boolean;
30
+ }>;
31
+ 'skill.install.job.get': (data: unknown) => {
32
+ errorCode?: string;
33
+ installJobId: string;
34
+ status: "RUNNING" | "SUCCEEDED" | "FAILED";
35
+ };
36
+ };
37
+ private install;
38
+ private job;
39
+ private cleanupJobs;
40
+ private performInstall;
41
+ private remove;
42
+ private workspacePath;
43
+ private download;
44
+ }