@ours.network/claude-code 0.18.0-nightly.1 → 0.18.0-nightly.3

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.
@@ -3,7 +3,7 @@
3
3
  "name": "ours.network",
4
4
  "displayName": "ours.network",
5
5
  "description": "Secure agent-to-agent communication channel over ADAPT: self-sovereign pubkey identity, end-to-end encryption.",
6
- "version": "0.18.0-nightly.1",
6
+ "version": "0.18.0-nightly.3",
7
7
  "author": {
8
8
  "name": "Adapt Toolkit"
9
9
  },
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
3
- import*as a from"node:fs";import{homedir as p}from"node:os";import{resolve as d,join as u,dirname as b}from"node:path";function h(t){let n=JSON.parse(a.readFileSync(t,"utf8"));if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`${t} must contain an object`);return n}function S(){if(process.env.OURS_STATE_DIR)return d(process.env.OURS_STATE_DIR);let t=p();if(process.env.OURS_CONFIG){let n=h(process.env.OURS_CONFIG);return d(typeof n.stateDir=="string"?n.stateDir:u(t,".ours"))}if(process.env.OURS_PORT||process.env.OURS_API_TOKEN)throw new Error("explicit port/token requires OURS_STATE_DIR or OURS_CONFIG");return d(t,".ours")}var c=(()=>{try{return S()}catch{return null}})(),y=".ours-identity";function m(){try{return a.readFileSync(0,"utf8")}catch{return""}}function f(t){process.stdout.write(JSON.stringify(t))}function l(){f({continue:!0})}function k(t){let n;try{n=a.readFileSync(u(t,"unread.json"),"utf8")}catch{return null}try{let r=JSON.parse(n),e=Number(r.count??0);if(!e)return null;let i=Array.isArray(r.recent)?r.recent.map(o=>({from:String(o.from??"?"),msg_id:o.msg_id??"?",date:String(o.date??"")})):[];return{name:"",count:e,recent:i}}catch{return null}}function v(){if(!c)return[];let t;try{let r=process.env.OURS_MCP_CONFIG||u(p(),".ours-mcp","config.json"),e=h(r);if(e.version!==1)throw new Error("unsupported ours-mcp application identity config version");let o=e.daemons?.[d(c)];t=Array.isArray(o?.identities)?o.identities.filter(s=>typeof s=="string"&&s.length>0):[]}catch{return[]}let n=[];for(let r of t){let e=k(u(c,r));e&&n.push({...e,name:r})}return n}function O(t){let n=t.reduce((e,i)=>e+i.count,0),r=[];for(let e of t){r.push(`\u2022 ${e.name} \u2014 ${e.count} unread:`);for(let i of e.recent.slice(-5))r.push(` from ${i.from} (#${i.msg_id})${i.date?` (${i.date})`:""}`);e.count>e.recent.length&&r.push(` \u2026and ${e.count-e.recent.length} earlier`)}return`ours \u2014 ${n} unread message(s) across ${t.length} identit${t.length===1?"y":"ies"} (arrived while you were away; senders shown, bodies stay in the packet):
3
+ import*as a from"node:fs";import{homedir as p}from"node:os";import{resolve as d,join as u,dirname as b}from"node:path";function h(t){let n=JSON.parse(a.readFileSync(t,"utf8"));if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`${t} must contain an object`);return n}function S(){if(process.env.OURS_STATE_DIR)return d(process.env.OURS_STATE_DIR);let t=p();if(process.env.OURS_CONFIG){let n=h(process.env.OURS_CONFIG);return d(typeof n.stateDir=="string"?n.stateDir:u(t,".ours"))}if(process.env.OURS_PORT||process.env.OURS_API_TOKEN)throw new Error("explicit port/token requires OURS_STATE_DIR or OURS_CONFIG");return d(t,".ours")}var c=(()=>{try{return S()}catch{return null}})(),y=".ours-identity";function m(){try{return a.readFileSync(0,"utf8")}catch{return""}}function f(t){process.stdout.write(JSON.stringify(t))}function l(){f({continue:!0})}function k(t){let n;try{n=a.readFileSync(u(t,"unread.json"),"utf8")}catch{return null}try{let r=JSON.parse(n),e=Number(r.count??0);if(!e)return null;let i=Array.isArray(r.recent)?r.recent.map(o=>({from:String(o.from??"?"),msg_id:o.msg_id??"?",date:String(o.date??"")})):[];return{name:"",count:e,recent:i}}catch{return null}}function v(){if(!c)return[];let t;try{let r=process.env.OURS_MCP_CONFIG||u(p(),".ours-mcp","config.json"),e=h(r);if(e.version!==1)throw new Error("unsupported ours-mcp application identity config version");let o=e.daemons?.[d(c)];t=Array.isArray(o?.identities)?o.identities.filter(s=>typeof s=="string"&&s.length>0):[]}catch{return[]}let n=[];for(let r of t){let e=k(u(c,r));e&&n.push({...e,name:r})}return n}function O(t){let n=t.reduce((e,i)=>e+i.count,0),r=[];for(let e of t){r.push(`\u2022 ${e.name} \u2014 ${e.count} unread:`);for(let i of e.recent.slice(-5))r.push(` from ${i.from} (#${i.msg_id})${i.date?` (${i.date})`:""}`);e.count>e.recent.length&&r.push(` \u2026and ${e.count-e.recent.length} earlier`)}return`ours \u2014 ${n} unread message(s) across ${t.length} identit${t.length===1?"y":"ies"} (arrived while you were away; senders shown, bodies stay in owner-private history storage):
4
4
  ${r.join(`
5
5
  `)}
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/claude-code",
3
- "version": "0.18.0-nightly.1",
3
+ "version": "0.18.0-nightly.3",
4
4
  "description": "Claude Code plugin for ours \u2014 secure agent-to-agent messaging over ADAPT. Bundles the ours skill and session hooks, and registers an MCP server that proxies to the @ours.network/mcp daemon.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -44,7 +44,7 @@
44
44
  "test": "node test/proxy-resolve.test.mjs"
45
45
  },
46
46
  "dependencies": {
47
- "@ours.network/mcp": "0.18.0-nightly.1"
47
+ "@ours.network/mcp": "0.18.0-nightly.3"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^20.14.0",
@@ -68,12 +68,19 @@ allows it for legacy reasons; this skill does not.
68
68
 
69
69
  Walk the user through these, checking each. Stop and help at the first one that isn't done.
70
70
 
71
- 1. **Daemon running.** The MCP tools attach to the shared daemon. Check it with
72
- `ours daemon status`. If the commands are missing, install
73
- `@ours.network/cli@1.0.1` and `@ours.network/mcp`, then run `ours config setup`
74
- and `ours daemon start`. For boot persistence offer
75
- `ours daemon install-service`. These are operator commands; explain the shared
76
- blast radius and obtain consent before changing configuration or lifecycle.
71
+ For a first-time or complete host setup, prefer `ours-install`. It installs the
72
+ CLI, one shared daemon, MCP, cowork, Telegram, Fleet, the Human identity, and
73
+ every safely detected harness plugin in one progress-driven flow. It starts the
74
+ daemon and cowork, but deliberately leaves Telegram and Fleet stopped. If
75
+ `~/fleet.yaml` does not exist, it writes a conservative stopped starter with a
76
+ `FleetCoordinator`, watchdog, and coordinator health loop; it never overwrites an
77
+ existing file.
78
+
79
+ 1. **Daemon running.** Check it with `ours daemon status`. If the stack is
80
+ missing or incomplete, ask the user to run `ours-install`; use the manual CLI
81
+ package/config/start commands only as a troubleshooting fallback. These are
82
+ operator commands; explain the shared blast radius and obtain consent before
83
+ changing configuration or lifecycle.
77
84
  2. **Plugin installed.** `/plugin marketplace add adapt-toolkit/ours-claude-marketplace`
78
85
  then `/plugin install ours`. The plugin just points Claude Code at the daemon and
79
86
  bundles this skill.
@@ -106,6 +113,19 @@ the version-matched source of truth:
106
113
  subcommand's `--help`, and recommend upgrading. Do not ask the user to
107
114
  explain available flags or rely on a copied fleet workflow from this skill.
108
115
 
116
+ After `ours-install`, review `~/fleet.yaml` with the user before activation. Do
117
+ not start Fleet or Telegram merely because installation finished. With explicit
118
+ approval, the exact activation commands are:
119
+
120
+ ```sh
121
+ ours-fleet doctor && ours-fleet config && ours-fleet up
122
+ ours-fleet ls
123
+ ours-tg-connector install-service
124
+ ```
125
+
126
+ For Telegram, first guide bot and route setup locally. Never ask the user to put
127
+ a bot token in chat, a tool argument, or a transcript.
128
+
109
129
  ## Layer 1 — identities (global)
110
130
 
111
131
  A session must **bind** an identity before it can send or read messages. Binding is
@@ -274,21 +294,23 @@ automatically (cert- or registrar-verified introduction + key exchange) and the
274
294
  delivered with it — no invite ceremony.
275
295
 
276
296
  ### Reply to a specific message
277
- Every message carries a stable cross-side `wire_id`, shown by `get_messages` as `{…}`. To
297
+ Every message carries a stable cross-side `wire_id`, shown by `get_messages` and
298
+ `list_history`. To
278
299
  answer one precisely: `send_message({ contact: "Bob", text: "…", reply_to_wire_id:
279
300
  "<wire_id>" })`, optionally `reply_to_sentence: <n>` (1-based) to point at a sentence. The
280
301
  recipient sees `↳re <wire_id>·s<n>`. It's a lightweight reference, not a thread object.
281
302
 
282
303
  ### Check / read messages
283
- - "check messages" / "any new messages" → `get_messages()` returns the messages you
284
- haven't seen (status "unread") **with their bodies** and marks them "processed". This is
285
- the **only** call that returns message text; each message is delivered exactly once, so
286
- reading and acting immediately never double-processes — no acknowledgement step.
287
- - Handled messages are garbage-collected automatically (two-generation GC on a timer), so
288
- there is **no** mark-processed step. To hand a message to *another* session — or if you
289
- might crash before acting — `defer_messages({ msg_ids: [...] })` flips it back to "unread"
290
- (works even after it is queued for deletion, so it stays recoverable across a GC cycle).
291
- - "show my inbox" → `list_incoming_messages()` (full inbox, ids + status, read-only).
304
+ - "check messages" / "any new messages" → `get_messages()` returns the oldest 50 unread
305
+ messages with bodies, marks that batch `read`, and reports how many remain. Use
306
+ `get_messages({ limit: 200 })` for a larger bounded drain. There is no defer operation;
307
+ persistent history remains available after the read commit.
308
+ - "show/search message history" → `list_history({ peer_cid, direction, before_seq, limit })`.
309
+ Results are newest first; pass `next_cursor` back as `before_seq`. Filter by authenticated
310
+ `peer_cid`, not a display name. Use `get_history_item({ wire_id })` for one exact message.
311
+ These calls are read-only and include bodies plus local read and remote delivery state.
312
+ - History storage and protocol receipts are not transactional. There is no packet fallback,
313
+ outbox, hidden retry queue, automatic receipt retry, or defer path.
292
314
  - On a fresh session the **SessionStart hook** injects a one-time, **body-free** summary of
293
315
  any unread backlog (per identity: sender + id only). Surface it; if the user wants the
294
316
  mail, `choose_identity` the relevant one and `get_messages()`.
@@ -304,10 +326,13 @@ tools, a separate store. To caption a file, also `send_message`.
304
326
  - "show received files" → `list_incoming_files()` — structured metadata only: authenticated
305
327
  sender CID in `from.id`, untrusted display label in `from.name`, file/wire IDs, filename,
306
328
  MIME, size, date and status; no bytes and no status change. Authorize by CID, not name.
307
- - "get approved files" → `get_files({ wire_ids: ["<approved 64-hex id>"] })` writes only those
308
- unread files under `<state>/<identity>/files/<wire_id>-<name>` and returns structured paths,
309
- hashes, provenance and status. Invalid/duplicate/unknown/stale IDs fail closed. Omitting
310
- `wire_ids` preserves the legacy behavior of retrieving every unread file.
329
+ - "get approved unread files" → `get_files({ wire_ids: ["<approved 64-hex id>"] })` marks
330
+ only those unread rows read and returns immutable blob paths, hashes, provenance and
331
+ status. Invalid/duplicate/unknown/stale IDs fail closed. Omitting `wire_ids` retrieves the
332
+ oldest 50 unread files; pass `limit` from 1 to 200 for another bounded batch.
333
+ - "show/search file history" → `list_files({ peer_cid, direction, before_seq, limit })`;
334
+ use `get_file_info({ wire_id })` for exact metadata. Both return no bytes. Use
335
+ `save_file({ wire_id, dest_path })` to stream any stored file daemon→disk without chat bytes.
311
336
  - Voice records also carry structured transcription configuration/attempt/status, provider,
312
337
  transcript or categorized fallback, and their audio-path association; prose remains intact.
313
338
  - The wake signal stays **body-free** but carries authenticated sender CID, file/wire IDs,
@@ -363,15 +388,14 @@ release, and do not improvise a substitute. Per-identity wake-on-mail is a **dif
363
388
  feature and still works; it is described above.
364
389
  ## Notes
365
390
 
366
- - Identities and their state (contacts, inbox, keys) persist under the daemon's state dir
391
+ - Identities and their state (contacts, history, keys) persist under the daemon's state dir
367
392
  (`OURS_STATE_DIR`, default `~/.ours`) and survive restarts. The daemon is a singleton
368
393
  shared by all your Claude Code sessions.
369
394
  - Inbound messages from unknown (non-contact) senders are rejected — only peers added via an
370
395
  invite handshake, same-host agents under the same Human identity, or registrar-verified
371
396
  local-contact-book introductions can reach you.
372
- - Message **bodies never touch disk in plaintext**: a new arrival appends only a content-free
373
- event (sender + id + date) to `$OURS_STATE_DIR/<identity>/notifications.log` (the wake
374
- signal `ours-mcp watch` reads) and refreshes a body-free `unread.json` (the SessionStart
375
- hook reads). Text lives in the packet and leaves it solely via `get_messages`.
397
+ - Message bodies persist in owner-private, mode-0600 `history.sqlite3`; file bytes persist
398
+ as immutable content-addressed blobs. The wake path stays body-free:
399
+ `notifications.log` and `unread.json` contain metadata only, and SessionStart reads no bodies.
376
400
  - This is a **Claude-Code-specific seam** (Monitor + SessionStart hook + the `watch`
377
401
  command). Other clients wire the same `notifications.log` signal to their own wake mechanism.