npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alessandroraffa/tangyr

v6.0.1

Published

CLI for the Tangyr discipline — install and manage operating kits for AI coding tools

Readme

@alessandroraffa/tangyr

CLI for the Tangyr discipline — install and manage operating kits for AI coding tools.

An operating kit is a self-contained package of agents, skills, commands, rules, and templates that implements the Tangyr discipline for a specific AI coding context. The CLI places kit artifacts into the environments consumed by supported tools.

Requirements

Node.js >=22. Running tangyr on an older Node.js version prints a clear error to stderr and exits without attempting to run any command.

Install / run

npx @alessandroraffa/tangyr <command>

Or install globally:

npm install -g @alessandroraffa/tangyr
tangyr <command>

Commands

Kit management

| Command | Description | | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | | tangyr install --kit <name> | Install a kit resolved from configured search paths (kitSearchPaths/kit in tangyr.config.yaml) | | tangyr install --kit-path <path> | Install a kit from an explicit kit root path | | tangyr uninstall | Remove Tangyr from the target scope | | tangyr sync | Synchronize installed kit to the latest version | | tangyr verify | Check installation integrity against the manifest | | tangyr cleanup | Remove managed artifacts; delegates to uninstall if manifest exists | | tangyr assess | Pre-installation conflict analysis | | tangyr list | Display kits discovered in configured search paths and any installed manifests |

Environment

| Command | Description | | --------------- | -------------------------------------------------- | | tangyr detect | Detect supported AI tool runtimes | | tangyr status | Show managed artifact status per tool | | tangyr doctor | Run environment diagnostics with optional auto-fix | | tangyr probe | Probe configured platform paths |

Configuration

| Command | Description | | ----------------- | ----------------------------------------------------- | | tangyr init | Create a tangyr.config.yaml interactively | | tangyr config | Display or edit Tangyr configuration | | tangyr validate | Validate the kit against kitFormat 1 conformity rules |

Command options

Options declared on individual commands in addition to the global flags below. --offline, --refresh, and install/sync's narrow --json are documented in Remote kit source; --scope in Scope.

| Command | Option | Description | | -------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | install, sync | --loss-report <path> | Write an extra copy of this run's semantic loss report to <path>. The report itself is scope state and is rewritten at <scope>/loss.json by every install and sync regardless, whether or not the run exits 3. | | install, sync | --ignore-errors | Suppress exit code 3 when the loss report contains error-severity entries | | install, sync | --kit <name> | Kit to install/synchronize: <name> for a local source, <name>@<version> for a remote one | | install, uninstall, assess | --tools <list> | Comma-separated list of target tools | | sync, status, verify, doctor, cleanup, probe | --target <tool> | Limit the command to a single target | | sync | --component <name> | Limit sync to one component type: instructions, agents, skills, commands, rules, hooks, mcp, settings, output-styles, lsp | | sync | --force | Rewrite managed artifacts even when provenance matches | | doctor | --fix | Apply safe automated fixes | | cleanup | --all | Include all enabled targets | | config | --edit | Interactively edit mutable config fields | | auth login | --skip-validation | Store the credential without contacting the configured origin | | kit update | --kit <name>@<version> | Remote kit reference to re-fetch into the cache | | cache clear | [target] | Positional: kit name or <name>@<version>; omit to clear the whole cache | | assess, detect, list, probe, validate | --json | Replace the command's output with JSON |

Authentication

| Command | Description | | -------------------- | --------------------------------------------------------------- | | tangyr auth login | Store a client ID/secret credential pair, validating by default | | tangyr auth status | Show current credential validation state | | tangyr auth logout | Remove the stored credential |

Resolved environment-first (TANGYR_ACCESS_CLIENT_ID/TANGYR_ACCESS_CLIENT_SECRET), then from the stored file, then interactively.

Downgrading. The credential file (~/.tangyr/credentials.json) uses the client ID/secret pair format. To revert to a CLI predating this format, delete that file and run tangyr auth login again on the older version — the older CLI's single-key reader rejects, rather than misreads, the newer pair-format file, so no silent corruption occurs in either direction.

Remote kit source

A kit can be installed from a signed remote origin instead of a local directory. The subsections below cover configuration, the cache layout, offline/refresh/freshness behavior, and the maintenance commands; see Exit codes below for the codes a remote install/sync/kit update can reach.

In brief. Once installed, install/sync resolve a warm cache entry with no network call for 24 hours after the last successful authorization check (--refresh forces a re-fetch and re-verify; --offline forces cache-only resolution and fails if nothing is cached). tangyr kit update refreshes the cache without running the install/sync pipeline; tangyr cache clear removes cached kits. See Offline, refresh, and authorization freshness and Maintenance commands below for the full behavior, and Authentication above for the credential-store downgrade path.

Quickstart

# 1. Authenticate — stores a validated credential pair.
export TANGYR_ACCESS_CLIENT_ID=... TANGYR_ACCESS_CLIENT_SECRET=...
tangyr auth login

# 2. Point the config at the remote origin (tangyr.config.yaml).
#    source:
#      type: remote
#      url: https://kits.example.com

# 3. Install by name. A bare name resolves through the channel pointer;
#    pin a version instead when you want an exact, immutable one.
tangyr install --kit acme-kit
tangyr install --kit [email protected]

sync --kit [email protected] follows the same form. Both commands fetch, verify the publisher signature and per-file content hashes, materialize into the local cache, and hand the result to the unchanged local install pipeline.

Remote source configuration

Set the source scheme in tangyr.config.yaml. All three keys are optional and additive — a config that omits them behaves exactly as before.

source:
  type: remote # "local" (default) or "remote"
  url: https://kits.example.com # required when type is remote
  channel: stable # channel a versionless --kit resolves through; default "stable"

type defaults to local when absent. When it is remote, the local source.* path keys are ignored at resolution; they are retained rather than stripped, so switching back to local needs no restoration. A type that is neither local nor remote is rejected at load rather than silently treated as local, and type: remote with no url is rejected as well.

A remote kit is referenced on both commands either by name and explicit version, or by name alone:

tangyr install --kit [email protected]   # exact, immutable
tangyr install --kit acme-kit         # resolves through the channel pointer
tangyr sync --kit [email protected]

An explicit <name>@<version> is immutable and never consults a channel. A bare <name> resolves through the signed channel pointer named by source.channel, defaulting to stable; source.channel must be lowercase alphanumeric with hyphens, 1-32 characters, and is rejected at config load rather than mid-resolution. Passing a bare URL as --kit is rejected with a message pointing at source.url.

A channel pointer is served no-store and is never cached, because "the newest version as of some earlier run" is not what a channel means. Two paths therefore answer from the last version this machine saw, and both say so: --offline, which has no other option and exits 8 if the channel has never been resolved here, and a genuine network failure, which falls back and reports the run as degraded. A pointer that is rolled back, expired, or signed for another channel does not fall back — it fails with exit 7, because proceeding on a remembered version would convert a detected downgrade into a silent one.

| Variable | Default | Purpose | | ------------------ | ------------------ | --------------------------------------------------------------------- | | TANGYR_CACHE_DIR | ~/.tangyr/cache/ | Root of the local materialized-kit cache. A leading ~/ is expanded. |

Cache layout

A materialized kit is published under the cache root at <cacheRoot>/<name>/<version>/. The presence of a .tangyr-complete marker file inside that directory is what makes the entry complete; an entry without one is a partial write from an interrupted materialization and is never read. A .staging/ sibling directory under the cache root holds in-progress materializations before they are published — it is not itself a cache entry, and is safe to remove between runs. A cache entry also carries .tangyr-bundle.json, the signed bundle (manifest, files, signature) that produced it, written immediately before the completion marker and read back on a warm-cache use to re-verify the publisher signature without a network fetch; a bundle that declares a file at either reserved filename is rejected before any write.

Publishing is atomic: a materialization is verified and written into a unique temporary directory under .staging/, then made visible with a single directory rename into <name>/<version>/, with the completion marker written last. Two concurrent materializations of the same <name>@<version> stage independently and cannot corrupt each other or expose a half-written kit.

A warm, currently-valid entry that is also freshness-fresh (within the 24-hour authorization-freshness window from the last successful check for that origin) resolves fully offline, with no network call at all. Beyond the window, the next install/sync performs a lightweight authorization re-check against the origin. A definitive denial fails closed with no cache fallback; a genuine network failure during that re-check falls back to the warm cache with a warning and, under --json, a {"degraded":true} signal. See Offline, refresh, and authorization freshness below.

The cache root also carries a .tangyr-freshness.json file, recording, per configured origin, the timestamp of the last successful authorization check — used by the cache-first freshness gate. This file is not itself a cache entry: it is excluded from tangyr cache clear.

Limits and file modes

A materialized bundle is bounded on two axes: the total decompressed payload may not exceed 50 MiB, and no single file inside it may exceed 5 MiB. The total ceiling is enforced while the response is streamed — the request is aborted the moment the running total would cross it, before the payload is buffered — so an oversized or maliciously compressed response is cut off mid-transfer rather than fully downloaded first. The per-file ceiling is checked after each file is decoded and hashed.

Materialized files are written at 0600 inside directories created at 0700. No symbolic or hard links are ever created by the materialization pipeline, and a pre-existing symbolic link at a write target is refused rather than followed.

Credential-precedence trap: setting exactly one of TANGYR_ACCESS_CLIENT_ID / TANGYR_ACCESS_CLIENT_SECRET fails every remote install and sync with exit 5 — even when a valid stored credential from tangyr auth login exists. A leftover shell export, a half-applied .env, or an inherited CI variable is enough to trigger it, because the partial environment is treated as a misconfiguration and never falls through to the stored credential. The fix: unset the stray variable, or set both.

CI and non-interactive operation

install/sync on a remote source resolve the credential from TANGYR_ACCESS_CLIENT_ID/TANGYR_ACCESS_CLIENT_SECRET or the stored credential file only — this path never prompts, with or without the global --non-interactive flag, so a CI pipeline supplying both environment variables never hits a prompt on the credential-resolution step. auth login additionally supports --non-interactive explicitly: under it, a credential that cannot be resolved fails closed naming both environment variables rather than prompting. The cache root is stable and documented (~/.tangyr/cache/ by default, TANGYR_CACHE_DIR-overridable — see Remote source configuration above), so a CI pipeline can restore it between runs to keep a warm cache. A fully non-interactive run with environment credentials against the documented cache path is exercised end to end by tests/integration/remote-first-run.test.ts; a real spawned CLI process reaching a real (fixture) origin non-interactively, with zero prompts, is additionally exercised by the exit-5 and exit-7 cases in tests/e2e/cli.test.ts. A full non-interactive success through a real spawned process against a real signed production origin is not yet provable — it requires a real, live, signed origin, which does not exist yet (plan Decision 9's origin-provisioning track).

Offline, refresh, and authorization freshness

The cache root's .tangyr-freshness.json file (Activity 1) records, per configured origin, the timestamp of the last successful authorization check. A warm, currently-valid entry resolves fully offline — no network call at all — for 24 hours from that timestamp. Beyond the window, the next install/sync performs a lightweight re-check against the origin before resolving.

--offline forces cache-only resolution: it never fetches and never mutates. A complete, valid entry resolves immediately; a missing or unusable entry exits 8 (Offline cache miss) naming the tangyr kit update remediation. --refresh forces a re-fetch, re-verify, and replace, bypassing the freshness gate entirely — it always reaches the origin. --offline and --refresh are mutually exclusive; passing both exits 1.

On a network failure during the lightweight re-check (not a definitive denial), resolution falls back to the warm cache with a warning printed to stderr, since the check could not confirm freshness. Under --json, this same condition also emits a {"degraded":true,"kit": "<name>@<version>"} line to stderr. Neither install nor sync has a full JSON output mode yet: --json is scoped to exactly this one signal and is a no-op when resolution was not degraded — the rest of the command's human-readable output is unchanged either way.

Migrating a pre-existing cache entry. A cache entry materialized before this StepLedger (no .tangyr-bundle.json) is not corrupt — it is a legitimate entry in an earlier format. It is treated as an offline cache miss (exit 8) specifically under --offline, because there is nothing to re-verify signature freshness against without a network attempt. Running tangyr kit update --kit <name>@<version>, or any install/sync without --offline, while online, upgrades it to the current format.

Maintenance commands

tangyr kit update --kit <name>@<version> is install-free: it re-fetches and re-verifies the named kit into the cache — replacing whatever is already there, regardless of the current cache state (missing, stale, warm-and-fresh, or a pre-existing legacy-format entry) — without running the install or sync pipeline. It does not affect the authorization-freshness window: it makes no call to the lightweight authorization probe and neither reads nor writes .tangyr-freshness.json, since a full re-fetch and re-verify is a strictly stronger refresh than a freshness check. A later install/sync still evaluates its own freshness window independently of any tangyr kit update that ran before it.

tangyr cache clear [target] removes cached kits from the local cache, in one of three forms, each a distinct blast radius:

tangyr cache clear                    # every cached kit, every version
tangyr cache clear acme-kit           # every version of acme-kit only
tangyr cache clear [email protected]     # exactly that one cache entry

No target is the widest and only destructive form: it removes every <cacheRoot>/<name> directory. A bare <name> removes every version of that one kit as a single directory. A <name>@<version> removes exactly one cache entry, the narrowest of the three. None of the three forms ever removes the cache root's .staging/ directory or its .tangyr-freshness.json file — both are reserved cache-root filenames, excluded by name from every form, not only from the no-argument enumeration.

The command is idempotent: running it again with the same argument, once its targets are already gone, reports "nothing to remove" and exits 0 rather than throwing or silently reporting a false positive. Without --yes, it prompts for confirmation, naming the targets it is about to remove rather than a bare count; --dry-run previews the targets without removing anything; --yes skips the prompt.

cache clear's [target] is this codebase's first use of Commander's .argument() for a command's own positional value — distinct from every .option() flag on this or any other command. It is the established pattern for a future command that needs exactly one optional positional value: .argument("[name]", "<description>"), with named flags still declared via .option(). Commander delivers the positional argument's value as the .action() callback's first parameter, ahead of any options object — the reverse of every option-only command's callback signature.

The kit's instructions file

One resolution, used by install, sync, every adapter and verify alike:

  1. source.instructions in tangyr.config.yaml, when set to something other than the ./AGENTS.md default. An explicit declaration naming a file the kit does not carry is an error, reported before anything is written.
  2. entrypoint in tangyr-kit.yaml.
  3. ORIENTATION.md, then AGENTS.md, then the legacy CLAUDE.md (which installs, with a notice naming the rename).

A kit whose entrypoint is ORIENTATION.md therefore needs no source.instructions, and verify recognises it.

Configuration file

tangyr.config.yaml declares schema: 1. A key this schema does not define is rejected, naming the key it was probably meant to be — an unrecognized key is never read, so whatever it was meant to set stays at its default, and target: for targets: silently reduced an install to one tool. Prefix a key with x- to keep it as an annotation.

In tangyr-kit.yaml, discipline is optional: declare it to opt into the corpus-version check, omit it for a kit that is not derived from the shared corpus.

Conflict policy

Before writing, each destination is classified and resolved according to onConflict in tangyr.config.yaml: ask (default; prompts, and resolves to skip when there is no one to ask), skip, backup, or overwrite.

--yes supplies the confirmation a non-interactive run needs; it does not override the posture. Under --yes, skip leaves conflicting destinations unchanged and says which, overwrite replaces without a backup, ask leaves the destination alone and names the postures that replace, and backup copies the existing file — or the existing directory, recursively — into .tangyr/backups/<timestamp>/ and records it in the manifest so uninstall can put it back. A symlink is backed up as a symlink and restored as one: its target is untouched either way, but the link itself is the operator's artifact — often the one committed with the project — so replacing it without a record left nothing to put back.

One decision is taken per destination, not per target, so a file several targets write — AGENTS.md is claimed by four — is backed up once.

Accepted loss

Some losses are structural: OpenCode expresses hooks only as plugin code, Cline has no committable persona file. A kit with hooks installed for OpenCode therefore exits 3 on every run, however correct the kit is.

acceptedLoss names those, so that a genuinely new error entry is still loud:

acceptedLoss:
  - opencode/hook-as-plugin # <target>/<entry-type>
  - cline/* # every error entry for one target
  - persona-not-representable # this entry type on any target

--ignore-errors remains the blunt instrument: it suppresses the exit code for every error entry, including ones nobody has looked at yet.

Not every error entry is structural. rule-not-delivered (below) reports a rule that did not reach a target on this run, which is usually fixable — by giving the target an instructions path of its own, or by not enabling it. Accept it when you have decided to live with it, not to quiet it.

What did not arrive

A kit's artifacts reach a target through that target's own surfaces: a file in its agents directory, a prompt, a workflow, a skill folder, a section in the instructions document it reads. The surfaces differ per target, and some targets have none for a given category.

After every target has written, the run measures what is actually on disk and says so — one line per target, the counts first, and whatever did not arrive named beneath:

What reached each target (measured on disk):
  claude-code  agents 1/1  commands 1/1  rules 1/1  skills 1/1  hooks 1/1
  copilot      agents 1/1  commands 1/1  rules 1/1  skills 1/1  hooks 1/1
  codex        agents 1/1  commands 1/1  rules 0/1  skills 1/1  hooks 1/1
               rules missing: git-safety
                 folded into AGENTS.md, which cline wrote last
  cline        agents 0/1  commands 1/1  rules 1/1  skills 1/1  hooks 0/1
               agents missing: reviewer
                 cline has no agents surface in this scope

Measured on the files, not on what the compilers intended: the two have disagreed, and the files are what the target reads. Nothing is printed when everything arrived; --verbose shows the block either way.

The same fact reaches the loss report — <scope>/loss.json, rewritten by every install and sync, plus any --loss-report <path> copy — as a rule-not-delivered, agent-not-delivered, command-not-delivered, skill-not-delivered or hook-not-delivered entry, one per artifact per target, at error severity — so a run that loses part of the kit exits 3 rather than reporting success. A category the target has no surface for at all is left to the structural entry that already says so (persona-not-representable, hook-as-plugin), rather than repeated once per artifact.

The loss report

.tangyr/loss.json (project scope) or ~/.tangyr/loss.json (global) is written by every install and sync, and removed by uninstall with the manifest. It holds one entry per target and component that could not be translated natively, each with its closed-vocabulary type, its severity and a human-readable detail, under a generatedAt timestamp.

It used to be written only when --loss-report <path> was passed, which nobody passes twice: a project found theirs five days stale, naming a component renamed since, while the kit had grown from two components to thirty-four. A report that is only ever a photograph of one run does not belong in a directory that looks like state.

Exit codes

The full exit-code taxonomy, in numeric order. Every code below 5 predates the remote-source work and is shared by every command; codes 5-9 are specific to a remote install/sync/kit update (see Remote kit source above for the pipeline that reaches them).

| Exit code | Meaning | Reached when | | --------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 0 | Success | The command completed with no fatal error. A sync/install whose loss report contains only info/warning-severity entries also exits 0. | | 1 | General failure | The generic, catch-all failure code for any error not mapped to a more specific code below — including a CLI usage error such as an unrecognized command, a malformed --kit <name>@<version> reference, or --offline and --refresh passed together; a corrupt or legacy-format installation manifest (ManifestCorruptError, ManifestLegacyError, src/core/manifest.ts) also falls through to this code, since handleFatalError carries no dedicated branch for either. | | 2 | Conflict | Reserved for a conflict-resolution failure (ConflictError, src/core/exit.ts); declared and routed by handleFatalError, but not thrown by any current production code path. | | 3 | Completed with error-severity loss | An install/sync run completes but its loss report contains at least one error-severity entry, distinct from the generic failure code 1. --ignore-errors on install/sync forces a zero exit for authors who accept the loss. | | 4 | Target not detected | Reserved for a configured target tool whose runtime cannot be detected (TargetNotDetectedError, src/core/exit.ts); declared and routed the same way as exit 2, and likewise not thrown by any current production code path. | | 5 | Authentication required | The resolved credential is missing, partially configured (see the credential-precedence trap above), or rejected by the origin (401/403) — at login, at a cold fetch, or at the post-freshness-window re-check. | | 6 | Remote unavailable | A transient fetch failure exhausts the bounded retry (3 attempts, 250 ms then 1000 ms backoff) with no warm cache to fall back to, or a warm-cache freshness re-check fails the same way with no usable fallback path. | | 7 | Integrity failure | The signed bundle fails signature verification (including an unknown or out-of-window signing-key identifier), identity binding, a per-file content-hash check, a size ceiling, or path sanitization; or the channel pointer for a versionless --kit <name> cannot be resolved, is not signed by a pinned key, or names a version older than the one already seen. | | 8 | Offline cache miss | --offline with no complete, valid cache entry for the requested reference. | | 9 | Incompatible version | The running CLI is older than the bundle's declared minimum-compatible version, or the origin rejects this CLI's contract version. | | 10 | Configuration missing | No tangyr.config.yaml is found (ConfigNotFoundError, src/core/config.ts) for a command that requires one. | | 130 | User interrupt | The process received SIGINT (Ctrl-C) during a command. |

Global flags

| Flag | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------- | | --config <path> | Use an explicit configuration file | | --kit-path <path> | Use an explicit operating kit root | | --project-root <path> | Directory a project-scope operation writes into (see Scope) | | --dry-run | Report actions without modifying files | | --yes | Skip confirmation prompts | | --non-interactive | Never prompt; fail closed when a required input is missing (auth login) | | --verbose | Detailed output | | --quiet | Errors only | | --no-color | Disable colors | | --claude-config-dir <path> | Claude Code global root directory (repeatable). Overrides claudeCodeGlobalPaths in the config file. |

Supported tools

  • Claude Code — instructions (AGENTS.md plus the CLAUDE.md shim), agents, skills, commands, rules, output styles, hooks, settings, and MCP, placed in scope-resolved Claude Code paths (fanned out across every configured global root — see Multiple Claude Code global roots)
  • GitHub Copilot — instructions (AGENTS.md plus the always-read .github/copilot-instructions.md mirror; project scope only, since Copilot has no global instructions slot), agents, prompts (compiled from commands), rules (.github/instructions/<name>.instructions.md, each carrying the rule's path scope as an applyTo glob; project scope only, since that is a repository path with no user-level equivalent), skills, hooks (~/.copilot/hooks or .github/hooks), and MCP. Hook matchers are rewritten to Copilot's own lowercase tool names (bash, view, create, edit, grep) — an untranslated Bash matcher never fires
  • OpenAI CodexAGENTS.md, agent TOML files, rules (.rules files for exec-policy, appended to the instructions file as guidance otherwise), skills and commands (both as skill directories at Codex's documented skills path — ~/.agents/skills globally, .agents/skills inside a repository — because Codex deprecated its custom prompts in favour of skills), hooks (hooks.json plus the shared ~/.agents/hooks script bridge; macOS and Linux only), and MCP in config.toml
  • OpenCode — instructions (~/.config/opencode/AGENTS.md globally; .opencode/AGENTS.md inside a project, declared in the instructions field of opencode.json, because the project-root AGENTS.md is written by four other targets in turn and only the last writer's content survives), agents, commands, skills (OpenCode-local plus the shared ~/.agents/skills link), and opencode.json (MCP, permissions, custom provider), in XDG config paths. Hooks are not delivered: OpenCode exposes hooks only as plugin code, so a declared hook event is dropped and recorded as a hook-as-plugin loss
  • Cursor — instructions, subagents, .mdc rules, commands, skills (Cursor-local plus shared), hooks.json plus the shared hook-script bridge (matchers rewritten to Cursor's tool names — Shell, Read, Write, Grep), mcp.json, and permissions.json
  • Cline — instructions (AGENTS.md, project scope only), rules, workflows (compiled from commands), skills, and MCP. Agents and hooks are not delivered: Cline has no committable persona file (persona-not-representable) and exposes hooks only as @cline/sdk plugin code (hook-as-plugin); both are recorded as losses rather than silently skipped

Quick start

# Initialize a configuration in your project
npx @alessandroraffa/tangyr init

# Install a kit by path (preview with --dry-run first)
npx @alessandroraffa/tangyr install --kit-path ./path/to/operating-kit --dry-run
npx @alessandroraffa/tangyr install --kit-path ./path/to/operating-kit

# Check installation status, then sync after a kit update
npx @alessandroraffa/tangyr status
npx @alessandroraffa/tangyr sync

Scope

Every write command accepts --scope global or --scope project. The CLI flag overrides config.scope for that invocation.

| Scope | Manifest location | Claude Code artifacts | | --------- | ------------------------- | ----------------------------------- | | global | ~/.tangyr/manifest.json | ~/.claude/ | | project | .tangyr/manifest.json | repo root (AGENTS.md, .claude/) |

Which directory is the project

A project-scope operation resolves its root in this order:

  1. --project-root <path>, when given.
  2. The directory of the tangyr.config.yaml this run discovered — one found in the working directory or an ancestor. That file sits at the project root by construction, and kitPath is already resolved against it.
  3. The working directory, when the config was named explicitly with -c. An explicit path may name a shared or CI config outside the tree and says nothing about where the project is.
  4. The nearest enclosing directory carrying tangyr.config.yaml or .tangyr/, for the commands that run without a config at all; failing that, the working directory.

Running from a subdirectory therefore installs at the project root rather than in the subdirectory. Deliberately only Tangyr's own markers are searched in step 4: a package.json or .git above the working directory says where some project starts, not that this installation belongs there.

Multiple Claude Code global roots

If you maintain more than one Claude Code account (e.g. ~/.claude and ~/.claude-work), you can keep them identical with a single sync operation.

Via config (tangyr.config.yaml):

claudeCodeGlobalPaths:
  - ~/.claude
  - ~/.claude-work

Via environment variable (TANGYR_CLAUDE_CONFIG_DIRS):

A colon-separated list of Claude Code global root directories. Entries support ~/ expansion. This layer is designed for machine-specific configuration that must not be committed to a shared kit config.

export TANGYR_CLAUDE_CONFIG_DIRS="~/.claude:~/.claude-work"
tangyr sync

Auto-sync use case: if a kit-change hook or automation runs tangyr sync, exporting TANGYR_CLAUDE_CONFIG_DIRS in the shell environment keeps multiple Claude configs in sync automatically — without putting machine-specific paths in a shared tangyr.config.yaml.

Via CLI flag (repeatable):

tangyr sync --claude-config-dir ~/.claude --claude-config-dir ~/.claude-work

Precedence: --claude-config-dir flag > TANGYR_CLAUDE_CONFIG_DIRS env > claudeCodeGlobalPaths in config > default ~/.claude.

Per-root artifacts (instructions, agents, skills, commands, rules, output-styles, hooks settings) are written under each root. Shared artifacts (~/.claude.json for MCP, ~/.agents/hooks for hooks) are resolved from the platform default and written once regardless of how many roots are configured.

The manifest records the full set of roots written so that verify and uninstall operate on all of them.