@nytka/cli
v0.4.5
Published
The nytka CLI: lint a project against the format, report what is installed and what it is allowed to do, and keep @nytka packages current.
Downloads
256
Readme
@nytka/cli
Lint a nytka project, see what is installed and what it is allowed to do, and keep the
@nytka/* packages current.
npm i -g @nytka/cli
nytka add gtm
nytka check
nytka info
nytka upgradeNothing needs installing at all if you would rather not — npx @nytka/cli <command> works the
same way. Add it as a dev dependency (npm i -D @nytka/cli) to run it in CI.
One caveat on the global install, if you use nvm. Global packages belong to a single node
version. Install under v22, switch to v20, and nytka is not there — which reads as a broken
install and is not one. nvm reinstall-packages v22 brings them across, or use npx and stop
thinking about it.
add
Install a connector and document what it needs.
nytka add gtm # short name
nytka add plugin-ads # or the prefixed form — both resolve
nytka add @acme/connector # or any package at all
nytka add gsc --yes # skip the prompt
nytka add gsc --dev # as a devDependency resolving @nytka/plugin-ads ... 0.1.0
./package.json + "@nytka/plugin-ads": "^0.1.0"
.env.example (new file)
+ # free, from your manager account's API Center
+ # Shape: aBcDeFgHiJkLmNoPqRsT
+ GOOGLE_ADS_DEVELOPER_TOKEN=
...
Continue? (Y/n)It writes .env.example, never .env. The variable name is documentation and belongs
in the committed file. The value is yours, and this command has no business near it. So
add makes a capability available and you turn it on by filling the value in.
The consequence worth having: add cannot destroy a credential. There is no code path
from it to your .env. A test plants a secret there and asserts it is neither read nor
modified.
There is no list of plugins in this CLI. A package opts in by carrying nytka-plugin in
its package.json keywords and describing what it needs in a nytka block — so npm's own
search is the catalogue, and a third-party connector works identically to a first-party one.
A test greps the source and fails if a plugin name is ever hardcoded.
Other things it does, each with a test:
- Running it twice is a no-op, keyed on the variable name rather than the text written last time — so a reworded note or a manual edit doesn't produce a second copy.
- Byte-clean. Your
.env.exampleis appended to, never parsed and rewritten, so every comment, blank line and hand edit survives exactly. - In a project with no
project.yamlit installs and documents anyway, and scaffolds nothing. A connector is useful on its own; adopting the format is a separate, deliberate act. - A package declaring no
nytkablock still installs, and says it declared nothing — a consumer must not reject a package over a field it doesn't recognise. - A failed
npm installleaves everything as it was. The install runs first; the.env.examplewrite only happens after it succeeds.
Without a TTY it refuses rather than hanging on a prompt nobody can answer — pass --yes.
check
Lints the project against the nytka format: required files, frontmatter, expiry dates, decision-graph consistency, dangling links, dataset registry sanity.
npx @nytka/cli check # the current project
npx @nytka/cli check ./some/project
npx @nytka/cli check --json # for CI, or for an agent
npx @nytka/cli check --today=2026-07-29Exit codes: 0 clean or warnings only, 1 at least one error, 2 the directory does
not exist.
Warnings never fail the command. A lint that exits non-zero for advice cannot go in CI, and one that is not in CI does not run.
This is the same lint as tools/nytka-lint.mjs in the framework repo — same source, same
output, byte for byte, and there is a test that proves it. See
decision 0009 for why it exists in two places and
which one you are allowed to edit.
info
What is installed, and what it can actually do.
Node v22.17.1
Platform darwin (arm64)
Project my-site
Plugins
@nytka/plugin-gsc 0.3.2 ready
@nytka/plugin-gtm 0.1.1 partial
GOOGLE_SERVICE_ACCOUNT_KEY_WRITE unset, optional — enables the mutating commands
@nytka/plugin-sanity 0.3.0 → 0.3.1 ready
1 package(s) behind the published version.
npm update will NOT move these — see: npx @nytka/cli upgrade
Warnings
GSC_SERVICE_ACCOUNT_KEY is set but GOOGLE_SERVICE_ACCOUNT_KEY takes precedenceA credential value is never read, printed or returned — only whether it is set. There is
a test that asserts a token planted in .env appears nowhere in the output, the JSON, or
anything the function returns. Credential paths count as values too.
Three states:
| | |
|---|---|
| ready | every declared variable is present |
| partial | an optional variable is unset, so some commands are unavailable. Not a fault |
| blocked | a required variable is missing. This is the only state that exits 1 |
partial is deliberate. A nytka plugin does what its filled-in variables let it do — there
are no feature flags and no config keys, so an unset optional variable is a capability that
is off, not a broken install.
Add --latest to also ask npm what the current versions are. Without it, info makes no
network request at all.
upgrade
npx @nytka/cli upgrade # show what would change
npx @nytka/cli upgrade --write # apply it
npm installWhy this is not npm update. A caret range means something different below 1.0.0.
^1.2.0 allows anything under 2.0.0, but ^0.1.0 allows only >=0.1.0 <0.2.0 — while the
major is 0, npm treats the second number as the breaking one.
So a project pinned at ^0.1.0 never sees 0.3.0. npm update reports success, npm
outdated reports nothing wrong — because by the range's own terms nothing is wrong — and
the project sits two minor versions behind indefinitely. Every @nytka package is 0.x, so
every project has this.
upgrade asks the registry what is actually published, compares it against what your range
permits, and rewrites the range. It marks the entries npm update could never have reached.
It writes nothing without --write, and when it does write it changes only the version
strings — your indentation, blank lines and key order survive byte for byte.
Importing it
import { lintProject, projectInfo, planUpgrade } from '@nytka/cli'
const { counts, findings } = lintProject('./my-project', { today: '2026-07-29' })Every export is pure: reads the filesystem, writes nothing, prints nothing, never exits.
upgradeProject is the one exception and only writes when passed { write: true }.
Declaring a plugin
Any package can be a nytka plugin. There is no central list and nothing to register — npm's own search is the catalogue.
{
"keywords": ["nytka-plugin"],
"nytka": {
"env": [
{ "name": "ACME_API_KEY" },
{ "name": "ACME_WRITE_KEY", "required": false, "enables": "the mutating commands" },
{ "name": "ACME_REGION", "aliases": ["ACME_ZONE"] }
],
"retired": [
{ "name": "ACME_TOKEN", "replacedBy": "ACME_API_KEY" }
]
}
}Three relationships, which are not interchangeable:
aliases— an older name this plugin still reads. Satisfies the requirement, and warns, because a fallback nobody remembers is how a vocabulary drifts.alternatives— an equally valid other name. Satisfies it and warns about nothing.retired— a name the plugin no longer reads. Satisfies nothing, and is reported loudly: a project with a retired variable in.envlooks configured to its owner and is not.
A package with no nytka block is reported as installed and declaring nothing. That is not an
error — a consumer must not reject something over a field it does not recognise.
What this does not do
No telemetry. Nothing is sent anywhere. The only network requests are upgrade and
info --latest, both to the npm registry, both stated in the command. There is a test that
fails if any other command opens a socket.
Licence
MIT
