Skip to main content
ClaudeWave
Skill84 estrellas del repoactualizado 1mo ago

claude-runner

Claude/Codex CLI execution layer — prompt loading, stream-json parsing, file output extraction, path sanitization, skill file writing, and skill rule generation

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/aspenkit/aspens /tmp/claude-runner && cp -r /tmp/claude-runner/.claude/skills/claude-runner ~/.claude/skills/claude-runner
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

skill.md

You are working on the **CLI execution layer** — the bridge between assembled prompts and the `claude -p` / `codex exec` CLIs, plus skill file I/O.

## Key Concepts
- **Stream-JSON protocol (Claude):** `runClaude()` always passes `--verbose --output-format stream-json`. Output is NDJSON: `type: 'result'` has final text + usage; `type: 'assistant'` has text/tool_use blocks; `type: 'user'` has tool_result blocks.
- **JSONL protocol (Codex):** `runCodex()` spawns `codex exec --json --sandbox read-only --ephemeral`. The `--ask-for-approval never` flag is **conditionally included** based on capability detection (see below). Prompt is passed via **stdin** (`'-'` placeholder arg) to avoid shell arg length limits. Stdin write happens **after** event handlers are attached so fast failures are captured. Events: `item.completed`/`item.updated` with normalized types.
- **Codex capability detection:** `getCodexExecCapabilities()` (internal, cached) runs `codex exec --help` and checks if `--ask-for-approval` appears in the help text. Result is cached in module-level `codexExecCapabilities` variable. If the help check fails (e.g., codex not installed), capabilities default to `{ supportsAskForApproval: false }`. `runCodex()` only adds `--ask-for-approval never` when `supportsAskForApproval` is true.
- **Unified routing:** `runLLM(prompt, options, backendId)` is the shared entry point — dispatches to `runClaude()` or `runCodex()` based on `backendId`. Exported from `runner.js` so command handlers no longer need local routing helpers.
- **Codex internals (private):** `normalizeCodexItemType()` converts PascalCase/kebab-case to snake_case. `collectCodexText()` recursively extracts text from nested event content. Both are internal to runner.js.
- **Prompt templating:** `loadPrompt(name, vars)` resolves `{{partial-name}}` from `src/prompts/partials/` first, then substitutes `{{varName}}` from `vars`. Target-specific vars (`skillsDir`, `skillFilename`, `instructionsFile`, `configDir`) are passed by command handlers.
- **File output parsing:** Primary: `<file path="...">content</file>` XML tags. Fallback: `<!-- file: path -->` comment markers. `parseFileOutput(output, allowedPaths)` accepts optional `{ dirPrefixes, exactFiles }` to override default allowed paths.
- **Path sanitization:** `sanitizePath(rawPath, allowedPaths)` (internal) blocks `..` traversal, absolute paths. Defaults: `.claude/` prefix + `CLAUDE.md` exact. Multi-target callers pass expanded allowed paths via `getAllowedPaths()` from `target.js`.
- **Validation:** `validateSkillFiles()` checks for truncation (XML tag collisions), missing frontmatter, missing sections, bad file path references.
- **Skill rules generation:** `extractRulesFromSkills()` reads all skills via `skill-reader.js`, produces `skill-rules.json` (v2.0) with file patterns, keywords, and intent patterns.
- **Domain patterns:** `generateDomainPatterns()` converts file patterns to bash `detect_skill_domain()` function using `BEGIN/END` markers.
- **Trigger parsing precedence:** `parseTriggersFrontmatter(content)` returns `{ filePatterns, keywords, alwaysActivate }` parsed from a `triggers:` block in YAML frontmatter (supports block lists, inline arrays, and `alwaysActivate: true` for the base skill); returns `null` when no `triggers:` key exists. `parseActivationPatterns` and `parseKeywords` prefer this frontmatter when present and fall back to legacy `## Activation` / `Keywords:` line parsing for older skills.
- **Settings merge:** `mergeSettings()` merges aspens hook config into existing `settings.json`. Detects aspens-managed hooks by `ASPENS_HOOK_MARKERS` (`skill-activation-prompt`, `graph-context-prompt`, `post-tool-use-tracker`, `save-tokens-statusline`, `save-tokens-prompt-guard`, `save-tokens-precompact`). Also handles `statusLine` merging — replaces existing statusLine only if the current one is aspens-managed (detected by `isAspensHook`), preserving user-custom statusLine configs. After merging hooks, `dedupeAspensHookEntries()` removes duplicate aspens-managed entries per event type.
- **Directory-scoped writes:** `writeTransformedFiles()` handles files outside `.claude/` (e.g., `src/billing/AGENTS.md`) with explicit path allowlist — only `CLAUDE.md`, `AGENTS.md` exact files and `.claude/`, `.agents/`, `.codex/` prefixes are permitted.
- **`findSkillFiles` matching:** Only matches the exact `skillFilename` (e.g., `skill.md` or `SKILL.md`), not arbitrary `.md` files in the skills directory.

## Critical Rules
- **Both `--verbose` and `--output-format stream-json` are required for Claude** — omitting either breaks stream parsing.
- **Codex uses `--json --sandbox read-only --ephemeral`** — `--sandbox read-only` restricts filesystem access, `--ephemeral` avoids persisting conversation. `--ask-for-approval never` is added only if `getCodexExecCapabilities()` confirms support. Prompt goes via stdin, not as a CLI arg.
- **Codex stdin write order matters** — event handlers (`stdout`, `stderr`, `close`, `error`) must be attached before writing to stdin, so fast failures are captured.
- **Path sanitization is non-negotiable** — `sanitizePath()` blocks `..` traversal, absolute paths, and any path not in the allowed set.
- **Prompt partials resolve before variables** — `{{skill-format}}` resolves to `partials/skill-format.md` first. If no file, falls through to variable substitution.
- **Timeout resolution:** `resolveTimeout(flagValue, fallbackSeconds)` — `--timeout` flag wins, then `ASPENS_TIMEOUT` env, then caller-provided fallback. Size-based defaults (small: 120s, medium: 300s, large: 600s, very-large: 900s) are set by command handlers, not runner.
- **Disk writes are sanitized** — `writeSkillFiles` and `writeTransformedFiles` pass every payload through `sanitizePublishedContent` so forbidden blocks (`## Activation`, `## Key Files`, hub/cluster/hotspot tables outside `code-map.md`) cannot leak to disk even if an earlier stage missed them.
- **`mergeSettings` preserves non-a