vibeusage 0.2.21 → 0.2.22
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 +306 -173
- package/README.old.md +324 -0
- package/README.zh-CN.md +304 -188
- package/package.json +32 -32
- package/src/cli.js +41 -37
- package/src/commands/activate-if-needed.js +41 -0
- package/src/commands/diagnostics.js +8 -9
- package/src/commands/doctor.js +31 -26
- package/src/commands/init.js +285 -218
- package/src/commands/status.js +86 -83
- package/src/commands/sync.js +182 -130
- package/src/commands/uninstall.js +66 -62
- package/src/lib/activation-check.js +290 -0
- package/src/lib/browser-auth.js +52 -54
- package/src/lib/claude-config.js +25 -25
- package/src/lib/cli-ui.js +35 -35
- package/src/lib/codex-config.js +40 -36
- package/src/lib/debug-flags.js +2 -2
- package/src/lib/diagnostics.js +70 -57
- package/src/lib/doctor.js +139 -132
- package/src/lib/fs.js +17 -17
- package/src/lib/gemini-config.js +44 -40
- package/src/lib/init-flow.js +16 -22
- package/src/lib/insforge-client.js +10 -10
- package/src/lib/insforge.js +9 -3
- package/src/lib/openclaw-hook.js +89 -66
- package/src/lib/openclaw-session-plugin.js +116 -92
- package/src/lib/opencode-config.js +31 -32
- package/src/lib/opencode-usage-audit.js +34 -31
- package/src/lib/progress.js +12 -13
- package/src/lib/project-usage-purge.js +23 -17
- package/src/lib/prompt.js +8 -4
- package/src/lib/rollout.js +342 -241
- package/src/lib/runtime-config.js +34 -22
- package/src/lib/subscriptions.js +94 -92
- package/src/lib/tracker-paths.js +6 -6
- package/src/lib/upload-throttle.js +35 -16
- package/src/lib/uploader.js +33 -29
- package/src/lib/vibeusage-api.js +72 -56
- package/src/lib/vibeusage-public-repo.js +41 -24
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
const DEFAULT_BASE_URL =
|
|
2
|
-
const DEFAULT_DASHBOARD_URL =
|
|
1
|
+
const DEFAULT_BASE_URL = "https://5tmappuk.us-east.insforge.app";
|
|
2
|
+
const DEFAULT_DASHBOARD_URL = "https://www.vibeusage.cc";
|
|
3
3
|
const DEFAULT_HTTP_TIMEOUT_MS = 20_000;
|
|
4
4
|
|
|
5
5
|
function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaults = {} } = {}) {
|
|
6
|
-
const baseUrl = pickString(
|
|
6
|
+
const baseUrl = pickString(
|
|
7
|
+
cli.baseUrl,
|
|
8
|
+
config.baseUrl,
|
|
9
|
+
env?.VIBEUSAGE_INSFORGE_BASE_URL,
|
|
10
|
+
defaults.baseUrl,
|
|
11
|
+
DEFAULT_BASE_URL,
|
|
12
|
+
);
|
|
7
13
|
const dashboardUrl = pickString(
|
|
8
14
|
cli.dashboardUrl,
|
|
9
15
|
config.dashboardUrl,
|
|
10
16
|
env?.VIBEUSAGE_DASHBOARD_URL,
|
|
11
17
|
defaults.dashboardUrl,
|
|
12
|
-
DEFAULT_DASHBOARD_URL
|
|
18
|
+
DEFAULT_DASHBOARD_URL,
|
|
19
|
+
);
|
|
20
|
+
const deviceToken = pickString(
|
|
21
|
+
cli.deviceToken,
|
|
22
|
+
config.deviceToken,
|
|
23
|
+
env?.VIBEUSAGE_DEVICE_TOKEN,
|
|
24
|
+
defaults.deviceToken,
|
|
25
|
+
null,
|
|
13
26
|
);
|
|
14
|
-
const deviceToken = pickString(cli.deviceToken, config.deviceToken, env?.VIBEUSAGE_DEVICE_TOKEN, defaults.deviceToken, null);
|
|
15
27
|
const httpTimeoutMs = pickHttpTimeoutMs(
|
|
16
28
|
cli.httpTimeoutMs,
|
|
17
29
|
config.httpTimeoutMs,
|
|
18
30
|
env?.VIBEUSAGE_HTTP_TIMEOUT_MS,
|
|
19
31
|
defaults.httpTimeoutMs,
|
|
20
|
-
DEFAULT_HTTP_TIMEOUT_MS
|
|
32
|
+
DEFAULT_HTTP_TIMEOUT_MS,
|
|
21
33
|
);
|
|
22
34
|
const debug = pickBoolean(cli.debug, config.debug, env?.VIBEUSAGE_DEBUG, defaults.debug, false);
|
|
23
35
|
const insforgeAnonKey = pickString(
|
|
@@ -25,15 +37,15 @@ function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaul
|
|
|
25
37
|
config.insforgeAnonKey,
|
|
26
38
|
env?.VIBEUSAGE_INSFORGE_ANON_KEY,
|
|
27
39
|
defaults.insforgeAnonKey,
|
|
28
|
-
|
|
40
|
+
"",
|
|
29
41
|
);
|
|
30
|
-
if (insforgeAnonKey.value == null) insforgeAnonKey.value =
|
|
42
|
+
if (insforgeAnonKey.value == null) insforgeAnonKey.value = "";
|
|
31
43
|
const autoRetryNoSpawn = pickBoolean(
|
|
32
44
|
cli.autoRetryNoSpawn,
|
|
33
45
|
config.autoRetryNoSpawn,
|
|
34
46
|
env?.VIBEUSAGE_AUTO_RETRY_NO_SPAWN,
|
|
35
47
|
defaults.autoRetryNoSpawn,
|
|
36
|
-
false
|
|
48
|
+
false,
|
|
37
49
|
);
|
|
38
50
|
|
|
39
51
|
return {
|
|
@@ -51,8 +63,8 @@ function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaul
|
|
|
51
63
|
httpTimeoutMs: httpTimeoutMs.source,
|
|
52
64
|
debug: debug.source,
|
|
53
65
|
insforgeAnonKey: insforgeAnonKey.source,
|
|
54
|
-
autoRetryNoSpawn: autoRetryNoSpawn.source
|
|
55
|
-
}
|
|
66
|
+
autoRetryNoSpawn: autoRetryNoSpawn.source,
|
|
67
|
+
},
|
|
56
68
|
};
|
|
57
69
|
}
|
|
58
70
|
|
|
@@ -69,37 +81,37 @@ function pickHttpTimeoutMs(...candidates) {
|
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function pickValue(candidates, normalize) {
|
|
72
|
-
const labels = [
|
|
84
|
+
const labels = ["cli", "config", "env", "default", "default"];
|
|
73
85
|
for (let i = 0; i < candidates.length; i += 1) {
|
|
74
86
|
const value = normalize(candidates[i]);
|
|
75
87
|
if (value !== undefined) {
|
|
76
|
-
return { value, source: labels[i] ||
|
|
88
|
+
return { value, source: labels[i] || "default" };
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
|
-
return { value: null, source:
|
|
91
|
+
return { value: null, source: "default" };
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
function normalizeString(value) {
|
|
83
|
-
if (typeof value !==
|
|
95
|
+
if (typeof value !== "string") return undefined;
|
|
84
96
|
const trimmed = value.trim();
|
|
85
97
|
if (!trimmed) return undefined;
|
|
86
98
|
return trimmed;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
function normalizeBoolean(value) {
|
|
90
|
-
if (typeof value ===
|
|
91
|
-
if (typeof value ===
|
|
92
|
-
if (typeof value ===
|
|
102
|
+
if (typeof value === "boolean") return value;
|
|
103
|
+
if (typeof value === "number") return value !== 0;
|
|
104
|
+
if (typeof value === "string") {
|
|
93
105
|
const trimmed = value.trim().toLowerCase();
|
|
94
106
|
if (!trimmed) return undefined;
|
|
95
|
-
if (trimmed ===
|
|
96
|
-
if (trimmed ===
|
|
107
|
+
if (trimmed === "1" || trimmed === "true") return true;
|
|
108
|
+
if (trimmed === "0" || trimmed === "false") return false;
|
|
97
109
|
}
|
|
98
110
|
return undefined;
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
function normalizeHttpTimeoutMs(value) {
|
|
102
|
-
if (value == null || value ===
|
|
114
|
+
if (value == null || value === "") return undefined;
|
|
103
115
|
const n = Number(value);
|
|
104
116
|
if (!Number.isFinite(n)) return undefined;
|
|
105
117
|
if (n <= 0) return 0;
|
|
@@ -116,5 +128,5 @@ module.exports = {
|
|
|
116
128
|
DEFAULT_BASE_URL,
|
|
117
129
|
DEFAULT_DASHBOARD_URL,
|
|
118
130
|
DEFAULT_HTTP_TIMEOUT_MS,
|
|
119
|
-
resolveRuntimeConfig
|
|
131
|
+
resolveRuntimeConfig,
|
|
120
132
|
};
|
package/src/lib/subscriptions.js
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
|
-
const os = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const cp = require(
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const cp = require("node:child_process");
|
|
5
5
|
|
|
6
|
-
const { readJson } = require(
|
|
6
|
+
const { readJson } = require("./fs");
|
|
7
|
+
const { resolveTrackerPaths } = require("./tracker-paths");
|
|
8
|
+
const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
|
|
7
9
|
|
|
8
|
-
const OPENAI_AUTH_CLAIM =
|
|
9
|
-
const MACOS_SECURITY_BIN =
|
|
10
|
-
const CLAUDE_CODE_KEYCHAIN_SERVICES = [
|
|
10
|
+
const OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
11
|
+
const MACOS_SECURITY_BIN = "/usr/bin/security";
|
|
12
|
+
const CLAUDE_CODE_KEYCHAIN_SERVICES = ["Claude Code-credentials"];
|
|
11
13
|
|
|
12
14
|
function normalizeString(value) {
|
|
13
|
-
if (typeof value !==
|
|
15
|
+
if (typeof value !== "string") return null;
|
|
14
16
|
const trimmed = value.trim();
|
|
15
17
|
return trimmed.length > 0 ? trimmed : null;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
function normalizeScalarToString(value) {
|
|
19
21
|
if (value === null || value === undefined) return null;
|
|
20
|
-
if (typeof value ===
|
|
21
|
-
if (typeof value ===
|
|
22
|
+
if (typeof value === "string") return normalizeString(value);
|
|
23
|
+
if (typeof value === "number") {
|
|
22
24
|
if (!Number.isFinite(value)) return null;
|
|
23
25
|
return normalizeString(String(value));
|
|
24
26
|
}
|
|
25
|
-
if (typeof value ===
|
|
27
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
26
28
|
return null;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function base64UrlDecodeToString(value) {
|
|
30
|
-
if (typeof value !==
|
|
32
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
31
33
|
const padLen = (4 - (value.length % 4)) % 4;
|
|
32
|
-
const padded = value +
|
|
33
|
-
const base64 = padded.replace(/-/g,
|
|
34
|
+
const padded = value + "=".repeat(padLen);
|
|
35
|
+
const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
34
36
|
try {
|
|
35
|
-
return Buffer.from(base64,
|
|
37
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
36
38
|
} catch (_e) {
|
|
37
39
|
return null;
|
|
38
40
|
}
|
|
@@ -41,7 +43,7 @@ function base64UrlDecodeToString(value) {
|
|
|
41
43
|
function decodeJwtPayload(token) {
|
|
42
44
|
const jwt = normalizeString(token);
|
|
43
45
|
if (!jwt) return null;
|
|
44
|
-
const parts = jwt.split(
|
|
46
|
+
const parts = jwt.split(".");
|
|
45
47
|
if (parts.length < 2) return null;
|
|
46
48
|
const decoded = base64UrlDecodeToString(parts[1]);
|
|
47
49
|
if (!decoded) return null;
|
|
@@ -53,9 +55,9 @@ function decodeJwtPayload(token) {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
function extractOpenAiAuthNamespace(payload) {
|
|
56
|
-
if (!payload || typeof payload !==
|
|
58
|
+
if (!payload || typeof payload !== "object") return null;
|
|
57
59
|
const ns = payload[OPENAI_AUTH_CLAIM];
|
|
58
|
-
if (!ns || typeof ns !==
|
|
60
|
+
if (!ns || typeof ns !== "object" || Array.isArray(ns)) return null;
|
|
59
61
|
return ns;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -80,7 +82,7 @@ function mergeSubscription(primary, secondary) {
|
|
|
80
82
|
planType: a.planType || b.planType || null,
|
|
81
83
|
activeStart: a.activeStart || b.activeStart || null,
|
|
82
84
|
activeUntil: a.activeUntil || b.activeUntil || null,
|
|
83
|
-
lastChecked: a.lastChecked || b.lastChecked || null
|
|
85
|
+
lastChecked: a.lastChecked || b.lastChecked || null,
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -88,26 +90,26 @@ function isDisplayablePlanType(planType) {
|
|
|
88
90
|
const normalized = normalizeString(planType);
|
|
89
91
|
if (!normalized) return false;
|
|
90
92
|
const v = normalized.toLowerCase();
|
|
91
|
-
if (v ===
|
|
93
|
+
if (v === "free" || v === "none" || v === "unknown") return false;
|
|
92
94
|
return true;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function resolveCodexHome({ home, env }) {
|
|
96
98
|
const explicit = normalizeString(env?.CODEX_HOME);
|
|
97
|
-
return explicit ? path.resolve(explicit) : path.join(home,
|
|
99
|
+
return explicit ? path.resolve(explicit) : path.join(home, ".codex");
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
function resolveOpencodeDataDir({ home, env }) {
|
|
101
103
|
const explicit = normalizeString(env?.XDG_DATA_HOME);
|
|
102
|
-
const base = explicit ? path.resolve(explicit) : path.join(home,
|
|
103
|
-
return path.join(base,
|
|
104
|
+
const base = explicit ? path.resolve(explicit) : path.join(home, ".local", "share");
|
|
105
|
+
return path.join(base, "opencode");
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
async function detectCodexChatgptSubscription({ home, env }) {
|
|
107
109
|
const codexHome = resolveCodexHome({ home, env });
|
|
108
|
-
const authPath = path.join(codexHome,
|
|
110
|
+
const authPath = path.join(codexHome, "auth.json");
|
|
109
111
|
const auth = await readJson(authPath);
|
|
110
|
-
if (!auth || typeof auth !==
|
|
112
|
+
if (!auth || typeof auth !== "object") return null;
|
|
111
113
|
|
|
112
114
|
const accessPayload = decodeJwtPayload(auth?.tokens?.access_token);
|
|
113
115
|
const idPayload = decodeJwtPayload(auth?.tokens?.id_token);
|
|
@@ -118,114 +120,95 @@ async function detectCodexChatgptSubscription({ home, env }) {
|
|
|
118
120
|
if (!merged || !isDisplayablePlanType(merged.planType)) return null;
|
|
119
121
|
|
|
120
122
|
return {
|
|
121
|
-
tool:
|
|
122
|
-
provider:
|
|
123
|
-
product:
|
|
123
|
+
tool: "codex",
|
|
124
|
+
provider: "openai",
|
|
125
|
+
product: "chatgpt",
|
|
124
126
|
planType: merged.planType,
|
|
125
127
|
activeStart: merged.activeStart,
|
|
126
128
|
activeUntil: merged.activeUntil,
|
|
127
|
-
lastChecked: merged.lastChecked
|
|
129
|
+
lastChecked: merged.lastChecked,
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
async function detectOpencodeChatgptSubscription({ home, env }) {
|
|
132
134
|
const dataDir = resolveOpencodeDataDir({ home, env });
|
|
133
|
-
const authPath = path.join(dataDir,
|
|
135
|
+
const authPath = path.join(dataDir, "auth.json");
|
|
134
136
|
const auth = await readJson(authPath);
|
|
135
|
-
if (!auth || typeof auth !==
|
|
137
|
+
if (!auth || typeof auth !== "object") return null;
|
|
136
138
|
|
|
137
139
|
const accessPayload = decodeJwtPayload(auth?.openai?.access);
|
|
138
140
|
const info = extractChatgptSubscriptionFromPayload(accessPayload);
|
|
139
141
|
if (!info || !isDisplayablePlanType(info.planType)) return null;
|
|
140
142
|
|
|
141
143
|
return {
|
|
142
|
-
tool:
|
|
143
|
-
provider:
|
|
144
|
-
product:
|
|
144
|
+
tool: "opencode",
|
|
145
|
+
provider: "openai",
|
|
146
|
+
product: "chatgpt",
|
|
145
147
|
planType: info.planType,
|
|
146
148
|
activeStart: info.activeStart,
|
|
147
149
|
activeUntil: info.activeUntil,
|
|
148
|
-
lastChecked: info.lastChecked
|
|
150
|
+
lastChecked: info.lastChecked,
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
function probeMacosKeychainGenericPassword({
|
|
153
|
-
service,
|
|
154
|
-
securityRunner,
|
|
155
|
-
timeoutMs
|
|
156
|
-
} = {}) {
|
|
154
|
+
function probeMacosKeychainGenericPassword({ service, securityRunner, timeoutMs } = {}) {
|
|
157
155
|
const svc = normalizeString(service);
|
|
158
156
|
if (!svc) return false;
|
|
159
157
|
|
|
160
|
-
const runner = typeof securityRunner ===
|
|
158
|
+
const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
|
|
161
159
|
if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return false;
|
|
162
160
|
|
|
163
|
-
const result = runner(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
stdio: 'ignore',
|
|
168
|
-
timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000
|
|
169
|
-
}
|
|
170
|
-
);
|
|
161
|
+
const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc], {
|
|
162
|
+
stdio: "ignore",
|
|
163
|
+
timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
|
|
164
|
+
});
|
|
171
165
|
|
|
172
166
|
if (!result || result.error) return false;
|
|
173
167
|
return result.status === 0;
|
|
174
168
|
}
|
|
175
169
|
|
|
176
|
-
function readMacosKeychainPassword({
|
|
177
|
-
service,
|
|
178
|
-
securityRunner,
|
|
179
|
-
timeoutMs
|
|
180
|
-
} = {}) {
|
|
170
|
+
function readMacosKeychainPassword({ service, securityRunner, timeoutMs } = {}) {
|
|
181
171
|
const svc = normalizeString(service);
|
|
182
172
|
if (!svc) return null;
|
|
183
173
|
|
|
184
|
-
const runner = typeof securityRunner ===
|
|
174
|
+
const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
|
|
185
175
|
if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return null;
|
|
186
176
|
|
|
187
|
-
const result = runner(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
|
|
193
|
-
encoding: 'utf8'
|
|
194
|
-
}
|
|
195
|
-
);
|
|
177
|
+
const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc, "-w"], {
|
|
178
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
179
|
+
timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
|
|
180
|
+
encoding: "utf8",
|
|
181
|
+
});
|
|
196
182
|
|
|
197
183
|
if (!result || result.error) return null;
|
|
198
184
|
if (result.status !== 0) return null;
|
|
199
185
|
|
|
200
186
|
const stdout =
|
|
201
|
-
typeof result.stdout ===
|
|
187
|
+
typeof result.stdout === "string"
|
|
202
188
|
? result.stdout
|
|
203
189
|
: Buffer.isBuffer(result.stdout)
|
|
204
|
-
? result.stdout.toString(
|
|
205
|
-
:
|
|
190
|
+
? result.stdout.toString("utf8")
|
|
191
|
+
: "";
|
|
206
192
|
const trimmed = stdout.trim();
|
|
207
193
|
return trimmed.length > 0 ? trimmed : null;
|
|
208
194
|
}
|
|
209
195
|
|
|
210
|
-
function detectClaudeCodeCredentialsPresence({
|
|
211
|
-
platform
|
|
212
|
-
securityRunner
|
|
213
|
-
} = {}) {
|
|
214
|
-
if (platform !== 'darwin') return null;
|
|
196
|
+
function detectClaudeCodeCredentialsPresence({ platform, securityRunner } = {}) {
|
|
197
|
+
if (platform !== "darwin") return null;
|
|
215
198
|
|
|
216
199
|
for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
|
|
217
200
|
const present = probeMacosKeychainGenericPassword({
|
|
218
201
|
service,
|
|
219
|
-
securityRunner
|
|
202
|
+
securityRunner,
|
|
220
203
|
});
|
|
221
204
|
if (!present) continue;
|
|
222
205
|
|
|
223
206
|
// Existence-only probe: do not read secrets or infer paid tier.
|
|
224
207
|
return {
|
|
225
|
-
tool:
|
|
226
|
-
provider:
|
|
227
|
-
product:
|
|
228
|
-
planType:
|
|
208
|
+
tool: "claude",
|
|
209
|
+
provider: "anthropic",
|
|
210
|
+
product: "credentials",
|
|
211
|
+
planType: "present",
|
|
229
212
|
};
|
|
230
213
|
}
|
|
231
214
|
|
|
@@ -233,10 +216,10 @@ function detectClaudeCodeCredentialsPresence({
|
|
|
233
216
|
}
|
|
234
217
|
|
|
235
218
|
function extractClaudeKeychainSubscription(payload) {
|
|
236
|
-
if (!payload || typeof payload !==
|
|
219
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
237
220
|
|
|
238
221
|
const oauth = payload.claudeAiOauth;
|
|
239
|
-
if (!oauth || typeof oauth !==
|
|
222
|
+
if (!oauth || typeof oauth !== "object" || Array.isArray(oauth)) return null;
|
|
240
223
|
|
|
241
224
|
const subscriptionType = normalizeScalarToString(oauth.subscriptionType);
|
|
242
225
|
const rateLimitTier = normalizeScalarToString(oauth.rateLimitTier);
|
|
@@ -245,16 +228,13 @@ function extractClaudeKeychainSubscription(payload) {
|
|
|
245
228
|
return { subscriptionType, rateLimitTier };
|
|
246
229
|
}
|
|
247
230
|
|
|
248
|
-
function detectClaudeCodeSubscriptionDetails({
|
|
249
|
-
platform
|
|
250
|
-
securityRunner
|
|
251
|
-
} = {}) {
|
|
252
|
-
if (platform !== 'darwin') return null;
|
|
231
|
+
function detectClaudeCodeSubscriptionDetails({ platform, securityRunner } = {}) {
|
|
232
|
+
if (platform !== "darwin") return null;
|
|
253
233
|
|
|
254
234
|
for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
|
|
255
235
|
const raw = readMacosKeychainPassword({
|
|
256
236
|
service,
|
|
257
|
-
securityRunner
|
|
237
|
+
securityRunner,
|
|
258
238
|
});
|
|
259
239
|
if (!raw) continue;
|
|
260
240
|
|
|
@@ -269,11 +249,11 @@ function detectClaudeCodeSubscriptionDetails({
|
|
|
269
249
|
if (!info) continue;
|
|
270
250
|
|
|
271
251
|
return {
|
|
272
|
-
tool:
|
|
273
|
-
provider:
|
|
274
|
-
product:
|
|
252
|
+
tool: "claude",
|
|
253
|
+
provider: "anthropic",
|
|
254
|
+
product: "subscription",
|
|
275
255
|
planType: info.subscriptionType,
|
|
276
|
-
rateLimitTier: info.rateLimitTier
|
|
256
|
+
rateLimitTier: info.rateLimitTier,
|
|
277
257
|
};
|
|
278
258
|
}
|
|
279
259
|
|
|
@@ -286,7 +266,7 @@ async function collectLocalSubscriptions({
|
|
|
286
266
|
platform = process.platform,
|
|
287
267
|
securityRunner,
|
|
288
268
|
probeKeychain = false,
|
|
289
|
-
probeKeychainDetails = false
|
|
269
|
+
probeKeychainDetails = false,
|
|
290
270
|
} = {}) {
|
|
291
271
|
const out = [];
|
|
292
272
|
|
|
@@ -308,10 +288,32 @@ async function collectLocalSubscriptions({
|
|
|
308
288
|
if (claude) out.push(claude);
|
|
309
289
|
}
|
|
310
290
|
|
|
291
|
+
const openclaw = await detectOpenclawSessionIntegration({ home, env });
|
|
292
|
+
if (openclaw) out.push(openclaw);
|
|
293
|
+
|
|
311
294
|
// Gemini: no stable local subscription/tier signal found yet.
|
|
312
295
|
return out;
|
|
313
296
|
}
|
|
314
297
|
|
|
298
|
+
async function detectOpenclawSessionIntegration({ home, env }) {
|
|
299
|
+
const { trackerDir } = await resolveTrackerPaths({ home });
|
|
300
|
+
let state = null;
|
|
301
|
+
try {
|
|
302
|
+
state = await probeOpenclawSessionPluginState({ home, trackerDir, env });
|
|
303
|
+
} catch (_err) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!state?.configured) return null;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
tool: "openclaw",
|
|
311
|
+
provider: "openclaw",
|
|
312
|
+
product: "session_plugin",
|
|
313
|
+
planType: "enabled",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
315
317
|
module.exports = {
|
|
316
|
-
collectLocalSubscriptions
|
|
318
|
+
collectLocalSubscriptions,
|
|
317
319
|
};
|
package/src/lib/tracker-paths.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
const os = require(
|
|
2
|
-
const path = require(
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
3
|
|
|
4
4
|
async function resolveTrackerPaths({ home = os.homedir() } = {}) {
|
|
5
|
-
const rootDir = path.join(home,
|
|
5
|
+
const rootDir = path.join(home, ".vibeusage");
|
|
6
6
|
return {
|
|
7
7
|
rootDir,
|
|
8
|
-
trackerDir: path.join(rootDir,
|
|
9
|
-
binDir: path.join(rootDir,
|
|
8
|
+
trackerDir: path.join(rootDir, "tracker"),
|
|
9
|
+
binDir: path.join(rootDir, "bin"),
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
module.exports = {
|
|
14
|
-
resolveTrackerPaths
|
|
14
|
+
resolveTrackerPaths,
|
|
15
15
|
};
|
|
@@ -6,20 +6,20 @@ const DEFAULTS = {
|
|
|
6
6
|
maxBatchesSmall: 2,
|
|
7
7
|
maxBatchesLarge: 4,
|
|
8
8
|
backoffInitialMs: 60_000,
|
|
9
|
-
backoffMaxMs: 30 * 60_000
|
|
9
|
+
backoffMaxMs: 30 * 60_000,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
function normalizeState(raw) {
|
|
13
|
-
const s = raw && typeof raw ===
|
|
13
|
+
const s = raw && typeof raw === "object" ? raw : {};
|
|
14
14
|
return {
|
|
15
15
|
version: 1,
|
|
16
16
|
lastSuccessMs: toSafeInt(s.lastSuccessMs),
|
|
17
17
|
nextAllowedAtMs: toSafeInt(s.nextAllowedAtMs),
|
|
18
18
|
backoffUntilMs: toSafeInt(s.backoffUntilMs),
|
|
19
19
|
backoffStep: toSafeInt(s.backoffStep),
|
|
20
|
-
lastErrorAt: typeof s.lastErrorAt ===
|
|
21
|
-
lastError: typeof s.lastError ===
|
|
22
|
-
updatedAt: typeof s.updatedAt ===
|
|
20
|
+
lastErrorAt: typeof s.lastErrorAt === "string" ? s.lastErrorAt : null,
|
|
21
|
+
lastError: typeof s.lastError === "string" ? s.lastError : null,
|
|
22
|
+
updatedAt: typeof s.updatedAt === "string" ? s.updatedAt : null,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -29,22 +29,41 @@ function decideAutoUpload({ nowMs, pendingBytes, state, config }) {
|
|
|
29
29
|
const pending = Number(pendingBytes || 0);
|
|
30
30
|
|
|
31
31
|
if (pending <= 0) {
|
|
32
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
allowed: false,
|
|
34
|
+
reason: "no-pending",
|
|
35
|
+
maxBatches: 0,
|
|
36
|
+
batchSize: cfg.batchSize,
|
|
37
|
+
blockedUntilMs: 0,
|
|
38
|
+
};
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const blockedUntilMs = Math.max(s.nextAllowedAtMs || 0, s.backoffUntilMs || 0);
|
|
36
42
|
if (blockedUntilMs > 0 && nowMs < blockedUntilMs) {
|
|
37
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
allowed: false,
|
|
45
|
+
reason: "throttled",
|
|
46
|
+
maxBatches: 0,
|
|
47
|
+
batchSize: cfg.batchSize,
|
|
48
|
+
blockedUntilMs,
|
|
49
|
+
};
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
const maxBatches = pending >= cfg.backlogBytes ? cfg.maxBatchesLarge : cfg.maxBatchesSmall;
|
|
41
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
allowed: true,
|
|
55
|
+
reason: "allowed",
|
|
56
|
+
maxBatches,
|
|
57
|
+
batchSize: cfg.batchSize,
|
|
58
|
+
blockedUntilMs: 0,
|
|
59
|
+
};
|
|
42
60
|
}
|
|
43
61
|
|
|
44
62
|
function recordUploadSuccess({ nowMs, state, config, randInt }) {
|
|
45
63
|
const cfg = { ...DEFAULTS, ...(config || {}) };
|
|
46
64
|
const s = normalizeState(state);
|
|
47
|
-
const jitter =
|
|
65
|
+
const jitter =
|
|
66
|
+
typeof randInt === "function" ? randInt(0, cfg.jitterMsMax) : randomInt(0, cfg.jitterMsMax);
|
|
48
67
|
const nextAllowedAtMs = nowMs + cfg.intervalMs + jitter;
|
|
49
68
|
|
|
50
69
|
return {
|
|
@@ -55,7 +74,7 @@ function recordUploadSuccess({ nowMs, state, config, randInt }) {
|
|
|
55
74
|
backoffStep: 0,
|
|
56
75
|
lastErrorAt: null,
|
|
57
76
|
lastError: null,
|
|
58
|
-
updatedAt: new Date(nowMs).toISOString()
|
|
77
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
59
78
|
};
|
|
60
79
|
}
|
|
61
80
|
|
|
@@ -83,13 +102,13 @@ function recordUploadFailure({ nowMs, state, error, config }) {
|
|
|
83
102
|
backoffUntilMs,
|
|
84
103
|
backoffStep: Math.min(20, (s.backoffStep || 0) + 1),
|
|
85
104
|
lastErrorAt: new Date(nowMs).toISOString(),
|
|
86
|
-
lastError: truncate(String(error?.message ||
|
|
87
|
-
updatedAt: new Date(nowMs).toISOString()
|
|
105
|
+
lastError: truncate(String(error?.message || "upload failed"), 200),
|
|
106
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
88
107
|
};
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
function parseRetryAfterMs(headerValue, nowMs = Date.now()) {
|
|
92
|
-
if (typeof headerValue !==
|
|
111
|
+
if (typeof headerValue !== "string" || headerValue.trim().length === 0) return null;
|
|
93
112
|
const v = headerValue.trim();
|
|
94
113
|
const seconds = Number(v);
|
|
95
114
|
if (Number.isFinite(seconds) && seconds >= 0) return Math.floor(seconds * 1000);
|
|
@@ -114,9 +133,9 @@ function toSafeInt(v) {
|
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
function truncate(s, maxLen) {
|
|
117
|
-
if (typeof s !==
|
|
136
|
+
if (typeof s !== "string") return "";
|
|
118
137
|
if (s.length <= maxLen) return s;
|
|
119
|
-
return s.slice(0, maxLen - 1) +
|
|
138
|
+
return s.slice(0, maxLen - 1) + "…";
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
module.exports = {
|
|
@@ -125,5 +144,5 @@ module.exports = {
|
|
|
125
144
|
decideAutoUpload,
|
|
126
145
|
recordUploadSuccess,
|
|
127
146
|
recordUploadFailure,
|
|
128
|
-
parseRetryAfterMs
|
|
147
|
+
parseRetryAfterMs,
|
|
129
148
|
};
|