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.
- package/README.md +120 -0
- package/dist/index.js +58 -91
- 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
|
+
[](https://github.com/armlynobinguar/meridian-core/blob/main/LICENSE)
|
|
6
|
+
[](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 {
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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(
|
|
403
|
-
if (!
|
|
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 =
|
|
372
|
+
const resources = sorobanData.build().resources();
|
|
407
373
|
return {
|
|
408
|
-
cpu_instructions:
|
|
374
|
+
cpu_instructions: resources.instructions(),
|
|
409
375
|
memory_bytes: 0,
|
|
410
|
-
read_bytes:
|
|
411
|
-
write_bytes:
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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:
|
|
523
|
-
events:
|
|
524
|
-
|
|
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
|
-
|
|
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
|
|
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",
|
|
1178
|
+
printJson({ product: "MERIDIAN", cli_version: CLI_VERSION, engine_version: MERIDIAN_VERSION });
|
|
1212
1179
|
return;
|
|
1213
1180
|
}
|
|
1214
|
-
console.log(`
|
|
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(
|
|
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.
|
|
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 &&
|
|
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.
|
|
29
|
+
"@meridian/core": "0.1.1",
|
|
30
30
|
"esbuild": "^0.28.1",
|
|
31
31
|
"tsx": "^4.19.2",
|
|
32
32
|
"typescript": "^5.7.2",
|