@spectratools/cli-shared 0.1.1 → 0.2.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.
@@ -0,0 +1,15 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ var __commonJS = (cb, mod) => function __require2() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+
12
+ export {
13
+ __require,
14
+ __commonJS
15
+ };
@@ -9,6 +9,9 @@ function weiToEth(wei, decimals = 6) {
9
9
  const fracStr = frac.toString().padStart(18, "0").slice(0, decimals).replace(/0+$/, "");
10
10
  return fracStr.length > 0 ? `${whole}.${fracStr}` : `${whole}`;
11
11
  }
12
+ function isAddress(address) {
13
+ return Address.validate(address);
14
+ }
12
15
  function checksumAddress(address) {
13
16
  return Address.checksum(address);
14
17
  }
@@ -22,6 +25,7 @@ function truncate(str, prefixLen = 6, suffixLen = 4) {
22
25
 
23
26
  export {
24
27
  weiToEth,
28
+ isAddress,
25
29
  checksumAddress,
26
30
  formatTimestamp,
27
31
  truncate
@@ -0,0 +1,201 @@
1
+ import {
2
+ __commonJS,
3
+ __require
4
+ } from "./chunk-MCKGQKYU.js";
5
+
6
+ // package.json
7
+ var require_package = __commonJS({
8
+ "package.json"(exports, module) {
9
+ module.exports = {
10
+ name: "@spectratools/cli-shared",
11
+ version: "0.2.0",
12
+ description: "Shared middleware, utilities, and testing helpers for spectra CLI tools",
13
+ type: "module",
14
+ license: "MIT",
15
+ author: "spectra-the-bot",
16
+ engines: {
17
+ node: ">=20"
18
+ },
19
+ exports: {
20
+ ".": {
21
+ types: "./dist/index.d.ts",
22
+ default: "./dist/index.js"
23
+ },
24
+ "./middleware": {
25
+ types: "./dist/middleware/index.d.ts",
26
+ default: "./dist/middleware/index.js"
27
+ },
28
+ "./utils": {
29
+ types: "./dist/utils/index.d.ts",
30
+ default: "./dist/utils/index.js"
31
+ },
32
+ "./telemetry": {
33
+ types: "./dist/telemetry/index.d.ts",
34
+ default: "./dist/telemetry/index.js"
35
+ },
36
+ "./testing": {
37
+ types: "./dist/testing/index.d.ts",
38
+ default: "./dist/testing/index.js"
39
+ }
40
+ },
41
+ scripts: {
42
+ build: "tsup",
43
+ typecheck: "tsc --noEmit -p tsconfig.json",
44
+ test: "vitest run",
45
+ prepublishOnly: "pnpm build"
46
+ },
47
+ devDependencies: {
48
+ typescript: "5.7.3",
49
+ vitest: "2.1.8"
50
+ },
51
+ dependencies: {
52
+ "@opentelemetry/api": "^1.9.0",
53
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
54
+ "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
55
+ "@opentelemetry/resources": "^2.6.0",
56
+ "@opentelemetry/sdk-metrics": "^2.6.0",
57
+ "@opentelemetry/sdk-node": "^0.213.0",
58
+ "@opentelemetry/sdk-trace-node": "^2.6.0",
59
+ "@opentelemetry/semantic-conventions": "^1.40.0",
60
+ ox: "^0.14.0"
61
+ },
62
+ files: ["dist", "README.md"],
63
+ main: "./dist/index.js",
64
+ types: "./dist/index.d.ts"
65
+ };
66
+ }
67
+ });
68
+
69
+ // src/telemetry/init.ts
70
+ var sdk;
71
+ var initialized = false;
72
+ function isOtelEnabled() {
73
+ return !!(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.SPECTRA_OTEL_ENABLED === "true");
74
+ }
75
+ function initTelemetry(serviceName) {
76
+ if (initialized) return;
77
+ initialized = true;
78
+ if (!isOtelEnabled()) return;
79
+ const sdkMod = __require("@opentelemetry/sdk-node");
80
+ const traceExpMod = __require("@opentelemetry/exporter-trace-otlp-http");
81
+ const metricExpMod = __require("@opentelemetry/exporter-metrics-otlp-http");
82
+ const metricsMod = __require("@opentelemetry/sdk-metrics");
83
+ const resourcesMod = __require("@opentelemetry/resources");
84
+ const semconvMod = __require("@opentelemetry/semantic-conventions");
85
+ let version = "0.0.0";
86
+ try {
87
+ const pkg = require_package();
88
+ version = pkg.version ?? version;
89
+ } catch {
90
+ }
91
+ const resolvedServiceName = process.env.OTEL_SERVICE_NAME || `spectra-${serviceName}`;
92
+ const resource = resourcesMod.resourceFromAttributes({
93
+ [semconvMod.ATTR_SERVICE_NAME]: resolvedServiceName,
94
+ [semconvMod.ATTR_SERVICE_VERSION]: version
95
+ });
96
+ const traceExporter = new traceExpMod.OTLPTraceExporter();
97
+ const metricExporter = new metricExpMod.OTLPMetricExporter();
98
+ sdk = new sdkMod.NodeSDK({
99
+ resource,
100
+ traceExporter,
101
+ metricReader: new metricsMod.PeriodicExportingMetricReader({
102
+ exporter: metricExporter
103
+ })
104
+ });
105
+ sdk.start();
106
+ const shutdown = () => {
107
+ sdk?.shutdown().then(
108
+ () => process.exit(0),
109
+ () => process.exit(1)
110
+ );
111
+ };
112
+ process.on("SIGTERM", shutdown);
113
+ process.on("SIGINT", shutdown);
114
+ }
115
+ function _getSdk() {
116
+ return sdk;
117
+ }
118
+
119
+ // src/telemetry/spans.ts
120
+ import { SpanStatusCode, context, trace } from "@opentelemetry/api";
121
+ var TRACER_NAME = "@spectratools/cli-shared";
122
+ var SENSITIVE_PATTERNS = [
123
+ /private[\s_-]?key/i,
124
+ /secret/i,
125
+ /password/i,
126
+ /passphrase/i,
127
+ /mnemonic/i,
128
+ /seed/i,
129
+ /token/i,
130
+ /api[\s_-]?key/i,
131
+ /auth/i
132
+ ];
133
+ function sanitizeAttributes(attrs) {
134
+ const result = {};
135
+ for (const [key, value] of Object.entries(attrs)) {
136
+ const strKey = key.toLowerCase();
137
+ const strVal = typeof value === "string" ? value : "";
138
+ const isSensitive = SENSITIVE_PATTERNS.some(
139
+ (p) => p.test(strKey) || strVal.length > 0 && p.test(strVal)
140
+ );
141
+ if (isSensitive) continue;
142
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
143
+ result[key] = value;
144
+ } else if (value !== void 0 && value !== null) {
145
+ result[key] = String(value);
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+ function createCommandSpan(commandName, args) {
151
+ const tracer = trace.getTracer(TRACER_NAME);
152
+ const span = tracer.startSpan(`cli.command.${commandName}`);
153
+ if (args) {
154
+ const safe = sanitizeAttributes(args);
155
+ for (const [k, v] of Object.entries(safe)) {
156
+ span.setAttribute(`cli.arg.${k}`, v);
157
+ }
158
+ }
159
+ return span;
160
+ }
161
+ async function withSpan(name, fn) {
162
+ const tracer = trace.getTracer(TRACER_NAME);
163
+ const span = tracer.startSpan(name);
164
+ const ctx = trace.setSpan(context.active(), span);
165
+ try {
166
+ const result = await context.with(ctx, () => fn(span));
167
+ span.setStatus({ code: SpanStatusCode.OK });
168
+ return result;
169
+ } catch (err) {
170
+ recordError(span, err);
171
+ throw err;
172
+ } finally {
173
+ span.end();
174
+ }
175
+ }
176
+ function recordError(span, error) {
177
+ span.setStatus({
178
+ code: SpanStatusCode.ERROR,
179
+ message: error instanceof Error ? error.message : String(error)
180
+ });
181
+ if (error instanceof Error) {
182
+ span.recordException(error);
183
+ } else {
184
+ span.recordException(new Error(String(error)));
185
+ }
186
+ }
187
+
188
+ // src/telemetry/shutdown.ts
189
+ async function shutdownTelemetry() {
190
+ const sdk2 = _getSdk();
191
+ if (!sdk2) return;
192
+ await sdk2.shutdown();
193
+ }
194
+
195
+ export {
196
+ initTelemetry,
197
+ createCommandSpan,
198
+ withSpan,
199
+ recordError,
200
+ shutdownTelemetry
201
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { ApiKeyAuthContext, CursorPaginationOptions, MissingApiKeyError, OffsetPaginationOptions, RateLimitOptions, RetryOptions, apiKeyAuth, createRateLimiter, paginateCursor, paginateOffset, withRateLimit, withRetry } from './middleware/index.js';
2
+ export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan } from './telemetry/index.js';
2
3
  export { MockResponse, MockServer, RecordedRequest, createMockServer } from './testing/index.js';
3
- export { HttpClientOptions, HttpError, RequestOptions, checksumAddress, createHttpClient, formatTimestamp, truncate, weiToEth } from './utils/index.js';
4
+ export { HttpClientOptions, HttpError, RequestOptions, checksumAddress, createHttpClient, formatTimestamp, isAddress, truncate, weiToEth } from './utils/index.js';
5
+ import '@opentelemetry/api';
package/dist/index.js CHANGED
@@ -7,32 +7,47 @@ import {
7
7
  withRateLimit,
8
8
  withRetry
9
9
  } from "./chunk-74BQLM22.js";
10
+ import {
11
+ createCommandSpan,
12
+ initTelemetry,
13
+ recordError,
14
+ shutdownTelemetry,
15
+ withSpan
16
+ } from "./chunk-YWGC33UJ.js";
17
+ import {
18
+ createMockServer
19
+ } from "./chunk-QEANTXUE.js";
10
20
  import {
11
21
  checksumAddress,
12
22
  formatTimestamp,
23
+ isAddress,
13
24
  truncate,
14
25
  weiToEth
15
- } from "./chunk-IW62SQFU.js";
26
+ } from "./chunk-OPDNZWSI.js";
16
27
  import {
17
28
  HttpError,
18
29
  createHttpClient
19
30
  } from "./chunk-TKORXDDX.js";
20
- import {
21
- createMockServer
22
- } from "./chunk-QEANTXUE.js";
31
+ import "./chunk-MCKGQKYU.js";
23
32
  export {
24
33
  HttpError,
25
34
  MissingApiKeyError,
26
35
  apiKeyAuth,
27
36
  checksumAddress,
37
+ createCommandSpan,
28
38
  createHttpClient,
29
39
  createMockServer,
30
40
  createRateLimiter,
31
41
  formatTimestamp,
42
+ initTelemetry,
43
+ isAddress,
32
44
  paginateCursor,
33
45
  paginateOffset,
46
+ recordError,
47
+ shutdownTelemetry,
34
48
  truncate,
35
49
  weiToEth,
36
50
  withRateLimit,
37
- withRetry
51
+ withRetry,
52
+ withSpan
38
53
  };
@@ -8,6 +8,7 @@ import {
8
8
  withRetry
9
9
  } from "../chunk-74BQLM22.js";
10
10
  import "../chunk-TKORXDDX.js";
11
+ import "../chunk-MCKGQKYU.js";
11
12
  export {
12
13
  MissingApiKeyError,
13
14
  apiKeyAuth,
@@ -0,0 +1,47 @@
1
+ import { Span } from '@opentelemetry/api';
2
+
3
+ /**
4
+ * Initialize OpenTelemetry SDK for a CLI service.
5
+ *
6
+ * This is a **lazy** initializer — if neither `OTEL_EXPORTER_OTLP_ENDPOINT` nor
7
+ * `SPECTRA_OTEL_ENABLED=true` is set, it returns immediately without importing
8
+ * any OTEL modules, ensuring zero overhead when instrumentation is disabled.
9
+ *
10
+ * @param serviceName - Logical name of the CLI service (used as fallback for `service.name`).
11
+ */
12
+ declare function initTelemetry(serviceName: string): void;
13
+
14
+ /**
15
+ * Create a root span for a CLI command invocation.
16
+ *
17
+ * @param commandName - The command being executed (e.g. `"account balance"`).
18
+ * @param args - Optional key-value arguments to attach as span attributes (sensitive values are stripped).
19
+ * @returns A started `Span`. The caller is responsible for calling `span.end()`.
20
+ */
21
+ declare function createCommandSpan(commandName: string, args?: Record<string, unknown>): Span;
22
+ /**
23
+ * Convenience wrapper that creates a child span, runs the given async function,
24
+ * and automatically ends the span. On error, the error is recorded on the span
25
+ * before being re-thrown.
26
+ *
27
+ * @param name - Span name.
28
+ * @param fn - Async function to execute within the span context.
29
+ * @returns The result of `fn`.
30
+ */
31
+ declare function withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
32
+ /**
33
+ * Attach error details and stack trace to a span and mark it as errored.
34
+ *
35
+ * @param span - The span to annotate.
36
+ * @param error - The error value (may be an `Error` instance or arbitrary value).
37
+ */
38
+ declare function recordError(span: Span, error: unknown): void;
39
+
40
+ /**
41
+ * Flush pending spans/metrics and shut down the OTEL SDK.
42
+ *
43
+ * Safe to call even if telemetry was never initialized — in that case it is a no-op.
44
+ */
45
+ declare function shutdownTelemetry(): Promise<void>;
46
+
47
+ export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan };
@@ -0,0 +1,15 @@
1
+ import {
2
+ createCommandSpan,
3
+ initTelemetry,
4
+ recordError,
5
+ shutdownTelemetry,
6
+ withSpan
7
+ } from "../chunk-YWGC33UJ.js";
8
+ import "../chunk-MCKGQKYU.js";
9
+ export {
10
+ createCommandSpan,
11
+ initTelemetry,
12
+ recordError,
13
+ shutdownTelemetry,
14
+ withSpan
15
+ };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  createMockServer
3
3
  } from "../chunk-QEANTXUE.js";
4
+ import "../chunk-MCKGQKYU.js";
4
5
  export {
5
6
  createMockServer
6
7
  };
@@ -2,6 +2,10 @@
2
2
  * Converts wei (as bigint or string) to a human-readable ETH string.
3
3
  */
4
4
  declare function weiToEth(wei: bigint | string, decimals?: number): string;
5
+ /**
6
+ * Returns true if the value is a valid 0x-prefixed 20-byte hex address.
7
+ */
8
+ declare function isAddress(address: string): boolean;
5
9
  /**
6
10
  * Checksums an Ethereum address using EIP-55.
7
11
  * Accepts lowercase or mixed-case hex addresses.
@@ -40,4 +44,4 @@ declare function createHttpClient(options: HttpClientOptions): {
40
44
  request: <T>(path: string, opts?: RequestOptions) => Promise<T>;
41
45
  };
42
46
 
43
- export { type HttpClientOptions, HttpError, type RequestOptions, checksumAddress, createHttpClient, formatTimestamp, truncate, weiToEth };
47
+ export { type HttpClientOptions, HttpError, type RequestOptions, checksumAddress, createHttpClient, formatTimestamp, isAddress, truncate, weiToEth };
@@ -1,18 +1,21 @@
1
1
  import {
2
2
  checksumAddress,
3
3
  formatTimestamp,
4
+ isAddress,
4
5
  truncate,
5
6
  weiToEth
6
- } from "../chunk-IW62SQFU.js";
7
+ } from "../chunk-OPDNZWSI.js";
7
8
  import {
8
9
  HttpError,
9
10
  createHttpClient
10
11
  } from "../chunk-TKORXDDX.js";
12
+ import "../chunk-MCKGQKYU.js";
11
13
  export {
12
14
  HttpError,
13
15
  checksumAddress,
14
16
  createHttpClient,
15
17
  formatTimestamp,
18
+ isAddress,
16
19
  truncate,
17
20
  weiToEth
18
21
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectratools/cli-shared",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Shared middleware, utilities, and testing helpers for spectra CLI tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,6 +21,10 @@
21
21
  "types": "./dist/utils/index.d.ts",
22
22
  "default": "./dist/utils/index.js"
23
23
  },
24
+ "./telemetry": {
25
+ "types": "./dist/telemetry/index.d.ts",
26
+ "default": "./dist/telemetry/index.js"
27
+ },
24
28
  "./testing": {
25
29
  "types": "./dist/testing/index.d.ts",
26
30
  "default": "./dist/testing/index.js"
@@ -31,6 +35,14 @@
31
35
  "vitest": "2.1.8"
32
36
  },
33
37
  "dependencies": {
38
+ "@opentelemetry/api": "^1.9.0",
39
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
40
+ "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
41
+ "@opentelemetry/resources": "^2.6.0",
42
+ "@opentelemetry/sdk-metrics": "^2.6.0",
43
+ "@opentelemetry/sdk-node": "^0.213.0",
44
+ "@opentelemetry/sdk-trace-node": "^2.6.0",
45
+ "@opentelemetry/semantic-conventions": "^1.40.0",
34
46
  "ox": "^0.14.0"
35
47
  },
36
48
  "files": [