@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/CHANGELOG.md +19 -0
- package/README.md +27 -8
- package/dist/cli.mjs +289 -105
- package/dist/gui/assets/index-D5a5y70Q.css +1 -0
- package/dist/gui/assets/index-DN1MQ9V0.js +40 -0
- package/dist/gui/index.html +2 -2
- package/package.json +1 -1
- package/dist/gui/assets/index-BrbHzdSV.css +0 -1
- package/dist/gui/assets/index-DoUp5wXM.js +0 -40
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
|
|
39231
|
-
name: "Anthropic \u5B98\u65B9\u6280\u80FD\u5E93",
|
|
39232
|
-
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 [
|
|
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 ===
|
|
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 ===
|
|
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/
|
|
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 (!
|
|
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 (
|
|
39339
|
-
for (const e of
|
|
39340
|
-
if (e.isDirectory() && !config.skills[e.name] &&
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
39491
|
+
if (!fs9.existsSync(dir)) continue;
|
|
39381
39492
|
let entries = [];
|
|
39382
39493
|
try {
|
|
39383
|
-
entries =
|
|
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 =
|
|
39500
|
+
const linkPath = path12.join(dir, e.name);
|
|
39390
39501
|
let target = "";
|
|
39391
39502
|
try {
|
|
39392
|
-
target =
|
|
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 =
|
|
39409
|
-
const exposed =
|
|
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 =
|
|
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 (!
|
|
39558
|
+
if (!fs9.existsSync(path12.join(skillDir(name), "SKILL.md"))) continue;
|
|
39448
39559
|
for (const agent of AGENTS) {
|
|
39449
|
-
const target =
|
|
39450
|
-
const exposed =
|
|
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 =
|
|
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 =
|
|
39594
|
+
real = fs9.realpathSync(target);
|
|
39484
39595
|
} catch {
|
|
39485
39596
|
return false;
|
|
39486
39597
|
}
|
|
39487
39598
|
let storeReal = null;
|
|
39488
39599
|
try {
|
|
39489
|
-
storeReal =
|
|
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
|
|
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
|
|
39505
|
-
import
|
|
39615
|
+
import fs10 from "node:fs";
|
|
39616
|
+
import path13 from "node:path";
|
|
39506
39617
|
function isManagedByStore(dir) {
|
|
39507
39618
|
try {
|
|
39508
|
-
if (!
|
|
39509
|
-
const real =
|
|
39510
|
-
const storeReal =
|
|
39511
|
-
return real === storeReal || real.startsWith(storeReal +
|
|
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 (!
|
|
39631
|
+
if (!fs10.existsSync(dir)) return [];
|
|
39521
39632
|
const out = [];
|
|
39522
|
-
for (const e of
|
|
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 =
|
|
39526
|
-
if (!
|
|
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 !
|
|
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 =
|
|
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] ||
|
|
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
|
-
|
|
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
|
|
39788
|
+
import fs11 from "node:fs";
|
|
39678
39789
|
import os3 from "node:os";
|
|
39679
|
-
import
|
|
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 =
|
|
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 ?
|
|
39695
|
-
if (!
|
|
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
|
-
|
|
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 (!
|
|
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({
|
|
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 =
|
|
39878
|
+
const staging = fs11.mkdtempSync(path14.join(os3.tmpdir(), "skillpot-new-"));
|
|
39735
39879
|
try {
|
|
39736
|
-
|
|
39737
|
-
|
|
39738
|
-
|
|
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
|
-
|
|
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({
|
|
39888
|
+
results.push({
|
|
39889
|
+
skill: n,
|
|
39890
|
+
status: "updated",
|
|
39891
|
+
detail: entry.checksum.slice(0, 15),
|
|
39892
|
+
diff: diff2
|
|
39893
|
+
});
|
|
39746
39894
|
} finally {
|
|
39747
|
-
|
|
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
|
|
39762
|
-
import
|
|
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 =
|
|
39766
|
-
if (!
|
|
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
|
|
39770
|
-
const p =
|
|
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(
|
|
39920
|
+
files.push(path15.relative(dir, p) + "/");
|
|
39773
39921
|
walk(p);
|
|
39774
39922
|
} else {
|
|
39775
|
-
files.push(
|
|
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:
|
|
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) =>
|
|
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}}
|