skilld 1.2.3 → 1.3.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 +21 -20
- package/dist/_chunks/agent.mjs +471 -17
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs +2 -2
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +8 -2
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +2 -2
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs +421 -0
- package/dist/_chunks/cli-helpers.mjs.map +1 -0
- package/dist/_chunks/detect.mjs +51 -22
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/detect2.mjs +2 -0
- package/dist/_chunks/embedding-cache.mjs +13 -4
- package/dist/_chunks/embedding-cache.mjs.map +1 -1
- package/dist/_chunks/formatting.mjs +1 -286
- package/dist/_chunks/formatting.mjs.map +1 -1
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/install.mjs +4 -3
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/list.mjs +3 -2
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/pool.mjs +3 -2
- package/dist/_chunks/pool.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +38 -4
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +3 -2
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +4 -3
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/setup.mjs +27 -0
- package/dist/_chunks/setup.mjs.map +1 -0
- package/dist/_chunks/shared.mjs +6 -2
- package/dist/_chunks/shared.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +1 -1
- package/dist/_chunks/sources.mjs +1 -1
- package/dist/_chunks/sync.mjs +389 -108
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/uninstall.mjs +16 -2
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/agent/index.d.mts +22 -4
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +3 -3
- package/dist/cli.mjs +619 -328
- package/dist/cli.mjs.map +1 -1
- package/dist/retriv/index.d.mts +18 -3
- package/dist/retriv/index.d.mts.map +1 -1
- package/dist/retriv/index.mjs +30 -1
- package/dist/retriv/index.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +2 -0
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +1 -0
- package/dist/retriv/worker.mjs.map +1 -1
- package/dist/sources/index.mjs +1 -1
- package/package.json +3 -2
- package/dist/_chunks/chunk.mjs +0 -15
package/dist/cli.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { c as getOAuthProviderList, i as getAvailableModels, l as loginOAuthProvider, o as getModelName, t as detectImportedPackages, u as logoutOAuthProvider } from "./_chunks/agent.mjs";
|
|
2
3
|
import { i as getPackageDbPath, o as getCacheDir, t as CACHE_DIR } from "./_chunks/config.mjs";
|
|
3
4
|
import "./_chunks/sanitize.mjs";
|
|
4
5
|
import "./_chunks/cache.mjs";
|
|
5
6
|
import "./_chunks/yaml.mjs";
|
|
6
7
|
import "./_chunks/markdown.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { n as getSharedSkillsDir, o as semverGt, r as mapInsert } from "./_chunks/shared.mjs";
|
|
8
9
|
import { r as fetchNpmRegistryMeta, t as fetchLatestVersion } from "./_chunks/sources.mjs";
|
|
9
|
-
import { a as targets, i as getAgentVersion } from "./_chunks/detect.mjs";
|
|
10
|
+
import { a as targets, i as getAgentVersion, r as detectTargetAgent, t as detectInstalledAgents } from "./_chunks/detect.mjs";
|
|
10
11
|
import { o as unlinkSkillFromAgents } from "./_chunks/prompts.mjs";
|
|
11
|
-
import { i as
|
|
12
|
-
import { C as hasCompletedWizard, O as updateConfig, T as readConfig, _ as requireInteractive, b as version, c as timeAgo, d as getInstalledGenerators, f as getRepoHint, g as relativeTime, h as promptForAgent, i as formatSource, l as timedSpinner, m as isInteractive, p as introLine, u as formatStatus, v as resolveAgent, w as hasConfig, x as defaultFeatures, y as sharedArgs } from "./_chunks/formatting.mjs";
|
|
12
|
+
import { S as readConfig, T as updateConfig, _ as version, a as getRepoHint, b as hasCompletedWizard, c as isInteractive, d as pickModel, f as promptForAgent, g as sharedArgs, h as resolveAgent, i as getInstalledGenerators, l as isRunningInsideAgent, m as requireInteractive, n as OAUTH_NOTE, o as guard, p as relativeTime, r as formatStatus, s as introLine, t as NO_MODELS_MESSAGE, u as menuLoop, v as defaultFeatures, x as hasConfig } from "./_chunks/cli-helpers.mjs";
|
|
13
13
|
import { c as removeLockEntry, i as iterateSkills, n as getSkillsDir, o as parsePackages, r as isOutdated, t as getProjectState } from "./_chunks/skills.mjs";
|
|
14
|
+
import { c as timeAgo, i as formatSource, l as timedSpinner } from "./_chunks/formatting.mjs";
|
|
14
15
|
import { join, resolve } from "pathe";
|
|
15
16
|
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
16
17
|
import { execSync } from "node:child_process";
|
|
@@ -19,117 +20,214 @@ import * as p from "@clack/prompts";
|
|
|
19
20
|
import { defineCommand, runMain } from "citty";
|
|
20
21
|
//#region src/commands/config.ts
|
|
21
22
|
async function configCommand() {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
23
|
+
const initConfig = readConfig();
|
|
24
|
+
const agentId = initConfig.agent || detectTargetAgent() || void 0;
|
|
25
|
+
const cyan = (s) => `\x1B[36m${s}\x1B[90m`;
|
|
26
|
+
const modelLabel = initConfig.skipLlm ? "skip" : initConfig.model ? cyan(getModelName(initConfig.model)) : "auto";
|
|
27
|
+
const agentLabel = agentId && targets[agentId] ? cyan(targets[agentId].displayName) : "auto-detect";
|
|
28
|
+
p.note(`\x1B[90mFetch docs → Enhance with ${modelLabel} → Install to ${agentLabel}\x1B[0m`, "How skilld works");
|
|
29
|
+
await menuLoop({
|
|
26
30
|
message: "Settings",
|
|
27
|
-
options:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
options: () => {
|
|
32
|
+
const config = readConfig();
|
|
33
|
+
const features = config.features ?? defaultFeatures;
|
|
34
|
+
const enabledCount = Object.values(features).filter(Boolean).length;
|
|
35
|
+
const modelHint = config.skipLlm ? "disabled" : config.model ? getModelName(config.model) : "auto";
|
|
36
|
+
const connectedOAuth = getOAuthProviderList().filter((pr) => pr.loggedIn).length;
|
|
37
|
+
const oauthHint = connectedOAuth > 0 ? `${connectedOAuth} connected` : "none";
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
label: "Data sources",
|
|
41
|
+
value: "features",
|
|
42
|
+
hint: `${enabledCount}/4 enabled · issues, releases, search, discussions`
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "OAuth providers",
|
|
46
|
+
value: "oauth",
|
|
47
|
+
hint: `${oauthHint} · pi-ai direct API`
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Enhancement model",
|
|
51
|
+
value: "model",
|
|
52
|
+
hint: `${modelHint} · rewrites SKILL.md with best practices`
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: "Target agent",
|
|
56
|
+
value: "agent",
|
|
57
|
+
hint: `${config.agent || "auto-detect"} · where skills are installed`
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
},
|
|
61
|
+
onSelect: async (action) => {
|
|
62
|
+
switch (action) {
|
|
63
|
+
case "features": {
|
|
64
|
+
const features = readConfig().features ?? defaultFeatures;
|
|
65
|
+
const selected = guard(await p.multiselect({
|
|
66
|
+
message: "Data sources",
|
|
67
|
+
options: [
|
|
68
|
+
{
|
|
69
|
+
label: "Semantic + token search",
|
|
70
|
+
value: "search",
|
|
71
|
+
hint: "local query engine to cut token costs and speed up grep"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: "Release notes",
|
|
75
|
+
value: "releases",
|
|
76
|
+
hint: "track changelogs for installed packages"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: "GitHub issues",
|
|
80
|
+
value: "issues",
|
|
81
|
+
hint: "surface common problems and solutions"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: "GitHub discussions",
|
|
85
|
+
value: "discussions",
|
|
86
|
+
hint: "include Q&A and community knowledge"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
initialValues: Object.entries(features).filter(([, v]) => v).map(([k]) => k),
|
|
90
|
+
required: false
|
|
91
|
+
}));
|
|
92
|
+
updateConfig({ features: {
|
|
93
|
+
search: selected.includes("search"),
|
|
94
|
+
issues: selected.includes("issues"),
|
|
95
|
+
discussions: selected.includes("discussions"),
|
|
96
|
+
releases: selected.includes("releases")
|
|
97
|
+
} });
|
|
98
|
+
p.log.success(`Data sources updated: ${selected.length} enabled`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "oauth":
|
|
102
|
+
await configureOAuth();
|
|
103
|
+
break;
|
|
104
|
+
case "model":
|
|
105
|
+
await configureModel();
|
|
106
|
+
break;
|
|
107
|
+
case "agent": {
|
|
108
|
+
const config = readConfig();
|
|
109
|
+
const agentChoice = guard(await p.select({
|
|
110
|
+
message: "Target agent — where should skills be installed?",
|
|
111
|
+
options: [{
|
|
112
|
+
label: "Auto-detect",
|
|
113
|
+
value: ""
|
|
114
|
+
}, ...Object.entries(targets).map(([id, a]) => ({
|
|
115
|
+
label: a.displayName,
|
|
116
|
+
value: id,
|
|
117
|
+
hint: a.skillsDir
|
|
118
|
+
}))],
|
|
119
|
+
initialValue: config.agent || ""
|
|
120
|
+
}));
|
|
121
|
+
updateConfig({ agent: agentChoice || void 0 });
|
|
122
|
+
p.log.success(agentChoice ? `Target agent set to ${agentChoice}` : "Target agent will be auto-detected");
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
42
125
|
}
|
|
43
|
-
]
|
|
44
|
-
});
|
|
45
|
-
if (p.isCancel(action)) {
|
|
46
|
-
p.cancel("Cancelled");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
switch (action) {
|
|
50
|
-
case "features": {
|
|
51
|
-
const selected = await p.multiselect({
|
|
52
|
-
message: "Enable features",
|
|
53
|
-
options: [
|
|
54
|
-
{
|
|
55
|
-
label: "Semantic + token search",
|
|
56
|
-
value: "search",
|
|
57
|
-
hint: "local query engine to cut token costs and speed up grep"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
label: "Release notes",
|
|
61
|
-
value: "releases",
|
|
62
|
-
hint: "track changelogs for installed packages"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
label: "GitHub issues",
|
|
66
|
-
value: "issues",
|
|
67
|
-
hint: "surface common problems and solutions"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
label: "GitHub discussions",
|
|
71
|
-
value: "discussions",
|
|
72
|
-
hint: "include Q&A and community knowledge"
|
|
73
|
-
}
|
|
74
|
-
].map((f) => ({
|
|
75
|
-
label: f.label,
|
|
76
|
-
value: f.value,
|
|
77
|
-
hint: f.hint
|
|
78
|
-
})),
|
|
79
|
-
initialValues: Object.entries(features).filter(([, v]) => v).map(([k]) => k),
|
|
80
|
-
required: false
|
|
81
|
-
});
|
|
82
|
-
if (p.isCancel(selected)) return;
|
|
83
|
-
updateConfig({ features: {
|
|
84
|
-
search: selected.includes("search"),
|
|
85
|
-
issues: selected.includes("issues"),
|
|
86
|
-
discussions: selected.includes("discussions"),
|
|
87
|
-
releases: selected.includes("releases")
|
|
88
|
-
} });
|
|
89
|
-
p.log.success(`Features updated: ${selected.length} enabled`);
|
|
90
|
-
break;
|
|
91
126
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function configureOAuth() {
|
|
130
|
+
p.note(OAUTH_NOTE, "How OAuth works");
|
|
131
|
+
await menuLoop({
|
|
132
|
+
message: "OAuth providers",
|
|
133
|
+
options: () => {
|
|
134
|
+
return getOAuthProviderList().map((pr) => ({
|
|
135
|
+
label: pr.name,
|
|
136
|
+
value: pr.id,
|
|
137
|
+
hint: pr.loggedIn ? "\x1B[32mconnected\x1B[0m" : "not connected"
|
|
138
|
+
}));
|
|
139
|
+
},
|
|
140
|
+
onSelect: async (providerId) => {
|
|
141
|
+
const pr = getOAuthProviderList().find((p2) => p2.id === providerId);
|
|
142
|
+
if (!pr) return;
|
|
143
|
+
if (pr.loggedIn) {
|
|
144
|
+
if (guard(await p.select({
|
|
145
|
+
message: pr.name,
|
|
146
|
+
options: [{
|
|
147
|
+
label: "Disconnect",
|
|
148
|
+
value: "disconnect"
|
|
149
|
+
}, {
|
|
150
|
+
label: "Back",
|
|
151
|
+
value: "back"
|
|
152
|
+
}]
|
|
153
|
+
})) === "disconnect") {
|
|
154
|
+
logoutOAuthProvider(providerId);
|
|
155
|
+
p.log.success(`Disconnected from ${pr.name}`);
|
|
156
|
+
}
|
|
96
157
|
return;
|
|
97
158
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
const spinner = p.spinner();
|
|
160
|
+
spinner.start("Connecting...");
|
|
161
|
+
const success = await loginOAuthProvider(providerId, {
|
|
162
|
+
onAuth: (url, instructions) => {
|
|
163
|
+
spinner.stop("Open this URL in your browser:");
|
|
164
|
+
p.log.info(` \x1B[36m${url}\x1B[0m`);
|
|
165
|
+
if (instructions) p.log.info(` \x1B[90m${instructions}\x1B[0m`);
|
|
166
|
+
spinner.start("Waiting for authentication...");
|
|
167
|
+
},
|
|
168
|
+
onPrompt: async (message, placeholder) => {
|
|
169
|
+
const value = await p.text({
|
|
170
|
+
message,
|
|
171
|
+
placeholder
|
|
172
|
+
});
|
|
173
|
+
if (p.isCancel(value)) return "";
|
|
174
|
+
return value;
|
|
175
|
+
},
|
|
176
|
+
onProgress: (msg) => p.log.step(msg)
|
|
177
|
+
}).catch((err) => {
|
|
178
|
+
spinner.stop(`Login failed: ${err.message}`);
|
|
179
|
+
return false;
|
|
109
180
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
181
|
+
spinner.stop();
|
|
182
|
+
if (success) p.log.success(`Connected to ${pr.name}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function configureModel() {
|
|
187
|
+
while (true) {
|
|
188
|
+
const available = await getAvailableModels();
|
|
189
|
+
if (available.length === 0) p.log.warn(NO_MODELS_MESSAGE);
|
|
190
|
+
const choice = await pickModel(available, {
|
|
191
|
+
before: available.length > 0 ? [{
|
|
192
|
+
label: "Auto",
|
|
193
|
+
value: "_auto",
|
|
194
|
+
hint: "picks best available model from connected providers"
|
|
195
|
+
}] : [],
|
|
196
|
+
after: [{
|
|
197
|
+
label: "Connect OAuth provider...",
|
|
198
|
+
value: "_connect",
|
|
199
|
+
hint: "use existing Claude Pro, ChatGPT Plus, etc."
|
|
200
|
+
}, {
|
|
201
|
+
label: "Skip enhancement",
|
|
202
|
+
value: "_skip",
|
|
203
|
+
hint: "base skill with docs, issues, and types"
|
|
204
|
+
}]
|
|
205
|
+
});
|
|
206
|
+
if (!choice) return;
|
|
207
|
+
if (choice === "_connect") {
|
|
208
|
+
await configureOAuth();
|
|
209
|
+
continue;
|
|
114
210
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
label: "Auto-detect",
|
|
120
|
-
value: ""
|
|
121
|
-
}, ...Object.entries(targets).map(([id, a]) => ({
|
|
122
|
-
label: a.displayName,
|
|
123
|
-
value: id,
|
|
124
|
-
hint: a.skillsDir
|
|
125
|
-
}))],
|
|
126
|
-
initialValue: config.agent || ""
|
|
211
|
+
if (choice === "_skip") {
|
|
212
|
+
updateConfig({
|
|
213
|
+
model: void 0,
|
|
214
|
+
skipLlm: true
|
|
127
215
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
216
|
+
p.log.success("Enhancement disabled - skills will use raw docs only");
|
|
217
|
+
} else if (choice === "_auto") {
|
|
218
|
+
updateConfig({
|
|
219
|
+
model: void 0,
|
|
220
|
+
skipLlm: false
|
|
221
|
+
});
|
|
222
|
+
p.log.success("Enhancement model will be auto-selected");
|
|
223
|
+
} else {
|
|
224
|
+
updateConfig({
|
|
225
|
+
model: choice,
|
|
226
|
+
skipLlm: false
|
|
227
|
+
});
|
|
228
|
+
p.log.success(`Enhancement model set to ${getModelName(choice)}`);
|
|
132
229
|
}
|
|
230
|
+
return;
|
|
133
231
|
}
|
|
134
232
|
}
|
|
135
233
|
const configCommandDef = defineCommand({
|
|
@@ -141,13 +239,7 @@ const configCommandDef = defineCommand({
|
|
|
141
239
|
async run() {
|
|
142
240
|
requireInteractive("config");
|
|
143
241
|
const state = await getProjectState(process.cwd());
|
|
144
|
-
|
|
145
|
-
const config = readConfig();
|
|
146
|
-
p.intro(introLine({
|
|
147
|
-
state,
|
|
148
|
-
generators,
|
|
149
|
-
modelId: config.model
|
|
150
|
-
}));
|
|
242
|
+
p.intro(introLine({ state }));
|
|
151
243
|
return configCommand();
|
|
152
244
|
}
|
|
153
245
|
});
|
|
@@ -239,7 +331,8 @@ const removeCommandDef = defineCommand({
|
|
|
239
331
|
const intro = {
|
|
240
332
|
state,
|
|
241
333
|
generators,
|
|
242
|
-
modelId: config.model
|
|
334
|
+
modelId: config.model,
|
|
335
|
+
agentId: agent || config.agent || void 0
|
|
243
336
|
};
|
|
244
337
|
p.intro(`${introLine(intro)} · remove (${scope})`);
|
|
245
338
|
return removeCommand(state, {
|
|
@@ -429,35 +522,50 @@ function hasGhCli() {
|
|
|
429
522
|
return false;
|
|
430
523
|
}
|
|
431
524
|
}
|
|
432
|
-
async function runWizard() {
|
|
433
|
-
if (!isInteractive()) return;
|
|
434
|
-
|
|
525
|
+
async function runWizard(opts = {}) {
|
|
526
|
+
if (!isInteractive()) return false;
|
|
527
|
+
const agentLabel = opts.agent ? targets[opts.agent].displayName : null;
|
|
528
|
+
const skillsDir = opts.agent ? targets[opts.agent].skillsDir : ".claude/skills";
|
|
529
|
+
const agentLine = agentLabel ? `\n\x1B[90mTarget agent: ${agentLabel}\x1B[0m` : "";
|
|
530
|
+
p.note(`Your AI agent reads docs from its training data - but APIs change,
|
|
531
|
+
versions drift, and patterns go stale. Skilld fixes this.
|
|
532
|
+
|
|
533
|
+
It generates a [1mSKILL.md[0m - a markdown reference card built from
|
|
534
|
+
the [1mactual docs, issues, and release notes[0m for the exact
|
|
535
|
+
package versions in your project. Your agent reads this file
|
|
536
|
+
every session - no hallucinated APIs.
|
|
537
|
+
|
|
538
|
+
[1mHow it works:[0m
|
|
539
|
+
1. Fetch docs, issues, and types for your packages
|
|
540
|
+
2. Optionally compress with an LLM into a concise cheat sheet
|
|
541
|
+
|
|
542
|
+
\x1B[90mExample: \`skilld add vue\` creates ${skillsDir}/vue-skilld/SKILL.md\nYour agent then knows the right APIs, gotchas, and patterns\nfor your exact version.\x1B[0m${agentLine}`, "Welcome to skilld");
|
|
435
543
|
const ghInstalled = hasGhCli();
|
|
436
544
|
if (ghInstalled) p.log.success("GitHub CLI detected — will use it to pull issues and discussions.");
|
|
437
|
-
else p.log.
|
|
545
|
+
else p.log.info("\x1B[90mGitHub CLI not installed — issues and discussions disabled.\n Install later to enable: \x1B[36mhttps://cli.github.com\x1B[0m");
|
|
438
546
|
const selected = await p.multiselect({
|
|
439
|
-
message: "
|
|
547
|
+
message: "What data sources should skills include?",
|
|
440
548
|
options: [
|
|
441
549
|
{
|
|
442
|
-
label: "
|
|
550
|
+
label: "Local search",
|
|
443
551
|
value: "search",
|
|
444
|
-
hint: "
|
|
552
|
+
hint: "query engine for `skilld search` across all skill docs"
|
|
445
553
|
},
|
|
446
554
|
{
|
|
447
555
|
label: "Release notes",
|
|
448
556
|
value: "releases",
|
|
449
|
-
hint: "
|
|
557
|
+
hint: "changelogs and migration notes per version"
|
|
450
558
|
},
|
|
451
559
|
{
|
|
452
560
|
label: "GitHub issues",
|
|
453
561
|
value: "issues",
|
|
454
|
-
hint: "
|
|
562
|
+
hint: "common bugs, workarounds, and solutions",
|
|
455
563
|
disabled: !ghInstalled
|
|
456
564
|
},
|
|
457
565
|
{
|
|
458
566
|
label: "GitHub discussions",
|
|
459
567
|
value: "discussions",
|
|
460
|
-
hint: "
|
|
568
|
+
hint: "community Q&A and usage examples",
|
|
461
569
|
disabled: !ghInstalled
|
|
462
570
|
}
|
|
463
571
|
],
|
|
@@ -466,7 +574,7 @@ async function runWizard() {
|
|
|
466
574
|
});
|
|
467
575
|
if (p.isCancel(selected)) {
|
|
468
576
|
p.cancel("Setup cancelled");
|
|
469
|
-
|
|
577
|
+
return false;
|
|
470
578
|
}
|
|
471
579
|
const features = {
|
|
472
580
|
search: selected.includes("search"),
|
|
@@ -474,43 +582,112 @@ async function runWizard() {
|
|
|
474
582
|
discussions: selected.includes("discussions"),
|
|
475
583
|
releases: selected.includes("releases")
|
|
476
584
|
};
|
|
477
|
-
|
|
585
|
+
p.note("An LLM can optionally summarize raw docs into a focused reference\nhighlighting best practices, gotchas, and migrations.\n\n\x1B[1mWithout LLM:\x1B[0m ~2 KB skill with package metadata, types, and links\n\x1B[1mWith LLM:\x1B[0m ~5 KB skill with curated gotchas, patterns, and migration notes\n\n\x1B[1mThis is a one-time build step\x1B[0m - it generates the SKILL.md, then your\ncoding agent reads the result every session. Can be a different model.\n\n\x1B[90mWorks with API keys, existing subscriptions (Claude Pro, ChatGPT Plus,\nCopilot, Gemini) via OAuth, or CLI tools (claude, gemini, codex).\x1B[0m", "Enhancement model (optional)");
|
|
478
586
|
let modelId;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
hint
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
p.cancel("Setup cancelled");
|
|
495
|
-
process.exit(0);
|
|
587
|
+
let skippedEnhancement = false;
|
|
588
|
+
let oauthJustConnected = false;
|
|
589
|
+
while (true) {
|
|
590
|
+
const allModels = process.env.SKILLD_NO_AGENTS ? [] : await getAvailableModels();
|
|
591
|
+
if (allModels.length === 0) p.log.warn(NO_MODELS_MESSAGE);
|
|
592
|
+
else if (oauthJustConnected) p.log.step(`${allModels.length} models now available. Select one below.`);
|
|
593
|
+
else {
|
|
594
|
+
const providers = /* @__PURE__ */ new Set();
|
|
595
|
+
for (const m of allModels) {
|
|
596
|
+
const vendor = m.vendorGroup ?? m.providerName;
|
|
597
|
+
if (!m.id.startsWith("pi:")) providers.add(`${vendor} via CLI`);
|
|
598
|
+
else if (m.hint?.includes("API key")) providers.add(`${vendor} via API key`);
|
|
599
|
+
else if (m.hint?.includes("OAuth")) providers.add(`${vendor} via OAuth`);
|
|
600
|
+
}
|
|
601
|
+
if (providers.size > 0) p.log.success(`Found: ${[...providers].join(", ")}`);
|
|
496
602
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
603
|
+
const choice = await pickModel(allModels, {
|
|
604
|
+
before: allModels.length > 0 ? [{
|
|
605
|
+
label: "Auto",
|
|
606
|
+
value: "_auto",
|
|
607
|
+
hint: "picks best available model from connected providers"
|
|
608
|
+
}] : [],
|
|
609
|
+
after: [{
|
|
610
|
+
label: "Connect OAuth provider...",
|
|
611
|
+
value: "_connect",
|
|
612
|
+
hint: "use existing Claude Pro, ChatGPT Plus, etc."
|
|
613
|
+
}, {
|
|
614
|
+
label: "Skip enhancement",
|
|
615
|
+
value: "_skip",
|
|
616
|
+
hint: "base skill with docs, issues, and types - add LLM later via `skilld config`"
|
|
617
|
+
}]
|
|
503
618
|
});
|
|
504
|
-
if (
|
|
619
|
+
if (choice === null) {
|
|
505
620
|
p.cancel("Setup cancelled");
|
|
506
|
-
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
if (choice === "_connect") {
|
|
624
|
+
await wizardConnectProvider();
|
|
625
|
+
oauthJustConnected = true;
|
|
626
|
+
continue;
|
|
507
627
|
}
|
|
628
|
+
if (choice === "_skip") {
|
|
629
|
+
skippedEnhancement = true;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
if (choice === "_auto") break;
|
|
633
|
+
modelId = choice;
|
|
634
|
+
break;
|
|
508
635
|
}
|
|
509
636
|
updateConfig({
|
|
510
637
|
features,
|
|
511
|
-
...modelId ? {
|
|
638
|
+
...modelId ? {
|
|
639
|
+
model: modelId,
|
|
640
|
+
skipLlm: false
|
|
641
|
+
} : {
|
|
642
|
+
model: void 0,
|
|
643
|
+
skipLlm: skippedEnhancement
|
|
644
|
+
}
|
|
512
645
|
});
|
|
513
|
-
|
|
646
|
+
const modelSummary = modelId ? getModelName(modelId) : skippedEnhancement ? "none (raw docs)" : "auto";
|
|
647
|
+
const featureList = Object.entries(features).filter(([, v]) => v).map(([k]) => k).join(", ") || "none";
|
|
648
|
+
p.log.success(`Model: ${modelSummary} · Features: ${featureList}`);
|
|
649
|
+
if (opts.showOutro !== false) p.note("Run \x1B[36mskilld add <pkg>\x1B[0m to generate skills for specific packages\nRun \x1B[36mskilld\x1B[0m to scan your project and pick packages interactively\nRun \x1B[36mskilld config\x1B[0m to change settings later", "Setup complete");
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
async function wizardConnectProvider() {
|
|
653
|
+
p.note(OAUTH_NOTE, "How OAuth works");
|
|
654
|
+
const providers = getOAuthProviderList();
|
|
655
|
+
const provider = await p.select({
|
|
656
|
+
message: "Connect provider",
|
|
657
|
+
options: providers.map((pr) => ({
|
|
658
|
+
label: pr.name,
|
|
659
|
+
value: pr.id,
|
|
660
|
+
hint: pr.loggedIn ? "connected" : void 0
|
|
661
|
+
}))
|
|
662
|
+
});
|
|
663
|
+
if (p.isCancel(provider)) return;
|
|
664
|
+
const spinner = p.spinner();
|
|
665
|
+
spinner.start("Connecting...");
|
|
666
|
+
const success = await loginOAuthProvider(provider, {
|
|
667
|
+
onAuth: (url, instructions) => {
|
|
668
|
+
spinner.stop("Open this URL in your browser:");
|
|
669
|
+
p.log.info(` \x1B[36m${url}\x1B[0m`);
|
|
670
|
+
if (instructions) p.log.info(` \x1B[90m${instructions}\x1B[0m`);
|
|
671
|
+
spinner.start("Waiting for authentication...");
|
|
672
|
+
},
|
|
673
|
+
onPrompt: async (message, placeholder) => {
|
|
674
|
+
const value = await p.text({
|
|
675
|
+
message,
|
|
676
|
+
placeholder
|
|
677
|
+
});
|
|
678
|
+
if (p.isCancel(value)) return "";
|
|
679
|
+
return value;
|
|
680
|
+
},
|
|
681
|
+
onProgress: (msg) => p.log.step(msg)
|
|
682
|
+
}).catch((err) => {
|
|
683
|
+
spinner.stop(`Login failed: ${err.message}`);
|
|
684
|
+
return false;
|
|
685
|
+
});
|
|
686
|
+
spinner.stop();
|
|
687
|
+
if (success) {
|
|
688
|
+
const name = providers.find((pr) => pr.id === provider)?.name ?? provider;
|
|
689
|
+
p.log.success(`Connected to ${name}`);
|
|
690
|
+
}
|
|
514
691
|
}
|
|
515
692
|
//#endregion
|
|
516
693
|
//#region src/cli.ts
|
|
@@ -616,7 +793,8 @@ const SUBCOMMAND_NAMES = [
|
|
|
616
793
|
"search",
|
|
617
794
|
"cache",
|
|
618
795
|
"validate",
|
|
619
|
-
"assemble"
|
|
796
|
+
"assemble",
|
|
797
|
+
"setup"
|
|
620
798
|
];
|
|
621
799
|
runMain(defineCommand({
|
|
622
800
|
meta: {
|
|
@@ -638,7 +816,8 @@ runMain(defineCommand({
|
|
|
638
816
|
search: () => import("./_chunks/search.mjs").then((m) => m.searchCommandDef),
|
|
639
817
|
cache: () => import("./_chunks/cache2.mjs").then((m) => m.cacheCommandDef),
|
|
640
818
|
validate: () => import("./_chunks/validate.mjs").then((m) => m.validateCommandDef),
|
|
641
|
-
assemble: () => import("./_chunks/assemble.mjs").then((m) => m.assembleCommandDef)
|
|
819
|
+
assemble: () => import("./_chunks/assemble.mjs").then((m) => m.assembleCommandDef),
|
|
820
|
+
setup: () => import("./_chunks/setup.mjs").then((m) => m.setupCommandDef)
|
|
642
821
|
},
|
|
643
822
|
async run({ args }) {
|
|
644
823
|
const firstArg = process.argv[2];
|
|
@@ -648,6 +827,7 @@ runMain(defineCommand({
|
|
|
648
827
|
const state = await getProjectState(cwd);
|
|
649
828
|
const status = formatStatus(state.synced.length, state.outdated.length);
|
|
650
829
|
console.log(`skilld v${version} · ${status}`);
|
|
830
|
+
if (isRunningInsideAgent()) console.log("Interactive wizard requires a standalone terminal (detected agent session).\nUse `skilld add <pkg>` to add skills non-interactively, or run `npx skilld` in a separate terminal.");
|
|
651
831
|
return;
|
|
652
832
|
}
|
|
653
833
|
let currentAgent = resolveAgent(args.agent);
|
|
@@ -656,11 +836,14 @@ runMain(defineCommand({
|
|
|
656
836
|
if (!currentAgent) return;
|
|
657
837
|
}
|
|
658
838
|
if (currentAgent === "none") {
|
|
659
|
-
|
|
839
|
+
if (!hasCompletedWizard()) {
|
|
840
|
+
if (!await runWizard()) return;
|
|
841
|
+
}
|
|
842
|
+
p.log.info("No agent selected - skills export as portable PROMPT_*.md files.\n Run \x1B[36mskilld add <pkg>\x1B[0m to generate prompts for any package.\n Run \x1B[36mskilld config\x1B[0m to set a target agent later.");
|
|
660
843
|
return;
|
|
661
844
|
}
|
|
662
845
|
const agent = currentAgent;
|
|
663
|
-
|
|
846
|
+
let { state, selfUpdate } = await brandLoader(async () => {
|
|
664
847
|
const config = readConfig();
|
|
665
848
|
const state = await getProjectState(cwd);
|
|
666
849
|
let selfUpdate = null;
|
|
@@ -702,14 +885,19 @@ runMain(defineCommand({
|
|
|
702
885
|
p.note(`\x1B[90m${version}\x1B[0m → \x1B[1m\x1B[32m${selfUpdate.latest}\x1B[0m${released}\n\x1B[36m${cmd}\x1B[0m`, "\x1B[33mUpdate available\x1B[0m");
|
|
703
886
|
}
|
|
704
887
|
if (state.skills.length === 0) {
|
|
705
|
-
if (!hasCompletedWizard())
|
|
888
|
+
if (!hasCompletedWizard()) {
|
|
889
|
+
if (!await runWizard({
|
|
890
|
+
agent,
|
|
891
|
+
showOutro: false
|
|
892
|
+
})) return;
|
|
893
|
+
} else p.log.step("No skills installed yet - pick some packages to get started.");
|
|
706
894
|
const pkgJsonPath = join(cwd, "package.json");
|
|
707
895
|
const hasPkgJson = existsSync(pkgJsonPath);
|
|
708
896
|
const projectName = hasPkgJson ? JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name : void 0;
|
|
709
897
|
const projectLabel = projectName ? `Generating skills for \x1B[36m${projectName}\x1B[0m` : "Generating skills for current directory";
|
|
710
898
|
p.log.step(projectLabel);
|
|
711
|
-
if (!hasPkgJson) p.log.warn("No package.json found
|
|
712
|
-
p.log.info("Tip:
|
|
899
|
+
if (!hasPkgJson) p.log.warn("No package.json found - enter npm package names manually.\n For best results, run skilld inside a JS/TS project directory.");
|
|
900
|
+
p.log.info("Tip: Add skills for packages with complex APIs or frequent breaking changes - not every dependency needs one.");
|
|
713
901
|
let setupComplete = false;
|
|
714
902
|
while (!setupComplete) {
|
|
715
903
|
const source = hasPkgJson ? await p.select({
|
|
@@ -718,16 +906,21 @@ runMain(defineCommand({
|
|
|
718
906
|
{
|
|
719
907
|
label: "Scan source files",
|
|
720
908
|
value: "imports",
|
|
721
|
-
hint: "
|
|
909
|
+
hint: "find actually used imports"
|
|
722
910
|
},
|
|
723
911
|
{
|
|
724
912
|
label: "Use package.json",
|
|
725
913
|
value: "deps",
|
|
726
|
-
hint: `
|
|
914
|
+
hint: `all ${state.deps.size} dependencies`
|
|
727
915
|
},
|
|
728
916
|
{
|
|
729
917
|
label: "Enter manually",
|
|
730
918
|
value: "manual"
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
label: "Skip for now",
|
|
922
|
+
value: "skip",
|
|
923
|
+
hint: "add skills later with `skilld add <pkg>`"
|
|
731
924
|
}
|
|
732
925
|
]
|
|
733
926
|
}) : "manual";
|
|
@@ -735,6 +928,10 @@ runMain(defineCommand({
|
|
|
735
928
|
p.cancel("Setup cancelled");
|
|
736
929
|
return;
|
|
737
930
|
}
|
|
931
|
+
if (source === "skip") {
|
|
932
|
+
p.log.info("Run \x1B[36mskilld add <pkg>\x1B[0m or \x1B[36mskilld\x1B[0m anytime to add skills.");
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
738
935
|
let selected;
|
|
739
936
|
if (source === "manual") {
|
|
740
937
|
const input = await p.text({
|
|
@@ -788,6 +985,23 @@ runMain(defineCommand({
|
|
|
788
985
|
}
|
|
789
986
|
const sourceMap = new Map(usages.map((u) => [u.name, u.source]));
|
|
790
987
|
const maxLen = Math.max(...packages.map((n) => n.length));
|
|
988
|
+
const preselect = packages.filter((name) => {
|
|
989
|
+
if (sourceMap.get(name) === "preset") return true;
|
|
990
|
+
return new Set([
|
|
991
|
+
"vue",
|
|
992
|
+
"nuxt",
|
|
993
|
+
"react",
|
|
994
|
+
"next",
|
|
995
|
+
"svelte",
|
|
996
|
+
"@sveltejs/kit",
|
|
997
|
+
"astro",
|
|
998
|
+
"solid-js",
|
|
999
|
+
"angular",
|
|
1000
|
+
"typescript",
|
|
1001
|
+
"vite",
|
|
1002
|
+
"vitest"
|
|
1003
|
+
]).has(name);
|
|
1004
|
+
});
|
|
791
1005
|
const choice = await p.multiselect({
|
|
792
1006
|
message: `Select packages (${packages.length} found)`,
|
|
793
1007
|
options: packages.map((name) => {
|
|
@@ -805,7 +1019,7 @@ runMain(defineCommand({
|
|
|
805
1019
|
value: name
|
|
806
1020
|
};
|
|
807
1021
|
}),
|
|
808
|
-
initialValues:
|
|
1022
|
+
initialValues: preselect
|
|
809
1023
|
});
|
|
810
1024
|
if (p.isCancel(choice)) continue;
|
|
811
1025
|
if (choice.length === 0) {
|
|
@@ -814,191 +1028,268 @@ runMain(defineCommand({
|
|
|
814
1028
|
}
|
|
815
1029
|
selected = choice;
|
|
816
1030
|
}
|
|
1031
|
+
const wizardConfig = readConfig();
|
|
817
1032
|
const { syncCommand } = await import("./_chunks/sync.mjs");
|
|
818
1033
|
await syncCommand(state, {
|
|
819
1034
|
packages: selected,
|
|
820
1035
|
global: false,
|
|
821
1036
|
agent,
|
|
822
|
-
|
|
1037
|
+
model: wizardConfig.model,
|
|
1038
|
+
yes: !wizardConfig.skipLlm
|
|
823
1039
|
});
|
|
824
1040
|
setupComplete = true;
|
|
825
1041
|
}
|
|
1042
|
+
const previewSkill = (await getProjectState(cwd)).skills[0];
|
|
1043
|
+
if (previewSkill) {
|
|
1044
|
+
const previewPath = join(cwd, targets[agent].skillsDir, previewSkill.name, "SKILL.md");
|
|
1045
|
+
if (existsSync(previewPath)) {
|
|
1046
|
+
const previewContent = readFileSync(previewPath, "utf-8");
|
|
1047
|
+
const previewLines = previewContent.split("\n").slice(0, 20).join("\n").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1B\].*?(?:\x07|\x1B\\)/g, "");
|
|
1048
|
+
const fileSize = (Buffer.byteLength(previewContent) / 1024).toFixed(1);
|
|
1049
|
+
p.note(`\x1B[90m${previewLines}\n...\x1B[0m`, `${targets[agent].skillsDir}/${previewSkill.name}/SKILL.md (${fileSize} KB)`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const agentName = targets[agent].displayName;
|
|
1053
|
+
const verifyLine = detectInstalledAgents().includes(agent) ? {
|
|
1054
|
+
"claude-code": "Start a new Claude Code session - skills load automatically.\nOr type /skill-name to invoke a specific skill.",
|
|
1055
|
+
"cursor": "Restart Cursor to pick up new skills.\nSkills appear in Settings > Cursor Rules.",
|
|
1056
|
+
"github-copilot": "Restart your editor to pick up new skills.\nCopilot discovers skills from .github/skills/ at startup.",
|
|
1057
|
+
"gemini-cli": "Start a new Gemini CLI session.\nVerify with /skills list.",
|
|
1058
|
+
"codex": "Start a new Codex session.\nSkills in .agents/skills/ are discovered at startup.",
|
|
1059
|
+
"windsurf": "Restart Windsurf to pick up new skills.\nSkills auto-invoke when their description matches your prompt.",
|
|
1060
|
+
"cline": "Restart your editor. Cline reads skill descriptions at startup.\nFull content loads on-demand when the agent invokes use_skill.",
|
|
1061
|
+
"goose": "Start a new Goose session.\nSkills are discovered automatically at startup.",
|
|
1062
|
+
"amp": "Start a new Amp session.\nReads skill descriptions at startup, full content on invocation.",
|
|
1063
|
+
"opencode": "Start a new OpenCode session.\nSkills are discovered automatically at startup.",
|
|
1064
|
+
"roo": "Restart your editor. Roo reads skill descriptions at startup."
|
|
1065
|
+
}[agent] ?? "" : `Skills are ready in ${targets[agent].skillsDir}/.\n\x1B[90m${agentName} was not detected on this machine.\nInstall it to use these skills, or run \`skilld config\` to change agents.\x1B[0m`;
|
|
1066
|
+
const firstPkg = previewSkill?.info?.packageName || previewSkill?.name;
|
|
1067
|
+
const trySuggestion = firstPkg ? `\n\n\x1B[36mTry it:\x1B[0m ask your agent "What are the gotchas or breaking changes in ${firstPkg}?"` : "";
|
|
1068
|
+
p.note(`${verifyLine}${trySuggestion}\n\nRun \x1B[36mskilld info\x1B[0m to see installed skills.\nRun \x1B[36mskilld\x1B[0m again to add more, update, or search.`, `${agentName} - next steps`);
|
|
1069
|
+
if (hasPkgJson) {
|
|
1070
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
1071
|
+
if (!pkgJson.scripts?.prepare?.includes("skilld")) {
|
|
1072
|
+
const prepareCmd = pkgJson.scripts?.prepare ? `${pkgJson.scripts.prepare} && skilld update -b` : "skilld update -b";
|
|
1073
|
+
p.log.info(`\x1B[90mKeep skills fresh by adding to package.json scripts:\n \x1B[36m"prepare": "${prepareCmd}"\x1B[0m\n \x1B[90mRefreshes docs on install. Run \`skilld update\` to regenerate LLM enhancements.\n Commit skilld-lock.yaml so teammates can run \`skilld install\`.\x1B[0m`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
826
1076
|
return;
|
|
827
1077
|
}
|
|
828
1078
|
const status = formatStatus(state.synced.length, state.outdated.length);
|
|
829
1079
|
p.log.info(status);
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1080
|
+
const refreshState = async () => {
|
|
1081
|
+
state = await getProjectState(cwd);
|
|
1082
|
+
};
|
|
1083
|
+
await menuLoop({
|
|
1084
|
+
message: "What would you like to do?",
|
|
1085
|
+
options: () => {
|
|
1086
|
+
const opts = [];
|
|
1087
|
+
opts.push({
|
|
1088
|
+
label: "Add new skills",
|
|
1089
|
+
value: "install"
|
|
1090
|
+
});
|
|
1091
|
+
if (state.outdated.length > 0) opts.push({
|
|
1092
|
+
label: "Update skills",
|
|
1093
|
+
value: "update",
|
|
1094
|
+
hint: `\x1B[33m${state.outdated.length} outdated\x1B[0m`
|
|
1095
|
+
});
|
|
1096
|
+
opts.push({
|
|
1097
|
+
label: "Remove skills",
|
|
1098
|
+
value: "remove"
|
|
1099
|
+
}, {
|
|
1100
|
+
label: "Search docs",
|
|
1101
|
+
value: "search"
|
|
1102
|
+
}, {
|
|
1103
|
+
label: "Info",
|
|
1104
|
+
value: "info"
|
|
1105
|
+
}, {
|
|
1106
|
+
label: "Configure",
|
|
1107
|
+
value: "config"
|
|
1108
|
+
});
|
|
1109
|
+
return opts;
|
|
1110
|
+
},
|
|
1111
|
+
onSelect: async (action) => {
|
|
1112
|
+
switch (action) {
|
|
1113
|
+
case "install": {
|
|
1114
|
+
const installedNames = new Set([...state.synced.map((s) => s.packageName), ...state.outdated.map((s) => s.packageName)].filter(Boolean));
|
|
1115
|
+
const uninstalledDeps = [...state.deps.keys()].filter((d) => !installedNames.has(d));
|
|
1116
|
+
const allDepsInstalled = uninstalledDeps.length === 0;
|
|
1117
|
+
const source = existsSync(join(cwd, "package.json")) ? guard(await p.select({
|
|
1118
|
+
message: "How should I find packages?",
|
|
1119
|
+
options: [
|
|
1120
|
+
{
|
|
1121
|
+
label: "Scan source files",
|
|
1122
|
+
value: "imports",
|
|
1123
|
+
hint: allDepsInstalled ? "all installed" : "find actually used imports",
|
|
1124
|
+
disabled: allDepsInstalled
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
label: "Use package.json",
|
|
1128
|
+
value: "deps",
|
|
1129
|
+
hint: allDepsInstalled ? "all installed" : `${uninstalledDeps.length} uninstalled`,
|
|
1130
|
+
disabled: allDepsInstalled
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
label: "Enter manually",
|
|
1134
|
+
value: "manual"
|
|
1135
|
+
}
|
|
1136
|
+
]
|
|
1137
|
+
})) : "manual";
|
|
1138
|
+
let selected;
|
|
1139
|
+
if (source === "manual") {
|
|
1140
|
+
const input = guard(await p.text({
|
|
1141
|
+
message: "Enter package names (space or comma-separated)",
|
|
1142
|
+
placeholder: "vue nuxt pinia"
|
|
1143
|
+
}));
|
|
1144
|
+
if (!input) return;
|
|
1145
|
+
selected = input.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
1146
|
+
if (selected.length === 0) return;
|
|
1147
|
+
} else {
|
|
1148
|
+
let usages;
|
|
1149
|
+
if (source === "imports") {
|
|
1150
|
+
const spinner = timedSpinner();
|
|
1151
|
+
spinner.start("Scanning imports...");
|
|
1152
|
+
const result = await detectImportedPackages(cwd);
|
|
1153
|
+
if (result.packages.length === 0) {
|
|
1154
|
+
spinner.stop("No imports found, falling back to package.json");
|
|
1155
|
+
usages = uninstalledDeps.map((name) => ({
|
|
1156
|
+
name,
|
|
1157
|
+
count: 0
|
|
1158
|
+
}));
|
|
1159
|
+
} else {
|
|
1160
|
+
const depSet = new Set(state.deps.keys());
|
|
1161
|
+
const matched = result.packages.filter((pkg) => depSet.has(pkg.name) || pkg.source === "preset");
|
|
1162
|
+
const alreadyInstalled = matched.filter((pkg) => installedNames.has(pkg.name));
|
|
1163
|
+
usages = matched.filter((pkg) => !installedNames.has(pkg.name));
|
|
1164
|
+
if (usages.length === 0) {
|
|
1165
|
+
spinner.stop("All detected imports already have skills");
|
|
1166
|
+
return;
|
|
1167
|
+
} else {
|
|
1168
|
+
spinner.stop(`Found ${matched.length} imported packages`);
|
|
1169
|
+
if (alreadyInstalled.length > 0) p.log.info(`${alreadyInstalled.length} already have skills installed`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
} else usages = uninstalledDeps.map((name) => ({
|
|
1173
|
+
name,
|
|
1174
|
+
count: 0
|
|
1175
|
+
}));
|
|
1176
|
+
const packages = usages.map((u) => u.name);
|
|
1177
|
+
if (packages.length === 0) {
|
|
1178
|
+
p.log.warn("No packages found");
|
|
1179
|
+
return;
|
|
885
1180
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1181
|
+
const usageMap = new Map(usages.map((u) => [u.name, u]));
|
|
1182
|
+
const sourceMap = new Map(usages.map((u) => [u.name, u.source]));
|
|
1183
|
+
const frameworks = new Set([
|
|
1184
|
+
"vue",
|
|
1185
|
+
"nuxt",
|
|
1186
|
+
"react",
|
|
1187
|
+
"next",
|
|
1188
|
+
"svelte",
|
|
1189
|
+
"@sveltejs/kit",
|
|
1190
|
+
"astro",
|
|
1191
|
+
"solid-js",
|
|
1192
|
+
"angular",
|
|
1193
|
+
"typescript",
|
|
1194
|
+
"vite",
|
|
1195
|
+
"vitest"
|
|
1196
|
+
]);
|
|
1197
|
+
const maxLen = Math.max(...packages.map((n) => n.length));
|
|
1198
|
+
const choice = guard(await p.multiselect({
|
|
1199
|
+
message: `Select packages your agent struggles with or that are new to you (${packages.length} found)`,
|
|
1200
|
+
options: packages.map((name) => {
|
|
1201
|
+
const ver = state.deps.get(name)?.replace(/^[\^~>=<]/, "") || "";
|
|
1202
|
+
const repo = getRepoHint(name, cwd);
|
|
1203
|
+
const hint = sourceMap.get(name) === "preset" ? "nuxt module" : frameworks.has(name) ? "framework" : (usageMap.get(name)?.count ?? 0) >= 5 ? `${usageMap.get(name).count} imports` : void 0;
|
|
1204
|
+
const pad = " ".repeat(maxLen - name.length + 2);
|
|
1205
|
+
const meta = [
|
|
1206
|
+
ver,
|
|
1207
|
+
hint,
|
|
1208
|
+
repo
|
|
1209
|
+
].filter(Boolean).join(" ");
|
|
1210
|
+
return {
|
|
1211
|
+
label: meta ? `${name}${pad}\x1B[90m${meta}\x1B[39m` : name,
|
|
1212
|
+
value: name
|
|
1213
|
+
};
|
|
1214
|
+
}),
|
|
1215
|
+
initialValues: []
|
|
1216
|
+
}));
|
|
1217
|
+
if (choice.length === 0) return;
|
|
1218
|
+
selected = choice;
|
|
1219
|
+
}
|
|
1220
|
+
const { syncCommand: sync } = await import("./_chunks/sync.mjs");
|
|
1221
|
+
await sync(state, {
|
|
1222
|
+
packages: selected,
|
|
1223
|
+
global: false,
|
|
1224
|
+
agent,
|
|
1225
|
+
yes: false
|
|
894
1226
|
});
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
spinner.start("Scanning imports...");
|
|
903
|
-
const result = await detectImportedPackages(cwd);
|
|
904
|
-
if (result.packages.length === 0) {
|
|
905
|
-
spinner.stop("No imports found, falling back to package.json");
|
|
906
|
-
usages = uninstalledDeps.map((name) => ({
|
|
907
|
-
name,
|
|
908
|
-
count: 0
|
|
909
|
-
}));
|
|
910
|
-
} else {
|
|
911
|
-
const depSet = new Set(state.deps.keys());
|
|
912
|
-
usages = result.packages.filter((pkg) => depSet.has(pkg.name) || pkg.source === "preset").filter((pkg) => !installedNames.has(pkg.name));
|
|
913
|
-
if (usages.length === 0) {
|
|
914
|
-
spinner.stop("All detected imports already have skills");
|
|
915
|
-
continue;
|
|
916
|
-
} else spinner.stop(`Found ${usages.length} imported packages`);
|
|
917
|
-
}
|
|
918
|
-
} else usages = uninstalledDeps.map((name) => ({
|
|
919
|
-
name,
|
|
920
|
-
count: 0
|
|
921
|
-
}));
|
|
922
|
-
const packages = usages.map((u) => u.name);
|
|
923
|
-
if (packages.length === 0) {
|
|
924
|
-
p.log.warn("No packages found");
|
|
925
|
-
continue;
|
|
1227
|
+
await refreshState();
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1230
|
+
case "update": {
|
|
1231
|
+
if (state.outdated.length === 0) {
|
|
1232
|
+
p.log.success("All skills up to date");
|
|
1233
|
+
return true;
|
|
926
1234
|
}
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
};
|
|
945
|
-
}),
|
|
946
|
-
initialValues: packages
|
|
1235
|
+
const selected = guard(await p.multiselect({
|
|
1236
|
+
message: "Select packages to update",
|
|
1237
|
+
options: state.outdated.map((s) => ({
|
|
1238
|
+
label: s.name,
|
|
1239
|
+
value: s.packageName || s.name,
|
|
1240
|
+
hint: `${s.info?.version ?? "unknown"} → ${s.latestVersion}`
|
|
1241
|
+
})),
|
|
1242
|
+
initialValues: state.outdated.map((s) => s.packageName || s.name)
|
|
1243
|
+
}));
|
|
1244
|
+
if (selected.length === 0) return;
|
|
1245
|
+
const { syncCommand: syncUpdate } = await import("./_chunks/sync.mjs");
|
|
1246
|
+
await syncUpdate(state, {
|
|
1247
|
+
packages: selected,
|
|
1248
|
+
global: false,
|
|
1249
|
+
agent,
|
|
1250
|
+
yes: false,
|
|
1251
|
+
mode: "update"
|
|
947
1252
|
});
|
|
948
|
-
|
|
949
|
-
|
|
1253
|
+
await refreshState();
|
|
1254
|
+
return true;
|
|
950
1255
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1256
|
+
case "remove": {
|
|
1257
|
+
const globalSkills = [...iterateSkills({ scope: "global" })];
|
|
1258
|
+
let removeGlobal = false;
|
|
1259
|
+
if (globalSkills.length > 0) removeGlobal = guard(await p.select({
|
|
1260
|
+
message: "Which skills?",
|
|
1261
|
+
options: [{
|
|
1262
|
+
label: "Project skills",
|
|
1263
|
+
value: "local"
|
|
1264
|
+
}, {
|
|
1265
|
+
label: "Global skills",
|
|
1266
|
+
value: "global",
|
|
1267
|
+
hint: `${globalSkills.length} installed`
|
|
1268
|
+
}]
|
|
1269
|
+
})) === "global";
|
|
1270
|
+
await removeCommand(state, {
|
|
1271
|
+
global: removeGlobal,
|
|
1272
|
+
agent,
|
|
1273
|
+
yes: false
|
|
1274
|
+
});
|
|
1275
|
+
await refreshState();
|
|
1276
|
+
break;
|
|
963
1277
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
packages: selected,
|
|
977
|
-
global: false,
|
|
978
|
-
agent,
|
|
979
|
-
yes: false
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
case "remove":
|
|
983
|
-
await removeCommand(state, {
|
|
984
|
-
global: false,
|
|
985
|
-
agent,
|
|
986
|
-
yes: false
|
|
987
|
-
});
|
|
988
|
-
continue;
|
|
989
|
-
case "search": {
|
|
990
|
-
const { interactiveSearch } = await import("./_chunks/search-interactive.mjs");
|
|
991
|
-
await interactiveSearch();
|
|
992
|
-
continue;
|
|
1278
|
+
case "search": {
|
|
1279
|
+
const { interactiveSearch } = await import("./_chunks/search-interactive.mjs");
|
|
1280
|
+
await interactiveSearch();
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
case "info":
|
|
1284
|
+
await statusCommand({ global: false });
|
|
1285
|
+
break;
|
|
1286
|
+
case "config":
|
|
1287
|
+
await configCommand();
|
|
1288
|
+
await refreshState();
|
|
1289
|
+
break;
|
|
993
1290
|
}
|
|
994
|
-
case "info":
|
|
995
|
-
await statusCommand({ global: false });
|
|
996
|
-
continue;
|
|
997
|
-
case "config":
|
|
998
|
-
await configCommand();
|
|
999
|
-
continue;
|
|
1000
1291
|
}
|
|
1001
|
-
}
|
|
1292
|
+
});
|
|
1002
1293
|
}
|
|
1003
1294
|
}));
|
|
1004
1295
|
//#endregion
|