Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

linstash

linstash is a single-executable Go CLI that moves selected Linear issues to a self-hosted Kan instance, verifies the copy, and archives the source issues through Linear's API.

Background

I'm broke, so I built this because I love Linear.

Linear is excellent for active work, while its free plan has a finite active issue allowance. Kan is a useful self-hosted home for work that no longer needs to occupy the active Linear workspace. linstash connects the two without running a server, daemon, webhook receiver, or scheduler.

Mapping

Linear Kan
Workspace Workspace
Team Board
Workflow status List
Project Label with the exact project name
Issue Card
Issue label Not copied
Sub-issue Card with parent/child metadata

Projects that span multiple Linear teams receive the same project label on each corresponding Kan board.

Install

The setup installers support macOS, Linux, and Windows on Intel/AMD (amd64) and ARM (arm64). They download the latest GitHub release, verify its SHA-256 checksum, install linstash, and can immediately configure credentials and the Linear/Kan mapping.

macOS or Linux:

curl -fsSL https://raw.githubusercontent.com/luberius/linstash/main/scripts/install.sh | sh

Windows PowerShell:

irm https://raw.githubusercontent.com/luberius/linstash/main/scripts/install.ps1 | iex

The installer asks whether to configure now. If accepted, it securely prompts for both credentials, persists them for the current user, runs interactive linstash configure, and finishes with linstash doctor. Canceling the configuration keeps the installation and saved credentials without running doctor against a missing configuration.

On macOS/Linux, make sure the default installation directory is on your PATH:

export PATH="$HOME/.local/bin:$PATH"
linstash version

To choose another directory or pin a version, set LINSTASH_INSTALL_DIR or LINSTASH_VERSION before running the installer:

curl -fsSL https://raw.githubusercontent.com/luberius/linstash/main/scripts/install.sh |
  LINSTASH_INSTALL_DIR="$HOME/bin" LINSTASH_VERSION="v1.0.0" sh

You can also download the binary for your operating system and architecture from the GitHub release and put it on your PATH.

Quick start

You need:

  • a Linear API key for the workspace you want to off-ramp;
  • a running Kan instance and API token;
  • permission to read and archive the selected Linear issues;
  • permission to create the configured workspace structure and cards in Kan.

Set credentials in your shell. They are read from the environment and are never written to the YAML configuration:

export LINEAR_API_KEY="lin_api_..."
export KAN_API_TOKEN="..."

Run the interactive configuration:

linstash configure

configure discovers your Linear teams, projects, and statuses, asks how Kan boards should be mapped, then previews every destination structure. Choose team_as_board for the V1 layout, or project_as_board when Linear projects should become boards and teams should become labels. In project mode you can also explicitly include projectless issues. They are excluded by default; choosing Yes reveals a team selector and routes each selected team's projectless issues to that team's own <Team>'s board. Nothing is created until you confirm the preview.

Interactive terminals use arrow-key selectors: / moves, Space toggles items in multi-select lists, and Enter confirms. This applies to Kan workspaces, mapping modes, projects, teams, statuses, tree policies, and import destinations. Redirected input uses an equivalent numbered fallback. Remote discovery and planning show a compact animated status symbol in a real terminal. Fast requests avoid flicker, and redirected output remains plain and stable for scripts.

Check the resulting connection and mappings:

linstash doctor

After initial setup, extend the existing off-ramp policy without repeating the full wizard:

linstash configure add project
linstash configure add team
linstash configure switch workspace

add project lists only Linear projects not already configured. add team is available in project_as_board mode and adds projectless fallback boards for teams not already configured. Both commands preserve the current workspace, mapping mode, statuses, tree policy, and existing selections; they preview and reconcile only the added topology. Confirming the project or team selection is the write authorization, so incremental add commands do not ask for a second confirmation.

If the existing status selection matches no workflow status for a newly affected team, the add command stops and asks you to rerun full configure to change that policy.

If an existing configured project or projectless team is no longer discoverable in Linear, incremental configure warns that the stale UUID remains in the local policy, then continues with the independent addition. Rerun full configure when you want to reconcile removed Linear objects.

configure switch workspace keeps the Linear workspace, mapping mode, stash group, status selection, and issue-tree policy while rebinding the destination to another existing or newly created Kan workspace. After confirmation it rebuilds destination mappings in a fresh state.db and retains the previous config and state in a timestamped sibling backup. A failed switch restores the original local files automatically.

Preview exactly what will move. plan is read-only:

linstash plan
linstash plan --group completed-project-work

Off-ramp the selected issues after reviewing the plan:

linstash offramp

offramp is an alias for stash; both commands copy issues into Kan, read them back for verification, and only then archive the Linear source:

linstash stash

Both commands ask for confirmation. Use --yes only for an intentional non-interactive run:

linstash offramp --group completed-project-work --yes

Review completed or incomplete attempts:

linstash status
linstash status --json

Read the full manual after installation:

man linstash

If the user man directory is not yet on MANPATH, print the same roff manual directly with linstash man. The macOS/Linux installer writes it to ~/.local/share/man/man1/linstash.1 by default; set LINSTASH_MAN_DIR to override that location.

To copy ordinary Kan boards into Linear without restoring or synchronizing stashed issues, run:

linstash import

Each run asks for the Kan boards and one existing Linear team. Boards map to projects, lists map to exact-name workflow statuses, labels map to issue labels, and cards map to issues. Name conflicts and missing status types are resolved interactively. The importer confirms each issue's stable identity and routing fields, records local idempotency state, and adds a linear-import: metadata comment to Kan. It never deletes Kan data, creates Linear teams, or unarchives Linear issues. If an individual Kan attachment cannot be accessed, the issue still imports: the CLI warns and adds one nonduplicating Linear comment identifying the file that was not copied. A Linear file-upload failure still fails that card. While applying the import, the CLI shows the current board and overall card progress.

V1.1.2 is the current release. It adds recoverable Kan workspace switching, stable-field import verification, archived-project mapping recovery, and visible import progress. V1.1 disposable live testing passed durable renamed-project reuse in a fresh process, card-level recovery, private attachment copy, inaccessible-attachment warnings, exact managed-marker counts, and unchanged no-op reruns. All six published binaries and the public installer passed checksum verification.

To keep configuration and local state in a specific location, pass the same explicit config path to every command:

linstash --config "$HOME/.config/linstash/config.yaml" configure
linstash --config "$HOME/.config/linstash/config.yaml" doctor
linstash --config "$HOME/.config/linstash/config.yaml" plan
linstash --config "$HOME/.config/linstash/config.yaml" offramp

import is a separate one-way copier. It is not an on-ramp, restore, or two-way synchronization feature.

Command reference

linstash configure  Discover Linear/Kan and write an explicit configuration
linstash doctor     Validate credentials, configuration, and remote mappings
linstash plan       Preview the exact issue trees that would be stashed
linstash stash      Copy, verify, and archive the selected Linear issues
linstash offramp    Alias for stash
linstash status     Report prior migrations and incomplete attempts
linstash import     Copy selected Kan boards into one existing Linear team
linstash man        Print the linstash(1) manual page

stash and its offramp alias display the same plan and ask once for confirmation. --yes supports intentional non-interactive use. Running either command is the only trigger: linstash does not install or operate a background process.

Selection groups

A stash group is a local named off-ramp policy, not a Linear or Kan object. It records which Linear projects may move, which issue statuses qualify, and how parent/child issue trees are handled. Configure currently creates one group named completed-project-work automatically; the name matters only when a command explicitly uses --group.

The status-category menu exposes five broad categories: backlog, unstarted, started, completed, and canceled. Those menu labels are built into linstash, but the actual workflow statuses are discovered live from Linear. Selecting Completed, for example, selects every live status in the relevant teams whose Linear category is completed, such as Done or Shipped. Live statuses with other types, such as duplicate, remain available through the individual-status selector.

Configure may also offer Select additional individual statuses (optional). Those choices come directly from the selected projects' live Linear teams and are displayed by status and team name. Use them only to include a specific status outside the selected categories; no UUID entry is required.

A generated group defaults to completed statuses and the safest tree policy:

version: 1

linear:
  workspace_id: "replace-with-linear-workspace-uuid"
  workspace_slug: "example-workspace"

kan:
  base_url: "https://kan.example.com"
  workspace_public_id: "resolved-by-linstash-configure"
  workspace_name: "Example Archive"
  workspace_slug: "example-archive"
  mapping_mode: "project_as_board"

groups:
  - name: "completed-project-work"
    project_ids:
      - "replace-with-linear-project-uuid"
    project_names:
      replace-with-linear-project-uuid: "Cached project name"
    orphan_team_ids:
      - "optional-team-uuid-for-projectless-issues"
    orphan_team_names:
      optional-team-uuid-for-projectless-issues: "Cached team name"
    statuses:
      types: ["completed"]
      ids: []
    tree_policy: "require_whole_tree"

Available tree policies are:

  • require_whole_tree: skip the tree unless every node matches.
  • stash_whole_tree: a matching root selects all descendants.
  • matching_nodes_only: stash only nodes that match the status selection.

Safety model

  • Normal stash runs never create workspaces, boards, or lists implicitly.
  • configure previews remote structures before creating them.
  • Every Kan card carries the immutable Linear issue UUID and a content checksum.
  • The full selected tree must be copied and read back successfully before any Linear issue is archived.
  • Source issues are archived with Linear's reversible issueArchive mutation, never deleted.
  • A failed or partial Kan write leaves the Linear source unarchived.
  • Comments retain original author and timestamp.
  • V1 off-ramp preserves Linear attachment URLs but does not download their bytes. The separate V1.1 Kan import copies accessible attachment bytes into Linear private storage.

Project status

V1 is complete. The workflow has automated unit, adapter, state, and workflow coverage, and the disposable Linear/Kan journey passed configure, read-only planning, verified copy, leaves-before-parent archive, no-op rerun, unarchive recovery, and live failure probes. See the redacted V1 acceptance record.

Development

Requirements:

  • Go 1.25 or newer
go test ./...
go vet ./...
CGO_ENABLED=0 go build ./cmd/linstash
./scripts/check-project-docs.sh
make release

Documentation

Start at docs/README.md. The product contract, architecture, technical design, and V1 acceptance criteria are maintained there.

License

MIT

About

Move inactive Linear issue trees to a self-hosted Kan workspace safely

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages