@shahimat/cli 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,19 @@
1
+ # Журнал @shahimat/cli
2
+
3
+ Свежее сверху.
4
+
5
+ ## 0.1.1
6
+
7
+ - `@shahimat/ai-core` 0.1.1.
8
+
9
+ ## 0.1.0
10
+
11
+ - Опции `--all`, `--every`, `--until`, `--job`, `--timeout` для команд
12
+ `ai ops …`.
13
+
14
+ - Пакет — точка входа: бинарь `ai`, скрипты `check`, `refresh`,
15
+ `docs verify` (перенесены из `@shahimat/ai-core`), оболочка, дерево
16
+ команд из `ai-core`, автоматов `@shahimat/harness` (динамически, если
17
+ установлен) и расширений проекта из `.agents/commands`,
18
+ `.agents/scripts`, `.agents/harnesses`.
19
+ - `runtime` для автоматов и ролей: исполнитель скриптов и команда `ai`.
package/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # @shahimat/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ CLI `ai` — единая точка входа для проектов на контракте
4
+ [`agent-operational-contract-v3`](https://github.com/Shahimat/ai-prompts/blob/main/prompts/productivity/agent-operational-contract-v3.md):
5
+ команды ядра [`@shahimat/ai-core`](https://www.npmjs.com/package/@shahimat/ai-core),
6
+ скрипты, автоматы [`@shahimat/harness`](https://www.npmjs.com/package/@shahimat/harness)
7
+ (если пакет установлен) и расширения проекта.
8
+
9
+ Node ≥ 24. macOS, Linux, Windows.
10
+
11
+ ## Запуск
12
+
13
+ Пакеты — в `devDependencies` проекта с точными версиями, вызов — из корня:
14
+
15
+ ```bash
16
+ npx ai <команда | скрипт | автомат> … [--root <dir>]
17
+ npx ai --help # дерево всего, что видно в этом проекте
18
+ npx ai # оболочка с переходом по уровням
19
+ ```
20
+
21
+ ## Скрипты
22
+
23
+ - `ai check` — все проверки, включённые в `context/ai-core.yml`.
24
+ - `ai refresh` — `context index`, `adapters sync` (если включены), затем
25
+ шаги `check`.
26
+
27
+ Скрипт — только последовательность команд, без своей логики. Скрипт
28
+ `docs verify` — шаг автомата `docs-delta`, в справке не виден.
29
+
30
+ ## Расширения проекта
31
+
32
+ `ai` подхватывает их при каждом запуске:
33
+
34
+ - `.agents/commands/<name>.{ts,mjs,js}` — своя команда:
35
+ `export default { path: [...], effect: 'query' | 'mutation', options?, run }`;
36
+ - `.agents/scripts/<name>.yml` — свой скрипт: `path` и `steps`
37
+ (`command: [...]`);
38
+ - `.agents/harnesses/<name>/` — свой автомат: `harness.yaml` и
39
+ `handlers/*.ts`; нужен `@shahimat/harness`;
40
+ - `.agents/agents/<name>.md` — свои роли (их запускает `ai role run` и
41
+ автоматы).
42
+
43
+ Имя, занятое встроенной командой, скриптом или автоматом, — ошибка. Своя
44
+ команда — атомарная операция; скрипт — последовательность команд.
45
+
46
+ ## Runtime
47
+
48
+ При старте `ai` задаёт `runtime` из `@shahimat/ai-core`: исполнитель
49
+ скриптов по пути и команду самого себя. Через него автоматы зовут
50
+ скрипты (`docs verify`, `refresh`), роли поднимают MCP-канал
51
+ (`ai role channel`), `expect serve` рендерит отчёт. Без `ai` runtime не
52
+ задан — ошибка.
53
+
54
+ ## Лицензия
55
+
56
+ MIT
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './cli.js';
3
+ process.exitCode = await run(process.argv.slice(2));
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type CommandTree } from '@shahimat/ai-core';
2
+ export declare function projectOf(start: string): string | undefined;
3
+ export declare function cliMain(tree: CommandTree, argv: string[], now?: Date): Promise<number>;
4
+ export declare function selfCommand(): string[];
5
+ export declare function prepare(argv: readonly string[]): Promise<CommandTree>;
6
+ export declare const main: (argv: string[], now?: Date) => Promise<number>;
7
+ export declare function runCli(tree: CommandTree, argv: string[]): Promise<number>;
8
+ export declare function run(argv: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { runShell } from './shell.js';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { setRuntime } from '@shahimat/ai-core';
8
+ import { runScript } from './scripts/index.js';
9
+ import { loadTree } from './tree.js';
10
+ import { UsageError } from '@shahimat/ai-core';
11
+ import { errorCode, errorMessage } from '@shahimat/ai-core';
12
+ export function projectOf(start) {
13
+ const home = path.resolve(homedir());
14
+ for (let dir = path.resolve(start);; dir = path.dirname(dir)) {
15
+ const claude = path.join(dir, '.claude');
16
+ if (dir !== home && existsSync(claude) && statSync(claude).isDirectory())
17
+ return dir;
18
+ if (path.dirname(dir) === dir)
19
+ return undefined;
20
+ }
21
+ }
22
+ export async function cliMain(tree, argv, now = new Date()) {
23
+ const { childrenOf, findCommand, isNamespace, renderUsage } = tree;
24
+ const { values, positionals } = parseArgs({
25
+ args: argv,
26
+ allowPositionals: true,
27
+ options: {
28
+ root: { type: 'string' },
29
+ out: { type: 'string' },
30
+ file: { type: 'string' },
31
+ html: { type: 'string' },
32
+ port: { type: 'string' },
33
+ 'raw-base': { type: 'string' },
34
+ apply: { type: 'boolean' },
35
+ run: { type: 'boolean' },
36
+ home: { type: 'string' },
37
+ name: { type: 'string' },
38
+ request: { type: 'string' },
39
+ all: { type: 'boolean' },
40
+ every: { type: 'string' },
41
+ until: { type: 'string' },
42
+ job: { type: 'string' },
43
+ timeout: { type: 'string' },
44
+ help: { type: 'boolean', short: 'h' },
45
+ },
46
+ });
47
+ if (values.help === true) {
48
+ console.log(renderUsage(positionals));
49
+ return 0;
50
+ }
51
+ const match = findCommand(positionals);
52
+ const root = values.root !== undefined
53
+ ? path.resolve(values.root)
54
+ : match?.command.kind === 'harness'
55
+ ? (projectOf(process.cwd()) ?? process.cwd())
56
+ : path.resolve('.');
57
+ const args = { root, now };
58
+ if (values.out !== undefined)
59
+ args.out = values.out;
60
+ if (values.file !== undefined)
61
+ args.file = values.file;
62
+ if (values.html !== undefined)
63
+ args.html = values.html;
64
+ if (values.port !== undefined)
65
+ args.port = values.port;
66
+ if (values['raw-base'] !== undefined)
67
+ args.rawBase = values['raw-base'];
68
+ if (values.apply === true)
69
+ args.apply = true;
70
+ if (values.run === true)
71
+ args.run = true;
72
+ if (values.home !== undefined)
73
+ args.home = values.home;
74
+ if (values.name !== undefined)
75
+ args.name = values.name;
76
+ if (values.request !== undefined)
77
+ args.request = values.request;
78
+ if (values.all === true)
79
+ args.all = true;
80
+ if (values.every !== undefined)
81
+ args.every = values.every;
82
+ if (values.until !== undefined)
83
+ args.until = values.until;
84
+ if (values.job !== undefined)
85
+ args.job = values.job;
86
+ if (values.timeout !== undefined)
87
+ args.timeout = values.timeout;
88
+ if (match !== undefined)
89
+ return match.command.run({ ...args, params: match.params });
90
+ const last = positionals.at(-1);
91
+ const prefix = positionals.slice(0, -1);
92
+ if (last === 'help' && (prefix.length === 0 || isNamespace(prefix))) {
93
+ console.log(renderUsage(prefix));
94
+ return 0;
95
+ }
96
+ if (last === 'list' && (prefix.length === 0 || isNamespace(prefix))) {
97
+ console.log(childrenOf(prefix).join('\n'));
98
+ return 0;
99
+ }
100
+ if (positionals.length > 0 && isNamespace(positionals))
101
+ throw new UsageError(`${positionals.join(' ')} — уровень, а не команда: ${childrenOf(positionals).join(' | ')}`);
102
+ throw new UsageError(`неизвестная команда: ${positionals.join(' ') || '<пусто>'}`);
103
+ }
104
+ export function selfCommand() {
105
+ const bin = fileURLToPath(import.meta.resolve('#bin'));
106
+ return bin.endsWith('.ts')
107
+ ? [process.execPath, '--conditions=source', bin]
108
+ : [process.execPath, bin];
109
+ }
110
+ function rootOf(argv) {
111
+ const i = argv.indexOf('--root');
112
+ const given = i >= 0 ? argv[i + 1] : argv.find((a) => a.startsWith('--root='))?.slice(7);
113
+ return given !== undefined ? path.resolve(given) : (projectOf(process.cwd()) ?? process.cwd());
114
+ }
115
+ export async function prepare(argv) {
116
+ const { tree, scripts } = await loadTree(rootOf(argv));
117
+ setRuntime({
118
+ self: selfCommand(),
119
+ runScript: (scriptPath, args) => {
120
+ const def = scripts.find((s) => s.path.join(' ') === scriptPath.join(' '));
121
+ if (def === undefined)
122
+ throw new UsageError(`нет скрипта ${scriptPath.join(' ')}`);
123
+ return runScript(def, args);
124
+ },
125
+ });
126
+ return tree;
127
+ }
128
+ export const main = async (argv, now = new Date()) => cliMain(await prepare(argv), argv, now);
129
+ export async function runCli(tree, argv) {
130
+ const again = (words) => runCli(tree, words);
131
+ if (argv.length === 0 && process.stdin.isTTY && process.stdout.isTTY)
132
+ return runShell(again, tree);
133
+ try {
134
+ return await cliMain(tree, argv);
135
+ }
136
+ catch (error) {
137
+ console.error(`ERROR: ${errorMessage(error)}`);
138
+ if (error instanceof UsageError) {
139
+ console.error(tree.renderUsage());
140
+ return 2;
141
+ }
142
+ return (errorCode(error) ?? '').startsWith('ERR_PARSE_ARGS') ? 2 : 1;
143
+ }
144
+ }
145
+ export async function run(argv) {
146
+ let tree;
147
+ try {
148
+ tree = await prepare(argv);
149
+ }
150
+ catch (error) {
151
+ console.error(`ERROR: ${errorMessage(error)}`);
152
+ return 2;
153
+ }
154
+ return runCli(tree, argv);
155
+ }
@@ -0,0 +1,14 @@
1
+ import { type CommandNode, type ScriptDef } from '@shahimat/ai-core';
2
+ export declare const EXTENSION_DIRS: {
3
+ readonly commands: '.agents/commands';
4
+ readonly scripts: '.agents/scripts';
5
+ readonly harnesses: '.agents/harnesses';
6
+ };
7
+ export declare function projectCommands(root: string): Promise<CommandNode[]>;
8
+ export declare function projectScripts(root: string): Promise<ScriptDef[]>;
9
+ export interface HarnessApi {
10
+ nodes: readonly CommandNode[];
11
+ project: (dir: string) => CommandNode[];
12
+ }
13
+ export declare function loadHarness(): Promise<HarnessApi | undefined>;
14
+ export declare function projectHarnesses(root: string, harness: HarnessApi | undefined): Promise<CommandNode[]>;
@@ -0,0 +1,128 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync } from 'node:fs';
10
+ import { readdir, readFile } from 'node:fs/promises';
11
+ import path from 'node:path';
12
+ import { pathToFileURL } from 'node:url';
13
+ import { errorCode, errorMessage, isRecord, isStringArray, UsageError, } from '@shahimat/ai-core';
14
+ import { parse } from 'yaml';
15
+ export const EXTENSION_DIRS = {
16
+ commands: '.agents/commands',
17
+ scripts: '.agents/scripts',
18
+ harnesses: '.agents/harnesses',
19
+ };
20
+ async function listDir(dir) {
21
+ try {
22
+ return (await readdir(dir)).sort();
23
+ }
24
+ catch (error) {
25
+ if (errorCode(error) === 'ENOENT')
26
+ return [];
27
+ throw error;
28
+ }
29
+ }
30
+ function asCommand(value, where) {
31
+ if (!isRecord(value) ||
32
+ !isStringArray(value['path']) ||
33
+ value['path'].length === 0 ||
34
+ (value['effect'] !== 'query' && value['effect'] !== 'mutation') ||
35
+ typeof value['run'] !== 'function')
36
+ throw new UsageError(`${where}: export default { path: string[], effect: query | mutation, run }`);
37
+ const run = value['run'];
38
+ const node = {
39
+ path: value['path'],
40
+ kind: 'command',
41
+ effect: value['effect'],
42
+ run: async (args) => {
43
+ const code = await Reflect.apply(run, undefined, [args]);
44
+ return typeof code === 'number' ? code : 0;
45
+ },
46
+ };
47
+ if (typeof value['options'] === 'string')
48
+ node.options = value['options'];
49
+ return node;
50
+ }
51
+ export async function projectCommands(root) {
52
+ const dir = path.join(root, EXTENSION_DIRS.commands);
53
+ const out = [];
54
+ for (const name of (await listDir(dir)).filter((n) => /\.(ts|js|mjs)$/.test(n))) {
55
+ const where = `${EXTENSION_DIRS.commands}/${name}`;
56
+ let mod;
57
+ try {
58
+ mod = await import(__rewriteRelativeImportExtension(pathToFileURL(path.join(dir, name)).href));
59
+ }
60
+ catch (error) {
61
+ throw new UsageError(`${where}: ${errorMessage(error).split('\n')[0] ?? ''}`);
62
+ }
63
+ out.push(asCommand(isRecord(mod) ? mod['default'] : undefined, where));
64
+ }
65
+ return out;
66
+ }
67
+ function asStep(value, where) {
68
+ if (!isRecord(value) || !isStringArray(value['command']))
69
+ throw new UsageError(`${where}: шаг — { command: string[] }`);
70
+ return { command: value['command'] };
71
+ }
72
+ export async function projectScripts(root) {
73
+ const dir = path.join(root, EXTENSION_DIRS.scripts);
74
+ const out = [];
75
+ for (const name of (await listDir(dir)).filter((n) => n.endsWith('.yml'))) {
76
+ const where = `${EXTENSION_DIRS.scripts}/${name}`;
77
+ const data = parse(await readFile(path.join(dir, name), 'utf8'));
78
+ if (!isRecord(data) || !isStringArray(data['path']) || !Array.isArray(data['steps']))
79
+ throw new UsageError(`${where}: скрипт — { path: string[], steps: [{ command: string[] }] }`);
80
+ out.push({
81
+ path: data['path'],
82
+ steps: data['steps'].map((s, i) => asStep(s, `${where}: steps[${i}]`)),
83
+ });
84
+ }
85
+ return out;
86
+ }
87
+ export async function loadHarness() {
88
+ let mod;
89
+ try {
90
+ mod = await import('@shahimat/harness');
91
+ }
92
+ catch (error) {
93
+ if (errorCode(error) === 'ERR_MODULE_NOT_FOUND')
94
+ return undefined;
95
+ throw error;
96
+ }
97
+ if (!isRecord(mod))
98
+ return undefined;
99
+ const { HARNESS_NODES, harnessNodes, projectHarnessModule } = mod;
100
+ if (!Array.isArray(HARNESS_NODES) ||
101
+ typeof harnessNodes !== 'function' ||
102
+ typeof projectHarnessModule !== 'function')
103
+ throw new UsageError('@shahimat/harness: несовместимая версия — нет HARNESS_NODES');
104
+ const nodes = HARNESS_NODES.filter(isCommandNode);
105
+ return {
106
+ nodes,
107
+ project: (dir) => {
108
+ const module = Reflect.apply(projectHarnessModule, undefined, [dir]);
109
+ const list = Reflect.apply(harnessNodes, undefined, [module]);
110
+ return Array.isArray(list) ? list.filter(isCommandNode) : [];
111
+ },
112
+ };
113
+ }
114
+ function isCommandNode(v) {
115
+ return (isRecord(v) &&
116
+ isStringArray(v['path']) &&
117
+ (v['kind'] === 'command' || v['kind'] === 'harness') &&
118
+ typeof v['run'] === 'function');
119
+ }
120
+ export async function projectHarnesses(root, harness) {
121
+ const dir = path.join(root, EXTENSION_DIRS.harnesses);
122
+ const names = (await listDir(dir)).filter((n) => existsSync(path.join(dir, n, 'harness.yaml')));
123
+ if (names.length === 0)
124
+ return [];
125
+ if (harness === undefined)
126
+ throw new UsageError(`${EXTENSION_DIRS.harnesses}: свои автоматы есть, а пакета @shahimat/harness нет — подключи модуль harness`);
127
+ return names.flatMap((n) => harness.project(path.join(dir, n)));
128
+ }
@@ -0,0 +1,5 @@
1
+ export { cliMain, main, prepare, projectOf, run, runCli, selfCommand } from './cli.ts';
2
+ export { promptOf, runShell, shellStep, splitWords, type ShellStep } from './shell.ts';
3
+ export { BIN, BUILTIN, BUILTIN_TREE, loadTree, scriptCommand, SCRIPTS, type LoadedTree, } from './tree.ts';
4
+ export { CHECK, DOCS_VERIFY, REFRESH, runScript } from './scripts/index.ts';
5
+ export { EXTENSION_DIRS, loadHarness, projectCommands, projectHarnesses, projectScripts, type HarnessApi, } from './extensions.ts';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { cliMain, main, prepare, projectOf, run, runCli, selfCommand } from './cli.js';
2
+ export { promptOf, runShell, shellStep, splitWords } from './shell.js';
3
+ export { BIN, BUILTIN, BUILTIN_TREE, loadTree, scriptCommand, SCRIPTS, } from './tree.js';
4
+ export { CHECK, DOCS_VERIFY, REFRESH, runScript } from './scripts/index.js';
5
+ export { EXTENSION_DIRS, loadHarness, projectCommands, projectHarnesses, projectScripts, } from './extensions.js';
@@ -0,0 +1,2 @@
1
+ import type { ScriptDef } from '@shahimat/ai-core';
2
+ export declare const CHECK: ScriptDef;
@@ -0,0 +1,9 @@
1
+ export const CHECK = {
2
+ path: ['check'],
3
+ steps: [
4
+ { command: ['changelog', 'lint'], enabledBy: 'changelog' },
5
+ { command: ['context', 'check'], enabledBy: 'context-index' },
6
+ { command: ['adapters', 'check'], enabledBy: 'adapters' },
7
+ { command: ['secrets', 'scan'], enabledBy: 'secrets' },
8
+ ],
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { ScriptDef } from '@shahimat/ai-core';
2
+ export declare const DOCS_VERIFY: ScriptDef;
@@ -0,0 +1,10 @@
1
+ import { CHECK } from './check.js';
2
+ export const DOCS_VERIFY = {
3
+ path: ['docs', 'verify'],
4
+ steps: [
5
+ { command: ['context', 'index'], enabledBy: 'context-index' },
6
+ { command: ['adapters', 'sync'], enabledBy: 'adapters' },
7
+ { command: ['delta', 'check'] },
8
+ ...CHECK.steps,
9
+ ],
10
+ };
@@ -0,0 +1,4 @@
1
+ export { CHECK } from './check.ts';
2
+ export { DOCS_VERIFY } from './docs-verify.ts';
3
+ export { REFRESH } from './refresh.ts';
4
+ export { runScript } from './run.ts';
@@ -0,0 +1,4 @@
1
+ export { CHECK } from './check.js';
2
+ export { DOCS_VERIFY } from './docs-verify.js';
3
+ export { REFRESH } from './refresh.js';
4
+ export { runScript } from './run.js';
@@ -0,0 +1,2 @@
1
+ import type { ScriptDef } from '@shahimat/ai-core';
2
+ export declare const REFRESH: ScriptDef;
@@ -0,0 +1,9 @@
1
+ import { CHECK } from './check.js';
2
+ export const REFRESH = {
3
+ path: ['refresh'],
4
+ steps: [
5
+ { command: ['context', 'index'], enabledBy: 'context-index' },
6
+ { command: ['adapters', 'sync'], enabledBy: 'adapters' },
7
+ ...CHECK.steps,
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { CommandArgs } from '@shahimat/ai-core';
2
+ import type { ScriptDef } from '@shahimat/ai-core';
3
+ export declare function runScript(script: ScriptDef, args: CommandArgs): Promise<number>;
@@ -0,0 +1,29 @@
1
+ import { CONFIG_PATH, loadConfig } from '@shahimat/ai-core';
2
+ import { UsageError } from '@shahimat/ai-core';
3
+ import { COMMAND_NODES } from '@shahimat/ai-core';
4
+ const commandOf = (path) => COMMAND_NODES.find((c) => c.path.length === path.length && c.path.every((p, i) => p === path[i]));
5
+ export async function runScript(script, args) {
6
+ const name = script.path.join(' ');
7
+ const gated = script.steps.some((s) => s.enabledBy !== undefined);
8
+ const config = gated ? await loadConfig(args.root) : null;
9
+ if (gated && config === null)
10
+ throw new UsageError(`нет ${CONFIG_PATH}: harness в проекте не настроен`);
11
+ const steps = script.steps.filter((s) => s.enabledBy === undefined || config?.checks[s.enabledBy] !== undefined);
12
+ if (steps.length === 0) {
13
+ console.log(`${name}: в ${CONFIG_PATH} не включено ни одной проверки`);
14
+ return 0;
15
+ }
16
+ let code = 0;
17
+ for (const step of steps) {
18
+ const command = commandOf(step.command);
19
+ if (command === undefined)
20
+ throw new Error(`${name}: нет команды ${step.command.join(' ')}`);
21
+ code = Math.max(code, await command.run({ ...args, params: {} }));
22
+ }
23
+ const ran = steps.map((s) => s.command.join(' ')).join(', ');
24
+ if (code === 0)
25
+ console.log(`${name} (${ran}): ok`);
26
+ else
27
+ console.error(`${name} (${ran}): есть нарушения`);
28
+ return code;
29
+ }
@@ -0,0 +1,11 @@
1
+ import type { CommandTree } from '@shahimat/ai-core';
2
+ export declare function splitWords(line: string): string[];
3
+ export interface ShellStep {
4
+ cwd: string[];
5
+ run?: string[];
6
+ output?: string;
7
+ exit?: boolean;
8
+ }
9
+ export declare function shellStep(cwd: readonly string[], line: string, tree: CommandTree): ShellStep;
10
+ export declare const promptOf: (cwd: readonly string[], bin?: string) => string;
11
+ export declare function runShell(exec: (words: string[]) => Promise<number>, tree: CommandTree): Promise<number>;
package/dist/shell.js ADDED
@@ -0,0 +1,57 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ export function splitWords(line) {
3
+ const words = [];
4
+ for (const m of line.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g))
5
+ words.push(m[1] ?? m[2] ?? m[3] ?? '');
6
+ return words;
7
+ }
8
+ export function shellStep(cwd, line, tree) {
9
+ const { childrenOf, findCommand, isNamespace, renderUsage } = tree;
10
+ const words = splitWords(line);
11
+ const here = [...cwd];
12
+ if (words.length === 0)
13
+ return { cwd: here };
14
+ const [first = ''] = words;
15
+ if (words.length === 1 && (first === 'exit' || first === 'quit'))
16
+ return { cwd: here, exit: true };
17
+ if (words.length === 1 && first === '..')
18
+ return { cwd: here.slice(0, -1) };
19
+ if (words.length === 1 && first === 'help')
20
+ return { cwd: here, output: renderUsage(here) };
21
+ const full = [...here, ...words];
22
+ const own = isNamespace([...here, 'list']) || findCommand([...here, 'list']) !== undefined;
23
+ if (first === 'list' && words.length === 1 && !own) {
24
+ return { cwd: here, output: childrenOf(here).join('\n') };
25
+ }
26
+ if (isNamespace(full) && words.every((w) => w !== 'list' && w !== 'help'))
27
+ return { cwd: full };
28
+ return { cwd: here, run: full };
29
+ }
30
+ export const promptOf = (cwd, bin = 'ai') => `/${[bin, ...cwd].join('/')}> `;
31
+ export async function runShell(exec, tree) {
32
+ let cwd = [];
33
+ for (;;) {
34
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
35
+ let line;
36
+ try {
37
+ line = await rl.question(promptOf(cwd, tree.bin));
38
+ }
39
+ catch {
40
+ return 0;
41
+ }
42
+ finally {
43
+ rl.close();
44
+ }
45
+ const step = shellStep(cwd, line, tree);
46
+ cwd = step.cwd;
47
+ if (step.exit === true)
48
+ return 0;
49
+ if (step.output !== undefined)
50
+ console.log(step.output);
51
+ if (step.run !== undefined) {
52
+ const code = await exec(step.run);
53
+ if (code !== 0)
54
+ console.log(`код ${code}`);
55
+ }
56
+ }
57
+ }
package/dist/tree.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { type Command, type CommandTree, type ScriptDef } from '@shahimat/ai-core';
2
+ export declare const BIN = "ai";
3
+ export declare const SCRIPTS: readonly ScriptDef[];
4
+ export declare const scriptCommand: (def: ScriptDef) => Command;
5
+ export declare const BUILTIN: readonly Command[];
6
+ export declare const BUILTIN_TREE: CommandTree;
7
+ export interface LoadedTree {
8
+ tree: CommandTree;
9
+ scripts: readonly ScriptDef[];
10
+ }
11
+ export declare function loadTree(root: string): Promise<LoadedTree>;
package/dist/tree.js ADDED
@@ -0,0 +1,38 @@
1
+ import { COMMAND_NODES, commandTree, UsageError, } from '@shahimat/ai-core';
2
+ import { loadHarness, projectCommands, projectHarnesses, projectScripts } from './extensions.js';
3
+ import { CHECK, DOCS_VERIFY, REFRESH, runScript } from './scripts/index.js';
4
+ export const BIN = 'ai';
5
+ export const SCRIPTS = [CHECK, REFRESH, DOCS_VERIFY];
6
+ const CLI_SCRIPTS = [CHECK, REFRESH];
7
+ export const scriptCommand = (def) => ({
8
+ path: def.path,
9
+ kind: 'script',
10
+ steps: def.steps,
11
+ run: (args) => runScript(def, args),
12
+ });
13
+ export const BUILTIN = [...CLI_SCRIPTS.map(scriptCommand), ...COMMAND_NODES];
14
+ export const BUILTIN_TREE = commandTree(BIN, BUILTIN);
15
+ function noClashes(builtin, own) {
16
+ const key = (c) => c.path.join(' ');
17
+ const taken = new Set(builtin.map(key));
18
+ const seen = new Set();
19
+ for (const c of own) {
20
+ if (taken.has(key(c)))
21
+ throw new UsageError(`${key(c)}: имя занято встроенной командой — переименуй расширение`);
22
+ if (seen.has(key(c)))
23
+ throw new UsageError(`${key(c)}: два расширения с одним именем`);
24
+ seen.add(key(c));
25
+ }
26
+ }
27
+ export async function loadTree(root) {
28
+ const harness = await loadHarness();
29
+ const builtin = [...BUILTIN, ...(harness?.nodes ?? [])];
30
+ const scripts = await projectScripts(root);
31
+ const own = [
32
+ ...scripts.map(scriptCommand),
33
+ ...(await projectCommands(root)),
34
+ ...(await projectHarnesses(root, harness)),
35
+ ];
36
+ noClashes(builtin, own);
37
+ return { tree: commandTree(BIN, [...builtin, ...own]), scripts: [...SCRIPTS, ...scripts] };
38
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@shahimat/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "CLI ai для проектов на контрактах ai-prompts: команды ai-core, скрипты, автоматы harness и расширения проекта",
5
+ "license": "MIT",
6
+ "author": "Gevorg Nakhapetyan",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Shahimat/ai-prompts.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/Shahimat/ai-prompts/tree/main/packages/cli#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Shahimat/ai-prompts/issues"
15
+ },
16
+ "keywords": [
17
+ "ai-agents",
18
+ "cli",
19
+ "agents-md",
20
+ "claude-code"
21
+ ],
22
+ "type": "module",
23
+ "engines": {
24
+ "node": ">=24"
25
+ },
26
+ "bin": {
27
+ "ai": "dist/bin.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "source": "./src/index.ts",
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "imports": {
38
+ "#*": {
39
+ "source": "./src/*.ts",
40
+ "types": "./dist/*.d.ts",
41
+ "default": "./dist/*.js"
42
+ }
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "CHANGELOG.md"
48
+ ],
49
+ "scripts": {
50
+ "build": "node ../build.ts",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run",
53
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
54
+ },
55
+ "dependencies": {
56
+ "@shahimat/ai-core": "0.1.1",
57
+ "yaml": "^2.9.1"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^24.19.2",
61
+ "typescript": "^7.0.2",
62
+ "vitest": "^5.0.4"
63
+ },
64
+ "peerDependencies": {
65
+ "@shahimat/harness": ">=0.4.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "@shahimat/harness": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "publishConfig": {
73
+ "access": "public",
74
+ "registry": "https://registry.npmjs.org/"
75
+ }
76
+ }