@hasna/instructions 0.4.43 → 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/index.js +1147 -182
- package/dist/cli/station-profile.test.d.ts +2 -0
- package/dist/cli/station-profile.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +677 -189
- 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/mcp/index.js +2 -2
- 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 +18 -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 {
|
|
@@ -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)) {
|
|
@@ -14584,7 +14615,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14584
14615
|
}
|
|
14585
14616
|
async function syncToDir(dir, opts = {}) {
|
|
14586
14617
|
const store = opts.store ?? resolveConfigStore();
|
|
14587
|
-
const home =
|
|
14618
|
+
const home = homedir8();
|
|
14588
14619
|
const absDir = expandPath(dir);
|
|
14589
14620
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14590
14621
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14643,10 +14674,10 @@ __export(exports_sync, {
|
|
|
14643
14674
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS,
|
|
14644
14675
|
CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
|
|
14645
14676
|
});
|
|
14646
|
-
import { existsSync as
|
|
14647
|
-
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";
|
|
14648
14679
|
function claudeRuleOutputs(fileName) {
|
|
14649
|
-
const stem =
|
|
14680
|
+
const stem = basename6(fileName, extname3(fileName));
|
|
14650
14681
|
return [
|
|
14651
14682
|
{ agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
|
|
14652
14683
|
];
|
|
@@ -14683,15 +14714,15 @@ function isGeneratedOutputTarget2(config, owners) {
|
|
|
14683
14714
|
return !!ownerIds && !ownerIds.has(config.id);
|
|
14684
14715
|
}
|
|
14685
14716
|
function hasClaudePromptSource() {
|
|
14686
|
-
return
|
|
14717
|
+
return existsSync10(expandPath("~/.claude/CLAUDE.md"));
|
|
14687
14718
|
}
|
|
14688
14719
|
function hasClaudeRuleSourceForCursorTarget(targetPath) {
|
|
14689
14720
|
const absoluteTargetPath = expandPath(targetPath);
|
|
14690
14721
|
const absolutePrefix = expandPath("~/.cursor/rules");
|
|
14691
14722
|
if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
|
|
14692
14723
|
return false;
|
|
14693
|
-
const stem =
|
|
14694
|
-
return
|
|
14724
|
+
const stem = basename6(absoluteTargetPath, ".mdc");
|
|
14725
|
+
return existsSync10(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync10(expandPath(`~/.claude/rules/${stem}.mdc`));
|
|
14695
14726
|
}
|
|
14696
14727
|
function isKnownGeneratedTargetPath(targetPath) {
|
|
14697
14728
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
@@ -14709,7 +14740,7 @@ async function syncProject(opts) {
|
|
|
14709
14740
|
const machine = detectMachineContext();
|
|
14710
14741
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14711
14742
|
const abs = join12(absDir, pf.file);
|
|
14712
|
-
if (!
|
|
14743
|
+
if (!existsSync10(abs))
|
|
14713
14744
|
continue;
|
|
14714
14745
|
try {
|
|
14715
14746
|
const rawContent = readFileSync8(abs, "utf-8");
|
|
@@ -14749,7 +14780,7 @@ async function syncProject(opts) {
|
|
|
14749
14780
|
{ dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
14750
14781
|
{ dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14751
14782
|
]) {
|
|
14752
|
-
if (!
|
|
14783
|
+
if (!existsSync10(ruleDir.dir))
|
|
14753
14784
|
continue;
|
|
14754
14785
|
const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14755
14786
|
for (const f of mdFiles) {
|
|
@@ -14793,7 +14824,7 @@ async function syncKnown(opts = {}) {
|
|
|
14793
14824
|
for (const known of targets) {
|
|
14794
14825
|
if (known.rulesDir) {
|
|
14795
14826
|
const absDir = expandPath(known.rulesDir);
|
|
14796
|
-
if (!
|
|
14827
|
+
if (!existsSync10(absDir)) {
|
|
14797
14828
|
result.skipped.push(known.rulesDir);
|
|
14798
14829
|
continue;
|
|
14799
14830
|
}
|
|
@@ -14834,7 +14865,7 @@ async function syncKnown(opts = {}) {
|
|
|
14834
14865
|
continue;
|
|
14835
14866
|
}
|
|
14836
14867
|
const abs = expandPath(known.path);
|
|
14837
|
-
if (!
|
|
14868
|
+
if (!existsSync10(abs)) {
|
|
14838
14869
|
result.skipped.push(known.path);
|
|
14839
14870
|
continue;
|
|
14840
14871
|
}
|
|
@@ -14940,7 +14971,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
|
|
|
14940
14971
|
}
|
|
14941
14972
|
function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
14942
14973
|
const path = expandPath(targetPath);
|
|
14943
|
-
if (!
|
|
14974
|
+
if (!existsSync10(path))
|
|
14944
14975
|
return `(file not found on disk: ${path})`;
|
|
14945
14976
|
const diskContent = readFileSync8(path, "utf-8");
|
|
14946
14977
|
if (diskContent === expectedContent)
|
|
@@ -15175,18 +15206,18 @@ __export(exports_package_manager_guard, {
|
|
|
15175
15206
|
scanPackageManagerSecrets: () => scanPackageManagerSecrets
|
|
15176
15207
|
});
|
|
15177
15208
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15178
|
-
import { existsSync as
|
|
15179
|
-
import { homedir as
|
|
15180
|
-
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";
|
|
15181
15212
|
function scanPackageManagerSecrets(options = {}) {
|
|
15182
|
-
const cwd = options.cwd ?
|
|
15183
|
-
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));
|
|
15184
15215
|
const findings = [];
|
|
15185
15216
|
let scannedFiles = 0;
|
|
15186
15217
|
for (const root of roots) {
|
|
15187
|
-
if (!
|
|
15218
|
+
if (!existsSync19(root))
|
|
15188
15219
|
continue;
|
|
15189
|
-
const stat =
|
|
15220
|
+
const stat = lstatSync7(root);
|
|
15190
15221
|
if (stat.isFile()) {
|
|
15191
15222
|
if (!shouldScanRepoFile(root))
|
|
15192
15223
|
continue;
|
|
@@ -15194,7 +15225,7 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15194
15225
|
if (text === null)
|
|
15195
15226
|
continue;
|
|
15196
15227
|
scannedFiles++;
|
|
15197
|
-
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root),
|
|
15228
|
+
findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
|
|
15198
15229
|
continue;
|
|
15199
15230
|
}
|
|
15200
15231
|
if (!stat.isDirectory())
|
|
@@ -15211,10 +15242,10 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15211
15242
|
}
|
|
15212
15243
|
}
|
|
15213
15244
|
if (options.includeHome) {
|
|
15214
|
-
const home =
|
|
15245
|
+
const home = homedir11();
|
|
15215
15246
|
for (const name of HOME_FILES) {
|
|
15216
|
-
const file =
|
|
15217
|
-
if (!
|
|
15247
|
+
const file = join20(home, name);
|
|
15248
|
+
if (!existsSync19(file))
|
|
15218
15249
|
continue;
|
|
15219
15250
|
const text = readTextFile(file);
|
|
15220
15251
|
if (text === null)
|
|
@@ -15234,16 +15265,16 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15234
15265
|
function collectRepoFiles(root) {
|
|
15235
15266
|
const out = [];
|
|
15236
15267
|
const visit = (dir) => {
|
|
15237
|
-
for (const entry of
|
|
15268
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
15238
15269
|
if (entry.isDirectory()) {
|
|
15239
15270
|
if (SKIP_DIRS.has(entry.name))
|
|
15240
15271
|
continue;
|
|
15241
|
-
visit(
|
|
15272
|
+
visit(join20(dir, entry.name));
|
|
15242
15273
|
continue;
|
|
15243
15274
|
}
|
|
15244
15275
|
if (!entry.isFile())
|
|
15245
15276
|
continue;
|
|
15246
|
-
const file =
|
|
15277
|
+
const file = join20(dir, entry.name);
|
|
15247
15278
|
if (shouldScanRepoFile(file))
|
|
15248
15279
|
out.push(file);
|
|
15249
15280
|
}
|
|
@@ -15252,11 +15283,11 @@ function collectRepoFiles(root) {
|
|
|
15252
15283
|
return out;
|
|
15253
15284
|
}
|
|
15254
15285
|
function shouldScanRepoFile(file) {
|
|
15255
|
-
const name =
|
|
15286
|
+
const name = basename7(file);
|
|
15256
15287
|
return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
|
|
15257
15288
|
}
|
|
15258
15289
|
function classifyRepoFile(file) {
|
|
15259
|
-
const name =
|
|
15290
|
+
const name = basename7(file);
|
|
15260
15291
|
if (isNpmrcName(name))
|
|
15261
15292
|
return "repo-npmrc";
|
|
15262
15293
|
if (isBunConfigName(name))
|
|
@@ -15278,10 +15309,10 @@ function isNpmrcName(name) {
|
|
|
15278
15309
|
}
|
|
15279
15310
|
function readTextFile(file) {
|
|
15280
15311
|
try {
|
|
15281
|
-
const stat =
|
|
15312
|
+
const stat = lstatSync7(file);
|
|
15282
15313
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15283
15314
|
return null;
|
|
15284
|
-
const buf =
|
|
15315
|
+
const buf = readFileSync16(file);
|
|
15285
15316
|
if (buf.includes(0))
|
|
15286
15317
|
return null;
|
|
15287
15318
|
return buf.toString("utf-8");
|
|
@@ -15481,7 +15512,7 @@ function trackedFiles(root) {
|
|
|
15481
15512
|
}
|
|
15482
15513
|
function isTrackedFile(file) {
|
|
15483
15514
|
try {
|
|
15484
|
-
const repoRoot = execFileSync2("git", ["-C",
|
|
15515
|
+
const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
|
|
15485
15516
|
encoding: "utf-8",
|
|
15486
15517
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15487
15518
|
}).trim();
|
|
@@ -15511,7 +15542,7 @@ function stripInlineComment(value) {
|
|
|
15511
15542
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15512
15543
|
}
|
|
15513
15544
|
function displayPath(file, root) {
|
|
15514
|
-
const home =
|
|
15545
|
+
const home = homedir11();
|
|
15515
15546
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15516
15547
|
return "~/" + toPosix(relative7(home, file));
|
|
15517
15548
|
if (isAbsolute5(root) && file.startsWith(root + "/"))
|
|
@@ -15569,8 +15600,12 @@ import { existsSync } from "fs";
|
|
|
15569
15600
|
import { homedir } from "os";
|
|
15570
15601
|
import { join } from "path";
|
|
15571
15602
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
15603
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
15604
|
+
import { isIP } from "net";
|
|
15572
15605
|
import { randomUUID } from "crypto";
|
|
15573
15606
|
import { spawn } from "child_process";
|
|
15607
|
+
import { request as nodeHttpRequest } from "http";
|
|
15608
|
+
import { request as nodeHttpsRequest } from "https";
|
|
15574
15609
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
15575
15610
|
function getPathValue(input, path) {
|
|
15576
15611
|
return path.split(".").reduce((value, part) => {
|
|
@@ -15973,6 +16008,214 @@ function signPayload(secret, timestamp, body) {
|
|
|
15973
16008
|
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
15974
16009
|
return `sha256=${digest}`;
|
|
15975
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
|
+
}
|
|
15976
16219
|
function now() {
|
|
15977
16220
|
return new Date().toISOString();
|
|
15978
16221
|
}
|
|
@@ -16003,9 +16246,18 @@ function buildWebhookRequest(event, channel, options = {}) {
|
|
|
16003
16246
|
}
|
|
16004
16247
|
return { body, headers };
|
|
16005
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
|
+
}
|
|
16006
16257
|
async function dispatchWebhook(event, channel, options = {}) {
|
|
16007
16258
|
if (!channel.webhook)
|
|
16008
16259
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
16260
|
+
const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
|
|
16009
16261
|
const startedAt = now();
|
|
16010
16262
|
let secret = channel.webhook.secret;
|
|
16011
16263
|
if (channel.webhook.secretRef) {
|
|
@@ -16022,10 +16274,14 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
16022
16274
|
}
|
|
16023
16275
|
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
16024
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
|
+
}
|
|
16025
16281
|
const controller = new AbortController;
|
|
16026
16282
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
16027
16283
|
try {
|
|
16028
|
-
const response = await (options.fetchImpl ?? fetch)(
|
|
16284
|
+
const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
|
|
16029
16285
|
method: "POST",
|
|
16030
16286
|
headers,
|
|
16031
16287
|
body,
|
|
@@ -16053,6 +16309,130 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
16053
16309
|
clearTimeout(timeout);
|
|
16054
16310
|
}
|
|
16055
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
|
+
}
|
|
16056
16436
|
function failedAttempt(startedAt, error) {
|
|
16057
16437
|
return {
|
|
16058
16438
|
attempt: 1,
|
|
@@ -16262,7 +16642,9 @@ class EventsClient {
|
|
|
16262
16642
|
this.transportOptions = {
|
|
16263
16643
|
fetchImpl: options.fetchImpl,
|
|
16264
16644
|
secretResolver: options.secretResolver,
|
|
16265
|
-
now: options.now
|
|
16645
|
+
now: options.now,
|
|
16646
|
+
tls: options.tls,
|
|
16647
|
+
webhookTargetPolicy: options.webhookTargetPolicy
|
|
16266
16648
|
};
|
|
16267
16649
|
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
16268
16650
|
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
|
@@ -16763,9 +17145,9 @@ var {
|
|
|
16763
17145
|
// src/cli/index.tsx
|
|
16764
17146
|
init_apply();
|
|
16765
17147
|
import chalk from "chalk";
|
|
16766
|
-
import { existsSync as
|
|
16767
|
-
import { homedir as
|
|
16768
|
-
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";
|
|
16769
17151
|
|
|
16770
17152
|
// src/lib/config-target-identity.ts
|
|
16771
17153
|
init_apply();
|
|
@@ -16811,7 +17193,7 @@ init_redact();
|
|
|
16811
17193
|
|
|
16812
17194
|
// src/lib/export.ts
|
|
16813
17195
|
init_config_store();
|
|
16814
|
-
import { existsSync as
|
|
17196
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
16815
17197
|
import { join as join13, resolve as resolve8 } from "path";
|
|
16816
17198
|
import { tmpdir } from "os";
|
|
16817
17199
|
async function exportConfigs(outputPath, opts = {}) {
|
|
@@ -16843,7 +17225,7 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16843
17225
|
}
|
|
16844
17226
|
return { path: absOutput, count: configs.length };
|
|
16845
17227
|
} finally {
|
|
16846
|
-
if (
|
|
17228
|
+
if (existsSync11(tmpDir)) {
|
|
16847
17229
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
16848
17230
|
}
|
|
16849
17231
|
}
|
|
@@ -16851,7 +17233,7 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
16851
17233
|
|
|
16852
17234
|
// src/lib/import.ts
|
|
16853
17235
|
init_config_store();
|
|
16854
|
-
import { existsSync as
|
|
17236
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
|
|
16855
17237
|
import { join as join14, resolve as resolve9 } from "path";
|
|
16856
17238
|
import { tmpdir as tmpdir2 } from "os";
|
|
16857
17239
|
async function importConfigs(bundlePath, opts = {}) {
|
|
@@ -16872,14 +17254,14 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16872
17254
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
16873
17255
|
}
|
|
16874
17256
|
const manifestPath = join14(tmpDir, "manifest.json");
|
|
16875
|
-
if (!
|
|
17257
|
+
if (!existsSync12(manifestPath))
|
|
16876
17258
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
16877
17259
|
const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
16878
17260
|
for (const meta of manifest.configs) {
|
|
16879
17261
|
try {
|
|
16880
17262
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
16881
17263
|
const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
16882
|
-
const content =
|
|
17264
|
+
const content = existsSync12(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
|
|
16883
17265
|
let existing = null;
|
|
16884
17266
|
try {
|
|
16885
17267
|
existing = await store.getConfig(meta.slug);
|
|
@@ -16913,7 +17295,7 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
16913
17295
|
}
|
|
16914
17296
|
return result;
|
|
16915
17297
|
} finally {
|
|
16916
|
-
if (
|
|
17298
|
+
if (existsSync12(tmpDir)) {
|
|
16917
17299
|
rmSync4(tmpDir, { recursive: true, force: true });
|
|
16918
17300
|
}
|
|
16919
17301
|
}
|
|
@@ -16930,14 +17312,14 @@ init_cursor_authority();
|
|
|
16930
17312
|
init_session_authority();
|
|
16931
17313
|
import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
|
|
16932
17314
|
import {
|
|
16933
|
-
existsSync as
|
|
17315
|
+
existsSync as existsSync13,
|
|
16934
17316
|
lstatSync as lstatSync4,
|
|
16935
17317
|
mkdirSync as mkdirSync6,
|
|
16936
17318
|
readFileSync as readFileSync10,
|
|
16937
17319
|
readdirSync as readdirSync3,
|
|
16938
17320
|
statSync as statSync6
|
|
16939
17321
|
} from "fs";
|
|
16940
|
-
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";
|
|
16941
17323
|
|
|
16942
17324
|
class SessionApplyError extends Error {
|
|
16943
17325
|
constructor(message) {
|
|
@@ -16958,7 +17340,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
|
|
|
16958
17340
|
}
|
|
16959
17341
|
assertCursorAuthorityUnchanged(plan);
|
|
16960
17342
|
const targetHome = assertSafeTargetHome(plan.targetHome);
|
|
16961
|
-
assertClaudeAuthorityStillClear(plan, targetHome);
|
|
17343
|
+
assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
|
|
16962
17344
|
const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
|
|
16963
17345
|
const files = [...payloadFiles, plan.manifestFile];
|
|
16964
17346
|
const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
|
|
@@ -17059,17 +17441,17 @@ function assertCursorAuthorityUnchanged(plan) {
|
|
|
17059
17441
|
throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
|
|
17060
17442
|
}
|
|
17061
17443
|
}
|
|
17062
|
-
function assertClaudeAuthorityStillClear(plan, targetHome) {
|
|
17444
|
+
function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthorities) {
|
|
17063
17445
|
if (plan.tool !== "claude" || plan.targetKind === "blocked")
|
|
17064
17446
|
return;
|
|
17065
|
-
const conflicts = detectClaudeAuthorityConflicts(targetHome);
|
|
17447
|
+
const conflicts = detectClaudeAuthorityConflicts(targetHome, ownedClaudeAuthorities);
|
|
17066
17448
|
if (conflicts.length === 0)
|
|
17067
17449
|
return;
|
|
17068
17450
|
const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
|
|
17069
17451
|
throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
|
|
17070
17452
|
}
|
|
17071
17453
|
function ensureSessionTargetHome(targetHome) {
|
|
17072
|
-
if (!
|
|
17454
|
+
if (!existsSync13(targetHome))
|
|
17073
17455
|
mkdirSync6(targetHome, { recursive: true, mode: 448 });
|
|
17074
17456
|
assertSafeTargetHome(targetHome);
|
|
17075
17457
|
}
|
|
@@ -17092,7 +17474,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
17092
17474
|
const drifted = [];
|
|
17093
17475
|
for (const file of previousManifest.files) {
|
|
17094
17476
|
const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
|
|
17095
|
-
if (!
|
|
17477
|
+
if (!existsSync13(target)) {
|
|
17096
17478
|
missing.push({
|
|
17097
17479
|
path: target,
|
|
17098
17480
|
relativePath: file.relativePath,
|
|
@@ -17244,7 +17626,7 @@ function requiredRestoreHash(file) {
|
|
|
17244
17626
|
}
|
|
17245
17627
|
function readSessionRenderSnapshot(snapshotPath) {
|
|
17246
17628
|
const resolved = resolve10(snapshotPath);
|
|
17247
|
-
if (!
|
|
17629
|
+
if (!existsSync13(resolved))
|
|
17248
17630
|
throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
|
|
17249
17631
|
const stat = lstatSync4(resolved);
|
|
17250
17632
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -17425,8 +17807,8 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
17425
17807
|
if (!Number.isFinite(createdAtMs)) {
|
|
17426
17808
|
throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
|
|
17427
17809
|
}
|
|
17428
|
-
for (const entry of readdirSync3(
|
|
17429
|
-
const candidatePath = resolve10(
|
|
17810
|
+
for (const entry of readdirSync3(dirname6(snapshotPath))) {
|
|
17811
|
+
const candidatePath = resolve10(dirname6(snapshotPath), entry);
|
|
17430
17812
|
if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
|
|
17431
17813
|
continue;
|
|
17432
17814
|
const candidateStat = lstatSync4(candidatePath);
|
|
@@ -17511,7 +17893,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
|
17511
17893
|
}
|
|
17512
17894
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
17513
17895
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
17514
|
-
const previousContent =
|
|
17896
|
+
const previousContent = existsSync13(target) ? readFileSync10(target, "utf-8") : null;
|
|
17515
17897
|
const previousSha256 = previousContent === null ? null : sha2568(previousContent);
|
|
17516
17898
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
17517
17899
|
const changed = previousContent !== file.content;
|
|
@@ -17610,7 +17992,7 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
|
|
|
17610
17992
|
}
|
|
17611
17993
|
function planStaleFileResult(file, targetHome, options) {
|
|
17612
17994
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
17613
|
-
if (!
|
|
17995
|
+
if (!existsSync13(target))
|
|
17614
17996
|
return null;
|
|
17615
17997
|
const previousContent = readFileSync10(target, "utf-8");
|
|
17616
17998
|
const previousSha256 = sha2568(previousContent);
|
|
@@ -17678,7 +18060,7 @@ function resolveManifestRelativePath(relativePath, targetHome) {
|
|
|
17678
18060
|
return target;
|
|
17679
18061
|
}
|
|
17680
18062
|
function readPreviousManifest(path) {
|
|
17681
|
-
if (!
|
|
18063
|
+
if (!existsSync13(path))
|
|
17682
18064
|
return null;
|
|
17683
18065
|
try {
|
|
17684
18066
|
const parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
@@ -17720,7 +18102,7 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
|
|
|
17720
18102
|
}
|
|
17721
18103
|
function currentSessionFileHash(path, targetHome) {
|
|
17722
18104
|
assertNoSymlinkSegments2(targetHome, path);
|
|
17723
|
-
if (!
|
|
18105
|
+
if (!existsSync13(path))
|
|
17724
18106
|
return null;
|
|
17725
18107
|
const stat = lstatSync4(path);
|
|
17726
18108
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -17735,7 +18117,7 @@ function requiredPreviousHash(result) {
|
|
|
17735
18117
|
return result.previousSha256;
|
|
17736
18118
|
}
|
|
17737
18119
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
17738
|
-
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) => {
|
|
17739
18121
|
const content = readFileSync10(result.path, "utf-8");
|
|
17740
18122
|
return {
|
|
17741
18123
|
path: result.path,
|
|
@@ -17807,7 +18189,7 @@ function assertSafeTargetHome(targetHome) {
|
|
|
17807
18189
|
throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
|
|
17808
18190
|
}
|
|
17809
18191
|
assertNoSymlinkAncestors2(normalized);
|
|
17810
|
-
if (
|
|
18192
|
+
if (existsSync13(normalized) && lstatSync4(normalized).isSymbolicLink()) {
|
|
17811
18193
|
throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
|
|
17812
18194
|
}
|
|
17813
18195
|
return normalized;
|
|
@@ -17818,7 +18200,7 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
17818
18200
|
let current = root;
|
|
17819
18201
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17820
18202
|
current = join15(current, segment);
|
|
17821
|
-
if (
|
|
18203
|
+
if (existsSync13(current) && lstatSync4(current).isSymbolicLink()) {
|
|
17822
18204
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
17823
18205
|
}
|
|
17824
18206
|
}
|
|
@@ -17830,7 +18212,7 @@ function assertNoSymlinkAncestors2(path) {
|
|
|
17830
18212
|
const rel = relative5(parsed.root, normalized);
|
|
17831
18213
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
17832
18214
|
current = join15(current, segment);
|
|
17833
|
-
if (!
|
|
18215
|
+
if (!existsSync13(current))
|
|
17834
18216
|
return;
|
|
17835
18217
|
if (lstatSync4(current).isSymbolicLink()) {
|
|
17836
18218
|
throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
|
|
@@ -17878,6 +18260,248 @@ function formatGlobalSourceCoverageWarnings(result) {
|
|
|
17878
18260
|
];
|
|
17879
18261
|
}
|
|
17880
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
|
+
|
|
17881
18505
|
// src/lib/platform-profiles.ts
|
|
17882
18506
|
init_config_store();
|
|
17883
18507
|
|
|
@@ -18193,18 +18817,18 @@ init_codewith_shared_todos_storage_standard();
|
|
|
18193
18817
|
|
|
18194
18818
|
// src/lib/managed-skill-runtimes.ts
|
|
18195
18819
|
import { createHash as createHash9 } from "crypto";
|
|
18196
|
-
import { spawnSync } from "child_process";
|
|
18820
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
18197
18821
|
import {
|
|
18198
|
-
existsSync as
|
|
18199
|
-
lstatSync as
|
|
18200
|
-
mkdirSync as
|
|
18201
|
-
readFileSync as
|
|
18822
|
+
existsSync as existsSync15,
|
|
18823
|
+
lstatSync as lstatSync6,
|
|
18824
|
+
mkdirSync as mkdirSync8,
|
|
18825
|
+
readFileSync as readFileSync12,
|
|
18202
18826
|
renameSync as renameSync2,
|
|
18203
18827
|
rmSync as rmSync5,
|
|
18204
|
-
writeFileSync as
|
|
18828
|
+
writeFileSync as writeFileSync5
|
|
18205
18829
|
} from "fs";
|
|
18206
|
-
import { homedir as
|
|
18207
|
-
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";
|
|
18208
18832
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
18209
18833
|
var INBOX_SKILL_MARKERS = [
|
|
18210
18834
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -18219,21 +18843,21 @@ function sha2569(content) {
|
|
|
18219
18843
|
}
|
|
18220
18844
|
function lstatOrNull(path) {
|
|
18221
18845
|
try {
|
|
18222
|
-
return
|
|
18846
|
+
return lstatSync6(path);
|
|
18223
18847
|
} catch {
|
|
18224
18848
|
return null;
|
|
18225
18849
|
}
|
|
18226
18850
|
}
|
|
18227
18851
|
function findSymlinkedAncestor(path) {
|
|
18228
|
-
const normalized =
|
|
18852
|
+
const normalized = resolve12(path);
|
|
18229
18853
|
const parsed = parse5(normalized);
|
|
18230
18854
|
let current = parsed.root;
|
|
18231
18855
|
const rel = relative6(parsed.root, normalized);
|
|
18232
18856
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18233
|
-
current =
|
|
18234
|
-
if (!
|
|
18857
|
+
current = join17(current, segment);
|
|
18858
|
+
if (!existsSync15(current))
|
|
18235
18859
|
return null;
|
|
18236
|
-
if (
|
|
18860
|
+
if (lstatSync6(current).isSymbolicLink())
|
|
18237
18861
|
return current;
|
|
18238
18862
|
}
|
|
18239
18863
|
return null;
|
|
@@ -18248,11 +18872,11 @@ function packagedInboxSkillPath(explicitPath) {
|
|
|
18248
18872
|
if (explicitPath)
|
|
18249
18873
|
return explicitPath;
|
|
18250
18874
|
const candidates = [
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
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")
|
|
18254
18878
|
];
|
|
18255
|
-
const found = candidates.find((candidate) =>
|
|
18879
|
+
const found = candidates.find((candidate) => existsSync15(candidate));
|
|
18256
18880
|
if (!found) {
|
|
18257
18881
|
throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
|
|
18258
18882
|
}
|
|
@@ -18264,7 +18888,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
18264
18888
|
if (!stat?.isFile()) {
|
|
18265
18889
|
throw new Error("packaged inbox skill contract is not a regular file");
|
|
18266
18890
|
}
|
|
18267
|
-
const content =
|
|
18891
|
+
const content = readFileSync12(assetPath, "utf8");
|
|
18268
18892
|
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
18269
18893
|
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
18270
18894
|
}
|
|
@@ -18274,7 +18898,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
18274
18898
|
return { content, sha256: sha2569(content) };
|
|
18275
18899
|
}
|
|
18276
18900
|
function runProbe(command, args) {
|
|
18277
|
-
const result =
|
|
18901
|
+
const result = spawnSync2(command, args, {
|
|
18278
18902
|
encoding: "utf8",
|
|
18279
18903
|
timeout: 5000,
|
|
18280
18904
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -18301,8 +18925,8 @@ function compareVersions(left, right) {
|
|
|
18301
18925
|
}
|
|
18302
18926
|
return 0;
|
|
18303
18927
|
}
|
|
18304
|
-
function inspectSkillMarkers(
|
|
18305
|
-
return INBOX_SKILL_MARKERS.map((parts) =>
|
|
18928
|
+
function inspectSkillMarkers(homeDir3) {
|
|
18929
|
+
return INBOX_SKILL_MARKERS.map((parts) => join17(homeDir3, ...parts)).map((path) => {
|
|
18306
18930
|
const stat = lstatOrNull(path);
|
|
18307
18931
|
if (!stat)
|
|
18308
18932
|
return null;
|
|
@@ -18311,16 +18935,16 @@ function inspectSkillMarkers(homeDir2) {
|
|
|
18311
18935
|
}
|
|
18312
18936
|
return {
|
|
18313
18937
|
path,
|
|
18314
|
-
content:
|
|
18938
|
+
content: readFileSync12(path, "utf8"),
|
|
18315
18939
|
mode: stat.mode & 511,
|
|
18316
18940
|
regular: true
|
|
18317
18941
|
};
|
|
18318
18942
|
}).filter((snapshot) => snapshot !== null);
|
|
18319
18943
|
}
|
|
18320
18944
|
function inspectInbox(options) {
|
|
18321
|
-
const
|
|
18945
|
+
const homeDir3 = options.homeDir ?? homedir10();
|
|
18322
18946
|
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
18323
|
-
const snapshots = inspectSkillMarkers(
|
|
18947
|
+
const snapshots = inspectSkillMarkers(homeDir3);
|
|
18324
18948
|
const skillPresent = snapshots.length > 0;
|
|
18325
18949
|
let canonicalContent = null;
|
|
18326
18950
|
let canonicalSha256 = null;
|
|
@@ -18346,7 +18970,7 @@ function inspectInbox(options) {
|
|
|
18346
18970
|
let reason = "skill not installed";
|
|
18347
18971
|
if (skillPresent) {
|
|
18348
18972
|
const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
|
|
18349
|
-
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);
|
|
18350
18974
|
if (nonRegular)
|
|
18351
18975
|
reason = "managed skill target is not a regular file";
|
|
18352
18976
|
else if (symlinkAncestor)
|
|
@@ -18433,11 +19057,11 @@ function cleanup(path) {
|
|
|
18433
19057
|
rmSync5(path, { force: true });
|
|
18434
19058
|
}
|
|
18435
19059
|
function writeAtomic(path, content, mode) {
|
|
18436
|
-
assertNoSymlinkAncestors3(
|
|
19060
|
+
assertNoSymlinkAncestors3(dirname8(path));
|
|
18437
19061
|
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
18438
19062
|
try {
|
|
18439
|
-
|
|
18440
|
-
|
|
19063
|
+
mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
|
|
19064
|
+
writeFileSync5(tempPath, content, { mode, flag: "wx" });
|
|
18441
19065
|
renameSync2(tempPath, path);
|
|
18442
19066
|
} finally {
|
|
18443
19067
|
cleanup(tempPath);
|
|
@@ -18445,7 +19069,7 @@ function writeAtomic(path, content, mode) {
|
|
|
18445
19069
|
}
|
|
18446
19070
|
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
18447
19071
|
lstat: lstatOrNull,
|
|
18448
|
-
read: (path) =>
|
|
19072
|
+
read: (path) => readFileSync12(path, "utf8"),
|
|
18449
19073
|
write: writeAtomic
|
|
18450
19074
|
};
|
|
18451
19075
|
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
@@ -18512,7 +19136,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
|
|
|
18512
19136
|
dry_run: dryRun
|
|
18513
19137
|
};
|
|
18514
19138
|
}
|
|
18515
|
-
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);
|
|
18516
19140
|
if (symlinkedAncestor) {
|
|
18517
19141
|
return {
|
|
18518
19142
|
runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
|
|
@@ -18595,28 +19219,28 @@ init_project_context();
|
|
|
18595
19219
|
init_config_store();
|
|
18596
19220
|
init_apply();
|
|
18597
19221
|
init_config_agents();
|
|
18598
|
-
import { existsSync as
|
|
19222
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
18599
19223
|
|
|
18600
19224
|
// src/lib/package-version.ts
|
|
18601
|
-
import { existsSync as
|
|
18602
|
-
import { dirname as
|
|
19225
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
19226
|
+
import { dirname as dirname9, join as join18 } from "path";
|
|
18603
19227
|
import { fileURLToPath } from "url";
|
|
18604
19228
|
var cached = null;
|
|
18605
19229
|
function getPackageVersion() {
|
|
18606
19230
|
if (cached)
|
|
18607
19231
|
return cached;
|
|
18608
19232
|
try {
|
|
18609
|
-
let dir =
|
|
19233
|
+
let dir = dirname9(fileURLToPath(import.meta.url));
|
|
18610
19234
|
for (let i = 0;i < 8; i++) {
|
|
18611
|
-
const pkgPath =
|
|
18612
|
-
if (
|
|
18613
|
-
const pkg = JSON.parse(
|
|
19235
|
+
const pkgPath = join18(dir, "package.json");
|
|
19236
|
+
if (existsSync16(pkgPath)) {
|
|
19237
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
18614
19238
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
18615
19239
|
cached = pkg.version;
|
|
18616
19240
|
return cached;
|
|
18617
19241
|
}
|
|
18618
19242
|
}
|
|
18619
|
-
const parent =
|
|
19243
|
+
const parent = dirname9(dir);
|
|
18620
19244
|
if (parent === dir)
|
|
18621
19245
|
break;
|
|
18622
19246
|
dir = parent;
|
|
@@ -18673,11 +19297,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
18673
19297
|
continue;
|
|
18674
19298
|
knownTargets += 1;
|
|
18675
19299
|
const targetPath = expandPath(config.target_path);
|
|
18676
|
-
if (!
|
|
19300
|
+
if (!existsSync17(targetPath)) {
|
|
18677
19301
|
missingTargets += 1;
|
|
18678
19302
|
continue;
|
|
18679
19303
|
}
|
|
18680
|
-
const disk =
|
|
19304
|
+
const disk = readFileSync14(targetPath, "utf-8");
|
|
18681
19305
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
18682
19306
|
if (redactedDisk !== config.content) {
|
|
18683
19307
|
driftedTargets += 1;
|
|
@@ -18770,6 +19394,204 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
18770
19394
|
|
|
18771
19395
|
// src/cli/index.tsx
|
|
18772
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
|
|
18773
19595
|
import { createRequire } from "module";
|
|
18774
19596
|
var pkg = createRequire(import.meta.url)("../../package.json");
|
|
18775
19597
|
var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -18875,11 +19697,11 @@ function parseSessionSource(value, order) {
|
|
|
18875
19697
|
if (!path)
|
|
18876
19698
|
throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
|
|
18877
19699
|
const absPath = resolveSessionPath(path);
|
|
18878
|
-
if (!
|
|
19700
|
+
if (!existsSync20(absPath))
|
|
18879
19701
|
throw new Error(`Instruction source file not found: ${absPath}`);
|
|
18880
19702
|
const content = readSessionInstructionSourceFile(absPath);
|
|
18881
19703
|
const source = sourceFromFilePath(absPath, content, order);
|
|
18882
|
-
const resolvedId = id || source.id ||
|
|
19704
|
+
const resolvedId = id || source.id || basename8(absPath);
|
|
18883
19705
|
return {
|
|
18884
19706
|
...source,
|
|
18885
19707
|
id: resolvedId,
|
|
@@ -18919,7 +19741,7 @@ function sessionSourceReplacements(values) {
|
|
|
18919
19741
|
return replacements;
|
|
18920
19742
|
}
|
|
18921
19743
|
function readSessionInstructionSourceFile(path) {
|
|
18922
|
-
const stat =
|
|
19744
|
+
const stat = lstatSync8(path);
|
|
18923
19745
|
if (stat.isSymbolicLink()) {
|
|
18924
19746
|
throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
|
|
18925
19747
|
}
|
|
@@ -18929,7 +19751,7 @@ function readSessionInstructionSourceFile(path) {
|
|
|
18929
19751
|
if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
|
|
18930
19752
|
throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
|
|
18931
19753
|
}
|
|
18932
|
-
return
|
|
19754
|
+
return readFileSync17(path, "utf-8");
|
|
18933
19755
|
}
|
|
18934
19756
|
function parseLayeredReference(value) {
|
|
18935
19757
|
const trimmed = value.trim();
|
|
@@ -18956,9 +19778,9 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
18956
19778
|
}
|
|
18957
19779
|
for (const value of opts.identityExport ?? []) {
|
|
18958
19780
|
const path = resolveSessionPath(value);
|
|
18959
|
-
if (!
|
|
19781
|
+
if (!existsSync20(path))
|
|
18960
19782
|
throw new Error(`Identity instruction export not found: ${path}`);
|
|
18961
|
-
const parsed = JSON.parse(
|
|
19783
|
+
const parsed = JSON.parse(readFileSync17(path, "utf-8"));
|
|
18962
19784
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
18963
19785
|
}
|
|
18964
19786
|
return sources.map((source) => {
|
|
@@ -18972,11 +19794,19 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
18972
19794
|
};
|
|
18973
19795
|
});
|
|
18974
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
|
+
}
|
|
18975
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;
|
|
18976
19804
|
if (!opts.compileProfile) {
|
|
18977
19805
|
if (opts.providerVariant)
|
|
18978
19806
|
throw new Error("--provider-variant requires --compile-profile.");
|
|
18979
19807
|
const sources = await collectSessionSources(opts, tool, store);
|
|
19808
|
+
if (station)
|
|
19809
|
+
sources.push(station);
|
|
18980
19810
|
return planSessionRender({
|
|
18981
19811
|
tool,
|
|
18982
19812
|
profile: opts.profile,
|
|
@@ -18985,7 +19815,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
|
|
|
18985
19815
|
sessionId: opts.sessionId,
|
|
18986
19816
|
codewithNativeImports: opts.codewithNativeImports,
|
|
18987
19817
|
allowEmptySources: opts.allowEmptySources,
|
|
18988
|
-
sources
|
|
19818
|
+
sources,
|
|
19819
|
+
ownedClaudeAuthorities
|
|
18989
19820
|
});
|
|
18990
19821
|
}
|
|
18991
19822
|
if (!opts.providerVersion?.trim())
|
|
@@ -19016,6 +19847,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
|
|
|
19016
19847
|
...opts.assetScope ? { asset_scope: opts.assetScope } : {},
|
|
19017
19848
|
...opts.assetSurface ? { asset_surface: opts.assetSurface } : {},
|
|
19018
19849
|
allow_asset_installers: opts.allowAssetInstallers,
|
|
19850
|
+
extra_sources: station ? [station] : undefined,
|
|
19851
|
+
ownedClaudeAuthorities,
|
|
19019
19852
|
graph_context: {
|
|
19020
19853
|
...opts.model ? { model: opts.model } : {},
|
|
19021
19854
|
...opts.path ? { path: opts.path } : {},
|
|
@@ -19079,19 +19912,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
|
|
|
19079
19912
|
if (value === "-")
|
|
19080
19913
|
return { json: readBoundedProjectContextStdin() };
|
|
19081
19914
|
const path = resolveSessionPath(value);
|
|
19082
|
-
if (!
|
|
19915
|
+
if (!existsSync20(path)) {
|
|
19083
19916
|
if (allowMissing)
|
|
19084
19917
|
return {};
|
|
19085
19918
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
|
|
19086
19919
|
}
|
|
19087
|
-
const stat =
|
|
19920
|
+
const stat = lstatSync8(path);
|
|
19088
19921
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
19089
19922
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
|
|
19090
19923
|
}
|
|
19091
19924
|
if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
|
|
19092
19925
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
|
|
19093
19926
|
}
|
|
19094
|
-
return { json:
|
|
19927
|
+
return { json: readFileSync17(path, "utf8"), sourcePath: path };
|
|
19095
19928
|
}
|
|
19096
19929
|
function readBoundedProjectContextStdin() {
|
|
19097
19930
|
const chunks = [];
|
|
@@ -19250,16 +20083,16 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
|
|
|
19250
20083
|
console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
|
|
19251
20084
|
});
|
|
19252
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) => {
|
|
19253
|
-
const abs =
|
|
19254
|
-
if (!
|
|
20086
|
+
const abs = resolve14(filePath);
|
|
20087
|
+
if (!existsSync20(abs)) {
|
|
19255
20088
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
19256
20089
|
process.exit(1);
|
|
19257
20090
|
}
|
|
19258
|
-
const rawContent =
|
|
20091
|
+
const rawContent = readFileSync17(abs, "utf-8");
|
|
19259
20092
|
const storedFmt = detectFormat(abs);
|
|
19260
20093
|
const fmt = redactFormatForTarget(abs, storedFmt);
|
|
19261
20094
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
19262
|
-
const targetPath = abs.startsWith(
|
|
20095
|
+
const targetPath = abs.startsWith(homedir12()) ? abs.replace(homedir12(), "~") : abs;
|
|
19263
20096
|
const name = opts.name || filePath.split("/").pop();
|
|
19264
20097
|
const store = resolveConfigStore();
|
|
19265
20098
|
const allConfigs = await store.listConfigs();
|
|
@@ -19427,14 +20260,14 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
19427
20260
|
if (opts.project) {
|
|
19428
20261
|
const dir = typeof opts.project === "string" ? opts.project : process.cwd();
|
|
19429
20262
|
if (opts.all) {
|
|
19430
|
-
const { readdirSync:
|
|
20263
|
+
const { readdirSync: readdirSync6 } = await import("fs");
|
|
19431
20264
|
const absDir = expandPath(dir);
|
|
19432
|
-
const entries =
|
|
20265
|
+
const entries = readdirSync6(absDir, { withFileTypes: true });
|
|
19433
20266
|
let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0, projects = 0;
|
|
19434
20267
|
for (const entry of entries) {
|
|
19435
20268
|
if (!entry.isDirectory())
|
|
19436
20269
|
continue;
|
|
19437
|
-
const projDir =
|
|
20270
|
+
const projDir = join21(absDir, entry.name);
|
|
19438
20271
|
const hasAgentConfig = [
|
|
19439
20272
|
"CLAUDE.md",
|
|
19440
20273
|
".mcp.json",
|
|
@@ -19447,7 +20280,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
19447
20280
|
".aicopilot",
|
|
19448
20281
|
".cursor",
|
|
19449
20282
|
".agents"
|
|
19450
|
-
].some((marker) =>
|
|
20283
|
+
].some((marker) => existsSync20(join21(projDir, marker)));
|
|
19451
20284
|
if (!hasAgentConfig)
|
|
19452
20285
|
continue;
|
|
19453
20286
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
|
|
@@ -19498,7 +20331,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
19498
20331
|
});
|
|
19499
20332
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
19500
20333
|
const store = resolveConfigStore();
|
|
19501
|
-
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");
|
|
19502
20335
|
const stats = await store.getConfigStats();
|
|
19503
20336
|
console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
|
|
19504
20337
|
console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
|
|
@@ -19882,7 +20715,7 @@ projectContextCmd.command("apply").description("Atomically write project context
|
|
|
19882
20715
|
}
|
|
19883
20716
|
});
|
|
19884
20717
|
var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
|
|
19885
|
-
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) => {
|
|
19886
20719
|
try {
|
|
19887
20720
|
const tool = opts.tool;
|
|
19888
20721
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -19933,7 +20766,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
|
|
|
19933
20766
|
process.exit(1);
|
|
19934
20767
|
}
|
|
19935
20768
|
});
|
|
19936
|
-
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) => {
|
|
19937
20770
|
try {
|
|
19938
20771
|
const tool = opts.tool;
|
|
19939
20772
|
if (!SESSION_RENDER_TOOLS.includes(tool)) {
|
|
@@ -19943,7 +20776,8 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
|
|
|
19943
20776
|
const store = resolveConfigStore();
|
|
19944
20777
|
const plan = await buildSessionRenderPlan(opts, tool, store, "apply");
|
|
19945
20778
|
const globalCoverage = opts.checkGlobalCoverage ? await checkGlobalSourceCoverage(plan, store) : null;
|
|
19946
|
-
const
|
|
20779
|
+
const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
|
|
20780
|
+
const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, ownedClaudeAuthorities });
|
|
19947
20781
|
if (opts.json) {
|
|
19948
20782
|
printJson({
|
|
19949
20783
|
...result,
|
|
@@ -20020,6 +20854,94 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
|
|
|
20020
20854
|
process.exit(1);
|
|
20021
20855
|
}
|
|
20022
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
|
+
});
|
|
20023
20945
|
var snapshotCmd = program.command("snapshot").description("Manage config version history");
|
|
20024
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) => {
|
|
20025
20947
|
try {
|
|
@@ -20269,14 +21191,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
|
|
|
20269
21191
|
} else if (target === "codex") {
|
|
20270
21192
|
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
20271
21193
|
const { join: j } = await import("path");
|
|
20272
|
-
const configPath = j(
|
|
21194
|
+
const configPath = j(homedir12(), ".codex", "config.toml");
|
|
20273
21195
|
const block = `
|
|
20274
21196
|
[mcp_servers.configs]
|
|
20275
21197
|
command = "${mcpBinary}"
|
|
20276
21198
|
args = []
|
|
20277
21199
|
`;
|
|
20278
21200
|
if (ex(configPath)) {
|
|
20279
|
-
const content =
|
|
21201
|
+
const content = readFileSync17(configPath, "utf-8");
|
|
20280
21202
|
if (content.includes("[mcp_servers.configs]")) {
|
|
20281
21203
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
20282
21204
|
continue;
|
|
@@ -20287,7 +21209,7 @@ args = []
|
|
|
20287
21209
|
} else if (target === "antigravity") {
|
|
20288
21210
|
const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
20289
21211
|
const { dirname: dn, join: j } = await import("path");
|
|
20290
|
-
const configPath = j(
|
|
21212
|
+
const configPath = j(homedir12(), ".gemini", "config", "mcp_config.json");
|
|
20291
21213
|
let settings = {};
|
|
20292
21214
|
if (ex(configPath)) {
|
|
20293
21215
|
try {
|
|
@@ -20371,7 +21293,7 @@ DB stats:`));
|
|
|
20371
21293
|
if (count > 0)
|
|
20372
21294
|
console.log(` ${key.padEnd(18)} ${count}`);
|
|
20373
21295
|
}
|
|
20374
|
-
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");
|
|
20375
21297
|
console.log(chalk.dim(`
|
|
20376
21298
|
${isApiTransport() ? "API" : "DB"}: ${location}`));
|
|
20377
21299
|
});
|
|
@@ -20432,10 +21354,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
|
|
|
20432
21354
|
});
|
|
20433
21355
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
20434
21356
|
const { mkdirSync: mk } = await import("fs");
|
|
20435
|
-
const backupDir =
|
|
21357
|
+
const backupDir = join21(getRawStoreRoot(), "backups");
|
|
20436
21358
|
mk(backupDir, { recursive: true });
|
|
20437
21359
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
20438
|
-
const outPath =
|
|
21360
|
+
const outPath = join21(backupDir, `configs-${ts}.tar.gz`);
|
|
20439
21361
|
const result = await exportConfigs(outPath, { store: resolveConfigStore() });
|
|
20440
21362
|
const { statSync: st } = await import("fs");
|
|
20441
21363
|
const size = st(outPath).size;
|
|
@@ -20463,9 +21385,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
20463
21385
|
console.log(chalk.cyan("Known files on disk:"));
|
|
20464
21386
|
for (const k of KNOWN_CONFIGS) {
|
|
20465
21387
|
if (k.rulesDir) {
|
|
20466
|
-
|
|
21388
|
+
existsSync20(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
|
|
20467
21389
|
} else {
|
|
20468
|
-
|
|
21390
|
+
existsSync20(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
|
|
20469
21391
|
}
|
|
20470
21392
|
}
|
|
20471
21393
|
const allConfigs = await store.listConfigs();
|
|
@@ -20543,6 +21465,7 @@ _configs() {
|
|
|
20543
21465
|
'scan:Scan for secrets'
|
|
20544
21466
|
'profile:Manage profiles'
|
|
20545
21467
|
'session:Plan and apply session instructions'
|
|
21468
|
+
'station-profile:Generate and inject the compact station profile block'
|
|
20546
21469
|
'snapshot:Version history'
|
|
20547
21470
|
'template:Template operations'
|
|
20548
21471
|
'mcp:Install MCP server'
|
|
@@ -20558,7 +21481,7 @@ compdef _configs configs`);
|
|
|
20558
21481
|
console.log(`# bash completion for configs
|
|
20559
21482
|
_configs_completions() {
|
|
20560
21483
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
20561
|
-
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"
|
|
20562
21485
|
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
20563
21486
|
}
|
|
20564
21487
|
complete -F _configs_completions configs`);
|
|
@@ -20618,16 +21541,16 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20618
21541
|
for (const k of KNOWN_CONFIGS) {
|
|
20619
21542
|
if (k.rulesDir) {
|
|
20620
21543
|
const absDir = expandPath2(k.rulesDir);
|
|
20621
|
-
if (!
|
|
21544
|
+
if (!existsSync20(absDir))
|
|
20622
21545
|
continue;
|
|
20623
|
-
const { readdirSync:
|
|
20624
|
-
for (const f of
|
|
20625
|
-
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);
|
|
20626
21549
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20627
21550
|
}
|
|
20628
21551
|
} else {
|
|
20629
21552
|
const abs = expandPath2(k.path);
|
|
20630
|
-
if (
|
|
21553
|
+
if (existsSync20(abs))
|
|
20631
21554
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20632
21555
|
}
|
|
20633
21556
|
}
|
|
@@ -20635,7 +21558,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20635
21558
|
const tick = async () => {
|
|
20636
21559
|
let changed = 0;
|
|
20637
21560
|
for (const [abs, oldMtime] of mtimes) {
|
|
20638
|
-
if (!
|
|
21561
|
+
if (!existsSync20(abs))
|
|
20639
21562
|
continue;
|
|
20640
21563
|
const newMtime = st(abs).mtimeMs;
|
|
20641
21564
|
if (newMtime !== oldMtime) {
|
|
@@ -20647,10 +21570,10 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20647
21570
|
for (const k of KNOWN_CONFIGS) {
|
|
20648
21571
|
if (k.rulesDir) {
|
|
20649
21572
|
const absDir = expandPath2(k.rulesDir);
|
|
20650
|
-
if (!
|
|
21573
|
+
if (!existsSync20(absDir))
|
|
20651
21574
|
continue;
|
|
20652
21575
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
20653
|
-
const abs =
|
|
21576
|
+
const abs = join21(absDir, f);
|
|
20654
21577
|
if (!mtimes.has(abs)) {
|
|
20655
21578
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20656
21579
|
changed++;
|
|
@@ -20658,7 +21581,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
20658
21581
|
}
|
|
20659
21582
|
} else {
|
|
20660
21583
|
const abs = expandPath2(k.path);
|
|
20661
|
-
if (
|
|
21584
|
+
if (existsSync20(abs) && !mtimes.has(abs)) {
|
|
20662
21585
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
20663
21586
|
changed++;
|
|
20664
21587
|
}
|
|
@@ -20686,11 +21609,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
20686
21609
|
if (!c.target_path)
|
|
20687
21610
|
continue;
|
|
20688
21611
|
const abs = expandPath(c.target_path);
|
|
20689
|
-
if (!
|
|
21612
|
+
if (!existsSync20(abs)) {
|
|
20690
21613
|
missing++;
|
|
20691
21614
|
continue;
|
|
20692
21615
|
}
|
|
20693
|
-
const disk =
|
|
21616
|
+
const disk = readFileSync17(abs, "utf-8");
|
|
20694
21617
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
|
|
20695
21618
|
if (redactedDisk !== c.content)
|
|
20696
21619
|
drifted++;
|
|
@@ -20757,7 +21680,7 @@ program.command("clean").description("Remove configs from DB whose target files
|
|
|
20757
21680
|
if (!c.target_path)
|
|
20758
21681
|
continue;
|
|
20759
21682
|
const abs = expandPath(c.target_path);
|
|
20760
|
-
if (!
|
|
21683
|
+
if (!existsSync20(abs)) {
|
|
20761
21684
|
if (printed < maxPrinted) {
|
|
20762
21685
|
if (opts.dryRun) {
|
|
20763
21686
|
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
|
|
@@ -20898,6 +21821,48 @@ program.command("feedback <message>").description("Send feedback about this serv
|
|
|
20898
21821
|
});
|
|
20899
21822
|
console.log(chalk.green("\u2713") + " Feedback saved. Thank you!");
|
|
20900
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
|
+
});
|
|
20901
21866
|
program.version(pkg.version).name("instructions");
|
|
20902
21867
|
registerEventsCommands(program, { source: "configs" });
|
|
20903
21868
|
program.parseAsync(process.argv).catch((e) => {
|