linctl

module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: MIT

README ΒΆ

linctl

CI Go Release

A Linear control surface for agent-safe coordination β€” free reads, target-pinned guarded writes.

linctl is a schema-aligned Go CLI for Linear. Reads are broad and cheap, so an agent can inspect anything. Writes are different: every mutation re-resolves the active token and fails closed unless the resolved org/team/project matches the target pinned for the repo. One agent, scoped to exactly one place it is allowed to change.

linctl issue list --mine --state started     # read anything
linctl issue create --title "Spike: exports" # write only inside the pinned target

⚑ Quickstart

Install
# Homebrew cask (macOS)
brew install --cask KyaniteHQ/linctl/linctl

# Go toolchain (macOS / Linux / Windows)
go install github.com/KyaniteHQ/linctl/cmd/linctl@latest

Prebuilt binaries (darwin/linux/windows Γ— amd64/arm64) and checksums are attached to every release.

From source checkout
git clone https://github.com/KyaniteHQ/linctl.git && cd linctl
go install ./cmd/linctl
linctl --version

Use your platform or distro package manager to install Go first. If you install Go manually from go.dev/dl, verify the published checksum and follow Go's platform-specific instructions instead of replacing a managed /usr/local/go.

Configure

Pin the target in .linctl.toml at the repo root, then supply a token by environment.

[target]
org_id     = "linear-org-id"
team_key   = "LIT"
team_id    = "linear-team-id"
project_id = "optional-linear-project-id"   # omit for team-scoped writes
export LINCTL_TOKEN="lin_api_..."   # or LINEAR_API_KEY; never commit a token

Credential precedence is LINCTL_TOKEN β†’ LINEAR_API_KEY β†’ a token in .linctl.toml / ~/.config/linctl/config.toml. A repo .linctl.toml overlays the global config.

First commands
linctl usage              # orientation β€” no token required
linctl target --json      # confirm the active token's org / team / project
linctl doctor             # config, token, and target health
linctl issue list --mine  # your issues in the pinned team

πŸ”’ How writes stay safe

linctl's vocabulary deliberately separates reads from writes:

  • Pinned Target β€” the org/team/(optional project) a repo declares in .linctl.toml as the only allowed destination for writes.
  • Resolved Target β€” the org/team/project proven from the active token at command time.
  • Target Mismatch β€” when the two disagree. For a guarded write this is a hard stop, never a prompt or a warning.
flowchart LR
    A[linctl write command] --> B[Resolve Target<br/>from active token]
    B --> C{Resolved matches<br/>Pinned Target?}
    C -->|match| D[Guarded write proceeds]
    C -->|mismatch| E[Target Mismatch<br/>hard stop Β· no mutation]

Team-scoped creates compare org + team (the entity does not exist yet). Resource-scoped updates and archives resolve the existing entity first, then compare the pinned project_id when one is configured. There is no bypass flag β€” --org, --team, and --project set the pinned target, they do not relax the guard. See docs/adr/0001-target-pinned-linear-writes.md.

πŸ“– Command reference

Across 59 documented command groups, linctl maps the Linear schema. The most-used ones are below; the exhaustive catalog with GraphQL backing lives in docs/domain-map.md, and linctl <group> --help lists every subcommand.

Context & health

linctl target --json          # resolved org/team/project for the active token
linctl doctor                 # config / token / target health report
linctl current                # the issue for the current git branch
linctl next --dry-run         # preview the top-ranked unblocked issue

Issues β€” reads, rich list filters, and guarded writes. Related: issue-relation, comment.

linctl issue list --state started --mine --limit 20
linctl issue list --has-blockers --created-after 2026-06-01
linctl issue get LIT-123 --json
linctl issue deps LIT-123                       # parent / children / blocks / blocked-by
linctl issue search "flaky export test"
linctl issue create --title "Spike: exports" --description-file ./spec.md

Projects β€” reads plus create/update/archive. Related: project-update, project-status, project-label, project-relation.

linctl project list --limit 20
linctl project get <project-id> --json
linctl project issues <project-id>
linctl project-milestone list <project-id>

Cycles & sprints β€” cycle writes the schema entity; sprint is a read-only report alias.

linctl cycle list
linctl cycle issues <cycle-id>
linctl sprint current                           # active cycle for the team
linctl sprint report <cycle-id>

Planning β€” Initiatives are the current strategic surface; roadmap* is legacy read-only.

linctl initiative list
linctl initiative projects <initiative-id>
linctl initiative-to-project list
linctl initiative-update list

Teams, users & org

linctl team list
linctl team members <team-id>
linctl user me
linctl user my-assigned-issues
linctl organization teams

Search

linctl search issues "rate limit"
linctl search projects "billing"
linctl semantic-search "exports are slow" --limit 20

Releases β€” release, release-note, release-pipeline, release-stage, issue-to-release, external-link.

linctl release list
linctl release-pipeline list
linctl release-stage list

Customers β€” customer, customer-need, customer-status, customer-tier.

linctl customer list
linctl customer-need list

Metadata & more β€” every group supports list/get plus entity-specific reads: label, document, template, workflow-state, time-schedule, notification, triage-responsibility, sla-configuration, rate-limit, application, audit-entry, agent-activity, agent-skill, external-user, custom-view, favorite, emoji, attachment. Run linctl <group> --help or see docs/domain-map.md.

🧰 Output & scripting

Output controls are global flags β€” combine them with any command.

Flag Effect
--json / --compact JSON output; --compact makes it single-line
--fields a,b.c project JSON to an allowlist of (dot-path) keys
--id-only emit only the Linear id, for $(...) chaining
--quiet suppress output on a successful write
--fail-on-empty exit non-zero when a list result is empty (monitors)
--sort FIELD --order asc|desc deterministic list ordering
--format minimal|compact|full human (non-JSON) output detail
--profile / --org / --team / --project config profile and target overrides
--timeout 30s per-request timeout
--debug structured diagnostics to stderr (LINCTL_DEBUG_JSON=1 for JSON)
linctl issue list --json --compact --fields identifier,title,state
id=$(linctl --id-only issue create --title "task"); linctl issue start "$id"
linctl issue list --fail-on-empty --sort title --order asc

Diagnostics go to stderr, so stdout stays clean for piping. Stable JSON shapes for parsing are documented in skills/linctl/references/json-output.md.

✍️ Guarded writes

Writes currently cover issues (create, template-backed create, guarded import, update/--append, start, comment, reply, close, done, and next start), issue relations (relate, unrelate), comments (update, delete), projects (create, update, archive), project updates (create), documents (create, update), cycles (create, update, archive), and project milestones (create, update). Each mutation is checked against the pinned target before it runs. For test runs, create namespaced throwaway resources (linctl-it-<runid>) and clean them up β€” close disposable issues, archive disposable projects.

πŸ€– For agents

linctl ships a skill that teaches an agent to drive it: see skills/linctl/SKILL.md. Verify a checkout with no credentials via bash skills/linctl/scripts/linctl-offline-smoke.sh, or do a read-only check with a token via bash skills/linctl/scripts/linctl-smoke.sh. The skill includes a drop-in AGENTS.md snippet for consuming repos.

πŸ”§ Development

go run github.com/go-task/task/v3/cmd/task@latest ci        # generate-check β†’ vet β†’ test β†’ build β†’ lint β†’ actionlint β†’ vuln
go run github.com/go-task/task/v3/cmd/task@latest coverage  # 100% hand-written statement coverage

internal/client/generated.go is generated by genqlient from internal/client/operations/*.graphql; CI fails on drift, so run go generate ./... and commit it after changing operations. Integration tests and the live smoke harness hit a disposable Linear org and never run under plain go test:

LINCTL_TEST_TOKEN=<token> go test -count=1 -tags=integration ./internal/client
go run github.com/go-task/task/v3/cmd/task@latest live-smoke

Contributor workflow and the release process are in CONTRIBUTING.md; domain vocabulary is in CONTEXT.md; command-to-GraphQL mapping and named test scenarios are under docs/.

πŸ“„ License

MIT Β© 2026 KyaniteHQ

Directories ΒΆ

Path Synopsis
cmd
linctl command
Package main starts the linctl binary.
Package main starts the linctl binary.
internal
cli
Package cli owns the linctl command-line surface.
Package cli owns the linctl command-line surface.
client
Package client contains Linear GraphQL client primitives.
Package client contains Linear GraphQL client primitives.
config
Package config loads linctl configuration from files, profiles, and environment variables.
Package config loads linctl configuration from files, profiles, and environment variables.
gitctx
Package gitctx derives Linear context from the current VCS checkout.
Package gitctx derives Linear context from the current VCS checkout.
render
Package render writes human and JSON command output.
Package render writes human and JSON command output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL