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.
- package/README.md +4 -0
- package/dist/index.cjs +407 -140
- 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
|
|
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 =
|
|
2156
|
+
const localBin = path8.resolve(baseDir, baseName);
|
|
2157
2157
|
if (fs7.existsSync(localBin)) return localBin;
|
|
2158
|
-
if (sourceExt.includes(
|
|
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 =
|
|
2177
|
-
|
|
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 =
|
|
2184
|
+
const legacyName = path8.basename(
|
|
2185
2185
|
this._scriptPath,
|
|
2186
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
3059
|
-
if (
|
|
3060
|
-
this._executableDir =
|
|
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
|
|
4959
|
+
const path8 = dijkstra.find_path(graph.map, "start", "end");
|
|
4960
4960
|
const optimizedSegs = [];
|
|
4961
|
-
for (let i2 = 1; i2 <
|
|
4962
|
-
optimizedSegs.push(graph.table[
|
|
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(
|
|
6005
|
-
log(`checking %s`,
|
|
6004
|
+
function check(path8, isFile, isDirectory) {
|
|
6005
|
+
log(`checking %s`, path8);
|
|
6006
6006
|
try {
|
|
6007
|
-
const stat = fs_1.statSync(
|
|
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(
|
|
6028
|
-
return check(
|
|
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:
|
|
6609
|
-
const fullPath = [...
|
|
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,
|
|
6726
|
+
constructor(parent, value, path8, key) {
|
|
6726
6727
|
this._cachedPath = [];
|
|
6727
6728
|
this.parent = parent;
|
|
6728
6729
|
this.data = value;
|
|
6729
|
-
this._path =
|
|
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(
|
|
10842
|
-
return (0, import_file_exists.exists)(
|
|
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(
|
|
11245
|
-
return /^\.(git)?$/.test(
|
|
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 [
|
|
11680
|
-
paths.add(
|
|
11681
|
-
(results[
|
|
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:
|
|
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,
|
|
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,
|
|
12452
|
+
return new InitSummary(bare, path8, false, result[1]);
|
|
12452
12453
|
}
|
|
12453
12454
|
if (result = reInitResponseRegex.exec(response)) {
|
|
12454
|
-
return new InitSummary(bare,
|
|
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,
|
|
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,
|
|
12475
|
+
constructor(bare, path8, existing, gitDir) {
|
|
12475
12476
|
this.bare = bare;
|
|
12476
|
-
this.path =
|
|
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,
|
|
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"),
|
|
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(
|
|
13313
|
-
this.path =
|
|
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(
|
|
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,
|
|
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,
|
|
13353
|
+
handler(result, path8);
|
|
13353
13354
|
}
|
|
13354
13355
|
if (raw !== "##" && raw !== "!!") {
|
|
13355
|
-
result.files.push(new FileStatusSummary(
|
|
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(
|
|
13707
|
+
hashObject(path8, write) {
|
|
13707
13708
|
return this._runTask(
|
|
13708
|
-
hashObjectTask(
|
|
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
|
|
14063
|
-
return
|
|
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,
|
|
14349
|
-
return subModuleTask(["add", repo,
|
|
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,
|
|
14664
|
-
return this._runTask(addSubModuleTask2(repo,
|
|
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
|
-
`###
|
|
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** |
|
|
16310
|
-
`| **AI Spend** | \`${costPrefix}${receipt.ai.cost.toFixed(2)}\` |
|
|
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
|
-
`>
|
|
16339
|
+
`> **Privacy Guarantee:** *Source code was never uploaded to Qodewk. Telemetry computed strictly from cryptographic Git metadata.*`,
|
|
16314
16340
|
``,
|
|
16315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16464
|
-
|
|
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
|
|
16470
|
-
|
|
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
|
|
16644
|
+
body
|
|
16478
16645
|
});
|
|
16479
16646
|
if (!response.ok) {
|
|
16480
|
-
|
|
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
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
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 (${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16783
|
+
push(` | ${" ".repeat(leftPad)}${content}${" ".repeat(rightPad)} |`);
|
|
16551
16784
|
};
|
|
16552
16785
|
const printDivider = (char = "=", width = INNER_WIDTH) => {
|
|
16553
|
-
|
|
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
|
-
|
|
16561
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
16852
|
+
} else if (isPublished && publicUrl) {
|
|
16853
|
+
const qrRows = renderTerminalQr(publicUrl);
|
|
16627
16854
|
if (qrRows.length > 0) {
|
|
16628
|
-
printCenteredRow(import_picocolors.default.dim("--- SCAN
|
|
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
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
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
|
-
|
|
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.
|
|
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:*",
|