@arcis/node 1.4.4 → 1.5.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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -6
  3. package/dist/astro/index.js +6141 -0
  4. package/dist/astro/index.js.map +1 -0
  5. package/dist/astro/index.mjs +6136 -0
  6. package/dist/astro/index.mjs.map +1 -0
  7. package/dist/bun/index.js +6195 -0
  8. package/dist/bun/index.js.map +1 -0
  9. package/dist/bun/index.mjs +6189 -0
  10. package/dist/bun/index.mjs.map +1 -0
  11. package/dist/core/constants.d.ts +3 -2
  12. package/dist/core/constants.d.ts.map +1 -1
  13. package/dist/core/index.js +4 -3
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/index.mjs +4 -3
  16. package/dist/core/index.mjs.map +1 -1
  17. package/dist/core/types.d.ts +32 -0
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/fastify/index.js +6160 -0
  20. package/dist/fastify/index.js.map +1 -0
  21. package/dist/fastify/index.mjs +6155 -0
  22. package/dist/fastify/index.mjs.map +1 -0
  23. package/dist/guards.d.ts +156 -0
  24. package/dist/guards.d.ts.map +1 -0
  25. package/dist/hono/index.js +6159 -0
  26. package/dist/hono/index.js.map +1 -0
  27. package/dist/hono/index.mjs +6154 -0
  28. package/dist/hono/index.mjs.map +1 -0
  29. package/dist/index.d.ts +23 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +7126 -178
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +7088 -179
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/koa/index.js +6158 -0
  36. package/dist/koa/index.js.map +1 -0
  37. package/dist/koa/index.mjs +6153 -0
  38. package/dist/koa/index.mjs.map +1 -0
  39. package/dist/logging/index.js.map +1 -1
  40. package/dist/logging/index.mjs.map +1 -1
  41. package/dist/logging/redactor.d.ts.map +1 -1
  42. package/dist/middleware/astro.d.ts +64 -0
  43. package/dist/middleware/astro.d.ts.map +1 -0
  44. package/dist/middleware/bot-detection.d.ts.map +1 -1
  45. package/dist/middleware/bun.d.ts +75 -0
  46. package/dist/middleware/bun.d.ts.map +1 -0
  47. package/dist/middleware/csrf.d.ts.map +1 -1
  48. package/dist/middleware/error-handler.d.ts.map +1 -1
  49. package/dist/middleware/fastify.d.ts +89 -0
  50. package/dist/middleware/fastify.d.ts.map +1 -0
  51. package/dist/middleware/graphql.d.ts +35 -0
  52. package/dist/middleware/graphql.d.ts.map +1 -0
  53. package/dist/middleware/hono.d.ts +63 -0
  54. package/dist/middleware/hono.d.ts.map +1 -0
  55. package/dist/middleware/index.d.ts +12 -0
  56. package/dist/middleware/index.d.ts.map +1 -1
  57. package/dist/middleware/index.js +6469 -119
  58. package/dist/middleware/index.js.map +1 -1
  59. package/dist/middleware/index.mjs +6459 -120
  60. package/dist/middleware/index.mjs.map +1 -1
  61. package/dist/middleware/koa.d.ts +84 -0
  62. package/dist/middleware/koa.d.ts.map +1 -0
  63. package/dist/middleware/main.d.ts +0 -30
  64. package/dist/middleware/main.d.ts.map +1 -1
  65. package/dist/middleware/mass-assign.d.ts +81 -0
  66. package/dist/middleware/mass-assign.d.ts.map +1 -0
  67. package/dist/middleware/method-allowlist.d.ts +66 -0
  68. package/dist/middleware/method-allowlist.d.ts.map +1 -0
  69. package/dist/middleware/nestjs.d.ts +62 -0
  70. package/dist/middleware/nestjs.d.ts.map +1 -0
  71. package/dist/middleware/nextjs.d.ts +102 -0
  72. package/dist/middleware/nextjs.d.ts.map +1 -0
  73. package/dist/middleware/nuxt.d.ts +61 -0
  74. package/dist/middleware/nuxt.d.ts.map +1 -0
  75. package/dist/middleware/overload.d.ts +92 -0
  76. package/dist/middleware/overload.d.ts.map +1 -0
  77. package/dist/middleware/protect.d.ts +91 -0
  78. package/dist/middleware/protect.d.ts.map +1 -0
  79. package/dist/middleware/rate-limit-sliding.d.ts.map +1 -1
  80. package/dist/middleware/rate-limit-token.d.ts.map +1 -1
  81. package/dist/middleware/rate-limit.d.ts.map +1 -1
  82. package/dist/middleware/response-splitting.d.ts +83 -0
  83. package/dist/middleware/response-splitting.d.ts.map +1 -0
  84. package/dist/middleware/sveltekit.d.ts +68 -0
  85. package/dist/middleware/sveltekit.d.ts.map +1 -0
  86. package/dist/middleware/token-budget.d.ts +75 -0
  87. package/dist/middleware/token-budget.d.ts.map +1 -0
  88. package/dist/nestjs/index.js +1724 -0
  89. package/dist/nestjs/index.js.map +1 -0
  90. package/dist/nestjs/index.mjs +1717 -0
  91. package/dist/nestjs/index.mjs.map +1 -0
  92. package/dist/nextjs/index.js +6184 -0
  93. package/dist/nextjs/index.js.map +1 -0
  94. package/dist/nextjs/index.mjs +6178 -0
  95. package/dist/nextjs/index.mjs.map +1 -0
  96. package/dist/nuxt/index.js +6141 -0
  97. package/dist/nuxt/index.js.map +1 -0
  98. package/dist/nuxt/index.mjs +6136 -0
  99. package/dist/nuxt/index.mjs.map +1 -0
  100. package/dist/sanitizers/encode.d.ts.map +1 -1
  101. package/dist/sanitizers/graphql.d.ts +72 -0
  102. package/dist/sanitizers/graphql.d.ts.map +1 -0
  103. package/dist/sanitizers/headers.d.ts +18 -0
  104. package/dist/sanitizers/headers.d.ts.map +1 -1
  105. package/dist/sanitizers/index.d.ts +4 -1
  106. package/dist/sanitizers/index.d.ts.map +1 -1
  107. package/dist/sanitizers/index.js +140 -66
  108. package/dist/sanitizers/index.js.map +1 -1
  109. package/dist/sanitizers/index.mjs +135 -67
  110. package/dist/sanitizers/index.mjs.map +1 -1
  111. package/dist/sanitizers/prompt-injection.d.ts +62 -0
  112. package/dist/sanitizers/prompt-injection.d.ts.map +1 -0
  113. package/dist/sanitizers/sanitize.d.ts +1 -1
  114. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  115. package/dist/sanitizers/xpath.d.ts +37 -0
  116. package/dist/sanitizers/xpath.d.ts.map +1 -0
  117. package/dist/stores/index.js +4 -4
  118. package/dist/stores/index.js.map +1 -1
  119. package/dist/stores/index.mjs +4 -4
  120. package/dist/stores/index.mjs.map +1 -1
  121. package/dist/stores/redis.d.ts +7 -1
  122. package/dist/stores/redis.d.ts.map +1 -1
  123. package/dist/sveltekit/index.js +6142 -0
  124. package/dist/sveltekit/index.js.map +1 -0
  125. package/dist/sveltekit/index.mjs +6137 -0
  126. package/dist/sveltekit/index.mjs.map +1 -0
  127. package/dist/validation/index.d.ts +2 -0
  128. package/dist/validation/index.d.ts.map +1 -1
  129. package/dist/validation/index.js +137 -12
  130. package/dist/validation/index.js.map +1 -1
  131. package/dist/validation/index.mjs +116 -13
  132. package/dist/validation/index.mjs.map +1 -1
  133. package/dist/validation/redirect.d.ts.map +1 -1
  134. package/dist/validation/schema.d.ts.map +1 -1
  135. package/dist/validation/url-async.d.ts +137 -0
  136. package/dist/validation/url-async.d.ts.map +1 -0
  137. package/package.json +52 -7
  138. package/scripts/postinstall.cjs +26 -0
  139. package/dist/cli/arcis.d.ts +0 -23
  140. package/dist/cli/arcis.d.ts.map +0 -1
  141. package/dist/cli/arcis.js +0 -312
  142. package/dist/cli/arcis.js.map +0 -1
  143. package/dist/cli/arcis.mjs +0 -309
  144. package/dist/cli/arcis.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/validation/redirect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mEAAmE;IACnE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,oCAAoC;AACpC,MAAM,WAAW,sBAAsB;IACrC,uCAAuC;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAqExB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAE1F"}
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/validation/redirect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mEAAmE;IACnE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,oCAAoC;AACpC,MAAM,WAAW,sBAAsB;IACrC,uCAAuC;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CA2ExB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAE1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/validation/schema.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,eAAe,CAAC;AAGtE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,MAAM,GAAG,OAAO,GAAG,QAAiB,GAC3C,cAAc,CA0BhB;AAoKD;;;GAGG;AACH,eAAO,MAAM,eAAe,iBAAW,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/validation/schema.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,eAAe,CAAC;AAGtE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,MAAM,GAAG,OAAO,GAAG,QAAiB,GAC3C,cAAc,CAkChB;AAoKD;;;GAGG;AACH,eAAO,MAAM,eAAe,iBAAW,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @module @arcis/node/validation/url-async
3
+ *
4
+ * Async SSRF guard that closes the DNS-rebinding TOCTOU gap left open
5
+ * by the synchronous `validateUrl` (sdk-vectors.md #31, issue #50).
6
+ *
7
+ * The synchronous `validateUrl` only checks the *string form* of the
8
+ * hostname. It catches obvious cases — `127.0.0.1`, `10.0.0.1`,
9
+ * `169.254.169.254` — but a hostname like `evil.com` passes through
10
+ * even when its DNS A-record points to `10.0.0.1`, because resolution
11
+ * happens later inside `fetch`. An attacker controlling the
12
+ * `evil.com` zone can also rebind the answer between Arcis's check
13
+ * and the actual TCP connect, which is the classic DNS TOCTOU.
14
+ *
15
+ * Two layers of fix shipped here:
16
+ *
17
+ * 1. **`validateUrlAsync(url, options)`** — runs the existing sync
18
+ * `validateUrl` first, then `dns.lookup(hostname, { all: true })`,
19
+ * then re-runs the same private-range check on every resolved
20
+ * address. Returns the pinned IP list so callers can reuse it for
21
+ * the actual connection (closing the TOCTOU window).
22
+ *
23
+ * 2. **`pinnedDnsLookup(ip)`** — returns a Node `lookup` callback
24
+ * that resolves any hostname to the pre-validated IP. Wire this
25
+ * into `https.request({ lookup })` / `http.request({ lookup })`
26
+ * so the connection uses the IP Arcis already validated, not
27
+ * whatever DNS returns at connect time. Pure stdlib — no undici,
28
+ * no extra dep.
29
+ *
30
+ * 3. **`safeFollowRedirect(prev, location, options)`** — when the
31
+ * server replies 30x, run the same async guard against the new
32
+ * Location URL. Resolves the absolute URL using the previous
33
+ * response URL as base. Caller decides whether to follow.
34
+ *
35
+ * The function signatures keep `lookup` injectable so tests can
36
+ * substitute a fake resolver without monkey-patching `node:dns`.
37
+ *
38
+ * ```ts
39
+ * import https from 'node:https';
40
+ * import { validateUrlAsync, pinnedDnsLookup } from '@arcis/node';
41
+ *
42
+ * const result = await validateUrlAsync(url);
43
+ * if (!result.safe) throw new Error(result.reason);
44
+ *
45
+ * https.get(url, { lookup: pinnedDnsLookup(result.resolvedIp!) }, (res) => {
46
+ * // The TCP connect now goes to result.resolvedIp regardless of
47
+ * // what DNS would say at this exact moment.
48
+ * });
49
+ * ```
50
+ */
51
+ import { type ValidateUrlOptions, type ValidateUrlResult } from './url';
52
+ /**
53
+ * Subset of `dns.lookup`'s `{ all: true }` callback signature. Kept
54
+ * narrow so a test fake can satisfy it without depending on Node's
55
+ * full `LookupAddress` type.
56
+ */
57
+ export type LookupAddress = {
58
+ address: string;
59
+ family: number;
60
+ };
61
+ /**
62
+ * Function shape compatible with `dns.lookup(hostname, { all: true })`.
63
+ * Returns a list of resolved addresses. Tests inject a fake.
64
+ */
65
+ export type DnsLookup = (hostname: string) => Promise<LookupAddress[]>;
66
+ export interface ValidateUrlAsyncOptions extends ValidateUrlOptions {
67
+ /**
68
+ * DNS lookup function. Defaults to a Promise wrapper around
69
+ * `dns.lookup(hostname, { all: true })`. Tests inject a stub.
70
+ */
71
+ lookup?: DnsLookup;
72
+ /**
73
+ * If true, accept the first non-private IP and ignore the rest.
74
+ * Default false: every resolved IP must pass the private-range
75
+ * check. Hosts with mixed-public/private answers (round-robin DNS
76
+ * with one internal record) still fail-closed.
77
+ */
78
+ acceptFirstPublic?: boolean;
79
+ }
80
+ export interface ValidateUrlAsyncResult extends ValidateUrlResult {
81
+ /**
82
+ * Single pinned IP (the first public address if all checks passed,
83
+ * or undefined when the string-only synchronous validator already
84
+ * decided — e.g., the hostname *was* a literal IP). Use this with
85
+ * `pinnedDnsLookup()` to wire the actual fetch.
86
+ */
87
+ resolvedIp?: string;
88
+ /** Every IP returned by DNS, in resolver order. */
89
+ resolvedIps?: string[];
90
+ }
91
+ /**
92
+ * Async SSRF guard with DNS resolution. Runs the sync validator
93
+ * first, then resolves DNS and validates every returned IP against
94
+ * the same private-range rules. Returns a pinned IP for the caller
95
+ * to reuse.
96
+ *
97
+ * Failure modes (any returns `{ safe: false, reason }`):
98
+ * - Sync validator already rejects (string-pattern fail).
99
+ * - DNS lookup throws (NXDOMAIN, network error). Reason carries the
100
+ * underlying error message.
101
+ * - DNS returns no addresses.
102
+ * - Any resolved address fails the private-range check (default) or
103
+ * *all* fail it when `acceptFirstPublic` is true.
104
+ */
105
+ export declare function validateUrlAsync(url: string, options?: ValidateUrlAsyncOptions): Promise<ValidateUrlAsyncResult>;
106
+ /**
107
+ * Build a `lookup` callback that pins the resolution to a single
108
+ * pre-validated IP, regardless of what DNS would say at connect time.
109
+ * Drop into `https.request({ lookup })` / `http.request({ lookup })`.
110
+ *
111
+ * Closes the TOCTOU window between `validateUrlAsync` and the actual
112
+ * TCP connect. The pinned IP must be one that already passed the
113
+ * async validator — wiring it without that check defeats the purpose.
114
+ *
115
+ * The returned function matches Node's `dns.lookup` callback shape
116
+ * for the `{ all: false, family: 0 }` case (single address). The
117
+ * default Node http/https stack uses that shape.
118
+ */
119
+ export declare function pinnedDnsLookup(ip: string): (hostname: string, options: {
120
+ family?: number;
121
+ hints?: number;
122
+ verbatim?: boolean;
123
+ }, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void) => void;
124
+ /**
125
+ * Validate a redirect target with the same TOCTOU-aware pipeline.
126
+ *
127
+ * `prev` is the URL of the response that returned the 30x; `location`
128
+ * is the raw `Location:` header value (which may be relative). The
129
+ * function resolves `location` against `prev` per RFC 3986 then runs
130
+ * `validateUrlAsync` on the absolute result.
131
+ *
132
+ * Use this on every hop of a redirect chain. Without it, a server
133
+ * that you trust today can redirect tomorrow's request to
134
+ * `http://169.254.169.254/`.
135
+ */
136
+ export declare function safeFollowRedirect(prev: string, location: string, options?: ValidateUrlAsyncOptions): Promise<ValidateUrlAsyncResult>;
137
+ //# sourceMappingURL=url-async.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-async.d.ts","sourceRoot":"","sources":["../../src/validation/url-async.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAGH,OAAO,EAAe,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAErF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AAUvE,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AA0BD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,sBAAsB,CAAC,CA4DjC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,GACT,CACD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EAChE,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,KACnF,IAAI,CAQR;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAWjC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcis/node",
3
- "version": "1.4.4",
4
- "description": "Zero-dependency security middleware for Node.js. Protects against XSS, SQL injection, CSRF, SSRF, HPP, rate limiting, bot detection, and 15+ more attack types. Supply chain attack scanner included.",
3
+ "version": "1.5.0",
4
+ "description": "Inside-the-app security middleware for Node.js. One install protects Express, NestJS, SvelteKit, Astro, Nuxt, Bun + Hono against XSS, SQL injection, CSRF, SSRF, HPP, prompt injection, bot traffic, rate limiting, and 20+ more attack types. Includes prompt-injection signature library, LLM token-budget middleware, 646-pattern bot corpus, and the @arcis/cli supply-chain scanner.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -50,14 +50,57 @@
50
50
  "types": "./dist/telemetry/index.d.ts",
51
51
  "import": "./dist/telemetry/index.mjs",
52
52
  "require": "./dist/telemetry/index.js"
53
+ },
54
+ "./fastify": {
55
+ "types": "./dist/fastify/index.d.ts",
56
+ "import": "./dist/fastify/index.mjs",
57
+ "require": "./dist/fastify/index.js"
58
+ },
59
+ "./koa": {
60
+ "types": "./dist/koa/index.d.ts",
61
+ "import": "./dist/koa/index.mjs",
62
+ "require": "./dist/koa/index.js"
63
+ },
64
+ "./nestjs": {
65
+ "types": "./dist/nestjs/index.d.ts",
66
+ "import": "./dist/nestjs/index.mjs",
67
+ "require": "./dist/nestjs/index.js"
68
+ },
69
+ "./nextjs": {
70
+ "types": "./dist/nextjs/index.d.ts",
71
+ "import": "./dist/nextjs/index.mjs",
72
+ "require": "./dist/nextjs/index.js"
73
+ },
74
+ "./sveltekit": {
75
+ "types": "./dist/sveltekit/index.d.ts",
76
+ "import": "./dist/sveltekit/index.mjs",
77
+ "require": "./dist/sveltekit/index.js"
78
+ },
79
+ "./astro": {
80
+ "types": "./dist/astro/index.d.ts",
81
+ "import": "./dist/astro/index.mjs",
82
+ "require": "./dist/astro/index.js"
83
+ },
84
+ "./nuxt": {
85
+ "types": "./dist/nuxt/index.d.ts",
86
+ "import": "./dist/nuxt/index.mjs",
87
+ "require": "./dist/nuxt/index.js"
88
+ },
89
+ "./bun": {
90
+ "types": "./dist/bun/index.d.ts",
91
+ "import": "./dist/bun/index.mjs",
92
+ "require": "./dist/bun/index.js"
93
+ },
94
+ "./hono": {
95
+ "types": "./dist/hono/index.d.ts",
96
+ "import": "./dist/hono/index.mjs",
97
+ "require": "./dist/hono/index.js"
53
98
  }
54
99
  },
55
100
  "files": [
56
- "dist"
101
+ "dist",
102
+ "scripts/postinstall.cjs"
57
103
  ],
58
- "bin": {
59
- "arcis": "./dist/cli/arcis.mjs"
60
- },
61
104
  "scripts": {
62
105
  "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap",
63
106
  "dev": "tsup --watch",
@@ -68,7 +111,8 @@
68
111
  "test:coverage": "vitest --coverage",
69
112
  "lint": "eslint src",
70
113
  "typecheck": "tsc --noEmit",
71
- "prepublishOnly": "npm run build"
114
+ "prepublishOnly": "npm run build",
115
+ "postinstall": "node scripts/postinstall.cjs"
72
116
  },
73
117
  "keywords": [
74
118
  "security",
@@ -100,6 +144,7 @@
100
144
  "express": ">=4.0.0"
101
145
  },
102
146
  "devDependencies": {
147
+ "@nestjs/common": "^11.1.19",
103
148
  "@types/express": "^5.0.6",
104
149
  "@types/node": "^25.5.0",
105
150
  "@typescript-eslint/eslint-plugin": "^8.58.0",
@@ -0,0 +1,26 @@
1
+ /* eslint-disable */
2
+ // Postinstall notice for @arcis/node.
3
+ //
4
+ // Surface the fact that the Arcis CLI (audit / scan / sca) ships as a
5
+ // separate native binary. Without this message, users who installed the
6
+ // Node SDK type `arcis` in their shell, get "command not found", and
7
+ // assume the package is broken.
8
+ //
9
+ // Skip in CI / non-TTY / when npm asks us not to log.
10
+ if (process.env.CI || process.env.ARCIS_SKIP_NOTICE) return;
11
+
12
+ const isTTY = process.stdout && process.stdout.isTTY;
13
+ const c = (s, code) => (isTTY ? `\x1b[${code}m${s}\x1b[0m` : s);
14
+
15
+ const lines = [
16
+ '',
17
+ c(' Arcis Node SDK installed.', '1;36'),
18
+ '',
19
+ c(' The CLI (audit / scan / sca) ships separately as a native binary:', '2'),
20
+ c(' npm install -g @arcis/cli', '32'),
21
+ '',
22
+ c(' This package is the SDK / middleware. It does not put a CLI on', '2'),
23
+ c(' your shell PATH. Docs: https://gagancm.github.io/arcis/documentation/cli.html', '2'),
24
+ '',
25
+ ];
26
+ for (const line of lines) console.log(line);
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * @arcis/node — `arcis` CLI dispatcher.
4
- *
5
- * Surface (mirrors the Python CLI's discovery layer):
6
- *
7
- * arcis → catalog
8
- * arcis --list → catalog with examples
9
- * arcis --help / -h → catalog + run-cmd hint
10
- * arcis --version / -V
11
- * arcis update [--apply] [--check]
12
- * arcis scan / audit / sca → forward to Python `arcis` if on PATH,
13
- * else print install hint
14
- *
15
- * Why we delegate scan/audit/sca to Python rather than re-implementing:
16
- * the threat database, audit rules, and attack-payload catalog are
17
- * Python-side data files. Maintaining two copies means drift; one
18
- * canonical CLI + a thin Node forwarder is simpler. The Node binary
19
- * exists primarily so users who installed `@arcis/node` from npm get
20
- * the same `arcis update` / discovery UX as Python users.
21
- */
22
- export {};
23
- //# sourceMappingURL=arcis.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"arcis.d.ts","sourceRoot":"","sources":["../../src/cli/arcis.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
package/dist/cli/arcis.js DELETED
@@ -1,312 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- var child_process = require('child_process');
5
- var fs = require('fs');
6
- var path = require('path');
7
- var url = require('url');
8
-
9
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
10
- var USE_COLOR = !process.env.NO_COLOR && process.stdout.isTTY === true;
11
- function c(text, ...codes) {
12
- if (!USE_COLOR) return text;
13
- return codes.join("") + text + "\x1B[0m";
14
- }
15
- var BOLD = "\x1B[1m";
16
- var DIM = "\x1B[2m";
17
- var GREEN = "\x1B[32m";
18
- var YELLOW = "\x1B[33m";
19
- var CYAN = "\x1B[36m";
20
- var RED = "\x1B[31m";
21
- function getInstalledVersion() {
22
- try {
23
- const here = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('arcis.js', document.baseURI).href)));
24
- let dir = path.dirname(here);
25
- for (let i = 0; i < 6; i++) {
26
- try {
27
- const raw = fs.readFileSync(path.resolve(dir, "package.json"), "utf-8");
28
- const pkg = JSON.parse(raw);
29
- if (pkg.name === "@arcis/node" && pkg.version) {
30
- return pkg.version;
31
- }
32
- } catch {
33
- }
34
- const parent = path.resolve(dir, "..");
35
- if (parent === dir) break;
36
- dir = parent;
37
- }
38
- } catch {
39
- }
40
- return "?";
41
- }
42
- async function fetchLatestVersion() {
43
- try {
44
- const controller = new AbortController();
45
- const timer = setTimeout(() => controller.abort(), 5e3);
46
- try {
47
- const resp = await fetch("https://registry.npmjs.org/@arcis/node", {
48
- headers: { Accept: "application/json" },
49
- signal: controller.signal
50
- });
51
- if (!resp.ok) return null;
52
- const data = await resp.json();
53
- return data["dist-tags"]?.latest ?? null;
54
- } finally {
55
- clearTimeout(timer);
56
- }
57
- } catch {
58
- return null;
59
- }
60
- }
61
- function parseVersion(v) {
62
- const parts = v.split(".");
63
- const out = [];
64
- for (const p of parts) {
65
- if (!/^\d+$/.test(p)) return null;
66
- out.push(Number(p));
67
- }
68
- return out;
69
- }
70
- function versionCompare(a, b) {
71
- const len = Math.max(a.length, b.length);
72
- for (let i = 0; i < len; i++) {
73
- const x = a[i] ?? 0;
74
- const y = b[i] ?? 0;
75
- if (x < y) return -1;
76
- if (x > y) return 1;
77
- }
78
- return 0;
79
- }
80
- function hasPythonArcis() {
81
- const probes = [
82
- { cmd: "arcis", args: ["--version"] },
83
- { cmd: "python", args: ["-m", "arcis.cli", "--version"] },
84
- { cmd: "python3", args: ["-m", "arcis.cli", "--version"] }
85
- ];
86
- for (const p of probes) {
87
- try {
88
- const result = child_process.spawnSync(p.cmd, p.args, {
89
- stdio: ["ignore", "ignore", "ignore"],
90
- timeout: 3e3,
91
- shell: process.platform === "win32"
92
- });
93
- if (result.status === 0) return true;
94
- } catch {
95
- }
96
- }
97
- return false;
98
- }
99
- function forwardToPython(args) {
100
- const candidates = [
101
- { cmd: "arcis", args },
102
- { cmd: "python", args: ["-m", "arcis.cli", ...args] },
103
- { cmd: "python3", args: ["-m", "arcis.cli", ...args] }
104
- ];
105
- for (const cand of candidates) {
106
- const result = child_process.spawnSync(cand.cmd, cand.args, {
107
- stdio: "inherit",
108
- shell: process.platform === "win32"
109
- });
110
- if (result.error) continue;
111
- process.exit(result.status ?? 0);
112
- }
113
- console.error(
114
- c("arcis: could not invoke the Python CLI. Install with:", RED, BOLD)
115
- );
116
- console.error(" pip install arcis");
117
- process.exit(127);
118
- }
119
- function printCatalog(verbose) {
120
- const ver = getInstalledVersion();
121
- console.log();
122
- console.log(` ${c("Arcis", BOLD, CYAN)} ${c(`v${ver}`, DIM)} ${c("(node)", DIM)}`);
123
- console.log(c(" Zero-dep security middleware + scanners.", DIM));
124
- console.log();
125
- console.log(c(" Commands", BOLD));
126
- const rows = [
127
- [
128
- "scan",
129
- "Send live attack payloads to a running app.",
130
- "arcis scan http://localhost:8000 --route POST:/echo --field q"
131
- ],
132
- [
133
- "audit",
134
- "Static-analyse Python / JS / TS source for unsafe patterns.",
135
- "arcis audit ."
136
- ],
137
- [
138
- "sca",
139
- "Match installed dependencies against the supply-chain threat DB.",
140
- "arcis sca ."
141
- ],
142
- ["update", "Check npm for a newer @arcis/node release.", "arcis update --apply"]
143
- ];
144
- for (const [name, desc, example] of rows) {
145
- console.log(` ${c(name.padEnd(8), BOLD, GREEN)} ${desc}`);
146
- if (verbose) {
147
- console.log(` ${c(example, DIM)}`);
148
- }
149
- }
150
- console.log();
151
- console.log(c(" Discovery", BOLD));
152
- console.log(` ${c("--list", BOLD, CYAN).padEnd(24)} Show this catalog (verbose).`);
153
- console.log(
154
- ` ${c("<cmd> --help", BOLD, CYAN).padEnd(24)} Show full flags for that command.`
155
- );
156
- console.log();
157
- console.log(c(" Note", BOLD));
158
- console.log(
159
- ` scan/audit/sca delegate to the Python CLI (canonical impl).`
160
- );
161
- console.log(` Install once with: ${c("pip install arcis", GREEN)}`);
162
- console.log();
163
- }
164
- async function updateCommand(args) {
165
- const apply = args.includes("--apply");
166
- const check = args.includes("--check");
167
- const yes = args.includes("--yes") || args.includes("-y");
168
- const current = getInstalledVersion();
169
- const latest = await fetchLatestVersion();
170
- if (check) {
171
- if (latest === null) {
172
- console.error("arcis: could not reach npm to check for updates");
173
- process.exitCode = 2;
174
- return;
175
- }
176
- const cur2 = parseVersion(current);
177
- const lat2 = parseVersion(latest);
178
- if (!cur2 || !lat2 || versionCompare(cur2, lat2) >= 0) {
179
- console.log(`@arcis/node ${current} is up-to-date`);
180
- process.exitCode = 0;
181
- return;
182
- }
183
- console.error(`@arcis/node ${current} is outdated; latest is ${latest}`);
184
- process.exitCode = 1;
185
- return;
186
- }
187
- console.log();
188
- console.log(c(" @arcis/node update check", BOLD, CYAN));
189
- console.log(c(" Source: https://registry.npmjs.org/@arcis/node", DIM));
190
- console.log();
191
- if (latest === null) {
192
- console.log(` Installed @arcis/node ${current}`);
193
- console.log(
194
- ` Latest ${c("? unreachable", YELLOW)} ${c("(network error or npm down)", DIM)}`
195
- );
196
- console.log();
197
- console.log(c(" Try again later, or run 'npm view @arcis/node version' directly.", DIM));
198
- console.log();
199
- process.exitCode = 2;
200
- return;
201
- }
202
- const cur = parseVersion(current);
203
- const lat = parseVersion(latest);
204
- if (!cur || !lat) {
205
- console.log(
206
- ` Installed @arcis/node ${current} ${c("(pre-release or unparseable)", DIM)}`
207
- );
208
- console.log(` Latest @arcis/node ${latest}`);
209
- console.log();
210
- console.log(c(" Skipping comparison \u2014 manually decide.", DIM));
211
- console.log();
212
- process.exitCode = 0;
213
- return;
214
- }
215
- if (versionCompare(cur, lat) >= 0) {
216
- console.log(` Installed @arcis/node ${current}`);
217
- console.log(` Latest @arcis/node ${latest}`);
218
- console.log();
219
- console.log(c(" You are on the latest version.", BOLD, GREEN));
220
- console.log();
221
- process.exitCode = 0;
222
- return;
223
- }
224
- console.log(` Installed @arcis/node ${current}`);
225
- console.log(
226
- ` Latest ${c(`@arcis/node ${latest}`, BOLD)} ${c("(update available)", YELLOW)}`
227
- );
228
- console.log();
229
- console.log(c(" Run to upgrade", BOLD));
230
- console.log(` ${c("npm install @arcis/node@latest", GREEN)}`);
231
- console.log();
232
- if (!apply) {
233
- console.log(c(" Or rerun: 'arcis update --apply' to upgrade in place.", DIM));
234
- console.log();
235
- process.exitCode = 1;
236
- return;
237
- }
238
- if (!yes && process.stdin.isTTY) {
239
- process.stdout.write("Upgrade now? [y/N] ");
240
- const response = await new Promise((res) => {
241
- process.stdin.once("data", (chunk) => res(chunk.toString().trim().toLowerCase()));
242
- });
243
- if (!response.startsWith("y")) {
244
- process.exitCode = 1;
245
- return;
246
- }
247
- }
248
- const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
249
- console.log(` $ ${npmCmd} install @arcis/node@latest`);
250
- const result = child_process.spawnSync(npmCmd, ["install", "@arcis/node@latest"], {
251
- stdio: "inherit"
252
- });
253
- process.exitCode = result.status ?? 1;
254
- }
255
- async function main() {
256
- const args = process.argv.slice(2);
257
- if (args.length === 0) {
258
- printCatalog(false);
259
- process.exit(0);
260
- }
261
- const arg0 = args[0];
262
- if (arg0 === "--list" || arg0 === "-l") {
263
- printCatalog(true);
264
- process.exit(0);
265
- }
266
- if (arg0 === "-h" || arg0 === "--help") {
267
- printCatalog(false);
268
- console.log(c(" Run 'arcis <command> --help' for full flags.", DIM));
269
- console.log();
270
- process.exit(0);
271
- }
272
- if (arg0 === "-V" || arg0 === "--version") {
273
- console.log(getInstalledVersion());
274
- process.exit(0);
275
- }
276
- if (arg0 === "update") {
277
- await updateCommand(args.slice(1));
278
- return;
279
- }
280
- if (arg0 === "scan" || arg0 === "audit" || arg0 === "sca") {
281
- if (!hasPythonArcis()) {
282
- console.error(
283
- c(
284
- `arcis: '${arg0}' is implemented by the Python CLI. Install with:`,
285
- YELLOW,
286
- BOLD
287
- )
288
- );
289
- console.error(" pip install arcis");
290
- console.error();
291
- console.error(c("Why: scan/audit/sca rely on Python's threat-DB and rule data.", DIM));
292
- console.error(
293
- c(
294
- "@arcis/node implements the middleware + dashboard upload \u2014 see the docs.",
295
- DIM
296
- )
297
- );
298
- process.exit(127);
299
- }
300
- forwardToPython(args);
301
- return;
302
- }
303
- console.error(`arcis: unknown command '${arg0}'`);
304
- console.error("Run 'arcis --list' for available commands.");
305
- process.exit(1);
306
- }
307
- main().catch((err) => {
308
- console.error(c(`arcis: unexpected error: ${err}`, RED));
309
- process.exit(1);
310
- });
311
- //# sourceMappingURL=arcis.js.map
312
- //# sourceMappingURL=arcis.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/cli/arcis.ts"],"names":["fileURLToPath","dirname","readFileSync","resolve","spawnSync","cur","lat"],"mappings":";;;;;;;;;AA6BA,IAAM,YACJ,CAAC,OAAA,CAAQ,IAAI,QAAA,IAAY,OAAA,CAAQ,OAAO,KAAA,KAAU,IAAA;AAEpD,SAAS,CAAA,CAAE,SAAiB,KAAA,EAAyB;AACnD,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AACvB,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA,GAAI,IAAA,GAAO,SAAA;AACjC;AAEA,IAAM,IAAA,GAAO,SAAA;AACb,IAAM,GAAA,GAAM,SAAA;AACZ,IAAM,KAAA,GAAQ,UAAA;AACd,IAAM,MAAA,GAAS,UAAA;AACf,IAAM,IAAA,GAAO,UAAA;AACb,IAAM,GAAA,GAAM,UAAA;AAIZ,SAAS,mBAAA,GAA8B;AAGrC,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAOA,iBAAA,CAAc,0PAAe,CAAA;AAE1C,IAAA,IAAI,GAAA,GAAMC,aAAQ,IAAI,CAAA;AACtB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,MAAA,IAAI;AACF,QAAA,MAAM,MAAMC,eAAA,CAAaC,YAAA,CAAQ,GAAA,EAAK,cAAc,GAAG,OAAO,CAAA;AAC9D,QAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC1B,QAAA,IAAI,GAAA,CAAI,IAAA,KAAS,aAAA,IAAiB,GAAA,CAAI,OAAA,EAAS;AAC7C,UAAA,OAAO,GAAA,CAAI,OAAA;AAAA,QACb;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,MAAM,MAAA,GAASA,YAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AAChC,MAAA,IAAI,WAAW,GAAA,EAAK;AACpB,MAAA,GAAA,GAAM,MAAA;AAAA,IACR;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,GAAA;AACT;AAMA,eAAe,kBAAA,GAA6C;AAC1D,EAAA,IAAI;AACF,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,GAAK,CAAA;AACxD,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,KAAA,CAAM,wCAAA,EAA0C;AAAA,QACjE,OAAA,EAAS,EAAE,MAAA,EAAQ,kBAAA,EAAmB;AAAA,QACtC,QAAQ,UAAA,CAAW;AAAA,OACpB,CAAA;AACD,MAAA,IAAI,CAAC,IAAA,CAAK,EAAA,EAAI,OAAO,IAAA;AACrB,MAAA,MAAM,IAAA,GAAQ,MAAM,IAAA,CAAK,IAAA,EAAK;AAC9B,MAAA,OAAO,IAAA,CAAK,WAAW,CAAA,EAAG,MAAA,IAAU,IAAA;AAAA,IACtC,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,CAAA,EAA4B;AAChD,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AACzB,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,CAAC,GAAG,OAAO,IAAA;AAC7B,IAAA,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,EACpB;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,cAAA,CAAe,GAAa,CAAA,EAAqB;AACxD,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,MAAA,EAAQ,EAAE,MAAM,CAAA;AACvC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,IAAA,MAAM,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,IAAK,CAAA;AAClB,IAAA,MAAM,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,IAAK,CAAA;AAClB,IAAA,IAAI,CAAA,GAAI,GAAG,OAAO,EAAA;AAClB,IAAA,IAAI,CAAA,GAAI,GAAG,OAAO,CAAA;AAAA,EACpB;AACA,EAAA,OAAO,CAAA;AACT;AAIA,SAAS,cAAA,GAA0B;AAGjC,EAAA,MAAM,MAAA,GAAiD;AAAA,IACrD,EAAE,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,CAAC,WAAW,CAAA,EAAE;AAAA,IACpC,EAAE,KAAK,QAAA,EAAU,IAAA,EAAM,CAAC,IAAA,EAAM,WAAA,EAAa,WAAW,CAAA,EAAE;AAAA,IACxD,EAAE,KAAK,SAAA,EAAW,IAAA,EAAM,CAAC,IAAA,EAAM,WAAA,EAAa,WAAW,CAAA;AAAE,GAC3D;AACA,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAASC,uBAAA,CAAU,CAAA,CAAE,GAAA,EAAK,EAAE,IAAA,EAAM;AAAA,QACtC,KAAA,EAAO,CAAC,QAAA,EAAU,QAAA,EAAU,QAAQ,CAAA;AAAA,QACpC,OAAA,EAAS,GAAA;AAAA,QACT,KAAA,EAAO,QAAQ,QAAA,KAAa;AAAA,OAC7B,CAAA;AACD,MAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAAA,IAClC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,gBAAgB,IAAA,EAAuB;AAI9C,EAAA,MAAM,UAAA,GAAqD;AAAA,IACzD,EAAE,GAAA,EAAK,OAAA,EAAS,IAAA,EAAK;AAAA,IACrB,EAAE,KAAK,QAAA,EAAU,IAAA,EAAM,CAAC,IAAA,EAAM,WAAA,EAAa,GAAG,IAAI,CAAA,EAAE;AAAA,IACpD,EAAE,KAAK,SAAA,EAAW,IAAA,EAAM,CAAC,IAAA,EAAM,WAAA,EAAa,GAAG,IAAI,CAAA;AAAE,GACvD;AACA,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,MAAM,MAAA,GAASA,uBAAA,CAAU,IAAA,CAAK,GAAA,EAAK,KAAK,IAAA,EAAM;AAAA,MAC5C,KAAA,EAAO,SAAA;AAAA,MACP,KAAA,EAAO,QAAQ,QAAA,KAAa;AAAA,KAC7B,CAAA;AACD,IAAA,IAAI,OAAO,KAAA,EAAO;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,MAAA,IAAU,CAAC,CAAA;AAAA,EACjC;AACA,EAAA,OAAA,CAAQ,KAAA;AAAA,IACN,CAAA,CAAE,uDAAA,EAAyD,GAAA,EAAK,IAAI;AAAA,GACtE;AACA,EAAA,OAAA,CAAQ,MAAM,qBAAqB,CAAA;AACnC,EAAA,OAAA,CAAQ,KAAK,GAAG,CAAA;AAClB;AAIA,SAAS,aAAa,OAAA,EAAwB;AAC5C,EAAA,MAAM,MAAM,mBAAA,EAAoB;AAChC,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,IAAI,CAAA,EAAA,EAAK,CAAA,CAAE,SAAS,IAAA,EAAM,IAAI,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,GAAG,CAAA,CAAA,EAAI,GAAG,CAAC,CAAA,EAAA,EAAK,EAAE,QAAA,EAAU,GAAG,CAAC,CAAA,CAAE,CAAA;AACpF,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,4CAAA,EAA8C,GAAG,CAAC,CAAA;AAChE,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,YAAA,EAAc,IAAI,CAAC,CAAA;AACjC,EAAA,MAAM,IAAA,GAAwC;AAAA,IAC5C;AAAA,MACE,MAAA;AAAA,MACA,6CAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA;AAAA,MACE,OAAA;AAAA,MACA,6DAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA;AAAA,MACE,KAAA;AAAA,MACA,kEAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,CAAC,QAAA,EAAU,4CAAA,EAA8C,sBAAsB;AAAA,GACjF;AACA,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,IAAA,EAAM,OAAO,KAAK,IAAA,EAAM;AACxC,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,IAAA,EAAO,CAAA,CAAE,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,EAAG,IAAA,EAAM,KAAK,CAAC,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA;AAC3D,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,IAAI,CAAA,aAAA,EAAgB,CAAA,CAAE,OAAA,EAAS,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,aAAA,EAAe,IAAI,CAAC,CAAA;AAClC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,IAAA,EAAO,CAAA,CAAE,QAAA,EAAU,IAAA,EAAM,IAAI,CAAA,CAAE,MAAA,CAAO,EAAE,CAAC,CAAA,6BAAA,CAA+B,CAAA;AACpF,EAAA,OAAA,CAAQ,GAAA;AAAA,IACN,CAAA,IAAA,EAAO,EAAE,cAAA,EAAgB,IAAA,EAAM,IAAI,CAAA,CAAE,MAAA,CAAO,EAAE,CAAC,CAAA,kCAAA;AAAA,GACjD;AACA,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,QAAA,EAAU,IAAI,CAAC,CAAA;AAC7B,EAAA,OAAA,CAAQ,GAAA;AAAA,IACN,CAAA,+DAAA;AAAA,GACF;AACA,EAAA,OAAA,CAAQ,IAAI,CAAA,wBAAA,EAA2B,CAAA,CAAE,mBAAA,EAAqB,KAAK,CAAC,CAAA,CAAE,CAAA;AACtE,EAAA,OAAA,CAAQ,GAAA,EAAI;AACd;AAIA,eAAe,cAAc,IAAA,EAA+B;AAM1D,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA;AACrC,EAAA,MAAM,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,IAAK,IAAA,CAAK,SAAS,IAAI,CAAA;AAExD,EAAA,MAAM,UAAU,mBAAA,EAAoB;AACpC,EAAA,MAAM,MAAA,GAAS,MAAM,kBAAA,EAAmB;AAExC,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,OAAA,CAAQ,MAAM,iDAAiD,CAAA;AAC/D,MAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,MAAA;AAAA,IACF;AACA,IAAA,MAAMC,IAAAA,GAAM,aAAa,OAAO,CAAA;AAChC,IAAA,MAAMC,IAAAA,GAAM,aAAa,MAAM,CAAA;AAC/B,IAAA,IAAI,CAACD,QAAO,CAACC,IAAAA,IAAO,eAAeD,IAAAA,EAAKC,IAAG,KAAK,CAAA,EAAG;AACjD,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,YAAA,EAAe,OAAO,CAAA,cAAA,CAAgB,CAAA;AAClD,MAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,MAAA;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,YAAA,EAAe,OAAO,CAAA,wBAAA,EAA2B,MAAM,CAAA,CAAE,CAAA;AACvE,IAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,4BAAA,EAA8B,IAAA,EAAM,IAAI,CAAC,CAAA;AACvD,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,kDAAA,EAAoD,GAAG,CAAC,CAAA;AACtE,EAAA,OAAA,CAAQ,GAAA,EAAI;AAEZ,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,OAAO,CAAA,CAAE,CAAA;AACpD,IAAA,OAAA,CAAQ,GAAA;AAAA,MACN,CAAA,gBAAA,EAAmB,EAAE,eAAA,EAAiB,MAAM,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,6BAAA,EAA+B,GAAG,CAAC,CAAA;AAAA,KACzF;AACA,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,oEAAA,EAAsE,GAAG,CAAC,CAAA;AACxF,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,GAAA,GAAM,aAAa,OAAO,CAAA;AAChC,EAAA,MAAM,GAAA,GAAM,aAAa,MAAM,CAAA;AAC/B,EAAA,IAAI,CAAC,GAAA,IAAO,CAAC,GAAA,EAAK;AAChB,IAAA,OAAA,CAAQ,GAAA;AAAA,MACN,+BAA+B,OAAO,CAAA,EAAA,EAAK,CAAA,CAAE,8BAAA,EAAgC,GAAG,CAAC,CAAA;AAAA,KACnF;AACA,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,MAAM,CAAA,CAAE,CAAA;AACnD,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,+CAAA,EAA4C,GAAG,CAAC,CAAA;AAC9D,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,cAAA,CAAe,GAAA,EAAK,GAAG,CAAA,IAAK,CAAA,EAAG;AACjC,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,OAAO,CAAA,CAAE,CAAA;AACpD,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,MAAM,CAAA,CAAE,CAAA;AACnD,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,kCAAA,EAAoC,IAAA,EAAM,KAAK,CAAC,CAAA;AAC9D,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,OAAO,CAAA,CAAE,CAAA;AACpD,EAAA,OAAA,CAAQ,GAAA;AAAA,IACN,CAAA,gBAAA,EAAmB,CAAA,CAAE,CAAA,YAAA,EAAe,MAAM,CAAA,CAAA,EAAI,IAAI,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,oBAAA,EAAsB,MAAM,CAAC,CAAA;AAAA,GACzF;AACA,EAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,kBAAA,EAAoB,IAAI,CAAC,CAAA;AACvC,EAAA,OAAA,CAAQ,IAAI,CAAA,IAAA,EAAO,CAAA,CAAE,gCAAA,EAAkC,KAAK,CAAC,CAAA,CAAE,CAAA;AAC/D,EAAA,OAAA,CAAQ,GAAA,EAAI;AAEZ,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,yDAAA,EAA2D,GAAG,CAAC,CAAA;AAC7E,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,GAAA,IAAO,OAAA,CAAQ,KAAA,CAAM,KAAA,EAAO;AAC/B,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,qBAAqB,CAAA;AAC1C,IAAA,MAAM,QAAA,GAAW,MAAM,IAAI,OAAA,CAAgB,CAAC,GAAA,KAAQ;AAClD,MAAA,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,MAAA,EAAQ,CAAC,KAAA,KAAkB,GAAA,CAAI,KAAA,CAAM,QAAA,EAAS,CAAE,IAAA,EAAK,CAAE,WAAA,EAAa,CAAC,CAAA;AAAA,IAC1F,CAAC,CAAA;AACD,IAAA,IAAI,CAAC,QAAA,CAAS,UAAA,CAAW,GAAG,CAAA,EAAG;AAC7B,MAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACnB,MAAA;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,QAAA,KAAa,OAAA,GAAU,SAAA,GAAY,KAAA;AAC1D,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,IAAA,EAAO,MAAM,CAAA,2BAAA,CAA6B,CAAA;AACtD,EAAA,MAAM,SAASF,uBAAA,CAAU,MAAA,EAAQ,CAAC,SAAA,EAAW,oBAAoB,CAAA,EAAG;AAAA,IAClE,KAAA,EAAO;AAAA,GACR,CAAA;AACD,EAAA,OAAA,CAAQ,QAAA,GAAW,OAAO,MAAA,IAAU,CAAA;AACtC;AAIA,eAAe,IAAA,GAAsB;AACnC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AAEjC,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,YAAA,CAAa,KAAK,CAAA;AAClB,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,MAAM,IAAA,GAAO,KAAK,CAAC,CAAA;AAEnB,EAAA,IAAI,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,IAAA,EAAM;AACtC,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,QAAA,EAAU;AACtC,IAAA,YAAA,CAAa,KAAK,CAAA;AAClB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,gDAAA,EAAkD,GAAG,CAAC,CAAA;AACpE,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,WAAA,EAAa;AACzC,IAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,CAAA;AACjC,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,MAAM,aAAA,CAAc,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AACjC,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,IAAA,KAAS,MAAA,IAAU,IAAA,KAAS,OAAA,IAAW,SAAS,KAAA,EAAO;AACzD,IAAA,IAAI,CAAC,gBAAe,EAAG;AACrB,MAAA,OAAA,CAAQ,KAAA;AAAA,QACN,CAAA;AAAA,UACE,WAAW,IAAI,CAAA,iDAAA,CAAA;AAAA,UACf,MAAA;AAAA,UACA;AAAA;AACF,OACF;AACA,MAAA,OAAA,CAAQ,MAAM,qBAAqB,CAAA;AACnC,MAAA,OAAA,CAAQ,KAAA,EAAM;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,+DAAA,EAAiE,GAAG,CAAC,CAAA;AACrF,MAAA,OAAA,CAAQ,KAAA;AAAA,QACN,CAAA;AAAA,UACE,+EAAA;AAAA,UACA;AAAA;AACF,OACF;AACA,MAAA,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,IAClB;AACA,IAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,IAAA;AAAA,EACF;AAEA,EAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,wBAAA,EAA2B,IAAI,CAAA,CAAA,CAAG,CAAA;AAChD,EAAA,OAAA,CAAQ,MAAM,4CAA4C,CAAA;AAC1D,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB;AAEA,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AACpB,EAAA,OAAA,CAAQ,MAAM,CAAA,CAAE,CAAA,yBAAA,EAA4B,GAAG,CAAA,CAAA,EAAI,GAAG,CAAC,CAAA;AACvD,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB,CAAC,CAAA","file":"arcis.js","sourcesContent":["#!/usr/bin/env node\n/**\n * @arcis/node — `arcis` CLI dispatcher.\n *\n * Surface (mirrors the Python CLI's discovery layer):\n *\n * arcis → catalog\n * arcis --list → catalog with examples\n * arcis --help / -h → catalog + run-cmd hint\n * arcis --version / -V\n * arcis update [--apply] [--check]\n * arcis scan / audit / sca → forward to Python `arcis` if on PATH,\n * else print install hint\n *\n * Why we delegate scan/audit/sca to Python rather than re-implementing:\n * the threat database, audit rules, and attack-payload catalog are\n * Python-side data files. Maintaining two copies means drift; one\n * canonical CLI + a thin Node forwarder is simpler. The Node binary\n * exists primarily so users who installed `@arcis/node` from npm get\n * the same `arcis update` / discovery UX as Python users.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// ── ANSI helpers ─────────────────────────────────────────────────────\n\nconst USE_COLOR =\n !process.env.NO_COLOR && process.stdout.isTTY === true;\n\nfunction c(text: string, ...codes: string[]): string {\n if (!USE_COLOR) return text;\n return codes.join(\"\") + text + \"\\x1b[0m\";\n}\n\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst CYAN = \"\\x1b[36m\";\nconst RED = \"\\x1b[31m\";\n\n// ── Version + npm registry helpers ───────────────────────────────────\n\nfunction getInstalledVersion(): string {\n // Read the package.json that ships with this build so we report the\n // actual installed version, not whatever was hardcoded.\n try {\n const here = fileURLToPath(import.meta.url);\n // Walk up to find the package root (contains package.json).\n let dir = dirname(here);\n for (let i = 0; i < 6; i++) {\n try {\n const raw = readFileSync(resolve(dir, \"package.json\"), \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n if (pkg.name === \"@arcis/node\" && pkg.version) {\n return pkg.version;\n }\n } catch {\n // climb\n }\n const parent = resolve(dir, \"..\");\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // ignore\n }\n return \"?\";\n}\n\ninterface NpmRegistryResponse {\n \"dist-tags\"?: { latest?: string };\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 5_000);\n try {\n const resp = await fetch(\"https://registry.npmjs.org/@arcis/node\", {\n headers: { Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!resp.ok) return null;\n const data = (await resp.json()) as NpmRegistryResponse;\n return data[\"dist-tags\"]?.latest ?? null;\n } finally {\n clearTimeout(timer);\n }\n } catch {\n return null;\n }\n}\n\nfunction parseVersion(v: string): number[] | null {\n const parts = v.split(\".\");\n const out: number[] = [];\n for (const p of parts) {\n if (!/^\\d+$/.test(p)) return null;\n out.push(Number(p));\n }\n return out;\n}\n\nfunction versionCompare(a: number[], b: number[]): number {\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const x = a[i] ?? 0;\n const y = b[i] ?? 0;\n if (x < y) return -1;\n if (x > y) return 1;\n }\n return 0;\n}\n\n// ── Python forwarder ─────────────────────────────────────────────────\n\nfunction hasPythonArcis(): boolean {\n // Check both `arcis` (Windows: arcis.exe) and `python -m arcis.cli` so\n // users who have the Python package installed but not on PATH still work.\n const probes: Array<{ cmd: string; args: string[] }> = [\n { cmd: \"arcis\", args: [\"--version\"] },\n { cmd: \"python\", args: [\"-m\", \"arcis.cli\", \"--version\"] },\n { cmd: \"python3\", args: [\"-m\", \"arcis.cli\", \"--version\"] },\n ];\n for (const p of probes) {\n try {\n const result = spawnSync(p.cmd, p.args, {\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n timeout: 3000,\n shell: process.platform === \"win32\",\n });\n if (result.status === 0) return true;\n } catch {\n // try next\n }\n }\n return false;\n}\n\nfunction forwardToPython(args: string[]): never {\n // Try `arcis` first, fall back to `python -m arcis.cli`. Mirror exit\n // code so CI scripts using the Node CLI get the same behavior they'd\n // get from the Python CLI.\n const candidates: Array<{ cmd: string; args: string[] }> = [\n { cmd: \"arcis\", args },\n { cmd: \"python\", args: [\"-m\", \"arcis.cli\", ...args] },\n { cmd: \"python3\", args: [\"-m\", \"arcis.cli\", ...args] },\n ];\n for (const cand of candidates) {\n const result = spawnSync(cand.cmd, cand.args, {\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n if (result.error) continue;\n process.exit(result.status ?? 0);\n }\n console.error(\n c(\"arcis: could not invoke the Python CLI. Install with:\", RED, BOLD),\n );\n console.error(\" pip install arcis\");\n process.exit(127);\n}\n\n// ── Catalog ──────────────────────────────────────────────────────────\n\nfunction printCatalog(verbose: boolean): void {\n const ver = getInstalledVersion();\n console.log();\n console.log(` ${c(\"Arcis\", BOLD, CYAN)} ${c(`v${ver}`, DIM)} ${c(\"(node)\", DIM)}`);\n console.log(c(\" Zero-dep security middleware + scanners.\", DIM));\n console.log();\n console.log(c(\" Commands\", BOLD));\n const rows: Array<[string, string, string]> = [\n [\n \"scan\",\n \"Send live attack payloads to a running app.\",\n \"arcis scan http://localhost:8000 --route POST:/echo --field q\",\n ],\n [\n \"audit\",\n \"Static-analyse Python / JS / TS source for unsafe patterns.\",\n \"arcis audit .\",\n ],\n [\n \"sca\",\n \"Match installed dependencies against the supply-chain threat DB.\",\n \"arcis sca .\",\n ],\n [\"update\", \"Check npm for a newer @arcis/node release.\", \"arcis update --apply\"],\n ];\n for (const [name, desc, example] of rows) {\n console.log(` ${c(name.padEnd(8), BOLD, GREEN)} ${desc}`);\n if (verbose) {\n console.log(` ${c(example, DIM)}`);\n }\n }\n console.log();\n console.log(c(\" Discovery\", BOLD));\n console.log(` ${c(\"--list\", BOLD, CYAN).padEnd(24)} Show this catalog (verbose).`);\n console.log(\n ` ${c(\"<cmd> --help\", BOLD, CYAN).padEnd(24)} Show full flags for that command.`,\n );\n console.log();\n console.log(c(\" Note\", BOLD));\n console.log(\n ` scan/audit/sca delegate to the Python CLI (canonical impl).`,\n );\n console.log(` Install once with: ${c(\"pip install arcis\", GREEN)}`);\n console.log();\n}\n\n// ── Update command ───────────────────────────────────────────────────\n\nasync function updateCommand(args: string[]): Promise<void> {\n // Note: this function uses `process.exitCode = N; return;` rather than\n // `process.exit(N)` because the fetch() call leaves an internal undici\n // handle open momentarily on Windows, and process.exit() during that\n // window throws a libuv assertion on stderr. exitCode + return lets\n // Node drain handles cleanly before exit.\n const apply = args.includes(\"--apply\");\n const check = args.includes(\"--check\");\n const yes = args.includes(\"--yes\") || args.includes(\"-y\");\n\n const current = getInstalledVersion();\n const latest = await fetchLatestVersion();\n\n if (check) {\n if (latest === null) {\n console.error(\"arcis: could not reach npm to check for updates\");\n process.exitCode = 2;\n return;\n }\n const cur = parseVersion(current);\n const lat = parseVersion(latest);\n if (!cur || !lat || versionCompare(cur, lat) >= 0) {\n console.log(`@arcis/node ${current} is up-to-date`);\n process.exitCode = 0;\n return;\n }\n console.error(`@arcis/node ${current} is outdated; latest is ${latest}`);\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(c(\" @arcis/node update check\", BOLD, CYAN));\n console.log(c(\" Source: https://registry.npmjs.org/@arcis/node\", DIM));\n console.log();\n\n if (latest === null) {\n console.log(` Installed @arcis/node ${current}`);\n console.log(\n ` Latest ${c(\"? unreachable\", YELLOW)} ${c(\"(network error or npm down)\", DIM)}`,\n );\n console.log();\n console.log(c(\" Try again later, or run 'npm view @arcis/node version' directly.\", DIM));\n console.log();\n process.exitCode = 2;\n return;\n }\n\n const cur = parseVersion(current);\n const lat = parseVersion(latest);\n if (!cur || !lat) {\n console.log(\n ` Installed @arcis/node ${current} ${c(\"(pre-release or unparseable)\", DIM)}`,\n );\n console.log(` Latest @arcis/node ${latest}`);\n console.log();\n console.log(c(\" Skipping comparison — manually decide.\", DIM));\n console.log();\n process.exitCode = 0;\n return;\n }\n\n if (versionCompare(cur, lat) >= 0) {\n console.log(` Installed @arcis/node ${current}`);\n console.log(` Latest @arcis/node ${latest}`);\n console.log();\n console.log(c(\" You are on the latest version.\", BOLD, GREEN));\n console.log();\n process.exitCode = 0;\n return;\n }\n\n console.log(` Installed @arcis/node ${current}`);\n console.log(\n ` Latest ${c(`@arcis/node ${latest}`, BOLD)} ${c(\"(update available)\", YELLOW)}`,\n );\n console.log();\n console.log(c(\" Run to upgrade\", BOLD));\n console.log(` ${c(\"npm install @arcis/node@latest\", GREEN)}`);\n console.log();\n\n if (!apply) {\n console.log(c(\" Or rerun: 'arcis update --apply' to upgrade in place.\", DIM));\n console.log();\n process.exitCode = 1;\n return;\n }\n\n if (!yes && process.stdin.isTTY) {\n process.stdout.write(\"Upgrade now? [y/N] \");\n const response = await new Promise<string>((res) => {\n process.stdin.once(\"data\", (chunk: Buffer) => res(chunk.toString().trim().toLowerCase()));\n });\n if (!response.startsWith(\"y\")) {\n process.exitCode = 1;\n return;\n }\n }\n\n const npmCmd = process.platform === \"win32\" ? \"npm.cmd\" : \"npm\";\n console.log(` $ ${npmCmd} install @arcis/node@latest`);\n const result = spawnSync(npmCmd, [\"install\", \"@arcis/node@latest\"], {\n stdio: \"inherit\",\n });\n process.exitCode = result.status ?? 1;\n}\n\n// ── Main ─────────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n\n if (args.length === 0) {\n printCatalog(false);\n process.exit(0);\n }\n\n const arg0 = args[0];\n\n if (arg0 === \"--list\" || arg0 === \"-l\") {\n printCatalog(true);\n process.exit(0);\n }\n\n if (arg0 === \"-h\" || arg0 === \"--help\") {\n printCatalog(false);\n console.log(c(\" Run 'arcis <command> --help' for full flags.\", DIM));\n console.log();\n process.exit(0);\n }\n\n if (arg0 === \"-V\" || arg0 === \"--version\") {\n console.log(getInstalledVersion());\n process.exit(0);\n }\n\n if (arg0 === \"update\") {\n await updateCommand(args.slice(1));\n return;\n }\n\n if (arg0 === \"scan\" || arg0 === \"audit\" || arg0 === \"sca\") {\n if (!hasPythonArcis()) {\n console.error(\n c(\n `arcis: '${arg0}' is implemented by the Python CLI. Install with:`,\n YELLOW,\n BOLD,\n ),\n );\n console.error(\" pip install arcis\");\n console.error();\n console.error(c(\"Why: scan/audit/sca rely on Python's threat-DB and rule data.\", DIM));\n console.error(\n c(\n \"@arcis/node implements the middleware + dashboard upload — see the docs.\",\n DIM,\n ),\n );\n process.exit(127);\n }\n forwardToPython(args);\n return;\n }\n\n console.error(`arcis: unknown command '${arg0}'`);\n console.error(\"Run 'arcis --list' for available commands.\");\n process.exit(1);\n}\n\nmain().catch((err) => {\n console.error(c(`arcis: unexpected error: ${err}`, RED));\n process.exit(1);\n});\n"]}