usagemax 0.3.7 → 0.3.8
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/README.md +6 -3
- package/package.json +2 -2
- package/src/cli.js +61 -22
- package/src/sources.js +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ logs. This example only inspects local source coverage:
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
set +x
|
|
56
|
-
bunx usagemax doctor --deep --json | jq '{complete, sources:
|
|
56
|
+
bunx usagemax doctor --deep --json | jq '{complete: .inventoryComplete, sources: .detectedSources}'
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
The JSON shape is intended for local automation; unsupported or unavailable
|
|
@@ -92,11 +92,14 @@ bunx usagemax sync --restart # restart an expired saved upload
|
|
|
92
92
|
bunx usagemax status --remote --json # remote check; secret is never printed
|
|
93
93
|
bunx usagemax doctor --deep --json # parse and audit retained history
|
|
94
94
|
bunx usagemax link UMX-… --no-sync # link without uploading yet
|
|
95
|
+
bunx usagemax --version --json # print CLI and ccusage versions
|
|
96
|
+
bunx usagemax update --json # machine-readable release check
|
|
97
|
+
bunx usagemax unlink --json # safe automation result; no secret output
|
|
95
98
|
```
|
|
96
99
|
|
|
97
100
|
## Sources and coverage
|
|
98
101
|
|
|
99
|
-
UsageMax pins [ccusage v20.0.
|
|
102
|
+
UsageMax pins [ccusage v20.0.23](https://github.com/ccusage/ccusage/releases/tag/v20.0.23)
|
|
100
103
|
and supports its 16 adapters: Amp, Claude Code, Codebuff, Codex, GitHub Copilot
|
|
101
104
|
CLI, Factory Droid, Gemini CLI, Goose, Grok Build, Hermes, Kilo Code, Kimi CLI,
|
|
102
105
|
OpenClaw, OpenCode, Pi, and Qwen Code. Named Pi-format stores are discovered as
|
|
@@ -132,7 +135,7 @@ printf '%s' "$USAGEMAX_COLLECTOR_TOKEN" \
|
|
|
132
135
|
--json
|
|
133
136
|
```
|
|
134
137
|
|
|
135
|
-
The `token status` command is included in CLI `0.3.
|
|
138
|
+
The `token status` command is included in CLI `0.3.8`. If a fresh environment
|
|
136
139
|
still has an older npm tag, run `usagemax update token status` or
|
|
137
140
|
`node packages/cli/src/cli.js token status` from this repository until the new
|
|
138
141
|
package is published.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usagemax",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Link local coding-agent usage to your UsageMax profile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"usagemax",
|
|
@@ -57,6 +57,6 @@
|
|
|
57
57
|
"prepublishOnly": "npm test"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"ccusage": "20.0.
|
|
60
|
+
"ccusage": "20.0.23"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ process.title = "UsageMax";
|
|
|
26
26
|
|
|
27
27
|
const require = createRequire(import.meta.url);
|
|
28
28
|
const executeFile = promisify(execFile);
|
|
29
|
-
const VERSION = "0.3.
|
|
29
|
+
const VERSION = "0.3.8";
|
|
30
30
|
const PUBLIC_API_ORIGIN = "https://usagemax.com/api";
|
|
31
31
|
const DEFAULT_LINK_ENDPOINT = `${PUBLIC_API_ORIGIN}/v1/devices/link`;
|
|
32
32
|
const DEFAULT_STATUS_ENDPOINT = `${PUBLIC_API_ORIGIN}/v1/devices/status`;
|
|
@@ -121,6 +121,7 @@ function help() {
|
|
|
121
121
|
process.stdout.write(" usagemax sync [--full] [--archives] [--restart] [--dry-run] [--explain] [--json]\n");
|
|
122
122
|
process.stdout.write(" Reconcile once; --archives performs one-time recovery\n");
|
|
123
123
|
process.stdout.write(" [--quiet|--no-progress] [--check-updates|--no-update-check] Control progress and the cached release check\n");
|
|
124
|
+
process.stdout.write(" [--json] Emit one JSON object per link/sync phase\n");
|
|
124
125
|
process.stdout.write(" usagemax status Show local link status\n");
|
|
125
126
|
process.stdout.write(" --remote [--json] Verify the stored collector credential without printing it\n");
|
|
126
127
|
process.stdout.write(" [--quiet|--no-progress] Disable interactive progress output\n");
|
|
@@ -132,8 +133,10 @@ function help() {
|
|
|
132
133
|
process.stdout.write(" usagemax doctor [--deep] [--json] [--quiet|--no-progress]\n");
|
|
133
134
|
process.stdout.write(" Check source coverage; --deep parses full history\n");
|
|
134
135
|
process.stdout.write(" usagemax update [command args] Check npm and optionally run the latest CLI\n");
|
|
136
|
+
process.stdout.write(" [--json] Emit machine-readable update status\n");
|
|
135
137
|
process.stdout.write(" usagemax report [...args] Run a local ccusage report\n");
|
|
136
138
|
process.stdout.write(" usagemax unlink [--revoke] Remove locally; --revoke also disables uploads\n");
|
|
139
|
+
process.stdout.write(" [--json] Emit machine-readable unlink status\n");
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
function ccusageCliPath() {
|
|
@@ -182,6 +185,7 @@ function warnVersion(body) {
|
|
|
182
185
|
async function link(args) {
|
|
183
186
|
const code = normalizeLinkCode(args[0]);
|
|
184
187
|
if (!code) throw new Error("Paste the one-use UMX link code shown at usagemax.com/account.");
|
|
188
|
+
const json = args.includes("--json");
|
|
185
189
|
const configuredEndpoint = process.env.USAGEMAX_LINK_ENDPOINT || DEFAULT_LINK_ENDPOINT;
|
|
186
190
|
const endpoint = validHttpsUrl(configuredEndpoint, { allowLocalhost: true });
|
|
187
191
|
if (!endpoint) throw new Error("USAGEMAX_LINK_ENDPOINT must use HTTPS, except for localhost development.");
|
|
@@ -223,13 +227,17 @@ async function link(args) {
|
|
|
223
227
|
};
|
|
224
228
|
await writeConfig(config);
|
|
225
229
|
warnVersion(body);
|
|
226
|
-
|
|
230
|
+
if (json) {
|
|
231
|
+
process.stdout.write(`${JSON.stringify({ linked: true, version: VERSION, deviceName: savedName, profileHandle: config.profileHandle || null, profileUrl: config.profileUrl, deviceIdConfigured: true, sync: args.includes("--no-sync") ? "skipped" : "started" })}\n`);
|
|
232
|
+
} else {
|
|
233
|
+
process.stdout.write(`Linked ${savedName} to ${config.profileHandle ? `@${config.profileHandle}` : "UsageMax"}.\n`);
|
|
234
|
+
}
|
|
227
235
|
if (args.includes("--no-sync")) {
|
|
228
|
-
process.stdout.write("No usage was uploaded. Run `bunx usagemax sync --full` when you are ready.\n");
|
|
236
|
+
if (!json) process.stdout.write("No usage was uploaded. Run `bunx usagemax sync --full` when you are ready.\n");
|
|
229
237
|
return;
|
|
230
238
|
}
|
|
231
|
-
process.stdout.write("Running the first one-shot sync…\n");
|
|
232
|
-
await sync(["--full"], config);
|
|
239
|
+
if (!json) process.stdout.write("Running the first one-shot sync…\n");
|
|
240
|
+
await sync(["--full", ...(json ? ["--json"] : [])], config);
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
async function sync(args, suppliedConfig) {
|
|
@@ -254,6 +262,7 @@ async function sync(args, suppliedConfig) {
|
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
async function syncPrepared(args, suppliedConfig, recovery, progress = createProgress({ noProgress: true })) {
|
|
265
|
+
const startedAt = Date.now();
|
|
257
266
|
const config = suppliedConfig || await readConfig();
|
|
258
267
|
if (!config) throw new Error("This computer is not linked. Open https://usagemax.com/account and create a link code.");
|
|
259
268
|
config.deviceId = await stableInstallationId(configDirectory(), config.deviceId);
|
|
@@ -269,7 +278,7 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
269
278
|
if (config.pendingSync) {
|
|
270
279
|
progress.update("Resuming the saved upload checkpoint…");
|
|
271
280
|
if (dryRun) {
|
|
272
|
-
const result = { ...config.pendingSync.result, dryRun: true, pendingRunId: config.pendingSync.runId };
|
|
281
|
+
const result = { ...config.pendingSync.result, dryRun: true, pendingRunId: config.pendingSync.runId, cliVersion: VERSION, ccusageVersion: CCUSAGE_VERSION, durationMs: Date.now() - startedAt };
|
|
273
282
|
process.stdout.write(json ? `${JSON.stringify(result)}\n` : `Dry run: saved run ${config.pendingSync.runId} awaits resume; no upload.\n`);
|
|
274
283
|
progress.succeed("Dry run complete; saved upload remains untouched.");
|
|
275
284
|
return result;
|
|
@@ -280,9 +289,10 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
280
289
|
warn: warnVersion,
|
|
281
290
|
onProgress: ({ index, total, operation, acknowledged }) => progress.update(`${acknowledged ? "Uploaded" : "Uploading"} ${index}/${total} · ${operation}`),
|
|
282
291
|
});
|
|
292
|
+
const summary = { ...result, cliVersion: VERSION, ccusageVersion: CCUSAGE_VERSION, durationMs: Date.now() - startedAt };
|
|
283
293
|
progress.succeed("Resumed and completed the saved sync.");
|
|
284
|
-
process.stdout.write(json ? `${JSON.stringify(
|
|
285
|
-
return
|
|
294
|
+
process.stdout.write(json ? `${JSON.stringify(summary)}\n` : "Resumed and completed the saved sync. Run sync again to scan newer local changes.\n");
|
|
295
|
+
return summary;
|
|
286
296
|
}
|
|
287
297
|
const inventory = await sourceInventory({ env: recovery.env, home: ccusageHome(recovery.env) });
|
|
288
298
|
progress.update(`Found ${inventory.sources.length} source${inventory.sources.length === 1 ? "" : "s"} and ${inventory.files}${inventory.truncated ? "+" : ""} local data file${inventory.files === 1 ? "" : "s"}.`);
|
|
@@ -292,9 +302,12 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
292
302
|
today, now: Date.now(), inventoryVersion: SOURCE_INVENTORY_VERSION, requestedFull, requestedArchives,
|
|
293
303
|
});
|
|
294
304
|
if (skip) {
|
|
295
|
-
const result = { accepted: 0, changedRows: 0, sessions: 0, sources: inventory.sources, corrections: 0, scanned: false, full: false, coverage: config.lastCoverage || "partial" };
|
|
305
|
+
const result = { accepted: 0, changedRows: 0, sessions: 0, sources: inventory.sources, corrections: 0, scanned: false, full: false, coverage: config.lastCoverage || "partial", skipReason: "inventory_unchanged", cliVersion: VERSION, ccusageVersion: CCUSAGE_VERSION, durationMs: Date.now() - startedAt };
|
|
296
306
|
if (json) process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
297
|
-
else
|
|
307
|
+
else {
|
|
308
|
+
process.stdout.write("Already up to date. Local usage files have not changed; no logs were parsed or uploaded.\n");
|
|
309
|
+
if (explain) process.stdout.write(`Skipped because the source inventory fingerprint is unchanged; coverage remains ${result.coverage}.\n`);
|
|
310
|
+
}
|
|
298
311
|
progress.succeed("No local changes; upload skipped.");
|
|
299
312
|
return;
|
|
300
313
|
}
|
|
@@ -334,6 +347,8 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
334
347
|
coverage: authoritative ? "complete" : "partial",
|
|
335
348
|
coverageReason: "Parser does not certify complete source/day coverage; decreases and deletions are protected.",
|
|
336
349
|
range: { from: days[0], to: days.at(-1) },
|
|
350
|
+
cliVersion: VERSION,
|
|
351
|
+
ccusageVersion: CCUSAGE_VERSION,
|
|
337
352
|
};
|
|
338
353
|
progress.update(`Prepared ${partitions.length} usage chunk${partitions.length === 1 ? "" : "s"} and ${sessions.length} session identifier${sessions.length === 1 ? "" : "s"}.`);
|
|
339
354
|
if (requestedArchives) {
|
|
@@ -341,6 +356,7 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
341
356
|
result.unsupportedArchives = recovery.unsupported;
|
|
342
357
|
}
|
|
343
358
|
if (dryRun) {
|
|
359
|
+
result.durationMs = Date.now() - startedAt;
|
|
344
360
|
if (json) process.stdout.write(`${JSON.stringify({ ...result, dryRun: true })}\n`);
|
|
345
361
|
else {
|
|
346
362
|
process.stdout.write(`Dry run: ${partitions.length} partition(s), ${result.changedRows} changed row(s), ${sessions.length} private session identifiers, no upload.\n`);
|
|
@@ -389,6 +405,7 @@ async function syncPrepared(args, suppliedConfig, recovery, progress = createPro
|
|
|
389
405
|
warn: warnVersion,
|
|
390
406
|
onProgress: ({ index, total, operation, acknowledged }) => progress.update(`${acknowledged ? "Uploaded" : "Uploading"} ${index}/${total} · ${operation}`),
|
|
391
407
|
});
|
|
408
|
+
result.durationMs = Date.now() - startedAt;
|
|
392
409
|
progress.succeed(`Sync complete · ${partitions.length} chunk${partitions.length === 1 ? "" : "s"}, ${sessions.length} session identifier${sessions.length === 1 ? "" : "s"}.`);
|
|
393
410
|
if (json) process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
394
411
|
else {
|
|
@@ -431,7 +448,7 @@ async function status(args = []) {
|
|
|
431
448
|
const progress = createProgress({ json, quiet: args.includes("--quiet"), noProgress: args.includes("--no-progress") });
|
|
432
449
|
if (!config) {
|
|
433
450
|
if (json) {
|
|
434
|
-
process.stdout.write(`${JSON.stringify({ linked: false, remote: remote ? { status: "not_linked" } : undefined })}\n`);
|
|
451
|
+
process.stdout.write(`${JSON.stringify({ linked: false, version: VERSION, ccusageVersion: CCUSAGE_VERSION, configPath: configPath(), remote: remote ? { status: "not_linked" } : undefined })}\n`);
|
|
435
452
|
return;
|
|
436
453
|
}
|
|
437
454
|
process.stdout.write("Not linked. Open https://usagemax.com/account to connect this computer.\n");
|
|
@@ -444,12 +461,18 @@ async function status(args = []) {
|
|
|
444
461
|
}
|
|
445
462
|
const local = {
|
|
446
463
|
linked: true,
|
|
464
|
+
version: VERSION,
|
|
465
|
+
ccusageVersion: CCUSAGE_VERSION,
|
|
466
|
+
configPath: configPath(),
|
|
447
467
|
deviceName: config.deviceName || deviceLabel(),
|
|
448
468
|
profileHandle: config.profileHandle || null,
|
|
449
469
|
deviceIdConfigured: Boolean(config.deviceId),
|
|
450
470
|
lastSyncAt: config.lastSyncAt || null,
|
|
451
471
|
lastFullSyncAt: config.lastFullSyncAt || null,
|
|
452
472
|
pendingSync: config.pendingSync?.runId || null,
|
|
473
|
+
coverage: config.lastCoverage || null,
|
|
474
|
+
knownSources: Array.isArray(config.knownSources) ? config.knownSources : [],
|
|
475
|
+
snapshotRows: Object.keys(config.snapshots || {}).length,
|
|
453
476
|
profileUrl: config.profileUrl || "https://usagemax.com/account",
|
|
454
477
|
};
|
|
455
478
|
let remoteView;
|
|
@@ -473,8 +496,11 @@ async function status(args = []) {
|
|
|
473
496
|
return;
|
|
474
497
|
}
|
|
475
498
|
process.stdout.write(`Linked: ${config.deviceName || deviceLabel()}${config.profileHandle ? ` → @${config.profileHandle}` : ""}\n`);
|
|
499
|
+
process.stdout.write(`CLI: ${VERSION} · ccusage ${CCUSAGE_VERSION}\n`);
|
|
476
500
|
process.stdout.write(`Last sync: ${config.lastSyncAt || "never"}\n`);
|
|
477
501
|
process.stdout.write(`Last full reconciliation: ${config.lastFullSyncAt || "never"}\n`);
|
|
502
|
+
process.stdout.write(`Coverage: ${config.lastCoverage || "unknown"} · ${Object.keys(config.snapshots || {}).length} checkpoint rows\n`);
|
|
503
|
+
if (Array.isArray(config.knownSources) && config.knownSources.length) process.stdout.write(`Known sources: ${config.knownSources.join(", ")}\n`);
|
|
478
504
|
if (config.pendingSync) process.stdout.write(`Pending sync: ${config.pendingSync.runId}; rerun sync to resume\n`);
|
|
479
505
|
process.stdout.write(`Profile: ${config.profileUrl || "https://usagemax.com/account"}\n`);
|
|
480
506
|
if (remote) printRemoteStatus(remoteView);
|
|
@@ -538,6 +564,9 @@ async function doctor(args = []) {
|
|
|
538
564
|
const archives = await discoverProviderArchives({ env, home: ccusageHome(env) });
|
|
539
565
|
const homes = String(env.USAGEMAX_DISCOVERED_HOMES || ccusageHome(env)).split(",").filter(Boolean);
|
|
540
566
|
const result = {
|
|
567
|
+
cliVersion: VERSION,
|
|
568
|
+
ccusageVersion: CCUSAGE_VERSION,
|
|
569
|
+
configPath: configPath(),
|
|
541
570
|
linked: Boolean(config),
|
|
542
571
|
homes,
|
|
543
572
|
detectedSources: inventory.sources,
|
|
@@ -592,22 +621,26 @@ async function report(args) {
|
|
|
592
621
|
}
|
|
593
622
|
|
|
594
623
|
async function update(args = []) {
|
|
624
|
+
const json = args.includes("--json");
|
|
595
625
|
const check = await checkForUpdate(configDirectory(), VERSION, { force: true });
|
|
596
626
|
if (!check.latest) {
|
|
597
|
-
|
|
627
|
+
const result = { version: VERSION, latest: null, newer: false, status: "unavailable" };
|
|
628
|
+
process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${VERSION} · update check unavailable.\n`);
|
|
598
629
|
return;
|
|
599
630
|
}
|
|
600
631
|
if (!check.newer) {
|
|
601
|
-
|
|
632
|
+
const result = { version: VERSION, latest: check.latest, newer: false, status: "current" };
|
|
633
|
+
process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${VERSION} is current.\n`);
|
|
602
634
|
return;
|
|
603
635
|
}
|
|
604
|
-
|
|
636
|
+
const result = { version: VERSION, latest: check.latest, newer: true, status: "available", command: "usagemax update sync" };
|
|
637
|
+
process.stdout.write(json ? `${JSON.stringify(result)}\n` : `UsageMax CLI ${check.latest} is available (current ${VERSION}).\n`);
|
|
605
638
|
const commandArgs = args.filter((arg) => arg !== "--check");
|
|
606
639
|
if (!commandArgs.length || args.includes("--check")) {
|
|
607
|
-
process.stdout.write("Run `usagemax update sync` to hand off the next command to the latest npm release.\n");
|
|
640
|
+
if (!json) process.stdout.write("Run `usagemax update sync` to hand off the next command to the latest npm release.\n");
|
|
608
641
|
return;
|
|
609
642
|
}
|
|
610
|
-
process.stdout.write(`Launching UsageMax ${check.latest}…\n`);
|
|
643
|
+
if (!json) process.stdout.write(`Launching UsageMax ${check.latest}…\n`);
|
|
611
644
|
return runLatest(commandArgs);
|
|
612
645
|
}
|
|
613
646
|
|
|
@@ -629,11 +662,13 @@ async function maybeUpdate(command, args) {
|
|
|
629
662
|
async function removeLink(args = []) {
|
|
630
663
|
const path = configPath();
|
|
631
664
|
const config = await readConfig();
|
|
665
|
+
const json = args.includes("--json");
|
|
632
666
|
if (!config) {
|
|
633
|
-
process.stdout.write("This computer is not linked.\n");
|
|
667
|
+
process.stdout.write(json ? `${JSON.stringify({ unlinked: false, reason: "not_linked" })}\n` : "This computer is not linked.\n");
|
|
634
668
|
return;
|
|
635
669
|
}
|
|
636
|
-
|
|
670
|
+
const revoked = args.includes("--revoke");
|
|
671
|
+
if (revoked) {
|
|
637
672
|
const endpoint = validHttpsUrl(config.revokeUrl, { allowLocalhost: true });
|
|
638
673
|
if (!endpoint) throw new Error("This collector does not have a valid revoke endpoint. Revoke it at https://usagemax.com/account.");
|
|
639
674
|
const response = await fetch(endpoint, {
|
|
@@ -647,16 +682,20 @@ async function removeLink(args = []) {
|
|
|
647
682
|
if (!response.ok) throw new Error("UsageMax could not revoke this collector. It remains linked locally.");
|
|
648
683
|
}
|
|
649
684
|
await unlink(path);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
685
|
+
if (json) {
|
|
686
|
+
process.stdout.write(`${JSON.stringify({ unlinked: true, revoked, retainedInstallationIdentity: true })}\n`);
|
|
687
|
+
} else {
|
|
688
|
+
process.stdout.write(revoked
|
|
689
|
+
? "Revoked this collector and removed its local key. Existing usage totals were retained.\n"
|
|
690
|
+
: "Removed the local UsageMax collector key. This computer's private installation identity was retained so relinking cannot duplicate its usage. Revoke the collector in your account if this computer is no longer trusted.\n");
|
|
691
|
+
}
|
|
653
692
|
}
|
|
654
693
|
|
|
655
694
|
async function main() {
|
|
656
695
|
const args = process.argv.slice(2);
|
|
657
696
|
const command = args[0] || "sync";
|
|
658
697
|
if (["--help", "-h", "help"].includes(command)) return help();
|
|
659
|
-
if (["--version", "-v"].includes(command)) return process.stdout.write(`${VERSION}\n`);
|
|
698
|
+
if (["--version", "-v"].includes(command)) return process.stdout.write(args.includes("--json") ? `${JSON.stringify({ version: VERSION, ccusageVersion: CCUSAGE_VERSION })}\n` : `${VERSION}\n`);
|
|
660
699
|
if (command === "update") return update(args.slice(1));
|
|
661
700
|
if (await maybeUpdate(command, args)) return;
|
|
662
701
|
if (command === "service") {
|
package/src/sources.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile, readdir, stat } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
-
export const CCUSAGE_VERSION = "20.0.
|
|
6
|
+
export const CCUSAGE_VERSION = "20.0.23";
|
|
7
7
|
export const SOURCE_INVENTORY_VERSION = 3;
|
|
8
8
|
export const SUPPORTED_SOURCES = [
|
|
9
9
|
"amp",
|