@heurist-network/skills 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1554 -0
  2. package/package.json +1 -1
package/dist/cli.js ADDED
@@ -0,0 +1,1554 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/api.ts
13
+ function getBaseUrl() {
14
+ return process.env["HEURIST_SKILLS_API"] || DEFAULT_API_URL;
15
+ }
16
+ async function fetchJson(path) {
17
+ const url = `${getBaseUrl()}${path}`;
18
+ const resp = await fetch(url, {
19
+ headers: { Accept: "application/json" },
20
+ signal: AbortSignal.timeout(3e4)
21
+ });
22
+ if (!resp.ok) {
23
+ throw new Error(`API error ${resp.status}: ${resp.statusText} (${url})`);
24
+ }
25
+ return resp.json();
26
+ }
27
+ async function listSkills(opts) {
28
+ const params = new URLSearchParams();
29
+ if (opts?.category) params.set("category", opts.category);
30
+ if (opts?.search) params.set("search", opts.search);
31
+ if (opts?.offset) params.set("offset", String(opts.offset));
32
+ if (opts?.limit) params.set("limit", String(opts.limit));
33
+ const qs = params.toString();
34
+ return fetchJson(`/skills${qs ? `?${qs}` : ""}`);
35
+ }
36
+ async function getSkill(slug) {
37
+ return fetchJson(`/skills/${slug}`);
38
+ }
39
+ async function listSkillFiles(slug) {
40
+ return fetchJson(`/skills/${slug}/files`);
41
+ }
42
+ async function downloadSkill(slug) {
43
+ const url = `${getBaseUrl()}/skills/${slug}/download`;
44
+ const resp = await fetch(url, {
45
+ signal: AbortSignal.timeout(6e4)
46
+ });
47
+ if (!resp.ok) {
48
+ throw new Error(`Download failed ${resp.status}: ${resp.statusText}`);
49
+ }
50
+ const sha256 = resp.headers.get("X-Skill-SHA256") || "";
51
+ const disposition = resp.headers.get("Content-Disposition") || "";
52
+ const contentType = resp.headers.get("Content-Type") || "";
53
+ const isZip = contentType.includes("zip") || disposition.includes(".zip");
54
+ const filenameMatch = disposition.match(/filename="(.+?)"/);
55
+ const filename = filenameMatch?.[1] || `${slug}-SKILL.md`;
56
+ const arrayBuffer = await resp.arrayBuffer();
57
+ const content = Buffer.from(arrayBuffer);
58
+ return { content, sha256, isZip, filename };
59
+ }
60
+ async function checkUpdates(installed) {
61
+ const url = `${getBaseUrl()}/check-updates`;
62
+ const resp = await fetch(url, {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ installed }),
66
+ signal: AbortSignal.timeout(3e4)
67
+ });
68
+ if (!resp.ok) {
69
+ throw new Error(`Check updates failed ${resp.status}: ${resp.statusText}`);
70
+ }
71
+ const data = await resp.json();
72
+ return data.updates;
73
+ }
74
+ var DEFAULT_API_URL;
75
+ var init_api = __esm({
76
+ "src/api.ts"() {
77
+ "use strict";
78
+ DEFAULT_API_URL = "https://mesh.heurist.ai";
79
+ }
80
+ });
81
+
82
+ // src/agents.ts
83
+ import { existsSync } from "fs";
84
+ import { homedir } from "os";
85
+ import { join } from "path";
86
+ async function detectInstalledAgents() {
87
+ const installed = [];
88
+ for (const key of Object.keys(agents)) {
89
+ if (await agents[key].detectInstalled()) {
90
+ installed.push(key);
91
+ }
92
+ }
93
+ return installed;
94
+ }
95
+ function isValidAgent(value) {
96
+ return value in agents;
97
+ }
98
+ function isUniversalAgent(type) {
99
+ return agents[type].skillsDir === ".agents/skills";
100
+ }
101
+ function getUniversalAgents() {
102
+ return Object.keys(agents).filter(isUniversalAgent);
103
+ }
104
+ function getNonUniversalAgents() {
105
+ return Object.keys(agents).filter((type) => !isUniversalAgent(type));
106
+ }
107
+ var home, configHome, codexHome, claudeHome, agents;
108
+ var init_agents = __esm({
109
+ "src/agents.ts"() {
110
+ "use strict";
111
+ home = homedir();
112
+ configHome = process.env["XDG_CONFIG_HOME"]?.trim() || join(home, ".config");
113
+ codexHome = process.env["CODEX_HOME"]?.trim() || join(home, ".codex");
114
+ claudeHome = process.env["CLAUDE_CONFIG_DIR"]?.trim() || join(home, ".claude");
115
+ agents = {
116
+ "claude-code": {
117
+ name: "claude-code",
118
+ displayName: "Claude Code",
119
+ skillsDir: ".claude/skills",
120
+ globalSkillsDir: join(claudeHome, "skills"),
121
+ detectInstalled: () => existsSync(claudeHome)
122
+ },
123
+ cursor: {
124
+ name: "cursor",
125
+ displayName: "Cursor",
126
+ skillsDir: ".agents/skills",
127
+ globalSkillsDir: join(home, ".cursor/skills"),
128
+ detectInstalled: () => existsSync(join(home, ".cursor"))
129
+ },
130
+ codex: {
131
+ name: "codex",
132
+ displayName: "Codex",
133
+ skillsDir: ".agents/skills",
134
+ globalSkillsDir: join(codexHome, "skills"),
135
+ detectInstalled: () => existsSync(codexHome) || existsSync("/etc/codex")
136
+ },
137
+ opencode: {
138
+ name: "opencode",
139
+ displayName: "OpenCode",
140
+ skillsDir: ".agents/skills",
141
+ globalSkillsDir: join(configHome, "opencode/skills"),
142
+ detectInstalled: () => existsSync(join(configHome, "opencode"))
143
+ },
144
+ cline: {
145
+ name: "cline",
146
+ displayName: "Cline",
147
+ skillsDir: ".agents/skills",
148
+ globalSkillsDir: join(home, ".agents/skills"),
149
+ detectInstalled: () => existsSync(join(home, ".cline"))
150
+ },
151
+ windsurf: {
152
+ name: "windsurf",
153
+ displayName: "Windsurf",
154
+ skillsDir: ".windsurf/skills",
155
+ globalSkillsDir: join(home, ".codeium/windsurf/skills"),
156
+ detectInstalled: () => existsSync(join(home, ".codeium/windsurf"))
157
+ },
158
+ "gemini-cli": {
159
+ name: "gemini-cli",
160
+ displayName: "Gemini CLI",
161
+ skillsDir: ".agents/skills",
162
+ globalSkillsDir: join(home, ".gemini/skills"),
163
+ detectInstalled: () => existsSync(join(home, ".gemini"))
164
+ },
165
+ "github-copilot": {
166
+ name: "github-copilot",
167
+ displayName: "GitHub Copilot",
168
+ skillsDir: ".agents/skills",
169
+ globalSkillsDir: join(home, ".copilot/skills"),
170
+ detectInstalled: () => existsSync(join(home, ".copilot"))
171
+ },
172
+ roo: {
173
+ name: "roo",
174
+ displayName: "Roo Code",
175
+ skillsDir: ".roo/skills",
176
+ globalSkillsDir: join(home, ".roo/skills"),
177
+ detectInstalled: () => existsSync(join(home, ".roo"))
178
+ },
179
+ continue: {
180
+ name: "continue",
181
+ displayName: "Continue",
182
+ skillsDir: ".continue/skills",
183
+ globalSkillsDir: join(home, ".continue/skills"),
184
+ detectInstalled: () => existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"))
185
+ }
186
+ };
187
+ }
188
+ });
189
+
190
+ // src/installer.ts
191
+ import {
192
+ access,
193
+ lstat,
194
+ mkdir,
195
+ readlink,
196
+ rm,
197
+ symlink,
198
+ writeFile
199
+ } from "fs/promises";
200
+ import { homedir as homedir2, platform } from "os";
201
+ import { dirname, join as join2, normalize, relative, resolve, sep } from "path";
202
+ import { inflateRawSync } from "zlib";
203
+ function sanitizeName(name) {
204
+ const sanitized = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "");
205
+ return sanitized.substring(0, 255) || "unnamed-skill";
206
+ }
207
+ function isPathSafe(basePath, targetPath) {
208
+ const normalizedBase = normalize(resolve(basePath));
209
+ const normalizedTarget = normalize(resolve(targetPath));
210
+ return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + sep);
211
+ }
212
+ function getCanonicalSkillsDir(scope, cwd = process.cwd()) {
213
+ const baseDir = scope === "global" ? homedir2() : cwd;
214
+ return join2(baseDir, ".agents", "skills");
215
+ }
216
+ function getCanonicalPath(scope, slug, cwd = process.cwd()) {
217
+ const canonicalBase = getCanonicalSkillsDir(scope, cwd);
218
+ const canonicalPath = join2(canonicalBase, sanitizeName(slug));
219
+ if (!isPathSafe(canonicalBase, canonicalPath)) {
220
+ throw new Error("Invalid skill slug: potential path traversal detected.");
221
+ }
222
+ return canonicalPath;
223
+ }
224
+ function getAgentBaseDir(scope, agentType, cwd = process.cwd()) {
225
+ if (isUniversalAgent(agentType)) {
226
+ return getCanonicalSkillsDir(scope, cwd);
227
+ }
228
+ const agent = agents[agentType];
229
+ if (scope === "global") {
230
+ if (!agent.globalSkillsDir) {
231
+ throw new Error(`${agent.displayName} does not support global skill installation.`);
232
+ }
233
+ return agent.globalSkillsDir;
234
+ }
235
+ return join2(cwd, agent.skillsDir);
236
+ }
237
+ function getInstallPath(scope, agentType, slug, cwd = process.cwd()) {
238
+ const baseDir = getAgentBaseDir(scope, agentType, cwd);
239
+ const installPath = join2(baseDir, sanitizeName(slug));
240
+ if (!isPathSafe(baseDir, installPath)) {
241
+ throw new Error("Invalid skill slug: potential path traversal detected.");
242
+ }
243
+ return installPath;
244
+ }
245
+ async function pathExists(path) {
246
+ try {
247
+ await access(path);
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ async function removePathIfExists(path) {
254
+ try {
255
+ await rm(path, { recursive: true, force: true });
256
+ return true;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ async function cleanAndCreateDirectory(path) {
262
+ await rm(path, { recursive: true, force: true }).catch(() => void 0);
263
+ await mkdir(path, { recursive: true });
264
+ }
265
+ async function createRelativeSymlink(target, linkPath) {
266
+ try {
267
+ const resolvedTarget = resolve(target);
268
+ const resolvedLinkPath = resolve(linkPath);
269
+ if (resolvedTarget === resolvedLinkPath) {
270
+ return true;
271
+ }
272
+ const existing = await lstat(linkPath).catch(() => null);
273
+ if (existing) {
274
+ if (existing.isSymbolicLink()) {
275
+ const currentTarget = await readlink(linkPath).catch(() => "");
276
+ if (resolve(dirname(linkPath), currentTarget) === resolvedTarget) {
277
+ return true;
278
+ }
279
+ }
280
+ await rm(linkPath, { recursive: true, force: true });
281
+ }
282
+ const linkDir = dirname(linkPath);
283
+ await mkdir(linkDir, { recursive: true });
284
+ const relativeTarget = relative(linkDir, target);
285
+ const symlinkType = platform() === "win32" ? "junction" : void 0;
286
+ await symlink(relativeTarget, linkPath, symlinkType);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+ async function writeSkillBundle(targetDir, content, isZip) {
293
+ await cleanAndCreateDirectory(targetDir);
294
+ if (!isZip) {
295
+ await writeFile(join2(targetDir, "SKILL.md"), content);
296
+ return;
297
+ }
298
+ const entries = parseZipEntries(content);
299
+ for (const entry of entries) {
300
+ const fullPath = join2(targetDir, entry.path);
301
+ if (!isPathSafe(targetDir, fullPath)) {
302
+ continue;
303
+ }
304
+ if (entry.isDirectory) {
305
+ await mkdir(fullPath, { recursive: true });
306
+ continue;
307
+ }
308
+ await mkdir(dirname(fullPath), { recursive: true });
309
+ await writeFile(fullPath, entry.content);
310
+ }
311
+ }
312
+ async function installDownloadedSkill(options) {
313
+ const cwd = options.cwd || process.cwd();
314
+ const canonicalPath = getCanonicalPath(options.scope, options.slug, cwd);
315
+ const writtenPaths = /* @__PURE__ */ new Set();
316
+ const agentInstalls = {};
317
+ const symlinkFallbackAgents = [];
318
+ const ensureBundleAt = async (path) => {
319
+ if (writtenPaths.has(path)) return;
320
+ await writeSkillBundle(path, options.content, options.isZip);
321
+ writtenPaths.add(path);
322
+ };
323
+ if (options.mode === "symlink") {
324
+ await ensureBundleAt(canonicalPath);
325
+ }
326
+ for (const agentType of options.agents) {
327
+ const agentPath = getInstallPath(options.scope, agentType, options.slug, cwd);
328
+ if (options.mode === "copy") {
329
+ await ensureBundleAt(agentPath);
330
+ agentInstalls[agentType] = {
331
+ path: agentPath,
332
+ kind: agentPath === canonicalPath ? "canonical" : "copy"
333
+ };
334
+ continue;
335
+ }
336
+ if (agentPath === canonicalPath) {
337
+ agentInstalls[agentType] = {
338
+ path: canonicalPath,
339
+ kind: "canonical"
340
+ };
341
+ continue;
342
+ }
343
+ const linked = await createRelativeSymlink(canonicalPath, agentPath);
344
+ if (linked) {
345
+ agentInstalls[agentType] = {
346
+ path: agentPath,
347
+ kind: "symlink"
348
+ };
349
+ continue;
350
+ }
351
+ await ensureBundleAt(agentPath);
352
+ agentInstalls[agentType] = {
353
+ path: agentPath,
354
+ kind: "copy"
355
+ };
356
+ symlinkFallbackAgents.push(agentType);
357
+ }
358
+ return {
359
+ canonicalPath,
360
+ agentInstalls,
361
+ symlinkFallbackAgents
362
+ };
363
+ }
364
+ function parseZipEntries(buf) {
365
+ const entries = [];
366
+ let eocdOffset = -1;
367
+ for (let i = buf.length - 22; i >= 0; i--) {
368
+ if (buf[i] === 80 && buf[i + 1] === 75 && buf[i + 2] === 5 && buf[i + 3] === 6) {
369
+ eocdOffset = i;
370
+ break;
371
+ }
372
+ }
373
+ if (eocdOffset === -1) {
374
+ throw new Error("Invalid zip: cannot find end of central directory.");
375
+ }
376
+ const centralDirOffset = buf.readUInt32LE(eocdOffset + 16);
377
+ const numEntries = buf.readUInt16LE(eocdOffset + 10);
378
+ let offset = centralDirOffset;
379
+ for (let i = 0; i < numEntries; i++) {
380
+ if (buf.readUInt32LE(offset) !== 33639248) {
381
+ break;
382
+ }
383
+ const compressedSize = buf.readUInt32LE(offset + 20);
384
+ const uncompressedSize = buf.readUInt32LE(offset + 24);
385
+ const filenameLen = buf.readUInt16LE(offset + 28);
386
+ const extraLen = buf.readUInt16LE(offset + 30);
387
+ const commentLen = buf.readUInt16LE(offset + 32);
388
+ const localHeaderOffset = buf.readUInt32LE(offset + 42);
389
+ const compressionMethod = buf.readUInt16LE(offset + 10);
390
+ const filename = buf.toString("utf-8", offset + 46, offset + 46 + filenameLen);
391
+ const isDirectory = filename.endsWith("/");
392
+ if (isDirectory) {
393
+ entries.push({ path: filename, isDirectory: true, content: Buffer.alloc(0) });
394
+ offset += 46 + filenameLen + extraLen + commentLen;
395
+ continue;
396
+ }
397
+ const localFilenameLen = buf.readUInt16LE(localHeaderOffset + 26);
398
+ const localExtraLen = buf.readUInt16LE(localHeaderOffset + 28);
399
+ const dataOffset = localHeaderOffset + 30 + localFilenameLen + localExtraLen;
400
+ let fileContent;
401
+ if (compressionMethod === 0) {
402
+ fileContent = buf.subarray(dataOffset, dataOffset + uncompressedSize);
403
+ } else if (compressionMethod === 8) {
404
+ const compressed = buf.subarray(dataOffset, dataOffset + compressedSize);
405
+ fileContent = inflateRawSync(compressed);
406
+ } else {
407
+ offset += 46 + filenameLen + extraLen + commentLen;
408
+ continue;
409
+ }
410
+ entries.push({
411
+ path: filename,
412
+ isDirectory: false,
413
+ content: fileContent
414
+ });
415
+ offset += 46 + filenameLen + extraLen + commentLen;
416
+ }
417
+ return entries;
418
+ }
419
+ var init_installer = __esm({
420
+ "src/installer.ts"() {
421
+ "use strict";
422
+ init_agents();
423
+ }
424
+ });
425
+
426
+ // src/lock.ts
427
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
428
+ import { homedir as homedir3 } from "os";
429
+ import { dirname as dirname2, join as join3 } from "path";
430
+ function getProjectLockPath(cwd = process.cwd()) {
431
+ return join3(cwd, "skills-lock.json");
432
+ }
433
+ function getGlobalLockPath() {
434
+ return join3(homedir3(), ".agents", ".skill-lock.json");
435
+ }
436
+ function readProjectLock(cwd = process.cwd()) {
437
+ return readLockFile(getProjectLockPath(cwd));
438
+ }
439
+ function readGlobalLock() {
440
+ return readLockFile(getGlobalLockPath());
441
+ }
442
+ function writeProjectLock(lock, cwd = process.cwd()) {
443
+ writeLockFile(getProjectLockPath(cwd), lock, true);
444
+ }
445
+ function writeGlobalLock(lock) {
446
+ writeLockFile(getGlobalLockPath(), lock, false);
447
+ }
448
+ function readLock(scope, cwd = process.cwd()) {
449
+ return scope === "global" ? readGlobalLock() : readProjectLock(cwd);
450
+ }
451
+ function writeLock(scope, lock, cwd = process.cwd()) {
452
+ if (scope === "global") {
453
+ writeGlobalLock(lock);
454
+ return;
455
+ }
456
+ writeProjectLock(lock, cwd);
457
+ }
458
+ function getLockEntry(scope, slug, cwd = process.cwd()) {
459
+ return readLock(scope, cwd).skills[slug];
460
+ }
461
+ function upsertLockEntry(scope, entry, cwd = process.cwd()) {
462
+ const lock = readLock(scope, cwd);
463
+ lock.skills[entry.slug] = entry;
464
+ writeLock(scope, lock, cwd);
465
+ }
466
+ function removeLockEntry(scope, slug, cwd = process.cwd()) {
467
+ const lock = readLock(scope, cwd);
468
+ if (!(slug in lock.skills)) {
469
+ return false;
470
+ }
471
+ delete lock.skills[slug];
472
+ writeLock(scope, lock, cwd);
473
+ return true;
474
+ }
475
+ function getInstalledEntries(scope = "all", cwd = process.cwd()) {
476
+ const entries = [];
477
+ if (scope === "all" || scope === "local") {
478
+ for (const entry of Object.values(readProjectLock(cwd).skills)) {
479
+ entries.push({ ...entry, scope: "local" });
480
+ }
481
+ }
482
+ if (scope === "all" || scope === "global") {
483
+ for (const entry of Object.values(readGlobalLock().skills)) {
484
+ entries.push({ ...entry, scope: "global" });
485
+ }
486
+ }
487
+ return entries;
488
+ }
489
+ function readLockFile(path) {
490
+ const raw = readRawLockFile(path);
491
+ if (!raw || typeof raw !== "object") {
492
+ return createEmptyLock();
493
+ }
494
+ const skills = isRecord(raw["skills"]) ? raw["skills"] : {};
495
+ const normalizedSkills = {};
496
+ for (const [slug, entry] of Object.entries(skills)) {
497
+ if (isLockEntry(entry)) {
498
+ normalizedSkills[slug] = entry;
499
+ }
500
+ }
501
+ return {
502
+ version: typeof raw["version"] === "number" ? raw["version"] : LOCK_VERSION,
503
+ skills: normalizedSkills
504
+ };
505
+ }
506
+ function writeLockFile(path, lock, sortSkills) {
507
+ const dir = dirname2(path);
508
+ if (!existsSync2(dir)) {
509
+ mkdirSync(dir, { recursive: true });
510
+ }
511
+ const sortedSkills = sortSkills ? Object.fromEntries(
512
+ Object.entries(lock.skills).sort(
513
+ ([left], [right]) => left.localeCompare(right)
514
+ )
515
+ ) : lock.skills;
516
+ const raw = readRawLockFile(path);
517
+ const foreignSkills = isRecord(raw?.["skills"]) ? Object.fromEntries(
518
+ Object.entries(raw["skills"]).filter(([, entry]) => !isLockEntry(entry))
519
+ ) : {};
520
+ writeFileSync(
521
+ path,
522
+ JSON.stringify(
523
+ {
524
+ ...raw && typeof raw === "object" ? raw : {},
525
+ version: typeof raw?.["version"] === "number" ? raw["version"] : LOCK_VERSION,
526
+ skills: {
527
+ ...foreignSkills,
528
+ ...sortedSkills
529
+ }
530
+ },
531
+ null,
532
+ 2
533
+ ) + "\n"
534
+ );
535
+ }
536
+ function createEmptyLock() {
537
+ return {
538
+ version: LOCK_VERSION,
539
+ skills: {}
540
+ };
541
+ }
542
+ function readRawLockFile(path) {
543
+ if (!existsSync2(path)) {
544
+ return void 0;
545
+ }
546
+ try {
547
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
548
+ return isRecord(parsed) ? parsed : void 0;
549
+ } catch {
550
+ return void 0;
551
+ }
552
+ }
553
+ function isRecord(value) {
554
+ return typeof value === "object" && value !== null;
555
+ }
556
+ function isLockEntry(value) {
557
+ if (!isRecord(value)) return false;
558
+ if (typeof value["slug"] !== "string") return false;
559
+ if (typeof value["name"] !== "string") return false;
560
+ if (typeof value["sha256"] !== "string") return false;
561
+ if (typeof value["installed_at"] !== "string") return false;
562
+ if (typeof value["is_zip"] !== "boolean") return false;
563
+ if (value["install_method"] !== "symlink" && value["install_method"] !== "copy") {
564
+ return false;
565
+ }
566
+ if (typeof value["canonical_path"] !== "string") return false;
567
+ return isRecord(value["agent_installs"]);
568
+ }
569
+ var LOCK_VERSION;
570
+ var init_lock = __esm({
571
+ "src/lock.ts"() {
572
+ "use strict";
573
+ LOCK_VERSION = 1;
574
+ }
575
+ });
576
+
577
+ // src/commands/add.ts
578
+ var add_exports = {};
579
+ __export(add_exports, {
580
+ addCommand: () => addCommand
581
+ });
582
+ import * as p from "@clack/prompts";
583
+ import pc from "picocolors";
584
+ async function addCommand(args) {
585
+ const options = parseAddOptions(args);
586
+ let slug = options.slug;
587
+ if (!slug) {
588
+ slug = await interactiveSearch();
589
+ if (!slug) return;
590
+ }
591
+ const spinner5 = p.spinner();
592
+ spinner5.start(`Fetching skill info for ${pc.cyan(slug)}`);
593
+ let detail;
594
+ try {
595
+ detail = await getSkill(slug);
596
+ } catch (err) {
597
+ spinner5.stop("Skill lookup failed.");
598
+ if (err instanceof Error && err.message.includes("API error 404")) {
599
+ throw new Error(`Skill ${slug} not found.`);
600
+ }
601
+ throw err;
602
+ }
603
+ if (detail.verification_status !== "verified") {
604
+ spinner5.stop(
605
+ `Skill ${pc.red(slug)} is ${pc.yellow(detail.verification_status)} and cannot be installed.`
606
+ );
607
+ throw new Error("Only verified skills can be installed.");
608
+ }
609
+ spinner5.stop(`Found: ${pc.cyan(detail.name)} \u2014 ${detail.description}`);
610
+ const warnings = getCapabilityWarnings(detail.capabilities);
611
+ if (warnings.length > 0) {
612
+ p.log.warn(
613
+ pc.yellow("Capabilities:") + "\n" + warnings.map((w) => ` - ${w}`).join("\n")
614
+ );
615
+ if (!options.skipConfirm) {
616
+ const proceed = await p.confirm({
617
+ message: "This skill has sensitive capabilities. Continue?"
618
+ });
619
+ if (!proceed || typeof proceed === "symbol") {
620
+ p.log.info("Cancelled.");
621
+ return;
622
+ }
623
+ }
624
+ }
625
+ const targetAgents = await resolveTargetAgents(options);
626
+ const scope = await resolveScope(options);
627
+ const installMode = await resolveInstallMode(options);
628
+ const existing = getLockEntry(scope, slug);
629
+ if (existing && !options.skipConfirm) {
630
+ const shouldOverwrite = await p.confirm({
631
+ message: `${pc.yellow(slug)} is already installed (${scope}). Reinstall?`
632
+ });
633
+ if (!shouldOverwrite || typeof shouldOverwrite === "symbol") {
634
+ p.log.info("Cancelled.");
635
+ return;
636
+ }
637
+ }
638
+ const summaryLines = buildSummaryLines({
639
+ slug,
640
+ scope,
641
+ mode: installMode,
642
+ agents: targetAgents
643
+ });
644
+ console.log();
645
+ p.note(summaryLines.join("\n"), "Installation Summary");
646
+ if (!options.skipConfirm) {
647
+ const confirmed = await p.confirm({ message: "Proceed with installation?" });
648
+ if (!confirmed || typeof confirmed === "symbol") {
649
+ p.log.info("Cancelled.");
650
+ return;
651
+ }
652
+ }
653
+ if (existing) {
654
+ await cleanupLockEntry(existing);
655
+ }
656
+ spinner5.start(`Downloading ${pc.cyan(slug)}`);
657
+ const download = await downloadSkill(slug);
658
+ spinner5.stop(
659
+ `Downloaded ${pc.green(download.filename)} (${formatBytes(download.content.length)})`
660
+ );
661
+ spinner5.start(`Installing to ${scope} scope`);
662
+ const installResult = await installDownloadedSkill({
663
+ scope,
664
+ slug,
665
+ content: download.content,
666
+ isZip: download.isZip,
667
+ agents: targetAgents,
668
+ mode: installMode
669
+ });
670
+ spinner5.stop(`Installed to ${pc.dim(installResult.canonicalPath)}`);
671
+ upsertLockEntry(scope, {
672
+ slug,
673
+ name: detail.name,
674
+ sha256: download.sha256,
675
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
676
+ is_zip: download.isZip,
677
+ install_method: installMode,
678
+ canonical_path: installResult.canonicalPath,
679
+ agent_installs: installResult.agentInstalls
680
+ });
681
+ if (installResult.symlinkFallbackAgents.length > 0) {
682
+ p.log.warn(
683
+ `Symlinks failed for ${installResult.symlinkFallbackAgents.map((agent) => agents[agent].displayName).join(", ")}.`
684
+ );
685
+ p.log.message(pc.dim("Files were copied instead."));
686
+ }
687
+ p.log.success(`${pc.green("+")} ${pc.bold(detail.name)} installed successfully.`);
688
+ }
689
+ function parseAddOptions(args) {
690
+ const options = {
691
+ requestedAgents: [],
692
+ global: false,
693
+ skipConfirm: false,
694
+ copy: false
695
+ };
696
+ for (let i = 0; i < args.length; i++) {
697
+ const arg = args[i];
698
+ if (arg === "-g" || arg === "--global") {
699
+ options.global = true;
700
+ continue;
701
+ }
702
+ if (arg === "-y" || arg === "--yes") {
703
+ options.skipConfirm = true;
704
+ continue;
705
+ }
706
+ if (arg === "--copy") {
707
+ options.copy = true;
708
+ continue;
709
+ }
710
+ if (arg === "-a" || arg === "--agent") {
711
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
712
+ options.requestedAgents.push(args[++i]);
713
+ }
714
+ continue;
715
+ }
716
+ if (!arg.startsWith("-") && !options.slug) {
717
+ options.slug = arg;
718
+ }
719
+ }
720
+ return options;
721
+ }
722
+ async function resolveTargetAgents(options) {
723
+ if (options.requestedAgents.length > 0) {
724
+ const invalid = options.requestedAgents.filter((agent) => !isValidAgent(agent));
725
+ if (invalid.length > 0) {
726
+ throw new Error(
727
+ `Invalid agent${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}`
728
+ );
729
+ }
730
+ return ensureUniversalAgents(options.requestedAgents);
731
+ }
732
+ const detectedAgents = await detectInstalledAgents();
733
+ const universalAgents = new Set(getUniversalAgents());
734
+ const detectedNonUniversal = detectedAgents.filter((agent) => !universalAgents.has(agent));
735
+ if (options.skipConfirm) {
736
+ return ensureUniversalAgents(detectedNonUniversal);
737
+ }
738
+ if (detectedNonUniversal.length === 1) {
739
+ const agent = detectedNonUniversal[0];
740
+ p.log.info(`Installing to: ${pc.cyan(agents[agent].displayName)}`);
741
+ return ensureUniversalAgents([agent]);
742
+ }
743
+ const selectableAgents = getNonUniversalAgents();
744
+ const selected = await p.multiselect({
745
+ message: detectedNonUniversal.length > 1 ? "Select additional agents to install to:" : "Select agents to install to:",
746
+ options: selectableAgents.map((agent) => ({
747
+ value: agent,
748
+ label: agents[agent].displayName,
749
+ hint: options.global ? agents[agent].globalSkillsDir : agents[agent].skillsDir
750
+ })),
751
+ initialValues: detectedNonUniversal
752
+ });
753
+ if (typeof selected === "symbol") {
754
+ throw new Error("Installation cancelled.");
755
+ }
756
+ return ensureUniversalAgents(selected);
757
+ }
758
+ async function resolveScope(options) {
759
+ if (options.global) {
760
+ return "global";
761
+ }
762
+ if (options.skipConfirm) {
763
+ return "local";
764
+ }
765
+ const selected = await p.select({
766
+ message: "Installation scope",
767
+ options: [
768
+ {
769
+ value: "local",
770
+ label: "Project",
771
+ hint: "Install in the current repository"
772
+ },
773
+ {
774
+ value: "global",
775
+ label: "Global",
776
+ hint: "Install in your home directory"
777
+ }
778
+ ]
779
+ });
780
+ if (typeof selected === "symbol") {
781
+ throw new Error("Installation cancelled.");
782
+ }
783
+ return selected;
784
+ }
785
+ async function resolveInstallMode(options) {
786
+ if (options.copy) {
787
+ return "copy";
788
+ }
789
+ if (options.skipConfirm) {
790
+ return "symlink";
791
+ }
792
+ const selected = await p.select({
793
+ message: "Installation method",
794
+ options: [
795
+ {
796
+ value: "symlink",
797
+ label: "Symlink (Recommended)",
798
+ hint: "Single source of truth, easier updates"
799
+ },
800
+ {
801
+ value: "copy",
802
+ label: "Copy",
803
+ hint: "Write separate copies into each agent directory"
804
+ }
805
+ ]
806
+ });
807
+ if (typeof selected === "symbol") {
808
+ throw new Error("Installation cancelled.");
809
+ }
810
+ return selected;
811
+ }
812
+ function ensureUniversalAgents(selectedAgents) {
813
+ const combined = new Set(getUniversalAgents());
814
+ for (const agent of selectedAgents) {
815
+ combined.add(agent);
816
+ }
817
+ return Array.from(combined);
818
+ }
819
+ function buildSummaryLines(options) {
820
+ const lines = [];
821
+ const canonicalPath = getCanonicalPath(options.scope, options.slug);
822
+ lines.push(`${pc.dim("scope:")} ${options.scope}`);
823
+ lines.push(`${pc.dim("method:")} ${options.mode}`);
824
+ lines.push(`${pc.dim("canonical:")} ${canonicalPath}`);
825
+ lines.push(
826
+ `${pc.dim("agents:")} ${options.agents.map((agent) => agents[agent].displayName).join(", ")}`
827
+ );
828
+ if (options.mode === "copy") {
829
+ for (const agent of options.agents) {
830
+ lines.push(
831
+ `${pc.dim(`${agents[agent].displayName}:`)} ${getInstallPath(options.scope, agent, options.slug)}`
832
+ );
833
+ }
834
+ }
835
+ return lines;
836
+ }
837
+ async function cleanupLockEntry(entry) {
838
+ const paths = /* @__PURE__ */ new Set();
839
+ paths.add(entry.canonical_path);
840
+ for (const install of Object.values(entry.agent_installs)) {
841
+ if (install?.path) {
842
+ paths.add(install.path);
843
+ }
844
+ }
845
+ for (const path of paths) {
846
+ await removePathIfExists(path);
847
+ }
848
+ }
849
+ async function interactiveSearch() {
850
+ const searchTerm = await p.text({
851
+ message: "Search skills:",
852
+ placeholder: "e.g. defi, swap, analytics"
853
+ });
854
+ if (typeof searchTerm === "symbol" || !searchTerm) return void 0;
855
+ const spinner5 = p.spinner();
856
+ spinner5.start("Searching marketplace...");
857
+ const result = await listSkills({ search: searchTerm, limit: 20 });
858
+ spinner5.stop(`Found ${result.skills.length} skill(s).`);
859
+ if (result.skills.length === 0) {
860
+ p.log.warn("No skills found matching that query.");
861
+ return void 0;
862
+ }
863
+ const selected = await p.select({
864
+ message: "Select a skill to install:",
865
+ options: result.skills.map((skill) => ({
866
+ value: skill.slug,
867
+ label: `${skill.name} ${pc.dim(`[${skill.category || "uncategorized"}]`)}`,
868
+ hint: skill.description
869
+ }))
870
+ });
871
+ if (typeof selected === "symbol") return void 0;
872
+ return selected;
873
+ }
874
+ function getCapabilityWarnings(capabilities) {
875
+ const warnings = [];
876
+ if (capabilities.requires_private_keys) warnings.push("Requires private keys");
877
+ if (capabilities.can_sign_transactions) warnings.push("Can sign transactions");
878
+ if (capabilities.uses_leverage) warnings.push("Uses leverage");
879
+ if (capabilities.requires_exchange_api_keys) warnings.push("Requires exchange API keys");
880
+ return warnings;
881
+ }
882
+ function formatBytes(bytes) {
883
+ if (bytes < 1024) return `${bytes} B`;
884
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
885
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
886
+ }
887
+ var init_add = __esm({
888
+ "src/commands/add.ts"() {
889
+ "use strict";
890
+ init_api();
891
+ init_agents();
892
+ init_installer();
893
+ init_lock();
894
+ }
895
+ });
896
+
897
+ // src/commands/remove.ts
898
+ var remove_exports = {};
899
+ __export(remove_exports, {
900
+ removeCommand: () => removeCommand
901
+ });
902
+ import * as p2 from "@clack/prompts";
903
+ import pc2 from "picocolors";
904
+ async function removeCommand(args) {
905
+ const options = parseRemoveOptions(args);
906
+ const requestedAgents = resolveRequestedAgents(options.requestedAgents);
907
+ const installed = getInstalledEntries(options.global ? "global" : "all");
908
+ if (installed.length === 0) {
909
+ p2.log.info("No skills installed.");
910
+ return;
911
+ }
912
+ const targets = await resolveTargets(installed, options);
913
+ if (targets.length === 0) {
914
+ p2.log.info("Nothing to remove.");
915
+ return;
916
+ }
917
+ if (!options.skipConfirm) {
918
+ const confirm3 = await p2.confirm({
919
+ message: `Remove ${targets.length} skill install(s)?`
920
+ });
921
+ if (!confirm3 || typeof confirm3 === "symbol") {
922
+ p2.log.info("Cancelled.");
923
+ return;
924
+ }
925
+ }
926
+ for (const target of targets) {
927
+ const result = await removeFromEntry(target, requestedAgents);
928
+ if (!result.changed) {
929
+ p2.log.warn(result.message);
930
+ continue;
931
+ }
932
+ if (result.remainingAgents.length > 0) {
933
+ p2.log.success(
934
+ `${pc2.red("-")} ${target.slug} updated (${target.scope}); remaining agents: ${result.remainingAgents.map((agent) => agents[agent].displayName).join(", ")}`
935
+ );
936
+ } else {
937
+ p2.log.success(`${pc2.red("-")} ${target.slug} removed (${target.scope}).`);
938
+ }
939
+ }
940
+ }
941
+ function parseRemoveOptions(args) {
942
+ const options = {
943
+ global: false,
944
+ skipConfirm: false,
945
+ removeAll: false,
946
+ requestedAgents: [],
947
+ slugs: []
948
+ };
949
+ for (let i = 0; i < args.length; i++) {
950
+ const arg = args[i];
951
+ if (arg === "-g" || arg === "--global") {
952
+ options.global = true;
953
+ continue;
954
+ }
955
+ if (arg === "-y" || arg === "--yes") {
956
+ options.skipConfirm = true;
957
+ continue;
958
+ }
959
+ if (arg === "--all") {
960
+ options.removeAll = true;
961
+ continue;
962
+ }
963
+ if (arg === "-a" || arg === "--agent") {
964
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
965
+ options.requestedAgents.push(args[++i]);
966
+ }
967
+ continue;
968
+ }
969
+ if (!arg.startsWith("-")) {
970
+ options.slugs.push(arg);
971
+ }
972
+ }
973
+ return options;
974
+ }
975
+ function resolveRequestedAgents(requestedAgents) {
976
+ if (requestedAgents.length === 0) {
977
+ return void 0;
978
+ }
979
+ const invalid = requestedAgents.filter((agent) => !isValidAgent(agent));
980
+ if (invalid.length > 0) {
981
+ throw new Error(
982
+ `Invalid agent${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}`
983
+ );
984
+ }
985
+ return requestedAgents;
986
+ }
987
+ async function resolveTargets(installed, options) {
988
+ if (options.removeAll) {
989
+ return installed;
990
+ }
991
+ if (options.slugs.length > 0) {
992
+ const targets = [];
993
+ for (const slug of options.slugs) {
994
+ const matches = installed.filter((entry) => entry.slug === slug);
995
+ if (matches.length === 0) {
996
+ p2.log.warn(`${slug} is not installed.`);
997
+ continue;
998
+ }
999
+ if (matches.length === 1 || options.global) {
1000
+ targets.push(matches[0]);
1001
+ continue;
1002
+ }
1003
+ const localMatch = matches.find((entry) => entry.scope === "local");
1004
+ const globalMatch = matches.find((entry) => entry.scope === "global");
1005
+ if (localMatch && globalMatch && !options.skipConfirm) {
1006
+ const selected2 = await p2.select({
1007
+ message: `${slug} is installed in both project and global scope. Remove from:`,
1008
+ options: [
1009
+ { value: "local", label: "Project" },
1010
+ { value: "global", label: "Global" },
1011
+ { value: "both", label: "Both" }
1012
+ ]
1013
+ });
1014
+ if (typeof selected2 === "symbol") {
1015
+ continue;
1016
+ }
1017
+ if (selected2 === "both") {
1018
+ targets.push(localMatch, globalMatch);
1019
+ continue;
1020
+ }
1021
+ targets.push(selected2 === "local" ? localMatch : globalMatch);
1022
+ continue;
1023
+ }
1024
+ targets.push(localMatch || matches[0]);
1025
+ if (localMatch && globalMatch && options.skipConfirm) {
1026
+ p2.log.info(`${slug} is also installed globally. Use --global to remove that copy.`);
1027
+ }
1028
+ }
1029
+ return targets;
1030
+ }
1031
+ const selected = await p2.multiselect({
1032
+ message: "Select installs to remove:",
1033
+ options: installed.map((entry) => ({
1034
+ value: `${entry.scope}:${entry.slug}`,
1035
+ label: `${entry.slug} \u2014 ${entry.name}`,
1036
+ hint: entry.scope
1037
+ }))
1038
+ });
1039
+ if (typeof selected === "symbol") {
1040
+ return [];
1041
+ }
1042
+ const selectedValues = new Set(selected);
1043
+ return installed.filter((entry) => selectedValues.has(`${entry.scope}:${entry.slug}`));
1044
+ }
1045
+ async function removeFromEntry(entry, requestedAgents) {
1046
+ const agentInstalls = { ...entry.agent_installs };
1047
+ const installedAgents = Object.keys(agentInstalls);
1048
+ const targetAgents = requestedAgents ? installedAgents.filter((agent) => requestedAgents.includes(agent)) : installedAgents;
1049
+ if (targetAgents.length === 0) {
1050
+ return {
1051
+ changed: false,
1052
+ message: `${entry.slug} is not installed for the selected agents.`,
1053
+ remainingAgents: installedAgents
1054
+ };
1055
+ }
1056
+ for (const agent of targetAgents) {
1057
+ const install = agentInstalls[agent];
1058
+ if (!install) continue;
1059
+ if (install.kind !== "canonical") {
1060
+ await removePathIfExists(install.path);
1061
+ }
1062
+ delete agentInstalls[agent];
1063
+ }
1064
+ const remainingInstalls = Object.values(agentInstalls).filter(
1065
+ (install) => Boolean(install)
1066
+ );
1067
+ const remainingNeedsCanonical = remainingInstalls.some((install) => install.kind !== "copy");
1068
+ if (!remainingNeedsCanonical) {
1069
+ await removePathIfExists(entry.canonical_path);
1070
+ }
1071
+ if (remainingInstalls.length === 0) {
1072
+ removeLockEntry(entry.scope, entry.slug);
1073
+ } else {
1074
+ upsertLockEntry(entry.scope, {
1075
+ ...stripScope(entry),
1076
+ agent_installs: agentInstalls
1077
+ });
1078
+ }
1079
+ return {
1080
+ changed: true,
1081
+ message: "",
1082
+ remainingAgents: Object.keys(agentInstalls)
1083
+ };
1084
+ }
1085
+ function stripScope(entry) {
1086
+ return {
1087
+ slug: entry.slug,
1088
+ name: entry.name,
1089
+ sha256: entry.sha256,
1090
+ installed_at: entry.installed_at,
1091
+ is_zip: entry.is_zip,
1092
+ install_method: entry.install_method,
1093
+ canonical_path: entry.canonical_path,
1094
+ agent_installs: entry.agent_installs
1095
+ };
1096
+ }
1097
+ var init_remove = __esm({
1098
+ "src/commands/remove.ts"() {
1099
+ "use strict";
1100
+ init_agents();
1101
+ init_installer();
1102
+ init_lock();
1103
+ }
1104
+ });
1105
+
1106
+ // src/commands/list.ts
1107
+ var list_exports = {};
1108
+ __export(list_exports, {
1109
+ listCommand: () => listCommand,
1110
+ listRemote: () => listRemote
1111
+ });
1112
+ import * as p3 from "@clack/prompts";
1113
+ import pc3 from "picocolors";
1114
+ async function listCommand(args) {
1115
+ const options = parseListOptions(args);
1116
+ if (options.showRemote) {
1117
+ await listRemote(options);
1118
+ return;
1119
+ }
1120
+ await listLocal(options.global ? "global" : "local", options.agentFilter);
1121
+ }
1122
+ function parseListOptions(args) {
1123
+ const options = {
1124
+ showRemote: false,
1125
+ global: false
1126
+ };
1127
+ for (let i = 0; i < args.length; i++) {
1128
+ const arg = args[i];
1129
+ if (arg === "--remote" || arg === "-r") {
1130
+ options.showRemote = true;
1131
+ continue;
1132
+ }
1133
+ if (arg === "-g" || arg === "--global") {
1134
+ options.global = true;
1135
+ continue;
1136
+ }
1137
+ if (arg === "--category" || arg === "-c") {
1138
+ const value = args[i + 1];
1139
+ if (value && !value.startsWith("-")) {
1140
+ options.category = value;
1141
+ i++;
1142
+ }
1143
+ continue;
1144
+ }
1145
+ if (arg === "--search" || arg === "-s") {
1146
+ const value = args[i + 1];
1147
+ if (value && !value.startsWith("-")) {
1148
+ options.search = value;
1149
+ i++;
1150
+ }
1151
+ continue;
1152
+ }
1153
+ if (arg === "-a" || arg === "--agent") {
1154
+ const selected = [];
1155
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
1156
+ const value = args[++i];
1157
+ if (!isValidAgent(value)) {
1158
+ throw new Error(`Invalid agent: ${value}`);
1159
+ }
1160
+ selected.push(value);
1161
+ }
1162
+ options.agentFilter = selected;
1163
+ }
1164
+ }
1165
+ return options;
1166
+ }
1167
+ async function listLocal(scope, agentFilter) {
1168
+ const installed = getInstalledEntries(scope).sort((left, right) => left.slug.localeCompare(right.slug));
1169
+ if (installed.length === 0) {
1170
+ p3.log.info(
1171
+ scope === "global" ? "No global skills installed." : "No project skills installed."
1172
+ );
1173
+ return;
1174
+ }
1175
+ const visibleSkills = (await Promise.all(installed.map((entry) => resolveVisibleInstall(entry)))).filter((entry) => Boolean(entry));
1176
+ const filtered = agentFilter ? visibleSkills.filter(
1177
+ (entry) => entry.activeAgents.some((agent) => agentFilter.includes(agent))
1178
+ ) : visibleSkills;
1179
+ if (filtered.length === 0) {
1180
+ p3.log.info("No skills matched the selected filters.");
1181
+ return;
1182
+ }
1183
+ p3.log.info(pc3.bold(`${scope === "global" ? "Global" : "Project"} skills (${filtered.length}):`));
1184
+ console.log();
1185
+ for (const entry of filtered) {
1186
+ const folder = entry.isZip ? pc3.dim(" [folder]") : "";
1187
+ console.log(` ${pc3.cyan(entry.slug)} \u2014 ${entry.name}${folder}`);
1188
+ console.log(` ${pc3.dim(entry.displayPath)}`);
1189
+ console.log(
1190
+ ` ${pc3.dim(`agents: ${entry.activeAgents.map((agent) => agents[agent].displayName).join(", ")}`)}`
1191
+ );
1192
+ console.log(` ${pc3.dim(`sha256: ${entry.sha256.slice(0, 16)}...`)}`);
1193
+ console.log();
1194
+ }
1195
+ }
1196
+ async function listRemote(options) {
1197
+ const spinner5 = p3.spinner();
1198
+ spinner5.start("Fetching skills from marketplace...");
1199
+ const result = await listSkills({
1200
+ category: options.category || void 0,
1201
+ search: options.search || void 0,
1202
+ limit: 50
1203
+ });
1204
+ spinner5.stop(`Showing ${result.skills.length} verified skill(s).`);
1205
+ if (result.skills.length === 0) {
1206
+ p3.log.warn("No skills found.");
1207
+ return;
1208
+ }
1209
+ const installed = new Set(getInstalledEntries("all").map((entry) => entry.slug));
1210
+ console.log();
1211
+ for (const skill of result.skills) {
1212
+ const status = installed.has(skill.slug) ? pc3.green(" [installed]") : "";
1213
+ const category = skill.category ? pc3.dim(` [${skill.category}]`) : "";
1214
+ const risk = skill.risk_tier ? pc3.dim(` risk:${skill.risk_tier}`) : "";
1215
+ console.log(` ${pc3.cyan(skill.slug)}${category}${risk}${status}`);
1216
+ console.log(` ${skill.description}`);
1217
+ const warnings = [];
1218
+ if (skill.capabilities.requires_private_keys) warnings.push("private-keys");
1219
+ if (skill.capabilities.can_sign_transactions) warnings.push("sign-tx");
1220
+ if (skill.capabilities.uses_leverage) warnings.push("leverage");
1221
+ if (warnings.length > 0) {
1222
+ console.log(` ${pc3.yellow(`caps: ${warnings.join(", ")}`)}`);
1223
+ }
1224
+ console.log();
1225
+ }
1226
+ }
1227
+ async function resolveVisibleInstall(entry) {
1228
+ const canonicalExists = await pathExists(entry.canonical_path);
1229
+ const activeAgents = [];
1230
+ let displayPath = canonicalExists ? entry.canonical_path : "";
1231
+ for (const [agentName, install] of Object.entries(entry.agent_installs)) {
1232
+ if (install.kind === "canonical") {
1233
+ if (canonicalExists) {
1234
+ activeAgents.push(agentName);
1235
+ }
1236
+ continue;
1237
+ }
1238
+ if (await pathExists(install.path)) {
1239
+ activeAgents.push(agentName);
1240
+ if (!displayPath) {
1241
+ displayPath = install.path;
1242
+ }
1243
+ }
1244
+ }
1245
+ if (activeAgents.length === 0) {
1246
+ return void 0;
1247
+ }
1248
+ return {
1249
+ slug: entry.slug,
1250
+ name: entry.name,
1251
+ sha256: entry.sha256,
1252
+ isZip: entry.is_zip,
1253
+ displayPath,
1254
+ activeAgents
1255
+ };
1256
+ }
1257
+ var init_list = __esm({
1258
+ "src/commands/list.ts"() {
1259
+ "use strict";
1260
+ init_api();
1261
+ init_agents();
1262
+ init_installer();
1263
+ init_lock();
1264
+ }
1265
+ });
1266
+
1267
+ // src/commands/find.ts
1268
+ var find_exports = {};
1269
+ __export(find_exports, {
1270
+ findCommand: () => findCommand
1271
+ });
1272
+ async function findCommand(args) {
1273
+ let search;
1274
+ let category;
1275
+ for (let i = 0; i < args.length; i++) {
1276
+ const arg = args[i];
1277
+ if ((arg === "--category" || arg === "-c") && args[i + 1] && !args[i + 1].startsWith("-")) {
1278
+ category = args[++i];
1279
+ } else if (!arg.startsWith("-") && search === void 0) {
1280
+ search = arg;
1281
+ }
1282
+ }
1283
+ const options = { showRemote: true, global: false, search, category };
1284
+ await listRemote(options);
1285
+ }
1286
+ var init_find = __esm({
1287
+ "src/commands/find.ts"() {
1288
+ "use strict";
1289
+ init_list();
1290
+ }
1291
+ });
1292
+
1293
+ // src/commands/info.ts
1294
+ var info_exports = {};
1295
+ __export(info_exports, {
1296
+ infoCommand: () => infoCommand
1297
+ });
1298
+ import * as p4 from "@clack/prompts";
1299
+ import pc4 from "picocolors";
1300
+ async function infoCommand(args) {
1301
+ const slug = args.filter((arg) => !arg.startsWith("-"))[0];
1302
+ if (!slug) {
1303
+ p4.log.error("Usage: heurist-skills info <slug>");
1304
+ return;
1305
+ }
1306
+ const spinner5 = p4.spinner();
1307
+ spinner5.start(`Fetching info for ${pc4.cyan(slug)}`);
1308
+ let detail;
1309
+ try {
1310
+ detail = await getSkill(slug);
1311
+ } catch (err) {
1312
+ if (err instanceof Error && err.message.includes("API error 404")) {
1313
+ spinner5.stop(`Skill ${pc4.red(slug)} not found.`);
1314
+ throw new Error(`Skill ${slug} not found.`);
1315
+ }
1316
+ spinner5.stop(`Failed to fetch info for ${pc4.cyan(slug)}.`);
1317
+ throw err;
1318
+ }
1319
+ spinner5.stop(`${pc4.bold(detail.slug)}`);
1320
+ console.log();
1321
+ console.log(` ${pc4.bold("Name:")} ${detail.name}`);
1322
+ console.log(` ${pc4.bold("Slug:")} ${detail.slug}`);
1323
+ console.log(` ${pc4.bold("Description:")} ${detail.description}`);
1324
+ console.log(` ${pc4.bold("Category:")} ${detail.category || "\u2014"}`);
1325
+ console.log(` ${pc4.bold("Risk Tier:")} ${detail.risk_tier || "\u2014"}`);
1326
+ console.log(` ${pc4.bold("Status:")} ${detail.verification_status}`);
1327
+ console.log(` ${pc4.bold("Source:")} ${detail.source_url || "\u2014"}`);
1328
+ if (detail.author?.display_name) {
1329
+ console.log(` ${pc4.bold("Author:")} ${detail.author.display_name}`);
1330
+ }
1331
+ if (detail.author?.github_username) {
1332
+ console.log(` ${pc4.bold("GitHub:")} ${detail.author.github_username}`);
1333
+ }
1334
+ console.log();
1335
+ console.log(` ${pc4.bold("Capabilities:")}`);
1336
+ const caps = detail.capabilities;
1337
+ const capList = [
1338
+ ["requires_secrets", caps.requires_secrets],
1339
+ ["requires_private_keys", caps.requires_private_keys],
1340
+ ["requires_exchange_api_keys", caps.requires_exchange_api_keys],
1341
+ ["can_sign_transactions", caps.can_sign_transactions],
1342
+ ["uses_leverage", caps.uses_leverage],
1343
+ ["accesses_user_portfolio", caps.accesses_user_portfolio]
1344
+ ];
1345
+ for (const [name, value] of capList) {
1346
+ console.log(` ${name}: ${value ? pc4.yellow("yes") : pc4.dim("no")}`);
1347
+ }
1348
+ try {
1349
+ const filesInfo = await listSkillFiles(slug);
1350
+ console.log();
1351
+ console.log(` ${pc4.bold("Files:")} (${filesInfo.file_count})`);
1352
+ for (const file of filesInfo.files) {
1353
+ console.log(` ${pc4.dim(file.path)}`);
1354
+ }
1355
+ } catch {
1356
+ }
1357
+ const installed = getInstalledEntries("all").filter((entry) => entry.slug === slug);
1358
+ if (installed.length > 0) {
1359
+ console.log();
1360
+ console.log(` ${pc4.bold("Installed:")}`);
1361
+ for (const entry of installed) {
1362
+ const canonicalExists = await pathExists(entry.canonical_path);
1363
+ const activeAgents = Object.entries(entry.agent_installs).filter(([, install]) => {
1364
+ if (!install) return false;
1365
+ if (install.kind === "canonical") {
1366
+ return canonicalExists;
1367
+ }
1368
+ return true;
1369
+ }).map(([agent]) => agents[agent].displayName);
1370
+ console.log(` ${entry.scope}: ${pc4.dim(entry.canonical_path)}`);
1371
+ console.log(` sha256: ${pc4.dim(`${entry.sha256.slice(0, 32)}...`)}`);
1372
+ if (activeAgents.length > 0) {
1373
+ console.log(` agents: ${pc4.dim(activeAgents.join(", "))}`);
1374
+ }
1375
+ }
1376
+ }
1377
+ console.log();
1378
+ }
1379
+ var init_info = __esm({
1380
+ "src/commands/info.ts"() {
1381
+ "use strict";
1382
+ init_api();
1383
+ init_agents();
1384
+ init_installer();
1385
+ init_lock();
1386
+ }
1387
+ });
1388
+
1389
+ // src/commands/check-updates.ts
1390
+ var check_updates_exports = {};
1391
+ __export(check_updates_exports, {
1392
+ checkUpdatesCommand: () => checkUpdatesCommand
1393
+ });
1394
+ import * as p5 from "@clack/prompts";
1395
+ import pc5 from "picocolors";
1396
+ async function checkUpdatesCommand(_args) {
1397
+ const installed = getInstalledEntries("all");
1398
+ if (installed.length === 0) {
1399
+ p5.log.info("No skills installed. Nothing to check.");
1400
+ return;
1401
+ }
1402
+ const spinner5 = p5.spinner();
1403
+ spinner5.start(
1404
+ `Checking updates for ${installed.length} installed skill(s)...`
1405
+ );
1406
+ const uniqueInstalled = Array.from(
1407
+ new Map(
1408
+ installed.map((entry) => [`${entry.slug}:${entry.sha256}`, {
1409
+ slug: entry.slug,
1410
+ sha256: entry.sha256
1411
+ }])
1412
+ ).values()
1413
+ );
1414
+ const updates = await checkUpdates(uniqueInstalled);
1415
+ const updatesBySlug = new Map(
1416
+ updates.map((update) => [update.slug, update.approved_sha256])
1417
+ );
1418
+ const outdated = installed.filter((entry) => {
1419
+ const approvedSha = updatesBySlug.get(entry.slug);
1420
+ return approvedSha && approvedSha !== entry.sha256;
1421
+ });
1422
+ if (outdated.length === 0) {
1423
+ spinner5.stop(pc5.green("All skills are up to date."));
1424
+ return;
1425
+ }
1426
+ spinner5.stop(pc5.yellow(`${outdated.length} update(s) available:`));
1427
+ console.log();
1428
+ for (const entry of outdated) {
1429
+ const approvedSha = updatesBySlug.get(entry.slug) || "unknown";
1430
+ console.log(` ${pc5.cyan(entry.slug)} ${pc5.dim(`[${entry.scope}]`)}`);
1431
+ console.log(` current: ${pc5.dim(entry.sha256.slice(0, 16))}...`);
1432
+ console.log(` latest: ${pc5.dim(approvedSha.slice(0, 16))}...`);
1433
+ console.log();
1434
+ }
1435
+ p5.log.info(
1436
+ `Run ${pc5.cyan("heurist-skills add <slug>")} with the original scope to reinstall an approved update.`
1437
+ );
1438
+ }
1439
+ var init_check_updates = __esm({
1440
+ "src/commands/check-updates.ts"() {
1441
+ "use strict";
1442
+ init_api();
1443
+ init_lock();
1444
+ }
1445
+ });
1446
+
1447
+ // src/cli.ts
1448
+ import "dotenv/config";
1449
+ import * as p6 from "@clack/prompts";
1450
+ import pc6 from "picocolors";
1451
+ var VERSION = "0.1.0";
1452
+ var LOGO = `
1453
+ ${pc6.cyan("\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1454
+ ${pc6.cyan("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
1455
+ ${pc6.cyan("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551")}
1456
+ ${pc6.cyan("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551")}
1457
+ ${pc6.cyan("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551")}
1458
+ ${pc6.cyan("\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D")}
1459
+ ${pc6.dim(` skills v${VERSION}`)}
1460
+ `;
1461
+ function printHelp() {
1462
+ console.log(LOGO);
1463
+ console.log(`${pc6.bold("Usage:")} heurist-skills <command> [options]`);
1464
+ console.log();
1465
+ console.log(`${pc6.bold("Commands:")}`);
1466
+ console.log(` ${pc6.cyan("add")} <slug> Install a skill from the marketplace (aliases: install)`);
1467
+ console.log(` ${pc6.cyan("remove")} <slug> Uninstall a skill (aliases: rm, uninstall)`);
1468
+ console.log(` ${pc6.cyan("list")} List project-installed skills (aliases: ls)`);
1469
+ console.log(` ${pc6.cyan("list")} --global List global-installed skills`);
1470
+ console.log(` ${pc6.cyan("find")} [query] Search the skill marketplace (aliases: search, f)`);
1471
+ console.log(` ${pc6.cyan("info")} <slug> Show detailed skill info (aliases: show)`);
1472
+ console.log(` ${pc6.cyan("check")} Check for available updates (aliases: check-updates, update-check)`);
1473
+ console.log(` ${pc6.cyan("help")} Show this help`);
1474
+ console.log();
1475
+ console.log(`${pc6.bold("Options:")}`);
1476
+ console.log(` ${pc6.dim("-g, --global")} Use the global scope (~/.agents/skills/)`);
1477
+ console.log(` ${pc6.dim("-a, --agent <agent>")} Target or filter specific agents`);
1478
+ console.log(` ${pc6.dim("--copy")} Copy files instead of symlinking`);
1479
+ console.log(` ${pc6.dim("-y, --yes")} Skip confirmation prompts`);
1480
+ console.log(` ${pc6.dim("--category, -c")} Filter by category (use with find/list --remote)`);
1481
+ console.log(` ${pc6.dim("--search, -s")} Filter by search term (use with list --remote)`);
1482
+ console.log();
1483
+ console.log(`${pc6.bold("Environment:")}`);
1484
+ console.log(` ${pc6.dim("HEURIST_SKILLS_API")} Override marketplace API URL`);
1485
+ console.log(` ${pc6.dim("(default: https://mesh.heurist.ai)")}`);
1486
+ console.log();
1487
+ }
1488
+ async function main() {
1489
+ const args = process.argv.slice(2);
1490
+ const command = args[0];
1491
+ const commandArgs = args.slice(1);
1492
+ if (!command || command === "help" || command === "--help" || command === "-h") {
1493
+ printHelp();
1494
+ return;
1495
+ }
1496
+ if (command === "--version" || command === "-v") {
1497
+ console.log(VERSION);
1498
+ return;
1499
+ }
1500
+ p6.intro(pc6.cyan("heurist-skills"));
1501
+ try {
1502
+ switch (command) {
1503
+ case "add":
1504
+ case "install": {
1505
+ const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
1506
+ await addCommand2(commandArgs);
1507
+ break;
1508
+ }
1509
+ case "remove":
1510
+ case "uninstall":
1511
+ case "rm": {
1512
+ const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
1513
+ await removeCommand2(commandArgs);
1514
+ break;
1515
+ }
1516
+ case "list":
1517
+ case "ls": {
1518
+ const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_list(), list_exports));
1519
+ await listCommand2(commandArgs);
1520
+ break;
1521
+ }
1522
+ case "find":
1523
+ case "f":
1524
+ case "search": {
1525
+ const { findCommand: findCommand2 } = await Promise.resolve().then(() => (init_find(), find_exports));
1526
+ await findCommand2(commandArgs);
1527
+ break;
1528
+ }
1529
+ case "info":
1530
+ case "show": {
1531
+ const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
1532
+ await infoCommand2(commandArgs);
1533
+ break;
1534
+ }
1535
+ case "check":
1536
+ case "check-updates":
1537
+ case "update-check": {
1538
+ const { checkUpdatesCommand: checkUpdatesCommand2 } = await Promise.resolve().then(() => (init_check_updates(), check_updates_exports));
1539
+ await checkUpdatesCommand2(commandArgs);
1540
+ break;
1541
+ }
1542
+ default:
1543
+ p6.log.error(`Unknown command: ${pc6.red(command)}`);
1544
+ console.log();
1545
+ printHelp();
1546
+ process.exit(1);
1547
+ }
1548
+ } catch (err) {
1549
+ p6.log.error(err.message);
1550
+ process.exit(1);
1551
+ }
1552
+ p6.outro(pc6.dim("Done."));
1553
+ }
1554
+ main();