@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 +2 -1
- package/skills/frontend-design/SKILL.md +42 -0
- package/src/agent.js +22 -8
- package/src/skills.js +37 -12
- package/src/tui.js +11 -5
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
134
|
+
// SKIP session_summary — đó là 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ữ
|
|
142
|
-
// 12 message gần
|
|
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
|
|
145
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
|
8
|
-
// skillPath(name) -> string
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
170
|
-
const
|
|
171
|
-
|
|
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 để
|