myshell-tools 1.0.0 → 2.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/CHANGELOG.md +44 -69
- package/LICENSE +21 -21
- package/README.md +178 -318
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/cost.d.ts +36 -0
- package/dist/commands/cost.js +103 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.js +115 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +60 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/core/assess.d.ts +25 -0
- package/dist/core/assess.js +142 -0
- package/dist/core/assess.js.map +1 -0
- package/dist/core/classify.d.ts +19 -0
- package/dist/core/classify.js +80 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/escalate.d.ts +32 -0
- package/dist/core/escalate.js +57 -0
- package/dist/core/escalate.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/orchestrate.d.ts +42 -0
- package/dist/core/orchestrate.js +439 -0
- package/dist/core/orchestrate.js.map +1 -0
- package/dist/core/policy.d.ts +9 -0
- package/dist/core/policy.js +27 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/prompt.d.ts +26 -0
- package/dist/core/prompt.js +125 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/review.d.ts +46 -0
- package/dist/core/review.js +148 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/route.d.ts +28 -0
- package/dist/core/route.js +52 -0
- package/dist/core/route.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.js +14 -0
- package/dist/core/types.js.map +1 -0
- package/dist/infra/atomic.d.ts +53 -0
- package/dist/infra/atomic.js +171 -0
- package/dist/infra/atomic.js.map +1 -0
- package/dist/infra/clock.d.ts +9 -0
- package/dist/infra/clock.js +15 -0
- package/dist/infra/clock.js.map +1 -0
- package/dist/infra/index.d.ts +9 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/ledger.d.ts +49 -0
- package/dist/infra/ledger.js +90 -0
- package/dist/infra/ledger.js.map +1 -0
- package/dist/infra/paths.d.ts +28 -0
- package/dist/infra/paths.js +38 -0
- package/dist/infra/paths.js.map +1 -0
- package/dist/infra/pricing.d.ts +47 -0
- package/dist/infra/pricing.js +151 -0
- package/dist/infra/pricing.js.map +1 -0
- package/dist/infra/session.d.ts +28 -0
- package/dist/infra/session.js +61 -0
- package/dist/infra/session.js.map +1 -0
- package/dist/interface/render.d.ts +27 -0
- package/dist/interface/render.js +134 -0
- package/dist/interface/render.js.map +1 -0
- package/dist/interface/repl.d.ts +23 -0
- package/dist/interface/repl.js +90 -0
- package/dist/interface/repl.js.map +1 -0
- package/dist/interface/run.d.ts +20 -0
- package/dist/interface/run.js +31 -0
- package/dist/interface/run.js.map +1 -0
- package/dist/providers/claude-parse.d.ts +24 -0
- package/dist/providers/claude-parse.js +113 -0
- package/dist/providers/claude-parse.js.map +1 -0
- package/dist/providers/claude.d.ts +45 -0
- package/dist/providers/claude.js +122 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex-parse.d.ts +32 -0
- package/dist/providers/codex-parse.js +145 -0
- package/dist/providers/codex-parse.js.map +1 -0
- package/dist/providers/codex.d.ts +44 -0
- package/dist/providers/codex.js +124 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect.d.ts +49 -0
- package/dist/providers/detect.js +125 -0
- package/dist/providers/detect.js.map +1 -0
- package/dist/providers/errors.d.ts +49 -0
- package/dist/providers/errors.js +189 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/port.d.ts +74 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/registry.d.ts +21 -0
- package/dist/providers/registry.js +34 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/ui/banner.d.ts +19 -0
- package/dist/ui/banner.js +32 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/spinner.d.ts +27 -0
- package/dist/ui/spinner.js +67 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/theme.d.ts +32 -0
- package/dist/ui/theme.js +56 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +55 -49
- package/data/orchestrator.json +0 -113
- package/src/auth/recovery.mjs +0 -328
- package/src/auth/refresh.mjs +0 -373
- package/src/chef.mjs +0 -348
- package/src/cli/doctor.mjs +0 -568
- package/src/cli/reset.mjs +0 -447
- package/src/cli/status.mjs +0 -379
- package/src/cli.mjs +0 -429
- package/src/commands/doctor.mjs +0 -375
- package/src/commands/help.mjs +0 -324
- package/src/commands/status.mjs +0 -331
- package/src/monitor/health.mjs +0 -486
- package/src/monitor/performance.mjs +0 -442
- package/src/monitor/report.mjs +0 -535
- package/src/orchestrator/classify.mjs +0 -391
- package/src/orchestrator/confidence.mjs +0 -151
- package/src/orchestrator/handoffs.mjs +0 -231
- package/src/orchestrator/review.mjs +0 -222
- package/src/providers/balance.mjs +0 -201
- package/src/providers/claude.mjs +0 -236
- package/src/providers/codex.mjs +0 -255
- package/src/providers/detect.mjs +0 -185
- package/src/providers/errors.mjs +0 -373
- package/src/providers/select.mjs +0 -162
- package/src/repl-enhanced.mjs +0 -417
- package/src/repl.mjs +0 -321
- package/src/state/archive.mjs +0 -366
- package/src/state/atomic.mjs +0 -116
- package/src/state/cleanup.mjs +0 -440
- package/src/state/recovery.mjs +0 -461
- package/src/state/session.mjs +0 -147
- package/src/ui/errors.mjs +0 -456
- package/src/ui/formatter.mjs +0 -327
- package/src/ui/icons.mjs +0 -318
- package/src/ui/progress.mjs +0 -468
- package/templates/prompts/confidence-format.txt +0 -14
- package/templates/prompts/ic-with-feedback.txt +0 -41
- package/templates/prompts/ic.txt +0 -13
- package/templates/prompts/manager-review.txt +0 -40
- package/templates/prompts/manager.txt +0 -14
- package/templates/prompts/worker.txt +0 -12
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/cost.ts — `myshell-tools cost` spend-summary command.
|
|
3
|
+
*
|
|
4
|
+
* Reads the on-disk ledger, computes real totals, and prints an honest
|
|
5
|
+
* per-model breakdown together with a counterfactual "what if you had used
|
|
6
|
+
* the manager-tier flagship for every call?" comparison.
|
|
7
|
+
*
|
|
8
|
+
* Counterfactual (apples-to-apples)
|
|
9
|
+
* ---------------------------------
|
|
10
|
+
* The "Billed total" is the real amount from the ledger (includes caching and
|
|
11
|
+
* discounts). The counterfactual compares ONLY like-for-like list prices:
|
|
12
|
+
* routed = sum over entries of listPrice(entry.model) × entry tokens
|
|
13
|
+
* flagship = sum over entries of listPrice(manager flagship) × entry tokens
|
|
14
|
+
* multiplier = flagship / routed
|
|
15
|
+
* We never compare the cache-adjusted billed total against a list-price flagship
|
|
16
|
+
* estimate — that would understate flagship and mislead.
|
|
17
|
+
*
|
|
18
|
+
* Honesty contract: every figure comes from the real ledger; no values are
|
|
19
|
+
* fabricated. If the ledger is empty the command says so and returns 0.
|
|
20
|
+
*/
|
|
21
|
+
import type { LedgerEntry } from '../core/types.js';
|
|
22
|
+
import type { OutputSink } from '../interface/render.js';
|
|
23
|
+
/**
|
|
24
|
+
* Build the cost-report lines from a (possibly empty) ledger entry array.
|
|
25
|
+
*
|
|
26
|
+
* Pure function: no I/O, no process.exit, no Date/Math.random.
|
|
27
|
+
* Called by runCost after reading the real ledger, and by unit tests.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatCostReport(entries: LedgerEntry[], color?: boolean): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Read the ledger for `cwd`, build the cost report, and write it to `out`.
|
|
32
|
+
*
|
|
33
|
+
* Returns 0 always (cost reporting is informational, not an error condition).
|
|
34
|
+
* Never calls process.exit — that is handled exclusively by src/cli.ts.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runCost(cwd: string, out: OutputSink): Promise<number>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/cost.ts — `myshell-tools cost` spend-summary command.
|
|
3
|
+
*
|
|
4
|
+
* Reads the on-disk ledger, computes real totals, and prints an honest
|
|
5
|
+
* per-model breakdown together with a counterfactual "what if you had used
|
|
6
|
+
* the manager-tier flagship for every call?" comparison.
|
|
7
|
+
*
|
|
8
|
+
* Counterfactual (apples-to-apples)
|
|
9
|
+
* ---------------------------------
|
|
10
|
+
* The "Billed total" is the real amount from the ledger (includes caching and
|
|
11
|
+
* discounts). The counterfactual compares ONLY like-for-like list prices:
|
|
12
|
+
* routed = sum over entries of listPrice(entry.model) × entry tokens
|
|
13
|
+
* flagship = sum over entries of listPrice(manager flagship) × entry tokens
|
|
14
|
+
* multiplier = flagship / routed
|
|
15
|
+
* We never compare the cache-adjusted billed total against a list-price flagship
|
|
16
|
+
* estimate — that would understate flagship and mislead.
|
|
17
|
+
*
|
|
18
|
+
* Honesty contract: every figure comes from the real ledger; no values are
|
|
19
|
+
* fabricated. If the ledger is empty the command says so and returns 0.
|
|
20
|
+
*/
|
|
21
|
+
import { readLedger, summarizeLedger } from '../infra/ledger.js';
|
|
22
|
+
import { getCheapestForTier, calculateCost, getModelPricing } from '../infra/pricing.js';
|
|
23
|
+
import { bold, dim, cyan, divider, label } from '../ui/theme.js';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Pure report builder — testable with hand-built LedgerEntry[]
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Build the cost-report lines from a (possibly empty) ledger entry array.
|
|
29
|
+
*
|
|
30
|
+
* Pure function: no I/O, no process.exit, no Date/Math.random.
|
|
31
|
+
* Called by runCost after reading the real ledger, and by unit tests.
|
|
32
|
+
*/
|
|
33
|
+
export function formatCostReport(entries, color = false) {
|
|
34
|
+
if (entries.length === 0) {
|
|
35
|
+
return ['No usage recorded yet — run a task first.'];
|
|
36
|
+
}
|
|
37
|
+
const summary = summarizeLedger(entries);
|
|
38
|
+
const flagship = getCheapestForTier('manager');
|
|
39
|
+
// Apples-to-apples: price BOTH the models actually used AND the flagship on the
|
|
40
|
+
// SAME basis (list price × tokens). We do NOT compare the billed total (which
|
|
41
|
+
// includes caching/discounts) against a list-price flagship estimate.
|
|
42
|
+
let routedListUsd = 0;
|
|
43
|
+
let flagshipListUsd = 0;
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const entryPricing = getModelPricing(entry.provider, entry.model);
|
|
46
|
+
if (entryPricing) {
|
|
47
|
+
routedListUsd += calculateCost(entry.inputTokens, entry.outputTokens, entryPricing);
|
|
48
|
+
}
|
|
49
|
+
flagshipListUsd += calculateCost(entry.inputTokens, entry.outputTokens, flagship);
|
|
50
|
+
}
|
|
51
|
+
const lines = [];
|
|
52
|
+
lines.push(bold('myshell-tools cost', color));
|
|
53
|
+
lines.push(divider(color));
|
|
54
|
+
// ---- Summary ---------------------------------------------------------------
|
|
55
|
+
lines.push(`${label('Billed total', color)}: $${summary.totalUsd.toFixed(4)} ` +
|
|
56
|
+
`${dim('(as billed, incl. caching/discounts)', color)}`);
|
|
57
|
+
lines.push(`${label('Total calls', color)}: ${summary.calls}`);
|
|
58
|
+
lines.push(divider(color));
|
|
59
|
+
// ---- Per-model breakdown ---------------------------------------------------
|
|
60
|
+
lines.push(bold('Per-model breakdown', color));
|
|
61
|
+
for (const [model, ms] of Object.entries(summary.byModel)) {
|
|
62
|
+
const callStr = ms.calls === 1 ? '1 call' : `${ms.calls} calls`;
|
|
63
|
+
lines.push(` ${cyan(model, color)}: ${callStr}, $${ms.usd.toFixed(4)}`);
|
|
64
|
+
}
|
|
65
|
+
lines.push(divider(color));
|
|
66
|
+
// ---- Counterfactual (list-price, token-for-token, apples-to-apples) --------
|
|
67
|
+
const flagshipLabel = `${flagship.model} (${flagship.tier} tier)`;
|
|
68
|
+
lines.push(bold('Counterfactual — list price, token-for-token', color));
|
|
69
|
+
lines.push(`${dim('Flagship model', color)}: ${flagshipLabel}` +
|
|
70
|
+
` ${dim(`($${flagship.inputPer1M}/M in, $${flagship.outputPer1M}/M out)`, color)}`);
|
|
71
|
+
lines.push(`${label('Routed (models used)', color)}: $${routedListUsd.toFixed(4)}`);
|
|
72
|
+
lines.push(`${label('Always-flagship', color)}: $${flagshipListUsd.toFixed(4)}`);
|
|
73
|
+
if (routedListUsd > 0 && flagshipListUsd > routedListUsd) {
|
|
74
|
+
const multiplier = flagshipListUsd / routedListUsd;
|
|
75
|
+
lines.push(`Routing saved you money: always-flagship would cost ${multiplier.toFixed(1)}x more ` +
|
|
76
|
+
`($${flagshipListUsd.toFixed(4)} vs $${routedListUsd.toFixed(4)} at list price).`);
|
|
77
|
+
}
|
|
78
|
+
else if (routedListUsd > 0) {
|
|
79
|
+
lines.push('Every call already used a flagship-tier (or pricier) model — no routing savings to show.');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
lines.push(dim('No priced models in the ledger — cannot compute a list-price comparison.', color));
|
|
83
|
+
}
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// I/O runner — called by cli.ts
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Read the ledger for `cwd`, build the cost report, and write it to `out`.
|
|
91
|
+
*
|
|
92
|
+
* Returns 0 always (cost reporting is informational, not an error condition).
|
|
93
|
+
* Never calls process.exit — that is handled exclusively by src/cli.ts.
|
|
94
|
+
*/
|
|
95
|
+
export async function runCost(cwd, out) {
|
|
96
|
+
const entries = await readLedger(cwd);
|
|
97
|
+
const lines = formatCostReport(entries, out.color);
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
out.write(line + '\n');
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/commands/cost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/doctor.ts — `myshell-tools doctor` health-check command.
|
|
3
|
+
*
|
|
4
|
+
* Probes the runtime environment (providers, filesystem, pricing) and prints
|
|
5
|
+
* an honest, human-readable report to an OutputSink. All displayed data comes
|
|
6
|
+
* from real detection results — no fabricated values.
|
|
7
|
+
*
|
|
8
|
+
* Honesty contract: authentication status for Claude is labelled OPTIMISTIC
|
|
9
|
+
* because we cannot cheaply probe auth state without spending API quota.
|
|
10
|
+
*/
|
|
11
|
+
import type { OutputSink } from '../interface/render.js';
|
|
12
|
+
import type { EnvironmentStatus } from '../providers/detect.js';
|
|
13
|
+
export interface DoctorExtras {
|
|
14
|
+
readonly nodeVersion: string;
|
|
15
|
+
readonly stateWritable: boolean;
|
|
16
|
+
readonly pricingStale: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the doctor report lines from pre-collected data.
|
|
20
|
+
*
|
|
21
|
+
* Pure function: no I/O, no process.exit, no side effects.
|
|
22
|
+
* Used by runDoctor after it collects the real data, and by unit tests with
|
|
23
|
+
* hand-built inputs.
|
|
24
|
+
*
|
|
25
|
+
* @param env - Full environment status (from detectEnvironment or a fake).
|
|
26
|
+
* @param extras - Supplemental runtime info (node version, write probe, etc.).
|
|
27
|
+
* @param color - Whether to emit ANSI colour codes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildDoctorReport(env: EnvironmentStatus, extras: DoctorExtras, color: boolean): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Detect the environment, probe I/O, build the report, and write it to `out`.
|
|
32
|
+
*
|
|
33
|
+
* Returns 0 when at least one provider is installed, 1 otherwise.
|
|
34
|
+
* Never calls process.exit — that is handled exclusively by src/cli.ts.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runDoctor(out: OutputSink): Promise<number>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/doctor.ts — `myshell-tools doctor` health-check command.
|
|
3
|
+
*
|
|
4
|
+
* Probes the runtime environment (providers, filesystem, pricing) and prints
|
|
5
|
+
* an honest, human-readable report to an OutputSink. All displayed data comes
|
|
6
|
+
* from real detection results — no fabricated values.
|
|
7
|
+
*
|
|
8
|
+
* Honesty contract: authentication status for Claude is labelled OPTIMISTIC
|
|
9
|
+
* because we cannot cheaply probe auth state without spending API quota.
|
|
10
|
+
*/
|
|
11
|
+
import { access, mkdir, writeFile, rm } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { detectEnvironment, getInstallCommand } from '../providers/detect.js';
|
|
14
|
+
import { isPricingStale } from '../infra/pricing.js';
|
|
15
|
+
import { bold, green, red, yellow, dim, divider, label } from '../ui/theme.js';
|
|
16
|
+
/**
|
|
17
|
+
* Build the doctor report lines from pre-collected data.
|
|
18
|
+
*
|
|
19
|
+
* Pure function: no I/O, no process.exit, no side effects.
|
|
20
|
+
* Used by runDoctor after it collects the real data, and by unit tests with
|
|
21
|
+
* hand-built inputs.
|
|
22
|
+
*
|
|
23
|
+
* @param env - Full environment status (from detectEnvironment or a fake).
|
|
24
|
+
* @param extras - Supplemental runtime info (node version, write probe, etc.).
|
|
25
|
+
* @param color - Whether to emit ANSI colour codes.
|
|
26
|
+
*/
|
|
27
|
+
export function buildDoctorReport(env, extras, color) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
lines.push(bold('myshell-tools doctor', color));
|
|
30
|
+
lines.push(divider(color));
|
|
31
|
+
// ---- Platform & Node -------------------------------------------------------
|
|
32
|
+
lines.push(`${label('Platform', color)}: ${env.platform}`);
|
|
33
|
+
lines.push(`${label('Node', color)}: ${extras.nodeVersion}`);
|
|
34
|
+
// ---- .myshell-tools writability ---------------------------------------------------
|
|
35
|
+
const writableText = extras.stateWritable
|
|
36
|
+
? green('writable', color)
|
|
37
|
+
: red('not writable', color);
|
|
38
|
+
lines.push(`${label('.myshell-tools dir', color)}: ${writableText}`);
|
|
39
|
+
// ---- Pricing staleness -----------------------------------------------------
|
|
40
|
+
const pricingText = extras.pricingStale
|
|
41
|
+
? yellow('stale — consider updating myshell-tools', color)
|
|
42
|
+
: green('up to date', color);
|
|
43
|
+
lines.push(`${label('Pricing table', color)}: ${pricingText}`);
|
|
44
|
+
lines.push(divider(color));
|
|
45
|
+
// ---- Providers -------------------------------------------------------------
|
|
46
|
+
lines.push(bold('Providers', color));
|
|
47
|
+
for (const ps of [env.claude, env.codex]) {
|
|
48
|
+
if (ps.installed) {
|
|
49
|
+
const versionStr = ps.version !== null ? ps.version : 'unknown';
|
|
50
|
+
lines.push(` ${green('✓', color)} ${bold(ps.id, color)} — installed, version: ${versionStr}`);
|
|
51
|
+
// Auth is optimistic for Claude; codex is always not-installed for now
|
|
52
|
+
if (ps.id === 'claude') {
|
|
53
|
+
lines.push(` ${label('auth', color)}: ${dim('assumed; verified on first run', color)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lines.push(` ${red('✗', color)} ${bold(ps.id, color)} — ${red('not installed', color)}`);
|
|
58
|
+
lines.push(` ${dim(`Install: ${getInstallCommand(ps.id)}`, color)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
lines.push(divider(color));
|
|
62
|
+
// ---- Overall status --------------------------------------------------------
|
|
63
|
+
if (env.hasAnyProvider) {
|
|
64
|
+
lines.push(green('Ready — at least one provider is available.', color));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
lines.push(red('No providers found.', color) +
|
|
68
|
+
' Install claude or codex to use myshell-tools.');
|
|
69
|
+
}
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// I/O runner — called by cli.ts
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Probe the .myshell-tools directory for writability.
|
|
77
|
+
*
|
|
78
|
+
* Creates .myshell-tools/ if needed, writes a temp file, then removes it.
|
|
79
|
+
* Returns true when successful, false on any I/O error.
|
|
80
|
+
*/
|
|
81
|
+
async function probestateWritable(cwd) {
|
|
82
|
+
const stateDir = join(cwd, '.myshell-tools');
|
|
83
|
+
const probe = join(stateDir, '.doctor-probe');
|
|
84
|
+
try {
|
|
85
|
+
await mkdir(stateDir, { recursive: true });
|
|
86
|
+
await writeFile(probe, '');
|
|
87
|
+
await rm(probe, { force: true });
|
|
88
|
+
await access(stateDir);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Detect the environment, probe I/O, build the report, and write it to `out`.
|
|
97
|
+
*
|
|
98
|
+
* Returns 0 when at least one provider is installed, 1 otherwise.
|
|
99
|
+
* Never calls process.exit — that is handled exclusively by src/cli.ts.
|
|
100
|
+
*/
|
|
101
|
+
export async function runDoctor(out) {
|
|
102
|
+
const env = await detectEnvironment();
|
|
103
|
+
const stateWritable = await probestateWritable(process.cwd());
|
|
104
|
+
const extras = {
|
|
105
|
+
nodeVersion: process.version,
|
|
106
|
+
stateWritable,
|
|
107
|
+
pricingStale: isPricingStale(),
|
|
108
|
+
};
|
|
109
|
+
const lines = buildDoctorReport(env, extras, out.color);
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
out.write(line + '\n');
|
|
112
|
+
}
|
|
113
|
+
return env.hasAnyProvider ? 0 : 1;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/login.ts — `myshell-tools login [claude|codex]`.
|
|
3
|
+
*
|
|
4
|
+
* Frictionless authentication: rather than make the user remember each vendor's
|
|
5
|
+
* CLI auth command, we delegate to the provider's OWN OAuth flow and inherit the
|
|
6
|
+
* terminal so the browser/device sign-in works in place.
|
|
7
|
+
*
|
|
8
|
+
* Security: myshell-tools never sees, handles, or stores raw credentials. Each
|
|
9
|
+
* CLI manages its own tokens; we only trigger its login. (This is what keeps the
|
|
10
|
+
* "use your subscription, no API keys" model honest.)
|
|
11
|
+
*/
|
|
12
|
+
import type { OutputSink } from '../interface/render.js';
|
|
13
|
+
import type { ProviderId } from '../providers/port.js';
|
|
14
|
+
export declare function isProviderId(value: string): value is ProviderId;
|
|
15
|
+
/**
|
|
16
|
+
* Run the interactive sign-in flow for one provider (or all installed providers
|
|
17
|
+
* when no argument is given). Returns 0 on success, 1 only for an invalid
|
|
18
|
+
* argument — individual sign-in failures are reported but do not fail the command.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runLogin(out: OutputSink, providerArg?: string): Promise<number>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/commands/login.ts — `myshell-tools login [claude|codex]`.
|
|
3
|
+
*
|
|
4
|
+
* Frictionless authentication: rather than make the user remember each vendor's
|
|
5
|
+
* CLI auth command, we delegate to the provider's OWN OAuth flow and inherit the
|
|
6
|
+
* terminal so the browser/device sign-in works in place.
|
|
7
|
+
*
|
|
8
|
+
* Security: myshell-tools never sees, handles, or stores raw credentials. Each
|
|
9
|
+
* CLI manages its own tokens; we only trigger its login. (This is what keeps the
|
|
10
|
+
* "use your subscription, no API keys" model honest.)
|
|
11
|
+
*/
|
|
12
|
+
import { execa } from 'execa';
|
|
13
|
+
import { detectProvider, getInstallCommand } from '../providers/detect.js';
|
|
14
|
+
import { bold, dim, green, red } from '../ui/theme.js';
|
|
15
|
+
/** Each provider's interactive sign-in command. */
|
|
16
|
+
const LOGIN_COMMAND = {
|
|
17
|
+
claude: { bin: 'claude', args: ['auth', 'login'] },
|
|
18
|
+
codex: { bin: 'codex', args: ['login'] },
|
|
19
|
+
};
|
|
20
|
+
export function isProviderId(value) {
|
|
21
|
+
return value === 'claude' || value === 'codex';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the interactive sign-in flow for one provider (or all installed providers
|
|
25
|
+
* when no argument is given). Returns 0 on success, 1 only for an invalid
|
|
26
|
+
* argument — individual sign-in failures are reported but do not fail the command.
|
|
27
|
+
*/
|
|
28
|
+
export async function runLogin(out, providerArg) {
|
|
29
|
+
let targets;
|
|
30
|
+
if (providerArg !== undefined) {
|
|
31
|
+
if (!isProviderId(providerArg)) {
|
|
32
|
+
out.write(red(`Unknown provider "${providerArg}". Use: claude or codex.\n`, out.color));
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
targets = [providerArg];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
targets = ['claude', 'codex'];
|
|
39
|
+
}
|
|
40
|
+
for (const id of targets) {
|
|
41
|
+
const status = await detectProvider(id);
|
|
42
|
+
if (!status.installed) {
|
|
43
|
+
out.write(dim(`${id}: not installed — skipping. Install with: ${getInstallCommand(id)}\n`, out.color));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
out.write(bold(`\nSigning in to ${id} — a browser window may open…\n`, out.color));
|
|
47
|
+
const { bin, args } = LOGIN_COMMAND[id];
|
|
48
|
+
// stdio:'inherit' hands the terminal to the provider CLI so its OAuth/device
|
|
49
|
+
// flow runs in place. reject:false so we report rather than throw.
|
|
50
|
+
const result = await execa(bin, [...args], { stdio: 'inherit', reject: false });
|
|
51
|
+
if (result.exitCode === 0) {
|
|
52
|
+
out.write(green(`✓ ${id} sign-in complete.\n`, out.color));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
out.write(red(`✗ ${id} sign-in did not complete (exit ${result.exitCode ?? 'unknown'}).\n`, out.color));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAG9B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAEvD,mDAAmD;AACnD,MAAM,aAAa,GAAmF;IACpG,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;IAClD,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;CACzC,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAe,EAAE,WAAoB;IAClE,IAAI,OAAqB,CAAC;IAC1B,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,WAAW,4BAA4B,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACxF,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,GAAG,CAAC,KAAK,CACP,GAAG,CAAC,GAAG,EAAE,6CAA6C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAC5F,CAAC;YACF,SAAS;QACX,CAAC;QAED,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,EAAE,iCAAiC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACnF,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;QACxC,6EAA6E;QAC7E,mEAAmE;QACnE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAEhF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CACP,GAAG,CAAC,KAAK,EAAE,mCAAmC,MAAM,CAAC,QAAQ,IAAI,SAAS,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAC7F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/assess.ts — parse real confidence signals from model output.
|
|
3
|
+
*
|
|
4
|
+
* The model is instructed (via prompt.ts) to end its response with a JSON
|
|
5
|
+
* envelope on its own line:
|
|
6
|
+
* {"confidence": 0.0-1.0, "escalate": true|false, "reason": "...", "needs_review": true|false}
|
|
7
|
+
*
|
|
8
|
+
* Honesty Contract: if the envelope is absent, truncated, or malformed we
|
|
9
|
+
* return confidence=null. We NEVER guess or fabricate a confidence number from
|
|
10
|
+
* keywords or heuristics (that was the v1 sin). This function must never throw
|
|
11
|
+
* on any input — it degrades gracefully on all malformed/garbage data.
|
|
12
|
+
*
|
|
13
|
+
* Pure module: no I/O, no time, no randomness.
|
|
14
|
+
*/
|
|
15
|
+
import type { Assessment } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Parse a trailing JSON confidence envelope from `output`.
|
|
18
|
+
*
|
|
19
|
+
* Returns real parsed values when the envelope is present and valid.
|
|
20
|
+
* Returns `{ confidence: null, escalate: false, reason: 'no confidence envelope', needsReview: false }`
|
|
21
|
+
* when the envelope is absent or unparseable.
|
|
22
|
+
*
|
|
23
|
+
* @param output - The full text output from the model.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assess(output: string): Assessment;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/assess.ts — parse real confidence signals from model output.
|
|
3
|
+
*
|
|
4
|
+
* The model is instructed (via prompt.ts) to end its response with a JSON
|
|
5
|
+
* envelope on its own line:
|
|
6
|
+
* {"confidence": 0.0-1.0, "escalate": true|false, "reason": "...", "needs_review": true|false}
|
|
7
|
+
*
|
|
8
|
+
* Honesty Contract: if the envelope is absent, truncated, or malformed we
|
|
9
|
+
* return confidence=null. We NEVER guess or fabricate a confidence number from
|
|
10
|
+
* keywords or heuristics (that was the v1 sin). This function must never throw
|
|
11
|
+
* on any input — it degrades gracefully on all malformed/garbage data.
|
|
12
|
+
*
|
|
13
|
+
* Pure module: no I/O, no time, no randomness.
|
|
14
|
+
*/
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Attempt to extract the last JSON object from `text` that looks like a
|
|
20
|
+
* confidence envelope. Returns the raw parsed object or null.
|
|
21
|
+
*
|
|
22
|
+
* Strategy: scan backwards for the last `{...}` block that contains at least
|
|
23
|
+
* the `confidence` key, then try to parse it. This is robust to:
|
|
24
|
+
* - trailing whitespace / newlines
|
|
25
|
+
* - the model emitting extra text after the envelope (we pick the LAST match)
|
|
26
|
+
* - duplicate envelopes (last one wins — the model may have regenerated it)
|
|
27
|
+
* - garbage / truncated JSON elsewhere in the output
|
|
28
|
+
*/
|
|
29
|
+
function extractEnvelope(text) {
|
|
30
|
+
// Find all candidates: substrings that start with '{' and end with '}'
|
|
31
|
+
// We do this by iterating over all '{' positions and trying to match the
|
|
32
|
+
// closing '}', handling depth. We collect all valid JSON objects and
|
|
33
|
+
// return the last one that has a 'confidence' key.
|
|
34
|
+
const candidates = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
while (i < text.length) {
|
|
37
|
+
const start = text.indexOf('{', i);
|
|
38
|
+
if (start === -1)
|
|
39
|
+
break;
|
|
40
|
+
// Walk forward tracking brace depth to find the matching '}'
|
|
41
|
+
let depth = 0;
|
|
42
|
+
let j = start;
|
|
43
|
+
let foundClose = false;
|
|
44
|
+
while (j < text.length) {
|
|
45
|
+
if (text[j] === '{') {
|
|
46
|
+
depth++;
|
|
47
|
+
}
|
|
48
|
+
else if (text[j] === '}') {
|
|
49
|
+
depth--;
|
|
50
|
+
if (depth === 0) {
|
|
51
|
+
foundClose = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
j++;
|
|
56
|
+
}
|
|
57
|
+
if (foundClose) {
|
|
58
|
+
const candidate = text.slice(start, j + 1);
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(candidate);
|
|
61
|
+
if (parsed !== null &&
|
|
62
|
+
typeof parsed === 'object' &&
|
|
63
|
+
!Array.isArray(parsed) &&
|
|
64
|
+
'confidence' in parsed) {
|
|
65
|
+
candidates.push(parsed);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Not valid JSON — skip
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
i = start + 1;
|
|
73
|
+
}
|
|
74
|
+
if (candidates.length === 0)
|
|
75
|
+
return null;
|
|
76
|
+
// Return the last valid envelope (handles duplicate/regenerated envelopes)
|
|
77
|
+
return candidates[candidates.length - 1] ?? null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Clamp a number to [0, 1].
|
|
81
|
+
*/
|
|
82
|
+
function clamp01(n) {
|
|
83
|
+
return Math.min(1, Math.max(0, n));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Coerce an unknown value to boolean — any truthy value maps to true.
|
|
87
|
+
*/
|
|
88
|
+
function coerceBool(v) {
|
|
89
|
+
if (typeof v === 'boolean')
|
|
90
|
+
return v;
|
|
91
|
+
if (typeof v === 'number')
|
|
92
|
+
return v !== 0;
|
|
93
|
+
if (typeof v === 'string')
|
|
94
|
+
return v === 'true' || v === '1';
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Public API
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/**
|
|
101
|
+
* Parse a trailing JSON confidence envelope from `output`.
|
|
102
|
+
*
|
|
103
|
+
* Returns real parsed values when the envelope is present and valid.
|
|
104
|
+
* Returns `{ confidence: null, escalate: false, reason: 'no confidence envelope', needsReview: false }`
|
|
105
|
+
* when the envelope is absent or unparseable.
|
|
106
|
+
*
|
|
107
|
+
* @param output - The full text output from the model.
|
|
108
|
+
*/
|
|
109
|
+
export function assess(output) {
|
|
110
|
+
const NULL_RESULT = {
|
|
111
|
+
confidence: null,
|
|
112
|
+
escalate: false,
|
|
113
|
+
reason: 'no confidence envelope',
|
|
114
|
+
needsReview: false,
|
|
115
|
+
};
|
|
116
|
+
// Guard: never throw on any input
|
|
117
|
+
if (typeof output !== 'string' || output.length === 0) {
|
|
118
|
+
return NULL_RESULT;
|
|
119
|
+
}
|
|
120
|
+
let envelope;
|
|
121
|
+
try {
|
|
122
|
+
envelope = extractEnvelope(output);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return NULL_RESULT;
|
|
126
|
+
}
|
|
127
|
+
if (envelope === null) {
|
|
128
|
+
return NULL_RESULT;
|
|
129
|
+
}
|
|
130
|
+
// Validate confidence: must be a finite number
|
|
131
|
+
if (typeof envelope.confidence !== 'number' || !isFinite(envelope.confidence)) {
|
|
132
|
+
return NULL_RESULT;
|
|
133
|
+
}
|
|
134
|
+
const confidence = clamp01(envelope.confidence);
|
|
135
|
+
const escalate = coerceBool(envelope.escalate);
|
|
136
|
+
const needsReview = coerceBool(envelope.needs_review);
|
|
137
|
+
const reason = typeof envelope.reason === 'string' && envelope.reason.trim().length > 0
|
|
138
|
+
? envelope.reason.trim()
|
|
139
|
+
: 'model provided no reason';
|
|
140
|
+
return { confidence, escalate, reason, needsReview };
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=assess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assess.js","sourceRoot":"","sources":["../../src/core/assess.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/classify.ts — pure task classification.
|
|
3
|
+
*
|
|
4
|
+
* Determines the orchestration tier and security risk level from a free-text
|
|
5
|
+
* task description using keyword/regex signals. No I/O, no time, no randomness.
|
|
6
|
+
*
|
|
7
|
+
* Honesty Contract: no fabricated confidence numbers are produced here.
|
|
8
|
+
* The rationale field names the matched signal so callers can audit decisions.
|
|
9
|
+
*/
|
|
10
|
+
import type { Classification } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Classify a free-text `task` string into a {@link Classification}.
|
|
13
|
+
*
|
|
14
|
+
* Tier priority: manager > worker > ic (ic is the default).
|
|
15
|
+
* Risk priority: critical > high > medium > low (low is the default).
|
|
16
|
+
*
|
|
17
|
+
* @param task - The raw task description from the user.
|
|
18
|
+
*/
|
|
19
|
+
export declare function classify(task: string): Classification;
|