Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions docs/plans/2026-03-01-task4-slash-commands-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Task 4: Slash Commands & Skills Support — Design Doc

> **Status: COMPLETED** — PR [#466](https://github.com/siteboon/claudecodeui/pull/466) created at commit `d7e89c6`.

**Goal:** Port all slash command enhancements from `feature/personal-enhancements` to a single focused PR for upstream.

**Branch:** `feat/slash-commands-skills` (from `main`)

**Approach:** Single comprehensive PR. Manual porting (no cherry-pick).

---

## What Upstream Already Has

Upstream main has a complete slash command system (built across PRs #211, #392, #374, #402):

- `server/routes/commands.js` (601 lines) — command discovery from `.claude/commands/`, 8 built-in commands, 3 API endpoints
- `src/components/chat/hooks/useSlashCommands.ts` (375 lines) — detection, fuzzy search, keyboard nav, `onExecuteCommand` callback
- `src/components/chat/view/subcomponents/CommandMenu.tsx` (224 lines) — dropdown UI
- `useChatComposerState.ts` — wired with `executeCommand`, single-command interception on submit

## What This PR Adds (3 Enhancements)

### Enhancement 1: Skill Discovery & Loading (~200 lines)

**Files:**
- `server/routes/commands.js` — Add `scanUserSkills()` for `~/.claude/skills/`, `scanPluginSkills()` for `~/.claude/plugins/`, `isSkill` flag on execute response, security checks for new paths
- `server/claude-sdk.js` — Add `systemPrompt.append = options.skillContent` (do NOT include `taskOutputFallbackHint` — that belongs to Task 5)
Comment on lines +26 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider documenting security measures for skill content injection.

Enhancement 1 introduces scanning and loading of SKILL.md files from ~/.claude/skills/ and ~/.claude/plugins/, with the content being injected into the system prompt via systemPrompt.append. However, the design doc does not mention validation, sanitization, or security checks on the skill content before injection.

While the files are read from user-controlled directories (which may imply trust), it would be valuable to document:

  1. Whether skill content undergoes any validation or sanitization before injection
  2. Size limits or format constraints on SKILL.md files
  3. How the system handles malformed or malicious skill content
  4. Whether the security checks mentioned in Step 2 (line 82) extend beyond path validation to content validation

This is especially important since the injected content becomes part of the system prompt that influences AI behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-03-01-task4-slash-commands-design.md` around lines 26 - 28,
Update the design doc to explicitly document content-security measures for skill
injection: state that scanUserSkills() and scanPluginSkills() must call a new
validateSkillContent()/sanitizeSkillContent() routine before passing text to
systemPrompt.append, describe reasonable constraints (e.g., MAX_SKILL_SIZE
bytes, allowed Markdown subset or schema, max heading/section counts), explain
how malformed or malicious content is handled (reject+log, strip disallowed
constructs, or load in read-only sandbox) and note that security checks beyond
path validation (content validation, size limits, logging, and failure modes)
are required and where they integrate (during scanning and before setting
isSkill on execute responses).


### Enhancement 2: Multi-Command Input (~130 lines)

**Files:**
- `useChatComposerState.ts` — Replace single-command interception with regex-based multi-command extraction, sequential skill loading, combined skill content, auto-submit with remaining text

### Enhancement 3: Command Selection as Autocomplete (~60 lines)

**Files:**
- `useSlashCommands.ts` — Remove `onExecuteCommand` parameter, change `selectCommandFromKeyboard` and `handleCommandSelect` to insert command name into input instead of executing immediately
- `ChatInterface.tsx` — Remove `onExecuteCommand` prop threading (no longer needed)

### Supporting: Skill-Loaded Card Rendering

**Files:**
- `useChatComposerState.ts` — `setChatMessages` push `{ type: 'skill-loaded', ... }` when skill loads
- `MessageComponent.tsx` — Add skill-loaded card rendering (purple collapsible card)

---

## Changes to EXCLUDE

These appear in the feature branch diff but are NOT slash-command-specific:

| Change | Reason to exclude |
|--------|-------------------|
| Gemini removal (all files) | Upstream feature, must keep |
| `latestMessage` removal from ChatInterface | Unrelated refactor |
| `onSessionProcessing` callback → effect | Unrelated behavior change |
| Scroll-to-bottom interval during loading | UI improvement, unrelated |
| `taskOutputFallbackHint` in systemPrompt.append | Belongs to Task 5 (Background Tasks) |
| `CommandMenu.tsx` deletion | Keep upstream's TSX version |

---

## Implementation Steps

### Step 1: Create branch and verify baseline

```bash
git checkout main
git checkout -b feat/slash-commands-skills
npm install
npm run build # verify clean baseline
```

### Step 2: Modify `server/routes/commands.js`

Add skill scanning functions and integrate into endpoints:

1. Add `scanUserSkills(skillsDir)` function (~40 lines) — scans `~/.claude/skills/` for `SKILL.md` files
2. Add `scanPluginSkills(pluginsDir)` function (~60 lines) — reads `installed_plugins.json`, scans each plugin's `skills/` dir
3. Modify `/list` endpoint — call both scan functions, append results to command list
4. Modify `/load` endpoint — expand security check to allow `.claude/skills/` and `.claude/plugins/`
5. Modify `/execute` endpoint — detect `isSkill` based on path, include in response

Comment on lines +76 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Implementation steps should reference security best practices.

Step 2 mentions expanding security checks to allow .claude/skills/ and .claude/plugins/ paths (line 82), but the design doesn't specify what security validations are performed during scanning. Consider adding guidance on:

  • Path traversal prevention (ensuring SKILL.md files don't reference paths outside allowed directories)
  • Symbolic link handling
  • File size limits during scanning
  • Error handling for inaccessible or malformed files

These details would help ensure the implementation is secure and robust.

📝 Suggested addition to Step 2

Add a security considerations subsection:

**Security considerations for Step 2:**
- Validate that resolved paths remain within `.claude/skills/` or `.claude/plugins/`
- Reject symbolic links pointing outside allowed directories
- Enforce maximum file size limit (e.g., 100KB per SKILL.md)
- Handle read errors gracefully without exposing system paths
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-03-01-task4-slash-commands-design.md` around lines 76 - 84,
Add concrete security validations when implementing scanUserSkills(skillsDir)
and scanPluginSkills(pluginsDir): resolve each SKILL.md to an absolute path and
ensure it stays inside the allowed base (prevent path traversal), reject or
carefully validate symlinks (deny links that resolve outside the allowed dir),
enforce a maximum file size (e.g., 100KB) and read files with size checks, and
catch/read errors to return sanitized errors (no system paths). When updating
the /list handler, call both scanUserSkills and scanPluginSkills and merge
results while filtering out any entries that failed validation; for /load expand
the allowlist to include .claude/skills/ and .claude/plugins/ but validate
requested paths by canonicalizing and ensuring they remain within those
directories before loading; for /execute detect isSkill by checking that the
canonicalized path is under the skills or plugins skill dirs and include that
flag in the response. Ensure all path checks use canonicalized/realpath
resolution and never trust raw input paths.

**Source of truth:** `git show feature/personal-enhancements:server/routes/commands.js`

### Step 3: Modify `server/claude-sdk.js`

Add skill content injection into system prompt:

1. Find the `sdkOptions` construction block (search for `systemPrompt`)
2. Add: `if (options.skillContent) { sdkOptions.systemPrompt.append = options.skillContent; }`
3. Do NOT include `taskOutputFallbackHint` (Task 5 specific)

**Caution:** Upstream did SDK upgrade (#446). Read current main version first to find correct injection point.

### Step 4: Modify `useSlashCommands.ts`

Change command selection from immediate-execute to autocomplete:

1. Remove `onExecuteCommand` from `UseSlashCommandsOptions` interface
2. Remove `onExecuteCommand` from function parameters
3. Remove `isPromiseLike` helper (no longer needed)
4. In `selectCommandFromKeyboard`: replace `onExecuteCommand(command)` with input insertion + cursor positioning
5. In `handleCommandSelect`: same replacement — insert command name, don't execute
6. Remove `selectedProject` null guard from `fetchCommands` (works without project)
7. Change `selectedProject.path` to `selectedProject?.path`

### Step 5: Modify `useChatComposerState.ts`

Add skill handling and multi-command parsing:

1. Add `pendingSkillContentRef = useRef<string | null>(null)`
2. Add to `CommandExecutionResult` interface: `command?`, `metadata?`, `isSkill?`, `userArgs?`
3. In `handleCustomCommand`: add skill path — if `isSkill`, show skill-loaded card, store/auto-submit
4. In `executeCommand`: pass `userArgs = argsText` to `handleCustomCommand` for custom results
5. Replace single-command interception in `handleSubmit` with multi-command regex extraction
6. Add `skillContent` to `claude-command` message: `skillContent: pendingSkillContentRef.current`
7. Remove `onExecuteCommand` from `useSlashCommands` call
8. **Do NOT remove** `geminiModel`, `onSessionProcessing`, `latestMessage` — those are unrelated

### Step 6: Modify `ChatInterface.tsx`

Remove `onExecuteCommand` prop threading:

1. Remove `onExecuteCommand` from the props passed to `useChatComposerState` (it no longer exists)
2. **Keep** all Gemini props, `latestMessage`, `onSessionProcessing` — don't touch unrelated code

### Step 7: Add skill-loaded card in `MessageComponent.tsx`

Add rendering for `message.type === 'skill-loaded'`:

1. Add purple collapsible card between system-injected and user message blocks
2. Show skill name, description, and content on expand

### Step 8: Build, verify, commit

```bash
npm run build
npm run typecheck
# Verify no Gemini code removed, no unrelated changes
git diff --stat HEAD
git add <specific files>
git commit -m "feat(chat): add skill support and multi-command input for slash commands"
git push -u origin feat/slash-commands-skills
gh pr create --repo siteboon/claudecodeui ...
```

---

## Checklist Per PR Rules

- [ ] Branch based on latest `main`
- [ ] No `debug:` commits
- [ ] No cross-feature changes (no Gemini removal, no scroll fixes, no background task code)
- [ ] `npm run build` passes
- [ ] `npm run typecheck` passes
- [ ] Commit messages follow Conventional Commits
- [ ] No `Co-Authored-By` lines

Comment on lines +151 to +160
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Checklist differs from main contribution plan.

This checklist omits the "emoji-generated lines" constraint that appears in the main contribution plan document (docs/plans/2026-03-01-upstream-pr-contribution.md line 101). For consistency across planning documents, both checklists should use identical wording.

🔄 Align with main plan checklist
-- [ ] No `Co-Authored-By` lines
+- [ ] No `Co-Authored-By` or emoji-generated lines in commits
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-03-01-task4-slash-commands-design.md` around lines 151 - 160,
Update the "Checklist Per PR Rules" section so it includes the same
"emoji-generated lines" constraint text used in the main contribution plan (the
document titled 2026-03-01-upstream-pr-contribution.md) to ensure identical
wording; modify the checklist under the heading "Checklist Per PR Rules" to add
the missing checkbox line exactly matching the main plan's phrasing and
formatting so both documents remain consistent.

---

## Conflict Risk Assessment

| File | Risk | Strategy |
|------|------|----------|
| `commands.js` | Low | Pure additions, no upstream changes |
| `useSlashCommands.ts` | Low | Interface change + behavior change |
| `useChatComposerState.ts` | Medium | Large logic changes, must preserve Gemini code |
| `claude-sdk.js` | High | SDK upgrade changed structure — read current version carefully |
| `ChatInterface.tsx` | Medium | Component refactored in #402 — adapt to new structure |
| `MessageComponent.tsx` | Low | Adding new block (skill-loaded card) |
Loading