@yakcc/cli 0.5.0-alpha.0 → 0.5.0-alpha.1

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
@@ -76,6 +76,16 @@ function byteSwap32(arr) {
76
76
  }
77
77
  return arr;
78
78
  }
79
+ function bytesToHex(bytes) {
80
+ abytes(bytes);
81
+ if (hasHexBuiltin)
82
+ return bytes.toHex();
83
+ let hex = "";
84
+ for (let i = 0; i < bytes.length; i++) {
85
+ hex += hexes[bytes[i]];
86
+ }
87
+ return hex;
88
+ }
79
89
  function createHasher(hashCons, info = {}) {
80
90
  const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
81
91
  const tmp = hashCons(void 0);
@@ -86,13 +96,18 @@ function createHasher(hashCons, info = {}) {
86
96
  Object.assign(hashC, info);
87
97
  return Object.freeze(hashC);
88
98
  }
89
- var isLE, swap8IfBE, swap32IfBE;
99
+ var isLE, swap8IfBE, swap32IfBE, hasHexBuiltin, hexes;
90
100
  var init_utils = __esm({
91
101
  "../../node_modules/.pnpm/@noble+hashes@2.2.0/node_modules/@noble/hashes/utils.js"() {
92
102
  "use strict";
93
103
  isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
94
104
  swap8IfBE = isLE ? (n) => n : (n) => byteSwap(n) >>> 0;
95
105
  swap32IfBE = isLE ? (u) => u : byteSwap32;
106
+ hasHexBuiltin = /* @__PURE__ */ (() => (
107
+ // @ts-ignore
108
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
109
+ ))();
110
+ hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
96
111
  }
97
112
  });
98
113
 
@@ -681,7 +696,7 @@ __export(contract_id_exports, {
681
696
  });
682
697
  function contractIdFromBytes(canonical) {
683
698
  const digest = blake3(canonical);
684
- return bytesToHex(digest);
699
+ return bytesToHex2(digest);
685
700
  }
686
701
  function contractId(spec) {
687
702
  return contractIdFromBytes(canonicalize(spec));
@@ -689,7 +704,7 @@ function contractId(spec) {
689
704
  function isValidContractId(s) {
690
705
  return /^[0-9a-f]{64}$/.test(s);
691
706
  }
692
- function bytesToHex(bytes) {
707
+ function bytesToHex2(bytes) {
693
708
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
694
709
  }
695
710
  var init_contract_id = __esm({
@@ -928,13 +943,13 @@ function concatBytes(...arrays) {
928
943
  }
929
944
  return out;
930
945
  }
931
- function bytesToHex2(bytes) {
946
+ function bytesToHex3(bytes) {
932
947
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
933
948
  }
934
949
  function specHash(spec) {
935
950
  const bytes = canonicalize(spec);
936
951
  const digest = blake3(bytes);
937
- return bytesToHex2(digest);
952
+ return bytesToHex3(digest);
938
953
  }
939
954
  function blockMerkleRoot(triplet) {
940
955
  if (triplet.kind === "foreign") {
@@ -960,7 +975,7 @@ function blockMerkleRootLocal(triplet) {
960
975
  const proofRootBytes = blake3(proofInput);
961
976
  const rootInput = concatBytes(specHashBytes, implHashBytes, proofRootBytes);
962
977
  const rootBytes = blake3(rootInput);
963
- return bytesToHex2(rootBytes);
978
+ return bytesToHex3(rootBytes);
964
979
  }
965
980
  function blockMerkleRootForeign(triplet) {
966
981
  const identityObj = {
@@ -972,7 +987,7 @@ function blockMerkleRootForeign(triplet) {
972
987
  };
973
988
  const identityBytes = canonicalize(identityObj);
974
989
  const rootBytes = blake3(identityBytes);
975
- return bytesToHex2(rootBytes);
990
+ return bytesToHex3(rootBytes);
976
991
  }
977
992
  function isLocalTriplet(t) {
978
993
  return t.kind !== "foreign";
@@ -992,7 +1007,7 @@ var init_merkle = __esm({
992
1007
 
993
1008
  // ../contracts/dist/canonical-ast.js
994
1009
  import { Project, ScriptKind, SyntaxKind, ts } from "ts-morph";
995
- function bytesToHex3(bytes) {
1010
+ function bytesToHex4(bytes) {
996
1011
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
997
1012
  }
998
1013
  function findEnclosingNode(file, range) {
@@ -1133,7 +1148,7 @@ function canonicalAstHash(source, sourceRange) {
1133
1148
  const renames = collectLocalRenames(root);
1134
1149
  const canonical = emitCanonical(root, renames);
1135
1150
  const digest = blake3(TEXT_ENCODER3.encode(canonical));
1136
- return bytesToHex3(digest);
1151
+ return bytesToHex4(digest);
1137
1152
  }
1138
1153
  var CanonicalAstParseError, TEXT_ENCODER3;
1139
1154
  var init_canonical_ast = __esm({
@@ -2226,7 +2241,7 @@ function isStricterThan(aRoot, bRoot, edgeSet, allRoots) {
2226
2241
  }
2227
2242
  return false;
2228
2243
  }
2229
- function bytesToHex4(bytes) {
2244
+ function bytesToHex5(bytes) {
2230
2245
  let hex = "";
2231
2246
  for (let i = 0; i < bytes.length; i++) {
2232
2247
  hex += bytes[i]?.toString(16).padStart(2, "0");
@@ -2858,7 +2873,7 @@ var init_storage = __esm({
2858
2873
  ORDER BY b.block_merkle_root ASC`).all();
2859
2874
  if (blockRows.length === 0)
2860
2875
  return [];
2861
- const EMPTY_SENTINEL = bytesToHex4(blake3(new Uint8Array(0)));
2876
+ const EMPTY_SENTINEL = bytesToHex5(blake3(new Uint8Array(0)));
2862
2877
  const allRoots = blockRows.map((r) => r.block_merkle_root);
2863
2878
  const placeholders = allRoots.map(() => "?").join(", ");
2864
2879
  const artifactRows = this.db.prepare(`SELECT block_merkle_root, path, bytes
@@ -2872,7 +2887,7 @@ var init_storage = __esm({
2872
2887
  entry = { implSourceHash: EMPTY_SENTINEL, manifestJsonHash: EMPTY_SENTINEL };
2873
2888
  hashMap.set(row.block_merkle_root, entry);
2874
2889
  }
2875
- const digest = bytesToHex4(blake3(new Uint8Array(row.bytes)));
2890
+ const digest = bytesToHex5(blake3(new Uint8Array(row.bytes)));
2876
2891
  if (row.path === "impl.ts") {
2877
2892
  entry.implSourceHash = digest;
2878
2893
  } else if (row.path === "proof/manifest.json") {
@@ -2911,7 +2926,7 @@ var init_storage = __esm({
2911
2926
  if (entry.workspacePath.startsWith("/") || entry.workspacePath.includes("..")) {
2912
2927
  throw new Error(`storeWorkspacePlumbing: workspacePath must be workspace-relative and must not contain '..': ${entry.workspacePath}`);
2913
2928
  }
2914
- const actualHash = bytesToHex4(blake3(entry.contentBytes));
2929
+ const actualHash = bytesToHex5(blake3(entry.contentBytes));
2915
2930
  if (actualHash !== entry.contentHash) {
2916
2931
  throw new Error(`storeWorkspacePlumbing: contentHash mismatch for ${entry.workspacePath}: stored=${entry.contentHash}, computed=${actualHash}`);
2917
2932
  }
@@ -2962,7 +2977,7 @@ var init_storage = __esm({
2962
2977
  if (entry.sourceFile.startsWith("/") || entry.sourceFile.includes("..")) {
2963
2978
  throw new Error(`storeSourceFileGlue: sourceFile must be workspace-relative and must not contain '..': ${entry.sourceFile}`);
2964
2979
  }
2965
- const actualHash = bytesToHex4(blake3(entry.contentBlob));
2980
+ const actualHash = bytesToHex5(blake3(entry.contentBlob));
2966
2981
  if (actualHash !== entry.contentHash) {
2967
2982
  throw new Error(`storeSourceFileGlue: contentHash mismatch for ${entry.sourceFile}: stored=${entry.contentHash}, computed=${actualHash}`);
2968
2983
  }
@@ -3887,11 +3902,11 @@ async function serveRegistry(registry, options) {
3887
3902
  console.error("[serveRegistry] Unhandled request error:", err);
3888
3903
  });
3889
3904
  });
3890
- await new Promise((resolve6, reject) => {
3905
+ await new Promise((resolve7, reject) => {
3891
3906
  server.once("error", reject);
3892
3907
  server.listen(port, host, () => {
3893
3908
  server.removeListener("error", reject);
3894
- resolve6();
3909
+ resolve7();
3895
3910
  });
3896
3911
  });
3897
3912
  const address = server.address();
@@ -3908,16 +3923,16 @@ async function serveRegistry(registry, options) {
3908
3923
  if (closed)
3909
3924
  return Promise.resolve();
3910
3925
  closed = true;
3911
- return new Promise((resolve6, reject) => {
3926
+ return new Promise((resolve7, reject) => {
3912
3927
  server.close((err) => {
3913
3928
  if (err !== void 0) {
3914
3929
  if (err.code === "ERR_SERVER_NOT_RUNNING") {
3915
- resolve6();
3930
+ resolve7();
3916
3931
  } else {
3917
3932
  reject(err);
3918
3933
  }
3919
3934
  } else {
3920
- resolve6();
3935
+ resolve7();
3921
3936
  }
3922
3937
  });
3923
3938
  });
@@ -4017,11 +4032,11 @@ async function runFederationServe(argv, logger, opts) {
4017
4032
  if (opts?.noBlock === true) {
4018
4033
  return { code: 0, handle };
4019
4034
  }
4020
- await new Promise((resolve6) => {
4035
+ await new Promise((resolve7) => {
4021
4036
  const shutdown = () => {
4022
4037
  handle.close().then(() => {
4023
- registry.close().finally(resolve6);
4024
- }).catch(resolve6);
4038
+ registry.close().finally(resolve7);
4039
+ }).catch(resolve7);
4025
4040
  };
4026
4041
  process.once("SIGINT", shutdown);
4027
4042
  process.once("SIGTERM", shutdown);
@@ -4236,18 +4251,18 @@ function byteSwap322(arr) {
4236
4251
  return arr;
4237
4252
  }
4238
4253
  var swap32IfBE2 = isLE2 ? (u) => u : byteSwap322;
4239
- var hasHexBuiltin = /* @__PURE__ */ (() => (
4254
+ var hasHexBuiltin2 = /* @__PURE__ */ (() => (
4240
4255
  // @ts-ignore
4241
4256
  typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
4242
4257
  ))();
4243
- var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
4244
- function bytesToHex5(bytes) {
4258
+ var hexes2 = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
4259
+ function bytesToHex6(bytes) {
4245
4260
  abytes2(bytes);
4246
- if (hasHexBuiltin)
4261
+ if (hasHexBuiltin2)
4247
4262
  return bytes.toHex();
4248
4263
  let hex = "";
4249
4264
  for (let i = 0; i < bytes.length; i++) {
4250
- hex += hexes[bytes[i]];
4265
+ hex += hexes2[bytes[i]];
4251
4266
  }
4252
4267
  return hex;
4253
4268
  }
@@ -4645,7 +4660,7 @@ async function renameWithRetry(src, dst) {
4645
4660
  }
4646
4661
  if (attempt < MAX_ATTEMPTS - 1) {
4647
4662
  const delay = BACKOFF_MS[attempt] ?? BACKOFF_MS[BACKOFF_MS.length - 1] ?? 160;
4648
- await new Promise((resolve6) => setTimeout(resolve6, delay));
4663
+ await new Promise((resolve7) => setTimeout(resolve7, delay));
4649
4664
  }
4650
4665
  }
4651
4666
  }
@@ -4706,12 +4721,12 @@ function normalizeSource(s) {
4706
4721
  var encoder = new TextEncoder();
4707
4722
  function sourceHash(unitSource) {
4708
4723
  const normalized = normalizeSource(unitSource);
4709
- return bytesToHex5(blake32(encoder.encode(normalized)));
4724
+ return bytesToHex6(blake32(encoder.encode(normalized)));
4710
4725
  }
4711
4726
  function keyFromIntentInputs(inputs) {
4712
4727
  const { sourceHash: sh, modelTag, promptVersion, schemaVersion } = inputs;
4713
4728
  const raw = `${sh}\0${modelTag}\0${promptVersion}\0${schemaVersion}`;
4714
- return bytesToHex5(blake32(encoder.encode(raw)));
4729
+ return bytesToHex6(blake32(encoder.encode(raw)));
4715
4730
  }
4716
4731
 
4717
4732
  // ../shave/dist/corpus/ai-derived.js
@@ -4751,7 +4766,7 @@ async function extractFromAiDerivedCached(intentCard, source, cacheDir, model, p
4751
4766
  if (cached === void 0)
4752
4767
  return void 0;
4753
4768
  const bytes = encoder2.encode(cached.content);
4754
- const contentHash = bytesToHex5(blake32(bytes));
4769
+ const contentHash = bytesToHex6(blake32(bytes));
4755
4770
  void intentCard;
4756
4771
  return {
4757
4772
  source: "ai-derived",
@@ -4783,7 +4798,7 @@ async function extractFromPropsFile(propsFilePath, _intentCard, source) {
4783
4798
  return void 0;
4784
4799
  }
4785
4800
  const bytes = encoder3.encode(fileContent);
4786
- const contentHash = bytesToHex5(blake32(bytes));
4801
+ const contentHash = bytesToHex6(blake32(bytes));
4787
4802
  return {
4788
4803
  source: "props-file",
4789
4804
  bytes,
@@ -4908,7 +4923,7 @@ function extractFromDocumentedUsage(intentCard, source) {
4908
4923
  `);
4909
4924
  }
4910
4925
  const bytes = encoder4.encode(content);
4911
- const contentHash = bytesToHex5(blake32(bytes));
4926
+ const contentHash = bytesToHex6(blake32(bytes));
4912
4927
  return {
4913
4928
  source: "documented-usage",
4914
4929
  bytes,
@@ -4985,7 +5000,7 @@ var UPSTREAM_TEST_PATH = "property-tests.fast-check.ts";
4985
5000
  function extractFromUpstreamTest(intentCard, source) {
4986
5001
  const content = buildUpstreamTestContent(intentCard, source);
4987
5002
  const bytes = encoder5.encode(content);
4988
- const contentHash = bytesToHex5(blake32(bytes));
5003
+ const contentHash = bytesToHex6(blake32(bytes));
4989
5004
  return {
4990
5005
  source: "upstream-test",
4991
5006
  bytes,
@@ -5505,7 +5520,7 @@ function safeCanonicalAstHash(node, nodeSource, fullSource, start, end) {
5505
5520
  return canonicalAstHash(fullSource, { start, end });
5506
5521
  } catch {
5507
5522
  const TEXT_ENC = new TextEncoder();
5508
- return bytesToHex5(blake32(TEXT_ENC.encode(nodeSource)));
5523
+ return bytesToHex6(blake32(TEXT_ENC.encode(nodeSource)));
5509
5524
  }
5510
5525
  }
5511
5526
  }
@@ -6906,9 +6921,9 @@ async function extractIntent(unitSource, ctx) {
6906
6921
  } catch (err) {
6907
6922
  console.warn(`[shave extractIntent] Corrupt cache entry for key ${cacheKey} (${err instanceof Error ? err.message : String(err)}); treating as miss.`);
6908
6923
  const { unlink: unlink2 } = await import("fs/promises");
6909
- const { join: join20 } = await import("path");
6924
+ const { join: join24 } = await import("path");
6910
6925
  const shard = cacheKey.slice(0, 3);
6911
- const filePath = join20(ctx.cacheDir, shard, `${cacheKey}.json`);
6926
+ const filePath = join24(ctx.cacheDir, shard, `${cacheKey}.json`);
6912
6927
  await unlink2(filePath).catch(() => {
6913
6928
  });
6914
6929
  }
@@ -8860,18 +8875,411 @@ async function compile(argv, logger, opts) {
8860
8875
  // src/index.ts
8861
8876
  init_federation();
8862
8877
 
8863
- // src/commands/hooks-aider-install.ts
8864
- import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
8878
+ // ../hooks-base/dist/telemetry.js
8879
+ init_blake3();
8880
+ init_utils();
8881
+ import { appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync6 } from "fs";
8865
8882
  import { homedir } from "os";
8866
- import { join as join10 } from "path";
8883
+ import { join as join13 } from "path";
8884
+
8885
+ // ../hooks-base/dist/telemetry-sink.js
8886
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
8887
+ import { join as join11 } from "path";
8888
+
8889
+ // ../hooks-base/dist/telemetry-config.js
8890
+ var DEFAULT_TELEMETRY_ENDPOINT = "https://metrics.yakcc.com";
8891
+ function resolveTelemetryConfig(env = process.env) {
8892
+ const fullIntent = env.YAKCC_TELEMETRY_FULL_INTENT === "1";
8893
+ if (env.YAKCC_TELEMETRY_DISABLED === "1") {
8894
+ return { disabled: true, endpoint: DEFAULT_TELEMETRY_ENDPOINT, fullIntent };
8895
+ }
8896
+ const rawEndpoint = env.YAKCC_TELEMETRY_ENDPOINT;
8897
+ if (rawEndpoint === "off") {
8898
+ return { disabled: true, endpoint: DEFAULT_TELEMETRY_ENDPOINT, fullIntent };
8899
+ }
8900
+ const endpoint = rawEndpoint !== void 0 && rawEndpoint !== "" ? rawEndpoint : DEFAULT_TELEMETRY_ENDPOINT;
8901
+ return { disabled: false, endpoint, fullIntent };
8902
+ }
8903
+
8904
+ // ../hooks-base/dist/telemetry-wire.js
8905
+ import { readFileSync as readFileSync5 } from "fs";
8906
+ import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
8907
+ import { fileURLToPath as fileURLToPath2 } from "url";
8908
+ var SCHEMA_VERSION2 = 1;
8909
+ var _cachedSource = null;
8910
+ function buildSource() {
8911
+ if (_cachedSource !== null)
8912
+ return _cachedSource;
8913
+ let cliVersion = "unknown";
8914
+ try {
8915
+ const __filename = fileURLToPath2(import.meta.url);
8916
+ const pkgPath = resolve3(join10(dirname6(__filename), "../package.json"));
8917
+ const pkgJson = JSON.parse(readFileSync5(pkgPath, "utf-8"));
8918
+ cliVersion = typeof pkgJson.version === "string" ? pkgJson.version : "unknown";
8919
+ } catch {
8920
+ }
8921
+ _cachedSource = {
8922
+ cliVersion,
8923
+ platform: process.platform,
8924
+ nodeVersion: process.version
8925
+ };
8926
+ return _cachedSource;
8927
+ }
8928
+ function buildEnvelope(events, sessionId, now = Date.now()) {
8929
+ return {
8930
+ schemaVersion: SCHEMA_VERSION2,
8931
+ sessionId,
8932
+ events,
8933
+ emittedAt: now,
8934
+ source: buildSource()
8935
+ };
8936
+ }
8937
+
8938
+ // ../hooks-base/dist/telemetry-sink.js
8939
+ var _SINK_FALLBACK_SESSION_ID = (() => {
8940
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
8941
+ return crypto.randomUUID();
8942
+ }
8943
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
8944
+ const r = Math.floor(Math.random() * 16);
8945
+ const v = c === "x" ? r : r & 3 | 8;
8946
+ return v.toString(16);
8947
+ });
8948
+ })();
8949
+ function _resolveSinkSessionId() {
8950
+ return process.env.CLAUDE_SESSION_ID ?? _SINK_FALLBACK_SESSION_ID;
8951
+ }
8952
+ var NoOpSink = class {
8953
+ send(_event) {
8954
+ }
8955
+ };
8956
+ var FileSink = class {
8957
+ dir;
8958
+ sessionId;
8959
+ initialized = false;
8960
+ // at-most-once warn set (DEC-TELEMETRY-EXPORT-FAIL-SILENT-005)
8961
+ warnedClasses = /* @__PURE__ */ new Set();
8962
+ constructor(dir, sessionId) {
8963
+ this.dir = dir;
8964
+ this.sessionId = sessionId ?? _resolveSinkSessionId();
8965
+ }
8966
+ send(event) {
8967
+ try {
8968
+ if (!this.initialized) {
8969
+ mkdirSync5(this.dir, { recursive: true });
8970
+ this.initialized = true;
8971
+ }
8972
+ const filePath = join11(this.dir, `${this.sessionId}.jsonl`);
8973
+ appendFileSync(filePath, `${JSON.stringify(event)}
8974
+ `, "utf-8");
8975
+ } catch (err) {
8976
+ this._warnOnce(err);
8977
+ }
8978
+ }
8979
+ _warnOnce(err) {
8980
+ const cls = err instanceof Error ? err.constructor.name : "UnknownError";
8981
+ if (!this.warnedClasses.has(cls)) {
8982
+ this.warnedClasses.add(cls);
8983
+ console.warn(`[yakcc telemetry] FileSink error (${cls}):`, err);
8984
+ }
8985
+ }
8986
+ };
8987
+ var BATCH_FLUSH_INTERVAL_MS = 5e3;
8988
+ var BATCH_FLUSH_SIZE = 50;
8989
+ var FETCH_TIMEOUT_MS = 1e4;
8990
+ var HttpsBatcherSink = class {
8991
+ endpoint;
8992
+ sessionId;
8993
+ buffer = [];
8994
+ timer = null;
8995
+ flushing = false;
8996
+ // at-most-once warn set (DEC-TELEMETRY-EXPORT-FAIL-SILENT-005)
8997
+ warnedClasses = /* @__PURE__ */ new Set();
8998
+ _beforeExitHandler;
8999
+ _registered = false;
9000
+ constructor(endpoint, sessionId) {
9001
+ this.endpoint = endpoint;
9002
+ this.sessionId = sessionId ?? _resolveSinkSessionId();
9003
+ this._beforeExitHandler = () => {
9004
+ this._flushSync();
9005
+ };
9006
+ }
9007
+ send(event) {
9008
+ this.buffer.push(event);
9009
+ this._ensureTimer();
9010
+ if (this.buffer.length >= BATCH_FLUSH_SIZE) {
9011
+ this._scheduleFlush();
9012
+ }
9013
+ }
9014
+ /** Start the interval timer and register the beforeExit handler (lazy, once). */
9015
+ _ensureTimer() {
9016
+ if (this._registered)
9017
+ return;
9018
+ this._registered = true;
9019
+ this.timer = setInterval(() => {
9020
+ this._scheduleFlush();
9021
+ }, BATCH_FLUSH_INTERVAL_MS);
9022
+ if (this.timer && typeof this.timer.unref === "function") {
9023
+ this.timer.unref();
9024
+ }
9025
+ process.on("beforeExit", this._beforeExitHandler);
9026
+ }
9027
+ /** Schedule a flush on the next microtask tick (non-blocking). */
9028
+ _scheduleFlush() {
9029
+ if (this.buffer.length === 0)
9030
+ return;
9031
+ const batch = this.buffer.splice(0, this.buffer.length);
9032
+ void this._flush(batch);
9033
+ }
9034
+ /** Async flush — fire-and-forget. DEC-TELEMETRY-EXPORT-FAIL-SILENT-005. */
9035
+ async _flush(batch) {
9036
+ if (batch.length === 0)
9037
+ return;
9038
+ const envelope = buildEnvelope(batch, this.sessionId);
9039
+ const controller = new AbortController();
9040
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
9041
+ try {
9042
+ const response = await fetch(this.endpoint, {
9043
+ method: "POST",
9044
+ headers: {
9045
+ "Content-Type": "application/json",
9046
+ "User-Agent": `yakcc-hooks-base/${envelope.source.cliVersion} (${envelope.source.platform}; node-${envelope.source.nodeVersion})`
9047
+ },
9048
+ body: JSON.stringify(envelope),
9049
+ signal: controller.signal
9050
+ });
9051
+ if (!response.ok) {
9052
+ this._warnOnce(new Error(`HTTP ${response.status}`), "HttpError");
9053
+ }
9054
+ } catch (err) {
9055
+ this._warnOnce(err);
9056
+ } finally {
9057
+ clearTimeout(timeoutId);
9058
+ }
9059
+ }
9060
+ /**
9061
+ * Best-effort exit-flush called from `beforeExit`.
9062
+ *
9063
+ * @decision DEC-TELEMETRY-EXPORT-BATCHING-004
9064
+ * `beforeExit` fires when the Node event loop becomes empty (not via process.exit()).
9065
+ * We start the async flush here. Because `beforeExit` fires when the loop is empty,
9066
+ * the pending fetch promise keeps the loop alive long enough for the flush to complete
9067
+ * under normal conditions (no explicit process.exit()). If `process.exit()` is called
9068
+ * by the host, inflight events are dropped — this is the documented NG5 behavior
9069
+ * (no retry queue, at-most-once delivery).
9070
+ *
9071
+ * Previous implementation used `Atomics.wait` as a bounded sleep, but
9072
+ * `Atomics.wait` on the main thread throws `TypeError` in Node ≥ 16 when
9073
+ * `--experimental-vm-modules` or other thread guards are active (Node 22 always
9074
+ * rejects it on the main thread). The async-only approach is correct here because
9075
+ * `beforeExit` itself is an async boundary: the outstanding fetch promise will
9076
+ * re-trigger the loop, and if the process exits without the loop draining further,
9077
+ * the events are already acknowledged as best-effort (DEC-TELEMETRY-EXPORT-BATCHING-004).
9078
+ */
9079
+ _flushSync() {
9080
+ if (this.buffer.length === 0)
9081
+ return;
9082
+ if (this.flushing)
9083
+ return;
9084
+ this.flushing = true;
9085
+ const batch = this.buffer.splice(0, this.buffer.length);
9086
+ void this._flush(batch);
9087
+ }
9088
+ /**
9089
+ * Deregister timer and handlers (for tests and clean shutdown).
9090
+ * Drains the buffer without flushing — events in-buffer at dispose time are
9091
+ * dropped (NG5: at-most-once delivery). This is intentional for test isolation:
9092
+ * after dispose(), no further async fetch calls will originate from this sink.
9093
+ */
9094
+ dispose() {
9095
+ if (this.timer !== null) {
9096
+ clearInterval(this.timer);
9097
+ this.timer = null;
9098
+ }
9099
+ process.removeListener("beforeExit", this._beforeExitHandler);
9100
+ this._registered = false;
9101
+ this.buffer.length = 0;
9102
+ }
9103
+ _warnOnce(err, classOverride) {
9104
+ const cls = classOverride ?? (err instanceof Error ? err.constructor.name : "UnknownError");
9105
+ if (!this.warnedClasses.has(cls)) {
9106
+ this.warnedClasses.add(cls);
9107
+ console.warn(`[yakcc telemetry] HttpsBatcherSink error (${cls}):`, err);
9108
+ }
9109
+ }
9110
+ };
9111
+ var CompositeSink = class {
9112
+ sinks;
9113
+ constructor(sinks) {
9114
+ this.sinks = sinks;
9115
+ }
9116
+ send(event) {
9117
+ for (const sink of this.sinks) {
9118
+ try {
9119
+ sink.send(event);
9120
+ } catch {
9121
+ }
9122
+ }
9123
+ }
9124
+ /**
9125
+ * Propagate dispose() to inner sinks that support it.
9126
+ * Safe to call multiple times (inner sinks are idempotent on dispose).
9127
+ * @internal — for test isolation and clean process shutdown.
9128
+ */
9129
+ dispose() {
9130
+ for (const sink of this.sinks) {
9131
+ if (sink instanceof HttpsBatcherSink) {
9132
+ sink.dispose();
9133
+ }
9134
+ }
9135
+ }
9136
+ };
9137
+ var _sinkSingleton = null;
9138
+ function selectSink(env = process.env, config) {
9139
+ if (_sinkSingleton !== null)
9140
+ return _sinkSingleton;
9141
+ _sinkSingleton = _buildSink(config ?? resolveTelemetryConfig(env));
9142
+ return _sinkSingleton;
9143
+ }
9144
+ function _buildSink(config, sessionId) {
9145
+ if (config.disabled) {
9146
+ return new NoOpSink();
9147
+ }
9148
+ const endpoint = config.endpoint;
9149
+ if (endpoint.startsWith("file://")) {
9150
+ const dir = endpoint.slice("file://".length);
9151
+ return new FileSink(dir, sessionId);
9152
+ }
9153
+ if (endpoint.startsWith("https://")) {
9154
+ return new CompositeSink([new HttpsBatcherSink(endpoint, sessionId)]);
9155
+ }
9156
+ console.warn(`[yakcc telemetry] Unknown endpoint scheme: ${endpoint} \u2014 using NoOpSink`);
9157
+ return new NoOpSink();
9158
+ }
9159
+
9160
+ // ../hooks-base/dist/enforcement-config.js
9161
+ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
9162
+ import { join as join12 } from "path";
9163
+
9164
+ // ../hooks-base/dist/telemetry.js
9165
+ var FALLBACK_SESSION_ID = (() => {
9166
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
9167
+ return crypto.randomUUID();
9168
+ }
9169
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
9170
+ const r = Math.floor(Math.random() * 16);
9171
+ const v = c === "x" ? r : r & 3 | 8;
9172
+ return v.toString(16);
9173
+ });
9174
+ })();
9175
+ function resolveSessionId() {
9176
+ return process.env.CLAUDE_SESSION_ID ?? FALLBACK_SESSION_ID;
9177
+ }
9178
+ function resolveTelemetryDir() {
9179
+ return process.env.YAKCC_TELEMETRY_DIR ?? join13(homedir(), ".yakcc", "telemetry");
9180
+ }
9181
+ function hashIntent(intentText) {
9182
+ const encoded = new TextEncoder().encode(intentText);
9183
+ const digest = blake3(encoded);
9184
+ return bytesToHex(digest);
9185
+ }
9186
+ function appendTelemetryEvent(event, sessionId, dir) {
9187
+ if (!existsSync4(dir)) {
9188
+ mkdirSync6(dir, { recursive: true });
9189
+ }
9190
+ const filePath = join13(dir, `${sessionId}.jsonl`);
9191
+ appendFileSync2(filePath, `${JSON.stringify(event)}
9192
+ `, "utf-8");
9193
+ try {
9194
+ selectSink().send(event);
9195
+ } catch {
9196
+ }
9197
+ }
9198
+
9199
+ // src/commands/hook-intercept.ts
9200
+ var WINDOWS_ILLEGAL_FILENAME_RE = /[<>:"/\\|?*\x00-\x1f]/;
9201
+ function sanitizeSessionId(raw) {
9202
+ if (WINDOWS_ILLEGAL_FILENAME_RE.test(raw)) return null;
9203
+ return raw;
9204
+ }
9205
+ var ALLOWED_TOOL_NAMES = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
9206
+ async function hookIntercept(argv, logger, options) {
9207
+ void argv;
9208
+ void logger;
9209
+ const stdinStream = options?.stdin ?? process.stdin;
9210
+ const telemetryDir = options?.telemetryDir ?? resolveTelemetryDir();
9211
+ const appendEvent = options?.appendEvent ?? appendTelemetryEvent;
9212
+ const now = options?.now ?? Date.now;
9213
+ const start = now();
9214
+ try {
9215
+ const chunks = [];
9216
+ for await (const chunk of stdinStream) {
9217
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
9218
+ }
9219
+ const stdinText = Buffer.concat(chunks).toString("utf-8").trim();
9220
+ if (!stdinText) {
9221
+ return 0;
9222
+ }
9223
+ let parsed;
9224
+ try {
9225
+ parsed = JSON.parse(stdinText);
9226
+ } catch {
9227
+ return 0;
9228
+ }
9229
+ if (typeof parsed !== "object" || parsed === null) {
9230
+ return 0;
9231
+ }
9232
+ const payload = parsed;
9233
+ const toolName = payload.tool_name;
9234
+ if (typeof toolName !== "string" || !ALLOWED_TOOL_NAMES.has(toolName)) {
9235
+ return 0;
9236
+ }
9237
+ const validToolName = toolName;
9238
+ let sessionId;
9239
+ const payloadSessionId = payload.session_id;
9240
+ if (typeof payloadSessionId === "string" && payloadSessionId.length > 0) {
9241
+ const sanitized = sanitizeSessionId(payloadSessionId);
9242
+ sessionId = sanitized ?? resolveSessionId();
9243
+ } else {
9244
+ sessionId = resolveSessionId();
9245
+ }
9246
+ const toolInput = typeof payload.tool_input === "object" && payload.tool_input !== null ? payload.tool_input : {};
9247
+ const intentText = typeof toolInput.new_string === "string" ? toolInput.new_string : typeof toolInput.content === "string" ? toolInput.content : "";
9248
+ const end = now();
9249
+ const event = {
9250
+ t: end,
9251
+ intentHash: hashIntent(intentText),
9252
+ toolName: validToolName,
9253
+ candidateCount: 0,
9254
+ // Phase 1: no registry query
9255
+ topScore: null,
9256
+ // Phase 1: no registry query
9257
+ substituted: false,
9258
+ // Phase 1: never substitutes
9259
+ substitutedAtomHash: null,
9260
+ // Phase 1: never substitutes
9261
+ latencyMs: end - start,
9262
+ outcome: "passthrough"
9263
+ // D-HOOK-5 canonical value per plan section 3
9264
+ };
9265
+ appendEvent(event, sessionId, telemetryDir);
9266
+ } catch {
9267
+ }
9268
+ return 0;
9269
+ }
9270
+
9271
+ // src/commands/hooks-aider-install.ts
9272
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync4 } from "fs";
9273
+ import { homedir as homedir2 } from "os";
9274
+ import { join as join14 } from "path";
8867
9275
  import { parseArgs as parseArgs6 } from "util";
8868
9276
  var AIDER_HOOK_MARKER_FILENAME = "yakcc-aider-hook.json";
8869
9277
  var YAKCC_AIDER_MARKER = "yakcc-hook-v1-aider";
8870
9278
  var HOOK_COMMAND = "yakcc hook-intercept";
8871
9279
  function readMarker(markerPath) {
8872
- if (!existsSync3(markerPath)) return null;
9280
+ if (!existsSync5(markerPath)) return null;
8873
9281
  try {
8874
- return JSON.parse(readFileSync5(markerPath, "utf-8"));
9282
+ return JSON.parse(readFileSync7(markerPath, "utf-8"));
8875
9283
  } catch {
8876
9284
  return null;
8877
9285
  }
@@ -8896,10 +9304,10 @@ async function hooksAiderInstall(argv, logger, overrideAiderDir) {
8896
9304
  logger.error(`error: ${err.message}`);
8897
9305
  return 1;
8898
9306
  }
8899
- const aiderDir = overrideAiderDir ?? join10(homedir(), ".aider");
8900
- const markerPath = join10(aiderDir, AIDER_HOOK_MARKER_FILENAME);
9307
+ const aiderDir = overrideAiderDir ?? join14(homedir2(), ".aider");
9308
+ const markerPath = join14(aiderDir, AIDER_HOOK_MARKER_FILENAME);
8901
9309
  try {
8902
- mkdirSync5(aiderDir, { recursive: true });
9310
+ mkdirSync7(aiderDir, { recursive: true });
8903
9311
  } catch (err) {
8904
9312
  logger.error(`error: cannot create ${aiderDir}: ${String(err)}`);
8905
9313
  return 1;
@@ -8945,26 +9353,194 @@ async function hooksAiderInstall(argv, logger, overrideAiderDir) {
8945
9353
  return 0;
8946
9354
  }
8947
9355
 
8948
- // src/commands/hooks-cursor-install.ts
8949
- import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
8950
- import { join as join11 } from "path";
9356
+ // src/commands/hooks-cline-install.ts
9357
+ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
9358
+ import { homedir as homedir3 } from "os";
9359
+ import { join as join15 } from "path";
8951
9360
  import { parseArgs as parseArgs7 } from "util";
9361
+ var CLINE_HOOK_MARKER_FILENAME = "yakcc-cline-hook.json";
9362
+ var YAKCC_CLINE_MARKER = "yakcc-hook-v1-cline";
9363
+ var HOOK_COMMAND2 = "yakcc hook-intercept";
9364
+ function readMarker2(markerPath) {
9365
+ if (!existsSync6(markerPath)) return null;
9366
+ try {
9367
+ return JSON.parse(readFileSync8(markerPath, "utf-8"));
9368
+ } catch {
9369
+ return null;
9370
+ }
9371
+ }
9372
+ function isYakccInstalled2(markerPath) {
9373
+ const marker = readMarker2(markerPath);
9374
+ return marker !== null && marker._yakcc === YAKCC_CLINE_MARKER;
9375
+ }
9376
+ async function hooksClineInstall(argv, logger, overrideClineDir) {
9377
+ let parsed;
9378
+ try {
9379
+ parsed = parseArgs7({
9380
+ args: [...argv],
9381
+ options: {
9382
+ target: { type: "string", short: "t" },
9383
+ uninstall: { type: "boolean" }
9384
+ },
9385
+ allowPositionals: false,
9386
+ strict: true
9387
+ });
9388
+ } catch (err) {
9389
+ logger.error(`error: ${err.message}`);
9390
+ return 1;
9391
+ }
9392
+ const clineDir = overrideClineDir ?? join15(homedir3(), ".config", "cline");
9393
+ const markerPath = join15(clineDir, CLINE_HOOK_MARKER_FILENAME);
9394
+ try {
9395
+ mkdirSync8(clineDir, { recursive: true });
9396
+ } catch (err) {
9397
+ logger.error(`error: cannot create ${clineDir}: ${String(err)}`);
9398
+ return 1;
9399
+ }
9400
+ if (parsed.values.uninstall) {
9401
+ if (!isYakccInstalled2(markerPath)) {
9402
+ logger.log("yakcc cline hook not installed \u2014 nothing to uninstall.");
9403
+ return 0;
9404
+ }
9405
+ try {
9406
+ rmSync2(markerPath);
9407
+ } catch (err) {
9408
+ logger.error(`error: cannot remove ${markerPath}: ${String(err)}`);
9409
+ return 1;
9410
+ }
9411
+ logger.log(`yakcc cline hook marker removed: ${markerPath}`);
9412
+ return 0;
9413
+ }
9414
+ if (isYakccInstalled2(markerPath)) {
9415
+ logger.log(`yakcc cline hook already installed at ${markerPath} (idempotent).`);
9416
+ return 0;
9417
+ }
9418
+ const marker = {
9419
+ command: HOOK_COMMAND2,
9420
+ description: "yakcc tool-call interception hook for Cline",
9421
+ sessionEnvVar: "CLINE_SESSION_ID",
9422
+ telemetryPrefix: "cline",
9423
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9424
+ _yakcc: YAKCC_CLINE_MARKER,
9425
+ note: "Cline (saoudrizwan.claude-dev) does not yet expose synchronous tool-call interception via a stable Node.js API. This marker documents the intended wiring (DEC-CLI-HOOKS-CLINE-INSTALL-001). When the Cline extension API stabilises, hook activation requires no reinstall."
9426
+ };
9427
+ try {
9428
+ writeFileSync5(markerPath, `${JSON.stringify(marker, null, 2)}
9429
+ `, "utf-8");
9430
+ } catch (err) {
9431
+ logger.error(`error: cannot write marker ${markerPath}: ${String(err)}`);
9432
+ return 1;
9433
+ }
9434
+ logger.log(`yakcc cline hook marker installed: ${markerPath}`);
9435
+ logger.log("note: Cline tool-call interception API not yet stable \u2014 see marker for details.");
9436
+ return 0;
9437
+ }
9438
+
9439
+ // src/commands/hooks-continue-install.ts
9440
+ import { existsSync as existsSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
9441
+ import { homedir as homedir4 } from "os";
9442
+ import { join as join16 } from "path";
9443
+ import { parseArgs as parseArgs8 } from "util";
9444
+ var CONTINUE_HOOK_MARKER_FILENAME = "yakcc-continue-hook.json";
9445
+ var YAKCC_CONTINUE_MARKER = "yakcc-hook-v1-continue";
9446
+ var HOOK_COMMAND3 = "yakcc hook-intercept";
9447
+ function readMarker3(markerPath) {
9448
+ if (!existsSync7(markerPath)) return null;
9449
+ try {
9450
+ return JSON.parse(readFileSync9(markerPath, "utf-8"));
9451
+ } catch {
9452
+ return null;
9453
+ }
9454
+ }
9455
+ function isYakccInstalled3(markerPath) {
9456
+ const marker = readMarker3(markerPath);
9457
+ return marker !== null && marker._yakcc === YAKCC_CONTINUE_MARKER;
9458
+ }
9459
+ async function hooksContinueInstall(argv, logger, overrideContinueDir) {
9460
+ let parsed;
9461
+ try {
9462
+ parsed = parseArgs8({
9463
+ args: [...argv],
9464
+ options: {
9465
+ target: { type: "string", short: "t" },
9466
+ uninstall: { type: "boolean" }
9467
+ },
9468
+ allowPositionals: false,
9469
+ strict: true
9470
+ });
9471
+ } catch (err) {
9472
+ logger.error(`error: ${err.message}`);
9473
+ return 1;
9474
+ }
9475
+ const continueDir = overrideContinueDir ?? join16(homedir4(), ".continue");
9476
+ const markerPath = join16(continueDir, CONTINUE_HOOK_MARKER_FILENAME);
9477
+ try {
9478
+ mkdirSync9(continueDir, { recursive: true });
9479
+ } catch (err) {
9480
+ logger.error(`error: cannot create ${continueDir}: ${String(err)}`);
9481
+ return 1;
9482
+ }
9483
+ if (parsed.values.uninstall) {
9484
+ if (!isYakccInstalled3(markerPath)) {
9485
+ logger.log("yakcc continue hook not installed \u2014 nothing to uninstall.");
9486
+ return 0;
9487
+ }
9488
+ try {
9489
+ rmSync3(markerPath);
9490
+ } catch (err) {
9491
+ logger.error(`error: cannot remove ${markerPath}: ${String(err)}`);
9492
+ return 1;
9493
+ }
9494
+ logger.log(`yakcc continue hook marker removed: ${markerPath}`);
9495
+ return 0;
9496
+ }
9497
+ if (isYakccInstalled3(markerPath)) {
9498
+ logger.log(`yakcc continue hook already installed at ${markerPath} (idempotent).`);
9499
+ return 0;
9500
+ }
9501
+ const marker = {
9502
+ command: HOOK_COMMAND3,
9503
+ description: "yakcc tool-call interception hook for Continue.dev",
9504
+ sessionEnvVar: "CONTINUE_SESSION_ID",
9505
+ telemetryPrefix: "continue",
9506
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9507
+ _yakcc: YAKCC_CONTINUE_MARKER,
9508
+ note: "Continue.dev (continue.continue) does not yet expose synchronous tool-call interception via a stable Node.js API. This marker documents the intended wiring (DEC-CLI-HOOKS-CONTINUE-INSTALL-001). When the Continue.dev API stabilises, hook activation requires no reinstall."
9509
+ };
9510
+ try {
9511
+ writeFileSync6(markerPath, `${JSON.stringify(marker, null, 2)}
9512
+ `, "utf-8");
9513
+ } catch (err) {
9514
+ logger.error(`error: cannot write marker ${markerPath}: ${String(err)}`);
9515
+ return 1;
9516
+ }
9517
+ logger.log(`yakcc continue hook marker installed: ${markerPath}`);
9518
+ logger.log(
9519
+ "note: Continue.dev tool-call interception API not yet stable \u2014 see marker for details."
9520
+ );
9521
+ return 0;
9522
+ }
9523
+
9524
+ // src/commands/hooks-cursor-install.ts
9525
+ import { existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
9526
+ import { join as join17 } from "path";
9527
+ import { parseArgs as parseArgs9 } from "util";
8952
9528
  var CURSOR_HOOK_MARKER_FILENAME = "yakcc-cursor-hook.json";
8953
9529
  var YAKCC_CURSOR_MARKER = "yakcc-hook-v1-cursor";
8954
- var HOOK_COMMAND2 = "yakcc hook-intercept";
9530
+ var HOOK_COMMAND4 = "yakcc hook-intercept";
8955
9531
  function readCursorSettings(settingsPath) {
8956
- if (!existsSync4(settingsPath)) return {};
9532
+ if (!existsSync8(settingsPath)) return {};
8957
9533
  try {
8958
- return JSON.parse(readFileSync6(settingsPath, "utf-8"));
9534
+ return JSON.parse(readFileSync10(settingsPath, "utf-8"));
8959
9535
  } catch {
8960
9536
  return {};
8961
9537
  }
8962
9538
  }
8963
9539
  function writeCursorSettings(settingsPath, settings) {
8964
- writeFileSync5(settingsPath, `${JSON.stringify(settings, null, 2)}
9540
+ writeFileSync7(settingsPath, `${JSON.stringify(settings, null, 2)}
8965
9541
  `, "utf-8");
8966
9542
  }
8967
- function isYakccInstalled2(settings) {
9543
+ function isYakccInstalled4(settings) {
8968
9544
  const hooks = settings.hooks;
8969
9545
  if (hooks == null || typeof hooks !== "object") return false;
8970
9546
  const yakccEntry = hooks.yakcc;
@@ -8972,7 +9548,7 @@ function isYakccInstalled2(settings) {
8972
9548
  return yakccEntry._yakcc === YAKCC_CURSOR_MARKER;
8973
9549
  }
8974
9550
  function applyInstall(settings) {
8975
- if (isYakccInstalled2(settings)) {
9551
+ if (isYakccInstalled4(settings)) {
8976
9552
  return { settings, alreadyInstalled: true };
8977
9553
  }
8978
9554
  return {
@@ -8981,7 +9557,7 @@ function applyInstall(settings) {
8981
9557
  hooks: {
8982
9558
  ...settings.hooks ?? {},
8983
9559
  yakcc: {
8984
- command: HOOK_COMMAND2,
9560
+ command: HOOK_COMMAND4,
8985
9561
  description: "yakcc tool-call interception hook for Cursor",
8986
9562
  sessionEnvVar: "CURSOR_SESSION_ID",
8987
9563
  _yakcc: YAKCC_CURSOR_MARKER
@@ -8992,7 +9568,7 @@ function applyInstall(settings) {
8992
9568
  };
8993
9569
  }
8994
9570
  function applyUninstall(settings) {
8995
- if (!isYakccInstalled2(settings)) {
9571
+ if (!isYakccInstalled4(settings)) {
8996
9572
  return { settings, wasInstalled: false };
8997
9573
  }
8998
9574
  const { yakcc: _removed, ...remainingHooks } = settings.hooks ?? {};
@@ -9001,7 +9577,7 @@ function applyUninstall(settings) {
9001
9577
  return { settings: newSettings, wasInstalled: true };
9002
9578
  }
9003
9579
  async function hooksCursorInstall(argv, logger) {
9004
- const { values } = parseArgs7({
9580
+ const { values } = parseArgs9({
9005
9581
  args: [...argv],
9006
9582
  options: {
9007
9583
  target: { type: "string", short: "t" },
@@ -9011,11 +9587,11 @@ async function hooksCursorInstall(argv, logger) {
9011
9587
  strict: true
9012
9588
  });
9013
9589
  const targetDir = values.target ?? ".";
9014
- const cursorDir = join11(targetDir, ".cursor");
9015
- const settingsPath = join11(cursorDir, "settings.json");
9016
- const markerPath = join11(cursorDir, CURSOR_HOOK_MARKER_FILENAME);
9590
+ const cursorDir = join17(targetDir, ".cursor");
9591
+ const settingsPath = join17(cursorDir, "settings.json");
9592
+ const markerPath = join17(cursorDir, CURSOR_HOOK_MARKER_FILENAME);
9017
9593
  try {
9018
- mkdirSync6(cursorDir, { recursive: true });
9594
+ mkdirSync10(cursorDir, { recursive: true });
9019
9595
  } catch (err) {
9020
9596
  logger.error(`error: cannot create ${cursorDir}: ${String(err)}`);
9021
9597
  return 1;
@@ -9043,11 +9619,11 @@ async function hooksCursorInstall(argv, logger) {
9043
9619
  return 1;
9044
9620
  }
9045
9621
  try {
9046
- writeFileSync5(
9622
+ writeFileSync7(
9047
9623
  markerPath,
9048
9624
  `${JSON.stringify(
9049
9625
  {
9050
- command: HOOK_COMMAND2,
9626
+ command: HOOK_COMMAND4,
9051
9627
  description: "yakcc tool-call interception hook for Cursor",
9052
9628
  sessionEnvVar: "CURSOR_SESSION_ID",
9053
9629
  telemetryPrefix: "cursor",
@@ -9067,7 +9643,7 @@ async function hooksCursorInstall(argv, logger) {
9067
9643
  logger.log(`yakcc cursor hook already installed at ${settingsPath} (idempotent).`);
9068
9644
  } else {
9069
9645
  logger.log(`yakcc cursor hook installed at ${settingsPath}`);
9070
- logger.log(`hook command: ${HOOK_COMMAND2}`);
9646
+ logger.log(`hook command: ${HOOK_COMMAND4}`);
9071
9647
  logger.log("session env var: CURSOR_SESSION_ID");
9072
9648
  logger.log(`marker: ${markerPath}`);
9073
9649
  logger.log("note: Cursor tool-call interception API not yet stable \u2014 see marker for details.");
@@ -9076,27 +9652,27 @@ async function hooksCursorInstall(argv, logger) {
9076
9652
  }
9077
9653
 
9078
9654
  // src/commands/hooks-install.ts
9079
- import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
9080
- import { join as join12 } from "path";
9081
- import { parseArgs as parseArgs8 } from "util";
9655
+ import { existsSync as existsSync9, mkdirSync as mkdirSync11, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
9656
+ import { join as join18 } from "path";
9657
+ import { parseArgs as parseArgs10 } from "util";
9082
9658
  var HOOK_EVENT = "PreToolUse";
9083
9659
  var HOOK_MATCHER = "Edit|Write|MultiEdit";
9084
- var HOOK_COMMAND3 = "yakcc hook-intercept";
9660
+ var HOOK_COMMAND5 = "yakcc hook-intercept";
9085
9661
  var YAKCC_MARKER = "yakcc-hook-v1";
9086
9662
  function readSettings(settingsPath) {
9087
- if (!existsSync5(settingsPath)) return {};
9663
+ if (!existsSync9(settingsPath)) return {};
9088
9664
  try {
9089
- return JSON.parse(readFileSync7(settingsPath, "utf-8"));
9665
+ return JSON.parse(readFileSync11(settingsPath, "utf-8"));
9090
9666
  } catch {
9091
9667
  return {};
9092
9668
  }
9093
9669
  }
9094
9670
  function writeSettings(settingsPath, settings) {
9095
- writeFileSync6(settingsPath, `${JSON.stringify(settings, null, 2)}
9671
+ writeFileSync8(settingsPath, `${JSON.stringify(settings, null, 2)}
9096
9672
  `, "utf-8");
9097
9673
  }
9098
9674
  function buildYakccHookObject() {
9099
- return { type: "command", command: HOOK_COMMAND3, _yakcc: YAKCC_MARKER };
9675
+ return { type: "command", command: HOOK_COMMAND5, _yakcc: YAKCC_MARKER };
9100
9676
  }
9101
9677
  function isYakccEntry(entry) {
9102
9678
  return entry.hooks.some((h) => h._yakcc === YAKCC_MARKER);
@@ -9135,7 +9711,7 @@ function applyUninstall2(settings) {
9135
9711
  return { settings: newSettings, wasInstalled: true };
9136
9712
  }
9137
9713
  async function hooksClaudeCodeInstall(argv, logger) {
9138
- const { values } = parseArgs8({
9714
+ const { values } = parseArgs10({
9139
9715
  args: [...argv],
9140
9716
  options: {
9141
9717
  target: { type: "string", short: "t" },
@@ -9145,11 +9721,11 @@ async function hooksClaudeCodeInstall(argv, logger) {
9145
9721
  strict: true
9146
9722
  });
9147
9723
  const targetDir = values.target ?? ".";
9148
- const claudeDir = join12(targetDir, ".claude");
9149
- const settingsPath = join12(claudeDir, "settings.json");
9150
- const claudeMdPath = join12(claudeDir, "CLAUDE.md");
9724
+ const claudeDir = join18(targetDir, ".claude");
9725
+ const settingsPath = join18(claudeDir, "settings.json");
9726
+ const claudeMdPath = join18(claudeDir, "CLAUDE.md");
9151
9727
  try {
9152
- mkdirSync7(claudeDir, { recursive: true });
9728
+ mkdirSync11(claudeDir, { recursive: true });
9153
9729
  } catch (err) {
9154
9730
  logger.error(`error: cannot create ${claudeDir}: ${String(err)}`);
9155
9731
  return 1;
@@ -9176,9 +9752,9 @@ async function hooksClaudeCodeInstall(argv, logger) {
9176
9752
  logger.error(`error: cannot write ${settingsPath}: ${String(err)}`);
9177
9753
  return 1;
9178
9754
  }
9179
- if (existsSync5(claudeMdPath)) {
9755
+ if (existsSync9(claudeMdPath)) {
9180
9756
  try {
9181
- rmSync2(claudeMdPath);
9757
+ rmSync4(claudeMdPath);
9182
9758
  } catch (err) {
9183
9759
  logger.error(`warning: cannot remove v0 stub ${claudeMdPath}: ${String(err)}`);
9184
9760
  }
@@ -9187,31 +9763,31 @@ async function hooksClaudeCodeInstall(argv, logger) {
9187
9763
  logger.log(`yakcc hook already installed at ${settingsPath} (idempotent).`);
9188
9764
  } else {
9189
9765
  logger.log(`yakcc hook installed at ${settingsPath}`);
9190
- logger.log(`tool-call interception: ${HOOK_MATCHER} \u2192 ${HOOK_COMMAND3}`);
9766
+ logger.log(`tool-call interception: ${HOOK_MATCHER} \u2192 ${HOOK_COMMAND5}`);
9191
9767
  }
9192
9768
  return 0;
9193
9769
  }
9194
9770
 
9195
9771
  // src/commands/hooks-windsurf-install.ts
9196
- import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
9197
- import { join as join13 } from "path";
9198
- import { parseArgs as parseArgs9 } from "util";
9772
+ import { existsSync as existsSync10, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
9773
+ import { join as join19 } from "path";
9774
+ import { parseArgs as parseArgs11 } from "util";
9199
9775
  var WINDSURF_HOOK_MARKER_FILENAME = "yakcc-windsurf-hook.json";
9200
9776
  var YAKCC_WINDSURF_MARKER = "yakcc-hook-v1-windsurf";
9201
- var HOOK_COMMAND4 = "yakcc hook-intercept";
9777
+ var HOOK_COMMAND6 = "yakcc hook-intercept";
9202
9778
  function readWindsurfSettings(settingsPath) {
9203
- if (!existsSync6(settingsPath)) return {};
9779
+ if (!existsSync10(settingsPath)) return {};
9204
9780
  try {
9205
- return JSON.parse(readFileSync8(settingsPath, "utf-8"));
9781
+ return JSON.parse(readFileSync12(settingsPath, "utf-8"));
9206
9782
  } catch {
9207
9783
  return {};
9208
9784
  }
9209
9785
  }
9210
9786
  function writeWindsurfSettings(settingsPath, settings) {
9211
- writeFileSync7(settingsPath, `${JSON.stringify(settings, null, 2)}
9787
+ writeFileSync9(settingsPath, `${JSON.stringify(settings, null, 2)}
9212
9788
  `, "utf-8");
9213
9789
  }
9214
- function isYakccInstalled3(settings) {
9790
+ function isYakccInstalled5(settings) {
9215
9791
  const hooks = settings.hooks;
9216
9792
  if (hooks == null || typeof hooks !== "object") return false;
9217
9793
  const yakccEntry = hooks.yakcc;
@@ -9219,7 +9795,7 @@ function isYakccInstalled3(settings) {
9219
9795
  return yakccEntry._yakcc === YAKCC_WINDSURF_MARKER;
9220
9796
  }
9221
9797
  function applyInstall3(settings) {
9222
- if (isYakccInstalled3(settings)) {
9798
+ if (isYakccInstalled5(settings)) {
9223
9799
  return { settings, alreadyInstalled: true };
9224
9800
  }
9225
9801
  return {
@@ -9228,7 +9804,7 @@ function applyInstall3(settings) {
9228
9804
  hooks: {
9229
9805
  ...settings.hooks ?? {},
9230
9806
  yakcc: {
9231
- command: HOOK_COMMAND4,
9807
+ command: HOOK_COMMAND6,
9232
9808
  description: "yakcc tool-call interception hook for Windsurf",
9233
9809
  sessionEnvVar: "WINDSURF_SESSION_ID",
9234
9810
  _yakcc: YAKCC_WINDSURF_MARKER
@@ -9239,7 +9815,7 @@ function applyInstall3(settings) {
9239
9815
  };
9240
9816
  }
9241
9817
  function applyUninstall3(settings) {
9242
- if (!isYakccInstalled3(settings)) {
9818
+ if (!isYakccInstalled5(settings)) {
9243
9819
  return { settings, wasInstalled: false };
9244
9820
  }
9245
9821
  const { yakcc: _removed, ...remainingHooks } = settings.hooks ?? {};
@@ -9248,7 +9824,7 @@ function applyUninstall3(settings) {
9248
9824
  return { settings: newSettings, wasInstalled: true };
9249
9825
  }
9250
9826
  async function hooksWindsurfInstall(argv, logger) {
9251
- const { values } = parseArgs9({
9827
+ const { values } = parseArgs11({
9252
9828
  args: [...argv],
9253
9829
  options: {
9254
9830
  target: { type: "string", short: "t" },
@@ -9258,11 +9834,11 @@ async function hooksWindsurfInstall(argv, logger) {
9258
9834
  strict: true
9259
9835
  });
9260
9836
  const targetDir = values.target ?? ".";
9261
- const windsurfDir = join13(targetDir, ".windsurf");
9262
- const settingsPath = join13(windsurfDir, "settings.json");
9263
- const markerPath = join13(windsurfDir, WINDSURF_HOOK_MARKER_FILENAME);
9837
+ const windsurfDir = join19(targetDir, ".windsurf");
9838
+ const settingsPath = join19(windsurfDir, "settings.json");
9839
+ const markerPath = join19(windsurfDir, WINDSURF_HOOK_MARKER_FILENAME);
9264
9840
  try {
9265
- mkdirSync8(windsurfDir, { recursive: true });
9841
+ mkdirSync12(windsurfDir, { recursive: true });
9266
9842
  } catch (err) {
9267
9843
  logger.error(`error: cannot create ${windsurfDir}: ${String(err)}`);
9268
9844
  return 1;
@@ -9290,11 +9866,11 @@ async function hooksWindsurfInstall(argv, logger) {
9290
9866
  return 1;
9291
9867
  }
9292
9868
  try {
9293
- writeFileSync7(
9869
+ writeFileSync9(
9294
9870
  markerPath,
9295
9871
  `${JSON.stringify(
9296
9872
  {
9297
- command: HOOK_COMMAND4,
9873
+ command: HOOK_COMMAND6,
9298
9874
  description: "yakcc tool-call interception hook for Windsurf",
9299
9875
  sessionEnvVar: "WINDSURF_SESSION_ID",
9300
9876
  telemetryPrefix: "windsurf",
@@ -9314,7 +9890,7 @@ async function hooksWindsurfInstall(argv, logger) {
9314
9890
  logger.log(`yakcc windsurf hook already installed at ${settingsPath} (idempotent).`);
9315
9891
  } else {
9316
9892
  logger.log(`yakcc windsurf hook installed at ${settingsPath}`);
9317
- logger.log(`hook command: ${HOOK_COMMAND4}`);
9893
+ logger.log(`hook command: ${HOOK_COMMAND6}`);
9318
9894
  logger.log("session env var: WINDSURF_SESSION_ID");
9319
9895
  logger.log(`marker: ${markerPath}`);
9320
9896
  logger.log(
@@ -9326,43 +9902,43 @@ async function hooksWindsurfInstall(argv, logger) {
9326
9902
 
9327
9903
  // src/commands/init.ts
9328
9904
  init_dist2();
9329
- import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
9330
- import { join as join18 } from "path";
9905
+ import { existsSync as existsSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
9906
+ import { join as join22 } from "path";
9331
9907
  import { parseArgs as parseArgs12 } from "util";
9332
9908
 
9333
9909
  // src/lib/ide-detect.ts
9334
- import { existsSync as existsSync7 } from "fs";
9335
- import { homedir as homedir2 } from "os";
9336
- import { join as join14 } from "path";
9910
+ import { existsSync as existsSync11 } from "fs";
9911
+ import { homedir as homedir5 } from "os";
9912
+ import { join as join20 } from "path";
9337
9913
  function buildCandidatePaths(home) {
9338
9914
  const platform = process.platform;
9339
9915
  const cursorCandidates = (() => {
9340
9916
  if (platform === "darwin") {
9341
- return [join14(home, "Library", "Application Support", "Cursor")];
9917
+ return [join20(home, "Library", "Application Support", "Cursor")];
9342
9918
  }
9343
9919
  if (platform === "win32") {
9344
9920
  const appdata = process.env.APPDATA;
9345
9921
  if (appdata !== void 0) {
9346
- return [join14(appdata, "Cursor")];
9922
+ return [join20(appdata, "Cursor")];
9347
9923
  }
9348
- return [join14(home, "AppData", "Roaming", "Cursor")];
9924
+ return [join20(home, "AppData", "Roaming", "Cursor")];
9349
9925
  }
9350
- return [join14(home, ".config", "Cursor")];
9926
+ return [join20(home, ".config", "Cursor")];
9351
9927
  })();
9352
9928
  const clineCandidates = [
9353
- join14(home, ".config", "cline"),
9929
+ join20(home, ".config", "cline"),
9354
9930
  // VS Code extension dir marker — check the containing directory prefix since
9355
9931
  // the actual extension dir includes a version suffix.
9356
- join14(home, ".vscode", "extensions", "saoudrizwan.claude-dev")
9932
+ join20(home, ".vscode", "extensions", "saoudrizwan.claude-dev")
9357
9933
  ];
9358
9934
  const continueCandidates = [
9359
- join14(home, ".continue"),
9360
- join14(home, ".vscode", "extensions", "continue.continue")
9935
+ join20(home, ".continue"),
9936
+ join20(home, ".vscode", "extensions", "continue.continue")
9361
9937
  ];
9362
- const windsurfCandidates = [join14(home, ".windsurf")];
9363
- const aiderCandidates = [join14(home, ".aider")];
9938
+ const windsurfCandidates = [join20(home, ".windsurf")];
9939
+ const aiderCandidates = [join20(home, ".aider")];
9364
9940
  return {
9365
- "claude-code": [join14(home, ".claude")],
9941
+ "claude-code": [join20(home, ".claude")],
9366
9942
  cursor: cursorCandidates,
9367
9943
  cline: clineCandidates,
9368
9944
  continue: continueCandidates,
@@ -9371,12 +9947,12 @@ function buildCandidatePaths(home) {
9371
9947
  };
9372
9948
  }
9373
9949
  function detectInstalledIdes(overrideHome) {
9374
- const home = overrideHome ?? homedir2();
9950
+ const home = overrideHome ?? homedir5();
9375
9951
  const candidates = buildCandidatePaths(home);
9376
9952
  const detected = [];
9377
9953
  for (const [name, paths] of Object.entries(candidates)) {
9378
9954
  for (const configDir of paths) {
9379
- if (existsSync7(configDir)) {
9955
+ if (existsSync11(configDir)) {
9380
9956
  detected.push({ name, configDir, installed: true });
9381
9957
  break;
9382
9958
  }
@@ -9393,185 +9969,17 @@ var KNOWN_IDE_NAMES = [
9393
9969
  "aider"
9394
9970
  ];
9395
9971
 
9396
- // src/commands/hooks-cline-install.ts
9397
- import { existsSync as existsSync8, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
9398
- import { homedir as homedir3 } from "os";
9399
- import { join as join15 } from "path";
9400
- import { parseArgs as parseArgs10 } from "util";
9401
- var CLINE_HOOK_MARKER_FILENAME = "yakcc-cline-hook.json";
9402
- var YAKCC_CLINE_MARKER = "yakcc-hook-v1-cline";
9403
- var HOOK_COMMAND5 = "yakcc hook-intercept";
9404
- function readMarker2(markerPath) {
9405
- if (!existsSync8(markerPath)) return null;
9406
- try {
9407
- return JSON.parse(readFileSync9(markerPath, "utf-8"));
9408
- } catch {
9409
- return null;
9410
- }
9411
- }
9412
- function isYakccInstalled4(markerPath) {
9413
- const marker = readMarker2(markerPath);
9414
- return marker !== null && marker._yakcc === YAKCC_CLINE_MARKER;
9415
- }
9416
- async function hooksClineInstall(argv, logger, overrideClineDir) {
9417
- let parsed;
9418
- try {
9419
- parsed = parseArgs10({
9420
- args: [...argv],
9421
- options: {
9422
- target: { type: "string", short: "t" },
9423
- uninstall: { type: "boolean" }
9424
- },
9425
- allowPositionals: false,
9426
- strict: true
9427
- });
9428
- } catch (err) {
9429
- logger.error(`error: ${err.message}`);
9430
- return 1;
9431
- }
9432
- const clineDir = overrideClineDir ?? join15(homedir3(), ".config", "cline");
9433
- const markerPath = join15(clineDir, CLINE_HOOK_MARKER_FILENAME);
9434
- try {
9435
- mkdirSync9(clineDir, { recursive: true });
9436
- } catch (err) {
9437
- logger.error(`error: cannot create ${clineDir}: ${String(err)}`);
9438
- return 1;
9439
- }
9440
- if (parsed.values.uninstall) {
9441
- if (!isYakccInstalled4(markerPath)) {
9442
- logger.log("yakcc cline hook not installed \u2014 nothing to uninstall.");
9443
- return 0;
9444
- }
9445
- try {
9446
- rmSync3(markerPath);
9447
- } catch (err) {
9448
- logger.error(`error: cannot remove ${markerPath}: ${String(err)}`);
9449
- return 1;
9450
- }
9451
- logger.log(`yakcc cline hook marker removed: ${markerPath}`);
9452
- return 0;
9453
- }
9454
- if (isYakccInstalled4(markerPath)) {
9455
- logger.log(`yakcc cline hook already installed at ${markerPath} (idempotent).`);
9456
- return 0;
9457
- }
9458
- const marker = {
9459
- command: HOOK_COMMAND5,
9460
- description: "yakcc tool-call interception hook for Cline",
9461
- sessionEnvVar: "CLINE_SESSION_ID",
9462
- telemetryPrefix: "cline",
9463
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9464
- _yakcc: YAKCC_CLINE_MARKER,
9465
- note: "Cline (saoudrizwan.claude-dev) does not yet expose synchronous tool-call interception via a stable Node.js API. This marker documents the intended wiring (DEC-CLI-HOOKS-CLINE-INSTALL-001). When the Cline extension API stabilises, hook activation requires no reinstall."
9466
- };
9467
- try {
9468
- writeFileSync8(markerPath, `${JSON.stringify(marker, null, 2)}
9469
- `, "utf-8");
9470
- } catch (err) {
9471
- logger.error(`error: cannot write marker ${markerPath}: ${String(err)}`);
9472
- return 1;
9473
- }
9474
- logger.log(`yakcc cline hook marker installed: ${markerPath}`);
9475
- logger.log("note: Cline tool-call interception API not yet stable \u2014 see marker for details.");
9476
- return 0;
9477
- }
9478
-
9479
- // src/commands/hooks-continue-install.ts
9480
- import { existsSync as existsSync9, mkdirSync as mkdirSync10, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
9481
- import { homedir as homedir4 } from "os";
9482
- import { join as join16 } from "path";
9483
- import { parseArgs as parseArgs11 } from "util";
9484
- var CONTINUE_HOOK_MARKER_FILENAME = "yakcc-continue-hook.json";
9485
- var YAKCC_CONTINUE_MARKER = "yakcc-hook-v1-continue";
9486
- var HOOK_COMMAND6 = "yakcc hook-intercept";
9487
- function readMarker3(markerPath) {
9488
- if (!existsSync9(markerPath)) return null;
9489
- try {
9490
- return JSON.parse(readFileSync10(markerPath, "utf-8"));
9491
- } catch {
9492
- return null;
9493
- }
9494
- }
9495
- function isYakccInstalled5(markerPath) {
9496
- const marker = readMarker3(markerPath);
9497
- return marker !== null && marker._yakcc === YAKCC_CONTINUE_MARKER;
9498
- }
9499
- async function hooksContinueInstall(argv, logger, overrideContinueDir) {
9500
- let parsed;
9501
- try {
9502
- parsed = parseArgs11({
9503
- args: [...argv],
9504
- options: {
9505
- target: { type: "string", short: "t" },
9506
- uninstall: { type: "boolean" }
9507
- },
9508
- allowPositionals: false,
9509
- strict: true
9510
- });
9511
- } catch (err) {
9512
- logger.error(`error: ${err.message}`);
9513
- return 1;
9514
- }
9515
- const continueDir = overrideContinueDir ?? join16(homedir4(), ".continue");
9516
- const markerPath = join16(continueDir, CONTINUE_HOOK_MARKER_FILENAME);
9517
- try {
9518
- mkdirSync10(continueDir, { recursive: true });
9519
- } catch (err) {
9520
- logger.error(`error: cannot create ${continueDir}: ${String(err)}`);
9521
- return 1;
9522
- }
9523
- if (parsed.values.uninstall) {
9524
- if (!isYakccInstalled5(markerPath)) {
9525
- logger.log("yakcc continue hook not installed \u2014 nothing to uninstall.");
9526
- return 0;
9527
- }
9528
- try {
9529
- rmSync4(markerPath);
9530
- } catch (err) {
9531
- logger.error(`error: cannot remove ${markerPath}: ${String(err)}`);
9532
- return 1;
9533
- }
9534
- logger.log(`yakcc continue hook marker removed: ${markerPath}`);
9535
- return 0;
9536
- }
9537
- if (isYakccInstalled5(markerPath)) {
9538
- logger.log(`yakcc continue hook already installed at ${markerPath} (idempotent).`);
9539
- return 0;
9540
- }
9541
- const marker = {
9542
- command: HOOK_COMMAND6,
9543
- description: "yakcc tool-call interception hook for Continue.dev",
9544
- sessionEnvVar: "CONTINUE_SESSION_ID",
9545
- telemetryPrefix: "continue",
9546
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9547
- _yakcc: YAKCC_CONTINUE_MARKER,
9548
- note: "Continue.dev (continue.continue) does not yet expose synchronous tool-call interception via a stable Node.js API. This marker documents the intended wiring (DEC-CLI-HOOKS-CONTINUE-INSTALL-001). When the Continue.dev API stabilises, hook activation requires no reinstall."
9549
- };
9550
- try {
9551
- writeFileSync9(markerPath, `${JSON.stringify(marker, null, 2)}
9552
- `, "utf-8");
9553
- } catch (err) {
9554
- logger.error(`error: cannot write marker ${markerPath}: ${String(err)}`);
9555
- return 1;
9556
- }
9557
- logger.log(`yakcc continue hook marker installed: ${markerPath}`);
9558
- logger.log(
9559
- "note: Continue.dev tool-call interception API not yet stable \u2014 see marker for details."
9560
- );
9561
- return 0;
9562
- }
9563
-
9564
9972
  // src/commands/seed-yakcc.ts
9565
9973
  init_dist2();
9566
- import { existsSync as existsSync10 } from "fs";
9567
- import { dirname as dirname6, join as join17 } from "path";
9568
- import { fileURLToPath as fileURLToPath2 } from "url";
9974
+ import { existsSync as existsSync12 } from "fs";
9975
+ import { dirname as dirname7, join as join21 } from "path";
9976
+ import { fileURLToPath as fileURLToPath3 } from "url";
9569
9977
  function findBootstrapSqlite() {
9570
- let dir = dirname6(fileURLToPath2(import.meta.url));
9978
+ let dir = dirname7(fileURLToPath3(import.meta.url));
9571
9979
  for (let i = 0; i < 30; i++) {
9572
- const candidate = join17(dir, "bootstrap", "yakcc.registry.sqlite");
9573
- if (existsSync10(candidate)) return candidate;
9574
- const parent = dirname6(dir);
9980
+ const candidate = join21(dir, "bootstrap", "yakcc.registry.sqlite");
9981
+ if (existsSync12(candidate)) return candidate;
9982
+ const parent = dirname7(dir);
9575
9983
  if (parent === dir) break;
9576
9984
  dir = parent;
9577
9985
  }
@@ -9660,16 +10068,16 @@ var YAKCC_SUBDIRS = ["registry", "telemetry", "config"];
9660
10068
  var DEFAULT_REGISTRY_SUBPATH = ".yakcc/registry.sqlite";
9661
10069
  var RC_FILENAME = ".yakccrc.json";
9662
10070
  function readRc(targetDir) {
9663
- const rcPath = join18(targetDir, RC_FILENAME);
9664
- if (!existsSync11(rcPath)) return null;
10071
+ const rcPath = join22(targetDir, RC_FILENAME);
10072
+ if (!existsSync13(rcPath)) return null;
9665
10073
  try {
9666
- return JSON.parse(readFileSync11(rcPath, "utf-8"));
10074
+ return JSON.parse(readFileSync13(rcPath, "utf-8"));
9667
10075
  } catch {
9668
10076
  return null;
9669
10077
  }
9670
10078
  }
9671
10079
  function writeRc(targetDir, rc) {
9672
- writeFileSync10(join18(targetDir, RC_FILENAME), `${JSON.stringify(rc, null, 2)}
10080
+ writeFileSync10(join22(targetDir, RC_FILENAME), `${JSON.stringify(rc, null, 2)}
9673
10081
  `, "utf-8");
9674
10082
  }
9675
10083
  function validatePeerUrl(url) {
@@ -9694,8 +10102,8 @@ function parseIdeList(raw) {
9694
10102
  return { ok: parts };
9695
10103
  }
9696
10104
  async function installHookForIde(ide, targetDir, logger, overrideHome) {
9697
- const { homedir: homedir6 } = await import("os");
9698
- const home = overrideHome ?? homedir6();
10105
+ const { homedir: homedir7 } = await import("os");
10106
+ const home = overrideHome ?? homedir7();
9699
10107
  switch (ide) {
9700
10108
  case "claude-code": {
9701
10109
  const code = await hooksClaudeCodeInstall(["--target", targetDir], logger);
@@ -9708,15 +10116,15 @@ async function installHookForIde(ide, targetDir, logger, overrideHome) {
9708
10116
  break;
9709
10117
  }
9710
10118
  case "cline": {
9711
- const { join: join20 } = await import("path");
9712
- const clineDir = join20(home, ".config", "cline");
10119
+ const { join: join24 } = await import("path");
10120
+ const clineDir = join24(home, ".config", "cline");
9713
10121
  const code = await hooksClineInstall([], logger, clineDir);
9714
10122
  if (code !== 0) throw new Error(`cline hook install failed (exit ${code})`);
9715
10123
  break;
9716
10124
  }
9717
10125
  case "continue": {
9718
- const { join: join20 } = await import("path");
9719
- const continueDir = join20(home, ".continue");
10126
+ const { join: join24 } = await import("path");
10127
+ const continueDir = join24(home, ".continue");
9720
10128
  const code = await hooksContinueInstall([], logger, continueDir);
9721
10129
  if (code !== 0) throw new Error(`continue hook install failed (exit ${code})`);
9722
10130
  break;
@@ -9727,8 +10135,8 @@ async function installHookForIde(ide, targetDir, logger, overrideHome) {
9727
10135
  break;
9728
10136
  }
9729
10137
  case "aider": {
9730
- const { join: join20 } = await import("path");
9731
- const aiderDir = join20(home, ".aider");
10138
+ const { join: join24 } = await import("path");
10139
+ const aiderDir = join24(home, ".aider");
9732
10140
  const code = await hooksAiderInstall([], logger, aiderDir);
9733
10141
  if (code !== 0) throw new Error(`aider hook install failed (exit ${code})`);
9734
10142
  break;
@@ -9790,12 +10198,12 @@ async function init(argv, logger, opts) {
9790
10198
  }
9791
10199
  explicitIdes = parseResult.ok;
9792
10200
  }
9793
- const yakccDir = join18(targetDir, YAKCC_DIR);
9794
- const yakccDirExists = existsSync11(yakccDir);
10201
+ const yakccDir = join22(targetDir, YAKCC_DIR);
10202
+ const yakccDirExists = existsSync13(yakccDir);
9795
10203
  try {
9796
- mkdirSync11(yakccDir, { recursive: true });
10204
+ mkdirSync13(yakccDir, { recursive: true });
9797
10205
  for (const sub of YAKCC_SUBDIRS) {
9798
- mkdirSync11(join18(yakccDir, sub), { recursive: true });
10206
+ mkdirSync13(join22(yakccDir, sub), { recursive: true });
9799
10207
  }
9800
10208
  } catch (err) {
9801
10209
  logger.error(`error: cannot create ${yakccDir}: ${String(err)}`);
@@ -9806,7 +10214,7 @@ async function init(argv, logger, opts) {
9806
10214
  } else {
9807
10215
  logger.log(" .yakcc/ created");
9808
10216
  }
9809
- const registryPath = join18(targetDir, DEFAULT_REGISTRY_SUBPATH);
10217
+ const registryPath = join22(targetDir, DEFAULT_REGISTRY_SUBPATH);
9810
10218
  const registryCode = await registryInit(["--path", registryPath], logger);
9811
10219
  if (registryCode !== 0) {
9812
10220
  return registryCode;
@@ -9891,7 +10299,7 @@ async function init(argv, logger, opts) {
9891
10299
  try {
9892
10300
  writeRc(targetDir, rc);
9893
10301
  } catch (err) {
9894
- logger.error(`error: cannot write ${join18(targetDir, RC_FILENAME)}: ${String(err)}`);
10302
+ logger.error(`error: cannot write ${join22(targetDir, RC_FILENAME)}: ${String(err)}`);
9895
10303
  return 1;
9896
10304
  }
9897
10305
  logger.log(` ${RC_FILENAME} written`);
@@ -9910,7 +10318,7 @@ async function init(argv, logger, opts) {
9910
10318
  // src/commands/propose.ts
9911
10319
  init_dist();
9912
10320
  init_dist2();
9913
- import { readFileSync as readFileSync12 } from "fs";
10321
+ import { readFileSync as readFileSync14 } from "fs";
9914
10322
  import { parseArgs as parseArgs13 } from "util";
9915
10323
  async function propose(argv, logger) {
9916
10324
  const { values, positionals } = parseArgs13({
@@ -9937,7 +10345,7 @@ async function propose(argv, logger) {
9937
10345
  const registryPath = values.registry ?? DEFAULT_REGISTRY_PATH3;
9938
10346
  let specJson;
9939
10347
  try {
9940
- specJson = readFileSync12(specFilePath, "utf-8");
10348
+ specJson = readFileSync14(specFilePath, "utf-8");
9941
10349
  } catch (err) {
9942
10350
  logger.error(`error: cannot read spec file ${specFilePath}: ${String(err)}`);
9943
10351
  return 1;
@@ -9978,7 +10386,7 @@ async function propose(argv, logger) {
9978
10386
 
9979
10387
  // src/commands/query.ts
9980
10388
  init_dist2();
9981
- import { readFileSync as readFileSync13 } from "fs";
10389
+ import { readFileSync as readFileSync15 } from "fs";
9982
10390
  import { parseArgs as parseArgs14 } from "util";
9983
10391
  function truncate(s, max) {
9984
10392
  return s.length <= max ? s : `${s.slice(0, max - 3)}...`;
@@ -10008,7 +10416,7 @@ async function query(argv, logger, opts) {
10008
10416
  if (cardFilePath !== void 0) {
10009
10417
  let cardJson;
10010
10418
  try {
10011
- cardJson = readFileSync13(cardFilePath, "utf-8");
10419
+ cardJson = readFileSync15(cardFilePath, "utf-8");
10012
10420
  } catch (err) {
10013
10421
  logger.error(`error: cannot read card file ${cardFilePath}: ${String(err)}`);
10014
10422
  return 1;
@@ -10086,9 +10494,9 @@ async function query(argv, logger, opts) {
10086
10494
  }
10087
10495
 
10088
10496
  // src/commands/registry-export.ts
10089
- import { existsSync as existsSync12, mkdirSync as mkdirSync12 } from "fs";
10497
+ import { existsSync as existsSync14, mkdirSync as mkdirSync14 } from "fs";
10090
10498
  import { createRequire as createRequire2 } from "module";
10091
- import { dirname as dirname7, resolve as resolve3 } from "path";
10499
+ import { dirname as dirname8, resolve as resolve4 } from "path";
10092
10500
  import { parseArgs as parseArgs15 } from "util";
10093
10501
  var _registryModuleUrl = new URL("../../../registry/src/index.ts", import.meta.url).href;
10094
10502
  var _req = createRequire2(_registryModuleUrl);
@@ -10106,13 +10514,13 @@ async function registryExport(argv, logger) {
10106
10514
  logger.error("error: --to <path> is required for 'registry export'");
10107
10515
  return 1;
10108
10516
  }
10109
- const sourcePath = resolve3(values.from);
10110
- const outputPath = resolve3(values.to);
10111
- if (!existsSync12(sourcePath)) {
10517
+ const sourcePath = resolve4(values.from);
10518
+ const outputPath = resolve4(values.to);
10519
+ if (!existsSync14(sourcePath)) {
10112
10520
  logger.error(`error: source registry not found at ${sourcePath}`);
10113
10521
  return 1;
10114
10522
  }
10115
- mkdirSync12(dirname7(outputPath), { recursive: true });
10523
+ mkdirSync14(dirname8(outputPath), { recursive: true });
10116
10524
  const escapedOutputPath = outputPath.replace(/'/g, "''");
10117
10525
  let Database2;
10118
10526
  try {
@@ -10153,8 +10561,8 @@ async function registryExport(argv, logger) {
10153
10561
 
10154
10562
  // src/commands/registry-rebuild.ts
10155
10563
  init_dist2();
10156
- import { mkdirSync as mkdirSync13 } from "fs";
10157
- import { dirname as dirname8 } from "path";
10564
+ import { mkdirSync as mkdirSync15 } from "fs";
10565
+ import { dirname as dirname9 } from "path";
10158
10566
  import { parseArgs as parseArgs16 } from "util";
10159
10567
  async function registryRebuild(argv, logger, opts) {
10160
10568
  const { values } = parseArgs16({
@@ -10166,8 +10574,8 @@ async function registryRebuild(argv, logger, opts) {
10166
10574
  strict: true
10167
10575
  });
10168
10576
  const registryPath = values.path ?? DEFAULT_REGISTRY_PATH3;
10169
- const parent = dirname8(registryPath);
10170
- mkdirSync13(parent, { recursive: true });
10577
+ const parent = dirname9(registryPath);
10578
+ mkdirSync15(parent, { recursive: true });
10171
10579
  let embeddingProvider = opts?.embeddings;
10172
10580
  if (embeddingProvider === void 0) {
10173
10581
  const { createLocalEmbeddingProvider: createLocalEmbeddingProvider2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
@@ -10207,7 +10615,7 @@ async function registryRebuild(argv, logger, opts) {
10207
10615
  // src/commands/search.ts
10208
10616
  init_dist();
10209
10617
  init_dist2();
10210
- import { readFileSync as readFileSync14 } from "fs";
10618
+ import { readFileSync as readFileSync16 } from "fs";
10211
10619
  import { parseArgs as parseArgs17 } from "util";
10212
10620
  var FREE_TEXT_SPEC_DEFAULTS = {
10213
10621
  inputs: [],
@@ -10259,7 +10667,7 @@ async function search(argv, logger, opts) {
10259
10667
  if (looksLikePath) {
10260
10668
  let specJson;
10261
10669
  try {
10262
- specJson = readFileSync14(query2, "utf-8");
10670
+ specJson = readFileSync16(query2, "utf-8");
10263
10671
  } catch (err) {
10264
10672
  logger.error(`error: cannot read spec file ${query2}: ${String(err)}`);
10265
10673
  return 1;
@@ -10368,7 +10776,7 @@ async function seed(argv, logger, opts) {
10368
10776
 
10369
10777
  // src/commands/shave.ts
10370
10778
  init_dist2();
10371
- import { resolve as resolve4 } from "path";
10779
+ import { resolve as resolve5 } from "path";
10372
10780
  import { parseArgs as parseArgs19 } from "util";
10373
10781
  var VALID_FOREIGN_POLICIES = ["allow", "reject", "tag"];
10374
10782
  var SHAVE_PARSE_OPTIONS = {
@@ -10419,7 +10827,7 @@ async function shave2(argv, logger) {
10419
10827
  const offline = parsed.values.offline === true;
10420
10828
  let registry;
10421
10829
  try {
10422
- registry = await openRegistry(resolve4(registryPath));
10830
+ registry = await openRegistry(resolve5(registryPath));
10423
10831
  } catch (err) {
10424
10832
  logger.error(`error: failed to open registry at ${registryPath}: ${err.message}`);
10425
10833
  return 1;
@@ -10433,7 +10841,7 @@ async function shave2(argv, logger) {
10433
10841
  findByCanonicalAstHash: registry.findByCanonicalAstHash?.bind(registry)
10434
10842
  };
10435
10843
  try {
10436
- const result = await shave(resolve4(sourcePath), shaveRegistry, { offline, foreignPolicy });
10844
+ const result = await shave(resolve5(sourcePath), shaveRegistry, { offline, foreignPolicy });
10437
10845
  logger.log(`Shaved ${result.sourcePath}:`);
10438
10846
  logger.log(` atoms: ${result.atoms.length}`);
10439
10847
  logger.log(` intentCards: ${result.intentCards.length}`);
@@ -10467,17 +10875,17 @@ async function shave2(argv, logger) {
10467
10875
  }
10468
10876
 
10469
10877
  // src/commands/uninstall.ts
10470
- import { existsSync as existsSync13, readFileSync as readFileSync15, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
10471
- import { homedir as homedir5 } from "os";
10472
- import { join as join19, resolve as resolve5 } from "path";
10878
+ import { existsSync as existsSync15, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
10879
+ import { homedir as homedir6 } from "os";
10880
+ import { join as join23, resolve as resolve6 } from "path";
10473
10881
  import { parseArgs as parseArgs20 } from "util";
10474
10882
  var YAKCC_DIR2 = ".yakcc";
10475
10883
  var RC_FILENAME2 = ".yakccrc.json";
10476
10884
  function readRc2(targetDir) {
10477
- const rcPath = join19(targetDir, RC_FILENAME2);
10478
- if (!existsSync13(rcPath)) return null;
10885
+ const rcPath = join23(targetDir, RC_FILENAME2);
10886
+ if (!existsSync15(rcPath)) return null;
10479
10887
  try {
10480
- return JSON.parse(readFileSync15(rcPath, "utf-8"));
10888
+ return JSON.parse(readFileSync17(rcPath, "utf-8"));
10481
10889
  } catch {
10482
10890
  return null;
10483
10891
  }
@@ -10493,20 +10901,20 @@ function parseIdeList2(raw) {
10493
10901
  return { ok: parts };
10494
10902
  }
10495
10903
  async function uninstallHookForIde(ide, targetDir, logger, overrideHome) {
10496
- const home = overrideHome ?? homedir5();
10904
+ const home = overrideHome ?? homedir6();
10497
10905
  switch (ide) {
10498
10906
  case "claude-code":
10499
10907
  return hooksClaudeCodeInstall(["--target", targetDir, "--uninstall"], logger);
10500
10908
  case "cursor":
10501
10909
  return hooksCursorInstall(["--target", targetDir, "--uninstall"], logger);
10502
10910
  case "cline":
10503
- return hooksClineInstall(["--uninstall"], logger, join19(home, ".config", "cline"));
10911
+ return hooksClineInstall(["--uninstall"], logger, join23(home, ".config", "cline"));
10504
10912
  case "continue":
10505
- return hooksContinueInstall(["--uninstall"], logger, join19(home, ".continue"));
10913
+ return hooksContinueInstall(["--uninstall"], logger, join23(home, ".continue"));
10506
10914
  case "windsurf":
10507
10915
  return hooksWindsurfInstall(["--target", targetDir, "--uninstall"], logger);
10508
10916
  case "aider":
10509
- return hooksAiderInstall(["--uninstall"], logger, join19(home, ".aider"));
10917
+ return hooksAiderInstall(["--uninstall"], logger, join23(home, ".aider"));
10510
10918
  }
10511
10919
  }
10512
10920
  async function uninstall(argv, logger, opts) {
@@ -10582,7 +10990,7 @@ async function uninstall(argv, logger, opts) {
10582
10990
  const updated = { ...rc, installedHooks: updatedHooks };
10583
10991
  try {
10584
10992
  writeFileSync11(
10585
- join19(targetDir, RC_FILENAME2),
10993
+ join23(targetDir, RC_FILENAME2),
10586
10994
  `${JSON.stringify(updated, null, 2)}
10587
10995
  `,
10588
10996
  "utf-8"
@@ -10594,22 +11002,22 @@ async function uninstall(argv, logger, opts) {
10594
11002
  }
10595
11003
  if (doPurge) {
10596
11004
  try {
10597
- rmSync5(join19(targetDir, YAKCC_DIR2), { recursive: true, force: true });
11005
+ rmSync5(join23(targetDir, YAKCC_DIR2), { recursive: true, force: true });
10598
11006
  } catch (err) {
10599
11007
  logger.error(`warning: cannot remove ${YAKCC_DIR2}: ${String(err)}`);
10600
11008
  }
10601
11009
  try {
10602
- rmSync5(join19(targetDir, RC_FILENAME2), { force: true });
11010
+ rmSync5(join23(targetDir, RC_FILENAME2), { force: true });
10603
11011
  } catch (err) {
10604
11012
  logger.error(`warning: cannot remove ${RC_FILENAME2}: ${String(err)}`);
10605
11013
  }
10606
11014
  }
10607
- const absTargetDir = resolve5(targetDir);
11015
+ const absTargetDir = resolve6(targetDir);
10608
11016
  const removedLine = removedIdes.length > 0 ? `Removed from: ${removedIdes.join(", ")}.` : "No hooks removed (nothing was installed).";
10609
11017
  if (doPurge) {
10610
11018
  logger.log(`${removedLine} Purged .yakcc/ and ${RC_FILENAME2} at ${absTargetDir}.`);
10611
11019
  } else {
10612
- logger.log(`${removedLine} Registry preserved at ${join19(absTargetDir, YAKCC_DIR2)}.`);
11020
+ logger.log(`${removedLine} Registry preserved at ${join23(absTargetDir, YAKCC_DIR2)}.`);
10613
11021
  }
10614
11022
  return 0;
10615
11023
  }
@@ -10677,6 +11085,13 @@ COMMANDS
10677
11085
  hooks cursor install Wire yakcc tool-call interception for Cursor
10678
11086
  [--target <dir>] Target project directory (default: .)
10679
11087
  [--uninstall] Remove the yakcc cursor hook entry
11088
+ hooks cline install Wire yakcc tool-call interception for Cline
11089
+ [--target <dir>] Target project directory (default: .)
11090
+ [--uninstall] Remove the yakcc cline hook entry
11091
+ hooks continue install Wire yakcc tool-call interception for Continue.dev
11092
+ [--target <dir>] Target project directory (default: .)
11093
+ [--uninstall] Remove the yakcc continue hook entry
11094
+ hook-intercept (internal -- invoked by IDE hook configs via PreToolUse)
10680
11095
  federation serve --registry <p> Start a read-only HTTP registry server
10681
11096
  [--port <n>] [--host <h>]
10682
11097
  federation mirror --remote <url> Mirror all blocks from a remote registry peer
@@ -10795,11 +11210,35 @@ async function runCli(argv, logger = CONSOLE_LOGGER, opts) {
10795
11210
  );
10796
11211
  return 1;
10797
11212
  }
11213
+ if (subcommand === "cline") {
11214
+ const [hooksSub, ...hooksRest] = rest;
11215
+ if (hooksSub === "install") {
11216
+ return hooksClineInstall(hooksRest, logger);
11217
+ }
11218
+ logger.error(
11219
+ `error: unknown hooks cline subcommand: ${hooksSub ?? "(none)"}. Did you mean 'hooks cline install'?`
11220
+ );
11221
+ return 1;
11222
+ }
11223
+ if (subcommand === "continue") {
11224
+ const [hooksSub, ...hooksRest] = rest;
11225
+ if (hooksSub === "install") {
11226
+ return hooksContinueInstall(hooksRest, logger);
11227
+ }
11228
+ logger.error(
11229
+ `error: unknown hooks continue subcommand: ${hooksSub ?? "(none)"}. Did you mean 'hooks continue install'?`
11230
+ );
11231
+ return 1;
11232
+ }
10798
11233
  logger.error(
10799
- `error: unknown hooks subcommand: ${subcommand ?? "(none)"}. Did you mean 'hooks claude-code install', 'hooks cursor install', 'hooks windsurf install', or 'hooks aider install'?`
11234
+ `error: unknown hooks subcommand: ${subcommand ?? "(none)"}. Did you mean 'hooks claude-code install', 'hooks cursor install', 'hooks windsurf install', 'hooks aider install', 'hooks cline install', or 'hooks continue install'?`
10800
11235
  );
10801
11236
  return 1;
10802
11237
  }
11238
+ case "hook-intercept": {
11239
+ const hookInterceptArgv = subcommand !== void 0 ? [subcommand, ...rest] : rest;
11240
+ return hookIntercept(hookInterceptArgv, logger);
11241
+ }
10803
11242
  case void 0:
10804
11243
  case "--help":
10805
11244
  case "-h": {