qodewk 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +74 -21
  2. package/dist/index.cjs +1597 -636
  3. package/package.json +3 -4
package/dist/index.cjs CHANGED
@@ -1153,8 +1153,8 @@ var require_command = __commonJS({
1153
1153
  "use strict";
1154
1154
  var EventEmitter2 = require("events").EventEmitter;
1155
1155
  var childProcess = require("child_process");
1156
- var path8 = require("path");
1157
- var fs7 = require("fs");
1156
+ var path9 = require("path");
1157
+ var fs9 = require("fs");
1158
1158
  var process2 = require("process");
1159
1159
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1160
1160
  var { CommanderError: CommanderError2 } = require_error();
@@ -2135,7 +2135,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2135
2135
  * @param {string} subcommandName
2136
2136
  */
2137
2137
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2138
- if (fs7.existsSync(executableFile)) return;
2138
+ if (fs9.existsSync(executableFile)) return;
2139
2139
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2140
2140
  const executableMissing = `'${executableFile}' does not exist
2141
2141
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2153,11 +2153,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2153
2153
  let launchWithNode = false;
2154
2154
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2155
2155
  function findFile(baseDir, baseName) {
2156
- const localBin = path8.resolve(baseDir, baseName);
2157
- if (fs7.existsSync(localBin)) return localBin;
2158
- if (sourceExt.includes(path8.extname(baseName))) return void 0;
2156
+ const localBin = path9.resolve(baseDir, baseName);
2157
+ if (fs9.existsSync(localBin)) return localBin;
2158
+ if (sourceExt.includes(path9.extname(baseName))) return void 0;
2159
2159
  const foundExt = sourceExt.find(
2160
- (ext) => fs7.existsSync(`${localBin}${ext}`)
2160
+ (ext) => fs9.existsSync(`${localBin}${ext}`)
2161
2161
  );
2162
2162
  if (foundExt) return `${localBin}${foundExt}`;
2163
2163
  return void 0;
@@ -2169,21 +2169,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2169
2169
  if (this._scriptPath) {
2170
2170
  let resolvedScriptPath;
2171
2171
  try {
2172
- resolvedScriptPath = fs7.realpathSync(this._scriptPath);
2172
+ resolvedScriptPath = fs9.realpathSync(this._scriptPath);
2173
2173
  } catch {
2174
2174
  resolvedScriptPath = this._scriptPath;
2175
2175
  }
2176
- executableDir = path8.resolve(
2177
- path8.dirname(resolvedScriptPath),
2176
+ executableDir = path9.resolve(
2177
+ path9.dirname(resolvedScriptPath),
2178
2178
  executableDir
2179
2179
  );
2180
2180
  }
2181
2181
  if (executableDir) {
2182
2182
  let localFile = findFile(executableDir, executableFile);
2183
2183
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2184
- const legacyName = path8.basename(
2184
+ const legacyName = path9.basename(
2185
2185
  this._scriptPath,
2186
- path8.extname(this._scriptPath)
2186
+ path9.extname(this._scriptPath)
2187
2187
  );
2188
2188
  if (legacyName !== this._name) {
2189
2189
  localFile = findFile(
@@ -2194,7 +2194,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2194
2194
  }
2195
2195
  executableFile = localFile || executableFile;
2196
2196
  }
2197
- launchWithNode = sourceExt.includes(path8.extname(executableFile));
2197
+ launchWithNode = sourceExt.includes(path9.extname(executableFile));
2198
2198
  let proc;
2199
2199
  if (process2.platform !== "win32") {
2200
2200
  if (launchWithNode) {
@@ -3041,7 +3041,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3041
3041
  * @return {Command}
3042
3042
  */
3043
3043
  nameFromFilename(filename) {
3044
- this._name = path8.basename(filename, path8.extname(filename));
3044
+ this._name = path9.basename(filename, path9.extname(filename));
3045
3045
  return this;
3046
3046
  }
3047
3047
  /**
@@ -3055,9 +3055,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3055
3055
  * @param {string} [path]
3056
3056
  * @return {(string|null|Command)}
3057
3057
  */
3058
- executableDir(path9) {
3059
- if (path9 === void 0) return this._executableDir;
3060
- this._executableDir = path9;
3058
+ executableDir(path10) {
3059
+ if (path10 === void 0) return this._executableDir;
3060
+ this._executableDir = path10;
3061
3061
  return this;
3062
3062
  }
3063
3063
  /**
@@ -4067,10 +4067,10 @@ var require_src2 = __commonJS({
4067
4067
  var fs_1 = require("fs");
4068
4068
  var debug_1 = __importDefault(require_src());
4069
4069
  var log = debug_1.default("@kwsites/file-exists");
4070
- function check(path8, isFile, isDirectory) {
4071
- log(`checking %s`, path8);
4070
+ function check(path9, isFile, isDirectory) {
4071
+ log(`checking %s`, path9);
4072
4072
  try {
4073
- const stat = fs_1.statSync(path8);
4073
+ const stat = fs_1.statSync(path9);
4074
4074
  if (stat.isFile() && isFile) {
4075
4075
  log(`[OK] path represents a file`);
4076
4076
  return true;
@@ -4090,8 +4090,8 @@ var require_src2 = __commonJS({
4090
4090
  throw e;
4091
4091
  }
4092
4092
  }
4093
- function exists2(path8, type = exports2.READABLE) {
4094
- return check(path8, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
4093
+ function exists2(path9, type = exports2.READABLE) {
4094
+ return check(path9, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
4095
4095
  }
4096
4096
  exports2.exists = exists2;
4097
4097
  exports2.FILE = 1;
@@ -4155,8 +4155,8 @@ var require_dist2 = __commonJS({
4155
4155
  });
4156
4156
 
4157
4157
  // src/index.ts
4158
- var fs6 = __toESM(require("fs"), 1);
4159
- var path7 = __toESM(require("path"), 1);
4158
+ var fs8 = __toESM(require("fs"), 1);
4159
+ var path8 = __toESM(require("path"), 1);
4160
4160
 
4161
4161
  // ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
4162
4162
  var import_index = __toESM(require_commander(), 1);
@@ -4176,204 +4176,7 @@ var {
4176
4176
  } = import_index.default;
4177
4177
 
4178
4178
  // src/index.ts
4179
- var import_picocolors = __toESM(require_picocolors(), 1);
4180
-
4181
- // src/barcode.ts
4182
- var CODE128_PATTERNS = [
4183
- "11011001100",
4184
- "11001101100",
4185
- "11001100110",
4186
- "10010011000",
4187
- "10010001100",
4188
- "10001001100",
4189
- "10011001000",
4190
- "10011000100",
4191
- "10001100100",
4192
- "11001001000",
4193
- "11001000100",
4194
- "11000100100",
4195
- "10110011100",
4196
- "10011011100",
4197
- "10011001110",
4198
- "10111001100",
4199
- "10011101100",
4200
- "10011100110",
4201
- "11001110010",
4202
- "11001011100",
4203
- "11001001110",
4204
- "11011100100",
4205
- "11001110100",
4206
- "11101101110",
4207
- "11101001100",
4208
- "11100101100",
4209
- "11100100110",
4210
- "11101100100",
4211
- "11100110100",
4212
- "11100110010",
4213
- "11011011000",
4214
- "11011000110",
4215
- "11000110110",
4216
- "10100011000",
4217
- "10001011000",
4218
- "10001000110",
4219
- "10110001000",
4220
- "10001101000",
4221
- "10001100010",
4222
- "11010001000",
4223
- "11000101000",
4224
- "11000100010",
4225
- "10110111000",
4226
- "10110001110",
4227
- "10001101110",
4228
- "10111011000",
4229
- "10111000110",
4230
- "10001110110",
4231
- "11101110110",
4232
- "11010001110",
4233
- "11000101110",
4234
- "11011101000",
4235
- "11011100010",
4236
- "11011101110",
4237
- "11101011000",
4238
- "11101000110",
4239
- "11100010110",
4240
- "11101101000",
4241
- "11101100010",
4242
- "11100011010",
4243
- "11101111010",
4244
- "11001000010",
4245
- "11110001010",
4246
- "10100110000",
4247
- "10100001100",
4248
- "10010110000",
4249
- "10010000110",
4250
- "10000101100",
4251
- "10000100110",
4252
- "10110010000",
4253
- "10110000100",
4254
- "10011010000",
4255
- "10011000010",
4256
- "10000110100",
4257
- "10000110010",
4258
- "11000010010",
4259
- "11001010000",
4260
- "11110111010",
4261
- "11000010100",
4262
- "10001111010",
4263
- "10100111100",
4264
- "10010111100",
4265
- "10010011110",
4266
- "10111100100",
4267
- "10011110100",
4268
- "10011110010",
4269
- "11110100100",
4270
- "11110010100",
4271
- "11110010010",
4272
- "11011011110",
4273
- "11011110110",
4274
- "11110110110",
4275
- "10101111000",
4276
- "10100011110",
4277
- "10001011110",
4278
- "10111101000",
4279
- "10111100010",
4280
- "11110101000",
4281
- "11110100010",
4282
- "10111011110",
4283
- "10111101110",
4284
- "11101011110",
4285
- "11110101110",
4286
- "11010000100",
4287
- "11010010000",
4288
- "11010011100",
4289
- "1100011101011"
4290
- ];
4291
- var START_B = 104;
4292
- var STOP = 106;
4293
- function encodeCode128B(text) {
4294
- const codes = [START_B];
4295
- for (const ch of text) {
4296
- const code = ch.charCodeAt(0) - 32;
4297
- if (code < 0 || code > 95) {
4298
- throw new Error(`Code 128-B cannot encode character: ${JSON.stringify(ch)}`);
4299
- }
4300
- codes.push(code);
4301
- }
4302
- let checksum = START_B;
4303
- for (let i2 = 1; i2 < codes.length; i2++) {
4304
- checksum += codes[i2] * i2;
4305
- }
4306
- codes.push(checksum % 103);
4307
- codes.push(STOP);
4308
- return codes;
4309
- }
4310
- function code128Modules(text) {
4311
- const codes = encodeCode128B(text);
4312
- let modules = "";
4313
- for (const code of codes) {
4314
- modules += CODE128_PATTERNS[code] ?? "";
4315
- }
4316
- return modules;
4317
- }
4318
- function renderTerminalBarcode(text, options = {}) {
4319
- const maxWidth = Math.max(16, options.maxWidth ?? 52);
4320
- const height = Math.max(2, options.height ?? 3);
4321
- const quietZone = Math.max(0, options.quietZone ?? 10);
4322
- let payload = text;
4323
- payload = [...payload].filter((ch) => {
4324
- const c3 = ch.charCodeAt(0);
4325
- return c3 >= 32 && c3 <= 126;
4326
- }).join("");
4327
- if (!payload) payload = "QODEWK";
4328
- let modules;
4329
- try {
4330
- modules = code128Modules(payload);
4331
- } catch {
4332
- modules = code128Modules("QODEWK");
4333
- }
4334
- const withQuiet = "0".repeat(quietZone) + modules + "0".repeat(quietZone);
4335
- const scaled = downsampleModules(withQuiet, maxWidth);
4336
- const row = modulesToBlocks(scaled);
4337
- return Array.from({ length: height }, () => row);
4338
- }
4339
- function downsampleModules(modules, maxWidth) {
4340
- if (modules.length <= maxWidth) return modules;
4341
- const out = [];
4342
- const ratio = modules.length / maxWidth;
4343
- for (let i2 = 0; i2 < maxWidth; i2++) {
4344
- const start = Math.floor(i2 * ratio);
4345
- const end = Math.max(start + 1, Math.floor((i2 + 1) * ratio));
4346
- let bars = 0;
4347
- for (let j2 = start; j2 < end && j2 < modules.length; j2++) {
4348
- if (modules[j2] === "1") bars++;
4349
- }
4350
- out.push(bars * 2 >= end - start ? "1" : "0");
4351
- }
4352
- return out.join("");
4353
- }
4354
- function modulesToBlocks(modules) {
4355
- let out = "";
4356
- for (const bit of modules) {
4357
- out += bit === "1" ? "\u2588" : " ";
4358
- }
4359
- return out;
4360
- }
4361
- function pickBarcodePayload(input) {
4362
- const maxChars = input.maxChars ?? 24;
4363
- const candidates = [];
4364
- if (input.publicUrl) candidates.push(input.publicUrl);
4365
- if (input.displayHost && input.receiptId) {
4366
- candidates.push(`${input.displayHost}/r/${input.receiptId}`);
4367
- }
4368
- candidates.push(input.receiptId);
4369
- if (input.receiptId.length > 12) {
4370
- candidates.push(input.receiptId.replace(/^rec_/, "").slice(0, 12));
4371
- }
4372
- for (const c3 of candidates) {
4373
- if (c3.length <= maxChars) return c3;
4374
- }
4375
- return candidates[candidates.length - 1].slice(0, maxChars);
4376
- }
4179
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
4377
4180
 
4378
4181
  // ../core/dist/index.js
4379
4182
  var crypto2 = __toESM(require("crypto"), 1);
@@ -4856,8 +4659,8 @@ function getErrorMap() {
4856
4659
 
4857
4660
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
4858
4661
  var makeIssue = (params) => {
4859
- const { data, path: path8, errorMaps, issueData } = params;
4860
- const fullPath = [...path8, ...issueData.path || []];
4662
+ const { data, path: path9, errorMaps, issueData } = params;
4663
+ const fullPath = [...path9, ...issueData.path || []];
4861
4664
  const fullIssue = {
4862
4665
  ...issueData,
4863
4666
  path: fullPath
@@ -4973,11 +4776,11 @@ var errorUtil;
4973
4776
 
4974
4777
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
4975
4778
  var ParseInputLazyPath = class {
4976
- constructor(parent, value, path8, key) {
4779
+ constructor(parent, value, path9, key) {
4977
4780
  this._cachedPath = [];
4978
4781
  this.parent = parent;
4979
4782
  this.data = value;
4980
- this._path = path8;
4783
+ this._path = path9;
4981
4784
  this._key = key;
4982
4785
  }
4983
4786
  get path() {
@@ -9089,8 +8892,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
9089
8892
  function forEachLineWithContent(input, callback) {
9090
8893
  return toLinesWithContent(input, true).map((line) => callback(line));
9091
8894
  }
9092
- function folderExists(path8) {
9093
- return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
8895
+ function folderExists(path9) {
8896
+ return (0, import_file_exists.exists)(path9, import_file_exists.FOLDER);
9094
8897
  }
9095
8898
  function append(target, item) {
9096
8899
  if (Array.isArray(target)) {
@@ -9492,8 +9295,8 @@ function checkIsRepoRootTask() {
9492
9295
  commands,
9493
9296
  format: "utf-8",
9494
9297
  onError,
9495
- parser(path8) {
9496
- return /^\.(git)?$/.test(path8.trim());
9298
+ parser(path9) {
9299
+ return /^\.(git)?$/.test(path9.trim());
9497
9300
  }
9498
9301
  };
9499
9302
  }
@@ -9927,11 +9730,11 @@ function parseGrep(grep) {
9927
9730
  const paths = /* @__PURE__ */ new Set();
9928
9731
  const results = {};
9929
9732
  forEachLineWithContent(grep, (input) => {
9930
- const [path8, line, preview] = input.split(NULL);
9931
- paths.add(path8);
9932
- (results[path8] = results[path8] || []).push({
9733
+ const [path9, line, preview] = input.split(NULL);
9734
+ paths.add(path9);
9735
+ (results[path9] = results[path9] || []).push({
9933
9736
  line: asNumber(line),
9934
- path: path8,
9737
+ path: path9,
9935
9738
  preview
9936
9739
  });
9937
9740
  });
@@ -10695,14 +10498,14 @@ var init_hash_object = __esm({
10695
10498
  init_task();
10696
10499
  }
10697
10500
  });
10698
- function parseInit(bare, path8, text) {
10501
+ function parseInit(bare, path9, text) {
10699
10502
  const response = String(text).trim();
10700
10503
  let result;
10701
10504
  if (result = initResponseRegex.exec(response)) {
10702
- return new InitSummary(bare, path8, false, result[1]);
10505
+ return new InitSummary(bare, path9, false, result[1]);
10703
10506
  }
10704
10507
  if (result = reInitResponseRegex.exec(response)) {
10705
- return new InitSummary(bare, path8, true, result[1]);
10508
+ return new InitSummary(bare, path9, true, result[1]);
10706
10509
  }
10707
10510
  let gitDir = "";
10708
10511
  const tokens = response.split(" ");
@@ -10713,7 +10516,7 @@ function parseInit(bare, path8, text) {
10713
10516
  break;
10714
10517
  }
10715
10518
  }
10716
- return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
10519
+ return new InitSummary(bare, path9, /^re/i.test(response), gitDir);
10717
10520
  }
10718
10521
  var InitSummary;
10719
10522
  var initResponseRegex;
@@ -10722,9 +10525,9 @@ var init_InitSummary = __esm({
10722
10525
  "src/lib/responses/InitSummary.ts"() {
10723
10526
  "use strict";
10724
10527
  InitSummary = class {
10725
- constructor(bare, path8, existing, gitDir) {
10528
+ constructor(bare, path9, existing, gitDir) {
10726
10529
  this.bare = bare;
10727
- this.path = path8;
10530
+ this.path = path9;
10728
10531
  this.existing = existing;
10729
10532
  this.gitDir = gitDir;
10730
10533
  }
@@ -10736,7 +10539,7 @@ var init_InitSummary = __esm({
10736
10539
  function hasBareCommand(command) {
10737
10540
  return command.includes(bareCommand);
10738
10541
  }
10739
- function initTask(bare = false, path8, customArgs) {
10542
+ function initTask(bare = false, path9, customArgs) {
10740
10543
  const commands = ["init", ...customArgs];
10741
10544
  if (bare && !hasBareCommand(commands)) {
10742
10545
  commands.splice(1, 0, bareCommand);
@@ -10745,7 +10548,7 @@ function initTask(bare = false, path8, customArgs) {
10745
10548
  commands,
10746
10549
  format: "utf-8",
10747
10550
  parser(text) {
10748
- return parseInit(commands.includes("--bare"), path8, text);
10551
+ return parseInit(commands.includes("--bare"), path9, text);
10749
10552
  }
10750
10553
  };
10751
10554
  }
@@ -11560,12 +11363,12 @@ var init_FileStatusSummary = __esm({
11560
11363
  "use strict";
11561
11364
  fromPathRegex = /^(.+)\0(.+)$/;
11562
11365
  FileStatusSummary = class {
11563
- constructor(path8, index, working_dir) {
11564
- this.path = path8;
11366
+ constructor(path9, index, working_dir) {
11367
+ this.path = path9;
11565
11368
  this.index = index;
11566
11369
  this.working_dir = working_dir;
11567
11370
  if (index === "R" || working_dir === "R") {
11568
- const detail = fromPathRegex.exec(path8) || [null, path8, path8];
11371
+ const detail = fromPathRegex.exec(path9) || [null, path9, path9];
11569
11372
  this.from = detail[2] || "";
11570
11373
  this.path = detail[1] || "";
11571
11374
  }
@@ -11596,14 +11399,14 @@ function splitLine(result, lineStr) {
11596
11399
  default:
11597
11400
  return;
11598
11401
  }
11599
- function data(index, workingDir, path8) {
11402
+ function data(index, workingDir, path9) {
11600
11403
  const raw = `${index}${workingDir}`;
11601
11404
  const handler = parsers6.get(raw);
11602
11405
  if (handler) {
11603
- handler(result, path8);
11406
+ handler(result, path9);
11604
11407
  }
11605
11408
  if (raw !== "##" && raw !== "!!") {
11606
- result.files.push(new FileStatusSummary(path8, index, workingDir));
11409
+ result.files.push(new FileStatusSummary(path9, index, workingDir));
11607
11410
  }
11608
11411
  }
11609
11412
  }
@@ -11954,9 +11757,9 @@ var init_simple_git_api = __esm({
11954
11757
  next
11955
11758
  );
11956
11759
  }
11957
- hashObject(path8, write) {
11760
+ hashObject(path9, write) {
11958
11761
  return this._runTask(
11959
- hashObjectTask(path8, write === true),
11762
+ hashObjectTask(path9, write === true),
11960
11763
  trailingFunctionArgument(arguments)
11961
11764
  );
11962
11765
  }
@@ -12310,8 +12113,8 @@ var init_branch = __esm({
12310
12113
  }
12311
12114
  });
12312
12115
  function toPath(input) {
12313
- const path8 = input.trim().replace(/^["']|["']$/g, "");
12314
- return path8 && (0, import_node_path.normalize)(path8);
12116
+ const path9 = input.trim().replace(/^["']|["']$/g, "");
12117
+ return path9 && (0, import_node_path.normalize)(path9);
12315
12118
  }
12316
12119
  var parseCheckIgnore;
12317
12120
  var init_CheckIgnore = __esm({
@@ -12596,8 +12399,8 @@ __export2(sub_module_exports, {
12596
12399
  subModuleTask: () => subModuleTask,
12597
12400
  updateSubModuleTask: () => updateSubModuleTask
12598
12401
  });
12599
- function addSubModuleTask(repo, path8) {
12600
- return subModuleTask(["add", repo, path8]);
12402
+ function addSubModuleTask(repo, path9) {
12403
+ return subModuleTask(["add", repo, path9]);
12601
12404
  }
12602
12405
  function initSubModuleTask(customArgs) {
12603
12406
  return subModuleTask(["init", ...customArgs]);
@@ -12911,8 +12714,8 @@ var require_git = __commonJS2({
12911
12714
  }
12912
12715
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
12913
12716
  };
12914
- Git2.prototype.submoduleAdd = function(repo, path8, then) {
12915
- return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
12717
+ Git2.prototype.submoduleAdd = function(repo, path9, then) {
12718
+ return this._runTask(addSubModuleTask2(repo, path9), trailingFunctionArgument2(arguments));
12916
12719
  };
12917
12720
  Git2.prototype.submoduleUpdate = function(args, then) {
12918
12721
  return this._runTask(
@@ -13493,6 +13296,57 @@ function parseSinceOption(since) {
13493
13296
  }
13494
13297
  return void 0;
13495
13298
  }
13299
+ async function resolveGitSha(git, ref) {
13300
+ const trimmed2 = ref.trim();
13301
+ if (!trimmed2) {
13302
+ throw new Error("Git reference cannot be empty.");
13303
+ }
13304
+ try {
13305
+ const full = (await git.raw(["rev-parse", "--verify", trimmed2])).trim();
13306
+ if (/^[0-9a-f]{40}$/i.test(full)) {
13307
+ return full.toLowerCase();
13308
+ }
13309
+ } catch {
13310
+ }
13311
+ try {
13312
+ const full = (await git.raw(["rev-parse", trimmed2])).trim();
13313
+ if (/^[0-9a-f]{40}$/i.test(full)) {
13314
+ return full.toLowerCase();
13315
+ }
13316
+ } catch {
13317
+ }
13318
+ try {
13319
+ const fullOrigin = (await git.raw(["rev-parse", `origin/${trimmed2}`])).trim();
13320
+ if (/^[0-9a-f]{40}$/i.test(fullOrigin)) {
13321
+ return fullOrigin.toLowerCase();
13322
+ }
13323
+ } catch {
13324
+ }
13325
+ try {
13326
+ const fullRef = (await git.raw(["rev-parse", `refs/heads/${trimmed2}`])).trim();
13327
+ if (/^[0-9a-f]{40}$/i.test(fullRef)) {
13328
+ return fullRef.toLowerCase();
13329
+ }
13330
+ } catch {
13331
+ }
13332
+ if (/^[0-9a-f]{40}$/i.test(trimmed2)) {
13333
+ return trimmed2.toLowerCase();
13334
+ }
13335
+ throw new Error(`Unable to resolve git reference '${ref}' to a valid commit SHA.`);
13336
+ }
13337
+ async function detectDefaultBaseBranch(git) {
13338
+ const candidates = ["origin/main", "main", "origin/master", "master"];
13339
+ for (const c3 of candidates) {
13340
+ try {
13341
+ const sha = (await git.raw(["rev-parse", "--verify", c3])).trim();
13342
+ if (/^[0-9a-f]{40}$/i.test(sha)) {
13343
+ return c3;
13344
+ }
13345
+ } catch {
13346
+ }
13347
+ }
13348
+ return "main";
13349
+ }
13496
13350
  async function extractGitMetrics(optionsOrPath = process.cwd()) {
13497
13351
  const options = typeof optionsOrPath === "string" ? { repoPath: optionsOrPath } : optionsOrPath;
13498
13352
  const repoPath = options.repoPath || process.cwd();
@@ -13503,9 +13357,22 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
13503
13357
  }
13504
13358
  const branchSummary = await git.branch();
13505
13359
  const branch = branchSummary.current || "HEAD";
13506
- let headSha = options.headSha || "0000000000000000000000000000000000000000";
13507
- let baseSha = options.baseSha;
13360
+ let headSha;
13361
+ if (options.headSha) {
13362
+ headSha = await resolveGitSha(git, options.headSha);
13363
+ } else {
13364
+ try {
13365
+ headSha = (await git.raw(["rev-parse", "HEAD"])).trim().toLowerCase();
13366
+ } catch {
13367
+ headSha = "0000000000000000000000000000000000000000";
13368
+ }
13369
+ }
13370
+ let baseSha = void 0;
13371
+ if (options.baseSha) {
13372
+ baseSha = await resolveGitSha(git, options.baseSha);
13373
+ }
13508
13374
  let commitMessage = void 0;
13375
+ let commitDate = void 0;
13509
13376
  let commitsCount = 1;
13510
13377
  const sinceDate = parseSinceOption(options.since);
13511
13378
  if (sinceDate && !options.baseSha) {
@@ -13537,6 +13404,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
13537
13404
  headSha = log.latest.hash;
13538
13405
  }
13539
13406
  commitMessage = log.latest.message;
13407
+ commitDate = log.latest.date;
13540
13408
  if (!options.baseSha && log.all.length > 1 && log.all[1]) {
13541
13409
  baseSha = log.all[1].hash;
13542
13410
  }
@@ -13544,6 +13412,27 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
13544
13412
  } catch {
13545
13413
  }
13546
13414
  }
13415
+ if (!commitMessage && headSha && headSha !== "0000000000000000000000000000000000000000") {
13416
+ try {
13417
+ const headLog = await git.show(["-s", "--format=%B%x00%aI", headSha]);
13418
+ const parts = headLog.split("\0");
13419
+ if (parts[0])
13420
+ commitMessage = parts[0].trim();
13421
+ if (parts[1])
13422
+ commitDate = parts[1].trim();
13423
+ } catch {
13424
+ }
13425
+ }
13426
+ if (baseSha && headSha && baseSha !== headSha) {
13427
+ try {
13428
+ const countRaw = await git.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
13429
+ const count = parseInt(countRaw.trim(), 10);
13430
+ if (!isNaN(count) && count > 0) {
13431
+ commitsCount = count;
13432
+ }
13433
+ } catch {
13434
+ }
13435
+ }
13547
13436
  let repoIdentifier = path.basename(path.resolve(repoPath));
13548
13437
  try {
13549
13438
  const remotes = await git.getRemotes(true);
@@ -13556,22 +13445,34 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
13556
13445
  const projectAlias = path.basename(path.resolve(repoPath)).toLowerCase().replace(/[^a-z0-9_-]/g, "-");
13557
13446
  const repoHash = computeSaltedHash(repoIdentifier);
13558
13447
  let diffSummary;
13559
- if (baseSha && headSha && baseSha !== headSha) {
13560
- const status = await git.status();
13561
- if (status.files.length > 0) {
13562
- diffSummary = await git.diffSummary([baseSha]);
13448
+ try {
13449
+ if (baseSha && headSha && baseSha !== headSha) {
13450
+ const status = await git.status();
13451
+ if (status.files.length > 0) {
13452
+ diffSummary = await git.diffSummary([baseSha]);
13453
+ } else {
13454
+ diffSummary = await git.diffSummary([`${baseSha}...${headSha}`]).catch(async () => {
13455
+ return await git.diffSummary([`${baseSha}..${headSha}`]);
13456
+ });
13457
+ }
13563
13458
  } else {
13564
- diffSummary = await git.diffSummary([`${baseSha}..${headSha}`]);
13459
+ const status = await git.status();
13460
+ if (status.files.length > 0) {
13461
+ diffSummary = await git.diffSummary(["HEAD"]);
13462
+ } else if (baseSha) {
13463
+ diffSummary = await git.diffSummary([`${baseSha}..${headSha}`]);
13464
+ } else if (headSha !== "0000000000000000000000000000000000000000") {
13465
+ diffSummary = await git.diffSummary([`${headSha}~1..${headSha}`]).catch(async () => {
13466
+ return await git.diffSummary([headSha]);
13467
+ });
13468
+ } else {
13469
+ diffSummary = { changed: 0, insertions: 0, deletions: 0, files: [] };
13470
+ }
13565
13471
  }
13566
- } else {
13567
- const status = await git.status();
13568
- if (status.files.length > 0) {
13472
+ } catch {
13473
+ try {
13569
13474
  diffSummary = await git.diffSummary(["HEAD"]);
13570
- } else if (baseSha) {
13571
- diffSummary = await git.diffSummary([`${baseSha}..${headSha}`]);
13572
- } else if (headSha !== "0000000000000000000000000000000000000000") {
13573
- diffSummary = await git.diffSummary([headSha]);
13574
- } else {
13475
+ } catch {
13575
13476
  diffSummary = { changed: 0, insertions: 0, deletions: 0, files: [] };
13576
13477
  }
13577
13478
  }
@@ -13621,6 +13522,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
13621
13522
  repoHash,
13622
13523
  projectAlias,
13623
13524
  commitMessage,
13525
+ commitDate,
13624
13526
  commitsCount
13625
13527
  };
13626
13528
  }
@@ -13757,7 +13659,17 @@ var RATE_CARDS = {
13757
13659
  cacheWritePerMTok: 3.75,
13758
13660
  contextWindow: 2e5
13759
13661
  },
13760
- "claude-3-7-sonnet": {
13662
+ "claude-sonnet-4": {
13663
+ id: "claude-sonnet-4",
13664
+ provider: "anthropic",
13665
+ name: "Claude Sonnet 4",
13666
+ inputPerMTok: 3,
13667
+ outputPerMTok: 15,
13668
+ cacheReadPerMTok: 0.3,
13669
+ cacheWritePerMTok: 3.75,
13670
+ contextWindow: 2e5
13671
+ },
13672
+ "claude-3-7-sonnet": {
13761
13673
  id: "claude-3-7-sonnet",
13762
13674
  provider: "anthropic",
13763
13675
  name: "Claude 3.7 Sonnet",
@@ -13798,6 +13710,26 @@ var RATE_CARDS = {
13798
13710
  cacheWritePerMTok: 2.5,
13799
13711
  contextWindow: 128e3
13800
13712
  },
13713
+ "gpt-4o-mini": {
13714
+ id: "gpt-4o-mini",
13715
+ provider: "openai",
13716
+ name: "GPT-4o Mini",
13717
+ inputPerMTok: 0.15,
13718
+ outputPerMTok: 0.6,
13719
+ cacheReadPerMTok: 0.075,
13720
+ cacheWritePerMTok: 0.15,
13721
+ contextWindow: 128e3
13722
+ },
13723
+ "o1": {
13724
+ id: "o1",
13725
+ provider: "openai",
13726
+ name: "o1",
13727
+ inputPerMTok: 15,
13728
+ outputPerMTok: 60,
13729
+ cacheReadPerMTok: 7.5,
13730
+ cacheWritePerMTok: 15,
13731
+ contextWindow: 2e5
13732
+ },
13801
13733
  "o3-mini": {
13802
13734
  id: "o3-mini",
13803
13735
  provider: "openai",
@@ -13889,31 +13821,163 @@ function getRateCard(modelId) {
13889
13821
  if (RATE_CARDS[key])
13890
13822
  return RATE_CARDS[key];
13891
13823
  if (key.includes("opus")) {
13892
- return RATE_CARDS["claude-opus-4-6-thinking"] || RATE_CARDS["claude-opus-4"];
13824
+ return RATE_CARDS["claude-opus-4-6-thinking"] ?? RATE_CARDS["claude-opus-4"];
13825
+ }
13826
+ if (key.includes("sonnet")) {
13827
+ if (key.includes("4-6") || key.includes("thinking")) {
13828
+ return RATE_CARDS["claude-sonnet-4-6-thinking"];
13829
+ }
13830
+ if (key.includes("3-7") || key.includes("sonnet-3-7")) {
13831
+ return RATE_CARDS["claude-3-7-sonnet"];
13832
+ }
13833
+ if (key.includes("3-5") || key.includes("sonnet-3-5")) {
13834
+ return RATE_CARDS["claude-3-5-sonnet"];
13835
+ }
13836
+ if (key.includes("sonnet-4") || key.includes("claude-4") || key.includes("sonnet-v4")) {
13837
+ return RATE_CARDS["claude-sonnet-4"] ?? RATE_CARDS["claude-sonnet-4-6-thinking"];
13838
+ }
13839
+ }
13840
+ if (key.includes("o1-mini") || key.includes("o3-mini")) {
13841
+ return RATE_CARDS["o3-mini"];
13893
13842
  }
13894
- if (key.includes("sonnet") && (key.includes("4-6") || key.includes("thinking"))) {
13895
- return RATE_CARDS["claude-sonnet-4-6-thinking"];
13843
+ if (key.includes("o1")) {
13844
+ return RATE_CARDS["o1"];
13896
13845
  }
13897
- if (key.includes("3-7") || key.includes("sonnet-3-7")) {
13898
- return RATE_CARDS["claude-3-7-sonnet"];
13846
+ if (key.includes("4o-mini")) {
13847
+ return RATE_CARDS["gpt-4o-mini"];
13899
13848
  }
13900
- if (key.includes("3-5") || key.includes("sonnet-3-5")) {
13901
- return RATE_CARDS["claude-3-5-sonnet"];
13849
+ if (key.includes("4o")) {
13850
+ return RATE_CARDS["gpt-4o"];
13902
13851
  }
13903
13852
  if (key.includes("gemini") && key.includes("flash")) {
13904
- return RATE_CARDS["gemini-3-8-flash"] || RATE_CARDS["gemini-2-0-flash"];
13853
+ return RATE_CARDS["gemini-3-8-flash"] ?? RATE_CARDS["gemini-2-0-flash"];
13905
13854
  }
13906
13855
  if (key.includes("gemini") && (key.includes("pro") || key.includes("2-5"))) {
13907
- return RATE_CARDS["gemini-2-5-pro"] || RATE_CARDS["gemini-1-5-pro"];
13856
+ return RATE_CARDS["gemini-2-5-pro"] ?? RATE_CARDS["gemini-1-5-pro"];
13908
13857
  }
13909
13858
  return RATE_CARDS["default"];
13910
13859
  }
13911
- function computeCost(inputTokens, outputTokens, cachedTokens, card) {
13860
+ function computeCost(inputTokens, outputTokens, cachedTokens, card, cacheWriteTokens = 0) {
13912
13861
  const freshInput = Math.max(0, inputTokens - cachedTokens);
13913
13862
  const freshCost = freshInput / 1e6 * card.inputPerMTok;
13914
- const cacheCost = cachedTokens / 1e6 * card.cacheReadPerMTok;
13863
+ const cacheReadCost = cachedTokens / 1e6 * card.cacheReadPerMTok;
13864
+ const cacheWriteCost = cacheWriteTokens / 1e6 * card.cacheWritePerMTok;
13915
13865
  const outCost = outputTokens / 1e6 * card.outputPerMTok;
13916
- return Number((freshCost + cacheCost + outCost).toFixed(4));
13866
+ return Number((freshCost + cacheReadCost + cacheWriteCost + outCost).toFixed(4));
13867
+ }
13868
+
13869
+ // ../core/dist/harvester/scoring.js
13870
+ function normalizeFilePath(filePath, repoPath) {
13871
+ let p2 = filePath.replace(/\\/g, "/").trim();
13872
+ p2 = p2.replace(/^file:\/\/\/?/, "");
13873
+ if (repoPath) {
13874
+ const normRepo = repoPath.replace(/\\/g, "/").replace(/\/$/, "");
13875
+ if (p2.toLowerCase().startsWith(normRepo.toLowerCase())) {
13876
+ p2 = p2.slice(normRepo.length);
13877
+ }
13878
+ }
13879
+ p2 = p2.replace(/^\.?\//, "");
13880
+ return p2.toLowerCase();
13881
+ }
13882
+ function scoreFootprint(fp, gitCtx) {
13883
+ if (!gitCtx) {
13884
+ return 0.5;
13885
+ }
13886
+ let score = 0.1;
13887
+ if (fp.boundCommitSha && gitCtx.headSha) {
13888
+ const bound = fp.boundCommitSha.toLowerCase().trim();
13889
+ const head = gitCtx.headSha.toLowerCase().trim();
13890
+ if (bound === head || bound.startsWith(head.slice(0, 7)) || head.startsWith(bound.slice(0, 7))) {
13891
+ score += 1;
13892
+ } else {
13893
+ score -= 0.5;
13894
+ }
13895
+ }
13896
+ if (gitCtx.changedFiles && gitCtx.changedFiles.length > 0) {
13897
+ const normChanged = gitCtx.changedFiles.map((f) => normalizeFilePath(f, fp.repoPath));
13898
+ const normEdited = (fp.filesEdited || []).map((f) => normalizeFilePath(f, fp.repoPath));
13899
+ if (normEdited.length > 0) {
13900
+ let matches = 0;
13901
+ for (const cf of normChanged) {
13902
+ const found = normEdited.some((ef) => ef === cf || cf.endsWith("/" + ef) || ef.endsWith("/" + cf) || cf.endsWith(ef) || ef.endsWith(cf));
13903
+ if (found)
13904
+ matches++;
13905
+ }
13906
+ const ratio = matches / normChanged.length;
13907
+ if (matches > 0) {
13908
+ score += ratio * 0.4;
13909
+ } else {
13910
+ score -= 0.15;
13911
+ }
13912
+ }
13913
+ }
13914
+ if (gitCtx.commitDate && fp.timestamp) {
13915
+ try {
13916
+ const commitTime = new Date(gitCtx.commitDate).getTime();
13917
+ const fpTime = new Date(fp.timestamp).getTime();
13918
+ if (!isNaN(commitTime) && !isNaN(fpTime)) {
13919
+ const diffMinutes = Math.abs(commitTime - fpTime) / (60 * 1e3);
13920
+ if (diffMinutes <= 15) {
13921
+ score += 0.25;
13922
+ } else if (diffMinutes <= 60) {
13923
+ score += 0.2;
13924
+ } else if (diffMinutes <= 180) {
13925
+ score += 0.15;
13926
+ } else if (diffMinutes <= 720) {
13927
+ score += 0.08;
13928
+ } else if (diffMinutes <= 1440) {
13929
+ score += 0.03;
13930
+ } else {
13931
+ score -= 0.1;
13932
+ }
13933
+ }
13934
+ } catch {
13935
+ }
13936
+ }
13937
+ switch (fp.mode) {
13938
+ case "verified":
13939
+ score += 0.1;
13940
+ break;
13941
+ case "observed":
13942
+ score += 0.08;
13943
+ break;
13944
+ case "imported":
13945
+ score += 0.05;
13946
+ break;
13947
+ case "estimated":
13948
+ score += 0.02;
13949
+ break;
13950
+ default:
13951
+ break;
13952
+ }
13953
+ return Math.max(0, Math.round(score * 100) / 100);
13954
+ }
13955
+ function selectPrimaryFootprint(footprints, gitCtx) {
13956
+ if (footprints.length === 0) {
13957
+ return { primary: void 0, rankedFootprints: [] };
13958
+ }
13959
+ if (!gitCtx || !gitCtx.headSha && !gitCtx.commitDate && (!gitCtx.changedFiles || gitCtx.changedFiles.length === 0)) {
13960
+ const sorted2 = [...footprints].sort((a, b2) => new Date(b2.timestamp).getTime() - new Date(a.timestamp).getTime());
13961
+ return { primary: sorted2[0], rankedFootprints: sorted2 };
13962
+ }
13963
+ const scored = footprints.map((fp) => {
13964
+ const score = scoreFootprint(fp, gitCtx);
13965
+ return {
13966
+ ...fp,
13967
+ attributionScore: score
13968
+ };
13969
+ });
13970
+ scored.sort((a, b2) => {
13971
+ const scoreDiff = (b2.attributionScore || 0) - (a.attributionScore || 0);
13972
+ if (Math.abs(scoreDiff) > 1e-3) {
13973
+ return scoreDiff;
13974
+ }
13975
+ return new Date(b2.timestamp).getTime() - new Date(a.timestamp).getTime();
13976
+ });
13977
+ return {
13978
+ primary: scored[0],
13979
+ rankedFootprints: scored
13980
+ };
13917
13981
  }
13918
13982
 
13919
13983
  // ../core/dist/harvester/antigravity.js
@@ -14002,7 +14066,7 @@ function harvestAntigravityFootprints(repoPath, sinceDate) {
14002
14066
  if ((tc.name === "replace_file_content" || tc.name === "write_to_file") && tc.args) {
14003
14067
  const file = tc.args.TargetFile || tc.args.targetFile;
14004
14068
  if (file) {
14005
- filesEdited.add(path3.relative(repoPath, file.replace(/"/g, "")).replace(/\\/g, "/"));
14069
+ filesEdited.add(normalizeFilePath(String(file), repoPath));
14006
14070
  }
14007
14071
  }
14008
14072
  }
@@ -14094,7 +14158,7 @@ function harvestClaudeFootprints(repoPath, projectAlias, sinceDate) {
14094
14158
  }
14095
14159
  if (data.tool_name === "Edit" || data.tool_name === "Write") {
14096
14160
  if (data.tool_input?.file_path) {
14097
- filesEdited.add(path4.relative(repoPath, data.tool_input.file_path).replace(/\\/g, "/"));
14161
+ filesEdited.add(normalizeFilePath(String(data.tool_input.file_path), repoPath));
14098
14162
  }
14099
14163
  }
14100
14164
  } catch {
@@ -14154,88 +14218,236 @@ function getSqliteDatabase2() {
14154
14218
  }
14155
14219
  function harvestCursorFootprints(repoPath, sinceDate) {
14156
14220
  const footprints = [];
14157
- const DatabaseSync2 = getSqliteDatabase2();
14158
- if (!DatabaseSync2)
14159
- return footprints;
14160
- let storageDir = "";
14161
- if (process.platform === "win32") {
14162
- storageDir = path5.join(process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"), "Cursor", "User", "workspaceStorage");
14163
- } else if (process.platform === "darwin") {
14164
- storageDir = path5.join(os4.homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage");
14165
- } else {
14166
- storageDir = path5.join(os4.homedir(), ".config", "Cursor", "User", "workspaceStorage");
14167
- }
14168
- if (!fs4.existsSync(storageDir))
14169
- return footprints;
14221
+ const normTarget = repoPath.toLowerCase().replace(/\\/g, "/").replace(/\/$/, "");
14222
+ const targetBasename = path5.basename(repoPath).toLowerCase();
14170
14223
  try {
14171
- const normTarget = repoPath.toLowerCase().replace(/\\/g, "/");
14172
- const wsDirs = fs4.readdirSync(storageDir);
14173
- for (const ws of wsDirs) {
14174
- const wsPath = path5.join(storageDir, ws);
14175
- const wsJsonPath = path5.join(wsPath, "workspace.json");
14176
- if (!fs4.existsSync(wsJsonPath))
14177
- continue;
14178
- try {
14179
- const wsJson = JSON.parse(fs4.readFileSync(wsJsonPath, "utf-8"));
14180
- const folder = (wsJson.folder || wsJson.workspace || "").toLowerCase().replace(/\\/g, "/");
14181
- if (!folder.includes(normTarget) && !normTarget.includes(folder))
14182
- continue;
14183
- const dbFile = path5.join(wsPath, "state.vscdb");
14184
- if (!fs4.existsSync(dbFile))
14185
- continue;
14186
- const dbStat = fs4.statSync(dbFile);
14187
- if (sinceDate && dbStat.mtime < sinceDate)
14188
- continue;
14189
- const db = new DatabaseSync2(dbFile, { readOnly: true });
14190
- const row = db.prepare("SELECT value FROM ItemTable WHERE key = 'workbench.panel.aichat.chatdata' OR key = 'aiService.generations' LIMIT 1").get();
14191
- if (row && row.value) {
14192
- const parsed = JSON.parse(row.value);
14193
- const tabs = parsed.tabs || (Array.isArray(parsed) ? parsed : []);
14194
- let stepCount = 0;
14195
- let taskTitle = "";
14196
- let model = "claude-3-5-sonnet";
14197
- for (const tab of tabs) {
14198
- const bubbles = tab.bubbles || [];
14199
- stepCount += bubbles.length;
14200
- if (!taskTitle && tab.chatTitle)
14201
- taskTitle = tab.chatTitle;
14202
- for (const b2 of bubbles) {
14203
- if (b2.modelType)
14204
- model = b2.modelType;
14224
+ const cursorProjectsDir = path5.join(os4.homedir(), ".cursor", "projects");
14225
+ if (fs4.existsSync(cursorProjectsDir)) {
14226
+ const projectDirs = fs4.readdirSync(cursorProjectsDir);
14227
+ for (const pDir of projectDirs) {
14228
+ const cleanPDir = pDir.toLowerCase().replace(/[^a-z0-9]/g, "");
14229
+ const cleanTarget = normTarget.toLowerCase().replace(/[^a-z0-9]/g, "");
14230
+ const cleanBase = targetBasename.replace(/[^a-z0-9]/g, "");
14231
+ if (cleanPDir.includes(cleanTarget) || cleanTarget.includes(cleanPDir) || cleanPDir.endsWith(cleanBase)) {
14232
+ const transcriptsDir = path5.join(cursorProjectsDir, pDir, "agent-transcripts");
14233
+ if (!fs4.existsSync(transcriptsDir))
14234
+ continue;
14235
+ const sessionDirs = fs4.readdirSync(transcriptsDir);
14236
+ for (const sDir of sessionDirs) {
14237
+ const sPath = path5.join(transcriptsDir, sDir);
14238
+ if (!fs4.statSync(sPath).isDirectory())
14239
+ continue;
14240
+ const jsonlFile = path5.join(sPath, `${sDir}.jsonl`);
14241
+ if (!fs4.existsSync(jsonlFile))
14242
+ continue;
14243
+ const stat = fs4.statSync(jsonlFile);
14244
+ if (sinceDate && stat.mtime < sinceDate)
14245
+ continue;
14246
+ try {
14247
+ const content = fs4.readFileSync(jsonlFile, "utf-8");
14248
+ const lines = content.split("\n");
14249
+ let stepCount = 0;
14250
+ let taskTitle = "";
14251
+ let model = "claude-3-5-sonnet";
14252
+ const filesEditedSet = /* @__PURE__ */ new Set();
14253
+ for (const line of lines) {
14254
+ if (!line.trim())
14255
+ continue;
14256
+ try {
14257
+ const entry = JSON.parse(line);
14258
+ if (!taskTitle && entry.role === "user" && entry.message?.content) {
14259
+ for (const c3 of entry.message.content) {
14260
+ if (c3.type === "text" && c3.text) {
14261
+ const m = c3.text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/);
14262
+ if (m && m[1]) {
14263
+ taskTitle = m[1].replace(/\n/g, " ").trim().slice(0, 60);
14264
+ } else if (!c3.text.startsWith("<timestamp>")) {
14265
+ taskTitle = c3.text.replace(/\n/g, " ").trim().slice(0, 60);
14266
+ }
14267
+ }
14268
+ }
14269
+ }
14270
+ if (entry.role === "assistant" && entry.message?.content) {
14271
+ for (const c3 of entry.message.content) {
14272
+ if (c3.type === "tool_use") {
14273
+ stepCount++;
14274
+ const name = (c3.name || "").toLowerCase();
14275
+ const inp = c3.input || {};
14276
+ if (name.includes("replace") || name.includes("edit") || name.includes("write") || name.includes("diff") || name.includes("apply")) {
14277
+ const target = inp.path || inp.file_path || inp.target_file || inp.file;
14278
+ if (target && typeof target === "string") {
14279
+ filesEditedSet.add(normalizeFilePath(target, repoPath));
14280
+ }
14281
+ }
14282
+ }
14283
+ }
14284
+ }
14285
+ } catch {
14286
+ }
14287
+ }
14288
+ if (stepCount > 0) {
14289
+ const rateCard = getRateCard(model);
14290
+ const estInput = stepCount * 3e3;
14291
+ const estCached = Math.round(estInput * 0.6);
14292
+ const estOutput = stepCount * 450;
14293
+ const cost = computeCost(estInput, estOutput, estCached, rateCard);
14294
+ footprints.push({
14295
+ id: `cursor_transcript_${sDir}`,
14296
+ platform: "cursor",
14297
+ sessionId: sDir,
14298
+ repoPath,
14299
+ taskTitle: taskTitle || "Cursor Agent Session",
14300
+ model,
14301
+ stepsCount: stepCount,
14302
+ filesEdited: Array.from(filesEditedSet),
14303
+ timestamp: stat.mtime.toISOString(),
14304
+ tokens: {
14305
+ input: estInput,
14306
+ output: estOutput,
14307
+ cached: estCached
14308
+ },
14309
+ cost,
14310
+ mode: "imported",
14311
+ confidence: 0.85,
14312
+ rawTranscriptPath: jsonlFile
14313
+ });
14314
+ }
14315
+ } catch {
14205
14316
  }
14206
14317
  }
14207
- if (stepCount > 0) {
14208
- const rateCard = getRateCard(model);
14209
- const estInput = stepCount * 2500;
14210
- const estCached = Math.round(estInput * 0.6);
14211
- const estOutput = stepCount * 250;
14212
- const cost = computeCost(estInput, estOutput, estCached, rateCard);
14213
- footprints.push({
14214
- id: `cursor_${ws}`,
14215
- platform: "cursor",
14216
- sessionId: ws,
14217
- repoPath,
14218
- taskTitle: taskTitle || "Cursor AI Session",
14219
- model,
14220
- stepsCount: stepCount,
14221
- filesEdited: [],
14222
- timestamp: dbStat.mtime.toISOString(),
14223
- tokens: {
14224
- input: estInput,
14225
- output: estOutput,
14226
- cached: estCached
14227
- },
14228
- cost,
14229
- mode: "imported",
14230
- confidence: 0.85,
14231
- rawTranscriptPath: dbFile
14232
- });
14318
+ }
14319
+ }
14320
+ }
14321
+ } catch {
14322
+ }
14323
+ const DatabaseSync2 = getSqliteDatabase2();
14324
+ if (DatabaseSync2) {
14325
+ let storageDir = "";
14326
+ if (process.platform === "win32") {
14327
+ storageDir = path5.join(process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"), "Cursor", "User", "workspaceStorage");
14328
+ } else if (process.platform === "darwin") {
14329
+ storageDir = path5.join(os4.homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage");
14330
+ } else {
14331
+ storageDir = path5.join(os4.homedir(), ".config", "Cursor", "User", "workspaceStorage");
14332
+ }
14333
+ if (fs4.existsSync(storageDir)) {
14334
+ try {
14335
+ const wsDirs = fs4.readdirSync(storageDir);
14336
+ for (const ws of wsDirs) {
14337
+ const wsPath = path5.join(storageDir, ws);
14338
+ const wsJsonPath = path5.join(wsPath, "workspace.json");
14339
+ if (!fs4.existsSync(wsJsonPath))
14340
+ continue;
14341
+ try {
14342
+ const wsJson = JSON.parse(fs4.readFileSync(wsJsonPath, "utf-8"));
14343
+ let folderUri = wsJson.folder || wsJson.workspace || "";
14344
+ try {
14345
+ folderUri = decodeURIComponent(folderUri.replace(/^file:\/\/\/?/, ""));
14346
+ } catch {
14347
+ }
14348
+ const folder = folderUri.toLowerCase().replace(/\\/g, "/");
14349
+ if (!folder.includes(normTarget) && !normTarget.includes(folder) && !folder.endsWith(targetBasename)) {
14350
+ continue;
14351
+ }
14352
+ const dbFile = path5.join(wsPath, "state.vscdb");
14353
+ if (!fs4.existsSync(dbFile))
14354
+ continue;
14355
+ const dbStat = fs4.statSync(dbFile);
14356
+ if (sinceDate && dbStat.mtime < sinceDate)
14357
+ continue;
14358
+ const db = new DatabaseSync2(dbFile, { readOnly: true });
14359
+ try {
14360
+ const rowRecent = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiCodeTracking.recentCommit' LIMIT 1").get();
14361
+ if (rowRecent && rowRecent.value) {
14362
+ const recent = JSON.parse(rowRecent.value);
14363
+ if (recent && recent.commitHash) {
14364
+ const added = Number(recent.linesAdded || recent.composerLinesAdded || 0);
14365
+ const deleted = Number(recent.linesDeleted || recent.composerLinesDeleted || 0);
14366
+ const estOut = Math.max(Math.round((added + deleted) * 35 / 4), 100);
14367
+ const estIn = estOut * 20;
14368
+ const estCached = Math.round(estIn * 0.6);
14369
+ const rateCard = getRateCard("claude-3-7-sonnet");
14370
+ const cost = computeCost(estIn, estOut, estCached, rateCard);
14371
+ const commitDate = recent.commitDate ? new Date(recent.commitDate).toISOString() : recent.timestamp ? new Date(recent.timestamp).toISOString() : dbStat.mtime.toISOString();
14372
+ footprints.push({
14373
+ id: `cursor_commit_${recent.commitHash.slice(0, 12)}`,
14374
+ platform: "cursor",
14375
+ sessionId: ws,
14376
+ repoPath,
14377
+ taskTitle: recent.commitMessage || "Cursor AI Commit",
14378
+ model: "claude-3-7-sonnet",
14379
+ stepsCount: Math.max(Math.round((added + deleted) / 10), 1),
14380
+ filesEdited: [],
14381
+ timestamp: commitDate,
14382
+ boundCommitSha: recent.commitHash,
14383
+ tokens: {
14384
+ input: estIn,
14385
+ output: estOut,
14386
+ cached: estCached
14387
+ },
14388
+ cost,
14389
+ mode: "verified",
14390
+ confidence: 0.95,
14391
+ rawTranscriptPath: dbFile
14392
+ });
14393
+ }
14394
+ }
14395
+ } catch {
14396
+ }
14397
+ const rowLegacy = db.prepare("SELECT value FROM ItemTable WHERE key = 'workbench.panel.aichat.chatdata' OR key = 'aiService.generations' LIMIT 1").get();
14398
+ if (rowLegacy && rowLegacy.value) {
14399
+ try {
14400
+ const parsed = JSON.parse(rowLegacy.value);
14401
+ const tabs = parsed.tabs || (Array.isArray(parsed) ? parsed : []);
14402
+ let stepCount = 0;
14403
+ let taskTitle = "";
14404
+ let model = "claude-3-5-sonnet";
14405
+ for (const tab of tabs) {
14406
+ const bubbles = tab.bubbles || [];
14407
+ stepCount += bubbles.length;
14408
+ if (!taskTitle && tab.chatTitle)
14409
+ taskTitle = tab.chatTitle;
14410
+ for (const b2 of bubbles) {
14411
+ if (b2.modelType)
14412
+ model = b2.modelType;
14413
+ }
14414
+ }
14415
+ if (stepCount > 0) {
14416
+ const rateCard = getRateCard(model);
14417
+ const estInput = stepCount * 2500;
14418
+ const estCached = Math.round(estInput * 0.6);
14419
+ const estOutput = stepCount * 250;
14420
+ const cost = computeCost(estInput, estOutput, estCached, rateCard);
14421
+ footprints.push({
14422
+ id: `cursor_chat_${ws}`,
14423
+ platform: "cursor",
14424
+ sessionId: ws,
14425
+ repoPath,
14426
+ taskTitle: taskTitle || "Cursor Chat Session",
14427
+ model,
14428
+ stepsCount: stepCount,
14429
+ filesEdited: [],
14430
+ timestamp: dbStat.mtime.toISOString(),
14431
+ tokens: {
14432
+ input: estInput,
14433
+ output: estOutput,
14434
+ cached: estCached
14435
+ },
14436
+ cost,
14437
+ mode: "imported",
14438
+ confidence: 0.8,
14439
+ rawTranscriptPath: dbFile
14440
+ });
14441
+ }
14442
+ } catch {
14443
+ }
14444
+ }
14445
+ } catch {
14233
14446
  }
14234
14447
  }
14235
14448
  } catch {
14236
14449
  }
14237
14450
  }
14238
- } catch {
14239
14451
  }
14240
14452
  return footprints;
14241
14453
  }
@@ -14389,6 +14601,35 @@ var LocalStateDB = class {
14389
14601
  return null;
14390
14602
  return JSON.parse(row.payload_json);
14391
14603
  }
14604
+ getRecentReceipts(limit = 10) {
14605
+ if (!this.db)
14606
+ return [];
14607
+ try {
14608
+ const rows = this.db.prepare("SELECT payload_json, claim_token, sync_status, created_at FROM receipts ORDER BY created_at DESC LIMIT ?").all(limit);
14609
+ return rows.map((r2) => ({
14610
+ receipt: JSON.parse(r2.payload_json),
14611
+ claimToken: r2.claim_token || void 0,
14612
+ syncStatus: r2.sync_status || "local",
14613
+ createdAt: r2.created_at
14614
+ }));
14615
+ } catch {
14616
+ return [];
14617
+ }
14618
+ }
14619
+ getStats() {
14620
+ if (!this.db)
14621
+ return { totalReceipts: 0, totalFootprints: 0 };
14622
+ try {
14623
+ const rRow = this.db.prepare("SELECT COUNT(*) as cnt FROM receipts").get();
14624
+ const fRow = this.db.prepare("SELECT COUNT(*) as cnt FROM agent_footprints").get();
14625
+ return {
14626
+ totalReceipts: rRow?.cnt || 0,
14627
+ totalFootprints: fRow?.cnt || 0
14628
+ };
14629
+ } catch {
14630
+ return { totalReceipts: 0, totalFootprints: 0 };
14631
+ }
14632
+ }
14392
14633
  close() {
14393
14634
  if (this.db) {
14394
14635
  this.db.close();
@@ -14440,7 +14681,8 @@ async function harvestUniversalFootprints(options) {
14440
14681
  const cursorFootprints = harvestCursorFootprints(options.repoPath, sinceDate);
14441
14682
  allFootprints.push(...cursorFootprints);
14442
14683
  }
14443
- allFootprints.sort((a, b2) => new Date(b2.timestamp).getTime() - new Date(a.timestamp).getTime());
14684
+ const { primary, rankedFootprints } = selectPrimaryFootprint(allFootprints, options.gitContext);
14685
+ allFootprints = rankedFootprints;
14444
14686
  try {
14445
14687
  const db = new LocalStateDB();
14446
14688
  for (const fp of allFootprints) {
@@ -14467,7 +14709,6 @@ async function harvestUniversalFootprints(options) {
14467
14709
  totalCached += fp.tokens.cached;
14468
14710
  totalCost += fp.cost;
14469
14711
  }
14470
- const primary = allFootprints[0];
14471
14712
  return {
14472
14713
  footprints: allFootprints,
14473
14714
  primaryFootprint: primary,
@@ -14501,16 +14742,16 @@ function estimateCost(options) {
14501
14742
  totalCost += s.cost;
14502
14743
  }
14503
14744
  return {
14504
- provider: primary.provider,
14505
- model: primary.model || "claude-3-7-sonnet",
14745
+ provider: options.provider || primary.provider,
14746
+ model: options.model || primary.model || "claude-3-7-sonnet",
14506
14747
  tokens: {
14507
14748
  input: totalInput,
14508
14749
  output: totalOutput,
14509
14750
  cached: totalCached
14510
14751
  },
14511
14752
  cost: Number(totalCost.toFixed(4)),
14512
- mode: primary.mode,
14513
- confidence: primary.confidence,
14753
+ mode: options.mode || primary.mode,
14754
+ confidence: options.confidence !== void 0 ? options.confidence : primary.confidence,
14514
14755
  sessions: options.sessions
14515
14756
  };
14516
14757
  }
@@ -14630,7 +14871,13 @@ async function generateReceipt(options = {}) {
14630
14871
  repoPath,
14631
14872
  projectAlias: metrics.projectAlias,
14632
14873
  since: options.since,
14633
- platform: options.platform
14874
+ platform: options.platform,
14875
+ gitContext: {
14876
+ headSha: metrics.headSha,
14877
+ commitDate: metrics.commitDate,
14878
+ changedFiles: metrics.changedFiles,
14879
+ branch: metrics.branch
14880
+ }
14634
14881
  });
14635
14882
  const primaryFp = harvestResult.primaryFootprint;
14636
14883
  const detected = detectProviderFromCommit(metrics.commitMessage);
@@ -14716,25 +14963,954 @@ async function generateReceipt(options = {}) {
14716
14963
  return ReceiptV1Schema.parse(receipt);
14717
14964
  }
14718
14965
 
14719
- // src/index.ts
14720
- var SHARE_PAYLOAD_MAX_BYTES = 5e4;
14721
- function resolveCliVersion() {
14722
- try {
14723
- const argv1 = process.argv[1] ? path7.dirname(path7.resolve(process.argv[1])) : process.cwd();
14724
- const candidates = [
14725
- path7.join(argv1, "..", "package.json"),
14726
- path7.join(argv1, "package.json")
14727
- ];
14728
- for (const candidate of candidates) {
14729
- if (!fs6.existsSync(candidate)) continue;
14730
- const pkg = JSON.parse(fs6.readFileSync(candidate, "utf-8"));
14731
- if (pkg.name === "qodewk" && pkg.version) return pkg.version;
14966
+ // src/receipt-view.ts
14967
+ var fs6 = __toESM(require("fs"), 1);
14968
+ var import_picocolors = __toESM(require_picocolors(), 1);
14969
+
14970
+ // src/barcode.ts
14971
+ var CODE128_PATTERNS = [
14972
+ "11011001100",
14973
+ "11001101100",
14974
+ "11001100110",
14975
+ "10010011000",
14976
+ "10010001100",
14977
+ "10001001100",
14978
+ "10011001000",
14979
+ "10011000100",
14980
+ "10001100100",
14981
+ "11001001000",
14982
+ "11001000100",
14983
+ "11000100100",
14984
+ "10110011100",
14985
+ "10011011100",
14986
+ "10011001110",
14987
+ "10111001100",
14988
+ "10011101100",
14989
+ "10011100110",
14990
+ "11001110010",
14991
+ "11001011100",
14992
+ "11001001110",
14993
+ "11011100100",
14994
+ "11001110100",
14995
+ "11101101110",
14996
+ "11101001100",
14997
+ "11100101100",
14998
+ "11100100110",
14999
+ "11101100100",
15000
+ "11100110100",
15001
+ "11100110010",
15002
+ "11011011000",
15003
+ "11011000110",
15004
+ "11000110110",
15005
+ "10100011000",
15006
+ "10001011000",
15007
+ "10001000110",
15008
+ "10110001000",
15009
+ "10001101000",
15010
+ "10001100010",
15011
+ "11010001000",
15012
+ "11000101000",
15013
+ "11000100010",
15014
+ "10110111000",
15015
+ "10110001110",
15016
+ "10001101110",
15017
+ "10111011000",
15018
+ "10111000110",
15019
+ "10001110110",
15020
+ "11101110110",
15021
+ "11010001110",
15022
+ "11000101110",
15023
+ "11011101000",
15024
+ "11011100010",
15025
+ "11011101110",
15026
+ "11101011000",
15027
+ "11101000110",
15028
+ "11100010110",
15029
+ "11101101000",
15030
+ "11101100010",
15031
+ "11100011010",
15032
+ "11101111010",
15033
+ "11001000010",
15034
+ "11110001010",
15035
+ "10100110000",
15036
+ "10100001100",
15037
+ "10010110000",
15038
+ "10010000110",
15039
+ "10000101100",
15040
+ "10000100110",
15041
+ "10110010000",
15042
+ "10110000100",
15043
+ "10011010000",
15044
+ "10011000010",
15045
+ "10000110100",
15046
+ "10000110010",
15047
+ "11000010010",
15048
+ "11001010000",
15049
+ "11110111010",
15050
+ "11000010100",
15051
+ "10001111010",
15052
+ "10100111100",
15053
+ "10010111100",
15054
+ "10010011110",
15055
+ "10111100100",
15056
+ "10011110100",
15057
+ "10011110010",
15058
+ "11110100100",
15059
+ "11110010100",
15060
+ "11110010010",
15061
+ "11011011110",
15062
+ "11011110110",
15063
+ "11110110110",
15064
+ "10101111000",
15065
+ "10100011110",
15066
+ "10001011110",
15067
+ "10111101000",
15068
+ "10111100010",
15069
+ "11110101000",
15070
+ "11110100010",
15071
+ "10111011110",
15072
+ "10111101110",
15073
+ "11101011110",
15074
+ "11110101110",
15075
+ "11010000100",
15076
+ "11010010000",
15077
+ "11010011100",
15078
+ "1100011101011"
15079
+ ];
15080
+ var START_B = 104;
15081
+ var STOP = 106;
15082
+ function encodeCode128B(text) {
15083
+ const codes = [START_B];
15084
+ for (const ch of text) {
15085
+ const code = ch.charCodeAt(0) - 32;
15086
+ if (code < 0 || code > 95) {
15087
+ throw new Error(`Code 128-B cannot encode character: ${JSON.stringify(ch)}`);
14732
15088
  }
14733
- } catch {
15089
+ codes.push(code);
14734
15090
  }
14735
- return "0.2.1";
14736
- }
14737
- function resolveSince(options) {
15091
+ let checksum = START_B;
15092
+ for (let i2 = 1; i2 < codes.length; i2++) {
15093
+ checksum += codes[i2] * i2;
15094
+ }
15095
+ codes.push(checksum % 103);
15096
+ codes.push(STOP);
15097
+ return codes;
15098
+ }
15099
+ function code128Modules(text) {
15100
+ const codes = encodeCode128B(text);
15101
+ let modules = "";
15102
+ for (const code of codes) {
15103
+ modules += CODE128_PATTERNS[code] ?? "";
15104
+ }
15105
+ return modules;
15106
+ }
15107
+ function renderTerminalBarcode(text, options = {}) {
15108
+ const maxWidth = Math.max(16, options.maxWidth ?? 52);
15109
+ const height = Math.max(2, options.height ?? 3);
15110
+ const quietZone = Math.max(0, options.quietZone ?? 10);
15111
+ let payload = text;
15112
+ payload = [...payload].filter((ch) => {
15113
+ const c3 = ch.charCodeAt(0);
15114
+ return c3 >= 32 && c3 <= 126;
15115
+ }).join("");
15116
+ if (!payload) payload = "QODEWK";
15117
+ let modules;
15118
+ try {
15119
+ modules = code128Modules(payload);
15120
+ } catch {
15121
+ modules = code128Modules("QODEWK");
15122
+ }
15123
+ const withQuiet = "0".repeat(quietZone) + modules + "0".repeat(quietZone);
15124
+ const scaled = downsampleModules(withQuiet, maxWidth);
15125
+ const row = modulesToBlocks(scaled);
15126
+ return Array.from({ length: height }, () => row);
15127
+ }
15128
+ function downsampleModules(modules, maxWidth) {
15129
+ if (modules.length <= maxWidth) return modules;
15130
+ const out = [];
15131
+ const ratio = modules.length / maxWidth;
15132
+ for (let i2 = 0; i2 < maxWidth; i2++) {
15133
+ const start = Math.floor(i2 * ratio);
15134
+ const end = Math.max(start + 1, Math.floor((i2 + 1) * ratio));
15135
+ let bars = 0;
15136
+ for (let j2 = start; j2 < end && j2 < modules.length; j2++) {
15137
+ if (modules[j2] === "1") bars++;
15138
+ }
15139
+ out.push(bars * 2 >= end - start ? "1" : "0");
15140
+ }
15141
+ return out.join("");
15142
+ }
15143
+ function modulesToBlocks(modules) {
15144
+ let out = "";
15145
+ for (const bit of modules) {
15146
+ out += bit === "1" ? "\u2588" : " ";
15147
+ }
15148
+ return out;
15149
+ }
15150
+ function pickBarcodePayload(input) {
15151
+ const maxChars = input.maxChars ?? 24;
15152
+ const candidates = [];
15153
+ if (input.publicUrl) candidates.push(input.publicUrl);
15154
+ if (input.displayHost && input.receiptId) {
15155
+ candidates.push(`${input.displayHost}/r/${input.receiptId}`);
15156
+ }
15157
+ candidates.push(input.receiptId);
15158
+ if (input.receiptId.length > 12) {
15159
+ candidates.push(input.receiptId.replace(/^rec_/, "").slice(0, 12));
15160
+ }
15161
+ for (const c3 of candidates) {
15162
+ if (c3.length <= maxChars) return c3;
15163
+ }
15164
+ return candidates[candidates.length - 1].slice(0, maxChars);
15165
+ }
15166
+
15167
+ // src/theme.ts
15168
+ var THEME = {
15169
+ primary: "#cc785c",
15170
+ primaryActive: "#a9583e",
15171
+ primaryDisabled: "#e6dfd8",
15172
+ ink: "#141413",
15173
+ body: "#3d3d3a",
15174
+ bodyStrong: "#252523",
15175
+ muted: "#6c6a64",
15176
+ mutedSoft: "#8e8b82",
15177
+ hairline: "#e6dfd8",
15178
+ hairlineSoft: "#ebe6df",
15179
+ canvas: "#faf9f5",
15180
+ surfaceCard: "#efe9de",
15181
+ surfaceCreamStrong: "#e8e0d2",
15182
+ surfaceDark: "#181715",
15183
+ surfaceDarkElevated: "#252320",
15184
+ surfaceDarkSoft: "#1f1e1b",
15185
+ teal: "#5db8a6",
15186
+ amber: "#e8a55a",
15187
+ success: "#5db872",
15188
+ error: "#c64545"
15189
+ };
15190
+ var ansiHex = (hexColor) => {
15191
+ const num = parseInt(hexColor.replace("#", ""), 16);
15192
+ const r2 = num >> 16 & 255;
15193
+ const g = num >> 8 & 255;
15194
+ const b2 = num & 255;
15195
+ return (text) => `\x1B[38;2;${r2};${g};${b2}m${text}\x1B[39m`;
15196
+ };
15197
+ var colors = {
15198
+ coral: ansiHex(THEME.primary),
15199
+ coralActive: ansiHex(THEME.primaryActive),
15200
+ muted: ansiHex(THEME.muted),
15201
+ mutedSoft: ansiHex(THEME.mutedSoft),
15202
+ teal: ansiHex(THEME.teal),
15203
+ amber: ansiHex(THEME.amber),
15204
+ green: ansiHex(THEME.success),
15205
+ red: ansiHex(THEME.error),
15206
+ hairline: ansiHex(THEME.hairline),
15207
+ canvas: ansiHex(THEME.canvas)
15208
+ };
15209
+ var stripAnsi = (str) => {
15210
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\([B0]/g, "");
15211
+ };
15212
+ var visibleWidth = (str) => {
15213
+ return stripAnsi(str).length;
15214
+ };
15215
+
15216
+ // src/receipt-view.ts
15217
+ function buildTerminalReceipt(receipt, publicUrl) {
15218
+ const lines = [];
15219
+ const coral = colors.coral;
15220
+ const green = colors.green;
15221
+ const red = colors.red;
15222
+ const teal = colors.teal;
15223
+ const INNER_WIDTH2 = 52;
15224
+ const push = (line) => lines.push(line);
15225
+ const printRow = (content, width = INNER_WIDTH2) => {
15226
+ const visLen = visibleWidth(content);
15227
+ const pad = Math.max(0, width - visLen);
15228
+ push(` | ${content}${" ".repeat(pad)} |`);
15229
+ };
15230
+ const printRowSplit = (left, right, width = INNER_WIDTH2) => {
15231
+ const leftVis = visibleWidth(left);
15232
+ const rightVis = visibleWidth(right);
15233
+ const pad = Math.max(1, width - leftVis - rightVis);
15234
+ push(` | ${left}${" ".repeat(pad)}${right} |`);
15235
+ };
15236
+ const printCenteredRow = (content, width = INNER_WIDTH2) => {
15237
+ const visLen = visibleWidth(content);
15238
+ const totalPad = Math.max(0, width - visLen);
15239
+ const leftPad = Math.floor(totalPad / 2);
15240
+ const rightPad = totalPad - leftPad;
15241
+ push(` | ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} |`);
15242
+ };
15243
+ const printDivider = (char = "=", width = INNER_WIDTH2) => {
15244
+ push(` | ${char.repeat(width)} |`);
15245
+ };
15246
+ const totalTokens = (receipt.ai.tokens.input + receipt.ai.tokens.output).toLocaleString();
15247
+ const inputK = `${Math.round(receipt.ai.tokens.input / 1e3)}k`;
15248
+ const outputK = `${Math.round(receipt.ai.tokens.output / 1e3)}k`;
15249
+ const costPrefix = receipt.ai.mode === "verified" ? "$" : "~$";
15250
+ const confidencePercent = `${Math.round(receipt.ai.confidence * 100)}%`;
15251
+ push("");
15252
+ push(" " + import_picocolors.default.bold(coral("/\\".repeat(28))));
15253
+ printCenteredRow(import_picocolors.default.bold(import_picocolors.default.white("Q O D E W K")));
15254
+ printCenteredRow(coral("*** PROOF OF SHIPMENT ***"));
15255
+ printRow("");
15256
+ printRowSplit(
15257
+ `ID: ${receipt.receipt.id.slice(0, 24)}`,
15258
+ `DATE: ${receipt.receipt.createdAt.slice(0, 10)}`
15259
+ );
15260
+ printRowSplit(
15261
+ `REPO: ${receipt.repository.projectAlias.slice(0, 18)}`,
15262
+ `BRANCH: ${receipt.repository.branch.slice(0, 16)}`
15263
+ );
15264
+ if (receipt.ai.task) {
15265
+ const taskClean = receipt.ai.task.length > 44 ? receipt.ai.task.slice(0, 43) + "\u2026" : receipt.ai.task;
15266
+ printRow(`TASK: ${taskClean}`);
15267
+ }
15268
+ printDivider("=");
15269
+ const itemsHeader = receipt.repository.commitsCount && receipt.repository.commitsCount > 1 ? `ITEMS CHANGED (${receipt.repository.commitsCount} COMMITS)` : "ITEMS CHANGED";
15270
+ printRowSplit(itemsHeader, "QTY");
15271
+ printDivider("-");
15272
+ printRowSplit("Files Touched", String(receipt.mutation.files));
15273
+ printRowSplit("Lines Inserted", green("+ " + receipt.mutation.insertions));
15274
+ printRowSplit("Lines Deleted", red("- " + receipt.mutation.deletions));
15275
+ printRowSplit(
15276
+ "Net Code Delta",
15277
+ teal((receipt.mutation.netLines >= 0 ? "+ " : "- ") + Math.abs(receipt.mutation.netLines))
15278
+ );
15279
+ printDivider("-");
15280
+ printRow("AI TELEMETRY & ATTRIBUTION");
15281
+ const provString = receipt.ai.provider + " \xB7 " + (receipt.ai.model || "Unknown");
15282
+ const cleanProv = provString.length > 40 ? provString.slice(0, 39) + "\u2026" : provString;
15283
+ printRow(`Provider: ${cleanProv}`);
15284
+ if (receipt.ai.sessions && receipt.ai.sessions.length > 1) {
15285
+ const others = receipt.ai.sessions.slice(1).map((s) => s.provider).filter((p2, i2, arr) => arr.indexOf(p2) === i2 && p2 !== receipt.ai.provider);
15286
+ if (others.length > 0) {
15287
+ printRow(import_picocolors.default.dim(`Also seen: ${others.join(", ")}`));
15288
+ }
15289
+ }
15290
+ if (receipt.ai.aiWrittenRatio !== void 0) {
15291
+ const aiPct = Math.round(receipt.ai.aiWrittenRatio * 100);
15292
+ printRowSplit("AI Written Code", `${aiPct}% (Human: ${100 - aiPct}%)`);
15293
+ }
15294
+ const tokenDetail = `${inputK} in (${Math.round(receipt.ai.tokens.cached / 1e3)}k cached) / ${outputK} out`;
15295
+ const cleanTokens = tokenDetail.length > 40 ? tokenDetail.slice(0, 39) + "\u2026" : tokenDetail;
15296
+ printRow(`Tokens: ${cleanTokens}`);
15297
+ printRowSplit("Total Tokens:", totalTokens);
15298
+ printDivider("=");
15299
+ const costLabel = receipt.ai.mode === "verified" ? "VERIFIED AI COST" : "ESTIMATED AI COST";
15300
+ printRowSplit(costLabel, import_picocolors.default.bold(coral(costPrefix + receipt.ai.cost.toFixed(2))));
15301
+ printRowSplit(`CONFIDENCE: ${confidencePercent}`, `[Mode: ${receipt.ai.mode}]`);
15302
+ printDivider("=");
15303
+ printRow("");
15304
+ const baseUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online";
15305
+ const isPublished = Boolean(publicUrl);
15306
+ const displayHost = baseUrl.replace(/^https?:\/\//, "");
15307
+ const barcodePayload = pickBarcodePayload({
15308
+ publicUrl,
15309
+ receiptId: receipt.receipt.id,
15310
+ displayHost,
15311
+ maxChars: 28
15312
+ });
15313
+ const barcodeRows = renderTerminalBarcode(barcodePayload, {
15314
+ maxWidth: INNER_WIDTH2,
15315
+ height: 3,
15316
+ quietZone: 8
15317
+ });
15318
+ for (const barRow of barcodeRows) {
15319
+ printCenteredRow(import_picocolors.default.bold(barRow));
15320
+ }
15321
+ if (isPublished) {
15322
+ printCenteredRow(import_picocolors.default.cyan(displayHost));
15323
+ printCenteredRow(import_picocolors.default.underline(import_picocolors.default.cyan(`r/${receipt.receipt.id}`)));
15324
+ } else {
15325
+ printCenteredRow(import_picocolors.default.yellow("[ LOCAL RECORD \u2014 NOT PUBLISHED ]"));
15326
+ }
15327
+ printRow("");
15328
+ if (process.env.QODEWK_TELEMETRY === "off") {
15329
+ printRow(` ${teal("[\u2713]")} QODEWK_TELEMETRY=off (Cloud sync disabled)`);
15330
+ }
15331
+ printRow(` ${green("[\u2713]")} Source code was never uploaded to Qodewk`);
15332
+ push(" " + import_picocolors.default.bold(coral("\\/".repeat(28))));
15333
+ push("");
15334
+ if (isPublished && publicUrl) {
15335
+ push(` ${import_picocolors.default.dim("Public Receipt:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(publicUrl))}`);
15336
+ } else {
15337
+ push(` ${import_picocolors.default.dim("Saved to local DB:")} ${import_picocolors.default.dim("~/.qodewk/state.db")}`);
15338
+ push(` ${import_picocolors.default.dim("To publish & get shareable URL:")} ${import_picocolors.default.cyan("qodewk share")}`);
15339
+ push(` ${import_picocolors.default.dim("Interactive menu:")} ${import_picocolors.default.cyan("qodewk menu")}`);
15340
+ }
15341
+ push("");
15342
+ return lines.join("\n");
15343
+ }
15344
+ async function renderTerminalReceipt(receipt, publicUrl) {
15345
+ console.log(buildTerminalReceipt(receipt, publicUrl));
15346
+ }
15347
+ async function outputReceipt(receipt, options, publicUrl) {
15348
+ const format = options.json ? "json" : options.format || "terminal";
15349
+ let content;
15350
+ if (format === "json") {
15351
+ content = JSON.stringify(receipt, null, 2);
15352
+ } else if (format === "markdown") {
15353
+ content = formatMarkdownReceipt(receipt, publicUrl);
15354
+ } else {
15355
+ content = buildTerminalReceipt(receipt, publicUrl);
15356
+ }
15357
+ if (options.out) {
15358
+ const fileContent = format === "terminal" ? stripAnsi(content) : content;
15359
+ fs6.writeFileSync(options.out, fileContent.endsWith("\n") ? fileContent : fileContent + "\n", "utf-8");
15360
+ console.log(import_picocolors.default.green(`\u2713 Receipt written to ${options.out}`));
15361
+ }
15362
+ if (!options.out) {
15363
+ if (format === "terminal") {
15364
+ console.log(content);
15365
+ } else {
15366
+ console.log(content);
15367
+ }
15368
+ }
15369
+ }
15370
+
15371
+ // src/hooks.ts
15372
+ var fs7 = __toESM(require("fs"), 1);
15373
+ var path7 = __toESM(require("path"), 1);
15374
+ var HOOK_MARKER_BEGIN = "# --- BEGIN QODEWK HOOK ---";
15375
+ var HOOK_MARKER_END = "# --- END QODEWK HOOK ---";
15376
+ var HOOK_SNIPPET = `
15377
+ ${HOOK_MARKER_BEGIN}
15378
+ # Non-blocking Qodewk background recorder (< 5ms)
15379
+ if command -v qodewk >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1 || [ -f "./node_modules/.bin/qodewk" ]; then
15380
+ ( ( qodewk record || pnpm qodewk record || npx qodewk record ) >/dev/null 2>&1 & )
15381
+ fi
15382
+ ${HOOK_MARKER_END}
15383
+ `;
15384
+ function resolveGitHooksDir(cwd = process.cwd()) {
15385
+ const gitPath = path7.join(cwd, ".git");
15386
+ if (!fs7.existsSync(gitPath)) return null;
15387
+ let gitCommonDir;
15388
+ const stat = fs7.statSync(gitPath);
15389
+ if (stat.isDirectory()) {
15390
+ gitCommonDir = gitPath;
15391
+ } else {
15392
+ const content = fs7.readFileSync(gitPath, "utf-8");
15393
+ const match = content.match(/gitdir:\s*(.+)/i);
15394
+ if (!match?.[1]) return null;
15395
+ let gitDir = match[1].trim();
15396
+ if (!path7.isAbsolute(gitDir)) {
15397
+ gitDir = path7.resolve(cwd, gitDir);
15398
+ }
15399
+ const commonFile = path7.join(gitDir, "commondir");
15400
+ if (fs7.existsSync(commonFile)) {
15401
+ let common = fs7.readFileSync(commonFile, "utf-8").trim();
15402
+ if (!path7.isAbsolute(common)) {
15403
+ common = path7.resolve(gitDir, common);
15404
+ }
15405
+ gitCommonDir = common;
15406
+ } else if (path7.basename(path7.dirname(gitDir)) === "worktrees") {
15407
+ gitCommonDir = path7.dirname(path7.dirname(gitDir));
15408
+ } else {
15409
+ gitCommonDir = gitDir;
15410
+ }
15411
+ }
15412
+ return path7.join(gitCommonDir, "hooks");
15413
+ }
15414
+ function checkHookStatus(hooksDir) {
15415
+ if (!hooksDir || !fs7.existsSync(hooksDir)) {
15416
+ return {
15417
+ isGit: Boolean(hooksDir),
15418
+ hooksDir,
15419
+ postCommitInstalled: false,
15420
+ postRewriteInstalled: false
15421
+ };
15422
+ }
15423
+ const postCommit = path7.join(hooksDir, "post-commit");
15424
+ const postRewrite = path7.join(hooksDir, "post-rewrite");
15425
+ const hasHook = (p2) => {
15426
+ if (!fs7.existsSync(p2)) return false;
15427
+ try {
15428
+ const content = fs7.readFileSync(p2, "utf-8");
15429
+ return content.includes(HOOK_MARKER_BEGIN);
15430
+ } catch {
15431
+ return false;
15432
+ }
15433
+ };
15434
+ return {
15435
+ isGit: true,
15436
+ hooksDir,
15437
+ postCommitInstalled: hasHook(postCommit),
15438
+ postRewriteInstalled: hasHook(postRewrite)
15439
+ };
15440
+ }
15441
+ function installHookFile(hookFile) {
15442
+ let content = "";
15443
+ if (fs7.existsSync(hookFile)) {
15444
+ content = fs7.readFileSync(hookFile, "utf-8");
15445
+ } else {
15446
+ content = "#!/bin/sh\n";
15447
+ }
15448
+ if (content.includes(HOOK_MARKER_BEGIN)) {
15449
+ return "exists";
15450
+ }
15451
+ fs7.writeFileSync(hookFile, content + HOOK_SNIPPET, { mode: 493 });
15452
+ return "installed";
15453
+ }
15454
+ function uninstallHookFile(hookFile) {
15455
+ if (!fs7.existsSync(hookFile)) return "absent";
15456
+ const content = fs7.readFileSync(hookFile, "utf-8");
15457
+ if (!content.includes(HOOK_MARKER_BEGIN)) return "missing";
15458
+ const regex = new RegExp(`\\n?${HOOK_MARKER_BEGIN}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, "g");
15459
+ const updated = content.replace(regex, "");
15460
+ fs7.writeFileSync(hookFile, updated, { mode: 493 });
15461
+ return "removed";
15462
+ }
15463
+
15464
+ // src/menu.ts
15465
+ var readline = __toESM(require("readline"), 1);
15466
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
15467
+ var INNER_WIDTH = 58;
15468
+ var SHARE_PAYLOAD_MAX_BYTES = 5e4;
15469
+ var MENU_ITEMS = [
15470
+ {
15471
+ id: "receipt",
15472
+ key: "1",
15473
+ label: "Generate Local Receipt",
15474
+ description: "Inspect git diff and print digital receipt"
15475
+ },
15476
+ {
15477
+ id: "audit",
15478
+ key: "2",
15479
+ label: "Audit Branch or Revision Range",
15480
+ description: "Compare against base commit or upstream branch"
15481
+ },
15482
+ {
15483
+ id: "share",
15484
+ key: "3",
15485
+ label: "Publish Receipt to Cloud",
15486
+ description: "Privacy-safe shareable URL & claim token"
15487
+ },
15488
+ {
15489
+ id: "hooks",
15490
+ key: "4",
15491
+ label: "Configure Git Hooks",
15492
+ description: "Non-blocking background telemetry recording"
15493
+ },
15494
+ {
15495
+ id: "storage",
15496
+ key: "5",
15497
+ label: "Database & Storage Status",
15498
+ description: "Inspect local SQLite (~/.qodewk/state.db) records"
15499
+ },
15500
+ {
15501
+ id: "exit",
15502
+ key: "0",
15503
+ label: "Exit",
15504
+ description: "Return to shell"
15505
+ }
15506
+ ];
15507
+ function buildMenuFrame(selectedIndex, items = MENU_ITEMS) {
15508
+ const lines = [];
15509
+ const coral = colors.coral;
15510
+ const mutedSoft = colors.mutedSoft;
15511
+ const amber = colors.amber;
15512
+ const border = colors.mutedSoft;
15513
+ const push = (line) => lines.push(line);
15514
+ const printRow = (content, width = INNER_WIDTH) => {
15515
+ const visLen = visibleWidth(content);
15516
+ const pad = Math.max(0, width - visLen);
15517
+ push(` ${border("\u2502")} ${content}${" ".repeat(pad)} ${border("\u2502")}`);
15518
+ };
15519
+ const printCenteredRow = (content, width = INNER_WIDTH) => {
15520
+ const visLen = visibleWidth(content);
15521
+ const totalPad = Math.max(0, width - visLen);
15522
+ const leftPad = Math.floor(totalPad / 2);
15523
+ const rightPad = totalPad - leftPad;
15524
+ push(` ${border("\u2502")} ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} ${border("\u2502")}`);
15525
+ };
15526
+ const printDivider = (width = INNER_WIDTH) => {
15527
+ push(` ${border("\u251C" + "\u2500".repeat(width + 2) + "\u2524")}`);
15528
+ };
15529
+ push("");
15530
+ push(` ${border("\u250C" + "\u2500".repeat(INNER_WIDTH + 2) + "\u2510")}`);
15531
+ printCenteredRow(import_picocolors2.default.bold(import_picocolors2.default.white("Q O D E W K")));
15532
+ printCenteredRow(coral("Telemetry Control Panel"));
15533
+ printDivider();
15534
+ printCenteredRow(mutedSoft("Use \u2191 / \u2193 to navigate \xB7 Enter to select \xB7 q to quit"));
15535
+ printDivider();
15536
+ printRow("");
15537
+ const mainItems = items.filter((item) => item.id !== "exit");
15538
+ const exitItem = items.find((item) => item.id === "exit");
15539
+ for (let i2 = 0; i2 < mainItems.length; i2++) {
15540
+ const item = mainItems[i2];
15541
+ const itemIndex = items.indexOf(item);
15542
+ const isSelected = itemIndex === selectedIndex;
15543
+ if (isSelected) {
15544
+ const pointer = import_picocolors2.default.bold(coral("\u203A"));
15545
+ const keyTag = import_picocolors2.default.bold(coral(`[${item.key}]`));
15546
+ const label = import_picocolors2.default.bold(import_picocolors2.default.white(item.label));
15547
+ printRow(` ${pointer} ${keyTag} ${label}`);
15548
+ printRow(` ${amber(item.description)}`);
15549
+ } else {
15550
+ const keyTag = mutedSoft(`[${item.key}]`);
15551
+ const label = import_picocolors2.default.dim(import_picocolors2.default.white(item.label));
15552
+ printRow(` ${keyTag} ${label}`);
15553
+ printRow(` ${import_picocolors2.default.dim(item.description)}`);
15554
+ }
15555
+ if (i2 < mainItems.length - 1) {
15556
+ printRow("");
15557
+ }
15558
+ }
15559
+ printRow("");
15560
+ if (exitItem) {
15561
+ printDivider();
15562
+ const exitIndex = items.indexOf(exitItem);
15563
+ const isSelected = exitIndex === selectedIndex;
15564
+ if (isSelected) {
15565
+ const pointer = import_picocolors2.default.bold(coral("\u203A"));
15566
+ const keyTag = import_picocolors2.default.bold(coral(`[${exitItem.key}]`));
15567
+ const label = import_picocolors2.default.bold(import_picocolors2.default.white(exitItem.label));
15568
+ printRow(` ${pointer} ${keyTag} ${label}`);
15569
+ printRow(` ${amber(exitItem.description)}`);
15570
+ } else {
15571
+ const keyTag = mutedSoft(`[${exitItem.key}]`);
15572
+ const label = import_picocolors2.default.dim(import_picocolors2.default.white(exitItem.label));
15573
+ printRow(` ${keyTag} ${label}`);
15574
+ printRow(` ${import_picocolors2.default.dim(exitItem.description)}`);
15575
+ }
15576
+ }
15577
+ printDivider();
15578
+ printCenteredRow(import_picocolors2.default.dim("[\u2713] Source code was never uploaded to Qodewk"));
15579
+ push(` ${border("\u2514" + "\u2500".repeat(INNER_WIDTH + 2) + "\u2518")}`);
15580
+ push("");
15581
+ return lines.join("\n");
15582
+ }
15583
+ function askLine(promptText) {
15584
+ return new Promise((resolve4) => {
15585
+ const rl = readline.createInterface({
15586
+ input: process.stdin,
15587
+ output: process.stdout
15588
+ });
15589
+ rl.question(promptText, (answer) => {
15590
+ rl.close();
15591
+ resolve4(answer.trim());
15592
+ });
15593
+ });
15594
+ }
15595
+ function waitForKeyToReturn() {
15596
+ return new Promise((resolve4) => {
15597
+ console.log(import_picocolors2.default.dim("\n Press any key or Enter to return to menu..."));
15598
+ if (!process.stdin.isTTY) {
15599
+ resolve4();
15600
+ return;
15601
+ }
15602
+ const wasRaw = process.stdin.isRaw;
15603
+ process.stdin.setRawMode(true);
15604
+ process.stdin.resume();
15605
+ const onKey = () => {
15606
+ process.stdin.removeListener("data", onKey);
15607
+ if (wasRaw !== void 0) process.stdin.setRawMode(wasRaw);
15608
+ resolve4();
15609
+ };
15610
+ process.stdin.once("data", onKey);
15611
+ });
15612
+ }
15613
+ async function runInteractiveMenu() {
15614
+ if (!process.stdin.isTTY) {
15615
+ console.log(buildMenuFrame(0));
15616
+ console.log(import_picocolors2.default.yellow("Interactive menu requires a TTY terminal. Use `qodewk --help` for commands."));
15617
+ return;
15618
+ }
15619
+ let selectedIndex = 0;
15620
+ let active = true;
15621
+ let lastRenderedLinesCount = 0;
15622
+ const render = () => {
15623
+ const frame = buildMenuFrame(selectedIndex);
15624
+ const lineCount = frame.split("\n").length;
15625
+ if (lastRenderedLinesCount > 0) {
15626
+ readline.cursorTo(process.stdout, 0);
15627
+ readline.moveCursor(process.stdout, 0, -lastRenderedLinesCount);
15628
+ readline.clearScreenDown(process.stdout);
15629
+ }
15630
+ process.stdout.write(frame);
15631
+ lastRenderedLinesCount = lineCount;
15632
+ };
15633
+ const cleanup = () => {
15634
+ try {
15635
+ process.stdin.setRawMode(false);
15636
+ } catch {
15637
+ }
15638
+ process.stdout.write("\x1B[?25h");
15639
+ };
15640
+ process.on("exit", cleanup);
15641
+ const executeSelectedAction = async () => {
15642
+ cleanup();
15643
+ console.log("");
15644
+ lastRenderedLinesCount = 0;
15645
+ const selectedItem = MENU_ITEMS[selectedIndex];
15646
+ switch (selectedItem.id) {
15647
+ case "receipt": {
15648
+ console.log(import_picocolors2.default.dim(" [\xB7] Generating local receipt from git diff..."));
15649
+ try {
15650
+ const receipt = await generateReceipt({ isPublic: false });
15651
+ const db = new LocalStateDB();
15652
+ db.saveReceipt(receipt);
15653
+ db.close();
15654
+ await renderTerminalReceipt(receipt);
15655
+ } catch (err) {
15656
+ const message = err instanceof Error ? err.message : String(err);
15657
+ console.error(import_picocolors2.default.red(`
15658
+ Failed to generate receipt: ${message}`));
15659
+ }
15660
+ await waitForKeyToReturn();
15661
+ break;
15662
+ }
15663
+ case "audit": {
15664
+ console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Audit Branch or Revision Diff")));
15665
+ console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
15666
+ let defaultBase = "origin/main";
15667
+ try {
15668
+ const { simpleGit: simpleGit2 } = await import("simple-git");
15669
+ defaultBase = await detectDefaultBaseBranch(simpleGit2(process.cwd()));
15670
+ } catch {
15671
+ }
15672
+ const base = await askLine(
15673
+ ` \u203A Base git ref or commit SHA [default: ${defaultBase}]: `
15674
+ );
15675
+ const head = await askLine(
15676
+ ` \u203A Head git ref or commit SHA [default: HEAD]: `
15677
+ );
15678
+ console.log(import_picocolors2.default.dim("\n [\xB7] Auditing git revision range..."));
15679
+ try {
15680
+ const receipt = await generateReceipt({
15681
+ baseSha: base || defaultBase,
15682
+ headSha: head || "HEAD",
15683
+ isPublic: false
15684
+ });
15685
+ const db = new LocalStateDB();
15686
+ db.saveReceipt(receipt);
15687
+ db.close();
15688
+ await renderTerminalReceipt(receipt);
15689
+ } catch (err) {
15690
+ const message = err instanceof Error ? err.message : String(err);
15691
+ console.error(import_picocolors2.default.red(`
15692
+ Audit failed: ${message}`));
15693
+ }
15694
+ await waitForKeyToReturn();
15695
+ break;
15696
+ }
15697
+ case "share": {
15698
+ console.log(import_picocolors2.default.dim(" [\xB7] Generating and sanitizing receipt for cloud publishing..."));
15699
+ try {
15700
+ const receipt = await generateReceipt({ isPublic: false });
15701
+ if (process.env.QODEWK_TELEMETRY === "off") {
15702
+ console.log(import_picocolors2.default.yellow("\n Cloud publishing is disabled by QODEWK_TELEMETRY=off."));
15703
+ await renderTerminalReceipt(receipt);
15704
+ await waitForKeyToReturn();
15705
+ break;
15706
+ }
15707
+ const sanitized = sanitizeReceiptForShare(receipt);
15708
+ let body = JSON.stringify(sanitized);
15709
+ if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES && sanitized.ai.sessions) {
15710
+ const trimmed2 = {
15711
+ ...sanitized,
15712
+ ai: { ...sanitized.ai, sessions: void 0 }
15713
+ };
15714
+ body = JSON.stringify(trimmed2);
15715
+ }
15716
+ const endpoint = process.env.QODEWK_API_URL || `${process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online"}/api/receipts`;
15717
+ console.log(import_picocolors2.default.dim(` [\xB7] Uploading metadata to ${endpoint}...`));
15718
+ const response = await fetch(endpoint, {
15719
+ method: "POST",
15720
+ headers: {
15721
+ "Content-Type": "application/json",
15722
+ "Content-Length": String(Buffer.byteLength(body, "utf-8"))
15723
+ },
15724
+ body
15725
+ });
15726
+ if (!response.ok) {
15727
+ const errText = await response.text().catch(() => "");
15728
+ throw new Error(`API responded with ${response.status}${errText ? `: ${errText.slice(0, 100)}` : ""}`);
15729
+ }
15730
+ const data = await response.json();
15731
+ const db = new LocalStateDB();
15732
+ db.saveReceipt(sanitized, data.claimToken);
15733
+ db.close();
15734
+ console.log("");
15735
+ console.log(colors.green(" [\u2713] Published successfully."));
15736
+ console.log(` ${import_picocolors2.default.dim("Public URL:")} ${import_picocolors2.default.underline(import_picocolors2.default.cyan(data.url))}`);
15737
+ console.log(` ${import_picocolors2.default.bold(colors.amber("Claim token:"))} ${colors.amber(data.claimToken)}`);
15738
+ console.log(import_picocolors2.default.dim(" (Saved to ~/.qodewk/state.db for future authorship proofs)\n"));
15739
+ await renderTerminalReceipt(sanitized, data.url);
15740
+ } catch (err) {
15741
+ const message = err instanceof Error ? err.message : String(err);
15742
+ console.error(import_picocolors2.default.red(`
15743
+ Share failed: ${message}`));
15744
+ }
15745
+ await waitForKeyToReturn();
15746
+ break;
15747
+ }
15748
+ case "hooks": {
15749
+ await manageHooksSubmenu();
15750
+ break;
15751
+ }
15752
+ case "storage": {
15753
+ await showStorageStatus();
15754
+ break;
15755
+ }
15756
+ case "exit": {
15757
+ active = false;
15758
+ cleanup();
15759
+ console.log(import_picocolors2.default.dim(" Exited Qodewk.\n"));
15760
+ process.exit(0);
15761
+ return;
15762
+ }
15763
+ }
15764
+ };
15765
+ const manageHooksSubmenu = async () => {
15766
+ const hooksDir = resolveGitHooksDir();
15767
+ const status = checkHookStatus(hooksDir);
15768
+ console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Qodewk Git Hooks Management")));
15769
+ console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
15770
+ if (!status.isGit) {
15771
+ console.log(import_picocolors2.default.yellow(" Not inside a Git repository. Hooks cannot be configured."));
15772
+ await waitForKeyToReturn();
15773
+ return;
15774
+ }
15775
+ console.log(` Hooks directory: ${import_picocolors2.default.dim(status.hooksDir || "None")}`);
15776
+ console.log(
15777
+ ` post-commit: ${status.postCommitInstalled ? colors.green("[\u2713 Installed]") : import_picocolors2.default.dim("[\xB7 Not installed]")}`
15778
+ );
15779
+ console.log(
15780
+ ` post-rewrite: ${status.postRewriteInstalled ? colors.green("[\u2713 Installed]") : import_picocolors2.default.dim("[\xB7 Not installed]")}`
15781
+ );
15782
+ console.log("");
15783
+ console.log(" [1] Install non-blocking hooks (< 5ms background recorder)");
15784
+ console.log(" [2] Uninstall Qodewk hooks");
15785
+ console.log(" [0] Back to main menu");
15786
+ console.log("");
15787
+ const choice = await askLine(" \u203A Select option [0-2]: ");
15788
+ if (choice === "1") {
15789
+ if (status.hooksDir) {
15790
+ const path9 = await import("path");
15791
+ const targets = ["post-commit", "post-rewrite"];
15792
+ for (const t2 of targets) {
15793
+ installHookFile(path9.join(status.hooksDir, t2));
15794
+ }
15795
+ console.log(colors.green("\n [\u2713] Non-blocking hooks installed successfully."));
15796
+ }
15797
+ } else if (choice === "2") {
15798
+ if (status.hooksDir) {
15799
+ const path9 = await import("path");
15800
+ const targets = ["post-commit", "post-rewrite"];
15801
+ for (const t2 of targets) {
15802
+ uninstallHookFile(path9.join(status.hooksDir, t2));
15803
+ }
15804
+ console.log(colors.green("\n [\u2713] Qodewk hooks removed."));
15805
+ }
15806
+ }
15807
+ await waitForKeyToReturn();
15808
+ };
15809
+ const showStorageStatus = async () => {
15810
+ console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Qodewk Local Database & Storage Status")));
15811
+ console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
15812
+ try {
15813
+ const db = new LocalStateDB();
15814
+ const stats = db.getStats();
15815
+ const recent = db.getRecentReceipts(5);
15816
+ db.close();
15817
+ console.log(` Database path: ${import_picocolors2.default.dim("~/.qodewk/state.db")}`);
15818
+ console.log(` Total saved receipts: ${import_picocolors2.default.bold(import_picocolors2.default.white(String(stats.totalReceipts)))}`);
15819
+ console.log(` Total agent footprints: ${import_picocolors2.default.bold(import_picocolors2.default.white(String(stats.totalFootprints)))}`);
15820
+ console.log("");
15821
+ if (recent.length === 0) {
15822
+ console.log(import_picocolors2.default.dim(" No stored receipts yet. Run 'Generate Local Receipt' to record your first."));
15823
+ } else {
15824
+ console.log(import_picocolors2.default.dim(" Recent Receipts:"));
15825
+ console.log(import_picocolors2.default.dim(" " + "ID".padEnd(26) + "DATE".padEnd(12) + "BRANCH".padEnd(18) + "COST"));
15826
+ console.log(import_picocolors2.default.dim(" " + "\u2500".repeat(60)));
15827
+ for (const item of recent) {
15828
+ const id = item.receipt.receipt.id.slice(0, 24).padEnd(26);
15829
+ const date = item.receipt.receipt.createdAt.slice(0, 10).padEnd(12);
15830
+ const branch = (item.receipt.repository.branch || "unknown").slice(0, 16).padEnd(18);
15831
+ const cost = colors.coral(`$${item.receipt.ai.cost.toFixed(2)}`);
15832
+ console.log(` ${id}${date}${branch}${cost}`);
15833
+ }
15834
+ }
15835
+ console.log("");
15836
+ console.log(` ${colors.green("[\u2713]")} ${import_picocolors2.default.dim("Privacy guarantee: raw source code is never stored in SQLite")}`);
15837
+ } catch (err) {
15838
+ const message = err instanceof Error ? err.message : String(err);
15839
+ console.error(import_picocolors2.default.red(` Failed to inspect storage: ${message}`));
15840
+ }
15841
+ await waitForKeyToReturn();
15842
+ };
15843
+ readline.emitKeypressEvents(process.stdin);
15844
+ const startLoop = () => {
15845
+ process.stdout.write("\x1B[?25l");
15846
+ process.stdin.setRawMode(true);
15847
+ process.stdin.resume();
15848
+ render();
15849
+ const onKeypress = async (_str, key) => {
15850
+ if (!active) return;
15851
+ if (key.ctrl && key.name === "c") {
15852
+ cleanup();
15853
+ process.exit(0);
15854
+ }
15855
+ if (key.name === "q" || key.name === "escape") {
15856
+ cleanup();
15857
+ console.log(import_picocolors2.default.dim("\n Exited Qodewk.\n"));
15858
+ process.exit(0);
15859
+ }
15860
+ if (key.name === "up" || key.name === "k") {
15861
+ selectedIndex = (selectedIndex - 1 + MENU_ITEMS.length) % MENU_ITEMS.length;
15862
+ render();
15863
+ return;
15864
+ }
15865
+ if (key.name === "down" || key.name === "j") {
15866
+ selectedIndex = (selectedIndex + 1) % MENU_ITEMS.length;
15867
+ render();
15868
+ return;
15869
+ }
15870
+ const itemByKey = MENU_ITEMS.findIndex((item) => item.key === key.name || item.key === _str);
15871
+ if (itemByKey !== -1) {
15872
+ selectedIndex = itemByKey;
15873
+ render();
15874
+ process.stdin.removeListener("keypress", onKeypress);
15875
+ await executeSelectedAction();
15876
+ if (active) {
15877
+ startLoop();
15878
+ }
15879
+ return;
15880
+ }
15881
+ if (key.name === "return" || key.name === "enter") {
15882
+ process.stdin.removeListener("keypress", onKeypress);
15883
+ await executeSelectedAction();
15884
+ if (active) {
15885
+ startLoop();
15886
+ }
15887
+ return;
15888
+ }
15889
+ };
15890
+ process.stdin.on("keypress", onKeypress);
15891
+ };
15892
+ startLoop();
15893
+ }
15894
+
15895
+ // src/index.ts
15896
+ var SHARE_PAYLOAD_MAX_BYTES2 = 5e4;
15897
+ function resolveCliVersion() {
15898
+ try {
15899
+ const argv1 = process.argv[1] ? path8.dirname(path8.resolve(process.argv[1])) : process.cwd();
15900
+ const candidates = [
15901
+ path8.join(argv1, "..", "package.json"),
15902
+ path8.join(argv1, "package.json")
15903
+ ];
15904
+ for (const candidate of candidates) {
15905
+ if (!fs8.existsSync(candidate)) continue;
15906
+ const pkg = JSON.parse(fs8.readFileSync(candidate, "utf-8"));
15907
+ if (pkg.name === "qodewk" && pkg.version) return pkg.version;
15908
+ }
15909
+ } catch {
15910
+ }
15911
+ return "0.3.0";
15912
+ }
15913
+ function resolveSince(options) {
14738
15914
  return options.today ? "today" : options.since;
14739
15915
  }
14740
15916
  function persistReceipt(receipt, claimToken) {
@@ -14745,70 +15921,12 @@ function persistReceipt(receipt, claimToken) {
14745
15921
  } catch {
14746
15922
  }
14747
15923
  }
14748
- function resolveGitHooksDir(cwd = process.cwd()) {
14749
- const gitPath = path7.join(cwd, ".git");
14750
- if (!fs6.existsSync(gitPath)) return null;
14751
- let gitCommonDir;
14752
- const stat = fs6.statSync(gitPath);
14753
- if (stat.isDirectory()) {
14754
- gitCommonDir = gitPath;
14755
- } else {
14756
- const content = fs6.readFileSync(gitPath, "utf-8");
14757
- const match = content.match(/gitdir:\s*(.+)/i);
14758
- if (!match?.[1]) return null;
14759
- let gitDir = match[1].trim();
14760
- if (!path7.isAbsolute(gitDir)) {
14761
- gitDir = path7.resolve(cwd, gitDir);
14762
- }
14763
- const commonFile = path7.join(gitDir, "commondir");
14764
- if (fs6.existsSync(commonFile)) {
14765
- let common = fs6.readFileSync(commonFile, "utf-8").trim();
14766
- if (!path7.isAbsolute(common)) {
14767
- common = path7.resolve(gitDir, common);
14768
- }
14769
- gitCommonDir = common;
14770
- } else if (path7.basename(path7.dirname(gitDir)) === "worktrees") {
14771
- gitCommonDir = path7.dirname(path7.dirname(gitDir));
14772
- } else {
14773
- gitCommonDir = gitDir;
14774
- }
14775
- }
14776
- return path7.join(gitCommonDir, "hooks");
14777
- }
14778
- var HOOK_MARKER_BEGIN = "# --- BEGIN QODEWK HOOK ---";
14779
- var HOOK_MARKER_END = "# --- END QODEWK HOOK ---";
14780
- var HOOK_SNIPPET = `
14781
- ${HOOK_MARKER_BEGIN}
14782
- # Non-blocking Qodewk background recorder (< 5ms)
14783
- if command -v qodewk >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1 || [ -f "./node_modules/.bin/qodewk" ]; then
14784
- ( ( qodewk record || pnpm qodewk record || npx qodewk record ) >/dev/null 2>&1 & )
14785
- fi
14786
- ${HOOK_MARKER_END}
14787
- `;
14788
- function installHookFile(hookFile) {
14789
- let content = "";
14790
- if (fs6.existsSync(hookFile)) {
14791
- content = fs6.readFileSync(hookFile, "utf-8");
14792
- } else {
14793
- content = "#!/bin/sh\n";
14794
- }
14795
- if (content.includes(HOOK_MARKER_BEGIN)) {
14796
- return "exists";
14797
- }
14798
- fs6.writeFileSync(hookFile, content + HOOK_SNIPPET, { mode: 493 });
14799
- return "installed";
14800
- }
14801
- function uninstallHookFile(hookFile) {
14802
- if (!fs6.existsSync(hookFile)) return "absent";
14803
- const content = fs6.readFileSync(hookFile, "utf-8");
14804
- if (!content.includes(HOOK_MARKER_BEGIN)) return "missing";
14805
- const regex = new RegExp(`\\n?${HOOK_MARKER_BEGIN}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, "g");
14806
- const updated = content.replace(regex, "");
14807
- fs6.writeFileSync(hookFile, updated, { mode: 493 });
14808
- return "removed";
14809
- }
14810
15924
  var program2 = new Command();
14811
- program2.name("qodewk").description("Universal telemetry and digital receipt generator for the AI coding agent era").version(resolveCliVersion()).option("-j, --json", "Output receipt in machine-readable JSON format").option("-f, --format <format>", "Output format (terminal, json, markdown)", "terminal").option("-o, --out <path>", "Write receipt output to specified file path").option("-p, --provider <provider>", "Specify AI provider (antigravity, claude, cursor, etc.)").option("-m, --model <model>", "Specify AI model identifier").option("-s, --since <duration>", "Harvest agent footprints since duration (e.g. today, 24h, 7d)").option("--today", "Harvest agent footprints for today").option("--platform <platform>", "Filter agent platform (antigravity, claude, cursor, all)").option("--anon", "Anonymize branch name in output").option("--local", "Force local-only mode (never open network sockets)").option("--barcode", "Render classic 1D thermal barcode (always on; kept for compatibility)").action(async (options) => {
15925
+ program2.name("qodewk").description("Universal telemetry and digital receipt generator for the AI coding agent era").version(resolveCliVersion()).option("-i, --interactive", "Launch interactive terminal control panel").option("--menu", "Launch interactive terminal control panel").option("-j, --json", "Output receipt in machine-readable JSON format").option("-f, --format <format>", "Output format (terminal, json, markdown)", "terminal").option("-o, --out <path>", "Write receipt output to specified file path").option("-p, --provider <provider>", "Specify AI provider (antigravity, claude, cursor, etc.)").option("-m, --model <model>", "Specify AI model identifier").option("-s, --since <duration>", "Harvest agent footprints since duration (e.g. today, 24h, 7d)").option("--today", "Harvest agent footprints for today").option("--platform <platform>", "Filter agent platform (antigravity, claude, cursor, all)").option("--anon", "Anonymize branch name in output").option("--local", "Force local-only mode (never open network sockets)").option("--barcode", "Render classic 1D thermal barcode (always on; kept for compatibility)").action(async (options) => {
15926
+ if (options.interactive || options.menu) {
15927
+ await runInteractiveMenu();
15928
+ return;
15929
+ }
14812
15930
  try {
14813
15931
  const receipt = await generateReceipt({
14814
15932
  provider: options.provider,
@@ -14822,11 +15940,14 @@ program2.name("qodewk").description("Universal telemetry and digital receipt gen
14822
15940
  await outputReceipt(receipt, options);
14823
15941
  } catch (err) {
14824
15942
  const message = err instanceof Error ? err.message : String(err);
14825
- console.error(import_picocolors.default.red(`Error generating receipt: ${message}`));
15943
+ console.error(import_picocolors3.default.red(`Error generating receipt: ${message}`));
14826
15944
  process.exit(1);
14827
15945
  }
14828
15946
  });
14829
- program2.command("audit").description("Audit PR or branch diff against a target base commit").option("-b, --base <base>", "Base git ref or commit SHA (e.g. origin/main)").option("-h, --head <head>", "Head git ref or commit SHA").option("-f, --format <format>", "Output format (terminal, json, markdown)", "terminal").option("-o, --out <path>", "Write receipt output to specified file path").option("-p, --provider <provider>", "Specify AI provider").option("-m, --model <model>", "Specify AI model").option("-s, --since <duration>", "Harvest agent footprints since duration").option("--today", "Harvest agent footprints for today").option("--platform <platform>", "Filter agent platform").option("--anon", "Anonymize branch name in output").option("--barcode", "Render classic 1D thermal barcode (always on; kept for compatibility)").action(async (options) => {
15947
+ program2.command("menu").description("Launch interactive terminal control panel").action(async () => {
15948
+ await runInteractiveMenu();
15949
+ });
15950
+ program2.command("audit").description("Audit PR or branch diff against a target base commit").option("-b, --base <base>", "Base git ref or commit SHA (e.g. origin/main)").option("-H, --head <head>", "Head git ref or commit SHA").option("-f, --format <format>", "Output format (terminal, json, markdown)", "terminal").option("-o, --out <path>", "Write receipt output to specified file path").option("-p, --provider <provider>", "Specify AI provider").option("-m, --model <model>", "Specify AI model").option("-s, --since <duration>", "Harvest agent footprints since duration").option("--today", "Harvest agent footprints for today").option("--platform <platform>", "Filter agent platform").option("--anon", "Anonymize branch name in output").option("--barcode", "Render classic 1D thermal barcode (always on; kept for compatibility)").action(async (options) => {
14830
15951
  try {
14831
15952
  const receipt = await generateReceipt({
14832
15953
  baseSha: options.base,
@@ -14842,7 +15963,7 @@ program2.command("audit").description("Audit PR or branch diff against a target
14842
15963
  await outputReceipt(receipt, options);
14843
15964
  } catch (err) {
14844
15965
  const message = err instanceof Error ? err.message : String(err);
14845
- console.error(import_picocolors.default.red(`Error auditing git range: ${message}`));
15966
+ console.error(import_picocolors3.default.red(`Error auditing git range: ${message}`));
14846
15967
  process.exit(1);
14847
15968
  }
14848
15969
  });
@@ -14859,31 +15980,31 @@ program2.command("share").description("Publish privacy-safe receipt to Qodewk cl
14859
15980
  const localFlag = program2.opts().local === true;
14860
15981
  if (process.env.QODEWK_TELEMETRY === "off" || localFlag) {
14861
15982
  console.log(
14862
- import_picocolors.default.yellow(
15983
+ import_picocolors3.default.yellow(
14863
15984
  `
14864
15985
  Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"}).`
14865
15986
  )
14866
15987
  );
14867
- console.log(import_picocolors.default.dim("Telemetry remains strictly stored in local SQLite (~/.qodewk/state.db).\n"));
15988
+ console.log(import_picocolors3.default.dim("Telemetry remains strictly stored in local SQLite (~/.qodewk/state.db).\n"));
14868
15989
  persistReceipt(receipt);
14869
15990
  await renderTerminalReceipt(receipt);
14870
15991
  return;
14871
15992
  }
14872
15993
  const sanitized = sanitizeReceiptForShare(receipt);
14873
15994
  let body = JSON.stringify(sanitized);
14874
- if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES && sanitized.ai.sessions) {
15995
+ if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES2 && sanitized.ai.sessions) {
14875
15996
  const trimmed2 = {
14876
15997
  ...sanitized,
14877
15998
  ai: { ...sanitized.ai, sessions: void 0 }
14878
15999
  };
14879
16000
  body = JSON.stringify(trimmed2);
14880
16001
  }
14881
- if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES) {
14882
- console.error(import_picocolors.default.red("Sanitized receipt still exceeds the 50 KB cloud payload limit."));
16002
+ if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES2) {
16003
+ console.error(import_picocolors3.default.red("Sanitized receipt still exceeds the 50 KB cloud payload limit."));
14883
16004
  process.exit(1);
14884
16005
  }
14885
16006
  const endpoint = process.env.QODEWK_API_URL || `${process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online"}/api/receipts`;
14886
- console.log(import_picocolors.default.dim(`Publishing receipt ${sanitized.receipt.id} to ${endpoint}...`));
16007
+ console.log(import_picocolors3.default.dim(`Publishing receipt ${sanitized.receipt.id} to ${endpoint}...`));
14887
16008
  try {
14888
16009
  const response = await fetch(endpoint, {
14889
16010
  method: "POST",
@@ -14900,11 +16021,11 @@ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"})
14900
16021
  const data = await response.json();
14901
16022
  persistReceipt(sanitized, data.claimToken);
14902
16023
  console.log("");
14903
- console.log(import_picocolors.default.green(" Published successfully."));
14904
- console.log(` ${import_picocolors.default.dim("Public URL:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(data.url))}`);
14905
- console.log(` ${import_picocolors.default.bold(import_picocolors.default.yellow(" Claim token (store securely \u2014 shown once):"))}`);
14906
- console.log(` ${import_picocolors.default.yellow(data.claimToken)}`);
14907
- console.log(import_picocolors.default.dim(" Saved to ~/.qodewk/state.db \u2014 required to prove authorship later."));
16024
+ console.log(import_picocolors3.default.green(" Published successfully."));
16025
+ console.log(` ${import_picocolors3.default.dim("Public URL:")} ${import_picocolors3.default.underline(import_picocolors3.default.cyan(data.url))}`);
16026
+ console.log(` ${import_picocolors3.default.bold(import_picocolors3.default.yellow(" Claim token (store securely \u2014 shown once):"))}`);
16027
+ console.log(` ${import_picocolors3.default.yellow(data.claimToken)}`);
16028
+ console.log(import_picocolors3.default.dim(" Saved to ~/.qodewk/state.db \u2014 required to prove authorship later."));
14908
16029
  console.log("");
14909
16030
  await renderTerminalReceipt(sanitized, data.url);
14910
16031
  if (options.json) {
@@ -14912,14 +16033,14 @@ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"})
14912
16033
  }
14913
16034
  } catch (networkErr) {
14914
16035
  const message = networkErr instanceof Error ? networkErr.message : String(networkErr);
14915
- console.log(import_picocolors.default.yellow(`
16036
+ console.log(import_picocolors3.default.yellow(`
14916
16037
  Could not reach cloud API (${message}). Rendered locally:`));
14917
16038
  persistReceipt(receipt);
14918
16039
  await renderTerminalReceipt(receipt);
14919
16040
  }
14920
16041
  } catch (err) {
14921
16042
  const message = err instanceof Error ? err.message : String(err);
14922
- console.error(import_picocolors.default.red(`Error sharing receipt: ${message}`));
16043
+ console.error(import_picocolors3.default.red(`Error sharing receipt: ${message}`));
14923
16044
  process.exit(1);
14924
16045
  }
14925
16046
  });
@@ -14937,35 +16058,35 @@ hookCommand.command("install").description("Install non-blocking post-commit and
14937
16058
  try {
14938
16059
  const hooksDir = resolveGitHooksDir();
14939
16060
  if (!hooksDir) {
14940
- console.error(import_picocolors.default.red("Error: Current directory is not a Git repository (.git not found)."));
16061
+ console.error(import_picocolors3.default.red("Error: Current directory is not a Git repository (.git not found)."));
14941
16062
  process.exit(1);
14942
16063
  }
14943
- if (!fs6.existsSync(hooksDir)) {
14944
- fs6.mkdirSync(hooksDir, { recursive: true });
16064
+ if (!fs8.existsSync(hooksDir)) {
16065
+ fs8.mkdirSync(hooksDir, { recursive: true });
14945
16066
  }
14946
16067
  const targets = ["post-commit", "post-rewrite"];
14947
16068
  let installed = 0;
14948
16069
  let existing = 0;
14949
16070
  for (const name of targets) {
14950
- const result = installHookFile(path7.join(hooksDir, name));
16071
+ const result = installHookFile(path8.join(hooksDir, name));
14951
16072
  if (result === "installed") installed++;
14952
16073
  else existing++;
14953
16074
  }
14954
16075
  if (installed === 0 && existing > 0) {
14955
- console.log(import_picocolors.default.yellow("Qodewk Git hooks are already installed (post-commit, post-rewrite)."));
16076
+ console.log(import_picocolors3.default.yellow("Qodewk Git hooks are already installed (post-commit, post-rewrite)."));
14956
16077
  return;
14957
16078
  }
14958
16079
  console.log(
14959
- import_picocolors.default.green(
16080
+ import_picocolors3.default.green(
14960
16081
  `\u2713 Non-blocking Qodewk hooks installed in ${hooksDir} (post-commit${installed > 1 || existing > 0 ? ", post-rewrite" : installed === 1 && targets.length === 2 ? " + post-rewrite" : ""})`
14961
16082
  )
14962
16083
  );
14963
16084
  if (existing > 0) {
14964
- console.log(import_picocolors.default.dim(` (${existing} hook file(s) already contained the Qodewk marker)`));
16085
+ console.log(import_picocolors3.default.dim(` (${existing} hook file(s) already contained the Qodewk marker)`));
14965
16086
  }
14966
16087
  } catch (err) {
14967
16088
  const message = err instanceof Error ? err.message : String(err);
14968
- console.error(import_picocolors.default.red(`Failed to install Git hook: ${message}`));
16089
+ console.error(import_picocolors3.default.red(`Failed to install Git hook: ${message}`));
14969
16090
  process.exit(1);
14970
16091
  }
14971
16092
  });
@@ -14973,184 +16094,24 @@ hookCommand.command("uninstall").description("Remove Qodewk post-commit and post
14973
16094
  try {
14974
16095
  const hooksDir = resolveGitHooksDir();
14975
16096
  if (!hooksDir) {
14976
- console.log(import_picocolors.default.yellow("No Git repository found."));
16097
+ console.log(import_picocolors3.default.yellow("No Git repository found."));
14977
16098
  return;
14978
16099
  }
14979
16100
  const targets = ["post-commit", "post-rewrite"];
14980
16101
  let removed = 0;
14981
16102
  for (const name of targets) {
14982
- const result = uninstallHookFile(path7.join(hooksDir, name));
16103
+ const result = uninstallHookFile(path8.join(hooksDir, name));
14983
16104
  if (result === "removed") removed++;
14984
16105
  }
14985
16106
  if (removed === 0) {
14986
- console.log(import_picocolors.default.yellow("Qodewk hooks are not installed."));
16107
+ console.log(import_picocolors3.default.yellow("Qodewk hooks are not installed."));
14987
16108
  return;
14988
16109
  }
14989
- console.log(import_picocolors.default.green(`\u2713 Qodewk hook markers removed from ${removed} file(s).`));
16110
+ console.log(import_picocolors3.default.green(`\u2713 Qodewk hook markers removed from ${removed} file(s).`));
14990
16111
  } catch (err) {
14991
16112
  const message = err instanceof Error ? err.message : String(err);
14992
- console.error(import_picocolors.default.red(`Failed to uninstall hook: ${message}`));
16113
+ console.error(import_picocolors3.default.red(`Failed to uninstall hook: ${message}`));
14993
16114
  process.exit(1);
14994
16115
  }
14995
16116
  });
14996
- var ansiHex = (hexColor) => {
14997
- const num = parseInt(hexColor.replace("#", ""), 16);
14998
- const r2 = num >> 16 & 255;
14999
- const g = num >> 8 & 255;
15000
- const b2 = num & 255;
15001
- return (text) => `\x1B[38;2;${r2};${g};${b2}m${text}\x1B[39m`;
15002
- };
15003
- var stripAnsi = (str) => {
15004
- return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\([B0]/g, "");
15005
- };
15006
- var visibleWidth = (str) => {
15007
- return stripAnsi(str).length;
15008
- };
15009
- function buildTerminalReceipt(receipt, publicUrl) {
15010
- const lines = [];
15011
- const coral = ansiHex("#cc785c");
15012
- const green = ansiHex("#5db872");
15013
- const red = ansiHex("#c64545");
15014
- const teal = ansiHex("#5db8a6");
15015
- const INNER_WIDTH = 52;
15016
- const push = (line) => lines.push(line);
15017
- const printRow = (content, width = INNER_WIDTH) => {
15018
- const visLen = visibleWidth(content);
15019
- const pad = Math.max(0, width - visLen);
15020
- push(` | ${content}${" ".repeat(pad)} |`);
15021
- };
15022
- const printRowSplit = (left, right, width = INNER_WIDTH) => {
15023
- const leftVis = visibleWidth(left);
15024
- const rightVis = visibleWidth(right);
15025
- const pad = Math.max(1, width - leftVis - rightVis);
15026
- push(` | ${left}${" ".repeat(pad)}${right} |`);
15027
- };
15028
- const printCenteredRow = (content, width = INNER_WIDTH) => {
15029
- const visLen = visibleWidth(content);
15030
- const totalPad = Math.max(0, width - visLen);
15031
- const leftPad = Math.floor(totalPad / 2);
15032
- const rightPad = totalPad - leftPad;
15033
- push(` | ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} |`);
15034
- };
15035
- const printDivider = (char = "=", width = INNER_WIDTH) => {
15036
- push(` | ${char.repeat(width)} |`);
15037
- };
15038
- const totalTokens = (receipt.ai.tokens.input + receipt.ai.tokens.output).toLocaleString();
15039
- const inputK = `${Math.round(receipt.ai.tokens.input / 1e3)}k`;
15040
- const outputK = `${Math.round(receipt.ai.tokens.output / 1e3)}k`;
15041
- const costPrefix = receipt.ai.mode === "verified" ? "$" : "~$";
15042
- const confidencePercent = `${Math.round(receipt.ai.confidence * 100)}%`;
15043
- push("");
15044
- push(" " + import_picocolors.default.bold(coral("/\\".repeat(28))));
15045
- printCenteredRow(import_picocolors.default.bold(import_picocolors.default.white("Q O D E W K")));
15046
- printCenteredRow(coral("*** PROOF OF SHIPMENT ***"));
15047
- printRow("");
15048
- printRowSplit(
15049
- `ID: ${receipt.receipt.id.slice(0, 24)}`,
15050
- `DATE: ${receipt.receipt.createdAt.slice(0, 10)}`
15051
- );
15052
- printRowSplit(
15053
- `REPO: ${receipt.repository.projectAlias.slice(0, 18)}`,
15054
- `BRANCH: ${receipt.repository.branch.slice(0, 16)}`
15055
- );
15056
- if (receipt.ai.task) {
15057
- const taskClean = receipt.ai.task.length > 44 ? receipt.ai.task.slice(0, 43) + "\u2026" : receipt.ai.task;
15058
- printRow(`TASK: ${taskClean}`);
15059
- }
15060
- printDivider("=");
15061
- const itemsHeader = receipt.repository.commitsCount && receipt.repository.commitsCount > 1 ? `ITEMS CHANGED (${receipt.repository.commitsCount} COMMITS)` : "ITEMS CHANGED";
15062
- printRowSplit(itemsHeader, "QTY");
15063
- printDivider("-");
15064
- printRowSplit("Files Touched", String(receipt.mutation.files));
15065
- printRowSplit("Lines Inserted", green("+ " + receipt.mutation.insertions));
15066
- printRowSplit("Lines Deleted", red("- " + receipt.mutation.deletions));
15067
- printRowSplit(
15068
- "Net Code Delta",
15069
- teal((receipt.mutation.netLines >= 0 ? "+ " : "- ") + Math.abs(receipt.mutation.netLines))
15070
- );
15071
- printDivider("-");
15072
- printRow("AI TELEMETRY & ATTRIBUTION");
15073
- const provString = receipt.ai.provider + " \xB7 " + (receipt.ai.model || "Unknown");
15074
- const cleanProv = provString.length > 40 ? provString.slice(0, 39) + "\u2026" : provString;
15075
- printRow(`Provider: ${cleanProv}`);
15076
- if (receipt.ai.aiWrittenRatio !== void 0) {
15077
- const aiPct = Math.round(receipt.ai.aiWrittenRatio * 100);
15078
- printRowSplit("AI Written Code", `${aiPct}% (Human: ${100 - aiPct}%)`);
15079
- }
15080
- const tokenDetail = `${inputK} in (${Math.round(receipt.ai.tokens.cached / 1e3)}k cached) / ${outputK} out`;
15081
- const cleanTokens = tokenDetail.length > 40 ? tokenDetail.slice(0, 39) + "\u2026" : tokenDetail;
15082
- printRow(`Tokens: ${cleanTokens}`);
15083
- printRowSplit("Total Tokens:", totalTokens);
15084
- printDivider("=");
15085
- const costLabel = receipt.ai.mode === "verified" ? "VERIFIED AI COST" : "ESTIMATED AI COST";
15086
- printRowSplit(costLabel, import_picocolors.default.bold(coral(costPrefix + receipt.ai.cost.toFixed(2))));
15087
- printRowSplit(`CONFIDENCE: ${confidencePercent}`, `[Mode: ${receipt.ai.mode}]`);
15088
- printDivider("=");
15089
- printRow("");
15090
- const baseUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online";
15091
- const isPublished = Boolean(publicUrl);
15092
- const displayHost = baseUrl.replace(/^https?:\/\//, "");
15093
- const barcodePayload = pickBarcodePayload({
15094
- publicUrl,
15095
- receiptId: receipt.receipt.id,
15096
- displayHost,
15097
- maxChars: 28
15098
- });
15099
- const barcodeRows = renderTerminalBarcode(barcodePayload, {
15100
- maxWidth: INNER_WIDTH,
15101
- height: 3,
15102
- quietZone: 8
15103
- });
15104
- for (const barRow of barcodeRows) {
15105
- printCenteredRow(import_picocolors.default.bold(barRow));
15106
- }
15107
- if (isPublished) {
15108
- printCenteredRow(import_picocolors.default.cyan(displayHost));
15109
- printCenteredRow(import_picocolors.default.underline(import_picocolors.default.cyan(`r/${receipt.receipt.id}`)));
15110
- } else {
15111
- printCenteredRow(import_picocolors.default.yellow("[ LOCAL RECORD \u2014 NOT PUBLISHED ]"));
15112
- }
15113
- printRow("");
15114
- if (process.env.QODEWK_TELEMETRY === "off") {
15115
- printRow(` ${teal("[\u2713]")} QODEWK_TELEMETRY=off (Cloud sync disabled)`);
15116
- }
15117
- printRow(` ${green("[\u2713]")} Source code was never uploaded to Qodewk`);
15118
- push(" " + import_picocolors.default.bold(coral("\\/".repeat(28))));
15119
- push("");
15120
- if (isPublished && publicUrl) {
15121
- push(` ${import_picocolors.default.dim("Public Receipt:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(publicUrl))}`);
15122
- } else {
15123
- push(` ${import_picocolors.default.dim("Saved to local DB:")} ${import_picocolors.default.dim("~/.qodewk/state.db")}`);
15124
- push(` ${import_picocolors.default.dim("To publish & get shareable URL:")} ${import_picocolors.default.cyan("qodewk share")}`);
15125
- }
15126
- push("");
15127
- return lines.join("\n");
15128
- }
15129
- async function renderTerminalReceipt(receipt, publicUrl) {
15130
- console.log(buildTerminalReceipt(receipt, publicUrl));
15131
- }
15132
- async function outputReceipt(receipt, options, publicUrl) {
15133
- const format = options.json ? "json" : options.format || "terminal";
15134
- let content;
15135
- if (format === "json") {
15136
- content = JSON.stringify(receipt, null, 2);
15137
- } else if (format === "markdown") {
15138
- content = formatMarkdownReceipt(receipt, publicUrl);
15139
- } else {
15140
- content = buildTerminalReceipt(receipt, publicUrl);
15141
- }
15142
- if (options.out) {
15143
- const fileContent = format === "terminal" ? stripAnsi(content) : content;
15144
- fs6.writeFileSync(options.out, fileContent.endsWith("\n") ? fileContent : fileContent + "\n", "utf-8");
15145
- console.log(import_picocolors.default.green(`\u2713 Receipt written to ${options.out}`));
15146
- }
15147
- if (!options.out) {
15148
- if (format === "terminal") {
15149
- console.log(content);
15150
- } else {
15151
- console.log(content);
15152
- }
15153
- } else if (format !== "terminal") {
15154
- }
15155
- }
15156
16117
  program2.parse(process.argv);