meridian-core 0.1.0 → 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 (67) hide show
  1. package/README.md +120 -0
  2. package/dist/index.js +1217 -4
  3. package/package.json +10 -11
  4. package/dist/cli.d.ts +0 -14
  5. package/dist/cli.d.ts.map +0 -1
  6. package/dist/cli.js +0 -46
  7. package/dist/cli.js.map +0 -1
  8. package/dist/commands/analyze.d.ts +0 -8
  9. package/dist/commands/analyze.d.ts.map +0 -1
  10. package/dist/commands/analyze.js +0 -78
  11. package/dist/commands/analyze.js.map +0 -1
  12. package/dist/commands/field.d.ts +0 -8
  13. package/dist/commands/field.d.ts.map +0 -1
  14. package/dist/commands/field.js +0 -46
  15. package/dist/commands/field.js.map +0 -1
  16. package/dist/commands/gravity.d.ts +0 -8
  17. package/dist/commands/gravity.d.ts.map +0 -1
  18. package/dist/commands/gravity.js +0 -47
  19. package/dist/commands/gravity.js.map +0 -1
  20. package/dist/commands/trace.d.ts +0 -8
  21. package/dist/commands/trace.d.ts.map +0 -1
  22. package/dist/commands/trace.js +0 -37
  23. package/dist/commands/trace.js.map +0 -1
  24. package/dist/commands/version.d.ts +0 -8
  25. package/dist/commands/version.d.ts.map +0 -1
  26. package/dist/commands/version.js +0 -21
  27. package/dist/commands/version.js.map +0 -1
  28. package/dist/index.d.ts +0 -3
  29. package/dist/index.d.ts.map +0 -1
  30. package/dist/index.js.map +0 -1
  31. package/dist/lib/errors.d.ts +0 -21
  32. package/dist/lib/errors.d.ts.map +0 -1
  33. package/dist/lib/errors.js +0 -37
  34. package/dist/lib/errors.js.map +0 -1
  35. package/dist/lib/input.d.ts +0 -11
  36. package/dist/lib/input.d.ts.map +0 -1
  37. package/dist/lib/input.js +0 -38
  38. package/dist/lib/input.js.map +0 -1
  39. package/dist/lib/manifest.d.ts +0 -10
  40. package/dist/lib/manifest.d.ts.map +0 -1
  41. package/dist/lib/manifest.js +0 -35
  42. package/dist/lib/manifest.js.map +0 -1
  43. package/dist/lib/options.d.ts +0 -26
  44. package/dist/lib/options.d.ts.map +0 -1
  45. package/dist/lib/options.js +0 -44
  46. package/dist/lib/options.js.map +0 -1
  47. package/dist/lib/output.d.ts +0 -32
  48. package/dist/lib/output.d.ts.map +0 -1
  49. package/dist/lib/output.js +0 -156
  50. package/dist/lib/output.js.map +0 -1
  51. package/src/cli.ts +0 -54
  52. package/src/commands/analyze.ts +0 -103
  53. package/src/commands/field.ts +0 -66
  54. package/src/commands/gravity.ts +0 -70
  55. package/src/commands/trace.ts +0 -51
  56. package/src/commands/version.ts +0 -21
  57. package/src/index.ts +0 -7
  58. package/src/lib/errors.ts +0 -42
  59. package/src/lib/input.test.ts +0 -38
  60. package/src/lib/input.ts +0 -43
  61. package/src/lib/manifest.test.ts +0 -60
  62. package/src/lib/manifest.ts +0 -41
  63. package/src/lib/options.test.ts +0 -27
  64. package/src/lib/options.ts +0 -47
  65. package/src/lib/output.ts +0 -180
  66. package/tsconfig.json +0 -10
  67. package/vitest.config.ts +0 -8
package/src/lib/errors.ts DELETED
@@ -1,42 +0,0 @@
1
- import pc from 'picocolors';
2
- import type { MeridianError } from '@meridian/core';
3
-
4
- /**
5
- * Check if a value is a structured MeridianError.
6
- *
7
- * @param value - Value to check
8
- * @returns True if the value matches the MeridianError shape
9
- */
10
- export function isMeridianError(value: unknown): value is MeridianError {
11
- return (
12
- typeof value === 'object' &&
13
- value !== null &&
14
- 'layer' in value &&
15
- 'code' in value &&
16
- 'hint' in value
17
- );
18
- }
19
-
20
- /**
21
- * Print a MeridianError to stderr and exit the process with code 1.
22
- *
23
- * @param error - Structured MeridianError to report
24
- */
25
- export function failWithMeridianError(error: MeridianError): never {
26
- console.error(pc.red(pc.bold(`✖ [${error.layer}] ${error.code}`)));
27
- console.error(pc.red(error.error));
28
- console.error(pc.dim(`hint: ${error.hint}`));
29
- process.exit(1);
30
- }
31
-
32
- /**
33
- * Print a generic error to stderr and exit the process with code 1.
34
- *
35
- * @param err - Error or unknown thrown value
36
- */
37
- export function failWithError(err: unknown): never {
38
- const message = err instanceof Error ? err.message : String(err);
39
- console.error(pc.red(pc.bold('✖ Error')));
40
- console.error(pc.red(message));
41
- process.exit(1);
42
- }
@@ -1,38 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { writeFile, mkdtemp, rm } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { resolveTxInput } from './input.js';
6
-
7
- describe('resolveTxInput', () => {
8
- it('returns the trimmed positional argument when provided', async () => {
9
- const result = await resolveTxInput(' AAAA-fake-xdr ', undefined);
10
- expect(result).toBe('AAAA-fake-xdr');
11
- });
12
-
13
- it('reads and trims XDR from a file when --file is provided', async () => {
14
- const dir = await mkdtemp(join(tmpdir(), 'meridian-cli-'));
15
- const filePath = join(dir, 'tx.xdr');
16
- await writeFile(filePath, ' file-based-xdr\n');
17
-
18
- try {
19
- const result = await resolveTxInput(undefined, filePath);
20
- expect(result).toBe('file-based-xdr');
21
- } finally {
22
- await rm(dir, { recursive: true, force: true });
23
- }
24
- });
25
-
26
- it('prefers --file over the positional argument', async () => {
27
- const dir = await mkdtemp(join(tmpdir(), 'meridian-cli-'));
28
- const filePath = join(dir, 'tx.xdr');
29
- await writeFile(filePath, 'from-file');
30
-
31
- try {
32
- const result = await resolveTxInput('from-arg', filePath);
33
- expect(result).toBe('from-file');
34
- } finally {
35
- await rm(dir, { recursive: true, force: true });
36
- }
37
- });
38
- });
package/src/lib/input.ts DELETED
@@ -1,43 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
-
3
- /**
4
- * Read all data from stdin as a UTF-8 string.
5
- *
6
- * @returns Trimmed stdin contents
7
- */
8
- async function readStdin(): Promise<string> {
9
- const chunks: Buffer[] = [];
10
- for await (const chunk of process.stdin) {
11
- chunks.push(Buffer.from(chunk));
12
- }
13
- return Buffer.concat(chunks).toString('utf-8').trim();
14
- }
15
-
16
- /**
17
- * Resolve the transaction XDR from a positional argument, a file path, or stdin.
18
- * Precedence: --file > positional arg > stdin.
19
- *
20
- * @param txArg - Positional tx argument, if provided
21
- * @param filePath - Path to a file containing the base64 XDR, if provided
22
- * @returns Trimmed base64-encoded transaction XDR
23
- * @throws If no XDR could be resolved from any source
24
- */
25
- export async function resolveTxInput(txArg?: string, filePath?: string): Promise<string> {
26
- if (filePath) {
27
- const contents = await readFile(filePath, 'utf-8');
28
- return contents.trim();
29
- }
30
-
31
- if (txArg && txArg.trim().length > 0) {
32
- return txArg.trim();
33
- }
34
-
35
- if (!process.stdin.isTTY) {
36
- const fromStdin = await readStdin();
37
- if (fromStdin.length > 0) return fromStdin;
38
- }
39
-
40
- throw new Error(
41
- 'No transaction XDR provided. Pass it as an argument, via --file <path>, or pipe it over stdin.',
42
- );
43
- }
@@ -1,60 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { writeFile, mkdtemp, rm } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { loadManifest } from './manifest.js';
6
-
7
- describe('loadManifest', () => {
8
- it('returns undefined when no path is given', async () => {
9
- const result = await loadManifest(undefined);
10
- expect(result).toBeUndefined();
11
- });
12
-
13
- it('parses a valid ecosystem manifest file', async () => {
14
- const dir = await mkdtemp(join(tmpdir(), 'meridian-cli-'));
15
- const filePath = join(dir, 'manifest.json');
16
- const manifest = {
17
- name: 'test-ecosystem',
18
- version: '1.0.0',
19
- contracts: [{ name: 'foo', address: 'CFOO', network: 'testnet' }],
20
- };
21
- await writeFile(filePath, JSON.stringify(manifest));
22
-
23
- try {
24
- const result = await loadManifest(filePath);
25
- expect(result).toEqual(manifest);
26
- } finally {
27
- await rm(dir, { recursive: true, force: true });
28
- }
29
- });
30
-
31
- it('throws for a missing file', async () => {
32
- await expect(loadManifest('/nonexistent/manifest.json')).rejects.toThrow(
33
- /Failed to read ecosystem manifest/,
34
- );
35
- });
36
-
37
- it('throws for invalid JSON', async () => {
38
- const dir = await mkdtemp(join(tmpdir(), 'meridian-cli-'));
39
- const filePath = join(dir, 'manifest.json');
40
- await writeFile(filePath, '{ not valid json');
41
-
42
- try {
43
- await expect(loadManifest(filePath)).rejects.toThrow(/not valid JSON/);
44
- } finally {
45
- await rm(dir, { recursive: true, force: true });
46
- }
47
- });
48
-
49
- it('throws when contracts field is missing', async () => {
50
- const dir = await mkdtemp(join(tmpdir(), 'meridian-cli-'));
51
- const filePath = join(dir, 'manifest.json');
52
- await writeFile(filePath, JSON.stringify({ name: 'x', version: '1.0.0' }));
53
-
54
- try {
55
- await expect(loadManifest(filePath)).rejects.toThrow(/must be an object with a "contracts"/);
56
- } finally {
57
- await rm(dir, { recursive: true, force: true });
58
- }
59
- });
60
- });
@@ -1,41 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import type { EcosystemManifest } from '@meridian/core';
3
-
4
- /**
5
- * Load and parse an ecosystem manifest JSON file.
6
- *
7
- * @param filePath - Path to the manifest JSON file
8
- * @returns Parsed EcosystemManifest, or undefined if no path was given
9
- * @throws If the file cannot be read or does not contain valid JSON
10
- */
11
- export async function loadManifest(filePath?: string): Promise<EcosystemManifest | undefined> {
12
- if (!filePath) return undefined;
13
-
14
- let raw: string;
15
- try {
16
- raw = await readFile(filePath, 'utf-8');
17
- } catch (err) {
18
- const message = err instanceof Error ? err.message : String(err);
19
- throw new Error(`Failed to read ecosystem manifest at ${filePath}: ${message}`);
20
- }
21
-
22
- let parsed: unknown;
23
- try {
24
- parsed = JSON.parse(raw);
25
- } catch (err) {
26
- const message = err instanceof Error ? err.message : String(err);
27
- throw new Error(`Ecosystem manifest at ${filePath} is not valid JSON: ${message}`);
28
- }
29
-
30
- if (
31
- typeof parsed !== 'object' ||
32
- parsed === null ||
33
- !Array.isArray((parsed as Record<string, unknown>).contracts)
34
- ) {
35
- throw new Error(
36
- `Ecosystem manifest at ${filePath} must be an object with a "contracts" array.`,
37
- );
38
- }
39
-
40
- return parsed as EcosystemManifest;
41
- }
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { parseNetwork, parseThreshold } from './options.js';
3
-
4
- describe('parseNetwork', () => {
5
- it('accepts mainnet and testnet', () => {
6
- expect(parseNetwork('mainnet')).toBe('mainnet');
7
- expect(parseNetwork('testnet')).toBe('testnet');
8
- });
9
-
10
- it('rejects invalid networks', () => {
11
- expect(() => parseNetwork('devnet')).toThrow();
12
- });
13
- });
14
-
15
- describe('parseThreshold', () => {
16
- it('accepts numbers within 0 and 1', () => {
17
- expect(parseThreshold('0')).toBe(0);
18
- expect(parseThreshold('0.75')).toBe(0.75);
19
- expect(parseThreshold('1')).toBe(1);
20
- });
21
-
22
- it('rejects out-of-range and non-numeric values', () => {
23
- expect(() => parseThreshold('1.5')).toThrow();
24
- expect(() => parseThreshold('-0.1')).toThrow();
25
- expect(() => parseThreshold('abc')).toThrow();
26
- });
27
- });
@@ -1,47 +0,0 @@
1
- import { Command, InvalidArgumentError } from 'commander';
2
- import type { Network } from '@meridian/core';
3
-
4
- /**
5
- * Parse and validate a --network option value.
6
- *
7
- * @param value - Raw CLI argument
8
- * @returns Validated Network
9
- * @throws InvalidArgumentError if the value is not "mainnet" or "testnet"
10
- */
11
- export function parseNetwork(value: string): Network {
12
- if (value !== 'mainnet' && value !== 'testnet') {
13
- throw new InvalidArgumentError('Network must be "mainnet" or "testnet".');
14
- }
15
- return value;
16
- }
17
-
18
- /**
19
- * Parse and validate a --confidence-threshold option value.
20
- *
21
- * @param value - Raw CLI argument
22
- * @returns Parsed float between 0 and 1
23
- * @throws InvalidArgumentError if the value is not a number in range
24
- */
25
- export function parseThreshold(value: string): number {
26
- const parsed = Number.parseFloat(value);
27
- if (Number.isNaN(parsed) || parsed < 0 || parsed > 1) {
28
- throw new InvalidArgumentError('Confidence threshold must be a number between 0 and 1.');
29
- }
30
- return parsed;
31
- }
32
-
33
- /**
34
- * Attach the options shared by every layer command (network, RPC, input, output).
35
- *
36
- * @param command - Commander command to extend
37
- * @returns The same command, for chaining
38
- */
39
- export function withCommonOptions(command: Command): Command {
40
- return command
41
- .argument('[tx]', 'Base64-encoded transaction XDR')
42
- .option('-n, --network <network>', 'Stellar network (mainnet | testnet)', parseNetwork, 'testnet')
43
- .option('--rpc-url <url>', 'Override the Soroban RPC endpoint (else read from env)')
44
- .option('-f, --file <path>', 'Read the transaction XDR from a file instead of an argument')
45
- .option('-e, --ecosystem <path>', 'Path to an ecosystem manifest JSON file')
46
- .option('--json', 'Print raw JSON instead of a formatted report');
47
- }
package/src/lib/output.ts DELETED
@@ -1,180 +0,0 @@
1
- import pc from 'picocolors';
2
- import type {
3
- AnalyzeResponse,
4
- FieldResult,
5
- GravityResult,
6
- TraceResult,
7
- Verdict,
8
- } from '@meridian/core';
9
-
10
- /**
11
- * Print any value as pretty-printed JSON.
12
- *
13
- * @param value - Value to serialize
14
- */
15
- export function printJson(value: unknown): void {
16
- console.log(JSON.stringify(value, null, 2));
17
- }
18
-
19
- /**
20
- * Colorize a verdict badge.
21
- *
22
- * @param verdict - MERIDIAN verdict
23
- * @returns Colorized verdict string
24
- */
25
- function verdictBadge(verdict: Verdict): string {
26
- switch (verdict) {
27
- case 'CLEAR':
28
- return pc.bgGreen(pc.black(' CLEAR '));
29
- case 'WARN':
30
- return pc.bgYellow(pc.black(' WARN '));
31
- case 'ABORT':
32
- return pc.bgRed(pc.white(pc.bold(' ABORT ')));
33
- default:
34
- return verdict;
35
- }
36
- }
37
-
38
- /**
39
- * Print a section header.
40
- *
41
- * @param title - Section title
42
- */
43
- function section(title: string): void {
44
- console.log('');
45
- console.log(pc.bold(pc.cyan(`── ${title} `.padEnd(48, '─'))));
46
- }
47
-
48
- /**
49
- * Print a labeled key/value line.
50
- *
51
- * @param label - Field label
52
- * @param value - Field value
53
- */
54
- function field(label: string, value: unknown): void {
55
- console.log(` ${pc.dim(label + ':')} ${value}`);
56
- }
57
-
58
- /**
59
- * Print a TraceResult in human-readable form.
60
- *
61
- * @param trace - TRACE layer result
62
- */
63
- export function printTrace(trace: TraceResult): void {
64
- section('TRACE');
65
- field('success', trace.success ? pc.green('true') : pc.red('false'));
66
- if (trace.staleness_warning) {
67
- field('staleness_warning', pc.yellow('true'));
68
- }
69
- if (trace.failure_point) {
70
- const fp = trace.failure_point;
71
- console.log(` ${pc.red('failure_point')}:`);
72
- field(' step_index', fp.step_index);
73
- if (fp.contract_id) field(' contract_id', fp.contract_id);
74
- if (fp.function_name) field(' function_name', fp.function_name);
75
- field(' error_code', fp.error_code);
76
- field(' root_cause', fp.root_cause);
77
- }
78
- field('execution_path', `${trace.execution_path.length} step(s)`);
79
- field('auth_entries', `${trace.auth_entries.length} entrie(s)`);
80
- field(
81
- 'fee_estimate',
82
- `total=${trace.fee_estimate.total_fee} base=${trace.fee_estimate.classic_base_fee} min_resource=${trace.fee_estimate.min_resource_fee}`,
83
- );
84
- field(
85
- 'resource_usage',
86
- `cpu=${trace.resource_usage.cpu_instructions} mem=${trace.resource_usage.memory_bytes}b read=${trace.resource_usage.read_bytes}b write=${trace.resource_usage.write_bytes}b`,
87
- );
88
- }
89
-
90
- /**
91
- * Print a FieldResult in human-readable form.
92
- *
93
- * @param result - FIELD layer result
94
- */
95
- export function printField(result: FieldResult): void {
96
- section('FIELD');
97
- field('contracts_mapped', result.contracts_mapped);
98
- field('manifest_coverage', `${Math.round(result.manifest_coverage * 100)}%`);
99
- if (result.ttl_warnings.length > 0) {
100
- console.log(` ${pc.yellow('ttl_warnings')}:`);
101
- for (const warning of result.ttl_warnings) {
102
- console.log(
103
- ` - ${warning.contract_id} (${warning.ledger_key}) ttl_remaining=${warning.ttl_remaining} [${warning.severity}]`,
104
- );
105
- }
106
- }
107
- if (result.dependency_graph.length > 0) {
108
- console.log(` ${pc.dim('dependency_graph')}:`);
109
- for (const node of result.dependency_graph) {
110
- const label = node.name ? `${node.name} (${node.address})` : node.address;
111
- const deps = node.dependencies.length > 0 ? ` → ${node.dependencies.join(', ')}` : '';
112
- console.log(` - ${label}${deps}`);
113
- }
114
- }
115
- }
116
-
117
- /**
118
- * Print a GravityResult in human-readable form.
119
- *
120
- * @param result - GRAVITY layer result
121
- */
122
- export function printGravity(result: GravityResult): void {
123
- section('GRAVITY');
124
- field('blast_radius', result.blast_radius);
125
- field('total_affected_users', result.total_affected_users);
126
- field('recovery', result.recovery);
127
- if (result.critical.length > 0) field('critical', pc.red(result.critical.join(', ')));
128
- if (result.warning.length > 0) field('warning', pc.yellow(result.warning.join(', ')));
129
- if (result.monitor.length > 0) field('monitor', pc.blue(result.monitor.join(', ')));
130
- if (result.safe.length > 0) field('safe', pc.green(result.safe.join(', ')));
131
-
132
- if (result.affected_contracts.length > 0) {
133
- console.log(` ${pc.dim('affected_contracts')}:`);
134
- for (const contract of result.affected_contracts) {
135
- const label = contract.name ? `${contract.name} (${contract.address})` : contract.address;
136
- console.log(` - [${contract.impact}] ${label} — ${contract.reason}`);
137
- }
138
- }
139
- }
140
-
141
- /**
142
- * Print a full AnalyzeResponse in human-readable form.
143
- *
144
- * @param response - Full analysis response including brief
145
- */
146
- export function printAnalysis(response: AnalyzeResponse): void {
147
- console.log('');
148
- console.log(`${pc.bold('MERIDIAN')} v${response.version} ${verdictBadge(response.verdict)} confidence=${response.confidence}`);
149
-
150
- printTrace(response.trace);
151
- printField(response.field);
152
- printGravity(response.gravity);
153
-
154
- if (response.fix_sequence && response.fix_sequence.length > 0) {
155
- section('FIX SEQUENCE');
156
- for (const step of response.fix_sequence) {
157
- console.log(
158
- ` ${pc.bold(String(step.order) + '.')} ${step.operation} — ${step.description} ${pc.dim(`(~${step.estimated_cost_stroops} stroops, ~${step.estimated_time_minutes}min)`)}`,
159
- );
160
- }
161
- }
162
-
163
- if (response.warnings && response.warnings.length > 0) {
164
- section('WARNINGS');
165
- for (const warning of response.warnings) {
166
- console.log(` ${pc.yellow('⚠')} ${warning}`);
167
- }
168
- }
169
-
170
- section('BRIEF');
171
- console.log(response.brief);
172
-
173
- section('META');
174
- field('analyzed_at', response.meta.analyzed_at);
175
- field('network', response.meta.network);
176
- field('ledger_sequence', response.meta.ledger_sequence);
177
- field('simulation_stale', response.meta.simulation_stale);
178
- field('processing_ms', response.meta.processing_ms);
179
- console.log('');
180
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist", "**/*.test.ts"],
9
- "references": [{ "path": "../core" }, { "path": "../ai" }]
10
- }
package/vitest.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- },
8
- });