@hasna/skills 0.10.34 → 0.10.35
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/bin/index.js +543 -333
- package/bin/mcp.js +124 -123
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +398 -214
- package/dist/lib/agent-integration.d.ts +27 -0
- package/dist/sdk/index.js +189 -6
- package/package.json +1 -1
|
@@ -133,6 +133,33 @@ export declare function planAgentIntegration(options: {
|
|
|
133
133
|
reviewedCacheAlias?: string;
|
|
134
134
|
codexNativeCatalog?: CodexNativeSkillCatalog;
|
|
135
135
|
}): AgentIntegrationPlan;
|
|
136
|
+
export interface ClaudeManagedHookProjection {
|
|
137
|
+
schema: "hasna.claude-managed-hook-projection.v1";
|
|
138
|
+
targetSha256: string;
|
|
139
|
+
sourceSha256: string;
|
|
140
|
+
replacements: Array<{
|
|
141
|
+
event: string;
|
|
142
|
+
groupIndex: number;
|
|
143
|
+
hookIndex: number;
|
|
144
|
+
beforeSha256: string;
|
|
145
|
+
command: string;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
export interface ClaudeManagedHookProjectionOptions {
|
|
149
|
+
home?: string;
|
|
150
|
+
dataDir?: string;
|
|
151
|
+
projectDir?: string;
|
|
152
|
+
/** Exact caller-owned bytes, never a new native home or a path to be written. */
|
|
153
|
+
targetSettings: string;
|
|
154
|
+
expectedTargetSha256: string;
|
|
155
|
+
/** Supply the preceding plan's source witness when checking it before use. */
|
|
156
|
+
expectedSourceSha256?: string;
|
|
157
|
+
}
|
|
158
|
+
/** Read-only projection for a copied Claude configuration. This does not install
|
|
159
|
+
* hooks, adopt target discovery, change policy, or confer write/admission authority.
|
|
160
|
+
* The caller must preserve the full target, hold its owning writer lease, replan
|
|
161
|
+
* against both witnesses, CAS the full bytes and revalidate after application. */
|
|
162
|
+
export declare function planClaudeManagedHookProjection(options: ClaudeManagedHookProjectionOptions): ClaudeManagedHookProjection;
|
|
136
163
|
/** Plan an explicitly authorized Stop hook replacement without adopting drift.
|
|
137
164
|
* The caller owns authorization of its exact Stop hook delta; Skills refuses
|
|
138
165
|
* every other settings change and owns discovery witnesses and guarded writes.
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.10.
|
|
32376
|
+
version: "0.10.35",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -42695,12 +42695,15 @@ function disableCodexBundledSkills(text2) {
|
|
|
42695
42695
|
// src/lib/agent-integration.ts
|
|
42696
42696
|
var sha4 = (data) => createHash18("sha256").update(data).digest("hex");
|
|
42697
42697
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
42698
|
-
function
|
|
42698
|
+
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
42699
42699
|
try {
|
|
42700
|
-
const { dlopen, read: read3 } = __require("bun:ffi");
|
|
42700
|
+
const { dlopen, read: read3, ptr } = __require("bun:ffi");
|
|
42701
42701
|
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
42702
42702
|
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
42703
42703
|
acl_free: { args: ["ptr"], returns: "i32" },
|
|
42704
|
+
acl_valid: { args: ["ptr"], returns: "i32" },
|
|
42705
|
+
acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
|
|
42706
|
+
acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
|
|
42704
42707
|
__error: { args: [], returns: "ptr" }
|
|
42705
42708
|
});
|
|
42706
42709
|
try {
|
|
@@ -42709,8 +42712,23 @@ function hasNoDarwinAcl(fd) {
|
|
|
42709
42712
|
return false;
|
|
42710
42713
|
const acl = library.symbols.acl_get_fd(fd);
|
|
42711
42714
|
if (acl) {
|
|
42712
|
-
|
|
42713
|
-
|
|
42715
|
+
try {
|
|
42716
|
+
if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
|
|
42717
|
+
return false;
|
|
42718
|
+
const entry = new BigUint64Array(1), tag = new Int32Array(1);
|
|
42719
|
+
for (let count = 0;count <= 128; count++) {
|
|
42720
|
+
const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
|
|
42721
|
+
if (status !== 0) {
|
|
42722
|
+
return status === -1 && read3.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
|
|
42723
|
+
}
|
|
42724
|
+
const value = read3.ptr(ptr(entry));
|
|
42725
|
+
if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
|
|
42726
|
+
return false;
|
|
42727
|
+
}
|
|
42728
|
+
return false;
|
|
42729
|
+
} finally {
|
|
42730
|
+
library.symbols.acl_free(acl);
|
|
42731
|
+
}
|
|
42714
42732
|
}
|
|
42715
42733
|
return (acl === null || acl === 0) && read3.i32(errno) === 2;
|
|
42716
42734
|
} finally {
|
|
@@ -42740,7 +42758,7 @@ function isSystemRootAlias(path) {
|
|
|
42740
42758
|
throw new Error("Unprotected system temporary directory");
|
|
42741
42759
|
const fd = openSync16(directory, constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW);
|
|
42742
42760
|
try {
|
|
42743
|
-
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !
|
|
42761
|
+
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !hasTrustedDarwinAcl(fd) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2))
|
|
42744
42762
|
throw new Error("Unverified system alias directory ACL");
|
|
42745
42763
|
} finally {
|
|
42746
42764
|
closeSync16(fd);
|
|
@@ -43388,6 +43406,171 @@ function planAgentIntegration(options) {
|
|
|
43388
43406
|
recheckRootAliases(aliases);
|
|
43389
43407
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
43390
43408
|
}
|
|
43409
|
+
function hookProjectionRefusal(reason) {
|
|
43410
|
+
throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
|
|
43411
|
+
}
|
|
43412
|
+
function projectedHookAcl(path, observed) {
|
|
43413
|
+
if (process.platform !== "darwin")
|
|
43414
|
+
return;
|
|
43415
|
+
const identity4 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}:${stat2.nlink}:${stat2.size}:${stat2.mtimeNs}`;
|
|
43416
|
+
const fd = openSync16(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK | (observed.isDirectory() ? constants12.O_DIRECTORY : 0));
|
|
43417
|
+
try {
|
|
43418
|
+
if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || !hasTrustedDarwinAcl(fd, true) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || identity4(lstatSync20(path, { bigint: true })) !== identity4(observed))
|
|
43419
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43420
|
+
} finally {
|
|
43421
|
+
closeSync16(fd);
|
|
43422
|
+
}
|
|
43423
|
+
}
|
|
43424
|
+
function projectedHookExecutable(command, expectedResolved) {
|
|
43425
|
+
try {
|
|
43426
|
+
if (!isAbsolute17(command) || /[\0\r\n]/.test(command) || resolve16(command) !== command)
|
|
43427
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43428
|
+
command = canonicalSystemPath(command);
|
|
43429
|
+
const parents3 = (file = command) => {
|
|
43430
|
+
const result = [];
|
|
43431
|
+
for (let path = dirname15(file);; path = dirname15(path)) {
|
|
43432
|
+
const stat2 = lstatSync20(path, { bigint: true });
|
|
43433
|
+
if (!stat2.isDirectory() || (stat2.mode & 0o022n) !== 0n && !(stat2.uid === 0n && (stat2.mode & 0o1000n) !== 0n))
|
|
43434
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43435
|
+
projectedHookAcl(path, stat2);
|
|
43436
|
+
result.push(`${path}:${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`);
|
|
43437
|
+
if (dirname15(path) === path)
|
|
43438
|
+
break;
|
|
43439
|
+
}
|
|
43440
|
+
return result;
|
|
43441
|
+
};
|
|
43442
|
+
const parentIdentity = parents3(), link = lstatSync20(command, { bigint: true });
|
|
43443
|
+
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
43444
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43445
|
+
const linkIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.mode}:${stat2.uid}:${stat2.ctimeNs}`;
|
|
43446
|
+
const resolved = realpathSync8(command);
|
|
43447
|
+
if (expectedResolved !== undefined && resolved !== expectedResolved)
|
|
43448
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43449
|
+
const resolvedParentIdentity = parents3(resolved);
|
|
43450
|
+
const manifest = snapshot(join23(dirname15(dirname15(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
43451
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
43452
|
+
const pkg = JSON.parse(manifest.text);
|
|
43453
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join23(dirname15(manifest.file), "bin/index.js"))
|
|
43454
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43455
|
+
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
43456
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
43457
|
+
if ((cli.stat.mode & 0o100n) === 0n)
|
|
43458
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43459
|
+
const recheck = () => {
|
|
43460
|
+
try {
|
|
43461
|
+
if (realpathSync8(command) !== resolved || linkIdentity(lstatSync20(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual6(parents3(), parentIdentity) || !isDeepStrictEqual6(parents3(resolved), resolvedParentIdentity))
|
|
43462
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43463
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
43464
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
43465
|
+
unchanged2(cli);
|
|
43466
|
+
unchanged2(manifest);
|
|
43467
|
+
} catch {
|
|
43468
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43469
|
+
}
|
|
43470
|
+
};
|
|
43471
|
+
recheck();
|
|
43472
|
+
return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
|
|
43473
|
+
} catch {
|
|
43474
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43475
|
+
}
|
|
43476
|
+
}
|
|
43477
|
+
function planClaudeManagedHookProjection(options) {
|
|
43478
|
+
if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
|
|
43479
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43480
|
+
const targetSha256 = sha4(options.targetSettings);
|
|
43481
|
+
if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
|
|
43482
|
+
hookProjectionRefusal("TARGET_CHANGED");
|
|
43483
|
+
let target;
|
|
43484
|
+
try {
|
|
43485
|
+
target = jsonObject(options.targetSettings, "target");
|
|
43486
|
+
} catch {
|
|
43487
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43488
|
+
}
|
|
43489
|
+
const home = resolve16(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
43490
|
+
const policyPath = join23(dataDir2, "agent-policy.json");
|
|
43491
|
+
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
43492
|
+
try {
|
|
43493
|
+
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
43494
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
43495
|
+
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
43496
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43497
|
+
const binding = snapshot2.value.bridge;
|
|
43498
|
+
settingsPath = canonicalAgentPath(join23(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
43499
|
+
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
43500
|
+
command = binding?.commands?.claude;
|
|
43501
|
+
profile = binding?.profiles?.claude;
|
|
43502
|
+
if (typeof command !== "string" || typeof profile !== "string")
|
|
43503
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43504
|
+
assertManagedAgentBridge("claude", { home, dataDir: dataDir2, projectDir: options.projectDir, profileId: profile });
|
|
43505
|
+
} catch {
|
|
43506
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
43507
|
+
}
|
|
43508
|
+
const executable = projectedHookExecutable(command);
|
|
43509
|
+
const sourceSha256 = sha4(JSON.stringify({
|
|
43510
|
+
home,
|
|
43511
|
+
policyPath,
|
|
43512
|
+
policy: sha4(policyBytes),
|
|
43513
|
+
settingsPath,
|
|
43514
|
+
settings: sha4(settingsBytes),
|
|
43515
|
+
executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
|
|
43516
|
+
}));
|
|
43517
|
+
if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
|
|
43518
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43519
|
+
const replacements = [];
|
|
43520
|
+
const observed = [executable];
|
|
43521
|
+
if (target.hooks !== undefined) {
|
|
43522
|
+
if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
|
|
43523
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43524
|
+
for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
|
|
43525
|
+
const groups = target.hooks[event];
|
|
43526
|
+
if (groups === undefined)
|
|
43527
|
+
continue;
|
|
43528
|
+
if (!Array.isArray(groups))
|
|
43529
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43530
|
+
let recognized = 0;
|
|
43531
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
43532
|
+
if (!group || !Array.isArray(group.hooks))
|
|
43533
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
43534
|
+
for (const [hookIndex, hook] of group.hooks.entries()) {
|
|
43535
|
+
if (hook?.type !== "command" || typeof hook.command !== "string")
|
|
43536
|
+
continue;
|
|
43537
|
+
if (!hook.command.includes("hook user-prompt --agent claude"))
|
|
43538
|
+
continue;
|
|
43539
|
+
const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
|
|
43540
|
+
if (!match || match[3] !== event)
|
|
43541
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43542
|
+
const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
|
|
43543
|
+
let rendered;
|
|
43544
|
+
try {
|
|
43545
|
+
rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
|
|
43546
|
+
} catch {
|
|
43547
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43548
|
+
}
|
|
43549
|
+
if (rendered !== hook.command)
|
|
43550
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
43551
|
+
const legacy = projectedHookExecutable(oldCommand, executable.resolved);
|
|
43552
|
+
if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
|
|
43553
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
43554
|
+
observed.push(legacy);
|
|
43555
|
+
if (++recognized > 1)
|
|
43556
|
+
hookProjectionRefusal("AMBIGUOUS");
|
|
43557
|
+
const replacement = renderAgentHookCommand(command, "claude", profile, event);
|
|
43558
|
+
if (replacement !== hook.command)
|
|
43559
|
+
replacements.push({ event, groupIndex, hookIndex, beforeSha256: sha4(hook.command), command: replacement });
|
|
43560
|
+
}
|
|
43561
|
+
}
|
|
43562
|
+
}
|
|
43563
|
+
}
|
|
43564
|
+
try {
|
|
43565
|
+
if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
|
|
43566
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43567
|
+
} catch {
|
|
43568
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
43569
|
+
}
|
|
43570
|
+
for (const item of observed)
|
|
43571
|
+
item.recheck();
|
|
43572
|
+
return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements };
|
|
43573
|
+
}
|
|
43391
43574
|
function planClaudeStopHookUpdate(options) {
|
|
43392
43575
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
43393
43576
|
}
|