@hasna/instructions 0.6.1 → 0.7.0
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 +123 -6
- package/dist/chunks/{apply-ynfn1av7.js → apply-7svah2qm.js} +5 -3
- package/dist/chunks/{apply-sfzja1f3.js → apply-jp13sa1e.js} +6 -3
- package/dist/chunks/database-wstmr56z.js +23 -0
- package/dist/chunks/{index-85n5wnd8.js → index-1cnyjnd7.js} +5 -7
- package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
- package/dist/chunks/{index-v7dkb384.js → index-4pfq42t5.js} +129 -77
- package/dist/chunks/index-5d7zdftc.js +186 -0
- package/dist/chunks/{index-1hpwskwd.js → index-7m4j8a33.js} +205 -329
- package/dist/chunks/index-8rb7ng2y.js +148 -0
- package/dist/chunks/index-arrrx0ed.js +344 -0
- package/dist/chunks/{index-9p9gn7gh.js → index-bzyxvwcd.js} +5 -7
- package/dist/chunks/index-fpeyzn3n.js +186 -0
- package/dist/chunks/{index-axhwg61p.js → index-fwbs6tma.js} +15 -4
- package/dist/chunks/index-n3fjspr0.js +119 -0
- package/dist/chunks/{index-e9epbsvs.js → index-z3ma4aqk.js} +15 -4
- package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
- package/dist/chunks/{local-a9nen65z.js → local-5wvv5xv3.js} +2 -2
- package/dist/chunks/{local-0d8qrc92.js → local-zxa5dpf4.js} +13 -188
- package/dist/chunks/s3-backup-n2z4fwyh.js +240 -0
- package/dist/chunks/s3-config-hhght91k.js +14 -0
- package/dist/chunks/s3-object-store-90tgbh6k.js +14 -0
- package/dist/chunks/{sync-m46fc257.js → sync-2075deve.js} +5 -4
- package/dist/chunks/{sync-vy1yjvs6.js → sync-r8t7zsp3.js} +4 -4
- package/dist/chunks/{template-02wjvv05.js → template-fsfxpe4p.js} +1 -1
- package/dist/chunks/{template-ke972qqe.js → template-wgnzdn7h.js} +1 -1
- package/dist/cli/index.js +1011 -247
- package/dist/data/config-store.d.ts +1 -0
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/index.js +840 -215
- package/dist/lib/apply.d.ts +7 -0
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/compact-output.d.ts +1 -0
- package/dist/lib/compact-output.d.ts.map +1 -1
- package/dist/lib/export.d.ts +22 -3
- package/dist/lib/export.d.ts.map +1 -1
- package/dist/lib/import.d.ts +18 -0
- package/dist/lib/import.d.ts.map +1 -1
- package/dist/lib/legacy-store-migration.d.ts +39 -0
- package/dist/lib/legacy-store-migration.d.ts.map +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
- package/dist/lib/sync-dir.d.ts.map +1 -1
- package/dist/mcp/index.js +31 -20
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +205 -10
- package/dist/sdk/resolve.d.ts +1 -1
- package/dist/sdk/resolve.d.ts.map +1 -1
- package/dist/sdk/v1-client.d.ts +57 -0
- package/dist/sdk/v1-client.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +212 -21
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts +13 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1180 -432
- package/dist/server/openapi.d.ts +1264 -92
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +25 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts +57 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +725 -0
- package/dist/storage/s3-backup.d.ts +76 -0
- package/dist/storage/s3-backup.d.ts.map +1 -0
- package/dist/storage/s3-config.d.ts +46 -0
- package/dist/storage/s3-config.d.ts.map +1 -0
- package/dist/storage/s3-object-store.d.ts +70 -0
- package/dist/storage/s3-object-store.d.ts.map +1 -0
- package/dist/types/index.d.ts +65 -3
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +110 -0
- package/migrations/0001_instructions.sql +63 -0
- package/migrations/0002_api_keys.sql +20 -0
- package/migrations/0003_idempotency_receipts.sql +24 -0
- package/package.json +22 -11
- package/assets/skills/inbox/SKILL.md +0 -86
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/types/index.ts
|
|
19
|
-
var CONFIG_KINDS, CONFIG_CATEGORIES, CONFIG_AGENTS, CONFIG_TRANSFORMS, CONFIG_FORMATS, PROFILE_CONFIG_BINDING_SCHEMA = "hasna.instructions.profile-config-binding/v1", INSTRUCTION_ACTIVATION_MODES, INSTRUCTION_FALLBACKS, PROFILE_ASSET_BINDING_SCHEMA = "hasna.instructions.profile-asset-binding/v1", ASSET_KINDS, ASSET_DESTINATION_STRATEGIES, ASSET_SCOPES, ASSET_UNINSTALL_POLICIES, ASSET_ROLLBACK_POLICIES, ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, TemplateRenderError;
|
|
19
|
+
var CONFIG_KINDS, CONFIG_CATEGORIES, CONFIG_AGENTS, CONFIG_TRANSFORMS, CONFIG_FORMATS, PROFILE_CONFIG_BINDING_SCHEMA = "hasna.instructions.profile-config-binding/v1", INSTRUCTION_ACTIVATION_MODES, INSTRUCTION_FALLBACKS, PROFILE_ASSET_BINDING_SCHEMA = "hasna.instructions.profile-asset-binding/v1", ASSET_KINDS, ASSET_DESTINATION_STRATEGIES, ASSET_SCOPES, ASSET_UNINSTALL_POLICIES, ASSET_ROLLBACK_POLICIES, INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA = "hasna.instructions.domain-archive/v2", ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, TemplateRenderError;
|
|
20
20
|
var init_types = __esm(() => {
|
|
21
21
|
CONFIG_KINDS = ["file", "reference"];
|
|
22
22
|
CONFIG_CATEGORIES = [
|
|
@@ -108,9 +108,9 @@ function parseCursor(value) {
|
|
|
108
108
|
const parsed = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
|
|
109
109
|
if (!Number.isFinite(parsed) || parsed < 0)
|
|
110
110
|
return 0;
|
|
111
|
-
return Math.floor(parsed);
|
|
111
|
+
return Math.min(Math.floor(parsed), MAX_LIST_CURSOR);
|
|
112
112
|
}
|
|
113
|
-
var DEFAULT_LIST_LIMIT = 20, MAX_LIST_LIMIT = 100;
|
|
113
|
+
var DEFAULT_LIST_LIMIT = 20, MAX_LIST_LIMIT = 100, MAX_LIST_CURSOR = 1e5;
|
|
114
114
|
|
|
115
115
|
// src/lib/bounded-read.ts
|
|
116
116
|
function normalizeBoundedReadOptions(options = {}) {
|
|
@@ -10925,7 +10925,7 @@ var init_instruction_graph = __esm(() => {
|
|
|
10925
10925
|
};
|
|
10926
10926
|
});
|
|
10927
10927
|
|
|
10928
|
-
//
|
|
10928
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/transport.js
|
|
10929
10929
|
import { isIP } from "net";
|
|
10930
10930
|
import { spawnSync } from "child_process";
|
|
10931
10931
|
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync5 } from "fs";
|
|
@@ -11658,7 +11658,7 @@ var init_transport = __esm(() => {
|
|
|
11658
11658
|
]);
|
|
11659
11659
|
});
|
|
11660
11660
|
|
|
11661
|
-
//
|
|
11661
|
+
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e8014c875821e0be/node_modules/@hasna/contracts/dist/client/storage.js
|
|
11662
11662
|
import { isIP as isIP2 } from "net";
|
|
11663
11663
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11664
11664
|
import { closeSync as closeSync3, fstatSync as fstatSync3, openSync as openSync3, readFileSync as readFileSync6 } from "fs";
|
|
@@ -13876,11 +13876,11 @@ var init_local = __esm(() => {
|
|
|
13876
13876
|
|
|
13877
13877
|
// src/data/config-store.ts
|
|
13878
13878
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
13879
|
-
function parseBoundedPagePayload(value, label) {
|
|
13879
|
+
function parseBoundedPagePayload(value, label, expected) {
|
|
13880
13880
|
const page = value;
|
|
13881
13881
|
const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
|
|
13882
13882
|
const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
|
|
13883
|
-
if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed)) {
|
|
13883
|
+
if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed) || expected !== undefined && Number(page.cursor) !== expected.cursor) {
|
|
13884
13884
|
throw new Error(`${label} returned an invalid or truncated bounded-read envelope`);
|
|
13885
13885
|
}
|
|
13886
13886
|
return {
|
|
@@ -13892,7 +13892,7 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
|
|
|
13892
13892
|
if (value && typeof value === "object") {
|
|
13893
13893
|
const candidate = value;
|
|
13894
13894
|
if ("items" in candidate || "total" in candidate || "complete" in candidate || "truncated" in candidate || "next_cursor" in candidate) {
|
|
13895
|
-
return parseBoundedPagePayload(value, label);
|
|
13895
|
+
return parseBoundedPagePayload(value, label, normalizeBoundedReadOptions(options));
|
|
13896
13896
|
}
|
|
13897
13897
|
}
|
|
13898
13898
|
if (!Array.isArray(legacyItems)) {
|
|
@@ -13902,6 +13902,57 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
|
|
|
13902
13902
|
const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
|
|
13903
13903
|
return { ...page, source_bounded: false };
|
|
13904
13904
|
}
|
|
13905
|
+
async function aggregateBoundedCollection(label, readPage, identity, options = {}) {
|
|
13906
|
+
let lastError = null;
|
|
13907
|
+
for (let attempt = 0;attempt < COLLECTION_READ_ATTEMPTS; attempt += 1) {
|
|
13908
|
+
try {
|
|
13909
|
+
const items = [];
|
|
13910
|
+
const seen = new Set;
|
|
13911
|
+
let expectedTotal = null;
|
|
13912
|
+
let cursor = 0;
|
|
13913
|
+
let previousIdentity = null;
|
|
13914
|
+
while (true) {
|
|
13915
|
+
const page = await readPage(cursor);
|
|
13916
|
+
if (page.cursor !== cursor) {
|
|
13917
|
+
throw new CollectionChangedWhilePagingError(label, `server returned cursor ${page.cursor} for requested cursor ${cursor}`);
|
|
13918
|
+
}
|
|
13919
|
+
if (expectedTotal === null)
|
|
13920
|
+
expectedTotal = page.total;
|
|
13921
|
+
else if (page.total !== expectedTotal) {
|
|
13922
|
+
throw new CollectionChangedWhilePagingError(label, `total changed from ${expectedTotal} to ${page.total}`);
|
|
13923
|
+
}
|
|
13924
|
+
for (const item of page.items) {
|
|
13925
|
+
const key = identity(item);
|
|
13926
|
+
if (!key)
|
|
13927
|
+
throw new CollectionChangedWhilePagingError(label, "an item had no stable identity");
|
|
13928
|
+
if (seen.has(key))
|
|
13929
|
+
throw new CollectionChangedWhilePagingError(label, `duplicate identity ${key}`);
|
|
13930
|
+
if (options.requireAscendingIdentity && page.source_bounded && previousIdentity !== null && key <= previousIdentity) {
|
|
13931
|
+
throw new CollectionChangedWhilePagingError(label, `identity order was not strictly increasing at ${key}`);
|
|
13932
|
+
}
|
|
13933
|
+
seen.add(key);
|
|
13934
|
+
previousIdentity = key;
|
|
13935
|
+
items.push(item);
|
|
13936
|
+
}
|
|
13937
|
+
if (page.complete) {
|
|
13938
|
+
if (items.length !== expectedTotal) {
|
|
13939
|
+
throw new CollectionChangedWhilePagingError(label, `received ${items.length} unique rows for total ${expectedTotal}`);
|
|
13940
|
+
}
|
|
13941
|
+
return items;
|
|
13942
|
+
}
|
|
13943
|
+
if (page.next_cursor === null || page.next_cursor <= cursor) {
|
|
13944
|
+
throw new CollectionChangedWhilePagingError(label, `cursor did not advance from ${cursor}`);
|
|
13945
|
+
}
|
|
13946
|
+
cursor = page.next_cursor;
|
|
13947
|
+
}
|
|
13948
|
+
} catch (error) {
|
|
13949
|
+
if (!(error instanceof CollectionChangedWhilePagingError))
|
|
13950
|
+
throw error;
|
|
13951
|
+
lastError = error;
|
|
13952
|
+
}
|
|
13953
|
+
}
|
|
13954
|
+
throw lastError ?? new CollectionChangedWhilePagingError(label, "read did not stabilize");
|
|
13955
|
+
}
|
|
13905
13956
|
function isLocalOptIn(env = process.env) {
|
|
13906
13957
|
return isInstructionsLocalOptIn(env);
|
|
13907
13958
|
}
|
|
@@ -13980,15 +14031,7 @@ class LocalConfigStore {
|
|
|
13980
14031
|
return (await localStoreModule()).pruneSnapshots(configId, keep, this.db);
|
|
13981
14032
|
}
|
|
13982
14033
|
async listProfiles() {
|
|
13983
|
-
|
|
13984
|
-
let cursor = 0;
|
|
13985
|
-
while (true) {
|
|
13986
|
-
const page = await this.listProfilesPage({ limit: 100, cursor });
|
|
13987
|
-
profiles.push(...page.items);
|
|
13988
|
-
if (page.complete)
|
|
13989
|
-
return profiles;
|
|
13990
|
-
cursor = page.next_cursor;
|
|
13991
|
-
}
|
|
14034
|
+
return aggregateBoundedCollection("profile list", (cursor) => this.listProfilesPage({ limit: 100, cursor }), (profile) => profile.id);
|
|
13992
14035
|
}
|
|
13993
14036
|
async listProfilesPage(options = {}) {
|
|
13994
14037
|
return (await localStoreModule()).listProfilesPage(options, this.db);
|
|
@@ -13997,15 +14040,7 @@ class LocalConfigStore {
|
|
|
13997
14040
|
return (await localStoreModule()).getProfile(idOrSlug, this.db);
|
|
13998
14041
|
}
|
|
13999
14042
|
async getProfileConfigs(idOrSlug) {
|
|
14000
|
-
|
|
14001
|
-
let cursor = 0;
|
|
14002
|
-
while (true) {
|
|
14003
|
-
const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
|
|
14004
|
-
configs.push(...page.items);
|
|
14005
|
-
if (page.complete)
|
|
14006
|
-
return configs;
|
|
14007
|
-
cursor = page.next_cursor;
|
|
14008
|
-
}
|
|
14043
|
+
return aggregateBoundedCollection("profile membership", (cursor) => this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor }), (config) => config.id);
|
|
14009
14044
|
}
|
|
14010
14045
|
async getProfileConfigsPage(idOrSlug, options = {}) {
|
|
14011
14046
|
return (await localStoreModule()).getProfileConfigsPage(idOrSlug, options, this.db);
|
|
@@ -14086,7 +14121,8 @@ class CloudConfigStore {
|
|
|
14086
14121
|
throw err;
|
|
14087
14122
|
}
|
|
14088
14123
|
}
|
|
14089
|
-
async
|
|
14124
|
+
async listConfigsPageRemote(filter, options) {
|
|
14125
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
14090
14126
|
const params = new URLSearchParams;
|
|
14091
14127
|
if (filter.category)
|
|
14092
14128
|
params.set("category", filter.category);
|
|
@@ -14096,16 +14132,21 @@ class CloudConfigStore {
|
|
|
14096
14132
|
params.set("kind", filter.kind);
|
|
14097
14133
|
if (filter.search)
|
|
14098
14134
|
params.set("search", filter.search);
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14135
|
+
params.set("limit", String(normalized.limit));
|
|
14136
|
+
params.set("cursor", String(normalized.cursor));
|
|
14137
|
+
const { data } = await this.request("GET", `/configs?${params.toString()}`);
|
|
14138
|
+
return parseBoundedOrLegacyPage(data, data?.configs, normalized, "config list");
|
|
14139
|
+
}
|
|
14140
|
+
async listConfigs(filter = {}) {
|
|
14141
|
+
const configs = await aggregateBoundedCollection("config list", (cursor) => this.listConfigsPageRemote(filter, { limit: 100, cursor }), (config) => config.id, { requireAscendingIdentity: true });
|
|
14142
|
+
let filtered = configs;
|
|
14102
14143
|
if (filter.tags && filter.tags.length > 0) {
|
|
14103
|
-
|
|
14144
|
+
filtered = filtered.filter((config) => filter.tags.every((tag) => config.tags.includes(tag)));
|
|
14104
14145
|
}
|
|
14105
14146
|
if (filter.is_template !== undefined) {
|
|
14106
|
-
|
|
14147
|
+
filtered = filtered.filter((config) => config.is_template === filter.is_template);
|
|
14107
14148
|
}
|
|
14108
|
-
return
|
|
14149
|
+
return filtered;
|
|
14109
14150
|
}
|
|
14110
14151
|
async getConfig(idOrSlug) {
|
|
14111
14152
|
const { status, data } = await this.request("GET", `/configs/${encodeURIComponent(idOrSlug)}`, undefined, { allow404: true });
|
|
@@ -14136,8 +14177,11 @@ class CloudConfigStore {
|
|
|
14136
14177
|
return data ?? { total: 0 };
|
|
14137
14178
|
}
|
|
14138
14179
|
async listSnapshots(configId) {
|
|
14139
|
-
|
|
14140
|
-
|
|
14180
|
+
return aggregateBoundedCollection("snapshot list", async (cursor) => {
|
|
14181
|
+
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
14182
|
+
const { data } = await this.request("GET", `/configs/${encodeURIComponent(configId)}/snapshots?limit=${normalized.limit}&cursor=${normalized.cursor}`);
|
|
14183
|
+
return parseBoundedOrLegacyPage(data, data?.snapshots, normalized, "snapshot list");
|
|
14184
|
+
}, (snapshot) => snapshot.id);
|
|
14141
14185
|
}
|
|
14142
14186
|
async getSnapshot(id) {
|
|
14143
14187
|
const { status, data } = await this.request("GET", `/snapshots/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
@@ -14160,15 +14204,7 @@ class CloudConfigStore {
|
|
|
14160
14204
|
return data?.pruned ?? 0;
|
|
14161
14205
|
}
|
|
14162
14206
|
async listProfiles() {
|
|
14163
|
-
|
|
14164
|
-
let cursor = 0;
|
|
14165
|
-
while (true) {
|
|
14166
|
-
const page = await this.listProfilesPage({ limit: 100, cursor });
|
|
14167
|
-
profiles.push(...page.items);
|
|
14168
|
-
if (page.complete)
|
|
14169
|
-
return profiles;
|
|
14170
|
-
cursor = page.next_cursor;
|
|
14171
|
-
}
|
|
14207
|
+
return aggregateBoundedCollection("profile list", (cursor) => this.listProfilesPage({ limit: 100, cursor }), (profile) => profile.id, { requireAscendingIdentity: true });
|
|
14172
14208
|
}
|
|
14173
14209
|
async listProfilesPage(options = {}) {
|
|
14174
14210
|
const normalized = normalizeBoundedReadOptions(options);
|
|
@@ -14192,15 +14228,7 @@ class CloudConfigStore {
|
|
|
14192
14228
|
return profile;
|
|
14193
14229
|
}
|
|
14194
14230
|
async getProfileConfigs(idOrSlug) {
|
|
14195
|
-
|
|
14196
|
-
let cursor = 0;
|
|
14197
|
-
while (true) {
|
|
14198
|
-
const page = await this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor });
|
|
14199
|
-
configs.push(...page.items);
|
|
14200
|
-
if (page.complete)
|
|
14201
|
-
return configs;
|
|
14202
|
-
cursor = page.next_cursor;
|
|
14203
|
-
}
|
|
14231
|
+
return aggregateBoundedCollection("profile membership", (cursor) => this.getProfileConfigsPage(idOrSlug, { limit: 100, cursor }), (config) => config.id);
|
|
14204
14232
|
}
|
|
14205
14233
|
async getProfileConfigsPage(idOrSlug, options = {}) {
|
|
14206
14234
|
const normalized = normalizeBoundedReadOptions(options);
|
|
@@ -14214,20 +14242,26 @@ class CloudConfigStore {
|
|
|
14214
14242
|
return parseBoundedOrLegacyPage(data.configs, data.profile.configs, normalized, "profile membership");
|
|
14215
14243
|
}
|
|
14216
14244
|
async getProfileConfigBindings(idOrSlug) {
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
const
|
|
14220
|
-
const
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14245
|
+
let routeMissing = false;
|
|
14246
|
+
const bindings = await aggregateBoundedCollection("profile config bindings", async (cursor) => {
|
|
14247
|
+
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
14248
|
+
const { status, data } = await this.requestProfileRoute(idOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/bindings?limit=${normalized.limit}&cursor=${normalized.cursor}`, (value) => Boolean(value && (Array.isArray(value.bindings) || Array.isArray(value.items))));
|
|
14249
|
+
if (status === 404) {
|
|
14250
|
+
routeMissing = true;
|
|
14251
|
+
return { ...boundedReadPage([], 0, normalized), source_bounded: false };
|
|
14252
|
+
}
|
|
14253
|
+
return parseBoundedOrLegacyPage(data, data?.bindings, normalized, "profile config bindings");
|
|
14254
|
+
}, (binding) => `${binding.profile_id}\x00${binding.config_id}`);
|
|
14255
|
+
if (!routeMissing)
|
|
14256
|
+
return bindings;
|
|
14257
|
+
const profile = await this.getProfile(idOrSlug);
|
|
14258
|
+
const configs = await this.getProfileConfigs(idOrSlug);
|
|
14259
|
+
return configs.map((config, sort_order) => ({
|
|
14260
|
+
profile_id: profile.id,
|
|
14261
|
+
config_id: config.id,
|
|
14262
|
+
sort_order,
|
|
14263
|
+
binding: legacyProfileConfigBinding()
|
|
14264
|
+
}));
|
|
14231
14265
|
}
|
|
14232
14266
|
async requestProfileRoute(idOrSlug, pathForId, isUsable) {
|
|
14233
14267
|
const attempted = new Set;
|
|
@@ -14285,14 +14319,20 @@ class CloudConfigStore {
|
|
|
14285
14319
|
await this.request("DELETE", `/profiles/${encodeURIComponent(profileIdOrSlug)}/configs/${encodeURIComponent(configId)}`, undefined, { allow404: true });
|
|
14286
14320
|
}
|
|
14287
14321
|
async getProfileAssetBindings(profileIdOrSlug) {
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14322
|
+
let routeMissing = false;
|
|
14323
|
+
const assets = await aggregateBoundedCollection("profile asset bindings", async (cursor) => {
|
|
14324
|
+
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
14325
|
+
const { status, data } = await this.requestProfileRoute(profileIdOrSlug, (profileId) => `/profiles/${encodeURIComponent(profileId)}/assets?limit=${normalized.limit}&cursor=${normalized.cursor}`, (value) => Boolean(value && (Array.isArray(value.assets) || Array.isArray(value.items))));
|
|
14326
|
+
if (status === 404) {
|
|
14327
|
+
routeMissing = true;
|
|
14328
|
+
return { ...boundedReadPage([], 0, normalized), source_bounded: false };
|
|
14329
|
+
}
|
|
14330
|
+
return parseBoundedOrLegacyPage(data, data?.assets, normalized, "profile asset bindings");
|
|
14331
|
+
}, (asset) => `${asset.profile_id}\x00${asset.binding.assetKey}`);
|
|
14332
|
+
if (!routeMissing)
|
|
14333
|
+
return assets;
|
|
14334
|
+
await this.getProfile(profileIdOrSlug);
|
|
14335
|
+
return [];
|
|
14296
14336
|
}
|
|
14297
14337
|
async addAssetToProfile(profileIdOrSlug, sourceConfigId, binding) {
|
|
14298
14338
|
const { data } = await this.request("POST", `/profiles/${encodeURIComponent(profileIdOrSlug)}/assets`, { source_config_id: sourceConfigId, binding }, { idempotent: true });
|
|
@@ -14362,8 +14402,11 @@ class CloudConfigStore {
|
|
|
14362
14402
|
await this.request("POST", "/machines/applied", { hostname: hostname2 });
|
|
14363
14403
|
}
|
|
14364
14404
|
async listMachines() {
|
|
14365
|
-
|
|
14366
|
-
|
|
14405
|
+
return aggregateBoundedCollection("machine list", async (cursor) => {
|
|
14406
|
+
const normalized = normalizeBoundedReadOptions({ limit: 100, cursor });
|
|
14407
|
+
const { data } = await this.request("GET", `/machines?limit=${normalized.limit}&cursor=${normalized.cursor}`);
|
|
14408
|
+
return parseBoundedOrLegacyPage(data, data?.machines, normalized, "machine list");
|
|
14409
|
+
}, (machine) => machine.id, { requireAscendingIdentity: true });
|
|
14367
14410
|
}
|
|
14368
14411
|
async sendFeedback(input) {
|
|
14369
14412
|
await this.request("POST", "/feedback", {
|
|
@@ -14386,13 +14429,19 @@ function resolveConfigStore(env = process.env, options = {}) {
|
|
|
14386
14429
|
const { client } = resolveInstructionsStorageClient(env, options);
|
|
14387
14430
|
return new CloudConfigStore(client);
|
|
14388
14431
|
}
|
|
14389
|
-
var LOCAL_OPT_IN_ENV, localStoreModulePromise = null;
|
|
14432
|
+
var CollectionChangedWhilePagingError, COLLECTION_READ_ATTEMPTS = 2, LOCAL_OPT_IN_ENV, localStoreModulePromise = null;
|
|
14390
14433
|
var init_config_store = __esm(() => {
|
|
14391
14434
|
init_types();
|
|
14392
14435
|
init_bounded_read();
|
|
14393
14436
|
init_instruction_graph();
|
|
14394
14437
|
init_transport_resolver();
|
|
14395
14438
|
init_local_opt_in();
|
|
14439
|
+
CollectionChangedWhilePagingError = class CollectionChangedWhilePagingError extends Error {
|
|
14440
|
+
constructor(label, detail) {
|
|
14441
|
+
super(`${label} changed while paging: ${detail}`);
|
|
14442
|
+
this.name = "CollectionChangedWhilePagingError";
|
|
14443
|
+
}
|
|
14444
|
+
};
|
|
14396
14445
|
LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV;
|
|
14397
14446
|
});
|
|
14398
14447
|
|
|
@@ -14415,7 +14464,7 @@ init_config_agents();
|
|
|
14415
14464
|
init_machine();
|
|
14416
14465
|
init_session_render();
|
|
14417
14466
|
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
14418
|
-
import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve9 } from "path";
|
|
14467
|
+
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute6, join as join12, relative as relative4, resolve as resolve9, sep as sep2 } from "path";
|
|
14419
14468
|
import { homedir as homedir7 } from "os";
|
|
14420
14469
|
|
|
14421
14470
|
// src/lib/session-render-ownership.ts
|
|
@@ -14504,6 +14553,17 @@ function expandPath(p) {
|
|
|
14504
14553
|
}
|
|
14505
14554
|
return resolve9(p);
|
|
14506
14555
|
}
|
|
14556
|
+
function compactPathForConfigHome(p) {
|
|
14557
|
+
const absolute = resolve9(p);
|
|
14558
|
+
const configHome = resolve9(getConfigHome());
|
|
14559
|
+
const conventionalHome = resolve9(process.env["HOME"] || homedir7());
|
|
14560
|
+
if (configHome !== conventionalHome)
|
|
14561
|
+
return absolute;
|
|
14562
|
+
const rel = relative4(configHome, absolute);
|
|
14563
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute6(rel))
|
|
14564
|
+
return absolute;
|
|
14565
|
+
return `~/${rel}`;
|
|
14566
|
+
}
|
|
14507
14567
|
function normalizeTargetPath(p) {
|
|
14508
14568
|
const expanded = expandPath(p);
|
|
14509
14569
|
try {
|
|
@@ -14957,7 +15017,7 @@ import {
|
|
|
14957
15017
|
writeFileSync as writeFileSync3
|
|
14958
15018
|
} from "fs";
|
|
14959
15019
|
import { homedir as homedir8 } from "os";
|
|
14960
|
-
import { dirname as dirname8, join as join14, parse as parse4, relative as
|
|
15020
|
+
import { dirname as dirname8, join as join14, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
|
|
14961
15021
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
14962
15022
|
var INBOX_SKILL_MARKERS = [
|
|
14963
15023
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -14981,7 +15041,7 @@ function findSymlinkedAncestor(path) {
|
|
|
14981
15041
|
const normalized = resolve10(path);
|
|
14982
15042
|
const parsed = parse4(normalized);
|
|
14983
15043
|
let current = parsed.root;
|
|
14984
|
-
const rel =
|
|
15044
|
+
const rel = relative5(parsed.root, normalized);
|
|
14985
15045
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
14986
15046
|
current = join14(current, segment);
|
|
14987
15047
|
if (!existsSync11(current))
|
|
@@ -15000,16 +15060,7 @@ function assertNoSymlinkAncestors2(path) {
|
|
|
15000
15060
|
function packagedInboxSkillPath(explicitPath) {
|
|
15001
15061
|
if (explicitPath)
|
|
15002
15062
|
return explicitPath;
|
|
15003
|
-
|
|
15004
|
-
join14(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
15005
|
-
join14(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
15006
|
-
join14(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
15007
|
-
];
|
|
15008
|
-
const found = candidates.find((candidate) => existsSync11(candidate));
|
|
15009
|
-
if (!found) {
|
|
15010
|
-
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
15011
|
-
}
|
|
15012
|
-
return found;
|
|
15063
|
+
throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
|
|
15013
15064
|
}
|
|
15014
15065
|
function readCanonicalSkill(explicitPath) {
|
|
15015
15066
|
const assetPath = packagedInboxSkillPath(explicitPath);
|
|
@@ -16028,7 +16079,7 @@ import {
|
|
|
16028
16079
|
readdirSync as readdirSync3,
|
|
16029
16080
|
statSync as statSync5
|
|
16030
16081
|
} from "fs";
|
|
16031
|
-
import { dirname as dirname10, isAbsolute as
|
|
16082
|
+
import { dirname as dirname10, isAbsolute as isAbsolute7, join as join17, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
|
|
16032
16083
|
|
|
16033
16084
|
class SessionApplyError extends Error {
|
|
16034
16085
|
constructor(message) {
|
|
@@ -16166,7 +16217,7 @@ function ensureSessionTargetHome(targetHome) {
|
|
|
16166
16217
|
}
|
|
16167
16218
|
function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
16168
16219
|
const safeTargetHome = assertSafeTargetHome(targetHome);
|
|
16169
|
-
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(
|
|
16220
|
+
const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative6(safeTargetHome, resolve11(manifestPath)), safeTargetHome) : resolve11(safeTargetHome, ".hasna", "session-render-manifest.json");
|
|
16170
16221
|
const checkedAt = new Date().toISOString();
|
|
16171
16222
|
const previousManifest = readPreviousManifest(resolvedManifestPath);
|
|
16172
16223
|
if (!previousManifest) {
|
|
@@ -16218,11 +16269,11 @@ function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
|
16218
16269
|
const targetHome = assertSafeTargetHome(snapshot.targetHome);
|
|
16219
16270
|
const resolvedSnapshotPath = resolve11(snapshotPath);
|
|
16220
16271
|
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
16221
|
-
const snapshotDirRelative =
|
|
16222
|
-
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !
|
|
16272
|
+
const snapshotDirRelative = relative6(snapshotDir, resolvedSnapshotPath);
|
|
16273
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute7(snapshotDirRelative);
|
|
16223
16274
|
if (!insideSnapshotDir) {
|
|
16224
|
-
const snapshotRelativePath =
|
|
16225
|
-
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") ||
|
|
16275
|
+
const snapshotRelativePath = relative6(targetHome, resolvedSnapshotPath);
|
|
16276
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute7(snapshotRelativePath)) {
|
|
16226
16277
|
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
16227
16278
|
}
|
|
16228
16279
|
}
|
|
@@ -16421,7 +16472,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
16421
16472
|
function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
|
|
16422
16473
|
assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
|
|
16423
16474
|
const manifestPath = resolve11(snapshot.manifestPath);
|
|
16424
|
-
const manifestRelativePath =
|
|
16475
|
+
const manifestRelativePath = relative6(targetHome, manifestPath).replaceAll("\\", "/");
|
|
16425
16476
|
resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
|
|
16426
16477
|
const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
|
|
16427
16478
|
if (manifestSha256 === null) {
|
|
@@ -16754,8 +16805,8 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
16754
16805
|
}
|
|
16755
16806
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
16756
16807
|
const target = resolve11(targetHome, ...file.relativePath.split("/"));
|
|
16757
|
-
const rel =
|
|
16758
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
16808
|
+
const rel = relative6(targetHome, target);
|
|
16809
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute7(rel)) {
|
|
16759
16810
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
16760
16811
|
}
|
|
16761
16812
|
if (resolve11(file.path) !== target) {
|
|
@@ -16766,8 +16817,8 @@ function resolvePlannedFilePath(plan, file, targetHome) {
|
|
|
16766
16817
|
}
|
|
16767
16818
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
16768
16819
|
const target = resolve11(targetHome, ...relativePath.split(/[\\/]+/));
|
|
16769
|
-
const rel =
|
|
16770
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
16820
|
+
const rel = relative6(targetHome, target);
|
|
16821
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute7(rel)) {
|
|
16771
16822
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
16772
16823
|
}
|
|
16773
16824
|
assertNoSymlinkSegments2(targetHome, target);
|
|
@@ -16811,7 +16862,7 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
|
|
|
16811
16862
|
function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
16812
16863
|
const actualHash = currentSessionFileHash(path, targetHome);
|
|
16813
16864
|
if (actualHash !== expectedHash) {
|
|
16814
|
-
throw new SessionApplyError(`Session apply path changed after planning: ${
|
|
16865
|
+
throw new SessionApplyError(`Session apply path changed after planning: ${relative6(targetHome, path)}`);
|
|
16815
16866
|
}
|
|
16816
16867
|
}
|
|
16817
16868
|
function currentSessionFileHash(path, targetHome) {
|
|
@@ -16896,7 +16947,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
16896
16947
|
};
|
|
16897
16948
|
}
|
|
16898
16949
|
function assertSafeTargetHome(targetHome) {
|
|
16899
|
-
if (!
|
|
16950
|
+
if (!isAbsolute7(targetHome))
|
|
16900
16951
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
16901
16952
|
const normalized = resolve11(targetHome);
|
|
16902
16953
|
if (normalized === parse5(normalized).root) {
|
|
@@ -16910,7 +16961,7 @@ function assertSafeTargetHome(targetHome) {
|
|
|
16910
16961
|
}
|
|
16911
16962
|
function assertNoSymlinkSegments2(root, target) {
|
|
16912
16963
|
assertNoSymlinkAncestors3(root);
|
|
16913
|
-
const rel =
|
|
16964
|
+
const rel = relative6(root, target);
|
|
16914
16965
|
let current = root;
|
|
16915
16966
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
16916
16967
|
current = join17(current, segment);
|
|
@@ -16923,7 +16974,7 @@ function assertNoSymlinkAncestors3(path) {
|
|
|
16923
16974
|
const normalized = resolve11(path);
|
|
16924
16975
|
const parsed = parse5(normalized);
|
|
16925
16976
|
let current = parsed.root;
|
|
16926
|
-
const rel =
|
|
16977
|
+
const rel = relative6(parsed.root, normalized);
|
|
16927
16978
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
16928
16979
|
current = join17(current, segment);
|
|
16929
16980
|
if (!existsSync15(current))
|
|
@@ -17268,8 +17319,7 @@ init_transforms();
|
|
|
17268
17319
|
// src/lib/sync-dir.ts
|
|
17269
17320
|
init_config_store();
|
|
17270
17321
|
import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
|
|
17271
|
-
import { join as join18, relative as
|
|
17272
|
-
import { homedir as homedir10 } from "os";
|
|
17322
|
+
import { join as join18, relative as relative7 } from "path";
|
|
17273
17323
|
init_redact();
|
|
17274
17324
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
17275
17325
|
function shouldSkip(p) {
|
|
@@ -17282,7 +17332,6 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
17282
17332
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
17283
17333
|
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join18(absDir, f)).filter((f) => statSync6(f).isFile());
|
|
17284
17334
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
17285
|
-
const home = homedir10();
|
|
17286
17335
|
const allConfigs = await store.listConfigs();
|
|
17287
17336
|
for (const file of files) {
|
|
17288
17337
|
if (shouldSkip(file)) {
|
|
@@ -17295,12 +17344,12 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
17295
17344
|
result.skipped.push(file + " (too large)");
|
|
17296
17345
|
continue;
|
|
17297
17346
|
}
|
|
17298
|
-
const targetPath = file
|
|
17347
|
+
const targetPath = compactPathForConfigHome(file);
|
|
17299
17348
|
const redacted = redactContent(content, redactFormatForTarget(targetPath, detectFormat(file)));
|
|
17300
17349
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
17301
17350
|
if (!existing) {
|
|
17302
17351
|
if (!opts.dryRun)
|
|
17303
|
-
await store.createConfig({ name:
|
|
17352
|
+
await store.createConfig({ name: relative7(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
|
|
17304
17353
|
result.added++;
|
|
17305
17354
|
} else if (existing.content !== redacted.content) {
|
|
17306
17355
|
if (!opts.dryRun)
|
|
@@ -17317,9 +17366,8 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
17317
17366
|
}
|
|
17318
17367
|
async function syncToDir(dir, opts = {}) {
|
|
17319
17368
|
const store = opts.store ?? resolveConfigStore();
|
|
17320
|
-
const home = homedir10();
|
|
17321
17369
|
const absDir = expandPath(dir);
|
|
17322
|
-
const normalized = dir.startsWith("~/") ? dir : absDir
|
|
17370
|
+
const normalized = dir.startsWith("~/") ? dir : compactPathForConfigHome(absDir);
|
|
17323
17371
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
17324
17372
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
17325
17373
|
for (const config of configs) {
|
|
@@ -17869,112 +17917,688 @@ function detectFormat(filePath) {
|
|
|
17869
17917
|
return "text";
|
|
17870
17918
|
}
|
|
17871
17919
|
// src/lib/export.ts
|
|
17920
|
+
init_types();
|
|
17872
17921
|
init_config_store();
|
|
17873
|
-
import {
|
|
17874
|
-
import {
|
|
17922
|
+
import { createHash as createHash11 } from "crypto";
|
|
17923
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
17924
|
+
import { dirname as dirname11, join as join20, resolve as resolve12 } from "path";
|
|
17875
17925
|
import { tmpdir } from "os";
|
|
17876
|
-
|
|
17926
|
+
var ARCHIVE_EXCLUSIONS = [
|
|
17927
|
+
{
|
|
17928
|
+
entity: "api_keys",
|
|
17929
|
+
classification: "security_state",
|
|
17930
|
+
reason: "API credentials are security state and must be provisioned independently, never copied in a domain archive."
|
|
17931
|
+
},
|
|
17932
|
+
{
|
|
17933
|
+
entity: "idempotency_receipts",
|
|
17934
|
+
classification: "transport_state",
|
|
17935
|
+
reason: "Request replay receipts are transport state and are not portable business-domain data."
|
|
17936
|
+
},
|
|
17937
|
+
{
|
|
17938
|
+
entity: "feedback",
|
|
17939
|
+
classification: "out_of_domain",
|
|
17940
|
+
reason: "Feedback is product telemetry, not part of the Instructions configuration domain."
|
|
17941
|
+
}
|
|
17942
|
+
];
|
|
17943
|
+
function compareText(left, right) {
|
|
17944
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
17945
|
+
}
|
|
17946
|
+
function sha25611(value) {
|
|
17947
|
+
return createHash11("sha256").update(value).digest("hex");
|
|
17948
|
+
}
|
|
17949
|
+
function canonicalize(value) {
|
|
17950
|
+
if (Array.isArray(value))
|
|
17951
|
+
return value.map(canonicalize);
|
|
17952
|
+
if (value && typeof value === "object") {
|
|
17953
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => compareText(left, right)).map(([key, entry]) => [key, canonicalize(entry)]));
|
|
17954
|
+
}
|
|
17955
|
+
return value;
|
|
17956
|
+
}
|
|
17957
|
+
function canonicalDomainJson(value) {
|
|
17958
|
+
return JSON.stringify(canonicalize(value));
|
|
17959
|
+
}
|
|
17960
|
+
function logicalAssetLocator(locator, idToSlug) {
|
|
17961
|
+
const match = /^config:\/\/([^@]+)@(\d+)$/.exec(locator);
|
|
17962
|
+
if (!match)
|
|
17963
|
+
return locator;
|
|
17964
|
+
let configId;
|
|
17965
|
+
try {
|
|
17966
|
+
configId = decodeURIComponent(match[1]);
|
|
17967
|
+
} catch {
|
|
17968
|
+
return locator;
|
|
17969
|
+
}
|
|
17970
|
+
const slug2 = idToSlug.get(configId);
|
|
17971
|
+
return slug2 ? `config-slug://${encodeURIComponent(slug2)}@${match[2]}` : locator;
|
|
17972
|
+
}
|
|
17973
|
+
function logicalCollections(domain, options) {
|
|
17974
|
+
const configs = [...domain.configs].sort((left, right) => compareText(left.slug, right.slug));
|
|
17975
|
+
const snapshots = [...domain.config_snapshots].sort((left, right) => compareText(left.config_slug, right.config_slug) || left.version - right.version || compareText(left.id, right.id));
|
|
17976
|
+
const profiles = [...domain.profiles].sort((left, right) => compareText(left.slug, right.slug));
|
|
17977
|
+
const configBindings = [...domain.profile_config_bindings].sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.config_slug, right.config_slug));
|
|
17978
|
+
const assetBindings = [...domain.profile_asset_bindings].sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.binding.assetKey, right.binding.assetKey));
|
|
17979
|
+
const machines = [...domain.machines].sort((left, right) => compareText(left.hostname, right.hostname));
|
|
17980
|
+
const idToSlug = new Map(configs.map((config) => [config.id, config.slug]));
|
|
17981
|
+
const configOrder = new Map;
|
|
17982
|
+
const assetOrder = new Map;
|
|
17983
|
+
return {
|
|
17984
|
+
configs: configs.map((config) => ({
|
|
17985
|
+
slug: config.slug,
|
|
17986
|
+
name: config.name,
|
|
17987
|
+
kind: config.kind,
|
|
17988
|
+
category: config.category,
|
|
17989
|
+
agent: config.agent,
|
|
17990
|
+
target_path: config.target_path,
|
|
17991
|
+
outputs: config.outputs,
|
|
17992
|
+
format: config.format,
|
|
17993
|
+
content: config.content,
|
|
17994
|
+
description: config.description,
|
|
17995
|
+
tags: [...config.tags].sort(compareText),
|
|
17996
|
+
is_template: config.is_template,
|
|
17997
|
+
version: config.version,
|
|
17998
|
+
...options.includeOperationalTimestamps ? {
|
|
17999
|
+
created_at: config.created_at,
|
|
18000
|
+
updated_at: config.updated_at,
|
|
18001
|
+
synced_at: config.synced_at
|
|
18002
|
+
} : {}
|
|
18003
|
+
})),
|
|
18004
|
+
config_snapshots: snapshots.map((snapshot) => ({
|
|
18005
|
+
config_slug: snapshot.config_slug,
|
|
18006
|
+
version: snapshot.version,
|
|
18007
|
+
content: snapshot.content,
|
|
18008
|
+
...options.includeOperationalTimestamps ? { created_at: snapshot.created_at } : {}
|
|
18009
|
+
})),
|
|
18010
|
+
profiles: profiles.map((profile) => ({
|
|
18011
|
+
slug: profile.slug,
|
|
18012
|
+
name: profile.name,
|
|
18013
|
+
description: profile.description,
|
|
18014
|
+
selectors: profile.selectors,
|
|
18015
|
+
variables: profile.variables,
|
|
18016
|
+
...options.includeOperationalTimestamps ? {
|
|
18017
|
+
created_at: profile.created_at,
|
|
18018
|
+
updated_at: profile.updated_at
|
|
18019
|
+
} : {}
|
|
18020
|
+
})),
|
|
18021
|
+
profile_config_bindings: configBindings.map((row) => {
|
|
18022
|
+
const order = configOrder.get(row.profile_slug) ?? 0;
|
|
18023
|
+
configOrder.set(row.profile_slug, order + 1);
|
|
18024
|
+
return {
|
|
18025
|
+
profile_slug: row.profile_slug,
|
|
18026
|
+
config_slug: row.config_slug,
|
|
18027
|
+
order,
|
|
18028
|
+
binding: row.binding
|
|
18029
|
+
};
|
|
18030
|
+
}),
|
|
18031
|
+
profile_asset_bindings: assetBindings.map((row) => {
|
|
18032
|
+
const order = assetOrder.get(row.profile_slug) ?? 0;
|
|
18033
|
+
assetOrder.set(row.profile_slug, order + 1);
|
|
18034
|
+
return {
|
|
18035
|
+
profile_slug: row.profile_slug,
|
|
18036
|
+
source_config_slug: row.source_config_slug,
|
|
18037
|
+
order,
|
|
18038
|
+
binding: {
|
|
18039
|
+
...row.binding,
|
|
18040
|
+
source: {
|
|
18041
|
+
...row.binding.source,
|
|
18042
|
+
locator: logicalAssetLocator(row.binding.source.locator, idToSlug)
|
|
18043
|
+
}
|
|
18044
|
+
}
|
|
18045
|
+
};
|
|
18046
|
+
}),
|
|
18047
|
+
machines: machines.map((machine) => ({
|
|
18048
|
+
hostname: machine.hostname,
|
|
18049
|
+
os: machine.os,
|
|
18050
|
+
arch: machine.arch,
|
|
18051
|
+
...options.includeOperationalTimestamps ? {
|
|
18052
|
+
created_at: machine.created_at,
|
|
18053
|
+
last_applied_at: machine.last_applied_at
|
|
18054
|
+
} : {
|
|
18055
|
+
applied: machine.last_applied_at !== null
|
|
18056
|
+
}
|
|
18057
|
+
}))
|
|
18058
|
+
};
|
|
18059
|
+
}
|
|
18060
|
+
function computeIntegrity(domain, options) {
|
|
18061
|
+
const collections = logicalCollections(domain, options);
|
|
18062
|
+
const counts = {
|
|
18063
|
+
configs: collections.configs.length,
|
|
18064
|
+
config_snapshots: collections.config_snapshots.length,
|
|
18065
|
+
profiles: collections.profiles.length,
|
|
18066
|
+
profile_config_bindings: collections.profile_config_bindings.length,
|
|
18067
|
+
profile_asset_bindings: collections.profile_asset_bindings.length,
|
|
18068
|
+
machines: collections.machines.length
|
|
18069
|
+
};
|
|
18070
|
+
const hashes = {
|
|
18071
|
+
configs: sha25611(canonicalDomainJson(collections.configs)),
|
|
18072
|
+
config_snapshots: sha25611(canonicalDomainJson(collections.config_snapshots)),
|
|
18073
|
+
profiles: sha25611(canonicalDomainJson(collections.profiles)),
|
|
18074
|
+
profile_config_bindings: sha25611(canonicalDomainJson(collections.profile_config_bindings)),
|
|
18075
|
+
profile_asset_bindings: sha25611(canonicalDomainJson(collections.profile_asset_bindings)),
|
|
18076
|
+
machines: sha25611(canonicalDomainJson(collections.machines))
|
|
18077
|
+
};
|
|
18078
|
+
return {
|
|
18079
|
+
algorithm: "sha256",
|
|
18080
|
+
canonicalization: options.canonicalization,
|
|
18081
|
+
counts,
|
|
18082
|
+
hashes,
|
|
18083
|
+
domain_sha256: sha25611(canonicalDomainJson({ counts, hashes }))
|
|
18084
|
+
};
|
|
18085
|
+
}
|
|
18086
|
+
function computeDomainIntegrity(domain) {
|
|
18087
|
+
return computeIntegrity(domain, {
|
|
18088
|
+
includeOperationalTimestamps: true,
|
|
18089
|
+
canonicalization: "hasna.instructions.logical-json/v1"
|
|
18090
|
+
});
|
|
18091
|
+
}
|
|
18092
|
+
function computeRestorableDomainIntegrity(domain) {
|
|
18093
|
+
return computeIntegrity(domain, {
|
|
18094
|
+
includeOperationalTimestamps: false,
|
|
18095
|
+
canonicalization: "hasna.instructions.restorable-logical-json/v1"
|
|
18096
|
+
});
|
|
18097
|
+
}
|
|
18098
|
+
function assertArray(value, label) {
|
|
18099
|
+
if (!Array.isArray(value))
|
|
18100
|
+
throw new Error(`Invalid v2 archive: ${label} must be an array`);
|
|
18101
|
+
}
|
|
18102
|
+
function assertUnique(values, label) {
|
|
18103
|
+
const seen = new Set;
|
|
18104
|
+
for (const value of values) {
|
|
18105
|
+
if (!value || seen.has(value))
|
|
18106
|
+
throw new Error(`Invalid v2 archive: duplicate or empty ${label} ${JSON.stringify(value)}`);
|
|
18107
|
+
seen.add(value);
|
|
18108
|
+
}
|
|
18109
|
+
}
|
|
18110
|
+
function assertTimestamp(value, label, nullable = false) {
|
|
18111
|
+
if (nullable && value === null)
|
|
18112
|
+
return;
|
|
18113
|
+
if (typeof value !== "string" || value.length === 0 || Number.isNaN(Date.parse(value))) {
|
|
18114
|
+
throw new Error(`Invalid v2 archive: ${label} must be ${nullable ? "null or " : ""}an ISO timestamp`);
|
|
18115
|
+
}
|
|
18116
|
+
}
|
|
18117
|
+
function validateInstructionsDomainArchive(value) {
|
|
18118
|
+
if (!value || typeof value !== "object")
|
|
18119
|
+
throw new Error("Invalid v2 archive: domain payload must be an object");
|
|
18120
|
+
const domain = value;
|
|
18121
|
+
if (domain.schema !== INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA)
|
|
18122
|
+
throw new Error("Invalid v2 archive: unsupported domain schema");
|
|
18123
|
+
assertArray(domain.configs, "configs");
|
|
18124
|
+
assertArray(domain.config_snapshots, "config_snapshots");
|
|
18125
|
+
assertArray(domain.profiles, "profiles");
|
|
18126
|
+
assertArray(domain.profile_config_bindings, "profile_config_bindings");
|
|
18127
|
+
assertArray(domain.profile_asset_bindings, "profile_asset_bindings");
|
|
18128
|
+
assertArray(domain.machines, "machines");
|
|
18129
|
+
const typed = domain;
|
|
18130
|
+
assertUnique(typed.configs.map((row) => row.slug), "config slug");
|
|
18131
|
+
assertUnique(typed.profiles.map((row) => row.slug), "profile slug");
|
|
18132
|
+
assertUnique(typed.machines.map((row) => row.hostname), "machine hostname");
|
|
18133
|
+
assertUnique(typed.config_snapshots.map((row) => `${row.config_slug}@${row.version}`), "config snapshot");
|
|
18134
|
+
assertUnique(typed.profile_config_bindings.map((row) => `${row.profile_slug}:${row.config_slug}`), "profile config binding");
|
|
18135
|
+
assertUnique(typed.profile_asset_bindings.map((row) => `${row.profile_slug}:${row.binding.assetKey}`), "profile asset binding");
|
|
18136
|
+
const configSlugs = new Set(typed.configs.map((row) => row.slug));
|
|
18137
|
+
const profileSlugs = new Set(typed.profiles.map((row) => row.slug));
|
|
18138
|
+
for (const config of typed.configs) {
|
|
18139
|
+
assertTimestamp(config.created_at, `config ${config.slug} created_at`);
|
|
18140
|
+
assertTimestamp(config.updated_at, `config ${config.slug} updated_at`);
|
|
18141
|
+
assertTimestamp(config.synced_at, `config ${config.slug} synced_at`, true);
|
|
18142
|
+
if (!Number.isSafeInteger(config.version) || config.version < 1)
|
|
18143
|
+
throw new Error(`Invalid v2 archive: config ${config.slug} has invalid version`);
|
|
18144
|
+
const snapshots = typed.config_snapshots.filter((row) => row.config_slug === config.slug).sort((a, b) => a.version - b.version);
|
|
18145
|
+
if (snapshots.some((row) => !Number.isSafeInteger(row.version) || row.version < 1 || row.version > config.version)) {
|
|
18146
|
+
throw new Error(`Invalid v2 archive: config ${config.slug} has an invalid snapshot version`);
|
|
18147
|
+
}
|
|
18148
|
+
if (snapshots.length) {
|
|
18149
|
+
const first = snapshots[0].version;
|
|
18150
|
+
const expected = Array.from({ length: config.version - first + 1 }, (_, index) => first + index);
|
|
18151
|
+
if (canonicalDomainJson(snapshots.map((row) => row.version)) !== canonicalDomainJson(expected) || snapshots.at(-1).version !== config.version) {
|
|
18152
|
+
throw new Error(`Invalid v2 archive: snapshots for ${config.slug} must be a contiguous suffix ending at version ${config.version}`);
|
|
18153
|
+
}
|
|
18154
|
+
if (snapshots.at(-1).content !== config.content) {
|
|
18155
|
+
throw new Error(`Invalid v2 archive: latest snapshot content differs from config ${config.slug}`);
|
|
18156
|
+
}
|
|
18157
|
+
}
|
|
18158
|
+
}
|
|
18159
|
+
for (const row of typed.config_snapshots) {
|
|
18160
|
+
if (!configSlugs.has(row.config_slug))
|
|
18161
|
+
throw new Error(`Invalid v2 archive: snapshot references missing config ${row.config_slug}`);
|
|
18162
|
+
assertTimestamp(row.created_at, `snapshot ${row.config_slug}@${row.version} created_at`);
|
|
18163
|
+
}
|
|
18164
|
+
for (const profile of typed.profiles) {
|
|
18165
|
+
assertTimestamp(profile.created_at, `profile ${profile.slug} created_at`);
|
|
18166
|
+
assertTimestamp(profile.updated_at, `profile ${profile.slug} updated_at`);
|
|
18167
|
+
}
|
|
18168
|
+
for (const row of typed.profile_config_bindings) {
|
|
18169
|
+
if (!profileSlugs.has(row.profile_slug) || !configSlugs.has(row.config_slug)) {
|
|
18170
|
+
throw new Error(`Invalid v2 archive: profile config binding has a missing logical reference`);
|
|
18171
|
+
}
|
|
18172
|
+
}
|
|
18173
|
+
for (const row of typed.profile_asset_bindings) {
|
|
18174
|
+
if (!profileSlugs.has(row.profile_slug) || !configSlugs.has(row.source_config_slug)) {
|
|
18175
|
+
throw new Error(`Invalid v2 archive: profile asset binding has a missing logical reference`);
|
|
18176
|
+
}
|
|
18177
|
+
}
|
|
18178
|
+
for (const machine of typed.machines) {
|
|
18179
|
+
assertTimestamp(machine.created_at, `machine ${machine.hostname} created_at`);
|
|
18180
|
+
assertTimestamp(machine.last_applied_at, `machine ${machine.hostname} last_applied_at`, true);
|
|
18181
|
+
}
|
|
18182
|
+
return typed;
|
|
18183
|
+
}
|
|
18184
|
+
function matchesFilter(config, filter) {
|
|
18185
|
+
if (!filter)
|
|
18186
|
+
return true;
|
|
18187
|
+
if (filter.category && config.category !== filter.category)
|
|
18188
|
+
return false;
|
|
18189
|
+
if (filter.agent && config.agent !== filter.agent)
|
|
18190
|
+
return false;
|
|
18191
|
+
if (filter.kind && config.kind !== filter.kind)
|
|
18192
|
+
return false;
|
|
18193
|
+
if (filter.is_template !== undefined && config.is_template !== filter.is_template)
|
|
18194
|
+
return false;
|
|
18195
|
+
if (filter.tags?.length && !filter.tags.every((tag) => config.tags.includes(tag)))
|
|
18196
|
+
return false;
|
|
18197
|
+
if (filter.search) {
|
|
18198
|
+
const needle = filter.search.toLocaleLowerCase();
|
|
18199
|
+
if (![config.name, config.description ?? "", config.content].some((value) => value.toLocaleLowerCase().includes(needle)))
|
|
18200
|
+
return false;
|
|
18201
|
+
}
|
|
18202
|
+
return true;
|
|
18203
|
+
}
|
|
18204
|
+
async function collectInstructionsDomain(opts = {}) {
|
|
17877
18205
|
const store = opts.store ?? resolveConfigStore();
|
|
17878
|
-
const
|
|
18206
|
+
const selectedProfile = opts.profileId ? await store.getProfile(opts.profileId) : null;
|
|
18207
|
+
const listedConfigs = selectedProfile ? await store.getProfileConfigs(selectedProfile.id) : await store.listConfigs(opts.filter);
|
|
18208
|
+
const configs = listedConfigs.filter((config) => matchesFilter(config, selectedProfile ? opts.filter : undefined));
|
|
18209
|
+
configs.sort((left, right) => compareText(left.slug, right.slug));
|
|
18210
|
+
const selectedIds = new Set(configs.map((config) => config.id));
|
|
18211
|
+
const profiles = (selectedProfile ? [selectedProfile] : await store.listProfiles()).sort((left, right) => compareText(left.slug, right.slug));
|
|
18212
|
+
const profileById = new Map(profiles.map((profile) => [profile.id, profile]));
|
|
18213
|
+
const configById = new Map(configs.map((config) => [config.id, config]));
|
|
18214
|
+
const configSnapshots = [];
|
|
18215
|
+
for (const config of configs) {
|
|
18216
|
+
for (const snapshot of await store.listSnapshots(config.id)) {
|
|
18217
|
+
configSnapshots.push({
|
|
18218
|
+
id: snapshot.id,
|
|
18219
|
+
config_slug: config.slug,
|
|
18220
|
+
content: snapshot.content,
|
|
18221
|
+
version: snapshot.version,
|
|
18222
|
+
created_at: snapshot.created_at
|
|
18223
|
+
});
|
|
18224
|
+
}
|
|
18225
|
+
}
|
|
18226
|
+
configSnapshots.sort((left, right) => compareText(left.config_slug, right.config_slug) || left.version - right.version || compareText(left.id, right.id));
|
|
18227
|
+
const profileConfigBindings = (await Promise.all(profiles.map(async (profile) => (await store.getProfileConfigBindings(profile.id)).filter((row) => selectedIds.has(row.config_id)).map((row) => ({
|
|
18228
|
+
profile_slug: profile.slug,
|
|
18229
|
+
config_slug: configById.get(row.config_id).slug,
|
|
18230
|
+
sort_order: row.sort_order,
|
|
18231
|
+
binding: row.binding
|
|
18232
|
+
}))))).flat().sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.config_slug, right.config_slug));
|
|
18233
|
+
const profileAssetBindings = (await Promise.all(profiles.map(async (profile) => (await store.getProfileAssetBindings(profile.id)).filter((row) => selectedIds.has(row.source_config_id)).map((row) => ({
|
|
18234
|
+
profile_slug: profile.slug,
|
|
18235
|
+
source_config_slug: configById.get(row.source_config_id).slug,
|
|
18236
|
+
sort_order: row.sort_order,
|
|
18237
|
+
binding: row.binding
|
|
18238
|
+
}))))).flat().sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.binding.assetKey, right.binding.assetKey));
|
|
18239
|
+
const archivedProfiles = profiles.filter((profile) => profileById.has(profile.id));
|
|
18240
|
+
const machines = (await store.listMachines()).sort((left, right) => compareText(left.hostname, right.hostname));
|
|
18241
|
+
return {
|
|
18242
|
+
schema: INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
18243
|
+
configs,
|
|
18244
|
+
config_snapshots: configSnapshots,
|
|
18245
|
+
profiles: archivedProfiles,
|
|
18246
|
+
profile_config_bindings: profileConfigBindings,
|
|
18247
|
+
profile_asset_bindings: profileAssetBindings,
|
|
18248
|
+
machines
|
|
18249
|
+
};
|
|
18250
|
+
}
|
|
18251
|
+
async function exportConfigs(outputPath, opts = {}) {
|
|
18252
|
+
const domain = validateInstructionsDomainArchive(await collectInstructionsDomain(opts));
|
|
18253
|
+
const integrity = computeDomainIntegrity(domain);
|
|
18254
|
+
const payload = `${JSON.stringify(domain, null, 2)}
|
|
18255
|
+
`;
|
|
18256
|
+
const manifest = {
|
|
18257
|
+
schema: INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
18258
|
+
version: "2.0.0",
|
|
18259
|
+
exported_at: new Date().toISOString(),
|
|
18260
|
+
payload: {
|
|
18261
|
+
path: "domain.json",
|
|
18262
|
+
sha256: sha25611(payload),
|
|
18263
|
+
size_bytes: Buffer.byteLength(payload)
|
|
18264
|
+
},
|
|
18265
|
+
integrity,
|
|
18266
|
+
exclusions: ARCHIVE_EXCLUSIONS
|
|
18267
|
+
};
|
|
17879
18268
|
const absOutput = resolve12(outputPath);
|
|
17880
|
-
|
|
17881
|
-
const
|
|
18269
|
+
mkdirSync8(dirname11(absOutput), { recursive: true });
|
|
18270
|
+
const stagingDir = mkdtempSync(join20(tmpdir(), "instructions-domain-export-"));
|
|
17882
18271
|
try {
|
|
17883
|
-
|
|
17884
|
-
|
|
17885
|
-
|
|
17886
|
-
|
|
17887
|
-
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
17888
|
-
};
|
|
17889
|
-
writeFileSync6(join20(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
17890
|
-
for (const config of configs) {
|
|
17891
|
-
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
17892
|
-
writeFileSync6(join20(contentsDir, fileName), config.content, "utf-8");
|
|
17893
|
-
}
|
|
17894
|
-
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
18272
|
+
writeFileSync6(join20(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
18273
|
+
`, "utf-8");
|
|
18274
|
+
writeFileSync6(join20(stagingDir, "domain.json"), payload, "utf-8");
|
|
18275
|
+
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
|
|
17895
18276
|
stdout: "pipe",
|
|
17896
18277
|
stderr: "pipe"
|
|
17897
18278
|
});
|
|
17898
18279
|
const exitCode = await proc.exited;
|
|
17899
18280
|
if (exitCode !== 0) {
|
|
17900
18281
|
const stderr = await new Response(proc.stderr).text();
|
|
17901
|
-
throw new Error(`tar failed: ${stderr}`);
|
|
18282
|
+
throw new Error(`tar failed: ${stderr.trim()}`);
|
|
17902
18283
|
}
|
|
17903
|
-
return { path: absOutput, count: configs.length };
|
|
18284
|
+
return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
|
|
17904
18285
|
} finally {
|
|
17905
|
-
if (existsSync18(
|
|
17906
|
-
rmSync4(
|
|
17907
|
-
}
|
|
18286
|
+
if (existsSync18(stagingDir))
|
|
18287
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
17908
18288
|
}
|
|
17909
18289
|
}
|
|
17910
18290
|
// src/lib/import.ts
|
|
18291
|
+
init_types();
|
|
17911
18292
|
init_config_store();
|
|
17912
|
-
import {
|
|
17913
|
-
import {
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
18293
|
+
import { createHash as createHash12 } from "crypto";
|
|
18294
|
+
import { resolve as resolve13 } from "path";
|
|
18295
|
+
function compareText2(left, right) {
|
|
18296
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
18297
|
+
}
|
|
18298
|
+
function emptyMutationCounts() {
|
|
18299
|
+
return { created: 0, updated: 0, skipped: 0 };
|
|
18300
|
+
}
|
|
18301
|
+
function emptyResult() {
|
|
18302
|
+
return {
|
|
18303
|
+
created: 0,
|
|
18304
|
+
updated: 0,
|
|
18305
|
+
skipped: 0,
|
|
18306
|
+
counts: {
|
|
18307
|
+
configs: emptyMutationCounts(),
|
|
18308
|
+
config_snapshots: { created: 0, skipped: 0 },
|
|
18309
|
+
profiles: emptyMutationCounts(),
|
|
18310
|
+
profile_config_bindings: emptyMutationCounts(),
|
|
18311
|
+
profile_asset_bindings: emptyMutationCounts(),
|
|
18312
|
+
machines: emptyMutationCounts()
|
|
18313
|
+
},
|
|
18314
|
+
errors: [],
|
|
18315
|
+
integrity: null
|
|
18316
|
+
};
|
|
18317
|
+
}
|
|
18318
|
+
function sha25612(value) {
|
|
18319
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
18320
|
+
}
|
|
18321
|
+
function normalizeMemberName(name) {
|
|
18322
|
+
let normalized = name;
|
|
18323
|
+
while (normalized.startsWith("./"))
|
|
18324
|
+
normalized = normalized.slice(2);
|
|
18325
|
+
if (normalized.startsWith("/") || normalized.includes("\x00") || normalized.split("/").some((segment) => segment === "..")) {
|
|
18326
|
+
throw new Error(`Invalid archive member path: ${JSON.stringify(name)}`);
|
|
18327
|
+
}
|
|
18328
|
+
return normalized;
|
|
18329
|
+
}
|
|
18330
|
+
async function openArchive(path) {
|
|
18331
|
+
const list = Bun.spawn(["tar", "tzf", path], { stdout: "pipe", stderr: "pipe" });
|
|
18332
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
18333
|
+
list.exited,
|
|
18334
|
+
new Response(list.stdout).text(),
|
|
18335
|
+
new Response(list.stderr).text()
|
|
18336
|
+
]);
|
|
18337
|
+
if (exitCode !== 0)
|
|
18338
|
+
throw new Error(`tar listing failed: ${stderr.trim()}`);
|
|
18339
|
+
const members = new Map;
|
|
18340
|
+
for (const raw of stdout.split(`
|
|
18341
|
+
`).filter(Boolean)) {
|
|
18342
|
+
const normalized = normalizeMemberName(raw);
|
|
18343
|
+
if (normalized.length === 0)
|
|
18344
|
+
continue;
|
|
18345
|
+
if (members.has(normalized))
|
|
18346
|
+
throw new Error(`Invalid archive: duplicate member ${normalized}`);
|
|
18347
|
+
members.set(normalized, raw);
|
|
18348
|
+
}
|
|
18349
|
+
return {
|
|
18350
|
+
members,
|
|
18351
|
+
async read(member) {
|
|
18352
|
+
const actual = members.get(member);
|
|
18353
|
+
if (!actual)
|
|
18354
|
+
throw new Error(`Invalid archive: missing ${member}`);
|
|
18355
|
+
const proc = Bun.spawn(["tar", "xOzf", path, "--", actual], { stdout: "pipe", stderr: "pipe" });
|
|
18356
|
+
const [code, content, memberError] = await Promise.all([
|
|
18357
|
+
proc.exited,
|
|
18358
|
+
new Response(proc.stdout).text(),
|
|
18359
|
+
new Response(proc.stderr).text()
|
|
18360
|
+
]);
|
|
18361
|
+
if (code !== 0)
|
|
18362
|
+
throw new Error(`tar read failed for ${member}: ${memberError.trim()}`);
|
|
18363
|
+
return content;
|
|
18364
|
+
}
|
|
18365
|
+
};
|
|
18366
|
+
}
|
|
18367
|
+
function isNotFound(error) {
|
|
18368
|
+
return Boolean(error && typeof error === "object" && (error.name === "ConfigNotFoundError" || error.name === "ProfileNotFoundError" || error.status === 404));
|
|
18369
|
+
}
|
|
18370
|
+
async function findConfig(store, slug2) {
|
|
17921
18371
|
try {
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
18372
|
+
return await store.getConfig(slug2);
|
|
18373
|
+
} catch (error) {
|
|
18374
|
+
if (isNotFound(error))
|
|
18375
|
+
return null;
|
|
18376
|
+
throw error;
|
|
18377
|
+
}
|
|
18378
|
+
}
|
|
18379
|
+
function verifyCounts(actual, expected) {
|
|
18380
|
+
if (canonicalDomainJson(actual) !== canonicalDomainJson(expected)) {
|
|
18381
|
+
throw new Error("Archive integrity failure: manifest counts do not match the domain payload");
|
|
18382
|
+
}
|
|
18383
|
+
}
|
|
18384
|
+
async function readV2(reader, manifest) {
|
|
18385
|
+
if (manifest.schema !== INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA || manifest.version !== "2.0.0" || manifest.payload?.path !== "domain.json" || manifest.integrity?.algorithm !== "sha256" || manifest.integrity?.canonicalization !== "hasna.instructions.logical-json/v1") {
|
|
18386
|
+
throw new Error("Invalid v2 archive manifest");
|
|
18387
|
+
}
|
|
18388
|
+
const payload = await reader.read("domain.json");
|
|
18389
|
+
if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25612(payload) !== manifest.payload.sha256) {
|
|
18390
|
+
throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
|
|
18391
|
+
}
|
|
18392
|
+
const domain = validateInstructionsDomainArchive(JSON.parse(payload));
|
|
18393
|
+
const actual = computeDomainIntegrity(domain);
|
|
18394
|
+
verifyCounts(actual.counts, manifest.integrity.counts);
|
|
18395
|
+
if (canonicalDomainJson(actual) !== canonicalDomainJson(manifest.integrity)) {
|
|
18396
|
+
throw new Error("Archive integrity failure: logical domain hashes do not match the manifest");
|
|
18397
|
+
}
|
|
18398
|
+
return domain;
|
|
18399
|
+
}
|
|
18400
|
+
function configInput(config, content) {
|
|
18401
|
+
return {
|
|
18402
|
+
name: config.name,
|
|
18403
|
+
kind: config.kind,
|
|
18404
|
+
category: config.category,
|
|
18405
|
+
agent: config.agent,
|
|
18406
|
+
target_path: config.target_path,
|
|
18407
|
+
outputs: config.outputs,
|
|
18408
|
+
format: config.format,
|
|
18409
|
+
content,
|
|
18410
|
+
description: config.description ?? undefined,
|
|
18411
|
+
tags: config.tags,
|
|
18412
|
+
is_template: config.is_template
|
|
18413
|
+
};
|
|
18414
|
+
}
|
|
18415
|
+
async function restoreNewConfig(store, archived, domain) {
|
|
18416
|
+
const snapshots = domain.config_snapshots.filter((row) => row.config_slug === archived.slug).sort((left, right) => left.version - right.version);
|
|
18417
|
+
const byVersion = new Map(snapshots.map((snapshot) => [snapshot.version, snapshot.content]));
|
|
18418
|
+
let content = byVersion.get(1) ?? snapshots[0]?.content ?? archived.content;
|
|
18419
|
+
let restored = await store.createConfig(configInput(archived, content));
|
|
18420
|
+
for (let version = 2;version <= archived.version; version++) {
|
|
18421
|
+
content = byVersion.get(version) ?? (version === archived.version ? archived.content : content);
|
|
18422
|
+
restored = await store.updateConfig(restored.id, {
|
|
18423
|
+
content,
|
|
18424
|
+
...version === archived.version ? {
|
|
18425
|
+
name: archived.name,
|
|
18426
|
+
kind: archived.kind,
|
|
18427
|
+
category: archived.category,
|
|
18428
|
+
agent: archived.agent,
|
|
18429
|
+
target_path: archived.target_path,
|
|
18430
|
+
outputs: archived.outputs,
|
|
18431
|
+
format: archived.format,
|
|
18432
|
+
description: archived.description,
|
|
18433
|
+
tags: archived.tags,
|
|
18434
|
+
is_template: archived.is_template,
|
|
18435
|
+
synced_at: archived.synced_at
|
|
18436
|
+
} : {}
|
|
17926
18437
|
});
|
|
17927
|
-
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
18438
|
+
}
|
|
18439
|
+
await store.pruneSnapshots(restored.id, snapshots.length);
|
|
18440
|
+
const restoredSnapshots = (await store.listSnapshots(restored.id)).map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent })).sort((left, right) => left.version - right.version);
|
|
18441
|
+
const expectedSnapshots = snapshots.map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent }));
|
|
18442
|
+
if (restored.version !== archived.version || restored.content !== archived.content || canonicalDomainJson(restoredSnapshots) !== canonicalDomainJson(expectedSnapshots)) {
|
|
18443
|
+
throw new Error(`restored config or snapshot history did not match archive for ${archived.slug}`);
|
|
18444
|
+
}
|
|
18445
|
+
return restored;
|
|
18446
|
+
}
|
|
18447
|
+
function rewriteAssetLocator(binding, archivedSourceId, restoredSourceId) {
|
|
18448
|
+
const match = /^config:\/\/([^@]+)@(\d+)$/.exec(binding.source.locator);
|
|
18449
|
+
if (!match)
|
|
18450
|
+
return binding;
|
|
18451
|
+
let locatorId;
|
|
18452
|
+
try {
|
|
18453
|
+
locatorId = decodeURIComponent(match[1]);
|
|
18454
|
+
} catch {
|
|
18455
|
+
return binding;
|
|
18456
|
+
}
|
|
18457
|
+
if (locatorId !== archivedSourceId)
|
|
18458
|
+
return binding;
|
|
18459
|
+
return {
|
|
18460
|
+
...binding,
|
|
18461
|
+
source: {
|
|
18462
|
+
...binding.source,
|
|
18463
|
+
locator: `config://${encodeURIComponent(restoredSourceId)}@${match[2]}`
|
|
17931
18464
|
}
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
18465
|
+
};
|
|
18466
|
+
}
|
|
18467
|
+
async function assertEmptyV2Destination(store) {
|
|
18468
|
+
const [configs, profiles, machines] = await Promise.all([
|
|
18469
|
+
store.listConfigs(),
|
|
18470
|
+
store.listProfiles(),
|
|
18471
|
+
store.listMachines()
|
|
18472
|
+
]);
|
|
18473
|
+
if (configs.length > 0 || profiles.length > 0 || machines.length > 0) {
|
|
18474
|
+
throw new Error(`Instructions domain archive v2 recovery requires an empty destination ` + `(found configs=${configs.length}, profiles=${profiles.length}, machines=${machines.length})`);
|
|
18475
|
+
}
|
|
18476
|
+
}
|
|
18477
|
+
async function importV2(domain, manifest, store, conflict) {
|
|
18478
|
+
if (conflict !== "skip") {
|
|
18479
|
+
throw new Error(`Instructions domain archive v2 does not support ${conflict}; restore into an empty destination`);
|
|
18480
|
+
}
|
|
18481
|
+
await assertEmptyV2Destination(store);
|
|
18482
|
+
const result = emptyResult();
|
|
18483
|
+
result.integrity = manifest.integrity;
|
|
18484
|
+
const configMap = new Map;
|
|
18485
|
+
for (const archived of domain.configs) {
|
|
18486
|
+
const restored = await restoreNewConfig(store, archived, domain);
|
|
18487
|
+
configMap.set(archived.slug, restored);
|
|
18488
|
+
result.counts.configs.created++;
|
|
18489
|
+
result.created++;
|
|
18490
|
+
result.counts.config_snapshots.created += domain.config_snapshots.filter((row) => row.config_slug === archived.slug).length;
|
|
18491
|
+
}
|
|
18492
|
+
const profileMap = new Map;
|
|
18493
|
+
for (const archived of domain.profiles) {
|
|
18494
|
+
const created = await store.createProfile({
|
|
18495
|
+
name: archived.name,
|
|
18496
|
+
description: archived.description ?? undefined,
|
|
18497
|
+
selectors: archived.selectors,
|
|
18498
|
+
variables: archived.variables
|
|
18499
|
+
});
|
|
18500
|
+
profileMap.set(archived.slug, created);
|
|
18501
|
+
result.counts.profiles.created++;
|
|
18502
|
+
}
|
|
18503
|
+
for (const archived of domain.profiles) {
|
|
18504
|
+
const restoredProfile = profileMap.get(archived.slug);
|
|
18505
|
+
if (!restoredProfile)
|
|
18506
|
+
throw new Error(`post-create profile mapping missing for ${archived.slug}`);
|
|
18507
|
+
const archivedMembership = domain.profile_config_bindings.filter((row) => row.profile_slug === archived.slug).sort((left, right) => left.sort_order - right.sort_order || compareText2(left.config_slug, right.config_slug));
|
|
18508
|
+
const archivedAssets = domain.profile_asset_bindings.filter((row) => row.profile_slug === archived.slug).sort((left, right) => left.sort_order - right.sort_order || compareText2(left.binding.assetKey, right.binding.assetKey));
|
|
18509
|
+
for (const row of archivedMembership) {
|
|
18510
|
+
const config = configMap.get(row.config_slug);
|
|
18511
|
+
if (!config)
|
|
18512
|
+
throw new Error(`missing restored config ${row.config_slug}`);
|
|
18513
|
+
await store.addConfigToProfile(restoredProfile.id, config.id);
|
|
18514
|
+
await store.setProfileConfigBinding(restoredProfile.id, config.id, row.binding);
|
|
18515
|
+
result.counts.profile_config_bindings.created++;
|
|
18516
|
+
}
|
|
18517
|
+
for (const row of archivedAssets) {
|
|
18518
|
+
const source = configMap.get(row.source_config_slug);
|
|
18519
|
+
const archivedSource = domain.configs.find((config) => config.slug === row.source_config_slug);
|
|
18520
|
+
if (!source || !archivedSource)
|
|
18521
|
+
throw new Error(`missing restored asset source ${row.source_config_slug}`);
|
|
18522
|
+
await store.addAssetToProfile(restoredProfile.id, source.id, rewriteAssetLocator(row.binding, archivedSource.id, source.id));
|
|
18523
|
+
result.counts.profile_asset_bindings.created++;
|
|
18524
|
+
}
|
|
18525
|
+
}
|
|
18526
|
+
for (const archived of domain.machines) {
|
|
18527
|
+
const restored = await store.registerMachine(archived.hostname, archived.os ?? undefined, archived.arch ?? undefined);
|
|
18528
|
+
if (archived.last_applied_at !== null)
|
|
18529
|
+
await store.updateMachineApplied(restored.hostname);
|
|
18530
|
+
result.counts.machines.created++;
|
|
18531
|
+
}
|
|
18532
|
+
const restoredDomain = await collectInstructionsDomain({ store });
|
|
18533
|
+
const expected = computeRestorableDomainIntegrity(domain);
|
|
18534
|
+
const actual = computeRestorableDomainIntegrity(restoredDomain);
|
|
18535
|
+
if (canonicalDomainJson(actual) !== canonicalDomainJson(expected)) {
|
|
18536
|
+
throw new Error(`Post-restore logical integrity verification failed: expected ${expected.domain_sha256}, got ${actual.domain_sha256}`);
|
|
18537
|
+
}
|
|
18538
|
+
return result;
|
|
18539
|
+
}
|
|
18540
|
+
async function importV1(reader, manifest, store, conflict) {
|
|
18541
|
+
const result = emptyResult();
|
|
18542
|
+
if (!Array.isArray(manifest.configs))
|
|
18543
|
+
throw new Error("Invalid v1 bundle: configs must be an array");
|
|
18544
|
+
for (const meta of manifest.configs) {
|
|
18545
|
+
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
18546
|
+
const contentPath = `contents/${meta.slug}.${ext}`;
|
|
18547
|
+
const content = reader.members.has(contentPath) ? await reader.read(contentPath) : "";
|
|
18548
|
+
const existing = await findConfig(store, meta.slug);
|
|
18549
|
+
if (existing) {
|
|
18550
|
+
if (conflict === "skip") {
|
|
18551
|
+
result.skipped++;
|
|
18552
|
+
result.counts.configs.skipped++;
|
|
18553
|
+
} else {
|
|
18554
|
+
await store.updateConfig(existing.id, {
|
|
18555
|
+
name: meta.name,
|
|
18556
|
+
kind: meta.kind,
|
|
18557
|
+
category: meta.category,
|
|
18558
|
+
agent: meta.agent,
|
|
18559
|
+
target_path: meta.target_path,
|
|
18560
|
+
outputs: meta.outputs,
|
|
18561
|
+
format: meta.format,
|
|
18562
|
+
content,
|
|
18563
|
+
description: meta.description,
|
|
18564
|
+
tags: meta.tags,
|
|
18565
|
+
is_template: meta.is_template,
|
|
18566
|
+
synced_at: meta.synced_at
|
|
18567
|
+
});
|
|
18568
|
+
result.updated++;
|
|
18569
|
+
result.counts.configs.updated++;
|
|
17970
18570
|
}
|
|
17971
|
-
}
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
rmSync5(tmpDir, { recursive: true, force: true });
|
|
18571
|
+
} else {
|
|
18572
|
+
await store.createConfig(configInput({ ...meta, content }, content));
|
|
18573
|
+
result.created++;
|
|
18574
|
+
result.counts.configs.created++;
|
|
17976
18575
|
}
|
|
17977
18576
|
}
|
|
18577
|
+
return result;
|
|
18578
|
+
}
|
|
18579
|
+
async function importConfigs(bundlePath, opts = {}) {
|
|
18580
|
+
const store = opts.store ?? resolveConfigStore();
|
|
18581
|
+
const conflict = opts.conflict ?? "skip";
|
|
18582
|
+
const reader = await openArchive(resolve13(bundlePath));
|
|
18583
|
+
const manifestText = await reader.read("manifest.json");
|
|
18584
|
+
let manifest;
|
|
18585
|
+
try {
|
|
18586
|
+
manifest = JSON.parse(manifestText);
|
|
18587
|
+
} catch {
|
|
18588
|
+
throw new Error("Invalid bundle: manifest.json is not valid JSON");
|
|
18589
|
+
}
|
|
18590
|
+
if (!manifest || typeof manifest !== "object" || typeof manifest.version !== "string") {
|
|
18591
|
+
throw new Error("Invalid bundle: manifest version is missing");
|
|
18592
|
+
}
|
|
18593
|
+
if (manifest.version === "2.0.0") {
|
|
18594
|
+
const v2 = manifest;
|
|
18595
|
+
const domain = await readV2(reader, v2);
|
|
18596
|
+
return importV2(domain, v2, store, conflict);
|
|
18597
|
+
}
|
|
18598
|
+
if (manifest.version.startsWith("1.")) {
|
|
18599
|
+
return importV1(reader, manifest, store, conflict);
|
|
18600
|
+
}
|
|
18601
|
+
throw new Error(`Unsupported Instructions archive version: ${manifest.version}`);
|
|
17978
18602
|
}
|
|
17979
18603
|
|
|
17980
18604
|
// src/index.ts
|
|
@@ -17983,9 +18607,9 @@ init_redact();
|
|
|
17983
18607
|
|
|
17984
18608
|
// src/lib/package-manager-guard.ts
|
|
17985
18609
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
17986
|
-
import { existsSync as
|
|
17987
|
-
import { homedir as
|
|
17988
|
-
import { basename as basename7, dirname as
|
|
18610
|
+
import { existsSync as existsSync19, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
|
|
18611
|
+
import { homedir as homedir10 } from "os";
|
|
18612
|
+
import { basename as basename7, dirname as dirname12, isAbsolute as isAbsolute8, join as join21, relative as relative8, resolve as resolve14 } from "path";
|
|
17989
18613
|
var SKIP_DIRS = new Set([
|
|
17990
18614
|
".git",
|
|
17991
18615
|
"node_modules",
|
|
@@ -18027,7 +18651,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
18027
18651
|
const findings = [];
|
|
18028
18652
|
let scannedFiles = 0;
|
|
18029
18653
|
for (const root of roots) {
|
|
18030
|
-
if (!
|
|
18654
|
+
if (!existsSync19(root))
|
|
18031
18655
|
continue;
|
|
18032
18656
|
const stat = lstatSync7(root);
|
|
18033
18657
|
if (stat.isFile()) {
|
|
@@ -18037,14 +18661,14 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
18037
18661
|
if (text === null)
|
|
18038
18662
|
continue;
|
|
18039
18663
|
scannedFiles++;
|
|
18040
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
18664
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname12(root)));
|
|
18041
18665
|
continue;
|
|
18042
18666
|
}
|
|
18043
18667
|
if (!stat.isDirectory())
|
|
18044
18668
|
continue;
|
|
18045
18669
|
const tracked = trackedFiles(root);
|
|
18046
18670
|
for (const file of collectRepoFiles(root)) {
|
|
18047
|
-
const rel = toPosix(
|
|
18671
|
+
const rel = toPosix(relative8(root, file));
|
|
18048
18672
|
const isTracked = tracked.has(rel);
|
|
18049
18673
|
const text = readTextFile(file);
|
|
18050
18674
|
if (text === null)
|
|
@@ -18054,10 +18678,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
18054
18678
|
}
|
|
18055
18679
|
}
|
|
18056
18680
|
if (options.includeHome) {
|
|
18057
|
-
const home =
|
|
18681
|
+
const home = homedir10();
|
|
18058
18682
|
for (const name of HOME_FILES) {
|
|
18059
|
-
const file =
|
|
18060
|
-
if (!
|
|
18683
|
+
const file = join21(home, name);
|
|
18684
|
+
if (!existsSync19(file))
|
|
18061
18685
|
continue;
|
|
18062
18686
|
const text = readTextFile(file);
|
|
18063
18687
|
if (text === null)
|
|
@@ -18081,12 +18705,12 @@ function collectRepoFiles(root) {
|
|
|
18081
18705
|
if (entry.isDirectory()) {
|
|
18082
18706
|
if (SKIP_DIRS.has(entry.name))
|
|
18083
18707
|
continue;
|
|
18084
|
-
visit(
|
|
18708
|
+
visit(join21(dir, entry.name));
|
|
18085
18709
|
continue;
|
|
18086
18710
|
}
|
|
18087
18711
|
if (!entry.isFile())
|
|
18088
18712
|
continue;
|
|
18089
|
-
const file =
|
|
18713
|
+
const file = join21(dir, entry.name);
|
|
18090
18714
|
if (shouldScanRepoFile(file))
|
|
18091
18715
|
out.push(file);
|
|
18092
18716
|
}
|
|
@@ -18124,7 +18748,7 @@ function readTextFile(file) {
|
|
|
18124
18748
|
const stat = lstatSync7(file);
|
|
18125
18749
|
if (!stat.isFile() || stat.size > 5000000)
|
|
18126
18750
|
return null;
|
|
18127
|
-
const buf =
|
|
18751
|
+
const buf = readFileSync17(file);
|
|
18128
18752
|
if (buf.includes(0))
|
|
18129
18753
|
return null;
|
|
18130
18754
|
return buf.toString("utf-8");
|
|
@@ -18324,11 +18948,11 @@ function trackedFiles(root) {
|
|
|
18324
18948
|
}
|
|
18325
18949
|
function isTrackedFile(file) {
|
|
18326
18950
|
try {
|
|
18327
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
18951
|
+
const repoRoot = execFileSync2("git", ["-C", dirname12(file), "rev-parse", "--show-toplevel"], {
|
|
18328
18952
|
encoding: "utf-8",
|
|
18329
18953
|
stdio: ["ignore", "pipe", "ignore"]
|
|
18330
18954
|
}).trim();
|
|
18331
|
-
const rel = toPosix(
|
|
18955
|
+
const rel = toPosix(relative8(repoRoot, file));
|
|
18332
18956
|
execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
|
|
18333
18957
|
stdio: ["ignore", "ignore", "ignore"]
|
|
18334
18958
|
});
|
|
@@ -18354,13 +18978,13 @@ function stripInlineComment(value) {
|
|
|
18354
18978
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
18355
18979
|
}
|
|
18356
18980
|
function displayPath(file, root) {
|
|
18357
|
-
const home =
|
|
18981
|
+
const home = homedir10();
|
|
18358
18982
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
18359
|
-
return "~/" + toPosix(
|
|
18360
|
-
if (
|
|
18361
|
-
return toPosix(
|
|
18983
|
+
return "~/" + toPosix(relative8(home, file));
|
|
18984
|
+
if (isAbsolute8(root) && file.startsWith(root + "/"))
|
|
18985
|
+
return toPosix(relative8(root, file));
|
|
18362
18986
|
if (file === home || file.startsWith(home + "/"))
|
|
18363
|
-
return "~/" + toPosix(
|
|
18987
|
+
return "~/" + toPosix(relative8(home, file));
|
|
18364
18988
|
return file;
|
|
18365
18989
|
}
|
|
18366
18990
|
function toPosix(path) {
|
|
@@ -18526,6 +19150,7 @@ export {
|
|
|
18526
19150
|
INSTRUCTION_ACTIVATION_MODES,
|
|
18527
19151
|
INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS,
|
|
18528
19152
|
INSTRUCTIONS_LOCAL_OPT_IN_ENV,
|
|
19153
|
+
INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
|
|
18529
19154
|
INBOX_CONVERSATIONS_MINIMUM_VERSION,
|
|
18530
19155
|
GLOBAL_AGENT_RULES_STANDARD_SLUG,
|
|
18531
19156
|
GLOBAL_AGENT_RULES_STANDARD_CONTENT,
|