myshell-tools 2.4.0 → 2.7.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/CHANGELOG.md +24 -0
- package/README.md +28 -12
- package/dist/cli.js +38 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/cost.js +4 -1
- package/dist/commands/cost.js.map +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/login.d.ts +51 -5
- package/dist/commands/login.js +207 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/core/assess.js +2 -62
- package/dist/core/assess.js.map +1 -1
- package/dist/core/budget.d.ts +26 -0
- package/dist/core/budget.js +37 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/history.d.ts +35 -0
- package/dist/core/history.js +116 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/json-envelope.d.ts +49 -0
- package/dist/core/json-envelope.js +117 -0
- package/dist/core/json-envelope.js.map +1 -0
- package/dist/core/orchestrate.js +107 -8
- package/dist/core/orchestrate.js.map +1 -1
- package/dist/core/policy.js +17 -9
- package/dist/core/policy.js.map +1 -1
- package/dist/core/prompt.d.ts +9 -4
- package/dist/core/prompt.js +14 -5
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/review.js +2 -49
- package/dist/core/review.js.map +1 -1
- package/dist/core/route.d.ts +13 -5
- package/dist/core/route.js +20 -6
- package/dist/core/route.js.map +1 -1
- package/dist/core/types.d.ts +37 -0
- package/dist/infra/credentials.d.ts +58 -0
- package/dist/infra/credentials.js +172 -0
- package/dist/infra/credentials.js.map +1 -0
- package/dist/infra/pricing.d.ts +17 -4
- package/dist/infra/pricing.js +73 -3
- package/dist/infra/pricing.js.map +1 -1
- package/dist/interface/menu.d.ts +26 -0
- package/dist/interface/menu.js +131 -26
- package/dist/interface/menu.js.map +1 -1
- package/dist/providers/detect.d.ts +17 -5
- package/dist/providers/detect.js +56 -4
- package/dist/providers/detect.js.map +1 -1
- package/dist/providers/install.js +1 -0
- package/dist/providers/install.js.map +1 -1
- package/dist/providers/opencode-parse.d.ts +49 -0
- package/dist/providers/opencode-parse.js +181 -0
- package/dist/providers/opencode-parse.js.map +1 -0
- package/dist/providers/opencode.d.ts +43 -0
- package/dist/providers/opencode.js +121 -0
- package/dist/providers/opencode.js.map +1 -0
- package/dist/providers/port.d.ts +1 -1
- package/dist/providers/registry.d.ts +2 -2
- package/dist/providers/registry.js +6 -2
- package/dist/providers/registry.js.map +1 -1
- package/package.json +2 -2
package/dist/core/route.d.ts
CHANGED
|
@@ -16,13 +16,21 @@ import type { ProviderId } from '../providers/port.js';
|
|
|
16
16
|
* Algorithm:
|
|
17
17
|
* 1. Walk `policy.providerOrderByTier[tier]` in order.
|
|
18
18
|
* 2. For the first provider that is present in `available`, resolve the
|
|
19
|
-
* cheapest model for that provider+tier via `getCheapestForTier
|
|
19
|
+
* cheapest model for that provider+tier via `getCheapestForTier`, further
|
|
20
|
+
* restricted to `availableModels[provider]` when that set is non-empty.
|
|
20
21
|
* 3. If none of the policy-preferred providers are available but `available`
|
|
21
22
|
* is non-empty, fall back to the globally cheapest model for that tier.
|
|
22
23
|
* 4. If `available` is empty, throw — there is nothing to route to.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* The `availableModels` parameter is additive/opt-in:
|
|
26
|
+
* - When absent or undefined → behaviour is IDENTICAL to today (no change).
|
|
27
|
+
* - When a provider's entry is present and non-empty → prefer a model in that
|
|
28
|
+
* list; if none match the pricing table, fall back to cheapest-for-tier
|
|
29
|
+
* (graceful degradation, never throws, never returns nothing).
|
|
30
|
+
*
|
|
31
|
+
* @param tier - The orchestration tier to route.
|
|
32
|
+
* @param available - Provider IDs that are currently reachable.
|
|
33
|
+
* @param policy - Active routing policy (from `DEFAULT_POLICY` or overrides).
|
|
34
|
+
* @param availableModels - Optional per-provider advertised model sets from detection.
|
|
27
35
|
*/
|
|
28
|
-
export declare function route(tier: Tier, available: ProviderId[], policy: Policy): RouteDecision;
|
|
36
|
+
export declare function route(tier: Tier, available: ProviderId[], policy: Policy, availableModels?: Partial<Record<ProviderId, readonly string[]>>): RouteDecision;
|
package/dist/core/route.js
CHANGED
|
@@ -15,16 +15,24 @@ import { getCheapestForTier } from '../infra/pricing.js';
|
|
|
15
15
|
* Algorithm:
|
|
16
16
|
* 1. Walk `policy.providerOrderByTier[tier]` in order.
|
|
17
17
|
* 2. For the first provider that is present in `available`, resolve the
|
|
18
|
-
* cheapest model for that provider+tier via `getCheapestForTier
|
|
18
|
+
* cheapest model for that provider+tier via `getCheapestForTier`, further
|
|
19
|
+
* restricted to `availableModels[provider]` when that set is non-empty.
|
|
19
20
|
* 3. If none of the policy-preferred providers are available but `available`
|
|
20
21
|
* is non-empty, fall back to the globally cheapest model for that tier.
|
|
21
22
|
* 4. If `available` is empty, throw — there is nothing to route to.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The `availableModels` parameter is additive/opt-in:
|
|
25
|
+
* - When absent or undefined → behaviour is IDENTICAL to today (no change).
|
|
26
|
+
* - When a provider's entry is present and non-empty → prefer a model in that
|
|
27
|
+
* list; if none match the pricing table, fall back to cheapest-for-tier
|
|
28
|
+
* (graceful degradation, never throws, never returns nothing).
|
|
29
|
+
*
|
|
30
|
+
* @param tier - The orchestration tier to route.
|
|
31
|
+
* @param available - Provider IDs that are currently reachable.
|
|
32
|
+
* @param policy - Active routing policy (from `DEFAULT_POLICY` or overrides).
|
|
33
|
+
* @param availableModels - Optional per-provider advertised model sets from detection.
|
|
26
34
|
*/
|
|
27
|
-
export function route(tier, available, policy) {
|
|
35
|
+
export function route(tier, available, policy, availableModels) {
|
|
28
36
|
if (available.length === 0) {
|
|
29
37
|
throw new Error(`route: no providers available for tier "${tier}" — start at least one provider`);
|
|
30
38
|
}
|
|
@@ -32,7 +40,13 @@ export function route(tier, available, policy) {
|
|
|
32
40
|
// Walk the preferred order and pick the first available provider.
|
|
33
41
|
for (const preferred of preferredOrder) {
|
|
34
42
|
if (available.includes(preferred)) {
|
|
35
|
-
|
|
43
|
+
// When advertised models are supplied for this provider, pass them as
|
|
44
|
+
// the allowed-model filter so we prefer a model the CLI actually has.
|
|
45
|
+
const providerAllowed = availableModels?.[preferred];
|
|
46
|
+
const allowedSet = providerAllowed !== undefined && providerAllowed.length > 0
|
|
47
|
+
? providerAllowed
|
|
48
|
+
: undefined;
|
|
49
|
+
const pricing = getCheapestForTier(tier, [preferred], allowedSet);
|
|
36
50
|
return {
|
|
37
51
|
tier,
|
|
38
52
|
provider: preferred,
|
package/dist/core/route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/core/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/core/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,KAAK,CACnB,IAAU,EACV,SAAuB,EACvB,MAAc,EACd,eAAgE;IAEhE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,2CAA2C,IAAI,iCAAiC,CACjF,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAExD,kEAAkE;IAClE,KAAK,MAAM,SAAS,IAAI,cAAc,EAAE,CAAC;QACvC,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,sEAAsE;YACtE,sEAAsE;YACtE,MAAM,eAAe,GAAG,eAAe,EAAE,CAAC,SAAS,CAAC,CAAC;YACrD,MAAM,UAAU,GACd,eAAe,KAAK,SAAS,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC;gBACzD,CAAC,CAAC,eAAe;gBACjB,CAAC,CAAC,SAAS,CAAC;YAChB,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;YAClE,OAAO;gBACL,IAAI;gBACJ,QAAQ,EAAE,SAAS;gBACnB,KAAK,EAAE,OAAO,CAAC,KAAK;aACrB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,0DAA0D;IAC1D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE,QAAQ,CAAC,QAAsB;QACzC,KAAK,EAAE,QAAQ,CAAC,KAAK;KACtB,CAAC;AACJ,CAAC"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -85,6 +85,25 @@ export interface Policy {
|
|
|
85
85
|
readonly escalateBelowConfidence: Record<Risk, number>;
|
|
86
86
|
/** Ordered provider preference per tier; route() honours availability. */
|
|
87
87
|
readonly providerOrderByTier: Record<Tier, readonly ProviderId[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Controls when cross-vendor review runs automatically.
|
|
90
|
+
*
|
|
91
|
+
* - `'auto'` : review when risk is high/critical OR the model sets needsReview
|
|
92
|
+
* (current default behaviour).
|
|
93
|
+
* - `'critical-only'` : review only when risk is `critical` (or needsReview AND critical).
|
|
94
|
+
* - `'off'` : never trigger an automatic cross-vendor review.
|
|
95
|
+
*
|
|
96
|
+
* Omitting the field is equivalent to `'auto'` (backward-compatible).
|
|
97
|
+
*/
|
|
98
|
+
readonly reviewPolicy?: 'auto' | 'critical-only' | 'off';
|
|
99
|
+
/**
|
|
100
|
+
* Per-task cost budget cap in USD. When `totalCostUsd` reaches or exceeds
|
|
101
|
+
* this value, orchestrate() stops spending (no new escalation, no new review)
|
|
102
|
+
* and accepts the best result produced so far.
|
|
103
|
+
*
|
|
104
|
+
* `null` or `undefined` (the default) means no cap is applied.
|
|
105
|
+
*/
|
|
106
|
+
readonly maxCostUsd?: number | null;
|
|
88
107
|
}
|
|
89
108
|
export interface OrchestrateDeps {
|
|
90
109
|
/** Available providers, keyed by id. Absent key = provider unavailable. */
|
|
@@ -96,6 +115,24 @@ export interface OrchestrateDeps {
|
|
|
96
115
|
readonly cwd: string;
|
|
97
116
|
readonly sandbox: SandboxLevel;
|
|
98
117
|
readonly timeoutMs: number;
|
|
118
|
+
/**
|
|
119
|
+
* Prior conversation history for context continuity. When provided, the most
|
|
120
|
+
* recent turns are compacted and injected into the first provider prompt so
|
|
121
|
+
* stateless one-shot providers (claude -p / codex exec) have multi-turn
|
|
122
|
+
* awareness. Leave undefined for fresh (one-shot) sessions.
|
|
123
|
+
*/
|
|
124
|
+
readonly history?: readonly SessionEntry[];
|
|
125
|
+
/**
|
|
126
|
+
* Advertised model lists from provider detection, keyed by provider id.
|
|
127
|
+
* When supplied, route() restricts candidates to models that the provider CLI
|
|
128
|
+
* actually advertises, preventing the CLI from routing to a model it cannot run.
|
|
129
|
+
*
|
|
130
|
+
* Absence (undefined) or an empty list for a provider → fall back to the
|
|
131
|
+
* standard cheapest-for-tier pricing-table behaviour (backward-compatible).
|
|
132
|
+
*
|
|
133
|
+
* Only include providers that are installed; exactOptionalPropertyTypes is ON.
|
|
134
|
+
*/
|
|
135
|
+
readonly availableModels?: Partial<Record<ProviderId, readonly string[]>>;
|
|
99
136
|
}
|
|
100
137
|
/**
|
|
101
138
|
* High-level events emitted by orchestrate(). The interface/render layer
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/credentials.ts — Persisted credential store for myshell-tools.
|
|
3
|
+
*
|
|
4
|
+
* Stores a small JSON file at <homeDir>/.myshell-tools/credentials.json with
|
|
5
|
+
* shape `{ claudeOauthToken?: string }` so the Claude OAuth token captured
|
|
6
|
+
* during `myshell-tools login claude --code` is available across restarts.
|
|
7
|
+
*
|
|
8
|
+
* On startup, `applyStoredCredentials` injects the token into `process.env` so
|
|
9
|
+
* that both the provider detection (`claude auth status`) and the spawned
|
|
10
|
+
* `claude -p …` child process see it via the inherited environment.
|
|
11
|
+
*
|
|
12
|
+
* Security: the file is written with mode 0o600 (owner-read-only) on POSIX
|
|
13
|
+
* systems. The chmod is best-effort — a failure is silently ignored so the
|
|
14
|
+
* function never throws on Windows or unusual filesystems.
|
|
15
|
+
*/
|
|
16
|
+
export interface Credentials {
|
|
17
|
+
claudeOauthToken?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load stored credentials. Never throws — missing or corrupt files return `{}`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadCredentials(homeDir?: string): Promise<Credentials>;
|
|
23
|
+
/**
|
|
24
|
+
* Persist the Claude OAuth token atomically to
|
|
25
|
+
* `~/.myshell-tools/credentials.json` with restrictive permissions (0o600).
|
|
26
|
+
*/
|
|
27
|
+
export declare function saveClaudeToken(token: string, homeDir?: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Remove the stored Claude OAuth token. Writes the file back without the
|
|
30
|
+
* token key so any future credential fields are preserved.
|
|
31
|
+
* Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export declare function clearClaudeToken(homeDir?: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Inject a previously-saved Claude OAuth token into `env` if:
|
|
36
|
+
* 1. A token is stored in `~/.myshell-tools/credentials.json`, AND
|
|
37
|
+
* 2. `env.CLAUDE_CODE_OAUTH_TOKEN` is not already set (user's explicit env wins).
|
|
38
|
+
*
|
|
39
|
+
* Called once at the very top of `main()` so every code path — detection,
|
|
40
|
+
* spawned `claude -p …`, and the menu — sees the token without the user
|
|
41
|
+
* needing to export it manually.
|
|
42
|
+
*
|
|
43
|
+
* Never throws.
|
|
44
|
+
*/
|
|
45
|
+
export declare function applyStoredCredentials(env: NodeJS.ProcessEnv, homeDir?: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Extract the first Claude long-lived OAuth token from `text`.
|
|
48
|
+
*
|
|
49
|
+
* Token format: `sk-ant-oat` followed by optional digits/lowercase-letters,
|
|
50
|
+
* then a dash, then one or more Base64url characters (`[A-Za-z0-9_-]+`).
|
|
51
|
+
*
|
|
52
|
+
* Returns `null` when no token is found. Never throws.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* extractClaudeToken('Token: sk-ant-oat01-abc-XYZ123') // → 'sk-ant-oat01-abc-XYZ123'
|
|
56
|
+
* extractClaudeToken('no token here') // → null
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractClaudeToken(text: string): string | null;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/credentials.ts — Persisted credential store for myshell-tools.
|
|
3
|
+
*
|
|
4
|
+
* Stores a small JSON file at <homeDir>/.myshell-tools/credentials.json with
|
|
5
|
+
* shape `{ claudeOauthToken?: string }` so the Claude OAuth token captured
|
|
6
|
+
* during `myshell-tools login claude --code` is available across restarts.
|
|
7
|
+
*
|
|
8
|
+
* On startup, `applyStoredCredentials` injects the token into `process.env` so
|
|
9
|
+
* that both the provider detection (`claude auth status`) and the spawned
|
|
10
|
+
* `claude -p …` child process see it via the inherited environment.
|
|
11
|
+
*
|
|
12
|
+
* Security: the file is written with mode 0o600 (owner-read-only) on POSIX
|
|
13
|
+
* systems. The chmod is best-effort — a failure is silently ignored so the
|
|
14
|
+
* function never throws on Windows or unusual filesystems.
|
|
15
|
+
*/
|
|
16
|
+
import { mkdir, readFile, chmod } from 'node:fs/promises';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { atomicWrite } from './atomic.js';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Path helpers (pure)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function getCredentialsDir(home) {
|
|
24
|
+
return join(home, '.myshell-tools');
|
|
25
|
+
}
|
|
26
|
+
function getCredentialsPath(home) {
|
|
27
|
+
return join(getCredentialsDir(home), 'credentials.json');
|
|
28
|
+
}
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Internal parse helper
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
/**
|
|
33
|
+
* Parse credentials from raw JSON text. Returns `{}` on any error so callers
|
|
34
|
+
* never need to handle thrown exceptions from the load path.
|
|
35
|
+
*/
|
|
36
|
+
function parseCredentials(raw) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const obj = parsed;
|
|
43
|
+
const result = {};
|
|
44
|
+
if (typeof obj['claudeOauthToken'] === 'string' && obj['claudeOauthToken'].length > 0) {
|
|
45
|
+
result.claudeOauthToken = obj['claudeOauthToken'];
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Public API
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Load stored credentials. Never throws — missing or corrupt files return `{}`.
|
|
58
|
+
*/
|
|
59
|
+
export async function loadCredentials(homeDir) {
|
|
60
|
+
const home = homeDir ?? homedir();
|
|
61
|
+
try {
|
|
62
|
+
const raw = await readFile(getCredentialsPath(home), 'utf8');
|
|
63
|
+
return parseCredentials(raw);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Persist the Claude OAuth token atomically to
|
|
71
|
+
* `~/.myshell-tools/credentials.json` with restrictive permissions (0o600).
|
|
72
|
+
*/
|
|
73
|
+
export async function saveClaudeToken(token, homeDir) {
|
|
74
|
+
const home = homeDir ?? homedir();
|
|
75
|
+
const dir = getCredentialsDir(home);
|
|
76
|
+
const path = getCredentialsPath(home);
|
|
77
|
+
await mkdir(dir, { recursive: true });
|
|
78
|
+
// Load existing credentials so we only replace the token key, preserving others.
|
|
79
|
+
const existing = await loadCredentials(homeDir);
|
|
80
|
+
const updated = { ...existing, claudeOauthToken: token };
|
|
81
|
+
// atomicWrite uses rename — ensures no partial writes on crash.
|
|
82
|
+
await atomicWrite(path, JSON.stringify(updated, null, 2));
|
|
83
|
+
// Best-effort: restrict to owner-read-only. Silently ignored on Windows or
|
|
84
|
+
// unusual filesystems where chmod is unavailable or unsupported.
|
|
85
|
+
try {
|
|
86
|
+
await chmod(path, 0o600);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Cross-platform best-effort only — never throws
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Remove the stored Claude OAuth token. Writes the file back without the
|
|
94
|
+
* token key so any future credential fields are preserved.
|
|
95
|
+
* Never throws.
|
|
96
|
+
*/
|
|
97
|
+
export async function clearClaudeToken(homeDir) {
|
|
98
|
+
try {
|
|
99
|
+
const home = homeDir ?? homedir();
|
|
100
|
+
const dir = getCredentialsDir(home);
|
|
101
|
+
const path = getCredentialsPath(home);
|
|
102
|
+
await mkdir(dir, { recursive: true });
|
|
103
|
+
// Load the raw file to preserve unknown future keys.
|
|
104
|
+
let rawObj = {};
|
|
105
|
+
try {
|
|
106
|
+
const raw = await readFile(path, 'utf8');
|
|
107
|
+
const parsed = JSON.parse(raw);
|
|
108
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
109
|
+
rawObj = parsed;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Missing or corrupt file — start from empty
|
|
114
|
+
}
|
|
115
|
+
delete rawObj['claudeOauthToken'];
|
|
116
|
+
await atomicWrite(path, JSON.stringify(rawObj, null, 2));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Never throws — clear is best-effort
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Inject a previously-saved Claude OAuth token into `env` if:
|
|
124
|
+
* 1. A token is stored in `~/.myshell-tools/credentials.json`, AND
|
|
125
|
+
* 2. `env.CLAUDE_CODE_OAUTH_TOKEN` is not already set (user's explicit env wins).
|
|
126
|
+
*
|
|
127
|
+
* Called once at the very top of `main()` so every code path — detection,
|
|
128
|
+
* spawned `claude -p …`, and the menu — sees the token without the user
|
|
129
|
+
* needing to export it manually.
|
|
130
|
+
*
|
|
131
|
+
* Never throws.
|
|
132
|
+
*/
|
|
133
|
+
export async function applyStoredCredentials(env, homeDir) {
|
|
134
|
+
try {
|
|
135
|
+
// Never overwrite an explicitly-set env var — user's env wins.
|
|
136
|
+
if (env['CLAUDE_CODE_OAUTH_TOKEN'] !== undefined) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const creds = await loadCredentials(homeDir);
|
|
140
|
+
if (creds.claudeOauthToken !== undefined) {
|
|
141
|
+
env['CLAUDE_CODE_OAUTH_TOKEN'] = creds.claudeOauthToken;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Never throws — startup injection is best-effort
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Pure token extraction helper
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Extract the first Claude long-lived OAuth token from `text`.
|
|
153
|
+
*
|
|
154
|
+
* Token format: `sk-ant-oat` followed by optional digits/lowercase-letters,
|
|
155
|
+
* then a dash, then one or more Base64url characters (`[A-Za-z0-9_-]+`).
|
|
156
|
+
*
|
|
157
|
+
* Returns `null` when no token is found. Never throws.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* extractClaudeToken('Token: sk-ant-oat01-abc-XYZ123') // → 'sk-ant-oat01-abc-XYZ123'
|
|
161
|
+
* extractClaudeToken('no token here') // → null
|
|
162
|
+
*/
|
|
163
|
+
export function extractClaudeToken(text) {
|
|
164
|
+
try {
|
|
165
|
+
const match = text.match(/sk-ant-oat[0-9a-z]*-[A-Za-z0-9_-]+/);
|
|
166
|
+
return match !== null && match[0] !== undefined ? match[0] : null;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/infra/credentials.ts"],"names":[],"mappings":"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"}
|
package/dist/infra/pricing.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Captured : 2026-05-29
|
|
11
11
|
*/
|
|
12
12
|
export interface ModelPricing {
|
|
13
|
-
readonly provider: 'claude' | 'codex';
|
|
13
|
+
readonly provider: 'claude' | 'codex' | 'opencode';
|
|
14
14
|
readonly model: string;
|
|
15
15
|
readonly aliases: readonly string[];
|
|
16
16
|
readonly tier: 'worker' | 'ic' | 'manager';
|
|
@@ -35,11 +35,24 @@ export declare function getModelPricing(provider: string, model: string): ModelP
|
|
|
35
35
|
export declare function calculateCost(inputTokens: number, outputTokens: number, pricing: ModelPricing): number;
|
|
36
36
|
/**
|
|
37
37
|
* Return the cheapest model (lowest inputPer1M) for a given tier,
|
|
38
|
-
* optionally restricted to the supplied provider IDs.
|
|
38
|
+
* optionally restricted to the supplied provider IDs and/or an allowed-model set.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* When `allowedModels` is supplied and non-empty for the relevant provider(s),
|
|
41
|
+
* only models whose `model` id or any alias appears in `allowedModels` are
|
|
42
|
+
* considered. The match is case-insensitive (mirrors getModelPricing behaviour).
|
|
43
|
+
* If no candidates survive the allowed-model filter, the filter is ignored and
|
|
44
|
+
* the full provider-scoped set is used (graceful degradation — never throws due
|
|
45
|
+
* to a missing advertised model).
|
|
46
|
+
*
|
|
47
|
+
* Throws if no matching model exists (no tier entries at all, or no entries for
|
|
48
|
+
* the given providers).
|
|
49
|
+
*
|
|
50
|
+
* @param tier - Orchestration tier to select for.
|
|
51
|
+
* @param availableProviders - Restrict to these provider IDs when supplied.
|
|
52
|
+
* @param allowedModels - Further restrict to models advertised by the CLI.
|
|
53
|
+
* The set contains model IDs and/or aliases (any case).
|
|
41
54
|
*/
|
|
42
|
-
export declare function getCheapestForTier(tier: 'worker' | 'ic' | 'manager', availableProviders?: string[]): ModelPricing;
|
|
55
|
+
export declare function getCheapestForTier(tier: 'worker' | 'ic' | 'manager', availableProviders?: string[], allowedModels?: readonly string[]): ModelPricing;
|
|
43
56
|
/**
|
|
44
57
|
* Returns true when the pricing table is older than maxAgeDays (default 90).
|
|
45
58
|
* Useful for emitting a staleness warning at runtime.
|
package/dist/infra/pricing.js
CHANGED
|
@@ -17,6 +17,7 @@ export const PRICING_TABLE = {
|
|
|
17
17
|
sourceUrls: [
|
|
18
18
|
'https://www.anthropic.com/pricing',
|
|
19
19
|
'https://platform.openai.com/docs/pricing',
|
|
20
|
+
'https://opencode.ai/docs',
|
|
20
21
|
],
|
|
21
22
|
models: [
|
|
22
23
|
// ---- Anthropic / Claude ------------------------------------------------
|
|
@@ -93,6 +94,48 @@ export const PRICING_TABLE = {
|
|
|
93
94
|
outputPer1M: 14,
|
|
94
95
|
contextWindow: 128_000,
|
|
95
96
|
},
|
|
97
|
+
// ---- opencode ----------------------------------------------------------
|
|
98
|
+
// opencode ships free models whose real cost is reported at runtime via the
|
|
99
|
+
// step_finish `cost` field in JSONL output (see opencode.ts). The zero-cost
|
|
100
|
+
// entries below exist solely for model SELECTION by getCheapestForTier/route
|
|
101
|
+
// when opencode is the only available provider. They must NOT displace
|
|
102
|
+
// claude/codex in the pricing sort when those providers are also available,
|
|
103
|
+
// because opencode's cost=0 entries would always win. The route() function
|
|
104
|
+
// respects providerOrderByTier (opencode last) before falling back to the
|
|
105
|
+
// pricing table, so this zero-cost sentinel is safe.
|
|
106
|
+
{
|
|
107
|
+
provider: 'opencode',
|
|
108
|
+
model: 'opencode/mimo-v2.5-free',
|
|
109
|
+
aliases: ['mimo-v2.5-free', 'opencode-worker'],
|
|
110
|
+
tier: 'worker',
|
|
111
|
+
// Real cost is reported at runtime by opencode's step_finish event.
|
|
112
|
+
// Zero here is a placeholder for model selection only — not for billing.
|
|
113
|
+
inputPer1M: 0,
|
|
114
|
+
outputPer1M: 0,
|
|
115
|
+
contextWindow: 32_000,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
provider: 'opencode',
|
|
119
|
+
model: 'opencode/deepseek-v4-flash-free',
|
|
120
|
+
aliases: ['deepseek-v4-flash-free', 'opencode-free'],
|
|
121
|
+
tier: 'ic',
|
|
122
|
+
// Real cost is reported at runtime by opencode's step_finish event.
|
|
123
|
+
// Zero here is a placeholder for model selection only — not for billing.
|
|
124
|
+
inputPer1M: 0,
|
|
125
|
+
outputPer1M: 0,
|
|
126
|
+
contextWindow: 128_000,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
provider: 'opencode',
|
|
130
|
+
model: 'opencode/big-pickle',
|
|
131
|
+
aliases: ['big-pickle', 'opencode-manager'],
|
|
132
|
+
tier: 'manager',
|
|
133
|
+
// Real cost is reported at runtime by opencode's step_finish event.
|
|
134
|
+
// Zero here is a placeholder for model selection only — not for billing.
|
|
135
|
+
inputPer1M: 0,
|
|
136
|
+
outputPer1M: 0,
|
|
137
|
+
contextWindow: 128_000,
|
|
138
|
+
},
|
|
96
139
|
],
|
|
97
140
|
};
|
|
98
141
|
// ---------------------------------------------------------------------------
|
|
@@ -118,11 +161,24 @@ export function calculateCost(inputTokens, outputTokens, pricing) {
|
|
|
118
161
|
}
|
|
119
162
|
/**
|
|
120
163
|
* Return the cheapest model (lowest inputPer1M) for a given tier,
|
|
121
|
-
* optionally restricted to the supplied provider IDs.
|
|
164
|
+
* optionally restricted to the supplied provider IDs and/or an allowed-model set.
|
|
122
165
|
*
|
|
123
|
-
*
|
|
166
|
+
* When `allowedModels` is supplied and non-empty for the relevant provider(s),
|
|
167
|
+
* only models whose `model` id or any alias appears in `allowedModels` are
|
|
168
|
+
* considered. The match is case-insensitive (mirrors getModelPricing behaviour).
|
|
169
|
+
* If no candidates survive the allowed-model filter, the filter is ignored and
|
|
170
|
+
* the full provider-scoped set is used (graceful degradation — never throws due
|
|
171
|
+
* to a missing advertised model).
|
|
172
|
+
*
|
|
173
|
+
* Throws if no matching model exists (no tier entries at all, or no entries for
|
|
174
|
+
* the given providers).
|
|
175
|
+
*
|
|
176
|
+
* @param tier - Orchestration tier to select for.
|
|
177
|
+
* @param availableProviders - Restrict to these provider IDs when supplied.
|
|
178
|
+
* @param allowedModels - Further restrict to models advertised by the CLI.
|
|
179
|
+
* The set contains model IDs and/or aliases (any case).
|
|
124
180
|
*/
|
|
125
|
-
export function getCheapestForTier(tier, availableProviders) {
|
|
181
|
+
export function getCheapestForTier(tier, availableProviders, allowedModels) {
|
|
126
182
|
let candidates = PRICING_TABLE.models.filter((m) => m.tier === tier);
|
|
127
183
|
if (availableProviders && availableProviders.length > 0) {
|
|
128
184
|
candidates = candidates.filter((m) => availableProviders.includes(m.provider));
|
|
@@ -131,6 +187,20 @@ export function getCheapestForTier(tier, availableProviders) {
|
|
|
131
187
|
throw new Error(`No models available for tier "${tier}"` +
|
|
132
188
|
(availableProviders ? ` with providers [${availableProviders.join(', ')}]` : ''));
|
|
133
189
|
}
|
|
190
|
+
// When an allowed-model set is provided and non-empty, further restrict
|
|
191
|
+
// candidates to those whose model id or any alias appears in the set.
|
|
192
|
+
// Case-insensitive to match getModelPricing behaviour.
|
|
193
|
+
if (allowedModels !== undefined && allowedModels.length > 0) {
|
|
194
|
+
const allowed = new Set(allowedModels.map((a) => a.toLowerCase()));
|
|
195
|
+
const filtered = candidates.filter((m) => allowed.has(m.model.toLowerCase()) ||
|
|
196
|
+
m.aliases.some((a) => allowed.has(a.toLowerCase())));
|
|
197
|
+
// Graceful degradation: if the filter eliminates all candidates (e.g. the
|
|
198
|
+
// provider advertised a model not yet in our pricing table), fall back to the
|
|
199
|
+
// full provider-scoped set — never throw, never return nothing.
|
|
200
|
+
if (filtered.length > 0) {
|
|
201
|
+
candidates = filtered;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
134
204
|
// Primary sort: inputPer1M ascending; secondary: outputPer1M ascending
|
|
135
205
|
return candidates.reduce((cheapest, m) => m.inputPer1M < cheapest.inputPer1M ||
|
|
136
206
|
(m.inputPer1M === cheapest.inputPer1M && m.outputPer1M < cheapest.outputPer1M)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/infra/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsBH,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,IAAI,EAAE,YAAY;IAClB,UAAU,EAAE;QACV,mCAAmC;QACnC,0CAA0C;
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/infra/pricing.ts"],"names":[],"mappings":"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"}
|
package/dist/interface/menu.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { SpendSummary } from '../infra/insights.js';
|
|
|
22
22
|
import type { EnvironmentStatus } from '../providers/detect.js';
|
|
23
23
|
import type { Provider, ProviderId, SandboxLevel } from '../providers/port.js';
|
|
24
24
|
import type { OutputSink } from './render.js';
|
|
25
|
+
import type { LoginMethod } from '../commands/login.js';
|
|
25
26
|
export interface MenuContext {
|
|
26
27
|
readonly version: string;
|
|
27
28
|
readonly clock: Clock;
|
|
@@ -41,6 +42,31 @@ export interface MenuContext {
|
|
|
41
42
|
* Returns the next trimmed line of input, or `null` on EOF/close.
|
|
42
43
|
*/
|
|
43
44
|
readonly readLine?: () => Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional injected installProvider for testing. When provided, `startMenu`
|
|
47
|
+
* uses this instead of the real `installProvider` from providers/install.ts,
|
|
48
|
+
* preventing real `npm install -g …` subprocesses from spawning during tests.
|
|
49
|
+
*/
|
|
50
|
+
readonly installProvider?: (id: ProviderId, out: OutputSink) => Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Optional injected login function for testing. When provided, `startMenu`
|
|
53
|
+
* uses this instead of the real `runLogin` from commands/login.ts, preventing
|
|
54
|
+
* real `claude`/`codex login` subprocesses from spawning during tests.
|
|
55
|
+
*
|
|
56
|
+
* The third argument mirrors the `opts` parameter of `runLogin` so the menu
|
|
57
|
+
* can pass the shared `readLine` function for the token-paste prompt.
|
|
58
|
+
*/
|
|
59
|
+
readonly login?: (out: OutputSink, providerArg?: string, opts?: {
|
|
60
|
+
method?: LoginMethod;
|
|
61
|
+
readLine?: () => Promise<string | null>;
|
|
62
|
+
}) => Promise<number>;
|
|
63
|
+
/**
|
|
64
|
+
* Optional injected detectEnvironment for testing. When provided, `startMenu`
|
|
65
|
+
* uses this instead of the real `detectEnvironment` from providers/detect.ts,
|
|
66
|
+
* preventing real `claude`/`codex`/`opencode --version` subprocesses from
|
|
67
|
+
* spawning during tests (e.g. after first-run onboarding or [j]/[k]/[o] login).
|
|
68
|
+
*/
|
|
69
|
+
readonly detectEnvironment?: () => Promise<EnvironmentStatus>;
|
|
44
70
|
}
|
|
45
71
|
/**
|
|
46
72
|
* Return the shell alias hint the user can add to their shell profile to make
|