@noobdemon/noob-cli 1.10.17 → 1.10.19
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 +9 -7
- package/src/repl.js +33 -32
- 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.19",
|
|
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
|
|
|
@@ -359,7 +359,7 @@ function memoryBlock() {
|
|
|
359
359
|
// breadcrumbs để (a) trả lời "phiên trước làm gì" mà KHÔNG cần /resume, (b) gợi
|
|
360
360
|
// ý /resume nếu user muốn tiếp tục. Bỏ qua phiên hiện tại (repl.js lọc).
|
|
361
361
|
// recentSessions: [{ id, title, turns, updatedAt }] — đã sort mới → cũ.
|
|
362
|
-
|
|
362
|
+
function recentSessionsBlock(recentSessions) {
|
|
363
363
|
if (!recentSessions || !recentSessions.length) return "";
|
|
364
364
|
const lines = [
|
|
365
365
|
"# RECENT SESSIONS IN THIS WORKSPACE (newest first)",
|
|
@@ -375,7 +375,7 @@ export function recentSessionsBlock(recentSessions) {
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
// "X ago" ngắn gọn, tiếng Việt. Dùng cho noob.md mtime + recent sessions.
|
|
378
|
-
|
|
378
|
+
function relTime(ts) {
|
|
379
379
|
if (!ts) return "—";
|
|
380
380
|
const ms = Date.now() - ts;
|
|
381
381
|
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 +426,10 @@ export function buildUserMessage(history) {
|
|
|
426
426
|
function isIncompleteResponse(text) {
|
|
427
427
|
if (!text) return false;
|
|
428
428
|
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
|
-
|
|
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
|
+
// Lưu ý: dùng anchor cuối CHUỖI (không phải /m anchor cuối DÒNG) — list bullet hợp lệ
|
|
431
|
+
// ở giữa văn bản (vd "1. xxx\n2. yyy") không được coi là incomplete.
|
|
432
|
+
if (/[A-Z]\.\s*$/.test(t) || /\(\d+\)\s*$/.test(t) || /(^|\n)\d+\.\s*$/.test(t)) return true;
|
|
431
433
|
// 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
434
|
const lastChar = t.slice(-1);
|
|
433
435
|
if (lastChar && !/[.!?:;)\]"'`#>\n]/.test(lastChar) && t.length > 50) {
|
|
@@ -435,7 +437,7 @@ function isIncompleteResponse(text) {
|
|
|
435
437
|
const lastLine = t.split("\n").pop().trim();
|
|
436
438
|
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
439
|
// 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|
|
|
440
|
+
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
441
|
}
|
|
440
442
|
return false;
|
|
441
443
|
}
|
|
@@ -445,7 +447,7 @@ function isIncompleteResponse(text) {
|
|
|
445
447
|
// contains its own ```code``` fences (e.g. a README), and the first inner fence
|
|
446
448
|
// would close the block early and break the JSON. Instead, find the ```tool (or
|
|
447
449
|
// ```json) opener and brace-match the first balanced JSON object after it.
|
|
448
|
-
|
|
450
|
+
function parseToolCall(text) {
|
|
449
451
|
for (const fence of ["tool", "json"]) {
|
|
450
452
|
const open = text.match(new RegExp("```" + fence + "[ \\t]*\\n"));
|
|
451
453
|
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) {
|
|
@@ -1368,7 +1370,6 @@ NGUYÊN TẮC:
|
|
|
1368
1370
|
// 60% (120k tokens) → nhắc nhẹ.
|
|
1369
1371
|
try {
|
|
1370
1372
|
const totalTokens = countMessages(state.history);
|
|
1371
|
-
tokenMeter.setContext(totalTokens);
|
|
1372
1373
|
const k = Math.round(totalTokens / 1000);
|
|
1373
1374
|
const pct = Math.round((totalTokens / CONTEXT_WINDOW) * 100);
|
|
1374
1375
|
// Mốc 3 (≥80% — 160k tokens): TỰ ĐỘNG compact.
|
|
@@ -1851,15 +1852,15 @@ NGUYÊN TẮC:
|
|
|
1851
1852
|
function fmtK(n) {
|
|
1852
1853
|
return n >= 1000000 ? (n / 1000000).toFixed(1) + "M" : n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n);
|
|
1853
1854
|
}
|
|
1854
|
-
function printAnswer(text, name, color) {
|
|
1855
|
-
if (!text?.trim()) return;
|
|
1856
|
-
console.log("\n" + chalk.hex(color).bold(" ● " + name));
|
|
1857
|
-
console.log(
|
|
1858
|
-
renderMarkdown(text)
|
|
1859
|
-
.split("\n")
|
|
1860
|
-
.map((l) => " " + l)
|
|
1861
|
-
.join("\n") + "\n",
|
|
1862
|
-
);
|
|
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
|
+
);
|
|
1863
1864
|
}
|
|
1864
1865
|
|
|
1865
1866
|
// In câu trả lời theo dòng token thời gian thực. Vì model emit lời + (tuỳ chọn)
|
|
@@ -1889,25 +1890,25 @@ function makeStreamPrinter(name, color) {
|
|
|
1889
1890
|
get suppressing() {
|
|
1890
1891
|
return suppress;
|
|
1891
1892
|
},
|
|
1892
|
-
push(delta) {
|
|
1893
|
-
buf += delta;
|
|
1894
|
-
if (suppress) return;
|
|
1895
|
-
const f = buf.indexOf("```tool");
|
|
1896
|
-
if (f !== -1) {
|
|
1897
|
-
write(buf.slice(printed, f));
|
|
1898
|
-
printed = buf.length;
|
|
1899
|
-
suppress = true;
|
|
1900
|
-
return;
|
|
1901
|
-
}
|
|
1902
|
-
const safeEnd = Math.max(printed, buf.length - HOLD);
|
|
1903
|
-
if (safeEnd > printed) {
|
|
1904
|
-
write(buf.slice(printed, safeEnd));
|
|
1905
|
-
printed = safeEnd;
|
|
1906
|
-
}
|
|
1907
|
-
},
|
|
1908
|
-
flush() {
|
|
1909
|
-
if (!suppress && printed < buf.length) write(buf.slice(printed));
|
|
1910
|
-
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");
|
|
1911
1912
|
},
|
|
1912
1913
|
};
|
|
1913
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