@glrs-dev/cli 2.0.0 → 2.1.0

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,13 @@
1
1
  # @glrs-dev/cli
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#51](https://github.com/iceglober/glrs/pull/51) [`c3c6be8`](https://github.com/iceglober/glrs/commit/c3c6be8fb21052275f0ff4c60ba1ed3d93d5532f) Thanks [@iceglober](https://github.com/iceglober)! - Add auto-update to the `glrs` CLI. On every invocation (rate-limited to once per hour), checks the npm registry for a newer version. If found, installs it globally via `bun add -g` and re-execs the command so the user always runs the latest version. Disable with `GLRS_AUTO_UPDATE=0`.
8
+
9
+ ## 2.0.1
10
+
3
11
  ## 2.0.0
4
12
 
5
13
  ## 1.2.0
@@ -0,0 +1,113 @@
1
+ // src/lib/auto-update.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ import { execFileSync } from "child_process";
6
+ var PACKAGE_NAME = "@glrs-dev/cli";
7
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
8
+ var REGISTRY_TIMEOUT_MS = 3e3;
9
+ function getStateDir() {
10
+ const dir = join(homedir(), ".glorious", "cli");
11
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
12
+ return dir;
13
+ }
14
+ function getStatePath() {
15
+ return join(getStateDir(), "auto-update.json");
16
+ }
17
+ function readState() {
18
+ try {
19
+ return JSON.parse(readFileSync(getStatePath(), "utf8"));
20
+ } catch {
21
+ return { lastCheckAt: 0, latestVersion: null };
22
+ }
23
+ }
24
+ function writeState(state) {
25
+ try {
26
+ writeFileSync(getStatePath(), JSON.stringify(state), "utf8");
27
+ } catch {
28
+ }
29
+ }
30
+ function getCurrentVersion() {
31
+ try {
32
+ const __dirname = import.meta.dir;
33
+ const pkgPath = join(__dirname, "..", "package.json");
34
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
35
+ return pkg.version ?? null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ async function fetchLatestVersion() {
41
+ try {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
44
+ const res = await fetch(
45
+ `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
46
+ { signal: controller.signal }
47
+ );
48
+ clearTimeout(timer);
49
+ if (!res.ok) return null;
50
+ const data = await res.json();
51
+ return data.version ?? null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ function isNewer(current, latest) {
57
+ const parse = (v) => v.split(".").map(Number);
58
+ const [cMaj, cMin, cPat] = parse(current);
59
+ const [lMaj, lMin, lPat] = parse(latest);
60
+ if (lMaj > cMaj) return true;
61
+ if (lMaj < cMaj) return false;
62
+ if (lMin > cMin) return true;
63
+ if (lMin < cMin) return false;
64
+ return lPat > cPat;
65
+ }
66
+ async function autoUpdate() {
67
+ if (process.env["GLRS_AUTO_UPDATE"] === "0") return false;
68
+ if (process.env["CI"]) return false;
69
+ if (process.env["GLRS_UPDATING"] === "1") return false;
70
+ const currentVersion = getCurrentVersion();
71
+ if (!currentVersion) return false;
72
+ const state = readState();
73
+ const now = Date.now();
74
+ if (now - state.lastCheckAt < CHECK_INTERVAL_MS) {
75
+ if (state.latestVersion && isNewer(currentVersion, state.latestVersion)) {
76
+ return doUpdate(currentVersion, state.latestVersion);
77
+ }
78
+ return false;
79
+ }
80
+ const latestVersion = await fetchLatestVersion();
81
+ writeState({ lastCheckAt: now, latestVersion });
82
+ if (!latestVersion) return false;
83
+ if (!isNewer(currentVersion, latestVersion)) return false;
84
+ return doUpdate(currentVersion, latestVersion);
85
+ }
86
+ function doUpdate(currentVersion, latestVersion) {
87
+ process.stderr.write(
88
+ `\x1B[36m[glrs]\x1B[0m Updating ${currentVersion} \u2192 ${latestVersion}...
89
+ `
90
+ );
91
+ try {
92
+ execFileSync("bun", ["add", "-g", `${PACKAGE_NAME}@${latestVersion}`], {
93
+ stdio: ["ignore", "ignore", "pipe"],
94
+ timeout: 3e4,
95
+ env: { ...process.env, GLRS_UPDATING: "1" }
96
+ });
97
+ process.stderr.write(
98
+ `\x1B[36m[glrs]\x1B[0m Updated to ${latestVersion} \u2713
99
+ `
100
+ );
101
+ return true;
102
+ } catch (err) {
103
+ process.stderr.write(
104
+ `\x1B[33m[glrs]\x1B[0m Auto-update failed (running ${currentVersion}): ${err instanceof Error ? err.message : String(err)}
105
+ `
106
+ );
107
+ return false;
108
+ }
109
+ }
110
+
111
+ export {
112
+ autoUpdate
113
+ };
package/dist/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env bun
2
+ import {
3
+ autoUpdate
4
+ } from "./chunk-SB3MLROC.js";
2
5
  import {
3
6
  HELP_TEXT,
4
7
  SUBCOMMANDS,
@@ -35,6 +38,24 @@ import "./chunk-3RG5ZIWI.js";
35
38
  import { spawn } from "child_process";
36
39
  import * as path from "path";
37
40
  import { subcommands, run } from "cmd-ts";
41
+ var updated = await autoUpdate();
42
+ if (updated) {
43
+ const child = spawn("glrs", process.argv.slice(2), {
44
+ stdio: "inherit",
45
+ env: { ...process.env, GLRS_UPDATING: "1" }
46
+ // prevent infinite loop
47
+ });
48
+ child.on("exit", (code, signal) => {
49
+ if (signal) {
50
+ process.kill(process.pid, signal);
51
+ return;
52
+ }
53
+ process.exit(code ?? 0);
54
+ });
55
+ child.on("error", () => process.exit(1));
56
+ await new Promise(() => {
57
+ });
58
+ }
38
59
  var args = process.argv.slice(2);
39
60
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
40
61
  process.stdout.write(HELP_TEXT);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Auto-update for the glrs CLI.
3
+ *
4
+ * On every invocation, checks if a newer version is available on npm.
5
+ * If yes, installs it globally and re-execs the current command so the
6
+ * user always runs the latest version.
7
+ *
8
+ * Rate-limited: checks at most once per hour (timestamp file).
9
+ * Non-blocking on network failure: if the registry is unreachable, skip silently.
10
+ * Opt-out: set GLRS_AUTO_UPDATE=0 to disable.
11
+ *
12
+ * The update installs @glrs-dev/cli@latest which pulls the harness as
13
+ * a dependency (via the fixed changesets group), so both packages update together.
14
+ */
15
+ /**
16
+ * Run the auto-update check. Call this at the top of cli.ts.
17
+ *
18
+ * Returns true if the CLI was updated and the process should re-exec.
19
+ * Returns false if no update needed or update failed.
20
+ */
21
+ declare function autoUpdate(): Promise<boolean>;
22
+
23
+ export { autoUpdate };
@@ -0,0 +1,7 @@
1
+ import {
2
+ autoUpdate
3
+ } from "../chunk-SB3MLROC.js";
4
+ import "../chunk-3RG5ZIWI.js";
5
+ export {
6
+ autoUpdate
7
+ };
@@ -22,8 +22,8 @@ import {
22
22
  flag as flag2,
23
23
  option as option3,
24
24
  optional as optional3,
25
- positional as positional2,
26
- restPositionals,
25
+ positional,
26
+ restPositionals as restPositionals2,
27
27
  string as string2,
28
28
  subcommands as subcommands2,
29
29
  run
@@ -642,7 +642,8 @@ var configureCmd = command({
642
642
  });
643
643
 
644
644
  // src/pilot/cli/scope.ts
645
- import { command as command2, positional, string } from "cmd-ts";
645
+ import { command as command2, restPositionals, string } from "cmd-ts";
646
+ import { input as input2 } from "@inquirer/prompts";
646
647
 
647
648
  // src/pilot/scope.ts
648
649
  import * as fs5 from "fs";
@@ -887,11 +888,10 @@ async function runScopePhase(opts) {
887
888
  closeDb();
888
889
  try {
889
890
  const { spawn: spawn2 } = await import("child_process");
890
- const promptPath = scopePath.replace("scope.json", ".scope-prompt.md");
891
- fs5.writeFileSync(promptPath, scoperPrompt, "utf8");
891
+ const scoperPrompt2 = buildScopePrompt({ goal, scopePath, workflowId });
892
892
  const child = spawn2(
893
893
  "opencode",
894
- ["--agent", "pilot-scoper", "--prompt", promptPath, "--directory", cwd],
894
+ ["--agent", "pilot-scoper", "--prompt", scoperPrompt2],
895
895
  {
896
896
  stdio: "inherit",
897
897
  // TUI takes over the terminal
@@ -903,10 +903,6 @@ async function runScopePhase(opts) {
903
903
  child.on("close", (code) => resolve2(code ?? 1));
904
904
  child.on("error", () => resolve2(1));
905
905
  });
906
- try {
907
- fs5.unlinkSync(promptPath);
908
- } catch {
909
- }
910
906
  if (exitCode !== 0) {
911
907
  return {
912
908
  ok: false,
@@ -999,14 +995,29 @@ var scopeCmd = command2({
999
995
  name: "scope",
1000
996
  description: "Start a new pilot workflow with interactive scoping. Produces scope.json for `pilot go`.",
1001
997
  args: {
1002
- goal: positional({
998
+ goalWords: restPositionals({
1003
999
  type: string,
1004
1000
  displayName: "goal",
1005
- description: 'What you want to build (e.g. "Add dark mode toggle to settings page")'
1001
+ description: "What you want to build (optional \u2014 will prompt if not provided)"
1006
1002
  })
1007
1003
  },
1008
- handler: async ({ goal }) => {
1004
+ handler: async ({ goalWords }) => {
1009
1005
  const cwd = process.cwd();
1006
+ let goal = goalWords.join(" ").trim();
1007
+ if (!goal) {
1008
+ if (!process.stdin.isTTY) {
1009
+ process.stderr.write("pilot scope: no goal provided and not running in a TTY.\n");
1010
+ process.stderr.write(' Usage: pilot scope "<what you want to build>"\n');
1011
+ process.exit(1);
1012
+ }
1013
+ goal = await input2({
1014
+ message: "What do you want to build?"
1015
+ });
1016
+ if (!goal.trim()) {
1017
+ process.stderr.write("pilot scope: goal cannot be empty.\n");
1018
+ process.exit(1);
1019
+ }
1020
+ }
1010
1021
  console.log(`
1011
1022
  \x1B[1mPilot v2 \u2014 Scope phase\x1B[0m`);
1012
1023
  console.log(`Goal: ${goal}
@@ -2293,7 +2304,7 @@ var planCheckCmd = command6({
2293
2304
  type: optional3(string2),
2294
2305
  description: "Structural validation; exits 1 if any item is invalid."
2295
2306
  }),
2296
- rest: restPositionals({
2307
+ rest: restPositionals2({
2297
2308
  type: string2,
2298
2309
  displayName: "plan-path",
2299
2310
  description: "Path to a plan markdown file. Required unless --run / --check is given."
@@ -2531,7 +2531,7 @@ import { join as join10 } from "path";
2531
2531
  var APP_KEY = "A-US-3617699429";
2532
2532
  var ENDPOINT = "https://us.aptabase.com/api/v0/event";
2533
2533
  var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
2534
- var PKG_VERSION = true ? "2.0.0" : "dev";
2534
+ var PKG_VERSION = true ? "2.1.0" : "dev";
2535
2535
  var DISABLED = process.env.HARNESS_OPENCODE_TELEMETRY === "0" || process.env.HARNESS_OPENCODE_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1" || process.env.CI === "true";
2536
2536
  var SESSION_ID = randomUUID();
2537
2537
  function getInstallId() {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/harness-plugin-opencode",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Unified CLI for the @glrs-dev ecosystem — OpenCode agent harness dispatch + worktree management.",
5
5
  "license": "MIT",
6
6
  "repository": {