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.
- package/README.md +30 -36
- package/cli.js +124 -50
- 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
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
126
|
-
|
|
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
|
|
136
|
-
* `sessions.json`: Local transcripts
|
|
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
|
-
// ββ
|
|
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
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
69
|
+
|
|
62
70
|
return core;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
// ββ Prompt
|
|
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
|
-
|
|
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]', '
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
276
|
-
targetAddress = await prompt("π
|
|
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
|
|
279
|
-
sellers.forEach((s, idx) => console.log(` ${idx + 1}. ${c.cyan(s.agent_id)}
|
|
280
|
-
const selection = await prompt(`\nπ Select a seller
|
|
281
|
-
targetAddress = `ANP/
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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 === '
|
|
310
|
-
|
|
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') {
|
|
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))
|
|
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.
|
|
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.
|
|
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.
|
|
22
|
+
"clinch-core": "0.4.0"
|
|
23
23
|
}
|
|
24
24
|
}
|