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.
- package/cli.js +253 -98
- 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,53 +144,160 @@ 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
|
-
|
|
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
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
271
|
+
If they DO specify a purchase intent, output EXACTLY this JSON schema:
|
|
173
272
|
{
|
|
174
273
|
"intent": "purchase",
|
|
175
|
-
"category": "string (e.g.
|
|
176
|
-
"item": "string (the
|
|
177
|
-
"max_budget": number (
|
|
178
|
-
|
|
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
|
|
192
|
-
|
|
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
|
-
|
|
235
|
-
if (
|
|
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
|
-
|
|
241
|
-
|
|
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('
|
|
349
|
+
console.log(c.yellow('\nConnecting to registry and completing PoW handshake...'));
|
|
244
350
|
|
|
245
|
-
const core = getClinchCore(
|
|
351
|
+
const core = getClinchCore(config);
|
|
246
352
|
await core.initialize();
|
|
247
353
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
432
|
+
const parsed = await parseIntentWithLLM(naturalIntent, cfg);
|
|
330
433
|
if (!parsed) {
|
|
331
|
-
|
|
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(`\
|
|
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
|
-
// ──
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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...`));
|
|
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
|
-
|
|
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
|
|
509
|
-
}
|
|
510
|
-
|
|
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
|
-
|
|
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
|
|