myshell-tools 2.3.0 → 2.6.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 +20 -0
- package/README.md +26 -10
- package/dist/cli.js +43 -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/install.d.ts +66 -0
- package/dist/commands/install.js +174 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/login.d.ts +41 -2
- package/dist/commands/login.js +116 -11
- 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/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 +12 -0
- package/dist/interface/menu.js +110 -25
- 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.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
|
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":"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;QAC1C,0BAA0B;KAC3B;IACD,MAAM,EAAE;QACN,2EAA2E;QAC3E;YACE,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,iBAAiB;YACxB,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,iBAAiB,CAAC;YAChD,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,EAAE;YACf,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,mBAAmB,CAAC;YACtD,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,EAAE;YACf,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,kBAAkB,CAAC;YACnD,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,GAAG;YACf,WAAW,EAAE,CAAC;YACd,aAAa,EAAE,OAAO;SACvB;QAED,2EAA2E;QAC3E;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;YAC9B,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,EAAE;YACf,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;YAC9B,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,GAAG;YACf,WAAW,EAAE,EAAE;YACf,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,cAAc;YACrB,OAAO,EAAE,CAAC,aAAa,EAAE,cAAc,CAAC;YACxC,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,GAAG;YAChB,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,cAAc;YACrB,OAAO,EAAE,CAAC,aAAa,EAAE,cAAc,CAAC;YACxC,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,GAAG;YACf,WAAW,EAAE,IAAI;YACjB,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,eAAe;YACtB,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,CAAC;YACnD,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,EAAE;YACf,aAAa,EAAE,OAAO;SACvB;QAED,2EAA2E;QAC3E,4EAA4E;QAC5E,4EAA4E;QAC5E,6EAA6E;QAC7E,uEAAuE;QACvE,4EAA4E;QAC5E,2EAA2E;QAC3E,0EAA0E;QAC1E,qDAAqD;QACrD;YACE,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,yBAAyB;YAChC,OAAO,EAAE,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;YAC9C,IAAI,EAAE,QAAQ;YACd,oEAAoE;YACpE,yEAAyE;YACzE,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,aAAa,EAAE,MAAM;SACtB;QACD;YACE,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,iCAAiC;YACxC,OAAO,EAAE,CAAC,wBAAwB,EAAE,eAAe,CAAC;YACpD,IAAI,EAAE,IAAI;YACV,oEAAoE;YACpE,yEAAyE;YACzE,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,aAAa,EAAE,OAAO;SACvB;QACD;YACE,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,qBAAqB;YAC5B,OAAO,EAAE,CAAC,YAAY,EAAE,kBAAkB,CAAC;YAC3C,IAAI,EAAE,SAAS;YACf,oEAAoE;YACpE,yEAAyE;YACzE,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,aAAa,EAAE,OAAO;SACvB;KACF;CACF,CAAC;AAEF,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,KAAa;IAEb,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAC9B,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,QAAQ,KAAK,QAAQ;QACvB,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM;YAC/B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC,CACvD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,WAAmB,EACnB,YAAoB,EACpB,OAAqB;IAErB,MAAM,SAAS,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IACjE,MAAM,UAAU,GAAG,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IACpE,OAAO,SAAS,GAAG,UAAU,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAiC,EACjC,kBAA6B,EAC7B,aAAiC;IAEjC,IAAI,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAErE,IAAI,kBAAkB,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACnC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CACxC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,iCAAiC,IAAI,GAAG;YACtC,CAAC,kBAAkB,CAAC,CAAC,CAAC,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CACnF,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,sEAAsE;IACtE,uDAAuD;IACvD,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YAClC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CACtD,CAAC;QACF,0EAA0E;QAC1E,8EAA8E;QAC9E,gEAAgE;QAChE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,UAAU,GAAG,QAAQ,CAAC;QACxB,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CACvC,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU;QAClC,CAAC,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC;QAC5E,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,QAAQ,CACb,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,UAAU,GAAG,EAAE;IAC5C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IACjD,OAAO,QAAQ,GAAG,UAAU,CAAC;AAC/B,CAAC"}
|
package/dist/interface/menu.d.ts
CHANGED
|
@@ -41,6 +41,18 @@ export interface MenuContext {
|
|
|
41
41
|
* Returns the next trimmed line of input, or `null` on EOF/close.
|
|
42
42
|
*/
|
|
43
43
|
readonly readLine?: () => Promise<string | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Optional injected installProvider for testing. When provided, `startMenu`
|
|
46
|
+
* uses this instead of the real `installProvider` from providers/install.ts,
|
|
47
|
+
* preventing real `npm install -g …` subprocesses from spawning during tests.
|
|
48
|
+
*/
|
|
49
|
+
readonly installProvider?: (id: ProviderId, out: OutputSink) => Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Optional injected login function for testing. When provided, `startMenu`
|
|
52
|
+
* uses this instead of the real `runLogin` from commands/login.ts, preventing
|
|
53
|
+
* real `claude`/`codex login` subprocesses from spawning during tests.
|
|
54
|
+
*/
|
|
55
|
+
readonly login?: (out: OutputSink, providerArg?: string) => Promise<number>;
|
|
44
56
|
}
|
|
45
57
|
/**
|
|
46
58
|
* Return the shell alias hint the user can add to their shell profile to make
|
package/dist/interface/menu.js
CHANGED
|
@@ -28,6 +28,7 @@ import { runTask } from './run.js';
|
|
|
28
28
|
import { runLogin } from '../commands/login.js';
|
|
29
29
|
import { runDoctor } from '../commands/doctor.js';
|
|
30
30
|
import { runCost } from '../commands/cost.js';
|
|
31
|
+
import { runInstall } from '../commands/install.js';
|
|
31
32
|
import { box, separator, menu, prompt } from '../ui/tui.js';
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
// Pure helpers — exported for unit tests
|
|
@@ -104,6 +105,18 @@ export function renderHeaderLines(env, _version) {
|
|
|
104
105
|
lines.push(`⚠️ ${ps.id}: not signed in${planSuffix}`);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
// opencode: only show when installed (never nag users who only use claude/codex).
|
|
109
|
+
// opencode is authenticated-when-installed (free models, no credentials required).
|
|
110
|
+
if (env.opencode.installed) {
|
|
111
|
+
const ps = env.opencode;
|
|
112
|
+
const planSuffix = ps.plan != null ? ` (${ps.plan})` : '';
|
|
113
|
+
if (ps.authenticated) {
|
|
114
|
+
lines.push(`✅ ${ps.id}: ready${planSuffix}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
lines.push(`⚠️ ${ps.id}: not signed in${planSuffix}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
107
120
|
return lines;
|
|
108
121
|
}
|
|
109
122
|
/**
|
|
@@ -197,7 +210,7 @@ function createLineReader(rl) {
|
|
|
197
210
|
// ---------------------------------------------------------------------------
|
|
198
211
|
// Welcome screen (first run)
|
|
199
212
|
// ---------------------------------------------------------------------------
|
|
200
|
-
async function runWelcome(ctx, out, readLine, mutableConfig) {
|
|
213
|
+
async function runWelcome(ctx, out, readLine, mutableConfig, installProviderFn, loginFn) {
|
|
201
214
|
// Use the mutable env so re-detection after installs is visible downstream.
|
|
202
215
|
let env = ctx.env;
|
|
203
216
|
const headerLines = renderHeaderLines(env, ctx.version);
|
|
@@ -217,7 +230,7 @@ async function runWelcome(ctx, out, readLine, mutableConfig) {
|
|
|
217
230
|
// EOF or 'n'/'no' → skip; anything else (including '') → yes
|
|
218
231
|
const skip = ans === null || ans.toLowerCase() === 'n' || ans.toLowerCase() === 'no';
|
|
219
232
|
if (!skip) {
|
|
220
|
-
const ok = await
|
|
233
|
+
const ok = await installProviderFn(id, out);
|
|
221
234
|
if (ok) {
|
|
222
235
|
didInstallAny = true;
|
|
223
236
|
}
|
|
@@ -240,7 +253,9 @@ async function runWelcome(ctx, out, readLine, mutableConfig) {
|
|
|
240
253
|
const ans = await readLine();
|
|
241
254
|
const skip = ans === null || ans.toLowerCase() === 'n' || ans.toLowerCase() === 'no';
|
|
242
255
|
if (!skip) {
|
|
243
|
-
|
|
256
|
+
// loginFn auto-detects the right method (code in containers/SSH where the
|
|
257
|
+
// localhost OAuth callback can't be reached, browser on a desktop).
|
|
258
|
+
await loginFn(out, id);
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
261
|
// ---- Mode / default-shell options ----------------------------------------
|
|
@@ -274,10 +289,10 @@ async function runWelcome(ctx, out, readLine, mutableConfig) {
|
|
|
274
289
|
...(updated.mode !== undefined ? { mode: updated.mode } : {}),
|
|
275
290
|
};
|
|
276
291
|
await saveConfig(saved);
|
|
277
|
-
// When the user
|
|
292
|
+
// When the user opts in, actually write the shell startup hook (real install,
|
|
293
|
+
// not just a hint). runInstall reports what it wrote and how to reverse.
|
|
278
294
|
if (setAsDefault) {
|
|
279
|
-
|
|
280
|
-
out.write('\n[info] To make myshell-tools your default, ' + hint + '\n\n');
|
|
295
|
+
await runInstall(out);
|
|
281
296
|
}
|
|
282
297
|
return saved;
|
|
283
298
|
}
|
|
@@ -314,8 +329,48 @@ async function runModeSelect(config, out, readLine) {
|
|
|
314
329
|
out.write(`Mode set to: ${newMode ?? 'balanced'}\n`);
|
|
315
330
|
return updated;
|
|
316
331
|
}
|
|
317
|
-
|
|
318
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Toggle the "set as default shell" preference and actually install/uninstall
|
|
334
|
+
* the shell startup hook to match. The config flag is only flipped when the
|
|
335
|
+
* hook write succeeds, so the stored value never lies about the real state.
|
|
336
|
+
*/
|
|
337
|
+
async function toggleDefaultShell(config, out) {
|
|
338
|
+
const enable = !config.setAsDefault;
|
|
339
|
+
// runInstall reports exactly what it wrote (or removed) and how to reverse.
|
|
340
|
+
const code = await runInstall(out, enable ? undefined : { uninstall: true });
|
|
341
|
+
// Only adopt the new state if the hook write succeeded; otherwise keep the old.
|
|
342
|
+
const setAsDefault = code === 0 ? enable : config.setAsDefault;
|
|
343
|
+
const updated = {
|
|
344
|
+
onboarded: config.onboarded,
|
|
345
|
+
setAsDefault,
|
|
346
|
+
...(config.mode !== undefined ? { mode: config.mode } : {}),
|
|
347
|
+
};
|
|
348
|
+
await saveConfig(updated);
|
|
349
|
+
return updated;
|
|
350
|
+
}
|
|
351
|
+
async function runSettings(_ctx, mutableCtx, out, readLine) {
|
|
352
|
+
const cfg = mutableCtx.config;
|
|
353
|
+
const settingsLines = [
|
|
354
|
+
'',
|
|
355
|
+
` [1] Mode: ${cfg.mode ?? 'balanced'}`,
|
|
356
|
+
` [2] Set as default shell: ${cfg.setAsDefault ? 'on' : 'off'}`,
|
|
357
|
+
'',
|
|
358
|
+
' [Enter] Back',
|
|
359
|
+
'',
|
|
360
|
+
];
|
|
361
|
+
out.write('\n' + box('Settings', settingsLines) + '\n\n');
|
|
362
|
+
out.write('> ');
|
|
363
|
+
const key = await readLine();
|
|
364
|
+
// EOF or Enter → back, no change
|
|
365
|
+
if (key === null || key.length === 0)
|
|
366
|
+
return;
|
|
367
|
+
if (key === '1') {
|
|
368
|
+
mutableCtx.config = await runModeSelect(mutableCtx.config, out, readLine);
|
|
369
|
+
}
|
|
370
|
+
else if (key === '2') {
|
|
371
|
+
mutableCtx.config = await toggleDefaultShell(mutableCtx.config, out);
|
|
372
|
+
}
|
|
373
|
+
// anything else → back
|
|
319
374
|
}
|
|
320
375
|
// ---------------------------------------------------------------------------
|
|
321
376
|
// Manage conversations screen
|
|
@@ -476,28 +531,33 @@ async function runImportNative(ctx, mutableCtx, out, readLine) {
|
|
|
476
531
|
// Raw provider passthrough
|
|
477
532
|
// ---------------------------------------------------------------------------
|
|
478
533
|
/**
|
|
479
|
-
* Launch the native `claude` or `
|
|
480
|
-
* so the user gets a raw provider session. The session is owned
|
|
481
|
-
* (not by myshell-tools); we simply hand over the terminal and wait.
|
|
534
|
+
* Launch the native `claude`, `codex`, or `opencode` interactive CLI directly
|
|
535
|
+
* (stdio:inherit), so the user gets a raw provider session. The session is owned
|
|
536
|
+
* by the native CLI (not by myshell-tools); we simply hand over the terminal and wait.
|
|
482
537
|
*
|
|
483
538
|
* On exit (any exit code), control returns to the myshell-tools menu.
|
|
484
539
|
*/
|
|
485
|
-
async function runRawProviderSession(out, readLine) {
|
|
486
|
-
|
|
540
|
+
async function runRawProviderSession(out, readLine, env) {
|
|
541
|
+
// Build the choice list dynamically: opencode only when installed.
|
|
542
|
+
const choices = [
|
|
543
|
+
{ label: 'Claude', bin: 'claude' },
|
|
544
|
+
{ label: 'Codex', bin: 'codex' },
|
|
545
|
+
];
|
|
546
|
+
if (env.opencode.installed) {
|
|
547
|
+
choices.push({ label: 'opencode', bin: 'opencode' });
|
|
548
|
+
}
|
|
549
|
+
const choiceLines = choices.map((c, i) => ` [${i + 1}] ${c.label}`).join('\n');
|
|
550
|
+
out.write(`\nOpen raw session with:\n${choiceLines}\n\n> `);
|
|
487
551
|
const choice = await readLine();
|
|
488
552
|
if (choice === null)
|
|
489
553
|
return;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
else if (choice === '2') {
|
|
495
|
-
bin = 'codex';
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
554
|
+
const idx = parseInt(choice, 10) - 1;
|
|
555
|
+
const selected = choices[idx];
|
|
556
|
+
if (selected === undefined) {
|
|
498
557
|
out.write('Cancelled.\n');
|
|
499
558
|
return;
|
|
500
559
|
}
|
|
560
|
+
const bin = selected.bin;
|
|
501
561
|
out.write(`\nLaunching ${bin} — press Ctrl+C or type /exit inside ${bin} to return.\n`);
|
|
502
562
|
// stdio:'inherit' hands the terminal to the native CLI so its interactive
|
|
503
563
|
// session runs in place. reject:false so we return to menu on any exit code.
|
|
@@ -551,6 +611,24 @@ async function runChatLoop(ctx, mutableCtx, convId, out, readLine) {
|
|
|
551
611
|
const policy = mutableCtx.config.mode !== undefined
|
|
552
612
|
? POLICY_PRESETS[mutableCtx.config.mode]
|
|
553
613
|
: DEFAULT_POLICY;
|
|
614
|
+
// Load prior history before each turn so the provider receives conversation
|
|
615
|
+
// context. load() returns only the entries persisted so far — the current
|
|
616
|
+
// user turn is appended by orchestrate() after this point, so there is no
|
|
617
|
+
// double-inclusion risk.
|
|
618
|
+
const priorHistory = await ctx.store.load(convId);
|
|
619
|
+
// Build per-provider advertised model sets from the live env so route()
|
|
620
|
+
// can prefer a model the CLI actually advertises. Only include installed
|
|
621
|
+
// providers (exactOptionalPropertyTypes is ON).
|
|
622
|
+
const menuAvailableModels = {};
|
|
623
|
+
if (ctx.env.claude.installed && ctx.env.claude.availableModels.length > 0) {
|
|
624
|
+
menuAvailableModels['claude'] = ctx.env.claude.availableModels;
|
|
625
|
+
}
|
|
626
|
+
if (ctx.env.codex.installed && ctx.env.codex.availableModels.length > 0) {
|
|
627
|
+
menuAvailableModels['codex'] = ctx.env.codex.availableModels;
|
|
628
|
+
}
|
|
629
|
+
if (ctx.env.opencode.installed && ctx.env.opencode.availableModels.length > 0) {
|
|
630
|
+
menuAvailableModels['opencode'] = ctx.env.opencode.availableModels;
|
|
631
|
+
}
|
|
554
632
|
const deps = {
|
|
555
633
|
clock: ctx.clock,
|
|
556
634
|
session: ctx.store.writer(convId),
|
|
@@ -560,6 +638,8 @@ async function runChatLoop(ctx, mutableCtx, convId, out, readLine) {
|
|
|
560
638
|
cwd: ctx.cwd,
|
|
561
639
|
sandbox: ctx.sandbox,
|
|
562
640
|
timeoutMs: ctx.timeoutMs,
|
|
641
|
+
...(priorHistory.length > 0 ? { history: priorHistory } : {}),
|
|
642
|
+
...(Object.keys(menuAvailableModels).length > 0 ? { availableModels: menuAvailableModels } : {}),
|
|
563
643
|
};
|
|
564
644
|
const ac = new AbortController();
|
|
565
645
|
currentAc = ac;
|
|
@@ -632,6 +712,9 @@ async function renderMainScreen(ctx, mutableCtx, metas, out) {
|
|
|
632
712
|
* that EOF resolves gracefully instead of throwing.
|
|
633
713
|
*/
|
|
634
714
|
export async function startMenu(ctx, out) {
|
|
715
|
+
// Resolve injected seams — use the real implementations when not provided.
|
|
716
|
+
const installProviderFn = ctx.installProvider !== undefined ? ctx.installProvider : installProvider;
|
|
717
|
+
const loginFn = ctx.login !== undefined ? ctx.login : runLogin;
|
|
635
718
|
// Build the readLine function — either injected (for tests) or backed by a
|
|
636
719
|
// real readline interface driven by the event-driven LineReader queue.
|
|
637
720
|
let readLine;
|
|
@@ -664,7 +747,7 @@ export async function startMenu(ctx, out) {
|
|
|
664
747
|
try {
|
|
665
748
|
// ---- A. First-run welcome -----------------------------------------------
|
|
666
749
|
if (!mutableCtx.config.onboarded) {
|
|
667
|
-
mutableCtx.config = await runWelcome(ctx, out, readLine, mutableCtx.config);
|
|
750
|
+
mutableCtx.config = await runWelcome(ctx, out, readLine, mutableCtx.config, installProviderFn, loginFn);
|
|
668
751
|
}
|
|
669
752
|
// ---- B. Main screen loop -------------------------------------------------
|
|
670
753
|
while (true) {
|
|
@@ -726,18 +809,20 @@ export async function startMenu(ctx, out) {
|
|
|
726
809
|
}
|
|
727
810
|
// ---- [r] Open a raw provider session ------------------------------------
|
|
728
811
|
if (key === 'r') {
|
|
729
|
-
await runRawProviderSession(out, readLine);
|
|
812
|
+
await runRawProviderSession(out, readLine, mutableCtx.env);
|
|
730
813
|
continue;
|
|
731
814
|
}
|
|
732
815
|
// ---- [j] Login Claude ---------------------------------------------------
|
|
816
|
+
// loginFn auto-detects the right sign-in method (code in containers/SSH,
|
|
817
|
+
// browser on a desktop). Force either with `myshell-tools login claude --code|--browser`.
|
|
733
818
|
if (key === 'j') {
|
|
734
|
-
await
|
|
819
|
+
await loginFn(out, 'claude');
|
|
735
820
|
mutableCtx.env = await detectEnvironment();
|
|
736
821
|
continue;
|
|
737
822
|
}
|
|
738
823
|
// ---- [k] Login Codex ----------------------------------------------------
|
|
739
824
|
if (key === 'k') {
|
|
740
|
-
await
|
|
825
|
+
await loginFn(out, 'codex');
|
|
741
826
|
mutableCtx.env = await detectEnvironment();
|
|
742
827
|
continue;
|
|
743
828
|
}
|