skilld 1.2.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +21 -20
  2. package/dist/_chunks/agent.mjs +471 -17
  3. package/dist/_chunks/agent.mjs.map +1 -1
  4. package/dist/_chunks/assemble.mjs +2 -2
  5. package/dist/_chunks/assemble.mjs.map +1 -1
  6. package/dist/_chunks/cache.mjs +8 -2
  7. package/dist/_chunks/cache.mjs.map +1 -1
  8. package/dist/_chunks/cache2.mjs +2 -2
  9. package/dist/_chunks/cache2.mjs.map +1 -1
  10. package/dist/_chunks/cli-helpers.mjs +421 -0
  11. package/dist/_chunks/cli-helpers.mjs.map +1 -0
  12. package/dist/_chunks/detect.mjs +51 -22
  13. package/dist/_chunks/detect.mjs.map +1 -1
  14. package/dist/_chunks/detect2.mjs +2 -0
  15. package/dist/_chunks/embedding-cache.mjs +13 -4
  16. package/dist/_chunks/embedding-cache.mjs.map +1 -1
  17. package/dist/_chunks/formatting.mjs +1 -286
  18. package/dist/_chunks/formatting.mjs.map +1 -1
  19. package/dist/_chunks/index.d.mts.map +1 -1
  20. package/dist/_chunks/install.mjs +4 -3
  21. package/dist/_chunks/install.mjs.map +1 -1
  22. package/dist/_chunks/list.mjs +44 -5
  23. package/dist/_chunks/list.mjs.map +1 -1
  24. package/dist/_chunks/pool.mjs +3 -2
  25. package/dist/_chunks/pool.mjs.map +1 -1
  26. package/dist/_chunks/prompts.mjs +38 -4
  27. package/dist/_chunks/prompts.mjs.map +1 -1
  28. package/dist/_chunks/search-interactive.mjs +3 -2
  29. package/dist/_chunks/search-interactive.mjs.map +1 -1
  30. package/dist/_chunks/search.mjs +4 -3
  31. package/dist/_chunks/search.mjs.map +1 -1
  32. package/dist/_chunks/setup.mjs +27 -0
  33. package/dist/_chunks/setup.mjs.map +1 -0
  34. package/dist/_chunks/shared.mjs +6 -2
  35. package/dist/_chunks/shared.mjs.map +1 -1
  36. package/dist/_chunks/skills.mjs +1 -1
  37. package/dist/_chunks/sources.mjs +1 -1
  38. package/dist/_chunks/sync.mjs +389 -108
  39. package/dist/_chunks/sync.mjs.map +1 -1
  40. package/dist/_chunks/uninstall.mjs +16 -2
  41. package/dist/_chunks/uninstall.mjs.map +1 -1
  42. package/dist/agent/index.d.mts +22 -4
  43. package/dist/agent/index.d.mts.map +1 -1
  44. package/dist/agent/index.mjs +3 -3
  45. package/dist/cli.mjs +619 -328
  46. package/dist/cli.mjs.map +1 -1
  47. package/dist/retriv/index.d.mts +18 -3
  48. package/dist/retriv/index.d.mts.map +1 -1
  49. package/dist/retriv/index.mjs +30 -1
  50. package/dist/retriv/index.mjs.map +1 -1
  51. package/dist/retriv/worker.d.mts +2 -0
  52. package/dist/retriv/worker.d.mts.map +1 -1
  53. package/dist/retriv/worker.mjs +1 -0
  54. package/dist/retriv/worker.mjs.map +1 -1
  55. package/dist/sources/index.mjs +1 -1
  56. package/package.json +6 -5
  57. 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 { a as semverGt, n as getSharedSkillsDir, r as mapInsert } from "./_chunks/shared.mjs";
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 getAvailableModels, t as detectImportedPackages } from "./_chunks/agent.mjs";
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 config = readConfig();
23
- const features = config.features ?? defaultFeatures;
24
- const enabledCount = Object.values(features).filter(Boolean).length;
25
- const action = await p.select({
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
- label: "Change features",
30
- value: "features",
31
- hint: `${enabledCount}/4 enabled`
32
- },
33
- {
34
- label: "Change model",
35
- value: "model",
36
- hint: config.model || "auto"
37
- },
38
- {
39
- label: "Change agent",
40
- value: "agent",
41
- hint: config.agent || "auto-detect"
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
- case "model": {
93
- const available = await getAvailableModels();
94
- if (available.length === 0) {
95
- p.log.warn("No LLM CLIs found");
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 model = await p.select({
99
- message: "Select default model",
100
- options: [{
101
- label: "Auto (prompt each time)",
102
- value: ""
103
- }, ...available.map((m) => ({
104
- label: m.recommended ? `${m.name} (Recommended)` : m.name,
105
- value: m.id,
106
- hint: m.hint
107
- }))],
108
- initialValue: config.model || ""
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
- if (p.isCancel(model)) return;
111
- updateConfig({ model: model || void 0 });
112
- p.log.success(model ? `Default model set to ${model}` : "Model will be prompted each time");
113
- break;
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
- case "agent": {
116
- const agentChoice = await p.select({
117
- message: "Select default agent",
118
- options: [{
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
- if (p.isCancel(agentChoice)) return;
129
- updateConfig({ agent: agentChoice || void 0 });
130
- p.log.success(agentChoice ? `Default agent set to ${agentChoice}` : "Agent will be auto-detected");
131
- break;
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
- const generators = getInstalledGenerators();
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
- p.note("Skilld gives your AI agent skill knowledge on your NPM\ndependencies gathered from versioned docs, source code\nand GitHub issues.", "Welcome to skilld");
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 SKILL.md - a markdown reference card built from
534
+ the actual docs, issues, and release notes for the exact
535
+ package versions in your project. Your agent reads this file
536
+ every session - no hallucinated APIs.
537
+
538
+ How it works:
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.warn("GitHub CLI not found. Install it to enable issues/discussions:\n \x1B[36mhttps://cli.github.com\x1B[0m");
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: "Which features would you like to enable?",
547
+ message: "What data sources should skills include?",
440
548
  options: [
441
549
  {
442
- label: "Semantic + token search",
550
+ label: "Local search",
443
551
  value: "search",
444
- hint: "local query engine to cut token costs and speed up grep"
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: "track changelogs for installed packages"
557
+ hint: "changelogs and migration notes per version"
450
558
  },
451
559
  {
452
560
  label: "GitHub issues",
453
561
  value: "issues",
454
- hint: "surface common problems and solutions",
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: "include Q&A and community knowledge",
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
- process.exit(0);
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
- const allModels = process.env.SKILLD_NO_AGENTS ? [] : await getAvailableModels();
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
- if (allModels.length > 0) {
480
- p.note("Skills work without an LLM, but one can rewrite your\nSKILL.md files with best practices and better structure.\n\x1B[90mThis is separate from the agent where skills are installed —\nthe target agent is auto-detected from your project files.\x1B[0m", "Optional: LLM optimization");
481
- const modelChoice = await p.select({
482
- message: "Model for generating SKILL.md",
483
- options: [{
484
- label: "Skip",
485
- value: "",
486
- hint: "use raw docs, no LLM needed"
487
- }, ...allModels.map((m) => ({
488
- label: m.recommended ? `${m.name} (Recommended)` : m.name,
489
- value: m.id,
490
- hint: `${m.agentName} · ${m.hint}`
491
- }))]
492
- });
493
- if (p.isCancel(modelChoice)) {
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
- modelId = modelChoice || void 0;
498
- } else {
499
- p.log.warn("No supported LLM CLIs detected (claude, gemini, codex).\n Skills will still work, but won't be LLM-optimized.");
500
- const proceed = await p.confirm({
501
- message: "Continue without LLM optimization?",
502
- initialValue: true
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 (p.isCancel(proceed) || !proceed) {
619
+ if (choice === null) {
505
620
  p.cancel("Setup cancelled");
506
- process.exit(0);
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 ? { model: modelId } : { skipLlm: true }
638
+ ...modelId ? {
639
+ model: modelId,
640
+ skipLlm: false
641
+ } : {
642
+ model: void 0,
643
+ skipLlm: skippedEnhancement
644
+ }
512
645
  });
513
- p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
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
- p.log.info("No agent selected. Use `skilld add <pkg>` to export portable prompts.");
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
- const { state, selfUpdate } = await brandLoader(async () => {
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()) await runWizard();
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 enter package names manually or run inside a project");
712
- p.log.info("Tip: Only generate skills for packages your agent struggles with.\n The fewer skills, the more context you have for everything else :)");
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: "Find actually used imports"
909
+ hint: "find actually used imports"
722
910
  },
723
911
  {
724
912
  label: "Use package.json",
725
913
  value: "deps",
726
- hint: `All ${state.deps.size} dependencies`
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: packages
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
- yes: false
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
- while (true) {
831
- const options = [];
832
- options.push({
833
- label: "Add new skills",
834
- value: "install"
835
- });
836
- if (state.outdated.length > 0) options.push({
837
- label: "Update skills",
838
- value: "update",
839
- hint: `\x1B[33m${state.outdated.length} outdated\x1B[0m`
840
- });
841
- options.push({
842
- label: "Remove skills",
843
- value: "remove"
844
- }, {
845
- label: "Search docs",
846
- value: "search"
847
- }, {
848
- label: "Info",
849
- value: "info"
850
- }, {
851
- label: "Configure",
852
- value: "config"
853
- });
854
- const action = await p.select({
855
- message: "What would you like to do?",
856
- options
857
- });
858
- if (p.isCancel(action)) {
859
- p.cancel("Cancelled");
860
- return;
861
- }
862
- switch (action) {
863
- case "install": {
864
- const installedNames = new Set(state.skills.map((s) => s.packageName || s.name));
865
- const uninstalledDeps = [...state.deps.keys()].filter((d) => !installedNames.has(d));
866
- const allDepsInstalled = uninstalledDeps.length === 0;
867
- const source = existsSync(join(cwd, "package.json")) ? await p.select({
868
- message: "How should I find packages?",
869
- options: [
870
- {
871
- label: "Scan source files",
872
- value: "imports",
873
- hint: allDepsInstalled ? "all installed" : "find actually used imports",
874
- disabled: allDepsInstalled
875
- },
876
- {
877
- label: "Use package.json",
878
- value: "deps",
879
- hint: allDepsInstalled ? "all installed" : `${uninstalledDeps.length} uninstalled`,
880
- disabled: allDepsInstalled
881
- },
882
- {
883
- label: "Enter manually",
884
- value: "manual"
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
- }) : "manual";
888
- if (p.isCancel(source)) continue;
889
- let selected;
890
- if (source === "manual") {
891
- const input = await p.text({
892
- message: "Enter package names (space or comma-separated)",
893
- placeholder: "vue nuxt pinia"
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
- if (p.isCancel(input) || !input) continue;
896
- selected = input.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
897
- if (selected.length === 0) continue;
898
- } else {
899
- let usages;
900
- if (source === "imports") {
901
- const spinner = timedSpinner();
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 sourceMap = new Map(usages.map((u) => [u.name, u.source]));
928
- const maxLen = Math.max(...packages.map((n) => n.length));
929
- const choice = await p.multiselect({
930
- message: `Select packages (${packages.length} found)`,
931
- options: packages.map((name) => {
932
- const ver = state.deps.get(name)?.replace(/^[\^~>=<]/, "") || "";
933
- const repo = getRepoHint(name, cwd);
934
- const hint = sourceMap.get(name) === "preset" ? "nuxt module" : void 0;
935
- const pad = " ".repeat(maxLen - name.length + 2);
936
- const meta = [
937
- ver,
938
- hint,
939
- repo
940
- ].filter(Boolean).join(" ");
941
- return {
942
- label: meta ? `${name}${pad}\x1B[90m${meta}\x1B[39m` : name,
943
- value: name
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
- if (p.isCancel(choice) || choice.length === 0) continue;
949
- selected = choice;
1253
+ await refreshState();
1254
+ return true;
950
1255
  }
951
- const { syncCommand: sync } = await import("./_chunks/sync.mjs");
952
- return sync(state, {
953
- packages: selected,
954
- global: false,
955
- agent,
956
- yes: false
957
- });
958
- }
959
- case "update": {
960
- if (state.outdated.length === 0) {
961
- p.log.success("All skills up to date");
962
- return;
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
- const selected = await p.multiselect({
965
- message: "Select packages to update",
966
- options: state.outdated.map((s) => ({
967
- label: s.name,
968
- value: s.packageName || s.name,
969
- hint: `${s.info?.version ?? "unknown"} → ${s.latestVersion}`
970
- })),
971
- initialValues: state.outdated.map((s) => s.packageName || s.name)
972
- });
973
- if (p.isCancel(selected) || selected.length === 0) continue;
974
- const { syncCommand: syncUpdate } = await import("./_chunks/sync.mjs");
975
- return syncUpdate(state, {
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