@ridit/milo 0.1.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/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/index.mjs +106603 -0
- package/package.json +64 -0
- package/src/commands/clear.ts +18 -0
- package/src/commands/crimes.ts +48 -0
- package/src/commands/feed.ts +20 -0
- package/src/commands/genz.ts +33 -0
- package/src/commands/help.ts +25 -0
- package/src/commands/init.ts +65 -0
- package/src/commands/mode.ts +22 -0
- package/src/commands/pet.ts +35 -0
- package/src/commands/provider.ts +46 -0
- package/src/commands/roast.ts +40 -0
- package/src/commands/vibe.ts +42 -0
- package/src/commands.ts +43 -0
- package/src/components/AsciiLogo.tsx +25 -0
- package/src/components/CommandSuggestions.tsx +78 -0
- package/src/components/Header.tsx +68 -0
- package/src/components/HighlightedCode.tsx +23 -0
- package/src/components/Message.tsx +43 -0
- package/src/components/ProviderWizard.tsx +278 -0
- package/src/components/Spinner.tsx +76 -0
- package/src/components/StatusBar.tsx +85 -0
- package/src/components/StructuredDiff.tsx +194 -0
- package/src/components/TextInput.tsx +144 -0
- package/src/components/messages/AssistantMessage.tsx +68 -0
- package/src/components/messages/ToolCallMessage.tsx +77 -0
- package/src/components/messages/ToolResultMessage.tsx +181 -0
- package/src/components/messages/UserMessage.tsx +32 -0
- package/src/components/permissions/PermissionCard.tsx +152 -0
- package/src/history.ts +27 -0
- package/src/hooks/useArrowKeyHistory.ts +0 -0
- package/src/hooks/useChat.ts +271 -0
- package/src/hooks/useDoublePress.ts +35 -0
- package/src/hooks/useTerminalSize.ts +24 -0
- package/src/hooks/useTextInput.ts +263 -0
- package/src/icons.ts +31 -0
- package/src/index.tsx +5 -0
- package/src/multi-agent/agent/agent.ts +33 -0
- package/src/multi-agent/orchestrator/orchestrator.ts +103 -0
- package/src/multi-agent/schemas.ts +12 -0
- package/src/multi-agent/types.ts +8 -0
- package/src/permissions.ts +54 -0
- package/src/pet.ts +239 -0
- package/src/screens/REPL.tsx +261 -0
- package/src/shortcuts.ts +37 -0
- package/src/skills/backend.ts +76 -0
- package/src/skills/cicd.ts +57 -0
- package/src/skills/colors.ts +72 -0
- package/src/skills/database.ts +55 -0
- package/src/skills/docker.ts +74 -0
- package/src/skills/frontend.ts +70 -0
- package/src/skills/git.ts +52 -0
- package/src/skills/testing.ts +73 -0
- package/src/skills/typography.ts +57 -0
- package/src/skills/uiux.ts +43 -0
- package/src/tools/AgentTool/prompt.ts +17 -0
- package/src/tools/AgentTool/tool.ts +22 -0
- package/src/tools/BashTool/prompt.ts +82 -0
- package/src/tools/BashTool/tool.ts +54 -0
- package/src/tools/FileEditTool/prompt.ts +13 -0
- package/src/tools/FileEditTool/tool.ts +39 -0
- package/src/tools/FileReadTool/prompt.ts +5 -0
- package/src/tools/FileReadTool/tool.ts +34 -0
- package/src/tools/FileWriteTool/prompt.ts +19 -0
- package/src/tools/FileWriteTool/tool.ts +34 -0
- package/src/tools/GlobTool/prompt.ts +11 -0
- package/src/tools/GlobTool/tool.ts +34 -0
- package/src/tools/GrepTool/prompt.ts +13 -0
- package/src/tools/GrepTool/tool.ts +41 -0
- package/src/tools/MemoryEditTool/prompt.ts +10 -0
- package/src/tools/MemoryEditTool/tool.ts +38 -0
- package/src/tools/MemoryReadTool/prompt.ts +9 -0
- package/src/tools/MemoryReadTool/tool.ts +47 -0
- package/src/tools/MemoryWriteTool/prompt.ts +10 -0
- package/src/tools/MemoryWriteTool/tool.ts +30 -0
- package/src/tools/OrchestratorTool/prompt.ts +26 -0
- package/src/tools/OrchestratorTool/tool.ts +20 -0
- package/src/tools/RecallTool/prompt.ts +13 -0
- package/src/tools/RecallTool/tool.ts +47 -0
- package/src/tools/ThinkTool/tool.ts +16 -0
- package/src/tools/WebFetchTool/prompt.ts +7 -0
- package/src/tools/WebFetchTool/tool.ts +33 -0
- package/src/tools/WebSearchTool/prompt.ts +8 -0
- package/src/tools/WebSearchTool/tool.ts +49 -0
- package/src/types.ts +124 -0
- package/src/utils/Cursor.ts +423 -0
- package/src/utils/PersistentShell.ts +306 -0
- package/src/utils/agent.ts +21 -0
- package/src/utils/chat.ts +21 -0
- package/src/utils/compaction.ts +71 -0
- package/src/utils/env.ts +11 -0
- package/src/utils/file.ts +42 -0
- package/src/utils/format.ts +46 -0
- package/src/utils/imagePaste.ts +78 -0
- package/src/utils/json.ts +10 -0
- package/src/utils/llm.ts +65 -0
- package/src/utils/markdown.ts +258 -0
- package/src/utils/messages.ts +81 -0
- package/src/utils/model.ts +16 -0
- package/src/utils/plan.ts +26 -0
- package/src/utils/providers.ts +100 -0
- package/src/utils/ripgrep.ts +175 -0
- package/src/utils/session.ts +100 -0
- package/src/utils/skills.ts +26 -0
- package/src/utils/systemPrompt.ts +218 -0
- package/src/utils/theme.ts +110 -0
- package/src/utils/tools.ts +58 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const COLORS_SKILL = `
|
|
2
|
+
## Skill: Colors & Dark Mode
|
|
3
|
+
|
|
4
|
+
### Color System
|
|
5
|
+
- Use HSL for manipulation — easier to reason about:
|
|
6
|
+
\`\`\`css
|
|
7
|
+
--color-primary: hsl(220, 90%, 56%);
|
|
8
|
+
--color-primary-light: hsl(220, 90%, 70%);
|
|
9
|
+
--color-primary-dark: hsl(220, 90%, 40%);
|
|
10
|
+
\`\`\`
|
|
11
|
+
- Semantic naming — never name by value:
|
|
12
|
+
\`\`\`css
|
|
13
|
+
/* ❌ wrong */
|
|
14
|
+
--color-blue: #3b82f6;
|
|
15
|
+
--color-red: #ef4444;
|
|
16
|
+
|
|
17
|
+
/* ✅ correct */
|
|
18
|
+
--color-primary: #3b82f6;
|
|
19
|
+
--color-error: #ef4444;
|
|
20
|
+
--color-success: #22c55e;
|
|
21
|
+
--color-warning: #f59e0b;
|
|
22
|
+
--color-info: #06b6d4;
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
### Palette Structure
|
|
26
|
+
- 1 primary (brand color)
|
|
27
|
+
- 1 secondary (accent)
|
|
28
|
+
- Neutrals (gray scale, 9-11 steps)
|
|
29
|
+
- Semantic (success, error, warning, info)
|
|
30
|
+
- Never use pure colors — desaturate slightly:
|
|
31
|
+
\`\`\`
|
|
32
|
+
❌ #ff0000 (pure red)
|
|
33
|
+
✅ #ef4444 (slightly desaturated)
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
### Contrast
|
|
37
|
+
- WCAG AA minimum:
|
|
38
|
+
- Normal text: 4.5:1
|
|
39
|
+
- Large text (18px+ or 14px+ bold): 3:1
|
|
40
|
+
- UI components (borders, icons): 3:1
|
|
41
|
+
- Tools: use oklch() for perceptually uniform colors
|
|
42
|
+
|
|
43
|
+
### Dark Mode
|
|
44
|
+
- Don't just invert — rethink:
|
|
45
|
+
\`\`\`css
|
|
46
|
+
:root {
|
|
47
|
+
--bg: #ffffff;
|
|
48
|
+
--bg-subtle: #f9fafb;
|
|
49
|
+
--text: #111827;
|
|
50
|
+
--text-muted: #6b7280;
|
|
51
|
+
--border: #e5e7eb;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[data-theme="dark"] {
|
|
55
|
+
--bg: #0f0f0f;
|
|
56
|
+
--bg-subtle: #1a1a1a;
|
|
57
|
+
--text: #f9fafb;
|
|
58
|
+
--text-muted: #9ca3af;
|
|
59
|
+
--border: #2a2a2a;
|
|
60
|
+
}
|
|
61
|
+
\`\`\`
|
|
62
|
+
- Dark mode: use #0f0f0f or #121212 not pure black
|
|
63
|
+
- Reduce saturation in dark mode — vivid colors look harsh
|
|
64
|
+
- Elevate surfaces with subtle lightness, not shadows:
|
|
65
|
+
\`\`\`
|
|
66
|
+
Layer 0 (base): #0f0f0f
|
|
67
|
+
Layer 1 (cards): #1a1a1a
|
|
68
|
+
Layer 2 (modals): #242424
|
|
69
|
+
Layer 3 (tooltip): #2e2e2e
|
|
70
|
+
\`\`\`
|
|
71
|
+
- Never use color as the only differentiator — always pair with icon/label
|
|
72
|
+
`;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const DATABASE_SKILL = `
|
|
2
|
+
## Skill: Database
|
|
3
|
+
|
|
4
|
+
### Queries
|
|
5
|
+
- Always use parameterized queries — never concatenate SQL:
|
|
6
|
+
\`\`\`ts
|
|
7
|
+
// ❌ wrong
|
|
8
|
+
db.query(\`SELECT * FROM users WHERE id = \${userId}\`);
|
|
9
|
+
|
|
10
|
+
// ✅ correct
|
|
11
|
+
db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
12
|
+
|
|
13
|
+
// ✅ with ORM (Drizzle)
|
|
14
|
+
db.select().from(users).where(eq(users.id, userId));
|
|
15
|
+
\`\`\`
|
|
16
|
+
- Never SELECT * in production — specify columns:
|
|
17
|
+
\`\`\`ts
|
|
18
|
+
db.select({ id: users.id, name: users.name, email: users.email }).from(users);
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
### Schema Design
|
|
22
|
+
- Always have a primary key
|
|
23
|
+
- Use \`created_at\` and \`updated_at\` timestamps on every table
|
|
24
|
+
- Soft delete with \`deleted_at\` when data matters:
|
|
25
|
+
\`\`\`ts
|
|
26
|
+
const users = pgTable('users', {
|
|
27
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
28
|
+
name: text('name').notNull(),
|
|
29
|
+
email: text('email').notNull().unique(),
|
|
30
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
31
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
32
|
+
deletedAt: timestamp('deleted_at'),
|
|
33
|
+
});
|
|
34
|
+
\`\`\`
|
|
35
|
+
- Index foreign keys and search columns:
|
|
36
|
+
\`\`\`sql
|
|
37
|
+
CREATE INDEX idx_posts_user_id ON posts(user_id);
|
|
38
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
### Transactions
|
|
42
|
+
- Use transactions for atomic operations:
|
|
43
|
+
\`\`\`ts
|
|
44
|
+
await db.transaction(async (tx) => {
|
|
45
|
+
const [order] = await tx.insert(orders).values(orderData).returning();
|
|
46
|
+
await tx.insert(orderItems).values(items.map(i => ({ ...i, orderId: order.id })));
|
|
47
|
+
await tx.update(inventory).set({ stock: sql\`stock - 1\` }).where(eq(inventory.id, item.id));
|
|
48
|
+
});
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
### Migrations
|
|
52
|
+
- Never alter production schema directly — always use migrations
|
|
53
|
+
- Make migrations reversible (up + down)
|
|
54
|
+
- Test migrations on staging before production
|
|
55
|
+
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const DOCKER_SKILL = `
|
|
2
|
+
## Skill: Docker
|
|
3
|
+
|
|
4
|
+
### Dockerfile Best Practices
|
|
5
|
+
- Multi-stage builds to keep images small:
|
|
6
|
+
\`\`\`dockerfile
|
|
7
|
+
# Build stage
|
|
8
|
+
FROM node:20-alpine AS builder
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY package*.json ./
|
|
11
|
+
RUN npm ci
|
|
12
|
+
COPY . .
|
|
13
|
+
RUN npm run build
|
|
14
|
+
|
|
15
|
+
# Production stage
|
|
16
|
+
FROM node:20-alpine AS runner
|
|
17
|
+
WORKDIR /app
|
|
18
|
+
RUN addgroup -S app && adduser -S app -G app
|
|
19
|
+
COPY --from=builder /app/dist ./dist
|
|
20
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
21
|
+
USER app
|
|
22
|
+
EXPOSE 3000
|
|
23
|
+
CMD ["node", "dist/index.js"]
|
|
24
|
+
\`\`\`
|
|
25
|
+
- Never use \`latest\` tag — pin versions: \`node:20.11-alpine\`
|
|
26
|
+
- Never run as root — always create and use a non-root user
|
|
27
|
+
- Use .dockerignore:
|
|
28
|
+
\`\`\`
|
|
29
|
+
node_modules
|
|
30
|
+
.env
|
|
31
|
+
.git
|
|
32
|
+
dist
|
|
33
|
+
*.log
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
### Docker Compose
|
|
37
|
+
\`\`\`yaml
|
|
38
|
+
services:
|
|
39
|
+
app:
|
|
40
|
+
build: .
|
|
41
|
+
ports:
|
|
42
|
+
- "3000:3000"
|
|
43
|
+
environment:
|
|
44
|
+
- NODE_ENV=production
|
|
45
|
+
- DATABASE_URL=\${DATABASE_URL}
|
|
46
|
+
depends_on:
|
|
47
|
+
db:
|
|
48
|
+
condition: service_healthy
|
|
49
|
+
|
|
50
|
+
db:
|
|
51
|
+
image: postgres:16-alpine
|
|
52
|
+
environment:
|
|
53
|
+
POSTGRES_DB: myapp
|
|
54
|
+
POSTGRES_USER: \${DB_USER}
|
|
55
|
+
POSTGRES_PASSWORD: \${DB_PASSWORD}
|
|
56
|
+
volumes:
|
|
57
|
+
- postgres_data:/var/lib/postgresql/data
|
|
58
|
+
healthcheck:
|
|
59
|
+
test: ["CMD-SHELL", "pg_isready -U \${DB_USER}"]
|
|
60
|
+
interval: 5s
|
|
61
|
+
timeout: 5s
|
|
62
|
+
retries: 5
|
|
63
|
+
|
|
64
|
+
volumes:
|
|
65
|
+
postgres_data:
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### Rules
|
|
69
|
+
- One process per container
|
|
70
|
+
- Use COPY over ADD unless extracting tarballs
|
|
71
|
+
- Set WORKDIR explicitly — never rely on default
|
|
72
|
+
- Use ARG for build-time, ENV for runtime variables
|
|
73
|
+
- Health checks on all services
|
|
74
|
+
`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const FRONTEND_SKILL = `
|
|
2
|
+
## Skill: Frontend Development
|
|
3
|
+
|
|
4
|
+
### Component Architecture
|
|
5
|
+
- Use functional components with TypeScript — never class components
|
|
6
|
+
- Always type props with explicit interfaces:
|
|
7
|
+
\`\`\`tsx
|
|
8
|
+
interface ButtonProps {
|
|
9
|
+
label: string;
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
variant?: 'primary' | 'secondary';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Button = ({ label, onClick, disabled = false, variant = 'primary' }: ButtonProps) => {
|
|
16
|
+
return <button onClick={onClick} disabled={disabled} className={variant}>{label}</button>;
|
|
17
|
+
};
|
|
18
|
+
\`\`\`
|
|
19
|
+
- Use named exports for components, default exports for pages/routes
|
|
20
|
+
- Keep components under 150 lines — split if larger
|
|
21
|
+
- One component per file
|
|
22
|
+
|
|
23
|
+
### Hooks
|
|
24
|
+
- Extract reusable logic into custom hooks prefixed with \`use\`:
|
|
25
|
+
\`\`\`ts
|
|
26
|
+
function useDebounce<T>(value: T, delay: number): T {
|
|
27
|
+
const [debounced, setDebounced] = useState(value);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const timer = setTimeout(() => setDebounced(value), delay);
|
|
30
|
+
return () => clearTimeout(timer);
|
|
31
|
+
}, [value, delay]);
|
|
32
|
+
return debounced;
|
|
33
|
+
}
|
|
34
|
+
\`\`\`
|
|
35
|
+
- Never put business logic directly in components — use hooks
|
|
36
|
+
- Keep useEffect dependencies complete — never suppress exhaustive-deps
|
|
37
|
+
|
|
38
|
+
### State Management
|
|
39
|
+
- Local state: useState / useReducer
|
|
40
|
+
- Server state: React Query / SWR
|
|
41
|
+
- Global state: Zustand or Context (avoid Redux unless needed)
|
|
42
|
+
- Never mutate state directly:
|
|
43
|
+
\`\`\`ts
|
|
44
|
+
// ❌ wrong
|
|
45
|
+
state.items.push(newItem);
|
|
46
|
+
|
|
47
|
+
// ✅ correct
|
|
48
|
+
setItems(prev => [...prev, newItem]);
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
### Performance
|
|
52
|
+
- Memoize expensive computations with useMemo
|
|
53
|
+
- Memoize callbacks passed to children with useCallback
|
|
54
|
+
- Wrap pure components with React.memo
|
|
55
|
+
- Lazy load heavy components:
|
|
56
|
+
\`\`\`ts
|
|
57
|
+
const HeavyChart = React.lazy(() => import('./HeavyChart'));
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
### TypeScript
|
|
61
|
+
- No \`any\` — use \`unknown\` and narrow down
|
|
62
|
+
- Use discriminated unions for complex state:
|
|
63
|
+
\`\`\`ts
|
|
64
|
+
type State =
|
|
65
|
+
| { status: 'idle' }
|
|
66
|
+
| { status: 'loading' }
|
|
67
|
+
| { status: 'success'; data: User[] }
|
|
68
|
+
| { status: 'error'; error: string };
|
|
69
|
+
\`\`\`
|
|
70
|
+
`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const GIT_SKILL = `
|
|
2
|
+
## Skill: Git
|
|
3
|
+
|
|
4
|
+
### Commit Conventions
|
|
5
|
+
- Use conventional commits:
|
|
6
|
+
\`\`\`
|
|
7
|
+
feat: add user authentication
|
|
8
|
+
fix: resolve login redirect loop
|
|
9
|
+
chore: update dependencies
|
|
10
|
+
refactor: extract auth middleware
|
|
11
|
+
docs: add API documentation
|
|
12
|
+
test: add unit tests for UserService
|
|
13
|
+
style: format with prettier
|
|
14
|
+
\`\`\`
|
|
15
|
+
- Imperative mood: "add feature" not "added feature"
|
|
16
|
+
- Keep subject under 72 characters
|
|
17
|
+
- Reference issues: \`fix: resolve login bug (#123)\`
|
|
18
|
+
|
|
19
|
+
### Branching
|
|
20
|
+
\`\`\`
|
|
21
|
+
main → production
|
|
22
|
+
feat/auth → new features
|
|
23
|
+
fix/login → bug fixes
|
|
24
|
+
chore/deps → maintenance
|
|
25
|
+
release/1.2.0 → release prep
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
### Best Practices
|
|
29
|
+
- Small, focused commits — one logical change per commit
|
|
30
|
+
- Never commit: .env files, secrets, node_modules, build artifacts
|
|
31
|
+
- Always pull before pushing on shared branches
|
|
32
|
+
- Squash WIP commits before merging:
|
|
33
|
+
\`\`\`bash
|
|
34
|
+
git rebase -i HEAD~3
|
|
35
|
+
\`\`\`
|
|
36
|
+
- Tag releases with semantic versioning:
|
|
37
|
+
\`\`\`bash
|
|
38
|
+
git tag -a v1.2.0 -m "Release v1.2.0"
|
|
39
|
+
git push origin v1.2.0
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
### .gitignore essentials
|
|
43
|
+
\`\`\`
|
|
44
|
+
node_modules/
|
|
45
|
+
.env
|
|
46
|
+
.env.local
|
|
47
|
+
dist/
|
|
48
|
+
build/
|
|
49
|
+
*.log
|
|
50
|
+
.DS_Store
|
|
51
|
+
\`\`\`
|
|
52
|
+
`;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const TESTING_SKILL = `
|
|
2
|
+
## Skill: Testing
|
|
3
|
+
|
|
4
|
+
### Unit Tests
|
|
5
|
+
- Test behavior, not implementation:
|
|
6
|
+
\`\`\`ts
|
|
7
|
+
// ❌ wrong — testing implementation
|
|
8
|
+
expect(component.state.isLoading).toBe(true);
|
|
9
|
+
|
|
10
|
+
// ✅ correct — testing behavior
|
|
11
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
|
12
|
+
\`\`\`
|
|
13
|
+
- Descriptive test names:
|
|
14
|
+
\`\`\`ts
|
|
15
|
+
describe('UserService', () => {
|
|
16
|
+
it('should throw 404 when user not found', async () => {});
|
|
17
|
+
it('should hash password before saving', async () => {});
|
|
18
|
+
it('should return user without password field', async () => {});
|
|
19
|
+
});
|
|
20
|
+
\`\`\`
|
|
21
|
+
- Always test error paths:
|
|
22
|
+
\`\`\`ts
|
|
23
|
+
it('should return error when email already exists', async () => {
|
|
24
|
+
await userService.create({ email: 'test@test.com' });
|
|
25
|
+
await expect(userService.create({ email: 'test@test.com' })).rejects.toThrow('Email already exists');
|
|
26
|
+
});
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
### Mocking
|
|
30
|
+
- Mock external dependencies in unit tests:
|
|
31
|
+
\`\`\`ts
|
|
32
|
+
jest.mock('../db', () => ({
|
|
33
|
+
users: { findOne: jest.fn() }
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
it('should return null when user not found', async () => {
|
|
37
|
+
mockDb.users.findOne.mockResolvedValue(null);
|
|
38
|
+
const result = await userService.findById('123');
|
|
39
|
+
expect(result).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
### Test Data
|
|
44
|
+
- Use factories, not hardcoded values:
|
|
45
|
+
\`\`\`ts
|
|
46
|
+
const createUser = (overrides = {}) => ({
|
|
47
|
+
id: crypto.randomUUID(),
|
|
48
|
+
name: 'Test User',
|
|
49
|
+
email: 'test@example.com',
|
|
50
|
+
createdAt: new Date(),
|
|
51
|
+
...overrides,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const user = createUser({ name: 'Alice' });
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
### Structure
|
|
58
|
+
- Keep tests independent — no shared mutable state
|
|
59
|
+
- One logical assertion per test
|
|
60
|
+
- Arrange → Act → Assert pattern:
|
|
61
|
+
\`\`\`ts
|
|
62
|
+
it('should update user name', async () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
const user = await createUser();
|
|
65
|
+
|
|
66
|
+
// Act
|
|
67
|
+
const updated = await userService.update(user.id, { name: 'New Name' });
|
|
68
|
+
|
|
69
|
+
// Assert
|
|
70
|
+
expect(updated.name).toBe('New Name');
|
|
71
|
+
});
|
|
72
|
+
\`\`\`
|
|
73
|
+
`;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const TYPOGRAPHY_SKILL = `
|
|
2
|
+
## Skill: Typography
|
|
3
|
+
|
|
4
|
+
### Type Scale
|
|
5
|
+
- Use a consistent ratio — 1.25x (Major Third) or 1.333x (Perfect Fourth):
|
|
6
|
+
\`\`\`
|
|
7
|
+
xs: 12px
|
|
8
|
+
sm: 14px
|
|
9
|
+
base: 16px
|
|
10
|
+
lg: 20px
|
|
11
|
+
xl: 24px
|
|
12
|
+
2xl: 32px
|
|
13
|
+
3xl: 40px
|
|
14
|
+
4xl: 48px
|
|
15
|
+
\`\`\`
|
|
16
|
+
|
|
17
|
+
### Readability
|
|
18
|
+
- Body text: 16px minimum, 1.5-1.6 line height
|
|
19
|
+
- Limit line length: 60-75 characters (45-75ch)
|
|
20
|
+
- Heading line height: 1.1-1.3 (tighter than body)
|
|
21
|
+
\`\`\`css
|
|
22
|
+
body {
|
|
23
|
+
font-size: 16px;
|
|
24
|
+
line-height: 1.6;
|
|
25
|
+
max-width: 65ch;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1, h2, h3 {
|
|
29
|
+
line-height: 1.2;
|
|
30
|
+
}
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
### Font Pairing
|
|
34
|
+
- Max 2-3 typefaces per design
|
|
35
|
+
- Classic combos:
|
|
36
|
+
- Inter + Fraunces (sans + serif)
|
|
37
|
+
- Geist + Geist Mono (code-friendly)
|
|
38
|
+
- DM Sans + DM Serif Display
|
|
39
|
+
- Use system font stack when performance matters:
|
|
40
|
+
\`\`\`css
|
|
41
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
### Weight & Style
|
|
45
|
+
- Body: 400
|
|
46
|
+
- UI labels, captions: 500
|
|
47
|
+
- Headings: 600-700
|
|
48
|
+
- Never use 500 for headings — jump from 400 to 600+
|
|
49
|
+
- Letter spacing:
|
|
50
|
+
- Headings: -0.02em (tighten)
|
|
51
|
+
- All caps / labels: 0.05-0.1em (loosen)
|
|
52
|
+
- Body: 0 (default)
|
|
53
|
+
|
|
54
|
+
### Colors
|
|
55
|
+
- Never pure black for body text — use #111 or #1a1a1a
|
|
56
|
+
- Hierarchy through weight and size, not just color
|
|
57
|
+
`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const UIUX_SKILL = `
|
|
2
|
+
## Skill: UI/UX Design
|
|
3
|
+
|
|
4
|
+
### Layout & Spacing
|
|
5
|
+
- Use a spacing scale — multiples of 4:
|
|
6
|
+
- 4, 8, 12, 16, 24, 32, 48, 64, 96, 128px
|
|
7
|
+
- Mobile first — design small then enhance:
|
|
8
|
+
\`\`\`css
|
|
9
|
+
.container {
|
|
10
|
+
padding: 16px;
|
|
11
|
+
}
|
|
12
|
+
@media (min-width: 768px) {
|
|
13
|
+
.container {
|
|
14
|
+
padding: 32px;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
\`\`\`
|
|
18
|
+
- Minimum touch target: 44x44px
|
|
19
|
+
- Consistent grid: 12-column or 4/8-column for mobile
|
|
20
|
+
|
|
21
|
+
### Interaction Design
|
|
22
|
+
- Always show loading states for async operations:
|
|
23
|
+
- Use skeleton screens for content, spinners for actions
|
|
24
|
+
- Show errors inline, close to the source — not just toasts
|
|
25
|
+
- Never disable buttons — show why the action is unavailable
|
|
26
|
+
- Keyboard navigation must work for all interactive elements
|
|
27
|
+
- Manage focus after modals/dialogs open and close
|
|
28
|
+
- Animations under 300ms for UI feedback, 500ms for transitions
|
|
29
|
+
|
|
30
|
+
### Component Patterns
|
|
31
|
+
- Cards: consistent padding (16-24px), subtle shadow or border
|
|
32
|
+
- Forms: label above input, error below, helper text below label
|
|
33
|
+
- Tables: zebra striping or row hover for readability
|
|
34
|
+
- Empty states: illustration + message + action
|
|
35
|
+
- 404/Error pages: clear message + way back home
|
|
36
|
+
|
|
37
|
+
### Accessibility
|
|
38
|
+
- Color is never the only differentiator
|
|
39
|
+
- Alt text on all meaningful images
|
|
40
|
+
- ARIA labels on icon-only buttons
|
|
41
|
+
- Focus indicators must be visible
|
|
42
|
+
- Contrast: 4.5:1 for text, 3:1 for large text and UI components
|
|
43
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const DESCRIPTION =
|
|
2
|
+
"Launch a focused sub-agent to handle a specific task.";
|
|
3
|
+
|
|
4
|
+
export const PROMPT = `Spawns a sub-agent with full tool access to complete a focused task.
|
|
5
|
+
|
|
6
|
+
Use this when:
|
|
7
|
+
- A subtask is too complex or long to handle inline
|
|
8
|
+
- You need to delegate a well-defined unit of work
|
|
9
|
+
- The task requires multiple tool calls but is a single concern
|
|
10
|
+
|
|
11
|
+
Do NOT use this for:
|
|
12
|
+
- Simple tool calls you can do directly
|
|
13
|
+
- Tasks requiring multiple parallel agents (use OrchestratorTool instead)
|
|
14
|
+
- Recursive delegation
|
|
15
|
+
|
|
16
|
+
The sub-agent has access to: FileReadTool, FileWriteTool, FileEditTool, BashTool, GrepTool.
|
|
17
|
+
It returns a text summary of what it did.`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { DESCRIPTION, PROMPT } from "./prompt";
|
|
4
|
+
|
|
5
|
+
export const AgentTool = tool({
|
|
6
|
+
title: "Agent",
|
|
7
|
+
description: DESCRIPTION + "\n\n" + PROMPT,
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
prompt: z.string().describe("The task for the sub-agent to perform"),
|
|
10
|
+
}),
|
|
11
|
+
execute: async ({
|
|
12
|
+
prompt,
|
|
13
|
+
}): Promise<{ success: boolean; result?: string; error?: string }> => {
|
|
14
|
+
try {
|
|
15
|
+
const { createAgent } = await import("../../utils/agent.js");
|
|
16
|
+
const result = await createAgent(prompt);
|
|
17
|
+
return { success: true, result };
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return { success: false, error: String(err) };
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FileReadTool } from "../FileReadTool/tool.js";
|
|
2
|
+
import { platform } from "os";
|
|
3
|
+
|
|
4
|
+
export const MAX_OUTPUT_LENGTH = 30000;
|
|
5
|
+
export const MAX_RENDERED_LINES = 50;
|
|
6
|
+
|
|
7
|
+
export const BANNED_COMMANDS = [
|
|
8
|
+
"alias",
|
|
9
|
+
"curl",
|
|
10
|
+
"curlie",
|
|
11
|
+
"wget",
|
|
12
|
+
"axel",
|
|
13
|
+
"aria2c",
|
|
14
|
+
"nc",
|
|
15
|
+
"telnet",
|
|
16
|
+
"lynx",
|
|
17
|
+
"w3m",
|
|
18
|
+
"links",
|
|
19
|
+
"httpie",
|
|
20
|
+
"xh",
|
|
21
|
+
"http-prompt",
|
|
22
|
+
"chrome",
|
|
23
|
+
"firefox",
|
|
24
|
+
"safari",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const isWindows = platform() === "win32";
|
|
28
|
+
|
|
29
|
+
const PLATFORM_NOTES = isWindows
|
|
30
|
+
? `- This is Windows — use dir instead of ls, findstr instead of grep, use backslashes in paths
|
|
31
|
+
- NEVER use find or grep — use findstr or dir /s instead`
|
|
32
|
+
: `- This is ${platform()} — use standard unix commands (ls, grep, find, cat etc.)`;
|
|
33
|
+
|
|
34
|
+
export const DESCRIPTION =
|
|
35
|
+
"Execute a bash command in a persistent shell session.";
|
|
36
|
+
|
|
37
|
+
export const PROMPT = `Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
|
|
38
|
+
|
|
39
|
+
Before executing the command, please follow these steps:
|
|
40
|
+
|
|
41
|
+
1. Directory Verification:
|
|
42
|
+
- If the command will create new directories or files, first verify the parent directory exists
|
|
43
|
+
|
|
44
|
+
2. Security Check:
|
|
45
|
+
- Some commands are banned. If you use one, you will receive an error. Explain it to the user.
|
|
46
|
+
- Banned commands: ${BANNED_COMMANDS.join(", ")}
|
|
47
|
+
|
|
48
|
+
3. Command Execution:
|
|
49
|
+
- After ensuring proper quoting, execute the command.
|
|
50
|
+
|
|
51
|
+
4. Output Processing:
|
|
52
|
+
- Output exceeding ${MAX_OUTPUT_LENGTH} characters will be truncated.
|
|
53
|
+
|
|
54
|
+
5. Return Result:
|
|
55
|
+
- Provide the processed output.
|
|
56
|
+
- Include any errors that occurred.
|
|
57
|
+
|
|
58
|
+
Usage notes:
|
|
59
|
+
${PLATFORM_NOTES}
|
|
60
|
+
- Use ; or && to chain multiple commands, never newlines
|
|
61
|
+
- Avoid cat, head, tail — use ${FileReadTool.title} to read files instead
|
|
62
|
+
- Only use tools available: FileReadTool, FileWriteTool, FileEditTool, BashTool
|
|
63
|
+
- Timeout defaults to 30 minutes, max 10 minutes per command
|
|
64
|
+
- All commands share the same shell session — env vars and cwd persist between commands
|
|
65
|
+
- Prefer absolute paths, avoid cd
|
|
66
|
+
- When listing directory contents recursively, NEVER recurse into node_modules, .git, dist, or build folders
|
|
67
|
+
- When using dir /s, always exclude node_modules: dir /s /b /a-d "path" | findstr /v "\\node_modules\\"
|
|
68
|
+
|
|
69
|
+
# Directory listing
|
|
70
|
+
Before listing files recursively:
|
|
71
|
+
1. Read .gitignore if it exists and exclude those directories
|
|
72
|
+
2. If .gitignore is not found, always exclude these folders by default: node_modules, .git, dist, build, .next, out, coverage
|
|
73
|
+
3. Never dump raw recursive listings — always filter to relevant files only
|
|
74
|
+
|
|
75
|
+
# Git commits
|
|
76
|
+
When asked to commit:
|
|
77
|
+
1. Run git status, git diff, and git log in a single step
|
|
78
|
+
2. Stage only relevant files
|
|
79
|
+
3. Write a concise commit message focused on "why" not "what"
|
|
80
|
+
4. Never use git commands with -i flag
|
|
81
|
+
5. Never push to remote
|
|
82
|
+
6. Never update git config`;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
DESCRIPTION,
|
|
5
|
+
PROMPT,
|
|
6
|
+
MAX_OUTPUT_LENGTH,
|
|
7
|
+
BANNED_COMMANDS,
|
|
8
|
+
} from "./prompt.js";
|
|
9
|
+
import { PersistentShell } from "../../utils/PersistentShell.js";
|
|
10
|
+
import { requestPermission } from "../../permissions.js";
|
|
11
|
+
|
|
12
|
+
const inputSchema = z.object({
|
|
13
|
+
command: z.string().describe("The bash command to execute"),
|
|
14
|
+
timeout: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Timeout in milliseconds, max 600000"),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const BashTool = {
|
|
21
|
+
description: DESCRIPTION + "\n\n" + PROMPT,
|
|
22
|
+
title: "Bash",
|
|
23
|
+
inputSchema,
|
|
24
|
+
execute: async ({ command, timeout }: z.infer<typeof inputSchema>) => {
|
|
25
|
+
const decision = await requestPermission("BashTool", { command });
|
|
26
|
+
if (decision === "deny")
|
|
27
|
+
return { success: false, output: "User denied permission" };
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const banned = BANNED_COMMANDS.find(
|
|
31
|
+
(cmd) => command.split(/\s+/)[0] === cmd,
|
|
32
|
+
);
|
|
33
|
+
if (banned)
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: `Command "${banned}" is not allowed`,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const shell = PersistentShell.getInstance();
|
|
40
|
+
const output = await shell.execute(command, timeout);
|
|
41
|
+
|
|
42
|
+
const truncated = output.length > MAX_OUTPUT_LENGTH;
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
output: truncated
|
|
46
|
+
? output.slice(0, MAX_OUTPUT_LENGTH) + "\n... (truncated)"
|
|
47
|
+
: output,
|
|
48
|
+
truncated,
|
|
49
|
+
};
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return { success: false, error: String(err) };
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const DESCRIPTION =
|
|
2
|
+
"Edit an existing file by replacing a specific string with new content.";
|
|
3
|
+
export const PROMPT = `Edits a file by replacing an exact string match with new content. The path parameter must be an absolute path, not a relative path.
|
|
4
|
+
|
|
5
|
+
The old_string must match exactly once in the file — if it matches zero or multiple times, the edit will fail. Make old_string specific enough to be unique, including surrounding context if needed.
|
|
6
|
+
|
|
7
|
+
Guidelines:
|
|
8
|
+
- Always read the file before editing so you know the exact content
|
|
9
|
+
- Never use this tool on a file you haven't read first
|
|
10
|
+
- Prefer small, targeted edits over rewriting large sections
|
|
11
|
+
- Preserve the original indentation and formatting
|
|
12
|
+
- If you need to make multiple edits, make them one at a time
|
|
13
|
+
- When using old_string, copy the exact content WITHOUT the line number prefix (e.g. use "app.listen" not " 9\tapp.listen")`;
|