agent-clinch 0.7.5 → 0.7.7

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/cli.js +253 -98
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -10,6 +10,7 @@ const path = require('path');
10
10
  const os = require('os');
11
11
  const readline = require('readline');
12
12
  const crypto = require('crypto');
13
+ const https = require('https');
13
14
 
14
15
  const CONFIG_DIR = path.join(os.homedir(), '.clinch');
15
16
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -44,7 +45,7 @@ function decrypt(encJson) {
44
45
  decrypted += decipher.final('utf8');
45
46
  return decrypted;
46
47
  } catch (e) {
47
- return null; // Decryption failed (file moved to different machine or tampered)
48
+ return null;
48
49
  }
49
50
  }
50
51
 
@@ -102,14 +103,14 @@ function saveSessionState(sessionId, core) {
102
103
  };
103
104
  fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
104
105
  } catch (e) {
105
- // Session might be deleted or errored, ignore gracefully in CLI
106
+ // Ignore gracefully
106
107
  }
107
108
  }
108
109
 
109
110
  function requireConfig() {
110
111
  const cfg = loadConfig();
111
112
  if (!cfg) {
112
- console.error('Not initialized. Run: clinch init');
113
+ console.error(c.red('Not initialized. Run: clinch init'));
113
114
  process.exit(1);
114
115
  }
115
116
  return cfg;
@@ -119,25 +120,23 @@ function getClinchCore(cfg) {
119
120
  let ClinchCoreModule;
120
121
  try { ClinchCoreModule = require('clinch-core'); }
121
122
  catch {
122
- console.error('clinch-core not found. Ensure it is linked or installed.');
123
+ console.error(c.red('clinch-core not found. Ensure it is linked or installed.'));
123
124
  process.exit(1);
124
125
  }
125
126
  const { ClinchCore } = ClinchCoreModule;
126
127
  const core = new ClinchCore({ registryUrl: cfg.registryUrl });
127
128
 
128
- // Dynamically hydrate ClinchCore memory with locally stored API keys
129
129
  const secrets = loadSecrets();
130
130
  for (const [domain, s] of Object.entries(secrets)) {
131
131
  core.registerSecret(domain, s.key, s.name);
132
132
  }
133
133
 
134
134
  core.on('log', msg => console.log(msg));
135
- core.on('error', err => console.error('Error:', err.message));
135
+ core.on('error', err => console.error(c.red('Error:'), err.message));
136
136
 
137
137
  return core;
138
138
  }
139
139
 
140
- // ── Prompt Helper ─────────────────────────────────────────────
141
140
  function prompt(question) {
142
141
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
143
142
  return new Promise(resolve => {
@@ -145,53 +144,160 @@ function prompt(question) {
145
144
  });
146
145
  }
147
146
 
148
- // ── Conversational AI Intent Parser (CLI Layer) ───────────────
149
- async function parseIntentWithLLM(userInput, modelPath) {
150
- console.log(c.dim("\n[Agent Q] Booting local parser model to analyze your request..."));
151
- let nodeLlama;
152
- try { nodeLlama = await import('node-llama-cpp'); }
153
- catch (e) {
154
- console.error(c.red("\nError: node-llama-cpp is required for conversational parsing."));
155
- console.error("Please run: npm install -g node-llama-cpp\n");
156
- process.exit(1);
147
+ // ── AI Engine Orchestration (Ollama vs GGUF) ──────────────────
148
+ async function ensureAIEngine(cfg) {
149
+ if (cfg.engine) return cfg;
150
+
151
+ console.log(c.bold("\n🤖 Local AI Engine Setup"));
152
+ console.log(c.dim("Agent Q requires a local AI to parse intents and auto-negotiate."));
153
+
154
+ const choice = await prompt("Which engine would you like to use?\n 1) Ollama (Recommended - Requires Ollama running locally)\n 2) Standalone GGUF (Downloads ~1.1GB model)\n👉 (1/2): ");
155
+
156
+ if (choice === '1') {
157
+ cfg.engine = 'ollama';
158
+ const model = await prompt("👉 Enter Ollama model name (default: llama3): ");
159
+ cfg.ollamaModel = model || 'llama3';
160
+ } else {
161
+ cfg.engine = 'gguf';
162
+ const dl = await prompt("👉 Download default Qwen 1.5B model? (Y/n): ");
163
+ if (dl.toLowerCase() !== 'n') {
164
+ cfg.ggufUrl = "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf";
165
+ } else {
166
+ cfg.ggufUrl = await prompt("👉 Enter custom GGUF download URL: ");
167
+ }
157
168
  }
169
+
170
+ saveConfig(cfg);
171
+ return cfg;
172
+ }
158
173
 
159
- const resolvedPath = path.resolve(modelPath);
160
- if (!fs.existsSync(resolvedPath)) {
161
- console.error(c.red(`\nError: Model not found at ${resolvedPath}`));
162
- process.exit(1);
174
+ function downloadFile(url, dest) {
175
+ return new Promise((resolve, reject) => {
176
+ const file = fs.createWriteStream(dest);
177
+ const request = (currentUrl) => {
178
+ https.get(currentUrl, (response) => {
179
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
180
+ return request(response.headers.location);
181
+ }
182
+ if (response.statusCode !== 200) {
183
+ return reject(new Error(`Download failed: ${response.statusCode}`));
184
+ }
185
+ const total = parseInt(response.headers['content-length'], 10);
186
+ let downloaded = 0;
187
+ response.on('data', (chunk) => {
188
+ downloaded += chunk.length;
189
+ const msg = total ? `${((downloaded / total) * 100).toFixed(1)}%` : `${(downloaded / 1024 / 1024).toFixed(1)} MB`;
190
+ process.stdout.write(`\r${c.yellow('Downloading model...')} ${msg}`);
191
+ });
192
+ response.pipe(file);
193
+ file.on('finish', () => {
194
+ console.log(c.green("\n✓ Download complete!"));
195
+ file.close(resolve);
196
+ });
197
+ }).on('error', (err) => {
198
+ fs.unlink(dest, () => reject(err));
199
+ });
200
+ };
201
+ request(url);
202
+ });
203
+ }
204
+
205
+ async function promptAI(systemPrompt, userText, cfg) {
206
+ if (cfg.engine === 'ollama') {
207
+ try {
208
+ console.log(c.dim(`\n[Agent Q] Dispatching request to local Ollama (${cfg.ollamaModel || 'llama3'})...`));
209
+ const res = await fetch('http://127.0.0.1:11434/api/chat', {
210
+ method: 'POST',
211
+ headers: { 'Content-Type': 'application/json' },
212
+ body: JSON.stringify({
213
+ model: cfg.ollamaModel || 'llama3',
214
+ messages: [
215
+ { role: 'system', content: systemPrompt },
216
+ { role: 'user', content: userText }
217
+ ],
218
+ stream: false
219
+ })
220
+ });
221
+ if (!res.ok) throw new Error(`Ollama returned status ${res.status}`);
222
+ const data = await res.json();
223
+ return data.message.content;
224
+ } catch (e) {
225
+ console.error(c.red(`\n[!] Ollama request failed: ${e.message}`));
226
+ console.error(c.dim(`Please ensure Ollama is running (http://127.0.0.1:11434) and the model is pulled.`));
227
+ process.exit(1);
228
+ }
229
+ } else {
230
+ // GGUF Execution
231
+ const resolvedPath = path.resolve(cfg.modelPath || path.join(CONFIG_DIR, 'model.gguf'));
232
+ if (!fs.existsSync(resolvedPath)) {
233
+ console.log(c.yellow(`\nModel not found at ${resolvedPath}`));
234
+ await downloadFile(cfg.ggufUrl || "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf", resolvedPath);
235
+ }
236
+
237
+ console.log(c.dim(`\n[Agent Q] Loading GGUF model into memory (this may take a few seconds)...`));
238
+
239
+ let nodeLlama;
240
+ try { nodeLlama = await import('node-llama-cpp'); }
241
+ catch (e) {
242
+ console.error(c.red("\nError: 'node-llama-cpp' is required for GGUF execution."));
243
+ console.error("Run: npm install -g node-llama-cpp");
244
+ process.exit(1);
245
+ }
246
+
247
+ const llama = await nodeLlama.getLlama();
248
+ const model = await llama.loadModel({ modelPath: resolvedPath });
249
+ const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
250
+ const session = new nodeLlama.LlamaChatSession({
251
+ contextSequence: context.getSequence(),
252
+ systemPrompt: systemPrompt,
253
+ chatWrapper: new nodeLlama.ChatMLChatWrapper()
254
+ });
255
+
256
+ console.log(c.dim(`[Agent Q] Model loaded. Analyzing intent...`));
257
+
258
+ let responseText = "";
259
+ await session.prompt(userText, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
260
+ return responseText;
163
261
  }
262
+ }
164
263
 
165
- const llama = await nodeLlama.getLlama();
166
- const model = await llama.loadModel({ modelPath: resolvedPath });
167
- const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
264
+ async function parseIntentWithLLM(userInput, cfg) {
265
+ const systemPrompt = `You are a structured data extractor for a purchasing agent.
266
+ Analyze the user's input.
168
267
 
169
- const systemPrompt = `You are a structured data extractor. Convert the user's conversational intent into a strict JSON schema.
170
- Your response MUST be ONLY valid JSON matching this schema exactly. Do not output conversational text.
268
+ If the user says a greeting (like "hi" or "hello") or does not clearly specify BOTH an item and a budget, output EXACTLY this JSON:
269
+ {"error": "Please specify what you want to buy and your maximum budget (e.g. 'Get me a laptop for under $500')."}
171
270
 
172
- JSON Schema:
271
+ If they DO specify a purchase intent, output EXACTLY this JSON schema:
173
272
  {
174
273
  "intent": "purchase",
175
- "category": "string (e.g. domain_name, electronics, kitchen_appliance)",
176
- "item": "string (the exact item, website, or product they want)",
177
- "max_budget": number (extract budget numeric value),
178
- "must_haves": ["array", "of", "strings", "representing", "conditions", "like", "WHOIS privacy", "warranty"]
179
- }`;
180
-
181
- const session = new nodeLlama.LlamaChatSession({
182
- contextSequence: context.getSequence(),
183
- systemPrompt: systemPrompt,
184
- chatWrapper: new nodeLlama.ChatMLChatWrapper()
185
- });
186
-
187
- let responseText = "";
188
- await session.prompt(userInput, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
274
+ "category": "string (e.g. electronics, domain_names, software, etc)",
275
+ "item": "string (the actual item requested)",
276
+ "max_budget": number (integer representing the max budget)
277
+ }
278
+ Your response MUST be ONLY valid JSON. Do not include conversational text.`;
189
279
 
190
280
  try {
191
- const cleanJson = responseText.replace(/```json|```/g, "").trim();
192
- return JSON.parse(cleanJson);
281
+ const rawRes = await promptAI(systemPrompt, userInput, cfg);
282
+ const cleanJson = rawRes.replace(/```json|```/g, "").trim();
283
+
284
+ const parsed = JSON.parse(cleanJson);
285
+
286
+ // Check if the LLM flagged the input as a greeting/unclear
287
+ if (parsed.error) {
288
+ console.log(c.yellow(`\n[Agent Q] ${parsed.error}`));
289
+ return null;
290
+ }
291
+
292
+ // Fallback validation to ensure it didn't hallucinate missing fields
293
+ if (!parsed.item || !parsed.max_budget) {
294
+ console.log(c.yellow(`\n[Agent Q] I couldn't quite figure out the item or budget from your request. Please be specific!`));
295
+ return null;
296
+ }
297
+
298
+ return parsed;
193
299
  } catch (e) {
194
- console.error(c.red("Failed to parse intent. Falling back to manual entry."));
300
+ console.error(c.red("\n[Agent Q] Failed to parse intent correctly. Falling back to manual entry."));
195
301
  return null;
196
302
  }
197
303
  }
@@ -226,34 +332,29 @@ program
226
332
  .command('init')
227
333
  .description('Initialize your Clinch buyer agent')
228
334
  .option('--registry <url>', 'Custom registry URL')
229
- .option('--model <path>', 'Path to custom GGUF model')
230
335
  .action(async (opts) => {
231
336
  banner();
232
337
  console.log(c.bold('Setting up your Clinch agent...\n'));
233
338
 
234
- const existing = loadConfig();
235
- if (existing) {
236
- const overwrite = await prompt('Config already exists. Overwrite? (y/N): ');
339
+ let config = loadConfig() || {};
340
+ if (config.pubKey) {
341
+ const overwrite = await prompt('Config already exists. Overwrite network identity? (y/N): ');
237
342
  if (overwrite.toLowerCase() !== 'y') { console.log('Aborted.'); process.exit(0); }
238
343
  }
239
344
 
240
- const registryUrl = opts.registry || 'https://everydaytok-agentq-core-logics.hf.space';
241
- const modelPath = opts.model || path.join(CONFIG_DIR, 'model.gguf');
345
+ config.registryUrl = opts.registry || 'https://everydaytok-agentq-core-logics.hf.space';
346
+ config.modelPath = path.join(CONFIG_DIR, 'model.gguf');
347
+ config = await ensureAIEngine(config);
242
348
 
243
- console.log(c.yellow('Connecting to registry and completing PoW handshake...'));
349
+ console.log(c.yellow('\nConnecting to registry and completing PoW handshake...'));
244
350
 
245
- const core = getClinchCore({ registryUrl });
351
+ const core = getClinchCore(config);
246
352
  await core.initialize();
247
353
 
248
- const config = {
249
- registryUrl,
250
- modelPath,
251
- pubKey: core.identityPubKey,
252
- token: core.jwtToken,
253
- mode: 'ANP/A',
254
- localOnly: false,
255
- createdAt: new Date().toISOString()
256
- };
354
+ config.pubKey = core.identityPubKey;
355
+ config.token = core.jwtToken;
356
+ config.mode = 'ANP/A';
357
+ config.createdAt = new Date().toISOString();
257
358
 
258
359
  saveConfig(config);
259
360
  core.disconnect();
@@ -306,12 +407,12 @@ program
306
407
  .argument('[address]', 'ANP address — format: MODE.domain.anp (e.g. ANP/C.amazon.anp)')
307
408
  .option('--budget <n>', 'Max budget (USD)')
308
409
  .option('--item <name>', 'Specific item to negotiate')
309
- .option('--category <name>', 'Market category (Triggers cascade negotiation across matching sellers if address is omitted)')
310
- .option('--squeeze <n>', 'Number of sellers to sequentially squeeze (Low urgency / price optimization)', '3')
311
- .option('--parallel <n>', 'Number of sellers to negotiate with simultaneously in parallel (High urgency / ride-hailing)')
312
- .option('--auto', 'Run sandbox auto-negotiation')
410
+ .option('--category <name>', 'Market category (Triggers cascade negotiation across matching sellers)')
411
+ .option('--squeeze <n>', 'Number of sellers to sequentially squeeze', '3')
412
+ .option('--parallel <n>', 'Number of sellers to negotiate with simultaneously')
413
+ .option('--auto', 'Run CLI-driven LLM auto-negotiation')
313
414
  .action(async (address, opts) => {
314
- const cfg = requireConfig();
415
+ let cfg = requireConfig();
315
416
  let targetAddress = address;
316
417
  let budget = opts.budget;
317
418
  let constraints = {};
@@ -321,14 +422,16 @@ program
321
422
  banner();
322
423
  console.log(c.bold("💬 Clinch Onboarding Wizard — Tell me what you're looking for.\n"));
323
424
 
425
+ cfg = await ensureAIEngine(cfg);
426
+
324
427
  const naturalIntent = await prompt("👉 Describe what you want to negotiate\n" +
325
428
  c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\n💬: "));
326
429
 
327
430
  if (!naturalIntent) process.exit(1);
328
431
 
329
- const parsed = await parseIntentWithLLM(naturalIntent, cfg.modelPath);
432
+ const parsed = await parseIntentWithLLM(naturalIntent, cfg);
330
433
  if (!parsed) {
331
- console.error(c.red("✗ Error parsing constraints. Please try manual entry."));
434
+ // We failed to parse correctly (or the user typed hi), gracefully exit rather than crashing
332
435
  process.exit(1);
333
436
  }
334
437
 
@@ -343,7 +446,7 @@ program
343
446
  constraints = parsed;
344
447
  budget = parsed.max_budget;
345
448
 
346
- console.log(c.dim(`\nQuerying registry for category "${parsed.category}"...`));
449
+ console.log(c.dim(`\n[Network] Querying registry for category "${parsed.category}"...`));
347
450
  const coreDiscovery = getClinchCore(cfg);
348
451
  await coreDiscovery.initialize(cfg.token);
349
452
  const results = await coreDiscovery.search(parsed.category);
@@ -360,11 +463,7 @@ program
360
463
  targetAddress = `ANP/C.${sellers[parseInt(selection) - 1].agent_id}`;
361
464
  }
362
465
  } else {
363
- constraints = {
364
- intent: 'purchase',
365
- item: opts.item || 'Item',
366
- max_budget: parseFloat(budget || 100)
367
- };
466
+ constraints = { intent: 'purchase', item: opts.item || 'Item', max_budget: parseFloat(budget || 100) };
368
467
  if (opts.category) constraints.category = opts.category;
369
468
  }
370
469
 
@@ -374,9 +473,65 @@ program
374
473
  runAuto = autoInput.toLowerCase() !== 'n';
375
474
  }
376
475
 
476
+ if (runAuto) {
477
+ cfg = await ensureAIEngine(cfg);
478
+ }
479
+
377
480
  const core = getClinchCore(cfg);
378
481
 
379
- // ── CASCADING ITERATIVE CASCADE TRIGGER (Sequential Squeeze vs. Parallel Concurrency) ──
482
+ // ── CLI AUTO-NEGOTIATION HOOK ──
483
+ if (runAuto) {
484
+ console.log(c.yellow(`\n🤖 Auto-mode initialized. Routing inference through: ${c.bold(cfg.engine)}`));
485
+
486
+ core.on('callback_received', async ({ sessionId, payload }) => {
487
+ const session = core.getSession(sessionId);
488
+ if (!session) return;
489
+ session.currentTurn++;
490
+
491
+ const incomingMessage = payload.message || JSON.stringify(payload);
492
+
493
+ // Extract basic price to check constraints
494
+ const priceMatch = incomingMessage.match(/price\s*:\s*\$?(\d+(?:\.\d{2})?)/i);
495
+ if (priceMatch) session.lastKnownPrice = parseFloat(priceMatch[1]);
496
+
497
+ if (session.lastKnownPrice > 0 && session.lastKnownPrice <= session.constraints.max_budget) {
498
+ console.log(c.green(`\n🎉 [Agent Q] Seller met budget conditions! Securing deal.`));
499
+ await core.sendCounter(sessionId, session.lastKnownPrice, "I accept this offer.");
500
+ return;
501
+ }
502
+
503
+ if (session.currentTurn > 6) {
504
+ console.log(c.red(`\n🛑 [Agent Q] Max turns reached. Exiting.`));
505
+ await core.exitSession(sessionId);
506
+ return;
507
+ }
508
+
509
+ const promptStr = core.buildAgentPrompt(sessionId, incomingMessage);
510
+ const aiResponse = await promptAI(promptStr, incomingMessage, cfg);
511
+
512
+ let price = null;
513
+ let msg = "Counter offer";
514
+
515
+ try {
516
+ const clean = aiResponse.replace(/```json|```/g, "").trim();
517
+ const parsed = JSON.parse(clean);
518
+ if (parsed.price) price = parsed.price;
519
+ if (parsed.message) msg = parsed.message;
520
+ } catch(e) {
521
+ const fallback = aiResponse.match(/"price"\s*:\s*(\d+(?:\.\d{2})?)/i);
522
+ if (fallback) price = parseFloat(fallback[1]);
523
+ }
524
+
525
+ if (price) {
526
+ await core.sendCounter(sessionId, Math.min(price, session.constraints.max_budget), msg);
527
+ } else {
528
+ console.log(c.yellow(`[Agent Q] Failed to parse price constraint. Sending safe fallback.`));
529
+ await core.sendCounter(sessionId, session.lastKnownPrice * 0.9, "Can you do slightly better?");
530
+ }
531
+ });
532
+ }
533
+
534
+ // ── CASCADING ITERATIVE CASCADE TRIGGER ──
380
535
  if (!targetAddress && opts.category) {
381
536
  let maxSellers = 3;
382
537
  let strategy = 'sequential';
@@ -391,12 +546,7 @@ program
391
546
  console.log(c.yellow(`🤖 Squeeze Mode: Sequentially bargaining across top ${maxSellers} sellers for "${opts.category}"...\n`));
392
547
  }
393
548
 
394
- if (runAuto) {
395
- await core.sandbox({ modelPath: cfg.modelPath });
396
- } else {
397
- await core.initialize(cfg.token);
398
- }
399
-
549
+ await core.initialize(cfg.token);
400
550
  const bestDeal = await core.negotiateCascade(opts.category, constraints, maxSellers, strategy);
401
551
 
402
552
  if (bestDeal) {
@@ -415,19 +565,20 @@ program
415
565
  process.exit(1);
416
566
  }
417
567
 
418
- if (runAuto) {
419
- console.log(c.yellow('🤖 Auto-mode: Local LLM sandbox active.\n'));
420
- await core.sandbox({ modelPath: cfg.modelPath });
421
- } else {
422
- await core.initialize(cfg.token);
423
- }
568
+ await core.initialize(cfg.token);
424
569
 
425
570
  core.on('session_started', ({ sessionId }) => {
426
571
  console.log(c.green(`\n✓ Session started: ${c.bold(sessionId)}`));
427
572
  saveSessionState(sessionId, core);
428
573
  });
429
574
 
430
- core.on('callback_received', ({ sessionId }) => saveSessionState(sessionId, core));
575
+ if (!runAuto) {
576
+ core.on('callback_received', ({ sessionId, payload }) => {
577
+ saveSessionState(sessionId, core);
578
+ console.log(c.cyan(`\n💬 Seller says:`), payload);
579
+ console.log(c.dim(`\nType a price to counter, or "exit" / "accept":`));
580
+ });
581
+ }
431
582
 
432
583
  core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
433
584
  saveSessionState(sessionId, core);
@@ -447,7 +598,7 @@ program
447
598
  const sessionId = await core.negotiate(targetAddress, constraints);
448
599
 
449
600
  if (!runAuto) {
450
- console.log(c.bold('\nManual mode — type a price to counter, or "exit" / "accept":\n'));
601
+ console.log(c.bold('\nManual mode — await seller response, then type a price to counter, or "exit" / "accept".\n'));
451
602
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
452
603
  rl.on('line', async (cmd) => {
453
604
  if (cmd === 'exit') {
@@ -455,7 +606,7 @@ program
455
606
  saveSessionState(sessionId, core);
456
607
  process.exit(0);
457
608
  }
458
- else if (cmd === 'accept') { console.log(c.green(`Accepting...`)); rl.close(); }
609
+ else if (cmd === 'accept') { console.log(c.green(`Accepting...`)); }
459
610
  else {
460
611
  const price = parseFloat(cmd);
461
612
  if (!isNaN(price)) {
@@ -493,7 +644,7 @@ program
493
644
  .argument('<sessionId>', 'The session ID to resume')
494
645
  .option('--auto', 'Resume with auto-negotiation')
495
646
  .action(async (sessionId, opts) => {
496
- const cfg = requireConfig();
647
+ let cfg = requireConfig();
497
648
  const sessions = loadSessions();
498
649
 
499
650
  if (!sessions[sessionId]) {
@@ -502,13 +653,12 @@ program
502
653
  }
503
654
 
504
655
  console.log(c.yellow(`\nRehydrating Session ${c.bold(sessionId)}...\n`));
505
- const core = getClinchCore(cfg);
506
-
507
656
  if (opts.auto) {
508
- await core.sandbox({ modelPath: cfg.modelPath });
509
- } else {
510
- await core.initialize(cfg.token);
511
- }
657
+ cfg = await ensureAIEngine(cfg);
658
+ }
659
+
660
+ const core = getClinchCore(cfg);
661
+ await core.initialize(cfg.token);
512
662
 
513
663
  core.importSessionState(sessions[sessionId].state);
514
664
 
@@ -532,6 +682,7 @@ program
532
682
  .option('--set', 'Interactively save a new API key credential')
533
683
  .option('--list', 'List domains with registered local credentials')
534
684
  .option('--remove <domain>', 'Delete a credential from your local vault')
685
+ .option('--show', 'Display the raw API keys when listing')
535
686
  .action(async (opts) => {
536
687
  const cfg = requireConfig();
537
688
  const secrets = loadSecrets();
@@ -556,9 +707,13 @@ program
556
707
  }
557
708
  console.log(c.bold('\n🔑 Registered Blind Key Credentials:\n'));
558
709
  entries.forEach(([domain, s]) => {
559
- console.log(` - ${c.cyan(domain)} (${c.dim(s.name || 'unnamed')})`);
710
+ if (opts.show) {
711
+ console.log(` - ${c.cyan(domain)} (${c.dim(s.name || 'unnamed')}) -> ${c.yellow(s.key)}`);
712
+ } else {
713
+ console.log(` - ${c.cyan(domain)} (${c.dim(s.name || 'unnamed')}) -> ${c.dim('••••••••••••')}`);
714
+ }
560
715
  });
561
- console.log('');
716
+ console.log(c.dim(opts.show ? '' : '\n(Run with --show to view raw keys)'));
562
717
  process.exit(0);
563
718
  }
564
719
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
  "name": "agent-clinch",
9
- "version": "0.7.5",
9
+ "version": "0.7.7",
10
10
  "description": "Clinch Protocol CLI — agent negotiation from your terminal",
11
11
  "main": "cli.js",
12
12
  "bin": {