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.
Files changed (153) hide show
  1. package/CHANGELOG.md +44 -69
  2. package/LICENSE +21 -21
  3. package/README.md +178 -318
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +130 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/cost.d.ts +36 -0
  8. package/dist/commands/cost.js +103 -0
  9. package/dist/commands/cost.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +36 -0
  11. package/dist/commands/doctor.js +115 -0
  12. package/dist/commands/doctor.js.map +1 -0
  13. package/dist/commands/login.d.ts +20 -0
  14. package/dist/commands/login.js +60 -0
  15. package/dist/commands/login.js.map +1 -0
  16. package/dist/core/assess.d.ts +25 -0
  17. package/dist/core/assess.js +142 -0
  18. package/dist/core/assess.js.map +1 -0
  19. package/dist/core/classify.d.ts +19 -0
  20. package/dist/core/classify.js +80 -0
  21. package/dist/core/classify.js.map +1 -0
  22. package/dist/core/escalate.d.ts +32 -0
  23. package/dist/core/escalate.js +57 -0
  24. package/dist/core/escalate.js.map +1 -0
  25. package/dist/core/index.d.ts +13 -0
  26. package/dist/core/index.js +12 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/core/orchestrate.d.ts +42 -0
  29. package/dist/core/orchestrate.js +439 -0
  30. package/dist/core/orchestrate.js.map +1 -0
  31. package/dist/core/policy.d.ts +9 -0
  32. package/dist/core/policy.js +27 -0
  33. package/dist/core/policy.js.map +1 -0
  34. package/dist/core/prompt.d.ts +26 -0
  35. package/dist/core/prompt.js +125 -0
  36. package/dist/core/prompt.js.map +1 -0
  37. package/dist/core/review.d.ts +46 -0
  38. package/dist/core/review.js +148 -0
  39. package/dist/core/review.js.map +1 -0
  40. package/dist/core/route.d.ts +28 -0
  41. package/dist/core/route.js +52 -0
  42. package/dist/core/route.js.map +1 -0
  43. package/dist/core/types.d.ts +141 -0
  44. package/dist/core/types.js +14 -0
  45. package/dist/core/types.js.map +1 -0
  46. package/dist/infra/atomic.d.ts +53 -0
  47. package/dist/infra/atomic.js +171 -0
  48. package/dist/infra/atomic.js.map +1 -0
  49. package/dist/infra/clock.d.ts +9 -0
  50. package/dist/infra/clock.js +15 -0
  51. package/dist/infra/clock.js.map +1 -0
  52. package/dist/infra/index.d.ts +9 -0
  53. package/dist/infra/index.js +7 -0
  54. package/dist/infra/index.js.map +1 -0
  55. package/dist/infra/ledger.d.ts +49 -0
  56. package/dist/infra/ledger.js +90 -0
  57. package/dist/infra/ledger.js.map +1 -0
  58. package/dist/infra/paths.d.ts +28 -0
  59. package/dist/infra/paths.js +38 -0
  60. package/dist/infra/paths.js.map +1 -0
  61. package/dist/infra/pricing.d.ts +47 -0
  62. package/dist/infra/pricing.js +151 -0
  63. package/dist/infra/pricing.js.map +1 -0
  64. package/dist/infra/session.d.ts +28 -0
  65. package/dist/infra/session.js +61 -0
  66. package/dist/infra/session.js.map +1 -0
  67. package/dist/interface/render.d.ts +27 -0
  68. package/dist/interface/render.js +134 -0
  69. package/dist/interface/render.js.map +1 -0
  70. package/dist/interface/repl.d.ts +23 -0
  71. package/dist/interface/repl.js +90 -0
  72. package/dist/interface/repl.js.map +1 -0
  73. package/dist/interface/run.d.ts +20 -0
  74. package/dist/interface/run.js +31 -0
  75. package/dist/interface/run.js.map +1 -0
  76. package/dist/providers/claude-parse.d.ts +24 -0
  77. package/dist/providers/claude-parse.js +113 -0
  78. package/dist/providers/claude-parse.js.map +1 -0
  79. package/dist/providers/claude.d.ts +45 -0
  80. package/dist/providers/claude.js +122 -0
  81. package/dist/providers/claude.js.map +1 -0
  82. package/dist/providers/codex-parse.d.ts +32 -0
  83. package/dist/providers/codex-parse.js +145 -0
  84. package/dist/providers/codex-parse.js.map +1 -0
  85. package/dist/providers/codex.d.ts +44 -0
  86. package/dist/providers/codex.js +124 -0
  87. package/dist/providers/codex.js.map +1 -0
  88. package/dist/providers/detect.d.ts +49 -0
  89. package/dist/providers/detect.js +125 -0
  90. package/dist/providers/detect.js.map +1 -0
  91. package/dist/providers/errors.d.ts +49 -0
  92. package/dist/providers/errors.js +189 -0
  93. package/dist/providers/errors.js.map +1 -0
  94. package/dist/providers/index.d.ts +9 -0
  95. package/dist/providers/index.js +7 -0
  96. package/dist/providers/index.js.map +1 -0
  97. package/dist/providers/port.d.ts +74 -0
  98. package/dist/providers/port.js +16 -0
  99. package/dist/providers/port.js.map +1 -0
  100. package/dist/providers/registry.d.ts +21 -0
  101. package/dist/providers/registry.js +34 -0
  102. package/dist/providers/registry.js.map +1 -0
  103. package/dist/ui/banner.d.ts +19 -0
  104. package/dist/ui/banner.js +32 -0
  105. package/dist/ui/banner.js.map +1 -0
  106. package/dist/ui/spinner.d.ts +27 -0
  107. package/dist/ui/spinner.js +67 -0
  108. package/dist/ui/spinner.js.map +1 -0
  109. package/dist/ui/theme.d.ts +32 -0
  110. package/dist/ui/theme.js +56 -0
  111. package/dist/ui/theme.js.map +1 -0
  112. package/package.json +55 -49
  113. package/data/orchestrator.json +0 -113
  114. package/src/auth/recovery.mjs +0 -328
  115. package/src/auth/refresh.mjs +0 -373
  116. package/src/chef.mjs +0 -348
  117. package/src/cli/doctor.mjs +0 -568
  118. package/src/cli/reset.mjs +0 -447
  119. package/src/cli/status.mjs +0 -379
  120. package/src/cli.mjs +0 -429
  121. package/src/commands/doctor.mjs +0 -375
  122. package/src/commands/help.mjs +0 -324
  123. package/src/commands/status.mjs +0 -331
  124. package/src/monitor/health.mjs +0 -486
  125. package/src/monitor/performance.mjs +0 -442
  126. package/src/monitor/report.mjs +0 -535
  127. package/src/orchestrator/classify.mjs +0 -391
  128. package/src/orchestrator/confidence.mjs +0 -151
  129. package/src/orchestrator/handoffs.mjs +0 -231
  130. package/src/orchestrator/review.mjs +0 -222
  131. package/src/providers/balance.mjs +0 -201
  132. package/src/providers/claude.mjs +0 -236
  133. package/src/providers/codex.mjs +0 -255
  134. package/src/providers/detect.mjs +0 -185
  135. package/src/providers/errors.mjs +0 -373
  136. package/src/providers/select.mjs +0 -162
  137. package/src/repl-enhanced.mjs +0 -417
  138. package/src/repl.mjs +0 -321
  139. package/src/state/archive.mjs +0 -366
  140. package/src/state/atomic.mjs +0 -116
  141. package/src/state/cleanup.mjs +0 -440
  142. package/src/state/recovery.mjs +0 -461
  143. package/src/state/session.mjs +0 -147
  144. package/src/ui/errors.mjs +0 -456
  145. package/src/ui/formatter.mjs +0 -327
  146. package/src/ui/icons.mjs +0 -318
  147. package/src/ui/progress.mjs +0 -468
  148. package/templates/prompts/confidence-format.txt +0 -14
  149. package/templates/prompts/ic-with-feedback.txt +0 -41
  150. package/templates/prompts/ic.txt +0 -13
  151. package/templates/prompts/manager-review.txt +0 -40
  152. package/templates/prompts/manager.txt +0 -14
  153. 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;