@ours.network/cli 1.0.1 → 2.0.2

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 CHANGED
@@ -61,10 +61,11 @@ ours daemon stop
61
61
  ours daemon restart
62
62
  ```
63
63
 
64
- `stop` and `restart` signal a process only when all three facts agree: the
65
- CLI-owned PID record, the PID reported by the selected daemon, and its expected
66
- state directory/port. A daemon started by ours-mcp, a container, systemd, or any
67
- other launcher remains fully attachable, but these commands refuse to signal it.
64
+ `stop` and `restart` control the selected daemon regardless of which CLI command
65
+ or service launcher originally started it. The endpoint must identify itself as
66
+ an ours daemon for the exact selected state directory; once that validation
67
+ passes, the daemon-reported PID is authoritative. PID files are compatibility
68
+ metadata and never an authorization or ownership gate.
68
69
  `start`, `serve`, and `restart` reject `--endpoint` because they must launch a
69
70
  locally selected daemon; this rejection happens before `restart` probes or stops
70
71
  anything.
@@ -114,9 +115,9 @@ identity create | create-root | create-temporary | close-temporary
114
115
  profile set-bio | set-persona
115
116
  invite create | list | revoke | accept
116
117
  contact list | local | policy | remove | rename | respond
117
- message send | list | get | defer
118
- file send | list | get | fetch | defer
119
- conversation show | receipts | mark-read | policy
118
+ message send | list | get
119
+ file send | list | get | fetch
120
+ history list | get | files | file-info
120
121
  daemon info | identities | unread | watch
121
122
  ```
122
123
 
@@ -128,13 +129,25 @@ ours profile set-bio --identity BuildBot --bio= # clear the bio
128
129
  ours profile set-persona --identity BuildBot --persona= # clear the persona
129
130
  ours message send --identity BuildBot --contact Peer --text "done" --json
130
131
  ours file get --identity BuildBot --wire-ids ID1,ID2 --json
131
- ours conversation policy --identity Human --keep-history true
132
+ ours history list --identity Human --limit 25 --json
133
+ ours history files --identity Human --peer-cid PEER_CID --json
132
134
  ```
133
135
 
134
136
  An explicit file retrieval accepts 1–32 unique 64-hex wire IDs. `daemon watch`
135
137
  first binds its `--identity` without force, changing the CLI lease; it can fail
136
138
  with `BOUND_ELSEWHERE` when another live session holds that identity.
137
139
 
140
+ Message and file payloads live in identity-scoped SQLite/blob storage, not in
141
+ the MUFL packet. `message get` and `file get` consume only the selected unread
142
+ rows; the `history` group provides the persistent read-only view. There is no
143
+ defer, conversation-policy, or manual mark-read command.
144
+
145
+ This storage epoch requires a clean reset. Old daemon state is not migrated:
146
+ startup refuses it without modifying it. Stop applications, optionally copy the
147
+ old state as a manual backup, remove it yourself, and start clean. Old
148
+ identities, contacts, invites, pending payloads, and history are not carried
149
+ forward, and the installer never removes state automatically.
150
+
138
151
  Malformed JSON, unknown input fields, invalid booleans/integers, and incomplete
139
152
  file inputs fail closed before the request. Destructive identity/contact/invite
140
153
  operations require `--yes`.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bootWrapper,
3
3
  wrapperBooted
4
- } from "./chunk-MHVYJY3H.js";
4
+ } from "./chunk-IL724CY2.js";
5
5
  import "./chunk-FXKFSNKS.js";
6
6
  export {
7
7
  bootWrapper,
@@ -9,6 +9,7 @@ import { spawn as nodeSpawn } from "node:child_process";
9
9
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
10
10
  import { join, resolve } from "node:path";
11
11
  var MANAGED_PID_FILE = "ours-cli-daemon.json";
12
+ var LEGACY_MANAGED_PID_FILE = "daemon.pid";
12
13
  var DAEMON_LOG_FILE = "ours-cli-daemon.log";
13
14
  var depsDefault = {
14
15
  fetch: globalThis.fetch,
@@ -19,6 +20,17 @@ var depsDefault = {
19
20
  cliPath: process.argv[1]
20
21
  };
21
22
  var pidPath = (stateDir) => join(stateDir, MANAGED_PID_FILE);
23
+ var legacyPidPath = (stateDir) => join(stateDir, LEGACY_MANAGED_PID_FILE);
24
+ function readLegacyManagedPid(stateDir) {
25
+ try {
26
+ const raw = readFileSync(legacyPidPath(stateDir), "utf8").trim();
27
+ if (!/^[0-9]+$/.test(raw)) return null;
28
+ const pid = Number(raw);
29
+ return Number.isSafeInteger(pid) && pid > 1 ? pid : null;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
22
34
  function readManagedRecord(stateDir) {
23
35
  try {
24
36
  const value = JSON.parse(readFileSync(pidPath(stateDir), "utf8"));
@@ -49,7 +61,9 @@ function writeManagedRecord(stateDir, port) {
49
61
  async function inspectDaemon(config, deps = depsDefault) {
50
62
  const stateDir = resolve(config.expectStateDir);
51
63
  const record = readManagedRecord(stateDir);
52
- const base = { stateDir, pidFile: pidPath(stateDir), stalePidFile: record !== null };
64
+ const legacyPid = readLegacyManagedPid(stateDir);
65
+ const hasOwnershipRecord = record !== null || legacyPid !== null;
66
+ const base = { stateDir, pidFile: pidPath(stateDir), stalePidFile: hasOwnershipRecord };
53
67
  let client;
54
68
  try {
55
69
  client = await attachClient(config, { fetch: deps.fetch, leaseToken: "ours-cli-status" });
@@ -71,10 +85,7 @@ async function inspectDaemon(config, deps = depsDefault) {
71
85
  if (info.name !== "ours" || !Number.isInteger(info.pid) || info.pid <= 1 || resolve(info.stateDir) !== stateDir) {
72
86
  throw new Error("the selected endpoint did not return a valid ours daemon identity");
73
87
  }
74
- const selectedUrl = new URL(config.baseUrl.value);
75
- const selectedPort = Number(selectedUrl.port || (selectedUrl.protocol === "https:" ? 443 : 80));
76
- const managed = record !== null && record.pid === info.pid && resolve(record.stateDir) === stateDir && record.port === selectedPort;
77
- return { state: "running", managed, info, ...base, stalePidFile: record !== null && !managed };
88
+ return { state: "running", managed: true, info, ...base, stalePidFile: false };
78
89
  }
79
90
  function childEnvironment(selection) {
80
91
  const env = { ...process.env };
@@ -87,7 +98,7 @@ function childEnvironment(selection) {
87
98
  async function serveDaemon(selection, managed = false) {
88
99
  if (selection.endpoint !== void 0) throw new Error("--endpoint selects a running daemon and cannot be used with daemon serve");
89
100
  Object.assign(process.env, childEnvironment(selection));
90
- const { startDaemon } = await import("./daemon-UUG6P6GK.js");
101
+ const { startDaemon } = await import("./daemon-A2L6UMGZ.js");
91
102
  const handle = await startDaemon();
92
103
  if (managed) {
93
104
  const response = await fetch(`http://127.0.0.1:${handle.port}/state-dir`);
@@ -125,7 +136,7 @@ async function startDaemonManaged(selection, deps = depsDefault) {
125
136
  while (deps.now() < deadline) {
126
137
  await deps.delay(100);
127
138
  const status = await inspectDaemon(config, deps);
128
- if (status.state === "running" && status.managed && status.info?.pid === child.pid) return status;
139
+ if (status.state === "running" && status.info?.pid === child.pid) return status;
129
140
  try {
130
141
  deps.kill(child.pid, 0);
131
142
  } catch {
@@ -146,6 +157,12 @@ function removeOwnedPidFile(status) {
146
157
  } catch {
147
158
  }
148
159
  }
160
+ if (readLegacyManagedPid(status.stateDir) !== null) {
161
+ try {
162
+ unlinkSync(legacyPidPath(status.stateDir));
163
+ } catch {
164
+ }
165
+ }
149
166
  }
150
167
  async function stopDaemonManaged(config, deps = depsDefault) {
151
168
  const status = await inspectDaemon(config, deps);
@@ -153,10 +170,8 @@ async function stopDaemonManaged(config, deps = depsDefault) {
153
170
  removeOwnedPidFile(status);
154
171
  return status;
155
172
  }
156
- if (!status.managed || !status.info) {
157
- throw new Error("the daemon is running but was not started by @ours.network/cli; refusing to signal an external shared daemon");
158
- }
159
- deps.kill(status.info.pid, "SIGTERM");
173
+ const pid = status.info.pid;
174
+ deps.kill(pid, "SIGTERM");
160
175
  const deadline = deps.now() + 1e4;
161
176
  while (deps.now() < deadline) {
162
177
  await deps.delay(100);
@@ -166,12 +181,14 @@ async function stopDaemonManaged(config, deps = depsDefault) {
166
181
  return { ...current, stalePidFile: false };
167
182
  }
168
183
  }
169
- throw new Error(`daemon pid ${status.info.pid} did not stop within 10 seconds`);
184
+ throw new Error(`daemon pid ${pid} did not stop within 10 seconds`);
170
185
  }
171
186
 
172
187
  export {
173
188
  MANAGED_PID_FILE,
189
+ LEGACY_MANAGED_PID_FILE,
174
190
  DAEMON_LOG_FILE,
191
+ readLegacyManagedPid,
175
192
  readManagedRecord,
176
193
  writeManagedRecord,
177
194
  inspectDaemon,