@opennous/cli 0.19.3 → 0.19.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @opennous/cli changelog
2
2
 
3
+ ## 0.19.4
4
+
5
+ ### Added: turn reading off, or remove a runner, from the app
6
+
7
+ - Reading turned off in Settings → AI subscription: the runner reads nothing on your plan until
8
+ it's back on, and says so once in its log (GET-1356).
9
+ - A runner removed in the app while it runs uninstalls itself on its next check-in: it stops, and
10
+ won't start at login again.
11
+
3
12
  ## 0.19.3
4
13
 
5
14
  ### Changed: history reads go faster
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/cli",
3
- "version": "0.19.3",
3
+ "version": "0.19.4",
4
4
  "description": "Ownable CLI — set up Ownable, connect it to your coding agent, and read the Context API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -664,7 +664,7 @@ const runnerCmd = program
664
664
  .option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
665
665
  .action(async (opts) => {
666
666
  const { detectAgents, installedAgents, liveRun, NO_AGENT } = await import("./backfill.js");
667
- const { runRunner, keepAwake } = await import("./runner.js");
667
+ const { runRunner, keepAwake, removeSelf } = await import("./runner.js");
668
668
  const agent = await pickAgent(opts.agent, opts.apiKey ? installedAgents : detectAgents, NO_AGENT);
669
669
  const apiKey = opts.apiKey ? savedModelKey(agent) : null;
670
670
  if (opts.apiKey && !apiKey) {
@@ -681,6 +681,12 @@ const runnerCmd = program
681
681
  agent, host, version, auth: apiKey ? "api_key" : "plan", run: liveRun({ agent, apiKey }), log: (l) => console.log(stamp(l)),
682
682
  awake: keepAwake({ log: (l) => console.log(stamp(l)) }),
683
683
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
684
+ // Removed in the app (GET-1356): the server forgets this machine, then it uninstalls itself.
685
+ onRemoved: async () => {
686
+ await api.softDelete("/v2/runner", { host }).catch(() => {});
687
+ removeSelf();
688
+ process.exit(0);
689
+ },
684
690
  api: {
685
691
  poll: (body) => api.softPost("/v2/runner/poll", body),
686
692
  answer: (id, body) => api.softPost(`/v2/runner/calls/${id}`, body),
package/src/runner.js CHANGED
@@ -123,8 +123,8 @@ export function pauseLine(agent, reason, until, now = Date.now()) {
123
123
  * `stop()` → true ends it (tests; the real one runs until the process is stopped).
124
124
  */
125
125
  export async function runRunner({ api, agent, host = hostname(), version = null, auth = "plan", run, sleep, log, now = Date.now,
126
- workers = WORKERS, limitWaitMs = LIMIT_WAIT_MS, stop = () => false, awake = null, clock = () => performance.now() }) {
127
- let limitedUntil = 0, limitReason = null, answered = 0, lastLine = null, lastError = null, backfillAt = -Infinity, sleptNoted = false;
126
+ workers = WORKERS, limitWaitMs = LIMIT_WAIT_MS, stop = () => false, awake = null, clock = () => performance.now(), onRemoved = null }) {
127
+ let limitedUntil = 0, limitReason = null, answered = 0, lastLine = null, lastError = null, backfillAt = -Infinity, sleptNoted = false, offNoted = false;
128
128
  const answerOne = async (call, models) => {
129
129
  for (let attempt = 0; ; attempt++) {
130
130
  // performance.now() stops while a Mac sleeps; the wall clock doesn't. A call that failed across
@@ -172,6 +172,22 @@ export async function runRunner({ api, agent, host = hostname(), version = null,
172
172
  await sleep(ERROR_BACKOFF_MS);
173
173
  continue;
174
174
  }
175
+ // Removed in the app (GET-1356): this machine stops for good, and won't start at login.
176
+ if (page.uninstall) {
177
+ log("Removed in Ownable. This machine stops reading and won't start at login again.");
178
+ awake?.release();
179
+ if (onRemoved) await onRemoved();
180
+ return { answered, removed: true };
181
+ }
182
+ // Reading turned off in the app (GET-1356): nothing is read on this plan until it's back on.
183
+ if (page.reading_off) {
184
+ if (!offNoted) log("Reading is off in Ownable. Nothing is read on your plan until you turn it back on.");
185
+ offNoted = true;
186
+ awake?.release();
187
+ if (now() - asked < 5000) await sleep(IDLE_MS);
188
+ continue;
189
+ }
190
+ if (offNoted) { log("Reading is on again."); offNoted = false; }
175
191
  // One command sets everything up (GET-1307): with no history read yet, the runner starts that
176
192
  // read itself, on this plan, and answers its calls like any other.
177
193
  if (page.needs_backfill && api.startBackfill && now() - backfillAt >= BACKFILL_RETRY_MS) {
@@ -311,6 +327,24 @@ export function uninstallService({ run = spawnSync, os = platform(), home = home
311
327
  return true;
312
328
  }
313
329
 
330
+ /**
331
+ * Removed in the app (GET-1356), from inside the running service: the start-at-login file goes first,
332
+ * then the service manager stops this process (so last). Returns whether a service was there.
333
+ */
334
+ export function removeSelf({ run = spawnSync, os = platform(), home = homedir() } = {}) {
335
+ const where = servicePaths(os, home);
336
+ if (!where || !existsSync(where.file)) return false;
337
+ if (where.kind === "launchd") {
338
+ rmSync(where.file, { force: true });
339
+ run("launchctl", ["remove", LABEL], { stdio: "ignore" });
340
+ } else {
341
+ run("systemctl", ["--user", "disable", "opennous-runner.service"], { stdio: "ignore" });
342
+ rmSync(where.file, { force: true });
343
+ run("systemctl", ["--user", "stop", "opennous-runner.service"], { stdio: "ignore" });
344
+ }
345
+ return true;
346
+ }
347
+
314
348
  /** Whether the service is installed, and running as far as the OS says. */
315
349
  export function serviceStatus({ run = spawnSync, os = platform(), home = homedir() } = {}) {
316
350
  const where = servicePaths(os, home);