@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.
- package/dist/index.js +161 -91
- 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
|
|
64
|
-
|
|
65
|
-
|
|
170
|
+
const contentHash = hash.bytes32(content);
|
|
171
|
+
format.success("Block Content Hash:");
|
|
172
|
+
format.value(contentHash);
|
|
66
173
|
}
|
|
67
174
|
catch (error) {
|
|
68
|
-
|
|
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",
|
|
182
|
+
.option("-i, --intent <intent>", "Agent intent", CONSTANTS.DEFAULT_INTENT)
|
|
76
183
|
.action((agent_name, options) => {
|
|
77
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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",
|
|
102
|
-
.option("--ledger <name>", "Ledger Name",
|
|
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
|
|
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
|
-
|
|
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 =
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
+
format.info(`Auditing Block ${blockId}...`);
|
|
193
263
|
const block = await chainClient.getBlock(blockId);
|
|
194
264
|
if (!block) {
|
|
195
|
-
|
|
265
|
+
format.error("Block not found on-chain.");
|
|
196
266
|
return;
|
|
197
267
|
}
|
|
198
|
-
|
|
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
|
-
|
|
273
|
+
format.warn("⚠️ No IPFS CID. Cannot perform deep content audit.");
|
|
204
274
|
return;
|
|
205
275
|
}
|
|
206
|
-
|
|
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 =
|
|
279
|
+
const calcHash = hash.bytes32(content);
|
|
210
280
|
console.log(` Calculated: ${calcHash}`);
|
|
211
281
|
if (calcHash === block.contentHash) {
|
|
212
|
-
|
|
282
|
+
format.success("✅ INTEGRITY VERIFIED: Content matches chain hash.");
|
|
213
283
|
}
|
|
214
284
|
else {
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
+
format.success(`✓ Downloaded ${filename}`);
|
|
255
325
|
}
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
329
|
+
format.warn("🔒 Bundle was decrypted using provided key");
|
|
260
330
|
}
|
|
261
331
|
}
|
|
262
332
|
catch (error) {
|
|
263
|
-
|
|
264
|
-
|
|
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.
|
|
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.
|
|
46
|
+
"@yamo/core": "^1.2.12",
|
|
47
47
|
"axios": "^1.13.2",
|
|
48
48
|
"chalk": "^4.1.2",
|
|
49
49
|
"commander": "^12.0.0",
|