tokencanary 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ Proprietary. All rights reserved.
2
+ See README for product and usage information.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Token Canary
2
+
3
+ A CLI and daemon that reduces Claude Code token usage using hooks and lightweight local analysis.
4
+
5
+ - **30-day free trial** — full optimization from first install
6
+ - **Prompt compression** — dedupe lines, keep first stack trace
7
+ - **License-aware** — optimization runs only when the license is valid
8
+
9
+ You still run `claude` as usual; no alias required.
10
+
11
+ ## Requirements
12
+
13
+ - Node.js 18+
14
+ - Claude Code installed (`claude` on PATH)
15
+ - macOS or Linux
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install -g tokencanary
21
+ tokencanary doctor
22
+ tokencanary install
23
+ ```
24
+
25
+ - `tokencanary doctor` checks that Claude Code is installed, the OS is supported, and the hook config directory is writable.
26
+ - `tokencanary install` creates a trial license, writes hook config to `~/.tokencanary/hooks.json`, and starts the daemon. Configure Claude Code to use the URLs in that file.
27
+
28
+ **From source** (this repo):
29
+
30
+ ```bash
31
+ pnpm install
32
+ pnpm build
33
+ pnpm --filter tokencanary exec -- node dist/cli.js doctor
34
+ pnpm --filter tokencanary exec -- node dist/cli.js install
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ | Command | Description |
40
+ |---------|-------------|
41
+ | `tokencanary doctor` | Check environment (Claude Code, OS, hook config) |
42
+ | `tokencanary install` | Install hooks, create trial license, start daemon |
43
+ | `tokencanary uninstall` | Remove hook config |
44
+ | `tokencanary status` | Show daemon and license status |
45
+ | `tokencanary logs` | Show recent logs from `~/.tokencanary/logs/` |
46
+ | `tokencanary upgrade` | Open browser to subscribe ($19/mo or $99/yr) |
47
+ | `tokencanary license` | Show license and trial info |
48
+
49
+ ## After install
50
+
51
+ 1. Run `tokencanary doctor` to verify the environment.
52
+ 2. Run `tokencanary install` to write hook config and start the daemon.
53
+ 3. Configure Claude Code to use the hook URLs in `~/.tokencanary/hooks.json` (UserPromptSubmit and PreToolUse endpoints).
54
+ 4. Use Claude Code as usual; prompts are compressed when the license is valid.
55
+
56
+ ## Trial and pricing
57
+
58
+ - **Trial:** 30 days from first install, full optimization.
59
+ - **After trial:** Optimization is disabled; Claude Code works normally. You are prompted to upgrade.
60
+ - **Pricing:** $19/month or $99/year. Run `tokencanary upgrade` to open the checkout.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ pnpm install
66
+ pnpm lint
67
+ pnpm build
68
+ pnpm typecheck
69
+ pnpm test
70
+ pnpm test:integration # may require network for daemon server test
71
+ ```
72
+
73
+ ## License
74
+
75
+ Proprietary. See LICENSE file.
@@ -0,0 +1,86 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-EIBTXUZ4.js";
4
+
5
+ // src/checks/claude.ts
6
+ import { execSync } from "child_process";
7
+ async function checkClaudeCodeInstalled() {
8
+ try {
9
+ execSync("which claude", { encoding: "utf8" });
10
+ return { name: "Claude Code", ok: true, message: "claude is installed" };
11
+ } catch {
12
+ return {
13
+ name: "Claude Code",
14
+ ok: false,
15
+ message: "claude not found on PATH; install Claude Code first"
16
+ };
17
+ }
18
+ }
19
+
20
+ // src/checks/os.ts
21
+ import { platform } from "os";
22
+ var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "linux"]);
23
+ async function checkOsSupported() {
24
+ const os = platform();
25
+ const ok = SUPPORTED.has(os);
26
+ return {
27
+ name: "OS",
28
+ ok,
29
+ message: ok ? `${os} is supported` : `${os} is not supported (use darwin or linux)`
30
+ };
31
+ }
32
+
33
+ // src/checks/hooks.ts
34
+ import { mkdir, writeFile, access } from "fs/promises";
35
+ import { join } from "path";
36
+ async function checkHookConfigWritable() {
37
+ const configDir = getConfigDir();
38
+ const hooksFile = join(configDir, "hooks.json");
39
+ try {
40
+ await mkdir(configDir, { recursive: true });
41
+ await writeFile(hooksFile, "{}", { flag: "w" });
42
+ await access(hooksFile);
43
+ return {
44
+ name: "Hook config",
45
+ ok: true,
46
+ message: `Config dir writable: ${configDir}`
47
+ };
48
+ } catch (err) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ return {
51
+ name: "Hook config",
52
+ ok: false,
53
+ message: `Cannot write to ${configDir}: ${msg}`
54
+ };
55
+ }
56
+ }
57
+
58
+ // src/commands/doctor.ts
59
+ async function runDoctor(_args) {
60
+ const checks = await runAllChecks();
61
+ const allOk = checks.every((c) => c.ok);
62
+ for (const c of checks) {
63
+ const icon = c.ok ? "\u2713" : "\u2717";
64
+ console.log(`${icon} ${c.name}: ${c.message}`);
65
+ }
66
+ if (!allOk) {
67
+ console.log("\nFix the issues above and run 'tokencanary doctor' again.");
68
+ return 1;
69
+ }
70
+ console.log("\nAll checks passed. Run 'tokencanary install' to install hooks.");
71
+ return 0;
72
+ }
73
+ async function runAllChecks() {
74
+ const [claude, os, hooks] = await Promise.all([
75
+ checkClaudeCodeInstalled(),
76
+ checkOsSupported(),
77
+ checkHookConfigWritable()
78
+ ]);
79
+ return [claude, os, hooks];
80
+ }
81
+
82
+ export {
83
+ runDoctor,
84
+ runAllChecks
85
+ };
86
+ //# sourceMappingURL=chunk-DVN7C4IR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/checks/claude.ts","../src/checks/os.ts","../src/checks/hooks.ts","../src/commands/doctor.ts"],"sourcesContent":["import type { DoctorCheck } from \"@token-canary/shared\";\nimport { execSync } from \"node:child_process\";\n\n/** Check that Claude Code (claude) is installed and on PATH */\nexport async function checkClaudeCodeInstalled(): Promise<DoctorCheck> {\n try {\n execSync(\"which claude\", { encoding: \"utf8\" });\n return { name: \"Claude Code\", ok: true, message: \"claude is installed\" };\n } catch {\n return {\n name: \"Claude Code\",\n ok: false,\n message: \"claude not found on PATH; install Claude Code first\",\n };\n }\n}\n","import type { DoctorCheck } from \"@token-canary/shared\";\nimport { platform } from \"node:os\";\n\nconst SUPPORTED = new Set([\"darwin\", \"linux\"]);\n\n/** Check that the OS is supported */\nexport async function checkOsSupported(): Promise<DoctorCheck> {\n const os = platform();\n const ok = SUPPORTED.has(os);\n return {\n name: \"OS\",\n ok,\n message: ok ? `${os} is supported` : `${os} is not supported (use darwin or linux)`,\n };\n}\n","import type { DoctorCheck } from \"@token-canary/shared\";\nimport { getConfigDir } from \"@token-canary/shared\";\nimport { mkdir, writeFile, access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Check that hook config directory is writable */\nexport async function checkHookConfigWritable(): Promise<DoctorCheck> {\n const configDir = getConfigDir();\n const hooksFile = join(configDir, \"hooks.json\");\n try {\n await mkdir(configDir, { recursive: true });\n await writeFile(hooksFile, \"{}\", { flag: \"w\" });\n await access(hooksFile);\n return {\n name: \"Hook config\",\n ok: true,\n message: `Config dir writable: ${configDir}`,\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n name: \"Hook config\",\n ok: false,\n message: `Cannot write to ${configDir}: ${msg}`,\n };\n }\n}\n","import type { DoctorCheck } from \"@token-canary/shared\";\nimport { checkClaudeCodeInstalled } from \"../checks/claude.js\";\nimport { checkOsSupported } from \"../checks/os.js\";\nimport { checkHookConfigWritable } from \"../checks/hooks.js\";\n\nexport async function runDoctor(_args: string[]): Promise<number> {\n const checks = await runAllChecks();\n const allOk = checks.every((c) => c.ok);\n for (const c of checks) {\n const icon = c.ok ? \"✓\" : \"✗\";\n console.log(`${icon} ${c.name}: ${c.message}`);\n }\n if (!allOk) {\n console.log(\"\\nFix the issues above and run 'tokencanary doctor' again.\");\n return 1;\n }\n console.log(\"\\nAll checks passed. Run 'tokencanary install' to install hooks.\");\n return 0;\n}\n\nexport async function runAllChecks(): Promise<DoctorCheck[]> {\n const [claude, os, hooks] = await Promise.all([\n checkClaudeCodeInstalled(),\n checkOsSupported(),\n checkHookConfigWritable(),\n ]);\n return [claude, os, hooks];\n}\n"],"mappings":";;;;;AACA,SAAS,gBAAgB;AAGzB,eAAsB,2BAAiD;AACrE,MAAI;AACF,aAAS,gBAAgB,EAAE,UAAU,OAAO,CAAC;AAC7C,WAAO,EAAE,MAAM,eAAe,IAAI,MAAM,SAAS,sBAAsB;AAAA,EACzE,QAAQ;AACN,WAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,SAAS;AAAA,IACX;AAAA,EACF;AACF;;;ACdA,SAAS,gBAAgB;AAEzB,IAAM,YAAY,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC;AAG7C,eAAsB,mBAAyC;AAC7D,QAAM,KAAK,SAAS;AACpB,QAAM,KAAK,UAAU,IAAI,EAAE;AAC3B,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,SAAS,KAAK,GAAG,EAAE,kBAAkB,GAAG,EAAE;AAAA,EAC5C;AACF;;;ACZA,SAAS,OAAO,WAAW,cAAc;AACzC,SAAS,YAAY;AAGrB,eAAsB,0BAAgD;AACpE,QAAM,YAAY,aAAa;AAC/B,QAAM,YAAY,KAAK,WAAW,YAAY;AAC9C,MAAI;AACF,UAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,UAAU,WAAW,MAAM,EAAE,MAAM,IAAI,CAAC;AAC9C,UAAM,OAAO,SAAS;AACtB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,SAAS,wBAAwB,SAAS;AAAA,IAC5C;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,SAAS,mBAAmB,SAAS,KAAK,GAAG;AAAA,IAC/C;AAAA,EACF;AACF;;;ACrBA,eAAsB,UAAU,OAAkC;AAChE,QAAM,SAAS,MAAM,aAAa;AAClC,QAAM,QAAQ,OAAO,MAAM,CAAC,MAAM,EAAE,EAAE;AACtC,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,EAAE,KAAK,WAAM;AAC1B,YAAQ,IAAI,GAAG,IAAI,IAAI,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,EAC/C;AACA,MAAI,CAAC,OAAO;AACV,YAAQ,IAAI,4DAA4D;AACxE,WAAO;AAAA,EACT;AACA,UAAQ,IAAI,kEAAkE;AAC9E,SAAO;AACT;AAEA,eAAsB,eAAuC;AAC3D,QAAM,CAAC,QAAQ,IAAI,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC5C,yBAAyB;AAAA,IACzB,iBAAiB;AAAA,IACjB,wBAAwB;AAAA,EAC1B,CAAC;AACD,SAAO,CAAC,QAAQ,IAAI,KAAK;AAC3B;","names":[]}
@@ -0,0 +1,137 @@
1
+ // ../shared/dist/constants.js
2
+ var APP_NAME = "tokencanary";
3
+ function getLogDir() {
4
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? process.env.TMP ?? "/tmp";
5
+ return `${home}/.${APP_NAME}/logs`;
6
+ }
7
+ function getConfigDir() {
8
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? process.env.TMP ?? "/tmp";
9
+ return `${home}/.${APP_NAME}`;
10
+ }
11
+ var TRIAL_DAYS = 30;
12
+ var OFFLINE_GRACE_DAYS = 3;
13
+ var DEFAULT_DAEMON_PORT = 3847;
14
+ var LICENSE_FILENAME = "license.json";
15
+ function getLicensePath() {
16
+ return `${getConfigDir()}/${LICENSE_FILENAME}`;
17
+ }
18
+ var DEFAULT_UPGRADE_URL = "https://checkout.stripe.com/placeholder";
19
+ function getUpgradeUrl() {
20
+ return process.env.TOKENCANARY_UPGRADE_URL ?? DEFAULT_UPGRADE_URL;
21
+ }
22
+ var HOOK_TIMEOUT_MS = 5e3;
23
+
24
+ // ../shared/dist/fingerprint.js
25
+ import { createHash } from "crypto";
26
+ import { hostname, platform, arch, cpus, networkInterfaces } from "os";
27
+ function getMachineId() {
28
+ const parts = [];
29
+ try {
30
+ parts.push(hostname());
31
+ } catch {
32
+ parts.push("unknown-host");
33
+ }
34
+ parts.push(platform());
35
+ parts.push(arch());
36
+ try {
37
+ const cpu = cpus()[0];
38
+ parts.push(cpu?.model ?? "unknown-cpu");
39
+ } catch {
40
+ parts.push("unknown-cpu");
41
+ }
42
+ try {
43
+ const nets = networkInterfaces();
44
+ const addrs = [];
45
+ for (const name of Object.keys(nets)) {
46
+ const list = nets[name];
47
+ if (list) {
48
+ for (const iface of list) {
49
+ if (iface.address && !iface.internal)
50
+ addrs.push(name + ":" + iface.address);
51
+ }
52
+ }
53
+ }
54
+ addrs.sort();
55
+ if (addrs.length > 0) {
56
+ parts.push(createHash("sha256").update(addrs.join(",")).digest("hex").slice(0, 16));
57
+ }
58
+ } catch {
59
+ }
60
+ return createHash("sha256").update(parts.join("|")).digest("hex");
61
+ }
62
+
63
+ // ../shared/dist/license.js
64
+ var MS_PER_DAY = 86400 * 1e3;
65
+ function createTrialLicense(installId, machineId) {
66
+ const now = /* @__PURE__ */ new Date();
67
+ const expiry = new Date(now.getTime() + TRIAL_DAYS * MS_PER_DAY);
68
+ return {
69
+ installId,
70
+ machineId,
71
+ plan: "trial",
72
+ expiry: expiry.toISOString(),
73
+ createdAt: now.toISOString()
74
+ };
75
+ }
76
+ function isLicenseValid(license, now = /* @__PURE__ */ new Date(), options) {
77
+ if (!license)
78
+ return false;
79
+ const expiry = new Date(license.expiry);
80
+ if (expiry > now)
81
+ return true;
82
+ if (options?.lastKnownValid) {
83
+ const graceEnd = new Date(options.lastKnownValid.getTime() + OFFLINE_GRACE_DAYS * MS_PER_DAY);
84
+ if (now < graceEnd)
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+ function getPlanDisplay(plan) {
90
+ switch (plan) {
91
+ case "trial":
92
+ return "Trial";
93
+ case "monthly":
94
+ return "Monthly";
95
+ case "yearly":
96
+ return "Yearly";
97
+ default:
98
+ return String(plan);
99
+ }
100
+ }
101
+
102
+ // ../shared/dist/license-store.js
103
+ import { readFileSync, writeFileSync, existsSync } from "fs";
104
+ function readLicenseSync() {
105
+ const path = getLicensePath();
106
+ if (!existsSync(path))
107
+ return null;
108
+ try {
109
+ const raw = readFileSync(path, "utf8");
110
+ const data = JSON.parse(raw);
111
+ if (typeof data.installId === "string" && typeof data.machineId === "string" && typeof data.plan === "string" && typeof data.expiry === "string" && typeof data.createdAt === "string") {
112
+ return data;
113
+ }
114
+ } catch {
115
+ }
116
+ return null;
117
+ }
118
+ function writeLicenseSync(license) {
119
+ const path = getLicensePath();
120
+ writeFileSync(path, JSON.stringify(license, null, 2), "utf8");
121
+ }
122
+
123
+ export {
124
+ getLogDir,
125
+ getConfigDir,
126
+ TRIAL_DAYS,
127
+ DEFAULT_DAEMON_PORT,
128
+ getUpgradeUrl,
129
+ HOOK_TIMEOUT_MS,
130
+ getMachineId,
131
+ createTrialLicense,
132
+ isLicenseValid,
133
+ getPlanDisplay,
134
+ readLicenseSync,
135
+ writeLicenseSync
136
+ };
137
+ //# sourceMappingURL=chunk-EIBTXUZ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../shared/src/constants.ts","../../shared/src/fingerprint.ts","../../shared/src/license.ts","../../shared/src/license-store.ts"],"sourcesContent":["/** App name for config/log paths */\nexport const APP_NAME = \"tokencanary\";\n\n/** Default log directory under user home */\nexport function getLogDir(): string {\n const home =\n process.env.HOME ?? process.env.USERPROFILE ?? process.env.TMP ?? \"/tmp\";\n return `${home}/.${APP_NAME}/logs`;\n}\n\n/** Default config directory under user home */\nexport function getConfigDir(): string {\n const home =\n process.env.HOME ?? process.env.USERPROFILE ?? process.env.TMP ?? \"/tmp\";\n return `${home}/.${APP_NAME}`;\n}\n\n/** Trial duration in days */\nexport const TRIAL_DAYS = 30;\n\n/** Offline grace period in days */\nexport const OFFLINE_GRACE_DAYS = 3;\n\n/** Default daemon port for hook HTTP server */\nexport const DEFAULT_DAEMON_PORT = 3847;\n\n/** License file name under config dir */\nexport const LICENSE_FILENAME = \"license.json\";\n\nexport function getLicensePath(): string {\n return `${getConfigDir()}/${LICENSE_FILENAME}`;\n}\n\n/** Stripe Checkout URL for upgrade (override with TOKENCANARY_UPGRADE_URL) */\nexport const DEFAULT_UPGRADE_URL = \"https://checkout.stripe.com/placeholder\";\n\nexport function getUpgradeUrl(): string {\n return process.env.TOKENCANARY_UPGRADE_URL ?? DEFAULT_UPGRADE_URL;\n}\n\n/** Hook request timeout in ms (fail open) */\nexport const HOOK_TIMEOUT_MS = 5000;\n","import { createHash } from \"node:crypto\";\nimport { hostname, platform, arch, cpus, networkInterfaces } from \"node:os\";\n\n/**\n * Deterministic machine fingerprint for license binding.\n * Uses hostname, OS, CPU model, and hashed network info (no raw MAC).\n */\nexport function getMachineId(): string {\n const parts: string[] = [];\n try {\n parts.push(hostname());\n } catch {\n parts.push(\"unknown-host\");\n }\n parts.push(platform());\n parts.push(arch());\n try {\n const cpu = cpus()[0];\n parts.push(cpu?.model ?? \"unknown-cpu\");\n } catch {\n parts.push(\"unknown-cpu\");\n }\n try {\n const nets = networkInterfaces();\n const addrs: string[] = [];\n for (const name of Object.keys(nets)) {\n const list = nets[name];\n if (list) {\n for (const iface of list) {\n if (iface.address && !iface.internal) addrs.push(name + \":\" + iface.address);\n }\n }\n }\n addrs.sort();\n if (addrs.length > 0) {\n parts.push(createHash(\"sha256\").update(addrs.join(\",\")).digest(\"hex\").slice(0, 16));\n }\n } catch {\n // skip network\n }\n return createHash(\"sha256\").update(parts.join(\"|\")).digest(\"hex\");\n}\n","import type { LicenseInfo, Plan } from \"./types.js\";\nimport { TRIAL_DAYS, OFFLINE_GRACE_DAYS } from \"./constants.js\";\n\nconst MS_PER_DAY = 86400 * 1000;\n\nexport function createTrialLicense(installId: string, machineId: string): LicenseInfo {\n const now = new Date();\n const expiry = new Date(now.getTime() + TRIAL_DAYS * MS_PER_DAY);\n return {\n installId,\n machineId,\n plan: \"trial\",\n expiry: expiry.toISOString(),\n createdAt: now.toISOString(),\n };\n}\n\n/**\n * Check if license is valid at the given date.\n * - Trial/monthly/yearly: valid if expiry > now.\n * - Offline grace: if lastKnownValid and within OFFLINE_GRACE_DAYS, still valid.\n */\nexport function isLicenseValid(\n license: LicenseInfo | null,\n now: Date = new Date(),\n options?: { lastKnownValid?: Date }\n): boolean {\n if (!license) return false;\n const expiry = new Date(license.expiry);\n if (expiry > now) return true;\n if (options?.lastKnownValid) {\n const graceEnd = new Date(\n options.lastKnownValid.getTime() + OFFLINE_GRACE_DAYS * MS_PER_DAY\n );\n if (now < graceEnd) return true;\n }\n return false;\n}\n\nexport function getPlanDisplay(plan: Plan): string {\n switch (plan) {\n case \"trial\":\n return \"Trial\";\n case \"monthly\":\n return \"Monthly\";\n case \"yearly\":\n return \"Yearly\";\n default:\n return String(plan);\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { getLicensePath } from \"./constants.js\";\nimport type { LicenseInfo } from \"./types.js\";\n\nexport function readLicenseSync(): LicenseInfo | null {\n const path = getLicensePath();\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf8\");\n const data = JSON.parse(raw) as LicenseInfo;\n if (\n typeof data.installId === \"string\" &&\n typeof data.machineId === \"string\" &&\n typeof data.plan === \"string\" &&\n typeof data.expiry === \"string\" &&\n typeof data.createdAt === \"string\"\n ) {\n return data;\n }\n } catch {\n // invalid or missing\n }\n return null;\n}\n\nexport function writeLicenseSync(license: LicenseInfo): void {\n const path = getLicensePath();\n writeFileSync(path, JSON.stringify(license, null, 2), \"utf8\");\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runDoctor
4
+ } from "./chunk-DVN7C4IR.js";
5
+ import "./chunk-EIBTXUZ4.js";
6
+
7
+ // src/commands/index.ts
8
+ var COMMANDS = {
9
+ doctor: runDoctor,
10
+ install: runInstall,
11
+ uninstall: runUninstall,
12
+ status: runStatus,
13
+ logs: runLogs,
14
+ upgrade: runUpgrade,
15
+ license: runLicense
16
+ };
17
+ async function runCli(args) {
18
+ const cmd = args[0];
19
+ if (!cmd || cmd === "--help" || cmd === "-h") {
20
+ printHelp();
21
+ return;
22
+ }
23
+ const handler = COMMANDS[cmd];
24
+ if (!handler) {
25
+ console.error(`Unknown command: ${cmd}`);
26
+ printHelp();
27
+ process.exit(1);
28
+ }
29
+ const code = await handler(args.slice(1));
30
+ process.exit(code);
31
+ }
32
+ function printHelp() {
33
+ console.log(`
34
+ tokencanary - Reduce Claude Code token usage
35
+
36
+ Usage: tokencanary <command> [options]
37
+
38
+ Commands:
39
+ doctor Check environment (Claude Code, OS, hook config)
40
+ install Install hooks and start daemon
41
+ uninstall Remove hooks and stop daemon
42
+ status Show daemon and license status
43
+ logs Show recent logs
44
+ upgrade Open browser to upgrade/subscribe
45
+ license Show license and trial info
46
+
47
+ Run 'tokencanary <command> --help' for command-specific help.
48
+ `);
49
+ }
50
+ async function runInstall(_args) {
51
+ const { runInstallCmd } = await import("./install-6MNQWAWD.js");
52
+ return runInstallCmd(_args);
53
+ }
54
+ async function runUninstall(_args) {
55
+ const { runUninstallCmd } = await import("./uninstall-HQ2TCX6F.js");
56
+ return runUninstallCmd(_args);
57
+ }
58
+ async function runStatus(_args) {
59
+ const { runStatusCmd } = await import("./status-UIDOEWKC.js");
60
+ return runStatusCmd(_args);
61
+ }
62
+ async function runLogs(_args) {
63
+ const { runLogsCmd } = await import("./logs-NMLIJXEJ.js");
64
+ return runLogsCmd(_args);
65
+ }
66
+ async function runUpgrade(_args) {
67
+ const { runUpgradeCmd } = await import("./upgrade-3WNDWZ7R.js");
68
+ return runUpgradeCmd(_args);
69
+ }
70
+ async function runLicense(_args) {
71
+ const { runLicenseCmd } = await import("./license-XSBCIOKK.js");
72
+ return runLicenseCmd(_args);
73
+ }
74
+
75
+ // src/cli.ts
76
+ runCli(process.argv.slice(2)).catch((err) => {
77
+ console.error(err instanceof Error ? err.message : String(err));
78
+ process.exit(1);
79
+ });
80
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/index.ts","../src/cli.ts"],"sourcesContent":["import { runDoctor } from \"./doctor.js\";\n\nconst COMMANDS: Record<string, (args: string[]) => Promise<number>> = {\n doctor: runDoctor,\n install: runInstall,\n uninstall: runUninstall,\n status: runStatus,\n logs: runLogs,\n upgrade: runUpgrade,\n license: runLicense,\n};\n\nexport async function runCli(args: string[]): Promise<void> {\n const cmd = args[0];\n if (!cmd || cmd === \"--help\" || cmd === \"-h\") {\n printHelp();\n return;\n }\n const handler = COMMANDS[cmd];\n if (!handler) {\n console.error(`Unknown command: ${cmd}`);\n printHelp();\n process.exit(1);\n }\n const code = await handler(args.slice(1));\n process.exit(code);\n}\n\nfunction printHelp(): void {\n console.log(`\ntokencanary - Reduce Claude Code token usage\n\nUsage: tokencanary <command> [options]\n\nCommands:\n doctor Check environment (Claude Code, OS, hook config)\n install Install hooks and start daemon\n uninstall Remove hooks and stop daemon\n status Show daemon and license status\n logs Show recent logs\n upgrade Open browser to upgrade/subscribe\n license Show license and trial info\n\nRun 'tokencanary <command> --help' for command-specific help.\n`);\n}\n\nasync function runInstall(_args: string[]): Promise<number> {\n const { runInstallCmd } = await import(\"./install.js\");\n return runInstallCmd(_args);\n}\n\nasync function runUninstall(_args: string[]): Promise<number> {\n const { runUninstallCmd } = await import(\"./uninstall.js\");\n return runUninstallCmd(_args);\n}\n\nasync function runStatus(_args: string[]): Promise<number> {\n const { runStatusCmd } = await import(\"./status.js\");\n return runStatusCmd(_args);\n}\n\nasync function runLogs(_args: string[]): Promise<number> {\n const { runLogsCmd } = await import(\"./logs.js\");\n return runLogsCmd(_args);\n}\n\nasync function runUpgrade(_args: string[]): Promise<number> {\n const { runUpgradeCmd } = await import(\"./upgrade.js\");\n return runUpgradeCmd(_args);\n}\n\nasync function runLicense(_args: string[]): Promise<number> {\n const { runLicenseCmd } = await import(\"./license.js\");\n return runLicenseCmd(_args);\n}\n","#!/usr/bin/env node\nimport { runCli } from \"./commands/index.js\";\n\nrunCli(process.argv.slice(2)).catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAEA,IAAM,WAAgE;AAAA,EACpE,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AACX;AAEA,eAAsB,OAAO,MAA+B;AAC1D,QAAM,MAAM,KAAK,CAAC;AAClB,MAAI,CAAC,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC5C,cAAU;AACV;AAAA,EACF;AACA,QAAM,UAAU,SAAS,GAAG;AAC5B,MAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,oBAAoB,GAAG,EAAE;AACvC,cAAU;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,OAAO,MAAM,QAAQ,KAAK,MAAM,CAAC,CAAC;AACxC,UAAQ,KAAK,IAAI;AACnB;AAEA,SAAS,YAAkB;AACzB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAeb;AACD;AAEA,eAAe,WAAW,OAAkC;AAC1D,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAc;AACrD,SAAO,cAAc,KAAK;AAC5B;AAEA,eAAe,aAAa,OAAkC;AAC5D,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,yBAAgB;AACzD,SAAO,gBAAgB,KAAK;AAC9B;AAEA,eAAe,UAAU,OAAkC;AACzD,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,sBAAa;AACnD,SAAO,aAAa,KAAK;AAC3B;AAEA,eAAe,QAAQ,OAAkC;AACvD,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,oBAAW;AAC/C,SAAO,WAAW,KAAK;AACzB;AAEA,eAAe,WAAW,OAAkC;AAC1D,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAc;AACrD,SAAO,cAAc,KAAK;AAC5B;AAEA,eAAe,WAAW,OAAkC;AAC1D,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAc;AACrD,SAAO,cAAc,KAAK;AAC5B;;;ACxEA,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,QAAiB;AACpD,UAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
package/dist/daemon.js ADDED
@@ -0,0 +1,153 @@
1
+ import {
2
+ DEFAULT_DAEMON_PORT,
3
+ HOOK_TIMEOUT_MS,
4
+ isLicenseValid,
5
+ readLicenseSync
6
+ } from "./chunk-EIBTXUZ4.js";
7
+
8
+ // ../daemon/src/index.ts
9
+ import { fileURLToPath } from "url";
10
+
11
+ // ../daemon/src/server.ts
12
+ import { createServer } from "http";
13
+
14
+ // ../hooks/dist/compress.js
15
+ var STACK_LINE = /^\s*at\s+/;
16
+ var MAX_DUPE_LINES = 5;
17
+ function isStackLine(line) {
18
+ return STACK_LINE.test(line.trim());
19
+ }
20
+ function dedupeLines(lines) {
21
+ const out = [];
22
+ let prev = "";
23
+ let count = 0;
24
+ for (const line of lines) {
25
+ if (line === prev) {
26
+ count++;
27
+ if (count <= MAX_DUPE_LINES)
28
+ out.push(line);
29
+ } else {
30
+ prev = line;
31
+ count = 1;
32
+ out.push(line);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ function keepFirstStack(lines) {
38
+ const out = [];
39
+ let inStack = false;
40
+ let keptOne = false;
41
+ for (const line of lines) {
42
+ if (isStackLine(line)) {
43
+ if (!keptOne) {
44
+ inStack = true;
45
+ out.push(line);
46
+ }
47
+ } else {
48
+ if (inStack)
49
+ keptOne = true;
50
+ inStack = false;
51
+ out.push(line);
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ function compressPrompt(text) {
57
+ if (!text || text.length === 0)
58
+ return text;
59
+ const lines = text.split(/\r?\n/);
60
+ let result = lines;
61
+ result = keepFirstStack(result);
62
+ result = dedupeLines(result);
63
+ return result.join("\n");
64
+ }
65
+
66
+ // ../daemon/src/handlers.ts
67
+ async function handleHook(name, payload) {
68
+ switch (name) {
69
+ case "UserPromptSubmit":
70
+ return handleUserPromptSubmit(payload);
71
+ case "PreToolUse":
72
+ return handlePreToolUse(payload);
73
+ default:
74
+ return payload;
75
+ }
76
+ }
77
+ async function handleUserPromptSubmit(payload) {
78
+ const license = readLicenseSync();
79
+ if (!isLicenseValid(license)) {
80
+ return payload;
81
+ }
82
+ if (payload === null || typeof payload !== "object") return payload;
83
+ const obj = payload;
84
+ const prompt = obj.prompt ?? obj.text ?? obj.content;
85
+ if (typeof prompt !== "string") return payload;
86
+ try {
87
+ const compressed = compressPrompt(prompt);
88
+ return { ...obj, prompt: compressed };
89
+ } catch {
90
+ return payload;
91
+ }
92
+ }
93
+ async function handlePreToolUse(payload) {
94
+ return payload;
95
+ }
96
+
97
+ // ../daemon/src/server.ts
98
+ var PORT = Number(process.env.TOKENCANARY_PORT) || DEFAULT_DAEMON_PORT;
99
+ function createDaemonServer() {
100
+ return createServer(async (req, res) => {
101
+ if (req.method !== "POST" || !req.url?.startsWith("/hook/")) {
102
+ res.writeHead(404, { "Content-Type": "application/json" });
103
+ res.end(JSON.stringify({ error: "Not found" }));
104
+ return;
105
+ }
106
+ const name = req.url.slice("/hook/".length).split("?")[0];
107
+ let body = "";
108
+ for await (const chunk of req) {
109
+ body += chunk;
110
+ }
111
+ let payload;
112
+ try {
113
+ payload = JSON.parse(body || "{}");
114
+ } catch {
115
+ res.writeHead(400, { "Content-Type": "application/json" });
116
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
117
+ return;
118
+ }
119
+ const timeout = new Promise(
120
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), HOOK_TIMEOUT_MS)
121
+ );
122
+ const work = handleHook(name, payload);
123
+ try {
124
+ const result = await Promise.race([work, timeout]);
125
+ res.writeHead(200, { "Content-Type": "application/json" });
126
+ res.end(JSON.stringify(result));
127
+ } catch {
128
+ res.writeHead(200, { "Content-Type": "application/json" });
129
+ res.end(JSON.stringify(payload));
130
+ }
131
+ });
132
+ }
133
+ async function startDaemon() {
134
+ const server = createDaemonServer();
135
+ server.listen(PORT, () => {
136
+ process.stdout.write(`Token Canary daemon listening on port ${PORT}
137
+ `);
138
+ });
139
+ }
140
+
141
+ // ../daemon/src/index.ts
142
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
143
+ startDaemon().catch((err) => {
144
+ console.error(err);
145
+ process.exit(1);
146
+ });
147
+ }
148
+ export {
149
+ createDaemonServer,
150
+ handleHook,
151
+ startDaemon
152
+ };
153
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../daemon/src/index.ts","../../daemon/src/server.ts","../../hooks/src/compress.ts","../../daemon/src/handlers.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { startDaemon as start } from \"./server.js\";\n\nexport { startDaemon, createDaemonServer } from \"./server.js\";\nexport { handleHook } from \"./handlers.js\";\n\nif (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {\n start().catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n });\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { DEFAULT_DAEMON_PORT, HOOK_TIMEOUT_MS } from \"@token-canary/shared\";\nimport { handleHook } from \"./handlers.js\";\n\nconst PORT = Number(process.env.TOKENCANARY_PORT) || DEFAULT_DAEMON_PORT;\n\nexport function createDaemonServer() {\n return createServer(async (req: IncomingMessage, res: ServerResponse) => {\n if (req.method !== \"POST\" || !req.url?.startsWith(\"/hook/\")) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n const name = req.url.slice(\"/hook/\".length).split(\"?\")[0];\n let body = \"\";\n for await (const chunk of req) {\n body += chunk;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(body || \"{}\");\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON\" }));\n return;\n }\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(\"timeout\")), HOOK_TIMEOUT_MS)\n );\n const work = handleHook(name, payload);\n try {\n const result = await Promise.race([work, timeout]);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n }\n });\n}\n\nexport async function startDaemon(): Promise<void> {\n const server = createDaemonServer();\n server.listen(PORT, () => {\n process.stdout.write(`Token Canary daemon listening on port ${PORT}\\n`);\n });\n}\n","/**\n * Compress prompt text: remove duplicate lines, keep error lines, keep first stack trace.\n * Used for UserPromptSubmit hook to reduce token usage.\n */\nconst STACK_LINE = /^\\s*at\\s+/;\nconst MAX_DUPE_LINES = 5;\n\nfunction isStackLine(line: string): boolean {\n return STACK_LINE.test(line.trim());\n}\n\n/** Remove duplicate consecutive lines, cap repeats at MAX_DUPE_LINES */\nfunction dedupeLines(lines: string[]): string[] {\n const out: string[] = [];\n let prev = \"\";\n let count = 0;\n for (const line of lines) {\n if (line === prev) {\n count++;\n if (count <= MAX_DUPE_LINES) out.push(line);\n } else {\n prev = line;\n count = 1;\n out.push(line);\n }\n }\n return out;\n}\n\n/** Keep first stack trace block (consecutive \"at ...\" lines), drop later ones */\nfunction keepFirstStack(lines: string[]): string[] {\n const out: string[] = [];\n let inStack = false;\n let keptOne = false;\n for (const line of lines) {\n if (isStackLine(line)) {\n if (!keptOne) {\n inStack = true;\n out.push(line);\n }\n } else {\n if (inStack) keptOne = true;\n inStack = false;\n out.push(line);\n }\n }\n return out;\n}\n\n/**\n * Compress prompt: dedupe lines, keep first stack trace only.\n * If the text is small (< MAX_LINES_BEFORE_DEDUPE lines), still dedupes.\n */\nexport function compressPrompt(text: string): string {\n if (!text || text.length === 0) return text;\n const lines = text.split(/\\r?\\n/);\n let result = lines;\n result = keepFirstStack(result);\n result = dedupeLines(result);\n return result.join(\"\\n\");\n}\n","import { readLicenseSync, isLicenseValid } from \"@token-canary/shared\";\nimport { compressPrompt } from \"@token-canary/hooks\";\n\n/** Route hook name to handler; fail open: return payload on error */\nexport async function handleHook(name: string, payload: unknown): Promise<unknown> {\n switch (name) {\n case \"UserPromptSubmit\":\n return handleUserPromptSubmit(payload);\n case \"PreToolUse\":\n return handlePreToolUse(payload);\n default:\n return payload;\n }\n}\n\nasync function handleUserPromptSubmit(payload: unknown): Promise<unknown> {\n const license = readLicenseSync();\n if (!isLicenseValid(license)) {\n return payload;\n }\n if (payload === null || typeof payload !== \"object\") return payload;\n const obj = payload as Record<string, unknown>;\n const prompt = obj.prompt ?? obj.text ?? obj.content;\n if (typeof prompt !== \"string\") return payload;\n try {\n const compressed = compressPrompt(prompt);\n return { ...obj, prompt: compressed };\n } catch {\n return payload;\n }\n}\n\nasync function handlePreToolUse(payload: unknown): Promise<unknown> {\n return payload;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,82 @@
1
+ import {
2
+ runAllChecks
3
+ } from "./chunk-DVN7C4IR.js";
4
+ import {
5
+ DEFAULT_DAEMON_PORT,
6
+ createTrialLicense,
7
+ getConfigDir,
8
+ getMachineId,
9
+ readLicenseSync,
10
+ writeLicenseSync
11
+ } from "./chunk-EIBTXUZ4.js";
12
+
13
+ // src/commands/install.ts
14
+ import { randomUUID } from "crypto";
15
+ import { mkdir, writeFile } from "fs/promises";
16
+ import { join as join2 } from "path";
17
+
18
+ // src/daemon-client.ts
19
+ import { spawn } from "child_process";
20
+ import { dirname, join } from "path";
21
+ import { fileURLToPath } from "url";
22
+ function getDaemonEntryPath() {
23
+ const cliUrl = import.meta.url;
24
+ const cliPath = fileURLToPath(cliUrl);
25
+ const cliDir = dirname(cliPath);
26
+ return join(cliDir, "daemon.js");
27
+ }
28
+ function startDaemonProcess() {
29
+ const entry = getDaemonEntryPath();
30
+ const child = spawn(process.execPath, [entry], {
31
+ stdio: "ignore",
32
+ detached: true
33
+ });
34
+ child.unref();
35
+ child.on("error", (err) => {
36
+ console.error("Token Canary daemon failed to start:", err.message);
37
+ console.error("Daemon path:", entry);
38
+ });
39
+ return child;
40
+ }
41
+ function getDaemonPort() {
42
+ return Number(process.env.TOKENCANARY_PORT) || DEFAULT_DAEMON_PORT;
43
+ }
44
+
45
+ // src/commands/install.ts
46
+ var HOOKS_FILENAME = "hooks.json";
47
+ async function runInstallCmd(_args) {
48
+ const checks = await runAllChecks();
49
+ const failed = checks.filter((c) => !c.ok);
50
+ if (failed.length > 0) {
51
+ for (const c of checks) {
52
+ console.log(`${c.ok ? "\u2713" : "\u2717"} ${c.name}: ${c.message}`);
53
+ }
54
+ console.log("\nFix the issues above and run 'tokencanary doctor'.");
55
+ return 1;
56
+ }
57
+ const configDir = getConfigDir();
58
+ await mkdir(configDir, { recursive: true });
59
+ let license = readLicenseSync();
60
+ if (!license) {
61
+ const installId = randomUUID();
62
+ const machineId = getMachineId();
63
+ license = createTrialLicense(installId, machineId);
64
+ writeLicenseSync(license);
65
+ console.log("Trial started: 30 days from today.");
66
+ }
67
+ const port = getDaemonPort();
68
+ const hooksConfig = {
69
+ UserPromptSubmit: `http://127.0.0.1:${port}/hook/UserPromptSubmit`,
70
+ PreToolUse: `http://127.0.0.1:${port}/hook/PreToolUse`
71
+ };
72
+ const hooksPath = join2(configDir, HOOKS_FILENAME);
73
+ await writeFile(hooksPath, JSON.stringify(hooksConfig, null, 2));
74
+ console.log(`Wrote ${hooksPath}`);
75
+ startDaemonProcess();
76
+ console.log("Daemon started. Configure Claude Code to use the URLs in the hooks file.");
77
+ return 0;
78
+ }
79
+ export {
80
+ runInstallCmd
81
+ };
82
+ //# sourceMappingURL=install-6MNQWAWD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/install.ts","../src/daemon-client.ts"],"sourcesContent":["import {\n getConfigDir,\n getMachineId,\n createTrialLicense,\n readLicenseSync,\n writeLicenseSync,\n} from \"@token-canary/shared\";\nimport { randomUUID } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getDaemonPort as getPort, startDaemonProcess } from \"../daemon-client.js\";\nimport { runAllChecks } from \"./doctor.js\";\n\nconst HOOKS_FILENAME = \"hooks.json\";\n\nexport async function runInstallCmd(_args: string[]): Promise<number> {\n const checks = await runAllChecks();\n const failed = checks.filter((c) => !c.ok);\n if (failed.length > 0) {\n for (const c of checks) {\n console.log(`${c.ok ? \"✓\" : \"✗\"} ${c.name}: ${c.message}`);\n }\n console.log(\"\\nFix the issues above and run 'tokencanary doctor'.\");\n return 1;\n }\n const configDir = getConfigDir();\n await mkdir(configDir, { recursive: true });\n let license = readLicenseSync();\n if (!license) {\n const installId = randomUUID();\n const machineId = getMachineId();\n license = createTrialLicense(installId, machineId);\n writeLicenseSync(license);\n console.log(\"Trial started: 30 days from today.\");\n }\n const port = getPort();\n const hooksConfig = {\n UserPromptSubmit: `http://127.0.0.1:${port}/hook/UserPromptSubmit`,\n PreToolUse: `http://127.0.0.1:${port}/hook/PreToolUse`,\n };\n const hooksPath = join(configDir, HOOKS_FILENAME);\n await writeFile(hooksPath, JSON.stringify(hooksConfig, null, 2));\n console.log(`Wrote ${hooksPath}`);\n startDaemonProcess();\n console.log(\"Daemon started. Configure Claude Code to use the URLs in the hooks file.\");\n return 0;\n}\n","import { spawn } from \"node:child_process\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_DAEMON_PORT } from \"@token-canary/shared\";\n\n/**\n * Resolve the daemon entry path relative to the running CLI.\n * When published, cli.js and daemon.js live in the same dist/ dir.\n */\nexport function getDaemonEntryPath(): string {\n const cliUrl = import.meta.url;\n const cliPath = fileURLToPath(cliUrl);\n const cliDir = dirname(cliPath);\n return join(cliDir, \"daemon.js\");\n}\n\nexport function startDaemonProcess(): ReturnType<typeof spawn> {\n const entry = getDaemonEntryPath();\n const child = spawn(process.execPath, [entry], {\n stdio: \"ignore\",\n detached: true,\n });\n child.unref();\n child.on(\"error\", (err) => {\n console.error(\"Token Canary daemon failed to start:\", err.message);\n console.error(\"Daemon path:\", entry);\n });\n return child;\n}\n\nexport function getDaemonPort(): number {\n return Number(process.env.TOKENCANARY_PORT) || DEFAULT_DAEMON_PORT;\n}\n"],"mappings":";;;;;;;;;;;;;AAOA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,iBAAiB;AACjC,SAAS,QAAAA,aAAY;;;ACTrB,SAAS,aAAa;AACtB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAOvB,SAAS,qBAA6B;AAC3C,QAAM,SAAS,YAAY;AAC3B,QAAM,UAAU,cAAc,MAAM;AACpC,QAAM,SAAS,QAAQ,OAAO;AAC9B,SAAO,KAAK,QAAQ,WAAW;AACjC;AAEO,SAAS,qBAA+C;AAC7D,QAAM,QAAQ,mBAAmB;AACjC,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,KAAK,GAAG;AAAA,IAC7C,OAAO;AAAA,IACP,UAAU;AAAA,EACZ,CAAC;AACD,QAAM,MAAM;AACZ,QAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,YAAQ,MAAM,wCAAwC,IAAI,OAAO;AACjE,YAAQ,MAAM,gBAAgB,KAAK;AAAA,EACrC,CAAC;AACD,SAAO;AACT;AAEO,SAAS,gBAAwB;AACtC,SAAO,OAAO,QAAQ,IAAI,gBAAgB,KAAK;AACjD;;;ADnBA,IAAM,iBAAiB;AAEvB,eAAsB,cAAc,OAAkC;AACpE,QAAM,SAAS,MAAM,aAAa;AAClC,QAAM,SAAS,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE;AACzC,MAAI,OAAO,SAAS,GAAG;AACrB,eAAW,KAAK,QAAQ;AACtB,cAAQ,IAAI,GAAG,EAAE,KAAK,WAAM,QAAG,IAAI,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,IAC3D;AACA,YAAQ,IAAI,sDAAsD;AAClE,WAAO;AAAA,EACT;AACA,QAAM,YAAY,aAAa;AAC/B,QAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,MAAI,UAAU,gBAAgB;AAC9B,MAAI,CAAC,SAAS;AACZ,UAAM,YAAY,WAAW;AAC7B,UAAM,YAAY,aAAa;AAC/B,cAAU,mBAAmB,WAAW,SAAS;AACjD,qBAAiB,OAAO;AACxB,YAAQ,IAAI,oCAAoC;AAAA,EAClD;AACA,QAAM,OAAO,cAAQ;AACrB,QAAM,cAAc;AAAA,IAClB,kBAAkB,oBAAoB,IAAI;AAAA,IAC1C,YAAY,oBAAoB,IAAI;AAAA,EACtC;AACA,QAAM,YAAYC,MAAK,WAAW,cAAc;AAChD,QAAM,UAAU,WAAW,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AAC/D,UAAQ,IAAI,SAAS,SAAS,EAAE;AAChC,qBAAmB;AACnB,UAAQ,IAAI,0EAA0E;AACtF,SAAO;AACT;","names":["join","join"]}
@@ -0,0 +1,31 @@
1
+ import {
2
+ TRIAL_DAYS,
3
+ getPlanDisplay,
4
+ isLicenseValid,
5
+ readLicenseSync
6
+ } from "./chunk-EIBTXUZ4.js";
7
+
8
+ // src/commands/license.ts
9
+ async function runLicenseCmd(_args) {
10
+ const license = readLicenseSync();
11
+ if (!license) {
12
+ console.log(`No license found. Run 'tokencanary install' to start your ${TRIAL_DAYS}-day trial.`);
13
+ return 0;
14
+ }
15
+ const valid = isLicenseValid(license);
16
+ const expiry = new Date(license.expiry);
17
+ console.log(`Plan: ${getPlanDisplay(license.plan)}`);
18
+ console.log(`Expires: ${expiry.toISOString().slice(0, 10)}`);
19
+ if (license.plan === "trial" && !valid) {
20
+ console.log("\nToken Canary trial expired.");
21
+ console.log("Upgrade to continue optimization: $19/month or $99/year");
22
+ console.log("Run: tokencanary upgrade");
23
+ } else if (valid) {
24
+ console.log("Status: active");
25
+ }
26
+ return 0;
27
+ }
28
+ export {
29
+ runLicenseCmd
30
+ };
31
+ //# sourceMappingURL=license-XSBCIOKK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/license.ts"],"sourcesContent":["import {\n readLicenseSync,\n isLicenseValid,\n getPlanDisplay,\n TRIAL_DAYS,\n} from \"@token-canary/shared\";\n\n/** Show license and trial info */\nexport async function runLicenseCmd(_args: string[]): Promise<number> {\n const license = readLicenseSync();\n if (!license) {\n console.log(`No license found. Run 'tokencanary install' to start your ${TRIAL_DAYS}-day trial.`);\n return 0;\n }\n const valid = isLicenseValid(license);\n const expiry = new Date(license.expiry);\n console.log(`Plan: ${getPlanDisplay(license.plan)}`);\n console.log(`Expires: ${expiry.toISOString().slice(0, 10)}`);\n if (license.plan === \"trial\" && !valid) {\n console.log(\"\\nToken Canary trial expired.\");\n console.log(\"Upgrade to continue optimization: $19/month or $99/year\");\n console.log(\"Run: tokencanary upgrade\");\n } else if (valid) {\n console.log(\"Status: active\");\n }\n return 0;\n}\n"],"mappings":";;;;;;;;AAQA,eAAsB,cAAc,OAAkC;AACpE,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,6DAA6D,UAAU,aAAa;AAChG,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,eAAe,OAAO;AACpC,QAAM,SAAS,IAAI,KAAK,QAAQ,MAAM;AACtC,UAAQ,IAAI,SAAS,eAAe,QAAQ,IAAI,CAAC,EAAE;AACnD,UAAQ,IAAI,YAAY,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE;AAC3D,MAAI,QAAQ,SAAS,WAAW,CAAC,OAAO;AACtC,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ,IAAI,yDAAyD;AACrE,YAAQ,IAAI,0BAA0B;AAAA,EACxC,WAAW,OAAO;AAChB,YAAQ,IAAI,gBAAgB;AAAA,EAC9B;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,40 @@
1
+ import {
2
+ getLogDir
3
+ } from "./chunk-EIBTXUZ4.js";
4
+
5
+ // src/commands/logs.ts
6
+ import { readdir, readFile } from "fs/promises";
7
+ import { join } from "path";
8
+ var MAX_LINES = 100;
9
+ async function runLogsCmd(_args) {
10
+ const logDir = getLogDir();
11
+ try {
12
+ const entries = await readdir(logDir, { withFileTypes: true });
13
+ const files = entries.filter((e) => e.isFile() && e.name.endsWith(".log")).map((e) => ({ name: e.name, path: join(logDir, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
14
+ if (files.length === 0) {
15
+ console.log(`Log directory: ${logDir}`);
16
+ console.log("No log files yet.");
17
+ return 0;
18
+ }
19
+ const latest = files[files.length - 1];
20
+ const content = await readFile(latest.path, "utf8");
21
+ const lines = content.split(/\r?\n/).filter((l) => l.length > 0);
22
+ const tail = lines.slice(-MAX_LINES);
23
+ console.log(`Tail of ${latest.name} (${logDir}):`);
24
+ console.log(tail.join("\n"));
25
+ } catch (err) {
26
+ const msg = err instanceof Error ? err.message : String(err);
27
+ if (msg.includes("ENOENT")) {
28
+ console.log(`Log directory: ${logDir}`);
29
+ console.log("Directory does not exist yet.");
30
+ } else {
31
+ console.error(msg);
32
+ return 1;
33
+ }
34
+ }
35
+ return 0;
36
+ }
37
+ export {
38
+ runLogsCmd
39
+ };
40
+ //# sourceMappingURL=logs-NMLIJXEJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/logs.ts"],"sourcesContent":["import { getLogDir } from \"@token-canary/shared\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst MAX_LINES = 100;\n\n/** Show recent logs from ~/.tokencanary/logs */\nexport async function runLogsCmd(_args: string[]): Promise<number> {\n const logDir = getLogDir();\n try {\n const entries = await readdir(logDir, { withFileTypes: true });\n const files = entries\n .filter((e) => e.isFile() && e.name.endsWith(\".log\"))\n .map((e) => ({ name: e.name, path: join(logDir, e.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n if (files.length === 0) {\n console.log(`Log directory: ${logDir}`);\n console.log(\"No log files yet.\");\n return 0;\n }\n const latest = files[files.length - 1];\n const content = await readFile(latest.path, \"utf8\");\n const lines = content.split(/\\r?\\n/).filter((l) => l.length > 0);\n const tail = lines.slice(-MAX_LINES);\n console.log(`Tail of ${latest.name} (${logDir}):`);\n console.log(tail.join(\"\\n\"));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ENOENT\")) {\n console.log(`Log directory: ${logDir}`);\n console.log(\"Directory does not exist yet.\");\n } else {\n console.error(msg);\n return 1;\n }\n }\n return 0;\n}\n"],"mappings":";;;;;AACA,SAAS,SAAS,gBAAgB;AAClC,SAAS,YAAY;AAErB,IAAM,YAAY;AAGlB,eAAsB,WAAW,OAAkC;AACjE,QAAM,SAAS,UAAU;AACzB,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;AAC7D,UAAM,QAAQ,QACX,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,KAAK,SAAS,MAAM,CAAC,EACnD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,KAAK,QAAQ,EAAE,IAAI,EAAE,EAAE,EACzD,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC9C,QAAI,MAAM,WAAW,GAAG;AACtB,cAAQ,IAAI,kBAAkB,MAAM,EAAE;AACtC,cAAQ,IAAI,mBAAmB;AAC/B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,MAAM,MAAM,SAAS,CAAC;AACrC,UAAM,UAAU,MAAM,SAAS,OAAO,MAAM,MAAM;AAClD,UAAM,QAAQ,QAAQ,MAAM,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/D,UAAM,OAAO,MAAM,MAAM,CAAC,SAAS;AACnC,YAAQ,IAAI,WAAW,OAAO,IAAI,KAAK,MAAM,IAAI;AACjD,YAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,QAAI,IAAI,SAAS,QAAQ,GAAG;AAC1B,cAAQ,IAAI,kBAAkB,MAAM,EAAE;AACtC,cAAQ,IAAI,+BAA+B;AAAA,IAC7C,OAAO;AACL,cAAQ,MAAM,GAAG;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,24 @@
1
+ import {
2
+ getPlanDisplay,
3
+ isLicenseValid,
4
+ readLicenseSync
5
+ } from "./chunk-EIBTXUZ4.js";
6
+
7
+ // src/commands/status.ts
8
+ async function runStatusCmd(_args) {
9
+ const license = readLicenseSync();
10
+ if (!license) {
11
+ console.log("License: none (run 'tokencanary install' to start trial)");
12
+ return 0;
13
+ }
14
+ const valid = isLicenseValid(license);
15
+ const expiry = new Date(license.expiry);
16
+ console.log(`Plan: ${getPlanDisplay(license.plan)}`);
17
+ console.log(`Expiry: ${expiry.toISOString().slice(0, 10)}`);
18
+ console.log(`Optimization: ${valid ? "enabled" : "disabled"}`);
19
+ return 0;
20
+ }
21
+ export {
22
+ runStatusCmd
23
+ };
24
+ //# sourceMappingURL=status-UIDOEWKC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/status.ts"],"sourcesContent":["import { readLicenseSync, isLicenseValid, getPlanDisplay } from \"@token-canary/shared\";\n\n/** Show daemon and license status */\nexport async function runStatusCmd(_args: string[]): Promise<number> {\n const license = readLicenseSync();\n if (!license) {\n console.log(\"License: none (run 'tokencanary install' to start trial)\");\n return 0;\n }\n const valid = isLicenseValid(license);\n const expiry = new Date(license.expiry);\n console.log(`Plan: ${getPlanDisplay(license.plan)}`);\n console.log(`Expiry: ${expiry.toISOString().slice(0, 10)}`);\n console.log(`Optimization: ${valid ? \"enabled\" : \"disabled\"}`);\n return 0;\n}\n"],"mappings":";;;;;;;AAGA,eAAsB,aAAa,OAAkC;AACnE,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,0DAA0D;AACtE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,eAAe,OAAO;AACpC,QAAM,SAAS,IAAI,KAAK,QAAQ,MAAM;AACtC,UAAQ,IAAI,SAAS,eAAe,QAAQ,IAAI,CAAC,EAAE;AACnD,UAAQ,IAAI,WAAW,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE;AAC1D,UAAQ,IAAI,iBAAiB,QAAQ,YAAY,UAAU,EAAE;AAC7D,SAAO;AACT;","names":[]}
@@ -0,0 +1,30 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-EIBTXUZ4.js";
4
+
5
+ // src/commands/uninstall.ts
6
+ import { unlink } from "fs/promises";
7
+ import { join } from "path";
8
+ var HOOKS_FILENAME = "hooks.json";
9
+ async function runUninstallCmd(_args) {
10
+ const configDir = getConfigDir();
11
+ const hooksPath = join(configDir, HOOKS_FILENAME);
12
+ try {
13
+ await unlink(hooksPath);
14
+ console.log(`Removed ${hooksPath}`);
15
+ } catch (err) {
16
+ const msg = err instanceof Error ? err.message : String(err);
17
+ if (msg.includes("ENOENT")) {
18
+ console.log("Hook config was not installed.");
19
+ } else {
20
+ console.error(msg);
21
+ return 1;
22
+ }
23
+ }
24
+ console.log("Daemon: stop any running daemon process manually if needed.");
25
+ return 0;
26
+ }
27
+ export {
28
+ runUninstallCmd
29
+ };
30
+ //# sourceMappingURL=uninstall-HQ2TCX6F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/uninstall.ts"],"sourcesContent":["import { getConfigDir } from \"@token-canary/shared\";\nimport { unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst HOOKS_FILENAME = \"hooks.json\";\n\n/** Remove hook config so Claude Code stops calling the daemon. Daemon exits when process ends. */\nexport async function runUninstallCmd(_args: string[]): Promise<number> {\n const configDir = getConfigDir();\n const hooksPath = join(configDir, HOOKS_FILENAME);\n try {\n await unlink(hooksPath);\n console.log(`Removed ${hooksPath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ENOENT\")) {\n console.log(\"Hook config was not installed.\");\n } else {\n console.error(msg);\n return 1;\n }\n }\n console.log(\"Daemon: stop any running daemon process manually if needed.\");\n return 0;\n}\n"],"mappings":";;;;;AACA,SAAS,cAAc;AACvB,SAAS,YAAY;AAErB,IAAM,iBAAiB;AAGvB,eAAsB,gBAAgB,OAAkC;AACtE,QAAM,YAAY,aAAa;AAC/B,QAAM,YAAY,KAAK,WAAW,cAAc;AAChD,MAAI;AACF,UAAM,OAAO,SAAS;AACtB,YAAQ,IAAI,WAAW,SAAS,EAAE;AAAA,EACpC,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,QAAI,IAAI,SAAS,QAAQ,GAAG;AAC1B,cAAQ,IAAI,gCAAgC;AAAA,IAC9C,OAAO;AACL,cAAQ,MAAM,GAAG;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,UAAQ,IAAI,6DAA6D;AACzE,SAAO;AACT;","names":[]}
@@ -0,0 +1,23 @@
1
+ import {
2
+ getUpgradeUrl
3
+ } from "./chunk-EIBTXUZ4.js";
4
+
5
+ // src/commands/upgrade.ts
6
+ import { exec } from "child_process";
7
+ import { platform } from "os";
8
+ async function runUpgradeCmd(_args) {
9
+ const url = getUpgradeUrl();
10
+ const runner = platform() === "darwin" ? "open" : "xdg-open";
11
+ exec(`${runner} "${url}"`, (err) => {
12
+ if (err) {
13
+ console.error("Could not open browser. Visit:", url);
14
+ }
15
+ });
16
+ console.log("Opening browser to subscribe...");
17
+ console.log("After payment, run: tokencanary license");
18
+ return 0;
19
+ }
20
+ export {
21
+ runUpgradeCmd
22
+ };
23
+ //# sourceMappingURL=upgrade-3WNDWZ7R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/upgrade.ts"],"sourcesContent":["import { getUpgradeUrl } from \"@token-canary/shared\";\nimport { exec } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\n/** Open browser to Stripe Checkout for upgrade */\nexport async function runUpgradeCmd(_args: string[]): Promise<number> {\n const url = getUpgradeUrl();\n const runner = platform() === \"darwin\" ? \"open\" : \"xdg-open\";\n exec(`${runner} \"${url}\"`, (err) => {\n if (err) {\n console.error(\"Could not open browser. Visit:\", url);\n }\n });\n console.log(\"Opening browser to subscribe...\");\n console.log(\"After payment, run: tokencanary license\");\n return 0;\n}\n"],"mappings":";;;;;AACA,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAGzB,eAAsB,cAAc,OAAkC;AACpE,QAAM,MAAM,cAAc;AAC1B,QAAM,SAAS,SAAS,MAAM,WAAW,SAAS;AAClD,OAAK,GAAG,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ;AAClC,QAAI,KAAK;AACP,cAAQ,MAAM,kCAAkC,GAAG;AAAA,IACrD;AAAA,EACF,CAAC;AACD,UAAQ,IAAI,iCAAiC;AAC7C,UAAQ,IAAI,yCAAyC;AACrD,SAAO;AACT;","names":[]}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "tokencanary",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "CLI and daemon that reduces Claude Code token usage via hooks and local analysis",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/AlexWebCanaries/token-canary.git"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "tokencanary": "dist/cli.js"
14
+ },
15
+ "main": "dist/cli.js",
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ }
24
+ }