@senpi/runtime 1.0.95-dev.runtime-phase-2.20260416194608 → 1.0.95-dev.runtime-phase-2.20260417171148

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.
@@ -0,0 +1,251 @@
1
+ /**
2
+ * External momentum producer.
3
+ *
4
+ * A standalone momentum scanner that:
5
+ * 1. Pulls instruments + latest candles from the live Senpi MCP server.
6
+ * 2. Computes last-vs-prev close % move against configurable thresholds.
7
+ * 3. Pushes MOMENTUM_BREAKOUT signals into a running runtime via the
8
+ * `openclaw senpi external-scanner ingest` CLI command.
9
+ *
10
+ * Prereqs:
11
+ * - `npm run build` so dist/ is current (used only for the MCP provider bridge).
12
+ * - A runtime installed from ./momentum.yaml is running for STRATEGY_ADDRESS.
13
+ * - `openclaw` CLI on PATH and authenticated against that runtime.
14
+ * - SENPI_API_KEY and SENPI_MCP_URL env vars set.
15
+ *
16
+ * Run: node src/scanners/external/momentum/producer.mjs
17
+ */
18
+
19
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
20
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
21
+ import { createSenpiScannerProviders } from "../../../../dist/scanners/index.js";
22
+ import { pushScannerSignals } from "../shared/ingest.mjs";
23
+
24
+ // ─── Config ─────────────────────────────────────────────────────────────────
25
+
26
+ const SENPI_API_KEY = process.env.SENPI_API_KEY;
27
+ const SENPI_MCP_URL = process.env.SENPI_MCP_URL;
28
+ const STRATEGY_ADDRESS = process.env.STRATEGY_ADDRESS;
29
+ const SCANNER_NAME =
30
+ process.env.EXTERNAL_SCANNER_NAME ?? "external_momentum";
31
+ const OPENCLAW_BIN = process.env.OPENCLAW_BIN ?? "openclaw";
32
+ const SCANNER_ID = "external-momentum-producer";
33
+
34
+ const config = {
35
+ timeframe: process.env.TIMEFRAME ?? "1h",
36
+ minMovePct: numEnv("MIN_MOVE_PCT", 1.5),
37
+ minDayVolume: numEnv("MIN_DAY_VOLUME", 1_000_000),
38
+ minLeverage: numEnv("MIN_LEVERAGE", 5),
39
+ maxCandidates: numEnv("MAX_CANDIDATES", 20),
40
+ minSignalScore: numEnv("MIN_SIGNAL_SCORE", 0.2),
41
+ };
42
+
43
+ if (!SENPI_API_KEY || !SENPI_MCP_URL || !STRATEGY_ADDRESS) {
44
+ console.error(
45
+ "Missing required env vars. Set SENPI_API_KEY, SENPI_MCP_URL, and STRATEGY_ADDRESS.\n" +
46
+ " export SENPI_API_KEY='...'\n" +
47
+ " export SENPI_MCP_URL='https://mcp.prod.senpi.ai/mcp'\n" +
48
+ " export STRATEGY_ADDRESS='0x...'",
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ function numEnv(name, fallback) {
54
+ const raw = process.env[name];
55
+ if (raw === undefined || raw === "") return fallback;
56
+ const parsed = Number(raw);
57
+ if (!Number.isFinite(parsed)) {
58
+ throw new Error(`Env ${name} must be a finite number, got "${raw}"`);
59
+ }
60
+ return parsed;
61
+ }
62
+
63
+ // ─── MCP → SenpiToolClient bridge ───────────────────────────────────────────
64
+
65
+ function createSenpiToolClient(mcpClient) {
66
+ return {
67
+ async callTool(name, args) {
68
+ const result = await mcpClient.callTool({ name, arguments: args });
69
+
70
+ if (result.isError) {
71
+ const errorText =
72
+ Array.isArray(result.content) &&
73
+ result.content.length > 0 &&
74
+ result.content[0].type === "text"
75
+ ? result.content[0].text
76
+ : JSON.stringify(result.content);
77
+ throw new Error(`MCP tool "${name}" returned error: ${errorText}`);
78
+ }
79
+
80
+ if (result.structuredContent !== undefined) {
81
+ return result.structuredContent;
82
+ }
83
+
84
+ if (Array.isArray(result.content) && result.content.length > 0) {
85
+ const first = result.content[0];
86
+ if (first.type === "text") {
87
+ try {
88
+ return JSON.parse(first.text);
89
+ } catch {
90
+ return first.text;
91
+ }
92
+ }
93
+ }
94
+
95
+ return result.content;
96
+ },
97
+ };
98
+ }
99
+
100
+ // ─── Momentum logic ─────────────────────────────────────────────────────────
101
+ // Self-contained port of src/scanners/implementations/momentum.ts. Kept free
102
+ // of any runtime-internal imports.
103
+
104
+ function clamp01(value) {
105
+ if (!Number.isFinite(value)) return 0;
106
+ if (value < 0) return 0;
107
+ if (value > 1) return 1;
108
+ return value;
109
+ }
110
+
111
+ function selectCandidates(instruments, cfg) {
112
+ const eligible = instruments.filter((inst) => {
113
+ if (inst.isDelisted) return false;
114
+ if ((inst.maxLeverage ?? 0) < cfg.minLeverage) return false;
115
+ const vol = inst.context?.dayNtlVlm ?? 0;
116
+ return vol >= cfg.minDayVolume;
117
+ });
118
+ eligible.sort(
119
+ (a, b) => (b.context?.dayNtlVlm ?? 0) - (a.context?.dayNtlVlm ?? 0),
120
+ );
121
+ return eligible.slice(0, cfg.maxCandidates);
122
+ }
123
+
124
+ function computeMovePct(series) {
125
+ if (!series || series.length < 2) return null;
126
+ const prev = series[series.length - 2]?.close;
127
+ const last = series[series.length - 1]?.close;
128
+ if (!Number.isFinite(prev) || !Number.isFinite(last) || prev <= 0) {
129
+ return null;
130
+ }
131
+ return { prev, last, movePct: ((last - prev) / prev) * 100 };
132
+ }
133
+
134
+ function buildSignal({ instrument, move, timeframe, now }) {
135
+ const movePct = move.movePct;
136
+ const absMove = Math.abs(movePct);
137
+ const direction = movePct > 0 ? "LONG" : "SHORT";
138
+ // Score scales from 0 at minMovePct to 1 at 5% move, clamped.
139
+ const score = clamp01((absMove - config.minMovePct) / (5 - config.minMovePct));
140
+
141
+ return {
142
+ scannerId: SCANNER_ID,
143
+ signalType: "MOMENTUM_BREAKOUT",
144
+ asset: instrument.name,
145
+ direction,
146
+ score,
147
+ timestamp: now,
148
+ factors: {
149
+ breakout: true,
150
+ large_move: absMove >= 3,
151
+ },
152
+ meta: {
153
+ timeframe,
154
+ movePct,
155
+ prev: move.prev,
156
+ last: move.last,
157
+ dayNtlVlm: instrument.context?.dayNtlVlm ?? 0,
158
+ maxLeverage: instrument.maxLeverage,
159
+ },
160
+ };
161
+ }
162
+
163
+ // ─── Main ───────────────────────────────────────────────────────────────────
164
+
165
+ async function main() {
166
+ console.log(`External momentum producer`);
167
+ console.log(` MCP: ${SENPI_MCP_URL}`);
168
+ console.log(` address: ${STRATEGY_ADDRESS}`);
169
+ console.log(` scanner: ${SCANNER_NAME}`);
170
+ console.log(` config: ${JSON.stringify(config)}`);
171
+
172
+ const transport = new StreamableHTTPClientTransport(new URL(SENPI_MCP_URL), {
173
+ requestInit: { headers: { Authorization: `Bearer ${SENPI_API_KEY}` } },
174
+ });
175
+ const mcpClient = new Client({
176
+ name: "external-momentum",
177
+ version: "0.0.1",
178
+ });
179
+ await mcpClient.connect(transport);
180
+
181
+ const providers = createSenpiScannerProviders({
182
+ client: createSenpiToolClient(mcpClient),
183
+ logLevel: "warn",
184
+ cacheTtlMs: { instruments: 0, smData: 0, candles: 0 },
185
+ candleIntervals: [config.timeframe],
186
+ maxCandleAssets: config.maxCandidates,
187
+ retries: 2,
188
+ });
189
+
190
+ try {
191
+ const instruments = await providers.getInstruments(STRATEGY_ADDRESS);
192
+ const candidates = selectCandidates(instruments, config);
193
+ console.log(
194
+ `\nInstruments: ${instruments.length}, candidates: ${candidates.length}`,
195
+ );
196
+
197
+ if (candidates.length === 0) {
198
+ console.log("No candidates after filtering — skipping ingest.");
199
+ return;
200
+ }
201
+
202
+ const candles = await providers.getCandles(STRATEGY_ADDRESS, candidates, {
203
+ scannerId: SCANNER_ID,
204
+ preferredAssets: candidates.map((c) => c.name),
205
+ });
206
+
207
+ const now = Date.now();
208
+ const signals = [];
209
+ for (const inst of candidates) {
210
+ const series = candles[inst.name]?.candles?.[config.timeframe];
211
+ const move = computeMovePct(series);
212
+ if (!move) continue;
213
+ if (Math.abs(move.movePct) < config.minMovePct) continue;
214
+ const signal = buildSignal({
215
+ instrument: inst,
216
+ move,
217
+ timeframe: config.timeframe,
218
+ now,
219
+ });
220
+ if (signal.score < config.minSignalScore) continue;
221
+ signals.push(signal);
222
+ }
223
+
224
+ console.log(`\nSignals: ${signals.length}`);
225
+ for (const s of signals) {
226
+ console.log(
227
+ ` ${s.asset} ${s.direction} move=${s.meta.movePct.toFixed(2)}% score=${s.score.toFixed(2)}`,
228
+ );
229
+ }
230
+
231
+ if (signals.length === 0) {
232
+ console.log("\nNothing to ingest — skipping gateway call.");
233
+ return;
234
+ }
235
+
236
+ const response = await pushScannerSignals({
237
+ address: STRATEGY_ADDRESS,
238
+ scanner: SCANNER_NAME,
239
+ signals,
240
+ openclawBin: OPENCLAW_BIN,
241
+ });
242
+ console.log("\nIngest response:", JSON.stringify(response));
243
+ } finally {
244
+ await transport.close();
245
+ }
246
+ }
247
+
248
+ main().catch((error) => {
249
+ console.error("\nProducer failed:", error.message ?? error);
250
+ process.exit(1);
251
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared ingest helpers for external-scanner producers under
3
+ * `src/scanners/external/`.
4
+ *
5
+ * Producers all need the same two things:
6
+ * 1. Normalize an in-producer signal into the external batch item shape
7
+ * expected by `senpi.ingestExternalScannerData`.
8
+ * 2. Shell out to the openclaw CLI to actually push the batch and parse the
9
+ * response.
10
+ *
11
+ * Keeping this in one place means producers never diverge on the wire format
12
+ * or response-validation rules, and a future CLI contract change only needs to
13
+ * be made here.
14
+ */
15
+
16
+ import { execFile } from "node:child_process";
17
+ import { promisify } from "node:util";
18
+
19
+ const execFileAsync = promisify(execFile);
20
+
21
+ /**
22
+ * Convert a producer-native signal object into the batch item shape consumed
23
+ * by the external-scanner ingest CLI/RPC.
24
+ *
25
+ * Producers emit richer signal objects (scannerId/factors/meta/timestamp) but
26
+ * the runtime only needs the top-level routing fields plus an opaque `data`
27
+ * blob — everything else is preserved under `data.source*` so downstream
28
+ * consumers can reconstruct the original context.
29
+ */
30
+ export function toExternalBatchItem(signal) {
31
+ return {
32
+ asset: signal.asset,
33
+ direction: signal.direction,
34
+ score: signal.score,
35
+ signal_type: signal.signalType,
36
+ data: {
37
+ sourceScannerId: signal.scannerId,
38
+ sourceSignalType: signal.signalType,
39
+ sourceTimestamp: signal.timestamp,
40
+ sourceFactors: signal.factors,
41
+ sourceMeta: signal.meta,
42
+ },
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Push a batch of scanner signals from an external producer into a running
48
+ * runtime via the `openclaw senpi external-scanner ingest` CLI command.
49
+ *
50
+ * Named from the producer's point of view: the scanner executing this code
51
+ * is pushing its signals out to the senpi-trading-runtime service.
52
+ *
53
+ * The CLI emits pure JSON on stdout:
54
+ * - success: { ok: true, result: { accepted, signalCount, ... } }
55
+ * - failure: { ok: false, error: { code, message } }
56
+ *
57
+ * @param {object} opts
58
+ * @param {string} opts.address Strategy wallet address.
59
+ * @param {string} opts.scanner Configured external_scanner name.
60
+ * @param {Array<object>} opts.signals Producer-native signals (will be
61
+ * normalized via `toExternalBatchItem`).
62
+ * @param {string} [opts.openclawBin] Path or name of the openclaw CLI
63
+ * (defaults to `"openclaw"`).
64
+ * @returns {Promise<object>} The inner `result` payload from the
65
+ * gateway on success.
66
+ */
67
+ export async function pushScannerSignals({
68
+ address,
69
+ scanner,
70
+ signals,
71
+ openclawBin = "openclaw",
72
+ }) {
73
+ const payload = {
74
+ signals: signals.map(toExternalBatchItem),
75
+ };
76
+ const { stdout } = await execFileAsync(openclawBin, [
77
+ "senpi",
78
+ "external-scanner",
79
+ "ingest",
80
+ "--address",
81
+ address,
82
+ "--scanner",
83
+ scanner,
84
+ "--payload",
85
+ JSON.stringify(payload),
86
+ ]);
87
+
88
+ let response;
89
+ try {
90
+ response = JSON.parse(stdout);
91
+ } catch {
92
+ throw new Error(`CLI returned non-JSON response:\n${stdout}`);
93
+ }
94
+
95
+ if (response?.ok !== true) {
96
+ const err = response?.error ?? {};
97
+ const code = err.code ?? "UNKNOWN";
98
+ const message = err.message ?? JSON.stringify(response);
99
+ throw new Error(`CLI rejected ingest (${code}): ${message}`);
100
+ }
101
+
102
+ const result = response.result ?? {};
103
+ if (!result.accepted) {
104
+ throw new Error(`CLI did not accept ingest: ${JSON.stringify(result)}`);
105
+ }
106
+ if (result.signalCount !== signals.length) {
107
+ throw new Error(
108
+ `Expected signalCount=${signals.length}, got ${result.signalCount}`,
109
+ );
110
+ }
111
+ return result;
112
+ }
@@ -2,8 +2,12 @@
2
2
  * Shared structured logger factory. Components create a logger with a namespace.
3
3
  */
4
4
  export type LogLevel = "debug" | "info" | "warn" | "error";
5
- export declare function setLogLevel(level: LogLevel): void;
5
+ export type LogLevelSource = "config" | "env" | "default" | "runtime";
6
+ /** Parse a candidate log-level string. Returns null for unknown values (caller decides how to warn/fallback). */
7
+ export declare function parseLogLevel(raw: string | undefined | null): LogLevel | null;
8
+ export declare function setLogLevel(level: LogLevel, source?: LogLevelSource): void;
6
9
  export declare function getLogLevel(): LogLevel;
10
+ export declare function getLogLevelSource(): LogLevelSource;
7
11
  export interface ErrorNotificationPayload {
8
12
  namespace: string;
9
13
  level: "error";
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAiC3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,wBAAwB,KAC9B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAErE;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAMD,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAkBD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA8CtD"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAW3D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,CAY7E;AAmBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAE,cAA0B,GAAG,IAAI,CAGrF;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,wBAAwB,KAC9B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAErE;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAMD,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAkBD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA8CtD"}
@@ -8,10 +8,10 @@ const levelOrder = {
8
8
  error: 3,
9
9
  };
10
10
  const DEFAULT_LEVEL = "info";
11
- function resolveInitialLevel() {
12
- const raw = process.env.SENPI_LOG_LEVEL;
11
+ /** Parse a candidate log-level string. Returns null for unknown values (caller decides how to warn/fallback). */
12
+ export function parseLogLevel(raw) {
13
13
  if (raw == null || raw === "")
14
- return DEFAULT_LEVEL;
14
+ return null;
15
15
  const normalized = raw.toLowerCase();
16
16
  if (normalized === "debug" ||
17
17
  normalized === "info" ||
@@ -19,18 +19,33 @@ function resolveInitialLevel() {
19
19
  normalized === "error") {
20
20
  return normalized;
21
21
  }
22
+ return null;
23
+ }
24
+ function resolveInitialLevel() {
25
+ const raw = process.env.SENPI_LOG_LEVEL;
26
+ if (raw == null || raw === "")
27
+ return { level: DEFAULT_LEVEL, source: "default" };
28
+ const parsed = parseLogLevel(raw);
29
+ if (parsed !== null)
30
+ return { level: parsed, source: "env" };
22
31
  console.warn(`[logger] Unknown SENPI_LOG_LEVEL="${raw}"; falling back to "${DEFAULT_LEVEL}"`);
23
- return DEFAULT_LEVEL;
32
+ return { level: DEFAULT_LEVEL, source: "default" };
24
33
  }
25
- let minLevel = resolveInitialLevel();
34
+ const initial = resolveInitialLevel();
35
+ let minLevel = initial.level;
36
+ let minLevelSource = initial.source;
26
37
  let errorNotifier = null;
27
38
  let notifyingError = false;
28
- export function setLogLevel(level) {
39
+ export function setLogLevel(level, source = "runtime") {
29
40
  minLevel = level;
41
+ minLevelSource = source;
30
42
  }
31
43
  export function getLogLevel() {
32
44
  return minLevel;
33
45
  }
46
+ export function getLogLevelSource() {
47
+ return minLevelSource;
48
+ }
34
49
  export function setErrorNotifier(notifier) {
35
50
  errorNotifier = notifier;
36
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,16 @@
15
15
  "type": "string",
16
16
  "description": "API key when mcpTransport is not provided by the gateway"
17
17
  },
18
+ "logLevel": {
19
+ "type": "string",
20
+ "enum": [
21
+ "debug",
22
+ "info",
23
+ "warn",
24
+ "error"
25
+ ],
26
+ "description": "Minimum log level (default: info). Overrides the SENPI_LOG_LEVEL env var when set."
27
+ },
18
28
  "autoUpdate": {
19
29
  "type": "object",
20
30
  "description": "Auto-update configuration. Enabled with auto-apply by default (opt-out). Omit this block entirely to use defaults.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senpi/runtime",
3
- "version": "1.0.95-dev.runtime-phase-2.20260416194608",
3
+ "version": "1.0.95-dev.runtime-phase-2.20260417171148",
4
4
  "description": "Senpi runtime plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,8 @@
13
13
  "scripts": {
14
14
  "setup:hooks": "sh -c 'git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true'",
15
15
  "prepare": "npm run setup:hooks",
16
- "build": "npm i && tsc",
16
+ "build": "npm i && tsc && npm run copy:external-scanners",
17
+ "copy:external-scanners": "node -e \"require('fs').rmSync('dist/scanners/external', { recursive: true, force: true }); require('fs').cpSync('src/scanners/external', 'dist/scanners/external', { recursive: true })\"",
17
18
  "typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
18
19
  "start": "node dist/index.js",
19
20
  "debug": "npm run build && node dist/debug.js",