@getxflow/cli 0.1.4 → 0.1.7
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/README.md +6 -0
- package/dist/api.js +5 -2
- package/dist/args.js +3 -1
- package/dist/bin.js +26 -3
- package/dist/commands/auth.js +12 -9
- package/dist/commands/deploy.js +65 -50
- package/dist/commands/env.js +143 -0
- package/dist/commands/functions.js +1 -2
- package/dist/commands/mcp.js +99 -0
- package/dist/commands/projects.js +13 -23
- package/dist/commands/skills.js +97 -19
- package/dist/commands/sources.js +4 -2
- package/dist/help.js +74 -25
- package/dist/template.js +1 -1
- package/dist/tree.js +0 -26
- package/dist/types.js +8 -1
- package/dist/version.js +1 -1
- package/package.json +22 -22
- package/skills/xflow/SKILL.md +92 -10
- package/dist/open-url.js +0 -24
|
@@ -5,15 +5,14 @@ exports.init = init;
|
|
|
5
5
|
exports.link = link;
|
|
6
6
|
exports.list = list;
|
|
7
7
|
exports.get = get;
|
|
8
|
-
exports.open = open;
|
|
9
8
|
const node_fs_1 = require("node:fs");
|
|
10
9
|
const node_path_1 = require("node:path");
|
|
11
10
|
const api_1 = require("../api");
|
|
12
11
|
const args_1 = require("../args");
|
|
13
12
|
const config_1 = require("../config");
|
|
14
13
|
const errors_1 = require("../errors");
|
|
15
|
-
const open_url_1 = require("../open-url");
|
|
16
14
|
const session_1 = require("../session");
|
|
15
|
+
const skills_1 = require("./skills");
|
|
17
16
|
const template_1 = require("../template");
|
|
18
17
|
const zip_1 = require("../zip");
|
|
19
18
|
const ui_1 = require("../ui");
|
|
@@ -99,24 +98,24 @@ async function init(args) {
|
|
|
99
98
|
(0, ui_1.note)((0, ui_1.dim)(' Разберитесь с папкой и подберите его: xflow link ' + project.id));
|
|
100
99
|
throw e;
|
|
101
100
|
}
|
|
101
|
+
(0, skills_1.installSkillQuietly)(target);
|
|
102
102
|
(0, ui_1.ok)(`Проект «${project.name}» создан: ${files.length} файлов шаблона`);
|
|
103
103
|
(0, ui_1.out)('');
|
|
104
104
|
(0, ui_1.out)(` ${(0, ui_1.bold)('Дальше:')}`);
|
|
105
105
|
(0, ui_1.out)(` cd ${(0, node_path_1.basename)(target)}`);
|
|
106
106
|
(0, ui_1.out)(' npm install');
|
|
107
107
|
(0, ui_1.out)(' npm run dev # разработка');
|
|
108
|
-
(0, ui_1.out)(' xflow skills # рассказать вашему ИИ-агенту про платформу');
|
|
109
108
|
(0, ui_1.out)(' xflow deploy # отправить код, собрать и выложить');
|
|
110
109
|
}
|
|
111
110
|
/** Связать текущую папку с уже существующим проектом. */
|
|
112
111
|
async function link(args) {
|
|
113
|
-
const projectId = args.words[0];
|
|
114
|
-
if (!projectId) {
|
|
115
|
-
throw new errors_1.CliError('Нужен идентификатор проекта', 'Список проектов: xflow projects list');
|
|
116
|
-
}
|
|
117
112
|
const root = (0, config_1.findProjectRoot)() ?? process.cwd();
|
|
118
113
|
const existing = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, config_1.CONFIG_FILE)) ? (0, config_1.readConfig)(root) : undefined;
|
|
119
114
|
const client = (0, session_1.connect)(existing);
|
|
115
|
+
const projectId = args.words[0];
|
|
116
|
+
if (!projectId) {
|
|
117
|
+
throw new errors_1.CliError('Нужен идентификатор проекта', 'Список: xflow projects list');
|
|
118
|
+
}
|
|
120
119
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
121
120
|
const dir = existing ? root : process.cwd();
|
|
122
121
|
(0, config_1.writeConfig)(dir, {
|
|
@@ -131,6 +130,7 @@ async function link(args) {
|
|
|
131
130
|
write(dir, '.env', (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions));
|
|
132
131
|
(0, ui_1.note)((0, ui_1.dim)(' Создан .env с токеном проекта и адресами функций'));
|
|
133
132
|
}
|
|
133
|
+
(0, skills_1.installSkillQuietly)(dir);
|
|
134
134
|
(0, ui_1.ok)(`Папка связана с проектом «${card.name}»`);
|
|
135
135
|
(0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
|
|
136
136
|
}
|
|
@@ -145,7 +145,7 @@ async function list() {
|
|
|
145
145
|
(0, ui_1.table)(projects.map((p) => [
|
|
146
146
|
p.id,
|
|
147
147
|
p.name,
|
|
148
|
-
p.
|
|
148
|
+
p.live_deploy_id ? 'опубликован' : p.dev_deploy_id ? 'только dev' : 'без сборки',
|
|
149
149
|
(0, ui_1.formatAge)(p.updated_at),
|
|
150
150
|
]));
|
|
151
151
|
}
|
|
@@ -162,9 +162,11 @@ async function get(args) {
|
|
|
162
162
|
if (card.description)
|
|
163
163
|
(0, ui_1.out)(card.description);
|
|
164
164
|
(0, ui_1.out)('');
|
|
165
|
-
(0, ui_1.out)(
|
|
166
|
-
(0, ui_1.out)(
|
|
167
|
-
(0, ui_1.out)(
|
|
165
|
+
(0, ui_1.out)(`Собранная версия: ${card.dev_deploy_id ?? '— (сборки нет)'}`);
|
|
166
|
+
(0, ui_1.out)(`Видят посетители: ${card.live_deploy_id ?? '— (не публиковался)'}`);
|
|
167
|
+
(0, ui_1.out)(`База: ${card.database ? `${card.database.name} (${card.database.schema})` : '—'}`);
|
|
168
|
+
(0, ui_1.out)('');
|
|
169
|
+
(0, ui_1.out)(card.project_url);
|
|
168
170
|
if (card.functions.length > 0) {
|
|
169
171
|
(0, ui_1.out)('');
|
|
170
172
|
(0, ui_1.out)('Функции:');
|
|
@@ -176,15 +178,3 @@ async function get(args) {
|
|
|
176
178
|
(0, ui_1.table)(card.schedules.map((s) => [` ${s.function_name}`, s.cron_expression, s.status]));
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
|
-
async function open(args) {
|
|
180
|
-
const { config } = (0, config_1.requireProject)();
|
|
181
|
-
const client = (0, session_1.connect)(config);
|
|
182
|
-
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}`);
|
|
183
|
-
const live = (0, args_1.flagBool)(args, 'live');
|
|
184
|
-
const url = live ? card.live_url : card.dev_url;
|
|
185
|
-
if (!url) {
|
|
186
|
-
throw new errors_1.CliError(live ? 'Проект не публиковался' : 'У проекта нет сборки', live ? 'Опубликовать текущую версию: xflow publish' : 'Собрать и выложить: xflow deploy');
|
|
187
|
-
}
|
|
188
|
-
(0, ui_1.note)((0, ui_1.dim)(url));
|
|
189
|
-
(0, open_url_1.openUrl)(url);
|
|
190
|
-
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installSkillQuietly = installSkillQuietly;
|
|
3
4
|
exports.skills = skills;
|
|
4
5
|
const node_fs_1 = require("node:fs");
|
|
5
6
|
const node_os_1 = require("node:os");
|
|
@@ -9,13 +10,38 @@ const errors_1 = require("../errors");
|
|
|
9
10
|
const ui_1 = require("../ui");
|
|
10
11
|
/**
|
|
11
12
|
* Формат SKILL.md общий для инструментов (agentskills.io), а папку каждый ищет
|
|
12
|
-
* свою: Claude Code в .claude/skills, Codex и OpenClaw в .agents/skills.
|
|
13
|
-
*
|
|
13
|
+
* свою: Claude Code в .claude/skills, Codex и OpenClaw в .agents/skills. Cursor к
|
|
14
|
+
* общему формату не пришёл и читает .cursor/rules/*.mdc, поэтому туда едет тот же
|
|
15
|
+
* текст под его заголовком. Спрашивать, чей агент, мы не хотим: раскладываем всем.
|
|
14
16
|
*/
|
|
15
|
-
const
|
|
17
|
+
const SKILL_DIRS = [
|
|
16
18
|
['.claude', 'skills'],
|
|
17
19
|
['.agents', 'skills'],
|
|
18
20
|
];
|
|
21
|
+
/**
|
|
22
|
+
* В домашней папке набор другой. Общий .agents/skills там читает OpenClaw, а
|
|
23
|
+
* Codex смотрит его только внутри репозитория: свой личный он держит отдельно,
|
|
24
|
+
* в .codex/skills. Без этой папки `--global` тихо не работает для Codex.
|
|
25
|
+
*/
|
|
26
|
+
const GLOBAL_SKILL_DIRS = [...SKILL_DIRS, ['.codex', 'skills']];
|
|
27
|
+
const CURSOR_RULE = ['.cursor', 'rules', 'xflow.mdc'];
|
|
28
|
+
/**
|
|
29
|
+
* Указатель в AGENTS.md.
|
|
30
|
+
*
|
|
31
|
+
* Скилл подхватывается лениво: только когда описание совпало с задачей. Просьба
|
|
32
|
+
* «добавь таблицу клиентов» с ним не совпадает, и агент не узнаёт, что проект
|
|
33
|
+
* вообще живёт на платформе. AGENTS.md читается всегда и без условий, поэтому
|
|
34
|
+
* пара строк здесь решает ту часть, которую ленивый скилл закрыть не может.
|
|
35
|
+
*/
|
|
36
|
+
const POINTER_MARKER = '<!-- xflow-skill -->';
|
|
37
|
+
const POINTER = `${POINTER_MARKER}
|
|
38
|
+
## XFlow
|
|
39
|
+
|
|
40
|
+
This project is hosted on the XFlow platform and ships through the \`xflow\` CLI, not
|
|
41
|
+
through a git push. Read \`.agents/skills/xflow/SKILL.md\` before deploying, publishing,
|
|
42
|
+
rolling back, touching the database or migrations, cloud functions, schedules,
|
|
43
|
+
environment variables or production logs. Command list: \`xflow help\`.
|
|
44
|
+
`;
|
|
19
45
|
/** Скилл едет в пакете рядом с dist: у CLI и у инструкций для агента одна версия. */
|
|
20
46
|
function skillSource() {
|
|
21
47
|
const path = (0, node_path_1.join)(__dirname, '..', '..', 'skills', 'xflow', 'SKILL.md');
|
|
@@ -24,27 +50,79 @@ function skillSource() {
|
|
|
24
50
|
}
|
|
25
51
|
return (0, node_fs_1.readFileSync)(path, 'utf-8');
|
|
26
52
|
}
|
|
53
|
+
/** Тот же текст под заголовком Cursor: поля у него свои, а тело общее. */
|
|
54
|
+
function cursorRule(skill) {
|
|
55
|
+
const description = /^description:\s*(.+)$/m.exec(skill)?.[1] ?? 'XFlow platform';
|
|
56
|
+
const body = skill.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
|
57
|
+
return `---\ndescription: ${description}\nalwaysApply: false\n---\n\n${body}`;
|
|
58
|
+
}
|
|
59
|
+
/** Пишет, только если содержимое отличается. Возвращает путь тронутого файла. */
|
|
60
|
+
function writeIfChanged(path, content) {
|
|
61
|
+
const before = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, 'utf-8') : null;
|
|
62
|
+
if (before === content)
|
|
63
|
+
return null;
|
|
64
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
65
|
+
(0, node_fs_1.writeFileSync)(path, content, 'utf-8');
|
|
66
|
+
return path;
|
|
67
|
+
}
|
|
27
68
|
/**
|
|
28
|
-
*
|
|
69
|
+
* Дописать указатель в AGENTS.md: только в конец и только один раз по маркеру.
|
|
70
|
+
* Файл принадлежит пользователю, там его правила, переписывать его мы не вправе.
|
|
71
|
+
* CLAUDE.md не трогаем вовсе: Claude Code и так читает .claude/skills.
|
|
72
|
+
*/
|
|
73
|
+
function appendPointer(base) {
|
|
74
|
+
const path = (0, node_path_1.join)(base, 'AGENTS.md');
|
|
75
|
+
const before = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, 'utf-8') : null;
|
|
76
|
+
if (before?.includes(POINTER_MARKER))
|
|
77
|
+
return null;
|
|
78
|
+
(0, node_fs_1.writeFileSync)(path, before ? `${before.replace(/\s*$/, '')}\n\n${POINTER}` : POINTER, 'utf-8');
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
81
|
+
/** Разложить инструкцию по папкам, где её найдёт агент. Отдаёт тронутые файлы. */
|
|
82
|
+
function install(base, global) {
|
|
83
|
+
const skill = skillSource();
|
|
84
|
+
const touched = [];
|
|
85
|
+
for (const dir of global ? GLOBAL_SKILL_DIRS : SKILL_DIRS) {
|
|
86
|
+
const path = writeIfChanged((0, node_path_1.join)(base, ...dir, 'xflow', 'SKILL.md'), skill);
|
|
87
|
+
if (path)
|
|
88
|
+
touched.push(path);
|
|
89
|
+
}
|
|
90
|
+
// Правило Cursor и указатель кладём только рядом с проектом: глобальных
|
|
91
|
+
// аналогов у них нет, а в домашней папке их никто не читает.
|
|
92
|
+
if (!global) {
|
|
93
|
+
const rule = writeIfChanged((0, node_path_1.join)(base, ...CURSOR_RULE), cursorRule(skill));
|
|
94
|
+
if (rule)
|
|
95
|
+
touched.push(rule);
|
|
96
|
+
const pointer = appendPointer(base);
|
|
97
|
+
if (pointer)
|
|
98
|
+
touched.push(pointer);
|
|
99
|
+
}
|
|
100
|
+
return touched;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Установка попутно, из `init` и `link`.
|
|
29
104
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
105
|
+
* Отдельной командой до этого доходили единицы, и агент оставался без знания о
|
|
106
|
+
* платформе — той самой, на которой ему предстоит работать. При этом инструкция
|
|
107
|
+
* не то, ради чего запускали команду, поэтому её сбой не роняет привязку папки.
|
|
32
108
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for (const root of ROOTS) {
|
|
38
|
-
const target = (0, node_path_1.join)(base, ...root, 'xflow', 'SKILL.md');
|
|
39
|
-
const before = (0, node_fs_1.existsSync)(target) ? (0, node_fs_1.readFileSync)(target, 'utf-8') : null;
|
|
40
|
-
if (before === content) {
|
|
41
|
-
(0, ui_1.note)((0, ui_1.dim)(` уже актуален: ${target}`));
|
|
42
|
-
continue;
|
|
109
|
+
function installSkillQuietly(base) {
|
|
110
|
+
try {
|
|
111
|
+
if (install(base, false).length > 0) {
|
|
112
|
+
(0, ui_1.note)((0, ui_1.dim)(' Инструкция о платформе разложена для ИИ-агента'));
|
|
43
113
|
}
|
|
44
|
-
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true });
|
|
45
|
-
(0, node_fs_1.writeFileSync)(target, content, 'utf-8');
|
|
46
|
-
(0, ui_1.out)(`${before === null ? 'создан' : 'обновлён'}: ${target}`);
|
|
47
114
|
}
|
|
115
|
+
catch {
|
|
116
|
+
(0, ui_1.note)((0, ui_1.dim)(' Инструкцию для ИИ-агента положить не удалось: xflow skills'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function skills(args) {
|
|
120
|
+
const global = (0, args_1.flagBool)(args, 'global');
|
|
121
|
+
const touched = install(global ? (0, node_os_1.homedir)() : process.cwd(), global);
|
|
122
|
+
for (const path of touched)
|
|
123
|
+
(0, ui_1.out)(path);
|
|
124
|
+
if (touched.length === 0)
|
|
125
|
+
(0, ui_1.note)((0, ui_1.dim)(' всё уже актуально'));
|
|
48
126
|
(0, ui_1.ok)(global ? 'Скилл xflow доступен во всех проектах' : 'Скилл xflow на месте');
|
|
49
127
|
(0, ui_1.note)((0, ui_1.dim)(' После обновления CLI повторите команду: скилл обновляется вместе с ним'));
|
|
50
128
|
}
|
package/dist/commands/sources.js
CHANGED
|
@@ -183,6 +183,8 @@ async function status() {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
(0, ui_1.out)('');
|
|
186
|
-
(0, ui_1.out)(
|
|
187
|
-
(0, ui_1.out)(
|
|
186
|
+
(0, ui_1.out)(`Собранная версия: ${card.dev_deploy_id ?? '— (сборки нет)'}`);
|
|
187
|
+
(0, ui_1.out)(`Видят посетители: ${card.live_deploy_id ?? '— (не публиковался)'}`);
|
|
188
|
+
(0, ui_1.out)('');
|
|
189
|
+
(0, ui_1.out)(card.project_url);
|
|
188
190
|
}
|
package/dist/help.js
CHANGED
|
@@ -18,8 +18,9 @@ ${(0, ui_1.bold)('Начало работы')}
|
|
|
18
18
|
xflow login вход через браузер
|
|
19
19
|
xflow init [папка] новый проект из шаблона платформы
|
|
20
20
|
xflow templates какие шаблоны доступны
|
|
21
|
-
xflow link <id> связать текущую папку с
|
|
22
|
-
xflow skills [--global]
|
|
21
|
+
xflow link <id> связать текущую папку с проектом (id: xflow projects list)
|
|
22
|
+
xflow skills [--global] обновить инструкцию о платформе для ИИ-агента
|
|
23
|
+
xflow mcp install доступ к платформе для агента без терминала
|
|
23
24
|
|
|
24
25
|
${(0, ui_1.bold)('Код')}
|
|
25
26
|
xflow status что на сервере и чем отличается локальная копия
|
|
@@ -28,10 +29,9 @@ ${(0, ui_1.bold)('Код')}
|
|
|
28
29
|
забрать исходники (по умолчанию последнюю ревизию)
|
|
29
30
|
|
|
30
31
|
${(0, ui_1.bold)('Выкладка')}
|
|
31
|
-
xflow deploy [--no-push]
|
|
32
|
-
отправить код, собрать локально и выложить на dev
|
|
32
|
+
xflow deploy [--no-push] отправить код, собрать на платформе и выложить на dev
|
|
33
33
|
xflow publish показать dev-версию посетителям
|
|
34
|
-
xflow rollback <номер версии> вернуть
|
|
34
|
+
xflow rollback <номер версии> вернуть проект на прошлую версию
|
|
35
35
|
xflow deployments история версий
|
|
36
36
|
|
|
37
37
|
${(0, ui_1.bold)('Функции')}
|
|
@@ -44,6 +44,9 @@ ${(0, ui_1.bold)('Функции')}
|
|
|
44
44
|
xflow schedules set <имя> "0 3 ? * * *"
|
|
45
45
|
запускать функцию по расписанию (UTC)
|
|
46
46
|
xflow schedules rm <имя> снять расписание
|
|
47
|
+
xflow env [check] переменные функций: что записано, чего не хватает
|
|
48
|
+
xflow env set ИМЯ=значение записать переменную
|
|
49
|
+
xflow env rm ИМЯ удалить переменную
|
|
47
50
|
|
|
48
51
|
${(0, ui_1.bold)('База данных')}
|
|
49
52
|
xflow db status какие миграции применены, а какие ждут
|
|
@@ -54,7 +57,6 @@ ${(0, ui_1.bold)('Справочное')}
|
|
|
54
57
|
xflow logs [--limit N] ошибки выложенного приложения в браузере
|
|
55
58
|
xflow projects list проекты организации
|
|
56
59
|
xflow projects get [id] карточка проекта
|
|
57
|
-
xflow open [--live] открыть приложение в браузере
|
|
58
60
|
xflow whoami чей ключ и что он может
|
|
59
61
|
xflow logout забыть ключ
|
|
60
62
|
|
|
@@ -106,6 +108,27 @@ ${(0, ui_1.bold)('xflow functions invoke')}.
|
|
|
106
108
|
|
|
107
109
|
Ошибки браузера собирает ${(0, ui_1.bold)('src/utils/error-logger.ts')} шаблона и только с
|
|
108
110
|
выложенных адресов: локальный ${(0, ui_1.bold)('npm run dev')} сюда не пишет.`,
|
|
111
|
+
env: `${(0, ui_1.bold)('xflow env')} — переменные окружения функций
|
|
112
|
+
|
|
113
|
+
Ключи, пароли и адреса чужих сервисов хранит платформа, а не репозиторий.
|
|
114
|
+
Значение уходит наверх и обратно не возвращается: увидеть его можно только внутри
|
|
115
|
+
функции. Так что своё же значение придётся хранить там, где вы его взяли.
|
|
116
|
+
|
|
117
|
+
xflow env что записано (имена, не значения)
|
|
118
|
+
xflow env check чего не хватает функциям этого проекта
|
|
119
|
+
xflow env set SMTP_PASSWORD=… записать
|
|
120
|
+
xflow env rm SMTP_PASSWORD удалить
|
|
121
|
+
|
|
122
|
+
--scope project видно только этому проекту, а не всей организации
|
|
123
|
+
|
|
124
|
+
${(0, ui_1.bold)('check')} читает исходники в ${(0, ui_1.bold)('functions/')} и ищет обращения ${(0, ui_1.bold)('process.env.ИМЯ')}. Это же
|
|
125
|
+
правило действует на выкатке: функция получает только те переменные, которые сама
|
|
126
|
+
упоминает по имени. Имя, собранное выражением (${(0, ui_1.bold)("process.env['KEY_' + n]")}), в окружение
|
|
127
|
+
не попадёт — читайте переменные буквально.
|
|
128
|
+
|
|
129
|
+
Значение доезжает до функции на выкатке, а не в момент записи: после ${(0, ui_1.bold)('env set')} нужно
|
|
130
|
+
${(0, ui_1.bold)('xflow functions deploy')} затронутых функций. То же и после удаления: в уже выложенной
|
|
131
|
+
функции старое значение останется до следующей выкатки.`,
|
|
109
132
|
schedules: `${(0, ui_1.bold)('xflow schedules')} — запуск функций по времени
|
|
110
133
|
|
|
111
134
|
Расписание это таймер-триггер Yandex: он зовёт функцию сам, без участия
|
|
@@ -138,22 +161,46 @@ ${(0, ui_1.bold)('xflow functions invoke')}.
|
|
|
138
161
|
Печатает статус, время ответа и тело. Ненулевой код возврата на статусе 4xx и
|
|
139
162
|
5xx: в CI такой вызов должен ронять шаг. Причину падения показывает
|
|
140
163
|
${(0, ui_1.bold)('xflow functions logs <имя>')}.`,
|
|
164
|
+
mcp: `${(0, ui_1.bold)('xflow mcp install')} — подключить агента к платформе напрямую
|
|
165
|
+
|
|
166
|
+
Прописывает сервер MCP в конфигурацию клиента, у которого есть своя команда
|
|
167
|
+
подключения: сегодня это Claude Code, остальным команда покажет, что вписать
|
|
168
|
+
руками. После этого агент управляет платформой без терминала: смотрит базу,
|
|
169
|
+
применяет миграции, выкладывает и вызывает функции, ставит расписания, читает
|
|
170
|
+
логи. Команды остаются: код, сборку и выкладку по-прежнему делает CLI, гнать
|
|
171
|
+
исходники через MCP значит забивать контекст агента и тратить ваши деньги.
|
|
172
|
+
|
|
173
|
+
Ключ доступа команда берёт из вашего входа и на экран не выводит: обычно её
|
|
174
|
+
запускает сам агент, а всё напечатанное попадает в его контекст и в историю
|
|
175
|
+
переписки.
|
|
176
|
+
|
|
177
|
+
--show-token всё же показать ключ (не под агентом)
|
|
178
|
+
|
|
179
|
+
Ключ один на человека в организации, и область у него не сужается: агент видит
|
|
180
|
+
все проекты организации и называет нужный сам. Проект он берёт из аргумента
|
|
181
|
+
${(0, ui_1.bold)('project_id')}, а не из вашей папки — папки он не видит.`,
|
|
141
182
|
skills: `${(0, ui_1.bold)('xflow skills')} — инструкция о платформе для ИИ-агента
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
и публиковать, откуда брать компоненты дизайн-системы, где смотреть
|
|
145
|
-
|
|
146
|
-
|
|
184
|
+
Про XFlow агенту знать неоткуда: в его обучении платформы нет. Инструкция объясняет,
|
|
185
|
+
как выкладывать и публиковать, откуда брать компоненты дизайн-системы, где смотреть
|
|
186
|
+
ошибки прода. ${(0, ui_1.bold)('init')} и ${(0, ui_1.bold)('link')} раскладывают её сами — эта команда нужна, чтобы
|
|
187
|
+
обновить её после обновления CLI: они едут одной версией.
|
|
147
188
|
|
|
148
|
-
|
|
149
|
-
${(0, ui_1.bold)('.claude/skills/xflow/')} читает Claude Code, ${(0, ui_1.bold)('.agents/skills/xflow/')} — Codex и OpenClaw.
|
|
189
|
+
Формат общий (agentskills.io), а папки у инструментов разные, поэтому кладём во все:
|
|
150
190
|
|
|
151
|
-
|
|
191
|
+
.claude/skills/xflow/SKILL.md Claude Code
|
|
192
|
+
.agents/skills/xflow/SKILL.md Codex, OpenClaw
|
|
193
|
+
.cursor/rules/xflow.mdc Cursor: общий формат он не читает
|
|
152
194
|
|
|
153
|
-
|
|
154
|
-
|
|
195
|
+
--global то же в домашней папке, тогда скилл виден во всех проектах.
|
|
196
|
+
Там добавляется ~/.codex/skills: общий каталог Codex смотрит
|
|
197
|
+
только внутри репозитория. Правило Cursor и AGENTS.md остаются
|
|
198
|
+
проектными, глобальных аналогов у них нет
|
|
155
199
|
|
|
156
|
-
|
|
200
|
+
Плюс несколько строк-указателей в ${(0, ui_1.bold)('AGENTS.md')}. Скилл подхватывается лениво, только
|
|
201
|
+
когда описание совпало с задачей, и на «добавь таблицу клиентов» он не сработает.
|
|
202
|
+
AGENTS.md агент читает всегда, поэтому указатель дописывается туда — в конец файла и
|
|
203
|
+
один раз. ${(0, ui_1.bold)('CLAUDE.md')} не трогаем: Claude Code и так читает .claude/skills.`,
|
|
157
204
|
push: `${(0, ui_1.bold)('xflow push')} — отправить исходники
|
|
158
205
|
|
|
159
206
|
Уходит вся рабочая копия целиком, одной ревизией. Не отправляются: node_modules,
|
|
@@ -178,18 +225,20 @@ ${(0, ui_1.bold)('AGENTS.md')} и ${(0, ui_1.bold)('CLAUDE.md')} команда
|
|
|
178
225
|
--force перезаписать папку целиком`,
|
|
179
226
|
deploy: `${(0, ui_1.bold)('xflow deploy')} — собрать и выложить
|
|
180
227
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
и dist).
|
|
228
|
+
Два шага: отправка исходников и сборка на платформе. Команда сборки и каталог
|
|
229
|
+
результата берутся из xflow.json (по умолчанию npm run build и dist).
|
|
184
230
|
|
|
185
|
-
--no-push не отправлять исходники,
|
|
186
|
-
--skip-build не собирать, взять готовый каталог сборки
|
|
231
|
+
--no-push не отправлять исходники, собрать из последней серверной ревизии
|
|
187
232
|
--force разрешить перезапись серверной ревизии при отправке
|
|
188
233
|
|
|
189
|
-
|
|
190
|
-
|
|
234
|
+
Собирает платформа, в чистой песочнице с одинаковой для всех версией Node,
|
|
235
|
+
поэтому «у меня работало» больше не зависит от вашей машины. Перед сборкой
|
|
236
|
+
проект проверяется на соответствие шаблону: несоответствия печатаются списком,
|
|
237
|
+
и сборка не запускается вовсе.
|
|
191
238
|
|
|
192
|
-
|
|
239
|
+
Собранную версию видно на странице проекта, ссылку CLI печатает. Посетителям — после
|
|
240
|
+
${(0, ui_1.bold)('xflow publish')}. Адрес самой сборки не печатается: в нём зашит номер версии, и
|
|
241
|
+
после следующей публикации такая ссылка молча отдаёт старую копию.`,
|
|
193
242
|
init: `${(0, ui_1.bold)('xflow init')} [папка] — новый проект
|
|
194
243
|
|
|
195
244
|
Разворачивает шаблон платформы: React на Vite, Tailwind, набор компонентов
|
|
@@ -209,7 +258,7 @@ ${(0, ui_1.bold)('AGENTS.md')} и ${(0, ui_1.bold)('CLAUDE.md')} команда
|
|
|
209
258
|
как подобрать его командой link.`,
|
|
210
259
|
rollback: `${(0, ui_1.bold)('xflow rollback')} <номер версии> — вернуть прошлую сборку
|
|
211
260
|
|
|
212
|
-
Переключает
|
|
261
|
+
Переключает проект на выбранную сборку. Возвращается только она: исходники
|
|
213
262
|
остаются на своей ревизии, и вернуть их — отдельное решение
|
|
214
263
|
(${(0, ui_1.bold)('xflow pull --revision N --into ./старая-версия')}).
|
|
215
264
|
|
package/dist/template.js
CHANGED
|
@@ -20,7 +20,7 @@ const README = `# %NAME%
|
|
|
20
20
|
\`\`\`bash
|
|
21
21
|
npm install # зависимости
|
|
22
22
|
npm run dev # разработка на localhost:3000
|
|
23
|
-
xflow deploy # отправить
|
|
23
|
+
xflow deploy # отправить код и собрать на платформе
|
|
24
24
|
xflow publish # показать dev-версию посетителям
|
|
25
25
|
\`\`\`
|
|
26
26
|
|
package/dist/tree.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.loadIgnoreRules = loadIgnoreRules;
|
|
|
4
4
|
exports.collectFiles = collectFiles;
|
|
5
5
|
exports.treeHash = treeHash;
|
|
6
6
|
exports.heaviest = heaviest;
|
|
7
|
-
exports.collectArtifact = collectArtifact;
|
|
8
7
|
const node_crypto_1 = require("node:crypto");
|
|
9
8
|
const node_fs_1 = require("node:fs");
|
|
10
9
|
const node_path_1 = require("node:path");
|
|
@@ -134,28 +133,3 @@ function treeHash(files) {
|
|
|
134
133
|
function heaviest(files, count = 5) {
|
|
135
134
|
return [...files].sort((a, b) => b.content.length - a.content.length).slice(0, count);
|
|
136
135
|
}
|
|
137
|
-
/** Собрать дерево из каталога сборки: пути внутри артефакта считаются от него. */
|
|
138
|
-
function collectArtifact(dir) {
|
|
139
|
-
const files = [];
|
|
140
|
-
const walk = (current) => {
|
|
141
|
-
for (const entry of (0, node_fs_1.readdirSync)(current, { withFileTypes: true })) {
|
|
142
|
-
const full = (0, node_path_1.join)(current, entry.name);
|
|
143
|
-
if (entry.isSymbolicLink())
|
|
144
|
-
continue;
|
|
145
|
-
if (entry.isDirectory()) {
|
|
146
|
-
walk(full);
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (!entry.isFile())
|
|
150
|
-
continue;
|
|
151
|
-
files.push({
|
|
152
|
-
path: (0, node_path_1.relative)(dir, full).split(node_path_1.sep).join('/'),
|
|
153
|
-
content: (0, node_fs_1.readFileSync)(full),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory())
|
|
158
|
-
return files;
|
|
159
|
-
walk(dir);
|
|
160
|
-
return files;
|
|
161
|
-
}
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Ответы публичного API, которые разбирает CLI.
|
|
4
|
+
*
|
|
5
|
+
* Адресов собранного приложения здесь нет: платформа их наружу не отдаёт. В них
|
|
6
|
+
* зашит номер версии, и после следующей публикации такая ссылка продолжает
|
|
7
|
+
* отвечать старой копией, а не ломается. Версии называем номерами, ссылка одна
|
|
8
|
+
* и ведёт на страницу проекта.
|
|
9
|
+
*/
|
|
3
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/version.js
CHANGED
|
@@ -6,6 +6,6 @@ exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
|
|
|
6
6
|
* рантайме нельзя, после сборки он лежит на уровень выше dist и в бандл не
|
|
7
7
|
* попадает.
|
|
8
8
|
*/
|
|
9
|
-
exports.CLI_VERSION = '0.1.
|
|
9
|
+
exports.CLI_VERSION = '0.1.7';
|
|
10
10
|
/** Адрес платформы по умолчанию. Переопределяется XFLOW_API_URL и полем `api` в xflow.json. */
|
|
11
11
|
exports.DEFAULT_API_URL = 'https://app.getxflow.com';
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@getxflow/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI платформы XFlow: синхронизация кода, деплой и публикация приложений",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"engines": {
|
|
7
|
-
"node": ">=20"
|
|
8
|
-
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"xflow": "dist/bin.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"skills"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsc -p tsconfig.json"
|
|
18
|
-
},
|
|
19
|
-
"publishConfig": {
|
|
20
|
-
"access": "public"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@getxflow/cli",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "CLI платформы XFlow: синхронизация кода, деплой и публикация приложений",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"xflow": "dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"skills"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
}
|
|
22
|
+
}
|