skilld 1.7.4 → 2.0.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/dist/_chunks/add.mjs +66 -0
- package/dist/_chunks/add.mjs.map +1 -0
- package/dist/_chunks/agent-prompt.mjs +88 -0
- package/dist/_chunks/agent-prompt.mjs.map +1 -0
- package/dist/_chunks/agent.mjs +81 -57
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/args.mjs +42 -0
- package/dist/_chunks/args.mjs.map +1 -0
- package/dist/_chunks/assemble.mjs +10 -7
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +33 -17
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +143 -183
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +7 -6
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/client.mjs +117 -0
- package/dist/_chunks/client.mjs.map +1 -0
- package/dist/_chunks/core.mjs +5 -5
- package/dist/_chunks/detect.mjs +53 -43
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/eject.mjs +69 -0
- package/dist/_chunks/eject.mjs.map +1 -0
- package/dist/_chunks/embedding-cache2.mjs +1 -1
- package/dist/_chunks/env.mjs +19 -0
- package/dist/_chunks/env.mjs.map +1 -0
- package/dist/_chunks/install-many.mjs +376 -0
- package/dist/_chunks/install-many.mjs.map +1 -0
- package/dist/_chunks/install.mjs +81 -326
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/intro.mjs +63 -0
- package/dist/_chunks/intro.mjs.map +1 -0
- package/dist/_chunks/list.mjs +2 -2
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs +3 -2
- package/dist/_chunks/lockfile.mjs.map +1 -1
- package/dist/_chunks/login.mjs +233 -0
- package/dist/_chunks/login.mjs.map +1 -0
- package/dist/_chunks/logout.mjs +27 -0
- package/dist/_chunks/logout.mjs.map +1 -0
- package/dist/_chunks/map.mjs +11 -0
- package/dist/_chunks/map.mjs.map +1 -0
- package/dist/_chunks/markdown.mjs +79 -54
- package/dist/_chunks/markdown.mjs.map +1 -1
- package/dist/_chunks/menu.mjs +33 -0
- package/dist/_chunks/menu.mjs.map +1 -0
- package/dist/_chunks/model-picker.mjs +61 -0
- package/dist/_chunks/model-picker.mjs.map +1 -0
- package/dist/_chunks/monorepo.mjs +4 -2
- package/dist/_chunks/monorepo.mjs.map +1 -1
- package/dist/_chunks/package-json.mjs.map +1 -1
- package/dist/_chunks/paths.mjs +3 -5
- package/dist/_chunks/paths.mjs.map +1 -1
- package/dist/_chunks/{sync-pipeline.mjs → pipeline.mjs} +346 -313
- package/dist/_chunks/pipeline.mjs.map +1 -0
- package/dist/_chunks/pool2.mjs +1 -1
- package/dist/_chunks/portable.mjs +151 -0
- package/dist/_chunks/portable.mjs.map +1 -0
- package/dist/_chunks/prepare-hook.mjs +2 -0
- package/dist/_chunks/prepare-hook2.mjs +61 -0
- package/dist/_chunks/prepare-hook2.mjs.map +1 -0
- package/dist/_chunks/prepare.mjs +47 -3
- package/dist/_chunks/prepare.mjs.map +1 -1
- package/dist/_chunks/prepare2.mjs +7 -6
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +484 -74
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/pull.mjs +219 -0
- package/dist/_chunks/pull.mjs.map +1 -0
- package/dist/_chunks/regex.mjs +19 -0
- package/dist/_chunks/regex.mjs.map +1 -0
- package/dist/_chunks/retriv.mjs +2 -171
- package/dist/_chunks/retriv2.mjs +159 -0
- package/dist/_chunks/retriv2.mjs.map +1 -0
- package/dist/_chunks/sanitize.mjs +12 -9
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-helpers.mjs +8 -6
- package/dist/_chunks/search-helpers.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +23 -20
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +3 -3
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/semver.mjs +2755 -1
- package/dist/_chunks/semver.mjs.map +1 -1
- package/dist/_chunks/skill-installer2.mjs +10 -11
- package/dist/_chunks/skill-installer2.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +6 -7
- package/dist/_chunks/skills.mjs.map +1 -1
- package/dist/_chunks/store.mjs +107 -0
- package/dist/_chunks/store.mjs.map +1 -0
- package/dist/_chunks/sync.mjs +411 -910
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/sync2.mjs +2 -5
- package/dist/_chunks/telemetry.mjs +26 -0
- package/dist/_chunks/telemetry.mjs.map +1 -0
- package/dist/_chunks/uninstall.mjs +12 -9
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/update.mjs +171 -0
- package/dist/_chunks/update.mjs.map +1 -0
- package/dist/_chunks/upload.mjs +3 -3
- package/dist/_chunks/validate.mjs +1 -1
- package/dist/_chunks/version.mjs +16 -17
- package/dist/_chunks/version.mjs.map +1 -1
- package/dist/_chunks/whoami.mjs +21 -0
- package/dist/_chunks/whoami.mjs.map +1 -0
- package/dist/_chunks/wizard.mjs +2 -190
- package/dist/_chunks/wizard2.mjs +200 -0
- package/dist/_chunks/wizard2.mjs.map +1 -0
- package/dist/cli.mjs +72 -53
- package/dist/cli.mjs.map +1 -1
- package/dist/prepare.mjs +4 -3
- package/dist/prepare.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +5 -1
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +1 -1
- package/package.json +19 -28
- package/dist/_chunks/author-group.mjs +0 -17
- package/dist/_chunks/author-group.mjs.map +0 -1
- package/dist/_chunks/cli-helpers.mjs +0 -335
- package/dist/_chunks/cli-helpers.mjs.map +0 -1
- package/dist/_chunks/cli-helpers2.mjs +0 -2
- package/dist/_chunks/index.d.mts +0 -344
- package/dist/_chunks/index.d.mts.map +0 -1
- package/dist/_chunks/index2.d.mts +0 -279
- package/dist/_chunks/index2.d.mts.map +0 -1
- package/dist/_chunks/index3.d.mts +0 -44
- package/dist/_chunks/index3.d.mts.map +0 -1
- package/dist/_chunks/index4.d.mts +0 -553
- package/dist/_chunks/index4.d.mts.map +0 -1
- package/dist/_chunks/package-registry.mjs +0 -465
- package/dist/_chunks/package-registry.mjs.map +0 -1
- package/dist/_chunks/retriv.mjs.map +0 -1
- package/dist/_chunks/setup.mjs +0 -17
- package/dist/_chunks/setup.mjs.map +0 -1
- package/dist/_chunks/sources.mjs +0 -2654
- package/dist/_chunks/sources.mjs.map +0 -1
- package/dist/_chunks/sync-pipeline.mjs.map +0 -1
- package/dist/_chunks/sync-registry.mjs +0 -65
- package/dist/_chunks/sync-registry.mjs.map +0 -1
- package/dist/_chunks/types.d.mts +0 -76
- package/dist/_chunks/types.d.mts.map +0 -1
- package/dist/_chunks/types2.d.mts +0 -88
- package/dist/_chunks/types2.d.mts.map +0 -1
- package/dist/_chunks/wizard.mjs.map +0 -1
- package/dist/agent/index.d.mts +0 -2
- package/dist/agent/index.mjs +0 -4
- package/dist/cache/index.d.mts +0 -2
- package/dist/cache/index.mjs +0 -5
- package/dist/index.d.mts +0 -6
- package/dist/index.mjs +0 -6
- package/dist/retriv/index.d.mts +0 -3
- package/dist/retriv/index.mjs +0 -2
- package/dist/sources/index.d.mts +0 -3
- package/dist/sources/index.mjs +0 -3
- package/dist/types.d.mts +0 -4
- package/dist/types.mjs +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { a as targets, i as getAgentVersion, t as detectInstalledAgents } from "./detect.mjs";
|
|
2
|
+
import { a as getModelName } from "./agent.mjs";
|
|
3
|
+
import { a as GIT_PROTOCOL_PREFIX_RE, i as GIT_PLUS_PREFIX_RE, o as GIT_SUFFIX_RE, r as GITHUB_SSH_URL_PREFIX_RE } from "./regex.mjs";
|
|
4
|
+
import { i as readPackageJsonSafe } from "./package-json.mjs";
|
|
5
|
+
import { t as version } from "./version.mjs";
|
|
6
|
+
import { styleText } from "node:util";
|
|
7
|
+
import { join } from "pathe";
|
|
8
|
+
const STATIC_REGEX_5 = /^https?:\/\/(www\.)?github\.com\//;
|
|
9
|
+
function getInstalledGenerators() {
|
|
10
|
+
return detectInstalledAgents().filter((id) => targets[id].cli).map((id) => {
|
|
11
|
+
const ver = getAgentVersion(id);
|
|
12
|
+
return ver ? {
|
|
13
|
+
name: targets[id].displayName,
|
|
14
|
+
version: ver
|
|
15
|
+
} : null;
|
|
16
|
+
}).filter((a) => a !== null);
|
|
17
|
+
}
|
|
18
|
+
function relativeTime(date) {
|
|
19
|
+
const diff = Date.now() - date.getTime();
|
|
20
|
+
const mins = Math.floor(diff / 6e4);
|
|
21
|
+
const hours = Math.floor(diff / 36e5);
|
|
22
|
+
const days = Math.floor(diff / 864e5);
|
|
23
|
+
if (mins < 1) return "just now";
|
|
24
|
+
if (mins < 60) return `${mins}m ago`;
|
|
25
|
+
if (hours < 24) return `${hours}h ago`;
|
|
26
|
+
return `${days}d ago`;
|
|
27
|
+
}
|
|
28
|
+
function getLastSynced(state) {
|
|
29
|
+
let latest = null;
|
|
30
|
+
for (const skill of state.skills) if (skill.info?.syncedAt) {
|
|
31
|
+
const d = new Date(skill.info.syncedAt);
|
|
32
|
+
if (!latest || d > latest) latest = d;
|
|
33
|
+
}
|
|
34
|
+
return latest ? relativeTime(latest) : null;
|
|
35
|
+
}
|
|
36
|
+
function introLine({ state, generators, modelId, agentId }) {
|
|
37
|
+
const name = styleText(["bold", "magenta"], "skilld");
|
|
38
|
+
const ver = styleText("gray", `v${version}`);
|
|
39
|
+
const lastSynced = getLastSynced(state);
|
|
40
|
+
const synced = lastSynced ? ` · ${styleText("gray", `synced ${lastSynced}`)}` : "";
|
|
41
|
+
const parts = [];
|
|
42
|
+
if (modelId) parts.push(getModelName(modelId));
|
|
43
|
+
else if (generators?.length) parts.push(generators.map((g) => `${g.name} v${g.version}`).join(", "));
|
|
44
|
+
if (agentId && targets[agentId]) parts.push(targets[agentId].displayName);
|
|
45
|
+
return `${name} ${ver}${synced}${parts.length > 0 ? `\n${styleText("gray", `↳ ${parts.join(" → ")}`)}` : ""}`;
|
|
46
|
+
}
|
|
47
|
+
function formatStatus(synced, outdated) {
|
|
48
|
+
const parts = [];
|
|
49
|
+
if (synced > 0) parts.push(styleText("green", `${synced} synced`));
|
|
50
|
+
if (outdated > 0) parts.push(styleText("yellow", `${outdated} outdated`));
|
|
51
|
+
return `Skills: ${parts.join(" · ")}`;
|
|
52
|
+
}
|
|
53
|
+
function getRepoHint(name, cwd) {
|
|
54
|
+
const result = readPackageJsonSafe(join(cwd, "node_modules", name, "package.json"));
|
|
55
|
+
if (!result) return void 0;
|
|
56
|
+
const pkg = result.parsed;
|
|
57
|
+
const url = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
|
|
58
|
+
if (!url) return void 0;
|
|
59
|
+
return url.replace(GIT_PLUS_PREFIX_RE, "").replace(GIT_SUFFIX_RE, "").replace(GIT_PROTOCOL_PREFIX_RE, "https://").replace(GITHUB_SSH_URL_PREFIX_RE, "https://github.com").replace(STATIC_REGEX_5, "");
|
|
60
|
+
}
|
|
61
|
+
export { relativeTime as a, introLine as i, getInstalledGenerators as n, getRepoHint as r, formatStatus as t };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=intro.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intro.mjs","names":["agents"],"sources":["../../src/cli/intro.ts"],"sourcesContent":["import type { AgentType } from '../agent/index.ts'\nimport type { ProjectState } from '../core/skills.ts'\nimport { styleText } from 'node:util'\nimport { join } from 'pathe'\nimport { agents, detectInstalledAgents, getAgentVersion, getModelName } from '../agent/index.ts'\nimport { readPackageJsonSafe } from '../core/package-json.ts'\nimport { GIT_PLUS_PREFIX_RE, GIT_PROTOCOL_PREFIX_RE, GIT_SUFFIX_RE, GITHUB_SSH_URL_PREFIX_RE } from '../core/regex.ts'\nimport { version } from '../version.ts'\n\nconst STATIC_REGEX_5 = /^https?:\\/\\/(www\\.)?github\\.com\\//\n\nexport interface IntroOptions {\n state: ProjectState\n generators?: Array<{ name: string, version: string }>\n modelId?: string\n agentId?: string\n}\n\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 = styleText(['bold', 'magenta'], 'skilld')\n const ver = styleText('gray', `v${version}`)\n const lastSynced = getLastSynced(state)\n const synced = lastSynced ? ` · ${styleText('gray', `synced ${lastSynced}`)}` : ''\n\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${styleText('gray', `↳ ${parts.join(' → ')}`)}`\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(styleText('green', `${synced} synced`))\n if (outdated > 0)\n parts.push(styleText('yellow', `${outdated} outdated`))\n return `Skills: ${parts.join(' · ')}`\n}\n\nexport function getRepoHint(name: string, cwd: string): string | undefined {\n const result = readPackageJsonSafe(join(cwd, 'node_modules', name, 'package.json'))\n if (!result)\n return undefined\n const pkg = result.parsed as Record<string, any>\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_PLUS_PREFIX_RE, '')\n .replace(GIT_SUFFIX_RE, '')\n .replace(GIT_PROTOCOL_PREFIX_RE, 'https://')\n .replace(GITHUB_SSH_URL_PREFIX_RE, 'https://github.com')\n .replace(STATIC_REGEX_5, '')\n}\n"],"mappings":";;;;;;;AASA,MAAM,iBAAiB;AASvB,SAAgB,yBAAmE;CAEjF,OADkB,uBACF,CACb,QAAO,OAAMA,QAAO,IAAI,IAAI,CAC5B,KAAK,OAAO;EACX,MAAM,MAAM,gBAAgB,GAAG;EAC/B,OAAO,MAAM;GAAE,MAAMA,QAAO,IAAI;GAAa,SAAS;GAAK,GAAG;GAC9D,CACD,QAAQ,MAA8C,MAAM,KAAK;;AAGtE,SAAgB,aAAa,MAAoB;CAE/C,MAAM,OADM,KAAK,KACD,GAAG,KAAK,SAAS;CACjC,MAAM,OAAO,KAAK,MAAM,OAAO,IAAM;CACrC,MAAM,QAAQ,KAAK,MAAM,OAAO,KAAQ;CACxC,MAAM,OAAO,KAAK,MAAM,OAAO,MAAS;CACxC,IAAI,OAAO,GACT,OAAO;CACT,IAAI,OAAO,IACT,OAAO,GAAG,KAAK;CACjB,IAAI,QAAQ,IACV,OAAO,GAAG,MAAM;CAClB,OAAO,GAAG,KAAK;;AAGjB,SAAgB,cAAc,OAAoC;CAChE,IAAI,SAAsB;CAC1B,KAAK,MAAM,SAAS,MAAM,QACxB,IAAI,MAAM,MAAM,UAAU;EACxB,MAAM,IAAI,IAAI,KAAK,MAAM,KAAK,SAAS;EACvC,IAAI,CAAC,UAAU,IAAI,QACjB,SAAS;;CAGf,OAAO,SAAS,aAAa,OAAO,GAAG;;AAGzC,SAAgB,UAAU,EAAE,OAAO,YAAY,SAAS,WAAiC;CACvF,MAAM,OAAO,UAAU,CAAC,QAAQ,UAAU,EAAE,SAAS;CACrD,MAAM,MAAM,UAAU,QAAQ,IAAI,UAAU;CAC5C,MAAM,aAAa,cAAc,MAAM;CACvC,MAAM,SAAS,aAAa,MAAM,UAAU,QAAQ,UAAU,aAAa,KAAK;CAEhF,MAAM,QAAkB,EAAE;CAC1B,IAAI,SACF,MAAM,KAAK,aAAa,QAAe,CAAC;MACrC,IAAI,YAAY,QACnB,MAAM,KAAK,WAAW,KAAI,MAAK,GAAG,EAAE,KAAK,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CAAC;CACvE,IAAI,WAAWA,QAAO,UACpB,MAAM,KAAKA,QAAO,SAAsB,YAAY;CAKtD,OAAO,GAAG,KAAK,GAAG,MAAM,SAJL,MAAM,SAAS,IAC9B,KAAK,UAAU,QAAQ,KAAK,MAAM,KAAK,MAAM,GAAG,KAChD;;AAKN,SAAgB,aAAa,QAAgB,UAA0B;CACrE,MAAM,QAAkB,EAAE;CAC1B,IAAI,SAAS,GACX,MAAM,KAAK,UAAU,SAAS,GAAG,OAAO,SAAS,CAAC;CACpD,IAAI,WAAW,GACb,MAAM,KAAK,UAAU,UAAU,GAAG,SAAS,WAAW,CAAC;CACzD,OAAO,WAAW,MAAM,KAAK,MAAM;;AAGrC,SAAgB,YAAY,MAAc,KAAiC;CACzE,MAAM,SAAS,oBAAoB,KAAK,KAAK,gBAAgB,MAAM,eAAe,CAAC;CACnF,IAAI,CAAC,QACH,OAAO,KAAA;CACT,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,OAAO,IAAI,eAAe,WAClC,IAAI,aACJ,IAAI,YAAY;CACpB,IAAI,CAAC,KACH,OAAO,KAAA;CACT,OAAO,IACJ,QAAQ,oBAAoB,GAAG,CAC/B,QAAQ,eAAe,GAAG,CAC1B,QAAQ,wBAAwB,WAAW,CAC3C,QAAQ,0BAA0B,qBAAqB,CACvD,QAAQ,gBAAgB,GAAG"}
|
package/dist/_chunks/list.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { m as timeAgo, u as formatSource } from "./prompts.mjs";
|
|
2
|
+
import { t as sharedArgs } from "./args.mjs";
|
|
3
3
|
import { i as iterateSkills, t as getProjectState } from "./skills.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
async function listCommand(opts = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.mjs","names":[],"sources":["../../src/commands/list.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { sharedArgs } from '../cli
|
|
1
|
+
{"version":3,"file":"list.mjs","names":[],"sources":["../../src/commands/list.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { sharedArgs } from '../cli/args.ts'\nimport { formatSource, timeAgo } from '../core/formatting.ts'\nimport { getProjectState, iterateSkills } from '../core/skills.ts'\n\nexport interface ListOptions {\n global?: boolean\n json?: boolean\n outdated?: boolean\n}\n\ninterface ListEntry {\n name: string\n version: string\n source: string\n synced: string\n latest?: string\n}\n\nexport async function listCommand(opts: ListOptions = {}): Promise<void> {\n if (opts.outdated) {\n const state = await getProjectState()\n const entries: ListEntry[] = state.outdated.map(skill => ({\n name: skill.name,\n version: skill.info?.version || '',\n latest: skill.latestVersion || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n }))\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('All skills are up to date\\n')\n return\n }\n\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const latW = Math.max(...entries.map(e => (e.latest || '').length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n `${e.version.padEnd(verW)} → ${(e.latest || '').padEnd(latW)}`,\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n return\n }\n\n const scope = opts.global ? 'global' : 'all'\n const skills = [...iterateSkills({ scope })]\n\n // Deduplicate by package identity\n const seen = new Set<string>()\n const entries: ListEntry[] = []\n\n for (const skill of skills) {\n const key = skill.info?.packageName || skill.name\n if (seen.has(key))\n continue\n seen.add(key)\n entries.push({\n name: skill.name,\n version: skill.info?.version || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n })\n }\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('No skills installed\\n')\n return\n }\n\n // Column widths\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n e.version.padEnd(verW),\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n}\n\nexport const listCommandDef = defineCommand({\n meta: { name: 'list', description: 'List installed skills' },\n args: {\n global: sharedArgs.global,\n json: {\n type: 'boolean' as const,\n description: 'Output as JSON',\n default: false,\n },\n outdated: {\n type: 'boolean' as const,\n alias: 'o',\n description: 'Show only outdated skills',\n default: false,\n },\n },\n run({ args }) {\n return listCommand({ global: args.global, json: args.json, outdated: args.outdated })\n },\n})\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { p as lockfilePath } from "./paths.mjs";
|
|
2
2
|
import { n as yamlParseKV, t as yamlEscape } from "./yaml.mjs";
|
|
3
3
|
import { i as parseFrontmatter } from "./markdown.mjs";
|
|
4
4
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
const STATIC_REGEX_1 = /^ {2}(\S+):$/;
|
|
5
6
|
function parsePackages(packages) {
|
|
6
7
|
if (!packages) return [];
|
|
7
8
|
return packages.split(",").map((s) => {
|
|
@@ -60,7 +61,7 @@ function readLock(skillsDir) {
|
|
|
60
61
|
const skills = {};
|
|
61
62
|
let currentSkill = null;
|
|
62
63
|
for (const line of content.split("\n")) {
|
|
63
|
-
const skillMatch = line.match(
|
|
64
|
+
const skillMatch = line.match(STATIC_REGEX_1);
|
|
64
65
|
if (skillMatch) {
|
|
65
66
|
currentSkill = skillMatch[1];
|
|
66
67
|
skills[currentSkill] = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/core/lockfile.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { parseFrontmatter } from './markdown.ts'\nimport { lockfilePath } from './paths.ts'\nimport { yamlEscape, yamlParseKV } from './yaml.ts'\n\nexport interface SkillInfo {\n packageName?: string\n version?: string\n /** All tracked packages as comma-separated \"name@version\" pairs (multi-package skills) */\n packages?: string\n repo?: string\n source?: string\n syncedAt?: string\n generator?: string\n /** Skill path within repo (git-sourced skills) */\n path?: string\n /** Git ref tracked for updates */\n ref?: string\n /** Git commit SHA at install time */\n commit?: string\n}\n\nexport function parsePackages(packages?: string): Array<{ name: string, version: string }> {\n if (!packages)\n return []\n return packages.split(',').map((s) => {\n const trimmed = s.trim()\n const atIdx = trimmed.lastIndexOf('@')\n if (atIdx <= 0)\n return { name: trimmed, version: '' }\n return { name: trimmed.slice(0, atIdx), version: trimmed.slice(atIdx + 1) }\n }).filter(p => p.name)\n}\n\nexport function parsePackageNames(packages?: string): Array<{ name: string }> {\n return parsePackages(packages).map(({ name }) => ({ name }))\n}\n\nexport function serializePackages(pkgs: Array<{ name: string, version: string }>): string {\n return pkgs.map(p => `${p.name}@${p.version}`).join(', ')\n}\n\nexport interface SkilldLock {\n skills: Record<string, SkillInfo>\n}\n\nconst SKILL_FM_KEYS: (keyof SkillInfo)[] = ['packageName', 'version', 'packages', 'repo', 'source', 'syncedAt', 'generator', 'path', 'ref', 'commit']\n\nfunction isSkillInfoKey(key: string): key is keyof SkillInfo {\n return (SKILL_FM_KEYS as readonly string[]).includes(key)\n}\n\nexport function parseSkillFrontmatter(skillPath: string): SkillInfo | null {\n if (!existsSync(skillPath))\n return null\n const content = readFileSync(skillPath, 'utf-8')\n const fm = parseFrontmatter(content)\n if (Object.keys(fm).length === 0)\n return null\n\n const info: SkillInfo = {}\n for (const key of SKILL_FM_KEYS) {\n if (fm[key])\n info[key] = fm[key]\n }\n return info\n}\n\nconst lockCache = new Map<string, SkilldLock>()\n\nexport function invalidateLockCache(skillsDir?: string): void {\n if (skillsDir)\n lockCache.delete(skillsDir)\n else\n lockCache.clear()\n}\n\nexport function readLock(skillsDir: string): SkilldLock | null {\n const cached = lockCache.get(skillsDir)\n if (cached)\n return { skills: { ...cached.skills } }\n const lockPath = lockfilePath(skillsDir)\n if (!existsSync(lockPath))\n return null\n const content = readFileSync(lockPath, 'utf-8')\n\n const skills: Record<string, SkillInfo> = {}\n let currentSkill: string | null = null\n\n for (const line of content.split('\\n')) {\n const skillMatch = line.match(/^ {2}(\\S+):$/)\n if (skillMatch) {\n currentSkill = skillMatch[1]!\n skills[currentSkill] = {}\n continue\n }\n if (currentSkill && line.startsWith(' ')) {\n const kv = yamlParseKV(line)\n if (kv && isSkillInfoKey(kv[0]))\n skills[currentSkill]![kv[0]] = kv[1]\n }\n }\n // Normalize legacy source values\n for (const info of Object.values(skills)) {\n if (info.source === 'npm')\n info.source = 'registry'\n }\n const lock = { skills }\n lockCache.set(skillsDir, lock)\n return { skills: { ...lock.skills } }\n}\n\n/**\n * Find the skill dir that tracks `packageName` (as primary or in `packages`),\n * or null. Single source of truth for \"is this package already synced?\".\n */\nexport function findSkillDirByPackage(lock: SkilldLock, packageName: string): string | null {\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (info.packageName === packageName)\n return dirName\n if (parsePackages(info.packages).some(p => p.name === packageName))\n return dirName\n }\n return null\n}\n\n/**\n * Every skill dir referencing `packageName`, optionally excluding one. Used by\n * the installer to dedupe stale entries when a skill is renamed or merged.\n */\nexport function findSkillDirsByPackage(lock: SkilldLock, packageName: string, excludeDir?: string): string[] {\n const out: string[] = []\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (dirName === excludeDir)\n continue\n if (info.packageName === packageName || parsePackages(info.packages).some(p => p.name === packageName))\n out.push(dirName)\n }\n return out\n}\n\n/**\n * Build a `packageName → skillDir` map covering both primary names and\n * additional packages tracked in `packages`. Used for related-skill lookup.\n */\nexport function buildPackageDirMap(lock: SkilldLock): Map<string, string> {\n const map = new Map<string, string>()\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (info.packageName)\n map.set(info.packageName, dirName)\n for (const pkg of parsePackages(info.packages))\n map.set(pkg.name, dirName)\n }\n return map\n}\n\nfunction serializeLock(lock: SkilldLock): string {\n let yaml = 'skills:\\n'\n for (const [name, skill] of Object.entries(lock.skills)) {\n yaml += ` ${name}:\\n`\n for (const key of SKILL_FM_KEYS) {\n if (skill[key])\n yaml += ` ${key}: ${yamlEscape(skill[key])}\\n`\n }\n }\n return yaml\n}\n\nexport function writeLock(skillsDir: string, skillName: string, info: SkillInfo): void {\n const lockPath = lockfilePath(skillsDir)\n let lock: SkilldLock = { skills: {} }\n if (existsSync(lockPath)) {\n lock = readLock(skillsDir) || { skills: {} }\n }\n\n const existing = lock.skills[skillName]\n if (existing && info.packageName) {\n // Merge packages list\n const existingPkgs = parsePackages(existing.packages)\n // Also include existing primary if not yet in packages list\n if (existing.packageName && !existingPkgs.some(p => p.name === existing.packageName)) {\n existingPkgs.unshift({ name: existing.packageName, version: existing.version || '' })\n }\n // Add/update new package\n const idx = existingPkgs.findIndex(p => p.name === info.packageName)\n if (idx >= 0) {\n existingPkgs[idx]!.version = info.version || ''\n }\n else {\n existingPkgs.push({ name: info.packageName, version: info.version || '' })\n }\n info.packages = serializePackages(existingPkgs)\n // Keep primary as first package\n info.packageName = existingPkgs[0]!.name\n info.version = existingPkgs[0]!.version\n // Preserve fields from existing entry that aren't in new info\n if (!info.repo && existing.repo)\n info.repo = existing.repo\n if (!info.source && existing.source)\n info.source = existing.source\n if (!info.generator && existing.generator)\n info.generator = existing.generator\n }\n\n lock.skills[skillName] = info\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n\n/**\n * Merge multiple lockfiles, preferring the most recently synced entry per skill.\n */\nexport function mergeLocks(locks: SkilldLock[]): SkilldLock {\n const merged: Record<string, SkillInfo> = {}\n for (const lock of locks) {\n for (const [name, info] of Object.entries(lock.skills)) {\n const existing = merged[name]\n if (!existing || (info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)))\n merged[name] = info\n }\n }\n return { skills: merged }\n}\n\n/**\n * Sync a lockfile to all other dirs that already have a skilld-lock.yaml.\n * Only updates existing lockfiles — does not create new ones.\n */\nexport function syncLockfilesToDirs(sourceLock: SkilldLock, dirs: string[]): void {\n for (const dir of dirs) {\n const lockPath = lockfilePath(dir)\n if (!existsSync(lockPath))\n continue\n const existing = readLock(dir)\n if (!existing)\n continue\n // Merge source into existing\n const merged = mergeLocks([existing, sourceLock])\n writeFileSync(lockPath, serializeLock(merged))\n invalidateLockCache(dir)\n }\n}\n\nexport function removeLockEntry(skillsDir: string, skillName: string): void {\n const lockPath = lockfilePath(skillsDir)\n const lock = readLock(skillsDir)\n if (!lock)\n return\n\n delete lock.skills[skillName]\n\n if (Object.keys(lock.skills).length === 0) {\n unlinkSync(lockPath)\n invalidateLockCache(skillsDir)\n return\n }\n\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n"],"mappings":";;;;AAsBA,SAAgB,cAAc,UAA6D;CACzF,IAAI,CAAC,UACH,OAAO,EAAE;CACX,OAAO,SAAS,MAAM,IAAI,CAAC,KAAK,MAAM;EACpC,MAAM,UAAU,EAAE,MAAM;EACxB,MAAM,QAAQ,QAAQ,YAAY,IAAI;EACtC,IAAI,SAAS,GACX,OAAO;GAAE,MAAM;GAAS,SAAS;GAAI;EACvC,OAAO;GAAE,MAAM,QAAQ,MAAM,GAAG,MAAM;GAAE,SAAS,QAAQ,MAAM,QAAQ,EAAE;GAAE;GAC3E,CAAC,QAAO,MAAK,EAAE,KAAK;;AAGxB,SAAgB,kBAAkB,UAA4C;CAC5E,OAAO,cAAc,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE;;AAG9D,SAAgB,kBAAkB,MAAwD;CACxF,OAAO,KAAK,KAAI,MAAK,GAAG,EAAE,KAAK,GAAG,EAAE,UAAU,CAAC,KAAK,KAAK;;AAO3D,MAAM,gBAAqC;CAAC;CAAe;CAAW;CAAY;CAAQ;CAAU;CAAY;CAAa;CAAQ;CAAO;CAAS;AAErJ,SAAS,eAAe,KAAqC;CAC3D,OAAQ,cAAoC,SAAS,IAAI;;AAG3D,SAAgB,sBAAsB,WAAqC;CACzE,IAAI,CAAC,WAAW,UAAU,EACxB,OAAO;CAET,MAAM,KAAK,iBADK,aAAa,WAAW,QACL,CAAC;CACpC,IAAI,OAAO,KAAK,GAAG,CAAC,WAAW,GAC7B,OAAO;CAET,MAAM,OAAkB,EAAE;CAC1B,KAAK,MAAM,OAAO,eAChB,IAAI,GAAG,MACL,KAAK,OAAO,GAAG;CAEnB,OAAO;;AAGT,MAAM,4BAAY,IAAI,KAAyB;AAE/C,SAAgB,oBAAoB,WAA0B;CAC5D,IAAI,WACF,UAAU,OAAO,UAAU;MAE3B,UAAU,OAAO;;AAGrB,SAAgB,SAAS,WAAsC;CAC7D,MAAM,SAAS,UAAU,IAAI,UAAU;CACvC,IAAI,QACF,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,QAAQ,EAAE;CACzC,MAAM,WAAW,aAAa,UAAU;CACxC,IAAI,CAAC,WAAW,SAAS,EACvB,OAAO;CACT,MAAM,UAAU,aAAa,UAAU,QAAQ;CAE/C,MAAM,SAAoC,EAAE;CAC5C,IAAI,eAA8B;CAElC,KAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,EAAE;EACtC,MAAM,aAAa,KAAK,MAAM,eAAe;EAC7C,IAAI,YAAY;GACd,eAAe,WAAW;GAC1B,OAAO,gBAAgB,EAAE;GACzB;;EAEF,IAAI,gBAAgB,KAAK,WAAW,OAAO,EAAE;GAC3C,MAAM,KAAK,YAAY,KAAK;GAC5B,IAAI,MAAM,eAAe,GAAG,GAAG,EAC7B,OAAO,cAAe,GAAG,MAAM,GAAG;;;CAIxC,KAAK,MAAM,QAAQ,OAAO,OAAO,OAAO,EACtC,IAAI,KAAK,WAAW,OAClB,KAAK,SAAS;CAElB,MAAM,OAAO,EAAE,QAAQ;CACvB,UAAU,IAAI,WAAW,KAAK;CAC9B,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,QAAQ,EAAE;;;;;EAOvC,IAAA,cAAgB,KAAA,SAAsB,CAAA,MAAkB,MAAA,EAAA,SAAoC,YAAA,EAAA,OAAA;;QAEpF;;SAKC,uBAAA,MAAA,aAAA,YAAA;;;;;;CAOT,OAAA;;SAGQ,mBAAY,MACd;OACE,sBAAqB,IAAA,KAAA;;EAG3B,IAAA,KAAO,aAAA,IAAA,IAAA,KAAA,aAAA,QAAA;;;;;;CAOT,IAAA,OAAgB;CACd,KAAA,MAAM,CAAA,MAAA,UAAM,OAAI,QAAqB,KAAA,OAAA,EAAA;EACrC,QAAK,KAAO,KAAA;EACV,KAAI,MAAK,OAAA,eACC,IAAK,MAAA,MAAa,QAAQ,OAAA,IAAA,IAAA,WAAA,MAAA,KAAA,CAAA;;;;;CAOxC,MAAA,WAAS,aAAwC,UAAA;CAC/C,IAAI,OAAO,EAAA,QAAA,EAAA,EAAA;CACX,IAAA,WAAY,SAAM,EAAA,OAAU,SAAO,UAAa,IAAO,EAAE,QAAA,EAAA,EAAA;OACvD,WAAa,KAAK,OAAA;KAClB,YAAW,KAAO,aAChB;;EAIJ,IAAA,SAAO,eAAA,CAAA,aAAA,MAAA,MAAA,EAAA,SAAA,SAAA,YAAA,EAAA,aAAA,QAAA;;GAGT,SAAgB,SAAU,WAAmB;GAC3C,CAAA;EACA,MAAI,MAAqB,aAAY,WAAA,MAAA,EAAA,SAAA,KAAA,YAAA;EACrC,IAAI,OAAA,GAAW,aACb,KAAO,UAAS,KAAA,WAAgB;OAG5B,aAAW,KAAK;GACtB,MAAI,KAAA;GAEF,SAAM,KAAA,WAAe;GAErB,CAAA;OACyB,WAAe,kBAAA,aAAA;OAAa,cAAkB,aAAW,GAAA;OAAK,UAAA,aAAA,GAAA;EAGvF,IAAA,CAAA,KAAM,QAAM,SAAa,MAAA,KAAU,OAAO,SAAS;EACnD,IAAI,CAAA,KAAA,UACF,SAAa,QAAM,KAAU,SAAK,SAAW;OAG7C,KAAA,aAAkB,SAAA,WAAA,KAAA,YAAA,SAAA;;MAA0B,OAAS,aAAK;eAAgB,UAAA,cAAA,KAAA,CAAA;qBAE5D,UAAA;;SAKX,WAAa,OAAA;OAEb,SAAK,EAAA;MAEN,MAAM,QAAA,OAAa,KAAA,MAAS,CAAA,MAC9B,SAAK,OAAA,QAAY,KAAS,OAAA,EAAA;;EAG9B,IAAK,CAAA,YAAO,KAAA,aAAa,CAAA,SAAA,YAAA,KAAA,WAAA,SAAA,WAAA,OAAA,QAAA;;CAEzB,OAAA,EAAA,QAAA,QAAoB;;;;EAMtB,MAAA,WAAgB,aAA4C,IAAA;EAC1D,IAAA,CAAM,WAAsC,SAAA,EAAA;EAC5C,MAAK,WAAM,SACT,IAAK;EACH,IAAA,CAAA,UAAM;EACN,cAAK,UAAkB,cAAc,WAAS,CAAA,UAAY,WAAK,CAAA,CAAW,CAAA;;;;;;;;CAWhF,OAAA,KAAgB,OAAA;CACd,IAAA,OAAW,KAAA,KAAO,OAAM,CAAA,WAAA,GAAA;EACtB,WAAM,SAAW;EACjB,oBAAgB,UACd;EACF;;eAKA,UAAc,cAAU,KADT,CAAA;qBAEf,UAAwB;;AAI5B,SAAgB,qBAAgB,GAAA,YAAmB,GAAyB,aAAA,GAAA,cAAA,GAAA,mBAAA,GAAA,yBAAA,GAAA,iBAAA,GAAA,0BAAA,GAAA,yBAAA,GAAA,sBAAA,GAAA,uBAAA"}
|
|
1
|
+
{"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/core/lockfile.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { parseFrontmatter } from './markdown.ts'\nimport { lockfilePath } from './paths.ts'\nimport { yamlEscape, yamlParseKV } from './yaml.ts'\n\nconst STATIC_REGEX_1 = /^ {2}(\\S+):$/\n\nexport interface SkillInfo {\n packageName?: string\n version?: string\n /** All tracked packages as comma-separated \"name@version\" pairs (multi-package skills) */\n packages?: string\n repo?: string\n source?: string\n syncedAt?: string\n generator?: string\n /** Skill path within repo (git-sourced skills) */\n path?: string\n /** Git ref tracked for updates */\n ref?: string\n /** Git commit SHA at install time */\n commit?: string\n}\n\nexport function parsePackages(packages?: string): Array<{ name: string, version: string }> {\n if (!packages)\n return []\n return packages.split(',').map((s) => {\n const trimmed = s.trim()\n const atIdx = trimmed.lastIndexOf('@')\n if (atIdx <= 0)\n return { name: trimmed, version: '' }\n return { name: trimmed.slice(0, atIdx), version: trimmed.slice(atIdx + 1) }\n }).filter(p => p.name)\n}\n\nexport function parsePackageNames(packages?: string): Array<{ name: string }> {\n return parsePackages(packages).map(({ name }) => ({ name }))\n}\n\nexport function serializePackages(pkgs: Array<{ name: string, version: string }>): string {\n return pkgs.map(p => `${p.name}@${p.version}`).join(', ')\n}\n\nexport interface SkilldLock {\n skills: Record<string, SkillInfo>\n}\n\nconst SKILL_FM_KEYS: (keyof SkillInfo)[] = ['packageName', 'version', 'packages', 'repo', 'source', 'syncedAt', 'generator', 'path', 'ref', 'commit']\n\nfunction isSkillInfoKey(key: string): key is keyof SkillInfo {\n return (SKILL_FM_KEYS as readonly string[]).includes(key)\n}\n\nexport function parseSkillFrontmatter(skillPath: string): SkillInfo | null {\n if (!existsSync(skillPath))\n return null\n const content = readFileSync(skillPath, 'utf-8')\n const fm = parseFrontmatter(content)\n if (Object.keys(fm).length === 0)\n return null\n\n const info: SkillInfo = {}\n for (const key of SKILL_FM_KEYS) {\n if (fm[key])\n info[key] = fm[key]\n }\n return info\n}\n\nconst lockCache = new Map<string, SkilldLock>()\n\nexport function invalidateLockCache(skillsDir?: string): void {\n if (skillsDir)\n lockCache.delete(skillsDir)\n else\n lockCache.clear()\n}\n\nexport function readLock(skillsDir: string): SkilldLock | null {\n const cached = lockCache.get(skillsDir)\n if (cached)\n return { skills: { ...cached.skills } }\n const lockPath = lockfilePath(skillsDir)\n if (!existsSync(lockPath))\n return null\n const content = readFileSync(lockPath, 'utf-8')\n\n const skills: Record<string, SkillInfo> = {}\n let currentSkill: string | null = null\n\n for (const line of content.split('\\n')) {\n const skillMatch = line.match(STATIC_REGEX_1)\n if (skillMatch) {\n currentSkill = skillMatch[1]!\n skills[currentSkill] = {}\n continue\n }\n if (currentSkill && line.startsWith(' ')) {\n const kv = yamlParseKV(line)\n if (kv && isSkillInfoKey(kv[0]))\n skills[currentSkill]![kv[0]] = kv[1]\n }\n }\n // Normalize legacy source values\n for (const info of Object.values(skills)) {\n if (info.source === 'npm')\n info.source = 'registry'\n }\n const lock = { skills }\n lockCache.set(skillsDir, lock)\n return { skills: { ...lock.skills } }\n}\n\n/**\n * Find the skill dir that tracks `packageName` (as primary or in `packages`),\n * or null. Single source of truth for \"is this package already synced?\".\n */\nexport function findSkillDirByPackage(lock: SkilldLock, packageName: string): string | null {\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (info.packageName === packageName)\n return dirName\n if (parsePackages(info.packages).some(p => p.name === packageName))\n return dirName\n }\n return null\n}\n\n/**\n * Every skill dir referencing `packageName`, optionally excluding one. Used by\n * the installer to dedupe stale entries when a skill is renamed or merged.\n */\nexport function findSkillDirsByPackage(lock: SkilldLock, packageName: string, excludeDir?: string): string[] {\n const out: string[] = []\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (dirName === excludeDir)\n continue\n if (info.packageName === packageName || parsePackages(info.packages).some(p => p.name === packageName))\n out.push(dirName)\n }\n return out\n}\n\n/**\n * Build a `packageName → skillDir` map covering both primary names and\n * additional packages tracked in `packages`. Used for related-skill lookup.\n */\nexport function buildPackageDirMap(lock: SkilldLock): Map<string, string> {\n const map = new Map<string, string>()\n for (const [dirName, info] of Object.entries(lock.skills)) {\n if (info.packageName)\n map.set(info.packageName, dirName)\n for (const pkg of parsePackages(info.packages))\n map.set(pkg.name, dirName)\n }\n return map\n}\n\nfunction serializeLock(lock: SkilldLock): string {\n let yaml = 'skills:\\n'\n for (const [name, skill] of Object.entries(lock.skills)) {\n yaml += ` ${name}:\\n`\n for (const key of SKILL_FM_KEYS) {\n if (skill[key])\n yaml += ` ${key}: ${yamlEscape(skill[key])}\\n`\n }\n }\n return yaml\n}\n\nexport function writeLock(skillsDir: string, skillName: string, info: SkillInfo): void {\n const lockPath = lockfilePath(skillsDir)\n let lock: SkilldLock = { skills: {} }\n if (existsSync(lockPath)) {\n lock = readLock(skillsDir) || { skills: {} }\n }\n\n const existing = lock.skills[skillName]\n if (existing && info.packageName) {\n // Merge packages list\n const existingPkgs = parsePackages(existing.packages)\n // Also include existing primary if not yet in packages list\n if (existing.packageName && !existingPkgs.some(p => p.name === existing.packageName)) {\n existingPkgs.unshift({ name: existing.packageName, version: existing.version || '' })\n }\n // Add/update new package\n const idx = existingPkgs.findIndex(p => p.name === info.packageName)\n if (idx >= 0) {\n existingPkgs[idx]!.version = info.version || ''\n }\n else {\n existingPkgs.push({ name: info.packageName, version: info.version || '' })\n }\n info.packages = serializePackages(existingPkgs)\n // Keep primary as first package\n info.packageName = existingPkgs[0]!.name\n info.version = existingPkgs[0]!.version\n // Preserve fields from existing entry that aren't in new info\n if (!info.repo && existing.repo)\n info.repo = existing.repo\n if (!info.source && existing.source)\n info.source = existing.source\n if (!info.generator && existing.generator)\n info.generator = existing.generator\n }\n\n lock.skills[skillName] = info\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n\n/**\n * Merge multiple lockfiles, preferring the most recently synced entry per skill.\n */\nexport function mergeLocks(locks: SkilldLock[]): SkilldLock {\n const merged: Record<string, SkillInfo> = {}\n for (const lock of locks) {\n for (const [name, info] of Object.entries(lock.skills)) {\n const existing = merged[name]\n if (!existing || (info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)))\n merged[name] = info\n }\n }\n return { skills: merged }\n}\n\n/**\n * Sync a lockfile to all other dirs that already have a skilld-lock.yaml.\n * Only updates existing lockfiles — does not create new ones.\n */\nexport function syncLockfilesToDirs(sourceLock: SkilldLock, dirs: string[]): void {\n for (const dir of dirs) {\n const lockPath = lockfilePath(dir)\n if (!existsSync(lockPath))\n continue\n const existing = readLock(dir)\n if (!existing)\n continue\n // Merge source into existing\n const merged = mergeLocks([existing, sourceLock])\n writeFileSync(lockPath, serializeLock(merged))\n invalidateLockCache(dir)\n }\n}\n\nexport function removeLockEntry(skillsDir: string, skillName: string): void {\n const lockPath = lockfilePath(skillsDir)\n const lock = readLock(skillsDir)\n if (!lock)\n return\n\n delete lock.skills[skillName]\n\n if (Object.keys(lock.skills).length === 0) {\n unlinkSync(lockPath)\n invalidateLockCache(skillsDir)\n return\n }\n\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { t as version } from "./version.mjs";
|
|
2
|
+
import { i as saveSession } from "./store.mjs";
|
|
3
|
+
import { i as getRegistryBase } from "./client.mjs";
|
|
4
|
+
import { t as track } from "./telemetry.mjs";
|
|
5
|
+
import { styleText } from "node:util";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import { defineCommand } from "citty";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
10
|
+
import { ofetch } from "ofetch";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
async function runDeviceFlow(opts) {
|
|
13
|
+
const start = await ofetch(`${opts.registryBase}/cli/device/start`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
body: {
|
|
16
|
+
cli_version: opts.cliVersion,
|
|
17
|
+
machine_hint: opts.machineHint
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
opts.onUserCode({
|
|
21
|
+
userCode: start.user_code,
|
|
22
|
+
verificationUri: start.verification_uri
|
|
23
|
+
});
|
|
24
|
+
const deadline = Date.now() + start.expires_in * 1e3;
|
|
25
|
+
const interval = opts.intervalMs ?? start.interval * 1e3;
|
|
26
|
+
while (Date.now() < deadline) {
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
28
|
+
const poll = await ofetch(`${opts.registryBase}/cli/device/poll`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: { device_code: start.device_code }
|
|
31
|
+
}).catch(() => null);
|
|
32
|
+
if (!poll || poll.status === "pending") continue;
|
|
33
|
+
if (poll.status === "expired") throw new Error("Device code expired before authorization");
|
|
34
|
+
if (poll.status === "denied") throw new Error("Device authorization denied");
|
|
35
|
+
if (poll.status === "authorized" && poll.tokens) return poll.tokens;
|
|
36
|
+
}
|
|
37
|
+
throw new Error("Device authorization timed out");
|
|
38
|
+
}
|
|
39
|
+
function isGhaOidcAvailable() {
|
|
40
|
+
return !!(process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && process.env.ACTIONS_ID_TOKEN_REQUEST_URL);
|
|
41
|
+
}
|
|
42
|
+
async function runOidcExchange(opts) {
|
|
43
|
+
const token = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
44
|
+
const url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
45
|
+
if (!token || !url) throw new Error("Not running in GitHub Actions with id-token: write permission");
|
|
46
|
+
const audience = opts.audience ?? "skilld.dev";
|
|
47
|
+
const idToken = await ofetch(`${url}&audience=${encodeURIComponent(audience)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
48
|
+
return ofetch(`${opts.registryBase}/cli/oidc/exchange`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: { id_token: idToken.value }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>skilld — signed in</title>
|
|
54
|
+
<body style="font-family: ui-sans-serif, system-ui; padding: 4rem; text-align: center">
|
|
55
|
+
<h1>Signed in to skilld</h1><p>You can close this tab and return to the CLI.</p>`;
|
|
56
|
+
function ERROR_HTML(msg) {
|
|
57
|
+
return `<!doctype html><meta charset="utf-8"><title>skilld — error</title>
|
|
58
|
+
<body style="font-family: ui-sans-serif, system-ui; padding: 4rem; text-align: center; color: #b00">
|
|
59
|
+
<h1>Sign-in failed</h1><p>${msg}</p>`;
|
|
60
|
+
}
|
|
61
|
+
const PLUS_RE = /\+/g;
|
|
62
|
+
const SLASH_RE = /\//g;
|
|
63
|
+
const EQ_RE = /=+$/;
|
|
64
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
65
|
+
const TRAILING_API_RE = /\/api$/;
|
|
66
|
+
function base64url(buf) {
|
|
67
|
+
return buf.toString("base64").replace(PLUS_RE, "-").replace(SLASH_RE, "_").replace(EQ_RE, "");
|
|
68
|
+
}
|
|
69
|
+
function generateVerifier() {
|
|
70
|
+
return base64url(randomBytes(32));
|
|
71
|
+
}
|
|
72
|
+
function challengeFromVerifier(verifier) {
|
|
73
|
+
return base64url(createHash("sha256").update(verifier).digest());
|
|
74
|
+
}
|
|
75
|
+
async function runPkceFlow(opts) {
|
|
76
|
+
const verifier = generateVerifier();
|
|
77
|
+
const challenge = challengeFromVerifier(verifier);
|
|
78
|
+
const state = base64url(randomBytes(16));
|
|
79
|
+
const { port, server, gotCode } = await bindLoopback(state);
|
|
80
|
+
const verificationUrl = new URL(`${opts.registryBase.replace(TRAILING_SLASH_RE, "").replace(TRAILING_API_RE, "")}/cli/authorize`);
|
|
81
|
+
verificationUrl.searchParams.set("challenge", challenge);
|
|
82
|
+
verificationUrl.searchParams.set("port", String(port));
|
|
83
|
+
verificationUrl.searchParams.set("state", state);
|
|
84
|
+
verificationUrl.searchParams.set("v", opts.cliVersion);
|
|
85
|
+
await (opts.openBrowser ?? defaultOpenBrowser)(verificationUrl.toString());
|
|
86
|
+
try {
|
|
87
|
+
const code = await Promise.race([gotCode, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("PKCE flow timed out")), opts.timeoutMs ?? 5 * 6e4))]);
|
|
88
|
+
return await ofetch(`${opts.registryBase}/cli/oauth/token`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: {
|
|
91
|
+
code,
|
|
92
|
+
code_verifier: verifier,
|
|
93
|
+
redirect_uri: `http://127.0.0.1:${port}/`
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
} finally {
|
|
97
|
+
server.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function bindLoopback(expectedState) {
|
|
101
|
+
let resolveCode;
|
|
102
|
+
let rejectCode;
|
|
103
|
+
const gotCode = new Promise((res, rej) => {
|
|
104
|
+
resolveCode = res;
|
|
105
|
+
rejectCode = rej;
|
|
106
|
+
});
|
|
107
|
+
const handler = (req, res) => {
|
|
108
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
109
|
+
const code = url.searchParams.get("code");
|
|
110
|
+
const state = url.searchParams.get("state");
|
|
111
|
+
if (!code || state !== expectedState) {
|
|
112
|
+
res.writeHead(400, { "content-type": "text/html" }).end(ERROR_HTML("Missing or invalid state parameter."));
|
|
113
|
+
rejectCode(/* @__PURE__ */ new Error("PKCE callback missing code or state mismatch"));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
res.writeHead(200, { "content-type": "text/html" }).end(SUCCESS_HTML);
|
|
117
|
+
resolveCode(code);
|
|
118
|
+
};
|
|
119
|
+
const v4 = createServer(handler);
|
|
120
|
+
const v6 = createServer(handler);
|
|
121
|
+
await new Promise((resolve, reject) => {
|
|
122
|
+
v4.once("error", reject).listen(0, "127.0.0.1", () => resolve());
|
|
123
|
+
});
|
|
124
|
+
const port = v4.address().port;
|
|
125
|
+
await new Promise((resolve) => {
|
|
126
|
+
v6.once("error", () => resolve()).listen(port, "::1", () => resolve());
|
|
127
|
+
});
|
|
128
|
+
const close = () => {
|
|
129
|
+
v4.closeAllConnections();
|
|
130
|
+
v6.closeAllConnections();
|
|
131
|
+
v4.close();
|
|
132
|
+
v6.close();
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
port,
|
|
136
|
+
server: { close },
|
|
137
|
+
gotCode
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function defaultOpenBrowser(url) {
|
|
141
|
+
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open", [url], {
|
|
142
|
+
stdio: "ignore",
|
|
143
|
+
detached: true
|
|
144
|
+
}).unref();
|
|
145
|
+
}
|
|
146
|
+
function shouldUseDevice(force) {
|
|
147
|
+
if (force) return true;
|
|
148
|
+
if (process.env.BROWSER) return false;
|
|
149
|
+
if (process.platform === "darwin" || process.platform === "win32") return false;
|
|
150
|
+
return !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
|
|
151
|
+
}
|
|
152
|
+
const loginCommandDef = defineCommand({
|
|
153
|
+
meta: {
|
|
154
|
+
name: "login",
|
|
155
|
+
description: "Authenticate with skilld.dev"
|
|
156
|
+
},
|
|
157
|
+
args: { device: {
|
|
158
|
+
type: "boolean",
|
|
159
|
+
description: "Use RFC 8628 device flow"
|
|
160
|
+
} },
|
|
161
|
+
async run({ args }) {
|
|
162
|
+
const registryBase = getRegistryBase();
|
|
163
|
+
if (isGhaOidcAvailable()) {
|
|
164
|
+
const spin = p.spinner();
|
|
165
|
+
spin.start("Exchanging GitHub Actions OIDC token");
|
|
166
|
+
const tokens = await runOidcExchange({ registryBase });
|
|
167
|
+
spin.stop(`Authenticated as @${tokens.login} (oidc)`);
|
|
168
|
+
await saveSession({
|
|
169
|
+
login: tokens.login,
|
|
170
|
+
accessToken: tokens.accessToken,
|
|
171
|
+
expiresAt: tokens.expiresAt,
|
|
172
|
+
tokens: { accessToken: tokens.accessToken }
|
|
173
|
+
});
|
|
174
|
+
track({
|
|
175
|
+
event: "auth-flow",
|
|
176
|
+
surface: "cli:auth",
|
|
177
|
+
flow: "oidc"
|
|
178
|
+
});
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
|
181
|
+
if (shouldUseDevice(!!args.device)) {
|
|
182
|
+
const tokens = await runDeviceFlow({
|
|
183
|
+
registryBase,
|
|
184
|
+
cliVersion: version,
|
|
185
|
+
onUserCode: ({ userCode, verificationUri }) => {
|
|
186
|
+
p.log.info(`Visit ${styleText("cyan", verificationUri)} and enter ${styleText("bold", userCode)}`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
await saveSession({
|
|
190
|
+
login: tokens.login,
|
|
191
|
+
accessToken: tokens.accessToken,
|
|
192
|
+
refreshToken: tokens.refreshToken,
|
|
193
|
+
expiresAt: tokens.expiresAt,
|
|
194
|
+
tokens: {
|
|
195
|
+
accessToken: tokens.accessToken,
|
|
196
|
+
refreshToken: tokens.refreshToken
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
track({
|
|
200
|
+
event: "auth-flow",
|
|
201
|
+
surface: "cli:auth",
|
|
202
|
+
flow: "device"
|
|
203
|
+
});
|
|
204
|
+
p.log.success(`Logged in as @${tokens.login}`);
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
p.log.info("Opening browser to authenticate…");
|
|
208
|
+
const tokens = await runPkceFlow({
|
|
209
|
+
registryBase,
|
|
210
|
+
cliVersion: version
|
|
211
|
+
});
|
|
212
|
+
await saveSession({
|
|
213
|
+
login: tokens.login,
|
|
214
|
+
accessToken: tokens.accessToken,
|
|
215
|
+
refreshToken: tokens.refreshToken,
|
|
216
|
+
expiresAt: tokens.expiresAt,
|
|
217
|
+
tokens: {
|
|
218
|
+
accessToken: tokens.accessToken,
|
|
219
|
+
refreshToken: tokens.refreshToken
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
track({
|
|
223
|
+
event: "auth-flow",
|
|
224
|
+
surface: "cli:auth",
|
|
225
|
+
flow: "pkce"
|
|
226
|
+
});
|
|
227
|
+
p.log.success(`Logged in as @${tokens.login}`);
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
export { loginCommandDef };
|
|
232
|
+
|
|
233
|
+
//# sourceMappingURL=login.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.mjs","names":[],"sources":["../../src/auth/device-flow.ts","../../src/auth/oidc.ts","../../src/auth/pkce-flow.ts","../../src/commands/login.ts"],"sourcesContent":["/**\n * RFC 8628 device flow. Used when --device is passed, no browser is detected,\n * or PKCE bind fails.\n */\n\nimport type { DevicePollResponse, DeviceStartResponse, TokenResponse } from 'skilld-protocol/wire'\nimport { ofetch } from 'ofetch'\n\nexport type { DeviceStartResponse }\n\nexport interface DeviceFlowOptions {\n registryBase: string\n cliVersion: string\n machineHint?: string\n /** Hook called once the user_code is known so the CLI can prompt the user. */\n onUserCode: (info: { userCode: string, verificationUri: string }) => void\n /** Override polling interval for tests. */\n intervalMs?: number\n}\n\nexport async function runDeviceFlow(opts: DeviceFlowOptions): Promise<TokenResponse> {\n const start = await ofetch<DeviceStartResponse>(`${opts.registryBase}/cli/device/start`, {\n method: 'POST',\n body: { cli_version: opts.cliVersion, machine_hint: opts.machineHint },\n })\n\n opts.onUserCode({ userCode: start.user_code, verificationUri: start.verification_uri })\n\n const deadline = Date.now() + start.expires_in * 1000\n const interval = opts.intervalMs ?? start.interval * 1000\n\n while (Date.now() < deadline) {\n await new Promise(resolve => setTimeout(resolve, interval))\n const poll = await ofetch<DevicePollResponse>(`${opts.registryBase}/cli/device/poll`, {\n method: 'POST',\n body: { device_code: start.device_code },\n }).catch(() => null)\n\n if (!poll || poll.status === 'pending')\n continue\n if (poll.status === 'expired')\n throw new Error('Device code expired before authorization')\n if (poll.status === 'denied')\n throw new Error('Device authorization denied')\n if (poll.status === 'authorized' && poll.tokens)\n return poll.tokens\n }\n\n throw new Error('Device authorization timed out')\n}\n","/**\n * GitHub Actions OIDC exchange. Auto-detected via `ACTIONS_ID_TOKEN_REQUEST_TOKEN`;\n * fetches a short-lived JWT against `audience=skilld.dev` and trades it for a\n * session token. No browser, no prompt, no refresh.\n */\n\nimport type { TokenResponse } from './types.ts'\nimport { ofetch } from 'ofetch'\n\ninterface GhaOidcResponse {\n value: string\n count?: number\n}\n\nexport function isGhaOidcAvailable(): boolean {\n return !!(process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && process.env.ACTIONS_ID_TOKEN_REQUEST_URL)\n}\n\nexport async function runOidcExchange(opts: { registryBase: string, audience?: string }): Promise<TokenResponse> {\n const token = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN\n const url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL\n if (!token || !url)\n throw new Error('Not running in GitHub Actions with id-token: write permission')\n\n const audience = opts.audience ?? 'skilld.dev'\n const idToken = await ofetch<GhaOidcResponse>(`${url}&audience=${encodeURIComponent(audience)}`, {\n headers: { Authorization: `Bearer ${token}` },\n })\n\n return ofetch<TokenResponse>(`${opts.registryBase}/cli/oidc/exchange`, {\n method: 'POST',\n body: { id_token: idToken.value },\n })\n}\n","/**\n * RFC 7636 PKCE loopback flow.\n *\n * Binds `127.0.0.1:<port>` and `[::1]:<port>` simultaneously so the browser\n * can hit either; opens the system browser to the verification URL; serves a\n * single GET callback that captures the auth code, then exchanges it for\n * tokens against `/api/cli/oauth/token`.\n */\n\nimport type { AddressInfo } from 'node:net'\nimport type { TokenResponse } from './types.ts'\nimport { spawn } from 'node:child_process'\nimport { createHash, randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport { ofetch } from 'ofetch'\n\nconst SUCCESS_HTML = `<!doctype html><meta charset=\"utf-8\"><title>skilld — signed in</title>\n<body style=\"font-family: ui-sans-serif, system-ui; padding: 4rem; text-align: center\">\n<h1>Signed in to skilld</h1><p>You can close this tab and return to the CLI.</p>`\n\nfunction ERROR_HTML(msg: string) {\n return `<!doctype html><meta charset=\"utf-8\"><title>skilld — error</title>\n<body style=\"font-family: ui-sans-serif, system-ui; padding: 4rem; text-align: center; color: #b00\">\n<h1>Sign-in failed</h1><p>${msg}</p>`\n}\n\nexport interface PkceFlowOptions {\n registryBase: string\n cliVersion: string\n openBrowser?: (url: string) => Promise<void> | void\n timeoutMs?: number\n}\n\nconst PLUS_RE = /\\+/g\nconst SLASH_RE = /\\//g\nconst EQ_RE = /=+$/\nconst TRAILING_SLASH_RE = /\\/$/\nconst TRAILING_API_RE = /\\/api$/\n\nfunction base64url(buf: Buffer): string {\n return buf.toString('base64').replace(PLUS_RE, '-').replace(SLASH_RE, '_').replace(EQ_RE, '')\n}\n\nfunction generateVerifier(): string {\n return base64url(randomBytes(32))\n}\n\nfunction challengeFromVerifier(verifier: string): string {\n return base64url(createHash('sha256').update(verifier).digest())\n}\n\nexport async function runPkceFlow(opts: PkceFlowOptions): Promise<TokenResponse> {\n const verifier = generateVerifier()\n const challenge = challengeFromVerifier(verifier)\n const state = base64url(randomBytes(16))\n\n const { port, server, gotCode } = await bindLoopback(state)\n const verificationUrl = new URL(`${opts.registryBase.replace(TRAILING_SLASH_RE, '').replace(TRAILING_API_RE, '')}/cli/authorize`)\n verificationUrl.searchParams.set('challenge', challenge)\n verificationUrl.searchParams.set('port', String(port))\n verificationUrl.searchParams.set('state', state)\n verificationUrl.searchParams.set('v', opts.cliVersion)\n\n await (opts.openBrowser ?? defaultOpenBrowser)(verificationUrl.toString())\n\n try {\n const code = await Promise.race([\n gotCode,\n new Promise<never>((_, reject) => setTimeout(() => reject(new Error('PKCE flow timed out')), opts.timeoutMs ?? 5 * 60_000)),\n ])\n\n return await ofetch<TokenResponse>(`${opts.registryBase}/cli/oauth/token`, {\n method: 'POST',\n body: { code, code_verifier: verifier, redirect_uri: `http://127.0.0.1:${port}/` },\n })\n }\n finally {\n server.close()\n }\n}\n\ninterface LoopbackBinding {\n port: number\n server: { close: () => void }\n gotCode: Promise<string>\n}\n\nasync function bindLoopback(expectedState: string): Promise<LoopbackBinding> {\n let resolveCode!: (code: string) => void\n let rejectCode!: (err: Error) => void\n const gotCode = new Promise<string>((res, rej) => {\n resolveCode = res\n rejectCode = rej\n })\n\n const handler = (req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse): void => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n if (!code || state !== expectedState) {\n res.writeHead(400, { 'content-type': 'text/html' }).end(ERROR_HTML('Missing or invalid state parameter.'))\n rejectCode(new Error('PKCE callback missing code or state mismatch'))\n return\n }\n res.writeHead(200, { 'content-type': 'text/html' }).end(SUCCESS_HTML)\n resolveCode(code)\n }\n\n const v4 = createServer(handler)\n const v6 = createServer(handler)\n\n await new Promise<void>((resolve, reject) => {\n v4.once('error', reject).listen(0, '127.0.0.1', () => resolve())\n })\n const port = (v4.address() as AddressInfo).port\n await new Promise<void>((resolve) => {\n v6.once('error', () => resolve()).listen(port, '::1', () => resolve())\n })\n\n const close = (): void => {\n // closeAllConnections() forces still-open keep-alive sockets shut so the\n // process can exit promptly after the browser hits the success page.\n v4.closeAllConnections()\n v6.closeAllConnections()\n v4.close()\n v6.close()\n }\n\n return {\n port,\n server: { close },\n gotCode,\n }\n}\n\nfunction defaultOpenBrowser(url: string): void {\n const cmd = process.platform === 'darwin'\n ? 'open'\n : process.platform === 'win32'\n ? 'start'\n : 'xdg-open'\n spawn(cmd, [url], { stdio: 'ignore', detached: true }).unref()\n}\n","/**\n * `skilld login` — authenticate with skilld.dev.\n *\n * Picks a flow based on env:\n * - `ACTIONS_ID_TOKEN_REQUEST_TOKEN` set → GHA OIDC exchange.\n * - `--device` or no `DISPLAY`/`BROWSER` env → RFC 8628 device flow.\n * - Otherwise → PKCE loopback.\n */\n\nimport { styleText } from 'node:util'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { runDeviceFlow } from '../auth/device-flow.ts'\nimport { isGhaOidcAvailable, runOidcExchange } from '../auth/oidc.ts'\nimport { runPkceFlow } from '../auth/pkce-flow.ts'\nimport { saveSession } from '../auth/store.ts'\nimport { getRegistryBase } from '../registry/client.ts'\nimport { track } from '../telemetry.ts'\nimport { version } from '../version.ts'\n\nfunction shouldUseDevice(force: boolean): boolean {\n if (force)\n return true\n if (process.env.BROWSER)\n return false\n if (process.platform === 'darwin' || process.platform === 'win32')\n return false\n return !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY\n}\n\nexport const loginCommandDef = defineCommand({\n meta: { name: 'login', description: 'Authenticate with skilld.dev' },\n args: {\n device: { type: 'boolean', description: 'Use RFC 8628 device flow' },\n },\n async run({ args }) {\n const registryBase = getRegistryBase()\n\n if (isGhaOidcAvailable()) {\n const spin = p.spinner()\n spin.start('Exchanging GitHub Actions OIDC token')\n const tokens = await runOidcExchange({ registryBase })\n spin.stop(`Authenticated as @${tokens.login} (oidc)`)\n await saveSession({\n login: tokens.login,\n accessToken: tokens.accessToken,\n expiresAt: tokens.expiresAt,\n tokens: { accessToken: tokens.accessToken },\n })\n track({ event: 'auth-flow', surface: 'cli:auth', flow: 'oidc' })\n process.exit(0)\n }\n\n if (shouldUseDevice(!!args.device)) {\n const tokens = await runDeviceFlow({\n registryBase,\n cliVersion: version,\n onUserCode: ({ userCode, verificationUri }) => {\n p.log.info(`Visit ${styleText('cyan', verificationUri)} and enter ${styleText('bold', userCode)}`)\n },\n })\n await saveSession({\n login: tokens.login,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n tokens: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken },\n })\n track({ event: 'auth-flow', surface: 'cli:auth', flow: 'device' })\n p.log.success(`Logged in as @${tokens.login}`)\n process.exit(0)\n }\n\n p.log.info('Opening browser to authenticate…')\n const tokens = await runPkceFlow({ registryBase, cliVersion: version })\n await saveSession({\n login: tokens.login,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n tokens: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken },\n })\n track({ event: 'auth-flow', surface: 'cli:auth', flow: 'pkce' })\n p.log.success(`Logged in as @${tokens.login}`)\n // Node's global fetch keep-alive pool + telemetry fire-and-forget leave\n // sockets ref'd; force exit so we don't wait for the 4s idle timeout.\n process.exit(0)\n },\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { n as loadSession, t as clearSession } from "./store.mjs";
|
|
2
|
+
import { i as getRegistryBase } from "./client.mjs";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { ofetch } from "ofetch";
|
|
6
|
+
const logoutCommandDef = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "logout",
|
|
9
|
+
description: "Sign out of skilld.dev"
|
|
10
|
+
},
|
|
11
|
+
async run() {
|
|
12
|
+
const session = await loadSession();
|
|
13
|
+
if (!session) {
|
|
14
|
+
p.log.info("Not logged in.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await ofetch(`${getRegistryBase()}/cli/logout`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { Authorization: `Bearer ${session.accessToken}` }
|
|
20
|
+
}).catch(() => {});
|
|
21
|
+
await clearSession();
|
|
22
|
+
p.log.success("Logged out.");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
export { logoutCommandDef };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=logout.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.mjs","names":[],"sources":["../../src/commands/logout.ts"],"sourcesContent":["/**\n * `skilld logout` — revoke the active session server-side and clear local credentials.\n * Local state is cleared even if the server revoke fails.\n */\n\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { ofetch } from 'ofetch'\nimport { clearSession, loadSession } from '../auth/store.ts'\nimport { getRegistryBase } from '../registry/client.ts'\n\nexport const logoutCommandDef = defineCommand({\n meta: { name: 'logout', description: 'Sign out of skilld.dev' },\n async run() {\n const session = await loadSession()\n if (!session) {\n p.log.info('Not logged in.')\n return\n }\n\n await ofetch(`${getRegistryBase()}/cli/logout`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${session.accessToken}` },\n }).catch(() => {})\n\n await clearSession()\n p.log.success('Logged out.')\n },\n})\n"],"mappings":";;;;;;;;EAWA,aAAa;EACX;OAAQ,MAAM;EAAU,MAAA,UAAa,MAAA,aAAA;EAA0B,IAAA,CAAA,SAAA;GAC/D,EAAA,IAAM,KAAM,iBAAA;GACV;;QAEI,OAAS,GAAA,iBAAiB,CAAA,cAAA;GAC5B,QAAA;;GAGF,CAAA,CAAA,YAAa,GAAG;QACd,cAAQ;IACR,IAAA,QAAW,cAAe;;EAG5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.mjs","names":[],"sources":["../../src/core/map.ts"],"sourcesContent":["/** Get-or-create for Maps. Polyfill for Map.getOrInsertComputed (not yet in Node.js). */\nexport function mapInsert<K, V>(map: Map<K, V>, key: K, create: () => V): V {\n let val = map.get(key)\n if (val === undefined) {\n val = create()\n map.set(key, val)\n }\n return val\n}\n"],"mappings":"AACA,SAAgB,UAAgB,KAAgB,KAAQ,QAAoB;CAC1E,IAAI,MAAM,IAAI,IAAI,IAAI;CACtB,IAAI,QAAQ,KAAA,GAAW;EACrB,MAAM,QAAQ;EACd,IAAI,IAAI,KAAK,IAAI;;CAEnB,OAAO"}
|