overlord-cli 0.2607122001.0 → 0.2607141312.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/index.js CHANGED
@@ -53,46 +53,6 @@ var init_errors = __esm({
53
53
  }
54
54
  });
55
55
 
56
- // src/version.ts
57
- import { existsSync, readFileSync } from "node:fs";
58
- import path from "node:path";
59
- import { fileURLToPath } from "node:url";
60
- function resolvePackageJsonPath() {
61
- let dir = path.dirname(fileURLToPath(import.meta.url));
62
- while (true) {
63
- const candidate = path.join(dir, "package.json");
64
- if (existsSync(candidate)) {
65
- const packageJson = JSON.parse(readFileSync(candidate, "utf8"));
66
- if (packageJson.name === "overlord-cli") {
67
- return candidate;
68
- }
69
- }
70
- const parent = path.dirname(dir);
71
- if (parent === dir) {
72
- break;
73
- }
74
- dir = parent;
75
- }
76
- throw new Error("Cannot locate overlord-cli package.json");
77
- }
78
- function getCliVersion() {
79
- const packageJson = JSON.parse(readFileSync(resolvePackageJsonPath(), "utf8"));
80
- return packageJson.version;
81
- }
82
- function runVersionCommand({ json = false } = {}) {
83
- const version = getCliVersion();
84
- if (json) {
85
- console.log(JSON.stringify({ version, node: process.version }));
86
- return;
87
- }
88
- console.log(`Overlord CLI ${version}`);
89
- }
90
- var init_version = __esm({
91
- "src/version.ts"() {
92
- "use strict";
93
- }
94
- });
95
-
96
56
  // src/args.ts
97
57
  function parseArgs(argv) {
98
58
  const positional = [];
@@ -164,6 +124,46 @@ var init_args = __esm({
164
124
  }
165
125
  });
166
126
 
127
+ // src/version.ts
128
+ import { existsSync, readFileSync } from "node:fs";
129
+ import path from "node:path";
130
+ import { fileURLToPath } from "node:url";
131
+ function resolvePackageJsonPath() {
132
+ let dir = path.dirname(fileURLToPath(import.meta.url));
133
+ while (true) {
134
+ const candidate = path.join(dir, "package.json");
135
+ if (existsSync(candidate)) {
136
+ const packageJson = JSON.parse(readFileSync(candidate, "utf8"));
137
+ if (packageJson.name === "overlord-cli") {
138
+ return candidate;
139
+ }
140
+ }
141
+ const parent = path.dirname(dir);
142
+ if (parent === dir) {
143
+ break;
144
+ }
145
+ dir = parent;
146
+ }
147
+ throw new Error("Cannot locate overlord-cli package.json");
148
+ }
149
+ function getCliVersion() {
150
+ const packageJson = JSON.parse(readFileSync(resolvePackageJsonPath(), "utf8"));
151
+ return packageJson.version;
152
+ }
153
+ function runVersionCommand({ json = false } = {}) {
154
+ const version = getCliVersion();
155
+ if (json) {
156
+ console.log(JSON.stringify({ version, node: process.version }));
157
+ return;
158
+ }
159
+ console.log(`Overlord CLI ${version}`);
160
+ }
161
+ var init_version = __esm({
162
+ "src/version.ts"() {
163
+ "use strict";
164
+ }
165
+ });
166
+
167
167
  // src/output.ts
168
168
  function printJson(value) {
169
169
  process.stdout.write(`${JSON.stringify(value, null, 2)}
@@ -1540,6 +1540,9 @@ function readStoredAuthCredentials() {
1540
1540
  if (typeof raw.token !== "string" || !raw.token.trim()) return null;
1541
1541
  if (raw.type !== "session_bearer" && raw.type !== "user_token") return null;
1542
1542
  if (typeof raw.backendUrl !== "string" || !raw.backendUrl.trim()) return null;
1543
+ if (raw.expiresAt !== void 0 && raw.expiresAt !== null && typeof raw.expiresAt !== "string") {
1544
+ raw.expiresAt = null;
1545
+ }
1543
1546
  return raw;
1544
1547
  } catch {
1545
1548
  return null;
@@ -1548,7 +1551,8 @@ function readStoredAuthCredentials() {
1548
1551
  function writeStoredAuthCredentials({
1549
1552
  type,
1550
1553
  token,
1551
- backendUrl
1554
+ backendUrl,
1555
+ expiresAt = null
1552
1556
  }) {
1553
1557
  const filePath = authCredentialsPath();
1554
1558
  mkdirSync2(path4.dirname(filePath), { recursive: true });
@@ -1556,7 +1560,8 @@ function writeStoredAuthCredentials({
1556
1560
  type,
1557
1561
  token,
1558
1562
  backendUrl: backendUrl.replace(/\/+$/, ""),
1559
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1563
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1564
+ expiresAt: expiresAt ?? null
1560
1565
  };
1561
1566
  writeFileSync2(filePath, `${JSON.stringify(payload, null, 2)}
1562
1567
  `, { mode: 384 });
@@ -1816,7 +1821,12 @@ async function createFullUserTokenFromSession({
1816
1821
  const payload = await readResponseJson(response);
1817
1822
  if (response.ok && payload && typeof payload === "object") {
1818
1823
  const secret = payload.secret;
1819
- if (typeof secret === "string" && secret.startsWith("out_")) return secret;
1824
+ if (typeof secret === "string" && secret.startsWith("out_")) {
1825
+ const tokenDto = payload.token;
1826
+ const rawExpiry = tokenDto && typeof tokenDto === "object" ? tokenDto.expiresAt : void 0;
1827
+ const expiresAt = typeof rawExpiry === "string" && rawExpiry.trim() ? rawExpiry : null;
1828
+ return { secret, expiresAt };
1829
+ }
1820
1830
  }
1821
1831
  throw new CliError({
1822
1832
  message: errorMessageFromJson(payload) ?? `Could not create CLI USER_TOKEN (${response.status}). Check your account permissions.`
@@ -1882,15 +1892,16 @@ async function runInteractiveAuthLogin({
1882
1892
  });
1883
1893
  await validateBearerToken({ backendUrl: normalizedBackendUrl, token: sessionToken });
1884
1894
  if (passwordCredentialTarget === "full_user_token") {
1885
- const userToken = await createFullUserTokenFromSession({
1895
+ const minted = await createFullUserTokenFromSession({
1886
1896
  backendUrl: normalizedBackendUrl,
1887
1897
  sessionToken
1888
1898
  });
1889
- await validateBearerToken({ backendUrl: normalizedBackendUrl, token: userToken });
1899
+ await validateBearerToken({ backendUrl: normalizedBackendUrl, token: minted.secret });
1890
1900
  writeStoredAuthCredentials({
1891
1901
  type: "user_token",
1892
- token: userToken,
1893
- backendUrl: normalizedBackendUrl
1902
+ token: minted.secret,
1903
+ backendUrl: normalizedBackendUrl,
1904
+ expiresAt: minted.expiresAt
1894
1905
  });
1895
1906
  return {
1896
1907
  ok: true,
@@ -1927,6 +1938,21 @@ __export(auth_status_exports, {
1927
1938
  resolveAuthCredentialSource: () => resolveAuthCredentialSource,
1928
1939
  resolveAuthStatus: () => resolveAuthStatus
1929
1940
  });
1941
+ function resolveExpiry(expiresAt) {
1942
+ if (typeof expiresAt !== "string" || !expiresAt.trim()) {
1943
+ return { expiresAt: null, expiresInDays: null, expired: false };
1944
+ }
1945
+ const parsed = new Date(expiresAt);
1946
+ if (Number.isNaN(parsed.getTime())) {
1947
+ return { expiresAt: null, expiresInDays: null, expired: false };
1948
+ }
1949
+ const msRemaining = parsed.getTime() - Date.now();
1950
+ return {
1951
+ expiresAt: parsed.toISOString(),
1952
+ expiresInDays: Math.floor(msRemaining / (24 * 60 * 60 * 1e3)),
1953
+ expired: msRemaining <= 0
1954
+ };
1955
+ }
1930
1956
  function normalizeBaseUrl3(value) {
1931
1957
  return value.replace(/\/+$/, "");
1932
1958
  }
@@ -1972,6 +1998,9 @@ async function resolveAuthStatus({
1972
1998
  const configPath = findEffectiveConfigPath();
1973
1999
  const credential = resolveAuthCredentialSource({ backendUrl, env });
1974
2000
  const token = resolveAuthBearerToken({ backendUrl, env });
2001
+ const expiry = resolveExpiry(
2002
+ credential.source === "stored" || credential.source === "stored_mismatch" ? readStoredAuthCredentials()?.expiresAt : null
2003
+ );
1975
2004
  if (!token) {
1976
2005
  return {
1977
2006
  backendUrl,
@@ -1981,7 +2010,8 @@ async function resolveAuthStatus({
1981
2010
  credentialSource: credential.source,
1982
2011
  credentialType: credential.type,
1983
2012
  credentialsPath: credential.credentialsPath,
1984
- validationError: credential.source === "stored_mismatch" ? `Stored credentials are for ${readStoredAuthCredentials()?.backendUrl ?? "another backend"}.` : null
2013
+ validationError: credential.source === "stored_mismatch" ? `Stored credentials are for ${readStoredAuthCredentials()?.backendUrl ?? "another backend"}.` : null,
2014
+ ...expiry
1985
2015
  };
1986
2016
  }
1987
2017
  try {
@@ -1994,7 +2024,8 @@ async function resolveAuthStatus({
1994
2024
  credentialSource: credential.source,
1995
2025
  credentialType: credential.type,
1996
2026
  credentialsPath: credential.credentialsPath,
1997
- validationError: null
2027
+ validationError: null,
2028
+ ...expiry
1998
2029
  };
1999
2030
  } catch (error) {
2000
2031
  return {
@@ -2005,7 +2036,8 @@ async function resolveAuthStatus({
2005
2036
  credentialSource: credential.source,
2006
2037
  credentialType: credential.type,
2007
2038
  credentialsPath: credential.credentialsPath,
2008
- validationError: error instanceof Error ? error.message : String(error)
2039
+ validationError: error instanceof Error ? error.message : String(error),
2040
+ ...expiry
2009
2041
  };
2010
2042
  }
2011
2043
  }
@@ -2925,12 +2957,12 @@ function createBackendClient() {
2925
2957
  const clientDevice = clientDeviceIdentity();
2926
2958
  async function request({
2927
2959
  method,
2928
- path: path26,
2960
+ path: path27,
2929
2961
  body,
2930
2962
  extraHeaders,
2931
2963
  rawBody
2932
2964
  }) {
2933
- const url = `${baseUrl}${path26.startsWith("/") ? path26 : `/${path26}`}`;
2965
+ const url = `${baseUrl}${path27.startsWith("/") ? path27 : `/${path27}`}`;
2934
2966
  const auth = resolveAuthHeaders({ baseUrl });
2935
2967
  let response;
2936
2968
  try {
@@ -2957,15 +2989,14 @@ Start the Desktop/local backend or run \`ovld config set cloud <url>\`.
2957
2989
  const payload = await readResponseJson2(response);
2958
2990
  if (!response.ok) {
2959
2991
  if (response.status === 401 && auth.headers.Authorization) {
2960
- if (auth.fromStored) clearStoredAuthCredentials();
2961
- const detail = errorMessageFromJson2(payload) ?? `Backend request failed: ${method} ${path26} (${response.status})`;
2992
+ const detail = errorMessageFromJson2(payload) ?? `Backend request failed: ${method} ${path27} (${response.status})`;
2962
2993
  throw new CliError({
2963
2994
  message: `${detail}
2964
- ` + (auth.fromStored ? "Saved credentials were cleared. Run `ovld auth login` to sign in again." : "Run `ovld auth login` or refresh your USER_TOKEN environment variable.")
2995
+ ` + (auth.fromStored ? "Your saved credentials were rejected. Run `ovld auth login` to sign in again." : "Run `ovld auth login` or refresh your USER_TOKEN environment variable.")
2965
2996
  });
2966
2997
  }
2967
2998
  throw new CliError({
2968
- message: errorMessageFromJson2(payload) ?? `Backend request failed: ${method} ${path26} (${response.status})`
2999
+ message: errorMessageFromJson2(payload) ?? `Backend request failed: ${method} ${path27} (${response.status})`
2969
3000
  });
2970
3001
  }
2971
3002
  return payload;
@@ -2973,13 +3004,13 @@ Start the Desktop/local backend or run \`ovld config set cloud <url>\`.
2973
3004
  return {
2974
3005
  baseUrl,
2975
3006
  health: () => request({ method: "GET", path: "/api/health" }),
2976
- get: (path26) => request({ method: "GET", path: path26 }),
2977
- post: ({ path: path26, body }) => request({ method: "POST", path: path26, body }),
2978
- patch: ({ path: path26, body }) => request({ method: "PATCH", path: path26, body }),
2979
- delete: (path26) => request({ method: "DELETE", path: path26 }),
2980
- postRaw: ({ path: path26, body, contentType, filename }) => request({
3007
+ get: (path27) => request({ method: "GET", path: path27 }),
3008
+ post: ({ path: path27, body }) => request({ method: "POST", path: path27, body }),
3009
+ patch: ({ path: path27, body }) => request({ method: "PATCH", path: path27, body }),
3010
+ delete: (path27) => request({ method: "DELETE", path: path27 }),
3011
+ postRaw: ({ path: path27, body, contentType, filename }) => request({
2981
3012
  method: "POST",
2982
- path: path26,
3013
+ path: path27,
2983
3014
  rawBody: body,
2984
3015
  extraHeaders: {
2985
3016
  "Content-Type": contentType || "application/octet-stream",
@@ -3509,7 +3540,10 @@ async function runFullSetupCommand({ json }) {
3509
3540
  const backend = await configureBackendForSetup();
3510
3541
  const auth = await configureAuthForSetup({
3511
3542
  backendUrl: backend.url,
3512
- passwordCredentialTarget: backend.mode === "cloud" ? "full_user_token" : "session_bearer"
3543
+ // Mint a long-lived (90-day) user token in both modes; see the note in
3544
+ // management.ts. Avoids the ~7-day session_bearer lifetime local logins used
3545
+ // to get.
3546
+ passwordCredentialTarget: "full_user_token"
3513
3547
  });
3514
3548
  const agents = await configureAgentsForSetup();
3515
3549
  const runtime = openCliRuntime();
@@ -4121,8 +4155,47 @@ async function runAuthStatusCommand({ json }) {
4121
4155
  console.log(`credential_source=${status.credentialSource}`);
4122
4156
  if (status.credentialType) console.log(`credential_type=${status.credentialType}`);
4123
4157
  if (status.credentialsPath) console.log(`credentials_path=${status.credentialsPath}`);
4158
+ if (status.expiresAt) {
4159
+ const remaining = status.expired ? "expired" : status.expiresInDays !== null ? `${status.expiresInDays} day(s) remaining` : "unknown";
4160
+ console.log(`expires_at=${status.expiresAt} (${remaining})`);
4161
+ }
4124
4162
  if (status.validationError) console.log(`validation_error=${status.validationError}`);
4125
4163
  }
4164
+ async function runAuthRepairCommand({
4165
+ rest,
4166
+ json
4167
+ }) {
4168
+ const { resolveAuthStatus: resolveAuthStatus2 } = await Promise.resolve().then(() => (init_auth_status(), auth_status_exports));
4169
+ const status = await resolveAuthStatus2();
4170
+ if (status.loggedIn) {
4171
+ if (json) {
4172
+ printJson({ ok: true, action: "none", reason: "healthy", status });
4173
+ } else {
4174
+ console.log("Authentication is healthy; no repair needed.");
4175
+ if (status.expiresAt && !status.expired && status.expiresInDays !== null) {
4176
+ console.log(
4177
+ `Credential expires ${status.expiresAt} (${status.expiresInDays} day(s) remaining).`
4178
+ );
4179
+ }
4180
+ }
4181
+ return;
4182
+ }
4183
+ if (status.credentialSource === "environment") {
4184
+ throw new CliError({
4185
+ message: `Your credential comes from an environment variable (OVERLORD_USER_TOKEN / OVLD_USER_TOKEN / USER_TOKEN) and cannot be repaired from disk.
4186
+ Refresh that variable with a valid token${status.validationError ? ` (backend said: ${status.validationError})` : ""}.`
4187
+ });
4188
+ }
4189
+ const { clearStoredAuthCredentials: clearStoredAuthCredentials2, readStoredAuthCredentials: readStoredAuthCredentials2 } = await Promise.resolve().then(() => (init_auth_credentials(), auth_credentials_exports));
4190
+ const hadStored = readStoredAuthCredentials2() !== null;
4191
+ if (hadStored) {
4192
+ clearStoredAuthCredentials2();
4193
+ if (!json) console.log("Cleared stale stored credentials; re-authenticating...");
4194
+ } else if (!json) {
4195
+ console.log("No stored credentials found; starting a fresh login...");
4196
+ }
4197
+ await runAuthLoginFlow({ parsed: parseArgs(rest.filter((arg) => arg !== "repair")), json });
4198
+ }
4126
4199
  async function runAuthCommand({ rest, json }) {
4127
4200
  const parsed = parseArgs(rest);
4128
4201
  const sub = parsed.positional[0] ?? "login";
@@ -4130,11 +4203,21 @@ async function runAuthCommand({ rest, json }) {
4130
4203
  await runAuthStatusCommand({ json });
4131
4204
  return;
4132
4205
  }
4206
+ if (sub === "repair") {
4207
+ await runAuthRepairCommand({ rest, json });
4208
+ return;
4209
+ }
4133
4210
  if (sub !== "login") {
4134
4211
  throw new CliError({
4135
- message: "Usage: ovld auth login [--token <out_...>] | ovld auth status [--json]"
4212
+ message: "Usage: ovld auth login [--token <out_...>] | ovld auth status [--json] | ovld auth repair [--json]"
4136
4213
  });
4137
4214
  }
4215
+ await runAuthLoginFlow({ parsed, json });
4216
+ }
4217
+ async function runAuthLoginFlow({
4218
+ parsed,
4219
+ json
4220
+ }) {
4138
4221
  const tokenFlag = flagValue(parsed.flags, "--token");
4139
4222
  const { findEffectiveConfigPath: findEffectiveConfigPath2, hasExplicitBackendConfig: hasExplicitBackendConfig2, loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4140
4223
  let config = loadConfig2();
@@ -4155,7 +4238,12 @@ async function runAuthCommand({ rest, json }) {
4155
4238
  const { loginWithUserToken: loginWithUserToken2, runInteractiveAuthLogin: runInteractiveAuthLogin2 } = await Promise.resolve().then(() => (init_auth_login(), auth_login_exports));
4156
4239
  const login = tokenFlag ? await loginWithUserToken2({ backendUrl, token: tokenFlag }) : await runInteractiveAuthLogin2({
4157
4240
  backendUrl,
4158
- passwordCredentialTarget: mode === "cloud" ? "full_user_token" : "session_bearer"
4241
+ // Always exchange the password sign-in for a long-lived (90-day) user
4242
+ // token, regardless of backend mode. Storing the raw Better Auth session
4243
+ // token (session_bearer) gave local-mode logins a ~7-day lifetime while
4244
+ // cloud logins got 90 days; minting a full_user_token in both modes keeps
4245
+ // the lifetime consistent and avoids surprise "token expired" churn.
4246
+ passwordCredentialTarget: "full_user_token"
4159
4247
  });
4160
4248
  if (json) {
4161
4249
  printJson({
@@ -4445,6 +4533,164 @@ var init_serve = __esm({
4445
4533
  }
4446
4534
  });
4447
4535
 
4536
+ // src/contract.ts
4537
+ var contract_exports = {};
4538
+ __export(contract_exports, {
4539
+ runContractCommand: () => runContractCommand,
4540
+ validateAgainstSchema: () => validateAgainstSchema,
4541
+ validateManifest: () => validateManifest
4542
+ });
4543
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
4544
+ import path10 from "node:path";
4545
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
4546
+ import { parse as parseYaml } from "yaml";
4547
+ function schemaPath() {
4548
+ const packaged = path10.join(
4549
+ path10.dirname(fileURLToPath5(import.meta.url)),
4550
+ "contract",
4551
+ "conformance-manifest.schema.yaml"
4552
+ );
4553
+ if (existsSync10(packaged)) return packaged;
4554
+ let dir = process.cwd();
4555
+ while (true) {
4556
+ const candidate = path10.join(dir, "contract", "conformance-manifest.schema.yaml");
4557
+ if (existsSync10(candidate)) return candidate;
4558
+ const parent = path10.dirname(dir);
4559
+ if (parent === dir) break;
4560
+ dir = parent;
4561
+ }
4562
+ return resolveRepoPath("contract/conformance-manifest.schema.yaml");
4563
+ }
4564
+ function loadSchema() {
4565
+ if (cachedSchema) return cachedSchema;
4566
+ const file = schemaPath();
4567
+ if (!existsSync10(file)) {
4568
+ throw new CliError({
4569
+ message: `Could not find conformance-manifest.schema.yaml (looked at ${file}).`
4570
+ });
4571
+ }
4572
+ cachedSchema = parseYaml(readFileSync8(file, "utf8"));
4573
+ return cachedSchema;
4574
+ }
4575
+ function validateAgainstSchema(value, schema, location = "$") {
4576
+ const errors = [];
4577
+ if (schema.type === "object") {
4578
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
4579
+ return [`${location}: must be an object`];
4580
+ }
4581
+ const obj = value;
4582
+ for (const key of schema.required ?? []) {
4583
+ if (!(key in obj) || obj[key] === void 0 || obj[key] === "") {
4584
+ errors.push(`${location}: missing required field "${key}"`);
4585
+ }
4586
+ }
4587
+ if (schema.additionalProperties === false) {
4588
+ const allowed = new Set(Object.keys(schema.properties ?? {}));
4589
+ for (const key of Object.keys(obj)) {
4590
+ if (!allowed.has(key)) {
4591
+ errors.push(`${location}: unexpected field "${key}"`);
4592
+ }
4593
+ }
4594
+ }
4595
+ for (const [key, propSchema] of Object.entries(schema.properties ?? {})) {
4596
+ if (obj[key] === void 0) continue;
4597
+ errors.push(...validateAgainstSchema(obj[key], propSchema, `${location}.${key}`));
4598
+ }
4599
+ return errors;
4600
+ }
4601
+ if (schema.type === "array") {
4602
+ if (!Array.isArray(value)) {
4603
+ return [`${location}: must be an array`];
4604
+ }
4605
+ if (schema.uniqueItems) {
4606
+ const seen = /* @__PURE__ */ new Set();
4607
+ value.forEach((item) => {
4608
+ const key = JSON.stringify(item);
4609
+ if (seen.has(key)) errors.push(`${location}: duplicate item ${key}`);
4610
+ seen.add(key);
4611
+ });
4612
+ }
4613
+ if (schema.items) {
4614
+ value.forEach((item, index) => {
4615
+ errors.push(...validateAgainstSchema(item, schema.items, `${location}[${index}]`));
4616
+ });
4617
+ }
4618
+ return errors;
4619
+ }
4620
+ if (schema.type === "string") {
4621
+ if (typeof value !== "string") {
4622
+ return [`${location}: must be a string`];
4623
+ }
4624
+ if (schema.enum && !schema.enum.includes(value)) {
4625
+ errors.push(`${location}: must be one of ${schema.enum.join(", ")} (got "${value}")`);
4626
+ }
4627
+ if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
4628
+ errors.push(`${location}: must match pattern ${schema.pattern} (got "${value}")`);
4629
+ }
4630
+ return errors;
4631
+ }
4632
+ return errors;
4633
+ }
4634
+ function validateManifest(manifest) {
4635
+ const schema = loadSchema();
4636
+ return validateAgainstSchema(manifest, schema, "$");
4637
+ }
4638
+ function readManifest(manifestPathArg) {
4639
+ const resolved = path10.resolve(process.cwd(), manifestPathArg);
4640
+ if (!existsSync10(resolved)) {
4641
+ throw new CliError({ message: `Manifest not found: ${manifestPathArg}` });
4642
+ }
4643
+ return parseYaml(readFileSync8(resolved, "utf8"));
4644
+ }
4645
+ function runContractCheck({ args, json }) {
4646
+ const manifestPathArg = args.find((arg) => !arg.startsWith("--"));
4647
+ if (!manifestPathArg) {
4648
+ throw new CliError({ message: "Usage: ovld contract check <manifest-path> [--json]" });
4649
+ }
4650
+ const manifest = readManifest(manifestPathArg);
4651
+ const errors = validateManifest(manifest);
4652
+ const valid = errors.length === 0;
4653
+ if (json) {
4654
+ console.log(JSON.stringify({ valid, manifest: manifestPathArg, errors }, null, 2));
4655
+ } else if (valid) {
4656
+ console.log(`\u2713 ${manifestPathArg} conforms to conformance-manifest.schema.yaml`);
4657
+ } else {
4658
+ console.log(`\u2717 ${manifestPathArg} does not conform to conformance-manifest.schema.yaml:`);
4659
+ for (const error of errors) {
4660
+ console.log(` - ${error}`);
4661
+ }
4662
+ }
4663
+ if (!valid) {
4664
+ throw new CliError({ message: `${errors.length} conformance error(s) found.` });
4665
+ }
4666
+ }
4667
+ async function runContractCommand({
4668
+ subcommand,
4669
+ args,
4670
+ primaryCommand
4671
+ }) {
4672
+ const json = args.includes("--json");
4673
+ const rest = args.filter((arg) => arg !== "--json");
4674
+ switch (subcommand) {
4675
+ case "check":
4676
+ runContractCheck({ args: rest, json });
4677
+ return;
4678
+ default:
4679
+ throw new CliError({
4680
+ message: `Unknown contract subcommand: ${subcommand}
4681
+ Usage: ${primaryCommand} contract check <manifest-path> [--json]`
4682
+ });
4683
+ }
4684
+ }
4685
+ var cachedSchema;
4686
+ var init_contract = __esm({
4687
+ "src/contract.ts"() {
4688
+ "use strict";
4689
+ init_config();
4690
+ init_errors();
4691
+ }
4692
+ });
4693
+
4448
4694
  // ../packages/core/dist/service/util.js
4449
4695
  function nowIso() {
4450
4696
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -4460,12 +4706,12 @@ var init_util2 = __esm({
4460
4706
  });
4461
4707
 
4462
4708
  // ../packages/core/dist/service/local-target/project-metadata.js
4463
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
4464
- import path10 from "node:path";
4709
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
4710
+ import path11 from "node:path";
4465
4711
  function parseProjectJson(projectJsonPath) {
4466
- if (!existsSync10(projectJsonPath))
4712
+ if (!existsSync11(projectJsonPath))
4467
4713
  return null;
4468
- const parsed = JSON.parse(readFileSync8(projectJsonPath, "utf8"));
4714
+ const parsed = JSON.parse(readFileSync9(projectJsonPath, "utf8"));
4469
4715
  if (typeof parsed.projectId !== "string" || typeof parsed.resourceId !== "string")
4470
4716
  return null;
4471
4717
  const resourceIdsByExecutionTarget = parsed.resourceIdsByExecutionTarget && typeof parsed.resourceIdsByExecutionTarget === "object" && !Array.isArray(parsed.resourceIdsByExecutionTarget) ? Object.fromEntries(Object.entries(parsed.resourceIdsByExecutionTarget).filter((entry) => typeof entry[0] === "string" && entry[0].trim().length > 0 && typeof entry[1] === "string" && entry[1].trim().length > 0)) : {};
@@ -4495,11 +4741,11 @@ function readProjectJsonLink(projectJsonPath, options = {}) {
4495
4741
  };
4496
4742
  }
4497
4743
  function writeProjectJson({ directoryPath, projectId, resourceId, resourceKey, executionTargetId, isPrimary }) {
4498
- const overlordDir = path10.join(directoryPath, ".overlord");
4744
+ const overlordDir = path11.join(directoryPath, ".overlord");
4499
4745
  mkdirSync5(overlordDir, { recursive: true });
4500
- mkdirSync5(path10.join(overlordDir, "tmp"), { recursive: true });
4501
- mkdirSync5(path10.join(overlordDir, "logs"), { recursive: true });
4502
- const projectJsonPath = path10.join(overlordDir, "project.json");
4746
+ mkdirSync5(path11.join(overlordDir, "tmp"), { recursive: true });
4747
+ mkdirSync5(path11.join(overlordDir, "logs"), { recursive: true });
4748
+ const projectJsonPath = path11.join(overlordDir, "project.json");
4503
4749
  const existing = readProjectJsonLink(projectJsonPath);
4504
4750
  const resourceIdsByExecutionTarget = {
4505
4751
  ...existing?.projectId === projectId ? existing.resourceIdsByExecutionTarget : {}
@@ -4531,7 +4777,7 @@ var init_project_metadata = __esm({
4531
4777
 
4532
4778
  // ../packages/core/dist/repository/git-tree.js
4533
4779
  import { execFileSync } from "node:child_process";
4534
- import path11 from "node:path";
4780
+ import path12 from "node:path";
4535
4781
  function runGit(cwd, args) {
4536
4782
  try {
4537
4783
  return execFileSync("git", args, {
@@ -4554,17 +4800,17 @@ function gitPath(cwd, args) {
4554
4800
  }
4555
4801
  }
4556
4802
  function addDirectoryAncestors(paths, filePath) {
4557
- let parent = path11.posix.dirname(filePath);
4803
+ let parent = path12.posix.dirname(filePath);
4558
4804
  while (parent !== "." && parent !== "/") {
4559
4805
  paths.add(parent);
4560
- parent = path11.posix.dirname(parent);
4806
+ parent = path12.posix.dirname(parent);
4561
4807
  }
4562
4808
  }
4563
4809
  function toEntry(entryPath, type) {
4564
- const parentPath = path11.posix.dirname(entryPath);
4810
+ const parentPath = path12.posix.dirname(entryPath);
4565
4811
  return {
4566
4812
  path: entryPath,
4567
- name: path11.posix.basename(entryPath),
4813
+ name: path12.posix.basename(entryPath),
4568
4814
  type,
4569
4815
  parentPath: parentPath === "." ? null : parentPath,
4570
4816
  depth: entryPath.split("/").length - 1
@@ -4576,7 +4822,7 @@ function compareEntries(a, b) {
4576
4822
  return a.path < b.path ? -1 : 1;
4577
4823
  }
4578
4824
  function readRepositoryTree(rootPath, options = {}) {
4579
- const resolvedRoot = path11.resolve(rootPath);
4825
+ const resolvedRoot = path12.resolve(rootPath);
4580
4826
  const gitRoot = gitPath(resolvedRoot, ["rev-parse", "--show-toplevel"]);
4581
4827
  if (!gitRoot) {
4582
4828
  throw new RepositoryReadError("not_git_repository", `${resolvedRoot} is not inside a git repository.`);
@@ -4599,7 +4845,7 @@ function readRepositoryTree(rootPath, options = {}) {
4599
4845
  const truncated = entries.length > maxEntries;
4600
4846
  return {
4601
4847
  rootPath: resolvedRoot,
4602
- gitRoot: path11.resolve(gitRoot),
4848
+ gitRoot: path12.resolve(gitRoot),
4603
4849
  branch: gitPath(gitRoot, ["branch", "--show-current"]),
4604
4850
  commit: gitPath(gitRoot, ["rev-parse", "--short", "HEAD"]),
4605
4851
  entries: truncated ? entries.slice(0, maxEntries) : entries,
@@ -4660,13 +4906,13 @@ var init_git_run = __esm({
4660
4906
  });
4661
4907
 
4662
4908
  // ../packages/core/dist/service/local-target/worktree-git.js
4663
- import { existsSync as existsSync11, realpathSync } from "node:fs";
4664
- import path12 from "node:path";
4909
+ import { existsSync as existsSync12, realpathSync } from "node:fs";
4910
+ import path13 from "node:path";
4665
4911
  function resolveRealPath(targetPath) {
4666
4912
  try {
4667
4913
  return realpathSync.native(targetPath);
4668
4914
  } catch {
4669
- return path12.resolve(targetPath);
4915
+ return path13.resolve(targetPath);
4670
4916
  }
4671
4917
  }
4672
4918
  function worktreeIsDirty(worktreePath) {
@@ -4696,7 +4942,7 @@ function removeGitWorktree({ primaryRepoPath, worktreePath, force }) {
4696
4942
  args.push(worktreePath);
4697
4943
  runGitResult(primaryRepoPath, args);
4698
4944
  runGitResult(primaryRepoPath, ["worktree", "prune"]);
4699
- return !existsSync11(worktreePath);
4945
+ return !existsSync12(worktreePath);
4700
4946
  }
4701
4947
  function collectManagedWorktrees({ worktreeRoot, projects }) {
4702
4948
  const resolvedRoot = resolveRealPath(worktreeRoot);
@@ -4704,7 +4950,7 @@ function collectManagedWorktrees({ worktreeRoot, projects }) {
4704
4950
  const seen = /* @__PURE__ */ new Set();
4705
4951
  for (const project of projects) {
4706
4952
  const repoPath = project.primaryRepoPath;
4707
- if (!repoPath || !existsSync11(repoPath))
4953
+ if (!repoPath || !existsSync12(repoPath))
4708
4954
  continue;
4709
4955
  const out = runGitResult(repoPath, ["worktree", "list", "--porcelain"]);
4710
4956
  if (!out.ok)
@@ -4715,7 +4961,7 @@ function collectManagedWorktrees({ worktreeRoot, projects }) {
4715
4961
  if (!currentPath)
4716
4962
  return;
4717
4963
  const resolved = resolveRealPath(currentPath);
4718
- const underRoot = resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path12.sep);
4964
+ const underRoot = resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path13.sep);
4719
4965
  if (underRoot && !seen.has(resolved)) {
4720
4966
  seen.add(resolved);
4721
4967
  entries.push({
@@ -4743,7 +4989,7 @@ function collectManagedWorktrees({ worktreeRoot, projects }) {
4743
4989
  return entries;
4744
4990
  }
4745
4991
  function removeManagedWorktree({ path: worktreePath, primaryRepoPath, force }) {
4746
- const target = path12.resolve(worktreePath);
4992
+ const target = path13.resolve(worktreePath);
4747
4993
  if (!force && worktreeIsDirty(target)) {
4748
4994
  return {
4749
4995
  removed: [],
@@ -4757,7 +5003,7 @@ function purgeManagedWorktrees({ entries }) {
4757
5003
  const removed = [];
4758
5004
  const skipped = [];
4759
5005
  for (const entry of entries) {
4760
- const target = path12.resolve(entry.path);
5006
+ const target = path13.resolve(entry.path);
4761
5007
  if (worktreeIsDirty(target)) {
4762
5008
  skipped.push({ path: target, reason: "uncommitted changes" });
4763
5009
  continue;
@@ -4782,10 +5028,10 @@ var init_worktree_git = __esm({
4782
5028
  });
4783
5029
 
4784
5030
  // ../packages/core/dist/service/local-target/branch-actions-git.js
4785
- import { existsSync as existsSync12 } from "node:fs";
5031
+ import { existsSync as existsSync13 } from "node:fs";
4786
5032
  function integrateBranch(input) {
4787
5033
  const { branchName, baseBranch, worktreePath, primaryRepoPath } = input;
4788
- if (!existsSync12(worktreePath)) {
5034
+ if (!existsSync13(worktreePath)) {
4789
5035
  return {
4790
5036
  ok: false,
4791
5037
  code: "BRANCH_NO_WORKTREE",
@@ -4867,7 +5113,7 @@ function commitBranch(input) {
4867
5113
  message: "A commit message is required."
4868
5114
  };
4869
5115
  }
4870
- if (!existsSync12(worktreePath)) {
5116
+ if (!existsSync13(worktreePath)) {
4871
5117
  return {
4872
5118
  ok: false,
4873
5119
  code: "BRANCH_NO_WORKTREE",
@@ -4923,7 +5169,7 @@ function pushParent(input) {
4923
5169
  };
4924
5170
  }
4925
5171
  let summary = `Pushed ${baseBranch} to origin.`;
4926
- if (existsSync12(worktreePath) && !worktreeIsDirty(worktreePath)) {
5172
+ if (existsSync13(worktreePath) && !worktreeIsDirty(worktreePath)) {
4927
5173
  if (removeGitWorktree({ primaryRepoPath, worktreePath, force: false })) {
4928
5174
  summary += ` Removed the merged worktree for ${branchName}.`;
4929
5175
  }
@@ -4932,7 +5178,7 @@ function pushParent(input) {
4932
5178
  }
4933
5179
  function publishBranch(input) {
4934
5180
  const { branchName, worktreePath, primaryRepoPath } = input;
4935
- const repo = existsSync12(worktreePath) ? worktreePath : primaryRepoPath;
5181
+ const repo = existsSync13(worktreePath) ? worktreePath : primaryRepoPath;
4936
5182
  const push = runGitResult(repo, ["push", "-u", "origin", branchName]);
4937
5183
  if (!push.ok) {
4938
5184
  return {
@@ -4982,7 +5228,7 @@ var init_branch_status_git = __esm({
4982
5228
  });
4983
5229
 
4984
5230
  // ../packages/core/dist/service/local-target/commit-message-diff-git.js
4985
- import { existsSync as existsSync13 } from "node:fs";
5231
+ import { existsSync as existsSync14 } from "node:fs";
4986
5232
  function collectWorktreeChanges(worktreePath) {
4987
5233
  const status = runGitResult(worktreePath, ["status", "--porcelain"]);
4988
5234
  const diff = runGitResult(worktreePath, ["diff", "HEAD"]);
@@ -4998,7 +5244,7 @@ ${diff.stdout}`);
4998
5244
  return sections.join("\n\n");
4999
5245
  }
5000
5246
  function gatherCommitMessageDiff(worktreePath) {
5001
- if (!existsSync13(worktreePath)) {
5247
+ if (!existsSync14(worktreePath)) {
5002
5248
  return {
5003
5249
  ok: false,
5004
5250
  code: "BRANCH_NO_WORKTREE",
@@ -5059,7 +5305,7 @@ var init_result = __esm({
5059
5305
  });
5060
5306
 
5061
5307
  // ../packages/core/dist/service/local-target/in-process-provider.js
5062
- import { existsSync as existsSync14 } from "node:fs";
5308
+ import { existsSync as existsSync15 } from "node:fs";
5063
5309
  var InProcessProvider;
5064
5310
  var init_in_process_provider = __esm({
5065
5311
  "../packages/core/dist/service/local-target/in-process-provider.js"() {
@@ -5087,7 +5333,7 @@ var init_in_process_provider = __esm({
5087
5333
  */
5088
5334
  async observeResource(input) {
5089
5335
  const observedAt = (/* @__PURE__ */ new Date()).toISOString();
5090
- const state = existsSync14(input.path) ? "available" : "missing";
5336
+ const state = existsSync15(input.path) ? "available" : "missing";
5091
5337
  return ok(this.target, { state, observedAt });
5092
5338
  }
5093
5339
  /**
@@ -5334,7 +5580,7 @@ var init_local_target_mutation_runner = __esm({
5334
5580
  });
5335
5581
 
5336
5582
  // ../packages/core/dist/service/project-resource-key.js
5337
- import path13 from "node:path";
5583
+ import path14 from "node:path";
5338
5584
  function deriveProjectResourceKey({ resourceKey, label, directoryPath }) {
5339
5585
  const explicit = resourceKey?.trim();
5340
5586
  if (explicit)
@@ -5342,7 +5588,7 @@ function deriveProjectResourceKey({ resourceKey, label, directoryPath }) {
5342
5588
  const labelKey = label?.trim();
5343
5589
  if (labelKey)
5344
5590
  return slugify(labelKey);
5345
- return slugify(path13.basename(path13.resolve(directoryPath)));
5591
+ return slugify(path14.basename(path14.resolve(directoryPath)));
5346
5592
  }
5347
5593
  var init_project_resource_key = __esm({
5348
5594
  "../packages/core/dist/service/project-resource-key.js"() {
@@ -5352,7 +5598,7 @@ var init_project_resource_key = __esm({
5352
5598
  });
5353
5599
 
5354
5600
  // src/branch-planning.ts
5355
- import path14 from "node:path";
5601
+ import path15 from "node:path";
5356
5602
  function stripCombiningMarks(value) {
5357
5603
  return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
5358
5604
  }
@@ -5385,7 +5631,7 @@ function missionWorktreePath({
5385
5631
  branch
5386
5632
  }) {
5387
5633
  const key = resourceKey.trim() || "project";
5388
- return path14.join(worktreeRoot, projectSlug || "project", key, branchLeaf(branch));
5634
+ return path15.join(worktreeRoot, projectSlug || "project", key, branchLeaf(branch));
5389
5635
  }
5390
5636
  function normalizeRemoteRef(ref) {
5391
5637
  return ref.replace(/^origin\//, "").replace(/^refs\/heads\//, "").replace(/^refs\/remotes\/origin\//, "");
@@ -5460,9 +5706,9 @@ var init_branch_planning = __esm({
5460
5706
 
5461
5707
  // src/branch-preparation.ts
5462
5708
  import { execFileSync as execFileSync4 } from "node:child_process";
5463
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, statSync as statSync3 } from "node:fs";
5709
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, statSync as statSync3 } from "node:fs";
5464
5710
  import os3 from "node:os";
5465
- import path15 from "node:path";
5711
+ import path16 from "node:path";
5466
5712
  function runGit3(cwd, args, options = {}) {
5467
5713
  try {
5468
5714
  return execFileSync4("git", args, {
@@ -5483,7 +5729,7 @@ function lines(value) {
5483
5729
  function resolveGitRoot(workingDirectory) {
5484
5730
  const root = runGit3(workingDirectory, ["rev-parse", "--show-toplevel"]);
5485
5731
  if (!root) throw new Error(`${workingDirectory} is not inside a git repository.`);
5486
- return path15.resolve(root);
5732
+ return path16.resolve(root);
5487
5733
  }
5488
5734
  function repoDefaultBranch(gitRoot) {
5489
5735
  const symbolic = runGit3(gitRoot, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], {
@@ -5617,9 +5863,9 @@ function missionSequence(mission, missionId) {
5617
5863
  }
5618
5864
  function resolveWorktreeRoot() {
5619
5865
  const override = process.env.OVERLORD_WORKTREE_ROOT?.trim();
5620
- if (override) return path15.resolve(override);
5866
+ if (override) return path16.resolve(override);
5621
5867
  const home = process.env.OVLD_HOME?.trim() || process.env.OVERLORD_HOME?.trim();
5622
- return path15.join(home ? path15.resolve(home) : path15.join(os3.homedir(), ".ovld"), "worktrees");
5868
+ return path16.join(home ? path16.resolve(home) : path16.join(os3.homedir(), ".ovld"), "worktrees");
5623
5869
  }
5624
5870
  function ensureBranchRef(gitRoot, decision) {
5625
5871
  if (decision.action === "reuse") return;
@@ -5630,8 +5876,8 @@ function ensureBranchRef(gitRoot, decision) {
5630
5876
  }
5631
5877
  function ensureWorktree(gitRoot, decision) {
5632
5878
  ensureBranchRef(gitRoot, decision);
5633
- mkdirSync6(path15.dirname(decision.worktreePath), { recursive: true });
5634
- if (existsSync15(decision.worktreePath)) {
5879
+ mkdirSync6(path16.dirname(decision.worktreePath), { recursive: true });
5880
+ if (existsSync16(decision.worktreePath)) {
5635
5881
  if (!statSync3(decision.worktreePath).isDirectory()) {
5636
5882
  throw new Error(`Worktree path exists and is not a directory: ${decision.worktreePath}`);
5637
5883
  }
@@ -5772,7 +6018,7 @@ var init_branch_preparation = __esm({
5772
6018
  });
5773
6019
 
5774
6020
  // src/discover-project-local.ts
5775
- import path16 from "node:path";
6021
+ import path17 from "node:path";
5776
6022
  async function resolvePreferredExecutionTargetId({
5777
6023
  backend
5778
6024
  }) {
@@ -5789,9 +6035,9 @@ function discoverProjectJsonFromFilesystem({
5789
6035
  workingDirectory,
5790
6036
  preferredExecutionTargetId
5791
6037
  }) {
5792
- let current = path16.resolve(workingDirectory);
6038
+ let current = path17.resolve(workingDirectory);
5793
6039
  while (true) {
5794
- const projectJsonPath = path16.join(current, ".overlord", "project.json");
6040
+ const projectJsonPath = path17.join(current, ".overlord", "project.json");
5795
6041
  const raw = readProjectJsonLink(projectJsonPath, { preferredExecutionTargetId });
5796
6042
  if (raw) {
5797
6043
  return {
@@ -5801,7 +6047,7 @@ function discoverProjectJsonFromFilesystem({
5801
6047
  isPrimary: raw.isPrimary
5802
6048
  };
5803
6049
  }
5804
- const parent = path16.dirname(current);
6050
+ const parent = path17.dirname(current);
5805
6051
  if (parent === current) break;
5806
6052
  current = parent;
5807
6053
  }
@@ -5843,7 +6089,7 @@ async function discoverProjectOnClient({
5843
6089
  const resolvedLocal = localWithTarget ?? local;
5844
6090
  if (!resolvedLocal) {
5845
6091
  throw new CliError({
5846
- message: `No linked Overlord project found for ${path16.resolve(workingDirectory)}. Run \`ovld add-cwd\` or \`ovld create-project\`.`
6092
+ message: `No linked Overlord project found for ${path17.resolve(workingDirectory)}. Run \`ovld add-cwd\` or \`ovld create-project\`.`
5847
6093
  });
5848
6094
  }
5849
6095
  const project = await resolveProjectByIdOrName({ backend, projectRef: resolvedLocal.projectId });
@@ -6143,7 +6389,7 @@ var init_terminal_launcher = __esm({
6143
6389
  // src/launch.ts
6144
6390
  import { spawnSync as spawnSync2 } from "node:child_process";
6145
6391
  import { chmodSync as chmodSync3, writeFileSync as writeFileSync5 } from "node:fs";
6146
- import path17 from "node:path";
6392
+ import path18 from "node:path";
6147
6393
  function overlordLaunchEnv({
6148
6394
  backendUrl,
6149
6395
  missionId,
@@ -6275,7 +6521,7 @@ function launchScriptPath({
6275
6521
  executionRequestId
6276
6522
  }) {
6277
6523
  const requestPart = executionRequestId ? safeLaunchScriptPart(executionRequestId) : `${process.pid}`;
6278
- return path17.join(tmpDir, `launch-${safeLaunchScriptPart(displayId)}-${requestPart}.sh`);
6524
+ return path18.join(tmpDir, `launch-${safeLaunchScriptPart(displayId)}-${requestPart}.sh`);
6279
6525
  }
6280
6526
  async function buildLaunchPlan({
6281
6527
  runtime,
@@ -6284,7 +6530,7 @@ async function buildLaunchPlan({
6284
6530
  const context = await loadMissionContext({ runtime, missionId: options.missionId });
6285
6531
  pruneStaleProjectTmp({ workingDirectory: options.workingDirectory, create: true });
6286
6532
  const tmpDir = ensureProjectTmpDir(options.workingDirectory);
6287
- const contextFile = path17.join(
6533
+ const contextFile = path18.join(
6288
6534
  tmpDir,
6289
6535
  `mission-${context.displayId.replace(/[^a-zA-Z0-9_-]/g, "-")}.md`
6290
6536
  );
@@ -6394,16 +6640,16 @@ var init_launch = __esm({
6394
6640
 
6395
6641
  // src/native-session.ts
6396
6642
  import { createHash as createHash3 } from "node:crypto";
6397
- import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
6643
+ import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
6398
6644
  import { homedir } from "node:os";
6399
- import path18 from "node:path";
6645
+ import path19 from "node:path";
6400
6646
  function sessionCachePath({
6401
6647
  agent,
6402
6648
  missionId,
6403
6649
  workingDirectory
6404
6650
  }) {
6405
- const key = createHash3("sha256").update(`${path18.resolve(workingDirectory)}\0${missionId}\0${agent}`).digest("hex");
6406
- return path18.join(resolveGlobalDataDir(), "native-sessions", key);
6651
+ const key = createHash3("sha256").update(`${path19.resolve(workingDirectory)}\0${missionId}\0${agent}`).digest("hex");
6652
+ return path19.join(resolveGlobalDataDir(), "native-sessions", key);
6407
6653
  }
6408
6654
  function readCachedNativeSessionId({
6409
6655
  agent,
@@ -6412,20 +6658,20 @@ function readCachedNativeSessionId({
6412
6658
  }) {
6413
6659
  try {
6414
6660
  const filePath = sessionCachePath({ agent, missionId, workingDirectory });
6415
- if (!existsSync16(filePath)) return void 0;
6416
- const raw = JSON.parse(readFileSync9(filePath, "utf8"));
6661
+ if (!existsSync17(filePath)) return void 0;
6662
+ const raw = JSON.parse(readFileSync10(filePath, "utf8"));
6417
6663
  return typeof raw.externalSessionId === "string" && raw.externalSessionId.trim() ? raw.externalSessionId.trim() : void 0;
6418
6664
  } catch {
6419
6665
  return void 0;
6420
6666
  }
6421
6667
  }
6422
6668
  function detectCodexSessionIdFromDisk(workingDirectory) {
6423
- const sessionsDir = path18.join(homedir(), ".codex", "sessions");
6424
- if (!existsSync16(sessionsDir)) return void 0;
6669
+ const sessionsDir = path19.join(homedir(), ".codex", "sessions");
6670
+ if (!existsSync17(sessionsDir)) return void 0;
6425
6671
  const candidates = [];
6426
6672
  const visit = (dir) => {
6427
6673
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
6428
- const filePath = path18.join(dir, entry.name);
6674
+ const filePath = path19.join(dir, entry.name);
6429
6675
  if (entry.isDirectory()) {
6430
6676
  visit(filePath);
6431
6677
  continue;
@@ -6450,7 +6696,7 @@ function detectCodexSessionIdFromDisk(workingDirectory) {
6450
6696
  let nativeId;
6451
6697
  let cwd;
6452
6698
  try {
6453
- const [firstLine = ""] = readFileSync9(candidate.filePath, "utf8").split(/\r?\n/, 1);
6699
+ const [firstLine = ""] = readFileSync10(candidate.filePath, "utf8").split(/\r?\n/, 1);
6454
6700
  const obj = JSON.parse(firstLine);
6455
6701
  const payload = obj.payload && typeof obj.payload === "object" ? obj.payload : obj;
6456
6702
  if (typeof payload.id === "string") nativeId = payload.id.match(UUID_RE)?.[0];
@@ -6458,10 +6704,10 @@ function detectCodexSessionIdFromDisk(workingDirectory) {
6458
6704
  } catch {
6459
6705
  nativeId = void 0;
6460
6706
  }
6461
- nativeId ??= path18.basename(candidate.filePath).match(UUID_RE)?.[0];
6707
+ nativeId ??= path19.basename(candidate.filePath).match(UUID_RE)?.[0];
6462
6708
  if (!nativeId) continue;
6463
6709
  fallback ??= nativeId;
6464
- if (cwd && path18.resolve(cwd) === path18.resolve(workingDirectory)) return nativeId;
6710
+ if (cwd && path19.resolve(cwd) === path19.resolve(workingDirectory)) return nativeId;
6465
6711
  }
6466
6712
  return fallback;
6467
6713
  }
@@ -6498,8 +6744,8 @@ var init_native_session = __esm({
6498
6744
  });
6499
6745
 
6500
6746
  // src/org-setup.ts
6501
- import { readFileSync as readFileSync10 } from "node:fs";
6502
- import path19 from "node:path";
6747
+ import { readFileSync as readFileSync11 } from "node:fs";
6748
+ import path20 from "node:path";
6503
6749
  import { createInterface as createInterface3 } from "node:readline";
6504
6750
  function suggestSlug(name) {
6505
6751
  const cleaned = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -6578,12 +6824,12 @@ async function runOrgSetupCommand({
6578
6824
  let logoWarning = null;
6579
6825
  if (logoPath) {
6580
6826
  try {
6581
- const resolvedPath = path19.resolve(logoPath);
6582
- const bytes = readFileSync10(resolvedPath);
6827
+ const resolvedPath = path20.resolve(logoPath);
6828
+ const bytes = readFileSync11(resolvedPath);
6583
6829
  const stored = await runtime.backend.postRaw({
6584
6830
  path: "/api/uploads/organization-images",
6585
6831
  body: bytes,
6586
- filename: path19.basename(resolvedPath)
6832
+ filename: path20.basename(resolvedPath)
6587
6833
  });
6588
6834
  await runtime.backend.patch({
6589
6835
  path: `/api/organizations/${encodeURIComponent(organization.id)}`,
@@ -7017,17 +7263,17 @@ var init_protocol_help = __esm({
7017
7263
 
7018
7264
  // src/vcs-sessions.ts
7019
7265
  import { createHash as createHash4 } from "node:crypto";
7020
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
7021
- import path20 from "node:path";
7266
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
7267
+ import path21 from "node:path";
7022
7268
  function manifestPath2(workingDirectory) {
7023
- const key = createHash4("sha256").update(path20.resolve(workingDirectory)).digest("hex");
7024
- return path20.join(resolveGlobalDataDir(), "vcs-sessions", `${key}.json`);
7269
+ const key = createHash4("sha256").update(path21.resolve(workingDirectory)).digest("hex");
7270
+ return path21.join(resolveGlobalDataDir(), "vcs-sessions", `${key}.json`);
7025
7271
  }
7026
7272
  function readEntries(workingDirectory) {
7027
7273
  try {
7028
7274
  const target = manifestPath2(workingDirectory);
7029
- if (!existsSync17(target)) return [];
7030
- const raw = JSON.parse(readFileSync11(target, "utf8"));
7275
+ if (!existsSync18(target)) return [];
7276
+ const raw = JSON.parse(readFileSync12(target, "utf8"));
7031
7277
  if (!Array.isArray(raw.entries)) return [];
7032
7278
  const now = Date.now();
7033
7279
  return raw.entries.flatMap((entry) => {
@@ -7046,7 +7292,7 @@ function readEntries(workingDirectory) {
7046
7292
  function writeEntries(workingDirectory, entries) {
7047
7293
  try {
7048
7294
  const target = manifestPath2(workingDirectory);
7049
- mkdirSync7(path20.dirname(target), { recursive: true });
7295
+ mkdirSync7(path21.dirname(target), { recursive: true });
7050
7296
  writeFileSync6(target, JSON.stringify({ entries }));
7051
7297
  } catch {
7052
7298
  }
@@ -7100,20 +7346,20 @@ var init_vcs_sessions = __esm({
7100
7346
  import { execFileSync as execFileSync5 } from "node:child_process";
7101
7347
  import { createHash as createHash5 } from "node:crypto";
7102
7348
  import {
7103
- existsSync as existsSync18,
7349
+ existsSync as existsSync19,
7104
7350
  mkdirSync as mkdirSync8,
7105
7351
  readdirSync as readdirSync4,
7106
- readFileSync as readFileSync12,
7352
+ readFileSync as readFileSync13,
7107
7353
  realpathSync as realpathSync2,
7108
7354
  rmSync as rmSync3,
7109
7355
  writeFileSync as writeFileSync7
7110
7356
  } from "node:fs";
7111
- import path21 from "node:path";
7357
+ import path22 from "node:path";
7112
7358
  function normalizePath(filePath) {
7113
7359
  return filePath.replace(/\\/g, "/").trim();
7114
7360
  }
7115
7361
  function sameWorkingDirectory(left, right) {
7116
- return typeof left === "string" && path21.resolve(left) === path21.resolve(right);
7362
+ return typeof left === "string" && path22.resolve(left) === path22.resolve(right);
7117
7363
  }
7118
7364
  function parsePorcelainLine(line) {
7119
7365
  const trimmedEnd = line.replace(/\s+$/, "");
@@ -7164,13 +7410,13 @@ function sessionKeyHash({
7164
7410
  workingDirectory,
7165
7411
  missionId
7166
7412
  }) {
7167
- return createHash5("sha256").update(`${path21.resolve(workingDirectory)}\0${missionId}`).digest("hex");
7413
+ return createHash5("sha256").update(`${path22.resolve(workingDirectory)}\0${missionId}`).digest("hex");
7168
7414
  }
7169
7415
  function baselineFilePath({
7170
7416
  workingDirectory,
7171
7417
  missionId
7172
7418
  }) {
7173
- return path21.join(
7419
+ return path22.join(
7174
7420
  resolveGlobalDataDir(),
7175
7421
  "vcs-baselines",
7176
7422
  `${sessionKeyHash({ workingDirectory, missionId })}.json`
@@ -7180,7 +7426,7 @@ function touchedFilesPath({
7180
7426
  workingDirectory,
7181
7427
  missionId
7182
7428
  }) {
7183
- return path21.join(
7429
+ return path22.join(
7184
7430
  resolveGlobalDataDir(),
7185
7431
  "vcs-touched",
7186
7432
  `${sessionKeyHash({ workingDirectory, missionId })}.json`
@@ -7190,7 +7436,7 @@ function rationaleNotesPath({
7190
7436
  workingDirectory,
7191
7437
  missionId
7192
7438
  }) {
7193
- return path21.join(
7439
+ return path22.join(
7194
7440
  resolveGlobalDataDir(),
7195
7441
  "vcs-rationale-notes",
7196
7442
  `${sessionKeyHash({ workingDirectory, missionId })}.json`
@@ -7200,7 +7446,7 @@ function bashSnapshotPath({
7200
7446
  workingDirectory,
7201
7447
  missionId
7202
7448
  }) {
7203
- return path21.join(
7449
+ return path22.join(
7204
7450
  resolveGlobalDataDir(),
7205
7451
  "vcs-touched",
7206
7452
  `${sessionKeyHash({ workingDirectory, missionId })}.bash-snapshot.json`
@@ -7212,8 +7458,8 @@ function readBashSnapshot({
7212
7458
  }) {
7213
7459
  try {
7214
7460
  const target = bashSnapshotPath({ workingDirectory, missionId });
7215
- if (!existsSync18(target)) return /* @__PURE__ */ new Map();
7216
- const raw = JSON.parse(readFileSync12(target, "utf8"));
7461
+ if (!existsSync19(target)) return /* @__PURE__ */ new Map();
7462
+ const raw = JSON.parse(readFileSync13(target, "utf8"));
7217
7463
  const byPath = /* @__PURE__ */ new Map();
7218
7464
  if (Array.isArray(raw.files)) {
7219
7465
  for (const entry of raw.files) {
@@ -7238,7 +7484,7 @@ function writeBashSnapshot({
7238
7484
  }) {
7239
7485
  try {
7240
7486
  const target = bashSnapshotPath({ workingDirectory, missionId });
7241
- mkdirSync8(path21.dirname(target), { recursive: true });
7487
+ mkdirSync8(path22.dirname(target), { recursive: true });
7242
7488
  writeFileSync7(target, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString(), files }));
7243
7489
  } catch {
7244
7490
  }
@@ -7272,12 +7518,12 @@ function recordBashObservedChanges({
7272
7518
  }
7273
7519
  }
7274
7520
  function normalizeAbsolute(filePath) {
7275
- const resolved = path21.resolve(filePath);
7521
+ const resolved = path22.resolve(filePath);
7276
7522
  try {
7277
7523
  return realpathSync2(resolved).replace(/\\/g, "/");
7278
7524
  } catch {
7279
7525
  try {
7280
- return path21.join(realpathSync2(path21.dirname(resolved)), path21.basename(resolved)).replace(/\\/g, "/");
7526
+ return path22.join(realpathSync2(path22.dirname(resolved)), path22.basename(resolved)).replace(/\\/g, "/");
7281
7527
  } catch {
7282
7528
  return resolved.replace(/\\/g, "/");
7283
7529
  }
@@ -7304,7 +7550,7 @@ function resetTouchedFiles({
7304
7550
  touchedFilesPath({ workingDirectory, missionId }),
7305
7551
  bashSnapshotPath({ workingDirectory, missionId })
7306
7552
  ]) {
7307
- if (existsSync18(target)) rmSync3(target);
7553
+ if (existsSync19(target)) rmSync3(target);
7308
7554
  }
7309
7555
  } catch {
7310
7556
  }
@@ -7315,7 +7561,7 @@ function resetRationaleNotes({
7315
7561
  }) {
7316
7562
  try {
7317
7563
  const target = rationaleNotesPath({ workingDirectory, missionId });
7318
- if (existsSync18(target)) rmSync3(target);
7564
+ if (existsSync19(target)) rmSync3(target);
7319
7565
  } catch {
7320
7566
  }
7321
7567
  }
@@ -7330,8 +7576,8 @@ function readRationaleNotes({
7330
7576
  }) {
7331
7577
  try {
7332
7578
  const target = rationaleNotesPath({ workingDirectory, missionId });
7333
- if (!existsSync18(target)) return [];
7334
- const raw = JSON.parse(readFileSync12(target, "utf8"));
7579
+ if (!existsSync19(target)) return [];
7580
+ const raw = JSON.parse(readFileSync13(target, "utf8"));
7335
7581
  if (!Array.isArray(raw.notes)) return [];
7336
7582
  return raw.notes.flatMap((note) => {
7337
7583
  if (typeof note !== "object" || note === null || typeof note.filePath !== "string") {
@@ -7365,7 +7611,7 @@ function recordRationaleNotes({
7365
7611
  intent: truncateNoteText(note.intent),
7366
7612
  transcriptContext: truncateNoteText(note.transcriptContext)
7367
7613
  })).filter((note) => note.filePath).map((note) => {
7368
- const absolute = normalizeAbsolute(path21.resolve(workingDirectory, note.filePath));
7614
+ const absolute = normalizeAbsolute(path22.resolve(workingDirectory, note.filePath));
7369
7615
  return {
7370
7616
  filePath: absolute,
7371
7617
  toolName: note.toolName,
@@ -7373,14 +7619,14 @@ function recordRationaleNotes({
7373
7619
  transcriptContext: note.transcriptContext,
7374
7620
  contentHash: hashWorktreeFile({
7375
7621
  workingDirectory,
7376
- filePath: path21.relative(workingDirectory, absolute)
7622
+ filePath: path22.relative(workingDirectory, absolute)
7377
7623
  }),
7378
7624
  recordedAt: (/* @__PURE__ */ new Date()).toISOString()
7379
7625
  };
7380
7626
  });
7381
7627
  if (additions.length === 0) return;
7382
7628
  const target = rationaleNotesPath({ workingDirectory, missionId });
7383
- mkdirSync8(path21.dirname(target), { recursive: true });
7629
+ mkdirSync8(path22.dirname(target), { recursive: true });
7384
7630
  const prior = readRationaleNotes({ workingDirectory, missionId });
7385
7631
  const notesToKeep = [...prior, ...additions].slice(-200);
7386
7632
  writeFileSync7(
@@ -7396,16 +7642,16 @@ function recordTouchedFiles({
7396
7642
  files
7397
7643
  }) {
7398
7644
  try {
7399
- const additions = files.map((filePath) => (filePath ?? "").trim()).filter(Boolean).map((filePath) => normalizeAbsolute(path21.resolve(workingDirectory, filePath)));
7645
+ const additions = files.map((filePath) => (filePath ?? "").trim()).filter(Boolean).map((filePath) => normalizeAbsolute(path22.resolve(workingDirectory, filePath)));
7400
7646
  if (additions.length === 0) return;
7401
7647
  const target = touchedFilesPath({ workingDirectory, missionId });
7402
- mkdirSync8(path21.dirname(target), { recursive: true });
7648
+ mkdirSync8(path22.dirname(target), { recursive: true });
7403
7649
  const existing = readTouchedLog({ workingDirectory, missionId })?.paths ?? /* @__PURE__ */ new Set();
7404
7650
  for (const filePath of additions) existing.add(filePath);
7405
7651
  writeFileSync7(
7406
7652
  target,
7407
7653
  JSON.stringify({
7408
- workingDirectory: path21.resolve(workingDirectory),
7654
+ workingDirectory: path22.resolve(workingDirectory),
7409
7655
  missionId,
7410
7656
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7411
7657
  paths: Array.from(existing)
@@ -7418,7 +7664,7 @@ function hasTouchedFilesLog({
7418
7664
  workingDirectory,
7419
7665
  missionId
7420
7666
  }) {
7421
- return existsSync18(touchedFilesPath({ workingDirectory, missionId }));
7667
+ return existsSync19(touchedFilesPath({ workingDirectory, missionId }));
7422
7668
  }
7423
7669
  function readTouchedLog({
7424
7670
  workingDirectory,
@@ -7426,8 +7672,8 @@ function readTouchedLog({
7426
7672
  }) {
7427
7673
  try {
7428
7674
  const target = touchedFilesPath({ workingDirectory, missionId });
7429
- if (!existsSync18(target)) return null;
7430
- const raw = JSON.parse(readFileSync12(target, "utf8"));
7675
+ if (!existsSync19(target)) return null;
7676
+ const raw = JSON.parse(readFileSync13(target, "utf8"));
7431
7677
  const result = /* @__PURE__ */ new Set();
7432
7678
  if (Array.isArray(raw.paths)) {
7433
7679
  for (const entry of raw.paths) {
@@ -7460,9 +7706,9 @@ function writeBaseline({
7460
7706
  }) {
7461
7707
  try {
7462
7708
  const target = baselineFilePath({ workingDirectory, missionId });
7463
- mkdirSync8(path21.dirname(target), { recursive: true });
7709
+ mkdirSync8(path22.dirname(target), { recursive: true });
7464
7710
  const snapshot = {
7465
- workingDirectory: path21.resolve(workingDirectory),
7711
+ workingDirectory: path22.resolve(workingDirectory),
7466
7712
  missionId,
7467
7713
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
7468
7714
  files: snapshotBaselineFiles({ workingDirectory, files })
@@ -7479,13 +7725,13 @@ function readPeerTouchedClaims({
7479
7725
  readActiveSessions(workingDirectory).map((entry) => entry.missionId.trim()).filter(Boolean)
7480
7726
  );
7481
7727
  if (activeMissionIds.size === 0) return [];
7482
- const directory = path21.join(resolveGlobalDataDir(), "vcs-touched");
7483
- if (!existsSync18(directory)) return [];
7728
+ const directory = path22.join(resolveGlobalDataDir(), "vcs-touched");
7729
+ if (!existsSync19(directory)) return [];
7484
7730
  const claims = [];
7485
7731
  for (const name of readdirSync4(directory)) {
7486
7732
  if (!name.endsWith(".json") || name.endsWith(".bash-snapshot.json")) continue;
7487
7733
  try {
7488
- const raw = JSON.parse(readFileSync12(path21.join(directory, name), "utf8"));
7734
+ const raw = JSON.parse(readFileSync13(path22.join(directory, name), "utf8"));
7489
7735
  const candidateMissionId = typeof raw.missionId === "string" && raw.missionId.trim() ? raw.missionId.trim() : null;
7490
7736
  if (!candidateMissionId || candidateMissionId === missionId) continue;
7491
7737
  if (!activeMissionIds.has(candidateMissionId)) continue;
@@ -7510,8 +7756,8 @@ function readBaselineSnapshot({
7510
7756
  }) {
7511
7757
  try {
7512
7758
  const target = baselineFilePath({ workingDirectory, missionId });
7513
- if (!existsSync18(target)) return /* @__PURE__ */ new Map();
7514
- const raw = JSON.parse(readFileSync12(target, "utf8"));
7759
+ if (!existsSync19(target)) return /* @__PURE__ */ new Map();
7760
+ const raw = JSON.parse(readFileSync13(target, "utf8"));
7515
7761
  if (Array.isArray(raw.files)) {
7516
7762
  const byPath = /* @__PURE__ */ new Map();
7517
7763
  for (const entry of raw.files) {
@@ -7605,10 +7851,10 @@ function compileIgnorePattern(rawPattern) {
7605
7851
  }
7606
7852
  function loadIgnoreRules(repoRoot) {
7607
7853
  try {
7608
- const target = path21.join(repoRoot, OVERLORD_IGNORE_FILENAME);
7609
- if (!existsSync18(target)) return [];
7854
+ const target = path22.join(repoRoot, OVERLORD_IGNORE_FILENAME);
7855
+ if (!existsSync19(target)) return [];
7610
7856
  const rules = [];
7611
- for (const rawLine of readFileSync12(target, "utf8").split("\n")) {
7857
+ for (const rawLine of readFileSync13(target, "utf8").split("\n")) {
7612
7858
  const line = rawLine.replace(/\r$/, "").trim();
7613
7859
  if (!line || line.startsWith("#")) continue;
7614
7860
  const rule = compileIgnorePattern(line);
@@ -7642,7 +7888,7 @@ function filterRunAttributableChanges({
7642
7888
  return [];
7643
7889
  }
7644
7890
  if (!touched) return [{ filePath: entry.filePath, vcsStatus: entry.vcsStatus }];
7645
- const absolute = normalizeAbsolute(path21.resolve(repoRoot, entry.filePath));
7891
+ const absolute = normalizeAbsolute(path22.resolve(repoRoot, entry.filePath));
7646
7892
  if (touched.paths.has(absolute)) {
7647
7893
  return [
7648
7894
  {
@@ -7673,7 +7919,7 @@ function filterRunAttributableChanges({
7673
7919
  });
7674
7920
  }
7675
7921
  function titleFromPath(filePath) {
7676
- const base = path21.basename(filePath).replace(/\.[^.]+$/, "");
7922
+ const base = path22.basename(filePath).replace(/\.[^.]+$/, "");
7677
7923
  const words = base.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").trim();
7678
7924
  if (!words) return `Update ${filePath}`;
7679
7925
  return `Update ${words.replace(/\b\w/g, (char) => char.toUpperCase())}`;
@@ -7696,7 +7942,7 @@ function draftChangeRationalesFromNotes({
7696
7942
  if (notes.length === 0 || files.length === 0) return [];
7697
7943
  const repoRoot = gitRepoRoot(workingDirectory) ?? workingDirectory;
7698
7944
  return files.flatMap((file) => {
7699
- const absolute = normalizeAbsolute(path21.resolve(repoRoot, file.filePath));
7945
+ const absolute = normalizeAbsolute(path22.resolve(repoRoot, file.filePath));
7700
7946
  const currentHash = hashWorktreeFile({ workingDirectory, filePath: file.filePath });
7701
7947
  const note = latestRelevantNote({ notes, filePath: absolute, currentHash });
7702
7948
  if (!note) return [];
@@ -7736,8 +7982,8 @@ var init_vcs = __esm({
7736
7982
  });
7737
7983
 
7738
7984
  // src/record-touched.ts
7739
- import { readFileSync as readFileSync13 } from "node:fs";
7740
- import path22 from "node:path";
7985
+ import { readFileSync as readFileSync14 } from "node:fs";
7986
+ import path23 from "node:path";
7741
7987
  function collectCandidates(toolInput) {
7742
7988
  const counts = /* @__PURE__ */ new Map();
7743
7989
  const order = [];
@@ -7786,7 +8032,7 @@ function editIntent({
7786
8032
  function recentAssistantContext(transcriptPath) {
7787
8033
  if (typeof transcriptPath !== "string" || !transcriptPath.trim()) return null;
7788
8034
  try {
7789
- const lines2 = readFileSync13(transcriptPath, "utf8").split("\n").slice(-200);
8035
+ const lines2 = readFileSync14(transcriptPath, "utf8").split("\n").slice(-200);
7790
8036
  for (let index = lines2.length - 1; index >= 0; index -= 1) {
7791
8037
  const line = lines2[index]?.trim();
7792
8038
  if (!line) continue;
@@ -7836,7 +8082,7 @@ function recordTouchedFromPayload({
7836
8082
  const toolInput = toolInputRaw && typeof toolInputRaw === "object" ? toolInputRaw : {};
7837
8083
  const toolName = typeof payload.tool_name === "string" && payload.tool_name.trim() ? payload.tool_name.trim() : "file edit";
7838
8084
  const cwd = typeof payload.cwd === "string" && payload.cwd.trim() ? payload.cwd.trim() : fallbackCwd;
7839
- const workingDirectory = path22.resolve(cwd);
8085
+ const workingDirectory = path23.resolve(cwd);
7840
8086
  const trimmedOverride = missionOverride?.trim();
7841
8087
  let missionId;
7842
8088
  let ambiguous = false;
@@ -7940,9 +8186,9 @@ var init_resource_observations = __esm({
7940
8186
 
7941
8187
  // src/runner-service.ts
7942
8188
  import { execFile } from "node:child_process";
7943
- import { chmodSync as chmodSync4, existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
8189
+ import { chmodSync as chmodSync4, existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
7944
8190
  import os4 from "node:os";
7945
- import path23 from "node:path";
8191
+ import path24 from "node:path";
7946
8192
  import { promisify } from "node:util";
7947
8193
  function writeServiceFile(filePath, contents) {
7948
8194
  writeFileSync8(filePath, contents, { mode: 384 });
@@ -7964,13 +8210,13 @@ function emptyRunnerServiceState() {
7964
8210
  };
7965
8211
  }
7966
8212
  function runnerServiceStatePath(dataDir = resolveGlobalDataDir()) {
7967
- return path23.join(dataDir, RUNNER_SERVICE_STATE_FILENAME);
8213
+ return path24.join(dataDir, RUNNER_SERVICE_STATE_FILENAME);
7968
8214
  }
7969
8215
  function readRunnerServiceState(dataDir = resolveGlobalDataDir()) {
7970
8216
  const filePath = runnerServiceStatePath(dataDir);
7971
- if (!existsSync19(filePath)) return emptyRunnerServiceState();
8217
+ if (!existsSync20(filePath)) return emptyRunnerServiceState();
7972
8218
  try {
7973
- const parsed = JSON.parse(readFileSync14(filePath, "utf8"));
8219
+ const parsed = JSON.parse(readFileSync15(filePath, "utf8"));
7974
8220
  return { ...emptyRunnerServiceState(), ...parsed };
7975
8221
  } catch {
7976
8222
  return emptyRunnerServiceState();
@@ -7986,14 +8232,33 @@ function patchRunnerServiceState(patch, dataDir = resolveGlobalDataDir()) {
7986
8232
  writeRunnerServiceState(next, dataDir);
7987
8233
  return next;
7988
8234
  }
8235
+ function runnerFocusStatePath(dataDir = resolveGlobalDataDir()) {
8236
+ return path24.join(dataDir, RUNNER_FOCUS_STATE_FILENAME);
8237
+ }
8238
+ function readDesktopFocusState(dataDir = resolveGlobalDataDir()) {
8239
+ const filePath = runnerFocusStatePath(dataDir);
8240
+ if (!existsSync20(filePath)) return { lastFocusedAt: null };
8241
+ try {
8242
+ const parsed = JSON.parse(readFileSync15(filePath, "utf8"));
8243
+ return { lastFocusedAt: parsed.lastFocusedAt ?? null };
8244
+ } catch {
8245
+ return { lastFocusedAt: null };
8246
+ }
8247
+ }
8248
+ function withinWindow(timestamp, now, windowMs) {
8249
+ if (!timestamp) return false;
8250
+ const ms = Date.parse(timestamp);
8251
+ if (Number.isNaN(ms)) return false;
8252
+ return now - ms <= windowMs;
8253
+ }
7989
8254
  function selectBasePollIntervalMs({
7990
8255
  lastLaunchedAt,
8256
+ lastDesktopFocusAt = null,
7991
8257
  now
7992
8258
  }) {
7993
- if (!lastLaunchedAt) return SLOW_POLL_INTERVAL_MS;
7994
- const launchedMs = Date.parse(lastLaunchedAt);
7995
- if (Number.isNaN(launchedMs)) return SLOW_POLL_INTERVAL_MS;
7996
- return now - launchedMs <= IDLE_BACKOFF_MS ? FAST_POLL_INTERVAL_MS : SLOW_POLL_INTERVAL_MS;
8259
+ const jobActive = withinWindow(lastLaunchedAt, now, IDLE_BACKOFF_MS);
8260
+ const focusActive = withinWindow(lastDesktopFocusAt, now, DESKTOP_FOCUS_WINDOW_MS);
8261
+ return jobActive || focusActive ? FAST_POLL_INTERVAL_MS : SLOW_POLL_INTERVAL_MS;
7997
8262
  }
7998
8263
  function applyPollJitter(intervalMs, random = Math.random) {
7999
8264
  const spread = intervalMs * 0.1;
@@ -8005,16 +8270,16 @@ function nextRunnerLastError(pollError) {
8005
8270
  function resolveOverlordAppInvocation({
8006
8271
  platform: platform2 = process.platform,
8007
8272
  homedir: homedir2 = os4.homedir(),
8008
- exists = existsSync19
8273
+ exists = existsSync20
8009
8274
  } = {}) {
8010
8275
  if (platform2 !== "darwin") return null;
8011
8276
  const appDirs = [
8012
8277
  "/Applications/Overlord.app",
8013
- path23.join(homedir2, "Applications", "Overlord.app")
8278
+ path24.join(homedir2, "Applications", "Overlord.app")
8014
8279
  ];
8015
8280
  for (const appDir of appDirs) {
8016
- const program = path23.join(appDir, "Contents", "MacOS", "Overlord");
8017
- const script = path23.join(appDir, "Contents", "Resources", "cli", "bin", "ovld.mjs");
8281
+ const program = path24.join(appDir, "Contents", "MacOS", "Overlord");
8282
+ const script = path24.join(appDir, "Contents", "Resources", "cli", "bin", "ovld.mjs");
8018
8283
  if (exists(program) && exists(script)) {
8019
8284
  return { program, args: [script, "runner", "supervise"], runAsElectronNode: true };
8020
8285
  }
@@ -8026,7 +8291,7 @@ function resolveOvldInvocation(argv = process.argv, execPath = process.execPath)
8026
8291
  if (override) {
8027
8292
  return { program: override, args: ["runner", "supervise"] };
8028
8293
  }
8029
- const scriptPath = argv[1] ? path23.resolve(argv[1]) : "";
8294
+ const scriptPath = argv[1] ? path24.resolve(argv[1]) : "";
8030
8295
  if (process.env.ELECTRON_RUN_AS_NODE) {
8031
8296
  return scriptPath ? { program: execPath, args: [scriptPath, "runner", "supervise"], runAsElectronNode: true } : { program: execPath, args: ["runner", "supervise"], runAsElectronNode: true };
8032
8297
  }
@@ -8059,7 +8324,7 @@ function buildRunnerServiceEnv({
8059
8324
  PATH: process.env.PATH?.trim() || "/usr/local/bin:/usr/bin:/bin"
8060
8325
  };
8061
8326
  const home = overlordHome?.trim() || process.env.OVLD_HOME?.trim();
8062
- if (home) env.OVLD_HOME = path23.resolve(home);
8327
+ if (home) env.OVLD_HOME = path24.resolve(home);
8063
8328
  const token = process.env.OVERLORD_USER_TOKEN?.trim() || process.env.OVLD_USER_TOKEN?.trim();
8064
8329
  if (token) env.OVERLORD_USER_TOKEN = token;
8065
8330
  if (runAsElectronNode || process.env.ELECTRON_RUN_AS_NODE) env.ELECTRON_RUN_AS_NODE = "1";
@@ -8098,9 +8363,9 @@ ${envEntries}
8098
8363
  <key>KeepAlive</key>
8099
8364
  <true/>
8100
8365
  <key>StandardOutPath</key>
8101
- <string>${xmlEscape(path23.join(logDir, "runner-service.out.log"))}</string>
8366
+ <string>${xmlEscape(path24.join(logDir, "runner-service.out.log"))}</string>
8102
8367
  <key>StandardErrorPath</key>
8103
- <string>${xmlEscape(path23.join(logDir, "runner-service.err.log"))}</string>
8368
+ <string>${xmlEscape(path24.join(logDir, "runner-service.err.log"))}</string>
8104
8369
  <key>ProcessType</key>
8105
8370
  <string>Background</string>
8106
8371
  </dict>
@@ -8131,12 +8396,12 @@ WantedBy=default.target
8131
8396
  `;
8132
8397
  }
8133
8398
  function launchAgentsDir() {
8134
- return path23.join(os4.homedir(), "Library", "LaunchAgents");
8399
+ return path24.join(os4.homedir(), "Library", "LaunchAgents");
8135
8400
  }
8136
8401
  function systemdUserDir() {
8137
8402
  const xdg = process.env.XDG_CONFIG_HOME?.trim();
8138
- const base = xdg ? path23.resolve(xdg) : path23.join(os4.homedir(), ".config");
8139
- return path23.join(base, "systemd", "user");
8403
+ const base = xdg ? path24.resolve(xdg) : path24.join(os4.homedir(), ".config");
8404
+ return path24.join(base, "systemd", "user");
8140
8405
  }
8141
8406
  async function runCommand(program, args) {
8142
8407
  const { stdout } = await execFileAsync(program, args);
@@ -8147,35 +8412,37 @@ function resolveServiceManager(platform2 = process.platform) {
8147
8412
  if (platform2 === "linux") return new SystemdUserManager();
8148
8413
  return null;
8149
8414
  }
8150
- var execFileAsync, RUNNER_SERVICE_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FAST_POLL_INTERVAL_MS, SLOW_POLL_INTERVAL_MS, IDLE_BACKOFF_MS, LaunchdManager, SystemdUserManager;
8415
+ var execFileAsync, RUNNER_SERVICE_STATE_FILENAME, RUNNER_FOCUS_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FAST_POLL_INTERVAL_MS, SLOW_POLL_INTERVAL_MS, IDLE_BACKOFF_MS, DESKTOP_FOCUS_WINDOW_MS, LaunchdManager, SystemdUserManager;
8151
8416
  var init_runner_service = __esm({
8152
8417
  "src/runner-service.ts"() {
8153
8418
  "use strict";
8154
8419
  init_config();
8155
8420
  execFileAsync = promisify(execFile);
8156
8421
  RUNNER_SERVICE_STATE_FILENAME = "runner-service.json";
8422
+ RUNNER_FOCUS_STATE_FILENAME = "runner-focus.json";
8157
8423
  LAUNCHD_LABEL = "io.overlord.runner";
8158
8424
  SYSTEMD_UNIT_NAME = "overlord-runner";
8159
8425
  FAST_POLL_INTERVAL_MS = 3e3;
8160
- SLOW_POLL_INTERVAL_MS = 1e4;
8426
+ SLOW_POLL_INTERVAL_MS = 5e3;
8161
8427
  IDLE_BACKOFF_MS = 2 * 60 * 60 * 1e3;
8428
+ DESKTOP_FOCUS_WINDOW_MS = 30 * 60 * 1e3;
8162
8429
  LaunchdManager = class {
8163
8430
  kind = "launchd";
8164
8431
  identifier = LAUNCHD_LABEL;
8165
8432
  unitPath() {
8166
- return path23.join(launchAgentsDir(), `${LAUNCHD_LABEL}.plist`);
8433
+ return path24.join(launchAgentsDir(), `${LAUNCHD_LABEL}.plist`);
8167
8434
  }
8168
8435
  render(opts) {
8169
8436
  return renderLaunchdPlist({
8170
8437
  label: LAUNCHD_LABEL,
8171
8438
  invocation: opts.invocation,
8172
8439
  env: opts.env,
8173
- logDir: path23.join(resolveGlobalDataDir(), "logs")
8440
+ logDir: path24.join(resolveGlobalDataDir(), "logs")
8174
8441
  });
8175
8442
  }
8176
8443
  async install(opts) {
8177
8444
  mkdirSync9(launchAgentsDir(), { recursive: true });
8178
- mkdirSync9(path23.join(resolveGlobalDataDir(), "logs"), { recursive: true });
8445
+ mkdirSync9(path24.join(resolveGlobalDataDir(), "logs"), { recursive: true });
8179
8446
  writeServiceFile(this.unitPath(), this.render(opts));
8180
8447
  if (opts.autoStart) await this.start();
8181
8448
  }
@@ -8205,10 +8472,10 @@ var init_runner_service = __esm({
8205
8472
  await this.stop();
8206
8473
  } catch {
8207
8474
  }
8208
- if (existsSync19(this.unitPath())) rmSync4(this.unitPath());
8475
+ if (existsSync20(this.unitPath())) rmSync4(this.unitPath());
8209
8476
  }
8210
8477
  async status() {
8211
- const installed = existsSync19(this.unitPath());
8478
+ const installed = existsSync20(this.unitPath());
8212
8479
  try {
8213
8480
  const { stdout } = await runCommand("launchctl", ["list"]);
8214
8481
  const line = stdout.split("\n").find((row) => row.includes(LAUNCHD_LABEL));
@@ -8224,7 +8491,7 @@ var init_runner_service = __esm({
8224
8491
  kind = "systemd";
8225
8492
  identifier = `${SYSTEMD_UNIT_NAME}.service`;
8226
8493
  unitPath() {
8227
- return path23.join(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
8494
+ return path24.join(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
8228
8495
  }
8229
8496
  render(opts) {
8230
8497
  return renderSystemdUnit(opts);
@@ -8251,14 +8518,14 @@ var init_runner_service = __esm({
8251
8518
  await runCommand("systemctl", ["--user", "disable", this.identifier]);
8252
8519
  } catch {
8253
8520
  }
8254
- if (existsSync19(this.unitPath())) rmSync4(this.unitPath());
8521
+ if (existsSync20(this.unitPath())) rmSync4(this.unitPath());
8255
8522
  try {
8256
8523
  await runCommand("systemctl", ["--user", "daemon-reload"]);
8257
8524
  } catch {
8258
8525
  }
8259
8526
  }
8260
8527
  async status() {
8261
- const installed = existsSync19(this.unitPath());
8528
+ const installed = existsSync20(this.unitPath());
8262
8529
  try {
8263
8530
  const { stdout } = await runCommand("systemctl", ["--user", "is-active", this.identifier]);
8264
8531
  return { installed, running: stdout.trim() === "active" ? "running" : "stopped" };
@@ -8325,14 +8592,14 @@ var init_select_prompt = __esm({
8325
8592
 
8326
8593
  // src/session-key.ts
8327
8594
  import { createHash as createHash6 } from "node:crypto";
8328
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
8329
- import path24 from "node:path";
8595
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync16, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
8596
+ import path25 from "node:path";
8330
8597
  function sessionKeyCachePath({
8331
8598
  missionId,
8332
8599
  workingDirectory
8333
8600
  }) {
8334
- const key = createHash6("sha256").update(`${path24.resolve(workingDirectory)}\0${missionId}`).digest("hex");
8335
- return path24.join(resolveGlobalDataDir(), "protocol-session-keys", key);
8601
+ const key = createHash6("sha256").update(`${path25.resolve(workingDirectory)}\0${missionId}`).digest("hex");
8602
+ return path25.join(resolveGlobalDataDir(), "protocol-session-keys", key);
8336
8603
  }
8337
8604
  function readCachedSessionKey({
8338
8605
  missionId,
@@ -8340,8 +8607,8 @@ function readCachedSessionKey({
8340
8607
  }) {
8341
8608
  try {
8342
8609
  const filePath = sessionKeyCachePath({ missionId, workingDirectory });
8343
- if (!existsSync20(filePath)) return void 0;
8344
- const raw = JSON.parse(readFileSync15(filePath, "utf8"));
8610
+ if (!existsSync21(filePath)) return void 0;
8611
+ const raw = JSON.parse(readFileSync16(filePath, "utf8"));
8345
8612
  return typeof raw.sessionKey === "string" && raw.sessionKey.trim() ? raw.sessionKey.trim() : void 0;
8346
8613
  } catch {
8347
8614
  return void 0;
@@ -8356,7 +8623,7 @@ function writeCachedSessionKey({
8356
8623
  const trimmed = sessionKey.trim();
8357
8624
  if (!trimmed) return;
8358
8625
  const filePath = sessionKeyCachePath({ missionId, workingDirectory });
8359
- mkdirSync10(path24.dirname(filePath), { recursive: true });
8626
+ mkdirSync10(path25.dirname(filePath), { recursive: true });
8360
8627
  writeFileSync9(
8361
8628
  filePath,
8362
8629
  JSON.stringify({ sessionKey: trimmed, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }),
@@ -8371,7 +8638,7 @@ function clearCachedSessionKey({
8371
8638
  }) {
8372
8639
  try {
8373
8640
  const filePath = sessionKeyCachePath({ missionId, workingDirectory });
8374
- if (existsSync20(filePath)) rmSync5(filePath);
8641
+ if (existsSync21(filePath)) rmSync5(filePath);
8375
8642
  } catch {
8376
8643
  }
8377
8644
  }
@@ -8389,9 +8656,9 @@ __export(commands_exports, {
8389
8656
  runManagementCommand: () => runManagementCommand,
8390
8657
  runProtocolCommand: () => runProtocolCommand
8391
8658
  });
8392
- import { existsSync as existsSync21, readFileSync as readFileSync16 } from "node:fs";
8659
+ import { existsSync as existsSync22, readFileSync as readFileSync17 } from "node:fs";
8393
8660
  import os5 from "node:os";
8394
- import path25 from "node:path";
8661
+ import path26 from "node:path";
8395
8662
  function writeProjectJsonFromResource({
8396
8663
  directory,
8397
8664
  projectId,
@@ -8425,7 +8692,7 @@ function readChangedFilesFromFlags(flags, stdin) {
8425
8692
  if (typeof fileFlag === "string") {
8426
8693
  const raw = fileFlag === "-" ? stdin ?? "" : (() => {
8427
8694
  try {
8428
- return readFileSync16(fileFlag, "utf8");
8695
+ return readFileSync17(fileFlag, "utf8");
8429
8696
  } catch {
8430
8697
  return "";
8431
8698
  }
@@ -8477,7 +8744,7 @@ function readJsonFlagContent({
8477
8744
  if (typeof filePath === "string") {
8478
8745
  if (fileInputs[fileFlag] !== void 0) return fileInputs[fileFlag];
8479
8746
  try {
8480
- return readFileSync16(filePath, "utf8");
8747
+ return readFileSync17(filePath, "utf8");
8481
8748
  } catch {
8482
8749
  return void 0;
8483
8750
  }
@@ -8768,7 +9035,7 @@ async function resolveProtocolFileInputs({
8768
9035
  } else if (process.stdin.isTTY) {
8769
9036
  stdinContent = "";
8770
9037
  } else {
8771
- stdinContent = readFileSync16(0, "utf8");
9038
+ stdinContent = readFileSync17(0, "utf8");
8772
9039
  }
8773
9040
  return stdinContent;
8774
9041
  };
@@ -8782,7 +9049,7 @@ async function resolveProtocolFileInputs({
8782
9049
  fileInputs[flagName] = content;
8783
9050
  stdinPayload = content;
8784
9051
  } else {
8785
- fileInputs[flagName] = readFileSync16(filePath, "utf8");
9052
+ fileInputs[flagName] = readFileSync17(filePath, "utf8");
8786
9053
  }
8787
9054
  }
8788
9055
  if (stdinPayload === void 0 && stdin !== void 0 && Object.keys(fileInputs).length === 0) {
@@ -8825,7 +9092,7 @@ async function runProtocolCommand({
8825
9092
  });
8826
9093
  if (subcommand === "record-touched") {
8827
9094
  const missionOverride = flagValue(parsed.flags, "--mission-id") ?? process.env.MISSION_ID ?? process.env.OVERLORD_MISSION_ID;
8828
- const rawPayload = stdin ?? (process.stdin.isTTY ? "" : readFileSync16(0, "utf8"));
9095
+ const rawPayload = stdin ?? (process.stdin.isTTY ? "" : readFileSync17(0, "utf8"));
8829
9096
  const result2 = recordTouchedFromPayload({
8830
9097
  rawPayload,
8831
9098
  missionOverride,
@@ -8940,7 +9207,7 @@ async function runProtocolCommand({
8940
9207
  if (subcommand === "deliver" && missionId) {
8941
9208
  clearCachedSessionKey({ missionId, workingDirectory });
8942
9209
  removeActiveSession({ workingDirectory, missionId });
8943
- const connectorInstallsEditHook = existsSync21(path25.join(os5.homedir(), ".claude"));
9210
+ const connectorInstallsEditHook = existsSync22(path26.join(os5.homedir(), ".claude"));
8944
9211
  if (connectorInstallsEditHook && !hasTouchedFilesLog({ workingDirectory, missionId }) && readChangedFiles(workingDirectory).length > 0) {
8945
9212
  const warning = `[overlord] warning: no touched-files log found at deliver for mission ${missionId} in ${workingDirectory}, even though this connector installs a PostToolUse edit hook. Deliver is falling back to baseline-only attribution, which can misattribute concurrent sessions' edits. Check ~/.ovld/logs/post-tool-use-hook.log for why the hook did not record any touched files.`;
8946
9213
  console.error(warning);
@@ -9031,7 +9298,7 @@ async function runManagementCommand({
9031
9298
  case "add-cwd": {
9032
9299
  const directory = flagValue(parsed.flags, "--directory") ?? process.cwd();
9033
9300
  const existingProjectJson = readProjectJsonLink(
9034
- path25.join(directory, ".overlord", "project.json")
9301
+ path26.join(directory, ".overlord", "project.json")
9035
9302
  );
9036
9303
  const resourceKey = flagValue(parsed.flags, "--key") ?? existingProjectJson?.resourceKey;
9037
9304
  let projectId = flagValue(parsed.flags, "--project-id");
@@ -9266,13 +9533,13 @@ async function runManagementCommand({
9266
9533
  rationales: `/api/missions/${encodeURIComponent(missionId)}/file-changes`,
9267
9534
  deliveries: `/api/missions/${encodeURIComponent(missionId)}/events`
9268
9535
  };
9269
- const path26 = sub ? pathBySub[sub] : void 0;
9270
- if (!path26) {
9536
+ const path27 = sub ? pathBySub[sub] : void 0;
9537
+ if (!path27) {
9271
9538
  throw new CliError({
9272
9539
  message: "Usage: ovld mission context|events|deliveries|artifacts|rationales <missionId> [--json]"
9273
9540
  });
9274
9541
  }
9275
- const result = await runtime.backend.get(path26);
9542
+ const result = await runtime.backend.get(path27);
9276
9543
  if (json || sub === "context") printJson(result);
9277
9544
  else if (Array.isArray(result)) for (const row of result) console.log(JSON.stringify(row));
9278
9545
  else printJson(result);
@@ -9468,8 +9735,10 @@ async function runRunnerSupervisor({
9468
9735
  lastError = error instanceof Error ? error.message : String(error);
9469
9736
  }
9470
9737
  const state = readRunnerServiceState();
9738
+ const focus = readDesktopFocusState();
9471
9739
  const base = selectBasePollIntervalMs({
9472
9740
  lastLaunchedAt: launched ? now.toISOString() : state.lastLaunchedAt,
9741
+ lastDesktopFocusAt: focus.lastFocusedAt,
9473
9742
  now: now.getTime()
9474
9743
  });
9475
9744
  patchRunnerServiceState({
@@ -9637,8 +9906,129 @@ var init_commands = __esm({
9637
9906
  });
9638
9907
 
9639
9908
  // src/index.ts
9909
+ init_args();
9640
9910
  init_errors();
9641
9911
 
9912
+ // src/flag-registry.ts
9913
+ init_errors();
9914
+ var GLOBAL_FLAGS = ["--json", "--help", "-h"];
9915
+ var LAUNCH_FLAGS = [
9916
+ "--agent",
9917
+ "--mission-id",
9918
+ "--objective-id",
9919
+ "--working-directory",
9920
+ "--terminal",
9921
+ "--no-terminal",
9922
+ "--dry-run",
9923
+ "--branch",
9924
+ "--no-worktree",
9925
+ "--model",
9926
+ "--thinking",
9927
+ "--flag",
9928
+ "--pre-command"
9929
+ ];
9930
+ var COMMAND_FLAGS = {
9931
+ version: [],
9932
+ update: ["--check", "--force", "--no-fund"],
9933
+ serve: ["--host", "--port", "--db"],
9934
+ init: [],
9935
+ doctor: [],
9936
+ setup: [],
9937
+ "agent-setup": ["--dry-run", "--home"],
9938
+ config: ["--path", "--url"],
9939
+ auth: ["--token", "--organization-id"],
9940
+ "user-token": ["--label", "--expires-in", "--no-expiry", "--scope", "--id"],
9941
+ prune: [],
9942
+ "create-project": ["--name", "--directory", "--no-directory"],
9943
+ "org-setup": [
9944
+ "--org-name",
9945
+ "--workspace-name",
9946
+ "--workspace-slug",
9947
+ "--logo",
9948
+ "--no-input",
9949
+ "--if-needed"
9950
+ ],
9951
+ "add-cwd": ["--directory", "--key", "--project-id", "--primary"],
9952
+ "add-url": ["--url", "--project-id", "--key", "--primary"],
9953
+ create: ["--objectives-json", "--objective", "--prompt", "--project-id", "--title", "--resource"],
9954
+ prompt: [
9955
+ "--objectives-json",
9956
+ "--objective",
9957
+ "--prompt",
9958
+ "--project-id",
9959
+ "--title",
9960
+ "--resource",
9961
+ "--agent"
9962
+ ],
9963
+ attach: ["--mission-id", "--agent", "--objective-id", "--model", "--thinking"],
9964
+ execution: ["--mission-id", "--agent", "--objective-id", "--model", "--thinking"],
9965
+ launch: LAUNCH_FLAGS,
9966
+ restart: LAUNCH_FLAGS,
9967
+ run: LAUNCH_FLAGS,
9968
+ connect: LAUNCH_FLAGS,
9969
+ resume: LAUNCH_FLAGS,
9970
+ runner: [
9971
+ "--project-id",
9972
+ "--poll-interval-ms",
9973
+ "--dry-run",
9974
+ "--branch",
9975
+ "--no-worktree",
9976
+ "--terminal",
9977
+ "--no-terminal",
9978
+ "--no-start"
9979
+ ],
9980
+ missions: ["--status", "--query", "--project-id", "--limit"],
9981
+ mission: [],
9982
+ changes: ["--mission-id", "--objective-id"]
9983
+ };
9984
+ function editDistance(a, b) {
9985
+ let prev = Array.from({ length: b.length + 1 }, (_unused, j) => j);
9986
+ for (let i = 1; i <= a.length; i += 1) {
9987
+ const curr = new Array(b.length + 1);
9988
+ curr[0] = i;
9989
+ for (let j = 1; j <= b.length; j += 1) {
9990
+ const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
9991
+ curr[j] = Math.min((curr[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
9992
+ }
9993
+ prev = curr;
9994
+ }
9995
+ return prev[b.length] ?? 0;
9996
+ }
9997
+ function suggestFlag(unknown, allowed) {
9998
+ let best;
9999
+ let bestDistance = Infinity;
10000
+ for (const candidate of allowed) {
10001
+ const distance = editDistance(unknown, candidate);
10002
+ if (distance < bestDistance) {
10003
+ bestDistance = distance;
10004
+ best = candidate;
10005
+ }
10006
+ }
10007
+ const threshold = Math.max(2, Math.floor(unknown.length / 3));
10008
+ return best !== void 0 && bestDistance <= threshold ? best : void 0;
10009
+ }
10010
+ function assertKnownFlags({
10011
+ command,
10012
+ flags,
10013
+ primaryCommand
10014
+ }) {
10015
+ const allowed = COMMAND_FLAGS[command];
10016
+ if (!allowed) return;
10017
+ const allowedSet = /* @__PURE__ */ new Set([...GLOBAL_FLAGS, ...allowed]);
10018
+ const unknown = [...flags.keys()].filter((flag) => flag.startsWith("--") && !allowedSet.has(flag));
10019
+ if (unknown.length === 0) return;
10020
+ const suggestionPool = [...GLOBAL_FLAGS, ...allowed];
10021
+ const details = unknown.map((flag) => {
10022
+ const suggestion = suggestFlag(flag, suggestionPool);
10023
+ return suggestion ? `${flag} (did you mean ${suggestion}?)` : flag;
10024
+ }).join(", ");
10025
+ const label = unknown.length === 1 ? "Unknown flag" : "Unknown flags";
10026
+ throw new CliError({
10027
+ message: `${label} for \`${primaryCommand} ${command}\`: ${details}
10028
+ Run \`${primaryCommand} help\` for usage.`
10029
+ });
10030
+ }
10031
+
9642
10032
  // src/help.ts
9643
10033
  function printHelp({ primaryCommand }) {
9644
10034
  console.log(`Overlord CLI
@@ -9663,6 +10053,8 @@ General:
9663
10053
  ${primaryCommand} serve [--host <h>] [--port <p>] [--db <path>] [--json]
9664
10054
  Boot the web/REST server (creates + migrates the DB on first run)
9665
10055
  ${primaryCommand} doctor [--json] Validate backend and connector installs
10056
+ ${primaryCommand} contract check <manifest-path> [--json]
10057
+ Validate a conformance-manifest.yaml against the contract schema
9666
10058
  ${primaryCommand} prune [--json] Delete the contents of .overlord/tmp in the current directory
9667
10059
  ${primaryCommand} config list [--json] Show local configuration
9668
10060
  ${primaryCommand} config set Choose local/cloud backend interactively
@@ -9750,7 +10142,8 @@ var DB_FREE_COMMANDS = /* @__PURE__ */ new Set([
9750
10142
  "config",
9751
10143
  "auth",
9752
10144
  "user-token",
9753
- "prune"
10145
+ "prune",
10146
+ "contract"
9754
10147
  ]);
9755
10148
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
9756
10149
  ...DB_FREE_COMMANDS,
@@ -9791,6 +10184,9 @@ async function dispatchCommand({
9791
10184
  args,
9792
10185
  stdin
9793
10186
  }) {
10187
+ if (command !== void 0) {
10188
+ assertKnownFlags({ command, flags: parseArgs(args).flags, primaryCommand });
10189
+ }
9794
10190
  switch (command) {
9795
10191
  case void 0:
9796
10192
  case "help":
@@ -9825,6 +10221,15 @@ async function dispatchCommand({
9825
10221
  await runServeCommand2({ rest: args });
9826
10222
  return;
9827
10223
  }
10224
+ case "contract": {
10225
+ const [subcommand, ...rest] = args;
10226
+ if (!subcommand) {
10227
+ throw new CliError({ message: "Usage: ovld contract <subcommand> [flags]" });
10228
+ }
10229
+ const { runContractCommand: runContractCommand2 } = await Promise.resolve().then(() => (init_contract(), contract_exports));
10230
+ await runContractCommand2({ subcommand, args: rest, primaryCommand });
10231
+ return;
10232
+ }
9828
10233
  case "protocol": {
9829
10234
  const [subcommand, ...rest] = args;
9830
10235
  if (!subcommand) {