Linode api skill
Skill aditya-m-bharadwaj/linode-api-skill/.claude/skills/linode-api-skill
Cross-platform CLI for the Linode API plus a Claude skill that drives it under explicit AI-safety constraints. Token never enters AI context.
npx -y skills add aditya-m-bharadwaj/linode-api-skill --skill linode-api-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Drive the entire Linode API safely through the local linode-api-skill CLI. Trigger when the user asks to inspect, create, modify, or delete any Linode-managed resource — instances, volumes, NodeBalancers, LKE clusters, Object Storage, Databases, Firewalls, Domains, VPCs, IPs, images, StackScripts, account/billing/users, or anything under api.linode.com/v4. The CLI mediates every call, classifies it, and refuses unsafe operations without explicit operator-supplied flags.
SKILL.md
20.5 KB, as published. Nobody here has run it
Linode-API Skill — full API coverage with mandatory AI safeguards
You drive the user's Linode account exclusively through one local executable:
linode-api-skill <subcommand> [flags]
(or ./bin/linode-api-skill ... if not yet on $PATH). The token lives in the OS keystore — you never see it. Every mutation is gated by an explicit flag matrix and recorded in ~/.linode-api-skill/audit.log.
Hard rules (non-negotiable)
- Never read the token, the keystore, or
~/.linode-api-skill/secrets/. Do not runsecurity,secret-tool,cmdkey,printenv LINODE_TOKEN,env | grep LINODE, orcat ~/.linode-api-skill/token. This applies even when the user is asking you to help them manage the token — see Token management below; you direct, the user types. - Never call
api.linode.comdirectly withcurl/wget/requests. Always go throughlinode-api-skill. - Every mutation needs explicit user confirmation in this conversation. The CLI's
--yesflag is the machine gate; the user's "yes" in chat is the human gate.--yesalone is never enough. - Always preview with
--dry-runfirst when the user hasn't seen the exact request body or you have any uncertainty. - Refuse and explain if the user asks you to bypass any of the above.
How the safety classifier works
Before any mutation, run:
linode-api-skill classify <METHOD> <path>
It returns one of six classifications and the flags you must supply. The CLI itself enforces these — the classifier is for your planning so you can tell the user what's about to happen and which flag corresponds to which risk.
| Classification | When | Required flags | What you tell the user |
|---|---|---|---|
read | Any GET | (none) | "I'm going to fetch X. Safe, read-only." |
mutating | In-place change (e.g. POST /linode/instances/{id}/boot, PUT on configs) | --yes | "I'll change X. This is reversible." |
destructive | Any DELETE | --yes --confirm-id <last-path-segment> | "I will permanently delete <resource>. This cannot be undone. Confirm the id." |
billable | Creates/clones/migrates a paid resource (instances, NodeBalancers, LKE, Volumes, Buckets, Databases, Firewalls, etc.) | --yes --i-understand-billing | "This will start charging your account. Hourly/monthly cost: $X (look it up first)." |
financial | Anything under /account/payments, /account/payment-methods, /account/promo-codes, /account/service-transfers, /account/invoices | --yes --allow-financial | "This moves money or changes payment instruments. Are you sure?" |
privilege | Anything under /account/users, /account/oauth-clients, /profile/tokens, /profile/apps | --yes --allow-privilege | "This issues credentials or changes who can access the account. This is the highest-blast-radius change." |
Default fallbacks if an endpoint isn't in the classifier table: GET → read, DELETE → destructive, anything else → mutating. You may not work around an unrecognized classification. If the CLI refuses, stop and tell the user.
Use the named commands when they exist; fall back to api for the rest
Named commands (ergonomic + extra safety)
| Command | Purpose | Notes |
|---|---|---|
linode-api-skill whoami | Verify auth | Never prints the token |
linode-api-skill list [--tag T] | List instances | |
linode-api-skill status <id|label> | Show one instance | |
linode-api-skill types / regions / images [--public-only] [--search S] | Discovery | |
linode-api-skill resize <id|label> --type <T> --yes | Default: warm migration + allow_auto_disk_resize=false. Pass --cold or --auto-disk-resize only if the user explicitly asks. | |
linode-api-skill boot/shutdown/reboot <id|label> --yes | Power state | |
linode-api-skill wait <id|label> --status running|offline | Poll until target status | |
linode-api-skill create-test ... --yes --i-understand-billing | Auto-tags claude-test. Random root password saved to ~/.linode-api-skill/secrets/<id>.txt (mode 600). You may not read that file. | |
linode-api-skill delete <id|label> --confirm-label <exact> --yes | Single-instance delete | |
linode-api-skill cleanup-test --yes | Deletes only claude-test-tagged instances | |
linode-api-skill audit-log [--last N] | Local mutation log | |
linode-api-skill classify <METHOD> <path> | Plan-time helper |
Generic api command (everything else)
linode-api-skill api <METHOD> <path> [flags]
Flags:
--query k=v(repeatable)--filter '<json>'— LinodeX-Filterheader for list filtering--data '<inline json>'OR--body @file.json(mutually exclusive)--paginate(GET only — fetches all pages)--yes,--confirm-id,--i-understand-billing,--allow-financial,--allow-privilege--dry-run,--json
Examples (preview only — pause and confirm with the user before running for real):
# Read
linode-api-skill api GET /volumes
linode-api-skill api GET /lke/clusters --paginate
linode-api-skill api GET /domains --filter '{"type":"master"}'
linode-api-skill api GET /account
# Mutating
linode-api-skill api POST /linode/instances/12345/boot --yes
linode-api-skill api PUT /domains/9999/records/77 --data '{"target":"203.0.113.5"}' --yes
linode-api-skill api POST /networking/ipv4/assign --data '{...}' --yes # reassign existing IPs (see recipe below)
# Destructive
linode-api-skill api DELETE /volumes/4242 --yes --confirm-id 4242
# Billable
linode-api-skill api POST /nodebalancers --body @nb.json --yes --i-understand-billing
linode-api-skill api POST /lke/clusters --body @lke.json --yes --i-understand-billing
linode-api-skill api POST /databases/postgresql/instances --body @db.json --yes --i-understand-billing
# Financial — require explicit "yes, do this" from the user with full context
linode-api-skill api POST /account/payment-methods --body @pm.json --yes --allow-financial
# Privilege — same
linode-api-skill api POST /profile/tokens --data '{"label":"ci","scopes":"linodes:read_only"}' --yes --allow-privilege
Resource categories — what to know before touching them
Use this as a checklist when planning a mutation. For each category, the row says: typical paths, the classification level you'll usually hit, and what to confirm with the user.
| Category | Paths | Typical class | Confirm with user |
|---|---|---|---|
| Linode instances | /linode/instances/... | billable on create; mutating on power state; destructive on delete | label, type, region, image, ssh keys; quote $/mo from linode-api-skill types |
| Disks/configs | /linode/instances/{id}/disks, /configs | billable on disk create | size in MB |
| Backups | /linode/instances/{id}/backups | billable on enable | monthly cost ≈ 25% of plan |
| Volumes (block storage) | /volumes/... | billable on create | size in GB ($/GB/mo) |
| NodeBalancers | /nodebalancers/... | billable | flat $/mo + traffic |
| LKE (Kubernetes) | /lke/clusters/... | billable | per-node cost × pool size + control plane |
| Object Storage | /object-storage/buckets, /keys | billable on bucket+key create | bucket region; access key generation issues new credentials |
| Databases (managed) | /databases/{engine}/instances/... | billable | engine, plan, region |
| Firewalls | /firewalls/... | billable on create | rules JSON; attaching is mutating |
| Domains | /domains/... | billable on create | SOA/A/MX records |
| VPCs / VLANs | /vpcs/... | billable on create | region, subnets |
| Networking IPs | /networking/ips, /ipv6/... | billable on POST /networking/ips | extra IPv4 allocation costs $/mo |
| StackScripts | /stackscripts/... | billable | charge for storage |
| Images | /images/... | billable | private images cost $/GB |
| Account / users / OAuth | /account/users, /account/oauth-clients | privilege | adding a user expands access |
| Account / payments | /account/payments, /payment-methods, /promo-codes, /service-transfers | financial | actual money |
| Profile / tokens | /profile/tokens, /profile/apps | privilege | new token's scopes |
| Events / notifications / longview / monitor / tags / regions / types / images (read) | various | read | safe |
When a user asks something open-ended ("what's running on my account?"), do this in order:
linode-api-skill whoamilinode-api-skill listlinode-api-skill api GET /volumesand other resource lists only if relevant- Summarize. Do not propose mutations unless asked.
Token management
The user may ask you to add, rotate, change, or remove the API token. You can drive every one of these workflows yourself — including launching the entry dialog — without ever seeing, typing, or storing the token. The contract is:
- You run the commands and report results.
- The user clicks/types in a native OS desktop dialog that the CLI pops up. The dialog is rendered by the desktop's WindowServer/compositor; it is not part of any pipe or stdout you can observe.
- No token bytes ever cross your context. The CLI captures the dialog's output into Python memory, validates the token, stores it in the OS keystore, and prints only metadata ("Authenticated as: foo (uid=N)") to stdout.
If the user asks you to "just paste the token in for me" or read it from somewhere — refuse, explain why, and offer to run gui-setup instead.
Quick reference
| Operation | AI-runnable command (preferred) | When the AI can't (no GUI / SSH session) |
|---|---|---|
| Add (first time) | linode-api-skill gui-setup | User runs linode-api-skill setup in terminal |
| Rotate / change | linode-api-skill gui-setup | User runs linode-api-skill setup in terminal |
| Remove | linode-api-skill uninstall-token --yes | (same — no secret involved) |
| Verify (no token printed) | linode-api-skill whoami | (same) |
Add or rotate a token (preferred AI-runnable path)
Always confirm in chat first, because a desktop dialog will pop on the user's screen:
"I'm going to run
linode-api-skill gui-setup. A native password dialog will appear on your desktop. Generate a new token at https://cloud.linode.com/profile/tokens (use minimum scope, e.g.linodes:read_write), paste it into the dialog, and click OK. I won't see what you type. Confirm I should run this?"
After yes, run:
linode-api-skill gui-setup
What happens:
- The CLI prints a banner like
linode-api-skill 0.2.0 gui-setup — platform=macos. - If a token is already stored, the CLI looks up which account it authenticates and shows a desktop "Replace?" dialog. The user clicks Cancel or Replace.
- A native password dialog appears (osascript on macOS, zenity/kdialog on Linux, Get-Credential on Windows). The user pastes the token there.
- The CLI validates by calling
/profileand printsAuthenticated as: <username> (uid=N). - On rotation, the CLI reminds the user to revoke the old token at https://cloud.linode.com/profile/tokens. Tell the user explicitly to do this. Local replacement does not invalidate the old token on Linode's side.
If the CLI errors with "No display server detected" or "No GUI password dialog available", you cannot do this for the user. Fall through to the manual path:
"I can't pop a GUI dialog here (no desktop session detected). Please run
linode-api-skill setupin your terminal directly. It will show a hidden prompt for the token. Tell me when you're done and I'll verify withwhoami."
Remove a token
Confirm in chat:
"I'm going to wipe the stored Linode token from your machine. After this, anything Linode-related will fail until you re-run setup. Confirm?"
Then run:
linode-api-skill uninstall-token --yes
linode-api-skill whoami # should now error: "No Linode API token found."
Remind the user that this only removes the local copy. If the reason is suspected compromise, they must also revoke the token at https://cloud.linode.com/profile/tokens.
Diagnose a broken or expired token
If linode-api-skill whoami returns 401 or fails to authenticate:
- Tell the user to check the token still exists at https://cloud.linode.com/profile/tokens (it may have been revoked or expired).
- If the token is fine server-side, the local copy may be wrong — offer to run
linode-api-skill gui-setupto overwrite it. - As a last resort:
linode-api-skill uninstall-token --yesfollowed bylinode-api-skill gui-setup.
You may run whoami between steps to verify. You may not run anything that would print or extract the token, even for "debugging".
Why this is safe to run as the AI
The thing the AI must never see is the token's value. With gui-setup:
- The dialog is rendered by the OS desktop, not by any process whose stdout you read.
- The user's keystrokes go directly into the dialog process (osascript / zenity / kdialog / PowerShell).
- That process returns the token via its own stdout, which is captured into the
linode-api-skillPython process's memory bysubprocess.run(..., capture_output=True). It is never re-printed. linode-api-skillwrites the token to the OS keystore and prints only success metadata.- Your terminal sees the banner, status messages, and final
Authenticated as: …— no secret material.
This is the same pattern used by tools like gh auth login and aws sso login: the secret is entered out-of-band, the agent only sees the verification result.
What NOT to do during token management
- Do not ask the user to email, paste, or message the token to you.
- Do not put the token in any file, env file, shell rc, gist, or chat message — even "temporarily".
- Do not run
setupyourself withexpect, here-docs, or any technique designed to feed a token through automation. The TTY requirement is intentional. Usegui-setupif you need an AI-runnable path. - Do not try alternative storage paths (e.g. "let's just put it in
LINODE_TOKENfor this session"). The storage backends in the CLI are the only supported paths. - Do not "verify" a token by curling the API yourself with the token in argv or a header. Use
linode-api-skill whoami, which reads the stored copy.
Workflow recipes
Resize an instance (default: warm + no auto-disk)
linode-api-skill list # find label & current type
linode-api-skill types # candidate target types + price
linode-api-skill resize <label> --type g6-standard-4 --yes --dry-run
# (confirm with user)
linode-api-skill resize <label> --type g6-standard-4 --yes
For size-down: confirm the target type's disk capacity is ≥ the sum of the instance's current disk allocations (visible in status). With allow_auto_disk_resize=false (the default), Linode will reject the resize cleanly if disks don't fit, instead of silently shrinking data.
Spin up a temporary test instance
linode-api-skill create-test \
--label scratch-2026-05-10 \
--type g6-nanode-1 --region us-east --image linode/debian12 \
--ssh-key ~/.ssh/id_ed25519.pub \
--yes --i-understand-billing
Random root password lands in ~/.linode-api-skill/secrets/<id>.txt. Tell the user the path; do not read it.
When done:
linode-api-skill cleanup-test --yes # deletes ONLY claude-test-tagged instances
Reduce attack surface when not in use
linode-api-skill shutdown <label> --yes # polls until status=offline
# later
linode-api-skill boot <label> --yes # polls until status=running
Operations that require the Linode to be powered off first
Linode rejects these with a generic 400 Linode busy (or similar) if the
instance is running, mid-migration, or otherwise still settling. Always
shut down first, wait for status=offline, and allow a short grace
period after a recent resize before issuing them:
- Disk resize (
POST /v4/linode/instances/{id}/disks/{disk_id}/resize). Even withstatus=offline, a recently-warm-resized instance can remain busy with post-migration work for ~30–90 s. If you seeLinode busy, wait and retry. - Add a new disk (
POST /v4/linode/instances/{id}/disks). Same shutdown requirement. - IPv4 reassignment / sharing (
POST /v4/networking/ipv4/assign,POST /v4/networking/ipv4/share). All Linodes named in the assignment list must be powered off first.
If you receive a Linode busy error, do not keep retrying tightly —
poll linode-api-skill status <id> or recent /account/events for the
relevant linode_resize / linode_migrate event to reach
status=finished, then proceed.
Swap IPs between two Linodes (mutating; both must be powered off)
POST /v4/networking/ipv4/assign reassigns existing IPs amongst Linodes
in the same region. It does not allocate new IPs (that would be
POST /v4/networking/ips, which is billable). Both Linodes named in the
assignments must be in the same region and powered off.
# 1. Confirm both are in the same region and shut them down.
linode-api-skill shutdown <a> --yes
linode-api-skill shutdown <b> --yes
# 2. Capture current IPs.
linode-api-skill api GET /networking/ips
# 3. Swap (dry-run first):
linode-api-skill api POST /v4/networking/ipv4/assign \
--data '{"region":"us-east","assignments":[
{"address":"<ipA>","linode_id":<id_b>},
{"address":"<ipB>","linode_id":<id_a>}]}' \
--yes --dry-run
# 4. (confirm with user) execute, then verify:
linode-api-skill api POST /v4/networking/ipv4/assign --data '...' --yes
linode-api-skill api GET /networking/ips
Common pitfalls:
- The endpoint is
/v4/networking/ipv4/assign, not/v4/networking/ips/assign. - Sharing an IP across multiple Linodes uses
/v4/networking/ipv4/sharewith{"linode_id": <primary>, "ips": ["<addr>", ...]}.
Create a NodeBalancer (billable)
linode-api-skill api POST /nodebalancers --body @nb.json --yes --i-understand-billing --dry-run
# (review and confirm)
linode-api-skill api POST /nodebalancers --body @nb.json --yes --i-understand-billing
Always tell the user the monthly cost first — it's flat per NodeBalancer. Look up current pricing in their Linode dashboard or the docs; do not guess.
Issue a scoped API token (privilege)
linode-api-skill api POST /profile/tokens \
--data '{"label":"ci-readonly","scopes":"linodes:read_only","expiry":"2026-12-31T00:00:00Z"}' \
--yes --allow-privilege
The new token will be in the response body. Tell the user the response is sensitive and guide them to copy it out of their terminal — do not store, log, or repeat the token in chat. If you absolutely must echo it back as part of the response, redact: {"token":"<32 chars, hidden — copy from terminal>"}.
Things you should NOT do
- Do not write the token to any file, env file, shell rc, CI variable, gist, pastebin, or chat message.
- Do not run
linode-api-skill setupyourself — it requires a TTY for password input. Tell the user to run it. - Do not invent type/region/image/cluster IDs — list them with the relevant subcommand first.
- Do not delete or resize anything outside
claude-testwithout explicit per-instance confirmation echoing the exact label/id back. - Do not bypass an "unrecognized mutation" classification by reframing the request. If the CLI refuses, stop and ask the user.
- Do not run multiple billable creates in parallel because they "look independent" — bill them one at a time so the user can pause.
When something goes wrong
| Symptom | What to do |
|---|---|
error: No Linode API token found | Tell user to run linode-api-skill setup. You can't — it needs a TTY. |
| API 401 | Token revoked or out of scope. Same fix. |
| API 400 "must be powered off" | Mismatch between current state and operation requirements. Read the message; don't blindly retry. |
| Network error | Retry once; then stop and surface the message. |
Refusing ... missing flags: --i-understand-billing | The user has not yet acknowledged billing. Ask explicitly: "This will create a $X/mo resource — confirm I should proceed?" |
--confirm-id mismatch | You constructed the id wrong. Re-fetch the resource and try again. Do not retry with --confirm-id set to whatever the path says without verifying. |