@@ -83,8 +83,28 @@ function getRawSessions() {
8383}
8484
8585function syncState ( core ) {
86- if ( fs . existsSync ( STATE_FILE ) ) core . importSessions ( getRawSessions ( ) ) ;
87- const save = ( ) => fs . writeFileSync ( STATE_FILE , JSON . stringify ( core . exportSessions ( ) , null , 2 ) ) ;
86+ let isWriting = false ;
87+ const load = ( ) => {
88+ if ( ! isWriting && fs . existsSync ( STATE_FILE ) ) {
89+ core . importSessions ( getRawSessions ( ) ) ;
90+ }
91+ } ;
92+
93+ load ( ) ; // Initial load
94+
95+ // Dynamically hot-reload state file so daemon always matches temporary CLI runs
96+ fs . watchFile ( STATE_FILE , { interval : 1000 } , ( curr , prev ) => {
97+ if ( curr . mtimeMs !== prev . mtimeMs ) {
98+ load ( ) ;
99+ }
100+ } ) ;
101+
102+ const save = ( ) => {
103+ isWriting = true ;
104+ fs . writeFileSync ( STATE_FILE , JSON . stringify ( core . exportSessions ( ) , null , 2 ) ) ;
105+ setTimeout ( ( ) => { isWriting = false ; } , 1500 ) ;
106+ } ;
107+
88108 core . on ( 'counter_received' , save ) ;
89109 core . on ( 'approval_required' , save ) ;
90110 core . on ( 'deal_signed' , save ) ;
@@ -138,7 +158,7 @@ program.command('config').description('Set CLI configuration')
138158 } ) ;
139159
140160program . command ( 'init' ) . description ( 'Initialize cryptographic identity' )
141- . option ( '--key <hex>' , 'Import an official private key provided by the Clinch Dashboard (Required for Sellers) ' )
161+ . option ( '--key <hex>' , 'Import private key' )
142162 . action ( async ( opts ) => {
143163 const rl = readline . createInterface ( { input : process . stdin , output : process . stdout } ) ;
144164 const pass = await new Promise ( resolve => rl . question ( 'Enter strong vault passphrase: ' , ans => { rl . close ( ) ; resolve ( ans ) ; } ) ) ;
@@ -151,14 +171,14 @@ program.command('init').description('Initialize cryptographic identity')
151171 const seed = crypto . randomBytes ( 32 ) ;
152172 const keyPair = nacl . sign . keyPair . fromSeed ( seed ) ;
153173 secretKeyHex = Buffer . from ( keyPair . secretKey ) . toString ( 'hex' ) ;
154- console . log ( `\n💡 Note: A local buyer-only identity was generated. If you are registering a seller node, you must claim a domain on the dashboard and run 'clinch init --key <PRIVATE_KEY>' instead .` ) ;
174+ console . log ( `\n💡 Generated new buyer-only identity.` ) ;
155175 }
156176
157177 await SecureVault . save ( pass , { privateKeyHex : secretKeyHex , blindKeys : { } } ) ;
158178 console . log ( `🎉 Identity configured and vault locked.` ) ;
159179 } ) ;
160180
161- program . command ( 'key' ) . description ( 'Manage third-party API credentials (Blind Key Pass vault) ' )
181+ program . command ( 'key' ) . description ( 'Manage third-party API credentials' )
162182 . option ( '--set <domain>' , 'Set a new key for a domain' )
163183 . option ( '--value <key>' , 'The actual secret key value' )
164184 . option ( '--list' , 'List registered domains' )
@@ -178,7 +198,7 @@ program.command('key').description('Manage third-party API credentials (Blind Ke
178198 } else if ( opts . set && opts . value ) {
179199 vaultRes . parsed . blindKeys [ opts . set ] = opts . value ;
180200 await SecureVault . save ( vaultRes . pass , vaultRes . parsed ) ;
181- console . log ( `✓ Key registered! Handshakes and counters targeting ${ opts . set } will silently inject this token. ` ) ;
201+ console . log ( `✓ Key registered!` ) ;
182202 } else {
183203 console . log ( "Provide --list, or --set <domain> --value <key>" ) ;
184204 }
@@ -203,6 +223,7 @@ program.command('start').description('Start the listener daemon')
203223
204224 core . on ( 'counter_received' , async ( s ) => {
205225 console . log ( `\n💬 Counter from ${ s . targetId } : $${ s . lastPrice } ` ) ;
226+ if ( s . lastMessage ) console . log ( ` [Message]: ${ s . lastMessage } ` ) ;
206227 if ( cfg . openClawWebhook ) await fetch ( cfg . openClawWebhook , { method : 'POST' , body : JSON . stringify ( { event : 'counter_received' , session : s } ) } ) . catch ( ( ) => { } ) ;
207228 } ) ;
208229
@@ -211,80 +232,70 @@ program.command('start').description('Start the listener daemon')
211232 process . on ( 'SIGTERM' , ( ) => { console . log ( '\n👋 Shutting down daemon...' ) ; process . exit ( 0 ) ; } ) ;
212233 } ) ;
213234
214- // --- DISCOVERY ---
215-
216- program . command ( 'discover <category>' ) . description ( 'Browse registered sellers for a category' )
235+ program . command ( 'discover <category>' ) . description ( 'Browse registered sellers' )
217236 . option ( '--direct' , 'JSON output mode' )
218237 . action ( async ( category , opts ) => {
219238 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
220- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
239+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
221240
222241 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
223242 const results = await core . discover ( category ) ;
224243
225244 if ( opts . direct ) return console . log ( JSON . stringify ( results ) ) ;
226245
227246 if ( ! results . length ) {
228- console . log ( `\n🔍 No sellers found for "${ category } ". Try a broader category.\n ` ) ;
247+ console . log ( `\n🔍 No sellers found for "${ category } ".` ) ;
229248 return ;
230249 }
231250
232251 console . log ( `\n🔍 Sellers for "${ category } " (${ results . length } found):\n` ) ;
233252 results . forEach ( ( r , i ) => {
234253 const name = r . display_name || `${ r . agent_id } ` ;
235254 const official = r . official_node ? ' ✓ Official' : '' ;
236- const modes = r . supported_modes && r . supported_modes . length ? r . supported_modes . join ( ', ' ) : 'none' ;
237-
238255 console . log ( ` [${ i + 1 } ] ${ name } ${ official } ` ) ;
239256 console . log ( ` Domain ID : ${ r . agent_id } ` ) ;
240- console . log ( ` Categories : ${ ( r . categories || [ ] ) . join ( ', ' ) } ` ) ;
241257 console . log ( ` Capabilities : ${ ( r . capabilities || [ ] ) . join ( ', ' ) } ` ) ;
242- console . log ( ` Modes : ${ modes } ` ) ;
243- console . log ( ` Reputation : ${ r . reputation_score } /100` ) ;
244258 console . log ( '' ) ;
245259 } ) ;
246- console . log ( ` 💡 Use --target <agent_id> with 'clinch negotiate' to target a specific seller.\n` ) ;
247260 } ) ;
248261
249- // --- CORE NEGOTIATION COMMANDS ---
250-
251- program . command ( 'status' ) . description ( 'List all active negotiations' )
262+ program . command ( 'status' ) . description ( 'List active negotiations' )
252263 . option ( '--direct' , 'JSON output mode' )
253264 . action ( ( opts ) => {
254265 const data = getRawSessions ( ) ;
255266 const sessions = Object . values ( data ) . filter ( s => s . state !== 'SIGNED' && s . state !== 'CANCELLED' ) ;
256267 if ( opts . direct ) return console . log ( JSON . stringify ( sessions ) ) ;
257268 console . log ( "\n📊 Active Negotiations:" ) ;
258269 if ( ! sessions . length ) console . log ( " None." ) ;
259- sessions . forEach ( s => console . log ( ` [${ s . state } ] ID: ${ s . sessionId } | Target: ${ s . targetId } | Item: ${ s . constraints . item } | Last Price: $${ s . lastPrice } ` ) ) ;
270+ sessions . forEach ( s => console . log ( ` [${ s . state } ] ID: ${ s . sessionId } | Target: ${ s . targetId } | Last Price: $${ s . lastPrice } ` ) ) ;
260271 console . log ( "" ) ;
261272 } ) ;
262273
263- program . command ( 'deals' ) . description ( 'List completed/ signed deals' )
274+ program . command ( 'deals' ) . description ( 'List signed deals' )
264275 . option ( '--direct' , 'JSON output mode' )
265276 . action ( ( opts ) => {
266277 const data = getRawSessions ( ) ;
267- const deals = Object . values ( data ) . filter ( s => s . state === 'SIGNED' && s . artifact !== null && s . artifact !== undefined ) . map ( s => s . artifact ) ;
278+ const deals = Object . values ( data ) . filter ( s => s . state === 'SIGNED' && s . artifact ) . map ( s => s . artifact ) ;
268279 if ( opts . direct ) return console . log ( JSON . stringify ( deals ) ) ;
269280 console . log ( "\n🔐 Signed Deals:" ) ;
270281 if ( ! deals . length ) console . log ( " None." ) ;
271- deals . forEach ( d => console . log ( ` ID: ${ d . sessionId } | Item: ${ d . item } | Price: $${ d . price } | Date: ${ new Date ( d . timestamp ) . toLocaleString ( ) } ` ) ) ;
282+ deals . forEach ( d => console . log ( ` ID: ${ d . sessionId } | Item: ${ d . item } | Price: $${ d . price } ` ) ) ;
272283 console . log ( "" ) ;
273284 } ) ;
274285
275286program . command ( 'negotiate <intent>' ) . description ( 'Initialize a negotiation' )
276- . option ( '--target <domain>' , 'Target seller domain (agent_id) ' )
287+ . option ( '--target <domain>' , 'Target seller domain' )
277288 . option ( '--direct' , 'JSON output mode' )
278289 . action ( async ( intent , opts ) => {
279290 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
280- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
291+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
281292
282293 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
283294 const save = syncState ( core ) ;
284295
285296 const constraints = await extractConstraints ( intent , opts . direct ) ;
286297 let target = opts . target || ( await core . discover ( constraints . item ) ) [ 0 ] ?. agent_id ;
287- if ( ! target ) return console . log ( opts . direct ? JSON . stringify ( { error : "No sellers found" } ) : "❌ No sellers found. Run 'clinch discover <category>' to browse available sellers. " ) ;
298+ if ( ! target ) return console . log ( opts . direct ? JSON . stringify ( { error : "No sellers found" } ) : "❌ No sellers found." ) ;
288299
289300 const session = await core . proposeDeal ( target , constraints ) ;
290301 save ( ) ;
@@ -303,7 +314,7 @@ program.command('counter <sessionId> <price>').description('Counter an offer')
303314 . option ( '--direct' , 'JSON output mode' )
304315 . action ( async ( sessionId , price , opts ) => {
305316 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
306- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
317+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
307318
308319 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
309320 const save = syncState ( core ) ;
@@ -313,19 +324,19 @@ program.command('counter <sessionId> <price>').description('Counter an offer')
313324 save ( ) ;
314325 if ( session . state === NegotiationState . CANCELLED ) {
315326 if ( opts . direct ) console . log ( JSON . stringify ( { status : "CANCELLED" , session } ) ) ;
316- else console . log ( `\n❌ Seller cancelled the negotiation .\nSession ID: ${ session . sessionId } \n` ) ;
327+ else console . log ( `\n❌ Session cancelled by peer .\nSession ID: ${ session . sessionId } \n` ) ;
317328 } else {
318329 if ( opts . direct ) console . log ( JSON . stringify ( { status : "COUNTERED" , session } ) ) ;
319- else console . log ( `✓ Counter of $${ price } sent for ${ sessionId } ` ) ;
330+ else console . log ( `✓ Counter of $${ price } sent. ` ) ;
320331 }
321332 } catch ( e ) { console . log ( opts . direct ? JSON . stringify ( { error : e . message } ) : `❌ Error: ${ e . message } ` ) ; }
322333 } ) ;
323334
324- program . command ( 'cancel <sessionId>' ) . description ( 'Cleanly exit a negotiation' )
335+ program . command ( 'cancel <sessionId>' ) . description ( 'Exit a negotiation' )
325336 . option ( '--direct' , 'JSON output mode' )
326337 . action ( async ( sessionId , opts ) => {
327338 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
328- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
339+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
329340
330341 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
331342 const save = syncState ( core ) ;
@@ -334,15 +345,15 @@ program.command('cancel <sessionId>').description('Cleanly exit a negotiation')
334345 const session = await core . cancelSession ( sessionId ) ;
335346 save ( ) ;
336347 if ( opts . direct ) console . log ( JSON . stringify ( { status : "CANCELLED" , session } ) ) ;
337- else console . log ( `✓ Session ${ sessionId } cleanly cancelled.` ) ;
348+ else console . log ( `✓ Session cleanly cancelled.` ) ;
338349 } catch ( e ) { console . log ( opts . direct ? JSON . stringify ( { error : e . message } ) : `❌ Error: ${ e . message } ` ) ; }
339350 } ) ;
340351
341- program . command ( 'approve <sessionId>' ) . description ( 'Cryptographically sign a CONFIRMED deal' )
352+ program . command ( 'approve <sessionId>' ) . description ( 'Sign a CONFIRMED deal' )
342353 . option ( '--direct' , 'JSON output mode' )
343354 . action ( async ( sessionId , opts ) => {
344355 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
345- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
356+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
346357
347358 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
348359 const save = syncState ( core ) ;
@@ -351,61 +362,45 @@ program.command('approve <sessionId>').description('Cryptographically sign a CON
351362 const artifact = await core . approveAndSign ( sessionId ) ;
352363 save ( ) ;
353364 if ( opts . direct ) console . log ( JSON . stringify ( { status : "SIGNED" , artifact } ) ) ;
354- else console . log ( `\n🔐 DEAL SIGNED AND COMMITTED!\nArtifact ID: ${ artifact . sessionId } \ n` ) ;
365+ else console . log ( `\n🔐 DEAL SIGNED AND COMMITTED!\n` ) ;
355366 } catch ( e ) { console . log ( opts . direct ? JSON . stringify ( { error : e . message } ) : `\n❌ Approval Failed: ${ e . message } \n` ) ; }
356367 } ) ;
357368
358- // --- SELLER MODE ---
359369const nodeCmd = program . command ( 'node' ) . description ( 'Node management commands' ) ;
360370
361- nodeCmd . command ( 'register <agentId> <endpoint>' ) . description ( 'Bind your dashboard .anp domain to your server endpoint' )
362- . option ( '--categories <list>' , 'Comma separated categories (e.g. "retail,hardware") ' , 'general' )
363- . option ( '--capabilities <list>' , 'Comma separated capabilities (e.g. "room_routing,ai_mediator") ' , 'http-webhook' )
364- . option ( '--modes <list>' , 'Supported protocol modes (e.g. "ANP/C") ' , 'ANP/C' )
371+ nodeCmd . command ( 'register <agentId> <endpoint>' ) . description ( 'Bind domain to endpoint' )
372+ . option ( '--categories <list>' , 'Categories ' , 'general' )
373+ . option ( '--capabilities <list>' , 'Capabilities ' , 'http-webhook' )
374+ . option ( '--modes <list>' , 'Protocol modes' , 'ANP/C' )
365375 . action ( async ( agentId , endpoint , opts ) => {
366376 const vaultRes = await SecureVault . unlock ( false ) ;
367377 if ( ! vaultRes ) return console . log ( "Run 'clinch init' first." ) ;
368378
369379 const core = await getInitializedCore ( vaultRes . parsed , false ) ;
370- const categories = opts . categories ? opts . categories . split ( ',' ) . map ( c => c . trim ( ) ) . filter ( Boolean ) : [ 'general' ] ;
371- const capabilities = opts . capabilities ? opts . capabilities . split ( ',' ) . map ( c => c . trim ( ) ) . filter ( Boolean ) : [ 'http-webhook' ] ;
372- const modes = opts . modes ? opts . modes . split ( ',' ) . map ( m => m . trim ( ) ) . filter ( Boolean ) : [ 'ANP/C' ] ;
380+ const categories = opts . categories ? opts . categories . split ( ',' ) . map ( c => c . trim ( ) ) : [ 'general' ] ;
381+ const capabilities = opts . capabilities ? opts . capabilities . split ( ',' ) . map ( c => c . trim ( ) ) : [ 'http-webhook' ] ;
382+ const modes = opts . modes ? opts . modes . split ( ',' ) . map ( m => m . trim ( ) ) : [ 'ANP/C' ] ;
373383
374384 try {
375- await core . registerNode (
376- agentId ,
377- endpoint ,
378- categories ,
379- capabilities ,
380- { supported_modes : modes }
381- ) ;
382-
383- console . log ( `\n✓ Endpoint successfully bound!` ) ;
384- console . log ( ` Domain : ${ agentId } ` ) ;
385- console . log ( ` Routing To : ${ endpoint } ` ) ;
386- console . log ( ` Categories : ${ categories . join ( ', ' ) } ` ) ;
387- console . log ( ` Capabilities : ${ capabilities . join ( ', ' ) } ` ) ;
388- console . log ( ` Modes : ${ modes . join ( ', ' ) } ` ) ;
389- console . log ( `\n💡 Note: Core domain parameters (Display Name, Instructions, Identity) are strictly managed via the Clinch Dashboard.\n` ) ;
385+ await core . registerNode ( agentId , endpoint , categories , capabilities , { supported_modes : modes } ) ;
386+ console . log ( `\n✓ Endpoint bound!` ) ;
390387 } catch ( e ) {
391388 console . log ( `\n❌ Registration Failed: ${ e . message } \n` ) ;
392389 }
393390 } ) ;
394391
395392program . command ( 'serve' ) . description ( 'Start Seller HTTP server' )
396393 . option ( '--port <p>' , 'Port to listen on' , 8080 )
397- . option ( '--config <file>' , 'Path to seller config JSON ' )
398- . option ( '--direct' , 'JSON output mode for background agent handling ' )
394+ . option ( '--config <file>' , 'Config path ' )
395+ . option ( '--direct' , 'JSON output mode' )
399396 . action ( async ( opts ) => {
400397 const vaultRes = await SecureVault . unlock ( opts . direct ) ;
401- if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked or Uninitialized " } ) : "❌ Run 'clinch init' first." ) ;
398+ if ( ! vaultRes ) return console . log ( opts . direct ? JSON . stringify ( { error : "Vault Locked" } ) : "❌ Run 'clinch init' first." ) ;
402399
403400 const core = await getInitializedCore ( vaultRes . parsed , opts . direct ) ;
404401 const save = syncState ( core ) ;
405402
406- const sellerCfg = opts . config && fs . existsSync ( opts . config )
407- ? JSON . parse ( fs . readFileSync ( opts . config ) )
408- : { defaultFloor : 45 , defaultApprove : 100 , maxTurns : 5 } ;
403+ const sellerCfg = opts . config && fs . existsSync ( opts . config ) ? JSON . parse ( fs . readFileSync ( opts . config ) ) : { defaultFloor : 45 , defaultApprove : 100 , maxTurns : 5 } ;
409404
410405 const app = express ( ) ;
411406 app . use ( express . json ( ) ) ;
@@ -414,11 +409,7 @@ program.command('serve').description('Start Seller HTTP server')
414409 const { session_id, constraints, buyer_pub_key } = req . body ;
415410 core . registerIncomingSession ( session_id , buyer_pub_key , constraints ) ;
416411
417- if ( opts . direct ) console . log ( JSON . stringify ( { event : "INCOMING_PROPOSAL" , session_id, constraints } ) ) ;
418- else console . log ( `\n🔔 Incoming Proposal: ${ session_id } wants ${ constraints . item } for ${ constraints . max_budget !== null ? '$' + constraints . max_budget : 'unspecified' } ` ) ;
419-
420- const categoryCfg = sellerCfg . categories ?. [ constraints . item ] || sellerCfg ;
421-
412+ const categoryCfg = sellerCfg ;
422413 if ( constraints . max_budget !== null && constraints . max_budget >= categoryCfg . defaultApprove ) {
423414 core . updateSessionStateLocally ( session_id , NegotiationState . CONFIRMED , categoryCfg . defaultApprove , 1 ) ;
424415 res . json ( { type : 'CONFIRM' , price : categoryCfg . defaultApprove } ) ;
0 commit comments