skilld 1.2.2 → 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/index2.d.mts.map +1 -1
- package/dist/_chunks/install.mjs +6 -4
- 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/sanitize.mjs +7 -0
- package/dist/_chunks/sanitize.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 +8 -8
- package/dist/_chunks/sources.mjs.map +1 -1
- package/dist/_chunks/sync.mjs +390 -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 +9 -8
- package/dist/_chunks/chunk.mjs +0 -15
package/dist/_chunks/cache2.mjs
CHANGED
|
@@ -54,7 +54,7 @@ async function cacheCleanCommand() {
|
|
|
54
54
|
const freedKB = Math.round(freedBytes / 1024);
|
|
55
55
|
if (expiredLlm > 0 || embeddingCleared) {
|
|
56
56
|
const parts = [];
|
|
57
|
-
if (expiredLlm > 0) parts.push(`${expiredLlm} expired
|
|
57
|
+
if (expiredLlm > 0) parts.push(`${expiredLlm} expired enhancement cache entries`);
|
|
58
58
|
if (embeddingCleared) parts.push("embedding cache");
|
|
59
59
|
p.log.success(`Removed ${parts.join(" + ")} (${freedKB}KB freed)`);
|
|
60
60
|
} else p.log.info("Cache is clean — no expired entries");
|
|
@@ -67,7 +67,7 @@ const cacheCommandDef = defineCommand({
|
|
|
67
67
|
},
|
|
68
68
|
args: { clean: {
|
|
69
69
|
type: "boolean",
|
|
70
|
-
description: "Remove expired
|
|
70
|
+
description: "Remove expired enhancement cache entries",
|
|
71
71
|
default: true
|
|
72
72
|
} },
|
|
73
73
|
async run() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache2.mjs","names":[],"sources":["../../src/commands/cache.ts"],"sourcesContent":["/**\n * Cache management commands\n */\n\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { CACHE_DIR } from '../cache/index.ts'\nimport { clearEmbeddingCache } from '../retriv/embedding-cache.ts'\n\nconst LLM_CACHE_DIR = join(CACHE_DIR, 'llm-cache')\nconst LLM_CACHE_MAX_AGE = 7 * 24 * 60 * 60 * 1000\n\nfunction safeRemove(path: string): number {\n try {\n const size = statSync(path).size\n rmSync(path)\n return size\n }\n catch {\n try {\n rmSync(path)\n }\n catch {}\n return 0\n }\n}\n\nexport async function cacheCleanCommand(): Promise<void> {\n let expiredLlm = 0\n let freedBytes = 0\n\n // Clean expired LLM cache entries\n if (existsSync(LLM_CACHE_DIR)) {\n const now = Date.now()\n for (const entry of readdirSync(LLM_CACHE_DIR)) {\n const path = join(LLM_CACHE_DIR, entry)\n try {\n const { timestamp } = JSON.parse(readFileSync(path, 'utf-8'))\n if (now - timestamp > LLM_CACHE_MAX_AGE) {\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n catch {\n // Corrupt cache entry — remove it\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n }\n\n // Clear embedding cache\n const embeddingDbPath = join(CACHE_DIR, 'embeddings.db')\n let embeddingCleared = false\n if (existsSync(embeddingDbPath)) {\n const size = statSync(embeddingDbPath).size\n clearEmbeddingCache()\n freedBytes += size\n embeddingCleared = true\n }\n\n const freedKB = Math.round(freedBytes / 1024)\n if (expiredLlm > 0 || embeddingCleared) {\n const parts: string[] = []\n if (expiredLlm > 0)\n parts.push(`${expiredLlm} expired
|
|
1
|
+
{"version":3,"file":"cache2.mjs","names":[],"sources":["../../src/commands/cache.ts"],"sourcesContent":["/**\n * Cache management commands\n */\n\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { CACHE_DIR } from '../cache/index.ts'\nimport { clearEmbeddingCache } from '../retriv/embedding-cache.ts'\n\nconst LLM_CACHE_DIR = join(CACHE_DIR, 'llm-cache')\nconst LLM_CACHE_MAX_AGE = 7 * 24 * 60 * 60 * 1000\n\nfunction safeRemove(path: string): number {\n try {\n const size = statSync(path).size\n rmSync(path)\n return size\n }\n catch {\n try {\n rmSync(path)\n }\n catch {}\n return 0\n }\n}\n\nexport async function cacheCleanCommand(): Promise<void> {\n let expiredLlm = 0\n let freedBytes = 0\n\n // Clean expired LLM cache entries\n if (existsSync(LLM_CACHE_DIR)) {\n const now = Date.now()\n for (const entry of readdirSync(LLM_CACHE_DIR)) {\n const path = join(LLM_CACHE_DIR, entry)\n try {\n const { timestamp } = JSON.parse(readFileSync(path, 'utf-8'))\n if (now - timestamp > LLM_CACHE_MAX_AGE) {\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n catch {\n // Corrupt cache entry — remove it\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n }\n\n // Clear embedding cache\n const embeddingDbPath = join(CACHE_DIR, 'embeddings.db')\n let embeddingCleared = false\n if (existsSync(embeddingDbPath)) {\n const size = statSync(embeddingDbPath).size\n clearEmbeddingCache()\n freedBytes += size\n embeddingCleared = true\n }\n\n const freedKB = Math.round(freedBytes / 1024)\n if (expiredLlm > 0 || embeddingCleared) {\n const parts: string[] = []\n if (expiredLlm > 0)\n parts.push(`${expiredLlm} expired enhancement cache entries`)\n if (embeddingCleared)\n parts.push('embedding cache')\n p.log.success(`Removed ${parts.join(' + ')} (${freedKB}KB freed)`)\n }\n else {\n p.log.info('Cache is clean — no expired entries')\n }\n}\n\nexport const cacheCommandDef = defineCommand({\n meta: { name: 'cache', description: 'Cache management', hidden: true },\n args: {\n clean: {\n type: 'boolean',\n description: 'Remove expired enhancement cache entries',\n default: true,\n },\n },\n async run() {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache clean`)\n await cacheCleanCommand()\n },\n})\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,gBAAgB,KAAK,WAAW,YAAY;AAClD,MAAM,oBAAoB,QAAc,KAAK;AAE7C,SAAS,WAAW,MAAsB;AACxC,KAAI;EACF,MAAM,OAAO,SAAS,KAAK,CAAC;AAC5B,SAAO,KAAK;AACZ,SAAO;SAEH;AACJ,MAAI;AACF,UAAO,KAAK;UAER;AACN,SAAO;;;AAIX,eAAsB,oBAAmC;CACvD,IAAI,aAAa;CACjB,IAAI,aAAa;AAGjB,KAAI,WAAW,cAAc,EAAE;EAC7B,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,SAAS,YAAY,cAAc,EAAE;GAC9C,MAAM,OAAO,KAAK,eAAe,MAAM;AACvC,OAAI;IACF,MAAM,EAAE,cAAc,KAAK,MAAM,aAAa,MAAM,QAAQ,CAAC;AAC7D,QAAI,MAAM,YAAY,mBAAmB;AACvC,mBAAc,WAAW,KAAK;AAC9B;;WAGE;AAEJ,kBAAc,WAAW,KAAK;AAC9B;;;;CAMN,MAAM,kBAAkB,KAAK,WAAW,gBAAgB;CACxD,IAAI,mBAAmB;AACvB,KAAI,WAAW,gBAAgB,EAAE;EAC/B,MAAM,OAAO,SAAS,gBAAgB,CAAC;AACvC,uBAAqB;AACrB,gBAAc;AACd,qBAAmB;;CAGrB,MAAM,UAAU,KAAK,MAAM,aAAa,KAAK;AAC7C,KAAI,aAAa,KAAK,kBAAkB;EACtC,MAAM,QAAkB,EAAE;AAC1B,MAAI,aAAa,EACf,OAAM,KAAK,GAAG,WAAW,oCAAoC;AAC/D,MAAI,iBACF,OAAM,KAAK,kBAAkB;AAC/B,IAAE,IAAI,QAAQ,WAAW,MAAM,KAAK,MAAM,CAAC,IAAI,QAAQ,WAAW;OAGlE,GAAE,IAAI,KAAK,sCAAsC;;AAIrD,MAAa,kBAAkB,cAAc;CAC3C,MAAM;EAAE,MAAM;EAAS,aAAa;EAAoB,QAAQ;EAAM;CACtE,MAAM,EACJ,OAAO;EACL,MAAM;EACN,aAAa;EACb,SAAS;EACV,EACF;CACD,MAAM,MAAM;AACV,IAAE,MAAM,2CAA2C;AACnD,QAAM,mBAAmB;;CAE5B,CAAC"}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { o as getModelName } from "./agent.mjs";
|
|
2
|
+
import { n as yamlParseKV, r as yamlUnescape, t as yamlEscape } from "./yaml.mjs";
|
|
3
|
+
import { a as targets, i as getAgentVersion, n as detectProjectAgents, r as detectTargetAgent, t as detectInstalledAgents } from "./detect.mjs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "pathe";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
import { detectCurrentAgent } from "unagent/env";
|
|
10
|
+
import { dirname as dirname$1, resolve as resolve$1 } from "node:path";
|
|
11
|
+
//#region src/core/config.ts
|
|
12
|
+
const defaultFeatures = {
|
|
13
|
+
search: true,
|
|
14
|
+
issues: true,
|
|
15
|
+
discussions: true,
|
|
16
|
+
releases: true
|
|
17
|
+
};
|
|
18
|
+
const CONFIG_DIR = join(homedir(), ".skilld");
|
|
19
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.yaml");
|
|
20
|
+
function hasConfig() {
|
|
21
|
+
return existsSync(CONFIG_PATH);
|
|
22
|
+
}
|
|
23
|
+
/** Whether the first-run wizard has been completed (not just agent selection) */
|
|
24
|
+
function hasCompletedWizard() {
|
|
25
|
+
if (!existsSync(CONFIG_PATH)) return false;
|
|
26
|
+
const config = readConfig();
|
|
27
|
+
return config.features !== void 0 || config.model !== void 0 || config.skipLlm !== void 0;
|
|
28
|
+
}
|
|
29
|
+
function readConfig() {
|
|
30
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
31
|
+
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
32
|
+
const config = {};
|
|
33
|
+
let inBlock = null;
|
|
34
|
+
const projects = [];
|
|
35
|
+
const features = {};
|
|
36
|
+
for (const line of content.split("\n")) {
|
|
37
|
+
if (line.startsWith("projects:")) {
|
|
38
|
+
inBlock = "projects";
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (line.startsWith("features:")) {
|
|
42
|
+
inBlock = "features";
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (inBlock === "projects") {
|
|
46
|
+
if (line.startsWith(" - ")) {
|
|
47
|
+
projects.push(yamlUnescape(line.slice(4)));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
inBlock = null;
|
|
51
|
+
}
|
|
52
|
+
if (inBlock === "features") {
|
|
53
|
+
const m = line.match(/^ {2}(\w+):\s*(.+)/);
|
|
54
|
+
if (m) {
|
|
55
|
+
const key = m[1];
|
|
56
|
+
if (key in defaultFeatures) features[key] = m[2] === "true";
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
inBlock = null;
|
|
60
|
+
}
|
|
61
|
+
const kv = yamlParseKV(line);
|
|
62
|
+
if (!kv) continue;
|
|
63
|
+
const [key, value] = kv;
|
|
64
|
+
if (key === "model" && value) config.model = value;
|
|
65
|
+
if (key === "agent" && value) config.agent = value;
|
|
66
|
+
if (key === "skipLlm") config.skipLlm = value === "true";
|
|
67
|
+
}
|
|
68
|
+
if (projects.length > 0) config.projects = projects;
|
|
69
|
+
if (Object.keys(features).length > 0) config.features = {
|
|
70
|
+
...defaultFeatures,
|
|
71
|
+
...features
|
|
72
|
+
};
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
function writeConfig(config) {
|
|
76
|
+
mkdirSync(CONFIG_DIR, {
|
|
77
|
+
recursive: true,
|
|
78
|
+
mode: 448
|
|
79
|
+
});
|
|
80
|
+
let yaml = "";
|
|
81
|
+
if (config.model) yaml += `model: ${config.model}\n`;
|
|
82
|
+
if (config.agent) yaml += `agent: ${config.agent}\n`;
|
|
83
|
+
if (config.skipLlm) yaml += `skipLlm: true\n`;
|
|
84
|
+
if (config.features) {
|
|
85
|
+
yaml += "features:\n";
|
|
86
|
+
for (const [k, v] of Object.entries(config.features)) yaml += ` ${k}: ${v}\n`;
|
|
87
|
+
}
|
|
88
|
+
if (config.projects?.length) {
|
|
89
|
+
yaml += "projects:\n";
|
|
90
|
+
for (const p of config.projects) yaml += ` - ${yamlEscape(p)}\n`;
|
|
91
|
+
}
|
|
92
|
+
writeFileSync(CONFIG_PATH, yaml, { mode: 384 });
|
|
93
|
+
}
|
|
94
|
+
function updateConfig(updates) {
|
|
95
|
+
writeConfig({
|
|
96
|
+
...readConfig(),
|
|
97
|
+
...updates
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function registerProject(projectPath) {
|
|
101
|
+
const config = readConfig();
|
|
102
|
+
const projects = new Set(config.projects || []);
|
|
103
|
+
projects.add(projectPath);
|
|
104
|
+
writeConfig({
|
|
105
|
+
...config,
|
|
106
|
+
projects: [...projects]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function unregisterProject(projectPath) {
|
|
110
|
+
const config = readConfig();
|
|
111
|
+
const projects = (config.projects || []).filter((p) => p !== projectPath);
|
|
112
|
+
writeConfig({
|
|
113
|
+
...config,
|
|
114
|
+
projects
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function getRegisteredProjects() {
|
|
118
|
+
return readConfig().projects || [];
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/version.ts
|
|
122
|
+
function findPackageJson() {
|
|
123
|
+
let dir = dirname$1(fileURLToPath(import.meta.url));
|
|
124
|
+
for (let i = 0; i < 5; i++) {
|
|
125
|
+
const candidate = resolve$1(dir, "package.json");
|
|
126
|
+
try {
|
|
127
|
+
const content = readFileSync(candidate, "utf8");
|
|
128
|
+
if (content) return content;
|
|
129
|
+
} catch {}
|
|
130
|
+
dir = resolve$1(dir, "..");
|
|
131
|
+
}
|
|
132
|
+
return "{\"version\":\"0.0.0\"}";
|
|
133
|
+
}
|
|
134
|
+
const version = JSON.parse(findPackageJson()).version;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/cli-helpers.ts
|
|
137
|
+
const sharedArgs = {
|
|
138
|
+
global: {
|
|
139
|
+
type: "boolean",
|
|
140
|
+
alias: "g",
|
|
141
|
+
description: "Install globally to ~/<agent>/skills",
|
|
142
|
+
default: false
|
|
143
|
+
},
|
|
144
|
+
agent: {
|
|
145
|
+
type: "enum",
|
|
146
|
+
options: Object.keys(targets),
|
|
147
|
+
alias: "a",
|
|
148
|
+
description: "Target agent — where skills are installed"
|
|
149
|
+
},
|
|
150
|
+
model: {
|
|
151
|
+
type: "string",
|
|
152
|
+
alias: "m",
|
|
153
|
+
description: "Enhancement model for SKILL.md generation",
|
|
154
|
+
valueHint: "id"
|
|
155
|
+
},
|
|
156
|
+
yes: {
|
|
157
|
+
type: "boolean",
|
|
158
|
+
alias: "y",
|
|
159
|
+
description: "Skip prompts, use defaults",
|
|
160
|
+
default: false
|
|
161
|
+
},
|
|
162
|
+
force: {
|
|
163
|
+
type: "boolean",
|
|
164
|
+
alias: "f",
|
|
165
|
+
description: "Ignore all caches, re-fetch docs and regenerate",
|
|
166
|
+
default: false
|
|
167
|
+
},
|
|
168
|
+
debug: {
|
|
169
|
+
type: "boolean",
|
|
170
|
+
description: "Save raw enhancement output to logs/ for each section",
|
|
171
|
+
default: false
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/** Thrown when a clack prompt is cancelled inside a menuLoop handler */
|
|
175
|
+
var MenuCancel = class extends Error {
|
|
176
|
+
name = "MenuCancel";
|
|
177
|
+
};
|
|
178
|
+
/** Assert a clack prompt result is not cancelled. Throws MenuCancel if cancelled. */
|
|
179
|
+
function guard(value) {
|
|
180
|
+
if (p.isCancel(value)) throw new MenuCancel();
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Run a select menu in a loop with automatic back-navigation.
|
|
185
|
+
*
|
|
186
|
+
* - Cancel (Escape) at the menu itself → exits (returns)
|
|
187
|
+
* - Cancel inside a handler (via guard()) → caught, loops back to menu
|
|
188
|
+
* - Handler returns truthy → exits (returns)
|
|
189
|
+
* - Handler returns void/false → loops back to menu
|
|
190
|
+
*
|
|
191
|
+
* Options are rebuilt each iteration so hints stay fresh after changes.
|
|
192
|
+
*/
|
|
193
|
+
async function menuLoop(opts) {
|
|
194
|
+
while (true) {
|
|
195
|
+
const options = await opts.options();
|
|
196
|
+
const initial = typeof opts.initialValue === "function" ? opts.initialValue() : opts.initialValue;
|
|
197
|
+
const choice = opts.searchable ? await p.autocomplete({
|
|
198
|
+
message: opts.message,
|
|
199
|
+
options,
|
|
200
|
+
...initial != null ? { initialValue: initial } : {}
|
|
201
|
+
}) : await p.select({
|
|
202
|
+
message: opts.message,
|
|
203
|
+
options,
|
|
204
|
+
...initial != null ? { initialValue: initial } : {}
|
|
205
|
+
});
|
|
206
|
+
if (p.isCancel(choice)) return;
|
|
207
|
+
try {
|
|
208
|
+
if (await opts.onSelect(choice)) return;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (err instanceof MenuCancel) continue;
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Check if we're running inside an AI coding agent */
|
|
216
|
+
function isRunningInsideAgent() {
|
|
217
|
+
return !!detectCurrentAgent();
|
|
218
|
+
}
|
|
219
|
+
/** Check if the current environment supports interactive prompts */
|
|
220
|
+
function isInteractive() {
|
|
221
|
+
if (isRunningInsideAgent()) return false;
|
|
222
|
+
if (process.env.CI) return false;
|
|
223
|
+
if (!process.stdout.isTTY) return false;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
/** Exit with error if interactive terminal is required but unavailable */
|
|
227
|
+
function requireInteractive(command) {
|
|
228
|
+
if (!isInteractive()) {
|
|
229
|
+
console.error(`Error: \`skilld ${command}\` requires an interactive terminal`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */
|
|
234
|
+
function resolveAgent(agentFlag) {
|
|
235
|
+
if (process.env.SKILLD_NO_AGENT) return null;
|
|
236
|
+
return agentFlag ?? detectTargetAgent() ?? readConfig().agent ?? null;
|
|
237
|
+
}
|
|
238
|
+
let _warnedNoAgent = false;
|
|
239
|
+
function warnNoAgent() {
|
|
240
|
+
if (_warnedNoAgent) return;
|
|
241
|
+
_warnedNoAgent = true;
|
|
242
|
+
p.log.warn("No target agent detected — falling back to prompt-only mode.\n Use --agent <name> to specify, or run `skilld config` to set a default.");
|
|
243
|
+
}
|
|
244
|
+
/** Prompt user to pick an agent when auto-detection fails */
|
|
245
|
+
async function promptForAgent() {
|
|
246
|
+
const noAgent = !!process.env.SKILLD_NO_AGENT;
|
|
247
|
+
const installed = noAgent ? [] : detectInstalledAgents();
|
|
248
|
+
const projectMatches = noAgent ? [] : detectProjectAgents();
|
|
249
|
+
if (!isInteractive()) {
|
|
250
|
+
if (installed.length === 1) {
|
|
251
|
+
updateConfig({ agent: installed[0] });
|
|
252
|
+
return installed[0];
|
|
253
|
+
}
|
|
254
|
+
warnNoAgent();
|
|
255
|
+
return "none";
|
|
256
|
+
}
|
|
257
|
+
p.log.info("Skilld generates reference cards from package docs so your AI agent\n always has accurate APIs for your exact dependency versions.");
|
|
258
|
+
const candidateIds = projectMatches.length > 0 ? projectMatches : installed.length > 0 ? installed : Object.keys(targets);
|
|
259
|
+
const sharedAgents = new Set(Object.entries(targets).filter(([, a]) => a.additionalSkillsDirs.some((d) => d.includes(".claude/skills"))).map(([id]) => id));
|
|
260
|
+
const sharedIds = candidateIds.filter((id) => id === "claude-code" || sharedAgents.has(id));
|
|
261
|
+
const isolatedIds = candidateIds.filter((id) => id !== "claude-code" && !sharedAgents.has(id));
|
|
262
|
+
const options = [];
|
|
263
|
+
if (sharedIds.length > 0 && isolatedIds.length > 0) for (const id of sharedIds) {
|
|
264
|
+
const a = targets[id];
|
|
265
|
+
const hint = id === "claude-code" ? `skills shared with ${sharedIds.length - 1} other agents` : `skills shared with Claude Code and others`;
|
|
266
|
+
options.push({
|
|
267
|
+
label: a.displayName,
|
|
268
|
+
value: id,
|
|
269
|
+
hint
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const isolatedAgentIds = new Set(Object.entries(targets).filter(([, a]) => a.additionalSkillsDirs.length === 0).map(([id]) => id));
|
|
273
|
+
for (const id of sharedIds.length > 0 && isolatedIds.length > 0 ? isolatedIds : candidateIds) {
|
|
274
|
+
if (options.some((o) => o.value === id)) continue;
|
|
275
|
+
const a = targets[id];
|
|
276
|
+
const hint = sharedAgents.has(id) && id !== "claude-code" ? "skills shared with Claude Code and others" : isolatedAgentIds.has(id) ? "skills only visible to this agent" : void 0;
|
|
277
|
+
options.push({
|
|
278
|
+
label: a.displayName,
|
|
279
|
+
value: id,
|
|
280
|
+
hint
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
options.push({
|
|
284
|
+
label: "No agent",
|
|
285
|
+
value: "none",
|
|
286
|
+
hint: "export as standalone files for any AI"
|
|
287
|
+
});
|
|
288
|
+
if (!_warnedNoAgent) {
|
|
289
|
+
_warnedNoAgent = true;
|
|
290
|
+
const hint = projectMatches.length > 1 ? `Multiple agent directories found: ${projectMatches.map((t) => targets[t].displayName).join(", ")}` : installed.length > 0 ? `Found ${installed.map((t) => targets[t].displayName).join(", ")} but couldn't determine which to use` : "No agents auto-detected";
|
|
291
|
+
const crossNote = sharedIds.length > 1 ? `\n \x1B[90mTip: Picking Claude Code shares skills with ${sharedIds.filter((id) => id !== "claude-code").map((id) => targets[id].displayName).join(", ")} automatically.\x1B[0m` : "";
|
|
292
|
+
p.log.warn(`${hint}\n Pick the agent you actively code with.${crossNote}`);
|
|
293
|
+
}
|
|
294
|
+
const choice = await p.select({
|
|
295
|
+
message: "Which AI coding agent do you use?",
|
|
296
|
+
options
|
|
297
|
+
});
|
|
298
|
+
if (p.isCancel(choice)) return null;
|
|
299
|
+
if (choice === "none") return "none";
|
|
300
|
+
updateConfig({ agent: choice });
|
|
301
|
+
p.log.success(`Target agent set to ${targets[choice].displayName}`);
|
|
302
|
+
return choice;
|
|
303
|
+
}
|
|
304
|
+
/** Get installed LLM generators with working CLIs (verified via --version) */
|
|
305
|
+
function getInstalledGenerators() {
|
|
306
|
+
return detectInstalledAgents().filter((id) => targets[id].cli).map((id) => {
|
|
307
|
+
const ver = getAgentVersion(id);
|
|
308
|
+
return ver ? {
|
|
309
|
+
name: targets[id].displayName,
|
|
310
|
+
version: ver
|
|
311
|
+
} : null;
|
|
312
|
+
}).filter((a) => a !== null);
|
|
313
|
+
}
|
|
314
|
+
function relativeTime(date) {
|
|
315
|
+
const diff = Date.now() - date.getTime();
|
|
316
|
+
const mins = Math.floor(diff / 6e4);
|
|
317
|
+
const hours = Math.floor(diff / 36e5);
|
|
318
|
+
const days = Math.floor(diff / 864e5);
|
|
319
|
+
if (mins < 1) return "just now";
|
|
320
|
+
if (mins < 60) return `${mins}m ago`;
|
|
321
|
+
if (hours < 24) return `${hours}h ago`;
|
|
322
|
+
return `${days}d ago`;
|
|
323
|
+
}
|
|
324
|
+
function getLastSynced(state) {
|
|
325
|
+
let latest = null;
|
|
326
|
+
for (const skill of state.skills) if (skill.info?.syncedAt) {
|
|
327
|
+
const d = new Date(skill.info.syncedAt);
|
|
328
|
+
if (!latest || d > latest) latest = d;
|
|
329
|
+
}
|
|
330
|
+
return latest ? relativeTime(latest) : null;
|
|
331
|
+
}
|
|
332
|
+
function introLine({ state, generators, modelId, agentId }) {
|
|
333
|
+
const name = "\x1B[1m\x1B[35mskilld\x1B[0m";
|
|
334
|
+
const ver = `\x1B[90mv${version}\x1B[0m`;
|
|
335
|
+
const lastSynced = getLastSynced(state);
|
|
336
|
+
const synced = lastSynced ? ` · \x1B[90msynced ${lastSynced}\x1B[0m` : "";
|
|
337
|
+
const parts = [];
|
|
338
|
+
if (modelId) parts.push(getModelName(modelId));
|
|
339
|
+
else if (generators?.length) parts.push(generators.map((g) => `${g.name} v${g.version}`).join(", "));
|
|
340
|
+
if (agentId && targets[agentId]) parts.push(targets[agentId].displayName);
|
|
341
|
+
return `${name} ${ver}${synced}${parts.length > 0 ? `\n\x1B[90m↳ ${parts.join(" → ")}\x1B[0m` : ""}`;
|
|
342
|
+
}
|
|
343
|
+
function formatStatus(synced, outdated) {
|
|
344
|
+
const parts = [];
|
|
345
|
+
if (synced > 0) parts.push(`\x1B[32m${synced} synced\x1B[0m`);
|
|
346
|
+
if (outdated > 0) parts.push(`\x1B[33m${outdated} outdated\x1B[0m`);
|
|
347
|
+
return `Skills: ${parts.join(" · ")}`;
|
|
348
|
+
}
|
|
349
|
+
const OAUTH_NOTE = "Use an existing subscription (Claude Pro, ChatGPT Plus, Gemini)\nwithout an API key. You authenticate directly with the provider\nin your browser - no data leaves your machine.\n\nA refresh token is stored locally at ~/.skilld/pi-ai-auth.json\nand used to call the provider API directly from your computer.\n\x1B[90mOAuth handled by pi-ai, an open-source local client library:\nhttps://github.com/badlogic/pi-mono\x1B[0m";
|
|
350
|
+
const NO_MODELS_MESSAGE = "No enhancement models detected.\n \x1B[90mSkills work fine without this - you get raw docs, issues, and types.\n Enhancement compresses them into a concise cheat sheet with gotchas.\x1B[0m\n\n To connect a model (optional):\n 1. Connect a subscription via OAuth below (Claude Pro, ChatGPT Plus, Copilot, Gemini)\n 2. Set an env var: ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY\n 3. Install a CLI tool: \x1B[36mclaude\x1B[0m, \x1B[36mgemini\x1B[0m, or \x1B[36mcodex\x1B[0m (restart wizard after)";
|
|
351
|
+
/** Group models by vendor for provider→model selection. Uses vendorGroup to merge CLI and API entries under one heading. */
|
|
352
|
+
function groupModelsByProvider(models) {
|
|
353
|
+
const byVendor = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const m of models) {
|
|
355
|
+
const key = m.vendorGroup ?? m.provider;
|
|
356
|
+
if (!byVendor.has(key)) byVendor.set(key, {
|
|
357
|
+
name: key,
|
|
358
|
+
models: []
|
|
359
|
+
});
|
|
360
|
+
byVendor.get(key).models.push(m);
|
|
361
|
+
}
|
|
362
|
+
return byVendor;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Smart provider→model picker. Skips the provider step when there's only 1 provider.
|
|
366
|
+
* Returns the selected model value, or a sentinel string from before/after options.
|
|
367
|
+
*/
|
|
368
|
+
async function pickModel(models, opts = {}) {
|
|
369
|
+
const byProvider = groupModelsByProvider(models);
|
|
370
|
+
const before = opts.before ?? [];
|
|
371
|
+
const after = opts.after ?? [];
|
|
372
|
+
if (byProvider.size === 1 && before.length === 0) {
|
|
373
|
+
const [, group] = [...byProvider.entries()][0];
|
|
374
|
+
const choice = await p.select({
|
|
375
|
+
message: `${group.name}`,
|
|
376
|
+
options: [...group.models.map((m) => ({
|
|
377
|
+
label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,
|
|
378
|
+
value: m.id,
|
|
379
|
+
hint: m.hint
|
|
380
|
+
})), ...after]
|
|
381
|
+
});
|
|
382
|
+
return p.isCancel(choice) ? null : choice;
|
|
383
|
+
}
|
|
384
|
+
const providerChoice = await p.select({
|
|
385
|
+
message: "Select provider",
|
|
386
|
+
options: [
|
|
387
|
+
...before,
|
|
388
|
+
...Array.from(byProvider.entries(), ([key, { name, models: ms }]) => ({
|
|
389
|
+
label: name,
|
|
390
|
+
value: key,
|
|
391
|
+
hint: `${ms.length} models`
|
|
392
|
+
})),
|
|
393
|
+
...after
|
|
394
|
+
]
|
|
395
|
+
});
|
|
396
|
+
if (p.isCancel(providerChoice)) return null;
|
|
397
|
+
const providerStr = providerChoice;
|
|
398
|
+
if (before.some((o) => o.value === providerStr) || after.some((o) => o.value === providerStr)) return providerStr;
|
|
399
|
+
const group = byProvider.get(providerStr);
|
|
400
|
+
const modelChoice = await p.select({
|
|
401
|
+
message: `Select model (${group.name})`,
|
|
402
|
+
options: group.models.map((m) => ({
|
|
403
|
+
label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,
|
|
404
|
+
value: m.id,
|
|
405
|
+
hint: m.hint
|
|
406
|
+
}))
|
|
407
|
+
});
|
|
408
|
+
return p.isCancel(modelChoice) ? null : modelChoice;
|
|
409
|
+
}
|
|
410
|
+
function getRepoHint(name, cwd) {
|
|
411
|
+
const pkgJsonPath = join(cwd, "node_modules", name, "package.json");
|
|
412
|
+
if (!existsSync(pkgJsonPath)) return void 0;
|
|
413
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
414
|
+
const url = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
|
|
415
|
+
if (!url) return void 0;
|
|
416
|
+
return url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com").replace(/^https?:\/\/(www\.)?github\.com\//, "");
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
export { registerProject as C, readConfig as S, updateConfig as T, version as _, getRepoHint as a, hasCompletedWizard as b, isInteractive as c, pickModel as d, promptForAgent as f, sharedArgs as g, resolveAgent as h, getInstalledGenerators as i, isRunningInsideAgent as l, requireInteractive as m, OAUTH_NOTE as n, guard as o, relativeTime as p, formatStatus as r, introLine as s, NO_MODELS_MESSAGE as t, menuLoop as u, defaultFeatures as v, unregisterProject as w, hasConfig as x, getRegisteredProjects as y };
|
|
420
|
+
|
|
421
|
+
//# sourceMappingURL=cli-helpers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-helpers.mjs","names":["dirname","resolve","agents"],"sources":["../../src/core/config.ts","../../src/version.ts","../../src/cli-helpers.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { yamlEscape, yamlParseKV, yamlUnescape } from './yaml.ts'\n\nexport interface FeaturesConfig {\n search: boolean\n issues: boolean\n discussions: boolean\n releases: boolean\n}\n\nexport const defaultFeatures: FeaturesConfig = {\n search: true,\n issues: true,\n discussions: true,\n releases: true,\n}\n\nexport interface SkilldConfig {\n model?: OptimizeModel\n agent?: string\n features?: FeaturesConfig\n projects?: string[]\n skipLlm?: boolean\n}\n\nconst CONFIG_DIR = join(homedir(), '.skilld')\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.yaml')\n\nexport function hasConfig(): boolean {\n return existsSync(CONFIG_PATH)\n}\n\n/** Whether the first-run wizard has been completed (not just agent selection) */\nexport function hasCompletedWizard(): boolean {\n if (!existsSync(CONFIG_PATH))\n return false\n const config = readConfig()\n return config.features !== undefined || config.model !== undefined || config.skipLlm !== undefined\n}\n\nexport function readConfig(): SkilldConfig {\n if (!existsSync(CONFIG_PATH))\n return {}\n\n const content = readFileSync(CONFIG_PATH, 'utf-8')\n const config: SkilldConfig = {}\n let inBlock: 'projects' | 'features' | null = null\n const projects: string[] = []\n const features: Partial<FeaturesConfig> = {}\n\n for (const line of content.split('\\n')) {\n if (line.startsWith('projects:')) {\n inBlock = 'projects'\n continue\n }\n if (line.startsWith('features:')) {\n inBlock = 'features'\n continue\n }\n if (inBlock === 'projects') {\n if (line.startsWith(' - ')) {\n projects.push(yamlUnescape(line.slice(4)))\n continue\n }\n inBlock = null\n }\n if (inBlock === 'features') {\n const m = line.match(/^ {2}(\\w+):\\s*(.+)/)\n if (m) {\n const key = m[1] as keyof FeaturesConfig\n if (key in defaultFeatures)\n features[key] = m[2] === 'true'\n continue\n }\n inBlock = null\n }\n const kv = yamlParseKV(line)\n if (!kv)\n continue\n const [key, value] = kv\n if (key === 'model' && value)\n config.model = value as OptimizeModel\n if (key === 'agent' && value)\n config.agent = value\n if (key === 'skipLlm')\n config.skipLlm = value === 'true'\n }\n\n if (projects.length > 0)\n config.projects = projects\n if (Object.keys(features).length > 0)\n config.features = { ...defaultFeatures, ...features }\n return config\n}\n\nexport function writeConfig(config: SkilldConfig): void {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n\n let yaml = ''\n if (config.model)\n yaml += `model: ${config.model}\\n`\n if (config.agent)\n yaml += `agent: ${config.agent}\\n`\n if (config.skipLlm)\n yaml += `skipLlm: true\\n`\n if (config.features) {\n yaml += 'features:\\n'\n for (const [k, v] of Object.entries(config.features)) {\n yaml += ` ${k}: ${v}\\n`\n }\n }\n if (config.projects?.length) {\n yaml += 'projects:\\n'\n for (const p of config.projects) {\n yaml += ` - ${yamlEscape(p)}\\n`\n }\n }\n\n writeFileSync(CONFIG_PATH, yaml, { mode: 0o600 })\n}\n\nexport function updateConfig(updates: Partial<SkilldConfig>): void {\n const config = readConfig()\n writeConfig({ ...config, ...updates })\n}\n\nexport function registerProject(projectPath: string): void {\n const config = readConfig()\n const projects = new Set(config.projects || [])\n projects.add(projectPath)\n writeConfig({ ...config, projects: [...projects] })\n}\n\nexport function unregisterProject(projectPath: string): void {\n const config = readConfig()\n const projects = (config.projects || []).filter(p => p !== projectPath)\n writeConfig({ ...config, projects })\n}\n\nexport function getRegisteredProjects(): string[] {\n return readConfig().projects || []\n}\n","import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Walk up from current file to find package.json (works in both src/ and dist/_chunks/)\nfunction findPackageJson(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, 'package.json')\n try {\n const content = readFileSync(candidate, 'utf8')\n if (content)\n return content\n }\n catch {}\n dir = resolve(dir, '..')\n }\n return '{\"version\":\"0.0.0\"}'\n}\n\nexport const version: string = JSON.parse(findPackageJson()).version\n","/**\n * Shared CLI helpers used by subcommand definitions and the main CLI entry.\n * Extracted to avoid circular deps between cli.ts and commands/*.ts.\n */\n\nimport type { AgentType, OptimizeModel } from './agent/index.ts'\nimport type { ProjectState } from './core/skills.ts'\nimport { existsSync, readFileSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { join } from 'pathe'\nimport { detectCurrentAgent } from 'unagent/env'\nimport { agents, detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgentVersion, getModelName } from './agent/index.ts'\nimport { readConfig, updateConfig } from './core/config.ts'\nimport { version } from './version.ts'\n\nexport type { AgentType, OptimizeModel }\n\nexport interface IntroOptions {\n state: ProjectState\n /** Installed CLIs that can serve as enhancement models */\n generators?: Array<{ name: string, version: string }>\n /** Configured enhancement model ID */\n modelId?: string\n /** Resolved target agent ID */\n agentId?: string\n}\n\nexport const sharedArgs = {\n global: {\n type: 'boolean' as const,\n alias: 'g',\n description: 'Install globally to ~/<agent>/skills',\n default: false,\n },\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Target agent — where skills are installed',\n },\n model: {\n type: 'string' as const,\n alias: 'm',\n description: 'Enhancement model for SKILL.md generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean' as const,\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean' as const,\n alias: 'f',\n description: 'Ignore all caches, re-fetch docs and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean' as const,\n description: 'Save raw enhancement output to logs/ for each section',\n default: false,\n },\n}\n\n// ── Menu loop utility ─────────────────────────────────────────────────\n\n/** Thrown when a clack prompt is cancelled inside a menuLoop handler */\nexport class MenuCancel extends Error { override name = 'MenuCancel' }\n\n/** Assert a clack prompt result is not cancelled. Throws MenuCancel if cancelled. */\nexport function guard<T>(value: T | symbol): T {\n if (p.isCancel(value))\n throw new MenuCancel()\n return value as T\n}\n\nexport interface MenuOption {\n label: string\n value: string\n hint?: string\n}\n\n/**\n * Run a select menu in a loop with automatic back-navigation.\n *\n * - Cancel (Escape) at the menu itself → exits (returns)\n * - Cancel inside a handler (via guard()) → caught, loops back to menu\n * - Handler returns truthy → exits (returns)\n * - Handler returns void/false → loops back to menu\n *\n * Options are rebuilt each iteration so hints stay fresh after changes.\n */\nexport async function menuLoop(opts: {\n message: string\n options: () => MenuOption[] | Promise<MenuOption[]>\n onSelect: (value: string) => Promise<boolean | void>\n initialValue?: string | (() => string | undefined)\n /** Use fuzzy-searchable autocomplete instead of static select */\n searchable?: boolean\n}): Promise<void> {\n while (true) {\n const options = await opts.options()\n const initial = typeof opts.initialValue === 'function' ? opts.initialValue() : opts.initialValue\n const choice = opts.searchable\n ? await p.autocomplete({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n : await p.select({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n if (p.isCancel(choice))\n return\n try {\n if (await opts.onSelect(choice as string))\n return\n }\n catch (err) {\n if (err instanceof MenuCancel)\n continue\n throw err\n }\n }\n}\n\n/** Check if we're running inside an AI coding agent */\nexport function isRunningInsideAgent(): boolean {\n return !!detectCurrentAgent()\n}\n\n/** Check if the current environment supports interactive prompts */\nexport function isInteractive(): boolean {\n if (isRunningInsideAgent())\n return false\n if (process.env.CI)\n return false\n if (!process.stdout.isTTY)\n return false\n return true\n}\n\n/** Exit with error if interactive terminal is required but unavailable */\nexport function requireInteractive(command: string): void {\n if (!isInteractive()) {\n console.error(`Error: \\`skilld ${command}\\` requires an interactive terminal`)\n process.exit(1)\n }\n}\n\n/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */\nexport function resolveAgent(agentFlag?: string): AgentType | 'none' | null {\n if (process.env.SKILLD_NO_AGENT)\n return null\n return (agentFlag as AgentType | undefined)\n ?? detectTargetAgent()\n ?? (readConfig().agent as AgentType | undefined)\n ?? null\n}\n\nlet _warnedNoAgent = false\nfunction warnNoAgent(): void {\n if (_warnedNoAgent)\n return\n _warnedNoAgent = true\n p.log.warn('No target agent detected — falling back to prompt-only mode.\\n Use --agent <name> to specify, or run `skilld config` to set a default.')\n}\n\n/** Prompt user to pick an agent when auto-detection fails */\nexport async function promptForAgent(): Promise<AgentType | 'none' | null> {\n const noAgent = !!process.env.SKILLD_NO_AGENT\n const installed = noAgent ? [] : detectInstalledAgents()\n const projectMatches = noAgent ? [] : detectProjectAgents()\n\n // Non-interactive: auto-select sole installed agent or fall back to prompt-only\n if (!isInteractive()) {\n if (installed.length === 1) {\n updateConfig({ agent: installed[0] })\n return installed[0]!\n }\n warnNoAgent()\n return 'none'\n }\n\n // Brief context before asking about agents\n p.log.info(\n `Skilld generates reference cards from package docs so your AI agent\\n`\n + ` always has accurate APIs for your exact dependency versions.`,\n )\n\n // Build options: prefer project-matched agents, then installed, then all\n const candidateIds = projectMatches.length > 0\n ? projectMatches\n : installed.length > 0\n ? installed\n : Object.keys(agents) as AgentType[]\n\n // Agents that also read .claude/skills/\n const sharedAgents = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.some(d => d.includes('.claude/skills')))\n .map(([id]) => id),\n )\n\n // Group: agents that share skills vs agents with their own directory\n const sharedIds = candidateIds.filter(id => id === 'claude-code' || sharedAgents.has(id))\n const isolatedIds = candidateIds.filter(id => id !== 'claude-code' && !sharedAgents.has(id))\n\n const options: Array<{ label: string, value: AgentType | 'none', hint?: string }> = []\n\n // Show shared-compatible agents first\n if (sharedIds.length > 0 && isolatedIds.length > 0) {\n for (const id of sharedIds) {\n const a = agents[id]\n const hint = id === 'claude-code'\n ? `skills shared with ${sharedIds.length - 1} other agents`\n : `skills shared with Claude Code and others`\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n }\n\n // Agents with isolated skill dirs\n const isolatedAgentIds = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.length === 0)\n .map(([id]) => id),\n )\n\n for (const id of (sharedIds.length > 0 && isolatedIds.length > 0 ? isolatedIds : candidateIds)) {\n if (options.some(o => o.value === id))\n continue\n const a = agents[id]\n const hint = sharedAgents.has(id) && id !== 'claude-code'\n ? 'skills shared with Claude Code and others'\n : isolatedAgentIds.has(id)\n ? 'skills only visible to this agent'\n : undefined\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n\n options.push({ label: 'No agent', value: 'none', hint: 'export as standalone files for any AI' })\n\n if (!_warnedNoAgent) {\n _warnedNoAgent = true\n const hint = projectMatches.length > 1\n ? `Multiple agent directories found: ${projectMatches.map(t => agents[t].displayName).join(', ')}`\n : installed.length > 0\n ? `Found ${installed.map(t => agents[t].displayName).join(', ')} but couldn't determine which to use`\n : 'No agents auto-detected'\n const crossNote = sharedIds.length > 1\n ? `\\n \\x1B[90mTip: Picking Claude Code shares skills with ${sharedIds.filter(id => id !== 'claude-code').map(id => agents[id].displayName).join(', ')} automatically.\\x1B[0m`\n : ''\n p.log.warn(`${hint}\\n Pick the agent you actively code with.${crossNote}`)\n }\n\n const choice = await p.select({\n message: 'Which AI coding agent do you use?',\n options,\n })\n\n if (p.isCancel(choice))\n return null\n\n if (choice === 'none')\n return 'none'\n\n // Save as default so they don't get asked again\n updateConfig({ agent: choice })\n p.log.success(`Target agent set to ${agents[choice].displayName}`)\n return choice\n}\n\n/** Get installed LLM generators with working CLIs (verified via --version) */\nexport function getInstalledGenerators(): Array<{ name: string, version: string }> {\n const installed = detectInstalledAgents()\n return installed\n .filter(id => agents[id].cli)\n .map((id) => {\n const ver = getAgentVersion(id)\n return ver ? { name: agents[id].displayName, version: ver } : null\n })\n .filter((a): a is { name: string, version: string } => a !== null)\n}\n\nexport function relativeTime(date: Date): string {\n const now = Date.now()\n const diff = now - date.getTime()\n const mins = Math.floor(diff / 60000)\n const hours = Math.floor(diff / 3600000)\n const days = Math.floor(diff / 86400000)\n if (mins < 1)\n return 'just now'\n if (mins < 60)\n return `${mins}m ago`\n if (hours < 24)\n return `${hours}h ago`\n return `${days}d ago`\n}\n\nexport function getLastSynced(state: ProjectState): string | null {\n let latest: Date | null = null\n for (const skill of state.skills) {\n if (skill.info?.syncedAt) {\n const d = new Date(skill.info.syncedAt)\n if (!latest || d > latest)\n latest = d\n }\n }\n return latest ? relativeTime(latest) : null\n}\n\nexport function introLine({ state, generators, modelId, agentId }: IntroOptions): string {\n const name = '\\x1B[1m\\x1B[35mskilld\\x1B[0m'\n const ver = `\\x1B[90mv${version}\\x1B[0m`\n const lastSynced = getLastSynced(state)\n const synced = lastSynced ? ` · \\x1B[90msynced ${lastSynced}\\x1B[0m` : ''\n\n // Status line: enhancement model → target agent\n const parts: string[] = []\n if (modelId)\n parts.push(getModelName(modelId as any))\n else if (generators?.length)\n parts.push(generators.map(g => `${g.name} v${g.version}`).join(', '))\n if (agentId && agents[agentId as AgentType])\n parts.push(agents[agentId as AgentType].displayName)\n const statusLine = parts.length > 0\n ? `\\n\\x1B[90m↳ ${parts.join(' → ')}\\x1B[0m`\n : ''\n\n return `${name} ${ver}${synced}${statusLine}`\n}\n\nexport function formatStatus(synced: number, outdated: number): string {\n const parts: string[] = []\n if (synced > 0)\n parts.push(`\\x1B[32m${synced} synced\\x1B[0m`)\n if (outdated > 0)\n parts.push(`\\x1B[33m${outdated} outdated\\x1B[0m`)\n return `Skills: ${parts.join(' · ')}`\n}\n\n// ── Shared UI constants ───────────────────────────────────────────────\n\nexport const OAUTH_NOTE\n = 'Use an existing subscription (Claude Pro, ChatGPT Plus, Gemini)\\n'\n + 'without an API key. You authenticate directly with the provider\\n'\n + 'in your browser - no data leaves your machine.\\n'\n + '\\n'\n + 'A refresh token is stored locally at ~/.skilld/pi-ai-auth.json\\n'\n + 'and used to call the provider API directly from your computer.\\n'\n + '\\x1B[90mOAuth handled by pi-ai, an open-source local client library:\\n'\n + 'https://github.com/badlogic/pi-mono\\x1B[0m'\n\nexport const NO_MODELS_MESSAGE = 'No enhancement models detected.\\n'\n + ' \\x1B[90mSkills work fine without this - you get raw docs, issues, and types.\\n'\n + ' Enhancement compresses them into a concise cheat sheet with gotchas.\\x1B[0m\\n'\n + '\\n'\n + ' To connect a model (optional):\\n'\n + ' 1. Connect a subscription via OAuth below (Claude Pro, ChatGPT Plus, Copilot, Gemini)\\n'\n + ' 2. Set an env var: ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY\\n'\n + ' 3. Install a CLI tool: \\x1B[36mclaude\\x1B[0m, \\x1B[36mgemini\\x1B[0m, or \\x1B[36mcodex\\x1B[0m (restart wizard after)'\n\n/** Group models by vendor for provider→model selection. Uses vendorGroup to merge CLI and API entries under one heading. */\nexport function groupModelsByProvider<T extends { provider: string, providerName: string, vendorGroup?: string }>(models: T[]): Map<string, { name: string, models: T[] }> {\n const byVendor = new Map<string, { name: string, models: T[] }>()\n for (const m of models) {\n const key = m.vendorGroup ?? m.provider\n if (!byVendor.has(key))\n byVendor.set(key, { name: key, models: [] })\n byVendor.get(key)!.models.push(m)\n }\n return byVendor\n}\n\nexport interface ModelPickerOptions {\n /** Extra options prepended (e.g. Auto, Connect OAuth) */\n before?: Array<{ label: string, value: string, hint?: string }>\n /** Extra options appended (e.g. Skip) */\n after?: Array<{ label: string, value: string, hint?: string }>\n}\n\n/**\n * Smart provider→model picker. Skips the provider step when there's only 1 provider.\n * Returns the selected model value, or a sentinel string from before/after options.\n */\nexport async function pickModel<T extends { provider: string, providerName: string, name: string, id: string, hint: string, recommended?: boolean }>(\n models: T[],\n opts: ModelPickerOptions = {},\n): Promise<string | null> {\n const byProvider = groupModelsByProvider(models)\n const before = opts.before ?? []\n const after = opts.after ?? []\n\n // Single provider → skip provider step, show models directly\n if (byProvider.size === 1 && before.length === 0) {\n const [, group] = [...byProvider.entries()][0]!\n const choice = await p.select({\n message: `${group.name}`,\n options: [\n ...group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n ...after,\n ],\n })\n return p.isCancel(choice) ? null : choice as string\n }\n\n // Multiple providers or has before options - two-step\n const providerChoice = await p.select({\n message: 'Select provider',\n options: [\n ...before,\n ...Array.from(byProvider.entries(), ([key, { name, models: ms }]) => ({\n label: name,\n value: key,\n hint: `${ms.length} models`,\n })),\n ...after,\n ],\n })\n\n if (p.isCancel(providerChoice))\n return null\n\n // Check if it's a sentinel from before/after\n const providerStr = providerChoice as string\n if (before.some(o => o.value === providerStr) || after.some(o => o.value === providerStr))\n return providerStr\n\n // Drill into provider's models\n const group = byProvider.get(providerStr)!\n const modelChoice = await p.select({\n message: `Select model (${group.name})`,\n options: group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n })\n\n return p.isCancel(modelChoice) ? null : modelChoice as string\n}\n\nexport function getRepoHint(name: string, cwd: string): string | undefined {\n const pkgJsonPath = join(cwd, 'node_modules', name, 'package.json')\n if (!existsSync(pkgJsonPath))\n return undefined\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))\n const url = typeof pkg.repository === 'string'\n ? pkg.repository\n : pkg.repository?.url\n if (!url)\n return undefined\n return url\n .replace(/^git\\+/, '')\n .replace(/\\.git$/, '')\n .replace(/^git:\\/\\//, 'https://')\n .replace(/^ssh:\\/\\/git@github\\.com/, 'https://github.com')\n .replace(/^https?:\\/\\/(www\\.)?github\\.com\\//, '')\n}\n"],"mappings":"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"}
|