@noobdemon/noob-cli 1.7.5 → 1.7.7

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.7.5",
3
+ "version": "1.7.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "bin/",
14
14
  "src/",
15
+ "skills/",
15
16
  "README.md",
16
17
  "LICENSE"
17
18
  ],
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: frontend-design
3
+ description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
4
+ license: Complete terms in LICENSE.txt
5
+ ---
6
+
7
+ This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
8
+
9
+ The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
10
+
11
+ ## Design Thinking
12
+
13
+ Before coding, understand the context and commit to a BOLD aesthetic direction:
14
+ - **Purpose**: What problem does this interface solve? Who uses it?
15
+ - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
16
+ - **Constraints**: Technical requirements (framework, performance, accessibility).
17
+ - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
18
+
19
+ **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
20
+
21
+ Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
22
+ - Production-grade and functional
23
+ - Visually striking and memorable
24
+ - Cohesive with a clear aesthetic point-of-view
25
+ - Meticulously refined in every detail
26
+
27
+ ## Frontend Aesthetics Guidelines
28
+
29
+ Focus on:
30
+ - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
31
+ - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
32
+ - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
33
+ - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
34
+ - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
35
+
36
+ NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
37
+
38
+ Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
39
+
40
+ **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
41
+
42
+ Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
package/src/agent.js CHANGED
@@ -123,14 +123,18 @@ function runtimeContext() {
123
123
  // Lược ngữ cảnh để không vượt context khi phiên dài. KHÔNG đụng vào history thật
124
124
  // (vẫn lưu/đầy đủ để resume) — chỉ thu gọn BẢN SAO dùng cho prompt.
125
125
  // Nếu history đã có summary (do summarizeHistory ghi vào _summary), dùng làm head.
126
- function compact(history, budget) {
126
+ // Exported cho test (scripts/test-compact.js) — không dùng ngoài file này.
127
+ export function compact(history, budget) {
127
128
  const len = (m) => (m.content || "").length + 24;
128
129
  let total = history.reduce((s, m) => s + len(m), 0);
129
130
  if (total <= budget) return history;
130
131
  const out = history.map((m) => ({ ...m }));
131
132
  // (1) Rút gọn các TOOL RESULT cũ trước (giữ 5 cái gần nhất) — đây là phần phình
132
133
  // nhất (đọc file lớn) và model đã xử lý xong rồi.
133
- const toolIdx = out.map((m, i) => (m.role === "tool" ? i : -1)).filter((i) => i >= 0);
134
+ // SKIP session_summary đó bộ nhớ dài hạn, không phải tool result cồng kềnh.
135
+ const toolIdx = out
136
+ .map((m, i) => (m.role === "tool" && m.name !== "session_summary" ? i : -1))
137
+ .filter((i) => i >= 0);
134
138
  for (const i of toolIdx.slice(0, Math.max(0, toolIdx.length - 5))) {
135
139
  if (total <= budget) break;
136
140
  const before = len(out[i]);
@@ -138,11 +142,17 @@ function compact(history, budget) {
138
142
  total -= before - len(out[i]);
139
143
  }
140
144
  if (total <= budget) return out;
141
- // (2) Vẫn dài → bỏ các lượt cũ nhất, giữ USER đầu tiên ( tả nhiệm vụ gốc) +
142
- // 12 message gần nhất.
145
+ // (2) Vẫn dài → bỏ các lượt cũ nhất, giữ MỌI session_summary (bộ nhớ dài hạn
146
+ // do maybeSummarize() chèn) + USER đầu tiên (nhiệm vụ gốc) + 12 message gần
147
+ // nhất. KHÔNG BỎ session_summary — đó là bộ nhớ phiên, mất nó là mất hết.
148
+ const summaryIdx = out
149
+ .map((m, i) => (m.role === "tool" && m.name === "session_summary" ? i : -1))
150
+ .filter((i) => i >= 0);
143
151
  const firstUser = out.findIndex((m) => m.role === "user");
144
- const head = firstUser >= 0 ? [out[firstUser]] : [];
145
- const tailStart = Math.max(firstUser + 1, out.length - 12);
152
+ const headIndices = new Set([...summaryIdx, ...(firstUser >= 0 ? [firstUser] : [])]);
153
+ const head = [...headIndices].sort((a, b) => a - b).map((i) => out[i]);
154
+ const maxHeadIdx = head.length ? Math.max(...headIndices) : -1;
155
+ const tailStart = Math.max(maxHeadIdx + 1, out.length - 12);
146
156
  const elided = { role: "tool", name: "context", content: "[… các lượt trước đã được lược bớt …]" };
147
157
  return [...head, elided, ...out.slice(tailStart)];
148
158
  }
@@ -214,7 +224,8 @@ ${transcript}`;
214
224
  // tool có thật (không phải từ lời model tự kể). Chống lỗi model "tưởng đã tạo
215
225
  // file" (chỉ kể trong văn xuôi, quên gọi write_file) rồi khăng khăng "file bị
216
226
  // revert". Dựng từ history ĐẦY ĐỦ (kể cả khi đã nén) để luôn đúng thực tế.
217
- function filesLedger(history) {
227
+ // Exported cho test (scripts/test-files-ledger.js).
228
+ export function filesLedger(history) {
218
229
  const touched = new Map(); // path -> "đã ghi" | "đã sửa"
219
230
  for (const m of history) {
220
231
  if (m.role !== "tool" || typeof m.content !== "string" || m.content.startsWith("ERROR")) continue;
@@ -265,7 +276,10 @@ function buildPrompt(history, extraToolsDoc) {
265
276
  parts.push("");
266
277
  }
267
278
  parts.push("=".repeat(60));
268
- parts.push("Continue. Emit a tool block to act, or reply in Markdown if done.");
279
+ // Recency bias: câu chốt cuối prompt nằm vị trí attention mạnh nhất. Nhắc
280
+ // model đối chiếu FILES CHANGED trước khi claim đã sửa file — chống ảo giác
281
+ // "đã tạo file" khi chưa gọi write_file/edit_file.
282
+ parts.push("Continue. Emit a tool block to act, or reply in Markdown if done. Before claiming any file was created/edited, verify it appears in the FILES CHANGED list above — if not, emit the tool call now.");
269
283
  return parts.join("\n");
270
284
  }
271
285
 
package/src/skills.js CHANGED
@@ -1,43 +1,68 @@
1
- // Quản lý skills của project: mỗi skill là một thư mục `skills/<name>/` chứa
2
- // `SKILL.md` (theo quy ước Anthropic). Khi user gõ `/<skill-name>`, repl sẽ nạp
3
- // nội dung SKILL.md đó như context bổ sung rồi giao việc cho agent.
1
+ // Quản lý skills: mỗi skill là một thư mục `skills/<name>/` chứa `SKILL.md`
2
+ // (theo quy ước Anthropic). Khi user gõ `/<skill-name>`, repl nạp nội dung
3
+ // SKILL.md đó như context bổ sung rồi giao việc cho agent.
4
+ //
5
+ // Resolve theo 2 tầng:
6
+ // 1) `<cwd>/skills/<name>/SKILL.md` — skill user tự viết trong project của họ
7
+ // (override được skill mặc định, để tinh chỉnh phong cách riêng).
8
+ // 2) `<noob-cli-package>/skills/<name>/SKILL.md` — skill đóng gói theo CLI,
9
+ // đảm bảo `npm i -g @noobdemon/noob-cli` xong là `/frontend-design` chạy
10
+ // được trên MỌI máy mà không cần clone repo.
4
11
  //
5
12
  // API tối giản — không over-engineer:
6
13
  // loadSkill(name) -> string | null (nội dung SKILL.md, null nếu không có)
7
- // listSkills() -> string[] (tên skill có sẵn cwd)
8
- // skillPath(name) -> string (đường dẫn tuyệt đối SKILL.md)
14
+ // listSkills() -> string[] (tên skill có sẵn, gộp 2 nguồn)
15
+ // skillPath(name) -> string | null (đường dẫn tuyệt đối SKILL.md đã resolve)
9
16
 
10
17
  import fs from "node:fs";
11
18
  import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ // Thư mục `skills/` đi kèm package (cùng cấp với `src/`, `bin/`).
22
+ // `import.meta.url` -> .../noob-cli/src/skills.js => ../skills
23
+ const PACKAGE_SKILLS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "skills");
12
24
 
13
- function skillsDir() {
25
+ function userSkillsDir() {
14
26
  return path.join(process.cwd(), "skills");
15
27
  }
16
28
 
29
+ function candidatePaths(name) {
30
+ return [
31
+ path.join(userSkillsDir(), name, "SKILL.md"), // user override trước
32
+ path.join(PACKAGE_SKILLS_DIR, name, "SKILL.md"), // built-in của CLI
33
+ ];
34
+ }
35
+
17
36
  export function skillPath(name) {
18
- return path.join(skillsDir(), name, "SKILL.md");
37
+ for (const p of candidatePaths(name)) {
38
+ if (fs.existsSync(p)) return p;
39
+ }
40
+ return null;
19
41
  }
20
42
 
21
43
  export function loadSkill(name) {
22
44
  try {
23
45
  const p = skillPath(name);
24
- if (!fs.existsSync(p)) return null;
46
+ if (!p) return null;
25
47
  return fs.readFileSync(p, "utf8");
26
48
  } catch {
27
49
  return null;
28
50
  }
29
51
  }
30
52
 
31
- export function listSkills() {
53
+ function listIn(dir) {
32
54
  try {
33
- const dir = skillsDir();
34
55
  if (!fs.existsSync(dir)) return [];
35
56
  return fs
36
57
  .readdirSync(dir, { withFileTypes: true })
37
58
  .filter((e) => e.isDirectory() && fs.existsSync(path.join(dir, e.name, "SKILL.md")))
38
- .map((e) => e.name)
39
- .sort();
59
+ .map((e) => e.name);
40
60
  } catch {
41
61
  return [];
42
62
  }
43
63
  }
64
+
65
+ export function listSkills() {
66
+ const merged = new Set([...listIn(userSkillsDir()), ...listIn(PACKAGE_SKILLS_DIR)]);
67
+ return [...merged].sort();
68
+ }
package/src/tui.js CHANGED
@@ -163,14 +163,20 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, completer }
163
163
  function topRow() {
164
164
  if (liveOut) {
165
165
  // Khi đang stream prose mà busy, ghép meta (elapsed+token) vào cuối liveOut
166
- // để user vẫn thấy phiên đang sống không bị che status bar.
166
+ // CHỈ KHI liveOut đủ ngắn để cả dòng (liveOut + meta) chắc chắn fit trong
167
+ // 1 dòng terminal. Nếu liveOut quá dài → ưu tiên prose, bỏ meta lượt này
168
+ // (tránh terminal wrap làm dòng tạm kẹt lại trong prose vĩnh viễn — xem
169
+ // Note "token meter chèn vào prose" trong noob.md).
167
170
  if (busy && busyMeta) {
168
171
  const meta = c.dim(" · " + busyMeta);
169
- const budget = Math.max(0, cols() - visLen(meta));
170
- const head = liveOut.length > budget ? liveOut.slice(0, budget) : liveOut;
171
- return head + meta;
172
+ const metaLen = visLen(meta);
173
+ const liveLen = visLen(liveOut);
174
+ if (liveLen + metaLen <= cols()) {
175
+ return liveOut + meta;
176
+ }
177
+ // liveOut đã dài: hiện prose nguyên trạng, bỏ meta để tránh wrap.
172
178
  }
173
- return liveOut.slice(0, cols());
179
+ return liveOut.length > cols() ? liveOut.slice(0, cols()) : liveOut;
174
180
  }
175
181
  const spin = FRAMES[frame % FRAMES.length];
176
182
  // Khi busy, LUÔN hiện elapsed+tokens (busyMeta) cạnh statusText/busyLabel để