# Goodlinksctl

> Read, search, and export a local GoodLinks library through the official GoodLinks API. Mutate data only with explicit user authorization.

- Skill: `viticci/goodlinksctl` (Agent Skill, multi-file: 13 files)
- Install (CLI): `npx skillmds@latest add viticci/goodlinksctl`
- Raw SKILL.md: https://api.skillmd.com/api/skills/viticci/goodlinksctl/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: viticci (https://skillmd.com/u/viticci)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/viticci/goodlinksctl

---


# GoodLinksCTL

Use the installed `goodlinksctl` command to work with the user's GoodLinks
library. GoodLinksCTL is an unofficial client for the local GoodLinks API. The
GoodLinks app must be installed and its API Server must be enabled.

## Install this skill

Install the CLI first:

```sh
uv tool install git+https://github.com/viticci/GoodLinksCTL.git
```

Then copy this file into the skills directory used by the agent. For Codex:

```sh
mkdir -p ~/.codex/skills/goodlinksctl
curl -fsSL https://raw.githubusercontent.com/viticci/GoodLinksCTL/main/SKILL.md \
  -o ~/.codex/skills/goodlinksctl/SKILL.md
```

Restart the agent session after installing or updating the skill.

## First checks

Run:

```sh
goodlinksctl doctor --json
goodlinksctl auth status --verify --json
```

`doctor` requires GoodLinks 3.2 or later. If no credential is configured, ask
whether the user wants to configure one. Only after explicit authorization, ask
the user to copy the token from GoodLinks Settings > API and pipe it into:

```sh
pbpaste | goodlinksctl setup --stdin
```

Never print the token, put it in a command argument, write it to a file, or
include it in logs. Persistent storage belongs in macOS Keychain. For a one-shot
call, pass it through stdin with global `--token-stdin`; for a command that also
consumes stdin, use `GOODLINKS_TOKEN` instead.

## Authorization boundary

Read, search, inspect, and export to stdout by default. Require explicit user
authorization before any action that changes GoodLinks or the filesystem,
including:

- adding, upserting, updating, tagging, marking read, starring, or deleting a
  link;
- changing a highlight note;
- using `--save`, `--force`, `auth set`, `auth delete`, setup, or credential
  rotation;
- creating a live scratch record for testing;
- deleting a scratch record, even if this agent created it.

Authorization for one mutation does not authorize later mutations. Never infer
permission from a general request to inspect or troubleshoot the library.

## Agent defaults

- Add `--json` for reads, mutations, doctor, and auth checks.
- Use `--all-pages` only when the request covers the whole library. It has a
  100,000-item safety cap; use `--max-items` to choose a lower cap.
- Batch stdin accepts at most 10,000 items and 16 MiB in total. Split larger
  jobs and verify each completed batch.
- Address existing records by exact IDs returned by GoodLinksCTL. Use
  `links get --url` when an exact saved URL is the known identifier.
- Repeated `--tag` filters are OR conditions because that is the official API
  behavior.
- Treat exit 7 as a partial result. Inspect every element in `results` or
  `preflight`; do not report the batch as successful.
- Use `goodlinksctl schema NAME` when a consumer needs a machine-readable
  contract.
- Plain HTTP is valid only for a loopback API URL. Remote origins require HTTPS.

## Read workflows

Whole library:

```sh
goodlinksctl --json links list --all-pages --limit 250
```

Search and filter:

```sh
goodlinksctl --json links list \
  --search 'shortcuts' \
  --tag apple \
  --read false \
  --sort newestSaved \
  --all-pages
```

Get exact records:

```sh
goodlinksctl --json links get LINK_ID
goodlinksctl --json links get --url 'https://example.com/article'
printf '%s\n' LINK_ID_1 LINK_ID_2 | goodlinksctl --json links get --stdin
```

Lists, tags, and highlights:

```sh
goodlinksctl --json lists list
goodlinksctl --json lists query unread --all-pages
goodlinksctl --json tags list
goodlinksctl --json highlights list --link-id LINK_ID --all-pages
```

Exports go to stdout unless `--save` is provided:

```sh
goodlinksctl links content LINK_ID --format markdown
goodlinksctl highlights export LINK_ID
```

Use `--save` only after the user authorizes the exact path. Do not add `--force`
unless replacing that exact path is also authorized.

## Authorized mutation workflows

After explicit user authorization, create or upsert:

```sh
goodlinksctl --json links add 'https://example.com' --title Example --tag research
```

Batch add accepts newline-delimited URLs with shared flags or JSONL with
per-item fields:

```sh
printf '%s\n' 'https://one.example' 'https://two.example' \
  | goodlinksctl --json links add --stdin --tag research

goodlinksctl --json links add --stdin-jsonl < links.jsonl
```

Update only the fields the user authorized:

```sh
goodlinksctl --json links update LINK_ID --starred --add-tag favorite
goodlinksctl --json links update --stdin-jsonl < updates.jsonl
goodlinksctl --json highlights update HIGHLIGHT_ID --note 'Key idea'
```

For a live mutation test, first obtain authorization to create and later delete
specific scratch records. Use an unmistakable `GoodLinksCTL Scratch` title and
a unique URL. Record the returned IDs and verify every mutation by fetching
those exact IDs. Do not create or clean up scratch data without authorization.

## Authorized delete protocol

Deletion moves links to GoodLinks trash. Never infer IDs from titles, positions,
or stale output.

1. Confirm that the user explicitly authorized deletion of the exact records.
2. Resolve their exact IDs with a fresh read.
3. Run the identical set through `--dry-run`.
4. Check every preflight row and require `summary.failed` to be zero.
5. Run again with `--yes` only if the authorized set is unchanged.
6. Verify the response and that those IDs are no longer addressable.

```sh
goodlinksctl --json links delete LINK_ID --dry-run
goodlinksctl --json links delete LINK_ID --yes
```

Do not use `--allow-partial` unless the user explicitly accepts deleting the
valid subset when another ID fails preflight.

## Errors

| Exit | Meaning |
| ---: | --- |
| 0 | Success |
| 2 | Usage or validation error |
| 3 | Missing or rejected credential |
| 4 | API server or connection failure |
| 5 | Resource not found |
| 6 | GoodLinks/API response failure |
| 7 | Partial batch or preflight failure |
| 8 | Destructive confirmation required |
| 9 | Local I/O or interrupted operation |

JSON errors are on stderr as `{ "ok": false, "error": { ... } }`. Preserve
stderr and the process exit status; do not turn a nonzero command into success
because stdout contains partial data.

Use `goodlinksctl help COMMAND ...` for the live command surface. The complete
written reference is at
<https://github.com/viticci/GoodLinksCTL/blob/main/docs/commands.md>.

