@noobdemon/noob-cli 1.10.19 → 1.11.0
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/CHANGELOG.md +465 -0
- package/README.md +113 -27
- package/bin/noob.js +40 -27
- package/package.json +30 -2
- package/src/agent.js +223 -139
- package/src/api.js +105 -48
- package/src/config.js +11 -11
- package/src/i18n.js +171 -148
- package/src/memory.js +24 -13
- package/src/models.js +96 -46
- package/src/prompts/system.md +85 -0
- package/src/repl/complete.js +120 -0
- package/src/repl/todos.js +38 -0
- package/src/repl/ultra.js +62 -0
- package/src/repl/workflow-commands.js +238 -0
- package/src/repl.js +794 -769
- package/src/sessions.js +20 -20
- package/src/skills.js +13 -9
- package/src/subagent.js +3 -3
- package/src/tokens.js +37 -12
- package/src/tools.js +202 -121
- package/src/tui.js +240 -124
- package/src/ui.js +44 -44
- package/src/update.js +21 -21
- package/src/workflows-builtin.js +16 -14
- package/src/workflows.js +29 -27
package/src/i18n.js
CHANGED
|
@@ -1,219 +1,242 @@
|
|
|
1
1
|
// Vietnamese UI strings for noob CLI. Single language (tiếng Việt).
|
|
2
2
|
export const t = {
|
|
3
|
-
tagline:
|
|
4
|
-
ready:
|
|
5
|
-
promptYou:
|
|
6
|
-
thinking:
|
|
7
|
-
searching:
|
|
8
|
-
merging:
|
|
9
|
-
bye:
|
|
10
|
-
interrupted:
|
|
11
|
-
pressAgainToExit:
|
|
12
|
-
running:
|
|
13
|
-
denied:
|
|
3
|
+
tagline: 'trợ lý lập trình trong terminal · sức mạnh từ Noob Demon',
|
|
4
|
+
ready: 'Nhập yêu cầu của bạn, hoặc gõ /help để xem lệnh.',
|
|
5
|
+
promptYou: 'bạn ',
|
|
6
|
+
thinking: 'đang suy nghĩ',
|
|
7
|
+
searching: 'đang tìm trên web',
|
|
8
|
+
merging: 'đang tổng hợp đa mô hình',
|
|
9
|
+
bye: 'tạm biệt 👋',
|
|
10
|
+
interrupted: 'đã ngắt',
|
|
11
|
+
pressAgainToExit: 'nhấn Ctrl+C lần nữa để thoát',
|
|
12
|
+
running: 'đang chạy…',
|
|
13
|
+
denied: 'đã từ chối',
|
|
14
14
|
queued: (n, txt) => `⏎ đã xếp hàng [${n}] · gửi khi model xong: ${txt}`,
|
|
15
15
|
queueCleared: (n) => `(đã xoá ${n} tin đang xếp hàng)`,
|
|
16
|
-
steerHint:
|
|
16
|
+
steerHint:
|
|
17
|
+
'💬 Gõ + Enter bất cứ lúc nào để chèn ý cho AI giữa chừng (không ngắt task đang chạy).',
|
|
17
18
|
steerWillInject: (txt) => `💬 sẽ chèn cho AI ở bước tới: ${txt}`,
|
|
18
19
|
steerInject: (txt) => `💬 đã chèn cho AI: ${txt}`,
|
|
19
|
-
permRetry:
|
|
20
|
+
permRetry: '→ gõ y (đồng ý) · n (từ chối) · a (luôn cho phép)',
|
|
20
21
|
|
|
21
22
|
// auth
|
|
22
23
|
notLoggedIn:
|
|
23
|
-
|
|
24
|
+
'Bạn chưa đăng nhập. Chạy: noob login <api-key>\nChưa có key? Liên hệ admin để lấy key (Pro / Pro+ / Trial).',
|
|
24
25
|
loginOk: (plan) => `Đăng nhập thành công. Gói: ${plan}.`,
|
|
25
26
|
loginSaved: (p) => `Đã lưu API key vào ${p}`,
|
|
26
|
-
loggedOut:
|
|
27
|
-
needKeyArg:
|
|
27
|
+
loggedOut: 'Đã đăng xuất, xoá API key khỏi máy.',
|
|
28
|
+
needKeyArg: 'Thiếu key. Dùng: noob login <api-key>',
|
|
28
29
|
|
|
29
30
|
// usage
|
|
30
|
-
usageTitle:
|
|
31
|
-
plan:
|
|
32
|
-
status:
|
|
33
|
-
remaining:
|
|
34
|
-
used:
|
|
35
|
-
unlimited:
|
|
36
|
-
resetAt:
|
|
31
|
+
usageTitle: 'Hạn mức API key',
|
|
32
|
+
plan: 'Gói',
|
|
33
|
+
status: 'Trạng thái',
|
|
34
|
+
remaining: 'Còn lại',
|
|
35
|
+
used: 'Đã dùng',
|
|
36
|
+
unlimited: 'không giới hạn',
|
|
37
|
+
resetAt: 'Đặt lại lúc',
|
|
37
38
|
trialLeft: (n) => `${n} lượt dùng thử còn lại`,
|
|
38
39
|
windowInfo: (used, limit) => `${used}/${limit} trong cửa sổ 5 giờ`,
|
|
39
40
|
|
|
40
41
|
// errors (gateway codes → VN)
|
|
41
|
-
errMissingKey:
|
|
42
|
-
errInvalidKey:
|
|
43
|
-
errKeyDead:
|
|
44
|
-
errTrialExhausted:
|
|
45
|
-
errDisabled:
|
|
42
|
+
errMissingKey: 'Thiếu API key. Chạy: noob login <key>',
|
|
43
|
+
errInvalidKey: 'API key không hợp lệ.',
|
|
44
|
+
errKeyDead: 'API key đã bị vô hiệu hoá (dead).',
|
|
45
|
+
errTrialExhausted: 'Key dùng thử đã hết 200 lượt — key đã dead. Liên hệ admin để nâng cấp.',
|
|
46
|
+
errDisabled: 'API key đã bị khoá.',
|
|
46
47
|
errRateLimited: (reset) =>
|
|
47
|
-
`Đã hết hạn mức trong cửa sổ 5 giờ.${reset ?
|
|
48
|
-
errConn:
|
|
48
|
+
`Đã hết hạn mức trong cửa sổ 5 giờ.${reset ? ' Đặt lại lúc ' + reset + '.' : ' Thử lại sau.'}`,
|
|
49
|
+
errConn: 'Lỗi kết nối tới máy chủ.',
|
|
49
50
|
|
|
50
51
|
// help
|
|
51
|
-
helpTitle:
|
|
52
|
-
helpCommands:
|
|
53
|
-
helpTips:
|
|
54
|
-
cmdModel:
|
|
55
|
-
cmdModels:
|
|
56
|
-
cmdMerge:
|
|
57
|
-
cmdSearch:
|
|
58
|
-
cmdChat:
|
|
59
|
-
cmdYolo:
|
|
60
|
-
cmdAgent:
|
|
61
|
-
cmdTokens:
|
|
62
|
-
cmdAutoYolo:
|
|
63
|
-
cmdInit:
|
|
64
|
-
cmdKarpathy:
|
|
65
|
-
cmdFrontendDesign:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
52
|
+
helpTitle: 'noob · trợ giúp',
|
|
53
|
+
helpCommands: 'Lệnh',
|
|
54
|
+
helpTips: 'Mẹo',
|
|
55
|
+
cmdModel: '/model [tên] đổi mô hình, hoặc liệt kê tất cả',
|
|
56
|
+
cmdModels: '/models liệt kê mọi mô hình',
|
|
57
|
+
cmdMerge: '/merge bật/tắt Merge AI (tổng hợp đa mô hình)',
|
|
58
|
+
cmdSearch: '/search bật/tắt chế độ tìm web',
|
|
59
|
+
cmdChat: '/chat quay lại chế độ chat thường',
|
|
60
|
+
cmdYolo: '/yolo bật/tắt tự duyệt (hoặc nhấn Shift+Tab)',
|
|
61
|
+
cmdAgent: '/agent on|off bật/tắt agent mode (model đẻ sub-agent song song/tuần tự/phân cấp)',
|
|
62
|
+
cmdTokens: '/tokens xem số token đã dùng trong phiên',
|
|
63
|
+
cmdAutoYolo: '/auto-yolo lưu/bỏ yolo làm mặc định mỗi lần chạy (cần xác nhận)',
|
|
64
|
+
cmdInit: '/init quét dự án & tạo noob.md (tổng quan + quy ước, như Claude Code)',
|
|
65
|
+
cmdKarpathy: '/karpathy [path] rà soát code theo 4 nguyên tắc Karpathy (/kc)',
|
|
66
|
+
cmdFrontendDesign:
|
|
67
|
+
'/frontend-design <yêu cầu> thiết kế UI frontend chất lượng cao theo skill (/fd)',
|
|
68
|
+
cmdImprove: '/improve [hint] phân tích workspace & đề xuất tính năng cải thiện (/imp)',
|
|
69
|
+
cmdUltra: '/ultra <mục tiêu> tự hành: noob tự nghĩ & tự làm nhiệm vụ tới khi xong (/u)',
|
|
70
|
+
cmdWorkflow:
|
|
71
|
+
'/workflow <yêu cầu>|help|patterns|builtins|list|save|load|run|delete|rm dynamic workflow đa sub-agent (/wf, /ultracode)',
|
|
72
|
+
cmdGoal: '/goal <text>|clear đặt HARD GOAL cho phiên (chống goal drift; không arg = xem)',
|
|
73
|
+
cmdLoop: '/loop <interval> <task> chạy task lặp lại (vd /loop 10m triage); /loop stop để dừng',
|
|
74
|
+
cmdLearn: '/learn [ghi chú] chưng cất bài học của phiên vào noob.md',
|
|
75
|
+
cmdCompact: '/compact tóm tắt phiên ngay để gọn ngữ cảnh (giữ trí nhớ dài hạn)',
|
|
76
|
+
cmdMemory: '/memory xem bộ nhớ noob.md (/mem)',
|
|
77
|
+
cmdAddDir:
|
|
78
|
+
'/add-dir <path> thêm thư mục ngoài cwd vào phạm vi tool (lưu theo workspace, không arg = liệt kê)',
|
|
79
|
+
cmdClear: '/clear /new xoá ngữ cảnh (mở phiên mới, phiên cũ vẫn resume được)',
|
|
80
|
+
cmdResume: '/resume [id] tiếp tục phiên cũ (không id = chọn từ danh sách)',
|
|
81
|
+
cmdSessions: '/sessions liệt kê các phiên đã lưu',
|
|
82
|
+
cmdLogin: '/login <key> đăng nhập bằng API key',
|
|
83
|
+
cmdLogout: '/logout đăng xuất',
|
|
84
|
+
cmdUsage: '/usage xem hạn mức key còn lại',
|
|
85
|
+
cmdStatus: '/status xem mô hình, version, trạng thái yolo, thư mục',
|
|
86
|
+
cmdVersion: '/version /v xem version hiện tại + trạng thái yolo',
|
|
87
|
+
cmdExit: '/exit /quit thoát',
|
|
88
|
+
tip1: '• Mô tả việc cần làm; noob sẽ đọc/sửa file & chạy lệnh giúp bạn.',
|
|
89
|
+
tip2: '• Đang chạy vẫn gõ tiếp được — tin sẽ xếp hàng & tự gửi khi model xong.',
|
|
90
|
+
tip3: '• Shift+Tab: bật/tắt yolo nhanh. Ctrl+C 1 lần = dừng lượt, 2 lần = thoát.',
|
|
91
|
+
tip4: '• Gõ @ để gắn file (chỉ chỗ cho AI đọc). ←/→ Home/End sửa giữa dòng; ↑/↓ gọi lại lệnh cũ.',
|
|
88
92
|
|
|
89
93
|
// misc
|
|
90
|
-
yoloOn:
|
|
91
|
-
yoloOff:
|
|
94
|
+
yoloOn: '⚠ yolo BẬT — tự động duyệt mọi thao tác sửa file & chạy lệnh',
|
|
95
|
+
yoloOff: '✓ yolo TẮT — sẽ hỏi trước khi sửa file & chạy lệnh',
|
|
92
96
|
|
|
93
97
|
// add-dir: auto-prompt khi model tag folder ngoài workspace
|
|
94
98
|
outOfScopeAdded: (root) => `✓ đã thêm ${root} vào phạm vi (lưu .noob/dirs.json).`,
|
|
95
|
-
outOfScopeRejected: (root) =>
|
|
96
|
-
|
|
99
|
+
outOfScopeRejected: (root) =>
|
|
100
|
+
`đã từ chối — ${root} không nằm trong phạm vi. Model có thể dùng /add-dir để thêm sau.`,
|
|
101
|
+
addDirRemoveNeedArg: 'Thiếu path. Dùng: /add-dir remove <đường-dẫn>',
|
|
97
102
|
addDirNotInScope: (p) => `${p} không có trong phạm vi (chỉ cwd + các folder đã /add-dir).`,
|
|
98
|
-
autoYoloWarn:
|
|
103
|
+
autoYoloWarn:
|
|
104
|
+
'⚠ yolo tự duyệt MỌI thao tác (sửa file/chạy lệnh) KHÔNG hỏi. Lưu làm mặc định = mỗi lần mở noob đều bật sẵn yolo.',
|
|
99
105
|
autoYoloConfirm: "Chắc chắn lưu yolo làm mặc định? gõ 'y' để xác nhận, phím khác để huỷ › ",
|
|
100
|
-
autoYoloOn:
|
|
101
|
-
autoYoloOff:
|
|
102
|
-
autoYoloCancel:
|
|
103
|
-
mergeOn:
|
|
104
|
-
mergeOff:
|
|
105
|
-
searchOn:
|
|
106
|
-
searchOff:
|
|
107
|
-
backToChat:
|
|
108
|
-
ctxCleared:
|
|
106
|
+
autoYoloOn: '⚡ Đã LƯU yolo làm mặc định — mọi phiên sau tự bật. Gõ /auto-yolo lần nữa để tắt.',
|
|
107
|
+
autoYoloOff: '✓ Đã bỏ yolo mặc định — phiên sau sẽ KHÔNG tự bật yolo (phiên này giữ nguyên).',
|
|
108
|
+
autoYoloCancel: 'Huỷ — không thay đổi gì.',
|
|
109
|
+
mergeOn: 'Merge AI: BẬT',
|
|
110
|
+
mergeOff: 'Merge AI: TẮT',
|
|
111
|
+
searchOn: 'Tìm web: BẬT',
|
|
112
|
+
searchOff: 'Tìm web: TẮT',
|
|
113
|
+
backToChat: 'quay lại chế độ chat',
|
|
114
|
+
ctxCleared: 'đã xoá ngữ cảnh',
|
|
109
115
|
unknownCmd: (c) => `không rõ lệnh: /${c}`,
|
|
110
|
-
tryHelp:
|
|
116
|
+
tryHelp: '(gõ /help)',
|
|
111
117
|
noModelMatch: (q) => `không có mô hình khớp "${q}"`,
|
|
112
|
-
modelListHint:
|
|
113
|
-
switchTo:
|
|
118
|
+
modelListHint: '/model <tên> để chuyển',
|
|
119
|
+
switchTo: '→ đã chuyển',
|
|
114
120
|
providerRefuses: (p) =>
|
|
115
121
|
`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.`,
|
|
116
|
-
maxSteps:
|
|
117
|
-
toolDenied:
|
|
122
|
+
maxSteps: '_(đã dừng: chạm giới hạn số bước tool)_',
|
|
123
|
+
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.',
|
|
118
124
|
|
|
119
125
|
// ultra (tự hành / self-quest) + bộ nhớ noob.md
|
|
120
|
-
ultraOn:
|
|
121
|
-
ultraDone:
|
|
122
|
-
ultraStopped:
|
|
123
|
-
ultraMax:
|
|
124
|
-
ultraNeedGoal:
|
|
126
|
+
ultraOn: 'Ultra: BẬT — noob tự lập kế hoạch & tự làm tới khi xong (Ctrl+C để dừng).',
|
|
127
|
+
ultraDone: 'Ultra: đã hoàn thành mục tiêu.',
|
|
128
|
+
ultraStopped: 'Ultra: đã dừng.',
|
|
129
|
+
ultraMax: 'Ultra: chạm giới hạn số vòng — dừng để bạn kiểm tra & ra lệnh tiếp.',
|
|
130
|
+
ultraNeedGoal: 'Cần mục tiêu. Dùng: /ultra <mô tả mục tiêu>',
|
|
125
131
|
ultraQuest: (n) => `tự nghĩ nhiệm vụ kế tiếp (vòng ${n})…`,
|
|
126
|
-
loopNeedArgs:
|
|
132
|
+
loopNeedArgs:
|
|
133
|
+
'Cần task. Dùng: /loop <interval> <task> (vd: /loop 5m kiểm tra log lỗi mới) · /loop stop để dừng · /loop để xem trạng thái',
|
|
127
134
|
loopBadInterval: (s) => `interval không hợp lệ: "${s}". Dùng dạng 30s / 5m / 1h / 2h30m.`,
|
|
128
|
-
loopStarted: (interval, task) =>
|
|
135
|
+
loopStarted: (interval, task) =>
|
|
136
|
+
`🔁 Loop BẬT — chạy mỗi ${interval} (không giới hạn token): ${task}`,
|
|
129
137
|
// [GỠ BUDGET 2026-06-06] loopBadBudget + loopBudgetExceeded giữ lại để tương thích ngược nếu có code cũ gọi, không dùng nữa.
|
|
130
138
|
loopBadBudget: (s) => `(deprecated) ngân sách không còn được hỗ trợ: "${s}".`,
|
|
131
139
|
loopBudgetExceeded: (used, budget, ticks) => `(deprecated) loop không còn cap token.`,
|
|
132
|
-
loopStopped:
|
|
133
|
-
loopNotRunning:
|
|
134
|
-
loopStatus: (interval, task, ticks, nextIn) =>
|
|
140
|
+
loopStopped: '🔁 Loop đã dừng.',
|
|
141
|
+
loopNotRunning: 'Không có loop nào đang chạy.',
|
|
142
|
+
loopStatus: (interval, task, ticks, nextIn) =>
|
|
143
|
+
`🔁 Loop: chạy mỗi ${interval} · đã ${ticks} lần · lần kế trong ~${nextIn}\n task: ${task}`,
|
|
135
144
|
loopTick: (n) => `🔁 loop tick #${n}…`,
|
|
136
145
|
loopAutoStop: (n) => `Loop tự dừng sau tick #${n} — model phát <<LOOP_DONE>> (task hoàn tất).`,
|
|
137
|
-
loopAlreadyRunning:
|
|
138
|
-
learning:
|
|
139
|
-
learnSuggest: (n) =>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
loopAlreadyRunning: 'Đã có loop đang chạy. /loop stop trước khi đặt loop mới.',
|
|
147
|
+
learning: 'đang chưng cất bài học vào noob.md…',
|
|
148
|
+
learnSuggest: (n) =>
|
|
149
|
+
`💡 Phiên này có ${n} lượt. Gõ /learn trước để chưng cất bài học vào noob.md (sau khi /new thì history sẽ mất).`,
|
|
150
|
+
memoryStatus: (lines, rules, notes, ago) =>
|
|
151
|
+
`📝 noob.md: ${lines} dòng (${rules} rules, ${notes} notes) · cập nhật ${ago}`,
|
|
152
|
+
memoryMissing: '📝 noob.md: chưa có — gõ /init để tạo từ dự án.',
|
|
153
|
+
compactRunning: 'đang tóm tắt phiên để gọn ngữ cảnh…',
|
|
154
|
+
compactEmpty: 'Phiên còn trống — không có gì để tóm tắt.',
|
|
155
|
+
compactSkipped: 'Phiên còn ngắn hoặc tóm tắt thất bại — bỏ qua.',
|
|
156
|
+
compactDone: (bMsgs, aMsgs, bK, aK, pct) =>
|
|
157
|
+
`Đã tóm tắt: ${bMsgs} → ${aMsgs} tin · ${bK}k → ${aK}k chars (giảm ${pct}%).`,
|
|
158
|
+
longSession: (k) =>
|
|
159
|
+
`Phiên dài (${k}k chars). Cân nhắc /compact để gọn ngữ cảnh (giữ trí nhớ) hoặc /clear để phiên mới hoàn toàn.`,
|
|
160
|
+
veryLongSession: (k) =>
|
|
161
|
+
`⚠ Phiên RẤT dài (${k}k chars) — model có thể chậm/lú. Khuyến nghị /compact ngay, hoặc /clear nếu task đã xong.`,
|
|
148
162
|
autoCompactTrigger: (k) => `Phiên đã đạt ${k}k chars — tự động tóm tắt để giữ model chạy mượt…`,
|
|
149
|
-
autoCompactDone: (bK, aK, pct) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
autoCompactDone: (bK, aK, pct) =>
|
|
164
|
+
`✓ Auto-compact: ${bK}k → ${aK}k chars (giảm ${pct}%). Trí nhớ dài hạn đã giữ lại trong session_summary.`,
|
|
165
|
+
autoCompactFail: 'Auto-compact thất bại — bạn nên /clear hoặc /compact thủ công.',
|
|
166
|
+
initRunning: 'đang quét dự án & soạn noob.md…',
|
|
167
|
+
frontendDesignRunning: 'đang vận dụng skill frontend-design…',
|
|
168
|
+
improveRunning: 'đang khảo sát workspace & soạn đề xuất cải thiện…',
|
|
169
|
+
frontendDesignNoSkill: 'không tìm thấy skills/frontend-design/SKILL.md — skill chưa được cài.',
|
|
170
|
+
frontendDesignNeedReq:
|
|
171
|
+
'cần mô tả yêu cầu. Ví dụ: /frontend-design landing page cho app nghe nhạc lo-fi',
|
|
172
|
+
workflowRunning: 'đang chạy dynamic workflow đa sub-agent…',
|
|
173
|
+
workflowNoSkill: 'không tìm thấy skills/dynamic-workflows/SKILL.md — skill chưa được cài.',
|
|
174
|
+
workflowNeedArg: 'cần mô tả task. Ví dụ: /workflow audit toàn bộ src/ tìm lỗ hổng SQL injection',
|
|
175
|
+
workflowAgentAutoOn: 'agent mode tự bật cho /workflow (cần spawn_agent)',
|
|
176
|
+
workflowAgentAskHint:
|
|
177
|
+
'🎼 /workflow cần spawn sub-agent (spawn_agent) — agent mode hiện đang TẮT.',
|
|
178
|
+
workflowAgentAskPrompt:
|
|
179
|
+
' bật agent mode và chạy workflow? [y] có, bật & chạy / [n] huỷ (gõ /agent rồi chạy lại nếu muốn) › ',
|
|
180
|
+
workflowAgentEnabled: 'đã bật agent mode cho workflow này.',
|
|
181
|
+
workflowAgentDenied:
|
|
182
|
+
'đã huỷ /workflow — agent mode vẫn TẮT. Gõ /agent rồi chạy lại lệnh nếu muốn.',
|
|
164
183
|
// saved workflows (CRUD)
|
|
165
|
-
workflowListEmpty: (dir) =>
|
|
184
|
+
workflowListEmpty: (dir) =>
|
|
185
|
+
`Chưa có workflow đã lưu. Tạo bằng /workflow save <name> <yêu cầu>. Thư mục: ${dir}`,
|
|
166
186
|
workflowListHeader: (dir) => `Workflow đã lưu (${dir}):`,
|
|
167
|
-
workflowSaveNeedArgs:
|
|
168
|
-
workflowSaveEmptyPrompt:
|
|
169
|
-
|
|
187
|
+
workflowSaveNeedArgs: 'Cách dùng: /workflow save <name> <yêu cầu workflow>',
|
|
188
|
+
workflowSaveEmptyPrompt:
|
|
189
|
+
'Thiếu yêu cầu workflow. VD: /workflow save code-audit-security "audit src/ tìm SQL injection"',
|
|
190
|
+
workflowSaveBadName: (n) =>
|
|
191
|
+
`Tên workflow không hợp lệ: '${n}'. Chỉ chấp nhận [a-z0-9_-], bắt đầu bằng chữ/số, tối đa 64 ký tự.`,
|
|
170
192
|
workflowSaveError: (n, e) => `Không lưu được workflow '${n}': ${e}`,
|
|
171
193
|
workflowSaveOk: (n, p) => `Đã lưu workflow '${n}' → ${p}`,
|
|
172
|
-
workflowSaveAskDesc:
|
|
173
|
-
workflowSaveDescPrompt:
|
|
174
|
-
workflowSaveDescSkipped:
|
|
194
|
+
workflowSaveAskDesc: 'thêm mô tả ngắn để dễ tìm sau này? [y/n] › ',
|
|
195
|
+
workflowSaveDescPrompt: 'mô tả (1 dòng): ',
|
|
196
|
+
workflowSaveDescSkipped: '(bỏ qua description — có thể thêm sau bằng cách save lại)',
|
|
175
197
|
workflowSaveDescOk: (n, d) => `Đã thêm mô tả cho '${n}': ${d}`,
|
|
176
|
-
workflowRunNeedName:
|
|
198
|
+
workflowRunNeedName: 'Cách dùng: /workflow run <name> [thêm ngữ cảnh]',
|
|
177
199
|
workflowRunError: (n, e) => `Không nạp được workflow '${n}': ${e}`,
|
|
178
200
|
workflowRunOk: (n) => `Chạy workflow đã lưu '${n}'…`,
|
|
179
201
|
workflowRunPreviewBuiltin: (n, title) => `Built-in workflow '${n}' (${title})`,
|
|
180
202
|
workflowRunPreviewSaved: (n) => `Workflow đã lưu '${n}'`,
|
|
181
|
-
workflowLoadNeedName:
|
|
203
|
+
workflowLoadNeedName: 'Cách dùng: /workflow load <name>',
|
|
182
204
|
workflowLoadError: (n, e) => `Không nạp được workflow '${n}': ${e}`,
|
|
183
205
|
workflowLoadOk: (n, p) => `Workflow '${n}' (${p}):`,
|
|
184
|
-
workflowDeleteNeedName:
|
|
206
|
+
workflowDeleteNeedName: 'Cách dùng: /workflow delete <name>',
|
|
185
207
|
workflowDeleteError: (n, e) => `Không xoá được workflow '${n}': ${e}`,
|
|
186
208
|
workflowDeleteOk: (n) => `Đã xoá workflow '${n}'.`,
|
|
187
209
|
workflowDeleteBuiltIn: (n) => `'${n}' là built-in workflow, không xoá được.`,
|
|
188
210
|
// discoverability (v1.9.1)
|
|
189
|
-
workflowHelpTitle:
|
|
190
|
-
workflowHelpSub:
|
|
191
|
-
|
|
211
|
+
workflowHelpTitle: '🎼 /workflow — orchestrate multi-agent workflow',
|
|
212
|
+
workflowHelpSub:
|
|
213
|
+
'Workflow chia task lớn thành sub-agent chạy song song/độc lập → chống 3 failure mode của single-context: agentic laziness, self-preferential bias, goal drift.',
|
|
214
|
+
workflowPatternsTitle: '🎼 6 pattern workflow (theo article Thariq)',
|
|
192
215
|
workflowBuiltinsTitle: (n) => `🎼 Workflow built-in (${n} mẫu ship sẵn):`,
|
|
193
216
|
initOverwriteWarn: (p) => `⚠ Đã có noob.md tại ${p}. /init sẽ ghi đè nội dung hiện tại.`,
|
|
194
217
|
initOverwriteConfirm: "Ghi đè? gõ 'y' để xác nhận, phím khác để huỷ › ",
|
|
195
|
-
initCancel:
|
|
218
|
+
initCancel: 'Huỷ /init — giữ nguyên noob.md.',
|
|
196
219
|
memoryEmpty: (p) => `Chưa có noob.md. noob sẽ tự tạo ở: ${p}`,
|
|
197
220
|
memoryStat: (n) => ` · ${n} dòng / ~200`,
|
|
198
221
|
|
|
199
222
|
// sessions (lưu lịch sử + resume)
|
|
200
223
|
sessionResumed: (id) => `Đã khôi phục phiên ${id}`,
|
|
201
|
-
sessionNonePrev:
|
|
224
|
+
sessionNonePrev: 'Chưa có phiên nào trước đó — bắt đầu phiên mới.',
|
|
202
225
|
sessionNotFound: (id) => `Không tìm thấy phiên "${id}".`,
|
|
203
|
-
sessionEmpty:
|
|
204
|
-
sessionPickTitle:
|
|
205
|
-
sessionListTitle:
|
|
226
|
+
sessionEmpty: 'Chưa có phiên đã lưu nào.',
|
|
227
|
+
sessionPickTitle: 'Chọn phiên để tiếp tục:',
|
|
228
|
+
sessionListTitle: 'Các phiên đã lưu:',
|
|
206
229
|
sessionPickPrompt: (n) => `chọn phiên [1-${n}], Enter để bỏ qua › `,
|
|
207
|
-
sessionPickBad:
|
|
208
|
-
sessionResumeHint:
|
|
230
|
+
sessionPickBad: 'lựa chọn không hợp lệ.',
|
|
231
|
+
sessionResumeHint: '/resume <id> để tiếp tục một phiên · hoặc chạy: noob -c (phiên gần nhất)',
|
|
209
232
|
|
|
210
233
|
// update
|
|
211
|
-
cmdUpdate:
|
|
234
|
+
cmdUpdate: '/update cập nhật noob lên bản mới nhất',
|
|
212
235
|
updateFound: (cur, lat) => `🆕 Có bản mới ${lat} (đang dùng ${cur}) — đang tự cập nhật nền…`,
|
|
213
|
-
updateBgDone:
|
|
214
|
-
updateChecking:
|
|
236
|
+
updateBgDone: 'Đang cập nhật nền. Mở lại noob để dùng bản mới.',
|
|
237
|
+
updateChecking: 'Đang kiểm tra cập nhật…',
|
|
215
238
|
updateLatest: (cur) => `Đã ở bản mới nhất (${cur}).`,
|
|
216
|
-
updating:
|
|
217
|
-
updateOk:
|
|
218
|
-
updateFail:
|
|
239
|
+
updating: 'Đang cập nhật…',
|
|
240
|
+
updateOk: '✓ Cập nhật xong. Mở lại noob để dùng bản mới.',
|
|
241
|
+
updateFail: '✗ Cập nhật thất bại. Thử thủ công: npm i -g @noobdemon/noob-cli@latest',
|
|
219
242
|
};
|
package/src/memory.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Bộ nhớ lâu dài của noob: file `noob.md` ở thư mục gốc dự án (giống CLAUDE.md /
|
|
2
2
|
// AGENTS.md). noob TỰ tạo & TỰ cập nhật nó qua write_file/edit_file để học và
|
|
3
3
|
// nhớ giữa các phiên. Runtime chỉ ĐỌC để chèn vào prompt — không tự ghi đè.
|
|
4
|
-
import fs from
|
|
5
|
-
import path from
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
const FILE =
|
|
7
|
+
const FILE = 'noob.md';
|
|
8
8
|
|
|
9
9
|
export function memoryPath() {
|
|
10
10
|
return path.resolve(process.cwd(), FILE);
|
|
@@ -13,7 +13,7 @@ export function memoryPath() {
|
|
|
13
13
|
/** Nội dung noob.md hiện tại, hoặc null nếu chưa có / rỗng. */
|
|
14
14
|
export function loadMemory() {
|
|
15
15
|
try {
|
|
16
|
-
const txt = fs.readFileSync(memoryPath(),
|
|
16
|
+
const txt = fs.readFileSync(memoryPath(), 'utf8').trim();
|
|
17
17
|
return txt || null;
|
|
18
18
|
} catch {
|
|
19
19
|
return null;
|
|
@@ -26,25 +26,36 @@ export function loadMemory() {
|
|
|
26
26
|
export function memoryStats() {
|
|
27
27
|
let txt;
|
|
28
28
|
try {
|
|
29
|
-
txt = fs.readFileSync(memoryPath(),
|
|
29
|
+
txt = fs.readFileSync(memoryPath(), 'utf8');
|
|
30
30
|
} catch {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
if (!txt.trim()) return null;
|
|
34
|
-
const lines = txt.split(
|
|
34
|
+
const lines = txt.split('\n');
|
|
35
35
|
let rules = 0;
|
|
36
36
|
let notes = 0;
|
|
37
|
-
let inSection =
|
|
37
|
+
let inSection = '';
|
|
38
38
|
for (const l of lines) {
|
|
39
|
-
if (/^##\s+Rules\b/i.test(l)) {
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
if (/^##\s+Rules\b/i.test(l)) {
|
|
40
|
+
inSection = 'rules';
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (/^##\s+Notes\b/i.test(l)) {
|
|
44
|
+
inSection = 'notes';
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (/^##\s/.test(l)) {
|
|
48
|
+
inSection = '';
|
|
49
|
+
continue;
|
|
50
|
+
} // mục khác → reset
|
|
42
51
|
if (/^\s*[-*]\s+/.test(l)) {
|
|
43
|
-
if (inSection ===
|
|
44
|
-
else if (inSection ===
|
|
52
|
+
if (inSection === 'rules') rules++;
|
|
53
|
+
else if (inSection === 'notes') notes++;
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
let mtime = 0;
|
|
48
|
-
try {
|
|
57
|
+
try {
|
|
58
|
+
mtime = fs.statSync(memoryPath()).mtimeMs;
|
|
59
|
+
} catch {}
|
|
49
60
|
return { lines: lines.length, rules, notes, mtime, path: memoryPath() };
|
|
50
61
|
}
|