@hasna/skills 0.10.28 → 0.10.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/bin/index.js +1149 -921
- package/bin/mcp.js +357 -278
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +684 -471
- package/dist/lib/agent-adapters.d.ts +7 -1
- package/dist/lib/agent-sumi.d.ts +15 -0
- package/dist/lib/native-discovery-roots.d.ts +1 -1
- package/dist/lib/registry.d.ts +20 -3
- package/dist/lib/selection-resolver.d.ts +1 -0
- package/dist/lib/skill-context.d.ts +2 -0
- package/dist/sdk/index.js +676 -467
- package/docs/plugin-admission.md +21 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6988,8 +6988,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6988
6988
|
});
|
|
6989
6989
|
|
|
6990
6990
|
// src/lib/registry.ts
|
|
6991
|
-
import { existsSync as
|
|
6992
|
-
import { join as
|
|
6991
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18, readdirSync as readdirSync12 } from "fs";
|
|
6992
|
+
import { join as join31 } from "path";
|
|
6993
6993
|
|
|
6994
6994
|
// src/lib/config.ts
|
|
6995
6995
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -7252,17 +7252,17 @@ function unsetConfig(key, scope = "project") {
|
|
|
7252
7252
|
|
|
7253
7253
|
// src/lib/portable-skills.ts
|
|
7254
7254
|
import {
|
|
7255
|
-
existsSync as
|
|
7255
|
+
existsSync as existsSync14,
|
|
7256
7256
|
mkdirSync as mkdirSync9,
|
|
7257
7257
|
mkdtempSync as mkdtempSync2,
|
|
7258
7258
|
readdirSync as readdirSync11,
|
|
7259
|
-
realpathSync as
|
|
7259
|
+
realpathSync as realpathSync10,
|
|
7260
7260
|
renameSync as renameSync5,
|
|
7261
7261
|
rmSync as rmSync4,
|
|
7262
7262
|
statSync as statSync10,
|
|
7263
7263
|
writeFileSync as writeFileSync8
|
|
7264
7264
|
} from "fs";
|
|
7265
|
-
import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as
|
|
7265
|
+
import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as join30, normalize as normalize4, relative as relative6, resolve as resolve18, sep as sep8 } from "path";
|
|
7266
7266
|
|
|
7267
7267
|
// src/lib/registry-data/index.ts
|
|
7268
7268
|
var SKILLS = [];
|
|
@@ -8904,6 +8904,8 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8904
8904
|
["codex", ".codex/skills"],
|
|
8905
8905
|
["codex", ".agents/skills"],
|
|
8906
8906
|
["gemini", ".gemini/skills"],
|
|
8907
|
+
["sumi", ".sumi/skills"],
|
|
8908
|
+
["sumi", ".sumi/skill"],
|
|
8907
8909
|
["codewith", ".codewith/skills"],
|
|
8908
8910
|
["opencode", ".config/opencode/skills"],
|
|
8909
8911
|
["opencode", ".opencode/skills"],
|
|
@@ -8918,12 +8920,12 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8918
8920
|
];
|
|
8919
8921
|
|
|
8920
8922
|
// src/lib/authoring-path.ts
|
|
8921
|
-
import { lstatSync as
|
|
8923
|
+
import { lstatSync as lstatSync23, realpathSync as realpathSync9 } from "fs";
|
|
8922
8924
|
import { homedir as homedir6 } from "os";
|
|
8923
|
-
import { basename as basename10, dirname as dirname18, join as
|
|
8925
|
+
import { basename as basename10, dirname as dirname18, join as join29, resolve as resolve17, sep as sep7 } from "path";
|
|
8924
8926
|
|
|
8925
8927
|
// src/lib/agent-adapters.ts
|
|
8926
|
-
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes"];
|
|
8928
|
+
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes", "sumi"];
|
|
8927
8929
|
function renderAgentHookCommand(command, agent, profileId, event) {
|
|
8928
8930
|
if (typeof profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) || profileId.includes(".."))
|
|
8929
8931
|
throw new Error("Invalid selection profile id");
|
|
@@ -8934,6 +8936,7 @@ var AGENT_ADAPTERS = {
|
|
|
8934
8936
|
codex: { root: ".codex/skills", config: ".codex/hooks.json", events: ["UserPromptSubmit", "SessionStart", "SubagentStart"], promptContext: true },
|
|
8935
8937
|
gemini: { root: ".gemini/skills", config: ".gemini/settings.json", events: ["BeforeAgent", "SessionStart"], promptContext: true },
|
|
8936
8938
|
opencode: { root: ".config/opencode/skills", config: ".config/opencode/opencode.json", events: ["chat.message"], promptContext: true },
|
|
8939
|
+
sumi: { root: ".hasna-internal/sumi/config/skills", config: ".hasna-internal/sumi/config/sumi.json", events: ["prompt", "context"], promptContext: true },
|
|
8937
8940
|
hermes: { root: ".hermes/skills", config: ".hermes/config.yaml", events: ["pre_llm_call", "pre_tool_call"], promptContext: true, promptFailureMode: "open", toolFailureMode: "supervised-child-errors-block", requiresNativeTrust: true },
|
|
8938
8941
|
cursor: { root: ".cursor/skills", config: ".cursor/hooks.json", events: ["beforeSubmitPrompt", "sessionStart"], promptContext: false }
|
|
8939
8942
|
};
|
|
@@ -9139,8 +9142,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9139
9142
|
|
|
9140
9143
|
// src/lib/agent-discovery.ts
|
|
9141
9144
|
import { createHash as createHash18 } from "crypto";
|
|
9142
|
-
import { lstatSync as
|
|
9143
|
-
import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as
|
|
9145
|
+
import { lstatSync as lstatSync22, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync8 } from "fs";
|
|
9146
|
+
import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join28, resolve as resolve16, sep as sep6 } from "path";
|
|
9144
9147
|
import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
|
|
9145
9148
|
|
|
9146
9149
|
// node_modules/yaml/dist/index.js
|
|
@@ -9652,12 +9655,12 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
9652
9655
|
}
|
|
9653
9656
|
|
|
9654
9657
|
// src/lib/plugin-discovery.ts
|
|
9655
|
-
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as
|
|
9656
|
-
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as
|
|
9658
|
+
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync19, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
9659
|
+
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join27, resolve as resolve14 } from "path";
|
|
9657
9660
|
|
|
9658
9661
|
// src/lib/plugin-admission.ts
|
|
9659
9662
|
import { homedir as homedir5 } from "os";
|
|
9660
|
-
import { isAbsolute as isAbsolute13, join as
|
|
9663
|
+
import { isAbsolute as isAbsolute13, join as join26, resolve as resolve13 } from "path";
|
|
9661
9664
|
|
|
9662
9665
|
// src/lib/selection-cache.ts
|
|
9663
9666
|
import { createHash as createHash6, randomUUID } from "crypto";
|
|
@@ -10914,7 +10917,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
10914
10917
|
// package.json
|
|
10915
10918
|
var package_default = {
|
|
10916
10919
|
name: "@hasna/skills",
|
|
10917
|
-
version: "0.10.
|
|
10920
|
+
version: "0.10.31",
|
|
10918
10921
|
description: "Skills library for AI coding agents",
|
|
10919
10922
|
type: "module",
|
|
10920
10923
|
bin: {
|
|
@@ -12724,19 +12727,19 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
|
|
|
12724
12727
|
}
|
|
12725
12728
|
|
|
12726
12729
|
// src/lib/skillinfo.ts
|
|
12727
|
-
import { existsSync as
|
|
12728
|
-
import { join as
|
|
12730
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
12731
|
+
import { join as join23 } from "path";
|
|
12729
12732
|
|
|
12730
12733
|
// src/lib/installer.ts
|
|
12731
|
-
import { existsSync as
|
|
12732
|
-
import { join as
|
|
12734
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync2 } from "fs";
|
|
12735
|
+
import { join as join21 } from "path";
|
|
12733
12736
|
import { homedir as homedir4 } from "os";
|
|
12734
12737
|
|
|
12735
12738
|
// src/lib/agent-sync.ts
|
|
12736
12739
|
import {
|
|
12737
12740
|
cpSync as cpSync2,
|
|
12738
|
-
existsSync as
|
|
12739
|
-
lstatSync as
|
|
12741
|
+
existsSync as existsSync10,
|
|
12742
|
+
lstatSync as lstatSync17,
|
|
12740
12743
|
mkdirSync as mkdirSync6,
|
|
12741
12744
|
mkdtempSync,
|
|
12742
12745
|
readFileSync as readFileSync13,
|
|
@@ -12747,7 +12750,7 @@ import {
|
|
|
12747
12750
|
writeFileSync as writeFileSync5
|
|
12748
12751
|
} from "fs";
|
|
12749
12752
|
import { homedir as homedir3 } from "os";
|
|
12750
|
-
import { basename as basename6, dirname as dirname12, join as
|
|
12753
|
+
import { basename as basename6, dirname as dirname12, join as join19 } from "path";
|
|
12751
12754
|
|
|
12752
12755
|
// src/lib/codex-local-mcp-controls.ts
|
|
12753
12756
|
function isCodexLocalStdioMcpServer(name, server) {
|
|
@@ -13506,6 +13509,29 @@ function mcpControls(path, read2) {
|
|
|
13506
13509
|
refuse2();
|
|
13507
13510
|
return sha2562;
|
|
13508
13511
|
}
|
|
13512
|
+
function isBundledCleanupHooks(root, namespace, value) {
|
|
13513
|
+
const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
|
|
13514
|
+
const exactKeys = (v, keys) => Object.keys(v).length === keys.length && keys.every((key) => Object.hasOwn(v, key));
|
|
13515
|
+
if (basename5(dirname10(root)) !== namespace || basename5(dirname10(dirname10(root))) !== "openai-bundled")
|
|
13516
|
+
return false;
|
|
13517
|
+
const server = namespace === "unified-computer-use" ? "cua_repl" : ["browser", "chrome", "chrome-dev", "chrome-internal", "computer-use"].includes(namespace) ? "node_repl" : undefined;
|
|
13518
|
+
if (!server || !object5(value) || !exactKeys(value, ["hooks"]) || !object5(value.hooks))
|
|
13519
|
+
return false;
|
|
13520
|
+
const events = Object.entries(value.hooks);
|
|
13521
|
+
if (!events.length || events.length > 3)
|
|
13522
|
+
return false;
|
|
13523
|
+
for (const [event, groups] of events) {
|
|
13524
|
+
if (!["Interrupt", "SubagentStop", "Stop"].includes(event) || !Array.isArray(groups) || groups.length !== 1)
|
|
13525
|
+
return false;
|
|
13526
|
+
const group = groups[0];
|
|
13527
|
+
if (!object5(group) || !exactKeys(group, ["hooks"]) || !Array.isArray(group.hooks) || group.hooks.length !== 1)
|
|
13528
|
+
return false;
|
|
13529
|
+
const handler = group.hooks[0];
|
|
13530
|
+
if (!object5(handler) || !exactKeys(handler, ["type", "server", "tool", "input"]) || handler.type !== "mcp_tool" || handler.server !== server || handler.tool !== "turn_ended" || !object5(handler.input) || !exactKeys(handler.input, ["hook_event_name", "session_id", "turn_id"]) || handler.input.hook_event_name !== "${hook_event_name}" || handler.input.turn_id !== "${turn_id}" || handler.input.session_id !== (event === "SubagentStop" ? "${agent_id}" : "${session_id}"))
|
|
13531
|
+
return false;
|
|
13532
|
+
}
|
|
13533
|
+
return true;
|
|
13534
|
+
}
|
|
13509
13535
|
function rootControls(root, read2) {
|
|
13510
13536
|
const manifestPath = join14(root, ".codex-plugin/plugin.json"), manifestFile = lstatSync12(manifestPath, { throwIfNoEntry: false });
|
|
13511
13537
|
if (!lstatSync12(join14(root, ".codex-plugin"), { throwIfNoEntry: false })?.isDirectory() || !manifestFile?.isFile() || manifestFile.size > 1024 * 1024)
|
|
@@ -13523,9 +13549,11 @@ function rootControls(root, read2) {
|
|
|
13523
13549
|
const cacheVersion = basename5(root), declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
|
|
13524
13550
|
if (declaredVersion === null || (declaredVersion ? declaredVersion !== cacheVersion : cacheVersion !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
|
|
13525
13551
|
refuse2();
|
|
13526
|
-
if (manifest.hooks !== undefined || manifest.commands !== undefined || manifest.mcpServers !== undefined && manifest.mcpServers !== "./.mcp.json" || manifest.apps !== undefined && manifest.apps !== "./.app.json" || lstatSync12(join14(root, "hooks"), { throwIfNoEntry: false }))
|
|
13552
|
+
if (manifest.hooks !== undefined && !isBundledCleanupHooks(root, manifest.name, manifest.hooks) || manifest.commands !== undefined || manifest.mcpServers !== undefined && manifest.mcpServers !== "./.mcp.json" || manifest.apps !== undefined && manifest.apps !== "./.app.json" || lstatSync12(join14(root, "hooks"), { throwIfNoEntry: false }))
|
|
13527
13553
|
refuse2();
|
|
13528
13554
|
const mcp = join14(root, ".mcp.json"), mcpPresent = lstatSync12(mcp, { throwIfNoEntry: false });
|
|
13555
|
+
if (manifest.hooks !== undefined && (manifest.mcpServers !== undefined || mcpPresent))
|
|
13556
|
+
refuse2();
|
|
13529
13557
|
if (mcpPresent && (!mcpPresent.isFile() || mcpPresent.isSymbolicLink() || mcpPresent.size > 1024 * 1024 || realpathSync5(mcp) !== mcp))
|
|
13530
13558
|
refuse2();
|
|
13531
13559
|
if (Boolean(mcpPresent) !== (manifest.mcpServers !== undefined))
|
|
@@ -14124,8 +14152,8 @@ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
|
|
|
14124
14152
|
// src/lib/agent-integration.ts
|
|
14125
14153
|
import { createHash as createHash12, randomUUID as randomUUID2 } from "crypto";
|
|
14126
14154
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
14127
|
-
import { existsSync as
|
|
14128
|
-
import { dirname as dirname11, join as
|
|
14155
|
+
import { existsSync as existsSync9, lstatSync as lstatSync16, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants7, linkSync } from "fs";
|
|
14156
|
+
import { dirname as dirname11, join as join18, resolve as resolve11, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
14129
14157
|
import { homedir as homedir2 } from "os";
|
|
14130
14158
|
|
|
14131
14159
|
// src/lib/managed-policy.ts
|
|
@@ -14519,6 +14547,114 @@ function disableCodexBundledSkills(text2) {
|
|
|
14519
14547
|
return [...candidates][0];
|
|
14520
14548
|
}
|
|
14521
14549
|
|
|
14550
|
+
// src/lib/agent-sumi.ts
|
|
14551
|
+
import { existsSync as existsSync8, lstatSync as lstatSync15, realpathSync as realpathSync6 } from "fs";
|
|
14552
|
+
import { join as join17, resolve as resolve10 } from "path";
|
|
14553
|
+
function sumiConfigDirectory(home, env = process.env) {
|
|
14554
|
+
for (const key of ["SUMI_CONFIG", "SUMI_CONFIG_CONTENT"])
|
|
14555
|
+
if (env[key] !== undefined)
|
|
14556
|
+
throw new Error(`Sumi ${key} requires a dedicated discovery adapter; unset it before Skills integration`);
|
|
14557
|
+
const selected = (key) => env[key]?.trim() ? env[key] : undefined;
|
|
14558
|
+
const expand = (value) => resolve10(value === "~" ? home : value.startsWith("~/") ? join17(home, value.slice(2)) : value);
|
|
14559
|
+
const explicit = selected("SUMI_CONFIG_DIR");
|
|
14560
|
+
const xdg = selected("XDG_CONFIG_HOME");
|
|
14561
|
+
const custom = selected("SUMI_HOME");
|
|
14562
|
+
const canonical5 = explicit ? expand(explicit) : xdg ? join17(expand(xdg), "sumi") : join17(custom ? expand(custom) : join17(home, ".hasna-internal/sumi"), "config");
|
|
14563
|
+
if (explicit || xdg || custom)
|
|
14564
|
+
return canonical5;
|
|
14565
|
+
const legacy = join17(home, ".config/sumi");
|
|
14566
|
+
const a = lstatSync15(canonical5, { throwIfNoEntry: false }), b = lstatSync15(legacy, { throwIfNoEntry: false });
|
|
14567
|
+
if (a && b && realpathSync6(canonical5) !== realpathSync6(legacy))
|
|
14568
|
+
throw new Error("Sumi config roots conflict; complete native home adoption before Skills integration");
|
|
14569
|
+
const path = a ? canonical5 : b ? legacy : canonical5;
|
|
14570
|
+
if (!existsSync8(path))
|
|
14571
|
+
return path;
|
|
14572
|
+
const physical = realpathSync6(path);
|
|
14573
|
+
if ((a?.isSymbolicLink() || b?.isSymbolicLink()) && ![canonical5, legacy].includes(physical))
|
|
14574
|
+
throw new Error("Sumi config alias must resolve to its canonical or legacy root");
|
|
14575
|
+
return physical;
|
|
14576
|
+
}
|
|
14577
|
+
function sumiBridgeRoot(home) {
|
|
14578
|
+
return join17(sumiConfigDirectory(home), "skills");
|
|
14579
|
+
}
|
|
14580
|
+
function sumiConfigPath(home) {
|
|
14581
|
+
return join17(sumiConfigDirectory(home), "sumi.json");
|
|
14582
|
+
}
|
|
14583
|
+
function renderSumiPlugin(command, profile) {
|
|
14584
|
+
return `// Managed by @hasna/skills. Regenerate with skills hook install --agent sumi.
|
|
14585
|
+
import { spawn } from "node:child_process";
|
|
14586
|
+
const command = ${JSON.stringify(command)};
|
|
14587
|
+
const profile = ${JSON.stringify(profile)};
|
|
14588
|
+
function context(input) {
|
|
14589
|
+
return new Promise((resolve, reject) => {
|
|
14590
|
+
const child = spawn(command, ["hook", "user-prompt", "--agent", "sumi", "--selection-profile", profile], { cwd: input.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
14591
|
+
const chunks = []; let bytes = 0, failure;
|
|
14592
|
+
const timer = setTimeout(() => { failure = new Error("Skills prompt hook timed out"); child.kill("SIGKILL"); }, 15000);
|
|
14593
|
+
child.stdout.on("data", chunk => { bytes += chunk.length; if (bytes > 1048576) { failure = new Error("Skills prompt hook output exceeded its limit"); child.kill("SIGKILL"); } else chunks.push(chunk); });
|
|
14594
|
+
child.stderr.on("data", () => {});
|
|
14595
|
+
child.stdin.on("error", () => {});
|
|
14596
|
+
child.on("error", error => { clearTimeout(timer); reject(error); });
|
|
14597
|
+
child.on("close", code => {
|
|
14598
|
+
clearTimeout(timer);
|
|
14599
|
+
if (failure || code !== 0) return reject(failure ?? new Error("Skills prompt hook failed"));
|
|
14600
|
+
try {
|
|
14601
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
14602
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("Invalid Skills hook response");
|
|
14603
|
+
if (result.decision === "block" || result.continue === false) throw new Error("Skills prompt hook refused");
|
|
14604
|
+
if (Object.keys(result).length && (!result.hookSpecificOutput || result.hookSpecificOutput.hookEventName !== input.hook_event_name)) throw new Error("Invalid Skills hook response");
|
|
14605
|
+
const text = result.hookSpecificOutput?.additionalContext ?? "";
|
|
14606
|
+
if (typeof text !== "string") throw new Error("Invalid Skills prompt context");
|
|
14607
|
+
resolve(text);
|
|
14608
|
+
} catch (error) { reject(error); }
|
|
14609
|
+
});
|
|
14610
|
+
child.stdin.end(JSON.stringify(input));
|
|
14611
|
+
});
|
|
14612
|
+
}
|
|
14613
|
+
export default {
|
|
14614
|
+
id: "local.hasna-skills-cli",
|
|
14615
|
+
async setup(ctx) {
|
|
14616
|
+
if (!ctx?.session?.hook || !ctx?.session?.get || !ctx?.skill?.transform || !ctx?.tool?.hook) throw new Error("Unsupported Sumi native Skills contract");
|
|
14617
|
+
const registrations = [];
|
|
14618
|
+
const sessions = new Set();
|
|
14619
|
+
async function input(sessionID, prompt, first) {
|
|
14620
|
+
// The Promise adapter unwraps single-data endpoint responses.
|
|
14621
|
+
const session = await ctx.session.get({ sessionID });
|
|
14622
|
+
if (!session || session.id !== sessionID || typeof session.location?.directory !== "string" || (session.parentID !== undefined && typeof session.parentID !== "string") || session.parentID === sessionID) throw new Error("Invalid Sumi session custody");
|
|
14623
|
+
return { hook_event_name: first ? session.parentID ? "SubagentStart" : "SessionStart" : "UserPromptSubmit", cwd: session.location.directory, session_id: sessionID, parent_session_id: session.parentID ?? null, prompt, restore: true };
|
|
14624
|
+
}
|
|
14625
|
+
registrations.push(await ctx.skill.transform(editor => {
|
|
14626
|
+
for (const skill of editor.list()) if (skill.id !== "skills-cli" || skill.name !== "skills-cli") editor.remove(skill.id);
|
|
14627
|
+
}));
|
|
14628
|
+
// Native config transforms may add skills after this transform. Guard
|
|
14629
|
+
// both direct tool calls and prompt attachments at the consumption edge.
|
|
14630
|
+
registrations.push(await ctx.tool.hook("execute.before", event => {
|
|
14631
|
+
if (event.tool === "skill" && event.input?.id !== "skills-cli") throw new Error("Native Sumi skill payload refused; use Skills CLI");
|
|
14632
|
+
}));
|
|
14633
|
+
registrations.push(await ctx.session.hook("prompt", async event => {
|
|
14634
|
+
if (typeof event.prompt?.text !== "string") throw new Error("Invalid Sumi prompt");
|
|
14635
|
+
if (event.prompt.skills !== undefined && (!Array.isArray(event.prompt.skills) || event.prompt.skills.some(skill => skill?.id !== "skills-cli"))) throw new Error("Native Sumi skill attachment refused; use Skills CLI");
|
|
14636
|
+
await context(await input(event.sessionID, event.prompt.text, !sessions.has(event.sessionID)));
|
|
14637
|
+
sessions.add(event.sessionID);
|
|
14638
|
+
}));
|
|
14639
|
+
registrations.push(await ctx.session.hook("context", async event => {
|
|
14640
|
+
const last = event.messages.findLast(message => message.role === "user");
|
|
14641
|
+
const prompt = (last?.content ?? []).filter(part => part.type === "text").map(part => part.text).join("\\n");
|
|
14642
|
+
// Recheck on every primary request, including resumed and synthetic
|
|
14643
|
+
// sessions. The CLI owns optional failures versus explicit refusals.
|
|
14644
|
+
const text = await context(await input(event.sessionID, prompt, !sessions.has(event.sessionID)));
|
|
14645
|
+
if (text) event.system.push({ type: "text", text });
|
|
14646
|
+
sessions.add(event.sessionID);
|
|
14647
|
+
}));
|
|
14648
|
+
return async () => { for (const registration of registrations) await registration.dispose(); };
|
|
14649
|
+
},
|
|
14650
|
+
};
|
|
14651
|
+
`;
|
|
14652
|
+
}
|
|
14653
|
+
var SUMI_SKILL_PERMISSIONS = [
|
|
14654
|
+
{ action: "skill", resource: "*", effect: "deny" },
|
|
14655
|
+
{ action: "skill", resource: "skills-cli", effect: "allow" }
|
|
14656
|
+
];
|
|
14657
|
+
|
|
14522
14658
|
// src/lib/agent-integration.ts
|
|
14523
14659
|
var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
|
|
14524
14660
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
@@ -14553,11 +14689,11 @@ function isSystemRootAlias(path) {
|
|
|
14553
14689
|
const target = `/private${path}`, link = `private${path}`;
|
|
14554
14690
|
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
14555
14691
|
try {
|
|
14556
|
-
const before =
|
|
14692
|
+
const before = lstatSync16(path, { bigint: true });
|
|
14557
14693
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
|
|
14558
14694
|
return false;
|
|
14559
14695
|
const directories = ["/", "/private", target].map((directory) => {
|
|
14560
|
-
const stat =
|
|
14696
|
+
const stat = lstatSync16(directory, { bigint: true });
|
|
14561
14697
|
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
14562
14698
|
throw new Error("Untrusted system alias directory");
|
|
14563
14699
|
if (directory !== "/private/tmp") {
|
|
@@ -14574,23 +14710,23 @@ function isSystemRootAlias(path) {
|
|
|
14574
14710
|
}
|
|
14575
14711
|
return { directory, identity: identity4(stat) };
|
|
14576
14712
|
});
|
|
14577
|
-
return
|
|
14713
|
+
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(lstatSync16(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync16(item.directory, { bigint: true })) === item.identity);
|
|
14578
14714
|
} catch {
|
|
14579
14715
|
return false;
|
|
14580
14716
|
}
|
|
14581
14717
|
}
|
|
14582
14718
|
function canonicalSystemPath(path) {
|
|
14583
|
-
const absolute =
|
|
14584
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
14719
|
+
const absolute = resolve11(path), root = `/${absolute.split(sep5)[1]}`;
|
|
14720
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync16(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14585
14721
|
return absolute;
|
|
14586
14722
|
if (!isSystemRootAlias(root))
|
|
14587
14723
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
14588
14724
|
return `/private${absolute}`;
|
|
14589
14725
|
}
|
|
14590
14726
|
function assertSafePath(path) {
|
|
14591
|
-
let cursor =
|
|
14727
|
+
let cursor = resolve11(path);
|
|
14592
14728
|
while (true) {
|
|
14593
|
-
if (
|
|
14729
|
+
if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
14594
14730
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
14595
14731
|
const parent = dirname11(cursor);
|
|
14596
14732
|
if (parent === cursor)
|
|
@@ -14600,16 +14736,16 @@ function assertSafePath(path) {
|
|
|
14600
14736
|
}
|
|
14601
14737
|
function readRootAlias(home, agent) {
|
|
14602
14738
|
assertSafePath(home);
|
|
14603
|
-
const realHome =
|
|
14604
|
-
const stat =
|
|
14739
|
+
const realHome = realpathSync7(home), alias = join18(realHome, `.${agent}`);
|
|
14740
|
+
const stat = lstatSync16(alias, { throwIfNoEntry: false, bigint: true });
|
|
14605
14741
|
if (!stat?.isSymbolicLink())
|
|
14606
14742
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
14607
|
-
const link = readlinkSync4(alias), targetPath =
|
|
14743
|
+
const link = readlinkSync4(alias), targetPath = resolve11(dirname11(alias), link);
|
|
14608
14744
|
assertSafePath(targetPath);
|
|
14609
|
-
const targetStat =
|
|
14745
|
+
const targetStat = lstatSync16(targetPath, { throwIfNoEntry: false });
|
|
14610
14746
|
if (!targetStat?.isDirectory())
|
|
14611
14747
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
14612
|
-
const target =
|
|
14748
|
+
const target = realpathSync7(targetPath), inside = relative5(realHome, target);
|
|
14613
14749
|
if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
|
|
14614
14750
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
14615
14751
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
@@ -14619,7 +14755,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14619
14755
|
return [];
|
|
14620
14756
|
const result = [];
|
|
14621
14757
|
for (const agent of ["claude", "codex"])
|
|
14622
|
-
if (
|
|
14758
|
+
if (lstatSync16(join18(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14623
14759
|
result.push(readRootAlias(home, agent));
|
|
14624
14760
|
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
|
|
14625
14761
|
throw new Error("Agent root alias targets must not overlap");
|
|
@@ -14627,7 +14763,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14627
14763
|
}
|
|
14628
14764
|
function canonicalAgentPath(path, aliases) {
|
|
14629
14765
|
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
|
|
14630
|
-
return binding ?
|
|
14766
|
+
return binding ? join18(binding.target, relative5(binding.alias, absolute)) : absolute;
|
|
14631
14767
|
}
|
|
14632
14768
|
function disabledCodexSkillPaths(config, aliases) {
|
|
14633
14769
|
const skills = config.skills;
|
|
@@ -14646,7 +14782,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14646
14782
|
continue;
|
|
14647
14783
|
let document2;
|
|
14648
14784
|
try {
|
|
14649
|
-
document2 =
|
|
14785
|
+
document2 = realpathSync7(canonicalAgentPath(entry.path, aliases));
|
|
14650
14786
|
} catch {
|
|
14651
14787
|
continue;
|
|
14652
14788
|
}
|
|
@@ -14658,7 +14794,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14658
14794
|
}
|
|
14659
14795
|
function recheckRootAliases(aliases) {
|
|
14660
14796
|
for (const binding of aliases) {
|
|
14661
|
-
if (!["claude", "codex"].includes(binding.agent) || binding.alias !==
|
|
14797
|
+
if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join18(binding.home, `.${binding.agent}`))
|
|
14662
14798
|
throw new Error("Invalid agent root alias binding");
|
|
14663
14799
|
const current = readRootAlias(binding.home, binding.agent);
|
|
14664
14800
|
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
@@ -14667,7 +14803,7 @@ function recheckRootAliases(aliases) {
|
|
|
14667
14803
|
}
|
|
14668
14804
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
14669
14805
|
assertSafePath(path);
|
|
14670
|
-
const before =
|
|
14806
|
+
const before = lstatSync16(path);
|
|
14671
14807
|
if (!before.isFile() || before.size > maximum)
|
|
14672
14808
|
throw new Error("Unsupported or oversized native skill file");
|
|
14673
14809
|
const descriptor = openSync8(path, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
@@ -14683,7 +14819,7 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
|
14683
14819
|
break;
|
|
14684
14820
|
length += count;
|
|
14685
14821
|
}
|
|
14686
|
-
const after = fstatSync8(descriptor), current =
|
|
14822
|
+
const after = fstatSync8(descriptor), current = lstatSync16(path);
|
|
14687
14823
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after.size || current.mtimeMs !== after.mtimeMs || current.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
14688
14824
|
throw new Error("Native skill file changed while reading");
|
|
14689
14825
|
return bytes.subarray(0, length);
|
|
@@ -14695,14 +14831,14 @@ function treeHash(root) {
|
|
|
14695
14831
|
const hash2 = createHash12("sha256");
|
|
14696
14832
|
let bytes = 0, files = 0;
|
|
14697
14833
|
function visit2(path) {
|
|
14698
|
-
const stat =
|
|
14834
|
+
const stat = lstatSync16(path);
|
|
14699
14835
|
if (stat.isSymbolicLink())
|
|
14700
14836
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
14701
14837
|
const rel = relative5(root, path);
|
|
14702
14838
|
if (stat.isDirectory()) {
|
|
14703
14839
|
hash2.update(`d\x00${rel}\x00`);
|
|
14704
14840
|
for (const name of readdirSync8(path).sort())
|
|
14705
|
-
visit2(
|
|
14841
|
+
visit2(join18(path, name));
|
|
14706
14842
|
return;
|
|
14707
14843
|
}
|
|
14708
14844
|
if (!stat.isFile())
|
|
@@ -14724,10 +14860,10 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14724
14860
|
const identities = new Set;
|
|
14725
14861
|
const selected = [];
|
|
14726
14862
|
for (const target of manifest.targets) {
|
|
14727
|
-
if (!isAbsolute11(target.projectRoot) ||
|
|
14863
|
+
if (!isAbsolute11(target.projectRoot) || resolve11(target.projectRoot) !== target.projectRoot)
|
|
14728
14864
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14729
|
-
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot,
|
|
14730
|
-
if (!
|
|
14865
|
+
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
|
|
14866
|
+
if (!existsSync9(projectRoot) || !lstatSync16(projectRoot).isDirectory())
|
|
14731
14867
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14732
14868
|
assertSafePath(projectRoot);
|
|
14733
14869
|
if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
@@ -14736,8 +14872,8 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14736
14872
|
if (identities.has(identity4))
|
|
14737
14873
|
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
14738
14874
|
identities.add(identity4);
|
|
14739
|
-
const expected = canonicalSystemPath(
|
|
14740
|
-
const matches = inventory.filter((entry2) => entry2.agent === target.agent &&
|
|
14875
|
+
const expected = canonicalSystemPath(resolve11(target.projectRoot, target.path));
|
|
14876
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve11(entry2.path) === expected);
|
|
14741
14877
|
if (matches.length !== 1)
|
|
14742
14878
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
14743
14879
|
const entry = matches[0];
|
|
@@ -14766,9 +14902,9 @@ function projectAncestorDirectories(projects) {
|
|
|
14766
14902
|
}
|
|
14767
14903
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
14768
14904
|
home = canonicalSystemPath(home);
|
|
14769
|
-
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) ||
|
|
14905
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve11(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
14770
14906
|
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
14771
|
-
if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) ||
|
|
14907
|
+
if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve11(path) !== path))
|
|
14772
14908
|
throw new Error("Disabled vendor paths must be exact absolute paths");
|
|
14773
14909
|
const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
|
|
14774
14910
|
const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
|
|
@@ -14776,14 +14912,18 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14776
14912
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
14777
14913
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
14778
14914
|
const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS;
|
|
14779
|
-
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(
|
|
14780
|
-
const bridgePaths = Object.
|
|
14915
|
+
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join18(home, path), aliases)]);
|
|
14916
|
+
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesAgent("sumi")).map(([agent, adapter]) => canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, adapter.root), CLI_BRIDGE_NAME), aliases));
|
|
14781
14917
|
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
14782
14918
|
for (const [agent, path] of rootDefinitions)
|
|
14783
|
-
roots.push([agent, canonicalAgentPath(
|
|
14919
|
+
roots.push([agent, canonicalAgentPath(join18(project, path), aliases)]);
|
|
14920
|
+
if (selectedAgents?.has("sumi"))
|
|
14921
|
+
for (const directory of [".claude", ".agents"])
|
|
14922
|
+
for (const name of ["skill", "skills"])
|
|
14923
|
+
roots.push(["sumi", canonicalAgentPath(join18(project, directory, name), aliases)]);
|
|
14784
14924
|
}
|
|
14785
14925
|
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
14786
|
-
if (installationInputs.some((input) =>
|
|
14926
|
+
if (installationInputs.some((input) => resolve11(input) !== input || realpathSync7(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
|
|
14787
14927
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
14788
14928
|
const entries = [], seen = new Set;
|
|
14789
14929
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
@@ -14797,13 +14937,13 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14797
14937
|
}
|
|
14798
14938
|
let reviewedCacheAliasFound = false;
|
|
14799
14939
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
14800
|
-
const link = readlinkSync4(path), lexicalTarget =
|
|
14940
|
+
const link = readlinkSync4(path), lexicalTarget = resolve11(parent, link);
|
|
14801
14941
|
if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
|
|
14802
14942
|
return false;
|
|
14803
14943
|
const target = canonicalSystemPath(lexicalTarget);
|
|
14804
14944
|
if (dirname11(target) !== parent)
|
|
14805
14945
|
return false;
|
|
14806
|
-
if (!
|
|
14946
|
+
if (!lstatSync16(target, { throwIfNoEntry: false })?.isDirectory())
|
|
14807
14947
|
return false;
|
|
14808
14948
|
const scan = cacheScans.get(target);
|
|
14809
14949
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
@@ -14826,37 +14966,40 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14826
14966
|
assertSafePath(path);
|
|
14827
14967
|
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep5}`)))
|
|
14828
14968
|
return scan;
|
|
14829
|
-
if (!
|
|
14969
|
+
if (!existsSync9(path))
|
|
14830
14970
|
return scan;
|
|
14831
|
-
const stat =
|
|
14832
|
-
if (stat.isFile())
|
|
14971
|
+
const stat = lstatSync16(path);
|
|
14972
|
+
if (stat.isFile()) {
|
|
14973
|
+
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
14974
|
+
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
14833
14975
|
return scan;
|
|
14976
|
+
}
|
|
14834
14977
|
if (!stat.isDirectory())
|
|
14835
14978
|
throw new Error(`Unsupported native discovery entry: ${path}`);
|
|
14836
|
-
if (
|
|
14979
|
+
if (existsSync9(join18(path, "SKILL.md"))) {
|
|
14837
14980
|
scan.hasSkills = true;
|
|
14838
14981
|
if (seen.has(path))
|
|
14839
14982
|
return scan;
|
|
14840
14983
|
seen.add(path);
|
|
14841
14984
|
let managed = false;
|
|
14842
|
-
const marker =
|
|
14843
|
-
if (
|
|
14985
|
+
const marker = join18(path, ".hasna-skills.json");
|
|
14986
|
+
if (existsSync9(marker)) {
|
|
14844
14987
|
try {
|
|
14845
14988
|
managed = JSON.parse(readFileSync12(marker, "utf8")).managedBy === "@hasna/skills";
|
|
14846
14989
|
} catch {}
|
|
14847
14990
|
}
|
|
14848
|
-
const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
|
|
14991
|
+
const bridge = (!vendor || agent === "sumi") && isOwnedCliBridge(path, bridgePaths);
|
|
14849
14992
|
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep5));
|
|
14850
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(
|
|
14993
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve11(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
14851
14994
|
return scan;
|
|
14852
14995
|
}
|
|
14853
|
-
if (depth > (vendor ? 32 : 3))
|
|
14996
|
+
if (depth > (agent === "sumi" ? 32 : vendor ? 32 : 3))
|
|
14854
14997
|
return { ...scan, complete: false };
|
|
14855
14998
|
const children = [], directory = opendirSync3(path);
|
|
14856
14999
|
try {
|
|
14857
15000
|
let child;
|
|
14858
15001
|
while ((child = directory.readSync()) !== null) {
|
|
14859
|
-
admit(
|
|
15002
|
+
admit(join18(path, child.name));
|
|
14860
15003
|
children.push(child);
|
|
14861
15004
|
}
|
|
14862
15005
|
} finally {
|
|
@@ -14868,13 +15011,13 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14868
15011
|
for (const { name } of children) {
|
|
14869
15012
|
if (name === "node_modules")
|
|
14870
15013
|
continue;
|
|
14871
|
-
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
|
|
15014
|
+
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
|
|
14872
15015
|
continue;
|
|
14873
15016
|
const isVendor = vendor || name === ".system";
|
|
14874
15017
|
if (isVendor && !options.includeVendor)
|
|
14875
15018
|
continue;
|
|
14876
|
-
const child =
|
|
14877
|
-
if (isVendor &&
|
|
15019
|
+
const child = join18(path, name);
|
|
15020
|
+
if (isVendor && lstatSync16(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
14878
15021
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
14879
15022
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
14880
15023
|
scan.entries++;
|
|
@@ -14900,11 +15043,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14900
15043
|
scanRoot(agent, path);
|
|
14901
15044
|
if (options.includeVendor) {
|
|
14902
15045
|
for (const agent of ["codex", "claude"].filter(includesAgent))
|
|
14903
|
-
scanRoot(agent, canonicalAgentPath(
|
|
15046
|
+
scanRoot(agent, canonicalAgentPath(join18(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
14904
15047
|
if (includesAgent("claude"))
|
|
14905
|
-
scanRoot("claude", canonicalAgentPath(
|
|
15048
|
+
scanRoot("claude", canonicalAgentPath(join18(home, ".claude", "plugins", "synced"), aliases), true);
|
|
14906
15049
|
if (includesAgent("gemini"))
|
|
14907
|
-
scanRoot("gemini",
|
|
15050
|
+
scanRoot("gemini", join18(home, ".gemini", "extensions"), true);
|
|
14908
15051
|
}
|
|
14909
15052
|
const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
14910
15053
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
@@ -14917,7 +15060,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14917
15060
|
}
|
|
14918
15061
|
function readOptional(path) {
|
|
14919
15062
|
assertSafePath(path);
|
|
14920
|
-
return
|
|
15063
|
+
return existsSync9(path) ? readFileSync12(path, "utf8") : null;
|
|
14921
15064
|
}
|
|
14922
15065
|
function jsonObject(text2, path) {
|
|
14923
15066
|
if (text2 === null)
|
|
@@ -15017,19 +15160,19 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
15017
15160
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
15018
15161
|
const bundled = disableCodexBundledSkills(text2);
|
|
15019
15162
|
let result = normalizeCodexInlinePathConfig(bundled);
|
|
15020
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
15163
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve11(bridgePath), resolve11(join18(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
15021
15164
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
15022
15165
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
15023
15166
|
throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
|
|
15024
15167
|
const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
|
|
15025
15168
|
const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
|
|
15026
15169
|
for (const skill of selections) {
|
|
15027
|
-
const path =
|
|
15170
|
+
const path = join18(skill.path, "SKILL.md");
|
|
15028
15171
|
let found = false;
|
|
15029
15172
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
15030
15173
|
const parsed = Bun.TOML.parse(section);
|
|
15031
15174
|
const declared = parsed.skills?.config?.[0];
|
|
15032
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
15175
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve11(path), resolve11(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
15033
15176
|
return section;
|
|
15034
15177
|
found = true;
|
|
15035
15178
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -15050,14 +15193,14 @@ enabled = false
|
|
|
15050
15193
|
return result;
|
|
15051
15194
|
}
|
|
15052
15195
|
function assertCodexInstallationInputs(home, projectDirs, inputs) {
|
|
15053
|
-
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
15196
|
+
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve11(directory, path)));
|
|
15054
15197
|
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep5) || input.startsWith(root + sep5))))
|
|
15055
15198
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
15056
15199
|
}
|
|
15057
15200
|
function planAgentIntegration(options) {
|
|
15058
15201
|
const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15059
15202
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
15060
|
-
const policyPath =
|
|
15203
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15061
15204
|
assertSafePath(policyPath);
|
|
15062
15205
|
const priorSnapshot = readManagedSkillPolicySnapshot(dataDir2), previousPolicy = priorSnapshot?.text ?? null, policy = priorSnapshot?.value ?? {};
|
|
15063
15206
|
if (policy.bridge !== undefined && (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge)))
|
|
@@ -15089,10 +15232,10 @@ function planAgentIntegration(options) {
|
|
|
15089
15232
|
}
|
|
15090
15233
|
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15091
15234
|
});
|
|
15092
|
-
const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(
|
|
15235
|
+
const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases)) ?? "") : {};
|
|
15093
15236
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
|
|
15094
15237
|
const observedNativeSources = [];
|
|
15095
|
-
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) =>
|
|
15238
|
+
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join18(entry.path, "SKILL.md")), canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), resolve11(options.projectDir ?? home), (path) => {
|
|
15096
15239
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
15097
15240
|
observedNativeSources.push({ path, sha256: sha3(bytes) });
|
|
15098
15241
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
@@ -15100,7 +15243,7 @@ function planAgentIntegration(options) {
|
|
|
15100
15243
|
const codexPluginSkills = codexPluginControls.skills;
|
|
15101
15244
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
15102
15245
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
15103
|
-
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path ===
|
|
15246
|
+
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path === join18(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
15104
15247
|
assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
|
|
15105
15248
|
const changes = [];
|
|
15106
15249
|
for (const agent of [...new Set(options.agents)]) {
|
|
@@ -15108,19 +15251,19 @@ function planAgentIntegration(options) {
|
|
|
15108
15251
|
throw new Error(`Unsupported agent: ${agent}`);
|
|
15109
15252
|
const { command, profileId: profileId2 } = bindings.get(agent);
|
|
15110
15253
|
const adapter = AGENT_ADAPTERS[agent];
|
|
15111
|
-
const bridgePath = canonicalAgentPath(
|
|
15254
|
+
const bridgePath = canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, adapter.root), CLI_BRIDGE_NAME), aliases);
|
|
15112
15255
|
assertSafePath(bridgePath);
|
|
15113
|
-
if (
|
|
15256
|
+
if (existsSync9(bridgePath) && !isOwnedCliBridge(bridgePath, [bridgePath]))
|
|
15114
15257
|
throw new Error(`Refusing to overwrite an unrecognized or modified Skills bridge: ${bridgePath}`);
|
|
15115
15258
|
for (const [name, after2] of Object.entries(CLI_BRIDGE_FILES)) {
|
|
15116
|
-
const path2 =
|
|
15259
|
+
const path2 = join18(bridgePath, name), before2 = readOptional(path2);
|
|
15117
15260
|
if (before2 !== after2)
|
|
15118
15261
|
changes.push({ path: path2, before: before2, after: after2 });
|
|
15119
15262
|
}
|
|
15120
|
-
const path = canonicalAgentPath(
|
|
15263
|
+
const path = canonicalAgentPath(agent === "sumi" ? sumiConfigPath(home) : join18(home, adapter.config), aliases);
|
|
15121
15264
|
const before = readOptional(path);
|
|
15122
15265
|
if (agent === "hermes") {
|
|
15123
|
-
const supervisorPath =
|
|
15266
|
+
const supervisorPath = join18(dataDir2, "agent-hooks", "hermes.js"), supervisorBefore = readOptional(supervisorPath), supervisorAfter = renderHermesSupervisor(command, profileId2);
|
|
15124
15267
|
const priorCommand = policy.bridge?.commands?.hermes, priorProfile = policy.bridge?.profiles?.hermes ?? policy.profileId;
|
|
15125
15268
|
const previous = policy.bridge?.supervisors?.hermes;
|
|
15126
15269
|
if (supervisorBefore !== null && supervisorBefore !== supervisorAfter && !(previous?.path === supervisorPath && typeof priorCommand === "string" && typeof priorProfile === "string" && supervisorBefore === renderHermesSupervisor(priorCommand, priorProfile)))
|
|
@@ -15131,19 +15274,30 @@ function planAgentIntegration(options) {
|
|
|
15131
15274
|
const after2 = configureHermesHooks(before, supervisor, previous);
|
|
15132
15275
|
if (before !== after2)
|
|
15133
15276
|
changes.push({ path, before, after: after2 });
|
|
15134
|
-
const optOut =
|
|
15277
|
+
const optOut = join18(home, ".hermes", HERMES_OPT_OUT), optOutBefore = readOptional(optOut);
|
|
15135
15278
|
if (optOutBefore === null)
|
|
15136
15279
|
changes.push({ path: optOut, before: null, after: `Managed by @hasna/skills: bundled native skill reseeding is disabled.
|
|
15137
15280
|
` });
|
|
15138
15281
|
continue;
|
|
15139
15282
|
}
|
|
15140
15283
|
const config = jsonObject(before, path);
|
|
15141
|
-
if (agent === "
|
|
15284
|
+
if (agent === "sumi") {
|
|
15285
|
+
if (config.permissions !== undefined && (!Array.isArray(config.permissions) || config.permissions.some((rule) => !rule || typeof rule.action !== "string" || typeof rule.resource !== "string" || !["allow", "deny", "ask"].includes(rule.effect))))
|
|
15286
|
+
throw new Error("Expected Sumi native permission rules");
|
|
15287
|
+
config.permissions = [...(config.permissions ?? []).filter((rule) => rule.action !== "skill"), ...SUMI_SKILL_PERMISSIONS];
|
|
15288
|
+
const pluginPath = join18(sumiConfigDirectory(home), "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
15289
|
+
const pluginAfter = renderSumiPlugin(command, profileId2);
|
|
15290
|
+
const priorCommand = policy.bridge?.commands?.sumi, priorProfile = policy.bridge?.profiles?.sumi ?? policy.profileId;
|
|
15291
|
+
if (pluginBefore !== null && pluginBefore !== pluginAfter && !(typeof priorCommand === "string" && typeof priorProfile === "string" && pluginBefore === renderSumiPlugin(priorCommand, priorProfile)))
|
|
15292
|
+
throw new Error("Refusing to overwrite a modified Sumi Skills plugin; preserve and review it first");
|
|
15293
|
+
if (pluginBefore !== pluginAfter)
|
|
15294
|
+
changes.push({ path: pluginPath, before: pluginBefore, after: pluginAfter });
|
|
15295
|
+
} else if (agent === "opencode") {
|
|
15142
15296
|
const permission = config.permission ?? {};
|
|
15143
15297
|
if (!permission || typeof permission !== "object" || Array.isArray(permission))
|
|
15144
15298
|
throw new Error("Expected OpenCode permission object");
|
|
15145
15299
|
config.permission = { ...permission, skill: { "*": "deny", [CLI_BRIDGE_NAME]: "allow" } };
|
|
15146
|
-
const pluginPath =
|
|
15300
|
+
const pluginPath = join18(home, ".config", "opencode", "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
15147
15301
|
const pluginAfter = renderOpenCodePlugin(command, profileId2);
|
|
15148
15302
|
const priorCommand = policy.bridge?.commands?.opencode;
|
|
15149
15303
|
const ownedPrevious = typeof priorCommand === "string" && typeof policy.profileId === "string" && pluginBefore === renderOpenCodePlugin(priorCommand, policy.bridge?.profiles?.opencode ?? policy.profileId);
|
|
@@ -15160,13 +15314,13 @@ function planAgentIntegration(options) {
|
|
|
15160
15314
|
if (before !== after)
|
|
15161
15315
|
changes.push({ path, before, after });
|
|
15162
15316
|
if (agent === "codex") {
|
|
15163
|
-
const configPath = canonicalAgentPath(
|
|
15317
|
+
const configPath = canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
15164
15318
|
const next = disableReviewedCodexPluginNames(disableCodexSkills(previous ?? "", nativeSkills, aliases, bridgePath), codexPluginSkills);
|
|
15165
15319
|
if (next !== (previous ?? ""))
|
|
15166
15320
|
changes.push({ path: configPath, before: previous, after: next });
|
|
15167
15321
|
}
|
|
15168
15322
|
}
|
|
15169
|
-
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(
|
|
15323
|
+
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join18(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join18(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
|
|
15170
15324
|
const discoveryAfter = discoveries.map((binding) => {
|
|
15171
15325
|
const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
|
|
15172
15326
|
if (binding.agent !== "codex")
|
|
@@ -15181,12 +15335,12 @@ function planAgentIntegration(options) {
|
|
|
15181
15335
|
version: CLI_BRIDGE_VERSION,
|
|
15182
15336
|
digest: CLI_BRIDGE_DIGEST,
|
|
15183
15337
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
15184
|
-
home:
|
|
15338
|
+
home: resolve11(home),
|
|
15185
15339
|
includeVendor: true,
|
|
15186
|
-
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path:
|
|
15340
|
+
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join18(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha3(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
|
|
15187
15341
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
15188
15342
|
profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
|
|
15189
|
-
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(
|
|
15343
|
+
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
|
|
15190
15344
|
rootAliases: aliases,
|
|
15191
15345
|
...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
|
|
15192
15346
|
discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
|
|
@@ -15209,7 +15363,7 @@ function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
|
15209
15363
|
const distance = relative5(canonical5(root), path);
|
|
15210
15364
|
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
|
|
15211
15365
|
};
|
|
15212
|
-
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(
|
|
15366
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(join18(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical5(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
15213
15367
|
}
|
|
15214
15368
|
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
15215
15369
|
try {
|
|
@@ -15226,8 +15380,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
|
15226
15380
|
}
|
|
15227
15381
|
}
|
|
15228
15382
|
function planClaudeHookEventsUpdate(options) {
|
|
15229
|
-
const home =
|
|
15230
|
-
const policyPath =
|
|
15383
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15384
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15231
15385
|
assertSafePath(policyPath);
|
|
15232
15386
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15233
15387
|
if (!snapshot)
|
|
@@ -15238,9 +15392,9 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15238
15392
|
}
|
|
15239
15393
|
const aliases = bridge.rootAliases ?? [];
|
|
15240
15394
|
recheckRootAliases(aliases);
|
|
15241
|
-
const path = canonicalAgentPath(
|
|
15395
|
+
const path = canonicalAgentPath(join18(home, ".claude", "settings.json"), aliases);
|
|
15242
15396
|
assertSafePath(path);
|
|
15243
|
-
const before =
|
|
15397
|
+
const before = lstatSync16(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
15244
15398
|
if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
|
|
15245
15399
|
throw new Error("Claude settings preimage changed");
|
|
15246
15400
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -15300,14 +15454,14 @@ function atomicWrite2(path, content) {
|
|
|
15300
15454
|
writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
|
|
15301
15455
|
renameSync2(temporary, path);
|
|
15302
15456
|
} finally {
|
|
15303
|
-
if (
|
|
15457
|
+
if (existsSync9(temporary))
|
|
15304
15458
|
unlinkSync3(temporary);
|
|
15305
15459
|
}
|
|
15306
15460
|
}
|
|
15307
15461
|
function planAgentSettingsWitnessUpgrade(options) {
|
|
15308
15462
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
15309
15463
|
throw new Error("Invalid Codex witness target version");
|
|
15310
|
-
const home =
|
|
15464
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15311
15465
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15312
15466
|
if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
|
|
15313
15467
|
throw new Error("Managed policy preimage changed");
|
|
@@ -15316,7 +15470,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15316
15470
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
15317
15471
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
15318
15472
|
throw new Error("Settings witness upgrade requires an existing reviewed native binding");
|
|
15319
|
-
const configPath = canonicalAgentPath(
|
|
15473
|
+
const configPath = canonicalAgentPath(join18(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
|
|
15320
15474
|
const settings = readOptional(configPath);
|
|
15321
15475
|
if (settings === null || sha3(settings) !== options.expectedSettingsSha256)
|
|
15322
15476
|
throw new Error("Native settings preimage changed");
|
|
@@ -15355,7 +15509,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15355
15509
|
const replacement = { ...binding, sources: rebound };
|
|
15356
15510
|
verifyAgentDiscovery(replacement);
|
|
15357
15511
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
15358
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path:
|
|
15512
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join18(dataDir2, "agent-policy.json"), before: snapshot.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
15359
15513
|
}
|
|
15360
15514
|
function applyAgentIntegration(plan) {
|
|
15361
15515
|
for (const source of plan.observedNativeSources ?? [])
|
|
@@ -15363,12 +15517,12 @@ function applyAgentIntegration(plan) {
|
|
|
15363
15517
|
throw new Error("Native identity source changed after planning");
|
|
15364
15518
|
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
15365
15519
|
throw new Error("Native settings changed after witness planning");
|
|
15366
|
-
const policyPath =
|
|
15520
|
+
const policyPath = join18(plan.dataDir, "agent-policy.json");
|
|
15367
15521
|
assertSafePath(policyPath);
|
|
15368
|
-
const currentText = (path) =>
|
|
15522
|
+
const currentText = (path) => resolve11(path) === resolve11(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
15369
15523
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
15370
15524
|
for (const change of plan.changes)
|
|
15371
|
-
if (
|
|
15525
|
+
if (resolve11(change.path) === resolve11(policyPath))
|
|
15372
15526
|
parseManagedSkillPolicy(change.after);
|
|
15373
15527
|
const aliases = plan.rootAliases ?? [];
|
|
15374
15528
|
recheckRootAliases(aliases);
|
|
@@ -15397,7 +15551,7 @@ function applyAgentIntegration(plan) {
|
|
|
15397
15551
|
for (const change of plan.changes)
|
|
15398
15552
|
if (currentText(change.path) !== change.before)
|
|
15399
15553
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15400
|
-
const backupRoot =
|
|
15554
|
+
const backupRoot = join18(plan.dataDir, "migration", randomUUID2()), backups = [], written = [], createdDirectories = new Set;
|
|
15401
15555
|
if (!plan.changes.length) {
|
|
15402
15556
|
recheckRootAliases(aliases);
|
|
15403
15557
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
@@ -15407,7 +15561,7 @@ function applyAgentIntegration(plan) {
|
|
|
15407
15561
|
chmodSync(backupRoot, 448);
|
|
15408
15562
|
for (const [index, change] of plan.changes.entries())
|
|
15409
15563
|
if (change.before !== null) {
|
|
15410
|
-
const backup =
|
|
15564
|
+
const backup = join18(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
15411
15565
|
writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
|
|
15412
15566
|
if (readFileSync12(backup, "utf8") !== change.before)
|
|
15413
15567
|
throw new Error("Native configuration preservation readback failed");
|
|
@@ -15418,7 +15572,7 @@ function applyAgentIntegration(plan) {
|
|
|
15418
15572
|
recheckRootAliases(aliases);
|
|
15419
15573
|
if (currentText(change.path) !== change.before)
|
|
15420
15574
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15421
|
-
for (let directory = dirname11(change.path);!
|
|
15575
|
+
for (let directory = dirname11(change.path);!existsSync9(directory); directory = dirname11(directory))
|
|
15422
15576
|
createdDirectories.add(directory);
|
|
15423
15577
|
const after = change.after;
|
|
15424
15578
|
atomicWrite2(change.path, after);
|
|
@@ -15438,7 +15592,7 @@ function applyAgentIntegration(plan) {
|
|
|
15438
15592
|
}
|
|
15439
15593
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
15440
15594
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
15441
|
-
atomicWrite2(
|
|
15595
|
+
atomicWrite2(join18(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha3(change.before), afterHash: sha3(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
15442
15596
|
`);
|
|
15443
15597
|
} catch (error) {
|
|
15444
15598
|
for (const change of written.reverse()) {
|
|
@@ -15492,7 +15646,7 @@ function writeArchiveJournal(path, value) {
|
|
|
15492
15646
|
} finally {
|
|
15493
15647
|
if (descriptor !== undefined)
|
|
15494
15648
|
closeSync8(descriptor);
|
|
15495
|
-
if (
|
|
15649
|
+
if (lstatSync16(temporary, { throwIfNoEntry: false }))
|
|
15496
15650
|
unlinkSync3(temporary);
|
|
15497
15651
|
}
|
|
15498
15652
|
}
|
|
@@ -15503,7 +15657,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15503
15657
|
recheckRootAliases(aliases);
|
|
15504
15658
|
for (const entry of inventory.filter((entry2) => entry2.bridge)) {
|
|
15505
15659
|
const adapter = AGENT_ADAPTERS[entry.agent];
|
|
15506
|
-
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(
|
|
15660
|
+
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(join18(entry.agent === "sumi" ? sumiBridgeRoot(entry.bridgeHome) : join18(entry.bridgeHome, adapter.root), CLI_BRIDGE_NAME), aliases) : undefined;
|
|
15507
15661
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
15508
15662
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
15509
15663
|
}
|
|
@@ -15514,11 +15668,11 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15514
15668
|
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
15515
15669
|
if (!selected.length)
|
|
15516
15670
|
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
15517
|
-
const operationId = randomUUID2(), archiveRoot =
|
|
15671
|
+
const operationId = randomUUID2(), archiveRoot = join18(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join18(archiveRoot, "receipt.json");
|
|
15518
15672
|
const entries = selected.map((entry, index) => ({
|
|
15519
|
-
source: entry.vendor ?
|
|
15520
|
-
archive:
|
|
15521
|
-
hash: entry.vendor ? sha3(readNativeBytes(
|
|
15673
|
+
source: entry.vendor ? join18(entry.path, "SKILL.md") : entry.path,
|
|
15674
|
+
archive: join18(archiveRoot, `${index}-${sha3(entry.path).slice(0, 12)}`),
|
|
15675
|
+
hash: entry.vendor ? sha3(readNativeBytes(join18(entry.path, "SKILL.md"))) : entry.hash,
|
|
15522
15676
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
15523
15677
|
status: "planned"
|
|
15524
15678
|
}));
|
|
@@ -15526,7 +15680,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15526
15680
|
const moved = [];
|
|
15527
15681
|
const verifyArchive = (entry) => {
|
|
15528
15682
|
assertSafePath(entry.archive);
|
|
15529
|
-
const stat =
|
|
15683
|
+
const stat = lstatSync16(entry.archive);
|
|
15530
15684
|
if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
|
|
15531
15685
|
throw new Error("Archive type changed");
|
|
15532
15686
|
if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15534,7 +15688,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15534
15688
|
};
|
|
15535
15689
|
assertSafePath(archiveRoot);
|
|
15536
15690
|
const created = [];
|
|
15537
|
-
for (let path = archiveRoot;!
|
|
15691
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname11(path))
|
|
15538
15692
|
created.push(path);
|
|
15539
15693
|
mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
|
|
15540
15694
|
try {
|
|
@@ -15574,7 +15728,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15574
15728
|
recheckRootAliases(aliases);
|
|
15575
15729
|
assertSafePath(entry.source);
|
|
15576
15730
|
assertSafePath(entry.archive);
|
|
15577
|
-
if (
|
|
15731
|
+
if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
|
|
15578
15732
|
entry.status = "recovery-required";
|
|
15579
15733
|
entry.conflict = "source-occupied";
|
|
15580
15734
|
continue;
|
|
@@ -15591,7 +15745,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15591
15745
|
syncArchiveDirectory(dirname11(entry.source));
|
|
15592
15746
|
unlinkSync3(entry.archive);
|
|
15593
15747
|
} else {
|
|
15594
|
-
if (
|
|
15748
|
+
if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
|
|
15595
15749
|
entry.status = "recovery-required";
|
|
15596
15750
|
entry.conflict = "source-occupied";
|
|
15597
15751
|
continue;
|
|
@@ -15622,8 +15776,8 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
15622
15776
|
assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
15623
15777
|
}
|
|
15624
15778
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
15625
|
-
const home =
|
|
15626
|
-
assertSafePath(
|
|
15779
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15780
|
+
assertSafePath(join18(dataDir2, "agent-policy.json"));
|
|
15627
15781
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15628
15782
|
if (!snapshot)
|
|
15629
15783
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
@@ -15634,14 +15788,14 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15634
15788
|
if (!Array.isArray(aliases))
|
|
15635
15789
|
throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
|
|
15636
15790
|
recheckRootAliases(aliases);
|
|
15637
|
-
const expected = canonicalAgentPath(
|
|
15791
|
+
const expected = canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, AGENT_ADAPTERS[agent].root), CLI_BRIDGE_NAME), aliases);
|
|
15638
15792
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
15639
15793
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
15640
15794
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
15641
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) ||
|
|
15795
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
15642
15796
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
15643
15797
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
15644
|
-
const configPath = canonicalAgentPath(
|
|
15798
|
+
const configPath = canonicalAgentPath(agent === "sumi" ? sumiConfigPath(home) : join18(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
|
|
15645
15799
|
const command = binding.commands?.[agent], profile = binding.profiles?.[agent];
|
|
15646
15800
|
if (typeof command !== "string" || typeof profile !== "string")
|
|
15647
15801
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
@@ -15649,11 +15803,14 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15649
15803
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
15650
15804
|
if (agent === "hermes") {
|
|
15651
15805
|
const supervisor = binding.supervisors?.hermes;
|
|
15652
|
-
if (supervisor?.path !==
|
|
15806
|
+
if (supervisor?.path !== join18(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha3(renderHermesSupervisor(command, profile)))
|
|
15653
15807
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
15654
15808
|
assertHermesProtection(home, config, command, profile, supervisor);
|
|
15809
|
+
} else if (agent === "sumi") {
|
|
15810
|
+
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || readOptional(join18(sumiConfigDirectory(home), "plugins", "skills-cli.js")) !== renderSumiPlugin(command, profile))
|
|
15811
|
+
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
15655
15812
|
} else if (agent === "opencode") {
|
|
15656
|
-
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(
|
|
15813
|
+
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join18(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
15657
15814
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
15658
15815
|
} else {
|
|
15659
15816
|
const required = {};
|
|
@@ -15670,21 +15827,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15670
15827
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
15671
15828
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
15672
15829
|
}
|
|
15673
|
-
const codexPath = canonicalAgentPath(
|
|
15830
|
+
const codexPath = canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases);
|
|
15674
15831
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
15675
15832
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
15676
15833
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
15677
15834
|
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
15678
15835
|
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
15679
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path,
|
|
15836
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join18(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
15680
15837
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
15681
15838
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
15682
|
-
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(
|
|
15839
|
+
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
|
|
15683
15840
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
15684
15841
|
const disabledPlugin = (entry) => {
|
|
15685
15842
|
if (agent !== "codex" || !entry.vendor)
|
|
15686
15843
|
return false;
|
|
15687
|
-
const document2 =
|
|
15844
|
+
const document2 = join18(entry.path, "SKILL.md"), cache = canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
15688
15845
|
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
15689
15846
|
};
|
|
15690
15847
|
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
@@ -15697,15 +15854,15 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15697
15854
|
return true;
|
|
15698
15855
|
if (!disabledPaths.has(entry.path))
|
|
15699
15856
|
return false;
|
|
15700
|
-
if (entry.path.startsWith(canonicalAgentPath(
|
|
15857
|
+
if (entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "plugins", "cache"), aliases) + sep5))
|
|
15701
15858
|
return true;
|
|
15702
|
-
const runtimeCache = canonicalAgentPath(
|
|
15859
|
+
const runtimeCache = canonicalAgentPath(join18(home, ".cache", "codex-runtimes"), aliases);
|
|
15703
15860
|
if (entry.path.startsWith(runtimeCache + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
15704
15861
|
return true;
|
|
15705
|
-
const bundledMarketplaces = canonicalAgentPath(
|
|
15862
|
+
const bundledMarketplaces = canonicalAgentPath(join18(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
|
|
15706
15863
|
if (entry.path.startsWith(bundledMarketplaces + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
15707
15864
|
return true;
|
|
15708
|
-
if (!entry.path.startsWith(canonicalAgentPath(
|
|
15865
|
+
if (!entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5))
|
|
15709
15866
|
return false;
|
|
15710
15867
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
15711
15868
|
};
|
|
@@ -15715,6 +15872,8 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15715
15872
|
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
15716
15873
|
try {
|
|
15717
15874
|
verifyAgentDiscovery(discovery, options.codexDiscoveryRecovery);
|
|
15875
|
+
if (agent === "sumi" && discovery.method === "reviewed")
|
|
15876
|
+
resolveAgentDiscovery({ home, agent, retainedReview: discovery, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15718
15877
|
if (discovery.method === "automatic") {
|
|
15719
15878
|
const current = resolveAgentDiscovery({ home, agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15720
15879
|
if (JSON.stringify(current) !== JSON.stringify(discovery))
|
|
@@ -15726,7 +15885,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15726
15885
|
let installationInputRoots = [];
|
|
15727
15886
|
if (agent === "codex") {
|
|
15728
15887
|
try {
|
|
15729
|
-
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(
|
|
15888
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
15730
15889
|
} catch {
|
|
15731
15890
|
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
15732
15891
|
}
|
|
@@ -15761,9 +15920,9 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15761
15920
|
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
15762
15921
|
}
|
|
15763
15922
|
function hasSkillsOwnershipMarker(dir) {
|
|
15764
|
-
const path =
|
|
15923
|
+
const path = join19(dir, SYNC_MARKER_FILE);
|
|
15765
15924
|
try {
|
|
15766
|
-
if (!
|
|
15925
|
+
if (!lstatSync17(path).isFile())
|
|
15767
15926
|
return false;
|
|
15768
15927
|
const marker = JSON.parse(readFileSync13(path, "utf8"));
|
|
15769
15928
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -15785,9 +15944,9 @@ function resolveSyncAgents(arg) {
|
|
|
15785
15944
|
function agentGlobalSkillsDir(agent, homeDir2 = homedir3()) {
|
|
15786
15945
|
switch (agent) {
|
|
15787
15946
|
case "opencode":
|
|
15788
|
-
return
|
|
15947
|
+
return join19(homeDir2, ".config", "opencode", "skills");
|
|
15789
15948
|
default:
|
|
15790
|
-
return
|
|
15949
|
+
return join19(homeDir2, `.${agent}`, "skills");
|
|
15791
15950
|
}
|
|
15792
15951
|
}
|
|
15793
15952
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -15846,8 +16005,8 @@ function resolveSyncCorpus(options = {}) {
|
|
|
15846
16005
|
function packageSourceRoots(source) {
|
|
15847
16006
|
const roots = [];
|
|
15848
16007
|
for (const sub of ["skills"]) {
|
|
15849
|
-
const candidate =
|
|
15850
|
-
if (
|
|
16008
|
+
const candidate = join19(source, sub);
|
|
16009
|
+
if (existsSync10(candidate) && isDirectory(candidate))
|
|
15851
16010
|
roots.push(candidate);
|
|
15852
16011
|
}
|
|
15853
16012
|
if (roots.length > 0)
|
|
@@ -15862,10 +16021,10 @@ function containsSkillDirectories(path) {
|
|
|
15862
16021
|
return false;
|
|
15863
16022
|
}
|
|
15864
16023
|
return entries.some((entry) => {
|
|
15865
|
-
const candidate =
|
|
16024
|
+
const candidate = join19(path, entry);
|
|
15866
16025
|
if (!isDirectory(candidate))
|
|
15867
16026
|
return false;
|
|
15868
|
-
return
|
|
16027
|
+
return existsSync10(join19(candidate, "SKILL.md")) || existsSync10(join19(candidate, "skill.json")) || existsSync10(join19(candidate, "package.json"));
|
|
15869
16028
|
});
|
|
15870
16029
|
}
|
|
15871
16030
|
function isDirectory(path) {
|
|
@@ -15905,7 +16064,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
15905
16064
|
actions.push({
|
|
15906
16065
|
skill: name,
|
|
15907
16066
|
agent,
|
|
15908
|
-
path:
|
|
16067
|
+
path: join19(agentGlobalSkillsDir(agent, homeDir2), name, "SKILL.md"),
|
|
15909
16068
|
action: "skip",
|
|
15910
16069
|
reason: "not found in this machine's corpus"
|
|
15911
16070
|
});
|
|
@@ -15935,7 +16094,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
15935
16094
|
}
|
|
15936
16095
|
function writeManagedAgentSkill(params) {
|
|
15937
16096
|
const homeDir2 = params.homeDir ?? homedir3();
|
|
15938
|
-
const dir =
|
|
16097
|
+
const dir = join19(agentGlobalSkillsDir(params.agent, homeDir2), params.skill);
|
|
15939
16098
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
15940
16099
|
skill: params.skill,
|
|
15941
16100
|
source: params.source,
|
|
@@ -15952,11 +16111,11 @@ function writeManagedAgentSkill(params) {
|
|
|
15952
16111
|
};
|
|
15953
16112
|
}
|
|
15954
16113
|
function writeManagedSkillDir(dir, skillMd, options) {
|
|
15955
|
-
const skillMdPath =
|
|
15956
|
-
const markerPath =
|
|
15957
|
-
const dirExists =
|
|
16114
|
+
const skillMdPath = join19(dir, "SKILL.md");
|
|
16115
|
+
const markerPath = join19(dir, SYNC_MARKER_FILE);
|
|
16116
|
+
const dirExists = existsSync10(dir);
|
|
15958
16117
|
const managed = hasSkillsOwnershipMarker(dir);
|
|
15959
|
-
const hasSkillMd =
|
|
16118
|
+
const hasSkillMd = existsSync10(skillMdPath);
|
|
15960
16119
|
if (dirExists && !managed && !hasSkillMd) {
|
|
15961
16120
|
return {
|
|
15962
16121
|
action: "skip",
|
|
@@ -15968,7 +16127,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15968
16127
|
return {
|
|
15969
16128
|
action: "skip",
|
|
15970
16129
|
path: skillMdPath,
|
|
15971
|
-
reason:
|
|
16130
|
+
reason: existsSync10(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
15972
16131
|
};
|
|
15973
16132
|
}
|
|
15974
16133
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
@@ -15991,11 +16150,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15991
16150
|
return { action, path: skillMdPath };
|
|
15992
16151
|
const parentDir = dirname12(dir);
|
|
15993
16152
|
mkdirSync6(parentDir, { recursive: true });
|
|
15994
|
-
const transactionDir = mkdtempSync(
|
|
15995
|
-
const candidateDir =
|
|
15996
|
-
const backupDir =
|
|
15997
|
-
const candidateSkillMdPath =
|
|
15998
|
-
const candidateMarkerPath =
|
|
16153
|
+
const transactionDir = mkdtempSync(join19(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
|
|
16154
|
+
const candidateDir = join19(transactionDir, "candidate");
|
|
16155
|
+
const backupDir = join19(transactionDir, "backup");
|
|
16156
|
+
const candidateSkillMdPath = join19(candidateDir, "SKILL.md");
|
|
16157
|
+
const candidateMarkerPath = join19(candidateDir, SYNC_MARKER_FILE);
|
|
15999
16158
|
const marker = {
|
|
16000
16159
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
16001
16160
|
skill: options.skill,
|
|
@@ -16022,9 +16181,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16022
16181
|
}
|
|
16023
16182
|
renameDirectory(candidateDir, dir);
|
|
16024
16183
|
} catch (error) {
|
|
16025
|
-
if (originalMoved &&
|
|
16184
|
+
if (originalMoved && existsSync10(backupDir)) {
|
|
16026
16185
|
try {
|
|
16027
|
-
if (
|
|
16186
|
+
if (existsSync10(dir))
|
|
16028
16187
|
rmSync(dir, { recursive: true, force: true });
|
|
16029
16188
|
renameDirectory(backupDir, dir);
|
|
16030
16189
|
originalMoved = false;
|
|
@@ -16044,7 +16203,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16044
16203
|
return { action, path: skillMdPath };
|
|
16045
16204
|
}
|
|
16046
16205
|
function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3()) {
|
|
16047
|
-
const dir =
|
|
16206
|
+
const dir = join19(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
16048
16207
|
if (!hasSkillsOwnershipMarker(dir))
|
|
16049
16208
|
return false;
|
|
16050
16209
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -16052,8 +16211,8 @@ function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3()) {
|
|
|
16052
16211
|
}
|
|
16053
16212
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
16054
16213
|
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
16055
|
-
const skillMdPath =
|
|
16056
|
-
if (
|
|
16214
|
+
const skillMdPath = join19(skillPath, "SKILL.md");
|
|
16215
|
+
if (existsSync10(skillMdPath))
|
|
16057
16216
|
return readFileSync13(skillMdPath, "utf-8");
|
|
16058
16217
|
}
|
|
16059
16218
|
return pointerSkillMd(name, description);
|
|
@@ -16094,8 +16253,8 @@ function resolveSkillAlias(name) {
|
|
|
16094
16253
|
}
|
|
16095
16254
|
|
|
16096
16255
|
// src/lib/project-state.ts
|
|
16097
|
-
import { existsSync as
|
|
16098
|
-
import { join as
|
|
16256
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
16257
|
+
import { join as join20 } from "path";
|
|
16099
16258
|
var VALID_PIN_SOURCES = [
|
|
16100
16259
|
"official",
|
|
16101
16260
|
"custom",
|
|
@@ -16110,14 +16269,14 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
16110
16269
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
16111
16270
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
16112
16271
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
16113
|
-
return
|
|
16272
|
+
return join20(targetDir, SKILLS_PROJECT_DIR);
|
|
16114
16273
|
}
|
|
16115
16274
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
16116
|
-
return
|
|
16275
|
+
return join20(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
16117
16276
|
}
|
|
16118
16277
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
16119
16278
|
const path = getProjectConfigPath(targetDir);
|
|
16120
|
-
if (!
|
|
16279
|
+
if (!existsSync11(path))
|
|
16121
16280
|
return null;
|
|
16122
16281
|
try {
|
|
16123
16282
|
return normalizeProjectConfig(JSON.parse(readFileSync14(path, "utf-8")));
|
|
@@ -16242,19 +16401,19 @@ function getSkillPath(name) {
|
|
|
16242
16401
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
16243
16402
|
if (extensionPath)
|
|
16244
16403
|
return extensionPath;
|
|
16245
|
-
return
|
|
16404
|
+
return join21(getPortableSkillsRoot(), skillName);
|
|
16246
16405
|
}
|
|
16247
16406
|
function getCanonicalSkillName(name) {
|
|
16248
16407
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
16249
16408
|
}
|
|
16250
16409
|
function skillExists(name) {
|
|
16251
|
-
return
|
|
16410
|
+
return existsSync12(getSkillPath(name));
|
|
16252
16411
|
}
|
|
16253
16412
|
function installSkill(name, options = {}) {
|
|
16254
16413
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
16255
16414
|
const canonicalName = getCanonicalSkillName(name);
|
|
16256
16415
|
const skillName = normalizeSkillName(canonicalName);
|
|
16257
|
-
if (!
|
|
16416
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16258
16417
|
const knownOfficial = Boolean(getSkill(name));
|
|
16259
16418
|
return {
|
|
16260
16419
|
skill: canonicalName,
|
|
@@ -16281,7 +16440,7 @@ function installSkill(name, options = {}) {
|
|
|
16281
16440
|
}
|
|
16282
16441
|
function installSkillSource(name, _options = {}) {
|
|
16283
16442
|
const canonicalName = getCanonicalSkillName(name);
|
|
16284
|
-
if (!
|
|
16443
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16285
16444
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
|
|
16286
16445
|
}
|
|
16287
16446
|
return {
|
|
@@ -16302,11 +16461,11 @@ function installSkillManifest(manifest, _options = {}) {
|
|
|
16302
16461
|
}
|
|
16303
16462
|
function createLocalSkillManifest(name, generateSkillMd) {
|
|
16304
16463
|
const sourcePath = getSkillPath(name);
|
|
16305
|
-
if (!
|
|
16464
|
+
if (!existsSync12(sourcePath))
|
|
16306
16465
|
return null;
|
|
16307
16466
|
let skillMd = "";
|
|
16308
|
-
const skillMdPath =
|
|
16309
|
-
if (
|
|
16467
|
+
const skillMdPath = join21(sourcePath, "SKILL.md");
|
|
16468
|
+
if (existsSync12(skillMdPath)) {
|
|
16310
16469
|
skillMd = readFileSync15(skillMdPath, "utf-8");
|
|
16311
16470
|
} else if (generateSkillMd) {
|
|
16312
16471
|
skillMd = generateSkillMd(name) ?? "";
|
|
@@ -16379,21 +16538,21 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
16379
16538
|
const base = projectDir || process.cwd();
|
|
16380
16539
|
switch (agent) {
|
|
16381
16540
|
case "pi":
|
|
16382
|
-
return scope === "project" ?
|
|
16541
|
+
return scope === "project" ? join21(base, ".pi", "skills") : join21(homedir4(), ".pi", "agent", "skills");
|
|
16383
16542
|
case "opencode":
|
|
16384
|
-
return scope === "project" ?
|
|
16543
|
+
return scope === "project" ? join21(base, ".opencode", "skills") : join21(homedir4(), ".config", "opencode", "skills");
|
|
16385
16544
|
default:
|
|
16386
|
-
return scope === "project" ?
|
|
16545
|
+
return scope === "project" ? join21(base, `.${agent}`, "skills") : join21(homedir4(), `.${agent}`, "skills");
|
|
16387
16546
|
}
|
|
16388
16547
|
}
|
|
16389
16548
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
16390
16549
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
16391
|
-
return
|
|
16550
|
+
return join21(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
16392
16551
|
}
|
|
16393
16552
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
16394
16553
|
assertNativeExportAllowed();
|
|
16395
16554
|
const canonicalName = getCanonicalSkillName(name);
|
|
16396
|
-
if (!
|
|
16555
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16397
16556
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
|
|
16398
16557
|
}
|
|
16399
16558
|
const scope = options.scope ?? "global";
|
|
@@ -16424,8 +16583,8 @@ function removeSkillForAgent(name, options) {
|
|
|
16424
16583
|
}
|
|
16425
16584
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
16426
16585
|
const sourcePath = getSkillPath(name);
|
|
16427
|
-
const skillMdPath =
|
|
16428
|
-
if (
|
|
16586
|
+
const skillMdPath = join21(sourcePath, "SKILL.md");
|
|
16587
|
+
if (existsSync12(skillMdPath))
|
|
16429
16588
|
return readFileSync15(skillMdPath, "utf-8");
|
|
16430
16589
|
if (generateSkillMd)
|
|
16431
16590
|
return generateSkillMd(name);
|
|
@@ -16444,7 +16603,7 @@ function warnMissingDependencies(name, targetDir) {
|
|
|
16444
16603
|
}
|
|
16445
16604
|
function generateMinimalSkillMd(name) {
|
|
16446
16605
|
const sourcePath = getSkillPath(name);
|
|
16447
|
-
if (!
|
|
16606
|
+
if (!existsSync12(sourcePath))
|
|
16448
16607
|
return null;
|
|
16449
16608
|
const canonicalName = getCanonicalSkillName(name);
|
|
16450
16609
|
const meta = getSkill(canonicalName);
|
|
@@ -16459,7 +16618,7 @@ function generateMinimalSkillMd(name) {
|
|
|
16459
16618
|
"---",
|
|
16460
16619
|
""
|
|
16461
16620
|
].filter(Boolean);
|
|
16462
|
-
const fallbackDoc = readFileIfExists(
|
|
16621
|
+
const fallbackDoc = readFileIfExists(join21(sourcePath, "README.md")) || readFileIfExists(join21(sourcePath, "CLAUDE.md"));
|
|
16463
16622
|
if (fallbackDoc)
|
|
16464
16623
|
return `${frontmatter.join(`
|
|
16465
16624
|
`)}${fallbackDoc.trim()}
|
|
@@ -16478,8 +16637,8 @@ skills run ${canonicalName}
|
|
|
16478
16637
|
`;
|
|
16479
16638
|
}
|
|
16480
16639
|
function readBundledSkillVersion(name) {
|
|
16481
|
-
const pkgPath =
|
|
16482
|
-
if (!
|
|
16640
|
+
const pkgPath = join21(getSkillPath(name), "package.json");
|
|
16641
|
+
if (!existsSync12(pkgPath))
|
|
16483
16642
|
return "unknown";
|
|
16484
16643
|
try {
|
|
16485
16644
|
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
@@ -16489,7 +16648,7 @@ function readBundledSkillVersion(name) {
|
|
|
16489
16648
|
}
|
|
16490
16649
|
}
|
|
16491
16650
|
function readFileIfExists(path) {
|
|
16492
|
-
return
|
|
16651
|
+
return existsSync12(path) ? readFileSync15(path, "utf-8") : null;
|
|
16493
16652
|
}
|
|
16494
16653
|
function loadProjectConfigCompat(targetDir) {
|
|
16495
16654
|
return loadProjectConfig(targetDir);
|
|
@@ -16533,8 +16692,8 @@ function selectSkillDoc(docs, file) {
|
|
|
16533
16692
|
function isInstructionSkillDir(skillPath, meta) {
|
|
16534
16693
|
if (meta?.kind === "instruction")
|
|
16535
16694
|
return true;
|
|
16536
|
-
const skillMdPath =
|
|
16537
|
-
if (!
|
|
16695
|
+
const skillMdPath = join23(skillPath, "SKILL.md");
|
|
16696
|
+
if (!existsSync13(skillMdPath))
|
|
16538
16697
|
return false;
|
|
16539
16698
|
try {
|
|
16540
16699
|
return parseSkillFrontmatter(readFileSync16(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
@@ -16560,12 +16719,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
|
|
|
16560
16719
|
];
|
|
16561
16720
|
function getSkillDocs(name) {
|
|
16562
16721
|
const skillPath = getSkillPath(name);
|
|
16563
|
-
if (!
|
|
16722
|
+
if (!existsSync13(skillPath))
|
|
16564
16723
|
return null;
|
|
16565
16724
|
return {
|
|
16566
|
-
skillMd: readIfExists(
|
|
16567
|
-
readme: readIfExists(
|
|
16568
|
-
claudeMd: readIfExists(
|
|
16725
|
+
skillMd: readIfExists(join23(skillPath, "SKILL.md")),
|
|
16726
|
+
readme: readIfExists(join23(skillPath, "README.md")),
|
|
16727
|
+
claudeMd: readIfExists(join23(skillPath, "CLAUDE.md"))
|
|
16569
16728
|
};
|
|
16570
16729
|
}
|
|
16571
16730
|
function getSkillBestDoc(name, file) {
|
|
@@ -16577,18 +16736,18 @@ function getSkillBestDoc(name, file) {
|
|
|
16577
16736
|
}
|
|
16578
16737
|
function getSkillRequirements(name) {
|
|
16579
16738
|
const skillPath = getSkillPath(name);
|
|
16580
|
-
if (!
|
|
16739
|
+
if (!existsSync13(skillPath))
|
|
16581
16740
|
return null;
|
|
16582
16741
|
const texts = [];
|
|
16583
16742
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
16584
|
-
const content = readIfExists(
|
|
16743
|
+
const content = readIfExists(join23(skillPath, file));
|
|
16585
16744
|
if (content)
|
|
16586
16745
|
texts.push(content);
|
|
16587
16746
|
}
|
|
16588
16747
|
const meta = getSkill(name);
|
|
16589
16748
|
let dependencies = {};
|
|
16590
16749
|
try {
|
|
16591
|
-
dependencies = JSON.parse(readFileSync16(
|
|
16750
|
+
dependencies = JSON.parse(readFileSync16(join23(skillPath, "package.json"), "utf8")).dependencies || {};
|
|
16592
16751
|
} catch {}
|
|
16593
16752
|
return getSkillRequirementsFromContent(name, texts, dependencies, meta);
|
|
16594
16753
|
}
|
|
@@ -16641,7 +16800,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
16641
16800
|
const meta = getSkill(name);
|
|
16642
16801
|
const canonicalName = meta?.name ?? name;
|
|
16643
16802
|
const skillPath = getSkillPath(canonicalName);
|
|
16644
|
-
if (!
|
|
16803
|
+
if (!existsSync13(skillPath)) {
|
|
16645
16804
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
16646
16805
|
}
|
|
16647
16806
|
if (isInstructionSkillDir(skillPath, meta)) {
|
|
@@ -16650,8 +16809,8 @@ async function runSkill(name, args, options = {}) {
|
|
|
16650
16809
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
16651
16810
|
};
|
|
16652
16811
|
}
|
|
16653
|
-
const pkgPath =
|
|
16654
|
-
if (!
|
|
16812
|
+
const pkgPath = join23(skillPath, "package.json");
|
|
16813
|
+
if (!existsSync13(pkgPath)) {
|
|
16655
16814
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
16656
16815
|
}
|
|
16657
16816
|
let entryPoint2;
|
|
@@ -16670,12 +16829,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
16670
16829
|
} catch {
|
|
16671
16830
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
16672
16831
|
}
|
|
16673
|
-
const entryPath =
|
|
16674
|
-
if (!
|
|
16832
|
+
const entryPath = join23(skillPath, entryPoint2);
|
|
16833
|
+
if (!existsSync13(entryPath)) {
|
|
16675
16834
|
return { exitCode: 1, error: `Entry point '${entryPoint2}' not found in skill '${name}'` };
|
|
16676
16835
|
}
|
|
16677
16836
|
const env = { ...process.env, ...options.env };
|
|
16678
|
-
if (!
|
|
16837
|
+
if (!existsSync13(join23(skillPath, "node_modules")) || hasDependencyPreparationMarker(skillPath)) {
|
|
16679
16838
|
const failure = await prepareSkillDependencies(skillPath, env, options.preparationTimeoutMs);
|
|
16680
16839
|
if (failure)
|
|
16681
16840
|
return failure;
|
|
@@ -16744,7 +16903,7 @@ function generateSkillMd(name) {
|
|
|
16744
16903
|
if (!meta)
|
|
16745
16904
|
return null;
|
|
16746
16905
|
const skillPath = getSkillPath(name);
|
|
16747
|
-
if (!
|
|
16906
|
+
if (!existsSync13(skillPath))
|
|
16748
16907
|
return null;
|
|
16749
16908
|
const frontmatter = [
|
|
16750
16909
|
"---",
|
|
@@ -16753,11 +16912,11 @@ function generateSkillMd(name) {
|
|
|
16753
16912
|
"---"
|
|
16754
16913
|
].join(`
|
|
16755
16914
|
`);
|
|
16756
|
-
const readme = readIfExists(
|
|
16757
|
-
const claudeMd = readIfExists(
|
|
16915
|
+
const readme = readIfExists(join23(skillPath, "README.md"));
|
|
16916
|
+
const claudeMd = readIfExists(join23(skillPath, "CLAUDE.md"));
|
|
16758
16917
|
let cliCommand = null;
|
|
16759
|
-
const pkgPath =
|
|
16760
|
-
if (
|
|
16918
|
+
const pkgPath = join23(skillPath, "package.json");
|
|
16919
|
+
if (existsSync13(pkgPath)) {
|
|
16761
16920
|
try {
|
|
16762
16921
|
const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
16763
16922
|
if (pkg.bin) {
|
|
@@ -16834,7 +16993,7 @@ function extractEnvVars(text2) {
|
|
|
16834
16993
|
}
|
|
16835
16994
|
function readIfExists(path) {
|
|
16836
16995
|
try {
|
|
16837
|
-
if (
|
|
16996
|
+
if (existsSync13(path)) {
|
|
16838
16997
|
return readFileSync16(path, "utf-8");
|
|
16839
16998
|
}
|
|
16840
16999
|
} catch {}
|
|
@@ -16901,6 +17060,8 @@ async function syncSelectionProfile(profileId, options = {}) {
|
|
|
16901
17060
|
async function resolveSelectionContext(profileId, options = {}) {
|
|
16902
17061
|
const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
|
|
16903
17062
|
const session = sessionState?.receipt ?? null;
|
|
17063
|
+
if (options.nativeParentSessionId !== undefined && session && (session.parent?.sessionId ?? null) !== options.nativeParentSessionId)
|
|
17064
|
+
throw new SkillSelectionError("SESSION_PARENT_MISMATCH", "The native session id is already bound to different parent custody.");
|
|
16904
17065
|
const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
|
|
16905
17066
|
if (!session && options.parentSessionId && !parentState) {
|
|
16906
17067
|
throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
|
|
@@ -17160,15 +17321,15 @@ function buildPluginProjection(entries) {
|
|
|
17160
17321
|
|
|
17161
17322
|
// src/lib/plugin-projection-store.ts
|
|
17162
17323
|
import { createHash as createHash14, randomUUID as randomUUID3 } from "crypto";
|
|
17163
|
-
import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as
|
|
17164
|
-
import { dirname as dirname13, isAbsolute as isAbsolute12, join as
|
|
17324
|
+
import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync18, mkdirSync as mkdirSync8, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
17325
|
+
import { dirname as dirname13, isAbsolute as isAbsolute12, join as join25, parse as parse5, resolve as resolve12 } from "path";
|
|
17165
17326
|
var READ_FLAGS = constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK;
|
|
17166
17327
|
var WRITE_FLAGS = constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW | constants8.O_NONBLOCK;
|
|
17167
17328
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
17168
17329
|
var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
17169
17330
|
function stat(path) {
|
|
17170
17331
|
try {
|
|
17171
|
-
return
|
|
17332
|
+
return lstatSync18(path, { bigint: true });
|
|
17172
17333
|
} catch (error) {
|
|
17173
17334
|
if (missing(error))
|
|
17174
17335
|
return null;
|
|
@@ -17176,7 +17337,7 @@ function stat(path) {
|
|
|
17176
17337
|
}
|
|
17177
17338
|
}
|
|
17178
17339
|
function canonical5(path) {
|
|
17179
|
-
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) &&
|
|
17340
|
+
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) && resolve12(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
|
|
17180
17341
|
}
|
|
17181
17342
|
function owner(stats, system = false) {
|
|
17182
17343
|
const current = uid();
|
|
@@ -17191,7 +17352,7 @@ function ancestors(path, create = false) {
|
|
|
17191
17352
|
let current = filesystemRoot;
|
|
17192
17353
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
17193
17354
|
if (segment)
|
|
17194
|
-
current =
|
|
17355
|
+
current = join25(current, segment);
|
|
17195
17356
|
let currentStat = stat(current);
|
|
17196
17357
|
if (!currentStat && create) {
|
|
17197
17358
|
try {
|
|
@@ -17306,7 +17467,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17306
17467
|
pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
|
|
17307
17468
|
const path = relative6 ? `${relative6}/${name}` : name;
|
|
17308
17469
|
pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
|
|
17309
|
-
const absolute =
|
|
17470
|
+
const absolute = join25(directory, name), entryStat = stat(absolute);
|
|
17310
17471
|
pluginNeed(entryStat, "Plugin tree member disappeared");
|
|
17311
17472
|
if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
|
|
17312
17473
|
const bytes2 = readBytes(absolute, 32, "native-pin");
|
|
@@ -17323,7 +17484,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17323
17484
|
directories.push({ path: absolute, stat: entryStat, names: pins });
|
|
17324
17485
|
for (const pin of pins) {
|
|
17325
17486
|
pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin) && Number(pin) <= 2147483647, "Unrecognized native plugin pin name");
|
|
17326
|
-
const pinPath =
|
|
17487
|
+
const pinPath = join25(absolute, pin), pinStat = stat(pinPath);
|
|
17327
17488
|
pluginNeed(pinStat, "Native plugin pin disappeared");
|
|
17328
17489
|
const bytes2 = readBytes(pinPath, 4096, "native-pin");
|
|
17329
17490
|
let value;
|
|
@@ -17421,7 +17582,7 @@ function materializePluginTree(root, entries) {
|
|
|
17421
17582
|
mkdirSync8(staging, { mode: 448 });
|
|
17422
17583
|
staged = stat(staging);
|
|
17423
17584
|
for (const entry of entries) {
|
|
17424
|
-
const path =
|
|
17585
|
+
const path = join25(staging, entry.path);
|
|
17425
17586
|
ancestors(path, true);
|
|
17426
17587
|
writeExclusive(path, entry.bytes, entry.mode);
|
|
17427
17588
|
}
|
|
@@ -17548,7 +17709,7 @@ function describeEntries(entries) {
|
|
|
17548
17709
|
|
|
17549
17710
|
// src/lib/plugin-admission.ts
|
|
17550
17711
|
function pluginAdmissionRoot() {
|
|
17551
|
-
return
|
|
17712
|
+
return join26(homedir5(), ".hasna", "skills", "plugin-admission");
|
|
17552
17713
|
}
|
|
17553
17714
|
function validatePluginTarget(value) {
|
|
17554
17715
|
pluginKeys(value, ["schemaVersion", "pluginId", "registrations", "native", "resolver"]);
|
|
@@ -17559,7 +17720,7 @@ function validatePluginTarget(value) {
|
|
|
17559
17720
|
for (const registration of value.registrations) {
|
|
17560
17721
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
17561
17722
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
17562
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) &&
|
|
17723
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) && resolve13(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
17563
17724
|
const key = JSON.stringify([registration.scope, registration.projectPath]);
|
|
17564
17725
|
pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
|
|
17565
17726
|
scopes.add(key);
|
|
@@ -17567,7 +17728,7 @@ function validatePluginTarget(value) {
|
|
|
17567
17728
|
for (const key of ["native", "resolver"]) {
|
|
17568
17729
|
pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
17569
17730
|
pluginText(value[key].executable);
|
|
17570
|
-
pluginNeed(isAbsolute13(value[key].executable) &&
|
|
17731
|
+
pluginNeed(isAbsolute13(value[key].executable) && resolve13(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
|
|
17571
17732
|
pluginDigest(value[key].digest);
|
|
17572
17733
|
}
|
|
17573
17734
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -17719,17 +17880,17 @@ function pluginResolverCommand(binding) {
|
|
|
17719
17880
|
}
|
|
17720
17881
|
function root(options) {
|
|
17721
17882
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
17722
|
-
pluginNeed(isAbsolute13(path) &&
|
|
17883
|
+
pluginNeed(isAbsolute13(path) && resolve13(path) === path, "Invalid plugin admission store root");
|
|
17723
17884
|
return path;
|
|
17724
17885
|
}
|
|
17725
17886
|
function pluginBindingPath(storeRoot, id) {
|
|
17726
17887
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
17727
|
-
return
|
|
17888
|
+
return join26(storeRoot, "bindings", `${id}.json`);
|
|
17728
17889
|
}
|
|
17729
17890
|
function pluginReceiptPath(storeRoot, id, digest) {
|
|
17730
17891
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
17731
17892
|
pluginDigest(digest);
|
|
17732
|
-
return
|
|
17893
|
+
return join26(storeRoot, "receipts", id, `${digest.slice(7)}.json`);
|
|
17733
17894
|
}
|
|
17734
17895
|
function readPluginBinding(storeRoot, id) {
|
|
17735
17896
|
const value = readPluginJson(pluginBindingPath(storeRoot, id));
|
|
@@ -17749,7 +17910,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest) {
|
|
|
17749
17910
|
pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest, "Plugin receipt identity differs from its filename");
|
|
17750
17911
|
const binding = readPluginBinding(storeRoot, id);
|
|
17751
17912
|
pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
|
|
17752
|
-
pluginNeed(receipt.materializedPath ===
|
|
17913
|
+
pluginNeed(receipt.materializedPath === join26(storeRoot, "objects", id, digest.slice(7)), "Plugin receipt points outside its immutable materialization");
|
|
17753
17914
|
pluginText(receipt.admittedAt, 64);
|
|
17754
17915
|
pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
|
|
17755
17916
|
return receipt;
|
|
@@ -17890,7 +18051,7 @@ async function admitPlugin(spec, profileId, target, approvedDigest, approvedEvid
|
|
|
17890
18051
|
const { plan, projection } = await candidate(spec, profileId, target, options);
|
|
17891
18052
|
pluginNeed(plan.planDigest === approvedDigest, "The plugin plan changed since review; obtain and review a new plan");
|
|
17892
18053
|
pluginNeed(plan.evidenceDigest === approvedEvidenceDigest, "The plugin routing evidence changed since review; obtain and review a new plan");
|
|
17893
|
-
const store = root(options), materializedPath =
|
|
18054
|
+
const store = root(options), materializedPath = join26(store, "objects", plan.bindingId, plan.planDigest.slice(7));
|
|
17894
18055
|
materializePluginTree(materializedPath, projection.files);
|
|
17895
18056
|
writePluginJsonImmutable(pluginBindingPath(store, plan.bindingId), plan.binding);
|
|
17896
18057
|
const path = pluginReceiptPath(store, plan.bindingId, plan.planDigest), existing = readPluginJson(path);
|
|
@@ -17924,21 +18085,21 @@ function validateManagedPluginWitnesses(value) {
|
|
|
17924
18085
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
17925
18086
|
seen.add(item.bindingId);
|
|
17926
18087
|
pluginText(item.storeRoot);
|
|
17927
|
-
pluginNeed(isAbsolute14(item.storeRoot) &&
|
|
18088
|
+
pluginNeed(isAbsolute14(item.storeRoot) && resolve14(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
17928
18089
|
}
|
|
17929
18090
|
}
|
|
17930
18091
|
function safe3(path) {
|
|
17931
18092
|
pluginText(path);
|
|
17932
|
-
pluginNeed(isAbsolute14(path) &&
|
|
18093
|
+
pluginNeed(isAbsolute14(path) && resolve14(path) === path, "Invalid plugin registry path");
|
|
17933
18094
|
for (let at = path;; at = dirname14(at)) {
|
|
17934
|
-
pluginNeed(!
|
|
18095
|
+
pluginNeed(!lstatSync19(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
17935
18096
|
if (at === dirname14(at))
|
|
17936
18097
|
break;
|
|
17937
18098
|
}
|
|
17938
18099
|
}
|
|
17939
18100
|
function readRegistry(path) {
|
|
17940
18101
|
safe3(path);
|
|
17941
|
-
const initial =
|
|
18102
|
+
const initial = lstatSync19(path);
|
|
17942
18103
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
17943
18104
|
const fd = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
17944
18105
|
try {
|
|
@@ -17952,7 +18113,7 @@ function readRegistry(path) {
|
|
|
17952
18113
|
break;
|
|
17953
18114
|
length += count;
|
|
17954
18115
|
}
|
|
17955
|
-
const after = fstatSync10(fd), current =
|
|
18116
|
+
const after = fstatSync10(fd), current = lstatSync19(path);
|
|
17956
18117
|
safe3(path);
|
|
17957
18118
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
17958
18119
|
let value;
|
|
@@ -17982,7 +18143,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
17982
18143
|
seen.add(target.pluginId);
|
|
17983
18144
|
const rows = plugins[target.pluginId];
|
|
17984
18145
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
17985
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot =
|
|
18146
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(dirname14(path), "cache", market, name);
|
|
17986
18147
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
17987
18148
|
plugins[target.pluginId] = rows.map((row) => {
|
|
17988
18149
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -17995,7 +18156,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
17995
18156
|
pluginText(row.sourceProducerPath);
|
|
17996
18157
|
pluginText(row.lastUpdated, 64);
|
|
17997
18158
|
pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
|
|
17998
|
-
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath ===
|
|
18159
|
+
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join27(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
17999
18160
|
const approval = (producer) => {
|
|
18000
18161
|
pluginText(producer);
|
|
18001
18162
|
const digest = `sha256:${basename7(producer)}`;
|
|
@@ -18025,7 +18186,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18025
18186
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
18026
18187
|
});
|
|
18027
18188
|
safe3(cacheRoot);
|
|
18028
|
-
const before =
|
|
18189
|
+
const before = lstatSync19(cacheRoot);
|
|
18029
18190
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
18030
18191
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
18031
18192
|
try {
|
|
@@ -18038,17 +18199,17 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18038
18199
|
directory.closeSync();
|
|
18039
18200
|
}
|
|
18040
18201
|
for (const version of names.sort()) {
|
|
18041
|
-
const files = pluginFileWitnesses(snapshotPluginTree(
|
|
18202
|
+
const files = pluginFileWitnesses(snapshotPluginTree(join27(cacheRoot, version), { nativeRuntime: `claude-${target.native.version}` }));
|
|
18042
18203
|
verifiedBytes += files.reduce((total, file) => total + file.size, 0);
|
|
18043
18204
|
pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
|
|
18044
18205
|
const serialized = JSON.stringify(files);
|
|
18045
|
-
const expected = currentFiles.get(
|
|
18206
|
+
const expected = currentFiles.get(join27(cacheRoot, version));
|
|
18046
18207
|
pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
|
|
18047
18208
|
pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
|
|
18048
18209
|
}
|
|
18049
18210
|
for (const row of rows)
|
|
18050
18211
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
18051
|
-
const after =
|
|
18212
|
+
const after = lstatSync19(cacheRoot);
|
|
18052
18213
|
safe3(cacheRoot);
|
|
18053
18214
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
18054
18215
|
}
|
|
@@ -18061,21 +18222,21 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
18061
18222
|
|
|
18062
18223
|
// src/lib/claude-marketplace-registry.ts
|
|
18063
18224
|
import { createHash as createHash15 } from "crypto";
|
|
18064
|
-
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as
|
|
18065
|
-
import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as
|
|
18225
|
+
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, readSync as readSync9 } from "fs";
|
|
18226
|
+
import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve15 } from "path";
|
|
18066
18227
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
18067
18228
|
function need5(value, reason) {
|
|
18068
18229
|
if (!value)
|
|
18069
18230
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
18070
18231
|
}
|
|
18071
18232
|
function absolutePath3(value) {
|
|
18072
|
-
need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) &&
|
|
18233
|
+
need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve15(value) === value, "requires a normalized absolute path");
|
|
18073
18234
|
}
|
|
18074
18235
|
var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
18075
18236
|
function parents2(path) {
|
|
18076
18237
|
const result = [];
|
|
18077
18238
|
for (let at = dirname15(path);; at = dirname15(at)) {
|
|
18078
|
-
const stat2 =
|
|
18239
|
+
const stat2 = lstatSync20(at, { bigint: true });
|
|
18079
18240
|
need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
18080
18241
|
result.push([at, stat2]);
|
|
18081
18242
|
if (at === dirname15(at))
|
|
@@ -18086,7 +18247,7 @@ function readRegistry2(path, budget) {
|
|
|
18086
18247
|
absolutePath3(path);
|
|
18087
18248
|
need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
18088
18249
|
need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
18089
|
-
const ancestors2 = parents2(path), initial =
|
|
18250
|
+
const ancestors2 = parents2(path), initial = lstatSync20(path, { bigint: true });
|
|
18090
18251
|
need5(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
18091
18252
|
need5(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
18092
18253
|
const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
@@ -18101,10 +18262,10 @@ function readRegistry2(path, budget) {
|
|
|
18101
18262
|
break;
|
|
18102
18263
|
length += count;
|
|
18103
18264
|
}
|
|
18104
|
-
const after = fstatSync11(fd, { bigint: true }), current =
|
|
18265
|
+
const after = fstatSync11(fd, { bigint: true }), current = lstatSync20(path, { bigint: true });
|
|
18105
18266
|
need5(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
18106
18267
|
for (const [ancestor, before] of ancestors2) {
|
|
18107
|
-
const now =
|
|
18268
|
+
const now = lstatSync20(ancestor, { bigint: true });
|
|
18108
18269
|
need5(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
18109
18270
|
}
|
|
18110
18271
|
budget.remaining -= length;
|
|
@@ -18244,7 +18405,7 @@ function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 10
|
|
|
18244
18405
|
import { createHash as createHash17 } from "crypto";
|
|
18245
18406
|
|
|
18246
18407
|
// src/lib/codex-hook-trust-files.ts
|
|
18247
|
-
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as
|
|
18408
|
+
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
18248
18409
|
import { createHash as createHash16 } from "crypto";
|
|
18249
18410
|
import { dirname as dirname16 } from "path";
|
|
18250
18411
|
function need6(value, code) {
|
|
@@ -18254,7 +18415,7 @@ function need6(value, code) {
|
|
|
18254
18415
|
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
18255
18416
|
function safeParents(file) {
|
|
18256
18417
|
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
18257
|
-
const s =
|
|
18418
|
+
const s = lstatSync21(p);
|
|
18258
18419
|
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
18259
18420
|
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
18260
18421
|
if (dirname16(p) === p)
|
|
@@ -18264,7 +18425,7 @@ function safeParents(file) {
|
|
|
18264
18425
|
function snapshot(file, privateFile = false, options = {}) {
|
|
18265
18426
|
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
18266
18427
|
safeParents(file);
|
|
18267
|
-
const initial =
|
|
18428
|
+
const initial = lstatSync21(file, { bigint: true });
|
|
18268
18429
|
need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
18269
18430
|
need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
18270
18431
|
const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
@@ -18279,7 +18440,7 @@ function snapshot(file, privateFile = false, options = {}) {
|
|
|
18279
18440
|
break;
|
|
18280
18441
|
length += n2;
|
|
18281
18442
|
}
|
|
18282
|
-
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before,
|
|
18443
|
+
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
|
|
18283
18444
|
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18284
18445
|
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
18285
18446
|
} finally {
|
|
@@ -18331,7 +18492,7 @@ function safe4(path) {
|
|
|
18331
18492
|
if (!isAbsolute16(path) || path.includes("\x00"))
|
|
18332
18493
|
throw new Error("Expected an absolute native discovery path");
|
|
18333
18494
|
for (let cursor = path;; cursor = dirname17(cursor)) {
|
|
18334
|
-
if (
|
|
18495
|
+
if (lstatSync22(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
18335
18496
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
18336
18497
|
if (dirname17(cursor) === cursor)
|
|
18337
18498
|
break;
|
|
@@ -18341,7 +18502,7 @@ function read2(path, changes) {
|
|
|
18341
18502
|
safe4(path);
|
|
18342
18503
|
if (changes?.has(path))
|
|
18343
18504
|
return changes.get(path);
|
|
18344
|
-
const stat2 =
|
|
18505
|
+
const stat2 = lstatSync22(path, { throwIfNoEntry: false });
|
|
18345
18506
|
if (!stat2)
|
|
18346
18507
|
return null;
|
|
18347
18508
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
@@ -18452,12 +18613,12 @@ function codexInstallationRoots(binding) {
|
|
|
18452
18613
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
18453
18614
|
throw new Error("Invalid native installation input proof");
|
|
18454
18615
|
const cache = dirname17(dirname17(proof.plugins[0].pluginParent));
|
|
18455
|
-
const config =
|
|
18616
|
+
const config = join28(dirname17(dirname17(cache)), "config.toml"), home = dirname17(dirname17(dirname17(cache)));
|
|
18456
18617
|
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
18457
|
-
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
18618
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join28(home, path));
|
|
18458
18619
|
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep6) || input.sourceRoot.startsWith(root2 + sep6)) || binding.sources.some((source) => basename9(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep6)))))
|
|
18459
18620
|
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
18460
|
-
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path ===
|
|
18621
|
+
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join28(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
18461
18622
|
throw new Error("Missing native installation input source witness");
|
|
18462
18623
|
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
18463
18624
|
const text2 = read2(path);
|
|
@@ -18499,19 +18660,19 @@ function captureDiscoveryPathSources(paths) {
|
|
|
18499
18660
|
const budget = discoveryByteBudget();
|
|
18500
18661
|
return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
|
|
18501
18662
|
}
|
|
18502
|
-
function assertProjectDiscovery(agent, directories, home, canonical6 =
|
|
18663
|
+
function assertProjectDiscovery(agent, directories, home, canonical6 = resolve16, reviewed) {
|
|
18503
18664
|
const homeConfigPath = canonical6(agentDiscoveryConfigPath(home, agent));
|
|
18504
18665
|
for (const directory of directories) {
|
|
18505
|
-
const isHome =
|
|
18666
|
+
const isHome = resolve16(directory) === resolve16(home);
|
|
18506
18667
|
if (agent === "hermes") {
|
|
18507
18668
|
assertHermesEnvironment(home);
|
|
18508
18669
|
continue;
|
|
18509
18670
|
}
|
|
18510
|
-
if (isHome && agent !== "claude")
|
|
18671
|
+
if (isHome && agent !== "claude" && agent !== "sumi")
|
|
18511
18672
|
continue;
|
|
18512
|
-
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : [".cursor/hooks.json"];
|
|
18673
|
+
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : agent === "sumi" ? ["sumi.json", "sumi.jsonc", ".sumi/sumi.json", ".sumi/sumi.jsonc"] : [".cursor/hooks.json"];
|
|
18513
18674
|
for (const suffix of paths) {
|
|
18514
|
-
const path = canonical6(
|
|
18675
|
+
const path = canonical6(join28(directory, suffix)), raw = read2(path);
|
|
18515
18676
|
if (raw === null)
|
|
18516
18677
|
continue;
|
|
18517
18678
|
const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
|
|
@@ -18520,7 +18681,7 @@ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15
|
|
|
18520
18681
|
if (suffix.endsWith(".jsonc"))
|
|
18521
18682
|
throw new Error(`NATIVE_SKILL_DRIFT: project JSONC discovery configuration requires review: ${path}`);
|
|
18522
18683
|
const config = parseConfig(raw, path, suffix.endsWith(".toml"));
|
|
18523
|
-
const keys = agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces", "skillOverrides"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : ["hooks"];
|
|
18684
|
+
const keys = agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces", "skillOverrides"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["plugin", "plugins", "skills", "permissions"] : ["hooks"];
|
|
18524
18685
|
let admittedPlugins = false;
|
|
18525
18686
|
if (agent === "claude" && reviewed?.agent === agent && reviewed.method === "reviewed" && config.enabledPlugins && typeof config.enabledPlugins === "object" && !Array.isArray(config.enabledPlugins)) {
|
|
18526
18687
|
const exact = reviewed.sources.find((source) => source.path === path && source.format === undefined && source.fields === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
@@ -18533,19 +18694,28 @@ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15
|
|
|
18533
18694
|
if (keys.some((key) => config[key] !== undefined && !(key === "enabledPlugins" && admittedPlugins)) || config.disableAllHooks === true || config.disableBundledSkills === false || config.hooksConfig?.enabled === false || config.permission?.skill !== undefined || config.permissions?.deny?.some((rule) => typeof rule === "string" && /^Skill(?:\(|$)/.test(rule)))
|
|
18534
18695
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
|
|
18535
18696
|
}
|
|
18697
|
+
if (agent === "sumi")
|
|
18698
|
+
for (const name of ["plugin", "plugins"]) {
|
|
18699
|
+
const path = canonical6(join28(directory, ".sumi", name));
|
|
18700
|
+
safe4(path);
|
|
18701
|
+
if (lstatSync22(path, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(path).length)
|
|
18702
|
+
throw new Error(`NATIVE_SKILL_DRIFT: project Sumi plugins require a dedicated discovery review: ${path}`);
|
|
18703
|
+
}
|
|
18536
18704
|
if (agent === "claude") {
|
|
18537
|
-
const commands = canonical6(
|
|
18705
|
+
const commands = canonical6(join28(directory, ".claude/commands"));
|
|
18538
18706
|
safe4(commands);
|
|
18539
|
-
if (
|
|
18707
|
+
if (lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
18540
18708
|
throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
|
|
18541
18709
|
}
|
|
18542
18710
|
}
|
|
18543
18711
|
}
|
|
18544
18712
|
function agentDiscoveryConfigPath(home, agent) {
|
|
18545
|
-
|
|
18713
|
+
if (agent === "sumi")
|
|
18714
|
+
return sumiConfigPath(home);
|
|
18715
|
+
return join28(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
|
|
18546
18716
|
}
|
|
18547
18717
|
function resolveAgentDiscovery(options) {
|
|
18548
|
-
const canonical6 = options.canonical ??
|
|
18718
|
+
const canonical6 = options.canonical ?? resolve16, home = resolve16(options.home), agent = options.agent;
|
|
18549
18719
|
const sources = [], roots = new Set, builtinNames = [];
|
|
18550
18720
|
const witness = (path, format, fields2) => {
|
|
18551
18721
|
const source = { path: canonical6(path), sha256: null, ...format ? { format, fields: fields2 } : {} };
|
|
@@ -18556,13 +18726,49 @@ function resolveAgentDiscovery(options) {
|
|
|
18556
18726
|
if (agent === "hermes")
|
|
18557
18727
|
assertHermesEnvironment(home);
|
|
18558
18728
|
const configPath = agentDiscoveryConfigPath(home, agent);
|
|
18559
|
-
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
|
|
18729
|
+
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["skills", "plugin", "plugins", "permissions"] : ["version"]);
|
|
18560
18730
|
const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
|
|
18561
18731
|
const unresolved = (detail) => {
|
|
18562
18732
|
throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
|
|
18563
18733
|
};
|
|
18734
|
+
if (agent === "sumi") {
|
|
18735
|
+
const directory = sumiConfigDirectory(home);
|
|
18736
|
+
if (witness(join28(directory, "sumi.jsonc")) !== null)
|
|
18737
|
+
unresolved("JSONC configuration requires a supported format adapter");
|
|
18738
|
+
for (const root2 of [directory, join28(home, ".claude"), join28(home, ".agents")])
|
|
18739
|
+
for (const name of ["skill", "skills"])
|
|
18740
|
+
roots.add(join28(root2, name));
|
|
18741
|
+
if (config.skills !== undefined && (!Array.isArray(config.skills) || config.skills.some((path) => typeof path !== "string")))
|
|
18742
|
+
unresolved("skills must be a string array");
|
|
18743
|
+
for (const value of config.skills ?? []) {
|
|
18744
|
+
if (typeof value !== "string" || /[\0$]/.test(value) || !value || !(isAbsolute16(value) || value.startsWith("~/")))
|
|
18745
|
+
unresolved("remote or relative skill sources need a dedicated discovery adapter");
|
|
18746
|
+
roots.add(value.startsWith("~/") ? join28(home, value.slice(2)) : value);
|
|
18747
|
+
}
|
|
18748
|
+
for (const key of ["plugin", "plugins"])
|
|
18749
|
+
if (config[key] !== undefined && !Array.isArray(config[key]))
|
|
18750
|
+
unresolved(`${key} must be an array`);
|
|
18751
|
+
const plugins = [join28(directory, "plugin"), join28(directory, "plugins")];
|
|
18752
|
+
for (const path of plugins) {
|
|
18753
|
+
safe4(path);
|
|
18754
|
+
if (lstatSync22(path, { throwIfNoEntry: false })?.isDirectory())
|
|
18755
|
+
for (const name of readdirSync10(path).sort()) {
|
|
18756
|
+
if (name === "skills-cli.js" && path === plugins[1])
|
|
18757
|
+
continue;
|
|
18758
|
+
const target = join28(path, name);
|
|
18759
|
+
if (!lstatSync22(target).isFile())
|
|
18760
|
+
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
18761
|
+
witness(target);
|
|
18762
|
+
}
|
|
18763
|
+
}
|
|
18764
|
+
for (const root2 of roots) {
|
|
18765
|
+
safe4(root2);
|
|
18766
|
+
if (lstatSync22(root2, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(root2).some((name) => name.endsWith(".md")))
|
|
18767
|
+
unresolved("flat markdown skills require a dedicated native migration adapter");
|
|
18768
|
+
}
|
|
18769
|
+
}
|
|
18564
18770
|
if (agent === "hermes") {
|
|
18565
|
-
const profile = witness(
|
|
18771
|
+
const profile = witness(join28(home, ".hermes/active_profile"));
|
|
18566
18772
|
if (profile?.trim() && profile.trim() !== "default")
|
|
18567
18773
|
unresolved("a non-default profile is active");
|
|
18568
18774
|
const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
|
|
@@ -18576,7 +18782,7 @@ function resolveAgentDiscovery(options) {
|
|
|
18576
18782
|
continue;
|
|
18577
18783
|
if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
|
|
18578
18784
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
18579
|
-
const path = normalized === "~" ? home : normalized.startsWith("~/") ?
|
|
18785
|
+
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join28(home, normalized.slice(2)) : resolve16(home, ".hermes", normalized);
|
|
18580
18786
|
safe4(path);
|
|
18581
18787
|
roots.add(path);
|
|
18582
18788
|
}
|
|
@@ -18585,8 +18791,8 @@ function resolveAgentDiscovery(options) {
|
|
|
18585
18791
|
const executable = Bun.which("gemini");
|
|
18586
18792
|
if (executable) {
|
|
18587
18793
|
let packageRoot;
|
|
18588
|
-
for (let directory = dirname17(
|
|
18589
|
-
const path =
|
|
18794
|
+
for (let directory = dirname17(realpathSync8(executable)), depth = 0;depth < 8; directory = dirname17(directory), depth++) {
|
|
18795
|
+
const path = join28(directory, "package.json"), raw = read2(path);
|
|
18590
18796
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
18591
18797
|
packageRoot = directory;
|
|
18592
18798
|
witness(path);
|
|
@@ -18597,12 +18803,12 @@ function resolveAgentDiscovery(options) {
|
|
|
18597
18803
|
}
|
|
18598
18804
|
if (!packageRoot)
|
|
18599
18805
|
unresolved("installed Gemini package layout is not recognized");
|
|
18600
|
-
const builtinRoot =
|
|
18806
|
+
const builtinRoot = join28(packageRoot, "bundle/builtin");
|
|
18601
18807
|
safe4(builtinRoot);
|
|
18602
|
-
if (!
|
|
18808
|
+
if (!lstatSync22(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
18603
18809
|
unresolved("installed Gemini builtin root is missing");
|
|
18604
18810
|
for (const name of readdirSync10(builtinRoot).sort()) {
|
|
18605
|
-
const text2 = witness(
|
|
18811
|
+
const text2 = witness(join28(builtinRoot, name, "SKILL.md"));
|
|
18606
18812
|
const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
|
|
18607
18813
|
if (!match?.[1] || builtinNames.includes(match[1]))
|
|
18608
18814
|
unresolved("installed Gemini builtin name is missing or ambiguous");
|
|
@@ -18644,39 +18850,39 @@ function resolveAgentDiscovery(options) {
|
|
|
18644
18850
|
function plugin(root2) {
|
|
18645
18851
|
root2 = canonical6(root2);
|
|
18646
18852
|
safe4(root2);
|
|
18647
|
-
const manifestPath =
|
|
18853
|
+
const manifestPath = join28(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
18648
18854
|
const raw = witness(manifestPath);
|
|
18649
18855
|
if (raw === null)
|
|
18650
18856
|
unresolved("plugin manifest missing");
|
|
18651
18857
|
const manifest = parseConfig(raw, manifestPath);
|
|
18652
|
-
const hooks = witness(
|
|
18858
|
+
const hooks = witness(join28(root2, "hooks/hooks.json"));
|
|
18653
18859
|
if (manifest.hooks !== undefined || hooks !== null)
|
|
18654
18860
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
18655
|
-
const commands =
|
|
18861
|
+
const commands = join28(root2, "commands");
|
|
18656
18862
|
safe4(commands);
|
|
18657
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
18863
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length))
|
|
18658
18864
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
18659
|
-
const rootSkill = witness(
|
|
18865
|
+
const rootSkill = witness(join28(root2, "SKILL.md"));
|
|
18660
18866
|
if (rootSkill !== null)
|
|
18661
18867
|
roots.add(root2);
|
|
18662
18868
|
const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
18663
18869
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
18664
18870
|
unresolved("unsupported plugin skill paths");
|
|
18665
18871
|
if (agent === "claude")
|
|
18666
|
-
roots.add(
|
|
18872
|
+
roots.add(join28(root2, "skills"));
|
|
18667
18873
|
for (const value of declared) {
|
|
18668
|
-
const path =
|
|
18874
|
+
const path = resolve16(root2, value);
|
|
18669
18875
|
if (path !== root2 && !path.startsWith(root2 + sep6))
|
|
18670
18876
|
unresolved("plugin skill path escapes its root");
|
|
18671
18877
|
roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
|
|
18672
18878
|
}
|
|
18673
18879
|
}
|
|
18674
18880
|
if (agent === "claude") {
|
|
18675
|
-
const registrations = witness(
|
|
18676
|
-
const installed = registrations === null ? {} : parseConfig(registrations,
|
|
18677
|
-
const commands = canonical6(
|
|
18881
|
+
const registrations = witness(join28(home, ".claude/plugins/installed_plugins.json"));
|
|
18882
|
+
const installed = registrations === null ? {} : parseConfig(registrations, join28(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
18883
|
+
const commands = canonical6(join28(home, ".claude/commands"));
|
|
18678
18884
|
safe4(commands);
|
|
18679
|
-
if (
|
|
18885
|
+
if (lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
18680
18886
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
18681
18887
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
18682
18888
|
if (enabled === false)
|
|
@@ -18698,16 +18904,16 @@ function resolveAgentDiscovery(options) {
|
|
|
18698
18904
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
18699
18905
|
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute16(market.source))
|
|
18700
18906
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
18701
|
-
const catalogText = witness(
|
|
18907
|
+
const catalogText = witness(join28(market.source, ".agents/plugins/marketplace.json"));
|
|
18702
18908
|
if (catalogText === null)
|
|
18703
18909
|
unresolved("marketplace catalog missing");
|
|
18704
|
-
const catalog = parseConfig(catalogText,
|
|
18910
|
+
const catalog = parseConfig(catalogText, join28(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
18705
18911
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
18706
18912
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
18707
|
-
plugin(
|
|
18913
|
+
plugin(resolve16(market.source, matches[0].source.path));
|
|
18708
18914
|
}
|
|
18709
18915
|
} else if (agent === "opencode") {
|
|
18710
|
-
if (witness(
|
|
18916
|
+
if (witness(join28(home, ".config/opencode/opencode.jsonc")) !== null || witness(join28(home, ".config/opencode/config.json")) !== null)
|
|
18711
18917
|
unresolved("additional config layers require review");
|
|
18712
18918
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
18713
18919
|
unresolved("external plugin or remote skill source");
|
|
@@ -18716,14 +18922,17 @@ function resolveAgentDiscovery(options) {
|
|
|
18716
18922
|
unresolved("relative or malformed added skill root");
|
|
18717
18923
|
roots.add(path);
|
|
18718
18924
|
}
|
|
18719
|
-
const localPlugins =
|
|
18925
|
+
const localPlugins = join28(home, ".config/opencode/plugins");
|
|
18720
18926
|
safe4(localPlugins);
|
|
18721
|
-
if (
|
|
18927
|
+
if (lstatSync22(localPlugins, { throwIfNoEntry: false }) && readdirSync10(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
18722
18928
|
unresolved("additional local plugin hooks require review");
|
|
18929
|
+
} else if (agent === "sumi") {
|
|
18930
|
+
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical6(configPath) && source.path !== canonical6(join28(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
|
|
18931
|
+
unresolved("external or foreign local plugins require review");
|
|
18723
18932
|
} else if (agent === "hermes") {
|
|
18724
|
-
for (const directory of [
|
|
18933
|
+
for (const directory of [join28(home, ".hermes/plugins"), join28(home, ".hermes/hermes-agent")]) {
|
|
18725
18934
|
safe4(directory);
|
|
18726
|
-
if (
|
|
18935
|
+
if (lstatSync22(directory, { throwIfNoEntry: false }))
|
|
18727
18936
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
18728
18937
|
}
|
|
18729
18938
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -18731,30 +18940,30 @@ function resolveAgentDiscovery(options) {
|
|
|
18731
18940
|
} else if (agent === "gemini") {
|
|
18732
18941
|
if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
|
|
18733
18942
|
unresolved("custom extension or skill paths");
|
|
18734
|
-
const directory =
|
|
18943
|
+
const directory = join28(home, ".gemini/extensions");
|
|
18735
18944
|
safe4(directory);
|
|
18736
|
-
if (
|
|
18945
|
+
if (lstatSync22(directory, { throwIfNoEntry: false }))
|
|
18737
18946
|
for (const name of readdirSync10(directory)) {
|
|
18738
|
-
const root2 =
|
|
18947
|
+
const root2 = join28(directory, name);
|
|
18739
18948
|
safe4(root2);
|
|
18740
|
-
if (!
|
|
18949
|
+
if (!lstatSync22(root2)?.isDirectory())
|
|
18741
18950
|
continue;
|
|
18742
|
-
const raw = witness(
|
|
18951
|
+
const raw = witness(join28(root2, "gemini-extension.json"));
|
|
18743
18952
|
if (raw === null)
|
|
18744
18953
|
unresolved("extension registration missing");
|
|
18745
|
-
const manifest = parseConfig(raw,
|
|
18746
|
-
if (manifest.hooks !== undefined || witness(
|
|
18954
|
+
const manifest = parseConfig(raw, join28(root2, "gemini-extension.json"));
|
|
18955
|
+
if (manifest.hooks !== undefined || witness(join28(root2, "hooks/hooks.json")) !== null || witness(join28(root2, ".gemini-extension-install.json")) !== null)
|
|
18747
18956
|
unresolved("extension hooks or linked registration require review");
|
|
18748
|
-
roots.add(
|
|
18957
|
+
roots.add(join28(root2, "skills"));
|
|
18749
18958
|
}
|
|
18750
18959
|
}
|
|
18751
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([
|
|
18960
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join28(home, ".hermes/plugins"), join28(home, ".hermes/hermes-agent")].map((path) => canonical6(path))) } : {} };
|
|
18752
18961
|
}
|
|
18753
18962
|
|
|
18754
18963
|
// src/lib/authoring-path.ts
|
|
18755
18964
|
var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
|
|
18756
18965
|
function pathParts(path) {
|
|
18757
|
-
return
|
|
18966
|
+
return resolve17(path).split(sep7).filter(Boolean);
|
|
18758
18967
|
}
|
|
18759
18968
|
function nativeRootMatch(path) {
|
|
18760
18969
|
const parts = pathParts(path);
|
|
@@ -18768,24 +18977,24 @@ function nativeRootMatch(path) {
|
|
|
18768
18977
|
return;
|
|
18769
18978
|
}
|
|
18770
18979
|
function physicalPath(path) {
|
|
18771
|
-
let cursor =
|
|
18980
|
+
let cursor = resolve17(path);
|
|
18772
18981
|
const suffix = [];
|
|
18773
|
-
while (!
|
|
18982
|
+
while (!lstatSync23(cursor, { throwIfNoEntry: false })) {
|
|
18774
18983
|
suffix.unshift(basename10(cursor));
|
|
18775
18984
|
const parent = dirname18(cursor);
|
|
18776
18985
|
if (parent === cursor)
|
|
18777
|
-
return
|
|
18986
|
+
return resolve17(path);
|
|
18778
18987
|
cursor = parent;
|
|
18779
18988
|
}
|
|
18780
|
-
return
|
|
18989
|
+
return resolve17(realpathSync9(cursor), ...suffix);
|
|
18781
18990
|
}
|
|
18782
18991
|
function contains(root2, path) {
|
|
18783
18992
|
return path === root2 || path.startsWith(root2 + sep7);
|
|
18784
18993
|
}
|
|
18785
18994
|
function configuredRoots(home, explicitHome) {
|
|
18786
|
-
const homeData =
|
|
18995
|
+
const homeData = join29(home, ".hasna", "skills");
|
|
18787
18996
|
const homePolicy = readManagedSkillPolicySnapshot(homeData);
|
|
18788
|
-
const configuredPolicy = !explicitHome &&
|
|
18997
|
+
const configuredPolicy = !explicitHome && resolve17(getDataDirReadOnly()) !== resolve17(homeData) ? readManagedSkillPolicySnapshot(getDataDirReadOnly()) : null;
|
|
18789
18998
|
const policy = homePolicy ?? configuredPolicy;
|
|
18790
18999
|
const bridge = policy?.value.bridge;
|
|
18791
19000
|
if (bridge && (typeof bridge.home !== "string" || physicalPath(bridge.home) !== physicalPath(home))) {
|
|
@@ -18814,8 +19023,8 @@ function configuredRoots(home, explicitHome) {
|
|
|
18814
19023
|
admitBinding(binding);
|
|
18815
19024
|
continue;
|
|
18816
19025
|
}
|
|
18817
|
-
const nativeHome =
|
|
18818
|
-
if (!
|
|
19026
|
+
const nativeHome = join29(home, agent === "opencode" ? ".config/opencode" : `.${agent}`);
|
|
19027
|
+
if (!lstatSync23(nativeHome, { throwIfNoEntry: false }))
|
|
18819
19028
|
continue;
|
|
18820
19029
|
roots.push(...resolveAgentDiscovery({ home, agent, canonical: physicalPath }).roots);
|
|
18821
19030
|
}
|
|
@@ -18824,22 +19033,22 @@ function configuredRoots(home, explicitHome) {
|
|
|
18824
19033
|
function assertPortableAuthoringPath(path, options = {}) {
|
|
18825
19034
|
if (typeof path !== "string" || !path || path.includes("\x00"))
|
|
18826
19035
|
throw new Error("Invalid portable authoring path");
|
|
18827
|
-
const lexical =
|
|
19036
|
+
const lexical = resolve17(path);
|
|
18828
19037
|
const physical = physicalPath(lexical);
|
|
18829
19038
|
const candidates = [lexical, physical];
|
|
18830
19039
|
let native = candidates.map(nativeRootMatch).find(Boolean);
|
|
18831
|
-
const home =
|
|
18832
|
-
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) =>
|
|
18833
|
-
defaults.push(
|
|
19040
|
+
const home = resolve17(options.homeDir ?? homedir6());
|
|
19041
|
+
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) => join29(home, ...parts));
|
|
19042
|
+
defaults.push(join29(home, ".codex/plugins/cache"), join29(home, ".claude/plugins/cache"), join29(home, ".claude/plugins/synced"), join29(home, ".gemini/extensions"));
|
|
18834
19043
|
for (const root2 of defaults) {
|
|
18835
|
-
if (candidates.some((candidate2) => contains(
|
|
19044
|
+
if (candidates.some((candidate2) => contains(resolve17(root2), candidate2) || contains(physicalPath(root2), candidate2)))
|
|
18836
19045
|
native ??= root2;
|
|
18837
19046
|
}
|
|
18838
19047
|
if (native) {
|
|
18839
19048
|
throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
|
|
18840
19049
|
}
|
|
18841
19050
|
for (const root2 of configuredRoots(home, options.homeDir !== undefined)) {
|
|
18842
|
-
if (candidates.some((candidate2) => contains(
|
|
19051
|
+
if (candidates.some((candidate2) => contains(resolve17(root2), candidate2) || contains(physicalPath(root2), candidate2))) {
|
|
18843
19052
|
throw new Error(`Refusing portable Skills authoring below configured native discovery root: ${lexical}`);
|
|
18844
19053
|
}
|
|
18845
19054
|
}
|
|
@@ -18851,15 +19060,15 @@ function getPortableSkillsRoot(options = {}) {
|
|
|
18851
19060
|
function resolvePortableSkillsRoot(options, migrate) {
|
|
18852
19061
|
if (options.rootDir)
|
|
18853
19062
|
return options.rootDir;
|
|
18854
|
-
const appDir = options.homeDir ?
|
|
18855
|
-
const cache =
|
|
19063
|
+
const appDir = options.homeDir ? join30(options.homeDir, ".hasna", "skills") : migrate ? getDataDir() : getDataDirReadOnly();
|
|
19064
|
+
const cache = join30(appDir, SKILLS_CACHE_DIRNAME);
|
|
18856
19065
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
18857
19066
|
return cache;
|
|
18858
|
-
const installed =
|
|
19067
|
+
const installed = join30(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
18859
19068
|
return installed;
|
|
18860
19069
|
}
|
|
18861
19070
|
function getPortableSkillPath(name, options = {}) {
|
|
18862
|
-
return
|
|
19071
|
+
return join30(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
18863
19072
|
}
|
|
18864
19073
|
function findPortableSkill(name, options = {}) {
|
|
18865
19074
|
let normalized;
|
|
@@ -18869,7 +19078,7 @@ function findPortableSkill(name, options = {}) {
|
|
|
18869
19078
|
return null;
|
|
18870
19079
|
}
|
|
18871
19080
|
const path = getPortableSkillPath(normalized, options);
|
|
18872
|
-
if (!
|
|
19081
|
+
if (!existsSync14(path) || !statSync10(path).isDirectory())
|
|
18873
19082
|
return null;
|
|
18874
19083
|
try {
|
|
18875
19084
|
return summarizePortableSkill(path, normalized);
|
|
@@ -18885,7 +19094,7 @@ function listPortableSkills(options = {}) {
|
|
|
18885
19094
|
for (const entry of readdirSync11(root2).sort()) {
|
|
18886
19095
|
if (entry.startsWith("."))
|
|
18887
19096
|
continue;
|
|
18888
|
-
const path =
|
|
19097
|
+
const path = join30(root2, entry);
|
|
18889
19098
|
if (!safeIsDirectory(path))
|
|
18890
19099
|
continue;
|
|
18891
19100
|
try {
|
|
@@ -18919,11 +19128,11 @@ function isOfficialSkillName(name) {
|
|
|
18919
19128
|
function scaffoldPortableSkill(name, options = {}) {
|
|
18920
19129
|
const skillName = normalizeNewPortableSkillName(name);
|
|
18921
19130
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
18922
|
-
assertPortableAuthoringPath(
|
|
19131
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, skillName), options);
|
|
18923
19132
|
const root2 = getPortableSkillsRoot(options);
|
|
18924
|
-
const skillPath =
|
|
19133
|
+
const skillPath = join30(root2, skillName);
|
|
18925
19134
|
assertPortableAuthoringPath(skillPath, options);
|
|
18926
|
-
if (
|
|
19135
|
+
if (existsSync14(skillPath)) {
|
|
18927
19136
|
if (!options.overwrite)
|
|
18928
19137
|
throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
|
|
18929
19138
|
rmSync4(skillPath, { recursive: true, force: true });
|
|
@@ -18941,7 +19150,7 @@ function scaffoldPortableSkill(name, options = {}) {
|
|
|
18941
19150
|
}
|
|
18942
19151
|
function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
18943
19152
|
const absoluteSource = normalize4(sourceDir);
|
|
18944
|
-
if (!
|
|
19153
|
+
if (!existsSync14(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
|
|
18945
19154
|
throw new Error(`Import directory not found: ${sourceDir}`);
|
|
18946
19155
|
}
|
|
18947
19156
|
const continueOnError = options.continueOnError ?? true;
|
|
@@ -18955,7 +19164,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
18955
19164
|
const skipped = [];
|
|
18956
19165
|
const entries = readdirSync11(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
18957
19166
|
for (const entryName of entries) {
|
|
18958
|
-
const childPath =
|
|
19167
|
+
const childPath = join30(absoluteSource, entryName);
|
|
18959
19168
|
if (!safeIsDirectory(childPath))
|
|
18960
19169
|
continue;
|
|
18961
19170
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -18984,19 +19193,19 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
18984
19193
|
};
|
|
18985
19194
|
}
|
|
18986
19195
|
function isSkillCandidate(dir) {
|
|
18987
|
-
return
|
|
19196
|
+
return existsSync14(join30(dir, "SKILL.md")) || existsSync14(join30(dir, "skill.json")) || existsSync14(join30(dir, "package.json"));
|
|
18988
19197
|
}
|
|
18989
19198
|
function physicalImportPath(path) {
|
|
18990
|
-
let ancestor =
|
|
19199
|
+
let ancestor = resolve18(path);
|
|
18991
19200
|
const suffix = [];
|
|
18992
|
-
while (!
|
|
19201
|
+
while (!existsSync14(ancestor)) {
|
|
18993
19202
|
suffix.unshift(basename11(ancestor));
|
|
18994
19203
|
ancestor = dirname19(ancestor);
|
|
18995
19204
|
}
|
|
18996
|
-
return
|
|
19205
|
+
return join30(realpathSync10(ancestor), ...suffix);
|
|
18997
19206
|
}
|
|
18998
19207
|
function assertDisjointImportPaths(source, destination) {
|
|
18999
|
-
const sourcePath =
|
|
19208
|
+
const sourcePath = realpathSync10(source);
|
|
19000
19209
|
const destinationPath = physicalImportPath(destination);
|
|
19001
19210
|
const contains2 = (parent, child) => {
|
|
19002
19211
|
const path = relative6(parent, child);
|
|
@@ -19008,7 +19217,7 @@ function assertDisjointImportPaths(source, destination) {
|
|
|
19008
19217
|
}
|
|
19009
19218
|
function portPortableSkill(sourcePath, options = {}) {
|
|
19010
19219
|
const absoluteSource = normalize4(sourcePath);
|
|
19011
|
-
if (!
|
|
19220
|
+
if (!existsSync14(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
|
|
19012
19221
|
throw new Error(`Skill source directory not found: ${sourcePath}`);
|
|
19013
19222
|
}
|
|
19014
19223
|
const inferred = readPortableSkillManifestForImport(absoluteSource);
|
|
@@ -19019,14 +19228,14 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
19019
19228
|
const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename11(absoluteSource)}')` : ""} matches a bundled official skill.`;
|
|
19020
19229
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
19021
19230
|
}
|
|
19022
|
-
const readOnlyDestination =
|
|
19231
|
+
const readOnlyDestination = join30(resolvePortableSkillsRoot(options, false), skillName);
|
|
19023
19232
|
assertPortableAuthoringPath(readOnlyDestination, options);
|
|
19024
19233
|
assertDisjointImportPaths(absoluteSource, readOnlyDestination);
|
|
19025
19234
|
const root2 = getPortableSkillsRoot(options);
|
|
19026
|
-
const destination =
|
|
19235
|
+
const destination = join30(root2, skillName);
|
|
19027
19236
|
assertPortableAuthoringPath(destination, options);
|
|
19028
19237
|
assertDisjointImportPaths(absoluteSource, destination);
|
|
19029
|
-
if (
|
|
19238
|
+
if (existsSync14(destination)) {
|
|
19030
19239
|
if (!options.overwrite)
|
|
19031
19240
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
19032
19241
|
rmSync4(destination, { recursive: true, force: true });
|
|
@@ -19068,13 +19277,13 @@ function buildCorpusManifest(input, name) {
|
|
|
19068
19277
|
function writeCorpusSkill(input, options = {}) {
|
|
19069
19278
|
const name = normalizePortableSkillName(input.name);
|
|
19070
19279
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
19071
|
-
assertPortableAuthoringPath(
|
|
19280
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, name), options);
|
|
19072
19281
|
const root2 = getPortableSkillsRoot(options);
|
|
19073
|
-
const skillPath =
|
|
19282
|
+
const skillPath = join30(root2, name);
|
|
19074
19283
|
assertPortableAuthoringPath(skillPath, options);
|
|
19075
|
-
const created = !
|
|
19284
|
+
const created = !existsSync14(skillPath);
|
|
19076
19285
|
mkdirSync9(skillPath, { recursive: true });
|
|
19077
|
-
writeFileSync8(
|
|
19286
|
+
writeFileSync8(join30(skillPath, "SKILL.md"), input.skillMd);
|
|
19078
19287
|
const manifest = buildCorpusManifest(input, name);
|
|
19079
19288
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
19080
19289
|
return { name, path: skillPath, manifest, created };
|
|
@@ -19082,22 +19291,22 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
19082
19291
|
function installCorpusSkillAtomically(input, options = {}) {
|
|
19083
19292
|
const name = normalizePortableSkillName(input.name);
|
|
19084
19293
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
19085
|
-
assertPortableAuthoringPath(
|
|
19294
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, name), options);
|
|
19086
19295
|
const root2 = getPortableSkillsRoot(options);
|
|
19087
|
-
const target =
|
|
19296
|
+
const target = join30(root2, name);
|
|
19088
19297
|
assertPortableAuthoringPath(target, options);
|
|
19089
|
-
const created = !
|
|
19298
|
+
const created = !existsSync14(target);
|
|
19090
19299
|
mkdirSync9(root2, { recursive: true });
|
|
19091
|
-
const staging = mkdtempSync2(
|
|
19300
|
+
const staging = mkdtempSync2(join30(root2, `.pull-${name}-`));
|
|
19092
19301
|
let moved = false;
|
|
19093
19302
|
let backup = null;
|
|
19094
19303
|
try {
|
|
19095
|
-
writeFileSync8(
|
|
19304
|
+
writeFileSync8(join30(staging, "SKILL.md"), input.skillMd);
|
|
19096
19305
|
const manifest = buildCorpusManifest(input, name);
|
|
19097
19306
|
writeSkillJsonWithHash(staging, manifest);
|
|
19098
|
-
if (
|
|
19099
|
-
backup = mkdtempSync2(
|
|
19100
|
-
renameSync5(target,
|
|
19307
|
+
if (existsSync14(target)) {
|
|
19308
|
+
backup = mkdtempSync2(join30(root2, `.pull-backup-${name}-`));
|
|
19309
|
+
renameSync5(target, join30(backup, name));
|
|
19101
19310
|
moved = true;
|
|
19102
19311
|
}
|
|
19103
19312
|
renameSync5(staging, target);
|
|
@@ -19106,9 +19315,9 @@ function installCorpusSkillAtomically(input, options = {}) {
|
|
|
19106
19315
|
return { name, path: target, manifest, created };
|
|
19107
19316
|
} catch (error) {
|
|
19108
19317
|
rmSync4(staging, { recursive: true, force: true });
|
|
19109
|
-
if (moved && backup &&
|
|
19318
|
+
if (moved && backup && existsSync14(join30(backup, name))) {
|
|
19110
19319
|
try {
|
|
19111
|
-
renameSync5(
|
|
19320
|
+
renameSync5(join30(backup, name), target);
|
|
19112
19321
|
} catch {}
|
|
19113
19322
|
}
|
|
19114
19323
|
throw error;
|
|
@@ -19120,10 +19329,10 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19120
19329
|
const issues = [...base.issues];
|
|
19121
19330
|
const warnings = [...base.warnings];
|
|
19122
19331
|
let manifest;
|
|
19123
|
-
if (
|
|
19124
|
-
const skillJsonPath =
|
|
19125
|
-
const skillMdPath =
|
|
19126
|
-
if (!
|
|
19332
|
+
if (existsSync14(skillPath)) {
|
|
19333
|
+
const skillJsonPath = join30(skillPath, "skill.json");
|
|
19334
|
+
const skillMdPath = join30(skillPath, "SKILL.md");
|
|
19335
|
+
if (!existsSync14(skillJsonPath) && !existsSync14(skillMdPath)) {
|
|
19127
19336
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
19128
19337
|
}
|
|
19129
19338
|
try {
|
|
@@ -19142,7 +19351,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19142
19351
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
19143
19352
|
}
|
|
19144
19353
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
19145
|
-
strict:
|
|
19354
|
+
strict: existsSync14(join30(skillPath, "skill.json")),
|
|
19146
19355
|
skillPath
|
|
19147
19356
|
});
|
|
19148
19357
|
for (const issue of contractIssues)
|
|
@@ -19182,8 +19391,8 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19182
19391
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
19183
19392
|
continue;
|
|
19184
19393
|
}
|
|
19185
|
-
const entryPath =
|
|
19186
|
-
if (!
|
|
19394
|
+
const entryPath = join30(skillPath, command.entry);
|
|
19395
|
+
if (!existsSync14(entryPath))
|
|
19187
19396
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
19188
19397
|
else if (statSync10(entryPath).isDirectory())
|
|
19189
19398
|
add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
|
|
@@ -19193,7 +19402,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19193
19402
|
} catch (error) {
|
|
19194
19403
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
19195
19404
|
}
|
|
19196
|
-
if (manifest?.kind !== "instruction" && !
|
|
19405
|
+
if (manifest?.kind !== "instruction" && !existsSync14(join30(skillPath, "AGENTS.md"))) {
|
|
19197
19406
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
19198
19407
|
}
|
|
19199
19408
|
}
|
|
@@ -19229,13 +19438,13 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
19229
19438
|
if (!isSafeRelativePath2(command.entry)) {
|
|
19230
19439
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
19231
19440
|
}
|
|
19232
|
-
const entryPath =
|
|
19233
|
-
if (!
|
|
19441
|
+
const entryPath = join30(skill.path, command.entry);
|
|
19442
|
+
if (!existsSync14(entryPath)) {
|
|
19234
19443
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
19235
19444
|
}
|
|
19236
|
-
const pkgPath =
|
|
19237
|
-
const nodeModules =
|
|
19238
|
-
if (
|
|
19445
|
+
const pkgPath = join30(skill.path, "package.json");
|
|
19446
|
+
const nodeModules = join30(skill.path, "node_modules");
|
|
19447
|
+
if (existsSync14(pkgPath) && !existsSync14(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
19239
19448
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
19240
19449
|
cwd: skill.path,
|
|
19241
19450
|
stdout: "pipe",
|
|
@@ -19483,7 +19692,7 @@ function parseSkillMdFrontmatter(content) {
|
|
|
19483
19692
|
return Object.keys(result).length > 0 ? result : null;
|
|
19484
19693
|
}
|
|
19485
19694
|
function discoverSkillsInDir(dir, source = "custom") {
|
|
19486
|
-
if (!
|
|
19695
|
+
if (!existsSync15(dir))
|
|
19487
19696
|
return [];
|
|
19488
19697
|
const result = [];
|
|
19489
19698
|
try {
|
|
@@ -19491,8 +19700,8 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19491
19700
|
for (const entry of entries) {
|
|
19492
19701
|
if (!entry.isDirectory())
|
|
19493
19702
|
continue;
|
|
19494
|
-
const skillMdPath =
|
|
19495
|
-
if (!
|
|
19703
|
+
const skillMdPath = join31(dir, entry.name, "SKILL.md");
|
|
19704
|
+
if (!existsSync15(skillMdPath))
|
|
19496
19705
|
continue;
|
|
19497
19706
|
let content;
|
|
19498
19707
|
try {
|
|
@@ -19511,7 +19720,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19511
19720
|
category: fm.category || "Development Tools",
|
|
19512
19721
|
tags: fm.tags || [],
|
|
19513
19722
|
...fm.kind ? { kind: fm.kind } : {},
|
|
19514
|
-
...isHostedMetadataSkillDir(
|
|
19723
|
+
...isHostedMetadataSkillDir(join31(dir, entry.name)) ? { serverOwned: true } : {},
|
|
19515
19724
|
source
|
|
19516
19725
|
});
|
|
19517
19726
|
}
|
|
@@ -19520,16 +19729,16 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19520
19729
|
}
|
|
19521
19730
|
function findExtensionSkillPath(name) {
|
|
19522
19731
|
const config = loadConfig();
|
|
19523
|
-
if (!config.extensionsDir || !
|
|
19732
|
+
if (!config.extensionsDir || !existsSync15(config.extensionsDir))
|
|
19524
19733
|
return null;
|
|
19525
19734
|
try {
|
|
19526
19735
|
const entries = readdirSync12(config.extensionsDir, { withFileTypes: true });
|
|
19527
19736
|
for (const entry of entries) {
|
|
19528
19737
|
if (!entry.isDirectory())
|
|
19529
19738
|
continue;
|
|
19530
|
-
const skillDir =
|
|
19531
|
-
const skillMdPath =
|
|
19532
|
-
if (!
|
|
19739
|
+
const skillDir = join31(config.extensionsDir, entry.name);
|
|
19740
|
+
const skillMdPath = join31(skillDir, "SKILL.md");
|
|
19741
|
+
if (!existsSync15(skillMdPath))
|
|
19533
19742
|
continue;
|
|
19534
19743
|
let content;
|
|
19535
19744
|
try {
|
|
@@ -19576,21 +19785,21 @@ function clearRegistryCache() {
|
|
|
19576
19785
|
registryCacheTime = 0;
|
|
19577
19786
|
registryCacheKey = null;
|
|
19578
19787
|
}
|
|
19579
|
-
function getSkillsByCategory(category) {
|
|
19580
|
-
return loadRegistry().filter((s) => s.category === category);
|
|
19788
|
+
function getSkillsByCategory(category, catalog) {
|
|
19789
|
+
return (catalog ?? loadRegistry()).filter((s) => s.category === category);
|
|
19581
19790
|
}
|
|
19582
19791
|
function getSkill(name) {
|
|
19583
19792
|
const registry = loadRegistry();
|
|
19584
19793
|
const slug = normalizeSkillSlug(name);
|
|
19585
19794
|
return registry.find((s) => s.name === slug) ?? registry.find((s) => s.name === resolveSkillAlias(slug));
|
|
19586
19795
|
}
|
|
19587
|
-
function getSkillsByTag(tag) {
|
|
19796
|
+
function getSkillsByTag(tag, catalog) {
|
|
19588
19797
|
const needle = tag.toLowerCase();
|
|
19589
|
-
return loadRegistry().filter((s) => s.tags.some((t2) => t2.toLowerCase().includes(needle)));
|
|
19798
|
+
return (catalog ?? loadRegistry()).filter((s) => s.tags.some((t2) => t2.toLowerCase().includes(needle)));
|
|
19590
19799
|
}
|
|
19591
|
-
function getAllTags() {
|
|
19800
|
+
function getAllTags(catalog) {
|
|
19592
19801
|
const tagSet = new Set;
|
|
19593
|
-
for (const skill of loadRegistry()) {
|
|
19802
|
+
for (const skill of catalog ?? loadRegistry()) {
|
|
19594
19803
|
for (const tag of skill.tags)
|
|
19595
19804
|
tagSet.add(tag.toLowerCase());
|
|
19596
19805
|
}
|
|
@@ -19598,20 +19807,20 @@ function getAllTags() {
|
|
|
19598
19807
|
}
|
|
19599
19808
|
// src/lib/run-state.ts
|
|
19600
19809
|
import { createHash as createHash19, randomBytes } from "crypto";
|
|
19601
|
-
import { existsSync as
|
|
19602
|
-
import { extname, join as
|
|
19810
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync19, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
19811
|
+
import { extname, join as join32, relative as relative7 } from "path";
|
|
19603
19812
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
19604
19813
|
const now = new Date;
|
|
19605
19814
|
const id = createRunId(now);
|
|
19606
19815
|
const day = now.toISOString().slice(0, 10);
|
|
19607
19816
|
const skillName = normalizeSkillName(params.skill);
|
|
19608
19817
|
const root2 = getProjectStateDir(targetDir);
|
|
19609
|
-
const runDir =
|
|
19610
|
-
const logsDir =
|
|
19611
|
-
const exportDir =
|
|
19818
|
+
const runDir = join32(root2, "runs", day, id);
|
|
19819
|
+
const logsDir = join32(runDir, "logs");
|
|
19820
|
+
const exportDir = join32(root2, "exports", skillName, id);
|
|
19612
19821
|
mkdirSync10(logsDir, { recursive: true });
|
|
19613
19822
|
mkdirSync10(exportDir, { recursive: true });
|
|
19614
|
-
mkdirSync10(
|
|
19823
|
+
mkdirSync10(join32(root2, "tmp"), { recursive: true });
|
|
19615
19824
|
const record3 = {
|
|
19616
19825
|
id,
|
|
19617
19826
|
skill: skillName,
|
|
@@ -19663,27 +19872,27 @@ function updateSkillRun(context, patch) {
|
|
|
19663
19872
|
return context.record;
|
|
19664
19873
|
}
|
|
19665
19874
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
19666
|
-
writeFileSync9(
|
|
19667
|
-
writeFileSync9(
|
|
19875
|
+
writeFileSync9(join32(context.logsDir, "stdout.log"), stdout);
|
|
19876
|
+
writeFileSync9(join32(context.logsDir, "stderr.log"), stderr);
|
|
19668
19877
|
}
|
|
19669
19878
|
function appendRunEvent(context, event, data = {}) {
|
|
19670
19879
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
19671
19880
|
`;
|
|
19672
|
-
const path =
|
|
19673
|
-
const previous =
|
|
19881
|
+
const path = join32(context.runDir, "events.ndjson");
|
|
19882
|
+
const previous = existsSync16(path) ? readFileSync19(path, "utf-8") : "";
|
|
19674
19883
|
writeFileSync9(path, previous + line);
|
|
19675
19884
|
}
|
|
19676
19885
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
19677
|
-
const runsRoot =
|
|
19678
|
-
if (!
|
|
19886
|
+
const runsRoot = join32(getProjectStateDir(targetDir), "runs");
|
|
19887
|
+
if (!existsSync16(runsRoot))
|
|
19679
19888
|
return [];
|
|
19680
19889
|
const records = [];
|
|
19681
19890
|
for (const day of readdirSync13(runsRoot).sort().reverse()) {
|
|
19682
|
-
const dayDir =
|
|
19891
|
+
const dayDir = join32(runsRoot, day);
|
|
19683
19892
|
if (!statSync11(dayDir).isDirectory())
|
|
19684
19893
|
continue;
|
|
19685
19894
|
for (const runId of readdirSync13(dayDir).sort().reverse()) {
|
|
19686
|
-
const record3 = readRunRecord(
|
|
19895
|
+
const record3 = readRunRecord(join32(dayDir, runId));
|
|
19687
19896
|
if (record3)
|
|
19688
19897
|
records.push(record3);
|
|
19689
19898
|
if (records.length >= limit)
|
|
@@ -19693,29 +19902,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
19693
19902
|
return records;
|
|
19694
19903
|
}
|
|
19695
19904
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
19696
|
-
const runsRoot =
|
|
19697
|
-
if (!
|
|
19905
|
+
const runsRoot = join32(getProjectStateDir(targetDir), "runs");
|
|
19906
|
+
if (!existsSync16(runsRoot))
|
|
19698
19907
|
return null;
|
|
19699
19908
|
for (const day of readdirSync13(runsRoot)) {
|
|
19700
|
-
const record3 = readRunRecord(
|
|
19909
|
+
const record3 = readRunRecord(join32(runsRoot, day, runId));
|
|
19701
19910
|
if (record3)
|
|
19702
19911
|
return record3;
|
|
19703
19912
|
}
|
|
19704
19913
|
return null;
|
|
19705
19914
|
}
|
|
19706
19915
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
19707
|
-
return
|
|
19916
|
+
return join32(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
19708
19917
|
}
|
|
19709
19918
|
function writeRunRecord(context) {
|
|
19710
|
-
writeFileSync9(
|
|
19919
|
+
writeFileSync9(join32(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
19711
19920
|
`);
|
|
19712
19921
|
}
|
|
19713
19922
|
function writeArtifactsManifest(context, artifacts) {
|
|
19714
|
-
writeFileSync9(
|
|
19923
|
+
writeFileSync9(join32(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
19715
19924
|
`);
|
|
19716
19925
|
}
|
|
19717
19926
|
function collectRunArtifacts(context) {
|
|
19718
|
-
if (!
|
|
19927
|
+
if (!existsSync16(context.exportDir))
|
|
19719
19928
|
return [];
|
|
19720
19929
|
const artifacts = [];
|
|
19721
19930
|
for (const path of walkFiles(context.exportDir)) {
|
|
@@ -19731,8 +19940,8 @@ function collectRunArtifacts(context) {
|
|
|
19731
19940
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
19732
19941
|
}
|
|
19733
19942
|
function readRunRecord(runDir) {
|
|
19734
|
-
const path =
|
|
19735
|
-
if (!
|
|
19943
|
+
const path = join32(runDir, "run.json");
|
|
19944
|
+
if (!existsSync16(path))
|
|
19736
19945
|
return null;
|
|
19737
19946
|
try {
|
|
19738
19947
|
return JSON.parse(readFileSync19(path, "utf-8"));
|
|
@@ -19743,7 +19952,7 @@ function readRunRecord(runDir) {
|
|
|
19743
19952
|
function walkFiles(dir) {
|
|
19744
19953
|
const files = [];
|
|
19745
19954
|
for (const entry of readdirSync13(dir)) {
|
|
19746
|
-
const full =
|
|
19955
|
+
const full = join32(dir, entry);
|
|
19747
19956
|
if (statSync11(full).isDirectory())
|
|
19748
19957
|
files.push(...walkFiles(full));
|
|
19749
19958
|
else
|
|
@@ -40148,10 +40357,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
40148
40357
|
let timer;
|
|
40149
40358
|
let deadlineExceeded = false;
|
|
40150
40359
|
const expired = Symbol("quote body deadline");
|
|
40151
|
-
const deadline = new Promise((
|
|
40360
|
+
const deadline = new Promise((resolve19) => {
|
|
40152
40361
|
timer = setTimeout(() => {
|
|
40153
40362
|
deadlineExceeded = true;
|
|
40154
|
-
|
|
40363
|
+
resolve19(expired);
|
|
40155
40364
|
reader.cancel().catch(() => {});
|
|
40156
40365
|
}, 1500);
|
|
40157
40366
|
});
|
|
@@ -41250,14 +41459,14 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
41250
41459
|
return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
|
|
41251
41460
|
}
|
|
41252
41461
|
// src/lib/scheduler.ts
|
|
41253
|
-
import { existsSync as
|
|
41254
|
-
import { join as
|
|
41462
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "fs";
|
|
41463
|
+
import { join as join33 } from "path";
|
|
41255
41464
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
41256
|
-
return
|
|
41465
|
+
return join33(targetDir, ".skills", "schedules.json");
|
|
41257
41466
|
}
|
|
41258
41467
|
function loadSchedules(targetDir = process.cwd()) {
|
|
41259
41468
|
const path = getSchedulesPath(targetDir);
|
|
41260
|
-
if (
|
|
41469
|
+
if (existsSync17(path)) {
|
|
41261
41470
|
try {
|
|
41262
41471
|
return JSON.parse(readFileSync20(path, "utf-8"));
|
|
41263
41472
|
} catch {}
|
|
@@ -41266,8 +41475,8 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
41266
41475
|
}
|
|
41267
41476
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
41268
41477
|
const path = getSchedulesPath(targetDir);
|
|
41269
|
-
const dir =
|
|
41270
|
-
if (!
|
|
41478
|
+
const dir = join33(targetDir, ".skills");
|
|
41479
|
+
if (!existsSync17(dir))
|
|
41271
41480
|
mkdirSync11(dir, { recursive: true });
|
|
41272
41481
|
writeFileSync10(path, JSON.stringify(data, null, 2));
|
|
41273
41482
|
}
|
|
@@ -41482,8 +41691,8 @@ function revisionIdOfRecord(record8) {
|
|
|
41482
41691
|
return revisionIdOf(record8);
|
|
41483
41692
|
}
|
|
41484
41693
|
// src/lib/pull.ts
|
|
41485
|
-
import { existsSync as
|
|
41486
|
-
import { dirname as dirname20, join as
|
|
41694
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync21, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
|
|
41695
|
+
import { dirname as dirname20, join as join34 } from "path";
|
|
41487
41696
|
|
|
41488
41697
|
// src/lib/skill-bundles.ts
|
|
41489
41698
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -41577,7 +41786,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
41577
41786
|
return reconcileTombstone(slug2, corpusOptions);
|
|
41578
41787
|
}
|
|
41579
41788
|
if (bundleResponse.status === 404) {
|
|
41580
|
-
const marker = readPullMarker(
|
|
41789
|
+
const marker = readPullMarker(join34(getPortableSkillsRoot(corpusOptions), slug2));
|
|
41581
41790
|
if (isPublishedInstallMarker(marker)) {
|
|
41582
41791
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
41583
41792
|
}
|
|
@@ -41616,7 +41825,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
41616
41825
|
return { name: slug2, success: false, error: `Skill '${slug2}' was not found on the configured Skills instance.` };
|
|
41617
41826
|
}
|
|
41618
41827
|
if (!meta3?.revisionId) {
|
|
41619
|
-
const marker = readPullMarker(
|
|
41828
|
+
const marker = readPullMarker(join34(getPortableSkillsRoot(corpusOptions), slug2));
|
|
41620
41829
|
if (isPublishedInstallMarker(marker)) {
|
|
41621
41830
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
41622
41831
|
}
|
|
@@ -41673,7 +41882,7 @@ function provenRevision(meta3, slug2, bundle2) {
|
|
|
41673
41882
|
return declared;
|
|
41674
41883
|
}
|
|
41675
41884
|
function reconcileTombstone(slug2, corpusOptions) {
|
|
41676
|
-
const target =
|
|
41885
|
+
const target = join34(getPortableSkillsRoot(corpusOptions), slug2);
|
|
41677
41886
|
if (!hasSkillsOwnershipMarker(target)) {
|
|
41678
41887
|
return { name: slug2, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
41679
41888
|
}
|
|
@@ -41682,7 +41891,7 @@ function reconcileTombstone(slug2, corpusOptions) {
|
|
|
41682
41891
|
}
|
|
41683
41892
|
function readPullMarker(dir) {
|
|
41684
41893
|
try {
|
|
41685
|
-
return JSON.parse(readFileSync21(
|
|
41894
|
+
return JSON.parse(readFileSync21(join34(dir, PULL_MARKER_FILE), "utf-8"));
|
|
41686
41895
|
} catch {
|
|
41687
41896
|
return null;
|
|
41688
41897
|
}
|
|
@@ -41817,14 +42026,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
41817
42026
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
41818
42027
|
const root2 = getPortableSkillsRoot(options);
|
|
41819
42028
|
mkdirSync12(root2, { recursive: true });
|
|
41820
|
-
const target =
|
|
41821
|
-
const created = !
|
|
41822
|
-
const staging = mkdtempSync3(
|
|
42029
|
+
const target = join34(root2, name);
|
|
42030
|
+
const created = !existsSync18(target);
|
|
42031
|
+
const staging = mkdtempSync3(join34(root2, `.pull-${name}-`));
|
|
41823
42032
|
let moved = false;
|
|
41824
42033
|
let backup = null;
|
|
41825
42034
|
try {
|
|
41826
42035
|
for (const entry of entries) {
|
|
41827
|
-
const destination =
|
|
42036
|
+
const destination = join34(staging, entry.path);
|
|
41828
42037
|
mkdirSync12(dirname20(destination), { recursive: true });
|
|
41829
42038
|
writeFileSync11(destination, entry.bytes, { mode: entry.mode });
|
|
41830
42039
|
}
|
|
@@ -41836,9 +42045,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
41836
42045
|
...marker.signature ? { signature: marker.signature } : {},
|
|
41837
42046
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
41838
42047
|
});
|
|
41839
|
-
if (
|
|
41840
|
-
backup = mkdtempSync3(
|
|
41841
|
-
renameSync6(target,
|
|
42048
|
+
if (existsSync18(target)) {
|
|
42049
|
+
backup = mkdtempSync3(join34(root2, `.pull-backup-${name}-`));
|
|
42050
|
+
renameSync6(target, join34(backup, name));
|
|
41842
42051
|
moved = true;
|
|
41843
42052
|
}
|
|
41844
42053
|
renameSync6(staging, target);
|
|
@@ -41846,9 +42055,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
41846
42055
|
rmSync5(backup, { recursive: true, force: true });
|
|
41847
42056
|
} catch (error61) {
|
|
41848
42057
|
rmSync5(staging, { recursive: true, force: true });
|
|
41849
|
-
if (moved && backup &&
|
|
42058
|
+
if (moved && backup && existsSync18(join34(backup, name))) {
|
|
41850
42059
|
try {
|
|
41851
|
-
renameSync6(
|
|
42060
|
+
renameSync6(join34(backup, name), target);
|
|
41852
42061
|
} catch {}
|
|
41853
42062
|
}
|
|
41854
42063
|
throw error61;
|
|
@@ -41867,7 +42076,7 @@ function writePullMarker(dir, record8) {
|
|
|
41867
42076
|
...record8.revisionId ? { revisionId: record8.revisionId } : {},
|
|
41868
42077
|
syncedAt: new Date().toISOString()
|
|
41869
42078
|
};
|
|
41870
|
-
writeFileSync11(
|
|
42079
|
+
writeFileSync11(join34(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
41871
42080
|
`);
|
|
41872
42081
|
}
|
|
41873
42082
|
async function safeMeta(client2, slug2) {
|
|
@@ -42309,12 +42518,12 @@ class RemotePrivatePublicationsClient {
|
|
|
42309
42518
|
previous = view;
|
|
42310
42519
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
42311
42520
|
return view;
|
|
42312
|
-
await new Promise((
|
|
42521
|
+
await new Promise((resolve19) => {
|
|
42313
42522
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
42314
42523
|
function done() {
|
|
42315
42524
|
clearTimeout(timer);
|
|
42316
42525
|
options.signal?.removeEventListener("abort", done);
|
|
42317
|
-
|
|
42526
|
+
resolve19();
|
|
42318
42527
|
}
|
|
42319
42528
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
42320
42529
|
});
|
|
@@ -43870,16 +44079,16 @@ function clone3(value) {
|
|
|
43870
44079
|
return JSON.parse(JSON.stringify(value));
|
|
43871
44080
|
}
|
|
43872
44081
|
// src/lib/feedback.ts
|
|
43873
|
-
import { appendFileSync, existsSync as
|
|
43874
|
-
import { dirname as dirname22, join as
|
|
44082
|
+
import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync14 } from "fs";
|
|
44083
|
+
import { dirname as dirname22, join as join35 } from "path";
|
|
43875
44084
|
import { Database } from "bun:sqlite";
|
|
43876
44085
|
function getFeedbackDbPath() {
|
|
43877
|
-
return
|
|
44086
|
+
return join35(getDataDir(), "skills.db");
|
|
43878
44087
|
}
|
|
43879
44088
|
function getFeedbackDb() {
|
|
43880
44089
|
const dbPath = getFeedbackDbPath();
|
|
43881
44090
|
const dir = dirname22(dbPath);
|
|
43882
|
-
if (!
|
|
44091
|
+
if (!existsSync19(dir))
|
|
43883
44092
|
mkdirSync14(dir, { recursive: true });
|
|
43884
44093
|
const db = new Database(dbPath);
|
|
43885
44094
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -43906,9 +44115,9 @@ function saveFeedback(input2) {
|
|
|
43906
44115
|
throw new Error("Feedback message is required");
|
|
43907
44116
|
const category = input2.category ?? "general";
|
|
43908
44117
|
if (isApiMode()) {
|
|
43909
|
-
const path =
|
|
44118
|
+
const path = join35(getDataDir(), "feedback.jsonl");
|
|
43910
44119
|
const dir = dirname22(path);
|
|
43911
|
-
if (!
|
|
44120
|
+
if (!existsSync19(dir))
|
|
43912
44121
|
mkdirSync14(dir, { recursive: true });
|
|
43913
44122
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
43914
44123
|
`);
|
|
@@ -43932,14 +44141,14 @@ function isApiMode(env = process.env) {
|
|
|
43932
44141
|
// src/lib/native-storage.ts
|
|
43933
44142
|
import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
|
|
43934
44143
|
import {
|
|
43935
|
-
existsSync as
|
|
44144
|
+
existsSync as existsSync20,
|
|
43936
44145
|
mkdirSync as mkdirSync15,
|
|
43937
44146
|
readFileSync as readFileSync23,
|
|
43938
44147
|
readdirSync as readdirSync14,
|
|
43939
44148
|
statSync as statSync12,
|
|
43940
44149
|
writeFileSync as writeFileSync13
|
|
43941
44150
|
} from "fs";
|
|
43942
|
-
import { dirname as dirname23, join as
|
|
44151
|
+
import { dirname as dirname23, join as join36, normalize as normalize5, relative as relative9, sep as sep9 } from "path";
|
|
43943
44152
|
var SKILLS_STORAGE_TABLES = [
|
|
43944
44153
|
"skills_sync_records",
|
|
43945
44154
|
"skills_sync_cursors"
|
|
@@ -44076,7 +44285,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
44076
44285
|
local: {
|
|
44077
44286
|
dataDir: getDataDir(),
|
|
44078
44287
|
projectStateDir: getProjectStateDir(targetDir),
|
|
44079
|
-
feedbackDbPath:
|
|
44288
|
+
feedbackDbPath: join36(getDataDir(), "skills.db")
|
|
44080
44289
|
},
|
|
44081
44290
|
remote: {
|
|
44082
44291
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -44099,7 +44308,7 @@ function getStorageStatus(options = {}) {
|
|
|
44099
44308
|
function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
44100
44309
|
const projectStateDir = getProjectStateDir(targetDir);
|
|
44101
44310
|
const files = [];
|
|
44102
|
-
if (
|
|
44311
|
+
if (existsSync20(projectStateDir)) {
|
|
44103
44312
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
44104
44313
|
const bytes = readFileSync23(filePath);
|
|
44105
44314
|
const relativePath = toPosix(relative9(targetDir, filePath));
|
|
@@ -44126,7 +44335,7 @@ function importSkillsLocalSnapshot(snapshot2, targetDir = process.cwd(), options
|
|
|
44126
44335
|
continue;
|
|
44127
44336
|
}
|
|
44128
44337
|
const absolutePath4 = resolveSnapshotPath(targetDir, file2.path);
|
|
44129
|
-
if (
|
|
44338
|
+
if (existsSync20(absolutePath4) && !options.overwrite) {
|
|
44130
44339
|
skipped += 1;
|
|
44131
44340
|
continue;
|
|
44132
44341
|
}
|
|
@@ -44433,7 +44642,7 @@ function parsePositiveInteger(value) {
|
|
|
44433
44642
|
function walkFiles2(dir) {
|
|
44434
44643
|
const files = [];
|
|
44435
44644
|
for (const entry of readdirSync14(dir)) {
|
|
44436
|
-
const full =
|
|
44645
|
+
const full = join36(dir, entry);
|
|
44437
44646
|
const stats = statSync12(full);
|
|
44438
44647
|
if (stats.isDirectory())
|
|
44439
44648
|
files.push(...walkFiles2(full));
|
|
@@ -44450,7 +44659,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
44450
44659
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
44451
44660
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
44452
44661
|
}
|
|
44453
|
-
return
|
|
44662
|
+
return join36(targetDir, normalizedPath);
|
|
44454
44663
|
}
|
|
44455
44664
|
function normalizeS3Prefix(prefix) {
|
|
44456
44665
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -44523,12 +44732,12 @@ import {
|
|
|
44523
44732
|
statSync as statSync14,
|
|
44524
44733
|
writeFileSync as writeFileSync14
|
|
44525
44734
|
} from "fs";
|
|
44526
|
-
import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as
|
|
44735
|
+
import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as resolve19, sep as sep11 } from "path";
|
|
44527
44736
|
|
|
44528
44737
|
// src/lib/portable-snapshot-filter.ts
|
|
44529
44738
|
import { readdirSync as readdirSync15, statSync as statSync13 } from "fs";
|
|
44530
44739
|
import { homedir as homedir7 } from "os";
|
|
44531
|
-
import { join as
|
|
44740
|
+
import { join as join37, sep as sep10 } from "path";
|
|
44532
44741
|
var SYNC_HOMES = [
|
|
44533
44742
|
{ name: "skills", subClass: "skills", agent: null },
|
|
44534
44743
|
{ name: "custom", subClass: "custom", agent: null },
|
|
@@ -44621,16 +44830,16 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
44621
44830
|
function homePathFor(definition, homesRoot) {
|
|
44622
44831
|
const home = homesRoot ?? homedir7();
|
|
44623
44832
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
44624
|
-
return
|
|
44833
|
+
return join37(skillsDataRootForHome(home), definition.name);
|
|
44625
44834
|
}
|
|
44626
44835
|
if (definition.agent === "opencode") {
|
|
44627
|
-
return
|
|
44836
|
+
return join37(home, ".config", "opencode", "skills");
|
|
44628
44837
|
}
|
|
44629
|
-
return
|
|
44838
|
+
return join37(home, `.${definition.agent}`, "skills");
|
|
44630
44839
|
}
|
|
44631
44840
|
function destinationFor(definition, stationId, relativePath) {
|
|
44632
|
-
const category = definition.subClass === "agent-homes" ?
|
|
44633
|
-
return
|
|
44841
|
+
const category = definition.subClass === "agent-homes" ? join37("agent-homes", definition.agent ?? "") : definition.name;
|
|
44842
|
+
return join37("resources", stationId, "skills", category, ...relativePath.split(sep10));
|
|
44634
44843
|
}
|
|
44635
44844
|
function walkEntries(absoluteRoot) {
|
|
44636
44845
|
let entries;
|
|
@@ -44641,7 +44850,7 @@ function walkEntries(absoluteRoot) {
|
|
|
44641
44850
|
}
|
|
44642
44851
|
const output2 = [];
|
|
44643
44852
|
for (const entry of entries) {
|
|
44644
|
-
const childFull =
|
|
44853
|
+
const childFull = join37(absoluteRoot, entry.name);
|
|
44645
44854
|
if (entry.isSymbolicLink()) {
|
|
44646
44855
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
44647
44856
|
continue;
|
|
@@ -44652,7 +44861,7 @@ function walkEntries(absoluteRoot) {
|
|
|
44652
44861
|
}
|
|
44653
44862
|
const nested = walkEntries(childFull);
|
|
44654
44863
|
for (const item of nested) {
|
|
44655
|
-
output2.push({ ...item, relativePath:
|
|
44864
|
+
output2.push({ ...item, relativePath: join37(entry.name, item.relativePath) });
|
|
44656
44865
|
}
|
|
44657
44866
|
continue;
|
|
44658
44867
|
}
|
|
@@ -44761,7 +44970,7 @@ function humanHomes(scanned) {
|
|
|
44761
44970
|
}));
|
|
44762
44971
|
}
|
|
44763
44972
|
function writeStationSnapshot(options) {
|
|
44764
|
-
const repoRoot =
|
|
44973
|
+
const repoRoot = resolve19(options.repoRoot ?? process.cwd());
|
|
44765
44974
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
44766
44975
|
const manifestFiles = plans.map((plan) => ({
|
|
44767
44976
|
relativePath: plan.source.relativePath,
|
|
@@ -44787,7 +44996,7 @@ function writeStationSnapshot(options) {
|
|
|
44787
44996
|
const conflicts = [];
|
|
44788
44997
|
const untouched = [];
|
|
44789
44998
|
for (const plan of plans) {
|
|
44790
|
-
const destination =
|
|
44999
|
+
const destination = resolve19(repoRoot, plan.destination);
|
|
44791
45000
|
const destinationRelative = relative10(repoRoot, destination);
|
|
44792
45001
|
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute19(destinationRelative)) {
|
|
44793
45002
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
@@ -44810,7 +45019,7 @@ function writeStationSnapshot(options) {
|
|
|
44810
45019
|
}
|
|
44811
45020
|
let written = 0;
|
|
44812
45021
|
for (const plan of untouched) {
|
|
44813
|
-
const destination =
|
|
45022
|
+
const destination = resolve19(repoRoot, plan.destination);
|
|
44814
45023
|
mkdirSync16(dirname24(destination), { recursive: true });
|
|
44815
45024
|
copyFileSync(plan.source.fullPath, destination);
|
|
44816
45025
|
written += 1;
|
|
@@ -44829,7 +45038,7 @@ function writeStationSnapshot(options) {
|
|
|
44829
45038
|
},
|
|
44830
45039
|
files: manifestFiles
|
|
44831
45040
|
};
|
|
44832
|
-
const manifestPath =
|
|
45041
|
+
const manifestPath = resolve19(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
44833
45042
|
mkdirSync16(dirname24(manifestPath), { recursive: true });
|
|
44834
45043
|
writeFileSync14(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
44835
45044
|
`);
|
|
@@ -44850,7 +45059,7 @@ import {
|
|
|
44850
45059
|
statSync as statSync15,
|
|
44851
45060
|
writeFileSync as writeFileSync15
|
|
44852
45061
|
} from "fs";
|
|
44853
|
-
import { dirname as dirname25, join as
|
|
45062
|
+
import { dirname as dirname25, join as join38, resolve as resolve20, sep as sep12 } from "path";
|
|
44854
45063
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
44855
45064
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
44856
45065
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -44858,11 +45067,11 @@ function fail(code2, message, detail = []) {
|
|
|
44858
45067
|
throw new StationSnapshotError(code2, message, detail);
|
|
44859
45068
|
}
|
|
44860
45069
|
function snapshotRootFor(repoRoot, stationId) {
|
|
44861
|
-
return
|
|
45070
|
+
return join38(repoRoot, "resources", stationId, "skills");
|
|
44862
45071
|
}
|
|
44863
45072
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
44864
45073
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
44865
|
-
const manifestPath =
|
|
45074
|
+
const manifestPath = join38(snapshotRoot, "sync-manifest.json");
|
|
44866
45075
|
let manifest;
|
|
44867
45076
|
try {
|
|
44868
45077
|
manifest = JSON.parse(readFileSync26(manifestPath, "utf8"));
|
|
@@ -44889,7 +45098,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
44889
45098
|
const hashMismatches = [];
|
|
44890
45099
|
const skippedByRule = [];
|
|
44891
45100
|
for (const agent of SYNC_AGENTS) {
|
|
44892
|
-
const agentRoot =
|
|
45101
|
+
const agentRoot = join38(snapshotRoot, "agent-homes", agent);
|
|
44893
45102
|
let identEntries;
|
|
44894
45103
|
try {
|
|
44895
45104
|
identEntries = readdirSync16(agentRoot, { withFileTypes: true });
|
|
@@ -44900,7 +45109,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
44900
45109
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
44901
45110
|
continue;
|
|
44902
45111
|
}
|
|
44903
|
-
const identRoot =
|
|
45112
|
+
const identRoot = join38(agentRoot, identEntry.name);
|
|
44904
45113
|
const entries = walkEntries(identRoot);
|
|
44905
45114
|
for (const entry of entries) {
|
|
44906
45115
|
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep12)];
|
|
@@ -45051,8 +45260,8 @@ function skillSha256(skill) {
|
|
|
45051
45260
|
`)).digest("hex");
|
|
45052
45261
|
}
|
|
45053
45262
|
function writeStationHydration(options) {
|
|
45054
|
-
const repoRoot =
|
|
45055
|
-
const cacheRoot =
|
|
45263
|
+
const repoRoot = resolve20(options.repoRoot ?? process.cwd());
|
|
45264
|
+
const cacheRoot = resolve20(options.cacheRoot ?? resolveCorpusRoot());
|
|
45056
45265
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
45057
45266
|
const resultSkills = plan.winners.map((skill) => ({
|
|
45058
45267
|
ident: skill.ident,
|
|
@@ -45085,7 +45294,7 @@ function writeStationHydration(options) {
|
|
|
45085
45294
|
const toWrite = [];
|
|
45086
45295
|
for (const skill of plan.winners) {
|
|
45087
45296
|
for (const file2 of skill.files) {
|
|
45088
|
-
const destination =
|
|
45297
|
+
const destination = join38(cacheRoot, skill.ident, file2.withinIdent);
|
|
45089
45298
|
const digest3 = sha256File(file2.winner.fullPath);
|
|
45090
45299
|
let existingDigest = null;
|
|
45091
45300
|
try {
|
|
@@ -45127,7 +45336,7 @@ function writeStationHydration(options) {
|
|
|
45127
45336
|
},
|
|
45128
45337
|
skills: resultSkills
|
|
45129
45338
|
};
|
|
45130
|
-
const hydrationManifestPath =
|
|
45339
|
+
const hydrationManifestPath = join38(dirname25(cacheRoot), `hydration-${options.stationId}.json`);
|
|
45131
45340
|
mkdirSync17(dirname25(hydrationManifestPath), { recursive: true });
|
|
45132
45341
|
writeFileSync15(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
45133
45342
|
`);
|
|
@@ -45139,23 +45348,23 @@ function writeStationHydration(options) {
|
|
|
45139
45348
|
};
|
|
45140
45349
|
}
|
|
45141
45350
|
// src/lib/private-publication-recovery.ts
|
|
45142
|
-
import { constants as constants12, closeSync as closeSync13, fsyncSync as fsyncSync5, fstatSync as fstatSync13, lstatSync as
|
|
45143
|
-
import { dirname as dirname26, isAbsolute as isAbsolute20, join as
|
|
45351
|
+
import { constants as constants12, closeSync as closeSync13, fsyncSync as fsyncSync5, fstatSync as fstatSync13, lstatSync as lstatSync24, mkdirSync as mkdirSync18, openSync as openSync13, readSync as readSync11, realpathSync as realpathSync11, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
45352
|
+
import { dirname as dirname26, isAbsolute as isAbsolute20, join as join39, resolve as resolve21 } from "path";
|
|
45144
45353
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
45145
45354
|
var fail2 = () => {
|
|
45146
45355
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
45147
45356
|
};
|
|
45148
45357
|
function safeDirectory(directory2) {
|
|
45149
|
-
if (!isAbsolute20(directory2) || directory2 !==
|
|
45358
|
+
if (!isAbsolute20(directory2) || directory2 !== resolve21(directory2) || realpathSync11(directory2) !== directory2)
|
|
45150
45359
|
return fail2();
|
|
45151
45360
|
for (let path = directory2;; path = dirname26(path)) {
|
|
45152
|
-
const stat2 =
|
|
45361
|
+
const stat2 = lstatSync24(path);
|
|
45153
45362
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
45154
45363
|
return fail2();
|
|
45155
45364
|
if (path === dirname26(path))
|
|
45156
45365
|
break;
|
|
45157
45366
|
}
|
|
45158
|
-
const own2 =
|
|
45367
|
+
const own2 = lstatSync24(directory2);
|
|
45159
45368
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
45160
45369
|
return fail2();
|
|
45161
45370
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -45166,7 +45375,7 @@ function unchangedDirectory(directory2, identity4) {
|
|
|
45166
45375
|
return fail2();
|
|
45167
45376
|
}
|
|
45168
45377
|
function readOwned(directory2, name, max) {
|
|
45169
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
45378
|
+
const identity4 = safeDirectory(directory2), file2 = join39(directory2, name);
|
|
45170
45379
|
const fd = openSync13(file2, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
45171
45380
|
try {
|
|
45172
45381
|
const stat2 = fstatSync13(fd);
|
|
@@ -45190,7 +45399,7 @@ function readOwned(directory2, name, max) {
|
|
|
45190
45399
|
}
|
|
45191
45400
|
}
|
|
45192
45401
|
function writeOwned(directory2, name, bytes) {
|
|
45193
|
-
const fd = openSync13(
|
|
45402
|
+
const fd = openSync13(join39(directory2, name), constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
|
|
45194
45403
|
try {
|
|
45195
45404
|
writeFileSync16(fd, bytes);
|
|
45196
45405
|
fsyncSync5(fd);
|
|
@@ -45203,7 +45412,7 @@ function save(directory2, value) {
|
|
|
45203
45412
|
writeOwned(directory2, temporary, JSON.stringify(value) + `
|
|
45204
45413
|
`);
|
|
45205
45414
|
unchangedDirectory(directory2, identity4);
|
|
45206
|
-
renameSync7(
|
|
45415
|
+
renameSync7(join39(directory2, temporary), join39(directory2, "receipt.json"));
|
|
45207
45416
|
const fd = openSync13(directory2, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
45208
45417
|
try {
|
|
45209
45418
|
fsyncSync5(fd);
|
|
@@ -45216,7 +45425,7 @@ function bind(client2, receipt) {
|
|
|
45216
45425
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
45217
45426
|
}
|
|
45218
45427
|
async function locked(directory2, action) {
|
|
45219
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
45428
|
+
const identity4 = safeDirectory(directory2), file2 = join39(directory2, "operation.lock");
|
|
45220
45429
|
let fd;
|
|
45221
45430
|
try {
|
|
45222
45431
|
fd = openSync13(file2, constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
|
|
@@ -45232,7 +45441,7 @@ async function locked(directory2, action) {
|
|
|
45232
45441
|
} finally {
|
|
45233
45442
|
closeSync13(fd);
|
|
45234
45443
|
unchangedDirectory(directory2, identity4);
|
|
45235
|
-
const now =
|
|
45444
|
+
const now = lstatSync24(file2);
|
|
45236
45445
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
45237
45446
|
fail2();
|
|
45238
45447
|
unlinkSync5(file2);
|
|
@@ -45284,7 +45493,7 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
45284
45493
|
phase: "prepared",
|
|
45285
45494
|
intent: null
|
|
45286
45495
|
};
|
|
45287
|
-
if (!isAbsolute20(recoveryDirectory) ||
|
|
45496
|
+
if (!isAbsolute20(recoveryDirectory) || resolve21(recoveryDirectory) !== recoveryDirectory || realpathSync11(dirname26(recoveryDirectory)) !== dirname26(recoveryDirectory))
|
|
45288
45497
|
return fail2();
|
|
45289
45498
|
mkdirSync18(recoveryDirectory, { mode: 448 });
|
|
45290
45499
|
safeDirectory(recoveryDirectory);
|
|
@@ -45376,7 +45585,7 @@ async function inspectLocked(client2, directory2, cancel) {
|
|
|
45376
45585
|
}
|
|
45377
45586
|
// src/lib/skill-context.ts
|
|
45378
45587
|
import { createHash as createHash26 } from "crypto";
|
|
45379
|
-
import { resolve as
|
|
45588
|
+
import { resolve as resolve22 } from "path";
|
|
45380
45589
|
function words(value) {
|
|
45381
45590
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
45382
45591
|
}
|
|
@@ -45398,7 +45607,11 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
45398
45607
|
}
|
|
45399
45608
|
const profileId = input2.profileId ?? "default";
|
|
45400
45609
|
const sessionId = input2.sessionId ? input2.agentId ? `${input2.sessionId}:${input2.agentId}` : input2.sessionId : undefined;
|
|
45401
|
-
|
|
45610
|
+
if (input2.parentSessionId !== undefined) {
|
|
45611
|
+
if (input2.agentId !== undefined || !sessionId || input2.parentSessionId === sessionId || input2.parentSessionId !== null && (typeof input2.parentSessionId !== "string" || !input2.parentSessionId.trim()))
|
|
45612
|
+
throw new SkillSelectionError("INVALID_SESSION", "Native session custody requires its own session id and a distinct parent, without a legacy agent id.");
|
|
45613
|
+
}
|
|
45614
|
+
const resolverOptions = { ...options, projectDir: input2.cwd ?? options.projectDir, sessionId, nativeParentSessionId: input2.parentSessionId, parentSessionId: input2.parentSessionId ?? (input2.agentId ? input2.sessionId : undefined) };
|
|
45402
45615
|
const resolved = await resolveSelectionContext(profileId, resolverOptions);
|
|
45403
45616
|
const profile = resolved.receipt.profile;
|
|
45404
45617
|
const explicit = new Set((input2.skills ?? []).map((spec) => selectionKey(exactProfileSelection(spec, profile))));
|
|
@@ -45411,7 +45624,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
45411
45624
|
const promptWords = words(prompt);
|
|
45412
45625
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
45413
45626
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
45414
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
45627
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve22(input2.cwd)] : []];
|
|
45415
45628
|
const candidates = profile.selections.map((selection) => {
|
|
45416
45629
|
const key = selectionKey(selection);
|
|
45417
45630
|
if (input2.restore && loaded.has(key))
|