@tickrmeter/ai-usage 0.1.0 → 0.1.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 +1 -0
- package/dist/claude.js +15 -2
- package/dist/cli.js +7 -2
- package/dist/scheduler.js +29 -32
- package/dist/storage.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Privacy-preserving personal subscription monitoring for Codex / ChatGPT and Clau
|
|
|
15
15
|
4. The helper detects the Codex desktop app, Codex CLI, and Claude Code, uploads normalized limit metadata, and installs a user-scoped five-minute sync using Task Scheduler, launchd, or a systemd user timer (cron fallback).
|
|
16
16
|
|
|
17
17
|
The initial `npx` command installs a fixed copy under `~/.tickrmeter-ai/app`. Scheduled runs invoke that fixed version, never `npx ...@latest`.
|
|
18
|
+
On Windows, Task Scheduler invokes that installed Node CLI directly. The helper does not install or execute hidden VBS, WScript, or PowerShell wrappers.
|
|
18
19
|
|
|
19
20
|
## Commands
|
|
20
21
|
|
package/dist/claude.js
CHANGED
|
@@ -41,7 +41,19 @@ const finitePercent = (value) => typeof value === "number" && Number.isFinite(va
|
|
|
41
41
|
const resetIso = (value) => {
|
|
42
42
|
if (typeof value !== "string" && typeof value !== "number")
|
|
43
43
|
return undefined;
|
|
44
|
-
const
|
|
44
|
+
const normalized = typeof value === "string" ? value.trim() : value;
|
|
45
|
+
if (normalized === "")
|
|
46
|
+
return undefined;
|
|
47
|
+
const numeric = typeof normalized === "number" ? normalized : Number(normalized);
|
|
48
|
+
const isNumericTimestamp = Number.isFinite(numeric) && /^\d+(\.\d+)?$/.test(String(normalized));
|
|
49
|
+
let date = isNumericTimestamp
|
|
50
|
+
? new Date(numeric < 10_000_000_000 ? numeric * 1000 : numeric)
|
|
51
|
+
: new Date(normalized);
|
|
52
|
+
// Versions before 0.1.1 interpreted Unix seconds as milliseconds and
|
|
53
|
+
// persisted a 1970 ISO date. Recover that timestamp during the next sync.
|
|
54
|
+
if (!isNumericTimestamp && date.getTime() >= 1_000_000_000 && date.getTime() < 10_000_000_000) {
|
|
55
|
+
date = new Date(date.getTime() * 1000);
|
|
56
|
+
}
|
|
45
57
|
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
46
58
|
};
|
|
47
59
|
const extractClaudeSnapshot = (input, capturedAt = new Date()) => {
|
|
@@ -80,7 +92,8 @@ const readClaudeCapture = () => {
|
|
|
80
92
|
const snapshot = (0, storage_1.readJson)((0, storage_1.getClaudeCapturePath)());
|
|
81
93
|
if (!snapshot || snapshot.source !== "claude" || Number.isNaN(Date.parse(snapshot.capturedAt)))
|
|
82
94
|
return null;
|
|
83
|
-
|
|
95
|
+
const windows = snapshot.windows?.map((window) => ({ ...window, resetsAt: resetIso(window.resetsAt) }));
|
|
96
|
+
return { ...snapshot, windows, stale: Date.now() - Date.parse(snapshot.capturedAt) > 60 * 60 * 1000 };
|
|
84
97
|
};
|
|
85
98
|
exports.readClaudeCapture = readClaudeCapture;
|
|
86
99
|
const quote = (value) => `"${value.replace(/"/g, '\\"')}"`;
|
package/dist/cli.js
CHANGED
|
@@ -65,8 +65,13 @@ const connect = async (code, server) => {
|
|
|
65
65
|
console.log("No provider snapshot was available yet. Run tickrmeter-ai doctor for setup help.");
|
|
66
66
|
};
|
|
67
67
|
const sync = async () => {
|
|
68
|
-
if ((0, storage_1.readConfig)())
|
|
69
|
-
(0, storage_1.installFixedCliCopy)();
|
|
68
|
+
if ((0, storage_1.readConfig)()) {
|
|
69
|
+
const installedCliPath = (0, storage_1.installFixedCliCopy)();
|
|
70
|
+
if (installedCliPath)
|
|
71
|
+
(0, scheduler_1.installScheduler)();
|
|
72
|
+
else
|
|
73
|
+
(0, scheduler_1.ensureScheduler)();
|
|
74
|
+
}
|
|
70
75
|
const report = await (0, sync_1.syncPersonalUsage)();
|
|
71
76
|
console.log(report.uploaded.length ? `Uploaded: ${report.uploaded.join(", ")}` : "No snapshots available to upload.");
|
|
72
77
|
report.providers.filter((provider) => !provider.ok).forEach(printProvider);
|
package/dist/scheduler.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.uninstallScheduler = exports.installScheduler = exports.buildCronLine = exports.buildSystemdTimer = exports.buildSystemdService = exports.buildLaunchAgentPlist = exports.buildWindowsTaskArgs = exports.
|
|
6
|
+
exports.uninstallScheduler = exports.ensureScheduler = exports.installScheduler = exports.buildCronLine = exports.buildSystemdTimer = exports.buildSystemdService = exports.buildLaunchAgentPlist = exports.buildWindowsTaskArgs = exports.getFixedSyncCommand = exports.resolveNodePath = exports.SCHEDULER_VERSION = exports.CRON_MARKER = exports.SYSTEMD_NAME = exports.LAUNCH_AGENT_LABEL = exports.WINDOWS_TASK_NAME = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -13,29 +13,21 @@ exports.WINDOWS_TASK_NAME = "TickrMeter AI Usage Sync";
|
|
|
13
13
|
exports.LAUNCH_AGENT_LABEL = "com.tickrmeter.ai-usage";
|
|
14
14
|
exports.SYSTEMD_NAME = "tickrmeter-ai-usage";
|
|
15
15
|
exports.CRON_MARKER = "# TickrMeter AI Usage Sync";
|
|
16
|
+
exports.SCHEDULER_VERSION = 3;
|
|
16
17
|
const quoteShell = (value) => `"${value.replace(/"/g, '\\"')}"`;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'Set shell = CreateObject("WScript.Shell")',
|
|
25
|
-
`command = ${quoteVbsString(command)}`,
|
|
26
|
-
"exitCode = shell.Run(command, 0, True)",
|
|
27
|
-
"WScript.Quit exitCode",
|
|
28
|
-
"",
|
|
29
|
-
].join("\r\n");
|
|
30
|
-
};
|
|
31
|
-
exports.buildWindowsSyncScript = buildWindowsSyncScript;
|
|
32
|
-
const getWindowsTaskCommand = (scriptPath = windowsSyncScriptPath()) => {
|
|
33
|
-
const windowsDirectory = process.env.SystemRoot || "C:\\Windows";
|
|
34
|
-
const wscriptPath = path_1.default.join(windowsDirectory, "System32", "wscript.exe");
|
|
35
|
-
return `${quoteShell(wscriptPath)} //B //NoLogo ${quoteShell(scriptPath)}`;
|
|
18
|
+
const resolveNodePath = (nodePath = process.execPath) => {
|
|
19
|
+
try {
|
|
20
|
+
return fs_1.default.realpathSync(nodePath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return nodePath;
|
|
24
|
+
}
|
|
36
25
|
};
|
|
37
|
-
exports.
|
|
38
|
-
const
|
|
26
|
+
exports.resolveNodePath = resolveNodePath;
|
|
27
|
+
const getFixedSyncCommand = (nodePath = (0, exports.resolveNodePath)(), cliPath = (0, storage_1.getInstalledCliPath)()) => `${quoteShell(nodePath)} ${quoteShell(cliPath)} sync`;
|
|
28
|
+
exports.getFixedSyncCommand = getFixedSyncCommand;
|
|
29
|
+
const legacyWindowsSyncScriptPath = () => path_1.default.join((0, storage_1.getDataDir)(), "sync-hidden.vbs");
|
|
30
|
+
const buildWindowsTaskArgs = (command = (0, exports.getFixedSyncCommand)()) => [
|
|
39
31
|
"/Create",
|
|
40
32
|
"/TN",
|
|
41
33
|
exports.WINDOWS_TASK_NAME,
|
|
@@ -48,7 +40,7 @@ const buildWindowsTaskArgs = (command = (0, exports.getWindowsTaskCommand)()) =>
|
|
|
48
40
|
"/F",
|
|
49
41
|
];
|
|
50
42
|
exports.buildWindowsTaskArgs = buildWindowsTaskArgs;
|
|
51
|
-
const buildLaunchAgentPlist = (nodePath =
|
|
43
|
+
const buildLaunchAgentPlist = (nodePath = (0, exports.resolveNodePath)(), cliPath = (0, storage_1.getInstalledCliPath)()) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
52
44
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
53
45
|
<plist version="1.0"><dict>
|
|
54
46
|
<key>Label</key><string>${exports.LAUNCH_AGENT_LABEL}</string>
|
|
@@ -58,7 +50,7 @@ const buildLaunchAgentPlist = (nodePath = process.execPath, cliPath = (0, storag
|
|
|
58
50
|
</dict></plist>
|
|
59
51
|
`;
|
|
60
52
|
exports.buildLaunchAgentPlist = buildLaunchAgentPlist;
|
|
61
|
-
const buildSystemdService = (nodePath =
|
|
53
|
+
const buildSystemdService = (nodePath = (0, exports.resolveNodePath)(), cliPath = (0, storage_1.getInstalledCliPath)()) => `[Unit]
|
|
62
54
|
Description=TickrMeter AI Usage sync
|
|
63
55
|
|
|
64
56
|
[Service]
|
|
@@ -108,13 +100,10 @@ const installCron = (runner = run) => {
|
|
|
108
100
|
const installScheduler = (platform = process.platform, runner = run) => {
|
|
109
101
|
let kind;
|
|
110
102
|
if (platform === "win32") {
|
|
111
|
-
const
|
|
112
|
-
(
|
|
113
|
-
const result = runner("schtasks", (0, exports.buildWindowsTaskArgs)((0, exports.getWindowsTaskCommand)(scriptPath)));
|
|
114
|
-
if (result.status !== 0) {
|
|
115
|
-
fs_1.default.rmSync(scriptPath, { force: true });
|
|
103
|
+
const result = runner("schtasks", (0, exports.buildWindowsTaskArgs)());
|
|
104
|
+
if (result.status !== 0)
|
|
116
105
|
throw new Error("AI_USAGE_SCHEDULER_INSTALL_FAILED");
|
|
117
|
-
}
|
|
106
|
+
fs_1.default.rmSync(legacyWindowsSyncScriptPath(), { force: true });
|
|
118
107
|
kind = "windows-task";
|
|
119
108
|
}
|
|
120
109
|
else if (platform === "darwin") {
|
|
@@ -141,17 +130,25 @@ const installScheduler = (platform = process.platform, runner = run) => {
|
|
|
141
130
|
}
|
|
142
131
|
}
|
|
143
132
|
const state = (0, storage_1.readState)();
|
|
144
|
-
(0, storage_1.writeState)({ ...state, scheduler: { platform, kind, installedAt: new Date().toISOString() } });
|
|
133
|
+
(0, storage_1.writeState)({ ...state, scheduler: { platform, kind, installedAt: new Date().toISOString(), version: exports.SCHEDULER_VERSION } });
|
|
145
134
|
return { kind };
|
|
146
135
|
};
|
|
147
136
|
exports.installScheduler = installScheduler;
|
|
137
|
+
const ensureScheduler = (platform = process.platform, runner = run) => {
|
|
138
|
+
const current = (0, storage_1.readState)().scheduler;
|
|
139
|
+
if (current?.version === exports.SCHEDULER_VERSION)
|
|
140
|
+
return { kind: current.kind, migrated: false };
|
|
141
|
+
const result = (0, exports.installScheduler)(platform, runner);
|
|
142
|
+
return { ...result, migrated: Boolean(current) };
|
|
143
|
+
};
|
|
144
|
+
exports.ensureScheduler = ensureScheduler;
|
|
148
145
|
const uninstallScheduler = (runner = run) => {
|
|
149
146
|
const state = (0, storage_1.readState)();
|
|
150
147
|
const kind = state.scheduler?.kind;
|
|
151
148
|
if (kind === "windows-task") {
|
|
152
149
|
runner("schtasks", ["/Delete", "/TN", exports.WINDOWS_TASK_NAME, "/F"]);
|
|
153
|
-
fs_1.default.rmSync(windowsSyncScriptPath(), { force: true });
|
|
154
150
|
}
|
|
151
|
+
fs_1.default.rmSync(legacyWindowsSyncScriptPath(), { force: true });
|
|
155
152
|
if (kind === "launch-agent") {
|
|
156
153
|
runner("launchctl", ["unload", launchAgentPath()]);
|
|
157
154
|
fs_1.default.rmSync(launchAgentPath(), { force: true });
|
package/dist/storage.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.removeLocalBridgeCredential = exports.installFixedCliCopy = exports.writ
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
exports.CLI_VERSION = "0.1.
|
|
10
|
+
exports.CLI_VERSION = "0.1.2";
|
|
11
11
|
const getDataDir = () => process.env.TICKRMETER_AI_HOME || path_1.default.join(os_1.default.homedir(), ".tickrmeter-ai");
|
|
12
12
|
exports.getDataDir = getDataDir;
|
|
13
13
|
const getConfigPath = () => path_1.default.join((0, exports.getDataDir)(), "config.json");
|