@originals/sdk 1.8.0 → 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,330 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI Create Command
4
+ *
5
+ * Creates a new CEL asset with an initial event.
6
+ * Generates Ed25519 key pair if --key not provided.
7
+ *
8
+ * Usage: originals-cel create --name <name> --file <path> [options]
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import type { DataIntegrityProof } from '../types';
14
+ import { PeerCelManager, CelSigner } from '../layers/PeerCelManager';
15
+ import { createExternalReference } from '../ExternalReferenceManager';
16
+ import { serializeEventLogJson } from '../serialization/json';
17
+ import { serializeEventLogCbor } from '../serialization/cbor';
18
+ import { multikey } from '../../crypto/Multikey';
19
+
20
+ /**
21
+ * Flags parsed from command line arguments
22
+ */
23
+ export interface CreateFlags {
24
+ name?: string;
25
+ file?: string;
26
+ key?: string;
27
+ output?: string;
28
+ format?: string;
29
+ help?: boolean;
30
+ h?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Result of the create command
35
+ */
36
+ export interface CreateResult {
37
+ success: boolean;
38
+ message: string;
39
+ log?: unknown;
40
+ keyGenerated?: boolean;
41
+ privateKey?: string;
42
+ publicKey?: string;
43
+ }
44
+
45
+ /**
46
+ * Lookup table for common file extensions to MIME types
47
+ */
48
+ const MIME_TYPES: Record<string, string> = {
49
+ '.png': 'image/png',
50
+ '.jpg': 'image/jpeg',
51
+ '.jpeg': 'image/jpeg',
52
+ '.gif': 'image/gif',
53
+ '.webp': 'image/webp',
54
+ '.svg': 'image/svg+xml',
55
+ '.pdf': 'application/pdf',
56
+ '.json': 'application/json',
57
+ '.txt': 'text/plain',
58
+ '.html': 'text/html',
59
+ '.css': 'text/css',
60
+ '.js': 'application/javascript',
61
+ '.mp3': 'audio/mpeg',
62
+ '.mp4': 'video/mp4',
63
+ '.webm': 'video/webm',
64
+ '.wav': 'audio/wav',
65
+ '.ogg': 'audio/ogg',
66
+ '.zip': 'application/zip',
67
+ '.bin': 'application/octet-stream',
68
+ };
69
+
70
+ /**
71
+ * Gets MIME type from file extension
72
+ */
73
+ function getMimeType(filePath: string): string {
74
+ const ext = path.extname(filePath).toLowerCase();
75
+ return MIME_TYPES[ext] || 'application/octet-stream';
76
+ }
77
+
78
+ /**
79
+ * Generates an Ed25519 key pair for signing
80
+ */
81
+ async function generateKeyPair(): Promise<{ privateKey: string; publicKey: string }> {
82
+ const ed25519 = await import('@noble/ed25519');
83
+ const privateKeyBytes = ed25519.utils.randomPrivateKey();
84
+ const publicKeyBytes = await (ed25519 as any).getPublicKeyAsync(privateKeyBytes);
85
+
86
+ return {
87
+ privateKey: multikey.encodePrivateKey(privateKeyBytes as Uint8Array, 'Ed25519'),
88
+ publicKey: multikey.encodePublicKey(publicKeyBytes as Uint8Array, 'Ed25519'),
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Loads an Ed25519 private key from a file
94
+ * Supports both raw multibase format and JSON format { privateKey: "z..." }
95
+ */
96
+ async function loadPrivateKey(keyPath: string): Promise<{ privateKey: string; publicKey: string }> {
97
+ if (!fs.existsSync(keyPath)) {
98
+ throw new Error(`Key file not found: ${keyPath}`);
99
+ }
100
+
101
+ const content = fs.readFileSync(keyPath, 'utf-8').trim();
102
+
103
+ let privateKey: string;
104
+
105
+ // Try parsing as JSON first
106
+ try {
107
+ const parsed = JSON.parse(content);
108
+ if (parsed.privateKey) {
109
+ privateKey = parsed.privateKey;
110
+ } else {
111
+ throw new Error('JSON key file must contain "privateKey" field');
112
+ }
113
+ } catch (e) {
114
+ // Not JSON, treat as raw multibase key
115
+ if (content.startsWith('z')) {
116
+ privateKey = content;
117
+ } else {
118
+ throw new Error('Key file must contain a z-base58btc multibase-encoded Ed25519 private key');
119
+ }
120
+ }
121
+
122
+ // Validate and decode to get public key
123
+ const decoded = multikey.decodePrivateKey(privateKey);
124
+ if (decoded.type !== 'Ed25519') {
125
+ throw new Error(`Expected Ed25519 key, got ${decoded.type}`);
126
+ }
127
+
128
+ // Derive public key from private key
129
+ const ed25519 = await import('@noble/ed25519');
130
+ const publicKeyBytes = await (ed25519 as any).getPublicKeyAsync(decoded.key);
131
+ const publicKey = multikey.encodePublicKey(publicKeyBytes as Uint8Array, 'Ed25519');
132
+
133
+ return { privateKey, publicKey };
134
+ }
135
+
136
+ /**
137
+ * Creates a signer function from a private key
138
+ */
139
+ function createSigner(privateKey: string, publicKey: string): CelSigner {
140
+ return async (data: unknown): Promise<DataIntegrityProof> => {
141
+ const ed25519 = await import('@noble/ed25519');
142
+ const decoded = multikey.decodePrivateKey(privateKey);
143
+
144
+ // Serialize data for signing (deterministic JSON)
145
+ const dataStr = JSON.stringify(data, Object.keys(data as object).sort());
146
+ const dataBytes = new TextEncoder().encode(dataStr);
147
+
148
+ // Sign the data
149
+ const signature = await (ed25519 as any).signAsync(dataBytes, decoded.key);
150
+ const proofValue = multikey.encodeMultibase(new Uint8Array(signature));
151
+
152
+ return {
153
+ type: 'DataIntegrityProof',
154
+ cryptosuite: 'eddsa-jcs-2022',
155
+ created: new Date().toISOString(),
156
+ verificationMethod: `did:key:${publicKey}#${publicKey}`,
157
+ proofPurpose: 'assertionMethod',
158
+ proofValue,
159
+ };
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Execute the create command
165
+ */
166
+ export async function createCommand(flags: CreateFlags): Promise<CreateResult> {
167
+ // Check for help flag
168
+ if (flags.help || flags.h) {
169
+ return {
170
+ success: true,
171
+ message: 'Use --help with the main CLI for full help text',
172
+ };
173
+ }
174
+
175
+ // Validate required arguments
176
+ if (!flags.name) {
177
+ return {
178
+ success: false,
179
+ message: 'Error: --name is required. Usage: originals-cel create --name <name> --file <path>',
180
+ };
181
+ }
182
+
183
+ if (!flags.file) {
184
+ return {
185
+ success: false,
186
+ message: 'Error: --file is required. Usage: originals-cel create --name <name> --file <path>',
187
+ };
188
+ }
189
+
190
+ // Validate format flag
191
+ const format = (flags.format || 'json').toLowerCase();
192
+ if (format !== 'json' && format !== 'cbor') {
193
+ return {
194
+ success: false,
195
+ message: 'Error: --format must be "json" or "cbor"',
196
+ };
197
+ }
198
+
199
+ // Check if file exists
200
+ if (!fs.existsSync(flags.file)) {
201
+ return {
202
+ success: false,
203
+ message: `Error: File not found: ${flags.file}`,
204
+ };
205
+ }
206
+
207
+ // Read file content
208
+ let fileContent: Uint8Array;
209
+ try {
210
+ fileContent = new Uint8Array(fs.readFileSync(flags.file));
211
+ } catch (e) {
212
+ return {
213
+ success: false,
214
+ message: `Error: Failed to read file: ${(e as Error).message}`,
215
+ };
216
+ }
217
+
218
+ // Get or generate key pair
219
+ let privateKey: string;
220
+ let publicKey: string;
221
+ let keyGenerated = false;
222
+
223
+ if (flags.key) {
224
+ try {
225
+ const loaded = await loadPrivateKey(flags.key);
226
+ privateKey = loaded.privateKey;
227
+ publicKey = loaded.publicKey;
228
+ } catch (e) {
229
+ return {
230
+ success: false,
231
+ message: `Error: Failed to load key: ${(e as Error).message}`,
232
+ };
233
+ }
234
+ } else {
235
+ // Generate new key pair
236
+ const generated = await generateKeyPair();
237
+ privateKey = generated.privateKey;
238
+ publicKey = generated.publicKey;
239
+ keyGenerated = true;
240
+ }
241
+
242
+ // Create signer
243
+ const signer = createSigner(privateKey, publicKey);
244
+
245
+ // Determine media type from file extension
246
+ const mediaType = getMimeType(flags.file);
247
+
248
+ // Create external reference for the file
249
+ const resource = createExternalReference(fileContent, mediaType);
250
+
251
+ // Create PeerCelManager and create the asset
252
+ const manager = new PeerCelManager(signer, {
253
+ verificationMethod: `did:key:${publicKey}#${publicKey}`,
254
+ proofPurpose: 'assertionMethod',
255
+ });
256
+
257
+ let eventLog;
258
+ try {
259
+ eventLog = await manager.create(flags.name, [resource]);
260
+ } catch (e) {
261
+ return {
262
+ success: false,
263
+ message: `Error: Failed to create asset: ${(e as Error).message}`,
264
+ };
265
+ }
266
+
267
+ // Serialize output
268
+ let output: string | Uint8Array;
269
+ try {
270
+ if (format === 'cbor') {
271
+ output = serializeEventLogCbor(eventLog);
272
+ } else {
273
+ output = serializeEventLogJson(eventLog);
274
+ }
275
+ } catch (e) {
276
+ return {
277
+ success: false,
278
+ message: `Error: Failed to serialize event log: ${(e as Error).message}`,
279
+ };
280
+ }
281
+
282
+ // Write output
283
+ if (flags.output) {
284
+ try {
285
+ if (format === 'cbor') {
286
+ fs.writeFileSync(flags.output, output as Uint8Array);
287
+ } else {
288
+ fs.writeFileSync(flags.output, output as string, 'utf-8');
289
+ }
290
+ } catch (e) {
291
+ return {
292
+ success: false,
293
+ message: `Error: Failed to write output file: ${(e as Error).message}`,
294
+ };
295
+ }
296
+ } else {
297
+ // Write to stdout
298
+ if (format === 'cbor') {
299
+ // For CBOR, output as base64 to stdout since it's binary
300
+ const base64 = Buffer.from(output as Uint8Array).toString('base64');
301
+ process.stdout.write(base64);
302
+ } else {
303
+ process.stdout.write(output as string);
304
+ }
305
+ }
306
+
307
+ // Build result
308
+ const result: CreateResult = {
309
+ success: true,
310
+ message: flags.output
311
+ ? `Created CEL asset "${flags.name}" and saved to ${flags.output}`
312
+ : `Created CEL asset "${flags.name}"`,
313
+ log: eventLog,
314
+ keyGenerated,
315
+ };
316
+
317
+ // Include key info if generated
318
+ if (keyGenerated) {
319
+ result.privateKey = privateKey;
320
+ result.publicKey = publicKey;
321
+
322
+ // Print key info to stderr so it doesn't mix with JSON output
323
+ console.error(`\n⚠️ New Ed25519 key pair generated. Save these keys securely!`);
324
+ console.error(`Private Key: ${privateKey}`);
325
+ console.error(`Public Key: ${publicKey}`);
326
+ console.error(`\nTo reuse this key, save it to a file and use --key <path>\n`);
327
+ }
328
+
329
+ return result;
330
+ }
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Originals CEL CLI
4
+ *
5
+ * Command-line interface for working with CEL (Cryptographic Event Log) assets.
6
+ * Part of the Originals Protocol SDK.
7
+ */
8
+
9
+ import { createCommand, CreateFlags } from './create';
10
+ import { verifyCommand, VerifyFlags } from './verify';
11
+ import { inspectCommand, InspectFlags } from './inspect';
12
+ import { migrateCommand, MigrateFlags } from './migrate';
13
+
14
+ // Version from package.json - will be replaced at build time or read dynamically
15
+ const VERSION = '1.5.0';
16
+
17
+ const HELP_TEXT = `
18
+ originals-cel - Cryptographic Event Log CLI for Originals Protocol
19
+
20
+ USAGE:
21
+ originals-cel <command> [options]
22
+
23
+ COMMANDS:
24
+ create Create a new CEL asset with an initial event
25
+ verify Verify an existing CEL event log
26
+ inspect Inspect a CEL log in human-readable format
27
+ migrate Migrate a CEL asset between layers (peer → webvh → btco)
28
+
29
+ GLOBAL OPTIONS:
30
+ --help, -h Show help for a command
31
+ --version, -v Show version number
32
+
33
+ EXAMPLES:
34
+ originals-cel create --name "My Asset" --file ./image.png
35
+ originals-cel verify --log ./asset.cel.json
36
+ originals-cel inspect --log ./asset.cel.json
37
+ originals-cel migrate --log ./asset.cel.json --to webvh --domain example.com
38
+
39
+ For more information on a specific command:
40
+ originals-cel <command> --help
41
+ `;
42
+
43
+ const CREATE_HELP = `
44
+ originals-cel create - Create a new CEL asset
45
+
46
+ USAGE:
47
+ originals-cel create --name <name> --file <path> [options]
48
+
49
+ REQUIRED:
50
+ --name <name> Name of the asset
51
+ --file <path> Path to the content file
52
+
53
+ OPTIONS:
54
+ --key <path> Path to Ed25519 private key (generates new if not provided)
55
+ --output <path> Output file path (stdout if not provided)
56
+ --format <type> Output format: json (default) or cbor
57
+ --help, -h Show this help message
58
+
59
+ EXAMPLES:
60
+ originals-cel create --name "My Art" --file ./artwork.png
61
+ originals-cel create --name "Document" --file ./doc.pdf --output ./doc.cel.json
62
+ originals-cel create --name "Asset" --file ./data.bin --format cbor --output ./asset.cel.cbor
63
+ `;
64
+
65
+ const VERIFY_HELP = `
66
+ originals-cel verify - Verify a CEL event log
67
+
68
+ USAGE:
69
+ originals-cel verify --log <path> [options]
70
+
71
+ REQUIRED:
72
+ --log <path> Path to the CEL event log file (.cel.json or .cel.cbor)
73
+
74
+ OPTIONS:
75
+ --help, -h Show this help message
76
+
77
+ OUTPUT:
78
+ Displays verification result with event-by-event breakdown.
79
+ Shows witness attestations if present.
80
+ Exit code 0 on success, 1 on verification failure.
81
+
82
+ EXAMPLES:
83
+ originals-cel verify --log ./asset.cel.json
84
+ originals-cel verify --log ./asset.cel.cbor
85
+ `;
86
+
87
+ const INSPECT_HELP = `
88
+ originals-cel inspect - Inspect a CEL log in human-readable format
89
+
90
+ USAGE:
91
+ originals-cel inspect --log <path> [options]
92
+
93
+ REQUIRED:
94
+ --log <path> Path to the CEL event log file
95
+
96
+ OPTIONS:
97
+ --help, -h Show this help message
98
+
99
+ OUTPUT:
100
+ Pretty-prints event timeline with timestamps.
101
+ Shows current state derived from events.
102
+ Lists witnesses and attestation times.
103
+ Shows layer history if migrations are present.
104
+
105
+ EXAMPLES:
106
+ originals-cel inspect --log ./asset.cel.json
107
+ `;
108
+
109
+ const MIGRATE_HELP = `
110
+ originals-cel migrate - Migrate a CEL asset between layers
111
+
112
+ USAGE:
113
+ originals-cel migrate --log <path> --to <layer> [options]
114
+
115
+ REQUIRED:
116
+ --log <path> Path to the CEL event log file
117
+ --to <layer> Target layer: webvh or btco
118
+
119
+ OPTIONS:
120
+ --domain <domain> Domain for webvh layer (required for --to webvh)
121
+ --wallet <path> Path to Bitcoin wallet key (required for --to btco)
122
+ --output <path> Output file path (stdout if not provided)
123
+ --help, -h Show this help message
124
+
125
+ MIGRATION PATHS:
126
+ peer → webvh Requires --domain
127
+ webvh → btco Requires --wallet
128
+
129
+ EXAMPLES:
130
+ originals-cel migrate --log ./asset.cel.json --to webvh --domain example.com
131
+ originals-cel migrate --log ./asset.cel.json --to btco --wallet ./wallet.key
132
+ originals-cel migrate --log ./asset.cel.json --to webvh --domain example.com --output ./migrated.cel.json
133
+ `;
134
+
135
+ /**
136
+ * Parse command line arguments into a structured object
137
+ */
138
+ function parseArgs(args: string[]): {
139
+ command: string | null;
140
+ flags: Record<string, string | boolean>;
141
+ } {
142
+ const flags: Record<string, string | boolean> = {};
143
+ let command: string | null = null;
144
+
145
+ for (let i = 0; i < args.length; i++) {
146
+ const arg = args[i];
147
+
148
+ if (arg.startsWith('--')) {
149
+ const key = arg.slice(2);
150
+ const nextArg = args[i + 1];
151
+
152
+ // Check if next arg exists and doesn't start with --
153
+ if (nextArg && !nextArg.startsWith('-')) {
154
+ flags[key] = nextArg;
155
+ i++; // Skip the value
156
+ } else {
157
+ flags[key] = true;
158
+ }
159
+ } else if (arg.startsWith('-')) {
160
+ const key = arg.slice(1);
161
+ flags[key] = true;
162
+ } else if (!command) {
163
+ command = arg;
164
+ }
165
+ }
166
+
167
+ return { command, flags };
168
+ }
169
+
170
+ /**
171
+ * Print message to stdout
172
+ */
173
+ function print(message: string): void {
174
+ console.log(message);
175
+ }
176
+
177
+ /**
178
+ * Print error message to stderr and exit
179
+ */
180
+ function error(message: string, exitCode = 1): never {
181
+ console.error(`Error: ${message}`);
182
+ process.exit(exitCode);
183
+ }
184
+
185
+ /**
186
+ * Show help for a specific command
187
+ */
188
+ function showCommandHelp(command: string): void {
189
+ switch (command) {
190
+ case 'create':
191
+ print(CREATE_HELP);
192
+ break;
193
+ case 'verify':
194
+ print(VERIFY_HELP);
195
+ break;
196
+ case 'inspect':
197
+ print(INSPECT_HELP);
198
+ break;
199
+ case 'migrate':
200
+ print(MIGRATE_HELP);
201
+ break;
202
+ default:
203
+ error(`Unknown command: ${command}`);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Run the create command asynchronously
209
+ */
210
+ async function runCreateCommand(flags: Record<string, string | boolean>): Promise<void> {
211
+ try {
212
+ const createFlags: CreateFlags = {
213
+ name: typeof flags.name === 'string' ? flags.name : undefined,
214
+ file: typeof flags.file === 'string' ? flags.file : undefined,
215
+ key: typeof flags.key === 'string' ? flags.key : undefined,
216
+ output: typeof flags.output === 'string' ? flags.output : undefined,
217
+ format: typeof flags.format === 'string' ? flags.format : undefined,
218
+ help: flags.help === true,
219
+ h: flags.h === true,
220
+ };
221
+
222
+ const result = await createCommand(createFlags);
223
+
224
+ if (!result.success) {
225
+ error(result.message.replace('Error: ', ''));
226
+ }
227
+ } catch (e) {
228
+ error((e as Error).message);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Run the verify command asynchronously
234
+ */
235
+ async function runVerifyCommand(flags: Record<string, string | boolean>): Promise<void> {
236
+ try {
237
+ const verifyFlags: VerifyFlags = {
238
+ log: typeof flags.log === 'string' ? flags.log : undefined,
239
+ help: flags.help === true,
240
+ h: flags.h === true,
241
+ };
242
+
243
+ const result = await verifyCommand(verifyFlags);
244
+
245
+ if (!result.success) {
246
+ error(result.message.replace('Error: ', ''));
247
+ }
248
+
249
+ // Exit with appropriate code based on verification result
250
+ if (result.verified === false) {
251
+ process.exit(1);
252
+ }
253
+ } catch (e) {
254
+ error((e as Error).message);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Run the inspect command asynchronously
260
+ */
261
+ async function runInspectCommand(flags: Record<string, string | boolean>): Promise<void> {
262
+ try {
263
+ const inspectFlags: InspectFlags = {
264
+ log: typeof flags.log === 'string' ? flags.log : undefined,
265
+ help: flags.help === true,
266
+ h: flags.h === true,
267
+ };
268
+
269
+ const result = await inspectCommand(inspectFlags);
270
+
271
+ if (!result.success) {
272
+ error(result.message.replace('Error: ', ''));
273
+ }
274
+ } catch (e) {
275
+ error((e as Error).message);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Run the migrate command asynchronously
281
+ */
282
+ async function runMigrateCommand(flags: Record<string, string | boolean>): Promise<void> {
283
+ try {
284
+ const migrateFlags: MigrateFlags = {
285
+ log: typeof flags.log === 'string' ? flags.log : undefined,
286
+ to: typeof flags.to === 'string' ? flags.to : undefined,
287
+ domain: typeof flags.domain === 'string' ? flags.domain : undefined,
288
+ wallet: typeof flags.wallet === 'string' ? flags.wallet : undefined,
289
+ output: typeof flags.output === 'string' ? flags.output : undefined,
290
+ format: typeof flags.format === 'string' ? flags.format : undefined,
291
+ help: flags.help === true,
292
+ h: flags.h === true,
293
+ };
294
+
295
+ const result = await migrateCommand(migrateFlags);
296
+
297
+ if (!result.success) {
298
+ error(result.message.replace('Error: ', ''));
299
+ }
300
+ } catch (e) {
301
+ error((e as Error).message);
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Main CLI entry point
307
+ */
308
+ export function main(args: string[] = process.argv.slice(2)): void {
309
+ const { command, flags } = parseArgs(args);
310
+
311
+ // Handle --version / -v
312
+ if (flags.version || flags.v) {
313
+ print(`originals-cel v${VERSION}`);
314
+ return;
315
+ }
316
+
317
+ // Handle --help / -h without command
318
+ if (!command || flags.help || flags.h) {
319
+ if (command && command !== 'help') {
320
+ showCommandHelp(command);
321
+ } else {
322
+ print(HELP_TEXT);
323
+ }
324
+ return;
325
+ }
326
+
327
+ // Route to subcommands
328
+ switch (command) {
329
+ case 'create':
330
+ if (flags.help || flags.h) {
331
+ print(CREATE_HELP);
332
+ return;
333
+ }
334
+ // Run create command asynchronously
335
+ runCreateCommand(flags);
336
+ return;
337
+
338
+ case 'verify':
339
+ if (flags.help || flags.h) {
340
+ print(VERIFY_HELP);
341
+ return;
342
+ }
343
+ // Run verify command asynchronously
344
+ runVerifyCommand(flags);
345
+ return;
346
+
347
+ case 'inspect':
348
+ if (flags.help || flags.h) {
349
+ print(INSPECT_HELP);
350
+ return;
351
+ }
352
+ // Run inspect command asynchronously
353
+ runInspectCommand(flags);
354
+ return;
355
+
356
+ case 'migrate':
357
+ if (flags.help || flags.h) {
358
+ print(MIGRATE_HELP);
359
+ return;
360
+ }
361
+ // Run migrate command asynchronously
362
+ runMigrateCommand(flags);
363
+ return;
364
+
365
+ case 'help':
366
+ // Handle "originals-cel help <command>"
367
+ const helpTarget = args[1];
368
+ if (helpTarget && !helpTarget.startsWith('-')) {
369
+ showCommandHelp(helpTarget);
370
+ } else {
371
+ print(HELP_TEXT);
372
+ }
373
+ break;
374
+
375
+ default:
376
+ error(`Unknown command: ${command}\n\nRun 'originals-cel --help' for usage.`);
377
+ }
378
+ }
379
+
380
+ // Run if executed directly
381
+ if (typeof process !== 'undefined' && process.argv[1]?.includes('cli')) {
382
+ main();
383
+ }