@vibescore/tracker 0.1.2 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibescore/tracker",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Codex CLI token usage tracker (macOS-first, notify-driven).",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
package/src/cli.js CHANGED
@@ -48,6 +48,7 @@ function printHelp() {
48
48
  '',
49
49
  'Notes:',
50
50
  ' - init installs a Codex notify hook and issues a device token (default: browser sign in/up).',
51
+ ' - optional: pass --link-code <code> to skip browser login when provided by Dashboard.',
51
52
  ' - when ~/.code/config.toml exists, init also installs an Every Code notify hook.',
52
53
  ' - optional: set VIBESCORE_DASHBOARD_URL (or --dashboard-url) to use a hosted landing page.',
53
54
  ' - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl (Every Code), then uploads token_count deltas.',
@@ -3,6 +3,7 @@ const path = require('node:path');
3
3
  const fs = require('node:fs/promises');
4
4
  const fssync = require('node:fs');
5
5
  const cp = require('node:child_process');
6
+ const crypto = require('node:crypto');
6
7
 
7
8
  const { ensureDir, writeFileAtomic, readJson, writeJson, chmod600IfPossible } = require('../lib/fs');
8
9
  const { prompt, promptHidden } = require('../lib/prompt');
@@ -14,7 +15,11 @@ const {
14
15
  } = require('../lib/codex-config');
15
16
  const { upsertClaudeHook, buildClaudeHookCommand } = require('../lib/claude-config');
16
17
  const { beginBrowserAuth } = require('../lib/browser-auth');
17
- const { issueDeviceTokenWithPassword, issueDeviceTokenWithAccessToken } = require('../lib/insforge');
18
+ const {
19
+ issueDeviceTokenWithPassword,
20
+ issueDeviceTokenWithAccessToken,
21
+ issueDeviceTokenWithLinkCode
22
+ } = require('../lib/insforge');
18
23
 
19
24
  async function cmdInit(argv) {
20
25
  const opts = parseArgs(argv);
@@ -29,6 +34,7 @@ async function cmdInit(argv) {
29
34
 
30
35
  const configPath = path.join(trackerDir, 'config.json');
31
36
  const notifyOriginalPath = path.join(trackerDir, 'codex_notify_original.json');
37
+ const linkCodeStatePath = path.join(trackerDir, 'link_code_state.json');
32
38
 
33
39
  const baseUrl = opts.baseUrl || process.env.VIBESCORE_INSFORGE_BASE_URL || 'https://5tmappuk.us-east.insforge.app';
34
40
  let dashboardUrl = opts.dashboardUrl || process.env.VIBESCORE_DASHBOARD_URL || null;
@@ -44,7 +50,36 @@ async function cmdInit(argv) {
44
50
 
45
51
  await installLocalTrackerApp({ appDir });
46
52
 
47
- if (!deviceToken && !opts.noAuth) {
53
+ if (!deviceToken && opts.linkCode) {
54
+ const deviceName = opts.deviceName || os.hostname();
55
+ const platform = normalizePlatform(process.platform);
56
+ const linkCode = String(opts.linkCode);
57
+ const linkCodeHash = crypto.createHash('sha256').update(linkCode).digest('hex');
58
+ const existingLinkState = await readJson(linkCodeStatePath);
59
+ let requestId =
60
+ existingLinkState?.linkCodeHash === linkCodeHash && existingLinkState?.requestId
61
+ ? existingLinkState.requestId
62
+ : null;
63
+ if (!requestId) {
64
+ requestId = crypto.randomUUID();
65
+ await writeJson(linkCodeStatePath, {
66
+ linkCodeHash,
67
+ requestId,
68
+ createdAt: new Date().toISOString()
69
+ });
70
+ await chmod600IfPossible(linkCodeStatePath);
71
+ }
72
+ const issued = await issueDeviceTokenWithLinkCode({
73
+ baseUrl,
74
+ linkCode,
75
+ requestId,
76
+ deviceName,
77
+ platform
78
+ });
79
+ deviceToken = issued.token;
80
+ deviceId = issued.deviceId;
81
+ await fs.rm(linkCodeStatePath, { force: true });
82
+ } else if (!deviceToken && !opts.noAuth) {
48
83
  const deviceName = opts.deviceName || os.hostname();
49
84
 
50
85
  if (opts.email || opts.password) {
@@ -171,6 +206,7 @@ function parseArgs(argv) {
171
206
  email: null,
172
207
  password: null,
173
208
  deviceName: null,
209
+ linkCode: null,
174
210
  noAuth: false,
175
211
  noOpen: false
176
212
  };
@@ -182,6 +218,7 @@ function parseArgs(argv) {
182
218
  else if (a === '--email') out.email = argv[++i] || null;
183
219
  else if (a === '--password') out.password = argv[++i] || null;
184
220
  else if (a === '--device-name') out.deviceName = argv[++i] || null;
221
+ else if (a === '--link-code') out.linkCode = argv[++i] || null;
185
222
  else if (a === '--no-auth') out.noAuth = true;
186
223
  else if (a === '--no-open') out.noOpen = true;
187
224
  else throw new Error(`Unknown option: ${a}`);
@@ -194,6 +231,13 @@ function maskSecret(s) {
194
231
  return `${s.slice(0, 4)}…${s.slice(-4)}`;
195
232
  }
196
233
 
234
+ function normalizePlatform(value) {
235
+ if (value === 'darwin') return 'macos';
236
+ if (value === 'win32') return 'windows';
237
+ if (value === 'linux') return 'linux';
238
+ return 'unknown';
239
+ }
240
+
197
241
  function buildNotifyHandler({ trackerDir, packageName }) {
198
242
  // Keep this file dependency-free: Node built-ins only.
199
243
  // It must never block Codex; it spawns sync in the background and exits 0.
@@ -377,7 +421,7 @@ async function installLocalTrackerApp({ appDir }) {
377
421
 
378
422
  function spawnInitSync({ trackerBinPath, packageName }) {
379
423
  const fallbackPkg = packageName || '@vibescore/tracker';
380
- const argv = ['sync'];
424
+ const argv = ['sync', '--drain'];
381
425
  const hasLocalRuntime = typeof trackerBinPath === 'string' && fssync.existsSync(trackerBinPath);
382
426
  const cmd = hasLocalRuntime
383
427
  ? [process.execPath, trackerBinPath, ...argv]
@@ -1,4 +1,4 @@
1
- const { issueDeviceToken, signInWithPassword } = require('./vibescore-api');
1
+ const { exchangeLinkCode, issueDeviceToken, signInWithPassword } = require('./vibescore-api');
2
2
 
3
3
  async function issueDeviceTokenWithPassword({ baseUrl, email, password, deviceName }) {
4
4
  const accessToken = await signInWithPassword({ baseUrl, email, password });
@@ -11,7 +11,13 @@ async function issueDeviceTokenWithAccessToken({ baseUrl, accessToken, deviceNam
11
11
  return issued;
12
12
  }
13
13
 
14
+ async function issueDeviceTokenWithLinkCode({ baseUrl, linkCode, requestId, deviceName, platform }) {
15
+ const issued = await exchangeLinkCode({ baseUrl, linkCode, requestId, deviceName, platform });
16
+ return { token: issued.token, deviceId: issued.deviceId };
17
+ }
18
+
14
19
  module.exports = {
15
20
  issueDeviceTokenWithPassword,
16
- issueDeviceTokenWithAccessToken
21
+ issueDeviceTokenWithAccessToken,
22
+ issueDeviceTokenWithLinkCode
17
23
  };
@@ -35,6 +35,33 @@ async function issueDeviceToken({ baseUrl, accessToken, deviceName, platform = '
35
35
  return { token, deviceId };
36
36
  }
37
37
 
38
+ async function exchangeLinkCode({ baseUrl, linkCode, requestId, deviceName, platform = 'macos' }) {
39
+ const data = await invokeFunction({
40
+ baseUrl,
41
+ accessToken: null,
42
+ slug: 'vibescore-link-code-exchange',
43
+ method: 'POST',
44
+ body: {
45
+ link_code: linkCode,
46
+ request_id: requestId,
47
+ device_name: deviceName,
48
+ platform
49
+ },
50
+ errorPrefix: 'Link code exchange failed'
51
+ });
52
+
53
+ const token = data?.token;
54
+ const deviceId = data?.device_id;
55
+ if (typeof token !== 'string' || token.length === 0) {
56
+ throw new Error('Link code exchange failed: missing token');
57
+ }
58
+ if (typeof deviceId !== 'string' || deviceId.length === 0) {
59
+ throw new Error('Link code exchange failed: missing device_id');
60
+ }
61
+
62
+ return { token, deviceId, userId: data?.user_id || null };
63
+ }
64
+
38
65
  async function ingestHourly({ baseUrl, deviceToken, hourly }) {
39
66
  const data = await invokeFunctionWithRetry({
40
67
  baseUrl,
@@ -72,6 +99,7 @@ async function syncHeartbeat({ baseUrl, deviceToken }) {
72
99
  module.exports = {
73
100
  signInWithPassword,
74
101
  issueDeviceToken,
102
+ exchangeLinkCode,
75
103
  ingestHourly,
76
104
  syncHeartbeat
77
105
  };