myshell-tools 1.0.0 → 2.0.0-alpha.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 (150) 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 +106 -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/core/assess.d.ts +25 -0
  14. package/dist/core/assess.js +142 -0
  15. package/dist/core/assess.js.map +1 -0
  16. package/dist/core/classify.d.ts +19 -0
  17. package/dist/core/classify.js +80 -0
  18. package/dist/core/classify.js.map +1 -0
  19. package/dist/core/escalate.d.ts +32 -0
  20. package/dist/core/escalate.js +57 -0
  21. package/dist/core/escalate.js.map +1 -0
  22. package/dist/core/index.d.ts +13 -0
  23. package/dist/core/index.js +12 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/orchestrate.d.ts +42 -0
  26. package/dist/core/orchestrate.js +439 -0
  27. package/dist/core/orchestrate.js.map +1 -0
  28. package/dist/core/policy.d.ts +9 -0
  29. package/dist/core/policy.js +27 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/prompt.d.ts +26 -0
  32. package/dist/core/prompt.js +125 -0
  33. package/dist/core/prompt.js.map +1 -0
  34. package/dist/core/review.d.ts +46 -0
  35. package/dist/core/review.js +148 -0
  36. package/dist/core/review.js.map +1 -0
  37. package/dist/core/route.d.ts +28 -0
  38. package/dist/core/route.js +52 -0
  39. package/dist/core/route.js.map +1 -0
  40. package/dist/core/types.d.ts +141 -0
  41. package/dist/core/types.js +14 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/infra/atomic.d.ts +53 -0
  44. package/dist/infra/atomic.js +171 -0
  45. package/dist/infra/atomic.js.map +1 -0
  46. package/dist/infra/clock.d.ts +9 -0
  47. package/dist/infra/clock.js +15 -0
  48. package/dist/infra/clock.js.map +1 -0
  49. package/dist/infra/index.d.ts +9 -0
  50. package/dist/infra/index.js +7 -0
  51. package/dist/infra/index.js.map +1 -0
  52. package/dist/infra/ledger.d.ts +49 -0
  53. package/dist/infra/ledger.js +90 -0
  54. package/dist/infra/ledger.js.map +1 -0
  55. package/dist/infra/paths.d.ts +28 -0
  56. package/dist/infra/paths.js +38 -0
  57. package/dist/infra/paths.js.map +1 -0
  58. package/dist/infra/pricing.d.ts +47 -0
  59. package/dist/infra/pricing.js +151 -0
  60. package/dist/infra/pricing.js.map +1 -0
  61. package/dist/infra/session.d.ts +28 -0
  62. package/dist/infra/session.js +61 -0
  63. package/dist/infra/session.js.map +1 -0
  64. package/dist/interface/render.d.ts +27 -0
  65. package/dist/interface/render.js +134 -0
  66. package/dist/interface/render.js.map +1 -0
  67. package/dist/interface/repl.d.ts +23 -0
  68. package/dist/interface/repl.js +90 -0
  69. package/dist/interface/repl.js.map +1 -0
  70. package/dist/interface/run.d.ts +20 -0
  71. package/dist/interface/run.js +31 -0
  72. package/dist/interface/run.js.map +1 -0
  73. package/dist/providers/claude-parse.d.ts +24 -0
  74. package/dist/providers/claude-parse.js +113 -0
  75. package/dist/providers/claude-parse.js.map +1 -0
  76. package/dist/providers/claude.d.ts +45 -0
  77. package/dist/providers/claude.js +122 -0
  78. package/dist/providers/claude.js.map +1 -0
  79. package/dist/providers/codex-parse.d.ts +32 -0
  80. package/dist/providers/codex-parse.js +145 -0
  81. package/dist/providers/codex-parse.js.map +1 -0
  82. package/dist/providers/codex.d.ts +44 -0
  83. package/dist/providers/codex.js +124 -0
  84. package/dist/providers/codex.js.map +1 -0
  85. package/dist/providers/detect.d.ts +49 -0
  86. package/dist/providers/detect.js +125 -0
  87. package/dist/providers/detect.js.map +1 -0
  88. package/dist/providers/errors.d.ts +49 -0
  89. package/dist/providers/errors.js +189 -0
  90. package/dist/providers/errors.js.map +1 -0
  91. package/dist/providers/index.d.ts +9 -0
  92. package/dist/providers/index.js +7 -0
  93. package/dist/providers/index.js.map +1 -0
  94. package/dist/providers/port.d.ts +74 -0
  95. package/dist/providers/port.js +16 -0
  96. package/dist/providers/port.js.map +1 -0
  97. package/dist/providers/registry.d.ts +21 -0
  98. package/dist/providers/registry.js +34 -0
  99. package/dist/providers/registry.js.map +1 -0
  100. package/dist/ui/banner.d.ts +19 -0
  101. package/dist/ui/banner.js +32 -0
  102. package/dist/ui/banner.js.map +1 -0
  103. package/dist/ui/spinner.d.ts +27 -0
  104. package/dist/ui/spinner.js +67 -0
  105. package/dist/ui/spinner.js.map +1 -0
  106. package/dist/ui/theme.d.ts +32 -0
  107. package/dist/ui/theme.js +56 -0
  108. package/dist/ui/theme.js.map +1 -0
  109. package/package.json +55 -49
  110. package/data/orchestrator.json +0 -113
  111. package/src/auth/recovery.mjs +0 -328
  112. package/src/auth/refresh.mjs +0 -373
  113. package/src/chef.mjs +0 -348
  114. package/src/cli/doctor.mjs +0 -568
  115. package/src/cli/reset.mjs +0 -447
  116. package/src/cli/status.mjs +0 -379
  117. package/src/cli.mjs +0 -429
  118. package/src/commands/doctor.mjs +0 -375
  119. package/src/commands/help.mjs +0 -324
  120. package/src/commands/status.mjs +0 -331
  121. package/src/monitor/health.mjs +0 -486
  122. package/src/monitor/performance.mjs +0 -442
  123. package/src/monitor/report.mjs +0 -535
  124. package/src/orchestrator/classify.mjs +0 -391
  125. package/src/orchestrator/confidence.mjs +0 -151
  126. package/src/orchestrator/handoffs.mjs +0 -231
  127. package/src/orchestrator/review.mjs +0 -222
  128. package/src/providers/balance.mjs +0 -201
  129. package/src/providers/claude.mjs +0 -236
  130. package/src/providers/codex.mjs +0 -255
  131. package/src/providers/detect.mjs +0 -185
  132. package/src/providers/errors.mjs +0 -373
  133. package/src/providers/select.mjs +0 -162
  134. package/src/repl-enhanced.mjs +0 -417
  135. package/src/repl.mjs +0 -321
  136. package/src/state/archive.mjs +0 -366
  137. package/src/state/atomic.mjs +0 -116
  138. package/src/state/cleanup.mjs +0 -440
  139. package/src/state/recovery.mjs +0 -461
  140. package/src/state/session.mjs +0 -147
  141. package/src/ui/errors.mjs +0 -456
  142. package/src/ui/formatter.mjs +0 -327
  143. package/src/ui/icons.mjs +0 -318
  144. package/src/ui/progress.mjs +0 -468
  145. package/templates/prompts/confidence-format.txt +0 -14
  146. package/templates/prompts/ic-with-feedback.txt +0 -41
  147. package/templates/prompts/ic.txt +0 -13
  148. package/templates/prompts/manager-review.txt +0 -40
  149. package/templates/prompts/manager.txt +0 -14
  150. package/templates/prompts/worker.txt +0 -12
@@ -0,0 +1,171 @@
1
+ /**
2
+ * atomic.ts — Atomic file operations for safe concurrent access
3
+ * Ported from myshell-tools/src/state/atomic.mjs with the following fixes:
4
+ * - Full TypeScript strict typing
5
+ * - Async (fs/promises) throughout
6
+ * - Async backoff instead of CPU-spinning lock acquisition
7
+ * - O(1) JSONL append via fs.appendFile (no read-then-rewrite)
8
+ */
9
+ import { open, rename, unlink, stat, appendFile } from 'node:fs/promises';
10
+ import { constants } from 'node:fs';
11
+ import { randomBytes } from 'node:crypto';
12
+ export class LockTimeoutError extends Error {
13
+ constructor(lockPath, timeoutMs) {
14
+ super(`Lock acquisition timed out after ${timeoutMs}ms for: ${lockPath}`);
15
+ this.name = 'LockTimeoutError';
16
+ }
17
+ }
18
+ export class AtomicWriteError extends Error {
19
+ constructor(filePath, cause) {
20
+ super(`Atomic write failed for: ${filePath} — ${cause instanceof Error ? cause.message : String(cause)}`);
21
+ this.name = 'AtomicWriteError';
22
+ }
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Internal helpers
26
+ // ---------------------------------------------------------------------------
27
+ const DEFAULT_TIMEOUT_MS = 5_000;
28
+ const DEFAULT_STALE_MS = 10_000;
29
+ /** Async sleep. */
30
+ function sleep(ms) {
31
+ return new Promise((resolve) => setTimeout(resolve, ms));
32
+ }
33
+ /** Unique suffix so concurrent processes never collide on the tmp file. */
34
+ function tmpSuffix() {
35
+ return `${process.pid}.${randomBytes(4).toString('hex')}`;
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Lock primitives
39
+ // ---------------------------------------------------------------------------
40
+ /**
41
+ * Acquire a `.lock` file using `O_EXCL` for atomic creation.
42
+ * Retries with exponential backoff until `timeoutMs` is reached.
43
+ * Steals locks whose mtime is older than `staleMs`.
44
+ *
45
+ * Throws `LockTimeoutError` if the lock cannot be acquired in time.
46
+ */
47
+ export async function acquireLock(lockPath, opts) {
48
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
49
+ const staleMs = opts?.staleMs ?? DEFAULT_STALE_MS;
50
+ const deadline = Date.now() + timeoutMs;
51
+ let attempt = 0;
52
+ while (Date.now() < deadline) {
53
+ try {
54
+ // O_EXCL guarantees atomic creation — only one caller wins
55
+ const fh = await open(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
56
+ try {
57
+ await fh.writeFile(JSON.stringify({ pid: process.pid, ts: Date.now() }));
58
+ }
59
+ finally {
60
+ await fh.close();
61
+ }
62
+ return; // lock acquired
63
+ }
64
+ catch (err) {
65
+ const nodeErr = err;
66
+ if (nodeErr.code !== 'EEXIST')
67
+ throw err;
68
+ // Check whether the existing lock is stale
69
+ try {
70
+ const st = await stat(lockPath);
71
+ if (Date.now() - st.mtimeMs > staleMs) {
72
+ // Stale — the holding process likely crashed; steal it
73
+ try {
74
+ await unlink(lockPath);
75
+ }
76
+ catch {
77
+ // Another process may have already removed it; harmless
78
+ }
79
+ continue; // retry immediately
80
+ }
81
+ }
82
+ catch {
83
+ // Lock file disappeared between the EEXIST and stat — retry
84
+ continue;
85
+ }
86
+ // Back off before the next attempt (50 ms, 100 ms, 200 ms … up to 1 s)
87
+ const waitMs = Math.min(50 * 2 ** attempt, 1_000);
88
+ attempt++;
89
+ // Don't sleep past the deadline
90
+ const remaining = deadline - Date.now();
91
+ if (remaining <= 0)
92
+ break;
93
+ await sleep(Math.min(waitMs, remaining));
94
+ }
95
+ }
96
+ throw new LockTimeoutError(lockPath, timeoutMs);
97
+ }
98
+ /**
99
+ * Release a lock file previously acquired with `acquireLock`.
100
+ * Silently ignores a missing lock file (idempotent).
101
+ */
102
+ export async function releaseLock(lockPath) {
103
+ try {
104
+ await unlink(lockPath);
105
+ }
106
+ catch {
107
+ // Already gone — that's fine
108
+ }
109
+ }
110
+ /**
111
+ * Convenience wrapper: acquire the lock, run `fn`, then always release.
112
+ * Re-throws any error from `fn` after releasing the lock.
113
+ */
114
+ export async function withLock(lockPath, fn, opts) {
115
+ await acquireLock(lockPath, opts);
116
+ try {
117
+ return await fn();
118
+ }
119
+ finally {
120
+ await releaseLock(lockPath);
121
+ }
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Atomic file operations
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Atomically write `data` to `filePath` using a tmp-file + rename strategy.
128
+ * The tmp file lives in the same directory to avoid cross-device rename issues.
129
+ */
130
+ export async function atomicWrite(filePath, data) {
131
+ const tmp = `${filePath}.tmp.${tmpSuffix()}`;
132
+ try {
133
+ const fh = await open(tmp, 'w');
134
+ try {
135
+ await fh.writeFile(data);
136
+ }
137
+ finally {
138
+ await fh.close();
139
+ }
140
+ await rename(tmp, filePath);
141
+ }
142
+ catch (err) {
143
+ // Best-effort cleanup of orphaned tmp file
144
+ try {
145
+ await unlink(tmp);
146
+ }
147
+ catch {
148
+ /* ignore */
149
+ }
150
+ throw new AtomicWriteError(filePath, err);
151
+ }
152
+ }
153
+ /**
154
+ * Atomically append a single JSONL entry to `filePath`.
155
+ *
156
+ * This is O(1) — it uses `fs.appendFile` rather than reading the entire file
157
+ * and rewriting it on every call. The caller is responsible for holding a lock
158
+ * when concurrent appends must be strictly ordered.
159
+ *
160
+ * Creates `filePath` if it does not exist.
161
+ */
162
+ export async function atomicAppendJSONL(filePath, entry) {
163
+ const line = JSON.stringify(entry) + '\n';
164
+ try {
165
+ await appendFile(filePath, line, 'utf8');
166
+ }
167
+ catch (err) {
168
+ throw new AtomicWriteError(filePath, err);
169
+ }
170
+ }
171
+ //# sourceMappingURL=atomic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/infra/atomic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * src/infra/clock.ts — System clock implementation of the Clock port.
3
+ *
4
+ * This is the SINGLE place in the codebase where impure time/random globals
5
+ * (Date, Math.random, randomUUID) are allowed. All other modules receive a
6
+ * Clock via injection, keeping them pure and testable.
7
+ */
8
+ import type { Clock } from '../core/types.js';
9
+ export declare const systemClock: Clock;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * src/infra/clock.ts — System clock implementation of the Clock port.
3
+ *
4
+ * This is the SINGLE place in the codebase where impure time/random globals
5
+ * (Date, Math.random, randomUUID) are allowed. All other modules receive a
6
+ * Clock via injection, keeping them pure and testable.
7
+ */
8
+ import { randomUUID } from 'node:crypto';
9
+ export const systemClock = {
10
+ now: () => Date.now(),
11
+ isoNow: () => new Date().toISOString(),
12
+ uuid: () => randomUUID(),
13
+ random: () => Math.random(),
14
+ };
15
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../../src/infra/clock.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IACrB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IACtC,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;IACxB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;CAC5B,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { acquireLock, releaseLock, withLock, atomicWrite, atomicAppendJSONL } from './atomic.js';
2
+ export type { LockOptions } from './atomic.js';
3
+ export { getModelPricing, calculateCost, getCheapestForTier, isPricingStale, PRICING_TABLE, } from './pricing.js';
4
+ export type { ModelPricing, PricingTable } from './pricing.js';
5
+ export { systemClock } from './clock.js';
6
+ export { getStateDir, getSessionsDir, getSessionFile, getLedgerFile } from './paths.js';
7
+ export { createSessionWriter, readSession } from './session.js';
8
+ export { createLedger, readLedger, summarizeLedger } from './ledger.js';
9
+ export type { ModelSummary, LedgerSummary } from './ledger.js';
@@ -0,0 +1,7 @@
1
+ export { acquireLock, releaseLock, withLock, atomicWrite, atomicAppendJSONL } from './atomic.js';
2
+ export { getModelPricing, calculateCost, getCheapestForTier, isPricingStale, PRICING_TABLE, } from './pricing.js';
3
+ export { systemClock } from './clock.js';
4
+ export { getStateDir, getSessionsDir, getSessionFile, getLedgerFile } from './paths.js';
5
+ export { createSessionWriter, readSession } from './session.js';
6
+ export { createLedger, readLedger, summarizeLedger } from './ledger.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infra/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEjG,OAAO,EACL,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * src/infra/ledger.ts — LedgerWriter implementation, ledger reader, and summarizer.
3
+ *
4
+ * The ledger is a JSONL file at `.myshell-tools/ledger.jsonl`. Each `record` call appends
5
+ * one LedgerEntry atomically. `readLedger` re-hydrates entries (skipping malformed
6
+ * lines). `summarizeLedger` is a pure reduction used by `myshell-tools cost`.
7
+ */
8
+ import type { LedgerEntry, LedgerWriter } from '../core/types.js';
9
+ /**
10
+ * Create a LedgerWriter for the given working directory.
11
+ *
12
+ * @param opts.cwd - The project working directory (parent of `.myshell-tools/`).
13
+ */
14
+ export declare function createLedger(opts: {
15
+ cwd: string;
16
+ }): LedgerWriter;
17
+ /**
18
+ * Read all LedgerEntry records from the ledger JSONL file.
19
+ *
20
+ * Returns an empty array if the file does not exist. Malformed lines are
21
+ * silently skipped.
22
+ *
23
+ * @param cwd - The project working directory.
24
+ */
25
+ export declare function readLedger(cwd: string): Promise<LedgerEntry[]>;
26
+ /** Per-model aggregation used in the summary. */
27
+ export interface ModelSummary {
28
+ readonly calls: number;
29
+ readonly usd: number;
30
+ }
31
+ /** Aggregate summary returned by `summarizeLedger`. */
32
+ export interface LedgerSummary {
33
+ readonly totalUsd: number;
34
+ readonly calls: number;
35
+ readonly byModel: Record<string, ModelSummary>;
36
+ }
37
+ /**
38
+ * Pure reduction over a ledger entry array.
39
+ *
40
+ * Computes:
41
+ * - `totalUsd` — sum of all `entry.usd` values
42
+ * - `calls` — total number of entries
43
+ * - `byModel` — per-model breakdown keyed by `entry.model`
44
+ *
45
+ * No I/O. Safe to call in tests with hand-built arrays.
46
+ *
47
+ * @param entries - Array of LedgerEntry objects (may be empty).
48
+ */
49
+ export declare function summarizeLedger(entries: LedgerEntry[]): LedgerSummary;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * src/infra/ledger.ts — LedgerWriter implementation, ledger reader, and summarizer.
3
+ *
4
+ * The ledger is a JSONL file at `.myshell-tools/ledger.jsonl`. Each `record` call appends
5
+ * one LedgerEntry atomically. `readLedger` re-hydrates entries (skipping malformed
6
+ * lines). `summarizeLedger` is a pure reduction used by `myshell-tools cost`.
7
+ */
8
+ import { mkdir, readFile } from 'node:fs/promises';
9
+ import { atomicAppendJSONL } from './atomic.js';
10
+ import { getStateDir, getLedgerFile } from './paths.js';
11
+ /**
12
+ * Create a LedgerWriter for the given working directory.
13
+ *
14
+ * @param opts.cwd - The project working directory (parent of `.myshell-tools/`).
15
+ */
16
+ export function createLedger(opts) {
17
+ const { cwd } = opts;
18
+ return {
19
+ async record(entry) {
20
+ await mkdir(getStateDir(cwd), { recursive: true });
21
+ await atomicAppendJSONL(getLedgerFile(cwd), entry);
22
+ },
23
+ };
24
+ }
25
+ /**
26
+ * Read all LedgerEntry records from the ledger JSONL file.
27
+ *
28
+ * Returns an empty array if the file does not exist. Malformed lines are
29
+ * silently skipped.
30
+ *
31
+ * @param cwd - The project working directory.
32
+ */
33
+ export async function readLedger(cwd) {
34
+ let raw;
35
+ try {
36
+ raw = await readFile(getLedgerFile(cwd), 'utf8');
37
+ }
38
+ catch (err) {
39
+ const nodeErr = err;
40
+ if (nodeErr.code === 'ENOENT')
41
+ return [];
42
+ throw err;
43
+ }
44
+ const entries = [];
45
+ for (const line of raw.split('\n')) {
46
+ const trimmed = line.trim();
47
+ if (trimmed.length === 0)
48
+ continue;
49
+ try {
50
+ entries.push(JSON.parse(trimmed));
51
+ }
52
+ catch {
53
+ // Skip malformed lines
54
+ }
55
+ }
56
+ return entries;
57
+ }
58
+ /**
59
+ * Pure reduction over a ledger entry array.
60
+ *
61
+ * Computes:
62
+ * - `totalUsd` — sum of all `entry.usd` values
63
+ * - `calls` — total number of entries
64
+ * - `byModel` — per-model breakdown keyed by `entry.model`
65
+ *
66
+ * No I/O. Safe to call in tests with hand-built arrays.
67
+ *
68
+ * @param entries - Array of LedgerEntry objects (may be empty).
69
+ */
70
+ export function summarizeLedger(entries) {
71
+ let totalUsd = 0;
72
+ const byModel = {};
73
+ for (const entry of entries) {
74
+ totalUsd += entry.usd;
75
+ const existing = byModel[entry.model];
76
+ if (existing !== undefined) {
77
+ existing.calls += 1;
78
+ existing.usd += entry.usd;
79
+ }
80
+ else {
81
+ byModel[entry.model] = { calls: 1, usd: entry.usd };
82
+ }
83
+ }
84
+ return {
85
+ totalUsd,
86
+ calls: entries.length,
87
+ byModel,
88
+ };
89
+ }
90
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/infra/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAqB;IAChD,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAErB,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAkB;YAC7B,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,iBAAiB,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAA4B,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACzC,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACnC,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAeD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,OAAsB;IACpD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,OAAO,GAAmD,EAAE,CAAC;IAEnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC;QAEtB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;YACpB,QAAQ,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * src/infra/paths.ts — Pure path helpers for myshell-tools's filesystem layout.
3
+ *
4
+ * All functions are pure path joins (no fs I/O). The canonical layout is:
5
+ * <cwd>/
6
+ * .myshell-tools/
7
+ * sessions/
8
+ * current.jsonl ← active session log
9
+ * ledger.jsonl ← cost/usage ledger
10
+ */
11
+ /**
12
+ * Returns the path to the `.myshell-tools` directory inside the given working dir.
13
+ */
14
+ export declare function getStateDir(cwd: string): string;
15
+ /**
16
+ * Returns the path to the `sessions` subdirectory inside `.myshell-tools`.
17
+ */
18
+ export declare function getSessionsDir(cwd: string): string;
19
+ /**
20
+ * Returns the path to the current session JSONL file.
21
+ * Path: <cwd>/.myshell-tools/sessions/current.jsonl
22
+ */
23
+ export declare function getSessionFile(cwd: string): string;
24
+ /**
25
+ * Returns the path to the cost/usage ledger JSONL file.
26
+ * Path: <cwd>/.myshell-tools/ledger.jsonl
27
+ */
28
+ export declare function getLedgerFile(cwd: string): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * src/infra/paths.ts — Pure path helpers for myshell-tools's filesystem layout.
3
+ *
4
+ * All functions are pure path joins (no fs I/O). The canonical layout is:
5
+ * <cwd>/
6
+ * .myshell-tools/
7
+ * sessions/
8
+ * current.jsonl ← active session log
9
+ * ledger.jsonl ← cost/usage ledger
10
+ */
11
+ import { join } from 'node:path';
12
+ /**
13
+ * Returns the path to the `.myshell-tools` directory inside the given working dir.
14
+ */
15
+ export function getStateDir(cwd) {
16
+ return join(cwd, '.myshell-tools');
17
+ }
18
+ /**
19
+ * Returns the path to the `sessions` subdirectory inside `.myshell-tools`.
20
+ */
21
+ export function getSessionsDir(cwd) {
22
+ return join(getStateDir(cwd), 'sessions');
23
+ }
24
+ /**
25
+ * Returns the path to the current session JSONL file.
26
+ * Path: <cwd>/.myshell-tools/sessions/current.jsonl
27
+ */
28
+ export function getSessionFile(cwd) {
29
+ return join(getSessionsDir(cwd), 'current.jsonl');
30
+ }
31
+ /**
32
+ * Returns the path to the cost/usage ledger JSONL file.
33
+ * Path: <cwd>/.myshell-tools/ledger.jsonl
34
+ */
35
+ export function getLedgerFile(cwd) {
36
+ return join(getStateDir(cwd), 'ledger.jsonl');
37
+ }
38
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/infra/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * src/infra/pricing.ts
3
+ *
4
+ * Corrected price table — Appendix A of the myshell-tools project plan.
5
+ *
6
+ * Provenance
7
+ * ----------
8
+ * Claude pricing : https://www.anthropic.com/pricing
9
+ * OpenAI pricing : https://platform.openai.com/docs/pricing
10
+ * Captured : 2026-05-29
11
+ */
12
+ export interface ModelPricing {
13
+ readonly provider: 'claude' | 'codex';
14
+ readonly model: string;
15
+ readonly aliases: readonly string[];
16
+ readonly tier: 'worker' | 'ic' | 'manager';
17
+ readonly inputPer1M: number;
18
+ readonly outputPer1M: number;
19
+ readonly contextWindow: number;
20
+ }
21
+ export interface PricingTable {
22
+ readonly asOf: string;
23
+ readonly sourceUrls: readonly string[];
24
+ readonly models: readonly ModelPricing[];
25
+ }
26
+ export declare const PRICING_TABLE: PricingTable;
27
+ /**
28
+ * Look up a model entry by exact model ID or any of its aliases.
29
+ * The lookup is case-insensitive.
30
+ */
31
+ export declare function getModelPricing(provider: string, model: string): ModelPricing | undefined;
32
+ /**
33
+ * Calculate the USD cost for a given number of input and output tokens.
34
+ */
35
+ export declare function calculateCost(inputTokens: number, outputTokens: number, pricing: ModelPricing): number;
36
+ /**
37
+ * Return the cheapest model (lowest inputPer1M) for a given tier,
38
+ * optionally restricted to the supplied provider IDs.
39
+ *
40
+ * Throws if no matching model exists.
41
+ */
42
+ export declare function getCheapestForTier(tier: 'worker' | 'ic' | 'manager', availableProviders?: string[]): ModelPricing;
43
+ /**
44
+ * Returns true when the pricing table is older than maxAgeDays (default 90).
45
+ * Useful for emitting a staleness warning at runtime.
46
+ */
47
+ export declare function isPricingStale(maxAgeDays?: number): boolean;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * src/infra/pricing.ts
3
+ *
4
+ * Corrected price table — Appendix A of the myshell-tools project plan.
5
+ *
6
+ * Provenance
7
+ * ----------
8
+ * Claude pricing : https://www.anthropic.com/pricing
9
+ * OpenAI pricing : https://platform.openai.com/docs/pricing
10
+ * Captured : 2026-05-29
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Data
14
+ // ---------------------------------------------------------------------------
15
+ export const PRICING_TABLE = {
16
+ asOf: '2026-05-29',
17
+ sourceUrls: [
18
+ 'https://www.anthropic.com/pricing',
19
+ 'https://platform.openai.com/docs/pricing',
20
+ ],
21
+ models: [
22
+ // ---- Anthropic / Claude ------------------------------------------------
23
+ {
24
+ provider: 'claude',
25
+ model: 'claude-opus-4-7',
26
+ aliases: ['opus', 'opus-4.7', 'claude-opus-4.7'],
27
+ tier: 'manager',
28
+ inputPer1M: 5,
29
+ outputPer1M: 25,
30
+ contextWindow: 200_000,
31
+ },
32
+ {
33
+ provider: 'claude',
34
+ model: 'claude-sonnet-4-6',
35
+ aliases: ['sonnet', 'sonnet-4.6', 'claude-sonnet-4.6'],
36
+ tier: 'ic',
37
+ inputPer1M: 3,
38
+ outputPer1M: 15,
39
+ contextWindow: 200_000,
40
+ },
41
+ {
42
+ provider: 'claude',
43
+ model: 'claude-haiku-4-5',
44
+ aliases: ['haiku', 'haiku-4.5', 'claude-haiku-4.5'],
45
+ tier: 'worker',
46
+ inputPer1M: 0.8,
47
+ outputPer1M: 4,
48
+ contextWindow: 200_000,
49
+ },
50
+ // ---- OpenAI / Codex ----------------------------------------------------
51
+ {
52
+ provider: 'codex',
53
+ model: 'gpt-5.5',
54
+ aliases: ['gpt5.5', 'gpt-5-5'],
55
+ tier: 'manager',
56
+ inputPer1M: 5,
57
+ outputPer1M: 30,
58
+ contextWindow: 128_000,
59
+ },
60
+ {
61
+ provider: 'codex',
62
+ model: 'gpt-5.4',
63
+ aliases: ['gpt5.4', 'gpt-5-4'],
64
+ tier: 'ic',
65
+ inputPer1M: 2.5,
66
+ outputPer1M: 15,
67
+ contextWindow: 128_000,
68
+ },
69
+ {
70
+ provider: 'codex',
71
+ model: 'gpt-5.4-mini',
72
+ aliases: ['gpt5.4-mini', 'gpt-5-4-mini'],
73
+ tier: 'worker',
74
+ inputPer1M: 0.75,
75
+ outputPer1M: 4.5,
76
+ contextWindow: 128_000,
77
+ },
78
+ {
79
+ provider: 'codex',
80
+ model: 'gpt-5.4-nano',
81
+ aliases: ['gpt5.4-nano', 'gpt-5-4-nano'],
82
+ tier: 'worker',
83
+ inputPer1M: 0.2,
84
+ outputPer1M: 1.25,
85
+ contextWindow: 128_000,
86
+ },
87
+ {
88
+ provider: 'codex',
89
+ model: 'gpt-5.2-codex',
90
+ aliases: ['codex', 'gpt5.2-codex', 'gpt-5-2-codex'],
91
+ tier: 'ic',
92
+ inputPer1M: 1.75,
93
+ outputPer1M: 14,
94
+ contextWindow: 128_000,
95
+ },
96
+ ],
97
+ };
98
+ // ---------------------------------------------------------------------------
99
+ // Helpers
100
+ // ---------------------------------------------------------------------------
101
+ /**
102
+ * Look up a model entry by exact model ID or any of its aliases.
103
+ * The lookup is case-insensitive.
104
+ */
105
+ export function getModelPricing(provider, model) {
106
+ const needle = model.toLowerCase();
107
+ return PRICING_TABLE.models.find((m) => m.provider === provider &&
108
+ (m.model.toLowerCase() === needle ||
109
+ m.aliases.some((a) => a.toLowerCase() === needle)));
110
+ }
111
+ /**
112
+ * Calculate the USD cost for a given number of input and output tokens.
113
+ */
114
+ export function calculateCost(inputTokens, outputTokens, pricing) {
115
+ const inputCost = (inputTokens / 1_000_000) * pricing.inputPer1M;
116
+ const outputCost = (outputTokens / 1_000_000) * pricing.outputPer1M;
117
+ return inputCost + outputCost;
118
+ }
119
+ /**
120
+ * Return the cheapest model (lowest inputPer1M) for a given tier,
121
+ * optionally restricted to the supplied provider IDs.
122
+ *
123
+ * Throws if no matching model exists.
124
+ */
125
+ export function getCheapestForTier(tier, availableProviders) {
126
+ let candidates = PRICING_TABLE.models.filter((m) => m.tier === tier);
127
+ if (availableProviders && availableProviders.length > 0) {
128
+ candidates = candidates.filter((m) => availableProviders.includes(m.provider));
129
+ }
130
+ if (candidates.length === 0) {
131
+ throw new Error(`No models available for tier "${tier}"` +
132
+ (availableProviders ? ` with providers [${availableProviders.join(', ')}]` : ''));
133
+ }
134
+ // Primary sort: inputPer1M ascending; secondary: outputPer1M ascending
135
+ return candidates.reduce((cheapest, m) => m.inputPer1M < cheapest.inputPer1M ||
136
+ (m.inputPer1M === cheapest.inputPer1M && m.outputPer1M < cheapest.outputPer1M)
137
+ ? m
138
+ : cheapest);
139
+ }
140
+ /**
141
+ * Returns true when the pricing table is older than maxAgeDays (default 90).
142
+ * Useful for emitting a staleness warning at runtime.
143
+ */
144
+ export function isPricingStale(maxAgeDays = 90) {
145
+ const asOf = new Date(PRICING_TABLE.asOf);
146
+ const now = new Date();
147
+ const diffMs = now.getTime() - asOf.getTime();
148
+ const diffDays = diffMs / (1_000 * 60 * 60 * 24);
149
+ return diffDays > maxAgeDays;
150
+ }
151
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/infra/pricing.ts"],"names":[],"mappings":"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"}