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
82 changes: 43 additions & 39 deletions .claude/skills/pipelex-mcp-source/SKILL.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ temp/
.pytest_cache/
.mypy_cache/
.ruff_cache/
# The plugin copies `make claude-local-mcp` renders, one per workshop.
.local-mcp/
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

## [Unreleased]

### Added

- **`make claude-local-mcp` and `make codex-local-mcp`**: one command starts Claude Code or Codex with the Pipelex tools of a local `pipelex-mcp` checkout, `MCP=<path>`, which it builds first, or of a published version, `MCP_VERSION=<version>`, and changes no file git tracks. Claude Code runs this checkout's skills from a copy rendered into the ignored `.local-mcp/` directory, loaded for that session in place of the installed plugin; Codex keeps its installed skills and has its `pipelex` server entry overridden. Both take the key from `PIPELEX_API_KEY` in the shell and warn when it is unset. The repository's `/pipelex-mcp-source` skill no longer edits `targets/defaults.toml` to switch workshops: it reports which version each deployment serves and names the target to run.

### Changed

- **`pipelex-explain` points at the method graph page**: the Pipelex tools are about to write a method's flowchart, `method-graph.html`, beside the files a validation reads by path, and `pipelex-explain` now gives that page's path before its text flow, so the whole method can be opened in a browser. The skill still writes nothing of its own, and the page is the one file an explanation of a bundle on disk leaves. `pipelex-catalog`'s check of whether a bundle would save asks the tools not to write the page, since that check is a question and writes no file.

### Fixed

- **The install page's Codex override keeps the key**: an entry of the same name in `~/.codex/config.toml`, or a `-c` override, replaces the plugin's entry whole, including the variable names it forwards, so the one-invocation `-c` form the page gave started the Pipelex tools without `PIPELEX_API_KEY`. The page now says an override passes the key only through its own `env` table or `env_vars`, and gives the `-c` form with `env_vars`.

## [0.9.2] - 2026-09-25

### Fixed
Expand Down
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ scripts/
├── gen_skill_docs.py # Template renderer (multi-target)
├── check.py # Validation / freshness / packaging checks, the hook bundle's provenance guard included
├── check_hook_fresh.py # Release gate: the vendored check.mjs against npm's latest engine and a rebuild in ../pipelex-sdk-js
└── hook_bundle.py # Reads the hook bundle's provenance banner and compares bundles below it
├── hook_bundle.py # Reads the hook bundle's provenance banner and compares bundles below it
└── local_mcp.py # Starts Claude Code or Codex on another pipelex-mcp workshop (`make claude-local-mcp` / `codex-local-mcp`)
tests/unit/ # Unit tests for renderer + checks, and the hook's sweep over the corpus
tests/data/mthds-corpus/ # Vendored MTHDS Test Corpus (generated by the workspace's corpus sync; never edited here)
tests/recipes/ # Opt-in: executes the synthetic-inputs recipes (`make test-recipes`)
Expand Down Expand Up @@ -111,6 +112,8 @@ make agent-test # Run unit tests quietly (output only on failure) — prefe
make gen-skill-docs # Build default target (prod); use TARGET=codex for others
make vendor-hook # Rebuild check.mjs in ../pipelex-sdk-js and copy it into templates/hooks/assets/
make check-hook-fresh # Release gate: fail when check.mjs is behind npm's tools-wasm or a rebuild in ../pipelex-sdk-js
make claude-local-mcp # Start Claude Code on this checkout's skills with another workshop: MCP=<pipelex-mcp checkout> or MCP_VERSION=x.y.z
make codex-local-mcp # The same for Codex, overriding only its pipelex server entry; neither touches a tracked file
```

### Editing workflow
Expand Down Expand Up @@ -177,6 +180,6 @@ So there is nothing to enable — the bundled hook loads on its own (hooks are S

## Key dependency

The plugin imports nothing and requires no install. Validation rides on the vendored `check.mjs` bundle (wasm engine + `@pipelex/sdk` → hosted API) and, for the MCP-backed skills (`pipelex-design`, `pipelex-organize`, `pipelex-edit`, `pipelex-inputs`, `pipelex-run`, `pipelex-catalog`, `pipelex-integrate`), on the plugin-declared `pipelex-mcp` server (tools `mthds_validate` / `mthds_inputs_template`; `mthds_codegen`, whose write arm — `output_dir`, relative to the workshop's working directory — writes `pipelex-integrate`'s generated trees to disk so no artifact byte passes through the model; `mthds_prepare_inputs`, which uploads `pipelex-inputs`' file-bearing values to Pipelex storage and rewrites them to `pipelex-storage://` references so a run can reach them; plus the `mthds_run` family, which runs a method only in `pipelex-run` — `pipelex-inputs` ends by offering the run and hands it over, and `pipelex-synthetic-inputs` calls it for one thing, the image generation that makes a photograph — and `mthds_list_methods` / `mthds_save_method` / `mthds_get_method`, which are `pipelex-catalog`'s: the last two are **workshop-only**, the save reads, validates and stores one bundle in a single call and finishes by writing the `pipelex-method.json` link that makes the next save an update, and the get brings a saved method's sources to disk. Both refuse rather than overwrite, and the link file is the workshop's to write — no skill writes one. `mthds_get_method`'s **inline** arm is `pipelex-explain`'s instead, and the split is the whole of it: called with an `output_dir` it writes a pull, which is `pipelex-catalog`'s gesture, and called without one it returns the source for a skill that writes nothing — which is why a strictly read-only skill can hold this tool at all; declared in the Claude and Codex manifests, and shipped on Vibe as the `mcp/vibe-mcp.toml` config fragment the user copies into `~/.vibe/config.toml`). The baked declaration is the **local workshop launcher** — `npx -y @pipelex/mcp@latest` over stdio, from the `[vars.mcp_server]` block in `targets/defaults.toml` — never a hosted URL: the hosted console is a connector users add in their host's own UI (see `docs/install.md`, "Which app takes what", and `docs/decisions.md`). Credential delivery: on Claude the manifest's `userConfig` prompts for the API key / base URL at enable time (keychain-stored) and the MCP entry spawns the `launch-pipelex-mcp.sh` wrapper, which receives them as `PIPELEX_PLUGIN_*` via `${user_config.*}` substitution and promotes each to `PIPELEX_API_KEY`/`PIPELEX_BASE_URL` **only when non-empty** — the canonical credential channel (required for Claude Desktop, which carries no shell env), with the non-empty guard keeping an unfilled option from shadowing a shell-exported key; injecting `PIPELEX_*` directly instead makes an empty option surface as a config-class `Unauthorized` that hard-stops every MCP-backed skill; on Codex the manifest forwards `PIPELEX_API_KEY`/`PIPELEX_BASE_URL` by name via `env_vars` because Codex whitelist-filters MCP spawn env; on Vibe the user writes the key into the fragment's `env` table, because Vibe also spawns stdio servers with a minimal environment and expands no variables in its config. Dev override: point `command`/`args` at a local checkout in `targets/defaults.toml` + `make build` on Claude; a same-named `[mcp_servers.pipelex]` entry in `~/.codex/config.toml` on Codex.
The plugin imports nothing and requires no install. Validation rides on the vendored `check.mjs` bundle (wasm engine + `@pipelex/sdk` → hosted API) and, for the MCP-backed skills (`pipelex-design`, `pipelex-organize`, `pipelex-edit`, `pipelex-inputs`, `pipelex-run`, `pipelex-catalog`, `pipelex-integrate`), on the plugin-declared `pipelex-mcp` server (tools `mthds_validate` / `mthds_inputs_template`; `mthds_codegen`, whose write arm — `output_dir`, relative to the workshop's working directory — writes `pipelex-integrate`'s generated trees to disk so no artifact byte passes through the model; `mthds_prepare_inputs`, which uploads `pipelex-inputs`' file-bearing values to Pipelex storage and rewrites them to `pipelex-storage://` references so a run can reach them; plus the `mthds_run` family, which runs a method only in `pipelex-run` — `pipelex-inputs` ends by offering the run and hands it over, and `pipelex-synthetic-inputs` calls it for one thing, the image generation that makes a photograph — and `mthds_list_methods` / `mthds_save_method` / `mthds_get_method`, which are `pipelex-catalog`'s: the last two are **workshop-only**, the save reads, validates and stores one bundle in a single call and finishes by writing the `pipelex-method.json` link that makes the next save an update, and the get brings a saved method's sources to disk. Both refuse rather than overwrite, and the link file is the workshop's to write — no skill writes one. `mthds_get_method`'s **inline** arm is `pipelex-explain`'s instead, and the split is the whole of it: called with an `output_dir` it writes a pull, which is `pipelex-catalog`'s gesture, and called without one it returns the source for a skill that writes nothing — which is why a strictly read-only skill can hold this tool at all; declared in the Claude and Codex manifests, and shipped on Vibe as the `mcp/vibe-mcp.toml` config fragment the user copies into `~/.vibe/config.toml`). The baked declaration is the **local workshop launcher** — `npx -y @pipelex/mcp@latest` over stdio, from the `[vars.mcp_server]` block in `targets/defaults.toml` — never a hosted URL: the hosted console is a connector users add in their host's own UI (see `docs/install.md`, "Which app takes what", and `docs/decisions.md`). Credential delivery: on Claude the manifest's `userConfig` prompts for the API key / base URL at enable time (keychain-stored) and the MCP entry spawns the `launch-pipelex-mcp.sh` wrapper, which receives them as `PIPELEX_PLUGIN_*` via `${user_config.*}` substitution and promotes each to `PIPELEX_API_KEY`/`PIPELEX_BASE_URL` **only when non-empty** — the canonical credential channel (required for Claude Desktop, which carries no shell env), with the non-empty guard keeping an unfilled option from shadowing a shell-exported key; injecting `PIPELEX_*` directly instead makes an empty option surface as a config-class `Unauthorized` that hard-stops every MCP-backed skill; on Codex the manifest forwards `PIPELEX_API_KEY`/`PIPELEX_BASE_URL` by name via `env_vars` because Codex whitelist-filters MCP spawn env; on Vibe the user writes the key into the fragment's `env` table, because Vibe also spawns stdio servers with a minimal environment and expands no variables in its config. Dev override: `make claude-local-mcp` or `make codex-local-mcp`, never an edit of `targets/defaults.toml` (`docs/development.md`, "A local build of `pipelex-mcp`").

**`pipelex-synthetic-inputs` depends on none of that for code-rendered files.** Everything but a photograph is MCP-free — no tool, no key, no Pipelex service — and a photograph alone needs the workshop, whose absence stops that one file and nothing else. `pipelex-scaffold` is the other skill that declares no MCP tool: git, the method-app family's initializer and `make serve`, the ecosystem's initializers and its own two scripts are all it uses, and it leaves a method app running and hands every other project it creates to `pipelex-integrate`. For code-rendered files, the only dependency is a Python the skill can reach: `uv` with ephemeral `--with` packages on the normal rung, and a venv it creates itself under `${XDG_CACHE_HOME:-$HOME/.cache}/pipelex-plugins/synth-venv` when `uv` is absent. Swapping the runner line is the *only* difference between the two rungs, and `tests/recipes` proves it by running real recipes through both. When neither rung is reachable the skill stops with the exact missing piece and, called from `pipelex-inputs`, returns no path so that one input is left unfilled rather than aborting the flow. Keep it and `pipelex-scaffold` out of the `MCP_SKILLS` tuple in `tests/unit/test_gen_skill_docs.py`, and `pipelex-explain` and `pipelex-lab` with them — the lab frames and writes keys without the workshop and stops only before its loop. For explain, the workshop is **optional** — it adds a verdict line to a bundle on disk, reads a saved method's source in full through `mthds_get_method`'s inline arm, and is the only way to reach a catalog id or a published address at all, but its absence never stops the skill from explaining local source. A workshop older than the release that brought `mthds_get_method` answers the other tools without it, so there a catalog id falls back to contract level, which is a narrower explanation and not a stop. The tuple asserts a hard stop, which is a different contract.
30 changes: 29 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ UV_MIN_VERSION = $(shell grep -m1 'required-version' pyproject.toml | sed -E 's/
format lint ruff-format ruff-lint pyright mypy fix-unused-imports fui \
test agent-test test-recipes tp \
cleanderived cleanenv cleanall reinstall ri \
codex-use-local codex-use-official codex-refresh codex-status
codex-use-local codex-use-official codex-refresh codex-status \
claude-local-mcp codex-local-mcp

##########################################################################################
### SETUP
Expand Down Expand Up @@ -224,3 +225,30 @@ codex-status: ## Show which source is currently registered for the Codex pipelex
"$$HOME/.codex/config.toml" \
| grep -E "^(source_type|source|last_revision|last_updated)" \
|| echo "• No '$(CODEX_MARKETPLACE_NAME)' marketplace registered."

##########################################################################################
### LOCAL WORKSHOP (start an agent on a pipelex-mcp the plugin does not ship)
##########################################################################################

# MCP is a pipelex-mcp checkout or worktree, whose workshop scripts/local_mcp.py builds
# with the checkout's own `make build-local` first; MCP_VERSION, a published @pipelex/mcp
# version or dist-tag, replaces it and builds nothing. WORKDIR is where the session
# starts, which is where the workshop resolves a { path } file. ARGS goes to claude or
# codex as it is. Neither target writes a tracked file: see "A local build of
# pipelex-mcp" in docs/development.md.
#
# Each is read from make's command line alone, and never assigned here: the names are
# generic, so a shell variable called WORKDIR or ARGS belongs to some other tool, and it
# reaches the session as it was. Each is taken unexpanded, so make acts on no `$` in it,
# and a path goes as one single-quoted shell word, which the shell does not act on either.
local_mcp_given = $(if $(filter command line,$(origin $(1))),$(value $(1)),$(2))
local_mcp_word = '$(subst ','\'',$(call local_mcp_given,$(1),$(2)))'

LOCAL_MCP_SOURCE = $(if $(call local_mcp_given,MCP_VERSION,),--mcp-version $(call local_mcp_word,MCP_VERSION,),--mcp $(call local_mcp_word,MCP,../pipelex-mcp))
LOCAL_MCP_OPTIONS = $(LOCAL_MCP_SOURCE) --workdir $(call local_mcp_word,WORKDIR,.) -- $(call local_mcp_given,ARGS,)

claude-local-mcp: install ## Start Claude Code on this checkout's skills with another workshop (MCP=path or MCP_VERSION=x.y.z)
@$(VENV_PYTHON) scripts/local_mcp.py claude $(LOCAL_MCP_OPTIONS)

codex-local-mcp: install ## Start Codex with another workshop in place of the plugin's (MCP=path or MCP_VERSION=x.y.z)
@$(VENV_PYTHON) scripts/local_mcp.py codex $(LOCAL_MCP_OPTIONS)
Loading
Loading