skillex 0.2.1 → 0.2.2
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/CHANGELOG.md +11 -0
- package/README.md +57 -18
- package/dist/adapters.js +15 -8
- package/dist/catalog.js +24 -3
- package/dist/cli.js +65 -20
- package/dist/config.d.ts +20 -1
- package/dist/config.js +31 -0
- package/dist/fs.d.ts +7 -0
- package/dist/fs.js +16 -3
- package/dist/install.d.ts +4 -4
- package/dist/install.js +55 -27
- package/dist/runner.js +9 -4
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +194 -50
- package/dist/types.d.ts +24 -2
- package/package.json +1 -1
package/dist/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
1
2
|
import * as path from "node:path";
|
|
2
|
-
import { createSymlink, ensureDir, readJson, readSymlink, readText, removePath, writeText } from "./fs.js";
|
|
3
3
|
import { getAdapter } from "./adapters.js";
|
|
4
|
+
import { copyPath, createSymlink, ensureDir, pathExists, readJson, readSymlink, readText, removePath, writeText, } from "./fs.js";
|
|
4
5
|
import { normalizeSkillContent, parseSkillFrontmatter } from "./skill.js";
|
|
5
6
|
import { SyncError } from "./types.js";
|
|
6
7
|
const MANAGED_START = "<!-- SKILLEX:START -->";
|
|
@@ -12,7 +13,7 @@ const LEGACY_AUTO_INJECT_BLOCKS = [
|
|
|
12
13
|
{ start: "<!-- ASKILL:AUTO-INJECT:START -->", end: "<!-- ASKILL:AUTO-INJECT:END -->" },
|
|
13
14
|
];
|
|
14
15
|
/**
|
|
15
|
-
* Loads installed skill documents from the
|
|
16
|
+
* Loads installed skill documents from the selected Skillex state directory.
|
|
16
17
|
*
|
|
17
18
|
* @param context - Workspace root and lockfile context.
|
|
18
19
|
* @returns Installed skill documents used for sync rendering.
|
|
@@ -21,7 +22,7 @@ export async function loadInstalledSkillDocuments(context) {
|
|
|
21
22
|
const installedEntries = Object.entries(context.lockfile.installed || {}).sort(([left], [right]) => left.localeCompare(right));
|
|
22
23
|
const documents = [];
|
|
23
24
|
for (const [skillId, metadata] of installedEntries) {
|
|
24
|
-
const skillDir =
|
|
25
|
+
const skillDir = resolveInstalledSkillPath(context.cwd, metadata.path);
|
|
25
26
|
const manifest = (await readJson(path.join(skillDir, "skill.json"), {})) || {};
|
|
26
27
|
const entry = manifest.entry || "SKILL.md";
|
|
27
28
|
const rawContent = (await readText(path.join(skillDir, entry), "")) || "";
|
|
@@ -40,7 +41,7 @@ export async function loadInstalledSkillDocuments(context) {
|
|
|
40
41
|
return documents;
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
* Synchronizes installed skills into the target
|
|
44
|
+
* Synchronizes installed skills into the target consumed by an adapter.
|
|
44
45
|
*
|
|
45
46
|
* @param options - Sync execution options.
|
|
46
47
|
* @returns Final sync result.
|
|
@@ -50,26 +51,50 @@ export async function syncAdapterFiles(options) {
|
|
|
50
51
|
try {
|
|
51
52
|
const prepared = await prepareSyncAdapterFiles(options);
|
|
52
53
|
if (!options.dryRun) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await ensureDir(path.dirname(prepared.generatedSourcePath));
|
|
56
|
-
await writeText(prepared.generatedSourcePath, prepared.nextContent);
|
|
57
|
-
}
|
|
58
|
-
if (prepared.syncMode === "symlink" && prepared.generatedSourcePath) {
|
|
54
|
+
if (prepared.directoryEntries) {
|
|
55
|
+
await ensureDir(prepared.absoluteTargetPath);
|
|
59
56
|
const createLink = options.linkFactory || createSymlink;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
let finalMode = prepared.syncMode;
|
|
58
|
+
for (const entry of prepared.directoryEntries) {
|
|
59
|
+
if (prepared.syncMode === "symlink") {
|
|
60
|
+
const linkResult = await createLink(entry.sourcePath, entry.absoluteTargetPath);
|
|
61
|
+
if (linkResult.fallback) {
|
|
62
|
+
(options.warn || console.error)(`Aviso: symlink indisponivel para ${entry.targetPath}; usando copia no lugar.`);
|
|
63
|
+
await copyPath(entry.sourcePath, entry.absoluteTargetPath);
|
|
64
|
+
finalMode = "copy";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await copyPath(entry.sourcePath, entry.absoluteTargetPath);
|
|
69
|
+
}
|
|
66
70
|
}
|
|
71
|
+
for (const cleanupPath of prepared.cleanupPaths) {
|
|
72
|
+
await removePath(cleanupPath);
|
|
73
|
+
}
|
|
74
|
+
prepared.syncMode = finalMode;
|
|
67
75
|
}
|
|
68
76
|
else {
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
await ensureDir(path.dirname(prepared.absoluteTargetPath));
|
|
78
|
+
if (prepared.generatedSourcePath) {
|
|
79
|
+
await ensureDir(path.dirname(prepared.generatedSourcePath));
|
|
80
|
+
await writeText(prepared.generatedSourcePath, prepared.nextContent);
|
|
81
|
+
}
|
|
82
|
+
if (prepared.syncMode === "symlink" && prepared.generatedSourcePath) {
|
|
83
|
+
const createLink = options.linkFactory || createSymlink;
|
|
84
|
+
const linkResult = await createLink(prepared.generatedSourcePath, prepared.absoluteTargetPath);
|
|
85
|
+
if (linkResult.fallback) {
|
|
86
|
+
(options.warn || console.error)(`Aviso: symlink indisponivel para ${prepared.targetPath}; usando copia no lugar.`);
|
|
87
|
+
await writeText(prepared.absoluteTargetPath, prepared.nextContent);
|
|
88
|
+
await removePath(prepared.generatedSourcePath);
|
|
89
|
+
prepared.syncMode = "copy";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await writeText(prepared.absoluteTargetPath, prepared.nextContent);
|
|
94
|
+
}
|
|
95
|
+
for (const cleanupPath of prepared.cleanupPaths) {
|
|
96
|
+
await removePath(cleanupPath);
|
|
97
|
+
}
|
|
73
98
|
}
|
|
74
99
|
}
|
|
75
100
|
return {
|
|
@@ -97,66 +122,80 @@ export async function syncAdapterFiles(options) {
|
|
|
97
122
|
*/
|
|
98
123
|
export async function prepareSyncAdapterFiles(options) {
|
|
99
124
|
const adapter = getAdapter(options.adapterId);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
125
|
+
const absoluteTargetPath = resolveAdapterTargetPath(adapter, options);
|
|
126
|
+
const targetPath = toDisplayPath(options.cwd, absoluteTargetPath, options.statePaths.scope);
|
|
127
|
+
const cleanupPaths = await resolveCleanupPaths(adapter, options, absoluteTargetPath);
|
|
128
|
+
if (adapter.syncMode === "managed-directory") {
|
|
129
|
+
return prepareManagedDirectorySync({
|
|
130
|
+
adapter,
|
|
131
|
+
absoluteTargetPath,
|
|
132
|
+
targetPath,
|
|
133
|
+
cleanupPaths,
|
|
134
|
+
options,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (options.statePaths.scope === "global") {
|
|
138
|
+
throw new SyncError(`Adapter ${adapter.id} nao suporta sync global no momento. Use --scope local.`, "GLOBAL_SYNC_UNSUPPORTED");
|
|
139
|
+
}
|
|
140
|
+
if (!adapter.syncTarget) {
|
|
141
|
+
throw new SyncError(`Adapter ${adapter.id} nao define um alvo de sync.`, "SYNC_TARGET_MISSING");
|
|
142
|
+
}
|
|
102
143
|
const body = renderInstalledSkills(options.skills);
|
|
103
144
|
const autoInjectBlock = buildAutoInjectBlock(options.skills);
|
|
104
|
-
const cleanupPaths = (adapter.legacySyncTargets || [])
|
|
105
|
-
.map((relativePath) => path.join(options.cwd, relativePath))
|
|
106
|
-
.filter((absolutePath) => absolutePath !== targetPath);
|
|
107
145
|
if (adapter.syncMode === "managed-block") {
|
|
108
|
-
const existing = (await readText(
|
|
146
|
+
const existing = (await readText(absoluteTargetPath, "")) || "";
|
|
109
147
|
const nextManaged = upsertManagedBlock(existing, wrapManagedBlock(MANAGED_START, MANAGED_END, body));
|
|
110
148
|
const nextContent = upsertAutoInjectBlock(nextManaged, autoInjectBlock);
|
|
111
149
|
return {
|
|
112
150
|
adapter: adapter.id,
|
|
113
|
-
absoluteTargetPath
|
|
114
|
-
targetPath
|
|
151
|
+
absoluteTargetPath,
|
|
152
|
+
targetPath,
|
|
115
153
|
cleanupPaths,
|
|
116
|
-
changed: normalizeComparableText(existing) !== normalizeComparableText(nextContent),
|
|
154
|
+
changed: normalizeComparableText(existing) !== normalizeComparableText(nextContent) || cleanupPaths.length > 0,
|
|
117
155
|
currentContent: existing,
|
|
118
156
|
nextContent,
|
|
119
|
-
diff: createTextDiff(existing, nextContent,
|
|
157
|
+
diff: createTextDiff(existing, nextContent, targetPath),
|
|
120
158
|
syncMode: "copy",
|
|
121
159
|
};
|
|
122
160
|
}
|
|
123
161
|
const nextContent = buildManagedFileContent(adapter.id, body, autoInjectBlock);
|
|
124
162
|
const requestedMode = options.mode || "symlink";
|
|
125
163
|
if (requestedMode === "copy") {
|
|
126
|
-
const existing = (await readText(
|
|
164
|
+
const existing = (await readText(absoluteTargetPath, "")) || "";
|
|
127
165
|
return {
|
|
128
166
|
adapter: adapter.id,
|
|
129
|
-
absoluteTargetPath
|
|
130
|
-
targetPath
|
|
167
|
+
absoluteTargetPath,
|
|
168
|
+
targetPath,
|
|
131
169
|
cleanupPaths,
|
|
132
|
-
changed: normalizeComparableText(existing) !== normalizeComparableText(nextContent),
|
|
170
|
+
changed: normalizeComparableText(existing) !== normalizeComparableText(nextContent) || cleanupPaths.length > 0,
|
|
133
171
|
currentContent: existing,
|
|
134
172
|
nextContent,
|
|
135
|
-
diff: createTextDiff(existing, nextContent,
|
|
173
|
+
diff: createTextDiff(existing, nextContent, targetPath),
|
|
136
174
|
syncMode: "copy",
|
|
137
175
|
};
|
|
138
176
|
}
|
|
139
177
|
const generatedSourcePath = path.join(options.statePaths.generatedDirPath, adapter.id, path.basename(adapter.syncTarget));
|
|
140
|
-
const currentDescriptor = await describeTarget(
|
|
141
|
-
const currentVisibleContent = (await readText(
|
|
142
|
-
const nextDescriptor = `symlink -> ${toPosix(generatedSourcePath)}\n`;
|
|
178
|
+
const currentDescriptor = await describeTarget(absoluteTargetPath);
|
|
179
|
+
const currentVisibleContent = (await readText(absoluteTargetPath, "")) || "";
|
|
180
|
+
const nextDescriptor = `symlink -> ${toPosix(path.relative(path.dirname(absoluteTargetPath), generatedSourcePath))}\n`;
|
|
143
181
|
const descriptorChanged = normalizeComparableText(currentDescriptor) !== normalizeComparableText(nextDescriptor);
|
|
144
182
|
const contentChanged = normalizeComparableText(currentVisibleContent) !== normalizeComparableText(nextContent);
|
|
145
183
|
return {
|
|
146
184
|
adapter: adapter.id,
|
|
147
|
-
absoluteTargetPath
|
|
148
|
-
targetPath
|
|
185
|
+
absoluteTargetPath,
|
|
186
|
+
targetPath,
|
|
149
187
|
cleanupPaths,
|
|
150
|
-
changed: descriptorChanged || contentChanged,
|
|
188
|
+
changed: descriptorChanged || contentChanged || cleanupPaths.length > 0,
|
|
151
189
|
currentContent: currentDescriptor,
|
|
152
190
|
nextContent,
|
|
153
191
|
diff: createManagedFileDiff({
|
|
154
|
-
targetPath
|
|
192
|
+
targetPath,
|
|
155
193
|
currentDescriptor,
|
|
156
194
|
nextDescriptor,
|
|
157
|
-
generatedPath:
|
|
195
|
+
generatedPath: toDisplayPath(options.cwd, generatedSourcePath, options.statePaths.scope),
|
|
158
196
|
currentContent: currentVisibleContent,
|
|
159
197
|
nextContent,
|
|
198
|
+
cleanupPaths: cleanupPaths.map((cleanupPath) => toDisplayPath(options.cwd, cleanupPath, options.statePaths.scope)),
|
|
160
199
|
}),
|
|
161
200
|
syncMode: "symlink",
|
|
162
201
|
generatedSourcePath,
|
|
@@ -206,6 +245,77 @@ export function buildAutoInjectBlock(skills) {
|
|
|
206
245
|
].join("\n");
|
|
207
246
|
return wrapManagedBlock(AUTO_INJECT_START, AUTO_INJECT_END, body);
|
|
208
247
|
}
|
|
248
|
+
async function prepareManagedDirectorySync(context) {
|
|
249
|
+
const requestedMode = context.options.mode || "symlink";
|
|
250
|
+
const directoryEntries = await Promise.all(context.options.skills.map(async (skill) => {
|
|
251
|
+
const absoluteSkillTargetPath = path.join(context.absoluteTargetPath, skill.id);
|
|
252
|
+
const targetPath = toDisplayPath(context.options.cwd, absoluteSkillTargetPath, context.options.statePaths.scope);
|
|
253
|
+
const currentDescriptor = await describeTarget(absoluteSkillTargetPath);
|
|
254
|
+
const nextDescriptor = requestedMode === "symlink"
|
|
255
|
+
? `symlink -> ${toPosix(path.relative(path.dirname(absoluteSkillTargetPath), skill.skillDir))}\n`
|
|
256
|
+
: "directory\n";
|
|
257
|
+
return {
|
|
258
|
+
skillId: skill.id,
|
|
259
|
+
sourcePath: skill.skillDir,
|
|
260
|
+
absoluteTargetPath: absoluteSkillTargetPath,
|
|
261
|
+
targetPath,
|
|
262
|
+
currentDescriptor,
|
|
263
|
+
nextDescriptor,
|
|
264
|
+
};
|
|
265
|
+
}));
|
|
266
|
+
const changed = directoryEntries.some((entry) => normalizeComparableText(entry.currentDescriptor) !== normalizeComparableText(entry.nextDescriptor)) || context.cleanupPaths.length > 0;
|
|
267
|
+
return {
|
|
268
|
+
adapter: context.adapter.id,
|
|
269
|
+
absoluteTargetPath: context.absoluteTargetPath,
|
|
270
|
+
targetPath: context.targetPath,
|
|
271
|
+
cleanupPaths: context.cleanupPaths,
|
|
272
|
+
changed,
|
|
273
|
+
currentContent: "",
|
|
274
|
+
nextContent: "",
|
|
275
|
+
diff: createManagedDirectoryDiff({
|
|
276
|
+
targetPath: context.targetPath,
|
|
277
|
+
entries: directoryEntries,
|
|
278
|
+
cleanupPaths: context.cleanupPaths.map((cleanupPath) => toDisplayPath(context.options.cwd, cleanupPath, context.options.statePaths.scope)),
|
|
279
|
+
}),
|
|
280
|
+
syncMode: requestedMode,
|
|
281
|
+
directoryEntries,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function resolveCleanupPaths(adapter, options, absoluteTargetPath) {
|
|
285
|
+
const cleanupPaths = new Set();
|
|
286
|
+
for (const legacyTarget of adapter.legacySyncTargets || []) {
|
|
287
|
+
const resolvedLegacyPath = options.statePaths.scope === "global"
|
|
288
|
+
? path.join(absoluteTargetPath, path.basename(legacyTarget))
|
|
289
|
+
: path.resolve(options.cwd, legacyTarget);
|
|
290
|
+
if (await pathExists(resolvedLegacyPath)) {
|
|
291
|
+
cleanupPaths.add(resolvedLegacyPath);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (adapter.syncMode === "managed-directory") {
|
|
295
|
+
const currentSkillIds = new Set(options.skills.map((skill) => skill.id));
|
|
296
|
+
for (const previousSkillId of options.previousSkillIds || []) {
|
|
297
|
+
if (!currentSkillIds.has(previousSkillId)) {
|
|
298
|
+
const stalePath = path.join(absoluteTargetPath, previousSkillId);
|
|
299
|
+
if (await pathExists(stalePath)) {
|
|
300
|
+
cleanupPaths.add(stalePath);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return [...cleanupPaths];
|
|
306
|
+
}
|
|
307
|
+
function resolveAdapterTargetPath(adapter, options) {
|
|
308
|
+
if (options.statePaths.scope === "global") {
|
|
309
|
+
if (!adapter.globalSyncTarget) {
|
|
310
|
+
throw new SyncError(`Adapter ${adapter.id} nao suporta sync global no momento. Use --scope local.`, "GLOBAL_SYNC_UNSUPPORTED");
|
|
311
|
+
}
|
|
312
|
+
return path.resolve(adapter.globalSyncTarget);
|
|
313
|
+
}
|
|
314
|
+
if (!adapter.syncTarget) {
|
|
315
|
+
throw new SyncError(`Adapter ${adapter.id} nao define um alvo de sync.`, "SYNC_TARGET_MISSING");
|
|
316
|
+
}
|
|
317
|
+
return path.join(options.cwd, adapter.syncTarget);
|
|
318
|
+
}
|
|
209
319
|
function renderSkillSection(skill) {
|
|
210
320
|
const body = skill.body.trim() || "_Sem conteudo._";
|
|
211
321
|
return [`### ${skill.name} (\`${skill.id}@${skill.version}\`)`, "", body].join("\n");
|
|
@@ -237,9 +347,6 @@ function buildManagedFileContent(adapterId, body, autoInjectBlock) {
|
|
|
237
347
|
"",
|
|
238
348
|
].join("\n");
|
|
239
349
|
case "cline":
|
|
240
|
-
case "codex":
|
|
241
|
-
case "claude":
|
|
242
|
-
case "gemini":
|
|
243
350
|
return `${sections.join("\n\n")}\n`;
|
|
244
351
|
default:
|
|
245
352
|
throw new SyncError(`Adapter desconhecido: ${adapterId}`, "SYNC_ADAPTER_UNKNOWN");
|
|
@@ -279,15 +386,43 @@ async function describeTarget(targetPath) {
|
|
|
279
386
|
if (linkTarget) {
|
|
280
387
|
return `symlink -> ${toPosix(linkTarget)}\n`;
|
|
281
388
|
}
|
|
282
|
-
|
|
283
|
-
|
|
389
|
+
try {
|
|
390
|
+
const stats = await fs.lstat(targetPath);
|
|
391
|
+
if (stats.isDirectory()) {
|
|
392
|
+
return "directory\n";
|
|
393
|
+
}
|
|
394
|
+
if (stats.isFile()) {
|
|
395
|
+
return "file\n";
|
|
396
|
+
}
|
|
397
|
+
return "path\n";
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
401
|
+
return "";
|
|
402
|
+
}
|
|
403
|
+
throw error;
|
|
284
404
|
}
|
|
285
|
-
|
|
405
|
+
}
|
|
406
|
+
function createManagedDirectoryDiff(context) {
|
|
407
|
+
const parts = [];
|
|
408
|
+
for (const entry of context.entries) {
|
|
409
|
+
if (normalizeComparableText(entry.currentDescriptor) === normalizeComparableText(entry.nextDescriptor)) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
parts.push(createTextDiff(entry.currentDescriptor, entry.nextDescriptor, entry.targetPath).trimEnd());
|
|
413
|
+
}
|
|
414
|
+
for (const cleanupPath of context.cleanupPaths) {
|
|
415
|
+
parts.push(`- remove ${cleanupPath}`);
|
|
416
|
+
}
|
|
417
|
+
if (parts.length === 0) {
|
|
418
|
+
return `Sem alteracoes em ${context.targetPath}.\n`;
|
|
419
|
+
}
|
|
420
|
+
return `${parts.join("\n")}\n`;
|
|
286
421
|
}
|
|
287
422
|
function createManagedFileDiff(context) {
|
|
288
423
|
const descriptorChanged = normalizeComparableText(context.currentDescriptor) !== normalizeComparableText(context.nextDescriptor);
|
|
289
424
|
const contentChanged = normalizeComparableText(context.currentContent) !== normalizeComparableText(context.nextContent);
|
|
290
|
-
if (!descriptorChanged && !contentChanged) {
|
|
425
|
+
if (!descriptorChanged && !contentChanged && context.cleanupPaths.length === 0) {
|
|
291
426
|
return `Sem alteracoes em ${context.targetPath}.\n`;
|
|
292
427
|
}
|
|
293
428
|
const parts = [];
|
|
@@ -297,6 +432,9 @@ function createManagedFileDiff(context) {
|
|
|
297
432
|
if (contentChanged) {
|
|
298
433
|
parts.push(createTextDiff(context.currentContent, context.nextContent, context.generatedPath).trimEnd());
|
|
299
434
|
}
|
|
435
|
+
for (const cleanupPath of context.cleanupPaths) {
|
|
436
|
+
parts.push(`- remove ${cleanupPath}`);
|
|
437
|
+
}
|
|
300
438
|
return `${parts.join("\n")}\n`;
|
|
301
439
|
}
|
|
302
440
|
function createTextDiff(currentContent, nextContent, targetPath) {
|
|
@@ -379,6 +517,12 @@ function diffLines(leftLines, rightLines) {
|
|
|
379
517
|
}
|
|
380
518
|
return operations;
|
|
381
519
|
}
|
|
520
|
+
function resolveInstalledSkillPath(cwd, skillPath) {
|
|
521
|
+
return path.isAbsolute(skillPath) ? skillPath : path.resolve(cwd, skillPath);
|
|
522
|
+
}
|
|
523
|
+
function toDisplayPath(cwd, targetPath, scope) {
|
|
524
|
+
return scope === "local" ? toPosix(path.relative(cwd, targetPath)) : toPosix(targetPath);
|
|
525
|
+
}
|
|
382
526
|
function escapeRegExp(value) {
|
|
383
527
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
384
528
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Supported sync file rendering modes.
|
|
6
6
|
*/
|
|
7
|
-
export type SyncMode = "managed-block" | "managed-file";
|
|
7
|
+
export type SyncMode = "managed-block" | "managed-file" | "managed-directory";
|
|
8
8
|
/**
|
|
9
9
|
* Workspace sync write modes.
|
|
10
10
|
*/
|
|
11
11
|
export type SyncWriteMode = "symlink" | "copy";
|
|
12
|
+
/**
|
|
13
|
+
* Supported install scopes.
|
|
14
|
+
*/
|
|
15
|
+
export type InstallScope = "local" | "global";
|
|
12
16
|
/**
|
|
13
17
|
* Marker used to detect an adapter in a workspace.
|
|
14
18
|
*/
|
|
@@ -23,7 +27,8 @@ export interface AdapterConfig {
|
|
|
23
27
|
id: string;
|
|
24
28
|
label: string;
|
|
25
29
|
markers: AdapterMarker[];
|
|
26
|
-
syncTarget
|
|
30
|
+
syncTarget?: string;
|
|
31
|
+
globalSyncTarget?: string;
|
|
27
32
|
legacySyncTargets?: string[];
|
|
28
33
|
syncMode: SyncMode;
|
|
29
34
|
}
|
|
@@ -165,6 +170,7 @@ export interface SyncMetadata {
|
|
|
165
170
|
adapter: string;
|
|
166
171
|
targetPath: string;
|
|
167
172
|
syncedAt: string;
|
|
173
|
+
skillIds?: string[] | undefined;
|
|
168
174
|
}
|
|
169
175
|
/**
|
|
170
176
|
* Full workspace lockfile structure.
|
|
@@ -202,6 +208,7 @@ export interface ResolvedSkillSelection {
|
|
|
202
208
|
* Common filesystem paths used by the local state manager.
|
|
203
209
|
*/
|
|
204
210
|
export interface StatePaths {
|
|
211
|
+
scope: InstallScope;
|
|
205
212
|
stateDir: string;
|
|
206
213
|
lockfilePath: string;
|
|
207
214
|
skillsDirPath: string;
|
|
@@ -212,6 +219,7 @@ export interface StatePaths {
|
|
|
212
219
|
*/
|
|
213
220
|
export interface ProjectOptions extends CatalogSourceInput {
|
|
214
221
|
cwd?: string | undefined;
|
|
222
|
+
scope?: InstallScope | undefined;
|
|
215
223
|
agentSkillsDir?: string | undefined;
|
|
216
224
|
adapter?: string | undefined;
|
|
217
225
|
autoSync?: boolean | undefined;
|
|
@@ -261,6 +269,18 @@ export interface PreparedSyncResult {
|
|
|
261
269
|
diff: string;
|
|
262
270
|
syncMode: SyncWriteMode;
|
|
263
271
|
generatedSourcePath?: string | undefined;
|
|
272
|
+
directoryEntries?: PreparedDirectoryEntry[] | undefined;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Prepared directory entry for directory-native adapter sync.
|
|
276
|
+
*/
|
|
277
|
+
export interface PreparedDirectoryEntry {
|
|
278
|
+
skillId: string;
|
|
279
|
+
sourcePath: string;
|
|
280
|
+
absoluteTargetPath: string;
|
|
281
|
+
targetPath: string;
|
|
282
|
+
currentDescriptor: string;
|
|
283
|
+
nextDescriptor: string;
|
|
264
284
|
}
|
|
265
285
|
/**
|
|
266
286
|
* Dry-run preview returned before a sync writes files.
|
|
@@ -286,9 +306,11 @@ export interface SyncResult {
|
|
|
286
306
|
*/
|
|
287
307
|
export interface SyncOptions {
|
|
288
308
|
cwd: string;
|
|
309
|
+
scope?: InstallScope | undefined;
|
|
289
310
|
adapterId: string;
|
|
290
311
|
statePaths: StatePaths;
|
|
291
312
|
skills: InstalledSkillDocument[];
|
|
313
|
+
previousSkillIds?: string[] | undefined;
|
|
292
314
|
mode?: SyncWriteMode | undefined;
|
|
293
315
|
dryRun?: boolean | undefined;
|
|
294
316
|
linkFactory?: ((targetPath: string, linkPath: string) => Promise<CreateSymlinkResult>) | undefined;
|