sidekick-shared 0.27.6 → 0.28.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 (269) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +58 -18
  3. package/dist/browser.d.ts +13 -2
  4. package/dist/browser.js +30 -2
  5. package/dist/claudePaths.d.ts +11 -0
  6. package/dist/claudePaths.js +61 -0
  7. package/dist/claudeProfiles.d.ts +2 -9
  8. package/dist/claudeProfiles.js +10 -25
  9. package/dist/codexPaths.d.ts +22 -0
  10. package/dist/codexPaths.js +67 -0
  11. package/dist/codexProfiles.d.ts +1 -4
  12. package/dist/codexProfiles.js +5 -43
  13. package/dist/doctor.js +2 -2
  14. package/dist/esm/accountChangeSignal.js +15 -0
  15. package/dist/esm/accountChanges.js +152 -0
  16. package/dist/esm/accountHealth.js +288 -0
  17. package/dist/esm/accountLaunch.js +145 -0
  18. package/dist/esm/accountLease.js +60 -0
  19. package/dist/esm/accountManager.js +606 -0
  20. package/dist/esm/accountRegistry.js +214 -0
  21. package/dist/esm/accountStatus.js +51 -0
  22. package/dist/esm/accountSwitch.js +74 -0
  23. package/dist/esm/accountSync.js +156 -0
  24. package/dist/esm/accountSyncTypes.js +4 -0
  25. package/dist/esm/accounts.js +1291 -0
  26. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  27. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  28. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  29. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  30. package/dist/esm/aggregation/costProvenance.js +31 -0
  31. package/dist/esm/aggregation/snapshot.js +161 -0
  32. package/dist/esm/aggregation/types.js +5 -0
  33. package/dist/esm/analytics/codeImpact.js +18 -0
  34. package/dist/esm/analytics/compactionLedger.js +37 -0
  35. package/dist/esm/analytics/qualityScore.js +112 -0
  36. package/dist/esm/autoSwitch.js +185 -0
  37. package/dist/esm/browser.js +34 -0
  38. package/dist/esm/claudeCredentialDoctor.js +239 -0
  39. package/dist/esm/claudeCredentialLedger.js +91 -0
  40. package/dist/esm/claudeCredentials.js +111 -0
  41. package/dist/esm/claudeLiveGuard.js +43 -0
  42. package/dist/esm/claudePaths.js +22 -0
  43. package/dist/esm/claudeProfiles.js +142 -0
  44. package/dist/esm/claudeRefreshLock.js +106 -0
  45. package/dist/esm/codexAuth.js +101 -0
  46. package/dist/esm/codexCredentialDoctor.js +265 -0
  47. package/dist/esm/codexCredentialStore.js +164 -0
  48. package/dist/esm/codexKeepAlive.js +112 -0
  49. package/dist/esm/codexPaths.js +172 -0
  50. package/dist/esm/codexProfiles.js +1251 -0
  51. package/dist/esm/codexQuota.js +564 -0
  52. package/dist/esm/codexQuotaWatcher.js +363 -0
  53. package/dist/esm/context/composer.js +71 -0
  54. package/dist/esm/context/sessionContext.js +546 -0
  55. package/dist/esm/credentialIO.js +283 -0
  56. package/dist/esm/credentials.js +49 -0
  57. package/dist/esm/doctor.js +305 -0
  58. package/dist/esm/ensureDefaultAccounts.js +62 -0
  59. package/dist/esm/errorHistory.js +56 -0
  60. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  61. package/dist/esm/extractors/gatherAssets.js +32 -0
  62. package/dist/esm/extractors/sessionAssets.js +176 -0
  63. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  64. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  65. package/dist/esm/extractors/tokenUsage.js +41 -0
  66. package/dist/esm/extractors/toolCall.js +142 -0
  67. package/dist/esm/failingTools.js +65 -0
  68. package/dist/esm/fileHead.js +27 -0
  69. package/dist/esm/formatters/eventHighlighter.js +332 -0
  70. package/dist/esm/formatters/noiseClassifier.js +209 -0
  71. package/dist/esm/formatters/sessionDump.js +328 -0
  72. package/dist/esm/formatters/toolSummary.js +226 -0
  73. package/dist/esm/formatting.js +82 -0
  74. package/dist/esm/handoffUrl.js +23 -0
  75. package/dist/esm/historicalStore.js +305 -0
  76. package/dist/esm/humanPrompt.js +88 -0
  77. package/dist/esm/index.js +213 -0
  78. package/dist/esm/keepAlive.js +189 -0
  79. package/dist/esm/liveCredentialGuard.js +33 -0
  80. package/dist/esm/modelAliases.js +51 -0
  81. package/dist/esm/modelCatalog.js +139 -0
  82. package/dist/esm/modelContext.js +280 -0
  83. package/dist/esm/modelInfo.js +832 -0
  84. package/dist/esm/multiProviderQuotaService.js +450 -0
  85. package/dist/esm/node.js +46 -0
  86. package/dist/esm/observedContextWindows.js +165 -0
  87. package/dist/esm/observedSessionCollector.js +939 -0
  88. package/dist/esm/parsers/changelogParser.js +71 -0
  89. package/dist/esm/parsers/codexHistory.js +89 -0
  90. package/dist/esm/parsers/codexParser.js +906 -0
  91. package/dist/esm/parsers/debugLogParser.js +126 -0
  92. package/dist/esm/parsers/jsonl.js +66 -0
  93. package/dist/esm/parsers/mcpToolName.js +28 -0
  94. package/dist/esm/parsers/openCodeParser.js +625 -0
  95. package/dist/esm/parsers/planExtractor.js +335 -0
  96. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  97. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  98. package/dist/esm/parsers/subagentScanner.js +360 -0
  99. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  100. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  101. package/dist/esm/paths.js +134 -0
  102. package/dist/esm/peakHours.js +194 -0
  103. package/dist/esm/phrases.js +1330 -0
  104. package/dist/esm/pricingCatalog.js +247 -0
  105. package/dist/esm/processDetection.js +200 -0
  106. package/dist/esm/projectMigration.js +59 -0
  107. package/dist/esm/promptHistory.js +1221 -0
  108. package/dist/esm/providerFailure.js +362 -0
  109. package/dist/esm/providerIds.js +6 -0
  110. package/dist/esm/providerQuota.js +1 -0
  111. package/dist/esm/providerServiceStatus.js +145 -0
  112. package/dist/esm/providerServiceStatusTypes.js +1 -0
  113. package/dist/esm/providerStatus.js +117 -0
  114. package/dist/esm/providers/claudeCode.js +575 -0
  115. package/dist/esm/providers/claudeListing.js +121 -0
  116. package/dist/esm/providers/codex.js +1034 -0
  117. package/dist/esm/providers/codexDatabase.js +297 -0
  118. package/dist/esm/providers/codexRollouts.js +215 -0
  119. package/dist/esm/providers/detect.js +185 -0
  120. package/dist/esm/providers/diagnostics.js +83 -0
  121. package/dist/esm/providers/directoryListingCache.js +206 -0
  122. package/dist/esm/providers/factory.js +54 -0
  123. package/dist/esm/providers/openCode.js +2008 -0
  124. package/dist/esm/providers/openCodeDatabase.js +419 -0
  125. package/dist/esm/providers/rolloutWalker.js +252 -0
  126. package/dist/esm/providers/types.js +12 -0
  127. package/dist/esm/quota.js +182 -0
  128. package/dist/esm/quotaHistory.js +364 -0
  129. package/dist/esm/quotaPoller.js +230 -0
  130. package/dist/esm/quotaPresentation.js +97 -0
  131. package/dist/esm/quotaResolver.js +231 -0
  132. package/dist/esm/quotaSnapshots.js +130 -0
  133. package/dist/esm/quotaThresholds.js +85 -0
  134. package/dist/esm/readers/decisions.js +24 -0
  135. package/dist/esm/readers/handoff.js +20 -0
  136. package/dist/esm/readers/helpers.js +27 -0
  137. package/dist/esm/readers/history.js +9 -0
  138. package/dist/esm/readers/notes.js +48 -0
  139. package/dist/esm/readers/plans.js +225 -0
  140. package/dist/esm/readers/tasks.js +19 -0
  141. package/dist/esm/report/htmlHelpers.js +280 -0
  142. package/dist/esm/report/htmlReportGenerator.js +704 -0
  143. package/dist/esm/report/index.js +7 -0
  144. package/dist/esm/report/logo.js +2 -0
  145. package/dist/esm/report/openBrowser.js +19 -0
  146. package/dist/esm/report/sessionReportInputs.js +33 -0
  147. package/dist/esm/report/transcriptParser.js +83 -0
  148. package/dist/esm/report/types.js +4 -0
  149. package/dist/esm/schemas/accountManager.js +204 -0
  150. package/dist/esm/schemas/accountStatus.js +25 -0
  151. package/dist/esm/schemas/assistantTurn.js +74 -0
  152. package/dist/esm/schemas/index.js +25 -0
  153. package/dist/esm/schemas/observedSessionV1.js +77 -0
  154. package/dist/esm/schemas/providerFailure.js +37 -0
  155. package/dist/esm/schemas/quota.js +132 -0
  156. package/dist/esm/schemas/quotaHistory.js +40 -0
  157. package/dist/esm/schemas/sessionEvent.js +216 -0
  158. package/dist/esm/schemas/stateFile.js +75 -0
  159. package/dist/esm/schemas/tokenEstimation.js +7 -0
  160. package/dist/esm/schemas/transcript.js +86 -0
  161. package/dist/esm/schemas/usageNormalization.js +38 -0
  162. package/dist/esm/search/advancedFilter.js +197 -0
  163. package/dist/esm/search/sessionSearch.js +66 -0
  164. package/dist/esm/sessionFingerprint.js +40 -0
  165. package/dist/esm/sessionMonitor.js +198 -0
  166. package/dist/esm/sessionPreviews.js +522 -0
  167. package/dist/esm/sessionPromptHistory.js +254 -0
  168. package/dist/esm/sessionPromptSignals.js +364 -0
  169. package/dist/esm/sessionStats.js +160 -0
  170. package/dist/esm/sessionTokenTotals.js +98 -0
  171. package/dist/esm/sessionTranscripts.js +49 -0
  172. package/dist/esm/stateFile.js +119 -0
  173. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  174. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  175. package/dist/esm/statusline/formatter.js +122 -0
  176. package/dist/esm/statusline/index.js +8 -0
  177. package/dist/esm/terminalSync.js +95 -0
  178. package/dist/esm/testing/credentialGuard.js +197 -0
  179. package/dist/esm/testing/index.js +12 -0
  180. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  181. package/dist/esm/tokenEstimation.js +67 -0
  182. package/dist/esm/tokenSummary.js +113 -0
  183. package/dist/esm/toolCategory.js +82 -0
  184. package/dist/esm/transcript.js +257 -0
  185. package/dist/esm/turns/assistantTurn.js +373 -0
  186. package/dist/esm/types/codex.js +23 -0
  187. package/dist/esm/types/decisionLog.js +5 -0
  188. package/dist/esm/types/historicalData.js +47 -0
  189. package/dist/esm/types/knowledgeNote.js +15 -0
  190. package/dist/esm/types/observedSessionV1.js +257 -0
  191. package/dist/esm/types/opencode.js +12 -0
  192. package/dist/esm/types/plan.js +7 -0
  193. package/dist/esm/types/sessionEvent.js +10 -0
  194. package/dist/esm/types/taskPersistence.js +12 -0
  195. package/dist/esm/usage/billingBlocks.js +148 -0
  196. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  197. package/dist/esm/usage/importSessionHistory.js +84 -0
  198. package/dist/esm/usage/subagentUsage.js +18 -0
  199. package/dist/esm/usage/usageEvents.js +402 -0
  200. package/dist/esm/usage/usageReports.js +202 -0
  201. package/dist/esm/usageNormalization.js +95 -0
  202. package/dist/esm/watchers/eventBridge.js +219 -0
  203. package/dist/esm/watchers/factory.js +63 -0
  204. package/dist/esm/watchers/jsonlTail.js +289 -0
  205. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  206. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  207. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  208. package/dist/esm/watchers/types.js +4 -0
  209. package/dist/esm/workspaceRoots.js +56 -0
  210. package/dist/esm/writers/atomic.js +375 -0
  211. package/dist/esm/writers/decisions.js +51 -0
  212. package/dist/esm/writers/notes.js +46 -0
  213. package/dist/esm/writers/tasks.js +63 -0
  214. package/dist/esm/zaiQuota.js +323 -0
  215. package/dist/esm/zaiQuotaApi.js +271 -0
  216. package/dist/esm/zaiQuotaWatcher.js +265 -0
  217. package/dist/fileHead.d.ts +22 -0
  218. package/dist/fileHead.js +33 -0
  219. package/dist/index.d.ts +16 -7
  220. package/dist/index.js +49 -15
  221. package/dist/node.d.ts +13 -1
  222. package/dist/node.js +43 -2
  223. package/dist/parsers/codexHistory.js +2 -2
  224. package/dist/parsers/codexParser.d.ts +12 -0
  225. package/dist/parsers/codexParser.js +34 -12
  226. package/dist/parsers/debugLogParser.js +2 -2
  227. package/dist/parsers/sessionPathResolver.js +5 -4
  228. package/dist/promptHistory.d.ts +32 -0
  229. package/dist/promptHistory.js +115 -59
  230. package/dist/providerFailure.d.ts +12 -6
  231. package/dist/providerFailure.js +45 -0
  232. package/dist/providers/claudeCode.js +7 -99
  233. package/dist/providers/claudeListing.d.ts +22 -0
  234. package/dist/providers/claudeListing.js +158 -0
  235. package/dist/providers/codex.d.ts +3 -1
  236. package/dist/providers/codex.js +26 -227
  237. package/dist/providers/codexRollouts.d.ts +44 -0
  238. package/dist/providers/codexRollouts.js +261 -0
  239. package/dist/providers/detect.js +4 -4
  240. package/dist/readers/plans.js +3 -3
  241. package/dist/schemas/index.d.ts +2 -0
  242. package/dist/schemas/index.js +7 -2
  243. package/dist/schemas/providerFailure.d.ts +154 -0
  244. package/dist/schemas/providerFailure.js +40 -0
  245. package/dist/sessionPromptHistory.d.ts +12 -7
  246. package/dist/sessionPromptHistory.js +15 -3
  247. package/dist/sessionPromptSignals.d.ts +9 -0
  248. package/dist/sessionPromptSignals.js +13 -0
  249. package/dist/testing/credentialGuard.d.ts +23 -0
  250. package/dist/testing/credentialGuard.js +233 -0
  251. package/dist/testing/index.d.ts +14 -0
  252. package/dist/testing/index.js +22 -0
  253. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  254. package/dist/testing/sessionLogFixtures.js +169 -0
  255. package/dist/tokenEstimation.d.ts +2 -0
  256. package/dist/tokenEstimation.js +21 -11
  257. package/dist/toolCategory.d.ts +18 -0
  258. package/dist/toolCategory.js +86 -0
  259. package/dist/types/codex.d.ts +4 -0
  260. package/dist/usage/usageEvents.d.ts +16 -0
  261. package/dist/usage/usageEvents.js +53 -9
  262. package/dist/watchers/jsonlTail.d.ts +26 -1
  263. package/dist/watchers/jsonlTail.js +136 -28
  264. package/dist/watchers/jsonlWatcher.js +7 -0
  265. package/dist/watchers/providerReaderWatcher.js +1 -0
  266. package/dist/watchers/types.d.ts +6 -0
  267. package/dist/workspaceRoots.d.ts +23 -0
  268. package/dist/workspaceRoots.js +93 -0
  269. package/package.json +22 -2
@@ -12,6 +12,8 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.SIGNAL_TEXT_CHARS = void 0;
14
14
  exports.createSessionRecordExtractor = createSessionRecordExtractor;
15
+ const mcpToolName_1 = require("./parsers/mcpToolName");
16
+ const toolCategory_1 = require("./toolCategory");
15
17
  /** Error text kept on a signal; tool output can be arbitrarily long. */
16
18
  exports.SIGNAL_TEXT_CHARS = 1024;
17
19
  const CLAUDE_REJECTED_PREFIX = "The user doesn't want to proceed with this tool use";
@@ -64,6 +66,13 @@ class RecordExtractor {
64
66
  const signal = { kind, timestamp, afterOrdinal: ordinal };
65
67
  if (detail.tool)
66
68
  signal.tool = detail.tool;
69
+ if (detail.tool || detail.mcp) {
70
+ const mcpServer = detail.mcpServer || (detail.tool ? (0, mcpToolName_1.parseMcpToolName)(detail.tool)?.serverName : undefined);
71
+ signal.toolCategory =
72
+ detail.mcp || !detail.tool ? 'mcp' : (0, toolCategory_1.classifyTool)(detail.tool, { mcpServer });
73
+ if (mcpServer)
74
+ signal.mcpServer = mcpServer;
75
+ }
67
76
  if (detail.fullText) {
68
77
  signal.text = detail.fullText;
69
78
  }
@@ -234,6 +243,8 @@ class CodexRecordExtractor extends RecordExtractor {
234
243
  if (failure !== null) {
235
244
  this.signal('toolError', timestamp, ordinal, {
236
245
  tool: invocation ? stringValue(invocation.tool) : undefined,
246
+ mcp: true,
247
+ mcpServer: invocation ? stringValue(invocation.server) : undefined,
237
248
  text: failure,
238
249
  });
239
250
  }
@@ -273,6 +284,8 @@ class CodexRecordExtractor extends RecordExtractor {
273
284
  if (failed) {
274
285
  this.signal('toolError', timestamp, ordinal, {
275
286
  tool: stringValue(row.tool),
287
+ mcp: true,
288
+ mcpServer: stringValue(row.server),
276
289
  text: row.error !== undefined && row.error !== null
277
290
  ? errorText(row.error)
278
291
  : (mcpFailure(row.result) ?? undefined),
@@ -0,0 +1,23 @@
1
+ export interface CredentialGuard {
2
+ /** The developer's real home, from the passwd entry. */
3
+ readonly realHome: string;
4
+ /** The temp directory `HOME` now points at. */
5
+ readonly testHome: string;
6
+ /** Violations recorded since the last `take()`. */
7
+ readonly violations: string[];
8
+ /** Drain and return the recorded violations. */
9
+ take(): string[];
10
+ /** Throw when anything reached real credentials or homes since the last check. */
11
+ assertNoViolations(): void;
12
+ }
13
+ export interface CredentialGuardOptions {
14
+ /** Binaries no child process may run (default `security`, `claude`, `codex`). */
15
+ blockedBinaries?: string[];
16
+ /** Extra real paths to protect, besides the Claude, Codex, and Sidekick homes. */
17
+ protectedPaths?: string[];
18
+ }
19
+ /**
20
+ * Installs the guard once per process and returns it; later calls return the
21
+ * same guard and ignore their options.
22
+ */
23
+ export declare function installCredentialGuard(options?: CredentialGuardOptions): CredentialGuard;
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.installCredentialGuard = installCredentialGuard;
37
+ /**
38
+ * Credential guard for test runners, published as `sidekick-shared/testing`.
39
+ *
40
+ * No test may reach the real macOS `security` binary, the real `claude` /
41
+ * `codex` CLIs, or the developer's real Claude, Codex, or Sidekick homes: a
42
+ * stray keychain write copies a live refresh token, and Claude Code rotates
43
+ * that token on every refresh, so a second copy is a login waiting to die.
44
+ *
45
+ * Three layers:
46
+ * - `HOME` (and the Windows equivalents) point at a per-process temp dir,
47
+ * removed on exit, and the home overrides (`CLAUDE_CONFIG_DIR`,
48
+ * `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `CODEX_HOME`, `SIDEKICK_CONFIG_DIR`)
49
+ * are unset, so every `os.homedir()`-derived path is isolated structurally;
50
+ * - the real `child_process` module object is patched, which reaches both ESM
51
+ * named imports (after `syncBuiltinESMExports`) and CommonJS code loaded
52
+ * through native `require`; per-file `vi.mock('child_process')` factories
53
+ * replace the module and never get here;
54
+ * - the real `fs` module refuses writes under the real homes and records reads.
55
+ *
56
+ * Production code swallows most errors, so violations are also recorded;
57
+ * call `assertNoViolations()` after each test (see `credentialGuard.setup.ts`
58
+ * for a vitest wiring). Framework-free; the real modules come from
59
+ * `process.getBuiltinModule` (Node 20.16+), or `require` in CommonJS.
60
+ *
61
+ * @module testing/credentialGuard
62
+ */
63
+ const os = __importStar(require("node:os"));
64
+ const path = __importStar(require("node:path"));
65
+ const GUARD_KEY = '__sidekickCredentialGuard';
66
+ const DEFAULT_BLOCKED_BINARIES = ['security', 'claude', 'codex'];
67
+ const CHILD_PROCESS_FUNCTIONS = [
68
+ 'spawn',
69
+ 'spawnSync',
70
+ 'execFile',
71
+ 'execFileSync',
72
+ 'exec',
73
+ 'execSync',
74
+ ];
75
+ const FS_WRITE_FUNCTIONS = [
76
+ 'writeFileSync',
77
+ 'appendFileSync',
78
+ 'mkdirSync',
79
+ 'rmSync',
80
+ 'rmdirSync',
81
+ 'unlinkSync',
82
+ 'renameSync',
83
+ 'copyFileSync',
84
+ 'cpSync',
85
+ 'chmodSync',
86
+ 'symlinkSync',
87
+ ];
88
+ const FS_READ_FUNCTIONS = [
89
+ 'readFileSync',
90
+ 'existsSync',
91
+ 'statSync',
92
+ 'lstatSync',
93
+ 'readdirSync',
94
+ 'openSync',
95
+ 'watch',
96
+ ];
97
+ /**
98
+ * Installs the guard once per process and returns it; later calls return the
99
+ * same guard and ignore their options.
100
+ */
101
+ function installCredentialGuard(options = {}) {
102
+ const existing = globalThis[GUARD_KEY];
103
+ if (existing)
104
+ return existing;
105
+ const childProcess = builtin('node:child_process');
106
+ const realFs = builtin('node:fs');
107
+ const fs = realFs;
108
+ const { syncBuiltinESMExports } = builtin('node:module');
109
+ const blockedBinaries = new Set((options.blockedBinaries ?? DEFAULT_BLOCKED_BINARIES).map((name) => name.toLowerCase()));
110
+ // os.userInfo() reads the passwd entry, so it still names the real home
111
+ // after HOME is redirected below.
112
+ const realHome = os.userInfo().homedir;
113
+ const testHome = realFs.mkdtempSync(path.join(os.tmpdir(), 'sidekick-test-home-'));
114
+ const removeTestHome = realFs.rmSync.bind(realFs);
115
+ process.once('exit', () => {
116
+ try {
117
+ removeTestHome(testHome, { recursive: true, force: true });
118
+ }
119
+ catch {
120
+ /* best effort */
121
+ }
122
+ });
123
+ const state = {
124
+ realHome,
125
+ testHome,
126
+ violations: [],
127
+ take() {
128
+ return state.violations.splice(0, state.violations.length);
129
+ },
130
+ assertNoViolations() {
131
+ const violations = state.take();
132
+ if (violations.length > 0) {
133
+ throw new Error(`Credential guard: this test reached real credentials or homes:\n- ${violations.join('\n- ')}`);
134
+ }
135
+ },
136
+ };
137
+ process.env.HOME = testHome;
138
+ process.env.USERPROFILE = testHome;
139
+ process.env.APPDATA = path.join(testHome, 'AppData', 'Roaming');
140
+ process.env.LOCALAPPDATA = path.join(testHome, 'AppData', 'Local');
141
+ delete process.env.CLAUDE_CONFIG_DIR;
142
+ delete process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
143
+ delete process.env.CODEX_HOME;
144
+ delete process.env.SIDEKICK_CONFIG_DIR;
145
+ const protectedRoots = path.resolve(realHome) === path.resolve(testHome)
146
+ ? []
147
+ : [
148
+ path.join(realHome, '.claude'),
149
+ path.join(realHome, '.claude.json'),
150
+ path.join(realHome, '.codex'),
151
+ path.join(realHome, '.config', 'sidekick'),
152
+ ...(options.protectedPaths ?? []),
153
+ ];
154
+ const isProtected = (target) => {
155
+ let candidate = null;
156
+ if (typeof target === 'string')
157
+ candidate = target;
158
+ else if (target instanceof URL && target.protocol === 'file:')
159
+ candidate = target.pathname;
160
+ if (!candidate)
161
+ return null;
162
+ const resolved = path.resolve(candidate);
163
+ return protectedRoots.some((root) => resolved === root || resolved.startsWith(root + path.sep))
164
+ ? resolved
165
+ : null;
166
+ };
167
+ const binaryName = (command) => {
168
+ if (typeof command !== 'string')
169
+ return '';
170
+ const first = command.trim().split(/\s+/)[0] ?? '';
171
+ return path
172
+ .basename(first.replace(/^["']|["']$/g, ''))
173
+ .replace(/\.(exe|cmd|bat)$/i, '')
174
+ .toLowerCase();
175
+ };
176
+ const record = (message) => {
177
+ state.violations.push(message);
178
+ throw new Error(`[credential guard] ${message}`);
179
+ };
180
+ for (const name of CHILD_PROCESS_FUNCTIONS) {
181
+ const original = childProcess[name];
182
+ if (typeof original !== 'function')
183
+ continue;
184
+ childProcess[name] = function guarded(...args) {
185
+ const binary = binaryName(args[0]);
186
+ if (blockedBinaries.has(binary)) {
187
+ record(`child_process.${name}('${binary}') reached the real binary; mock child_process or inject a runner`);
188
+ }
189
+ return original.apply(this, args);
190
+ };
191
+ }
192
+ for (const name of FS_WRITE_FUNCTIONS) {
193
+ const original = fs[name];
194
+ if (typeof original !== 'function')
195
+ continue;
196
+ fs[name] = function guarded(...args) {
197
+ for (const arg of args.slice(0, 2)) {
198
+ const hit = isProtected(arg);
199
+ if (hit)
200
+ record(`fs.${name}(${hit}) would write inside the real home`);
201
+ }
202
+ return original.apply(this, args);
203
+ };
204
+ }
205
+ for (const name of FS_READ_FUNCTIONS) {
206
+ const original = fs[name];
207
+ if (typeof original !== 'function')
208
+ continue;
209
+ fs[name] = function guarded(...args) {
210
+ const hit = isProtected(args[0]);
211
+ // Reads are recorded rather than thrown so existsSync keeps its
212
+ // never-throws contract; the afterEach below still fails the test.
213
+ if (hit)
214
+ state.violations.push(`fs.${name}(${hit}) read the real home`);
215
+ return original.apply(this, args);
216
+ };
217
+ }
218
+ syncBuiltinESMExports();
219
+ globalThis[GUARD_KEY] = state;
220
+ return state;
221
+ }
222
+ /**
223
+ * The real module object of a Node built-in: `process.getBuiltinModule`
224
+ * (Node 20.16+), or `require` in CommonJS on older Node.
225
+ */
226
+ function builtin(id) {
227
+ if (typeof process.getBuiltinModule === 'function')
228
+ return process.getBuiltinModule(id);
229
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- CommonJS fallback for Node < 20.16
230
+ if (typeof require === 'function')
231
+ return require(id);
232
+ throw new Error('installCredentialGuard() needs Node 20.16+ outside CommonJS.');
233
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Testing entry (`sidekick-shared/testing`): helpers for test suites that
3
+ * exercise sidekick-shared against Claude Code and Codex session data.
4
+ *
5
+ * - `installCredentialGuard()` keeps tests away from the real `security`,
6
+ * `claude`, and `codex` binaries and the real Claude, Codex, and Sidekick
7
+ * homes.
8
+ * - The session-log builders write Claude Code and Codex logs in the shapes
9
+ * the readers parse.
10
+ */
11
+ export { installCredentialGuard } from './credentialGuard';
12
+ export type { CredentialGuard, CredentialGuardOptions } from './credentialGuard';
13
+ export { appendJsonl, claudeFiller, codexFiller, createSessionLogFixtures, ts, writeJsonl, } from './sessionLogFixtures';
14
+ export type { Row, SessionLogFixtureContext } from './sessionLogFixtures';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeJsonl = exports.ts = exports.createSessionLogFixtures = exports.codexFiller = exports.claudeFiller = exports.appendJsonl = exports.installCredentialGuard = void 0;
4
+ /**
5
+ * Testing entry (`sidekick-shared/testing`): helpers for test suites that
6
+ * exercise sidekick-shared against Claude Code and Codex session data.
7
+ *
8
+ * - `installCredentialGuard()` keeps tests away from the real `security`,
9
+ * `claude`, and `codex` binaries and the real Claude, Codex, and Sidekick
10
+ * homes.
11
+ * - The session-log builders write Claude Code and Codex logs in the shapes
12
+ * the readers parse.
13
+ */
14
+ var credentialGuard_1 = require("./credentialGuard");
15
+ Object.defineProperty(exports, "installCredentialGuard", { enumerable: true, get: function () { return credentialGuard_1.installCredentialGuard; } });
16
+ var sessionLogFixtures_1 = require("./sessionLogFixtures");
17
+ Object.defineProperty(exports, "appendJsonl", { enumerable: true, get: function () { return sessionLogFixtures_1.appendJsonl; } });
18
+ Object.defineProperty(exports, "claudeFiller", { enumerable: true, get: function () { return sessionLogFixtures_1.claudeFiller; } });
19
+ Object.defineProperty(exports, "codexFiller", { enumerable: true, get: function () { return sessionLogFixtures_1.codexFiller; } });
20
+ Object.defineProperty(exports, "createSessionLogFixtures", { enumerable: true, get: function () { return sessionLogFixtures_1.createSessionLogFixtures; } });
21
+ Object.defineProperty(exports, "ts", { enumerable: true, get: function () { return sessionLogFixtures_1.ts; } });
22
+ Object.defineProperty(exports, "writeJsonl", { enumerable: true, get: function () { return sessionLogFixtures_1.writeJsonl; } });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Claude Code and Codex session-log builders shared by the prompt-history
3
+ * tests, published through `sidekick-shared/testing`.
4
+ */
5
+ export type Row = Record<string, unknown>;
6
+ export declare function ts(second: number): string;
7
+ export declare function writeJsonl(file: string, rows: Row[]): void;
8
+ export declare function appendJsonl(file: string, rows: Row[]): void;
9
+ /** A Claude Code line with no message: read and ignored, like progress records. */
10
+ export declare function claudeFiller(bytes: number, second: number): Row;
11
+ /** A Codex line that is neither a prompt nor usage. */
12
+ export declare function codexFiller(bytes: number, second: number): Row;
13
+ export interface SessionLogFixtureContext {
14
+ /** The mocked home directory holding `.claude/` and `.codex/`. */
15
+ home: string;
16
+ /** Default working directory recorded on Claude lines. */
17
+ repo: string;
18
+ }
19
+ /** Builders that read the per-test home and repo lazily from `context`. */
20
+ export declare function createSessionLogFixtures(context: () => SessionLogFixtureContext): {
21
+ claudeFile: (workspace: string, sessionId: string) => string;
22
+ claudeUser: (content: unknown, second: number, extra?: Row) => Row;
23
+ claudeAssistant: (id: string, second: number, extra?: Row, message?: Row) => Row;
24
+ codexFile: () => string;
25
+ codexMeta: (cwd: string | undefined, source?: unknown, extra?: Row) => Row;
26
+ codexUser: (texts: string[], second: number) => Row;
27
+ codexTokenCount: (second: number) => Row;
28
+ };
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Code and Codex session-log builders shared by the prompt-history
4
+ * tests, published through `sidekick-shared/testing`.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.ts = ts;
41
+ exports.writeJsonl = writeJsonl;
42
+ exports.appendJsonl = appendJsonl;
43
+ exports.claudeFiller = claudeFiller;
44
+ exports.codexFiller = codexFiller;
45
+ exports.createSessionLogFixtures = createSessionLogFixtures;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
49
+ function ts(second) {
50
+ return new Date(Date.UTC(2026, 8, 1, 10, 0, second)).toISOString();
51
+ }
52
+ function writeJsonl(file, rows) {
53
+ fs.mkdirSync(path.dirname(file), { recursive: true });
54
+ fs.writeFileSync(file, rows.map((row) => JSON.stringify(row)).join('\n') + '\n');
55
+ }
56
+ function appendJsonl(file, rows) {
57
+ fs.appendFileSync(file, rows.map((row) => JSON.stringify(row)).join('\n') + '\n');
58
+ }
59
+ /** A Claude Code line with no message: read and ignored, like progress records. */
60
+ function claudeFiller(bytes, second) {
61
+ return { type: 'system', subtype: 'filler', timestamp: ts(second), content: 'f'.repeat(bytes) };
62
+ }
63
+ /** A Codex line that is neither a prompt nor usage. */
64
+ function codexFiller(bytes, second) {
65
+ return {
66
+ timestamp: ts(second),
67
+ type: 'event_msg',
68
+ payload: { type: 'agent_message', message: 'f'.repeat(bytes) },
69
+ };
70
+ }
71
+ /** Builders that read the per-test home and repo lazily from `context`. */
72
+ function createSessionLogFixtures(context) {
73
+ let codexCounter = 0;
74
+ function claudeFile(workspace, sessionId) {
75
+ return path.join(context().home, '.claude', 'projects', (0, sessionPathResolver_1.encodeWorkspacePath)(workspace), `${sessionId}.jsonl`);
76
+ }
77
+ function claudeUser(content, second, extra = {}) {
78
+ return {
79
+ type: 'user',
80
+ entrypoint: 'cli',
81
+ cwd: context().repo,
82
+ gitBranch: 'main',
83
+ timestamp: ts(second),
84
+ message: { role: 'user', content },
85
+ ...extra,
86
+ };
87
+ }
88
+ /** One Claude Code assistant record; `message` fields are merged over the defaults. */
89
+ function claudeAssistant(id, second, extra = {}, message = {}) {
90
+ return {
91
+ type: 'assistant',
92
+ entrypoint: 'cli',
93
+ cwd: context().repo,
94
+ timestamp: ts(second),
95
+ message: {
96
+ id,
97
+ role: 'assistant',
98
+ model: 'claude-opus-5',
99
+ content: [{ type: 'text', text: 'ok' }],
100
+ stop_reason: 'end_turn',
101
+ usage: {
102
+ input_tokens: 10,
103
+ output_tokens: 20,
104
+ cache_read_input_tokens: 30,
105
+ cache_creation_input_tokens: 40,
106
+ },
107
+ ...message,
108
+ },
109
+ ...extra,
110
+ };
111
+ }
112
+ function codexFile() {
113
+ codexCounter++;
114
+ const id = `019d86b0-b20c-7b02-a3b2-${String(codexCounter).padStart(12, '0')}`;
115
+ return path.join(context().home, '.codex', 'sessions', '2026', '09', '01', `rollout-2026-09-01T10-00-${String(codexCounter).padStart(2, '0')}-${id}.jsonl`);
116
+ }
117
+ function codexMeta(cwd, source = 'cli', extra = {}) {
118
+ return {
119
+ timestamp: ts(0),
120
+ type: 'session_meta',
121
+ payload: {
122
+ id: 'meta',
123
+ timestamp: ts(0),
124
+ ...(cwd !== undefined ? { cwd } : {}),
125
+ originator: 'codex-tui',
126
+ source,
127
+ git: { branch: 'feature/x' },
128
+ ...extra,
129
+ },
130
+ };
131
+ }
132
+ function codexUser(texts, second) {
133
+ return {
134
+ timestamp: ts(second),
135
+ type: 'response_item',
136
+ payload: {
137
+ type: 'message',
138
+ role: 'user',
139
+ content: texts.map((text) => ({ type: 'input_text', text })),
140
+ },
141
+ };
142
+ }
143
+ function codexTokenCount(second) {
144
+ return {
145
+ timestamp: ts(second),
146
+ type: 'event_msg',
147
+ payload: {
148
+ type: 'token_count',
149
+ info: {
150
+ last_token_usage: {
151
+ input_tokens: 100,
152
+ cached_input_tokens: 60,
153
+ output_tokens: 7,
154
+ cache_write_input_tokens: 5,
155
+ },
156
+ },
157
+ },
158
+ };
159
+ }
160
+ return {
161
+ claudeFile,
162
+ claudeUser,
163
+ claudeAssistant,
164
+ codexFile,
165
+ codexMeta,
166
+ codexUser,
167
+ codexTokenCount,
168
+ };
169
+ }
@@ -13,6 +13,8 @@ export interface TokenEstimate {
13
13
  confidence: TokenEstimationConfidence;
14
14
  provenance: string;
15
15
  }
16
+ /** Latin/source-code characters per token in the fallback estimate. */
17
+ export declare const ASCII_CHARS_PER_TOKEN = 3.5;
16
18
  /**
17
19
  * Estimate tokens without bundling a tokenizer.
18
20
  *
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ASCII_CHARS_PER_TOKEN = void 0;
3
4
  exports.estimateTextTokens = estimateTextTokens;
4
5
  exports.estimateSerializedTokens = estimateSerializedTokens;
6
+ /** Latin/source-code characters per token in the fallback estimate. */
7
+ exports.ASCII_CHARS_PER_TOKEN = 3.5;
5
8
  const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
6
9
  const EMOJI = /\p{Extended_Pictographic}/u;
7
10
  /**
@@ -30,19 +33,26 @@ function estimateTextTokens(text, options = {}) {
30
33
  provenance: options.model ?? 'injected-counter',
31
34
  };
32
35
  }
33
- let asciiChars = 0;
36
+ // ASCII is never CJK or pictographic, so the leading ASCII run is counted
37
+ // without the per-code-point regex tests; most source text is all ASCII.
38
+ let firstNonAscii = 0;
39
+ while (firstNonAscii < text.length && text.charCodeAt(firstNonAscii) <= 127)
40
+ firstNonAscii++;
41
+ let asciiChars = firstNonAscii;
34
42
  let conservativeTokens = 0;
35
- for (const codePoint of text) {
36
- if (EMOJI.test(codePoint))
37
- conservativeTokens += 2;
38
- else if (CJK.test(codePoint))
39
- conservativeTokens += 1;
40
- else if ((codePoint.codePointAt(0) ?? 128) <= 127)
41
- asciiChars += codePoint.length;
42
- else
43
- conservativeTokens += 1;
43
+ if (firstNonAscii < text.length) {
44
+ for (const codePoint of text.slice(firstNonAscii)) {
45
+ if (EMOJI.test(codePoint))
46
+ conservativeTokens += 2;
47
+ else if (CJK.test(codePoint))
48
+ conservativeTokens += 1;
49
+ else if ((codePoint.codePointAt(0) ?? 128) <= 127)
50
+ asciiChars += codePoint.length;
51
+ else
52
+ conservativeTokens += 1;
53
+ }
44
54
  }
45
- const count = Math.ceil(asciiChars / 3.5) + conservativeTokens;
55
+ const count = Math.ceil(asciiChars / exports.ASCII_CHARS_PER_TOKEN) + conservativeTokens;
46
56
  return {
47
57
  count: Number.isFinite(count) && count >= 0 ? count : 0,
48
58
  method: 'sidekick-fallback-v1',
@@ -0,0 +1,18 @@
1
+ /**
2
+ * One vocabulary for what a tool call does, across Claude Code, Codex, and
3
+ * OpenCode tool names. Browser-safe: no Node imports.
4
+ *
5
+ * @module toolCategory
6
+ */
7
+ export declare const TOOL_CATEGORIES: readonly ["shell", "file_read", "file_edit", "search", "web", "subagent", "planning", "mcp", "other"];
8
+ export type ToolCategory = (typeof TOOL_CATEGORIES)[number];
9
+ export interface ClassifyToolOptions {
10
+ /** The MCP server the call went to, when the log records it separately (Codex). */
11
+ mcpServer?: string;
12
+ }
13
+ /**
14
+ * Classifies a tool name from any supported provider. `mcp__<server>__<tool>`
15
+ * names and calls with a known `mcpServer` are `mcp`; unknown names are
16
+ * `other`.
17
+ */
18
+ export declare function classifyTool(name: string, options?: ClassifyToolOptions): ToolCategory;