diff --git a/.wiki/concepts/using-markitdown-cli-migration.md b/.wiki/concepts/using-markitdown-cli-migration.md new file mode 100644 index 0000000..8a33a41 --- /dev/null +++ b/.wiki/concepts/using-markitdown-cli-migration.md @@ -0,0 +1,58 @@ +--- +title: using-markitdown — MCP → CLI migration +type: concept +updated: 2026-06-09 +--- + +# using-markitdown — MCP → CLI migration + +`using-markitdown` v1.0.0 → v1.0.1 (PATCH). Rewrote the skill from the Docker-based +`mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (v0.1.6, +on `PATH`). + +## Why + +The MCP path ran markitdown inside a Docker container with a single host directory +bind-mounted (`-v C:\Users\vitya:/workdir`). That forced a brittle host→container path +translation for every local file (`file:///workdir/...`), and the failure mode +(`[Errno 2] No such file or directory: '/c:/Users/...'`) was a recurring foot-gun. The +container also could not see files outside its one mount. + +The CLI is a normal local process: it sees the full host filesystem, takes a plain path +or URL as its positional arg, and writes markdown to stdout (or to a file with `-o`). No +mount, no path rewriting, no `file://` URIs. The whole "Docker-mount caveat (READ FIRST)" +section of the skill became dead weight and was removed. + +## CLI contract + +``` +markitdown # → markdown to stdout +markitdown -o out.md # → write to a file +cat file.pdf | markitdown -x pdf # → stdin + format hint +``` + +Verified on this machine: `markitdown 0.1.6`; URL fetch (`markitdown https://example.com`) +and stdout conversion both work. + +## Container cleanup gotcha + +The task asked to run `docker stop markitdown-mcp && docker rm markitdown-mcp`. There was +**no container named `markitdown-mcp`** — the MCP server spawns a fresh anonymously-named +container from the `markitdown-mcp:latest` image per session, and three had piled up +(`sharp_jones`, `boring_goldberg`, `admiring_kowalevski`, ages 47s–28h). The correct +decommission is by image ancestor, not by name: + +``` +docker rm -f $(docker ps -aq --filter "ancestor=markitdown-mcp:latest") +``` + +(Stopping them races with the server's own `--rm` cleanup, briefly leaving "Dead" +containers that finish removing themselves — re-checking the filter confirms none remain.) + +## Out of scope / follow-up + +The `markitdown` **MCP server registration** in `~/.claude.json` was left untouched (the +task scoped only the running container, and editing user-global config is cross-cutting). +While that entry remains, a new container will respawn on the next session that loads the +MCP. A full decommission would deregister `mcpServers.markitdown` from `~/.claude.json` — +recommended as a separate, explicitly-confirmed step. diff --git a/.wiki/index.md b/.wiki/index.md index dc2a720..270c324 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -47,6 +47,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [using-system-snapshot-design.md](concepts/using-system-snapshot-design.md) — `using-system-snapshot` v0.1.0 — thin read-only skill wrapping the single `mcp__projects-meta__meta_system_snapshot` call (poller + local docker + cached task summary); replaces scattered `tasklist`/`docker ps`/manual `meta_status`; core rule = no liveness claim without calling the tool this turn; three-line output; defers deep docker to [[using-vds-ops]] and precise tasks to [[using-projects-meta]] - [using-tasks-status-archival.md](concepts/using-tasks-status-archival.md) — `using-tasks` v1.3.0 done-task archival rule (≥10 🟢 → `.tasks/archive/YYYY-MM.md`) fixes STATUS.md bloat; documents why `tasks_get_status` (single-task, by slug) / `tasks_aggregate` (cross-project cache) can't replace the orientation board-read, so the literal task instruction was not followed - [delegate-task-negative-trigger-fp.md](concepts/delegate-task-negative-trigger-fp.md) — `delegate-task` v0.2.1 FP fix: «создать задачу себе» stem-matched the «создать задачу на агента» positive trigger; abstract "does NOT apply when doing the work yourself" carve-out loses to literal stem-match under the 1%-rule → made the negative literal + routed (→ using-tasks). Verified pos 5/5, neg 4/5 (was 0/5) +- [using-markitdown-cli-migration.md](concepts/using-markitdown-cli-migration.md) — `using-markitdown` v1.0.0→v1.0.1 (PATCH): rewrote from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (0.1.6, on PATH); dropped the host→container `file://` mount caveat; container decommission is by image ancestor (`--filter ancestor=markitdown-mcp:latest`), not by the non-existent name `markitdown-mcp` ## Packages diff --git a/.wiki/log.md b/.wiki/log.md index 330582c..9549246 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -74,3 +74,4 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`. ## [2026-06-09] decision | using-tasks-session-break — `using-tasks` 1.1.0→1.2.0 (MINOR): added the `session_break` marker. Task author sets `session_break: true | ""` in task frontmatter (mirrored as `**Session break:**` on the local board); after the task closes 🟢, before `tasks_claim_next`, an autonomous agent prints the verbatim line `🔚 SESSION BOUNDARY — [slug] закрыта. Рекомендую завершить текущую сессию. Следующий трек: [value | "см. STATUS.md"]` and stops instead of chaining the next task. Absent → behaviour unchanged. Enforced in Task completion step 6 + Rules bullet + format docs. Marker not heuristic: the stop-point is an authoring choice, not a runner guess. ## [2026-06-09] review | using-tasks-status-archival v1.3.0 — VERDICT PASS 3/3 (using-tasks-status-read-perf-review). Criterion «ориентация через `tasks_get_status`, не Read» is satisfied by a **validated deviation**, not a literal swap: re-verified against the live tool schema that `tasks_get_status(target_project, slug)→{status, found}` takes a required slug and returns ONE task — it cannot enumerate the board, so it cannot drive orientation; the implementer correctly rejected the impossible instruction and fixed the real problem (bloat→archival). No regression: orientation still reads local STATUS.md (Session start §2) and the «what's next» flow still reads the board — change is purely additive. Archival rule clear & complete (≥10 threshold, two trigger points, monthly append-only archive, verbatim blocks, dedicated commit, cross-referenced). One informational non-blocking note: this repo's own STATUS.md (>10 🟢) would itself trip the rule — dogfooding tracked separately as tasks-board-cleanup-2026-05. No follow-up tasks. Verdict appended to concepts/using-tasks-status-archival.md. ## [2026-06-09] decision | delegate-task-review-weight — `delegate-task` 0.2.2→0.2.3 (PATCH): Step 5 (paired `-review` task) now sets an explicit `weight`, inherited from the impl-task with a `needs-claude` floor (impl `needs-human`→review `needs-human`; `needs-claude`→`needs-claude`; `cheap-ok`→`needs-claude`). Root cause of commit `c0af151` ("add Weight: needs-claude to 4 review tasks — reconciler was skipping them"): the authoring skill omitted `weight` on review tasks, making them invisible to fleet routing. Floor (not pure inheritance) chosen to stay internally consistent with the skill's own "What NOT to do" bullet that forbids `cheap-ok` for review tasks — a `cheap-ok` impl would otherwise propagate a forbidden `cheap-ok` review. Added a What-NOT-to-do bullet against weightless review tasks. Concept page concepts/delegate-task-review-weight.md + index. TDD N/A (markdown policy artifact). +## [2026-06-09] decision | using-markitdown-cli-migration — `using-markitdown` 1.0.0→1.0.1 (PATCH): rewrote the skill from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (v0.1.6, on PATH). Tool block now `markitdown ` → stdout (or `-o file`); removed the whole "Docker-mount caveat (READ FIRST)" section (host→container `file://` translation + `[Errno 2] /c:/Users/...` symptom are gone — CLI sees the full host FS). Updated the ingest pattern (use `-o` straight into `.wiki/raw/`), the gotchas table (`command not found` → check `markitdown --version`, install `pip install markitdown[all]`; dropped the MCP "tool not available / ToolSearch" row), and the contrast-table header (CLI, not MCP). Description frontmatter (the WHEN-to-use triggers) left unchanged. Container decommission: the task's literal `docker stop/rm markitdown-mcp` had no target — no container is named that; the MCP spawns anonymously-named containers from `markitdown-mcp:latest` per session (3 had piled up). Removed all by image ancestor (`docker rm -f $(docker ps -aq --filter "ancestor=markitdown-mcp:latest")`), verified none remain. Left the `mcpServers.markitdown` entry in `~/.claude.json` untouched (out of scope; a container will respawn next session until it's deregistered — flagged as a follow-up). Concept page concepts/using-markitdown-cli-migration.md + index. TDD N/A (markdown skill). diff --git a/dist/using-markitdown.skill b/dist/using-markitdown.skill index 05f271c..542a41c 100644 Binary files a/dist/using-markitdown.skill and b/dist/using-markitdown.skill differ diff --git a/skills/using-markitdown/SKILL.md b/skills/using-markitdown/SKILL.md index 1febec3..154b5b4 100644 --- a/skills/using-markitdown/SKILL.md +++ b/skills/using-markitdown/SKILL.md @@ -1,40 +1,36 @@ --- name: using-markitdown -version: 1.0.0 +version: 1.0.1 description: Use when capturing external content into a markdown-based knowledge base, wiki `raw/` directory, or any pipeline that must preserve the source's full text — for web pages, PDFs, DOCX/PPTX/XLSX, EPUB, CSV/JSON/XML, ZIP archives, images (with OCR/EXIF), audio (with transcription), or YouTube URLs. Also use when WebFetch returned an LLM-summarized version but the raw content is what's needed. --- # using-markitdown -> Convert almost any URI to plain markdown using Microsoft's `markitdown` MCP server. Returns **raw textual content**, not an LLM summary. +> Convert almost any path or URL to plain markdown using Microsoft's `markitdown` CLI (v0.1.6, on `PATH`). Returns **raw textual content**, not an LLM summary. ## Tool ``` -mcp__markitdown__convert_to_markdown(uri: string) → markdown string +markitdown # → markdown to stdout +markitdown -o out.md # → write markdown to a file +cat file.pdf | markitdown # → read from stdin (use -x/-m to hint the format) ``` -`uri` accepts: `http://`, `https://`, `file://`, `data:`. +The positional argument accepts a **local file path** (host path, normal slashes) or an `http://` / `https://` URL. The CLI runs natively, so it sees your full host filesystem — no Docker mount, no `file://` URI translation, no path rewriting. -## Local files — Docker-mount caveat (READ FIRST) +Useful flags: `-o ` (write to a file instead of stdout), `-x ` / `-m ` (format hint when reading from stdin). -The markitdown MCP usually runs in a **Docker container** with a single host directory bind-mounted. The container does **not** see your full host filesystem. `file://` URIs must point to the **in-container path**, not the host path. +## Local files -1. Open `~/.claude.json` and find `mcpServers.markitdown.args`. Look for the `-v` flag — e.g. `-v C:\Users\vitya:/workdir` means host `C:\Users\vitya` is mounted at `/workdir` inside the container. -2. Translate the host path to the container path before forming the URI. -3. Forward slashes only inside the container path. - -**Example.** Host file at `C:\Users\vitya\modular\heart-and-mask\.wiki\raw\foo.html` with mount `C:\Users\vitya:/workdir`: +Pass the host path directly — relative or absolute, with native separators: ``` -file:///workdir/modular/heart-and-mask/.wiki/raw/foo.html +markitdown C:\Users\vitya\modular\heart-and-mask\.wiki\raw\foo.html -o foo.md ``` -**Symptom of getting this wrong:** `[Errno 2] No such file or directory: '/c:/Users/...'` — the container literally tried to open the host-shaped path. The fix is path translation, not URL encoding. +No mount caveats: the CLI is a normal local process. The old Docker `-v` mount translation and `/c:/Users/...` `[Errno 2]` symptom no longer apply. -**If the file falls outside the mount:** either copy it into the mounted tree, or extend the mount in `~/.claude.json` (a Claude restart is required for MCP changes to take effect — MCP servers are spawned at session start). - -**Filenames.** Non-ASCII filenames (Cyrillic, etc.) inside `file://` URIs are flaky across the URL-encode → urllib → Docker → host-FS chain. Rename to Latin kebab-case **before** calling markitdown. +**Filenames.** Non-ASCII filenames (Cyrillic, etc.) still travel better as Latin kebab-case through downstream wiki/ingest steps. Rename to Latin kebab-case before saving the output, per `.wiki/CLAUDE.md` naming rules. ## When to use @@ -47,18 +43,19 @@ file:///workdir/modular/heart-and-mask/.wiki/raw/foo.html - You only need a *summary* or an *answer about* a page → use **WebFetch** (cheaper, runs through a small model, returns prose). - The URI is GitHub/PR/issue/release content → use `gh` CLI (richer metadata, structured output). - The URI is private/authenticated (GDocs, Confluence, Jira, Slack, Notion, `share.google/*` sign-in walls) → markitdown receives the **public-facing fallback page** (sign-in screen, cookie banner) and returns *that* as markdown. Verify the result is real content before saving. -- **The URI is a browser-rendered web page the user is already viewing** → ask the user to capture it via **Obsidian Web Clipper** (browser extension, runs Readability extraction client-side) and drop the resulting `.md` into `raw/`. Web Clipper output is dramatically cleaner than markitdown's HTML pass — no nav chrome, no sidebar history, no cookie banners — plus it carries YAML frontmatter (title / source URL / date) out of the box. Reserves markitdown for things browsers can't easily save (PDF, DOCX, PPTX, XLSX, EPUB, file:// resources). Note: rename the resulting file to Latin kebab-case before ingest (Web Clipper preserves the page `` verbatim, often non-ASCII). +- **The URI is a browser-rendered web page the user is already viewing** → ask the user to capture it via **Obsidian Web Clipper** (browser extension, runs Readability extraction client-side) and drop the resulting `.md` into `raw/`. Web Clipper output is dramatically cleaner than markitdown's HTML pass — no nav chrome, no sidebar history, no cookie banners — plus it carries YAML frontmatter (title / source URL / date) out of the box. Reserves markitdown for things browsers can't easily save (PDF, DOCX, PPTX, XLSX, EPUB, local files). Note: rename the resulting file to Latin kebab-case before ingest (Web Clipper preserves the page `<title>` verbatim, often non-ASCII). ## Pattern: ingest a remote source into a wiki ``` -1. mcp__markitdown__convert_to_markdown(uri="https://example.com/foo.pdf") -2. Inspect the head of the result. If it looks like a sign-in/cookie/consent page, abort — ask the user for an alternative (manual save, paste, authenticated MCP). -3. Write the result to .wiki/raw/<slug>.md (kebab-case, Latin only). -4. Register the new file in .wiki/raw/README.md. -5. Hand off to the wiki ingest workflow (creates sources/<slug>.md summary + entity/concept updates). +1. markitdown "https://example.com/foo.pdf" -o .wiki/raw/<slug>.md (kebab-case, Latin only) +2. Inspect the head of the result. If it looks like a sign-in/cookie/consent page, abort — ask the user for an alternative (manual save, paste, authenticated source). +3. Register the new file in .wiki/raw/README.md. +4. Hand off to the wiki ingest workflow (creates sources/<slug>.md summary + entity/concept updates). ``` +For a huge (book-length) document, write straight to a file with `-o` and summarize *from the saved file* — do not pipe the whole markdown through working context. + ## Common gotchas | Symptom | Cause | Fix | @@ -66,8 +63,8 @@ file:///workdir/modular/heart-and-mask/.wiki/raw/foo.html | Output is a Google/Microsoft sign-in page in some random language | URI behind auth wall | Ask user to export the content manually (Save as PDF, copy-paste) and put it in `raw/` | | Output is mostly nav/cookie banner text | Site is JS-rendered or anti-bot | Try the cached or print URL; or ask user for HTML export | | Output lacks images / diagrams | Markdown is text-only by design | Save the original asset separately under `raw/assets/`; reference it from the `sources/` summary | -| Tool not available in session | MCP server not loaded | Confirm `mcp__markitdown__convert_to_markdown` appears via ToolSearch; load with `select:mcp__markitdown__convert_to_markdown` | -| Huge output (book-length) | Whole document converted in one call | Save raw, then summarize *from the saved file* — do not hold the entire markdown in working context | +| `markitdown: command not found` | CLI not on `PATH` | Confirm with `markitdown --version` (expect `markitdown 0.1.6`); install with `pip install markitdown[all]` if missing | +| Huge output (book-length) | Whole document converted in one call | Use `-o <file>` to save raw, then summarize *from the saved file* — do not hold the entire markdown in working context | ## Quick contrast with WebFetch and Web Clipper