@h-rig/core 0.0.6-alpha.17 → 0.0.6-alpha.170

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 (107) hide show
  1. package/dist/src/agent-role-registry.d.ts +4 -0
  2. package/dist/src/agent-role-registry.js +27 -0
  3. package/dist/src/authority-paths.d.ts +15 -0
  4. package/dist/src/authority-paths.js +80 -0
  5. package/dist/src/baked-secrets.d.ts +6 -0
  6. package/dist/src/baked-secrets.js +121 -0
  7. package/dist/src/build-time-config.d.ts +12 -0
  8. package/dist/src/build-time-config.js +25 -0
  9. package/dist/src/build-time-config.macro.d.ts +1 -0
  10. package/dist/src/capability-loaders.d.ts +51 -0
  11. package/dist/src/capability-loaders.js +758 -0
  12. package/dist/src/capability.d.ts +79 -0
  13. package/dist/src/capability.js +63 -0
  14. package/dist/src/checkout-root.d.ts +1 -0
  15. package/dist/src/checkout-root.js +30 -0
  16. package/dist/src/config-env.d.ts +4 -0
  17. package/dist/src/config-env.js +23 -0
  18. package/dist/src/config.d.ts +3 -0
  19. package/dist/src/config.js +44 -0
  20. package/dist/src/declarative-config.d.ts +14 -0
  21. package/dist/src/declarative-config.js +82 -0
  22. package/dist/src/default-kernel.d.ts +1 -0
  23. package/dist/src/default-kernel.js +12 -0
  24. package/dist/src/define-config.d.ts +20 -0
  25. package/dist/src/define-config.js +28 -15
  26. package/dist/src/define-plugin.d.ts +13 -0
  27. package/dist/src/define-plugin.js +4 -43
  28. package/dist/src/embedded-plugins.d.ts +59 -0
  29. package/dist/src/embedded-plugins.js +22 -0
  30. package/dist/src/exec.d.ts +13 -0
  31. package/dist/src/exec.js +101 -0
  32. package/dist/src/harness-paths.d.ts +18 -0
  33. package/dist/src/harness-paths.js +141 -0
  34. package/dist/src/hook-materializer.d.ts +72 -0
  35. package/dist/src/hook-materializer.js +281 -0
  36. package/dist/src/hook-protocol.d.ts +2 -0
  37. package/dist/src/hook-protocol.js +462 -0
  38. package/dist/src/hook-runner.d.ts +48 -0
  39. package/dist/src/hook-runner.js +756 -0
  40. package/dist/src/hook-runtime.d.ts +52 -0
  41. package/dist/src/hook-runtime.js +462 -0
  42. package/dist/src/index.d.ts +8 -0
  43. package/dist/src/index.js +210 -2499
  44. package/dist/src/json-files.d.ts +9 -0
  45. package/dist/src/json-files.js +125 -0
  46. package/dist/src/kernel-boot.d.ts +2 -0
  47. package/dist/src/kernel-boot.js +10 -0
  48. package/dist/src/kernel-entrypoint.d.ts +22 -0
  49. package/dist/src/kernel-entrypoint.js +548 -0
  50. package/dist/src/kernel-plugin-abi.d.ts +1 -0
  51. package/dist/src/kernel-plugin-abi.js +1 -0
  52. package/dist/src/kernel-resolver.d.ts +2 -0
  53. package/dist/src/kernel-resolver.js +6 -0
  54. package/dist/src/layout.d.ts +10 -0
  55. package/dist/src/layout.js +144 -0
  56. package/dist/src/load-config.d.ts +2 -0
  57. package/dist/src/load-config.js +423 -30
  58. package/dist/src/placement.d.ts +50 -0
  59. package/dist/src/placement.js +996 -0
  60. package/dist/src/plugin-host-context.d.ts +66 -0
  61. package/dist/src/plugin-host-context.js +1292 -0
  62. package/dist/src/plugin-host-registries.d.ts +31 -0
  63. package/dist/src/plugin-host-registries.js +79 -0
  64. package/dist/src/plugin-host.d.ts +77 -0
  65. package/dist/src/plugin-host.js +127 -63
  66. package/dist/src/plugin-runtime.d.ts +173 -0
  67. package/dist/src/profile-ops.d.ts +9 -0
  68. package/dist/src/profile-ops.js +252 -0
  69. package/dist/src/project-plugins.d.ts +63 -0
  70. package/dist/src/project-plugins.js +793 -0
  71. package/dist/src/remote-config.d.ts +183 -0
  72. package/dist/src/remote-config.js +574 -0
  73. package/dist/src/root-resolver.d.ts +5 -0
  74. package/dist/src/root-resolver.js +69 -0
  75. package/dist/src/run-provisioning.d.ts +58 -0
  76. package/dist/src/run-provisioning.js +128 -0
  77. package/dist/src/runtime-context.d.ts +20 -0
  78. package/dist/src/runtime-context.js +257 -0
  79. package/dist/src/runtime-events.d.ts +44 -0
  80. package/dist/src/runtime-events.js +212 -0
  81. package/dist/src/runtime-overlay.d.ts +11 -0
  82. package/dist/src/runtime-overlay.js +71 -0
  83. package/dist/src/runtime-paths.d.ts +21 -0
  84. package/dist/src/runtime-paths.js +181 -0
  85. package/dist/src/runtime-provisioning-env.d.ts +5 -0
  86. package/dist/src/runtime-provisioning-env.js +217 -0
  87. package/dist/src/runtime-runner-context.d.ts +12 -0
  88. package/dist/src/runtime-runner-context.js +1 -0
  89. package/dist/src/safe-identifiers.d.ts +44 -0
  90. package/dist/src/safe-identifiers.js +96 -0
  91. package/dist/src/scope-rules.d.ts +4 -0
  92. package/dist/src/scope-rules.js +21 -0
  93. package/dist/src/server-paths.d.ts +26 -0
  94. package/dist/src/server-paths.js +308 -0
  95. package/dist/src/setup-version.d.ts +3 -0
  96. package/dist/src/setup-version.js +14 -0
  97. package/dist/src/task-record-reader.d.ts +3 -0
  98. package/dist/src/task-record-reader.js +9 -0
  99. package/dist/src/validator-registry.d.ts +27 -0
  100. package/dist/src/validator-registry.js +64 -0
  101. package/package.json +166 -10
  102. package/dist/src/engineReadModelReducer.js +0 -1780
  103. package/dist/src/rig-init-builder.js +0 -57
  104. package/dist/src/rigSelectors.js +0 -293
  105. package/dist/src/taskGraph.js +0 -64
  106. package/dist/src/taskGraphCodes.js +0 -26
  107. package/dist/src/taskGraphLayout.js +0 -374
@@ -0,0 +1,574 @@
1
+ // @bun
2
+ // packages/core/src/remote-config.ts
3
+ import { randomUUID } from "crypto";
4
+ import { chmodSync, existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir, tmpdir } from "os";
6
+ import { dirname as dirname2, join, resolve as resolve2 } from "path";
7
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
8
+
9
+ // packages/core/src/authority-paths.ts
10
+ import { existsSync } from "fs";
11
+ import { dirname, resolve } from "path";
12
+ function normalizeOptionalString(value) {
13
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
14
+ }
15
+ function resolveAuthorityPaths(projectRoot) {
16
+ const normalizedRoot = resolve(projectRoot);
17
+ const stateRoot = resolveAuthorityStateRoot(normalizedRoot);
18
+ const stateDir = resolveAuthorityStateDir(normalizedRoot);
19
+ return {
20
+ projectRoot: normalizedRoot,
21
+ harnessRoot: resolve(normalizedRoot, "rig"),
22
+ runsDir: resolve(stateRoot, "runs"),
23
+ remoteDir: resolve(stateRoot, "remote"),
24
+ stateDir,
25
+ remoteEndpointsPath: resolve(stateRoot, "remote", "endpoints.toml"),
26
+ remoteSecretsPath: resolve(stateDir, "remote-secrets.toml")
27
+ };
28
+ }
29
+ function resolveAuthorityStateRoot(projectRoot) {
30
+ const normalizedRoot = resolve(projectRoot);
31
+ const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
32
+ if (taskWorkspace) {
33
+ return resolve(taskWorkspace, ".rig");
34
+ }
35
+ const stateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
36
+ if (stateDir) {
37
+ return dirname(resolve(stateDir));
38
+ }
39
+ const logsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
40
+ if (logsDir) {
41
+ return dirname(resolve(logsDir));
42
+ }
43
+ const sessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
44
+ if (sessionFile) {
45
+ return dirname(dirname(resolve(sessionFile)));
46
+ }
47
+ const projectStateRoot = resolve(normalizedRoot, ".rig");
48
+ if (existsSync(projectStateRoot)) {
49
+ return projectStateRoot;
50
+ }
51
+ return resolve(normalizedRoot, ".rig");
52
+ }
53
+ function resolveAuthorityStateDir(projectRoot) {
54
+ const explicit = normalizeOptionalString(process.env.RIG_STATE_DIR);
55
+ if (explicit) {
56
+ return resolve(explicit);
57
+ }
58
+ return resolve(resolveAuthorityStateRoot(projectRoot), "state");
59
+ }
60
+
61
+ // packages/core/src/remote-config.ts
62
+ var DEFAULT_REMOTE_PORT = 7890;
63
+ var BACKBONE_DEFAULTS_KEY = Symbol.for("rig.remote-config.backbone-defaults");
64
+ function backboneDefaultsState() {
65
+ const global = globalThis;
66
+ return global[BACKBONE_DEFAULTS_KEY] ??= {};
67
+ }
68
+ function registerBackboneDefaults(defaults) {
69
+ const relayUrl = normalizeString(defaults.relayUrl);
70
+ const registryBaseUrl = normalizeString(defaults.registryBaseUrl);
71
+ const sshTarget = normalizeString(defaults.sshTarget);
72
+ const state = backboneDefaultsState();
73
+ if (relayUrl)
74
+ state.relayUrl = relayUrl;
75
+ if (registryBaseUrl)
76
+ state.registryBaseUrl = registryBaseUrl;
77
+ if (sshTarget)
78
+ state.sshTarget = sshTarget;
79
+ }
80
+ function resolveRelayUrl(env = process.env) {
81
+ return normalizeString(env.RIG_COLLAB_RELAY) || normalizeString(env.RIG_SPIKE_RELAY) || backboneDefaultsState().relayUrl || "";
82
+ }
83
+ function resolveSshTarget(env = process.env) {
84
+ return normalizeString(env.RIG_SSH_TARGET) || backboneDefaultsState().sshTarget || "";
85
+ }
86
+ var REMOTES_CONFIG_PATH = join(homedir(), ".config", "rig", "remotes.toml");
87
+
88
+ class RemoteCliError extends Error {
89
+ code;
90
+ exitCode;
91
+ details;
92
+ constructor(code, message, exitCode = 1, details) {
93
+ super(message);
94
+ this.name = "RemoteCliError";
95
+ this.code = code;
96
+ this.exitCode = exitCode;
97
+ this.details = details;
98
+ }
99
+ }
100
+ function loadRemotesConfig(configPath = REMOTES_CONFIG_PATH) {
101
+ if (!existsSync2(configPath)) {
102
+ return { version: 1, remotes: {} };
103
+ }
104
+ try {
105
+ const content = readFileSync(configPath, "utf-8");
106
+ if (!content.trim()) {
107
+ return { version: 1, remotes: {} };
108
+ }
109
+ const parsed = parseToml(content);
110
+ return {
111
+ version: parsed.version ?? 1,
112
+ remotes: parsed.remotes ?? {}
113
+ };
114
+ } catch (error) {
115
+ throw new RemoteCliError("RIG_REMOTE_CONFIG_PARSE_ERROR", `Failed to parse remotes config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`, 2, { configPath });
116
+ }
117
+ }
118
+ function saveRemotesConfig(config, configPath = REMOTES_CONFIG_PATH) {
119
+ mkdirSync(dirname2(configPath), { recursive: true });
120
+ writeFileSync(configPath, `${stringifyToml(config).trimEnd()}
121
+ `, "utf-8");
122
+ try {
123
+ chmodSync(configPath, 384);
124
+ } catch {}
125
+ }
126
+ function readTomlFile(path, fallback) {
127
+ if (!existsSync2(path))
128
+ return fallback;
129
+ const raw = readFileSync(path, "utf8");
130
+ if (!raw.trim())
131
+ return fallback;
132
+ return parseToml(raw);
133
+ }
134
+ function writeTomlFile(path, value) {
135
+ mkdirSync(dirname2(path), { recursive: true });
136
+ writeFileSync(path, `${stringifyToml(value).trimEnd()}
137
+ `, "utf8");
138
+ }
139
+ function normalizeStringArray(value) {
140
+ if (!Array.isArray(value))
141
+ return [];
142
+ return value.map((entry) => normalizeString(entry)).filter((entry) => entry !== "");
143
+ }
144
+ function normalizePort(value) {
145
+ const port = typeof value === "number" ? value : Number(value);
146
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : DEFAULT_REMOTE_PORT;
147
+ }
148
+ function normalizeRegistryBaseUrl(raw) {
149
+ const trimmed = raw.replace(/\/+$/, "");
150
+ if (!trimmed)
151
+ return trimmed;
152
+ return /\/registry$/.test(trimmed) ? trimmed : `${trimmed}/registry`;
153
+ }
154
+ function loadRemoteEndpointsToml(projectRoot) {
155
+ const paths = resolveAuthorityPaths(projectRoot);
156
+ const parsed = readTomlFile(paths.remoteEndpointsPath, { version: 1, endpoints: {} });
157
+ return {
158
+ version: parsed.version ?? 1,
159
+ endpoints: parsed.endpoints ?? {}
160
+ };
161
+ }
162
+ function loadRemoteSecretsToml(projectRoot) {
163
+ const paths = resolveAuthorityPaths(projectRoot);
164
+ const parsed = readTomlFile(paths.remoteSecretsPath, { version: 1, secrets: {} });
165
+ return {
166
+ version: parsed.version ?? 1,
167
+ secrets: parsed.secrets ?? {}
168
+ };
169
+ }
170
+ function readJsonRecordFile(path) {
171
+ try {
172
+ if (!existsSync2(path))
173
+ return {};
174
+ const raw = readFileSync(path, "utf8");
175
+ if (!raw.trim())
176
+ return {};
177
+ const parsed = JSON.parse(raw);
178
+ return parsed && typeof parsed === "object" ? parsed : {};
179
+ } catch {
180
+ return {};
181
+ }
182
+ }
183
+ function coerceString(value) {
184
+ return typeof value === "string" ? value.trim() : "";
185
+ }
186
+ function resolveOwnerNamespaceKey(projectRoot, env = process.env) {
187
+ const fromEnv = normalizeString(env.RIG_GITHUB_NAMESPACE_KEY);
188
+ if (fromEnv)
189
+ return fromEnv;
190
+ const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
191
+ const auth = readJsonRecordFile(resolve2(stateDir, "github-auth.json"));
192
+ const namespaceKey = coerceString(auth.userNamespaceKey);
193
+ if (namespaceKey)
194
+ return namespaceKey;
195
+ const userId = coerceString(auth.userId);
196
+ return userId ? `gh:${userId}` : undefined;
197
+ }
198
+ function forceLocalTransport(env) {
199
+ return env.RIG_FORCE_LOCAL === "1" || env.RIG_FORCE_LOCAL === "true";
200
+ }
201
+ function resolveDispatchTransportPlacement(projectRoot, env = process.env) {
202
+ if (forceLocalTransport(env)) {
203
+ return { kind: "local", alias: "local", sshTarget: null, selected: null };
204
+ }
205
+ const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
206
+ const conn = readJsonRecordFile(resolve2(stateDir, "connection.json"));
207
+ const selectedAlias = normalizeString(env.RIG_REMOTE_ALIAS) || coerceString(conn.selected);
208
+ if (selectedAlias === "local") {
209
+ return { kind: "local", alias: "local", sshTarget: null, selected: null };
210
+ }
211
+ const selected = resolveSelectedRemote(projectRoot, env);
212
+ const sshTarget = selected?.sshTarget ?? resolveSshTarget(env);
213
+ if (!sshTarget) {
214
+ return { kind: "local", alias: "local", sshTarget: null, selected: null };
215
+ }
216
+ return { kind: "remote", alias: selected?.alias ?? sshTarget, sshTarget, selected };
217
+ }
218
+ function resolveSelectedRemote(projectRoot, env = process.env) {
219
+ const stateDir = resolveAuthorityPaths(projectRoot).stateDir;
220
+ const conn = readJsonRecordFile(resolve2(stateDir, "connection.json"));
221
+ const selected = normalizeString(env.RIG_REMOTE_ALIAS) || coerceString(conn.selected);
222
+ if (!selected || selected === "local")
223
+ return null;
224
+ const endpoints = Object.values(loadRemoteEndpointsToml(projectRoot).endpoints ?? {});
225
+ const match = endpoints.find((entry) => normalizeString(entry.alias) === selected);
226
+ if (!match) {
227
+ if (selected === "remote") {
228
+ const sshTarget = resolveSshTarget(env);
229
+ if (!sshTarget)
230
+ return null;
231
+ return {
232
+ alias: "remote",
233
+ host: sshTarget,
234
+ port: 22,
235
+ sshTarget,
236
+ checkout: coerceString(conn.serverProjectRoot) || null,
237
+ registryBaseUrl: null,
238
+ secretRef: null
239
+ };
240
+ }
241
+ return null;
242
+ }
243
+ const host = normalizeString(match.host) || selected;
244
+ const explicitRegistryUrl = normalizeString(env.RIG_REGISTRY_URL) || normalizeString(env.REGISTRY_URL);
245
+ const registryBaseUrl = explicitRegistryUrl ? normalizeRegistryBaseUrl(explicitRegistryUrl) : null;
246
+ return {
247
+ alias: selected,
248
+ host,
249
+ port: normalizePort(match.port),
250
+ sshTarget: host || normalizeString(match.alias) || coerceString(conn.serverProjectRootAlias),
251
+ checkout: coerceString(conn.serverProjectRoot) || null,
252
+ registryBaseUrl,
253
+ secretRef: normalizeString(match.secret_ref) || null
254
+ };
255
+ }
256
+ function resolveRegistryBaseUrl(projectRoot, env = process.env) {
257
+ return normalizeRegistryBaseUrl(normalizeString(env.RIG_REGISTRY_URL) || normalizeString(env.REGISTRY_URL) || resolveSelectedRemote(projectRoot, env)?.registryBaseUrl || backboneDefaultsState().registryBaseUrl || "");
258
+ }
259
+ var rigOmpConfigOverlayPath = null;
260
+ function resolveRigOmpConfigOverlayPath(projectRoot = process.cwd()) {
261
+ if (rigOmpConfigOverlayPath && existsSync2(rigOmpConfigOverlayPath))
262
+ return rigOmpConfigOverlayPath;
263
+ const overlay = [
264
+ `collab.relayUrl: ${resolveRelayUrl()}`,
265
+ `registry.url: ${resolveRegistryBaseUrl(projectRoot)}`,
266
+ "# Cockpit owns the screen: skip OMP's welcome/startup so the drone board renders cleanly.",
267
+ "startup.quiet: true",
268
+ ""
269
+ ].join(`
270
+ `);
271
+ const base = tmpdir();
272
+ mkdirSync(base, { recursive: true });
273
+ const dir = mkdtempSync(join(base, "rig-config-"));
274
+ const file = join(dir, "rig-default-config.yml");
275
+ writeFileSync(file, overlay);
276
+ rigOmpConfigOverlayPath = file;
277
+ return file;
278
+ }
279
+ function saveRemoteEndpointsToml(projectRoot, payload) {
280
+ writeTomlFile(resolveAuthorityPaths(projectRoot).remoteEndpointsPath, payload);
281
+ }
282
+ function saveRemoteSecretsToml(projectRoot, payload) {
283
+ const paths = resolveAuthorityPaths(projectRoot);
284
+ writeTomlFile(paths.remoteSecretsPath, payload);
285
+ try {
286
+ chmodSync(paths.remoteSecretsPath, 384);
287
+ } catch {}
288
+ }
289
+ function listAuthorityRemoteEndpoints(projectRoot) {
290
+ const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
291
+ const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
292
+ return Object.values(endpoints).map((entry) => {
293
+ const token = secrets[entry.secret_ref] ?? "";
294
+ return {
295
+ id: entry.id,
296
+ alias: entry.alias,
297
+ host: entry.host,
298
+ port: normalizePort(entry.port),
299
+ token,
300
+ autoConnect: Boolean(entry.auto_connect),
301
+ addedAt: entry.created_at,
302
+ updatedAt: entry.updated_at,
303
+ lastConnectedAt: normalizeString(entry.last_connected_at) || null,
304
+ labels: normalizeStringArray(entry.labels),
305
+ capabilities: normalizeStringArray(entry.capabilities),
306
+ transport: normalizeString(entry.transport) || "websocket",
307
+ secretRef: entry.secret_ref
308
+ };
309
+ }).sort((left, right) => left.alias.localeCompare(right.alias));
310
+ }
311
+ function nextRemoteEndpointRecord(input) {
312
+ const timestamp = new Date().toISOString();
313
+ const secretRef = input.existing?.secret_ref ?? randomUUID();
314
+ return {
315
+ record: {
316
+ id: input.endpointId ?? input.existing?.id ?? randomUUID(),
317
+ alias: input.alias,
318
+ host: input.host,
319
+ port: normalizePort(input.port),
320
+ transport: normalizeString(input.transport) || normalizeString(input.existing?.transport) || "websocket",
321
+ auto_connect: input.autoConnect ?? input.existing?.auto_connect ?? false,
322
+ labels: input.labels ?? normalizeStringArray(input.existing?.labels),
323
+ capabilities: input.capabilities ?? normalizeStringArray(input.existing?.capabilities),
324
+ secret_ref: secretRef,
325
+ created_at: input.existing?.created_at ?? timestamp,
326
+ updated_at: timestamp,
327
+ last_connected_at: input.existing?.last_connected_at ?? null
328
+ },
329
+ secretRef
330
+ };
331
+ }
332
+ function upsertAuthorityRemoteEndpoint(projectRoot, input) {
333
+ const endpointsToml = loadRemoteEndpointsToml(projectRoot);
334
+ const secretsToml = loadRemoteSecretsToml(projectRoot);
335
+ const existing = Object.values(endpointsToml.endpoints ?? {}).find((entry) => entry.alias === input.alias) ?? null;
336
+ const { record, secretRef } = nextRemoteEndpointRecord({
337
+ existing,
338
+ alias: input.alias,
339
+ host: input.host,
340
+ port: input.port,
341
+ token: input.token,
342
+ ...input.endpointId !== undefined ? { endpointId: input.endpointId } : {},
343
+ ...input.autoConnect !== undefined ? { autoConnect: input.autoConnect } : {},
344
+ ...input.transport !== undefined ? { transport: input.transport } : {},
345
+ ...input.labels !== undefined ? { labels: input.labels } : {},
346
+ ...input.capabilities !== undefined ? { capabilities: input.capabilities } : {}
347
+ });
348
+ endpointsToml.endpoints = {
349
+ ...endpointsToml.endpoints ?? {},
350
+ [record.id]: record
351
+ };
352
+ secretsToml.secrets = {
353
+ ...secretsToml.secrets ?? {},
354
+ [secretRef]: input.token
355
+ };
356
+ saveRemoteEndpointsToml(projectRoot, endpointsToml);
357
+ saveRemoteSecretsToml(projectRoot, secretsToml);
358
+ return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id);
359
+ }
360
+ function updateAuthorityRemoteEndpoint(projectRoot, input) {
361
+ const endpointsToml = loadRemoteEndpointsToml(projectRoot);
362
+ const secretsToml = loadRemoteSecretsToml(projectRoot);
363
+ const current = Object.values(endpointsToml.endpoints ?? {}).find((entry) => input.endpointId && entry.id === input.endpointId || input.alias && entry.alias === input.alias);
364
+ if (!current)
365
+ return null;
366
+ const record = {
367
+ ...current,
368
+ alias: normalizeString(input.alias) || current.alias,
369
+ host: normalizeString(input.host) || current.host,
370
+ port: input.port !== undefined ? normalizePort(input.port) : current.port,
371
+ auto_connect: input.autoConnect ?? current.auto_connect,
372
+ transport: normalizeString(input.transport) || current.transport,
373
+ labels: input.labels ?? normalizeStringArray(current.labels),
374
+ capabilities: input.capabilities ?? normalizeStringArray(current.capabilities),
375
+ updated_at: new Date().toISOString()
376
+ };
377
+ endpointsToml.endpoints = {
378
+ ...endpointsToml.endpoints ?? {},
379
+ [record.id]: record
380
+ };
381
+ if (input.token !== undefined) {
382
+ secretsToml.secrets = {
383
+ ...secretsToml.secrets ?? {},
384
+ [record.secret_ref]: input.token
385
+ };
386
+ }
387
+ saveRemoteEndpointsToml(projectRoot, endpointsToml);
388
+ saveRemoteSecretsToml(projectRoot, secretsToml);
389
+ return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id) ?? null;
390
+ }
391
+ function removeAuthorityRemoteEndpoint(projectRoot, endpointIdOrAlias) {
392
+ const endpointsToml = loadRemoteEndpointsToml(projectRoot);
393
+ const secretsToml = loadRemoteSecretsToml(projectRoot);
394
+ const entry = Object.values(endpointsToml.endpoints ?? {}).find((candidate) => candidate.id === endpointIdOrAlias || candidate.alias === endpointIdOrAlias);
395
+ if (!entry)
396
+ return false;
397
+ const nextEndpoints = { ...endpointsToml.endpoints ?? {} };
398
+ delete nextEndpoints[entry.id];
399
+ const nextSecrets = { ...secretsToml.secrets ?? {} };
400
+ delete nextSecrets[entry.secret_ref];
401
+ saveRemoteEndpointsToml(projectRoot, { version: endpointsToml.version ?? 1, endpoints: nextEndpoints });
402
+ saveRemoteSecretsToml(projectRoot, { version: secretsToml.version ?? 1, secrets: nextSecrets });
403
+ return true;
404
+ }
405
+ function markAuthorityRemoteEndpointConnected(projectRoot, endpointId, connectedAt = new Date().toISOString()) {
406
+ const endpointsToml = loadRemoteEndpointsToml(projectRoot);
407
+ const entry = endpointsToml.endpoints?.[endpointId];
408
+ if (!entry)
409
+ return;
410
+ endpointsToml.endpoints = {
411
+ ...endpointsToml.endpoints ?? {},
412
+ [endpointId]: {
413
+ ...entry,
414
+ last_connected_at: connectedAt,
415
+ updated_at: connectedAt
416
+ }
417
+ };
418
+ saveRemoteEndpointsToml(projectRoot, endpointsToml);
419
+ }
420
+ function importLegacyRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
421
+ if (!existsSync2(legacyPath)) {
422
+ return { imported: 0, skipped: 0, sourcePath: legacyPath };
423
+ }
424
+ const parsed = readTomlFile(legacyPath, { version: 1, remotes: {} });
425
+ let imported = 0;
426
+ let skipped = 0;
427
+ for (const [alias, entry] of Object.entries(parsed.remotes ?? {})) {
428
+ const host = normalizeString(entry.host);
429
+ const token = normalizeString(entry.token);
430
+ if (!host || !token) {
431
+ skipped += 1;
432
+ continue;
433
+ }
434
+ upsertAuthorityRemoteEndpoint(projectRoot, {
435
+ alias,
436
+ host,
437
+ port: normalizePort(entry.port),
438
+ token
439
+ });
440
+ imported += 1;
441
+ }
442
+ return { imported, skipped, sourcePath: legacyPath };
443
+ }
444
+ function doctorAuthorityRemoteEndpoints(projectRoot) {
445
+ const paths = resolveAuthorityPaths(projectRoot);
446
+ const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
447
+ const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
448
+ const missingSecrets = Object.values(endpoints).filter((entry) => !normalizeString(secrets[entry.secret_ref])).map((entry) => entry.alias);
449
+ const warnings = [];
450
+ if (!existsSync2(paths.remoteEndpointsPath)) {
451
+ warnings.push("Remote endpoint manifest is missing.");
452
+ }
453
+ if (!existsSync2(paths.remoteSecretsPath)) {
454
+ warnings.push("Remote secret store is missing.");
455
+ }
456
+ return {
457
+ manifestPath: paths.remoteEndpointsPath,
458
+ secretsPath: paths.remoteSecretsPath,
459
+ endpointCount: Object.keys(endpoints).length,
460
+ missingSecrets,
461
+ warnings
462
+ };
463
+ }
464
+ function listManagedRemoteEndpoints(configPath = REMOTES_CONFIG_PATH, projectRoot) {
465
+ if (projectRoot) {
466
+ return listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
467
+ id: entry.id,
468
+ alias: entry.alias,
469
+ host: entry.host,
470
+ port: entry.port,
471
+ token: entry.token,
472
+ addedAt: entry.addedAt,
473
+ lastConnected: entry.lastConnectedAt
474
+ }));
475
+ }
476
+ const config = loadRemotesConfig(configPath);
477
+ return Object.entries(config.remotes ?? {}).map(([alias, entry]) => ({
478
+ id: alias,
479
+ alias,
480
+ host: normalizeString(entry.host) || "",
481
+ port: Number.isFinite(entry.port) ? Number(entry.port) : DEFAULT_REMOTE_PORT,
482
+ token: normalizeString(entry.token) || "",
483
+ addedAt: normalizeString(entry.addedAt) || null,
484
+ lastConnected: normalizeString(entry.lastConnected) || null
485
+ })).sort((left, right) => left.alias.localeCompare(right.alias));
486
+ }
487
+ function upsertManagedRemoteEndpoint(input, configPath = REMOTES_CONFIG_PATH, projectRoot) {
488
+ if (projectRoot) {
489
+ const saved = upsertAuthorityRemoteEndpoint(projectRoot, {
490
+ ...input,
491
+ token: input.token ?? ""
492
+ });
493
+ return {
494
+ id: saved.id,
495
+ alias: saved.alias,
496
+ host: saved.host,
497
+ port: saved.port,
498
+ token: saved.token,
499
+ addedAt: saved.addedAt,
500
+ lastConnected: saved.lastConnectedAt
501
+ };
502
+ }
503
+ const config = loadRemotesConfig(configPath);
504
+ const existing = config.remotes?.[input.alias];
505
+ const nextEntry = {
506
+ host: input.host,
507
+ port: input.port,
508
+ ...input.token !== undefined ? { token: input.token } : {},
509
+ addedAt: normalizeString(existing?.addedAt) || new Date().toISOString(),
510
+ ...normalizeString(existing?.lastConnected) ? { lastConnected: normalizeString(existing?.lastConnected) } : {}
511
+ };
512
+ const nextConfig = {
513
+ version: config.version ?? 1,
514
+ remotes: {
515
+ ...config.remotes ?? {},
516
+ [input.alias]: nextEntry
517
+ }
518
+ };
519
+ saveRemotesConfig(nextConfig, configPath);
520
+ return {
521
+ id: input.alias,
522
+ alias: input.alias,
523
+ host: input.host,
524
+ port: input.port,
525
+ token: input.token ?? "",
526
+ addedAt: nextEntry.addedAt ?? null,
527
+ lastConnected: nextEntry.lastConnected ?? null
528
+ };
529
+ }
530
+ function removeManagedRemoteEndpoint(alias, configPath = REMOTES_CONFIG_PATH, projectRoot) {
531
+ if (projectRoot) {
532
+ return removeAuthorityRemoteEndpoint(projectRoot, alias);
533
+ }
534
+ const config = loadRemotesConfig(configPath);
535
+ if (!config.remotes?.[alias]) {
536
+ return false;
537
+ }
538
+ const nextRemotes = { ...config.remotes ?? {} };
539
+ delete nextRemotes[alias];
540
+ saveRemotesConfig({
541
+ version: config.version ?? 1,
542
+ remotes: nextRemotes
543
+ }, configPath);
544
+ return true;
545
+ }
546
+ function normalizeString(value) {
547
+ if (!value) {
548
+ return "";
549
+ }
550
+ return value.trim();
551
+ }
552
+ export {
553
+ upsertManagedRemoteEndpoint,
554
+ updateAuthorityRemoteEndpoint,
555
+ resolveSshTarget,
556
+ resolveSelectedRemote,
557
+ resolveRigOmpConfigOverlayPath,
558
+ resolveRelayUrl,
559
+ resolveRegistryBaseUrl,
560
+ resolveOwnerNamespaceKey,
561
+ resolveDispatchTransportPlacement,
562
+ removeManagedRemoteEndpoint,
563
+ registerBackboneDefaults,
564
+ normalizeString,
565
+ markAuthorityRemoteEndpointConnected,
566
+ loadRemotesConfig,
567
+ listManagedRemoteEndpoints,
568
+ listAuthorityRemoteEndpoints,
569
+ importLegacyRemoteEndpoints,
570
+ doctorAuthorityRemoteEndpoints,
571
+ RemoteCliError,
572
+ REMOTES_CONFIG_PATH,
573
+ DEFAULT_REMOTE_PORT
574
+ };
@@ -0,0 +1,5 @@
1
+ export declare function resolveProjectRoot(options?: {
2
+ cwd?: string;
3
+ projectRootEnv?: string;
4
+ fallbackFromDir?: string;
5
+ }): string;
@@ -0,0 +1,69 @@
1
+ // @bun
2
+ // packages/core/src/root-resolver.ts
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { dirname, parse, resolve } from "path";
5
+
6
+ // packages/core/src/layout.ts
7
+ import {
8
+ RIG_ARTIFACTS_DIRNAME,
9
+ RIG_DEFINITION_DIRNAME,
10
+ RIG_STATE_DIRNAME
11
+ } from "@rig/contracts";
12
+
13
+ // packages/core/src/root-resolver.ts
14
+ function hasProjectMarker(candidate) {
15
+ return existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME));
16
+ }
17
+ function resolveProjectRoot(options) {
18
+ const cwd = options?.cwd || process.cwd();
19
+ const envRoot = options?.projectRootEnv || process.env.PROJECT_RIG_ROOT || "";
20
+ const fallbackFromDir = options?.fallbackFromDir || cwd;
21
+ if (envRoot && hasProjectMarker(envRoot)) {
22
+ return envRoot;
23
+ }
24
+ const walked = walkUpForRoot(cwd);
25
+ if (walked) {
26
+ return walked;
27
+ }
28
+ const configRoot = readConfiguredRoot();
29
+ if (configRoot && hasProjectMarker(configRoot)) {
30
+ return configRoot;
31
+ }
32
+ let fileDir = resolve(fallbackFromDir);
33
+ for (let i = 0;i < 5; i += 1) {
34
+ if (hasProjectMarker(fileDir)) {
35
+ return fileDir;
36
+ }
37
+ fileDir = dirname(fileDir);
38
+ }
39
+ return cwd;
40
+ }
41
+ function walkUpForRoot(start) {
42
+ let searchDir = resolve(start);
43
+ const root = parse(searchDir).root || "/";
44
+ while (searchDir !== root) {
45
+ if (hasProjectMarker(searchDir)) {
46
+ return searchDir;
47
+ }
48
+ searchDir = dirname(searchDir);
49
+ }
50
+ if (hasProjectMarker(root)) {
51
+ return root;
52
+ }
53
+ return "";
54
+ }
55
+ function readConfiguredRoot() {
56
+ const configPath = resolve(process.env.HOME || "~", ".config", "project-rig", "root");
57
+ if (!existsSync(configPath)) {
58
+ return "";
59
+ }
60
+ try {
61
+ const value = readFileSync(configPath, "utf-8").split(/\r?\n/)[0]?.trim() || "";
62
+ return value;
63
+ } catch {
64
+ return "";
65
+ }
66
+ }
67
+ export {
68
+ resolveProjectRoot
69
+ };