theclawbay 0.3.59 → 0.3.60

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 CHANGED
@@ -29,3 +29,7 @@ Saves the API key without writing local client config.
29
29
  `theclawbay logout`
30
30
 
31
31
  Removes The Claw Bay-managed local config from this machine.
32
+
33
+ `theclawbay usage`
34
+
35
+ Shows your current shared 5-hour and weekly usage windows.
@@ -49,6 +49,8 @@ const CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING = "claudeCode.disableLogin
49
49
  const ANTHROPIC_PROVIDER_ID = "anthropic";
50
50
  const MANAGED_EDITOR_TERMINAL_ENV_NAMES = [
51
51
  ENV_KEY_NAME,
52
+ ];
53
+ const MANAGED_CLAUDE_ENV_NAMES = [
52
54
  CLAUDE_ENV_API_KEY_NAME,
53
55
  CLAUDE_ENV_BASE_URL_NAME,
54
56
  CLAUDE_ENV_SIMPLE_MODE_NAME,
@@ -736,6 +738,18 @@ async function readFileIfExists(filePath) {
736
738
  throw error;
737
739
  }
738
740
  }
741
+ async function removeFileIfExists(filePath) {
742
+ try {
743
+ await promises_1.default.unlink(filePath);
744
+ return true;
745
+ }
746
+ catch (error) {
747
+ const err = error;
748
+ if (err.code === "ENOENT")
749
+ return false;
750
+ throw error;
751
+ }
752
+ }
739
753
  async function readJsonObjectFile(filePath) {
740
754
  const existingRaw = await readFileIfExists(filePath);
741
755
  if (existingRaw === null || !existingRaw.trim())
@@ -1948,14 +1962,9 @@ function terminalIntegratedEnvSettingsKey() {
1948
1962
  return "terminal.integrated.env.linux";
1949
1963
  }
1950
1964
  function buildManagedEditorTerminalEnv(params) {
1951
- const env = {
1965
+ return {
1952
1966
  [ENV_KEY_NAME]: params.apiKey,
1953
1967
  };
1954
- if (params.claudeEnabled) {
1955
- env[CLAUDE_ENV_API_KEY_NAME] = params.apiKey;
1956
- env[CLAUDE_ENV_BASE_URL_NAME] = anthropicCompatibleProxyUrl(params.backendUrl);
1957
- }
1958
- return env;
1959
1968
  }
1960
1969
  function buildManagedClaudeEditorEnv(params) {
1961
1970
  return {
@@ -2069,7 +2078,7 @@ async function persistClaudeEditorSettings(params) {
2069
2078
  nextEnv[name] = value;
2070
2079
  changed = true;
2071
2080
  }
2072
- for (const name of MANAGED_EDITOR_TERMINAL_ENV_NAMES) {
2081
+ for (const name of MANAGED_CLAUDE_ENV_NAMES) {
2073
2082
  if (name in desiredEnv || !(name in nextEnv))
2074
2083
  continue;
2075
2084
  delete nextEnv[name];
@@ -2099,6 +2108,126 @@ async function persistClaudeEditorSettings(params) {
2099
2108
  await writeJsonObjectFile(CLAUDE_EDITOR_SETTINGS_STATE_PATH, Array.from(snapshotByPath.values()), 0o600);
2100
2109
  return managedPaths;
2101
2110
  }
2111
+ async function cleanupClaudeEditorSettings() {
2112
+ const snapshotRaw = await readFileIfExists(CLAUDE_EDITOR_SETTINGS_STATE_PATH);
2113
+ if (snapshotRaw === null || !snapshotRaw.trim())
2114
+ return [];
2115
+ let entries = [];
2116
+ try {
2117
+ entries = JSON.parse(snapshotRaw);
2118
+ }
2119
+ catch {
2120
+ return [];
2121
+ }
2122
+ const updated = [];
2123
+ for (const entry of entries) {
2124
+ const settings = await readJsonObjectFile(entry.settingsPath);
2125
+ if (!entry.envHadKey) {
2126
+ delete settings[CLAUDE_CODE_EDITOR_ENV_SETTING];
2127
+ }
2128
+ else {
2129
+ settings[CLAUDE_CODE_EDITOR_ENV_SETTING] = entry.envPreviousValue;
2130
+ }
2131
+ if (!entry.disableLoginPromptHadKey) {
2132
+ delete settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING];
2133
+ }
2134
+ else {
2135
+ settings[CLAUDE_CODE_EDITOR_DISABLE_LOGIN_PROMPT_SETTING] = entry.disableLoginPromptPreviousValue;
2136
+ }
2137
+ if (entry.existed || Object.keys(settings).length > 0) {
2138
+ await writeJsonObjectFile(entry.settingsPath, settings);
2139
+ updated.push(entry.settingsPath);
2140
+ continue;
2141
+ }
2142
+ if (await removeFileIfExists(entry.settingsPath)) {
2143
+ updated.push(entry.settingsPath);
2144
+ }
2145
+ }
2146
+ await removeFileIfExists(CLAUDE_EDITOR_SETTINGS_STATE_PATH);
2147
+ return updated;
2148
+ }
2149
+ async function cleanupClaudeDesktop3pConfig() {
2150
+ const snapshotRaw = await readFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
2151
+ if (snapshotRaw === null || !snapshotRaw.trim())
2152
+ return false;
2153
+ let snapshot = null;
2154
+ try {
2155
+ snapshot = JSON.parse(snapshotRaw);
2156
+ }
2157
+ catch {
2158
+ snapshot = null;
2159
+ }
2160
+ if (!snapshot?.configPath) {
2161
+ await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
2162
+ return false;
2163
+ }
2164
+ if (!snapshot.existed) {
2165
+ const removed = await removeFileIfExists(snapshot.configPath);
2166
+ await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
2167
+ return removed;
2168
+ }
2169
+ const previousRaw = snapshot.previousRaw ?? "";
2170
+ const existingRaw = await readFileIfExists(snapshot.configPath);
2171
+ if (existingRaw === previousRaw) {
2172
+ await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
2173
+ return false;
2174
+ }
2175
+ await promises_1.default.mkdir(node_path_1.default.dirname(snapshot.configPath), { recursive: true });
2176
+ await promises_1.default.writeFile(snapshot.configPath, previousRaw, "utf8");
2177
+ await removeFileIfExists(CLAUDE_DESKTOP_3P_STATE_PATH);
2178
+ return true;
2179
+ }
2180
+ async function cleanupLegacyClaudeEditorTerminalEnvSettings(params) {
2181
+ const snapshotRaw = await readFileIfExists(EDITOR_TERMINAL_ENV_STATE_PATH);
2182
+ if (snapshotRaw === null || !snapshotRaw.trim())
2183
+ return [];
2184
+ let entries = [];
2185
+ try {
2186
+ entries = JSON.parse(snapshotRaw);
2187
+ }
2188
+ catch {
2189
+ return [];
2190
+ }
2191
+ const settingsKey = terminalIntegratedEnvSettingsKey();
2192
+ const managedClaudeEnv = {
2193
+ [CLAUDE_ENV_API_KEY_NAME]: params.apiKey,
2194
+ [CLAUDE_ENV_BASE_URL_NAME]: anthropicCompatibleProxyUrl(params.backendUrl),
2195
+ };
2196
+ const updated = [];
2197
+ for (const entry of entries) {
2198
+ const settings = await readJsonObjectFile(entry.settingsPath);
2199
+ const currentValue = settings[settingsKey];
2200
+ if (typeof currentValue !== "object" || currentValue === null || Array.isArray(currentValue))
2201
+ continue;
2202
+ const currentEnv = { ...currentValue };
2203
+ const previousEnv = typeof entry.previousValue === "object" &&
2204
+ entry.previousValue !== null &&
2205
+ !Array.isArray(entry.previousValue)
2206
+ ? { ...entry.previousValue }
2207
+ : {};
2208
+ let changed = false;
2209
+ for (const name of MANAGED_CLAUDE_ENV_NAMES) {
2210
+ const expectedManagedValue = managedClaudeEnv[name];
2211
+ const currentManagedValue = currentEnv[name];
2212
+ const currentMatchesManaged = typeof expectedManagedValue === "string" ? currentManagedValue === expectedManagedValue : false;
2213
+ if (!currentMatchesManaged)
2214
+ continue;
2215
+ if (Object.prototype.hasOwnProperty.call(previousEnv, name)) {
2216
+ currentEnv[name] = previousEnv[name];
2217
+ }
2218
+ else {
2219
+ delete currentEnv[name];
2220
+ }
2221
+ changed = true;
2222
+ }
2223
+ if (!changed)
2224
+ continue;
2225
+ settings[settingsKey] = currentEnv;
2226
+ await writeJsonObjectFile(entry.settingsPath, settings);
2227
+ updated.push(entry.settingsPath);
2228
+ }
2229
+ return updated;
2230
+ }
2102
2231
  function expandClaudeDesktopInferenceModels(modelIds) {
2103
2232
  const expanded = [];
2104
2233
  const add = (value) => {
@@ -2852,6 +2981,7 @@ class SetupCommand extends base_command_1.BaseCommand {
2852
2981
  let updatedVsCodeEnvFiles = [];
2853
2982
  let updatedEditorTerminalSettings = [];
2854
2983
  let updatedClaudeEditorSettings = [];
2984
+ let restoredClaudeDesktop3pConfig = false;
2855
2985
  let claudeDesktop3pConfigPathManaged = null;
2856
2986
  let sessionMigration = null;
2857
2987
  let authSeed = null;
@@ -2914,18 +3044,34 @@ class SetupCommand extends base_command_1.BaseCommand {
2914
3044
  backendUrl,
2915
3045
  claudeEnabled: claudeEnvEnabled,
2916
3046
  });
2917
- if (selectedSetupClients.has("claude")) {
2918
- updatedClaudeEditorSettings = await persistClaudeEditorSettings({
2919
- apiKey: authCredential,
2920
- backendUrl,
2921
- claudeEnabled: claudeEnvEnabled,
2922
- });
3047
+ }
3048
+ if (selectedSetupClients.has("claude")) {
3049
+ updatedClaudeEditorSettings = await persistClaudeEditorSettings({
3050
+ apiKey: authCredential,
3051
+ backendUrl,
3052
+ claudeEnabled: claudeEnvEnabled,
3053
+ });
3054
+ if (claudeEnvEnabled) {
2923
3055
  claudeDesktop3pConfigPathManaged = await writeClaudeDesktop3pConfig({
2924
3056
  backendUrl,
2925
3057
  apiKey: authCredential,
2926
3058
  claudeModels: claudeAccess?.enabled ? claudeAccess.models : [],
2927
3059
  });
2928
3060
  }
3061
+ else {
3062
+ restoredClaudeDesktop3pConfig = await cleanupClaudeDesktop3pConfig();
3063
+ }
3064
+ }
3065
+ else {
3066
+ updatedEditorTerminalSettings = [
3067
+ ...updatedEditorTerminalSettings,
3068
+ ...await cleanupLegacyClaudeEditorTerminalEnvSettings({
3069
+ apiKey: authCredential,
3070
+ backendUrl,
3071
+ }),
3072
+ ];
3073
+ updatedClaudeEditorSettings = await cleanupClaudeEditorSettings();
3074
+ restoredClaudeDesktop3pConfig = await cleanupClaudeDesktop3pConfig();
2929
3075
  }
2930
3076
  if (selectedSetupClients.has("codex")) {
2931
3077
  progress.update("Configuring Codex");
@@ -3143,6 +3289,9 @@ class SetupCommand extends base_command_1.BaseCommand {
3143
3289
  if (updatedClaudeEditorSettings.length > 0) {
3144
3290
  this.log(`- Claude editor settings updated: ${updatedClaudeEditorSettings.join(", ")}`);
3145
3291
  }
3292
+ if (restoredClaudeDesktop3pConfig) {
3293
+ this.log("- Claude Desktop 3P config: restored the pre-The Claw Bay config.");
3294
+ }
3146
3295
  if (selectedSetupClients.has("codex")) {
3147
3296
  this.log(`- Codex: configured (${codexConfigPath})`);
3148
3297
  if (resolved)
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from "../lib/base-command";
2
+ export default class UsageCommand extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ backend: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
6
+ "api-key": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../lib/base-command");
5
+ const api_key_1 = require("../lib/managed/api-key");
6
+ const config_1 = require("../lib/managed/config");
7
+ const errors_1 = require("../lib/managed/errors");
8
+ const DEFAULT_BACKEND_URL = "https://theclawbay.com";
9
+ function formatPercent(value) {
10
+ if (typeof value !== "number" || !Number.isFinite(value))
11
+ return "n/a";
12
+ return `${value.toFixed(value >= 10 ? 1 : 2).replace(/\.0+$/u, "").replace(/(\.\d*?)0+$/u, "$1")}%`;
13
+ }
14
+ function formatUsd(value) {
15
+ if (typeof value !== "number" || !Number.isFinite(value))
16
+ return "n/a";
17
+ return new Intl.NumberFormat("en-US", {
18
+ style: "currency",
19
+ currency: "USD",
20
+ minimumFractionDigits: 2,
21
+ maximumFractionDigits: 2,
22
+ }).format(value);
23
+ }
24
+ function formatTimestamp(value) {
25
+ if (!value)
26
+ return "n/a";
27
+ const parsed = Date.parse(value);
28
+ if (!Number.isFinite(parsed))
29
+ return value;
30
+ return new Date(parsed).toISOString();
31
+ }
32
+ function formatDuration(seconds) {
33
+ const totalSeconds = Math.max(0, Math.round(seconds));
34
+ const days = Math.floor(totalSeconds / 86400);
35
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
36
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
37
+ const remainingSeconds = totalSeconds % 60;
38
+ const parts = [];
39
+ if (days > 0)
40
+ parts.push(`${days}d`);
41
+ if (hours > 0)
42
+ parts.push(`${hours}h`);
43
+ if (minutes > 0)
44
+ parts.push(`${minutes}m`);
45
+ if (parts.length === 0 || (parts.length < 2 && remainingSeconds > 0)) {
46
+ parts.push(`${remainingSeconds}s`);
47
+ }
48
+ return parts.slice(0, 2).join(" ");
49
+ }
50
+ function printWindow(log, label, window, showCosts) {
51
+ log(`${label}`);
52
+ log(` Used: ${formatPercent(window.progressPercentUsed ?? window.percentUsed)}`);
53
+ log(` Remaining: ${formatPercent(window.percentRemaining)}`);
54
+ log(` Requests: ${window.requestCount} total, ${window.successCount} succeeded, ${window.failedCount} failed`);
55
+ log(` Runtime: ${window.runtimeSecondsUsed.toFixed(1)}s`);
56
+ log(` Resets: ${formatDuration(window.secondsUntilReset)} (${formatTimestamp(window.windowEnd)})`);
57
+ if (showCosts) {
58
+ log(` Cost: ${formatUsd(window.estimatedCostUsdUsed)} used / ${formatUsd(window.costUsdLimit)} limit / ${formatUsd(window.costUsdRemaining)} remaining`);
59
+ }
60
+ log(` Last request: ${formatTimestamp(window.lastRequestAt)}`);
61
+ if (window.limitReached) {
62
+ log(" Status: limit reached");
63
+ }
64
+ }
65
+ class UsageCommand extends base_command_1.BaseCommand {
66
+ async run() {
67
+ await this.runSafe(async () => {
68
+ const { flags } = await this.parse(UsageCommand);
69
+ let managed = null;
70
+ try {
71
+ managed = await (0, config_1.readManagedConfig)();
72
+ }
73
+ catch (error) {
74
+ if (!(error instanceof errors_1.ManagedConfigMissingError))
75
+ throw error;
76
+ }
77
+ const credential = (flags["api-key"] ?? managed?.credential ?? "").trim();
78
+ if (!credential) {
79
+ this.error('No saved credential found. Run "theclawbay setup" or pass --api-key.');
80
+ }
81
+ const backendRaw = flags.backend ??
82
+ (0, api_key_1.tryInferBackendUrlFromApiKey)(credential) ??
83
+ managed?.backendUrl ??
84
+ DEFAULT_BACKEND_URL;
85
+ const backendUrl = backendRaw.replace(/\/+$/u, "");
86
+ const response = await fetch(`${backendUrl}/api/codex-auth/v1/quota`, {
87
+ headers: {
88
+ Authorization: `Bearer ${credential}`,
89
+ Accept: "application/json",
90
+ },
91
+ signal: AbortSignal.timeout(20000),
92
+ });
93
+ const body = (await response.json().catch(() => ({})));
94
+ if (!response.ok) {
95
+ this.error(body.error || `Failed to load usage (HTTP ${response.status})`);
96
+ }
97
+ if (flags.json) {
98
+ this.log(JSON.stringify(body, null, 2));
99
+ return;
100
+ }
101
+ const showCosts = body.usageLimitPresentation !== "percent_only";
102
+ this.log("The Claw Bay usage");
103
+ this.log(`Observed: ${formatTimestamp(body.observedAt)}`);
104
+ this.log(`Auth scope: ${body.pooled ? "shared pooled usage" : body.mode}`);
105
+ if (body.mode === "direct" && typeof body.planMultiplier === "number" && Number.isFinite(body.planMultiplier)) {
106
+ this.log(`Plan multiplier: ${body.planMultiplier}x`);
107
+ }
108
+ if (body.anyLimitReached) {
109
+ this.log("Status: at least one usage window has been reached");
110
+ }
111
+ this.log("");
112
+ printWindow((message) => this.log(message), "5-hour window", body.usage.fiveHour, showCosts);
113
+ this.log("");
114
+ printWindow((message) => this.log(message), "Weekly window", body.usage.weekly, showCosts);
115
+ });
116
+ }
117
+ }
118
+ UsageCommand.description = "Show your current The Claw Bay pooled 5-hour and weekly usage";
119
+ UsageCommand.flags = {
120
+ backend: core_1.Flags.string({
121
+ required: false,
122
+ description: "Backend base URL override (default: saved managed config or https://theclawbay.com)",
123
+ }),
124
+ "api-key": core_1.Flags.string({
125
+ required: false,
126
+ aliases: ["apiKey"],
127
+ description: "API key or linked device-session token override",
128
+ }),
129
+ json: core_1.Flags.boolean({
130
+ required: false,
131
+ default: false,
132
+ description: "Print the raw quota response as JSON",
133
+ }),
134
+ };
135
+ exports.default = UsageCommand;
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.59",
3
+ "version": "0.3.60",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/RCRTCBHAL900/TheClawBay"
9
+ },
6
10
  "bin": {
7
11
  "theclawbay": "dist/index.js"
8
12
  },
@@ -17,6 +17,15 @@
17
17
  "cachedInputPer1M": 0.125,
18
18
  "outputPer1M": 10.0
19
19
  },
20
+ {
21
+ "id": "gpt-image-1.5",
22
+ "label": "GPT Image 1.5",
23
+ "note": "Native image generation model for direct image outputs.",
24
+ "tone": "coral",
25
+ "inputPer1M": 8.0,
26
+ "cachedInputPer1M": 2.0,
27
+ "outputPer1M": 32.0
28
+ },
20
29
  {
21
30
  "id": "gpt-5.3-codex",
22
31
  "label": "GPT-5.3 Codex",