@ours.network/claude-code 0.18.0-nightly.2 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/hooks/runner.js +1 -1
- package/package.json +2 -2
- package/skills/ours/SKILL.md +23 -19
|
@@ -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.
|
|
6
|
+
"version": "0.18.0-nightly.3",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Adapt Toolkit"
|
|
9
9
|
},
|
package/dist/hooks/runner.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
47
|
+
"@ours.network/mcp": "0.18.0-nightly.3"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^20.14.0",
|
package/skills/ours/SKILL.md
CHANGED
|
@@ -294,21 +294,23 @@ automatically (cert- or registrar-verified introduction + key exchange) and the
|
|
|
294
294
|
delivered with it — no invite ceremony.
|
|
295
295
|
|
|
296
296
|
### Reply to a specific message
|
|
297
|
-
Every message carries a stable cross-side `wire_id`, shown by `get_messages`
|
|
297
|
+
Every message carries a stable cross-side `wire_id`, shown by `get_messages` and
|
|
298
|
+
`list_history`. To
|
|
298
299
|
answer one precisely: `send_message({ contact: "Bob", text: "…", reply_to_wire_id:
|
|
299
300
|
"<wire_id>" })`, optionally `reply_to_sentence: <n>` (1-based) to point at a sentence. The
|
|
300
301
|
recipient sees `↳re <wire_id>·s<n>`. It's a lightweight reference, not a thread object.
|
|
301
302
|
|
|
302
303
|
### Check / read messages
|
|
303
|
-
- "check messages" / "any new messages" → `get_messages()` returns the
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
-
|
|
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.
|
|
312
314
|
- On a fresh session the **SessionStart hook** injects a one-time, **body-free** summary of
|
|
313
315
|
any unread backlog (per identity: sender + id only). Surface it; if the user wants the
|
|
314
316
|
mail, `choose_identity` the relevant one and `get_messages()`.
|
|
@@ -324,10 +326,13 @@ tools, a separate store. To caption a file, also `send_message`.
|
|
|
324
326
|
- "show received files" → `list_incoming_files()` — structured metadata only: authenticated
|
|
325
327
|
sender CID in `from.id`, untrusted display label in `from.name`, file/wire IDs, filename,
|
|
326
328
|
MIME, size, date and status; no bytes and no status change. Authorize by CID, not name.
|
|
327
|
-
- "get approved files" → `get_files({ wire_ids: ["<approved 64-hex id>"] })`
|
|
328
|
-
unread
|
|
329
|
-
|
|
330
|
-
`
|
|
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.
|
|
331
336
|
- Voice records also carry structured transcription configuration/attempt/status, provider,
|
|
332
337
|
transcript or categorized fallback, and their audio-path association; prose remains intact.
|
|
333
338
|
- The wake signal stays **body-free** but carries authenticated sender CID, file/wire IDs,
|
|
@@ -383,15 +388,14 @@ release, and do not improvise a substitute. Per-identity wake-on-mail is a **dif
|
|
|
383
388
|
feature and still works; it is described above.
|
|
384
389
|
## Notes
|
|
385
390
|
|
|
386
|
-
- Identities and their state (contacts,
|
|
391
|
+
- Identities and their state (contacts, history, keys) persist under the daemon's state dir
|
|
387
392
|
(`OURS_STATE_DIR`, default `~/.ours`) and survive restarts. The daemon is a singleton
|
|
388
393
|
shared by all your Claude Code sessions.
|
|
389
394
|
- Inbound messages from unknown (non-contact) senders are rejected — only peers added via an
|
|
390
395
|
invite handshake, same-host agents under the same Human identity, or registrar-verified
|
|
391
396
|
local-contact-book introductions can reach you.
|
|
392
|
-
- Message
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
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.
|
|
396
400
|
- This is a **Claude-Code-specific seam** (Monitor + SessionStart hook + the `watch`
|
|
397
401
|
command). Other clients wire the same `notifications.log` signal to their own wake mechanism.
|