@hasna/skills 0.1.69 → 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/README.md +1 -1
- package/bin/index.js +825 -72
- package/bin/mcp.js +13 -3
- package/bin/server.js +12 -2
- package/bin/worker.js +8 -0
- package/dist/cli/commands/hydrate.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +636 -2
- 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/dist/sdk/index.js +8 -0
- package/package.json +4 -2
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: {
|
|
@@ -36912,6 +36912,7 @@ var init_package = __esm(() => {
|
|
|
36912
36912
|
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
36913
36913
|
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
36914
36914
|
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
36915
|
+
"docker:check": "bash scripts/docker-build-check.sh",
|
|
36915
36916
|
migrate: "bun run ./src/server/migrate.ts",
|
|
36916
36917
|
typecheck: "tsc --noEmit",
|
|
36917
36918
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
@@ -36944,7 +36945,8 @@ var init_package = __esm(() => {
|
|
|
36944
36945
|
"@types/react": "^18.2.0",
|
|
36945
36946
|
"bun-types": "1.3.14",
|
|
36946
36947
|
"react-devtools-core": "^7.0.1",
|
|
36947
|
-
typescript: "^5"
|
|
36948
|
+
typescript: "^5",
|
|
36949
|
+
yaml: "^2.9.0"
|
|
36948
36950
|
},
|
|
36949
36951
|
dependencies: {
|
|
36950
36952
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
@@ -38320,6 +38322,14 @@ var init_development_tools = __esm(() => {
|
|
|
38320
38322
|
category: "Development Tools",
|
|
38321
38323
|
tags: ["config", "validation", "schema", "linting"]
|
|
38322
38324
|
},
|
|
38325
|
+
{
|
|
38326
|
+
name: "oss-app-two-backend-storage",
|
|
38327
|
+
displayName: "OSS App Two-Backend Storage",
|
|
38328
|
+
description: "Recipe for the Hasna two-backend storage contract: client transport + HTTP store, server PG/SQLite backend, pg-migrations + apply script, fail-closed URL-without-key, bun bins, contract manifest, Dockerfile",
|
|
38329
|
+
category: "Development Tools",
|
|
38330
|
+
tags: ["storage", "backend", "postgresql", "sqlite", "two-backend", "oss-app"],
|
|
38331
|
+
kind: "instruction"
|
|
38332
|
+
},
|
|
38323
38333
|
{
|
|
38324
38334
|
name: "session-inject-monitor",
|
|
38325
38335
|
displayName: "Session Inject Monitor",
|
|
@@ -69906,7 +69916,7 @@ var init_server3 = __esm(() => {
|
|
|
69906
69916
|
server = buildServer();
|
|
69907
69917
|
});
|
|
69908
69918
|
|
|
69909
|
-
// ../../node_modules/.bun/@hono+node-server@1.19.17+
|
|
69919
|
+
// ../../node_modules/.bun/@hono+node-server@1.19.17+2145b681a064c8e9/node_modules/@hono/node-server/dist/index.mjs
|
|
69910
69920
|
import { Readable } from "stream";
|
|
69911
69921
|
import crypto2 from "crypto";
|
|
69912
69922
|
var GlobalRequest, Request, newHeadersFromIncoming = (incoming) => {
|
|
@@ -71385,13 +71395,344 @@ var init_completion = __esm(() => {
|
|
|
71385
71395
|
categoryNames = CATEGORIES.map((c) => c);
|
|
71386
71396
|
});
|
|
71387
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
|
+
|
|
71388
71729
|
// src/cli/commands/create-sync-config.ts
|
|
71389
71730
|
var exports_create_sync_config = {};
|
|
71390
71731
|
__export(exports_create_sync_config, {
|
|
71391
71732
|
registerCreateSync: () => registerCreateSync
|
|
71392
71733
|
});
|
|
71393
|
-
import { existsSync as existsSync27, writeFileSync as
|
|
71394
|
-
import { join as
|
|
71734
|
+
import { existsSync as existsSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16 } from "fs";
|
|
71735
|
+
import { join as join29 } from "path";
|
|
71395
71736
|
function registerCreateSync(parent) {
|
|
71396
71737
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
71397
71738
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -71466,13 +71807,13 @@ function registerCreateSync(parent) {
|
|
|
71466
71807
|
console.log(`${source_default.cyan("project")}: ${pp}${existsSync27(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
71467
71808
|
});
|
|
71468
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));
|
|
71469
|
-
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));
|
|
71470
71811
|
}
|
|
71471
71812
|
function handleCreate(name, options) {
|
|
71472
71813
|
const bare = name.trim();
|
|
71473
71814
|
const dirName = bare;
|
|
71474
71815
|
const baseDir = getPortableSkillsRoot();
|
|
71475
|
-
const skillDir =
|
|
71816
|
+
const skillDir = join29(baseDir, dirName);
|
|
71476
71817
|
if (existsSync27(skillDir)) {
|
|
71477
71818
|
console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : source_default.red(`Skill '${bare}' already exists at ${skillDir}`));
|
|
71478
71819
|
process.exitCode = 1;
|
|
@@ -71481,8 +71822,8 @@ function handleCreate(name, options) {
|
|
|
71481
71822
|
const description = options.description || `${bare} skill`;
|
|
71482
71823
|
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
|
|
71483
71824
|
const displayName2 = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
71484
|
-
|
|
71485
|
-
|
|
71825
|
+
mkdirSync16(join29(skillDir, "src"), { recursive: true });
|
|
71826
|
+
writeFileSync17(join29(skillDir, "SKILL.md"), [
|
|
71486
71827
|
"---",
|
|
71487
71828
|
`name: ${bare}`,
|
|
71488
71829
|
`description: ${description}`,
|
|
@@ -71502,11 +71843,11 @@ function handleCreate(name, options) {
|
|
|
71502
71843
|
""
|
|
71503
71844
|
].join(`
|
|
71504
71845
|
`));
|
|
71505
|
-
|
|
71846
|
+
writeFileSync17(join29(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName2} \u2014 ${description}`, ` */`, "", `console.log("${displayName2}");`, ""].join(`
|
|
71506
71847
|
`));
|
|
71507
|
-
|
|
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) + `
|
|
71508
71849
|
`);
|
|
71509
|
-
|
|
71850
|
+
writeFileSync17(join29(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
|
|
71510
71851
|
`);
|
|
71511
71852
|
clearRegistryCache();
|
|
71512
71853
|
if (options.json)
|
|
@@ -71515,11 +71856,15 @@ function handleCreate(name, options) {
|
|
|
71515
71856
|
console.log(source_default.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
|
|
71516
71857
|
console.log(source_default.dim(` Category: ${options.category}`));
|
|
71517
71858
|
console.log(source_default.dim(` Tags: ${tags.join(", ")}`));
|
|
71518
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
71519
|
-
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")}`);
|
|
71520
71861
|
}
|
|
71521
71862
|
}
|
|
71522
71863
|
function handleSync(names, options) {
|
|
71864
|
+
if (options.station) {
|
|
71865
|
+
handleStationSnapshot(names, options);
|
|
71866
|
+
return;
|
|
71867
|
+
}
|
|
71523
71868
|
const modes = [options.check, options.adopt, options.prune].filter(Boolean).length;
|
|
71524
71869
|
if (modes > 1) {
|
|
71525
71870
|
const message = "--check, --adopt, and --prune are mutually exclusive";
|
|
@@ -71580,6 +71925,68 @@ function handleSync(names, options) {
|
|
|
71580
71925
|
process.exitCode = 1;
|
|
71581
71926
|
}
|
|
71582
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
|
+
}
|
|
71583
71990
|
function handleSyncCheck(json) {
|
|
71584
71991
|
const census = censusHomeDrift();
|
|
71585
71992
|
if (json) {
|
|
@@ -71691,6 +72098,350 @@ var init_create_sync_config = __esm(() => {
|
|
|
71691
72098
|
init_agent_sync();
|
|
71692
72099
|
init_home_adoption();
|
|
71693
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();
|
|
71694
72445
|
});
|
|
71695
72446
|
|
|
71696
72447
|
// src/cli/commands/portable-skills.ts
|
|
@@ -72021,8 +72772,8 @@ var init_schedule = __esm(() => {
|
|
|
72021
72772
|
});
|
|
72022
72773
|
|
|
72023
72774
|
// src/lib/registry-sync.ts
|
|
72024
|
-
import { mkdirSync as
|
|
72025
|
-
import { dirname as
|
|
72775
|
+
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync19 } from "fs";
|
|
72776
|
+
import { dirname as dirname13, relative as relative5 } from "path";
|
|
72026
72777
|
function createRegistrySyncArtifact(options = {}) {
|
|
72027
72778
|
const profile = options.profile ?? "all";
|
|
72028
72779
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -72034,7 +72785,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
72034
72785
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
72035
72786
|
const skills = registry2.map((skill) => {
|
|
72036
72787
|
const skillPath = getSkillPath(skill.name);
|
|
72037
|
-
const directory =
|
|
72788
|
+
const directory = relative5(process.cwd(), skillPath) || skillPath;
|
|
72038
72789
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
72039
72790
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
72040
72791
|
return {
|
|
@@ -72079,8 +72830,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
72079
72830
|
};
|
|
72080
72831
|
}
|
|
72081
72832
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
72082
|
-
|
|
72083
|
-
|
|
72833
|
+
mkdirSync18(dirname13(path), { recursive: true });
|
|
72834
|
+
writeFileSync19(path, `${JSON.stringify(artifact, null, 2)}
|
|
72084
72835
|
`);
|
|
72085
72836
|
}
|
|
72086
72837
|
function buildDocs(name) {
|
|
@@ -72099,7 +72850,7 @@ var init_registry_sync = __esm(() => {
|
|
|
72099
72850
|
});
|
|
72100
72851
|
|
|
72101
72852
|
// src/lib/revision.ts
|
|
72102
|
-
import { createHash as
|
|
72853
|
+
import { createHash as createHash6 } from "crypto";
|
|
72103
72854
|
function revisionIdOf(content) {
|
|
72104
72855
|
const canonical = JSON.stringify({
|
|
72105
72856
|
slug: content.slug,
|
|
@@ -72114,7 +72865,7 @@ function revisionIdOf(content) {
|
|
|
72114
72865
|
bundleSha256: content.bundleSha256 ?? null,
|
|
72115
72866
|
bundleByteSize: content.bundleByteSize ?? null
|
|
72116
72867
|
});
|
|
72117
|
-
return
|
|
72868
|
+
return createHash6("sha256").update(canonical).digest("hex");
|
|
72118
72869
|
}
|
|
72119
72870
|
var REVISION_ID_PATTERN;
|
|
72120
72871
|
var init_revision = __esm(() => {
|
|
@@ -72122,9 +72873,9 @@ var init_revision = __esm(() => {
|
|
|
72122
72873
|
});
|
|
72123
72874
|
|
|
72124
72875
|
// src/lib/skill-bundle.ts
|
|
72125
|
-
import { createHash as
|
|
72126
|
-
import { readFileSync as
|
|
72127
|
-
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";
|
|
72128
72879
|
function isDotenvFile(lower) {
|
|
72129
72880
|
if (lower === ".env" || lower.startsWith(".env."))
|
|
72130
72881
|
return true;
|
|
@@ -72153,7 +72904,7 @@ function ownBytes(view) {
|
|
|
72153
72904
|
return out;
|
|
72154
72905
|
}
|
|
72155
72906
|
function sha256Hex(bytes) {
|
|
72156
|
-
return
|
|
72907
|
+
return createHash7("sha256").update(bytes).digest("hex");
|
|
72157
72908
|
}
|
|
72158
72909
|
function collectSkillBundleEntries(dir) {
|
|
72159
72910
|
const entries = [];
|
|
@@ -72161,9 +72912,9 @@ function collectSkillBundleEntries(dir) {
|
|
|
72161
72912
|
return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
72162
72913
|
}
|
|
72163
72914
|
function walk(root, current, out) {
|
|
72164
|
-
for (const entry of
|
|
72165
|
-
const absolute =
|
|
72166
|
-
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("/");
|
|
72167
72918
|
const isRootLevel = !rel.includes("/");
|
|
72168
72919
|
if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
|
|
72169
72920
|
continue;
|
|
@@ -72183,10 +72934,10 @@ function walk(root, current, out) {
|
|
|
72183
72934
|
continue;
|
|
72184
72935
|
if (isCredentialFile(entry.name))
|
|
72185
72936
|
continue;
|
|
72186
|
-
const stats =
|
|
72937
|
+
const stats = statSync18(absolute);
|
|
72187
72938
|
out.push({
|
|
72188
72939
|
path: rel,
|
|
72189
|
-
bytes: ownBytes(
|
|
72940
|
+
bytes: ownBytes(readFileSync24(absolute)),
|
|
72190
72941
|
mode: stats.mode & 64 ? 493 : 420
|
|
72191
72942
|
});
|
|
72192
72943
|
}
|
|
@@ -72472,8 +73223,8 @@ var init_skill_bundles = __esm(() => {
|
|
|
72472
73223
|
});
|
|
72473
73224
|
|
|
72474
73225
|
// src/lib/pull.ts
|
|
72475
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
72476
|
-
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";
|
|
72477
73228
|
async function pullSkills(options = {}) {
|
|
72478
73229
|
const client = options.client !== undefined ? options.client : createRemoteSkillsClient();
|
|
72479
73230
|
if (!client) {
|
|
@@ -72518,7 +73269,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
72518
73269
|
return reconcileTombstone(slug, corpusOptions);
|
|
72519
73270
|
}
|
|
72520
73271
|
if (bundleResponse.status === 404) {
|
|
72521
|
-
const marker = readPullMarker(
|
|
73272
|
+
const marker = readPullMarker(join32(getPortableSkillsRoot(corpusOptions), slug));
|
|
72522
73273
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
72523
73274
|
return { name: slug, success: true, purged: true, removed: false };
|
|
72524
73275
|
}
|
|
@@ -72549,7 +73300,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
72549
73300
|
return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
|
|
72550
73301
|
}
|
|
72551
73302
|
if (!meta?.revisionId) {
|
|
72552
|
-
const marker = readPullMarker(
|
|
73303
|
+
const marker = readPullMarker(join32(getPortableSkillsRoot(corpusOptions), slug));
|
|
72553
73304
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
72554
73305
|
return { name: slug, success: true, purged: true, removed: false };
|
|
72555
73306
|
}
|
|
@@ -72606,8 +73357,8 @@ function provenRevision(meta, slug, bundle) {
|
|
|
72606
73357
|
return declared;
|
|
72607
73358
|
}
|
|
72608
73359
|
function reconcileTombstone(slug, corpusOptions) {
|
|
72609
|
-
const target =
|
|
72610
|
-
if (!existsSync28(
|
|
73360
|
+
const target = join32(getPortableSkillsRoot(corpusOptions), slug);
|
|
73361
|
+
if (!existsSync28(join32(target, PULL_MARKER_FILE))) {
|
|
72611
73362
|
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
72612
73363
|
}
|
|
72613
73364
|
rmSync6(target, { recursive: true, force: true });
|
|
@@ -72615,7 +73366,7 @@ function reconcileTombstone(slug, corpusOptions) {
|
|
|
72615
73366
|
}
|
|
72616
73367
|
function readPullMarker(dir) {
|
|
72617
73368
|
try {
|
|
72618
|
-
return JSON.parse(
|
|
73369
|
+
return JSON.parse(readFileSync25(join32(dir, PULL_MARKER_FILE), "utf-8"));
|
|
72619
73370
|
} catch {
|
|
72620
73371
|
return null;
|
|
72621
73372
|
}
|
|
@@ -72735,17 +73486,17 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
72735
73486
|
}
|
|
72736
73487
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
72737
73488
|
const root = getPortableSkillsRoot(options);
|
|
72738
|
-
|
|
72739
|
-
const target =
|
|
73489
|
+
mkdirSync19(root, { recursive: true });
|
|
73490
|
+
const target = join32(root, name);
|
|
72740
73491
|
const created = !existsSync28(target);
|
|
72741
|
-
const staging = mkdtempSync3(
|
|
73492
|
+
const staging = mkdtempSync3(join32(root, `.pull-${name}-`));
|
|
72742
73493
|
let moved = false;
|
|
72743
73494
|
let backup = null;
|
|
72744
73495
|
try {
|
|
72745
73496
|
for (const entry of entries) {
|
|
72746
|
-
const destination =
|
|
72747
|
-
|
|
72748
|
-
|
|
73497
|
+
const destination = join32(staging, entry.path);
|
|
73498
|
+
mkdirSync19(dirname14(destination), { recursive: true });
|
|
73499
|
+
writeFileSync20(destination, entry.bytes, { mode: entry.mode });
|
|
72749
73500
|
}
|
|
72750
73501
|
writePullMarker(staging, {
|
|
72751
73502
|
skill: name,
|
|
@@ -72756,8 +73507,8 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
72756
73507
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
72757
73508
|
});
|
|
72758
73509
|
if (existsSync28(target)) {
|
|
72759
|
-
backup = mkdtempSync3(
|
|
72760
|
-
renameSync4(target,
|
|
73510
|
+
backup = mkdtempSync3(join32(root, `.pull-backup-${name}-`));
|
|
73511
|
+
renameSync4(target, join32(backup, name));
|
|
72761
73512
|
moved = true;
|
|
72762
73513
|
}
|
|
72763
73514
|
renameSync4(staging, target);
|
|
@@ -72765,9 +73516,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
72765
73516
|
rmSync6(backup, { recursive: true, force: true });
|
|
72766
73517
|
} catch (error2) {
|
|
72767
73518
|
rmSync6(staging, { recursive: true, force: true });
|
|
72768
|
-
if (moved && backup && existsSync28(
|
|
73519
|
+
if (moved && backup && existsSync28(join32(backup, name))) {
|
|
72769
73520
|
try {
|
|
72770
|
-
renameSync4(
|
|
73521
|
+
renameSync4(join32(backup, name), target);
|
|
72771
73522
|
} catch {}
|
|
72772
73523
|
}
|
|
72773
73524
|
throw error2;
|
|
@@ -72786,7 +73537,7 @@ function writePullMarker(dir, record3) {
|
|
|
72786
73537
|
...record3.revisionId ? { revisionId: record3.revisionId } : {},
|
|
72787
73538
|
syncedAt: new Date().toISOString()
|
|
72788
73539
|
};
|
|
72789
|
-
|
|
73540
|
+
writeFileSync20(join32(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
72790
73541
|
`);
|
|
72791
73542
|
}
|
|
72792
73543
|
async function safeMeta(client, slug) {
|
|
@@ -72860,12 +73611,12 @@ function registerRegistry(parent) {
|
|
|
72860
73611
|
async function writeJson2(value, space) {
|
|
72861
73612
|
const text = `${JSON.stringify(value, null, space)}
|
|
72862
73613
|
`;
|
|
72863
|
-
await new Promise((
|
|
73614
|
+
await new Promise((resolve3, reject2) => {
|
|
72864
73615
|
process.stdout.write(text, (error2) => {
|
|
72865
73616
|
if (error2)
|
|
72866
73617
|
reject2(error2);
|
|
72867
73618
|
else
|
|
72868
|
-
|
|
73619
|
+
resolve3();
|
|
72869
73620
|
});
|
|
72870
73621
|
});
|
|
72871
73622
|
}
|
|
@@ -72957,8 +73708,8 @@ __export(exports_publish, {
|
|
|
72957
73708
|
pushSkill: () => pushSkill,
|
|
72958
73709
|
PushSkillError: () => PushSkillError
|
|
72959
73710
|
});
|
|
72960
|
-
import { existsSync as existsSync29, readFileSync as
|
|
72961
|
-
import { join as
|
|
73711
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
|
|
73712
|
+
import { join as join33 } from "path";
|
|
72962
73713
|
function registerPublish(parent) {
|
|
72963
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) => {
|
|
72964
73715
|
try {
|
|
@@ -72999,8 +73750,8 @@ async function pushSkill(name, options = {}) {
|
|
|
72999
73750
|
}
|
|
73000
73751
|
const manifest = readPortableSkillManifest(skill.path, skill.name);
|
|
73001
73752
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
73002
|
-
const skillMdPath =
|
|
73003
|
-
const skillMd = existsSync29(skillMdPath) ?
|
|
73753
|
+
const skillMdPath = join33(skill.path, "SKILL.md");
|
|
73754
|
+
const skillMd = existsSync29(skillMdPath) ? readFileSync26(skillMdPath, "utf-8") : undefined;
|
|
73004
73755
|
const base2 = {
|
|
73005
73756
|
slug: skill.name,
|
|
73006
73757
|
path: skill.path,
|
|
@@ -73117,10 +73868,10 @@ __export(exports_auth, {
|
|
|
73117
73868
|
import { createInterface as createInterface2 } from "readline";
|
|
73118
73869
|
function prompt(question) {
|
|
73119
73870
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
73120
|
-
return new Promise((
|
|
73871
|
+
return new Promise((resolve3) => {
|
|
73121
73872
|
rl.question(question, (answer) => {
|
|
73122
73873
|
rl.close();
|
|
73123
|
-
|
|
73874
|
+
resolve3(answer.trim());
|
|
73124
73875
|
});
|
|
73125
73876
|
});
|
|
73126
73877
|
}
|
|
@@ -73270,7 +74021,7 @@ function printWhoami(payload) {
|
|
|
73270
74021
|
console.log(source_default.dim("(offline \u2014 showing cached info)"));
|
|
73271
74022
|
}
|
|
73272
74023
|
function sleep(ms) {
|
|
73273
|
-
return new Promise((
|
|
74024
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
73274
74025
|
}
|
|
73275
74026
|
function browserCommand(url) {
|
|
73276
74027
|
if (process.platform === "darwin")
|
|
@@ -73771,11 +74522,11 @@ var init_storage = __esm(() => {
|
|
|
73771
74522
|
});
|
|
73772
74523
|
|
|
73773
74524
|
// src/lib/registry-reconcile.ts
|
|
73774
|
-
import { existsSync as existsSync30, readFileSync as
|
|
73775
|
-
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";
|
|
73776
74527
|
function isDirectory2(path) {
|
|
73777
74528
|
try {
|
|
73778
|
-
return
|
|
74529
|
+
return statSync19(path).isDirectory();
|
|
73779
74530
|
} catch {
|
|
73780
74531
|
return false;
|
|
73781
74532
|
}
|
|
@@ -73783,15 +74534,15 @@ function isDirectory2(path) {
|
|
|
73783
74534
|
function migrationNeeded(options) {
|
|
73784
74535
|
if (options.rootDir)
|
|
73785
74536
|
return false;
|
|
73786
|
-
const appDir = options.homeDir ?
|
|
73787
|
-
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)));
|
|
73788
74539
|
}
|
|
73789
74540
|
function readBaseline(skillDir) {
|
|
73790
|
-
const markerPath =
|
|
74541
|
+
const markerPath = join34(skillDir, PULL_MARKER_FILE);
|
|
73791
74542
|
if (!existsSync30(markerPath))
|
|
73792
74543
|
return;
|
|
73793
74544
|
try {
|
|
73794
|
-
const marker = JSON.parse(
|
|
74545
|
+
const marker = JSON.parse(readFileSync27(markerPath, "utf-8"));
|
|
73795
74546
|
return {
|
|
73796
74547
|
...typeof marker.contentHash === "string" && marker.contentHash ? { contentHash: marker.contentHash } : {},
|
|
73797
74548
|
...typeof marker.version === "string" && marker.version ? { version: marker.version } : {}
|
|
@@ -73801,11 +74552,11 @@ function readBaseline(skillDir) {
|
|
|
73801
74552
|
}
|
|
73802
74553
|
}
|
|
73803
74554
|
function readCursor(root) {
|
|
73804
|
-
const path =
|
|
74555
|
+
const path = join34(root, SYNC_CURSOR_FILE);
|
|
73805
74556
|
if (!existsSync30(path))
|
|
73806
74557
|
return { runCount: 0 };
|
|
73807
74558
|
try {
|
|
73808
|
-
const cursor = JSON.parse(
|
|
74559
|
+
const cursor = JSON.parse(readFileSync27(path, "utf-8"));
|
|
73809
74560
|
return { runCount: typeof cursor.runCount === "number" ? cursor.runCount : 0 };
|
|
73810
74561
|
} catch {
|
|
73811
74562
|
return { runCount: 0 };
|
|
@@ -73814,12 +74565,12 @@ function readCursor(root) {
|
|
|
73814
74565
|
function resolveCorpusRootReadOnly(options) {
|
|
73815
74566
|
if (options.rootDir)
|
|
73816
74567
|
return { root: options.rootDir, migrationPending: false };
|
|
73817
|
-
const appDir = options.homeDir ?
|
|
73818
|
-
const cache3 =
|
|
74568
|
+
const appDir = options.homeDir ? join34(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
74569
|
+
const cache3 = join34(appDir, SKILLS_CACHE_DIRNAME);
|
|
73819
74570
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
73820
74571
|
return { root: cache3, migrationPending: false };
|
|
73821
74572
|
}
|
|
73822
|
-
return { root:
|
|
74573
|
+
return { root: join34(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
73823
74574
|
}
|
|
73824
74575
|
function remoteRowToSkill(record3) {
|
|
73825
74576
|
const slug = typeof record3.slug === "string" ? record3.slug : typeof record3.name === "string" ? record3.name : undefined;
|
|
@@ -73951,7 +74702,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
73951
74702
|
for (const slug of allSlugs) {
|
|
73952
74703
|
const local = locals.get(slug);
|
|
73953
74704
|
const remote = remotes.get(slug);
|
|
73954
|
-
const baseline = local ? readBaseline(
|
|
74705
|
+
const baseline = local ? readBaseline(join34(root, slug)) : undefined;
|
|
73955
74706
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
73956
74707
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
73957
74708
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -74010,7 +74761,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
74010
74761
|
try {
|
|
74011
74762
|
await pushSkill(slug, { rootDir: root, client });
|
|
74012
74763
|
const pushed = locals.get(slug);
|
|
74013
|
-
writePullMarker(
|
|
74764
|
+
writePullMarker(join34(root, slug), {
|
|
74014
74765
|
skill: slug,
|
|
74015
74766
|
...pushed?.version ? { version: pushed.version } : {},
|
|
74016
74767
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -74082,7 +74833,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
74082
74833
|
runCount: readCursor(root).runCount + 1,
|
|
74083
74834
|
summary
|
|
74084
74835
|
};
|
|
74085
|
-
|
|
74836
|
+
writeFileSync21(join34(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor, null, 2)}
|
|
74086
74837
|
`);
|
|
74087
74838
|
return {
|
|
74088
74839
|
corpusRoot: root,
|
|
@@ -81894,6 +82645,8 @@ var { registerCompletion: registerCompletion2 } = await Promise.resolve().then((
|
|
|
81894
82645
|
registerCompletion2(program2);
|
|
81895
82646
|
var { registerCreateSync: registerCreateSync2 } = await Promise.resolve().then(() => (init_create_sync_config(), exports_create_sync_config));
|
|
81896
82647
|
registerCreateSync2(program2);
|
|
82648
|
+
var { registerHydrate: registerHydrate2 } = await Promise.resolve().then(() => (init_hydrate(), exports_hydrate));
|
|
82649
|
+
registerHydrate2(program2);
|
|
81897
82650
|
var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Promise.resolve().then(() => (init_portable_skills2(), exports_portable_skills));
|
|
81898
82651
|
registerPortableSkillCommands2(program2);
|
|
81899
82652
|
var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
|