Skip to content

Commit a20a5b6

Browse files
Update cli.js
1 parent 11f8ea0 commit a20a5b6

1 file changed

Lines changed: 61 additions & 70 deletions

File tree

cli.js

Lines changed: 61 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,28 @@ function getRawSessions() {
8383
}
8484

8585
function syncState(core) {
86-
if (fs.existsSync(STATE_FILE)) core.importSessions(getRawSessions());
87-
const save = () => fs.writeFileSync(STATE_FILE, JSON.stringify(core.exportSessions(), null, 2));
86+
let isWriting = false;
87+
const load = () => {
88+
if (!isWriting && fs.existsSync(STATE_FILE)) {
89+
core.importSessions(getRawSessions());
90+
}
91+
};
92+
93+
load(); // Initial load
94+
95+
// Dynamically hot-reload state file so daemon always matches temporary CLI runs
96+
fs.watchFile(STATE_FILE, { interval: 1000 }, (curr, prev) => {
97+
if (curr.mtimeMs !== prev.mtimeMs) {
98+
load();
99+
}
100+
});
101+
102+
const save = () => {
103+
isWriting = true;
104+
fs.writeFileSync(STATE_FILE, JSON.stringify(core.exportSessions(), null, 2));
105+
setTimeout(() => { isWriting = false; }, 1500);
106+
};
107+
88108
core.on('counter_received', save);
89109
core.on('approval_required', save);
90110
core.on('deal_signed', save);
@@ -138,7 +158,7 @@ program.command('config').description('Set CLI configuration')
138158
});
139159

140160
program.command('init').description('Initialize cryptographic identity')
141-
.option('--key <hex>', 'Import an official private key provided by the Clinch Dashboard (Required for Sellers)')
161+
.option('--key <hex>', 'Import private key')
142162
.action(async (opts) => {
143163
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
144164
const pass = await new Promise(resolve => rl.question('Enter strong vault passphrase: ', ans => { rl.close(); resolve(ans); }));
@@ -151,14 +171,14 @@ program.command('init').description('Initialize cryptographic identity')
151171
const seed = crypto.randomBytes(32);
152172
const keyPair = nacl.sign.keyPair.fromSeed(seed);
153173
secretKeyHex = Buffer.from(keyPair.secretKey).toString('hex');
154-
console.log(`\n💡 Note: A local buyer-only identity was generated. If you are registering a seller node, you must claim a domain on the dashboard and run 'clinch init --key <PRIVATE_KEY>' instead.`);
174+
console.log(`\n💡 Generated new buyer-only identity.`);
155175
}
156176

157177
await SecureVault.save(pass, { privateKeyHex: secretKeyHex, blindKeys: {} });
158178
console.log(`🎉 Identity configured and vault locked.`);
159179
});
160180

161-
program.command('key').description('Manage third-party API credentials (Blind Key Pass vault)')
181+
program.command('key').description('Manage third-party API credentials')
162182
.option('--set <domain>', 'Set a new key for a domain')
163183
.option('--value <key>', 'The actual secret key value')
164184
.option('--list', 'List registered domains')
@@ -178,7 +198,7 @@ program.command('key').description('Manage third-party API credentials (Blind Ke
178198
} else if (opts.set && opts.value) {
179199
vaultRes.parsed.blindKeys[opts.set] = opts.value;
180200
await SecureVault.save(vaultRes.pass, vaultRes.parsed);
181-
console.log(`✓ Key registered! Handshakes and counters targeting ${opts.set} will silently inject this token.`);
201+
console.log(`✓ Key registered!`);
182202
} else {
183203
console.log("Provide --list, or --set <domain> --value <key>");
184204
}
@@ -203,6 +223,7 @@ program.command('start').description('Start the listener daemon')
203223

204224
core.on('counter_received', async (s) => {
205225
console.log(`\n💬 Counter from ${s.targetId}: $${s.lastPrice}`);
226+
if (s.lastMessage) console.log(` [Message]: ${s.lastMessage}`);
206227
if (cfg.openClawWebhook) await fetch(cfg.openClawWebhook, { method: 'POST', body: JSON.stringify({ event: 'counter_received', session: s }) }).catch(()=>{});
207228
});
208229

@@ -211,80 +232,70 @@ program.command('start').description('Start the listener daemon')
211232
process.on('SIGTERM', () => { console.log('\n👋 Shutting down daemon...'); process.exit(0); });
212233
});
213234

214-
// --- DISCOVERY ---
215-
216-
program.command('discover <category>').description('Browse registered sellers for a category')
235+
program.command('discover <category>').description('Browse registered sellers')
217236
.option('--direct', 'JSON output mode')
218237
.action(async (category, opts) => {
219238
const vaultRes = await SecureVault.unlock(opts.direct);
220-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
239+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
221240

222241
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
223242
const results = await core.discover(category);
224243

225244
if (opts.direct) return console.log(JSON.stringify(results));
226245

227246
if (!results.length) {
228-
console.log(`\n🔍 No sellers found for "${category}". Try a broader category.\n`);
247+
console.log(`\n🔍 No sellers found for "${category}".`);
229248
return;
230249
}
231250

232251
console.log(`\n🔍 Sellers for "${category}" (${results.length} found):\n`);
233252
results.forEach((r, i) => {
234253
const name = r.display_name || `${r.agent_id}`;
235254
const official = r.official_node ? ' ✓ Official' : '';
236-
const modes = r.supported_modes && r.supported_modes.length ? r.supported_modes.join(', ') : 'none';
237-
238255
console.log(` [${i + 1}] ${name}${official}`);
239256
console.log(` Domain ID : ${r.agent_id}`);
240-
console.log(` Categories : ${(r.categories || []).join(', ')}`);
241257
console.log(` Capabilities : ${(r.capabilities || []).join(', ')}`);
242-
console.log(` Modes : ${modes}`);
243-
console.log(` Reputation : ${r.reputation_score}/100`);
244258
console.log('');
245259
});
246-
console.log(` 💡 Use --target <agent_id> with 'clinch negotiate' to target a specific seller.\n`);
247260
});
248261

249-
// --- CORE NEGOTIATION COMMANDS ---
250-
251-
program.command('status').description('List all active negotiations')
262+
program.command('status').description('List active negotiations')
252263
.option('--direct', 'JSON output mode')
253264
.action((opts) => {
254265
const data = getRawSessions();
255266
const sessions = Object.values(data).filter(s => s.state !== 'SIGNED' && s.state !== 'CANCELLED');
256267
if (opts.direct) return console.log(JSON.stringify(sessions));
257268
console.log("\n📊 Active Negotiations:");
258269
if (!sessions.length) console.log(" None.");
259-
sessions.forEach(s => console.log(` [${s.state}] ID: ${s.sessionId} | Target: ${s.targetId} | Item: ${s.constraints.item} | Last Price: $${s.lastPrice}`));
270+
sessions.forEach(s => console.log(` [${s.state}] ID: ${s.sessionId} | Target: ${s.targetId} | Last Price: $${s.lastPrice}`));
260271
console.log("");
261272
});
262273

263-
program.command('deals').description('List completed/signed deals')
274+
program.command('deals').description('List signed deals')
264275
.option('--direct', 'JSON output mode')
265276
.action((opts) => {
266277
const data = getRawSessions();
267-
const deals = Object.values(data).filter(s => s.state === 'SIGNED' && s.artifact !== null && s.artifact !== undefined).map(s => s.artifact);
278+
const deals = Object.values(data).filter(s => s.state === 'SIGNED' && s.artifact).map(s => s.artifact);
268279
if (opts.direct) return console.log(JSON.stringify(deals));
269280
console.log("\n🔐 Signed Deals:");
270281
if (!deals.length) console.log(" None.");
271-
deals.forEach(d => console.log(` ID: ${d.sessionId} | Item: ${d.item} | Price: $${d.price} | Date: ${new Date(d.timestamp).toLocaleString()}`));
282+
deals.forEach(d => console.log(` ID: ${d.sessionId} | Item: ${d.item} | Price: $${d.price}`));
272283
console.log("");
273284
});
274285

275286
program.command('negotiate <intent>').description('Initialize a negotiation')
276-
.option('--target <domain>', 'Target seller domain (agent_id)')
287+
.option('--target <domain>', 'Target seller domain')
277288
.option('--direct', 'JSON output mode')
278289
.action(async (intent, opts) => {
279290
const vaultRes = await SecureVault.unlock(opts.direct);
280-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
291+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
281292

282293
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
283294
const save = syncState(core);
284295

285296
const constraints = await extractConstraints(intent, opts.direct);
286297
let target = opts.target || (await core.discover(constraints.item))[0]?.agent_id;
287-
if (!target) return console.log(opts.direct ? JSON.stringify({ error: "No sellers found" }) : "❌ No sellers found. Run 'clinch discover <category>' to browse available sellers.");
298+
if (!target) return console.log(opts.direct ? JSON.stringify({ error: "No sellers found" }) : "❌ No sellers found.");
288299

289300
const session = await core.proposeDeal(target, constraints);
290301
save();
@@ -303,7 +314,7 @@ program.command('counter <sessionId> <price>').description('Counter an offer')
303314
.option('--direct', 'JSON output mode')
304315
.action(async (sessionId, price, opts) => {
305316
const vaultRes = await SecureVault.unlock(opts.direct);
306-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
317+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
307318

308319
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
309320
const save = syncState(core);
@@ -313,19 +324,19 @@ program.command('counter <sessionId> <price>').description('Counter an offer')
313324
save();
314325
if (session.state === NegotiationState.CANCELLED) {
315326
if (opts.direct) console.log(JSON.stringify({ status: "CANCELLED", session }));
316-
else console.log(`\n❌ Seller cancelled the negotiation.\nSession ID: ${session.sessionId}\n`);
327+
else console.log(`\n❌ Session cancelled by peer.\nSession ID: ${session.sessionId}\n`);
317328
} else {
318329
if (opts.direct) console.log(JSON.stringify({ status: "COUNTERED", session }));
319-
else console.log(`✓ Counter of $${price} sent for ${sessionId}`);
330+
else console.log(`✓ Counter of $${price} sent.`);
320331
}
321332
} catch (e) { console.log(opts.direct ? JSON.stringify({ error: e.message }) : `❌ Error: ${e.message}`); }
322333
});
323334

324-
program.command('cancel <sessionId>').description('Cleanly exit a negotiation')
335+
program.command('cancel <sessionId>').description('Exit a negotiation')
325336
.option('--direct', 'JSON output mode')
326337
.action(async (sessionId, opts) => {
327338
const vaultRes = await SecureVault.unlock(opts.direct);
328-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
339+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
329340

330341
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
331342
const save = syncState(core);
@@ -334,15 +345,15 @@ program.command('cancel <sessionId>').description('Cleanly exit a negotiation')
334345
const session = await core.cancelSession(sessionId);
335346
save();
336347
if (opts.direct) console.log(JSON.stringify({ status: "CANCELLED", session }));
337-
else console.log(`✓ Session ${sessionId} cleanly cancelled.`);
348+
else console.log(`✓ Session cleanly cancelled.`);
338349
} catch (e) { console.log(opts.direct ? JSON.stringify({ error: e.message }) : `❌ Error: ${e.message}`); }
339350
});
340351

341-
program.command('approve <sessionId>').description('Cryptographically sign a CONFIRMED deal')
352+
program.command('approve <sessionId>').description('Sign a CONFIRMED deal')
342353
.option('--direct', 'JSON output mode')
343354
.action(async (sessionId, opts) => {
344355
const vaultRes = await SecureVault.unlock(opts.direct);
345-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
356+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
346357

347358
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
348359
const save = syncState(core);
@@ -351,61 +362,45 @@ program.command('approve <sessionId>').description('Cryptographically sign a CON
351362
const artifact = await core.approveAndSign(sessionId);
352363
save();
353364
if (opts.direct) console.log(JSON.stringify({ status: "SIGNED", artifact }));
354-
else console.log(`\n🔐 DEAL SIGNED AND COMMITTED!\nArtifact ID: ${artifact.sessionId}\n`);
365+
else console.log(`\n🔐 DEAL SIGNED AND COMMITTED!\n`);
355366
} catch (e) { console.log(opts.direct ? JSON.stringify({ error: e.message }) : `\n❌ Approval Failed: ${e.message}\n`); }
356367
});
357368

358-
// --- SELLER MODE ---
359369
const nodeCmd = program.command('node').description('Node management commands');
360370

361-
nodeCmd.command('register <agentId> <endpoint>').description('Bind your dashboard .anp domain to your server endpoint')
362-
.option('--categories <list>', 'Comma separated categories (e.g. "retail,hardware")', 'general')
363-
.option('--capabilities <list>', 'Comma separated capabilities (e.g. "room_routing,ai_mediator")', 'http-webhook')
364-
.option('--modes <list>', 'Supported protocol modes (e.g. "ANP/C")', 'ANP/C')
371+
nodeCmd.command('register <agentId> <endpoint>').description('Bind domain to endpoint')
372+
.option('--categories <list>', 'Categories', 'general')
373+
.option('--capabilities <list>', 'Capabilities', 'http-webhook')
374+
.option('--modes <list>', 'Protocol modes', 'ANP/C')
365375
.action(async (agentId, endpoint, opts) => {
366376
const vaultRes = await SecureVault.unlock(false);
367377
if (!vaultRes) return console.log("Run 'clinch init' first.");
368378

369379
const core = await getInitializedCore(vaultRes.parsed, false);
370-
const categories = opts.categories ? opts.categories.split(',').map(c => c.trim()).filter(Boolean) : ['general'];
371-
const capabilities = opts.capabilities ? opts.capabilities.split(',').map(c => c.trim()).filter(Boolean) : ['http-webhook'];
372-
const modes = opts.modes ? opts.modes.split(',').map(m => m.trim()).filter(Boolean) : ['ANP/C'];
380+
const categories = opts.categories ? opts.categories.split(',').map(c => c.trim()) : ['general'];
381+
const capabilities = opts.capabilities ? opts.capabilities.split(',').map(c => c.trim()) : ['http-webhook'];
382+
const modes = opts.modes ? opts.modes.split(',').map(m => m.trim()) : ['ANP/C'];
373383

374384
try {
375-
await core.registerNode(
376-
agentId,
377-
endpoint,
378-
categories,
379-
capabilities,
380-
{ supported_modes: modes }
381-
);
382-
383-
console.log(`\n✓ Endpoint successfully bound!`);
384-
console.log(` Domain : ${agentId}`);
385-
console.log(` Routing To : ${endpoint}`);
386-
console.log(` Categories : ${categories.join(', ')}`);
387-
console.log(` Capabilities : ${capabilities.join(', ')}`);
388-
console.log(` Modes : ${modes.join(', ')}`);
389-
console.log(`\n💡 Note: Core domain parameters (Display Name, Instructions, Identity) are strictly managed via the Clinch Dashboard.\n`);
385+
await core.registerNode(agentId, endpoint, categories, capabilities, { supported_modes: modes });
386+
console.log(`\n✓ Endpoint bound!`);
390387
} catch (e) {
391388
console.log(`\n❌ Registration Failed: ${e.message}\n`);
392389
}
393390
});
394391

395392
program.command('serve').description('Start Seller HTTP server')
396393
.option('--port <p>', 'Port to listen on', 8080)
397-
.option('--config <file>', 'Path to seller config JSON')
398-
.option('--direct', 'JSON output mode for background agent handling')
394+
.option('--config <file>', 'Config path')
395+
.option('--direct', 'JSON output mode')
399396
.action(async (opts) => {
400397
const vaultRes = await SecureVault.unlock(opts.direct);
401-
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked or Uninitialized" }) : "❌ Run 'clinch init' first.");
398+
if (!vaultRes) return console.log(opts.direct ? JSON.stringify({ error: "Vault Locked" }) : "❌ Run 'clinch init' first.");
402399

403400
const core = await getInitializedCore(vaultRes.parsed, opts.direct);
404401
const save = syncState(core);
405402

406-
const sellerCfg = opts.config && fs.existsSync(opts.config)
407-
? JSON.parse(fs.readFileSync(opts.config))
408-
: { defaultFloor: 45, defaultApprove: 100, maxTurns: 5 };
403+
const sellerCfg = opts.config && fs.existsSync(opts.config) ? JSON.parse(fs.readFileSync(opts.config)) : { defaultFloor: 45, defaultApprove: 100, maxTurns: 5 };
409404

410405
const app = express();
411406
app.use(express.json());
@@ -414,11 +409,7 @@ program.command('serve').description('Start Seller HTTP server')
414409
const { session_id, constraints, buyer_pub_key } = req.body;
415410
core.registerIncomingSession(session_id, buyer_pub_key, constraints);
416411

417-
if (opts.direct) console.log(JSON.stringify({ event: "INCOMING_PROPOSAL", session_id, constraints }));
418-
else console.log(`\n🔔 Incoming Proposal: ${session_id} wants ${constraints.item} for ${constraints.max_budget !== null ? '$'+constraints.max_budget : 'unspecified'}`);
419-
420-
const categoryCfg = sellerCfg.categories?.[constraints.item] || sellerCfg;
421-
412+
const categoryCfg = sellerCfg;
422413
if (constraints.max_budget !== null && constraints.max_budget >= categoryCfg.defaultApprove) {
423414
core.updateSessionStateLocally(session_id, NegotiationState.CONFIRMED, categoryCfg.defaultApprove, 1);
424415
res.json({ type: 'CONFIRM', price: categoryCfg.defaultApprove });

0 commit comments

Comments
 (0)