subconscious-cli 6.3.1 → 8.0.0
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 +5 -2
- package/bin/agent-launch.js +1 -25
- package/bin/agent-spawn.js +2 -2
- package/bin/agents.js +27 -22
- package/bin/auth.js +31 -7
- package/bin/claude-config.js +13 -629
- package/bin/cli.js +43 -0
- package/bin/feedback.js +2 -1
- package/bin/model-capabilities.js +45 -5
- package/bin/models.js +126 -36
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/network.js +710 -0
- package/bin/opencode-provider.js +17 -5
- package/bin/profiles.js +46 -2
- package/bin/runbook/claude-code/agent.json +6 -27
- package/bin/runbook/claude-code/install.sh +3 -2
- package/bin/runbook/shared.json +10 -0
- package/bin/tui.js +1 -1
- package/bin/update-check.js +2 -1
- package/bin/upgrade.js +2 -1
- package/bin/usage.js +85 -62
- package/bin/windows/launch.js +5 -17
- package/package.json +1 -1
- package/bin/claude-config-drift.js +0 -58
- package/bin/claude-config-plugins.js +0 -103
package/bin/opencode-provider.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { modelIds, positiveInteger } from './launch-values.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
catalogEntriesFromMetadata,
|
|
5
|
+
modelSupportsVision,
|
|
6
|
+
} from './model-capabilities.js';
|
|
4
7
|
|
|
5
8
|
export const OPENCODE_PROVIDER_ID = 'subconscious';
|
|
6
9
|
const OPENCODE_IMAGE_WINDOW_PLUGIN = fileURLToPath(
|
|
@@ -40,15 +43,22 @@ export function opencodeModelDisplayName(modelId) {
|
|
|
40
43
|
return slug.split(/[-_]/).filter(Boolean).map(formatToken).join(' ');
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
export function buildOpenCodeModels(
|
|
46
|
+
export function buildOpenCodeModels(
|
|
47
|
+
modelIds,
|
|
48
|
+
{ context, output, entries = [] },
|
|
49
|
+
) {
|
|
50
|
+
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
44
51
|
return Object.fromEntries(
|
|
45
52
|
modelIds.map((id) => [
|
|
46
53
|
id,
|
|
47
54
|
{
|
|
48
55
|
name: opencodeModelDisplayName(id),
|
|
49
56
|
tools: true,
|
|
50
|
-
limit: {
|
|
51
|
-
|
|
57
|
+
limit: {
|
|
58
|
+
context: entryById.get(id)?.limits?.context_length ?? context,
|
|
59
|
+
output: entryById.get(id)?.limits?.max_output_tokens ?? output,
|
|
60
|
+
},
|
|
61
|
+
...(modelSupportsVision(id, entries)
|
|
52
62
|
? {
|
|
53
63
|
attachment: true,
|
|
54
64
|
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
@@ -65,6 +75,7 @@ export function buildOpenCodeConfig({
|
|
|
65
75
|
modelIds,
|
|
66
76
|
context,
|
|
67
77
|
output,
|
|
78
|
+
entries = [],
|
|
68
79
|
}) {
|
|
69
80
|
return {
|
|
70
81
|
$schema: 'https://opencode.ai/config.json',
|
|
@@ -82,7 +93,7 @@ export function buildOpenCodeConfig({
|
|
|
82
93
|
headers: { 'x-subconscious-client': 'opencode' },
|
|
83
94
|
modelsDiscovery: { enabled: false },
|
|
84
95
|
},
|
|
85
|
-
models: buildOpenCodeModels(modelIds, { context, output }),
|
|
96
|
+
models: buildOpenCodeModels(modelIds, { context, output, entries }),
|
|
86
97
|
},
|
|
87
98
|
},
|
|
88
99
|
model: `${OPENCODE_PROVIDER_ID}/${model}`,
|
|
@@ -100,5 +111,6 @@ export function openCodeConfigFromEnv(env) {
|
|
|
100
111
|
'OPENCODE_CONTEXT_LIMIT',
|
|
101
112
|
),
|
|
102
113
|
output: positiveInteger(env.OPENCODE_OUTPUT_LIMIT, 'OPENCODE_OUTPUT_LIMIT'),
|
|
114
|
+
entries: catalogEntriesFromMetadata(env.SUBC_MODEL_METADATA),
|
|
103
115
|
});
|
|
104
116
|
}
|
package/bin/profiles.js
CHANGED
|
@@ -12,9 +12,11 @@ import os from 'node:os';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { c } from './colors.js';
|
|
14
14
|
import { PUBLIC_CATALOG_FALLBACK_MESSAGE } from './models.js';
|
|
15
|
+
import { describeFailure } from './network.js';
|
|
15
16
|
import {
|
|
16
17
|
profileTemplate,
|
|
17
18
|
RUNBOOK_DEFAULTS,
|
|
19
|
+
resolvedProfileValues,
|
|
18
20
|
SUPPORTED_MODELS,
|
|
19
21
|
} from './profile-settings.js';
|
|
20
22
|
|
|
@@ -26,6 +28,10 @@ export {
|
|
|
26
28
|
SUPPORTED_MODELS,
|
|
27
29
|
} from './profile-settings.js';
|
|
28
30
|
|
|
31
|
+
export function systemCaEnabledFor(profile) {
|
|
32
|
+
return String(resolvedProfileValues(profile).SYSTEM_CA ?? '').trim() === 'on';
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
import { runWindows, windowsEditor } from './windows/process.js';
|
|
30
36
|
|
|
31
37
|
export const DEFAULT_PROFILE = 'default';
|
|
@@ -560,11 +566,49 @@ export async function configCommand(
|
|
|
560
566
|
export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
561
567
|
const selectedModel =
|
|
562
568
|
options.selectedModel || models[0] || RUNBOOK_DEFAULTS.MODEL;
|
|
569
|
+
const entryById = new Map(
|
|
570
|
+
(options.entries ?? []).map((entry) => [entry.id, entry]),
|
|
571
|
+
);
|
|
572
|
+
const lifecycleNote = (model) => {
|
|
573
|
+
const lifecycle = entryById.get(model)?.lifecycle;
|
|
574
|
+
if (!lifecycle) return '';
|
|
575
|
+
const date = lifecycle.deprecation_date
|
|
576
|
+
? ` until ${lifecycle.deprecation_date}`
|
|
577
|
+
: '';
|
|
578
|
+
const replacement = lifecycle.replacement
|
|
579
|
+
? ` ${c.dim}-> ${lifecycle.replacement}${c.reset}`
|
|
580
|
+
: '';
|
|
581
|
+
if (lifecycle.status === 'deprecated') {
|
|
582
|
+
return ` ${c.yellow}[deprecated${date}]${c.reset}${replacement}`;
|
|
583
|
+
}
|
|
584
|
+
if (lifecycle.status === 'retired') {
|
|
585
|
+
return ` ${c.red}[retired${date}]${c.reset}${replacement}`;
|
|
586
|
+
}
|
|
587
|
+
return '';
|
|
588
|
+
};
|
|
563
589
|
console.log(`\n ${c.bold}Available models${c.reset}\n`);
|
|
564
590
|
for (const model of models) {
|
|
565
591
|
const suffix =
|
|
566
592
|
model === selectedModel ? ` ${c.dim}(default)${c.reset}` : '';
|
|
567
|
-
console.log(
|
|
593
|
+
console.log(
|
|
594
|
+
` ${c.cyan}${model}${c.reset}${suffix}${lifecycleNote(model)}`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
const selectedLifecycle = entryById.get(selectedModel)?.lifecycle;
|
|
598
|
+
if (selectedLifecycle?.status === 'deprecated') {
|
|
599
|
+
const when = selectedLifecycle.deprecation_date
|
|
600
|
+
? ` on ${selectedLifecycle.deprecation_date}`
|
|
601
|
+
: '';
|
|
602
|
+
const to = selectedLifecycle.replacement
|
|
603
|
+
? ` Migrate to ${selectedLifecycle.replacement}.`
|
|
604
|
+
: '';
|
|
605
|
+
console.error(
|
|
606
|
+
`\n ${c.yellow}Selected model ${selectedModel} is deprecated${when}.${to}${c.reset}`,
|
|
607
|
+
);
|
|
608
|
+
} else if (selectedLifecycle?.status === 'retired') {
|
|
609
|
+
console.error(
|
|
610
|
+
`\n ${c.yellow}Selected model ${selectedModel} is retired and may stop serving without notice.${c.reset}`,
|
|
611
|
+
);
|
|
568
612
|
}
|
|
569
613
|
if (options.source === 'public' && options.hasApiKey) {
|
|
570
614
|
console.error(`\n ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}`);
|
|
@@ -572,7 +616,7 @@ export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
|
572
616
|
console.error(
|
|
573
617
|
`\n ${c.yellow}Could not fetch the live model catalog; showing packaged defaults.${c.reset}`,
|
|
574
618
|
);
|
|
575
|
-
console.error(` ${c.dim}${options.error
|
|
619
|
+
console.error(` ${c.dim}${describeFailure(options.error)}${c.reset}`);
|
|
576
620
|
}
|
|
577
621
|
console.log();
|
|
578
622
|
}
|
|
@@ -28,11 +28,14 @@
|
|
|
28
28
|
},
|
|
29
29
|
"help": {
|
|
30
30
|
"usage": "subc [-p NAME] claude [help|status|uninstall] [Claude arguments...]",
|
|
31
|
-
"behavior": "Launches Claude Code with the active Subconscious profile and model picker
|
|
31
|
+
"behavior": "Launches Claude Code with the active Subconscious profile and model picker, using your own Claude config (~/.claude, or your CLAUDE_CONFIG_DIR) unchanged - plans, sessions, memory, skills and settings stay where plain claude keeps them. Subconscious routing, credentials and model pinning ride the launch env and --settings, which persist nothing. A /model pick inside a subc session changes your Claude default, as it does in plain claude. Persistent files are leftover-only: subc claude uninstall.",
|
|
32
32
|
"options": [
|
|
33
33
|
["help", "Show this help"],
|
|
34
|
-
["status", "Inspect
|
|
35
|
-
[
|
|
34
|
+
["status", "Inspect leftovers: ~/.claude/subconscious-gateway.env"],
|
|
35
|
+
[
|
|
36
|
+
"uninstall",
|
|
37
|
+
"Remove leftover ~/.claude/subconscious-gateway.env from older persistent setup"
|
|
38
|
+
],
|
|
36
39
|
["--model MODEL", "Override the profile model for this launch"]
|
|
37
40
|
],
|
|
38
41
|
"args": ["-- ARGS...", "Pass remaining arguments to Claude Code"]
|
|
@@ -113,18 +116,6 @@
|
|
|
113
116
|
"type": "integer",
|
|
114
117
|
"min": 1
|
|
115
118
|
},
|
|
116
|
-
{
|
|
117
|
-
"name": "CLAUDE_CODE_ISOLATE",
|
|
118
|
-
"flag": "--no-isolate",
|
|
119
|
-
"flag_value": "off",
|
|
120
|
-
"default": "on",
|
|
121
|
-
"strict": true,
|
|
122
|
-
"choices": ["on", "off"],
|
|
123
|
-
"help": "Use ~/.claude directly, so a /model pick in subc also changes plain Claude Code",
|
|
124
|
-
"label": "Isolated Claude config",
|
|
125
|
-
"description": "When on, subc claude sessions use ~/.subconscious/claude-code as the Claude config dir. It links back to ~/.claude for CLAUDE.md, skills, hooks and session history, copies the plugin index, rebuilds settings.json from ~/.claude/settings.json on each launch, and keeps only the subc model pick apart.",
|
|
126
|
-
"comment": "CLAUDE_CODE_ISOLATE=off shares ~/.claude with plain Claude Code."
|
|
127
|
-
},
|
|
128
119
|
{ "name": "MAX_CONCURRENT_SUBAGENTS" },
|
|
129
120
|
{
|
|
130
121
|
"name": "MAX_SUBAGENT_SPAWN_DEPTH",
|
|
@@ -159,12 +150,6 @@
|
|
|
159
150
|
"override": ["ANTHROPIC_SMALL_FAST_MODEL"],
|
|
160
151
|
"description": "Small/fast background model."
|
|
161
152
|
},
|
|
162
|
-
{
|
|
163
|
-
"name": "CLAUDE_CONFIG_DIR",
|
|
164
|
-
"value": "{claudeConfigDir}",
|
|
165
|
-
"optional": true,
|
|
166
|
-
"description": "Claude Code config dir for subc sessions, prepared before each launch to share the user's own Claude config (~/.claude, or a CLAUDE_CONFIG_DIR they set, which becomes the source rather than the launch dir). When CLAUDE_CODE_ISOLATE=off, the user's own CLAUDE_CONFIG_DIR is kept, or none is set, so sessions use it directly."
|
|
167
|
-
},
|
|
168
153
|
{
|
|
169
154
|
"name": "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
170
155
|
"value": "0",
|
|
@@ -251,12 +236,6 @@
|
|
|
251
236
|
},
|
|
252
237
|
"description": "Replaces the built-in model picker with the live catalog and allowlists only those models."
|
|
253
238
|
},
|
|
254
|
-
{
|
|
255
|
-
"kind": "file",
|
|
256
|
-
"path": "~/.subconscious/claude-code",
|
|
257
|
-
"lifetime": "persistent",
|
|
258
|
-
"description": "Claude Code config dir for subc sessions (~/.subconscious/claude-code, honoring $SUBC_CONFIG_DIR), set via CLAUDE_CONFIG_DIR. Each launch links CLAUDE.md, history.jsonl and keybindings.json (when present) and the rules, agents, commands, skills, output-styles, hooks, projects, file-history, todos, plans and ide folders (created in ~/.claude when missing) back to ~/.claude, copies the plugin index files (plugin changes made inside subc stay in subc and reset on the next launch), rebuilds settings.json from ~/.claude/settings.json (keeping the subc model pick, dropping apiKeyHelper and env names subc sets), and copies MCP servers, trust and onboarding state from ~/.claude.json (never login state). Settings or MCP servers changed inside a subc session are replaced on the next launch; that launch names them and saves them in settings.discarded.json or mcp.discarded.json in this dir. subc never deletes it and never writes ~/.claude settings; it refuses to launch when this dir resolves to your own config. To remove it, delete the links first: rm -r with a trailing slash, or PowerShell Remove-Item -Recurse, follows them into ~/.claude. Opt out with CLAUDE_CODE_ISOLATE=off or subc claude --no-isolate."
|
|
259
|
-
},
|
|
260
239
|
{
|
|
261
240
|
"kind": "file",
|
|
262
241
|
"path": "~/.claude/subconscious-gateway.env",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ── Subconscious API Gateway — Claude Code leftovers ──────────────────────────
|
|
3
3
|
# Claude Code is launch-only: `subc claude` passes everything as env and flags
|
|
4
|
-
# and writes nothing.
|
|
5
|
-
# ~/.claude/subconscious-gateway.env
|
|
4
|
+
# and writes nothing persistent. One leftover remains from older releases:
|
|
5
|
+
# the ~/.claude/subconscious-gateway.env file from the first persistent setup.
|
|
6
|
+
# This script reports and removes it.
|
|
6
7
|
#
|
|
7
8
|
# subc claude status
|
|
8
9
|
# subc claude uninstall
|
package/bin/runbook/shared.json
CHANGED
|
@@ -51,6 +51,16 @@
|
|
|
51
51
|
"description": "Maximum concurrent Claude/Codex subagent threads.",
|
|
52
52
|
"type": "integer",
|
|
53
53
|
"min": 1
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "SYSTEM_CA",
|
|
57
|
+
"label": "Trust the OS certificate store",
|
|
58
|
+
"description": "Trust certificates from the operating system store, for a TLS-inspecting corporate proxy. Off by default.",
|
|
59
|
+
"type": "choice",
|
|
60
|
+
"strict": true,
|
|
61
|
+
"choices": ["off", "on"],
|
|
62
|
+
"default": "off",
|
|
63
|
+
"template": false
|
|
54
64
|
}
|
|
55
65
|
]
|
|
56
66
|
}
|
package/bin/tui.js
CHANGED
|
@@ -330,7 +330,7 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
|
|
|
330
330
|
});
|
|
331
331
|
if (signal?.aborted) return;
|
|
332
332
|
await enqueuePatch({
|
|
333
|
-
models: catalog.
|
|
333
|
+
models: catalog.defaultModels,
|
|
334
334
|
modelError: catalog.error?.message || '',
|
|
335
335
|
modelSource: catalog.source,
|
|
336
336
|
modelsLoading: false,
|
package/bin/update-check.js
CHANGED
|
@@ -6,6 +6,7 @@ import readline from 'node:readline';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { LOGO_ART_SMALL_LINES } from './branding.js';
|
|
8
8
|
import { c, colorEnabled } from './colors.js';
|
|
9
|
+
import { proxyAwareFetch } from './network.js';
|
|
9
10
|
import { spawnWindows } from './windows/process.js';
|
|
10
11
|
|
|
11
12
|
export const PACKAGE_NAME = 'subconscious-cli';
|
|
@@ -162,7 +163,7 @@ export async function currentVersion() {
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
export async function fetchLatestVersion(options = {}) {
|
|
165
|
-
const fetchImpl = options.fetchImpl ||
|
|
166
|
+
const fetchImpl = options.fetchImpl || proxyAwareFetch;
|
|
166
167
|
const timeoutMs = options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS;
|
|
167
168
|
const signal = options.signal;
|
|
168
169
|
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable');
|
package/bin/upgrade.js
CHANGED
|
@@ -10,6 +10,7 @@ import fs from 'node:fs/promises';
|
|
|
10
10
|
import readline from 'node:readline';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { c } from './colors.js';
|
|
13
|
+
import { proxyAwareFetch } from './network.js';
|
|
13
14
|
import {
|
|
14
15
|
compareVersions,
|
|
15
16
|
detectInstallTarget,
|
|
@@ -42,7 +43,7 @@ export async function currentCliVersion() {
|
|
|
42
43
|
return pkg.version;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export async function fetchLatestVersion(fetchImpl =
|
|
46
|
+
export async function fetchLatestVersion(fetchImpl = proxyAwareFetch) {
|
|
46
47
|
const res = await fetchImpl(
|
|
47
48
|
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
48
49
|
{
|
package/bin/usage.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { getApiKey, getPlatformUrl } from './auth.js';
|
|
6
6
|
import { c } from './colors.js';
|
|
7
|
+
import {
|
|
8
|
+
describeFetchError,
|
|
9
|
+
proxyAwareFetch,
|
|
10
|
+
proxyHint,
|
|
11
|
+
tlsTrustHint,
|
|
12
|
+
} from './network.js';
|
|
7
13
|
import { DEFAULT_PROFILE } from './profiles.js';
|
|
8
14
|
import { printLoginUpgradeWarning } from './upgrade.js';
|
|
9
15
|
|
|
@@ -49,15 +55,6 @@ export function shortModelSlug(slug) {
|
|
|
49
55
|
: text;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export function isLegacyUsagePayload(data) {
|
|
53
|
-
if (!data || typeof data !== 'object') return true;
|
|
54
|
-
if ('credits' in data && data.plan && typeof data.plan === 'object')
|
|
55
|
-
return false;
|
|
56
|
-
return (
|
|
57
|
-
'dailyTokenCeiling' in data || 'consumedToday' in data || !('plan' in data)
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
58
|
export function meterColor(percentage) {
|
|
62
59
|
if (percentage >= 100) return c.red;
|
|
63
60
|
if (percentage >= 80) return c.yellow;
|
|
@@ -85,27 +82,25 @@ export function formatAllowancePercent(percent) {
|
|
|
85
82
|
return `${whole}%`;
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
/**
|
|
85
|
+
/** Percent-only allowance payloads from the current platform. */
|
|
89
86
|
export function describeDailyAllowance(allowance) {
|
|
90
87
|
if (!allowance || typeof allowance !== 'object') return null;
|
|
91
88
|
if (allowance.basis === 'unavailable') return { kind: 'unavailable' };
|
|
92
89
|
if (allowance.basis === 'credit' || allowance.basis === 'tokens') {
|
|
93
90
|
return { kind: allowance.basis, percent: Number(allowance.percent) || 0 };
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
const consumed = Number(allowance.consumed) || 0;
|
|
97
|
-
const amount = Number(allowance.amount) || 0;
|
|
98
|
-
if (amount <= 0) return null;
|
|
99
|
-
return { kind: 'tokens', percent: (consumed / amount) * 100 };
|
|
92
|
+
return null;
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
export function formatUsageDisplay(data) {
|
|
103
96
|
const lines = [];
|
|
104
97
|
lines.push(`\n ${c.bold}Subconscious Usage${c.reset}\n`);
|
|
105
98
|
|
|
106
|
-
const
|
|
99
|
+
const billingTerms = data.billingTerms ?? null;
|
|
100
|
+
const invoiced = billingTerms === 'invoiced';
|
|
107
101
|
const plan = data.plan ?? {};
|
|
108
102
|
const credits = data.credits ?? {};
|
|
103
|
+
const invoice = invoiced ? (data.invoice ?? {}) : {};
|
|
109
104
|
const models = Array.isArray(data.models) ? data.models : [];
|
|
110
105
|
|
|
111
106
|
const planLabel = plan.label || 'No plan';
|
|
@@ -113,55 +108,81 @@ export function formatUsageDisplay(data) {
|
|
|
113
108
|
const planLine = planStatus ? `${planLabel} · ${planStatus}` : planLabel;
|
|
114
109
|
|
|
115
110
|
lines.push(` ${c.dim}Plan${c.reset} ${planLine}`);
|
|
116
|
-
if (
|
|
117
|
-
lines.push(` ${c.dim}Billing${c.reset}
|
|
111
|
+
if (invoiced) {
|
|
112
|
+
lines.push(` ${c.dim}Billing${c.reset} Invoiced monthly (net 30)`);
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
const allowance = plan.dailyAllowance;
|
|
121
|
-
const unlimitedComp = Boolean(plan.isUnlimitedComp);
|
|
122
116
|
|
|
123
|
-
|
|
117
|
+
// Invoiced orgs bill against the spend limit, not a daily grant, so the
|
|
118
|
+
// daily allowance does not pertain to them and is never shown.
|
|
119
|
+
const described = invoiced ? null : describeDailyAllowance(allowance);
|
|
120
|
+
if (described) {
|
|
124
121
|
lines.push('');
|
|
125
122
|
lines.push(` ${c.dim}Daily allowance${c.reset}`);
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
: 'of daily tokens used';
|
|
123
|
+
if (described.kind === 'unavailable') {
|
|
124
|
+
lines.push(` ${c.dim}Daily credit unavailable${c.reset}`);
|
|
125
|
+
lines.push(
|
|
126
|
+
` ${c.dim}Today's credit usage could not be read just now. Your plan and access are unaffected, and the figure returns on its own.${c.reset}`,
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
const label =
|
|
130
|
+
described.kind === 'credit'
|
|
131
|
+
? 'of daily credit used'
|
|
132
|
+
: 'of daily tokens used';
|
|
133
|
+
lines.push(
|
|
134
|
+
` ${renderProgressBar(described.percent)} ${formatAllowancePercent(described.percent)} ${label}`,
|
|
135
|
+
);
|
|
136
|
+
lines.push(` ${c.dim}Resets midnight UTC${c.reset}`);
|
|
137
|
+
if (described.kind === 'credit' && described.percent >= 100) {
|
|
142
138
|
lines.push(
|
|
143
|
-
` ${
|
|
139
|
+
` ${c.yellow}Past today's allowance - further usage bills from your balance.${c.reset}`,
|
|
144
140
|
);
|
|
145
|
-
lines.push(` ${c.dim}Resets midnight UTC${c.reset}`);
|
|
146
|
-
if (described.kind === 'credit' && described.percent >= 100) {
|
|
147
|
-
lines.push(
|
|
148
|
-
` ${c.yellow}Past today's allowance - further usage bills from your balance.${c.reset}`,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
141
|
}
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
145
|
+
if (invoiced) {
|
|
146
|
+
const accrued = invoice.accruedThisPeriodDollars;
|
|
147
|
+
const limit = invoice.limitDollars;
|
|
148
|
+
const periodEnd = invoice.periodEnd ? new Date(invoice.periodEnd) : null;
|
|
149
|
+
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(` ${c.dim}Spend this period${c.reset}`);
|
|
152
|
+
if (accrued == null) {
|
|
153
|
+
lines.push(` ${c.dim}Spend could not be read just now.${c.reset}`);
|
|
154
|
+
lines.push(
|
|
155
|
+
` ${c.dim}Your plan and access are unaffected, and the figure returns on its own.${c.reset}`,
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
const limitText =
|
|
159
|
+
limit != null ? ` of ${formatCurrency(limit)} limit` : '';
|
|
160
|
+
const spendColor = limit != null && accrued >= limit ? c.red : c.reset;
|
|
161
|
+
lines.push(
|
|
162
|
+
` ${spendColor}${formatCurrency(accrued)}${c.reset}${c.dim}${limitText}${c.reset}`,
|
|
163
|
+
);
|
|
164
|
+
if (periodEnd && !Number.isNaN(periodEnd.getTime())) {
|
|
165
|
+
const isoDay = periodEnd.toISOString().slice(0, 10);
|
|
166
|
+
lines.push(` ${c.dim}Period ends ${isoDay}${c.reset}`);
|
|
167
|
+
}
|
|
168
|
+
if (limit != null && accrued >= limit) {
|
|
169
|
+
lines.push(
|
|
170
|
+
` ${c.red}At the spend limit - further requests follow your limit policy.${c.reset}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (credits.balanceDollars != null || data.credits) {
|
|
175
|
+
lines.push('');
|
|
162
176
|
lines.push(
|
|
163
|
-
` ${c.dim}
|
|
177
|
+
` ${c.dim}Credits${c.reset} ${formatCurrency(credits.balanceDollars ?? 0)}`,
|
|
164
178
|
);
|
|
179
|
+
const overage = Number(credits.overageThisPeriodDollars) || 0;
|
|
180
|
+
if (overage > 0 || allowance) {
|
|
181
|
+
const overageColor = overage > 0 ? c.yellow : c.reset;
|
|
182
|
+
lines.push(
|
|
183
|
+
` ${c.dim}Overage${c.reset} ${overageColor}${formatCurrency(overage)} this period${c.reset}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
if (models.length > 0) {
|
|
@@ -181,7 +202,11 @@ export function formatUsageDisplay(data) {
|
|
|
181
202
|
return lines.join('\n');
|
|
182
203
|
}
|
|
183
204
|
|
|
184
|
-
export async function fetchUsage(
|
|
205
|
+
export async function fetchUsage(
|
|
206
|
+
apiKey,
|
|
207
|
+
platformUrl,
|
|
208
|
+
fetchImpl = proxyAwareFetch,
|
|
209
|
+
) {
|
|
185
210
|
const res = await fetchImpl(
|
|
186
211
|
`${platformUrl.replace(/\/$/, '')}${USAGE_API_PATH}`,
|
|
187
212
|
{
|
|
@@ -241,14 +266,6 @@ export async function usageCommand(argv = [], options = {}) {
|
|
|
241
266
|
throw new Error(message);
|
|
242
267
|
}
|
|
243
268
|
|
|
244
|
-
if (isLegacyUsagePayload(data)) {
|
|
245
|
-
console.log(`\n ${c.yellow}Platform upgrade required.${c.reset}`);
|
|
246
|
-
console.log(
|
|
247
|
-
` ${c.dim}This platform host returns a legacy usage shape. Deploy the latest platform or point PLATFORM_URL at a current host.${c.reset}\n`,
|
|
248
|
-
);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
269
|
if (jsonOutput) {
|
|
253
270
|
console.log(JSON.stringify(data, null, 2));
|
|
254
271
|
return;
|
|
@@ -260,10 +277,16 @@ export async function usageCommand(argv = [], options = {}) {
|
|
|
260
277
|
console.log(
|
|
261
278
|
`\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`,
|
|
262
279
|
);
|
|
263
|
-
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}
|
|
280
|
+
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}`);
|
|
281
|
+
const proxy = proxyHint(error, platformUrl);
|
|
282
|
+
if (proxy) console.log(` ${c.dim}${proxy}${c.reset}`);
|
|
283
|
+
console.log();
|
|
264
284
|
return;
|
|
265
285
|
}
|
|
266
|
-
console.error(`\n ${c.red}${error
|
|
286
|
+
console.error(`\n ${c.red}${describeFetchError(error)}${c.reset}`);
|
|
287
|
+
const hint = tlsTrustHint(error) || proxyHint(error, platformUrl);
|
|
288
|
+
if (hint) console.error(` ${c.yellow}${hint}${c.reset}`);
|
|
289
|
+
console.error();
|
|
267
290
|
process.exitCode = 1;
|
|
268
291
|
}
|
|
269
292
|
}
|
package/bin/windows/launch.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
resolveInputs,
|
|
10
10
|
takeFlags,
|
|
11
11
|
} from '../agent-launch.js';
|
|
12
|
-
import { prepareClaudeLaunch } from '../claude-config.js';
|
|
13
12
|
import { claudePickerSettings } from '../claude-picker.js';
|
|
14
13
|
import { modelIds, positiveInteger } from '../launch-values.js';
|
|
15
14
|
import { modelSupportsVision } from '../model-capabilities.js';
|
|
@@ -62,13 +61,7 @@ function plannedLaunch(agent, argv, env) {
|
|
|
62
61
|
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
63
62
|
}
|
|
64
63
|
const plan = claudeLaunch(agent, argv, env);
|
|
65
|
-
return {
|
|
66
|
-
command: plan.argv[0],
|
|
67
|
-
args: plan.argv.slice(1),
|
|
68
|
-
env: plan.env,
|
|
69
|
-
// Runs only when the launch executes, so planning never touches disk.
|
|
70
|
-
prepare: (options) => prepareClaudeLaunch(agent, plan.env, env, options),
|
|
71
|
-
};
|
|
64
|
+
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
function codexCatalog(env, context, maxContext, compact) {
|
|
@@ -274,17 +267,12 @@ export async function windowsLaunch(
|
|
|
274
267
|
throw new Error(`No native Windows launcher is available for ${id}`);
|
|
275
268
|
}
|
|
276
269
|
|
|
277
|
-
export async function executeWindowsLaunch(spec, run
|
|
270
|
+
export async function executeWindowsLaunch(spec, run) {
|
|
278
271
|
try {
|
|
279
|
-
const prepared = spec.prepare?.(prepareOptions);
|
|
280
|
-
for (const warning of prepared?.warnings ?? []) {
|
|
281
|
-
console.error(` ${warning}`);
|
|
282
|
-
}
|
|
283
|
-
// Claude Code would write into the user's own config there.
|
|
284
|
-
if (prepared?.refused) return 1;
|
|
285
|
-
const env = { ...spec.env, ...prepared?.env };
|
|
286
272
|
const options =
|
|
287
|
-
spec.input === undefined
|
|
273
|
+
spec.input === undefined
|
|
274
|
+
? { env: spec.env }
|
|
275
|
+
: { env: spec.env, input: spec.input };
|
|
288
276
|
return await run(spec.command, spec.args, options);
|
|
289
277
|
} finally {
|
|
290
278
|
await spec.cleanup?.();
|
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
|
|
3
|
-
// Each launch rebuilds the subc copies of settings.json and the MCP servers
|
|
4
|
-
// in .claude.json from the user's own files, so anything a subc session
|
|
5
|
-
// changed in those copies is replaced. These helpers find what a session
|
|
6
|
-
// changed, by comparing the copies with what the last launch wrote, so the
|
|
7
|
-
// launch can save it aside and name it instead of losing it silently.
|
|
8
|
-
|
|
9
|
-
const isObject = (value) =>
|
|
10
|
-
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Keys of `current` that a session added or changed since `written`.
|
|
14
|
-
* Removed keys are ignored: Claude Code drops keys on its own.
|
|
15
|
-
*
|
|
16
|
-
* @param {Record<string, unknown>} written what the last launch wrote
|
|
17
|
-
* @param {Record<string, unknown>} current what the copy holds now
|
|
18
|
-
* @param {string[]} [ignored] keys subc keeps across launches
|
|
19
|
-
* @returns {string[]}
|
|
20
|
-
*/
|
|
21
|
-
export function changedKeys(written, current, ignored = []) {
|
|
22
|
-
if (!isObject(written) || !isObject(current)) return [];
|
|
23
|
-
return Object.keys(current).filter(
|
|
24
|
-
(key) =>
|
|
25
|
-
!ignored.includes(key) && !isDeepStrictEqual(current[key], written[key]),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The MCP servers of a .claude.json: user scope and each project. */
|
|
30
|
-
export function mcpServersOf(state) {
|
|
31
|
-
const projects = isObject(state?.projects) ? state.projects : {};
|
|
32
|
-
return {
|
|
33
|
-
mcpServers: isObject(state?.mcpServers) ? state.mcpServers : {},
|
|
34
|
-
projects: Object.fromEntries(
|
|
35
|
-
Object.entries(projects)
|
|
36
|
-
.filter(([, project]) => isObject(project?.mcpServers))
|
|
37
|
-
.map(([name, project]) => [name, project.mcpServers]),
|
|
38
|
-
),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* MCP server names a session added or changed since `written`, as
|
|
44
|
-
* `name` for user scope and `name (project)` for a project.
|
|
45
|
-
*
|
|
46
|
-
* @param {ReturnType<typeof mcpServersOf>} written
|
|
47
|
-
* @param {ReturnType<typeof mcpServersOf>} current
|
|
48
|
-
* @returns {string[]}
|
|
49
|
-
*/
|
|
50
|
-
export function changedMcpServers(written, current) {
|
|
51
|
-
const changed = changedKeys(written.mcpServers, current.mcpServers);
|
|
52
|
-
for (const [project, servers] of Object.entries(current.projects)) {
|
|
53
|
-
for (const name of changedKeys(written.projects[project] ?? {}, servers)) {
|
|
54
|
-
changed.push(`${name} (${project})`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return changed;
|
|
58
|
-
}
|