qodewk 0.2.0 → 0.2.2

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 +4 -0
  2. package/dist/index.cjs +407 -140
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -114,10 +114,14 @@ Unlike single-vendor utilities (like Chit, which only support Claude Code CLI),
114
114
  | `npx qodewk --platform <platform>` | Filter telemetry to a specific platform (`antigravity`, `claude`, `cursor`, `all`) | Single-platform audit |
115
115
  | `npx qodewk --json` | Export machine-readable telemetry conforming to canonical `ReceiptV1` schema | Formatted JSON output |
116
116
  | `npx qodewk share` | Publish privacy-safe metadata to Qodewk Cloud and generate short link | `https://qodewk.dev/r/rec_...` |
117
+ | `npx qodewk share --today` | Share today's harvested footprint window | Public URL + claim token |
117
118
  | `npx qodewk audit --base <branch>` | Calculate aggregate diff and telemetry across an entire PR branch range | Git revision delta receipt |
119
+ | `npx qodewk hook install` / `hooks install` | Install non-blocking `post-commit` + `post-rewrite` hooks | Local auto-record |
120
+ | `npx qodewk record` / `record-event` | Silent SQLite record (hook entrypoint) | exit 0 always |
118
121
  | `npx qodewk -f markdown -o receipt.md` | Export sticky Markdown receipt directly formatted for GitHub PR comments | File `receipt.md` |
119
122
  | `npx qodewk -p <provider> -m <model>` | Override detected provider & frontier model pricing rate card | Custom model cost estimate |
120
123
  | `npx qodewk --anon` | Redact sensitive repository and branch identifiers | Privacy-hardened receipt |
124
+ | `npx qodewk --local` | Force local-only mode (no cloud sockets on share) | Local thermal receipt |
121
125
 
122
126
  ---
123
127
 
package/dist/index.cjs CHANGED
@@ -1153,7 +1153,7 @@ var require_command = __commonJS({
1153
1153
  "use strict";
1154
1154
  var EventEmitter2 = require("events").EventEmitter;
1155
1155
  var childProcess = require("child_process");
1156
- var path7 = require("path");
1156
+ var path8 = require("path");
1157
1157
  var fs7 = require("fs");
1158
1158
  var process2 = require("process");
1159
1159
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -2153,9 +2153,9 @@ 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 = path7.resolve(baseDir, baseName);
2156
+ const localBin = path8.resolve(baseDir, baseName);
2157
2157
  if (fs7.existsSync(localBin)) return localBin;
2158
- if (sourceExt.includes(path7.extname(baseName))) return void 0;
2158
+ if (sourceExt.includes(path8.extname(baseName))) return void 0;
2159
2159
  const foundExt = sourceExt.find(
2160
2160
  (ext) => fs7.existsSync(`${localBin}${ext}`)
2161
2161
  );
@@ -2173,17 +2173,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2173
2173
  } catch {
2174
2174
  resolvedScriptPath = this._scriptPath;
2175
2175
  }
2176
- executableDir = path7.resolve(
2177
- path7.dirname(resolvedScriptPath),
2176
+ executableDir = path8.resolve(
2177
+ path8.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 = path7.basename(
2184
+ const legacyName = path8.basename(
2185
2185
  this._scriptPath,
2186
- path7.extname(this._scriptPath)
2186
+ path8.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(path7.extname(executableFile));
2197
+ launchWithNode = sourceExt.includes(path8.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 = path7.basename(filename, path7.extname(filename));
3044
+ this._name = path8.basename(filename, path8.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(path8) {
3059
- if (path8 === void 0) return this._executableDir;
3060
- this._executableDir = path8;
3058
+ executableDir(path9) {
3059
+ if (path9 === void 0) return this._executableDir;
3060
+ this._executableDir = path9;
3061
3061
  return this;
3062
3062
  }
3063
3063
  /**
@@ -4956,10 +4956,10 @@ var require_segments = __commonJS({
4956
4956
  const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled());
4957
4957
  const nodes = buildNodes(segs);
4958
4958
  const graph = buildGraph(nodes, version);
4959
- const path7 = dijkstra.find_path(graph.map, "start", "end");
4959
+ const path8 = dijkstra.find_path(graph.map, "start", "end");
4960
4960
  const optimizedSegs = [];
4961
- for (let i2 = 1; i2 < path7.length - 1; i2++) {
4962
- optimizedSegs.push(graph.table[path7[i2]].node);
4961
+ for (let i2 = 1; i2 < path8.length - 1; i2++) {
4962
+ optimizedSegs.push(graph.table[path8[i2]].node);
4963
4963
  }
4964
4964
  return exports2.fromArray(mergeSegments(optimizedSegs));
4965
4965
  };
@@ -6001,10 +6001,10 @@ var require_src2 = __commonJS({
6001
6001
  var fs_1 = require("fs");
6002
6002
  var debug_1 = __importDefault(require_src());
6003
6003
  var log = debug_1.default("@kwsites/file-exists");
6004
- function check(path7, isFile, isDirectory) {
6005
- log(`checking %s`, path7);
6004
+ function check(path8, isFile, isDirectory) {
6005
+ log(`checking %s`, path8);
6006
6006
  try {
6007
- const stat = fs_1.statSync(path7);
6007
+ const stat = fs_1.statSync(path8);
6008
6008
  if (stat.isFile() && isFile) {
6009
6009
  log(`[OK] path represents a file`);
6010
6010
  return true;
@@ -6024,8 +6024,8 @@ var require_src2 = __commonJS({
6024
6024
  throw e;
6025
6025
  }
6026
6026
  }
6027
- function exists2(path7, type = exports2.READABLE) {
6028
- return check(path7, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
6027
+ function exists2(path8, type = exports2.READABLE) {
6028
+ return check(path8, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
6029
6029
  }
6030
6030
  exports2.exists = exists2;
6031
6031
  exports2.FILE = 1;
@@ -6090,6 +6090,7 @@ var require_dist2 = __commonJS({
6090
6090
 
6091
6091
  // src/index.ts
6092
6092
  var fs6 = __toESM(require("fs"), 1);
6093
+ var path7 = __toESM(require("path"), 1);
6093
6094
 
6094
6095
  // ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
6095
6096
  var import_index = __toESM(require_commander(), 1);
@@ -6605,8 +6606,8 @@ function getErrorMap() {
6605
6606
 
6606
6607
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
6607
6608
  var makeIssue = (params) => {
6608
- const { data, path: path7, errorMaps, issueData } = params;
6609
- const fullPath = [...path7, ...issueData.path || []];
6609
+ const { data, path: path8, errorMaps, issueData } = params;
6610
+ const fullPath = [...path8, ...issueData.path || []];
6610
6611
  const fullIssue = {
6611
6612
  ...issueData,
6612
6613
  path: fullPath
@@ -6722,11 +6723,11 @@ var errorUtil;
6722
6723
 
6723
6724
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
6724
6725
  var ParseInputLazyPath = class {
6725
- constructor(parent, value, path7, key) {
6726
+ constructor(parent, value, path8, key) {
6726
6727
  this._cachedPath = [];
6727
6728
  this.parent = parent;
6728
6729
  this.data = value;
6729
- this._path = path7;
6730
+ this._path = path8;
6730
6731
  this._key = key;
6731
6732
  }
6732
6733
  get path() {
@@ -10838,8 +10839,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
10838
10839
  function forEachLineWithContent(input, callback) {
10839
10840
  return toLinesWithContent(input, true).map((line) => callback(line));
10840
10841
  }
10841
- function folderExists(path7) {
10842
- return (0, import_file_exists.exists)(path7, import_file_exists.FOLDER);
10842
+ function folderExists(path8) {
10843
+ return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
10843
10844
  }
10844
10845
  function append(target, item) {
10845
10846
  if (Array.isArray(target)) {
@@ -11241,8 +11242,8 @@ function checkIsRepoRootTask() {
11241
11242
  commands,
11242
11243
  format: "utf-8",
11243
11244
  onError,
11244
- parser(path7) {
11245
- return /^\.(git)?$/.test(path7.trim());
11245
+ parser(path8) {
11246
+ return /^\.(git)?$/.test(path8.trim());
11246
11247
  }
11247
11248
  };
11248
11249
  }
@@ -11676,11 +11677,11 @@ function parseGrep(grep) {
11676
11677
  const paths = /* @__PURE__ */ new Set();
11677
11678
  const results = {};
11678
11679
  forEachLineWithContent(grep, (input) => {
11679
- const [path7, line, preview] = input.split(NULL);
11680
- paths.add(path7);
11681
- (results[path7] = results[path7] || []).push({
11680
+ const [path8, line, preview] = input.split(NULL);
11681
+ paths.add(path8);
11682
+ (results[path8] = results[path8] || []).push({
11682
11683
  line: asNumber(line),
11683
- path: path7,
11684
+ path: path8,
11684
11685
  preview
11685
11686
  });
11686
11687
  });
@@ -12444,14 +12445,14 @@ var init_hash_object = __esm({
12444
12445
  init_task();
12445
12446
  }
12446
12447
  });
12447
- function parseInit(bare, path7, text) {
12448
+ function parseInit(bare, path8, text) {
12448
12449
  const response = String(text).trim();
12449
12450
  let result;
12450
12451
  if (result = initResponseRegex.exec(response)) {
12451
- return new InitSummary(bare, path7, false, result[1]);
12452
+ return new InitSummary(bare, path8, false, result[1]);
12452
12453
  }
12453
12454
  if (result = reInitResponseRegex.exec(response)) {
12454
- return new InitSummary(bare, path7, true, result[1]);
12455
+ return new InitSummary(bare, path8, true, result[1]);
12455
12456
  }
12456
12457
  let gitDir = "";
12457
12458
  const tokens = response.split(" ");
@@ -12462,7 +12463,7 @@ function parseInit(bare, path7, text) {
12462
12463
  break;
12463
12464
  }
12464
12465
  }
12465
- return new InitSummary(bare, path7, /^re/i.test(response), gitDir);
12466
+ return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
12466
12467
  }
12467
12468
  var InitSummary;
12468
12469
  var initResponseRegex;
@@ -12471,9 +12472,9 @@ var init_InitSummary = __esm({
12471
12472
  "src/lib/responses/InitSummary.ts"() {
12472
12473
  "use strict";
12473
12474
  InitSummary = class {
12474
- constructor(bare, path7, existing, gitDir) {
12475
+ constructor(bare, path8, existing, gitDir) {
12475
12476
  this.bare = bare;
12476
- this.path = path7;
12477
+ this.path = path8;
12477
12478
  this.existing = existing;
12478
12479
  this.gitDir = gitDir;
12479
12480
  }
@@ -12485,7 +12486,7 @@ var init_InitSummary = __esm({
12485
12486
  function hasBareCommand(command) {
12486
12487
  return command.includes(bareCommand);
12487
12488
  }
12488
- function initTask(bare = false, path7, customArgs) {
12489
+ function initTask(bare = false, path8, customArgs) {
12489
12490
  const commands = ["init", ...customArgs];
12490
12491
  if (bare && !hasBareCommand(commands)) {
12491
12492
  commands.splice(1, 0, bareCommand);
@@ -12494,7 +12495,7 @@ function initTask(bare = false, path7, customArgs) {
12494
12495
  commands,
12495
12496
  format: "utf-8",
12496
12497
  parser(text) {
12497
- return parseInit(commands.includes("--bare"), path7, text);
12498
+ return parseInit(commands.includes("--bare"), path8, text);
12498
12499
  }
12499
12500
  };
12500
12501
  }
@@ -13309,12 +13310,12 @@ var init_FileStatusSummary = __esm({
13309
13310
  "use strict";
13310
13311
  fromPathRegex = /^(.+)\0(.+)$/;
13311
13312
  FileStatusSummary = class {
13312
- constructor(path7, index, working_dir) {
13313
- this.path = path7;
13313
+ constructor(path8, index, working_dir) {
13314
+ this.path = path8;
13314
13315
  this.index = index;
13315
13316
  this.working_dir = working_dir;
13316
13317
  if (index === "R" || working_dir === "R") {
13317
- const detail = fromPathRegex.exec(path7) || [null, path7, path7];
13318
+ const detail = fromPathRegex.exec(path8) || [null, path8, path8];
13318
13319
  this.from = detail[2] || "";
13319
13320
  this.path = detail[1] || "";
13320
13321
  }
@@ -13345,14 +13346,14 @@ function splitLine(result, lineStr) {
13345
13346
  default:
13346
13347
  return;
13347
13348
  }
13348
- function data(index, workingDir, path7) {
13349
+ function data(index, workingDir, path8) {
13349
13350
  const raw = `${index}${workingDir}`;
13350
13351
  const handler = parsers6.get(raw);
13351
13352
  if (handler) {
13352
- handler(result, path7);
13353
+ handler(result, path8);
13353
13354
  }
13354
13355
  if (raw !== "##" && raw !== "!!") {
13355
- result.files.push(new FileStatusSummary(path7, index, workingDir));
13356
+ result.files.push(new FileStatusSummary(path8, index, workingDir));
13356
13357
  }
13357
13358
  }
13358
13359
  }
@@ -13703,9 +13704,9 @@ var init_simple_git_api = __esm({
13703
13704
  next
13704
13705
  );
13705
13706
  }
13706
- hashObject(path7, write) {
13707
+ hashObject(path8, write) {
13707
13708
  return this._runTask(
13708
- hashObjectTask(path7, write === true),
13709
+ hashObjectTask(path8, write === true),
13709
13710
  trailingFunctionArgument(arguments)
13710
13711
  );
13711
13712
  }
@@ -14059,8 +14060,8 @@ var init_branch = __esm({
14059
14060
  }
14060
14061
  });
14061
14062
  function toPath(input) {
14062
- const path7 = input.trim().replace(/^["']|["']$/g, "");
14063
- return path7 && (0, import_node_path.normalize)(path7);
14063
+ const path8 = input.trim().replace(/^["']|["']$/g, "");
14064
+ return path8 && (0, import_node_path.normalize)(path8);
14064
14065
  }
14065
14066
  var parseCheckIgnore;
14066
14067
  var init_CheckIgnore = __esm({
@@ -14345,8 +14346,8 @@ __export2(sub_module_exports, {
14345
14346
  subModuleTask: () => subModuleTask,
14346
14347
  updateSubModuleTask: () => updateSubModuleTask
14347
14348
  });
14348
- function addSubModuleTask(repo, path7) {
14349
- return subModuleTask(["add", repo, path7]);
14349
+ function addSubModuleTask(repo, path8) {
14350
+ return subModuleTask(["add", repo, path8]);
14350
14351
  }
14351
14352
  function initSubModuleTask(customArgs) {
14352
14353
  return subModuleTask(["init", ...customArgs]);
@@ -14660,8 +14661,8 @@ var require_git = __commonJS2({
14660
14661
  }
14661
14662
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
14662
14663
  };
14663
- Git2.prototype.submoduleAdd = function(repo, path7, then) {
14664
- return this._runTask(addSubModuleTask2(repo, path7), trailingFunctionArgument2(arguments));
14664
+ Git2.prototype.submoduleAdd = function(repo, path8, then) {
14665
+ return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
14665
14666
  };
14666
14667
  Git2.prototype.submoduleUpdate = function(args, then) {
14667
14668
  return this._runTask(
@@ -15331,9 +15332,15 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
15331
15332
  let renames = 0;
15332
15333
  const extCounts = {};
15333
15334
  let totalTrackedFiles = 0;
15335
+ const changedFiles = [];
15334
15336
  for (const f of diffSummary.files) {
15335
15337
  if (f.file.includes(" => ")) {
15336
15338
  renames++;
15339
+ const dest = f.file.split(" => ").pop()?.trim();
15340
+ if (dest)
15341
+ changedFiles.push(dest.replace(/\\/g, "/"));
15342
+ } else {
15343
+ changedFiles.push(f.file.replace(/\\/g, "/"));
15337
15344
  }
15338
15345
  const ext = path.extname(f.file).replace(/^\./, "").toLowerCase();
15339
15346
  const lang = mapExtensionToLanguage(ext);
@@ -15357,6 +15364,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
15357
15364
  netLines,
15358
15365
  renames,
15359
15366
  languages,
15367
+ changedFiles,
15360
15368
  branch,
15361
15369
  headSha,
15362
15370
  baseSha,
@@ -15366,6 +15374,21 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
15366
15374
  commitsCount
15367
15375
  };
15368
15376
  }
15377
+ function computeAiWrittenRatio(changedFiles, aiEditedFiles) {
15378
+ if (changedFiles.length === 0 || aiEditedFiles.length === 0) {
15379
+ return void 0;
15380
+ }
15381
+ const norm = (f) => f.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
15382
+ const aiNorm = aiEditedFiles.map(norm);
15383
+ let hit = 0;
15384
+ for (const cf of changedFiles) {
15385
+ const c3 = norm(cf);
15386
+ const matched = aiNorm.some((a) => a === c3 || c3.endsWith("/" + a) || a.endsWith("/" + c3) || c3.endsWith(a) || a.endsWith(c3));
15387
+ if (matched)
15388
+ hit++;
15389
+ }
15390
+ return Math.round(hit / changedFiles.length * 100) / 100;
15391
+ }
15369
15392
  function mapExtensionToLanguage(ext) {
15370
15393
  const map = {
15371
15394
  ts: "TypeScript",
@@ -16295,9 +16318,12 @@ function formatMarkdownReceipt(receipt, publicUrl) {
16295
16318
  const costPrefix = receipt.ai.mode === "verified" ? "$" : "~$";
16296
16319
  const confidencePercent = `${Math.round(receipt.ai.confidence * 100)}%`;
16297
16320
  const confidenceTier = receipt.ai.confidence >= 0.8 ? "High" : receipt.ai.confidence >= 0.4 ? "Medium" : "Low";
16321
+ const tokenProvenance = receipt.ai.mode === "verified" || receipt.ai.mode === "observed" ? "Observed" : "Estimated";
16322
+ const costProvenance = receipt.ai.mode === "verified" ? "Verified" : receipt.ai.mode === "observed" ? "Observed" : "Estimated";
16323
+ const tokenDisplay = receipt.ai.mode === "verified" || receipt.ai.mode === "observed" ? totalTokens : `~${totalTokens}`;
16298
16324
  const lines = [
16299
16325
  `<!-- QODEWK_RECEIPT_START:${receipt.receipt.id} -->`,
16300
- `### \u{1F9FE} Qodewk Telemetry Receipt`,
16326
+ `### Qodewk Telemetry Receipt`,
16301
16327
  ``,
16302
16328
  `| Metric | Measurement | Provenance |`,
16303
16329
  `| :--- | :--- | :--- |`,
@@ -16306,19 +16332,42 @@ function formatMarkdownReceipt(receipt, publicUrl) {
16306
16332
  `| **Lines Deleted** | \`-${receipt.mutation.deletions}\` | Observed |`,
16307
16333
  `| **Net Delta** | \`${receipt.mutation.netLines >= 0 ? "+" : ""}${receipt.mutation.netLines}\` | Observed |`,
16308
16334
  `| **AI Model** | \`${receipt.ai.provider} \xB7 ${receipt.ai.model || "Unknown"}\` | ${receipt.ai.mode} |`,
16309
- `| **Tokens Consumed** | \`~${totalTokens}\` | Estimated |`,
16310
- `| **AI Spend** | \`${costPrefix}${receipt.ai.cost.toFixed(2)}\` | Estimated |`,
16335
+ `| **Tokens Consumed** | \`${tokenDisplay}\` | ${tokenProvenance} |`,
16336
+ `| **AI Spend** | \`${costPrefix}${receipt.ai.cost.toFixed(2)}\` | ${costProvenance} |`,
16311
16337
  `| **Confidence** | \`${confidencePercent}\` (${confidenceTier}) | Dual-Engine |`,
16312
16338
  ``,
16313
- `> \u{1F512} **Privacy Guarantee:** *Source code was never uploaded to Qodewk. Telemetry computed strictly from cryptographic Git metadata.*`,
16339
+ `> **Privacy Guarantee:** *Source code was never uploaded to Qodewk. Telemetry computed strictly from cryptographic Git metadata.*`,
16314
16340
  ``,
16315
- `\u{1F517} [**View Full Digital Receipt \u2192**](${url})`,
16341
+ `[**View Full Digital Receipt \u2192**](${url})`,
16316
16342
  `<!-- QODEWK_RECEIPT_END -->`
16317
16343
  ];
16318
16344
  return lines.join("\n");
16319
16345
  }
16320
16346
 
16321
16347
  // ../core/dist/index.js
16348
+ function sanitizeReceiptForShare(receipt) {
16349
+ const sessions = receipt.ai.sessions?.map((s) => ({
16350
+ provider: s.provider,
16351
+ model: s.model,
16352
+ task: s.task,
16353
+ tokens: s.tokens,
16354
+ cost: s.cost,
16355
+ confidence: s.confidence,
16356
+ mode: s.mode
16357
+ }));
16358
+ return {
16359
+ ...receipt,
16360
+ privacy: {
16361
+ ...receipt.privacy,
16362
+ sourceExcluded: true,
16363
+ isPublic: true
16364
+ },
16365
+ ai: {
16366
+ ...receipt.ai,
16367
+ sessions
16368
+ }
16369
+ };
16370
+ }
16322
16371
  async function generateReceipt(options = {}) {
16323
16372
  const metrics = await extractGitMetrics({
16324
16373
  repoPath: options.repoPath || process.cwd(),
@@ -16334,7 +16383,7 @@ async function generateReceipt(options = {}) {
16334
16383
  platform: options.platform
16335
16384
  });
16336
16385
  const primaryFp = harvestResult.primaryFootprint;
16337
- let detected = detectProviderFromCommit(metrics.commitMessage);
16386
+ const detected = detectProviderFromCommit(metrics.commitMessage);
16338
16387
  const provider = options.provider || primaryFp?.platform || detected?.provider || "unknown";
16339
16388
  const model = options.model || primaryFp?.model || detected?.model;
16340
16389
  const task = options.task || primaryFp?.taskTitle || (harvestResult.tasks.length > 0 ? harvestResult.tasks[0] : void 0);
@@ -16348,10 +16397,7 @@ async function generateReceipt(options = {}) {
16348
16397
  confidence: fp.confidence,
16349
16398
  mode: fp.mode
16350
16399
  }));
16351
- let aiWrittenRatio = void 0;
16352
- if (primaryFp) {
16353
- aiWrittenRatio = 0.88;
16354
- }
16400
+ const aiWrittenRatio = computeAiWrittenRatio(metrics.changedFiles, harvestResult.filesEdited);
16355
16401
  const costResult = estimateCost({
16356
16402
  files: metrics.files,
16357
16403
  insertions: metrics.insertions,
@@ -16361,18 +16407,22 @@ async function generateReceipt(options = {}) {
16361
16407
  sessions: sessions.length > 0 ? sessions : void 0,
16362
16408
  confidence: primaryFp?.confidence || detected?.confidence
16363
16409
  });
16364
- const receiptId = `rec_${crypto2.randomBytes(12).toString("hex")}`;
16365
16410
  const now = (/* @__PURE__ */ new Date()).toISOString();
16366
16411
  const payloadToHash = {
16367
16412
  repoHash: metrics.repoHash,
16368
16413
  headSha: metrics.headSha,
16414
+ baseSha: metrics.baseSha,
16369
16415
  files: metrics.files,
16370
16416
  insertions: metrics.insertions,
16371
16417
  deletions: metrics.deletions,
16372
16418
  cost: costResult.cost,
16373
- tokens: costResult.tokens
16419
+ tokens: costResult.tokens,
16420
+ mode: costResult.mode,
16421
+ provider: costResult.provider,
16422
+ model: costResult.model
16374
16423
  };
16375
16424
  const contentHash = crypto2.createHash("sha256").update(JSON.stringify(payloadToHash)).digest("hex");
16425
+ const receiptId = `rec_${contentHash.slice(0, 24)}`;
16376
16426
  const receipt = {
16377
16427
  version: "1.0",
16378
16428
  receipt: {
@@ -16409,7 +16459,7 @@ async function generateReceipt(options = {}) {
16409
16459
  },
16410
16460
  privacy: {
16411
16461
  sourceExcluded: true,
16412
- isPublic: options.isPublic ?? true,
16462
+ isPublic: options.isPublic ?? false,
16413
16463
  anonymizeBranch: options.anonymizeBranch ?? false
16414
16464
  }
16415
16465
  };
@@ -16417,80 +16467,279 @@ async function generateReceipt(options = {}) {
16417
16467
  }
16418
16468
 
16419
16469
  // src/index.ts
16470
+ var SHARE_PAYLOAD_MAX_BYTES = 5e4;
16471
+ function resolveCliVersion() {
16472
+ try {
16473
+ const argv1 = process.argv[1] ? path7.dirname(path7.resolve(process.argv[1])) : process.cwd();
16474
+ const candidates = [
16475
+ path7.join(argv1, "..", "package.json"),
16476
+ path7.join(argv1, "package.json")
16477
+ ];
16478
+ for (const candidate of candidates) {
16479
+ if (!fs6.existsSync(candidate)) continue;
16480
+ const pkg = JSON.parse(fs6.readFileSync(candidate, "utf-8"));
16481
+ if (pkg.name === "qodewk" && pkg.version) return pkg.version;
16482
+ }
16483
+ } catch {
16484
+ }
16485
+ return "0.2.1";
16486
+ }
16487
+ function resolveSince(options) {
16488
+ return options.today ? "today" : options.since;
16489
+ }
16490
+ function persistReceipt(receipt, claimToken) {
16491
+ try {
16492
+ const db = new LocalStateDB();
16493
+ db.saveReceipt(receipt, claimToken);
16494
+ db.close();
16495
+ } catch {
16496
+ }
16497
+ }
16498
+ function resolveGitHooksDir(cwd = process.cwd()) {
16499
+ const gitPath = path7.join(cwd, ".git");
16500
+ if (!fs6.existsSync(gitPath)) return null;
16501
+ let gitCommonDir;
16502
+ const stat = fs6.statSync(gitPath);
16503
+ if (stat.isDirectory()) {
16504
+ gitCommonDir = gitPath;
16505
+ } else {
16506
+ const content = fs6.readFileSync(gitPath, "utf-8");
16507
+ const match = content.match(/gitdir:\s*(.+)/i);
16508
+ if (!match?.[1]) return null;
16509
+ let gitDir = match[1].trim();
16510
+ if (!path7.isAbsolute(gitDir)) {
16511
+ gitDir = path7.resolve(cwd, gitDir);
16512
+ }
16513
+ const commonFile = path7.join(gitDir, "commondir");
16514
+ if (fs6.existsSync(commonFile)) {
16515
+ let common = fs6.readFileSync(commonFile, "utf-8").trim();
16516
+ if (!path7.isAbsolute(common)) {
16517
+ common = path7.resolve(gitDir, common);
16518
+ }
16519
+ gitCommonDir = common;
16520
+ } else if (path7.basename(path7.dirname(gitDir)) === "worktrees") {
16521
+ gitCommonDir = path7.dirname(path7.dirname(gitDir));
16522
+ } else {
16523
+ gitCommonDir = gitDir;
16524
+ }
16525
+ }
16526
+ return path7.join(gitCommonDir, "hooks");
16527
+ }
16528
+ var HOOK_MARKER_BEGIN = "# --- BEGIN QODEWK HOOK ---";
16529
+ var HOOK_MARKER_END = "# --- END QODEWK HOOK ---";
16530
+ var HOOK_SNIPPET = `
16531
+ ${HOOK_MARKER_BEGIN}
16532
+ # Non-blocking Qodewk background recorder (< 5ms)
16533
+ if command -v qodewk >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1 || [ -f "./node_modules/.bin/qodewk" ]; then
16534
+ ( ( qodewk record || pnpm qodewk record || npx qodewk record ) >/dev/null 2>&1 & )
16535
+ fi
16536
+ ${HOOK_MARKER_END}
16537
+ `;
16538
+ function installHookFile(hookFile) {
16539
+ let content = "";
16540
+ if (fs6.existsSync(hookFile)) {
16541
+ content = fs6.readFileSync(hookFile, "utf-8");
16542
+ } else {
16543
+ content = "#!/bin/sh\n";
16544
+ }
16545
+ if (content.includes(HOOK_MARKER_BEGIN)) {
16546
+ return "exists";
16547
+ }
16548
+ fs6.writeFileSync(hookFile, content + HOOK_SNIPPET, { mode: 493 });
16549
+ return "installed";
16550
+ }
16551
+ function uninstallHookFile(hookFile) {
16552
+ if (!fs6.existsSync(hookFile)) return "absent";
16553
+ const content = fs6.readFileSync(hookFile, "utf-8");
16554
+ if (!content.includes(HOOK_MARKER_BEGIN)) return "missing";
16555
+ const regex = new RegExp(`\\n?${HOOK_MARKER_BEGIN}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, "g");
16556
+ const updated = content.replace(regex, "");
16557
+ fs6.writeFileSync(hookFile, updated, { mode: 493 });
16558
+ return "removed";
16559
+ }
16420
16560
  var program2 = new Command();
16421
- program2.name("qodewk").description("Universal telemetry and digital receipt generator for the AI coding agent era").version("0.1.10").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("--barcode", "Render 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16561
+ 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 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16422
16562
  try {
16423
- const since = options.today ? "today" : options.since;
16424
16563
  const receipt = await generateReceipt({
16425
16564
  provider: options.provider,
16426
16565
  model: options.model,
16427
- since,
16566
+ since: resolveSince(options),
16428
16567
  platform: options.platform,
16429
- anonymizeBranch: options.anon
16568
+ anonymizeBranch: options.anon,
16569
+ isPublic: false
16430
16570
  });
16431
- try {
16432
- const db = new LocalStateDB();
16433
- db.saveReceipt(receipt);
16434
- db.close();
16435
- } catch {
16436
- }
16571
+ persistReceipt(receipt);
16437
16572
  await outputReceipt(receipt, options);
16438
16573
  } catch (err) {
16439
- console.error(import_picocolors.default.red(`Error generating receipt: ${err.message}`));
16574
+ const message = err instanceof Error ? err.message : String(err);
16575
+ console.error(import_picocolors.default.red(`Error generating receipt: ${message}`));
16440
16576
  process.exit(1);
16441
16577
  }
16442
16578
  });
16443
- 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("--barcode", "Render 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16579
+ 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 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16444
16580
  try {
16445
16581
  const receipt = await generateReceipt({
16446
16582
  baseSha: options.base,
16447
16583
  headSha: options.head,
16448
16584
  provider: options.provider,
16449
- model: options.model
16585
+ model: options.model,
16586
+ since: resolveSince(options),
16587
+ platform: options.platform,
16588
+ anonymizeBranch: options.anon,
16589
+ isPublic: false
16450
16590
  });
16591
+ persistReceipt(receipt);
16451
16592
  await outputReceipt(receipt, options);
16452
16593
  } catch (err) {
16453
- console.error(import_picocolors.default.red(`Error auditing git range: ${err.message}`));
16594
+ const message = err instanceof Error ? err.message : String(err);
16595
+ console.error(import_picocolors.default.red(`Error auditing git range: ${message}`));
16454
16596
  process.exit(1);
16455
16597
  }
16456
16598
  });
16457
- program2.command("share").description("Publish privacy-safe receipt to Qodewk cloud and get a shareable URL").option("-p, --provider <provider>", "Specify AI provider").option("-m, --model <model>", "Specify AI model").option("--barcode", "Render 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16599
+ program2.command("share").description("Publish privacy-safe receipt to Qodewk cloud and get a shareable URL").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("-f, --format <format>", "Local fallback output format", "terminal").option("-j, --json", "Also print JSON after share").option("--barcode", "Render 1D barcode simulation instead of scannable 2D QR code").action(async (options) => {
16458
16600
  try {
16459
16601
  const receipt = await generateReceipt({
16460
16602
  provider: options.provider,
16461
- model: options.model
16603
+ model: options.model,
16604
+ since: resolveSince(options),
16605
+ platform: options.platform,
16606
+ anonymizeBranch: options.anon,
16607
+ isPublic: false
16462
16608
  });
16463
- if (process.env.QODEWK_TELEMETRY === "off") {
16464
- console.log(import_picocolors.default.yellow("\n\u26A0\uFE0F Cloud publishing is disabled because QODEWK_TELEMETRY=off."));
16609
+ const localFlag = program2.opts().local === true;
16610
+ if (process.env.QODEWK_TELEMETRY === "off" || localFlag) {
16611
+ console.log(
16612
+ import_picocolors.default.yellow(
16613
+ `
16614
+ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"}).`
16615
+ )
16616
+ );
16465
16617
  console.log(import_picocolors.default.dim("Telemetry remains strictly stored in local SQLite (~/.qodewk/state.db).\n"));
16618
+ persistReceipt(receipt);
16466
16619
  await renderTerminalReceipt(receipt, void 0, options.barcode);
16467
16620
  return;
16468
16621
  }
16469
- const endpoint = process.env.QODEWK_API_URL || "https://qodewk.flinkeo.online/api/receipts";
16470
- console.log(import_picocolors.default.dim(`Publishing receipt ${receipt.receipt.id} to ${endpoint}...`));
16622
+ const sanitized = sanitizeReceiptForShare(receipt);
16623
+ let body = JSON.stringify(sanitized);
16624
+ if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES && sanitized.ai.sessions) {
16625
+ const trimmed2 = {
16626
+ ...sanitized,
16627
+ ai: { ...sanitized.ai, sessions: void 0 }
16628
+ };
16629
+ body = JSON.stringify(trimmed2);
16630
+ }
16631
+ if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES) {
16632
+ console.error(import_picocolors.default.red("Sanitized receipt still exceeds the 50 KB cloud payload limit."));
16633
+ process.exit(1);
16634
+ }
16635
+ const endpoint = process.env.QODEWK_API_URL || `${process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online"}/api/receipts`;
16636
+ console.log(import_picocolors.default.dim(`Publishing receipt ${sanitized.receipt.id} to ${endpoint}...`));
16471
16637
  try {
16472
16638
  const response = await fetch(endpoint, {
16473
16639
  method: "POST",
16474
16640
  headers: {
16475
- "Content-Type": "application/json"
16641
+ "Content-Type": "application/json",
16642
+ "Content-Length": String(Buffer.byteLength(body, "utf-8"))
16476
16643
  },
16477
- body: JSON.stringify(receipt)
16644
+ body
16478
16645
  });
16479
16646
  if (!response.ok) {
16480
- throw new Error(`API responded with status ${response.status}`);
16647
+ const errText = await response.text().catch(() => "");
16648
+ throw new Error(`API responded with status ${response.status}${errText ? `: ${errText.slice(0, 200)}` : ""}`);
16481
16649
  }
16482
16650
  const data = await response.json();
16483
- const db = new LocalStateDB();
16484
- db.saveReceipt(receipt, data.claimToken);
16485
- db.close();
16486
- await renderTerminalReceipt(receipt, data.url, options.barcode);
16651
+ persistReceipt(sanitized, data.claimToken);
16652
+ console.log("");
16653
+ console.log(import_picocolors.default.green(" Published successfully."));
16654
+ console.log(` ${import_picocolors.default.dim("Public URL:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(data.url))}`);
16655
+ console.log(` ${import_picocolors.default.bold(import_picocolors.default.yellow(" Claim token (store securely \u2014 shown once):"))}`);
16656
+ console.log(` ${import_picocolors.default.yellow(data.claimToken)}`);
16657
+ console.log(import_picocolors.default.dim(" Saved to ~/.qodewk/state.db \u2014 required to prove authorship later."));
16658
+ console.log("");
16659
+ await renderTerminalReceipt(sanitized, data.url, options.barcode);
16660
+ if (options.json) {
16661
+ console.log(JSON.stringify({ url: data.url, claimToken: data.claimToken, receipt: sanitized }, null, 2));
16662
+ }
16487
16663
  } catch (networkErr) {
16664
+ const message = networkErr instanceof Error ? networkErr.message : String(networkErr);
16488
16665
  console.log(import_picocolors.default.yellow(`
16489
- Could not reach cloud API (${networkErr.message}). Rendered locally:`));
16666
+ Could not reach cloud API (${message}). Rendered locally:`));
16667
+ persistReceipt(receipt);
16490
16668
  await renderTerminalReceipt(receipt, void 0, options.barcode);
16491
16669
  }
16492
16670
  } catch (err) {
16493
- console.error(import_picocolors.default.red(`Error sharing receipt: ${err.message}`));
16671
+ const message = err instanceof Error ? err.message : String(err);
16672
+ console.error(import_picocolors.default.red(`Error sharing receipt: ${message}`));
16673
+ process.exit(1);
16674
+ }
16675
+ });
16676
+ program2.command("record").alias("record-event").description("Silently record local telemetry receipt into SQLite (used by git hooks)").action(async () => {
16677
+ try {
16678
+ const receipt = await generateReceipt({ isPublic: false });
16679
+ persistReceipt(receipt);
16680
+ process.exit(0);
16681
+ } catch {
16682
+ process.exit(0);
16683
+ }
16684
+ });
16685
+ var hookCommand = program2.command("hook").aliases(["hooks"]).description("Manage non-blocking Git hooks for automatic telemetry recording");
16686
+ hookCommand.command("install").description("Install non-blocking post-commit and post-rewrite Git hooks").action(async () => {
16687
+ try {
16688
+ const hooksDir = resolveGitHooksDir();
16689
+ if (!hooksDir) {
16690
+ console.error(import_picocolors.default.red("Error: Current directory is not a Git repository (.git not found)."));
16691
+ process.exit(1);
16692
+ }
16693
+ if (!fs6.existsSync(hooksDir)) {
16694
+ fs6.mkdirSync(hooksDir, { recursive: true });
16695
+ }
16696
+ const targets = ["post-commit", "post-rewrite"];
16697
+ let installed = 0;
16698
+ let existing = 0;
16699
+ for (const name of targets) {
16700
+ const result = installHookFile(path7.join(hooksDir, name));
16701
+ if (result === "installed") installed++;
16702
+ else existing++;
16703
+ }
16704
+ if (installed === 0 && existing > 0) {
16705
+ console.log(import_picocolors.default.yellow("Qodewk Git hooks are already installed (post-commit, post-rewrite)."));
16706
+ return;
16707
+ }
16708
+ console.log(
16709
+ import_picocolors.default.green(
16710
+ `\u2713 Non-blocking Qodewk hooks installed in ${hooksDir} (post-commit${installed > 1 || existing > 0 ? ", post-rewrite" : installed === 1 && targets.length === 2 ? " + post-rewrite" : ""})`
16711
+ )
16712
+ );
16713
+ if (existing > 0) {
16714
+ console.log(import_picocolors.default.dim(` (${existing} hook file(s) already contained the Qodewk marker)`));
16715
+ }
16716
+ } catch (err) {
16717
+ const message = err instanceof Error ? err.message : String(err);
16718
+ console.error(import_picocolors.default.red(`Failed to install Git hook: ${message}`));
16719
+ process.exit(1);
16720
+ }
16721
+ });
16722
+ hookCommand.command("uninstall").description("Remove Qodewk post-commit and post-rewrite Git hooks").action(async () => {
16723
+ try {
16724
+ const hooksDir = resolveGitHooksDir();
16725
+ if (!hooksDir) {
16726
+ console.log(import_picocolors.default.yellow("No Git repository found."));
16727
+ return;
16728
+ }
16729
+ const targets = ["post-commit", "post-rewrite"];
16730
+ let removed = 0;
16731
+ for (const name of targets) {
16732
+ const result = uninstallHookFile(path7.join(hooksDir, name));
16733
+ if (result === "removed") removed++;
16734
+ }
16735
+ if (removed === 0) {
16736
+ console.log(import_picocolors.default.yellow("Qodewk hooks are not installed."));
16737
+ return;
16738
+ }
16739
+ console.log(import_picocolors.default.green(`\u2713 Qodewk hook markers removed from ${removed} file(s).`));
16740
+ } catch (err) {
16741
+ const message = err instanceof Error ? err.message : String(err);
16742
+ console.error(import_picocolors.default.red(`Failed to uninstall hook: ${message}`));
16494
16743
  process.exit(1);
16495
16744
  }
16496
16745
  });
@@ -16501,64 +16750,48 @@ var ansiHex = (hexColor) => {
16501
16750
  const b2 = num & 255;
16502
16751
  return (text) => `\x1B[38;2;${r2};${g};${b2}m${text}\x1B[39m`;
16503
16752
  };
16504
- async function outputReceipt(receipt, options, publicUrl) {
16505
- let content = "";
16506
- const format = options.json ? "json" : options.format || "terminal";
16507
- if (format === "json") {
16508
- content = JSON.stringify(receipt, null, 2);
16509
- } else if (format === "markdown") {
16510
- content = formatMarkdownReceipt(receipt, publicUrl);
16511
- }
16512
- if (options.out) {
16513
- fs6.writeFileSync(options.out, content || formatMarkdownReceipt(receipt, publicUrl), "utf-8");
16514
- console.log(import_picocolors.default.green(`\u2713 Receipt written to ${options.out}`));
16515
- }
16516
- if (format === "terminal" && !options.out) {
16517
- await renderTerminalReceipt(receipt, publicUrl, options.barcode);
16518
- } else if (format !== "terminal" && !options.out) {
16519
- console.log(content);
16520
- }
16521
- }
16522
16753
  var stripAnsi = (str) => {
16523
16754
  return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\([B0]/g, "");
16524
16755
  };
16525
16756
  var visibleWidth = (str) => {
16526
16757
  return stripAnsi(str).length;
16527
16758
  };
16528
- async function renderTerminalReceipt(receipt, publicUrl, useBarcode) {
16759
+ function buildTerminalReceipt(receipt, publicUrl, useBarcode) {
16760
+ const lines = [];
16529
16761
  const coral = ansiHex("#cc785c");
16530
16762
  const green = ansiHex("#5db872");
16531
16763
  const red = ansiHex("#c64545");
16532
16764
  const teal = ansiHex("#5db8a6");
16533
16765
  const INNER_WIDTH = 52;
16766
+ const push = (line) => lines.push(line);
16534
16767
  const printRow = (content, width = INNER_WIDTH) => {
16535
16768
  const visLen = visibleWidth(content);
16536
16769
  const pad = Math.max(0, width - visLen);
16537
- console.log(` | ${content}${" ".repeat(pad)} |`);
16770
+ push(` | ${content}${" ".repeat(pad)} |`);
16538
16771
  };
16539
16772
  const printRowSplit = (left, right, width = INNER_WIDTH) => {
16540
16773
  const leftVis = visibleWidth(left);
16541
16774
  const rightVis = visibleWidth(right);
16542
16775
  const pad = Math.max(1, width - leftVis - rightVis);
16543
- console.log(` | ${left}${" ".repeat(pad)}${right} |`);
16776
+ push(` | ${left}${" ".repeat(pad)}${right} |`);
16544
16777
  };
16545
16778
  const printCenteredRow = (content, width = INNER_WIDTH) => {
16546
16779
  const visLen = visibleWidth(content);
16547
16780
  const totalPad = Math.max(0, width - visLen);
16548
16781
  const leftPad = Math.floor(totalPad / 2);
16549
16782
  const rightPad = totalPad - leftPad;
16550
- console.log(` | ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} |`);
16783
+ push(` | ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} |`);
16551
16784
  };
16552
16785
  const printDivider = (char = "=", width = INNER_WIDTH) => {
16553
- console.log(` | ${char.repeat(width)} |`);
16786
+ push(` | ${char.repeat(width)} |`);
16554
16787
  };
16555
16788
  const totalTokens = (receipt.ai.tokens.input + receipt.ai.tokens.output).toLocaleString();
16556
16789
  const inputK = `${Math.round(receipt.ai.tokens.input / 1e3)}k`;
16557
16790
  const outputK = `${Math.round(receipt.ai.tokens.output / 1e3)}k`;
16558
16791
  const costPrefix = receipt.ai.mode === "verified" ? "$" : "~$";
16559
16792
  const confidencePercent = `${Math.round(receipt.ai.confidence * 100)}%`;
16560
- console.log("");
16561
- console.log(" " + import_picocolors.default.bold(coral("/\\".repeat(28))));
16793
+ push("");
16794
+ push(" " + import_picocolors.default.bold(coral("/\\".repeat(28))));
16562
16795
  printCenteredRow(import_picocolors.default.bold(import_picocolors.default.white("Q O D E W K")));
16563
16796
  printCenteredRow(coral("*** PROOF OF SHIPMENT ***"));
16564
16797
  printRow("");
@@ -16600,18 +16833,12 @@ async function renderTerminalReceipt(receipt, publicUrl, useBarcode) {
16600
16833
  printRowSplit("Total Tokens:", totalTokens);
16601
16834
  printDivider("=");
16602
16835
  const costLabel = receipt.ai.mode === "verified" ? "VERIFIED AI COST" : "ESTIMATED AI COST";
16603
- printRowSplit(
16604
- costLabel,
16605
- import_picocolors.default.bold(coral(costPrefix + receipt.ai.cost.toFixed(2)))
16606
- );
16607
- printRowSplit(
16608
- `CONFIDENCE: ${confidencePercent}`,
16609
- `[Mode: ${receipt.ai.mode}]`
16610
- );
16836
+ printRowSplit(costLabel, import_picocolors.default.bold(coral(costPrefix + receipt.ai.cost.toFixed(2))));
16837
+ printRowSplit(`CONFIDENCE: ${confidencePercent}`, `[Mode: ${receipt.ai.mode}]`);
16611
16838
  printDivider("=");
16612
16839
  printRow("");
16613
16840
  const baseUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.QODEWK_APP_URL || "https://qodewk.flinkeo.online";
16614
- const urlToDisplay = publicUrl || `${baseUrl}/r/${receipt.receipt.id}`;
16841
+ const isPublished = Boolean(publicUrl);
16615
16842
  const displayHost = baseUrl.replace(/^https?:\/\//, "");
16616
16843
  if (useBarcode) {
16617
16844
  const barChars = ["||| ", "| | ", "|||| ", "|| | ", "|| || "];
@@ -16622,10 +16849,10 @@ async function renderTerminalReceipt(receipt, publicUrl, useBarcode) {
16622
16849
  }
16623
16850
  barcodePattern += "|||";
16624
16851
  printCenteredRow(import_picocolors.default.bold(barcodePattern));
16625
- } else {
16626
- const qrRows = renderTerminalQr(urlToDisplay);
16852
+ } else if (isPublished && publicUrl) {
16853
+ const qrRows = renderTerminalQr(publicUrl);
16627
16854
  if (qrRows.length > 0) {
16628
- printCenteredRow(import_picocolors.default.dim("--- SCAN WITH PHONE ---"));
16855
+ printCenteredRow(import_picocolors.default.dim("--- SCAN PUBLIC RECEIPT ---"));
16629
16856
  printRow("");
16630
16857
  for (const qrRow of qrRows) {
16631
16858
  printCenteredRow(qrRow);
@@ -16634,20 +16861,60 @@ async function renderTerminalReceipt(receipt, publicUrl, useBarcode) {
16634
16861
  } else {
16635
16862
  printCenteredRow(import_picocolors.default.bold("||| | ||||| ||| |||| |||||| |||| ||| ||||||| |||"));
16636
16863
  }
16864
+ } else {
16865
+ printCenteredRow(import_picocolors.default.dim("--- LOCAL PROOF (NOT PUBLISHED) ---"));
16866
+ printCenteredRow(import_picocolors.default.dim(`id ${receipt.receipt.id.slice(0, 28)}`));
16867
+ }
16868
+ if (isPublished) {
16869
+ printCenteredRow(import_picocolors.default.cyan(displayHost));
16870
+ printCenteredRow(import_picocolors.default.underline(import_picocolors.default.cyan(`r/${receipt.receipt.id}`)));
16871
+ } else {
16872
+ printCenteredRow(import_picocolors.default.yellow("[ LOCAL RECORD \u2014 NOT PUBLISHED ]"));
16637
16873
  }
16638
- printCenteredRow(import_picocolors.default.cyan(displayHost));
16639
- printCenteredRow(import_picocolors.default.underline(import_picocolors.default.cyan(`r/${receipt.receipt.id}`)));
16640
16874
  printRow("");
16641
16875
  if (process.env.QODEWK_TELEMETRY === "off") {
16642
16876
  printRow(` ${teal("[\u2713]")} QODEWK_TELEMETRY=off (Cloud sync disabled)`);
16643
16877
  }
16644
16878
  printRow(` ${green("[\u2713]")} Source code was never uploaded to Qodewk`);
16645
- console.log(" " + import_picocolors.default.bold(coral("\\/".repeat(28))));
16646
- console.log("");
16647
- console.log(` ${import_picocolors.default.dim("\u{1F517} Public Receipt:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(urlToDisplay))}`);
16879
+ push(" " + import_picocolors.default.bold(coral("\\/".repeat(28))));
16880
+ push("");
16881
+ if (isPublished && publicUrl) {
16882
+ push(` ${import_picocolors.default.dim("Public Receipt:")} ${import_picocolors.default.underline(import_picocolors.default.cyan(publicUrl))}`);
16883
+ } else {
16884
+ push(` ${import_picocolors.default.dim("Saved to local DB:")} ${import_picocolors.default.dim("~/.qodewk/state.db")}`);
16885
+ push(` ${import_picocolors.default.dim("To publish & get shareable URL:")} ${import_picocolors.default.cyan("qodewk share")}`);
16886
+ }
16648
16887
  if (!useBarcode) {
16649
- console.log(` ${import_picocolors.default.dim("\u{1F4CA} View classic 1D barcode:")} ${import_picocolors.default.cyan("pnpm qodewk --barcode")}`);
16888
+ push(` ${import_picocolors.default.dim("View classic 1D barcode:")} ${import_picocolors.default.cyan("qodewk --barcode")}`);
16889
+ }
16890
+ push("");
16891
+ return lines.join("\n");
16892
+ }
16893
+ async function renderTerminalReceipt(receipt, publicUrl, useBarcode) {
16894
+ console.log(buildTerminalReceipt(receipt, publicUrl, useBarcode));
16895
+ }
16896
+ async function outputReceipt(receipt, options, publicUrl) {
16897
+ const format = options.json ? "json" : options.format || "terminal";
16898
+ let content;
16899
+ if (format === "json") {
16900
+ content = JSON.stringify(receipt, null, 2);
16901
+ } else if (format === "markdown") {
16902
+ content = formatMarkdownReceipt(receipt, publicUrl);
16903
+ } else {
16904
+ content = buildTerminalReceipt(receipt, publicUrl, options.barcode);
16905
+ }
16906
+ if (options.out) {
16907
+ const fileContent = format === "terminal" ? stripAnsi(content) : content;
16908
+ fs6.writeFileSync(options.out, fileContent.endsWith("\n") ? fileContent : fileContent + "\n", "utf-8");
16909
+ console.log(import_picocolors.default.green(`\u2713 Receipt written to ${options.out}`));
16910
+ }
16911
+ if (!options.out) {
16912
+ if (format === "terminal") {
16913
+ console.log(content);
16914
+ } else {
16915
+ console.log(content);
16916
+ }
16917
+ } else if (format !== "terminal") {
16650
16918
  }
16651
- console.log("");
16652
16919
  }
16653
16920
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qodewk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Universal telemetry and digital receipt generator for the AI coding agent era",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "scripts": {
15
15
  "build": "tsup",
16
16
  "typecheck": "tsc --noEmit",
17
+ "test": "node --test test/smoke.test.mjs",
17
18
  "prepublishOnly": "pnpm run build"
18
19
  },
19
20
  "publishConfig": {
@@ -43,8 +44,7 @@
43
44
  "dependencies": {
44
45
  "commander": "^13.1.0",
45
46
  "picocolors": "^1.1.1",
46
- "qrcode": "^1.5.4",
47
- "simple-git": "^3.27.0"
47
+ "qrcode": "^1.5.4"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@qodewk/core": "workspace:*",