@hasna/skills 0.1.70 → 0.1.71
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 +813 -70
- package/bin/mcp.js +1 -1
- package/bin/server.js +1 -1
- package/dist/cli/commands/hydrate.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +625 -1
- package/dist/lib/portable-snapshot-filter.d.ts +48 -0
- package/dist/lib/station-hydrate.d.ts +103 -0
- package/dist/lib/station-snapshot.d.ts +96 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36860,7 +36860,7 @@ var package_default;
|
|
|
36860
36860
|
var init_package = __esm(() => {
|
|
36861
36861
|
package_default = {
|
|
36862
36862
|
name: "@hasna/skills",
|
|
36863
|
-
version: "0.1.
|
|
36863
|
+
version: "0.1.71",
|
|
36864
36864
|
description: "Skills library for AI coding agents",
|
|
36865
36865
|
type: "module",
|
|
36866
36866
|
bin: {
|
|
@@ -71395,13 +71395,344 @@ var init_completion = __esm(() => {
|
|
|
71395
71395
|
categoryNames = CATEGORIES.map((c) => c);
|
|
71396
71396
|
});
|
|
71397
71397
|
|
|
71398
|
+
// src/lib/portable-snapshot-filter.ts
|
|
71399
|
+
import { readdirSync as readdirSync15, statSync as statSync15 } from "fs";
|
|
71400
|
+
import { homedir as homedir9 } from "os";
|
|
71401
|
+
import { join as join28, sep as sep3 } from "path";
|
|
71402
|
+
function isExcludedSkillFileName(fileName) {
|
|
71403
|
+
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
71404
|
+
return true;
|
|
71405
|
+
}
|
|
71406
|
+
return EXCLUDE_FILE_PATTERNS.some((pattern) => pattern.test(fileName));
|
|
71407
|
+
}
|
|
71408
|
+
function isPortableWithinSkill(relativeParts) {
|
|
71409
|
+
if (relativeParts.length < 2) {
|
|
71410
|
+
return false;
|
|
71411
|
+
}
|
|
71412
|
+
const [, second] = relativeParts;
|
|
71413
|
+
if (PORTABLE_TOP_LEVEL.has(second)) {
|
|
71414
|
+
return relativeParts.length === 2;
|
|
71415
|
+
}
|
|
71416
|
+
if (relativeParts.length < 3) {
|
|
71417
|
+
return false;
|
|
71418
|
+
}
|
|
71419
|
+
return PORTABLE_SUBDIRS.has(second);
|
|
71420
|
+
}
|
|
71421
|
+
function homePathFor(definition, homesRoot) {
|
|
71422
|
+
const home = homesRoot ?? homedir9();
|
|
71423
|
+
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
71424
|
+
return join28(home, ".hasna", "skills", definition.name);
|
|
71425
|
+
}
|
|
71426
|
+
if (definition.agent === "opencode") {
|
|
71427
|
+
return join28(home, ".config", "opencode", "skills");
|
|
71428
|
+
}
|
|
71429
|
+
return join28(home, `.${definition.agent}`, "skills");
|
|
71430
|
+
}
|
|
71431
|
+
function destinationFor(definition, stationId, relativePath) {
|
|
71432
|
+
const category = definition.subClass === "agent-homes" ? join28("agent-homes", definition.agent ?? "") : definition.name;
|
|
71433
|
+
return join28("resources", stationId, "skills", category, ...relativePath.split(sep3));
|
|
71434
|
+
}
|
|
71435
|
+
function walkEntries(absoluteRoot) {
|
|
71436
|
+
let entries;
|
|
71437
|
+
try {
|
|
71438
|
+
entries = readdirSync15(absoluteRoot, { withFileTypes: true });
|
|
71439
|
+
} catch {
|
|
71440
|
+
return [];
|
|
71441
|
+
}
|
|
71442
|
+
const output = [];
|
|
71443
|
+
for (const entry of entries) {
|
|
71444
|
+
const childFull = join28(absoluteRoot, entry.name);
|
|
71445
|
+
if (entry.isSymbolicLink()) {
|
|
71446
|
+
output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
71447
|
+
continue;
|
|
71448
|
+
}
|
|
71449
|
+
if (entry.isDirectory()) {
|
|
71450
|
+
if (EXCLUDE_DIR_NAMES.has(entry.name) || EXCLUDE_DIR_PATTERNS.some((pattern) => pattern.test(entry.name))) {
|
|
71451
|
+
continue;
|
|
71452
|
+
}
|
|
71453
|
+
const nested = walkEntries(childFull);
|
|
71454
|
+
for (const item of nested) {
|
|
71455
|
+
output.push({ ...item, relativePath: join28(entry.name, item.relativePath) });
|
|
71456
|
+
}
|
|
71457
|
+
continue;
|
|
71458
|
+
}
|
|
71459
|
+
if (entry.isFile()) {
|
|
71460
|
+
output.push({ kind: "file", relativePath: entry.name, fullPath: childFull });
|
|
71461
|
+
}
|
|
71462
|
+
}
|
|
71463
|
+
return output;
|
|
71464
|
+
}
|
|
71465
|
+
function isRegularFile(filePath) {
|
|
71466
|
+
try {
|
|
71467
|
+
return statSync15(filePath).isFile();
|
|
71468
|
+
} catch {
|
|
71469
|
+
return false;
|
|
71470
|
+
}
|
|
71471
|
+
}
|
|
71472
|
+
var SYNC_HOMES, EXCLUDE_DIR_NAMES, EXCLUDE_DIR_PATTERNS, EXCLUDE_FILE_NAMES, EXCLUDE_FILE_PATTERNS, PORTABLE_TOP_LEVEL, PORTABLE_SUBDIRS, REFUSED_SCANNER_FLAGGED;
|
|
71473
|
+
var init_portable_snapshot_filter = __esm(() => {
|
|
71474
|
+
SYNC_HOMES = [
|
|
71475
|
+
{ name: "skills", subClass: "skills", agent: null },
|
|
71476
|
+
{ name: "custom", subClass: "custom", agent: null },
|
|
71477
|
+
{ name: "claude", subClass: "agent-homes", agent: "claude" },
|
|
71478
|
+
{ name: "codewith", subClass: "agent-homes", agent: "codewith" },
|
|
71479
|
+
{ name: "codex", subClass: "agent-homes", agent: "codex" },
|
|
71480
|
+
{ name: "opencode", subClass: "agent-homes", agent: "opencode" },
|
|
71481
|
+
{ name: "cursor", subClass: "agent-homes", agent: "cursor" }
|
|
71482
|
+
];
|
|
71483
|
+
EXCLUDE_DIR_NAMES = new Set([
|
|
71484
|
+
".git",
|
|
71485
|
+
"node_modules",
|
|
71486
|
+
"__pycache__",
|
|
71487
|
+
".cache",
|
|
71488
|
+
".pytest_cache",
|
|
71489
|
+
".mypy_cache",
|
|
71490
|
+
".ruff_cache"
|
|
71491
|
+
]);
|
|
71492
|
+
EXCLUDE_DIR_PATTERNS = [
|
|
71493
|
+
/^\.merge-pr\.rollback-/
|
|
71494
|
+
];
|
|
71495
|
+
EXCLUDE_FILE_NAMES = new Set([
|
|
71496
|
+
".DS_Store",
|
|
71497
|
+
"package-lock.json",
|
|
71498
|
+
"pnpm-lock.yaml",
|
|
71499
|
+
"yarn.lock",
|
|
71500
|
+
"Cargo.lock"
|
|
71501
|
+
]);
|
|
71502
|
+
EXCLUDE_FILE_PATTERNS = [
|
|
71503
|
+
/^\._/,
|
|
71504
|
+
/\.bak$/,
|
|
71505
|
+
/\.orig$/,
|
|
71506
|
+
/\.rej$/,
|
|
71507
|
+
/~$/,
|
|
71508
|
+
/\.pyc$/,
|
|
71509
|
+
/\.pyo$/,
|
|
71510
|
+
/\.log$/,
|
|
71511
|
+
/\.db$/,
|
|
71512
|
+
/\.sqlite(\d)?$/,
|
|
71513
|
+
/^bun\.lock/,
|
|
71514
|
+
/\.env($|\.)/,
|
|
71515
|
+
/\.pem$/,
|
|
71516
|
+
/\.key$/,
|
|
71517
|
+
/\.p12$/,
|
|
71518
|
+
/\.pfx$/,
|
|
71519
|
+
/\.jks$/,
|
|
71520
|
+
/^id_rsa/,
|
|
71521
|
+
/^id_ed25519/,
|
|
71522
|
+
/^credentials/
|
|
71523
|
+
];
|
|
71524
|
+
PORTABLE_TOP_LEVEL = new Set(["SKILL.md", "skill.json"]);
|
|
71525
|
+
PORTABLE_SUBDIRS = new Set(["scripts", "assets", "references"]);
|
|
71526
|
+
REFUSED_SCANNER_FLAGGED = new Set([
|
|
71527
|
+
"aws-cross-account-app-migration/SKILL.md",
|
|
71528
|
+
"aws-cross-account-app-migration/scripts/selftest.sh",
|
|
71529
|
+
"gateway-serve/SKILL.md",
|
|
71530
|
+
"infinity-drain/SKILL.md",
|
|
71531
|
+
"infinity-run/SKILL.md",
|
|
71532
|
+
"oss-saas-code-cleanup/SKILL.md",
|
|
71533
|
+
"repo-project-familiarization/scripts/repo_shape.py",
|
|
71534
|
+
"repo-project-familiarization/scripts/session_history.py",
|
|
71535
|
+
"scale-check/SKILL.md",
|
|
71536
|
+
"standard-align-repo/SKILL.md",
|
|
71537
|
+
"standard-build-iapp/SKILL.md",
|
|
71538
|
+
"standard-build-oss/SKILL.md",
|
|
71539
|
+
"skill-image/SKILL.md",
|
|
71540
|
+
"skill-scale-check/SKILL.md",
|
|
71541
|
+
"sqlite-to-rds-parity-migrate/scripts/parity-migrate.ts",
|
|
71542
|
+
"pdf-operations/scripts/pdf_ops.py"
|
|
71543
|
+
]);
|
|
71544
|
+
});
|
|
71545
|
+
|
|
71546
|
+
// src/lib/station-snapshot.ts
|
|
71547
|
+
import { createHash as createHash4 } from "crypto";
|
|
71548
|
+
import {
|
|
71549
|
+
copyFileSync as copyFileSync2,
|
|
71550
|
+
mkdirSync as mkdirSync15,
|
|
71551
|
+
readFileSync as readFileSync22,
|
|
71552
|
+
statSync as statSync16,
|
|
71553
|
+
writeFileSync as writeFileSync16
|
|
71554
|
+
} from "fs";
|
|
71555
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative4, resolve, sep as sep4 } from "path";
|
|
71556
|
+
function validateStationId(stationId) {
|
|
71557
|
+
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
71558
|
+
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
71559
|
+
}
|
|
71560
|
+
}
|
|
71561
|
+
function sha256File(filePath) {
|
|
71562
|
+
return createHash4("sha256").update(readFileSync22(filePath)).digest("hex");
|
|
71563
|
+
}
|
|
71564
|
+
function scanHome(definition, homesRoot) {
|
|
71565
|
+
const homePath = homePathFor(definition, homesRoot);
|
|
71566
|
+
const entries = walkEntries(homePath);
|
|
71567
|
+
const portable = [];
|
|
71568
|
+
const skipped = [];
|
|
71569
|
+
for (const entry of entries) {
|
|
71570
|
+
const relativeParts = entry.relativePath.split(sep4);
|
|
71571
|
+
const fileName = relativeParts[relativeParts.length - 1];
|
|
71572
|
+
if (entry.kind === "symlink") {
|
|
71573
|
+
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
71574
|
+
continue;
|
|
71575
|
+
}
|
|
71576
|
+
if (!isPortableWithinSkill(relativeParts)) {
|
|
71577
|
+
skipped.push({ relativePath: entry.relativePath, reason: "not-portable" });
|
|
71578
|
+
continue;
|
|
71579
|
+
}
|
|
71580
|
+
if (isExcludedSkillFileName(fileName)) {
|
|
71581
|
+
skipped.push({ relativePath: entry.relativePath, reason: "excluded" });
|
|
71582
|
+
continue;
|
|
71583
|
+
}
|
|
71584
|
+
if (REFUSED_SCANNER_FLAGGED.has(entry.relativePath)) {
|
|
71585
|
+
skipped.push({ relativePath: entry.relativePath, reason: "refused-scanner-flagged" });
|
|
71586
|
+
continue;
|
|
71587
|
+
}
|
|
71588
|
+
if (!isRegularFile(entry.fullPath)) {
|
|
71589
|
+
skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
|
|
71590
|
+
continue;
|
|
71591
|
+
}
|
|
71592
|
+
const info = statSync16(entry.fullPath);
|
|
71593
|
+
portable.push({
|
|
71594
|
+
relativePath: entry.relativePath,
|
|
71595
|
+
fullPath: entry.fullPath,
|
|
71596
|
+
size: info.size,
|
|
71597
|
+
mtimeMs: info.mtimeMs,
|
|
71598
|
+
mtimeIso: info.mtime.toISOString()
|
|
71599
|
+
});
|
|
71600
|
+
}
|
|
71601
|
+
return { definition, homePath, portable, skipped };
|
|
71602
|
+
}
|
|
71603
|
+
function planStationSnapshot(options) {
|
|
71604
|
+
validateStationId(options.stationId);
|
|
71605
|
+
const scanned = SYNC_HOMES.map((definition) => scanHome(definition, options.homesRoot));
|
|
71606
|
+
const symlinks = scanned.reduce((sum2, item) => sum2 + item.skipped.filter((entry) => entry.reason === "symlink").length, 0);
|
|
71607
|
+
if (symlinks > 0) {
|
|
71608
|
+
throw new StationSnapshotError("SYMLINKS_REFUSED", `${symlinks} symlink(s) inside skill homes; symlinks are refused (fail closed)`);
|
|
71609
|
+
}
|
|
71610
|
+
const plans = [];
|
|
71611
|
+
for (const item of scanned) {
|
|
71612
|
+
for (const file of item.portable) {
|
|
71613
|
+
plans.push({
|
|
71614
|
+
definition: item.definition,
|
|
71615
|
+
source: file,
|
|
71616
|
+
destination: destinationFor(item.definition, options.stationId, file.relativePath),
|
|
71617
|
+
digest: sha256File(file.fullPath)
|
|
71618
|
+
});
|
|
71619
|
+
}
|
|
71620
|
+
}
|
|
71621
|
+
const totalBytes = plans.reduce((sum2, plan) => sum2 + plan.source.size, 0);
|
|
71622
|
+
return { scanned, plans, totalBytes };
|
|
71623
|
+
}
|
|
71624
|
+
function humanHomes(scanned) {
|
|
71625
|
+
return scanned.map((item) => ({
|
|
71626
|
+
name: item.definition.name,
|
|
71627
|
+
homePath: item.homePath,
|
|
71628
|
+
files: item.portable.length,
|
|
71629
|
+
skipped: item.skipped.length
|
|
71630
|
+
}));
|
|
71631
|
+
}
|
|
71632
|
+
function writeStationSnapshot(options) {
|
|
71633
|
+
const repoRoot = resolve(options.repoRoot ?? process.cwd());
|
|
71634
|
+
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
71635
|
+
const manifestFiles = plans.map((plan) => ({
|
|
71636
|
+
relativePath: plan.source.relativePath,
|
|
71637
|
+
destination: plan.destination,
|
|
71638
|
+
subClass: plan.definition.subClass,
|
|
71639
|
+
agent: plan.definition.agent,
|
|
71640
|
+
sha256: plan.digest,
|
|
71641
|
+
sourceMtimeMs: plan.source.mtimeMs,
|
|
71642
|
+
sourceMtimeIso: plan.source.mtimeIso,
|
|
71643
|
+
size: plan.source.size
|
|
71644
|
+
}));
|
|
71645
|
+
const base2 = {
|
|
71646
|
+
stationId: options.stationId,
|
|
71647
|
+
mode: "dry-run",
|
|
71648
|
+
repoRoot,
|
|
71649
|
+
stats: { files: plans.length, bytes: totalBytes },
|
|
71650
|
+
homes: humanHomes(scanned),
|
|
71651
|
+
files: manifestFiles
|
|
71652
|
+
};
|
|
71653
|
+
if (options.dryRun !== false) {
|
|
71654
|
+
return base2;
|
|
71655
|
+
}
|
|
71656
|
+
const conflicts = [];
|
|
71657
|
+
const untouched = [];
|
|
71658
|
+
for (const plan of plans) {
|
|
71659
|
+
const destination = resolve(repoRoot, plan.destination);
|
|
71660
|
+
const destinationRelative = relative4(repoRoot, destination);
|
|
71661
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute3(destinationRelative)) {
|
|
71662
|
+
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
71663
|
+
}
|
|
71664
|
+
let existingDigest = null;
|
|
71665
|
+
try {
|
|
71666
|
+
existingDigest = sha256File(destination);
|
|
71667
|
+
} catch {}
|
|
71668
|
+
if (existingDigest !== null) {
|
|
71669
|
+
if (existingDigest === plan.digest) {
|
|
71670
|
+
continue;
|
|
71671
|
+
}
|
|
71672
|
+
conflicts.push(`existing destination differs from staged source: ${plan.destination}`);
|
|
71673
|
+
continue;
|
|
71674
|
+
}
|
|
71675
|
+
untouched.push(plan);
|
|
71676
|
+
}
|
|
71677
|
+
if (conflicts.length > 0) {
|
|
71678
|
+
throw new StationSnapshotError("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
|
|
71679
|
+
}
|
|
71680
|
+
let written = 0;
|
|
71681
|
+
for (const plan of untouched) {
|
|
71682
|
+
const destination = resolve(repoRoot, plan.destination);
|
|
71683
|
+
mkdirSync15(dirname11(destination), { recursive: true });
|
|
71684
|
+
copyFileSync2(plan.source.fullPath, destination);
|
|
71685
|
+
written += 1;
|
|
71686
|
+
}
|
|
71687
|
+
const unchanged = plans.length - untouched.length;
|
|
71688
|
+
const manifest = {
|
|
71689
|
+
schema: STATION_SYNC_MANIFEST_SCHEMA,
|
|
71690
|
+
stationId: options.stationId,
|
|
71691
|
+
syncedAt: new Date().toISOString(),
|
|
71692
|
+
producer: STATION_SNAPSHOT_PRODUCER,
|
|
71693
|
+
stats: {
|
|
71694
|
+
written,
|
|
71695
|
+
unchanged,
|
|
71696
|
+
files: plans.length,
|
|
71697
|
+
bytes: totalBytes
|
|
71698
|
+
},
|
|
71699
|
+
files: manifestFiles
|
|
71700
|
+
};
|
|
71701
|
+
const manifestPath = resolve(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
71702
|
+
mkdirSync15(dirname11(manifestPath), { recursive: true });
|
|
71703
|
+
writeFileSync16(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
71704
|
+
`);
|
|
71705
|
+
return {
|
|
71706
|
+
...base2,
|
|
71707
|
+
mode: "populate",
|
|
71708
|
+
stats: { files: plans.length, bytes: totalBytes, written, unchanged },
|
|
71709
|
+
manifestPath
|
|
71710
|
+
};
|
|
71711
|
+
}
|
|
71712
|
+
var STATION_SYNC_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-sync-manifest/v1", STATION_SNAPSHOT_PRODUCER, StationSnapshotError;
|
|
71713
|
+
var init_station_snapshot = __esm(() => {
|
|
71714
|
+
init_package();
|
|
71715
|
+
init_portable_snapshot_filter();
|
|
71716
|
+
STATION_SNAPSHOT_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
71717
|
+
StationSnapshotError = class StationSnapshotError extends Error {
|
|
71718
|
+
code;
|
|
71719
|
+
detail;
|
|
71720
|
+
constructor(code, message, detail = []) {
|
|
71721
|
+
super(message);
|
|
71722
|
+
this.name = "StationSnapshotError";
|
|
71723
|
+
this.code = code;
|
|
71724
|
+
this.detail = detail;
|
|
71725
|
+
}
|
|
71726
|
+
};
|
|
71727
|
+
});
|
|
71728
|
+
|
|
71398
71729
|
// src/cli/commands/create-sync-config.ts
|
|
71399
71730
|
var exports_create_sync_config = {};
|
|
71400
71731
|
__export(exports_create_sync_config, {
|
|
71401
71732
|
registerCreateSync: () => registerCreateSync
|
|
71402
71733
|
});
|
|
71403
|
-
import { existsSync as existsSync27, writeFileSync as
|
|
71404
|
-
import { join as
|
|
71734
|
+
import { existsSync as existsSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16 } from "fs";
|
|
71735
|
+
import { join as join29 } from "path";
|
|
71405
71736
|
function registerCreateSync(parent) {
|
|
71406
71737
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
71407
71738
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -71476,13 +71807,13 @@ function registerCreateSync(parent) {
|
|
|
71476
71807
|
console.log(`${source_default.cyan("project")}: ${pp}${existsSync27(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
71477
71808
|
});
|
|
71478
71809
|
parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
|
|
71479
|
-
parent.command("sync").alias("render").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).description("Write corpus skills into each coding agent's global skills folder, per-tool adapted").action((names, options) => handleSync(names, options));
|
|
71810
|
+
parent.command("sync").alias("render").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).option("--station <id>", "Per-station snapshot mode: snapshot the installed skill homes into resources/<station>/skills with a v3 sync-manifest (dry-run by default; --populate writes)").option("--populate", "Write the per-station snapshot (station mode; the default is dry-run)", false).option("--repo-root <path>", "Station snapshot destination repo root (default: cwd)").option("--homes-root <dir>", "Build the station snapshot from a staged mirror of the skill homes instead of this machine's $HOME").description("Write corpus skills into each coding agent's global skills folder, per-tool adapted; with --station, snapshot the homes into a reviewed snapshot repo instead").action((names, options) => handleSync(names, options));
|
|
71480
71811
|
}
|
|
71481
71812
|
function handleCreate(name, options) {
|
|
71482
71813
|
const bare = name.trim();
|
|
71483
71814
|
const dirName = bare;
|
|
71484
71815
|
const baseDir = getPortableSkillsRoot();
|
|
71485
|
-
const skillDir =
|
|
71816
|
+
const skillDir = join29(baseDir, dirName);
|
|
71486
71817
|
if (existsSync27(skillDir)) {
|
|
71487
71818
|
console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : source_default.red(`Skill '${bare}' already exists at ${skillDir}`));
|
|
71488
71819
|
process.exitCode = 1;
|
|
@@ -71491,8 +71822,8 @@ function handleCreate(name, options) {
|
|
|
71491
71822
|
const description = options.description || `${bare} skill`;
|
|
71492
71823
|
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
|
|
71493
71824
|
const displayName2 = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
71494
|
-
|
|
71495
|
-
|
|
71825
|
+
mkdirSync16(join29(skillDir, "src"), { recursive: true });
|
|
71826
|
+
writeFileSync17(join29(skillDir, "SKILL.md"), [
|
|
71496
71827
|
"---",
|
|
71497
71828
|
`name: ${bare}`,
|
|
71498
71829
|
`description: ${description}`,
|
|
@@ -71512,11 +71843,11 @@ function handleCreate(name, options) {
|
|
|
71512
71843
|
""
|
|
71513
71844
|
].join(`
|
|
71514
71845
|
`));
|
|
71515
|
-
|
|
71846
|
+
writeFileSync17(join29(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName2} \u2014 ${description}`, ` */`, "", `console.log("${displayName2}");`, ""].join(`
|
|
71516
71847
|
`));
|
|
71517
|
-
|
|
71848
|
+
writeFileSync17(join29(skillDir, "package.json"), JSON.stringify({ name: bare, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
|
|
71518
71849
|
`);
|
|
71519
|
-
|
|
71850
|
+
writeFileSync17(join29(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
|
|
71520
71851
|
`);
|
|
71521
71852
|
clearRegistryCache();
|
|
71522
71853
|
if (options.json)
|
|
@@ -71525,11 +71856,15 @@ function handleCreate(name, options) {
|
|
|
71525
71856
|
console.log(source_default.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
|
|
71526
71857
|
console.log(source_default.dim(` Category: ${options.category}`));
|
|
71527
71858
|
console.log(source_default.dim(` Tags: ${tags.join(", ")}`));
|
|
71528
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
71529
|
-
console.log(` ${source_default.cyan("Run:")} bun ${
|
|
71859
|
+
console.log(` ${source_default.cyan("Edit:")} ${join29(skillDir, "src", "index.ts")}`);
|
|
71860
|
+
console.log(` ${source_default.cyan("Run:")} bun ${join29(skillDir, "src", "index.ts")}`);
|
|
71530
71861
|
}
|
|
71531
71862
|
}
|
|
71532
71863
|
function handleSync(names, options) {
|
|
71864
|
+
if (options.station) {
|
|
71865
|
+
handleStationSnapshot(names, options);
|
|
71866
|
+
return;
|
|
71867
|
+
}
|
|
71533
71868
|
const modes = [options.check, options.adopt, options.prune].filter(Boolean).length;
|
|
71534
71869
|
if (modes > 1) {
|
|
71535
71870
|
const message = "--check, --adopt, and --prune are mutually exclusive";
|
|
@@ -71590,6 +71925,68 @@ function handleSync(names, options) {
|
|
|
71590
71925
|
process.exitCode = 1;
|
|
71591
71926
|
}
|
|
71592
71927
|
}
|
|
71928
|
+
function handleStationSnapshot(names, options) {
|
|
71929
|
+
const station = options.station;
|
|
71930
|
+
if (!station)
|
|
71931
|
+
return;
|
|
71932
|
+
if (options.populate && options.dryRun) {
|
|
71933
|
+
const message = "--populate and --dry-run are mutually exclusive";
|
|
71934
|
+
if (options.json)
|
|
71935
|
+
console.log(JSON.stringify({ error: message }));
|
|
71936
|
+
else
|
|
71937
|
+
console.error(source_default.red(message));
|
|
71938
|
+
process.exitCode = 1;
|
|
71939
|
+
return;
|
|
71940
|
+
}
|
|
71941
|
+
const incompatible = names.length > 0 || options.check || options.adopt || options.prune || options.force || options.all || options.source !== undefined;
|
|
71942
|
+
if (incompatible) {
|
|
71943
|
+
const message = "--station (per-station snapshot mode) cannot be combined with corpus->home sync names, --check, --adopt, --prune, --force, --all, or --source";
|
|
71944
|
+
if (options.json)
|
|
71945
|
+
console.log(JSON.stringify({ error: message }));
|
|
71946
|
+
else
|
|
71947
|
+
console.error(source_default.red(message));
|
|
71948
|
+
process.exitCode = 1;
|
|
71949
|
+
return;
|
|
71950
|
+
}
|
|
71951
|
+
try {
|
|
71952
|
+
const result2 = writeStationSnapshot({
|
|
71953
|
+
stationId: station,
|
|
71954
|
+
repoRoot: options.repoRoot,
|
|
71955
|
+
homesRoot: options.homesRoot,
|
|
71956
|
+
dryRun: !options.populate
|
|
71957
|
+
});
|
|
71958
|
+
if (result2.mode === "dry-run") {
|
|
71959
|
+
if (options.json) {
|
|
71960
|
+
console.log(JSON.stringify({
|
|
71961
|
+
stationId: result2.stationId,
|
|
71962
|
+
mode: "dry-run",
|
|
71963
|
+
stats: { files: result2.stats.files, bytes: result2.stats.bytes },
|
|
71964
|
+
homes: Object.fromEntries(result2.homes.map((home) => [
|
|
71965
|
+
home.name,
|
|
71966
|
+
{ homePath: home.homePath, files: home.files, skipped: home.skipped }
|
|
71967
|
+
]))
|
|
71968
|
+
}, null, 2));
|
|
71969
|
+
return;
|
|
71970
|
+
}
|
|
71971
|
+
console.log(`DRY-RUN station=${result2.stationId} files=${result2.stats.files} bytes=${result2.stats.bytes}`);
|
|
71972
|
+
for (const home of result2.homes) {
|
|
71973
|
+
console.log(` ${home.name}: ${home.files} files, ${home.skipped} skipped`);
|
|
71974
|
+
}
|
|
71975
|
+
return;
|
|
71976
|
+
}
|
|
71977
|
+
console.log(`POPULATE station=${result2.stationId} written=${result2.stats.written} unchanged=${result2.stats.unchanged} total=${result2.stats.files} bytes=${result2.stats.bytes}`);
|
|
71978
|
+
} catch (error2) {
|
|
71979
|
+
if (error2 instanceof StationSnapshotError) {
|
|
71980
|
+
for (const line of error2.detail)
|
|
71981
|
+
console.error(`CONFLICT ${line}`);
|
|
71982
|
+
console.error(`FAIL ${error2.message}`);
|
|
71983
|
+
process.exitCode = 2;
|
|
71984
|
+
} else {
|
|
71985
|
+
console.error(`FAIL ${error2.stack ?? error2.message}`);
|
|
71986
|
+
process.exitCode = 1;
|
|
71987
|
+
}
|
|
71988
|
+
}
|
|
71989
|
+
}
|
|
71593
71990
|
function handleSyncCheck(json) {
|
|
71594
71991
|
const census = censusHomeDrift();
|
|
71595
71992
|
if (json) {
|
|
@@ -71701,6 +72098,350 @@ var init_create_sync_config = __esm(() => {
|
|
|
71701
72098
|
init_agent_sync();
|
|
71702
72099
|
init_home_adoption();
|
|
71703
72100
|
init_home_census();
|
|
72101
|
+
init_station_snapshot();
|
|
72102
|
+
});
|
|
72103
|
+
|
|
72104
|
+
// src/lib/station-hydrate.ts
|
|
72105
|
+
import { createHash as createHash5 } from "crypto";
|
|
72106
|
+
import {
|
|
72107
|
+
copyFileSync as copyFileSync3,
|
|
72108
|
+
mkdirSync as mkdirSync17,
|
|
72109
|
+
readdirSync as readdirSync16,
|
|
72110
|
+
readFileSync as readFileSync23,
|
|
72111
|
+
statSync as statSync17,
|
|
72112
|
+
writeFileSync as writeFileSync18
|
|
72113
|
+
} from "fs";
|
|
72114
|
+
import { dirname as dirname12, join as join30, resolve as resolve2, sep as sep5 } from "path";
|
|
72115
|
+
function fail2(code, message, detail = []) {
|
|
72116
|
+
throw new StationSnapshotError(code, message, detail);
|
|
72117
|
+
}
|
|
72118
|
+
function snapshotRootFor(repoRoot, stationId) {
|
|
72119
|
+
return join30(repoRoot, "resources", stationId, "skills");
|
|
72120
|
+
}
|
|
72121
|
+
function readSnapshotManifest(repoRoot, stationId) {
|
|
72122
|
+
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
72123
|
+
const manifestPath = join30(snapshotRoot, "sync-manifest.json");
|
|
72124
|
+
let manifest;
|
|
72125
|
+
try {
|
|
72126
|
+
manifest = JSON.parse(readFileSync23(manifestPath, "utf8"));
|
|
72127
|
+
} catch (error2) {
|
|
72128
|
+
fail2("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
|
|
72129
|
+
}
|
|
72130
|
+
const sourceSnapshotSha = sha256File(manifestPath);
|
|
72131
|
+
return { manifest, manifestPath, sourceSnapshotSha };
|
|
72132
|
+
}
|
|
72133
|
+
function planStationHydration(stationId, repoRoot) {
|
|
72134
|
+
validateStationId(stationId);
|
|
72135
|
+
const { manifest, sourceSnapshotSha } = readSnapshotManifest(repoRoot, stationId);
|
|
72136
|
+
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
72137
|
+
const manifestHashes = new Map;
|
|
72138
|
+
for (const file of manifest.files ?? []) {
|
|
72139
|
+
const relativePath = file.relativePath;
|
|
72140
|
+
const agent = file.agent;
|
|
72141
|
+
if (agent && relativePath) {
|
|
72142
|
+
manifestHashes.set(`${agent}${MANIFEST_HASH_KEY_SEP}${relativePath}`, file.sha256);
|
|
72143
|
+
}
|
|
72144
|
+
}
|
|
72145
|
+
const candidates = [];
|
|
72146
|
+
const symlinks = [];
|
|
72147
|
+
const skippedByRule = [];
|
|
72148
|
+
for (const agent of SYNC_AGENTS) {
|
|
72149
|
+
const agentRoot = join30(snapshotRoot, "agent-homes", agent);
|
|
72150
|
+
let identEntries;
|
|
72151
|
+
try {
|
|
72152
|
+
identEntries = readdirSync16(agentRoot, { withFileTypes: true });
|
|
72153
|
+
} catch {
|
|
72154
|
+
continue;
|
|
72155
|
+
}
|
|
72156
|
+
for (const identEntry of identEntries) {
|
|
72157
|
+
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
72158
|
+
continue;
|
|
72159
|
+
}
|
|
72160
|
+
const identRoot = join30(agentRoot, identEntry.name);
|
|
72161
|
+
const entries = walkEntries(identRoot);
|
|
72162
|
+
for (const entry of entries) {
|
|
72163
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
|
|
72164
|
+
if (entry.kind === "symlink") {
|
|
72165
|
+
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
72166
|
+
continue;
|
|
72167
|
+
}
|
|
72168
|
+
if (!isPortableWithinSkill(relativeParts)) {
|
|
72169
|
+
skippedByRule.push({
|
|
72170
|
+
ident: identEntry.name,
|
|
72171
|
+
agent,
|
|
72172
|
+
relativePath: entry.relativePath,
|
|
72173
|
+
reason: "not-portable"
|
|
72174
|
+
});
|
|
72175
|
+
continue;
|
|
72176
|
+
}
|
|
72177
|
+
const fileName = relativeParts[relativeParts.length - 1];
|
|
72178
|
+
if (isExcludedSkillFileName(fileName)) {
|
|
72179
|
+
skippedByRule.push({
|
|
72180
|
+
ident: identEntry.name,
|
|
72181
|
+
agent,
|
|
72182
|
+
relativePath: entry.relativePath,
|
|
72183
|
+
reason: "excluded"
|
|
72184
|
+
});
|
|
72185
|
+
continue;
|
|
72186
|
+
}
|
|
72187
|
+
const withinIdent = relativeParts.slice(1).join(sep5);
|
|
72188
|
+
const homeRelative = relativeParts.join(sep5);
|
|
72189
|
+
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
72190
|
+
skippedByRule.push({
|
|
72191
|
+
ident: identEntry.name,
|
|
72192
|
+
agent,
|
|
72193
|
+
relativePath: entry.relativePath,
|
|
72194
|
+
reason: "refused-scanner-flagged"
|
|
72195
|
+
});
|
|
72196
|
+
continue;
|
|
72197
|
+
}
|
|
72198
|
+
if (!isRegularFile(entry.fullPath)) {
|
|
72199
|
+
skippedByRule.push({
|
|
72200
|
+
ident: identEntry.name,
|
|
72201
|
+
agent,
|
|
72202
|
+
relativePath: entry.relativePath,
|
|
72203
|
+
reason: "not-regular-file"
|
|
72204
|
+
});
|
|
72205
|
+
continue;
|
|
72206
|
+
}
|
|
72207
|
+
const info = statSync17(entry.fullPath);
|
|
72208
|
+
candidates.push({
|
|
72209
|
+
ident: identEntry.name,
|
|
72210
|
+
agent,
|
|
72211
|
+
withinIdent,
|
|
72212
|
+
fullPath: entry.fullPath,
|
|
72213
|
+
size: info.size,
|
|
72214
|
+
mtimeMs: info.mtimeMs,
|
|
72215
|
+
manifestHash: manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null
|
|
72216
|
+
});
|
|
72217
|
+
}
|
|
72218
|
+
}
|
|
72219
|
+
}
|
|
72220
|
+
if (symlinks.length > 0) {
|
|
72221
|
+
fail2("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
|
|
72222
|
+
}
|
|
72223
|
+
const byIdent = new Map;
|
|
72224
|
+
for (const candidate of candidates) {
|
|
72225
|
+
const group = byIdent.get(candidate.ident) ?? [];
|
|
72226
|
+
group.push(candidate);
|
|
72227
|
+
byIdent.set(candidate.ident, group);
|
|
72228
|
+
}
|
|
72229
|
+
const winners = [];
|
|
72230
|
+
for (const [ident, group] of byIdent) {
|
|
72231
|
+
const byFile = new Map;
|
|
72232
|
+
for (const candidate of group) {
|
|
72233
|
+
const copies = byFile.get(candidate.withinIdent) ?? [];
|
|
72234
|
+
copies.push(candidate);
|
|
72235
|
+
byFile.set(candidate.withinIdent, copies);
|
|
72236
|
+
}
|
|
72237
|
+
const files = [];
|
|
72238
|
+
for (const [withinIdent, copies] of byFile) {
|
|
72239
|
+
let eligible = copies;
|
|
72240
|
+
if (withinIdent === "SKILL.md") {
|
|
72241
|
+
const content = [];
|
|
72242
|
+
for (const copy of copies) {
|
|
72243
|
+
let isStub = false;
|
|
72244
|
+
try {
|
|
72245
|
+
isStub = isPointerSkillMd(readFileSync23(copy.fullPath, "utf8"));
|
|
72246
|
+
} catch {
|
|
72247
|
+
isStub = false;
|
|
72248
|
+
}
|
|
72249
|
+
if (!isStub)
|
|
72250
|
+
content.push(copy);
|
|
72251
|
+
}
|
|
72252
|
+
if (content.length > 0) {
|
|
72253
|
+
eligible = content;
|
|
72254
|
+
}
|
|
72255
|
+
}
|
|
72256
|
+
eligible.sort((left, right) => {
|
|
72257
|
+
const leftHash = left.manifestHash !== null;
|
|
72258
|
+
const rightHash = right.manifestHash !== null;
|
|
72259
|
+
if (leftHash !== rightHash) {
|
|
72260
|
+
return leftHash ? -1 : 1;
|
|
72261
|
+
}
|
|
72262
|
+
if (right.mtimeMs !== left.mtimeMs) {
|
|
72263
|
+
return right.mtimeMs - left.mtimeMs;
|
|
72264
|
+
}
|
|
72265
|
+
return SYNC_AGENTS.indexOf(left.agent) - SYNC_AGENTS.indexOf(right.agent);
|
|
72266
|
+
});
|
|
72267
|
+
const winner = eligible[0];
|
|
72268
|
+
files.push({
|
|
72269
|
+
withinIdent,
|
|
72270
|
+
winner,
|
|
72271
|
+
alternates: copies.filter((copy) => copy !== winner).map((copy) => copy.agent)
|
|
72272
|
+
});
|
|
72273
|
+
}
|
|
72274
|
+
files.sort((left, right) => left.withinIdent.localeCompare(right.withinIdent));
|
|
72275
|
+
winners.push({ ident, files });
|
|
72276
|
+
}
|
|
72277
|
+
winners.sort((left, right) => left.ident.localeCompare(right.ident));
|
|
72278
|
+
const totalFiles = winners.reduce((sum2, skill) => sum2 + skill.files.length, 0);
|
|
72279
|
+
const totalBytes = winners.reduce((sum2, skill) => sum2 + skill.files.reduce((inner, file) => inner + file.winner.size, 0), 0);
|
|
72280
|
+
return { manifest, sourceSnapshotSha, winners, skippedByRule, totalFiles, totalBytes };
|
|
72281
|
+
}
|
|
72282
|
+
function skillSha256(skill) {
|
|
72283
|
+
const skillMd = skill.files.find((file) => file.withinIdent === "SKILL.md");
|
|
72284
|
+
if (skillMd) {
|
|
72285
|
+
return sha256File(skillMd.winner.fullPath);
|
|
72286
|
+
}
|
|
72287
|
+
if (skill.files.length === 1) {
|
|
72288
|
+
return sha256File(skill.files[0].winner.fullPath);
|
|
72289
|
+
}
|
|
72290
|
+
const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
|
|
72291
|
+
return createHash5("sha256").update(joined.sort().join(`
|
|
72292
|
+
`)).digest("hex");
|
|
72293
|
+
}
|
|
72294
|
+
function writeStationHydration(options) {
|
|
72295
|
+
const repoRoot = resolve2(options.repoRoot ?? process.cwd());
|
|
72296
|
+
const cacheRoot = resolve2(options.cacheRoot ?? resolveCorpusRoot());
|
|
72297
|
+
const plan = planStationHydration(options.stationId, repoRoot);
|
|
72298
|
+
const resultSkills = plan.winners.map((skill) => ({
|
|
72299
|
+
ident: skill.ident,
|
|
72300
|
+
files: skill.files.map((file) => ({
|
|
72301
|
+
relativePath: file.withinIdent,
|
|
72302
|
+
sourceAgent: file.winner.agent,
|
|
72303
|
+
sourceMtimeMs: file.winner.mtimeMs,
|
|
72304
|
+
size: file.winner.size
|
|
72305
|
+
})),
|
|
72306
|
+
sha256: skillSha256(skill)
|
|
72307
|
+
}));
|
|
72308
|
+
const base2 = {
|
|
72309
|
+
stationId: options.stationId,
|
|
72310
|
+
mode: "dry-run",
|
|
72311
|
+
cacheRoot,
|
|
72312
|
+
snapshotRoot: snapshotRootFor(repoRoot, options.stationId),
|
|
72313
|
+
sourceSnapshotSha: plan.sourceSnapshotSha,
|
|
72314
|
+
stats: {
|
|
72315
|
+
idents: plan.winners.length,
|
|
72316
|
+
files: plan.totalFiles,
|
|
72317
|
+
bytes: plan.totalBytes
|
|
72318
|
+
},
|
|
72319
|
+
winners: plan.winners,
|
|
72320
|
+
skills: resultSkills
|
|
72321
|
+
};
|
|
72322
|
+
if (options.dryRun !== false) {
|
|
72323
|
+
return base2;
|
|
72324
|
+
}
|
|
72325
|
+
const conflicts = [];
|
|
72326
|
+
const toWrite = [];
|
|
72327
|
+
for (const skill of plan.winners) {
|
|
72328
|
+
for (const file of skill.files) {
|
|
72329
|
+
const destination = join30(cacheRoot, skill.ident, file.withinIdent);
|
|
72330
|
+
const digest = sha256File(file.winner.fullPath);
|
|
72331
|
+
let existingDigest = null;
|
|
72332
|
+
try {
|
|
72333
|
+
existingDigest = sha256File(destination);
|
|
72334
|
+
} catch {}
|
|
72335
|
+
if (existingDigest !== null) {
|
|
72336
|
+
if (existingDigest === digest) {
|
|
72337
|
+
continue;
|
|
72338
|
+
}
|
|
72339
|
+
conflicts.push(`existing destination differs from snapshot winner: ${destination}`);
|
|
72340
|
+
continue;
|
|
72341
|
+
}
|
|
72342
|
+
toWrite.push({ destination, fullPath: file.winner.fullPath });
|
|
72343
|
+
}
|
|
72344
|
+
}
|
|
72345
|
+
if (conflicts.length > 0) {
|
|
72346
|
+
fail2("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
|
|
72347
|
+
}
|
|
72348
|
+
let written = 0;
|
|
72349
|
+
for (const entry of toWrite) {
|
|
72350
|
+
mkdirSync17(dirname12(entry.destination), { recursive: true });
|
|
72351
|
+
copyFileSync3(entry.fullPath, entry.destination);
|
|
72352
|
+
written += 1;
|
|
72353
|
+
}
|
|
72354
|
+
const unchanged = plan.totalFiles - written;
|
|
72355
|
+
const hydration = {
|
|
72356
|
+
schema: STATION_HYDRATION_MANIFEST_SCHEMA,
|
|
72357
|
+
stationId: options.stationId,
|
|
72358
|
+
hydratedAt: new Date().toISOString(),
|
|
72359
|
+
producer: STATION_HYDRATION_PRODUCER,
|
|
72360
|
+
sourceSnapshotSha: plan.sourceSnapshotSha,
|
|
72361
|
+
cacheRoot,
|
|
72362
|
+
stats: {
|
|
72363
|
+
idents: plan.winners.length,
|
|
72364
|
+
written,
|
|
72365
|
+
unchanged,
|
|
72366
|
+
files: plan.totalFiles,
|
|
72367
|
+
bytes: plan.totalBytes
|
|
72368
|
+
},
|
|
72369
|
+
skills: resultSkills
|
|
72370
|
+
};
|
|
72371
|
+
const hydrationManifestPath = join30(dirname12(cacheRoot), `hydration-${options.stationId}.json`);
|
|
72372
|
+
mkdirSync17(dirname12(hydrationManifestPath), { recursive: true });
|
|
72373
|
+
writeFileSync18(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
72374
|
+
`);
|
|
72375
|
+
return {
|
|
72376
|
+
...base2,
|
|
72377
|
+
mode: "apply",
|
|
72378
|
+
stats: { ...base2.stats, written, unchanged },
|
|
72379
|
+
manifestPath: hydrationManifestPath
|
|
72380
|
+
};
|
|
72381
|
+
}
|
|
72382
|
+
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1", STATION_HYDRATION_PRODUCER, MANIFEST_HASH_KEY_SEP;
|
|
72383
|
+
var init_station_hydrate = __esm(() => {
|
|
72384
|
+
init_package();
|
|
72385
|
+
init_agent_sync();
|
|
72386
|
+
init_home_migration();
|
|
72387
|
+
init_portable_snapshot_filter();
|
|
72388
|
+
init_station_snapshot();
|
|
72389
|
+
STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
72390
|
+
MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
72391
|
+
});
|
|
72392
|
+
|
|
72393
|
+
// src/cli/commands/hydrate.ts
|
|
72394
|
+
var exports_hydrate = {};
|
|
72395
|
+
__export(exports_hydrate, {
|
|
72396
|
+
registerHydrate: () => registerHydrate
|
|
72397
|
+
});
|
|
72398
|
+
function registerHydrate(parent) {
|
|
72399
|
+
parent.command("hydrate").description("Hydrate the canonical dedup corpus cache from a reviewed per-station skills snapshot").requiredOption("--station <id>", "Station id (slug) naming the snapshot under resources/<id>/skills").option("--apply", "Write into the corpus cache (the default is dry-run)", false).option("--dry-run", "Report without writing anything (the default)", false).option("--cache-root <dir>", "Override the destination corpus cache (used to stage another station's cache before rsync)").option("--repo-root <path>", "Repo root holding resources/<station>/skills (default: cwd)").action((options) => {
|
|
72400
|
+
handleHydrate(options);
|
|
72401
|
+
});
|
|
72402
|
+
}
|
|
72403
|
+
function handleHydrate(options) {
|
|
72404
|
+
if (options.apply && options.dryRun) {
|
|
72405
|
+
console.error(source_default.red("--apply and --dry-run are mutually exclusive"));
|
|
72406
|
+
process.exitCode = 2;
|
|
72407
|
+
return;
|
|
72408
|
+
}
|
|
72409
|
+
let result2;
|
|
72410
|
+
try {
|
|
72411
|
+
result2 = writeStationHydration({
|
|
72412
|
+
stationId: options.station,
|
|
72413
|
+
repoRoot: options.repoRoot,
|
|
72414
|
+
cacheRoot: options.cacheRoot,
|
|
72415
|
+
dryRun: !options.apply
|
|
72416
|
+
});
|
|
72417
|
+
} catch (error2) {
|
|
72418
|
+
if (error2 instanceof StationSnapshotError) {
|
|
72419
|
+
for (const line of error2.detail)
|
|
72420
|
+
console.error(`CONFLICT ${line}`);
|
|
72421
|
+
console.error(`FAIL ${error2.message}`);
|
|
72422
|
+
process.exitCode = 2;
|
|
72423
|
+
} else {
|
|
72424
|
+
console.error(`FAIL ${error2.stack ?? error2.message}`);
|
|
72425
|
+
process.exitCode = 1;
|
|
72426
|
+
}
|
|
72427
|
+
return;
|
|
72428
|
+
}
|
|
72429
|
+
if (result2.mode === "dry-run") {
|
|
72430
|
+
console.log(`DRY-RUN station=${result2.stationId} idents=${result2.stats.idents} files=${result2.stats.files} bytes=${result2.stats.bytes}`);
|
|
72431
|
+
console.log(` cache-root=${result2.cacheRoot} snapshot-sha=${result2.sourceSnapshotSha.slice(0, 12)}`);
|
|
72432
|
+
for (const skill of result2.winners) {
|
|
72433
|
+
const merged = skill.files.length > 1 ? ` (${skill.files.length} files, alternates: ${skill.files.map((file) => file.alternates.length > 0 ? `${file.withinIdent}<-${file.alternates.join(",")}` : null).filter(Boolean).join("; ") || "none"})` : "";
|
|
72434
|
+
console.log(` ${skill.ident}${merged}`);
|
|
72435
|
+
}
|
|
72436
|
+
return;
|
|
72437
|
+
}
|
|
72438
|
+
console.log(`HYDRATE station=${result2.stationId} idents=${result2.stats.idents} written=${result2.stats.written} unchanged=${result2.stats.unchanged} files=${result2.stats.files} bytes=${result2.stats.bytes}`);
|
|
72439
|
+
console.log(` manifest=${result2.manifestPath} snapshot-sha=${result2.sourceSnapshotSha}`);
|
|
72440
|
+
}
|
|
72441
|
+
var init_hydrate = __esm(() => {
|
|
72442
|
+
init_source();
|
|
72443
|
+
init_station_snapshot();
|
|
72444
|
+
init_station_hydrate();
|
|
71704
72445
|
});
|
|
71705
72446
|
|
|
71706
72447
|
// src/cli/commands/portable-skills.ts
|
|
@@ -72031,8 +72772,8 @@ var init_schedule = __esm(() => {
|
|
|
72031
72772
|
});
|
|
72032
72773
|
|
|
72033
72774
|
// src/lib/registry-sync.ts
|
|
72034
|
-
import { mkdirSync as
|
|
72035
|
-
import { dirname as
|
|
72775
|
+
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync19 } from "fs";
|
|
72776
|
+
import { dirname as dirname13, relative as relative5 } from "path";
|
|
72036
72777
|
function createRegistrySyncArtifact(options = {}) {
|
|
72037
72778
|
const profile = options.profile ?? "all";
|
|
72038
72779
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -72044,7 +72785,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
72044
72785
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
72045
72786
|
const skills = registry2.map((skill) => {
|
|
72046
72787
|
const skillPath = getSkillPath(skill.name);
|
|
72047
|
-
const directory =
|
|
72788
|
+
const directory = relative5(process.cwd(), skillPath) || skillPath;
|
|
72048
72789
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
72049
72790
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
72050
72791
|
return {
|
|
@@ -72089,8 +72830,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
72089
72830
|
};
|
|
72090
72831
|
}
|
|
72091
72832
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
72092
|
-
|
|
72093
|
-
|
|
72833
|
+
mkdirSync18(dirname13(path), { recursive: true });
|
|
72834
|
+
writeFileSync19(path, `${JSON.stringify(artifact, null, 2)}
|
|
72094
72835
|
`);
|
|
72095
72836
|
}
|
|
72096
72837
|
function buildDocs(name) {
|
|
@@ -72109,7 +72850,7 @@ var init_registry_sync = __esm(() => {
|
|
|
72109
72850
|
});
|
|
72110
72851
|
|
|
72111
72852
|
// src/lib/revision.ts
|
|
72112
|
-
import { createHash as
|
|
72853
|
+
import { createHash as createHash6 } from "crypto";
|
|
72113
72854
|
function revisionIdOf(content) {
|
|
72114
72855
|
const canonical = JSON.stringify({
|
|
72115
72856
|
slug: content.slug,
|
|
@@ -72124,7 +72865,7 @@ function revisionIdOf(content) {
|
|
|
72124
72865
|
bundleSha256: content.bundleSha256 ?? null,
|
|
72125
72866
|
bundleByteSize: content.bundleByteSize ?? null
|
|
72126
72867
|
});
|
|
72127
|
-
return
|
|
72868
|
+
return createHash6("sha256").update(canonical).digest("hex");
|
|
72128
72869
|
}
|
|
72129
72870
|
var REVISION_ID_PATTERN;
|
|
72130
72871
|
var init_revision = __esm(() => {
|
|
@@ -72132,9 +72873,9 @@ var init_revision = __esm(() => {
|
|
|
72132
72873
|
});
|
|
72133
72874
|
|
|
72134
72875
|
// src/lib/skill-bundle.ts
|
|
72135
|
-
import { createHash as
|
|
72136
|
-
import { readFileSync as
|
|
72137
|
-
import { join as
|
|
72876
|
+
import { createHash as createHash7 } from "crypto";
|
|
72877
|
+
import { readFileSync as readFileSync24, readdirSync as readdirSync17, statSync as statSync18 } from "fs";
|
|
72878
|
+
import { join as join31, relative as relative6 } from "path";
|
|
72138
72879
|
function isDotenvFile(lower) {
|
|
72139
72880
|
if (lower === ".env" || lower.startsWith(".env."))
|
|
72140
72881
|
return true;
|
|
@@ -72163,7 +72904,7 @@ function ownBytes(view) {
|
|
|
72163
72904
|
return out;
|
|
72164
72905
|
}
|
|
72165
72906
|
function sha256Hex(bytes) {
|
|
72166
|
-
return
|
|
72907
|
+
return createHash7("sha256").update(bytes).digest("hex");
|
|
72167
72908
|
}
|
|
72168
72909
|
function collectSkillBundleEntries(dir) {
|
|
72169
72910
|
const entries = [];
|
|
@@ -72171,9 +72912,9 @@ function collectSkillBundleEntries(dir) {
|
|
|
72171
72912
|
return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
72172
72913
|
}
|
|
72173
72914
|
function walk(root, current, out) {
|
|
72174
|
-
for (const entry of
|
|
72175
|
-
const absolute =
|
|
72176
|
-
const rel =
|
|
72915
|
+
for (const entry of readdirSync17(current, { withFileTypes: true })) {
|
|
72916
|
+
const absolute = join31(current, entry.name);
|
|
72917
|
+
const rel = relative6(root, absolute).split("\\").join("/");
|
|
72177
72918
|
const isRootLevel = !rel.includes("/");
|
|
72178
72919
|
if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
|
|
72179
72920
|
continue;
|
|
@@ -72193,10 +72934,10 @@ function walk(root, current, out) {
|
|
|
72193
72934
|
continue;
|
|
72194
72935
|
if (isCredentialFile(entry.name))
|
|
72195
72936
|
continue;
|
|
72196
|
-
const stats =
|
|
72937
|
+
const stats = statSync18(absolute);
|
|
72197
72938
|
out.push({
|
|
72198
72939
|
path: rel,
|
|
72199
|
-
bytes: ownBytes(
|
|
72940
|
+
bytes: ownBytes(readFileSync24(absolute)),
|
|
72200
72941
|
mode: stats.mode & 64 ? 493 : 420
|
|
72201
72942
|
});
|
|
72202
72943
|
}
|
|
@@ -72482,8 +73223,8 @@ var init_skill_bundles = __esm(() => {
|
|
|
72482
73223
|
});
|
|
72483
73224
|
|
|
72484
73225
|
// src/lib/pull.ts
|
|
72485
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
72486
|
-
import { dirname as
|
|
73226
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync20 } from "fs";
|
|
73227
|
+
import { dirname as dirname14, join as join32 } from "path";
|
|
72487
73228
|
async function pullSkills(options = {}) {
|
|
72488
73229
|
const client = options.client !== undefined ? options.client : createRemoteSkillsClient();
|
|
72489
73230
|
if (!client) {
|
|
@@ -72528,7 +73269,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
72528
73269
|
return reconcileTombstone(slug, corpusOptions);
|
|
72529
73270
|
}
|
|
72530
73271
|
if (bundleResponse.status === 404) {
|
|
72531
|
-
const marker = readPullMarker(
|
|
73272
|
+
const marker = readPullMarker(join32(getPortableSkillsRoot(corpusOptions), slug));
|
|
72532
73273
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
72533
73274
|
return { name: slug, success: true, purged: true, removed: false };
|
|
72534
73275
|
}
|
|
@@ -72559,7 +73300,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
72559
73300
|
return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
|
|
72560
73301
|
}
|
|
72561
73302
|
if (!meta?.revisionId) {
|
|
72562
|
-
const marker = readPullMarker(
|
|
73303
|
+
const marker = readPullMarker(join32(getPortableSkillsRoot(corpusOptions), slug));
|
|
72563
73304
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
72564
73305
|
return { name: slug, success: true, purged: true, removed: false };
|
|
72565
73306
|
}
|
|
@@ -72616,8 +73357,8 @@ function provenRevision(meta, slug, bundle) {
|
|
|
72616
73357
|
return declared;
|
|
72617
73358
|
}
|
|
72618
73359
|
function reconcileTombstone(slug, corpusOptions) {
|
|
72619
|
-
const target =
|
|
72620
|
-
if (!existsSync28(
|
|
73360
|
+
const target = join32(getPortableSkillsRoot(corpusOptions), slug);
|
|
73361
|
+
if (!existsSync28(join32(target, PULL_MARKER_FILE))) {
|
|
72621
73362
|
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
72622
73363
|
}
|
|
72623
73364
|
rmSync6(target, { recursive: true, force: true });
|
|
@@ -72625,7 +73366,7 @@ function reconcileTombstone(slug, corpusOptions) {
|
|
|
72625
73366
|
}
|
|
72626
73367
|
function readPullMarker(dir) {
|
|
72627
73368
|
try {
|
|
72628
|
-
return JSON.parse(
|
|
73369
|
+
return JSON.parse(readFileSync25(join32(dir, PULL_MARKER_FILE), "utf-8"));
|
|
72629
73370
|
} catch {
|
|
72630
73371
|
return null;
|
|
72631
73372
|
}
|
|
@@ -72745,17 +73486,17 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
72745
73486
|
}
|
|
72746
73487
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
72747
73488
|
const root = getPortableSkillsRoot(options);
|
|
72748
|
-
|
|
72749
|
-
const target =
|
|
73489
|
+
mkdirSync19(root, { recursive: true });
|
|
73490
|
+
const target = join32(root, name);
|
|
72750
73491
|
const created = !existsSync28(target);
|
|
72751
|
-
const staging = mkdtempSync3(
|
|
73492
|
+
const staging = mkdtempSync3(join32(root, `.pull-${name}-`));
|
|
72752
73493
|
let moved = false;
|
|
72753
73494
|
let backup = null;
|
|
72754
73495
|
try {
|
|
72755
73496
|
for (const entry of entries) {
|
|
72756
|
-
const destination =
|
|
72757
|
-
|
|
72758
|
-
|
|
73497
|
+
const destination = join32(staging, entry.path);
|
|
73498
|
+
mkdirSync19(dirname14(destination), { recursive: true });
|
|
73499
|
+
writeFileSync20(destination, entry.bytes, { mode: entry.mode });
|
|
72759
73500
|
}
|
|
72760
73501
|
writePullMarker(staging, {
|
|
72761
73502
|
skill: name,
|
|
@@ -72766,8 +73507,8 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
72766
73507
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
72767
73508
|
});
|
|
72768
73509
|
if (existsSync28(target)) {
|
|
72769
|
-
backup = mkdtempSync3(
|
|
72770
|
-
renameSync4(target,
|
|
73510
|
+
backup = mkdtempSync3(join32(root, `.pull-backup-${name}-`));
|
|
73511
|
+
renameSync4(target, join32(backup, name));
|
|
72771
73512
|
moved = true;
|
|
72772
73513
|
}
|
|
72773
73514
|
renameSync4(staging, target);
|
|
@@ -72775,9 +73516,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
72775
73516
|
rmSync6(backup, { recursive: true, force: true });
|
|
72776
73517
|
} catch (error2) {
|
|
72777
73518
|
rmSync6(staging, { recursive: true, force: true });
|
|
72778
|
-
if (moved && backup && existsSync28(
|
|
73519
|
+
if (moved && backup && existsSync28(join32(backup, name))) {
|
|
72779
73520
|
try {
|
|
72780
|
-
renameSync4(
|
|
73521
|
+
renameSync4(join32(backup, name), target);
|
|
72781
73522
|
} catch {}
|
|
72782
73523
|
}
|
|
72783
73524
|
throw error2;
|
|
@@ -72796,7 +73537,7 @@ function writePullMarker(dir, record3) {
|
|
|
72796
73537
|
...record3.revisionId ? { revisionId: record3.revisionId } : {},
|
|
72797
73538
|
syncedAt: new Date().toISOString()
|
|
72798
73539
|
};
|
|
72799
|
-
|
|
73540
|
+
writeFileSync20(join32(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
72800
73541
|
`);
|
|
72801
73542
|
}
|
|
72802
73543
|
async function safeMeta(client, slug) {
|
|
@@ -72870,12 +73611,12 @@ function registerRegistry(parent) {
|
|
|
72870
73611
|
async function writeJson2(value, space) {
|
|
72871
73612
|
const text = `${JSON.stringify(value, null, space)}
|
|
72872
73613
|
`;
|
|
72873
|
-
await new Promise((
|
|
73614
|
+
await new Promise((resolve3, reject2) => {
|
|
72874
73615
|
process.stdout.write(text, (error2) => {
|
|
72875
73616
|
if (error2)
|
|
72876
73617
|
reject2(error2);
|
|
72877
73618
|
else
|
|
72878
|
-
|
|
73619
|
+
resolve3();
|
|
72879
73620
|
});
|
|
72880
73621
|
});
|
|
72881
73622
|
}
|
|
@@ -72967,8 +73708,8 @@ __export(exports_publish, {
|
|
|
72967
73708
|
pushSkill: () => pushSkill,
|
|
72968
73709
|
PushSkillError: () => PushSkillError
|
|
72969
73710
|
});
|
|
72970
|
-
import { existsSync as existsSync29, readFileSync as
|
|
72971
|
-
import { join as
|
|
73711
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
|
|
73712
|
+
import { join as join33 } from "path";
|
|
72972
73713
|
function registerPublish(parent) {
|
|
72973
73714
|
parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
|
|
72974
73715
|
try {
|
|
@@ -73009,8 +73750,8 @@ async function pushSkill(name, options = {}) {
|
|
|
73009
73750
|
}
|
|
73010
73751
|
const manifest = readPortableSkillManifest(skill.path, skill.name);
|
|
73011
73752
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
73012
|
-
const skillMdPath =
|
|
73013
|
-
const skillMd = existsSync29(skillMdPath) ?
|
|
73753
|
+
const skillMdPath = join33(skill.path, "SKILL.md");
|
|
73754
|
+
const skillMd = existsSync29(skillMdPath) ? readFileSync26(skillMdPath, "utf-8") : undefined;
|
|
73014
73755
|
const base2 = {
|
|
73015
73756
|
slug: skill.name,
|
|
73016
73757
|
path: skill.path,
|
|
@@ -73127,10 +73868,10 @@ __export(exports_auth, {
|
|
|
73127
73868
|
import { createInterface as createInterface2 } from "readline";
|
|
73128
73869
|
function prompt(question) {
|
|
73129
73870
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
73130
|
-
return new Promise((
|
|
73871
|
+
return new Promise((resolve3) => {
|
|
73131
73872
|
rl.question(question, (answer) => {
|
|
73132
73873
|
rl.close();
|
|
73133
|
-
|
|
73874
|
+
resolve3(answer.trim());
|
|
73134
73875
|
});
|
|
73135
73876
|
});
|
|
73136
73877
|
}
|
|
@@ -73280,7 +74021,7 @@ function printWhoami(payload) {
|
|
|
73280
74021
|
console.log(source_default.dim("(offline \u2014 showing cached info)"));
|
|
73281
74022
|
}
|
|
73282
74023
|
function sleep(ms) {
|
|
73283
|
-
return new Promise((
|
|
74024
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
73284
74025
|
}
|
|
73285
74026
|
function browserCommand(url) {
|
|
73286
74027
|
if (process.platform === "darwin")
|
|
@@ -73781,11 +74522,11 @@ var init_storage = __esm(() => {
|
|
|
73781
74522
|
});
|
|
73782
74523
|
|
|
73783
74524
|
// src/lib/registry-reconcile.ts
|
|
73784
|
-
import { existsSync as existsSync30, readFileSync as
|
|
73785
|
-
import { join as
|
|
74525
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, statSync as statSync19, writeFileSync as writeFileSync21 } from "fs";
|
|
74526
|
+
import { join as join34 } from "path";
|
|
73786
74527
|
function isDirectory2(path) {
|
|
73787
74528
|
try {
|
|
73788
|
-
return
|
|
74529
|
+
return statSync19(path).isDirectory();
|
|
73789
74530
|
} catch {
|
|
73790
74531
|
return false;
|
|
73791
74532
|
}
|
|
@@ -73793,15 +74534,15 @@ function isDirectory2(path) {
|
|
|
73793
74534
|
function migrationNeeded(options) {
|
|
73794
74535
|
if (options.rootDir)
|
|
73795
74536
|
return false;
|
|
73796
|
-
const appDir = options.homeDir ?
|
|
73797
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
74537
|
+
const appDir = options.homeDir ? join34(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
74538
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join34(appDir, SKILLS_CACHE_DIRNAME)));
|
|
73798
74539
|
}
|
|
73799
74540
|
function readBaseline(skillDir) {
|
|
73800
|
-
const markerPath =
|
|
74541
|
+
const markerPath = join34(skillDir, PULL_MARKER_FILE);
|
|
73801
74542
|
if (!existsSync30(markerPath))
|
|
73802
74543
|
return;
|
|
73803
74544
|
try {
|
|
73804
|
-
const marker = JSON.parse(
|
|
74545
|
+
const marker = JSON.parse(readFileSync27(markerPath, "utf-8"));
|
|
73805
74546
|
return {
|
|
73806
74547
|
...typeof marker.contentHash === "string" && marker.contentHash ? { contentHash: marker.contentHash } : {},
|
|
73807
74548
|
...typeof marker.version === "string" && marker.version ? { version: marker.version } : {}
|
|
@@ -73811,11 +74552,11 @@ function readBaseline(skillDir) {
|
|
|
73811
74552
|
}
|
|
73812
74553
|
}
|
|
73813
74554
|
function readCursor(root) {
|
|
73814
|
-
const path =
|
|
74555
|
+
const path = join34(root, SYNC_CURSOR_FILE);
|
|
73815
74556
|
if (!existsSync30(path))
|
|
73816
74557
|
return { runCount: 0 };
|
|
73817
74558
|
try {
|
|
73818
|
-
const cursor = JSON.parse(
|
|
74559
|
+
const cursor = JSON.parse(readFileSync27(path, "utf-8"));
|
|
73819
74560
|
return { runCount: typeof cursor.runCount === "number" ? cursor.runCount : 0 };
|
|
73820
74561
|
} catch {
|
|
73821
74562
|
return { runCount: 0 };
|
|
@@ -73824,12 +74565,12 @@ function readCursor(root) {
|
|
|
73824
74565
|
function resolveCorpusRootReadOnly(options) {
|
|
73825
74566
|
if (options.rootDir)
|
|
73826
74567
|
return { root: options.rootDir, migrationPending: false };
|
|
73827
|
-
const appDir = options.homeDir ?
|
|
73828
|
-
const cache3 =
|
|
74568
|
+
const appDir = options.homeDir ? join34(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
74569
|
+
const cache3 = join34(appDir, SKILLS_CACHE_DIRNAME);
|
|
73829
74570
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
73830
74571
|
return { root: cache3, migrationPending: false };
|
|
73831
74572
|
}
|
|
73832
|
-
return { root:
|
|
74573
|
+
return { root: join34(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
73833
74574
|
}
|
|
73834
74575
|
function remoteRowToSkill(record3) {
|
|
73835
74576
|
const slug = typeof record3.slug === "string" ? record3.slug : typeof record3.name === "string" ? record3.name : undefined;
|
|
@@ -73961,7 +74702,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
73961
74702
|
for (const slug of allSlugs) {
|
|
73962
74703
|
const local = locals.get(slug);
|
|
73963
74704
|
const remote = remotes.get(slug);
|
|
73964
|
-
const baseline = local ? readBaseline(
|
|
74705
|
+
const baseline = local ? readBaseline(join34(root, slug)) : undefined;
|
|
73965
74706
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
73966
74707
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
73967
74708
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -74020,7 +74761,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
74020
74761
|
try {
|
|
74021
74762
|
await pushSkill(slug, { rootDir: root, client });
|
|
74022
74763
|
const pushed = locals.get(slug);
|
|
74023
|
-
writePullMarker(
|
|
74764
|
+
writePullMarker(join34(root, slug), {
|
|
74024
74765
|
skill: slug,
|
|
74025
74766
|
...pushed?.version ? { version: pushed.version } : {},
|
|
74026
74767
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -74092,7 +74833,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
74092
74833
|
runCount: readCursor(root).runCount + 1,
|
|
74093
74834
|
summary
|
|
74094
74835
|
};
|
|
74095
|
-
|
|
74836
|
+
writeFileSync21(join34(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
|
|
74096
74837
|
`);
|
|
74097
74838
|
return {
|
|
74098
74839
|
corpusRoot: root,
|
|
@@ -81904,6 +82645,8 @@ var { registerCompletion: registerCompletion2 } = await Promise.resolve().then((
|
|
|
81904
82645
|
registerCompletion2(program2);
|
|
81905
82646
|
var { registerCreateSync: registerCreateSync2 } = await Promise.resolve().then(() => (init_create_sync_config(), exports_create_sync_config));
|
|
81906
82647
|
registerCreateSync2(program2);
|
|
82648
|
+
var { registerHydrate: registerHydrate2 } = await Promise.resolve().then(() => (init_hydrate(), exports_hydrate));
|
|
82649
|
+
registerHydrate2(program2);
|
|
81907
82650
|
var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Promise.resolve().then(() => (init_portable_skills2(), exports_portable_skills));
|
|
81908
82651
|
registerPortableSkillCommands2(program2);
|
|
81909
82652
|
var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
|