From 40409be4d69efa2d48a03cd2850e8ebd301deb31 Mon Sep 17 00:00:00 2001 From: Ben Barber Date: Wed, 29 Jul 2026 13:32:54 -0400 Subject: [PATCH 1/7] =?UTF-8?q?feat(plugin):=20Claude=20Code=20plugin=20?= =?UTF-8?q?=E2=80=94=20/path:share=20and=20/path:query=20with=20self-insta?= =?UTF-8?q?lling=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New .claude-plugin/marketplace.json (marketplace 'toolpath') + plugins/claude-code/ (plugin 'path'). Install: /plugin marketplace add empathic/toolpath, then /plugin install path@toolpath. Future harness integrations land as siblings under plugins/. No committed binaries: both commands run through scripts/ensure-path.sh, which prefers an existing Toolpath install (identity-checked via --help), else ~/.local/bin/path, else ~/.toolpath/bin/path, else downloads the latest GitHub release (sha256-verified, adapted from install.sh) and installs globally to ~/.local/bin — diverting to ~/.toolpath/bin when a foreign binary named 'path' claims the name. MIN_VERSION warns when the resolved CLI predates the command docs. Command docs encode two discovered constraints: inline !-context commands and model-issued Bash must avoid $PWD (the permission checker rejects commands it cannot statically analyze — hence the 'sessions' helper mode), and --project must always be absolute (path-cli does not canonicalize relative values; '.' silently matches nothing). Tests: scripts/test-plugin.sh (manifest consistency + offline bootstrap tests against a stubbed curl/release: resolution order, exec/sessions modes, min-version warning, clean install, foreign-name fallback, checksum rejection), wired into quality_gates.sh as the new 'plugin' gate; plugin scripts join the shellcheck gate. Verified end-to-end headless: claude --plugin-dir with /path:query answering from the real cache and /path:share resolving the live session and failing cleanly against an unreachable server. --- .claude-plugin/marketplace.json | 21 ++ CHANGELOG.md | 33 +++ CLAUDE.md | 6 + README.md | 11 + .../claude-code/.claude-plugin/plugin.json | 16 ++ plugins/claude-code/README.md | 55 +++++ plugins/claude-code/commands/query.md | 72 ++++++ plugins/claude-code/commands/share.md | 53 +++++ plugins/claude-code/scripts/ensure-path.sh | 216 ++++++++++++++++++ scripts/quality_gates.sh | 11 +- scripts/test-plugin.sh | 198 ++++++++++++++++ 11 files changed, 689 insertions(+), 3 deletions(-) create mode 100644 .claude-plugin/marketplace.json create mode 100644 plugins/claude-code/.claude-plugin/plugin.json create mode 100644 plugins/claude-code/README.md create mode 100644 plugins/claude-code/commands/query.md create mode 100644 plugins/claude-code/commands/share.md create mode 100755 plugins/claude-code/scripts/ensure-path.sh create mode 100755 scripts/test-plugin.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 00000000..3fa236c8 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", + "name": "toolpath", + "version": "0.1.0", + "description": "Toolpath — provenance for AI coding sessions. Share agent sessions to Pathbase and query your local session history via the path CLI", + "owner": { + "name": "Empathic" + }, + "plugins": [ + { + "name": "path", + "description": "Slash commands for the Toolpath path CLI: /path:share uploads an agent session to Pathbase, /path:query runs jaq queries over your local session cache. Installs the path binary globally on first use", + "version": "0.1.0", + "author": { + "name": "Empathic" + }, + "source": "./plugins/claude-code", + "category": "productivity" + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index dd31e345..f9276ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,39 @@ API. Together they broke resume for most projected sessions. resume, and without one the first appended entry landed on the same line as the last projected entry, corrupting the JSONL. +## Claude Code plugin — `/path:share` and `/path:query` — 2026-07-29 + +Adds a Claude Code plugin so users get the `path` CLI without a manual +install: `/plugin marketplace add empathic/toolpath`, then +`/plugin install path@toolpath`. + +- **`.claude-plugin/marketplace.json`** (marketplace `toolpath`) + + **`plugins/claude-code/`** (plugin `path` 0.1.0). Future harness + integrations land as siblings under `plugins/`. +- Two slash commands: `/path:share` uploads an agent session to Pathbase + (defaults to the current conversation; takes a session hint, + `--harness`, and the `share` pass-through flags) and `/path:query` + answers questions about the local session cache, translating plain + English into jaq filters. +- No committed binaries: both commands run through + `plugins/claude-code/scripts/ensure-path.sh`, which prefers an + existing Toolpath install, else downloads the latest GitHub release + (sha256-verified, same logic as `install.sh`) and installs globally to + `~/.local/bin` (`~/.toolpath/bin` when a foreign `path` binary claims + the name). Warns when the resolved CLI predates the plugin's + `MIN_VERSION`. +- Command docs encode two discovered constraints: inline `!` context + commands and model-issued Bash must avoid `$PWD` (Claude Code's + permission checker rejects non-statically-analyzable commands — the + `sessions` helper mode exists for this), and `--project` must be + absolute (path-cli does not canonicalize relative values; `.` silently + matches nothing). +- Tests: `scripts/test-plugin.sh` — manifest consistency plus offline + bootstrap tests against a stubbed curl/release (resolution order, exec + and sessions modes, min-version warning, clean install, foreign-name + fallback, checksum rejection) — wired into `quality_gates.sh` as the + new `plugin` gate; plugin shell scripts join the shellcheck gate. + ## `path p cache sync` — incremental session ingestion — 2026-07-16 Adds `path p cache sync [types…]`, the first step toward a cache that diff --git a/CLAUDE.md b/CLAUDE.md index 2de9f950..8baa15d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,11 @@ crates/ toolpath-cli/ # deprecated shim that re-exports path-cli (excluded from the workspace; see below) pathbase-client/ # progenitor-derived client for the Pathbase HTTP API # (spec at crates/pathbase-client/openapi.json; refresh via scripts/refresh-pathbase-openapi.sh) +.claude-plugin/ + marketplace.json # Claude Code plugin marketplace (add with `/plugin marketplace add empathic/toolpath`) +plugins/ + claude-code/ # Claude Code plugin "path": /path:share + /path:query, bundles the CLI + # (future harness integrations land as siblings: plugins/gemini-cli/, plugins/codex/, ...) schema/toolpath.schema.json # JSON Schema for the toolpath format examples/*.json # 12 example documents (step, path, graph) RFC.md # full format specification @@ -281,4 +286,5 @@ Build the site after changes: `cd site && pnpm run build` (should produce 11 pag - `path resume ` is the inverse of `path share`. It accepts a Pathbase URL, an `owner/repo/slug` shorthand, a local toolpath JSON file, or a cache id; resolves it (caching URL fetches under `~/.toolpath/documents/` unless `--no-cache`); validates that the document is a single agent-bearing `Path`; then opens an `fzf` harness picker (skipped with `--harness X`). The picker pre-selects the source harness inferred from `path.meta.source` (`claude-code`/`gemini-cli`/`codex`/`opencode`/`pi`) when it's installed. After picking, `path resume` projects the session into the harness's on-disk layout under the chosen working directory (default: shell cwd; override with `-C, --cwd P`) and `execvp`'s the harness's resume command (`claude -r ` / `gemini --resume ` / `codex resume ` / `opencode --session ` / `pi --session `). On Windows it spawns and waits, propagating the exit code. The exec is mockable via `cmd_resume::ExecStrategy` — production uses `RealExec`; integration tests use `RecordingExec` to capture the recipe without launching a real harness. - `path query` does not load the whole cache into memory when it can avoid it. `crates/path-cli/src/query/plan.rs` parses the jaq filter into jaq's own AST (`jaq_core::load::parse::Term`) and classifies it into a `Plan`: `PerFileStream` (`.[] | g` element-wise work — run per document, print as you go), `Decompose { reduce }` (algebraic aggregations — run the whole filter per file, concatenate the per-file outputs, then run a derived combine: `map`→`add` (array concat), top-N `sort_by(k)|.[:N]`→`add | sort_by(k)|.[:N]`, `length`→`add` over exact integer counts), or `Slurp` (the always-correct whole-array fallback). Recognition is conservative — a non-distributive prefix like `unique`/`group_by` slurps, and so do scalar `add` (float sums re-associate across per-file partials), `min`/`max` (`[] | min == null` poisons the merge), and any unrecognized tail — so **the planner never changes an answer** — `crates/path-cli/src/query/filter.rs` tests assert streamed output equals slurp byte-for-byte. `filter::execute` compiles the filter once (jaq's compiled `Filter` is fully owned, so it's reused across files) and drives the plan; `mod.rs::stream_files` yields one document's wrapped steps at a time. `TOOLPATH_QUERY_EXPLAIN=1` prints the chosen plan to stderr. No user-facing flag — it's automatic. Tie-break caveat: a streamed top-N matches slurp's *ranking*, but boundary ties may resolve to different specific rows. - Cache sync: `path p cache sync [types…]` (`crates/path-cli/src/artifact.rs`: `ArtifactType` + `ArtifactRef` + the stamp helpers; `sync/engine.rs`: manifest + ingestion loop, no UI — it reports through a `SyncObserver` trait, `&mut ()` for a silent sync; `sync/sources.rs`: an `ArtifactSource` trait — enumerate / stamp / derive — with one impl per provider, so the engine never matches on artifact type; `cmd_cache.rs`: the stderr progress line + summary) incrementally ingests artifacts into the cache — no args syncs every artifact type. Change detection is **stat-level**: each artifact is enumerated as an `ArtifactRef` whose fingerprint is the source file's mtime + size (claude: the *whole session chain* — max segment mtime + summed segment sizes via `claude_chain_stamp`, because Claude Code rotates to a new file on continuation while the chain keeps its oldest segment's id, so appends land in the newest file, not the head; the chain comes from the same cached index `list_conversations` builds; codex: rollout file, id from the stem's trailing UUID; pi: session file, id from a one-line header peek; copilot: `session-state//events.jsonl`, pure read-dir + stat) or the DB row's updated-at (opencode: header-only `SELECT time_updated`; cursor: composer headers' `lastUpdatedAt`, bubble-less drafts skipped, workspace-less composers *included* unlike `share`). Gemini enumerates via `PathResolver::list_session_entries` (`toolpath-gemini` 0.6.1), whose identity peek is bounded to the first 4 KiB of a main file. Deciding "nothing changed" reads no session bodies — a no-op sync is milliseconds. Changed/new artifacts derive through the same provider managers (each source calls the `derive_*_session_with` helpers in `derive.rs`). Manifest at `~/.toolpath/manifest.json`: artifact type → artifact id → `{path?, cache_id, modified?, size?, synced_at}`; atomic temp+rename writes, `0600`, checkpointed every 10 writes (interruption-safe: a killed run keeps nearly everything it derived, and derives run newest-first so partial progress covers the sessions that matter most); writers serialize on an advisory lock (`manifest.json.lock`) and every write is a locked read-merge-save — checkpoints merge only the records the run wrote — so concurrent invocations (query auto-syncs, imports) union their records instead of clobbering each other. Pending work reports progress on stderr (`\r`-updating ` done/total` on a TTY, a plain line every 25 items otherwise; no-op syncs stay silent). Sync always writes the cache with force — refresh semantics — and never deletes: artifacts removed upstream keep their cache docs and manifest records (archive, not mirror). Derivation failures warn and tally, they don't abort. A record's `cache_id` is *optional*: a record without one is "known, not materialized" — created when `p cache rm` evicts a doc (rm downgrades the record; the next sync re-materializes it, and sync also verifies the doc file actually exists before skipping, so even out-of-band deletions self-heal). Claude derives leave `DeriveConfig.project_path` unset so `path.base` comes from the session's own recorded cwd rather than the lossy slug. `path query` runs this sync implicitly before reading, scoped to its flags (`--source X` → that type; `--id`s → their prefixes; bare query → all types; `--input`-only → none), quiet unless something was ingested, degrading to the cache as-is if sync fails; `--no-sync` opts out. `p import` and `share` record what they write: every session derive carries a provenance `ArtifactRef` (stamped *before* the source is read, in `DerivedDoc.provenance`), and the cache-write sites call `sync::record_artifact` so the next sync sees those artifacts as unchanged instead of re-deriving them. Every import flow — explicit `--session`, picker multi-select, `--all`, and the most-recent fallbacks — loops the per-session helpers, so every session write is recorded; there is no bulk `derive_project` path in the CLI anymore, and `p import pi --all` now emits one Path per session like every other provider (it used to emit a single combined Graph). `--no-cache` paths record nothing: the manifest describes the cache. +- Claude Code plugin: `.claude-plugin/marketplace.json` (marketplace `toolpath`) + `plugins/claude-code/` (plugin `path`, so commands are `/path:share` and `/path:query`). The plugin does **not** commit binaries — both commands invoke the CLI through `plugins/claude-code/scripts/ensure-path.sh`, which prefers an existing Toolpath `path` on PATH (identity-checked via `--help`), else `~/.local/bin/path`, else `~/.toolpath/bin/path`, else downloads the latest GitHub release (sha256-verified, same logic as `scripts/install.sh`) and installs globally to `~/.local/bin` — falling back to `~/.toolpath/bin` when a foreign binary named `path` claims the name. Two hard-won constraints baked into the command docs: slash-command inline `!` context commands and model-issued Bash must not contain `$PWD`/variables (Claude Code's permission checker rejects commands it can't statically analyze — hence the `sessions` helper mode), and `--project` must always be an absolute path (path-cli does not canonicalize relative `--project` values; `.` silently matches nothing). Tests: `scripts/test-plugin.sh` (manifest consistency + offline bootstrap tests against a stubbed curl/release), wired in as the `plugin` quality gate; plugin shell scripts are shellchecked. Dev loop: `claude --plugin-dir ./plugins/claude-code`. Future harness integrations go under `plugins//` (only Claude Code plugins are marketplace entries; other harnesses distribute their own way). Version bumps: keep `plugins/claude-code/.claude-plugin/plugin.json` and the matching entry in `.claude-plugin/marketplace.json` in lockstep (test-plugin.sh asserts this); the binary is unpinned (latest release) with `MIN_VERSION` in ensure-path.sh naming the oldest CLI the command docs support. - `ArtifactType` (`crates/path-cli/src/artifact.rs`) is the general enum naming artifact sources — the seven agent harnesses (incl. copilot) plus `Git` (8 variants). Git artifacts are *recorded* in the manifest by `p import git` (id `-`, `path` = the repo directory) but never *discovered* — there is no machine-wide registry of repos — so sync reports them and leaves them alone. Github and pathbase are deliberately not artifact types: they are remote services, not local artifact sources, and their imports stay out of the manifest. It derives `clap::ValueEnum` and is used by `p cache sync` types, the sync manifest keys, `ArtifactRow.artifact_type`, and `cmd_import`'s cache-id prefixes (`name()` is both the manifest key and the `make_id` source string). The deliberately parallel `Harness` enum (`crates/path-cli/src/harness.rs`, alongside `HarnessBundle`) names the seven agent *runtimes* — things sessions can be shared from and resumed into — and is what `share`/`resume` `--harness` take, so future non-harness artifact types stay unrepresentable there (you can't resume into a git repo). `Harness::artifact_type()` maps into the general enum; `ArtifactType::harness()` is the partial inverse. Keep new code on `ArtifactType` unless it's genuinely harness-only. diff --git a/README.md b/README.md index f587db82..d3a82eba 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ cargo install path-cli Both install a binary called `path`. +Claude Code users can skip the manual install entirely — the Toolpath plugin +bundles the CLI (downloaded and installed globally on first use) and adds +`/path:share` and `/path:query` slash commands: + +``` +/plugin marketplace add empathic/toolpath +/plugin install path@toolpath +``` + +See [plugins/claude-code](plugins/claude-code/) for details. + > The older `toolpath-cli` crate name still works — `cargo install toolpath-cli` is now a thin shim that pulls in `path-cli` and installs the same binary. New users should reach for `path-cli` directly; the shim will eventually be retired. ## Workspace diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json new file mode 100644 index 00000000..2feadb04 --- /dev/null +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -0,0 +1,16 @@ +{ + "name": "path", + "version": "0.1.0", + "description": "Toolpath for Claude Code — /path:share uploads an agent session to Pathbase, /path:query answers questions about your local session history. Bundles the path CLI, installed globally on first use", + "author": { + "name": "Empathic" + }, + "keywords": [ + "toolpath", + "provenance", + "sessions", + "pathbase", + "share", + "query" + ] +} diff --git a/plugins/claude-code/README.md b/plugins/claude-code/README.md new file mode 100644 index 00000000..7982d5e6 --- /dev/null +++ b/plugins/claude-code/README.md @@ -0,0 +1,55 @@ +# Toolpath plugin for Claude Code + +Slash commands for the [Toolpath](https://toolpath.net) `path` CLI, with no +manual install step: the plugin resolves the binary on first use — preferring +an existing install, otherwise downloading the latest release and installing +it globally to `~/.local/bin`. + +## Install + +Inside Claude Code: + +``` +/plugin marketplace add empathic/toolpath +/plugin install path@toolpath +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `/path:share` | Share an agent session to Pathbase and get a link. With no arguments it shares the current conversation; pass a hint to pick another session, `--harness ` for another harness, and `--anon` / `--public` / `--repo` / `--name` / `--url` to control the upload. | +| `/path:query` | Ask questions about your local agent-session history. Takes plain English (translated to a jaq filter) or a jaq filter verbatim, plus `--source` / `--project` scoping. | + +## How the binary is bundled + +Both commands run the CLI through `scripts/ensure-path.sh`, which resolves in +order: + +1. `path` already on `PATH`, if it identifies as the Toolpath CLI (an + existing cargo / Homebrew / install.sh install is always preferred) +2. `~/.local/bin/path` (or `$TOOLPATH_INSTALL_DIR/path`) +3. `~/.toolpath/bin/path` (or `$TOOLPATH_CONFIG_DIR/bin/path`) +4. Otherwise it downloads the latest GitHub release for your platform, + verifies the sha256 checksum, and installs to `~/.local/bin` — falling + back to `~/.toolpath/bin` if an unrelated binary named `path` already + claims the `~/.local/bin` name. + +The install is global: after first use, `path` works from your own shell too +(the script prints a PATH hint if `~/.local/bin` isn't on it). Platforms +without prebuilt binaries (Intel Mac, native Windows) get a +`cargo install path-cli` recipe instead. + +## Development + +Run Claude Code with the local plugin: + +```bash +claude --plugin-dir ./plugins/claude-code +``` + +Validate manifests and exercise the bootstrap logic offline: + +```bash +scripts/test-plugin.sh # from the repo root +``` diff --git a/plugins/claude-code/commands/query.md b/plugins/claude-code/commands/query.md new file mode 100644 index 00000000..26f0c358 --- /dev/null +++ b/plugins/claude-code/commands/query.md @@ -0,0 +1,72 @@ +--- +description: Query your local agent-session history with plain English or a jaq filter +argument-hint: " [--source claude|codex|...]" +allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh:*) +--- + +## Context + +- Toolpath CLI: !`"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh"` + +## Your task + +Answer the user's question by querying the local Toolpath cache — the on-disk archive of their agent coding sessions (Claude Code, Gemini CLI, Codex, Copilot, opencode, Cursor, Pi) and derived git/GitHub history. Translate plain English into a jaq (jq-compatible) filter; run a filter verbatim if the user already wrote one. + +User arguments: $ARGUMENTS + +Always invoke the CLI through the wrapper (it resolves or installs the binary regardless of PATH): + +``` +"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh" exec query [--source ] [--project ] [-r|-c] '' +``` + +### Data model + +`query` flattens every step of every cached document into one JSON array; the filter runs over that array. Each element wraps one step: + +```json +{ + "cache_id": "claude-path-claude-code-6987afe8", + "step": { "id": "...", "actor": "agent:claude-opus-5", "timestamp": "2026-07-29T15:44:20.239Z" }, + "change": [ { "artifact": null, "structural": { "type": "conversation.append", "role": "user", "text": "..." } } ], + "dead_end": false, + "path": { "id": "...", "base": { }, "meta": { "source": "claude-code" } } +} +``` + +- `step.actor` is `human:`, `agent:` (e.g. `agent:claude-opus-5`, `agent:gpt-5.2-codex`), or `tool:` (e.g. `tool:claude-code`); to filter by harness use `path.meta.source` or `--source`, not the actor. +- `change[].structural.type` for agent sessions is one of `conversation.append` (role, text, thinking, tool_uses, token_usage, ...), `conversation.event`, `conversation.compact`, or `file.write`; git/GitHub-derived steps carry file diffs instead. +- `dead_end` marks steps not on the ancestry of the path head (abandoned work). +- For the full field reference run `exec kind agent-coding-session`. + +### Scoping and freshness + +- `--source claude|gemini|codex|copilot|opencode|cursor|pi|git|github` narrows by harness, `--project ` by project, `--kind ` by path kind, `--id ` by document; `--input ` queries a file without touching the cache. +- `-r` prints raw strings (like `jq -r`); `-c` forces compact output. +- path-cli 0.16+ auto-syncs the queried scope from the installed harnesses before running. On older versions, if results look empty or stale, fill the cache first with `exec p cache sync` (0.16+) or `exec p import claude --project --force`, and inspect it with `exec p cache ls`. Always write `--project` as a literal absolute path — `$PWD` fails the permission check, and relative paths match nothing. + +### Example filters + +```bash +# ids of abandoned (dead-end) steps +'map(select(.dead_end)) | map(.step.id)' + +# sessions where a user prompt mentions "tailscale" +'[.[] | select(any(.change[]?.structural; + .type == "conversation.append" and .role == "user" + and ((.text // "") | test("tailscale"; "i")))) + | .cache_id] | unique' + +# steps that burned >50k input tokens in one message +'map(select(any(.change[]?.structural.token_usage; .input_tokens > 50000)))' + +# step count per source document, largest first +'group_by(.cache_id) | map({id: .[0].cache_id, steps: length}) | sort_by(-.steps)' + +# agent (vs. human) steps this month +'map(select((.step.actor | startswith("agent:")) and .step.timestamp > "2026-07")) | length' +``` + +### Report + +Interpret the JSON for the user — answer the question in prose, quoting the relevant ids/values, rather than dumping raw output. Show the filter you ran so they can refine it. If a filter errors, fix it and retry rather than reporting the syntax error. diff --git a/plugins/claude-code/commands/share.md b/plugins/claude-code/commands/share.md new file mode 100644 index 00000000..a82d1fa7 --- /dev/null +++ b/plugins/claude-code/commands/share.md @@ -0,0 +1,53 @@ +--- +description: Share an agent session to Pathbase and get a link +argument-hint: "[session hint] [--anon] [--public] [--harness ]" +allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh:*) +--- + +## Context + +- Toolpath CLI: !`"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh"` +- Auth: !`"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh" exec auth status` +- Claude Code sessions for this project, newest first: !`"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh" sessions` + +## Your task + +Share an agent session to Pathbase with the Toolpath CLI and report the resulting URL. + +User arguments: $ARGUMENTS + +Always invoke the CLI through the wrapper (it resolves or installs the binary regardless of PATH): + +``` +"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh" exec +``` + +In every command you run, write paths as literal absolute strings — never `$PWD` or other variables (they fail the permission check), and never relative paths like `.` (the CLI matches them against nothing). + +### Choose the session + +The session list above is TSV: project, session id, timestamp, step count, first user message. + +- **No arguments**: share the current conversation. The list is newest-first and includes this live session, so take the first row's session id. +- **A textual hint**: match it against the first-user-message and session-id columns. If no row matches confidently, show the closest candidates and ask which one. +- **`--harness `** (claude / gemini / codex / copilot / opencode / cursor / pi): list that harness instead with `exec p list --format tsv`. For claude/gemini/pi add `--project ` and read the session id from column 2; for the others the session id is column 1. + +If the list above is empty, run `exec p list claude --format tsv` without `--project` and match rows by the project column — its decoded paths are lossy (`.`, `_`, and `/` all display as `/`), so compare loosely against the cwd. + +### Check auth + +If the Auth context shows no valid login and the user did not pass `--anon`, stop and ask: upload anonymously (`--anon` — unlisted, addressable only by UUID), or log in first? Logging in means the user runs `path auth login` in their own terminal (it needs an interactive code paste — never run it yourself), then re-runs `/path:share`. + +### Upload + +``` +"${CLAUDE_PLUGIN_ROOT}/scripts/ensure-path.sh" exec share --harness --session [flags] +``` + +- Add `--project ` for claude/gemini/pi session ids; omit it for codex/opencode/cursor/copilot. +- Pass through any of `--anon`, `--public`, `--repo `, `--name