@subvalue/cli 0.1.2 → 0.1.4

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 (40) hide show
  1. package/PRE_RELEASE.md +6 -6
  2. package/README.md +7 -5
  3. package/dist/PRICING.md +15 -16
  4. package/dist/packages/cli/src/index.js +109 -20
  5. package/dist/packages/cli/src/protocol.js +13 -0
  6. package/dist/packages/cli/src/runtime.js +44 -14
  7. package/dist/packages/cli/src/server.js +306 -62
  8. package/dist/packages/core/src/calendar.js +47 -0
  9. package/dist/packages/core/src/coverage.js +12 -0
  10. package/dist/packages/core/src/demo.js +61 -9
  11. package/dist/packages/core/src/metadata.js +52 -9
  12. package/dist/packages/core/src/pricing/current.js +346 -297
  13. package/dist/packages/core/src/pricing/index.js +373 -73
  14. package/dist/packages/core/src/providers/claude/index.js +93 -21
  15. package/dist/packages/core/src/providers/codex/index.js +213 -46
  16. package/dist/packages/core/src/scanner.js +226 -46
  17. package/dist/packages/core/src/security.js +127 -34
  18. package/dist/packages/core/src/storage.js +215 -47
  19. package/dist/packages/core/src/subscriptions.js +146 -21
  20. package/dist/packages/core/src/summary.js +409 -51
  21. package/dist/packages/core/src/types.js +71 -17
  22. package/dist/packages/core/src/usage-json.js +166 -47
  23. package/dist/packages/receipt/src/index.js +387 -80
  24. package/dist/packages/ui/src/brand.js +48 -3
  25. package/dist/public/app.css +2280 -56
  26. package/dist/public/apps/web/src/landing.js +200 -8
  27. package/dist/public/apps/web/src/main.js +1143 -63
  28. package/dist/public/index.html +24 -1
  29. package/dist/public/landing.css +3235 -70
  30. package/dist/public/landing.html +21 -1
  31. package/dist/public/logo/wordmark-monochrome.svg +1 -0
  32. package/dist/public/logo/wordmark.svg +1 -0
  33. package/dist/public/packages/core/src/calendar.js +47 -0
  34. package/dist/public/packages/core/src/coverage.js +12 -0
  35. package/dist/public/packages/core/src/subscriptions.js +146 -21
  36. package/dist/public/packages/receipt/src/index.js +387 -80
  37. package/dist/public/packages/ui/src/brand.js +48 -3
  38. package/dist/public/privacy.css +197 -6
  39. package/dist/public/privacy.html +1 -1
  40. package/package.json +1 -1
package/PRE_RELEASE.md CHANGED
@@ -6,13 +6,13 @@ The normal installation command is `npx @subvalue/cli`. These instructions valid
6
6
 
7
7
  Install Node.js **24.13 or newer** on Windows, macOS or Linux. Check `node --version`.
8
8
 
9
- With access to the private repository, open the latest successful [Validate run](https://github.com/axelmtn/subvalue/actions/workflows/ci.yml). Download its `subvalue-cli-…` artifact and unzip it. Only `subvalue-cli-0.1.2.tgz` should be inside. The artifact expires after 14 days; a new Validate run regenerates it.
9
+ With repository access, open the latest successful [Validate run](https://github.com/axelmtn/subvalue/actions/workflows/ci.yml). Download its `subvalue-cli-…` artifact and unzip it. Only one `subvalue-cli-<version>.tgz` should be inside. The artifact expires after 14 days; a new Validate run regenerates it.
10
10
 
11
- Put the `.tgz` in a new folder outside any repository. Open a terminal in that folder.
11
+ Put the `.tgz` in a new folder outside any repository. Rename this test copy to `subvalue-cli.tgz` and open a terminal in that folder. The commands below work for any package version.
12
12
 
13
13
  ```sh
14
- npx --offline --yes --package ./subvalue-cli-0.1.2.tgz subvalue --dry-run
15
- npx --offline --yes --package ./subvalue-cli-0.1.2.tgz subvalue
14
+ npx --offline --yes --package ./subvalue-cli.tgz subvalue --dry-run
15
+ npx --offline --yes --package ./subvalue-cli.tgz subvalue
16
16
  ```
17
17
 
18
18
  The first command checks detection without reading session contents. The second scans real local usage, opens the browser, and serves only `127.0.0.1`. A different loopback port is selected if 4731 is occupied. No demo data is injected.
@@ -29,7 +29,7 @@ The first command checks detection without reading session contents. The second
29
29
  For scan timings and cache counts:
30
30
 
31
31
  ```sh
32
- npx --offline --yes --package ./subvalue-cli-0.1.2.tgz subvalue --verbose
32
+ npx --offline --yes --package ./subvalue-cli.tgz subvalue --verbose
33
33
  ```
34
34
 
35
35
  Default metadata storage:
@@ -40,7 +40,7 @@ Default metadata storage:
40
40
 
41
41
  Do not send source logs, databases, credentials or conversation content when reporting a problem. Report the OS, Node version, safe error text, scan timing and cache counts. Review screenshots before sharing them.
42
42
 
43
- ## Build from the private checkout
43
+ ## Build from source
44
44
 
45
45
  ```sh
46
46
  npm ci --ignore-scripts --no-audit --no-fund
package/README.md CHANGED
@@ -11,12 +11,14 @@ npx @subvalue/cli --dry-run
11
11
  npx @subvalue/cli
12
12
  ```
13
13
 
14
- The source repository is currently private. For optional installation from a local package, see [Package validation](PRE_RELEASE.md).
14
+ For optional installation from a local package, see [Package validation](PRE_RELEASE.md).
15
15
 
16
16
  ## Run locally
17
17
 
18
18
  Requires **Node.js 24.13 or newer**. Runtime and production build have no external dependencies.
19
19
 
20
+ For development, install dependencies with `npm ci --ignore-scripts`. Run `npm run check` for formatting, TypeScript, ESLint, build and unit/integration tests, then `npm run test:e2e` for browser and receipt-export checks. `npm run format` applies the shared format; CI rejects formatting drift on Windows, Linux and macOS.
21
+
20
22
  ```sh
21
23
  npm run build
22
24
  npm start -- --dry-run
@@ -42,7 +44,7 @@ npm run demo
42
44
 
43
45
  First run shows detected history, then asks how each provider was used before rendering results. Subscription users can select a plan or enter a custom USD amount. API, mixed usage and an explicit “Set up later” choice never imply a subscription.
44
46
 
45
- Month compares the full monthly subscription against usage imported so far. Other ranges use calendar-day proration, including multi-month ranges. Amounts and API prices are USD; no invented exchange rates. Billing declarations do not overwrite historical records or prove their original billing mode. Settings offers bundled, verified ChatGPT/Claude plan presets and a custom amount. Annual presets retain the exact annual price divided by 12, with per-seat plans labeled.
47
+ Month opens a floating calendar-month picker (including historical months). Previous/next arrows move by one calendar month, seven or thirty days for rolling presets, or the selected custom duration. Comparisons use the full declared monthly price for each complete calendar month selected. The current month is marked In progress. 7D, 30D and partial custom ranges show API-equivalent usage without subscription comparisons. Custom ranges covering whole calendar months compare the corresponding number of monthly charges. Amounts and API prices are USD; no invented exchange rates. Billing declarations do not overwrite historical records or prove their original billing mode. Settings offers bundled, verified ChatGPT/Claude plan presets and a custom amount. Annual presets retain the exact annual price divided by 12, with per-seat plans labeled.
46
48
 
47
49
  ## Source access
48
50
 
@@ -66,7 +68,7 @@ Codex excludes inherited ordinal prefixes, uses cumulative deltas, suppresses re
66
68
 
67
69
  The versioned catalog supports mappings with confidence and primary-source provenance, half-open effective intervals, cache reads/writes, Claude cache durations, and long-context bands. Rates and release evidence were checked against [OpenAI pricing](https://developers.openai.com/api/docs/pricing) and [Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing) on **2026-10-05**. Historical versions include the July 30 Luna/Terra reductions and August 21 Sol promotion. Exact public GPT-5.6/6 and Claude identifiers carry mapping provenance; long-context rates use the verified 272k request threshold. New-model launch days use their documented initial API-equivalent rate. Calendar-dated changes between rates remain unknown because their exact UTC switch time is not documented. Sol pricing after the confirmed promotion remains unavailable. See [the pricing evidence ledger](dist/PRICING.md). Internal/unknown model identifiers are never mapped by resemblance.
68
70
 
69
- Overview and receipt display the sum of priceable events; the compact priced percentage identifies partial pricing, and unpriced model identities remain visible in Details. No guessed price or zero is assigned to unknown events. Both views share a display comparison of the known priced amount with the declared prorated subscription: Observed Value / You Saved is their difference, and Value Multiple is their ratio. The normalized summary still marks the full-usage cost and comparison unknown when pricing is incomplete. Partial coverage remains visible; these observed values do not infer a certain outcome or an exact break-even date. API and mixed billing have no subscription comparison. Totals from several subscriptions cannot include API/mixed providers in a subscription comparison.
71
+ Overview and receipt display the sum of priceable events; the compact priced percentage identifies partial pricing, and unpriced model identities remain visible in Details. No guessed price or zero is assigned to unknown events. Both views share a display comparison of the known priced amount with the declared full-month subscription: Observed Value / You Saved is their difference, and Value Multiple is their ratio. The normalized summary still marks the full-usage cost and comparison unknown when pricing is incomplete. Partial coverage remains visible; these observed values do not infer a certain outcome or an exact break-even date. API and mixed billing have no subscription comparison. Totals from several subscriptions cannot include API/mixed providers in a subscription comparison.
70
72
 
71
73
  ## Architecture
72
74
 
@@ -107,7 +109,7 @@ Browser validation uses an isolated headless browser with fixture data. Windows
107
109
 
108
110
  GitHub Actions validates Windows, Linux, and macOS on both Apple Silicon and Intel. Each runner checks types, lint, build, unit/integration tests, responsive browser pages, receipt PNG export, and the locally packed CLI from a temporary directory outside the checkout. The package requires Node.js 24.13 or newer on each platform.
109
111
 
110
- Static landing output is `apps/web/dist`, suitable for a static host including Vercel. It contains no local API server or user data. Its preview is marked as an example. Public GitHub links remain release placeholders while the repository is private.
112
+ Static landing output is `apps/web/dist`, suitable for a static host including Vercel. It contains no local API server or user data. Its preview is marked as an example. The source repository is public at [github.com/axelmtn/subvalue](https://github.com/axelmtn/subvalue).
111
113
 
112
114
  ## Package / release
113
115
 
@@ -118,4 +120,4 @@ npm pack --workspace @subvalue/cli --ignore-scripts --pack-destination artifacts
118
120
 
119
121
  Package contents are allowlisted to `dist/`, README, PRE_RELEASE, PRIVACY, and LICENSE. No lifecycle/postinstall scripts or runtime dependencies. Local databases, exports, attachments and test artifacts are ignored by Git and excluded from npm contents.
120
122
 
121
- The initial npm release is published manually from the audited package. The GitHub repository stays private. npm does not support provenance for private source repositories; no provenance attestation is claimed for this release. The future release workflow remains disabled until trusted publishing is configured and `NPM_PUBLISH_ENABLED=true` is set. It uses a protected environment and OIDC rather than a permanent npm token.
123
+ Manual npm releases use the audited package and do not claim a provenance attestation. The source repository is public. The future release workflow remains disabled until trusted publishing is configured and `NPM_PUBLISH_ENABLED=true` is set. It uses a protected environment and OIDC rather than a permanent npm token.
package/dist/PRICING.md CHANGED
@@ -2,21 +2,21 @@
2
2
 
3
3
  USD per million tokens, standard first-party API equivalent. Exact public model IDs only. A mapping's HIGH confidence describes identity, not completeness of retained history. This catalog is bundled and makes no network requests.
4
4
 
5
- | Model | Effective date | Input / cached input / output | Evidence |
6
- |---|---|---|---|
7
- | GPT-5.6 Sol | 2026-07-09 | 5 / 0.50 / 30 | [Launch](https://openai.com/index/gpt-5-6/) |
8
- | GPT-5.6 Sol | 2026-08-21 | 4 / 0.40 / 20 | [API changelog, Aug 21](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-5.6-sol) |
9
- | GPT-5.6 Luna | 2026-07-09 | 1 / 0.10 / 6 | [Launch](https://openai.com/index/gpt-5-6/) |
10
- | GPT-5.6 Luna | 2026-07-30 | 0.20 / 0.02 / 1.20 | [Price change](https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/), [model](https://developers.openai.com/api/docs/models/gpt-5.6-luna) |
11
- | GPT-6 Astra | 2026-09-03 | 10 / 1 / 50 | [API changelog](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6-astra) |
12
- | GPT-6 Sol | 2026-09-22 | 2 / 0.20 / 10 | [API changelog, Sep 22](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6-sol) |
13
- | GPT-6 Luna | 2026-09-22 | 0.10 / 0.01 / 0.50 | [API changelog, Sep 22](https://developers.openai.com/api/docs/changelog) |
14
- | GPT-6.1 Sol | 2026-09-29 | 2 / 0.10 / 10 | [API changelog, Sep 29](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6.1-sol) |
15
- | Claude Fable 5 | 2026-06-09; restored 2026-07-01 | 10 / 1 / 50 | [Announcement, including suspension](https://www.anthropic.com/news/claude-fable-5-mythos-5), [release notes](https://platform.claude.com/docs/en/release-notes/overview) |
16
- | Claude Opus 4.8 | 2026-05-28 | 5 / 0.50 / 25 | [Announcement](https://www.anthropic.com/news/claude-opus-4-8) |
17
- | Claude Opus 5 | 2026-07-24 | 5 / 0.50 / 25 | [Announcement](https://www.anthropic.com/news/claude-opus-5) |
18
- | Claude Opus 4.7 / 4.6 / 4.5 | 2026-04-16 / 2026-02-05 / 2025-11-24 | 5 / 0.50 / 25 | [4.7](https://www.anthropic.com/news/claude-opus-4-7), [4.6](https://www.anthropic.com/news/claude-opus-4-6), [4.5](https://www.anthropic.com/news/claude-opus-4-5) |
19
- | Claude Sonnet 4.5 / 4.6 | 2025-09-29 / 2026-02-17 | 3 / 0.30 / 15 | [4.5](https://www.anthropic.com/news/claude-sonnet-4-5), [4.6](https://www.anthropic.com/news/claude-sonnet-4-6) |
5
+ | Model | Effective date | Input / cached input / output | Evidence |
6
+ | --------------------------- | ------------------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | GPT-5.6 Sol | 2026-07-09 | 5 / 0.50 / 30 | [Launch](https://openai.com/index/gpt-5-6/) |
8
+ | GPT-5.6 Sol | 2026-08-21 | 4 / 0.40 / 20 | [API changelog, Aug 21](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-5.6-sol) |
9
+ | GPT-5.6 Luna | 2026-07-09 | 1 / 0.10 / 6 | [Launch](https://openai.com/index/gpt-5-6/) |
10
+ | GPT-5.6 Luna | 2026-07-30 | 0.20 / 0.02 / 1.20 | [Price change](https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/), [model](https://developers.openai.com/api/docs/models/gpt-5.6-luna) |
11
+ | GPT-6 Astra | 2026-09-03 | 10 / 1 / 50 | [API changelog](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6-astra) |
12
+ | GPT-6 Sol | 2026-09-22 | 2 / 0.20 / 10 | [API changelog, Sep 22](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6-sol) |
13
+ | GPT-6 Luna | 2026-09-22 | 0.10 / 0.01 / 0.50 | [API changelog, Sep 22](https://developers.openai.com/api/docs/changelog) |
14
+ | GPT-6.1 Sol | 2026-09-29 | 2 / 0.10 / 10 | [API changelog, Sep 29](https://developers.openai.com/api/docs/changelog), [model](https://developers.openai.com/api/docs/models/gpt-6.1-sol) |
15
+ | Claude Fable 5 | 2026-06-09; restored 2026-07-01 | 10 / 1 / 50 | [Announcement, including suspension](https://www.anthropic.com/news/claude-fable-5-mythos-5), [release notes](https://platform.claude.com/docs/en/release-notes/overview) |
16
+ | Claude Opus 4.8 | 2026-05-28 | 5 / 0.50 / 25 | [Announcement](https://www.anthropic.com/news/claude-opus-4-8) |
17
+ | Claude Opus 5 | 2026-07-24 | 5 / 0.50 / 25 | [Announcement](https://www.anthropic.com/news/claude-opus-5) |
18
+ | Claude Opus 4.7 / 4.6 / 4.5 | 2026-04-16 / 2026-02-05 / 2025-11-24 | 5 / 0.50 / 25 | [4.7](https://www.anthropic.com/news/claude-opus-4-7), [4.6](https://www.anthropic.com/news/claude-opus-4-6), [4.5](https://www.anthropic.com/news/claude-opus-4-5) |
19
+ | Claude Sonnet 4.5 / 4.6 | 2025-09-29 / 2026-02-17 | 3 / 0.30 / 15 | [4.5](https://www.anthropic.com/news/claude-sonnet-4-5), [4.6](https://www.anthropic.com/news/claude-sonnet-4-6) |
20
20
 
21
21
  Cache policy: [OpenAI](https://developers.openai.com/api/docs/guides/prompt-caching), [Claude](https://platform.claude.com/docs/en/about-claude/pricing). OpenAI cache writes replace the ordinary input rate, at 1.25× input; cached input and writes are subsets of reported input. Claude input, cache read and cache creation are separate. Claude writes cost 1.25× (5m) or 2× (1h) input. Unknown durations stay unavailable.
22
22
 
@@ -28,7 +28,6 @@ A new model has one documented initial rate: launch-day API-equivalent estimates
28
28
 
29
29
  `codex-auto-review`, unknown aliases, nonstandard tiers/speeds and server tool charges remain unavailable. No inferred subscription billing, regional surcharge, discounts, batch rate or tax. These are standard API-equivalent estimates, not reconstructed invoices.
30
30
 
31
-
32
31
  ## Catalog completeness audit — October 5, 2026
33
32
 
34
33
  The [OpenAI standard text/code pricing table](https://developers.openai.com/api/docs/pricing) was checked in full, including its embedded table data: all 40 rows have matching input, cached-input, cache-write (where published), and output rates. A separate checked fixture verifies every row. Six additional older Codex models are covered from their individual official model pages: GPT-5-Codex, GPT-5.1-Codex, GPT-5.1-Codex-Max, GPT-5.1-Codex-Mini, GPT-5.2-Codex and Codex-Mini-Latest. GPT-5.3-Codex, GPT-5.6-Cyber and Chat-Latest are also explicitly covered. Older/current-only snapshots start October 5; current prices are never silently backdated to unverified historical periods. No prefix-based or invented snapshot aliases.
@@ -1,24 +1,113 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
- import {fileURLToPath} from 'node:url';
4
- import {dataDirectory,openBrowser} from './runtime.js';
5
- import {detectedProviders,sourceRoots} from '../../core/src/security.js';
6
-
7
- import {scan} from '../../core/src/scanner.js';
8
- import {startServer} from './server.js';
9
- const argv=process.argv.slice(2);const allowed=new Set(['--dry-run','--demo','--no-open','--scan-only','--help','--port','--data-dir','--verbose']);
10
- function value(flag ) {const i=argv.indexOf(flag);return i<0?undefined:argv[i+1];}
11
- async function main(){
12
- for(let i=0;i<argv.length;i++){if(!allowed.has(argv[i]))throw new Error('Unknown option. Use --help.');if(['--port','--data-dir'].includes(argv[i])){if(!argv[i+1]||argv[i+1].startsWith('--'))throw new Error('Option requires a value');i++;}}
13
- if(argv.includes('--help')){console.log('SubValue\n\nsubvalue [--dry-run] [--no-open] [--scan-only] [--verbose] [--port 4731] [--data-dir DIRECTORY]\nsubvalue --demo\n\nRequires Node.js 24.13+. No account or API keys.');return;}
14
- if(argv.includes('--dry-run')){const d=detectedProviders();console.log(`Codex ${d.codex?'Detected':'Not detected'}\nClaude Code ${d.claude?'Detected':'Not detected'}\n\nSources:`);for(const r of sourceRoots())console.log(r.safeLabel);return;}
15
- const demo=argv.includes('--demo');const port=Number(value('--port')??4731);if(!Number.isInteger(port)||port<0||port>65535)throw new Error('Invalid port');
16
- let store ;
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dataDirectory, openBrowser } from './runtime.js';
5
+ import { detectedProviders, sourceRoots } from '../../core/src/security.js';
6
+
7
+ import { scan } from '../../core/src/scanner.js';
8
+ import { startServer } from './server.js';
9
+ const argv = process.argv.slice(2);
10
+ const allowed = new Set([
11
+ '--dry-run',
12
+ '--demo',
13
+ '--no-open',
14
+ '--scan-only',
15
+ '--help',
16
+ '--port',
17
+ '--data-dir',
18
+ '--verbose',
19
+ ]);
20
+ function value(flag ) {
21
+ const i = argv.indexOf(flag);
22
+ return i < 0 ? undefined : argv[i + 1];
23
+ }
24
+ async function main() {
25
+ for (let i = 0; i < argv.length; i++) {
26
+ if (!allowed.has(argv[i])) throw new Error('Unknown option. Use --help.');
27
+ if (['--port', '--data-dir'].includes(argv[i])) {
28
+ if (!argv[i + 1] || argv[i + 1].startsWith('--')) throw new Error('Option requires a value');
29
+ i++;
30
+ }
31
+ }
32
+ if (argv.includes('--help')) {
33
+ console.log(
34
+ 'SubValue\n\nsubvalue [--dry-run] [--no-open] [--scan-only] [--verbose] [--port 4731] [--data-dir DIRECTORY]\nsubvalue --demo\n\nRequires Node.js 24.13+. No account or API keys.',
35
+ );
36
+ return;
37
+ }
38
+ if (argv.includes('--dry-run')) {
39
+ const d = detectedProviders();
40
+ console.log(
41
+ `Codex ${d.codex ? 'Detected' : 'Not detected'}\nClaude Code ${d.claude ? 'Detected' : 'Not detected'}\n\nSources:`,
42
+ );
43
+ for (const r of sourceRoots()) console.log(r.safeLabel);
44
+ return;
45
+ }
46
+ const demo = argv.includes('--demo');
47
+ const port = Number(value('--port') ?? 4731);
48
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('Invalid port');
49
+ let store ;
17
50
  console.log('SubValue\n');
18
- if(!demo){const {Store}=await import('../../core/src/storage.js');store=new Store(value('--data-dir')??dataDirectory());let last=0;const verbose=argv.includes('--verbose');const started=Date.now();console.log('Scanning local usage…');const result=await scan(store,{onProgress:p=>{if(Date.now()-last>1000||p.phase==='complete'){last=Date.now();const text=`${p.provider}: ${p.files}/${p.total} files · ${Math.round(p.bytes/1e6)} MB`;if(verbose)console.log(text);else if(process.stdout.isTTY)process.stdout.write(`\r${text.padEnd(65)}`);}}});if(!verbose&&process.stdout.isTTY)process.stdout.write('\r'+' '.repeat(65)+'\r');for(const s of result)console.log(`${s.provider==='codex'?'Codex ':'Claude Code'} ${s.diagnostics.records?'✓':s.detected?'No usage found':'Not detected'}`);if(verbose)console.log(`${((Date.now()-started)/1000).toFixed(1)}s · ${result.reduce((n,s)=>n+s.cached,0)} files cached`);if(argv.includes('--scan-only')){store.close();return;}}
19
- const running=await startServer({store,port,publicDirectory:path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../../../public'),demo}).catch(e=>{store?.close();throw e;});
20
- let closing=false;const close=async()=>{if(closing)return;closing=true;await running.close();store?.close();process.exit(0);};process.on('SIGINT',()=>void close());process.on('SIGTERM',()=>void close());
21
- if(demo)console.log('DEMO — fixture data');
22
- if(!argv.includes('--no-open')){console.log('Opening dashboard…');console.log(running.url);if(!await openBrowser(running.url))console.log(`Open in your browser: ${running.url}`);}else console.log(running.url);
51
+ if (!demo) {
52
+ const { Store } = await import('../../core/src/storage.js');
53
+ store = new Store(value('--data-dir') ?? dataDirectory());
54
+ let last = 0;
55
+ const verbose = argv.includes('--verbose');
56
+ const started = Date.now();
57
+ console.log('Scanning local usage…');
58
+ const result = await scan(store, {
59
+ onProgress: (p) => {
60
+ if (Date.now() - last > 1000 || p.phase === 'complete') {
61
+ last = Date.now();
62
+ const text = `${p.provider}: ${p.files}/${p.total} files · ${Math.round(p.bytes / 1e6)} MB`;
63
+ if (verbose) console.log(text);
64
+ else if (process.stdout.isTTY) process.stdout.write(`\r${text.padEnd(65)}`);
65
+ }
66
+ },
67
+ });
68
+ if (!verbose && process.stdout.isTTY) process.stdout.write('\r' + ' '.repeat(65) + '\r');
69
+ for (const s of result)
70
+ console.log(
71
+ `${s.provider === 'codex' ? 'Codex ' : 'Claude Code'} ${s.diagnostics.records ? '✓' : s.detected ? 'No usage found' : 'Not detected'}`,
72
+ );
73
+ if (verbose)
74
+ console.log(
75
+ `${((Date.now() - started) / 1000).toFixed(1)}s · ${result.reduce((n, s) => n + s.cached, 0)} files cached`,
76
+ );
77
+ if (argv.includes('--scan-only')) {
78
+ store.close();
79
+ return;
80
+ }
81
+ }
82
+ const running = await startServer({
83
+ store,
84
+ port,
85
+ publicDirectory: path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../public'),
86
+ demo,
87
+ }).catch((e) => {
88
+ store?.close();
89
+ throw e;
90
+ });
91
+ let closing = false;
92
+ const close = async () => {
93
+ if (closing) return;
94
+ closing = true;
95
+ await running.close();
96
+ store?.close();
97
+ process.exit(0);
98
+ };
99
+ process.on('SIGINT', () => void close());
100
+ process.on('SIGTERM', () => void close());
101
+ if (demo) console.log('DEMO — fixture data');
102
+ if (!argv.includes('--no-open')) {
103
+ console.log('Opening dashboard…');
104
+ console.log(running.url);
105
+ if (!(await openBrowser(running.url))) console.log(`Open in your browser: ${running.url}`);
106
+ } else console.log(running.url);
23
107
  }
24
- main().catch(()=>{console.error('SubValue could not start. Check the port, data-directory permissions, and Node version. No source files were modified.');process.exitCode=1;});
108
+ main().catch(() => {
109
+ console.error(
110
+ 'SubValue could not start. Check the port, data-directory permissions, and Node version. No source files were modified.',
111
+ );
112
+ process.exitCode = 1;
113
+ });
@@ -0,0 +1,13 @@
1
+
2
+
3
+
4
+ /** Safe bootstrap metadata shared by the local server and bundled dashboard. */
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
@@ -1,22 +1,52 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
- import {spawn} from 'node:child_process';
3
+ import { spawn } from 'node:child_process';
4
4
 
5
- export function dataDirectory(platform=process.platform,home=os.homedir(),env =process.env) {
6
- if(platform==='win32')return path.join(env.LOCALAPPDATA&&path.isAbsolute(env.LOCALAPPDATA)?env.LOCALAPPDATA:path.join(home,'AppData','Local'),'SubValue');
7
- if(platform==='darwin')return path.join(home,'Library','Application Support','SubValue');
8
- return path.join(env.XDG_DATA_HOME&&path.isAbsolute(env.XDG_DATA_HOME)?env.XDG_DATA_HOME:path.join(home,'.local','share'),'subvalue');
5
+ export function dataDirectory(
6
+ platform = process.platform,
7
+ home = os.homedir(),
8
+ env = process.env,
9
+ ) {
10
+ if (platform === 'win32')
11
+ return path.join(
12
+ env.LOCALAPPDATA && path.isAbsolute(env.LOCALAPPDATA)
13
+ ? env.LOCALAPPDATA
14
+ : path.join(home, 'AppData', 'Local'),
15
+ 'SubValue',
16
+ );
17
+ if (platform === 'darwin') return path.join(home, 'Library', 'Application Support', 'SubValue');
18
+ return path.join(
19
+ env.XDG_DATA_HOME && path.isAbsolute(env.XDG_DATA_HOME)
20
+ ? env.XDG_DATA_HOME
21
+ : path.join(home, '.local', 'share'),
22
+ 'subvalue',
23
+ );
9
24
  }
10
25
 
11
- export function openBrowser(url ,launch=spawn,platform=process.platform) {
26
+ export function openBrowser(
27
+ url ,
28
+ launch = spawn,
29
+ platform = process.platform,
30
+ ) {
12
31
  // URLs are generated by our loopback server; no shell interpolation is used.
13
- if(!/^http:\/\/127\.0\.0\.1:\d+$/.test(url))return Promise.resolve(false);
14
- const executable=platform==='win32'?'rundll32':platform==='darwin'?'open':'xdg-open';
15
- const args=platform==='win32'?['url.dll,FileProtocolHandler',url]:[url];
16
- return new Promise(resolve=>{
17
- const child=launch(executable,args,{stdio:'ignore',windowsHide:true});
18
- let finished=false;const done=(ok )=>{if(finished)return;finished=true;clearTimeout(timeout);resolve(ok);};
19
- const timeout=setTimeout(()=>{child.unref();done(true);},3000);
20
- child.once('error',()=>done(false));child.once('exit',code=>done(code===0));
32
+ if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(url)) return Promise.resolve(false);
33
+ const executable =
34
+ platform === 'win32' ? 'rundll32' : platform === 'darwin' ? 'open' : 'xdg-open';
35
+ const args = platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url];
36
+ return new Promise((resolve) => {
37
+ const child = launch(executable, args, { stdio: 'ignore', windowsHide: true });
38
+ let finished = false;
39
+ const done = (ok ) => {
40
+ if (finished) return;
41
+ finished = true;
42
+ clearTimeout(timeout);
43
+ resolve(ok);
44
+ };
45
+ const timeout = setTimeout(() => {
46
+ child.unref();
47
+ done(true);
48
+ }, 3000);
49
+ child.once('error', () => done(false));
50
+ child.once('exit', (code) => done(code === 0));
21
51
  });
22
52
  }