@guilz-dev/belay 0.1.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.
Files changed (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +268 -0
  3. package/agent-belay-logo.png +0 -0
  4. package/dist/adapters/claude/adapter.d.ts +7 -0
  5. package/dist/adapters/claude/adapter.js +114 -0
  6. package/dist/adapters/claude/hooks.d.ts +13 -0
  7. package/dist/adapters/claude/hooks.js +49 -0
  8. package/dist/adapters/claude/runtime-entry.d.ts +4 -0
  9. package/dist/adapters/claude/runtime-entry.js +260 -0
  10. package/dist/adapters/codex/adapter.d.ts +7 -0
  11. package/dist/adapters/codex/adapter.js +73 -0
  12. package/dist/adapters/codex/hooks.d.ts +21 -0
  13. package/dist/adapters/codex/hooks.js +78 -0
  14. package/dist/adapters/codex/runtime-entry.d.ts +4 -0
  15. package/dist/adapters/codex/runtime-entry.js +237 -0
  16. package/dist/adapters/cursor/adapter.d.ts +7 -0
  17. package/dist/adapters/cursor/adapter.js +29 -0
  18. package/dist/adapters/cursor/hooks.d.ts +2 -0
  19. package/dist/adapters/cursor/hooks.js +26 -0
  20. package/dist/adapters/cursor/runtime-entry.d.ts +4 -0
  21. package/dist/adapters/cursor/runtime-entry.js +143 -0
  22. package/dist/adapters/layouts/claude.d.ts +2 -0
  23. package/dist/adapters/layouts/claude.js +40 -0
  24. package/dist/adapters/layouts/codex.d.ts +2 -0
  25. package/dist/adapters/layouts/codex.js +43 -0
  26. package/dist/adapters/layouts/cursor.d.ts +2 -0
  27. package/dist/adapters/layouts/cursor.js +40 -0
  28. package/dist/adapters/layouts/index.d.ts +7 -0
  29. package/dist/adapters/layouts/index.js +23 -0
  30. package/dist/adapters/layouts/protected-paths.d.ts +3 -0
  31. package/dist/adapters/layouts/protected-paths.js +15 -0
  32. package/dist/adapters/layouts/scope.d.ts +19 -0
  33. package/dist/adapters/layouts/scope.js +86 -0
  34. package/dist/adapters/layouts/types.d.ts +14 -0
  35. package/dist/adapters/layouts/types.js +1 -0
  36. package/dist/adapters/registry.d.ts +4 -0
  37. package/dist/adapters/registry.js +14 -0
  38. package/dist/adapters/shared/gate-runtime.d.ts +51 -0
  39. package/dist/adapters/shared/gate-runtime.js +518 -0
  40. package/dist/adapters/shared/repo-root.d.ts +2 -0
  41. package/dist/adapters/shared/repo-root.js +17 -0
  42. package/dist/adapters/types.d.ts +19 -0
  43. package/dist/adapters/types.js +1 -0
  44. package/dist/branding.d.ts +3 -0
  45. package/dist/branding.js +3 -0
  46. package/dist/bundle/claude-runtime.mjs +5323 -0
  47. package/dist/bundle/codex-runtime.mjs +5310 -0
  48. package/dist/bundle/cursor-runtime.mjs +5208 -0
  49. package/dist/cleanup-orphans.d.ts +7 -0
  50. package/dist/cleanup-orphans.js +59 -0
  51. package/dist/cli.d.ts +2 -0
  52. package/dist/cli.js +631 -0
  53. package/dist/commands/approve.d.ts +14 -0
  54. package/dist/commands/approve.js +65 -0
  55. package/dist/commands/audit.d.ts +59 -0
  56. package/dist/commands/audit.js +132 -0
  57. package/dist/commands/classify-for-report.d.ts +9 -0
  58. package/dist/commands/classify-for-report.js +85 -0
  59. package/dist/commands/doctor.d.ts +3 -0
  60. package/dist/commands/doctor.js +366 -0
  61. package/dist/commands/dogfood.d.ts +5 -0
  62. package/dist/commands/dogfood.js +71 -0
  63. package/dist/commands/explain.d.ts +3 -0
  64. package/dist/commands/explain.js +133 -0
  65. package/dist/commands/health-snapshot.d.ts +2 -0
  66. package/dist/commands/health-snapshot.js +166 -0
  67. package/dist/commands/init-wizard.d.ts +16 -0
  68. package/dist/commands/init-wizard.js +50 -0
  69. package/dist/commands/metrics.d.ts +7 -0
  70. package/dist/commands/metrics.js +89 -0
  71. package/dist/commands/recover.d.ts +3 -0
  72. package/dist/commands/recover.js +105 -0
  73. package/dist/commands/report.d.ts +3 -0
  74. package/dist/commands/report.js +65 -0
  75. package/dist/commands/revoke.d.ts +5 -0
  76. package/dist/commands/revoke.js +22 -0
  77. package/dist/commands/simulate.d.ts +14 -0
  78. package/dist/commands/simulate.js +55 -0
  79. package/dist/commands/status.d.ts +5 -0
  80. package/dist/commands/status.js +107 -0
  81. package/dist/config-io.d.ts +23 -0
  82. package/dist/config-io.js +180 -0
  83. package/dist/conformance/guarantee-table.d.ts +14 -0
  84. package/dist/conformance/guarantee-table.js +95 -0
  85. package/dist/conformance/types.d.ts +6 -0
  86. package/dist/conformance/types.js +1 -0
  87. package/dist/core/approval-service.d.ts +26 -0
  88. package/dist/core/approval-service.js +41 -0
  89. package/dist/core/approval-token.d.ts +11 -0
  90. package/dist/core/approval-token.js +61 -0
  91. package/dist/core/approval.d.ts +19 -0
  92. package/dist/core/approval.js +58 -0
  93. package/dist/core/audit-analysis.d.ts +10 -0
  94. package/dist/core/audit-analysis.js +147 -0
  95. package/dist/core/audit-metrics.d.ts +51 -0
  96. package/dist/core/audit-metrics.js +155 -0
  97. package/dist/core/audit-query.d.ts +11 -0
  98. package/dist/core/audit-query.js +142 -0
  99. package/dist/core/audit-summary.d.ts +33 -0
  100. package/dist/core/audit-summary.js +111 -0
  101. package/dist/core/audit-types.d.ts +65 -0
  102. package/dist/core/audit-types.js +2 -0
  103. package/dist/core/capability/allowlist.d.ts +10 -0
  104. package/dist/core/capability/allowlist.js +53 -0
  105. package/dist/core/capability/broker.d.ts +17 -0
  106. package/dist/core/capability/broker.js +29 -0
  107. package/dist/core/capability/index.d.ts +5 -0
  108. package/dist/core/capability/index.js +4 -0
  109. package/dist/core/capability/paths.d.ts +1 -0
  110. package/dist/core/capability/paths.js +20 -0
  111. package/dist/core/capability/reasons.d.ts +2 -0
  112. package/dist/core/capability/reasons.js +4 -0
  113. package/dist/core/capability/types.d.ts +10 -0
  114. package/dist/core/capability/types.js +1 -0
  115. package/dist/core/capability-approval.d.ts +28 -0
  116. package/dist/core/capability-approval.js +43 -0
  117. package/dist/core/classify-subagent.d.ts +2 -0
  118. package/dist/core/classify-subagent.js +69 -0
  119. package/dist/core/classify-tool.d.ts +3 -0
  120. package/dist/core/classify-tool.js +311 -0
  121. package/dist/core/config-layers.d.ts +23 -0
  122. package/dist/core/config-layers.js +59 -0
  123. package/dist/core/config.d.ts +219 -0
  124. package/dist/core/config.js +720 -0
  125. package/dist/core/control-plane-isolation.d.ts +10 -0
  126. package/dist/core/control-plane-isolation.js +83 -0
  127. package/dist/core/custom-command-match.d.ts +2 -0
  128. package/dist/core/custom-command-match.js +8 -0
  129. package/dist/core/egress/allowlist.d.ts +7 -0
  130. package/dist/core/egress/allowlist.js +33 -0
  131. package/dist/core/egress/env.d.ts +3 -0
  132. package/dist/core/egress/env.js +17 -0
  133. package/dist/core/egress/fingerprint.d.ts +3 -0
  134. package/dist/core/egress/fingerprint.js +35 -0
  135. package/dist/core/egress/policy.d.ts +8 -0
  136. package/dist/core/egress/policy.js +47 -0
  137. package/dist/core/egress/proxy-server.d.ts +21 -0
  138. package/dist/core/egress/proxy-server.js +263 -0
  139. package/dist/core/egress/types.d.ts +25 -0
  140. package/dist/core/egress/types.js +1 -0
  141. package/dist/core/egress-approval.d.ts +48 -0
  142. package/dist/core/egress-approval.js +129 -0
  143. package/dist/core/fingerprint.d.ts +6 -0
  144. package/dist/core/fingerprint.js +24 -0
  145. package/dist/core/gate-contract.d.ts +48 -0
  146. package/dist/core/gate-contract.js +50 -0
  147. package/dist/core/gate-engine.d.ts +20 -0
  148. package/dist/core/gate-engine.js +172 -0
  149. package/dist/core/glob.d.ts +1 -0
  150. package/dist/core/glob.js +39 -0
  151. package/dist/core/index.d.ts +19 -0
  152. package/dist/core/index.js +15 -0
  153. package/dist/core/integrity.d.ts +15 -0
  154. package/dist/core/integrity.js +68 -0
  155. package/dist/core/judge-api-key.d.ts +4 -0
  156. package/dist/core/judge-api-key.js +11 -0
  157. package/dist/core/judge-config.d.ts +29 -0
  158. package/dist/core/judge-config.js +85 -0
  159. package/dist/core/judge-doctor.d.ts +7 -0
  160. package/dist/core/judge-doctor.js +124 -0
  161. package/dist/core/judgment.d.ts +6 -0
  162. package/dist/core/judgment.js +38 -0
  163. package/dist/core/notify.d.ts +13 -0
  164. package/dist/core/notify.js +44 -0
  165. package/dist/core/path-utils.d.ts +12 -0
  166. package/dist/core/path-utils.js +107 -0
  167. package/dist/core/reclassify.d.ts +15 -0
  168. package/dist/core/reclassify.js +82 -0
  169. package/dist/core/recover-advice.d.ts +30 -0
  170. package/dist/core/recover-advice.js +177 -0
  171. package/dist/core/recover-git-probe.d.ts +8 -0
  172. package/dist/core/recover-git-probe.js +50 -0
  173. package/dist/core/recover-select.d.ts +10 -0
  174. package/dist/core/recover-select.js +60 -0
  175. package/dist/core/scrub.d.ts +3 -0
  176. package/dist/core/scrub.js +87 -0
  177. package/dist/core/shell-substitution.d.ts +6 -0
  178. package/dist/core/shell-substitution.js +130 -0
  179. package/dist/core/shell-tokenizer.d.ts +5 -0
  180. package/dist/core/shell-tokenizer.js +129 -0
  181. package/dist/core/shell-unparseable.d.ts +4 -0
  182. package/dist/core/shell-unparseable.js +96 -0
  183. package/dist/core/transactional/diff-evaluator.d.ts +2 -0
  184. package/dist/core/transactional/diff-evaluator.js +84 -0
  185. package/dist/core/transactional/eligibility.d.ts +4 -0
  186. package/dist/core/transactional/eligibility.js +44 -0
  187. package/dist/core/transactional/git-worktree.d.ts +13 -0
  188. package/dist/core/transactional/git-worktree.js +189 -0
  189. package/dist/core/transactional/index.d.ts +5 -0
  190. package/dist/core/transactional/index.js +4 -0
  191. package/dist/core/transactional/reasons.d.ts +4 -0
  192. package/dist/core/transactional/reasons.js +8 -0
  193. package/dist/core/transactional/runner.d.ts +2 -0
  194. package/dist/core/transactional/runner.js +113 -0
  195. package/dist/core/transactional/types.d.ts +46 -0
  196. package/dist/core/transactional/types.js +1 -0
  197. package/dist/core/types.d.ts +90 -0
  198. package/dist/core/types.js +1 -0
  199. package/dist/core/v2/adapter.d.ts +14 -0
  200. package/dist/core/v2/adapter.js +118 -0
  201. package/dist/core/v2/containment.d.ts +19 -0
  202. package/dist/core/v2/containment.js +91 -0
  203. package/dist/core/v2/egress-classify.d.ts +7 -0
  204. package/dist/core/v2/egress-classify.js +216 -0
  205. package/dist/core/v2/fingerprint.d.ts +1 -0
  206. package/dist/core/v2/fingerprint.js +4 -0
  207. package/dist/core/v2/index.d.ts +12 -0
  208. package/dist/core/v2/index.js +10 -0
  209. package/dist/core/v2/judge-audit.d.ts +2 -0
  210. package/dist/core/v2/judge-audit.js +15 -0
  211. package/dist/core/v2/judge-factory.d.ts +25 -0
  212. package/dist/core/v2/judge-factory.js +75 -0
  213. package/dist/core/v2/judge-outbound.d.ts +12 -0
  214. package/dist/core/v2/judge-outbound.js +73 -0
  215. package/dist/core/v2/judge.d.ts +47 -0
  216. package/dist/core/v2/judge.js +264 -0
  217. package/dist/core/v2/launcher-resolve.d.ts +12 -0
  218. package/dist/core/v2/launcher-resolve.js +190 -0
  219. package/dist/core/v2/overrides.d.ts +7 -0
  220. package/dist/core/v2/overrides.js +37 -0
  221. package/dist/core/v2/parser.d.ts +21 -0
  222. package/dist/core/v2/parser.js +213 -0
  223. package/dist/core/v2/types.d.ts +67 -0
  224. package/dist/core/v2/types.js +1 -0
  225. package/dist/core/v2/verdict.d.ts +2 -0
  226. package/dist/core/v2/verdict.js +699 -0
  227. package/dist/corpus/evaluate.d.ts +24 -0
  228. package/dist/corpus/evaluate.js +69 -0
  229. package/dist/defaults.d.ts +18 -0
  230. package/dist/defaults.js +155 -0
  231. package/dist/egress-daemon.d.ts +1 -0
  232. package/dist/egress-daemon.js +52 -0
  233. package/dist/index.d.ts +17 -0
  234. package/dist/index.js +15 -0
  235. package/dist/installer/bootstrap.d.ts +5 -0
  236. package/dist/installer/bootstrap.js +61 -0
  237. package/dist/installer/runtime-artifacts.d.ts +3 -0
  238. package/dist/installer/runtime-artifacts.js +23 -0
  239. package/dist/installer/scope-config.d.ts +8 -0
  240. package/dist/installer/scope-config.js +25 -0
  241. package/dist/installer.d.ts +22 -0
  242. package/dist/installer.js +169 -0
  243. package/dist/node-resolution.d.ts +8 -0
  244. package/dist/node-resolution.js +237 -0
  245. package/dist/operational-insights.d.ts +19 -0
  246. package/dist/operational-insights.js +24 -0
  247. package/dist/presets.d.ts +4 -0
  248. package/dist/presets.js +95 -0
  249. package/dist/services/egress-service.d.ts +57 -0
  250. package/dist/services/egress-service.js +334 -0
  251. package/dist/services/sandbox-service.d.ts +38 -0
  252. package/dist/services/sandbox-service.js +95 -0
  253. package/dist/templates.d.ts +7 -0
  254. package/dist/templates.js +56 -0
  255. package/dist/types.d.ts +230 -0
  256. package/dist/types.js +1 -0
  257. package/dist/version.d.ts +1 -0
  258. package/dist/version.js +1 -0
  259. package/package.json +65 -0
  260. package/skills/belay/SKILL.md +52 -0
  261. package/skills/belay/belay-approve.md +7 -0
  262. package/skills/belay/belay-explain.md +11 -0
  263. package/skills/belay/belay-recover.md +13 -0
  264. package/skills/belay/belay-report.md +7 -0
  265. package/skills/belay/belay-status.md +9 -0
  266. package/skills/belay/belay-why.md +11 -0
@@ -0,0 +1,169 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { mergeCursorHooksFile } from './adapters/cursor/hooks.js';
5
+ import { cursorLayout } from './adapters/layouts/cursor.js';
6
+ import { resolveScopedPaths } from './adapters/layouts/scope.js';
7
+ import { getAdapter } from './adapters/registry.js';
8
+ import { dogfoodProject } from './commands/dogfood.js';
9
+ import { detectAdapterName, loadConfigFile, mergeAndWriteConfig, writeConfigFile, } from './config-io.js';
10
+ import { isFreshConfigInput, mergeConfig, normalizeConfig } from './core/config.js';
11
+ import { runtimeIntegrityFiles, writeIntegrityManifest } from './core/integrity.js';
12
+ import { resolveInitJudgeConfig } from './core/judge-config.js';
13
+ import { bootstrapStateFiles, writeSkillArtifacts } from './installer/bootstrap.js';
14
+ import { writeRuntimeArtifacts } from './installer/runtime-artifacts.js';
15
+ import { applyInstallScope, resolveOperationScope } from './installer/scope-config.js';
16
+ import { applyConfigPreset } from './presets.js';
17
+ async function pathExists(filePath) {
18
+ try {
19
+ const { stat } = await import('node:fs/promises');
20
+ await stat(filePath);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ async function ensureDir(dirPath) {
28
+ await mkdir(dirPath, { recursive: true });
29
+ }
30
+ export async function loadHooksFile(hooksPath) {
31
+ if (!existsSync(hooksPath)) {
32
+ return { version: 1, hooks: {} };
33
+ }
34
+ const raw = await readFile(hooksPath, 'utf8');
35
+ try {
36
+ const parsed = JSON.parse(raw);
37
+ if (!parsed || typeof parsed !== 'object' || typeof parsed.version !== 'number') {
38
+ throw new Error('hooks.json must contain a numeric version field.');
39
+ }
40
+ if (!parsed.hooks || typeof parsed.hooks !== 'object') {
41
+ throw new Error('hooks.json must contain an object hooks field.');
42
+ }
43
+ return parsed;
44
+ }
45
+ catch (error) {
46
+ const detail = error instanceof Error ? error.message : 'Unknown JSON parse failure.';
47
+ throw new Error(`Invalid hooks.json at ${hooksPath}: ${detail}`);
48
+ }
49
+ }
50
+ export function mergeHooksFile(current, platform = process.platform, hooksDir, repoRoot) {
51
+ const resolvedRepo = path.resolve(repoRoot ?? process.cwd());
52
+ const resolvedHooksDir = hooksDir ?? cursorLayout.hooksDir(resolvedRepo);
53
+ return mergeCursorHooksFile(current, platform, resolvedHooksDir, resolvedRepo);
54
+ }
55
+ export async function initCursorProject(options = {}) {
56
+ const repoRoot = path.resolve(options.targetDir ?? process.cwd());
57
+ const scope = await resolveOperationScope(repoRoot, 'cursor', options);
58
+ const paths = resolveScopedPaths(cursorLayout, scope, repoRoot);
59
+ const withSkill = options.withSkill === true;
60
+ const hooksFile = await loadHooksFile(paths.hooksSettingsPath);
61
+ const mergedHooks = mergeCursorHooksFile(hooksFile, process.platform, paths.hooksDir, repoRoot);
62
+ await ensureDir(paths.hooksDir);
63
+ const config = await mergeAndWriteConfig(repoRoot, 'cursor');
64
+ await applyInstallScope(repoRoot, 'cursor', scope, config);
65
+ await writeRuntimeArtifacts('cursor', paths);
66
+ await bootstrapStateFiles(repoRoot, config, paths);
67
+ if (withSkill) {
68
+ await writeSkillArtifacts('cursor', paths);
69
+ }
70
+ await mkdir(path.dirname(paths.hooksSettingsPath), { recursive: true });
71
+ await writeFile(paths.hooksSettingsPath, `${JSON.stringify(mergedHooks, null, 2)}\n`, 'utf8');
72
+ await writeIntegrityManifest(repoRoot, cursorLayout, runtimeIntegrityFiles(cursorLayout, paths));
73
+ return { repoRoot, withSkill };
74
+ }
75
+ export async function upgradeCursorProject(options = {}) {
76
+ const repoRoot = path.resolve(options.targetDir ?? process.cwd());
77
+ const scope = await resolveOperationScope(repoRoot, 'cursor', options);
78
+ const paths = resolveScopedPaths(cursorLayout, scope, repoRoot);
79
+ const config = await mergeAndWriteConfig(repoRoot, 'cursor');
80
+ await applyInstallScope(repoRoot, 'cursor', scope, config);
81
+ await writeRuntimeArtifacts('cursor', paths);
82
+ const hooksFile = await loadHooksFile(paths.hooksSettingsPath);
83
+ const merged = mergeCursorHooksFile(hooksFile, process.platform, paths.hooksDir, repoRoot);
84
+ await mkdir(path.dirname(paths.hooksSettingsPath), { recursive: true });
85
+ await writeFile(paths.hooksSettingsPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
86
+ if (options.withSkill) {
87
+ await writeSkillArtifacts('cursor', paths);
88
+ }
89
+ await writeIntegrityManifest(repoRoot, cursorLayout, runtimeIntegrityFiles(cursorLayout, paths));
90
+ return { repoRoot };
91
+ }
92
+ function resolveAdapterName(options, repoRoot) {
93
+ if (options.adapter === 'claude') {
94
+ return 'claude';
95
+ }
96
+ if (options.adapter === 'codex') {
97
+ return 'codex';
98
+ }
99
+ if (options.adapter === 'cursor') {
100
+ return 'cursor';
101
+ }
102
+ if (repoRoot) {
103
+ return detectAdapterName(repoRoot);
104
+ }
105
+ return 'cursor';
106
+ }
107
+ async function applyInitJudgeConfig(repoRoot, adapterName, options) {
108
+ if (options.skipJudgeWrite) {
109
+ return;
110
+ }
111
+ const layout = getAdapter(adapterName).layout;
112
+ const configPath = layout.configPath(repoRoot);
113
+ let existingConfig = {};
114
+ if (await pathExists(configPath)) {
115
+ existingConfig = JSON.parse(await readFile(configPath, 'utf8'));
116
+ }
117
+ const isFresh = isFreshConfigInput(existingConfig);
118
+ const mergedConfig = await loadConfigFile(repoRoot, adapterName);
119
+ const hasExplicitJudgeFlags = options.judgeProfile || options.judgeProvider || options.judgeModel || options.judgeEndpoint;
120
+ const judge = resolveInitJudgeConfig({
121
+ isFresh,
122
+ hasExplicitJudgeFlags: Boolean(hasExplicitJudgeFlags),
123
+ judgeProfile: options.judgeProfile,
124
+ judgeProvider: options.judgeProvider,
125
+ judgeModel: options.judgeModel,
126
+ judgeEndpoint: options.judgeEndpoint,
127
+ acceptCloudJudge: options.acceptCloudJudge,
128
+ existingJudge: mergedConfig.judge,
129
+ });
130
+ const configWithJudge = normalizeConfig({ ...mergedConfig, version: 4, judge });
131
+ await writeConfigFile(repoRoot, configWithJudge, adapterName);
132
+ }
133
+ async function refreshIntegrityManifest(repoRoot, adapterName) {
134
+ const layout = getAdapter(adapterName).layout;
135
+ const config = await loadConfigFile(repoRoot, adapterName);
136
+ const scope = config.installScope === 'global' ? 'global' : 'project';
137
+ const paths = resolveScopedPaths(layout, scope, repoRoot);
138
+ await writeIntegrityManifest(repoRoot, layout, runtimeIntegrityFiles(layout, paths));
139
+ }
140
+ export async function initProject(options = {}) {
141
+ const repoRoot = path.resolve(options.targetDir ?? process.cwd());
142
+ const adapterName = resolveAdapterName(options, repoRoot);
143
+ const adapter = getAdapter(adapterName);
144
+ const result = await adapter.install(repoRoot, options);
145
+ await applyInitJudgeConfig(repoRoot, adapterName, options);
146
+ if (options.preset) {
147
+ const existing = await loadConfigFile(repoRoot, adapterName);
148
+ const presetConfig = mergeConfig(applyConfigPreset(options.preset));
149
+ const merged = mergeConfig(presetConfig, existing);
150
+ await writeConfigFile(repoRoot, merged, adapterName);
151
+ }
152
+ if (options.dogfood === true) {
153
+ await dogfoodProject({ targetDir: repoRoot, adapter: adapterName });
154
+ }
155
+ await refreshIntegrityManifest(repoRoot, adapterName);
156
+ return {
157
+ repoRoot,
158
+ withSkill: result.withSkill,
159
+ dogfood: options.dogfood === true,
160
+ adapter: adapterName,
161
+ };
162
+ }
163
+ export async function upgradeProject(options = {}) {
164
+ const repoRoot = path.resolve(options.targetDir ?? process.cwd());
165
+ const adapterName = resolveAdapterName(options, repoRoot);
166
+ await getAdapter(adapterName).upgrade(repoRoot, options);
167
+ await refreshIntegrityManifest(repoRoot, adapterName);
168
+ return { repoRoot, adapter: adapterName };
169
+ }
@@ -0,0 +1,8 @@
1
+ export interface NodeResolutionResult {
2
+ ok: boolean;
3
+ detail: string;
4
+ path?: string;
5
+ }
6
+ export declare function resolveNodeBinary(): NodeResolutionResult;
7
+ export declare function buildRunnerScript(defaultNodePath: string): string;
8
+ export declare function buildWindowsRunnerScript(defaultNodePath: string): string;
@@ -0,0 +1,237 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { accessSync, constants, existsSync } from 'node:fs';
3
+ import { delimiter } from 'node:path';
4
+ function isExecutable(path) {
5
+ if (!path) {
6
+ return false;
7
+ }
8
+ try {
9
+ accessSync(path, constants.X_OK);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function firstExecutableInPath() {
17
+ const pathValue = process.env.PATH ?? '';
18
+ for (const segment of pathValue.split(delimiter)) {
19
+ if (!segment) {
20
+ continue;
21
+ }
22
+ const candidate = `${segment}/node`;
23
+ if (isExecutable(candidate)) {
24
+ return candidate;
25
+ }
26
+ }
27
+ return undefined;
28
+ }
29
+ function execTool(command, args) {
30
+ try {
31
+ const output = execFileSync(command, args, {
32
+ encoding: 'utf8',
33
+ stdio: ['ignore', 'pipe', 'ignore'],
34
+ }).trim();
35
+ return output || undefined;
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
41
+ function resolveViaKnownManagers() {
42
+ const mise = execTool('mise', ['which', 'node']);
43
+ if (isExecutable(mise)) {
44
+ return mise;
45
+ }
46
+ const fnm = execTool('fnm', ['which']);
47
+ if (isExecutable(fnm)) {
48
+ return fnm;
49
+ }
50
+ const nvmScript = process.env.NVM_DIR && existsSync(`${process.env.NVM_DIR}/nvm.sh`)
51
+ ? `${process.env.NVM_DIR}/nvm.sh`
52
+ : existsSync(`${process.env.HOME ?? ''}/.nvm/nvm.sh`)
53
+ ? `${process.env.HOME ?? ''}/.nvm/nvm.sh`
54
+ : undefined;
55
+ if (!nvmScript) {
56
+ return undefined;
57
+ }
58
+ const command = `. "${nvmScript}" >/dev/null 2>&1 && nvm which current 2>/dev/null`;
59
+ const resolved = execTool('/bin/sh', ['-lc', command]);
60
+ if (isExecutable(resolved)) {
61
+ return resolved;
62
+ }
63
+ return undefined;
64
+ }
65
+ export function resolveNodeBinary() {
66
+ if (isExecutable(process.execPath)) {
67
+ return {
68
+ ok: true,
69
+ detail: 'Resolved via process.execPath.',
70
+ path: process.execPath,
71
+ };
72
+ }
73
+ const pathNode = firstExecutableInPath();
74
+ if (pathNode) {
75
+ return {
76
+ ok: true,
77
+ detail: 'Resolved via PATH.',
78
+ path: pathNode,
79
+ };
80
+ }
81
+ const managerNode = resolveViaKnownManagers();
82
+ if (managerNode) {
83
+ return {
84
+ ok: true,
85
+ detail: 'Resolved via mise/fnm/nvm.',
86
+ path: managerNode,
87
+ };
88
+ }
89
+ return {
90
+ ok: false,
91
+ detail: 'Unable to resolve Node from process.execPath, PATH, or known managers (mise/fnm/nvm).',
92
+ };
93
+ }
94
+ export function buildRunnerScript(defaultNodePath) {
95
+ const escapedDefault = defaultNodePath.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
96
+ return `#!/bin/sh
97
+ set -u
98
+
99
+ DEFAULT_NODE="${escapedDefault}"
100
+
101
+ resolve_node() {
102
+ if [ -n "$DEFAULT_NODE" ] && [ -x "$DEFAULT_NODE" ]; then
103
+ printf '%s\\n' "$DEFAULT_NODE"
104
+ return 0
105
+ fi
106
+
107
+ if command -v node >/dev/null 2>&1; then
108
+ command -v node
109
+ return 0
110
+ fi
111
+
112
+ if command -v mise >/dev/null 2>&1; then
113
+ NODE_FROM_MISE="$(mise which node 2>/dev/null || true)"
114
+ if [ -n "$NODE_FROM_MISE" ] && [ -x "$NODE_FROM_MISE" ]; then
115
+ printf '%s\\n' "$NODE_FROM_MISE"
116
+ return 0
117
+ fi
118
+ fi
119
+
120
+ if command -v fnm >/dev/null 2>&1; then
121
+ NODE_FROM_FNM="$(fnm which 2>/dev/null || true)"
122
+ if [ -n "$NODE_FROM_FNM" ] && [ -x "$NODE_FROM_FNM" ]; then
123
+ printf '%s\\n' "$NODE_FROM_FNM"
124
+ return 0
125
+ fi
126
+ fi
127
+
128
+ NVM_SCRIPT=""
129
+ if [ -n "\${NVM_DIR:-}" ] && [ -s "$NVM_DIR/nvm.sh" ]; then
130
+ NVM_SCRIPT="$NVM_DIR/nvm.sh"
131
+ elif [ -s "$HOME/.nvm/nvm.sh" ]; then
132
+ NVM_SCRIPT="$HOME/.nvm/nvm.sh"
133
+ fi
134
+
135
+ if [ -n "$NVM_SCRIPT" ]; then
136
+ NODE_FROM_NVM="$(/bin/sh -lc ". \\"$NVM_SCRIPT\\" >/dev/null 2>&1 && nvm which current 2>/dev/null" || true)"
137
+ if [ -n "$NODE_FROM_NVM" ] && [ -x "$NODE_FROM_NVM" ]; then
138
+ printf '%s\\n' "$NODE_FROM_NVM"
139
+ return 0
140
+ fi
141
+ fi
142
+
143
+ return 1
144
+ }
145
+
146
+ fail_gate() {
147
+ printf '{"permission":"deny","user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}\\n'
148
+ exit 0
149
+ }
150
+
151
+ fail_before_submit() {
152
+ printf '{"continue":false,"user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}\\n'
153
+ exit 0
154
+ }
155
+
156
+ fail_audit() {
157
+ echo "belay: unable to resolve Node for audit hook" >&2
158
+ printf '{}\\n'
159
+ exit 0
160
+ }
161
+
162
+ HOOK_NAME="\${1:-}"
163
+ if [ -z "$HOOK_NAME" ]; then
164
+ fail_audit
165
+ fi
166
+ shift
167
+
168
+ NODE_BIN="$(resolve_node || true)"
169
+ if [ -z "$NODE_BIN" ]; then
170
+ case "$HOOK_NAME" in
171
+ belay-before-submit)
172
+ fail_before_submit
173
+ ;;
174
+ belay-shell-gate|belay-tool-gate)
175
+ fail_gate
176
+ ;;
177
+ *)
178
+ fail_audit
179
+ ;;
180
+ esac
181
+ fi
182
+
183
+ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
184
+ exec "$NODE_BIN" "$SCRIPT_DIR/$HOOK_NAME.mjs" "$@"
185
+ `;
186
+ }
187
+ export function buildWindowsRunnerScript(defaultNodePath) {
188
+ const escapedDefault = defaultNodePath.replaceAll('\\', '\\\\');
189
+ return `@echo off
190
+ setlocal EnableExtensions
191
+
192
+ set "DEFAULT_NODE=${escapedDefault}"
193
+ set "HOOK_NAME=%~1"
194
+ if "%HOOK_NAME%"=="" goto fail_audit
195
+ shift
196
+
197
+ call :resolve_node
198
+ if not defined NODE_BIN goto fail_by_hook
199
+
200
+ "%NODE_BIN%" "%~dp0%HOOK_NAME%.mjs" %*
201
+ exit /b 0
202
+
203
+ :resolve_node
204
+ if defined DEFAULT_NODE if exist "%DEFAULT_NODE%" (
205
+ set "NODE_BIN=%DEFAULT_NODE%"
206
+ exit /b 0
207
+ )
208
+ for %%I in (node.exe) do if not "%%~$PATH:I"=="" (
209
+ set "NODE_BIN=%%~$PATH:I"
210
+ exit /b 0
211
+ )
212
+ for %%I in ("%USERPROFILE%\\.nvm\\v*\\node.exe") do if exist "%%~fI" (
213
+ set "NODE_BIN=%%~fI"
214
+ exit /b 0
215
+ )
216
+ exit /b 1
217
+
218
+ :fail_by_hook
219
+ if /I "%HOOK_NAME%"=="belay-before-submit" goto fail_before_submit
220
+ if /I "%HOOK_NAME%"=="belay-shell-gate" goto fail_gate
221
+ if /I "%HOOK_NAME%"=="belay-tool-gate" goto fail_gate
222
+ goto fail_audit
223
+
224
+ :fail_gate
225
+ echo {"permission":"deny","user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}
226
+ exit /b 0
227
+
228
+ :fail_before_submit
229
+ echo {"continue":false,"user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}
230
+ exit /b 0
231
+
232
+ :fail_audit
233
+ 1>&2 echo belay: unable to resolve Node for audit hook
234
+ echo {}
235
+ exit /b 0
236
+ `;
237
+ }
@@ -0,0 +1,19 @@
1
+ import type { BelayConfigV3 } from './core/config.js';
2
+ export interface DogfoodStatus {
3
+ active: boolean;
4
+ mode: string;
5
+ unknownLocalEffect: string;
6
+ readyForEnforce: boolean;
7
+ gateEvents: number;
8
+ wouldBlockCount: number;
9
+ wouldBlockRate: number;
10
+ notes: string[];
11
+ }
12
+ export interface OperationalInsights {
13
+ repoRoot: string;
14
+ dogfood: DogfoodStatus;
15
+ }
16
+ export declare function isDogfoodConfig(config: BelayConfigV3): boolean;
17
+ export declare function loadOperationalInsights(options?: {
18
+ targetDir?: string;
19
+ }): Promise<OperationalInsights>;
@@ -0,0 +1,24 @@
1
+ import path from 'node:path';
2
+ import { metricsProject } from './commands/metrics.js';
3
+ import { loadConfigFile } from './config-io.js';
4
+ export function isDogfoodConfig(config) {
5
+ return config.mode === 'audit' && config.policy.unknownLocalEffect === 'deny';
6
+ }
7
+ export async function loadOperationalInsights(options = {}) {
8
+ const repoRoot = path.resolve(options.targetDir ?? process.cwd());
9
+ const config = await loadConfigFile(repoRoot);
10
+ const metrics = await metricsProject({ targetDir: repoRoot });
11
+ return {
12
+ repoRoot,
13
+ dogfood: {
14
+ active: isDogfoodConfig(config),
15
+ mode: config.mode,
16
+ unknownLocalEffect: config.policy.unknownLocalEffect,
17
+ readyForEnforce: metrics.dogfood.readyForEnforce,
18
+ gateEvents: metrics.gateEvents,
19
+ wouldBlockCount: metrics.wouldBlockCount,
20
+ wouldBlockRate: metrics.wouldBlockRate,
21
+ notes: metrics.dogfood.notes,
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,4 @@
1
+ import type { BelayConfigV3 } from './core/config.js';
2
+ export type ConfigPresetName = 'strict' | 'standard' | 'audit-first' | 'l1-full-recommended';
3
+ export declare const CONFIG_PRESETS: Record<ConfigPresetName, Partial<BelayConfigV3>>;
4
+ export declare function applyConfigPreset(preset: ConfigPresetName, extra?: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,95 @@
1
+ import { DEFAULT_CONFIG_V3 } from './core/config.js';
2
+ export const CONFIG_PRESETS = {
3
+ strict: {
4
+ mode: 'enforce',
5
+ policy: {
6
+ ...DEFAULT_CONFIG_V3.policy,
7
+ unknownLocalEffect: 'deny',
8
+ unparseableShell: 'deny',
9
+ confidenceThresholds: { allow: 0.9, flag: 0.8 },
10
+ modelAssist: { enabled: false },
11
+ },
12
+ sandbox: { ...DEFAULT_CONFIG_V3.sandbox },
13
+ },
14
+ standard: {
15
+ mode: 'enforce',
16
+ },
17
+ 'audit-first': {
18
+ mode: 'audit',
19
+ policy: {
20
+ ...DEFAULT_CONFIG_V3.policy,
21
+ unknownLocalEffect: 'deny',
22
+ unparseableShell: 'deny',
23
+ confidenceThresholds: { allow: 0.88, flag: 0.72 },
24
+ modelAssist: { enabled: false },
25
+ },
26
+ sandbox: { ...DEFAULT_CONFIG_V3.sandbox },
27
+ },
28
+ 'l1-full-recommended': {
29
+ mode: 'enforce',
30
+ policy: {
31
+ ...DEFAULT_CONFIG_V3.policy,
32
+ confidenceThresholds: { ...DEFAULT_CONFIG_V3.policy.confidenceThresholds },
33
+ modelAssist: { ...DEFAULT_CONFIG_V3.policy.modelAssist },
34
+ },
35
+ sandbox: {
36
+ enabled: true,
37
+ runtime: 'container',
38
+ denyNetworkByDefault: true,
39
+ },
40
+ egress: {
41
+ ...DEFAULT_CONFIG_V3.egress,
42
+ enabled: true,
43
+ demoteL3External: true,
44
+ },
45
+ approvalSigning: {
46
+ required: true,
47
+ },
48
+ controlPlane: {
49
+ ...DEFAULT_CONFIG_V3.controlPlane,
50
+ isolation: {
51
+ mode: 'separate-user',
52
+ verifyAgentWritable: true,
53
+ },
54
+ },
55
+ },
56
+ };
57
+ export function applyConfigPreset(preset, extra = {}) {
58
+ const base = CONFIG_PRESETS[preset] ?? CONFIG_PRESETS.standard;
59
+ return {
60
+ version: 3,
61
+ ...base,
62
+ ...extra,
63
+ policy: {
64
+ ...DEFAULT_CONFIG_V3.policy,
65
+ ...(base.policy ?? {}),
66
+ ...extra.policy,
67
+ },
68
+ sandbox: {
69
+ ...DEFAULT_CONFIG_V3.sandbox,
70
+ ...(base.sandbox ?? {}),
71
+ ...extra.sandbox,
72
+ },
73
+ egress: {
74
+ ...DEFAULT_CONFIG_V3.egress,
75
+ ...(base.egress ?? {}),
76
+ ...extra.egress,
77
+ },
78
+ approvalSigning: {
79
+ ...DEFAULT_CONFIG_V3.approvalSigning,
80
+ ...(base.approvalSigning ?? {}),
81
+ ...extra.approvalSigning,
82
+ },
83
+ controlPlane: {
84
+ ...DEFAULT_CONFIG_V3.controlPlane,
85
+ ...(base.controlPlane ?? {}),
86
+ ...extra.controlPlane,
87
+ isolation: {
88
+ ...DEFAULT_CONFIG_V3.controlPlane.isolation,
89
+ ...(base.controlPlane?.isolation ?? {}),
90
+ ...(extra.controlPlane
91
+ ?.isolation ?? {}),
92
+ },
93
+ },
94
+ };
95
+ }
@@ -0,0 +1,57 @@
1
+ import { loadConfigFile } from '../config-io.js';
2
+ import { type BelayConfigV3 } from '../core/config.js';
3
+ import type { ApprovalStateFile } from '../core/types.js';
4
+ export interface EgressServiceOptions {
5
+ targetDir?: string;
6
+ }
7
+ export interface EgressStatusReport {
8
+ repoRoot: string;
9
+ enabled: boolean;
10
+ running: boolean;
11
+ host: string;
12
+ port: number;
13
+ pid: number | null;
14
+ startedAt: string | null;
15
+ boundRepoRoot: string | null;
16
+ repoRootMismatch: boolean;
17
+ foreignProxy: boolean;
18
+ portOccupied: boolean;
19
+ proxyEnv: Record<string, string>;
20
+ }
21
+ export declare function isEgressProxyActiveForRepo(config: BelayConfigV3, repoRoot: string, repoLocalStateDir: string): boolean;
22
+ export declare function egressStatus(options?: EgressServiceOptions): Promise<EgressStatusReport>;
23
+ export declare function startEgressProxy(options?: EgressServiceOptions): Promise<{
24
+ ok: boolean;
25
+ message: string;
26
+ }>;
27
+ export declare function stopEgressProxy(options?: EgressServiceOptions): Promise<{
28
+ ok: boolean;
29
+ message: string;
30
+ }>;
31
+ export declare function egressEnv(options?: EgressServiceOptions): Promise<{
32
+ ok: boolean;
33
+ message: string;
34
+ env: Record<string, string>;
35
+ }>;
36
+ export declare function formatEgressStatusReport(report: EgressStatusReport): string;
37
+ export declare function createEgressApprovalStore(repoRoot: string, config: Awaited<ReturnType<typeof loadConfigFile>>): {
38
+ allowlistPath: string;
39
+ loadPending(): Promise<{
40
+ filePath: string;
41
+ state: ApprovalStateFile;
42
+ }>;
43
+ loadApproved(): Promise<{
44
+ filePath: string;
45
+ state: ApprovalStateFile;
46
+ }>;
47
+ writePending(_filePath: string, state: ApprovalStateFile): Promise<void>;
48
+ writeApproved(_filePath: string, state: ApprovalStateFile): Promise<void>;
49
+ };
50
+ export declare function writeEgressDaemonState(params: {
51
+ stateDir: string;
52
+ pid: number;
53
+ host: string;
54
+ port: number;
55
+ repoRoot: string;
56
+ }): Promise<void>;
57
+ export declare function clearEgressDaemonState(stateDir: string): Promise<void>;