meridian-core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +120 -0
  2. package/dist/index.js +58 -91
  3. package/package.json +3 -3
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # meridian-core
2
+
3
+ **MERIDIAN command-line interface — pre-execution intelligence for Stellar developers, from your terminal.**
4
+
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/armlynobinguar/meridian-core/blob/main/LICENSE)
6
+ [![npm](https://img.shields.io/npm/v/meridian-core?label=meridian-core)](https://www.npmjs.com/package/meridian-core)
7
+
8
+ MERIDIAN simulates a Stellar transaction end-to-end, maps every contract it touches downstream, scores what breaks if something goes wrong, and returns a plain-language GenAI risk brief — all before you submit.
9
+
10
+ Full project docs, architecture, and the REST API live in the [monorepo README](https://github.com/armlynobinguar/meridian-core#readme). This package is the standalone `meridian` / `meridian-core` CLI.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g meridian-core
16
+ meridian-core --help
17
+ ```
18
+
19
+ ## Requirements
20
+
21
+ - Node.js **>= 20**
22
+ - A Soroban RPC endpoint for the network you're targeting (testnet/mainnet)
23
+ - *(Optional)* An [Anthropic API key](https://console.anthropic.com/) for GenAI-synthesized briefs — falls back to a deterministic brief without one
24
+
25
+ ## Verdict States
26
+
27
+ | Verdict | Meaning |
28
+ |---|---|
29
+ | 🟢 `CLEAR` | Safe to submit |
30
+ | 🟡 `WARN` | Submit with caution — review warnings |
31
+ | 🔴 `ABORT` | Do not submit — critical failure predicted |
32
+
33
+ ## Commands
34
+
35
+ | Command | Description |
36
+ |---|---|
37
+ | `meridian analyze [tx]` | Full pipeline: TRACE + FIELD + GRAVITY + BRIEF *(default command)* |
38
+ | `meridian trace [tx]` | TRACE only — simulate and report the execution path |
39
+ | `meridian field [tx]` | TRACE + FIELD — map the dependency graph touched by the transaction |
40
+ | `meridian gravity [tx]` | TRACE + FIELD + GRAVITY — score the blast radius |
41
+ | `meridian version` | Print CLI and engine version |
42
+ | `meridian --help` / `meridian <command> --help` | Show detailed help |
43
+
44
+ `tx` is the base64-encoded transaction XDR. It can be passed as an argument, via `--file`, or piped over stdin.
45
+
46
+ ## Options
47
+
48
+ | Flag | Applies to | Description |
49
+ |---|---|---|
50
+ | `-n, --network <network>` | all | `mainnet` or `testnet` (default: `testnet`) |
51
+ | `--rpc-url <url>` | all | Override the Soroban RPC endpoint instead of reading it from env |
52
+ | `-f, --file <path>` | all | Read the transaction XDR from a file instead of an argument |
53
+ | `-e, --ecosystem <path>` | `field`, `gravity`, `analyze` | Path to an ecosystem manifest JSON file |
54
+ | `--json` | all | Print raw JSON instead of a formatted report |
55
+ | `--skip-field` | `analyze` | Skip the FIELD dependency-mapping layer |
56
+ | `--skip-gravity` | `analyze` | Skip the GRAVITY blast-radius layer |
57
+ | `--confidence-threshold <n>` | `analyze` | Minimum confidence (0–1) required for a `CLEAR` verdict |
58
+ | `--no-brief` | `analyze` | Skip GenAI BRIEF synthesis (structured layers only) |
59
+ | `--api-key <key>` | `analyze` | Anthropic API key for BRIEF synthesis (else read from env) |
60
+
61
+ ## Examples
62
+
63
+ ```bash
64
+ # Full analysis (default command — "analyze" can be omitted)
65
+ meridian analyze <base64-xdr> --network testnet
66
+
67
+ # Read the XDR from a file
68
+ meridian analyze --file tx.xdr --network mainnet
69
+
70
+ # Pipe it in via stdin
71
+ cat tx.xdr | meridian analyze --network testnet --json
72
+
73
+ # Override the RPC endpoint without setting env vars
74
+ meridian analyze <base64-xdr> --network testnet --rpc-url https://soroban-testnet.stellar.org
75
+
76
+ # Score blast radius against a known ecosystem manifest
77
+ meridian gravity <base64-xdr> --ecosystem manifest.json --network testnet
78
+
79
+ # Fast structured-only analysis (no GenAI call)
80
+ meridian analyze <base64-xdr> --network testnet --no-brief
81
+
82
+ # TRACE only, fastest path
83
+ meridian trace <base64-xdr> --network testnet
84
+
85
+ # Check installed versions
86
+ meridian version
87
+ ```
88
+
89
+ ## Ecosystem Manifest
90
+
91
+ An optional JSON file describing known contracts in your ecosystem, used by `field`, `gravity`, and `analyze` to enrich dependency mapping, blast-radius scoring, and affected-user counts:
92
+
93
+ ```json
94
+ {
95
+ "name": "my-ecosystem",
96
+ "version": "1.0.0",
97
+ "contracts": [
98
+ {
99
+ "name": "token-vault",
100
+ "address": "CABC...XYZ",
101
+ "network": "testnet",
102
+ "dependencies": ["CDEF...UVW"],
103
+ "active_users": 4200,
104
+ "criticality": "HIGH"
105
+ }
106
+ ]
107
+ }
108
+ ```
109
+
110
+ ## Environment Variables
111
+
112
+ | Variable | Required | Description |
113
+ |---|---|---|
114
+ | `STELLAR_RPC_TESTNET` | For testnet use | Soroban RPC endpoint for testnet |
115
+ | `STELLAR_RPC_MAINNET` | For mainnet use | Soroban RPC endpoint for mainnet |
116
+ | `ANTHROPIC_API_KEY` | No | Claude API key for BRIEF synthesis — falls back to a deterministic brief if unset |
117
+
118
+ ## License
119
+
120
+ MIT — see the [monorepo repository](https://github.com/armlynobinguar/meridian-core) for full source and license details.
package/dist/index.js CHANGED
@@ -3,6 +3,18 @@
3
3
  // src/cli.ts
4
4
  import { Command as Command7 } from "commander";
5
5
 
6
+ // src/version.ts
7
+ import { createRequire } from "node:module";
8
+ var require2 = createRequire(import.meta.url);
9
+ var { version } = require2("../package.json");
10
+ var CLI_VERSION = version;
11
+
12
+ // src/commands/analyze.ts
13
+ import { Command as Command2 } from "commander";
14
+
15
+ // ../core/src/analyze.ts
16
+ import { createRequire as createRequire2 } from "node:module";
17
+
6
18
  // ../core/src/logger.ts
7
19
  var Logger = class {
8
20
  level;
@@ -260,60 +272,14 @@ function createMeridianError(error, code, hint, layer) {
260
272
  }
261
273
 
262
274
  // ../core/src/trace/parser.ts
263
- import { TransactionBuilder, FeeBumpTransaction } from "@stellar/stellar-sdk";
275
+ import {
276
+ Address,
277
+ FeeBumpTransaction,
278
+ TransactionBuilder,
279
+ humanizeEvents,
280
+ xdr
281
+ } from "@stellar/stellar-sdk";
264
282
  var STALENESS_THRESHOLD = 5;
265
- function extractFootprint(simData) {
266
- const footprintContracts = /* @__PURE__ */ new Set();
267
- const readOnly = [];
268
- const readWrite = [];
269
- if (!simData || !("transactionData" in simData)) {
270
- return {
271
- ledgerSequence: 0,
272
- latestLedger: 0,
273
- footprintContracts: [],
274
- readOnly: [],
275
- readWrite: []
276
- };
277
- }
278
- const txData = simData.transactionData;
279
- const resources = txData?.resources;
280
- const footprint = resources?.footprint;
281
- if (footprint?.readOnly) {
282
- for (const key of footprint.readOnly) {
283
- const keyStr = ledgerKeyToString(key);
284
- readOnly.push(keyStr);
285
- const contractId = extractContractFromLedgerKey(keyStr);
286
- if (contractId) footprintContracts.add(contractId);
287
- }
288
- }
289
- if (footprint?.readWrite) {
290
- for (const key of footprint.readWrite) {
291
- const keyStr = ledgerKeyToString(key);
292
- readWrite.push(keyStr);
293
- const contractId = extractContractFromLedgerKey(keyStr);
294
- if (contractId) footprintContracts.add(contractId);
295
- }
296
- }
297
- return {
298
- ledgerSequence: 0,
299
- latestLedger: 0,
300
- footprintContracts: [...footprintContracts],
301
- readOnly,
302
- readWrite
303
- };
304
- }
305
- function ledgerKeyToString(key) {
306
- if (typeof key === "string") return key;
307
- if (key && typeof key === "object" && "contractData" in key) {
308
- const contractData = key.contractData;
309
- return contractData?.contract ?? JSON.stringify(key);
310
- }
311
- return JSON.stringify(key);
312
- }
313
- function extractContractFromLedgerKey(keyStr) {
314
- const match = keyStr.match(/C[A-Z0-9]{55}/);
315
- return match?.[0];
316
- }
317
283
  function parseExecutionPath(txXdr) {
318
284
  const steps = [];
319
285
  let index = 0;
@@ -365,16 +331,16 @@ function parseOperation(op, index) {
365
331
  }
366
332
  function parseAuthEntries(events) {
367
333
  const entries = [];
368
- for (const event of events) {
369
- if (event.type === "contract" && event.topic) {
370
- const topicStr = event.topic.map((t) => String(t)).join(":");
371
- if (topicStr.includes("require_auth") || topicStr.includes("auth")) {
372
- entries.push({
373
- address: event.contractId?.() ?? "unknown",
374
- contract_id: event.contractId?.(),
375
- credentials: event.topic.map((t) => String(t))
376
- });
377
- }
334
+ const humanized = humanizeEvents(events);
335
+ for (const event of humanized) {
336
+ const topicStrs = event.topics.map((t) => String(t));
337
+ const topicStr = topicStrs.join(":");
338
+ if (topicStr.includes("require_auth") || topicStr.includes("auth")) {
339
+ entries.push({
340
+ address: event.contractId ?? "unknown",
341
+ contract_id: event.contractId,
342
+ credentials: topicStrs
343
+ });
378
344
  }
379
345
  }
380
346
  return entries;
@@ -399,16 +365,16 @@ function computeFeeEstimate(txXdr, minResourceFee) {
399
365
  total_fee: classicBaseFee + resourceFee
400
366
  };
401
367
  }
402
- function extractResourceUsage(simData) {
403
- if (!simData || !("transactionData" in simData)) {
368
+ function extractResourceUsage(sorobanData) {
369
+ if (!sorobanData) {
404
370
  return { cpu_instructions: 0, memory_bytes: 0, read_bytes: 0, write_bytes: 0 };
405
371
  }
406
- const resources = simData.transactionData?.resources;
372
+ const resources = sorobanData.build().resources();
407
373
  return {
408
- cpu_instructions: Number(resources?.instructions ?? 0),
374
+ cpu_instructions: resources.instructions(),
409
375
  memory_bytes: 0,
410
- read_bytes: Number(resources?.readBytes ?? 0),
411
- write_bytes: Number(resources?.writeBytes ?? 0)
376
+ read_bytes: resources.readBytes(),
377
+ write_bytes: resources.writeBytes()
412
378
  };
413
379
  }
414
380
  function parseFailurePoint(error, executionPath) {
@@ -437,7 +403,6 @@ function parseFailurePoint(error, executionPath) {
437
403
  }
438
404
  function parseSimulationResult(raw, txXdr) {
439
405
  const executionPath = parseExecutionPath(txXdr);
440
- const footprint = extractFootprint(raw.transactionData);
441
406
  const stalenessDelta = raw.latestLedger - raw.simulationLedger;
442
407
  const isStale = stalenessDelta > STALENESS_THRESHOLD;
443
408
  if (!raw.success && raw.error) {
@@ -447,23 +412,22 @@ function parseSimulationResult(raw, txXdr) {
447
412
  execution_path: executionPath,
448
413
  auth_entries: parseAuthEntries(raw.events),
449
414
  fee_estimate: computeFeeEstimate(txXdr, raw.minResourceFee),
450
- resource_usage: extractResourceUsage(raw.transactionData),
415
+ resource_usage: extractResourceUsage(raw.sorobanData),
451
416
  staleness_warning: isStale
452
417
  };
453
418
  }
454
- void footprint;
455
419
  return {
456
420
  success: true,
457
421
  execution_path: executionPath,
458
422
  auth_entries: parseAuthEntries(raw.events),
459
423
  fee_estimate: computeFeeEstimate(txXdr, raw.minResourceFee),
460
- resource_usage: extractResourceUsage(raw.transactionData),
424
+ resource_usage: extractResourceUsage(raw.sorobanData),
461
425
  staleness_warning: isStale
462
426
  };
463
427
  }
464
428
 
465
429
  // ../core/src/trace/rpc.ts
466
- import { rpc as rpc2, TransactionBuilder as TransactionBuilder2, Networks } from "@stellar/stellar-sdk";
430
+ import { rpc, TransactionBuilder as TransactionBuilder2, Networks } from "@stellar/stellar-sdk";
467
431
  var DEFAULT_TIMEOUT_MS = 3e4;
468
432
  function resolveRpcUrl(network) {
469
433
  const envKey = network === "mainnet" ? "STELLAR_RPC_MAINNET" : "STELLAR_RPC_TESTNET";
@@ -485,7 +449,7 @@ async function withTimeout(promise, timeoutMs, label) {
485
449
  }
486
450
  }
487
451
  async function simulateTransaction(txXdr, rpcUrl, timeoutMs = DEFAULT_TIMEOUT_MS) {
488
- const server = new rpc2.Server(rpcUrl, { allowHttp: rpcUrl.startsWith("http://") });
452
+ const server = new rpc.Server(rpcUrl, { allowHttp: rpcUrl.startsWith("http://") });
489
453
  try {
490
454
  logger.debug("simulateTransaction:start", { rpcUrl });
491
455
  const latestLedgerResponse = await withTimeout(
@@ -503,25 +467,23 @@ async function simulateTransaction(txXdr, rpcUrl, timeoutMs = DEFAULT_TIMEOUT_MS
503
467
  timeoutMs,
504
468
  "simulateTransaction"
505
469
  );
506
- if (rpc2.Api.isSimulationError(simResponse)) {
470
+ if (rpc.Api.isSimulationError(simResponse)) {
507
471
  return {
508
472
  success: false,
509
473
  latestLedger: latestLedgerResponse.sequence,
510
474
  simulationLedger: latestLedgerResponse.sequence,
511
475
  minResourceFee: "0",
512
- events: [],
513
- error: simResponse.error,
514
- transactionData: simResponse
476
+ events: simResponse.events,
477
+ error: simResponse.error
515
478
  };
516
479
  }
517
- const successResponse = simResponse;
518
480
  return {
519
481
  success: true,
520
482
  latestLedger: latestLedgerResponse.sequence,
521
483
  simulationLedger: latestLedgerResponse.sequence,
522
- minResourceFee: successResponse.minResourceFee,
523
- events: successResponse.events ?? [],
524
- transactionData: successResponse
484
+ minResourceFee: simResponse.minResourceFee,
485
+ events: simResponse.events ?? [],
486
+ sorobanData: simResponse.transactionData
525
487
  };
526
488
  } catch (err) {
527
489
  logger.error("simulateTransaction:failed", {
@@ -550,7 +512,15 @@ async function trace(txXdr, options) {
550
512
  }
551
513
 
552
514
  // ../core/src/analyze.ts
553
- var MERIDIAN_VERSION = "0.1.0";
515
+ function resolveEngineVersion() {
516
+ if (true) {
517
+ return "0.1.1";
518
+ }
519
+ const require3 = createRequire2(import.meta.url);
520
+ const { version: version2 } = require3("../package.json");
521
+ return version2;
522
+ }
523
+ var MERIDIAN_VERSION = resolveEngineVersion();
554
524
  var DEFAULT_CONFIDENCE_THRESHOLD = 0.75;
555
525
  function computeVerdict(traceSuccess, blastRadius, confidence, threshold, isStale) {
556
526
  if (!traceSuccess) {
@@ -688,9 +658,6 @@ function emptyGravityResult() {
688
658
  };
689
659
  }
690
660
 
691
- // src/commands/analyze.ts
692
- import { Command as Command2 } from "commander";
693
-
694
661
  // ../ai/src/brief.ts
695
662
  import Anthropic from "@anthropic-ai/sdk";
696
663
  var BRIEF_MODEL = "claude-sonnet-4-6";
@@ -1206,12 +1173,12 @@ function gravityCommand() {
1206
1173
  // src/commands/version.ts
1207
1174
  import { Command as Command6 } from "commander";
1208
1175
  function versionCommand() {
1209
- return new Command6("version").description("Print MERIDIAN product and core engine version").option("--json", "Print raw JSON instead of a formatted report").action((options) => {
1176
+ return new Command6("version").description("Print MERIDIAN CLI and core engine version").option("--json", "Print raw JSON instead of a formatted report").action((options) => {
1210
1177
  if (options.json) {
1211
- printJson({ product: "MERIDIAN", version: MERIDIAN_VERSION });
1178
+ printJson({ product: "MERIDIAN", cli_version: CLI_VERSION, engine_version: MERIDIAN_VERSION });
1212
1179
  return;
1213
1180
  }
1214
- console.log(`MERIDIAN v${MERIDIAN_VERSION}`);
1181
+ console.log(`meridian-core v${CLI_VERSION} (engine v${MERIDIAN_VERSION})`);
1215
1182
  });
1216
1183
  }
1217
1184
 
@@ -1219,7 +1186,7 @@ function versionCommand() {
1219
1186
  function buildProgram() {
1220
1187
  const program = new Command7("meridian").description(
1221
1188
  "MERIDIAN \u2014 pre-execution intelligence for Stellar developers.\nKnow what crosses before it does."
1222
- ).version(MERIDIAN_VERSION, "-v, --version", "Print the MERIDIAN version").addHelpText(
1189
+ ).version(CLI_VERSION, "-v, --version", "Print the meridian-core CLI version").addHelpText(
1223
1190
  "after",
1224
1191
  `
1225
1192
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meridian-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MERIDIAN command-line interface — pre-execution intelligence from your terminal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "dist"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --alias:@meridian/core=./src/internal/meridian-core.ts --alias:@meridian/ai=./src/internal/meridian-ai.ts --external:commander --external:picocolors --external:@stellar/stellar-sdk --external:@anthropic-ai/sdk && chmod +x dist/index.js",
15
+ "build": "tsc --noEmit && node scripts/build.mjs",
16
16
  "dev": "tsx watch src/index.ts",
17
17
  "start": "node dist/index.js",
18
18
  "test": "vitest run",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@meridian/ai": "0.1.0",
29
- "@meridian/core": "0.1.0",
29
+ "@meridian/core": "0.1.1",
30
30
  "esbuild": "^0.28.1",
31
31
  "tsx": "^4.19.2",
32
32
  "typescript": "^5.7.2",