scxq2-cc 1.0.0

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 ADDED
@@ -0,0 +1,340 @@
1
+ <p align="center">
2
+ <img src="scxq2-logo.svg" alt="SCXQ2 Logo" width="200" height="200">
3
+ </p>
4
+
5
+ <h1 align="center">SCXQ2</h1>
6
+
7
+ <p align="center">
8
+ <strong>Compression Calculus Engine</strong><br>
9
+ Deterministic, Proof-Generating, Content-Addressable Language Packs
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="#installation">Installation</a> •
14
+ <a href="#quick-start">Quick Start</a> •
15
+ <a href="#api">API</a> •
16
+ <a href="#specification">Specification</a> •
17
+ <a href="#security">Security</a>
18
+ </p>
19
+
20
+ ---
21
+
22
+ ## Overview
23
+
24
+ SCXQ2 is a **frozen, deterministic compression calculus** that produces **content-addressable language packs**. It implements CC-v1 (Compression Calculus v1) operators to create self-verifying artifacts with cryptographic proofs of reversibility.
25
+
26
+ ### Key Features
27
+
28
+ - **Deterministic** - Same input always produces identical output
29
+ - **Content-Addressable** - SHA-256 identity hashes for all artifacts
30
+ - **Proof-Generating** - Every compression includes reversibility proof
31
+ - **Universal Runtime** - Works in Node.js, browsers, and workers
32
+ - **Multi-Lane** - Compress multiple sources with shared dictionary
33
+ - **Type-Safe** - Full TypeScript definitions included
34
+
35
+ ### What SCXQ2 Is
36
+
37
+ - A **representation algebra** for compressing text
38
+ - A **language artifact** format (dict + block + proof)
39
+ - A **deterministic encoding** (DICT16-B64)
40
+
41
+ ### What SCXQ2 Is NOT
42
+
43
+ - ❌ Not encryption
44
+ - ❌ Not a file format
45
+ - ❌ Not a transport protocol
46
+ - ❌ Not an execution language
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ npm install @asx/scxq2-cc
54
+ ```
55
+
56
+ **Requirements:** Node.js 18+ or modern browser with WebCrypto
57
+
58
+ ---
59
+
60
+ ## Quick Start
61
+
62
+ ### Basic Compression
63
+
64
+ ```javascript
65
+ import { ccCompress, ccDecompress } from '@asx/scxq2-cc';
66
+
67
+ // Compress source code
68
+ const source = `
69
+ function hello() {
70
+ console.log("Hello, World!");
71
+ }
72
+ `;
73
+
74
+ const pack = await ccCompress(source, { maxDict: 512 });
75
+
76
+ console.log(pack.proof.ok); // true - roundtrip verified
77
+ console.log(pack.dict.dict.length); // number of dictionary entries
78
+ console.log(pack.audit.sizes.ratio); // compression ratio
79
+
80
+ // Decompress
81
+ const roundtrip = ccDecompress(pack.dict, pack.block);
82
+ console.log(roundtrip === source); // true
83
+ ```
84
+
85
+ ### Multi-Lane Compression
86
+
87
+ ```javascript
88
+ import { ccCompressLanes, ccDecompress } from '@asx/scxq2-cc';
89
+
90
+ const pack = await ccCompressLanes({
91
+ lanes: [
92
+ { lane_id: 'index', text: 'export * from "./utils";' },
93
+ { lane_id: 'utils', text: 'export function utils() { return 42; }' },
94
+ { lane_id: 'types', text: 'export interface Config { value: number; }' }
95
+ ]
96
+ });
97
+
98
+ // All lanes share the same dictionary
99
+ console.log(pack.dict.dict.length); // shared dictionary size
100
+ console.log(pack.lanes.length); // 3 blocks
101
+
102
+ // Decompress each lane
103
+ for (const block of pack.lanes) {
104
+ const text = ccDecompress(pack.dict, block);
105
+ console.log(`Lane ${block.lane_id}: ${text.length} chars`);
106
+ }
107
+ ```
108
+
109
+ ### Synchronous API (Node.js only)
110
+
111
+ ```javascript
112
+ import { ccCompressSync, ccCompressLanesSync } from '@asx/scxq2-cc';
113
+
114
+ // Sync single-lane
115
+ const pack = ccCompressSync(source);
116
+
117
+ // Sync multi-lane
118
+ const multiPack = ccCompressLanesSync({ lanes: [...] });
119
+ ```
120
+
121
+ ---
122
+
123
+ ## API
124
+
125
+ ### Core Functions
126
+
127
+ | Function | Description |
128
+ |----------|-------------|
129
+ | `ccCompress(input, opts?)` | Async compression with proof |
130
+ | `ccCompressSync(input, opts?)` | Sync compression (Node.js only) |
131
+ | `ccCompressLanes(input, opts?)` | Async multi-lane compression |
132
+ | `ccCompressLanesSync(input, opts?)` | Sync multi-lane (Node.js only) |
133
+ | `ccDecompress(dict, block)` | Decompress block using dictionary |
134
+ | `verifyPack(dict, block)` | Verify pack structure |
135
+
136
+ ### Compression Options
137
+
138
+ ```typescript
139
+ interface CCCompressOptions {
140
+ maxDict?: number; // Max dictionary entries (1-65535, default: 1024)
141
+ minLen?: number; // Min token length (2-128, default: 3)
142
+ noStrings?: boolean; // Skip string literal tokens
143
+ noWS?: boolean; // Skip whitespace tokens
144
+ noPunct?: boolean; // Skip punctuation tokens
145
+ enableFieldOps?: boolean; // Enable JSON key extraction
146
+ enableEdgeOps?: boolean; // Enable edge witnesses
147
+ created_utc?: string; // ISO timestamp
148
+ source_file?: string; // Source identifier
149
+ }
150
+ ```
151
+
152
+ ### Result Objects
153
+
154
+ ```typescript
155
+ interface CCResult {
156
+ dict: SCXQ2Dict; // Dictionary with token array
157
+ block: SCXQ2Block; // Encoded block with b64 payload
158
+ proof: CCProof; // Reversibility proof
159
+ audit: CCAudit; // Compression metrics
160
+ }
161
+ ```
162
+
163
+ ### Utility Functions
164
+
165
+ ```javascript
166
+ import {
167
+ canon, // Canonical JSON serialization
168
+ sha256HexUtf8, // Async SHA-256 hash
169
+ bytesToBase64, // Encode bytes to base64
170
+ base64ToBytes // Decode base64 to bytes
171
+ } from '@asx/scxq2-cc';
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Encoding Format
177
+
178
+ SCXQ2 uses a simple bytecode format:
179
+
180
+ | Byte | Meaning |
181
+ |------|---------|
182
+ | `0x00-0x7F` | ASCII literal (1 byte) |
183
+ | `0x80 [hi] [lo]` | Dictionary reference (3 bytes) |
184
+ | `0x81 [hi] [lo]` | UTF-16 code unit (3 bytes) |
185
+
186
+ ### Dictionary Properties
187
+
188
+ - Maximum 65,535 entries (16-bit index)
189
+ - Ordered longest-first for greedy matching
190
+ - UTF-16 code-unit indexed
191
+ - Immutable once sealed
192
+
193
+ ---
194
+
195
+ ## Specification
196
+
197
+ SCXQ2 implements the frozen **CC-v1 (Compression Calculus v1)** specification.
198
+
199
+ ### Invariants (Non-Negotiable)
200
+
201
+ 1. **Deterministic Canonical Form** - Canonical JSON, stable UTF-8
202
+ 2. **Reversibility** - Every block losslessly decodable
203
+ 3. **Single-Hash Identity** - One SHA-256 identifies entire pack
204
+ 4. **No Runtime Authority** - No execution, IO, or environment semantics
205
+ 5. **Lane Isolation** - Blocks independent except shared dictionary
206
+ 6. **Proof-Bound** - Proof inseparable from content
207
+ 7. **Compression-Only** - Never introduces meaning, only representation
208
+
209
+ ### Pack Structure
210
+
211
+ ```
212
+ SCXQ2 PACK
213
+ ├── Dictionary (shared)
214
+ ├── Blocks[] (lanes)
215
+ │ ├── Encoded byte stream (b64)
216
+ │ ├── Optional lane_id
217
+ │ └── Optional edges (EDGE witnesses)
218
+ ├── Proof
219
+ └── pack_sha256_canon (identity)
220
+ ```
221
+
222
+ ### CC Operators
223
+
224
+ | Operator | Purpose |
225
+ |----------|---------|
226
+ | `CC.NORM` | Normalize newlines, optional whitespace policy |
227
+ | `CC.DICT` | Extract dictionary from token stream |
228
+ | `CC.FIELD` | Structural JSON key augmentation |
229
+ | `CC.LANE` | Multi-lane product construction |
230
+ | `CC.EDGE` | Adjacency witnesses for analysis |
231
+
232
+ ---
233
+
234
+ ## Security
235
+
236
+ SCXQ2 is **compression representation**, not encryption.
237
+
238
+ ### Threat Mitigations
239
+
240
+ - **Memory Safety** - No out-of-bounds access, bounded allocations
241
+ - **Time Safety** - O(n) decode time, predictable worst-case
242
+ - **Deterministic Failure** - Stable error codes, fail-closed
243
+ - **Integrity** - SHA-256 identity prevents silent mutation
244
+
245
+ ### Decompression Bomb Protection
246
+
247
+ Set `maxOutputUnits` to limit decoded output size:
248
+
249
+ ```javascript
250
+ // Verifier with output limit
251
+ const result = await ccCompress(input, {
252
+ maxDict: 1024
253
+ // Implementation can add maxOutputUnits for decode limits
254
+ });
255
+ ```
256
+
257
+ ### Security Non-Goals
258
+
259
+ SCXQ2 does NOT provide:
260
+ - Confidentiality
261
+ - Authentication
262
+ - Authorization
263
+ - Tamper-proofing against hash recomputation
264
+
265
+ ---
266
+
267
+ ## Error Codes
268
+
269
+ | Code | Phase | Description |
270
+ |------|-------|-------------|
271
+ | `scxq2.error.pack_*` | pack | Pack structure errors |
272
+ | `scxq2.error.dict_*` | dict | Dictionary errors |
273
+ | `scxq2.error.block_*` | block | Block errors |
274
+ | `scxq2.error.decode_*` | decode | Decoding errors |
275
+ | `scxq2.error.proof_*` | proof | Proof verification errors |
276
+
277
+ ---
278
+
279
+ ## Project Structure
280
+
281
+ ```
282
+ scxq2-cc/
283
+ ├── package.json
284
+ ├── README.md
285
+ ├── BRAND.md # Brand guidelines
286
+ ├── SCXQ2_language.md # Full language specification
287
+ ├── SCXQ2_CC_ENGINE_V1.md # Engine specification
288
+ ├── NPM.md # NPM module documentation
289
+ ├── scxq2-logo.svg # Logo
290
+ ├── src/
291
+ │ ├── index.js # Main entry point
292
+ │ ├── engine.js # Core CC engine
293
+ │ ├── canon.js # Canonical JSON
294
+ │ ├── sha.js # SHA-256 utilities
295
+ │ └── base64.js # Base64 utilities
296
+ └── dist/
297
+ ├── index.js # Built entry point
298
+ ├── index.d.ts # TypeScript definitions
299
+ └── ...
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Constants
305
+
306
+ ```javascript
307
+ import { CC_ENGINE, SCXQ2_ENCODING, CC_OPS } from '@asx/scxq2-cc';
308
+
309
+ console.log(CC_ENGINE['@id']);
310
+ // "asx://cc/engine/scxq2.v1"
311
+
312
+ console.log(SCXQ2_ENCODING);
313
+ // { mode: "SCXQ2-DICT16-B64", encoding: "SCXQ2-1" }
314
+
315
+ console.log(CC_OPS);
316
+ // { NORM: "cc.norm.v1", DICT: "cc.dict.v1", ... }
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Final Law
322
+
323
+ > **If two SCXQ2 packs have the same `pack_sha256_canon`, they are the same language object.**
324
+
325
+ Everything else is projection.
326
+
327
+ ---
328
+
329
+ ## License
330
+
331
+ MIT
332
+
333
+ ---
334
+
335
+ ## Links
336
+
337
+ - [SCXQ2 Language Specification](./SCXQ2_language.md)
338
+ - [CC Engine Specification](./SCXQ2_CC_ENGINE_V1.md)
339
+ - [NPM Module Documentation](./NPM.md)
340
+ - [Brand Guidelines](./BRAND.md)
package/dist/base64.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * SCXQ2 Base64 Utilities
3
+ *
4
+ * Universal base64 encoding/decoding that works in Node.js, browsers, and workers.
5
+ * Handles the "base64:" prefix format used in some SCXQ2 contexts.
6
+ *
7
+ * @module @asx/scxq2-cc/base64
8
+ * @version 1.0.0
9
+ */
10
+
11
+ /**
12
+ * Encodes bytes to base64 string.
13
+ *
14
+ * @param {Uint8Array|number[]} bytes - Bytes to encode
15
+ * @returns {string} Base64-encoded string
16
+ */
17
+ export function bytesToBase64(bytes) {
18
+ // Ensure we have a proper array-like
19
+ const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
20
+
21
+ // Node.js Buffer
22
+ if (typeof Buffer !== "undefined" && Buffer.from) {
23
+ return Buffer.from(arr).toString("base64");
24
+ }
25
+
26
+ // Browser/Worker: use btoa
27
+ if (typeof btoa === "function") {
28
+ let binary = "";
29
+ for (let i = 0; i < arr.length; i++) {
30
+ binary += String.fromCharCode(arr[i]);
31
+ }
32
+ return btoa(binary);
33
+ }
34
+
35
+ throw new Error("SCXQ2: no base64 encoder available");
36
+ }
37
+
38
+ /**
39
+ * Decodes base64 string to bytes.
40
+ * Automatically strips "base64:" prefix if present.
41
+ *
42
+ * @param {string} b64 - Base64-encoded string
43
+ * @returns {Uint8Array} Decoded bytes
44
+ */
45
+ export function base64ToBytes(b64) {
46
+ // Strip optional "base64:" prefix
47
+ const clean = String(b64).startsWith("base64:")
48
+ ? String(b64).slice(7)
49
+ : String(b64);
50
+
51
+ // Node.js Buffer
52
+ if (typeof Buffer !== "undefined" && Buffer.from) {
53
+ return new Uint8Array(Buffer.from(clean, "base64"));
54
+ }
55
+
56
+ // Browser/Worker: use atob
57
+ if (typeof atob === "function") {
58
+ const binary = atob(clean);
59
+ const bytes = new Uint8Array(binary.length);
60
+ for (let i = 0; i < binary.length; i++) {
61
+ bytes[i] = binary.charCodeAt(i);
62
+ }
63
+ return bytes;
64
+ }
65
+
66
+ throw new Error("SCXQ2: no base64 decoder available");
67
+ }
68
+
69
+ /**
70
+ * Validates that a string is valid base64.
71
+ *
72
+ * @param {string} b64 - String to validate
73
+ * @returns {boolean} True if valid base64
74
+ */
75
+ export function isValidBase64(b64) {
76
+ const clean = String(b64).startsWith("base64:")
77
+ ? String(b64).slice(7)
78
+ : String(b64);
79
+
80
+ // Standard base64 regex
81
+ const regex = /^[A-Za-z0-9+/]*={0,2}$/;
82
+ return regex.test(clean) && clean.length % 4 === 0;
83
+ }
package/dist/canon.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SCXQ2 Canonical JSON Utilities
3
+ *
4
+ * Provides deterministic JSON serialization with sorted keys for
5
+ * content-addressable hashing and reproducible pack identities.
6
+ *
7
+ * @module @asx/scxq2-cc/canon
8
+ * @version 1.0.0
9
+ */
10
+
11
+ /**
12
+ * Recursively sorts object keys for deterministic JSON output.
13
+ * Arrays are preserved in order, objects have keys sorted alphabetically.
14
+ *
15
+ * @param {*} value - Any JSON-serializable value
16
+ * @returns {*} Value with all nested object keys sorted
17
+ */
18
+ export function sortKeysDeep(value) {
19
+ if (Array.isArray(value)) {
20
+ return value.map(sortKeysDeep);
21
+ }
22
+
23
+ if (value !== null && typeof value === "object") {
24
+ const sorted = {};
25
+ const keys = Object.keys(value).sort();
26
+ for (const key of keys) {
27
+ sorted[key] = sortKeysDeep(value[key]);
28
+ }
29
+ return sorted;
30
+ }
31
+
32
+ return value;
33
+ }
34
+
35
+ /**
36
+ * Produces canonical JSON string with sorted keys.
37
+ * This is the required serialization for all SCXQ2 hash computations.
38
+ *
39
+ * @param {*} obj - Object to serialize
40
+ * @returns {string} Canonical JSON string
41
+ */
42
+ export function canon(obj) {
43
+ return JSON.stringify(sortKeysDeep(obj));
44
+ }
45
+
46
+ /**
47
+ * Creates a shallow copy of an object with specified fields removed.
48
+ * Used for computing hashes that exclude the hash field itself.
49
+ *
50
+ * @param {Object} obj - Source object
51
+ * @param {string[]} fields - Fields to exclude
52
+ * @returns {Object} New object without excluded fields
53
+ */
54
+ export function strip(obj, fields) {
55
+ const copy = { ...obj };
56
+ for (const field of fields) {
57
+ delete copy[field];
58
+ }
59
+ return copy;
60
+ }
package/dist/cli.mjs ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SCXQ2 CLI
4
+ *
5
+ * Commands:
6
+ * verify <pack.json> - Verify pack integrity
7
+ * decode <pack.json> [--lane ID] - Decode block to stdout
8
+ * inspect <pack.json> - Print pack summary
9
+ *
10
+ * @version 1.0.0
11
+ */
12
+
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import { scxq2PackVerify, scxq2DecodeUtf16, SCXQ2_DEFAULT_POLICY } from "./verify.js";
16
+
17
+ /* =============================================================================
18
+ Helpers
19
+ ============================================================================= */
20
+
21
+ function usage() {
22
+ console.log(`
23
+ scxq2 <command> [args]
24
+
25
+ Commands:
26
+ verify <pack.json> Verify pack (default policy)
27
+ decode <pack.json> [--lane ID] Decode first block or matching lane_id
28
+ inspect <pack.json> Print pack summary (hashes, lanes, sizes)
29
+
30
+ Flags:
31
+ --no-roundtrip Skip roundtrip verification
32
+ --no-proof Skip proof verification
33
+ --maxOutputUnits N Set max output code units
34
+
35
+ Examples:
36
+ scxq2 verify mypack.json
37
+ scxq2 decode mypack.json --lane main
38
+ scxq2 inspect mypack.json
39
+ `);
40
+ process.exit(2);
41
+ }
42
+
43
+ function readJson(fp) {
44
+ try {
45
+ const s = fs.readFileSync(fp, "utf8");
46
+ return JSON.parse(s);
47
+ } catch (e) {
48
+ console.error(`Error reading ${fp}: ${e.message}`);
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ function parseArgs(argv) {
54
+ const out = { _: [] };
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const a = argv[i];
57
+ if (!a.startsWith("--")) {
58
+ out._.push(a);
59
+ } else {
60
+ const k = a.slice(2);
61
+ const v = (i + 1 < argv.length && !argv[i + 1].startsWith("--"))
62
+ ? argv[++i]
63
+ : true;
64
+ out[k] = v;
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+
70
+ function applyPolicyFlags(args) {
71
+ const p = { ...SCXQ2_DEFAULT_POLICY };
72
+ if (args["no-roundtrip"]) p.requireRoundtrip = false;
73
+ if (args["no-proof"]) p.requireProof = false;
74
+ if (args.maxOutputUnits) p.maxOutputUnits = Number(args.maxOutputUnits);
75
+ return p;
76
+ }
77
+
78
+ function b64ToBytes(b64) {
79
+ return Buffer.from(b64, "base64");
80
+ }
81
+
82
+ function formatBytes(bytes) {
83
+ if (bytes < 1024) return `${bytes} B`;
84
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
85
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
86
+ }
87
+
88
+ /* =============================================================================
89
+ Commands
90
+ ============================================================================= */
91
+
92
+ function cmdVerify(pack, policy) {
93
+ const res = scxq2PackVerify(pack, policy);
94
+ if (!res.ok) {
95
+ console.error(JSON.stringify(res, null, 2));
96
+ process.exit(1);
97
+ }
98
+ console.log(JSON.stringify(res, null, 2));
99
+ process.exit(0);
100
+ }
101
+
102
+ function cmdInspect(pack) {
103
+ const blocks = pack.blocks || [];
104
+ const lanes = blocks.map((b, i) => ({
105
+ index: i,
106
+ lane_id: b.lane_id ?? null,
107
+ b64_bytes: b.b64 ? Buffer.from(b.b64, "base64").length : null,
108
+ original_bytes: b.original_bytes_utf8 ?? null,
109
+ block_sha: b.block_sha256_canon?.slice(0, 16) + "..." ?? null
110
+ }));
111
+
112
+ const dictSize = pack.dict?.dict?.length ?? 0;
113
+ const totalB64 = blocks.reduce((acc, b) => acc + (b.b64 ? Buffer.from(b.b64, "base64").length : 0), 0);
114
+
115
+ const summary = {
116
+ pack_sha256_canon: pack.pack_sha256_canon ?? null,
117
+ dict_sha256_canon: pack.dict?.dict_sha256_canon ?? null,
118
+ dict_entries: dictSize,
119
+ blocks: lanes.length,
120
+ total_encoded_bytes: totalB64,
121
+ total_encoded_display: formatBytes(totalB64),
122
+ lanes
123
+ };
124
+
125
+ console.log(JSON.stringify(summary, null, 2));
126
+ process.exit(0);
127
+ }
128
+
129
+ function cmdDecode(pack, policy, laneId) {
130
+ const blocks = pack.blocks || [];
131
+
132
+ if (!blocks.length) {
133
+ console.error("Error: no blocks in pack");
134
+ process.exit(1);
135
+ }
136
+
137
+ let block = blocks[0];
138
+
139
+ if (laneId != null && laneId !== true) {
140
+ const hit = blocks.find(x => String(x.lane_id) === String(laneId));
141
+ if (!hit) {
142
+ console.error(`Error: lane not found: ${laneId}`);
143
+ console.error(`Available lanes: ${blocks.map(b => b.lane_id ?? "(unnamed)").join(", ")}`);
144
+ process.exit(1);
145
+ }
146
+ block = hit;
147
+ }
148
+
149
+ const bytes = b64ToBytes(block.b64);
150
+ const dec = scxq2DecodeUtf16(pack.dict.dict, bytes, { maxOutputUnits: policy.maxOutputUnits });
151
+
152
+ if (!dec.ok) {
153
+ console.error(JSON.stringify(dec, null, 2));
154
+ process.exit(1);
155
+ }
156
+
157
+ process.stdout.write(dec.value);
158
+ process.exit(0);
159
+ }
160
+
161
+ /* =============================================================================
162
+ Main
163
+ ============================================================================= */
164
+
165
+ const args = parseArgs(process.argv.slice(2));
166
+ const cmd = args._[0];
167
+ const file = args._[1];
168
+
169
+ if (!cmd) usage();
170
+ if (cmd === "help" || cmd === "--help" || cmd === "-h") usage();
171
+ if (!file) {
172
+ console.error("Error: missing pack file argument");
173
+ usage();
174
+ }
175
+
176
+ const pack = readJson(file);
177
+ const policy = applyPolicyFlags(args);
178
+
179
+ switch (cmd) {
180
+ case "verify":
181
+ cmdVerify(pack, policy);
182
+ break;
183
+ case "inspect":
184
+ cmdInspect(pack);
185
+ break;
186
+ case "decode":
187
+ cmdDecode(pack, policy, args.lane);
188
+ break;
189
+ default:
190
+ console.error(`Unknown command: ${cmd}`);
191
+ usage();
192
+ }