relayax-cli 0.4.28 → 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.
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
- export declare function installGlobalUserCommands(): {
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;
@@ -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) {
@@ -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
- // Scope 자동결정: --global/--local 플래그 > recommended_scope > agent_type 기반
206
- const scope = _opts.global ? 'global'
207
- : _opts.local ? 'local'
208
- : resolvedAgent.recommended_scope ?? (resolvedAgent.type === 'passive' ? 'local' : 'global');
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 = await (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`);
@@ -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): Promise<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
  */
@@ -26,33 +26,31 @@ const SYMLINK_DIRS = ['skills', 'commands', 'agents', 'rules'];
26
26
  * @param scope 'global' | 'local'
27
27
  * @param projectPath 프로젝트 루트 경로 (local scope 시 사용)
28
28
  */
29
- async function deploySymlinks(agentDir, scope, projectPath) {
29
+ async function deploySymlinks(agentDir, scope, projectPath, overrideTools) {
30
30
  const result = { symlinks: [], warnings: [] };
31
- // 감지된 AI tool 목록
32
- const tools = scope === 'global'
33
- ? (0, ai_tools_js_1.detectGlobalCLIs)()
34
- : (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
35
- // 글로벌: Claude Code를 기본으로 포함
36
- if (scope === 'global') {
37
- const hasClaudeCode = tools.some((t) => t.value === 'claude');
38
- if (!hasClaudeCode) {
39
- tools.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
40
- }
31
+ let tools;
32
+ if (overrideTools) {
33
+ tools = overrideTools;
41
34
  }
42
- // 로컬: AI tool 디렉토리가 없으면 TTY에서 선택
43
- if (scope === 'local' && tools.length === 0) {
44
- if (process.stdout.isTTY) {
45
- const { checkbox } = await import('@inquirer/prompts');
46
- const selected = await checkbox({
47
- message: `Select tools to set up (${ai_tools_js_1.AI_TOOLS.length} available)`,
48
- choices: ai_tools_js_1.AI_TOOLS.map((t) => ({ name: t.name, value: t })),
49
- });
50
- tools.push(...selected);
51
- }
52
- else {
53
- // Non-TTY (JSON 모드 등): Claude Code 기본
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')) {
54
40
  tools.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
55
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
+ }
56
54
  }
57
55
  for (const tool of tools) {
58
56
  const baseDir = scope === 'global'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.4.28",
3
+ "version": "0.4.29",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {