@optimuslabs/harness-map-staging 1.5.26 → 1.5.27
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync, readSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
+
import { readClaudeLoginPlan } from './claude_login_plan.js';
|
|
4
5
|
/**
|
|
5
6
|
* Every org Claude Desktop's signed-in account has used, so both tenants are recorded when one
|
|
6
7
|
* login belongs to a team org and a personal org and switches between them in Desktop.
|
|
@@ -20,7 +21,8 @@ import { homedir } from 'node:os';
|
|
|
20
21
|
* Reads `lastKnownAccountUuid` from config.json and nothing else from it -- the same file holds
|
|
21
22
|
* Desktop's encrypted token caches, which are never touched or uploaded. The email, and the active
|
|
22
23
|
* 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.
|
|
24
|
+
* and plan, the same org): Desktop's own files name neither. The plan prefers Claude Code's stored
|
|
25
|
+
* login over that cached profile (see claude_login_plan.ts). macOS only.
|
|
24
26
|
*/
|
|
25
27
|
const FILE_TYPE = 'claude_desktop_orgs';
|
|
26
28
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -124,7 +126,7 @@ function claudeJsonDetails(home, account, orgUuids) {
|
|
|
124
126
|
const name = text(oauth.organizationName);
|
|
125
127
|
if (name)
|
|
126
128
|
details.orgNames[org] = name;
|
|
127
|
-
const plan = claudeCodePlan(claudeJson, oauth);
|
|
129
|
+
const plan = readClaudeLoginPlan(home) || claudeCodePlan(claudeJson, oauth);
|
|
128
130
|
if (plan)
|
|
129
131
|
details.orgPlans[org] = plan;
|
|
130
132
|
return details;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* The plan of the login Claude Code is actually using, from its stored OAuth credentials.
|
|
7
|
+
*
|
|
8
|
+
* `~/.claude.json`'s `oauthAccount` is a cached profile and can disagree with the login: a Team
|
|
9
|
+
* login has been seen there as `organizationType: "claude_pro"` on the person's personal org, while
|
|
10
|
+
* `/status` (which reads the credentials) said "Claude Team account". The credentials'
|
|
11
|
+
* `claudeAiOauth.subscriptionType` is what Claude Code bills and runs prompts against, so it wins.
|
|
12
|
+
*
|
|
13
|
+
* Credentials live in `~/.claude/.credentials.json` on Linux and Windows, and in the macOS login
|
|
14
|
+
* keychain as "Claude Code-credentials". Only `subscriptionType` is read out; the tokens beside it
|
|
15
|
+
* are never kept, logged or uploaded. The keychain is only read for the real home directory: it
|
|
16
|
+
* belongs to the signed-in macOS user, not to any other home a scan is pointed at.
|
|
17
|
+
*/
|
|
18
|
+
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
19
|
+
const PLAN = /^[a-z0-9_-]{1,64}$/i;
|
|
20
|
+
function planFrom(credentials) {
|
|
21
|
+
try {
|
|
22
|
+
const plan = JSON.parse(credentials)?.claudeAiOauth
|
|
23
|
+
?.subscriptionType;
|
|
24
|
+
return typeof plan === 'string' && PLAN.test(plan.trim()) ? plan.trim() : '';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function readCredentialsFile(home) {
|
|
31
|
+
try {
|
|
32
|
+
return readFileSync(join(home, '.claude', '.credentials.json'), 'utf8');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readKeychainCredentials(home) {
|
|
39
|
+
if (process.platform !== 'darwin' || home !== homedir())
|
|
40
|
+
return '';
|
|
41
|
+
try {
|
|
42
|
+
return execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
45
|
+
timeout: 3000,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
53
|
+
/** Claude Code's login plan (e.g. "team", "pro"), or '' when no stored login names one. */
|
|
54
|
+
export function readClaudeLoginPlan(home = homedir()) {
|
|
55
|
+
return planFrom(readCredentialsFile(home)) || planFrom(readKeychainCredentials(home));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sets the login plan as the top-level `subscriptionType` of each uploaded `~/.claude.json` with a
|
|
59
|
+
* claude.ai login, the field the backend reads first for Claude's plan. Files are returned as given
|
|
60
|
+
* when there is no such upload or no plan.
|
|
61
|
+
*/
|
|
62
|
+
export function withClaudeLoginPlan(files, home = homedir()) {
|
|
63
|
+
const claudeJsons = files.filter((f) => basename(f.file_path) === '.claude.json' && isObject(f.raw_content?.oauthAccount));
|
|
64
|
+
if (claudeJsons.length === 0)
|
|
65
|
+
return files;
|
|
66
|
+
const plan = readClaudeLoginPlan(home);
|
|
67
|
+
if (!plan)
|
|
68
|
+
return files;
|
|
69
|
+
for (const file of claudeJsons)
|
|
70
|
+
file.raw_content.subscriptionType = plan;
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
@@ -26,6 +26,7 @@ import { canonicalCursorUserStateVscdbPath } from './remediation_config_path.js'
|
|
|
26
26
|
import { ensureWorkspaceRepoEnv, resolveProjectRoot } from './workspace_repo.js';
|
|
27
27
|
import { scanActiveWorktrees, activeWorktreeRootSet, filterConfigFilesToActiveWorktrees, } from './worktree_scanner.js';
|
|
28
28
|
import { createSignature, canonicalizePayload } from '../sender/signing.js';
|
|
29
|
+
import { withClaudeLoginPlan } from '../collection/claude_login_plan.js';
|
|
29
30
|
const PROJECT_ROOT = resolveProjectRoot();
|
|
30
31
|
const configFileKey = (c) => `${c.file_type}\t${c.file_path}`;
|
|
31
32
|
/** Appends the entries not in the batch yet (by file type and path), recording each as seen. */
|
|
@@ -70,7 +71,7 @@ async function fetchPatterns(endpointBase) {
|
|
|
70
71
|
function scanPatterns(patternsResponse) {
|
|
71
72
|
persistVscdbComposerContractFromPatternsResponse(patternsResponse);
|
|
72
73
|
hookRunLog(`scanning config files`);
|
|
73
|
-
|
|
74
|
+
const files = collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT, hookRunLog, {
|
|
74
75
|
vscdbEntrySpecs: patternsResponse.vscdb_entry_specs,
|
|
75
76
|
vscdb_read_queries: patternsResponse.vscdb_read_queries,
|
|
76
77
|
vscdb_merge_from_composer_state: patternsResponse.vscdb_merge_from_composer_state,
|
|
@@ -80,6 +81,7 @@ function scanPatterns(patternsResponse) {
|
|
|
80
81
|
absolute_path_prefixes: patternsResponse.absolute_path_prefixes,
|
|
81
82
|
mcp_tool_glob_spec: patternsResponse.mcp_tool_glob_spec ?? null,
|
|
82
83
|
});
|
|
84
|
+
return withClaudeLoginPlan(files, HOME_DIR);
|
|
83
85
|
}
|
|
84
86
|
/** MCP tool caches: the global cache, Cursor's per-project caches and its workspace vscdb files. */
|
|
85
87
|
function addMcpToolCaches(patternsResponse, configFiles, seen) {
|