nebula-ai 0.0.15 → 0.0.16

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.
Files changed (2) hide show
  1. package/bin/nebula-ai.js +32 -68
  2. package/package.json +1 -1
package/bin/nebula-ai.js CHANGED
@@ -46,6 +46,10 @@ import { homedir } from "node:os";
46
46
  import { dirname, join } from "node:path";
47
47
  import { pipeline } from "node:stream/promises";
48
48
  import { fileURLToPath } from "node:url";
49
+ import { detectInstallMethod, getChannel, getUpdateCheckCachePath, getUpgradeCommand, isNewerVersion, isNpx, isUpdateCheckDisabled, UPDATE_CHECK_TTL_MS, } from "./update-check-lib.js";
50
+ // Re-export the pure update-check helpers so older tests / downstream
51
+ // importers that still reach for `./launcher.js` keep working.
52
+ export { detectInstallMethod, getChannel, getUpdateCheckCachePath, getUpgradeCommand, isNewerVersion, isNpx, isUpdateCheckDisabled, } from "./update-check-lib.js";
49
53
  // The public half of the ed25519 key that signs `checksums.json`. The
50
54
  // private half lives only in the `NEBULA_AI_SIGNING_PRIVATE_KEY` GitHub
51
55
  // Actions secret.
@@ -66,68 +70,11 @@ const SUPPORTED_PLATFORMS = new Set([
66
70
  "linux-arm64",
67
71
  "win32-x64",
68
72
  ]);
69
- // ─── update-check constants ────────────────────────────────────────────────
70
- const UPDATE_CHECK_TTL = 4 * 60 * 60 * 1000; // 4 hours
71
73
  // ─── pure helpers (exported for tests) ─────────────────────────────────────
72
- /**
73
- * Return the path to the update-check cache file. Lives at the top of the
74
- * cache directory (NOT under a version-specific subdirectory) so it persists
75
- * across version upgrades.
76
- */
77
- export function getUpdateCheckCachePath() {
78
- const base = process.env.NEBULA_AI_CACHE_DIR ||
79
- (process.platform === "win32"
80
- ? process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local")
81
- : process.env.XDG_CACHE_HOME || join(homedir(), ".cache"));
82
- return join(base, "nebula-ai", "update-check.json");
83
- }
84
- /**
85
- * Determine the release channel from a version string.
86
- * Dev versions look like `x.y.z-dev.YYYYMMDDHHMMSS.sha`.
87
- */
88
- export function getChannel(version) {
89
- return version.includes("-dev.") ? "dev" : "prod";
90
- }
91
- /**
92
- * Return true if `latest` is a newer semver than `current` (prod), or simply
93
- * different (dev — timestamps are monotonically increasing).
94
- */
95
- export function isNewerVersion(latest, current) {
96
- if (current.includes("-dev.")) {
97
- return latest !== current;
98
- }
99
- // Strip any prerelease suffix (e.g., "-beta.1") before parsing.
100
- const clean = (v) => v.replace(/-.*$/, "");
101
- const [a1, a2, a3] = clean(latest).split(".").map(Number);
102
- const [b1, b2, b3] = clean(current).split(".").map(Number);
103
- if (a1 !== b1)
104
- return a1 > b1;
105
- if (a2 !== b2)
106
- return a2 > b2;
107
- if (a3 !== b3)
108
- return a3 > b3;
109
- // Same numeric version: a stable release is newer than a prerelease
110
- // (e.g. 1.0.0 is newer than 1.0.0-beta.1).
111
- if (current.includes("-") && !latest.includes("-"))
112
- return true;
113
- return false;
114
- }
115
- /**
116
- * Return true when the update check should be suppressed entirely.
117
- */
118
- export function isUpdateCheckDisabled() {
119
- return (process.env.NEBULA_AI_NO_UPDATE_CHECK === "1" ||
120
- process.env.NEBULA_AI_NO_UPDATE_CHECK === "true" ||
121
- process.env.CI === "true" ||
122
- !process.stderr.isTTY);
123
- }
124
- /**
125
- * Return true when running under `npx` (which always fetches the latest
126
- * wrapper, making the check redundant).
127
- */
128
- export function isNpx() {
129
- return process.env.npm_command === "exec";
130
- }
74
+ //
75
+ // Version/update-check helpers live in ./update-check-lib.ts and are
76
+ // re-exported above. Supply-chain helpers remain here because they import
77
+ // node:crypto and aren't used outside the launcher.
131
78
  export function sha256HexSync(buffer) {
132
79
  return createHash("sha256").update(buffer).digest("hex");
133
80
  }
@@ -288,7 +235,7 @@ function maybeSpawnUpdateCheck(currentVersion) {
288
235
  const cachePath = getUpdateCheckCachePath();
289
236
  // Skip if the cache is still fresh (stat is cheaper than read + parse).
290
237
  try {
291
- if (Date.now() - statSync(cachePath).mtimeMs < UPDATE_CHECK_TTL)
238
+ if (Date.now() - statSync(cachePath).mtimeMs < UPDATE_CHECK_TTL_MS)
292
239
  return;
293
240
  }
294
241
  catch {
@@ -331,16 +278,22 @@ function maybeSpawnUpdateCheck(currentVersion) {
331
278
  /**
332
279
  * Read the cached check result (written by a *previous* invocation's
333
280
  * background process) and print a one-line update notification to stderr
334
- * if a newer version is available.
281
+ * if a newer version is available. The suggested upgrade command is
282
+ * chosen based on the detected install method so pnpm/yarn/bun/brew users
283
+ * see an actually-applicable command.
335
284
  */
336
- function maybeShowUpdateNotification(currentVersion) {
285
+ function maybeShowUpdateNotification(currentVersion, installMethod) {
337
286
  try {
338
287
  const raw = readFileSync(getUpdateCheckCachePath(), "utf8");
339
288
  const cached = JSON.parse(raw);
340
289
  if (cached.latest && isNewerVersion(cached.latest, currentVersion)) {
341
- const cmd = currentVersion.includes("-dev.")
342
- ? "npm install -g nebula-ai@dev"
343
- : "npm install -g nebula-ai";
290
+ const channel = getChannel(currentVersion);
291
+ // Fall back to the canonical npm command when the install method
292
+ // is unknown — matches the wrapper's install-time docs.
293
+ const cmd = getUpgradeCommand(installMethod, channel) ||
294
+ (channel === "dev"
295
+ ? "npm install -g nebula-ai@dev"
296
+ : "npm install -g nebula-ai");
344
297
  process.stderr.write(`\nnebula-ai: update available ${currentVersion} \u2192 ${cached.latest}\n` +
345
298
  ` Run: ${cmd}\n`);
346
299
  }
@@ -404,12 +357,23 @@ async function main() {
404
357
  if (!isUpdateCheckDisabled() && !isNpx()) {
405
358
  maybeSpawnUpdateCheck(version);
406
359
  }
360
+ // Figure out how the user installed us (npm/pnpm/yarn/bun/npx/...) from
361
+ // the wrapper's own launch path + env. We forward the result to the Bun
362
+ // binary via NEBULA_AI_INSTALL_METHOD so the in-TUI "What's new" banner
363
+ // can surface the right upgrade command without redoing the detection.
364
+ const installMethod = detectInstallMethod({
365
+ execPath: fileURLToPath(import.meta.url),
366
+ argv0: process.argv[0],
367
+ npmCommand: process.env.npm_command,
368
+ npmUserAgent: process.env.npm_config_user_agent,
369
+ });
407
370
  const result = spawnSync(binaryPath, process.argv.slice(2), {
408
371
  stdio: "inherit",
372
+ env: { ...process.env, NEBULA_AI_INSTALL_METHOD: installMethod },
409
373
  });
410
374
  // Show update notification (reads PREVIOUS check's cached result).
411
375
  if (!isUpdateCheckDisabled() && !isNpx()) {
412
- maybeShowUpdateNotification(version);
376
+ maybeShowUpdateNotification(version, installMethod);
413
377
  }
414
378
  if (result.error) {
415
379
  console.error(`nebula-ai: unable to start the application.\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nebula-ai",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "CLI/TUI for the nebula.gg AI task orchestration platform",
5
5
  "type": "module",
6
6
  "bin": {