@noobdemon/noob-cli 1.0.2 → 1.0.3

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/bin/noob.js CHANGED
@@ -4,6 +4,7 @@ import { config } from "../src/config.js";
4
4
  import { usage, ApiError } from "../src/api.js";
5
5
  import { c } from "../src/ui.js";
6
6
  import { t } from "../src/i18n.js";
7
+ import { checkLatest, runUpdate, CURRENT } from "../src/update.js";
7
8
 
8
9
  const argv = process.argv.slice(2);
9
10
  const opts = { yolo: false, model: undefined, prompt: undefined };
@@ -43,6 +44,17 @@ if (sub === "login") {
43
44
  config.clearKey();
44
45
  console.log(c.ok(t.loggedOut));
45
46
  process.exit(0);
47
+ } else if (sub === "update") {
48
+ console.log(c.dim(t.updateChecking));
49
+ const v = await checkLatest({ throttle: false });
50
+ if (!v) {
51
+ console.log(c.ok(t.updateLatest(CURRENT)));
52
+ process.exit(0);
53
+ }
54
+ console.log(c.tool(t.updateFound(CURRENT, v)));
55
+ const ok = await runUpdate({ background: false });
56
+ console.log(ok ? c.ok(t.updateOk) : c.err(t.updateFail));
57
+ process.exit(ok ? 0 : 1);
46
58
  } else if (sub === "usage") {
47
59
  if (!config.apiKey) {
48
60
  console.log(c.tool(t.notLoggedIn));
@@ -73,6 +85,7 @@ Cách dùng:
73
85
  noob login <api-key> đăng nhập bằng API key
74
86
  noob logout đăng xuất
75
87
  noob usage xem hạn mức key
88
+ noob update cập nhật lên bản mới nhất
76
89
 
77
90
  Tuỳ chọn:
78
91
  -m, --model <id> chọn mô hình (vd: gateway-claude-opus-4-7)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noobdemon/noob-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/config.js CHANGED
@@ -55,6 +55,13 @@ export const config = {
55
55
  cache.model = id;
56
56
  write(cache);
57
57
  },
58
+ get(k) {
59
+ return cache[k];
60
+ },
61
+ set(k, v) {
62
+ cache[k] = v;
63
+ write(cache);
64
+ },
58
65
  get path() {
59
66
  return FILE;
60
67
  },
package/src/i18n.js CHANGED
@@ -79,4 +79,14 @@ export const t = {
79
79
  `lưu ý: mô hình ${p} trên gateway này hay từ chối giao thức tool; nên dùng Anthropic/DeepSeek cho tác vụ sửa code.`,
80
80
  maxSteps: "_(đã dừng: chạm giới hạn số bước tool)_",
81
81
  toolDenied: "Người dùng từ chối thao tác này. Hãy đổi cách làm hoặc hỏi lại.",
82
+
83
+ // update
84
+ cmdUpdate: "/update cập nhật noob lên bản mới nhất",
85
+ updateFound: (cur, lat) => `🆕 Có bản mới ${lat} (đang dùng ${cur}) — đang tự cập nhật nền…`,
86
+ updateBgDone: "Đang cập nhật nền. Mở lại noob để dùng bản mới.",
87
+ updateChecking: "Đang kiểm tra cập nhật…",
88
+ updateLatest: (cur) => `Đã ở bản mới nhất (${cur}).`,
89
+ updating: "Đang cập nhật…",
90
+ updateOk: "✓ Cập nhật xong. Mở lại noob để dùng bản mới.",
91
+ updateFail: "✗ Cập nhật thất bại. Thử thủ công: npm i -g @noobdemon/noob-cli@latest",
82
92
  };
package/src/repl.js CHANGED
@@ -9,6 +9,7 @@ import { MODELS, PROVIDERS, findModel, providerColor, DEFAULT_MODEL } from "./mo
9
9
  import { c, banner, modelBadge, renderMarkdown, box } from "./ui.js";
10
10
  import { config } from "./config.js";
11
11
  import { t } from "./i18n.js";
12
+ import { checkLatest, runUpdate, CURRENT } from "./update.js";
12
13
 
13
14
  export async function startRepl(opts = {}) {
14
15
  const state = {
@@ -92,6 +93,18 @@ export async function startRepl(opts = {}) {
92
93
  if (!config.apiKey) console.log("\n" + c.tool(" " + t.notLoggedIn) + "\n");
93
94
  else console.log(c.dim(" " + t.ready + "\n"));
94
95
 
96
+ // Auto-update: non-blocking startup check; if newer, update in the background.
97
+ if (process.env.NOOB_NO_AUTOUPDATE !== "1") {
98
+ checkLatest()
99
+ .then((v) => {
100
+ if (!v) return;
101
+ console.log(c.tool(" " + t.updateFound(CURRENT, v)));
102
+ runUpdate({ background: true });
103
+ console.log(c.dim(" " + t.updateBgDone));
104
+ })
105
+ .catch(() => {});
106
+ }
107
+
95
108
  if (opts.prompt) {
96
109
  console.log(c.user(t.promptYou) + c.dim("› ") + opts.prompt);
97
110
  await handle(opts.prompt);
@@ -247,6 +260,9 @@ export async function startRepl(opts = {}) {
247
260
  case "usage":
248
261
  await showUsage();
249
262
  break;
263
+ case "update":
264
+ await doUpdate();
265
+ break;
250
266
  case "clear":
251
267
  case "new":
252
268
  state.history = [];
@@ -293,6 +309,15 @@ export async function startRepl(opts = {}) {
293
309
  }
294
310
  }
295
311
 
312
+ async function doUpdate() {
313
+ console.log(c.dim(" " + t.updateChecking));
314
+ const v = await checkLatest({ throttle: false });
315
+ if (!v) return console.log(c.ok(" " + t.updateLatest(CURRENT)));
316
+ console.log(c.tool(" " + t.updateFound(CURRENT, v)));
317
+ const ok = await runUpdate({ background: false });
318
+ console.log(ok ? c.ok(" " + t.updateOk) : c.err(" " + t.updateFail));
319
+ }
320
+
296
321
  function selectModel(q) {
297
322
  const s = q.toLowerCase();
298
323
  const m =
@@ -371,6 +396,7 @@ function printHelp() {
371
396
  " " + t.cmdLogin,
372
397
  " " + t.cmdLogout,
373
398
  " " + t.cmdUsage,
399
+ " " + t.cmdUpdate,
374
400
  " " + t.cmdClear,
375
401
  " " + t.cmdStatus,
376
402
  " " + t.cmdExit,
package/src/update.js ADDED
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import { spawn } from "node:child_process";
3
+ import { config } from "./config.js";
4
+
5
+ const NAME = "@noobdemon/noob-cli";
6
+ const CHECK_EVERY = 6 * 3600 * 1000; // 6h throttle
7
+
8
+ export const CURRENT = (() => {
9
+ try {
10
+ return JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
11
+ } catch {
12
+ return "0.0.0";
13
+ }
14
+ })();
15
+
16
+ function cmp(a, b) {
17
+ const pa = String(a).split(".").map(Number);
18
+ const pb = String(b).split(".").map(Number);
19
+ for (let i = 0; i < 3; i++) {
20
+ if ((pa[i] || 0) > (pb[i] || 0)) return 1;
21
+ if ((pa[i] || 0) < (pb[i] || 0)) return -1;
22
+ }
23
+ return 0;
24
+ }
25
+
26
+ export async function fetchLatest(timeout = 2500) {
27
+ const ctrl = new AbortController();
28
+ const t = setTimeout(() => ctrl.abort(), timeout);
29
+ try {
30
+ const r = await fetch(`https://registry.npmjs.org/${NAME}/latest`, { signal: ctrl.signal });
31
+ if (!r.ok) return null;
32
+ const j = await r.json();
33
+ return j.version || null;
34
+ } catch {
35
+ return null;
36
+ } finally {
37
+ clearTimeout(t);
38
+ }
39
+ }
40
+
41
+ /** Returns the newer version string if an update exists (respecting the throttle), else null. */
42
+ export async function checkLatest({ throttle = true } = {}) {
43
+ if (throttle) {
44
+ const last = config.get("lastUpdateCheck") || 0;
45
+ if (Date.now() - last < CHECK_EVERY) return null;
46
+ config.set("lastUpdateCheck", Date.now());
47
+ }
48
+ const v = await fetchLatest();
49
+ return v && cmp(v, CURRENT) > 0 ? v : null;
50
+ }
51
+
52
+ /** Run `npm i -g @noobdemon/noob-cli@latest`. background=true detaches and returns immediately. */
53
+ export function runUpdate({ background = false } = {}) {
54
+ const isWin = process.platform === "win32";
55
+ const cmd = isWin ? "npm.cmd" : "npm";
56
+ const args = ["i", "-g", `${NAME}@latest`];
57
+ const env = { ...process.env };
58
+ if (process.env.NOOB_INSECURE_TLS === "1") {
59
+ env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
60
+ args.push("--strict-ssl=false");
61
+ }
62
+ if (background) {
63
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore", env, shell: isWin });
64
+ child.unref();
65
+ return Promise.resolve(true);
66
+ }
67
+ return new Promise((res) => {
68
+ const child = spawn(cmd, args, { stdio: "inherit", env, shell: isWin });
69
+ child.on("close", (code) => res(code === 0));
70
+ child.on("error", () => res(false));
71
+ });
72
+ }