@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 +2 -3
- package/src/agent.js +20 -23
- package/src/repl.js +33 -31
- package/src/sessions.js +2 -0
- package/src/ui.js +2 -2
- package/src/update.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noobdemon/noob-cli",
|
|
3
|
-
"version": "1.10.
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
/^(
|
|
91
|
-
/^(
|
|
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
|
|
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
|
-
|
|
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|
|
|
129
|
+
/\b(ultra|workflow)\b/i,
|
|
126
130
|
];
|
|
127
131
|
|
|
128
132
|
export function classifyEffort(userMessage) {
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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