---
name: anchorify
version: 0.3.0
requires-cli: ">=0.3.0"
description: |
  Publish or update a file as a public URL via Anchorify. Handles
  markdown, code (with syntax highlighting), JSON, YAML, CSV, TSV,
  HTML, and slide decks (Marp + reveal.js) — the render type is chosen
  from the filename extension, frontmatter, or overridden via --type.
  Use when the user asks to "publish this", "share as a URL", "make
  this shareable", "give me a link to this file", or wants a
  Google-Doc-shaped link to a deliverable they can hand to a
  non-technical recipient. Also use to update an existing share —
  "update the published version", "push a new version of <file>" —
  the CLI auto-detects whether the file is already published via a
  local mapping.
---

# anchorify

**Skill version 0.3.0** · Requires `anchorify` CLI ≥ 0.3.0. Check yours with `anchorify --version`; upgrade with the procedure in [Upgrading](#upgrading-the-cli-and-skill) below.

Wrap the `anchorify` CLI to publish or update a file at a stable
public URL on **anchorify.io**. Supports markdown, code, JSON,
YAML, CSV, TSV, HTML, and slide decks (Marp + reveal.js).

## Install or refresh

Preferred — the CLI installs the skill for you:

```bash
anchorify skill install
```

This writes `~/.claude/skills/anchorify/SKILL.md` from the
canonical doc, creating directories as needed. Re-run any time to
update; pass `--force` to overwrite without prompting when the local
file has been modified.

Manual fallback (if you don't yet have the CLI or want to inspect the
file before saving):

```bash
mkdir -p ~/.claude/skills/anchorify
curl -fsSL https://anchorify.io/docs/anchorify-skill.md \
  > ~/.claude/skills/anchorify/SKILL.md
```

The local copy is a snapshot — re-run `anchorify skill install`
when the version above (top of this page) is higher than the one in
your local SKILL.md.

> Migrating from the old `publish-md` or `justforwarded` skill? Move
> the folder:
> `mv ~/.claude/skills/justforwarded ~/.claude/skills/anchorify` (or
> from `publish-md`) and re-fetch from the URL above. The old
> `/docs/publish-md-skill.md` and `/docs/justforwarded-skill.md` URLs
> still 308-redirect to this page, so any existing installation keeps
> working.

## Upgrading the CLI and skill

**Trigger:** when the user asks any of "upgrade anchorify", "update
the CLI", "is there a newer version", "you're on an old version",
"refresh the skill", "is my skill out of date" — OR when a publish /
lint call fails with `error: 4xx` mentioning an unsupported field /
content type that newer CLIs handle.

**Procedure** (run these on the user's behalf, in order):

1. Read the installed CLI version:
   ```bash
   anchorify --version
   ```
   If the command isn't found, the CLI isn't installed — skip to step 3.

2. Read the latest published version:
   ```bash
   npm view anchorify-cli version
   ```
   No auth required; this hits the public npm registry. The npm
   package is `anchorify-cli` (the bare `anchorify` name was already
   taken by an unrelated utility); the binary it installs is named
   `anchorify`.

3. If installed < latest (or CLI is missing), upgrade and refresh the
   skill in one go:
   ```bash
   npm install -g anchorify-cli@latest && anchorify skill install --force
   ```
   `--force` overwrites the local SKILL.md without prompting — the
   user is explicitly asking for a refresh, so it's safe.

4. Confirm the new versions:
   ```bash
   anchorify --version
   head -3 ~/.claude/skills/anchorify/SKILL.md
   ```

5. Report the before → after versions to the user. If the upgrade
   crossed a major version, point them at the [release notes / git
   log](https://github.com/sankalp0o/repo-share/commits/main) for
   what changed.

**Skill-only refresh** (the CLI is current but the local SKILL.md is
stale): the third command alone is enough:

```bash
anchorify skill install --force
```

The skill version is shown both in this page's title and in the
frontmatter (`version:` key at the top of the raw markdown). When the
canonical page shows a higher version than the local copy, refresh.

## Prerequisites

The user needs the `anchorify` CLI installed and authenticated.

If `anchorify` is missing, tell them to install it (one-time setup)
and run `anchorify login`. The login flow asks for a host
(`https://anchorify.io`) and a token they copy from
`https://anchorify.io/dashboard`.

If a publish call returns `error: 401`, the user is not logged in —
tell them to run `anchorify login`.

## Config & environment

Two env vars change where the CLI reads/writes config and which host it
hits. Both exist mainly for sandboxed testing — sub-agents and CI runs
should set them to avoid accidentally publishing to production with a
developer's logged-in token.

- **`ANCHORIFY_CONFIG_DIR`** — overrides the config directory (default
  `~/.config/anchorify`). Redirects both `auth.json` and
  `published.json` to the given path. Use this to fully sandbox a test:

  ```bash
  ANCHORIFY_CONFIG_DIR=/tmp/anchorify-sandbox anchorify login \
    --host http://localhost:3737 --token <local-token>
  ANCHORIFY_CONFIG_DIR=/tmp/anchorify-sandbox anchorify --list
  ```

  Nothing in the user's real `~/.config/anchorify/` is touched. The
  legacy `JF_CONFIG_DIR` is still honored as a fallback so older test
  scripts keep working.

- **`ANCHORIFY_HOST` + `ANCHORIFY_TOKEN`** — when **both** are set,
  they override `auth.json` (and a one-line `[anchorify] using
  ANCHORIFY_HOST/TOKEN from env (auth.json ignored)` notice is printed
  to stderr if `auth.json` exists, so the override is visible). Setting
  only one of them falls back to `auth.json` — partial env doesn't
  trigger the override, since mixing an env host with a file token (or
  vice versa) would produce a confusing token-mismatch. The legacy
  `REPO_SHARE_HOST` + `REPO_SHARE_TOKEN` pair is still honored when the
  `ANCHORIFY_*` pair isn't set.

## How URLs work

Per-user URLs are `https://anchorify.io/<username>/<slug>` —
e.g. `https://anchorify.io/alice/q1-report`. The username prefix
is fixed per token; the slug is what you pick.

The CLI maintains a local mapping at
`~/.config/anchorify/published.json` (absolute file path →
`{id, url, filename, last_published_at}`). You do **not** have to
remember slugs across sessions — read the mapping or run
`anchorify --list`.

## Publishing a new file (file path NOT in the mapping)

1. **List existing slugs** so you don't pick a taken one:

   ```bash
   anchorify --list
   ```

   Output: one tab-separated line per published item — `<url>\t<filename>\t<source-path>`.
   Scope is the signed-in user.

2. **Lint the file before publishing** so you catch authoring issues
   (slide-deck frontmatter that won't be detected, JSON parse errors,
   CSVs with the wrong delimiter, etc.):

   ```bash
   anchorify lint <absolute-path>
   ```

   - Exit 0 = clean (or only `info`/`warn`-severity notes).
   - Exit 1 = at least one `error`-severity issue; the file WILL look
     broken to recipients. Surface the warning to the user and either
     fix the file or confirm they want to publish anyway.

3. **Pick a slug.** Rules:
   - Lowercase letters, digits, dashes only
   - 3–40 chars is the sweet spot (60 max)
   - No leading/trailing dash
   - Derived from the file's H1 title or filename, NOT generic ("notes", "doc")
   - Distinct from every existing slug in `--list`

   Examples: `q1-report`, `cardinal-heating-audit`, `geology-week3`.

4. **Publish:**

   ```bash
   anchorify <absolute-path> --slug <slug>
   ```

   Output is a single line — the public URL. Return that URL to the
   user, nothing else around it. If the publish response carried any
   authoring warnings, the CLI prints them to stderr; surface those
   to the user too.

5. **If you get `error: 409 {"error":"slug taken",...}`**, pick a
   different slug and retry.

## Updating an existing publish

If the file's absolute path is in the local mapping (or the user says
"update"), just run with no flags. The CLI looks up the file and
updates the same URL:

```bash
anchorify <absolute-path>
```

URL is unchanged; content is replaced. Return the URL.

To update by id explicitly (e.g. "push v2 to hello-world"), pass
`--id <id>`.

## Other operations

**Delete a share:**

```bash
anchorify delete <absolute-path>
# or by slug:
anchorify delete --slug <slug>
```

The local mapping entry is removed when deleting via path. Deleted
URLs return 410 Gone (recipients see the link existed but is gone).

**Password-protect a share:**

```bash
# At publish time:
anchorify <abs-path> --slug <slug> --password <pw>
# Clear an existing password:
anchorify <abs-path> --no-password
```

Recipients see a password gate; only the correct password renders
content. View counts only increment on actual content render.

**Visibility (public / unlisted / members):**

Every share has one of three tiers. By default a new share is **unlisted** —
it renders for anyone with the URL, but the page sets `noindex` so search
engines and AI crawlers don't pick it up. (The pre-V3 value `secret` is
the legacy alias for `unlisted` and is still accepted everywhere.) Other
tiers:

- `public` — listed on the owner's profile and indexable.
- `members` — only signed-in org admins + project viewers can read; anon → `404`.

Set the tier at publish time:

```bash
# Create as public:
anchorify <abs-path> --slug <slug> --public
# Force unlisted (default; useful as an explicit flip on update):
anchorify <abs-path> --unlisted
# Members-only (signed-in org/project viewers only):
anchorify <abs-path> --members
```

Updates without a visibility flag preserve the existing tier. The flags
are mutually exclusive.

**Flip an existing share's visibility** without re-publishing:

```bash
anchorify visibility <slug-or-id> public
anchorify visibility <slug-or-id> unlisted
anchorify visibility <slug-or-id> members
```

Resolves `<slug-or-id>` against `--list` (matches by id, slug, or URL
suffix). If an unlisted share has a password and you flip it to public,
the server refuses to silently clear the password — re-run with
`--force` to clear the password and make it public:

```bash
anchorify visibility <slug-or-id> public --force
```

**Force a brand-new URL** even if the file is in the mapping:

```bash
anchorify <abs-path> --new
```

## Content type detection

Anchorify renders shares differently depending on their content
type — markdown via `marked`, code with `highlight.js`, CSV/TSV as a
sortable table, JSON/YAML pretty-printed and highlighted, HTML
sanitized and rendered.

The CLI **does not** classify the file itself. It just sends the
filename + content; the server picks a renderer:

1. **Auto-detection from file extension** (default). The server maps
   common extensions:
   - `.md`, `.markdown` → markdown
   - `.json` → json
   - `.yaml`, `.yml` → yaml
   - `.csv` → csv
   - `.tsv` → tsv
   - `.html`, `.htm` → html
   - `.js`, `.ts`, `.py`, `.go`, `.rs`, `.rb`, `.sh`, `.sql`, etc.
     → code (with language inferred for syntax highlighting)
   - Anything else → markdown
2. **Explicit `--type` override** for ambiguous extensions. Pass one
   of: `markdown`, `code`, `json`, `yaml`, `csv`, `tsv`, `html`.

```bash
# Auto-detect from extension — no flag needed:
anchorify data.csv --slug q1-numbers          # renders as a table
anchorify settings.yaml --slug prod-config    # yaml-highlighted
anchorify snippet.py --slug pyrun             # python-highlighted

# Override when the extension lies (e.g. a .txt file that is actually code):
anchorify weird.txt --slug snippet --type code
anchorify notes.txt --slug brief --type markdown
```

`--type` is validated client-side against the allowed set, so you get
an immediate error for typos rather than a server round-trip. Updates
to an existing share that don't pass `--type` keep whatever type the
share was created with (no silent reclassification).

**Fix an existing share's render type** without re-publishing:

```bash
anchorify type <slug-or-id> markdown
anchorify type <slug-or-id> code
anchorify type <slug-or-id> json
```

Resolves `<slug-or-id>` against `--list` (matches by id, slug, or URL
suffix). Use this to recover from the paste-flow gotcha where leaving
Filename blank in the web "+ New share" form silently classifies the
share as markdown — Python / JSON / CSV pastes will render wrong until
the type is flipped.

## Collaboration subcommands

The CLI surfaces five collaboration features. Each prints
tab-separated rows on success so a parent agent or `jq`-pipe can
parse the output.

### `notifications` — list your in-app notifications

```bash
anchorify notifications              # all, newest 50
anchorify notifications --unread     # unread only
anchorify notifications --limit 10   # cap at 10
```

Output: `<id>\t<kind>\t<created_at_iso>\t<title>\t<link>`.

Use this when the user asks "what's new", "any new comments on my
shares", or "did anyone request access". See
[`/docs/notifications`](/docs/notifications) for the full set of
notification kinds.

### `link-perm` — set the anyone-with-the-link permission

```bash
anchorify link-perm <slug-or-id> none
anchorify link-perm <slug-or-id> can_view
anchorify link-perm <slug-or-id> can_comment
anchorify link-perm <slug-or-id> can_suggest
```

Sets the additive grant attached to the URL. `none` (default) =
read-only. `can_comment` = anonymous URL-holders can post comments +
reactions. `can_suggest` = URL-holders can submit suggested changes.
See [`/docs/link-permissions`](/docs/link-permissions) for the full
matrix.

### `versions` — list and restore a share's version history

```bash
# List versions, newest first:
anchorify versions <slug-or-id>
# Output: <version-id>\t<created_at_iso>\t<source>\t<author>\t<status>

# Restore the share to an older version:
anchorify versions restore <slug-or-id> <version-id>
```

`source` is one of `publish` / `rollback` / `suggestion` /
`agent_edit`. `status` is `approved` / `suggested` / `rejected`. The
restore appends a new `rollback` row to the history (so even the
rollback is versioned) and prints `ok` on success.

### `audit` — list audit events for an org

```bash
anchorify audit                              # your home org, 50 events
anchorify audit --org <slug>                 # explicit org
anchorify audit --limit 200
anchorify audit --action share.delete        # filter by action
```

Output: `<created_at_iso>\t<action>\t<actor>\t<target_type>:<target_id>`.

The `--action` filter matches the dotted action keys recorded by the
service — see [`/docs/audit`](/docs/audit) for the catalog.

### `suggestions` — list suggestions submitted on your shares or by you

```bash
# Open suggestions on a specific share you own (owner-only):
anchorify suggestions <slug-or-id>

# Every suggestion YOU have submitted, across every share:
anchorify suggestions mine
```

The `mine` form output: `<version-id>\t<share-url>\t<status>\t<created_at_iso>`.

Approve/reject from the web UI — the CLI lists only.

### `report-bug` — file a bug from the CLI or an agent

```bash
# Bare summary — opens with empty body. Anonymous is OK (rate-limited
# per IP); when logged in, the report carries your user id + org.
anchorify report-bug "CLI hung on publish"

# Inline body + agent breadcrumbs.
anchorify report-bug "Editor save 500'd" \
  --body "Saving from /editor returned 500; retry succeeded." \
  --context '{"slug":"q1-report","last_http_status":500}'

# Body from a file (useful for long repro steps):
anchorify report-bug "Render crash on big csv" \
  --file ~/jf-bug-repro.md \
  --surface dashboard
```

Flags:

- `--body <text>` — inline long-form body. Mutually exclusive with `--file`.
- `--file <path>` — read body from a file. Helpful for paste-from-editor.
- `--context '<json>'` — free-form JSON breadcrumbs (slug, last HTTP
  status, recent error message). Must parse to an object.
- `--surface cli|api|agent|dashboard|vscode|raycast|obsidian` — which
  surface filed the report. Defaults to `cli`.

On success prints the report id on stdout. Every error response from
`/api/v1/*` also includes a `report_with` hint with the exact CLI
shape an agent can chain inline.

## Notes & failure modes

- File path must be **absolute** — resolve `~` and relative paths first.
- The CLI prints **only the URL** on success. On failure, `error: <code> <body>` to stderr.
- **401** = run `anchorify login` (auth missing or token revoked)
- **409** = slug collision; pick a different slug and retry
- **400** = invalid slug format (re-read the slug rules above)
- **404** on `delete` = slug doesn't belong to the signed-in user
- The returned URL is **publicly readable** unless a password is set.
  Warn the user before publishing if the file might contain
  credentials, client names, or anything they wouldn't want a stranger
  to see.
- The local mapping at `~/.config/anchorify/published.json` is plain
  JSON; read/inspect it directly when debugging.
