@hasna/instructions 0.4.36 → 0.4.42
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 +9 -8
- package/assets/skills/inbox/SKILL.md +86 -0
- package/dashboard/README.md +34 -70
- package/dist/cli/index.js +1673 -516
- package/dist/cli/raw-store-root.test.d.ts +2 -0
- package/dist/cli/raw-store-root.test.d.ts.map +1 -0
- package/dist/data/config-store.d.ts +4 -4
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/db/configs.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/generated/storage-kit/backend.d.ts +20 -0
- package/dist/generated/storage-kit/backend.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +2 -2
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/own.d.ts +11 -0
- package/dist/generated/storage-kit/own.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +7 -6
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/tls.d.ts +30 -3
- package/dist/generated/storage-kit/tls.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +945 -372
- package/dist/lib/instruction-graph.d.ts.map +1 -1
- package/dist/lib/managed-skill-runtimes.d.ts +80 -0
- package/dist/lib/managed-skill-runtimes.d.ts.map +1 -0
- package/dist/lib/managed-skill-runtimes.test.d.ts +2 -0
- package/dist/lib/managed-skill-runtimes.test.d.ts.map +1 -0
- package/dist/lib/raw-store-root.d.ts +17 -0
- package/dist/lib/raw-store-root.d.ts.map +1 -0
- package/dist/lib/retired-storage-mode.d.ts +8 -0
- package/dist/lib/retired-storage-mode.d.ts.map +1 -0
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-authority.d.ts +27 -0
- package/dist/lib/session-authority.d.ts.map +1 -0
- package/dist/lib/session-authority.test.d.ts +2 -0
- package/dist/lib/session-authority.test.d.ts.map +1 -0
- package/dist/lib/session-render.d.ts +7 -3
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/mcp/index.js +177 -115
- package/dist/server/cloud-auth.test.d.ts +2 -0
- package/dist/server/cloud-auth.test.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +7 -4
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/cloud.test.d.ts +2 -0
- package/dist/server/cloud.test.d.ts.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +781 -325
- package/dist/status.d.ts +11 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/dist/storage/cloud-store.test.d.ts +2 -0
- package/dist/storage/cloud-store.test.d.ts.map +1 -0
- package/package.json +10 -7
- package/dist/generated/storage-kit/mode.d.ts +0 -48
- package/dist/generated/storage-kit/mode.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -47,7 +47,7 @@ var __export = (target, all) => {
|
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
|
-
// node_modules/commander/lib/error.js
|
|
50
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
|
|
51
51
|
var require_error = __commonJS((exports) => {
|
|
52
52
|
class CommanderError extends Error {
|
|
53
53
|
constructor(exitCode, code, message) {
|
|
@@ -71,7 +71,7 @@ var require_error = __commonJS((exports) => {
|
|
|
71
71
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
// node_modules/commander/lib/argument.js
|
|
74
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
|
|
75
75
|
var require_argument = __commonJS((exports) => {
|
|
76
76
|
var { InvalidArgumentError } = require_error();
|
|
77
77
|
|
|
@@ -150,7 +150,7 @@ var require_argument = __commonJS((exports) => {
|
|
|
150
150
|
exports.humanReadableArgName = humanReadableArgName;
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
// node_modules/commander/lib/help.js
|
|
153
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
|
|
154
154
|
var require_help = __commonJS((exports) => {
|
|
155
155
|
var { humanReadableArgName } = require_argument();
|
|
156
156
|
|
|
@@ -500,7 +500,7 @@ ${itemIndentStr}`);
|
|
|
500
500
|
exports.stripColor = stripColor;
|
|
501
501
|
});
|
|
502
502
|
|
|
503
|
-
// node_modules/commander/lib/option.js
|
|
503
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
|
|
504
504
|
var require_option = __commonJS((exports) => {
|
|
505
505
|
var { InvalidArgumentError } = require_error();
|
|
506
506
|
|
|
@@ -678,7 +678,7 @@ var require_option = __commonJS((exports) => {
|
|
|
678
678
|
exports.DualOptions = DualOptions;
|
|
679
679
|
});
|
|
680
680
|
|
|
681
|
-
// node_modules/commander/lib/suggestSimilar.js
|
|
681
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
682
682
|
var require_suggestSimilar = __commonJS((exports) => {
|
|
683
683
|
var maxDistance = 3;
|
|
684
684
|
function editDistance(a, b) {
|
|
@@ -751,7 +751,7 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
751
751
|
exports.suggestSimilar = suggestSimilar;
|
|
752
752
|
});
|
|
753
753
|
|
|
754
|
-
// node_modules/commander/lib/command.js
|
|
754
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
|
|
755
755
|
var require_command = __commonJS((exports) => {
|
|
756
756
|
var EventEmitter = __require("events").EventEmitter;
|
|
757
757
|
var childProcess = __require("child_process");
|
|
@@ -2061,7 +2061,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2061
2061
|
exports.useColor = useColor;
|
|
2062
2062
|
});
|
|
2063
2063
|
|
|
2064
|
-
// node_modules/commander/index.js
|
|
2064
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
|
|
2065
2065
|
var require_commander = __commonJS((exports) => {
|
|
2066
2066
|
var { Argument } = require_argument();
|
|
2067
2067
|
var { Command } = require_command();
|
|
@@ -2136,19 +2136,51 @@ var init_types = __esm(() => {
|
|
|
2136
2136
|
};
|
|
2137
2137
|
});
|
|
2138
2138
|
|
|
2139
|
+
// src/lib/retired-storage-mode.ts
|
|
2140
|
+
function firstDefinedEnvKey(env, keys) {
|
|
2141
|
+
for (const key of keys) {
|
|
2142
|
+
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
2143
|
+
return key;
|
|
2144
|
+
}
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
function assertNoLegacyStorageMode(env = process.env) {
|
|
2148
|
+
const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
|
|
2149
|
+
if (!legacyKey)
|
|
2150
|
+
return;
|
|
2151
|
+
throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_INSTRUCTIONS_API_URL + HASNA_INSTRUCTIONS_API_KEY. ` + `On the server, set HASNA_INSTRUCTIONS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
|
|
2152
|
+
}
|
|
2153
|
+
var LEGACY_STORAGE_MODE_KEYS;
|
|
2154
|
+
var init_retired_storage_mode = __esm(() => {
|
|
2155
|
+
LEGACY_STORAGE_MODE_KEYS = [
|
|
2156
|
+
"HASNA_INSTRUCTIONS_STORAGE_MODE",
|
|
2157
|
+
"HASNA_INSTRUCTIONS_MODE",
|
|
2158
|
+
"INSTRUCTIONS_STORAGE_MODE",
|
|
2159
|
+
"INSTRUCTIONS_MODE"
|
|
2160
|
+
];
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
// src/lib/raw-store-root.ts
|
|
2164
|
+
import { homedir as homedir2 } from "os";
|
|
2165
|
+
import { join as join2, resolve } from "path";
|
|
2166
|
+
function getRawStoreRoot() {
|
|
2167
|
+
return resolve(process.env[RAW_STORE_ROOT_ENV] || join2(process.env["HOME"] || homedir2(), ".hasna", "instructions"));
|
|
2168
|
+
}
|
|
2169
|
+
var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME";
|
|
2170
|
+
var init_raw_store_root = () => {};
|
|
2171
|
+
|
|
2139
2172
|
// src/db/database.ts
|
|
2140
2173
|
import { Database } from "bun:sqlite";
|
|
2141
2174
|
import { existsSync as existsSync2, mkdirSync, rmSync } from "fs";
|
|
2142
|
-
import { join as
|
|
2175
|
+
import { join as join3 } from "path";
|
|
2143
2176
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2144
2177
|
function getDbPath() {
|
|
2145
2178
|
if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
|
|
2146
2179
|
return process.env["HASNA_INSTRUCTIONS_DB_PATH"];
|
|
2147
2180
|
}
|
|
2148
|
-
const
|
|
2149
|
-
const dir = join2(home, ".hasna", "instructions");
|
|
2181
|
+
const dir = getRawStoreRoot();
|
|
2150
2182
|
mkdirSync(dir, { recursive: true });
|
|
2151
|
-
return
|
|
2183
|
+
return join3(dir, "instructions.db");
|
|
2152
2184
|
}
|
|
2153
2185
|
function uuid() {
|
|
2154
2186
|
return randomUUID3();
|
|
@@ -2162,8 +2194,9 @@ function slugify(name) {
|
|
|
2162
2194
|
function getDatabase(path) {
|
|
2163
2195
|
if (_db)
|
|
2164
2196
|
return _db;
|
|
2197
|
+
assertNoLegacyStorageMode();
|
|
2165
2198
|
if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
|
|
2166
|
-
throw new Error("instructions is
|
|
2199
|
+
throw new Error("instructions is using the HTTP API transport (HASNA_INSTRUCTIONS_API_URL set): this command is not wired to the API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
|
|
2167
2200
|
}
|
|
2168
2201
|
const dbPath = path || getDbPath();
|
|
2169
2202
|
const db = new Database(dbPath);
|
|
@@ -2244,6 +2277,8 @@ function insertFeedback(input, db) {
|
|
|
2244
2277
|
}
|
|
2245
2278
|
var MIGRATIONS, _db = null;
|
|
2246
2279
|
var init_database = __esm(() => {
|
|
2280
|
+
init_retired_storage_mode();
|
|
2281
|
+
init_raw_store_root();
|
|
2247
2282
|
MIGRATIONS = [
|
|
2248
2283
|
`
|
|
2249
2284
|
CREATE TABLE IF NOT EXISTS configs (
|
|
@@ -2328,6 +2363,37 @@ var init_database = __esm(() => {
|
|
|
2328
2363
|
];
|
|
2329
2364
|
});
|
|
2330
2365
|
|
|
2366
|
+
// src/db/snapshots.ts
|
|
2367
|
+
function createSnapshot(configId, content, version, db) {
|
|
2368
|
+
const d = db || getDatabase();
|
|
2369
|
+
const id = uuid();
|
|
2370
|
+
const ts = now2();
|
|
2371
|
+
d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
|
|
2372
|
+
return { id, config_id: configId, content, version, created_at: ts };
|
|
2373
|
+
}
|
|
2374
|
+
function listSnapshots(configId, db) {
|
|
2375
|
+
const d = db || getDatabase();
|
|
2376
|
+
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
|
|
2377
|
+
}
|
|
2378
|
+
function getSnapshot(id, db) {
|
|
2379
|
+
const d = db || getDatabase();
|
|
2380
|
+
return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
|
|
2381
|
+
}
|
|
2382
|
+
function getSnapshotByVersion(configId, version, db) {
|
|
2383
|
+
const d = db || getDatabase();
|
|
2384
|
+
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
|
|
2385
|
+
}
|
|
2386
|
+
function pruneSnapshots(configId, keep = 10, db) {
|
|
2387
|
+
const d = db || getDatabase();
|
|
2388
|
+
const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
|
|
2389
|
+
SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
|
|
2390
|
+
)`, [configId, configId, keep]);
|
|
2391
|
+
return result.changes;
|
|
2392
|
+
}
|
|
2393
|
+
var init_snapshots = __esm(() => {
|
|
2394
|
+
init_database();
|
|
2395
|
+
});
|
|
2396
|
+
|
|
2331
2397
|
// src/db/configs.ts
|
|
2332
2398
|
function rowToConfig(row) {
|
|
2333
2399
|
let outputs = [];
|
|
@@ -2366,25 +2432,28 @@ function createConfig(input, db) {
|
|
|
2366
2432
|
const slug = uniqueSlug(input.name, d);
|
|
2367
2433
|
const tags = JSON.stringify(input.tags || []);
|
|
2368
2434
|
const outputs = JSON.stringify(input.outputs || []);
|
|
2369
|
-
d.
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2435
|
+
return d.transaction(() => {
|
|
2436
|
+
d.run(`INSERT INTO configs (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at, synced_at)
|
|
2437
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
|
|
2438
|
+
id,
|
|
2439
|
+
input.name,
|
|
2440
|
+
slug,
|
|
2441
|
+
input.kind ?? "file",
|
|
2442
|
+
input.category,
|
|
2443
|
+
input.agent ?? "global",
|
|
2444
|
+
input.target_path ?? null,
|
|
2445
|
+
outputs,
|
|
2446
|
+
input.format ?? "text",
|
|
2447
|
+
input.content,
|
|
2448
|
+
input.description ?? null,
|
|
2449
|
+
tags,
|
|
2450
|
+
input.is_template ? 1 : 0,
|
|
2451
|
+
ts,
|
|
2452
|
+
ts
|
|
2453
|
+
]);
|
|
2454
|
+
createSnapshot(id, input.content, 1, d);
|
|
2455
|
+
return getConfig(id, d);
|
|
2456
|
+
})();
|
|
2388
2457
|
}
|
|
2389
2458
|
function getConfig(idOrSlug, db) {
|
|
2390
2459
|
const d = db || getDatabase();
|
|
@@ -2489,9 +2558,13 @@ function updateConfig(idOrSlug, input, db) {
|
|
|
2489
2558
|
updates.push("synced_at = ?");
|
|
2490
2559
|
params.push(input.synced_at);
|
|
2491
2560
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2561
|
+
return d.transaction(() => {
|
|
2562
|
+
params.push(existing.id);
|
|
2563
|
+
d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
2564
|
+
const updated = getConfigById(existing.id, d);
|
|
2565
|
+
createSnapshot(updated.id, updated.content, updated.version, d);
|
|
2566
|
+
return updated;
|
|
2567
|
+
})();
|
|
2495
2568
|
}
|
|
2496
2569
|
function deleteConfig(idOrSlug, db) {
|
|
2497
2570
|
const d = db || getDatabase();
|
|
@@ -2511,6 +2584,7 @@ function getConfigStats(db) {
|
|
|
2511
2584
|
var init_configs = __esm(() => {
|
|
2512
2585
|
init_types();
|
|
2513
2586
|
init_database();
|
|
2587
|
+
init_snapshots();
|
|
2514
2588
|
});
|
|
2515
2589
|
|
|
2516
2590
|
// src/lib/template.ts
|
|
@@ -2584,9 +2658,9 @@ var init_template = __esm(() => {
|
|
|
2584
2658
|
});
|
|
2585
2659
|
|
|
2586
2660
|
// src/lib/machine.ts
|
|
2587
|
-
import { arch as currentArch, homedir as
|
|
2661
|
+
import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
|
|
2588
2662
|
import { existsSync as existsSync3 } from "fs";
|
|
2589
|
-
import { join as
|
|
2663
|
+
import { join as join4 } from "path";
|
|
2590
2664
|
function normalizeOsFamily(os) {
|
|
2591
2665
|
const value = (os ?? "").trim().toLowerCase();
|
|
2592
2666
|
if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
|
|
@@ -2598,11 +2672,11 @@ function normalizeOsFamily(os) {
|
|
|
2598
2672
|
return value || "unknown";
|
|
2599
2673
|
}
|
|
2600
2674
|
function detectMachineContext(overrides = {}) {
|
|
2601
|
-
const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ??
|
|
2675
|
+
const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
|
|
2602
2676
|
const os = overrides.os ?? currentOsType();
|
|
2603
2677
|
const osFamily = normalizeOsFamily(os);
|
|
2604
|
-
const bunBinDir = overrides.bun_bin_dir ??
|
|
2605
|
-
const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH :
|
|
2678
|
+
const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir, ".bun", "bin");
|
|
2679
|
+
const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join4(bunBinDir, "bun");
|
|
2606
2680
|
return {
|
|
2607
2681
|
id: "current-machine",
|
|
2608
2682
|
hostname: overrides.hostname ?? currentHostname(),
|
|
@@ -2612,10 +2686,10 @@ function detectMachineContext(overrides = {}) {
|
|
|
2612
2686
|
created_at: "",
|
|
2613
2687
|
os_family: osFamily,
|
|
2614
2688
|
home_dir: homeDir,
|
|
2615
|
-
workspace_root: overrides.workspace_root ??
|
|
2689
|
+
workspace_root: overrides.workspace_root ?? join4(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
|
|
2616
2690
|
bun_bin_dir: bunBinDir,
|
|
2617
2691
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
2618
|
-
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${
|
|
2692
|
+
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join4("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
2619
2693
|
};
|
|
2620
2694
|
}
|
|
2621
2695
|
function machineContextToVariables(machine) {
|
|
@@ -3037,7 +3111,7 @@ Policy reference: \`${CODEWITH_SHARED_TODOS_STORAGE_POLICY_REFERENCE}\`
|
|
|
3037
3111
|
`;
|
|
3038
3112
|
});
|
|
3039
3113
|
|
|
3040
|
-
// node_modules/zod/v3/helpers/util.js
|
|
3114
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
3041
3115
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
3042
3116
|
const t = typeof data;
|
|
3043
3117
|
switch (t) {
|
|
@@ -3168,7 +3242,7 @@ var init_util = __esm(() => {
|
|
|
3168
3242
|
]);
|
|
3169
3243
|
});
|
|
3170
3244
|
|
|
3171
|
-
// node_modules/zod/v3/ZodError.js
|
|
3245
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
3172
3246
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
3173
3247
|
const json = JSON.stringify(obj, null, 2);
|
|
3174
3248
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -3289,7 +3363,7 @@ var init_ZodError = __esm(() => {
|
|
|
3289
3363
|
};
|
|
3290
3364
|
});
|
|
3291
3365
|
|
|
3292
|
-
// node_modules/zod/v3/locales/en.js
|
|
3366
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
3293
3367
|
var errorMap = (issue, _ctx) => {
|
|
3294
3368
|
let message;
|
|
3295
3369
|
switch (issue.code) {
|
|
@@ -3396,7 +3470,7 @@ var init_en = __esm(() => {
|
|
|
3396
3470
|
en_default = errorMap;
|
|
3397
3471
|
});
|
|
3398
3472
|
|
|
3399
|
-
// node_modules/zod/v3/errors.js
|
|
3473
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
3400
3474
|
function setErrorMap(map) {
|
|
3401
3475
|
overrideErrorMap = map;
|
|
3402
3476
|
}
|
|
@@ -3409,7 +3483,7 @@ var init_errors = __esm(() => {
|
|
|
3409
3483
|
overrideErrorMap = en_default;
|
|
3410
3484
|
});
|
|
3411
3485
|
|
|
3412
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
3486
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
3413
3487
|
function addIssueToContext(ctx, issueData) {
|
|
3414
3488
|
const overrideMap = getErrorMap();
|
|
3415
3489
|
const issue = makeIssue({
|
|
@@ -3514,10 +3588,10 @@ var init_parseUtil = __esm(() => {
|
|
|
3514
3588
|
});
|
|
3515
3589
|
});
|
|
3516
3590
|
|
|
3517
|
-
// node_modules/zod/v3/helpers/typeAliases.js
|
|
3591
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
3518
3592
|
var init_typeAliases = () => {};
|
|
3519
3593
|
|
|
3520
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
3594
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
3521
3595
|
var errorUtil;
|
|
3522
3596
|
var init_errorUtil = __esm(() => {
|
|
3523
3597
|
(function(errorUtil2) {
|
|
@@ -3526,7 +3600,7 @@ var init_errorUtil = __esm(() => {
|
|
|
3526
3600
|
})(errorUtil || (errorUtil = {}));
|
|
3527
3601
|
});
|
|
3528
3602
|
|
|
3529
|
-
// node_modules/zod/v3/types.js
|
|
3603
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
3530
3604
|
class ParseInputLazyPath {
|
|
3531
3605
|
constructor(parent, value, path, key) {
|
|
3532
3606
|
this._cachedPath = [];
|
|
@@ -6877,7 +6951,7 @@ var init_types2 = __esm(() => {
|
|
|
6877
6951
|
NEVER = INVALID;
|
|
6878
6952
|
});
|
|
6879
6953
|
|
|
6880
|
-
// node_modules/zod/v3/external.js
|
|
6954
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
6881
6955
|
var exports_external = {};
|
|
6882
6956
|
__export(exports_external, {
|
|
6883
6957
|
void: () => voidType,
|
|
@@ -6997,7 +7071,7 @@ var init_external = __esm(() => {
|
|
|
6997
7071
|
init_ZodError();
|
|
6998
7072
|
});
|
|
6999
7073
|
|
|
7000
|
-
// node_modules/zod/index.js
|
|
7074
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
7001
7075
|
var init_zod = __esm(() => {
|
|
7002
7076
|
init_external();
|
|
7003
7077
|
init_external();
|
|
@@ -7189,7 +7263,7 @@ var init_redact = __esm(() => {
|
|
|
7189
7263
|
VALUE_PATTERNS = [
|
|
7190
7264
|
{ re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
|
|
7191
7265
|
{ re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
|
|
7192
|
-
{ re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
|
|
7266
|
+
{ re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
|
|
7193
7267
|
{ re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
|
|
7194
7268
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
|
|
7195
7269
|
{ re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
|
|
@@ -7239,7 +7313,7 @@ import {
|
|
|
7239
7313
|
statSync,
|
|
7240
7314
|
writeFileSync
|
|
7241
7315
|
} from "fs";
|
|
7242
|
-
import { basename, dirname, isAbsolute, join as
|
|
7316
|
+
import { basename, dirname, isAbsolute, join as join5, parse, relative, resolve as resolve2 } from "path";
|
|
7243
7317
|
function managedObservationMaxBytes(relativePath) {
|
|
7244
7318
|
return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
|
|
7245
7319
|
}
|
|
@@ -7410,7 +7484,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
7410
7484
|
if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
|
|
7411
7485
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
|
|
7412
7486
|
}
|
|
7413
|
-
const plannedIndexes = input.files.filter((file) => file.role === "index" &&
|
|
7487
|
+
const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve2(file.path) === paths.target);
|
|
7414
7488
|
if (plannedIndexes.length !== 1) {
|
|
7415
7489
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
|
|
7416
7490
|
}
|
|
@@ -7468,7 +7542,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
|
|
|
7468
7542
|
verify();
|
|
7469
7543
|
return action(null);
|
|
7470
7544
|
}
|
|
7471
|
-
const lockPath =
|
|
7545
|
+
const lockPath = resolve2(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
7472
7546
|
const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
|
|
7473
7547
|
try {
|
|
7474
7548
|
verify();
|
|
@@ -7494,7 +7568,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
7494
7568
|
if (!isRecord(observed) || typeof observed.path !== "string") {
|
|
7495
7569
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
|
|
7496
7570
|
}
|
|
7497
|
-
const path =
|
|
7571
|
+
const path = resolve2(observed.path);
|
|
7498
7572
|
if (!allowedPaths.has(path) || observedPaths.has(path)) {
|
|
7499
7573
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
|
|
7500
7574
|
}
|
|
@@ -7516,7 +7590,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
7516
7590
|
function applyProjectContext(options) {
|
|
7517
7591
|
const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
|
|
7518
7592
|
const now3 = options.now ?? new Date;
|
|
7519
|
-
const lockPath =
|
|
7593
|
+
const lockPath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
7520
7594
|
const lock = options.dry_run ? null : acquireWorkspaceLock(workspaceRoot, lockPath, options.test_hooks?.after_lock_open, options.test_hooks?.before_stale_lock_remove, options.test_hooks?.process_start_identity);
|
|
7521
7595
|
try {
|
|
7522
7596
|
const resolved = resolveBundleForApply(options, workspaceRoot, now3);
|
|
@@ -7663,7 +7737,7 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
|
|
|
7663
7737
|
if (!options.expected_project_id) {
|
|
7664
7738
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
|
|
7665
7739
|
}
|
|
7666
|
-
const cachePath =
|
|
7740
|
+
const cachePath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
7667
7741
|
const cache = readProjectContextCache(cachePath, workspaceRoot);
|
|
7668
7742
|
if (!cache)
|
|
7669
7743
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
|
|
@@ -8019,7 +8093,7 @@ function buildManifest(plan, now3) {
|
|
|
8019
8093
|
function buildSessionCompatibilityManifest(plan, now3) {
|
|
8020
8094
|
const paths = runtimePaths(plan.workspace_root, plan.runtime);
|
|
8021
8095
|
const tool = manifestTool(plan.runtime);
|
|
8022
|
-
const targetHome = plan.runtime === "codewith" ?
|
|
8096
|
+
const targetHome = plan.runtime === "codewith" ? resolve2(plan.workspace_root, ".codewith") : plan.workspace_root;
|
|
8023
8097
|
const targetRelativePath = sessionTargetRelativePath(plan.runtime);
|
|
8024
8098
|
const existing = existsSync4(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
|
|
8025
8099
|
schema: SESSION_RENDER_SCHEMA,
|
|
@@ -8041,7 +8115,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
|
|
|
8041
8115
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
|
|
8042
8116
|
}
|
|
8043
8117
|
const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
|
|
8044
|
-
if (existingTargetHome !== null &&
|
|
8118
|
+
if (existingTargetHome !== null && resolve2(existingTargetHome) !== targetHome) {
|
|
8045
8119
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
|
|
8046
8120
|
}
|
|
8047
8121
|
const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
|
|
@@ -8327,9 +8401,9 @@ function writeMetadataSnapshot(plan, now3) {
|
|
|
8327
8401
|
const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
|
|
8328
8402
|
if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
|
|
8329
8403
|
return null;
|
|
8330
|
-
const snapshotDir =
|
|
8404
|
+
const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
8331
8405
|
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
8332
|
-
const snapshotPath =
|
|
8406
|
+
const snapshotPath = resolve2(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
|
|
8333
8407
|
const snapshot = {
|
|
8334
8408
|
schema: "hasna.configs.session-render-snapshot/v1",
|
|
8335
8409
|
kind: "project-context-metadata",
|
|
@@ -8345,8 +8419,8 @@ function writeMetadataSnapshot(plan, now3) {
|
|
|
8345
8419
|
return snapshotPath;
|
|
8346
8420
|
}
|
|
8347
8421
|
function metadataSnapshotMatchesManifest(plan, manifest) {
|
|
8348
|
-
const snapshotDir =
|
|
8349
|
-
const snapshotPath =
|
|
8422
|
+
const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
8423
|
+
const snapshotPath = resolve2(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
|
|
8350
8424
|
if (!existsSync4(snapshotPath))
|
|
8351
8425
|
return false;
|
|
8352
8426
|
const record = readJsonRecord(snapshotPath, plan.workspace_root);
|
|
@@ -8395,10 +8469,10 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
|
|
|
8395
8469
|
sha256: nextHash
|
|
8396
8470
|
};
|
|
8397
8471
|
});
|
|
8398
|
-
const snapshotDir =
|
|
8472
|
+
const snapshotDir = resolve2(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
|
|
8399
8473
|
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
8400
8474
|
const timestamp = now3.toISOString().replace(/[:.]/g, "-");
|
|
8401
|
-
const snapshotPath =
|
|
8475
|
+
const snapshotPath = resolve2(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
|
|
8402
8476
|
const snapshot = {
|
|
8403
8477
|
schema: "hasna.configs.session-render-snapshot/v2",
|
|
8404
8478
|
createdAt: now3.toISOString(),
|
|
@@ -8463,7 +8537,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
|
|
|
8463
8537
|
}
|
|
8464
8538
|
}
|
|
8465
8539
|
function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
|
|
8466
|
-
const dir =
|
|
8540
|
+
const dir = resolve2(path, "..");
|
|
8467
8541
|
ensureSafeDirectory(dir, workspaceRoot, 448);
|
|
8468
8542
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
8469
8543
|
const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
|
|
@@ -8480,7 +8554,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
8480
8554
|
const previous = anchoredFileObservation(directory, targetName);
|
|
8481
8555
|
const previousMode = previous?.mode ?? defaultMode;
|
|
8482
8556
|
const tempName = `.project-context-${randomUUID5()}.tmp`;
|
|
8483
|
-
const tempPath =
|
|
8557
|
+
const tempPath = join5(dir, tempName);
|
|
8484
8558
|
let fd = null;
|
|
8485
8559
|
let preserveTemp = false;
|
|
8486
8560
|
let directoryChanged = false;
|
|
@@ -8611,7 +8685,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
|
|
|
8611
8685
|
}
|
|
8612
8686
|
const dir = dirname(path);
|
|
8613
8687
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8614
|
-
const tempPath =
|
|
8688
|
+
const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8615
8689
|
let fd = null;
|
|
8616
8690
|
let tempIdentity = null;
|
|
8617
8691
|
try {
|
|
@@ -8668,7 +8742,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
8668
8742
|
}
|
|
8669
8743
|
const dir = dirname(path);
|
|
8670
8744
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8671
|
-
const tempPath =
|
|
8745
|
+
const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8672
8746
|
const desiredHash = sha2562(content);
|
|
8673
8747
|
let fd = null;
|
|
8674
8748
|
let tempIdentity = null;
|
|
@@ -8737,11 +8811,11 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
|
8737
8811
|
return createHash2("sha256").update(readFileSync(path)).digest("hex");
|
|
8738
8812
|
}
|
|
8739
8813
|
function writeProjectContextCoordinatedFile(input) {
|
|
8740
|
-
atomicWriteFile(
|
|
8814
|
+
atomicWriteFile(resolve2(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
|
|
8741
8815
|
}
|
|
8742
8816
|
function removeProjectContextCoordinatedFile(input) {
|
|
8743
8817
|
const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
|
|
8744
|
-
const path =
|
|
8818
|
+
const path = resolve2(input.path);
|
|
8745
8819
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
8746
8820
|
const dir = dirname(path);
|
|
8747
8821
|
const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
|
|
@@ -8767,7 +8841,7 @@ function removeProjectContextCoordinatedFile(input) {
|
|
|
8767
8841
|
throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
|
|
8768
8842
|
}
|
|
8769
8843
|
displaced = true;
|
|
8770
|
-
input.test_hooks?.after_displace?.(
|
|
8844
|
+
input.test_hooks?.after_displace?.(join5(dir, displacedName));
|
|
8771
8845
|
const moved = anchoredFileObservation(directory, displacedName);
|
|
8772
8846
|
if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
|
|
8773
8847
|
throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
|
|
@@ -8813,7 +8887,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
|
|
|
8813
8887
|
}
|
|
8814
8888
|
const dir = dirname(path);
|
|
8815
8889
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8816
|
-
const displacedPath =
|
|
8890
|
+
const displacedPath = join5(dir, `.project-context-delete-${randomUUID5()}.tmp`);
|
|
8817
8891
|
let displaced = false;
|
|
8818
8892
|
try {
|
|
8819
8893
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
@@ -8884,7 +8958,7 @@ function anchoredOpenExclusive(directory, name, mode) {
|
|
|
8884
8958
|
const requestedMode = mode & 4095;
|
|
8885
8959
|
let fd;
|
|
8886
8960
|
try {
|
|
8887
|
-
fd = openSync(
|
|
8961
|
+
fd = openSync(join5(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
|
|
8888
8962
|
} catch {
|
|
8889
8963
|
throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
8890
8964
|
}
|
|
@@ -8927,7 +9001,7 @@ function anchoredFileObservation(directory, name) {
|
|
|
8927
9001
|
const stat = fstatSync(fd);
|
|
8928
9002
|
if (!stat.isFile())
|
|
8929
9003
|
throw new ProjectContextHashRace("managed output is not a regular file");
|
|
8930
|
-
const relativePath = relativePosix(directory.workspaceRoot,
|
|
9004
|
+
const relativePath = relativePosix(directory.workspaceRoot, join5(directory.path, name));
|
|
8931
9005
|
const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
|
|
8932
9006
|
if (maxBytes !== null && stat.size > maxBytes) {
|
|
8933
9007
|
throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
|
|
@@ -8953,7 +9027,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
|
|
|
8953
9027
|
return observed;
|
|
8954
9028
|
}
|
|
8955
9029
|
function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
8956
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9030
|
+
assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
|
|
8957
9031
|
let stat;
|
|
8958
9032
|
try {
|
|
8959
9033
|
stat = lstatSync(path);
|
|
@@ -8966,7 +9040,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
|
8966
9040
|
return { dev: stat.dev, ino: stat.ino };
|
|
8967
9041
|
}
|
|
8968
9042
|
function assertManagedDirectoryStable(path, workspaceRoot, expected) {
|
|
8969
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9043
|
+
assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
|
|
8970
9044
|
let current;
|
|
8971
9045
|
try {
|
|
8972
9046
|
current = lstatSync(path);
|
|
@@ -9099,10 +9173,10 @@ function resolveAnchoredFsOps() {
|
|
|
9099
9173
|
return null;
|
|
9100
9174
|
}
|
|
9101
9175
|
function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
|
|
9102
|
-
const lockDirectory =
|
|
9176
|
+
const lockDirectory = resolve2(lockPath, "..");
|
|
9103
9177
|
ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
|
|
9104
9178
|
assertNoSymlinkSegments(workspaceRoot, lockPath);
|
|
9105
|
-
const tempPath =
|
|
9179
|
+
const tempPath = join5(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
|
|
9106
9180
|
let fd = null;
|
|
9107
9181
|
let openedIdentity = null;
|
|
9108
9182
|
let openedContentHash = null;
|
|
@@ -9174,7 +9248,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
|
|
|
9174
9248
|
if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
|
|
9175
9249
|
return;
|
|
9176
9250
|
rmSync2(lockPath);
|
|
9177
|
-
fsyncDirectory(
|
|
9251
|
+
fsyncDirectory(resolve2(lockPath, ".."));
|
|
9178
9252
|
} catch {}
|
|
9179
9253
|
}
|
|
9180
9254
|
function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
|
|
@@ -9253,7 +9327,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
|
|
|
9253
9327
|
throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
|
|
9254
9328
|
}
|
|
9255
9329
|
rmSync2(candidatePath);
|
|
9256
|
-
fsyncDirectory(
|
|
9330
|
+
fsyncDirectory(resolve2(lockPath, ".."));
|
|
9257
9331
|
exchanged = false;
|
|
9258
9332
|
return true;
|
|
9259
9333
|
} catch (error) {
|
|
@@ -9332,8 +9406,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
9332
9406
|
}
|
|
9333
9407
|
return;
|
|
9334
9408
|
}
|
|
9335
|
-
const lockDirectory =
|
|
9336
|
-
const releasePath =
|
|
9409
|
+
const lockDirectory = resolve2(lockPath, "..");
|
|
9410
|
+
const releasePath = join5(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
|
|
9337
9411
|
let releaseFd = null;
|
|
9338
9412
|
let releaseIdentity = null;
|
|
9339
9413
|
let releaseHash = null;
|
|
@@ -9413,7 +9487,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
9413
9487
|
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
9414
9488
|
let current = workspaceRoot;
|
|
9415
9489
|
for (const segment of segments) {
|
|
9416
|
-
current =
|
|
9490
|
+
current = join5(current, segment);
|
|
9417
9491
|
if (existsSync4(current)) {
|
|
9418
9492
|
if (lstatSync(current).isSymbolicLink())
|
|
9419
9493
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
@@ -9421,7 +9495,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
9421
9495
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
|
|
9422
9496
|
} else {
|
|
9423
9497
|
mkdirSync2(current, { mode });
|
|
9424
|
-
fsyncDirectory(
|
|
9498
|
+
fsyncDirectory(resolve2(current, ".."));
|
|
9425
9499
|
}
|
|
9426
9500
|
}
|
|
9427
9501
|
}
|
|
@@ -9477,11 +9551,11 @@ function scanGeneratedContent(content) {
|
|
|
9477
9551
|
function runtimePaths(workspaceRoot, runtime) {
|
|
9478
9552
|
const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
|
|
9479
9553
|
return {
|
|
9480
|
-
target:
|
|
9481
|
-
fragment:
|
|
9482
|
-
manifest:
|
|
9483
|
-
cache:
|
|
9484
|
-
sessionManifest: runtime === "codewith" ?
|
|
9554
|
+
target: resolve2(workspaceRoot, ...relativeTarget.split("/")),
|
|
9555
|
+
fragment: resolve2(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
|
|
9556
|
+
manifest: resolve2(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
|
|
9557
|
+
cache: resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
|
|
9558
|
+
sessionManifest: runtime === "codewith" ? resolve2(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve2(workspaceRoot, ".hasna", "session-render-manifest.json")
|
|
9485
9559
|
};
|
|
9486
9560
|
}
|
|
9487
9561
|
function projectContextSessionGuardPaths(paths, runtime) {
|
|
@@ -9491,7 +9565,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
|
|
|
9491
9565
|
paths.fragment,
|
|
9492
9566
|
paths.target,
|
|
9493
9567
|
paths.sessionManifest,
|
|
9494
|
-
...runtime === "codewith" ? [
|
|
9568
|
+
...runtime === "codewith" ? [resolve2(paths.target, "..", "CODEWITH.override.md")] : []
|
|
9495
9569
|
];
|
|
9496
9570
|
}
|
|
9497
9571
|
function sessionTargetRelativePath(runtime) {
|
|
@@ -9511,12 +9585,12 @@ function projectContextRuntimeForSessionTool(tool) {
|
|
|
9511
9585
|
return null;
|
|
9512
9586
|
}
|
|
9513
9587
|
function projectContextWorkspaceForSession(input, runtime) {
|
|
9514
|
-
const targetHome =
|
|
9588
|
+
const targetHome = resolve2(input.target_home);
|
|
9515
9589
|
if (runtime === "codewith") {
|
|
9516
9590
|
const workspaceRoot = basename(targetHome) === ".codewith" ? dirname(targetHome) : null;
|
|
9517
9591
|
if (!workspaceRoot)
|
|
9518
9592
|
return null;
|
|
9519
|
-
if (input.project_root &&
|
|
9593
|
+
if (input.project_root && resolve2(input.project_root) !== workspaceRoot) {
|
|
9520
9594
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
|
|
9521
9595
|
}
|
|
9522
9596
|
if (!existsSync4(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
|
|
@@ -9530,7 +9604,7 @@ function projectContextWorkspaceForSession(input, runtime) {
|
|
|
9530
9604
|
function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
9531
9605
|
if (runtime !== "codewith")
|
|
9532
9606
|
return;
|
|
9533
|
-
const override =
|
|
9607
|
+
const override = resolve2(workspaceRoot, ".codewith", "CODEWITH.override.md");
|
|
9534
9608
|
if (!existsSync4(override))
|
|
9535
9609
|
return;
|
|
9536
9610
|
assertNoSymlinkSegments(workspaceRoot, override);
|
|
@@ -9541,7 +9615,7 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
|
9541
9615
|
function assertSafeWorkspaceRoot(path) {
|
|
9542
9616
|
if (!isAbsolute(path))
|
|
9543
9617
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
|
|
9544
|
-
const normalized =
|
|
9618
|
+
const normalized = resolve2(path);
|
|
9545
9619
|
if (normalized === parse(normalized).root)
|
|
9546
9620
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
|
|
9547
9621
|
if (!existsSync4(normalized) || !lstatSync(normalized).isDirectory())
|
|
@@ -9558,17 +9632,17 @@ function assertNoSymlinkSegments(root, target) {
|
|
|
9558
9632
|
}
|
|
9559
9633
|
let current = root;
|
|
9560
9634
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
9561
|
-
current =
|
|
9635
|
+
current = join5(current, segment);
|
|
9562
9636
|
if (existsSync4(current) && lstatSync(current).isSymbolicLink()) {
|
|
9563
9637
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
9564
9638
|
}
|
|
9565
9639
|
}
|
|
9566
9640
|
}
|
|
9567
9641
|
function assertNoSymlinkAncestors(path) {
|
|
9568
|
-
const normalized =
|
|
9642
|
+
const normalized = resolve2(path);
|
|
9569
9643
|
let current = parse(normalized).root;
|
|
9570
9644
|
for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
|
|
9571
|
-
current =
|
|
9645
|
+
current = join5(current, segment);
|
|
9572
9646
|
if (!existsSync4(current))
|
|
9573
9647
|
return;
|
|
9574
9648
|
if (lstatSync(current).isSymbolicLink())
|
|
@@ -9604,10 +9678,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
|
|
|
9604
9678
|
}
|
|
9605
9679
|
function durableSourcePath(path, workspaceRoot) {
|
|
9606
9680
|
if (!path || path.startsWith("/dev/fd/"))
|
|
9607
|
-
return
|
|
9608
|
-
const normalized = isAbsolute(path) ?
|
|
9681
|
+
return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
9682
|
+
const normalized = isAbsolute(path) ? resolve2(path) : resolve2(workspaceRoot, path);
|
|
9609
9683
|
if (normalized.startsWith("/dev/fd/"))
|
|
9610
|
-
return
|
|
9684
|
+
return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
9611
9685
|
return normalized;
|
|
9612
9686
|
}
|
|
9613
9687
|
function compareRevisions(incoming, previous) {
|
|
@@ -10167,7 +10241,7 @@ function compareProviderVersions(left, right) {
|
|
|
10167
10241
|
|
|
10168
10242
|
// src/lib/asset-plan.ts
|
|
10169
10243
|
import { createHash as createHash3 } from "crypto";
|
|
10170
|
-
import { isAbsolute as isAbsolute2, posix, resolve as
|
|
10244
|
+
import { isAbsolute as isAbsolute2, posix, resolve as resolve3 } from "path";
|
|
10171
10245
|
function assetCapability(provider, surface, kind, support, strategies, note, providerVersionRange = "*") {
|
|
10172
10246
|
return Object.freeze({
|
|
10173
10247
|
schema: ASSET_CAPABILITY_SCHEMA,
|
|
@@ -10374,8 +10448,8 @@ function resolveAssetDestination(item, roots) {
|
|
|
10374
10448
|
if (!isAbsolute2(root))
|
|
10375
10449
|
throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
|
|
10376
10450
|
const relativePath = safeRelativePath(item.destination.relativePath);
|
|
10377
|
-
const target =
|
|
10378
|
-
const normalizedRoot =
|
|
10451
|
+
const target = resolve3(root, ...relativePath.split("/"));
|
|
10452
|
+
const normalizedRoot = resolve3(root);
|
|
10379
10453
|
if (target === normalizedRoot)
|
|
10380
10454
|
throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
|
|
10381
10455
|
if (!target.startsWith(`${normalizedRoot}/`))
|
|
@@ -10555,13 +10629,13 @@ var init_asset_plan = __esm(() => {
|
|
|
10555
10629
|
// src/lib/cursor-authority.ts
|
|
10556
10630
|
import { createHash as createHash4 } from "crypto";
|
|
10557
10631
|
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
10558
|
-
import { homedir as
|
|
10559
|
-
import { join as
|
|
10632
|
+
import { homedir as homedir4 } from "os";
|
|
10633
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
10560
10634
|
function sha2564(content) {
|
|
10561
10635
|
return createHash4("sha256").update(content).digest("hex");
|
|
10562
10636
|
}
|
|
10563
10637
|
function homeDir() {
|
|
10564
|
-
return process.env["HOME"] ||
|
|
10638
|
+
return process.env["HOME"] || homedir4();
|
|
10565
10639
|
}
|
|
10566
10640
|
function markerPayload(content, markerLine, markerIndex) {
|
|
10567
10641
|
const index = markerIndex ?? content.indexOf(markerLine);
|
|
@@ -10577,12 +10651,12 @@ function baseObservation(path) {
|
|
|
10577
10651
|
};
|
|
10578
10652
|
}
|
|
10579
10653
|
function observeCursorGlobalAuthority(options = {}) {
|
|
10580
|
-
const authorityPath =
|
|
10654
|
+
const authorityPath = resolve4(join6(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10581
10655
|
const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
|
|
10582
10656
|
return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
|
|
10583
10657
|
}
|
|
10584
10658
|
function observeCursorGlobalAuthorityAtPath(authorityPath) {
|
|
10585
|
-
return observeCursorGlobalAuthorityPath(
|
|
10659
|
+
return observeCursorGlobalAuthorityPath(resolve4(authorityPath), (path) => readFileSync2(path, "utf8"));
|
|
10586
10660
|
}
|
|
10587
10661
|
function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
|
|
10588
10662
|
const base = baseObservation(authorityPath);
|
|
@@ -10727,7 +10801,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
|
|
|
10727
10801
|
};
|
|
10728
10802
|
}
|
|
10729
10803
|
function isCursorGlobalAuthorityPath(path) {
|
|
10730
|
-
return
|
|
10804
|
+
return resolve4(path) === resolve4(join6(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10731
10805
|
}
|
|
10732
10806
|
function stampCursorGlobalAuthorityMarker(content) {
|
|
10733
10807
|
if (CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN.test(content))
|
|
@@ -10770,11 +10844,88 @@ var init_cursor_authority = __esm(() => {
|
|
|
10770
10844
|
CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
|
|
10771
10845
|
});
|
|
10772
10846
|
|
|
10773
|
-
// src/lib/session-
|
|
10847
|
+
// src/lib/session-authority.ts
|
|
10774
10848
|
import { createHash as createHash5 } from "crypto";
|
|
10775
|
-
import {
|
|
10776
|
-
import {
|
|
10777
|
-
|
|
10849
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
10850
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
10851
|
+
function sha2565(content) {
|
|
10852
|
+
return createHash5("sha256").update(content).digest("hex");
|
|
10853
|
+
}
|
|
10854
|
+
function detectClaudeAuthorityConflicts(targetHome) {
|
|
10855
|
+
const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
10856
|
+
let stat;
|
|
10857
|
+
try {
|
|
10858
|
+
stat = lstatSync3(authorityPath);
|
|
10859
|
+
} catch {
|
|
10860
|
+
return [];
|
|
10861
|
+
}
|
|
10862
|
+
const provenanceBase = {
|
|
10863
|
+
tool: "claude",
|
|
10864
|
+
relativePath: CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH,
|
|
10865
|
+
path: authorityPath
|
|
10866
|
+
};
|
|
10867
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
10868
|
+
return [{
|
|
10869
|
+
...provenanceBase,
|
|
10870
|
+
kind: "invalid-unmanaged-authority",
|
|
10871
|
+
sha256: null,
|
|
10872
|
+
markers: [],
|
|
10873
|
+
provenance: {
|
|
10874
|
+
source: "filesystem",
|
|
10875
|
+
authority: "unmanaged",
|
|
10876
|
+
observedPath: authorityPath,
|
|
10877
|
+
detection: "non-regular-file"
|
|
10878
|
+
},
|
|
10879
|
+
reason: "Claude target contains unmanaged AGENTS.md that is not a regular file; authority cannot be verified safely."
|
|
10880
|
+
}];
|
|
10881
|
+
}
|
|
10882
|
+
if (statSync2(authorityPath).size > CLAUDE_LEGACY_AUTHORITY_MAX_BYTES) {
|
|
10883
|
+
return [{
|
|
10884
|
+
...provenanceBase,
|
|
10885
|
+
kind: "invalid-unmanaged-authority",
|
|
10886
|
+
sha256: null,
|
|
10887
|
+
markers: [],
|
|
10888
|
+
provenance: {
|
|
10889
|
+
source: "filesystem",
|
|
10890
|
+
authority: "unmanaged",
|
|
10891
|
+
observedPath: authorityPath,
|
|
10892
|
+
detection: "oversized-file"
|
|
10893
|
+
},
|
|
10894
|
+
reason: `Claude target contains unmanaged AGENTS.md larger than ${CLAUDE_LEGACY_AUTHORITY_MAX_BYTES} bytes; authority cannot be classified safely.`
|
|
10895
|
+
}];
|
|
10896
|
+
}
|
|
10897
|
+
const content = readFileSync3(authorityPath, "utf8");
|
|
10898
|
+
const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
|
|
10899
|
+
const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
|
|
10900
|
+
return [{
|
|
10901
|
+
...provenanceBase,
|
|
10902
|
+
kind: knownLegacy ? "known-legacy-no-worktree" : "unknown-unmanaged-authority",
|
|
10903
|
+
sha256: sha2565(content),
|
|
10904
|
+
markers,
|
|
10905
|
+
provenance: {
|
|
10906
|
+
source: "filesystem",
|
|
10907
|
+
authority: "unmanaged",
|
|
10908
|
+
observedPath: authorityPath,
|
|
10909
|
+
detection: knownLegacy ? "known-legacy-markers" : "unknown-content"
|
|
10910
|
+
},
|
|
10911
|
+
reason: knownLegacy ? "Claude target contains unmanaged legacy AGENTS.md with no-worktree directives; migrate or remove it through an owned authority path before applying." : "Claude target contains unmanaged AGENTS.md with unknown authority content; refusing to guess whether it conflicts with the managed Claude render."
|
|
10912
|
+
}];
|
|
10913
|
+
}
|
|
10914
|
+
var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md", CLAUDE_LEGACY_AUTHORITY_MAX_BYTES, CLAUDE_LEGACY_MARKERS;
|
|
10915
|
+
var init_session_authority = __esm(() => {
|
|
10916
|
+
CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
|
|
10917
|
+
CLAUDE_LEGACY_MARKERS = [
|
|
10918
|
+
{ id: "claude-agent-rules-heading", pattern: /^# Agent Rules \(Claude\)/m },
|
|
10919
|
+
{ id: "no-worktrees-heading", pattern: /^## No Worktrees/m },
|
|
10920
|
+
{ id: "no-worktrees-directive", pattern: /\bNever use git worktrees\b/m }
|
|
10921
|
+
];
|
|
10922
|
+
});
|
|
10923
|
+
|
|
10924
|
+
// src/lib/session-render.ts
|
|
10925
|
+
import { createHash as createHash6 } from "crypto";
|
|
10926
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "fs";
|
|
10927
|
+
import { homedir as homedir5 } from "os";
|
|
10928
|
+
import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute3, join as join8, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
|
|
10778
10929
|
function normalizeSessionInstructionLayer(value) {
|
|
10779
10930
|
if (value === "provider")
|
|
10780
10931
|
return "tool";
|
|
@@ -10791,11 +10942,11 @@ function ensureTrailingNewline3(content) {
|
|
|
10791
10942
|
`) ? content : `${content}
|
|
10792
10943
|
`;
|
|
10793
10944
|
}
|
|
10794
|
-
function
|
|
10795
|
-
return
|
|
10945
|
+
function sha2566(content) {
|
|
10946
|
+
return createHash6("sha256").update(content).digest("hex");
|
|
10796
10947
|
}
|
|
10797
10948
|
function fingerprint(value) {
|
|
10798
|
-
return
|
|
10949
|
+
return sha2566(JSON.stringify(value));
|
|
10799
10950
|
}
|
|
10800
10951
|
function canonicalFingerprintValue(value) {
|
|
10801
10952
|
if (Array.isArray(value))
|
|
@@ -10813,7 +10964,7 @@ function ruleAttestation(rule) {
|
|
|
10813
10964
|
};
|
|
10814
10965
|
const applied = metadata["payloadFloorApplied"];
|
|
10815
10966
|
return {
|
|
10816
|
-
contentSha256:
|
|
10967
|
+
contentSha256: sha2566(rule.content ?? ""),
|
|
10817
10968
|
payloadFloorApplied: typeof applied === "boolean" ? applied : null,
|
|
10818
10969
|
flooredFromRulesVersion: read("flooredFromRulesVersion"),
|
|
10819
10970
|
flooredFromPayloadSha256: read("flooredFromPayloadSha256"),
|
|
@@ -10859,16 +11010,14 @@ function slug(value) {
|
|
|
10859
11010
|
function yamlQuote2(value) {
|
|
10860
11011
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
10861
11012
|
}
|
|
10862
|
-
function getRawStoreRoot() {
|
|
10863
|
-
return resolve4(process.env[RAW_STORE_ROOT_ENV] || join6(process.env["HOME"] || homedir4(), ".hasna", "configs"));
|
|
10864
|
-
}
|
|
10865
11013
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
10866
|
-
|
|
11014
|
+
const home = process.env["HOME"] || homedir5();
|
|
11015
|
+
return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
10867
11016
|
}
|
|
10868
11017
|
function joinTarget(targetHome, relativePath) {
|
|
10869
11018
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
10870
11019
|
const safeRelativePath2 = assertSafeRelativePath(relativePath);
|
|
10871
|
-
return
|
|
11020
|
+
return join8(safeTargetHome, ...safeRelativePath2.split("/"));
|
|
10872
11021
|
}
|
|
10873
11022
|
function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
10874
11023
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
@@ -10879,7 +11028,7 @@ function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
|
10879
11028
|
relativePath: safeRelativePath2,
|
|
10880
11029
|
role,
|
|
10881
11030
|
content: normalizedContent,
|
|
10882
|
-
sha256:
|
|
11031
|
+
sha256: sha2566(normalizedContent),
|
|
10883
11032
|
sourceIds
|
|
10884
11033
|
};
|
|
10885
11034
|
}
|
|
@@ -10913,7 +11062,7 @@ function applyAgentOperatingRulesFloor(source, content) {
|
|
|
10913
11062
|
const floored = {
|
|
10914
11063
|
payloadFloorApplied: true,
|
|
10915
11064
|
flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
|
|
10916
|
-
flooredFromPayloadSha256:
|
|
11065
|
+
flooredFromPayloadSha256: sha2566(content)
|
|
10917
11066
|
};
|
|
10918
11067
|
return {
|
|
10919
11068
|
content: payload.content,
|
|
@@ -10935,7 +11084,7 @@ function applyAgentOperatingRulesFloorToRule(source, rule, content) {
|
|
|
10935
11084
|
const floored = {
|
|
10936
11085
|
payloadFloorApplied: true,
|
|
10937
11086
|
flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
|
|
10938
|
-
flooredFromPayloadSha256:
|
|
11087
|
+
flooredFromPayloadSha256: sha2566(content)
|
|
10939
11088
|
};
|
|
10940
11089
|
return {
|
|
10941
11090
|
content: payload.content,
|
|
@@ -10954,7 +11103,7 @@ function skippedSource(source, reason) {
|
|
|
10954
11103
|
order: source.resolvedOrder,
|
|
10955
11104
|
path: source.path ?? null,
|
|
10956
11105
|
hash: source.hash ?? null,
|
|
10957
|
-
renderedPayloadSha256:
|
|
11106
|
+
renderedPayloadSha256: sha2566(source.content),
|
|
10958
11107
|
nonOverridable: source.nonOverridable === true,
|
|
10959
11108
|
provenance: source.provenance ?? null
|
|
10960
11109
|
}
|
|
@@ -10996,7 +11145,7 @@ function compareSessionInstructionSources(a, b) {
|
|
|
10996
11145
|
return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
|
|
10997
11146
|
}
|
|
10998
11147
|
function semanticPolicyIntegrity(body) {
|
|
10999
|
-
return
|
|
11148
|
+
return sha2566(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
|
|
11000
11149
|
}
|
|
11001
11150
|
function semanticPolicyDeclaration(source) {
|
|
11002
11151
|
const normalize = (value) => value.replace(/\r\n/g, `
|
|
@@ -11221,7 +11370,7 @@ function composeSources(sources, tool) {
|
|
|
11221
11370
|
targetSourceId: target.id,
|
|
11222
11371
|
targetNormalizedSourceId: target.normalizedId,
|
|
11223
11372
|
targetHash: target.hash ?? null,
|
|
11224
|
-
targetRenderedPayloadSha256:
|
|
11373
|
+
targetRenderedPayloadSha256: sha2566(target.content),
|
|
11225
11374
|
targetNonOverridable: protectedReplacement,
|
|
11226
11375
|
authority: protectedReplacement ? "canonical-identity-export/codewith-provider/v1" : "overridable-source/v1"
|
|
11227
11376
|
}
|
|
@@ -11312,16 +11461,50 @@ function indexHeader(tool, profile) {
|
|
|
11312
11461
|
].join(`
|
|
11313
11462
|
`);
|
|
11314
11463
|
}
|
|
11315
|
-
function
|
|
11464
|
+
function isCompiledClaudeGlobSource(adapter, source, providerVersion) {
|
|
11465
|
+
if (adapter.tool !== "claude")
|
|
11466
|
+
return false;
|
|
11467
|
+
if (providerVersion && !providerVersionSatisfies(providerVersion, CLAUDE_PATHS_RULES_VERSION_RANGE))
|
|
11468
|
+
return false;
|
|
11469
|
+
const compiledBinding = source.provenance?.["profileBinding"];
|
|
11470
|
+
if (!compiledBinding || typeof compiledBinding !== "object")
|
|
11471
|
+
return false;
|
|
11472
|
+
const activation = source.metadata?.["activation"];
|
|
11473
|
+
return activation?.mode === "glob";
|
|
11474
|
+
}
|
|
11475
|
+
function claudeGlobRuleFile(targetHome, index, source) {
|
|
11476
|
+
const globs = source.globs ?? [];
|
|
11477
|
+
if (globs.length === 0)
|
|
11478
|
+
throw new Error(`Claude glob source ${source.id} has no paths.`);
|
|
11479
|
+
const n = String(index + 1).padStart(2, "0");
|
|
11480
|
+
const relativePath = posix2.join("rules", `${n}-${source.normalizedId}.md`);
|
|
11481
|
+
const content = [
|
|
11482
|
+
"---",
|
|
11483
|
+
"paths:",
|
|
11484
|
+
...globs.map((glob) => ` - ${yamlQuote2(glob)}`),
|
|
11485
|
+
"---",
|
|
11486
|
+
"",
|
|
11487
|
+
sectionForSource(source),
|
|
11488
|
+
...source.resolvedRules.flatMap((rule) => ["", sectionForRule(source, rule)])
|
|
11489
|
+
].join(`
|
|
11490
|
+
`);
|
|
11491
|
+
return makeFile(targetHome, relativePath, "rule", content, [source.id, ...source.resolvedRules.map((rule) => rule.id)]);
|
|
11492
|
+
}
|
|
11493
|
+
function buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion) {
|
|
11316
11494
|
const indexFile = adapter.indexFile;
|
|
11317
|
-
const fragments = sources.flatMap((source, index2) =>
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11495
|
+
const fragments = sources.flatMap((source, index2) => {
|
|
11496
|
+
if (isCompiledClaudeGlobSource(adapter, source, providerVersion))
|
|
11497
|
+
return [];
|
|
11498
|
+
return [
|
|
11499
|
+
makeFile(targetHome, fragmentPath(adapter, index2, source), "fragment", sectionForSource(source), [source.id]),
|
|
11500
|
+
...source.resolvedRules.map((rule) => makeFile(targetHome, ruleFragmentPath(adapter, source, rule), "rule", sectionForRule(source, rule), [source.id, rule.id]))
|
|
11501
|
+
];
|
|
11502
|
+
});
|
|
11503
|
+
const conditionalRules = sources.flatMap((source, index2) => isCompiledClaudeGlobSource(adapter, source, providerVersion) ? [claudeGlobRuleFile(targetHome, index2, source)] : []);
|
|
11321
11504
|
const imports = fragments.map((file) => `@${importPath(indexFile, file.relativePath)}`);
|
|
11322
11505
|
const index = makeFile(targetHome, indexFile, "index", [indexHeader(adapter.tool, profile), ...imports].join(`
|
|
11323
11506
|
`), sources.map((source) => source.id));
|
|
11324
|
-
return [index, ...fragments];
|
|
11507
|
+
return [index, ...fragments, ...conditionalRules];
|
|
11325
11508
|
}
|
|
11326
11509
|
function buildFlattenedMarkdownFiles(targetHome, adapter, profile, sources) {
|
|
11327
11510
|
const content = [
|
|
@@ -11403,7 +11586,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
11403
11586
|
...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
|
|
11404
11587
|
]);
|
|
11405
11588
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
11406
|
-
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(
|
|
11589
|
+
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
11407
11590
|
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
11408
11591
|
const config = {
|
|
11409
11592
|
...selectedConfig,
|
|
@@ -11509,10 +11692,10 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
|
|
|
11509
11692
|
}
|
|
11510
11693
|
return file;
|
|
11511
11694
|
}
|
|
11512
|
-
function buildFiles(targetHome, adapter, profile, sources, providerConfig) {
|
|
11695
|
+
function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
|
|
11513
11696
|
switch (adapter.mode) {
|
|
11514
11697
|
case "native-imports":
|
|
11515
|
-
return buildNativeImportFiles(targetHome, adapter, profile, sources);
|
|
11698
|
+
return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
|
|
11516
11699
|
case "flattened-markdown":
|
|
11517
11700
|
return buildFlattenedMarkdownFiles(targetHome, adapter, profile, sources);
|
|
11518
11701
|
case "cursor-mdc":
|
|
@@ -11552,7 +11735,7 @@ function buildAssetFiles(input, targetHome, blocked) {
|
|
|
11552
11735
|
relativePath: assertSafeRelativePath(relativePath),
|
|
11553
11736
|
role: "asset",
|
|
11554
11737
|
content,
|
|
11555
|
-
sha256:
|
|
11738
|
+
sha256: sha2566(content),
|
|
11556
11739
|
sourceIds: [item.sourceConfigId, item.assetId]
|
|
11557
11740
|
};
|
|
11558
11741
|
});
|
|
@@ -11592,7 +11775,7 @@ function adapterFor(input) {
|
|
|
11592
11775
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
11593
11776
|
}
|
|
11594
11777
|
function getHomeDir() {
|
|
11595
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
11778
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
|
|
11596
11779
|
}
|
|
11597
11780
|
function cleanSessionPathInput(path) {
|
|
11598
11781
|
const trimmed = path.trim();
|
|
@@ -11607,16 +11790,16 @@ function resolveSessionPath(path) {
|
|
|
11607
11790
|
throw new Error("Session render path cannot be empty.");
|
|
11608
11791
|
const home = getHomeDir();
|
|
11609
11792
|
if (cleaned === "~")
|
|
11610
|
-
return
|
|
11793
|
+
return resolve6(home);
|
|
11611
11794
|
if (cleaned.startsWith("~/"))
|
|
11612
|
-
return
|
|
11795
|
+
return resolve6(home, cleaned.slice(2));
|
|
11613
11796
|
if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
|
|
11614
|
-
return
|
|
11797
|
+
return resolve6(home);
|
|
11615
11798
|
if (cleaned.startsWith("{{HOME}}/"))
|
|
11616
|
-
return
|
|
11799
|
+
return resolve6(home, cleaned.slice("{{HOME}}/".length));
|
|
11617
11800
|
if (cleaned.startsWith("${HOME}/"))
|
|
11618
|
-
return
|
|
11619
|
-
return
|
|
11801
|
+
return resolve6(home, cleaned.slice("${HOME}/".length));
|
|
11802
|
+
return resolve6(cleaned);
|
|
11620
11803
|
}
|
|
11621
11804
|
function assertSafeRelativePath(relativePath) {
|
|
11622
11805
|
if (!relativePath.trim())
|
|
@@ -11632,7 +11815,7 @@ function assertSafeRelativePath(relativePath) {
|
|
|
11632
11815
|
function assertSafeTargetRoot(targetHome) {
|
|
11633
11816
|
if (!isAbsolute3(targetHome))
|
|
11634
11817
|
throw new Error(`Session render target must be an absolute path: ${targetHome}`);
|
|
11635
|
-
const normalized =
|
|
11818
|
+
const normalized = resolve6(targetHome);
|
|
11636
11819
|
if (normalized === parse2(normalized).root) {
|
|
11637
11820
|
throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
|
|
11638
11821
|
}
|
|
@@ -11730,7 +11913,7 @@ function planSessionRender(input) {
|
|
|
11730
11913
|
blockers: targetBlockers
|
|
11731
11914
|
} = resolveRenderTarget(input);
|
|
11732
11915
|
const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
|
|
11733
|
-
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : [];
|
|
11916
|
+
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
|
|
11734
11917
|
const blockers = [
|
|
11735
11918
|
...targetBlockers,
|
|
11736
11919
|
...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
|
|
@@ -11759,7 +11942,7 @@ function planSessionRender(input) {
|
|
|
11759
11942
|
if (input.providerConfig && input.tool !== "opencode") {
|
|
11760
11943
|
throw new Error("Provider base config is supported only for OpenCode session renders.");
|
|
11761
11944
|
}
|
|
11762
|
-
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig);
|
|
11945
|
+
const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
|
|
11763
11946
|
const projectContext = blocked ? null : composeProjectContextSessionRender({
|
|
11764
11947
|
tool: input.tool,
|
|
11765
11948
|
adapter_mode: adapter.mode,
|
|
@@ -11828,7 +12011,7 @@ function planSessionRender(input) {
|
|
|
11828
12011
|
hash: rule.hash ?? null,
|
|
11829
12012
|
...ruleAttestation(rule)
|
|
11830
12013
|
})),
|
|
11831
|
-
renderedPayloadSha256:
|
|
12014
|
+
renderedPayloadSha256: sha2566(source.content),
|
|
11832
12015
|
provenance: source.provenance ?? null,
|
|
11833
12016
|
metadata: source.metadata ?? null
|
|
11834
12017
|
})),
|
|
@@ -11866,8 +12049,8 @@ function planSessionRender(input) {
|
|
|
11866
12049
|
...input.providerConfig ? {
|
|
11867
12050
|
providerConfig: {
|
|
11868
12051
|
sourceId: input.providerConfig.sourceId,
|
|
11869
|
-
selectedPayloadSha256:
|
|
11870
|
-
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ??
|
|
12052
|
+
selectedPayloadSha256: sha2566(input.providerConfig.content),
|
|
12053
|
+
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
|
|
11871
12054
|
selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
|
|
11872
12055
|
}
|
|
11873
12056
|
} : {},
|
|
@@ -12153,7 +12336,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
12153
12336
|
}
|
|
12154
12337
|
return;
|
|
12155
12338
|
}
|
|
12156
|
-
const stat =
|
|
12339
|
+
const stat = statSync3(resolvedPath);
|
|
12157
12340
|
if (!stat.isFile()) {
|
|
12158
12341
|
throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
|
|
12159
12342
|
}
|
|
@@ -12162,7 +12345,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
12162
12345
|
if (!pathIsInside(realPath, realBase)) {
|
|
12163
12346
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
|
|
12164
12347
|
}
|
|
12165
|
-
return
|
|
12348
|
+
return readFileSync4(realPath, "utf-8");
|
|
12166
12349
|
}
|
|
12167
12350
|
function resolveIdentitySourcePath(path, baseDir, sourceId) {
|
|
12168
12351
|
const cleaned = cleanSessionPathInput(path);
|
|
@@ -12170,8 +12353,8 @@ function resolveIdentitySourcePath(path, baseDir, sourceId) {
|
|
|
12170
12353
|
throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
|
|
12171
12354
|
if (cleaned.includes("\\"))
|
|
12172
12355
|
throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
|
|
12173
|
-
const resolvedPath = isAbsolute3(cleaned) ?
|
|
12174
|
-
if (!pathIsInside(resolvedPath,
|
|
12356
|
+
const resolvedPath = isAbsolute3(cleaned) ? resolve6(cleaned) : resolve6(baseDir, cleaned);
|
|
12357
|
+
if (!pathIsInside(resolvedPath, resolve6(baseDir))) {
|
|
12175
12358
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
|
|
12176
12359
|
}
|
|
12177
12360
|
return resolvedPath;
|
|
@@ -12259,7 +12442,7 @@ function asStringArray(value) {
|
|
|
12259
12442
|
return [];
|
|
12260
12443
|
return value.filter((item) => typeof item === "string");
|
|
12261
12444
|
}
|
|
12262
|
-
var
|
|
12445
|
+
var ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, IDENTITY_EXPORT_AUTHORITY, CODEWITH_PROTECTED_REPLACEMENT_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CLAUDE_PATHS_RULES_VERSION_RANGE = ">=2.1.84", CANONICAL_IDENTITY_EXPORT_PACKAGES;
|
|
12263
12446
|
var init_session_render = __esm(() => {
|
|
12264
12447
|
init_global_agent_rules_standard();
|
|
12265
12448
|
init_codewith_shared_todos_storage_standard();
|
|
@@ -12268,8 +12451,11 @@ var init_session_render = __esm(() => {
|
|
|
12268
12451
|
init_transforms();
|
|
12269
12452
|
init_asset_plan();
|
|
12270
12453
|
init_cursor_authority();
|
|
12454
|
+
init_session_authority();
|
|
12271
12455
|
init_session_render_contract();
|
|
12272
12456
|
init_session_render_contract();
|
|
12457
|
+
init_raw_store_root();
|
|
12458
|
+
init_raw_store_root();
|
|
12273
12459
|
SESSION_RENDER_TOOLS = [
|
|
12274
12460
|
"claude",
|
|
12275
12461
|
"codex",
|
|
@@ -12470,7 +12656,7 @@ var init_session_render = __esm(() => {
|
|
|
12470
12656
|
});
|
|
12471
12657
|
|
|
12472
12658
|
// src/lib/instruction-graph.ts
|
|
12473
|
-
import { createHash as
|
|
12659
|
+
import { createHash as createHash7 } from "crypto";
|
|
12474
12660
|
function capability(provider, providerVersionRange, selectedRepresentation, loadingPath, options = {}) {
|
|
12475
12661
|
return Object.freeze({
|
|
12476
12662
|
schema: PROVIDER_CAPABILITY_SCHEMA,
|
|
@@ -12717,7 +12903,7 @@ function compileInstructionGraph(input) {
|
|
|
12717
12903
|
effective_activation: effective.get(configId),
|
|
12718
12904
|
fallback: row.binding.fallback,
|
|
12719
12905
|
required: row.binding.required,
|
|
12720
|
-
content_sha256:
|
|
12906
|
+
content_sha256: sha2567(config.content),
|
|
12721
12907
|
dependencies: dependencies.get(configId) ?? []
|
|
12722
12908
|
};
|
|
12723
12909
|
});
|
|
@@ -12754,7 +12940,7 @@ function compileInstructionGraph(input) {
|
|
|
12754
12940
|
diagnostics
|
|
12755
12941
|
};
|
|
12756
12942
|
return {
|
|
12757
|
-
plan: deepFreeze2({ ...planWithoutHash, source_hash:
|
|
12943
|
+
plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2567(stableJson2(planWithoutHash)) }),
|
|
12758
12944
|
sources,
|
|
12759
12945
|
capability: capability2
|
|
12760
12946
|
};
|
|
@@ -12779,7 +12965,6 @@ function planProfileSessionRender(input) {
|
|
|
12779
12965
|
});
|
|
12780
12966
|
const {
|
|
12781
12967
|
profile_id: _profileId,
|
|
12782
|
-
provider_version: _providerVersion,
|
|
12783
12968
|
configs: _configs,
|
|
12784
12969
|
bindings: _bindings,
|
|
12785
12970
|
asset_configs: _assetConfigs,
|
|
@@ -12928,8 +13113,8 @@ function assertExactOnce(units, artifacts) {
|
|
|
12928
13113
|
function errorDiagnostic(configId, code, message) {
|
|
12929
13114
|
return { severity: "error", code, config_id: configId, message };
|
|
12930
13115
|
}
|
|
12931
|
-
function
|
|
12932
|
-
return
|
|
13116
|
+
function sha2567(value) {
|
|
13117
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
12933
13118
|
}
|
|
12934
13119
|
function stableJson2(value) {
|
|
12935
13120
|
const canonical = (entry) => Array.isArray(entry) ? entry.map(canonical) : entry && typeof entry === "object" ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, canonical(child)])) : entry;
|
|
@@ -12949,7 +13134,12 @@ var init_instruction_graph = __esm(() => {
|
|
|
12949
13134
|
init_asset_plan();
|
|
12950
13135
|
COMMON_FALLBACKS = ["fail", "flatten", "promote-always", "omit"];
|
|
12951
13136
|
DEFAULT_PROVIDER_CAPABILITIES = Object.freeze({
|
|
12952
|
-
claude: capability("claude", ">=1.0.0", "native-import", "CLAUDE.md @ imports
|
|
13137
|
+
claude: capability("claude", ">=1.0.0", "native-import", "CLAUDE.md @ imports plus path-gated rules", {
|
|
13138
|
+
activation_modes: ["always", "glob"],
|
|
13139
|
+
native_imports: true,
|
|
13140
|
+
conditional_artifacts: true,
|
|
13141
|
+
asset_surface: "code"
|
|
13142
|
+
}),
|
|
12953
13143
|
codex: capability("codex", ">=0.1.0", "flattened", "AGENTS.md", { asset_surface: "cli" }),
|
|
12954
13144
|
cursor: capability("cursor", ">=1.0.0", "cursor-rule", ".cursor/rules/*.mdc", { activation_modes: ["always", "glob"], conditional_artifacts: true, asset_surface: "ide" }),
|
|
12955
13145
|
opencode: capability("opencode", ">=1.0.0", "managed-fragment", "opencode.json instructions", { provider_variant: "v1-instructions", session_surface: "opencode-config-instructions" }),
|
|
@@ -13228,37 +13418,6 @@ var init_profiles = __esm(() => {
|
|
|
13228
13418
|
init_asset_plan();
|
|
13229
13419
|
});
|
|
13230
13420
|
|
|
13231
|
-
// src/db/snapshots.ts
|
|
13232
|
-
function createSnapshot(configId, content, version, db) {
|
|
13233
|
-
const d = db || getDatabase();
|
|
13234
|
-
const id = uuid();
|
|
13235
|
-
const ts = now2();
|
|
13236
|
-
d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
|
|
13237
|
-
return { id, config_id: configId, content, version, created_at: ts };
|
|
13238
|
-
}
|
|
13239
|
-
function listSnapshots(configId, db) {
|
|
13240
|
-
const d = db || getDatabase();
|
|
13241
|
-
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
|
|
13242
|
-
}
|
|
13243
|
-
function getSnapshot(id, db) {
|
|
13244
|
-
const d = db || getDatabase();
|
|
13245
|
-
return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
|
|
13246
|
-
}
|
|
13247
|
-
function getSnapshotByVersion(configId, version, db) {
|
|
13248
|
-
const d = db || getDatabase();
|
|
13249
|
-
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
|
|
13250
|
-
}
|
|
13251
|
-
function pruneSnapshots(configId, keep = 10, db) {
|
|
13252
|
-
const d = db || getDatabase();
|
|
13253
|
-
const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
|
|
13254
|
-
SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
|
|
13255
|
-
)`, [configId, configId, keep]);
|
|
13256
|
-
return result.changes;
|
|
13257
|
-
}
|
|
13258
|
-
var init_snapshots = __esm(() => {
|
|
13259
|
-
init_database();
|
|
13260
|
-
});
|
|
13261
|
-
|
|
13262
13421
|
// src/db/machines.ts
|
|
13263
13422
|
import { arch, hostname, type } from "os";
|
|
13264
13423
|
function currentHostname2() {
|
|
@@ -13350,16 +13509,17 @@ function formatCliError(err, env = process.env) {
|
|
|
13350
13509
|
return err instanceof Error ? err.message : String(err);
|
|
13351
13510
|
}
|
|
13352
13511
|
function resolveCloudConfig(env = process.env) {
|
|
13512
|
+
assertNoLegacyStorageMode(env);
|
|
13353
13513
|
const apiUrl = env[API_URL_ENV]?.trim();
|
|
13354
13514
|
const apiKey = env[API_KEY_ENV]?.trim();
|
|
13355
13515
|
if (!apiUrl && !apiKey)
|
|
13356
13516
|
return null;
|
|
13357
13517
|
if (!apiUrl || !apiKey) {
|
|
13358
|
-
throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the
|
|
13518
|
+
throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the HTTP API, ` + `or unset both to use the local store.`);
|
|
13359
13519
|
}
|
|
13360
13520
|
return { apiUrl, apiKey };
|
|
13361
13521
|
}
|
|
13362
|
-
function
|
|
13522
|
+
function isApiTransport(env = process.env) {
|
|
13363
13523
|
return resolveCloudConfig(env) !== null;
|
|
13364
13524
|
}
|
|
13365
13525
|
|
|
@@ -13845,6 +14005,7 @@ var init_config_store = __esm(() => {
|
|
|
13845
14005
|
init_types();
|
|
13846
14006
|
init_bounded_read();
|
|
13847
14007
|
init_instruction_graph();
|
|
14008
|
+
init_retired_storage_mode();
|
|
13848
14009
|
CloudHttpError = class CloudHttpError extends Error {
|
|
13849
14010
|
status;
|
|
13850
14011
|
body;
|
|
@@ -13858,8 +14019,8 @@ var init_config_store = __esm(() => {
|
|
|
13858
14019
|
});
|
|
13859
14020
|
|
|
13860
14021
|
// src/lib/session-render-ownership.ts
|
|
13861
|
-
import { existsSync as existsSync6, readFileSync as
|
|
13862
|
-
import { dirname as dirname3, join as
|
|
14022
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
14023
|
+
import { dirname as dirname3, join as join9, parse as parse3, relative as relative3, sep } from "path";
|
|
13863
14024
|
function toSegments(absolutePath2) {
|
|
13864
14025
|
return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
13865
14026
|
}
|
|
@@ -13880,7 +14041,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
13880
14041
|
try {
|
|
13881
14042
|
if (!existsSync6(manifestPath))
|
|
13882
14043
|
return null;
|
|
13883
|
-
stats =
|
|
14044
|
+
stats = statSync4(manifestPath);
|
|
13884
14045
|
} catch {
|
|
13885
14046
|
return null;
|
|
13886
14047
|
}
|
|
@@ -13890,7 +14051,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
13890
14051
|
}
|
|
13891
14052
|
let manifest;
|
|
13892
14053
|
try {
|
|
13893
|
-
manifest = JSON.parse(
|
|
14054
|
+
manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
13894
14055
|
} catch {
|
|
13895
14056
|
return null;
|
|
13896
14057
|
}
|
|
@@ -13907,7 +14068,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
|
13907
14068
|
const root = parse3(absolutePath2).root;
|
|
13908
14069
|
let home = dirname3(absolutePath2);
|
|
13909
14070
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
13910
|
-
const manifestPath =
|
|
14071
|
+
const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
13911
14072
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
13912
14073
|
if (relativePaths) {
|
|
13913
14074
|
const claimed = relative3(home, absolutePath2).split(sep).join("/");
|
|
@@ -13942,17 +14103,17 @@ __export(exports_apply, {
|
|
|
13942
14103
|
applyConfigs: () => applyConfigs,
|
|
13943
14104
|
applyConfig: () => applyConfig
|
|
13944
14105
|
});
|
|
13945
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as
|
|
13946
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
13947
|
-
import { homedir as
|
|
14106
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
14107
|
+
import { basename as basename4, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
|
|
14108
|
+
import { homedir as homedir6 } from "os";
|
|
13948
14109
|
function getConfigHome() {
|
|
13949
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
14110
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
13950
14111
|
}
|
|
13951
14112
|
function expandPath(p) {
|
|
13952
14113
|
if (p.startsWith("~/")) {
|
|
13953
|
-
return
|
|
14114
|
+
return resolve7(getConfigHome(), p.slice(2));
|
|
13954
14115
|
}
|
|
13955
|
-
return
|
|
14116
|
+
return resolve7(p);
|
|
13956
14117
|
}
|
|
13957
14118
|
function normalizeTargetPath(p) {
|
|
13958
14119
|
const expanded = expandPath(p);
|
|
@@ -13964,7 +14125,7 @@ function normalizeTargetPath(p) {
|
|
|
13964
14125
|
while (true) {
|
|
13965
14126
|
if (existsSync7(current)) {
|
|
13966
14127
|
try {
|
|
13967
|
-
return
|
|
14128
|
+
return resolve7(realpathSync2(current), ...missingSegments);
|
|
13968
14129
|
} catch {
|
|
13969
14130
|
return expanded;
|
|
13970
14131
|
}
|
|
@@ -13993,7 +14154,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
13993
14154
|
}
|
|
13994
14155
|
const path = expandPath(renderedTargetPath);
|
|
13995
14156
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
13996
|
-
const previousContent = existsSync7(path) ?
|
|
14157
|
+
const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
|
|
13997
14158
|
const changed = previousContent !== renderedForTarget;
|
|
13998
14159
|
if (!opts.dryRun) {
|
|
13999
14160
|
const dir = dirname4(path);
|
|
@@ -14033,7 +14194,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
14033
14194
|
const path = expandPath(targetPath);
|
|
14034
14195
|
if (!existsSync7(path))
|
|
14035
14196
|
return [];
|
|
14036
|
-
current =
|
|
14197
|
+
current = readFileSync6(path, "utf-8");
|
|
14037
14198
|
} catch {
|
|
14038
14199
|
return secretTokens;
|
|
14039
14200
|
}
|
|
@@ -14357,7 +14518,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
|
14357
14518
|
getConfigHome(),
|
|
14358
14519
|
opts.vars?.["HOME_DIR"]
|
|
14359
14520
|
].filter((home) => typeof home === "string" && home.length > 0));
|
|
14360
|
-
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(
|
|
14521
|
+
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join10(home, ...relativePath.split("/"))))))
|
|
14361
14522
|
return true;
|
|
14362
14523
|
return sessionRenderOwnsPath(normalized);
|
|
14363
14524
|
}
|
|
@@ -14375,9 +14536,9 @@ var init_apply = __esm(() => {
|
|
|
14375
14536
|
});
|
|
14376
14537
|
|
|
14377
14538
|
// src/lib/sync-dir.ts
|
|
14378
|
-
import { existsSync as existsSync8, readdirSync, readFileSync as
|
|
14379
|
-
import { join as
|
|
14380
|
-
import { homedir as
|
|
14539
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
14540
|
+
import { join as join11, relative as relative4 } from "path";
|
|
14541
|
+
import { homedir as homedir7 } from "os";
|
|
14381
14542
|
function shouldSkip(p) {
|
|
14382
14543
|
return SKIP.some((s) => p.includes(s));
|
|
14383
14544
|
}
|
|
@@ -14386,9 +14547,9 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14386
14547
|
const absDir = expandPath(dir);
|
|
14387
14548
|
if (!existsSync8(absDir))
|
|
14388
14549
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14389
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) =>
|
|
14550
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
|
|
14390
14551
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14391
|
-
const home =
|
|
14552
|
+
const home = homedir7();
|
|
14392
14553
|
const allConfigs = await store.listConfigs();
|
|
14393
14554
|
for (const file of files) {
|
|
14394
14555
|
if (shouldSkip(file)) {
|
|
@@ -14396,7 +14557,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14396
14557
|
continue;
|
|
14397
14558
|
}
|
|
14398
14559
|
try {
|
|
14399
|
-
const content =
|
|
14560
|
+
const content = readFileSync7(file, "utf-8");
|
|
14400
14561
|
if (content.length > 500000) {
|
|
14401
14562
|
result.skipped.push(file + " (too large)");
|
|
14402
14563
|
continue;
|
|
@@ -14422,7 +14583,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14422
14583
|
}
|
|
14423
14584
|
async function syncToDir(dir, opts = {}) {
|
|
14424
14585
|
const store = opts.store ?? resolveConfigStore();
|
|
14425
|
-
const home =
|
|
14586
|
+
const home = homedir7();
|
|
14426
14587
|
const absDir = expandPath(dir);
|
|
14427
14588
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14428
14589
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14446,7 +14607,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
14446
14607
|
}
|
|
14447
14608
|
function walkDir(dir, files = []) {
|
|
14448
14609
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14449
|
-
const full =
|
|
14610
|
+
const full = join11(dir, entry.name);
|
|
14450
14611
|
if (shouldSkip(full))
|
|
14451
14612
|
continue;
|
|
14452
14613
|
if (entry.isDirectory())
|
|
@@ -14480,8 +14641,8 @@ __export(exports_sync, {
|
|
|
14480
14641
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS,
|
|
14481
14642
|
CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
|
|
14482
14643
|
});
|
|
14483
|
-
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as
|
|
14484
|
-
import { basename as basename5, extname as extname3, join as
|
|
14644
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
14645
|
+
import { basename as basename5, extname as extname3, join as join12 } from "path";
|
|
14485
14646
|
function claudeRuleOutputs(fileName) {
|
|
14486
14647
|
const stem = basename5(fileName, extname3(fileName));
|
|
14487
14648
|
return [
|
|
@@ -14545,11 +14706,11 @@ async function syncProject(opts) {
|
|
|
14545
14706
|
const allConfigs = await store.listConfigs();
|
|
14546
14707
|
const machine = detectMachineContext();
|
|
14547
14708
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14548
|
-
const abs =
|
|
14709
|
+
const abs = join12(absDir, pf.file);
|
|
14549
14710
|
if (!existsSync9(abs))
|
|
14550
14711
|
continue;
|
|
14551
14712
|
try {
|
|
14552
|
-
const rawContent =
|
|
14713
|
+
const rawContent = readFileSync8(abs, "utf-8");
|
|
14553
14714
|
if (rawContent.length > 500000) {
|
|
14554
14715
|
result.skipped.push(pf.file);
|
|
14555
14716
|
continue;
|
|
@@ -14578,20 +14739,20 @@ async function syncProject(opts) {
|
|
|
14578
14739
|
}
|
|
14579
14740
|
}
|
|
14580
14741
|
for (const ruleDir of [
|
|
14581
|
-
{ dir:
|
|
14582
|
-
{ dir:
|
|
14583
|
-
{ dir:
|
|
14584
|
-
{ dir:
|
|
14585
|
-
{ dir:
|
|
14586
|
-
{ dir:
|
|
14587
|
-
{ dir:
|
|
14742
|
+
{ dir: join12(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
14743
|
+
{ dir: join12(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
14744
|
+
{ dir: join12(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
14745
|
+
{ dir: join12(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
14746
|
+
{ dir: join12(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
14747
|
+
{ dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
14748
|
+
{ dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14588
14749
|
]) {
|
|
14589
14750
|
if (!existsSync9(ruleDir.dir))
|
|
14590
14751
|
continue;
|
|
14591
14752
|
const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14592
14753
|
for (const f of mdFiles) {
|
|
14593
|
-
const abs =
|
|
14594
|
-
const raw =
|
|
14754
|
+
const abs = join12(ruleDir.dir, f);
|
|
14755
|
+
const raw = readFileSync8(abs, "utf-8");
|
|
14595
14756
|
const redacted = redactContent(raw, "markdown");
|
|
14596
14757
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14597
14758
|
const content = machineAware.content;
|
|
@@ -14637,13 +14798,13 @@ async function syncKnown(opts = {}) {
|
|
|
14637
14798
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
14638
14799
|
const ruleFiles = readdirSync2(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
14639
14800
|
for (const f of ruleFiles) {
|
|
14640
|
-
const abs2 =
|
|
14801
|
+
const abs2 = join12(absDir, f);
|
|
14641
14802
|
const targetPath = abs2.replace(home, "~");
|
|
14642
14803
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
14643
14804
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
14644
14805
|
continue;
|
|
14645
14806
|
}
|
|
14646
|
-
const raw =
|
|
14807
|
+
const raw = readFileSync8(abs2, "utf-8");
|
|
14647
14808
|
const redacted = redactContent(raw, "markdown");
|
|
14648
14809
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14649
14810
|
const content = machineAware.content;
|
|
@@ -14676,7 +14837,7 @@ async function syncKnown(opts = {}) {
|
|
|
14676
14837
|
continue;
|
|
14677
14838
|
}
|
|
14678
14839
|
try {
|
|
14679
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
14840
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
|
|
14680
14841
|
if (rawContent.length > 500000) {
|
|
14681
14842
|
result.skipped.push(known.path + " (too large)");
|
|
14682
14843
|
continue;
|
|
@@ -14779,7 +14940,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
|
14779
14940
|
const path = expandPath(targetPath);
|
|
14780
14941
|
if (!existsSync9(path))
|
|
14781
14942
|
return `(file not found on disk: ${path})`;
|
|
14782
|
-
const diskContent =
|
|
14943
|
+
const diskContent = readFileSync8(path, "utf-8");
|
|
14783
14944
|
if (diskContent === expectedContent)
|
|
14784
14945
|
return "(no diff \u2014 identical)";
|
|
14785
14946
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -15012,18 +15173,18 @@ __export(exports_package_manager_guard, {
|
|
|
15012
15173
|
scanPackageManagerSecrets: () => scanPackageManagerSecrets
|
|
15013
15174
|
});
|
|
15014
15175
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15015
|
-
import { existsSync as
|
|
15016
|
-
import { homedir as
|
|
15017
|
-
import { basename as basename6, dirname as
|
|
15176
|
+
import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
|
|
15177
|
+
import { homedir as homedir9 } from "os";
|
|
15178
|
+
import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative7, resolve as resolve12 } from "path";
|
|
15018
15179
|
function scanPackageManagerSecrets(options = {}) {
|
|
15019
|
-
const cwd = options.cwd ?
|
|
15020
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
15180
|
+
const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
|
|
15181
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve12(cwd, root));
|
|
15021
15182
|
const findings = [];
|
|
15022
15183
|
let scannedFiles = 0;
|
|
15023
15184
|
for (const root of roots) {
|
|
15024
|
-
if (!
|
|
15185
|
+
if (!existsSync16(root))
|
|
15025
15186
|
continue;
|
|
15026
|
-
const stat =
|
|
15187
|
+
const stat = lstatSync6(root);
|
|
15027
15188
|
if (stat.isFile()) {
|
|
15028
15189
|
if (!shouldScanRepoFile(root))
|
|
15029
15190
|
continue;
|
|
@@ -15031,14 +15192,14 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15031
15192
|
if (text === null)
|
|
15032
15193
|
continue;
|
|
15033
15194
|
scannedFiles++;
|
|
15034
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15195
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
|
|
15035
15196
|
continue;
|
|
15036
15197
|
}
|
|
15037
15198
|
if (!stat.isDirectory())
|
|
15038
15199
|
continue;
|
|
15039
15200
|
const tracked = trackedFiles(root);
|
|
15040
15201
|
for (const file of collectRepoFiles(root)) {
|
|
15041
|
-
const rel = toPosix(
|
|
15202
|
+
const rel = toPosix(relative7(root, file));
|
|
15042
15203
|
const isTracked = tracked.has(rel);
|
|
15043
15204
|
const text = readTextFile(file);
|
|
15044
15205
|
if (text === null)
|
|
@@ -15048,10 +15209,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15048
15209
|
}
|
|
15049
15210
|
}
|
|
15050
15211
|
if (options.includeHome) {
|
|
15051
|
-
const home =
|
|
15212
|
+
const home = homedir9();
|
|
15052
15213
|
for (const name of HOME_FILES) {
|
|
15053
|
-
const file =
|
|
15054
|
-
if (!
|
|
15214
|
+
const file = join18(home, name);
|
|
15215
|
+
if (!existsSync16(file))
|
|
15055
15216
|
continue;
|
|
15056
15217
|
const text = readTextFile(file);
|
|
15057
15218
|
if (text === null)
|
|
@@ -15075,12 +15236,12 @@ function collectRepoFiles(root) {
|
|
|
15075
15236
|
if (entry.isDirectory()) {
|
|
15076
15237
|
if (SKIP_DIRS.has(entry.name))
|
|
15077
15238
|
continue;
|
|
15078
|
-
visit(
|
|
15239
|
+
visit(join18(dir, entry.name));
|
|
15079
15240
|
continue;
|
|
15080
15241
|
}
|
|
15081
15242
|
if (!entry.isFile())
|
|
15082
15243
|
continue;
|
|
15083
|
-
const file =
|
|
15244
|
+
const file = join18(dir, entry.name);
|
|
15084
15245
|
if (shouldScanRepoFile(file))
|
|
15085
15246
|
out.push(file);
|
|
15086
15247
|
}
|
|
@@ -15115,10 +15276,10 @@ function isNpmrcName(name) {
|
|
|
15115
15276
|
}
|
|
15116
15277
|
function readTextFile(file) {
|
|
15117
15278
|
try {
|
|
15118
|
-
const stat =
|
|
15279
|
+
const stat = lstatSync6(file);
|
|
15119
15280
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15120
15281
|
return null;
|
|
15121
|
-
const buf =
|
|
15282
|
+
const buf = readFileSync14(file);
|
|
15122
15283
|
if (buf.includes(0))
|
|
15123
15284
|
return null;
|
|
15124
15285
|
return buf.toString("utf-8");
|
|
@@ -15318,11 +15479,11 @@ function trackedFiles(root) {
|
|
|
15318
15479
|
}
|
|
15319
15480
|
function isTrackedFile(file) {
|
|
15320
15481
|
try {
|
|
15321
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
15482
|
+
const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
|
|
15322
15483
|
encoding: "utf-8",
|
|
15323
15484
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15324
15485
|
}).trim();
|
|
15325
|
-
const rel = toPosix(
|
|
15486
|
+
const rel = toPosix(relative7(repoRoot, file));
|
|
15326
15487
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
15327
15488
|
stdio: ["ignore", "ignore", "ignore"]
|
|
15328
15489
|
});
|
|
@@ -15348,13 +15509,13 @@ function stripInlineComment(value) {
|
|
|
15348
15509
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15349
15510
|
}
|
|
15350
15511
|
function displayPath(file, root) {
|
|
15351
|
-
const home =
|
|
15512
|
+
const home = homedir9();
|
|
15352
15513
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15353
|
-
return "~/" + toPosix(
|
|
15514
|
+
return "~/" + toPosix(relative7(home, file));
|
|
15354
15515
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|
|
15355
|
-
return toPosix(
|
|
15516
|
+
return toPosix(relative7(root, file));
|
|
15356
15517
|
if (file === home || file.startsWith(home + "/"))
|
|
15357
|
-
return "~/" + toPosix(
|
|
15518
|
+
return "~/" + toPosix(relative7(home, file));
|
|
15358
15519
|
return file;
|
|
15359
15520
|
}
|
|
15360
15521
|
function toPosix(path) {
|
|
@@ -15392,15 +15553,16 @@ var init_package_manager_guard = __esm(() => {
|
|
|
15392
15553
|
TOKEN_VALUE_PATTERNS = [
|
|
15393
15554
|
{ re: /npm_[A-Za-z0-9]{36,}/, rule: "literal-npm-token", detail: "literal npm token-like value" },
|
|
15394
15555
|
{ re: /gh[pousr]_[A-Za-z0-9_]{36,}/, rule: "literal-github-token", detail: "literal GitHub token-like value" },
|
|
15395
|
-
{ re: /sk-ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
|
|
15556
|
+
{ re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
|
|
15396
15557
|
{ re: /sk-[A-Za-z0-9]{48,}/, rule: "literal-openai-key", detail: "literal OpenAI key-like value" },
|
|
15397
15558
|
{ re: /AKIA[0-9A-Z]{16}/, rule: "literal-aws-access-key", detail: "literal AWS access-key-like value" },
|
|
15398
15559
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
15399
15560
|
];
|
|
15400
15561
|
});
|
|
15401
15562
|
|
|
15402
|
-
//
|
|
15563
|
+
// ../events/dist/commander.js
|
|
15403
15564
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
15565
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
15404
15566
|
import { existsSync } from "fs";
|
|
15405
15567
|
import { homedir } from "os";
|
|
15406
15568
|
import { join } from "path";
|
|
@@ -15416,29 +15578,83 @@ function getPathValue(input, path) {
|
|
|
15416
15578
|
return;
|
|
15417
15579
|
}, input);
|
|
15418
15580
|
}
|
|
15419
|
-
function
|
|
15420
|
-
const
|
|
15421
|
-
|
|
15581
|
+
function getFieldValues(input, path) {
|
|
15582
|
+
const values = [];
|
|
15583
|
+
const push = (value) => {
|
|
15584
|
+
if (!values.some((item) => Object.is(item, value)))
|
|
15585
|
+
values.push(value);
|
|
15586
|
+
};
|
|
15587
|
+
if (path.includes(".") && path in input)
|
|
15588
|
+
push(input[path]);
|
|
15589
|
+
const nestedValue = getPathValue(input, path);
|
|
15590
|
+
if (nestedValue !== undefined || !path.includes("."))
|
|
15591
|
+
push(nestedValue);
|
|
15592
|
+
return values;
|
|
15593
|
+
}
|
|
15594
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
15595
|
+
let body = "";
|
|
15596
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
15597
|
+
const char = pattern[index];
|
|
15598
|
+
if (char === "*") {
|
|
15599
|
+
if (pattern[index + 1] === "*") {
|
|
15600
|
+
body += ".*";
|
|
15601
|
+
index += 1;
|
|
15602
|
+
} else {
|
|
15603
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
15604
|
+
}
|
|
15605
|
+
} else {
|
|
15606
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
15607
|
+
}
|
|
15608
|
+
}
|
|
15609
|
+
return new RegExp(`^${body}$`);
|
|
15422
15610
|
}
|
|
15423
|
-
function matchString(value, matcher) {
|
|
15611
|
+
function matchString(value, matcher, options = {}) {
|
|
15424
15612
|
if (matcher === undefined)
|
|
15425
15613
|
return true;
|
|
15426
15614
|
if (value === undefined)
|
|
15427
15615
|
return false;
|
|
15428
15616
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
15429
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
15617
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
15430
15618
|
}
|
|
15431
15619
|
function matchRecord(input, matcher) {
|
|
15432
15620
|
if (!matcher)
|
|
15433
15621
|
return true;
|
|
15434
15622
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
15435
|
-
const
|
|
15436
|
-
|
|
15437
|
-
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
15438
|
-
}
|
|
15439
|
-
return actual === expected;
|
|
15623
|
+
const actualValues = getFieldValues(input, path);
|
|
15624
|
+
return matchField(actualValues, expected, path);
|
|
15440
15625
|
});
|
|
15441
15626
|
}
|
|
15627
|
+
function matchField(actualValues, expected, path) {
|
|
15628
|
+
if (isNegativeMatcher(expected)) {
|
|
15629
|
+
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
15630
|
+
}
|
|
15631
|
+
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
15632
|
+
}
|
|
15633
|
+
function matchPositiveField(actual, expected, path) {
|
|
15634
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
15635
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
15636
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
15637
|
+
}));
|
|
15638
|
+
}
|
|
15639
|
+
if (Array.isArray(actual)) {
|
|
15640
|
+
return actual.some((item) => item === expected);
|
|
15641
|
+
}
|
|
15642
|
+
return actual === expected;
|
|
15643
|
+
}
|
|
15644
|
+
function stringCandidates(actual) {
|
|
15645
|
+
if (actual === undefined)
|
|
15646
|
+
return [];
|
|
15647
|
+
if (Array.isArray(actual)) {
|
|
15648
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
15649
|
+
}
|
|
15650
|
+
return [String(actual)];
|
|
15651
|
+
}
|
|
15652
|
+
function isPrimitiveFieldValue(value) {
|
|
15653
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
15654
|
+
}
|
|
15655
|
+
function isNegativeMatcher(value) {
|
|
15656
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
15657
|
+
}
|
|
15442
15658
|
function eventMatchesFilter(event, filter) {
|
|
15443
15659
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
15444
15660
|
}
|
|
@@ -15451,16 +15667,29 @@ function channelMatchesEvent(channel, event) {
|
|
|
15451
15667
|
}
|
|
15452
15668
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
15453
15669
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
15670
|
+
var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
|
|
15671
|
+
var DEFAULT_EVENT_PAGE_LIMIT = 100;
|
|
15672
|
+
var MAX_EVENT_PAGE_LIMIT = 1000;
|
|
15454
15673
|
function getEventsDataDir(override) {
|
|
15455
15674
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
15456
15675
|
}
|
|
15676
|
+
function getActiveEventsDirEnv() {
|
|
15677
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
15678
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
15679
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
15680
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
15681
|
+
return null;
|
|
15682
|
+
}
|
|
15683
|
+
|
|
15457
15684
|
class JsonEventsStore {
|
|
15458
15685
|
dataDir;
|
|
15686
|
+
runtime;
|
|
15459
15687
|
channelsPath;
|
|
15460
15688
|
eventsPath;
|
|
15461
15689
|
deliveriesPath;
|
|
15462
15690
|
constructor(dataDir = getEventsDataDir()) {
|
|
15463
15691
|
this.dataDir = dataDir;
|
|
15692
|
+
this.runtime = localJsonRuntime(dataDir);
|
|
15464
15693
|
this.channelsPath = join(dataDir, "channels.json");
|
|
15465
15694
|
this.eventsPath = join(dataDir, "events.json");
|
|
15466
15695
|
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
@@ -15508,13 +15737,58 @@ class JsonEventsStore {
|
|
|
15508
15737
|
await this.writeJson(this.eventsPath, events);
|
|
15509
15738
|
return event;
|
|
15510
15739
|
}
|
|
15511
|
-
async
|
|
15740
|
+
async appendEventOnce(event, options = {}) {
|
|
15512
15741
|
await this.init();
|
|
15513
|
-
|
|
15742
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15743
|
+
const dedupe = options.dedupe !== false;
|
|
15744
|
+
if (dedupe) {
|
|
15745
|
+
const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
|
|
15746
|
+
if (existing) {
|
|
15747
|
+
return {
|
|
15748
|
+
event: existing,
|
|
15749
|
+
stored: false,
|
|
15750
|
+
deduped: true,
|
|
15751
|
+
identity: { id: existing.id, dedupeKey: existing.dedupeKey }
|
|
15752
|
+
};
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
events.push(event);
|
|
15756
|
+
await this.writeJson(this.eventsPath, events);
|
|
15757
|
+
return {
|
|
15758
|
+
event,
|
|
15759
|
+
stored: true,
|
|
15760
|
+
deduped: false,
|
|
15761
|
+
identity: { id: event.id, dedupeKey: event.dedupeKey }
|
|
15762
|
+
};
|
|
15763
|
+
}
|
|
15764
|
+
async listEvents(options = {}) {
|
|
15765
|
+
await this.init();
|
|
15766
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15767
|
+
return queryEvents(events, options);
|
|
15768
|
+
}
|
|
15769
|
+
async listEventsPage(options = {}) {
|
|
15770
|
+
await this.init();
|
|
15771
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15772
|
+
const queried = queryEvents(events, {
|
|
15773
|
+
eventId: options.eventId,
|
|
15774
|
+
source: options.source,
|
|
15775
|
+
type: options.type
|
|
15776
|
+
});
|
|
15777
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
15778
|
+
const limit = normalizeEventPageLimit(options.limit);
|
|
15779
|
+
const pageEvents = queried.slice(offset, offset + limit);
|
|
15780
|
+
const nextOffset = offset + pageEvents.length;
|
|
15781
|
+
const hasMore = nextOffset < queried.length;
|
|
15782
|
+
return {
|
|
15783
|
+
events: pageEvents,
|
|
15784
|
+
cursor: options.cursor,
|
|
15785
|
+
nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
|
|
15786
|
+
hasMore
|
|
15787
|
+
};
|
|
15514
15788
|
}
|
|
15515
15789
|
async findEventByIdentity(identity) {
|
|
15516
15790
|
const events = await this.listEvents();
|
|
15517
|
-
return events
|
|
15791
|
+
return findEventByIdentity(events, identity);
|
|
15518
15792
|
}
|
|
15519
15793
|
async appendDelivery(result) {
|
|
15520
15794
|
await this.init();
|
|
@@ -15565,6 +15839,130 @@ class JsonEventsStore {
|
|
|
15565
15839
|
});
|
|
15566
15840
|
}
|
|
15567
15841
|
}
|
|
15842
|
+
function localJsonRuntime(dataDir = getEventsDataDir()) {
|
|
15843
|
+
return {
|
|
15844
|
+
mode: "local-files",
|
|
15845
|
+
name: "json-events-store",
|
|
15846
|
+
remote: false,
|
|
15847
|
+
localFiles: true,
|
|
15848
|
+
localSqlite: false,
|
|
15849
|
+
postgres: false,
|
|
15850
|
+
s3: false,
|
|
15851
|
+
aws: false,
|
|
15852
|
+
durable: true,
|
|
15853
|
+
idempotency: "best-effort-local",
|
|
15854
|
+
replayCursors: true,
|
|
15855
|
+
description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
|
|
15856
|
+
};
|
|
15857
|
+
}
|
|
15858
|
+
function encodeLocalJsonEventCursor(offset, options = {}) {
|
|
15859
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
15860
|
+
throw new Error(`Invalid event cursor offset: ${offset}`);
|
|
15861
|
+
const payload = {
|
|
15862
|
+
offset,
|
|
15863
|
+
eventId: options.eventId,
|
|
15864
|
+
source: options.source,
|
|
15865
|
+
type: options.type
|
|
15866
|
+
};
|
|
15867
|
+
return `${LOCAL_JSON_EVENT_CURSOR_PREFIX}${Buffer2.from(JSON.stringify(payload), "utf-8").toString("base64url")}`;
|
|
15868
|
+
}
|
|
15869
|
+
function decodeLocalJsonEventCursor(cursor, options = {}) {
|
|
15870
|
+
if (!cursor)
|
|
15871
|
+
return 0;
|
|
15872
|
+
if (!cursor.startsWith(LOCAL_JSON_EVENT_CURSOR_PREFIX))
|
|
15873
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15874
|
+
const rawPayload = cursor.slice(LOCAL_JSON_EVENT_CURSOR_PREFIX.length);
|
|
15875
|
+
let payload;
|
|
15876
|
+
try {
|
|
15877
|
+
payload = JSON.parse(Buffer2.from(rawPayload, "base64url").toString("utf-8"));
|
|
15878
|
+
} catch {
|
|
15879
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15880
|
+
}
|
|
15881
|
+
const offset = payload.offset;
|
|
15882
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
15883
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15884
|
+
assertCursorFilter("eventId", payload.eventId, options.eventId);
|
|
15885
|
+
assertCursorFilter("source", payload.source, options.source);
|
|
15886
|
+
assertCursorFilter("type", payload.type, options.type);
|
|
15887
|
+
return offset;
|
|
15888
|
+
}
|
|
15889
|
+
function normalizeEventPageLimit(limit) {
|
|
15890
|
+
if (limit === undefined)
|
|
15891
|
+
return DEFAULT_EVENT_PAGE_LIMIT;
|
|
15892
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
15893
|
+
throw new Error(`Event page limit must be a positive integer, got ${limit}`);
|
|
15894
|
+
return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
|
|
15895
|
+
}
|
|
15896
|
+
function queryEvents(events, options) {
|
|
15897
|
+
let rows = events;
|
|
15898
|
+
if (options.eventId)
|
|
15899
|
+
rows = rows.filter((event) => event.id === options.eventId);
|
|
15900
|
+
if (options.source)
|
|
15901
|
+
rows = rows.filter((event) => event.source === options.source);
|
|
15902
|
+
if (options.type)
|
|
15903
|
+
rows = rows.filter((event) => event.type === options.type);
|
|
15904
|
+
if (options.cursor) {
|
|
15905
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
15906
|
+
rows = rows.slice(offset);
|
|
15907
|
+
}
|
|
15908
|
+
if (options.limit !== undefined)
|
|
15909
|
+
rows = rows.slice(0, normalizeEventPageLimit(options.limit));
|
|
15910
|
+
return rows;
|
|
15911
|
+
}
|
|
15912
|
+
function assertCursorFilter(name, cursorValue, optionValue) {
|
|
15913
|
+
if (cursorValue !== optionValue)
|
|
15914
|
+
throw new Error(`Local JSON event cursor ${name} filter mismatch`);
|
|
15915
|
+
}
|
|
15916
|
+
function findEventByIdentity(events, identity) {
|
|
15917
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
15918
|
+
}
|
|
15919
|
+
async function getEventsStatus(dataDir) {
|
|
15920
|
+
const store = new JsonEventsStore(dataDir);
|
|
15921
|
+
await store.init();
|
|
15922
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
15923
|
+
store.listChannels(),
|
|
15924
|
+
store.listEvents(),
|
|
15925
|
+
store.listDeliveries()
|
|
15926
|
+
]);
|
|
15927
|
+
const transports = channels.reduce((counts, channel) => {
|
|
15928
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
15929
|
+
return counts;
|
|
15930
|
+
}, {});
|
|
15931
|
+
return {
|
|
15932
|
+
service: "events",
|
|
15933
|
+
schemaVersion: "1.0",
|
|
15934
|
+
dataDir: store.dataDir,
|
|
15935
|
+
storage: store.runtime,
|
|
15936
|
+
env: {
|
|
15937
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
15938
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
15939
|
+
active: getActiveEventsDirEnv()
|
|
15940
|
+
},
|
|
15941
|
+
files: {
|
|
15942
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
15943
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
15944
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
15945
|
+
},
|
|
15946
|
+
counts: {
|
|
15947
|
+
channels: channels.length,
|
|
15948
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
15949
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
15950
|
+
events: events.length,
|
|
15951
|
+
deliveries: deliveries.length
|
|
15952
|
+
},
|
|
15953
|
+
transports,
|
|
15954
|
+
safety: {
|
|
15955
|
+
includesEventPayloads: false,
|
|
15956
|
+
includesWebhookSecrets: false,
|
|
15957
|
+
listOutputsRedactSecrets: true,
|
|
15958
|
+
statusOutputIsMetadataOnly: true
|
|
15959
|
+
}
|
|
15960
|
+
};
|
|
15961
|
+
}
|
|
15962
|
+
function statusFile(dataDir, fileName, records) {
|
|
15963
|
+
const path = join(dataDir, fileName);
|
|
15964
|
+
return { path, exists: existsSync(path), records };
|
|
15965
|
+
}
|
|
15568
15966
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
15569
15967
|
function buildSignatureBase(timestamp, body) {
|
|
15570
15968
|
return `${timestamp}.${body}`;
|
|
@@ -15579,21 +15977,27 @@ function now() {
|
|
|
15579
15977
|
function truncate(value, max = 4096) {
|
|
15580
15978
|
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
15581
15979
|
}
|
|
15582
|
-
function buildWebhookRequest(event, channel) {
|
|
15980
|
+
function buildWebhookRequest(event, channel, options = {}) {
|
|
15583
15981
|
if (!channel.webhook)
|
|
15584
15982
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
15983
|
+
for (const name of Object.keys(channel.webhook.headers ?? {})) {
|
|
15984
|
+
if (/^x-hasna-/i.test(name)) {
|
|
15985
|
+
throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
|
|
15986
|
+
}
|
|
15987
|
+
}
|
|
15585
15988
|
const body = JSON.stringify(event);
|
|
15586
|
-
const timestamp =
|
|
15989
|
+
const timestamp = options.timestamp ?? new Date().toISOString();
|
|
15587
15990
|
const headers = {
|
|
15588
15991
|
"Content-Type": "application/json",
|
|
15589
15992
|
"User-Agent": "@hasna/events",
|
|
15590
15993
|
"X-Hasna-Event-Id": event.id,
|
|
15591
15994
|
"X-Hasna-Event-Type": event.type,
|
|
15592
|
-
|
|
15593
|
-
|
|
15995
|
+
...channel.webhook.headers,
|
|
15996
|
+
"X-Hasna-Timestamp": timestamp
|
|
15594
15997
|
};
|
|
15595
|
-
|
|
15596
|
-
|
|
15998
|
+
const secret = options.secret ?? channel.webhook.secret;
|
|
15999
|
+
if (secret) {
|
|
16000
|
+
headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
|
|
15597
16001
|
}
|
|
15598
16002
|
return { body, headers };
|
|
15599
16003
|
}
|
|
@@ -15601,7 +16005,21 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
15601
16005
|
if (!channel.webhook)
|
|
15602
16006
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
15603
16007
|
const startedAt = now();
|
|
15604
|
-
|
|
16008
|
+
let secret = channel.webhook.secret;
|
|
16009
|
+
if (channel.webhook.secretRef) {
|
|
16010
|
+
if (!options.secretResolver) {
|
|
16011
|
+
return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
|
|
16012
|
+
}
|
|
16013
|
+
try {
|
|
16014
|
+
secret = await options.secretResolver(channel.webhook.secretRef);
|
|
16015
|
+
} catch {
|
|
16016
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
16017
|
+
}
|
|
16018
|
+
if (!secret)
|
|
16019
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
16020
|
+
}
|
|
16021
|
+
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
16022
|
+
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
15605
16023
|
const controller = new AbortController;
|
|
15606
16024
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
15607
16025
|
try {
|
|
@@ -15633,6 +16051,15 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
15633
16051
|
clearTimeout(timeout);
|
|
15634
16052
|
}
|
|
15635
16053
|
}
|
|
16054
|
+
function failedAttempt(startedAt, error) {
|
|
16055
|
+
return {
|
|
16056
|
+
attempt: 1,
|
|
16057
|
+
status: "failed",
|
|
16058
|
+
startedAt,
|
|
16059
|
+
completedAt: now(),
|
|
16060
|
+
error
|
|
16061
|
+
};
|
|
16062
|
+
}
|
|
15636
16063
|
async function dispatchCommand(event, channel) {
|
|
15637
16064
|
if (!channel.command)
|
|
15638
16065
|
throw new Error(`Channel ${channel.id} has no command config`);
|
|
@@ -15721,14 +16148,98 @@ function createDeliveryResult(event, channel, attempts) {
|
|
|
15721
16148
|
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
15722
16149
|
};
|
|
15723
16150
|
}
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
16151
|
+
|
|
16152
|
+
class EventValidationError extends Error {
|
|
16153
|
+
eventType;
|
|
16154
|
+
issues;
|
|
16155
|
+
constructor(eventType, issues) {
|
|
16156
|
+
const detail = issues.map((issue) => `${issue.path || "<root>"}: ${issue.message}`).join("; ");
|
|
16157
|
+
super(`Event validation failed for type "${eventType}": ${detail}`);
|
|
16158
|
+
this.name = "EventValidationError";
|
|
16159
|
+
this.eventType = eventType;
|
|
16160
|
+
this.issues = issues;
|
|
16161
|
+
}
|
|
16162
|
+
}
|
|
16163
|
+
|
|
16164
|
+
class EventTypeCatalog {
|
|
16165
|
+
definitions = new Map;
|
|
16166
|
+
register(definition) {
|
|
16167
|
+
this.definitions.set(definition.type, definition);
|
|
16168
|
+
return this;
|
|
16169
|
+
}
|
|
16170
|
+
unregister(type) {
|
|
16171
|
+
return this.definitions.delete(type);
|
|
16172
|
+
}
|
|
16173
|
+
has(type) {
|
|
16174
|
+
return this.definitions.has(type);
|
|
16175
|
+
}
|
|
16176
|
+
get(type) {
|
|
16177
|
+
return this.definitions.get(type);
|
|
16178
|
+
}
|
|
16179
|
+
list() {
|
|
16180
|
+
return [...this.definitions.values()];
|
|
16181
|
+
}
|
|
16182
|
+
validateEvent(event) {
|
|
16183
|
+
const definition = this.definitions.get(event.type);
|
|
16184
|
+
if (!definition)
|
|
16185
|
+
return { ok: true };
|
|
16186
|
+
return definition.validate(event.data, event);
|
|
16187
|
+
}
|
|
16188
|
+
assertEventValid(event) {
|
|
16189
|
+
const result = this.validateEvent(event);
|
|
16190
|
+
if (!result.ok) {
|
|
16191
|
+
throw new EventValidationError(event.type, result.issues);
|
|
16192
|
+
}
|
|
16193
|
+
}
|
|
16194
|
+
}
|
|
16195
|
+
var defaultEventTypeCatalog = new EventTypeCatalog;
|
|
16196
|
+
var APP_EVENT_V1_MAX_DATA_BYTES = 32 * 1024;
|
|
16197
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
16198
|
+
if (paths.length === 0)
|
|
16199
|
+
return event;
|
|
16200
|
+
const copy = structuredClone(event);
|
|
16201
|
+
for (const path of paths) {
|
|
16202
|
+
setPath(copy, path, replacement);
|
|
16203
|
+
}
|
|
16204
|
+
return copy;
|
|
16205
|
+
}
|
|
16206
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
16207
|
+
return redactValue(event, replacement);
|
|
16208
|
+
}
|
|
16209
|
+
function shouldRedactKey(key) {
|
|
16210
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
16211
|
+
}
|
|
16212
|
+
function redactValue(value, replacement) {
|
|
16213
|
+
if (Array.isArray(value))
|
|
16214
|
+
return value.map((item) => redactValue(item, replacement));
|
|
16215
|
+
if (!value || typeof value !== "object")
|
|
16216
|
+
return value;
|
|
16217
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
16218
|
+
key,
|
|
16219
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
16220
|
+
]));
|
|
16221
|
+
}
|
|
16222
|
+
function setPath(input, path, replacement) {
|
|
16223
|
+
const parts = path.split(".");
|
|
16224
|
+
let cursor = input;
|
|
16225
|
+
for (const part of parts.slice(0, -1)) {
|
|
16226
|
+
const next = cursor[part];
|
|
16227
|
+
if (!next || typeof next !== "object")
|
|
16228
|
+
return;
|
|
16229
|
+
cursor = next;
|
|
16230
|
+
}
|
|
16231
|
+
const last = parts.at(-1);
|
|
16232
|
+
if (last && last in cursor)
|
|
16233
|
+
cursor[last] = replacement;
|
|
16234
|
+
}
|
|
16235
|
+
function createEvent(input) {
|
|
16236
|
+
return {
|
|
16237
|
+
id: input.id ?? randomUUID2(),
|
|
16238
|
+
source: input.source,
|
|
16239
|
+
type: input.type,
|
|
16240
|
+
time: normalizeTime(input.time),
|
|
16241
|
+
subject: input.subject,
|
|
16242
|
+
severity: input.severity ?? "info",
|
|
15732
16243
|
data: input.data ?? {},
|
|
15733
16244
|
message: input.message,
|
|
15734
16245
|
dedupeKey: input.dedupeKey,
|
|
@@ -15741,10 +16252,18 @@ class EventsClient {
|
|
|
15741
16252
|
store;
|
|
15742
16253
|
redactors;
|
|
15743
16254
|
transportOptions;
|
|
16255
|
+
catalog;
|
|
16256
|
+
validateCatalogTypes;
|
|
15744
16257
|
constructor(options = {}) {
|
|
15745
16258
|
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
15746
16259
|
this.redactors = options.redactors ?? [];
|
|
15747
|
-
this.transportOptions = {
|
|
16260
|
+
this.transportOptions = {
|
|
16261
|
+
fetchImpl: options.fetchImpl,
|
|
16262
|
+
secretResolver: options.secretResolver,
|
|
16263
|
+
now: options.now
|
|
16264
|
+
};
|
|
16265
|
+
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
16266
|
+
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
|
15748
16267
|
}
|
|
15749
16268
|
async addChannel(input) {
|
|
15750
16269
|
const timestamp = new Date().toISOString();
|
|
@@ -15762,18 +16281,40 @@ class EventsClient {
|
|
|
15762
16281
|
}
|
|
15763
16282
|
async emit(input, options = {}) {
|
|
15764
16283
|
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
15765
|
-
if (options.
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
15773
|
-
return { event, deliveries, deduped: false };
|
|
15774
|
-
}
|
|
15775
|
-
async listEvents() {
|
|
15776
|
-
|
|
16284
|
+
if (options.validate ?? this.validateCatalogTypes) {
|
|
16285
|
+
this.catalog.assertEventValid(event);
|
|
16286
|
+
}
|
|
16287
|
+
const append = await this.appendEvent(event, { dedupe: options.dedupe !== false });
|
|
16288
|
+
if (append.deduped) {
|
|
16289
|
+
return { event: append.event, deliveries: [], deduped: true };
|
|
16290
|
+
}
|
|
16291
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(append.event);
|
|
16292
|
+
return { event: append.event, deliveries, deduped: false };
|
|
16293
|
+
}
|
|
16294
|
+
async listEvents(options = {}) {
|
|
16295
|
+
if (Object.keys(options).length === 0)
|
|
16296
|
+
return this.store.listEvents();
|
|
16297
|
+
return queryClientEvents(await this.store.listEvents(), options);
|
|
16298
|
+
}
|
|
16299
|
+
async listEventsPage(options = {}) {
|
|
16300
|
+
if (this.store.listEventsPage)
|
|
16301
|
+
return this.store.listEventsPage(options);
|
|
16302
|
+
const events = queryClientEvents(await this.store.listEvents(), {
|
|
16303
|
+
eventId: options.eventId,
|
|
16304
|
+
source: options.source,
|
|
16305
|
+
type: options.type
|
|
16306
|
+
});
|
|
16307
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
16308
|
+
const limit = normalizeEventPageLimit(options.limit);
|
|
16309
|
+
const pageEvents = events.slice(offset, offset + limit);
|
|
16310
|
+
const nextOffset = offset + pageEvents.length;
|
|
16311
|
+
const hasMore = nextOffset < events.length;
|
|
16312
|
+
return {
|
|
16313
|
+
events: pageEvents,
|
|
16314
|
+
cursor: options.cursor,
|
|
16315
|
+
nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
|
|
16316
|
+
hasMore
|
|
16317
|
+
};
|
|
15777
16318
|
}
|
|
15778
16319
|
async listDeliveries() {
|
|
15779
16320
|
return this.store.listDeliveries();
|
|
@@ -15790,7 +16331,7 @@ class EventsClient {
|
|
|
15790
16331
|
}
|
|
15791
16332
|
return deliveries;
|
|
15792
16333
|
}
|
|
15793
|
-
async
|
|
16334
|
+
async matchChannel(id, input = {}) {
|
|
15794
16335
|
const channel = await this.store.getChannel(id);
|
|
15795
16336
|
if (!channel)
|
|
15796
16337
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -15807,28 +16348,71 @@ class EventsClient {
|
|
|
15807
16348
|
time: input.time,
|
|
15808
16349
|
id: input.id
|
|
15809
16350
|
});
|
|
16351
|
+
const matched = channelMatchesEvent(channel, event);
|
|
16352
|
+
return {
|
|
16353
|
+
channelId: channel.id,
|
|
16354
|
+
matched,
|
|
16355
|
+
event,
|
|
16356
|
+
filters: channel.filters,
|
|
16357
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
16358
|
+
};
|
|
16359
|
+
}
|
|
16360
|
+
async testChannel(id, input = {}, options = {}) {
|
|
16361
|
+
const channel = await this.store.getChannel(id);
|
|
16362
|
+
if (!channel)
|
|
16363
|
+
throw new Error(`Channel not found: ${id}`);
|
|
16364
|
+
const match = await this.matchChannel(id, input);
|
|
16365
|
+
const event = match.event;
|
|
16366
|
+
if (options.honorFilters && !match.matched) {
|
|
16367
|
+
const timestamp = new Date().toISOString();
|
|
16368
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
16369
|
+
attempt: 1,
|
|
16370
|
+
status: "skipped",
|
|
16371
|
+
startedAt: timestamp,
|
|
16372
|
+
completedAt: timestamp,
|
|
16373
|
+
error: match.reason
|
|
16374
|
+
}]);
|
|
16375
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
16376
|
+
await this.store.appendDelivery(result2);
|
|
16377
|
+
return result2;
|
|
16378
|
+
}
|
|
15810
16379
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
15811
16380
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
15812
16381
|
await this.store.appendDelivery(result);
|
|
15813
16382
|
return result;
|
|
15814
16383
|
}
|
|
15815
16384
|
async replay(options = {}) {
|
|
15816
|
-
const
|
|
15817
|
-
if (options.eventId && event.id !== options.eventId)
|
|
15818
|
-
return false;
|
|
15819
|
-
if (options.source && event.source !== options.source)
|
|
15820
|
-
return false;
|
|
15821
|
-
if (options.type && event.type !== options.type)
|
|
15822
|
-
return false;
|
|
15823
|
-
return true;
|
|
15824
|
-
});
|
|
16385
|
+
const page = options.cursor || options.limit !== undefined ? await this.listEventsPage(options) : { events: await this.listEvents(options), hasMore: false };
|
|
15825
16386
|
if (options.dryRun)
|
|
15826
|
-
return { events, deliveries: [] };
|
|
16387
|
+
return { events: page.events, deliveries: [], cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
|
|
15827
16388
|
const deliveries = [];
|
|
15828
|
-
for (const event of events) {
|
|
16389
|
+
for (const event of page.events) {
|
|
15829
16390
|
deliveries.push(...await this.deliver(event));
|
|
15830
16391
|
}
|
|
15831
|
-
return { events, deliveries };
|
|
16392
|
+
return { events: page.events, deliveries, cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
|
|
16393
|
+
}
|
|
16394
|
+
async appendEvent(event, options) {
|
|
16395
|
+
if (this.store.appendEventOnce) {
|
|
16396
|
+
return this.store.appendEventOnce(event, { dedupe: options.dedupe });
|
|
16397
|
+
}
|
|
16398
|
+
if (options.dedupe) {
|
|
16399
|
+
const existing = await this.store.findEventByIdentity({ id: event.id, dedupeKey: event.dedupeKey });
|
|
16400
|
+
if (existing) {
|
|
16401
|
+
return {
|
|
16402
|
+
event: existing,
|
|
16403
|
+
stored: false,
|
|
16404
|
+
deduped: true,
|
|
16405
|
+
identity: { id: existing.id, dedupeKey: existing.dedupeKey }
|
|
16406
|
+
};
|
|
16407
|
+
}
|
|
16408
|
+
}
|
|
16409
|
+
const stored = await this.store.appendEvent(event);
|
|
16410
|
+
return {
|
|
16411
|
+
event: stored,
|
|
16412
|
+
stored: true,
|
|
16413
|
+
deduped: false,
|
|
16414
|
+
identity: { id: stored.id, dedupeKey: stored.dedupeKey }
|
|
16415
|
+
};
|
|
15832
16416
|
}
|
|
15833
16417
|
async applyRedaction(event, channel) {
|
|
15834
16418
|
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
@@ -15855,15 +16439,6 @@ class EventsClient {
|
|
|
15855
16439
|
return createDeliveryResult(event, channel, attempts);
|
|
15856
16440
|
}
|
|
15857
16441
|
}
|
|
15858
|
-
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
15859
|
-
if (paths.length === 0)
|
|
15860
|
-
return event;
|
|
15861
|
-
const copy = structuredClone(event);
|
|
15862
|
-
for (const path of paths) {
|
|
15863
|
-
setPath(copy, path, replacement);
|
|
15864
|
-
}
|
|
15865
|
-
return copy;
|
|
15866
|
-
}
|
|
15867
16442
|
function sanitizeChannelForOutput(channel) {
|
|
15868
16443
|
const copy = structuredClone(channel);
|
|
15869
16444
|
if (copy.webhook?.secret)
|
|
@@ -15876,34 +16451,19 @@ function sanitizeChannelForOutput(channel) {
|
|
|
15876
16451
|
function sanitizeChannelsForOutput(channels) {
|
|
15877
16452
|
return channels.map(sanitizeChannelForOutput);
|
|
15878
16453
|
}
|
|
15879
|
-
function
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
15893
|
-
]));
|
|
15894
|
-
}
|
|
15895
|
-
function setPath(input, path, replacement) {
|
|
15896
|
-
const parts = path.split(".");
|
|
15897
|
-
let cursor = input;
|
|
15898
|
-
for (const part of parts.slice(0, -1)) {
|
|
15899
|
-
const next = cursor[part];
|
|
15900
|
-
if (!next || typeof next !== "object")
|
|
15901
|
-
return;
|
|
15902
|
-
cursor = next;
|
|
15903
|
-
}
|
|
15904
|
-
const last = parts.at(-1);
|
|
15905
|
-
if (last && last in cursor)
|
|
15906
|
-
cursor[last] = replacement;
|
|
16454
|
+
function queryClientEvents(events, options) {
|
|
16455
|
+
let rows = events;
|
|
16456
|
+
if (options.eventId)
|
|
16457
|
+
rows = rows.filter((event) => event.id === options.eventId);
|
|
16458
|
+
if (options.source)
|
|
16459
|
+
rows = rows.filter((event) => event.source === options.source);
|
|
16460
|
+
if (options.type)
|
|
16461
|
+
rows = rows.filter((event) => event.type === options.type);
|
|
16462
|
+
if (options.cursor)
|
|
16463
|
+
rows = rows.slice(decodeLocalJsonEventCursor(options.cursor, options));
|
|
16464
|
+
if (options.limit !== undefined)
|
|
16465
|
+
rows = rows.slice(0, normalizeEventPageLimit(options.limit));
|
|
16466
|
+
return rows;
|
|
15907
16467
|
}
|
|
15908
16468
|
function normalizeTime(value) {
|
|
15909
16469
|
if (!value)
|
|
@@ -15917,6 +16477,77 @@ function normalizeRetryPolicy(policy) {
|
|
|
15917
16477
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
15918
16478
|
};
|
|
15919
16479
|
}
|
|
16480
|
+
function parseFieldMatchers(values, label, typed = false) {
|
|
16481
|
+
if (!values?.length)
|
|
16482
|
+
return;
|
|
16483
|
+
const result = {};
|
|
16484
|
+
for (const value of values) {
|
|
16485
|
+
const parsed = parseMatcherExpression(value, label);
|
|
16486
|
+
const path = parsed.path;
|
|
16487
|
+
if (path in result)
|
|
16488
|
+
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
16489
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
16490
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
16491
|
+
}
|
|
16492
|
+
return result;
|
|
16493
|
+
}
|
|
16494
|
+
function parseFilterOptions(options) {
|
|
16495
|
+
const filter2 = {};
|
|
16496
|
+
if (options.source)
|
|
16497
|
+
filter2.source = options.source;
|
|
16498
|
+
if (options.type)
|
|
16499
|
+
filter2.type = options.type;
|
|
16500
|
+
if (options.subject)
|
|
16501
|
+
filter2.subject = options.subject;
|
|
16502
|
+
if (options.severity)
|
|
16503
|
+
filter2.severity = options.severity;
|
|
16504
|
+
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
16505
|
+
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
16506
|
+
if (Object.keys(data).length > 0)
|
|
16507
|
+
filter2.data = data;
|
|
16508
|
+
if (Object.keys(metadata).length > 0)
|
|
16509
|
+
filter2.metadata = metadata;
|
|
16510
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
16511
|
+
}
|
|
16512
|
+
function mergeMatchers(...records) {
|
|
16513
|
+
const result = {};
|
|
16514
|
+
for (const record of records) {
|
|
16515
|
+
if (!record)
|
|
16516
|
+
continue;
|
|
16517
|
+
for (const [path, value] of Object.entries(record)) {
|
|
16518
|
+
if (path in result)
|
|
16519
|
+
throw new Error(`Duplicate filter path: ${path}`);
|
|
16520
|
+
result[path] = value;
|
|
16521
|
+
}
|
|
16522
|
+
}
|
|
16523
|
+
return result;
|
|
16524
|
+
}
|
|
16525
|
+
function parseTypedMatcherValue(value, label) {
|
|
16526
|
+
const parsed = JSON.parse(value);
|
|
16527
|
+
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
16528
|
+
return parsed;
|
|
16529
|
+
}
|
|
16530
|
+
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
16531
|
+
}
|
|
16532
|
+
function parseMatcherExpression(value, label) {
|
|
16533
|
+
const negativeSeparator = value.indexOf("!=");
|
|
16534
|
+
if (negativeSeparator > 0) {
|
|
16535
|
+
return {
|
|
16536
|
+
path: value.slice(0, negativeSeparator),
|
|
16537
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
16538
|
+
negated: true
|
|
16539
|
+
};
|
|
16540
|
+
}
|
|
16541
|
+
const separator = value.indexOf("=");
|
|
16542
|
+
if (separator <= 0)
|
|
16543
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
16544
|
+
return {
|
|
16545
|
+
path: value.slice(0, separator),
|
|
16546
|
+
rawValue: value.slice(separator + 1),
|
|
16547
|
+
negated: false
|
|
16548
|
+
};
|
|
16549
|
+
}
|
|
16550
|
+
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
15920
16551
|
function parseJsonObject(value, fallback) {
|
|
15921
16552
|
if (!value)
|
|
15922
16553
|
return fallback;
|
|
@@ -15938,18 +16569,6 @@ function parseHeaders(values) {
|
|
|
15938
16569
|
}
|
|
15939
16570
|
return headers;
|
|
15940
16571
|
}
|
|
15941
|
-
function parseFilter(options) {
|
|
15942
|
-
const filter2 = {};
|
|
15943
|
-
if (options.source)
|
|
15944
|
-
filter2.source = options.source;
|
|
15945
|
-
if (options.type)
|
|
15946
|
-
filter2.type = options.type;
|
|
15947
|
-
if (options.subject)
|
|
15948
|
-
filter2.subject = options.subject;
|
|
15949
|
-
if (options.severity)
|
|
15950
|
-
filter2.severity = options.severity;
|
|
15951
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
15952
|
-
}
|
|
15953
16572
|
function createClient(options) {
|
|
15954
16573
|
if (options.createClient)
|
|
15955
16574
|
return options.createClient();
|
|
@@ -15961,22 +16580,30 @@ function print(value, json, text) {
|
|
|
15961
16580
|
else
|
|
15962
16581
|
console.log(text);
|
|
15963
16582
|
}
|
|
16583
|
+
function fail(error, json) {
|
|
16584
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16585
|
+
if (json)
|
|
16586
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
16587
|
+
else
|
|
16588
|
+
console.error(message);
|
|
16589
|
+
process.exitCode = 1;
|
|
16590
|
+
}
|
|
15964
16591
|
function hasJsonOption(options) {
|
|
15965
16592
|
return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
|
|
15966
16593
|
}
|
|
15967
16594
|
function wantsJson(actionOptions, command) {
|
|
15968
16595
|
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
15969
16596
|
}
|
|
15970
|
-
function
|
|
15971
|
-
const
|
|
15972
|
-
|
|
16597
|
+
function registerChannelCommands(program, options) {
|
|
16598
|
+
const channels = program.command(options.channelsCommandName ?? "channels").description("Manage Hasna event channels");
|
|
16599
|
+
channels.command("add").description("Add or replace a channel").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--data <path=value...>", "Event data field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
|
|
15973
16600
|
const timestamp = new Date().toISOString();
|
|
15974
16601
|
const channel = {
|
|
15975
16602
|
id: actionOptions.id,
|
|
15976
16603
|
name: actionOptions.name,
|
|
15977
16604
|
enabled: !actionOptions.disabled,
|
|
15978
16605
|
transport: actionOptions.transport,
|
|
15979
|
-
filters:
|
|
16606
|
+
filters: parseFilterOptions(actionOptions),
|
|
15980
16607
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
15981
16608
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
15982
16609
|
createdAt: timestamp,
|
|
@@ -15992,35 +16619,61 @@ function registerWebhookCommands(program, options) {
|
|
|
15992
16619
|
const saved = await createClient(options).addChannel(channel);
|
|
15993
16620
|
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
15994
16621
|
});
|
|
15995
|
-
|
|
15996
|
-
const
|
|
16622
|
+
channels.command("list").description("List configured channels").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16623
|
+
const channels2 = await createClient(options).listChannels();
|
|
15997
16624
|
if (wantsJson(actionOptions, command)) {
|
|
15998
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(
|
|
16625
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
|
|
15999
16626
|
return;
|
|
16000
16627
|
}
|
|
16001
|
-
if (!
|
|
16628
|
+
if (!channels2.length) {
|
|
16002
16629
|
console.log("No channels configured.");
|
|
16003
16630
|
return;
|
|
16004
16631
|
}
|
|
16005
|
-
for (const channel of
|
|
16632
|
+
for (const channel of channels2) {
|
|
16006
16633
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
16007
16634
|
}
|
|
16008
16635
|
});
|
|
16009
|
-
|
|
16636
|
+
channels.command("status").description("Show events channel storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16637
|
+
const status = await getEventsStatus(options.dataDir);
|
|
16638
|
+
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
16639
|
+
});
|
|
16640
|
+
channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
16010
16641
|
const removed = await createClient(options).removeChannel(id);
|
|
16011
16642
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
16012
16643
|
});
|
|
16013
|
-
|
|
16014
|
-
const
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16644
|
+
channels.command("test").description("Send a test event to one channel").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--honor-filters", "Skip delivery when the sample event does not match channel filters", false).option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
16645
|
+
const json = wantsJson(actionOptions, command);
|
|
16646
|
+
try {
|
|
16647
|
+
const result = await createClient(options).testChannel(id, {
|
|
16648
|
+
source: actionOptions.source ?? options.source,
|
|
16649
|
+
type: actionOptions.type,
|
|
16650
|
+
subject: actionOptions.subject ?? id,
|
|
16651
|
+
message: actionOptions.message,
|
|
16652
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
16653
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
16654
|
+
}, { honorFilters: actionOptions.honorFilters });
|
|
16655
|
+
print(result, json, `${result.status}: ${result.channelId}`);
|
|
16656
|
+
} catch (error) {
|
|
16657
|
+
fail(error, json);
|
|
16658
|
+
}
|
|
16659
|
+
});
|
|
16660
|
+
channels.command("match").description("Check whether a sample event matches one channel without delivering").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events match preview").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
16661
|
+
const json = wantsJson(actionOptions, command);
|
|
16662
|
+
try {
|
|
16663
|
+
const result = await createClient(options).matchChannel(id, {
|
|
16664
|
+
source: actionOptions.source ?? options.source,
|
|
16665
|
+
type: actionOptions.type,
|
|
16666
|
+
subject: actionOptions.subject ?? id,
|
|
16667
|
+
message: actionOptions.message,
|
|
16668
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
16669
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
16670
|
+
});
|
|
16671
|
+
print(result, json, `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
|
|
16672
|
+
} catch (error) {
|
|
16673
|
+
fail(error, json);
|
|
16674
|
+
}
|
|
16022
16675
|
});
|
|
16023
|
-
return
|
|
16676
|
+
return channels;
|
|
16024
16677
|
}
|
|
16025
16678
|
function registerEventCommands(program, options) {
|
|
16026
16679
|
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
@@ -16037,7 +16690,8 @@ function registerEventCommands(program, options) {
|
|
|
16037
16690
|
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
16038
16691
|
print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
16039
16692
|
});
|
|
16040
|
-
|
|
16693
|
+
const defaultListLimit = options.defaultEventListLimit ?? DEFAULT_EVENT_LIST_LIMIT;
|
|
16694
|
+
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", `Limit to the most recent <n> events (default ${defaultListLimit}; use 0 for all)`, parseNumber, defaultListLimit).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16041
16695
|
let rows = await createClient(options).listEvents();
|
|
16042
16696
|
if (actionOptions.source)
|
|
16043
16697
|
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
@@ -16056,19 +16710,21 @@ function registerEventCommands(program, options) {
|
|
|
16056
16710
|
for (const event of rows)
|
|
16057
16711
|
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
16058
16712
|
});
|
|
16059
|
-
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16713
|
+
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--cursor <cursor>", "Opaque replay cursor from a previous page").option("--limit <n>", "Maximum events to replay", parseNumber).option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16060
16714
|
const result = await createClient(options).replay({
|
|
16061
16715
|
eventId: actionOptions.id,
|
|
16062
16716
|
source: actionOptions.source,
|
|
16063
16717
|
type: actionOptions.type,
|
|
16718
|
+
cursor: actionOptions.cursor,
|
|
16719
|
+
limit: actionOptions.limit,
|
|
16064
16720
|
dryRun: actionOptions.dryRun
|
|
16065
16721
|
});
|
|
16066
|
-
print(result, wantsJson(actionOptions, command),
|
|
16722
|
+
print(result, wantsJson(actionOptions, command), replaySummary(result.events.length, result.deliveries.length, result.nextCursor));
|
|
16067
16723
|
});
|
|
16068
16724
|
return events;
|
|
16069
16725
|
}
|
|
16070
16726
|
function registerEventsCommands(program, options) {
|
|
16071
|
-
|
|
16727
|
+
registerChannelCommands(program, options);
|
|
16072
16728
|
registerEventCommands(program, options);
|
|
16073
16729
|
}
|
|
16074
16730
|
function parseNumber(value) {
|
|
@@ -16081,8 +16737,12 @@ function collectValues(value, previous) {
|
|
|
16081
16737
|
previous.push(value);
|
|
16082
16738
|
return previous;
|
|
16083
16739
|
}
|
|
16740
|
+
function replaySummary(events, deliveries, nextCursor) {
|
|
16741
|
+
const suffix = nextCursor ? `, next cursor: ${nextCursor}` : "";
|
|
16742
|
+
return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
|
|
16743
|
+
}
|
|
16084
16744
|
|
|
16085
|
-
// node_modules/commander/esm.mjs
|
|
16745
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
|
|
16086
16746
|
var import__ = __toESM(require_commander(), 1);
|
|
16087
16747
|
var {
|
|
16088
16748
|
program,
|
|
@@ -16101,9 +16761,9 @@ var {
|
|
|
16101
16761
|
// src/cli/index.tsx
|
|
16102
16762
|
init_apply();
|
|
16103
16763
|
import chalk from "chalk";
|
|
16104
|
-
import { existsSync as
|
|
16105
|
-
import { homedir as
|
|
16106
|
-
import { basename as basename7, join as
|
|
16764
|
+
import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
|
|
16765
|
+
import { homedir as homedir10 } from "os";
|
|
16766
|
+
import { basename as basename7, join as join19, resolve as resolve13 } from "path";
|
|
16107
16767
|
|
|
16108
16768
|
// src/lib/config-target-identity.ts
|
|
16109
16769
|
init_apply();
|
|
@@ -16150,14 +16810,14 @@ init_redact();
|
|
|
16150
16810
|
// src/lib/export.ts
|
|
16151
16811
|
init_config_store();
|
|
16152
16812
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16153
|
-
import { join as
|
|
16813
|
+
import { join as join13, resolve as resolve8 } from "path";
|
|
16154
16814
|
import { tmpdir } from "os";
|
|
16155
16815
|
async function exportConfigs(outputPath, opts = {}) {
|
|
16156
16816
|
const store = opts.store ?? resolveConfigStore();
|
|
16157
16817
|
const configs = await store.listConfigs(opts.filter);
|
|
16158
|
-
const absOutput =
|
|
16159
|
-
const tmpDir =
|
|
16160
|
-
const contentsDir =
|
|
16818
|
+
const absOutput = resolve8(outputPath);
|
|
16819
|
+
const tmpDir = join13(tmpdir(), `configs-export-${Date.now()}`);
|
|
16820
|
+
const contentsDir = join13(tmpDir, "contents");
|
|
16161
16821
|
try {
|
|
16162
16822
|
mkdirSync4(contentsDir, { recursive: true });
|
|
16163
16823
|
const manifest = {
|
|
@@ -16165,10 +16825,10 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16165
16825
|
exported_at: new Date().toISOString(),
|
|
16166
16826
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
16167
16827
|
};
|
|
16168
|
-
writeFileSync3(
|
|
16828
|
+
writeFileSync3(join13(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
16169
16829
|
for (const config of configs) {
|
|
16170
16830
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
16171
|
-
writeFileSync3(
|
|
16831
|
+
writeFileSync3(join13(contentsDir, fileName), config.content, "utf-8");
|
|
16172
16832
|
}
|
|
16173
16833
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
16174
16834
|
stdout: "pipe",
|
|
@@ -16189,14 +16849,14 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16189
16849
|
|
|
16190
16850
|
// src/lib/import.ts
|
|
16191
16851
|
init_config_store();
|
|
16192
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as
|
|
16193
|
-
import { join as
|
|
16852
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
|
|
16853
|
+
import { join as join14, resolve as resolve9 } from "path";
|
|
16194
16854
|
import { tmpdir as tmpdir2 } from "os";
|
|
16195
16855
|
async function importConfigs(bundlePath, opts = {}) {
|
|
16196
16856
|
const store = opts.store ?? resolveConfigStore();
|
|
16197
16857
|
const conflict = opts.conflict ?? "skip";
|
|
16198
|
-
const absPath =
|
|
16199
|
-
const tmpDir =
|
|
16858
|
+
const absPath = resolve9(bundlePath);
|
|
16859
|
+
const tmpDir = join14(tmpdir2(), `configs-import-${Date.now()}`);
|
|
16200
16860
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
16201
16861
|
try {
|
|
16202
16862
|
mkdirSync5(tmpDir, { recursive: true });
|
|
@@ -16209,15 +16869,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16209
16869
|
const stderr = await new Response(proc.stderr).text();
|
|
16210
16870
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
16211
16871
|
}
|
|
16212
|
-
const manifestPath =
|
|
16872
|
+
const manifestPath = join14(tmpDir, "manifest.json");
|
|
16213
16873
|
if (!existsSync11(manifestPath))
|
|
16214
16874
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
16215
|
-
const manifest = JSON.parse(
|
|
16875
|
+
const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
16216
16876
|
for (const meta of manifest.configs) {
|
|
16217
16877
|
try {
|
|
16218
16878
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
16219
|
-
const contentFile =
|
|
16220
|
-
const content = existsSync11(contentFile) ?
|
|
16879
|
+
const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
16880
|
+
const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
|
|
16221
16881
|
let existing = null;
|
|
16222
16882
|
try {
|
|
16223
16883
|
existing = await store.getConfig(meta.slug);
|
|
@@ -16265,16 +16925,17 @@ init_machine();
|
|
|
16265
16925
|
init_project_context();
|
|
16266
16926
|
init_session_render();
|
|
16267
16927
|
init_cursor_authority();
|
|
16268
|
-
|
|
16928
|
+
init_session_authority();
|
|
16929
|
+
import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
|
|
16269
16930
|
import {
|
|
16270
16931
|
existsSync as existsSync12,
|
|
16271
|
-
lstatSync as
|
|
16932
|
+
lstatSync as lstatSync4,
|
|
16272
16933
|
mkdirSync as mkdirSync6,
|
|
16273
|
-
readFileSync as
|
|
16934
|
+
readFileSync as readFileSync10,
|
|
16274
16935
|
readdirSync as readdirSync3,
|
|
16275
|
-
statSync as
|
|
16936
|
+
statSync as statSync6
|
|
16276
16937
|
} from "fs";
|
|
16277
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as
|
|
16938
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
|
|
16278
16939
|
|
|
16279
16940
|
class SessionApplyError extends Error {
|
|
16280
16941
|
constructor(message) {
|
|
@@ -16295,6 +16956,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16295
16956
|
}
|
|
16296
16957
|
assertCursorAuthorityUnchanged(plan);
|
|
16297
16958
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
16959
|
+
assertClaudeAuthorityStillClear(plan, targetHome);
|
|
16298
16960
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
16299
16961
|
const files = [...payloadFiles, plan.manifestFile];
|
|
16300
16962
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
@@ -16395,6 +17057,15 @@ function assertCursorAuthorityUnchanged(plan) {
|
|
|
16395
17057
|
throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
|
|
16396
17058
|
}
|
|
16397
17059
|
}
|
|
17060
|
+
function assertClaudeAuthorityStillClear(plan, targetHome) {
|
|
17061
|
+
if (plan.tool !== "claude" || plan.targetKind === "blocked")
|
|
17062
|
+
return;
|
|
17063
|
+
const conflicts = detectClaudeAuthorityConflicts(targetHome);
|
|
17064
|
+
if (conflicts.length === 0)
|
|
17065
|
+
return;
|
|
17066
|
+
const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
|
|
17067
|
+
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
17068
|
+
}
|
|
16398
17069
|
function ensureSessionTargetHome(targetHome) {
|
|
16399
17070
|
if (!existsSync12(targetHome))
|
|
16400
17071
|
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
@@ -16402,7 +17073,7 @@ function ensureSessionTargetHome(targetHome) {
|
|
|
16402
17073
|
}
|
|
16403
17074
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16404
17075
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16405
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome,
|
|
17076
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve10(manifestPath)), safeTargetHome) : resolve10(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16406
17077
|
const checkedAt = new Date().toISOString();
|
|
16407
17078
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16408
17079
|
if (!previousManifest) {
|
|
@@ -16429,7 +17100,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16429
17100
|
});
|
|
16430
17101
|
continue;
|
|
16431
17102
|
}
|
|
16432
|
-
const actualSha256 =
|
|
17103
|
+
const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
|
|
16433
17104
|
if (actualSha256 !== file.sha256) {
|
|
16434
17105
|
drifted.push({
|
|
16435
17106
|
path: target,
|
|
@@ -16452,7 +17123,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16452
17123
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
16453
17124
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
16454
17125
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16455
|
-
const resolvedSnapshotPath =
|
|
17126
|
+
const resolvedSnapshotPath = resolve10(snapshotPath);
|
|
16456
17127
|
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
16457
17128
|
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
|
|
16458
17129
|
throw new SessionApplyError("Session snapshot must be stored inside its target home.");
|
|
@@ -16570,19 +17241,19 @@ function requiredRestoreHash(file) {
|
|
|
16570
17241
|
return file.previousSha256;
|
|
16571
17242
|
}
|
|
16572
17243
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
16573
|
-
const resolved =
|
|
17244
|
+
const resolved = resolve10(snapshotPath);
|
|
16574
17245
|
if (!existsSync12(resolved))
|
|
16575
17246
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
16576
|
-
const stat =
|
|
17247
|
+
const stat = lstatSync4(resolved);
|
|
16577
17248
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
16578
17249
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
16579
17250
|
}
|
|
16580
|
-
if (
|
|
17251
|
+
if (statSync6(resolved).size > 32 * 1024 * 1024) {
|
|
16581
17252
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
16582
17253
|
}
|
|
16583
17254
|
let parsed;
|
|
16584
17255
|
try {
|
|
16585
|
-
parsed = JSON.parse(
|
|
17256
|
+
parsed = JSON.parse(readFileSync10(resolved, "utf8"));
|
|
16586
17257
|
} catch {
|
|
16587
17258
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
16588
17259
|
}
|
|
@@ -16604,7 +17275,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16604
17275
|
const previousManifest = snapshot.previousManifest;
|
|
16605
17276
|
const previousFiles = new Map;
|
|
16606
17277
|
for (const file of snapshot.files) {
|
|
16607
|
-
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" ||
|
|
17278
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2568(file.content) !== file.sha256) {
|
|
16608
17279
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
16609
17280
|
}
|
|
16610
17281
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -16651,7 +17322,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16651
17322
|
}
|
|
16652
17323
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
16653
17324
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
16654
|
-
const manifestPath =
|
|
17325
|
+
const manifestPath = resolve10(snapshot.manifestPath);
|
|
16655
17326
|
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
16656
17327
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
16657
17328
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
@@ -16660,7 +17331,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16660
17331
|
}
|
|
16661
17332
|
let parsedManifest;
|
|
16662
17333
|
try {
|
|
16663
|
-
parsedManifest = JSON.parse(
|
|
17334
|
+
parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
16664
17335
|
} catch {
|
|
16665
17336
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
16666
17337
|
}
|
|
@@ -16668,7 +17339,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16668
17339
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
16669
17340
|
}
|
|
16670
17341
|
const appliedManifest = parsedManifest;
|
|
16671
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
17342
|
+
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve10(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
|
|
16672
17343
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
16673
17344
|
}
|
|
16674
17345
|
const afterFiles = [];
|
|
@@ -16753,16 +17424,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
16753
17424
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
16754
17425
|
}
|
|
16755
17426
|
for (const entry of readdirSync3(dirname5(snapshotPath))) {
|
|
16756
|
-
const candidatePath =
|
|
16757
|
-
if (candidatePath ===
|
|
17427
|
+
const candidatePath = resolve10(dirname5(snapshotPath), entry);
|
|
17428
|
+
if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
|
|
16758
17429
|
continue;
|
|
16759
|
-
const candidateStat =
|
|
17430
|
+
const candidateStat = lstatSync4(candidatePath);
|
|
16760
17431
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
16761
17432
|
continue;
|
|
16762
17433
|
try {
|
|
16763
|
-
const candidate = JSON.parse(
|
|
17434
|
+
const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
|
|
16764
17435
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
16765
|
-
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" &&
|
|
17436
|
+
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve10(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
16766
17437
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
16767
17438
|
}
|
|
16768
17439
|
} catch (error) {
|
|
@@ -16820,7 +17491,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16820
17491
|
return "create";
|
|
16821
17492
|
}
|
|
16822
17493
|
if (file.role === "manifest" && previousManifest) {
|
|
16823
|
-
const previousManifestSha256 =
|
|
17494
|
+
const previousManifestSha256 = sha2568(`${JSON.stringify(previousManifest, null, 2)}
|
|
16824
17495
|
`);
|
|
16825
17496
|
if (previousManifestSha256 !== file.sha256) {
|
|
16826
17497
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -16831,15 +17502,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16831
17502
|
}
|
|
16832
17503
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
16833
17504
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
16834
|
-
if (
|
|
17505
|
+
if (resolve10(recordedPath) !== path) {
|
|
16835
17506
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
16836
17507
|
}
|
|
16837
17508
|
return path;
|
|
16838
17509
|
}
|
|
16839
17510
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
16840
17511
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
16841
|
-
const previousContent = existsSync12(target) ?
|
|
16842
|
-
const previousSha256 = previousContent === null ? null :
|
|
17512
|
+
const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
|
|
17513
|
+
const previousSha256 = previousContent === null ? null : sha2568(previousContent);
|
|
16843
17514
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
16844
17515
|
const changed = previousContent !== file.content;
|
|
16845
17516
|
if (previousContent !== null && !options.force && !previouslyManaged) {
|
|
@@ -16916,28 +17587,31 @@ function planFileResult(plan, file, targetHome, previousHashes, previousManifest
|
|
|
16916
17587
|
function assertNotSilentManagedWipeout(plan, results) {
|
|
16917
17588
|
if (plan.allowEmptySources)
|
|
16918
17589
|
return;
|
|
16919
|
-
const
|
|
16920
|
-
const underManagedDir = (relativePath) => relativePath === managedDir || relativePath.startsWith(`${managedDir}/`);
|
|
16921
|
-
const staleDeletions = results.filter((result) => result.action === "delete" && underManagedDir(result.relativePath));
|
|
17590
|
+
const staleDeletions = results.filter((result) => result.action === "delete" && isPlanManagedFile(plan, result.relativePath, result.role));
|
|
16922
17591
|
if (staleDeletions.length === 0)
|
|
16923
17592
|
return;
|
|
16924
|
-
const managedRetained = results.some((result) => result.action !== "delete" &&
|
|
17593
|
+
const managedRetained = results.some((result) => result.action !== "delete" && isPlanManagedFile(plan, result.relativePath, result.role));
|
|
16925
17594
|
if (managedRetained)
|
|
16926
17595
|
return;
|
|
16927
|
-
throw new SessionApplyError(`Session render plan for ${plan.tool} (${plan.adapter.mode}) would delete ${staleDeletions.length} ` +
|
|
17596
|
+
throw new SessionApplyError(`Session render plan for ${plan.tool} (${plan.adapter.mode}) would delete ${staleDeletions.length} ` + "previously managed file(s) and create or update none: " + `${staleDeletions.map((result) => result.relativePath).join(", ")}. ` + "Pass --allow-empty-sources only for explicit empty renders.");
|
|
17597
|
+
}
|
|
17598
|
+
function isPlanManagedFile(plan, relativePath, role) {
|
|
17599
|
+
const managedDir = plan.adapter.managedDir;
|
|
17600
|
+
if (relativePath === managedDir || relativePath.startsWith(`${managedDir}/`))
|
|
17601
|
+
return true;
|
|
17602
|
+
return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
|
|
16928
17603
|
}
|
|
16929
17604
|
function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
|
|
16930
17605
|
if (!previousManifest)
|
|
16931
17606
|
return [];
|
|
16932
|
-
|
|
16933
|
-
return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => file.relativePath === plan.adapter.managedDir || file.relativePath.startsWith(managedPrefix)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
|
|
17607
|
+
return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
|
|
16934
17608
|
}
|
|
16935
17609
|
function planStaleFileResult(file, targetHome, options) {
|
|
16936
17610
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
16937
17611
|
if (!existsSync12(target))
|
|
16938
17612
|
return null;
|
|
16939
|
-
const previousContent =
|
|
16940
|
-
const previousSha256 =
|
|
17613
|
+
const previousContent = readFileSync10(target, "utf-8");
|
|
17614
|
+
const previousSha256 = sha2568(previousContent);
|
|
16941
17615
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
16942
17616
|
return {
|
|
16943
17617
|
path: target,
|
|
@@ -16981,19 +17655,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
16981
17655
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
16982
17656
|
}
|
|
16983
17657
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
16984
|
-
const target =
|
|
17658
|
+
const target = resolve10(targetHome, ...file.relativePath.split("/"));
|
|
16985
17659
|
const rel = relative5(targetHome, target);
|
|
16986
17660
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
16987
17661
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
16988
17662
|
}
|
|
16989
|
-
if (
|
|
17663
|
+
if (resolve10(file.path) !== target) {
|
|
16990
17664
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
16991
17665
|
}
|
|
16992
17666
|
assertNoSymlinkSegments2(targetHome, target);
|
|
16993
17667
|
return target;
|
|
16994
17668
|
}
|
|
16995
17669
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
16996
|
-
const target =
|
|
17670
|
+
const target = resolve10(targetHome, ...relativePath.split(/[\\/]+/));
|
|
16997
17671
|
const rel = relative5(targetHome, target);
|
|
16998
17672
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
16999
17673
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
@@ -17005,7 +17679,7 @@ function readPreviousManifest(path) {
|
|
|
17005
17679
|
if (!existsSync12(path))
|
|
17006
17680
|
return null;
|
|
17007
17681
|
try {
|
|
17008
|
-
const parsed = JSON.parse(
|
|
17682
|
+
const parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
17009
17683
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
17010
17684
|
return null;
|
|
17011
17685
|
if (!Array.isArray(parsed.files))
|
|
@@ -17046,11 +17720,11 @@ function currentSessionFileHash(path, targetHome) {
|
|
|
17046
17720
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17047
17721
|
if (!existsSync12(path))
|
|
17048
17722
|
return null;
|
|
17049
|
-
const stat =
|
|
17723
|
+
const stat = lstatSync4(path);
|
|
17050
17724
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17051
17725
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
17052
17726
|
}
|
|
17053
|
-
return
|
|
17727
|
+
return sha2568(readFileSync10(path, "utf-8"));
|
|
17054
17728
|
}
|
|
17055
17729
|
function requiredPreviousHash(result) {
|
|
17056
17730
|
if (result.previousSha256 === null) {
|
|
@@ -17060,12 +17734,12 @@ function requiredPreviousHash(result) {
|
|
|
17060
17734
|
}
|
|
17061
17735
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
17062
17736
|
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync12(result.path)).map((result) => {
|
|
17063
|
-
const content =
|
|
17737
|
+
const content = readFileSync10(result.path, "utf-8");
|
|
17064
17738
|
return {
|
|
17065
17739
|
path: result.path,
|
|
17066
17740
|
relativePath: result.relativePath,
|
|
17067
17741
|
role: result.role,
|
|
17068
|
-
sha256:
|
|
17742
|
+
sha256: sha2568(content),
|
|
17069
17743
|
content
|
|
17070
17744
|
};
|
|
17071
17745
|
});
|
|
@@ -17078,7 +17752,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17078
17752
|
};
|
|
17079
17753
|
}
|
|
17080
17754
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
17081
|
-
const snapshotPath =
|
|
17755
|
+
const snapshotPath = resolve10(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
|
|
17082
17756
|
const afterFiles = results.map((result) => {
|
|
17083
17757
|
if (result.action === "conflict") {
|
|
17084
17758
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -17126,12 +17800,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17126
17800
|
function assertSafeTargetHome(targetHome) {
|
|
17127
17801
|
if (!isAbsolute4(targetHome))
|
|
17128
17802
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
17129
|
-
const normalized =
|
|
17803
|
+
const normalized = resolve10(targetHome);
|
|
17130
17804
|
if (normalized === parse4(normalized).root) {
|
|
17131
17805
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17132
17806
|
}
|
|
17133
17807
|
assertNoSymlinkAncestors2(normalized);
|
|
17134
|
-
if (existsSync12(normalized) &&
|
|
17808
|
+
if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
|
|
17135
17809
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17136
17810
|
}
|
|
17137
17811
|
return normalized;
|
|
@@ -17141,32 +17815,33 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
17141
17815
|
const rel = relative5(root, target);
|
|
17142
17816
|
let current = root;
|
|
17143
17817
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17144
|
-
current =
|
|
17145
|
-
if (existsSync12(current) &&
|
|
17818
|
+
current = join15(current, segment);
|
|
17819
|
+
if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
|
|
17146
17820
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17147
17821
|
}
|
|
17148
17822
|
}
|
|
17149
17823
|
}
|
|
17150
17824
|
function assertNoSymlinkAncestors2(path) {
|
|
17151
|
-
const normalized =
|
|
17825
|
+
const normalized = resolve10(path);
|
|
17152
17826
|
const parsed = parse4(normalized);
|
|
17153
17827
|
let current = parsed.root;
|
|
17154
17828
|
const rel = relative5(parsed.root, normalized);
|
|
17155
17829
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17156
|
-
current =
|
|
17830
|
+
current = join15(current, segment);
|
|
17157
17831
|
if (!existsSync12(current))
|
|
17158
17832
|
return;
|
|
17159
|
-
if (
|
|
17833
|
+
if (lstatSync4(current).isSymbolicLink()) {
|
|
17160
17834
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
17161
17835
|
}
|
|
17162
17836
|
}
|
|
17163
17837
|
}
|
|
17164
|
-
function
|
|
17165
|
-
return
|
|
17838
|
+
function sha2568(content) {
|
|
17839
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
17166
17840
|
}
|
|
17167
17841
|
|
|
17168
17842
|
// src/cli/index.tsx
|
|
17169
17843
|
init_session_render();
|
|
17844
|
+
init_raw_store_root();
|
|
17170
17845
|
init_asset_plan();
|
|
17171
17846
|
init_instruction_graph();
|
|
17172
17847
|
|
|
@@ -17513,34 +18188,433 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
17513
18188
|
|
|
17514
18189
|
// src/cli/index.tsx
|
|
17515
18190
|
init_codewith_shared_todos_storage_standard();
|
|
18191
|
+
|
|
18192
|
+
// src/lib/managed-skill-runtimes.ts
|
|
18193
|
+
import { createHash as createHash9 } from "crypto";
|
|
18194
|
+
import { spawnSync } from "child_process";
|
|
18195
|
+
import {
|
|
18196
|
+
existsSync as existsSync13,
|
|
18197
|
+
lstatSync as lstatSync5,
|
|
18198
|
+
mkdirSync as mkdirSync7,
|
|
18199
|
+
readFileSync as readFileSync11,
|
|
18200
|
+
renameSync as renameSync2,
|
|
18201
|
+
rmSync as rmSync5,
|
|
18202
|
+
writeFileSync as writeFileSync4
|
|
18203
|
+
} from "fs";
|
|
18204
|
+
import { homedir as homedir8 } from "os";
|
|
18205
|
+
import { dirname as dirname6, join as join16, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
|
|
18206
|
+
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
18207
|
+
var INBOX_SKILL_MARKERS = [
|
|
18208
|
+
[".claude", "skills", "inbox", "SKILL.md"],
|
|
18209
|
+
[".codex", "skills", "inbox", "SKILL.md"],
|
|
18210
|
+
[".codewith", "skills", "inbox", "SKILL.md"],
|
|
18211
|
+
[".config", "opencode", "skills", "inbox", "SKILL.md"],
|
|
18212
|
+
[".cursor", "skills", "inbox", "SKILL.md"]
|
|
18213
|
+
];
|
|
18214
|
+
var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
|
|
18215
|
+
function sha2569(content) {
|
|
18216
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
18217
|
+
}
|
|
18218
|
+
function lstatOrNull(path) {
|
|
18219
|
+
try {
|
|
18220
|
+
return lstatSync5(path);
|
|
18221
|
+
} catch {
|
|
18222
|
+
return null;
|
|
18223
|
+
}
|
|
18224
|
+
}
|
|
18225
|
+
function findSymlinkedAncestor(path) {
|
|
18226
|
+
const normalized = resolve11(path);
|
|
18227
|
+
const parsed = parse5(normalized);
|
|
18228
|
+
let current = parsed.root;
|
|
18229
|
+
const rel = relative6(parsed.root, normalized);
|
|
18230
|
+
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18231
|
+
current = join16(current, segment);
|
|
18232
|
+
if (!existsSync13(current))
|
|
18233
|
+
return null;
|
|
18234
|
+
if (lstatSync5(current).isSymbolicLink())
|
|
18235
|
+
return current;
|
|
18236
|
+
}
|
|
18237
|
+
return null;
|
|
18238
|
+
}
|
|
18239
|
+
function assertNoSymlinkAncestors3(path) {
|
|
18240
|
+
const found = findSymlinkedAncestor(path);
|
|
18241
|
+
if (found !== null) {
|
|
18242
|
+
throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
|
|
18243
|
+
}
|
|
18244
|
+
}
|
|
18245
|
+
function packagedInboxSkillPath(explicitPath) {
|
|
18246
|
+
if (explicitPath)
|
|
18247
|
+
return explicitPath;
|
|
18248
|
+
const candidates = [
|
|
18249
|
+
join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18250
|
+
join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18251
|
+
join16(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
18252
|
+
];
|
|
18253
|
+
const found = candidates.find((candidate) => existsSync13(candidate));
|
|
18254
|
+
if (!found) {
|
|
18255
|
+
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
18256
|
+
}
|
|
18257
|
+
return found;
|
|
18258
|
+
}
|
|
18259
|
+
function readCanonicalSkill(explicitPath) {
|
|
18260
|
+
const assetPath = packagedInboxSkillPath(explicitPath);
|
|
18261
|
+
const stat = lstatOrNull(assetPath);
|
|
18262
|
+
if (!stat?.isFile()) {
|
|
18263
|
+
throw new Error("packaged inbox skill contract is not a regular file");
|
|
18264
|
+
}
|
|
18265
|
+
const content = readFileSync11(assetPath, "utf8");
|
|
18266
|
+
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
18267
|
+
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
18268
|
+
}
|
|
18269
|
+
if (!content.includes("There is no separate")) {
|
|
18270
|
+
throw new Error("packaged inbox skill contract does not retire the legacy executable");
|
|
18271
|
+
}
|
|
18272
|
+
return { content, sha256: sha2569(content) };
|
|
18273
|
+
}
|
|
18274
|
+
function runProbe(command, args) {
|
|
18275
|
+
const result = spawnSync(command, args, {
|
|
18276
|
+
encoding: "utf8",
|
|
18277
|
+
timeout: 5000,
|
|
18278
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18279
|
+
});
|
|
18280
|
+
if (result.error || result.status !== 0) {
|
|
18281
|
+
return { ok: false, output: "" };
|
|
18282
|
+
}
|
|
18283
|
+
return {
|
|
18284
|
+
ok: true,
|
|
18285
|
+
output: `${result.stdout ?? ""}
|
|
18286
|
+
${result.stderr ?? ""}`.trim()
|
|
18287
|
+
};
|
|
18288
|
+
}
|
|
18289
|
+
function parseVersion(output) {
|
|
18290
|
+
return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
|
|
18291
|
+
}
|
|
18292
|
+
function compareVersions(left, right) {
|
|
18293
|
+
const a = left.split(".").map(Number);
|
|
18294
|
+
const b = right.split(".").map(Number);
|
|
18295
|
+
for (let i = 0;i < Math.max(a.length, b.length); i++) {
|
|
18296
|
+
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
18297
|
+
if (delta !== 0)
|
|
18298
|
+
return delta;
|
|
18299
|
+
}
|
|
18300
|
+
return 0;
|
|
18301
|
+
}
|
|
18302
|
+
function inspectSkillMarkers(homeDir2) {
|
|
18303
|
+
return INBOX_SKILL_MARKERS.map((parts) => join16(homeDir2, ...parts)).map((path) => {
|
|
18304
|
+
const stat = lstatOrNull(path);
|
|
18305
|
+
if (!stat)
|
|
18306
|
+
return null;
|
|
18307
|
+
if (!stat.isFile()) {
|
|
18308
|
+
return { path, content: null, mode: null, regular: false };
|
|
18309
|
+
}
|
|
18310
|
+
return {
|
|
18311
|
+
path,
|
|
18312
|
+
content: readFileSync11(path, "utf8"),
|
|
18313
|
+
mode: stat.mode & 511,
|
|
18314
|
+
regular: true
|
|
18315
|
+
};
|
|
18316
|
+
}).filter((snapshot) => snapshot !== null);
|
|
18317
|
+
}
|
|
18318
|
+
function inspectInbox(options) {
|
|
18319
|
+
const homeDir2 = options.homeDir ?? homedir8();
|
|
18320
|
+
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
18321
|
+
const snapshots = inspectSkillMarkers(homeDir2);
|
|
18322
|
+
const skillPresent = snapshots.length > 0;
|
|
18323
|
+
let canonicalContent = null;
|
|
18324
|
+
let canonicalSha256 = null;
|
|
18325
|
+
let assetError = null;
|
|
18326
|
+
try {
|
|
18327
|
+
const canonical = readCanonicalSkill(options.assetPath);
|
|
18328
|
+
canonicalContent = canonical.content;
|
|
18329
|
+
canonicalSha256 = canonical.sha256;
|
|
18330
|
+
} catch (error) {
|
|
18331
|
+
assetError = error instanceof Error ? error.message : String(error);
|
|
18332
|
+
}
|
|
18333
|
+
const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
|
|
18334
|
+
const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
|
|
18335
|
+
const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
|
|
18336
|
+
const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
|
|
18337
|
+
const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
|
|
18338
|
+
const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
|
|
18339
|
+
const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
|
|
18340
|
+
const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
|
|
18341
|
+
const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
|
|
18342
|
+
const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
|
|
18343
|
+
const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
|
|
18344
|
+
let reason = "skill not installed";
|
|
18345
|
+
if (skillPresent) {
|
|
18346
|
+
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
18347
|
+
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
|
|
18348
|
+
if (nonRegular)
|
|
18349
|
+
reason = "managed skill target is not a regular file";
|
|
18350
|
+
else if (symlinkAncestor)
|
|
18351
|
+
reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
|
|
18352
|
+
else if (assetError)
|
|
18353
|
+
reason = assetError;
|
|
18354
|
+
else if (!versionProbe.ok)
|
|
18355
|
+
reason = "conversations command unavailable";
|
|
18356
|
+
else if (!runtimeVersion)
|
|
18357
|
+
reason = "conversations version is unreadable";
|
|
18358
|
+
else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
|
|
18359
|
+
reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
|
|
18360
|
+
} else if (!helpProbe.ok)
|
|
18361
|
+
reason = "conversations watch help is unavailable";
|
|
18362
|
+
else if (!supportsFrom || !supportsAll || !supportsFullContent) {
|
|
18363
|
+
const missing = [
|
|
18364
|
+
!supportsFrom ? "--from" : null,
|
|
18365
|
+
!supportsAll ? "--all" : null,
|
|
18366
|
+
!supportsFullContent ? "--full-content" : null
|
|
18367
|
+
].filter((flag) => flag !== null);
|
|
18368
|
+
reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
|
|
18369
|
+
} else if (staleMarkers.length > 0)
|
|
18370
|
+
reason = "skill contract stale";
|
|
18371
|
+
else if (hostedHeartbeat === "failed")
|
|
18372
|
+
reason = "hosted heartbeat failed; manual fallback required";
|
|
18373
|
+
else if (hostedHeartbeat === "unverified")
|
|
18374
|
+
reason = "hosted heartbeat unverified; manual fallback required";
|
|
18375
|
+
else if (!deliveryVerified)
|
|
18376
|
+
reason = "hosted heartbeat passed; channel and DM delivery verification required";
|
|
18377
|
+
else
|
|
18378
|
+
reason = "ready";
|
|
18379
|
+
}
|
|
18380
|
+
return {
|
|
18381
|
+
status: {
|
|
18382
|
+
skill: "inbox",
|
|
18383
|
+
runtime: "conversations watch",
|
|
18384
|
+
minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
18385
|
+
skill_present: skillPresent,
|
|
18386
|
+
skill_markers: snapshots.map((snapshot) => snapshot.path),
|
|
18387
|
+
skill_contracts_current: snapshots.length - staleMarkers.length,
|
|
18388
|
+
stale_skill_markers: staleMarkers,
|
|
18389
|
+
expected_skill_sha256: canonicalSha256,
|
|
18390
|
+
runtime_command: runtimeCommand,
|
|
18391
|
+
runtime_present: versionProbe.ok,
|
|
18392
|
+
runtime_version: runtimeVersion,
|
|
18393
|
+
watch_supports_from: supportsFrom,
|
|
18394
|
+
watch_supports_all: supportsAll,
|
|
18395
|
+
watch_supports_full_content: supportsFullContent,
|
|
18396
|
+
hosted_heartbeat: hostedHeartbeat,
|
|
18397
|
+
delivery_verified: deliveryVerified,
|
|
18398
|
+
manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
|
|
18399
|
+
healthy: !skillPresent || reason === "ready",
|
|
18400
|
+
reason
|
|
18401
|
+
},
|
|
18402
|
+
canonicalContent,
|
|
18403
|
+
snapshots
|
|
18404
|
+
};
|
|
18405
|
+
}
|
|
18406
|
+
function inspectManagedSkillRuntimes(options = {}) {
|
|
18407
|
+
const runtime = inspectInbox(options).status;
|
|
18408
|
+
const installed = runtime.skill_present ? [runtime] : [];
|
|
18409
|
+
return {
|
|
18410
|
+
runtimes: [runtime],
|
|
18411
|
+
skills_present: installed.length,
|
|
18412
|
+
healthy: installed.filter((item) => item.healthy).length,
|
|
18413
|
+
missing: installed.filter((item) => !item.healthy).length
|
|
18414
|
+
};
|
|
18415
|
+
}
|
|
18416
|
+
function runtimeReadyForWrite(status) {
|
|
18417
|
+
return status.runtime_present && status.runtime_version !== null && compareVersions(status.runtime_version, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && status.watch_supports_from && status.watch_supports_all && status.watch_supports_full_content;
|
|
18418
|
+
}
|
|
18419
|
+
function projectUpdatedStatus(status, contractCount) {
|
|
18420
|
+
const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
|
|
18421
|
+
return {
|
|
18422
|
+
...status,
|
|
18423
|
+
skill_contracts_current: contractCount,
|
|
18424
|
+
stale_skill_markers: [],
|
|
18425
|
+
manual_fallback_ready: true,
|
|
18426
|
+
healthy,
|
|
18427
|
+
reason: healthy ? "ready" : status.hosted_heartbeat === "failed" ? "hosted heartbeat failed; manual fallback required" : status.hosted_heartbeat === "unverified" ? "hosted heartbeat unverified; manual fallback required" : "hosted heartbeat passed; channel and DM delivery verification required"
|
|
18428
|
+
};
|
|
18429
|
+
}
|
|
18430
|
+
function cleanup(path) {
|
|
18431
|
+
rmSync5(path, { force: true });
|
|
18432
|
+
}
|
|
18433
|
+
function writeAtomic(path, content, mode) {
|
|
18434
|
+
assertNoSymlinkAncestors3(dirname6(path));
|
|
18435
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
18436
|
+
try {
|
|
18437
|
+
mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
|
|
18438
|
+
writeFileSync4(tempPath, content, { mode, flag: "wx" });
|
|
18439
|
+
renameSync2(tempPath, path);
|
|
18440
|
+
} finally {
|
|
18441
|
+
cleanup(tempPath);
|
|
18442
|
+
}
|
|
18443
|
+
}
|
|
18444
|
+
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
18445
|
+
lstat: lstatOrNull,
|
|
18446
|
+
read: (path) => readFileSync11(path, "utf8"),
|
|
18447
|
+
write: writeAtomic
|
|
18448
|
+
};
|
|
18449
|
+
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
18450
|
+
const written = [];
|
|
18451
|
+
try {
|
|
18452
|
+
for (const snapshot of snapshots) {
|
|
18453
|
+
const currentStat = fileOperations.lstat(snapshot.path);
|
|
18454
|
+
if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
|
|
18455
|
+
throw new Error("managed skill changed after inspection; refusing a stale write");
|
|
18456
|
+
}
|
|
18457
|
+
fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
|
|
18458
|
+
written.push(snapshot);
|
|
18459
|
+
}
|
|
18460
|
+
return { ok: true, error: null, rollback_conflicts: [] };
|
|
18461
|
+
} catch (error) {
|
|
18462
|
+
const rollbackConflicts = [];
|
|
18463
|
+
for (const snapshot of written.reverse()) {
|
|
18464
|
+
const currentStat = fileOperations.lstat(snapshot.path);
|
|
18465
|
+
if (!currentStat?.isFile()) {
|
|
18466
|
+
rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
|
|
18467
|
+
continue;
|
|
18468
|
+
}
|
|
18469
|
+
let currentContent;
|
|
18470
|
+
try {
|
|
18471
|
+
currentContent = fileOperations.read(snapshot.path);
|
|
18472
|
+
} catch {
|
|
18473
|
+
rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
|
|
18474
|
+
continue;
|
|
18475
|
+
}
|
|
18476
|
+
if (currentContent !== canonicalContent) {
|
|
18477
|
+
rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
|
|
18478
|
+
continue;
|
|
18479
|
+
}
|
|
18480
|
+
try {
|
|
18481
|
+
fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
|
|
18482
|
+
} catch {
|
|
18483
|
+
rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
|
|
18484
|
+
}
|
|
18485
|
+
}
|
|
18486
|
+
return {
|
|
18487
|
+
ok: false,
|
|
18488
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18489
|
+
rollback_conflicts: rollbackConflicts
|
|
18490
|
+
};
|
|
18491
|
+
}
|
|
18492
|
+
}
|
|
18493
|
+
async function reconcileManagedSkillRuntimes(options = {}) {
|
|
18494
|
+
const dryRun = options.dryRun ?? false;
|
|
18495
|
+
const before = inspectInbox(options);
|
|
18496
|
+
const status = before.status;
|
|
18497
|
+
if (!status.skill_present) {
|
|
18498
|
+
return {
|
|
18499
|
+
runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18500
|
+
changed: 0,
|
|
18501
|
+
failed: 0,
|
|
18502
|
+
dry_run: dryRun
|
|
18503
|
+
};
|
|
18504
|
+
}
|
|
18505
|
+
if (before.snapshots.some((snapshot) => !snapshot.regular)) {
|
|
18506
|
+
return {
|
|
18507
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18508
|
+
changed: 0,
|
|
18509
|
+
failed: 1,
|
|
18510
|
+
dry_run: dryRun
|
|
18511
|
+
};
|
|
18512
|
+
}
|
|
18513
|
+
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
|
|
18514
|
+
if (symlinkedAncestor) {
|
|
18515
|
+
return {
|
|
18516
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18517
|
+
changed: 0,
|
|
18518
|
+
failed: 1,
|
|
18519
|
+
dry_run: dryRun
|
|
18520
|
+
};
|
|
18521
|
+
}
|
|
18522
|
+
if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
|
|
18523
|
+
return {
|
|
18524
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18525
|
+
changed: 0,
|
|
18526
|
+
failed: 1,
|
|
18527
|
+
dry_run: dryRun
|
|
18528
|
+
};
|
|
18529
|
+
}
|
|
18530
|
+
const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
|
|
18531
|
+
if (staleSnapshots.length === 0) {
|
|
18532
|
+
return {
|
|
18533
|
+
runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18534
|
+
changed: 0,
|
|
18535
|
+
failed: 0,
|
|
18536
|
+
dry_run: dryRun
|
|
18537
|
+
};
|
|
18538
|
+
}
|
|
18539
|
+
if (dryRun) {
|
|
18540
|
+
const projected = projectUpdatedStatus(status, before.snapshots.length);
|
|
18541
|
+
return {
|
|
18542
|
+
runtimes: [{
|
|
18543
|
+
...projected,
|
|
18544
|
+
action: "update",
|
|
18545
|
+
dry_run: true,
|
|
18546
|
+
skill_contracts_changed: staleSnapshots.length
|
|
18547
|
+
}],
|
|
18548
|
+
changed: 1,
|
|
18549
|
+
failed: 0,
|
|
18550
|
+
dry_run: true
|
|
18551
|
+
};
|
|
18552
|
+
}
|
|
18553
|
+
const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
|
|
18554
|
+
path: snapshot.path,
|
|
18555
|
+
content: snapshot.content,
|
|
18556
|
+
mode: snapshot.mode ?? 420
|
|
18557
|
+
})), before.canonicalContent);
|
|
18558
|
+
if (!transaction.ok) {
|
|
18559
|
+
const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
|
|
18560
|
+
const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
|
|
18561
|
+
return {
|
|
18562
|
+
runtimes: [{
|
|
18563
|
+
...status,
|
|
18564
|
+
action: "failed",
|
|
18565
|
+
dry_run: false,
|
|
18566
|
+
skill_contracts_changed: 0,
|
|
18567
|
+
reason
|
|
18568
|
+
}],
|
|
18569
|
+
changed: 0,
|
|
18570
|
+
failed: 1,
|
|
18571
|
+
dry_run: false
|
|
18572
|
+
};
|
|
18573
|
+
}
|
|
18574
|
+
const after = inspectInbox(options).status;
|
|
18575
|
+
const accepted = after.healthy || after.manual_fallback_ready;
|
|
18576
|
+
return {
|
|
18577
|
+
runtimes: [{
|
|
18578
|
+
...after,
|
|
18579
|
+
action: accepted ? "update" : "failed",
|
|
18580
|
+
dry_run: false,
|
|
18581
|
+
skill_contracts_changed: accepted ? staleSnapshots.length : 0
|
|
18582
|
+
}],
|
|
18583
|
+
changed: accepted ? 1 : 0,
|
|
18584
|
+
failed: accepted ? 0 : 1,
|
|
18585
|
+
dry_run: false
|
|
18586
|
+
};
|
|
18587
|
+
}
|
|
18588
|
+
|
|
18589
|
+
// src/cli/index.tsx
|
|
17516
18590
|
init_project_context();
|
|
17517
18591
|
|
|
17518
18592
|
// src/status.ts
|
|
17519
18593
|
init_config_store();
|
|
17520
18594
|
init_apply();
|
|
17521
18595
|
init_config_agents();
|
|
17522
|
-
import { existsSync as
|
|
18596
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
17523
18597
|
|
|
17524
18598
|
// src/lib/package-version.ts
|
|
17525
|
-
import { existsSync as
|
|
17526
|
-
import { dirname as
|
|
18599
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
18600
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
17527
18601
|
import { fileURLToPath } from "url";
|
|
17528
18602
|
var cached = null;
|
|
17529
18603
|
function getPackageVersion() {
|
|
17530
18604
|
if (cached)
|
|
17531
18605
|
return cached;
|
|
17532
18606
|
try {
|
|
17533
|
-
let dir =
|
|
18607
|
+
let dir = dirname7(fileURLToPath(import.meta.url));
|
|
17534
18608
|
for (let i = 0;i < 8; i++) {
|
|
17535
|
-
const pkgPath =
|
|
17536
|
-
if (
|
|
17537
|
-
const pkg = JSON.parse(
|
|
18609
|
+
const pkgPath = join17(dir, "package.json");
|
|
18610
|
+
if (existsSync14(pkgPath)) {
|
|
18611
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
17538
18612
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
17539
18613
|
cached = pkg.version;
|
|
17540
18614
|
return cached;
|
|
17541
18615
|
}
|
|
17542
18616
|
}
|
|
17543
|
-
const parent =
|
|
18617
|
+
const parent = dirname7(dir);
|
|
17544
18618
|
if (parent === dir)
|
|
17545
18619
|
break;
|
|
17546
18620
|
dir = parent;
|
|
@@ -17573,7 +18647,7 @@ function countBy(items, getValue) {
|
|
|
17573
18647
|
}
|
|
17574
18648
|
return counts;
|
|
17575
18649
|
}
|
|
17576
|
-
async function getConfigsStatus(store = resolveConfigStore()) {
|
|
18650
|
+
async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
17577
18651
|
let databaseReachable = true;
|
|
17578
18652
|
let configs = [];
|
|
17579
18653
|
let categoryStats = { total: 0 };
|
|
@@ -17597,11 +18671,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17597
18671
|
continue;
|
|
17598
18672
|
knownTargets += 1;
|
|
17599
18673
|
const targetPath = expandPath(config.target_path);
|
|
17600
|
-
if (!
|
|
18674
|
+
if (!existsSync15(targetPath)) {
|
|
17601
18675
|
missingTargets += 1;
|
|
17602
18676
|
continue;
|
|
17603
18677
|
}
|
|
17604
|
-
const disk =
|
|
18678
|
+
const disk = readFileSync13(targetPath, "utf-8");
|
|
17605
18679
|
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
17606
18680
|
if (redactedDisk !== config.content) {
|
|
17607
18681
|
driftedTargets += 1;
|
|
@@ -17627,7 +18701,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17627
18701
|
}
|
|
17628
18702
|
}
|
|
17629
18703
|
const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
|
|
17630
|
-
const
|
|
18704
|
+
const managedSkillRuntimes = inspectManagedSkillRuntimes({
|
|
18705
|
+
homeDir: options.homeDir,
|
|
18706
|
+
conversationsCommand: options.conversationsCommand
|
|
18707
|
+
});
|
|
18708
|
+
const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 && managedSkillRuntimes.missing === 0 ? "ok" : "warn";
|
|
17631
18709
|
return {
|
|
17632
18710
|
service: "configs",
|
|
17633
18711
|
schemaVersion: "1.0",
|
|
@@ -17657,7 +18735,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17657
18735
|
profileLinks,
|
|
17658
18736
|
machines,
|
|
17659
18737
|
snapshots,
|
|
17660
|
-
knownTargets
|
|
18738
|
+
knownTargets,
|
|
18739
|
+
managedSkillRuntimes: {
|
|
18740
|
+
skillsPresent: managedSkillRuntimes.skills_present,
|
|
18741
|
+
healthy: managedSkillRuntimes.healthy,
|
|
18742
|
+
missing: managedSkillRuntimes.missing
|
|
18743
|
+
}
|
|
17661
18744
|
},
|
|
17662
18745
|
health: {
|
|
17663
18746
|
status,
|
|
@@ -17666,10 +18749,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17666
18749
|
missingTargets,
|
|
17667
18750
|
unredactedSecretFindings,
|
|
17668
18751
|
retiredAgentRows,
|
|
18752
|
+
missingManagedSkillRuntimes: managedSkillRuntimes.missing,
|
|
17669
18753
|
hasDrift: driftedTargets > 0,
|
|
17670
18754
|
hasMissingTargets: missingTargets > 0,
|
|
17671
18755
|
hasUnredactedSecrets: unredactedSecretFindings > 0,
|
|
17672
|
-
hasRetiredAgentRows: retiredAgentRows > 0
|
|
18756
|
+
hasRetiredAgentRows: retiredAgentRows > 0,
|
|
18757
|
+
hasMissingManagedSkillRuntimes: managedSkillRuntimes.missing > 0
|
|
17673
18758
|
},
|
|
17674
18759
|
safety: {
|
|
17675
18760
|
includesConfigValues: false,
|
|
@@ -17711,6 +18796,17 @@ function printLine(text = "") {
|
|
|
17711
18796
|
function printJson(value) {
|
|
17712
18797
|
printLine(JSON.stringify(value, null, 2));
|
|
17713
18798
|
}
|
|
18799
|
+
function printManagedSkillRuntimeReport(report) {
|
|
18800
|
+
for (const runtime of report.runtimes) {
|
|
18801
|
+
if (!runtime.skill_present)
|
|
18802
|
+
continue;
|
|
18803
|
+
const prefix = runtime.action === "failed" ? chalk.red("[failed]") : runtime.dry_run ? chalk.yellow("[dry-run]") : runtime.manual_fallback_ready && !runtime.healthy ? chalk.yellow("[degraded]") : runtime.action === "unchanged" ? chalk.dim("=") : chalk.green("\u2713");
|
|
18804
|
+
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
|
|
18805
|
+
if (runtime.action === "update") {
|
|
18806
|
+
console.log(chalk.dim(` skill contracts: ${runtime.skill_contracts_changed}`));
|
|
18807
|
+
}
|
|
18808
|
+
}
|
|
18809
|
+
}
|
|
17714
18810
|
function fmtConfig(c, format) {
|
|
17715
18811
|
if (format === "json")
|
|
17716
18812
|
return JSON.stringify(c, null, 2);
|
|
@@ -17777,7 +18873,7 @@ function parseSessionSource(value, order) {
|
|
|
17777
18873
|
if (!path)
|
|
17778
18874
|
throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
|
|
17779
18875
|
const absPath = resolveSessionPath(path);
|
|
17780
|
-
if (!
|
|
18876
|
+
if (!existsSync17(absPath))
|
|
17781
18877
|
throw new Error(`Instruction source file not found: ${absPath}`);
|
|
17782
18878
|
const content = readSessionInstructionSourceFile(absPath);
|
|
17783
18879
|
const source = sourceFromFilePath(absPath, content, order);
|
|
@@ -17821,7 +18917,7 @@ function sessionSourceReplacements(values) {
|
|
|
17821
18917
|
return replacements;
|
|
17822
18918
|
}
|
|
17823
18919
|
function readSessionInstructionSourceFile(path) {
|
|
17824
|
-
const stat =
|
|
18920
|
+
const stat = lstatSync7(path);
|
|
17825
18921
|
if (stat.isSymbolicLink()) {
|
|
17826
18922
|
throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
|
|
17827
18923
|
}
|
|
@@ -17831,7 +18927,7 @@ function readSessionInstructionSourceFile(path) {
|
|
|
17831
18927
|
if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
|
|
17832
18928
|
throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
|
|
17833
18929
|
}
|
|
17834
|
-
return
|
|
18930
|
+
return readFileSync15(path, "utf-8");
|
|
17835
18931
|
}
|
|
17836
18932
|
function parseLayeredReference(value) {
|
|
17837
18933
|
const trimmed = value.trim();
|
|
@@ -17858,9 +18954,9 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
17858
18954
|
}
|
|
17859
18955
|
for (const value of opts.identityExport ?? []) {
|
|
17860
18956
|
const path = resolveSessionPath(value);
|
|
17861
|
-
if (!
|
|
18957
|
+
if (!existsSync17(path))
|
|
17862
18958
|
throw new Error(`Identity instruction export not found: ${path}`);
|
|
17863
|
-
const parsed = JSON.parse(
|
|
18959
|
+
const parsed = JSON.parse(readFileSync15(path, "utf-8"));
|
|
17864
18960
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
17865
18961
|
}
|
|
17866
18962
|
return sources.map((source) => {
|
|
@@ -17981,19 +19077,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
|
|
|
17981
19077
|
if (value === "-")
|
|
17982
19078
|
return { json: readBoundedProjectContextStdin() };
|
|
17983
19079
|
const path = resolveSessionPath(value);
|
|
17984
|
-
if (!
|
|
19080
|
+
if (!existsSync17(path)) {
|
|
17985
19081
|
if (allowMissing)
|
|
17986
19082
|
return {};
|
|
17987
19083
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
|
|
17988
19084
|
}
|
|
17989
|
-
const stat =
|
|
19085
|
+
const stat = lstatSync7(path);
|
|
17990
19086
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17991
19087
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
|
|
17992
19088
|
}
|
|
17993
19089
|
if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
|
|
17994
19090
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
|
|
17995
19091
|
}
|
|
17996
|
-
return { json:
|
|
19092
|
+
return { json: readFileSync15(path, "utf8"), sourcePath: path };
|
|
17997
19093
|
}
|
|
17998
19094
|
function readBoundedProjectContextStdin() {
|
|
17999
19095
|
const chunks = [];
|
|
@@ -18152,15 +19248,15 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
|
|
|
18152
19248
|
console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
|
|
18153
19249
|
});
|
|
18154
19250
|
program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
|
|
18155
|
-
const abs =
|
|
18156
|
-
if (!
|
|
19251
|
+
const abs = resolve13(filePath);
|
|
19252
|
+
if (!existsSync17(abs)) {
|
|
18157
19253
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
18158
19254
|
process.exit(1);
|
|
18159
19255
|
}
|
|
18160
|
-
const rawContent =
|
|
19256
|
+
const rawContent = readFileSync15(abs, "utf-8");
|
|
18161
19257
|
const fmt = detectFormat(abs);
|
|
18162
19258
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
18163
|
-
const targetPath = abs.startsWith(
|
|
19259
|
+
const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
|
|
18164
19260
|
const name = opts.name || filePath.split("/").pop();
|
|
18165
19261
|
const store = resolveConfigStore();
|
|
18166
19262
|
const allConfigs = await store.listConfigs();
|
|
@@ -18335,7 +19431,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
18335
19431
|
for (const entry of entries) {
|
|
18336
19432
|
if (!entry.isDirectory())
|
|
18337
19433
|
continue;
|
|
18338
|
-
const projDir =
|
|
19434
|
+
const projDir = join19(absDir, entry.name);
|
|
18339
19435
|
const hasAgentConfig = [
|
|
18340
19436
|
"CLAUDE.md",
|
|
18341
19437
|
".mcp.json",
|
|
@@ -18348,7 +19444,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
18348
19444
|
".aicopilot",
|
|
18349
19445
|
".cursor",
|
|
18350
19446
|
".agents"
|
|
18351
|
-
].some((marker) =>
|
|
19447
|
+
].some((marker) => existsSync17(join19(projDir, marker)));
|
|
18352
19448
|
if (!hasAgentConfig)
|
|
18353
19449
|
continue;
|
|
18354
19450
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
|
|
@@ -18399,10 +19495,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
18399
19495
|
});
|
|
18400
19496
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
18401
19497
|
const store = resolveConfigStore();
|
|
18402
|
-
const dbPath =
|
|
19498
|
+
const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
|
|
18403
19499
|
const stats = await store.getConfigStats();
|
|
18404
19500
|
console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
|
|
18405
|
-
console.log(chalk.cyan(
|
|
19501
|
+
console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
|
|
18406
19502
|
console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
|
|
18407
19503
|
console.log();
|
|
18408
19504
|
console.log(chalk.bold("By category:"));
|
|
@@ -18622,7 +19718,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
|
|
|
18622
19718
|
process.exit(1);
|
|
18623
19719
|
}
|
|
18624
19720
|
});
|
|
18625
|
-
profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
|
|
19721
|
+
profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
|
|
18626
19722
|
try {
|
|
18627
19723
|
const store = resolveConfigStore();
|
|
18628
19724
|
const { machine, profile } = await getMachineProfileContext(opts, store);
|
|
@@ -18659,6 +19755,14 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
|
|
|
18659
19755
|
}
|
|
18660
19756
|
if (report.failures.length > 0)
|
|
18661
19757
|
process.exitCode = 1;
|
|
19758
|
+
const runtimeReport = await reconcileManagedSkillRuntimes({
|
|
19759
|
+
dryRun: opts.dryRun,
|
|
19760
|
+
agent: opts.from,
|
|
19761
|
+
deliveryVerified: opts.deliveryVerified
|
|
19762
|
+
});
|
|
19763
|
+
printManagedSkillRuntimeReport(runtimeReport);
|
|
19764
|
+
if (runtimeReport.failed > 0)
|
|
19765
|
+
process.exitCode = 1;
|
|
18662
19766
|
console.log(chalk.dim(`
|
|
18663
19767
|
${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
|
|
18664
19768
|
} catch (e) {
|
|
@@ -19146,14 +20250,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
|
|
|
19146
20250
|
} else if (target === "codex") {
|
|
19147
20251
|
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
19148
20252
|
const { join: j } = await import("path");
|
|
19149
|
-
const configPath = j(
|
|
20253
|
+
const configPath = j(homedir10(), ".codex", "config.toml");
|
|
19150
20254
|
const block = `
|
|
19151
20255
|
[mcp_servers.configs]
|
|
19152
20256
|
command = "${mcpBinary}"
|
|
19153
20257
|
args = []
|
|
19154
20258
|
`;
|
|
19155
20259
|
if (ex(configPath)) {
|
|
19156
|
-
const content =
|
|
20260
|
+
const content = readFileSync15(configPath, "utf-8");
|
|
19157
20261
|
if (content.includes("[mcp_servers.configs]")) {
|
|
19158
20262
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
19159
20263
|
continue;
|
|
@@ -19164,7 +20268,7 @@ args = []
|
|
|
19164
20268
|
} else if (target === "antigravity") {
|
|
19165
20269
|
const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
19166
20270
|
const { dirname: dn, join: j } = await import("path");
|
|
19167
|
-
const configPath = j(
|
|
20271
|
+
const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
|
|
19168
20272
|
let settings = {};
|
|
19169
20273
|
if (ex(configPath)) {
|
|
19170
20274
|
try {
|
|
@@ -19248,9 +20352,9 @@ DB stats:`));
|
|
|
19248
20352
|
if (count > 0)
|
|
19249
20353
|
console.log(` ${key.padEnd(18)} ${count}`);
|
|
19250
20354
|
}
|
|
19251
|
-
const location =
|
|
20355
|
+
const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
|
|
19252
20356
|
console.log(chalk.dim(`
|
|
19253
|
-
${
|
|
20357
|
+
${isApiTransport() ? "API" : "DB"}: ${location}`));
|
|
19254
20358
|
});
|
|
19255
20359
|
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
|
|
19256
20360
|
const status = await getConfigsStatus(resolveConfigStore());
|
|
@@ -19266,14 +20370,53 @@ program.command("status").description("Health check: total configs, drift from d
|
|
|
19266
20370
|
console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
|
|
19267
20371
|
console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
|
|
19268
20372
|
console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
|
|
20373
|
+
console.log(chalk.cyan("Skill runtimes:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green(`${status.counts.managedSkillRuntimes.healthy} ready`) : chalk.yellow(`${status.health.missingManagedSkillRuntimes} missing`)} (${status.counts.managedSkillRuntimes.skillsPresent} managed skill(s) present)`);
|
|
19269
20374
|
console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
|
|
19270
20375
|
});
|
|
20376
|
+
var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
|
|
20377
|
+
managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the full local runtime status as JSON").action((opts) => {
|
|
20378
|
+
const report = inspectManagedSkillRuntimes({
|
|
20379
|
+
agent: opts.from,
|
|
20380
|
+
deliveryVerified: opts.deliveryVerified
|
|
20381
|
+
});
|
|
20382
|
+
if (opts.json) {
|
|
20383
|
+
printJson(report);
|
|
20384
|
+
if (report.missing > 0)
|
|
20385
|
+
process.exitCode = 1;
|
|
20386
|
+
return;
|
|
20387
|
+
}
|
|
20388
|
+
if (report.skills_present === 0) {
|
|
20389
|
+
console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
|
|
20390
|
+
return;
|
|
20391
|
+
}
|
|
20392
|
+
for (const runtime of report.runtimes) {
|
|
20393
|
+
if (!runtime.skill_present)
|
|
20394
|
+
continue;
|
|
20395
|
+
const prefix = runtime.healthy ? chalk.green("\u2713") : chalk.yellow("!");
|
|
20396
|
+
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
|
|
20397
|
+
}
|
|
20398
|
+
if (report.missing > 0)
|
|
20399
|
+
process.exitCode = 1;
|
|
20400
|
+
});
|
|
20401
|
+
managedSkillsCmd.command("apply").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the reconcile report as JSON").action(async (opts) => {
|
|
20402
|
+
const report = await reconcileManagedSkillRuntimes({
|
|
20403
|
+
dryRun: opts.dryRun,
|
|
20404
|
+
agent: opts.from,
|
|
20405
|
+
deliveryVerified: opts.deliveryVerified
|
|
20406
|
+
});
|
|
20407
|
+
if (opts.json)
|
|
20408
|
+
printJson(report);
|
|
20409
|
+
else
|
|
20410
|
+
printManagedSkillRuntimeReport(report);
|
|
20411
|
+
if (report.failed > 0)
|
|
20412
|
+
process.exitCode = 1;
|
|
20413
|
+
});
|
|
19271
20414
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
19272
20415
|
const { mkdirSync: mk } = await import("fs");
|
|
19273
|
-
const backupDir =
|
|
20416
|
+
const backupDir = join19(getRawStoreRoot(), "backups");
|
|
19274
20417
|
mk(backupDir, { recursive: true });
|
|
19275
20418
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
19276
|
-
const outPath =
|
|
20419
|
+
const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
|
|
19277
20420
|
const result = await exportConfigs(outPath, { store: resolveConfigStore() });
|
|
19278
20421
|
const { statSync: st } = await import("fs");
|
|
19279
20422
|
const size = st(outPath).size;
|
|
@@ -19291,7 +20434,7 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
19291
20434
|
const store = resolveConfigStore();
|
|
19292
20435
|
let issues = 0;
|
|
19293
20436
|
const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
|
|
19294
|
-
const
|
|
20437
|
+
const fail2 = (msg) => {
|
|
19295
20438
|
issues++;
|
|
19296
20439
|
console.log(chalk.red(" \u2717 ") + msg);
|
|
19297
20440
|
};
|
|
@@ -19301,9 +20444,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
19301
20444
|
console.log(chalk.cyan("Known files on disk:"));
|
|
19302
20445
|
for (const k of KNOWN_CONFIGS) {
|
|
19303
20446
|
if (k.rulesDir) {
|
|
19304
|
-
|
|
20447
|
+
existsSync17(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
|
|
19305
20448
|
} else {
|
|
19306
|
-
|
|
20449
|
+
existsSync17(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
|
|
19307
20450
|
}
|
|
19308
20451
|
}
|
|
19309
20452
|
const allConfigs = await store.listConfigs();
|
|
@@ -19316,7 +20459,7 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19316
20459
|
JSON.parse(c.content);
|
|
19317
20460
|
validCount++;
|
|
19318
20461
|
} catch {
|
|
19319
|
-
|
|
20462
|
+
fail2(`${c.slug}: invalid JSON`);
|
|
19320
20463
|
}
|
|
19321
20464
|
} else {
|
|
19322
20465
|
validCount++;
|
|
@@ -19328,13 +20471,13 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19328
20471
|
const found = scanSecrets(c.content, c.format);
|
|
19329
20472
|
secretCount += found.length;
|
|
19330
20473
|
}
|
|
19331
|
-
secretCount === 0 ? pass("No unredacted secrets") :
|
|
20474
|
+
secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
|
|
19332
20475
|
const duplicateTargets = findDuplicateTargetPathGroups(allConfigs);
|
|
19333
20476
|
if (duplicateTargets.length === 0) {
|
|
19334
20477
|
pass("No target path is claimed by more than one config");
|
|
19335
20478
|
} else {
|
|
19336
20479
|
const rowCount = duplicateTargets.reduce((total, group) => total + group.configs.length, 0);
|
|
19337
|
-
|
|
20480
|
+
fail2(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
|
|
19338
20481
|
for (const group of duplicateTargets) {
|
|
19339
20482
|
console.log(chalk.yellow(` ${group.target_path}`));
|
|
19340
20483
|
for (const c of group.configs) {
|
|
@@ -19348,7 +20491,7 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19348
20491
|
pass("No reference config name is claimed by more than one row");
|
|
19349
20492
|
} else {
|
|
19350
20493
|
const rowCount = duplicateReferenceNames.reduce((total, group) => total + group.configs.length, 0);
|
|
19351
|
-
|
|
20494
|
+
fail2(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
|
|
19352
20495
|
for (const group of duplicateReferenceNames) {
|
|
19353
20496
|
console.log(chalk.yellow(` ${group.name}`));
|
|
19354
20497
|
for (const c of group.configs) {
|
|
@@ -19456,16 +20599,16 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19456
20599
|
for (const k of KNOWN_CONFIGS) {
|
|
19457
20600
|
if (k.rulesDir) {
|
|
19458
20601
|
const absDir = expandPath2(k.rulesDir);
|
|
19459
|
-
if (!
|
|
20602
|
+
if (!existsSync17(absDir))
|
|
19460
20603
|
continue;
|
|
19461
20604
|
const { readdirSync: readdirSync5 } = await import("fs");
|
|
19462
20605
|
for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
19463
|
-
const abs =
|
|
20606
|
+
const abs = join19(absDir, f);
|
|
19464
20607
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19465
20608
|
}
|
|
19466
20609
|
} else {
|
|
19467
20610
|
const abs = expandPath2(k.path);
|
|
19468
|
-
if (
|
|
20611
|
+
if (existsSync17(abs))
|
|
19469
20612
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19470
20613
|
}
|
|
19471
20614
|
}
|
|
@@ -19473,7 +20616,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19473
20616
|
const tick = async () => {
|
|
19474
20617
|
let changed = 0;
|
|
19475
20618
|
for (const [abs, oldMtime] of mtimes) {
|
|
19476
|
-
if (!
|
|
20619
|
+
if (!existsSync17(abs))
|
|
19477
20620
|
continue;
|
|
19478
20621
|
const newMtime = st(abs).mtimeMs;
|
|
19479
20622
|
if (newMtime !== oldMtime) {
|
|
@@ -19485,10 +20628,10 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19485
20628
|
for (const k of KNOWN_CONFIGS) {
|
|
19486
20629
|
if (k.rulesDir) {
|
|
19487
20630
|
const absDir = expandPath2(k.rulesDir);
|
|
19488
|
-
if (!
|
|
20631
|
+
if (!existsSync17(absDir))
|
|
19489
20632
|
continue;
|
|
19490
20633
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
19491
|
-
const abs =
|
|
20634
|
+
const abs = join19(absDir, f);
|
|
19492
20635
|
if (!mtimes.has(abs)) {
|
|
19493
20636
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19494
20637
|
changed++;
|
|
@@ -19496,7 +20639,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19496
20639
|
}
|
|
19497
20640
|
} else {
|
|
19498
20641
|
const abs = expandPath2(k.path);
|
|
19499
|
-
if (
|
|
20642
|
+
if (existsSync17(abs) && !mtimes.has(abs)) {
|
|
19500
20643
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19501
20644
|
changed++;
|
|
19502
20645
|
}
|
|
@@ -19524,11 +20667,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
19524
20667
|
if (!c.target_path)
|
|
19525
20668
|
continue;
|
|
19526
20669
|
const abs = expandPath(c.target_path);
|
|
19527
|
-
if (!
|
|
20670
|
+
if (!existsSync17(abs)) {
|
|
19528
20671
|
missing++;
|
|
19529
20672
|
continue;
|
|
19530
20673
|
}
|
|
19531
|
-
const disk =
|
|
20674
|
+
const disk = readFileSync15(abs, "utf-8");
|
|
19532
20675
|
const { content: redactedDisk } = redactContent(disk, c.format);
|
|
19533
20676
|
if (redactedDisk !== c.content)
|
|
19534
20677
|
drifted++;
|
|
@@ -19595,7 +20738,7 @@ program.command("clean").description("Remove configs from DB whose target files
|
|
|
19595
20738
|
if (!c.target_path)
|
|
19596
20739
|
continue;
|
|
19597
20740
|
const abs = expandPath(c.target_path);
|
|
19598
|
-
if (!
|
|
20741
|
+
if (!existsSync17(abs)) {
|
|
19599
20742
|
if (printed < maxPrinted) {
|
|
19600
20743
|
if (opts.dryRun) {
|
|
19601
20744
|
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
|
|
@@ -19619,7 +20762,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
|
|
|
19619
20762
|
console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
|
|
19620
20763
|
}
|
|
19621
20764
|
});
|
|
19622
|
-
program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
|
|
20765
|
+
program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
|
|
19623
20766
|
const store = resolveConfigStore();
|
|
19624
20767
|
const packages = [
|
|
19625
20768
|
{ name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
|
|
@@ -19682,8 +20825,22 @@ Initializing configs:`));
|
|
|
19682
20825
|
} else {
|
|
19683
20826
|
console.log(chalk.dim(" would run: configs init"));
|
|
19684
20827
|
}
|
|
20828
|
+
console.log(chalk.cyan(`
|
|
20829
|
+
Reconciling managed skill runtimes:`));
|
|
20830
|
+
const runtimeReport = await reconcileManagedSkillRuntimes({
|
|
20831
|
+
dryRun: opts.dryRun,
|
|
20832
|
+
agent: opts.from,
|
|
20833
|
+
deliveryVerified: opts.deliveryVerified
|
|
20834
|
+
});
|
|
20835
|
+
printManagedSkillRuntimeReport(runtimeReport);
|
|
20836
|
+
if (runtimeReport.failed > 0) {
|
|
20837
|
+
console.error(chalk.red(`
|
|
20838
|
+
Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
|
|
20839
|
+
process.exitCode = 1;
|
|
20840
|
+
return;
|
|
20841
|
+
}
|
|
19685
20842
|
console.log(chalk.bold(`
|
|
19686
|
-
\u2713 Bootstrap complete.`) + chalk.dim(" Restart
|
|
20843
|
+
\u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
|
|
19687
20844
|
});
|
|
19688
20845
|
program.command("pull").description("Alias for sync (read from disk into DB)").option("-a, --agent <agent>", "only sync this agent").option("--dry-run", "preview without writing").action(async (opts) => {
|
|
19689
20846
|
const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent, store: resolveConfigStore() });
|