agent-clinch 0.7.5 → 0.7.6
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/cli.js +225 -89
- 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;
|
|
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
|
-
//
|
|
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,27 +144,120 @@ function prompt(question) {
|
|
|
145
144
|
});
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
// ──
|
|
149
|
-
async function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
const res = await fetch('http://127.0.0.1:11434/api/chat', {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
model: cfg.ollamaModel || 'llama3',
|
|
213
|
+
messages: [
|
|
214
|
+
{ role: 'system', content: systemPrompt },
|
|
215
|
+
{ role: 'user', content: userText }
|
|
216
|
+
],
|
|
217
|
+
stream: false
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) throw new Error(`Ollama returned status ${res.status}`);
|
|
221
|
+
const data = await res.json();
|
|
222
|
+
return data.message.content;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(c.red(`\n[!] Ollama request failed: ${e.message}`));
|
|
225
|
+
console.error(c.dim(`Please ensure Ollama is running (http://127.0.0.1:11434) and the model is pulled.`));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// GGUF Execution
|
|
230
|
+
const resolvedPath = path.resolve(cfg.modelPath || path.join(CONFIG_DIR, 'model.gguf'));
|
|
231
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
232
|
+
console.log(c.yellow(`\nModel not found at ${resolvedPath}`));
|
|
233
|
+
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);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let nodeLlama;
|
|
237
|
+
try { nodeLlama = await import('node-llama-cpp'); }
|
|
238
|
+
catch (e) {
|
|
239
|
+
console.error(c.red("\nError: 'node-llama-cpp' is required for GGUF execution."));
|
|
240
|
+
console.error("Run: npm install -g node-llama-cpp");
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
164
243
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
244
|
+
const llama = await nodeLlama.getLlama();
|
|
245
|
+
const model = await llama.loadModel({ modelPath: resolvedPath });
|
|
246
|
+
const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
|
|
247
|
+
const session = new nodeLlama.LlamaChatSession({
|
|
248
|
+
contextSequence: context.getSequence(),
|
|
249
|
+
systemPrompt: systemPrompt,
|
|
250
|
+
chatWrapper: new nodeLlama.ChatMLChatWrapper()
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
let responseText = "";
|
|
254
|
+
await session.prompt(userText, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
|
|
255
|
+
return responseText;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
168
258
|
|
|
259
|
+
async function parseIntentWithLLM(userInput, cfg) {
|
|
260
|
+
console.log(c.dim(`\n[Agent Q] Booting local parser (${cfg.engine}) to analyze your request...`));
|
|
169
261
|
const systemPrompt = `You are a structured data extractor. Convert the user's conversational intent into a strict JSON schema.
|
|
170
262
|
Your response MUST be ONLY valid JSON matching this schema exactly. Do not output conversational text.
|
|
171
263
|
|
|
@@ -174,21 +266,12 @@ JSON Schema:
|
|
|
174
266
|
"intent": "purchase",
|
|
175
267
|
"category": "string (e.g. domain_name, electronics, kitchen_appliance)",
|
|
176
268
|
"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"]
|
|
269
|
+
"max_budget": number (extract budget numeric value)
|
|
179
270
|
}`;
|
|
180
271
|
|
|
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; } });
|
|
189
|
-
|
|
190
272
|
try {
|
|
191
|
-
const
|
|
273
|
+
const rawRes = await promptAI(systemPrompt, userInput, cfg);
|
|
274
|
+
const cleanJson = rawRes.replace(/```json|```/g, "").trim();
|
|
192
275
|
return JSON.parse(cleanJson);
|
|
193
276
|
} catch (e) {
|
|
194
277
|
console.error(c.red("Failed to parse intent. Falling back to manual entry."));
|
|
@@ -226,34 +309,29 @@ program
|
|
|
226
309
|
.command('init')
|
|
227
310
|
.description('Initialize your Clinch buyer agent')
|
|
228
311
|
.option('--registry <url>', 'Custom registry URL')
|
|
229
|
-
.option('--model <path>', 'Path to custom GGUF model')
|
|
230
312
|
.action(async (opts) => {
|
|
231
313
|
banner();
|
|
232
314
|
console.log(c.bold('Setting up your Clinch agent...\n'));
|
|
233
315
|
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
const overwrite = await prompt('Config already exists. Overwrite? (y/N): ');
|
|
316
|
+
let config = loadConfig() || {};
|
|
317
|
+
if (config.pubKey) {
|
|
318
|
+
const overwrite = await prompt('Config already exists. Overwrite network identity? (y/N): ');
|
|
237
319
|
if (overwrite.toLowerCase() !== 'y') { console.log('Aborted.'); process.exit(0); }
|
|
238
320
|
}
|
|
239
321
|
|
|
240
|
-
|
|
241
|
-
|
|
322
|
+
config.registryUrl = opts.registry || 'https://everydaytok-agentq-core-logics.hf.space';
|
|
323
|
+
config.modelPath = path.join(CONFIG_DIR, 'model.gguf');
|
|
324
|
+
config = await ensureAIEngine(config);
|
|
242
325
|
|
|
243
|
-
console.log(c.yellow('
|
|
326
|
+
console.log(c.yellow('\nConnecting to registry and completing PoW handshake...'));
|
|
244
327
|
|
|
245
|
-
const core = getClinchCore(
|
|
328
|
+
const core = getClinchCore(config);
|
|
246
329
|
await core.initialize();
|
|
247
330
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
token: core.jwtToken,
|
|
253
|
-
mode: 'ANP/A',
|
|
254
|
-
localOnly: false,
|
|
255
|
-
createdAt: new Date().toISOString()
|
|
256
|
-
};
|
|
331
|
+
config.pubKey = core.identityPubKey;
|
|
332
|
+
config.token = core.jwtToken;
|
|
333
|
+
config.mode = 'ANP/A';
|
|
334
|
+
config.createdAt = new Date().toISOString();
|
|
257
335
|
|
|
258
336
|
saveConfig(config);
|
|
259
337
|
core.disconnect();
|
|
@@ -306,12 +384,12 @@ program
|
|
|
306
384
|
.argument('[address]', 'ANP address — format: MODE.domain.anp (e.g. ANP/C.amazon.anp)')
|
|
307
385
|
.option('--budget <n>', 'Max budget (USD)')
|
|
308
386
|
.option('--item <name>', 'Specific item to negotiate')
|
|
309
|
-
.option('--category <name>', 'Market category (Triggers cascade negotiation across matching sellers
|
|
310
|
-
.option('--squeeze <n>', 'Number of sellers to sequentially squeeze
|
|
311
|
-
.option('--parallel <n>', 'Number of sellers to negotiate with simultaneously
|
|
312
|
-
.option('--auto', 'Run
|
|
387
|
+
.option('--category <name>', 'Market category (Triggers cascade negotiation across matching sellers)')
|
|
388
|
+
.option('--squeeze <n>', 'Number of sellers to sequentially squeeze', '3')
|
|
389
|
+
.option('--parallel <n>', 'Number of sellers to negotiate with simultaneously')
|
|
390
|
+
.option('--auto', 'Run CLI-driven LLM auto-negotiation')
|
|
313
391
|
.action(async (address, opts) => {
|
|
314
|
-
|
|
392
|
+
let cfg = requireConfig();
|
|
315
393
|
let targetAddress = address;
|
|
316
394
|
let budget = opts.budget;
|
|
317
395
|
let constraints = {};
|
|
@@ -321,12 +399,14 @@ program
|
|
|
321
399
|
banner();
|
|
322
400
|
console.log(c.bold("💬 Clinch Onboarding Wizard — Tell me what you're looking for.\n"));
|
|
323
401
|
|
|
402
|
+
cfg = await ensureAIEngine(cfg);
|
|
403
|
+
|
|
324
404
|
const naturalIntent = await prompt("👉 Describe what you want to negotiate\n" +
|
|
325
405
|
c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\n💬: "));
|
|
326
406
|
|
|
327
407
|
if (!naturalIntent) process.exit(1);
|
|
328
408
|
|
|
329
|
-
const parsed = await parseIntentWithLLM(naturalIntent, cfg
|
|
409
|
+
const parsed = await parseIntentWithLLM(naturalIntent, cfg);
|
|
330
410
|
if (!parsed) {
|
|
331
411
|
console.error(c.red("✗ Error parsing constraints. Please try manual entry."));
|
|
332
412
|
process.exit(1);
|
|
@@ -360,11 +440,7 @@ program
|
|
|
360
440
|
targetAddress = `ANP/C.${sellers[parseInt(selection) - 1].agent_id}`;
|
|
361
441
|
}
|
|
362
442
|
} else {
|
|
363
|
-
constraints = {
|
|
364
|
-
intent: 'purchase',
|
|
365
|
-
item: opts.item || 'Item',
|
|
366
|
-
max_budget: parseFloat(budget || 100)
|
|
367
|
-
};
|
|
443
|
+
constraints = { intent: 'purchase', item: opts.item || 'Item', max_budget: parseFloat(budget || 100) };
|
|
368
444
|
if (opts.category) constraints.category = opts.category;
|
|
369
445
|
}
|
|
370
446
|
|
|
@@ -374,9 +450,68 @@ program
|
|
|
374
450
|
runAuto = autoInput.toLowerCase() !== 'n';
|
|
375
451
|
}
|
|
376
452
|
|
|
453
|
+
if (runAuto) {
|
|
454
|
+
cfg = await ensureAIEngine(cfg);
|
|
455
|
+
}
|
|
456
|
+
|
|
377
457
|
const core = getClinchCore(cfg);
|
|
378
458
|
|
|
379
|
-
// ──
|
|
459
|
+
// ── CLI AUTO-NEGOTIATION HOOK ──
|
|
460
|
+
if (runAuto) {
|
|
461
|
+
console.log(c.yellow(`\n🤖 Auto-mode initialized. Routing inference through: ${c.bold(cfg.engine)}`));
|
|
462
|
+
|
|
463
|
+
core.on('callback_received', async ({ sessionId, payload }) => {
|
|
464
|
+
const session = core.getSession(sessionId);
|
|
465
|
+
if (!session) return;
|
|
466
|
+
session.currentTurn++;
|
|
467
|
+
|
|
468
|
+
const incomingMessage = payload.message || JSON.stringify(payload);
|
|
469
|
+
|
|
470
|
+
// Extract basic price to check constraints
|
|
471
|
+
const priceMatch = incomingMessage.match(/price\s*:\s*\$?(\d+(?:\.\d{2})?)/i);
|
|
472
|
+
if (priceMatch) session.lastKnownPrice = parseFloat(priceMatch[1]);
|
|
473
|
+
|
|
474
|
+
if (session.lastKnownPrice > 0 && session.lastKnownPrice <= session.constraints.max_budget) {
|
|
475
|
+
console.log(c.green(`\n🎉 [Agent Q] Seller met budget conditions! Securing deal.`));
|
|
476
|
+
await core.sendCounter(sessionId, session.lastKnownPrice, "I accept this offer.");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (session.currentTurn > 6) {
|
|
481
|
+
console.log(c.red(`\n🛑 [Agent Q] Max turns reached. Exiting.`));
|
|
482
|
+
await core.exitSession(sessionId);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Ask the core to build the context prompt, but evaluate it HERE in the CLI
|
|
487
|
+
const promptStr = core.buildAgentPrompt(sessionId, incomingMessage);
|
|
488
|
+
console.log(c.dim(`\n[Agent Q] Evaluating turn ${session.currentTurn} with ${cfg.engine}...`));
|
|
489
|
+
|
|
490
|
+
const aiResponse = await promptAI(promptStr, incomingMessage, cfg);
|
|
491
|
+
|
|
492
|
+
let price = null;
|
|
493
|
+
let msg = "Counter offer";
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const clean = aiResponse.replace(/```json|```/g, "").trim();
|
|
497
|
+
const parsed = JSON.parse(clean);
|
|
498
|
+
if (parsed.price) price = parsed.price;
|
|
499
|
+
if (parsed.message) msg = parsed.message;
|
|
500
|
+
} catch(e) {
|
|
501
|
+
const fallback = aiResponse.match(/"price"\s*:\s*(\d+(?:\.\d{2})?)/i);
|
|
502
|
+
if (fallback) price = parseFloat(fallback[1]);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (price) {
|
|
506
|
+
await core.sendCounter(sessionId, Math.min(price, session.constraints.max_budget), msg);
|
|
507
|
+
} else {
|
|
508
|
+
console.log(c.yellow(`[Agent Q] Failed to parse price constraint. Sending safe fallback.`));
|
|
509
|
+
await core.sendCounter(sessionId, session.lastKnownPrice * 0.9, "Can you do slightly better?");
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ── CASCADING ITERATIVE CASCADE TRIGGER ──
|
|
380
515
|
if (!targetAddress && opts.category) {
|
|
381
516
|
let maxSellers = 3;
|
|
382
517
|
let strategy = 'sequential';
|
|
@@ -391,12 +526,7 @@ program
|
|
|
391
526
|
console.log(c.yellow(`🤖 Squeeze Mode: Sequentially bargaining across top ${maxSellers} sellers for "${opts.category}"...\n`));
|
|
392
527
|
}
|
|
393
528
|
|
|
394
|
-
|
|
395
|
-
await core.sandbox({ modelPath: cfg.modelPath });
|
|
396
|
-
} else {
|
|
397
|
-
await core.initialize(cfg.token);
|
|
398
|
-
}
|
|
399
|
-
|
|
529
|
+
await core.initialize(cfg.token);
|
|
400
530
|
const bestDeal = await core.negotiateCascade(opts.category, constraints, maxSellers, strategy);
|
|
401
531
|
|
|
402
532
|
if (bestDeal) {
|
|
@@ -415,19 +545,20 @@ program
|
|
|
415
545
|
process.exit(1);
|
|
416
546
|
}
|
|
417
547
|
|
|
418
|
-
|
|
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
|
-
}
|
|
548
|
+
await core.initialize(cfg.token);
|
|
424
549
|
|
|
425
550
|
core.on('session_started', ({ sessionId }) => {
|
|
426
551
|
console.log(c.green(`\n✓ Session started: ${c.bold(sessionId)}`));
|
|
427
552
|
saveSessionState(sessionId, core);
|
|
428
553
|
});
|
|
429
554
|
|
|
430
|
-
|
|
555
|
+
if (!runAuto) {
|
|
556
|
+
core.on('callback_received', ({ sessionId, payload }) => {
|
|
557
|
+
saveSessionState(sessionId, core);
|
|
558
|
+
console.log(c.cyan(`\n💬 Seller says:`), payload);
|
|
559
|
+
console.log(c.dim(`\nType a price to counter, or "exit" / "accept":`));
|
|
560
|
+
});
|
|
561
|
+
}
|
|
431
562
|
|
|
432
563
|
core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
|
|
433
564
|
saveSessionState(sessionId, core);
|
|
@@ -447,7 +578,7 @@ program
|
|
|
447
578
|
const sessionId = await core.negotiate(targetAddress, constraints);
|
|
448
579
|
|
|
449
580
|
if (!runAuto) {
|
|
450
|
-
console.log(c.bold('\nManual mode — type a price to counter, or "exit" / "accept"
|
|
581
|
+
console.log(c.bold('\nManual mode — await seller response, then type a price to counter, or "exit" / "accept".\n'));
|
|
451
582
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
452
583
|
rl.on('line', async (cmd) => {
|
|
453
584
|
if (cmd === 'exit') {
|
|
@@ -455,7 +586,7 @@ program
|
|
|
455
586
|
saveSessionState(sessionId, core);
|
|
456
587
|
process.exit(0);
|
|
457
588
|
}
|
|
458
|
-
else if (cmd === 'accept') { console.log(c.green(`Accepting...`));
|
|
589
|
+
else if (cmd === 'accept') { console.log(c.green(`Accepting...`)); }
|
|
459
590
|
else {
|
|
460
591
|
const price = parseFloat(cmd);
|
|
461
592
|
if (!isNaN(price)) {
|
|
@@ -493,7 +624,7 @@ program
|
|
|
493
624
|
.argument('<sessionId>', 'The session ID to resume')
|
|
494
625
|
.option('--auto', 'Resume with auto-negotiation')
|
|
495
626
|
.action(async (sessionId, opts) => {
|
|
496
|
-
|
|
627
|
+
let cfg = requireConfig();
|
|
497
628
|
const sessions = loadSessions();
|
|
498
629
|
|
|
499
630
|
if (!sessions[sessionId]) {
|
|
@@ -502,13 +633,13 @@ program
|
|
|
502
633
|
}
|
|
503
634
|
|
|
504
635
|
console.log(c.yellow(`\nRehydrating Session ${c.bold(sessionId)}...\n`));
|
|
505
|
-
const core = getClinchCore(cfg);
|
|
506
|
-
|
|
507
636
|
if (opts.auto) {
|
|
508
|
-
await
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
637
|
+
cfg = await ensureAIEngine(cfg);
|
|
638
|
+
// ... hook auto logic here if needed (omitted for brevity, handled heavily in negotiate)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const core = getClinchCore(cfg);
|
|
642
|
+
await core.initialize(cfg.token);
|
|
512
643
|
|
|
513
644
|
core.importSessionState(sessions[sessionId].state);
|
|
514
645
|
|
|
@@ -532,6 +663,7 @@ program
|
|
|
532
663
|
.option('--set', 'Interactively save a new API key credential')
|
|
533
664
|
.option('--list', 'List domains with registered local credentials')
|
|
534
665
|
.option('--remove <domain>', 'Delete a credential from your local vault')
|
|
666
|
+
.option('--show', 'Display the raw API keys when listing')
|
|
535
667
|
.action(async (opts) => {
|
|
536
668
|
const cfg = requireConfig();
|
|
537
669
|
const secrets = loadSecrets();
|
|
@@ -556,9 +688,13 @@ program
|
|
|
556
688
|
}
|
|
557
689
|
console.log(c.bold('\n🔑 Registered Blind Key Credentials:\n'));
|
|
558
690
|
entries.forEach(([domain, s]) => {
|
|
559
|
-
|
|
691
|
+
if (opts.show) {
|
|
692
|
+
console.log(` - ${c.cyan(domain)} (${c.dim(s.name || 'unnamed')}) -> ${c.yellow(s.key)}`);
|
|
693
|
+
} else {
|
|
694
|
+
console.log(` - ${c.cyan(domain)} (${c.dim(s.name || 'unnamed')}) -> ${c.dim('••••••••••••')}`);
|
|
695
|
+
}
|
|
560
696
|
});
|
|
561
|
-
console.log('');
|
|
697
|
+
console.log(c.dim(opts.show ? '' : '\n(Run with --show to view raw keys)'));
|
|
562
698
|
process.exit(0);
|
|
563
699
|
}
|
|
564
700
|
|