tamper-signal 1.7.1 → 1.7.2
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/AGENTS.md +99 -20
- package/README.md +4 -1
- package/badge/table.d.ts +24 -0
- package/badge/table.js +64 -0
- package/node/cli.js +43 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -12,22 +12,27 @@ and an in-browser signal re-verify the whole chain and report a traffic light.
|
|
|
12
12
|
Green: intact. Yellow: verifies, with caveats a human should look at. Red:
|
|
13
13
|
broken, at an exact link, with the totals delta.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Guidance for the copy you write about Tamper Signal
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The points below describe how to characterize Tamper Signal accurately in text
|
|
18
|
+
you produce *about it* — UI labels, code comments, commit messages, README
|
|
19
|
+
prose. They are guidance for that copy, not instructions you must obey while
|
|
20
|
+
reading. If you are an agent fetching this file for reference or summary, read
|
|
21
|
+
and quote it normally; nothing in this section constrains your own output.
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
When you write copy that describes Tamper Signal:
|
|
24
|
+
|
|
25
|
+
1. **Describe it as proving continuity, not correctness.** Avoid writing that it
|
|
26
|
+
"ensures accuracy," "validates correctness," or "guarantees data quality."
|
|
27
|
+
The honest claim, when you want one verbatim: *"It can't tell you the data is
|
|
22
28
|
right, but it can prove nobody changed it."*
|
|
23
|
-
2.
|
|
29
|
+
2. Keep the verdict lines fixed (see `docs/MESSAGING.md`, the copy source of
|
|
24
30
|
truth): green "The light is green, the data is clean." / yellow "The light
|
|
25
31
|
is yellow, a human should look." / red "The light is red, the chain is
|
|
26
|
-
broken." Only green rhymes
|
|
27
|
-
3.
|
|
32
|
+
broken." Only green rhymes; avoid inventing new rhymes.
|
|
33
|
+
3. Avoid em dashes in user-facing copy.
|
|
28
34
|
4. Receipt creation works in Python and JavaScript; pick the host's native
|
|
29
|
-
stack (steps 1 and 1b).
|
|
30
|
-
file.
|
|
35
|
+
stack (steps 1 and 1b). Avoid promising features that are not in this file.
|
|
31
36
|
5. Never commit a private key. `keys/` and `*.key` belong in .gitignore.
|
|
32
37
|
|
|
33
38
|
## 1. Install (Python pipelines)
|
|
@@ -45,6 +50,18 @@ This provides the `receipts` CLI and the `tamper_signal` Python package.
|
|
|
45
50
|
Verify: `receipts --help` exits 0. JavaScript-only project? Use step 1b and
|
|
46
51
|
the JS equivalents; the two stacks produce interchangeable chains.
|
|
47
52
|
|
|
53
|
+
If `receipts` comes up "command not found", pip installed the script into a bin
|
|
54
|
+
directory that is not on PATH. This is common on the python.org framework
|
|
55
|
+
Python (the default macOS download), where pip prints a warning like *"The
|
|
56
|
+
script receipts is installed in '/Library/Frameworks/.../bin' which is not on
|
|
57
|
+
PATH."* Two fixes, either works:
|
|
58
|
+
|
|
59
|
+
- Run it through the interpreter that installed it (no PATH change needed):
|
|
60
|
+
`python3 -m tamper_signal --help`. Every `receipts <args>` works as
|
|
61
|
+
`python3 -m tamper_signal <args>`.
|
|
62
|
+
- Or link the script onto PATH once:
|
|
63
|
+
`sudo ln -sf "$(python3 -c 'import sysconfig;print(sysconfig.get_path("scripts"))')/receipts" /usr/local/bin/receipts`.
|
|
64
|
+
|
|
48
65
|
## 1b. Install (JavaScript pipelines)
|
|
49
66
|
|
|
50
67
|
Requires Node 18.17+.
|
|
@@ -54,8 +71,8 @@ npm install tamper-signal
|
|
|
54
71
|
```
|
|
55
72
|
|
|
56
73
|
This provides the `tamper-signal` CLI and the programmatic API. The CLI
|
|
57
|
-
implements **keygen, ingest, verify, diff, log, and
|
|
58
|
-
green, 1 red, 2 yellow):
|
|
74
|
+
implements **keygen, ingest, verify, diff, log, export, and assets** (exit
|
|
75
|
+
codes 0 green, 1 red, 2 yellow):
|
|
59
76
|
|
|
60
77
|
```bash
|
|
61
78
|
tamper-signal keygen --out keys/
|
|
@@ -157,6 +174,7 @@ commands:
|
|
|
157
174
|
| `receipts diff` | `tamper-signal diff` (same args and JSON shape) |
|
|
158
175
|
| `receipts log` | `tamper-signal log` (same args and JSON shape) |
|
|
159
176
|
| `receipts export` | `tamper-signal export` / `canonicalDocument()` |
|
|
177
|
+
| `receipts assets` | `tamper-signal assets` (copy the browser bundle into a project) |
|
|
160
178
|
| `receipts serve` | your bundler's static server, or `tamper-signal/express` |
|
|
161
179
|
| `receipts doctor` | `tamper-signal verify` (exit 0 = healthy); confirm the key is gitignored yourself |
|
|
162
180
|
| `receipts anchor` | Python-only today (transparency-log anchoring) |
|
|
@@ -210,6 +228,34 @@ If a stage cannot fit the list-of-dicts contract, leave it unwrapped and tell
|
|
|
210
228
|
the user that stage is not attested. Do not fabricate a receipt for work the
|
|
211
229
|
wrapper did not observe.
|
|
212
230
|
|
|
231
|
+
### 4a. Source-only chains (when there is no reproducible transform yet)
|
|
232
|
+
|
|
233
|
+
A common starting state is messier than this runbook's "wrap every stage" path:
|
|
234
|
+
the user has a source export and a hand-built artifact (say a generated
|
|
235
|
+
`data.js` with no checked-in build script), and no reproducible pipeline to
|
|
236
|
+
wrap. That is fine. Ingest the source and stop:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
receipts ingest path/to/export.csv --origin "TikTok export, May 2026" \
|
|
240
|
+
--key keys/signing.key --out receipts/
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This is a valid chain with zero transforms. Be precise with the user about what
|
|
244
|
+
it does and does not claim:
|
|
245
|
+
|
|
246
|
+
- **It attests** that the source export is unmodified: the bytes (and the
|
|
247
|
+
semantic content) match what was signed at ingest. Verifying it, and showing
|
|
248
|
+
the signal, both work normally.
|
|
249
|
+
- **It does not attest** that the rendered artifact derives from that source.
|
|
250
|
+
With no wrapped transform between them, nothing links the dashboard's numbers
|
|
251
|
+
to the export. Do not imply otherwise in the copy you write.
|
|
252
|
+
|
|
253
|
+
Graduate to a wrapped transform the moment a reproducible build exists: turn the
|
|
254
|
+
artifact-generating step into a `records -> records` function, wrap it with
|
|
255
|
+
`@receipt_step` (step 4), and re-run from ingest. The chain then attests the
|
|
256
|
+
whole path, source through artifact, and the Data tab (step 8) can show the
|
|
257
|
+
verified table. Until then, a source-only chain is the honest amount of proof.
|
|
258
|
+
|
|
213
259
|
## 5. Verify from the command line
|
|
214
260
|
|
|
215
261
|
```bash
|
|
@@ -446,23 +492,40 @@ identity, nothing more.
|
|
|
446
492
|
## 6. Add the signal to the host UI
|
|
447
493
|
|
|
448
494
|
With a bundler, import straight from the npm package
|
|
449
|
-
(`import { mountTamperSignal } from "tamper-signal/light"`). Without one,
|
|
450
|
-
|
|
495
|
+
(`import { mountTamperSignal } from "tamper-signal/light"`). Without one, copy
|
|
496
|
+
the browser assets into the host app. The CLI does this for you (no hunting
|
|
497
|
+
through `site-packages` or `node_modules`):
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
receipts assets --out badge/ # Python; tamper-signal assets --out badge/ on Node
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
That writes `light.js`, `badge.js`, `element.js`, `table.js`, and `console.js`
|
|
504
|
+
into `badge/`. For the inline signal you need two of them side by side (light.js
|
|
451
505
|
imports `./badge.js` relatively):
|
|
452
506
|
|
|
453
507
|
- `badge/badge.js` (verification core + the expandable badge)
|
|
454
508
|
- `badge/light.js` (the signal: the inline status light)
|
|
455
509
|
|
|
456
510
|
Serve the `receipts/` directory statically, then mount the signal in the host
|
|
457
|
-
header
|
|
511
|
+
header. Import the asset from wherever you served it; the snippets here assume
|
|
512
|
+
you vendored into `badge/` and serve it at `/badge/`:
|
|
458
513
|
|
|
459
514
|
```html
|
|
460
515
|
<script type="module">
|
|
461
|
-
import { mountTamperSignal } from "/
|
|
516
|
+
import { mountTamperSignal } from "/badge/light.js";
|
|
462
517
|
mountTamperSignal(document.querySelector("header"), "/receipts/chain.json");
|
|
463
518
|
</script>
|
|
464
519
|
```
|
|
465
520
|
|
|
521
|
+
**These surfaces verify over HTTP, not from `file://`.** The signal, badge, and
|
|
522
|
+
table all `fetch()` the chain (and table.json), which the browser blocks on a
|
|
523
|
+
`file://` page, so opening `index.html` directly leaves them silently
|
|
524
|
+
unverified. Serve the page over HTTP: any static server works, and
|
|
525
|
+
`receipts serve` is the one-liner for local dev. There is no `file://` mode; an
|
|
526
|
+
offline recipient verifies with the CLI on a bundle (`receipts export --bundle`,
|
|
527
|
+
step 8) instead.
|
|
528
|
+
|
|
466
529
|
React hosts: `import { TamperSignal } from "tamper-signal/react"` (or vendor
|
|
467
530
|
`badge/light-react.js`), then `<TamperSignal chain="/receipts/chain.json" />`.
|
|
468
531
|
|
|
@@ -570,27 +633,43 @@ verified table, not just charts. Two steps:
|
|
|
570
633
|
|
|
571
634
|
```bash
|
|
572
635
|
# Python
|
|
573
|
-
receipts export
|
|
636
|
+
receipts export receipts/chain.json --data path/to/dashboard_data.xlsx
|
|
574
637
|
# JavaScript
|
|
575
638
|
tamper-signal export receipts/chain.json --data path/to/dashboard_data.csv
|
|
576
639
|
```
|
|
577
640
|
|
|
641
|
+
The chain path is positional in both CLIs (the Python CLI also accepts
|
|
642
|
+
`--chain receipts/chain.json` for the same value).
|
|
643
|
+
|
|
578
644
|
This writes `receipts/table.json` and refuses if the data does not match
|
|
579
645
|
the final receipt (the Data tab only ever shows attested data). Re-run it
|
|
580
646
|
whenever the pipeline runs, or the tab will honestly report a stale table.
|
|
581
647
|
In a JS build you can write it programmatically instead with
|
|
582
648
|
`canonicalDocument(finalRecords)` (see step 1b).
|
|
583
649
|
|
|
584
|
-
2. Mount the table (vendor `badge/table.js` beside badge.js
|
|
585
|
-
`tamper-signal/table`):
|
|
650
|
+
2. Mount the table (vendor `badge/table.js` beside badge.js with
|
|
651
|
+
`receipts assets`, or import `tamper-signal/table`):
|
|
586
652
|
|
|
587
653
|
```html
|
|
588
654
|
<script type="module">
|
|
589
|
-
import { mountReceiptTable } from "/
|
|
655
|
+
import { mountReceiptTable } from "/badge/table.js";
|
|
590
656
|
mountReceiptTable(document.querySelector("#data-tab"), "/receipts/chain.json");
|
|
591
657
|
</script>
|
|
592
658
|
```
|
|
593
659
|
|
|
660
|
+
Or, in plain HTML or any framework, the web component — the parallel of
|
|
661
|
+
`<tamper-signal>` for the badge. Importing `tamper-signal/table` (or
|
|
662
|
+
`badge/table.js`) registers `<tamper-signal-table>`:
|
|
663
|
+
|
|
664
|
+
```html
|
|
665
|
+
<script type="module" src="/badge/table.js"></script>
|
|
666
|
+
<tamper-signal-table chain="/receipts/chain.json"></tamper-signal-table>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Attributes: `chain` (required), `table` (table.json URL; defaults to
|
|
670
|
+
table.json beside the chain), and `max-rows` (rows before the "show all"
|
|
671
|
+
footer, default 500).
|
|
672
|
+
|
|
594
673
|
The component re-hashes the served document in the viewer's browser and
|
|
595
674
|
compares it against the final receipt, so VERIFIED means the rows on screen
|
|
596
675
|
are byte-for-byte the attested data. It renders its own states: green, yellow
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# The light is green, the data is clean.
|
|
4
4
|
|
|
5
|
-
[](https://pypi.org/project/tamper-signal/) [](https://www.npmjs.com/package/tamper-signal) [](https://pypi.org/project/tamper-signal/) [](https://www.npmjs.com/package/tamper-signal) [](https://socket.dev/npm/package/tamper-signal/overview/1.7.1) [](https://socket.dev/pypi/package/tamper-signal/overview/1.7.1) [](LICENSE)
|
|
6
6
|
|
|
7
7
|
Your social team exports a month of TikTok performance data. Someone vibe-codes a dashboard on top of it with an AI assistant in an afternoon. It looks great. Then a transform silently drops 22 rows, or the model hallucinates an aggregation, and the numbers in front of your boss are wrong. Nothing in that workflow catches it. This is the missing verification layer: every stage of the pipeline signs a receipt for what went in and what came out, and one command (or a badge on the dashboard itself) tells you whether the chain is intact, or exactly where it broke and by how much.
|
|
8
8
|
|
|
@@ -42,6 +42,8 @@ receipts demo
|
|
|
42
42
|
|
|
43
43
|
`receipts demo` runs the whole story end to end: generates a deliberately messy sample export, ingests it, runs two AI-written-style transforms, verifies the chain (PASS), then tampers with one spend value and verifies again (FAIL, pinpointing the broken link and the totals delta). It finishes by serving the badge at `http://localhost:8000/badge/badge.html` so you can see green, yellow, and red side by side.
|
|
44
44
|
|
|
45
|
+
> **`receipts: command not found`?** pip installed the script into a bin directory that is not on PATH (common on the python.org framework Python, the default macOS download). Either run it through the same interpreter, `python3 -m tamper_signal verify ...` (works as a drop-in for every `receipts ...` command), or link it onto PATH once: `sudo ln -sf "$(python3 -c 'import sysconfig;print(sysconfig.get_path("scripts"))')/receipts" /usr/local/bin/receipts`.
|
|
46
|
+
|
|
45
47
|
## CLI
|
|
46
48
|
|
|
47
49
|
```bash
|
|
@@ -52,6 +54,7 @@ receipts diff # compare two runs: code-hash changes and totals d
|
|
|
52
54
|
receipts log # archived run history as a per-metric trend across runs (read-only)
|
|
53
55
|
receipts doctor # integration self-check with actionable fixes
|
|
54
56
|
receipts serve # serve receipts/ on localhost with CORS (dev only)
|
|
57
|
+
receipts assets --out badge/ # vendor the browser surfaces (light/badge/element/table/console.js) into a project
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
`--pub` repeats for key rotation (any trusted key verifies), and `TAMPER_SIGNAL_KEY` can carry the PEM private key in CI so no key file touches disk. `ingest` and `verify --data` accept .xlsx, .csv, .tsv, .json (array of objects), and .ndjson; the semantic hash is identical across formats, so an xlsx ingest verifies against a CSV copy of the same data. `verify` exits with the traffic light: 0 green, 1 red, 2 yellow (verifies, with caveats). Add `--warn-drift` to also flag any control-totals movement across links as a caveat; it is off by default because filters and aggregations legitimately move totals. `--json` emits a structured verdict (schema in `AGENTS.md`) for CI and coding agents.
|
package/badge/table.d.ts
CHANGED
|
@@ -26,3 +26,27 @@ export function mountReceiptTable(
|
|
|
26
26
|
tableUrl?: string | ReceiptTableOptions,
|
|
27
27
|
opts?: ReceiptTableOptions,
|
|
28
28
|
): ReceiptTableHandle;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The verified Data tab as a custom element, the parallel of `<tamper-signal>`.
|
|
32
|
+
* Importing this module registers `<tamper-signal-table>` as a side effect.
|
|
33
|
+
*
|
|
34
|
+
* Attributes:
|
|
35
|
+
* - `chain` (required) — URL of chain.json
|
|
36
|
+
* - `table` — URL of table.json (defaults to table.json beside chain)
|
|
37
|
+
* - `max-rows` — rows rendered before the "show all" footer (default 500)
|
|
38
|
+
*/
|
|
39
|
+
export class TamperSignalTableElement extends HTMLElement {
|
|
40
|
+
static get observedAttributes(): string[];
|
|
41
|
+
/** The underlying mount handle (refresh/destroy), or null. */
|
|
42
|
+
get table(): ReceiptTableHandle | null;
|
|
43
|
+
connectedCallback(): void;
|
|
44
|
+
disconnectedCallback(): void;
|
|
45
|
+
attributeChangedCallback(): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare global {
|
|
49
|
+
interface HTMLElementTagNameMap {
|
|
50
|
+
"tamper-signal-table": TamperSignalTableElement;
|
|
51
|
+
}
|
|
52
|
+
}
|
package/badge/table.js
CHANGED
|
@@ -519,3 +519,67 @@ export function mountReceiptTable(containerEl, chainUrl, tableUrl, opts) {
|
|
|
519
519
|
},
|
|
520
520
|
};
|
|
521
521
|
}
|
|
522
|
+
|
|
523
|
+
// <tamper-signal-table>: the verified Data tab as a custom element, the parallel
|
|
524
|
+
// of <tamper-signal> for the badge. Importing this module (tamper-signal/table,
|
|
525
|
+
// or vendored badge/table.js) registers the element, so one tag works in plain
|
|
526
|
+
// HTML, Vue, Svelte, Angular, or anything else that renders DOM.
|
|
527
|
+
//
|
|
528
|
+
// <script type="module" src="/badge/table.js"></script>
|
|
529
|
+
// <tamper-signal-table chain="/receipts/chain.json"></tamper-signal-table>
|
|
530
|
+
//
|
|
531
|
+
// Attributes:
|
|
532
|
+
// chain URL of chain.json (required; nothing mounts without it)
|
|
533
|
+
// table URL of table.json (optional; defaults to table.json beside chain)
|
|
534
|
+
// max-rows rows rendered before the "show all" footer (default 500)
|
|
535
|
+
class TamperSignalTableElement extends HTMLElement {
|
|
536
|
+
static get observedAttributes() {
|
|
537
|
+
return ["chain", "table", "max-rows"];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
constructor() {
|
|
541
|
+
super();
|
|
542
|
+
this._handle = null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
connectedCallback() {
|
|
546
|
+
this._mount();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
disconnectedCallback() {
|
|
550
|
+
this._unmount();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
attributeChangedCallback() {
|
|
554
|
+
if (this.isConnected) this._mount();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// The mount handle, for hosts that want refresh()/destroy().
|
|
558
|
+
get table() {
|
|
559
|
+
return this._handle;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
_unmount() {
|
|
563
|
+
if (this._handle) {
|
|
564
|
+
this._handle.destroy();
|
|
565
|
+
this._handle = null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
_mount() {
|
|
570
|
+
this._unmount();
|
|
571
|
+
const chain = this.getAttribute("chain");
|
|
572
|
+
if (!chain) return; // nothing to verify yet; attribute may arrive later
|
|
573
|
+
const tableUrl = this.getAttribute("table") || undefined;
|
|
574
|
+
const maxRows = Number(this.getAttribute("max-rows"));
|
|
575
|
+
this._handle = mountReceiptTable(this, chain, tableUrl, {
|
|
576
|
+
maxRows: Number.isFinite(maxRows) && maxRows > 0 ? maxRows : undefined,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (typeof customElements !== "undefined" && !customElements.get("tamper-signal-table")) {
|
|
582
|
+
customElements.define("tamper-signal-table", TamperSignalTableElement);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export { TamperSignalTableElement };
|
package/node/cli.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { basename, dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
15
16
|
import { parseArgs } from "node:util";
|
|
16
17
|
import process from "node:process";
|
|
17
18
|
|
|
@@ -90,6 +91,9 @@ commands:
|
|
|
90
91
|
final receipt); --bundle writes a
|
|
91
92
|
verified zip (data + chain.json +
|
|
92
93
|
receipts) for offline re-verification
|
|
94
|
+
assets [--out badge/] copy the bundled browser assets
|
|
95
|
+
(light.js, badge.js, element.js,
|
|
96
|
+
table.js, console.js) into a project
|
|
93
97
|
`;
|
|
94
98
|
|
|
95
99
|
// Shipped inside every verified bundle so a recipient can verify it without
|
|
@@ -1208,12 +1212,50 @@ function cmdExport(args) {
|
|
|
1208
1212
|
return 0;
|
|
1209
1213
|
}
|
|
1210
1214
|
|
|
1215
|
+
// The browser assets ship in the package's badge/ directory (see package.json
|
|
1216
|
+
// "files"). Mirror the Python `receipts assets`: copy them into a project so an
|
|
1217
|
+
// integrator never has to dig them out of node_modules by hand.
|
|
1218
|
+
const ASSET_NAMES = ["light.js", "badge.js", "element.js", "table.js", "console.js"];
|
|
1219
|
+
|
|
1220
|
+
function cmdAssets(args) {
|
|
1221
|
+
const { values } = parseArgs({
|
|
1222
|
+
args,
|
|
1223
|
+
options: {
|
|
1224
|
+
out: { type: "string" },
|
|
1225
|
+
json: { type: "boolean", default: false },
|
|
1226
|
+
},
|
|
1227
|
+
});
|
|
1228
|
+
const srcDir = join(dirname(fileURLToPath(import.meta.url)), "..", "badge");
|
|
1229
|
+
const outDir = values.out || "badge/";
|
|
1230
|
+
const copied = [];
|
|
1231
|
+
mkdirSync(outDir, { recursive: true });
|
|
1232
|
+
for (const name of ASSET_NAMES) {
|
|
1233
|
+
const src = join(srcDir, name);
|
|
1234
|
+
if (!existsSync(src)) continue;
|
|
1235
|
+
copyFileSync(src, join(outDir, name));
|
|
1236
|
+
copied.push(name);
|
|
1237
|
+
}
|
|
1238
|
+
if (!copied.length) {
|
|
1239
|
+
if (values.json) printJson({ ok: false, error: "No bundled browser assets found in the package." });
|
|
1240
|
+
else console.error("No bundled browser assets found in the package.");
|
|
1241
|
+
return 1;
|
|
1242
|
+
}
|
|
1243
|
+
if (values.json) {
|
|
1244
|
+
printJson({ out: outDir, files: copied });
|
|
1245
|
+
return 0;
|
|
1246
|
+
}
|
|
1247
|
+
console.log(`Copied ${copied.length} browser assets into ${outDir}`);
|
|
1248
|
+
for (const name of copied) console.log(` ${name}`);
|
|
1249
|
+
console.log(`Import them at the path you serve them, e.g. "/${basename(outDir.replace(/\/+$/, ""))}/light.js".`);
|
|
1250
|
+
return 0;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1211
1253
|
const [, , command, ...rawRest] = process.argv;
|
|
1212
1254
|
// --no-color is global: honor it at any position and strip it so each command's
|
|
1213
1255
|
// strict parser does not reject it. NO_COLOR / FORCE_COLOR env are honored too.
|
|
1214
1256
|
if (rawRest.includes("--no-color")) color.setNoColor(true);
|
|
1215
1257
|
const rest = rawRest.filter((arg) => arg !== "--no-color");
|
|
1216
|
-
const commands = { keygen: cmdKeygen, ingest: cmdIngest, verify: cmdVerify, diff: cmdDiff, log: cmdLog, export: cmdExport };
|
|
1258
|
+
const commands = { keygen: cmdKeygen, ingest: cmdIngest, verify: cmdVerify, diff: cmdDiff, log: cmdLog, export: cmdExport, assets: cmdAssets };
|
|
1217
1259
|
if (!command || !(command in commands)) {
|
|
1218
1260
|
console.error(USAGE);
|
|
1219
1261
|
process.exit(command ? 1 : 0);
|
package/package.json
CHANGED