relayax-cli 0.4.27 → 0.4.29
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/init.d.ts +3 -2
- package/dist/commands/init.js +6 -3
- package/dist/commands/install.js +57 -12
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.js +23 -8
- package/dist/commands/update.js +1 -1
- package/dist/lib/installer.d.ts +2 -1
- package/dist/lib/installer.js +24 -10
- package/dist/prompts/create.md +14 -2
- package/package.json +1 -1
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
2
|
+
import { type AITool } from '../lib/ai-tools.js';
|
|
3
|
+
export declare function installGlobalUserCommands(overrideTools?: AITool[]): {
|
|
3
4
|
installed: boolean;
|
|
4
5
|
commands: string[];
|
|
5
6
|
tools: string[];
|
|
@@ -8,5 +9,5 @@ export declare function installGlobalUserCommands(): {
|
|
|
8
9
|
/**
|
|
9
10
|
* 글로벌 User 커맨드가 이미 설치되어 있는지 확인한다.
|
|
10
11
|
*/
|
|
11
|
-
export declare function hasGlobalUserCommands(): boolean;
|
|
12
|
+
export declare function hasGlobalUserCommands(overrideTools?: AITool[]): boolean;
|
|
12
13
|
export declare function registerInit(program: Command): void;
|
package/dist/commands/init.js
CHANGED
|
@@ -44,8 +44,8 @@ const LEGACY_COMMANDS = {
|
|
|
44
44
|
'relay-install': 'relay install (CLI) 또는 /relay-explore',
|
|
45
45
|
'relay-publish': 'relay publish --patch (CLI) 또는 /relay-create',
|
|
46
46
|
};
|
|
47
|
-
function installGlobalUserCommands() {
|
|
48
|
-
const globalCLIs = (0, ai_tools_js_1.detectGlobalCLIs)();
|
|
47
|
+
function installGlobalUserCommands(overrideTools) {
|
|
48
|
+
const globalCLIs = overrideTools ?? (0, ai_tools_js_1.detectGlobalCLIs)();
|
|
49
49
|
const currentIds = new Set(command_adapter_js_1.USER_COMMANDS.map((c) => c.id));
|
|
50
50
|
const commands = [];
|
|
51
51
|
const tools = [];
|
|
@@ -77,7 +77,10 @@ function installGlobalUserCommands() {
|
|
|
77
77
|
/**
|
|
78
78
|
* 글로벌 User 커맨드가 이미 설치되어 있는지 확인한다.
|
|
79
79
|
*/
|
|
80
|
-
function hasGlobalUserCommands() {
|
|
80
|
+
function hasGlobalUserCommands(overrideTools) {
|
|
81
|
+
if (overrideTools) {
|
|
82
|
+
return overrideTools.every((tool) => command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPathForTool)(tool.skillsDir, cmd.id))));
|
|
83
|
+
}
|
|
81
84
|
return command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
82
85
|
}
|
|
83
86
|
function registerInit(program) {
|
package/dist/commands/install.js
CHANGED
|
@@ -18,6 +18,7 @@ const paths_js_1 = require("../lib/paths.js");
|
|
|
18
18
|
const error_report_js_1 = require("../lib/error-report.js");
|
|
19
19
|
const step_tracker_js_1 = require("../lib/step-tracker.js");
|
|
20
20
|
const installer_js_1 = require("../lib/installer.js");
|
|
21
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
21
22
|
function registerInstall(program) {
|
|
22
23
|
program
|
|
23
24
|
.command('install <slug>')
|
|
@@ -30,13 +31,6 @@ function registerInstall(program) {
|
|
|
30
31
|
const json = program.opts().json ?? false;
|
|
31
32
|
const projectPath = (0, paths_js_1.resolveProjectPath)(_opts.project);
|
|
32
33
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
33
|
-
// Auto-init: 글로벌 커맨드가 없으면 자동 설치
|
|
34
|
-
if (!(0, init_js_1.hasGlobalUserCommands)()) {
|
|
35
|
-
if (!json) {
|
|
36
|
-
console.error('\x1b[33m⚙ 글로벌 커맨드를 자동 설치합니다...\x1b[0m');
|
|
37
|
-
}
|
|
38
|
-
(0, init_js_1.installGlobalUserCommands)();
|
|
39
|
-
}
|
|
40
34
|
(0, step_tracker_js_1.trackCommand)('install', { slug: slugInput });
|
|
41
35
|
try {
|
|
42
36
|
// Resolve scoped slug and fetch agent metadata
|
|
@@ -202,10 +196,61 @@ function registerInstall(program) {
|
|
|
202
196
|
throw new Error('에이전트 정보를 가져오지 못했습니다.');
|
|
203
197
|
// Re-bind as non-optional so TypeScript tracks the narrowing through nested scopes
|
|
204
198
|
let resolvedAgent = agent;
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
200
|
+
const interactive = isTTY && !json;
|
|
201
|
+
const defaultScope = resolvedAgent.recommended_scope ?? (resolvedAgent.type === 'passive' ? 'local' : 'global');
|
|
202
|
+
// ── Scope 결정: 플래그 > TTY prompt > 자동결정 ──
|
|
203
|
+
let scope;
|
|
204
|
+
if (_opts.global) {
|
|
205
|
+
scope = 'global';
|
|
206
|
+
}
|
|
207
|
+
else if (_opts.local) {
|
|
208
|
+
scope = 'local';
|
|
209
|
+
}
|
|
210
|
+
else if (interactive) {
|
|
211
|
+
const { select } = await import('@inquirer/prompts');
|
|
212
|
+
scope = await select({
|
|
213
|
+
message: '설치 범위를 선택하세요',
|
|
214
|
+
choices: [
|
|
215
|
+
{ name: '글로벌 (~/.relay/agents/) — 모든 프로젝트에서 사용', value: 'global' },
|
|
216
|
+
{ name: '로컬 (./.relay/agents/) — 이 프로젝트에서만 사용', value: 'local' },
|
|
217
|
+
],
|
|
218
|
+
default: defaultScope,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
scope = defaultScope;
|
|
223
|
+
}
|
|
224
|
+
// ── AI tools 선택: 감지된 건 pre-checked, 나머지는 선택 가능 ──
|
|
225
|
+
let selectedTools;
|
|
226
|
+
if (interactive) {
|
|
227
|
+
const detected = scope === 'global'
|
|
228
|
+
? (0, ai_tools_js_1.detectGlobalCLIs)()
|
|
229
|
+
: (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
230
|
+
if (scope === 'global' && !detected.some((t) => t.value === 'claude')) {
|
|
231
|
+
detected.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
|
|
232
|
+
}
|
|
233
|
+
const detectedValues = new Set(detected.map((t) => t.value));
|
|
234
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
235
|
+
selectedTools = await checkbox({
|
|
236
|
+
message: '설치할 AI 도구를 선택하세요 (감지된 도구는 자동 선택됨)',
|
|
237
|
+
choices: ai_tools_js_1.AI_TOOLS.map((t) => ({
|
|
238
|
+
name: t.name,
|
|
239
|
+
value: t,
|
|
240
|
+
checked: detectedValues.has(t.value),
|
|
241
|
+
})),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// ── init 통합: 선택된 tools에 글로벌 commands 설치 ──
|
|
245
|
+
if (selectedTools) {
|
|
246
|
+
(0, init_js_1.installGlobalUserCommands)(selectedTools);
|
|
247
|
+
}
|
|
248
|
+
else if (!(0, init_js_1.hasGlobalUserCommands)()) {
|
|
249
|
+
if (!json) {
|
|
250
|
+
console.error('\x1b[33m⚙ 글로벌 커맨드를 자동 설치합니다...\x1b[0m');
|
|
251
|
+
}
|
|
252
|
+
(0, init_js_1.installGlobalUserCommands)();
|
|
253
|
+
}
|
|
209
254
|
const agentDir = scope === 'global'
|
|
210
255
|
? path_1.default.join(os_1.default.homedir(), '.relay', 'agents', parsed.owner, parsed.name)
|
|
211
256
|
: path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
@@ -256,7 +301,7 @@ function registerInstall(program) {
|
|
|
256
301
|
// 4.5. Inject preamble (update check) into SKILL.md and commands
|
|
257
302
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
258
303
|
// 5. Deploy symlinks to detected AI tool directories
|
|
259
|
-
const deploy = (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath);
|
|
304
|
+
const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath, selectedTools);
|
|
260
305
|
for (const w of deploy.warnings) {
|
|
261
306
|
if (!json)
|
|
262
307
|
console.error(`\x1b[33m${w}\x1b[0m`);
|
|
@@ -68,6 +68,7 @@ export interface PublishMetadata {
|
|
|
68
68
|
requires?: Requires;
|
|
69
69
|
visibility?: 'public' | 'private' | 'internal';
|
|
70
70
|
type?: 'command' | 'passive' | 'hybrid';
|
|
71
|
+
recommended_scope?: 'global' | 'local';
|
|
71
72
|
cli_version?: string;
|
|
72
73
|
agent_names?: string[];
|
|
73
74
|
skill_names?: string[];
|
package/dist/commands/publish.js
CHANGED
|
@@ -45,6 +45,7 @@ function parseRelayYaml(content) {
|
|
|
45
45
|
requires,
|
|
46
46
|
visibility,
|
|
47
47
|
type,
|
|
48
|
+
recommended_scope: raw.recommended_scope === 'global' ? 'global' : raw.recommended_scope === 'local' ? 'local' : undefined,
|
|
48
49
|
source: raw.source ? String(raw.source) : undefined,
|
|
49
50
|
org_slug: raw.org_slug ? String(raw.org_slug) : undefined,
|
|
50
51
|
};
|
|
@@ -268,6 +269,7 @@ function registerPublish(program) {
|
|
|
268
269
|
.option('--token <token>', '인증 토큰')
|
|
269
270
|
.option('--space <slug>', '배포할 Space 지정')
|
|
270
271
|
.option('--org <slug>', 'Organization slug 지정')
|
|
272
|
+
.option('--no-org', '개인 계정으로 배포 (Organization 무시)')
|
|
271
273
|
.option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
|
|
272
274
|
.option('--patch', 'patch 버전 범프')
|
|
273
275
|
.option('--minor', 'minor 버전 범프')
|
|
@@ -470,10 +472,18 @@ function registerPublish(program) {
|
|
|
470
472
|
try {
|
|
471
473
|
const { fetchMyOrgs } = await import('./orgs.js');
|
|
472
474
|
const orgs = await fetchMyOrgs(token);
|
|
475
|
+
// --no-org: skip org selection entirely (personal deployment)
|
|
476
|
+
const skipOrg = opts.noOrg === true;
|
|
473
477
|
// Determine explicit org slug: --org > --space (legacy) > relay.yaml org_slug
|
|
474
|
-
const explicitOrgSlug = opts.org ?? opts.space ?? config.org_slug;
|
|
478
|
+
const explicitOrgSlug = skipOrg ? undefined : (opts.org ?? opts.space ?? config.org_slug);
|
|
475
479
|
// --org / --space / relay.yaml org_slug: resolve Org by slug
|
|
476
|
-
if (
|
|
480
|
+
if (skipOrg) {
|
|
481
|
+
// Personal deployment — no org
|
|
482
|
+
if (!json) {
|
|
483
|
+
console.error('\x1b[2m 개인 계정으로 배포합니다.\x1b[0m\n');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else if (explicitOrgSlug) {
|
|
477
487
|
const matched = orgs.find((o) => o.slug === explicitOrgSlug);
|
|
478
488
|
if (matched) {
|
|
479
489
|
selectedOrgId = matched.id;
|
|
@@ -528,18 +538,22 @@ function registerPublish(program) {
|
|
|
528
538
|
console.error(` → Organization: ${chosenLabel}\n`);
|
|
529
539
|
}
|
|
530
540
|
}
|
|
531
|
-
else if (orgs.length >
|
|
532
|
-
// --json 모드 +
|
|
533
|
-
(0, error_report_js_1.reportCliError)('publish', 'MISSING_ORG',
|
|
541
|
+
else if (orgs.length > 0 && json) {
|
|
542
|
+
// --json 모드 + Org 있음: 에이전트가 선택할 수 있도록 에러 반환
|
|
543
|
+
(0, error_report_js_1.reportCliError)('publish', 'MISSING_ORG', `${orgs.length} orgs, none selected`);
|
|
534
544
|
console.error(JSON.stringify({
|
|
535
545
|
error: 'MISSING_ORG',
|
|
536
|
-
message: '
|
|
537
|
-
fix:
|
|
538
|
-
options:
|
|
546
|
+
message: '배포 대상을 선택하세요.',
|
|
547
|
+
fix: `개인 배포: relay publish --no-org --json / Org 배포: relay publish --org <slug> --json`,
|
|
548
|
+
options: [
|
|
549
|
+
{ value: '__personal__', label: '개인 계정으로 배포' },
|
|
550
|
+
...orgs.map((o) => ({ value: o.slug, label: `${o.name} (${o.slug})` })),
|
|
551
|
+
],
|
|
539
552
|
}));
|
|
540
553
|
process.exit(1);
|
|
541
554
|
}
|
|
542
555
|
else if (orgs.length > 0) {
|
|
556
|
+
// non-json, non-TTY fallback (rare) — auto-select first org
|
|
543
557
|
selectedOrgId = orgs[0].id;
|
|
544
558
|
selectedOrgSlug = orgs[0].slug;
|
|
545
559
|
}
|
|
@@ -691,6 +705,7 @@ function registerPublish(program) {
|
|
|
691
705
|
agent_names: listDir(relayDir, 'agents'),
|
|
692
706
|
skill_names: listDir(relayDir, 'skills'),
|
|
693
707
|
type: config.type ?? 'hybrid',
|
|
708
|
+
recommended_scope: config.recommended_scope,
|
|
694
709
|
agent_details: detectedAgents,
|
|
695
710
|
skill_details: detectedSkills,
|
|
696
711
|
...(selectedOrgId ? { org_id: selectedOrgId } : {}),
|
package/dist/commands/update.js
CHANGED
|
@@ -98,7 +98,7 @@ function registerUpdate(program) {
|
|
|
98
98
|
// Inject preamble
|
|
99
99
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
100
100
|
// Deploy symlinks (always — handles migration from legacy deployed_files)
|
|
101
|
-
const deploy = (0, installer_js_1.deploySymlinks)(agentDir, currentScope, projectPath);
|
|
101
|
+
const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, currentScope, projectPath);
|
|
102
102
|
// Update installed.json
|
|
103
103
|
const installRecord = {
|
|
104
104
|
agent_id: agent.id,
|
package/dist/lib/installer.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AITool } from './ai-tools.js';
|
|
1
2
|
export interface DeployResult {
|
|
2
3
|
symlinks: string[];
|
|
3
4
|
warnings: string[];
|
|
@@ -11,7 +12,7 @@ export interface DeployResult {
|
|
|
11
12
|
* @param scope 'global' | 'local'
|
|
12
13
|
* @param projectPath 프로젝트 루트 경로 (local scope 시 사용)
|
|
13
14
|
*/
|
|
14
|
-
export declare function deploySymlinks(agentDir: string, scope: 'global' | 'local', projectPath: string): DeployResult
|
|
15
|
+
export declare function deploySymlinks(agentDir: string, scope: 'global' | 'local', projectPath: string, overrideTools?: AITool[]): Promise<DeployResult>;
|
|
15
16
|
/**
|
|
16
17
|
* symlink 목록을 기반으로 symlink를 제거한다.
|
|
17
18
|
*/
|
package/dist/lib/installer.js
CHANGED
|
@@ -26,18 +26,31 @@ const SYMLINK_DIRS = ['skills', 'commands', 'agents', 'rules'];
|
|
|
26
26
|
* @param scope 'global' | 'local'
|
|
27
27
|
* @param projectPath 프로젝트 루트 경로 (local scope 시 사용)
|
|
28
28
|
*/
|
|
29
|
-
function deploySymlinks(agentDir, scope, projectPath) {
|
|
29
|
+
async function deploySymlinks(agentDir, scope, projectPath, overrideTools) {
|
|
30
30
|
const result = { symlinks: [], warnings: [] };
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
let tools;
|
|
32
|
+
if (overrideTools) {
|
|
33
|
+
tools = overrideTools;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
tools = scope === 'global'
|
|
37
|
+
? (0, ai_tools_js_1.detectGlobalCLIs)()
|
|
38
|
+
: (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
39
|
+
if (scope === 'global' && !tools.some((t) => t.value === 'claude')) {
|
|
39
40
|
tools.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
|
|
40
41
|
}
|
|
42
|
+
if (scope === 'local' && tools.length === 0) {
|
|
43
|
+
if (process.stdout.isTTY) {
|
|
44
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
45
|
+
tools = await checkbox({
|
|
46
|
+
message: `Select tools to set up (${ai_tools_js_1.AI_TOOLS.length} available)`,
|
|
47
|
+
choices: ai_tools_js_1.AI_TOOLS.map((t) => ({ name: t.name, value: t })),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
tools = [{ name: 'Claude Code', value: 'claude', skillsDir: '.claude' }];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
41
54
|
}
|
|
42
55
|
for (const tool of tools) {
|
|
43
56
|
const baseDir = scope === 'global'
|
|
@@ -75,7 +88,8 @@ function deploySymlinks(agentDir, scope, projectPath) {
|
|
|
75
88
|
continue;
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
|
-
|
|
91
|
+
const relativeSrc = path_1.default.relative(path_1.default.dirname(destPath), srcPath);
|
|
92
|
+
fs_1.default.symlinkSync(relativeSrc, destPath);
|
|
79
93
|
result.symlinks.push(destPath);
|
|
80
94
|
}
|
|
81
95
|
}
|
package/dist/prompts/create.md
CHANGED
|
@@ -110,8 +110,17 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
|
|
|
110
110
|
- name, slug, description, version, tags
|
|
111
111
|
- requires (판단 결과)
|
|
112
112
|
- org, visibility
|
|
113
|
+
- **recommended_scope** — 설치 시 기본 배치 범위:
|
|
114
|
+
- `local` — rules/ 디렉토리가 있거나 프레임워크 특화 태그(nextjs, react, vue, angular, svelte, nuxt, remix, astro, django, rails, laravel, spring, express, fastapi, flask)가 있을 때
|
|
115
|
+
- `global` — 그 외 범용 도구
|
|
113
116
|
|
|
114
|
-
**사용자에게 질문하여 최종 확인** 후
|
|
117
|
+
**사용자에게 질문하여 최종 확인** 후 배포합니다.
|
|
118
|
+
|
|
119
|
+
배포 명령어는 사용자의 선택에 따라 다릅니다:
|
|
120
|
+
- **개인 배포**: `relay publish --no-org --json`
|
|
121
|
+
- **Org 배포**: `relay publish --org {org_slug} --json`
|
|
122
|
+
|
|
123
|
+
⚠️ `relay publish --json`만 실행하면 org 선택 에러가 발생합니다. 반드시 `--no-org` 또는 `--org`를 명시하세요.
|
|
115
124
|
|
|
116
125
|
---
|
|
117
126
|
|
|
@@ -140,7 +149,10 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
|
|
|
140
149
|
### 3. 배포
|
|
141
150
|
|
|
142
151
|
변경 요약을 보여주고 **사용자에게 질문하여 최종 확인** 후 배포합니다.
|
|
143
|
-
|
|
152
|
+
|
|
153
|
+
배포 명령어는 사용자의 선택(또는 기존 relay.yaml 설정)에 따라:
|
|
154
|
+
- **개인 배포**: `relay publish --no-org --json`
|
|
155
|
+
- **Org 배포**: `relay publish --org {org_slug} --json`
|
|
144
156
|
버전 범프가 필요하면 사용자에게 질문하여 patch/minor/major 중 확인합니다.
|
|
145
157
|
|
|
146
158
|
---
|