myshell-tools 2.15.0 → 3.2.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 (57) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/README.md +39 -44
  3. package/dist/cli.js +33 -22
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/cost.js +44 -21
  6. package/dist/commands/cost.js.map +1 -1
  7. package/dist/commands/doctor.d.ts +17 -4
  8. package/dist/commands/doctor.js +62 -35
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/login.d.ts +18 -10
  11. package/dist/commands/login.js +81 -109
  12. package/dist/commands/login.js.map +1 -1
  13. package/dist/core/json-envelope.js +31 -3
  14. package/dist/core/json-envelope.js.map +1 -1
  15. package/dist/core/native-session.d.ts +57 -0
  16. package/dist/core/native-session.js +68 -0
  17. package/dist/core/native-session.js.map +1 -0
  18. package/dist/core/orchestrate.js +106 -43
  19. package/dist/core/orchestrate.js.map +1 -1
  20. package/dist/core/types.d.ts +26 -0
  21. package/dist/infra/atomic.d.ts +9 -1
  22. package/dist/infra/atomic.js +12 -2
  23. package/dist/infra/atomic.js.map +1 -1
  24. package/dist/infra/config.d.ts +8 -0
  25. package/dist/infra/config.js.map +1 -1
  26. package/dist/infra/credentials.d.ts +69 -3
  27. package/dist/infra/credentials.js +118 -9
  28. package/dist/infra/credentials.js.map +1 -1
  29. package/dist/infra/health.d.ts +57 -0
  30. package/dist/infra/health.js +96 -0
  31. package/dist/infra/health.js.map +1 -0
  32. package/dist/infra/insights.d.ts +14 -0
  33. package/dist/infra/insights.js +31 -0
  34. package/dist/infra/insights.js.map +1 -1
  35. package/dist/interface/menu.d.ts +70 -5
  36. package/dist/interface/menu.js +292 -88
  37. package/dist/interface/menu.js.map +1 -1
  38. package/dist/interface/render.js +20 -13
  39. package/dist/interface/render.js.map +1 -1
  40. package/dist/providers/claude.d.ts +24 -8
  41. package/dist/providers/claude.js +77 -15
  42. package/dist/providers/claude.js.map +1 -1
  43. package/dist/providers/codex-parse.js +14 -2
  44. package/dist/providers/codex-parse.js.map +1 -1
  45. package/dist/providers/codex.d.ts +13 -1
  46. package/dist/providers/codex.js +19 -8
  47. package/dist/providers/codex.js.map +1 -1
  48. package/dist/providers/detect.js +22 -1
  49. package/dist/providers/detect.js.map +1 -1
  50. package/dist/providers/port.d.ts +15 -0
  51. package/dist/providers/registry.d.ts +8 -4
  52. package/dist/providers/registry.js +7 -6
  53. package/dist/providers/registry.js.map +1 -1
  54. package/dist/ui/help.d.ts +17 -0
  55. package/dist/ui/help.js +106 -0
  56. package/dist/ui/help.js.map +1 -0
  57. package/package.json +1 -1
@@ -17,6 +17,14 @@ export interface AppConfig {
17
17
  * disable auto-update and show only the notification banner instead.
18
18
  */
19
19
  autoUpdate?: boolean;
20
+ /**
21
+ * EXPERIMENTAL (default off). When true, a conversation that stays on the same
22
+ * provider reuses that provider's native session (Claude `--session-id`/
23
+ * `--resume`) instead of replaying a compacted history block into each prompt
24
+ * — better context fidelity and less re-sent context. Scoped to Claude for
25
+ * now. Verify live behavior with `npm run test:integration` before relying on it.
26
+ */
27
+ nativeSessions?: boolean;
20
28
  }
21
29
  /**
22
30
  * Load the global app config. Returns defaults merged with any on-disk
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/infra/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoB1C,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,MAAM,QAAQ,GAAc;IAC1B,SAAS,EAAE,KAAK;IAChB,YAAY,EAAE,KAAK;IACnB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB;IAC/C,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;IAClC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;QACjC,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACrD,uEAAuE;QACvE,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;QACjC,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAiB,EAAE,OAAgB;IAClE,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/infra/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA4B1C,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,MAAM,QAAQ,GAAc;IAC1B,SAAS,EAAE,KAAK;IAChB,YAAY,EAAE,KAAK;IACnB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB;IAC/C,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;IAClC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;QACjC,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACrD,uEAAuE;QACvE,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;QACjC,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAiB,EAAE,OAAgB;IAClE,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC"}
@@ -5,9 +5,11 @@
5
5
  * shape `{ claudeOauthToken?: string }` so the Claude OAuth token captured
6
6
  * during `myshell-tools login claude --code` is available across restarts.
7
7
  *
8
- * On startup, `applyStoredCredentials` injects the token into `process.env` so
9
- * that both the provider detection (`claude auth status`) and the spawned
10
- * `claude -p …` child process see it via the inherited environment.
8
+ * Token scoping: instead of injecting the token into the global `process.env`
9
+ * at startup (which would expose it to every child process), callers use
10
+ * `loadClaudeToken()` + `claudeEnv()` to build a scoped env object that is
11
+ * passed only to Claude CLI invocations. Other providers (codex, opencode,
12
+ * npm) never see the token.
11
13
  *
12
14
  * Security: the file is written with mode 0o600 (owner-read-only) on POSIX
13
15
  * systems. The chmod is best-effort — a failure is silently ignored so the
@@ -15,16 +17,77 @@
15
17
  */
16
18
  export interface Credentials {
17
19
  claudeOauthToken?: string;
20
+ claudeTokenCapturedAt?: string;
21
+ }
22
+ export interface ClaudeTokenStatus {
23
+ /** ISO string of when the token was saved. */
24
+ readonly capturedAt: string;
25
+ /** ISO string of the computed expiry (capturedAt + lifetimeDays). */
26
+ readonly expiresAt: string;
27
+ /** Whole days remaining until expiry (floor). 0 when expiring today, negative when expired. */
28
+ readonly daysLeft: number;
29
+ /** True when daysLeft <= 0. */
30
+ readonly expired: boolean;
31
+ /** True when 0 < daysLeft <= warnWithinDays. */
32
+ readonly nearExpiry: boolean;
18
33
  }
34
+ /**
35
+ * Compute token lifetime status from a stored ISO capture timestamp.
36
+ *
37
+ * Pure — no I/O, no Date.now() inside. Pass `nowMs` for deterministic testing.
38
+ * Never throws.
39
+ *
40
+ * @param capturedAtIso - ISO string stored at save time, or undefined/null.
41
+ * @param nowMs - Current epoch-ms (e.g. Date.now() from the caller).
42
+ * @param lifetimeDays - Total token lifetime in days (default 365).
43
+ * @param warnWithinDays - Warn when this many days or fewer remain (default 14).
44
+ * @returns Status object, or null when capturedAtIso is missing or unparseable.
45
+ */
46
+ export declare function claudeTokenStatus(capturedAtIso: string | undefined, nowMs: number, lifetimeDays?: number, warnWithinDays?: number): ClaudeTokenStatus | null;
19
47
  /**
20
48
  * Load stored credentials. Never throws — missing or corrupt files return `{}`.
21
49
  */
22
50
  export declare function loadCredentials(homeDir?: string): Promise<Credentials>;
51
+ /**
52
+ * Load the stored Claude OAuth token. Returns `null` when no token is stored
53
+ * or when the credentials file is missing/corrupt. Never throws.
54
+ *
55
+ * This is a thin convenience wrapper over `loadCredentials` that returns the
56
+ * token string directly so callers don't need to destructure `Credentials`.
57
+ */
58
+ export declare function loadClaudeToken(homeDir?: string): Promise<string | null>;
59
+ /**
60
+ * Build a child-process environment that injects the Claude OAuth token into
61
+ * ONLY the `CLAUDE_CODE_OAUTH_TOKEN` variable, leaving all other variables
62
+ * from `baseEnv` intact.
63
+ *
64
+ * Rules (pure — no I/O):
65
+ * - Returns `baseEnv` unchanged when `token` is `null` (nothing stored).
66
+ * - Returns `baseEnv` unchanged when `baseEnv.CLAUDE_CODE_OAUTH_TOKEN` is
67
+ * already set — the user's explicitly-exported value always wins.
68
+ * - Otherwise returns `{ ...baseEnv, CLAUDE_CODE_OAUTH_TOKEN: token }`.
69
+ *
70
+ * Pass the result as the `env` option to execa for Claude CLI spawns only.
71
+ * Never pass it to codex/opencode/npm children — they do not need it and
72
+ * should not see the token.
73
+ *
74
+ * Pure / never throws.
75
+ */
76
+ export declare function claudeEnv(baseEnv: NodeJS.ProcessEnv, token: string | null): NodeJS.ProcessEnv;
23
77
  /**
24
78
  * Persist the Claude OAuth token atomically to
25
79
  * `~/.myshell-tools/credentials.json` with restrictive permissions (0o600).
80
+ *
81
+ * Records `claudeTokenCapturedAt` (ISO timestamp) so the token's age can be
82
+ * tracked for expiry warnings.
26
83
  */
27
84
  export declare function saveClaudeToken(token: string, homeDir?: string): Promise<void>;
85
+ /**
86
+ * Load the ISO timestamp recorded when the Claude OAuth token was last saved.
87
+ * Returns `undefined` when no token has been saved, or when the stored value
88
+ * is missing from an older credential file. Never throws.
89
+ */
90
+ export declare function loadClaudeTokenCapturedAt(homeDir?: string): Promise<string | undefined>;
28
91
  /**
29
92
  * Remove the stored Claude OAuth token. Writes the file back without the
30
93
  * token key so any future credential fields are preserved.
@@ -75,6 +138,8 @@ export declare function stripPastedSecretWrapper(raw: string): string;
75
138
  * - `'api-key'` — starts with `sk-ant-api` (a raw Anthropic API key, NOT what we want).
76
139
  * - `'none'` — neither; blank or unrecognised.
77
140
  *
141
+ * Uses `startsWith` semantics: mid-string occurrences of `sk-ant-oat` or
142
+ * `sk-ant-api` do NOT classify as oauth-token or api-key respectively.
78
143
  * Input is pre-normalised (trimmed, quotes stripped) by the caller.
79
144
  * Pure / never throws.
80
145
  *
@@ -82,5 +147,6 @@ export declare function stripPastedSecretWrapper(raw: string): string;
82
147
  * classifyPastedSecret('sk-ant-oat01-abc-XYZ') // → 'oauth-token'
83
148
  * classifyPastedSecret('sk-ant-api03-abc-XYZ') // → 'api-key'
84
149
  * classifyPastedSecret('not-a-token') // → 'none'
150
+ * classifyPastedSecret('prefix sk-ant-oat01-x') // → 'none'
85
151
  */
86
152
  export declare function classifyPastedSecret(s: string): 'oauth-token' | 'api-key' | 'none';
@@ -5,9 +5,11 @@
5
5
  * shape `{ claudeOauthToken?: string }` so the Claude OAuth token captured
6
6
  * during `myshell-tools login claude --code` is available across restarts.
7
7
  *
8
- * On startup, `applyStoredCredentials` injects the token into `process.env` so
9
- * that both the provider detection (`claude auth status`) and the spawned
10
- * `claude -p …` child process see it via the inherited environment.
8
+ * Token scoping: instead of injecting the token into the global `process.env`
9
+ * at startup (which would expose it to every child process), callers use
10
+ * `loadClaudeToken()` + `claudeEnv()` to build a scoped env object that is
11
+ * passed only to Claude CLI invocations. Other providers (codex, opencode,
12
+ * npm) never see the token.
11
13
  *
12
14
  * Security: the file is written with mode 0o600 (owner-read-only) on POSIX
13
15
  * systems. The chmod is best-effort — a failure is silently ignored so the
@@ -17,6 +19,40 @@ import { mkdir, readFile, chmod } from 'node:fs/promises';
17
19
  import { homedir } from 'node:os';
18
20
  import { join } from 'node:path';
19
21
  import { atomicWrite } from './atomic.js';
22
+ /**
23
+ * Compute token lifetime status from a stored ISO capture timestamp.
24
+ *
25
+ * Pure — no I/O, no Date.now() inside. Pass `nowMs` for deterministic testing.
26
+ * Never throws.
27
+ *
28
+ * @param capturedAtIso - ISO string stored at save time, or undefined/null.
29
+ * @param nowMs - Current epoch-ms (e.g. Date.now() from the caller).
30
+ * @param lifetimeDays - Total token lifetime in days (default 365).
31
+ * @param warnWithinDays - Warn when this many days or fewer remain (default 14).
32
+ * @returns Status object, or null when capturedAtIso is missing or unparseable.
33
+ */
34
+ export function claudeTokenStatus(capturedAtIso, nowMs, lifetimeDays = 365, warnWithinDays = 14) {
35
+ try {
36
+ if (capturedAtIso === undefined || capturedAtIso === null || capturedAtIso.length === 0) {
37
+ return null;
38
+ }
39
+ const capturedMs = new Date(capturedAtIso).getTime();
40
+ if (!Number.isFinite(capturedMs)) {
41
+ return null;
42
+ }
43
+ const lifetimeMs = lifetimeDays * 24 * 60 * 60 * 1000;
44
+ const expiresMs = capturedMs + lifetimeMs;
45
+ const expiresAt = new Date(expiresMs).toISOString();
46
+ const msLeft = expiresMs - nowMs;
47
+ const daysLeft = Math.floor(msLeft / (24 * 60 * 60 * 1000));
48
+ const expired = daysLeft <= 0;
49
+ const nearExpiry = !expired && daysLeft <= warnWithinDays;
50
+ return { capturedAt: capturedAtIso, expiresAt, daysLeft, expired, nearExpiry };
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
20
56
  // ---------------------------------------------------------------------------
21
57
  // Path helpers (pure)
22
58
  // ---------------------------------------------------------------------------
@@ -44,6 +80,9 @@ function parseCredentials(raw) {
44
80
  if (typeof obj['claudeOauthToken'] === 'string' && obj['claudeOauthToken'].length > 0) {
45
81
  result.claudeOauthToken = obj['claudeOauthToken'];
46
82
  }
83
+ if (typeof obj['claudeTokenCapturedAt'] === 'string' && obj['claudeTokenCapturedAt'].length > 0) {
84
+ result.claudeTokenCapturedAt = obj['claudeTokenCapturedAt'];
85
+ }
47
86
  return result;
48
87
  }
49
88
  catch {
@@ -66,20 +105,73 @@ export async function loadCredentials(homeDir) {
66
105
  return {};
67
106
  }
68
107
  }
108
+ /**
109
+ * Load the stored Claude OAuth token. Returns `null` when no token is stored
110
+ * or when the credentials file is missing/corrupt. Never throws.
111
+ *
112
+ * This is a thin convenience wrapper over `loadCredentials` that returns the
113
+ * token string directly so callers don't need to destructure `Credentials`.
114
+ */
115
+ export async function loadClaudeToken(homeDir) {
116
+ try {
117
+ const creds = await loadCredentials(homeDir);
118
+ return creds.claudeOauthToken ?? null;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Build a child-process environment that injects the Claude OAuth token into
126
+ * ONLY the `CLAUDE_CODE_OAUTH_TOKEN` variable, leaving all other variables
127
+ * from `baseEnv` intact.
128
+ *
129
+ * Rules (pure — no I/O):
130
+ * - Returns `baseEnv` unchanged when `token` is `null` (nothing stored).
131
+ * - Returns `baseEnv` unchanged when `baseEnv.CLAUDE_CODE_OAUTH_TOKEN` is
132
+ * already set — the user's explicitly-exported value always wins.
133
+ * - Otherwise returns `{ ...baseEnv, CLAUDE_CODE_OAUTH_TOKEN: token }`.
134
+ *
135
+ * Pass the result as the `env` option to execa for Claude CLI spawns only.
136
+ * Never pass it to codex/opencode/npm children — they do not need it and
137
+ * should not see the token.
138
+ *
139
+ * Pure / never throws.
140
+ */
141
+ export function claudeEnv(baseEnv, token) {
142
+ if (token === null) {
143
+ return baseEnv;
144
+ }
145
+ if (baseEnv['CLAUDE_CODE_OAUTH_TOKEN'] !== undefined) {
146
+ // User's explicitly-exported env wins — do not overwrite.
147
+ return baseEnv;
148
+ }
149
+ return { ...baseEnv, CLAUDE_CODE_OAUTH_TOKEN: token };
150
+ }
69
151
  /**
70
152
  * Persist the Claude OAuth token atomically to
71
153
  * `~/.myshell-tools/credentials.json` with restrictive permissions (0o600).
154
+ *
155
+ * Records `claudeTokenCapturedAt` (ISO timestamp) so the token's age can be
156
+ * tracked for expiry warnings.
72
157
  */
73
158
  export async function saveClaudeToken(token, homeDir) {
74
159
  const home = homeDir ?? homedir();
75
160
  const dir = getCredentialsDir(home);
76
161
  const path = getCredentialsPath(home);
77
- await mkdir(dir, { recursive: true });
162
+ // Create the directory with restrictive permissions (0o700) so it is never
163
+ // world-readable. recursive:true is a no-op when it already exists.
164
+ await mkdir(dir, { recursive: true, mode: 0o700 });
78
165
  // Load existing credentials so we only replace the token key, preserving others.
79
166
  const existing = await loadCredentials(homeDir);
80
- const updated = { ...existing, claudeOauthToken: token };
81
- // atomicWrite uses rename — ensures no partial writes on crash.
82
- await atomicWrite(path, JSON.stringify(updated, null, 2));
167
+ const updated = {
168
+ ...existing,
169
+ claudeOauthToken: token,
170
+ claudeTokenCapturedAt: new Date().toISOString(),
171
+ };
172
+ // atomicWrite with mode 0o600 guarantees the temp file is never more permissive
173
+ // than the final destination — no world-readable window before the rename.
174
+ await atomicWrite(path, JSON.stringify(updated, null, 2), 0o600);
83
175
  // Best-effort: restrict to owner-read-only. Silently ignored on Windows or
84
176
  // unusual filesystems where chmod is unavailable or unsupported.
85
177
  try {
@@ -89,6 +181,20 @@ export async function saveClaudeToken(token, homeDir) {
89
181
  // Cross-platform best-effort only — never throws
90
182
  }
91
183
  }
184
+ /**
185
+ * Load the ISO timestamp recorded when the Claude OAuth token was last saved.
186
+ * Returns `undefined` when no token has been saved, or when the stored value
187
+ * is missing from an older credential file. Never throws.
188
+ */
189
+ export async function loadClaudeTokenCapturedAt(homeDir) {
190
+ try {
191
+ const creds = await loadCredentials(homeDir);
192
+ return creds.claudeTokenCapturedAt;
193
+ }
194
+ catch {
195
+ return undefined;
196
+ }
197
+ }
92
198
  /**
93
199
  * Remove the stored Claude OAuth token. Writes the file back without the
94
200
  * token key so any future credential fields are preserved.
@@ -200,6 +306,8 @@ export function stripPastedSecretWrapper(raw) {
200
306
  * - `'api-key'` — starts with `sk-ant-api` (a raw Anthropic API key, NOT what we want).
201
307
  * - `'none'` — neither; blank or unrecognised.
202
308
  *
309
+ * Uses `startsWith` semantics: mid-string occurrences of `sk-ant-oat` or
310
+ * `sk-ant-api` do NOT classify as oauth-token or api-key respectively.
203
311
  * Input is pre-normalised (trimmed, quotes stripped) by the caller.
204
312
  * Pure / never throws.
205
313
  *
@@ -207,12 +315,13 @@ export function stripPastedSecretWrapper(raw) {
207
315
  * classifyPastedSecret('sk-ant-oat01-abc-XYZ') // → 'oauth-token'
208
316
  * classifyPastedSecret('sk-ant-api03-abc-XYZ') // → 'api-key'
209
317
  * classifyPastedSecret('not-a-token') // → 'none'
318
+ * classifyPastedSecret('prefix sk-ant-oat01-x') // → 'none'
210
319
  */
211
320
  export function classifyPastedSecret(s) {
212
321
  try {
213
- if (s.includes('sk-ant-oat'))
322
+ if (s.startsWith('sk-ant-oat'))
214
323
  return 'oauth-token';
215
- if (s.includes('sk-ant-api'))
324
+ if (s.startsWith('sk-ant-api'))
216
325
  return 'api-key';
217
326
  return 'none';
218
327
  }
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/infra/credentials.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/infra/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * src/infra/health.ts — Self-health evaluation for myshell-tools.
3
+ *
4
+ * The product goal is "it just works": the user should never run a diagnostic
5
+ * command. Instead the control panel evaluates its own environment health at
6
+ * startup and surfaces a short, actionable warning ONLY when something is
7
+ * actually wrong. No problems → nothing shown (silence == healthy).
8
+ *
9
+ * This module covers the diagnostics that are NOT already visible elsewhere in
10
+ * the UI: Node version, state-directory writability, and pricing-table
11
+ * staleness. Provider install/auth status and Claude-token expiry are already
12
+ * rendered in the header, so they are intentionally not duplicated here.
13
+ *
14
+ * `evaluateHealth` is PURE (no I/O) so it is trivially testable. The one piece
15
+ * that needs I/O — probing whether the state directory is writable — is a
16
+ * separate function the caller runs once at startup and feeds in.
17
+ */
18
+ type HealthSeverity = 'warn' | 'error';
19
+ export interface HealthIssue {
20
+ /** Stable identifier for the issue (useful for tests and de-duplication). */
21
+ readonly id: string;
22
+ readonly severity: HealthSeverity;
23
+ /** One-line, human-readable message that already includes the fix. */
24
+ readonly message: string;
25
+ }
26
+ export interface HealthInputs {
27
+ /** process.version, e.g. "v20.20.0". */
28
+ readonly nodeVersion: string;
29
+ /** Whether the .myshell-tools state directory could be written to. */
30
+ readonly stateWritable: boolean;
31
+ /** Whether the bundled pricing seed is past its staleness window. */
32
+ readonly pricingStale: boolean;
33
+ /** Minimum supported Node major version (defaults to 20). */
34
+ readonly minNodeMajor?: number;
35
+ }
36
+ /**
37
+ * Parse the major version from a Node version string ("v20.20.0" → 20).
38
+ * Returns null when the string can't be parsed (so callers can skip the check
39
+ * rather than emit a bogus warning). Pure; never throws.
40
+ */
41
+ export declare function nodeMajor(version: string): number | null;
42
+ /**
43
+ * Evaluate environment health and return the issues worth surfacing.
44
+ *
45
+ * Pure — takes a snapshot of inputs and returns zero or more issues, most
46
+ * severe first (errors before warnings). An empty array means "all healthy,
47
+ * show nothing".
48
+ */
49
+ export declare function evaluateHealth(inputs: HealthInputs): HealthIssue[];
50
+ /**
51
+ * Probe whether the .myshell-tools state directory under `cwd` is writable.
52
+ *
53
+ * Creates the directory if needed, writes and removes a temp file. Returns true
54
+ * on success, false on any I/O error. Never throws.
55
+ */
56
+ export declare function probeStateWritable(cwd: string): Promise<boolean>;
57
+ export {};
@@ -0,0 +1,96 @@
1
+ /**
2
+ * src/infra/health.ts — Self-health evaluation for myshell-tools.
3
+ *
4
+ * The product goal is "it just works": the user should never run a diagnostic
5
+ * command. Instead the control panel evaluates its own environment health at
6
+ * startup and surfaces a short, actionable warning ONLY when something is
7
+ * actually wrong. No problems → nothing shown (silence == healthy).
8
+ *
9
+ * This module covers the diagnostics that are NOT already visible elsewhere in
10
+ * the UI: Node version, state-directory writability, and pricing-table
11
+ * staleness. Provider install/auth status and Claude-token expiry are already
12
+ * rendered in the header, so they are intentionally not duplicated here.
13
+ *
14
+ * `evaluateHealth` is PURE (no I/O) so it is trivially testable. The one piece
15
+ * that needs I/O — probing whether the state directory is writable — is a
16
+ * separate function the caller runs once at startup and feeds in.
17
+ */
18
+ import { mkdir, writeFile, rm, access } from 'node:fs/promises';
19
+ import { join } from 'node:path';
20
+ // ---------------------------------------------------------------------------
21
+ // Pure helpers
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Parse the major version from a Node version string ("v20.20.0" → 20).
25
+ * Returns null when the string can't be parsed (so callers can skip the check
26
+ * rather than emit a bogus warning). Pure; never throws.
27
+ */
28
+ export function nodeMajor(version) {
29
+ const m = /^v?(\d+)\./.exec(version.trim());
30
+ if (m === null)
31
+ return null;
32
+ const n = parseInt(m[1] ?? '', 10);
33
+ return Number.isFinite(n) ? n : null;
34
+ }
35
+ /**
36
+ * Evaluate environment health and return the issues worth surfacing.
37
+ *
38
+ * Pure — takes a snapshot of inputs and returns zero or more issues, most
39
+ * severe first (errors before warnings). An empty array means "all healthy,
40
+ * show nothing".
41
+ */
42
+ export function evaluateHealth(inputs) {
43
+ const minMajor = inputs.minNodeMajor ?? 20;
44
+ const issues = [];
45
+ // State directory not writable — the most serious: nothing persists.
46
+ if (!inputs.stateWritable) {
47
+ issues.push({
48
+ id: 'state-not-writable',
49
+ severity: 'error',
50
+ message: "Can't write to the .myshell-tools state directory — conversations and " +
51
+ 'cost tracking will not be saved. Check the directory permissions.',
52
+ });
53
+ }
54
+ // Node below the supported floor — the compiled CLI targets Node >= minMajor.
55
+ const major = nodeMajor(inputs.nodeVersion);
56
+ if (major !== null && major < minMajor) {
57
+ issues.push({
58
+ id: 'node-too-old',
59
+ severity: 'warn',
60
+ message: `Node ${inputs.nodeVersion} is below the supported v${minMajor} — upgrade Node if you hit errors.`,
61
+ });
62
+ }
63
+ // Stale pricing seed — cost estimates may drift; updating refreshes them.
64
+ if (inputs.pricingStale) {
65
+ issues.push({
66
+ id: 'pricing-stale',
67
+ severity: 'warn',
68
+ message: 'Cost estimates may be out of date — update to refresh them: npm install -g myshell-tools@latest',
69
+ });
70
+ }
71
+ return issues;
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // I/O probe (run once at startup; result fed into evaluateHealth)
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Probe whether the .myshell-tools state directory under `cwd` is writable.
78
+ *
79
+ * Creates the directory if needed, writes and removes a temp file. Returns true
80
+ * on success, false on any I/O error. Never throws.
81
+ */
82
+ export async function probeStateWritable(cwd) {
83
+ const stateDir = join(cwd, '.myshell-tools');
84
+ const probe = join(stateDir, '.health-probe');
85
+ try {
86
+ await mkdir(stateDir, { recursive: true });
87
+ await writeFile(probe, '');
88
+ await rm(probe, { force: true });
89
+ await access(stateDir);
90
+ return true;
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/infra/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA2BjC,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,qEAAqE;IACrE,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,oBAAoB;YACxB,QAAQ,EAAE,OAAO;YACjB,OAAO,EACL,wEAAwE;gBACxE,mEAAmE;SACtE,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,cAAc;YAClB,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,QAAQ,MAAM,CAAC,WAAW,4BAA4B,QAAQ,oCAAoC;SAC5G,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,eAAe;YACnB,QAAQ,EAAE,MAAM;YAChB,OAAO,EACL,iGAAiG;SACpG,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,kEAAkE;AAClE,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -18,6 +18,11 @@ export interface SpendSummary {
18
18
  readonly totalUsd: number;
19
19
  /** Total number of ledger entries (calls). */
20
20
  readonly calls: number;
21
+ /** Real tokens (input + output) used today (UTC). The transparent primary
22
+ * signal — measured, not an estimate, unlike the USD figures. */
23
+ readonly todayTokens: number;
24
+ /** Real tokens (input + output) used across all time. */
25
+ readonly totalTokens: number;
21
26
  /** Per-provider breakdown keyed by ProviderId string. */
22
27
  readonly byProvider: Record<string, {
23
28
  readonly usd: number;
@@ -64,3 +69,12 @@ export declare function providerHealth(entries: LedgerEntry[]): ProviderHealth[]
64
69
  * @param n - Amount in US dollars (may be 0).
65
70
  */
66
71
  export declare function formatUsd(n: number): string;
72
+ /**
73
+ * Format a token count compactly: 942 → "942", 12_400 → "12.4k", 3_000_000 → "3M".
74
+ *
75
+ * Tokens are REAL measured values (unlike the USD estimates), so this is the
76
+ * primary live signal. Pure; never throws. Negative/NaN inputs clamp to "0".
77
+ *
78
+ * @param n - Token count.
79
+ */
80
+ export declare function formatTokens(n: number): string;
@@ -22,11 +22,16 @@ export function summarizeSpend(entries, nowIso) {
22
22
  const todayDate = nowIso.slice(0, 10); // 'YYYY-MM-DD'
23
23
  let todayUsd = 0;
24
24
  let totalUsd = 0;
25
+ let todayTokens = 0;
26
+ let totalTokens = 0;
25
27
  const byProvider = {};
26
28
  for (const entry of entries) {
27
29
  totalUsd += entry.usd;
30
+ const entryTokens = entry.inputTokens + entry.outputTokens;
31
+ totalTokens += entryTokens;
28
32
  if (entry.timestamp.slice(0, 10) === todayDate) {
29
33
  todayUsd += entry.usd;
34
+ todayTokens += entryTokens;
30
35
  }
31
36
  const existing = byProvider[entry.provider];
32
37
  if (existing !== undefined) {
@@ -41,6 +46,8 @@ export function summarizeSpend(entries, nowIso) {
41
46
  todayUsd,
42
47
  totalUsd,
43
48
  calls: entries.length,
49
+ todayTokens,
50
+ totalTokens,
44
51
  byProvider,
45
52
  };
46
53
  }
@@ -102,4 +109,28 @@ export function providerHealth(entries) {
102
109
  export function formatUsd(n) {
103
110
  return '$' + n.toFixed(4);
104
111
  }
112
+ // ---------------------------------------------------------------------------
113
+ // formatTokens
114
+ // ---------------------------------------------------------------------------
115
+ /**
116
+ * Format a token count compactly: 942 → "942", 12_400 → "12.4k", 3_000_000 → "3M".
117
+ *
118
+ * Tokens are REAL measured values (unlike the USD estimates), so this is the
119
+ * primary live signal. Pure; never throws. Negative/NaN inputs clamp to "0".
120
+ *
121
+ * @param n - Token count.
122
+ */
123
+ export function formatTokens(n) {
124
+ if (!Number.isFinite(n) || n <= 0)
125
+ return '0';
126
+ if (n < 1000)
127
+ return String(Math.round(n));
128
+ if (n < 1_000_000) {
129
+ const k = n / 1000;
130
+ // One decimal, but drop a trailing ".0" (12000 → "12k", 12400 → "12.4k").
131
+ return (Number.isInteger(k) ? String(k) : k.toFixed(1)) + 'k';
132
+ }
133
+ const m = n / 1_000_000;
134
+ return (Number.isInteger(m) ? String(m) : m.toFixed(1)) + 'M';
135
+ }
105
136
  //# sourceMappingURL=insights.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"insights.js","sourceRoot":"","sources":["../../src/infra/insights.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"insights.js","sourceRoot":"","sources":["../../src/infra/insights.ts"],"names":[],"mappings":"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"}