@getxflow/cli 0.1.7 → 0.1.9
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 +45 -44
- package/dist/api.js +10 -15
- package/dist/args.js +1 -1
- package/dist/bin.js +11 -7
- package/dist/commands/auth.js +33 -18
- package/dist/commands/db.js +18 -18
- package/dist/commands/deploy.js +18 -18
- package/dist/commands/env.js +36 -25
- package/dist/commands/functions.js +21 -21
- package/dist/commands/logs.js +6 -6
- package/dist/commands/mcp.js +14 -14
- package/dist/commands/projects.js +23 -23
- package/dist/commands/schedules.js +8 -8
- package/dist/commands/skills.js +6 -6
- package/dist/commands/sources.js +32 -32
- package/dist/config.js +5 -5
- package/dist/help.js +252 -250
- package/dist/limits.js +78 -0
- package/dist/session.js +1 -1
- package/dist/template.js +22 -22
- package/dist/ui.js +12 -12
- package/dist/version.js +1 -1
- package/dist/zip.js +7 -7
- package/package.json +22 -22
- package/skills/xflow/SKILL.md +19 -1
package/dist/commands/env.js
CHANGED
|
@@ -39,21 +39,23 @@ function sourceFiles(dir, found = []) {
|
|
|
39
39
|
}
|
|
40
40
|
/** Какие переменные упоминают функции проекта, и в какой из них. */
|
|
41
41
|
function referencedByFunctions(root) {
|
|
42
|
-
const
|
|
42
|
+
const needed = new Map();
|
|
43
|
+
const provided = new Map();
|
|
43
44
|
for (const file of sourceFiles((0, node_path_1.join)(root, FUNCTIONS_DIR))) {
|
|
44
45
|
const functionName = file.slice((0, node_path_1.join)(root, FUNCTIONS_DIR).length + 1).split(/[\\/]/)[0];
|
|
45
46
|
const code = (0, node_fs_1.readFileSync)(file, 'utf-8');
|
|
46
47
|
for (const match of code.matchAll(ENV_REFERENCE)) {
|
|
47
48
|
const name = match[1] || match[2];
|
|
48
|
-
if (!name
|
|
49
|
+
if (!name)
|
|
49
50
|
continue;
|
|
50
|
-
const
|
|
51
|
+
const target = PROVIDED.has(name) ? provided : needed;
|
|
52
|
+
const users = target.get(name) ?? [];
|
|
51
53
|
if (!users.includes(functionName))
|
|
52
54
|
users.push(functionName);
|
|
53
|
-
|
|
55
|
+
target.set(name, users);
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
return
|
|
58
|
+
return { needed, provided };
|
|
57
59
|
}
|
|
58
60
|
async function fetchVariables() {
|
|
59
61
|
const { root, config } = (0, config_1.requireProject)();
|
|
@@ -64,12 +66,12 @@ async function fetchVariables() {
|
|
|
64
66
|
async function envList() {
|
|
65
67
|
const { rows } = await fetchVariables();
|
|
66
68
|
if (rows.length === 0) {
|
|
67
|
-
(0, ui_1.note)('
|
|
68
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
69
|
+
(0, ui_1.note)('No variables');
|
|
70
|
+
(0, ui_1.note)((0, ui_1.dim)(' To store one: xflow env set SMTP_PASSWORD=secret'));
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
|
-
(0, ui_1.table)(rows.map((row) => [row.name, row.scope === 'project' ? '
|
|
72
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
73
|
+
(0, ui_1.table)(rows.map((row) => [row.name, row.scope === 'project' ? 'this project only' : 'the whole organization']));
|
|
74
|
+
(0, ui_1.note)((0, ui_1.dim)(' The platform never returns values: they are visible only inside the function'));
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* Сверить, хватает ли функциям переменных.
|
|
@@ -79,14 +81,23 @@ async function envList() {
|
|
|
79
81
|
*/
|
|
80
82
|
async function envCheck() {
|
|
81
83
|
const { names, root } = await fetchVariables();
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
(0, ui_1.note)('
|
|
84
|
+
const { needed, provided } = referencedByFunctions(root);
|
|
85
|
+
if (needed.size === 0 && provided.size === 0) {
|
|
86
|
+
(0, ui_1.note)('The functions of this project read no environment variables');
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
89
|
+
// Платформенные показываем отдельной таблицей, а не молчим о них: раньше
|
|
90
|
+
// функция, читающая только DATABASE_URL, получала ответ «переменных не
|
|
91
|
+
// читают», то есть команда делала ложное утверждение о собственном коде.
|
|
92
|
+
if (provided.size > 0) {
|
|
93
|
+
(0, ui_1.out)((0, ui_1.bold)('Provided by the platform:'));
|
|
94
|
+
(0, ui_1.table)([...provided.entries()].sort().map(([name, users]) => [name, users.join(', ')]));
|
|
95
|
+
if (needed.size > 0)
|
|
96
|
+
(0, ui_1.out)('');
|
|
97
|
+
}
|
|
87
98
|
const missing = [];
|
|
88
99
|
const present = [];
|
|
89
|
-
for (const [name, users] of [...
|
|
100
|
+
for (const [name, users] of [...needed.entries()].sort()) {
|
|
90
101
|
const row = [name, users.join(', ')];
|
|
91
102
|
if (names.has(name))
|
|
92
103
|
present.push(row);
|
|
@@ -94,24 +105,24 @@ async function envCheck() {
|
|
|
94
105
|
missing.push(row);
|
|
95
106
|
}
|
|
96
107
|
if (present.length > 0) {
|
|
97
|
-
(0, ui_1.out)((0, ui_1.bold)('
|
|
108
|
+
(0, ui_1.out)((0, ui_1.bold)('Stored on the platform:'));
|
|
98
109
|
(0, ui_1.table)(present);
|
|
99
110
|
}
|
|
100
111
|
if (missing.length === 0) {
|
|
101
|
-
(0, ui_1.ok)('
|
|
112
|
+
(0, ui_1.ok)('Every function has the variables it needs');
|
|
102
113
|
return;
|
|
103
114
|
}
|
|
104
115
|
(0, ui_1.out)('');
|
|
105
|
-
(0, ui_1.fail)('
|
|
116
|
+
(0, ui_1.fail)('Missing on the platform:');
|
|
106
117
|
(0, ui_1.table)(missing);
|
|
107
|
-
throw new errors_1.CliError(
|
|
118
|
+
throw new errors_1.CliError(`Missing variables: ${missing.length}`, 'To store them: xflow env set NAME=value. Until then the function receives undefined');
|
|
108
119
|
}
|
|
109
120
|
async function envSet(args) {
|
|
110
121
|
const { root, config } = (0, config_1.requireProject)();
|
|
111
122
|
const client = (0, session_1.connect)(config);
|
|
112
123
|
const pair = args.words[1];
|
|
113
124
|
if (!pair || !pair.includes('=')) {
|
|
114
|
-
throw new errors_1.CliError('
|
|
125
|
+
throw new errors_1.CliError('A NAME=value pair is required', 'For example: xflow env set SMTP_PASSWORD=secret');
|
|
115
126
|
}
|
|
116
127
|
const name = pair.slice(0, pair.indexOf('=')).trim();
|
|
117
128
|
const value = pair.slice(pair.indexOf('=') + 1);
|
|
@@ -119,12 +130,12 @@ async function envSet(args) {
|
|
|
119
130
|
method: 'POST',
|
|
120
131
|
body: { name, value, scope: (0, args_1.flagString)(args, 'scope') === 'project' ? 'project' : 'organization' },
|
|
121
132
|
});
|
|
122
|
-
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)}
|
|
133
|
+
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} stored (${result.scope === 'project' ? 'this project only' : 'the whole organization'})`);
|
|
123
134
|
// Значение попадает в функцию на выкатке, а не в момент записи: пока функцию
|
|
124
135
|
// не передеплоили, в её окружении лежит прежнее.
|
|
125
|
-
const users = referencedByFunctions(root).get(name);
|
|
136
|
+
const users = referencedByFunctions(root).needed.get(name);
|
|
126
137
|
if (users && users.length > 0) {
|
|
127
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
138
|
+
(0, ui_1.note)((0, ui_1.dim)(` For the value to arrive, redeploy: xflow functions deploy ${users.join(' && xflow functions deploy ')}`));
|
|
128
139
|
}
|
|
129
140
|
}
|
|
130
141
|
async function envRemove(args) {
|
|
@@ -132,12 +143,12 @@ async function envRemove(args) {
|
|
|
132
143
|
const client = (0, session_1.connect)(config);
|
|
133
144
|
const name = args.words[1];
|
|
134
145
|
if (!name)
|
|
135
|
-
throw new errors_1.CliError('
|
|
146
|
+
throw new errors_1.CliError('A variable name is required', 'What is stored: xflow env');
|
|
136
147
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/env?name=${encodeURIComponent(name)}`, { method: 'DELETE' });
|
|
137
148
|
if (!result.removed) {
|
|
138
|
-
(0, ui_1.note)(
|
|
149
|
+
(0, ui_1.note)(`There is no variable ${name}`);
|
|
139
150
|
return;
|
|
140
151
|
}
|
|
141
|
-
(0, ui_1.ok)(`${name}
|
|
142
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
152
|
+
(0, ui_1.ok)(`${name} deleted`);
|
|
153
|
+
(0, ui_1.note)((0, ui_1.dim)(' Functions already deployed keep the value until their next deploy'));
|
|
143
154
|
}
|
|
@@ -48,7 +48,7 @@ function bundle(root, entry) {
|
|
|
48
48
|
esbuild = require(require.resolve('esbuild', { paths: [root] }));
|
|
49
49
|
}
|
|
50
50
|
catch {
|
|
51
|
-
throw new errors_1.CliError('
|
|
51
|
+
throw new errors_1.CliError('Could not find esbuild in the project', 'Install it: npm i -D esbuild. It usually comes with Vite already');
|
|
52
52
|
}
|
|
53
53
|
const result = esbuild.buildSync({
|
|
54
54
|
entryPoints: [entry],
|
|
@@ -62,7 +62,7 @@ function bundle(root, entry) {
|
|
|
62
62
|
});
|
|
63
63
|
const text = result.outputFiles[0]?.text;
|
|
64
64
|
if (!text)
|
|
65
|
-
throw new errors_1.CliError(
|
|
65
|
+
throw new errors_1.CliError(`Building ${entry} produced nothing`);
|
|
66
66
|
return text;
|
|
67
67
|
}
|
|
68
68
|
async function functionsList() {
|
|
@@ -70,13 +70,13 @@ async function functionsList() {
|
|
|
70
70
|
const client = (0, session_1.connect)(config);
|
|
71
71
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/functions`);
|
|
72
72
|
if (data.functions.length === 0) {
|
|
73
|
-
(0, ui_1.note)(
|
|
73
|
+
(0, ui_1.note)(`No functions. Put the code in ${FUNCTIONS_DIR}/<name>/index.ts and run xflow functions deploy`);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
(0, ui_1.table)(data.functions.map((fn) => [
|
|
77
77
|
fn.name,
|
|
78
|
-
fn.status === 'deployed' ? '
|
|
79
|
-
fn.last_deployed_at ? (0, ui_1.formatAge)(fn.last_deployed_at) : '
|
|
78
|
+
fn.status === 'deployed' ? 'deployed' : fn.status === 'failed' ? 'failed' : fn.status,
|
|
79
|
+
fn.last_deployed_at ? (0, ui_1.formatAge)(fn.last_deployed_at) : '-',
|
|
80
80
|
fn.error_message ?? '',
|
|
81
81
|
]));
|
|
82
82
|
}
|
|
@@ -120,13 +120,13 @@ async function functionsInvoke(args) {
|
|
|
120
120
|
const client = (0, session_1.connect)(config);
|
|
121
121
|
const name = args.words[1];
|
|
122
122
|
if (!name)
|
|
123
|
-
throw new errors_1.CliError('
|
|
123
|
+
throw new errors_1.CliError('A function name is required', 'What is deployed: xflow functions list');
|
|
124
124
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}`);
|
|
125
125
|
const fn = card.functions.find((item) => item.name === name);
|
|
126
126
|
if (!fn || !fn.invoke_url) {
|
|
127
|
-
throw new errors_1.CliError(
|
|
128
|
-
?
|
|
129
|
-
:
|
|
127
|
+
throw new errors_1.CliError(`Function ${name} is not deployed`, card.functions.length > 0
|
|
128
|
+
? `Deployed: ${card.functions.map((item) => item.name).join(', ')}`
|
|
129
|
+
: `To deploy it: xflow functions deploy ${name}`);
|
|
130
130
|
}
|
|
131
131
|
const data = (0, args_1.flagString)(args, 'data');
|
|
132
132
|
const method = ((0, args_1.flagString)(args, 'method') ?? (data ? 'POST' : 'GET')).toUpperCase();
|
|
@@ -141,21 +141,21 @@ async function functionsInvoke(args) {
|
|
|
141
141
|
'X-Project-Token': card.project_token ?? '',
|
|
142
142
|
},
|
|
143
143
|
body: sendsBody ? (data ?? '{}') : undefined,
|
|
144
|
-
// У функции свой потолок в
|
|
144
|
+
// У функции свой потолок в 90 секунд: ждём чуть дольше, чтобы увидеть её
|
|
145
145
|
// собственный таймаут, а не свой.
|
|
146
|
-
signal: AbortSignal.timeout(
|
|
146
|
+
signal: AbortSignal.timeout(100_000),
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
catch (e) {
|
|
150
|
-
throw new errors_1.CliError(
|
|
150
|
+
throw new errors_1.CliError(`The function did not answer: ${e instanceof Error ? e.message : String(e)}`, 'Check that it is deployed: xflow functions list');
|
|
151
151
|
}
|
|
152
152
|
const elapsed = Date.now() - started;
|
|
153
153
|
const text = await response.text();
|
|
154
|
-
(0, ui_1.note)((0, ui_1.dim)(`${response.status} ${response.statusText}
|
|
154
|
+
(0, ui_1.note)((0, ui_1.dim)(`${response.status} ${response.statusText} in ${elapsed} ms`));
|
|
155
155
|
if (text)
|
|
156
156
|
(0, ui_1.out)(prettyBody(text));
|
|
157
157
|
if (!response.ok) {
|
|
158
|
-
throw new errors_1.CliError(
|
|
158
|
+
throw new errors_1.CliError(`The function answered ${response.status}`, `The stack and console output: xflow functions logs ${name}`);
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
async function functionsDeploy(args) {
|
|
@@ -164,23 +164,23 @@ async function functionsDeploy(args) {
|
|
|
164
164
|
const wanted = args.words[1];
|
|
165
165
|
const names = wanted ? [wanted] : discover(root);
|
|
166
166
|
if (names.length === 0) {
|
|
167
|
-
throw new errors_1.CliError(
|
|
167
|
+
throw new errors_1.CliError(`The project has no functions`, `Create ${FUNCTIONS_DIR}/<name>/index.ts exporting handler and try again`);
|
|
168
168
|
}
|
|
169
169
|
for (const name of names) {
|
|
170
170
|
const entry = entryFor(root, name);
|
|
171
171
|
if (!entry) {
|
|
172
|
-
throw new errors_1.CliError(
|
|
172
|
+
throw new errors_1.CliError(`Could not find ${FUNCTIONS_DIR}/${name}/index.ts`, `Available functions: ${discover(root).join(', ') || 'none at all'}`);
|
|
173
173
|
}
|
|
174
|
-
(0, ui_1.step)(
|
|
174
|
+
(0, ui_1.step)(`Building ${name}`);
|
|
175
175
|
const code = bundle(root, entry);
|
|
176
|
-
(0, ui_1.step)(
|
|
176
|
+
(0, ui_1.step)(`Deploying ${name}`);
|
|
177
177
|
const result = await (0, api_1.apiUpload)(client, `/api/v1/projects/${config.projectId}/functions?name=${encodeURIComponent(name)}`, Buffer.from(code, 'utf-8'), { 'Content-Type': 'application/javascript' });
|
|
178
|
-
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)}
|
|
178
|
+
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} deployed`);
|
|
179
179
|
(0, ui_1.out)(result.url);
|
|
180
180
|
if (result.secrets.length > 0) {
|
|
181
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
181
|
+
(0, ui_1.note)((0, ui_1.dim)(` Organization secrets in the environment: ${result.secrets.join(', ')}`));
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
const available = await refreshFunctionsEnv(root, client, config.projectId);
|
|
185
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
185
|
+
(0, ui_1.note)((0, ui_1.dim)(` Addresses in .env updated (${available.join(', ')}). In the frontend: xflow.functions.invoke('${names[0]}')`));
|
|
186
186
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -27,14 +27,14 @@ async function fetchLogs(source, name, args) {
|
|
|
27
27
|
/** Свежее — внизу: так последняя ошибка оказывается перед глазами, а не уезжает вверх. */
|
|
28
28
|
function render(rows) {
|
|
29
29
|
for (const row of [...rows].reverse()) {
|
|
30
|
-
const label = row.source === 'function' ? (row.function ?? '
|
|
30
|
+
const label = row.source === 'function' ? (row.function ?? 'function') : 'browser';
|
|
31
31
|
(0, ui_1.out)(`${(0, ui_1.dim)(stamp(row.timestamp))} ${(0, ui_1.bold)(label)} ${row.message}`);
|
|
32
32
|
if (row.stack) {
|
|
33
33
|
const lines = row.stack.split('\n');
|
|
34
34
|
for (const line of lines.slice(0, STACK_MAX_LINES))
|
|
35
35
|
(0, ui_1.out)((0, ui_1.dim)(` ${line}`));
|
|
36
36
|
if (lines.length > STACK_MAX_LINES)
|
|
37
|
-
(0, ui_1.out)((0, ui_1.dim)(` …
|
|
37
|
+
(0, ui_1.out)((0, ui_1.dim)(` … ${lines.length - STACK_MAX_LINES} more lines`));
|
|
38
38
|
}
|
|
39
39
|
(0, ui_1.out)('');
|
|
40
40
|
}
|
|
@@ -42,8 +42,8 @@ function render(rows) {
|
|
|
42
42
|
async function logs(args) {
|
|
43
43
|
const rows = await fetchLogs('client', undefined, args);
|
|
44
44
|
if (rows.length === 0) {
|
|
45
|
-
(0, ui_1.note)('
|
|
46
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
45
|
+
(0, ui_1.note)('No browser errors');
|
|
46
|
+
(0, ui_1.note)((0, ui_1.dim)(' This collects crashes of the released application, not of a local npm run dev'));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
render(rows);
|
|
@@ -52,8 +52,8 @@ async function functionsLogs(args) {
|
|
|
52
52
|
const name = args.words[1];
|
|
53
53
|
const rows = await fetchLogs('function', name, args);
|
|
54
54
|
if (rows.length === 0) {
|
|
55
|
-
(0, ui_1.note)(name ?
|
|
56
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
55
|
+
(0, ui_1.note)(name ? `Function ${name} has not crashed` : 'No function has crashed');
|
|
56
|
+
(0, ui_1.note)((0, ui_1.dim)(' Only failed calls land here: successful ones write nothing'));
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
render(rows);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -58,42 +58,42 @@ async function mcpInstall(args) {
|
|
|
58
58
|
const client = (0, session_1.connect)(root ? (0, config_1.readConfig)(root) : undefined);
|
|
59
59
|
const url = `${client.apiUrl.replace(/\/+$/, '')}/api/mcp`;
|
|
60
60
|
const identity = await (0, api_1.apiJson)(client, '/api/v1/me');
|
|
61
|
-
(0, ui_1.out)(`${(0, ui_1.bold)('
|
|
62
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
63
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
61
|
+
(0, ui_1.out)(`${(0, ui_1.bold)('MCP server')} ${url}`);
|
|
62
|
+
(0, ui_1.note)((0, ui_1.dim)(` Organization: ${identity.organization.name ?? 'unnamed'}`));
|
|
63
|
+
(0, ui_1.note)((0, ui_1.dim)(' The key covers the whole organization: the agent names the project itself'));
|
|
64
64
|
let found = 0;
|
|
65
65
|
let installed = 0;
|
|
66
66
|
for (const target of CLIENTS) {
|
|
67
67
|
if (!hasBinary(target.binary))
|
|
68
68
|
continue;
|
|
69
69
|
found++;
|
|
70
|
-
(0, ui_1.step)(
|
|
70
|
+
(0, ui_1.step)(`Writing into ${target.label}`);
|
|
71
71
|
execute(target.binary, [...target.reset]);
|
|
72
72
|
const result = execute(target.binary, target.args(url, client.token));
|
|
73
73
|
if (result.status === 0) {
|
|
74
74
|
installed++;
|
|
75
|
-
(0, ui_1.ok)(`${target.label}:
|
|
75
|
+
(0, ui_1.ok)(`${target.label}: the xflow server is connected`);
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
const reason = `${result.stderr ?? ''}${result.stdout ?? ''}`.trim();
|
|
79
|
-
(0, ui_1.note)(`${target.label}:
|
|
79
|
+
(0, ui_1.note)(`${target.label}: the command exited with an error`);
|
|
80
80
|
if (reason)
|
|
81
|
-
(0, ui_1.out)((0, ui_1.dim)(` ${reason.split(client.token).join('
|
|
81
|
+
(0, ui_1.out)((0, ui_1.dim)(` ${reason.split(client.token).join('<key>').split('\n').slice(0, 5).join('\n ')}`));
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
if (installed === 0) {
|
|
85
|
-
(0, ui_1.note)(found === 0 ? '
|
|
86
|
-
(0, ui_1.out)(`
|
|
87
|
-
(0, ui_1.out)(`
|
|
88
|
-
(0, ui_1.out)(`
|
|
85
|
+
(0, ui_1.note)(found === 0 ? 'Found no client with its own connect command.' : 'Add the server by hand:');
|
|
86
|
+
(0, ui_1.out)(` type: HTTP (streamable)`);
|
|
87
|
+
(0, ui_1.out)(` address: ${url}`);
|
|
88
|
+
(0, ui_1.out)(` header: Authorization: Bearer <key>`);
|
|
89
89
|
}
|
|
90
90
|
// Ключ печатаем только по явной просьбе: см. комментарий в шапке файла.
|
|
91
91
|
if ((0, args_1.flagBool)(args, 'show-token')) {
|
|
92
92
|
(0, ui_1.out)('');
|
|
93
|
-
(0, ui_1.out)(`${(0, ui_1.bold)('
|
|
94
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
93
|
+
(0, ui_1.out)(`${(0, ui_1.bold)('Key')} ${client.token}`);
|
|
94
|
+
(0, ui_1.note)((0, ui_1.dim)(' Do not leave it in the chat with the agent'));
|
|
95
95
|
}
|
|
96
96
|
else {
|
|
97
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
97
|
+
(0, ui_1.note)((0, ui_1.dim)(` The key lives in ${(0, node_path_1.join)((0, node_os_1.homedir)(), '.xflow', 'credentials.json')} and is not printed`));
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -41,10 +41,10 @@ async function templates() {
|
|
|
41
41
|
const client = (0, session_1.connect)();
|
|
42
42
|
const { templates: list } = await (0, api_1.apiJson)(client, '/api/v1/templates');
|
|
43
43
|
if (list.length === 0) {
|
|
44
|
-
(0, ui_1.note)('
|
|
44
|
+
(0, ui_1.note)('No templates');
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
(0, ui_1.table)(list.map((t) => [t.id, t.name, t.is_default ? '
|
|
47
|
+
(0, ui_1.table)(list.map((t) => [t.id, t.name, t.is_default ? 'default' : '', t.description ?? '']));
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Создать проект: шаблон платформы, локальная папка, проект на платформе.
|
|
@@ -62,19 +62,19 @@ async function init(args) {
|
|
|
62
62
|
const name = (0, args_1.flagString)(args, 'name') ?? (0, node_path_1.basename)(target);
|
|
63
63
|
const client = (0, session_1.connect)();
|
|
64
64
|
if (!isEmptyEnough(target)) {
|
|
65
|
-
throw new errors_1.CliError(
|
|
65
|
+
throw new errors_1.CliError(`The directory ${target} is not empty`, 'Create the project in an empty folder: xflow init my-app. To link an existing folder to a project: xflow link <id>');
|
|
66
66
|
}
|
|
67
67
|
let templateId = (0, args_1.flagString)(args, 'template');
|
|
68
68
|
if (!templateId) {
|
|
69
69
|
const { templates: list } = await (0, api_1.apiJson)(client, '/api/v1/templates');
|
|
70
70
|
templateId = (list.find((t) => t.is_default) ?? list[0])?.id;
|
|
71
71
|
if (!templateId)
|
|
72
|
-
throw new errors_1.CliError('
|
|
72
|
+
throw new errors_1.CliError('The platform has no templates at all');
|
|
73
73
|
}
|
|
74
|
-
(0, ui_1.step)('
|
|
74
|
+
(0, ui_1.step)('Fetching the template');
|
|
75
75
|
const archive = await (0, api_1.apiBinary)(client, `/api/v1/templates/${templateId}/archive`);
|
|
76
76
|
const files = (0, zip_1.zipRead)(archive);
|
|
77
|
-
(0, ui_1.step)('
|
|
77
|
+
(0, ui_1.step)('Creating the project on the platform');
|
|
78
78
|
const project = await (0, api_1.apiJson)(client, '/api/v1/projects', {
|
|
79
79
|
method: 'POST',
|
|
80
80
|
body: { name, database_id: (0, args_1.flagString)(args, 'database') ?? null },
|
|
@@ -94,18 +94,18 @@ async function init(args) {
|
|
|
94
94
|
(0, config_1.ignoreStateInGit)(target);
|
|
95
95
|
}
|
|
96
96
|
catch (e) {
|
|
97
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
98
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
97
|
+
(0, ui_1.note)((0, ui_1.dim)(` The project "${project.name}" is already created (${project.id}).`));
|
|
98
|
+
(0, ui_1.note)((0, ui_1.dim)(' Sort the folder out and pick it up: xflow link ' + project.id));
|
|
99
99
|
throw e;
|
|
100
100
|
}
|
|
101
101
|
(0, skills_1.installSkillQuietly)(target);
|
|
102
|
-
(0, ui_1.ok)(
|
|
102
|
+
(0, ui_1.ok)(`Project "${project.name}" created: ${files.length} template files`);
|
|
103
103
|
(0, ui_1.out)('');
|
|
104
|
-
(0, ui_1.out)(` ${(0, ui_1.bold)('
|
|
104
|
+
(0, ui_1.out)(` ${(0, ui_1.bold)('Next:')}`);
|
|
105
105
|
(0, ui_1.out)(` cd ${(0, node_path_1.basename)(target)}`);
|
|
106
106
|
(0, ui_1.out)(' npm install');
|
|
107
|
-
(0, ui_1.out)(' npm run dev #
|
|
108
|
-
(0, ui_1.out)(' xflow deploy #
|
|
107
|
+
(0, ui_1.out)(' npm run dev # develop');
|
|
108
|
+
(0, ui_1.out)(' xflow deploy # send the code, build and release');
|
|
109
109
|
}
|
|
110
110
|
/** Связать текущую папку с уже существующим проектом. */
|
|
111
111
|
async function link(args) {
|
|
@@ -114,7 +114,7 @@ async function link(args) {
|
|
|
114
114
|
const client = (0, session_1.connect)(existing);
|
|
115
115
|
const projectId = args.words[0];
|
|
116
116
|
if (!projectId) {
|
|
117
|
-
throw new errors_1.CliError('
|
|
117
|
+
throw new errors_1.CliError('A project identifier is required', 'The list: xflow projects list');
|
|
118
118
|
}
|
|
119
119
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
120
120
|
const dir = existing ? root : process.cwd();
|
|
@@ -128,10 +128,10 @@ async function link(args) {
|
|
|
128
128
|
// молча теряет доступ к облачным функциям. Восстанавливаем, но чужой не трогаем.
|
|
129
129
|
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(dir, '.env')) && card.project_token) {
|
|
130
130
|
write(dir, '.env', (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions));
|
|
131
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
131
|
+
(0, ui_1.note)((0, ui_1.dim)(' Created .env with the project token and the function addresses'));
|
|
132
132
|
}
|
|
133
133
|
(0, skills_1.installSkillQuietly)(dir);
|
|
134
|
-
(0, ui_1.ok)(
|
|
134
|
+
(0, ui_1.ok)(`The folder is linked to the project "${card.name}"`);
|
|
135
135
|
(0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
|
|
136
136
|
}
|
|
137
137
|
async function list() {
|
|
@@ -139,13 +139,13 @@ async function list() {
|
|
|
139
139
|
const client = (0, session_1.connect)(root ? (0, config_1.readConfig)(root) : undefined);
|
|
140
140
|
const { projects } = await (0, api_1.apiJson)(client, '/api/v1/projects');
|
|
141
141
|
if (projects.length === 0) {
|
|
142
|
-
(0, ui_1.note)('
|
|
142
|
+
(0, ui_1.note)('No projects. To create one: xflow init');
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
(0, ui_1.table)(projects.map((p) => [
|
|
146
146
|
p.id,
|
|
147
147
|
p.name,
|
|
148
|
-
p.live_deploy_id ? '
|
|
148
|
+
p.live_deploy_id ? 'published' : p.dev_deploy_id ? 'dev only' : 'no build',
|
|
149
149
|
(0, ui_1.formatAge)(p.updated_at),
|
|
150
150
|
]));
|
|
151
151
|
}
|
|
@@ -154,7 +154,7 @@ async function get(args) {
|
|
|
154
154
|
const config = root ? (0, config_1.readConfig)(root) : undefined;
|
|
155
155
|
const projectId = args.words[0] ?? config?.projectId;
|
|
156
156
|
if (!projectId) {
|
|
157
|
-
throw new errors_1.CliError('
|
|
157
|
+
throw new errors_1.CliError('A project identifier is required', 'Or run the command inside the project folder');
|
|
158
158
|
}
|
|
159
159
|
const client = (0, session_1.connect)(config);
|
|
160
160
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
@@ -162,19 +162,19 @@ 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)(`Built version: ${card.dev_deploy_id ?? '(no build)'}`);
|
|
166
|
+
(0, ui_1.out)(`Visitors see: ${card.live_deploy_id ?? '(never published)'}`);
|
|
167
|
+
(0, ui_1.out)(`Database: ${card.database ? `${card.database.name} (${card.database.schema})` : '(none)'}`);
|
|
168
168
|
(0, ui_1.out)('');
|
|
169
169
|
(0, ui_1.out)(card.project_url);
|
|
170
170
|
if (card.functions.length > 0) {
|
|
171
171
|
(0, ui_1.out)('');
|
|
172
|
-
(0, ui_1.out)('
|
|
172
|
+
(0, ui_1.out)('Functions:');
|
|
173
173
|
(0, ui_1.table)(card.functions.map((f) => [` ${f.name}`, f.status, (0, ui_1.formatAge)(f.last_deployed_at)]));
|
|
174
174
|
}
|
|
175
175
|
if (card.schedules.length > 0) {
|
|
176
176
|
(0, ui_1.out)('');
|
|
177
|
-
(0, ui_1.out)('
|
|
177
|
+
(0, ui_1.out)('Schedules:');
|
|
178
178
|
(0, ui_1.table)(card.schedules.map((s) => [` ${s.function_name}`, s.cron_expression, s.status]));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -14,8 +14,8 @@ async function schedulesList() {
|
|
|
14
14
|
const client = (0, session_1.connect)(config);
|
|
15
15
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/schedules`);
|
|
16
16
|
if (data.schedules.length === 0) {
|
|
17
|
-
(0, ui_1.note)('
|
|
18
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
17
|
+
(0, ui_1.note)('No schedules');
|
|
18
|
+
(0, ui_1.note)((0, ui_1.dim)(' To run a function on a timer: xflow schedules set <function> "0 3 ? * * *"'));
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
(0, ui_1.table)(data.schedules.map((row) => [
|
|
@@ -31,21 +31,21 @@ async function schedulesSet(args) {
|
|
|
31
31
|
const functionName = args.words[1];
|
|
32
32
|
const cron = args.words[2];
|
|
33
33
|
if (!functionName || !cron) {
|
|
34
|
-
throw new errors_1.CliError('
|
|
34
|
+
throw new errors_1.CliError('A function name and a schedule are required', 'For example: xflow schedules set nightly-report "0 3 ? * * *" runs every day at 03:00 UTC');
|
|
35
35
|
}
|
|
36
36
|
const row = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/schedules`, {
|
|
37
37
|
method: 'POST',
|
|
38
38
|
body: { function: functionName, cron, payload: (0, args_1.flagString)(args, 'payload') ?? null },
|
|
39
39
|
});
|
|
40
|
-
(0, ui_1.ok)(`${(0, ui_1.bold)(row.function)}
|
|
41
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
40
|
+
(0, ui_1.ok)(`${(0, ui_1.bold)(row.function)} runs ${row.description}`);
|
|
41
|
+
(0, ui_1.note)((0, ui_1.dim)(` The time is UTC. To check by hand: xflow functions invoke ${row.function}`));
|
|
42
42
|
}
|
|
43
43
|
async function schedulesRemove(args) {
|
|
44
44
|
const { config } = (0, config_1.requireProject)();
|
|
45
45
|
const client = (0, session_1.connect)(config);
|
|
46
46
|
const functionName = args.words[1];
|
|
47
47
|
if (!functionName) {
|
|
48
|
-
throw new errors_1.CliError('
|
|
48
|
+
throw new errors_1.CliError('A function name is required', 'What runs on a timer: xflow schedules list');
|
|
49
49
|
}
|
|
50
50
|
const cron = args.words[2];
|
|
51
51
|
const query = new URLSearchParams({ function: functionName });
|
|
@@ -53,8 +53,8 @@ async function schedulesRemove(args) {
|
|
|
53
53
|
query.set('cron', cron);
|
|
54
54
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/schedules?${query.toString()}`, { method: 'DELETE' });
|
|
55
55
|
if (result.removed === 0) {
|
|
56
|
-
(0, ui_1.note)(
|
|
56
|
+
(0, ui_1.note)(`Function ${functionName} has no such schedule`);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
(0, ui_1.ok)(
|
|
59
|
+
(0, ui_1.ok)(`Schedules removed: ${result.removed}`);
|
|
60
60
|
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -46,7 +46,7 @@ environment variables or production logs. Command list: \`xflow help\`.
|
|
|
46
46
|
function skillSource() {
|
|
47
47
|
const path = (0, node_path_1.join)(__dirname, '..', '..', 'skills', 'xflow', 'SKILL.md');
|
|
48
48
|
if (!(0, node_fs_1.existsSync)(path)) {
|
|
49
|
-
throw new errors_1.CliError('
|
|
49
|
+
throw new errors_1.CliError('The CLI package has no skill file', 'Reinstall @getxflow/cli');
|
|
50
50
|
}
|
|
51
51
|
return (0, node_fs_1.readFileSync)(path, 'utf-8');
|
|
52
52
|
}
|
|
@@ -109,11 +109,11 @@ function install(base, global) {
|
|
|
109
109
|
function installSkillQuietly(base) {
|
|
110
110
|
try {
|
|
111
111
|
if (install(base, false).length > 0) {
|
|
112
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
112
|
+
(0, ui_1.note)((0, ui_1.dim)(' The platform instructions are laid out for the AI agent'));
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
catch {
|
|
116
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
116
|
+
(0, ui_1.note)((0, ui_1.dim)(' Could not lay out the AI agent instructions: xflow skills'));
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
function skills(args) {
|
|
@@ -122,7 +122,7 @@ function skills(args) {
|
|
|
122
122
|
for (const path of touched)
|
|
123
123
|
(0, ui_1.out)(path);
|
|
124
124
|
if (touched.length === 0)
|
|
125
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
126
|
-
(0, ui_1.ok)(global ? '
|
|
127
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
125
|
+
(0, ui_1.note)((0, ui_1.dim)(' everything is already up to date'));
|
|
126
|
+
(0, ui_1.ok)(global ? 'The xflow skill is available in every project' : 'The xflow skill is in place');
|
|
127
|
+
(0, ui_1.note)((0, ui_1.dim)(' After a CLI update run the command again: the skill ships with it'));
|
|
128
128
|
}
|