@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.
- package/PRE_RELEASE.md +6 -6
- package/README.md +7 -5
- package/dist/PRICING.md +15 -16
- package/dist/packages/cli/src/index.js +109 -20
- package/dist/packages/cli/src/protocol.js +13 -0
- package/dist/packages/cli/src/runtime.js +44 -14
- package/dist/packages/cli/src/server.js +306 -62
- package/dist/packages/core/src/calendar.js +47 -0
- package/dist/packages/core/src/coverage.js +12 -0
- package/dist/packages/core/src/demo.js +61 -9
- package/dist/packages/core/src/metadata.js +52 -9
- package/dist/packages/core/src/pricing/current.js +346 -297
- package/dist/packages/core/src/pricing/index.js +373 -73
- package/dist/packages/core/src/providers/claude/index.js +93 -21
- package/dist/packages/core/src/providers/codex/index.js +213 -46
- package/dist/packages/core/src/scanner.js +226 -46
- package/dist/packages/core/src/security.js +127 -34
- package/dist/packages/core/src/storage.js +215 -47
- package/dist/packages/core/src/subscriptions.js +146 -21
- package/dist/packages/core/src/summary.js +409 -51
- package/dist/packages/core/src/types.js +71 -17
- package/dist/packages/core/src/usage-json.js +166 -47
- package/dist/packages/receipt/src/index.js +387 -80
- package/dist/packages/ui/src/brand.js +48 -3
- package/dist/public/app.css +2280 -56
- package/dist/public/apps/web/src/landing.js +200 -8
- package/dist/public/apps/web/src/main.js +1143 -63
- package/dist/public/index.html +24 -1
- package/dist/public/landing.css +3235 -70
- package/dist/public/landing.html +21 -1
- package/dist/public/logo/wordmark-monochrome.svg +1 -0
- package/dist/public/logo/wordmark.svg +1 -0
- package/dist/public/packages/core/src/calendar.js +47 -0
- package/dist/public/packages/core/src/coverage.js +12 -0
- package/dist/public/packages/core/src/subscriptions.js +146 -21
- package/dist/public/packages/receipt/src/index.js +387 -80
- package/dist/public/packages/ui/src/brand.js +48 -3
- package/dist/public/privacy.css +197 -6
- package/dist/public/privacy.html +1 -1
- 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
|
|
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.
|
|
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
|
|
15
|
-
npx --offline --yes --package ./subvalue-cli
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
| GPT-5.6 Sol
|
|
8
|
-
| GPT-5.6 Sol
|
|
9
|
-
| GPT-5.6 Luna
|
|
10
|
-
| GPT-5.6 Luna
|
|
11
|
-
| GPT-6 Astra
|
|
12
|
-
| GPT-6 Sol
|
|
13
|
-
| GPT-6 Luna
|
|
14
|
-
| GPT-6.1 Sol
|
|
15
|
-
| Claude Fable 5
|
|
16
|
-
| Claude Opus 4.8
|
|
17
|
-
| 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
|
|
19
|
-
| Claude Sonnet 4.5 / 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);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(()=>{
|
|
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
|
+
});
|
|
@@ -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(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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(
|
|
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=
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
}
|