@miphamai/cli 0.81.6 → 0.81.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/README.md +9 -9
- package/bin/daemon.ts +7 -32
- package/bin/mipham.ts +43 -29
- package/package.json +5 -2
- package/skills/standard/mipham-code-setup.SKILL.md +3 -3
- package/src/agent/sub-agent.ts +12 -1
- package/src/commands/project.ts +92 -12
- package/src/config/keys-manager.ts +3 -3
- package/src/config/loader.ts +82 -1
- package/src/core/context.ts +10 -2
- package/src/core/engine.ts +19 -4
- package/src/core/metrics.ts +8 -0
- package/src/core/paths.ts +79 -0
- package/src/core/permission-rules.ts +121 -13
- package/src/core/permission.ts +3 -0
- package/src/core/session-log.ts +11 -2
- package/src/daemon/engine-capabilities.ts +131 -0
- package/src/daemon/index.ts +4 -1
- package/src/daemon/launch.ts +287 -0
- package/src/daemon/remote-engine.ts +2 -0
- package/src/daemon/server.ts +9 -0
- package/src/daemon/session-worker.ts +7 -4
- package/src/i18n-core/locales/en-US.json +6 -7
- package/src/i18n-core/locales/zh-CN.json +6 -7
- package/src/index.tsx +79 -0
- package/src/mcp/client.ts +4 -2
- package/src/providers/anthropic.ts +2 -0
- package/src/shared/package-info.ts +1 -1
- package/src/shared/types.ts +15 -0
- package/src/skills/bundled-skills.ts +1 -1
- package/src/telemetry/consent.ts +209 -0
- package/src/telemetry/crash.ts +197 -0
- package/src/telemetry/endpoint.ts +82 -0
- package/src/telemetry/index.ts +153 -0
- package/src/telemetry/payload.ts +141 -0
- package/src/telemetry/queue.ts +95 -0
- package/src/telemetry/redact.ts +127 -0
- package/src/telemetry/transport.ts +81 -0
- package/src/tools/agent/workflow.ts +11 -4
- package/src/tools/exec/bash.ts +6 -4
- package/src/tools/exec/enter-worktree.ts +6 -5
- package/src/tools/exec/exit-worktree.ts +10 -5
- package/src/tools/exec/git.ts +18 -8
- package/src/tools/system/config.ts +3 -3
- package/src/ui/app.tsx +40 -11
- package/src/ui/commands.ts +159 -34
- package/src/workflow/primitives/agent.ts +4 -2
- package/src/core/task-runner-tasks.json +0 -14
- package/src/core/task-runner.ts +0 -163
- package/src/skills/mipham/runtime.ts +0 -66
- package/src/skills/standard/runtime.ts +0 -62
|
@@ -18,7 +18,7 @@ export const BUNDLED_SKILLS: ReadonlyArray<BundledSkill> = [
|
|
|
18
18
|
{ type: 'standard', raw: "---\nname: grill-with-docs\ndescription: A relentless interview to sharpen a plan or design, creating CONTEXT.md (shared language) and ADRs (architectural decisions) as we go. Use before any non-trivial implementation to align on requirements and terminology.\nversion: 1.0.0\nuser-invocable: true\nallowed-tools:\n - Read\n - Write\n - Edit\n - Bash\n - Glob\n - Grep\n - WebSearch\n - WebFetch\n---\n\n# Grill With Docs — Deep Requirements Alignment\n\nInspired by Matt Pocock's `grill-with-docs` and `domain-modeling` skills. Before writing code, run a structured interview to align on requirements, establish shared language, and record architectural decisions.\n\n## When to Use\n\n- Before any non-trivial feature implementation\n- When requirements are fuzzy (\"make it faster\", \"add X\")\n- When you need to establish project terminology\n- When architectural decisions need to be recorded\n- User says: \"plan X\", \"design Y\", \"what should we do about Z\"\n\n## When NOT to Use\n\n- Trivial bug fixes with clear expected behavior\n- One-line changes\n- Tasks where the requirements are already crystal clear\n\n---\n\n## The Interview Flow\n\n### Phase 1: Understand the Intent\n\nStart by understanding what the user actually wants. Don't ask \"what should I build?\" — ask about their goal.\n\n**Core Questions:**\n\n1. What problem are you solving? (Not what feature you're building)\n2. Who is this for? (End user, developer, internal tool?)\n3. What does success look like? (How will you know when it's done?)\n4. What's the deadline or priority context?\n\n**Anti-pattern**: Jumping to implementation questions (\"Do you want REST or GraphQL?\") before understanding the problem.\n\n### Phase 2: Sharpen the Language\n\nIdentify vague or overloaded terms and pin them down **immediately**. This is the single highest-leverage activity — shared language reduces token waste and prevents misunderstandings.\n\n**Technique: The Canonical Term**\n\n- When the user uses multiple words for the same thing, pick one as canonical\n- List rejected alternatives under `_Avoid_`\n- Be opinionated — the glossary is prescriptive, not descriptive\n\n```\nUser: \"We need a way for users to save articles for later.\"\nYou: \"Let's pin that down. 'Save for later' could mean bookmarking, or a reading list, or offline download. Which one?\"\nUser: \"Like a reading list — they can come back to it.\"\nYou: \"Got it. Let's call it a **Reading List**. Avoid 'bookmark', 'save', 'favorites'.\"\n→ Write to CONTEXT.md immediately.\n```\n\n**Technique: The Boundary Test**\n\n- When a term is proposed, test its boundaries with edge cases\n- \"Does X include Y? What about Z?\"\n\n**Technique: The Code Cross-Reference**\n\n- When the user describes how something works, check if existing code agrees\n- Surface contradictions immediately\n\n### Phase 3: Probe Edge Cases\n\nBefore accepting any requirement, stress-test it with edge cases.\n\n**Edge Case Inventory:**\n\n- **Empty state**: What does the user see when there's nothing yet?\n- **Error state**: What happens when things go wrong?\n- **Extreme values**: What about 0? What about 10,000?\n- **Concurrency**: What if two people do this at the same time?\n- **Permissions**: Who can do this? Who cannot?\n- **Scale**: What changes at 10x the current volume?\n\n**Technique: The 5 Whys**\nWhen a requirement seems odd, dig deeper:\n\n```\nUser: \"We need real-time updates.\"\nYou: \"Why real-time?\"\nUser: \"Because users need to see changes immediately.\"\nYou: \"Why do they need to see changes immediately?\"\nUser: \"Because they're collaborating on the same document.\"\n→ Now you know the REAL requirement is collaboration, not real-time.\n```\n\n### Phase 4: Make Architecture Decisions\n\nWhen a design decision meets ALL three criteria, offer to record it as an ADR:\n\n1. **Hard to reverse** — changing your mind later has real cost\n2. **Surprising without context** — a future reader would wonder \"why?\"\n3. **The result of a real trade-off** — there were genuine alternatives\n\n**What qualifies for an ADR:**\n\n- Architecture shape (monorepo vs polyrepo, event sourcing vs CRUD)\n- Integration patterns between contexts\n- Technology choices with lock-in (database, message bus, auth provider)\n- Deliberate deviations from convention (\"we use raw SQL because...\")\n- Constraints not visible in code (\"we can't use X because compliance\")\n\n**ADR Format** (write to `docs/adr/NNNN-slug.md`):\n\n```markdown\n# {Short title of the decision}\n\n{1-3 sentences: context, decision, and why.}\n```\n\nOnly add optional sections (Status, Considered Options, Consequences) when they add genuine value. Most ADRs are a single paragraph.\n\n### Phase 5: Write the CONTEXT.md\n\nAfter the interview, synthesize everything into `CONTEXT.md`.\n\n**Format** (`CONTEXT.md` at project root):\n\n```markdown\n# {Project Name} Context\n\n{One or two sentence description of the project domain.}\n\n## Language\n\n**{Term}**:\n{One or two sentence definition of what it IS.}\n_Avoid_: {alternative terms that should not be used}\n\n## Decisions\n\n- [ADR 0001: {Title}](docs/adr/0001-slug.md) — {one-line summary}\n```\n\n**Rules:**\n\n- Be opinionated — pick the best term, ban the rest\n- Only include domain-specific terms (not general programming concepts)\n- Keep definitions tight — one or two sentences\n- Update inline during the conversation, don't batch\n- CONTEXT.md is a glossary, NOT a spec or implementation plan\n\n---\n\n## During the Conversation\n\n### DO\n\n- Challenge the user when they use vague terms — \"What do you mean by 'fast'?\"\n- Propose canonical terms and write them down immediately\n- Invent edge cases and probe boundaries\n- Offer ADRs sparingly (only when all 3 criteria are met)\n- Cross-reference with existing code if available\n- Call out contradictions between what the user says and what the code does\n\n### DON'T\n\n- Rush to implementation questions before understanding the problem\n- Write ADRs for trivial decisions\n- Let fuzzy language slide — pin it down now or pay later\n- Treat CONTEXT.md as a spec or scratch pad\n- Ask yes/no questions when open-ended ones would reveal more\n\n---\n\n## Output\n\nAfter the interview, the user should have:\n\n1. **CONTEXT.md** — shared language glossary (created or updated)\n2. **ADRs** (if needed) — architectural decisions in `docs/adr/`\n3. **Clear requirements** — edge cases explored, assumptions surfaced\n4. **Shared understanding** — you and the user now mean the same thing by the same words\n\n---\n\n## Integration with Mipham Code\n\n- **Memory System**: Key terms go to project memory for persistence across sessions\n- **Critical Thinking Layer**: Apply the 5-dimension self-check (evidence standard, equivalence verification, counter-example search, confidence calibration, depth check) to your own interview questions\n- **Workflow**: For complex projects, the output of this skill feeds directly into `/implement`\n" },
|
|
19
19
|
{ type: 'standard', raw: "---\nname: implement\ndescription: Build work from a spec or tickets with systematic discipline — TDD at pre-agreed seams, incremental verification, code review before commit. Use when implementing features, bugfixes, or any planned work.\nversion: 1.0.0\nuser-invocable: true\n---\n\n# Implement — Structured Build Execution\n\n融合 Superpowers executing-plans(计划审阅 + 隔离工作区)+ Matt Pocock implement(TDD 接缝 + 增量验证 + 提交前审查)。\n\n## When to Use\n\n- Implementing work from a written spec or ticket set\n- Executing a development plan with clear deliverables\n- Building a feature with predefined success criteria\n\n## When NOT to Use\n\n- Exploratory coding / prototyping → use `prototype` skill\n- Quick one-line fixes → just fix it\n- No spec or tickets exist → use `to-tickets` or `to-spec` first\n\n---\n\n## Step 1: Load and Review\n\n### 1.1 Ensure isolated workspace\n\nUse git worktree or a feature branch. Never implement on main/master without explicit consent.\n\n### 1.2 Read the plan/spec/tickets\n\nRead the full spec or ticket set. Understand:\n\n- What is being built?\n- What are the acceptance criteria?\n- What are the pre-agreed seams (where TDD should be applied)?\n\n### 1.3 Review critically\n\nBefore writing any code:\n\n- Are there gaps or ambiguities in the spec?\n- Are the success criteria testable?\n- Do you understand every instruction?\n\n**If concerns exist, raise them before starting.** Don't guess.\n\n---\n\n## Step 2: Execute Tasks\n\nFor each task in order:\n\n### 2.1 At pre-agreed seams: TDD\n\nWhere the spec specifies (or where interfaces are well-defined):\n\n1. Write a **failing test** that asserts the expected behavior\n2. Watch it fail (red)\n3. Write the **minimum code** to make it pass (green)\n4. Refactor if needed, keeping tests green\n\nUse the `tdd` skill for full red-green-refactor discipline.\n\n### 2.2 Incremental verification\n\nDuring implementation:\n\n- **Run typecheck** after each significant change: `pnpm typecheck`\n- **Run relevant test file** after each task: `pnpm test -- <file>`\n- **Don't wait** until everything is done to discover type errors\n\n### 2.3 One task at a time\n\n- Follow each step exactly — the plan has bite-sized steps for a reason\n- One change at a time. No \"while I'm here\" improvements.\n- Mark tasks as complete after verification passes\n\n---\n\n## Step 3: Final Verification\n\nAfter all tasks are complete:\n\n### 3.1 Full test suite\n\n```bash\npnpm test\n```\n\nAll tests must pass. If any fail, fix before proceeding.\n\n### 3.2 Lint and format\n\n```bash\npnpm lint\npnpm format\n```\n\nCI must be green.\n\n---\n\n## Step 4: Code Review\n\n**Before committing**, run code review:\n\nUse the `code-review` skill for a two-axis review:\n\n- **Standards**: Does the diff follow the repo's coding standards?\n- **Spec**: Does it faithfully implement the originating issue/spec?\n\nFix any findings before committing.\n\n---\n\n## Step 5: Commit\n\nCommit your work to the current branch.\n\n```bash\ngit add -A\ngit commit -m \"<type>: <description>\"\n```\n\n- Follow Conventional Commits\n- Reference the spec/ticket in the commit message\n- **Do NOT commit unless explicitly asked** (per CLAUDE.md §关键约束)\n\n---\n\n## When to Stop and Ask\n\n**STOP immediately when:**\n\n- A task is blocked (missing dependency, unclear instruction, verification fails repeatedly)\n- The spec has a critical gap that prevents starting\n- You don't understand an instruction\n- 3+ fix attempts fail — this may be an architectural issue\n\n**Ask for clarification rather than guessing.**\n\n---\n\n## Quick Reference\n\n| Step | Key Activities | Done When |\n| -------------- | ------------------------------------------------------------ | -------------------------------- |\n| **1. Review** | Load spec, isolate workspace, review critically | All concerns raised and resolved |\n| **2. Execute** | TDD at seams, incremental typecheck/test, one task at a time | All tasks complete and verified |\n| **3. Verify** | Full test suite, lint, format | CI-ready (all green) |\n| **4. Review** | Two-axis code review (standards + spec) | Findings addressed |\n| **5. Commit** | Conventional Commits, reference spec/ticket | Work committed to branch |\n" },
|
|
20
20
|
{ type: 'standard', raw: "---\nname: memory\ndescription: Read and write persistent memory files for context retention across sessions — one fact per file with frontmatter\nversion: 2.0.0\n---\n\n# Memory Skill\n\nManage persistent memory stored as markdown files with YAML frontmatter.\n\n## File Format\n\nEach memory is one `.md` file under the `memory/` directory:\n\n```markdown\n---\nname: <kebab-case-slug>\ndescription: <one-line summary>\nmetadata:\n type: user | feedback | project | reference\n---\n\n<the fact body>\n\n**Why:** <rationale>\n**How to apply:** <practical guidance>\n```\n\n## File Path Conventions\n\n- Directory: `~/.mipham/memory/` (user-level) or `./.mipham/memory/` (project-level)\n- Filename: `<name-slug>.md` (lowercase, hyphens)\n- Index: `MEMORY.md` — one line per memory file, maintained automatically\n\n## Operations\n\n### List Memories\n\nScan `MEMORY.md` index for available memories. The index has one line per memory:\n\n```markdown\n- [Title](file.md) — brief hook\n```\n\n### Read Memory\n\nRead the full markdown file including frontmatter. Parse YAML frontmatter for metadata.\n\n### Write Memory\n\n1. Check for existing file with same `name:` slug — update if found\n2. Create new file if no match\n3. Add/update entry in `MEMORY.md` index\n4. Never write what the repo already records (code structure, git history, CLAUDE.md)\n\n### Delete Memory\n\nRemove the file and its index entry. Use when a memory is incorrect or superseded.\n\n## Best Practices\n\n- **One fact per file** — atomic, focused, easy to find\n- **Descriptive slugs** — `npm-publish-workflow` not `memory-1`\n- **Link related memories** — use `[[slug-name]]` wikilinks in body\n- **Check before writing** — search existing memories to avoid duplicates\n- **Types matter**: `user` (who), `feedback` (corrections), `project` (goals), `reference` (external)\n\n## Example\n\n```markdown\n---\nname: api-rate-limit\ndescription: OpenAI API has 500 RPM limit on our tier\nmetadata:\n type: reference\n---\n\nThe OpenAI API key for production has a hard 500 requests/minute limit.\nExceeding it returns HTTP 429 with a Retry-After header.\n\n**Why:** We hit this in production during peak usage\n**How to apply:** Use exponential backoff; batch requests where possible\n```\n" },
|
|
21
|
-
{ type: 'standard', raw: "---\nname: mipham-code-setup\ndescription: Install, configure, diagnose, and troubleshoot Mipham Code — the multi-model open-core intelligent coding terminal. Covers setup wizard, API keys, providers, models, skills, permissions, workspace trust, shell/IDE integration, and first-run onboarding.\nversion: 2.0.0\nuser-invocable: true\nallowed-tools:\n - Read\n - Write\n - Edit\n - Bash\n - Skill\n---\n\n# Mipham Code Setup — Executable Setup Workflow\n\n**Type**: Rigid — follow the decision tree exactly. Don't skip diagnostic phases.\n\n**Purpose**: Guide users from zero to fully configured Mipham Code. This skill is BOTH:\n\n1. A self-contained diagnostic + configuration workflow the AI can execute\n2. A reference for `/setup` command behavior and slash commands\n\n**Triggers**: \"setup mipham\", \"configure mipham\", \"install mipham code\", \"mipham not working\", \"mipham setup\", \"first time using mipham\", \"help me set up\", \"getting started\", `/setup`\n\n---\n\n## Phase 0: Environment Detection (ALWAYS RUN FIRST)\n\nBefore doing anything, run these diagnostic checks. Report results in a status table.\n\n### 0.1 — Detect Installation\n\n```bash\nwhich mipham 2>/dev/null\nmipham --version 2>/dev/null\nbun --version 2>/dev/null\nnode --version 2>/dev/null\n```\n\n### 0.2 — Detect Configuration\n\n```bash\nls -la .mipham/config.yml 2>/dev/null\nls -la ~/.mipham/config.yml 2>/dev/null\nls -la MIPHAM.md 2>/dev/null\nls -la CLAUDE.md 2>/dev/null\n```\n\n### 0.3 — Detect API Keys\n\n```bash\nenv | grep -E 'ANTHROPIC_API_KEY|OPENAI_API_KEY|DEEPSEEK_API_KEY|QWEN_API_KEY|DOUBAO_API_KEY|HUNYUAN_API_KEY|GEMINI_API_KEY' | cut -d= -f1\n```\n\n### 0.4 — Detect Skills & Permissions\n\n```bash\nls .mipham/skills/ 2>/dev/null\ncat .mipham/config.yml 2>/dev/null | grep -E 'permission|trust' || echo \"no config\"\n```\n\n### 0.5 — Detect Workspace Trust\n\n```bash\ncat ~/.mipham/trusted-workspaces.json 2>/dev/null || echo \"no trust store\"\n```\n\n### Status Report Format\n\nAfter detection, present results as:\n\n```\n── Mipham Code Status ──\n\nInstallation: [✅/⬜] mipham CLI [✅/⬜] Bun [✅/⬜] Node.js\nProject: [✅/⬜] .mipham/ [✅/⬜] config.yml [✅/⬜] MIPHAM.md\nUser Config: [✅/⬜] ~/.mipham/config.yml\nAPI Keys: [N] set (list names or \"none\")\nSkills: [N] installed\nPermissions: [mode] (default/acceptEdits/plan/bypassPermissions)\nTrust: [✅/⬜] workspace trusted\n```\n\nThen proceed to ONLY the phases where something is missing. Don't re-run already-configured steps unless asked.\n\n---\n\n## Phase 1: Installation\n\n**Trigger**: `mipham --version` fails.\n\n### Option A: Quick Install (recommended)\n\n```bash\ncurl -fsSL https://mipham.ai/install.sh | bash\n```\n\nThen restart the shell or run:\n\n```bash\nexport PATH=\"$HOME/.mipham/bin:$PATH\"\n```\n\n### Option B: npm Global Install\n\n```bash\nnpm install -g @miphamai/cli\nmipham\n```\n\n### Option C: From Source (developers)\n\n```bash\ngit clone https://github.com/One-Mipham/mipham-code\ncd mipham-code/apps/cli\nbun install && bun run bin/mipham\n```\n\n### ✅ Verification\n\n```bash\nmipham --version # Should print version ≥ 0.24.0\nmipham --help # Should print usage\n```\n\n---\n\n## Phase 2: Project Initialization\n\n**Trigger**: Missing `.mipham/` directory or `MIPHAM.md`.\n\n### 2.1 — Create .mipham/ directory\n\n```bash\nmkdir -p .mipham\n```\n\n### 2.2 — Create .mipham/config.yml\n\nWrite a minimal config. Ask the user which provider they want to use first, or pick a sensible default:\n\n```yaml\ndefaultProvider: anthropic\ndefaultModel: claude-sonnet-4-6\npermission: default\n```\n\n**Providers available** (alphabetical):\n\n| Provider | Type | Example Models |\n| --------- | ------------- | -------------------------------------- |\n| anthropic | Native SDK | Claude Haiku 4.5, Sonnet 4.6, Opus 4.8 |\n| deepseek | OpenAI Compat | V4 Flash, V4 Pro |\n| doubao | OpenAI Compat | Seed 1.6, Seed 2.0 |\n| gemini | OpenAI Compat | 3.0 Flash, 3.0 Pro, 2.5 Pro |\n| hunyuan | OpenAI Compat | Lite, TurboS, 2.0, T1 |\n| openai | OpenAI Compat | GPT-5.4 Mini, GPT-5.4, GPT-5.5, Codex |\n| qwen | OpenAI Compat | Qwen Plus, Qwen Max |\n\n### 2.3 — Create MIPHAM.md (optional but recommended)\n\nCreate `MIPHAM.md` in project root to define AI personality:\n\n```markdown\n# MIPHAM.md\n\n## Project Context\n\n- **Project**: [name]\n- **Language**: [zh-CN / en]\n- **Stack**: [TypeScript / Python / etc.]\n\n## Preferences\n\n- Code style: [e.g., functional, OOP]\n- Comment language: [e.g., English]\n- Test framework: [e.g., Vitest]\n```\n\n### ✅ Verification\n\n```bash\nls -la .mipham/config.yml MIPHAM.md\n```\n\n---\n\n## Phase 3: API Key Configuration\n\n**Trigger**: Missing API keys in environment.\n\n### 3.1 — Identify Required Providers\n\nAsk the user which providers they plan to use. For each, set the env var.\n\n### 3.2 — Set API Keys\n\n**Recommended: Environment variables** (not in config files — avoids accidental commits):\n\n```bash\nexport ANTHROPIC_API_KEY=\"sk-ant-...\"\nexport OPENAI_API_KEY=\"sk-...\"\nexport DEEPSEEK_API_KEY=\"sk-...\"\nexport QWEN_API_KEY=\"sk-...\"\nexport DOUBAO_API_KEY=\"...\"\nexport HUNYUAN_API_KEY=\"...\"\nexport GEMINI_API_KEY=\"...\"\n```\n\nAdd these to `~/.zshrc` or `~/.bashrc` for persistence:\n\n```bash\necho 'export ANTHROPIC_API_KEY=\"sk-ant-...\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n**Alternative**: Store in `~/.mipham/config.yml`:\n\n```yaml\nproviders:\n - id: anthropic\n apiKey: $ANTHROPIC_API_KEY\n - id: openai\n apiKey: $OPENAI_API_KEY\n```\n\n### 3.3 — Verify Keys\n\n```bash\nenv | grep API_KEY\n```\n\n### ❗Security Rules\n\n- NEVER hardcode API keys in project config files (`.mipham/config.yml` in project root should use `$ENV_VAR` references, not raw keys)\n- NEVER commit API keys to git\n- Add to `.gitignore`: `.mipham/config.yml` (if it contains keys), `.env`, `*.pem`\n\n---\n\n## Phase 4: Provider & Model Configuration\n\n**Trigger**: Need to set default or enable/disable providers.\n\n### 4.1 — Set Default Provider & Model\n\nIn `.mipham/config.yml`:\n\n```yaml\ndefaultProvider: anthropic\ndefaultModel: claude-sonnet-4-6\n```\n\nOr use slash commands:\n\n```\n/model # Interactive model picker (Ctrl+P)\n/switch # Switch provider\n/providers # List all configured providers\n```\n\n### 4.2 — Enable/Disable Providers\n\n```yaml\nproviders:\n - id: anthropic\n status: active\n - id: openai\n status: active\n - id: deepseek\n status: disabled\n```\n\n### ✅ Verification\n\n```\n/model # Should show available models\n/providers # Should list active providers\n```\n\n---\n\n## Phase 5: Skills Installation\n\n**Trigger**: No or few skills installed.\n\n### 5.1 — Built-in Skills\n\nMipham Code ships with 17 built-in skills loaded automatically:\n\n- **Standard (14)**: code-review, compassionate-communication, doc-generator, github-ops, memory, mipham-code-setup, security-review, self-review, superpower, systematic-debugging, tdd, test-driven-development, web-access, web-search\n- **Mipham (3)**: om-artifact, om-model-optimize, om-security\n\n### 5.2 — Community Skills\n\nInstall from the community registry:\n\n```\n/setup 4 # Guided skill browser\n```\n\nOr directly:\n\n```bash\n# Skills are loaded from:\n# - apps/cli/skills/standard/ (built-in standard)\n# - apps/cli/skills/mipham/ (built-in mipham)\n# - ~/.mipham/skills/ (user-installed)\n# - .mipham/skills/ (project-local)\n```\n\n### 5.3 — Install Specific Skills\n\n```\n/skills install <name> # Install from registry\n/skills list # List available\n/skills search <query> # Search registry\n```\n\n### ✅ Verification\n\n```\n/skills list # Should show installed skills with counts\n```\n\n---\n\n## Phase 6: Permissions Configuration\n\n**Trigger**: Permission mode not configured or wrong for use case.\n\n### 6.1 — Permission Modes\n\n| Mode | Behavior | Use Case |\n| ------------------- | ------------------------------- | ----------------------------------- |\n| `default` | Prompt for each tool | Normal development (recommended) |\n| `acceptEdits` | Auto-allow edits, prompt others | Active coding sessions |\n| `plan` | Plan-only, no tool execution | Design & architecture work |\n| `bypassPermissions` | Skip all checks | ⚠️ Only for fully trusted codebases |\n\n### 6.2 — Configure\n\nIn `.mipham/config.yml`:\n\n```yaml\npermission: default\n```\n\nOr via slash command:\n\n```\n/permissions # View current settings\n/setup 5 # Permission setup wizard\n```\n\n### 6.3 — CI/CD Safety\n\nFor CI/CD environments, use the `default` mode (the daemon default): headless\nsessions never prompt, so `ask`-level tools (Bash/Write/Edit) are blocked rather\nthan auto-approved.\n\n### ✅ Verification\n\n```\n/permissions # Should show current mode\n```\n\n---\n\n## Phase 7: Workspace Trust\n\n**Trigger**: Untrusted workspace (prompted on startup in v0.24.3+).\n\n### 7.1 — Understanding Workspace Trust\n\nWorkspace trust is a security mechanism that prevents AI from operating in untrusted directories. Trust is **hierarchical**: trusting `/Users/me/Projects` implicitly trusts all subdirectories.\n\n### 7.2 — Trust a Workspace\n\n**Interactive**: Accept the trust prompt when launching Mipham Code in a new directory.\n\n**Manual**:\n\n```\n/trust # Show trust status\n/trust add <dir> # Trust a directory\n/trust remove <dir> # Revoke trust\n```\n\n### 7.3 — Trust Store\n\n```\n~/.mipham/trusted-workspaces.json\n```\n\n### 7.4 — Auto-Trust for Worktrees\n\nWhen using git worktrees, Mipham Code automatically trusts worktree directories if the parent workspace is already trusted (via `EnterWorktree`).\n\n### ✅ Verification\n\n```\n/trust # Should show \"✅ Yes\" for current directory\n```\n\n---\n\n## Phase 8: Shell & IDE Integration\n\n**Trigger**: Want terminal integration, aliases, or IDE plugins.\n\n### 8.1 — Shell Alias\n\nAdd to `~/.zshrc` or `~/.bashrc`:\n\n```bash\nalias mipham='cd ~/your-project && bun run ~/path/to/mipham-code/apps/cli/bin/mipham.ts'\n# Or if installed globally:\nalias mipham='mipham'\n```\n\n### 8.2 — VS Code Integration\n\nRun `/ide` to auto-generate `.vscode/` config files:\n\n- `settings.json` — terminal profile \"mipham\" using Bun\n- `keybindings.json` — Cmd+Esc to focus terminal, Cmd+Shift+M for new terminal\n- `extensions.json` — recommends `miphamai.mipham-code` extension\n\nTo use after generation:\n\n1. Restart VS Code (or Cmd+Shift+P → Reload Window)\n2. Open terminal: Ctrl+` or Cmd+Esc\n3. Select \"mipham\" profile from terminal dropdown\n\nInstall the VS Code extension:\n\n```bash\ncode --install-extension miphamai.mipham-code\n```\n\n### 8.3 — JetBrains Integration\n\nSettings → Tools → Terminal → Shell path → `bun run mipham`\n\n### 8.4 — Terminal Setup\n\n```\n/terminal-setup # Shell & terminal config wizard\n/setup 6 # Shell integration (part of full wizard)\n```\n\n### ✅ Verification\n\n```bash\nwhich mipham # Should resolve\n# In VS Code: Ctrl+` → select \"mipham\" profile\n```\n\n---\n\n## Phase 9: Full Verification\n\nRun after all configuration phases complete.\n\n### 9.1 — System Diagnostics\n\n```\n/doctor # System diagnostics check\n```\n\n### 9.2 — End-to-End Test\n\nStart a conversation and verify:\n\n1. Model responds (not stuck on \"connecting...\")\n2. File tools work: \"read CLAUDE.md\"\n3. Bash works: \"list files in current directory\"\n4. Skills load: `/skills list`\n\n### 9.3 — Common Issues & Fixes\n\n| Symptom | Diagnosis | Fix |\n| ------------------------- | -------------------------------------- | ------------------------------------------------ |\n| \"Provider not registered\" | Missing or invalid API key | `env \\| grep API_KEY`; check key format |\n| \"Model not found\" | Model ID mismatch or disabled provider | `/models` to list available; `/switch` to change |\n| Slow responses | Large model, network, or context full | `/fast on` or switch to Flash model; `/compact` |\n| Context full | Too many messages in history | `/compact` to compress; `/clear` to reset |\n| Permission denied | Tool blocked by permission mode | `/permissions` to check; adjust mode |\n| \"Workspace not trusted\" | New directory, not yet trusted | Accept startup prompt or run `/trust` |\n| MCP tools not available | Server not connected | `/mcp connect <name>` or check config |\n| Update not applying | Cached binary | `mipham update --force` then restart |\n| Config changes ignored | YAML syntax error | Validate with `mipham --check-config` |\n\n### 9.4 — Get Help\n\n```\n/help # Full command reference\n/setup # Re-run setup wizard\n/doctor # Run diagnostics\n```\n\nChat-based help: \"help me configure X\" or \"why isn't Y working?\"\n\n---\n\n## Quick Reference: Essential Slash Commands\n\n| Category | Command | Purpose |\n| ------------- | ----------------- | ------------------------------------------------------ |\n| **Setup** | `/setup` | Full 6-step setup wizard |\n| | `/setup 1` | Initialize project (.mipham/ + MIPHAM.md + config.yml) |\n| | `/setup 2` | Configure providers & API keys |\n| | `/setup 3` | Choose default model |\n| | `/setup 4` | Browse & install skills |\n| | `/setup 5` | Configure permissions |\n| | `/setup 6` | Shell & IDE integration |\n| **Diagnosis** | `/doctor` | System diagnostics |\n| | `/trust` | Workspace trust status |\n| | `/permissions` | Tool permission settings |\n| **Model** | `/model` | Interactive model picker (Ctrl+P) |\n| | `/switch` | Switch provider |\n| | `/models` | List available models |\n| **Session** | `/clear` | Reset conversation |\n| | `/compact` | Compress context |\n| | `/rename` | Rename session |\n| **Workflow** | `/plan` | Enter plan mode |\n| | `/review` | Code review |\n| | `/todos` | Task list |\n| **IDE** | `/ide` | Generate VS Code integration files |\n| | `/terminal-setup` | Shell & terminal config |\n| **Skills** | `/skills list` | List installed skills |\n| | `/skills search` | Search skill registry |\n| | `/skills install` | Install a skill |\n\n---\n\n## Post-Setup: What to Do Next\n\nAfter configuration is verified:\n\n1. **Initialize your project**: \"help me understand this codebase\"\n2. **Set up CLAUDE.md**: `/init` to generate project documentation for the AI\n3. **Install relevant skills**: `/setup 4` or `/skills search`\n4. **Configure MCP servers**: `/mcp connect` for external tool integration\n5. **Start coding**: Just start a conversation — the AI will use tools and skills automatically\n" },
|
|
21
|
+
{ type: 'standard', raw: "---\nname: mipham-code-setup\ndescription: Install, configure, diagnose, and troubleshoot Mipham Code — the multi-model open-core intelligent coding terminal. Covers setup wizard, API keys, providers, models, skills, permissions, workspace trust, shell/IDE integration, and first-run onboarding.\nversion: 2.0.0\nuser-invocable: true\nallowed-tools:\n - Read\n - Write\n - Edit\n - Bash\n - Skill\n---\n\n# Mipham Code Setup — Executable Setup Workflow\n\n**Type**: Rigid — follow the decision tree exactly. Don't skip diagnostic phases.\n\n**Purpose**: Guide users from zero to fully configured Mipham Code. This skill is BOTH:\n\n1. A self-contained diagnostic + configuration workflow the AI can execute\n2. A reference for `/setup` command behavior and slash commands\n\n**Triggers**: \"setup mipham\", \"configure mipham\", \"install mipham code\", \"mipham not working\", \"mipham setup\", \"first time using mipham\", \"help me set up\", \"getting started\", `/setup`\n\n---\n\n## Phase 0: Environment Detection (ALWAYS RUN FIRST)\n\nBefore doing anything, run these diagnostic checks. Report results in a status table.\n\n### 0.1 — Detect Installation\n\n```bash\nwhich mipham 2>/dev/null\nmipham --version 2>/dev/null\nbun --version 2>/dev/null\nnode --version 2>/dev/null\n```\n\n### 0.2 — Detect Configuration\n\n```bash\nls -la .mipham/config.yml 2>/dev/null\nls -la ~/.mipham/config.yml 2>/dev/null\nls -la MIPHAM.md 2>/dev/null\nls -la CLAUDE.md 2>/dev/null\n```\n\n### 0.3 — Detect API Keys\n\n```bash\nenv | grep -E 'ANTHROPIC_API_KEY|OPENAI_API_KEY|DEEPSEEK_API_KEY|QWEN_API_KEY|DOUBAO_API_KEY|HUNYUAN_API_KEY|GEMINI_API_KEY' | cut -d= -f1\n```\n\n### 0.4 — Detect Skills & Permissions\n\n```bash\nls .mipham/skills/ 2>/dev/null\ncat .mipham/config.yml 2>/dev/null | grep -E 'permission|trust' || echo \"no config\"\n```\n\n### 0.5 — Detect Workspace Trust\n\n```bash\ncat ~/.mipham/trusted-workspaces.json 2>/dev/null || echo \"no trust store\"\n```\n\n### Status Report Format\n\nAfter detection, present results as:\n\n```\n── Mipham Code Status ──\n\nInstallation: [✅/⬜] mipham CLI [✅/⬜] Bun [✅/⬜] Node.js\nProject: [✅/⬜] .mipham/ [✅/⬜] config.yml [✅/⬜] MIPHAM.md\nUser Config: [✅/⬜] ~/.mipham/config.yml\nAPI Keys: [N] set (list names or \"none\")\nSkills: [N] installed\nPermissions: [mode] (default/acceptEdits/plan/bypassPermissions)\nTrust: [✅/⬜] workspace trusted\n```\n\nThen proceed to ONLY the phases where something is missing. Don't re-run already-configured steps unless asked.\n\n---\n\n## Phase 1: Installation\n\n**Trigger**: `mipham --version` fails.\n\n### Option A: Quick Install (recommended)\n\n```bash\ncurl -fsSL https://mipham.ai/install.sh | bash\n```\n\nThen restart the shell or run:\n\n```bash\nexport PATH=\"$HOME/.mipham/bin:$PATH\"\n```\n\n### Option B: npm Global Install\n\n```bash\nnpm install -g @miphamai/cli\nmipham\n```\n\n### Option C: From Source (developers)\n\n```bash\ngit clone https://github.com/One-Mipham/mipham-code\ncd mipham-code/apps/cli\nbun install && bun run bin/mipham\n```\n\n### ✅ Verification\n\n```bash\nmipham --version # Should print version ≥ 0.24.0\nmipham --help # Should print usage\n```\n\n---\n\n## Phase 2: Project Initialization\n\n**Trigger**: Missing `.mipham/` directory or `MIPHAM.md`.\n\n### 2.1 — Create .mipham/ directory\n\n```bash\nmkdir -p .mipham\n```\n\n### 2.2 — Create .mipham/config.yml\n\nWrite a minimal config. Ask the user which provider they want to use first, or pick a sensible default:\n\n```yaml\ndefaultProvider: anthropic\ndefaultModel: claude-sonnet-4-6\npermission: default\n```\n\n**Providers available** (alphabetical):\n\n| Provider | Type | Example Models |\n| --------- | ------------- | -------------------------------------- |\n| anthropic | Native SDK | Claude Haiku 4.5, Sonnet 4.6, Opus 4.8 |\n| deepseek | OpenAI Compat | V4 Flash, V4 Pro |\n| doubao | OpenAI Compat | Seed 1.6, Seed 2.0 |\n| gemini | OpenAI Compat | 3.0 Flash, 3.0 Pro, 2.5 Pro |\n| hunyuan | OpenAI Compat | Lite, TurboS, 2.0, T1 |\n| openai | OpenAI Compat | GPT-5.4 Mini, GPT-5.4, GPT-5.5, Codex |\n| qwen | OpenAI Compat | Qwen Plus, Qwen Max |\n\n### 2.3 — Create MIPHAM.md (optional but recommended)\n\nCreate `MIPHAM.md` in project root to define AI personality:\n\n```markdown\n# MIPHAM.md\n\n## Project Context\n\n- **Project**: [name]\n- **Language**: [zh-CN / en]\n- **Stack**: [TypeScript / Python / etc.]\n\n## Preferences\n\n- Code style: [e.g., functional, OOP]\n- Comment language: [e.g., English]\n- Test framework: [e.g., Vitest]\n```\n\n### ✅ Verification\n\n```bash\nls -la .mipham/config.yml MIPHAM.md\n```\n\n---\n\n## Phase 3: API Key Configuration\n\n**Trigger**: Missing API keys in environment.\n\n### 3.1 — Identify Required Providers\n\nAsk the user which providers they plan to use. For each, set the env var.\n\n### 3.2 — Set API Keys\n\n**Recommended: Environment variables** (not in config files — avoids accidental commits):\n\n```bash\nexport ANTHROPIC_API_KEY=\"sk-ant-...\"\nexport OPENAI_API_KEY=\"sk-...\"\nexport DEEPSEEK_API_KEY=\"sk-...\"\nexport QWEN_API_KEY=\"sk-...\"\nexport DOUBAO_API_KEY=\"...\"\nexport HUNYUAN_API_KEY=\"...\"\nexport GEMINI_API_KEY=\"...\"\n```\n\nAdd these to `~/.zshrc` or `~/.bashrc` for persistence:\n\n```bash\necho 'export ANTHROPIC_API_KEY=\"sk-ant-...\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n**Alternative**: Store in `~/.mipham/config.yml`:\n\n```yaml\nproviders:\n - id: anthropic\n apiKey: $ANTHROPIC_API_KEY\n - id: openai\n apiKey: $OPENAI_API_KEY\n```\n\n### 3.3 — Verify Keys\n\n```bash\nenv | grep API_KEY\n```\n\n### ❗Security Rules\n\n- NEVER hardcode API keys in project config files (`.mipham/config.yml` in project root should use `$ENV_VAR` references, not raw keys)\n- NEVER commit API keys to git\n- Add to `.gitignore`: `.mipham/config.yml` (if it contains keys), `.env`, `*.pem`\n\n---\n\n## Phase 4: Provider & Model Configuration\n\n**Trigger**: Need to set default or enable/disable providers.\n\n### 4.1 — Set Default Provider & Model\n\nIn `.mipham/config.yml`:\n\n```yaml\ndefaultProvider: anthropic\ndefaultModel: claude-sonnet-4-6\n```\n\nOr use slash commands:\n\n```\n/model # Interactive model picker (Ctrl+P)\n/switch # Switch provider\n/providers # List all configured providers\n```\n\n### 4.2 — Enable/Disable Providers\n\n```yaml\nproviders:\n - id: anthropic\n status: active\n - id: openai\n status: active\n - id: deepseek\n status: disabled\n```\n\n### ✅ Verification\n\n```\n/model # Should show available models\n/providers # Should list active providers\n```\n\n---\n\n## Phase 5: Skills Installation\n\n**Trigger**: No or few skills installed.\n\n### 5.1 — Built-in Skills\n\nMipham Code ships with 28 built-in skills loaded automatically:\n\n- **Standard (22)**: code-review, codebase-design, compassionate-communication, debug-loop, doc-generator, domain-modeling, github-ops, grill-with-docs, implement, memory, mipham-code-setup, research, safe-coding, security-review, self-review, superpower, tdd, to-spec, triage, trim-process-prose, web-access, web-search\n- **Mipham (6)**: doc-sync, om-artifact, om-model-optimize, om-security, save-to-wiki, self-audit\n\n### 5.2 — Community Skills\n\nInstall from the community registry:\n\n```\n/setup 4 # Guided skill browser\n```\n\nOr directly:\n\n```bash\n# Skills are loaded from:\n# - apps/cli/skills/standard/ (built-in standard)\n# - apps/cli/skills/mipham/ (built-in mipham)\n# - ~/.mipham/skills/ (user-installed)\n# - .mipham/skills/ (project-local)\n```\n\n### 5.3 — Install Specific Skills\n\n```\n/skills install <name> # Install from registry\n/skills list # List available\n/skills search <query> # Search registry\n```\n\n### ✅ Verification\n\n```\n/skills list # Should show installed skills with counts\n```\n\n---\n\n## Phase 6: Permissions Configuration\n\n**Trigger**: Permission mode not configured or wrong for use case.\n\n### 6.1 — Permission Modes\n\n| Mode | Behavior | Use Case |\n| ------------------- | ------------------------------- | ----------------------------------- |\n| `default` | Prompt for each tool | Normal development (recommended) |\n| `acceptEdits` | Auto-allow edits, prompt others | Active coding sessions |\n| `plan` | Plan-only, no tool execution | Design & architecture work |\n| `bypassPermissions` | Skip all checks | ⚠️ Only for fully trusted codebases |\n\n### 6.2 — Configure\n\nIn `.mipham/config.yml`:\n\n```yaml\npermission: default\n```\n\nOr via slash command:\n\n```\n/permissions # View current settings\n/setup 5 # Permission setup wizard\n```\n\n### 6.3 — CI/CD Safety\n\nFor CI/CD environments, use the `default` mode (the daemon default): headless\nsessions never prompt, so `ask`-level tools (Bash/Write/Edit) are blocked rather\nthan auto-approved.\n\n### ✅ Verification\n\n```\n/permissions # Should show current mode\n```\n\n---\n\n## Phase 7: Workspace Trust\n\n**Trigger**: Untrusted workspace (prompted on startup in v0.24.3+).\n\n### 7.1 — Understanding Workspace Trust\n\nWorkspace trust is a security mechanism that prevents AI from operating in untrusted directories. Trust is **hierarchical**: trusting `/Users/me/Projects` implicitly trusts all subdirectories.\n\n### 7.2 — Trust a Workspace\n\n**Interactive**: Accept the trust prompt when launching Mipham Code in a new directory.\n\n**Manual**:\n\n```\n/trust # Show trust status\n/trust add <dir> # Trust a directory\n/trust remove <dir> # Revoke trust\n```\n\n### 7.3 — Trust Store\n\n```\n~/.mipham/trusted-workspaces.json\n```\n\n### 7.4 — Auto-Trust for Worktrees\n\nWhen using git worktrees, Mipham Code automatically trusts worktree directories if the parent workspace is already trusted (via `EnterWorktree`).\n\n### ✅ Verification\n\n```\n/trust # Should show \"✅ Yes\" for current directory\n```\n\n---\n\n## Phase 8: Shell & IDE Integration\n\n**Trigger**: Want terminal integration, aliases, or IDE plugins.\n\n### 8.1 — Shell Alias\n\nAdd to `~/.zshrc` or `~/.bashrc`:\n\n```bash\nalias mipham='cd ~/your-project && bun run ~/path/to/mipham-code/apps/cli/bin/mipham.ts'\n# Or if installed globally:\nalias mipham='mipham'\n```\n\n### 8.2 — VS Code Integration\n\nRun `/ide` to auto-generate `.vscode/` config files:\n\n- `settings.json` — terminal profile \"mipham\" using Bun\n- `keybindings.json` — Cmd+Esc to focus terminal, Cmd+Shift+M for new terminal\n- `extensions.json` — recommends `miphamai.mipham-code` extension\n\nTo use after generation:\n\n1. Restart VS Code (or Cmd+Shift+P → Reload Window)\n2. Open terminal: Ctrl+` or Cmd+Esc\n3. Select \"mipham\" profile from terminal dropdown\n\nInstall the VS Code extension:\n\n```bash\ncode --install-extension miphamai.mipham-code\n```\n\n### 8.3 — JetBrains Integration\n\nSettings → Tools → Terminal → Shell path → `bun run mipham`\n\n### 8.4 — Terminal Setup\n\n```\n/terminal-setup # Shell & terminal config wizard\n/setup 6 # Shell integration (part of full wizard)\n```\n\n### ✅ Verification\n\n```bash\nwhich mipham # Should resolve\n# In VS Code: Ctrl+` → select \"mipham\" profile\n```\n\n---\n\n## Phase 9: Full Verification\n\nRun after all configuration phases complete.\n\n### 9.1 — System Diagnostics\n\n```\n/doctor # System diagnostics check\n```\n\n### 9.2 — End-to-End Test\n\nStart a conversation and verify:\n\n1. Model responds (not stuck on \"connecting...\")\n2. File tools work: \"read CLAUDE.md\"\n3. Bash works: \"list files in current directory\"\n4. Skills load: `/skills list`\n\n### 9.3 — Common Issues & Fixes\n\n| Symptom | Diagnosis | Fix |\n| ------------------------- | -------------------------------------- | ------------------------------------------------ |\n| \"Provider not registered\" | Missing or invalid API key | `env \\| grep API_KEY`; check key format |\n| \"Model not found\" | Model ID mismatch or disabled provider | `/models` to list available; `/switch` to change |\n| Slow responses | Large model, network, or context full | `/fast on` or switch to Flash model; `/compact` |\n| Context full | Too many messages in history | `/compact` to compress; `/clear` to reset |\n| Permission denied | Tool blocked by permission mode | `/permissions` to check; adjust mode |\n| \"Workspace not trusted\" | New directory, not yet trusted | Accept startup prompt or run `/trust` |\n| MCP tools not available | Server not connected | `/mcp connect <name>` or check config |\n| Update not applying | Cached binary | `mipham update --force` then restart |\n| Config changes ignored | YAML syntax error | Validate with `mipham --check-config` |\n\n### 9.4 — Get Help\n\n```\n/help # Full command reference\n/setup # Re-run setup wizard\n/doctor # Run diagnostics\n```\n\nChat-based help: \"help me configure X\" or \"why isn't Y working?\"\n\n---\n\n## Quick Reference: Essential Slash Commands\n\n| Category | Command | Purpose |\n| ------------- | ----------------- | ------------------------------------------------------ |\n| **Setup** | `/setup` | Full 6-step setup wizard |\n| | `/setup 1` | Initialize project (.mipham/ + MIPHAM.md + config.yml) |\n| | `/setup 2` | Configure providers & API keys |\n| | `/setup 3` | Choose default model |\n| | `/setup 4` | Browse & install skills |\n| | `/setup 5` | Configure permissions |\n| | `/setup 6` | Shell & IDE integration |\n| **Diagnosis** | `/doctor` | System diagnostics |\n| | `/trust` | Workspace trust status |\n| | `/permissions` | Tool permission settings |\n| **Model** | `/model` | Interactive model picker (Ctrl+P) |\n| | `/switch` | Switch provider |\n| | `/models` | List available models |\n| **Session** | `/clear` | Reset conversation |\n| | `/compact` | Compress context |\n| | `/rename` | Rename session |\n| **Workflow** | `/plan` | Enter plan mode |\n| | `/review` | Code review |\n| | `/todos` | Task list |\n| **IDE** | `/ide` | Generate VS Code integration files |\n| | `/terminal-setup` | Shell & terminal config |\n| **Skills** | `/skills list` | List installed skills |\n| | `/skills search` | Search skill registry |\n| | `/skills install` | Install a skill |\n\n---\n\n## Post-Setup: What to Do Next\n\nAfter configuration is verified:\n\n1. **Initialize your project**: \"help me understand this codebase\"\n2. **Set up CLAUDE.md**: `/init` to generate project documentation for the AI\n3. **Install relevant skills**: `/setup 4` or `/skills search`\n4. **Configure MCP servers**: `/mcp connect` for external tool integration\n5. **Start coding**: Just start a conversation — the AI will use tools and skills automatically\n" },
|
|
22
22
|
{ type: 'standard', raw: "---\nname: research\ndescription: Deep research against primary sources, executed as a background agent. Collects findings into a single cited Markdown file. Use for investigation that requires reading official docs, source code, specs, or first-party APIs — not secondary summaries.\nversion: 1.0.0\nuser-invocable: true\nallowed-tools:\n - WebSearch\n - WebFetch\n - Agent\n - Bash\n - Write\n - Read\n---\n\n# Research — Background Deep Research\n\n融合 Mipham web-search v3.0(查询构建+验证)+ Matt Pocock research(后台代理+一手来源+Markdown 报告)。\n\n## When to Use\n\n- \"Research X for me\"\n- \"Find out everything about Y from primary sources\"\n- \"Investigate Z and write up findings\"\n- Any question where googling + reading multiple sources is the right answer\n\n## When NOT to Use\n\n- Quick fact lookup → use `/web-search` directly\n- Question answerable from code already in context\n- Pure logic/algorithmic question\n\n---\n\n## Phase 0: Route\n\n```\nResearch task is...\n├── Quick (1-2 sources, immediate answer)?\n│ └── → Use web-search skill directly (Phase 0-4)\n│\n├── Deep (multiple sources, needs synthesis)?\n│ └── → THIS SKILL — background agent\n│\n└── Login-walled / SPA-only sources?\n └── → web-access skill (ComputerUse browser)\n```\n\n---\n\n## Phase 1: Spin Up Background Agent\n\nLaunch a **background agent** to do the heavy reading, so you keep working while it researches.\n\nThe agent's instructions:\n\n```\nYou are a research agent. Your task:\n\n1. Investigate the question against PRIMARY SOURCES ONLY:\n - Official documentation (docs.*.com, *.org)\n - Source code repositories (GitHub, GitLab)\n - Technical specifications (RFCs, standards)\n - First-party API references\n - NOT: blog posts, Medium articles, forum threads, secondary summaries\n\n2. For every claim, follow it back to the source that owns it.\n If a secondary source makes a claim, find the primary source and cite that.\n\n3. Use WebSearch to find sources.\n Use WebFetch to deep-read promising pages.\n Cross-reference critical claims across 2+ independent primary sources.\n\n4. Write findings to a SINGLE Markdown file.\n - Cite every claim with its primary source URL\n - Distinguish between facts (needs citation) and reasoning (your own)\n - Flag outdated content (\"article from 2024, may be stale\")\n - Note if a source is official docs vs community\n\n5. Save the file where the repo already keeps such notes.\n Match existing conventions. If none exist, put it in docs/research/.\n```\n\n---\n\n## Phase 2: Report Format\n\nThe agent writes findings in this structure:\n\n```markdown\n# [Research Topic]\n\n**Date**: YYYY-MM-DD\n**Sources**: N primary, M cross-references\n\n## Key Findings\n\n- [Finding 1] — [Source](URL)\n- [Finding 2] — [Source](URL)\n\n## Detailed Analysis\n\n### [Subtopic A]\n\n[Claim and citation]\n\n### [Subtopic B]\n\n[Claim and citation]\n\n## Source Evaluation\n\n| Source | Type | Authority | Notes |\n| ----------- | ------------- | --------- | --------------------- |\n| [Name](URL) | Official docs | High | Current as of YYYY-MM |\n| [Name](URL) | Source code | High | Tag vX.Y.Z |\n\n## Open Questions\n\n- [Question 1]\n- [Question 2]\n\nSources:\n\n- [Title](URL) — brief note\n```\n\n---\n\n## Phase 3: Review\n\nWhen the background agent completes:\n\n1. Read the output file\n2. Spot-check: did it follow the chain back to primary sources?\n3. Flag any claims that need further verification\n4. Surface uncertainties to the user\n\n---\n\n## Research Quality Checklist\n\n- [ ] Every factual claim has a primary source citation\n- [ ] At least one critical claim is cross-referenced (2+ sources)\n- [ ] Source type is clearly identified (official docs / source code / spec / community)\n- [ ] Outdated content is flagged with publication year\n- [ ] Reasoning vs facts are clearly distinguished\n- [ ] File saved in repo-appropriate location\n" },
|
|
23
23
|
{ type: 'standard', raw: "---\nname: safe-coding\ndescription: Safe coding rules for code generation — validate external/user input before use and throw RangeError on invalid input\nversion: 1.0.0\n---\n\n# Safe Coding\n\n处理外部/用户输入前必须校验:`null`、`undefined`、空字符串、格式非法时,抛出 `RangeError`,消息为 `'invalid input'`。\n" },
|
|
24
24
|
{ type: 'standard', raw: "---\nname: security-review\ndescription: Security audit skill — vulnerability scanning, OWASP Top 10, secrets detection, supply chain analysis, and compliance checking\nversion: 1.0.0\n---\n\n# Security Review\n\nComprehensive security audit for codebases. Covers vulnerability detection, compliance, and hardening recommendations.\n\n## Audit Checklist\n\n### 1. Secrets & Credentials\n\n- [ ] No hardcoded API keys, tokens, or passwords in source files\n- [ ] `.env` and `*.pem` files in `.gitignore`\n- [ ] API keys use environment variables or secret managers\n- [ ] No credentials in git history (check `git log -p`)\n- [ ] CI/CD secrets stored securely (not in workflow files)\n\n### 2. OWASP Top 10\n\n- [ ] **Injection**: SQL, NoSQL, OS command, LDAP injection points\n- [ ] **Broken Authentication**: Weak password policies, missing MFA\n- [ ] **Sensitive Data Exposure**: Unencrypted PII, missing TLS\n- [ ] **XXE**: XML external entity processing\n- [ ] **Broken Access Control**: Missing authorization checks\n- [ ] **Security Misconfiguration**: Default credentials, verbose errors\n- [ ] **XSS**: Reflected, stored, DOM-based cross-site scripting\n- [ ] **Insecure Deserialization**: Untrusted data deserialization\n- [ ] **Using Vulnerable Components**: Outdated dependencies with CVEs\n- [ ] **Insufficient Logging**: Missing audit trails for auth events\n\n### 3. Supply Chain\n\n- [ ] All dependencies have known licenses (no copyleft/GPL)\n- [ ] No dependencies with critical CVEs\n- [ ] Lock files committed (pnpm-lock.yaml, package-lock.json)\n- [ ] Dependency update policy in place\n- [ ] SBOM (Software Bill of Materials) available\n\n### 4. Network & API Security\n\n- [ ] TLS 1.3 enforced for all external communications\n- [ ] API endpoints have rate limiting\n- [ ] CORS configured with explicit origins (not `*`)\n- [ ] SSRF protections in place (URL validation, IP filtering)\n- [ ] WebSocket connections use WSS\n- [ ] GraphQL endpoints have query depth limits\n\n### 5. File System & Path Security\n\n- [ ] Path traversal protections (no `../../../etc/passwd`)\n- [ ] File upload validation (type, size, content inspection)\n- [ ] Symlink attacks prevented\n- [ ] Sensitive directories blocked (`/etc`, `/proc`, `/sys`)\n- [ ] Temporary files cleaned up after use\n\n### 6. Code-Level Security\n\n- [ ] No `eval()` or `Function()` with user input\n- [ ] No `child_process.exec()` with unsanitized input\n- [ ] Regex patterns safe from ReDoS\n- [ ] Prototype pollution prevented\n- [ ] No `dangerouslySetInnerHTML` without sanitization (React)\n- [ ] SQL queries use parameterized statements\n\n### 7. Authentication & Sessions\n\n- [ ] Passwords hashed with bcrypt/argon2 (not MD5/SHA1)\n- [ ] Session tokens use `httpOnly`, `secure`, `SameSite=Strict`\n- [ ] JWT tokens have reasonable expiration\n- [ ] Account lockout after failed attempts\n- [ ] Password reset tokens expire and are single-use\n\n### 8. Data Protection\n\n- [ ] PII data encrypted at rest (AES-256-GCM)\n- [ ] Data encrypted in transit (TLS 1.3)\n- [ ] Logs do not contain sensitive data\n- [ ] Database backups encrypted\n- [ ] Data retention policies defined\n\n### 9. Infrastructure\n\n- [ ] Infrastructure as Code (Terraform/Pulumi) used\n- [ ] Cloud resources not publicly exposed unless intended\n- [ ] Security groups / firewalls restrict inbound traffic\n- [ ] Container images scanned for vulnerabilities\n- [ ] Kubernetes pods run as non-root\n\n### 10. Logging & Monitoring\n\n- [ ] Authentication events logged\n- [ ] Failed access attempts logged and alerted\n- [ ] Structured logging format (JSON)\n- [ ] No PII in log messages\n- [ ] Alert thresholds configured for critical events\n\n## Report Format\n\n```\nSecurity Review Report\n======================\nDate: YYYY-MM-DD\nSeverity: Critical | High | Medium | Low\n\nFinding #N: [Title]\nSeverity: Critical/High/Medium/Low\nLocation: file:line\nDescription: [What was found]\nRisk: [What could happen]\nFix: [How to resolve]\n```\n\n## Compliance Standards\n\n- OWASP ASVS Level 2\n- PCI DSS (if handling payment data)\n- GDPR (if handling EU personal data)\n- SOC 2 Type II\n- ISO 27001\n" },
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { readSettingsDoc, writeSettingsDoc, settingsPathFor } from '../config/loader'
|
|
5
|
+
import { resolveEndpoint, type EndpointSource } from './endpoint'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Consent for telemetry.
|
|
9
|
+
*
|
|
10
|
+
* The switch deliberately lives in `settings.json`, not `config.yml`, for two
|
|
11
|
+
* independent reasons — either alone would disqualify config.yml:
|
|
12
|
+
*
|
|
13
|
+
* 1. First-run setup is detected by pure file existence
|
|
14
|
+
* (`index.tsx` `needsSetup = !hasUserConfig && !hasProjectConfig`). Writing
|
|
15
|
+
* config.yml on first run would make the setup wizard never appear again.
|
|
16
|
+
* 2. config.yml merges shallowly (`config/loader.ts` `{ ...base, ...override }`;
|
|
17
|
+
* only `mergeProviders` deep-merges). Adding a `telemetry:` key would knock
|
|
18
|
+
* out the defaults of every sibling table.
|
|
19
|
+
*
|
|
20
|
+
* `settings.json` is the only config writer in the repo that preserves keys it
|
|
21
|
+
* does not model.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The shape we persist under `settings.json`'s `telemetry` key. */
|
|
25
|
+
export interface TelemetrySettings {
|
|
26
|
+
enabled?: boolean
|
|
27
|
+
endpoint?: string
|
|
28
|
+
installId?: string
|
|
29
|
+
promptedAt?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Why telemetry ended up on or off — surfaced by `/telemetry status`. */
|
|
33
|
+
export type ConsentSource = 'env-off' | 'project-veto' | 'user-optin' | 'default-off'
|
|
34
|
+
|
|
35
|
+
export interface TelemetryConsent {
|
|
36
|
+
enabled: boolean
|
|
37
|
+
endpoint: string
|
|
38
|
+
source: ConsentSource
|
|
39
|
+
/**
|
|
40
|
+
* Which tier supplied `endpoint` — `env`, `user`, or the shipped `default`.
|
|
41
|
+
* `off` when the hard kill switch fired first and no destination was resolved
|
|
42
|
+
* at all. Kept separate from `source` because "who decided whether to
|
|
43
|
+
* collect" and "who decided where to send" are different questions, and a
|
|
44
|
+
* user debugging an unexpected destination needs the second one.
|
|
45
|
+
*/
|
|
46
|
+
endpointSource: EndpointSource | 'off'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hard kill switch. Its semantics are deliberately stronger than "do not send":
|
|
51
|
+
* nothing is collected, no queue file is created, and there is no network call.
|
|
52
|
+
* Only the exact value `off` is recognised — `MIPHAM_TELEMETRY=1` is *not*
|
|
53
|
+
* consent (see `resolveTelemetry`).
|
|
54
|
+
*/
|
|
55
|
+
export function isHardDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
56
|
+
return env.MIPHAM_TELEMETRY === 'off'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read the `telemetry` block of one settings.json scope.
|
|
61
|
+
*
|
|
62
|
+
* `readSettingsDoc` *throws* on a malformed file (by design — it refuses to
|
|
63
|
+
* clobber a file holding the user's other settings). Telemetry must never take
|
|
64
|
+
* the CLI down, and must fail closed: a broken settings.json means "off".
|
|
65
|
+
*/
|
|
66
|
+
export function readTelemetrySettings(
|
|
67
|
+
scope: 'user' | 'project',
|
|
68
|
+
cwd: string = process.cwd(),
|
|
69
|
+
): TelemetrySettings {
|
|
70
|
+
try {
|
|
71
|
+
const doc = readSettingsDoc(settingsPathFor(scope, cwd))
|
|
72
|
+
const block = doc.telemetry
|
|
73
|
+
if (!block || typeof block !== 'object' || Array.isArray(block)) return {}
|
|
74
|
+
return block as TelemetrySettings
|
|
75
|
+
} catch {
|
|
76
|
+
return {}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decide whether to collect, and where to send.
|
|
82
|
+
*
|
|
83
|
+
* The two are resolved independently. Having a destination is not consent to
|
|
84
|
+
* send to it (see `resolveEndpoint` for the destination chain, including the
|
|
85
|
+
* `none` sentinel); `endpointSource` reports which tier supplied it.
|
|
86
|
+
*
|
|
87
|
+
* Three tiers, fail-closed:
|
|
88
|
+
* 1. `MIPHAM_TELEMETRY=off` — hard off, overrides everything.
|
|
89
|
+
* 2. user `settings.json` `telemetry.enabled === true` — the user's own consent.
|
|
90
|
+
* 3. project `settings.json` `telemetry.enabled === false` — **veto only**.
|
|
91
|
+
*
|
|
92
|
+
* Tier 3 is asymmetric on purpose: a project must not be able to *grant*
|
|
93
|
+
* consent, or cloning a repository would be equivalent to that repository
|
|
94
|
+
* consenting on the user's behalf. Same shape as the existing
|
|
95
|
+
* `permissionRestrictions` fail-closed downgrade.
|
|
96
|
+
*
|
|
97
|
+
* There is intentionally no env var that grants consent. Consent has to be a
|
|
98
|
+
* persistent, deliberate act (the first-run prompt or `/telemetry on`) — env
|
|
99
|
+
* vars are inherited by child processes and end up in CI logs, so they must not
|
|
100
|
+
* be a channel for consent-by-proxy.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveTelemetry(
|
|
103
|
+
cwd: string = process.cwd(),
|
|
104
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
105
|
+
): TelemetryConsent {
|
|
106
|
+
if (isHardDisabled(env)) {
|
|
107
|
+
return { enabled: false, endpoint: '', source: 'env-off', endpointSource: 'off' }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const user = readTelemetrySettings('user', cwd)
|
|
111
|
+
const project = readTelemetrySettings('project', cwd)
|
|
112
|
+
const { endpoint, source: endpointSource } = resolveEndpoint(user.endpoint, env)
|
|
113
|
+
|
|
114
|
+
if (project.enabled === false) {
|
|
115
|
+
return { enabled: false, endpoint, source: 'project-veto', endpointSource }
|
|
116
|
+
}
|
|
117
|
+
if (user.enabled === true) {
|
|
118
|
+
return { enabled: true, endpoint, source: 'user-optin', endpointSource }
|
|
119
|
+
}
|
|
120
|
+
return { enabled: false, endpoint, source: 'default-off', endpointSource }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function patchUserTelemetry(patch: TelemetrySettings, cwd: string): void {
|
|
124
|
+
const path = settingsPathFor('user', cwd)
|
|
125
|
+
const doc = readSettingsDoc(path)
|
|
126
|
+
const block = doc.telemetry
|
|
127
|
+
const current: TelemetrySettings =
|
|
128
|
+
block && typeof block === 'object' && !Array.isArray(block) ? (block as TelemetrySettings) : {}
|
|
129
|
+
doc.telemetry = { ...current, ...patch }
|
|
130
|
+
writeSettingsDoc(path, doc)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The anonymous install id.
|
|
135
|
+
*
|
|
136
|
+
* Generated on first use, persisted to the *user* scope so it is stable across
|
|
137
|
+
* projects. It is not an identity: it is not derived from, nor joined to, any
|
|
138
|
+
* account, machine fingerprint, hostname or MAC address — it is a bare random
|
|
139
|
+
* UUID. `/telemetry reset-id` replaces it.
|
|
140
|
+
*/
|
|
141
|
+
export function getOrCreateInstallId(cwd: string = process.cwd()): string {
|
|
142
|
+
const existing = readTelemetrySettings('user', cwd).installId
|
|
143
|
+
if (typeof existing === 'string' && existing.length > 0) return existing
|
|
144
|
+
const id = randomUUID()
|
|
145
|
+
try {
|
|
146
|
+
patchUserTelemetry({ installId: id }, cwd)
|
|
147
|
+
} catch {
|
|
148
|
+
// Read-only HOME: report under an ephemeral id rather than fail the session.
|
|
149
|
+
}
|
|
150
|
+
return id
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function resetInstallId(cwd: string = process.cwd()): string {
|
|
154
|
+
const id = randomUUID()
|
|
155
|
+
patchUserTelemetry({ installId: id }, cwd)
|
|
156
|
+
return id
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Persist the user's answer from `/telemetry on|off`. */
|
|
160
|
+
export function setTelemetryEnabled(enabled: boolean, cwd: string = process.cwd()): void {
|
|
161
|
+
patchUserTelemetry({ enabled }, cwd)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Endpoint override, persisted by `/telemetry endpoint <url>`.
|
|
166
|
+
*
|
|
167
|
+
* `none` is accepted and stored like any other value: the sentinel is
|
|
168
|
+
* interpreted at resolution time (`resolveEndpoint`), not at write time.
|
|
169
|
+
*/
|
|
170
|
+
export function setTelemetryEndpoint(endpoint: string, cwd: string = process.cwd()): void {
|
|
171
|
+
patchUserTelemetry({ endpoint }, cwd)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The prompt is one-shot per machine, tracked by a marker *separate* from
|
|
176
|
+
* `enabled` — otherwise "asked and declined" and "never asked" would be
|
|
177
|
+
* indistinguishable and the question would reappear forever.
|
|
178
|
+
*/
|
|
179
|
+
export function wasPrompted(cwd: string = process.cwd()): boolean {
|
|
180
|
+
const marker = readTelemetrySettings('user', cwd).promptedAt
|
|
181
|
+
return typeof marker === 'string' && marker.length > 0
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function markPrompted(cwd: string = process.cwd(), now: Date = new Date()): void {
|
|
185
|
+
try {
|
|
186
|
+
patchUserTelemetry({ promptedAt: now.toISOString() }, cwd)
|
|
187
|
+
} catch {
|
|
188
|
+
/* read-only HOME — the prompt may reappear, which is benign */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Whether it is safe to ask. A non-TTY (piped, daemon, CI) gets no prompt: it
|
|
194
|
+
* would block on input that can never arrive. Those runs stay opted out and
|
|
195
|
+
* still record the marker, so nothing is asked twice.
|
|
196
|
+
*/
|
|
197
|
+
export function isInteractive(
|
|
198
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
199
|
+
isTTY: boolean | undefined = process.stdout.isTTY,
|
|
200
|
+
): boolean {
|
|
201
|
+
if (env.CI) return false
|
|
202
|
+
if (env.MIPHAM_NON_INTERACTIVE === '1') return false
|
|
203
|
+
return isTTY === true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** `~/.mipham/telemetry` — the queue directory. */
|
|
207
|
+
export function telemetryDir(): string {
|
|
208
|
+
return join(homedir(), '.mipham', 'telemetry')
|
|
209
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { PACKAGE_VERSION } from '../shared/package-info'
|
|
3
|
+
import type { QueuedEvent } from './queue'
|
|
4
|
+
import { hashMessage, redactStack, runtimeTag } from './redact'
|
|
5
|
+
import { SCHEMA_VERSION } from './payload'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Crash capture.
|
|
9
|
+
*
|
|
10
|
+
* Installing `uncaughtException` / `unhandledRejection` listeners is a
|
|
11
|
+
* **dangerous act**, not a bookkeeping one: once a listener exists, Node no
|
|
12
|
+
* longer applies its own default handling. The default is "print the error,
|
|
13
|
+
* exit 1". A handler that merely records the error would therefore turn every
|
|
14
|
+
* crash into a **silent hang** — the process keeps running with a broken
|
|
15
|
+
* state and no diagnostic.
|
|
16
|
+
*
|
|
17
|
+
* So the contract enforced below, and locked by tests, is:
|
|
18
|
+
* 1. write the original stack to stderr — the user must still see the crash;
|
|
19
|
+
* 2. record a redacted copy for the payload;
|
|
20
|
+
* 3. terminate with a non-zero code, so exit paths (`process.on('exit')`)
|
|
21
|
+
* run and the session is reported as crashed.
|
|
22
|
+
*
|
|
23
|
+
* There were no such listeners anywhere in the repo before this module.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export type CrashOrigin = 'uncaughtException' | 'unhandledRejection' | 'render'
|
|
27
|
+
|
|
28
|
+
export interface CrashRecord {
|
|
29
|
+
errorName: string
|
|
30
|
+
messageHash: string
|
|
31
|
+
/**
|
|
32
|
+
* Redacted frames, **local to this process** — never on the wire.
|
|
33
|
+
*
|
|
34
|
+
* Until schema v2 the payload carried these; the collector discarded them on
|
|
35
|
+
* arrival (a frame string has nowhere to live in dimensional aggregates), so
|
|
36
|
+
* v2 stopped sending them. They are still redacted here rather than dropped
|
|
37
|
+
* raw: this record is what a local diagnostic reads, and the redaction
|
|
38
|
+
* guarantee must not depend on which consumer is asking.
|
|
39
|
+
*/
|
|
40
|
+
stackFrames: string[]
|
|
41
|
+
frameCount: number
|
|
42
|
+
origin: CrashOrigin
|
|
43
|
+
occurredAt: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Sinks, injected so tests can observe termination without killing the runner. */
|
|
47
|
+
export interface CrashSinks {
|
|
48
|
+
writeStderr: (text: string) => void
|
|
49
|
+
exit: (code: number) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultSinks: CrashSinks = {
|
|
53
|
+
writeStderr: (text) => {
|
|
54
|
+
process.stderr.write(text)
|
|
55
|
+
},
|
|
56
|
+
exit: (code) => {
|
|
57
|
+
process.exit(code)
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let lastCrash: CrashRecord | null = null
|
|
62
|
+
let installed = false
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Our own listeners, kept by reference.
|
|
66
|
+
*
|
|
67
|
+
* `resetCrashState` must remove exactly these — `removeAllListeners` would also
|
|
68
|
+
* tear out the test runner's own handlers, which is how a test seam turns into
|
|
69
|
+
* a silent loss of crash reporting everywhere else.
|
|
70
|
+
*/
|
|
71
|
+
let onUncaught: ((err: unknown) => void) | null = null
|
|
72
|
+
let onRejection: ((reason: unknown) => void) | null = null
|
|
73
|
+
|
|
74
|
+
/** Normalise anything thrown into an Error, without ever throwing itself. */
|
|
75
|
+
function toError(thrown: unknown): Error {
|
|
76
|
+
if (thrown instanceof Error) return thrown
|
|
77
|
+
if (typeof thrown === 'string') return new Error(thrown)
|
|
78
|
+
try {
|
|
79
|
+
return new Error(JSON.stringify(thrown))
|
|
80
|
+
} catch {
|
|
81
|
+
return new Error(String(thrown))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Record a crash without terminating.
|
|
87
|
+
*
|
|
88
|
+
* Split out from `handleFatal` for the one caller that must survive the error:
|
|
89
|
+
* React's error boundary catches a render failure and keeps the app running, so
|
|
90
|
+
* there is nothing to terminate. Failures here are swallowed — a diagnostic
|
|
91
|
+
* must never be more fragile than the code it is diagnosing.
|
|
92
|
+
*/
|
|
93
|
+
export function recordCrash(thrown: unknown, origin: CrashOrigin, now: Date = new Date()): void {
|
|
94
|
+
try {
|
|
95
|
+
const error = toError(thrown)
|
|
96
|
+
const { frames, frameCount } = redactStack(error.stack ?? '')
|
|
97
|
+
lastCrash = {
|
|
98
|
+
errorName: error.name,
|
|
99
|
+
messageHash: hashMessage(error.message),
|
|
100
|
+
stackFrames: frames,
|
|
101
|
+
frameCount,
|
|
102
|
+
origin,
|
|
103
|
+
occurredAt: now.toISOString(),
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
/* capture is best-effort */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Record and terminate. Exported so the test can drive it directly — a test
|
|
112
|
+
* that had to trigger a real uncaught exception would take the runner down
|
|
113
|
+
* with it.
|
|
114
|
+
*/
|
|
115
|
+
export function handleFatal(
|
|
116
|
+
thrown: unknown,
|
|
117
|
+
origin: CrashOrigin,
|
|
118
|
+
sinks: CrashSinks = defaultSinks,
|
|
119
|
+
now: Date = new Date(),
|
|
120
|
+
): void {
|
|
121
|
+
const error = toError(thrown)
|
|
122
|
+
|
|
123
|
+
// 1. Preserve the observable default behaviour: the user sees the crash.
|
|
124
|
+
// Written verbatim — this goes to the user's terminal, not the wire.
|
|
125
|
+
try {
|
|
126
|
+
sinks.writeStderr(`\n${error.stack ?? `${error.name}: ${error.message}`}\n`)
|
|
127
|
+
} catch {
|
|
128
|
+
/* a broken stderr must not block termination */
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. Record the redacted copy that may leave the machine.
|
|
132
|
+
recordCrash(error, origin, now)
|
|
133
|
+
|
|
134
|
+
// 3. Terminate. Never fall through — that is the silent-hang bug.
|
|
135
|
+
sinks.exit(1)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Install the listeners once. Idempotent: a second call is a no-op, so a
|
|
140
|
+
* double `initTelemetry()` cannot stack two handlers that each call `exit`.
|
|
141
|
+
*/
|
|
142
|
+
export function installCrashHandlers(sinks: CrashSinks = defaultSinks): void {
|
|
143
|
+
if (installed) return
|
|
144
|
+
installed = true
|
|
145
|
+
|
|
146
|
+
onUncaught = (err: unknown) => handleFatal(err, 'uncaughtException', sinks)
|
|
147
|
+
onRejection = (reason: unknown) => handleFatal(reason, 'unhandledRejection', sinks)
|
|
148
|
+
|
|
149
|
+
process.on('uncaughtException', onUncaught)
|
|
150
|
+
process.on('unhandledRejection', onRejection)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Whether this session crashed — reported in the session payload. */
|
|
154
|
+
export function hasCrashed(): boolean {
|
|
155
|
+
return lastCrash !== null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getLastCrash(): CrashRecord | null {
|
|
159
|
+
return lastCrash
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Test seam: clear recorded state and allow re-installation. */
|
|
163
|
+
export function resetCrashState(): void {
|
|
164
|
+
lastCrash = null
|
|
165
|
+
installed = false
|
|
166
|
+
if (onUncaught) process.off('uncaughtException', onUncaught)
|
|
167
|
+
if (onRejection) process.off('unhandledRejection', onRejection)
|
|
168
|
+
onUncaught = null
|
|
169
|
+
onRejection = null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The `crash` event. Holds a message *digest*, never the message text: error
|
|
174
|
+
* messages routinely embed paths and user data.
|
|
175
|
+
*
|
|
176
|
+
* No `stackFrames` since schema v2 — see `SCHEMA_VERSION` in `payload.ts` and
|
|
177
|
+
* the `stackFrames` note on `CrashRecord`.
|
|
178
|
+
*/
|
|
179
|
+
export function buildCrashEvent(installId: string): QueuedEvent | null {
|
|
180
|
+
if (!lastCrash) return null
|
|
181
|
+
return {
|
|
182
|
+
id: randomUUID(),
|
|
183
|
+
kind: 'crash',
|
|
184
|
+
payload: {
|
|
185
|
+
installId,
|
|
186
|
+
schemaVersion: SCHEMA_VERSION,
|
|
187
|
+
occurredAt: lastCrash.occurredAt,
|
|
188
|
+
appVersion: PACKAGE_VERSION,
|
|
189
|
+
runtime: runtimeTag(),
|
|
190
|
+
platform: `${process.platform}/${process.arch}`,
|
|
191
|
+
errorName: lastCrash.errorName,
|
|
192
|
+
messageHash: lastCrash.messageHash,
|
|
193
|
+
frameCount: lastCrash.frameCount,
|
|
194
|
+
origin: lastCrash.origin,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where telemetry is sent.
|
|
3
|
+
*
|
|
4
|
+
* This module is the source of truth for the *destination contract*: the path
|
|
5
|
+
* below has to match the `location =` block in
|
|
6
|
+
* `apps/telemetry/deploy/nginx/log.onemipham.com.conf`. A drift between the two
|
|
7
|
+
* is not a loud failure — every event gets a 404, the client treats 4xx as
|
|
8
|
+
* "permanently unacceptable" and deletes it, and telemetry quietly becomes a
|
|
9
|
+
* no-op for everyone. `apps/cli/test/integrity/telemetry-contract.test.ts` is
|
|
10
|
+
* the mechanical defence against that, which is why the constant lives in a
|
|
11
|
+
* module of its own: the test needs to import exactly one file.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The hosted receiver. Public, write-only, unauthenticated: the CLI cannot hold
|
|
16
|
+
* a secret (it ships to npm as Apache-2.0 source), so this address is designed
|
|
17
|
+
* to be useless to anyone who finds it — aggregate dimensions only, no readback.
|
|
18
|
+
*/
|
|
19
|
+
export const OFFICIAL_TELEMETRY_ENDPOINT = 'https://log.onemipham.com/v1/events'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Opt in *and* send nowhere.
|
|
23
|
+
*
|
|
24
|
+
* Without this there is no way to express that state. Resolution is
|
|
25
|
+
* `env || user || default` and an empty string is falsy, so `endpoint: ""`
|
|
26
|
+
* falls *through* to the next tier instead of clearing the destination. Before
|
|
27
|
+
* T1b the default was empty and the two were indistinguishable; now the default
|
|
28
|
+
* is a real URL, so an empty override would silently start sending. `none`
|
|
29
|
+
* keeps the capability: self-hosted installs and internal-network audits that
|
|
30
|
+
* want telemetry recorded locally but nothing leaving the machine.
|
|
31
|
+
*
|
|
32
|
+
* Exact match only, the same rule as `MIPHAM_TELEMETRY=off`.
|
|
33
|
+
*/
|
|
34
|
+
export const NO_ENDPOINT = 'none'
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Which tier supplied the destination.
|
|
38
|
+
*
|
|
39
|
+
* `off` is not in this union: it is not a destination that was resolved at all,
|
|
40
|
+
* it is the hard kill switch firing before resolution. `TelemetryConsent`
|
|
41
|
+
* widens the type for that case.
|
|
42
|
+
*/
|
|
43
|
+
export type EndpointSource = 'env' | 'user' | 'default'
|
|
44
|
+
|
|
45
|
+
export interface ResolvedEndpoint {
|
|
46
|
+
/** Destination. Empty means nothing is ever sent. */
|
|
47
|
+
endpoint: string
|
|
48
|
+
source: EndpointSource
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the destination, first match wins:
|
|
53
|
+
*
|
|
54
|
+
* 1. `MIPHAM_TELEMETRY_ENDPOINT`
|
|
55
|
+
* 2. user `settings.json` → `telemetry.endpoint`
|
|
56
|
+
* 3. {@link OFFICIAL_TELEMETRY_ENDPOINT}
|
|
57
|
+
*
|
|
58
|
+
* {@link NO_ENDPOINT} at any winning tier resolves to an empty destination, and
|
|
59
|
+
* still reports the tier that supplied it — "which tier decided" and "did it
|
|
60
|
+
* decide to send" are separate questions, and `/telemetry status` answers both.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveEndpoint(
|
|
63
|
+
userEndpoint: string | undefined,
|
|
64
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
65
|
+
): ResolvedEndpoint {
|
|
66
|
+
const fromEnv = env.MIPHAM_TELEMETRY_ENDPOINT
|
|
67
|
+
const raw = fromEnv || userEndpoint || OFFICIAL_TELEMETRY_ENDPOINT
|
|
68
|
+
return {
|
|
69
|
+
endpoint: raw === NO_ENDPOINT ? '' : raw,
|
|
70
|
+
source: fromEnv ? 'env' : userEndpoint ? 'user' : 'default',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Host of the official receiver, for user-facing copy.
|
|
76
|
+
*
|
|
77
|
+
* Derived rather than retyped: the first-run prompt names the destination, and
|
|
78
|
+
* a hard-coded second copy of it would keep naming the old host after a move.
|
|
79
|
+
*/
|
|
80
|
+
export function officialEndpointHost(): string {
|
|
81
|
+
return new URL(OFFICIAL_TELEMETRY_ENDPOINT).host
|
|
82
|
+
}
|