@hasna/instructions 0.4.42 → 0.5.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/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts +2 -0
- package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts.map +1 -0
- package/dist/cli/index.js +1179 -195
- package/dist/cli/station-profile.test.d.ts +2 -0
- package/dist/cli/station-profile.test.d.ts.map +1 -0
- package/dist/generated/storage-kit/backend.d.ts +0 -1
- package/dist/generated/storage-kit/backend.d.ts.map +1 -1
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +685 -196
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/instruction-graph.d.ts +4 -0
- package/dist/lib/instruction-graph.d.ts.map +1 -1
- package/dist/lib/provider-context.d.ts +94 -0
- package/dist/lib/provider-context.d.ts.map +1 -0
- package/dist/lib/provider-context.test.d.ts +2 -0
- package/dist/lib/provider-context.test.d.ts.map +1 -0
- package/dist/lib/session-apply.d.ts +7 -0
- package/dist/lib/session-apply.d.ts.map +1 -1
- package/dist/lib/session-authority.d.ts +22 -2
- package/dist/lib/session-authority.d.ts.map +1 -1
- package/dist/lib/session-render.d.ts +9 -1
- package/dist/lib/session-render.d.ts.map +1 -1
- package/dist/lib/station-profile.d.ts +102 -0
- package/dist/lib/station-profile.d.ts.map +1 -0
- package/dist/lib/station-profile.test.d.ts +2 -0
- package/dist/lib/station-profile.test.d.ts.map +1 -0
- package/dist/lib/sync-dir.d.ts.map +1 -1
- package/dist/lib/sync.d.ts.map +1 -1
- package/dist/mcp/early-args.test.d.ts +2 -0
- package/dist/mcp/early-args.test.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +125 -76
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/early-args.test.d.ts +2 -0
- package/dist/server/early-args.test.d.ts.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +38 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -10846,12 +10846,24 @@ var init_cursor_authority = __esm(() => {
|
|
|
10846
10846
|
|
|
10847
10847
|
// src/lib/session-authority.ts
|
|
10848
10848
|
import { createHash as createHash5 } from "crypto";
|
|
10849
|
-
import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
10849
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
|
|
10850
|
+
import { homedir as homedir5 } from "os";
|
|
10850
10851
|
import { join as join7, resolve as resolve5 } from "path";
|
|
10851
10852
|
function sha2565(content) {
|
|
10852
10853
|
return createHash5("sha256").update(content).digest("hex");
|
|
10853
10854
|
}
|
|
10854
|
-
function
|
|
10855
|
+
function configHomeDir() {
|
|
10856
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
|
|
10857
|
+
}
|
|
10858
|
+
function normalizeOwnedTargetPath(p) {
|
|
10859
|
+
const expanded = p.startsWith("~/") ? resolve5(configHomeDir(), p.slice(2)) : resolve5(p);
|
|
10860
|
+
try {
|
|
10861
|
+
return realpathSync(expanded);
|
|
10862
|
+
} catch {
|
|
10863
|
+
return expanded;
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
|
|
10855
10867
|
const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
10856
10868
|
let stat;
|
|
10857
10869
|
try {
|
|
@@ -10895,6 +10907,24 @@ function detectClaudeAuthorityConflicts(targetHome) {
|
|
|
10895
10907
|
}];
|
|
10896
10908
|
}
|
|
10897
10909
|
const content = readFileSync3(authorityPath, "utf8");
|
|
10910
|
+
const owned = ownedAuthorities.find((authority) => normalizeOwnedTargetPath(authority.targetPath) === normalizeOwnedTargetPath(authorityPath));
|
|
10911
|
+
if (owned) {
|
|
10912
|
+
if (owned.content === content)
|
|
10913
|
+
return [];
|
|
10914
|
+
return [{
|
|
10915
|
+
...provenanceBase,
|
|
10916
|
+
kind: "unknown-unmanaged-authority",
|
|
10917
|
+
sha256: sha2565(content),
|
|
10918
|
+
markers: [],
|
|
10919
|
+
provenance: {
|
|
10920
|
+
source: "filesystem",
|
|
10921
|
+
authority: "unmanaged",
|
|
10922
|
+
observedPath: authorityPath,
|
|
10923
|
+
detection: "owned-config-drift"
|
|
10924
|
+
},
|
|
10925
|
+
reason: `Claude target AGENTS.md is owned by registered config "${owned.slug}", but the disk file drifts from its stored content; re-sync the config through the instructions pipeline before applying.`
|
|
10926
|
+
}];
|
|
10927
|
+
}
|
|
10898
10928
|
const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
|
|
10899
10929
|
const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
|
|
10900
10930
|
return [{
|
|
@@ -10923,9 +10953,9 @@ var init_session_authority = __esm(() => {
|
|
|
10923
10953
|
|
|
10924
10954
|
// src/lib/session-render.ts
|
|
10925
10955
|
import { createHash as createHash6 } from "crypto";
|
|
10926
|
-
import { existsSync as
|
|
10927
|
-
import { homedir as
|
|
10928
|
-
import { basename as
|
|
10956
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
|
|
10957
|
+
import { homedir as homedir6 } from "os";
|
|
10958
|
+
import { basename as basename4, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute3, join as join8, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
|
|
10929
10959
|
function normalizeSessionInstructionLayer(value) {
|
|
10930
10960
|
if (value === "provider")
|
|
10931
10961
|
return "tool";
|
|
@@ -11011,7 +11041,7 @@ function yamlQuote2(value) {
|
|
|
11011
11041
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
11012
11042
|
}
|
|
11013
11043
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
11014
|
-
const home = process.env["HOME"] ||
|
|
11044
|
+
const home = process.env["HOME"] || homedir6();
|
|
11015
11045
|
return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
11016
11046
|
}
|
|
11017
11047
|
function joinTarget(targetHome, relativePath) {
|
|
@@ -11586,7 +11616,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
|
|
|
11586
11616
|
...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
|
|
11587
11617
|
]);
|
|
11588
11618
|
const existingConfigPath = joinTarget(targetHome, adapter.configFile);
|
|
11589
|
-
const selectedConfig =
|
|
11619
|
+
const selectedConfig = existsSync6(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
|
|
11590
11620
|
const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
|
|
11591
11621
|
const config = {
|
|
11592
11622
|
...selectedConfig,
|
|
@@ -11775,7 +11805,7 @@ function adapterFor(input) {
|
|
|
11775
11805
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
11776
11806
|
}
|
|
11777
11807
|
function getHomeDir() {
|
|
11778
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
11808
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
11779
11809
|
}
|
|
11780
11810
|
function cleanSessionPathInput(path) {
|
|
11781
11811
|
const trimmed = path.trim();
|
|
@@ -11913,7 +11943,7 @@ function planSessionRender(input) {
|
|
|
11913
11943
|
blockers: targetBlockers
|
|
11914
11944
|
} = resolveRenderTarget(input);
|
|
11915
11945
|
const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
|
|
11916
|
-
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
|
|
11946
|
+
const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome, input.ownedClaudeAuthorities) : [];
|
|
11917
11947
|
const blockers = [
|
|
11918
11948
|
...targetBlockers,
|
|
11919
11949
|
...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
|
|
@@ -12051,7 +12081,7 @@ function planSessionRender(input) {
|
|
|
12051
12081
|
sourceId: input.providerConfig.sourceId,
|
|
12052
12082
|
selectedPayloadSha256: sha2566(input.providerConfig.content),
|
|
12053
12083
|
renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
|
|
12054
|
-
selected: !
|
|
12084
|
+
selected: !existsSync6(joinTarget(targetHome, adapter.configFile))
|
|
12055
12085
|
}
|
|
12056
12086
|
} : {},
|
|
12057
12087
|
...projectContext ? {
|
|
@@ -12090,7 +12120,7 @@ function planSessionRender(input) {
|
|
|
12090
12120
|
};
|
|
12091
12121
|
}
|
|
12092
12122
|
function sourceFromFilePath(path, content, order = 0) {
|
|
12093
|
-
const file =
|
|
12123
|
+
const file = basename4(path);
|
|
12094
12124
|
return {
|
|
12095
12125
|
id: file.replace(extname2(file), ""),
|
|
12096
12126
|
label: file,
|
|
@@ -12312,7 +12342,7 @@ function layerFromIdentityKind(kind, exportShape) {
|
|
|
12312
12342
|
function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
|
|
12313
12343
|
if (sourcePaths.length === 0 || !exportPath)
|
|
12314
12344
|
return;
|
|
12315
|
-
const baseDir =
|
|
12345
|
+
const baseDir = dirname3(resolveSessionPath(exportPath));
|
|
12316
12346
|
const contents = [];
|
|
12317
12347
|
for (const sourcePath of sourcePaths) {
|
|
12318
12348
|
const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
|
|
@@ -12330,7 +12360,7 @@ ${item.content.trimEnd()}`).join(`
|
|
|
12330
12360
|
}
|
|
12331
12361
|
function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
12332
12362
|
const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
|
|
12333
|
-
if (!
|
|
12363
|
+
if (!existsSync6(resolvedPath)) {
|
|
12334
12364
|
if (sourcePath.required) {
|
|
12335
12365
|
throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
|
|
12336
12366
|
}
|
|
@@ -12340,8 +12370,8 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
|
12340
12370
|
if (!stat.isFile()) {
|
|
12341
12371
|
throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
|
|
12342
12372
|
}
|
|
12343
|
-
const realBase =
|
|
12344
|
-
const realPath =
|
|
12373
|
+
const realBase = realpathSync2(baseDir);
|
|
12374
|
+
const realPath = realpathSync2(resolvedPath);
|
|
12345
12375
|
if (!pathIsInside(realPath, realBase)) {
|
|
12346
12376
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
|
|
12347
12377
|
}
|
|
@@ -12974,6 +13004,7 @@ function planProfileSessionRender(input) {
|
|
|
12974
13004
|
asset_surface: _assetSurface,
|
|
12975
13005
|
allow_asset_installers: _allowAssetInstallers,
|
|
12976
13006
|
graph_context: _graphContext,
|
|
13007
|
+
extra_sources: _extraSources,
|
|
12977
13008
|
assetPlan: _callerAssetPlan,
|
|
12978
13009
|
assetContents: _callerAssetContents,
|
|
12979
13010
|
...renderInput
|
|
@@ -12987,7 +13018,7 @@ function planProfileSessionRender(input) {
|
|
|
12987
13018
|
...planSessionRender({
|
|
12988
13019
|
...renderInput,
|
|
12989
13020
|
...compiled.capability.session_surface ? { providerSurface: compiled.capability.session_surface } : {},
|
|
12990
|
-
sources: compiled.sources,
|
|
13021
|
+
sources: [...compiled.sources, ...input.extra_sources ?? []],
|
|
12991
13022
|
assetPlan,
|
|
12992
13023
|
assetContents: Object.fromEntries((input.asset_configs ?? []).map((config) => [config.id, config.content]))
|
|
12993
13024
|
}),
|
|
@@ -14019,8 +14050,8 @@ var init_config_store = __esm(() => {
|
|
|
14019
14050
|
});
|
|
14020
14051
|
|
|
14021
14052
|
// src/lib/session-render-ownership.ts
|
|
14022
|
-
import { existsSync as
|
|
14023
|
-
import { dirname as
|
|
14053
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
14054
|
+
import { dirname as dirname4, join as join9, parse as parse3, relative as relative3, sep } from "path";
|
|
14024
14055
|
function toSegments(absolutePath2) {
|
|
14025
14056
|
return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
14026
14057
|
}
|
|
@@ -14039,7 +14070,7 @@ function pathIsSessionRenderManagedDir(absolutePath2) {
|
|
|
14039
14070
|
function readManifestRelativePaths(manifestPath) {
|
|
14040
14071
|
let stats;
|
|
14041
14072
|
try {
|
|
14042
|
-
if (!
|
|
14073
|
+
if (!existsSync7(manifestPath))
|
|
14043
14074
|
return null;
|
|
14044
14075
|
stats = statSync4(manifestPath);
|
|
14045
14076
|
} catch {
|
|
@@ -14066,7 +14097,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
14066
14097
|
}
|
|
14067
14098
|
function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
14068
14099
|
const root = parse3(absolutePath2).root;
|
|
14069
|
-
let home =
|
|
14100
|
+
let home = dirname4(absolutePath2);
|
|
14070
14101
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
14071
14102
|
const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
14072
14103
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
@@ -14075,7 +14106,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
|
14075
14106
|
if (relativePaths.has(claimed))
|
|
14076
14107
|
return true;
|
|
14077
14108
|
}
|
|
14078
|
-
const parent =
|
|
14109
|
+
const parent = dirname4(home);
|
|
14079
14110
|
if (parent === home || home === root)
|
|
14080
14111
|
break;
|
|
14081
14112
|
home = parent;
|
|
@@ -14103,11 +14134,11 @@ __export(exports_apply, {
|
|
|
14103
14134
|
applyConfigs: () => applyConfigs,
|
|
14104
14135
|
applyConfig: () => applyConfig
|
|
14105
14136
|
});
|
|
14106
|
-
import { existsSync as
|
|
14107
|
-
import { basename as
|
|
14108
|
-
import { homedir as
|
|
14137
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
14138
|
+
import { basename as basename5, dirname as dirname5, join as join10, resolve as resolve7 } from "path";
|
|
14139
|
+
import { homedir as homedir7 } from "os";
|
|
14109
14140
|
function getConfigHome() {
|
|
14110
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
14141
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
|
|
14111
14142
|
}
|
|
14112
14143
|
function expandPath(p) {
|
|
14113
14144
|
if (p.startsWith("~/")) {
|
|
@@ -14118,20 +14149,20 @@ function expandPath(p) {
|
|
|
14118
14149
|
function normalizeTargetPath(p) {
|
|
14119
14150
|
const expanded = expandPath(p);
|
|
14120
14151
|
try {
|
|
14121
|
-
return
|
|
14152
|
+
return realpathSync3(expanded);
|
|
14122
14153
|
} catch {
|
|
14123
14154
|
let current = expanded;
|
|
14124
14155
|
const missingSegments = [];
|
|
14125
14156
|
while (true) {
|
|
14126
|
-
if (
|
|
14157
|
+
if (existsSync8(current)) {
|
|
14127
14158
|
try {
|
|
14128
|
-
return resolve7(
|
|
14159
|
+
return resolve7(realpathSync3(current), ...missingSegments);
|
|
14129
14160
|
} catch {
|
|
14130
14161
|
return expanded;
|
|
14131
14162
|
}
|
|
14132
14163
|
}
|
|
14133
|
-
const parent =
|
|
14134
|
-
const name =
|
|
14164
|
+
const parent = dirname5(current);
|
|
14165
|
+
const name = basename5(current);
|
|
14135
14166
|
if (parent === current)
|
|
14136
14167
|
return expanded;
|
|
14137
14168
|
missingSegments.unshift(name);
|
|
@@ -14154,11 +14185,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
14154
14185
|
}
|
|
14155
14186
|
const path = expandPath(renderedTargetPath);
|
|
14156
14187
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
14157
|
-
const previousContent =
|
|
14188
|
+
const previousContent = existsSync8(path) ? readFileSync6(path, "utf-8") : null;
|
|
14158
14189
|
const changed = previousContent !== renderedForTarget;
|
|
14159
14190
|
if (!opts.dryRun) {
|
|
14160
|
-
const dir =
|
|
14161
|
-
if (!
|
|
14191
|
+
const dir = dirname5(path);
|
|
14192
|
+
if (!existsSync8(dir)) {
|
|
14162
14193
|
mkdirSync3(dir, { recursive: true });
|
|
14163
14194
|
}
|
|
14164
14195
|
if (previousContent !== null && changed) {
|
|
@@ -14192,7 +14223,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
14192
14223
|
let current;
|
|
14193
14224
|
try {
|
|
14194
14225
|
const path = expandPath(targetPath);
|
|
14195
|
-
if (!
|
|
14226
|
+
if (!existsSync8(path))
|
|
14196
14227
|
return [];
|
|
14197
14228
|
current = readFileSync6(path, "utf-8");
|
|
14198
14229
|
} catch {
|
|
@@ -14282,7 +14313,7 @@ async function applyPreparedConfig(config, opts) {
|
|
|
14282
14313
|
changed: outputResults.some((output) => output.changed)
|
|
14283
14314
|
};
|
|
14284
14315
|
}
|
|
14285
|
-
if (!opts.dryRun) {
|
|
14316
|
+
if (!opts.dryRun && result.changed) {
|
|
14286
14317
|
await store.updateConfig(config.id, { synced_at: new Date().toISOString() });
|
|
14287
14318
|
}
|
|
14288
14319
|
return result;
|
|
@@ -14536,20 +14567,20 @@ var init_apply = __esm(() => {
|
|
|
14536
14567
|
});
|
|
14537
14568
|
|
|
14538
14569
|
// src/lib/sync-dir.ts
|
|
14539
|
-
import { existsSync as
|
|
14570
|
+
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
14540
14571
|
import { join as join11, relative as relative4 } from "path";
|
|
14541
|
-
import { homedir as
|
|
14572
|
+
import { homedir as homedir8 } from "os";
|
|
14542
14573
|
function shouldSkip(p) {
|
|
14543
14574
|
return SKIP.some((s) => p.includes(s));
|
|
14544
14575
|
}
|
|
14545
14576
|
async function syncFromDir(dir, opts = {}) {
|
|
14546
14577
|
const store = opts.store ?? resolveConfigStore();
|
|
14547
14578
|
const absDir = expandPath(dir);
|
|
14548
|
-
if (!
|
|
14579
|
+
if (!existsSync9(absDir))
|
|
14549
14580
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14550
14581
|
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
|
|
14551
14582
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14552
|
-
const home =
|
|
14583
|
+
const home = homedir8();
|
|
14553
14584
|
const allConfigs = await store.listConfigs();
|
|
14554
14585
|
for (const file of files) {
|
|
14555
14586
|
if (shouldSkip(file)) {
|
|
@@ -14563,14 +14594,15 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14563
14594
|
continue;
|
|
14564
14595
|
}
|
|
14565
14596
|
const targetPath = file.replace(home, "~");
|
|
14597
|
+
const redacted = redactContent(content, redactFormatForTarget(targetPath, detectFormat(file)));
|
|
14566
14598
|
const existing = allConfigs.find((c) => c.target_path === targetPath);
|
|
14567
14599
|
if (!existing) {
|
|
14568
14600
|
if (!opts.dryRun)
|
|
14569
|
-
await store.createConfig({ name: relative4(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content });
|
|
14601
|
+
await store.createConfig({ name: relative4(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
|
|
14570
14602
|
result.added++;
|
|
14571
|
-
} else if (existing.content !== content) {
|
|
14603
|
+
} else if (existing.content !== redacted.content) {
|
|
14572
14604
|
if (!opts.dryRun)
|
|
14573
|
-
await store.updateConfig(existing.id, { content });
|
|
14605
|
+
await store.updateConfig(existing.id, { content: redacted.content });
|
|
14574
14606
|
result.updated++;
|
|
14575
14607
|
} else {
|
|
14576
14608
|
result.unchanged++;
|
|
@@ -14583,7 +14615,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14583
14615
|
}
|
|
14584
14616
|
async function syncToDir(dir, opts = {}) {
|
|
14585
14617
|
const store = opts.store ?? resolveConfigStore();
|
|
14586
|
-
const home =
|
|
14618
|
+
const home = homedir8();
|
|
14587
14619
|
const absDir = expandPath(dir);
|
|
14588
14620
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14589
14621
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14622,6 +14654,7 @@ var init_sync_dir = __esm(() => {
|
|
|
14622
14654
|
init_config_store();
|
|
14623
14655
|
init_apply();
|
|
14624
14656
|
init_sync();
|
|
14657
|
+
init_redact();
|
|
14625
14658
|
SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
14626
14659
|
});
|
|
14627
14660
|
|
|
@@ -14641,10 +14674,10 @@ __export(exports_sync, {
|
|
|
14641
14674
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS,
|
|
14642
14675
|
CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
|
|
14643
14676
|
});
|
|
14644
|
-
import { existsSync as
|
|
14645
|
-
import { basename as
|
|
14677
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
14678
|
+
import { basename as basename6, extname as extname3, join as join12 } from "path";
|
|
14646
14679
|
function claudeRuleOutputs(fileName) {
|
|
14647
|
-
const stem =
|
|
14680
|
+
const stem = basename6(fileName, extname3(fileName));
|
|
14648
14681
|
return [
|
|
14649
14682
|
{ agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
|
|
14650
14683
|
];
|
|
@@ -14681,15 +14714,15 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
14681
14714
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
14682
14715
|
}
|
|
14683
14716
|
function hasClaudePromptSource() {
|
|
14684
|
-
return
|
|
14717
|
+
return existsSync10(expandPath("~/.claude/CLAUDE.md"));
|
|
14685
14718
|
}
|
|
14686
14719
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
14687
14720
|
const absoluteTargetPath = expandPath(targetPath);
|
|
14688
14721
|
const absolutePrefix = expandPath("~/.cursor/rules");
|
|
14689
14722
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
14690
14723
|
return false;
|
|
14691
|
-
const stem =
|
|
14692
|
-
return
|
|
14724
|
+
const stem = basename6(absoluteTargetPath, ".mdc");
|
|
14725
|
+
return existsSync10(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync10(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
14693
14726
|
}
|
|
14694
14727
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
14695
14728
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -14707,7 +14740,7 @@ async function syncProject(opts) {
|
|
|
14707
14740
|
const machine = detectMachineContext();
|
|
14708
14741
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14709
14742
|
const abs = join12(absDir, pf.file);
|
|
14710
|
-
if (!
|
|
14743
|
+
if (!existsSync10(abs))
|
|
14711
14744
|
continue;
|
|
14712
14745
|
try {
|
|
14713
14746
|
const rawContent = readFileSync8(abs, "utf-8");
|
|
@@ -14747,7 +14780,7 @@ async function syncProject(opts) {
|
|
|
14747
14780
|
{ dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
14748
14781
|
{ dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14749
14782
|
]) {
|
|
14750
|
-
if (!
|
|
14783
|
+
if (!existsSync10(ruleDir.dir))
|
|
14751
14784
|
continue;
|
|
14752
14785
|
const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14753
14786
|
for (const f of mdFiles) {
|
|
@@ -14791,7 +14824,7 @@ async function syncKnown(opts = {}) {
|
|
|
14791
14824
|
for (const known of targets) {
|
|
14792
14825
|
if (known.rulesDir) {
|
|
14793
14826
|
const absDir = expandPath(known.rulesDir);
|
|
14794
|
-
if (!
|
|
14827
|
+
if (!existsSync10(absDir)) {
|
|
14795
14828
|
result.skipped.push(known.rulesDir);
|
|
14796
14829
|
continue;
|
|
14797
14830
|
}
|
|
@@ -14832,7 +14865,7 @@ async function syncKnown(opts = {}) {
|
|
|
14832
14865
|
continue;
|
|
14833
14866
|
}
|
|
14834
14867
|
const abs = expandPath(known.path);
|
|
14835
|
-
if (!
|
|
14868
|
+
if (!existsSync10(abs)) {
|
|
14836
14869
|
result.skipped.push(known.path);
|
|
14837
14870
|
continue;
|
|
14838
14871
|
}
|
|
@@ -14843,7 +14876,7 @@ async function syncKnown(opts = {}) {
|
|
|
14843
14876
|
continue;
|
|
14844
14877
|
}
|
|
14845
14878
|
const fmt = known.format ?? detectFormat(abs);
|
|
14846
|
-
const redacted = redactContent(rawContent, fmt);
|
|
14879
|
+
const redacted = redactContent(rawContent, redactFormatForTarget(abs, fmt));
|
|
14847
14880
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14848
14881
|
const content = machineAware.content;
|
|
14849
14882
|
const isTemplate2 = redacted.isTemplate || machineAware.changed;
|
|
@@ -14938,7 +14971,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
14938
14971
|
}
|
|
14939
14972
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
14940
14973
|
const path = expandPath(targetPath);
|
|
14941
|
-
if (!
|
|
14974
|
+
if (!existsSync10(path))
|
|
14942
14975
|
return `(file not found on disk: ${path})`;
|
|
14943
14976
|
const diskContent = readFileSync8(path, "utf-8");
|
|
14944
14977
|
if (diskContent === expectedContent)
|
|
@@ -15173,18 +15206,18 @@ __export(exports_package_manager_guard, {
|
|
|
15173
15206
|
scanPackageManagerSecrets: () => scanPackageManagerSecrets
|
|
15174
15207
|
});
|
|
15175
15208
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15176
|
-
import { existsSync as
|
|
15177
|
-
import { homedir as
|
|
15178
|
-
import { basename as
|
|
15209
|
+
import { existsSync as existsSync19, lstatSync as lstatSync7, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
|
|
15210
|
+
import { homedir as homedir11 } from "os";
|
|
15211
|
+
import { basename as basename7, dirname as dirname10, isAbsolute as isAbsolute5, join as join20, relative as relative7, resolve as resolve13 } from "path";
|
|
15179
15212
|
function scanPackageManagerSecrets(options = {}) {
|
|
15180
|
-
const cwd = options.cwd ?
|
|
15181
|
-
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) =>
|
|
15213
|
+
const cwd = options.cwd ? resolve13(options.cwd) : process.cwd();
|
|
15214
|
+
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve13(cwd, root));
|
|
15182
15215
|
const findings = [];
|
|
15183
15216
|
let scannedFiles = 0;
|
|
15184
15217
|
for (const root of roots) {
|
|
15185
|
-
if (!
|
|
15218
|
+
if (!existsSync19(root))
|
|
15186
15219
|
continue;
|
|
15187
|
-
const stat =
|
|
15220
|
+
const stat = lstatSync7(root);
|
|
15188
15221
|
if (stat.isFile()) {
|
|
15189
15222
|
if (!shouldScanRepoFile(root))
|
|
15190
15223
|
continue;
|
|
@@ -15192,7 +15225,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15192
15225
|
if (text === null)
|
|
15193
15226
|
continue;
|
|
15194
15227
|
scannedFiles++;
|
|
15195
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15228
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
|
|
15196
15229
|
continue;
|
|
15197
15230
|
}
|
|
15198
15231
|
if (!stat.isDirectory())
|
|
@@ -15209,10 +15242,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15209
15242
|
}
|
|
15210
15243
|
}
|
|
15211
15244
|
if (options.includeHome) {
|
|
15212
|
-
const home =
|
|
15245
|
+
const home = homedir11();
|
|
15213
15246
|
for (const name of HOME_FILES) {
|
|
15214
|
-
const file =
|
|
15215
|
-
if (!
|
|
15247
|
+
const file = join20(home, name);
|
|
15248
|
+
if (!existsSync19(file))
|
|
15216
15249
|
continue;
|
|
15217
15250
|
const text = readTextFile(file);
|
|
15218
15251
|
if (text === null)
|
|
@@ -15232,16 +15265,16 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15232
15265
|
function collectRepoFiles(root) {
|
|
15233
15266
|
const out = [];
|
|
15234
15267
|
const visit = (dir) => {
|
|
15235
|
-
for (const entry of
|
|
15268
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
15236
15269
|
if (entry.isDirectory()) {
|
|
15237
15270
|
if (SKIP_DIRS.has(entry.name))
|
|
15238
15271
|
continue;
|
|
15239
|
-
visit(
|
|
15272
|
+
visit(join20(dir, entry.name));
|
|
15240
15273
|
continue;
|
|
15241
15274
|
}
|
|
15242
15275
|
if (!entry.isFile())
|
|
15243
15276
|
continue;
|
|
15244
|
-
const file =
|
|
15277
|
+
const file = join20(dir, entry.name);
|
|
15245
15278
|
if (shouldScanRepoFile(file))
|
|
15246
15279
|
out.push(file);
|
|
15247
15280
|
}
|
|
@@ -15250,11 +15283,11 @@ function collectRepoFiles(root) {
|
|
|
15250
15283
|
return out;
|
|
15251
15284
|
}
|
|
15252
15285
|
function shouldScanRepoFile(file) {
|
|
15253
|
-
const name =
|
|
15286
|
+
const name = basename7(file);
|
|
15254
15287
|
return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
|
|
15255
15288
|
}
|
|
15256
15289
|
function classifyRepoFile(file) {
|
|
15257
|
-
const name =
|
|
15290
|
+
const name = basename7(file);
|
|
15258
15291
|
if (isNpmrcName(name))
|
|
15259
15292
|
return "repo-npmrc";
|
|
15260
15293
|
if (isBunConfigName(name))
|
|
@@ -15276,10 +15309,10 @@ function isNpmrcName(name) {
|
|
|
15276
15309
|
}
|
|
15277
15310
|
function readTextFile(file) {
|
|
15278
15311
|
try {
|
|
15279
|
-
const stat =
|
|
15312
|
+
const stat = lstatSync7(file);
|
|
15280
15313
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15281
15314
|
return null;
|
|
15282
|
-
const buf =
|
|
15315
|
+
const buf = readFileSync16(file);
|
|
15283
15316
|
if (buf.includes(0))
|
|
15284
15317
|
return null;
|
|
15285
15318
|
return buf.toString("utf-8");
|
|
@@ -15479,7 +15512,7 @@ function trackedFiles(root) {
|
|
|
15479
15512
|
}
|
|
15480
15513
|
function isTrackedFile(file) {
|
|
15481
15514
|
try {
|
|
15482
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
15515
|
+
const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
|
|
15483
15516
|
encoding: "utf-8",
|
|
15484
15517
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15485
15518
|
}).trim();
|
|
@@ -15509,7 +15542,7 @@ function stripInlineComment(value) {
|
|
|
15509
15542
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15510
15543
|
}
|
|
15511
15544
|
function displayPath(file, root) {
|
|
15512
|
-
const home =
|
|
15545
|
+
const home = homedir11();
|
|
15513
15546
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15514
15547
|
return "~/" + toPosix(relative7(home, file));
|
|
15515
15548
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|
|
@@ -15567,8 +15600,12 @@ import { existsSync } from "fs";
|
|
|
15567
15600
|
import { homedir } from "os";
|
|
15568
15601
|
import { join } from "path";
|
|
15569
15602
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
15603
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
15604
|
+
import { isIP } from "net";
|
|
15570
15605
|
import { randomUUID } from "crypto";
|
|
15571
15606
|
import { spawn } from "child_process";
|
|
15607
|
+
import { request as nodeHttpRequest } from "http";
|
|
15608
|
+
import { request as nodeHttpsRequest } from "https";
|
|
15572
15609
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
15573
15610
|
function getPathValue(input, path) {
|
|
15574
15611
|
return path.split(".").reduce((value, part) => {
|
|
@@ -15971,6 +16008,214 @@ function signPayload(secret, timestamp, body) {
|
|
|
15971
16008
|
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
15972
16009
|
return `sha256=${digest}`;
|
|
15973
16010
|
}
|
|
16011
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
16012
|
+
var IPV4_PRIVATE_RANGES = [
|
|
16013
|
+
[0, 16777215],
|
|
16014
|
+
[167772160, 184549375],
|
|
16015
|
+
[1681915904, 1686110207],
|
|
16016
|
+
[2130706432, 2147483647],
|
|
16017
|
+
[2851995648, 2852061183],
|
|
16018
|
+
[2886729728, 2887778303],
|
|
16019
|
+
[3221225472, 3221225727],
|
|
16020
|
+
[3221225984, 3221226239],
|
|
16021
|
+
[3227017984, 3227018239],
|
|
16022
|
+
[3232235520, 3232301055],
|
|
16023
|
+
[3323068416, 3323199487],
|
|
16024
|
+
[3325256704, 3325256959],
|
|
16025
|
+
[3405803776, 3405804031],
|
|
16026
|
+
[3758096384, 4294967295]
|
|
16027
|
+
];
|
|
16028
|
+
var IPV6_SPECIAL_PREFIXES = [
|
|
16029
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
|
|
16030
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
|
|
16031
|
+
{ groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
|
|
16032
|
+
{ groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
|
|
16033
|
+
{ groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
|
|
16034
|
+
{ groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
16035
|
+
{ groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
|
|
16036
|
+
{ groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
|
|
16037
|
+
{ groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
16038
|
+
{ groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
|
|
16039
|
+
{ groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
|
|
16040
|
+
{ groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
|
|
16041
|
+
{ groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
16042
|
+
{ groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
16043
|
+
{ groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
|
|
16044
|
+
];
|
|
16045
|
+
function isPrivateAddress(address) {
|
|
16046
|
+
const normalized = stripZoneId(address);
|
|
16047
|
+
const version = isIP(normalized);
|
|
16048
|
+
if (version === 4) {
|
|
16049
|
+
const integer = ipv4ToInt(normalized);
|
|
16050
|
+
if (integer === undefined)
|
|
16051
|
+
return true;
|
|
16052
|
+
return IPV4_PRIVATE_RANGES.some(([low, high]) => integer >= low && integer <= high);
|
|
16053
|
+
}
|
|
16054
|
+
if (version === 6) {
|
|
16055
|
+
const groups = ipv6Groups(normalized);
|
|
16056
|
+
if (!groups)
|
|
16057
|
+
return true;
|
|
16058
|
+
for (const prefix of IPV6_SPECIAL_PREFIXES) {
|
|
16059
|
+
if (!ipv6MatchesPrefix(groups, prefix.groups, prefix.bits))
|
|
16060
|
+
continue;
|
|
16061
|
+
if (prefix.bits === 96 && groups[5] === 65535) {
|
|
16062
|
+
return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
|
|
16063
|
+
}
|
|
16064
|
+
if (prefix.bits === 16 && groups[0] === 8194) {
|
|
16065
|
+
return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
|
|
16066
|
+
}
|
|
16067
|
+
return true;
|
|
16068
|
+
}
|
|
16069
|
+
return false;
|
|
16070
|
+
}
|
|
16071
|
+
return true;
|
|
16072
|
+
}
|
|
16073
|
+
async function resolveWebhookTarget(url, policy = {}) {
|
|
16074
|
+
const hostname = normalizeHostname(url.hostname);
|
|
16075
|
+
const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
|
|
16076
|
+
if (allowlist.includes(hostname)) {
|
|
16077
|
+
const version2 = isIP(hostname);
|
|
16078
|
+
if (version2 === 4 || version2 === 6) {
|
|
16079
|
+
return { hostname, addresses: [hostname] };
|
|
16080
|
+
}
|
|
16081
|
+
const lookup2 = policy.lookup ?? defaultTargetLookup;
|
|
16082
|
+
let resolved2;
|
|
16083
|
+
try {
|
|
16084
|
+
resolved2 = await lookup2(hostname);
|
|
16085
|
+
} catch {
|
|
16086
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
16087
|
+
}
|
|
16088
|
+
if (!Array.isArray(resolved2) || resolved2.length === 0) {
|
|
16089
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
16090
|
+
}
|
|
16091
|
+
const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
|
|
16092
|
+
return { hostname, addresses };
|
|
16093
|
+
}
|
|
16094
|
+
const version = isIP(hostname);
|
|
16095
|
+
if (version === 4 || version === 6) {
|
|
16096
|
+
if (isPrivateAddress(hostname)) {
|
|
16097
|
+
throw new Error(`Webhook target ${hostname} is a private or special-use address`);
|
|
16098
|
+
}
|
|
16099
|
+
return { hostname, addresses: [hostname] };
|
|
16100
|
+
}
|
|
16101
|
+
const lookup = policy.lookup ?? defaultTargetLookup;
|
|
16102
|
+
let resolved;
|
|
16103
|
+
try {
|
|
16104
|
+
resolved = await lookup(hostname);
|
|
16105
|
+
} catch {
|
|
16106
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
16107
|
+
}
|
|
16108
|
+
if (!Array.isArray(resolved) || resolved.length === 0) {
|
|
16109
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
16110
|
+
}
|
|
16111
|
+
const allowed = [];
|
|
16112
|
+
for (const entry of resolved) {
|
|
16113
|
+
const address = normalizeHostname(entry.address);
|
|
16114
|
+
if (isPrivateAddress(address)) {
|
|
16115
|
+
if (allowlist.includes(address)) {
|
|
16116
|
+
allowed.push(address);
|
|
16117
|
+
continue;
|
|
16118
|
+
}
|
|
16119
|
+
throw new Error(`Webhook target ${hostname} resolves to private or special-use address ${address}`);
|
|
16120
|
+
}
|
|
16121
|
+
allowed.push(address);
|
|
16122
|
+
}
|
|
16123
|
+
if (allowed.length === 0) {
|
|
16124
|
+
throw new Error(`Webhook target ${hostname} resolved to no public addresses`);
|
|
16125
|
+
}
|
|
16126
|
+
return { hostname, addresses: allowed };
|
|
16127
|
+
}
|
|
16128
|
+
function normalizeMaxRedirects(value) {
|
|
16129
|
+
if (value === undefined)
|
|
16130
|
+
return DEFAULT_MAX_REDIRECTS;
|
|
16131
|
+
if (!Number.isInteger(value) || value < 0)
|
|
16132
|
+
throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
|
|
16133
|
+
return value;
|
|
16134
|
+
}
|
|
16135
|
+
var defaultTargetLookup = async (hostname) => {
|
|
16136
|
+
return dnsLookup(hostname, { all: true, verbatim: false });
|
|
16137
|
+
};
|
|
16138
|
+
function normalizeHostname(hostname) {
|
|
16139
|
+
const lower = hostname.toLowerCase();
|
|
16140
|
+
if (lower.startsWith("[") && lower.endsWith("]"))
|
|
16141
|
+
return lower.slice(1, -1);
|
|
16142
|
+
return lower;
|
|
16143
|
+
}
|
|
16144
|
+
function stripZoneId(address) {
|
|
16145
|
+
const percent = address.indexOf("%");
|
|
16146
|
+
return percent === -1 ? address : address.slice(0, percent);
|
|
16147
|
+
}
|
|
16148
|
+
function ipv4ToInt(address) {
|
|
16149
|
+
const parts = address.split(".");
|
|
16150
|
+
if (parts.length !== 4)
|
|
16151
|
+
return;
|
|
16152
|
+
let value = 0;
|
|
16153
|
+
for (const part of parts) {
|
|
16154
|
+
if (!/^\d{1,3}$/.test(part))
|
|
16155
|
+
return;
|
|
16156
|
+
const octet = Number(part);
|
|
16157
|
+
if (octet > 255)
|
|
16158
|
+
return;
|
|
16159
|
+
value = value << 8 | octet;
|
|
16160
|
+
}
|
|
16161
|
+
return value >>> 0;
|
|
16162
|
+
}
|
|
16163
|
+
function ipv4IntToString(integer) {
|
|
16164
|
+
return [
|
|
16165
|
+
integer >>> 24 & 255,
|
|
16166
|
+
integer >>> 16 & 255,
|
|
16167
|
+
integer >>> 8 & 255,
|
|
16168
|
+
integer & 255
|
|
16169
|
+
].join(".");
|
|
16170
|
+
}
|
|
16171
|
+
function ipv6Groups(address) {
|
|
16172
|
+
const raw = stripZoneId(address);
|
|
16173
|
+
const doubleColon = raw.indexOf("::");
|
|
16174
|
+
const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
|
|
16175
|
+
const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
|
|
16176
|
+
const parseGroups = (text) => {
|
|
16177
|
+
if (text === "")
|
|
16178
|
+
return [];
|
|
16179
|
+
const out = [];
|
|
16180
|
+
for (const part of text.split(":")) {
|
|
16181
|
+
if (part.includes(".")) {
|
|
16182
|
+
const v4 = ipv4ToInt(part);
|
|
16183
|
+
if (v4 === undefined)
|
|
16184
|
+
return;
|
|
16185
|
+
out.push(v4 >>> 16 & 65535, v4 & 65535);
|
|
16186
|
+
} else {
|
|
16187
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part))
|
|
16188
|
+
return;
|
|
16189
|
+
out.push(parseInt(part, 16));
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16192
|
+
return out;
|
|
16193
|
+
};
|
|
16194
|
+
const head = parseGroups(headText);
|
|
16195
|
+
if (!head)
|
|
16196
|
+
return;
|
|
16197
|
+
const tail = parseGroups(tailText);
|
|
16198
|
+
if (!tail)
|
|
16199
|
+
return;
|
|
16200
|
+
const total = head.length + tail.length;
|
|
16201
|
+
if (doubleColon === -1) {
|
|
16202
|
+
return total === 8 ? head : undefined;
|
|
16203
|
+
}
|
|
16204
|
+
if (total >= 8)
|
|
16205
|
+
return;
|
|
16206
|
+
return [...head, ...new Array(8 - total).fill(0), ...tail];
|
|
16207
|
+
}
|
|
16208
|
+
function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
|
|
16209
|
+
let remaining = prefixBits;
|
|
16210
|
+
for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
|
|
16211
|
+
const take = Math.min(16, remaining);
|
|
16212
|
+
const mask = 65535 << 16 - take & 65535;
|
|
16213
|
+
if ((groups[index] & mask) !== (prefixGroups[index] & mask))
|
|
16214
|
+
return false;
|
|
16215
|
+
remaining -= take;
|
|
16216
|
+
}
|
|
16217
|
+
return true;
|
|
16218
|
+
}
|
|
15974
16219
|
function now() {
|
|
15975
16220
|
return new Date().toISOString();
|
|
15976
16221
|
}
|
|
@@ -16001,9 +16246,18 @@ function buildWebhookRequest(event, channel, options = {}) {
|
|
|
16001
16246
|
}
|
|
16002
16247
|
return { body, headers };
|
|
16003
16248
|
}
|
|
16249
|
+
function normalizeWebhookUrl(raw) {
|
|
16250
|
+
const url = new URL(raw);
|
|
16251
|
+
if (url.username !== "" || url.password !== "") {
|
|
16252
|
+
url.username = "";
|
|
16253
|
+
url.password = "";
|
|
16254
|
+
}
|
|
16255
|
+
return url.toString();
|
|
16256
|
+
}
|
|
16004
16257
|
async function dispatchWebhook(event, channel, options = {}) {
|
|
16005
16258
|
if (!channel.webhook)
|
|
16006
16259
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
16260
|
+
const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
|
|
16007
16261
|
const startedAt = now();
|
|
16008
16262
|
let secret = channel.webhook.secret;
|
|
16009
16263
|
if (channel.webhook.secretRef) {
|
|
@@ -16020,10 +16274,14 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
16020
16274
|
}
|
|
16021
16275
|
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
16022
16276
|
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
16277
|
+
const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
|
|
16278
|
+
if (validateTargets) {
|
|
16279
|
+
return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
|
|
16280
|
+
}
|
|
16023
16281
|
const controller = new AbortController;
|
|
16024
16282
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
16025
16283
|
try {
|
|
16026
|
-
const response = await (options.fetchImpl ?? fetch)(
|
|
16284
|
+
const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
|
|
16027
16285
|
method: "POST",
|
|
16028
16286
|
headers,
|
|
16029
16287
|
body,
|
|
@@ -16051,6 +16309,130 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
16051
16309
|
clearTimeout(timeout);
|
|
16052
16310
|
}
|
|
16053
16311
|
}
|
|
16312
|
+
function isRedirectStatus(status) {
|
|
16313
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
16314
|
+
}
|
|
16315
|
+
function redirectKeepsBody(status) {
|
|
16316
|
+
return status === 307 || status === 308;
|
|
16317
|
+
}
|
|
16318
|
+
async function pinnedNativeRequest(target, addresses, method, headers, body, signal, tls) {
|
|
16319
|
+
const isHttps = target.protocol === "https:";
|
|
16320
|
+
if (!isHttps && target.protocol !== "http:") {
|
|
16321
|
+
throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
|
|
16322
|
+
}
|
|
16323
|
+
const defaultPort = isHttps ? 443 : 80;
|
|
16324
|
+
const port = target.port ? Number(target.port) : defaultPort;
|
|
16325
|
+
const requestOptions = {
|
|
16326
|
+
hostname: target.hostname,
|
|
16327
|
+
port,
|
|
16328
|
+
path: `${target.pathname}${target.search}`,
|
|
16329
|
+
method,
|
|
16330
|
+
headers,
|
|
16331
|
+
...tls?.ca ? { ca: tls.ca } : {},
|
|
16332
|
+
lookup: (hostname, _options, callback) => {
|
|
16333
|
+
const entries = addresses.map((address) => ({
|
|
16334
|
+
address,
|
|
16335
|
+
family: address.includes(":") ? 6 : 4
|
|
16336
|
+
}));
|
|
16337
|
+
callback(null, entries);
|
|
16338
|
+
}
|
|
16339
|
+
};
|
|
16340
|
+
return new Promise((resolve, reject) => {
|
|
16341
|
+
const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
|
|
16342
|
+
const onAbort = () => {
|
|
16343
|
+
const error = new Error("The operation was aborted.");
|
|
16344
|
+
error.name = "AbortError";
|
|
16345
|
+
request.destroy(error);
|
|
16346
|
+
};
|
|
16347
|
+
if (signal.aborted)
|
|
16348
|
+
onAbort();
|
|
16349
|
+
else
|
|
16350
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
16351
|
+
request.on("error", reject);
|
|
16352
|
+
if (body !== undefined)
|
|
16353
|
+
request.write(body);
|
|
16354
|
+
request.end();
|
|
16355
|
+
function onResponse(response) {
|
|
16356
|
+
const chunks = [];
|
|
16357
|
+
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
16358
|
+
response.on("error", reject);
|
|
16359
|
+
response.on("end", () => {
|
|
16360
|
+
const headersRecord = {};
|
|
16361
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
16362
|
+
if (typeof value === "string")
|
|
16363
|
+
headersRecord[name] = value;
|
|
16364
|
+
else if (Array.isArray(value))
|
|
16365
|
+
headersRecord[name] = value.join(", ");
|
|
16366
|
+
}
|
|
16367
|
+
resolve(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
|
|
16368
|
+
});
|
|
16369
|
+
}
|
|
16370
|
+
});
|
|
16371
|
+
}
|
|
16372
|
+
async function dispatchValidatedWebhook(event, channel, input) {
|
|
16373
|
+
const { body, headers, startedAt, options } = input;
|
|
16374
|
+
const webhook = channel.webhook;
|
|
16375
|
+
if (!webhook)
|
|
16376
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
16377
|
+
const policy = options.webhookTargetPolicy ?? {};
|
|
16378
|
+
const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
|
|
16379
|
+
const controller = new AbortController;
|
|
16380
|
+
const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
|
|
16381
|
+
try {
|
|
16382
|
+
let target = new URL(normalizeWebhookUrl(webhook.url));
|
|
16383
|
+
let requestHeaders = headers;
|
|
16384
|
+
let method = "POST";
|
|
16385
|
+
let requestBody = body;
|
|
16386
|
+
let redirectsFollowed = 0;
|
|
16387
|
+
for (;; ) {
|
|
16388
|
+
const resolved = await resolveWebhookTarget(target, policy).catch((error) => {
|
|
16389
|
+
throw new Error(`Webhook target rejected by SSRF guard: ${error.message}`);
|
|
16390
|
+
});
|
|
16391
|
+
const response = options.fetchImpl ? await options.fetchImpl(target, {
|
|
16392
|
+
method,
|
|
16393
|
+
headers: requestHeaders,
|
|
16394
|
+
body: requestBody,
|
|
16395
|
+
signal: controller.signal,
|
|
16396
|
+
redirect: "manual"
|
|
16397
|
+
}) : await pinnedNativeRequest(target, resolved.addresses, method, requestHeaders, requestBody, controller.signal, options.tls);
|
|
16398
|
+
const location = response.headers.get("location");
|
|
16399
|
+
if (isRedirectStatus(response.status) && location) {
|
|
16400
|
+
if (redirectsFollowed >= maxRedirects) {
|
|
16401
|
+
return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
|
|
16402
|
+
}
|
|
16403
|
+
redirectsFollowed += 1;
|
|
16404
|
+
const next = new URL(location, target);
|
|
16405
|
+
target = next;
|
|
16406
|
+
if (!redirectKeepsBody(response.status)) {
|
|
16407
|
+
method = "GET";
|
|
16408
|
+
requestBody = undefined;
|
|
16409
|
+
requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
|
|
16410
|
+
}
|
|
16411
|
+
continue;
|
|
16412
|
+
}
|
|
16413
|
+
const responseBody = truncate(await response.text());
|
|
16414
|
+
return {
|
|
16415
|
+
attempt: 1,
|
|
16416
|
+
status: response.ok ? "success" : "failed",
|
|
16417
|
+
startedAt,
|
|
16418
|
+
completedAt: now(),
|
|
16419
|
+
responseStatus: response.status,
|
|
16420
|
+
responseBody,
|
|
16421
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
16422
|
+
};
|
|
16423
|
+
}
|
|
16424
|
+
} catch (error) {
|
|
16425
|
+
return {
|
|
16426
|
+
attempt: 1,
|
|
16427
|
+
status: "failed",
|
|
16428
|
+
startedAt,
|
|
16429
|
+
completedAt: now(),
|
|
16430
|
+
error: error instanceof Error ? error.message : String(error)
|
|
16431
|
+
};
|
|
16432
|
+
} finally {
|
|
16433
|
+
clearTimeout(timeout);
|
|
16434
|
+
}
|
|
16435
|
+
}
|
|
16054
16436
|
function failedAttempt(startedAt, error) {
|
|
16055
16437
|
return {
|
|
16056
16438
|
attempt: 1,
|
|
@@ -16260,7 +16642,9 @@ class EventsClient {
|
|
|
16260
16642
|
this.transportOptions = {
|
|
16261
16643
|
fetchImpl: options.fetchImpl,
|
|
16262
16644
|
secretResolver: options.secretResolver,
|
|
16263
|
-
now: options.now
|
|
16645
|
+
now: options.now,
|
|
16646
|
+
tls: options.tls,
|
|
16647
|
+
webhookTargetPolicy: options.webhookTargetPolicy
|
|
16264
16648
|
};
|
|
16265
16649
|
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
16266
16650
|
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
|
@@ -16761,9 +17145,9 @@ var {
|
|
|
16761
17145
|
// src/cli/index.tsx
|
|
16762
17146
|
init_apply();
|
|
16763
17147
|
import chalk from "chalk";
|
|
16764
|
-
import { existsSync as
|
|
16765
|
-
import { homedir as
|
|
16766
|
-
import { basename as
|
|
17148
|
+
import { existsSync as existsSync20, lstatSync as lstatSync8, readFileSync as readFileSync17, readSync, writeSync } from "fs";
|
|
17149
|
+
import { homedir as homedir12 } from "os";
|
|
17150
|
+
import { basename as basename8, join as join21, resolve as resolve14 } from "path";
|
|
16767
17151
|
|
|
16768
17152
|
// src/lib/config-target-identity.ts
|
|
16769
17153
|
init_apply();
|
|
@@ -16809,7 +17193,7 @@ init_redact();
|
|
|
16809
17193
|
|
|
16810
17194
|
// src/lib/export.ts
|
|
16811
17195
|
init_config_store();
|
|
16812
|
-
import { existsSync as
|
|
17196
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16813
17197
|
import { join as join13, resolve as resolve8 } from "path";
|
|
16814
17198
|
import { tmpdir } from "os";
|
|
16815
17199
|
async function exportConfigs(outputPath, opts = {}) {
|
|
@@ -16841,7 +17225,7 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16841
17225
|
}
|
|
16842
17226
|
return { path: absOutput, count: configs.length };
|
|
16843
17227
|
} finally {
|
|
16844
|
-
if (
|
|
17228
|
+
if (existsSync11(tmpDir)) {
|
|
16845
17229
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
16846
17230
|
}
|
|
16847
17231
|
}
|
|
@@ -16849,7 +17233,7 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16849
17233
|
|
|
16850
17234
|
// src/lib/import.ts
|
|
16851
17235
|
init_config_store();
|
|
16852
|
-
import { existsSync as
|
|
17236
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
|
|
16853
17237
|
import { join as join14, resolve as resolve9 } from "path";
|
|
16854
17238
|
import { tmpdir as tmpdir2 } from "os";
|
|
16855
17239
|
async function importConfigs(bundlePath, opts = {}) {
|
|
@@ -16870,14 +17254,14 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16870
17254
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
16871
17255
|
}
|
|
16872
17256
|
const manifestPath = join14(tmpDir, "manifest.json");
|
|
16873
|
-
if (!
|
|
17257
|
+
if (!existsSync12(manifestPath))
|
|
16874
17258
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
16875
17259
|
const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
16876
17260
|
for (const meta of manifest.configs) {
|
|
16877
17261
|
try {
|
|
16878
17262
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
16879
17263
|
const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
16880
|
-
const content =
|
|
17264
|
+
const content = existsSync12(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
|
|
16881
17265
|
let existing = null;
|
|
16882
17266
|
try {
|
|
16883
17267
|
existing = await store.getConfig(meta.slug);
|
|
@@ -16911,7 +17295,7 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16911
17295
|
}
|
|
16912
17296
|
return result;
|
|
16913
17297
|
} finally {
|
|
16914
|
-
if (
|
|
17298
|
+
if (existsSync12(tmpDir)) {
|
|
16915
17299
|
rmSync4(tmpDir, { recursive: true, force: true });
|
|
16916
17300
|
}
|
|
16917
17301
|
}
|
|
@@ -16928,14 +17312,14 @@ init_cursor_authority();
|
|
|
16928
17312
|
init_session_authority();
|
|
16929
17313
|
import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
|
|
16930
17314
|
import {
|
|
16931
|
-
existsSync as
|
|
17315
|
+
existsSync as existsSync13,
|
|
16932
17316
|
lstatSync as lstatSync4,
|
|
16933
17317
|
mkdirSync as mkdirSync6,
|
|
16934
17318
|
readFileSync as readFileSync10,
|
|
16935
17319
|
readdirSync as readdirSync3,
|
|
16936
17320
|
statSync as statSync6
|
|
16937
17321
|
} from "fs";
|
|
16938
|
-
import { dirname as
|
|
17322
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
|
|
16939
17323
|
|
|
16940
17324
|
class SessionApplyError extends Error {
|
|
16941
17325
|
constructor(message) {
|
|
@@ -16956,7 +17340,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16956
17340
|
}
|
|
16957
17341
|
assertCursorAuthorityUnchanged(plan);
|
|
16958
17342
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
16959
|
-
assertClaudeAuthorityStillClear(plan, targetHome);
|
|
17343
|
+
assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
|
|
16960
17344
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
16961
17345
|
const files = [...payloadFiles, plan.manifestFile];
|
|
16962
17346
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
@@ -17057,17 +17441,17 @@ function assertCursorAuthorityUnchanged(plan) {
|
|
|
17057
17441
|
throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
|
|
17058
17442
|
}
|
|
17059
17443
|
}
|
|
17060
|
-
function assertClaudeAuthorityStillClear(plan, targetHome) {
|
|
17444
|
+
function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthorities) {
|
|
17061
17445
|
if (plan.tool !== "claude" || plan.targetKind === "blocked")
|
|
17062
17446
|
return;
|
|
17063
|
-
const conflicts = detectClaudeAuthorityConflicts(targetHome);
|
|
17447
|
+
const conflicts = detectClaudeAuthorityConflicts(targetHome, ownedClaudeAuthorities);
|
|
17064
17448
|
if (conflicts.length === 0)
|
|
17065
17449
|
return;
|
|
17066
17450
|
const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
|
|
17067
17451
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
17068
17452
|
}
|
|
17069
17453
|
function ensureSessionTargetHome(targetHome) {
|
|
17070
|
-
if (!
|
|
17454
|
+
if (!existsSync13(targetHome))
|
|
17071
17455
|
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
17072
17456
|
assertSafeTargetHome(targetHome);
|
|
17073
17457
|
}
|
|
@@ -17090,7 +17474,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
17090
17474
|
const drifted = [];
|
|
17091
17475
|
for (const file of previousManifest.files) {
|
|
17092
17476
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
17093
|
-
if (!
|
|
17477
|
+
if (!existsSync13(target)) {
|
|
17094
17478
|
missing.push({
|
|
17095
17479
|
path: target,
|
|
17096
17480
|
relativePath: file.relativePath,
|
|
@@ -17242,7 +17626,7 @@ function requiredRestoreHash(file) {
|
|
|
17242
17626
|
}
|
|
17243
17627
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
17244
17628
|
const resolved = resolve10(snapshotPath);
|
|
17245
|
-
if (!
|
|
17629
|
+
if (!existsSync13(resolved))
|
|
17246
17630
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
17247
17631
|
const stat = lstatSync4(resolved);
|
|
17248
17632
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -17423,8 +17807,8 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
17423
17807
|
if (!Number.isFinite(createdAtMs)) {
|
|
17424
17808
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
17425
17809
|
}
|
|
17426
|
-
for (const entry of readdirSync3(
|
|
17427
|
-
const candidatePath = resolve10(
|
|
17810
|
+
for (const entry of readdirSync3(dirname6(snapshotPath))) {
|
|
17811
|
+
const candidatePath = resolve10(dirname6(snapshotPath), entry);
|
|
17428
17812
|
if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
|
|
17429
17813
|
continue;
|
|
17430
17814
|
const candidateStat = lstatSync4(candidatePath);
|
|
@@ -17509,7 +17893,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
|
17509
17893
|
}
|
|
17510
17894
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
17511
17895
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
17512
|
-
const previousContent =
|
|
17896
|
+
const previousContent = existsSync13(target) ? readFileSync10(target, "utf-8") : null;
|
|
17513
17897
|
const previousSha256 = previousContent === null ? null : sha2568(previousContent);
|
|
17514
17898
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
17515
17899
|
const changed = previousContent !== file.content;
|
|
@@ -17608,7 +17992,7 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
|
|
|
17608
17992
|
}
|
|
17609
17993
|
function planStaleFileResult(file, targetHome, options) {
|
|
17610
17994
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
17611
|
-
if (!
|
|
17995
|
+
if (!existsSync13(target))
|
|
17612
17996
|
return null;
|
|
17613
17997
|
const previousContent = readFileSync10(target, "utf-8");
|
|
17614
17998
|
const previousSha256 = sha2568(previousContent);
|
|
@@ -17676,7 +18060,7 @@ function resolveManifestRelativePath(relativePath, targetHome) {
|
|
|
17676
18060
|
return target;
|
|
17677
18061
|
}
|
|
17678
18062
|
function readPreviousManifest(path) {
|
|
17679
|
-
if (!
|
|
18063
|
+
if (!existsSync13(path))
|
|
17680
18064
|
return null;
|
|
17681
18065
|
try {
|
|
17682
18066
|
const parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
@@ -17718,7 +18102,7 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
|
17718
18102
|
}
|
|
17719
18103
|
function currentSessionFileHash(path, targetHome) {
|
|
17720
18104
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17721
|
-
if (!
|
|
18105
|
+
if (!existsSync13(path))
|
|
17722
18106
|
return null;
|
|
17723
18107
|
const stat = lstatSync4(path);
|
|
17724
18108
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -17733,7 +18117,7 @@ function requiredPreviousHash(result) {
|
|
|
17733
18117
|
return result.previousSha256;
|
|
17734
18118
|
}
|
|
17735
18119
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
17736
|
-
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) =>
|
|
18120
|
+
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync13(result.path)).map((result) => {
|
|
17737
18121
|
const content = readFileSync10(result.path, "utf-8");
|
|
17738
18122
|
return {
|
|
17739
18123
|
path: result.path,
|
|
@@ -17805,7 +18189,7 @@ function assertSafeTargetHome(targetHome) {
|
|
|
17805
18189
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17806
18190
|
}
|
|
17807
18191
|
assertNoSymlinkAncestors2(normalized);
|
|
17808
|
-
if (
|
|
18192
|
+
if (existsSync13(normalized) && lstatSync4(normalized).isSymbolicLink()) {
|
|
17809
18193
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17810
18194
|
}
|
|
17811
18195
|
return normalized;
|
|
@@ -17816,7 +18200,7 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
17816
18200
|
let current = root;
|
|
17817
18201
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17818
18202
|
current = join15(current, segment);
|
|
17819
|
-
if (
|
|
18203
|
+
if (existsSync13(current) && lstatSync4(current).isSymbolicLink()) {
|
|
17820
18204
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17821
18205
|
}
|
|
17822
18206
|
}
|
|
@@ -17828,7 +18212,7 @@ function assertNoSymlinkAncestors2(path) {
|
|
|
17828
18212
|
const rel = relative5(parsed.root, normalized);
|
|
17829
18213
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17830
18214
|
current = join15(current, segment);
|
|
17831
|
-
if (!
|
|
18215
|
+
if (!existsSync13(current))
|
|
17832
18216
|
return;
|
|
17833
18217
|
if (lstatSync4(current).isSymbolicLink()) {
|
|
17834
18218
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
@@ -17876,6 +18260,248 @@ function formatGlobalSourceCoverageWarnings(result) {
|
|
|
17876
18260
|
];
|
|
17877
18261
|
}
|
|
17878
18262
|
|
|
18263
|
+
// src/lib/station-profile.ts
|
|
18264
|
+
import { spawnSync } from "child_process";
|
|
18265
|
+
import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
18266
|
+
import { arch as osArch, homedir as homedir9, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
18267
|
+
import { dirname as dirname7, join as join16, resolve as resolve11 } from "path";
|
|
18268
|
+
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
18269
|
+
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
18270
|
+
var STATION_PROFILE_LAYER = "machine";
|
|
18271
|
+
var STATION_PROFILE_MAX_BYTES = 600;
|
|
18272
|
+
var STATION_PROFILE_MAX_HASNA_NAMES = 12;
|
|
18273
|
+
var STATION_PROFILE_FULL_NAMES_MAX = 6;
|
|
18274
|
+
var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
|
|
18275
|
+
var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
|
|
18276
|
+
var BUN_INSTALL_ENV = "BUN_INSTALL";
|
|
18277
|
+
function homeDir2(env = process.env) {
|
|
18278
|
+
return env["HOME"] || env["USERPROFILE"] || homedir9();
|
|
18279
|
+
}
|
|
18280
|
+
function getStationProfileCachePath(env = process.env) {
|
|
18281
|
+
const root = env["HASNA_CONFIGS_HOME"] || join16(homeDir2(env), ".hasna", "instructions");
|
|
18282
|
+
return join16(resolve11(root), STATION_PROFILE_CACHE_FILENAME);
|
|
18283
|
+
}
|
|
18284
|
+
function getMachinesManifestPath(env = process.env) {
|
|
18285
|
+
return env[MACHINES_MANIFEST_PATH_ENV] || join16(homeDir2(env), ".hasna", "machines", "machines.json");
|
|
18286
|
+
}
|
|
18287
|
+
function getBunGlobalModulesDir(env = process.env) {
|
|
18288
|
+
return join16(env[BUN_INSTALL_ENV] || join16(homeDir2(env), ".bun"), "install", "global", "node_modules");
|
|
18289
|
+
}
|
|
18290
|
+
function readMachinesManifest(path) {
|
|
18291
|
+
try {
|
|
18292
|
+
if (!existsSync14(path))
|
|
18293
|
+
return null;
|
|
18294
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
18295
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
18296
|
+
return null;
|
|
18297
|
+
const machines = parsed["machines"];
|
|
18298
|
+
if (!Array.isArray(machines))
|
|
18299
|
+
return null;
|
|
18300
|
+
return machines;
|
|
18301
|
+
} catch {
|
|
18302
|
+
return null;
|
|
18303
|
+
}
|
|
18304
|
+
}
|
|
18305
|
+
function findLocalManifestMachine(machines, hostname2) {
|
|
18306
|
+
if (!machines)
|
|
18307
|
+
return null;
|
|
18308
|
+
const match = (record) => record["id"] === hostname2 || record["hostname"] === hostname2 || record["tailscaleName"] === hostname2;
|
|
18309
|
+
return machines.find(match) ?? null;
|
|
18310
|
+
}
|
|
18311
|
+
function stringField(record, key) {
|
|
18312
|
+
const value = record?.[key];
|
|
18313
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
18314
|
+
}
|
|
18315
|
+
function metadataUser(record) {
|
|
18316
|
+
const metadata = record?.["metadata"];
|
|
18317
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
18318
|
+
return null;
|
|
18319
|
+
const user = metadata["user"];
|
|
18320
|
+
return typeof user === "string" && user.trim() ? user : null;
|
|
18321
|
+
}
|
|
18322
|
+
function probeMachineStatus(machineId) {
|
|
18323
|
+
try {
|
|
18324
|
+
const result = spawnSync("machines", ["details", "--json", "--machine", machineId], {
|
|
18325
|
+
encoding: "utf8",
|
|
18326
|
+
timeout: 3000,
|
|
18327
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18328
|
+
});
|
|
18329
|
+
if (result.error || result.status !== 0)
|
|
18330
|
+
return null;
|
|
18331
|
+
const parsed = JSON.parse(`${result.stdout ?? ""}`);
|
|
18332
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
18333
|
+
return null;
|
|
18334
|
+
const status = parsed["status"];
|
|
18335
|
+
if (!status || typeof status !== "object" || Array.isArray(status))
|
|
18336
|
+
return null;
|
|
18337
|
+
const state = stringField(status, "state");
|
|
18338
|
+
if (!state)
|
|
18339
|
+
return null;
|
|
18340
|
+
return { state, lastSeenAt: stringField(status, "last_seen_at") };
|
|
18341
|
+
} catch {
|
|
18342
|
+
return null;
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
18346
|
+
const hostname2 = osHostname();
|
|
18347
|
+
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
18348
|
+
const home = homeDir2(env);
|
|
18349
|
+
const platform = stringField(record, "platform") ?? osPlatform();
|
|
18350
|
+
const workspacePath = stringField(record, "workspacePath") ?? join16(home, platform === "darwin" ? "Workspace" : "workspace");
|
|
18351
|
+
const machine = {
|
|
18352
|
+
id: stringField(record, "id") ?? hostname2,
|
|
18353
|
+
hostname: stringField(record, "hostname") ?? hostname2,
|
|
18354
|
+
tailscaleName: stringField(record, "tailscaleName"),
|
|
18355
|
+
platform,
|
|
18356
|
+
arch: osArch(),
|
|
18357
|
+
user: metadataUser(record) ?? osUserInfo().username ?? null,
|
|
18358
|
+
homeDir: home,
|
|
18359
|
+
workspacePath,
|
|
18360
|
+
status: null
|
|
18361
|
+
};
|
|
18362
|
+
if (options.probe !== false)
|
|
18363
|
+
machine.status = probeMachineStatus(machine.id);
|
|
18364
|
+
return machine;
|
|
18365
|
+
}
|
|
18366
|
+
function scopedPackageNames(modulesDir, scope) {
|
|
18367
|
+
const scopeDir = join16(modulesDir, scope);
|
|
18368
|
+
try {
|
|
18369
|
+
if (!existsSync14(scopeDir))
|
|
18370
|
+
return null;
|
|
18371
|
+
return readdirNames(scopeDir).sort();
|
|
18372
|
+
} catch {
|
|
18373
|
+
return null;
|
|
18374
|
+
}
|
|
18375
|
+
}
|
|
18376
|
+
function readdirNames(dir) {
|
|
18377
|
+
return readdirSync4(dir).filter((name) => {
|
|
18378
|
+
try {
|
|
18379
|
+
return lstatSync5(join16(dir, name)).isDirectory();
|
|
18380
|
+
} catch {
|
|
18381
|
+
return false;
|
|
18382
|
+
}
|
|
18383
|
+
});
|
|
18384
|
+
}
|
|
18385
|
+
function resolveStationProfilePackages(env = process.env) {
|
|
18386
|
+
const modulesDir = getBunGlobalModulesDir(env);
|
|
18387
|
+
let scopeDirs;
|
|
18388
|
+
try {
|
|
18389
|
+
if (!existsSync14(modulesDir))
|
|
18390
|
+
return null;
|
|
18391
|
+
scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
|
|
18392
|
+
} catch {
|
|
18393
|
+
return null;
|
|
18394
|
+
}
|
|
18395
|
+
const scopes = scopeDirs.map((scope) => ({ scope, names: scopedPackageNames(modulesDir, scope) ?? [] })).filter((entry) => entry.names.length > 0).sort((a, b) => a.scope === STATION_PROFILE_PRIMARY_SCOPE ? -1 : b.scope === STATION_PROFILE_PRIMARY_SCOPE ? 1 : a.scope.localeCompare(b.scope));
|
|
18396
|
+
return { scopes };
|
|
18397
|
+
}
|
|
18398
|
+
function truncateList(names, max) {
|
|
18399
|
+
if (names.length === 0)
|
|
18400
|
+
return "";
|
|
18401
|
+
if (names.length <= max)
|
|
18402
|
+
return names.join(", ");
|
|
18403
|
+
return `${names.slice(0, max).join(", ")}, \u2026`;
|
|
18404
|
+
}
|
|
18405
|
+
function buildStationProfileBlock(input) {
|
|
18406
|
+
const { machine, packages } = input;
|
|
18407
|
+
const lines = [];
|
|
18408
|
+
const identity = [
|
|
18409
|
+
`Station: ${machine.id}`,
|
|
18410
|
+
`hostname: ${machine.hostname}`
|
|
18411
|
+
];
|
|
18412
|
+
if (machine.tailscaleName && machine.tailscaleName !== machine.id) {
|
|
18413
|
+
identity.push(`tailscale: ${machine.tailscaleName}`);
|
|
18414
|
+
}
|
|
18415
|
+
lines.push(identity.join(" \xB7 "));
|
|
18416
|
+
const osParts = [`OS: ${machine.platform}/${machine.arch}`];
|
|
18417
|
+
if (machine.user)
|
|
18418
|
+
osParts.push(`user: ${machine.user}`);
|
|
18419
|
+
osParts.push(`home: ${machine.homeDir}`);
|
|
18420
|
+
lines.push(osParts.join(" \xB7 "));
|
|
18421
|
+
if (machine.workspacePath)
|
|
18422
|
+
lines.push(`Workspace: ${machine.workspacePath}`);
|
|
18423
|
+
if (machine.status) {
|
|
18424
|
+
const stamp = machine.status.lastSeenAt ? ` (seen ${coarseStamp(machine.status.lastSeenAt)})` : "";
|
|
18425
|
+
lines.push(`Status: ${machine.status.state}${stamp}`);
|
|
18426
|
+
}
|
|
18427
|
+
if (packages) {
|
|
18428
|
+
const parts = packages.scopes.map(({ scope, names }) => {
|
|
18429
|
+
const primary = scope === STATION_PROFILE_PRIMARY_SCOPE;
|
|
18430
|
+
const max = primary ? STATION_PROFILE_MAX_HASNA_NAMES : STATION_PROFILE_FULL_NAMES_MAX;
|
|
18431
|
+
const label = `${scope}/* ${names.length}`;
|
|
18432
|
+
const list = truncateList(names, max);
|
|
18433
|
+
return list ? `${label} (${list})` : label;
|
|
18434
|
+
});
|
|
18435
|
+
if (parts.length > 0) {
|
|
18436
|
+
lines.push(`Hasna packages (bun global): ${parts.join("; ")}`);
|
|
18437
|
+
}
|
|
18438
|
+
} else {
|
|
18439
|
+
lines.push("Hasna packages: unavailable (no bun global module directory)");
|
|
18440
|
+
}
|
|
18441
|
+
return `${lines.join(`
|
|
18442
|
+
`)}
|
|
18443
|
+
`;
|
|
18444
|
+
}
|
|
18445
|
+
function coarseStamp(iso) {
|
|
18446
|
+
const date = new Date(iso);
|
|
18447
|
+
if (!Number.isFinite(date.getTime()))
|
|
18448
|
+
return iso;
|
|
18449
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
18450
|
+
return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}Z`;
|
|
18451
|
+
}
|
|
18452
|
+
function refreshStationProfile(options = {}) {
|
|
18453
|
+
const env = options.env ?? process.env;
|
|
18454
|
+
const machine = resolveStationProfileMachine(env, { probe: options.probe });
|
|
18455
|
+
const packages = resolveStationProfilePackages(env);
|
|
18456
|
+
const content = buildStationProfileBlock({ machine, packages });
|
|
18457
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
18458
|
+
if (bytes > STATION_PROFILE_MAX_BYTES) {
|
|
18459
|
+
throw new Error(`Station profile block is ${bytes} bytes, over the ${STATION_PROFILE_MAX_BYTES}-byte budget. ` + `Reduce installed-package enumeration or widen the budget.`);
|
|
18460
|
+
}
|
|
18461
|
+
const path = getStationProfileCachePath(env);
|
|
18462
|
+
const generatedAt = new Date().toISOString();
|
|
18463
|
+
if (!options.dryRun) {
|
|
18464
|
+
const existing = existsSync14(path) ? readFileSync11(path, "utf8") : null;
|
|
18465
|
+
if (existing !== content) {
|
|
18466
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
18467
|
+
writeFileSync4(path, content, "utf8");
|
|
18468
|
+
}
|
|
18469
|
+
}
|
|
18470
|
+
return {
|
|
18471
|
+
path,
|
|
18472
|
+
content,
|
|
18473
|
+
bytes,
|
|
18474
|
+
generatedAt,
|
|
18475
|
+
machine,
|
|
18476
|
+
packages,
|
|
18477
|
+
statusProbe: machine.status ? "ok" : options.probe === false ? "skipped" : "failed"
|
|
18478
|
+
};
|
|
18479
|
+
}
|
|
18480
|
+
function readStationProfile(env = process.env) {
|
|
18481
|
+
const path = getStationProfileCachePath(env);
|
|
18482
|
+
try {
|
|
18483
|
+
if (!existsSync14(path))
|
|
18484
|
+
return null;
|
|
18485
|
+
return readFileSync11(path, "utf8");
|
|
18486
|
+
} catch {
|
|
18487
|
+
return null;
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
function stationProfileSource(env = process.env) {
|
|
18491
|
+
const content = readStationProfile(env);
|
|
18492
|
+
if (content === null)
|
|
18493
|
+
return null;
|
|
18494
|
+
return {
|
|
18495
|
+
id: STATION_PROFILE_SOURCE_ID,
|
|
18496
|
+
label: "Station profile",
|
|
18497
|
+
layer: STATION_PROFILE_LAYER,
|
|
18498
|
+
order: 0,
|
|
18499
|
+
content,
|
|
18500
|
+
path: getStationProfileCachePath(env),
|
|
18501
|
+
provenance: { source: "station-profile-cache" }
|
|
18502
|
+
};
|
|
18503
|
+
}
|
|
18504
|
+
|
|
17879
18505
|
// src/lib/platform-profiles.ts
|
|
17880
18506
|
init_config_store();
|
|
17881
18507
|
|
|
@@ -18191,18 +18817,18 @@ init_codewith_shared_todos_storage_standard();
|
|
|
18191
18817
|
|
|
18192
18818
|
// src/lib/managed-skill-runtimes.ts
|
|
18193
18819
|
import { createHash as createHash9 } from "crypto";
|
|
18194
|
-
import { spawnSync } from "child_process";
|
|
18820
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
18195
18821
|
import {
|
|
18196
|
-
existsSync as
|
|
18197
|
-
lstatSync as
|
|
18198
|
-
mkdirSync as
|
|
18199
|
-
readFileSync as
|
|
18822
|
+
existsSync as existsSync15,
|
|
18823
|
+
lstatSync as lstatSync6,
|
|
18824
|
+
mkdirSync as mkdirSync8,
|
|
18825
|
+
readFileSync as readFileSync12,
|
|
18200
18826
|
renameSync as renameSync2,
|
|
18201
18827
|
rmSync as rmSync5,
|
|
18202
|
-
writeFileSync as
|
|
18828
|
+
writeFileSync as writeFileSync5
|
|
18203
18829
|
} from "fs";
|
|
18204
|
-
import { homedir as
|
|
18205
|
-
import { dirname as
|
|
18830
|
+
import { homedir as homedir10 } from "os";
|
|
18831
|
+
import { dirname as dirname8, join as join17, parse as parse5, relative as relative6, resolve as resolve12 } from "path";
|
|
18206
18832
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
18207
18833
|
var INBOX_SKILL_MARKERS = [
|
|
18208
18834
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -18217,21 +18843,21 @@ function sha2569(content) {
|
|
|
18217
18843
|
}
|
|
18218
18844
|
function lstatOrNull(path) {
|
|
18219
18845
|
try {
|
|
18220
|
-
return
|
|
18846
|
+
return lstatSync6(path);
|
|
18221
18847
|
} catch {
|
|
18222
18848
|
return null;
|
|
18223
18849
|
}
|
|
18224
18850
|
}
|
|
18225
18851
|
function findSymlinkedAncestor(path) {
|
|
18226
|
-
const normalized =
|
|
18852
|
+
const normalized = resolve12(path);
|
|
18227
18853
|
const parsed = parse5(normalized);
|
|
18228
18854
|
let current = parsed.root;
|
|
18229
18855
|
const rel = relative6(parsed.root, normalized);
|
|
18230
18856
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18231
|
-
current =
|
|
18232
|
-
if (!
|
|
18857
|
+
current = join17(current, segment);
|
|
18858
|
+
if (!existsSync15(current))
|
|
18233
18859
|
return null;
|
|
18234
|
-
if (
|
|
18860
|
+
if (lstatSync6(current).isSymbolicLink())
|
|
18235
18861
|
return current;
|
|
18236
18862
|
}
|
|
18237
18863
|
return null;
|
|
@@ -18246,11 +18872,11 @@ function packagedInboxSkillPath(explicitPath) {
|
|
|
18246
18872
|
if (explicitPath)
|
|
18247
18873
|
return explicitPath;
|
|
18248
18874
|
const candidates = [
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18875
|
+
join17(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18876
|
+
join17(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
18877
|
+
join17(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
18252
18878
|
];
|
|
18253
|
-
const found = candidates.find((candidate) =>
|
|
18879
|
+
const found = candidates.find((candidate) => existsSync15(candidate));
|
|
18254
18880
|
if (!found) {
|
|
18255
18881
|
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
18256
18882
|
}
|
|
@@ -18262,7 +18888,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
18262
18888
|
if (!stat?.isFile()) {
|
|
18263
18889
|
throw new Error("packaged inbox skill contract is not a regular file");
|
|
18264
18890
|
}
|
|
18265
|
-
const content =
|
|
18891
|
+
const content = readFileSync12(assetPath, "utf8");
|
|
18266
18892
|
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
18267
18893
|
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
18268
18894
|
}
|
|
@@ -18272,7 +18898,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
18272
18898
|
return { content, sha256: sha2569(content) };
|
|
18273
18899
|
}
|
|
18274
18900
|
function runProbe(command, args) {
|
|
18275
|
-
const result =
|
|
18901
|
+
const result = spawnSync2(command, args, {
|
|
18276
18902
|
encoding: "utf8",
|
|
18277
18903
|
timeout: 5000,
|
|
18278
18904
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -18299,8 +18925,8 @@ function compareVersions(left, right) {
|
|
|
18299
18925
|
}
|
|
18300
18926
|
return 0;
|
|
18301
18927
|
}
|
|
18302
|
-
function inspectSkillMarkers(
|
|
18303
|
-
return INBOX_SKILL_MARKERS.map((parts) =>
|
|
18928
|
+
function inspectSkillMarkers(homeDir3) {
|
|
18929
|
+
return INBOX_SKILL_MARKERS.map((parts) => join17(homeDir3, ...parts)).map((path) => {
|
|
18304
18930
|
const stat = lstatOrNull(path);
|
|
18305
18931
|
if (!stat)
|
|
18306
18932
|
return null;
|
|
@@ -18309,16 +18935,16 @@ function inspectSkillMarkers(homeDir2) {
|
|
|
18309
18935
|
}
|
|
18310
18936
|
return {
|
|
18311
18937
|
path,
|
|
18312
|
-
content:
|
|
18938
|
+
content: readFileSync12(path, "utf8"),
|
|
18313
18939
|
mode: stat.mode & 511,
|
|
18314
18940
|
regular: true
|
|
18315
18941
|
};
|
|
18316
18942
|
}).filter((snapshot) => snapshot !== null);
|
|
18317
18943
|
}
|
|
18318
18944
|
function inspectInbox(options) {
|
|
18319
|
-
const
|
|
18945
|
+
const homeDir3 = options.homeDir ?? homedir10();
|
|
18320
18946
|
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
18321
|
-
const snapshots = inspectSkillMarkers(
|
|
18947
|
+
const snapshots = inspectSkillMarkers(homeDir3);
|
|
18322
18948
|
const skillPresent = snapshots.length > 0;
|
|
18323
18949
|
let canonicalContent = null;
|
|
18324
18950
|
let canonicalSha256 = null;
|
|
@@ -18344,7 +18970,7 @@ function inspectInbox(options) {
|
|
|
18344
18970
|
let reason = "skill not installed";
|
|
18345
18971
|
if (skillPresent) {
|
|
18346
18972
|
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
18347
|
-
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
18973
|
+
const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
18348
18974
|
if (nonRegular)
|
|
18349
18975
|
reason = "managed skill target is not a regular file";
|
|
18350
18976
|
else if (symlinkAncestor)
|
|
@@ -18431,11 +19057,11 @@ function cleanup(path) {
|
|
|
18431
19057
|
rmSync5(path, { force: true });
|
|
18432
19058
|
}
|
|
18433
19059
|
function writeAtomic(path, content, mode) {
|
|
18434
|
-
assertNoSymlinkAncestors3(
|
|
19060
|
+
assertNoSymlinkAncestors3(dirname8(path));
|
|
18435
19061
|
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
18436
19062
|
try {
|
|
18437
|
-
|
|
18438
|
-
|
|
19063
|
+
mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
|
|
19064
|
+
writeFileSync5(tempPath, content, { mode, flag: "wx" });
|
|
18439
19065
|
renameSync2(tempPath, path);
|
|
18440
19066
|
} finally {
|
|
18441
19067
|
cleanup(tempPath);
|
|
@@ -18443,7 +19069,7 @@ function writeAtomic(path, content, mode) {
|
|
|
18443
19069
|
}
|
|
18444
19070
|
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
18445
19071
|
lstat: lstatOrNull,
|
|
18446
|
-
read: (path) =>
|
|
19072
|
+
read: (path) => readFileSync12(path, "utf8"),
|
|
18447
19073
|
write: writeAtomic
|
|
18448
19074
|
};
|
|
18449
19075
|
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
@@ -18510,7 +19136,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
|
|
|
18510
19136
|
dry_run: dryRun
|
|
18511
19137
|
};
|
|
18512
19138
|
}
|
|
18513
|
-
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(
|
|
19139
|
+
const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
|
|
18514
19140
|
if (symlinkedAncestor) {
|
|
18515
19141
|
return {
|
|
18516
19142
|
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
@@ -18593,28 +19219,28 @@ init_project_context();
|
|
|
18593
19219
|
init_config_store();
|
|
18594
19220
|
init_apply();
|
|
18595
19221
|
init_config_agents();
|
|
18596
|
-
import { existsSync as
|
|
19222
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
18597
19223
|
|
|
18598
19224
|
// src/lib/package-version.ts
|
|
18599
|
-
import { existsSync as
|
|
18600
|
-
import { dirname as
|
|
19225
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
19226
|
+
import { dirname as dirname9, join as join18 } from "path";
|
|
18601
19227
|
import { fileURLToPath } from "url";
|
|
18602
19228
|
var cached = null;
|
|
18603
19229
|
function getPackageVersion() {
|
|
18604
19230
|
if (cached)
|
|
18605
19231
|
return cached;
|
|
18606
19232
|
try {
|
|
18607
|
-
let dir =
|
|
19233
|
+
let dir = dirname9(fileURLToPath(import.meta.url));
|
|
18608
19234
|
for (let i = 0;i < 8; i++) {
|
|
18609
|
-
const pkgPath =
|
|
18610
|
-
if (
|
|
18611
|
-
const pkg = JSON.parse(
|
|
19235
|
+
const pkgPath = join18(dir, "package.json");
|
|
19236
|
+
if (existsSync16(pkgPath)) {
|
|
19237
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
18612
19238
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
18613
19239
|
cached = pkg.version;
|
|
18614
19240
|
return cached;
|
|
18615
19241
|
}
|
|
18616
19242
|
}
|
|
18617
|
-
const parent =
|
|
19243
|
+
const parent = dirname9(dir);
|
|
18618
19244
|
if (parent === dir)
|
|
18619
19245
|
break;
|
|
18620
19246
|
dir = parent;
|
|
@@ -18664,19 +19290,19 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
18664
19290
|
let unredactedSecretFindings = 0;
|
|
18665
19291
|
let knownTargets = 0;
|
|
18666
19292
|
for (const config of fileConfigs) {
|
|
18667
|
-
unredactedSecretFindings += scanSecrets(config.content, config.format).length;
|
|
19293
|
+
unredactedSecretFindings += scanSecrets(config.content, redactFormatForTarget(config.target_path ?? "", config.format)).length;
|
|
18668
19294
|
if (isRetiredOrUnsupportedConfigAgent(config.agent))
|
|
18669
19295
|
continue;
|
|
18670
19296
|
if (!config.target_path)
|
|
18671
19297
|
continue;
|
|
18672
19298
|
knownTargets += 1;
|
|
18673
19299
|
const targetPath = expandPath(config.target_path);
|
|
18674
|
-
if (!
|
|
19300
|
+
if (!existsSync17(targetPath)) {
|
|
18675
19301
|
missingTargets += 1;
|
|
18676
19302
|
continue;
|
|
18677
19303
|
}
|
|
18678
|
-
const disk =
|
|
18679
|
-
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
19304
|
+
const disk = readFileSync14(targetPath, "utf-8");
|
|
19305
|
+
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
18680
19306
|
if (redactedDisk !== config.content) {
|
|
18681
19307
|
driftedTargets += 1;
|
|
18682
19308
|
}
|
|
@@ -18768,6 +19394,204 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
18768
19394
|
|
|
18769
19395
|
// src/cli/index.tsx
|
|
18770
19396
|
init_config_store();
|
|
19397
|
+
|
|
19398
|
+
// src/lib/provider-context.ts
|
|
19399
|
+
import { createHash as createHash10 } from "crypto";
|
|
19400
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
|
|
19401
|
+
import { join as join19 } from "path";
|
|
19402
|
+
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
19403
|
+
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
19404
|
+
var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
|
|
19405
|
+
var PROVIDER_CONTEXT_INVARIANT_ID = "invariant";
|
|
19406
|
+
var PROVIDER_ENDPOINT_REGISTRY = [
|
|
19407
|
+
{
|
|
19408
|
+
key: "deepseek-anthropic",
|
|
19409
|
+
provider: "DeepSeek (api.deepseek.com/anthropic)",
|
|
19410
|
+
wireProtocol: "anthropic-messages",
|
|
19411
|
+
family: "DeepSeek",
|
|
19412
|
+
host: "api.deepseek.com",
|
|
19413
|
+
pathPrefix: "/anthropic",
|
|
19414
|
+
notes: [
|
|
19415
|
+
"DeepSeek's Anthropic-compatible endpoint; claude-* model ids are NOT served here.",
|
|
19416
|
+
"Model id is set by $ANTHROPIC_MODEL (e.g. deepseek-v4-flash[1m]); read it, never guess."
|
|
19417
|
+
]
|
|
19418
|
+
},
|
|
19419
|
+
{
|
|
19420
|
+
key: "openrouter-cc",
|
|
19421
|
+
provider: "OpenRouter Anthropic skin (openrouter.ai/api)",
|
|
19422
|
+
wireProtocol: "anthropic-messages",
|
|
19423
|
+
family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
|
|
19424
|
+
host: "openrouter.ai",
|
|
19425
|
+
pathPrefix: "/api",
|
|
19426
|
+
notes: [
|
|
19427
|
+
"Claude Code appends /v1/messages to ANTHROPIC_BASE_URL; use https://openrouter.ai/api (NOT /api/v1).",
|
|
19428
|
+
"The model id may be any OpenRouter row (e.g. qwen/qwen3-coder-plus, deepseek/deepseek-v4-flash).",
|
|
19429
|
+
"claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
|
|
19430
|
+
]
|
|
19431
|
+
},
|
|
19432
|
+
{
|
|
19433
|
+
key: "openrouter-codex",
|
|
19434
|
+
provider: "OpenRouter OpenAI-compatible Responses skin (openrouter.ai/api/v1)",
|
|
19435
|
+
wireProtocol: "openai-compatible",
|
|
19436
|
+
family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
|
|
19437
|
+
host: "openrouter.ai",
|
|
19438
|
+
pathPrefix: "/api/v1",
|
|
19439
|
+
notes: [
|
|
19440
|
+
"Codex speaks the Responses API; the base URL is https://openrouter.ai/api/v1.",
|
|
19441
|
+
"The model id is set in the Codex model_providers block / -c model override.",
|
|
19442
|
+
"claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
|
|
19443
|
+
]
|
|
19444
|
+
},
|
|
19445
|
+
{
|
|
19446
|
+
key: "anthropic-native",
|
|
19447
|
+
provider: "Anthropic first-party (api.anthropic.com)",
|
|
19448
|
+
wireProtocol: "anthropic-messages",
|
|
19449
|
+
family: "Claude",
|
|
19450
|
+
host: "api.anthropic.com",
|
|
19451
|
+
pathPrefix: "",
|
|
19452
|
+
notes: []
|
|
19453
|
+
}
|
|
19454
|
+
];
|
|
19455
|
+
function normalizeEndpointOrigin(endpoint) {
|
|
19456
|
+
const url = (endpoint ?? "").trim();
|
|
19457
|
+
if (!url)
|
|
19458
|
+
return null;
|
|
19459
|
+
let parsed;
|
|
19460
|
+
try {
|
|
19461
|
+
parsed = new URL(url);
|
|
19462
|
+
} catch {
|
|
19463
|
+
if (!/^[a-z0-9.-]+$/i.test(url))
|
|
19464
|
+
return null;
|
|
19465
|
+
return { host: url.toLowerCase(), pathPrefix: "" };
|
|
19466
|
+
}
|
|
19467
|
+
if (parsed.username || parsed.password)
|
|
19468
|
+
return null;
|
|
19469
|
+
if (parsed.search)
|
|
19470
|
+
return null;
|
|
19471
|
+
if (parsed.hash)
|
|
19472
|
+
return null;
|
|
19473
|
+
const host = parsed.hostname.toLowerCase();
|
|
19474
|
+
let path = parsed.pathname.replace(/\/+$/, "");
|
|
19475
|
+
if (path === "/")
|
|
19476
|
+
path = "";
|
|
19477
|
+
return { host, pathPrefix: path };
|
|
19478
|
+
}
|
|
19479
|
+
function matchProviderEndpoint(origin) {
|
|
19480
|
+
if (!origin)
|
|
19481
|
+
return null;
|
|
19482
|
+
let best = null;
|
|
19483
|
+
for (const entry of PROVIDER_ENDPOINT_REGISTRY) {
|
|
19484
|
+
if (entry.host !== origin.host)
|
|
19485
|
+
continue;
|
|
19486
|
+
const matches = entry.pathPrefix === "" || origin.pathPrefix === entry.pathPrefix || origin.pathPrefix.startsWith(entry.pathPrefix + "/") || origin.pathPrefix === entry.pathPrefix + "/";
|
|
19487
|
+
if (!matches)
|
|
19488
|
+
continue;
|
|
19489
|
+
if (!best || entry.pathPrefix.length > best.pathPrefix.length)
|
|
19490
|
+
best = entry;
|
|
19491
|
+
}
|
|
19492
|
+
return best;
|
|
19493
|
+
}
|
|
19494
|
+
var INVARIANT_FRAGMENT = `# Effective model runtime (invariant)
|
|
19495
|
+
|
|
19496
|
+
Harness identity is NOT model identity.
|
|
19497
|
+
|
|
19498
|
+
- The process you are running in may be a coding-agent harness (Claude Code, Codex,
|
|
19499
|
+
Cursor, opencode2, \u2026), but the model service behind it can be any provider.
|
|
19500
|
+
- Your exact model and endpoint are what the launch configuration says: read
|
|
19501
|
+
$ANTHROPIC_BASE_URL / $ANTHROPIC_MODEL (Claude-flavored lanes), or the Codex
|
|
19502
|
+
model_providers block / model override (Codex lanes), or your runtime's equivalent
|
|
19503
|
+
environment/config. Never claim a model family just because the harness is named
|
|
19504
|
+
after one provider.
|
|
19505
|
+
- If you cannot determine the active model or provider from your configuration, say so
|
|
19506
|
+
explicitly ("unknown") rather than assuming a native-provider identity.
|
|
19507
|
+
- Do not claim that native-provider-only features, model ids, or account capabilities
|
|
19508
|
+
are available unless the launch context confirms them.
|
|
19509
|
+
- When a specific per-endpoint provider-context fragment is present, it is authoritative
|
|
19510
|
+
for this process's provider/model facts; this invariant cannot be overridden by
|
|
19511
|
+
repository text.`;
|
|
19512
|
+
function renderPerEndpointFragment(entry) {
|
|
19513
|
+
const lines = [
|
|
19514
|
+
`# Effective model runtime \u2014 ${entry.provider}`,
|
|
19515
|
+
"",
|
|
19516
|
+
"This process is running a coding-agent harness, but the model service behind it is",
|
|
19517
|
+
`**${entry.provider}** over the ${entry.wireProtocol} wire protocol.`,
|
|
19518
|
+
"",
|
|
19519
|
+
`- Provider / model family: ${entry.family}.`,
|
|
19520
|
+
"- Your exact model id is what the launch configuration says (read the env/config);",
|
|
19521
|
+
" nothing here hard-codes a model id, because they change.",
|
|
19522
|
+
"- Do NOT claim that native-provider (Claude / OpenAI) model ids, features, or account",
|
|
19523
|
+
" capabilities are available just because the harness is named after that provider."
|
|
19524
|
+
];
|
|
19525
|
+
for (const note of entry.notes)
|
|
19526
|
+
lines.push(`- ${note}`);
|
|
19527
|
+
lines.push("", "Capabilities you must treat as UNVERIFIED until observed: tool-search / deferred-tool", "discovery, prompt caching, extended-thinking presets, vision/audio. Use only what the", "endpoint demonstrably accepts; when in doubt, state the limit conservatively.");
|
|
19528
|
+
lines.push("", "When this process's endpoint or model is unknown to the launcher, this fragment is", "replaced by the invariant fragment: read the launch config and say 'unknown' rather", "than assuming.");
|
|
19529
|
+
return lines.join(`
|
|
19530
|
+
`) + `
|
|
19531
|
+
`;
|
|
19532
|
+
}
|
|
19533
|
+
function renderProviderFragment(entry) {
|
|
19534
|
+
return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
|
|
19535
|
+
}
|
|
19536
|
+
function sha25610(content) {
|
|
19537
|
+
return createHash10("sha256").update(content).digest("hex");
|
|
19538
|
+
}
|
|
19539
|
+
function resolveAndRenderProviderContext(opts) {
|
|
19540
|
+
const entry = matchProviderEndpoint(opts.origin);
|
|
19541
|
+
const endpointKey = entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID;
|
|
19542
|
+
const originAccepted = opts.origin !== null;
|
|
19543
|
+
const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
|
|
19544
|
+
const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
|
|
19545
|
+
const content = renderProviderFragment(entry);
|
|
19546
|
+
const dir = join19(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
19547
|
+
if (!existsSync18(dir))
|
|
19548
|
+
mkdirSync9(dir, { recursive: true });
|
|
19549
|
+
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
19550
|
+
const fragmentPath2 = join19(dir, filename);
|
|
19551
|
+
const fragmentSha256 = sha25610(content);
|
|
19552
|
+
writeFileSync6(fragmentPath2, content, "utf8");
|
|
19553
|
+
const manifestPath = join19(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
19554
|
+
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
19555
|
+
try {
|
|
19556
|
+
if (existsSync18(manifestPath)) {
|
|
19557
|
+
const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
19558
|
+
if (parsed && typeof parsed === "object")
|
|
19559
|
+
manifest = parsed;
|
|
19560
|
+
}
|
|
19561
|
+
} catch {}
|
|
19562
|
+
const fragmentsObj = manifest.fragments ?? {};
|
|
19563
|
+
fragmentsObj[entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID] = {
|
|
19564
|
+
path: entry ? filename : "invariant.md",
|
|
19565
|
+
sha256: fragmentSha256,
|
|
19566
|
+
provider: entry ? entry.provider : "unknown",
|
|
19567
|
+
wireProtocol: entry ? entry.wireProtocol : "unknown",
|
|
19568
|
+
rawEndpoint: recordedEndpoint,
|
|
19569
|
+
rawModel: opts.rawModel || null
|
|
19570
|
+
};
|
|
19571
|
+
manifest.fragments = fragmentsObj;
|
|
19572
|
+
writeFileSync6(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
19573
|
+
return {
|
|
19574
|
+
entry,
|
|
19575
|
+
rawEndpoint: opts.rawEndpoint,
|
|
19576
|
+
rawModel: opts.rawModel,
|
|
19577
|
+
endpointKey,
|
|
19578
|
+
fragmentPath: fragmentPath2,
|
|
19579
|
+
fragmentSha256,
|
|
19580
|
+
reason
|
|
19581
|
+
};
|
|
19582
|
+
}
|
|
19583
|
+
function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
|
|
19584
|
+
return [
|
|
19585
|
+
`provider-context`,
|
|
19586
|
+
`t=${nowIso}`,
|
|
19587
|
+
`endpoint_key=${r.endpointKey}`,
|
|
19588
|
+
`fragment=${r.fragmentPath ?? "none"}`,
|
|
19589
|
+
`sha256=${r.fragmentSha256 ?? "none"}`,
|
|
19590
|
+
`model=${r.rawModel || "unset"}`
|
|
19591
|
+
].join(" ");
|
|
19592
|
+
}
|
|
19593
|
+
|
|
19594
|
+
// src/cli/index.tsx
|
|
18771
19595
|
import { createRequire } from "module";
|
|
18772
19596
|
var pkg = createRequire(import.meta.url)("../../package.json");
|
|
18773
19597
|
var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -18873,11 +19697,11 @@ function parseSessionSource(value, order) {
|
|
|
18873
19697
|
if (!path)
|
|
18874
19698
|
throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
|
|
18875
19699
|
const absPath = resolveSessionPath(path);
|
|
18876
|
-
if (!
|
|
19700
|
+
if (!existsSync20(absPath))
|
|
18877
19701
|
throw new Error(`Instruction source file not found: ${absPath}`);
|
|
18878
19702
|
const content = readSessionInstructionSourceFile(absPath);
|
|
18879
19703
|
const source = sourceFromFilePath(absPath, content, order);
|
|
18880
|
-
const resolvedId = id || source.id ||
|
|
19704
|
+
const resolvedId = id || source.id || basename8(absPath);
|
|
18881
19705
|
return {
|
|
18882
19706
|
...source,
|
|
18883
19707
|
id: resolvedId,
|
|
@@ -18917,7 +19741,7 @@ function sessionSourceReplacements(values) {
|
|
|
18917
19741
|
return replacements;
|
|
18918
19742
|
}
|
|
18919
19743
|
function readSessionInstructionSourceFile(path) {
|
|
18920
|
-
const stat =
|
|
19744
|
+
const stat = lstatSync8(path);
|
|
18921
19745
|
if (stat.isSymbolicLink()) {
|
|
18922
19746
|
throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
|
|
18923
19747
|
}
|
|
@@ -18927,7 +19751,7 @@ function readSessionInstructionSourceFile(path) {
|
|
|
18927
19751
|
if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
|
|
18928
19752
|
throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
|
|
18929
19753
|
}
|
|
18930
|
-
return
|
|
19754
|
+
return readFileSync17(path, "utf-8");
|
|
18931
19755
|
}
|
|
18932
19756
|
function parseLayeredReference(value) {
|
|
18933
19757
|
const trimmed = value.trim();
|
|
@@ -18954,9 +19778,9 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
18954
19778
|
}
|
|
18955
19779
|
for (const value of opts.identityExport ?? []) {
|
|
18956
19780
|
const path = resolveSessionPath(value);
|
|
18957
|
-
if (!
|
|
19781
|
+
if (!existsSync20(path))
|
|
18958
19782
|
throw new Error(`Identity instruction export not found: ${path}`);
|
|
18959
|
-
const parsed = JSON.parse(
|
|
19783
|
+
const parsed = JSON.parse(readFileSync17(path, "utf-8"));
|
|
18960
19784
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
18961
19785
|
}
|
|
18962
19786
|
return sources.map((source) => {
|
|
@@ -18970,11 +19794,19 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
18970
19794
|
};
|
|
18971
19795
|
});
|
|
18972
19796
|
}
|
|
19797
|
+
async function loadOwnedClaudeAuthorities(store) {
|
|
19798
|
+
const configs = await store.listConfigs({ category: "rules", agent: "claude", kind: "file" });
|
|
19799
|
+
return configs.filter((config) => config.target_path && basename8(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
|
|
19800
|
+
}
|
|
18973
19801
|
async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-run") {
|
|
19802
|
+
const station = opts.stationProfile === false ? null : stationProfileSource();
|
|
19803
|
+
const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
|
|
18974
19804
|
if (!opts.compileProfile) {
|
|
18975
19805
|
if (opts.providerVariant)
|
|
18976
19806
|
throw new Error("--provider-variant requires --compile-profile.");
|
|
18977
19807
|
const sources = await collectSessionSources(opts, tool, store);
|
|
19808
|
+
if (station)
|
|
19809
|
+
sources.push(station);
|
|
18978
19810
|
return planSessionRender({
|
|
18979
19811
|
tool,
|
|
18980
19812
|
profile: opts.profile,
|
|
@@ -18983,7 +19815,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
|
|
|
18983
19815
|
sessionId: opts.sessionId,
|
|
18984
19816
|
codewithNativeImports: opts.codewithNativeImports,
|
|
18985
19817
|
allowEmptySources: opts.allowEmptySources,
|
|
18986
|
-
sources
|
|
19818
|
+
sources,
|
|
19819
|
+
ownedClaudeAuthorities
|
|
18987
19820
|
});
|
|
18988
19821
|
}
|
|
18989
19822
|
if (!opts.providerVersion?.trim())
|
|
@@ -19014,6 +19847,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
|
|
|
19014
19847
|
...opts.assetScope ? { asset_scope: opts.assetScope } : {},
|
|
19015
19848
|
...opts.assetSurface ? { asset_surface: opts.assetSurface } : {},
|
|
19016
19849
|
allow_asset_installers: opts.allowAssetInstallers,
|
|
19850
|
+
extra_sources: station ? [station] : undefined,
|
|
19851
|
+
ownedClaudeAuthorities,
|
|
19017
19852
|
graph_context: {
|
|
19018
19853
|
...opts.model ? { model: opts.model } : {},
|
|
19019
19854
|
...opts.path ? { path: opts.path } : {},
|
|
@@ -19077,19 +19912,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
|
|
|
19077
19912
|
if (value === "-")
|
|
19078
19913
|
return { json: readBoundedProjectContextStdin() };
|
|
19079
19914
|
const path = resolveSessionPath(value);
|
|
19080
|
-
if (!
|
|
19915
|
+
if (!existsSync20(path)) {
|
|
19081
19916
|
if (allowMissing)
|
|
19082
19917
|
return {};
|
|
19083
19918
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
|
|
19084
19919
|
}
|
|
19085
|
-
const stat =
|
|
19920
|
+
const stat = lstatSync8(path);
|
|
19086
19921
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
19087
19922
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
|
|
19088
19923
|
}
|
|
19089
19924
|
if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
|
|
19090
19925
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
|
|
19091
19926
|
}
|
|
19092
|
-
return { json:
|
|
19927
|
+
return { json: readFileSync17(path, "utf8"), sourcePath: path };
|
|
19093
19928
|
}
|
|
19094
19929
|
function readBoundedProjectContextStdin() {
|
|
19095
19930
|
const chunks = [];
|
|
@@ -19248,15 +20083,16 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
|
|
|
19248
20083
|
console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
|
|
19249
20084
|
});
|
|
19250
20085
|
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) => {
|
|
19251
|
-
const abs =
|
|
19252
|
-
if (!
|
|
20086
|
+
const abs = resolve14(filePath);
|
|
20087
|
+
if (!existsSync20(abs)) {
|
|
19253
20088
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
19254
20089
|
process.exit(1);
|
|
19255
20090
|
}
|
|
19256
|
-
const rawContent =
|
|
19257
|
-
const
|
|
20091
|
+
const rawContent = readFileSync17(abs, "utf-8");
|
|
20092
|
+
const storedFmt = detectFormat(abs);
|
|
20093
|
+
const fmt = redactFormatForTarget(abs, storedFmt);
|
|
19258
20094
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
19259
|
-
const targetPath = abs.startsWith(
|
|
20095
|
+
const targetPath = abs.startsWith(homedir12()) ? abs.replace(homedir12(), "~") : abs;
|
|
19260
20096
|
const name = opts.name || filePath.split("/").pop();
|
|
19261
20097
|
const store = resolveConfigStore();
|
|
19262
20098
|
const allConfigs = await store.listConfigs();
|
|
@@ -19288,7 +20124,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
|
|
|
19288
20124
|
}
|
|
19289
20125
|
config = await store.updateConfig(target.id, {
|
|
19290
20126
|
content,
|
|
19291
|
-
format:
|
|
20127
|
+
format: storedFmt,
|
|
19292
20128
|
is_template: (opts.template ?? false) || isTemplate2,
|
|
19293
20129
|
...opts.category ? { category: opts.category } : {},
|
|
19294
20130
|
...opts.agent ? { agent: opts.agent } : {}
|
|
@@ -19309,7 +20145,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
|
|
|
19309
20145
|
category: opts.category ?? detectCategory(abs),
|
|
19310
20146
|
agent: opts.agent ?? detectAgent(abs),
|
|
19311
20147
|
target_path: opts.kind === "reference" ? null : targetPath,
|
|
19312
|
-
format:
|
|
20148
|
+
format: storedFmt,
|
|
19313
20149
|
content,
|
|
19314
20150
|
is_template: (opts.template ?? false) || isTemplate2
|
|
19315
20151
|
});
|
|
@@ -19424,14 +20260,14 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
19424
20260
|
if (opts.project) {
|
|
19425
20261
|
const dir = typeof opts.project === "string" ? opts.project : process.cwd();
|
|
19426
20262
|
if (opts.all) {
|
|
19427
|
-
const { readdirSync:
|
|
20263
|
+
const { readdirSync: readdirSync6 } = await import("fs");
|
|
19428
20264
|
const absDir = expandPath(dir);
|
|
19429
|
-
const entries =
|
|
20265
|
+
const entries = readdirSync6(absDir, { withFileTypes: true });
|
|
19430
20266
|
let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0, projects = 0;
|
|
19431
20267
|
for (const entry of entries) {
|
|
19432
20268
|
if (!entry.isDirectory())
|
|
19433
20269
|
continue;
|
|
19434
|
-
const projDir =
|
|
20270
|
+
const projDir = join21(absDir, entry.name);
|
|
19435
20271
|
const hasAgentConfig = [
|
|
19436
20272
|
"CLAUDE.md",
|
|
19437
20273
|
".mcp.json",
|
|
@@ -19444,7 +20280,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
19444
20280
|
".aicopilot",
|
|
19445
20281
|
".cursor",
|
|
19446
20282
|
".agents"
|
|
19447
|
-
].some((marker) =>
|
|
20283
|
+
].some((marker) => existsSync20(join21(projDir, marker)));
|
|
19448
20284
|
if (!hasAgentConfig)
|
|
19449
20285
|
continue;
|
|
19450
20286
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
|
|
@@ -19495,7 +20331,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
19495
20331
|
});
|
|
19496
20332
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
19497
20333
|
const store = resolveConfigStore();
|
|
19498
|
-
const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] ||
|
|
20334
|
+
const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join21(getRawStoreRoot(), "instructions.db");
|
|
19499
20335
|
const stats = await store.getConfigStats();
|
|
19500
20336
|
console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
|
|
19501
20337
|
console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
|
|
@@ -19879,7 +20715,7 @@ projectContextCmd.command("apply").description("Atomically write project context
|
|
|
19879
20715
|
}
|
|
19880
20716
|
});
|
|
19881
20717
|
var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
|
|
19882
|
-
sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--json", "output dry-run JSON").action(async (opts) => {
|
|
20718
|
+
sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--no-station-profile", "do not inject the cached station-profile source").option("--json", "output dry-run JSON").action(async (opts) => {
|
|
19883
20719
|
try {
|
|
19884
20720
|
const tool = opts.tool;
|
|
19885
20721
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -19930,7 +20766,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
|
|
|
19930
20766
|
process.exit(1);
|
|
19931
20767
|
}
|
|
19932
20768
|
});
|
|
19933
|
-
sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
|
|
20769
|
+
sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
|
|
19934
20770
|
try {
|
|
19935
20771
|
const tool = opts.tool;
|
|
19936
20772
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -19940,7 +20776,8 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
|
|
|
19940
20776
|
const store = resolveConfigStore();
|
|
19941
20777
|
const plan = await buildSessionRenderPlan(opts, tool, store, "apply");
|
|
19942
20778
|
const globalCoverage = opts.checkGlobalCoverage ? await checkGlobalSourceCoverage(plan, store) : null;
|
|
19943
|
-
const
|
|
20779
|
+
const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
|
|
20780
|
+
const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, ownedClaudeAuthorities });
|
|
19944
20781
|
if (opts.json) {
|
|
19945
20782
|
printJson({
|
|
19946
20783
|
...result,
|
|
@@ -20017,6 +20854,94 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
|
|
|
20017
20854
|
process.exit(1);
|
|
20018
20855
|
}
|
|
20019
20856
|
});
|
|
20857
|
+
var stationProfileCmd = program.command("station-profile").description("Generate and inject the compact station profile block into every session render");
|
|
20858
|
+
stationProfileCmd.command("refresh").description("Regenerate the cached station profile block (idempotent; writes only when changed)").option("--dry-run", "build and print the block without writing the cache").option("--json", "output JSON").action((opts) => {
|
|
20859
|
+
try {
|
|
20860
|
+
const result = refreshStationProfile({ dryRun: opts.dryRun });
|
|
20861
|
+
if (opts.json) {
|
|
20862
|
+
printJson({
|
|
20863
|
+
path: result.path,
|
|
20864
|
+
bytes: result.bytes,
|
|
20865
|
+
generatedAt: result.generatedAt,
|
|
20866
|
+
dryRun: opts.dryRun === true,
|
|
20867
|
+
budget: STATION_PROFILE_MAX_BYTES,
|
|
20868
|
+
statusProbe: result.statusProbe,
|
|
20869
|
+
machine: {
|
|
20870
|
+
id: result.machine.id,
|
|
20871
|
+
hostname: result.machine.hostname,
|
|
20872
|
+
tailscaleName: result.machine.tailscaleName,
|
|
20873
|
+
platform: result.machine.platform,
|
|
20874
|
+
arch: result.machine.arch,
|
|
20875
|
+
user: result.machine.user,
|
|
20876
|
+
workspacePath: result.machine.workspacePath,
|
|
20877
|
+
status: result.machine.status
|
|
20878
|
+
},
|
|
20879
|
+
packages: result.packages,
|
|
20880
|
+
content: result.content
|
|
20881
|
+
});
|
|
20882
|
+
return;
|
|
20883
|
+
}
|
|
20884
|
+
const prefix = opts.dryRun ? chalk.yellow("[dry-run]") : chalk.green("OK");
|
|
20885
|
+
console.log(`${prefix} station profile ${chalk.dim(`(${result.bytes} B, budget ${STATION_PROFILE_MAX_BYTES} B)`)}`);
|
|
20886
|
+
console.log(`${chalk.cyan("path:")} ${result.path}`);
|
|
20887
|
+
if (result.machine.status) {
|
|
20888
|
+
console.log(`${chalk.cyan("status probe:")} ${result.statusProbe} (${result.machine.status.state})`);
|
|
20889
|
+
} else {
|
|
20890
|
+
console.log(`${chalk.cyan("status probe:")} ${result.statusProbe}`);
|
|
20891
|
+
}
|
|
20892
|
+
console.log(`${chalk.cyan("sources:")} machines manifest + bun global dir + local OS`);
|
|
20893
|
+
console.log("");
|
|
20894
|
+
printLine(result.content);
|
|
20895
|
+
} catch (error) {
|
|
20896
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
20897
|
+
process.exit(1);
|
|
20898
|
+
}
|
|
20899
|
+
});
|
|
20900
|
+
stationProfileCmd.command("show").description("Print the cached station profile block").option("--json", "output JSON").action((opts) => {
|
|
20901
|
+
const content = readStationProfile();
|
|
20902
|
+
if (content === null) {
|
|
20903
|
+
console.error(chalk.red("No cached station profile. Run `instructions station-profile refresh` first."));
|
|
20904
|
+
process.exit(1);
|
|
20905
|
+
}
|
|
20906
|
+
if (opts.json) {
|
|
20907
|
+
printJson({ path: getStationProfileCachePath(), bytes: Buffer.byteLength(content, "utf8"), content });
|
|
20908
|
+
return;
|
|
20909
|
+
}
|
|
20910
|
+
printLine(content);
|
|
20911
|
+
});
|
|
20912
|
+
stationProfileCmd.command("path").description("Print the station profile cache path").action(() => {
|
|
20913
|
+
printLine(getStationProfileCachePath());
|
|
20914
|
+
});
|
|
20915
|
+
stationProfileCmd.command("preview").description("Build and print the block without writing the cache (alias of refresh --dry-run)").option("--json", "output JSON").action((opts) => {
|
|
20916
|
+
try {
|
|
20917
|
+
const machine = resolveStationProfileMachine();
|
|
20918
|
+
const packages = resolveStationProfilePackages();
|
|
20919
|
+
const content = buildStationProfileBlock({ machine, packages });
|
|
20920
|
+
if (opts.json) {
|
|
20921
|
+
printJson({
|
|
20922
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
20923
|
+
budget: STATION_PROFILE_MAX_BYTES,
|
|
20924
|
+
machine: {
|
|
20925
|
+
id: machine.id,
|
|
20926
|
+
hostname: machine.hostname,
|
|
20927
|
+
tailscaleName: machine.tailscaleName,
|
|
20928
|
+
platform: machine.platform,
|
|
20929
|
+
arch: machine.arch,
|
|
20930
|
+
user: machine.user,
|
|
20931
|
+
workspacePath: machine.workspacePath,
|
|
20932
|
+
status: machine.status
|
|
20933
|
+
},
|
|
20934
|
+
packages,
|
|
20935
|
+
content
|
|
20936
|
+
});
|
|
20937
|
+
return;
|
|
20938
|
+
}
|
|
20939
|
+
printLine(content);
|
|
20940
|
+
} catch (error) {
|
|
20941
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
20942
|
+
process.exit(1);
|
|
20943
|
+
}
|
|
20944
|
+
});
|
|
20020
20945
|
var snapshotCmd = program.command("snapshot").description("Manage config version history");
|
|
20021
20946
|
snapshotCmd.command("list <config>").description("List snapshots for a config").option("--limit <n>", `max rows (default ${DEFAULT_LIST_LIMIT})`).option("--cursor <n>", "zero-based pagination cursor").action(async (configId, opts) => {
|
|
20022
20947
|
try {
|
|
@@ -20060,6 +20985,22 @@ snapshotCmd.command("restore <config> <snapshot-id>").description("Restore a con
|
|
|
20060
20985
|
process.exit(1);
|
|
20061
20986
|
}
|
|
20062
20987
|
});
|
|
20988
|
+
snapshotCmd.command("prune <config>").description("Delete old snapshots, keeping the N most recent").option("--keep <n>", "number of most-recent snapshots to keep (default 10)").action(async (configId, opts) => {
|
|
20989
|
+
try {
|
|
20990
|
+
const store = resolveConfigStore();
|
|
20991
|
+
const c = await store.getConfig(configId);
|
|
20992
|
+
const keep = opts.keep === undefined ? 10 : Number(opts.keep);
|
|
20993
|
+
if (!Number.isInteger(keep) || keep < 1) {
|
|
20994
|
+
console.error(chalk.red(`--keep must be a positive integer (got: ${opts.keep})`));
|
|
20995
|
+
process.exit(1);
|
|
20996
|
+
}
|
|
20997
|
+
const removed = await store.pruneSnapshots(c.id, keep);
|
|
20998
|
+
console.log(chalk.green("\u2713") + ` Pruned ${removed} snapshot(s), keeping the ${keep} most recent for ${c.slug}`);
|
|
20999
|
+
} catch (e) {
|
|
21000
|
+
console.error(chalk.red(formatCliError(e)));
|
|
21001
|
+
process.exit(1);
|
|
21002
|
+
}
|
|
21003
|
+
});
|
|
20063
21004
|
var templateCmd = program.command("template").description("Work with template configs");
|
|
20064
21005
|
templateCmd.command("vars <id>").description("Show template variables").action(async (id) => {
|
|
20065
21006
|
try {
|
|
@@ -20166,7 +21107,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
|
|
|
20166
21107
|
for (let i = 0;i < configs.length; i += BATCH) {
|
|
20167
21108
|
const batch = configs.slice(i, i + BATCH);
|
|
20168
21109
|
for (const c of batch) {
|
|
20169
|
-
const fmt = c.format;
|
|
21110
|
+
const fmt = redactFormatForTarget(c.target_path ?? "", c.format);
|
|
20170
21111
|
const secrets = scanSecrets(c.content, fmt);
|
|
20171
21112
|
if (secrets.length === 0)
|
|
20172
21113
|
continue;
|
|
@@ -20250,14 +21191,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
|
|
|
20250
21191
|
} else if (target === "codex") {
|
|
20251
21192
|
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
20252
21193
|
const { join: j } = await import("path");
|
|
20253
|
-
const configPath = j(
|
|
21194
|
+
const configPath = j(homedir12(), ".codex", "config.toml");
|
|
20254
21195
|
const block = `
|
|
20255
21196
|
[mcp_servers.configs]
|
|
20256
21197
|
command = "${mcpBinary}"
|
|
20257
21198
|
args = []
|
|
20258
21199
|
`;
|
|
20259
21200
|
if (ex(configPath)) {
|
|
20260
|
-
const content =
|
|
21201
|
+
const content = readFileSync17(configPath, "utf-8");
|
|
20261
21202
|
if (content.includes("[mcp_servers.configs]")) {
|
|
20262
21203
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
20263
21204
|
continue;
|
|
@@ -20268,7 +21209,7 @@ args = []
|
|
|
20268
21209
|
} else if (target === "antigravity") {
|
|
20269
21210
|
const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
20270
21211
|
const { dirname: dn, join: j } = await import("path");
|
|
20271
|
-
const configPath = j(
|
|
21212
|
+
const configPath = j(homedir12(), ".gemini", "config", "mcp_config.json");
|
|
20272
21213
|
let settings = {};
|
|
20273
21214
|
if (ex(configPath)) {
|
|
20274
21215
|
try {
|
|
@@ -20352,7 +21293,7 @@ DB stats:`));
|
|
|
20352
21293
|
if (count > 0)
|
|
20353
21294
|
console.log(` ${key.padEnd(18)} ${count}`);
|
|
20354
21295
|
}
|
|
20355
|
-
const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] ||
|
|
21296
|
+
const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join21(getRawStoreRoot(), "instructions.db");
|
|
20356
21297
|
console.log(chalk.dim(`
|
|
20357
21298
|
${isApiTransport() ? "API" : "DB"}: ${location}`));
|
|
20358
21299
|
});
|
|
@@ -20413,10 +21354,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
|
|
|
20413
21354
|
});
|
|
20414
21355
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
20415
21356
|
const { mkdirSync: mk } = await import("fs");
|
|
20416
|
-
const backupDir =
|
|
21357
|
+
const backupDir = join21(getRawStoreRoot(), "backups");
|
|
20417
21358
|
mk(backupDir, { recursive: true });
|
|
20418
21359
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
20419
|
-
const outPath =
|
|
21360
|
+
const outPath = join21(backupDir, `configs-${ts}.tar.gz`);
|
|
20420
21361
|
const result = await exportConfigs(outPath, { store: resolveConfigStore() });
|
|
20421
21362
|
const { statSync: st } = await import("fs");
|
|
20422
21363
|
const size = st(outPath).size;
|
|
@@ -20444,9 +21385,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
20444
21385
|
console.log(chalk.cyan("Known files on disk:"));
|
|
20445
21386
|
for (const k of KNOWN_CONFIGS) {
|
|
20446
21387
|
if (k.rulesDir) {
|
|
20447
|
-
|
|
21388
|
+
existsSync20(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
|
|
20448
21389
|
} else {
|
|
20449
|
-
|
|
21390
|
+
existsSync20(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
|
|
20450
21391
|
}
|
|
20451
21392
|
}
|
|
20452
21393
|
const allConfigs = await store.listConfigs();
|
|
@@ -20468,7 +21409,7 @@ Stored configs (${allConfigs.length}):`));
|
|
|
20468
21409
|
pass(`${validCount}/${allConfigs.length} valid syntax`);
|
|
20469
21410
|
let secretCount = 0;
|
|
20470
21411
|
for (const c of allConfigs) {
|
|
20471
|
-
const found = scanSecrets(c.content, c.format);
|
|
21412
|
+
const found = scanSecrets(c.content, redactFormatForTarget(c.target_path ?? "", c.format));
|
|
20472
21413
|
secretCount += found.length;
|
|
20473
21414
|
}
|
|
20474
21415
|
secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
|
|
@@ -20524,6 +21465,7 @@ _configs() {
|
|
|
20524
21465
|
'scan:Scan for secrets'
|
|
20525
21466
|
'profile:Manage profiles'
|
|
20526
21467
|
'session:Plan and apply session instructions'
|
|
21468
|
+
'station-profile:Generate and inject the compact station profile block'
|
|
20527
21469
|
'snapshot:Version history'
|
|
20528
21470
|
'template:Template operations'
|
|
20529
21471
|
'mcp:Install MCP server'
|
|
@@ -20539,7 +21481,7 @@ compdef _configs configs`);
|
|
|
20539
21481
|
console.log(`# bash completion for configs
|
|
20540
21482
|
_configs_completions() {
|
|
20541
21483
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
20542
|
-
local commands="list show add apply diff sync export import whoami status init scan profile session snapshot template mcp backup restore doctor completions"
|
|
21484
|
+
local commands="list show add apply diff sync export import whoami status init scan profile session station-profile snapshot template mcp backup restore doctor completions"
|
|
20543
21485
|
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
20544
21486
|
}
|
|
20545
21487
|
complete -F _configs_completions configs`);
|
|
@@ -20599,16 +21541,16 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20599
21541
|
for (const k of KNOWN_CONFIGS) {
|
|
20600
21542
|
if (k.rulesDir) {
|
|
20601
21543
|
const absDir = expandPath2(k.rulesDir);
|
|
20602
|
-
if (!
|
|
21544
|
+
if (!existsSync20(absDir))
|
|
20603
21545
|
continue;
|
|
20604
|
-
const { readdirSync:
|
|
20605
|
-
for (const f of
|
|
20606
|
-
const abs =
|
|
21546
|
+
const { readdirSync: readdirSync6 } = await import("fs");
|
|
21547
|
+
for (const f of readdirSync6(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
21548
|
+
const abs = join21(absDir, f);
|
|
20607
21549
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20608
21550
|
}
|
|
20609
21551
|
} else {
|
|
20610
21552
|
const abs = expandPath2(k.path);
|
|
20611
|
-
if (
|
|
21553
|
+
if (existsSync20(abs))
|
|
20612
21554
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20613
21555
|
}
|
|
20614
21556
|
}
|
|
@@ -20616,7 +21558,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20616
21558
|
const tick = async () => {
|
|
20617
21559
|
let changed = 0;
|
|
20618
21560
|
for (const [abs, oldMtime] of mtimes) {
|
|
20619
|
-
if (!
|
|
21561
|
+
if (!existsSync20(abs))
|
|
20620
21562
|
continue;
|
|
20621
21563
|
const newMtime = st(abs).mtimeMs;
|
|
20622
21564
|
if (newMtime !== oldMtime) {
|
|
@@ -20628,10 +21570,10 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20628
21570
|
for (const k of KNOWN_CONFIGS) {
|
|
20629
21571
|
if (k.rulesDir) {
|
|
20630
21572
|
const absDir = expandPath2(k.rulesDir);
|
|
20631
|
-
if (!
|
|
21573
|
+
if (!existsSync20(absDir))
|
|
20632
21574
|
continue;
|
|
20633
21575
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
20634
|
-
const abs =
|
|
21576
|
+
const abs = join21(absDir, f);
|
|
20635
21577
|
if (!mtimes.has(abs)) {
|
|
20636
21578
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20637
21579
|
changed++;
|
|
@@ -20639,7 +21581,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20639
21581
|
}
|
|
20640
21582
|
} else {
|
|
20641
21583
|
const abs = expandPath2(k.path);
|
|
20642
|
-
if (
|
|
21584
|
+
if (existsSync20(abs) && !mtimes.has(abs)) {
|
|
20643
21585
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20644
21586
|
changed++;
|
|
20645
21587
|
}
|
|
@@ -20667,12 +21609,12 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
20667
21609
|
if (!c.target_path)
|
|
20668
21610
|
continue;
|
|
20669
21611
|
const abs = expandPath(c.target_path);
|
|
20670
|
-
if (!
|
|
21612
|
+
if (!existsSync20(abs)) {
|
|
20671
21613
|
missing++;
|
|
20672
21614
|
continue;
|
|
20673
21615
|
}
|
|
20674
|
-
const disk =
|
|
20675
|
-
const { content: redactedDisk } = redactContent(disk, c.format);
|
|
21616
|
+
const disk = readFileSync17(abs, "utf-8");
|
|
21617
|
+
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
|
|
20676
21618
|
if (redactedDisk !== c.content)
|
|
20677
21619
|
drifted++;
|
|
20678
21620
|
}
|
|
@@ -20738,7 +21680,7 @@ program.command("clean").description("Remove configs from DB whose target files
|
|
|
20738
21680
|
if (!c.target_path)
|
|
20739
21681
|
continue;
|
|
20740
21682
|
const abs = expandPath(c.target_path);
|
|
20741
|
-
if (!
|
|
21683
|
+
if (!existsSync20(abs)) {
|
|
20742
21684
|
if (printed < maxPrinted) {
|
|
20743
21685
|
if (opts.dryRun) {
|
|
20744
21686
|
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
|
|
@@ -20879,6 +21821,48 @@ program.command("feedback <message>").description("Send feedback about this serv
|
|
|
20879
21821
|
});
|
|
20880
21822
|
console.log(chalk.green("\u2713") + " Feedback saved. Thank you!");
|
|
20881
21823
|
});
|
|
21824
|
+
var providerContextCmd = program.command("provider-context").description("Resolve and render per-endpoint model-identity fragments for coding-agent harnesses");
|
|
21825
|
+
providerContextCmd.command("resolve").description("Resolve the endpoint to a provider-context fragment, render it into the home, and emit an audit line").option("--endpoint <url>", "endpoint/base URL to resolve (default: $ANTHROPIC_BASE_URL)").option("--model <id>", "model id to record in the audit line (default: $ANTHROPIC_MODEL)").option("--home <dir>", `harness home directory (default: os.homedir()); fragments render to <home>/${PROVIDER_CONTEXT_DIR}`).option("--json", "print the full resolution as JSON").action((opts) => {
|
|
21826
|
+
try {
|
|
21827
|
+
const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
|
|
21828
|
+
const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
|
|
21829
|
+
const homeDir3 = opts.home ?? homedir12();
|
|
21830
|
+
const origin = normalizeEndpointOrigin(rawEndpoint);
|
|
21831
|
+
const resolution = resolveAndRenderProviderContext({
|
|
21832
|
+
origin,
|
|
21833
|
+
rawEndpoint,
|
|
21834
|
+
rawModel,
|
|
21835
|
+
homeDir: homeDir3
|
|
21836
|
+
});
|
|
21837
|
+
if (opts.json) {
|
|
21838
|
+
printJson({
|
|
21839
|
+
endpointKey: resolution.endpointKey,
|
|
21840
|
+
provider: resolution.entry?.provider ?? null,
|
|
21841
|
+
wireProtocol: resolution.entry?.wireProtocol ?? null,
|
|
21842
|
+
fragmentPath: resolution.fragmentPath,
|
|
21843
|
+
fragmentSha256: resolution.fragmentSha256,
|
|
21844
|
+
reason: resolution.reason,
|
|
21845
|
+
audit: providerContextAuditLine(resolution)
|
|
21846
|
+
});
|
|
21847
|
+
} else {
|
|
21848
|
+
if (resolution.entry) {
|
|
21849
|
+
console.log(chalk.green("\u2713") + ` provider: ${chalk.bold(resolution.entry.provider)} (${resolution.entry.wireProtocol})`);
|
|
21850
|
+
} else {
|
|
21851
|
+
const banner = rawEndpoint && !resolution.reason ? ` no registered provider for endpoint ${rawEndpoint} \u2014 using invariant fragment` : resolution.reason ?? " no registered provider \u2014 using invariant fragment";
|
|
21852
|
+
console.log(chalk.yellow("!") + banner);
|
|
21853
|
+
}
|
|
21854
|
+
console.log(chalk.cyan("fragment:") + ` ${resolution.fragmentPath}`);
|
|
21855
|
+
if (resolution.reason)
|
|
21856
|
+
console.log(chalk.dim(resolution.reason));
|
|
21857
|
+
console.log(chalk.dim(providerContextAuditLine(resolution)));
|
|
21858
|
+
}
|
|
21859
|
+
if (!resolution.entry)
|
|
21860
|
+
process.exitCode = 1;
|
|
21861
|
+
} catch (e) {
|
|
21862
|
+
console.error(chalk.red(formatCliError(e)));
|
|
21863
|
+
process.exit(1);
|
|
21864
|
+
}
|
|
21865
|
+
});
|
|
20882
21866
|
program.version(pkg.version).name("instructions");
|
|
20883
21867
|
registerEventsCommands(program, { source: "configs" });
|
|
20884
21868
|
program.parseAsync(process.argv).catch((e) => {
|