@hasna/instructions 0.4.36 → 0.4.39
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 +1839 -721
- 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 +1237 -704
- 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 +5 -3
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/mcp/index.js +303 -246
- package/dist/server/cloud.d.ts +2 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +875 -496
- 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
|
@@ -20,12 +20,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
20
20
|
}
|
|
21
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
24
|
+
for (let key of __getOwnPropNames(mod))
|
|
25
|
+
if (!__hasOwnProp.call(to, key))
|
|
26
|
+
__defProp(to, key, {
|
|
27
|
+
get: __accessProp.bind(mod, key),
|
|
28
|
+
enumerable: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
29
31
|
if (canCache)
|
|
30
32
|
cache.set(mod, to);
|
|
31
33
|
return to;
|
|
@@ -47,8 +49,8 @@ var __export = (target, all) => {
|
|
|
47
49
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
50
|
var __require = import.meta.require;
|
|
49
51
|
|
|
50
|
-
// node_modules/commander/lib/error.js
|
|
51
|
-
var require_error = __commonJS((exports)
|
|
52
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
|
|
53
|
+
var require_error = __commonJS(function(exports) {
|
|
52
54
|
class CommanderError extends Error {
|
|
53
55
|
constructor(exitCode, code, message) {
|
|
54
56
|
super(message);
|
|
@@ -71,8 +73,8 @@ var require_error = __commonJS((exports) => {
|
|
|
71
73
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
72
74
|
});
|
|
73
75
|
|
|
74
|
-
// node_modules/commander/lib/argument.js
|
|
75
|
-
var require_argument = __commonJS((exports)
|
|
76
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
|
|
77
|
+
var require_argument = __commonJS(function(exports) {
|
|
76
78
|
var { InvalidArgumentError } = require_error();
|
|
77
79
|
|
|
78
80
|
class Argument {
|
|
@@ -150,8 +152,8 @@ var require_argument = __commonJS((exports) => {
|
|
|
150
152
|
exports.humanReadableArgName = humanReadableArgName;
|
|
151
153
|
});
|
|
152
154
|
|
|
153
|
-
// node_modules/commander/lib/help.js
|
|
154
|
-
var require_help = __commonJS((exports)
|
|
155
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
|
|
156
|
+
var require_help = __commonJS(function(exports) {
|
|
155
157
|
var { humanReadableArgName } = require_argument();
|
|
156
158
|
|
|
157
159
|
class Help {
|
|
@@ -500,8 +502,8 @@ ${itemIndentStr}`);
|
|
|
500
502
|
exports.stripColor = stripColor;
|
|
501
503
|
});
|
|
502
504
|
|
|
503
|
-
// node_modules/commander/lib/option.js
|
|
504
|
-
var require_option = __commonJS((exports)
|
|
505
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
|
|
506
|
+
var require_option = __commonJS(function(exports) {
|
|
505
507
|
var { InvalidArgumentError } = require_error();
|
|
506
508
|
|
|
507
509
|
class Option {
|
|
@@ -678,8 +680,8 @@ var require_option = __commonJS((exports) => {
|
|
|
678
680
|
exports.DualOptions = DualOptions;
|
|
679
681
|
});
|
|
680
682
|
|
|
681
|
-
// node_modules/commander/lib/suggestSimilar.js
|
|
682
|
-
var require_suggestSimilar = __commonJS((exports)
|
|
683
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
684
|
+
var require_suggestSimilar = __commonJS(function(exports) {
|
|
683
685
|
var maxDistance = 3;
|
|
684
686
|
function editDistance(a, b) {
|
|
685
687
|
if (Math.abs(a.length - b.length) > maxDistance)
|
|
@@ -751,8 +753,8 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
751
753
|
exports.suggestSimilar = suggestSimilar;
|
|
752
754
|
});
|
|
753
755
|
|
|
754
|
-
// node_modules/commander/lib/command.js
|
|
755
|
-
var require_command = __commonJS((exports)
|
|
756
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
|
|
757
|
+
var require_command = __commonJS(function(exports) {
|
|
756
758
|
var EventEmitter = __require("events").EventEmitter;
|
|
757
759
|
var childProcess = __require("child_process");
|
|
758
760
|
var path = __require("path");
|
|
@@ -2061,8 +2063,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2061
2063
|
exports.useColor = useColor;
|
|
2062
2064
|
});
|
|
2063
2065
|
|
|
2064
|
-
// node_modules/commander/index.js
|
|
2065
|
-
var require_commander = __commonJS((exports)
|
|
2066
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
|
|
2067
|
+
var require_commander = __commonJS(function(exports) {
|
|
2066
2068
|
var { Argument } = require_argument();
|
|
2067
2069
|
var { Command } = require_command();
|
|
2068
2070
|
var { CommanderError, InvalidArgumentError } = require_error();
|
|
@@ -2136,19 +2138,51 @@ var init_types = __esm(() => {
|
|
|
2136
2138
|
};
|
|
2137
2139
|
});
|
|
2138
2140
|
|
|
2141
|
+
// src/lib/retired-storage-mode.ts
|
|
2142
|
+
function firstDefinedEnvKey(env, keys) {
|
|
2143
|
+
for (const key of keys) {
|
|
2144
|
+
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
2145
|
+
return key;
|
|
2146
|
+
}
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
function assertNoLegacyStorageMode(env = process.env) {
|
|
2150
|
+
const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
|
|
2151
|
+
if (!legacyKey)
|
|
2152
|
+
return;
|
|
2153
|
+
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.`);
|
|
2154
|
+
}
|
|
2155
|
+
var LEGACY_STORAGE_MODE_KEYS;
|
|
2156
|
+
var init_retired_storage_mode = __esm(() => {
|
|
2157
|
+
LEGACY_STORAGE_MODE_KEYS = [
|
|
2158
|
+
"HASNA_INSTRUCTIONS_STORAGE_MODE",
|
|
2159
|
+
"HASNA_INSTRUCTIONS_MODE",
|
|
2160
|
+
"INSTRUCTIONS_STORAGE_MODE",
|
|
2161
|
+
"INSTRUCTIONS_MODE"
|
|
2162
|
+
];
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
// src/lib/raw-store-root.ts
|
|
2166
|
+
import { homedir as homedir2 } from "os";
|
|
2167
|
+
import { join as join2, resolve } from "path";
|
|
2168
|
+
function getRawStoreRoot() {
|
|
2169
|
+
return resolve(process.env[RAW_STORE_ROOT_ENV] || join2(process.env["HOME"] || homedir2(), ".hasna", "instructions"));
|
|
2170
|
+
}
|
|
2171
|
+
var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME";
|
|
2172
|
+
var init_raw_store_root = () => {};
|
|
2173
|
+
|
|
2139
2174
|
// src/db/database.ts
|
|
2140
2175
|
import { Database } from "bun:sqlite";
|
|
2141
2176
|
import { existsSync as existsSync2, mkdirSync, rmSync } from "fs";
|
|
2142
|
-
import { join as
|
|
2177
|
+
import { join as join3 } from "path";
|
|
2143
2178
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2144
2179
|
function getDbPath() {
|
|
2145
2180
|
if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
|
|
2146
2181
|
return process.env["HASNA_INSTRUCTIONS_DB_PATH"];
|
|
2147
2182
|
}
|
|
2148
|
-
const
|
|
2149
|
-
const dir = join2(home, ".hasna", "instructions");
|
|
2183
|
+
const dir = getRawStoreRoot();
|
|
2150
2184
|
mkdirSync(dir, { recursive: true });
|
|
2151
|
-
return
|
|
2185
|
+
return join3(dir, "instructions.db");
|
|
2152
2186
|
}
|
|
2153
2187
|
function uuid() {
|
|
2154
2188
|
return randomUUID3();
|
|
@@ -2162,8 +2196,9 @@ function slugify(name) {
|
|
|
2162
2196
|
function getDatabase(path) {
|
|
2163
2197
|
if (_db)
|
|
2164
2198
|
return _db;
|
|
2199
|
+
assertNoLegacyStorageMode();
|
|
2165
2200
|
if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
|
|
2166
|
-
throw new Error("instructions is
|
|
2201
|
+
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
2202
|
}
|
|
2168
2203
|
const dbPath = path || getDbPath();
|
|
2169
2204
|
const db = new Database(dbPath);
|
|
@@ -2244,6 +2279,8 @@ function insertFeedback(input, db) {
|
|
|
2244
2279
|
}
|
|
2245
2280
|
var MIGRATIONS, _db = null;
|
|
2246
2281
|
var init_database = __esm(() => {
|
|
2282
|
+
init_retired_storage_mode();
|
|
2283
|
+
init_raw_store_root();
|
|
2247
2284
|
MIGRATIONS = [
|
|
2248
2285
|
`
|
|
2249
2286
|
CREATE TABLE IF NOT EXISTS configs (
|
|
@@ -2328,6 +2365,37 @@ var init_database = __esm(() => {
|
|
|
2328
2365
|
];
|
|
2329
2366
|
});
|
|
2330
2367
|
|
|
2368
|
+
// src/db/snapshots.ts
|
|
2369
|
+
function createSnapshot(configId, content, version, db) {
|
|
2370
|
+
const d = db || getDatabase();
|
|
2371
|
+
const id = uuid();
|
|
2372
|
+
const ts = now2();
|
|
2373
|
+
d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
|
|
2374
|
+
return { id, config_id: configId, content, version, created_at: ts };
|
|
2375
|
+
}
|
|
2376
|
+
function listSnapshots(configId, db) {
|
|
2377
|
+
const d = db || getDatabase();
|
|
2378
|
+
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
|
|
2379
|
+
}
|
|
2380
|
+
function getSnapshot(id, db) {
|
|
2381
|
+
const d = db || getDatabase();
|
|
2382
|
+
return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
|
|
2383
|
+
}
|
|
2384
|
+
function getSnapshotByVersion(configId, version, db) {
|
|
2385
|
+
const d = db || getDatabase();
|
|
2386
|
+
return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
|
|
2387
|
+
}
|
|
2388
|
+
function pruneSnapshots(configId, keep = 10, db) {
|
|
2389
|
+
const d = db || getDatabase();
|
|
2390
|
+
const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
|
|
2391
|
+
SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
|
|
2392
|
+
)`, [configId, configId, keep]);
|
|
2393
|
+
return result.changes;
|
|
2394
|
+
}
|
|
2395
|
+
var init_snapshots = __esm(() => {
|
|
2396
|
+
init_database();
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2331
2399
|
// src/db/configs.ts
|
|
2332
2400
|
function rowToConfig(row) {
|
|
2333
2401
|
let outputs = [];
|
|
@@ -2366,25 +2434,28 @@ function createConfig(input, db) {
|
|
|
2366
2434
|
const slug = uniqueSlug(input.name, d);
|
|
2367
2435
|
const tags = JSON.stringify(input.tags || []);
|
|
2368
2436
|
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
|
-
|
|
2437
|
+
return d.transaction(() => {
|
|
2438
|
+
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)
|
|
2439
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
|
|
2440
|
+
id,
|
|
2441
|
+
input.name,
|
|
2442
|
+
slug,
|
|
2443
|
+
input.kind ?? "file",
|
|
2444
|
+
input.category,
|
|
2445
|
+
input.agent ?? "global",
|
|
2446
|
+
input.target_path ?? null,
|
|
2447
|
+
outputs,
|
|
2448
|
+
input.format ?? "text",
|
|
2449
|
+
input.content,
|
|
2450
|
+
input.description ?? null,
|
|
2451
|
+
tags,
|
|
2452
|
+
input.is_template ? 1 : 0,
|
|
2453
|
+
ts,
|
|
2454
|
+
ts
|
|
2455
|
+
]);
|
|
2456
|
+
createSnapshot(id, input.content, 1, d);
|
|
2457
|
+
return getConfig(id, d);
|
|
2458
|
+
})();
|
|
2388
2459
|
}
|
|
2389
2460
|
function getConfig(idOrSlug, db) {
|
|
2390
2461
|
const d = db || getDatabase();
|
|
@@ -2489,9 +2560,13 @@ function updateConfig(idOrSlug, input, db) {
|
|
|
2489
2560
|
updates.push("synced_at = ?");
|
|
2490
2561
|
params.push(input.synced_at);
|
|
2491
2562
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2563
|
+
return d.transaction(() => {
|
|
2564
|
+
params.push(existing.id);
|
|
2565
|
+
d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
2566
|
+
const updated = getConfigById(existing.id, d);
|
|
2567
|
+
createSnapshot(updated.id, updated.content, updated.version, d);
|
|
2568
|
+
return updated;
|
|
2569
|
+
})();
|
|
2495
2570
|
}
|
|
2496
2571
|
function deleteConfig(idOrSlug, db) {
|
|
2497
2572
|
const d = db || getDatabase();
|
|
@@ -2511,16 +2586,17 @@ function getConfigStats(db) {
|
|
|
2511
2586
|
var init_configs = __esm(() => {
|
|
2512
2587
|
init_types();
|
|
2513
2588
|
init_database();
|
|
2589
|
+
init_snapshots();
|
|
2514
2590
|
});
|
|
2515
2591
|
|
|
2516
2592
|
// src/lib/template.ts
|
|
2517
2593
|
var exports_template = {};
|
|
2518
2594
|
__export(exports_template, {
|
|
2519
|
-
|
|
2520
|
-
renderTemplate: () => renderTemplate,
|
|
2521
|
-
parseTemplateVars: () => parseTemplateVars,
|
|
2595
|
+
extractTemplateVars: () => extractTemplateVars,
|
|
2522
2596
|
isTemplate: () => isTemplate,
|
|
2523
|
-
|
|
2597
|
+
parseTemplateVars: () => parseTemplateVars,
|
|
2598
|
+
renderTemplate: () => renderTemplate,
|
|
2599
|
+
renderTemplatePreview: () => renderTemplatePreview
|
|
2524
2600
|
});
|
|
2525
2601
|
function parseTemplateVars(content) {
|
|
2526
2602
|
const names = new Set;
|
|
@@ -2584,9 +2660,9 @@ var init_template = __esm(() => {
|
|
|
2584
2660
|
});
|
|
2585
2661
|
|
|
2586
2662
|
// src/lib/machine.ts
|
|
2587
|
-
import { arch as currentArch, homedir as
|
|
2663
|
+
import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
|
|
2588
2664
|
import { existsSync as existsSync3 } from "fs";
|
|
2589
|
-
import { join as
|
|
2665
|
+
import { join as join4 } from "path";
|
|
2590
2666
|
function normalizeOsFamily(os) {
|
|
2591
2667
|
const value = (os ?? "").trim().toLowerCase();
|
|
2592
2668
|
if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
|
|
@@ -2598,11 +2674,11 @@ function normalizeOsFamily(os) {
|
|
|
2598
2674
|
return value || "unknown";
|
|
2599
2675
|
}
|
|
2600
2676
|
function detectMachineContext(overrides = {}) {
|
|
2601
|
-
const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ??
|
|
2677
|
+
const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
|
|
2602
2678
|
const os = overrides.os ?? currentOsType();
|
|
2603
2679
|
const osFamily = normalizeOsFamily(os);
|
|
2604
|
-
const bunBinDir = overrides.bun_bin_dir ??
|
|
2605
|
-
const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH :
|
|
2680
|
+
const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir, ".bun", "bin");
|
|
2681
|
+
const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join4(bunBinDir, "bun");
|
|
2606
2682
|
return {
|
|
2607
2683
|
id: "current-machine",
|
|
2608
2684
|
hostname: overrides.hostname ?? currentHostname(),
|
|
@@ -2612,10 +2688,10 @@ function detectMachineContext(overrides = {}) {
|
|
|
2612
2688
|
created_at: "",
|
|
2613
2689
|
os_family: osFamily,
|
|
2614
2690
|
home_dir: homeDir,
|
|
2615
|
-
workspace_root: overrides.workspace_root ??
|
|
2691
|
+
workspace_root: overrides.workspace_root ?? join4(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
|
|
2616
2692
|
bun_bin_dir: bunBinDir,
|
|
2617
2693
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
2618
|
-
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${
|
|
2694
|
+
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join4("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
2619
2695
|
};
|
|
2620
2696
|
}
|
|
2621
2697
|
function machineContextToVariables(machine) {
|
|
@@ -3037,7 +3113,7 @@ Policy reference: \`${CODEWITH_SHARED_TODOS_STORAGE_POLICY_REFERENCE}\`
|
|
|
3037
3113
|
`;
|
|
3038
3114
|
});
|
|
3039
3115
|
|
|
3040
|
-
// node_modules/zod/v3/helpers/util.js
|
|
3116
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
3041
3117
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
3042
3118
|
const t = typeof data;
|
|
3043
3119
|
switch (t) {
|
|
@@ -3168,7 +3244,7 @@ var init_util = __esm(() => {
|
|
|
3168
3244
|
]);
|
|
3169
3245
|
});
|
|
3170
3246
|
|
|
3171
|
-
// node_modules/zod/v3/ZodError.js
|
|
3247
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
3172
3248
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
3173
3249
|
const json = JSON.stringify(obj, null, 2);
|
|
3174
3250
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -3289,7 +3365,7 @@ var init_ZodError = __esm(() => {
|
|
|
3289
3365
|
};
|
|
3290
3366
|
});
|
|
3291
3367
|
|
|
3292
|
-
// node_modules/zod/v3/locales/en.js
|
|
3368
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
3293
3369
|
var errorMap = (issue, _ctx) => {
|
|
3294
3370
|
let message;
|
|
3295
3371
|
switch (issue.code) {
|
|
@@ -3396,7 +3472,7 @@ var init_en = __esm(() => {
|
|
|
3396
3472
|
en_default = errorMap;
|
|
3397
3473
|
});
|
|
3398
3474
|
|
|
3399
|
-
// node_modules/zod/v3/errors.js
|
|
3475
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
3400
3476
|
function setErrorMap(map) {
|
|
3401
3477
|
overrideErrorMap = map;
|
|
3402
3478
|
}
|
|
@@ -3409,7 +3485,7 @@ var init_errors = __esm(() => {
|
|
|
3409
3485
|
overrideErrorMap = en_default;
|
|
3410
3486
|
});
|
|
3411
3487
|
|
|
3412
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
3488
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
3413
3489
|
function addIssueToContext(ctx, issueData) {
|
|
3414
3490
|
const overrideMap = getErrorMap();
|
|
3415
3491
|
const issue = makeIssue({
|
|
@@ -3514,10 +3590,10 @@ var init_parseUtil = __esm(() => {
|
|
|
3514
3590
|
});
|
|
3515
3591
|
});
|
|
3516
3592
|
|
|
3517
|
-
// node_modules/zod/v3/helpers/typeAliases.js
|
|
3593
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
3518
3594
|
var init_typeAliases = () => {};
|
|
3519
3595
|
|
|
3520
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
3596
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
3521
3597
|
var errorUtil;
|
|
3522
3598
|
var init_errorUtil = __esm(() => {
|
|
3523
3599
|
(function(errorUtil2) {
|
|
@@ -3526,7 +3602,7 @@ var init_errorUtil = __esm(() => {
|
|
|
3526
3602
|
})(errorUtil || (errorUtil = {}));
|
|
3527
3603
|
});
|
|
3528
3604
|
|
|
3529
|
-
// node_modules/zod/v3/types.js
|
|
3605
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
3530
3606
|
class ParseInputLazyPath {
|
|
3531
3607
|
constructor(parent, value, path, key) {
|
|
3532
3608
|
this._cachedPath = [];
|
|
@@ -6877,208 +6953,208 @@ var init_types2 = __esm(() => {
|
|
|
6877
6953
|
NEVER = INVALID;
|
|
6878
6954
|
});
|
|
6879
6955
|
|
|
6880
|
-
// node_modules/zod/v3/external.js
|
|
6956
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
6881
6957
|
var exports_external = {};
|
|
6882
6958
|
__export(exports_external, {
|
|
6883
|
-
|
|
6884
|
-
util: () => util,
|
|
6885
|
-
unknown: () => unknownType,
|
|
6886
|
-
union: () => unionType,
|
|
6887
|
-
undefined: () => undefinedType,
|
|
6888
|
-
tuple: () => tupleType,
|
|
6889
|
-
transformer: () => effectsType,
|
|
6890
|
-
symbol: () => symbolType,
|
|
6891
|
-
string: () => stringType,
|
|
6892
|
-
strictObject: () => strictObjectType,
|
|
6893
|
-
setErrorMap: () => setErrorMap,
|
|
6894
|
-
set: () => setType,
|
|
6895
|
-
record: () => recordType,
|
|
6896
|
-
quotelessJson: () => quotelessJson,
|
|
6897
|
-
promise: () => promiseType,
|
|
6898
|
-
preprocess: () => preprocessType,
|
|
6899
|
-
pipeline: () => pipelineType,
|
|
6900
|
-
ostring: () => ostring,
|
|
6901
|
-
optional: () => optionalType,
|
|
6902
|
-
onumber: () => onumber,
|
|
6903
|
-
oboolean: () => oboolean,
|
|
6904
|
-
objectUtil: () => objectUtil,
|
|
6905
|
-
object: () => objectType,
|
|
6906
|
-
number: () => numberType,
|
|
6907
|
-
nullable: () => nullableType,
|
|
6908
|
-
null: () => nullType,
|
|
6909
|
-
never: () => neverType,
|
|
6910
|
-
nativeEnum: () => nativeEnumType,
|
|
6911
|
-
nan: () => nanType,
|
|
6912
|
-
map: () => mapType,
|
|
6913
|
-
makeIssue: () => makeIssue,
|
|
6914
|
-
literal: () => literalType,
|
|
6915
|
-
lazy: () => lazyType,
|
|
6916
|
-
late: () => late,
|
|
6917
|
-
isValid: () => isValid,
|
|
6918
|
-
isDirty: () => isDirty,
|
|
6919
|
-
isAsync: () => isAsync,
|
|
6920
|
-
isAborted: () => isAborted,
|
|
6921
|
-
intersection: () => intersectionType,
|
|
6922
|
-
instanceof: () => instanceOfType,
|
|
6923
|
-
getParsedType: () => getParsedType,
|
|
6924
|
-
getErrorMap: () => getErrorMap,
|
|
6925
|
-
function: () => functionType,
|
|
6926
|
-
enum: () => enumType,
|
|
6927
|
-
effect: () => effectsType,
|
|
6928
|
-
discriminatedUnion: () => discriminatedUnionType,
|
|
6929
|
-
defaultErrorMap: () => en_default,
|
|
6930
|
-
datetimeRegex: () => datetimeRegex,
|
|
6931
|
-
date: () => dateType,
|
|
6932
|
-
custom: () => custom,
|
|
6933
|
-
coerce: () => coerce,
|
|
6934
|
-
boolean: () => booleanType,
|
|
6935
|
-
bigint: () => bigIntType,
|
|
6936
|
-
array: () => arrayType,
|
|
6937
|
-
any: () => anyType,
|
|
6938
|
-
addIssueToContext: () => addIssueToContext,
|
|
6939
|
-
ZodVoid: () => ZodVoid,
|
|
6940
|
-
ZodUnknown: () => ZodUnknown,
|
|
6941
|
-
ZodUnion: () => ZodUnion,
|
|
6942
|
-
ZodUndefined: () => ZodUndefined,
|
|
6943
|
-
ZodType: () => ZodType,
|
|
6944
|
-
ZodTuple: () => ZodTuple,
|
|
6945
|
-
ZodTransformer: () => ZodEffects,
|
|
6946
|
-
ZodSymbol: () => ZodSymbol,
|
|
6947
|
-
ZodString: () => ZodString,
|
|
6948
|
-
ZodSet: () => ZodSet,
|
|
6949
|
-
ZodSchema: () => ZodType,
|
|
6950
|
-
ZodRecord: () => ZodRecord,
|
|
6951
|
-
ZodReadonly: () => ZodReadonly,
|
|
6952
|
-
ZodPromise: () => ZodPromise,
|
|
6953
|
-
ZodPipeline: () => ZodPipeline,
|
|
6954
|
-
ZodParsedType: () => ZodParsedType,
|
|
6955
|
-
ZodOptional: () => ZodOptional,
|
|
6956
|
-
ZodObject: () => ZodObject,
|
|
6957
|
-
ZodNumber: () => ZodNumber,
|
|
6958
|
-
ZodNullable: () => ZodNullable,
|
|
6959
|
-
ZodNull: () => ZodNull,
|
|
6960
|
-
ZodNever: () => ZodNever,
|
|
6961
|
-
ZodNativeEnum: () => ZodNativeEnum,
|
|
6962
|
-
ZodNaN: () => ZodNaN,
|
|
6963
|
-
ZodMap: () => ZodMap,
|
|
6964
|
-
ZodLiteral: () => ZodLiteral,
|
|
6965
|
-
ZodLazy: () => ZodLazy,
|
|
6966
|
-
ZodIssueCode: () => ZodIssueCode,
|
|
6967
|
-
ZodIntersection: () => ZodIntersection,
|
|
6968
|
-
ZodFunction: () => ZodFunction,
|
|
6969
|
-
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
6970
|
-
ZodError: () => ZodError,
|
|
6971
|
-
ZodEnum: () => ZodEnum,
|
|
6972
|
-
ZodEffects: () => ZodEffects,
|
|
6973
|
-
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
6974
|
-
ZodDefault: () => ZodDefault,
|
|
6975
|
-
ZodDate: () => ZodDate,
|
|
6976
|
-
ZodCatch: () => ZodCatch,
|
|
6977
|
-
ZodBranded: () => ZodBranded,
|
|
6978
|
-
ZodBoolean: () => ZodBoolean,
|
|
6979
|
-
ZodBigInt: () => ZodBigInt,
|
|
6980
|
-
ZodArray: () => ZodArray,
|
|
6981
|
-
ZodAny: () => ZodAny,
|
|
6982
|
-
Schema: () => ZodType,
|
|
6983
|
-
ParseStatus: () => ParseStatus,
|
|
6984
|
-
OK: () => OK,
|
|
6985
|
-
NEVER: () => NEVER,
|
|
6986
|
-
INVALID: () => INVALID,
|
|
6987
|
-
EMPTY_PATH: () => EMPTY_PATH,
|
|
6959
|
+
BRAND: () => BRAND,
|
|
6988
6960
|
DIRTY: () => DIRTY,
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
6961
|
+
EMPTY_PATH: () => EMPTY_PATH,
|
|
6962
|
+
INVALID: () => INVALID,
|
|
6963
|
+
NEVER: () => NEVER,
|
|
6964
|
+
OK: () => OK,
|
|
6965
|
+
ParseStatus: () => ParseStatus,
|
|
6966
|
+
Schema: () => ZodType,
|
|
6967
|
+
ZodAny: () => ZodAny,
|
|
6968
|
+
ZodArray: () => ZodArray,
|
|
6969
|
+
ZodBigInt: () => ZodBigInt,
|
|
6970
|
+
ZodBoolean: () => ZodBoolean,
|
|
6971
|
+
ZodBranded: () => ZodBranded,
|
|
6972
|
+
ZodCatch: () => ZodCatch,
|
|
6973
|
+
ZodDate: () => ZodDate,
|
|
6974
|
+
ZodDefault: () => ZodDefault,
|
|
6975
|
+
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
6976
|
+
ZodEffects: () => ZodEffects,
|
|
6977
|
+
ZodEnum: () => ZodEnum,
|
|
6978
|
+
ZodError: () => ZodError,
|
|
6979
|
+
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
6980
|
+
ZodFunction: () => ZodFunction,
|
|
6981
|
+
ZodIntersection: () => ZodIntersection,
|
|
6982
|
+
ZodIssueCode: () => ZodIssueCode,
|
|
6983
|
+
ZodLazy: () => ZodLazy,
|
|
6984
|
+
ZodLiteral: () => ZodLiteral,
|
|
6985
|
+
ZodMap: () => ZodMap,
|
|
6986
|
+
ZodNaN: () => ZodNaN,
|
|
6987
|
+
ZodNativeEnum: () => ZodNativeEnum,
|
|
6988
|
+
ZodNever: () => ZodNever,
|
|
6989
|
+
ZodNull: () => ZodNull,
|
|
6990
|
+
ZodNullable: () => ZodNullable,
|
|
6991
|
+
ZodNumber: () => ZodNumber,
|
|
6992
|
+
ZodObject: () => ZodObject,
|
|
6993
|
+
ZodOptional: () => ZodOptional,
|
|
6994
|
+
ZodParsedType: () => ZodParsedType,
|
|
6995
|
+
ZodPipeline: () => ZodPipeline,
|
|
6996
|
+
ZodPromise: () => ZodPromise,
|
|
6997
|
+
ZodReadonly: () => ZodReadonly,
|
|
6998
|
+
ZodRecord: () => ZodRecord,
|
|
6999
|
+
ZodSchema: () => ZodType,
|
|
7000
|
+
ZodSet: () => ZodSet,
|
|
7001
|
+
ZodString: () => ZodString,
|
|
7002
|
+
ZodSymbol: () => ZodSymbol,
|
|
7003
|
+
ZodTransformer: () => ZodEffects,
|
|
7004
|
+
ZodTuple: () => ZodTuple,
|
|
7005
|
+
ZodType: () => ZodType,
|
|
7006
|
+
ZodUndefined: () => ZodUndefined,
|
|
7007
|
+
ZodUnion: () => ZodUnion,
|
|
7008
|
+
ZodUnknown: () => ZodUnknown,
|
|
7009
|
+
ZodVoid: () => ZodVoid,
|
|
7010
|
+
addIssueToContext: () => addIssueToContext,
|
|
7011
|
+
any: () => anyType,
|
|
7012
|
+
array: () => arrayType,
|
|
7013
|
+
bigint: () => bigIntType,
|
|
7014
|
+
boolean: () => booleanType,
|
|
7015
|
+
coerce: () => coerce,
|
|
7016
|
+
custom: () => custom,
|
|
7017
|
+
date: () => dateType,
|
|
7018
|
+
datetimeRegex: () => datetimeRegex,
|
|
7019
|
+
defaultErrorMap: () => en_default,
|
|
7020
|
+
discriminatedUnion: () => discriminatedUnionType,
|
|
7021
|
+
effect: () => effectsType,
|
|
7022
|
+
enum: () => enumType,
|
|
7023
|
+
function: () => functionType,
|
|
7024
|
+
getErrorMap: () => getErrorMap,
|
|
7025
|
+
getParsedType: () => getParsedType,
|
|
7026
|
+
instanceof: () => instanceOfType,
|
|
7027
|
+
intersection: () => intersectionType,
|
|
7028
|
+
isAborted: () => isAborted,
|
|
7029
|
+
isAsync: () => isAsync,
|
|
7030
|
+
isDirty: () => isDirty,
|
|
7031
|
+
isValid: () => isValid,
|
|
7032
|
+
late: () => late,
|
|
7033
|
+
lazy: () => lazyType,
|
|
7034
|
+
literal: () => literalType,
|
|
7035
|
+
makeIssue: () => makeIssue,
|
|
7036
|
+
map: () => mapType,
|
|
7037
|
+
nan: () => nanType,
|
|
7038
|
+
nativeEnum: () => nativeEnumType,
|
|
7039
|
+
never: () => neverType,
|
|
7040
|
+
null: () => nullType,
|
|
7041
|
+
nullable: () => nullableType,
|
|
7042
|
+
number: () => numberType,
|
|
7043
|
+
object: () => objectType,
|
|
7044
|
+
objectUtil: () => objectUtil,
|
|
7045
|
+
oboolean: () => oboolean,
|
|
7046
|
+
onumber: () => onumber,
|
|
7047
|
+
optional: () => optionalType,
|
|
7048
|
+
ostring: () => ostring,
|
|
7049
|
+
pipeline: () => pipelineType,
|
|
7050
|
+
preprocess: () => preprocessType,
|
|
7051
|
+
promise: () => promiseType,
|
|
7052
|
+
quotelessJson: () => quotelessJson,
|
|
7053
|
+
record: () => recordType,
|
|
7054
|
+
set: () => setType,
|
|
7055
|
+
setErrorMap: () => setErrorMap,
|
|
7056
|
+
strictObject: () => strictObjectType,
|
|
7057
|
+
string: () => stringType,
|
|
7058
|
+
symbol: () => symbolType,
|
|
7059
|
+
transformer: () => effectsType,
|
|
7060
|
+
tuple: () => tupleType,
|
|
7061
|
+
undefined: () => undefinedType,
|
|
7062
|
+
union: () => unionType,
|
|
7063
|
+
unknown: () => unknownType,
|
|
7064
|
+
util: () => util,
|
|
7065
|
+
void: () => voidType
|
|
7066
|
+
});
|
|
7067
|
+
var init_external = __esm(() => {
|
|
7068
|
+
init_errors();
|
|
7069
|
+
init_parseUtil();
|
|
7070
|
+
init_typeAliases();
|
|
7071
|
+
init_util();
|
|
7072
|
+
init_types2();
|
|
7073
|
+
init_ZodError();
|
|
7074
|
+
});
|
|
7075
|
+
|
|
7076
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
7077
|
+
var init_zod = __esm(() => {
|
|
7078
|
+
init_external();
|
|
7079
|
+
init_external();
|
|
7080
|
+
});
|
|
7081
|
+
|
|
7082
|
+
// src/lib/redact.ts
|
|
7083
|
+
function redactShell(content) {
|
|
7084
|
+
const redacted = [];
|
|
7085
|
+
const lines = content.split(`
|
|
7086
|
+
`);
|
|
7087
|
+
const out = [];
|
|
7088
|
+
for (let i = 0;i < lines.length; i++) {
|
|
7089
|
+
const line = lines[i];
|
|
7090
|
+
const m = line.match(/^(\s*(?:export\s+)?)([A-Z][A-Z0-9_]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
|
|
7091
|
+
if (m) {
|
|
7092
|
+
const [, prefix, key, eq, quote, value] = m;
|
|
7093
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
7094
|
+
const reason = reasonFor(key, value);
|
|
7095
|
+
redacted.push({ varName: key, line: i + 1, reason });
|
|
7096
|
+
out.push(`${prefix}${key}${eq}${quote}{{${key}}}${quote}`);
|
|
7097
|
+
continue;
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
out.push(line);
|
|
7101
|
+
}
|
|
7102
|
+
return { content: out.join(`
|
|
7103
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
7104
|
+
}
|
|
7105
|
+
function redactJson(content) {
|
|
7106
|
+
const redacted = [];
|
|
7107
|
+
const lines = content.split(`
|
|
7108
|
+
`);
|
|
7109
|
+
const out = [];
|
|
7110
|
+
for (let i = 0;i < lines.length; i++) {
|
|
7111
|
+
const line = lines[i];
|
|
7112
|
+
const m = line.match(/^(\s*"([^"]+)"\s*:\s*)"([^"]+)"(,?)(\s*)$/);
|
|
7113
|
+
if (m) {
|
|
7114
|
+
const [, prefix, key, value, comma, trail] = m;
|
|
7115
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
7116
|
+
const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
7117
|
+
redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
|
|
7118
|
+
out.push(`${prefix}"{{${varName}}}"${comma}${trail}`);
|
|
7119
|
+
continue;
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
let newLine = line;
|
|
7123
|
+
for (const { re, reason } of VALUE_PATTERNS) {
|
|
7124
|
+
newLine = newLine.replace(re, (match) => {
|
|
7125
|
+
const varName = `REDACTED_${reason.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
7126
|
+
redacted.push({ varName, line: i + 1, reason });
|
|
7127
|
+
return `{{${varName}}}`;
|
|
7128
|
+
});
|
|
7129
|
+
}
|
|
7130
|
+
out.push(newLine);
|
|
7131
|
+
}
|
|
7132
|
+
return { content: out.join(`
|
|
7133
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
7134
|
+
}
|
|
7135
|
+
function redactToml(content) {
|
|
7136
|
+
const redacted = [];
|
|
7137
|
+
const lines = content.split(`
|
|
7138
|
+
`);
|
|
7139
|
+
const out = [];
|
|
7140
|
+
for (let i = 0;i < lines.length; i++) {
|
|
7141
|
+
const line = lines[i];
|
|
7142
|
+
const m = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9_\-]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
|
|
7143
|
+
if (m) {
|
|
7144
|
+
const [, indent, key, eq, quote, value] = m;
|
|
7145
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
7146
|
+
const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
7147
|
+
redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
|
|
7148
|
+
out.push(`${indent}${key}${eq}${quote}{{${varName}}}${quote}`);
|
|
7149
|
+
continue;
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
out.push(line);
|
|
7153
|
+
}
|
|
7154
|
+
return { content: out.join(`
|
|
7155
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
7156
|
+
}
|
|
7157
|
+
function redactIni(content) {
|
|
7082
7158
|
const redacted = [];
|
|
7083
7159
|
const lines = content.split(`
|
|
7084
7160
|
`);
|
|
@@ -7189,7 +7265,7 @@ var init_redact = __esm(() => {
|
|
|
7189
7265
|
VALUE_PATTERNS = [
|
|
7190
7266
|
{ re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
|
|
7191
7267
|
{ re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
|
|
7192
|
-
{ re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
|
|
7268
|
+
{ re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
|
|
7193
7269
|
{ re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
|
|
7194
7270
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
|
|
7195
7271
|
{ re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
|
|
@@ -7239,7 +7315,7 @@ import {
|
|
|
7239
7315
|
statSync,
|
|
7240
7316
|
writeFileSync
|
|
7241
7317
|
} from "fs";
|
|
7242
|
-
import { basename, dirname, isAbsolute, join as
|
|
7318
|
+
import { basename, dirname, isAbsolute, join as join5, parse, relative, resolve as resolve2 } from "path";
|
|
7243
7319
|
function managedObservationMaxBytes(relativePath) {
|
|
7244
7320
|
return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
|
|
7245
7321
|
}
|
|
@@ -7410,7 +7486,7 @@ function composeProjectContextSessionRender(input) {
|
|
|
7410
7486
|
if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
|
|
7411
7487
|
throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
|
|
7412
7488
|
}
|
|
7413
|
-
const plannedIndexes = input.files.filter((file) => file.role === "index" &&
|
|
7489
|
+
const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve2(file.path) === paths.target);
|
|
7414
7490
|
if (plannedIndexes.length !== 1) {
|
|
7415
7491
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
|
|
7416
7492
|
}
|
|
@@ -7468,7 +7544,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
|
|
|
7468
7544
|
verify();
|
|
7469
7545
|
return action(null);
|
|
7470
7546
|
}
|
|
7471
|
-
const lockPath =
|
|
7547
|
+
const lockPath = resolve2(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
7472
7548
|
const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
|
|
7473
7549
|
try {
|
|
7474
7550
|
verify();
|
|
@@ -7494,7 +7570,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
7494
7570
|
if (!isRecord(observed) || typeof observed.path !== "string") {
|
|
7495
7571
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
|
|
7496
7572
|
}
|
|
7497
|
-
const path =
|
|
7573
|
+
const path = resolve2(observed.path);
|
|
7498
7574
|
if (!allowedPaths.has(path) || observedPaths.has(path)) {
|
|
7499
7575
|
throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
|
|
7500
7576
|
}
|
|
@@ -7516,7 +7592,7 @@ function validateProjectContextSessionGuard(guard) {
|
|
|
7516
7592
|
function applyProjectContext(options) {
|
|
7517
7593
|
const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
|
|
7518
7594
|
const now3 = options.now ?? new Date;
|
|
7519
|
-
const lockPath =
|
|
7595
|
+
const lockPath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
|
|
7520
7596
|
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
7597
|
try {
|
|
7522
7598
|
const resolved = resolveBundleForApply(options, workspaceRoot, now3);
|
|
@@ -7663,7 +7739,7 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
|
|
|
7663
7739
|
if (!options.expected_project_id) {
|
|
7664
7740
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
|
|
7665
7741
|
}
|
|
7666
|
-
const cachePath =
|
|
7742
|
+
const cachePath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
7667
7743
|
const cache = readProjectContextCache(cachePath, workspaceRoot);
|
|
7668
7744
|
if (!cache)
|
|
7669
7745
|
throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
|
|
@@ -8019,7 +8095,7 @@ function buildManifest(plan, now3) {
|
|
|
8019
8095
|
function buildSessionCompatibilityManifest(plan, now3) {
|
|
8020
8096
|
const paths = runtimePaths(plan.workspace_root, plan.runtime);
|
|
8021
8097
|
const tool = manifestTool(plan.runtime);
|
|
8022
|
-
const targetHome = plan.runtime === "codewith" ?
|
|
8098
|
+
const targetHome = plan.runtime === "codewith" ? resolve2(plan.workspace_root, ".codewith") : plan.workspace_root;
|
|
8023
8099
|
const targetRelativePath = sessionTargetRelativePath(plan.runtime);
|
|
8024
8100
|
const existing = existsSync4(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
|
|
8025
8101
|
schema: SESSION_RENDER_SCHEMA,
|
|
@@ -8041,7 +8117,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
|
|
|
8041
8117
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
|
|
8042
8118
|
}
|
|
8043
8119
|
const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
|
|
8044
|
-
if (existingTargetHome !== null &&
|
|
8120
|
+
if (existingTargetHome !== null && resolve2(existingTargetHome) !== targetHome) {
|
|
8045
8121
|
throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
|
|
8046
8122
|
}
|
|
8047
8123
|
const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
|
|
@@ -8327,9 +8403,9 @@ function writeMetadataSnapshot(plan, now3) {
|
|
|
8327
8403
|
const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
|
|
8328
8404
|
if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
|
|
8329
8405
|
return null;
|
|
8330
|
-
const snapshotDir =
|
|
8406
|
+
const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
8331
8407
|
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
8332
|
-
const snapshotPath =
|
|
8408
|
+
const snapshotPath = resolve2(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
|
|
8333
8409
|
const snapshot = {
|
|
8334
8410
|
schema: "hasna.configs.session-render-snapshot/v1",
|
|
8335
8411
|
kind: "project-context-metadata",
|
|
@@ -8345,8 +8421,8 @@ function writeMetadataSnapshot(plan, now3) {
|
|
|
8345
8421
|
return snapshotPath;
|
|
8346
8422
|
}
|
|
8347
8423
|
function metadataSnapshotMatchesManifest(plan, manifest) {
|
|
8348
|
-
const snapshotDir =
|
|
8349
|
-
const snapshotPath =
|
|
8424
|
+
const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
|
|
8425
|
+
const snapshotPath = resolve2(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
|
|
8350
8426
|
if (!existsSync4(snapshotPath))
|
|
8351
8427
|
return false;
|
|
8352
8428
|
const record = readJsonRecord(snapshotPath, plan.workspace_root);
|
|
@@ -8395,10 +8471,10 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
|
|
|
8395
8471
|
sha256: nextHash
|
|
8396
8472
|
};
|
|
8397
8473
|
});
|
|
8398
|
-
const snapshotDir =
|
|
8474
|
+
const snapshotDir = resolve2(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
|
|
8399
8475
|
ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
|
|
8400
8476
|
const timestamp = now3.toISOString().replace(/[:.]/g, "-");
|
|
8401
|
-
const snapshotPath =
|
|
8477
|
+
const snapshotPath = resolve2(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
|
|
8402
8478
|
const snapshot = {
|
|
8403
8479
|
schema: "hasna.configs.session-render-snapshot/v2",
|
|
8404
8480
|
createdAt: now3.toISOString(),
|
|
@@ -8463,7 +8539,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
|
|
|
8463
8539
|
}
|
|
8464
8540
|
}
|
|
8465
8541
|
function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
|
|
8466
|
-
const dir =
|
|
8542
|
+
const dir = resolve2(path, "..");
|
|
8467
8543
|
ensureSafeDirectory(dir, workspaceRoot, 448);
|
|
8468
8544
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
8469
8545
|
const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
|
|
@@ -8480,7 +8556,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
8480
8556
|
const previous = anchoredFileObservation(directory, targetName);
|
|
8481
8557
|
const previousMode = previous?.mode ?? defaultMode;
|
|
8482
8558
|
const tempName = `.project-context-${randomUUID5()}.tmp`;
|
|
8483
|
-
const tempPath =
|
|
8559
|
+
const tempPath = join5(dir, tempName);
|
|
8484
8560
|
let fd = null;
|
|
8485
8561
|
let preserveTemp = false;
|
|
8486
8562
|
let directoryChanged = false;
|
|
@@ -8611,7 +8687,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
|
|
|
8611
8687
|
}
|
|
8612
8688
|
const dir = dirname(path);
|
|
8613
8689
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8614
|
-
const tempPath =
|
|
8690
|
+
const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8615
8691
|
let fd = null;
|
|
8616
8692
|
let tempIdentity = null;
|
|
8617
8693
|
try {
|
|
@@ -8668,7 +8744,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
8668
8744
|
}
|
|
8669
8745
|
const dir = dirname(path);
|
|
8670
8746
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8671
|
-
const tempPath =
|
|
8747
|
+
const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8672
8748
|
const desiredHash = sha2562(content);
|
|
8673
8749
|
let fd = null;
|
|
8674
8750
|
let tempIdentity = null;
|
|
@@ -8737,11 +8813,11 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
|
|
|
8737
8813
|
return createHash2("sha256").update(readFileSync(path)).digest("hex");
|
|
8738
8814
|
}
|
|
8739
8815
|
function writeProjectContextCoordinatedFile(input) {
|
|
8740
|
-
atomicWriteFile(
|
|
8816
|
+
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
8817
|
}
|
|
8742
8818
|
function removeProjectContextCoordinatedFile(input) {
|
|
8743
8819
|
const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
|
|
8744
|
-
const path =
|
|
8820
|
+
const path = resolve2(input.path);
|
|
8745
8821
|
assertNoSymlinkSegments(workspaceRoot, path);
|
|
8746
8822
|
const dir = dirname(path);
|
|
8747
8823
|
const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
|
|
@@ -8767,7 +8843,7 @@ function removeProjectContextCoordinatedFile(input) {
|
|
|
8767
8843
|
throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
|
|
8768
8844
|
}
|
|
8769
8845
|
displaced = true;
|
|
8770
|
-
input.test_hooks?.after_displace?.(
|
|
8846
|
+
input.test_hooks?.after_displace?.(join5(dir, displacedName));
|
|
8771
8847
|
const moved = anchoredFileObservation(directory, displacedName);
|
|
8772
8848
|
if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
|
|
8773
8849
|
throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
|
|
@@ -8813,7 +8889,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
|
|
|
8813
8889
|
}
|
|
8814
8890
|
const dir = dirname(path);
|
|
8815
8891
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8816
|
-
const displacedPath =
|
|
8892
|
+
const displacedPath = join5(dir, `.project-context-delete-${randomUUID5()}.tmp`);
|
|
8817
8893
|
let displaced = false;
|
|
8818
8894
|
try {
|
|
8819
8895
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
@@ -8884,7 +8960,7 @@ function anchoredOpenExclusive(directory, name, mode) {
|
|
|
8884
8960
|
const requestedMode = mode & 4095;
|
|
8885
8961
|
let fd;
|
|
8886
8962
|
try {
|
|
8887
|
-
fd = openSync(
|
|
8963
|
+
fd = openSync(join5(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
|
|
8888
8964
|
} catch {
|
|
8889
8965
|
throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
8890
8966
|
}
|
|
@@ -8927,7 +9003,7 @@ function anchoredFileObservation(directory, name) {
|
|
|
8927
9003
|
const stat = fstatSync(fd);
|
|
8928
9004
|
if (!stat.isFile())
|
|
8929
9005
|
throw new ProjectContextHashRace("managed output is not a regular file");
|
|
8930
|
-
const relativePath = relativePosix(directory.workspaceRoot,
|
|
9006
|
+
const relativePath = relativePosix(directory.workspaceRoot, join5(directory.path, name));
|
|
8931
9007
|
const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
|
|
8932
9008
|
if (maxBytes !== null && stat.size > maxBytes) {
|
|
8933
9009
|
throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
|
|
@@ -8953,7 +9029,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
|
|
|
8953
9029
|
return observed;
|
|
8954
9030
|
}
|
|
8955
9031
|
function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
8956
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9032
|
+
assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
|
|
8957
9033
|
let stat;
|
|
8958
9034
|
try {
|
|
8959
9035
|
stat = lstatSync(path);
|
|
@@ -8966,7 +9042,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
|
8966
9042
|
return { dev: stat.dev, ino: stat.ino };
|
|
8967
9043
|
}
|
|
8968
9044
|
function assertManagedDirectoryStable(path, workspaceRoot, expected) {
|
|
8969
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9045
|
+
assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
|
|
8970
9046
|
let current;
|
|
8971
9047
|
try {
|
|
8972
9048
|
current = lstatSync(path);
|
|
@@ -9099,10 +9175,10 @@ function resolveAnchoredFsOps() {
|
|
|
9099
9175
|
return null;
|
|
9100
9176
|
}
|
|
9101
9177
|
function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
|
|
9102
|
-
const lockDirectory =
|
|
9178
|
+
const lockDirectory = resolve2(lockPath, "..");
|
|
9103
9179
|
ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
|
|
9104
9180
|
assertNoSymlinkSegments(workspaceRoot, lockPath);
|
|
9105
|
-
const tempPath =
|
|
9181
|
+
const tempPath = join5(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
|
|
9106
9182
|
let fd = null;
|
|
9107
9183
|
let openedIdentity = null;
|
|
9108
9184
|
let openedContentHash = null;
|
|
@@ -9174,7 +9250,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
|
|
|
9174
9250
|
if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
|
|
9175
9251
|
return;
|
|
9176
9252
|
rmSync2(lockPath);
|
|
9177
|
-
fsyncDirectory(
|
|
9253
|
+
fsyncDirectory(resolve2(lockPath, ".."));
|
|
9178
9254
|
} catch {}
|
|
9179
9255
|
}
|
|
9180
9256
|
function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
|
|
@@ -9253,7 +9329,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
|
|
|
9253
9329
|
throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
|
|
9254
9330
|
}
|
|
9255
9331
|
rmSync2(candidatePath);
|
|
9256
|
-
fsyncDirectory(
|
|
9332
|
+
fsyncDirectory(resolve2(lockPath, ".."));
|
|
9257
9333
|
exchanged = false;
|
|
9258
9334
|
return true;
|
|
9259
9335
|
} catch (error) {
|
|
@@ -9332,8 +9408,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
9332
9408
|
}
|
|
9333
9409
|
return;
|
|
9334
9410
|
}
|
|
9335
|
-
const lockDirectory =
|
|
9336
|
-
const releasePath =
|
|
9411
|
+
const lockDirectory = resolve2(lockPath, "..");
|
|
9412
|
+
const releasePath = join5(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
|
|
9337
9413
|
let releaseFd = null;
|
|
9338
9414
|
let releaseIdentity = null;
|
|
9339
9415
|
let releaseHash = null;
|
|
@@ -9413,7 +9489,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
9413
9489
|
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
9414
9490
|
let current = workspaceRoot;
|
|
9415
9491
|
for (const segment of segments) {
|
|
9416
|
-
current =
|
|
9492
|
+
current = join5(current, segment);
|
|
9417
9493
|
if (existsSync4(current)) {
|
|
9418
9494
|
if (lstatSync(current).isSymbolicLink())
|
|
9419
9495
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
@@ -9421,7 +9497,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
9421
9497
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
|
|
9422
9498
|
} else {
|
|
9423
9499
|
mkdirSync2(current, { mode });
|
|
9424
|
-
fsyncDirectory(
|
|
9500
|
+
fsyncDirectory(resolve2(current, ".."));
|
|
9425
9501
|
}
|
|
9426
9502
|
}
|
|
9427
9503
|
}
|
|
@@ -9477,11 +9553,11 @@ function scanGeneratedContent(content) {
|
|
|
9477
9553
|
function runtimePaths(workspaceRoot, runtime) {
|
|
9478
9554
|
const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
|
|
9479
9555
|
return {
|
|
9480
|
-
target:
|
|
9481
|
-
fragment:
|
|
9482
|
-
manifest:
|
|
9483
|
-
cache:
|
|
9484
|
-
sessionManifest: runtime === "codewith" ?
|
|
9556
|
+
target: resolve2(workspaceRoot, ...relativeTarget.split("/")),
|
|
9557
|
+
fragment: resolve2(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
|
|
9558
|
+
manifest: resolve2(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
|
|
9559
|
+
cache: resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
|
|
9560
|
+
sessionManifest: runtime === "codewith" ? resolve2(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve2(workspaceRoot, ".hasna", "session-render-manifest.json")
|
|
9485
9561
|
};
|
|
9486
9562
|
}
|
|
9487
9563
|
function projectContextSessionGuardPaths(paths, runtime) {
|
|
@@ -9491,7 +9567,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
|
|
|
9491
9567
|
paths.fragment,
|
|
9492
9568
|
paths.target,
|
|
9493
9569
|
paths.sessionManifest,
|
|
9494
|
-
...runtime === "codewith" ? [
|
|
9570
|
+
...runtime === "codewith" ? [resolve2(paths.target, "..", "CODEWITH.override.md")] : []
|
|
9495
9571
|
];
|
|
9496
9572
|
}
|
|
9497
9573
|
function sessionTargetRelativePath(runtime) {
|
|
@@ -9511,12 +9587,12 @@ function projectContextRuntimeForSessionTool(tool) {
|
|
|
9511
9587
|
return null;
|
|
9512
9588
|
}
|
|
9513
9589
|
function projectContextWorkspaceForSession(input, runtime) {
|
|
9514
|
-
const targetHome =
|
|
9590
|
+
const targetHome = resolve2(input.target_home);
|
|
9515
9591
|
if (runtime === "codewith") {
|
|
9516
9592
|
const workspaceRoot = basename(targetHome) === ".codewith" ? dirname(targetHome) : null;
|
|
9517
9593
|
if (!workspaceRoot)
|
|
9518
9594
|
return null;
|
|
9519
|
-
if (input.project_root &&
|
|
9595
|
+
if (input.project_root && resolve2(input.project_root) !== workspaceRoot) {
|
|
9520
9596
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
|
|
9521
9597
|
}
|
|
9522
9598
|
if (!existsSync4(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
|
|
@@ -9530,7 +9606,7 @@ function projectContextWorkspaceForSession(input, runtime) {
|
|
|
9530
9606
|
function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
9531
9607
|
if (runtime !== "codewith")
|
|
9532
9608
|
return;
|
|
9533
|
-
const override =
|
|
9609
|
+
const override = resolve2(workspaceRoot, ".codewith", "CODEWITH.override.md");
|
|
9534
9610
|
if (!existsSync4(override))
|
|
9535
9611
|
return;
|
|
9536
9612
|
assertNoSymlinkSegments(workspaceRoot, override);
|
|
@@ -9541,7 +9617,7 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
|
|
|
9541
9617
|
function assertSafeWorkspaceRoot(path) {
|
|
9542
9618
|
if (!isAbsolute(path))
|
|
9543
9619
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
|
|
9544
|
-
const normalized =
|
|
9620
|
+
const normalized = resolve2(path);
|
|
9545
9621
|
if (normalized === parse(normalized).root)
|
|
9546
9622
|
throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
|
|
9547
9623
|
if (!existsSync4(normalized) || !lstatSync(normalized).isDirectory())
|
|
@@ -9558,17 +9634,17 @@ function assertNoSymlinkSegments(root, target) {
|
|
|
9558
9634
|
}
|
|
9559
9635
|
let current = root;
|
|
9560
9636
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
9561
|
-
current =
|
|
9637
|
+
current = join5(current, segment);
|
|
9562
9638
|
if (existsSync4(current) && lstatSync(current).isSymbolicLink()) {
|
|
9563
9639
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
9564
9640
|
}
|
|
9565
9641
|
}
|
|
9566
9642
|
}
|
|
9567
9643
|
function assertNoSymlinkAncestors(path) {
|
|
9568
|
-
const normalized =
|
|
9644
|
+
const normalized = resolve2(path);
|
|
9569
9645
|
let current = parse(normalized).root;
|
|
9570
9646
|
for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
|
|
9571
|
-
current =
|
|
9647
|
+
current = join5(current, segment);
|
|
9572
9648
|
if (!existsSync4(current))
|
|
9573
9649
|
return;
|
|
9574
9650
|
if (lstatSync(current).isSymbolicLink())
|
|
@@ -9604,10 +9680,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
|
|
|
9604
9680
|
}
|
|
9605
9681
|
function durableSourcePath(path, workspaceRoot) {
|
|
9606
9682
|
if (!path || path.startsWith("/dev/fd/"))
|
|
9607
|
-
return
|
|
9608
|
-
const normalized = isAbsolute(path) ?
|
|
9683
|
+
return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
9684
|
+
const normalized = isAbsolute(path) ? resolve2(path) : resolve2(workspaceRoot, path);
|
|
9609
9685
|
if (normalized.startsWith("/dev/fd/"))
|
|
9610
|
-
return
|
|
9686
|
+
return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
|
|
9611
9687
|
return normalized;
|
|
9612
9688
|
}
|
|
9613
9689
|
function compareRevisions(incoming, previous) {
|
|
@@ -10167,7 +10243,7 @@ function compareProviderVersions(left, right) {
|
|
|
10167
10243
|
|
|
10168
10244
|
// src/lib/asset-plan.ts
|
|
10169
10245
|
import { createHash as createHash3 } from "crypto";
|
|
10170
|
-
import { isAbsolute as isAbsolute2, posix, resolve as
|
|
10246
|
+
import { isAbsolute as isAbsolute2, posix, resolve as resolve3 } from "path";
|
|
10171
10247
|
function assetCapability(provider, surface, kind, support, strategies, note, providerVersionRange = "*") {
|
|
10172
10248
|
return Object.freeze({
|
|
10173
10249
|
schema: ASSET_CAPABILITY_SCHEMA,
|
|
@@ -10374,8 +10450,8 @@ function resolveAssetDestination(item, roots) {
|
|
|
10374
10450
|
if (!isAbsolute2(root))
|
|
10375
10451
|
throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
|
|
10376
10452
|
const relativePath = safeRelativePath(item.destination.relativePath);
|
|
10377
|
-
const target =
|
|
10378
|
-
const normalizedRoot =
|
|
10453
|
+
const target = resolve3(root, ...relativePath.split("/"));
|
|
10454
|
+
const normalizedRoot = resolve3(root);
|
|
10379
10455
|
if (target === normalizedRoot)
|
|
10380
10456
|
throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
|
|
10381
10457
|
if (!target.startsWith(`${normalizedRoot}/`))
|
|
@@ -10555,13 +10631,13 @@ var init_asset_plan = __esm(() => {
|
|
|
10555
10631
|
// src/lib/cursor-authority.ts
|
|
10556
10632
|
import { createHash as createHash4 } from "crypto";
|
|
10557
10633
|
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
10558
|
-
import { homedir as
|
|
10559
|
-
import { join as
|
|
10634
|
+
import { homedir as homedir4 } from "os";
|
|
10635
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
10560
10636
|
function sha2564(content) {
|
|
10561
10637
|
return createHash4("sha256").update(content).digest("hex");
|
|
10562
10638
|
}
|
|
10563
10639
|
function homeDir() {
|
|
10564
|
-
return process.env["HOME"] ||
|
|
10640
|
+
return process.env["HOME"] || homedir4();
|
|
10565
10641
|
}
|
|
10566
10642
|
function markerPayload(content, markerLine, markerIndex) {
|
|
10567
10643
|
const index = markerIndex ?? content.indexOf(markerLine);
|
|
@@ -10577,12 +10653,12 @@ function baseObservation(path) {
|
|
|
10577
10653
|
};
|
|
10578
10654
|
}
|
|
10579
10655
|
function observeCursorGlobalAuthority(options = {}) {
|
|
10580
|
-
const authorityPath =
|
|
10656
|
+
const authorityPath = resolve4(join6(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10581
10657
|
const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
|
|
10582
10658
|
return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
|
|
10583
10659
|
}
|
|
10584
10660
|
function observeCursorGlobalAuthorityAtPath(authorityPath) {
|
|
10585
|
-
return observeCursorGlobalAuthorityPath(
|
|
10661
|
+
return observeCursorGlobalAuthorityPath(resolve4(authorityPath), (path) => readFileSync2(path, "utf8"));
|
|
10586
10662
|
}
|
|
10587
10663
|
function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
|
|
10588
10664
|
const base = baseObservation(authorityPath);
|
|
@@ -10727,7 +10803,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
|
|
|
10727
10803
|
};
|
|
10728
10804
|
}
|
|
10729
10805
|
function isCursorGlobalAuthorityPath(path) {
|
|
10730
|
-
return
|
|
10806
|
+
return resolve4(path) === resolve4(join6(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10731
10807
|
}
|
|
10732
10808
|
function stampCursorGlobalAuthorityMarker(content) {
|
|
10733
10809
|
if (CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN.test(content))
|
|
@@ -10770,11 +10846,88 @@ var init_cursor_authority = __esm(() => {
|
|
|
10770
10846
|
CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
|
|
10771
10847
|
});
|
|
10772
10848
|
|
|
10773
|
-
// src/lib/session-
|
|
10849
|
+
// src/lib/session-authority.ts
|
|
10774
10850
|
import { createHash as createHash5 } from "crypto";
|
|
10775
|
-
import {
|
|
10776
|
-
import {
|
|
10777
|
-
|
|
10851
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
10852
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
10853
|
+
function sha2565(content) {
|
|
10854
|
+
return createHash5("sha256").update(content).digest("hex");
|
|
10855
|
+
}
|
|
10856
|
+
function detectClaudeAuthorityConflicts(targetHome) {
|
|
10857
|
+
const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
10858
|
+
let stat;
|
|
10859
|
+
try {
|
|
10860
|
+
stat = lstatSync3(authorityPath);
|
|
10861
|
+
} catch {
|
|
10862
|
+
return [];
|
|
10863
|
+
}
|
|
10864
|
+
const provenanceBase = {
|
|
10865
|
+
tool: "claude",
|
|
10866
|
+
relativePath: CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH,
|
|
10867
|
+
path: authorityPath
|
|
10868
|
+
};
|
|
10869
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
10870
|
+
return [{
|
|
10871
|
+
...provenanceBase,
|
|
10872
|
+
kind: "invalid-unmanaged-authority",
|
|
10873
|
+
sha256: null,
|
|
10874
|
+
markers: [],
|
|
10875
|
+
provenance: {
|
|
10876
|
+
source: "filesystem",
|
|
10877
|
+
authority: "unmanaged",
|
|
10878
|
+
observedPath: authorityPath,
|
|
10879
|
+
detection: "non-regular-file"
|
|
10880
|
+
},
|
|
10881
|
+
reason: "Claude target contains unmanaged AGENTS.md that is not a regular file; authority cannot be verified safely."
|
|
10882
|
+
}];
|
|
10883
|
+
}
|
|
10884
|
+
if (statSync2(authorityPath).size > CLAUDE_LEGACY_AUTHORITY_MAX_BYTES) {
|
|
10885
|
+
return [{
|
|
10886
|
+
...provenanceBase,
|
|
10887
|
+
kind: "invalid-unmanaged-authority",
|
|
10888
|
+
sha256: null,
|
|
10889
|
+
markers: [],
|
|
10890
|
+
provenance: {
|
|
10891
|
+
source: "filesystem",
|
|
10892
|
+
authority: "unmanaged",
|
|
10893
|
+
observedPath: authorityPath,
|
|
10894
|
+
detection: "oversized-file"
|
|
10895
|
+
},
|
|
10896
|
+
reason: `Claude target contains unmanaged AGENTS.md larger than ${CLAUDE_LEGACY_AUTHORITY_MAX_BYTES} bytes; authority cannot be classified safely.`
|
|
10897
|
+
}];
|
|
10898
|
+
}
|
|
10899
|
+
const content = readFileSync3(authorityPath, "utf8");
|
|
10900
|
+
const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
|
|
10901
|
+
const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
|
|
10902
|
+
return [{
|
|
10903
|
+
...provenanceBase,
|
|
10904
|
+
kind: knownLegacy ? "known-legacy-no-worktree" : "unknown-unmanaged-authority",
|
|
10905
|
+
sha256: sha2565(content),
|
|
10906
|
+
markers,
|
|
10907
|
+
provenance: {
|
|
10908
|
+
source: "filesystem",
|
|
10909
|
+
authority: "unmanaged",
|
|
10910
|
+
observedPath: authorityPath,
|
|
10911
|
+
detection: knownLegacy ? "known-legacy-markers" : "unknown-content"
|
|
10912
|
+
},
|
|
10913
|
+
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."
|
|
10914
|
+
}];
|
|
10915
|
+
}
|
|
10916
|
+
var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md", CLAUDE_LEGACY_AUTHORITY_MAX_BYTES, CLAUDE_LEGACY_MARKERS;
|
|
10917
|
+
var init_session_authority = __esm(() => {
|
|
10918
|
+
CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
|
|
10919
|
+
CLAUDE_LEGACY_MARKERS = [
|
|
10920
|
+
{ id: "claude-agent-rules-heading", pattern: /^# Agent Rules \(Claude\)/m },
|
|
10921
|
+
{ id: "no-worktrees-heading", pattern: /^## No Worktrees/m },
|
|
10922
|
+
{ id: "no-worktrees-directive", pattern: /\bNever use git worktrees\b/m }
|
|
10923
|
+
];
|
|
10924
|
+
});
|
|
10925
|
+
|
|
10926
|
+
// src/lib/session-render.ts
|
|
10927
|
+
import { createHash as createHash6 } from "crypto";
|
|
10928
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "fs";
|
|
10929
|
+
import { homedir as homedir5 } from "os";
|
|
10930
|
+
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
10931
|
function normalizeSessionInstructionLayer(value) {
|
|
10779
10932
|
if (value === "provider")
|
|
10780
10933
|
return "tool";
|
|
@@ -10791,11 +10944,11 @@ function ensureTrailingNewline3(content) {
|
|
|
10791
10944
|
`) ? content : `${content}
|
|
10792
10945
|
`;
|
|
10793
10946
|
}
|
|
10794
|
-
function
|
|
10795
|
-
return
|
|
10947
|
+
function sha2566(content) {
|
|
10948
|
+
return createHash6("sha256").update(content).digest("hex");
|
|
10796
10949
|
}
|
|
10797
10950
|
function fingerprint(value) {
|
|
10798
|
-
return
|
|
10951
|
+
return sha2566(JSON.stringify(value));
|
|
10799
10952
|
}
|
|
10800
10953
|
function canonicalFingerprintValue(value) {
|
|
10801
10954
|
if (Array.isArray(value))
|
|
@@ -10813,7 +10966,7 @@ function ruleAttestation(rule) {
|
|
|
10813
10966
|
};
|
|
10814
10967
|
const applied = metadata["payloadFloorApplied"];
|
|
10815
10968
|
return {
|
|
10816
|
-
contentSha256:
|
|
10969
|
+
contentSha256: sha2566(rule.content ?? ""),
|
|
10817
10970
|
payloadFloorApplied: typeof applied === "boolean" ? applied : null,
|
|
10818
10971
|
flooredFromRulesVersion: read("flooredFromRulesVersion"),
|
|
10819
10972
|
flooredFromPayloadSha256: read("flooredFromPayloadSha256"),
|
|
@@ -10859,16 +11012,14 @@ function slug(value) {
|
|
|
10859
11012
|
function yamlQuote2(value) {
|
|
10860
11013
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
10861
11014
|
}
|
|
10862
|
-
function getRawStoreRoot() {
|
|
10863
|
-
return resolve4(process.env[RAW_STORE_ROOT_ENV] || join6(process.env["HOME"] || homedir4(), ".hasna", "configs"));
|
|
10864
|
-
}
|
|
10865
11015
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
10866
|
-
|
|
11016
|
+
const home = process.env["HOME"] || homedir5();
|
|
11017
|
+
return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
10867
11018
|
}
|
|
10868
11019
|
function joinTarget(targetHome, relativePath) {
|
|
10869
11020
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
10870
11021
|
const safeRelativePath2 = assertSafeRelativePath(relativePath);
|
|
10871
|
-
return
|
|
11022
|
+
return join8(safeTargetHome, ...safeRelativePath2.split("/"));
|
|
10872
11023
|
}
|
|
10873
11024
|
function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
10874
11025
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
@@ -10879,7 +11030,7 @@ function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
|
10879
11030
|
relativePath: safeRelativePath2,
|
|
10880
11031
|
role,
|
|
10881
11032
|
content: normalizedContent,
|
|
10882
|
-
sha256:
|
|
11033
|
+
sha256: sha2566(normalizedContent),
|
|
10883
11034
|
sourceIds
|
|
10884
11035
|
};
|
|
10885
11036
|
}
|
|
@@ -10913,7 +11064,7 @@ function applyAgentOperatingRulesFloor(source, content) {
|
|
|
10913
11064
|
const floored = {
|
|
10914
11065
|
payloadFloorApplied: true,
|
|
10915
11066
|
flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
|
|
10916
|
-
flooredFromPayloadSha256:
|
|
11067
|
+
flooredFromPayloadSha256: sha2566(content)
|
|
10917
11068
|
};
|
|
10918
11069
|
return {
|
|
10919
11070
|
content: payload.content,
|
|
@@ -10935,7 +11086,7 @@ function applyAgentOperatingRulesFloorToRule(source, rule, content) {
|
|
|
10935
11086
|
const floored = {
|
|
10936
11087
|
payloadFloorApplied: true,
|
|
10937
11088
|
flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
|
|
10938
|
-
flooredFromPayloadSha256:
|
|
11089
|
+
flooredFromPayloadSha256: sha2566(content)
|
|
10939
11090
|
};
|
|
10940
11091
|
return {
|
|
10941
11092
|
content: payload.content,
|
|
@@ -10954,7 +11105,7 @@ function skippedSource(source, reason) {
|
|
|
10954
11105
|
order: source.resolvedOrder,
|
|
10955
11106
|
path: source.path ?? null,
|
|
10956
11107
|
hash: source.hash ?? null,
|
|
10957
|
-
renderedPayloadSha256:
|
|
11108
|
+
renderedPayloadSha256: sha2566(source.content),
|
|
10958
11109
|
nonOverridable: source.nonOverridable === true,
|
|
10959
11110
|
provenance: source.provenance ?? null
|
|
10960
11111
|
}
|
|
@@ -10996,7 +11147,7 @@ function compareSessionInstructionSources(a, b) {
|
|
|
10996
11147
|
return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
|
|
10997
11148
|
}
|
|
10998
11149
|
function semanticPolicyIntegrity(body) {
|
|
10999
|
-
return
|
|
11150
|
+
return sha2566(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
|
|
11000
11151
|
}
|
|
11001
11152
|
function semanticPolicyDeclaration(source) {
|
|
11002
11153
|
const normalize = (value) => value.replace(/\r\n/g, `
|
|
@@ -11221,7 +11372,7 @@ function composeSources(sources, tool) {
|
|
|
11221
11372
|
targetSourceId: target.id,
|
|
11222
11373
|
targetNormalizedSourceId: target.normalizedId,
|
|
11223
11374
|
targetHash: target.hash ?? null,
|
|
11224
|
-
targetRenderedPayloadSha256:
|
|
11375
|
+
targetRenderedPayloadSha256: sha2566(target.content),
|
|
11225
11376
|
targetNonOverridable: protectedReplacement,
|
|
11226
11377
|
authority: protectedReplacement ? "canonical-identity-export/codewith-provider/v1" : "overridable-source/v1"
|
|
11227
11378
|
}
|
|
@@ -11403,7 +11554,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
11403
11554
|
...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
|
|
11404
11555
|
]);
|
|
11405
11556
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
11406
|
-
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(
|
|
11557
|
+
const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
11407
11558
|
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
11408
11559
|
const config = {
|
|
11409
11560
|
...selectedConfig,
|
|
@@ -11552,7 +11703,7 @@ function buildAssetFiles(input, targetHome, blocked) {
|
|
|
11552
11703
|
relativePath: assertSafeRelativePath(relativePath),
|
|
11553
11704
|
role: "asset",
|
|
11554
11705
|
content,
|
|
11555
|
-
sha256:
|
|
11706
|
+
sha256: sha2566(content),
|
|
11556
11707
|
sourceIds: [item.sourceConfigId, item.assetId]
|
|
11557
11708
|
};
|
|
11558
11709
|
});
|
|
@@ -11592,7 +11743,7 @@ function adapterFor(input) {
|
|
|
11592
11743
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
11593
11744
|
}
|
|
11594
11745
|
function getHomeDir() {
|
|
11595
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
11746
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
|
|
11596
11747
|
}
|
|
11597
11748
|
function cleanSessionPathInput(path) {
|
|
11598
11749
|
const trimmed = path.trim();
|
|
@@ -11607,16 +11758,16 @@ function resolveSessionPath(path) {
|
|
|
11607
11758
|
throw new Error("Session render path cannot be empty.");
|
|
11608
11759
|
const home = getHomeDir();
|
|
11609
11760
|
if (cleaned === "~")
|
|
11610
|
-
return
|
|
11761
|
+
return resolve6(home);
|
|
11611
11762
|
if (cleaned.startsWith("~/"))
|
|
11612
|
-
return
|
|
11763
|
+
return resolve6(home, cleaned.slice(2));
|
|
11613
11764
|
if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
|
|
11614
|
-
return
|
|
11765
|
+
return resolve6(home);
|
|
11615
11766
|
if (cleaned.startsWith("{{HOME}}/"))
|
|
11616
|
-
return
|
|
11767
|
+
return resolve6(home, cleaned.slice("{{HOME}}/".length));
|
|
11617
11768
|
if (cleaned.startsWith("${HOME}/"))
|
|
11618
|
-
return
|
|
11619
|
-
return
|
|
11769
|
+
return resolve6(home, cleaned.slice("${HOME}/".length));
|
|
11770
|
+
return resolve6(cleaned);
|
|
11620
11771
|
}
|
|
11621
11772
|
function assertSafeRelativePath(relativePath) {
|
|
11622
11773
|
if (!relativePath.trim())
|
|
@@ -11632,7 +11783,7 @@ function assertSafeRelativePath(relativePath) {
|
|
|
11632
11783
|
function assertSafeTargetRoot(targetHome) {
|
|
11633
11784
|
if (!isAbsolute3(targetHome))
|
|
11634
11785
|
throw new Error(`Session render target must be an absolute path: ${targetHome}`);
|
|
11635
|
-
const normalized =
|
|
11786
|
+
const normalized = resolve6(targetHome);
|
|
11636
11787
|
if (normalized === parse2(normalized).root) {
|
|
11637
11788
|
throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
|
|
11638
11789
|
}
|
|
@@ -11730,7 +11881,7 @@ function planSessionRender(input) {
|
|
|
11730
11881
|
blockers: targetBlockers
|
|
11731
11882
|
} = resolveRenderTarget(input);
|
|
11732
11883
|
const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
|
|
11733
|
-
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : [];
|
|
11884
|
+
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
|
|
11734
11885
|
const blockers = [
|
|
11735
11886
|
...targetBlockers,
|
|
11736
11887
|
...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
|
|
@@ -11828,7 +11979,7 @@ function planSessionRender(input) {
|
|
|
11828
11979
|
hash: rule.hash ?? null,
|
|
11829
11980
|
...ruleAttestation(rule)
|
|
11830
11981
|
})),
|
|
11831
|
-
renderedPayloadSha256:
|
|
11982
|
+
renderedPayloadSha256: sha2566(source.content),
|
|
11832
11983
|
provenance: source.provenance ?? null,
|
|
11833
11984
|
metadata: source.metadata ?? null
|
|
11834
11985
|
})),
|
|
@@ -11866,8 +12017,8 @@ function planSessionRender(input) {
|
|
|
11866
12017
|
...input.providerConfig ? {
|
|
11867
12018
|
providerConfig: {
|
|
11868
12019
|
sourceId: input.providerConfig.sourceId,
|
|
11869
|
-
selectedPayloadSha256:
|
|
11870
|
-
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ??
|
|
12020
|
+
selectedPayloadSha256: sha2566(input.providerConfig.content),
|
|
12021
|
+
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
|
|
11871
12022
|
selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
|
|
11872
12023
|
}
|
|
11873
12024
|
} : {},
|
|
@@ -12153,7 +12304,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
12153
12304
|
}
|
|
12154
12305
|
return;
|
|
12155
12306
|
}
|
|
12156
|
-
const stat =
|
|
12307
|
+
const stat = statSync3(resolvedPath);
|
|
12157
12308
|
if (!stat.isFile()) {
|
|
12158
12309
|
throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
|
|
12159
12310
|
}
|
|
@@ -12162,7 +12313,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
12162
12313
|
if (!pathIsInside(realPath, realBase)) {
|
|
12163
12314
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
|
|
12164
12315
|
}
|
|
12165
|
-
return
|
|
12316
|
+
return readFileSync4(realPath, "utf-8");
|
|
12166
12317
|
}
|
|
12167
12318
|
function resolveIdentitySourcePath(path, baseDir, sourceId) {
|
|
12168
12319
|
const cleaned = cleanSessionPathInput(path);
|
|
@@ -12170,8 +12321,8 @@ function resolveIdentitySourcePath(path, baseDir, sourceId) {
|
|
|
12170
12321
|
throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
|
|
12171
12322
|
if (cleaned.includes("\\"))
|
|
12172
12323
|
throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
|
|
12173
|
-
const resolvedPath = isAbsolute3(cleaned) ?
|
|
12174
|
-
if (!pathIsInside(resolvedPath,
|
|
12324
|
+
const resolvedPath = isAbsolute3(cleaned) ? resolve6(cleaned) : resolve6(baseDir, cleaned);
|
|
12325
|
+
if (!pathIsInside(resolvedPath, resolve6(baseDir))) {
|
|
12175
12326
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
|
|
12176
12327
|
}
|
|
12177
12328
|
return resolvedPath;
|
|
@@ -12259,7 +12410,7 @@ function asStringArray(value) {
|
|
|
12259
12410
|
return [];
|
|
12260
12411
|
return value.filter((item) => typeof item === "string");
|
|
12261
12412
|
}
|
|
12262
|
-
var
|
|
12413
|
+
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, CANONICAL_IDENTITY_EXPORT_PACKAGES;
|
|
12263
12414
|
var init_session_render = __esm(() => {
|
|
12264
12415
|
init_global_agent_rules_standard();
|
|
12265
12416
|
init_codewith_shared_todos_storage_standard();
|
|
@@ -12268,8 +12419,11 @@ var init_session_render = __esm(() => {
|
|
|
12268
12419
|
init_transforms();
|
|
12269
12420
|
init_asset_plan();
|
|
12270
12421
|
init_cursor_authority();
|
|
12422
|
+
init_session_authority();
|
|
12271
12423
|
init_session_render_contract();
|
|
12272
12424
|
init_session_render_contract();
|
|
12425
|
+
init_raw_store_root();
|
|
12426
|
+
init_raw_store_root();
|
|
12273
12427
|
SESSION_RENDER_TOOLS = [
|
|
12274
12428
|
"claude",
|
|
12275
12429
|
"codex",
|
|
@@ -12470,7 +12624,7 @@ var init_session_render = __esm(() => {
|
|
|
12470
12624
|
});
|
|
12471
12625
|
|
|
12472
12626
|
// src/lib/instruction-graph.ts
|
|
12473
|
-
import { createHash as
|
|
12627
|
+
import { createHash as createHash7 } from "crypto";
|
|
12474
12628
|
function capability(provider, providerVersionRange, selectedRepresentation, loadingPath, options = {}) {
|
|
12475
12629
|
return Object.freeze({
|
|
12476
12630
|
schema: PROVIDER_CAPABILITY_SCHEMA,
|
|
@@ -12717,7 +12871,7 @@ function compileInstructionGraph(input) {
|
|
|
12717
12871
|
effective_activation: effective.get(configId),
|
|
12718
12872
|
fallback: row.binding.fallback,
|
|
12719
12873
|
required: row.binding.required,
|
|
12720
|
-
content_sha256:
|
|
12874
|
+
content_sha256: sha2567(config.content),
|
|
12721
12875
|
dependencies: dependencies.get(configId) ?? []
|
|
12722
12876
|
};
|
|
12723
12877
|
});
|
|
@@ -12754,7 +12908,7 @@ function compileInstructionGraph(input) {
|
|
|
12754
12908
|
diagnostics
|
|
12755
12909
|
};
|
|
12756
12910
|
return {
|
|
12757
|
-
plan: deepFreeze2({ ...planWithoutHash, source_hash:
|
|
12911
|
+
plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2567(stableJson2(planWithoutHash)) }),
|
|
12758
12912
|
sources,
|
|
12759
12913
|
capability: capability2
|
|
12760
12914
|
};
|
|
@@ -12928,8 +13082,8 @@ function assertExactOnce(units, artifacts) {
|
|
|
12928
13082
|
function errorDiagnostic(configId, code, message) {
|
|
12929
13083
|
return { severity: "error", code, config_id: configId, message };
|
|
12930
13084
|
}
|
|
12931
|
-
function
|
|
12932
|
-
return
|
|
13085
|
+
function sha2567(value) {
|
|
13086
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
12933
13087
|
}
|
|
12934
13088
|
function stableJson2(value) {
|
|
12935
13089
|
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;
|
|
@@ -13228,37 +13382,6 @@ var init_profiles = __esm(() => {
|
|
|
13228
13382
|
init_asset_plan();
|
|
13229
13383
|
});
|
|
13230
13384
|
|
|
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
13385
|
// src/db/machines.ts
|
|
13263
13386
|
import { arch, hostname, type } from "os";
|
|
13264
13387
|
function currentHostname2() {
|
|
@@ -13350,16 +13473,17 @@ function formatCliError(err, env = process.env) {
|
|
|
13350
13473
|
return err instanceof Error ? err.message : String(err);
|
|
13351
13474
|
}
|
|
13352
13475
|
function resolveCloudConfig(env = process.env) {
|
|
13476
|
+
assertNoLegacyStorageMode(env);
|
|
13353
13477
|
const apiUrl = env[API_URL_ENV]?.trim();
|
|
13354
13478
|
const apiKey = env[API_KEY_ENV]?.trim();
|
|
13355
13479
|
if (!apiUrl && !apiKey)
|
|
13356
13480
|
return null;
|
|
13357
13481
|
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
|
|
13482
|
+
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
13483
|
}
|
|
13360
13484
|
return { apiUrl, apiKey };
|
|
13361
13485
|
}
|
|
13362
|
-
function
|
|
13486
|
+
function isApiTransport(env = process.env) {
|
|
13363
13487
|
return resolveCloudConfig(env) !== null;
|
|
13364
13488
|
}
|
|
13365
13489
|
|
|
@@ -13845,6 +13969,7 @@ var init_config_store = __esm(() => {
|
|
|
13845
13969
|
init_types();
|
|
13846
13970
|
init_bounded_read();
|
|
13847
13971
|
init_instruction_graph();
|
|
13972
|
+
init_retired_storage_mode();
|
|
13848
13973
|
CloudHttpError = class CloudHttpError extends Error {
|
|
13849
13974
|
status;
|
|
13850
13975
|
body;
|
|
@@ -13858,8 +13983,8 @@ var init_config_store = __esm(() => {
|
|
|
13858
13983
|
});
|
|
13859
13984
|
|
|
13860
13985
|
// src/lib/session-render-ownership.ts
|
|
13861
|
-
import { existsSync as existsSync6, readFileSync as
|
|
13862
|
-
import { dirname as dirname3, join as
|
|
13986
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
13987
|
+
import { dirname as dirname3, join as join9, parse as parse3, relative as relative3, sep } from "path";
|
|
13863
13988
|
function toSegments(absolutePath2) {
|
|
13864
13989
|
return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
13865
13990
|
}
|
|
@@ -13880,7 +14005,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
13880
14005
|
try {
|
|
13881
14006
|
if (!existsSync6(manifestPath))
|
|
13882
14007
|
return null;
|
|
13883
|
-
stats =
|
|
14008
|
+
stats = statSync4(manifestPath);
|
|
13884
14009
|
} catch {
|
|
13885
14010
|
return null;
|
|
13886
14011
|
}
|
|
@@ -13890,7 +14015,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
13890
14015
|
}
|
|
13891
14016
|
let manifest;
|
|
13892
14017
|
try {
|
|
13893
|
-
manifest = JSON.parse(
|
|
14018
|
+
manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
13894
14019
|
} catch {
|
|
13895
14020
|
return null;
|
|
13896
14021
|
}
|
|
@@ -13907,7 +14032,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
|
13907
14032
|
const root = parse3(absolutePath2).root;
|
|
13908
14033
|
let home = dirname3(absolutePath2);
|
|
13909
14034
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
13910
|
-
const manifestPath =
|
|
14035
|
+
const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
13911
14036
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
13912
14037
|
if (relativePaths) {
|
|
13913
14038
|
const claimed = relative3(home, absolutePath2).split(sep).join("/");
|
|
@@ -13934,25 +14059,25 @@ var init_session_render_ownership = __esm(() => {
|
|
|
13934
14059
|
// src/lib/apply.ts
|
|
13935
14060
|
var exports_apply = {};
|
|
13936
14061
|
__export(exports_apply, {
|
|
13937
|
-
|
|
13938
|
-
normalizeTargetPath: () => normalizeTargetPath,
|
|
13939
|
-
getConfigHome: () => getConfigHome,
|
|
13940
|
-
expandPath: () => expandPath,
|
|
13941
|
-
applyConfigsWithReport: () => applyConfigsWithReport,
|
|
14062
|
+
applyConfig: () => applyConfig,
|
|
13942
14063
|
applyConfigs: () => applyConfigs,
|
|
13943
|
-
|
|
14064
|
+
applyConfigsWithReport: () => applyConfigsWithReport,
|
|
14065
|
+
expandPath: () => expandPath,
|
|
14066
|
+
getConfigHome: () => getConfigHome,
|
|
14067
|
+
normalizeTargetPath: () => normalizeTargetPath,
|
|
14068
|
+
previewConfigs: () => previewConfigs
|
|
13944
14069
|
});
|
|
13945
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as
|
|
13946
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
13947
|
-
import { homedir as
|
|
14070
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
14071
|
+
import { basename as basename4, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
|
|
14072
|
+
import { homedir as homedir6 } from "os";
|
|
13948
14073
|
function getConfigHome() {
|
|
13949
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
14074
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
13950
14075
|
}
|
|
13951
14076
|
function expandPath(p) {
|
|
13952
14077
|
if (p.startsWith("~/")) {
|
|
13953
|
-
return
|
|
14078
|
+
return resolve7(getConfigHome(), p.slice(2));
|
|
13954
14079
|
}
|
|
13955
|
-
return
|
|
14080
|
+
return resolve7(p);
|
|
13956
14081
|
}
|
|
13957
14082
|
function normalizeTargetPath(p) {
|
|
13958
14083
|
const expanded = expandPath(p);
|
|
@@ -13964,7 +14089,7 @@ function normalizeTargetPath(p) {
|
|
|
13964
14089
|
while (true) {
|
|
13965
14090
|
if (existsSync7(current)) {
|
|
13966
14091
|
try {
|
|
13967
|
-
return
|
|
14092
|
+
return resolve7(realpathSync2(current), ...missingSegments);
|
|
13968
14093
|
} catch {
|
|
13969
14094
|
return expanded;
|
|
13970
14095
|
}
|
|
@@ -13993,7 +14118,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
13993
14118
|
}
|
|
13994
14119
|
const path = expandPath(renderedTargetPath);
|
|
13995
14120
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
13996
|
-
const previousContent = existsSync7(path) ?
|
|
14121
|
+
const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
|
|
13997
14122
|
const changed = previousContent !== renderedForTarget;
|
|
13998
14123
|
if (!opts.dryRun) {
|
|
13999
14124
|
const dir = dirname4(path);
|
|
@@ -14033,7 +14158,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
14033
14158
|
const path = expandPath(targetPath);
|
|
14034
14159
|
if (!existsSync7(path))
|
|
14035
14160
|
return [];
|
|
14036
|
-
current =
|
|
14161
|
+
current = readFileSync6(path, "utf-8");
|
|
14037
14162
|
} catch {
|
|
14038
14163
|
return secretTokens;
|
|
14039
14164
|
}
|
|
@@ -14357,7 +14482,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
|
14357
14482
|
getConfigHome(),
|
|
14358
14483
|
opts.vars?.["HOME_DIR"]
|
|
14359
14484
|
].filter((home) => typeof home === "string" && home.length > 0));
|
|
14360
|
-
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(
|
|
14485
|
+
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join10(home, ...relativePath.split("/"))))))
|
|
14361
14486
|
return true;
|
|
14362
14487
|
return sessionRenderOwnsPath(normalized);
|
|
14363
14488
|
}
|
|
@@ -14375,9 +14500,9 @@ var init_apply = __esm(() => {
|
|
|
14375
14500
|
});
|
|
14376
14501
|
|
|
14377
14502
|
// src/lib/sync-dir.ts
|
|
14378
|
-
import { existsSync as existsSync8, readdirSync, readFileSync as
|
|
14379
|
-
import { join as
|
|
14380
|
-
import { homedir as
|
|
14503
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
14504
|
+
import { join as join11, relative as relative4 } from "path";
|
|
14505
|
+
import { homedir as homedir7 } from "os";
|
|
14381
14506
|
function shouldSkip(p) {
|
|
14382
14507
|
return SKIP.some((s) => p.includes(s));
|
|
14383
14508
|
}
|
|
@@ -14386,9 +14511,9 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14386
14511
|
const absDir = expandPath(dir);
|
|
14387
14512
|
if (!existsSync8(absDir))
|
|
14388
14513
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14389
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) =>
|
|
14514
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
|
|
14390
14515
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14391
|
-
const home =
|
|
14516
|
+
const home = homedir7();
|
|
14392
14517
|
const allConfigs = await store.listConfigs();
|
|
14393
14518
|
for (const file of files) {
|
|
14394
14519
|
if (shouldSkip(file)) {
|
|
@@ -14396,7 +14521,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14396
14521
|
continue;
|
|
14397
14522
|
}
|
|
14398
14523
|
try {
|
|
14399
|
-
const content =
|
|
14524
|
+
const content = readFileSync7(file, "utf-8");
|
|
14400
14525
|
if (content.length > 500000) {
|
|
14401
14526
|
result.skipped.push(file + " (too large)");
|
|
14402
14527
|
continue;
|
|
@@ -14422,7 +14547,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14422
14547
|
}
|
|
14423
14548
|
async function syncToDir(dir, opts = {}) {
|
|
14424
14549
|
const store = opts.store ?? resolveConfigStore();
|
|
14425
|
-
const home =
|
|
14550
|
+
const home = homedir7();
|
|
14426
14551
|
const absDir = expandPath(dir);
|
|
14427
14552
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14428
14553
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14446,7 +14571,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
14446
14571
|
}
|
|
14447
14572
|
function walkDir(dir, files = []) {
|
|
14448
14573
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14449
|
-
const full =
|
|
14574
|
+
const full = join11(dir, entry.name);
|
|
14450
14575
|
if (shouldSkip(full))
|
|
14451
14576
|
continue;
|
|
14452
14577
|
if (entry.isDirectory())
|
|
@@ -14467,21 +14592,21 @@ var init_sync_dir = __esm(() => {
|
|
|
14467
14592
|
// src/lib/sync.ts
|
|
14468
14593
|
var exports_sync = {};
|
|
14469
14594
|
__export(exports_sync, {
|
|
14470
|
-
|
|
14471
|
-
syncToDir: () => syncToDir,
|
|
14472
|
-
syncProject: () => syncProject,
|
|
14473
|
-
syncKnown: () => syncKnown,
|
|
14474
|
-
syncFromDir: () => syncFromDir,
|
|
14475
|
-
diffConfig: () => diffConfig,
|
|
14476
|
-
detectFormat: () => detectFormat,
|
|
14477
|
-
detectCategory: () => detectCategory,
|
|
14478
|
-
detectAgent: () => detectAgent,
|
|
14479
|
-
PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
|
|
14595
|
+
CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS,
|
|
14480
14596
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS,
|
|
14481
|
-
|
|
14597
|
+
PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
|
|
14598
|
+
detectAgent: () => detectAgent,
|
|
14599
|
+
detectCategory: () => detectCategory,
|
|
14600
|
+
detectFormat: () => detectFormat,
|
|
14601
|
+
diffConfig: () => diffConfig,
|
|
14602
|
+
syncFromDir: () => syncFromDir,
|
|
14603
|
+
syncKnown: () => syncKnown,
|
|
14604
|
+
syncProject: () => syncProject,
|
|
14605
|
+
syncToDir: () => syncToDir,
|
|
14606
|
+
syncToDisk: () => syncToDisk
|
|
14482
14607
|
});
|
|
14483
|
-
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as
|
|
14484
|
-
import { basename as basename5, extname as extname3, join as
|
|
14608
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
14609
|
+
import { basename as basename5, extname as extname3, join as join12 } from "path";
|
|
14485
14610
|
function claudeRuleOutputs(fileName) {
|
|
14486
14611
|
const stem = basename5(fileName, extname3(fileName));
|
|
14487
14612
|
return [
|
|
@@ -14545,11 +14670,11 @@ async function syncProject(opts) {
|
|
|
14545
14670
|
const allConfigs = await store.listConfigs();
|
|
14546
14671
|
const machine = detectMachineContext();
|
|
14547
14672
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14548
|
-
const abs =
|
|
14673
|
+
const abs = join12(absDir, pf.file);
|
|
14549
14674
|
if (!existsSync9(abs))
|
|
14550
14675
|
continue;
|
|
14551
14676
|
try {
|
|
14552
|
-
const rawContent =
|
|
14677
|
+
const rawContent = readFileSync8(abs, "utf-8");
|
|
14553
14678
|
if (rawContent.length > 500000) {
|
|
14554
14679
|
result.skipped.push(pf.file);
|
|
14555
14680
|
continue;
|
|
@@ -14578,20 +14703,20 @@ async function syncProject(opts) {
|
|
|
14578
14703
|
}
|
|
14579
14704
|
}
|
|
14580
14705
|
for (const ruleDir of [
|
|
14581
|
-
{ dir:
|
|
14582
|
-
{ dir:
|
|
14583
|
-
{ dir:
|
|
14584
|
-
{ dir:
|
|
14585
|
-
{ dir:
|
|
14586
|
-
{ dir:
|
|
14587
|
-
{ dir:
|
|
14706
|
+
{ dir: join12(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
14707
|
+
{ dir: join12(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
14708
|
+
{ dir: join12(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
14709
|
+
{ dir: join12(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
14710
|
+
{ dir: join12(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
14711
|
+
{ dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
14712
|
+
{ dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14588
14713
|
]) {
|
|
14589
14714
|
if (!existsSync9(ruleDir.dir))
|
|
14590
14715
|
continue;
|
|
14591
14716
|
const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14592
14717
|
for (const f of mdFiles) {
|
|
14593
|
-
const abs =
|
|
14594
|
-
const raw =
|
|
14718
|
+
const abs = join12(ruleDir.dir, f);
|
|
14719
|
+
const raw = readFileSync8(abs, "utf-8");
|
|
14595
14720
|
const redacted = redactContent(raw, "markdown");
|
|
14596
14721
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14597
14722
|
const content = machineAware.content;
|
|
@@ -14637,13 +14762,13 @@ async function syncKnown(opts = {}) {
|
|
|
14637
14762
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
14638
14763
|
const ruleFiles = readdirSync2(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
14639
14764
|
for (const f of ruleFiles) {
|
|
14640
|
-
const abs2 =
|
|
14765
|
+
const abs2 = join12(absDir, f);
|
|
14641
14766
|
const targetPath = abs2.replace(home, "~");
|
|
14642
14767
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
14643
14768
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
14644
14769
|
continue;
|
|
14645
14770
|
}
|
|
14646
|
-
const raw =
|
|
14771
|
+
const raw = readFileSync8(abs2, "utf-8");
|
|
14647
14772
|
const redacted = redactContent(raw, "markdown");
|
|
14648
14773
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14649
14774
|
const content = machineAware.content;
|
|
@@ -14676,7 +14801,7 @@ async function syncKnown(opts = {}) {
|
|
|
14676
14801
|
continue;
|
|
14677
14802
|
}
|
|
14678
14803
|
try {
|
|
14679
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
14804
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
|
|
14680
14805
|
if (rawContent.length > 500000) {
|
|
14681
14806
|
result.skipped.push(known.path + " (too large)");
|
|
14682
14807
|
continue;
|
|
@@ -14779,7 +14904,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
|
14779
14904
|
const path = expandPath(targetPath);
|
|
14780
14905
|
if (!existsSync9(path))
|
|
14781
14906
|
return `(file not found on disk: ${path})`;
|
|
14782
|
-
const diskContent =
|
|
14907
|
+
const diskContent = readFileSync8(path, "utf-8");
|
|
14783
14908
|
if (diskContent === expectedContent)
|
|
14784
14909
|
return "(no diff \u2014 identical)";
|
|
14785
14910
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -15012,18 +15137,18 @@ __export(exports_package_manager_guard, {
|
|
|
15012
15137
|
scanPackageManagerSecrets: () => scanPackageManagerSecrets
|
|
15013
15138
|
});
|
|
15014
15139
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15015
|
-
import { existsSync as
|
|
15016
|
-
import { homedir as
|
|
15017
|
-
import { basename as basename6, dirname as
|
|
15140
|
+
import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
|
|
15141
|
+
import { homedir as homedir9 } from "os";
|
|
15142
|
+
import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative7, resolve as resolve12 } from "path";
|
|
15018
15143
|
function scanPackageManagerSecrets(options = {}) {
|
|
15019
|
-
const cwd = options.cwd ?
|
|
15020
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
15144
|
+
const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
|
|
15145
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve12(cwd, root));
|
|
15021
15146
|
const findings = [];
|
|
15022
15147
|
let scannedFiles = 0;
|
|
15023
15148
|
for (const root of roots) {
|
|
15024
|
-
if (!
|
|
15149
|
+
if (!existsSync16(root))
|
|
15025
15150
|
continue;
|
|
15026
|
-
const stat =
|
|
15151
|
+
const stat = lstatSync6(root);
|
|
15027
15152
|
if (stat.isFile()) {
|
|
15028
15153
|
if (!shouldScanRepoFile(root))
|
|
15029
15154
|
continue;
|
|
@@ -15031,14 +15156,14 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15031
15156
|
if (text === null)
|
|
15032
15157
|
continue;
|
|
15033
15158
|
scannedFiles++;
|
|
15034
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15159
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
|
|
15035
15160
|
continue;
|
|
15036
15161
|
}
|
|
15037
15162
|
if (!stat.isDirectory())
|
|
15038
15163
|
continue;
|
|
15039
15164
|
const tracked = trackedFiles(root);
|
|
15040
15165
|
for (const file of collectRepoFiles(root)) {
|
|
15041
|
-
const rel = toPosix(
|
|
15166
|
+
const rel = toPosix(relative7(root, file));
|
|
15042
15167
|
const isTracked = tracked.has(rel);
|
|
15043
15168
|
const text = readTextFile(file);
|
|
15044
15169
|
if (text === null)
|
|
@@ -15048,10 +15173,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15048
15173
|
}
|
|
15049
15174
|
}
|
|
15050
15175
|
if (options.includeHome) {
|
|
15051
|
-
const home =
|
|
15176
|
+
const home = homedir9();
|
|
15052
15177
|
for (const name of HOME_FILES) {
|
|
15053
|
-
const file =
|
|
15054
|
-
if (!
|
|
15178
|
+
const file = join18(home, name);
|
|
15179
|
+
if (!existsSync16(file))
|
|
15055
15180
|
continue;
|
|
15056
15181
|
const text = readTextFile(file);
|
|
15057
15182
|
if (text === null)
|
|
@@ -15075,12 +15200,12 @@ function collectRepoFiles(root) {
|
|
|
15075
15200
|
if (entry.isDirectory()) {
|
|
15076
15201
|
if (SKIP_DIRS.has(entry.name))
|
|
15077
15202
|
continue;
|
|
15078
|
-
visit(
|
|
15203
|
+
visit(join18(dir, entry.name));
|
|
15079
15204
|
continue;
|
|
15080
15205
|
}
|
|
15081
15206
|
if (!entry.isFile())
|
|
15082
15207
|
continue;
|
|
15083
|
-
const file =
|
|
15208
|
+
const file = join18(dir, entry.name);
|
|
15084
15209
|
if (shouldScanRepoFile(file))
|
|
15085
15210
|
out.push(file);
|
|
15086
15211
|
}
|
|
@@ -15115,10 +15240,10 @@ function isNpmrcName(name) {
|
|
|
15115
15240
|
}
|
|
15116
15241
|
function readTextFile(file) {
|
|
15117
15242
|
try {
|
|
15118
|
-
const stat =
|
|
15243
|
+
const stat = lstatSync6(file);
|
|
15119
15244
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15120
15245
|
return null;
|
|
15121
|
-
const buf =
|
|
15246
|
+
const buf = readFileSync14(file);
|
|
15122
15247
|
if (buf.includes(0))
|
|
15123
15248
|
return null;
|
|
15124
15249
|
return buf.toString("utf-8");
|
|
@@ -15318,11 +15443,11 @@ function trackedFiles(root) {
|
|
|
15318
15443
|
}
|
|
15319
15444
|
function isTrackedFile(file) {
|
|
15320
15445
|
try {
|
|
15321
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
15446
|
+
const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
|
|
15322
15447
|
encoding: "utf-8",
|
|
15323
15448
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15324
15449
|
}).trim();
|
|
15325
|
-
const rel = toPosix(
|
|
15450
|
+
const rel = toPosix(relative7(repoRoot, file));
|
|
15326
15451
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
15327
15452
|
stdio: ["ignore", "ignore", "ignore"]
|
|
15328
15453
|
});
|
|
@@ -15348,13 +15473,13 @@ function stripInlineComment(value) {
|
|
|
15348
15473
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15349
15474
|
}
|
|
15350
15475
|
function displayPath(file, root) {
|
|
15351
|
-
const home =
|
|
15476
|
+
const home = homedir9();
|
|
15352
15477
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15353
|
-
return "~/" + toPosix(
|
|
15478
|
+
return "~/" + toPosix(relative7(home, file));
|
|
15354
15479
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|
|
15355
|
-
return toPosix(
|
|
15480
|
+
return toPosix(relative7(root, file));
|
|
15356
15481
|
if (file === home || file.startsWith(home + "/"))
|
|
15357
|
-
return "~/" + toPosix(
|
|
15482
|
+
return "~/" + toPosix(relative7(home, file));
|
|
15358
15483
|
return file;
|
|
15359
15484
|
}
|
|
15360
15485
|
function toPosix(path) {
|
|
@@ -15392,15 +15517,16 @@ var init_package_manager_guard = __esm(() => {
|
|
|
15392
15517
|
TOKEN_VALUE_PATTERNS = [
|
|
15393
15518
|
{ re: /npm_[A-Za-z0-9]{36,}/, rule: "literal-npm-token", detail: "literal npm token-like value" },
|
|
15394
15519
|
{ 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" },
|
|
15520
|
+
{ re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
|
|
15396
15521
|
{ re: /sk-[A-Za-z0-9]{48,}/, rule: "literal-openai-key", detail: "literal OpenAI key-like value" },
|
|
15397
15522
|
{ re: /AKIA[0-9A-Z]{16}/, rule: "literal-aws-access-key", detail: "literal AWS access-key-like value" },
|
|
15398
15523
|
{ re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
|
|
15399
15524
|
];
|
|
15400
15525
|
});
|
|
15401
15526
|
|
|
15402
|
-
//
|
|
15527
|
+
// ../events/dist/commander.js
|
|
15403
15528
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
15529
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
15404
15530
|
import { existsSync } from "fs";
|
|
15405
15531
|
import { homedir } from "os";
|
|
15406
15532
|
import { join } from "path";
|
|
@@ -15416,29 +15542,83 @@ function getPathValue(input, path) {
|
|
|
15416
15542
|
return;
|
|
15417
15543
|
}, input);
|
|
15418
15544
|
}
|
|
15419
|
-
function
|
|
15420
|
-
const
|
|
15421
|
-
|
|
15545
|
+
function getFieldValues(input, path) {
|
|
15546
|
+
const values = [];
|
|
15547
|
+
const push = (value) => {
|
|
15548
|
+
if (!values.some((item) => Object.is(item, value)))
|
|
15549
|
+
values.push(value);
|
|
15550
|
+
};
|
|
15551
|
+
if (path.includes(".") && path in input)
|
|
15552
|
+
push(input[path]);
|
|
15553
|
+
const nestedValue = getPathValue(input, path);
|
|
15554
|
+
if (nestedValue !== undefined || !path.includes("."))
|
|
15555
|
+
push(nestedValue);
|
|
15556
|
+
return values;
|
|
15557
|
+
}
|
|
15558
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
15559
|
+
let body = "";
|
|
15560
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
15561
|
+
const char = pattern[index];
|
|
15562
|
+
if (char === "*") {
|
|
15563
|
+
if (pattern[index + 1] === "*") {
|
|
15564
|
+
body += ".*";
|
|
15565
|
+
index += 1;
|
|
15566
|
+
} else {
|
|
15567
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
15568
|
+
}
|
|
15569
|
+
} else {
|
|
15570
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
15571
|
+
}
|
|
15572
|
+
}
|
|
15573
|
+
return new RegExp(`^${body}$`);
|
|
15422
15574
|
}
|
|
15423
|
-
function matchString(value, matcher) {
|
|
15575
|
+
function matchString(value, matcher, options = {}) {
|
|
15424
15576
|
if (matcher === undefined)
|
|
15425
15577
|
return true;
|
|
15426
15578
|
if (value === undefined)
|
|
15427
15579
|
return false;
|
|
15428
15580
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
15429
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
15581
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
15430
15582
|
}
|
|
15431
15583
|
function matchRecord(input, matcher) {
|
|
15432
15584
|
if (!matcher)
|
|
15433
15585
|
return true;
|
|
15434
15586
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
15435
|
-
const
|
|
15436
|
-
|
|
15437
|
-
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
15438
|
-
}
|
|
15439
|
-
return actual === expected;
|
|
15587
|
+
const actualValues = getFieldValues(input, path);
|
|
15588
|
+
return matchField(actualValues, expected, path);
|
|
15440
15589
|
});
|
|
15441
15590
|
}
|
|
15591
|
+
function matchField(actualValues, expected, path) {
|
|
15592
|
+
if (isNegativeMatcher(expected)) {
|
|
15593
|
+
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
15594
|
+
}
|
|
15595
|
+
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
15596
|
+
}
|
|
15597
|
+
function matchPositiveField(actual, expected, path) {
|
|
15598
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
15599
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
15600
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
15601
|
+
}));
|
|
15602
|
+
}
|
|
15603
|
+
if (Array.isArray(actual)) {
|
|
15604
|
+
return actual.some((item) => item === expected);
|
|
15605
|
+
}
|
|
15606
|
+
return actual === expected;
|
|
15607
|
+
}
|
|
15608
|
+
function stringCandidates(actual) {
|
|
15609
|
+
if (actual === undefined)
|
|
15610
|
+
return [];
|
|
15611
|
+
if (Array.isArray(actual)) {
|
|
15612
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
15613
|
+
}
|
|
15614
|
+
return [String(actual)];
|
|
15615
|
+
}
|
|
15616
|
+
function isPrimitiveFieldValue(value) {
|
|
15617
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
15618
|
+
}
|
|
15619
|
+
function isNegativeMatcher(value) {
|
|
15620
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
15621
|
+
}
|
|
15442
15622
|
function eventMatchesFilter(event, filter) {
|
|
15443
15623
|
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
15624
|
}
|
|
@@ -15451,16 +15631,29 @@ function channelMatchesEvent(channel, event) {
|
|
|
15451
15631
|
}
|
|
15452
15632
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
15453
15633
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
15634
|
+
var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
|
|
15635
|
+
var DEFAULT_EVENT_PAGE_LIMIT = 100;
|
|
15636
|
+
var MAX_EVENT_PAGE_LIMIT = 1000;
|
|
15454
15637
|
function getEventsDataDir(override) {
|
|
15455
15638
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
15456
15639
|
}
|
|
15640
|
+
function getActiveEventsDirEnv() {
|
|
15641
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
15642
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
15643
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
15644
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
15645
|
+
return null;
|
|
15646
|
+
}
|
|
15647
|
+
|
|
15457
15648
|
class JsonEventsStore {
|
|
15458
15649
|
dataDir;
|
|
15650
|
+
runtime;
|
|
15459
15651
|
channelsPath;
|
|
15460
15652
|
eventsPath;
|
|
15461
15653
|
deliveriesPath;
|
|
15462
15654
|
constructor(dataDir = getEventsDataDir()) {
|
|
15463
15655
|
this.dataDir = dataDir;
|
|
15656
|
+
this.runtime = localJsonRuntime(dataDir);
|
|
15464
15657
|
this.channelsPath = join(dataDir, "channels.json");
|
|
15465
15658
|
this.eventsPath = join(dataDir, "events.json");
|
|
15466
15659
|
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
@@ -15508,13 +15701,58 @@ class JsonEventsStore {
|
|
|
15508
15701
|
await this.writeJson(this.eventsPath, events);
|
|
15509
15702
|
return event;
|
|
15510
15703
|
}
|
|
15511
|
-
async
|
|
15704
|
+
async appendEventOnce(event, options = {}) {
|
|
15705
|
+
await this.init();
|
|
15706
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15707
|
+
const dedupe = options.dedupe !== false;
|
|
15708
|
+
if (dedupe) {
|
|
15709
|
+
const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
|
|
15710
|
+
if (existing) {
|
|
15711
|
+
return {
|
|
15712
|
+
event: existing,
|
|
15713
|
+
stored: false,
|
|
15714
|
+
deduped: true,
|
|
15715
|
+
identity: { id: existing.id, dedupeKey: existing.dedupeKey }
|
|
15716
|
+
};
|
|
15717
|
+
}
|
|
15718
|
+
}
|
|
15719
|
+
events.push(event);
|
|
15720
|
+
await this.writeJson(this.eventsPath, events);
|
|
15721
|
+
return {
|
|
15722
|
+
event,
|
|
15723
|
+
stored: true,
|
|
15724
|
+
deduped: false,
|
|
15725
|
+
identity: { id: event.id, dedupeKey: event.dedupeKey }
|
|
15726
|
+
};
|
|
15727
|
+
}
|
|
15728
|
+
async listEvents(options = {}) {
|
|
15512
15729
|
await this.init();
|
|
15513
|
-
|
|
15730
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15731
|
+
return queryEvents(events, options);
|
|
15732
|
+
}
|
|
15733
|
+
async listEventsPage(options = {}) {
|
|
15734
|
+
await this.init();
|
|
15735
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
15736
|
+
const queried = queryEvents(events, {
|
|
15737
|
+
eventId: options.eventId,
|
|
15738
|
+
source: options.source,
|
|
15739
|
+
type: options.type
|
|
15740
|
+
});
|
|
15741
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
15742
|
+
const limit = normalizeEventPageLimit(options.limit);
|
|
15743
|
+
const pageEvents = queried.slice(offset, offset + limit);
|
|
15744
|
+
const nextOffset = offset + pageEvents.length;
|
|
15745
|
+
const hasMore = nextOffset < queried.length;
|
|
15746
|
+
return {
|
|
15747
|
+
events: pageEvents,
|
|
15748
|
+
cursor: options.cursor,
|
|
15749
|
+
nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
|
|
15750
|
+
hasMore
|
|
15751
|
+
};
|
|
15514
15752
|
}
|
|
15515
15753
|
async findEventByIdentity(identity) {
|
|
15516
15754
|
const events = await this.listEvents();
|
|
15517
|
-
return events
|
|
15755
|
+
return findEventByIdentity(events, identity);
|
|
15518
15756
|
}
|
|
15519
15757
|
async appendDelivery(result) {
|
|
15520
15758
|
await this.init();
|
|
@@ -15565,6 +15803,130 @@ class JsonEventsStore {
|
|
|
15565
15803
|
});
|
|
15566
15804
|
}
|
|
15567
15805
|
}
|
|
15806
|
+
function localJsonRuntime(dataDir = getEventsDataDir()) {
|
|
15807
|
+
return {
|
|
15808
|
+
mode: "local-files",
|
|
15809
|
+
name: "json-events-store",
|
|
15810
|
+
remote: false,
|
|
15811
|
+
localFiles: true,
|
|
15812
|
+
localSqlite: false,
|
|
15813
|
+
postgres: false,
|
|
15814
|
+
s3: false,
|
|
15815
|
+
aws: false,
|
|
15816
|
+
durable: true,
|
|
15817
|
+
idempotency: "best-effort-local",
|
|
15818
|
+
replayCursors: true,
|
|
15819
|
+
description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
|
|
15820
|
+
};
|
|
15821
|
+
}
|
|
15822
|
+
function encodeLocalJsonEventCursor(offset, options = {}) {
|
|
15823
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
15824
|
+
throw new Error(`Invalid event cursor offset: ${offset}`);
|
|
15825
|
+
const payload = {
|
|
15826
|
+
offset,
|
|
15827
|
+
eventId: options.eventId,
|
|
15828
|
+
source: options.source,
|
|
15829
|
+
type: options.type
|
|
15830
|
+
};
|
|
15831
|
+
return `${LOCAL_JSON_EVENT_CURSOR_PREFIX}${Buffer2.from(JSON.stringify(payload), "utf-8").toString("base64url")}`;
|
|
15832
|
+
}
|
|
15833
|
+
function decodeLocalJsonEventCursor(cursor, options = {}) {
|
|
15834
|
+
if (!cursor)
|
|
15835
|
+
return 0;
|
|
15836
|
+
if (!cursor.startsWith(LOCAL_JSON_EVENT_CURSOR_PREFIX))
|
|
15837
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15838
|
+
const rawPayload = cursor.slice(LOCAL_JSON_EVENT_CURSOR_PREFIX.length);
|
|
15839
|
+
let payload;
|
|
15840
|
+
try {
|
|
15841
|
+
payload = JSON.parse(Buffer2.from(rawPayload, "base64url").toString("utf-8"));
|
|
15842
|
+
} catch {
|
|
15843
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15844
|
+
}
|
|
15845
|
+
const offset = payload.offset;
|
|
15846
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
15847
|
+
throw new Error(`Invalid local JSON event cursor: ${cursor}`);
|
|
15848
|
+
assertCursorFilter("eventId", payload.eventId, options.eventId);
|
|
15849
|
+
assertCursorFilter("source", payload.source, options.source);
|
|
15850
|
+
assertCursorFilter("type", payload.type, options.type);
|
|
15851
|
+
return offset;
|
|
15852
|
+
}
|
|
15853
|
+
function normalizeEventPageLimit(limit) {
|
|
15854
|
+
if (limit === undefined)
|
|
15855
|
+
return DEFAULT_EVENT_PAGE_LIMIT;
|
|
15856
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
15857
|
+
throw new Error(`Event page limit must be a positive integer, got ${limit}`);
|
|
15858
|
+
return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
|
|
15859
|
+
}
|
|
15860
|
+
function queryEvents(events, options) {
|
|
15861
|
+
let rows = events;
|
|
15862
|
+
if (options.eventId)
|
|
15863
|
+
rows = rows.filter((event) => event.id === options.eventId);
|
|
15864
|
+
if (options.source)
|
|
15865
|
+
rows = rows.filter((event) => event.source === options.source);
|
|
15866
|
+
if (options.type)
|
|
15867
|
+
rows = rows.filter((event) => event.type === options.type);
|
|
15868
|
+
if (options.cursor) {
|
|
15869
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
15870
|
+
rows = rows.slice(offset);
|
|
15871
|
+
}
|
|
15872
|
+
if (options.limit !== undefined)
|
|
15873
|
+
rows = rows.slice(0, normalizeEventPageLimit(options.limit));
|
|
15874
|
+
return rows;
|
|
15875
|
+
}
|
|
15876
|
+
function assertCursorFilter(name, cursorValue, optionValue) {
|
|
15877
|
+
if (cursorValue !== optionValue)
|
|
15878
|
+
throw new Error(`Local JSON event cursor ${name} filter mismatch`);
|
|
15879
|
+
}
|
|
15880
|
+
function findEventByIdentity(events, identity) {
|
|
15881
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
15882
|
+
}
|
|
15883
|
+
async function getEventsStatus(dataDir) {
|
|
15884
|
+
const store = new JsonEventsStore(dataDir);
|
|
15885
|
+
await store.init();
|
|
15886
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
15887
|
+
store.listChannels(),
|
|
15888
|
+
store.listEvents(),
|
|
15889
|
+
store.listDeliveries()
|
|
15890
|
+
]);
|
|
15891
|
+
const transports = channels.reduce((counts, channel) => {
|
|
15892
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
15893
|
+
return counts;
|
|
15894
|
+
}, {});
|
|
15895
|
+
return {
|
|
15896
|
+
service: "events",
|
|
15897
|
+
schemaVersion: "1.0",
|
|
15898
|
+
dataDir: store.dataDir,
|
|
15899
|
+
storage: store.runtime,
|
|
15900
|
+
env: {
|
|
15901
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
15902
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
15903
|
+
active: getActiveEventsDirEnv()
|
|
15904
|
+
},
|
|
15905
|
+
files: {
|
|
15906
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
15907
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
15908
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
15909
|
+
},
|
|
15910
|
+
counts: {
|
|
15911
|
+
channels: channels.length,
|
|
15912
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
15913
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
15914
|
+
events: events.length,
|
|
15915
|
+
deliveries: deliveries.length
|
|
15916
|
+
},
|
|
15917
|
+
transports,
|
|
15918
|
+
safety: {
|
|
15919
|
+
includesEventPayloads: false,
|
|
15920
|
+
includesWebhookSecrets: false,
|
|
15921
|
+
listOutputsRedactSecrets: true,
|
|
15922
|
+
statusOutputIsMetadataOnly: true
|
|
15923
|
+
}
|
|
15924
|
+
};
|
|
15925
|
+
}
|
|
15926
|
+
function statusFile(dataDir, fileName, records) {
|
|
15927
|
+
const path = join(dataDir, fileName);
|
|
15928
|
+
return { path, exists: existsSync(path), records };
|
|
15929
|
+
}
|
|
15568
15930
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
15569
15931
|
function buildSignatureBase(timestamp, body) {
|
|
15570
15932
|
return `${timestamp}.${body}`;
|
|
@@ -15579,21 +15941,27 @@ function now() {
|
|
|
15579
15941
|
function truncate(value, max = 4096) {
|
|
15580
15942
|
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
15581
15943
|
}
|
|
15582
|
-
function buildWebhookRequest(event, channel) {
|
|
15944
|
+
function buildWebhookRequest(event, channel, options = {}) {
|
|
15583
15945
|
if (!channel.webhook)
|
|
15584
15946
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
15947
|
+
for (const name of Object.keys(channel.webhook.headers ?? {})) {
|
|
15948
|
+
if (/^x-hasna-/i.test(name)) {
|
|
15949
|
+
throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
|
|
15950
|
+
}
|
|
15951
|
+
}
|
|
15585
15952
|
const body = JSON.stringify(event);
|
|
15586
|
-
const timestamp =
|
|
15953
|
+
const timestamp = options.timestamp ?? new Date().toISOString();
|
|
15587
15954
|
const headers = {
|
|
15588
15955
|
"Content-Type": "application/json",
|
|
15589
15956
|
"User-Agent": "@hasna/events",
|
|
15590
15957
|
"X-Hasna-Event-Id": event.id,
|
|
15591
15958
|
"X-Hasna-Event-Type": event.type,
|
|
15592
|
-
|
|
15593
|
-
|
|
15959
|
+
...channel.webhook.headers,
|
|
15960
|
+
"X-Hasna-Timestamp": timestamp
|
|
15594
15961
|
};
|
|
15595
|
-
|
|
15596
|
-
|
|
15962
|
+
const secret = options.secret ?? channel.webhook.secret;
|
|
15963
|
+
if (secret) {
|
|
15964
|
+
headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
|
|
15597
15965
|
}
|
|
15598
15966
|
return { body, headers };
|
|
15599
15967
|
}
|
|
@@ -15601,7 +15969,21 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
15601
15969
|
if (!channel.webhook)
|
|
15602
15970
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
15603
15971
|
const startedAt = now();
|
|
15604
|
-
|
|
15972
|
+
let secret = channel.webhook.secret;
|
|
15973
|
+
if (channel.webhook.secretRef) {
|
|
15974
|
+
if (!options.secretResolver) {
|
|
15975
|
+
return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
|
|
15976
|
+
}
|
|
15977
|
+
try {
|
|
15978
|
+
secret = await options.secretResolver(channel.webhook.secretRef);
|
|
15979
|
+
} catch {
|
|
15980
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
15981
|
+
}
|
|
15982
|
+
if (!secret)
|
|
15983
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
15984
|
+
}
|
|
15985
|
+
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
15986
|
+
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
15605
15987
|
const controller = new AbortController;
|
|
15606
15988
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
15607
15989
|
try {
|
|
@@ -15633,6 +16015,15 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
15633
16015
|
clearTimeout(timeout);
|
|
15634
16016
|
}
|
|
15635
16017
|
}
|
|
16018
|
+
function failedAttempt(startedAt, error) {
|
|
16019
|
+
return {
|
|
16020
|
+
attempt: 1,
|
|
16021
|
+
status: "failed",
|
|
16022
|
+
startedAt,
|
|
16023
|
+
completedAt: now(),
|
|
16024
|
+
error
|
|
16025
|
+
};
|
|
16026
|
+
}
|
|
15636
16027
|
async function dispatchCommand(event, channel) {
|
|
15637
16028
|
if (!channel.command)
|
|
15638
16029
|
throw new Error(`Channel ${channel.id} has no command config`);
|
|
@@ -15721,6 +16112,90 @@ function createDeliveryResult(event, channel, attempts) {
|
|
|
15721
16112
|
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
15722
16113
|
};
|
|
15723
16114
|
}
|
|
16115
|
+
|
|
16116
|
+
class EventValidationError extends Error {
|
|
16117
|
+
eventType;
|
|
16118
|
+
issues;
|
|
16119
|
+
constructor(eventType, issues) {
|
|
16120
|
+
const detail = issues.map((issue) => `${issue.path || "<root>"}: ${issue.message}`).join("; ");
|
|
16121
|
+
super(`Event validation failed for type "${eventType}": ${detail}`);
|
|
16122
|
+
this.name = "EventValidationError";
|
|
16123
|
+
this.eventType = eventType;
|
|
16124
|
+
this.issues = issues;
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
|
|
16128
|
+
class EventTypeCatalog {
|
|
16129
|
+
definitions = new Map;
|
|
16130
|
+
register(definition) {
|
|
16131
|
+
this.definitions.set(definition.type, definition);
|
|
16132
|
+
return this;
|
|
16133
|
+
}
|
|
16134
|
+
unregister(type) {
|
|
16135
|
+
return this.definitions.delete(type);
|
|
16136
|
+
}
|
|
16137
|
+
has(type) {
|
|
16138
|
+
return this.definitions.has(type);
|
|
16139
|
+
}
|
|
16140
|
+
get(type) {
|
|
16141
|
+
return this.definitions.get(type);
|
|
16142
|
+
}
|
|
16143
|
+
list() {
|
|
16144
|
+
return [...this.definitions.values()];
|
|
16145
|
+
}
|
|
16146
|
+
validateEvent(event) {
|
|
16147
|
+
const definition = this.definitions.get(event.type);
|
|
16148
|
+
if (!definition)
|
|
16149
|
+
return { ok: true };
|
|
16150
|
+
return definition.validate(event.data, event);
|
|
16151
|
+
}
|
|
16152
|
+
assertEventValid(event) {
|
|
16153
|
+
const result = this.validateEvent(event);
|
|
16154
|
+
if (!result.ok) {
|
|
16155
|
+
throw new EventValidationError(event.type, result.issues);
|
|
16156
|
+
}
|
|
16157
|
+
}
|
|
16158
|
+
}
|
|
16159
|
+
var defaultEventTypeCatalog = new EventTypeCatalog;
|
|
16160
|
+
var APP_EVENT_V1_MAX_DATA_BYTES = 32 * 1024;
|
|
16161
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
16162
|
+
if (paths.length === 0)
|
|
16163
|
+
return event;
|
|
16164
|
+
const copy = structuredClone(event);
|
|
16165
|
+
for (const path of paths) {
|
|
16166
|
+
setPath(copy, path, replacement);
|
|
16167
|
+
}
|
|
16168
|
+
return copy;
|
|
16169
|
+
}
|
|
16170
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
16171
|
+
return redactValue(event, replacement);
|
|
16172
|
+
}
|
|
16173
|
+
function shouldRedactKey(key) {
|
|
16174
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
16175
|
+
}
|
|
16176
|
+
function redactValue(value, replacement) {
|
|
16177
|
+
if (Array.isArray(value))
|
|
16178
|
+
return value.map((item) => redactValue(item, replacement));
|
|
16179
|
+
if (!value || typeof value !== "object")
|
|
16180
|
+
return value;
|
|
16181
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
16182
|
+
key,
|
|
16183
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
16184
|
+
]));
|
|
16185
|
+
}
|
|
16186
|
+
function setPath(input, path, replacement) {
|
|
16187
|
+
const parts = path.split(".");
|
|
16188
|
+
let cursor = input;
|
|
16189
|
+
for (const part of parts.slice(0, -1)) {
|
|
16190
|
+
const next = cursor[part];
|
|
16191
|
+
if (!next || typeof next !== "object")
|
|
16192
|
+
return;
|
|
16193
|
+
cursor = next;
|
|
16194
|
+
}
|
|
16195
|
+
const last = parts.at(-1);
|
|
16196
|
+
if (last && last in cursor)
|
|
16197
|
+
cursor[last] = replacement;
|
|
16198
|
+
}
|
|
15724
16199
|
function createEvent(input) {
|
|
15725
16200
|
return {
|
|
15726
16201
|
id: input.id ?? randomUUID2(),
|
|
@@ -15741,10 +16216,18 @@ class EventsClient {
|
|
|
15741
16216
|
store;
|
|
15742
16217
|
redactors;
|
|
15743
16218
|
transportOptions;
|
|
16219
|
+
catalog;
|
|
16220
|
+
validateCatalogTypes;
|
|
15744
16221
|
constructor(options = {}) {
|
|
15745
16222
|
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
15746
16223
|
this.redactors = options.redactors ?? [];
|
|
15747
|
-
this.transportOptions = {
|
|
16224
|
+
this.transportOptions = {
|
|
16225
|
+
fetchImpl: options.fetchImpl,
|
|
16226
|
+
secretResolver: options.secretResolver,
|
|
16227
|
+
now: options.now
|
|
16228
|
+
};
|
|
16229
|
+
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
16230
|
+
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
|
15748
16231
|
}
|
|
15749
16232
|
async addChannel(input) {
|
|
15750
16233
|
const timestamp = new Date().toISOString();
|
|
@@ -15762,18 +16245,40 @@ class EventsClient {
|
|
|
15762
16245
|
}
|
|
15763
16246
|
async emit(input, options = {}) {
|
|
15764
16247
|
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
|
-
|
|
16248
|
+
if (options.validate ?? this.validateCatalogTypes) {
|
|
16249
|
+
this.catalog.assertEventValid(event);
|
|
16250
|
+
}
|
|
16251
|
+
const append = await this.appendEvent(event, { dedupe: options.dedupe !== false });
|
|
16252
|
+
if (append.deduped) {
|
|
16253
|
+
return { event: append.event, deliveries: [], deduped: true };
|
|
16254
|
+
}
|
|
16255
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(append.event);
|
|
16256
|
+
return { event: append.event, deliveries, deduped: false };
|
|
16257
|
+
}
|
|
16258
|
+
async listEvents(options = {}) {
|
|
16259
|
+
if (Object.keys(options).length === 0)
|
|
16260
|
+
return this.store.listEvents();
|
|
16261
|
+
return queryClientEvents(await this.store.listEvents(), options);
|
|
16262
|
+
}
|
|
16263
|
+
async listEventsPage(options = {}) {
|
|
16264
|
+
if (this.store.listEventsPage)
|
|
16265
|
+
return this.store.listEventsPage(options);
|
|
16266
|
+
const events = queryClientEvents(await this.store.listEvents(), {
|
|
16267
|
+
eventId: options.eventId,
|
|
16268
|
+
source: options.source,
|
|
16269
|
+
type: options.type
|
|
16270
|
+
});
|
|
16271
|
+
const offset = decodeLocalJsonEventCursor(options.cursor, options);
|
|
16272
|
+
const limit = normalizeEventPageLimit(options.limit);
|
|
16273
|
+
const pageEvents = events.slice(offset, offset + limit);
|
|
16274
|
+
const nextOffset = offset + pageEvents.length;
|
|
16275
|
+
const hasMore = nextOffset < events.length;
|
|
16276
|
+
return {
|
|
16277
|
+
events: pageEvents,
|
|
16278
|
+
cursor: options.cursor,
|
|
16279
|
+
nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
|
|
16280
|
+
hasMore
|
|
16281
|
+
};
|
|
15777
16282
|
}
|
|
15778
16283
|
async listDeliveries() {
|
|
15779
16284
|
return this.store.listDeliveries();
|
|
@@ -15790,7 +16295,7 @@ class EventsClient {
|
|
|
15790
16295
|
}
|
|
15791
16296
|
return deliveries;
|
|
15792
16297
|
}
|
|
15793
|
-
async
|
|
16298
|
+
async matchChannel(id, input = {}) {
|
|
15794
16299
|
const channel = await this.store.getChannel(id);
|
|
15795
16300
|
if (!channel)
|
|
15796
16301
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -15807,28 +16312,71 @@ class EventsClient {
|
|
|
15807
16312
|
time: input.time,
|
|
15808
16313
|
id: input.id
|
|
15809
16314
|
});
|
|
16315
|
+
const matched = channelMatchesEvent(channel, event);
|
|
16316
|
+
return {
|
|
16317
|
+
channelId: channel.id,
|
|
16318
|
+
matched,
|
|
16319
|
+
event,
|
|
16320
|
+
filters: channel.filters,
|
|
16321
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
16322
|
+
};
|
|
16323
|
+
}
|
|
16324
|
+
async testChannel(id, input = {}, options = {}) {
|
|
16325
|
+
const channel = await this.store.getChannel(id);
|
|
16326
|
+
if (!channel)
|
|
16327
|
+
throw new Error(`Channel not found: ${id}`);
|
|
16328
|
+
const match = await this.matchChannel(id, input);
|
|
16329
|
+
const event = match.event;
|
|
16330
|
+
if (options.honorFilters && !match.matched) {
|
|
16331
|
+
const timestamp = new Date().toISOString();
|
|
16332
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
16333
|
+
attempt: 1,
|
|
16334
|
+
status: "skipped",
|
|
16335
|
+
startedAt: timestamp,
|
|
16336
|
+
completedAt: timestamp,
|
|
16337
|
+
error: match.reason
|
|
16338
|
+
}]);
|
|
16339
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
16340
|
+
await this.store.appendDelivery(result2);
|
|
16341
|
+
return result2;
|
|
16342
|
+
}
|
|
15810
16343
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
15811
16344
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
15812
16345
|
await this.store.appendDelivery(result);
|
|
15813
16346
|
return result;
|
|
15814
16347
|
}
|
|
15815
16348
|
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
|
-
});
|
|
16349
|
+
const page = options.cursor || options.limit !== undefined ? await this.listEventsPage(options) : { events: await this.listEvents(options), hasMore: false };
|
|
15825
16350
|
if (options.dryRun)
|
|
15826
|
-
return { events, deliveries: [] };
|
|
16351
|
+
return { events: page.events, deliveries: [], cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
|
|
15827
16352
|
const deliveries = [];
|
|
15828
|
-
for (const event of events) {
|
|
16353
|
+
for (const event of page.events) {
|
|
15829
16354
|
deliveries.push(...await this.deliver(event));
|
|
15830
16355
|
}
|
|
15831
|
-
return { events, deliveries };
|
|
16356
|
+
return { events: page.events, deliveries, cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
|
|
16357
|
+
}
|
|
16358
|
+
async appendEvent(event, options) {
|
|
16359
|
+
if (this.store.appendEventOnce) {
|
|
16360
|
+
return this.store.appendEventOnce(event, { dedupe: options.dedupe });
|
|
16361
|
+
}
|
|
16362
|
+
if (options.dedupe) {
|
|
16363
|
+
const existing = await this.store.findEventByIdentity({ id: event.id, dedupeKey: event.dedupeKey });
|
|
16364
|
+
if (existing) {
|
|
16365
|
+
return {
|
|
16366
|
+
event: existing,
|
|
16367
|
+
stored: false,
|
|
16368
|
+
deduped: true,
|
|
16369
|
+
identity: { id: existing.id, dedupeKey: existing.dedupeKey }
|
|
16370
|
+
};
|
|
16371
|
+
}
|
|
16372
|
+
}
|
|
16373
|
+
const stored = await this.store.appendEvent(event);
|
|
16374
|
+
return {
|
|
16375
|
+
event: stored,
|
|
16376
|
+
stored: true,
|
|
16377
|
+
deduped: false,
|
|
16378
|
+
identity: { id: stored.id, dedupeKey: stored.dedupeKey }
|
|
16379
|
+
};
|
|
15832
16380
|
}
|
|
15833
16381
|
async applyRedaction(event, channel) {
|
|
15834
16382
|
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
@@ -15855,15 +16403,6 @@ class EventsClient {
|
|
|
15855
16403
|
return createDeliveryResult(event, channel, attempts);
|
|
15856
16404
|
}
|
|
15857
16405
|
}
|
|
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
16406
|
function sanitizeChannelForOutput(channel) {
|
|
15868
16407
|
const copy = structuredClone(channel);
|
|
15869
16408
|
if (copy.webhook?.secret)
|
|
@@ -15876,34 +16415,19 @@ function sanitizeChannelForOutput(channel) {
|
|
|
15876
16415
|
function sanitizeChannelsForOutput(channels) {
|
|
15877
16416
|
return channels.map(sanitizeChannelForOutput);
|
|
15878
16417
|
}
|
|
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;
|
|
16418
|
+
function queryClientEvents(events, options) {
|
|
16419
|
+
let rows = events;
|
|
16420
|
+
if (options.eventId)
|
|
16421
|
+
rows = rows.filter((event) => event.id === options.eventId);
|
|
16422
|
+
if (options.source)
|
|
16423
|
+
rows = rows.filter((event) => event.source === options.source);
|
|
16424
|
+
if (options.type)
|
|
16425
|
+
rows = rows.filter((event) => event.type === options.type);
|
|
16426
|
+
if (options.cursor)
|
|
16427
|
+
rows = rows.slice(decodeLocalJsonEventCursor(options.cursor, options));
|
|
16428
|
+
if (options.limit !== undefined)
|
|
16429
|
+
rows = rows.slice(0, normalizeEventPageLimit(options.limit));
|
|
16430
|
+
return rows;
|
|
15907
16431
|
}
|
|
15908
16432
|
function normalizeTime(value) {
|
|
15909
16433
|
if (!value)
|
|
@@ -15917,6 +16441,77 @@ function normalizeRetryPolicy(policy) {
|
|
|
15917
16441
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
15918
16442
|
};
|
|
15919
16443
|
}
|
|
16444
|
+
function parseFieldMatchers(values, label, typed = false) {
|
|
16445
|
+
if (!values?.length)
|
|
16446
|
+
return;
|
|
16447
|
+
const result = {};
|
|
16448
|
+
for (const value of values) {
|
|
16449
|
+
const parsed = parseMatcherExpression(value, label);
|
|
16450
|
+
const path = parsed.path;
|
|
16451
|
+
if (path in result)
|
|
16452
|
+
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
16453
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
16454
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
16455
|
+
}
|
|
16456
|
+
return result;
|
|
16457
|
+
}
|
|
16458
|
+
function parseFilterOptions(options) {
|
|
16459
|
+
const filter2 = {};
|
|
16460
|
+
if (options.source)
|
|
16461
|
+
filter2.source = options.source;
|
|
16462
|
+
if (options.type)
|
|
16463
|
+
filter2.type = options.type;
|
|
16464
|
+
if (options.subject)
|
|
16465
|
+
filter2.subject = options.subject;
|
|
16466
|
+
if (options.severity)
|
|
16467
|
+
filter2.severity = options.severity;
|
|
16468
|
+
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
16469
|
+
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
16470
|
+
if (Object.keys(data).length > 0)
|
|
16471
|
+
filter2.data = data;
|
|
16472
|
+
if (Object.keys(metadata).length > 0)
|
|
16473
|
+
filter2.metadata = metadata;
|
|
16474
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
16475
|
+
}
|
|
16476
|
+
function mergeMatchers(...records) {
|
|
16477
|
+
const result = {};
|
|
16478
|
+
for (const record of records) {
|
|
16479
|
+
if (!record)
|
|
16480
|
+
continue;
|
|
16481
|
+
for (const [path, value] of Object.entries(record)) {
|
|
16482
|
+
if (path in result)
|
|
16483
|
+
throw new Error(`Duplicate filter path: ${path}`);
|
|
16484
|
+
result[path] = value;
|
|
16485
|
+
}
|
|
16486
|
+
}
|
|
16487
|
+
return result;
|
|
16488
|
+
}
|
|
16489
|
+
function parseTypedMatcherValue(value, label) {
|
|
16490
|
+
const parsed = JSON.parse(value);
|
|
16491
|
+
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
16492
|
+
return parsed;
|
|
16493
|
+
}
|
|
16494
|
+
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
16495
|
+
}
|
|
16496
|
+
function parseMatcherExpression(value, label) {
|
|
16497
|
+
const negativeSeparator = value.indexOf("!=");
|
|
16498
|
+
if (negativeSeparator > 0) {
|
|
16499
|
+
return {
|
|
16500
|
+
path: value.slice(0, negativeSeparator),
|
|
16501
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
16502
|
+
negated: true
|
|
16503
|
+
};
|
|
16504
|
+
}
|
|
16505
|
+
const separator = value.indexOf("=");
|
|
16506
|
+
if (separator <= 0)
|
|
16507
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
16508
|
+
return {
|
|
16509
|
+
path: value.slice(0, separator),
|
|
16510
|
+
rawValue: value.slice(separator + 1),
|
|
16511
|
+
negated: false
|
|
16512
|
+
};
|
|
16513
|
+
}
|
|
16514
|
+
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
15920
16515
|
function parseJsonObject(value, fallback) {
|
|
15921
16516
|
if (!value)
|
|
15922
16517
|
return fallback;
|
|
@@ -15938,18 +16533,6 @@ function parseHeaders(values) {
|
|
|
15938
16533
|
}
|
|
15939
16534
|
return headers;
|
|
15940
16535
|
}
|
|
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
16536
|
function createClient(options) {
|
|
15954
16537
|
if (options.createClient)
|
|
15955
16538
|
return options.createClient();
|
|
@@ -15961,22 +16544,30 @@ function print(value, json, text) {
|
|
|
15961
16544
|
else
|
|
15962
16545
|
console.log(text);
|
|
15963
16546
|
}
|
|
16547
|
+
function fail(error, json) {
|
|
16548
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16549
|
+
if (json)
|
|
16550
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
16551
|
+
else
|
|
16552
|
+
console.error(message);
|
|
16553
|
+
process.exitCode = 1;
|
|
16554
|
+
}
|
|
15964
16555
|
function hasJsonOption(options) {
|
|
15965
16556
|
return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
|
|
15966
16557
|
}
|
|
15967
16558
|
function wantsJson(actionOptions, command) {
|
|
15968
16559
|
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
15969
16560
|
}
|
|
15970
|
-
function
|
|
15971
|
-
const
|
|
15972
|
-
|
|
16561
|
+
function registerChannelCommands(program, options) {
|
|
16562
|
+
const channels = program.command(options.channelsCommandName ?? "channels").description("Manage Hasna event channels");
|
|
16563
|
+
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
16564
|
const timestamp = new Date().toISOString();
|
|
15974
16565
|
const channel = {
|
|
15975
16566
|
id: actionOptions.id,
|
|
15976
16567
|
name: actionOptions.name,
|
|
15977
16568
|
enabled: !actionOptions.disabled,
|
|
15978
16569
|
transport: actionOptions.transport,
|
|
15979
|
-
filters:
|
|
16570
|
+
filters: parseFilterOptions(actionOptions),
|
|
15980
16571
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
15981
16572
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
15982
16573
|
createdAt: timestamp,
|
|
@@ -15992,35 +16583,61 @@ function registerWebhookCommands(program, options) {
|
|
|
15992
16583
|
const saved = await createClient(options).addChannel(channel);
|
|
15993
16584
|
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
15994
16585
|
});
|
|
15995
|
-
|
|
15996
|
-
const
|
|
16586
|
+
channels.command("list").description("List configured channels").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16587
|
+
const channels2 = await createClient(options).listChannels();
|
|
15997
16588
|
if (wantsJson(actionOptions, command)) {
|
|
15998
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(
|
|
16589
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
|
|
15999
16590
|
return;
|
|
16000
16591
|
}
|
|
16001
|
-
if (!
|
|
16592
|
+
if (!channels2.length) {
|
|
16002
16593
|
console.log("No channels configured.");
|
|
16003
16594
|
return;
|
|
16004
16595
|
}
|
|
16005
|
-
for (const channel of
|
|
16596
|
+
for (const channel of channels2) {
|
|
16006
16597
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
16007
16598
|
}
|
|
16008
16599
|
});
|
|
16009
|
-
|
|
16600
|
+
channels.command("status").description("Show events channel storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
16601
|
+
const status = await getEventsStatus(options.dataDir);
|
|
16602
|
+
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
16603
|
+
});
|
|
16604
|
+
channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
16010
16605
|
const removed = await createClient(options).removeChannel(id);
|
|
16011
16606
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
16012
16607
|
});
|
|
16013
|
-
|
|
16014
|
-
const
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16608
|
+
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) => {
|
|
16609
|
+
const json = wantsJson(actionOptions, command);
|
|
16610
|
+
try {
|
|
16611
|
+
const result = await createClient(options).testChannel(id, {
|
|
16612
|
+
source: actionOptions.source ?? options.source,
|
|
16613
|
+
type: actionOptions.type,
|
|
16614
|
+
subject: actionOptions.subject ?? id,
|
|
16615
|
+
message: actionOptions.message,
|
|
16616
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
16617
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
16618
|
+
}, { honorFilters: actionOptions.honorFilters });
|
|
16619
|
+
print(result, json, `${result.status}: ${result.channelId}`);
|
|
16620
|
+
} catch (error) {
|
|
16621
|
+
fail(error, json);
|
|
16622
|
+
}
|
|
16623
|
+
});
|
|
16624
|
+
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) => {
|
|
16625
|
+
const json = wantsJson(actionOptions, command);
|
|
16626
|
+
try {
|
|
16627
|
+
const result = await createClient(options).matchChannel(id, {
|
|
16628
|
+
source: actionOptions.source ?? options.source,
|
|
16629
|
+
type: actionOptions.type,
|
|
16630
|
+
subject: actionOptions.subject ?? id,
|
|
16631
|
+
message: actionOptions.message,
|
|
16632
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
16633
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
16634
|
+
});
|
|
16635
|
+
print(result, json, `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
|
|
16636
|
+
} catch (error) {
|
|
16637
|
+
fail(error, json);
|
|
16638
|
+
}
|
|
16022
16639
|
});
|
|
16023
|
-
return
|
|
16640
|
+
return channels;
|
|
16024
16641
|
}
|
|
16025
16642
|
function registerEventCommands(program, options) {
|
|
16026
16643
|
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
@@ -16037,7 +16654,8 @@ function registerEventCommands(program, options) {
|
|
|
16037
16654
|
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
16038
16655
|
print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
16039
16656
|
});
|
|
16040
|
-
|
|
16657
|
+
const defaultListLimit = options.defaultEventListLimit ?? DEFAULT_EVENT_LIST_LIMIT;
|
|
16658
|
+
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
16659
|
let rows = await createClient(options).listEvents();
|
|
16042
16660
|
if (actionOptions.source)
|
|
16043
16661
|
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
@@ -16056,19 +16674,21 @@ function registerEventCommands(program, options) {
|
|
|
16056
16674
|
for (const event of rows)
|
|
16057
16675
|
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
16058
16676
|
});
|
|
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) => {
|
|
16677
|
+
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
16678
|
const result = await createClient(options).replay({
|
|
16061
16679
|
eventId: actionOptions.id,
|
|
16062
16680
|
source: actionOptions.source,
|
|
16063
16681
|
type: actionOptions.type,
|
|
16682
|
+
cursor: actionOptions.cursor,
|
|
16683
|
+
limit: actionOptions.limit,
|
|
16064
16684
|
dryRun: actionOptions.dryRun
|
|
16065
16685
|
});
|
|
16066
|
-
print(result, wantsJson(actionOptions, command),
|
|
16686
|
+
print(result, wantsJson(actionOptions, command), replaySummary(result.events.length, result.deliveries.length, result.nextCursor));
|
|
16067
16687
|
});
|
|
16068
16688
|
return events;
|
|
16069
16689
|
}
|
|
16070
16690
|
function registerEventsCommands(program, options) {
|
|
16071
|
-
|
|
16691
|
+
registerChannelCommands(program, options);
|
|
16072
16692
|
registerEventCommands(program, options);
|
|
16073
16693
|
}
|
|
16074
16694
|
function parseNumber(value) {
|
|
@@ -16081,8 +16701,12 @@ function collectValues(value, previous) {
|
|
|
16081
16701
|
previous.push(value);
|
|
16082
16702
|
return previous;
|
|
16083
16703
|
}
|
|
16704
|
+
function replaySummary(events, deliveries, nextCursor) {
|
|
16705
|
+
const suffix = nextCursor ? `, next cursor: ${nextCursor}` : "";
|
|
16706
|
+
return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
|
|
16707
|
+
}
|
|
16084
16708
|
|
|
16085
|
-
// node_modules/commander/esm.mjs
|
|
16709
|
+
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
|
|
16086
16710
|
var import__ = __toESM(require_commander(), 1);
|
|
16087
16711
|
var {
|
|
16088
16712
|
program,
|
|
@@ -16101,9 +16725,9 @@ var {
|
|
|
16101
16725
|
// src/cli/index.tsx
|
|
16102
16726
|
init_apply();
|
|
16103
16727
|
import chalk from "chalk";
|
|
16104
|
-
import { existsSync as
|
|
16105
|
-
import { homedir as
|
|
16106
|
-
import { basename as basename7, join as
|
|
16728
|
+
import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
|
|
16729
|
+
import { homedir as homedir10 } from "os";
|
|
16730
|
+
import { basename as basename7, join as join19, resolve as resolve13 } from "path";
|
|
16107
16731
|
|
|
16108
16732
|
// src/lib/config-target-identity.ts
|
|
16109
16733
|
init_apply();
|
|
@@ -16150,14 +16774,14 @@ init_redact();
|
|
|
16150
16774
|
// src/lib/export.ts
|
|
16151
16775
|
init_config_store();
|
|
16152
16776
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16153
|
-
import { join as
|
|
16777
|
+
import { join as join13, resolve as resolve8 } from "path";
|
|
16154
16778
|
import { tmpdir } from "os";
|
|
16155
16779
|
async function exportConfigs(outputPath, opts = {}) {
|
|
16156
16780
|
const store = opts.store ?? resolveConfigStore();
|
|
16157
16781
|
const configs = await store.listConfigs(opts.filter);
|
|
16158
|
-
const absOutput =
|
|
16159
|
-
const tmpDir =
|
|
16160
|
-
const contentsDir =
|
|
16782
|
+
const absOutput = resolve8(outputPath);
|
|
16783
|
+
const tmpDir = join13(tmpdir(), `configs-export-${Date.now()}`);
|
|
16784
|
+
const contentsDir = join13(tmpDir, "contents");
|
|
16161
16785
|
try {
|
|
16162
16786
|
mkdirSync4(contentsDir, { recursive: true });
|
|
16163
16787
|
const manifest = {
|
|
@@ -16165,10 +16789,10 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16165
16789
|
exported_at: new Date().toISOString(),
|
|
16166
16790
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
16167
16791
|
};
|
|
16168
|
-
writeFileSync3(
|
|
16792
|
+
writeFileSync3(join13(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
16169
16793
|
for (const config of configs) {
|
|
16170
16794
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
16171
|
-
writeFileSync3(
|
|
16795
|
+
writeFileSync3(join13(contentsDir, fileName), config.content, "utf-8");
|
|
16172
16796
|
}
|
|
16173
16797
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
16174
16798
|
stdout: "pipe",
|
|
@@ -16189,14 +16813,14 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16189
16813
|
|
|
16190
16814
|
// src/lib/import.ts
|
|
16191
16815
|
init_config_store();
|
|
16192
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as
|
|
16193
|
-
import { join as
|
|
16816
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
|
|
16817
|
+
import { join as join14, resolve as resolve9 } from "path";
|
|
16194
16818
|
import { tmpdir as tmpdir2 } from "os";
|
|
16195
16819
|
async function importConfigs(bundlePath, opts = {}) {
|
|
16196
16820
|
const store = opts.store ?? resolveConfigStore();
|
|
16197
16821
|
const conflict = opts.conflict ?? "skip";
|
|
16198
|
-
const absPath =
|
|
16199
|
-
const tmpDir =
|
|
16822
|
+
const absPath = resolve9(bundlePath);
|
|
16823
|
+
const tmpDir = join14(tmpdir2(), `configs-import-${Date.now()}`);
|
|
16200
16824
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
16201
16825
|
try {
|
|
16202
16826
|
mkdirSync5(tmpDir, { recursive: true });
|
|
@@ -16209,15 +16833,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16209
16833
|
const stderr = await new Response(proc.stderr).text();
|
|
16210
16834
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
16211
16835
|
}
|
|
16212
|
-
const manifestPath =
|
|
16836
|
+
const manifestPath = join14(tmpDir, "manifest.json");
|
|
16213
16837
|
if (!existsSync11(manifestPath))
|
|
16214
16838
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
16215
|
-
const manifest = JSON.parse(
|
|
16839
|
+
const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
16216
16840
|
for (const meta of manifest.configs) {
|
|
16217
16841
|
try {
|
|
16218
16842
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
16219
|
-
const contentFile =
|
|
16220
|
-
const content = existsSync11(contentFile) ?
|
|
16843
|
+
const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
16844
|
+
const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
|
|
16221
16845
|
let existing = null;
|
|
16222
16846
|
try {
|
|
16223
16847
|
existing = await store.getConfig(meta.slug);
|
|
@@ -16265,16 +16889,17 @@ init_machine();
|
|
|
16265
16889
|
init_project_context();
|
|
16266
16890
|
init_session_render();
|
|
16267
16891
|
init_cursor_authority();
|
|
16268
|
-
|
|
16892
|
+
init_session_authority();
|
|
16893
|
+
import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
|
|
16269
16894
|
import {
|
|
16270
16895
|
existsSync as existsSync12,
|
|
16271
|
-
lstatSync as
|
|
16896
|
+
lstatSync as lstatSync4,
|
|
16272
16897
|
mkdirSync as mkdirSync6,
|
|
16273
|
-
readFileSync as
|
|
16898
|
+
readFileSync as readFileSync10,
|
|
16274
16899
|
readdirSync as readdirSync3,
|
|
16275
|
-
statSync as
|
|
16900
|
+
statSync as statSync6
|
|
16276
16901
|
} from "fs";
|
|
16277
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as
|
|
16902
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
|
|
16278
16903
|
|
|
16279
16904
|
class SessionApplyError extends Error {
|
|
16280
16905
|
constructor(message) {
|
|
@@ -16295,6 +16920,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16295
16920
|
}
|
|
16296
16921
|
assertCursorAuthorityUnchanged(plan);
|
|
16297
16922
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
16923
|
+
assertClaudeAuthorityStillClear(plan, targetHome);
|
|
16298
16924
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
16299
16925
|
const files = [...payloadFiles, plan.manifestFile];
|
|
16300
16926
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
@@ -16395,6 +17021,15 @@ function assertCursorAuthorityUnchanged(plan) {
|
|
|
16395
17021
|
throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
|
|
16396
17022
|
}
|
|
16397
17023
|
}
|
|
17024
|
+
function assertClaudeAuthorityStillClear(plan, targetHome) {
|
|
17025
|
+
if (plan.tool !== "claude" || plan.targetKind === "blocked")
|
|
17026
|
+
return;
|
|
17027
|
+
const conflicts = detectClaudeAuthorityConflicts(targetHome);
|
|
17028
|
+
if (conflicts.length === 0)
|
|
17029
|
+
return;
|
|
17030
|
+
const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
|
|
17031
|
+
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
17032
|
+
}
|
|
16398
17033
|
function ensureSessionTargetHome(targetHome) {
|
|
16399
17034
|
if (!existsSync12(targetHome))
|
|
16400
17035
|
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
@@ -16402,7 +17037,7 @@ function ensureSessionTargetHome(targetHome) {
|
|
|
16402
17037
|
}
|
|
16403
17038
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16404
17039
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16405
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome,
|
|
17040
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve10(manifestPath)), safeTargetHome) : resolve10(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16406
17041
|
const checkedAt = new Date().toISOString();
|
|
16407
17042
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16408
17043
|
if (!previousManifest) {
|
|
@@ -16429,7 +17064,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16429
17064
|
});
|
|
16430
17065
|
continue;
|
|
16431
17066
|
}
|
|
16432
|
-
const actualSha256 =
|
|
17067
|
+
const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
|
|
16433
17068
|
if (actualSha256 !== file.sha256) {
|
|
16434
17069
|
drifted.push({
|
|
16435
17070
|
path: target,
|
|
@@ -16452,7 +17087,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
16452
17087
|
function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
16453
17088
|
const snapshot = readSessionRenderSnapshot(snapshotPath);
|
|
16454
17089
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16455
|
-
const resolvedSnapshotPath =
|
|
17090
|
+
const resolvedSnapshotPath = resolve10(snapshotPath);
|
|
16456
17091
|
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
16457
17092
|
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
|
|
16458
17093
|
throw new SessionApplyError("Session snapshot must be stored inside its target home.");
|
|
@@ -16570,19 +17205,19 @@ function requiredRestoreHash(file) {
|
|
|
16570
17205
|
return file.previousSha256;
|
|
16571
17206
|
}
|
|
16572
17207
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
16573
|
-
const resolved =
|
|
17208
|
+
const resolved = resolve10(snapshotPath);
|
|
16574
17209
|
if (!existsSync12(resolved))
|
|
16575
17210
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
16576
|
-
const stat =
|
|
17211
|
+
const stat = lstatSync4(resolved);
|
|
16577
17212
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
16578
17213
|
throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
|
|
16579
17214
|
}
|
|
16580
|
-
if (
|
|
17215
|
+
if (statSync6(resolved).size > 32 * 1024 * 1024) {
|
|
16581
17216
|
throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
|
|
16582
17217
|
}
|
|
16583
17218
|
let parsed;
|
|
16584
17219
|
try {
|
|
16585
|
-
parsed = JSON.parse(
|
|
17220
|
+
parsed = JSON.parse(readFileSync10(resolved, "utf8"));
|
|
16586
17221
|
} catch {
|
|
16587
17222
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
16588
17223
|
}
|
|
@@ -16604,7 +17239,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16604
17239
|
const previousManifest = snapshot.previousManifest;
|
|
16605
17240
|
const previousFiles = new Map;
|
|
16606
17241
|
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" ||
|
|
17242
|
+
if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2568(file.content) !== file.sha256) {
|
|
16608
17243
|
throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
|
|
16609
17244
|
}
|
|
16610
17245
|
resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
|
|
@@ -16651,7 +17286,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16651
17286
|
}
|
|
16652
17287
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
16653
17288
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
16654
|
-
const manifestPath =
|
|
17289
|
+
const manifestPath = resolve10(snapshot.manifestPath);
|
|
16655
17290
|
const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
|
|
16656
17291
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
16657
17292
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
@@ -16660,7 +17295,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16660
17295
|
}
|
|
16661
17296
|
let parsedManifest;
|
|
16662
17297
|
try {
|
|
16663
|
-
parsedManifest = JSON.parse(
|
|
17298
|
+
parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
16664
17299
|
} catch {
|
|
16665
17300
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
16666
17301
|
}
|
|
@@ -16668,7 +17303,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
16668
17303
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
|
|
16669
17304
|
}
|
|
16670
17305
|
const appliedManifest = parsedManifest;
|
|
16671
|
-
if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" ||
|
|
17306
|
+
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
17307
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
|
|
16673
17308
|
}
|
|
16674
17309
|
const afterFiles = [];
|
|
@@ -16753,16 +17388,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
16753
17388
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
16754
17389
|
}
|
|
16755
17390
|
for (const entry of readdirSync3(dirname5(snapshotPath))) {
|
|
16756
|
-
const candidatePath =
|
|
16757
|
-
if (candidatePath ===
|
|
17391
|
+
const candidatePath = resolve10(dirname5(snapshotPath), entry);
|
|
17392
|
+
if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
|
|
16758
17393
|
continue;
|
|
16759
|
-
const candidateStat =
|
|
17394
|
+
const candidateStat = lstatSync4(candidatePath);
|
|
16760
17395
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
16761
17396
|
continue;
|
|
16762
17397
|
try {
|
|
16763
|
-
const candidate = JSON.parse(
|
|
17398
|
+
const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
|
|
16764
17399
|
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" &&
|
|
17400
|
+
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
17401
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
16767
17402
|
}
|
|
16768
17403
|
} catch (error) {
|
|
@@ -16820,7 +17455,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16820
17455
|
return "create";
|
|
16821
17456
|
}
|
|
16822
17457
|
if (file.role === "manifest" && previousManifest) {
|
|
16823
|
-
const previousManifestSha256 =
|
|
17458
|
+
const previousManifestSha256 = sha2568(`${JSON.stringify(previousManifest, null, 2)}
|
|
16824
17459
|
`);
|
|
16825
17460
|
if (previousManifestSha256 !== file.sha256) {
|
|
16826
17461
|
throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
|
|
@@ -16831,15 +17466,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
|
|
|
16831
17466
|
}
|
|
16832
17467
|
function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
16833
17468
|
const path = resolveManifestRelativePath(relativePath, targetHome);
|
|
16834
|
-
if (
|
|
17469
|
+
if (resolve10(recordedPath) !== path) {
|
|
16835
17470
|
throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
|
|
16836
17471
|
}
|
|
16837
17472
|
return path;
|
|
16838
17473
|
}
|
|
16839
17474
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
16840
17475
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
16841
|
-
const previousContent = existsSync12(target) ?
|
|
16842
|
-
const previousSha256 = previousContent === null ? null :
|
|
17476
|
+
const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
|
|
17477
|
+
const previousSha256 = previousContent === null ? null : sha2568(previousContent);
|
|
16843
17478
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
16844
17479
|
const changed = previousContent !== file.content;
|
|
16845
17480
|
if (previousContent !== null && !options.force && !previouslyManaged) {
|
|
@@ -16936,8 +17571,8 @@ function planStaleFileResult(file, targetHome, options) {
|
|
|
16936
17571
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
16937
17572
|
if (!existsSync12(target))
|
|
16938
17573
|
return null;
|
|
16939
|
-
const previousContent =
|
|
16940
|
-
const previousSha256 =
|
|
17574
|
+
const previousContent = readFileSync10(target, "utf-8");
|
|
17575
|
+
const previousSha256 = sha2568(previousContent);
|
|
16941
17576
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
16942
17577
|
return {
|
|
16943
17578
|
path: target,
|
|
@@ -16981,19 +17616,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
16981
17616
|
return previousHashes.get(file.relativePath) === previousSha256;
|
|
16982
17617
|
}
|
|
16983
17618
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
16984
|
-
const target =
|
|
17619
|
+
const target = resolve10(targetHome, ...file.relativePath.split("/"));
|
|
16985
17620
|
const rel = relative5(targetHome, target);
|
|
16986
17621
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
16987
17622
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
16988
17623
|
}
|
|
16989
|
-
if (
|
|
17624
|
+
if (resolve10(file.path) !== target) {
|
|
16990
17625
|
throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
|
|
16991
17626
|
}
|
|
16992
17627
|
assertNoSymlinkSegments2(targetHome, target);
|
|
16993
17628
|
return target;
|
|
16994
17629
|
}
|
|
16995
17630
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
16996
|
-
const target =
|
|
17631
|
+
const target = resolve10(targetHome, ...relativePath.split(/[\\/]+/));
|
|
16997
17632
|
const rel = relative5(targetHome, target);
|
|
16998
17633
|
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
|
|
16999
17634
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
@@ -17005,7 +17640,7 @@ function readPreviousManifest(path) {
|
|
|
17005
17640
|
if (!existsSync12(path))
|
|
17006
17641
|
return null;
|
|
17007
17642
|
try {
|
|
17008
|
-
const parsed = JSON.parse(
|
|
17643
|
+
const parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
17009
17644
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
17010
17645
|
return null;
|
|
17011
17646
|
if (!Array.isArray(parsed.files))
|
|
@@ -17046,11 +17681,11 @@ function currentSessionFileHash(path, targetHome) {
|
|
|
17046
17681
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17047
17682
|
if (!existsSync12(path))
|
|
17048
17683
|
return null;
|
|
17049
|
-
const stat =
|
|
17684
|
+
const stat = lstatSync4(path);
|
|
17050
17685
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17051
17686
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
17052
17687
|
}
|
|
17053
|
-
return
|
|
17688
|
+
return sha2568(readFileSync10(path, "utf-8"));
|
|
17054
17689
|
}
|
|
17055
17690
|
function requiredPreviousHash(result) {
|
|
17056
17691
|
if (result.previousSha256 === null) {
|
|
@@ -17060,12 +17695,12 @@ function requiredPreviousHash(result) {
|
|
|
17060
17695
|
}
|
|
17061
17696
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
17062
17697
|
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync12(result.path)).map((result) => {
|
|
17063
|
-
const content =
|
|
17698
|
+
const content = readFileSync10(result.path, "utf-8");
|
|
17064
17699
|
return {
|
|
17065
17700
|
path: result.path,
|
|
17066
17701
|
relativePath: result.relativePath,
|
|
17067
17702
|
role: result.role,
|
|
17068
|
-
sha256:
|
|
17703
|
+
sha256: sha2568(content),
|
|
17069
17704
|
content
|
|
17070
17705
|
};
|
|
17071
17706
|
});
|
|
@@ -17078,7 +17713,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17078
17713
|
};
|
|
17079
17714
|
}
|
|
17080
17715
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
17081
|
-
const snapshotPath =
|
|
17716
|
+
const snapshotPath = resolve10(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
|
|
17082
17717
|
const afterFiles = results.map((result) => {
|
|
17083
17718
|
if (result.action === "conflict") {
|
|
17084
17719
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -17126,12 +17761,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
17126
17761
|
function assertSafeTargetHome(targetHome) {
|
|
17127
17762
|
if (!isAbsolute4(targetHome))
|
|
17128
17763
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
17129
|
-
const normalized =
|
|
17764
|
+
const normalized = resolve10(targetHome);
|
|
17130
17765
|
if (normalized === parse4(normalized).root) {
|
|
17131
17766
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17132
17767
|
}
|
|
17133
17768
|
assertNoSymlinkAncestors2(normalized);
|
|
17134
|
-
if (existsSync12(normalized) &&
|
|
17769
|
+
if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
|
|
17135
17770
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17136
17771
|
}
|
|
17137
17772
|
return normalized;
|
|
@@ -17141,32 +17776,33 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
17141
17776
|
const rel = relative5(root, target);
|
|
17142
17777
|
let current = root;
|
|
17143
17778
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17144
|
-
current =
|
|
17145
|
-
if (existsSync12(current) &&
|
|
17779
|
+
current = join15(current, segment);
|
|
17780
|
+
if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
|
|
17146
17781
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17147
17782
|
}
|
|
17148
17783
|
}
|
|
17149
17784
|
}
|
|
17150
17785
|
function assertNoSymlinkAncestors2(path) {
|
|
17151
|
-
const normalized =
|
|
17786
|
+
const normalized = resolve10(path);
|
|
17152
17787
|
const parsed = parse4(normalized);
|
|
17153
17788
|
let current = parsed.root;
|
|
17154
17789
|
const rel = relative5(parsed.root, normalized);
|
|
17155
17790
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17156
|
-
current =
|
|
17791
|
+
current = join15(current, segment);
|
|
17157
17792
|
if (!existsSync12(current))
|
|
17158
17793
|
return;
|
|
17159
|
-
if (
|
|
17794
|
+
if (lstatSync4(current).isSymbolicLink()) {
|
|
17160
17795
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
17161
17796
|
}
|
|
17162
17797
|
}
|
|
17163
17798
|
}
|
|
17164
|
-
function
|
|
17165
|
-
return
|
|
17799
|
+
function sha2568(content) {
|
|
17800
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
17166
17801
|
}
|
|
17167
17802
|
|
|
17168
17803
|
// src/cli/index.tsx
|
|
17169
17804
|
init_session_render();
|
|
17805
|
+
init_raw_store_root();
|
|
17170
17806
|
init_asset_plan();
|
|
17171
17807
|
init_instruction_graph();
|
|
17172
17808
|
|
|
@@ -17513,34 +18149,433 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
|
|
|
17513
18149
|
|
|
17514
18150
|
// src/cli/index.tsx
|
|
17515
18151
|
init_codewith_shared_todos_storage_standard();
|
|
18152
|
+
|
|
18153
|
+
// src/lib/managed-skill-runtimes.ts
|
|
18154
|
+
import { createHash as createHash9 } from "crypto";
|
|
18155
|
+
import { spawnSync } from "child_process";
|
|
18156
|
+
import {
|
|
18157
|
+
existsSync as existsSync13,
|
|
18158
|
+
lstatSync as lstatSync5,
|
|
18159
|
+
mkdirSync as mkdirSync7,
|
|
18160
|
+
readFileSync as readFileSync11,
|
|
18161
|
+
renameSync as renameSync2,
|
|
18162
|
+
rmSync as rmSync5,
|
|
18163
|
+
writeFileSync as writeFileSync4
|
|
18164
|
+
} from "fs";
|
|
18165
|
+
import { homedir as homedir8 } from "os";
|
|
18166
|
+
import { dirname as dirname6, join as join16, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
|
|
18167
|
+
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
18168
|
+
var INBOX_SKILL_MARKERS = [
|
|
18169
|
+
[".claude", "skills", "inbox", "SKILL.md"],
|
|
18170
|
+
[".codex", "skills", "inbox", "SKILL.md"],
|
|
18171
|
+
[".codewith", "skills", "inbox", "SKILL.md"],
|
|
18172
|
+
[".config", "opencode", "skills", "inbox", "SKILL.md"],
|
|
18173
|
+
[".cursor", "skills", "inbox", "SKILL.md"]
|
|
18174
|
+
];
|
|
18175
|
+
var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
|
|
18176
|
+
function sha2569(content) {
|
|
18177
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
18178
|
+
}
|
|
18179
|
+
function lstatOrNull(path) {
|
|
18180
|
+
try {
|
|
18181
|
+
return lstatSync5(path);
|
|
18182
|
+
} catch {
|
|
18183
|
+
return null;
|
|
18184
|
+
}
|
|
18185
|
+
}
|
|
18186
|
+
function findSymlinkedAncestor(path) {
|
|
18187
|
+
const normalized = resolve11(path);
|
|
18188
|
+
const parsed = parse5(normalized);
|
|
18189
|
+
let current = parsed.root;
|
|
18190
|
+
const rel = relative6(parsed.root, normalized);
|
|
18191
|
+
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18192
|
+
current = join16(current, segment);
|
|
18193
|
+
if (!existsSync13(current))
|
|
18194
|
+
return null;
|
|
18195
|
+
if (lstatSync5(current).isSymbolicLink())
|
|
18196
|
+
return current;
|
|
18197
|
+
}
|
|
18198
|
+
return null;
|
|
18199
|
+
}
|
|
18200
|
+
function assertNoSymlinkAncestors3(path) {
|
|
18201
|
+
const found = findSymlinkedAncestor(path);
|
|
18202
|
+
if (found !== null) {
|
|
18203
|
+
throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
|
|
18204
|
+
}
|
|
18205
|
+
}
|
|
18206
|
+
function packagedInboxSkillPath(explicitPath) {
|
|
18207
|
+
if (explicitPath)
|
|
18208
|
+
return explicitPath;
|
|
18209
|
+
const candidates = [
|
|
18210
|
+
join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18211
|
+
join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18212
|
+
join16(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
18213
|
+
];
|
|
18214
|
+
const found = candidates.find((candidate) => existsSync13(candidate));
|
|
18215
|
+
if (!found) {
|
|
18216
|
+
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
18217
|
+
}
|
|
18218
|
+
return found;
|
|
18219
|
+
}
|
|
18220
|
+
function readCanonicalSkill(explicitPath) {
|
|
18221
|
+
const assetPath = packagedInboxSkillPath(explicitPath);
|
|
18222
|
+
const stat = lstatOrNull(assetPath);
|
|
18223
|
+
if (!stat?.isFile()) {
|
|
18224
|
+
throw new Error("packaged inbox skill contract is not a regular file");
|
|
18225
|
+
}
|
|
18226
|
+
const content = readFileSync11(assetPath, "utf8");
|
|
18227
|
+
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
18228
|
+
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
18229
|
+
}
|
|
18230
|
+
if (!content.includes("There is no separate")) {
|
|
18231
|
+
throw new Error("packaged inbox skill contract does not retire the legacy executable");
|
|
18232
|
+
}
|
|
18233
|
+
return { content, sha256: sha2569(content) };
|
|
18234
|
+
}
|
|
18235
|
+
function runProbe(command, args) {
|
|
18236
|
+
const result = spawnSync(command, args, {
|
|
18237
|
+
encoding: "utf8",
|
|
18238
|
+
timeout: 5000,
|
|
18239
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18240
|
+
});
|
|
18241
|
+
if (result.error || result.status !== 0) {
|
|
18242
|
+
return { ok: false, output: "" };
|
|
18243
|
+
}
|
|
18244
|
+
return {
|
|
18245
|
+
ok: true,
|
|
18246
|
+
output: `${result.stdout ?? ""}
|
|
18247
|
+
${result.stderr ?? ""}`.trim()
|
|
18248
|
+
};
|
|
18249
|
+
}
|
|
18250
|
+
function parseVersion(output) {
|
|
18251
|
+
return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
|
|
18252
|
+
}
|
|
18253
|
+
function compareVersions(left, right) {
|
|
18254
|
+
const a = left.split(".").map(Number);
|
|
18255
|
+
const b = right.split(".").map(Number);
|
|
18256
|
+
for (let i = 0;i < Math.max(a.length, b.length); i++) {
|
|
18257
|
+
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
18258
|
+
if (delta !== 0)
|
|
18259
|
+
return delta;
|
|
18260
|
+
}
|
|
18261
|
+
return 0;
|
|
18262
|
+
}
|
|
18263
|
+
function inspectSkillMarkers(homeDir2) {
|
|
18264
|
+
return INBOX_SKILL_MARKERS.map((parts) => join16(homeDir2, ...parts)).map((path) => {
|
|
18265
|
+
const stat = lstatOrNull(path);
|
|
18266
|
+
if (!stat)
|
|
18267
|
+
return null;
|
|
18268
|
+
if (!stat.isFile()) {
|
|
18269
|
+
return { path, content: null, mode: null, regular: false };
|
|
18270
|
+
}
|
|
18271
|
+
return {
|
|
18272
|
+
path,
|
|
18273
|
+
content: readFileSync11(path, "utf8"),
|
|
18274
|
+
mode: stat.mode & 511,
|
|
18275
|
+
regular: true
|
|
18276
|
+
};
|
|
18277
|
+
}).filter((snapshot) => snapshot !== null);
|
|
18278
|
+
}
|
|
18279
|
+
function inspectInbox(options) {
|
|
18280
|
+
const homeDir2 = options.homeDir ?? homedir8();
|
|
18281
|
+
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
18282
|
+
const snapshots = inspectSkillMarkers(homeDir2);
|
|
18283
|
+
const skillPresent = snapshots.length > 0;
|
|
18284
|
+
let canonicalContent = null;
|
|
18285
|
+
let canonicalSha256 = null;
|
|
18286
|
+
let assetError = null;
|
|
18287
|
+
try {
|
|
18288
|
+
const canonical = readCanonicalSkill(options.assetPath);
|
|
18289
|
+
canonicalContent = canonical.content;
|
|
18290
|
+
canonicalSha256 = canonical.sha256;
|
|
18291
|
+
} catch (error) {
|
|
18292
|
+
assetError = error instanceof Error ? error.message : String(error);
|
|
18293
|
+
}
|
|
18294
|
+
const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
|
|
18295
|
+
const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
|
|
18296
|
+
const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
|
|
18297
|
+
const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
|
|
18298
|
+
const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
|
|
18299
|
+
const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
|
|
18300
|
+
const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
|
|
18301
|
+
const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
|
|
18302
|
+
const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
|
|
18303
|
+
const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
|
|
18304
|
+
const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
|
|
18305
|
+
let reason = "skill not installed";
|
|
18306
|
+
if (skillPresent) {
|
|
18307
|
+
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
18308
|
+
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
|
|
18309
|
+
if (nonRegular)
|
|
18310
|
+
reason = "managed skill target is not a regular file";
|
|
18311
|
+
else if (symlinkAncestor)
|
|
18312
|
+
reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
|
|
18313
|
+
else if (assetError)
|
|
18314
|
+
reason = assetError;
|
|
18315
|
+
else if (!versionProbe.ok)
|
|
18316
|
+
reason = "conversations command unavailable";
|
|
18317
|
+
else if (!runtimeVersion)
|
|
18318
|
+
reason = "conversations version is unreadable";
|
|
18319
|
+
else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
|
|
18320
|
+
reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
|
|
18321
|
+
} else if (!helpProbe.ok)
|
|
18322
|
+
reason = "conversations watch help is unavailable";
|
|
18323
|
+
else if (!supportsFrom || !supportsAll || !supportsFullContent) {
|
|
18324
|
+
const missing = [
|
|
18325
|
+
!supportsFrom ? "--from" : null,
|
|
18326
|
+
!supportsAll ? "--all" : null,
|
|
18327
|
+
!supportsFullContent ? "--full-content" : null
|
|
18328
|
+
].filter((flag) => flag !== null);
|
|
18329
|
+
reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
|
|
18330
|
+
} else if (staleMarkers.length > 0)
|
|
18331
|
+
reason = "skill contract stale";
|
|
18332
|
+
else if (hostedHeartbeat === "failed")
|
|
18333
|
+
reason = "hosted heartbeat failed; manual fallback required";
|
|
18334
|
+
else if (hostedHeartbeat === "unverified")
|
|
18335
|
+
reason = "hosted heartbeat unverified; manual fallback required";
|
|
18336
|
+
else if (!deliveryVerified)
|
|
18337
|
+
reason = "hosted heartbeat passed; channel and DM delivery verification required";
|
|
18338
|
+
else
|
|
18339
|
+
reason = "ready";
|
|
18340
|
+
}
|
|
18341
|
+
return {
|
|
18342
|
+
status: {
|
|
18343
|
+
skill: "inbox",
|
|
18344
|
+
runtime: "conversations watch",
|
|
18345
|
+
minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
18346
|
+
skill_present: skillPresent,
|
|
18347
|
+
skill_markers: snapshots.map((snapshot) => snapshot.path),
|
|
18348
|
+
skill_contracts_current: snapshots.length - staleMarkers.length,
|
|
18349
|
+
stale_skill_markers: staleMarkers,
|
|
18350
|
+
expected_skill_sha256: canonicalSha256,
|
|
18351
|
+
runtime_command: runtimeCommand,
|
|
18352
|
+
runtime_present: versionProbe.ok,
|
|
18353
|
+
runtime_version: runtimeVersion,
|
|
18354
|
+
watch_supports_from: supportsFrom,
|
|
18355
|
+
watch_supports_all: supportsAll,
|
|
18356
|
+
watch_supports_full_content: supportsFullContent,
|
|
18357
|
+
hosted_heartbeat: hostedHeartbeat,
|
|
18358
|
+
delivery_verified: deliveryVerified,
|
|
18359
|
+
manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
|
|
18360
|
+
healthy: !skillPresent || reason === "ready",
|
|
18361
|
+
reason
|
|
18362
|
+
},
|
|
18363
|
+
canonicalContent,
|
|
18364
|
+
snapshots
|
|
18365
|
+
};
|
|
18366
|
+
}
|
|
18367
|
+
function inspectManagedSkillRuntimes(options = {}) {
|
|
18368
|
+
const runtime = inspectInbox(options).status;
|
|
18369
|
+
const installed = runtime.skill_present ? [runtime] : [];
|
|
18370
|
+
return {
|
|
18371
|
+
runtimes: [runtime],
|
|
18372
|
+
skills_present: installed.length,
|
|
18373
|
+
healthy: installed.filter((item) => item.healthy).length,
|
|
18374
|
+
missing: installed.filter((item) => !item.healthy).length
|
|
18375
|
+
};
|
|
18376
|
+
}
|
|
18377
|
+
function runtimeReadyForWrite(status) {
|
|
18378
|
+
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;
|
|
18379
|
+
}
|
|
18380
|
+
function projectUpdatedStatus(status, contractCount) {
|
|
18381
|
+
const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
|
|
18382
|
+
return {
|
|
18383
|
+
...status,
|
|
18384
|
+
skill_contracts_current: contractCount,
|
|
18385
|
+
stale_skill_markers: [],
|
|
18386
|
+
manual_fallback_ready: true,
|
|
18387
|
+
healthy,
|
|
18388
|
+
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"
|
|
18389
|
+
};
|
|
18390
|
+
}
|
|
18391
|
+
function cleanup(path) {
|
|
18392
|
+
rmSync5(path, { force: true });
|
|
18393
|
+
}
|
|
18394
|
+
function writeAtomic(path, content, mode) {
|
|
18395
|
+
assertNoSymlinkAncestors3(dirname6(path));
|
|
18396
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
18397
|
+
try {
|
|
18398
|
+
mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
|
|
18399
|
+
writeFileSync4(tempPath, content, { mode, flag: "wx" });
|
|
18400
|
+
renameSync2(tempPath, path);
|
|
18401
|
+
} finally {
|
|
18402
|
+
cleanup(tempPath);
|
|
18403
|
+
}
|
|
18404
|
+
}
|
|
18405
|
+
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
18406
|
+
lstat: lstatOrNull,
|
|
18407
|
+
read: (path) => readFileSync11(path, "utf8"),
|
|
18408
|
+
write: writeAtomic
|
|
18409
|
+
};
|
|
18410
|
+
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
18411
|
+
const written = [];
|
|
18412
|
+
try {
|
|
18413
|
+
for (const snapshot of snapshots) {
|
|
18414
|
+
const currentStat = fileOperations.lstat(snapshot.path);
|
|
18415
|
+
if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
|
|
18416
|
+
throw new Error("managed skill changed after inspection; refusing a stale write");
|
|
18417
|
+
}
|
|
18418
|
+
fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
|
|
18419
|
+
written.push(snapshot);
|
|
18420
|
+
}
|
|
18421
|
+
return { ok: true, error: null, rollback_conflicts: [] };
|
|
18422
|
+
} catch (error) {
|
|
18423
|
+
const rollbackConflicts = [];
|
|
18424
|
+
for (const snapshot of written.reverse()) {
|
|
18425
|
+
const currentStat = fileOperations.lstat(snapshot.path);
|
|
18426
|
+
if (!currentStat?.isFile()) {
|
|
18427
|
+
rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
|
|
18428
|
+
continue;
|
|
18429
|
+
}
|
|
18430
|
+
let currentContent;
|
|
18431
|
+
try {
|
|
18432
|
+
currentContent = fileOperations.read(snapshot.path);
|
|
18433
|
+
} catch {
|
|
18434
|
+
rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
|
|
18435
|
+
continue;
|
|
18436
|
+
}
|
|
18437
|
+
if (currentContent !== canonicalContent) {
|
|
18438
|
+
rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
|
|
18439
|
+
continue;
|
|
18440
|
+
}
|
|
18441
|
+
try {
|
|
18442
|
+
fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
|
|
18443
|
+
} catch {
|
|
18444
|
+
rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
|
|
18445
|
+
}
|
|
18446
|
+
}
|
|
18447
|
+
return {
|
|
18448
|
+
ok: false,
|
|
18449
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18450
|
+
rollback_conflicts: rollbackConflicts
|
|
18451
|
+
};
|
|
18452
|
+
}
|
|
18453
|
+
}
|
|
18454
|
+
async function reconcileManagedSkillRuntimes(options = {}) {
|
|
18455
|
+
const dryRun = options.dryRun ?? false;
|
|
18456
|
+
const before = inspectInbox(options);
|
|
18457
|
+
const status = before.status;
|
|
18458
|
+
if (!status.skill_present) {
|
|
18459
|
+
return {
|
|
18460
|
+
runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18461
|
+
changed: 0,
|
|
18462
|
+
failed: 0,
|
|
18463
|
+
dry_run: dryRun
|
|
18464
|
+
};
|
|
18465
|
+
}
|
|
18466
|
+
if (before.snapshots.some((snapshot) => !snapshot.regular)) {
|
|
18467
|
+
return {
|
|
18468
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18469
|
+
changed: 0,
|
|
18470
|
+
failed: 1,
|
|
18471
|
+
dry_run: dryRun
|
|
18472
|
+
};
|
|
18473
|
+
}
|
|
18474
|
+
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
|
|
18475
|
+
if (symlinkedAncestor) {
|
|
18476
|
+
return {
|
|
18477
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18478
|
+
changed: 0,
|
|
18479
|
+
failed: 1,
|
|
18480
|
+
dry_run: dryRun
|
|
18481
|
+
};
|
|
18482
|
+
}
|
|
18483
|
+
if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
|
|
18484
|
+
return {
|
|
18485
|
+
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18486
|
+
changed: 0,
|
|
18487
|
+
failed: 1,
|
|
18488
|
+
dry_run: dryRun
|
|
18489
|
+
};
|
|
18490
|
+
}
|
|
18491
|
+
const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
|
|
18492
|
+
if (staleSnapshots.length === 0) {
|
|
18493
|
+
return {
|
|
18494
|
+
runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
18495
|
+
changed: 0,
|
|
18496
|
+
failed: 0,
|
|
18497
|
+
dry_run: dryRun
|
|
18498
|
+
};
|
|
18499
|
+
}
|
|
18500
|
+
if (dryRun) {
|
|
18501
|
+
const projected = projectUpdatedStatus(status, before.snapshots.length);
|
|
18502
|
+
return {
|
|
18503
|
+
runtimes: [{
|
|
18504
|
+
...projected,
|
|
18505
|
+
action: "update",
|
|
18506
|
+
dry_run: true,
|
|
18507
|
+
skill_contracts_changed: staleSnapshots.length
|
|
18508
|
+
}],
|
|
18509
|
+
changed: 1,
|
|
18510
|
+
failed: 0,
|
|
18511
|
+
dry_run: true
|
|
18512
|
+
};
|
|
18513
|
+
}
|
|
18514
|
+
const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
|
|
18515
|
+
path: snapshot.path,
|
|
18516
|
+
content: snapshot.content,
|
|
18517
|
+
mode: snapshot.mode ?? 420
|
|
18518
|
+
})), before.canonicalContent);
|
|
18519
|
+
if (!transaction.ok) {
|
|
18520
|
+
const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
|
|
18521
|
+
const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
|
|
18522
|
+
return {
|
|
18523
|
+
runtimes: [{
|
|
18524
|
+
...status,
|
|
18525
|
+
action: "failed",
|
|
18526
|
+
dry_run: false,
|
|
18527
|
+
skill_contracts_changed: 0,
|
|
18528
|
+
reason
|
|
18529
|
+
}],
|
|
18530
|
+
changed: 0,
|
|
18531
|
+
failed: 1,
|
|
18532
|
+
dry_run: false
|
|
18533
|
+
};
|
|
18534
|
+
}
|
|
18535
|
+
const after = inspectInbox(options).status;
|
|
18536
|
+
const accepted = after.healthy || after.manual_fallback_ready;
|
|
18537
|
+
return {
|
|
18538
|
+
runtimes: [{
|
|
18539
|
+
...after,
|
|
18540
|
+
action: accepted ? "update" : "failed",
|
|
18541
|
+
dry_run: false,
|
|
18542
|
+
skill_contracts_changed: accepted ? staleSnapshots.length : 0
|
|
18543
|
+
}],
|
|
18544
|
+
changed: accepted ? 1 : 0,
|
|
18545
|
+
failed: accepted ? 0 : 1,
|
|
18546
|
+
dry_run: false
|
|
18547
|
+
};
|
|
18548
|
+
}
|
|
18549
|
+
|
|
18550
|
+
// src/cli/index.tsx
|
|
17516
18551
|
init_project_context();
|
|
17517
18552
|
|
|
17518
18553
|
// src/status.ts
|
|
17519
18554
|
init_config_store();
|
|
17520
18555
|
init_apply();
|
|
17521
18556
|
init_config_agents();
|
|
17522
|
-
import { existsSync as
|
|
18557
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
17523
18558
|
|
|
17524
18559
|
// src/lib/package-version.ts
|
|
17525
|
-
import { existsSync as
|
|
17526
|
-
import { dirname as
|
|
18560
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
18561
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
17527
18562
|
import { fileURLToPath } from "url";
|
|
17528
18563
|
var cached = null;
|
|
17529
18564
|
function getPackageVersion() {
|
|
17530
18565
|
if (cached)
|
|
17531
18566
|
return cached;
|
|
17532
18567
|
try {
|
|
17533
|
-
let dir =
|
|
18568
|
+
let dir = dirname7(fileURLToPath(import.meta.url));
|
|
17534
18569
|
for (let i = 0;i < 8; i++) {
|
|
17535
|
-
const pkgPath =
|
|
17536
|
-
if (
|
|
17537
|
-
const pkg = JSON.parse(
|
|
18570
|
+
const pkgPath = join17(dir, "package.json");
|
|
18571
|
+
if (existsSync14(pkgPath)) {
|
|
18572
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
17538
18573
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
17539
18574
|
cached = pkg.version;
|
|
17540
18575
|
return cached;
|
|
17541
18576
|
}
|
|
17542
18577
|
}
|
|
17543
|
-
const parent =
|
|
18578
|
+
const parent = dirname7(dir);
|
|
17544
18579
|
if (parent === dir)
|
|
17545
18580
|
break;
|
|
17546
18581
|
dir = parent;
|
|
@@ -17573,7 +18608,7 @@ function countBy(items, getValue) {
|
|
|
17573
18608
|
}
|
|
17574
18609
|
return counts;
|
|
17575
18610
|
}
|
|
17576
|
-
async function getConfigsStatus(store = resolveConfigStore()) {
|
|
18611
|
+
async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
17577
18612
|
let databaseReachable = true;
|
|
17578
18613
|
let configs = [];
|
|
17579
18614
|
let categoryStats = { total: 0 };
|
|
@@ -17597,11 +18632,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17597
18632
|
continue;
|
|
17598
18633
|
knownTargets += 1;
|
|
17599
18634
|
const targetPath = expandPath(config.target_path);
|
|
17600
|
-
if (!
|
|
18635
|
+
if (!existsSync15(targetPath)) {
|
|
17601
18636
|
missingTargets += 1;
|
|
17602
18637
|
continue;
|
|
17603
18638
|
}
|
|
17604
|
-
const disk =
|
|
18639
|
+
const disk = readFileSync13(targetPath, "utf-8");
|
|
17605
18640
|
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
17606
18641
|
if (redactedDisk !== config.content) {
|
|
17607
18642
|
driftedTargets += 1;
|
|
@@ -17627,7 +18662,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17627
18662
|
}
|
|
17628
18663
|
}
|
|
17629
18664
|
const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
|
|
17630
|
-
const
|
|
18665
|
+
const managedSkillRuntimes = inspectManagedSkillRuntimes({
|
|
18666
|
+
homeDir: options.homeDir,
|
|
18667
|
+
conversationsCommand: options.conversationsCommand
|
|
18668
|
+
});
|
|
18669
|
+
const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 && managedSkillRuntimes.missing === 0 ? "ok" : "warn";
|
|
17631
18670
|
return {
|
|
17632
18671
|
service: "configs",
|
|
17633
18672
|
schemaVersion: "1.0",
|
|
@@ -17657,7 +18696,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17657
18696
|
profileLinks,
|
|
17658
18697
|
machines,
|
|
17659
18698
|
snapshots,
|
|
17660
|
-
knownTargets
|
|
18699
|
+
knownTargets,
|
|
18700
|
+
managedSkillRuntimes: {
|
|
18701
|
+
skillsPresent: managedSkillRuntimes.skills_present,
|
|
18702
|
+
healthy: managedSkillRuntimes.healthy,
|
|
18703
|
+
missing: managedSkillRuntimes.missing
|
|
18704
|
+
}
|
|
17661
18705
|
},
|
|
17662
18706
|
health: {
|
|
17663
18707
|
status,
|
|
@@ -17666,10 +18710,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
|
|
|
17666
18710
|
missingTargets,
|
|
17667
18711
|
unredactedSecretFindings,
|
|
17668
18712
|
retiredAgentRows,
|
|
18713
|
+
missingManagedSkillRuntimes: managedSkillRuntimes.missing,
|
|
17669
18714
|
hasDrift: driftedTargets > 0,
|
|
17670
18715
|
hasMissingTargets: missingTargets > 0,
|
|
17671
18716
|
hasUnredactedSecrets: unredactedSecretFindings > 0,
|
|
17672
|
-
hasRetiredAgentRows: retiredAgentRows > 0
|
|
18717
|
+
hasRetiredAgentRows: retiredAgentRows > 0,
|
|
18718
|
+
hasMissingManagedSkillRuntimes: managedSkillRuntimes.missing > 0
|
|
17673
18719
|
},
|
|
17674
18720
|
safety: {
|
|
17675
18721
|
includesConfigValues: false,
|
|
@@ -17711,6 +18757,17 @@ function printLine(text = "") {
|
|
|
17711
18757
|
function printJson(value) {
|
|
17712
18758
|
printLine(JSON.stringify(value, null, 2));
|
|
17713
18759
|
}
|
|
18760
|
+
function printManagedSkillRuntimeReport(report) {
|
|
18761
|
+
for (const runtime of report.runtimes) {
|
|
18762
|
+
if (!runtime.skill_present)
|
|
18763
|
+
continue;
|
|
18764
|
+
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");
|
|
18765
|
+
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
|
|
18766
|
+
if (runtime.action === "update") {
|
|
18767
|
+
console.log(chalk.dim(` skill contracts: ${runtime.skill_contracts_changed}`));
|
|
18768
|
+
}
|
|
18769
|
+
}
|
|
18770
|
+
}
|
|
17714
18771
|
function fmtConfig(c, format) {
|
|
17715
18772
|
if (format === "json")
|
|
17716
18773
|
return JSON.stringify(c, null, 2);
|
|
@@ -17777,7 +18834,7 @@ function parseSessionSource(value, order) {
|
|
|
17777
18834
|
if (!path)
|
|
17778
18835
|
throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
|
|
17779
18836
|
const absPath = resolveSessionPath(path);
|
|
17780
|
-
if (!
|
|
18837
|
+
if (!existsSync17(absPath))
|
|
17781
18838
|
throw new Error(`Instruction source file not found: ${absPath}`);
|
|
17782
18839
|
const content = readSessionInstructionSourceFile(absPath);
|
|
17783
18840
|
const source = sourceFromFilePath(absPath, content, order);
|
|
@@ -17821,7 +18878,7 @@ function sessionSourceReplacements(values) {
|
|
|
17821
18878
|
return replacements;
|
|
17822
18879
|
}
|
|
17823
18880
|
function readSessionInstructionSourceFile(path) {
|
|
17824
|
-
const stat =
|
|
18881
|
+
const stat = lstatSync7(path);
|
|
17825
18882
|
if (stat.isSymbolicLink()) {
|
|
17826
18883
|
throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
|
|
17827
18884
|
}
|
|
@@ -17831,7 +18888,7 @@ function readSessionInstructionSourceFile(path) {
|
|
|
17831
18888
|
if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
|
|
17832
18889
|
throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
|
|
17833
18890
|
}
|
|
17834
|
-
return
|
|
18891
|
+
return readFileSync15(path, "utf-8");
|
|
17835
18892
|
}
|
|
17836
18893
|
function parseLayeredReference(value) {
|
|
17837
18894
|
const trimmed = value.trim();
|
|
@@ -17858,9 +18915,9 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
17858
18915
|
}
|
|
17859
18916
|
for (const value of opts.identityExport ?? []) {
|
|
17860
18917
|
const path = resolveSessionPath(value);
|
|
17861
|
-
if (!
|
|
18918
|
+
if (!existsSync17(path))
|
|
17862
18919
|
throw new Error(`Identity instruction export not found: ${path}`);
|
|
17863
|
-
const parsed = JSON.parse(
|
|
18920
|
+
const parsed = JSON.parse(readFileSync15(path, "utf-8"));
|
|
17864
18921
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
17865
18922
|
}
|
|
17866
18923
|
return sources.map((source) => {
|
|
@@ -17981,19 +19038,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
|
|
|
17981
19038
|
if (value === "-")
|
|
17982
19039
|
return { json: readBoundedProjectContextStdin() };
|
|
17983
19040
|
const path = resolveSessionPath(value);
|
|
17984
|
-
if (!
|
|
19041
|
+
if (!existsSync17(path)) {
|
|
17985
19042
|
if (allowMissing)
|
|
17986
19043
|
return {};
|
|
17987
19044
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
|
|
17988
19045
|
}
|
|
17989
|
-
const stat =
|
|
19046
|
+
const stat = lstatSync7(path);
|
|
17990
19047
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17991
19048
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
|
|
17992
19049
|
}
|
|
17993
19050
|
if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
|
|
17994
19051
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
|
|
17995
19052
|
}
|
|
17996
|
-
return { json:
|
|
19053
|
+
return { json: readFileSync15(path, "utf8"), sourcePath: path };
|
|
17997
19054
|
}
|
|
17998
19055
|
function readBoundedProjectContextStdin() {
|
|
17999
19056
|
const chunks = [];
|
|
@@ -18152,15 +19209,15 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
|
|
|
18152
19209
|
console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
|
|
18153
19210
|
});
|
|
18154
19211
|
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 (!
|
|
19212
|
+
const abs = resolve13(filePath);
|
|
19213
|
+
if (!existsSync17(abs)) {
|
|
18157
19214
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
18158
19215
|
process.exit(1);
|
|
18159
19216
|
}
|
|
18160
|
-
const rawContent =
|
|
19217
|
+
const rawContent = readFileSync15(abs, "utf-8");
|
|
18161
19218
|
const fmt = detectFormat(abs);
|
|
18162
19219
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
18163
|
-
const targetPath = abs.startsWith(
|
|
19220
|
+
const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
|
|
18164
19221
|
const name = opts.name || filePath.split("/").pop();
|
|
18165
19222
|
const store = resolveConfigStore();
|
|
18166
19223
|
const allConfigs = await store.listConfigs();
|
|
@@ -18335,7 +19392,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
18335
19392
|
for (const entry of entries) {
|
|
18336
19393
|
if (!entry.isDirectory())
|
|
18337
19394
|
continue;
|
|
18338
|
-
const projDir =
|
|
19395
|
+
const projDir = join19(absDir, entry.name);
|
|
18339
19396
|
const hasAgentConfig = [
|
|
18340
19397
|
"CLAUDE.md",
|
|
18341
19398
|
".mcp.json",
|
|
@@ -18348,7 +19405,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
18348
19405
|
".aicopilot",
|
|
18349
19406
|
".cursor",
|
|
18350
19407
|
".agents"
|
|
18351
|
-
].some((marker) =>
|
|
19408
|
+
].some((marker) => existsSync17(join19(projDir, marker)));
|
|
18352
19409
|
if (!hasAgentConfig)
|
|
18353
19410
|
continue;
|
|
18354
19411
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
|
|
@@ -18399,10 +19456,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
18399
19456
|
});
|
|
18400
19457
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
18401
19458
|
const store = resolveConfigStore();
|
|
18402
|
-
const dbPath =
|
|
19459
|
+
const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
|
|
18403
19460
|
const stats = await store.getConfigStats();
|
|
18404
19461
|
console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
|
|
18405
|
-
console.log(chalk.cyan(
|
|
19462
|
+
console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
|
|
18406
19463
|
console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
|
|
18407
19464
|
console.log();
|
|
18408
19465
|
console.log(chalk.bold("By category:"));
|
|
@@ -18622,7 +19679,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
|
|
|
18622
19679
|
process.exit(1);
|
|
18623
19680
|
}
|
|
18624
19681
|
});
|
|
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) => {
|
|
19682
|
+
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
19683
|
try {
|
|
18627
19684
|
const store = resolveConfigStore();
|
|
18628
19685
|
const { machine, profile } = await getMachineProfileContext(opts, store);
|
|
@@ -18659,6 +19716,14 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
|
|
|
18659
19716
|
}
|
|
18660
19717
|
if (report.failures.length > 0)
|
|
18661
19718
|
process.exitCode = 1;
|
|
19719
|
+
const runtimeReport = await reconcileManagedSkillRuntimes({
|
|
19720
|
+
dryRun: opts.dryRun,
|
|
19721
|
+
agent: opts.from,
|
|
19722
|
+
deliveryVerified: opts.deliveryVerified
|
|
19723
|
+
});
|
|
19724
|
+
printManagedSkillRuntimeReport(runtimeReport);
|
|
19725
|
+
if (runtimeReport.failed > 0)
|
|
19726
|
+
process.exitCode = 1;
|
|
18662
19727
|
console.log(chalk.dim(`
|
|
18663
19728
|
${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
|
|
18664
19729
|
} catch (e) {
|
|
@@ -19146,14 +20211,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
|
|
|
19146
20211
|
} else if (target === "codex") {
|
|
19147
20212
|
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
19148
20213
|
const { join: j } = await import("path");
|
|
19149
|
-
const configPath = j(
|
|
20214
|
+
const configPath = j(homedir10(), ".codex", "config.toml");
|
|
19150
20215
|
const block = `
|
|
19151
20216
|
[mcp_servers.configs]
|
|
19152
20217
|
command = "${mcpBinary}"
|
|
19153
20218
|
args = []
|
|
19154
20219
|
`;
|
|
19155
20220
|
if (ex(configPath)) {
|
|
19156
|
-
const content =
|
|
20221
|
+
const content = readFileSync15(configPath, "utf-8");
|
|
19157
20222
|
if (content.includes("[mcp_servers.configs]")) {
|
|
19158
20223
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
19159
20224
|
continue;
|
|
@@ -19164,7 +20229,7 @@ args = []
|
|
|
19164
20229
|
} else if (target === "antigravity") {
|
|
19165
20230
|
const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
19166
20231
|
const { dirname: dn, join: j } = await import("path");
|
|
19167
|
-
const configPath = j(
|
|
20232
|
+
const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
|
|
19168
20233
|
let settings = {};
|
|
19169
20234
|
if (ex(configPath)) {
|
|
19170
20235
|
try {
|
|
@@ -19248,9 +20313,9 @@ DB stats:`));
|
|
|
19248
20313
|
if (count > 0)
|
|
19249
20314
|
console.log(` ${key.padEnd(18)} ${count}`);
|
|
19250
20315
|
}
|
|
19251
|
-
const location =
|
|
20316
|
+
const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
|
|
19252
20317
|
console.log(chalk.dim(`
|
|
19253
|
-
${
|
|
20318
|
+
${isApiTransport() ? "API" : "DB"}: ${location}`));
|
|
19254
20319
|
});
|
|
19255
20320
|
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
|
|
19256
20321
|
const status = await getConfigsStatus(resolveConfigStore());
|
|
@@ -19266,14 +20331,53 @@ program.command("status").description("Health check: total configs, drift from d
|
|
|
19266
20331
|
console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
|
|
19267
20332
|
console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
|
|
19268
20333
|
console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
|
|
20334
|
+
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
20335
|
console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
|
|
19270
20336
|
});
|
|
20337
|
+
var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
|
|
20338
|
+
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) => {
|
|
20339
|
+
const report = inspectManagedSkillRuntimes({
|
|
20340
|
+
agent: opts.from,
|
|
20341
|
+
deliveryVerified: opts.deliveryVerified
|
|
20342
|
+
});
|
|
20343
|
+
if (opts.json) {
|
|
20344
|
+
printJson(report);
|
|
20345
|
+
if (report.missing > 0)
|
|
20346
|
+
process.exitCode = 1;
|
|
20347
|
+
return;
|
|
20348
|
+
}
|
|
20349
|
+
if (report.skills_present === 0) {
|
|
20350
|
+
console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
|
|
20351
|
+
return;
|
|
20352
|
+
}
|
|
20353
|
+
for (const runtime of report.runtimes) {
|
|
20354
|
+
if (!runtime.skill_present)
|
|
20355
|
+
continue;
|
|
20356
|
+
const prefix = runtime.healthy ? chalk.green("\u2713") : chalk.yellow("!");
|
|
20357
|
+
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
|
|
20358
|
+
}
|
|
20359
|
+
if (report.missing > 0)
|
|
20360
|
+
process.exitCode = 1;
|
|
20361
|
+
});
|
|
20362
|
+
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) => {
|
|
20363
|
+
const report = await reconcileManagedSkillRuntimes({
|
|
20364
|
+
dryRun: opts.dryRun,
|
|
20365
|
+
agent: opts.from,
|
|
20366
|
+
deliveryVerified: opts.deliveryVerified
|
|
20367
|
+
});
|
|
20368
|
+
if (opts.json)
|
|
20369
|
+
printJson(report);
|
|
20370
|
+
else
|
|
20371
|
+
printManagedSkillRuntimeReport(report);
|
|
20372
|
+
if (report.failed > 0)
|
|
20373
|
+
process.exitCode = 1;
|
|
20374
|
+
});
|
|
19271
20375
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
19272
20376
|
const { mkdirSync: mk } = await import("fs");
|
|
19273
|
-
const backupDir =
|
|
20377
|
+
const backupDir = join19(getRawStoreRoot(), "backups");
|
|
19274
20378
|
mk(backupDir, { recursive: true });
|
|
19275
20379
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
19276
|
-
const outPath =
|
|
20380
|
+
const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
|
|
19277
20381
|
const result = await exportConfigs(outPath, { store: resolveConfigStore() });
|
|
19278
20382
|
const { statSync: st } = await import("fs");
|
|
19279
20383
|
const size = st(outPath).size;
|
|
@@ -19291,7 +20395,7 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
19291
20395
|
const store = resolveConfigStore();
|
|
19292
20396
|
let issues = 0;
|
|
19293
20397
|
const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
|
|
19294
|
-
const
|
|
20398
|
+
const fail2 = (msg) => {
|
|
19295
20399
|
issues++;
|
|
19296
20400
|
console.log(chalk.red(" \u2717 ") + msg);
|
|
19297
20401
|
};
|
|
@@ -19301,9 +20405,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
19301
20405
|
console.log(chalk.cyan("Known files on disk:"));
|
|
19302
20406
|
for (const k of KNOWN_CONFIGS) {
|
|
19303
20407
|
if (k.rulesDir) {
|
|
19304
|
-
|
|
20408
|
+
existsSync17(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
|
|
19305
20409
|
} else {
|
|
19306
|
-
|
|
20410
|
+
existsSync17(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
|
|
19307
20411
|
}
|
|
19308
20412
|
}
|
|
19309
20413
|
const allConfigs = await store.listConfigs();
|
|
@@ -19316,7 +20420,7 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19316
20420
|
JSON.parse(c.content);
|
|
19317
20421
|
validCount++;
|
|
19318
20422
|
} catch {
|
|
19319
|
-
|
|
20423
|
+
fail2(`${c.slug}: invalid JSON`);
|
|
19320
20424
|
}
|
|
19321
20425
|
} else {
|
|
19322
20426
|
validCount++;
|
|
@@ -19328,13 +20432,13 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19328
20432
|
const found = scanSecrets(c.content, c.format);
|
|
19329
20433
|
secretCount += found.length;
|
|
19330
20434
|
}
|
|
19331
|
-
secretCount === 0 ? pass("No unredacted secrets") :
|
|
20435
|
+
secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
|
|
19332
20436
|
const duplicateTargets = findDuplicateTargetPathGroups(allConfigs);
|
|
19333
20437
|
if (duplicateTargets.length === 0) {
|
|
19334
20438
|
pass("No target path is claimed by more than one config");
|
|
19335
20439
|
} else {
|
|
19336
20440
|
const rowCount = duplicateTargets.reduce((total, group) => total + group.configs.length, 0);
|
|
19337
|
-
|
|
20441
|
+
fail2(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
|
|
19338
20442
|
for (const group of duplicateTargets) {
|
|
19339
20443
|
console.log(chalk.yellow(` ${group.target_path}`));
|
|
19340
20444
|
for (const c of group.configs) {
|
|
@@ -19348,7 +20452,7 @@ Stored configs (${allConfigs.length}):`));
|
|
|
19348
20452
|
pass("No reference config name is claimed by more than one row");
|
|
19349
20453
|
} else {
|
|
19350
20454
|
const rowCount = duplicateReferenceNames.reduce((total, group) => total + group.configs.length, 0);
|
|
19351
|
-
|
|
20455
|
+
fail2(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
|
|
19352
20456
|
for (const group of duplicateReferenceNames) {
|
|
19353
20457
|
console.log(chalk.yellow(` ${group.name}`));
|
|
19354
20458
|
for (const c of group.configs) {
|
|
@@ -19456,16 +20560,16 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19456
20560
|
for (const k of KNOWN_CONFIGS) {
|
|
19457
20561
|
if (k.rulesDir) {
|
|
19458
20562
|
const absDir = expandPath2(k.rulesDir);
|
|
19459
|
-
if (!
|
|
20563
|
+
if (!existsSync17(absDir))
|
|
19460
20564
|
continue;
|
|
19461
20565
|
const { readdirSync: readdirSync5 } = await import("fs");
|
|
19462
20566
|
for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
19463
|
-
const abs =
|
|
20567
|
+
const abs = join19(absDir, f);
|
|
19464
20568
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19465
20569
|
}
|
|
19466
20570
|
} else {
|
|
19467
20571
|
const abs = expandPath2(k.path);
|
|
19468
|
-
if (
|
|
20572
|
+
if (existsSync17(abs))
|
|
19469
20573
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19470
20574
|
}
|
|
19471
20575
|
}
|
|
@@ -19473,7 +20577,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19473
20577
|
const tick = async () => {
|
|
19474
20578
|
let changed = 0;
|
|
19475
20579
|
for (const [abs, oldMtime] of mtimes) {
|
|
19476
|
-
if (!
|
|
20580
|
+
if (!existsSync17(abs))
|
|
19477
20581
|
continue;
|
|
19478
20582
|
const newMtime = st(abs).mtimeMs;
|
|
19479
20583
|
if (newMtime !== oldMtime) {
|
|
@@ -19485,10 +20589,10 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19485
20589
|
for (const k of KNOWN_CONFIGS) {
|
|
19486
20590
|
if (k.rulesDir) {
|
|
19487
20591
|
const absDir = expandPath2(k.rulesDir);
|
|
19488
|
-
if (!
|
|
20592
|
+
if (!existsSync17(absDir))
|
|
19489
20593
|
continue;
|
|
19490
20594
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
19491
|
-
const abs =
|
|
20595
|
+
const abs = join19(absDir, f);
|
|
19492
20596
|
if (!mtimes.has(abs)) {
|
|
19493
20597
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19494
20598
|
changed++;
|
|
@@ -19496,7 +20600,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
19496
20600
|
}
|
|
19497
20601
|
} else {
|
|
19498
20602
|
const abs = expandPath2(k.path);
|
|
19499
|
-
if (
|
|
20603
|
+
if (existsSync17(abs) && !mtimes.has(abs)) {
|
|
19500
20604
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
19501
20605
|
changed++;
|
|
19502
20606
|
}
|
|
@@ -19524,11 +20628,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
19524
20628
|
if (!c.target_path)
|
|
19525
20629
|
continue;
|
|
19526
20630
|
const abs = expandPath(c.target_path);
|
|
19527
|
-
if (!
|
|
20631
|
+
if (!existsSync17(abs)) {
|
|
19528
20632
|
missing++;
|
|
19529
20633
|
continue;
|
|
19530
20634
|
}
|
|
19531
|
-
const disk =
|
|
20635
|
+
const disk = readFileSync15(abs, "utf-8");
|
|
19532
20636
|
const { content: redactedDisk } = redactContent(disk, c.format);
|
|
19533
20637
|
if (redactedDisk !== c.content)
|
|
19534
20638
|
drifted++;
|
|
@@ -19595,7 +20699,7 @@ program.command("clean").description("Remove configs from DB whose target files
|
|
|
19595
20699
|
if (!c.target_path)
|
|
19596
20700
|
continue;
|
|
19597
20701
|
const abs = expandPath(c.target_path);
|
|
19598
|
-
if (!
|
|
20702
|
+
if (!existsSync17(abs)) {
|
|
19599
20703
|
if (printed < maxPrinted) {
|
|
19600
20704
|
if (opts.dryRun) {
|
|
19601
20705
|
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
|
|
@@ -19619,7 +20723,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
|
|
|
19619
20723
|
console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
|
|
19620
20724
|
}
|
|
19621
20725
|
});
|
|
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) => {
|
|
20726
|
+
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
20727
|
const store = resolveConfigStore();
|
|
19624
20728
|
const packages = [
|
|
19625
20729
|
{ name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
|
|
@@ -19682,8 +20786,22 @@ Initializing configs:`));
|
|
|
19682
20786
|
} else {
|
|
19683
20787
|
console.log(chalk.dim(" would run: configs init"));
|
|
19684
20788
|
}
|
|
20789
|
+
console.log(chalk.cyan(`
|
|
20790
|
+
Reconciling managed skill runtimes:`));
|
|
20791
|
+
const runtimeReport = await reconcileManagedSkillRuntimes({
|
|
20792
|
+
dryRun: opts.dryRun,
|
|
20793
|
+
agent: opts.from,
|
|
20794
|
+
deliveryVerified: opts.deliveryVerified
|
|
20795
|
+
});
|
|
20796
|
+
printManagedSkillRuntimeReport(runtimeReport);
|
|
20797
|
+
if (runtimeReport.failed > 0) {
|
|
20798
|
+
console.error(chalk.red(`
|
|
20799
|
+
Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
|
|
20800
|
+
process.exitCode = 1;
|
|
20801
|
+
return;
|
|
20802
|
+
}
|
|
19685
20803
|
console.log(chalk.bold(`
|
|
19686
|
-
\u2713 Bootstrap complete.`) + chalk.dim(" Restart
|
|
20804
|
+
\u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
|
|
19687
20805
|
});
|
|
19688
20806
|
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
20807
|
const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent, store: resolveConfigStore() });
|