@danieljvdm/dev-kit 0.2.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 +290 -0
- package/bin/dev-kit.mjs +3 -0
- package/dev-kit.example.jsonc +13 -0
- package/package.json +69 -0
- package/schema/dev-kit.schema.json +128 -0
- package/schema/skill-sources.schema.json +83 -0
- package/skill-sources.jsonc +55 -0
- package/skill-sources.lock.json +136 -0
- package/skills/dev-kit/SKILL.md +145 -0
- package/skills/dev-kit/agents/openai.yaml +4 -0
- package/skills/effect-ts/SKILL.md +242 -0
- package/skills/effect-ts/UPSTREAM.md +28 -0
- package/skills/effect-ts/agents/openai.yaml +5 -0
- package/skills/effect-ts/references/audit-services.md +144 -0
- package/skills/effect-ts/references/features.md +525 -0
- package/skills/effect-ts/references/guide-cli.md +106 -0
- package/skills/effect-ts/references/guide-effect.md +453 -0
- package/skills/effect-ts/references/guide-error-handling.md +574 -0
- package/skills/effect-ts/references/guide-http-boundaries.md +55 -0
- package/skills/effect-ts/references/guide-layers.md +1017 -0
- package/skills/effect-ts/references/guide-observability.md +771 -0
- package/skills/effect-ts/references/guide-retries.md +446 -0
- package/skills/effect-ts/references/guide-schedule.md +357 -0
- package/skills/effect-ts/references/guide-schema.md +671 -0
- package/skills/effect-ts/references/guide-sql.md +539 -0
- package/skills/effect-ts/references/guide-testing.md +534 -0
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +131 -0
- package/skills/effect-ts/references/version-and-source.md +87 -0
- package/src/bin/dev-kit.ts +372 -0
- package/src/catalog-manager.ts +345 -0
- package/src/catalog.ts +246 -0
- package/src/cli-ui.ts +110 -0
- package/src/effect-source.ts +325 -0
- package/src/effect-tsgo.ts +256 -0
- package/src/gitignore.ts +212 -0
- package/src/index.ts +98 -0
- package/src/manifest.ts +133 -0
- package/src/node-symbolic-link.ts +31 -0
- package/src/path-digest.ts +140 -0
- package/src/project-process-lock.ts +76 -0
- package/src/project-state.ts +67 -0
- package/src/skill-manager.ts +326 -0
- package/src/source-manifest.ts +51 -0
- package/src/sync.ts +900 -0
- package/src/tool-metadata.ts +3 -0
- package/src/typescript-package-name.ts +5 -0
- package/src/vendor.ts +848 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { DigestSchema } from "./path-digest.ts";
|
|
4
|
+
|
|
5
|
+
export const ManagedSkillOutputSchema = Schema.Struct({
|
|
6
|
+
resourceId: Schema.String,
|
|
7
|
+
path: Schema.String,
|
|
8
|
+
skill: Schema.String,
|
|
9
|
+
target: Schema.Literals(["agents", "claude", "opencode"]),
|
|
10
|
+
mode: Schema.Literals(["copy", "symlink"]),
|
|
11
|
+
kind: Schema.Literals(["directory", "symlink"]),
|
|
12
|
+
digest: DigestSchema,
|
|
13
|
+
catalog: Schema.optional(
|
|
14
|
+
Schema.Struct({
|
|
15
|
+
source: Schema.String,
|
|
16
|
+
repository: Schema.String,
|
|
17
|
+
resolved: Schema.String,
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
});
|
|
21
|
+
export type ManagedSkillOutput = typeof ManagedSkillOutputSchema.Type;
|
|
22
|
+
|
|
23
|
+
export const EffectTsgoLockSchema = Schema.Struct({
|
|
24
|
+
effectTsgoVersion: Schema.String,
|
|
25
|
+
typescriptPackage: Schema.String,
|
|
26
|
+
typescriptVersion: Schema.String,
|
|
27
|
+
});
|
|
28
|
+
export type EffectTsgoLock = typeof EffectTsgoLockSchema.Type;
|
|
29
|
+
|
|
30
|
+
export const EffectSourceLockSchema = Schema.Struct({
|
|
31
|
+
packageName: Schema.String,
|
|
32
|
+
packageVersion: Schema.String,
|
|
33
|
+
path: Schema.String,
|
|
34
|
+
repository: Schema.String,
|
|
35
|
+
tag: Schema.String,
|
|
36
|
+
});
|
|
37
|
+
export type EffectSourceLock = typeof EffectSourceLockSchema.Type;
|
|
38
|
+
|
|
39
|
+
export const DevKitLockSchema = Schema.Struct({
|
|
40
|
+
version: Schema.Literal(1),
|
|
41
|
+
toolVersion: Schema.String,
|
|
42
|
+
manifestDigest: DigestSchema,
|
|
43
|
+
setup: Schema.optional(
|
|
44
|
+
Schema.Struct({
|
|
45
|
+
effectSource: Schema.optional(EffectSourceLockSchema),
|
|
46
|
+
effectTsgo: Schema.optional(EffectTsgoLockSchema),
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
outputs: Schema.Array(ManagedSkillOutputSchema),
|
|
50
|
+
});
|
|
51
|
+
export type DevKitLock = typeof DevKitLockSchema.Type;
|
|
52
|
+
|
|
53
|
+
export const OwnershipReceiptSchema = Schema.Struct({
|
|
54
|
+
resourceId: Schema.String,
|
|
55
|
+
path: Schema.String,
|
|
56
|
+
mode: Schema.Literals(["copy", "symlink"]),
|
|
57
|
+
kind: Schema.Literals(["directory", "symlink"]),
|
|
58
|
+
digest: DigestSchema,
|
|
59
|
+
});
|
|
60
|
+
export type OwnershipReceipt = typeof OwnershipReceiptSchema.Type;
|
|
61
|
+
|
|
62
|
+
export const AppliedStateSchema = Schema.Struct({
|
|
63
|
+
version: Schema.Literal(1),
|
|
64
|
+
appliedLockDigest: DigestSchema,
|
|
65
|
+
outputs: Schema.Array(OwnershipReceiptSchema),
|
|
66
|
+
});
|
|
67
|
+
export type AppliedState = typeof AppliedStateSchema.Type;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { applyEdits, modify, parse as parseJsonc, type ParseError } from "jsonc-parser";
|
|
2
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
3
|
+
import { Prompt } from "effect/unstable/cli";
|
|
4
|
+
|
|
5
|
+
import { loadSkillCatalog } from "./catalog.ts";
|
|
6
|
+
import { isInteractiveTerminal, printDetail, printLine, printStatus } from "./cli-ui.ts";
|
|
7
|
+
import { DevKitManifestSchema } from "./manifest.ts";
|
|
8
|
+
import { runProjectSkillPlan } from "./sync.ts";
|
|
9
|
+
import { patchProjectGitignore } from "./gitignore.ts";
|
|
10
|
+
|
|
11
|
+
class SkillManagerError extends Schema.TaggedErrorClass<SkillManagerError>()(
|
|
12
|
+
"SkillManagerError",
|
|
13
|
+
{ message: Schema.String },
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
type ManagerOptions = {
|
|
17
|
+
readonly projectDir?: string;
|
|
18
|
+
readonly manifestPath?: string;
|
|
19
|
+
readonly apply?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const packageRoot = Effect.fn("skillManagerPackageRoot")(function* () {
|
|
23
|
+
const path = yield* Path.Path;
|
|
24
|
+
return path.resolve(path.dirname(yield* path.fromFileUrl(new URL(import.meta.url))), "..");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
28
|
+
options: ManagerOptions,
|
|
29
|
+
) {
|
|
30
|
+
const path = yield* Path.Path;
|
|
31
|
+
const projectDir = path.resolve(options.projectDir ?? ".");
|
|
32
|
+
return {
|
|
33
|
+
projectDir,
|
|
34
|
+
manifestPath: path.resolve(projectDir, options.manifestPath ?? "dev-kit.jsonc"),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const defaultManifest = `{
|
|
39
|
+
"$schema": "./node_modules/@danieljvdm/dev-kit/schema/dev-kit.schema.json",
|
|
40
|
+
"include": [],
|
|
41
|
+
"targets": {
|
|
42
|
+
"agents": { "enabled": true, "mode": "copy" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
48
|
+
options: ManagerOptions,
|
|
49
|
+
create = false,
|
|
50
|
+
) {
|
|
51
|
+
const fs = yield* FileSystem.FileSystem;
|
|
52
|
+
const paths = yield* resolvePaths(options);
|
|
53
|
+
if (!(yield* fs.exists(paths.manifestPath))) {
|
|
54
|
+
if (!create) {
|
|
55
|
+
return yield* new SkillManagerError({
|
|
56
|
+
message: "dev-kit.jsonc not found. Run `dev-kit init` first.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
yield* fs.writeFileString(paths.manifestPath, defaultManifest);
|
|
60
|
+
yield* patchProjectGitignore({ projectDir: paths.projectDir });
|
|
61
|
+
}
|
|
62
|
+
const raw = yield* fs.readFileString(paths.manifestPath);
|
|
63
|
+
const errors: Array<ParseError> = [];
|
|
64
|
+
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
65
|
+
if (errors.length > 0) {
|
|
66
|
+
return yield* new SkillManagerError({ message: `could not parse ${paths.manifestPath}` });
|
|
67
|
+
}
|
|
68
|
+
const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema)(parsed).pipe(
|
|
69
|
+
Effect.mapError((error) => new SkillManagerError({ message: error.message })),
|
|
70
|
+
);
|
|
71
|
+
return { ...paths, manifest, raw };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const writeArray = Effect.fn("writeManifestArray")(function* (
|
|
75
|
+
manifestPath: string,
|
|
76
|
+
raw: string,
|
|
77
|
+
property: "include" | "exclude",
|
|
78
|
+
values: ReadonlyArray<string>,
|
|
79
|
+
) {
|
|
80
|
+
const fs = yield* FileSystem.FileSystem;
|
|
81
|
+
const parsed = parseJsonc(raw) as Record<string, unknown>;
|
|
82
|
+
const current = Array.isArray(parsed[property])
|
|
83
|
+
? (parsed[property] as Array<unknown>).filter((value): value is string => typeof value === "string")
|
|
84
|
+
: undefined;
|
|
85
|
+
if (current === undefined) {
|
|
86
|
+
if (values.length === 0) return;
|
|
87
|
+
const edits = modify(raw, [property], [...values], {
|
|
88
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
89
|
+
});
|
|
90
|
+
yield* fs.writeFileString(manifestPath, applyEdits(raw, edits));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let next = raw;
|
|
94
|
+
const retained = [...current];
|
|
95
|
+
for (let index = current.length - 1; index >= 0; index -= 1) {
|
|
96
|
+
if (!values.includes(current[index]!)) {
|
|
97
|
+
next = applyEdits(next, modify(next, [property, index], undefined, {
|
|
98
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
99
|
+
}));
|
|
100
|
+
retained.splice(index, 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const value of values) {
|
|
104
|
+
if (retained.includes(value)) continue;
|
|
105
|
+
next = applyEdits(next, modify(next, [property, retained.length], value, {
|
|
106
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
107
|
+
isArrayInsertion: true,
|
|
108
|
+
}));
|
|
109
|
+
retained.push(value);
|
|
110
|
+
}
|
|
111
|
+
if (next !== raw) yield* fs.writeFileString(manifestPath, next);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const selectedNames = (
|
|
115
|
+
include: ReadonlyArray<string>,
|
|
116
|
+
exclude: ReadonlyArray<string>,
|
|
117
|
+
families: Readonly<Record<string, ReadonlyArray<string>>>,
|
|
118
|
+
) => {
|
|
119
|
+
const selected = new Set<string>();
|
|
120
|
+
for (const name of include) {
|
|
121
|
+
for (const skill of families[name] ?? [name]) selected.add(skill);
|
|
122
|
+
}
|
|
123
|
+
for (const name of exclude) {
|
|
124
|
+
for (const skill of families[name] ?? [name]) selected.delete(skill);
|
|
125
|
+
}
|
|
126
|
+
return selected;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const summary = (description: string, fallback: string): string => {
|
|
130
|
+
const text = description || fallback;
|
|
131
|
+
const firstSentence = text.match(/^.*?[.!?](?:\s|$)/)?.[0]?.trim() ?? text;
|
|
132
|
+
return firstSentence.length > 96 ? `${firstSentence.slice(0, 93).trimEnd()}…` : firstSentence;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const applyIfRequested = (options: ManagerOptions) =>
|
|
136
|
+
options.apply === false
|
|
137
|
+
? printStatus("success", "Manifest updated", "run dev-kit sync to apply")
|
|
138
|
+
: runProjectSkillPlan({
|
|
139
|
+
...(options.projectDir === undefined ? {} : { projectDir: options.projectDir }),
|
|
140
|
+
...(options.manifestPath === undefined ? {} : { manifestPath: options.manifestPath }),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const initProject = Effect.fn("initDevKitProject")(function* (options: ManagerOptions) {
|
|
144
|
+
const fs = yield* FileSystem.FileSystem;
|
|
145
|
+
const paths = yield* resolvePaths(options);
|
|
146
|
+
if (yield* fs.exists(paths.manifestPath)) {
|
|
147
|
+
yield* printStatus("info", "Already initialized", paths.manifestPath);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
yield* fs.writeFileString(paths.manifestPath, defaultManifest);
|
|
151
|
+
yield* patchProjectGitignore({ projectDir: paths.projectDir });
|
|
152
|
+
yield* printStatus("success", "Created dev-kit.jsonc");
|
|
153
|
+
yield* printDetail("Add a skill with: dev-kit add <name>");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export const addSkills = Effect.fn("addManagedSkills")(function* (
|
|
157
|
+
names: ReadonlyArray<string>,
|
|
158
|
+
options: ManagerOptions,
|
|
159
|
+
) {
|
|
160
|
+
const current = yield* readManifest(options, true);
|
|
161
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
162
|
+
const known = new Set([...catalog.skills.map((skill) => skill.name), ...Object.keys(catalog.families)]);
|
|
163
|
+
const unknown = names.filter((name) => !known.has(name));
|
|
164
|
+
if (unknown.length > 0) {
|
|
165
|
+
return yield* new SkillManagerError({
|
|
166
|
+
message: `unknown skill${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}. Try \`dev-kit search ${unknown[0]}\`.`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const include = [...new Set([...current.manifest.include, ...names])];
|
|
170
|
+
const exclude = (current.manifest.exclude ?? []).filter((name) => !names.includes(name));
|
|
171
|
+
yield* writeArray(current.manifestPath, current.raw, "include", include);
|
|
172
|
+
const reread = yield* FileSystem.FileSystem;
|
|
173
|
+
yield* writeArray(
|
|
174
|
+
current.manifestPath,
|
|
175
|
+
yield* reread.readFileString(current.manifestPath),
|
|
176
|
+
"exclude",
|
|
177
|
+
exclude,
|
|
178
|
+
);
|
|
179
|
+
yield* applyIfRequested(options);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
export const removeSkills = Effect.fn("removeManagedSkills")(function* (
|
|
183
|
+
names: ReadonlyArray<string>,
|
|
184
|
+
options: ManagerOptions,
|
|
185
|
+
) {
|
|
186
|
+
const current = yield* readManifest(options);
|
|
187
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
188
|
+
const before = selectedNames(
|
|
189
|
+
current.manifest.include,
|
|
190
|
+
current.manifest.exclude ?? [],
|
|
191
|
+
catalog.families,
|
|
192
|
+
);
|
|
193
|
+
const absent = names.filter((name) => !before.has(name) && !current.manifest.include.includes(name));
|
|
194
|
+
if (absent.length > 0) {
|
|
195
|
+
return yield* new SkillManagerError({ message: `not selected: ${absent.join(", ")}` });
|
|
196
|
+
}
|
|
197
|
+
const include = current.manifest.include.filter((name) => !names.includes(name));
|
|
198
|
+
const excluded = new Set(current.manifest.exclude ?? []);
|
|
199
|
+
for (const name of names) {
|
|
200
|
+
if (before.has(name) && !current.manifest.include.includes(name)) excluded.add(name);
|
|
201
|
+
else excluded.delete(name);
|
|
202
|
+
}
|
|
203
|
+
yield* writeArray(current.manifestPath, current.raw, "include", include);
|
|
204
|
+
const fs = yield* FileSystem.FileSystem;
|
|
205
|
+
yield* writeArray(
|
|
206
|
+
current.manifestPath,
|
|
207
|
+
yield* fs.readFileString(current.manifestPath),
|
|
208
|
+
"exclude",
|
|
209
|
+
[...excluded].sort(),
|
|
210
|
+
);
|
|
211
|
+
yield* applyIfRequested(options);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
export const listSkills = Effect.fn("listManagedSkills")(function* (
|
|
215
|
+
options: ManagerOptions & { readonly all?: boolean; readonly query?: string },
|
|
216
|
+
) {
|
|
217
|
+
const fs = yield* FileSystem.FileSystem;
|
|
218
|
+
const paths = yield* resolvePaths(options);
|
|
219
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
220
|
+
const manifest = (yield* fs.exists(paths.manifestPath))
|
|
221
|
+
? (yield* readManifest(options)).manifest
|
|
222
|
+
: { include: [], exclude: [] };
|
|
223
|
+
const selected = selectedNames(manifest.include, manifest.exclude ?? [], catalog.families);
|
|
224
|
+
const query = options.query?.toLowerCase();
|
|
225
|
+
const visible = catalog.skills.filter((skill) =>
|
|
226
|
+
(options.all || selected.has(skill.name)) &&
|
|
227
|
+
(!query || `${skill.name} ${skill.description} ${skill.source}`.toLowerCase().includes(query)),
|
|
228
|
+
);
|
|
229
|
+
if (visible.length === 0) {
|
|
230
|
+
yield* printStatus("info", query ? "No matching skills" : "No skills selected");
|
|
231
|
+
if (!query && !options.all) yield* printDetail("Browse with: dev-kit list --all");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
for (const skill of visible) {
|
|
235
|
+
const marker = selected.has(skill.name) ? "✓" : " ";
|
|
236
|
+
const origin = skill.bundled ? "built in" : skill.source;
|
|
237
|
+
yield* printLine(`${marker} ${skill.name} ${summary(skill.description, origin)}`);
|
|
238
|
+
}
|
|
239
|
+
yield* printLine();
|
|
240
|
+
yield* printLine(`${selected.size} selected · ${catalog.skills.length} approved`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
export const showSkill = Effect.fn("showCatalogSkill")(function* (name: string) {
|
|
244
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
245
|
+
const skill = catalog.skills.find((candidate) => candidate.name === name);
|
|
246
|
+
if (!skill) return yield* new SkillManagerError({ message: `unknown skill: ${name}` });
|
|
247
|
+
yield* printLine(skill.name);
|
|
248
|
+
if (skill.description) yield* printLine(skill.description);
|
|
249
|
+
yield* printLine(`Source: ${skill.bundled ? "dev-kit (built in)" : skill.source}`);
|
|
250
|
+
if (!skill.bundled) {
|
|
251
|
+
const source = catalog.lock?.sources.find((candidate) => candidate.id === skill.source);
|
|
252
|
+
if (source) {
|
|
253
|
+
yield* printLine(`Repository: ${source.repository}`);
|
|
254
|
+
yield* printLine(`Approved commit: ${source.resolved}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export const showDashboard = Effect.fn("showSkillDashboard")(function* (options: ManagerOptions) {
|
|
260
|
+
yield* printLine("dev-kit skills");
|
|
261
|
+
yield* printLine();
|
|
262
|
+
yield* listSkills({ ...options, all: false });
|
|
263
|
+
yield* printLine("Add dev-kit add <skill>");
|
|
264
|
+
yield* printLine("Browse dev-kit list --all");
|
|
265
|
+
yield* printLine("Find dev-kit search <words>");
|
|
266
|
+
yield* printLine("Remove dev-kit remove <skill>");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
export const chooseSkillsToAdd = Effect.fn("chooseSkillsToAdd")(function* (
|
|
270
|
+
options: ManagerOptions,
|
|
271
|
+
) {
|
|
272
|
+
if (!(yield* isInteractiveTerminal)) {
|
|
273
|
+
return yield* new SkillManagerError({ message: "pass one or more skill names, or run this command in a terminal" });
|
|
274
|
+
}
|
|
275
|
+
const current = yield* readManifest(options, true);
|
|
276
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
277
|
+
const selected = selectedNames(
|
|
278
|
+
current.manifest.include,
|
|
279
|
+
current.manifest.exclude ?? [],
|
|
280
|
+
catalog.families,
|
|
281
|
+
);
|
|
282
|
+
const available = catalog.skills.filter((skill) => !selected.has(skill.name));
|
|
283
|
+
if (available.length === 0) {
|
|
284
|
+
yield* printStatus("success", "All approved skills are selected");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const names = yield* Prompt.multiSelect({
|
|
288
|
+
message: "Choose skills to add",
|
|
289
|
+
choices: available.map((skill) => ({
|
|
290
|
+
title: skill.name,
|
|
291
|
+
value: skill.name,
|
|
292
|
+
description: summary(skill.description, skill.source),
|
|
293
|
+
})),
|
|
294
|
+
min: 1,
|
|
295
|
+
});
|
|
296
|
+
yield* addSkills(names, options);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export const chooseSkillsToRemove = Effect.fn("chooseSkillsToRemove")(function* (
|
|
300
|
+
options: ManagerOptions,
|
|
301
|
+
) {
|
|
302
|
+
if (!(yield* isInteractiveTerminal)) {
|
|
303
|
+
return yield* new SkillManagerError({ message: "pass one or more skill names, or run this command in a terminal" });
|
|
304
|
+
}
|
|
305
|
+
const current = yield* readManifest(options);
|
|
306
|
+
const catalog = yield* loadSkillCatalog(yield* packageRoot());
|
|
307
|
+
const selected = selectedNames(
|
|
308
|
+
current.manifest.include,
|
|
309
|
+
current.manifest.exclude ?? [],
|
|
310
|
+
catalog.families,
|
|
311
|
+
);
|
|
312
|
+
if (selected.size === 0) {
|
|
313
|
+
yield* printStatus("info", "No skills selected");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const names = yield* Prompt.multiSelect({
|
|
317
|
+
message: "Choose skills to remove",
|
|
318
|
+
choices: catalog.skills.filter((skill) => selected.has(skill.name)).map((skill) => ({
|
|
319
|
+
title: skill.name,
|
|
320
|
+
value: skill.name,
|
|
321
|
+
description: summary(skill.description, skill.source),
|
|
322
|
+
})),
|
|
323
|
+
min: 1,
|
|
324
|
+
});
|
|
325
|
+
yield* removeSkills(names, options);
|
|
326
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { DigestSchema } from "./path-digest.ts";
|
|
4
|
+
|
|
5
|
+
export const ExternalSkillSourceSchema = Schema.Struct({
|
|
6
|
+
id: Schema.String,
|
|
7
|
+
repository: Schema.String,
|
|
8
|
+
ref: Schema.String,
|
|
9
|
+
skillsPath: Schema.String,
|
|
10
|
+
include: Schema.Array(Schema.String),
|
|
11
|
+
exclude: Schema.optional(Schema.Array(Schema.String)),
|
|
12
|
+
licensePath: Schema.optional(Schema.String),
|
|
13
|
+
stripFrontmatter: Schema.optional(Schema.Array(Schema.String)),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type ExternalSkillSource = typeof ExternalSkillSourceSchema.Type;
|
|
17
|
+
|
|
18
|
+
export const SkillSourcesManifestSchema = Schema.Struct({
|
|
19
|
+
$schema: Schema.optional(Schema.String),
|
|
20
|
+
sources: Schema.Array(ExternalSkillSourceSchema),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type SkillSourcesManifest = typeof SkillSourcesManifestSchema.Type;
|
|
24
|
+
|
|
25
|
+
export const LockedSkillSourceSchema = Schema.Struct({
|
|
26
|
+
id: Schema.String,
|
|
27
|
+
repository: Schema.String,
|
|
28
|
+
ref: Schema.String,
|
|
29
|
+
resolved: Schema.String,
|
|
30
|
+
skillsPath: Schema.String,
|
|
31
|
+
include: Schema.Array(Schema.String),
|
|
32
|
+
exclude: Schema.optional(Schema.Array(Schema.String)),
|
|
33
|
+
skills: Schema.Array(Schema.String),
|
|
34
|
+
descriptions: Schema.optional(
|
|
35
|
+
Schema.Record(Schema.String, Schema.String),
|
|
36
|
+
),
|
|
37
|
+
digests: Schema.optional(
|
|
38
|
+
Schema.Record(Schema.String, DigestSchema),
|
|
39
|
+
),
|
|
40
|
+
licensePath: Schema.optional(Schema.String),
|
|
41
|
+
stripFrontmatter: Schema.optional(Schema.Array(Schema.String)),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type LockedSkillSource = typeof LockedSkillSourceSchema.Type;
|
|
45
|
+
|
|
46
|
+
export const SkillSourcesLockSchema = Schema.Struct({
|
|
47
|
+
version: Schema.Literal(1),
|
|
48
|
+
sources: Schema.Array(LockedSkillSourceSchema),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type SkillSourcesLock = typeof SkillSourcesLockSchema.Type;
|