relayax-cli 0.4.33 β 0.4.35
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/dist/commands/install.js
CHANGED
|
@@ -398,7 +398,11 @@ function registerInstall(program) {
|
|
|
398
398
|
(0, installer_js_1.printRequiresCheck)(requiresResults);
|
|
399
399
|
const setupCmd = resolvedAgent.commands.find((c) => c.name.startsWith('setup-'));
|
|
400
400
|
if (setupCmd && requiresResults.some((r) => r.status === 'missing' || r.status === 'warn')) {
|
|
401
|
-
|
|
401
|
+
const toolNames = selectedTools
|
|
402
|
+
? selectedTools.map((t) => t.name).slice(0, 2).join(' λλ ')
|
|
403
|
+
: 'Claude Code';
|
|
404
|
+
console.log(`\n \x1b[36mπ μ€μ μ΄ νμν©λλ€.\x1b[0m`);
|
|
405
|
+
console.log(` \x1b[36m ${toolNames}λ₯Ό μ΄κ³ \x1b[1m/${setupCmd.name}\x1b[0m\x1b[36m μ μ
λ ₯νμΈμ\x1b[0m`);
|
|
402
406
|
}
|
|
403
407
|
}
|
|
404
408
|
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -16,6 +16,7 @@ const error_report_js_1 = require("../lib/error-report.js");
|
|
|
16
16
|
const step_tracker_js_1 = require("../lib/step-tracker.js");
|
|
17
17
|
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
18
18
|
const setup_command_js_1 = require("../lib/setup-command.js");
|
|
19
|
+
const requires_suggest_js_1 = require("../lib/requires-suggest.js");
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
21
|
const cliPkg = require('../../package.json');
|
|
21
22
|
const VALID_DIRS = ['skills', 'agents', 'rules', 'commands', 'bin'];
|
|
@@ -681,6 +682,28 @@ function registerPublish(program) {
|
|
|
681
682
|
console.error(` β relay.yamlμ visibility: ${config.visibility} μ μ₯λ¨ (${visLabelMap[config.visibility]})\n`);
|
|
682
683
|
}
|
|
683
684
|
}
|
|
685
|
+
// ββ Requires μλ κ°μ§ + μ μ ββ
|
|
686
|
+
if (isTTY && !json) {
|
|
687
|
+
const suggestions = (0, requires_suggest_js_1.suggestRequires)(relayDir, config.requires);
|
|
688
|
+
if (suggestions.length > 0) {
|
|
689
|
+
console.error('\n\x1b[33mβ‘ requiresμ λΉ μ§ νλͺ©μ΄ κ°μ§λμμ΅λλ€:\x1b[0m');
|
|
690
|
+
for (const line of (0, requires_suggest_js_1.formatSuggestions)(suggestions)) {
|
|
691
|
+
console.error(line);
|
|
692
|
+
}
|
|
693
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
694
|
+
const addThem = await confirm({
|
|
695
|
+
message: 'requiresμ μΆκ°ν κΉμ?',
|
|
696
|
+
default: true,
|
|
697
|
+
});
|
|
698
|
+
if (addThem) {
|
|
699
|
+
config.requires = (0, requires_suggest_js_1.mergeIntoRequires)(config.requires ?? {}, suggestions);
|
|
700
|
+
const yamlData = js_yaml_1.default.load(fs_1.default.readFileSync(relayYamlPath, 'utf-8'));
|
|
701
|
+
yamlData.requires = config.requires;
|
|
702
|
+
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
703
|
+
console.error(' β relay.yamlμ requires μ
λ°μ΄νΈλ¨\n');
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
684
707
|
// Generate setup command BEFORE detectCommands so it's included in metadata
|
|
685
708
|
{
|
|
686
709
|
const commandsDir = path_1.default.join(relayDir, 'commands');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Requires } from '../commands/publish.js';
|
|
2
|
+
interface Suggestion {
|
|
3
|
+
category: 'env' | 'cli' | 'pip' | 'runtime';
|
|
4
|
+
name: string;
|
|
5
|
+
source: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
setup_hint?: string;
|
|
8
|
+
install?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* .relay/ λλ ν 리λ₯Ό μ€μΊνμ¬ requiresμ λΉ μ§ νλͺ©μ μ μνλ€.
|
|
13
|
+
*/
|
|
14
|
+
export declare function suggestRequires(relayDir: string, currentRequires?: Requires): Suggestion[];
|
|
15
|
+
/**
|
|
16
|
+
* μ μ νλͺ©μ μ¬λμ΄ μ½κΈ° μ’μ ννλ‘ ν¬λ§·νλ€.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatSuggestions(suggestions: Suggestion[]): string[];
|
|
19
|
+
/**
|
|
20
|
+
* μ μ νλͺ©μ requires κ°μ²΄μ λ³ν©νλ€.
|
|
21
|
+
*/
|
|
22
|
+
export declare function mergeIntoRequires(requires: Requires, suggestions: Suggestion[]): Requires;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.suggestRequires = suggestRequires;
|
|
7
|
+
exports.formatSuggestions = formatSuggestions;
|
|
8
|
+
exports.mergeIntoRequires = mergeIntoRequires;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
// Python stdlib β μ΄ λͺ©λ‘μ μλ importλ pip ν¨ν€μ§ ν보
|
|
12
|
+
const PYTHON_STDLIB = new Set([
|
|
13
|
+
'abc', 'argparse', 'ast', 'asyncio', 'base64', 'bisect', 'calendar',
|
|
14
|
+
'cmath', 'codecs', 'collections', 'concurrent', 'contextlib', 'copy',
|
|
15
|
+
'csv', 'ctypes', 'dataclasses', 'datetime', 'decimal', 'difflib',
|
|
16
|
+
'email', 'enum', 'errno', 'filecmp', 'fnmatch', 'fractions', 'ftplib',
|
|
17
|
+
'functools', 'gc', 'gettext', 'glob', 'gzip', 'hashlib', 'heapq',
|
|
18
|
+
'hmac', 'html', 'http', 'imaplib', 'importlib', 'inspect', 'io',
|
|
19
|
+
'itertools', 'json', 'keyword', 'linecache', 'locale', 'logging',
|
|
20
|
+
'lzma', 'math', 'mimetypes', 'multiprocessing', 'numbers', 'operator',
|
|
21
|
+
'os', 'pathlib', 'pickle', 'platform', 'plistlib', 'pprint',
|
|
22
|
+
'profile', 'pstats', 'queue', 'random', 're', 'readline',
|
|
23
|
+
'reprlib', 'resource', 'rlcompleter', 'sched', 'secrets', 'select',
|
|
24
|
+
'shelve', 'shlex', 'shutil', 'signal', 'site', 'smtplib', 'socket',
|
|
25
|
+
'socketserver', 'sqlite3', 'ssl', 'stat', 'statistics', 'string',
|
|
26
|
+
'struct', 'subprocess', 'sys', 'sysconfig', 'syslog', 'tempfile',
|
|
27
|
+
'textwrap', 'threading', 'time', 'timeit', 'token', 'tokenize',
|
|
28
|
+
'tomllib', 'trace', 'traceback', 'tracemalloc', 'types', 'typing',
|
|
29
|
+
'unicodedata', 'unittest', 'urllib', 'uuid', 'venv', 'warnings',
|
|
30
|
+
'wave', 'weakref', 'webbrowser', 'xml', 'xmlrpc', 'zipfile', 'zipimport', 'zlib',
|
|
31
|
+
// μμ£Ό μ°μ΄λ μλΈλͺ¨λ
|
|
32
|
+
'os.path', 'collections.abc', 'concurrent.futures', 'urllib.parse',
|
|
33
|
+
'http.client', 'http.server', 'email.mime',
|
|
34
|
+
]);
|
|
35
|
+
// pip ν¨ν€μ§λͺ
β import μ΄λ¦ λ§€ν (λ€λ₯Έ κ²½μ°λ§)
|
|
36
|
+
const PIP_IMPORT_MAP = {
|
|
37
|
+
'Pillow': 'PIL',
|
|
38
|
+
'google-genai': 'google',
|
|
39
|
+
'scikit-learn': 'sklearn',
|
|
40
|
+
'python-dotenv': 'dotenv',
|
|
41
|
+
'beautifulsoup4': 'bs4',
|
|
42
|
+
'opencv-python': 'cv2',
|
|
43
|
+
'pyyaml': 'yaml',
|
|
44
|
+
};
|
|
45
|
+
// import μ΄λ¦ β pip ν¨ν€μ§λͺ
μλ§€ν
|
|
46
|
+
const IMPORT_TO_PIP = {};
|
|
47
|
+
for (const [pip, imp] of Object.entries(PIP_IMPORT_MAP)) {
|
|
48
|
+
IMPORT_TO_PIP[imp] = pip;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* .relay/ λλ ν 리λ₯Ό μ€μΊνμ¬ requiresμ λΉ μ§ νλͺ©μ μ μνλ€.
|
|
52
|
+
*/
|
|
53
|
+
function suggestRequires(relayDir, currentRequires) {
|
|
54
|
+
const suggestions = [];
|
|
55
|
+
const existing = normalizeExisting(currentRequires);
|
|
56
|
+
// λͺ¨λ μ€ν¬λ¦½νΈ νμΌ μμ§
|
|
57
|
+
const scripts = findScripts(relayDir);
|
|
58
|
+
for (const scriptPath of scripts) {
|
|
59
|
+
const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
60
|
+
const relName = path_1.default.relative(relayDir, scriptPath);
|
|
61
|
+
const ext = path_1.default.extname(scriptPath);
|
|
62
|
+
// Python μ€ν¬λ¦½νΈ
|
|
63
|
+
if (ext === '.py') {
|
|
64
|
+
// νκ²½λ³μ κ°μ§: os.environ.get("VAR") / os.environ["VAR"] / os.getenv("VAR")
|
|
65
|
+
const envPattern = /os\.environ\.get\(\s*['"](\w+)['"]/g;
|
|
66
|
+
const envPattern2 = /os\.environ\[['"](\w+)['"]\]/g;
|
|
67
|
+
const envPattern3 = /os\.getenv\(\s*['"](\w+)['"]/g;
|
|
68
|
+
for (const pattern of [envPattern, envPattern2, envPattern3]) {
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
71
|
+
const varName = match[1];
|
|
72
|
+
if (!existing.envNames.has(varName)) {
|
|
73
|
+
suggestions.push({ category: 'env', name: varName, source: relName });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// pip ν¨ν€μ§ κ°μ§: import xxx / from xxx import
|
|
78
|
+
const importPattern = /^(?:import|from)\s+(\w+)/gm;
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
81
|
+
const moduleName = match[1];
|
|
82
|
+
if (PYTHON_STDLIB.has(moduleName))
|
|
83
|
+
continue;
|
|
84
|
+
const pipName = IMPORT_TO_PIP[moduleName] ?? moduleName;
|
|
85
|
+
if (!existing.pipNames.has(pipName) && !existing.pipNames.has(moduleName)) {
|
|
86
|
+
suggestions.push({ category: 'pip', name: pipName, source: relName });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// shebang β python3 λ°νμ
|
|
90
|
+
if (content.startsWith('#!/') && content.includes('python')) {
|
|
91
|
+
if (!existing.hasRuntime.python) {
|
|
92
|
+
suggestions.push({ category: 'runtime', name: 'python', source: relName });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Node/TS μ€ν¬λ¦½νΈ
|
|
97
|
+
if (ext === '.js' || ext === '.ts' || ext === '.mjs') {
|
|
98
|
+
// νκ²½λ³μ κ°μ§: process.env.VAR / process.env['VAR']
|
|
99
|
+
const envPattern = /process\.env\.(\w+)/g;
|
|
100
|
+
const envPattern2 = /process\.env\[['"](\w+)['"]\]/g;
|
|
101
|
+
for (const pattern of [envPattern, envPattern2]) {
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
104
|
+
const varName = match[1];
|
|
105
|
+
if (['NODE_ENV', 'PATH', 'HOME', 'PWD'].includes(varName))
|
|
106
|
+
continue;
|
|
107
|
+
if (!existing.envNames.has(varName)) {
|
|
108
|
+
suggestions.push({ category: 'env', name: varName, source: relName });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// setup-* μ€ν¬ κ°μ§: κ΄λ ¨ envκ° requiresμ μμΌλ©΄ μ μ
|
|
115
|
+
const skillsDir = path_1.default.join(relayDir, 'skills');
|
|
116
|
+
if (fs_1.default.existsSync(skillsDir)) {
|
|
117
|
+
for (const entry of fs_1.default.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
118
|
+
if (entry.isDirectory() && entry.name.startsWith('setup-')) {
|
|
119
|
+
const setupName = entry.name; // e.g., setup-kling
|
|
120
|
+
const skillMd = path_1.default.join(skillsDir, setupName, 'SKILL.md');
|
|
121
|
+
if (fs_1.default.existsSync(skillMd)) {
|
|
122
|
+
const skillContent = fs_1.default.readFileSync(skillMd, 'utf-8');
|
|
123
|
+
// SKILL.mdμμ νκ²½λ³μ μ°Έμ‘° μ°ΎκΈ°
|
|
124
|
+
const envRefs = /[A-Z][A-Z0-9_]{2,}/g;
|
|
125
|
+
let match;
|
|
126
|
+
while ((match = envRefs.exec(skillContent)) !== null) {
|
|
127
|
+
const varName = match[0];
|
|
128
|
+
if (['SKILL', 'WHEN', 'THEN', 'SHALL', 'MUST', 'TODO', 'NOTE', 'IMPORTANT', 'WARNING', 'ERROR', 'JSON', 'API', 'URL', 'HTTP', 'HTTPS', 'MCP', 'CLI', 'SDK', 'README', 'YAML'].includes(varName))
|
|
129
|
+
continue;
|
|
130
|
+
if (varName.endsWith('_KEY') || varName.endsWith('_TOKEN') || varName.endsWith('_SECRET') || varName.endsWith('_SESSION')) {
|
|
131
|
+
if (!existing.envNames.has(varName)) {
|
|
132
|
+
suggestions.push({
|
|
133
|
+
category: 'env',
|
|
134
|
+
name: varName,
|
|
135
|
+
source: `skills/${setupName}/SKILL.md`,
|
|
136
|
+
setup_hint: `/${setupName} μ€ν¬μ μ€ννμΈμ`,
|
|
137
|
+
required: false,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// CLI λꡬ κ°μ§: subprocess.run(['cmd', ...]) / execSync('cmd ...')
|
|
147
|
+
for (const scriptPath of scripts) {
|
|
148
|
+
const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
149
|
+
const relName = path_1.default.relative(relayDir, scriptPath);
|
|
150
|
+
// Python subprocess
|
|
151
|
+
const subprocessPattern = /subprocess\.(?:run|Popen|call)\(\s*\[?\s*['"](\w+)['"]/g;
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = subprocessPattern.exec(content)) !== null) {
|
|
154
|
+
const cmd = match[1];
|
|
155
|
+
if (['python', 'python3', 'node', 'npm', 'npx'].includes(cmd))
|
|
156
|
+
continue;
|
|
157
|
+
if (!existing.cliNames.has(cmd)) {
|
|
158
|
+
suggestions.push({ category: 'cli', name: cmd, source: relName });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Node execSync
|
|
162
|
+
const execPattern = /execSync\(\s*['"`](\w+)/g;
|
|
163
|
+
while ((match = execPattern.exec(content)) !== null) {
|
|
164
|
+
const cmd = match[1];
|
|
165
|
+
if (['node', 'npm', 'npx', 'git'].includes(cmd))
|
|
166
|
+
continue;
|
|
167
|
+
if (!existing.cliNames.has(cmd)) {
|
|
168
|
+
suggestions.push({ category: 'cli', name: cmd, source: relName });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// μ€λ³΅ μ κ±°
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
return suggestions.filter((s) => {
|
|
175
|
+
const key = `${s.category}:${s.name}`;
|
|
176
|
+
if (seen.has(key))
|
|
177
|
+
return false;
|
|
178
|
+
seen.add(key);
|
|
179
|
+
return true;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function normalizeExisting(requires) {
|
|
183
|
+
const envNames = new Set();
|
|
184
|
+
const cliNames = new Set();
|
|
185
|
+
const pipNames = new Set();
|
|
186
|
+
const npmNames = new Set();
|
|
187
|
+
const hasRuntime = { node: false, python: false };
|
|
188
|
+
if (!requires)
|
|
189
|
+
return { envNames, cliNames, pipNames, npmNames, hasRuntime };
|
|
190
|
+
for (const e of requires.env ?? [])
|
|
191
|
+
envNames.add(e.name);
|
|
192
|
+
for (const c of requires.cli ?? [])
|
|
193
|
+
cliNames.add(c.name);
|
|
194
|
+
for (const n of requires.npm ?? [])
|
|
195
|
+
npmNames.add(typeof n === 'string' ? n : n.name);
|
|
196
|
+
// pipμ Requires νμ
μ μμ§λ§ relay.yamlμ μ‘΄μ¬ν μ μμ
|
|
197
|
+
const raw = requires;
|
|
198
|
+
if (Array.isArray(raw.pip)) {
|
|
199
|
+
for (const p of raw.pip)
|
|
200
|
+
pipNames.add(typeof p === 'string' ? p : p.name);
|
|
201
|
+
}
|
|
202
|
+
if (requires.runtime?.node)
|
|
203
|
+
hasRuntime.node = true;
|
|
204
|
+
if (requires.runtime?.python)
|
|
205
|
+
hasRuntime.python = true;
|
|
206
|
+
return { envNames, cliNames, pipNames, npmNames, hasRuntime };
|
|
207
|
+
}
|
|
208
|
+
function findScripts(dir) {
|
|
209
|
+
const scripts = [];
|
|
210
|
+
if (!fs_1.default.existsSync(dir))
|
|
211
|
+
return scripts;
|
|
212
|
+
function walk(d) {
|
|
213
|
+
for (const entry of fs_1.default.readdirSync(d, { withFileTypes: true })) {
|
|
214
|
+
if (entry.name.startsWith('.'))
|
|
215
|
+
continue;
|
|
216
|
+
const full = path_1.default.join(d, entry.name);
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
walk(full);
|
|
219
|
+
}
|
|
220
|
+
else if (/\.(py|js|ts|mjs|sh)$/.test(entry.name)) {
|
|
221
|
+
scripts.push(full);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
walk(dir);
|
|
226
|
+
return scripts;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* μ μ νλͺ©μ μ¬λμ΄ μ½κΈ° μ’μ ννλ‘ ν¬λ§·νλ€.
|
|
230
|
+
*/
|
|
231
|
+
function formatSuggestions(suggestions) {
|
|
232
|
+
const lines = [];
|
|
233
|
+
const envs = suggestions.filter((s) => s.category === 'env');
|
|
234
|
+
const clis = suggestions.filter((s) => s.category === 'cli');
|
|
235
|
+
const pips = suggestions.filter((s) => s.category === 'pip');
|
|
236
|
+
const runtimes = suggestions.filter((s) => s.category === 'runtime');
|
|
237
|
+
if (envs.length > 0) {
|
|
238
|
+
lines.push(' νκ²½λ³μ:');
|
|
239
|
+
for (const e of envs) {
|
|
240
|
+
const hint = e.setup_hint ? ` (${e.setup_hint})` : '';
|
|
241
|
+
lines.push(` ${e.name}${hint} β ${e.source}μμ κ°μ§`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (clis.length > 0) {
|
|
245
|
+
lines.push(' CLI λꡬ:');
|
|
246
|
+
for (const c of clis)
|
|
247
|
+
lines.push(` ${c.name} β ${c.source}μμ κ°μ§`);
|
|
248
|
+
}
|
|
249
|
+
if (pips.length > 0) {
|
|
250
|
+
lines.push(' pip ν¨ν€μ§:');
|
|
251
|
+
for (const p of pips)
|
|
252
|
+
lines.push(` ${p.name} β ${p.source}μμ κ°μ§`);
|
|
253
|
+
}
|
|
254
|
+
if (runtimes.length > 0) {
|
|
255
|
+
lines.push(' λ°νμ:');
|
|
256
|
+
for (const r of runtimes)
|
|
257
|
+
lines.push(` ${r.name} β ${r.source}μμ κ°μ§`);
|
|
258
|
+
}
|
|
259
|
+
return lines;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* μ μ νλͺ©μ requires κ°μ²΄μ λ³ν©νλ€.
|
|
263
|
+
*/
|
|
264
|
+
function mergeIntoRequires(requires, suggestions) {
|
|
265
|
+
const result = { ...requires };
|
|
266
|
+
for (const s of suggestions) {
|
|
267
|
+
if (s.category === 'env') {
|
|
268
|
+
if (!result.env)
|
|
269
|
+
result.env = [];
|
|
270
|
+
result.env.push({
|
|
271
|
+
name: s.name,
|
|
272
|
+
required: s.required ?? true,
|
|
273
|
+
description: s.description,
|
|
274
|
+
setup_hint: s.setup_hint,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
else if (s.category === 'cli') {
|
|
278
|
+
if (!result.cli)
|
|
279
|
+
result.cli = [];
|
|
280
|
+
result.cli.push({
|
|
281
|
+
name: s.name,
|
|
282
|
+
install: s.install,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (s.category === 'runtime') {
|
|
286
|
+
if (!result.runtime)
|
|
287
|
+
result.runtime = {};
|
|
288
|
+
if (s.name === 'python')
|
|
289
|
+
result.runtime.python = '>=3.8';
|
|
290
|
+
if (s.name === 'node')
|
|
291
|
+
result.runtime.node = '>=18';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|