@originals/sdk 1.8.1 → 1.8.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 (145) hide show
  1. package/dist/utils/hash.js +1 -0
  2. package/package.json +6 -5
  3. package/src/adapters/FeeOracleMock.ts +9 -0
  4. package/src/adapters/index.ts +5 -0
  5. package/src/adapters/providers/OrdHttpProvider.ts +126 -0
  6. package/src/adapters/providers/OrdMockProvider.ts +101 -0
  7. package/src/adapters/types.ts +66 -0
  8. package/src/bitcoin/BitcoinManager.ts +329 -0
  9. package/src/bitcoin/BroadcastClient.ts +54 -0
  10. package/src/bitcoin/OrdinalsClient.ts +120 -0
  11. package/src/bitcoin/PSBTBuilder.ts +106 -0
  12. package/src/bitcoin/fee-calculation.ts +38 -0
  13. package/src/bitcoin/providers/OrdNodeProvider.ts +92 -0
  14. package/src/bitcoin/providers/OrdinalsProvider.ts +56 -0
  15. package/src/bitcoin/providers/types.ts +59 -0
  16. package/src/bitcoin/transactions/commit.ts +465 -0
  17. package/src/bitcoin/transactions/index.ts +13 -0
  18. package/src/bitcoin/transfer.ts +43 -0
  19. package/src/bitcoin/utxo-selection.ts +322 -0
  20. package/src/bitcoin/utxo.ts +113 -0
  21. package/src/cel/ExternalReferenceManager.ts +87 -0
  22. package/src/cel/OriginalsCel.ts +460 -0
  23. package/src/cel/algorithms/createEventLog.ts +68 -0
  24. package/src/cel/algorithms/deactivateEventLog.ts +109 -0
  25. package/src/cel/algorithms/index.ts +11 -0
  26. package/src/cel/algorithms/updateEventLog.ts +99 -0
  27. package/src/cel/algorithms/verifyEventLog.ts +306 -0
  28. package/src/cel/algorithms/witnessEvent.ts +87 -0
  29. package/src/cel/cli/create.ts +330 -0
  30. package/src/cel/cli/index.ts +383 -0
  31. package/src/cel/cli/inspect.ts +549 -0
  32. package/src/cel/cli/migrate.ts +473 -0
  33. package/src/cel/cli/verify.ts +249 -0
  34. package/src/cel/hash.ts +71 -0
  35. package/src/cel/index.ts +16 -0
  36. package/src/cel/layers/BtcoCelManager.ts +408 -0
  37. package/src/cel/layers/PeerCelManager.ts +371 -0
  38. package/src/cel/layers/WebVHCelManager.ts +361 -0
  39. package/src/cel/layers/index.ts +27 -0
  40. package/src/cel/serialization/cbor.ts +189 -0
  41. package/src/cel/serialization/index.ts +10 -0
  42. package/src/cel/serialization/json.ts +209 -0
  43. package/src/cel/types.ts +160 -0
  44. package/src/cel/witnesses/BitcoinWitness.ts +184 -0
  45. package/src/cel/witnesses/HttpWitness.ts +241 -0
  46. package/src/cel/witnesses/WitnessService.ts +51 -0
  47. package/src/cel/witnesses/index.ts +11 -0
  48. package/src/contexts/credentials-v1.json +237 -0
  49. package/src/contexts/credentials-v2-examples.json +5 -0
  50. package/src/contexts/credentials-v2.json +340 -0
  51. package/src/contexts/credentials.json +237 -0
  52. package/src/contexts/data-integrity-v2.json +81 -0
  53. package/src/contexts/dids.json +58 -0
  54. package/src/contexts/ed255192020.json +93 -0
  55. package/src/contexts/ordinals-plus.json +23 -0
  56. package/src/contexts/originals.json +22 -0
  57. package/src/core/OriginalsSDK.ts +420 -0
  58. package/src/crypto/Multikey.ts +194 -0
  59. package/src/crypto/Signer.ts +262 -0
  60. package/src/crypto/noble-init.ts +138 -0
  61. package/src/did/BtcoDidResolver.ts +231 -0
  62. package/src/did/DIDManager.ts +705 -0
  63. package/src/did/Ed25519Verifier.ts +68 -0
  64. package/src/did/KeyManager.ts +239 -0
  65. package/src/did/WebVHManager.ts +499 -0
  66. package/src/did/createBtcoDidDocument.ts +60 -0
  67. package/src/did/providers/OrdinalsClientProviderAdapter.ts +68 -0
  68. package/src/events/EventEmitter.ts +222 -0
  69. package/src/events/index.ts +19 -0
  70. package/src/events/types.ts +331 -0
  71. package/src/examples/basic-usage.ts +78 -0
  72. package/src/examples/create-module-original.ts +435 -0
  73. package/src/examples/full-lifecycle-flow.ts +514 -0
  74. package/src/examples/run.ts +60 -0
  75. package/src/index.ts +204 -0
  76. package/src/kinds/KindRegistry.ts +320 -0
  77. package/src/kinds/index.ts +74 -0
  78. package/src/kinds/types.ts +470 -0
  79. package/src/kinds/validators/AgentValidator.ts +257 -0
  80. package/src/kinds/validators/AppValidator.ts +211 -0
  81. package/src/kinds/validators/DatasetValidator.ts +242 -0
  82. package/src/kinds/validators/DocumentValidator.ts +311 -0
  83. package/src/kinds/validators/MediaValidator.ts +269 -0
  84. package/src/kinds/validators/ModuleValidator.ts +225 -0
  85. package/src/kinds/validators/base.ts +276 -0
  86. package/src/kinds/validators/index.ts +12 -0
  87. package/src/lifecycle/BatchOperations.ts +381 -0
  88. package/src/lifecycle/LifecycleManager.ts +2156 -0
  89. package/src/lifecycle/OriginalsAsset.ts +524 -0
  90. package/src/lifecycle/ProvenanceQuery.ts +280 -0
  91. package/src/lifecycle/ResourceVersioning.ts +163 -0
  92. package/src/migration/MigrationManager.ts +587 -0
  93. package/src/migration/audit/AuditLogger.ts +176 -0
  94. package/src/migration/checkpoint/CheckpointManager.ts +112 -0
  95. package/src/migration/checkpoint/CheckpointStorage.ts +101 -0
  96. package/src/migration/index.ts +33 -0
  97. package/src/migration/operations/BaseMigration.ts +126 -0
  98. package/src/migration/operations/PeerToBtcoMigration.ts +105 -0
  99. package/src/migration/operations/PeerToWebvhMigration.ts +62 -0
  100. package/src/migration/operations/WebvhToBtcoMigration.ts +105 -0
  101. package/src/migration/rollback/RollbackManager.ts +170 -0
  102. package/src/migration/state/StateMachine.ts +92 -0
  103. package/src/migration/state/StateTracker.ts +156 -0
  104. package/src/migration/types.ts +356 -0
  105. package/src/migration/validation/BitcoinValidator.ts +107 -0
  106. package/src/migration/validation/CredentialValidator.ts +62 -0
  107. package/src/migration/validation/DIDCompatibilityValidator.ts +151 -0
  108. package/src/migration/validation/LifecycleValidator.ts +64 -0
  109. package/src/migration/validation/StorageValidator.ts +79 -0
  110. package/src/migration/validation/ValidationPipeline.ts +213 -0
  111. package/src/resources/ResourceManager.ts +655 -0
  112. package/src/resources/index.ts +21 -0
  113. package/src/resources/types.ts +202 -0
  114. package/src/storage/LocalStorageAdapter.ts +64 -0
  115. package/src/storage/MemoryStorageAdapter.ts +29 -0
  116. package/src/storage/StorageAdapter.ts +25 -0
  117. package/src/storage/index.ts +3 -0
  118. package/src/types/bitcoin.ts +98 -0
  119. package/src/types/common.ts +92 -0
  120. package/src/types/credentials.ts +89 -0
  121. package/src/types/did.ts +31 -0
  122. package/src/types/external-shims.d.ts +53 -0
  123. package/src/types/index.ts +7 -0
  124. package/src/types/network.ts +178 -0
  125. package/src/utils/EventLogger.ts +298 -0
  126. package/src/utils/Logger.ts +324 -0
  127. package/src/utils/MetricsCollector.ts +358 -0
  128. package/src/utils/bitcoin-address.ts +132 -0
  129. package/src/utils/cbor.ts +31 -0
  130. package/src/utils/encoding.ts +135 -0
  131. package/src/utils/hash.ts +12 -0
  132. package/src/utils/retry.ts +46 -0
  133. package/src/utils/satoshi-validation.ts +196 -0
  134. package/src/utils/serialization.ts +102 -0
  135. package/src/utils/telemetry.ts +44 -0
  136. package/src/utils/validation.ts +123 -0
  137. package/src/vc/CredentialManager.ts +955 -0
  138. package/src/vc/Issuer.ts +105 -0
  139. package/src/vc/Verifier.ts +54 -0
  140. package/src/vc/cryptosuites/bbs.ts +253 -0
  141. package/src/vc/cryptosuites/bbsSimple.ts +21 -0
  142. package/src/vc/cryptosuites/eddsa.ts +99 -0
  143. package/src/vc/documentLoader.ts +81 -0
  144. package/src/vc/proofs/data-integrity.ts +33 -0
  145. package/src/vc/utils/jsonld.ts +18 -0
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI Inspect Command
4
+ *
5
+ * Inspects a CEL event log in human-readable format.
6
+ * Displays event timeline, current state, witness attestations, and layer history.
7
+ *
8
+ * Usage: originals-cel inspect --log <path> [options]
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import type { EventLog, LogEntry, DataIntegrityProof, WitnessProof, AssetState, ExternalReference } from '../types';
14
+ import { parseEventLogJson } from '../serialization/json';
15
+ import { parseEventLogCbor } from '../serialization/cbor';
16
+
17
+ /**
18
+ * Flags parsed from command line arguments
19
+ */
20
+ export interface InspectFlags {
21
+ log?: string;
22
+ help?: boolean;
23
+ h?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Result of the inspect command
28
+ */
29
+ export interface InspectResult {
30
+ success: boolean;
31
+ message: string;
32
+ state?: AssetState;
33
+ }
34
+
35
+ /**
36
+ * Migration event data structure
37
+ */
38
+ interface MigrationData {
39
+ sourceDid?: string;
40
+ targetDid?: string;
41
+ layer?: string;
42
+ domain?: string;
43
+ migratedAt?: string;
44
+ txid?: string;
45
+ inscriptionId?: string;
46
+ }
47
+
48
+ /**
49
+ * Check if a proof is a WitnessProof (has witnessedAt field)
50
+ */
51
+ function isWitnessProof(proof: DataIntegrityProof | WitnessProof): proof is WitnessProof {
52
+ return 'witnessedAt' in proof && typeof (proof as WitnessProof).witnessedAt === 'string';
53
+ }
54
+
55
+ /**
56
+ * Check if event data contains migration information
57
+ */
58
+ function isMigrationEvent(data: unknown): data is MigrationData {
59
+ if (typeof data !== 'object' || data === null) return false;
60
+ const d = data as Record<string, unknown>;
61
+ return (
62
+ ('sourceDid' in d && 'targetDid' in d) ||
63
+ ('layer' in d && typeof d.layer === 'string' && ['peer', 'webvh', 'btco'].includes(d.layer))
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Loads and parses an event log from a file
69
+ * Supports both JSON (.json) and CBOR (.cbor) formats
70
+ */
71
+ function loadEventLog(filePath: string): EventLog {
72
+ if (!fs.existsSync(filePath)) {
73
+ throw new Error(`File not found: ${filePath}`);
74
+ }
75
+
76
+ const ext = path.extname(filePath).toLowerCase();
77
+ const content = fs.readFileSync(filePath);
78
+
79
+ if (ext === '.cbor') {
80
+ // Parse as CBOR binary
81
+ return parseEventLogCbor(new Uint8Array(content));
82
+ } else {
83
+ // Default to JSON parsing
84
+ return parseEventLogJson(content.toString('utf-8'));
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Format a timestamp for display
90
+ */
91
+ function formatTimestamp(timestamp: string): string {
92
+ try {
93
+ const date = new Date(timestamp);
94
+ return date.toLocaleString('en-US', {
95
+ year: 'numeric',
96
+ month: 'short',
97
+ day: '2-digit',
98
+ hour: '2-digit',
99
+ minute: '2-digit',
100
+ second: '2-digit',
101
+ timeZoneName: 'short',
102
+ });
103
+ } catch {
104
+ return timestamp;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Format ISO timestamp to relative time
110
+ */
111
+ function formatRelativeTime(timestamp: string): string {
112
+ try {
113
+ const date = new Date(timestamp);
114
+ const now = new Date();
115
+ const diffMs = now.getTime() - date.getTime();
116
+ const diffMins = Math.floor(diffMs / 60000);
117
+ const diffHours = Math.floor(diffMs / 3600000);
118
+ const diffDays = Math.floor(diffMs / 86400000);
119
+
120
+ if (diffMins < 1) return 'just now';
121
+ if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
122
+ if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
123
+ if (diffDays < 30) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
124
+ return formatTimestamp(timestamp);
125
+ } catch {
126
+ return timestamp;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Truncate long strings for display
132
+ */
133
+ function truncate(str: string, maxLen: number): string {
134
+ if (str.length <= maxLen) return str;
135
+ return str.substring(0, maxLen - 3) + '...';
136
+ }
137
+
138
+ /**
139
+ * Format DID for display (truncate middle)
140
+ */
141
+ function formatDid(did: string): string {
142
+ if (did.length <= 50) return did;
143
+ return did.substring(0, 25) + '...' + did.substring(did.length - 15);
144
+ }
145
+
146
+ /**
147
+ * Get event type badge
148
+ */
149
+ function getEventBadge(type: string): string {
150
+ switch (type) {
151
+ case 'create': return '🆕 CREATE';
152
+ case 'update': return '✏️ UPDATE';
153
+ case 'deactivate': return '🔒 DEACTIVATE';
154
+ default: return `📋 ${type.toUpperCase()}`;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Get layer badge
160
+ */
161
+ function getLayerBadge(layer: string): string {
162
+ switch (layer) {
163
+ case 'peer': return '🏠 Peer (Layer 0)';
164
+ case 'webvh': return '🌐 WebVH (Layer 1)';
165
+ case 'btco': return '₿ Bitcoin (Layer 2)';
166
+ default: return layer;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Derive current state from event log (simplified version)
172
+ */
173
+ function deriveCurrentState(log: EventLog): AssetState {
174
+ if (!log.events || log.events.length === 0) {
175
+ throw new Error('Cannot derive state from empty event log');
176
+ }
177
+
178
+ const createEvent = log.events[0];
179
+ if (createEvent.type !== 'create') {
180
+ throw new Error('First event must be a create event');
181
+ }
182
+
183
+ const createData = createEvent.data as Record<string, unknown>;
184
+
185
+ // Initialize state from create event
186
+ const state: AssetState = {
187
+ did: (createData.did as string) || 'unknown',
188
+ name: createData.name as string,
189
+ layer: (createData.layer as 'peer' | 'webvh' | 'btco') || 'peer',
190
+ resources: (createData.resources as ExternalReference[]) || [],
191
+ creator: createData.creator as string,
192
+ createdAt: createData.createdAt as string,
193
+ updatedAt: undefined,
194
+ deactivated: false,
195
+ metadata: {},
196
+ };
197
+
198
+ // Apply subsequent events
199
+ for (let i = 1; i < log.events.length; i++) {
200
+ const event = log.events[i];
201
+
202
+ if (event.type === 'update') {
203
+ const updateData = event.data as Record<string, unknown>;
204
+
205
+ // Update known fields
206
+ if (updateData.name !== undefined) state.name = updateData.name as string;
207
+ if (updateData.resources !== undefined) state.resources = updateData.resources as ExternalReference[];
208
+ if (updateData.updatedAt !== undefined) state.updatedAt = updateData.updatedAt as string;
209
+ if (updateData.did !== undefined) state.did = updateData.did as string;
210
+ if (updateData.targetDid !== undefined) state.did = updateData.targetDid as string;
211
+ if (updateData.layer !== undefined) state.layer = updateData.layer as 'peer' | 'webvh' | 'btco';
212
+
213
+ // Store migration data in metadata
214
+ if (isMigrationEvent(updateData)) {
215
+ state.metadata = state.metadata || {};
216
+ if (updateData.sourceDid) state.metadata.sourceDid = updateData.sourceDid;
217
+ if (updateData.domain) state.metadata.domain = updateData.domain;
218
+ if (updateData.txid) state.metadata.txid = updateData.txid;
219
+ if (updateData.inscriptionId) state.metadata.inscriptionId = updateData.inscriptionId;
220
+ }
221
+
222
+ // Store other fields in metadata
223
+ for (const [key, value] of Object.entries(updateData)) {
224
+ if (!['name', 'resources', 'updatedAt', 'did', 'targetDid', 'layer', 'creator', 'createdAt', 'sourceDid', 'domain', 'txid', 'inscriptionId'].includes(key)) {
225
+ state.metadata = state.metadata || {};
226
+ state.metadata[key] = value;
227
+ }
228
+ }
229
+ } else if (event.type === 'deactivate') {
230
+ state.deactivated = true;
231
+ const deactivateData = event.data as Record<string, unknown>;
232
+ if (deactivateData.deactivatedAt !== undefined) {
233
+ state.updatedAt = deactivateData.deactivatedAt as string;
234
+ }
235
+ if (deactivateData.reason !== undefined) {
236
+ state.metadata = state.metadata || {};
237
+ state.metadata.deactivationReason = deactivateData.reason;
238
+ }
239
+ }
240
+ }
241
+
242
+ return state;
243
+ }
244
+
245
+ /**
246
+ * Extract layer history from events
247
+ */
248
+ function extractLayerHistory(log: EventLog): Array<{ layer: string; timestamp: string; did?: string }> {
249
+ const history: Array<{ layer: string; timestamp: string; did?: string }> = [];
250
+
251
+ for (const event of log.events) {
252
+ const data = event.data as Record<string, unknown>;
253
+
254
+ if (event.type === 'create' && data.layer) {
255
+ history.push({
256
+ layer: data.layer as string,
257
+ timestamp: (data.createdAt as string) || event.proof[0]?.created || 'unknown',
258
+ did: data.did as string,
259
+ });
260
+ }
261
+
262
+ if (event.type === 'update' && isMigrationEvent(event.data)) {
263
+ const migrationData = event.data as MigrationData;
264
+ history.push({
265
+ layer: migrationData.layer ?? 'unknown',
266
+ timestamp: migrationData.migratedAt ?? event.proof[0]?.created ?? 'unknown',
267
+ did: migrationData.targetDid ?? 'unknown',
268
+ });
269
+ }
270
+ }
271
+
272
+ return history;
273
+ }
274
+
275
+ /**
276
+ * Extract all witness attestations from the log
277
+ */
278
+ function extractWitnesses(log: EventLog): Array<{
279
+ eventIndex: number;
280
+ eventType: string;
281
+ witnessedAt: string;
282
+ cryptosuite: string;
283
+ verificationMethod: string;
284
+ }> {
285
+ const witnesses: Array<{
286
+ eventIndex: number;
287
+ eventType: string;
288
+ witnessedAt: string;
289
+ cryptosuite: string;
290
+ verificationMethod: string;
291
+ }> = [];
292
+
293
+ for (let i = 0; i < log.events.length; i++) {
294
+ const event = log.events[i];
295
+ for (const proof of event.proof) {
296
+ if (isWitnessProof(proof)) {
297
+ witnesses.push({
298
+ eventIndex: i,
299
+ eventType: event.type,
300
+ witnessedAt: proof.witnessedAt,
301
+ cryptosuite: proof.cryptosuite,
302
+ verificationMethod: proof.verificationMethod,
303
+ });
304
+ }
305
+ }
306
+ }
307
+
308
+ return witnesses;
309
+ }
310
+
311
+ /**
312
+ * Output the inspection result to stdout
313
+ */
314
+ function outputInspection(log: EventLog, state: AssetState): void {
315
+ console.log('\n═══════════════════════════════════════════════════════');
316
+ console.log(' CEL Event Log Inspector');
317
+ console.log('═══════════════════════════════════════════════════════\n');
318
+
319
+ // ─────────────────────────────────────────────────────────
320
+ // Current State Section
321
+ // ─────────────────────────────────────────────────────────
322
+ console.log('📦 CURRENT STATE');
323
+ console.log('───────────────────────────────────────────────────────');
324
+
325
+ // Status badge
326
+ if (state.deactivated) {
327
+ console.log(` Status: 🔒 DEACTIVATED`);
328
+ if (state.metadata?.deactivationReason) {
329
+ console.log(` Reason: ${state.metadata.deactivationReason}`);
330
+ }
331
+ } else {
332
+ console.log(` Status: ✅ ACTIVE`);
333
+ }
334
+
335
+ console.log(` Name: ${state.name || '(unnamed)'}`);
336
+ console.log(` Layer: ${getLayerBadge(state.layer)}`);
337
+ console.log(` DID: ${formatDid(state.did)}`);
338
+
339
+ if (state.creator) {
340
+ console.log(` Creator: ${formatDid(state.creator)}`);
341
+ }
342
+
343
+ if (state.createdAt) {
344
+ console.log(` Created: ${formatTimestamp(state.createdAt)} (${formatRelativeTime(state.createdAt)})`);
345
+ }
346
+
347
+ if (state.updatedAt) {
348
+ console.log(` Updated: ${formatTimestamp(state.updatedAt)} (${formatRelativeTime(state.updatedAt)})`);
349
+ }
350
+
351
+ // Resources
352
+ if (state.resources && state.resources.length > 0) {
353
+ console.log(`\n 📎 Resources (${state.resources.length}):`);
354
+ for (let i = 0; i < state.resources.length; i++) {
355
+ const res = state.resources[i];
356
+ console.log(` [${i + 1}] ${res.mediaType || 'unknown'}`);
357
+ console.log(` Hash: ${truncate(res.digestMultibase, 50)}`);
358
+ if (res.url && res.url.length > 0) {
359
+ console.log(` URL: ${res.url[0]}`);
360
+ }
361
+ }
362
+ }
363
+
364
+ // Additional metadata
365
+ if (state.metadata && Object.keys(state.metadata).length > 0) {
366
+ const displayKeys = Object.keys(state.metadata).filter(
367
+ k => !['deactivationReason'].includes(k)
368
+ );
369
+ if (displayKeys.length > 0) {
370
+ console.log(`\n 📋 Metadata:`);
371
+ for (const key of displayKeys) {
372
+ const value = state.metadata[key];
373
+ const displayValue = typeof value === 'string'
374
+ ? truncate(value, 50)
375
+ : JSON.stringify(value);
376
+ console.log(` ${key}: ${displayValue}`);
377
+ }
378
+ }
379
+ }
380
+
381
+ // ─────────────────────────────────────────────────────────
382
+ // Layer History Section
383
+ // ─────────────────────────────────────────────────────────
384
+ const layerHistory = extractLayerHistory(log);
385
+ if (layerHistory.length > 1) {
386
+ console.log('\n\n🗺️ LAYER HISTORY');
387
+ console.log('───────────────────────────────────────────────────────');
388
+
389
+ for (let i = 0; i < layerHistory.length; i++) {
390
+ const entry = layerHistory[i];
391
+ const isLast = i === layerHistory.length - 1;
392
+ const prefix = isLast ? ' └─' : ' ├─';
393
+ const arrow = i > 0 ? ' ← ' : '';
394
+
395
+ console.log(`${prefix} ${getLayerBadge(entry.layer)}`);
396
+ if (entry.timestamp) {
397
+ console.log(` ${isLast ? ' ' : '│'} ${formatTimestamp(entry.timestamp)}`);
398
+ }
399
+ if (entry.did) {
400
+ console.log(` ${isLast ? ' ' : '│'} ${formatDid(entry.did)}`);
401
+ }
402
+ }
403
+ }
404
+
405
+ // ─────────────────────────────────────────────────────────
406
+ // Witness Attestations Section
407
+ // ─────────────────────────────────────────────────────────
408
+ const witnesses = extractWitnesses(log);
409
+ if (witnesses.length > 0) {
410
+ console.log('\n\n🔏 WITNESS ATTESTATIONS');
411
+ console.log('───────────────────────────────────────────────────────');
412
+
413
+ for (let i = 0; i < witnesses.length; i++) {
414
+ const witness = witnesses[i];
415
+ console.log(`\n [${i + 1}] Event #${witness.eventIndex + 1} (${witness.eventType})`);
416
+ console.log(` Witnessed: ${formatTimestamp(witness.witnessedAt)}`);
417
+ console.log(` Cryptosuite: ${witness.cryptosuite}`);
418
+ console.log(` Witness: ${truncate(witness.verificationMethod, 50)}`);
419
+ }
420
+ }
421
+
422
+ // ─────────────────────────────────────────────────────────
423
+ // Event Timeline Section
424
+ // ─────────────────────────────────────────────────────────
425
+ console.log('\n\n📜 EVENT TIMELINE');
426
+ console.log('───────────────────────────────────────────────────────');
427
+
428
+ for (let i = 0; i < log.events.length; i++) {
429
+ const event = log.events[i];
430
+ const data = event.data as Record<string, unknown>;
431
+ const isLast = i === log.events.length - 1;
432
+ const prefix = isLast ? '└─' : '├─';
433
+ const connector = isLast ? ' ' : '│ ';
434
+
435
+ // Event header with type badge
436
+ console.log(`\n ${prefix} ${getEventBadge(event.type)}`);
437
+
438
+ // Timestamp from proof
439
+ const timestamp = event.proof[0]?.created || data.createdAt || data.updatedAt || data.migratedAt || data.deactivatedAt;
440
+ if (timestamp) {
441
+ console.log(` ${connector} 📅 ${formatTimestamp(timestamp as string)}`);
442
+ }
443
+
444
+ // Event-specific details
445
+ if (event.type === 'create') {
446
+ if (data.name) console.log(` ${connector} Name: ${data.name}`);
447
+ if (data.did) console.log(` ${connector} DID: ${formatDid(data.did as string)}`);
448
+ if (data.layer) console.log(` ${connector} Layer: ${data.layer}`);
449
+ if (data.resources && Array.isArray(data.resources)) {
450
+ console.log(` ${connector} Resources: ${data.resources.length} file(s)`);
451
+ }
452
+ } else if (event.type === 'update') {
453
+ // Show what changed
454
+ const changes: string[] = [];
455
+ if (data.name) changes.push(`name → "${data.name}"`);
456
+ if (data.resources) changes.push(`resources updated`);
457
+ if (isMigrationEvent(event.data)) {
458
+ const migrationData = event.data as MigrationData;
459
+ changes.push(`migrated to ${migrationData.layer ?? 'unknown'}`);
460
+ if (migrationData.domain) changes.push(`domain: ${migrationData.domain}`);
461
+ if (migrationData.txid) changes.push(`txid: ${truncate(migrationData.txid, 20)}`);
462
+ }
463
+
464
+ if (changes.length > 0) {
465
+ console.log(` ${connector} Changes: ${changes.join(', ')}`);
466
+ } else {
467
+ // Show raw data keys
468
+ const keys = Object.keys(data).filter(k => k !== 'updatedAt');
469
+ if (keys.length > 0) {
470
+ console.log(` ${connector} Fields: ${keys.join(', ')}`);
471
+ }
472
+ }
473
+ } else if (event.type === 'deactivate') {
474
+ if (data.reason) console.log(` ${connector} Reason: ${data.reason}`);
475
+ }
476
+
477
+ // Proof summary
478
+ const controllerProofs = event.proof.filter(p => !isWitnessProof(p));
479
+ const witnessProofs = event.proof.filter(p => isWitnessProof(p));
480
+
481
+ const proofParts: string[] = [];
482
+ if (controllerProofs.length > 0) {
483
+ proofParts.push(`${controllerProofs.length} controller`);
484
+ }
485
+ if (witnessProofs.length > 0) {
486
+ proofParts.push(`${witnessProofs.length} witness`);
487
+ }
488
+ console.log(` ${connector} 🔐 Proofs: ${proofParts.join(', ')}`);
489
+
490
+ // Hash chain link
491
+ if (event.previousEvent) {
492
+ console.log(` ${connector} 🔗 Chain: ${truncate(event.previousEvent, 40)}`);
493
+ }
494
+ }
495
+
496
+ console.log('\n═══════════════════════════════════════════════════════\n');
497
+ }
498
+
499
+ /**
500
+ * Execute the inspect command
501
+ */
502
+ export async function inspectCommand(flags: InspectFlags): Promise<InspectResult> {
503
+ // Check for help flag
504
+ if (flags.help || flags.h) {
505
+ return {
506
+ success: true,
507
+ message: 'Use --help with the main CLI for full help text',
508
+ };
509
+ }
510
+
511
+ // Validate required arguments
512
+ if (!flags.log) {
513
+ return {
514
+ success: false,
515
+ message: 'Error: --log is required. Usage: originals-cel inspect --log <path>',
516
+ };
517
+ }
518
+
519
+ // Load the event log
520
+ let eventLog: EventLog;
521
+ try {
522
+ eventLog = loadEventLog(flags.log);
523
+ } catch (e) {
524
+ return {
525
+ success: false,
526
+ message: `Error: Failed to load event log: ${(e as Error).message}`,
527
+ };
528
+ }
529
+
530
+ // Derive current state
531
+ let state: AssetState;
532
+ try {
533
+ state = deriveCurrentState(eventLog);
534
+ } catch (e) {
535
+ return {
536
+ success: false,
537
+ message: `Error: Failed to derive state: ${(e as Error).message}`,
538
+ };
539
+ }
540
+
541
+ // Output the inspection
542
+ outputInspection(eventLog, state);
543
+
544
+ return {
545
+ success: true,
546
+ message: 'Inspection complete',
547
+ state,
548
+ };
549
+ }