balladeer 1.0.16 → 1.0.17

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.
@@ -16,9 +16,9 @@ var __commonJS = (cb, mod) => function __require2() {
16
16
  };
17
17
  var __copyProps = (to, from, except, desc) => {
18
18
  if (from && typeof from === "object" || typeof from === "function") {
19
- for (let key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ for (let key2 of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
21
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
22
22
  }
23
23
  return to;
24
24
  };
@@ -366,9 +366,9 @@ var require_toml_parser = __commonJS({
366
366
  var hasOwnProperty = Object.prototype.hasOwnProperty;
367
367
  var defineProperty = Object.defineProperty;
368
368
  var descriptor = { configurable: true, enumerable: true, writable: true, value: void 0 };
369
- function hasKey(obj, key) {
370
- if (hasOwnProperty.call(obj, key)) return true;
371
- if (key === "__proto__") defineProperty(obj, "__proto__", descriptor);
369
+ function hasKey(obj, key2) {
370
+ if (hasOwnProperty.call(obj, key2)) return true;
371
+ if (key2 === "__proto__") defineProperty(obj, "__proto__", descriptor);
372
372
  return false;
373
373
  }
374
374
  var INLINE_TABLE = Symbol("inline-table");
@@ -570,11 +570,11 @@ var require_toml_parser = __commonJS({
570
570
  parseAssign() {
571
571
  return this.callNow(this.parseKeyword, this.recordAssignKeyword);
572
572
  }
573
- recordAssignKeyword(key) {
573
+ recordAssignKeyword(key2) {
574
574
  if (this.state.resultTable) {
575
- this.state.resultTable.push(key);
575
+ this.state.resultTable.push(key2);
576
576
  } else {
577
- this.state.resultTable = [key];
577
+ this.state.resultTable = [key2];
578
578
  }
579
579
  return this.goto(this.parseAssignKeywordPreDot);
580
580
  }
@@ -1779,10 +1779,10 @@ var require_stringify = __commonJS({
1779
1779
  return new Error("Array values can't have mixed types");
1780
1780
  }
1781
1781
  function getInlineKeys(obj) {
1782
- return Object.keys(obj).filter((key) => isInline(obj[key]));
1782
+ return Object.keys(obj).filter((key2) => isInline(obj[key2]));
1783
1783
  }
1784
1784
  function getComplexKeys(obj) {
1785
- return Object.keys(obj).filter((key) => !isInline(obj[key]));
1785
+ return Object.keys(obj).filter((key2) => !isInline(obj[key2]));
1786
1786
  }
1787
1787
  function toJSON(obj) {
1788
1788
  let nobj = Array.isArray(obj) ? [] : Object.prototype.hasOwnProperty.call(obj, "__proto__") ? { ["__proto__"]: void 0 } : {};
@@ -1803,16 +1803,16 @@ var require_stringify = __commonJS({
1803
1803
  complexKeys = getComplexKeys(obj);
1804
1804
  var result = [];
1805
1805
  var inlineIndent = indent || "";
1806
- inlineKeys.forEach((key) => {
1807
- var type = tomlType2(obj[key]);
1806
+ inlineKeys.forEach((key2) => {
1807
+ var type = tomlType2(obj[key2]);
1808
1808
  if (type !== "undefined" && type !== "null") {
1809
- result.push(inlineIndent + stringifyKey(key) + " = " + stringifyAnyInline(obj[key], true));
1809
+ result.push(inlineIndent + stringifyKey(key2) + " = " + stringifyAnyInline(obj[key2], true));
1810
1810
  }
1811
1811
  });
1812
1812
  if (result.length > 0) result.push("");
1813
1813
  var complexIndent = prefix && inlineKeys.length > 0 ? indent + " " : "";
1814
- complexKeys.forEach((key) => {
1815
- result.push(stringifyComplex(prefix, complexIndent, key, obj[key]));
1814
+ complexKeys.forEach((key2) => {
1815
+ result.push(stringifyComplex(prefix, complexIndent, key2, obj[key2]));
1816
1816
  });
1817
1817
  return result.join("\n");
1818
1818
  }
@@ -1857,8 +1857,8 @@ var require_stringify = __commonJS({
1857
1857
  return "table";
1858
1858
  }
1859
1859
  }
1860
- function stringifyKey(key) {
1861
- var keyStr = String(key);
1860
+ function stringifyKey(key2) {
1861
+ var keyStr = String(key2);
1862
1862
  if (/^[-A-Za-z0-9_]+$/.test(keyStr)) {
1863
1863
  return keyStr;
1864
1864
  } else {
@@ -1977,27 +1977,27 @@ var require_stringify = __commonJS({
1977
1977
  function stringifyInlineTable(value) {
1978
1978
  value = toJSON(value);
1979
1979
  var result = [];
1980
- Object.keys(value).forEach((key) => {
1981
- result.push(stringifyKey(key) + " = " + stringifyAnyInline(value[key], false));
1980
+ Object.keys(value).forEach((key2) => {
1981
+ result.push(stringifyKey(key2) + " = " + stringifyAnyInline(value[key2], false));
1982
1982
  });
1983
1983
  return "{ " + result.join(", ") + (result.length > 0 ? " " : "") + "}";
1984
1984
  }
1985
- function stringifyComplex(prefix, indent, key, value) {
1985
+ function stringifyComplex(prefix, indent, key2, value) {
1986
1986
  var valueType = tomlType2(value);
1987
1987
  if (valueType === "array") {
1988
- return stringifyArrayOfTables(prefix, indent, key, value);
1988
+ return stringifyArrayOfTables(prefix, indent, key2, value);
1989
1989
  } else if (valueType === "table") {
1990
- return stringifyComplexTable(prefix, indent, key, value);
1990
+ return stringifyComplexTable(prefix, indent, key2, value);
1991
1991
  } else {
1992
1992
  throw typeError(valueType);
1993
1993
  }
1994
1994
  }
1995
- function stringifyArrayOfTables(prefix, indent, key, values) {
1995
+ function stringifyArrayOfTables(prefix, indent, key2, values) {
1996
1996
  values = toJSON(values);
1997
1997
  validateArray(values);
1998
1998
  var firstValueType = tomlType2(values[0]);
1999
1999
  if (firstValueType !== "table") throw typeError(firstValueType);
2000
- var fullKey = prefix + stringifyKey(key);
2000
+ var fullKey = prefix + stringifyKey(key2);
2001
2001
  var result = "";
2002
2002
  values.forEach((table) => {
2003
2003
  if (result.length > 0) result += "\n";
@@ -2006,8 +2006,8 @@ var require_stringify = __commonJS({
2006
2006
  });
2007
2007
  return result;
2008
2008
  }
2009
- function stringifyComplexTable(prefix, indent, key, value) {
2010
- var fullKey = prefix + stringifyKey(key);
2009
+ function stringifyComplexTable(prefix, indent, key2, value) {
2010
+ var fullKey = prefix + stringifyKey(key2);
2011
2011
  var result = "";
2012
2012
  if (getInlineKeys(value).length > 0) {
2013
2013
  result += indent + "[" + fullKey + "]\n";
@@ -2026,8 +2026,133 @@ var require_toml = __commonJS({
2026
2026
  }
2027
2027
  });
2028
2028
 
2029
+ // src/relay.ts
2030
+ import { createHash } from "node:crypto";
2031
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2032
+ import { join } from "node:path";
2033
+ var RELAY_FIELD = "forThePerson";
2034
+ var MAX_SENTENCE = 400;
2035
+ var MAX_SUBJECT = 200;
2036
+ var MAX_PENDING = 8;
2037
+ function clean(value, limit) {
2038
+ if (typeof value !== "string") return void 0;
2039
+ const text = value.replace(/[\u0000-\u001f\u007f]/g, " ").trim();
2040
+ return text ? text.slice(0, limit) : void 0;
2041
+ }
2042
+ function noticeFrom(value) {
2043
+ if (!value || typeof value !== "object") return void 0;
2044
+ const record = value;
2045
+ const sentence = clean(record.sentence, MAX_SENTENCE);
2046
+ if (!sentence) return void 0;
2047
+ const subjects = (Array.isArray(record.subjects) ? record.subjects : []).map((s) => clean(s, MAX_SUBJECT)).filter((s) => s !== void 0).slice(0, 4);
2048
+ return { sentence, subjects };
2049
+ }
2050
+ function noticeFromToolResponse(response) {
2051
+ const visit = (value, depth) => {
2052
+ if (depth > 4 || value === null || value === void 0) return void 0;
2053
+ if (typeof value === "string") {
2054
+ if (!value.includes(RELAY_FIELD)) return void 0;
2055
+ try {
2056
+ return visit(JSON.parse(value), depth + 1);
2057
+ } catch {
2058
+ return void 0;
2059
+ }
2060
+ }
2061
+ if (Array.isArray(value)) {
2062
+ for (const item of value) {
2063
+ const found = visit(item, depth + 1);
2064
+ if (found) return found;
2065
+ }
2066
+ return void 0;
2067
+ }
2068
+ if (typeof value !== "object") return void 0;
2069
+ const record = value;
2070
+ if (RELAY_FIELD in record) return noticeFrom(record[RELAY_FIELD]);
2071
+ for (const key2 of ["structuredContent", "content", "text", "result", "tool_response"]) {
2072
+ const found = visit(record[key2], depth + 1);
2073
+ if (found) return found;
2074
+ }
2075
+ return void 0;
2076
+ };
2077
+ return visit(response, 0);
2078
+ }
2079
+ function relayNote(notice2) {
2080
+ return `Balladeer just acted on the person's behalf: ${notice2.sentence} Make sure your reply tells them this, in your own words and in about one line.`;
2081
+ }
2082
+ var WORD = /[a-z0-9]+/g;
2083
+ var QUIET = new Set(
2084
+ "a an the and or of to in on for with is are be by as at from that this it its not no".split(" ")
2085
+ );
2086
+ function words(text) {
2087
+ return (text.toLowerCase().match(WORD) ?? []).filter((w) => w.length > 2 && !QUIET.has(w));
2088
+ }
2089
+ function mentions(reply, subject) {
2090
+ const wanted = [...new Set(words(subject))];
2091
+ if (!wanted.length) return true;
2092
+ const said = new Set(words(reply));
2093
+ const found = wanted.filter((w) => said.has(w) || said.has(w.replace(/s$/, ""))).length;
2094
+ return found / wanted.length >= 0.5;
2095
+ }
2096
+ function relayed(reply, notice2) {
2097
+ return notice2.subjects.length === 0 || notice2.subjects.some((s) => mentions(reply, s));
2098
+ }
2099
+ function reminder(notices) {
2100
+ const told = notices.map((n) => n.sentence).join(" ");
2101
+ return `Your reply does not yet tell the person what Balladeer did: ${told} Add it in your own words, in about one line.`;
2102
+ }
2103
+ function pendingPath(directory, sessionId) {
2104
+ return join(directory, `${createHash("sha256").update(sessionId).digest("hex")}.relay.json`);
2105
+ }
2106
+ function readPending(directory, sessionId) {
2107
+ try {
2108
+ const parsed = JSON.parse(readFileSync(pendingPath(directory, sessionId), "utf8"));
2109
+ return parsed.schemaVersion === 1 && Array.isArray(parsed.notices) ? parsed.notices.map(noticeFrom).filter((n) => n !== void 0) : [];
2110
+ } catch {
2111
+ return [];
2112
+ }
2113
+ }
2114
+ function writePending(directory, sessionId, notices) {
2115
+ mkdirSync(directory, { recursive: true, mode: 448 });
2116
+ const path = pendingPath(directory, sessionId);
2117
+ const temporary = `${path}.${process.pid}.tmp`;
2118
+ const body = { schemaVersion: 1, notices: notices.slice(-MAX_PENDING) };
2119
+ writeFileSync(temporary, JSON.stringify(body), { mode: 384 });
2120
+ renameSync(temporary, path);
2121
+ }
2122
+ function isBalladeerTool(name) {
2123
+ return typeof name === "string" && /^mcp__balladeer[a-z0-9_-]*__/i.test(name);
2124
+ }
2125
+ function relayHook(input, directory) {
2126
+ try {
2127
+ const sessionId = typeof input.session_id === "string" ? input.session_id : void 0;
2128
+ if (input.hook_event_name === "PostToolUse") {
2129
+ if (!isBalladeerTool(input.tool_name)) return void 0;
2130
+ const notice2 = noticeFromToolResponse(input.tool_response ?? input.tool_output);
2131
+ if (!notice2) return void 0;
2132
+ if (sessionId && directory)
2133
+ writePending(directory, sessionId, [...readPending(directory, sessionId), notice2]);
2134
+ return JSON.stringify({
2135
+ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: relayNote(notice2) }
2136
+ });
2137
+ }
2138
+ if (input.hook_event_name === "Stop") {
2139
+ if (!sessionId || !directory) return void 0;
2140
+ const pending = readPending(directory, sessionId);
2141
+ if (!pending.length) return void 0;
2142
+ const reply = typeof input.last_assistant_message === "string" ? input.last_assistant_message : "";
2143
+ const missing = pending.filter((n) => !relayed(reply, n));
2144
+ writePending(directory, sessionId, []);
2145
+ if (!missing.length || input.stop_hook_active === true) return void 0;
2146
+ return JSON.stringify({ decision: "block", reason: reminder(missing) });
2147
+ }
2148
+ return void 0;
2149
+ } catch {
2150
+ return void 0;
2151
+ }
2152
+ }
2153
+
2029
2154
  // src/wire.ts
2030
- var CLI_VERSION = "1.0.16";
2155
+ var CLI_VERSION = "1.0.17";
2031
2156
  var CLIENT_HEADER = "x-balladeer-client";
2032
2157
  var UPDATE_STATE_HEADER = "x-balladeer-update-state";
2033
2158
  var HOOK_HOST_HEADER = "x-balladeer-hook";
@@ -2040,15 +2165,15 @@ import {
2040
2165
  fchmodSync,
2041
2166
  fsyncSync,
2042
2167
  lstatSync,
2043
- mkdirSync,
2168
+ mkdirSync as mkdirSync2,
2044
2169
  openSync,
2045
- readFileSync,
2046
- renameSync,
2170
+ readFileSync as readFileSync2,
2171
+ renameSync as renameSync2,
2047
2172
  unlinkSync,
2048
2173
  writeSync
2049
2174
  } from "node:fs";
2050
2175
  import { homedir } from "node:os";
2051
- import { dirname, join, parse, resolve } from "node:path";
2176
+ import { dirname, join as join2, parse, resolve } from "node:path";
2052
2177
  var StoreError = class extends Error {
2053
2178
  code;
2054
2179
  constructor(code, message, options) {
@@ -2062,11 +2187,11 @@ function configHome(environment = process.env) {
2062
2187
  const explicit = environment.BALLADEER_CONFIG_HOME?.trim();
2063
2188
  if (explicit) return resolve(explicit);
2064
2189
  const xdg = environment.XDG_CONFIG_HOME?.trim();
2065
- if (xdg) return resolve(join(xdg, "balladeer"));
2066
- return resolve(join(homedir(), ".config", "balladeer"));
2190
+ if (xdg) return resolve(join2(xdg, "balladeer"));
2191
+ return resolve(join2(homedir(), ".config", "balladeer"));
2067
2192
  }
2068
2193
  function credentialsPath(environment = process.env) {
2069
- return join(configHome(environment), "credentials.json");
2194
+ return join2(configHome(environment), "credentials.json");
2070
2195
  }
2071
2196
  function assertPrivate(path, kind) {
2072
2197
  const stats = lstatSync(path);
@@ -2098,7 +2223,7 @@ function readCredentials(environment = process.env) {
2098
2223
  let raw;
2099
2224
  try {
2100
2225
  assertPrivate(path, "file");
2101
- raw = readFileSync(path, "utf8");
2226
+ raw = readFileSync2(path, "utf8");
2102
2227
  } catch (error) {
2103
2228
  if (error?.code === "ENOENT") {
2104
2229
  return { ...EMPTY, pendingPairings: [], sessions: [], agents: [] };
@@ -2154,8 +2279,8 @@ function newerVersionPublished() {
2154
2279
 
2155
2280
  // src/self-update.ts
2156
2281
  import { spawn } from "node:child_process";
2157
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
2158
- import { delimiter, dirname as dirname2, join as join2 } from "node:path";
2282
+ import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2283
+ import { delimiter, dirname as dirname2, join as join3 } from "node:path";
2159
2284
 
2160
2285
  // src/release.ts
2161
2286
  var HOOK_SPECIFIER = "balladeer@1";
@@ -2167,24 +2292,24 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
2167
2292
  var SELF_UPDATE_ENVIRONMENT = "BALLADEER_SELF_UPDATE";
2168
2293
  var STATE_PATTERN = /^(?:started|unstartable|installed|failed):[A-Za-z0-9._-]{1,40}$/;
2169
2294
  function stampPath(environment) {
2170
- return join2(configHome(environment), STAMP);
2295
+ return join3(configHome(environment), STAMP);
2171
2296
  }
2172
2297
  function statePath(environment) {
2173
- return join2(configHome(environment), STATE);
2298
+ return join3(configHome(environment), STATE);
2174
2299
  }
2175
2300
  function lastStarted(environment) {
2176
2301
  const path = stampPath(environment);
2177
2302
  if (!existsSync(path)) return 0;
2178
2303
  try {
2179
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
2304
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
2180
2305
  return typeof parsed.startedAt === "number" ? parsed.startedAt : 0;
2181
2306
  } catch {
2182
2307
  return 0;
2183
2308
  }
2184
2309
  }
2185
2310
  function writePrivate(path, value, environment) {
2186
- mkdirSync2(configHome(environment), { recursive: true, mode: 448 });
2187
- writeFileSync(path, JSON.stringify(value) + "\n", { mode: 384 });
2311
+ mkdirSync3(configHome(environment), { recursive: true, mode: 448 });
2312
+ writeFileSync2(path, JSON.stringify(value) + "\n", { mode: 384 });
2188
2313
  }
2189
2314
  function recordSelfUpdateState(environment, state, at) {
2190
2315
  if (!STATE_PATTERN.test(state)) return;
@@ -2195,7 +2320,7 @@ function recordSelfUpdateState(environment, state, at) {
2195
2320
  }
2196
2321
  function selfUpdateState(environment) {
2197
2322
  try {
2198
- const parsed = JSON.parse(readFileSync2(statePath(environment), "utf8"));
2323
+ const parsed = JSON.parse(readFileSync3(statePath(environment), "utf8"));
2199
2324
  return typeof parsed.state === "string" && STATE_PATTERN.test(parsed.state) ? parsed.state : void 0;
2200
2325
  } catch {
2201
2326
  return void 0;
@@ -2209,9 +2334,9 @@ function resolveLaunch(specifier, environment, execPath = process.execPath, exis
2209
2334
  PATH: [bin, environment.PATH ?? ""].filter((part) => part.length > 0).join(delimiter),
2210
2335
  [SELF_UPDATE_ENVIRONMENT]: "1"
2211
2336
  };
2212
- const script = join2(bin, "..", "lib", "node_modules", "npm", "bin", "npx-cli.js");
2337
+ const script = join3(bin, "..", "lib", "node_modules", "npm", "bin", "npx-cli.js");
2213
2338
  if (exists(script)) return { command: execPath, args: [script, ...tail], environment: child };
2214
- const shim = join2(bin, process.platform === "win32" ? "npx.cmd" : "npx");
2339
+ const shim = join3(bin, process.platform === "win32" ? "npx.cmd" : "npx");
2215
2340
  if (exists(shim)) return { command: shim, args: tail, environment: child };
2216
2341
  return { command: "npx", args: tail, environment: child };
2217
2342
  }
@@ -2249,25 +2374,25 @@ function startSelfUpdateIfDue(newer, deps) {
2249
2374
  }
2250
2375
 
2251
2376
  // src/guidance.ts
2252
- import { createHash, randomBytes } from "node:crypto";
2377
+ import { createHash as createHash2, randomBytes } from "node:crypto";
2253
2378
  import {
2254
2379
  constants,
2255
2380
  closeSync as closeSync2,
2256
2381
  fstatSync,
2257
2382
  lstatSync as lstatSync2,
2258
- mkdirSync as mkdirSync3,
2383
+ mkdirSync as mkdirSync4,
2259
2384
  openSync as openSync2,
2260
- readFileSync as readFileSync3,
2261
- renameSync as renameSync2,
2385
+ readFileSync as readFileSync4,
2386
+ renameSync as renameSync3,
2262
2387
  unlinkSync as unlinkSync2,
2263
- writeFileSync as writeFileSync2
2388
+ writeFileSync as writeFileSync3
2264
2389
  } from "node:fs";
2265
- import { join as join3 } from "node:path";
2390
+ import { join as join4 } from "node:path";
2266
2391
  var GUIDANCE_UNAVAILABLE = "Balladeer could not verify the current workspace guidance and capture mode. Continue the user's authorized work, but do not make unsolicited capture offers or file inferred promises. Do not reuse earlier Quiet/Thorough permission or cached instructions as current. An explicit request to record still requires current Balladeer tool checks and named-human agreement to meaning; never invent approval. Retry current guidance at the next hook boundary.";
2267
2392
  var HEX = /^[a-f0-9]{64}$/;
2268
2393
  var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
2269
2394
  var MAX_BYTES = 4e4;
2270
- var sha = (value) => createHash("sha256").update(value).digest("hex");
2395
+ var sha = (value) => createHash2("sha256").update(value).digest("hex");
2271
2396
  function object(value) {
2272
2397
  return value !== null && typeof value === "object" && !Array.isArray(value);
2273
2398
  }
@@ -2282,13 +2407,13 @@ function guidanceEndpoint(agent) {
2282
2407
  const expected = new URL(agent.controlPlane);
2283
2408
  if (url.origin !== expected.origin || url.protocol !== "https:" || url.pathname !== "/api/mcp" || url.username || url.password || url.hash || !UUID.test(agent.workspaceId) || !UUID.test(agent.repositoryId))
2284
2409
  throw new Error("invalid_guidance_scope");
2285
- for (const [key, value] of [
2410
+ for (const [key2, value] of [
2286
2411
  ["workspaceId", agent.workspaceId],
2287
2412
  ["repositoryId", agent.repositoryId]
2288
2413
  ]) {
2289
- if (url.searchParams.getAll(key).length > 1 || url.searchParams.has(key) && url.searchParams.get(key) !== value)
2414
+ if (url.searchParams.getAll(key2).length > 1 || url.searchParams.has(key2) && url.searchParams.get(key2) !== value)
2290
2415
  throw new Error("invalid_guidance_scope");
2291
- url.searchParams.set(key, value);
2416
+ url.searchParams.set(key2, value);
2292
2417
  }
2293
2418
  url.pathname = "/api/agent-guidance";
2294
2419
  return url;
@@ -2299,7 +2424,7 @@ function readJson(path) {
2299
2424
  fd = openSync2(path, constants.O_RDONLY | constants.O_NOFOLLOW);
2300
2425
  const stat = fstatSync(fd);
2301
2426
  if (!stat.isFile() || stat.size > MAX_BYTES || (stat.mode & 63) !== 0) return void 0;
2302
- return JSON.parse(readFileSync3(fd, "utf8"));
2427
+ return JSON.parse(readFileSync4(fd, "utf8"));
2303
2428
  } catch {
2304
2429
  return void 0;
2305
2430
  } finally {
@@ -2308,7 +2433,7 @@ function readJson(path) {
2308
2433
  }
2309
2434
  function privateDirectory(path) {
2310
2435
  try {
2311
- mkdirSync3(path, { mode: 448 });
2436
+ mkdirSync4(path, { mode: 448 });
2312
2437
  } catch {
2313
2438
  }
2314
2439
  const stat = lstatSync2(path);
@@ -2318,9 +2443,9 @@ function privateDirectory(path) {
2318
2443
  function atomicJson(path, value) {
2319
2444
  const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
2320
2445
  try {
2321
- writeFileSync2(temporary, `${JSON.stringify(value)}
2446
+ writeFileSync3(temporary, `${JSON.stringify(value)}
2322
2447
  `, { flag: "wx", mode: 384 });
2323
- renameSync2(temporary, path);
2448
+ renameSync3(temporary, path);
2324
2449
  } finally {
2325
2450
  try {
2326
2451
  unlinkSync2(temporary);
@@ -2342,16 +2467,16 @@ function guidanceScopeKey(agent) {
2342
2467
  function cacheDirectory(environment) {
2343
2468
  const home = configHome(environment);
2344
2469
  privateDirectory(home);
2345
- const directory = join3(home, "guidance-v1");
2470
+ const directory = join4(home, "guidance-v1");
2346
2471
  privateDirectory(directory);
2347
2472
  return directory;
2348
2473
  }
2349
2474
  function cached(directory, scopeKey, agent) {
2350
2475
  try {
2351
- const pointer = readJson(join3(directory, `${scopeKey}.current.json`));
2476
+ const pointer = readJson(join4(directory, `${scopeKey}.current.json`));
2352
2477
  if (!object(pointer) || typeof pointer.key !== "string" || !HEX.test(pointer.key))
2353
2478
  return void 0;
2354
- const body = readJson(join3(directory, `${pointer.key}.body.json`));
2479
+ const body = readJson(join4(directory, `${pointer.key}.body.json`));
2355
2480
  if (!object(body) || typeof body.etag !== "string" || !/^"[a-f0-9]{64}"$/.test(body.etag))
2356
2481
  return void 0;
2357
2482
  const document = parseGuidance(body.document, agent);
@@ -2427,12 +2552,12 @@ async function loadGuidance(input) {
2427
2552
  if (abort.signal.aborted) throw new Error("guidance_deadline");
2428
2553
  if (directory) {
2429
2554
  try {
2430
- const key = sha(JSON.stringify([scopeKey, document.revision]));
2431
- atomicJson(join3(directory, `${key}.body.json`), { document, etag });
2432
- atomicJson(join3(directory, `${scopeKey}.current.json`), { key });
2433
- if (previous && previous.key !== key) {
2555
+ const key2 = sha(JSON.stringify([scopeKey, document.revision]));
2556
+ atomicJson(join4(directory, `${key2}.body.json`), { document, etag });
2557
+ atomicJson(join4(directory, `${scopeKey}.current.json`), { key: key2 });
2558
+ if (previous && previous.key !== key2) {
2434
2559
  try {
2435
- unlinkSync2(join3(directory, `${previous.key}.body.json`));
2560
+ unlinkSync2(join4(directory, `${previous.key}.body.json`));
2436
2561
  } catch {
2437
2562
  }
2438
2563
  }
@@ -2474,7 +2599,7 @@ function recordGuidanceContext(input, write2) {
2474
2599
  let path;
2475
2600
  let emitted = true;
2476
2601
  try {
2477
- path = join3(cacheDirectory(input.environment), `${contextKey}.context.json`);
2602
+ path = join4(cacheDirectory(input.environment), `${contextKey}.context.json`);
2478
2603
  const previous = readJson(path);
2479
2604
  emitted = always || !object(previous) || previous.status === "unavailable" || previous.digest !== document?.digest || previous.revision !== document?.revision || (previous.runtimeGeneration ?? null) !== (input.runtimeGeneration ?? null);
2480
2605
  } catch {
@@ -2510,14 +2635,14 @@ import {
2510
2635
  existsSync as existsSync3,
2511
2636
  lstatSync as lstatSync4,
2512
2637
  linkSync,
2513
- mkdirSync as mkdirSync5,
2514
- readFileSync as readFileSync6,
2515
- renameSync as renameSync5,
2638
+ mkdirSync as mkdirSync6,
2639
+ readFileSync as readFileSync7,
2640
+ renameSync as renameSync6,
2516
2641
  unlinkSync as unlinkSync5,
2517
- writeFileSync as writeFileSync3
2642
+ writeFileSync as writeFileSync4
2518
2643
  } from "node:fs";
2519
2644
  var import_toml2 = __toESM(require_toml(), 1);
2520
- import { dirname as dirname4, join as join6, relative } from "node:path";
2645
+ import { dirname as dirname4, join as join7, relative } from "node:path";
2521
2646
 
2522
2647
  // src/codex-config.ts
2523
2648
  var import_toml = __toESM(require_toml(), 1);
@@ -2526,26 +2651,26 @@ import {
2526
2651
  existsSync as existsSync2,
2527
2652
  fsyncSync as fsyncSync3,
2528
2653
  lstatSync as lstatSync3,
2529
- mkdirSync as mkdirSync4,
2654
+ mkdirSync as mkdirSync5,
2530
2655
  openSync as openSync4,
2531
- readFileSync as readFileSync5,
2532
- renameSync as renameSync4,
2656
+ readFileSync as readFileSync6,
2657
+ renameSync as renameSync5,
2533
2658
  unlinkSync as unlinkSync4,
2534
2659
  writeSync as writeSync3
2535
2660
  } from "node:fs";
2536
- import { join as join5 } from "node:path";
2661
+ import { join as join6 } from "node:path";
2537
2662
 
2538
2663
  // src/mcp-config.ts
2539
2664
  import {
2540
2665
  closeSync as closeSync3,
2541
2666
  fsyncSync as fsyncSync2,
2542
2667
  openSync as openSync3,
2543
- readFileSync as readFileSync4,
2544
- renameSync as renameSync3,
2668
+ readFileSync as readFileSync5,
2669
+ renameSync as renameSync4,
2545
2670
  unlinkSync as unlinkSync3,
2546
2671
  writeSync as writeSync2
2547
2672
  } from "node:fs";
2548
- import { basename, dirname as dirname3, isAbsolute, join as join4 } from "node:path";
2673
+ import { basename, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
2549
2674
  var MCP_CONFIG_FILE = ".mcp.json";
2550
2675
  function sameOrigin(left, right) {
2551
2676
  try {
@@ -2588,7 +2713,7 @@ function currentEntry(parsed) {
2588
2713
  }
2589
2714
  function readMcpConfig(repositoryRoot) {
2590
2715
  try {
2591
- return JSON.parse(readFileSync4(join4(repositoryRoot, MCP_CONFIG_FILE), "utf8"));
2716
+ return JSON.parse(readFileSync5(join5(repositoryRoot, MCP_CONFIG_FILE), "utf8"));
2592
2717
  } catch {
2593
2718
  return void 0;
2594
2719
  }
@@ -2598,7 +2723,7 @@ function readMcpConfig(repositoryRoot) {
2598
2723
  var CODEX_CONFIG_FILE = ".codex/config.toml";
2599
2724
  function readCodexEntry(root) {
2600
2725
  try {
2601
- const config = (0, import_toml.parse)(readFileSync5(join5(root, CODEX_CONFIG_FILE), "utf8"));
2726
+ const config = (0, import_toml.parse)(readFileSync6(join6(root, CODEX_CONFIG_FILE), "utf8"));
2602
2727
  const servers = config.mcp_servers;
2603
2728
  return servers && typeof servers === "object" && !Array.isArray(servers) && !(servers instanceof Date) ? servers.balladeer : void 0;
2604
2729
  } catch {
@@ -2607,7 +2732,7 @@ function readCodexEntry(root) {
2607
2732
  }
2608
2733
  function hasManagedCodexEntry(root, controlPlane) {
2609
2734
  try {
2610
- const text = readFileSync5(join5(root, CODEX_CONFIG_FILE), "utf8");
2735
+ const text = readFileSync6(join6(root, CODEX_CONFIG_FILE), "utf8");
2611
2736
  const starts = [...text.matchAll(/^# balladeer:mcp:start\r?$/gm)];
2612
2737
  const ends = [...text.matchAll(/^# balladeer:mcp:end\r?$/gm)];
2613
2738
  if (starts.length !== 1 || ends.length !== 1 || !starts[0] || !ends[0] || starts[0].index >= ends[0].index)
@@ -3032,7 +3157,7 @@ function readRegular(path, allowMissing = false, writable = true) {
3032
3157
  const stat = lstatSync4(path);
3033
3158
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE || writable && (stat.mode & 146) === 0)
3034
3159
  throw new Error("file_not_safely_writable");
3035
- return readFileSync6(path, "utf8");
3160
+ return readFileSync7(path, "utf8");
3036
3161
  }
3037
3162
  function safeParent(root, path) {
3038
3163
  let at = dirname4(path);
@@ -3054,7 +3179,7 @@ function validateProjectGuidanceScope(options) {
3054
3179
  stdio: ["ignore", "pipe", "ignore"],
3055
3180
  timeout: 1e3
3056
3181
  }).trim();
3057
- const path = join6(root, options.hook === "codex" ? ".codex/config.toml" : ".mcp.json");
3182
+ const path = join7(root, options.hook === "codex" ? ".codex/config.toml" : ".mcp.json");
3058
3183
  safeParent(root, path);
3059
3184
  readRegular(path, false, false);
3060
3185
  const entry = options.hook === "codex" ? readCodexEntry(root) : currentEntry(readMcpConfig(root));
@@ -3168,31 +3293,52 @@ function repositoryHints(cwd = process.cwd()) {
3168
3293
  }
3169
3294
 
3170
3295
  // src/hook-trust.ts
3171
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
3172
- import { join as join7 } from "node:path";
3296
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
3297
+ import { join as join8 } from "node:path";
3298
+ var HOSTS_REQUIRING_TRUST = ["codex"];
3173
3299
  var FILE = "hook-trust.json";
3300
+ var key = (host, set) => set === "guidance" ? host : `${host}:${set}`;
3174
3301
  function read(environment) {
3175
3302
  try {
3176
- const parsed = JSON.parse(readFileSync7(join7(configHome(environment), FILE), "utf8"));
3303
+ const parsed = JSON.parse(readFileSync8(join8(configHome(environment), FILE), "utf8"));
3177
3304
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
3178
3305
  } catch {
3179
3306
  return {};
3180
3307
  }
3181
3308
  }
3309
+ function pushCheckAwaitingFile(environment, host) {
3310
+ return join8(configHome(environment), `${host}-push-check-awaiting-first-run`);
3311
+ }
3312
+ function forget(path) {
3313
+ try {
3314
+ rmSync(path, { force: true });
3315
+ } catch {
3316
+ }
3317
+ }
3182
3318
  function write(environment, marks) {
3183
3319
  try {
3184
- mkdirSync6(configHome(environment), { recursive: true, mode: 448 });
3185
- writeFileSync4(join7(configHome(environment), FILE), JSON.stringify(marks) + "\n", {
3320
+ mkdirSync7(configHome(environment), { recursive: true, mode: 448 });
3321
+ writeFileSync5(join8(configHome(environment), FILE), JSON.stringify(marks) + "\n", {
3186
3322
  mode: 384
3187
3323
  });
3324
+ for (const host of HOSTS_REQUIRING_TRUST) {
3325
+ const file = pushCheckAwaitingFile(environment, host);
3326
+ if (awaiting(marks[key(host, "push-check")])) writeFileSync5(file, "", { mode: 384 });
3327
+ else forget(file);
3328
+ }
3188
3329
  } catch {
3189
3330
  }
3190
3331
  }
3191
- function noteHookFired(environment, host, now = Date.now()) {
3332
+ function noteHookFired(environment, host, now = Date.now(), set = "guidance") {
3333
+ if (set === "push-check") forget(pushCheckAwaitingFile(environment, host));
3192
3334
  const marks = read(environment);
3193
- const mark = marks[host];
3335
+ const mark = marks[key(host, set)];
3194
3336
  if (mark?.firedAt !== void 0 && mark.firedAt >= (mark.writtenAt ?? 0)) return;
3195
- write(environment, { ...marks, [host]: { ...mark, firedAt: now } });
3337
+ write(environment, { ...marks, [key(host, set)]: { ...mark, firedAt: now } });
3338
+ }
3339
+ function awaiting(mark) {
3340
+ if (mark?.writtenAt === void 0) return false;
3341
+ return mark.firedAt === void 0 || mark.firedAt < mark.writtenAt;
3196
3342
  }
3197
3343
 
3198
3344
  // src/commands/guidance.ts
@@ -3237,7 +3383,45 @@ function eventFrom(input) {
3237
3383
  function identifier(input) {
3238
3384
  return typeof input === "string" && input.length > 0 && input.length <= 256 ? input : void 0;
3239
3385
  }
3386
+ async function runRelayNote(options) {
3387
+ const input = await new Promise((resolve2) => {
3388
+ const chunks = [];
3389
+ let bytes = 0;
3390
+ let settled = false;
3391
+ const done = (value) => {
3392
+ if (settled) return;
3393
+ settled = true;
3394
+ clearTimeout(timer);
3395
+ options.stdin.removeListener("data", data);
3396
+ resolve2(value);
3397
+ };
3398
+ const data = (chunk) => {
3399
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
3400
+ bytes += buffer.length;
3401
+ if (bytes > 1048576) done(void 0);
3402
+ else chunks.push(buffer);
3403
+ };
3404
+ const timer = setTimeout(() => done(void 0), 1e3);
3405
+ options.stdin.on("data", data);
3406
+ options.stdin.once("end", () => {
3407
+ try {
3408
+ done(JSON.parse(Buffer.concat(chunks).toString("utf8")));
3409
+ } catch {
3410
+ done(void 0);
3411
+ }
3412
+ });
3413
+ options.stdin.once("error", () => done(void 0));
3414
+ });
3415
+ if (!input || typeof input !== "object" || Array.isArray(input)) return 0;
3416
+ const record = input;
3417
+ if (record.hook_event_name !== "PostToolUse") return 0;
3418
+ const out = relayHook(record);
3419
+ if (out) options.write(`${out}
3420
+ `);
3421
+ return 0;
3422
+ }
3240
3423
  async function runGuidance(options) {
3424
+ if (options.event === "PostToolUse") return runRelayNote(options);
3241
3425
  const projectScoped = options.repositoryId !== void 0;
3242
3426
  if (projectScoped && !validateProjectGuidanceScope({
3243
3427
  cwd: options.cwd,