@noobdemon/noob-cli 1.10.18 → 1.10.20

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": "@noobdemon/noob-cli",
3
- "version": "1.10.18",
3
+ "version": "1.10.20",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -43,7 +43,6 @@
43
43
  "gpt-tokenizer": "^3.4.0",
44
44
  "gradient-string": "^3.0.0",
45
45
  "marked": "^15.0.12",
46
- "marked-terminal": "^7.3.0",
47
- "ora": "^8.2.0"
46
+ "marked-terminal": "^7.3.0"
48
47
  }
49
48
  }
package/src/agent.js CHANGED
@@ -5,7 +5,7 @@ import { listRoots } from "./tools.js";
5
5
  import { t } from "./i18n.js";
6
6
  import { countTokens } from "./tokens.js";
7
7
 
8
- export const SYSTEM = `You are noob, an agentic coding assistant in the spirit of Claude Code. You help with software engineering tasks by reading and editing files and running commands in the user's current working directory.
8
+ const SYSTEM = `You are noob, an agentic coding assistant in the spirit of Claude Code. You help with software engineering tasks by reading and editing files and running commands in the user's current working directory.
9
9
 
10
10
  You do NOT access anything yourself. Instead, a local runtime executes tools on your behalf: you emit a tool-call JSON block, the runtime runs it on the user's machine and replies with the result. This is fully supported — never claim you "can't access the terminal/filesystem". Just emit the tool call.
11
11
 
@@ -86,17 +86,21 @@ Follow this pattern exactly. Your very first response to a task that needs the f
86
86
  // LOW/MEDIUM = model skip thinking cho vấn đề đơn → nhanh hơn nhiều.
87
87
  const LOW_PATTERNS = [
88
88
  /^(list|ls|dir)\s/i,
89
- /^(xem|hiện|đọc|read)\s+(file|thư mục|folder)/i,
90
- /^(tìm|find|grep|search)\s+.{0,30}$/i,
91
- /^(có|is|are|was|were)\s+.+\?$/i,
89
+ /^@/, // @file reference — typically a quick read
90
+ /^(xem|hiện|show|display|liệt kê)\s/i,
91
+ /^(đọc|read)\s+\S+/i,
92
+ /^(tìm|find|grep|search)\s/i,
93
+ /^(có|is|are|was|were|what|where|how|why|who|which)\s+.+\?$/i,
94
+ /^(giải thích|explain)\s/i,
92
95
  /^(version|phiên bản)\s*\??$/i,
93
- /^(help|trợ giúp|help)\s*$/i,
96
+ /^(help|trợ giúp)\s*$/i,
94
97
  /^(cwd|thư mục hiện tại)\s*$/i,
95
98
  /^(status|trạng thái)\s*$/i,
96
99
  /^(tokens?|token)\s*$/i,
97
100
  /^(memory|noob\.md)\s*$/i,
98
101
  /^(logout|đăng xuất)\s*$/i,
99
- /^@/, // @file reference — typically a quick read
102
+ /^(hello|hi|chào|xin chào)\s*$/i,
103
+ /^(cảm ơn|cám ơn|thanks)\s*$/i,
100
104
  ];
101
105
  const MEDIUM_PATTERNS = [
102
106
  /^(edit|sửa|fix|thay đổi)\s/i,
@@ -122,20 +126,11 @@ const HIGH_PATTERNS = [
122
126
  /(test|kiểm chứng)\s+(toàn bộ|all|comprehensive|end.to.end)/i,
123
127
  /(tạo|create|write)\s+(noob\.md|SKILL|skill|workflow)/i,
124
128
  /(ghi|write)\s+.+\s+(vào|into|to)\s+.+/i, // write X into Y — multi-step
125
- /\b(ultra|goal|workflow)\b/i,
129
+ /\b(ultra|workflow)\b/i,
126
130
  ];
127
131
 
128
132
  export function classifyEffort(userMessage) {
129
- const msg = (userMessage || "").trim();
130
- if (!msg) return "medium";
131
- // Kiểm high TRƯỚC (nhiều pattern hơn, ưu tiên)
132
- for (const rx of HIGH_PATTERNS) if (rx.test(msg)) return "high";
133
- // Kiểm low TRƯỚC medium — các thao tác đọc/list đơn nên ưu tiên low
134
- for (const rx of LOW_PATTERNS) if (rx.test(msg)) return "low";
135
- // Kiểm medium
136
- for (const rx of MEDIUM_PATTERNS) if (rx.test(msg)) return "medium";
137
- // Mặc định: message dài (>200 chars) → medium, ngắn → low
138
- return msg.length > 200 ? "medium" : "low";
133
+ return "high";
139
134
  }
140
135
 
141
136
  // Số bước tool tối đa cho một lượt. Đặt rất cao theo yêu cầu người dùng: task
@@ -359,7 +354,7 @@ function memoryBlock() {
359
354
  // breadcrumbs để (a) trả lời "phiên trước làm gì" mà KHÔNG cần /resume, (b) gợi
360
355
  // ý /resume nếu user muốn tiếp tục. Bỏ qua phiên hiện tại (repl.js lọc).
361
356
  // recentSessions: [{ id, title, turns, updatedAt }] — đã sort mới → cũ.
362
- export function recentSessionsBlock(recentSessions) {
357
+ function recentSessionsBlock(recentSessions) {
363
358
  if (!recentSessions || !recentSessions.length) return "";
364
359
  const lines = [
365
360
  "# RECENT SESSIONS IN THIS WORKSPACE (newest first)",
@@ -375,7 +370,7 @@ export function recentSessionsBlock(recentSessions) {
375
370
  }
376
371
 
377
372
  // "X ago" ngắn gọn, tiếng Việt. Dùng cho noob.md mtime + recent sessions.
378
- export function relTime(ts) {
373
+ function relTime(ts) {
379
374
  if (!ts) return "—";
380
375
  const ms = Date.now() - ts;
381
376
  if (ms < 5000) return "vừa xong"; // < 5s hoặc tương lai → "vừa xong" (tránh "0s trước" xấu)
@@ -426,8 +421,10 @@ export function buildUserMessage(history) {
426
421
  function isIncompleteResponse(text) {
427
422
  if (!text) return false;
428
423
  const t = text.trimEnd();
429
- // Kết thúc bằng dấu cuối danh sách chưa đóng: "A.", "B.", "1.", "(1)" mà không có gì sau
430
- if (/[A-Z]\.\s*$/.test(t) || /\(\d+\)\s*$/.test(t) || /^\d+\.\s*$/m.test(t)) return true;
424
+ // Kết thúc bằng dấu cuối danh sách chưa đóng: "A.", "B.", "1.", "(1)" mà không có gì sau.
425
+ // Lưu ý: dùng anchor cuối CHUỖI (không phải /m anchor cuối DÒNG) list bullet hợp lệ
426
+ // ở giữa văn bản (vd "1. xxx\n2. yyy") không được coi là incomplete.
427
+ if (/[A-Z]\.\s*$/.test(t) || /\(\d+\)\s*$/.test(t) || /(^|\n)\d+\.\s*$/.test(t)) return true;
431
428
  // Kết thúc giữa câu — không có dấu câu cuối cùng (. ! ? : ; ) và không phải markdown/code
432
429
  const lastChar = t.slice(-1);
433
430
  if (lastChar && !/[.!?:;)\]"'`#>\n]/.test(lastChar) && t.length > 50) {
@@ -435,7 +432,7 @@ function isIncompleteResponse(text) {
435
432
  const lastLine = t.split("\n").pop().trim();
436
433
  if (/\s(ví|vd|hay|hoặc|và|nhưng|mà|hoac|or|and|but|e\.g|i\.e)\s*$/i.test(lastLine)) return true;
437
434
  // Dòng cuối là 1 câu bắt đầu nhưng chưa xong (có chủ ngữ nhưng không có vị ngữ hoàn chỉnh)
438
- if (/\s(Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn)\s+(muốn|có|thấy|nên|cần|đã|đang|sẽ|chọn|chọn|chọn)\s*$/i.test(lastLine)) return true;
435
+ if (/\s(Bạn|Tôi|Mình|Anh|Em|Chị|Ông|Bà|Họ)\s+(muốn|có|thấy|nên|cần|đã|đang|sẽ|chọn|định|sắp|vừa)\s*$/i.test(lastLine)) return true;
439
436
  }
440
437
  return false;
441
438
  }
@@ -445,7 +442,7 @@ function isIncompleteResponse(text) {
445
442
  // contains its own ```code``` fences (e.g. a README), and the first inner fence
446
443
  // would close the block early and break the JSON. Instead, find the ```tool (or
447
444
  // ```json) opener and brace-match the first balanced JSON object after it.
448
- export function parseToolCall(text) {
445
+ function parseToolCall(text) {
449
446
  for (const fence of ["tool", "json"]) {
450
447
  const open = text.match(new RegExp("```" + fence + "[ \\t]*\\n"));
451
448
  if (!open) continue;
package/src/repl.js CHANGED
@@ -17,7 +17,8 @@ import { checkLatest, runUpdate, CURRENT } from "./update.js";
17
17
  import * as sessions from "./sessions.js";
18
18
  import { loadSkill, listSkills } from "./skills.js";
19
19
  import { saveWorkflow, loadWorkflow, listWorkflows, deleteWorkflow, workflowsDir } from "./workflows.js";
20
- import { getBuiltinWorkflow, listBuiltinWorkflows, loadBuiltinPrompt } from "./workflows-builtin.js";
20
+ import { getBuiltinWorkflow, listBuiltinWorkflows, loadBuiltinPrompt } from "./workflows-builtin.js";
21
+
21
22
 
22
23
  // Lệnh dùng cho autocomplete. Gõ "/l" → lọc các lệnh có "l" (login, logout,
23
24
  // clear, models, yolo…); ↑/↓ chọn, Tab điền, Enter chạy mục đang sáng.
@@ -102,7 +103,7 @@ function fileMatches(frag) {
102
103
  }
103
104
 
104
105
  // Gợi ý cho thanh nhập: /lệnh (điền-rồi-gửi) hoặc @file (chỉ chèn, gõ tiếp).
105
- export function completeInput(text) {
106
+ function completeInput(text) {
106
107
  if (text.startsWith("/") && !/\s/.test(text)) {
107
108
  const q = text.slice(1).toLowerCase();
108
109
  const items = SLASH.filter((cmd) => cmd.name.slice(1).toLowerCase().includes(q));
@@ -120,7 +121,7 @@ export function completeInput(text) {
120
121
 
121
122
  // File thật được nhắc bằng @ trong tin nhắn → thêm chú thích để model đọc nhanh,
122
123
  // đúng chỗ (bỏ qua @ không trỏ tới file có thật, vd @tên người).
123
- export function mentionedFiles(text) {
124
+ function mentionedFiles(text) {
124
125
  const out = new Set();
125
126
  const re = /(?:^|\s)@([^\s]+)/g;
126
127
  let m;
@@ -316,6 +317,7 @@ export async function startRepl(opts = {}) {
316
317
  const m = findModel(s.model);
317
318
  if (m) state.model = m;
318
319
  }
320
+ if (s.tokens) tokenMeter.restore(s.tokens); // khôi phục counter cộng dồn để hiển thị nhất quán
319
321
  updateTitle();
320
322
  // Re-arm /loop nếu phiên cũ đang chạy loop (timer/running không serialize được).
321
323
  if (s.loop && s.loop.task && s.loop.intervalMs) {
@@ -1850,15 +1852,15 @@ NGUYÊN TẮC:
1850
1852
  function fmtK(n) {
1851
1853
  return n >= 1000000 ? (n / 1000000).toFixed(1) + "M" : n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n);
1852
1854
  }
1853
- function printAnswer(text, name, color) {
1854
- if (!text?.trim()) return;
1855
- console.log("\n" + chalk.hex(color).bold(" ● " + name));
1856
- console.log(
1857
- renderMarkdown(text)
1858
- .split("\n")
1859
- .map((l) => " " + l)
1860
- .join("\n") + "\n",
1861
- );
1855
+ function printAnswer(text, name, color) {
1856
+ if (!text?.trim()) return;
1857
+ console.log("\n" + chalk.hex(color).bold(" ● " + name));
1858
+ console.log(
1859
+ renderMarkdown(text)
1860
+ .split("\n")
1861
+ .map((l) => " " + l)
1862
+ .join("\n") + "\n",
1863
+ );
1862
1864
  }
1863
1865
 
1864
1866
  // In câu trả lời theo dòng token thời gian thực. Vì model emit lời + (tuỳ chọn)
@@ -1888,25 +1890,25 @@ function makeStreamPrinter(name, color) {
1888
1890
  get suppressing() {
1889
1891
  return suppress;
1890
1892
  },
1891
- push(delta) {
1892
- buf += delta;
1893
- if (suppress) return;
1894
- const f = buf.indexOf("```tool");
1895
- if (f !== -1) {
1896
- write(buf.slice(printed, f));
1897
- printed = buf.length;
1898
- suppress = true;
1899
- return;
1900
- }
1901
- const safeEnd = Math.max(printed, buf.length - HOLD);
1902
- if (safeEnd > printed) {
1903
- write(buf.slice(printed, safeEnd));
1904
- printed = safeEnd;
1905
- }
1906
- },
1907
- flush() {
1908
- if (!suppress && printed < buf.length) write(buf.slice(printed));
1909
- if (started) process.stdout.write("\n");
1893
+ push(delta) {
1894
+ buf += delta;
1895
+ if (suppress) return;
1896
+ const f = buf.indexOf("```tool");
1897
+ if (f !== -1) {
1898
+ write(buf.slice(printed, f));
1899
+ printed = buf.length;
1900
+ suppress = true;
1901
+ return;
1902
+ }
1903
+ const safeEnd = Math.max(printed, buf.length - HOLD);
1904
+ if (safeEnd > printed) {
1905
+ write(buf.slice(printed, safeEnd));
1906
+ printed = safeEnd;
1907
+ }
1908
+ },
1909
+ flush() {
1910
+ if (!suppress && printed < buf.length) write(buf.slice(printed));
1911
+ if (started) process.stdout.write("\n");
1910
1912
  },
1911
1913
  };
1912
1914
  }
package/src/sessions.js CHANGED
@@ -109,7 +109,9 @@ export function latest(cwd = null) {
109
109
  return l.length ? load(l[0].id) : null;
110
110
  }
111
111
 
112
+ // Xoá file phiên theo id. Best-effort — trả false nếu không tồn tại / không xoá được.
112
113
  export function remove(id) {
114
+ if (!id) return false;
113
115
  try {
114
116
  fs.unlinkSync(path.join(DIR, id + ".json"));
115
117
  return true;
package/src/ui.js CHANGED
@@ -8,7 +8,7 @@ import { PROVIDERS, providerColor } from "./models.js";
8
8
  import { t } from "./i18n.js";
9
9
 
10
10
  const BRAND = ["#a78bfa", "#3b82f6", "#06b6d4"];
11
- export const brand = gradient(BRAND);
11
+ const brand = gradient(BRAND);
12
12
 
13
13
  export const c = {
14
14
  dim: chalk.hex("#6b7280"),
@@ -35,7 +35,7 @@ export function banner() {
35
35
  console.log(c.dim(" ") + brand("Noob Demon") + c.dim(" · " + t.tagline + "\n"));
36
36
  }
37
37
 
38
- export function rule(label = "") {
38
+ function rule(label = "") {
39
39
  const w = Math.min(term(), 100);
40
40
  if (!label) return c.dim("─".repeat(w));
41
41
  const head = `── ${label} `;
package/src/update.js CHANGED
@@ -23,7 +23,7 @@ function cmp(a, b) {
23
23
  return 0;
24
24
  }
25
25
 
26
- export async function fetchLatest(timeout = 2500) {
26
+ async function fetchLatest(timeout = 2500) {
27
27
  const ctrl = new AbortController();
28
28
  const t = setTimeout(() => ctrl.abort(), timeout);
29
29
  try {