@ai-studio-3d/vyasa 0.1.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 (237) hide show
  1. package/README.md +69 -0
  2. package/data/common-kit.json +34 -0
  3. package/data/pricing.json +119 -0
  4. package/data/schema/AnalyticsData.json +214 -0
  5. package/data/schema/FoundationData.json +42 -0
  6. package/data/schema/KitData.json +321 -0
  7. package/data/schema/SearchData.json +173 -0
  8. package/data/schema/SessionDetailData.json +231 -0
  9. package/data/schema/SessionsListData.json +115 -0
  10. package/data/schema/WasteData.json +95 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +343 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/billing/index.d.ts +4 -0
  16. package/dist/core/billing/index.d.ts.map +1 -0
  17. package/dist/core/billing/index.js +43 -0
  18. package/dist/core/billing/index.js.map +1 -0
  19. package/dist/core/budget/index.d.ts +30 -0
  20. package/dist/core/budget/index.d.ts.map +1 -0
  21. package/dist/core/budget/index.js +61 -0
  22. package/dist/core/budget/index.js.map +1 -0
  23. package/dist/core/codex/index.d.ts +3 -0
  24. package/dist/core/codex/index.d.ts.map +1 -0
  25. package/dist/core/codex/index.js +212 -0
  26. package/dist/core/codex/index.js.map +1 -0
  27. package/dist/core/cursor/index.d.ts +3 -0
  28. package/dist/core/cursor/index.d.ts.map +1 -0
  29. package/dist/core/cursor/index.js +304 -0
  30. package/dist/core/cursor/index.js.map +1 -0
  31. package/dist/core/cursor/scan.d.ts +28 -0
  32. package/dist/core/cursor/scan.d.ts.map +1 -0
  33. package/dist/core/cursor/scan.js +79 -0
  34. package/dist/core/cursor/scan.js.map +1 -0
  35. package/dist/core/decisions/index.d.ts +21 -0
  36. package/dist/core/decisions/index.d.ts.map +1 -0
  37. package/dist/core/decisions/index.js +133 -0
  38. package/dist/core/decisions/index.js.map +1 -0
  39. package/dist/core/dlp/index.d.ts +7 -0
  40. package/dist/core/dlp/index.d.ts.map +1 -0
  41. package/dist/core/dlp/index.js +115 -0
  42. package/dist/core/dlp/index.js.map +1 -0
  43. package/dist/core/health/index.d.ts +10 -0
  44. package/dist/core/health/index.d.ts.map +1 -0
  45. package/dist/core/health/index.js +21 -0
  46. package/dist/core/health/index.js.map +1 -0
  47. package/dist/core/index/index.d.ts +9 -0
  48. package/dist/core/index/index.d.ts.map +1 -0
  49. package/dist/core/index/index.js +103 -0
  50. package/dist/core/index/index.js.map +1 -0
  51. package/dist/core/index/ingest.d.ts +16 -0
  52. package/dist/core/index/ingest.d.ts.map +1 -0
  53. package/dist/core/index/ingest.js +184 -0
  54. package/dist/core/index/ingest.js.map +1 -0
  55. package/dist/core/index/platforms.d.ts +21 -0
  56. package/dist/core/index/platforms.d.ts.map +1 -0
  57. package/dist/core/index/platforms.js +44 -0
  58. package/dist/core/index/platforms.js.map +1 -0
  59. package/dist/core/index/pricing-map.d.ts +10 -0
  60. package/dist/core/index/pricing-map.d.ts.map +1 -0
  61. package/dist/core/index/pricing-map.js +16 -0
  62. package/dist/core/index/pricing-map.js.map +1 -0
  63. package/dist/core/index/progress.d.ts +26 -0
  64. package/dist/core/index/progress.d.ts.map +1 -0
  65. package/dist/core/index/progress.js +59 -0
  66. package/dist/core/index/progress.js.map +1 -0
  67. package/dist/core/index/scan.d.ts +9 -0
  68. package/dist/core/index/scan.d.ts.map +1 -0
  69. package/dist/core/index/scan.js +42 -0
  70. package/dist/core/index/scan.js.map +1 -0
  71. package/dist/core/index/statements.d.ts +18 -0
  72. package/dist/core/index/statements.d.ts.map +1 -0
  73. package/dist/core/index/statements.js +82 -0
  74. package/dist/core/index/statements.js.map +1 -0
  75. package/dist/core/index/tail.d.ts +42 -0
  76. package/dist/core/index/tail.d.ts.map +1 -0
  77. package/dist/core/index/tail.js +123 -0
  78. package/dist/core/index/tail.js.map +1 -0
  79. package/dist/core/jsonl/index.d.ts +3 -0
  80. package/dist/core/jsonl/index.d.ts.map +1 -0
  81. package/dist/core/jsonl/index.js +106 -0
  82. package/dist/core/jsonl/index.js.map +1 -0
  83. package/dist/core/jsonl-validate/index.d.ts +10 -0
  84. package/dist/core/jsonl-validate/index.d.ts.map +1 -0
  85. package/dist/core/jsonl-validate/index.js +40 -0
  86. package/dist/core/jsonl-validate/index.js.map +1 -0
  87. package/dist/core/kit/detection.d.ts +94 -0
  88. package/dist/core/kit/detection.d.ts.map +1 -0
  89. package/dist/core/kit/detection.js +312 -0
  90. package/dist/core/kit/detection.js.map +1 -0
  91. package/dist/core/kit/index.d.ts +12 -0
  92. package/dist/core/kit/index.d.ts.map +1 -0
  93. package/dist/core/kit/index.js +131 -0
  94. package/dist/core/kit/index.js.map +1 -0
  95. package/dist/core/kit/scan.d.ts +28 -0
  96. package/dist/core/kit/scan.d.ts.map +1 -0
  97. package/dist/core/kit/scan.js +432 -0
  98. package/dist/core/kit/scan.js.map +1 -0
  99. package/dist/core/recap/index.d.ts +18 -0
  100. package/dist/core/recap/index.d.ts.map +1 -0
  101. package/dist/core/recap/index.js +88 -0
  102. package/dist/core/recap/index.js.map +1 -0
  103. package/dist/core/repetition/index.d.ts +5 -0
  104. package/dist/core/repetition/index.d.ts.map +1 -0
  105. package/dist/core/repetition/index.js +0 -0
  106. package/dist/core/repetition/index.js.map +1 -0
  107. package/dist/core/schema/index.d.ts +514 -0
  108. package/dist/core/schema/index.d.ts.map +1 -0
  109. package/dist/core/schema/index.js +2 -0
  110. package/dist/core/schema/index.js.map +1 -0
  111. package/dist/core/schema-watch/index.d.ts +34 -0
  112. package/dist/core/schema-watch/index.d.ts.map +1 -0
  113. package/dist/core/schema-watch/index.js +87 -0
  114. package/dist/core/schema-watch/index.js.map +1 -0
  115. package/dist/core/search/index.d.ts +44 -0
  116. package/dist/core/search/index.d.ts.map +1 -0
  117. package/dist/core/search/index.js +641 -0
  118. package/dist/core/search/index.js.map +1 -0
  119. package/dist/core/settings/index.d.ts +24 -0
  120. package/dist/core/settings/index.d.ts.map +1 -0
  121. package/dist/core/settings/index.js +76 -0
  122. package/dist/core/settings/index.js.map +1 -0
  123. package/dist/core/share/bundle.d.ts +40 -0
  124. package/dist/core/share/bundle.d.ts.map +1 -0
  125. package/dist/core/share/bundle.js +158 -0
  126. package/dist/core/share/bundle.js.map +1 -0
  127. package/dist/core/share/canonical/from-claude.d.ts +13 -0
  128. package/dist/core/share/canonical/from-claude.d.ts.map +1 -0
  129. package/dist/core/share/canonical/from-claude.js +129 -0
  130. package/dist/core/share/canonical/from-claude.js.map +1 -0
  131. package/dist/core/share/canonical/from-codex.d.ts +18 -0
  132. package/dist/core/share/canonical/from-codex.d.ts.map +1 -0
  133. package/dist/core/share/canonical/from-codex.js +183 -0
  134. package/dist/core/share/canonical/from-codex.js.map +1 -0
  135. package/dist/core/share/canonical/to-claude.d.ts +15 -0
  136. package/dist/core/share/canonical/to-claude.d.ts.map +1 -0
  137. package/dist/core/share/canonical/to-claude.js +146 -0
  138. package/dist/core/share/canonical/to-claude.js.map +1 -0
  139. package/dist/core/share/canonical/to-codex.d.ts +21 -0
  140. package/dist/core/share/canonical/to-codex.d.ts.map +1 -0
  141. package/dist/core/share/canonical/to-codex.js +124 -0
  142. package/dist/core/share/canonical/to-codex.js.map +1 -0
  143. package/dist/core/share/canonical/tool-map.d.ts +61 -0
  144. package/dist/core/share/canonical/tool-map.d.ts.map +1 -0
  145. package/dist/core/share/canonical/tool-map.js +299 -0
  146. package/dist/core/share/canonical/tool-map.js.map +1 -0
  147. package/dist/core/share/canonical/types.d.ts +57 -0
  148. package/dist/core/share/canonical/types.d.ts.map +1 -0
  149. package/dist/core/share/canonical/types.js +9 -0
  150. package/dist/core/share/canonical/types.js.map +1 -0
  151. package/dist/core/share/import.d.ts +28 -0
  152. package/dist/core/share/import.d.ts.map +1 -0
  153. package/dist/core/share/import.js +174 -0
  154. package/dist/core/share/import.js.map +1 -0
  155. package/dist/core/share/manifest.d.ts +37 -0
  156. package/dist/core/share/manifest.d.ts.map +1 -0
  157. package/dist/core/share/manifest.js +31 -0
  158. package/dist/core/share/manifest.js.map +1 -0
  159. package/dist/core/share/preview.d.ts +4 -0
  160. package/dist/core/share/preview.d.ts.map +1 -0
  161. package/dist/core/share/preview.js +153 -0
  162. package/dist/core/share/preview.js.map +1 -0
  163. package/dist/core/share/primer.d.ts +19 -0
  164. package/dist/core/share/primer.d.ts.map +1 -0
  165. package/dist/core/share/primer.js +196 -0
  166. package/dist/core/share/primer.js.map +1 -0
  167. package/dist/core/share/resume.d.ts +10 -0
  168. package/dist/core/share/resume.d.ts.map +1 -0
  169. package/dist/core/share/resume.js +58 -0
  170. package/dist/core/share/resume.js.map +1 -0
  171. package/dist/core/share/scrub.d.ts +10 -0
  172. package/dist/core/share/scrub.d.ts.map +1 -0
  173. package/dist/core/share/scrub.js +198 -0
  174. package/dist/core/share/scrub.js.map +1 -0
  175. package/dist/core/share/tar.d.ts +12 -0
  176. package/dist/core/share/tar.d.ts.map +1 -0
  177. package/dist/core/share/tar.js +78 -0
  178. package/dist/core/share/tar.js.map +1 -0
  179. package/dist/core/tags/index.d.ts +48 -0
  180. package/dist/core/tags/index.d.ts.map +1 -0
  181. package/dist/core/tags/index.js +113 -0
  182. package/dist/core/tags/index.js.map +1 -0
  183. package/dist/core/today/index.d.ts +25 -0
  184. package/dist/core/today/index.d.ts.map +1 -0
  185. package/dist/core/today/index.js +42 -0
  186. package/dist/core/today/index.js.map +1 -0
  187. package/dist/core/waste/abandoned.d.ts +12 -0
  188. package/dist/core/waste/abandoned.d.ts.map +1 -0
  189. package/dist/core/waste/abandoned.js +127 -0
  190. package/dist/core/waste/abandoned.js.map +1 -0
  191. package/dist/core/waste/cache-miss.d.ts +9 -0
  192. package/dist/core/waste/cache-miss.d.ts.map +1 -0
  193. package/dist/core/waste/cache-miss.js +84 -0
  194. package/dist/core/waste/cache-miss.js.map +1 -0
  195. package/dist/core/waste/index.d.ts +12 -0
  196. package/dist/core/waste/index.d.ts.map +1 -0
  197. package/dist/core/waste/index.js +45 -0
  198. package/dist/core/waste/index.js.map +1 -0
  199. package/dist/core/waste/wrong-model.d.ts +11 -0
  200. package/dist/core/waste/wrong-model.d.ts.map +1 -0
  201. package/dist/core/waste/wrong-model.js +104 -0
  202. package/dist/core/waste/wrong-model.js.map +1 -0
  203. package/dist/db/index.d.ts +10 -0
  204. package/dist/db/index.d.ts.map +1 -0
  205. package/dist/db/index.js +29 -0
  206. package/dist/db/index.js.map +1 -0
  207. package/dist/db/migrate.d.ts +8 -0
  208. package/dist/db/migrate.d.ts.map +1 -0
  209. package/dist/db/migrate.js +282 -0
  210. package/dist/db/migrate.js.map +1 -0
  211. package/dist/server/events.d.ts +4 -0
  212. package/dist/server/events.d.ts.map +1 -0
  213. package/dist/server/events.js +54 -0
  214. package/dist/server/events.js.map +1 -0
  215. package/dist/server/index.d.ts +7 -0
  216. package/dist/server/index.d.ts.map +1 -0
  217. package/dist/server/index.js +238 -0
  218. package/dist/server/index.js.map +1 -0
  219. package/dist/server/routes/foundation.d.ts +2 -0
  220. package/dist/server/routes/foundation.d.ts.map +1 -0
  221. package/dist/server/routes/foundation.js +2 -0
  222. package/dist/server/routes/foundation.js.map +1 -0
  223. package/dist/server/routes/search.d.ts +2 -0
  224. package/dist/server/routes/search.d.ts.map +1 -0
  225. package/dist/server/routes/search.js +2 -0
  226. package/dist/server/routes/search.js.map +1 -0
  227. package/dist/server/routes/sessions.d.ts +2 -0
  228. package/dist/server/routes/sessions.d.ts.map +1 -0
  229. package/dist/server/routes/sessions.js +2 -0
  230. package/dist/server/routes/sessions.js.map +1 -0
  231. package/dist/server/routes/waste.d.ts +2 -0
  232. package/dist/server/routes/waste.d.ts.map +1 -0
  233. package/dist/server/routes/waste.js +2 -0
  234. package/dist/server/routes/waste.js.map +1 -0
  235. package/dist/web/assets/index-Ba1VvTj0.js +37 -0
  236. package/dist/web/index.html +12 -0
  237. package/package.json +76 -0
package/dist/cli.js ADDED
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'node:url';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join, basename } from 'node:path';
5
+ import net from 'node:net';
6
+ import readline from 'node:readline';
7
+ import { readFileSync, existsSync } from 'node:fs';
8
+ import { getDb, closeDb } from './db/index.js';
9
+ import { buildIndex } from './core/index/index.js';
10
+ import { claudeAdapter, codexAdapter, cursorAdapter } from './core/index/platforms.js';
11
+ import { TailWatcher } from './core/index/tail.js';
12
+ import { computeAllWaste } from './core/waste/index.js';
13
+ import { summarizeSessionWaste } from './core/today/index.js';
14
+ import { capStatus, weeklyBurn, weekKey, fmtTokens } from './core/budget/index.js';
15
+ import { getLastBurnNudge, setLastBurnNudge } from './core/settings/index.js';
16
+ import { scanSession } from './core/dlp/index.js';
17
+ import { buildServer } from './server/index.js';
18
+ import { importBundle, ImportError } from './core/share/import.js';
19
+ import { detectAgentBinary, spawnResume } from './core/share/resume.js';
20
+ import fastifyStatic from '@fastify/static';
21
+ import open from 'open';
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+ const _require = createRequire(import.meta.url);
25
+ const { version } = _require('../package.json');
26
+ // ── Port probing ─────────────────────────────────────────────────────────────
27
+ function findFreePort(start, end) {
28
+ return new Promise((resolve, reject) => {
29
+ const attempt = (port) => {
30
+ if (port > end)
31
+ return reject(new Error(`No free port in ${start}–${end}`));
32
+ const server = net.createServer();
33
+ server.listen(port, '127.0.0.1', () => {
34
+ server.close(() => resolve(port));
35
+ });
36
+ server.on('error', (err) => {
37
+ if (err.code === 'EADDRINUSE')
38
+ attempt(port + 1);
39
+ else
40
+ reject(err);
41
+ });
42
+ };
43
+ attempt(start);
44
+ });
45
+ }
46
+ // Note: small TOCTOU window between releasing the probe socket and Fastify binding — acceptable for a local tool.
47
+ // ── Import (receiver of .vyasa bundles) ──────────────────────────────────────
48
+ function promptYesNo(question) {
49
+ return new Promise((resolve) => {
50
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
51
+ rl.question(question, (ans) => {
52
+ rl.close();
53
+ const a = ans.trim().toLowerCase();
54
+ resolve(a === '' || a === 'y' || a === 'yes');
55
+ });
56
+ });
57
+ }
58
+ async function runImport(bundlePath, opts) {
59
+ let result;
60
+ try {
61
+ // Open the analyzer DB so the bundle's tags + custom name get restored locally.
62
+ const db = getDb();
63
+ result = importBundle(bundlePath, { toPlatform: opts.toPlatform, db });
64
+ }
65
+ catch (err) {
66
+ if (err instanceof ImportError) {
67
+ console.error(`[vyasa] import failed: ${err.message}`);
68
+ }
69
+ else {
70
+ console.error(`[vyasa] unexpected error during import:`, err);
71
+ }
72
+ process.exit(1);
73
+ }
74
+ if (result.translated) {
75
+ console.log(`✓ Translated ${result.sourcePlatform} → ${result.platform}`);
76
+ }
77
+ if (result.alreadyExisted) {
78
+ console.log(`✓ Session already at ${result.placedAt} (skipped, mtime matches)`);
79
+ }
80
+ else {
81
+ console.log(`✓ Placed at ${result.placedAt}`);
82
+ }
83
+ if (result.metaRestored) {
84
+ const t = result.manifest.tags?.length ? ` + ${result.manifest.tags.length} tag(s)` : '';
85
+ console.log(`✓ Restored name/tags${t}`);
86
+ }
87
+ // Optional auto-resume
88
+ if (!opts.noResume) {
89
+ const bin = detectAgentBinary(result.platform);
90
+ if (bin) {
91
+ let go = opts.yes;
92
+ if (!opts.yes) {
93
+ go = await promptYesNo(`→ Detected ${result.platform} (${bin}). Resume now? [Y/n] `);
94
+ }
95
+ if (go) {
96
+ spawnResume(bin, result.platform, result.sessionId);
97
+ return; // unreachable; spawnResume calls process.exit
98
+ }
99
+ }
100
+ }
101
+ // Manual fallback hint
102
+ if (result.platform === 'claude-code') {
103
+ console.log('→ Run `claude` (then `/resume`) to continue — your imported session will be in the list.');
104
+ }
105
+ else {
106
+ console.log(`→ Run \`codex --resume ${result.sessionId}\` to continue.`);
107
+ }
108
+ }
109
+ // ── Main ─────────────────────────────────────────────────────────────────────
110
+ async function main() {
111
+ const args = process.argv.slice(2);
112
+ const rebuild = args.includes('--rebuild');
113
+ const schemaBless = args.includes('--schema-bless');
114
+ const isImport = args[0] === 'import';
115
+ console.log(`vyasa v${version}`);
116
+ // `import <file>`: place a .vyasa bundle, optionally auto-resume, then exit.
117
+ // Stays before getDb() since the import path doesn't need the analyzer DB.
118
+ if (isImport) {
119
+ const bundlePath = args[1];
120
+ if (!bundlePath) {
121
+ console.error('Usage: vyasa import <bundle.vyasa> [--to=<platform>] [--yes] [--no-resume]');
122
+ console.error(' --to=<platform> translate to claude-code or codex (default: keep source)');
123
+ process.exit(1);
124
+ }
125
+ const yes = args.includes('--yes') || args.includes('-y');
126
+ const noResume = args.includes('--no-resume');
127
+ const toFlag = args.find((a) => a.startsWith('--to='));
128
+ let toPlatform;
129
+ if (toFlag) {
130
+ const v = toFlag.slice('--to='.length);
131
+ if (v !== 'claude-code' && v !== 'codex') {
132
+ console.error(`[vyasa] --to must be 'claude-code' or 'codex'; got '${v}'`);
133
+ process.exit(1);
134
+ }
135
+ toPlatform = v;
136
+ }
137
+ await runImport(bundlePath, { yes, noResume, toPlatform });
138
+ return;
139
+ }
140
+ // 1. Open DB
141
+ const db = getDb();
142
+ // --schema-bless: maintainer-only dev path, always exits after blessing
143
+ if (schemaBless) {
144
+ if (rebuild)
145
+ db.prepare('DELETE FROM _file_index').run();
146
+ console.log('Indexing…');
147
+ try {
148
+ const { filesIngested, filesScanned } = await buildIndex(db, [claudeAdapter, codexAdapter, cursorAdapter]);
149
+ console.log(`✓ ${filesIngested} file(s) indexed (${filesScanned} scanned)`);
150
+ }
151
+ catch (err) {
152
+ console.error('[vyasa] indexing failed:', err);
153
+ }
154
+ const goldenPath = join(__dirname, '../data/schema-fingerprints.json');
155
+ if (existsSync(goldenPath)) {
156
+ const { blessGolden } = await import('./core/schema-watch/index.js');
157
+ blessGolden(db, goldenPath);
158
+ console.log('✓ schema-fingerprints.json updated');
159
+ }
160
+ closeDb();
161
+ process.exit(0);
162
+ }
163
+ // --rebuild: force full re-index synchronously before server start
164
+ if (rebuild) {
165
+ db.prepare('DELETE FROM _file_index').run();
166
+ console.log('Indexing…');
167
+ try {
168
+ const { filesIngested, filesScanned } = await buildIndex(db, [claudeAdapter, codexAdapter, cursorAdapter]);
169
+ console.log(`✓ ${filesIngested} file(s) indexed (${filesScanned} scanned)`);
170
+ }
171
+ catch (err) {
172
+ console.error('[vyasa] indexing failed:', err);
173
+ }
174
+ }
175
+ // 2. Find a free port
176
+ let port;
177
+ try {
178
+ port = await findFreePort(3000, 3099);
179
+ }
180
+ catch (err) {
181
+ console.error('[vyasa]', err instanceof Error ? err.message : err);
182
+ process.exit(1);
183
+ }
184
+ // 3. Build server (with tail watcher so SSE route can subscribe to file events)
185
+ // Note: cursor is intentionally NOT tailed. Cursor's state.vscdb isn't an
186
+ // append-only JSONL — chokidar watching it would fire on every Cursor
187
+ // write and trigger expensive full re-scans. New Cursor sessions are
188
+ // picked up on the next vyasa run (or `--rebuild`).
189
+ const tailWatcher = new TailWatcher(db, [
190
+ { rootDir: claudeAdapter.rootDir, platform: 'claude-code' },
191
+ { rootDir: codexAdapter.rootDir, platform: 'codex' },
192
+ ]);
193
+ const app = buildServer(db, tailWatcher);
194
+ // 4. Register static file serving for the SPA
195
+ const webDistDir = join(__dirname, 'web');
196
+ let spaHtml = null;
197
+ try {
198
+ spaHtml = readFileSync(join(webDistDir, 'index.html'), 'utf-8');
199
+ }
200
+ catch { /* dist not built */ }
201
+ // wildcard:true matches asset paths per-request via a glob route, so a rebuilt
202
+ // bundle (new hashed filenames) is served on the next request instead of 404ing
203
+ // until restart. wildcard:false globbed the dist dir once at boot and froze that
204
+ // route list — stale after any rebuild/upgrade. The setNotFoundHandler below still
205
+ // owns the SPA fallback for client routes (non-/api 404s → index.html).
206
+ await app.register(fastifyStatic, {
207
+ root: webDistDir,
208
+ prefix: '/',
209
+ wildcard: true,
210
+ });
211
+ // SPA catch-all: serve index.html for any non-/api 404
212
+ app.setNotFoundHandler((req, reply) => {
213
+ if (req.url.startsWith('/api'))
214
+ return reply.code(404).send({ error: 'Not found' });
215
+ if (spaHtml)
216
+ return reply.type('text/html').send(spaHtml);
217
+ reply.code(404).send('Not found');
218
+ });
219
+ // 5. Start server — browser opens immediately, indexing runs in background
220
+ await app.listen({ port, host: '127.0.0.1' });
221
+ const url = `http://127.0.0.1:${port}`;
222
+ console.log(`Listening on ${url}`);
223
+ // 6. Open browser
224
+ try {
225
+ await open(url);
226
+ }
227
+ catch {
228
+ // Non-fatal — user can open manually
229
+ }
230
+ // 6b. The daily heartbeat (Round-4 sim's "one bet": the daily surface is the PUSH
231
+ // nudge, not a dashboard). When a session goes idle, print a token-led line —
232
+ // IFF something is worth saying: a waste cause above the floor, OR the weekly
233
+ // token cap crossed a 75/100% threshold. Silent on clean sessions. Leads with
234
+ // TOKENS, not dollars (dollars read $0 on ~93% of sessions + are distrusted);
235
+ // keeps the loved jump-to-exact-moment Replay deep-link. Never opens a browser.
236
+ const NUDGE_IDLE_MS = 45_000;
237
+ const NUDGE_FLOOR_CENTS = 5; // $0.05 — waste below this isn't worth a line
238
+ const nudgeTimers = new Map();
239
+ const nudged = new Set();
240
+ // DLP dedup: fire a secret/PII warning once per (session, reason), so a long-lived session
241
+ // that keeps the same key in scope doesn't re-warn on every idle. In-memory like `nudged`.
242
+ const dlpNudged = new Set();
243
+ tailWatcher.on('session-updated', (e) => {
244
+ const existing = nudgeTimers.get(e.sessionId);
245
+ if (existing)
246
+ clearTimeout(existing);
247
+ nudged.delete(e.sessionId); // it changed again → eligible to nudge at the next idle
248
+ const timer = setTimeout(() => {
249
+ nudgeTimers.delete(e.sessionId);
250
+ if (nudged.has(e.sessionId))
251
+ return;
252
+ try {
253
+ computeAllWaste(db); // refresh persisted waste (also keeps the Waste tab live)
254
+ const sum = summarizeSessionWaste(db, e.sessionId);
255
+ if (!sum)
256
+ return;
257
+ const detail = [];
258
+ // Limit-burn: fire once per (week, threshold) when the rolling 7-day cap is crossed.
259
+ const cap = capStatus(db);
260
+ if (cap.capSet && cap.crossed) {
261
+ const key = `${weekKey()}:${cap.crossed}`;
262
+ if (getLastBurnNudge(db) !== key) {
263
+ setLastBurnNudge(db, weekKey(), cap.crossed);
264
+ const left = cap.daysToExhaust != null ? ` — ~${cap.daysToExhaust.toFixed(1)} days left at this rate` : '';
265
+ detail.push(`⚠ ${cap.pct.toFixed(0)}% of your weekly token cap${left}`);
266
+ }
267
+ }
268
+ // Waste: the single biggest cause (each cause ≤ cost, never claims you wasted more than you spent).
269
+ if (sum.top && sum.top.amountCents >= NUDGE_FLOOR_CENTS) {
270
+ const waste = (sum.top.amountCents / 100).toFixed(2);
271
+ const anchor = sum.top.anchorMsgIdx != null ? `#msg-${sum.top.anchorMsgIdx}` : '';
272
+ detail.push(`⚠ $${waste} on ${sum.top.cause} → ${url}/session/${e.sessionId}${anchor}`);
273
+ }
274
+ // DLP: surface a freshly-detected secret/PII in this session. Best-effort + redacted —
275
+ // scanSession never emits the raw value, and we dedupe once per (session, reason) so a
276
+ // standing key doesn't re-warn on every idle. Independent of the waste/burn lines above.
277
+ for (const f of scanSession(db, e.sessionId)) {
278
+ const key = `${f.sessionId}:${f.reason}`;
279
+ if (dlpNudged.has(key))
280
+ continue;
281
+ dlpNudged.add(key);
282
+ detail.push(`⚠ possible ${f.reason} in this session → ${url}/session/${f.sessionId}#msg-${f.msgIdx}`);
283
+ }
284
+ if (detail.length === 0)
285
+ return; // nothing worth saying → stay silent
286
+ nudged.add(e.sessionId);
287
+ const proj = basename(sum.project || e.sessionId);
288
+ const burn = weeklyBurn(db);
289
+ const dollars = sum.costCents >= 1 ? ` ($${(sum.costCents / 100).toFixed(2)})` : '';
290
+ const head = `vyasa · ${proj}: ${fmtTokens(sum.tokens)} tok this session · ${fmtTokens(burn.tokensLast7d)} this week${dollars}`;
291
+ process.stderr.write('\n' + [head, ...detail.map((d) => ' ' + d)].join('\n') + '\n');
292
+ }
293
+ catch {
294
+ // Nudge is best-effort — never crash the server over it.
295
+ }
296
+ }, NUDGE_IDLE_MS);
297
+ nudgeTimers.set(e.sessionId, timer);
298
+ });
299
+ // 7. Kick off background indexing — unawaited, runs while user explores the UI.
300
+ // Start the tail watcher only after the initial bulk ingest finishes so we
301
+ // don't race chokidar against buildIndex.
302
+ let indexingDone = Promise.resolve();
303
+ indexingDone = (async () => {
304
+ try {
305
+ const { filesIngested, filesScanned } = await buildIndex(db, [claudeAdapter, codexAdapter, cursorAdapter]);
306
+ console.log(`✓ ${filesIngested} file(s) indexed (${filesScanned} scanned)`);
307
+ }
308
+ catch (err) {
309
+ console.error('[vyasa] indexing failed:', err);
310
+ }
311
+ finally {
312
+ // Start tailing even if buildIndex threw — users still want live updates
313
+ // for new sessions; `--rebuild` can clean up any partial state later.
314
+ tailWatcher.start();
315
+ }
316
+ })();
317
+ console.log('Press Ctrl+C to stop');
318
+ // 8. Shutdown handlers — bound each teardown step so a hung subsystem
319
+ // (stuck indexing promise, slow watcher.close, hung server) never blocks SIGINT.
320
+ let shuttingDown = false;
321
+ const withTimeout = (p, ms) => Promise.race([p, new Promise((r) => setTimeout(r, ms))]);
322
+ const shutdown = async () => {
323
+ if (shuttingDown)
324
+ return;
325
+ shuttingDown = true;
326
+ for (const t of nudgeTimers.values())
327
+ clearTimeout(t);
328
+ nudgeTimers.clear();
329
+ await withTimeout(indexingDone, 5000);
330
+ await withTimeout(tailWatcher.close(), 3000);
331
+ await withTimeout(app.close(), 3000);
332
+ closeDb();
333
+ console.log('Stopped.');
334
+ process.exit(0);
335
+ };
336
+ process.on('SIGINT', shutdown);
337
+ process.on('SIGTERM', shutdown);
338
+ }
339
+ main().catch((err) => {
340
+ console.error('[vyasa] fatal:', err);
341
+ process.exit(1);
342
+ });
343
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { BillingData, AnalyticsRange } from '../schema/index.js';
3
+ export declare function getBilling(db: Database.Database, range: AnalyticsRange): BillingData;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/billing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAc,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEjF,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,GAAG,WAAW,CAmDpF"}
@@ -0,0 +1,43 @@
1
+ export function getBilling(db, range) {
2
+ // Mirror getAnalytics' cutoff logic.
3
+ const cutoff = range === 'all'
4
+ ? null
5
+ : (() => {
6
+ const days = range === '7d' ? 7 : range === '30d' ? 30 : 90;
7
+ const d = new Date();
8
+ d.setDate(d.getDate() - days);
9
+ return d.toISOString();
10
+ })();
11
+ const cutoffClause = cutoff ? 'WHERE s.started_at >= ?' : '';
12
+ const cutoffParam = cutoff ? [cutoff] : [];
13
+ // Per-tag rollup: a session counts under each of its tags (correct for client billing).
14
+ const rows = db
15
+ .prepare(`SELECT st.tag AS tag,
16
+ COUNT(*) AS sessionCount,
17
+ SUM(s.total_input_tokens + s.total_output_tokens) AS tokens,
18
+ SUM(s.total_cost_cents) AS costCents
19
+ FROM session_tags st
20
+ JOIN sessions s ON s.id = st.session_id
21
+ ${cutoffClause}
22
+ GROUP BY st.tag
23
+ ORDER BY costCents DESC, tokens DESC`)
24
+ .all(...cutoffParam);
25
+ const billingRows = rows.map((r) => ({
26
+ tag: r.tag,
27
+ sessionCount: r.sessionCount,
28
+ tokens: r.tokens ?? 0,
29
+ costCents: r.costCents ?? 0,
30
+ }));
31
+ // Sessions with no tag at all, within the range.
32
+ const untaggedClause = cutoff
33
+ ? 'WHERE s.started_at >= ? AND st.session_id IS NULL'
34
+ : 'WHERE st.session_id IS NULL';
35
+ const untagged = db
36
+ .prepare(`SELECT COUNT(*) AS n
37
+ FROM sessions s
38
+ LEFT JOIN session_tags st ON st.session_id = s.id
39
+ ${untaggedClause}`)
40
+ .get(...cutoffParam);
41
+ return { rangeLabel: range, rows: billingRows, untaggedSessions: untagged.n };
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/billing/index.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,UAAU,CAAC,EAAqB,EAAE,KAAqB;IACrE,qCAAqC;IACrC,MAAM,MAAM,GACV,KAAK,KAAK,KAAK;QACb,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC3D,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAA;YACpB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;YAC7B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAA;QACxB,CAAC,CAAC,EAAE,CAAA;IAEV,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5D,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE1C,wFAAwF;IACxF,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;;;;SAMG,YAAY;;4CAEuB,CACvC;SACA,GAAG,CAAC,GAAG,WAAW,CAAoF,CAAA;IAEzG,MAAM,WAAW,GAAiB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjD,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC;QACrB,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC;KAC5B,CAAC,CAAC,CAAA;IAEH,iDAAiD;IACjD,MAAM,cAAc,GAAG,MAAM;QAC3B,CAAC,CAAC,mDAAmD;QACrD,CAAC,CAAC,6BAA6B,CAAA;IACjC,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CACN;;;SAGG,cAAc,EAAE,CACpB;SACA,GAAG,CAAC,GAAG,WAAW,CAAkB,CAAA;IAEvC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAA;AAC/E,CAAC"}
@@ -0,0 +1,30 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface WeeklyBurn {
3
+ /** Total tokens in the last 7 days. */
4
+ tokensLast7d: number;
5
+ /** Tokens in the last 24h. */
6
+ tokensToday: number;
7
+ /** Average tokens/day over the trailing 7-day window. */
8
+ dailyRate: number;
9
+ /** Straight-line projection of a full 7-day window's burn at the current daily rate (dailyRate × 7). */
10
+ projectedWeekEnd: number;
11
+ }
12
+ export declare function weeklyBurn(db: Database.Database, nowMs?: number): WeeklyBurn;
13
+ export type CapStatus = {
14
+ capSet: false;
15
+ } | {
16
+ capSet: true;
17
+ cap: number;
18
+ used: number;
19
+ pct: number;
20
+ /** Highest threshold the current rolling usage has crossed, or null. */
21
+ crossed: 100 | 75 | null;
22
+ /** Days until the cap is hit at the current daily rate; null if rate is ~0 or already over. */
23
+ daysToExhaust: number | null;
24
+ };
25
+ export declare function capStatus(db: Database.Database, nowMs?: number): CapStatus;
26
+ /** ISO week key (Monday-anchored, UTC) — stable dedup key so the nudge fires once per threshold per week. */
27
+ export declare function weekKey(nowMs?: number): string;
28
+ /** Compact human token count: 4200000 -> "4.2M", 312000 -> "312k". */
29
+ export declare function fmtTokens(n: number): string;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/budget/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAmBrC,MAAM,WAAW,UAAU;IACzB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,wGAAwG;IACxG,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAeD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAE,MAAmB,GAAG,UAAU,CAMxF;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IACE,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,CAAA;IACxB,+FAA+F;IAC/F,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAEL,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAE,MAAmB,GAAG,SAAS,CActF;AAED,6GAA6G;AAC7G,wBAAgB,OAAO,CAAC,KAAK,GAAE,MAAmB,GAAG,MAAM,CAK1D;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3C"}
@@ -0,0 +1,61 @@
1
+ import { getWeeklyTokenCap } from '../settings/index.js';
2
+ /**
3
+ * Weekly token-burn rollup + cap status — the data behind the limit-burn push nudge.
4
+ *
5
+ * Models a ROLLING 7-day window (Anthropic's weekly limit is a 7-day rolling window;
6
+ * Codex telemetry confirms window_minutes=10080). We sum session-level token totals by
7
+ * `started_at`. Claude JSONL carries NO cap ceiling, so the cap is user-entered
8
+ * (core/settings); with no cap set, capStatus reports capSet:false and the nudge stays
9
+ * silent — we never show a confidently-wrong gauge (per the Round-4 sim).
10
+ *
11
+ * "Tokens" here = input + output + cache_creation + cache_read (total throughput against
12
+ * the limit), summed from the sessions table.
13
+ */
14
+ const DAY_MS = 24 * 60 * 60 * 1000;
15
+ const WEEK_MS = 7 * DAY_MS;
16
+ function sumTokensSince(db, sinceMs, nowMs) {
17
+ const row = db.prepare(`
18
+ SELECT COALESCE(SUM(total_input_tokens + total_output_tokens + total_cache_creation + total_cache_read), 0) AS tokens
19
+ FROM sessions
20
+ WHERE started_at IS NOT NULL
21
+ AND started_at >= ?
22
+ AND started_at <= ?
23
+ `).get(new Date(sinceMs).toISOString(), new Date(nowMs).toISOString());
24
+ return row.tokens ?? 0;
25
+ }
26
+ export function weeklyBurn(db, nowMs = Date.now()) {
27
+ const tokensLast7d = sumTokensSince(db, nowMs - WEEK_MS, nowMs);
28
+ const tokensToday = sumTokensSince(db, nowMs - DAY_MS, nowMs);
29
+ const dailyRate = tokensLast7d / 7;
30
+ const projectedWeekEnd = dailyRate * 7;
31
+ return { tokensLast7d, tokensToday, dailyRate, projectedWeekEnd };
32
+ }
33
+ export function capStatus(db, nowMs = Date.now()) {
34
+ const cap = getWeeklyTokenCap(db);
35
+ if (cap == null)
36
+ return { capSet: false };
37
+ const { tokensLast7d: used, dailyRate } = weeklyBurn(db, nowMs);
38
+ const pct = (used / cap) * 100;
39
+ const crossed = pct >= 100 ? 100 : pct >= 75 ? 75 : null;
40
+ let daysToExhaust = null;
41
+ if (used < cap && dailyRate > 0) {
42
+ daysToExhaust = (cap - used) / dailyRate;
43
+ }
44
+ return { capSet: true, cap, used, pct, crossed, daysToExhaust };
45
+ }
46
+ /** ISO week key (Monday-anchored, UTC) — stable dedup key so the nudge fires once per threshold per week. */
47
+ export function weekKey(nowMs = Date.now()) {
48
+ const d = new Date(nowMs);
49
+ const day = (d.getUTCDay() + 6) % 7; // Mon=0..Sun=6
50
+ const monday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - day));
51
+ return monday.toISOString().slice(0, 10);
52
+ }
53
+ /** Compact human token count: 4200000 -> "4.2M", 312000 -> "312k". */
54
+ export function fmtTokens(n) {
55
+ if (n >= 1_000_000)
56
+ return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
57
+ if (n >= 1_000)
58
+ return Math.round(n / 1_000) + 'k';
59
+ return String(n);
60
+ }
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/budget/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ParsedSession } from '../schema/index.js';
2
+ export declare function parseCodex(filePath: string): Promise<ParsedSession>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/codex/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EAOd,MAAM,oBAAoB,CAAA;AAkD3B,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA8MzE"}