@splinterzzz/ouro 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splinterzzz/ouro",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Loop engineering CLI: repo-rooted kanban + agent loop, powered by Claude Code / Codex headless mode (no API key required).",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -0,0 +1,86 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { spawn } from "node:child_process";
3
+ import chalk from "chalk";
4
+
5
+ // `ouro --upgrade` — pull the newest published ouro without making the user
6
+ // remember the package name or the -g install incantation. Read our own name
7
+ // and version out of package.json so this keeps working if the package is ever
8
+ // renamed or forked.
9
+
10
+ const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
11
+
12
+ const REGISTRY = "https://registry.npmjs.org";
13
+
14
+ async function latestVersion(name) {
15
+ // Scoped names (@scope/pkg) need the slash percent-encoded for the registry.
16
+ const res = await fetch(`${REGISTRY}/${name.replace("/", "%2f")}/latest`, {
17
+ headers: { accept: "application/json" },
18
+ signal: AbortSignal.timeout(8000),
19
+ });
20
+ if (!res.ok) throw new Error(`registry returned ${res.status}`);
21
+ return (await res.json()).version;
22
+ }
23
+
24
+ // Numeric-aware semver compare, enough for the "is latest newer than ours"
25
+ // question. Prerelease tags are ignored — the registry `latest` dist-tag never
26
+ // points at one, so we'd never see them here.
27
+ function isNewer(a, b) {
28
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
29
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
30
+ for (let i = 0; i < 3; i++) {
31
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
32
+ }
33
+ return false;
34
+ }
35
+
36
+ function runInstall(spec) {
37
+ return new Promise((resolve, reject) => {
38
+ // npm is a .cmd shim on Windows — spawn without a shell needs the real name.
39
+ const cmd = process.platform === "win32" ? "npm.cmd" : "npm";
40
+ const proc = spawn(cmd, ["install", "-g", spec], { stdio: "inherit", windowsHide: true });
41
+ proc.on("error", reject);
42
+ proc.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`npm exited with code ${code}`))));
43
+ });
44
+ }
45
+
46
+ export async function upgradeCommand() {
47
+ const name = pkg.name;
48
+ const current = pkg.version;
49
+
50
+ console.log("");
51
+ console.log(chalk.gray(` current ${current}`));
52
+
53
+ let latest;
54
+ try {
55
+ latest = await latestVersion(name);
56
+ } catch (err) {
57
+ console.log(chalk.red(` ! couldn't reach the npm registry: ${err.message}`));
58
+ console.log(chalk.gray(` Try manually: `) + chalk.cyan(`npm install -g ${name}@latest`));
59
+ console.log("");
60
+ return;
61
+ }
62
+
63
+ console.log(chalk.gray(` latest ${latest}`));
64
+
65
+ if (!isNewer(latest, current)) {
66
+ console.log("");
67
+ console.log(chalk.green(` ✓ already on the latest version`));
68
+ console.log("");
69
+ return;
70
+ }
71
+
72
+ console.log("");
73
+ console.log(chalk.cyan(` Updating ${name} → ${latest} …`));
74
+ console.log("");
75
+
76
+ try {
77
+ await runInstall(`${name}@latest`);
78
+ console.log("");
79
+ console.log(chalk.green(` ✓ upgraded to ${latest}`) + chalk.gray(` — restart running services with `) + chalk.cyan("ouro restart"));
80
+ } catch (err) {
81
+ console.log("");
82
+ console.log(chalk.red(` ! upgrade failed: ${err.message}`));
83
+ console.log(chalk.gray(` Try manually: `) + chalk.cyan(`npm install -g ${name}@latest`));
84
+ }
85
+ console.log("");
86
+ }
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ import { startCommand } from "./commands/start.js";
10
10
  import { stopCommand } from "./commands/stop.js";
11
11
  import { statusCommand } from "./commands/status.js";
12
12
  import { logsCommand } from "./commands/logs.js";
13
+ import { upgradeCommand } from "./commands/upgrade.js";
13
14
 
14
15
  const pkg = JSON.parse(
15
16
  readFileSync(new URL("../package.json", import.meta.url), "utf8")
@@ -29,7 +30,19 @@ program
29
30
  "and agents that run on your existing Claude Code / Codex subscription.\n" +
30
31
  "No API key required."
31
32
  )
32
- .version(pkg.version);
33
+ .version(pkg.version)
34
+ // Root `--upgrade` flag so `ouro --upgrade` works, mirroring `--version`.
35
+ // With subcommands present, commander only runs this root action when none
36
+ // matched — so `ouro` alone still falls through to help, and every real
37
+ // subcommand is untouched.
38
+ .option("--upgrade", "update ouro to the latest published version")
39
+ .action((opts) => (opts.upgrade ? upgradeCommand() : program.help()));
40
+
41
+ // `ouro upgrade` — the idiomatic subcommand form of the same thing.
42
+ program
43
+ .command("upgrade")
44
+ .description("Update ouro to the latest published version")
45
+ .action(upgradeCommand);
33
46
 
34
47
  program
35
48
  .command("init")