Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

All notable changes to this project are documented in this file.

## [0.2.4]

Fixes a false attribution introduced in 0.2.3: the Kimi Code scanner ran
unconditionally, so it could claim Kimi Code was installed when it never was.

- The Kimi Code scanner (MCP servers and skills, both scopes) now runs only
when its own install marker exists on disk — `~/.kimi-code` at user scope,
`.kimi-code` under the current directory at project scope — checked
independently, so a project-only marker still scans that project with the
user pass gated closed, and vice versa
- The bug: `~/.agents/skills` is not Kimi's own directory. It is the shared
install target [skills.sh](https://github.com/vercel-labs/skills.sh) uses
for several non-Kimi tools (Cline, Warp, Zed, Dexto, Loaf), so a user with
one of those installed and no Kimi Code at all saw a phantom "Kimi Code"
section. At user scope this was usually masked by scan-order dedupe
(Claude Code's link farm wins); at project scope there was no dedupe to
mask it, and devcat's own test suite already proved the misattribution
- Without the marker, Kimi Code now contributes nothing — no section in any
report, no path in the empty-state "Looked in" list, no entry in `--json`
`paths_checked`. The skill goes undetected rather than misattributed:
undercount-honest, the same "no path is claimed unless it was actually
checked" rule `paths_checked` already followed for every other client
- With the marker present, output is unchanged from 0.2.3

## [0.2.3]

Kimi Code joins Claude Code, Codex, and Cursor as a fourth scanned harness —
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ That's the whole thing — the report above is what it prints.
<summary>The same output as text</summary>

```
devcat v0.2.3
devcat v0.2.4

✓ Your AI-coding stack — 26 tools

Expand All @@ -40,7 +40,7 @@ Kimi Code · 3 tools
Cursor · 2 tools
██ 2 mcp figma, postgres

26 tools in Claude Code, Codex, Kimi Code, and Cursor · 14 locations checked
26 tools in Claude Code, Codex, Kimi Code, and Cursor · 16 locations checked
3 project-scoped · 23 user-wide

Share it — npx devcat-cli --markdown
Expand Down Expand Up @@ -107,7 +107,7 @@ npx devcat-cli --json | jq '.clients[] | {label, total}'

```json
{
"cli_version": "0.2.3",
"cli_version": "0.2.4",
"total": 26,
"project_scoped": 3,
"user_scoped": 23,
Expand Down Expand Up @@ -145,11 +145,13 @@ Two kinds of location, read two different ways:

| | Where | How |
|---|---|---|
| **MCP servers** | Claude Code `~/.claude.json`, `~/.claude/settings.json`, `.mcp.json` (project) · Codex `~/.codex/config.toml`, `.codex/config.toml` (project) · Kimi Code `~/.kimi-code/mcp.json`, `.kimi-code/mcp.json` (project — read from the exact working directory, not found by an upward walk) · Cursor `~/.cursor/mcp.json`, `.cursor/mcp.json` (project) | The file is read and parsed. Only the server **names** (the keys) are kept. Kimi Code's config is JSON, same `{ "mcpServers": {...} }` shape as Claude Code and Cursor — `config.toml` holds Kimi Code's own settings, never MCP servers. |
| **MCP servers** | Claude Code `~/.claude.json`, `~/.claude/settings.json`, `.mcp.json` (project) · Codex `~/.codex/config.toml`, `.codex/config.toml` (project) · Kimi Code (only when installed — see below) `~/.kimi-code/mcp.json`, `.kimi-code/mcp.json` (project — read from the exact working directory, not found by an upward walk) · Cursor `~/.cursor/mcp.json`, `.cursor/mcp.json` (project) | The file is read and parsed. Only the server **names** (the keys) are kept. Kimi Code's config is JSON, same `{ "mcpServers": {...} }` shape as Claude Code and Cursor — `config.toml` holds Kimi Code's own settings, never MCP servers. |
| **Plugins** | Claude Code `~/.claude/plugins/installed_plugins.json` | Same — parsed, keys kept. |
| **Skills** | Claude Code `~/.claude/skills/`, `.claude/skills/` (project) · Codex `~/.codex/skills/` · Kimi Code `~/.kimi-code/skills/` and `~/.agents/skills/` (user), `.kimi-code/skills/` and `.agents/skills/` (project) | The directory is listed. A child counts as a skill if it contains a `SKILL.md`. **No file is opened** — not even the `SKILL.md`, whose presence is all that is checked. The name is the folder's. |
| **Skills** | Claude Code `~/.claude/skills/`, `.claude/skills/` (project) · Codex `~/.codex/skills/` · Kimi Code (only when installed — see below) `~/.kimi-code/skills/` and `~/.agents/skills/` (user), `.kimi-code/skills/` and `.agents/skills/` (project) | The directory is listed. A child counts as a skill if it contains a `SKILL.md`. **No file is opened** — not even the `SKILL.md`, whose presence is all that is checked. The name is the folder's. |
| **Subagents** | Claude Code `~/.claude/agents/`, `.claude/agents/` (project) | The directory is listed. Two shapes count: `<name>.md`, where the name is the file's; and `<name>/<name>.md`, where the name is the folder's and the inner filename must match. A folder holding only other markdown — a README, notes — is not a subagent. **No file is opened.** |

**Kimi Code is the one client gated on its own install marker.** `~/.agents/skills` is not Kimi's directory — it is the shared install target [skills.sh](https://github.com/vercel-labs/skills.sh) uses for several non-Kimi tools (Cline, Warp, Zed, Dexto, Loaf), so its mere presence proves nothing about whether Kimi Code itself is installed. The whole Kimi Code scanner — MCP servers and skills, both scopes — runs only when `~/.kimi-code` (user) or `.kimi-code` under the current directory (project) actually exists on disk; each scope is checked independently, so a project-only marker still scans that project with the user pass gated closed, and vice versa. Without its marker, Kimi Code contributes nothing at all: no section in any report, no entry in `paths_checked`, no line in the empty-state "Looked in" list below — the same "only what was actually checked" discipline `paths_checked` follows everywhere else in this doc.

So: config files are read and parsed, and the only thing taken **out of their contents** is the tool's name. Directory scans open nothing at all.

Nothing else inside a config is retained: environment variable values, command-line arguments, install paths, and every other field are dropped at the parser and never appear in any output. Missing and malformed files are skipped silently — a broken `.mcp.json` never fails the scan.
Expand All @@ -173,7 +175,7 @@ Project-scoped entries are found by walking up from the current directory, so th

- Anything found as a folder — skills, subagents — is identified by its **resolved symlink target**. Two links to one directory are one entry however they are named, and two genuinely different skills that happen to share a name both survive.
- MCP servers and plugins are keys in a config file with no path of their own, so they are identified by (type, name) — the same identity the server matches on.
- When two locations do hold the same thing, the first wins in a fixed scan order: project before user, and Claude Code before Codex before Kimi Code before Cursor. `~/.claude/skills` and `~/.codex/skills` are commonly link farms into one shared directory that Kimi Code also reads directly at `~/.agents/skills` — no farm of its own needed — so a skill any of the three can see is listed once under Claude Code. A skill only Codex (or only Kimi Code) has still appears under that client.
- When two locations do hold the same thing, the first wins in a fixed scan order: project before user, and Claude Code before Codex before Kimi Code before Cursor. `~/.claude/skills` and `~/.codex/skills` are commonly link farms into one shared directory that Kimi Code also reads directly at `~/.agents/skills` — no farm of its own needed — so a skill any of the three can see is listed once under Claude Code. A skill only Codex (or only Kimi Code) has still appears under that client (Kimi Code: only when installed — see above).

Install it globally if you run it often:

Expand Down
4 changes: 2 additions & 2 deletions assets/devcat-report.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "devcat-cli",
"version": "0.2.3",
"version": "0.2.4",
"description": "See your whole AI-coding stack in one command. npx devcat-cli scans this machine for the MCP servers, plugins, skills, and subagents installed across Claude Code, Codex, Kimi Code, and Cursor and prints them grouped — locally, with no account and no network call.",
"license": "MIT",
"author": "Andrew Noble (https://github.com/AnobleSCM)",
Expand Down
33 changes: 32 additions & 1 deletion src/manifest/kimi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readFile } from 'node:fs/promises';
import { readFile, stat } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { findUpwardDir, isUserLevelPath } from '../lib/findUpward.js';
Expand Down Expand Up @@ -66,12 +66,43 @@ interface SourceScan {
* path). detect() scans Claude Code, then Codex, then Kimi Code, so a
* skill all three shelves link to is listed once under Claude Code —
* deterministically, not by whichever filesystem answered first.
*
* Install-marker gate: `.agents/skills` is not Kimi's own directory — it is
* the shared global install target skills.sh (vercel-labs) uses for several
* NON-Kimi tools (Cline, Warp, Zed, Dexto, Loaf). Its mere presence proves
* nothing about Kimi, so this whole detector runs only when Kimi's own
* config directory exists: `~/.kimi-code` at user scope, `<cwd>/.kimi-code`
* at project scope, checked independently (a project-only marker still
* runs the project pass with the user pass gated closed, and vice versa).
* A missing marker means zero contribution — no tools, and no paths added
* to pathsScanned, because nothing was actually checked.
*/
export async function detectKimiCode(opts: { cwd?: string; scope: 'project' | 'user' }): Promise<SourceScan> {
if (!(await kimiInstalled(opts))) return { tools: [], pathsScanned: [] };
const [mcp, skills] = await Promise.all([detectKimiMcp(opts), detectKimiSkills(opts)]);
return mergeScans([mcp, skills]);
}

/** Pure existence check — no file is read, matching this scanner's names-only philosophy. */
async function kimiInstalled(opts: { cwd?: string; scope: 'project' | 'user' }): Promise<boolean> {
if (opts.scope === 'user') return dirExists(join(homedir(), '.kimi-code'));
return opts.cwd != null && (await dirExists(join(opts.cwd, '.kimi-code')));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve upward discovery when checking the project marker

When DevCat runs from a subdirectory of a Kimi project, this exact-cwd check returns false even if the project root contains .kimi-code/skills. The detector below deliberately uses findUpwardDir() for both Kimi skill roots because Kimi resolves them from the project root, so the early return prevents that existing upward scan and silently omits valid project skills. The project gate should recognize the marker at the same upward-resolved root rather than requiring <cwd>/.kimi-code.

Useful? React with 👍 / 👎.

}

/**
* True only when `path` is a directory — a stray file named `.kimi-code`
* must not open the gate. `stat` (not `lstat`) follows symlinks, so a
* symlink to a real directory still passes; that's the one stat() call
* this already needed, so the directory check costs nothing extra.
*/
async function dirExists(path: string): Promise<boolean> {
try {
return (await stat(path)).isDirectory();
} catch {
return false;
}
}

/**
* Kimi Code MCP servers.
*
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* Sent as cli_version on POST /api/device/token per Phase 40 D-06.
* Server validates against semver regex /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.
*/
export const CLI_VERSION = '0.2.3';
export const CLI_VERSION = '0.2.4';
Loading
Loading