Driving RouterOS with centrs

SkillFiles & storage

Lets your agent read and change settings on MikroTik routers, run commands, move files, and find nearby devices.

Use Driving RouterOS with centrs in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Driving RouterOS with centrs and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Driving RouterOS with centrs skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Driving RouterOS with centrsStart free
About this skill

Use whenever a task touches a real MikroTik RouterOS device or CHR: reading or changing config, running a RouterOS CLI command non-interactively, checking a command is well-formed before sending it, moving files on or off a device, discovering neighbors, or doing the same thing across several router

What this skill tells your AI

The instructions your AI receives, as published by tikoci/routeros-skills in routeros-centrs/SKILL.md and read by Ahel’s review.

What this is for

@tikoci/centrs is a friendly conduit to RouterOS, not a configuration abstraction. You still speak RouterOS; centrs handles the parts that are tedious and error-prone to do by hand:

  • resolving <router> to an address, credentials, port, and a protocol;
  • validating a RouterOS-shaped command before it runs;
  • returning the same structured envelope whatever transport carried the call.

Validation and structured diagnostics are the product. Without them it would be a worse curl.

Reach for centrs when you need to run a RouterOS command, read state, or move a file — from a shell script, a test, or an agent loop.

Don't reach for it when you need RouterOS documentation (use the rosetta MCP or the routeros-fundamentals skill), or when you need to create the router itself (use routeros-quickchr-cli from a shell, or routeros-quickchr for the TypeScript library — centrs consumes a quickchr VM, it does not boot one).

Status: 0.1.x preview under active development. The repo README publishes preview builds under npm's next tag, so pin @next rather than assuming latest tracks them — confirm with npm view @tikoci/centrs dist-tags. Expect breaking changes before 1.0 and exercise writes on lab or disposable targets first. docs/MATRIX.md is the single source of truth for what works today; treat anything not green there as not-yet-shipped.

Install and first call

bunx @tikoci/centrs@next --help                 # no install
bun add @tikoci/centrs@next                     # library + local CLI

The offline analyzer needs no router and no credentials — it is the cheapest way to confirm centrs works at all:

bunx @tikoci/centrs@next explain '/ip/address print' --json

The loop: explain → run

This is the pattern centrs exists for. Analyze the command offline, then run it.

# 1. Is this well-formed, and what will it actually do?
centrs explain '/ip/address/add address=192.0.2.1/24 interface=ether2'

# 2. Run it. Writes need --yes when there is no TTY to prompt on.
centrs execute lab --yes '/ip/address/add address=192.0.2.1/24 interface=ether2'

# 3. Read it back as data.
centrs retrieve lab /ip/address --json

explain is offline: it opens no connection, canonicalizes the input, reports structure and syntax diagnostics, and tells you which centrs command would carry it. It is also the first thing to run when a command is rejected — see When a command is rejected.

explain is deliberately conservative: a pass verdict means centrs found nothing wrong offline, never that RouterOS will accept it. The envelope says so itself with runtimeAcceptance: "not-proven". Live device probes are designed, not shipped.

Naming a router

Every router-facing command resolves a target. Four ways, in the order you will want them:

You haveUse
A CHR booted by quickchr--quickchr <name> — resolves host, port, and credentials from the live VM; no credential plumbing at all
A router saved in WinBoxthe positional <router> — centrs reads ~/.config/tikoci/winbox.cdb (the WinBox address book is the device registry; centrs keeps no store of its own)
Neither--host / --port / --username / --password, or CENTRS_USERNAME / CENTRS_PASSWORD
Nothing yetcentrs discover over MNDP, --save to write found neighbors into the CDB

--quickchr is exclusive of positional targets and of --host/--port/--username/ --password; pick one mechanism per call.

Fan out instead of looping

--quickchr repeats, and the CDB selectors fan out too. Prefer these to a shell loop — one envelope, bounded concurrency, per-target errors that do not abort the rest:

centrs retrieve --quickchr sun --quickchr earth --quickchr comet /system/resource --json
centrs execute --group edge --yes '/system/ntp/client/set enabled=yes'
centrs retrieve --all /system/resource --json

Fan-out selectors: --group <name>, --all, --where <attr>=<value>, --near, --bbox. --default is a target selector too, but it picks the single reserved __default__ record rather than fanning out.

The command map

Twelve commands. The three obvious ones are not the whole surface — explain, api, and discover are the ones most callers never find.

CommandPurposeState
retrieve <router> <path>Read RouterOS stateshipped (REST, native API)
execute <router> '<cli>'Run a RouterOS CLI-shaped read/writeshipped (REST, native API, SSH, MAC-Telnet)
api <router> <endpoint>Structured passthrough, gh api style: -X PUT, -f k=v, --query, --streamshipped (REST, native API)
explain '<input>'Offline analysis of a RouterOS commandshipped offline; live probes designed
transfer <router> upload|download|list|remove|mkdir|copyDevice filesshipped (REST, native API, SFTP)
terminal <router>Interactive consoleshipped (SSH, MAC-Telnet)
discoverMNDP neighbor discovery, --save into the CDBshipped
devicesThe CDB device registry, and the atomic write layer every CDB mutation routes through — including discover --saveshipped
settingscentrs's own preferences (centrs.env)shipped
mcpScoped stdio MCP server, CDB-gatedshipped
btestMikroTik bandwidth test, client or servershipped
checkReachability + health batterydesigned only — not implemented

retrieve vs execute vs api: retrieve reads a menu; api is the structured operation surface (it can write); execute takes a literal RouterOS CLI string and is the only one that reaches SSH and MAC-Telnet. There is no update command — CLI-shaped writes ride execute.

Read the envelope, not the text

Every call returns one shape, whatever the transport (the one exception is api --raw, which deliberately strips the envelope and emits bare RouterOS JSON):

{
  "ok": true,
  "data": [ /* the payload */ ],
  "warnings": [],            // always present; non-fatal anomalies about this result
  "tips": [],                // always present; advice that is NOT a problem
  "meta": {
    "target": {},            // resolved target + where each field came from
    "via": "rest-api",       // the protocol actually chosen
    "settings": {},          // which setting won, and from which source
    "validation": {},        // validator name + result, if validation ran
    "operation": { "objectCount": 3 }
  }
}

Pass --json (or --format json). This is the single most-missed thing about centrs. retrieve and execute default to --format text, and their text output renders data as pretty-printed JSON — so piping the default straight into a JSON parser appears to work while silently discarding warnings, tips, and all of meta. (api already defaults to JSON.)

Two consequences worth internalizing:

  • meta.operation.objectCount is the reliable row count. data itself is shape-unstable today: zero rows come back as an empty object, one row as a bare object, and N rows as an array (centrs#360). All three need handling, and the empty object is the one that bites — it is truthy, so the obvious d ? [d] : [] invents a row that does not exist, exactly during the failover or empty-menu read you were measuring. Take the count from meta, and normalize with something that excludes it:

    const rows = Array.isArray(d)
      ? d
      : d && typeof d === "object" && Object.keys(d).length > 0
        ? [d]
        : [];
    
  • tips and warnings are separate channels on purpose. A tip is explicitly not a problem; do not treat a non-empty tips array as a failure.

Writing

Write-shaped commands are gated. With a TTY, centrs prompts; without one it refuses:

$ centrs execute lab '/ip/address/add address=192.0.2.1/24 interface=ether2'
[usage/confirmation-required] Write-shaped RouterOS execute commands require explicit confirmation.
Fix: Pass `--yes` in non-interactive automation, or answer `yes` at the TTY prompt after reviewing the command.

So: a non-interactive execute or api write needs --yes, as does a mutating transfer fan-out across several routers. Overwriting an existing file is a separate gate — transfer --force / --overwrite, not --yes. Never disable validation to make a write succeed: validation is the product, and --no-validate should be a deliberate, explained choice, not a workaround.

Files

centrs transfer <router> upload ./routeros-7.24.4.npk
centrs transfer <router> list
centrs transfer <router> download flash/backup.rsc ./backup.rsc

RouterOS's /file contents write caps at 60 KB, so REST and native API cannot carry a large upload. With no --via pinned, centrs already notices the size and auto-selects SFTP — you do not need to pick the transport. On a device with no SSH key installed that auto-hop can fail on authentication rather than on size: the SSH clients run with BatchMode=yes, so an empty or password-only credential is not usable there even though REST accepts it.

When a command is rejected

Validation is two stages, and which one rejected you is the whole diagnosis:

  1. Offline — the same analyzer explain uses runs first, with no connection. A syntax fault is refused here with the offending byte span, and its remediation tells you to run centrs explain to see it in context. No round trip happens.
  2. Device — :parse plus /console/inspect on the router, for the semantic half offline analysis cannot decide.

--validate=false disables both. A clean offline pass is necessary, never sufficient.

The device stage is where the error text is thin, and the failure mode is worth knowing because it costs agents real time:

$ centrs execute lab '/container/print'
[validation/syntax] RouterOS rejected the command syntax while parsing it.
Fix: Fix the RouterOS CLI syntax (quotes, brackets, attribute form), then retry.

That remediation is often wrong. validation/syntax is what you get when the path does not exist for any reason — including a menu whose package is not installed or whose device-mode feature is off (centrs#361). Re-quoting a command that has no quoting problem is an infinite loop. When you see it:

  1. Run centrs explain '<the same command>'. If it passes offline, the input is well-formed and the problem is the device, not the string — and since stage 1 already ran the same analyzer, a rejection you received from a router is by definition one offline analysis let through. Re-quoting cannot help.
  2. Check the device — centrs retrieve <router> /system/package --json, then centrs retrieve <router> /system/device-mode --json. A missing container, zerotier, or wireless package is the usual answer.
  3. Only then suspect the syntax — and use explain's diagnostics rather than guessing.

Re-run with --verbose to get the error context, which carries the device's own :parse output; the default two-line render drops it, along with the per-code details URL and the (line N column M) position (centrs#362). Every error code has a page at https://tikoci.github.io/centrs/errors/<code>.

Known rough edges

Behaviors to plan around rather than debug from scratch. Each links to the tracking issue — if you hit one, add evidence there rather than working around it silently.

  • A hang can be the success path. /system/device-mode/update container=yes blocks by design while RouterOS waits for a hard power-cycle to confirm. centrs reports that as a plain timeout on every transport (centrs#363). For a CHR, set device-mode at VM creation and let quickchr do the power-cycle. The same applies to /system/reboot, package installs, and unbounded /ping.
  • No wait primitive. There is no --wait / --until; readiness and convergence polling is the caller's job today (centrs#364). Poll a cheap read (retrieve <router> /system/resource) against a wall-clock deadline, and do not silence its errors — "not converged" and "centrs failed" must stay distinguishable.
  • Output can contain secrets. centrs redacts credentials it holds; it has no notion of secrets RouterOS returns (centrs#359). /file print detail can inline private keys and API tokens, and /export show-sensitive=yes is one word from the safe default. Prefer targeted reads over broad dumps, especially through MCP, where the output leaves the machine.
  • REST timeouts cap at 60 s. --timeout above that is rejected on rest-api.
  • check is not implemented. It is designed in the matrix; do not build on it.

Authoritative docs & related skills

  • centrs repo: README · docs/CLI.md (generated full flag reference) · docs/CONSTITUTION.md (envelope, error model, protocol selection) · docs/MATRIX.md (what actually works) · commands/ (per-command contract and worked examples)
  • routeros-quickchr-cli / routeros-quickchr — boot the CHR that --quickchr then targets: the -cli skill for the shell path, the other for TypeScript harnesses. The pair is the normal grounding loop: quickchr creates the router, centrs drives it.
  • routeros-fundamentals / routeros-scripting — what to actually say to RouterOS once centrs can reach it.
  • routeros-syntax-inspection — /console/inspect and :parse, the machinery behind centrs's validation gate.
  • routeros-mac-telnet / routeros-mndp — the L2 protocols behind execute --via mac-telnet and discover.

This skill is a starting point, not a manual. docs/CLI.md and commands/<name>/ in the repo are authoritative and versioned; when they disagree with this file, they win — and the disagreement is worth reporting at https://github.com/tikoci/centrs/issues.

Signals

GitHub stars
67
Forks
15
Last commit
Sep 2026
Advanced
Item type
skill
Key
routeros-centrs
Source
github.com/tikoci/routeros-skills