agent-clinch 0.1.0 β†’ 0.4.0

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 (3) hide show
  1. package/README.md +30 -36
  2. package/cli.js +124 -50
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  The Clinch CLI is the official reference implementation of a Clinch Protocol buyer agent. It allows you to discover sellers, negotiate deals interactively, or dispatch an autonomous local AI agent ("Agent Q") to haggle on your behalfβ€”right from your terminal.
6
6
 
7
- By keeping the execution edge-first, all cryptographic keys, session transcripts, downloaded models, and deal artifacts are stored strictly on your local machine.
7
+ By keeping the execution edge-first, all cryptographic keys, session transcripts, downloaded models, and deal artifacts are stored strictly on your local machine. With robust state serialization, you can even close your terminal and resume dropped or asynchronous negotiations later.
8
8
 
9
9
  ---
10
10
 
@@ -44,7 +44,7 @@ The easiest way to use Clinch is to simply type:
44
44
  clinch negotiate
45
45
  ```
46
46
 
47
- Instead of memorizing flags and seller domains, the CLI boots a local LLM to ask what you want in plain English.
47
+ Instead of memorizing flags and routing prefixes, the CLI boots a local LLM to ask what you want in plain English.
48
48
 
49
49
  ```text
50
50
  πŸ’¬ Clinch Onboarding Wizard β€” Tell me what you're looking for.
@@ -63,14 +63,14 @@ Instead of memorizing flags and seller domains, the CLI boots a local LLM to ask
63
63
  - Must Haves: warranty, fast shipping
64
64
  ```
65
65
 
66
- Agent Q parses your natural language into a strict JSON `ConstraintVector`, queries the Clinch Registry to find matching sellers, and lets you select your target. It then seamlessly transitions into the negotiation phase.
66
+ Agent Q parses your natural language into a strict JSON constraint vector, queries the Clinch Registry to find matching sellers, and lets you select your target. It then seamlessly transitions into the negotiation phase.
67
67
 
68
68
  ### 3. Explicit Negotiation (Manual or Auto Mode)
69
- If you already know the seller's address and want to bypass the wizard, you can pass arguments directly:
69
+ If you already know the seller's address and want to bypass the wizard, you can pass arguments directly. **Note: Clinch Protocol requires strict routing prefixes (e.g., `ANP/C.`) on all addresses.**
70
70
 
71
71
  **Manual Mode:** Open an interactive terminal where you manually input counter-offers.
72
72
  ```bash
73
- clinch negotiate ANP/A.amazon.anp --category "electronics" --item "Ninja Blender" --budget 85.00
73
+ clinch negotiate ANP/C.amazon.anp --budget 85.00
74
74
  ```
75
75
  * Type a number (e.g., `45.50`) to send a counter-offer.
76
76
  * Type `accept` to seal the deal.
@@ -78,7 +78,20 @@ clinch negotiate ANP/A.amazon.anp --category "electronics" --item "Ninja Blender
78
78
 
79
79
  **Auto Mode:** Add the `--auto` flag to hand control over to the local LLM Sandbox (Qwen 2.5 1.5B). The agent will evaluate the seller's offers and negotiate autonomously up to your strict max budget.
80
80
  ```bash
81
- clinch negotiate ANP/A.cloudflare.anp --category "domain" --item "my-startup.io" --budget 50.00 --auto
81
+ clinch negotiate ANP/C.cloudflare.anp --budget 50.00 --auto
82
+ ```
83
+
84
+ ### 4. Resuming Asynchronous Sessions
85
+ If you exit a negotiation before it concludes, or a seller places your offer in an asynchronous callback queue, the CLI automatically saves your exact session state and cryptographic session keys to disk.
86
+
87
+ List your saved sessions:
88
+ ```bash
89
+ clinch sessions
90
+ ```
91
+
92
+ Resume a specific session and listen for webhooks/callbacks:
93
+ ```bash
94
+ clinch resume sess_a1b2c3d4 --auto
82
95
  ```
83
96
 
84
97
  ---
@@ -87,43 +100,25 @@ clinch negotiate ANP/A.cloudflare.anp --category "domain" --item "my-startup.io"
87
100
 
88
101
  ### `clinch init [options]`
89
102
  Initializes the agent and performs network registration.
90
- * `--registry <url>`: Override the default dynamic router.
103
+ * `--registry <url>`: Override the default dynamic router (Useful for local testing).
91
104
  * `--model <path>`: Specify a custom path for the `.gguf` model file.
92
105
 
93
106
  ### `clinch query <category> [options]`
94
107
  Queries the registry for seller nodes.
95
- * `--mode <mode>`: Filter by agent protocol mode (e.g., `ANP/A`, `ANP/C`).
96
- * `--json`: Output raw JSON for scripting.
108
+ * `--mode <mode>`: Filter by agent protocol mode (e.g., `ANP/C`).
97
109
 
98
110
  ### `clinch negotiate [address] [options]`
99
- Opens a session with a target seller address. If `address` or `--budget` are omitted, launches the conversational wizard.
111
+ Opens a session with a target seller address. If `address` or `--budget` are omitted, launches the conversational wizard.
112
+ * `[address]`: The target seller address MUST include the protocol prefix (e.g., `ANP/C.seller_domain`).
100
113
  * `--budget <n>`: Your absolute maximum budget in USD.
101
- * `--item <name>`: The specific item being negotiated.
102
- * `--category <name>`: The market category.
103
- * `--intent <intent>`: e.g., `purchase`, `lease`.
104
114
  * `--auto`: Delegates turn-based negotiation to the local AI sandbox.
105
115
 
106
- ### `clinch sessions [options]`
107
- Lists historical and active negotiation sessions.
108
- * `--last <n>`: Number of recent sessions to show (Default: 20).
109
- * `--outcome <s>`: Filter by `ACTIVE`, `CONVERGED`, or `STALEMATE`.
110
-
111
- ### `clinch deals [options]`
112
- Lists successfully converged, cryptographically signed deal artifacts.
113
- * `--verify <id>`: Fetches the deal artifact from the network and validates the cryptographic chain, Registry signature, and Seller signature.
114
-
115
- ### `clinch callbacks [options]`
116
- Connects to the WebSocket queue and listens for incoming, asynchronous counter-offers from sellers you previously `exit`ed on.
117
- * `--pending`: Only show unread callbacks.
118
-
119
- ### `clinch config [options]`
120
- View or update your agent's local configuration variables.
121
- * `--mode <mode>`: Update default handshake mode.
122
- * `--registry <url>`: Point CLI to a new registry network.
123
- * `--local-only <bool>`: Opt-out of anonymous session reporting.
116
+ ### `clinch sessions`
117
+ Lists all historical and active negotiation sessions stored on your machine, displaying the session ID, target seller, current turn, and status.
124
118
 
125
- ### `clinch status`
126
- Pings the active Registry network to check health and latency.
119
+ ### `clinch resume <sessionId> [options]`
120
+ Rehydrates a specific session's state and cryptographic keys into memory to continue negotiating or wait for remote seller callbacks.
121
+ * `--auto`: Immediately hands the resumed session back to the local AI sandbox to evaluate incoming callbacks.
127
122
 
128
123
  ---
129
124
 
@@ -132,6 +127,5 @@ Pings the active Registry network to check health and latency.
132
127
  The Clinch CLI operates on a strict zero-trust, edge-first model. Your data never leaves your machine unless explicitly sent as a constraint during a session.
133
128
 
134
129
  All state is stored locally in your home directory (`~/.clinch/`):
135
- * `config.json`: Your identity keys, JWT token, and preferences. **Do not share this file.**
136
- * `sessions.json`: Local transcripts of your negotiations.
137
- * `deals.json`: Your cryptographic deal artifacts.
130
+ * `config.json`: Your identity keys and JWT token. **Do not share this file.**
131
+ * `sessions.json`: Local transcripts, current turns, constraint vectors, and the ephemeral Ed25519 session keys necessary to prove your identity during resumed negotiations.
package/cli.js CHANGED
@@ -13,9 +13,9 @@ const readline = require('readline');
13
13
  const CONFIG_DIR = path.join(os.homedir(), '.clinch');
14
14
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
15
15
  const SESSIONS_FILE = path.join(CONFIG_DIR, 'sessions.json');
16
- const DEALS_FILE = path.join(CONFIG_DIR, 'deals.json');
16
+ const DEALS_FILE = path.join(CONFIG_DIR, 'deals.json'); // Restored
17
17
 
18
- // ── Config helpers ────────────────────────────────────────────
18
+ // ── Persistence Helpers ───────────────────────────────────────
19
19
  function loadConfig() {
20
20
  if (!fs.existsSync(CONFIG_FILE)) return null;
21
21
  return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
@@ -27,12 +27,22 @@ function saveConfig(config) {
27
27
  }
28
28
 
29
29
  function loadSessions() {
30
- if (!fs.existsSync(SESSIONS_FILE)) return [];
30
+ if (!fs.existsSync(SESSIONS_FILE)) return {};
31
31
  return JSON.parse(fs.readFileSync(SESSIONS_FILE, 'utf8'));
32
32
  }
33
33
 
34
- function saveSessions(sessions) {
35
- fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
34
+ function saveSessionState(sessionId, core) {
35
+ try {
36
+ const serialized = core.exportSessionState(sessionId);
37
+ const sessions = loadSessions();
38
+ sessions[sessionId] = {
39
+ updatedAt: new Date().toISOString(),
40
+ state: serialized
41
+ };
42
+ fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
43
+ } catch (e) {
44
+ // Session might be deleted or errored, ignore gracefully in CLI
45
+ }
36
46
  }
37
47
 
38
48
  function requireConfig() {
@@ -46,9 +56,8 @@ function requireConfig() {
46
56
 
47
57
  function getClinchCore(cfg) {
48
58
  let ClinchCoreModule;
49
- try {
50
- ClinchCoreModule = require('clinch-core');
51
- } catch {
59
+ try { ClinchCoreModule = require('clinch-core'); }
60
+ catch {
52
61
  console.error('clinch-core not found. Ensure it is linked or installed.');
53
62
  process.exit(1);
54
63
  }
@@ -57,12 +66,11 @@ function getClinchCore(cfg) {
57
66
 
58
67
  core.on('log', msg => console.log(msg));
59
68
  core.on('error', err => console.error('Error:', err.message));
60
- core.on('status_changed', s => process.stdout.write(`\r[${s}] \r`));
61
-
69
+
62
70
  return core;
63
71
  }
64
72
 
65
- // ── Prompt helper ─────────────────────────────────────────────
73
+ // ── Prompt Helper ─────────────────────────────────────────────
66
74
  function prompt(question) {
67
75
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
68
76
  return new Promise(resolve => {
@@ -73,11 +81,9 @@ function prompt(question) {
73
81
  // ── Conversational AI Intent Parser (CLI Layer) ───────────────
74
82
  async function parseIntentWithLLM(userInput, modelPath) {
75
83
  console.log(c.dim("\n[Agent Q] Booting local parser model to analyze your request..."));
76
-
77
84
  let nodeLlama;
78
- try {
79
- nodeLlama = await import('node-llama-cpp');
80
- } catch (e) {
85
+ try { nodeLlama = await import('node-llama-cpp'); }
86
+ catch (e) {
81
87
  console.error(c.red("\nError: node-llama-cpp is required for conversational parsing."));
82
88
  console.error("Please run: npm install -g node-llama-cpp\n");
83
89
  process.exit(1);
@@ -86,16 +92,12 @@ async function parseIntentWithLLM(userInput, modelPath) {
86
92
  const resolvedPath = path.resolve(modelPath);
87
93
  if (!fs.existsSync(resolvedPath)) {
88
94
  console.error(c.red(`\nError: Model not found at ${resolvedPath}`));
89
- console.error("Please run 'clinch init' again or specify the model path.\n");
90
95
  process.exit(1);
91
96
  }
92
97
 
93
98
  const llama = await nodeLlama.getLlama();
94
99
  const model = await llama.loadModel({ modelPath: resolvedPath });
95
- const context = await model.createContext({
96
- contextSize: 2048,
97
- threads: Math.max(1, os.cpus().length - 1)
98
- });
100
+ const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
99
101
 
100
102
  const systemPrompt = `You are a structured data extractor. Convert the user's conversational intent into a strict JSON schema.
101
103
  Your response MUST be ONLY valid JSON matching this schema exactly. Do not output conversational text.
@@ -116,10 +118,7 @@ JSON Schema:
116
118
  });
117
119
 
118
120
  let responseText = "";
119
- await session.prompt(userInput, {
120
- maxTokens: 1500,
121
- onTextChunk: (chunk) => { responseText += chunk; }
122
- });
121
+ await session.prompt(userInput, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
123
122
 
124
123
  try {
125
124
  const cleanJson = responseText.replace(/```json|```/g, "").trim();
@@ -168,9 +167,7 @@ program
168
167
  const existing = loadConfig();
169
168
  if (existing) {
170
169
  const overwrite = await prompt('Config already exists. Overwrite? (y/N): ');
171
- if (overwrite.toLowerCase() !== 'y') {
172
- console.log('Aborted.'); return;
173
- }
170
+ if (overwrite.toLowerCase() !== 'y') { console.log('Aborted.'); return; }
174
171
  }
175
172
 
176
173
  const registryUrl = opts.registry || 'https://everydaytok-agentq-core-logics.hf.space';
@@ -219,13 +216,15 @@ program
219
216
  sellers.forEach((s, i) => {
220
217
  const tier = s.verification_tier === 'verified' ? c.green('βœ“ Verified') : c.dim('Unverified');
221
218
  console.log(` ${c.bold((i+1) + '.')} ${c.cyan(s.agent_id)} (${tier})`);
219
+ console.log(` ANP address: ${c.yellow('ANP/C.' + s.agent_id)}`);
220
+ console.log(` Modes: ${(s.supported_modes || []).join(', ')}`);
222
221
  });
223
222
  });
224
223
 
225
224
  program
226
225
  .command('negotiate')
227
226
  .description('Start a negotiation with a seller agent')
228
- .argument('[address]', 'Seller address (optional, triggers wizard if omitted)')
227
+ .argument('[address]', 'ANP address β€” format: MODE.domain.anp (e.g. ANP/A.amazon.anp)')
229
228
  .option('--budget <n>', 'Max budget (USD)')
230
229
  .option('--auto', 'Run sandbox auto-negotiation')
231
230
  .action(async (address, opts) => {
@@ -234,18 +233,17 @@ program
234
233
  let budget = opts.budget;
235
234
  let constraints = {};
236
235
 
237
- // ── WIZARD MODE: Leverage CLI-layer Agent Q ──
236
+ // ── WIZARD MODE ──
238
237
  if (!targetAddress || !budget) {
239
238
  banner();
240
239
  console.log(c.bold("πŸ’¬ Clinch Onboarding Wizard β€” Tell me what you're looking for.\n"));
241
240
 
242
241
  const naturalIntent = await prompt("πŸ‘‰ Describe what you want to negotiate\n" +
243
- c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars, must have WHOIS privacy')\n\nπŸ’¬: "));
242
+ c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\nπŸ’¬: "));
244
243
 
245
244
  if (!naturalIntent) process.exit(1);
246
245
 
247
246
  const parsed = await parseIntentWithLLM(naturalIntent, cfg.modelPath);
248
-
249
247
  if (!parsed) {
250
248
  console.error(c.red("βœ— Error parsing constraints. Please try manual entry."));
251
249
  process.exit(1);
@@ -254,8 +252,7 @@ program
254
252
  console.log(c.bold("\nπŸ“Š Extracted Intention Context:"));
255
253
  console.log(` - Category: ${c.cyan(parsed.category)}`);
256
254
  console.log(` - Target Item: ${c.cyan(parsed.item)}`);
257
- console.log(` - Max Budget: ${c.green("$" + parsed.max_budget)}`);
258
- console.log(` - Must Haves: ${c.yellow(parsed.must_haves.join(', ') || 'none')}\n`);
255
+ console.log(` - Max Budget: ${c.green("$" + parsed.max_budget)}\n`);
259
256
 
260
257
  const confirm = await prompt("πŸ‘‰ Is this correct? (Y/n): ");
261
258
  if (confirm.toLowerCase() === 'n') process.exit(0);
@@ -263,8 +260,7 @@ program
263
260
  constraints = parsed;
264
261
  budget = parsed.max_budget;
265
262
 
266
- // ── Seller Discovery ──
267
- console.log(c.dim(`\nQuerying registry to find compatible sellers for "${parsed.category}"...`));
263
+ console.log(c.dim(`\nQuerying registry for category "${parsed.category}"...`));
268
264
  const coreDiscovery = getClinchCore(cfg);
269
265
  await coreDiscovery.initialize(cfg.token);
270
266
  const results = await coreDiscovery.search(parsed.category);
@@ -272,30 +268,35 @@ program
272
268
 
273
269
  const sellers = results.results || [];
274
270
  if (sellers.length === 0) {
275
- console.log(c.yellow(`\nNo certified sellers found on the network for category "${parsed.category}".`));
276
- targetAddress = await prompt("πŸ‘‰ Please enter a seller address manually (e.g. amazon.anp): ");
271
+ console.log(c.yellow(`\nNo sellers found for "${parsed.category}".`));
272
+ targetAddress = await prompt("πŸ‘‰ Enter address manually (e.g. ANP/A.amazon.anp): ");
277
273
  } else {
278
- console.log(c.bold(`\nAvailable certified sellers on the network:`));
279
- sellers.forEach((s, idx) => console.log(` ${idx + 1}. ${c.cyan(s.agent_id)} (${s.display_name})`));
280
- const selection = await prompt(`\nπŸ‘‰ Select a seller to target (1-${sellers.length}): `);
281
- targetAddress = `ANP/A.${sellers[parseInt(selection) - 1].agent_id}`;
274
+ console.log(c.bold(`\nAvailable sellers:`));
275
+ sellers.forEach((s, idx) => console.log(` ${idx + 1}. ${c.cyan(s.agent_id)}`));
276
+ const selection = await prompt(`\nπŸ‘‰ Select a seller (1-${sellers.length}): `);
277
+ targetAddress = `ANP/C.${sellers[parseInt(selection) - 1].agent_id}`;
282
278
  }
283
279
  } else {
284
280
  constraints = { intent: 'purchase', item: 'Item', max_budget: parseFloat(budget) };
285
281
  }
286
282
 
287
- // ── INITIATE CORE PROTOCOL NEGOTIATION ──
288
- console.log(c.cyan(`\nStarting negotiation with ${c.bold(targetAddress)}...`));
289
- const core = getClinchCore(cfg);
283
+ if (!targetAddress.startsWith('ANP/')) {
284
+ console.error(c.red(`\nβœ— Invalid Address: ${targetAddress}`));
285
+ console.error(" Address MUST include the protocol mode prefix.");
286
+ console.error(" Example: ANP/C.amazon.anp\n");
287
+ process.exit(1);
288
+ }
290
289
 
290
+ const core = getClinchCore(cfg);
291
291
  let runAuto = opts.auto;
292
+
292
293
  if (runAuto === undefined) {
293
294
  const autoInput = await prompt("\nπŸ‘‰ Let Agent Q negotiate autonomously? (Y/n): ");
294
295
  runAuto = autoInput.toLowerCase() !== 'n';
295
296
  }
296
297
 
297
298
  if (runAuto) {
298
- console.log(c.yellow('πŸ€– Auto-mode: Local LLM sandbox is taking the wheel...\n'));
299
+ console.log(c.yellow('πŸ€– Auto-mode: Local LLM sandbox active.\n'));
299
300
  await core.sandbox({ modelPath: cfg.modelPath });
300
301
  } else {
301
302
  await core.initialize(cfg.token);
@@ -303,11 +304,24 @@ program
303
304
 
304
305
  core.on('session_started', ({ sessionId }) => {
305
306
  console.log(c.green(`\nβœ“ Session started: ${c.bold(sessionId)}`));
307
+ saveSessionState(sessionId, core); // Initial save
308
+ });
309
+
310
+ core.on('callback_received', ({ sessionId }) => saveSessionState(sessionId, core));
311
+
312
+ core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
313
+ saveSessionState(sessionId, core);
314
+ if (outcome === 'deal') {
315
+ console.log(c.green(c.bold(`\nπŸŽ‰ DEAL SECURED at $${finalPrice}`)));
316
+ process.exit(0);
317
+ }
306
318
  });
307
319
 
308
320
  core.on('status_changed', status => {
309
- if (status === 'CONVERGED') console.log(c.green(`\nβœ“ DEAL REACHED at $${core.lastKnownPrice}`));
310
- if (status === 'STALEMATE') console.log(c.red('\nβœ— No deal reached (stalemate)'));
321
+ if (status === 'STALEMATE') {
322
+ console.log(c.red('\nβœ— Stalemate. Exiting.'));
323
+ process.exit(0);
324
+ }
311
325
  });
312
326
 
313
327
  const sessionId = await core.negotiate(targetAddress, constraints);
@@ -316,19 +330,79 @@ program
316
330
  console.log(c.bold('\nManual mode β€” type a price to counter, or "exit" / "accept":\n'));
317
331
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
318
332
  rl.on('line', async (cmd) => {
319
- if (cmd === 'exit') { await core.exitSession(sessionId); process.exit(0); }
333
+ if (cmd === 'exit') {
334
+ await core.exitSession(sessionId);
335
+ saveSessionState(sessionId, core);
336
+ process.exit(0);
337
+ }
320
338
  else if (cmd === 'accept') { console.log(c.green(`Accepting...`)); rl.close(); }
321
339
  else {
322
340
  const price = parseFloat(cmd);
323
- if (!isNaN(price)) await core.sendCounter(sessionId, price, 'Counter offer');
341
+ if (!isNaN(price)) {
342
+ await core.sendCounter(sessionId, price, 'Counter offer');
343
+ saveSessionState(sessionId, core);
344
+ }
324
345
  }
325
346
  });
326
347
  }
327
348
  });
328
349
 
350
+ program
351
+ .command('sessions')
352
+ .description('List saved negotiation sessions')
353
+ .action(() => {
354
+ const sessions = loadSessions();
355
+ const ids = Object.keys(sessions);
356
+ if (ids.length === 0) return console.log(c.yellow('No saved sessions found.'));
357
+
358
+ console.log(c.bold(`Found ${ids.length} session(s):\n`));
359
+ ids.forEach(id => {
360
+ const s = JSON.parse(sessions[id].state);
361
+ console.log(` ${c.cyan(id)} - Target: ${s.sellerId} | Status: ${c.bold(s.status)} | Turn: ${s.currentTurn}`);
362
+ });
363
+ });
364
+
365
+ program
366
+ .command('resume')
367
+ .description('Resume a dropped or asynchronous negotiation session')
368
+ .argument('<sessionId>', 'The session ID to resume')
369
+ .option('--auto', 'Resume with auto-negotiation')
370
+ .action(async (sessionId, opts) => {
371
+ const cfg = requireConfig();
372
+ const sessions = loadSessions();
373
+
374
+ if (!sessions[sessionId]) {
375
+ console.error(c.red(`Session ${sessionId} not found in local store.`));
376
+ process.exit(1);
377
+ }
378
+
379
+ console.log(c.yellow(`\nRehydrating Session ${c.bold(sessionId)}...\n`));
380
+ const core = getClinchCore(cfg);
381
+
382
+ if (opts.auto) {
383
+ await core.sandbox({ modelPath: cfg.modelPath });
384
+ } else {
385
+ await core.initialize(cfg.token);
386
+ }
387
+
388
+ core.importSessionState(sessions[sessionId].state);
389
+
390
+ core.on('callback_received', ({ id }) => saveSessionState(id, core));
391
+ core.on('session_closed', ({ outcome, finalPrice }) => {
392
+ saveSessionState(sessionId, core);
393
+ if (outcome === 'deal') console.log(c.green(c.bold(`\nπŸŽ‰ DEAL SECURED at $${finalPrice}`)));
394
+ process.exit(0);
395
+ });
396
+
397
+ console.log(c.green('βœ“ State rehydrated. Listening for webhooks/callbacks...\n'));
398
+ if (!opts.auto) {
399
+ console.log(c.dim('Awaiting remote updates. Press Ctrl+C to detach.'));
400
+ }
401
+ });
402
+
329
403
  program
330
404
  .name('clinch')
331
405
  .description('Clinch Protocol β€” Agent Negotiation CLI')
332
- .version('0.1.0');
406
+ .version('0.4.0');
333
407
 
334
408
  program.parse();
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
  "name": "agent-clinch",
9
- "version": "0.1.0",
9
+ "version": "0.4.0",
10
10
  "description": "Clinch Protocol CLI β€” agent negotiation from your terminal",
11
11
  "main": "cli.js",
12
12
  "bin": {
@@ -19,6 +19,6 @@
19
19
  "dependencies": {
20
20
  "commander": "^14.0.3",
21
21
  "node-llama-cpp": "^3.18.1",
22
- "clinch-core": "0.1.0"
22
+ "clinch-core": "0.4.0"
23
23
  }
24
24
  }