@nitra/cursor 1.13.68 → 1.13.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.72] - 2026-05-21
8
+
9
+ ### Changed
10
+
11
+ - **CLI скілів спрощено** — лише `npx @nitra/cursor skill list`, `skill <id> ["task"]` (промпт на stdout), `skill cursor <id> ["task"]`, `skill claude <id> ["task"]`. Прибрано `skill prompt`, bins `n-skills` / `n-claude`, підкоманду `claude` у `n-cursor`. Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs).
12
+
13
+ ## [1.13.71] - 2026-05-21
14
+
15
+ ### Added
16
+
17
+ - **Claude-first UX для скілів** — `npx @nitra/cursor claude taze "task"` і bin **`n-claude`** (замінено спрощеним `skill` у 1.13.72).
18
+
19
+ ## [1.13.70] - 2026-05-21
20
+
21
+ ### Added
22
+
23
+ - **CLI скілів без синку в проєкт** — `npx @nitra/cursor skill list|prompt|claude|cursor <id> "task"` і bin **`n-skills`** (`npx -p @nitra/cursor n-skills …`). Читає `skills/<id>/SKILL.md` з установленого пакета, збирає промпт із CWD (`package.json`, `tsconfig.json`, `.n-cursor.json`) і виводить на stdout або делегує в `claude -p` / `cursor-agent -p`. Id скілу — каталог у пакеті (`lint`, `fix`, …) або з префіксом `n-` (`n-lint` → `lint`). Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs), [n-skills.js](bin/n-skills.js), [n-cursor.js](bin/n-cursor.js).
24
+
25
+ ## [1.13.69] - 2026-05-21
26
+
27
+ ### Changed
28
+
29
+ - **CLI `check` без аргументів** більше не парсить `AGENTS.md` — список правил для прогону будується з **`*.mdc` у `.cursor/rules/`** (той самий дисковий індекс, що для `AGENTS.md` / `CLAUDE.md`): `n-bun.mdc` → `check bun`, ручні `conftest.mdc` тощо — за наявності programmatic check у пакеті. Явний `check bun ga` без змін. Зачеплено: [discover-check-rules-from-cursor.mjs](scripts/utils/discover-check-rules-from-cursor.mjs), [n-cursor.js](bin/n-cursor.js), [fix/SKILL.md](skills/fix/SKILL.md).
30
+
7
31
  ## [1.13.68] - 2026-05-21
8
32
 
9
33
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -5,9 +5,9 @@
5
5
  *
6
6
  * Використання:
7
7
  * `npx \@nitra/cursor` — завантажити cursor-правила
8
- * `npx \@nitra/cursor check` — перевірити правила, перелічені в AGENTS.md (якщо є check-*.mjs);
8
+ * `npx \@nitra/cursor check` — перевірити правила з `.cursor/rules/*.mdc`, для яких у пакеті є check/policy;
9
9
  * якщо в корені вже є `.n-cursor.json`, спочатку зчитується конфіг і за потреби дописується `$schema`
10
- * `npx \@nitra/cursor check bun` — перевірити лише вказані правила (ігнорує AGENTS.md)
10
+ * `npx \@nitra/cursor check bun` — перевірити лише вказані правила (ігнорує `.cursor/rules/`)
11
11
  * `npx \@nitra/cursor rename-yaml-extensions` — k8s `*.yml` → `*.yaml`, `.github` `*.yaml` → `*.yml` (опції: `--dry-run`, `--root=…`; див. bin/rename-yaml-extensions.mjs)
12
12
  * `npx \@nitra/cursor stop-hook` — точка входу Stop hook Claude Code (читає stdin, виходить 0 при `stop_hook_active`,
13
13
  * інакше викликає `check`); прописується автоматично в `.claude/settings.json`
@@ -19,6 +19,10 @@
19
19
  * `npx \@nitra/cursor lint-docker` — канонічний lint-docker (docker.mdc): `hadolint` по `Dockerfile`/`*.Dockerfile`
20
20
  * `npx \@nitra/cursor lint-text` — канонічний lint-text (text.mdc): `cspell` → `shellcheck` (з auto-fix) →
21
21
  * `markdownlint-cli2 --fix` → `v8r` (json/json5/yaml/yml/toml)
22
+ * `npx \@nitra/cursor skill list` — скіли пакета без синку в проєкт
23
+ * `npx \@nitra/cursor skill taze` — промпт на stdout
24
+ * `npx \@nitra/cursor skill cursor taze ["task"]` — Cursor CLI (`cursor-agent -p`)
25
+ * `npx \@nitra/cursor skill claude taze ["task"]` — Claude Code CLI (`claude -p`)
22
26
  *
23
27
  * Agent інтеграція: під час синку, окрім `.cursor/rules` і `.claude/commands` (з skills), CLI ще раз
24
28
  * синхронізує `.claude/settings.json` (hooks + permissions; merge — користувацькі поля зберігаються),
@@ -78,6 +82,7 @@ import {
78
82
  } from '../scripts/auto-rules.mjs'
79
83
  import { detectAutoSkills } from '../scripts/auto-skills.mjs'
80
84
  import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
85
+ import { discoverCheckRulesFromCursorRules } from '../scripts/utils/discover-check-rules-from-cursor.mjs'
81
86
  import { discoverCheckableRules } from '../scripts/utils/discover-checkable-rules.mjs'
82
87
  import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
83
88
  import { runLintDocker } from '../rules/docker/lint/lint.mjs'
@@ -89,6 +94,7 @@ import { runRule } from '../scripts/utils/run-rule.mjs'
89
94
  import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
90
95
  import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
91
96
  import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
97
+ import { runSkillsCli } from '../scripts/skills-cli.mjs'
92
98
  import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
93
99
 
94
100
  const PACKAGE_NAME = '@nitra/cursor'
@@ -978,57 +984,10 @@ function discoverCheckScripts() {
978
984
  }
979
985
 
980
986
  /**
981
- * Перетворює базове ім'я .mdc у .cursor/rules на id скрипта check-<id>.mjs
982
- * @param {string} mdcBasename наприклад n-bun.mdc або script.mdc
983
- * @returns {string} id без суфікса .mdc та без префікса n- для керованих правил
984
- */
985
- function mdcBasenameToCheckId(mdcBasename) {
986
- const base = basename(mdcBasename)
987
- const withoutExt = base.endsWith('.mdc') ? base.slice(0, -'.mdc'.length) : base
988
- return withoutExt.startsWith(RULE_PREFIX) ? withoutExt.slice(RULE_PREFIX.length) : withoutExt
989
- }
990
-
991
- /**
992
- * Зчитує AGENTS.md і повертає унікальні id перевірок у порядку згадування, лише ті що є в available
993
- * @param {string[]} available імена з discoverCheckScripts()
994
- * @returns {Promise<string[]>} унікальні id перевірок у порядку згадування в AGENTS.md
995
- */
996
- async function discoverCheckRulesFromAgentsMd(available) {
997
- const agentsPath = join(cwd(), AGENTS_FILE)
998
- if (!existsSync(agentsPath)) {
999
- throw new Error(
1000
- `Немає ${AGENTS_FILE}. Запустіть \`npx ${PACKAGE_NAME}\` або вкажіть правила: \`npx ${PACKAGE_NAME} check bun ga\``
1001
- )
1002
- }
1003
- const text = await readFile(agentsPath, 'utf8')
1004
- const re = /\.cursor\/rules\/([^\s#`>]+\.mdc)/g
1005
- const raw = []
1006
- let m
1007
- while ((m = re.exec(text)) !== null) {
1008
- raw.push(m[1])
1009
- }
1010
- if (raw.length === 0) {
1011
- throw new Error(
1012
- `У ${AGENTS_FILE} немає посилань \`.cursor/rules/….mdc\`. Оновіть файл (\`npx ${PACKAGE_NAME}\`) або передайте правила явно.`
1013
- )
1014
- }
1015
- const seen = new Set()
1016
- const ordered = []
1017
- for (const pathFragment of raw) {
1018
- const id = mdcBasenameToCheckId(pathFragment)
1019
- if (available.includes(id) && !seen.has(id)) {
1020
- seen.add(id)
1021
- ordered.push(id)
1022
- }
1023
- }
1024
- return ordered
1025
- }
1026
-
1027
- /**
1028
- * Запускає перевірки: без аргументів — за списком у AGENTS.md; з аргументами — лише вказані правила.
987
+ * Запускає перевірки: без аргументів — за `*.mdc` у `.cursor/rules/`; з аргументами лише вказані правила.
1029
988
  * Делегує оркестрацію concern-ів (JS-checks + policy через `runConftestBatch`) у `runRule`;
1030
989
  * сам `runChecks` відповідає лише за фільтр id, агрегацію exit-кодів і shared walk-cache на прогон.
1031
- * @param {string[]} requestedRules імена правил; порожній масив — брати з AGENTS.md
990
+ * @param {string[]} requestedRules імена правил; порожній масив — брати з `.cursor/rules/*.mdc`
1032
991
  * @returns {Promise<void>}
1033
992
  */
1034
993
  async function runChecks(requestedRules) {
@@ -1054,10 +1013,16 @@ async function runChecks(requestedRules) {
1054
1013
  if (requestedRules.length > 0) {
1055
1014
  idsToCheck = requestedRules
1056
1015
  } else {
1057
- idsToCheck = await discoverCheckRulesFromAgentsMd(available)
1016
+ const mdcFiles = await listProjectRulesMdcFiles()
1017
+ if (mdcFiles.length === 0) {
1018
+ throw new Error(
1019
+ `Немає файлів *.mdc у ${RULES_DIR}/. Запустіть \`npx ${PACKAGE_NAME}\` або вкажіть правила: \`npx ${PACKAGE_NAME} check bun ga\``
1020
+ )
1021
+ }
1022
+ idsToCheck = discoverCheckRulesFromCursorRules(available, mdcFiles)
1058
1023
  if (idsToCheck.length === 0) {
1059
1024
  console.log(
1060
- `\n🔍 ${PACKAGE_NAME} check — у ${AGENTS_FILE} немає правил з programmatic перевіркою ` +
1025
+ `\n🔍 ${PACKAGE_NAME} check — у ${RULES_DIR}/ немає правил з programmatic перевіркою ` +
1061
1026
  `(відповідного check-*.mjs або policy/<name>/target.json у пакеті). Нічого не запущено.\n`
1062
1027
  )
1063
1028
  return
@@ -1344,6 +1309,11 @@ try {
1344
1309
 
1345
1310
  break
1346
1311
  }
1312
+ case 'skill': {
1313
+ process.exitCode = await runSkillsCli(args)
1314
+
1315
+ break
1316
+ }
1347
1317
  case undefined:
1348
1318
  case '': {
1349
1319
  await runSync()
@@ -1353,7 +1323,7 @@ try {
1353
1323
  default: {
1354
1324
  console.error(`❌ Невідома команда: ${command}`)
1355
1325
  console.error(
1356
- ` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text`
1326
+ ` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, skill`
1357
1327
  )
1358
1328
  process.exitCode = 1
1359
1329
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.68",
3
+ "version": "1.13.72",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,219 @@
1
+ /**
2
+ * CLI запуску скілів пакета `@nitra/cursor` без синку правил у проєкт.
3
+ *
4
+ * Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
5
+ * Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
6
+ * `.n-cursor.json`) — далі stdout або делегування в `cursor-agent` / `claude`.
7
+ *
8
+ * Підтримувані формати:
9
+ * `npx @nitra/cursor skill list`
10
+ * `npx @nitra/cursor skill taze`
11
+ * `npx @nitra/cursor skill cursor taze`
12
+ * `npx @nitra/cursor skill cursor taze "онови залежності"`
13
+ * `npx @nitra/cursor skill claude taze` — те саме через Claude Code CLI
14
+ */
15
+
16
+ import { spawnSync } from 'node:child_process'
17
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
18
+ import { dirname, join } from 'node:path'
19
+ import { cwd } from 'node:process'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const RUNNERS = new Set(['cursor', 'claude'])
23
+
24
+ const USAGE_LINES = [
25
+ 'Usage:',
26
+ ' npx @nitra/cursor skill list',
27
+ ' npx @nitra/cursor skill <skill-id> ["task"]',
28
+ ' npx @nitra/cursor skill cursor <skill-id> ["task"]',
29
+ ' npx @nitra/cursor skill claude <skill-id> ["task"]',
30
+ '',
31
+ 'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
32
+ ]
33
+
34
+ /**
35
+ * @param {string} name
36
+ * @returns {boolean}
37
+ */
38
+ function isBinaryInPath(name) {
39
+ const probe = spawnSync('command', ['-v', name], { shell: true, encoding: 'utf8' })
40
+ return probe.status === 0
41
+ }
42
+
43
+ /**
44
+ * @param {string} name ім'я скілу з CLI або каталогу `.cursor/skills/n-*`
45
+ * @returns {string} id каталогу в `npm/skills/<id>/`
46
+ */
47
+ export function normalizeSkillId(name) {
48
+ if (!name || typeof name !== 'string') {
49
+ return ''
50
+ }
51
+ return name.startsWith('n-') ? name.slice(2) : name
52
+ }
53
+
54
+ /**
55
+ * @param {string} skillsRoot абсолютний шлях до `skills/` пакета
56
+ * @returns {string[]}
57
+ */
58
+ export function listSkillIds(skillsRoot) {
59
+ if (!existsSync(skillsRoot)) {
60
+ return []
61
+ }
62
+
63
+ return readdirSync(skillsRoot, { withFileTypes: true })
64
+ .filter(entry => entry.isDirectory())
65
+ .map(entry => entry.name)
66
+ .filter(name => existsSync(join(skillsRoot, name, 'SKILL.md')))
67
+ .sort((a, b) => a.localeCompare(b))
68
+ }
69
+
70
+ /**
71
+ * @param {string} skillsRoot
72
+ * @param {string} skillId нормалізований id (без префікса n-)
73
+ * @returns {string}
74
+ */
75
+ function getSkillMdPath(skillsRoot, skillId) {
76
+ return join(skillsRoot, skillId, 'SKILL.md')
77
+ }
78
+
79
+ /**
80
+ * @param {string} path
81
+ * @returns {string | null}
82
+ */
83
+ function readIfExists(path) {
84
+ return existsSync(path) ? readFileSync(path, 'utf8') : null
85
+ }
86
+
87
+ /**
88
+ * @param {string} skillsRoot
89
+ * @param {string} rawSkillName
90
+ * @param {string} task
91
+ * @param {string} [projectDir]
92
+ * @returns {string}
93
+ */
94
+ export function buildSkillPrompt(skillsRoot, rawSkillName, task, projectDir = cwd()) {
95
+ const skillId = normalizeSkillId(rawSkillName)
96
+ const skillPath = getSkillMdPath(skillsRoot, skillId)
97
+
98
+ if (!skillId || !existsSync(skillPath)) {
99
+ const available = listSkillIds(skillsRoot).join(', ')
100
+ throw new Error(`Unknown skill "${rawSkillName}". Available skills: ${available || '(none)'}`)
101
+ }
102
+
103
+ const skill = readFileSync(skillPath, 'utf8')
104
+ const packageJson = readIfExists(join(projectDir, 'package.json'))
105
+ const tsconfig = readIfExists(join(projectDir, 'tsconfig.json'))
106
+ const nCursorJson = readIfExists(join(projectDir, '.n-cursor.json'))
107
+
108
+ return [
109
+ '# Task',
110
+ task || 'Execute the skill instructions for this project.',
111
+ '',
112
+ '# Skill',
113
+ skill,
114
+ '',
115
+ '# Current project',
116
+ `Directory: ${projectDir}`,
117
+ '',
118
+ packageJson ? `## package.json\n\n\`\`\`json\n${packageJson}\n\`\`\`` : '',
119
+ tsconfig ? `## tsconfig.json\n\n\`\`\`json\n${tsconfig}\n\`\`\`` : '',
120
+ nCursorJson ? `## .n-cursor.json\n\n\`\`\`json\n${nCursorJson}\n\`\`\`` : ''
121
+ ]
122
+ .filter(Boolean)
123
+ .join('\n\n')
124
+ }
125
+
126
+ /**
127
+ * @param {'claude' | 'cursor'} kind
128
+ * @param {string} prompt
129
+ * @param {string} projectDir
130
+ * @returns {number}
131
+ */
132
+ function runLlmCli(kind, prompt, projectDir) {
133
+ if (kind === 'claude') {
134
+ if (!isBinaryInPath('claude')) {
135
+ throw new Error('`claude` not found in PATH. Install Claude Code CLI or use `skill cursor`.')
136
+ }
137
+
138
+ const result = spawnSync('claude', ['-p'], {
139
+ input: prompt,
140
+ cwd: projectDir,
141
+ stdio: ['pipe', 'inherit', 'inherit'],
142
+ encoding: 'utf8'
143
+ })
144
+ return result.status ?? 1
145
+ }
146
+
147
+ if (!isBinaryInPath('cursor-agent')) {
148
+ throw new Error('`cursor-agent` not found in PATH. Install Cursor CLI or use `skill claude`.')
149
+ }
150
+
151
+ const result = spawnSync('cursor-agent', ['-p'], {
152
+ input: prompt,
153
+ cwd: projectDir,
154
+ stdio: ['pipe', 'inherit', 'inherit'],
155
+ encoding: 'utf8'
156
+ })
157
+ return result.status ?? 1
158
+ }
159
+
160
+ /**
161
+ * Корінь пакета `@nitra/cursor` (каталог з `skills/`, `rules/`, …).
162
+ * @param {string} [fromModuleUrl] для тестів — `import.meta.url` викликача
163
+ * @returns {string}
164
+ */
165
+ export function resolveBundledPackageRoot(fromModuleUrl = import.meta.url) {
166
+ return join(dirname(fileURLToPath(fromModuleUrl)), '..')
167
+ }
168
+
169
+ /**
170
+ * @param {string[]} argv аргументи після `skill` у `n-cursor`
171
+ * @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options]
172
+ * @returns {Promise<number>} exit code
173
+ */
174
+ export async function runSkillsCli(argv, options = {}) {
175
+ const log = options.log ?? (line => console.log(line))
176
+ const logError = options.logError ?? (line => console.error(line))
177
+ const packageRoot = options.packageRoot ?? resolveBundledPackageRoot()
178
+ const skillsRoot = join(packageRoot, 'skills')
179
+ const projectDir = options.projectDir ?? cwd()
180
+
181
+ const [first, second, ...rest] = argv
182
+ const skillIds = listSkillIds(skillsRoot)
183
+
184
+ try {
185
+ if (!first) {
186
+ logError(USAGE_LINES.join('\n'))
187
+ return 1
188
+ }
189
+
190
+ if (first === 'list') {
191
+ log('Available skills:')
192
+ for (const id of skillIds) {
193
+ log(`- ${id}`)
194
+ }
195
+ return 0
196
+ }
197
+
198
+ if (RUNNERS.has(first)) {
199
+ if (!second) {
200
+ throw new Error(`Skill name is required after "${first}"`)
201
+ }
202
+ const task = rest.join(' ')
203
+ const prompt = buildSkillPrompt(skillsRoot, second, task, projectDir)
204
+ return runLlmCli(/** @type {'claude' | 'cursor'} */ (first), prompt, projectDir)
205
+ }
206
+
207
+ if (skillIds.includes(normalizeSkillId(first))) {
208
+ const task = [second, ...rest].filter(Boolean).join(' ')
209
+ log(buildSkillPrompt(skillsRoot, first, task, projectDir))
210
+ return 0
211
+ }
212
+
213
+ logError(USAGE_LINES.join('\n'))
214
+ return 1
215
+ } catch (error) {
216
+ logError(error instanceof Error ? error.message : String(error))
217
+ return 1
218
+ }
219
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Визначає список id правил для `npx @nitra/cursor check` без аргументів:
3
+ * зчитує базові імена `*.mdc` у `.cursor/rules/` і залишає лише ті id,
4
+ * для яких у пакеті є programmatic перевірка (JS-концерн або policy з target.json).
5
+ */
6
+
7
+ /** Префікс керованих правил пакета у `.cursor/rules/`. */
8
+ export const MANAGED_RULE_FILE_PREFIX = 'n-'
9
+
10
+ /**
11
+ * Перетворює базове ім'я `.mdc` у id правила для `check <id>`.
12
+ * @param {string} mdcBasename наприклад `n-bun.mdc` або `my-rule.mdc`
13
+ * @returns {string} id без `.mdc`; для `n-*` — без префікса `n-`
14
+ */
15
+ export function mdcBasenameToCheckId(mdcBasename) {
16
+ const base = mdcBasename.includes('/') ? mdcBasename.slice(mdcBasename.lastIndexOf('/') + 1) : mdcBasename
17
+ const withoutExt = base.endsWith('.mdc') ? base.slice(0, -'.mdc'.length) : base
18
+ return withoutExt.startsWith(MANAGED_RULE_FILE_PREFIX)
19
+ ? withoutExt.slice(MANAGED_RULE_FILE_PREFIX.length)
20
+ : withoutExt
21
+ }
22
+
23
+ /**
24
+ * Будує впорядкований список id перевірок за файлами правил на диску.
25
+ * @param {string[]} available id з `discoverCheckableRules` (алфавітний порядок пакета)
26
+ * @param {string[]} mdcBasenames відсортовані імена `*.mdc` з `.cursor/rules/`
27
+ * @returns {string[]} унікальні id у порядку `mdcBasenames`, лише присутні в `available`
28
+ */
29
+ export function discoverCheckRulesFromCursorRules(available, mdcBasenames) {
30
+ const seen = new Set()
31
+ const ordered = []
32
+ for (const basename of mdcBasenames) {
33
+ const id = mdcBasenameToCheckId(basename)
34
+ if (available.includes(id) && !seen.has(id)) {
35
+ seen.add(id)
36
+ ordered.push(id)
37
+ }
38
+ }
39
+ return ordered
40
+ }
@@ -12,7 +12,7 @@ description: >-
12
12
 
13
13
  ## Workflow
14
14
 
15
- 1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `AGENTS.md`, для яких є `check-*.mjs`; повний набір — явні аргументи: `npx @nitra/cursor check bun ga …`):
15
+ 1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor check bun ga …`):
16
16
 
17
17
  ```bash
18
18
  npx @nitra/cursor check