@tec-explorer/skillpot 0.6.0 → 0.8.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/cli.mjs CHANGED
@@ -39109,7 +39109,28 @@ var DANGEROUS_PATTERNS = [
39109
39109
  [/\bcurl\s+[^\n|]*\|\s*(?:ba|z)?sh\b/, "curl \u7BA1\u9053\u6267\u884C\u811A\u672C"],
39110
39110
  [/\bwget\s+[^\n|]*\|\s*(?:ba|z)?sh\b/, "wget \u7BA1\u9053\u6267\u884C\u811A\u672C"],
39111
39111
  [/\bsudo\b/, "sudo \u63D0\u6743"],
39112
- [/\bchmod\s+777\b/, "chmod 777 \u5F00\u653E\u5199\u6743\u9650"]
39112
+ [/\bchmod\s+777\b/, "chmod 777 \u5F00\u653E\u5199\u6743\u9650"],
39113
+ // —— 凭据与敏感信息(§9.1:读取 env / credentials)——
39114
+ [/\.ssh[/']|\.aws[/']|\.kube[/']|\.netrc\b/, "\u89E6\u78B0 SSH/\u4E91\u5382\u5546\u51ED\u636E\u6587\u4EF6"],
39115
+ [
39116
+ /\b(?:API[_-]?KEY|APIKEY|SECRET[_-]?KEY|ACCESS[_-]?TOKEN|PRIVATE[_-]?KEY|AWS_SECRET[_-]?ACCESS[_-]?KEY)\b/i,
39117
+ "\u5F15\u7528\u5BC6\u94A5\u7C7B\u6807\u8BC6\u7B26"
39118
+ ],
39119
+ [/\bprintenv\b/, "printenv \u5BFC\u51FA\u73AF\u5883\u53D8\u91CF"],
39120
+ [
39121
+ /process\.env\.[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)\w*/i,
39122
+ "\u8BFB\u53D6 Node \u5BC6\u94A5\u7C7B\u73AF\u5883\u53D8\u91CF"
39123
+ ],
39124
+ // —— 外发数据(§9.1:外发网络请求)——
39125
+ [
39126
+ /\bcurl\b[^\n]*(?:-X\s*(?:POST|PUT)\b|--data(?:-binary|-raw|-urlencode)?\b|--form\b|--upload-file\b|\s-T\s)/,
39127
+ "curl \u5411\u5916\u53D1\u9001\u6570\u636E\uFF08POST/PUT/\u4E0A\u4F20\uFF09"
39128
+ ],
39129
+ [/\bwget\b[^\n]*--(?:post-data|post-file)\b/, "wget \u5411\u5916\u53D1\u9001\u6570\u636E"],
39130
+ [/\bnc\b[^\n]*\s-e\b/, "nc -e \u8FDC\u7A0B\u6267\u884C/\u53CD\u5F39 shell"],
39131
+ [/\b(?:scp|rsync)\b[^\n]*\b\w+@(?!localhost|127\.0\.0\.1)/, "\u5411\u8FDC\u7AEF\u4E3B\u673A\u62F7\u8D1D\u6587\u4EF6"],
39132
+ // —— 反取证 ——
39133
+ [/\.(?:bash|zsh)_history/, "\u89E6\u78B0 shell \u5386\u53F2\u6587\u4EF6"]
39113
39134
  ];
39114
39135
  var SCRIPT_EXT = /\.(sh|bash|zsh|py|js|mjs|cjs|ts|rb|pl)$/;
39115
39136
  function lintSkill(dir) {
@@ -39227,14 +39248,15 @@ import { execFile as execFile2 } from "node:child_process";
39227
39248
  import { promisify as promisify2 } from "node:util";
39228
39249
  var execFileP2 = promisify2(execFile2);
39229
39250
  var OFFICIAL_URL = "https://github.com/anthropics/skills.git";
39230
- var OFFICIAL = {
39231
- name: "Anthropic \u5B98\u65B9\u6280\u80FD\u5E93",
39232
- url: OFFICIAL_URL,
39233
- builtin: true
39234
- };
39251
+ var BUILTIN_SOURCES = [
39252
+ { name: "Anthropic \u5B98\u65B9\u6280\u80FD\u5E93", url: OFFICIAL_URL, builtin: true },
39253
+ { name: "Vercel \u5B98\u65B9\u6280\u80FD\u96C6", url: "https://github.com/vercel-labs/agent-skills.git", builtin: true },
39254
+ { name: "Superpowers \u793E\u533A\u6280\u80FD\u96C6", url: "https://github.com/obra/superpowers.git", builtin: true },
39255
+ { name: "Matt Pocock \u6280\u80FD\u96C6", url: "https://github.com/mattpocock/skills.git", builtin: true }
39256
+ ];
39235
39257
  function listSources() {
39236
39258
  const config = loadConfig();
39237
- return [OFFICIAL, ...(config.sources ?? []).map((s) => ({ name: s.name ?? urlLabel(s.url), url: s.url, builtin: false }))];
39259
+ return [...BUILTIN_SOURCES, ...(config.sources ?? []).map((s) => ({ name: s.name ?? urlLabel(s.url), url: s.url, builtin: false }))];
39238
39260
  }
39239
39261
  function urlLabel(url) {
39240
39262
  const tail = url.replace(/\.git$/, "").replace(/\/+$/, "");
@@ -39242,7 +39264,7 @@ function urlLabel(url) {
39242
39264
  }
39243
39265
  function addSource(url, name) {
39244
39266
  if (!isGitSource(url)) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684 git \u5730\u5740\uFF1A${url}`);
39245
- if (url === OFFICIAL_URL) throw new Error("\u5B98\u65B9\u6E90\u5DF2\u5185\u7F6E\uFF0C\u65E0\u9700\u6DFB\u52A0");
39267
+ if (BUILTIN_SOURCES.some((s) => s.url === url)) throw new Error("\u5185\u7F6E\u6E90\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u6DFB\u52A0");
39246
39268
  const config = loadConfig();
39247
39269
  config.sources = config.sources ?? [];
39248
39270
  if (config.sources.some((s) => s.url === url)) throw new Error(`\u6E90\u5DF2\u5B58\u5728\uFF1A${url}`);
@@ -39256,7 +39278,7 @@ function addSource(url, name) {
39256
39278
  return { name: entry.name ?? urlLabel(url), url, builtin: false };
39257
39279
  }
39258
39280
  function removeSource(url) {
39259
- if (url === OFFICIAL_URL) throw new Error("\u5185\u7F6E\u6E90\u4E0D\u53EF\u79FB\u9664");
39281
+ if (BUILTIN_SOURCES.some((s) => s.url === url)) throw new Error("\u5185\u7F6E\u6E90\u4E0D\u53EF\u79FB\u9664");
39260
39282
  const config = loadConfig();
39261
39283
  const before = (config.sources ?? []).length;
39262
39284
  config.sources = (config.sources ?? []).filter((s) => s.url !== url);
@@ -39319,25 +39341,114 @@ async function installFromMarket(url, subdir, opts = {}) {
39319
39341
  return addSkill(`${url}#${subdir}`, { name: opts.name, for: opts.for });
39320
39342
  }
39321
39343
 
39322
- // src/core/doctor.ts
39344
+ // src/tui/matrix.ts
39323
39345
  import fs8 from "node:fs";
39324
39346
  import path11 from "node:path";
39347
+ function deriveMatrix(agents) {
39348
+ const config = loadConfig();
39349
+ const skills = Object.keys(config.skills).sort();
39350
+ const list = agents ?? detectAll().map((r) => ({
39351
+ id: r.id,
39352
+ name: r.name,
39353
+ installed: r.installed,
39354
+ skillsDir: r.skillsDir
39355
+ }));
39356
+ const cells = {};
39357
+ for (const s of skills) {
39358
+ cells[s] = {};
39359
+ for (const a of list) {
39360
+ const target = path11.join(a.skillsDir, s);
39361
+ let actual = false;
39362
+ let managed = false;
39363
+ try {
39364
+ const st = fs8.lstatSync(target);
39365
+ actual = true;
39366
+ managed = st.isSymbolicLink() && fs8.realpathSync(target) === fs8.realpathSync(skillDir(s));
39367
+ } catch {
39368
+ }
39369
+ cells[s][a.id] = {
39370
+ enabled: config.skills[s].expose[a.id] === true,
39371
+ actual,
39372
+ managed
39373
+ };
39374
+ }
39375
+ }
39376
+ return { skills, agents: list, cells };
39377
+ }
39378
+
39379
+ // src/core/audit.ts
39380
+ function runAudit() {
39381
+ const matrix = deriveMatrix();
39382
+ const config = loadConfig();
39383
+ const agents = [];
39384
+ for (const a of matrix.agents) {
39385
+ if (!a.installed) continue;
39386
+ const active = [];
39387
+ const external = [];
39388
+ const findings = [];
39389
+ for (const skill of matrix.skills) {
39390
+ const cs = matrix.cells[skill]?.[a.id];
39391
+ if (!cs) continue;
39392
+ if (cs.actual && cs.managed) {
39393
+ active.push({
39394
+ skill,
39395
+ source: config.skills[skill]?.source ?? "\u672A\u767B\u8BB0\uFF08\u4E2D\u592E\u4ED3\u5E93\u5B58\u5728\u4F46 config \u7F3A\u5931\uFF09",
39396
+ enabled: cs.enabled
39397
+ });
39398
+ if (!cs.enabled) {
39399
+ findings.push({
39400
+ level: "warn",
39401
+ agent: a.id,
39402
+ message: `'${skill}' \u53D7\u7BA1\u94FE\u63A5\u4ECD\u5B58\u5728\uFF0C\u4F46\u77E9\u9635\u5DF2\u58F0\u660E\u5173\u95ED\uFF08disable \u6216 doctor --fix \u53EF\u6E05\u7406\uFF09`
39403
+ });
39404
+ }
39405
+ } else if (cs.actual && !cs.managed) {
39406
+ external.push({ name: skill, path: `${a.skillsDir}/${skill}` });
39407
+ if (cs.enabled) {
39408
+ findings.push({
39409
+ level: "error",
39410
+ agent: a.id,
39411
+ message: `'${skill}' \u58F0\u660E\u5BF9 ${a.id} \u5F00\u653E\uFF0C\u4F46\u8BE5\u4F4D\u7F6E\u88AB\u5916\u90E8\u540C\u540D\u6761\u76EE\u5360\u7528\u2014\u2014\u5B9E\u9645\u751F\u6548\u7684\u4E0D\u662F\u4E2D\u592E\u4ED3\u5E93\u7248\u672C\uFF08\u53EF\u80FD\u88AB\u7ED5\u8FC7/\u906E\u853D\uFF09`
39412
+ });
39413
+ }
39414
+ } else if (cs.enabled && !cs.actual) {
39415
+ findings.push({
39416
+ level: "warn",
39417
+ agent: a.id,
39418
+ message: `'${skill}' \u58F0\u660E\u5F00\u653E\u4F46\u94FE\u63A5\u7F3A\u5931\uFF08\u6F02\u79FB\uFF0Cdoctor --fix \u53EF\u91CD\u540C\u6B65\uFF09`
39419
+ });
39420
+ }
39421
+ }
39422
+ agents.push({
39423
+ agent: a.id,
39424
+ agentName: a.name,
39425
+ active,
39426
+ external,
39427
+ findings
39428
+ });
39429
+ }
39430
+ return { agents, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
39431
+ }
39432
+
39433
+ // src/core/doctor.ts
39434
+ import fs9 from "node:fs";
39435
+ import path12 from "node:path";
39325
39436
  function runDoctor() {
39326
39437
  const issues = [];
39327
39438
  const config = loadConfig();
39328
39439
  const state = loadState();
39329
39440
  const home = agentHome();
39330
39441
  for (const [name, entry] of Object.entries(config.skills)) {
39331
- if (!fs8.existsSync(path11.join(skillDir(name), "SKILL.md"))) {
39442
+ if (!fs9.existsSync(path12.join(skillDir(name), "SKILL.md"))) {
39332
39443
  issues.push({
39333
39444
  level: "error",
39334
39445
  message: `config \u4E2D\u7684 '${name}' \u5728\u4E2D\u592E\u4ED3\u5E93\u7F3A\u5931\uFF08source=${entry.source}\uFF09`
39335
39446
  });
39336
39447
  }
39337
39448
  }
39338
- if (fs8.existsSync(storeDir())) {
39339
- for (const e of fs8.readdirSync(storeDir(), { withFileTypes: true })) {
39340
- if (e.isDirectory() && !config.skills[e.name] && fs8.existsSync(path11.join(storeDir(), e.name, "SKILL.md"))) {
39449
+ if (fs9.existsSync(storeDir())) {
39450
+ for (const e of fs9.readdirSync(storeDir(), { withFileTypes: true })) {
39451
+ if (e.isDirectory() && !config.skills[e.name] && fs9.existsSync(path12.join(storeDir(), e.name, "SKILL.md"))) {
39341
39452
  issues.push({
39342
39453
  level: "warn",
39343
39454
  message: `\u4E2D\u592E\u4ED3\u5E93\u4E2D\u7684 '${e.name}' \u672A\u767B\u8BB0\u8FDB config\uFF08\u624B\u52A8\u62F7\u5165\uFF1F\uFF09`,
@@ -39349,7 +39460,7 @@ function runDoctor() {
39349
39460
  for (const link2 of state.links) {
39350
39461
  let st = null;
39351
39462
  try {
39352
- st = fs8.lstatSync(link2.link_path);
39463
+ st = fs9.lstatSync(link2.link_path);
39353
39464
  } catch {
39354
39465
  }
39355
39466
  if (!st) {
@@ -39367,7 +39478,7 @@ function runDoctor() {
39367
39478
  });
39368
39479
  continue;
39369
39480
  }
39370
- if (!fs8.existsSync(link2.link_path)) {
39481
+ if (!fs9.existsSync(link2.link_path)) {
39371
39482
  issues.push({
39372
39483
  level: "error",
39373
39484
  message: `\u65AD\u94FE\uFF1A${link2.link_path} \u6307\u5411\u7684\u76EE\u6807\u4E0D\u5B58\u5728`,
@@ -39377,19 +39488,19 @@ function runDoctor() {
39377
39488
  }
39378
39489
  for (const agent of AGENTS) {
39379
39490
  const dir = agent.skillsDir(home);
39380
- if (!fs8.existsSync(dir)) continue;
39491
+ if (!fs9.existsSync(dir)) continue;
39381
39492
  let entries = [];
39382
39493
  try {
39383
- entries = fs8.readdirSync(dir, { withFileTypes: true });
39494
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
39384
39495
  } catch {
39385
39496
  continue;
39386
39497
  }
39387
39498
  for (const e of entries) {
39388
39499
  if (!e.isSymbolicLink()) continue;
39389
- const linkPath = path11.join(dir, e.name);
39500
+ const linkPath = path12.join(dir, e.name);
39390
39501
  let target = "";
39391
39502
  try {
39392
- target = fs8.readlinkSync(linkPath);
39503
+ target = fs9.readlinkSync(linkPath);
39393
39504
  } catch {
39394
39505
  continue;
39395
39506
  }
@@ -39405,8 +39516,8 @@ function runDoctor() {
39405
39516
  }
39406
39517
  for (const [name, entry] of Object.entries(config.skills)) {
39407
39518
  for (const agent of AGENTS) {
39408
- const target = path11.join(agent.skillsDir(home), name);
39409
- const exposed = fs8.existsSync(target);
39519
+ const target = path12.join(agent.skillsDir(home), name);
39520
+ const exposed = fs9.existsSync(target);
39410
39521
  const wanted = entry.expose[agent.id] === true;
39411
39522
  if (wanted && !exposed) {
39412
39523
  issues.push({
@@ -39431,7 +39542,7 @@ function fixDoctor() {
39431
39542
  state.links = state.links.filter((l) => {
39432
39543
  let ok = false;
39433
39544
  try {
39434
- ok = fs8.existsSync(l.link_path) && fs8.lstatSync(l.link_path).isSymbolicLink();
39545
+ ok = fs9.existsSync(l.link_path) && fs9.lstatSync(l.link_path).isSymbolicLink();
39435
39546
  } catch {
39436
39547
  ok = false;
39437
39548
  }
@@ -39444,10 +39555,10 @@ function fixDoctor() {
39444
39555
  saveState(state);
39445
39556
  const config = loadConfig();
39446
39557
  for (const [name, entry] of Object.entries(config.skills)) {
39447
- if (!fs8.existsSync(path11.join(skillDir(name), "SKILL.md"))) continue;
39558
+ if (!fs9.existsSync(path12.join(skillDir(name), "SKILL.md"))) continue;
39448
39559
  for (const agent of AGENTS) {
39449
- const target = path11.join(agent.skillsDir(agentHome()), name);
39450
- const exposed = fs8.existsSync(target);
39560
+ const target = path12.join(agent.skillsDir(agentHome()), name);
39561
+ const exposed = fs9.existsSync(target);
39451
39562
  const wanted = entry.expose[agent.id] === true;
39452
39563
  if (wanted && !exposed) {
39453
39564
  try {
@@ -39469,7 +39580,7 @@ function fixDoctor() {
39469
39580
  function isOurSymlink(target, state, skill, agentId) {
39470
39581
  let isLink = false;
39471
39582
  try {
39472
- isLink = fs8.lstatSync(target).isSymbolicLink();
39583
+ isLink = fs9.lstatSync(target).isSymbolicLink();
39473
39584
  } catch {
39474
39585
  return false;
39475
39586
  }
@@ -39480,13 +39591,13 @@ function isOurSymlink(target, state, skill, agentId) {
39480
39591
  if (inLedger) return true;
39481
39592
  let real = null;
39482
39593
  try {
39483
- real = fs8.realpathSync(target);
39594
+ real = fs9.realpathSync(target);
39484
39595
  } catch {
39485
39596
  return false;
39486
39597
  }
39487
39598
  let storeReal = null;
39488
39599
  try {
39489
- storeReal = fs8.realpathSync(skillDir(skill));
39600
+ storeReal = fs9.realpathSync(skillDir(skill));
39490
39601
  } catch {
39491
39602
  return false;
39492
39603
  }
@@ -39494,21 +39605,21 @@ function isOurSymlink(target, state, skill, agentId) {
39494
39605
  }
39495
39606
  function sameRealPath(a, b) {
39496
39607
  try {
39497
- return fs8.realpathSync(a) === fs8.realpathSync(b);
39608
+ return fs9.realpathSync(a) === fs9.realpathSync(b);
39498
39609
  } catch {
39499
39610
  return false;
39500
39611
  }
39501
39612
  }
39502
39613
 
39503
39614
  // src/core/adopt.ts
39504
- import fs9 from "node:fs";
39505
- import path12 from "node:path";
39615
+ import fs10 from "node:fs";
39616
+ import path13 from "node:path";
39506
39617
  function isManagedByStore(dir) {
39507
39618
  try {
39508
- if (!fs9.lstatSync(dir).isSymbolicLink()) return false;
39509
- const real = fs9.realpathSync(dir);
39510
- const storeReal = fs9.realpathSync(storeDir());
39511
- return real === storeReal || real.startsWith(storeReal + path12.sep);
39619
+ if (!fs10.lstatSync(dir).isSymbolicLink()) return false;
39620
+ const real = fs10.realpathSync(dir);
39621
+ const storeReal = fs10.realpathSync(storeDir());
39622
+ return real === storeReal || real.startsWith(storeReal + path13.sep);
39512
39623
  } catch {
39513
39624
  return false;
39514
39625
  }
@@ -39517,13 +39628,13 @@ function scanAgentSkills(agentId) {
39517
39628
  const agent = getAgent(agentId);
39518
39629
  if (!agent) throw new Error(`\u672A\u77E5 agent '${agentId}'`);
39519
39630
  const dir = agent.skillsDir(agentHome());
39520
- if (!fs9.existsSync(dir)) return [];
39631
+ if (!fs10.existsSync(dir)) return [];
39521
39632
  const out = [];
39522
- for (const e of fs9.readdirSync(dir, { withFileTypes: true })) {
39633
+ for (const e of fs10.readdirSync(dir, { withFileTypes: true })) {
39523
39634
  if (e.name.startsWith(".")) continue;
39524
39635
  if (!e.isDirectory() && !e.isSymbolicLink()) continue;
39525
- const p = path12.join(dir, e.name);
39526
- if (!fs9.existsSync(path12.join(p, "SKILL.md"))) continue;
39636
+ const p = path13.join(dir, e.name);
39637
+ if (!fs10.existsSync(path13.join(p, "SKILL.md"))) continue;
39527
39638
  out.push({ name: e.name, path: p });
39528
39639
  }
39529
39640
  return out;
@@ -39531,7 +39642,7 @@ function scanAgentSkills(agentId) {
39531
39642
  function scanAdoptable(agentId) {
39532
39643
  return scanAgentSkills(agentId).filter((s) => {
39533
39644
  try {
39534
- return !fs9.lstatSync(s.path).isSymbolicLink();
39645
+ return !fs10.lstatSync(s.path).isSymbolicLink();
39535
39646
  } catch {
39536
39647
  return false;
39537
39648
  }
@@ -39562,7 +39673,7 @@ function adoptSkills(opts = {}) {
39562
39673
  }
39563
39674
  let isOwnSymlink = false;
39564
39675
  try {
39565
- isOwnSymlink = fs9.lstatSync(skill.path).isSymbolicLink();
39676
+ isOwnSymlink = fs10.lstatSync(skill.path).isSymbolicLink();
39566
39677
  } catch {
39567
39678
  }
39568
39679
  if (isOwnSymlink) {
@@ -39582,7 +39693,7 @@ function adoptSkills(opts = {}) {
39582
39693
  items.push({ agent: agentId, name: skill.name, path: skill.path, status: "skipped-invalid", detail: "\u76EE\u5F55\u540D\u4E0D\u5408\u6CD5" });
39583
39694
  continue;
39584
39695
  }
39585
- const existsInStore = !!config.skills[name] || fs9.existsSync(skillDir(name));
39696
+ const existsInStore = !!config.skills[name] || fs10.existsSync(skillDir(name));
39586
39697
  if (opts.dryRun) {
39587
39698
  items.push({
39588
39699
  agent: agentId,
@@ -39639,7 +39750,7 @@ function adoptSkills(opts = {}) {
39639
39750
  saveConfig(config);
39640
39751
  for (const mv of pendingMoves) {
39641
39752
  try {
39642
- fs9.rmSync(mv.path, { recursive: true, force: true });
39753
+ fs10.rmSync(mv.path, { recursive: true, force: true });
39643
39754
  enableSkill(mv.name, [mv.agentId]);
39644
39755
  items.push({
39645
39756
  agent: mv.agentId,
@@ -39674,30 +39785,56 @@ function adoptSkills(opts = {}) {
39674
39785
  }
39675
39786
 
39676
39787
  // src/core/update.ts
39677
- import fs10 from "node:fs";
39788
+ import fs11 from "node:fs";
39678
39789
  import os3 from "node:os";
39679
- import path13 from "node:path";
39790
+ import path14 from "node:path";
39680
39791
  import { execFile as execFile3 } from "node:child_process";
39681
39792
  import { promisify as promisify3 } from "node:util";
39682
39793
  var execFileP3 = promisify3(execFile3);
39794
+ function diffTree(oldDir, newDir) {
39795
+ const IGNORED2 = /* @__PURE__ */ new Set([".git"]);
39796
+ const collect = (root) => {
39797
+ const map = /* @__PURE__ */ new Map();
39798
+ const walk = (d) => {
39799
+ for (const e of fs11.readdirSync(d, { withFileTypes: true })) {
39800
+ if (IGNORED2.has(e.name)) continue;
39801
+ const p = path14.join(d, e.name);
39802
+ if (e.isDirectory()) walk(p);
39803
+ else if (e.isFile()) map.set(path14.relative(root, p), p);
39804
+ }
39805
+ };
39806
+ walk(root);
39807
+ return map;
39808
+ };
39809
+ const oldFiles = collect(oldDir);
39810
+ const newFiles = collect(newDir);
39811
+ const diff2 = { added: [], removed: [], modified: [] };
39812
+ for (const [rel, p] of newFiles) {
39813
+ if (!oldFiles.has(rel)) diff2.added.push(rel);
39814
+ else if (fs11.readFileSync(p) !== fs11.readFileSync(oldFiles.get(rel))) diff2.modified.push(rel);
39815
+ }
39816
+ for (const rel of oldFiles.keys()) if (!newFiles.has(rel)) diff2.removed.push(rel);
39817
+ for (const k of ["added", "removed", "modified"]) diff2[k].sort();
39818
+ return diff2;
39819
+ }
39683
39820
  function parseGitSource(source) {
39684
39821
  return source.startsWith("git:") ? source.slice(4) : null;
39685
39822
  }
39686
39823
  async function fetchRemote(repoSpec) {
39687
39824
  const [url, sub] = repoSpec.split("#");
39688
- const cloneDir = fs10.mkdtempSync(path13.join(os3.tmpdir(), "skillpot-update-"));
39825
+ const cloneDir = fs11.mkdtempSync(path14.join(os3.tmpdir(), "skillpot-update-"));
39689
39826
  try {
39690
39827
  await execFileP3("git", ["clone", "--depth", "1", url, cloneDir], {
39691
39828
  timeout: 3e5,
39692
39829
  maxBuffer: 16 * 1024 * 1024
39693
39830
  });
39694
- const contentDir = sub ? path13.join(cloneDir, sub) : cloneDir;
39695
- if (!fs10.existsSync(path13.join(contentDir, "SKILL.md"))) {
39831
+ const contentDir = sub ? path14.join(cloneDir, sub) : cloneDir;
39832
+ if (!fs11.existsSync(path14.join(contentDir, "SKILL.md"))) {
39696
39833
  throw new Error(`\u8FDC\u7AEF ${sub ? `#${sub} ` : ""}\u4E2D\u6CA1\u6709 SKILL.md`);
39697
39834
  }
39698
39835
  return { checksum: dirChecksum(contentDir), cloneDir, sub };
39699
39836
  } catch (e) {
39700
- fs10.rmSync(cloneDir, { recursive: true, force: true });
39837
+ fs11.rmSync(cloneDir, { recursive: true, force: true });
39701
39838
  throw e;
39702
39839
  }
39703
39840
  }
@@ -39716,7 +39853,7 @@ async function updateSkills(name, opts = {}) {
39716
39853
  results.push({ skill: n, status: "local", detail: entry.source });
39717
39854
  continue;
39718
39855
  }
39719
- if (!fs10.existsSync(skillDir(n))) {
39856
+ if (!fs11.existsSync(skillDir(n))) {
39720
39857
  results.push({ skill: n, status: "error", detail: "\u4E2D\u592E\u4ED3\u5E93\u7F3A\u5931" });
39721
39858
  continue;
39722
39859
  }
@@ -39727,24 +39864,35 @@ async function updateSkills(name, opts = {}) {
39727
39864
  results.push({ skill: n, status: "up-to-date" });
39728
39865
  continue;
39729
39866
  }
39867
+ const srcDir = fetched.sub ? path14.join(fetched.cloneDir, fetched.sub) : fetched.cloneDir;
39868
+ const diff2 = diffTree(skillDir(n), srcDir);
39730
39869
  if (opts.check) {
39731
- results.push({ skill: n, status: "outdated", detail: `\u8FDC\u7AEF ${fetched.checksum.slice(0, 15)}` });
39870
+ results.push({
39871
+ skill: n,
39872
+ status: "outdated",
39873
+ detail: `\u8FDC\u7AEF ${fetched.checksum.slice(0, 15)}`,
39874
+ diff: diff2
39875
+ });
39732
39876
  continue;
39733
39877
  }
39734
- const staging = fs10.mkdtempSync(path13.join(os3.tmpdir(), "skillpot-new-"));
39878
+ const staging = fs11.mkdtempSync(path14.join(os3.tmpdir(), "skillpot-new-"));
39735
39879
  try {
39736
- const srcDir = fetched.sub ? path13.join(fetched.cloneDir, fetched.sub) : fetched.cloneDir;
39737
- fs10.cpSync(srcDir, staging, { recursive: true });
39738
- fs10.rmSync(skillDir(n), { recursive: true, force: true });
39739
- fs10.cpSync(staging, skillDir(n), { recursive: true });
39880
+ fs11.cpSync(srcDir, staging, { recursive: true });
39881
+ fs11.rmSync(skillDir(n), { recursive: true, force: true });
39882
+ fs11.cpSync(staging, skillDir(n), { recursive: true });
39740
39883
  } finally {
39741
- fs10.rmSync(staging, { recursive: true, force: true });
39884
+ fs11.rmSync(staging, { recursive: true, force: true });
39742
39885
  }
39743
39886
  entry.checksum = dirChecksum(skillDir(n));
39744
39887
  entry.installed_at = (/* @__PURE__ */ new Date()).toISOString();
39745
- results.push({ skill: n, status: "updated", detail: entry.checksum.slice(0, 15) });
39888
+ results.push({
39889
+ skill: n,
39890
+ status: "updated",
39891
+ detail: entry.checksum.slice(0, 15),
39892
+ diff: diff2
39893
+ });
39746
39894
  } finally {
39747
- fs10.rmSync(fetched.cloneDir, { recursive: true, force: true });
39895
+ fs11.rmSync(fetched.cloneDir, { recursive: true, force: true });
39748
39896
  }
39749
39897
  } catch (e) {
39750
39898
  results.push({ skill: n, status: "error", detail: e instanceof Error ? e.message : String(e) });
@@ -39758,21 +39906,21 @@ async function updateSkills(name, opts = {}) {
39758
39906
  import readline from "node:readline";
39759
39907
 
39760
39908
  // src/core/skill-detail.ts
39761
- import fs11 from "node:fs";
39762
- import path14 from "node:path";
39909
+ import fs12 from "node:fs";
39910
+ import path15 from "node:path";
39763
39911
  function readSkillDetail(name) {
39764
39912
  const dir = skillDir(name);
39765
- const mdPath = path14.join(dir, "SKILL.md");
39766
- if (!fs11.existsSync(mdPath)) return null;
39913
+ const mdPath = path15.join(dir, "SKILL.md");
39914
+ if (!fs12.existsSync(mdPath)) return null;
39767
39915
  const files = [];
39768
39916
  const walk = (d) => {
39769
- for (const e of fs11.readdirSync(d, { withFileTypes: true })) {
39770
- const p = path14.join(d, e.name);
39917
+ for (const e of fs12.readdirSync(d, { withFileTypes: true })) {
39918
+ const p = path15.join(d, e.name);
39771
39919
  if (e.isDirectory()) {
39772
- files.push(path14.relative(dir, p) + "/");
39920
+ files.push(path15.relative(dir, p) + "/");
39773
39921
  walk(p);
39774
39922
  } else {
39775
- files.push(path14.relative(dir, p));
39923
+ files.push(path15.relative(dir, p));
39776
39924
  }
39777
39925
  }
39778
39926
  };
@@ -39781,7 +39929,7 @@ function readSkillDetail(name) {
39781
39929
  name,
39782
39930
  meta: readSkillMeta(dir),
39783
39931
  files: files.sort(),
39784
- skillMd: fs11.readFileSync(mdPath, "utf8")
39932
+ skillMd: fs12.readFileSync(mdPath, "utf8")
39785
39933
  };
39786
39934
  }
39787
39935
 
@@ -39918,41 +40066,6 @@ import path16 from "node:path";
39918
40066
  import { spawn } from "node:child_process";
39919
40067
  import { fileURLToPath } from "node:url";
39920
40068
 
39921
- // src/tui/matrix.ts
39922
- import fs12 from "node:fs";
39923
- import path15 from "node:path";
39924
- function deriveMatrix(agents) {
39925
- const config = loadConfig();
39926
- const skills = Object.keys(config.skills).sort();
39927
- const list = agents ?? detectAll().map((r) => ({
39928
- id: r.id,
39929
- name: r.name,
39930
- installed: r.installed,
39931
- skillsDir: r.skillsDir
39932
- }));
39933
- const cells = {};
39934
- for (const s of skills) {
39935
- cells[s] = {};
39936
- for (const a of list) {
39937
- const target = path15.join(a.skillsDir, s);
39938
- let actual = false;
39939
- let managed = false;
39940
- try {
39941
- const st = fs12.lstatSync(target);
39942
- actual = true;
39943
- managed = st.isSymbolicLink() && fs12.realpathSync(target) === fs12.realpathSync(skillDir(s));
39944
- } catch {
39945
- }
39946
- cells[s][a.id] = {
39947
- enabled: config.skills[s].expose[a.id] === true,
39948
- actual,
39949
- managed
39950
- };
39951
- }
39952
- }
39953
- return { skills, agents: list, cells };
39954
- }
39955
-
39956
40069
  // src/tui/cells.ts
39957
40070
  var import_picocolors = __toESM(require_picocolors());
39958
40071
  function cellGlyph(cs) {
@@ -40119,6 +40232,25 @@ async function handleApiRequest(method, pathname, query, body, expectedToken, go
40119
40232
  uninstallSkill(b.skill);
40120
40233
  return { status: 200, body: { ok: true, message: `\u5DF2\u5378\u8F7D ${b.skill}` } };
40121
40234
  }
40235
+ if (method === "POST" && pathname === "/api/bulk") {
40236
+ const b = body ?? {};
40237
+ if (typeof b.agent !== "string" || typeof b.enable !== "boolean") {
40238
+ return { status: 400, body: { error: "\u9700\u8981 agent \u5B57\u7B26\u4E32\u4E0E enable \u5E03\u5C14\u5B57\u6BB5" } };
40239
+ }
40240
+ const names = Object.keys(loadConfig().skills).sort();
40241
+ const changed = [];
40242
+ const skipped = [];
40243
+ for (const n of names) {
40244
+ try {
40245
+ const r = b.enable ? enableSkill(n, [b.agent]) : disableSkill(n, [b.agent]);
40246
+ if (r.linked.length) changed.push(n);
40247
+ else if (r.skipped.length) skipped.push({ skill: n, reason: r.skipped[0].reason });
40248
+ } catch (e) {
40249
+ skipped.push({ skill: n, reason: e instanceof Error ? e.message : String(e) });
40250
+ }
40251
+ }
40252
+ return { status: 200, body: { changed, skipped } };
40253
+ }
40122
40254
  if (method === "GET" && pathname === "/api/market/sources") {
40123
40255
  return { status: 200, body: { sources: listSources() } };
40124
40256
  }
@@ -46268,6 +46400,45 @@ program2.command("doctor").description("\u4F53\u68C0\uFF1A\u65AD\u94FE\u3001expo
46268
46400
  printIssues(issues);
46269
46401
  })
46270
46402
  );
46403
+ program2.command("audit").description("\u5BA1\u8BA1\uFF1A\u6BCF\u4E2A Agent \u5B9E\u9645\u751F\u6548\u7684 skill\u3001\u6765\u6E90\u4E0E\u88AB\u7ED5\u8FC7/\u906E\u853D\u60C5\u51B5").option("--json", "\u4EE5 JSON \u8F93\u51FA").action(
46404
+ run((opts) => {
46405
+ const report = runAudit();
46406
+ if (opts.json) {
46407
+ console.log(JSON.stringify(report, null, 2));
46408
+ return;
46409
+ }
46410
+ for (const a of report.agents) {
46411
+ console.log(
46412
+ import_picocolors2.default.bold(`${a.agentName} (${a.agent})`) + ` \u2014 \u5B9E\u9645\u751F\u6548 ${a.active.length} \u4E2A`
46413
+ );
46414
+ if (a.active.length) {
46415
+ console.log(
46416
+ renderTable(
46417
+ ["Skill", "\u6765\u6E90", "\u58F0\u660E"],
46418
+ a.active.map((e) => [
46419
+ e.skill,
46420
+ e.source.length > 64 ? e.source.slice(0, 61) + "\u2026" : e.source,
46421
+ e.enabled ? "\u5F00\u653E" : import_picocolors2.default.yellow("\u5173\u95ED\uFF08\u94FE\u63A5\u6B8B\u7559\uFF09")
46422
+ ])
46423
+ )
46424
+ );
46425
+ } else {
46426
+ console.log(import_picocolors2.default.dim(" \uFF08\u65E0\u751F\u6548 skill\uFF09"));
46427
+ }
46428
+ for (const e of a.external) {
46429
+ console.log(import_picocolors2.default.yellow(`\u26A0 \u5916\u90E8\u540C\u540D\u6761\u76EE\uFF08\u975E\u672C\u5DE5\u5177\u521B\u5EFA\uFF09\uFF1A${e.path}`));
46430
+ }
46431
+ for (const f of a.findings) {
46432
+ console.log(f.level === "error" ? import_picocolors2.default.red(`\u2717 ${f.message}`) : import_picocolors2.default.yellow(`\u26A0 ${f.message}`));
46433
+ }
46434
+ console.log();
46435
+ }
46436
+ const total = report.agents.reduce((n, a) => n + a.findings.length, 0);
46437
+ console.log(
46438
+ total ? import_picocolors2.default.yellow(`\u5BA1\u8BA1\u5B8C\u6210\uFF0C\u5171 ${total} \u6761\u53D1\u73B0\uFF0C\u8BE6\u89C1\u4E0A\u8868`) : import_picocolors2.default.green("\u5BA1\u8BA1\u901A\u8FC7\uFF1A\u5404 Agent \u5B9E\u9645\u751F\u6548\u72B6\u6001\u4E0E\u77E9\u9635\u4E00\u81F4")
46439
+ );
46440
+ })
46441
+ );
46271
46442
  function printIssues(issues) {
46272
46443
  for (const i of issues) {
46273
46444
  const tag = i.level === "error" ? import_picocolors2.default.red("error") : import_picocolors2.default.yellow("warn ");
@@ -46355,7 +46526,20 @@ program2.command("update [skill]").description("\u68C0\u67E5\u5E76\u5E94\u7528 g
46355
46526
  console.log(
46356
46527
  renderTable(
46357
46528
  ["Skill", "\u72B6\u6001", "\u5907\u6CE8"],
46358
- results.map((r) => [r.skill, label[r.status] ?? r.status, r.detail ?? ""])
46529
+ results.map((r) => {
46530
+ let note = r.detail ?? "";
46531
+ if (r.diff) {
46532
+ const { added, modified, removed } = r.diff;
46533
+ const counts = [
46534
+ added.length ? `+${added.length}` : "",
46535
+ modified.length ? `~${modified.length}` : "",
46536
+ removed.length ? `-${removed.length}` : ""
46537
+ ].filter(Boolean).join(" ");
46538
+ const paths = [...added, ...modified, ...removed];
46539
+ note = `${note ? note + " " : ""}diff ${counts}\uFF08${paths.slice(0, 4).join("\u3001")}${paths.length > 4 ? " \u7B49" : ""}\uFF09`;
46540
+ }
46541
+ return [r.skill, label[r.status] ?? r.status, note];
46542
+ })
46359
46543
  )
46360
46544
  );
46361
46545
  })
@@ -0,0 +1 @@
1
+ :root{--bg: #f6f7f9;--panel: #ffffff;--ink: #1c2330;--dim: #8a93a3;--line: #e3e7ee;--accent: #2563eb;--ok: #16a34a;--warn: #d97706;--conflict: #dc2626;--muted: #c8cdd6}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.6 -apple-system,PingFang SC,Segoe UI,Microsoft YaHei,sans-serif}.app{max-width:980px;margin:0 auto;padding:0 20px 60px}.topbar{display:flex;align-items:center;gap:24px;padding:14px 0;border-bottom:1px solid var(--line);margin-bottom:20px;flex-wrap:wrap}.brand{font-size:18px;font-weight:700}.brand .ver{font-size:12px;color:var(--dim);font-weight:400;margin-left:4px}.tabs{display:flex;gap:6px}.tab{border:1px solid transparent;background:none;padding:6px 14px;border-radius:8px;font-size:14px;color:var(--dim);cursor:pointer}.tab.active{background:var(--panel);border-color:var(--line);color:var(--ink);font-weight:600}.meta{margin-left:auto;color:var(--dim);font-size:12px}.toolbar{display:flex;align-items:center;gap:12px;margin-bottom:12px}.toolbar .input{min-width:200px;max-width:320px}.segment{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--panel)}.seg-btn{border:none;background:none;padding:6px 14px;font-size:13px;color:var(--dim);cursor:pointer}.seg-btn+.seg-btn{border-left:1px solid var(--line)}.seg-btn.active{background:var(--accent);color:#fff}.danger-btn{color:var(--conflict)}.matrix{border-collapse:collapse;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;width:100%}.matrix th,.matrix td{border-bottom:1px solid var(--line);padding:9px 14px;text-align:center}.matrix thead th{background:#fafbfc;font-size:13px;color:var(--ink)}.col-bulk{display:flex;gap:4px;justify-content:center;margin-top:3px}.bulk-btn{border:1px solid var(--line);background:var(--panel);border-radius:5px;font-size:10.5px;padding:0 6px;color:var(--dim);cursor:pointer}.bulk-btn:hover:not(:disabled){color:var(--accent);border-color:var(--accent)}.bulk-btn:disabled{opacity:.45;cursor:not-allowed}.load-more{text-align:center;margin-top:14px}.load-more .btn{padding:8px 24px}.matrix tr:last-child td{border-bottom:none}.matrix .skill-col,.matrix .skill-name{text-align:left;font-weight:600;min-width:160px}.matrix .skill-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:500}.matrix .agent-off{color:var(--dim)}.cell{cursor:pointer;font-size:16px;-webkit-user-select:none;user-select:none;transition:background .12s}.cell:hover{filter:brightness(.94)}.cell.busy{opacity:.4;cursor:wait}.cell.ok{color:var(--ok);background:#f0fdf4}.cell.warn{color:var(--warn);background:#fffbeb}.cell.conflict{color:var(--conflict);background:#fef2f2}.cell.muted{color:var(--muted);background:var(--panel)}.legend{color:var(--dim);font-size:12.5px;margin-top:12px}.dim{color:var(--dim)}.doctor-head{display:flex;align-items:center;justify-content:space-between;gap:16px}.doctor-head h2{font-size:16px;margin:0 0 12px}.btn{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-bottom:12px}.btn:disabled{opacity:.45;cursor:not-allowed}.issue-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.issue{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 14px;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}.issue.level-error{border-left:3px solid var(--conflict)}.issue.level-warn{border-left:3px solid var(--warn)}.issue.level-info{border-left:3px solid var(--accent)}.badge{font-size:11px;padding:1px 8px;border-radius:999px;color:#fff;flex-shrink:0}.badge-error{background:var(--conflict)}.badge-warn{background:var(--warn)}.badge-info{background:var(--accent)}.issue-msg{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;word-break:break-all}.fix-hint{color:var(--dim);font-size:12px}.link{cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px}.link:hover{color:var(--accent)}.update-table .src{max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn.small-btn{padding:4px 12px;margin:0;font-size:12px}.btn.danger{background:var(--conflict)}.modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;background:#0f172a73;display:flex;align-items:flex-start;justify-content:center;padding:48px 16px;z-index:20;overflow:auto}.modal{background:var(--panel);border-radius:12px;padding:20px 24px;width:100%;max-width:760px;box-shadow:0 16px 48px #00000040}.modal-head{display:flex;align-items:center;justify-content:space-between;gap:16px}.modal-head h2{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:17px}.modal-actions{display:flex;gap:8px}.modal-actions .tab{border-color:var(--line)}.detail-files{margin:12px 0}.file-list{background:#fafbfc;border:1px solid var(--line);border-radius:8px;padding:8px 12px;max-height:140px;overflow:auto}.skill-md{background:#fafbfc;border:1px solid var(--line);border-radius:8px;padding:12px 16px;font-size:12.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;max-height:320px;overflow:auto;white-space:pre-wrap;word-break:break-word}.panel h2{font-size:16px;margin:0 0 6px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:18px 20px}.form-row{display:flex;align-items:center;gap:16px;margin:12px 0;flex-wrap:wrap}.form-row.options{gap:24px}.input{border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:13px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;min-width:260px}.input:focus{outline:2px solid rgb(37 99 235 / 35%);border-color:var(--accent)}.grow{flex:1}.agent-picker{display:flex;gap:12px;flex-wrap:wrap}.pick{display:inline-flex;align-items:center;gap:5px;font-size:13px;cursor:pointer;white-space:nowrap}.pick.off{color:var(--dim)}.adopt-group{margin:14px 0}.adopt-agent{font-weight:700;margin-bottom:6px}.adopt-item{display:flex;align-items:center;gap:10px;padding:7px 10px;border:1px solid var(--line);border-radius:8px;margin-bottom:6px;cursor:pointer;background:#fdfdfe}.adopt-item.off{opacity:.55;cursor:not-allowed}.adopt-item .path{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.result-box{margin-top:16px;border:1px solid var(--line);border-left:3px solid var(--ok);border-radius:8px;padding:12px 16px;display:flex;flex-direction:column;gap:4px}.result-title{font-weight:700}.lint-box{border-top:1px dashed var(--line);margin-top:6px;padding-top:8px;display:flex;flex-direction:column;gap:3px}.warn-text{color:var(--warn);font-size:13px}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.small{font-size:12px}.empty,.loading{padding:60px 0;text-align:center;color:var(--dim)}.empty code{background:#eceff3;padding:1px 6px;border-radius:4px}.toasts{position:fixed;bottom:24px;left:50%;transform:translate(-50%);display:flex;flex-direction:column;gap:8px;align-items:center;z-index:10}.toast{background:#111827;color:#f9fafb;padding:9px 18px;border-radius:8px;font-size:13px;box-shadow:0 6px 24px #0000002e;animation:rise .18s ease-out}.toast.bad{background:#7f1d1d}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}