@aarmos/cli 0.1.1 → 0.1.3
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 +3 -1
- package/dist/index.js +393 -18
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Run your agent locally, across any protocol, with a signed receipt — in 60 seconds.
|
|
4
4
|
|
|
5
|
+
> **Not what you're looking for?** If you use the browser PWA at [aarmos.io](https://aarmos.io) and want it to reach your local files/shell/git, you want [`@aarmos/bridge`](https://www.npmjs.com/package/@aarmos/bridge) instead. This package is the terminal-native agent runtime.
|
|
6
|
+
|
|
5
7
|
```bash
|
|
6
8
|
npm i -g @aarmos/cli
|
|
7
9
|
aarmos init
|
|
@@ -16,7 +18,7 @@ aarmos run ./my-agent.js
|
|
|
16
18
|
| `aarmos run <agent>` | Run agent under policy — writes AVAR receipt on every execution |
|
|
17
19
|
| `aarmos proxy` | Transparent local HTTP proxy on 127.0.0.1 (any framework enrolls via `HTTPS_PROXY`) |
|
|
18
20
|
| `aarmos daemon start|stop|status` | Manage background proxy daemon |
|
|
19
|
-
| `aarmos verify <receipt>` |
|
|
21
|
+
| `aarmos verify <receipt>` | Verify an AVAR receipt locally (signatures + hash chain, via `@aarmos/avar-core`). Works with the daemon stopped. Zero network. |
|
|
20
22
|
|
|
21
23
|
## Guarantees
|
|
22
24
|
|
package/dist/index.js
CHANGED
|
@@ -299,29 +299,404 @@ function isRunning() {
|
|
|
299
299
|
// src/commands/verify.ts
|
|
300
300
|
import { Command as Command5 } from "commander";
|
|
301
301
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
302
|
+
import { unzipSync, strFromU8 } from "fflate";
|
|
303
|
+
|
|
304
|
+
// ../avar-core/src/canonicalize.ts
|
|
305
|
+
var NFC = (s) => s.normalize("NFC");
|
|
306
|
+
function encodeString(s) {
|
|
307
|
+
return JSON.stringify(NFC(s));
|
|
308
|
+
}
|
|
309
|
+
function encodeNumber(n) {
|
|
310
|
+
if (!Number.isFinite(n)) {
|
|
311
|
+
throw new Error(`avar-core/canonicalize: non-finite number (${n}) is forbidden.`);
|
|
312
|
+
}
|
|
313
|
+
return JSON.stringify(n);
|
|
314
|
+
}
|
|
315
|
+
function canonicalize(value) {
|
|
316
|
+
if (value === null) return "null";
|
|
317
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
318
|
+
if (typeof value === "number") return encodeNumber(value);
|
|
319
|
+
if (typeof value === "string") return encodeString(value);
|
|
320
|
+
if (typeof value === "undefined") {
|
|
321
|
+
throw new Error("avar-core/canonicalize: undefined is forbidden at value position.");
|
|
322
|
+
}
|
|
323
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
|
|
324
|
+
throw new Error(`avar-core/canonicalize: ${typeof value} is forbidden.`);
|
|
325
|
+
}
|
|
326
|
+
if (Array.isArray(value)) {
|
|
327
|
+
return "[" + value.map((v) => canonicalize(v)).join(",") + "]";
|
|
328
|
+
}
|
|
329
|
+
if (typeof value === "object") {
|
|
330
|
+
const obj = value;
|
|
331
|
+
const keys = [];
|
|
332
|
+
for (const k of Object.keys(obj)) {
|
|
333
|
+
if (obj[k] === void 0) continue;
|
|
334
|
+
keys.push(NFC(k));
|
|
335
|
+
}
|
|
336
|
+
keys.sort();
|
|
337
|
+
const parts = new Array(keys.length);
|
|
338
|
+
for (let i = 0; i < keys.length; i++) {
|
|
339
|
+
const k = keys[i];
|
|
340
|
+
const rawVal = obj[k] ?? findKey(obj, k);
|
|
341
|
+
parts[i] = encodeString(k) + ":" + canonicalize(rawVal);
|
|
342
|
+
}
|
|
343
|
+
return "{" + parts.join(",") + "}";
|
|
344
|
+
}
|
|
345
|
+
throw new Error(`avar-core/canonicalize: unsupported type ${typeof value}.`);
|
|
346
|
+
}
|
|
347
|
+
function findKey(obj, nfcKey) {
|
|
348
|
+
for (const k of Object.keys(obj)) {
|
|
349
|
+
if (NFC(k) === nfcKey) return obj[k];
|
|
350
|
+
}
|
|
351
|
+
return void 0;
|
|
352
|
+
}
|
|
353
|
+
function utf8(s) {
|
|
354
|
+
return new TextEncoder().encode(s);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ../avar-core/src/hash.ts
|
|
358
|
+
var GENESIS_PREV_HASH = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
359
|
+
function getSubtle() {
|
|
360
|
+
const c = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
361
|
+
if (!c || !c.subtle) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
"avar-core: WebCrypto SubtleCrypto is unavailable. Requires Node 20+ or a modern browser."
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return c.subtle;
|
|
367
|
+
}
|
|
368
|
+
async function sha256Hex(input) {
|
|
369
|
+
const bytes = typeof input === "string" ? utf8(input) : input;
|
|
370
|
+
const digest = await getSubtle().digest("SHA-256", bytes);
|
|
371
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
372
|
+
}
|
|
373
|
+
async function computeEntryHash(entry, prevHash) {
|
|
374
|
+
const withPrev = { ...entry, prevHash };
|
|
375
|
+
const {
|
|
376
|
+
entryHash: _e,
|
|
377
|
+
signature: _sig,
|
|
378
|
+
devicePubKey: _pk,
|
|
379
|
+
...rest
|
|
380
|
+
} = withPrev;
|
|
381
|
+
void _e;
|
|
382
|
+
void _sig;
|
|
383
|
+
void _pk;
|
|
384
|
+
return sha256Hex(prevHash + "\n" + canonicalize(rest));
|
|
385
|
+
}
|
|
386
|
+
async function computeStepHash(step, prevStepHash) {
|
|
387
|
+
const asRec = step;
|
|
388
|
+
const { stepHash: _s, prevStepHash: _p, ...rest } = asRec;
|
|
389
|
+
void _s;
|
|
390
|
+
void _p;
|
|
391
|
+
const withPrev = { ...rest, prevStepHash };
|
|
392
|
+
return sha256Hex(prevStepHash + "\n" + canonicalize(withPrev));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ../avar-core/src/signature.ts
|
|
396
|
+
function getSubtle2() {
|
|
397
|
+
const c = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
398
|
+
if (!c || !c.subtle) {
|
|
399
|
+
throw new Error("avar-core: WebCrypto SubtleCrypto is unavailable.");
|
|
400
|
+
}
|
|
401
|
+
return c.subtle;
|
|
402
|
+
}
|
|
403
|
+
function b64uDecode(s) {
|
|
404
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4);
|
|
405
|
+
const bin = typeof atob === "function" ? atob(b64) : globalThis.Buffer.from(b64, "base64").toString("binary");
|
|
406
|
+
const out = new Uint8Array(bin.length);
|
|
407
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
async function verifySignature(signedBody, signatureB64u, publicKeyB64u) {
|
|
411
|
+
try {
|
|
412
|
+
const raw = b64uDecode(publicKeyB64u);
|
|
413
|
+
const key = await getSubtle2().importKey("raw", raw, "Ed25519", false, [
|
|
414
|
+
"verify"
|
|
415
|
+
]);
|
|
416
|
+
const sig = b64uDecode(signatureB64u);
|
|
417
|
+
const bytes = utf8(canonicalize(signedBody));
|
|
418
|
+
return await getSubtle2().verify(
|
|
419
|
+
"Ed25519",
|
|
420
|
+
key,
|
|
421
|
+
sig,
|
|
422
|
+
bytes
|
|
423
|
+
);
|
|
424
|
+
} catch {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async function computeDeviceFingerprint(publicKeyB64u) {
|
|
429
|
+
const hex = await sha256Hex(utf8(publicKeyB64u));
|
|
430
|
+
return hex.slice(0, 12);
|
|
431
|
+
}
|
|
432
|
+
function signedBodyOf(entry) {
|
|
433
|
+
const { signature: _sig, devicePubKey: _pk, ...rest } = entry;
|
|
434
|
+
void _sig;
|
|
435
|
+
void _pk;
|
|
436
|
+
return rest;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ../avar-core/src/verify.ts
|
|
440
|
+
var SPEC_VERSION = "avar/1";
|
|
441
|
+
async function verifyBundle(bundle) {
|
|
442
|
+
const issues = [];
|
|
443
|
+
let formatOk = true;
|
|
444
|
+
const specV = bundle.specVersion.trim();
|
|
445
|
+
if (specV !== SPEC_VERSION) {
|
|
446
|
+
formatOk = false;
|
|
447
|
+
issues.push({
|
|
448
|
+
index: -1,
|
|
449
|
+
kind: "spec-version-mismatch",
|
|
450
|
+
detail: `Expected "${SPEC_VERSION}", got "${specV}".`
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (bundle.manifest.format !== SPEC_VERSION) {
|
|
454
|
+
formatOk = false;
|
|
455
|
+
issues.push({
|
|
456
|
+
index: -1,
|
|
457
|
+
kind: "manifest-invalid",
|
|
458
|
+
detail: `manifest.format expected "${SPEC_VERSION}", got "${bundle.manifest.format}".`
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
const actualSha = await sha256Hex(bundle.entriesNdjsonBytes);
|
|
462
|
+
const entriesSha256Ok = actualSha === bundle.manifest.entriesSha256;
|
|
463
|
+
if (!entriesSha256Ok) {
|
|
464
|
+
issues.push({
|
|
465
|
+
index: -1,
|
|
466
|
+
kind: "entries-sha256-mismatch",
|
|
467
|
+
detail: `Expected ${bundle.manifest.entriesSha256}, got ${actualSha}.`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const entries = bundle.entries;
|
|
471
|
+
const entryCount = entries.length;
|
|
472
|
+
let signaturesOk = true;
|
|
473
|
+
let fingerprintsOk = true;
|
|
474
|
+
let signedCount = 0;
|
|
475
|
+
let unsignedCount = 0;
|
|
476
|
+
for (let i = 0; i < entries.length; i++) {
|
|
477
|
+
const e = entries[i];
|
|
478
|
+
const hasSig = typeof e.signature === "string" && typeof e.devicePubKey === "string";
|
|
479
|
+
if (!hasSig) {
|
|
480
|
+
unsignedCount++;
|
|
481
|
+
continue;
|
|
309
482
|
}
|
|
310
|
-
|
|
483
|
+
signedCount++;
|
|
484
|
+
if (typeof e.deviceFingerprint === "string") {
|
|
485
|
+
const expected = await computeDeviceFingerprint(e.devicePubKey);
|
|
486
|
+
if (expected !== e.deviceFingerprint) {
|
|
487
|
+
fingerprintsOk = false;
|
|
488
|
+
issues.push({
|
|
489
|
+
index: i,
|
|
490
|
+
kind: "fingerprint-mismatch",
|
|
491
|
+
detail: `Expected ${expected}, got ${e.deviceFingerprint}.`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const ok = await verifySignature(signedBodyOf(e), e.signature, e.devicePubKey);
|
|
496
|
+
if (!ok) {
|
|
497
|
+
signaturesOk = false;
|
|
498
|
+
issues.push({ index: i, kind: "signature-invalid" });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
let chainOk = true;
|
|
502
|
+
let unchainedCount = 0;
|
|
503
|
+
let expectedPrev = GENESIS_PREV_HASH;
|
|
504
|
+
for (let i = 0; i < entries.length; i++) {
|
|
505
|
+
const e = entries[i];
|
|
506
|
+
const isLegacyUnchained = !e.entryHash || !e.prevHash;
|
|
507
|
+
if (isLegacyUnchained) {
|
|
508
|
+
unchainedCount++;
|
|
509
|
+
expectedPrev = GENESIS_PREV_HASH;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (e.prevHash !== expectedPrev) {
|
|
513
|
+
chainOk = false;
|
|
514
|
+
issues.push({
|
|
515
|
+
index: i,
|
|
516
|
+
kind: "chain-broken",
|
|
517
|
+
detail: `prevHash mismatch at entry ${i}.`
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
const recomputed = await computeEntryHash(e, e.prevHash);
|
|
521
|
+
if (recomputed !== e.entryHash) {
|
|
522
|
+
chainOk = false;
|
|
523
|
+
issues.push({
|
|
524
|
+
index: i,
|
|
525
|
+
kind: "chain-broken",
|
|
526
|
+
detail: `entryHash mismatch at entry ${i} (body modified after signing).`
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
expectedPrev = e.entryHash;
|
|
530
|
+
}
|
|
531
|
+
const perStepChainOk = await verifyAllStepChains(entries, issues);
|
|
532
|
+
const chainHead = computeChainHead(entries);
|
|
533
|
+
const anyHardFail = !formatOk || !entriesSha256Ok || !chainOk || !perStepChainOk || !signaturesOk || !fingerprintsOk;
|
|
534
|
+
const verdict = anyHardFail ? "invalid" : unsignedCount > 0 || unchainedCount > 0 ? "valid-with-warnings" : "valid";
|
|
535
|
+
return {
|
|
536
|
+
formatOk,
|
|
537
|
+
entriesSha256Ok,
|
|
538
|
+
chainOk,
|
|
539
|
+
perStepChainOk,
|
|
540
|
+
signaturesOk,
|
|
541
|
+
fingerprintsOk,
|
|
542
|
+
entryCount,
|
|
543
|
+
signedCount,
|
|
544
|
+
unsignedCount,
|
|
545
|
+
unchainedCount,
|
|
546
|
+
chainHead,
|
|
547
|
+
issues,
|
|
548
|
+
verdict
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async function verifyAllStepChains(entries, issues) {
|
|
552
|
+
let ok = true;
|
|
553
|
+
for (let i = 0; i < entries.length; i++) {
|
|
554
|
+
const e = entries[i];
|
|
555
|
+
if (!Array.isArray(e.steps) || e.steps.length === 0) continue;
|
|
556
|
+
const anyChained = e.steps.some(hasChainFields);
|
|
557
|
+
const allChained = e.steps.every(hasChainFields);
|
|
558
|
+
if (anyChained && !allChained) {
|
|
559
|
+
ok = false;
|
|
560
|
+
issues.push({ index: i, kind: "partial-step-chain" });
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (!anyChained) continue;
|
|
564
|
+
let prev = "step-genesis:0000000000000000000000000000000000000000000000000000000000000000";
|
|
565
|
+
for (let j = 0; j < e.steps.length; j++) {
|
|
566
|
+
const s = e.steps[j];
|
|
567
|
+
if (s.prevStepHash !== prev) {
|
|
568
|
+
ok = false;
|
|
569
|
+
issues.push({
|
|
570
|
+
index: i,
|
|
571
|
+
kind: "step-chain-broken",
|
|
572
|
+
detail: `entry ${i} step ${j}: prevStepHash mismatch.`
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
const recomputed = await computeStepHash(s, s.prevStepHash);
|
|
576
|
+
if (recomputed !== s.stepHash) {
|
|
577
|
+
ok = false;
|
|
578
|
+
issues.push({
|
|
579
|
+
index: i,
|
|
580
|
+
kind: "step-chain-broken",
|
|
581
|
+
detail: `entry ${i} step ${j}: stepHash mismatch (step body modified).`
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
prev = s.stepHash;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return ok;
|
|
588
|
+
}
|
|
589
|
+
function hasChainFields(s) {
|
|
590
|
+
return typeof s.prevStepHash === "string" && typeof s.stepHash === "string";
|
|
591
|
+
}
|
|
592
|
+
function computeChainHead(entries) {
|
|
593
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
594
|
+
const e = entries[i];
|
|
595
|
+
if (e.entryHash) return { entryHash: e.entryHash, index: i };
|
|
596
|
+
}
|
|
597
|
+
return { entryHash: "", index: -1 };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/commands/verify.ts
|
|
601
|
+
async function runVerify(opts) {
|
|
602
|
+
const { file, json = false, quiet = false } = opts;
|
|
603
|
+
if (!existsSync5(file)) {
|
|
604
|
+
if (!quiet) process.stderr.write(`\u2717 receipt not found: ${file}
|
|
605
|
+
`);
|
|
606
|
+
return 2;
|
|
607
|
+
}
|
|
608
|
+
const bytes = readFileSync4(file);
|
|
609
|
+
const isZip = looksLikeZip(bytes);
|
|
610
|
+
if (!isZip) {
|
|
311
611
|
try {
|
|
312
|
-
const parsed = JSON.parse(
|
|
612
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
313
613
|
const entries = Array.isArray(parsed.entries) ? parsed.entries.length : 0;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
614
|
+
if (json) {
|
|
615
|
+
process.stdout.write(
|
|
616
|
+
JSON.stringify({
|
|
617
|
+
verdict: "parsed-only",
|
|
618
|
+
format: "json",
|
|
619
|
+
entryCount: entries,
|
|
620
|
+
note: "JSON receipt parsed; full verification requires an .avar.zip bundle."
|
|
621
|
+
}) + "\n"
|
|
622
|
+
);
|
|
623
|
+
} else if (!quiet) {
|
|
624
|
+
process.stdout.write(`\u2713 receipt parsed (${entries} entr${entries === 1 ? "y" : "ies"})
|
|
625
|
+
`);
|
|
626
|
+
process.stdout.write(` spec_version: ${parsed.spec_version ?? "unknown"}
|
|
627
|
+
`);
|
|
628
|
+
process.stdout.write(` signature_alg: ${parsed.signature_alg ?? "unknown"}
|
|
629
|
+
`);
|
|
630
|
+
process.stdout.write(
|
|
631
|
+
"\nNote: JSON receipts are parse-only. For full signature + hash-chain\nverification, export an .avar.zip bundle.\n"
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
return 0;
|
|
321
635
|
} catch (err) {
|
|
322
|
-
|
|
323
|
-
|
|
636
|
+
if (!quiet) process.stderr.write(`\u2717 invalid JSON: ${String(err)}
|
|
637
|
+
`);
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
let bundle;
|
|
642
|
+
try {
|
|
643
|
+
bundle = parseBundleBytes(bytes);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
if (!quiet) process.stderr.write(`\u2717 malformed .avar.zip: ${String(err)}
|
|
646
|
+
`);
|
|
647
|
+
return 1;
|
|
648
|
+
}
|
|
649
|
+
const report = await verifyBundle(bundle);
|
|
650
|
+
const failed = report.verdict === "invalid";
|
|
651
|
+
if (json) {
|
|
652
|
+
process.stdout.write(JSON.stringify(report) + "\n");
|
|
653
|
+
} else if (!quiet) {
|
|
654
|
+
const mark = failed ? "\u2717" : "\u2713";
|
|
655
|
+
process.stdout.write(`${mark} verdict: ${report.verdict}
|
|
656
|
+
`);
|
|
657
|
+
process.stdout.write(` entries: ${report.entryCount}
|
|
658
|
+
`);
|
|
659
|
+
process.stdout.write(` signed: ${report.signedCount}
|
|
660
|
+
`);
|
|
661
|
+
process.stdout.write(` unsigned: ${report.unsignedCount}
|
|
662
|
+
`);
|
|
663
|
+
process.stdout.write(` unchained: ${report.unchainedCount}
|
|
664
|
+
`);
|
|
665
|
+
if (report.issues.length) {
|
|
666
|
+
process.stdout.write(` issues:
|
|
667
|
+
`);
|
|
668
|
+
for (const issue of report.issues) {
|
|
669
|
+
process.stdout.write(` - [${issue.kind}] ${issue.detail}
|
|
670
|
+
`);
|
|
671
|
+
}
|
|
324
672
|
}
|
|
673
|
+
process.stdout.write("\nverified locally \u2014 no Aarmos service was contacted.\n");
|
|
674
|
+
}
|
|
675
|
+
return failed ? 1 : 0;
|
|
676
|
+
}
|
|
677
|
+
function looksLikeZip(bytes) {
|
|
678
|
+
return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
|
|
679
|
+
}
|
|
680
|
+
function parseBundleBytes(bytes) {
|
|
681
|
+
const files = unzipSync(bytes);
|
|
682
|
+
const requireFile = (name) => {
|
|
683
|
+
const f = files[name];
|
|
684
|
+
if (!f) throw new Error(`missing ${name} in bundle`);
|
|
685
|
+
return f;
|
|
686
|
+
};
|
|
687
|
+
const specVersion = strFromU8(requireFile("SPEC-VERSION")).trim();
|
|
688
|
+
const manifest = JSON.parse(strFromU8(requireFile("manifest.json")));
|
|
689
|
+
const pubkeys = JSON.parse(strFromU8(requireFile("pubkeys.json")));
|
|
690
|
+
const entriesNdjsonBytes = requireFile("entries.ndjson");
|
|
691
|
+
const entries = strFromU8(entriesNdjsonBytes).split("\n").filter((l) => l.length > 0).map((l) => JSON.parse(l));
|
|
692
|
+
return { specVersion, manifest, entriesNdjsonBytes, entries, pubkeys };
|
|
693
|
+
}
|
|
694
|
+
function verifyCommand() {
|
|
695
|
+
return new Command5("verify").description(
|
|
696
|
+
"Verify an AVAR receipt (.avar.zip bundle or legacy .json) locally \u2014 signatures + hash chain via @aarmos/avar-core. Works with the Aarmos daemon stopped. Zero network."
|
|
697
|
+
).argument("<receipt>", "Path to AVAR receipt (.avar.zip or .json)").option("--json", "Emit machine-readable JSON report on stdout").option("--quiet", "Suppress non-error output; exit code carries the verdict").action(async (receipt, opts) => {
|
|
698
|
+
const rc = await runVerify({ file: receipt, json: opts.json, quiet: opts.quiet });
|
|
699
|
+
process.exit(rc);
|
|
325
700
|
});
|
|
326
701
|
}
|
|
327
702
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aarmos/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Aarmos CLI — run any agent locally, across any protocol (MCP, OpenAPI, deep-link), with a signed AVAR receipt on every execution. Sovereign runtime, universal tool gateway, verifiable governance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aarmos",
|
|
@@ -36,14 +36,16 @@
|
|
|
36
36
|
"author": "Aarmatix LLC",
|
|
37
37
|
"license": "Apache-2.0",
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@aarmos/avar-core": "^1.0.0-rc.3",
|
|
39
40
|
"commander": "^12.1.0",
|
|
41
|
+
"fflate": "^0.8.3",
|
|
40
42
|
"zod": "^3.23.8"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^22.5.0",
|
|
44
|
-
"tsup": "^8.
|
|
46
|
+
"tsup": "^8.5.1",
|
|
45
47
|
"tsx": "^4.19.0",
|
|
46
|
-
"typescript": "^
|
|
48
|
+
"typescript": "^6.0.3"
|
|
47
49
|
},
|
|
48
50
|
"engines": {
|
|
49
51
|
"node": ">=18.17"
|