agent-clinch 0.1.0 β 0.2.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 +123 -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,8 @@ 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');
|
|
17
16
|
|
|
18
|
-
// ββ
|
|
17
|
+
// ββ Persistence Helpers βββββββββββββββββββββββββββββββββββββββ
|
|
19
18
|
function loadConfig() {
|
|
20
19
|
if (!fs.existsSync(CONFIG_FILE)) return null;
|
|
21
20
|
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
@@ -27,12 +26,22 @@ function saveConfig(config) {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
function loadSessions() {
|
|
30
|
-
if (!fs.existsSync(SESSIONS_FILE)) return
|
|
29
|
+
if (!fs.existsSync(SESSIONS_FILE)) return {};
|
|
31
30
|
return JSON.parse(fs.readFileSync(SESSIONS_FILE, 'utf8'));
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
function
|
|
35
|
-
|
|
33
|
+
function saveSessionState(sessionId, core) {
|
|
34
|
+
try {
|
|
35
|
+
const serialized = core.exportSessionState(sessionId);
|
|
36
|
+
const sessions = loadSessions();
|
|
37
|
+
sessions[sessionId] = {
|
|
38
|
+
updatedAt: new Date().toISOString(),
|
|
39
|
+
state: serialized
|
|
40
|
+
};
|
|
41
|
+
fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// Session might be deleted or errored, ignore gracefully in CLI
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
function requireConfig() {
|
|
@@ -46,9 +55,8 @@ function requireConfig() {
|
|
|
46
55
|
|
|
47
56
|
function getClinchCore(cfg) {
|
|
48
57
|
let ClinchCoreModule;
|
|
49
|
-
try {
|
|
50
|
-
|
|
51
|
-
} catch {
|
|
58
|
+
try { ClinchCoreModule = require('clinch-core'); }
|
|
59
|
+
catch {
|
|
52
60
|
console.error('clinch-core not found. Ensure it is linked or installed.');
|
|
53
61
|
process.exit(1);
|
|
54
62
|
}
|
|
@@ -57,12 +65,11 @@ function getClinchCore(cfg) {
|
|
|
57
65
|
|
|
58
66
|
core.on('log', msg => console.log(msg));
|
|
59
67
|
core.on('error', err => console.error('Error:', err.message));
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
|
|
62
69
|
return core;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
// ββ Prompt
|
|
72
|
+
// ββ Prompt Helper βββββββββββββββββββββββββββββββββββββββββββββ
|
|
66
73
|
function prompt(question) {
|
|
67
74
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
68
75
|
return new Promise(resolve => {
|
|
@@ -73,11 +80,9 @@ function prompt(question) {
|
|
|
73
80
|
// ββ Conversational AI Intent Parser (CLI Layer) βββββββββββββββ
|
|
74
81
|
async function parseIntentWithLLM(userInput, modelPath) {
|
|
75
82
|
console.log(c.dim("\n[Agent Q] Booting local parser model to analyze your request..."));
|
|
76
|
-
|
|
77
83
|
let nodeLlama;
|
|
78
|
-
try {
|
|
79
|
-
|
|
80
|
-
} catch (e) {
|
|
84
|
+
try { nodeLlama = await import('node-llama-cpp'); }
|
|
85
|
+
catch (e) {
|
|
81
86
|
console.error(c.red("\nError: node-llama-cpp is required for conversational parsing."));
|
|
82
87
|
console.error("Please run: npm install -g node-llama-cpp\n");
|
|
83
88
|
process.exit(1);
|
|
@@ -86,16 +91,12 @@ async function parseIntentWithLLM(userInput, modelPath) {
|
|
|
86
91
|
const resolvedPath = path.resolve(modelPath);
|
|
87
92
|
if (!fs.existsSync(resolvedPath)) {
|
|
88
93
|
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
94
|
process.exit(1);
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
const llama = await nodeLlama.getLlama();
|
|
94
98
|
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
|
-
});
|
|
99
|
+
const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
|
|
99
100
|
|
|
100
101
|
const systemPrompt = `You are a structured data extractor. Convert the user's conversational intent into a strict JSON schema.
|
|
101
102
|
Your response MUST be ONLY valid JSON matching this schema exactly. Do not output conversational text.
|
|
@@ -116,10 +117,7 @@ JSON Schema:
|
|
|
116
117
|
});
|
|
117
118
|
|
|
118
119
|
let responseText = "";
|
|
119
|
-
await session.prompt(userInput, {
|
|
120
|
-
maxTokens: 1500,
|
|
121
|
-
onTextChunk: (chunk) => { responseText += chunk; }
|
|
122
|
-
});
|
|
120
|
+
await session.prompt(userInput, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
|
|
123
121
|
|
|
124
122
|
try {
|
|
125
123
|
const cleanJson = responseText.replace(/```json|```/g, "").trim();
|
|
@@ -168,9 +166,7 @@ program
|
|
|
168
166
|
const existing = loadConfig();
|
|
169
167
|
if (existing) {
|
|
170
168
|
const overwrite = await prompt('Config already exists. Overwrite? (y/N): ');
|
|
171
|
-
if (overwrite.toLowerCase() !== 'y') {
|
|
172
|
-
console.log('Aborted.'); return;
|
|
173
|
-
}
|
|
169
|
+
if (overwrite.toLowerCase() !== 'y') { console.log('Aborted.'); return; }
|
|
174
170
|
}
|
|
175
171
|
|
|
176
172
|
const registryUrl = opts.registry || 'https://everydaytok-agentq-core-logics.hf.space';
|
|
@@ -219,13 +215,15 @@ program
|
|
|
219
215
|
sellers.forEach((s, i) => {
|
|
220
216
|
const tier = s.verification_tier === 'verified' ? c.green('β Verified') : c.dim('Unverified');
|
|
221
217
|
console.log(` ${c.bold((i+1) + '.')} ${c.cyan(s.agent_id)} (${tier})`);
|
|
218
|
+
console.log(` ANP address: ${c.yellow('ANP/C.' + s.agent_id)}`);
|
|
219
|
+
console.log(` Modes: ${(s.supported_modes || []).join(', ')}`);
|
|
222
220
|
});
|
|
223
221
|
});
|
|
224
222
|
|
|
225
223
|
program
|
|
226
224
|
.command('negotiate')
|
|
227
225
|
.description('Start a negotiation with a seller agent')
|
|
228
|
-
.argument('[address]', '
|
|
226
|
+
.argument('[address]', 'ANP address β format: MODE.domain.anp (e.g. ANP/A.amazon.anp)')
|
|
229
227
|
.option('--budget <n>', 'Max budget (USD)')
|
|
230
228
|
.option('--auto', 'Run sandbox auto-negotiation')
|
|
231
229
|
.action(async (address, opts) => {
|
|
@@ -234,18 +232,17 @@ program
|
|
|
234
232
|
let budget = opts.budget;
|
|
235
233
|
let constraints = {};
|
|
236
234
|
|
|
237
|
-
// ββ WIZARD MODE
|
|
235
|
+
// ββ WIZARD MODE ββ
|
|
238
236
|
if (!targetAddress || !budget) {
|
|
239
237
|
banner();
|
|
240
238
|
console.log(c.bold("π¬ Clinch Onboarding Wizard β Tell me what you're looking for.\n"));
|
|
241
239
|
|
|
242
240
|
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
|
|
241
|
+
c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\nπ¬: "));
|
|
244
242
|
|
|
245
243
|
if (!naturalIntent) process.exit(1);
|
|
246
244
|
|
|
247
245
|
const parsed = await parseIntentWithLLM(naturalIntent, cfg.modelPath);
|
|
248
|
-
|
|
249
246
|
if (!parsed) {
|
|
250
247
|
console.error(c.red("β Error parsing constraints. Please try manual entry."));
|
|
251
248
|
process.exit(1);
|
|
@@ -254,8 +251,7 @@ program
|
|
|
254
251
|
console.log(c.bold("\nπ Extracted Intention Context:"));
|
|
255
252
|
console.log(` - Category: ${c.cyan(parsed.category)}`);
|
|
256
253
|
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`);
|
|
254
|
+
console.log(` - Max Budget: ${c.green("$" + parsed.max_budget)}\n`);
|
|
259
255
|
|
|
260
256
|
const confirm = await prompt("π Is this correct? (Y/n): ");
|
|
261
257
|
if (confirm.toLowerCase() === 'n') process.exit(0);
|
|
@@ -263,8 +259,7 @@ program
|
|
|
263
259
|
constraints = parsed;
|
|
264
260
|
budget = parsed.max_budget;
|
|
265
261
|
|
|
266
|
-
|
|
267
|
-
console.log(c.dim(`\nQuerying registry to find compatible sellers for "${parsed.category}"...`));
|
|
262
|
+
console.log(c.dim(`\nQuerying registry for category "${parsed.category}"...`));
|
|
268
263
|
const coreDiscovery = getClinchCore(cfg);
|
|
269
264
|
await coreDiscovery.initialize(cfg.token);
|
|
270
265
|
const results = await coreDiscovery.search(parsed.category);
|
|
@@ -272,30 +267,35 @@ program
|
|
|
272
267
|
|
|
273
268
|
const sellers = results.results || [];
|
|
274
269
|
if (sellers.length === 0) {
|
|
275
|
-
console.log(c.yellow(`\nNo
|
|
276
|
-
targetAddress = await prompt("π
|
|
270
|
+
console.log(c.yellow(`\nNo sellers found for "${parsed.category}".`));
|
|
271
|
+
targetAddress = await prompt("π Enter address manually (e.g. ANP/A.amazon.anp): ");
|
|
277
272
|
} 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/
|
|
273
|
+
console.log(c.bold(`\nAvailable sellers:`));
|
|
274
|
+
sellers.forEach((s, idx) => console.log(` ${idx + 1}. ${c.cyan(s.agent_id)}`));
|
|
275
|
+
const selection = await prompt(`\nπ Select a seller (1-${sellers.length}): `);
|
|
276
|
+
targetAddress = `ANP/C.${sellers[parseInt(selection) - 1].agent_id}`;
|
|
282
277
|
}
|
|
283
278
|
} else {
|
|
284
279
|
constraints = { intent: 'purchase', item: 'Item', max_budget: parseFloat(budget) };
|
|
285
280
|
}
|
|
286
281
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
282
|
+
if (!targetAddress.startsWith('ANP/')) {
|
|
283
|
+
console.error(c.red(`\nβ Invalid Address: ${targetAddress}`));
|
|
284
|
+
console.error(" Address MUST include the protocol mode prefix.");
|
|
285
|
+
console.error(" Example: ANP/C.amazon.anp\n");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
290
288
|
|
|
289
|
+
const core = getClinchCore(cfg);
|
|
291
290
|
let runAuto = opts.auto;
|
|
291
|
+
|
|
292
292
|
if (runAuto === undefined) {
|
|
293
293
|
const autoInput = await prompt("\nπ Let Agent Q negotiate autonomously? (Y/n): ");
|
|
294
294
|
runAuto = autoInput.toLowerCase() !== 'n';
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
if (runAuto) {
|
|
298
|
-
console.log(c.yellow('π€ Auto-mode: Local LLM sandbox
|
|
298
|
+
console.log(c.yellow('π€ Auto-mode: Local LLM sandbox active.\n'));
|
|
299
299
|
await core.sandbox({ modelPath: cfg.modelPath });
|
|
300
300
|
} else {
|
|
301
301
|
await core.initialize(cfg.token);
|
|
@@ -303,11 +303,24 @@ program
|
|
|
303
303
|
|
|
304
304
|
core.on('session_started', ({ sessionId }) => {
|
|
305
305
|
console.log(c.green(`\nβ Session started: ${c.bold(sessionId)}`));
|
|
306
|
+
saveSessionState(sessionId, core); // Initial save
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
core.on('callback_received', ({ sessionId }) => saveSessionState(sessionId, core));
|
|
310
|
+
|
|
311
|
+
core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
|
|
312
|
+
saveSessionState(sessionId, core);
|
|
313
|
+
if (outcome === 'deal') {
|
|
314
|
+
console.log(c.green(c.bold(`\nπ DEAL SECURED at $${finalPrice}`)));
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
306
317
|
});
|
|
307
318
|
|
|
308
319
|
core.on('status_changed', status => {
|
|
309
|
-
if (status === '
|
|
310
|
-
|
|
320
|
+
if (status === 'STALEMATE') {
|
|
321
|
+
console.log(c.red('\nβ Stalemate. Exiting.'));
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
311
324
|
});
|
|
312
325
|
|
|
313
326
|
const sessionId = await core.negotiate(targetAddress, constraints);
|
|
@@ -316,19 +329,79 @@ program
|
|
|
316
329
|
console.log(c.bold('\nManual mode β type a price to counter, or "exit" / "accept":\n'));
|
|
317
330
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
318
331
|
rl.on('line', async (cmd) => {
|
|
319
|
-
if (cmd === 'exit') {
|
|
332
|
+
if (cmd === 'exit') {
|
|
333
|
+
await core.exitSession(sessionId);
|
|
334
|
+
saveSessionState(sessionId, core);
|
|
335
|
+
process.exit(0);
|
|
336
|
+
}
|
|
320
337
|
else if (cmd === 'accept') { console.log(c.green(`Accepting...`)); rl.close(); }
|
|
321
338
|
else {
|
|
322
339
|
const price = parseFloat(cmd);
|
|
323
|
-
if (!isNaN(price))
|
|
340
|
+
if (!isNaN(price)) {
|
|
341
|
+
await core.sendCounter(sessionId, price, 'Counter offer');
|
|
342
|
+
saveSessionState(sessionId, core);
|
|
343
|
+
}
|
|
324
344
|
}
|
|
325
345
|
});
|
|
326
346
|
}
|
|
327
347
|
});
|
|
328
348
|
|
|
349
|
+
program
|
|
350
|
+
.command('sessions')
|
|
351
|
+
.description('List saved negotiation sessions')
|
|
352
|
+
.action(() => {
|
|
353
|
+
const sessions = loadSessions();
|
|
354
|
+
const ids = Object.keys(sessions);
|
|
355
|
+
if (ids.length === 0) return console.log(c.yellow('No saved sessions found.'));
|
|
356
|
+
|
|
357
|
+
console.log(c.bold(`Found ${ids.length} session(s):\n`));
|
|
358
|
+
ids.forEach(id => {
|
|
359
|
+
const s = JSON.parse(sessions[id].state);
|
|
360
|
+
console.log(` ${c.cyan(id)} - Target: ${s.sellerId} | Status: ${c.bold(s.status)} | Turn: ${s.currentTurn}`);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
program
|
|
365
|
+
.command('resume')
|
|
366
|
+
.description('Resume a dropped or asynchronous negotiation session')
|
|
367
|
+
.argument('<sessionId>', 'The session ID to resume')
|
|
368
|
+
.option('--auto', 'Resume with auto-negotiation')
|
|
369
|
+
.action(async (sessionId, opts) => {
|
|
370
|
+
const cfg = requireConfig();
|
|
371
|
+
const sessions = loadSessions();
|
|
372
|
+
|
|
373
|
+
if (!sessions[sessionId]) {
|
|
374
|
+
console.error(c.red(`Session ${sessionId} not found in local store.`));
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log(c.yellow(`\nRehydrating Session ${c.bold(sessionId)}...\n`));
|
|
379
|
+
const core = getClinchCore(cfg);
|
|
380
|
+
|
|
381
|
+
if (opts.auto) {
|
|
382
|
+
await core.sandbox({ modelPath: cfg.modelPath });
|
|
383
|
+
} else {
|
|
384
|
+
await core.initialize(cfg.token);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
core.importSessionState(sessions[sessionId].state);
|
|
388
|
+
|
|
389
|
+
core.on('callback_received', ({ id }) => saveSessionState(id, core));
|
|
390
|
+
core.on('session_closed', ({ outcome, finalPrice }) => {
|
|
391
|
+
saveSessionState(sessionId, core);
|
|
392
|
+
if (outcome === 'deal') console.log(c.green(c.bold(`\nπ DEAL SECURED at $${finalPrice}`)));
|
|
393
|
+
process.exit(0);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
console.log(c.green('β State rehydrated. Listening for webhooks/callbacks...\n'));
|
|
397
|
+
if (!opts.auto) {
|
|
398
|
+
console.log(c.dim('Awaiting remote updates. Press Ctrl+C to detach.'));
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
329
402
|
program
|
|
330
403
|
.name('clinch')
|
|
331
404
|
.description('Clinch Protocol β Agent Negotiation CLI')
|
|
332
|
-
.version('0.
|
|
405
|
+
.version('0.2.0');
|
|
333
406
|
|
|
334
407
|
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.2.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.2.0"
|
|
23
23
|
}
|
|
24
24
|
}
|