@optimuslabs/harness-map-staging 1.5.25 → 1.5.26

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.
@@ -0,0 +1,173 @@
1
+ import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync, readSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ /**
5
+ * Every org Claude Desktop's signed-in account has used, so both tenants are recorded when one
6
+ * login belongs to a team org and a personal org and switches between them in Desktop.
7
+ *
8
+ * Desktop's "two accounts" in that case are one account (one account UUID, one email) in two
9
+ * orgs, and ~/.claude.json's `oauthAccount` only ever names the active org. Desktop keeps a session
10
+ * directory per org it has used, under the account:
11
+ * ~/Library/Application Support/Claude/claude-code-sessions/<accountUuid>/<orgUuid>/
12
+ * ~/Library/Application Support/Claude/local-agent-mode-sessions/<accountUuid>/<orgUuid>/
13
+ *
14
+ * The folders are only read when one account has ever used Desktop on this Mac. Switching between
15
+ * separate Desktop logins leaves folders (and token-log entries) pairing each account with the
16
+ * other's orgs, so with more than one account they cannot say which org is whose. Then only the org
17
+ * Desktop is signed into now is reported: the newest `[oauth-v2] … orgId=` line of Desktop's own
18
+ * main.log, which is the login in use and cannot belong to another account.
19
+ *
20
+ * Reads `lastKnownAccountUuid` from config.json and nothing else from it -- the same file holds
21
+ * Desktop's encrypted token caches, which are never touched or uploaded. The email, and the active
22
+ * org's name and plan, come from ~/.claude.json only when it is the same account (and, for the name
23
+ * and plan, the same org): Desktop's own files name neither. macOS only.
24
+ */
25
+ const FILE_TYPE = 'claude_desktop_orgs';
26
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27
+ const SESSION_ROOTS = ['claude-code-sessions', 'local-agent-mode-sessions'];
28
+ /** Only the end of Desktop's main.log is read: the newest sign-in is all that matters. */
29
+ const DESKTOP_LOG_TAIL_BYTES = 256 * 1024;
30
+ const DESKTOP_ORG_LINE = /\[oauth-v2\].*?orgId=([0-9a-f-]{36})/i;
31
+ const DESKTOP_ACCOUNT = /cacheKey=acct:([0-9a-f-]{36})/i;
32
+ function readTail(path, bytes) {
33
+ let fd;
34
+ try {
35
+ fd = openSync(path, 'r');
36
+ const size = fstatSync(fd).size;
37
+ const length = Math.min(size, bytes);
38
+ const buffer = Buffer.alloc(length);
39
+ readSync(fd, buffer, 0, length, size - length);
40
+ return buffer.toString('utf8');
41
+ }
42
+ finally {
43
+ if (fd !== undefined)
44
+ closeSync(fd);
45
+ }
46
+ }
47
+ /**
48
+ * The org (and, when logged, account) Claude Desktop is signed into now, or null when unknown.
49
+ *
50
+ * Desktop keeps its own login, apart from Claude Code's ~/.claude.json, and logs every token lookup
51
+ * as `[oauth-v2] … orgId=<org>` (with `cacheKey=acct:<account>|…` on lookups): the newest such line
52
+ * is the org Desktop is using.
53
+ */
54
+ export function readDesktopActiveLogin(home = homedir()) {
55
+ const logPath = join(home, 'Library', 'Logs', 'Claude', 'main.log');
56
+ let tail = '';
57
+ try {
58
+ if (existsSync(logPath))
59
+ tail = readTail(logPath, DESKTOP_LOG_TAIL_BYTES);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ const lines = tail.split('\n').filter((line) => DESKTOP_ORG_LINE.test(line));
65
+ const newest = lines.length ? lines[lines.length - 1] : '';
66
+ const org = newest.match(DESKTOP_ORG_LINE)?.[1]?.toLowerCase() ?? '';
67
+ if (!org)
68
+ return null;
69
+ return { org, account: newest.match(DESKTOP_ACCOUNT)?.[1]?.toLowerCase() ?? '' };
70
+ }
71
+ function claudeSupportDir(home) {
72
+ return join(home, 'Library', 'Application Support', 'Claude');
73
+ }
74
+ function readJson(path) {
75
+ try {
76
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
77
+ return parsed && typeof parsed === 'object' ? parsed : null;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ function text(value) {
84
+ return typeof value === 'string' ? value.trim() : '';
85
+ }
86
+ /** The lowercased UUID-named directories in `dir`, or [] when it does not exist. */
87
+ function uuidDirs(dir) {
88
+ try {
89
+ return readdirSync(dir, { withFileTypes: true })
90
+ .filter((entry) => entry.isDirectory() && UUID.test(entry.name))
91
+ .map((entry) => entry.name.toLowerCase());
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ }
97
+ /** Every account with a session directory, in any session root. */
98
+ function accountsWithSessions(supportDir) {
99
+ return new Set(SESSION_ROOTS.flatMap((root) => uuidDirs(join(supportDir, root))));
100
+ }
101
+ /** The org UUIDs with a session directory under this account, in any session root. */
102
+ function orgsForAccount(supportDir, account) {
103
+ return [...new Set(SESSION_ROOTS.flatMap((root) => uuidDirs(join(supportDir, root, account))))].sort();
104
+ }
105
+ /** The orgs to report: every one with a single account on this Mac, else the active one. */
106
+ function orgsToReport(supportDir, account, active) {
107
+ const accounts = accountsWithSessions(supportDir);
108
+ const orgs = new Set(accounts.size === 1 && accounts.has(account) ? orgsForAccount(supportDir, account) : []);
109
+ if (active)
110
+ orgs.add(active.org);
111
+ return [...orgs].sort();
112
+ }
113
+ /** The email, and that org's name and plan, only when ~/.claude.json is signed into the same account. */
114
+ function claudeJsonDetails(home, account, orgUuids) {
115
+ const details = { email: '', orgNames: {}, orgPlans: {} };
116
+ const claudeJson = readJson(join(home, '.claude.json'));
117
+ const oauth = claudeJson?.oauthAccount;
118
+ if (!oauth || text(oauth.accountUuid).toLowerCase() !== account)
119
+ return details;
120
+ details.email = text(oauth.emailAddress);
121
+ const org = text(oauth.organizationUuid).toLowerCase();
122
+ if (!org || !orgUuids.includes(org))
123
+ return details;
124
+ const name = text(oauth.organizationName);
125
+ if (name)
126
+ details.orgNames[org] = name;
127
+ const plan = claudeCodePlan(claudeJson, oauth);
128
+ if (plan)
129
+ details.orgPlans[org] = plan;
130
+ return details;
131
+ }
132
+ /** Raw, in the order the backend reads Claude Code's plan, so both normalise alike. */
133
+ function claudeCodePlan(claudeJson, oauth) {
134
+ return (text(claudeJson?.subscriptionType) ||
135
+ text(oauth.subscriptionType) ||
136
+ text(oauth.organizationType) ||
137
+ text(oauth.seatTier));
138
+ }
139
+ /**
140
+ * One aggregate naming the orgs the signed-in Desktop account has used -- every one with a single
141
+ * account on this Mac, else the active one -- or [] when this isn't macOS or none is known.
142
+ */
143
+ function collectClaudeDesktopOrgs(home = homedir()) {
144
+ if (process.platform !== 'darwin')
145
+ return [];
146
+ const supportDir = claudeSupportDir(home);
147
+ const configPath = join(supportDir, 'config.json');
148
+ if (!existsSync(configPath))
149
+ return [];
150
+ const active = readDesktopActiveLogin(home);
151
+ const account = (active?.account || text(readJson(configPath)?.lastKnownAccountUuid)).toLowerCase();
152
+ if (!UUID.test(account))
153
+ return [];
154
+ const orgUuids = orgsToReport(supportDir, account, active);
155
+ if (orgUuids.length === 0)
156
+ return [];
157
+ const { email, orgNames, orgPlans } = claudeJsonDetails(home, account, orgUuids);
158
+ return [
159
+ {
160
+ file_type: FILE_TYPE,
161
+ file_path: configPath,
162
+ raw_content: {
163
+ account_uuid: account,
164
+ email,
165
+ org_uuids: orgUuids,
166
+ org_names: orgNames,
167
+ org_plans: orgPlans,
168
+ active_org_uuid: active?.org ?? '',
169
+ },
170
+ },
171
+ ];
172
+ }
173
+ export { collectClaudeDesktopOrgs, FILE_TYPE as CLAUDE_DESKTOP_ORGS_FILE_TYPE };
@@ -339,4 +339,4 @@ function collectCodexTokenUsage(home = homedir()) {
339
339
  },
340
340
  ];
341
341
  }
342
- export { collectCodexTokenUsage, TOKEN_USAGE_RECENCY_DAYS, TOKEN_USAGE_FILE_TYPE };
342
+ export { collectCodexTokenUsage, readCodexIdentity, TOKEN_USAGE_RECENCY_DAYS, TOKEN_USAGE_FILE_TYPE };
@@ -236,6 +236,7 @@ export { collectHermesTokenUsage } from './hermes_token_usage_collector.js';
236
236
  export { collectOpenclawTokenUsage } from './openclaw_token_usage_collector.js';
237
237
  export { collectPiTokenUsage } from './pi_token_usage_collector.js';
238
238
  export { collectCoworkDesktopVersion } from './cowork_desktop_version_collector.js';
239
+ export { collectClaudeDesktopOrgs } from './claude_desktop_orgs_collector.js';
239
240
  export { collectGrokCliVersion } from './grok_cli_version_collector.js';
240
241
  export { collectClaudeDesktopExtensionManifests, collectClaudeDesktopExtensionSettingsFiles, enrichClaudeDesktopExtensionsInstallationsUpload, mergeClaudeDesktopExtensionEnableSettings, } from './claude_desktop_extensions_collector.js';
241
242
  export { collectCursorProjectWorkspaceMcpConfigs } from './cursor_project_mcp_collector.js';
@@ -43,6 +43,38 @@ function querySqlite(sqlite3, dbPath, sql) {
43
43
  return null;
44
44
  }
45
45
  }
46
+ /** ItemTable row holding Cursor's persisted app state, including the login's teams. */
47
+ const PERSISTENT_STORAGE_KEY = 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser';
48
+ /**
49
+ * The Cursor teams the signed-in login belongs to, as strings, or null when they cannot be read.
50
+ *
51
+ * `aiSettings.teamIds` is the list Cursor's own `AllowedTeamId` policy checks. An empty list is a
52
+ * login on no team (a personal account) and is returned as []; only an unreadable or missing value
53
+ * is null, so the backend can tell "personal" from "unknown". Only that one field is extracted --
54
+ * the row is large and holds unrelated state.
55
+ */
56
+ function readCursorTeamIds(sqlite3, dbPath) {
57
+ const raw = querySqlite(sqlite3, dbPath, `SELECT json_extract(value, '$.aiSettings.teamIds') FROM ItemTable WHERE key = '${PERSISTENT_STORAGE_KEY}';`);
58
+ if (raw === null)
59
+ return null;
60
+ try {
61
+ const parsed = JSON.parse(raw.trim());
62
+ if (!Array.isArray(parsed))
63
+ return null;
64
+ return parsed
65
+ .filter((t) => typeof t === 'number' || typeof t === 'string')
66
+ .map((t) => String(t).trim())
67
+ .filter((t) => t.length > 0);
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** The email Cursor's signed-in login uses, or '' when there is none or it cannot be read. */
74
+ function readCursorEmail(sqlite3, dbPath) {
75
+ const raw = querySqlite(sqlite3, dbPath, `SELECT value FROM ItemTable WHERE key = 'cursorAuth/cachedEmail';`);
76
+ return (raw ?? '').trim();
77
+ }
46
78
  function num(value) {
47
79
  if (typeof value === 'number' && Number.isFinite(value))
48
80
  return value;
@@ -52,19 +84,44 @@ function num(value) {
52
84
  }
53
85
  return 0;
54
86
  }
55
- /**
56
- * Aggregate Cursor token usage / sessions / version / identity from state.vscdb.
57
- * Returns a single-element array (one per-machine aggregate) or [] when no usable DB exists.
58
- */
59
- function collectCursorTokenUsage(home = homedir()) {
60
- const dbPath = vscdbPath(home);
61
- if (!existsSync(dbPath))
62
- return [];
63
- const cutoffMs = Date.now() - TOKEN_USAGE_RECENCY_DAYS * 24 * 60 * 60 * 1000;
64
- const sqlite3 = resolveSqlite3Binary();
65
- if (!sqlite3)
66
- return [];
67
- // Identity: version, email, plan, subscription status, sign-up type, display name.
87
+ /** The `key<FIELD_SEP>value` rows of a query's output, split; rows without a separator are skipped. */
88
+ function splitRows(raw) {
89
+ const rows = [];
90
+ for (const row of (raw ?? '').split(ROW_SEP)) {
91
+ const trimmed = row.trim();
92
+ const sep = trimmed.indexOf(FIELD_SEP);
93
+ if (sep > -1)
94
+ rows.push([trimmed.slice(0, sep), trimmed.slice(sep + 1)]);
95
+ }
96
+ return rows;
97
+ }
98
+ /** ItemTable key → the identity field it fills (the scoped profile is handled on its own). */
99
+ const IDENTITY_KEYS = {
100
+ 'cursor.startupMetrics.lastVersion': 'version',
101
+ 'cursorAuth/cachedEmail': 'email',
102
+ 'cursorAuth/stripeMembershipType': 'plan',
103
+ 'cursorAuth/stripeSubscriptionStatus': 'subscriptionStatus',
104
+ 'cursorAuth/cachedSignUpType': 'signUpType',
105
+ };
106
+ /** The display name in Cursor's cached scoped profile, or '' when it is missing or not JSON. */
107
+ function profileDisplayName(value) {
108
+ try {
109
+ const profile = JSON.parse(value);
110
+ return typeof profile['displayName'] === 'string' ? profile['displayName'] : '';
111
+ }
112
+ catch {
113
+ /* non-JSON or missing — skip */
114
+ return '';
115
+ }
116
+ }
117
+ function setIdentityField(identity, key, value) {
118
+ if (key === 'cursorAuth/cachedScopedProfile')
119
+ identity.displayName = profileDisplayName(value);
120
+ else if (IDENTITY_KEYS[key])
121
+ identity[IDENTITY_KEYS[key]] = value;
122
+ }
123
+ /** Identity: version, email, plan, subscription status, sign-up type, display name. */
124
+ function readCursorIdentity(sqlite3, dbPath) {
68
125
  const identityRaw = querySqlite(sqlite3, dbPath, `SELECT key || char(31) || COALESCE(value, '') || char(30)
69
126
  FROM ItemTable
70
127
  WHERE key IN (
@@ -75,54 +132,24 @@ function collectCursorTokenUsage(home = homedir()) {
75
132
  'cursorAuth/cachedSignUpType',
76
133
  'cursorAuth/cachedScopedProfile'
77
134
  );`);
78
- let version = '';
79
- let email = '';
80
- let plan = '';
81
- let subscriptionStatus = '';
82
- let signUpType = '';
83
- let displayName = '';
84
- if (identityRaw) {
85
- for (const row of identityRaw.split(ROW_SEP)) {
86
- const trimmed = row.trim();
87
- if (!trimmed)
88
- continue;
89
- const sep = trimmed.indexOf(FIELD_SEP);
90
- if (sep === -1)
91
- continue;
92
- const key = trimmed.slice(0, sep);
93
- const value = trimmed.slice(sep + 1);
94
- switch (key) {
95
- case 'cursor.startupMetrics.lastVersion':
96
- version = value;
97
- break;
98
- case 'cursorAuth/cachedEmail':
99
- email = value;
100
- break;
101
- case 'cursorAuth/stripeMembershipType':
102
- plan = value;
103
- break;
104
- case 'cursorAuth/stripeSubscriptionStatus':
105
- subscriptionStatus = value;
106
- break;
107
- case 'cursorAuth/cachedSignUpType':
108
- signUpType = value;
109
- break;
110
- case 'cursorAuth/cachedScopedProfile':
111
- try {
112
- const profile = JSON.parse(value);
113
- if (typeof profile['displayName'] === 'string')
114
- displayName = profile['displayName'];
115
- }
116
- catch {
117
- /* non-JSON or missing — skip */
118
- }
119
- break;
120
- }
121
- }
122
- }
123
- // Usage: aggregate session counts and model names from composerData entries in the last 30
124
- // days. composerData:<conversationId> entries carry a Unix ms timestamp in $.createdAt and
125
- // the model in $.modelConfig.modelName. Token counts are not written by Cursor 3.8+.
135
+ const identity = {
136
+ version: '',
137
+ email: '',
138
+ plan: '',
139
+ subscriptionStatus: '',
140
+ signUpType: '',
141
+ displayName: '',
142
+ };
143
+ for (const [key, value] of splitRows(identityRaw))
144
+ setIdentityField(identity, key, value);
145
+ return identity;
146
+ }
147
+ /**
148
+ * Usage: aggregate session counts and model names from composerData entries since `cutoffMs`.
149
+ * composerData:<conversationId> entries carry a Unix ms timestamp in $.createdAt and
150
+ * the model in $.modelConfig.modelName. Token counts are not written by Cursor 3.8+.
151
+ */
152
+ function readCursorUsage(sqlite3, dbPath, cutoffMs) {
126
153
  // cutoffMs is a safe integer — interpolating directly avoids an extra round-trip.
127
154
  // key range (not LIKE 'composerData:%') so the query hits the primary-key index on `key`
128
155
  // instead of a full-table scan. SQLite's LIKE optimizer only rewrites a prefix match to an
@@ -140,53 +167,62 @@ function collectCursorTokenUsage(home = homedir()) {
140
167
  WHERE key >= 'composerData:' AND key < 'composerData;'
141
168
  AND CAST(json_extract(value, '$.createdAt') AS INTEGER) >= ${cutoffMs}
142
169
  GROUP BY json_extract(value, '$.modelConfig.modelName');`);
143
- const models = new Set();
144
- let sessionCount = 0;
145
170
  // Cursor 3.8+ doesn't write token counts, so there's no real per-model token weighting — the
146
171
  // per-model composer COUNT from the query above is the only usage-weight signal available.
147
172
  // Mirrors Cowork's session-count convention (see cowork_extract.py): "Most used model" is
148
173
  // derived from whichever model has the most composers, not the most tokens.
149
- const modelComposerCounts = {};
150
- if (usageRaw) {
151
- for (const row of usageRaw.split(ROW_SEP)) {
152
- const trimmed = row.trim();
153
- if (!trimmed)
154
- continue;
155
- const sep = trimmed.indexOf(FIELD_SEP);
156
- if (sep === -1)
157
- continue;
158
- const model = trimmed.slice(0, sep).trim();
159
- const cnt = num(trimmed.slice(sep + 1));
160
- sessionCount += cnt;
161
- if (model) {
162
- models.add(model);
163
- modelComposerCounts[model] = (modelComposerCounts[model] ?? 0) + cnt;
164
- }
165
- }
174
+ const usage = { models: new Set(), sessionCount: 0, modelComposerCounts: {} };
175
+ for (const [rawModel, rawCount] of splitRows(usageRaw)) {
176
+ const model = rawModel.trim();
177
+ const cnt = num(rawCount);
178
+ usage.sessionCount += cnt;
179
+ if (!model)
180
+ continue;
181
+ usage.models.add(model);
182
+ usage.modelComposerCounts[model] = (usage.modelComposerCounts[model] ?? 0) + cnt;
166
183
  }
167
- if (sessionCount === 0 && !email && !version)
184
+ return usage;
185
+ }
186
+ /**
187
+ * Aggregate Cursor token usage / sessions / version / identity from state.vscdb.
188
+ * Returns a single-element array (one per-machine aggregate) or [] when no usable DB exists.
189
+ */
190
+ function collectCursorTokenUsage(home = homedir()) {
191
+ const dbPath = vscdbPath(home);
192
+ if (!existsSync(dbPath))
193
+ return [];
194
+ const cutoffMs = Date.now() - TOKEN_USAGE_RECENCY_DAYS * 24 * 60 * 60 * 1000;
195
+ const sqlite3 = resolveSqlite3Binary();
196
+ if (!sqlite3)
197
+ return [];
198
+ const identity = readCursorIdentity(sqlite3, dbPath);
199
+ const usage = readCursorUsage(sqlite3, dbPath, cutoffMs);
200
+ if (usage.sessionCount === 0 && !identity.email && !identity.version)
168
201
  return [];
202
+ const teamIds = readCursorTeamIds(sqlite3, dbPath);
169
203
  return [
170
204
  {
171
205
  file_type: TOKEN_USAGE_FILE_TYPE,
172
206
  file_path: dbPath,
173
207
  raw_content: {
174
- version,
175
- session_count: sessionCount,
176
- models: [...models].sort(),
177
- model_token_split: modelComposerCounts,
208
+ version: identity.version,
209
+ session_count: usage.sessionCount,
210
+ models: [...usage.models].sort(),
211
+ model_token_split: usage.modelComposerCounts,
178
212
  window_days: TOKEN_USAGE_RECENCY_DAYS,
179
213
  totals: {
180
214
  input_tokens: 0,
181
215
  output_tokens: 0,
182
216
  },
183
- email,
184
- display_name: displayName,
185
- plan,
186
- subscription_status: subscriptionStatus,
187
- auth_method: signUpType ? 'cursor_account' : '',
217
+ email: identity.email,
218
+ display_name: identity.displayName,
219
+ plan: identity.plan,
220
+ subscription_status: identity.subscriptionStatus,
221
+ auth_method: identity.signUpType ? 'cursor_account' : '',
222
+ // Omitted when unreadable: the backend reads a missing list as unknown, [] as personal.
223
+ ...(teamIds !== null ? { team_ids: teamIds } : {}),
188
224
  },
189
225
  },
190
226
  ];
191
227
  }
192
- export { collectCursorTokenUsage, TOKEN_USAGE_RECENCY_DAYS, TOKEN_USAGE_FILE_TYPE, vscdbPath };
228
+ export { collectCursorTokenUsage, readCursorEmail, readCursorTeamIds, TOKEN_USAGE_RECENCY_DAYS, TOKEN_USAGE_FILE_TYPE, vscdbPath, };