@yamo/cli 1.3.4 → 1.3.10

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 (2) hide show
  1. package/dist/index.js +161 -91
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -46,9 +46,116 @@ const dotenv = __importStar(require("dotenv"));
46
46
  const core_1 = require("@yamo/core");
47
47
  const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf8'));
48
48
  dotenv.config();
49
+ // Constants
50
+ const CONSTANTS = {
51
+ HASH_PATTERN: /^0x[a-fA-F0-9]{64}$/,
52
+ GENESIS_HASH: "0x0000000000000000000000000000000000000000000000000000000000000000",
53
+ DEFAULT_FILENAME: "block.yamo",
54
+ DEFAULT_CONSENSUS: "cli_manual",
55
+ DEFAULT_LEDGER: "yamo_cli",
56
+ DEFAULT_INTENT: "execute_task",
57
+ HEX_PREFIX: "0x",
58
+ HASH_ALGORITHM: "sha256",
59
+ };
49
60
  const program = new commander_1.Command();
50
61
  const ipfsManager = new core_1.IpfsManager();
51
62
  const chainClient = new core_1.YamoChainClient();
63
+ // Hash utilities
64
+ const hash = {
65
+ sha256: (content) => {
66
+ return crypto_1.default.createHash(CONSTANTS.HASH_ALGORITHM).update(content).digest("hex");
67
+ },
68
+ bytes32: (content) => {
69
+ return `${CONSTANTS.HEX_PREFIX}${hash.sha256(content)}`;
70
+ },
71
+ };
72
+ // Response formatting helpers
73
+ const format = {
74
+ success: (msg) => console.log(chalk_1.default.green(msg)),
75
+ error: (msg) => console.error(chalk_1.default.red(`Error: ${msg}`)),
76
+ info: (msg) => console.log(chalk_1.default.blue(msg)),
77
+ warn: (msg) => console.log(chalk_1.default.yellow(msg)),
78
+ detail: (msg) => console.log(chalk_1.default.gray(msg)),
79
+ value: (msg) => console.log(chalk_1.default.cyan(msg)),
80
+ };
81
+ // Error handling helper
82
+ function handleCommandError(error, context) {
83
+ if (error instanceof Error) {
84
+ const message = context ? `${context}: ${error.message}` : error.message;
85
+ format.error(message);
86
+ }
87
+ else {
88
+ format.error("Unknown error occurred");
89
+ }
90
+ }
91
+ // Validation helpers
92
+ function validateBytes32(value, fieldName) {
93
+ if (!value.match(CONSTANTS.HASH_PATTERN)) {
94
+ throw new Error(`${fieldName} must be a valid bytes32 hash (0x + 64 hex chars). ` +
95
+ `Received: ${value.substring(0, 20)}...` +
96
+ `\nDo NOT include algorithm prefixes like "sha256:"`);
97
+ }
98
+ }
99
+ function validateBlockId(blockId) {
100
+ if (!blockId)
101
+ throw new Error("blockId is required");
102
+ const parts = blockId.split('_');
103
+ if (parts.length < 2) {
104
+ throw new Error(`blockId must follow format {origin}_{workflow} (e.g., 'claude_chain'). Received: ${blockId}`);
105
+ }
106
+ }
107
+ // Submit command helpers
108
+ async function validateEncryptionKey(key) {
109
+ const { validatePasswordStrength } = await import("@yamo/core");
110
+ try {
111
+ validatePasswordStrength(key);
112
+ }
113
+ catch (e) {
114
+ format.error("Password validation failed:");
115
+ format.error(e.message);
116
+ format.warn("\nKey requirements:");
117
+ console.error(" • Minimum 12 characters");
118
+ console.error(" • Mix of uppercase, lowercase, numbers, symbols");
119
+ console.error(" • Avoid common patterns (password, 123456, qwerty)");
120
+ throw e;
121
+ }
122
+ }
123
+ function prepareIpfsFiles(content, file) {
124
+ const files = [{ name: CONSTANTS.DEFAULT_FILENAME, content }];
125
+ const outputMatch = content.match(/output:\s*([^;]+);/);
126
+ if (outputMatch) {
127
+ const artifactName = outputMatch[1].trim();
128
+ // Security: Check for path traversal patterns
129
+ if (artifactName.includes('..') || artifactName.startsWith('/')) {
130
+ throw new Error(`Invalid artifact name: ${artifactName} (path-like names are not allowed)`);
131
+ }
132
+ const artifactPath = path_1.default.join(path_1.default.dirname(file), artifactName);
133
+ const resolvedPath = path_1.default.resolve(artifactPath);
134
+ const inputDir = path_1.default.resolve(path_1.default.dirname(file));
135
+ if (!resolvedPath.startsWith(inputDir)) {
136
+ throw new Error(`Artifact path outside allowed directory: ${artifactName}`);
137
+ }
138
+ if (fs_1.default.existsSync(resolvedPath)) {
139
+ format.info(`Bundling output: ${artifactName}`);
140
+ files.push({ name: artifactName, content: fs_1.default.readFileSync(resolvedPath, "utf8") });
141
+ }
142
+ }
143
+ return files;
144
+ }
145
+ async function resolvePreviousBlock(prev) {
146
+ if (prev) {
147
+ validateBytes32(prev, "previousBlock");
148
+ return prev;
149
+ }
150
+ format.info("[INFO] No previousBlock provided, fetching latest block from chain...");
151
+ const latestHash = await chainClient.getLatestBlockHash();
152
+ if (latestHash && latestHash !== CONSTANTS.GENESIS_HASH) {
153
+ format.success(`[INFO] Using latest block's contentHash: ${latestHash}`);
154
+ return latestHash;
155
+ }
156
+ format.warn("[INFO] No existing blocks found, using genesis");
157
+ return CONSTANTS.GENESIS_HASH;
158
+ }
52
159
  program
53
160
  .name("yamo")
54
161
  .description("YAMO Protocol CLI - Manage Agentic Reasoning Chains")
@@ -60,21 +167,22 @@ program
60
167
  .action((file) => {
61
168
  try {
62
169
  const content = fs_1.default.readFileSync(file, "utf8").trim();
63
- const hash = crypto_1.default.createHash("sha256").update(content).digest("hex");
64
- console.log(chalk_1.default.green("Block Content Hash:"));
65
- console.log(chalk_1.default.cyan(`0x${hash}`));
170
+ const contentHash = hash.bytes32(content);
171
+ format.success("Block Content Hash:");
172
+ format.value(contentHash);
66
173
  }
67
174
  catch (error) {
68
- console.error(chalk_1.default.red(`Error: ${error.message}`));
175
+ handleCommandError(error);
69
176
  }
70
177
  });
71
178
  program
72
179
  .command("init")
73
180
  .description("Initialize a new YAMO block template")
74
181
  .argument("<agent_name>", "Name of the agent")
75
- .option("-i, --intent <intent>", "Agent intent", "execute_task")
182
+ .option("-i, --intent <intent>", "Agent intent", CONSTANTS.DEFAULT_INTENT)
76
183
  .action((agent_name, options) => {
77
- const template = `
184
+ try {
185
+ const template = `
78
186
  agent: ${agent_name};
79
187
  intent: ${options.intent};
80
188
  context:
@@ -88,9 +196,12 @@ meta: confidence;0.9;
88
196
  log: session_start;timestamp;${new Date().toISOString()};
89
197
  handoff: User;
90
198
  `.trim();
91
- const filename = "block.yamo";
92
- fs_1.default.writeFileSync(filename, template);
93
- console.log(chalk_1.default.green(`Created YAMO template: ${chalk_1.default.bold(filename)}`));
199
+ fs_1.default.writeFileSync(CONSTANTS.DEFAULT_FILENAME, template);
200
+ format.success(`Created YAMO template: ${chalk_1.default.bold(CONSTANTS.DEFAULT_FILENAME)}`);
201
+ }
202
+ catch (error) {
203
+ handleCommandError(error);
204
+ }
94
205
  });
95
206
  program
96
207
  .command("submit")
@@ -98,88 +209,47 @@ program
98
209
  .argument("<file>", "Path to the YAMO file")
99
210
  .requiredOption("--id <blockId>", "Unique Block ID")
100
211
  .option("--prev <previousBlock>", "Previous Block Hash (omits to auto-fetch from chain)")
101
- .option("--consensus <type>", "Consensus Type", "cli_manual")
102
- .option("--ledger <name>", "Ledger Name", "yamo_cli")
212
+ .option("--consensus <type>", "Consensus Type", CONSTANTS.DEFAULT_CONSENSUS)
213
+ .option("--ledger <name>", "Ledger Name", CONSTANTS.DEFAULT_LEDGER)
103
214
  .option("--ipfs", "Upload content to IPFS before submitting")
104
215
  .option("-e, --encrypt", "Encrypt the bundle")
105
216
  .option("-k, --key <key>", "Encryption key (or set YAMO_ENCRYPTION_KEY)")
106
217
  .action(async (file, options) => {
107
218
  try {
108
- // Validate password if encryption is enabled
219
+ // Validate inputs
220
+ validateBlockId(options.id);
221
+ // Validate encryption key if needed
109
222
  if (options.encrypt) {
110
223
  const key = options.key || process.env.YAMO_ENCRYPTION_KEY;
111
224
  if (!key) {
112
225
  throw new Error("Encryption enabled but no key provided. Use --key or set YAMO_ENCRYPTION_KEY");
113
226
  }
114
- try {
115
- const { validatePasswordStrength } = await import("@yamo/core");
116
- validatePasswordStrength(key);
117
- }
118
- catch (e) {
119
- console.error(chalk_1.default.red("Password validation failed:"));
120
- console.error(chalk_1.default.red(e.message));
121
- console.error(chalk_1.default.yellow("\nKey requirements:"));
122
- console.error(" • Minimum 12 characters");
123
- console.error(" • Mix of uppercase, lowercase, numbers, symbols");
124
- console.error(" • Avoid common patterns (password, 123456, qwerty)");
125
- throw e;
126
- }
227
+ await validateEncryptionKey(key);
127
228
  }
229
+ // Calculate content hash
128
230
  const content = fs_1.default.readFileSync(file, "utf8").trim();
129
- const contentHash = "0x" + crypto_1.default.createHash("sha256").update(content).digest("hex");
130
- console.log(chalk_1.default.blue(`Calculated Hash: ${contentHash}`));
131
- let ipfsCID = undefined;
231
+ const contentHash = hash.bytes32(content);
232
+ format.info(`Calculated Hash: ${contentHash}`);
233
+ // Handle IPFS upload
234
+ let ipfsCID;
132
235
  if (options.ipfs) {
133
- const outputMatch = content.match(/output:\s*([^;]+);/);
134
- const files = [{ name: "block.yamo", content }];
135
- if (outputMatch) {
136
- const artifactName = outputMatch[1].trim();
137
- // Security: Check for path traversal patterns in artifact name (Part 3: Security Fixes)
138
- if (artifactName.includes('..') || artifactName.startsWith('/')) {
139
- throw new Error(`Invalid artifact name: ${artifactName} (path-like names are not allowed)`);
140
- }
141
- const artifactPath = path_1.default.join(path_1.default.dirname(file), artifactName);
142
- // Security: Resolve to absolute path and restrict to input file directory
143
- const resolvedPath = path_1.default.resolve(artifactPath);
144
- const inputDir = path_1.default.resolve(path_1.default.dirname(file));
145
- if (!resolvedPath.startsWith(inputDir)) {
146
- throw new Error(`Artifact path outside allowed directory: ${artifactName}`);
147
- }
148
- if (fs_1.default.existsSync(resolvedPath)) {
149
- console.log(chalk_1.default.cyan(`Bundling output: ${artifactName}`));
150
- files.push({ name: artifactName, content: fs_1.default.readFileSync(resolvedPath, "utf8") });
151
- }
152
- }
153
- let encryptionKey = undefined;
154
- if (options.encrypt) {
155
- encryptionKey = options.key || process.env.YAMO_ENCRYPTION_KEY;
156
- if (!encryptionKey) {
157
- throw new Error("Encryption enabled but no key provided. Use --key or YAMO_ENCRYPTION_KEY.");
158
- }
159
- console.log(chalk_1.default.yellow("🔒 Encrypting bundle..."));
236
+ const files = prepareIpfsFiles(content, file);
237
+ const encryptionKey = options.encrypt
238
+ ? (options.key || process.env.YAMO_ENCRYPTION_KEY)
239
+ : undefined;
240
+ if (encryptionKey) {
241
+ format.warn("🔒 Encrypting bundle...");
160
242
  }
161
243
  ipfsCID = await ipfsManager.upload({ content, files, encryptionKey });
162
- console.log(chalk_1.default.cyan(`IPFS Bundle CID: ${ipfsCID}`));
163
- }
164
- // Auto-fetch previousBlock if not provided (chain continuation)
165
- let resolvedPreviousBlock = options.prev;
166
- if (!resolvedPreviousBlock) {
167
- console.log(chalk_1.default.blue(`[INFO] No previousBlock provided, fetching latest block from chain...`));
168
- const latestBlock = await chainClient.getLatestBlock();
169
- if (latestBlock) {
170
- resolvedPreviousBlock = latestBlock.contentHash;
171
- console.log(chalk_1.default.green(`[INFO] Using latest block's contentHash: ${resolvedPreviousBlock}`));
172
- }
173
- else {
174
- // No blocks exist yet, use genesis
175
- resolvedPreviousBlock = "0x0000000000000000000000000000000000000000000000000000000000000000";
176
- console.log(chalk_1.default.yellow(`[INFO] No existing blocks found, using genesis`));
177
- }
244
+ format.info(`IPFS Bundle CID: ${ipfsCID}`);
178
245
  }
246
+ // Resolve previous block
247
+ const resolvedPreviousBlock = await resolvePreviousBlock(options.prev);
248
+ // Submit to blockchain
179
249
  await chainClient.submitBlock(options.id, resolvedPreviousBlock, contentHash, options.consensus, options.ledger, ipfsCID);
180
250
  }
181
251
  catch (error) {
182
- console.error(chalk_1.default.red(`Error: ${error.message}`));
252
+ handleCommandError(error);
183
253
  }
184
254
  });
185
255
  program
@@ -189,36 +259,36 @@ program
189
259
  .option("-k, --key <key>", "Decryption key")
190
260
  .action(async (blockId, options) => {
191
261
  try {
192
- console.log(chalk_1.default.blue(`Auditing Block ${blockId}...`));
262
+ format.info(`Auditing Block ${blockId}...`);
193
263
  const block = await chainClient.getBlock(blockId);
194
264
  if (!block) {
195
- console.error(chalk_1.default.red("Block not found on-chain."));
265
+ format.error("Block not found on-chain.");
196
266
  return;
197
267
  }
198
- console.log(chalk_1.default.gray(`Found on-chain record:`));
268
+ format.detail("Found on-chain record:");
199
269
  console.log(` Agent: ${block.agentAddress}`);
200
270
  console.log(` Hash: ${block.contentHash}`);
201
271
  console.log(` IPFS: ${block.ipfsCID || "None"}`);
202
272
  if (!block.ipfsCID) {
203
- console.log(chalk_1.default.yellow("⚠️ No IPFS CID. Cannot perform deep content audit."));
273
+ format.warn("⚠️ No IPFS CID. Cannot perform deep content audit.");
204
274
  return;
205
275
  }
206
- console.log(chalk_1.default.blue("Fetching content from IPFS..."));
276
+ format.info("Fetching content from IPFS...");
207
277
  const key = options.key || process.env.YAMO_ENCRYPTION_KEY;
208
278
  const content = await ipfsManager.download(block.ipfsCID, key);
209
- const calcHash = "0x" + crypto_1.default.createHash("sha256").update(content).digest("hex");
279
+ const calcHash = hash.bytes32(content);
210
280
  console.log(` Calculated: ${calcHash}`);
211
281
  if (calcHash === block.contentHash) {
212
- console.log(chalk_1.default.green("✅ INTEGRITY VERIFIED: Content matches chain hash."));
282
+ format.success("✅ INTEGRITY VERIFIED: Content matches chain hash.");
213
283
  }
214
284
  else {
215
- console.log(chalk_1.default.red("❌ INTEGRITY FAILED: Hash mismatch!"));
285
+ format.error("❌ INTEGRITY FAILED: Hash mismatch!");
216
286
  console.log(` Expected: ${block.contentHash}`);
217
287
  console.log(` Got: ${calcHash}`);
218
288
  }
219
289
  }
220
290
  catch (error) {
221
- console.error(chalk_1.default.red(`Error: ${error.message}`));
291
+ handleCommandError(error);
222
292
  }
223
293
  });
224
294
  program
@@ -229,7 +299,7 @@ program
229
299
  .option("-o, --output <dir>", "Output directory (default: ./bundle_<cid>)", "./bundle_<cid>")
230
300
  .action(async (cid, options) => {
231
301
  try {
232
- console.log(chalk_1.default.blue(`Downloading bundle ${cid}...`));
302
+ format.info(`Downloading bundle ${cid}...`);
233
303
  const { IpfsManager } = await import("@yamo/core");
234
304
  const ipfs = new IpfsManager();
235
305
  const key = options.key || process.env.YAMO_ENCRYPTION_KEY;
@@ -240,28 +310,28 @@ program
240
310
  fs_1.default.mkdirSync(outputDir, { recursive: true });
241
311
  }
242
312
  // Write block.yamo
243
- fs_1.default.writeFileSync(path_1.default.join(outputDir, "block.yamo"), bundle.block);
244
- console.log(chalk_1.default.green(`✓ Downloaded block.yamo`));
313
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, CONSTANTS.DEFAULT_FILENAME), bundle.block);
314
+ format.success(`✓ Downloaded ${CONSTANTS.DEFAULT_FILENAME}`);
245
315
  // Write metadata
246
316
  if (bundle.metadata) {
247
317
  fs_1.default.writeFileSync(path_1.default.join(outputDir, "metadata.json"), JSON.stringify(bundle.metadata, null, 2));
248
- console.log(chalk_1.default.green(`✓ Downloaded metadata.json`));
318
+ format.success("✓ Downloaded metadata.json");
249
319
  }
250
320
  // Write artifact files
251
321
  for (const [filename, content] of Object.entries(bundle.files)) {
252
322
  const filePath = path_1.default.join(outputDir, filename);
253
323
  fs_1.default.writeFileSync(filePath, content);
254
- console.log(chalk_1.default.green(`✓ Downloaded ${filename}`));
324
+ format.success(`✓ Downloaded ${filename}`);
255
325
  }
256
- console.log(chalk_1.default.green(`\nBundle saved to: ${outputDir}`));
257
- console.log(chalk_1.default.gray(`Files: ${1 + Object.keys(bundle.files).length} total`));
326
+ format.success(`\nBundle saved to: ${outputDir}`);
327
+ format.detail(`Files: ${1 + Object.keys(bundle.files).length} total`);
258
328
  if (bundle.metadata?.hasEncryption) {
259
- console.log(chalk_1.default.yellow("🔒 Bundle was decrypted using provided key"));
329
+ format.warn("🔒 Bundle was decrypted using provided key");
260
330
  }
261
331
  }
262
332
  catch (error) {
263
- console.error(chalk_1.default.red(`Error: ${error.message}`));
264
- console.error(chalk_1.default.gray("\nIf the bundle is encrypted, provide --key or set YAMO_ENCRYPTION_KEY"));
333
+ handleCommandError(error);
334
+ format.detail("\nIf the bundle is encrypted, provide --key or set YAMO_ENCRYPTION_KEY");
265
335
  }
266
336
  });
267
337
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamo/cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.10",
4
4
  "description": "YAMO Protocol v0.4 - Command-line tools for blockchain integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",
@@ -43,7 +43,7 @@
43
43
  "typescript": "^5.9.3"
44
44
  },
45
45
  "dependencies": {
46
- "@yamo/core": "^1.2.7",
46
+ "@yamo/core": "^1.2.12",
47
47
  "axios": "^1.13.2",
48
48
  "chalk": "^4.1.2",
49
49
  "commander": "^12.0.0",