@subvalue/cli 0.1.3 → 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 (37) hide show
  1. package/PRE_RELEASE.md +6 -6
  2. package/README.md +5 -3
  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 +41 -18
  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 +408 -59
  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 +386 -82
  24. package/dist/packages/ui/src/brand.js +42 -27
  25. package/dist/public/app.css +2280 -61
  26. package/dist/public/apps/web/src/landing.js +200 -8
  27. package/dist/public/apps/web/src/main.js +1143 -98
  28. package/dist/public/index.html +24 -1
  29. package/dist/public/landing.css +3235 -80
  30. package/dist/public/landing.html +21 -1
  31. package/dist/public/packages/core/src/calendar.js +41 -18
  32. package/dist/public/packages/core/src/coverage.js +12 -0
  33. package/dist/public/packages/core/src/subscriptions.js +146 -21
  34. package/dist/public/packages/receipt/src/index.js +386 -82
  35. package/dist/public/packages/ui/src/brand.js +42 -27
  36. package/dist/public/privacy.css +197 -9
  37. 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
@@ -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
  }