Metadata-Version: 2.5
Name: claude-artifact-owner
Version: 0.1.0
Summary: Keep Claude Code sessions working on their artifacts after cswap switches claude.ai accounts
Project-URL: Homepage, https://github.com/cskiraly/claude-artifact-owner
Project-URL: Issues, https://github.com/cskiraly/claude-artifact-owner/issues
Author: Csaba Kiraly
License: MIT
License-File: LICENSE
Keywords: artifacts,claude,claude-code,cswap,hooks
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# claude-artifact-owner

Keep Claude Code sessions working on their artifacts after [cswap](https://github.com/realiti4/claude-swap)
switches claude.ai accounts.

An artifact belongs to the claude.ai account that published it. cswap switches the login that all ordinary Claude
Code sessions share, and they follow it at their next message. From then on, the artifacts a session published look
deleted to it: reads return "artifact not found — it may have been deleted, or it has not been shared with you", and
in auto mode the permission review can fail first ("could not look up whose artifact this is … HTTP 404"). Sessions
then guess at what happened, or publish a copy under a new link.

claude-artifact-owner is a pair of Claude Code hooks, run by its `artifact-owner` command. It records which account
owns each artifact, and runs an Artifact call on an artifact another of your accounts owns as that account, in a
short headless helper session. The session gets the call's result back.

**Experimental.** Tested with Claude Code 2.1.308 to 2.1.310 and cswap 0.28, on Linux. Not affiliated with Anthropic.

## Install

You need cswap with your accounts added, `cswap run` working for them (cswap's per-terminal session mode), and
Claude Code with the Artifact tool (a claude.ai login).

```
uv tool install claude-artifact-owner  # or: pipx install claude-artifact-owner
artifact-owner refresh                 # record who owns your existing artifacts: one helper session per account
artifact-owner hooks                   # print the hook entries for ~/.claude/settings.json
```

To install from GitHub instead: `uv tool install git+https://github.com/cskiraly/claude-artifact-owner`.

Add the two entries that `artifact-owner hooks` prints to the `hooks` of `~/.claude/settings.json` (next to any
`PreToolUse` and `PostToolUse` entries you already have). Then add this to `~/.claude/CLAUDE.md`, so that sessions
trust what the hook hands back (without it, a session may report a routed result as unverified):

```
- **artifact-owner** is a hook I set up for cswap account switches. When an Artifact call is on an
  artifact that one of my other claude.ai accounts owns, it runs the call as that account and hands
  back the result as a "hook error" that names artifact-owner. When it says it ran the call, the
  result is real and the call is done; when it says it couldn't, tell me.
```

Running sessions may need a restart, or a look at `/hooks`, before they use the new hooks.

## How it works

- **Record (PostToolUse on Artifact):** stores the account that owns each artifact, taken from publishes, reads that
  say `owned: true`, and `list` results. For a publish it also stores the session and the file. Records go to
  `~/.local/state/artifact-owner/owners.jsonl`.
- **Route (PreToolUse on Artifact, ArtifactComments, ArtifactData):** for a call that names a link, or a file this
  session published, whose owner isn't the account the session now runs as, it runs the same call in a helper
  session as the owner's cswap account (matched by email, and by organization when an email is in more than one
  account). It then denies the original call, with the helper's result as the reason ("the call is done").
  A result over 9000 characters is cut there, and the whole of it goes to a file in the session's scratchpad, which
  the reason names. Later calls are routed the same way. A routed publish is recorded for the calling session, so a
  later republish that names only the file finds the link.
- **Passes through:** `open`, `pin`, `unpin`, `quickstart`, calls without a link, and links with no recorded owner
  (run `artifact-owner refresh` when the log says "owner unknown"; it records up to 200 artifacts per account, the
  most recently opened or updated). Artifact deletes (the `delete` action) are never routed: the session is told to
  leave them to you. Other changes, such as ArtifactData writes and comments, are routed and get the usual
  review.
- **Helper:** `cswap run <owner> -- -p …` with Haiku, in safe mode (no CLAUDE.md, skills, plugins, hooks or MCP
  servers), with only the Artifact tools and Read, at most 8 turns, no saved transcript, and access to the calling
  session's scratchpad and the file's folder (`--add-dir`; its tools only read there, except that a publish with
  `edits` saves the edited file). It runs in auto mode with permission prompts off, so its calls get the usual
  review, and that review never lets it delete an artifact. A republish reads the live version first, as the tool
  requires. The result counts only if the helper made exactly the call asked for; any other call is logged. The
  Artifact tool is off in headless sessions; the helper turns it on with `CLAUDE_CODE_ARTIFACT=1`, a variable found
  in Claude Code itself and not documented, so a new version may change it.
- **Republishes:** only the session that published a page republishes it through the hook (a publish record of that
  session; what a transcript says doesn't count), or a session you allowed to. The helper doesn't know whether you
  asked for a change, and its review takes the request for yours: from Claude Code 2.1.310 it lets another session's
  republish of a private page through. So any other session is told that the page is in another account, and that
  you can allow it, switch to that account (`cswap switch <n>`), or have it publish a copy under a new link. The
  helper gets one auto-mode rule, republishing a private page you own is fine, since the review has refused those
  too (2.1.309). The review still looks the page up when the publish happens, so a shared page gets the plain review.
- **Allowing a republish:** type `! artifact-owner allow <link>` in the session that should republish the page; the
  session's refusal names the command. That session may then republish the page through the hook for 15 minutes
  (`allow <link> 60` for an hour, `allow <link> 0` withdraws it). The grant is tied to the session by
  `CLAUDE_CODE_SESSION_ID`, which Claude Code sets for the commands run in a session. The session is told the
  command is yours to run. If it ran it with its Bash tool anyway, that would go through its own permission check
  (a prompt, or in auto mode the review, which sees what you asked), unlike the routed publish.
- **Failure:** the session is told the artifact isn't deleted, why the helper failed (the review refused it, the
  owner account is at its limit, …), and the options: try again later, `cswap switch <owner>`, or a copy with a new
  link.

A routed call takes about 6–40 seconds and, at list prices, a fraction of a cent, on the owner account's usage.

## Usage

```
artifact-owner hook                    the PreToolUse/PostToolUse hook (hook JSON on stdin)
artifact-owner hooks                   print the hook entries for ~/.claude/settings.json
artifact-owner refresh                 record the owner of every artifact of every account
artifact-owner who URL                 print the recorded owner of an artifact
artifact-owner run ACCOUNT TOOL JSON   run one call as ACCOUNT (cswap number or email), print the result
artifact-owner allow URL [MINUTES]     let this Claude Code session republish a page it didn't publish, for
                                       MINUTES (15; 0 withdraws): type it in the session, after a !
```

The log, `~/.local/state/artifact-owner/log`, has one line per routing decision, helper run and error. The raw
output of the last helper run is in `helper-last.out` next to it, and that of failed ones in `helper-failed-*.out`.

## Caveats

- The own-page rule is applied by the reviewer, a model, so it can still refuse; the session then tells you. A
  page's sharing can change between the review's lookup and the publish, a matter of seconds.
- Pages published before the hooks were installed have no record of the session that published them, so their
  republishes are refused until you allow them.
- Live notifications (comments sent to Claude, new versions) don't come back to a session that was switched away.
- An artifact shared with the current account as an editor is still routed to its owner. That costs a helper run,
  and another session's republish is refused until you allow it, though the current account could publish it
  directly.
- A switch between a publish and its PostToolUse hook records the wrong owner. cswap switches rarely, so this is
  rare.
- The helper runs the newest installed Claude Code, which can be newer than a running session's.
- A call whose input doesn't fit in one command-line argument (128 KiB on Linux), such as very large `edits`, can't
  be routed; the session is told why.

The real fix belongs in Claude Code or claude.ai: a session should still reach the artifacts it published after the
account changes.

## Tests

- `uv run pytest`: the logic, on helper output and hook input captured from Claude Code (accounts, ids, titles and
  paths replaced), with no sessions started.
- `tests/e2e.sh`: reads and republishes a throwaway page that another of your accounts owns, through real headless
  sessions; the header says how to set it up.

## Uninstall

Remove the two hook entries from `~/.claude/settings.json` (and the CLAUDE.md line), then:

```
uv tool uninstall claude-artifact-owner
rm -r ~/.local/state/artifact-owner
```

## License

MIT
