relayax-cli 0.1.91 → 0.1.93

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.
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerCreate(program: Command): void;
@@ -0,0 +1,138 @@
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.registerCreate = registerCreate;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const js_yaml_1 = __importDefault(require("js-yaml"));
10
+ const ai_tools_js_1 = require("../lib/ai-tools.js");
11
+ const command_adapter_js_1 = require("../lib/command-adapter.js");
12
+ const DEFAULT_DIRS = ['skills', 'commands'];
13
+ /**
14
+ * 글로벌 User 커맨드가 없으면 설치한다.
15
+ */
16
+ function ensureGlobalUserCommands() {
17
+ const allExist = command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
18
+ if (allExist)
19
+ return false;
20
+ const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
21
+ fs_1.default.mkdirSync(globalDir, { recursive: true });
22
+ for (const cmd of command_adapter_js_1.USER_COMMANDS) {
23
+ fs_1.default.writeFileSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id), (0, command_adapter_js_1.formatCommandFile)(cmd));
24
+ }
25
+ return true;
26
+ }
27
+ function registerCreate(program) {
28
+ program
29
+ .command('create <name>')
30
+ .description('새 에이전트 팀 프로젝트를 생성합니다')
31
+ .action(async (name) => {
32
+ const json = program.opts().json ?? false;
33
+ const projectPath = process.cwd();
34
+ const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
35
+ const isTTY = Boolean(process.stdin.isTTY) && !json;
36
+ // 1. relay.yaml 이미 존재하면 에러
37
+ if (fs_1.default.existsSync(relayYamlPath)) {
38
+ if (json) {
39
+ console.error(JSON.stringify({ error: 'ALREADY_EXISTS', message: 'relay.yaml이 이미 존재합니다.' }));
40
+ }
41
+ else {
42
+ console.error('relay.yaml이 이미 존재합니다. 기존 팀 프로젝트에서는 `relay init`을 사용하세요.');
43
+ }
44
+ process.exit(1);
45
+ }
46
+ // 2. 메타데이터 수집
47
+ const defaultSlug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
48
+ let description = '';
49
+ let tags = [];
50
+ let visibility = 'public';
51
+ if (isTTY) {
52
+ const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
53
+ console.log(`\n \x1b[33m⚡\x1b[0m \x1b[1mrelay create\x1b[0m — 새 팀 프로젝트\n`);
54
+ description = await promptInput({
55
+ message: '팀 설명:',
56
+ validate: (v) => v.trim().length > 0 ? true : '설명을 입력해주세요.',
57
+ });
58
+ const tagsRaw = await promptInput({
59
+ message: '태그 (쉼표로 구분, 선택):',
60
+ default: '',
61
+ });
62
+ tags = tagsRaw.split(',').map((t) => t.trim()).filter(Boolean);
63
+ visibility = await promptSelect({
64
+ message: '공개 범위:',
65
+ choices: [
66
+ { name: '전체 공개', value: 'public' },
67
+ { name: '로그인 사용자만', value: 'login-only' },
68
+ { name: '초대 코드 필요', value: 'invite-only' },
69
+ ],
70
+ });
71
+ }
72
+ // 3. relay.yaml 생성
73
+ const yamlData = {
74
+ name,
75
+ slug: defaultSlug,
76
+ description,
77
+ version: '1.0.0',
78
+ tags,
79
+ visibility,
80
+ };
81
+ fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
82
+ // 4. 디렉토리 구조 생성
83
+ const createdDirs = [];
84
+ for (const dir of DEFAULT_DIRS) {
85
+ const dirPath = path_1.default.join(projectPath, dir);
86
+ if (!fs_1.default.existsSync(dirPath)) {
87
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
88
+ createdDirs.push(dir);
89
+ }
90
+ }
91
+ // 5. 로컬 Builder 슬래시 커맨드 설치
92
+ const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
93
+ const localResults = [];
94
+ for (const tool of detected) {
95
+ const adapter = (0, command_adapter_js_1.createAdapter)(tool);
96
+ const installed = [];
97
+ for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
98
+ const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
99
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
100
+ fs_1.default.writeFileSync(filePath, adapter.formatFile(cmd));
101
+ installed.push(cmd.id);
102
+ }
103
+ localResults.push({ tool: tool.name, commands: installed });
104
+ }
105
+ // 6. 글로벌 User 커맨드 (없으면 설치)
106
+ const globalInstalled = ensureGlobalUserCommands();
107
+ // 7. 출력
108
+ if (json) {
109
+ console.log(JSON.stringify({
110
+ status: 'ok',
111
+ name,
112
+ slug: defaultSlug,
113
+ relay_yaml: 'created',
114
+ directories: createdDirs,
115
+ local_commands: localResults,
116
+ global_commands: globalInstalled ? 'installed' : 'already',
117
+ }));
118
+ }
119
+ else {
120
+ console.log(`\n\x1b[32m✓ ${name} 팀 프로젝트 생성 완료\x1b[0m\n`);
121
+ console.log(` relay.yaml 생성됨`);
122
+ if (createdDirs.length > 0) {
123
+ console.log(` 디렉토리 생성: ${createdDirs.join(', ')}`);
124
+ }
125
+ if (localResults.length > 0) {
126
+ console.log(`\n \x1b[36mBuilder 커맨드 (로컬)\x1b[0m`);
127
+ for (const r of localResults) {
128
+ console.log(` ${r.tool}: ${r.commands.map((c) => `/${c}`).join(', ')}`);
129
+ }
130
+ }
131
+ if (globalInstalled) {
132
+ console.log(`\n \x1b[36mUser 커맨드 (글로벌)\x1b[0m — 설치됨`);
133
+ }
134
+ console.log(`\n 다음 단계: \x1b[33m/relay-publish\x1b[0m로 마켓플레이스에 배포`);
135
+ console.log(' IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.\n');
136
+ }
137
+ });
138
+ }
@@ -28,11 +28,13 @@ function showWelcome() {
28
28
  ' \x1b[33m⚡\x1b[0m \x1b[1mrelay\x1b[0m — Agent Team Marketplace',
29
29
  '',
30
30
  ' 에이전트 CLI에 relay 커맨드를 연결합니다.',
31
- ' 설치 후 에이전트가 팀을 탐색하고 설치할 수 있습니다.',
32
31
  '',
33
- ' \x1b[2m/relay-explore\x1b[0m 마켓플레이스 탐색',
34
- ' \x1b[2m/relay-install\x1b[0m 팀 설치',
35
- ' \x1b[2m/relay-publish\x1b[0m 배포',
32
+ ' \x1b[2mUser 커맨드 (글로벌)\x1b[0m',
33
+ ' /relay-explore 마켓플레이스 탐색',
34
+ ' /relay-install 설치',
35
+ ' /relay-list 설치된 팀 목록',
36
+ ' /relay-update 팀 업데이트',
37
+ ' /relay-uninstall 팀 삭제',
36
38
  '',
37
39
  ];
38
40
  console.log(lines.join('\n'));
@@ -54,10 +56,44 @@ async function selectToolsInteractively(detectedIds) {
54
56
  });
55
57
  return selected;
56
58
  }
59
+ /**
60
+ * 글로벌 User 커맨드를 ~/.claude/commands/relay/에 설치한다.
61
+ */
62
+ function installGlobalUserCommands() {
63
+ const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
64
+ fs_1.default.mkdirSync(globalDir, { recursive: true });
65
+ const commands = [];
66
+ for (const cmd of command_adapter_js_1.USER_COMMANDS) {
67
+ const filePath = (0, command_adapter_js_1.getGlobalCommandPath)(cmd.id);
68
+ fs_1.default.writeFileSync(filePath, (0, command_adapter_js_1.formatCommandFile)(cmd));
69
+ commands.push(cmd.id);
70
+ }
71
+ return { installed: true, commands };
72
+ }
73
+ /**
74
+ * 글로벌 User 커맨드가 이미 설치되어 있는지 확인한다.
75
+ */
76
+ function hasGlobalUserCommands() {
77
+ return command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
78
+ }
79
+ /**
80
+ * 팀 프로젝트인지 감지한다 (relay.yaml 또는 팀 디렉토리 구조).
81
+ */
82
+ function isTeamProject(projectPath) {
83
+ if (fs_1.default.existsSync(path_1.default.join(projectPath, 'relay.yaml'))) {
84
+ return true;
85
+ }
86
+ return VALID_TEAM_DIRS.some((d) => {
87
+ const dirPath = path_1.default.join(projectPath, d);
88
+ if (!fs_1.default.existsSync(dirPath))
89
+ return false;
90
+ return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
91
+ });
92
+ }
57
93
  function registerInit(program) {
58
94
  program
59
95
  .command('init')
60
- .description('에이전트 CLI 감지하고 relay 슬래시 커맨드를 설치합니다')
96
+ .description('에이전트 CLI relay 슬래시 커맨드를 설치합니다')
61
97
  .option('--tools <tools>', '설치할 에이전트 CLI 지정 (all 또는 쉼표 구분)')
62
98
  .option('--update', '이미 설치된 슬래시 커맨드를 최신 버전으로 업데이트')
63
99
  .action(async (opts) => {
@@ -65,118 +101,127 @@ function registerInit(program) {
65
101
  const projectPath = process.cwd();
66
102
  const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
67
103
  const detectedIds = new Set(detected.map((t) => t.value));
68
- // 1. 도구 선택
69
- let targetToolIds;
70
- if (opts.update) {
71
- // --update: 기존에 relay 커맨드가 설치된 CLI만 찾아서 덮어쓰기
72
- const installed = detected.filter((tool) => {
73
- const cmdDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
74
- return fs_1.default.existsSync(cmdDir);
75
- });
76
- if (installed.length === 0) {
77
- if (json) {
78
- console.error(JSON.stringify({ error: 'NO_RELAY_COMMANDS', message: '설치된 relay 슬래시 커맨드가 없습니다. relay init을 먼저 실행하세요.' }));
79
- }
80
- else {
81
- console.error('설치된 relay 슬래시 커맨드가 없습니다. relay init을 먼저 실행하세요.');
104
+ const isBuilder = isTeamProject(projectPath);
105
+ // ── 1. 글로벌 User 커맨드 설치 ──
106
+ let globalStatus = 'already';
107
+ if (opts.update || !hasGlobalUserCommands()) {
108
+ installGlobalUserCommands();
109
+ globalStatus = opts.update ? 'updated' : 'installed';
110
+ }
111
+ // ── 2. 로컬 Builder 커맨드 (팀 프로젝트인 경우) ──
112
+ const localResults = [];
113
+ if (isBuilder) {
114
+ // 도구 선택
115
+ let targetToolIds;
116
+ if (opts.update) {
117
+ const installed = detected.filter((tool) => {
118
+ const cmdDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
119
+ return fs_1.default.existsSync(cmdDir);
120
+ });
121
+ if (installed.length === 0 && !json) {
122
+ console.error('로컬에 설치된 relay Builder 커맨드가 없습니다.');
82
123
  }
83
- process.exit(1);
124
+ targetToolIds = installed.map((t) => t.value);
84
125
  }
85
- targetToolIds = installed.map((t) => t.value);
86
- }
87
- else if (opts.tools) {
88
- // --tools 옵션: 비대화형
89
- targetToolIds = resolveTools(opts.tools);
90
- }
91
- else if (!json && process.stdin.isTTY) {
92
- // 기본(human) + TTY: 대화형 UI
93
- showWelcome();
94
- if (detected.length > 0) {
95
- console.log(` 감지된 에이전트 CLI: \x1b[36m${detected.map((t) => t.name).join(', ')}\x1b[0m\n`);
126
+ else if (opts.tools) {
127
+ targetToolIds = resolveTools(opts.tools);
96
128
  }
97
- targetToolIds = await selectToolsInteractively(detectedIds);
98
- if (targetToolIds.length === 0) {
99
- console.log('\n 선택된 도구가 없습니다.');
100
- return;
129
+ else if (!json && process.stdin.isTTY) {
130
+ showWelcome();
131
+ if (detected.length > 0) {
132
+ console.log(` 감지된 에이전트 CLI: \x1b[36m${detected.map((t) => t.name).join(', ')}\x1b[0m\n`);
133
+ }
134
+ console.log(' \x1b[2mBuilder 프로젝트 감지 → 로컬 Builder 커맨드도 설치합니다.\x1b[0m\n');
135
+ targetToolIds = await selectToolsInteractively(detectedIds);
136
+ if (targetToolIds.length === 0) {
137
+ console.log('\n 선택된 도구가 없습니다.');
138
+ // 글로벌은 이미 설치됨
139
+ if (globalStatus === 'installed') {
140
+ console.log(' 글로벌 User 커맨드는 설치되었습니다.\n');
141
+ }
142
+ return;
143
+ }
101
144
  }
102
- }
103
- else {
104
- // --json 모드 또는 비TTY (에이전트가 호출): 감지된 것만 자동 사용
105
- if (detected.length === 0) {
106
- console.error(JSON.stringify({
107
- error: 'NO_AGENT_CLI',
108
- message: '에이전트 CLI 디렉토리를 찾을 수 없습니다. --tools 옵션으로 지정하세요.',
109
- }));
110
- process.exit(1);
145
+ else {
146
+ if (detected.length === 0) {
147
+ if (json) {
148
+ console.error(JSON.stringify({
149
+ error: 'NO_AGENT_CLI',
150
+ message: '에이전트 CLI 디렉토리를 찾을 수 없습니다. --tools 옵션으로 지정하세요.',
151
+ }));
152
+ }
153
+ // 글로벌은 설치했으므로 에러로 종료하지 않음
154
+ targetToolIds = [];
155
+ }
156
+ else {
157
+ targetToolIds = detected.map((t) => t.value);
158
+ }
111
159
  }
112
- targetToolIds = detected.map((t) => t.value);
113
- }
114
- // 2. 에이전트 CLI에 슬래시 커맨드 설치
115
- const results = [];
116
- for (const toolId of targetToolIds) {
117
- const tool = ai_tools_js_1.AI_TOOLS.find((t) => t.value === toolId);
118
- if (!tool)
119
- continue;
120
- const adapter = (0, command_adapter_js_1.createAdapter)(tool);
121
- const installedCommands = [];
122
- for (const cmd of command_adapter_js_1.RELAY_COMMANDS) {
123
- const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
124
- const fileContent = adapter.formatFile(cmd);
125
- fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
126
- fs_1.default.writeFileSync(filePath, fileContent);
127
- installedCommands.push(cmd.id);
160
+ // Builder 커맨드 설치
161
+ for (const toolId of targetToolIds) {
162
+ const tool = ai_tools_js_1.AI_TOOLS.find((t) => t.value === toolId);
163
+ if (!tool)
164
+ continue;
165
+ const adapter = (0, command_adapter_js_1.createAdapter)(tool);
166
+ const installedCommands = [];
167
+ for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
168
+ const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
169
+ const fileContent = adapter.formatFile(cmd);
170
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
171
+ fs_1.default.writeFileSync(filePath, fileContent);
172
+ installedCommands.push(cmd.id);
173
+ }
174
+ localResults.push({ tool: tool.name, commands: installedCommands });
128
175
  }
129
- results.push({ tool: tool.name, commands: installedCommands });
130
176
  }
131
- // 3. relay.yaml 생성 ( 패키지 구조가 있는 경우)
132
- const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
133
- let relayYamlStatus = 'skipped';
134
- if (fs_1.default.existsSync(relayYamlPath)) {
135
- relayYamlStatus = 'exists';
136
- }
137
- else {
138
- const hasTeamDirs = VALID_TEAM_DIRS.some((d) => {
139
- const dirPath = path_1.default.join(projectPath, d);
140
- if (!fs_1.default.existsSync(dirPath))
141
- return false;
142
- return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
143
- });
144
- if (hasTeamDirs) {
145
- const dirName = path_1.default.basename(projectPath);
146
- const yaml = [
147
- `name: "${dirName}"`,
148
- `slug: "${dirName}"`,
149
- `description: ""`,
150
- `version: "1.0.0"`,
151
- `tags: []`,
152
- ].join('\n') + '\n';
153
- fs_1.default.writeFileSync(relayYamlPath, yaml);
154
- relayYamlStatus = 'created';
155
- }
177
+ else if (!json && process.stdin.isTTY) {
178
+ // User 모드: 글로벌만 설치, 안내 표시
179
+ showWelcome();
156
180
  }
157
- // 4. 출력
181
+ // ── 3. 출력 ──
158
182
  if (json) {
159
183
  console.log(JSON.stringify({
160
184
  status: 'ok',
161
- tools: results,
162
- relay_yaml: relayYamlStatus,
185
+ mode: isBuilder ? 'builder' : 'user',
186
+ global: {
187
+ status: globalStatus,
188
+ path: (0, command_adapter_js_1.getGlobalCommandDir)(),
189
+ commands: command_adapter_js_1.USER_COMMANDS.map((c) => c.id),
190
+ },
191
+ local: isBuilder ? localResults : undefined,
163
192
  }));
164
193
  }
165
194
  else {
166
- console.log(`\n\x1b[32m✓ relay ${opts.update ? '슬래시 커맨드 업데이트' : '초기화'} 완료\x1b[0m\n`);
167
- for (const r of results) {
168
- console.log(` \x1b[36m${r.tool}\x1b[0m`);
169
- for (const cmd of r.commands) {
170
- console.log(` /${cmd}`);
195
+ console.log(`\n\x1b[32m✓ relay ${opts.update ? '업데이트' : '초기화'} 완료\x1b[0m\n`);
196
+ // 글로벌
197
+ if (globalStatus !== 'already') {
198
+ console.log(` \x1b[36mUser 커맨드 (글로벌)\x1b[0m ${globalStatus === 'updated' ? '업데이트됨' : '설치됨'}`);
199
+ console.log(` ${(0, command_adapter_js_1.getGlobalCommandDir)()}`);
200
+ for (const cmd of command_adapter_js_1.USER_COMMANDS) {
201
+ console.log(` /${cmd.id}`);
171
202
  }
203
+ console.log();
204
+ }
205
+ else {
206
+ console.log(` \x1b[36mUser 커맨드 (글로벌)\x1b[0m — 이미 설치됨`);
207
+ console.log();
172
208
  }
173
- if (relayYamlStatus === 'created') {
174
- console.log(`\n relay.yaml 생성됨 (팀 패키지 구조 감지)`);
209
+ // 로컬 Builder
210
+ if (localResults.length > 0) {
211
+ console.log(` \x1b[36mBuilder 커맨드 (로컬)\x1b[0m`);
212
+ for (const r of localResults) {
213
+ console.log(` ${r.tool}`);
214
+ for (const cmd of r.commands) {
215
+ console.log(` /${cmd}`);
216
+ }
217
+ }
218
+ console.log();
175
219
  }
176
- else if (relayYamlStatus === 'exists') {
177
- console.log(`\n relay.yaml 이미 존재`);
220
+ if (!isBuilder) {
221
+ console.log(' 팀을 만들려면 \x1b[33mrelay create <name>\x1b[0m을 사용하세요.');
222
+ console.log();
178
223
  }
179
- console.log('\n IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.');
224
+ console.log(' IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.');
180
225
  }
181
226
  });
182
227
  }
@@ -9,8 +9,7 @@ function registerInstall(program) {
9
9
  program
10
10
  .command('install <slug>')
11
11
  .description('에이전트 팀 설치 (감지된 에이전트 CLI에 설치)')
12
- .option('--path <install_path>', '설치 경로 지정 (기본: ./.claude)')
13
- .option('--code <code>', '초대 코드 (invite-only 팀 설치 시 필요)')
12
+ .option('--path <install_path>', '설치 경로 지정')
14
13
  .action(async (slug, opts) => {
15
14
  const json = program.opts().json ?? false;
16
15
  const installPath = (0, config_js_1.getInstallPath)(opts.path);
@@ -23,17 +22,17 @@ function registerInstall(program) {
23
22
  if (visibility === 'login-only') {
24
23
  const token = (0, config_js_1.loadToken)();
25
24
  if (!token) {
26
- const err = { error: 'LOGIN_REQUIRED', visibility: 'login-only', slug, message: '이 팀은 로그인이 필요합니다.' };
27
- console.error(JSON.stringify(err));
25
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'login-only', slug, message: '이 팀은 로그인이 필요합니다.' }));
28
26
  process.exit(1);
29
27
  }
30
28
  }
31
29
  else if (visibility === 'invite-only') {
32
- if (!opts.code) {
33
- const err = { error: 'INVITE_REQUIRED', visibility: 'invite-only', slug, message: '초대 코드가 필요합니다.' };
34
- console.error(JSON.stringify(err));
30
+ const token = (0, config_js_1.loadToken)();
31
+ if (!token) {
32
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'invite-only', slug, message: '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.' }));
35
33
  process.exit(1);
36
34
  }
35
+ // 실제 초대 여부는 서버(install API)에서 체크
37
36
  }
38
37
  // 3. Download package
39
38
  const tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
@@ -51,7 +50,7 @@ function registerInstall(program) {
51
50
  };
52
51
  (0, config_js_1.saveInstalled)(installed);
53
52
  // 7. Report install (non-blocking)
54
- await (0, api_js_1.reportInstall)(slug, opts.code);
53
+ await (0, api_js_1.reportInstall)(slug);
55
54
  const result = {
56
55
  status: 'ok',
57
56
  team: team.name,
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerInvite(program: Command): void;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerInvite = registerInvite;
4
+ const config_js_1 = require("../lib/config.js");
5
+ function registerInvite(program) {
6
+ const inviteCmd = program
7
+ .command('invite <slug>')
8
+ .description('invite-only 팀에 사용자를 초대합니다');
9
+ inviteCmd
10
+ .command('add <username>')
11
+ .description('사용자 초대')
12
+ .action(async (username) => {
13
+ const json = program.opts().json ?? false;
14
+ const slug = inviteCmd.args[0];
15
+ const token = (0, config_js_1.loadToken)();
16
+ if (!token) {
17
+ console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다. `relay login`을 먼저 실행하세요.' }));
18
+ process.exit(1);
19
+ }
20
+ try {
21
+ const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Authorization': `Bearer ${token}`,
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ body: JSON.stringify({ username }),
28
+ });
29
+ const body = await res.json();
30
+ if (!res.ok) {
31
+ if (json) {
32
+ console.error(JSON.stringify(body));
33
+ }
34
+ else {
35
+ console.error(`오류: ${body.message ?? '초대에 실패했습니다'}`);
36
+ }
37
+ process.exit(1);
38
+ }
39
+ if (json) {
40
+ console.log(JSON.stringify(body));
41
+ }
42
+ else {
43
+ console.log(`✓ @${username}을(를) ${slug}에 초대했습니다.`);
44
+ }
45
+ }
46
+ catch (err) {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ console.error(JSON.stringify({ error: 'INVITE_FAILED', message }));
49
+ process.exit(1);
50
+ }
51
+ });
52
+ inviteCmd
53
+ .command('remove <username>')
54
+ .description('초대 취소')
55
+ .action(async (username) => {
56
+ const json = program.opts().json ?? false;
57
+ const slug = inviteCmd.args[0];
58
+ const token = (0, config_js_1.loadToken)();
59
+ if (!token) {
60
+ console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다.' }));
61
+ process.exit(1);
62
+ }
63
+ try {
64
+ const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
65
+ method: 'DELETE',
66
+ headers: {
67
+ 'Authorization': `Bearer ${token}`,
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ body: JSON.stringify({ username }),
71
+ });
72
+ const body = await res.json();
73
+ if (!res.ok) {
74
+ if (json) {
75
+ console.error(JSON.stringify(body));
76
+ }
77
+ else {
78
+ console.error(`오류: ${body.message ?? '초대 취소에 실패했습니다'}`);
79
+ }
80
+ process.exit(1);
81
+ }
82
+ if (json) {
83
+ console.log(JSON.stringify(body));
84
+ }
85
+ else {
86
+ console.log(`✓ @${username}의 초대를 취소했습니다.`);
87
+ }
88
+ }
89
+ catch (err) {
90
+ const message = err instanceof Error ? err.message : String(err);
91
+ console.error(JSON.stringify({ error: 'REVOKE_FAILED', message }));
92
+ process.exit(1);
93
+ }
94
+ });
95
+ inviteCmd
96
+ .command('list')
97
+ .description('초대 목록 조회')
98
+ .action(async () => {
99
+ const json = program.opts().json ?? false;
100
+ const slug = inviteCmd.args[0];
101
+ const token = (0, config_js_1.loadToken)();
102
+ if (!token) {
103
+ console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다.' }));
104
+ process.exit(1);
105
+ }
106
+ try {
107
+ const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
108
+ headers: { 'Authorization': `Bearer ${token}` },
109
+ });
110
+ const body = await res.json();
111
+ if (!res.ok) {
112
+ console.error(JSON.stringify(body));
113
+ process.exit(1);
114
+ }
115
+ const invites = body.invites ?? [];
116
+ if (json) {
117
+ console.log(JSON.stringify(body));
118
+ }
119
+ else if (invites.length === 0) {
120
+ console.log('초대된 사용자가 없습니다.');
121
+ }
122
+ else {
123
+ console.log(`초대된 사용자 (${invites.length}명):`);
124
+ for (const inv of invites) {
125
+ console.log(` @${inv.profiles?.username ?? '(알 수 없음)'}`);
126
+ }
127
+ }
128
+ }
129
+ catch (err) {
130
+ const message = err instanceof Error ? err.message : String(err);
131
+ console.error(JSON.stringify({ error: 'LIST_FAILED', message }));
132
+ process.exit(1);
133
+ }
134
+ });
135
+ }
@@ -19,11 +19,4 @@ export interface Requires {
19
19
  env?: RequiresEnv[];
20
20
  teams?: string[];
21
21
  }
22
- export interface ContactInfo {
23
- email?: string;
24
- kakao?: string;
25
- x?: string;
26
- linkedin?: string;
27
- website?: string;
28
- }
29
22
  export declare function registerPublish(program: Command): void;
@@ -40,10 +40,7 @@ function parseRelayYaml(content) {
40
40
  tags,
41
41
  portfolio,
42
42
  requires,
43
- welcome: raw.welcome ? String(raw.welcome) : undefined,
44
- contact: raw.contact,
45
43
  visibility,
46
- invite_code: raw.invite_code ? String(raw.invite_code) : undefined,
47
44
  };
48
45
  }
49
46
  function detectCommands(teamDir) {
@@ -194,7 +191,7 @@ function registerPublish(program) {
194
191
  process.exit(1);
195
192
  }
196
193
  // Interactive onboarding: create relay.yaml
197
- const { input: promptInput, select: promptSelect, confirm: promptConfirm } = await import('@inquirer/prompts');
194
+ const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
198
195
  const dirName = path_1.default.basename(teamDir);
199
196
  const defaultSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
200
197
  console.error('\n\x1b[36m릴레이 팀 패키지를 초기화합니다.\x1b[0m');
@@ -223,40 +220,11 @@ function registerPublish(program) {
223
220
  { name: '초대 코드 필요', value: 'invite-only' },
224
221
  ],
225
222
  });
226
- let invite_code;
223
+ console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: relayax.com/dashboard/edit\x1b[0m');
227
224
  if (visibility === 'invite-only') {
228
- invite_code = await promptInput({
229
- message: '초대 코드:',
230
- validate: (v) => v.trim().length > 0 ? true : '초대 코드를 입력해주세요.',
231
- });
232
- }
233
- // Welcome message section
234
- console.error('\n\x1b[33m┌─────────────────────────────────────────────────────────────┐\x1b[0m');
235
- console.error('\x1b[33m│ welcome 메시지란? │\x1b[0m');
236
- console.error('\x1b[33m│ 설치할 때마다 이 메시지와 연락처가 설치자에게 전달됩니다. │\x1b[0m');
237
- console.error('\x1b[33m│ (설치 = 명함 전달) │\x1b[0m');
238
- console.error('\x1b[33m└─────────────────────────────────────────────────────────────┘\x1b[0m\n');
239
- const wantsWelcome = await promptConfirm({
240
- message: 'welcome 메시지를 작성하시겠습니까?',
241
- default: false,
242
- });
243
- let welcome;
244
- let contactEmail;
245
- let contactKakao;
246
- if (wantsWelcome) {
247
- welcome = await promptInput({
248
- message: 'welcome 메시지:',
249
- validate: (v) => v.trim().length > 0 ? true : '메시지를 입력해주세요.',
250
- });
251
- contactEmail = await promptInput({
252
- message: '이메일 (선택):',
253
- default: '',
254
- });
255
- contactKakao = await promptInput({
256
- message: '카카오 오픈채팅 링크 (선택):',
257
- default: '',
258
- });
225
+ console.error('\x1b[2m💡 invite-only 팀은 `relay invite <slug> add @username`으로 사용자를 초대하세요.\x1b[0m');
259
226
  }
227
+ console.error('');
260
228
  const tags = tagsRaw
261
229
  .split(',')
262
230
  .map((t) => t.trim())
@@ -269,18 +237,6 @@ function registerPublish(program) {
269
237
  tags,
270
238
  visibility,
271
239
  };
272
- if (invite_code)
273
- yamlData.invite_code = invite_code;
274
- if (welcome)
275
- yamlData.welcome = welcome;
276
- if (contactEmail || contactKakao) {
277
- const contact = {};
278
- if (contactEmail)
279
- contact.email = contactEmail;
280
- if (contactKakao)
281
- contact.kakao = contactKakao;
282
- yamlData.contact = contact;
283
- }
284
240
  fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
285
241
  console.error(`\n\x1b[32m✓ relay.yaml이 생성되었습니다.\x1b[0m\n`);
286
242
  }
@@ -294,9 +250,9 @@ function registerPublish(program) {
294
250
  }));
295
251
  process.exit(1);
296
252
  }
297
- // Welcome hint when welcome field is missing and TTY is available
298
- if (isTTY && !config.welcome) {
299
- console.error('💡 welcome 메시지를 추가하면 설치할 때마다 명함이 전달됩니다. relay.yaml에 welcome 필드를 추가해보세요.');
253
+ // Profile hint
254
+ if (isTTY) {
255
+ console.error('💡 프로필에 연락처를 설정하면 설치 명함이 전달됩니다: relayax.com/dashboard/edit');
300
256
  }
301
257
  // Validate structure
302
258
  const hasDirs = VALID_DIRS.some((d) => {
@@ -341,10 +297,7 @@ function registerPublish(program) {
341
297
  version: config.version,
342
298
  changelog: config.changelog,
343
299
  requires: config.requires,
344
- welcome: config.welcome,
345
- contact: config.contact,
346
300
  visibility: config.visibility,
347
- invite_code: config.invite_code,
348
301
  };
349
302
  if (!json) {
350
303
  console.error(`패키지 생성 중... (${config.name} v${config.version})`);
@@ -12,7 +12,7 @@ function formatTable(results) {
12
12
  ? r.description.slice(0, 47) + '...'
13
13
  : r.description,
14
14
  installs: String(r.install_count),
15
- commands: r.commands.join(', ') || '-',
15
+ commands: r.commands.map((c) => typeof c === 'string' ? c : c.name).join(', ') || '-',
16
16
  }));
17
17
  const cols = ['slug', 'name', 'description', 'installs', 'commands'];
18
18
  const widths = cols.map((col) => Math.max(col.length, ...rows.map((r) => r[col].length)));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerStatus(program: Command): void;
@@ -0,0 +1,116 @@
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.registerStatus = registerStatus;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const ai_tools_js_1 = require("../lib/ai-tools.js");
10
+ const config_js_1 = require("../lib/config.js");
11
+ const command_adapter_js_1 = require("../lib/command-adapter.js");
12
+ async function resolveUsername(token) {
13
+ try {
14
+ const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
15
+ headers: { Authorization: `Bearer ${token}` },
16
+ });
17
+ if (!res.ok)
18
+ return undefined;
19
+ const body = await res.json();
20
+ return body.username;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ function registerStatus(program) {
27
+ program
28
+ .command('status')
29
+ .description('현재 relay 환경 상태를 표시합니다')
30
+ .action(async () => {
31
+ const json = program.opts().json ?? false;
32
+ const projectPath = process.cwd();
33
+ // 1. 로그인 상태
34
+ const token = (0, config_js_1.loadToken)();
35
+ let username;
36
+ if (token) {
37
+ username = await resolveUsername(token);
38
+ }
39
+ // 2. 에이전트 감지
40
+ const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
41
+ const primaryAgent = detected.length > 0 ? detected[0] : null;
42
+ // 글로벌 커맨드 상태
43
+ const hasGlobal = command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
44
+ // 로컬 Builder 커맨드 상태
45
+ let hasLocal = false;
46
+ if (primaryAgent) {
47
+ const localDir = path_1.default.join(projectPath, primaryAgent.skillsDir, 'commands', 'relay');
48
+ hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
49
+ }
50
+ // 3. 팀 정보
51
+ const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
52
+ let team = null;
53
+ if (fs_1.default.existsSync(relayYamlPath)) {
54
+ try {
55
+ const yaml = await import('js-yaml');
56
+ const content = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
57
+ const raw = yaml.load(content);
58
+ team = {
59
+ is_team: true,
60
+ name: String(raw.name ?? ''),
61
+ slug: String(raw.slug ?? ''),
62
+ version: String(raw.version ?? ''),
63
+ };
64
+ }
65
+ catch {
66
+ team = { is_team: true };
67
+ }
68
+ }
69
+ else {
70
+ team = { is_team: false };
71
+ }
72
+ // 4. 출력
73
+ if (json) {
74
+ const result = {
75
+ login: { authenticated: !!token, username },
76
+ agent: {
77
+ detected: primaryAgent?.name ?? null,
78
+ global_commands: hasGlobal,
79
+ local_commands: hasLocal,
80
+ },
81
+ team,
82
+ };
83
+ console.log(JSON.stringify(result));
84
+ }
85
+ else {
86
+ console.log('');
87
+ // 로그인
88
+ if (token && username) {
89
+ console.log(` \x1b[32m✓\x1b[0m 로그인: \x1b[36m${username}\x1b[0m`);
90
+ }
91
+ else if (token) {
92
+ console.log(` \x1b[32m✓\x1b[0m 로그인: 인증됨`);
93
+ }
94
+ else {
95
+ console.log(` \x1b[31m✗\x1b[0m 로그인: 미인증 (\x1b[33mrelay login\x1b[0m으로 로그인)`);
96
+ }
97
+ // 에이전트
98
+ if (primaryAgent) {
99
+ const globalLabel = hasGlobal ? '\x1b[32m글로벌 ✓\x1b[0m' : '\x1b[31m글로벌 ✗\x1b[0m';
100
+ const localLabel = hasLocal ? '\x1b[32m로컬 ✓\x1b[0m' : '\x1b[2m로컬 —\x1b[0m';
101
+ console.log(` \x1b[32m✓\x1b[0m 에이전트: \x1b[36m${primaryAgent.name}\x1b[0m (${globalLabel} ${localLabel})`);
102
+ }
103
+ else {
104
+ console.log(` \x1b[31m✗\x1b[0m 에이전트: 감지 안 됨`);
105
+ }
106
+ // 팀
107
+ if (team?.is_team && team.name) {
108
+ console.log(` \x1b[32m✓\x1b[0m 현재 팀: \x1b[36m${team.name}\x1b[0m v${team.version}`);
109
+ }
110
+ else {
111
+ console.log(` \x1b[2m—\x1b[0m 현재 프로젝트: 팀 아님`);
112
+ }
113
+ console.log('');
114
+ }
115
+ });
116
+ }
@@ -62,7 +62,7 @@ function registerUpdate(program) {
62
62
  };
63
63
  (0, config_js_1.saveInstalled)(installed);
64
64
  // Report install (non-blocking)
65
- await (0, api_js_1.reportInstall)(slug, opts.code);
65
+ await (0, api_js_1.reportInstall)(slug);
66
66
  const result = {
67
67
  status: 'updated',
68
68
  slug,
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const init_js_1 = require("./commands/init.js");
6
+ const create_js_1 = require("./commands/create.js");
7
+ const status_js_1 = require("./commands/status.js");
6
8
  const search_js_1 = require("./commands/search.js");
7
9
  const install_js_1 = require("./commands/install.js");
8
10
  const list_js_1 = require("./commands/list.js");
@@ -20,6 +22,8 @@ program
20
22
  .version(pkg.version)
21
23
  .option('--json', '구조화된 JSON 출력');
22
24
  (0, init_js_1.registerInit)(program);
25
+ (0, create_js_1.registerCreate)(program);
26
+ (0, status_js_1.registerStatus)(program);
23
27
  (0, search_js_1.registerSearch)(program);
24
28
  (0, install_js_1.registerInstall)(program);
25
29
  (0, list_js_1.registerList)(program);
package/dist/lib/api.d.ts CHANGED
@@ -7,4 +7,4 @@ export interface TeamVersionInfo {
7
7
  created_at: string;
8
8
  }
9
9
  export declare function fetchTeamVersions(slug: string): Promise<TeamVersionInfo[]>;
10
- export declare function reportInstall(slug: string, code?: string): Promise<void>;
10
+ export declare function reportInstall(slug: string): Promise<void>;
package/dist/lib/api.js CHANGED
@@ -36,9 +36,8 @@ async function fetchTeamVersions(slug) {
36
36
  }
37
37
  return res.json();
38
38
  }
39
- async function reportInstall(slug, code) {
40
- const base = `${config_js_1.API_URL}/api/registry/${slug}/install`;
41
- const url = code ? `${base}?code=${encodeURIComponent(code)}` : base;
39
+ async function reportInstall(slug) {
40
+ const url = `${config_js_1.API_URL}/api/registry/${slug}/install`;
42
41
  await fetch(url, { method: 'POST' }).catch(() => {
43
42
  // non-critical: ignore errors
44
43
  });
@@ -10,12 +10,24 @@ export interface ToolCommandAdapter {
10
10
  formatFile(content: CommandContent): string;
11
11
  }
12
12
  /**
13
- * 기본 어댑터 — 대부분의 에이전트 CLI가 동일한 패턴 사용.
14
- * {skillsDir}/commands/relay/{id}.md
13
+ * 로컬 어댑터 — 프로젝트 디렉토리 기준.
14
+ * {projectPath}/{skillsDir}/commands/relay/{id}.md
15
15
  */
16
16
  export declare function createAdapter(tool: AITool): ToolCommandAdapter;
17
17
  /**
18
- * relay 슬래시 커맨드 템플릿.
19
- * CLI 명령어(원자적 API)를 조합하는 에이전트 워크플로우.
18
+ * 글로벌 슬래시 커맨드 파일 경로.
19
+ * ~/.claude/commands/relay/{id}.md
20
20
  */
21
+ export declare function getGlobalCommandPath(commandId: string): string;
22
+ /**
23
+ * 글로벌 슬래시 커맨드 디렉토리.
24
+ */
25
+ export declare function getGlobalCommandDir(): string;
26
+ /**
27
+ * 커맨드 콘텐츠를 파일 형식으로 포맷.
28
+ */
29
+ export declare function formatCommandFile(content: CommandContent): string;
30
+ export declare const USER_COMMANDS: CommandContent[];
31
+ export declare const BUILDER_COMMANDS: CommandContent[];
32
+ /** 하위 호환 — 기존 코드에서 RELAY_COMMANDS를 참조하는 경우 */
21
33
  export declare const RELAY_COMMANDS: CommandContent[];
@@ -3,12 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RELAY_COMMANDS = void 0;
6
+ exports.RELAY_COMMANDS = exports.BUILDER_COMMANDS = exports.USER_COMMANDS = void 0;
7
7
  exports.createAdapter = createAdapter;
8
+ exports.getGlobalCommandPath = getGlobalCommandPath;
9
+ exports.getGlobalCommandDir = getGlobalCommandDir;
10
+ exports.formatCommandFile = formatCommandFile;
11
+ const os_1 = __importDefault(require("os"));
8
12
  const path_1 = __importDefault(require("path"));
9
13
  /**
10
- * 기본 어댑터 — 대부분의 에이전트 CLI가 동일한 패턴 사용.
11
- * {skillsDir}/commands/relay/{id}.md
14
+ * 로컬 어댑터 — 프로젝트 디렉토리 기준.
15
+ * {projectPath}/{skillsDir}/commands/relay/{id}.md
12
16
  */
13
17
  function createAdapter(tool) {
14
18
  return {
@@ -22,10 +26,33 @@ function createAdapter(tool) {
22
26
  };
23
27
  }
24
28
  /**
25
- * relay 슬래시 커맨드 템플릿.
26
- * CLI 명령어(원자적 API)를 조합하는 에이전트 워크플로우.
29
+ * 글로벌 슬래시 커맨드 파일 경로.
30
+ * ~/.claude/commands/relay/{id}.md
27
31
  */
28
- exports.RELAY_COMMANDS = [
32
+ function getGlobalCommandPath(commandId) {
33
+ return path_1.default.join(os_1.default.homedir(), '.claude', 'commands', 'relay', `${commandId}.md`);
34
+ }
35
+ /**
36
+ * 글로벌 슬래시 커맨드 디렉토리.
37
+ */
38
+ function getGlobalCommandDir() {
39
+ return path_1.default.join(os_1.default.homedir(), '.claude', 'commands', 'relay');
40
+ }
41
+ /**
42
+ * 커맨드 콘텐츠를 파일 형식으로 포맷.
43
+ */
44
+ function formatCommandFile(content) {
45
+ return `---\ndescription: ${content.description}\n---\n\n${content.body}\n`;
46
+ }
47
+ // ─── Login JIT 공통 안내 ───
48
+ const LOGIN_JIT_GUIDE = `
49
+ ### 인증 오류 처리
50
+ - 커맨드 실행 결과에 \`LOGIN_REQUIRED\` 에러가 포함되면:
51
+ 1. 사용자에게 "이 팀은 로그인이 필요합니다"라고 안내합니다.
52
+ 2. \`relay login\` 명령어를 실행합니다.
53
+ 3. 로그인 완료 후 원래 커맨드를 재실행합니다.`;
54
+ // ─── User Commands (글로벌 설치) ───
55
+ exports.USER_COMMANDS = [
29
56
  {
30
57
  id: 'relay-explore',
31
58
  description: 'relay 마켓플레이스를 탐색하고 프로젝트에 맞는 팀을 찾습니다',
@@ -81,6 +108,7 @@ exports.RELAY_COMMANDS = [
81
108
  ### 5. 완료 안내
82
109
  - 사용 가능한 커맨드 안내
83
110
  - "바로 사용해볼까요?" 제안
111
+ ${LOGIN_JIT_GUIDE}
84
112
 
85
113
  ## 예시
86
114
 
@@ -88,13 +116,80 @@ exports.RELAY_COMMANDS = [
88
116
  → relay install contents-team 실행
89
117
  → requires 확인: ✓ playwright, ✓ sharp 설치됨
90
118
  → ✓ 설치 완료!
91
- ┌─ @haemin ──────────────────────────────┐
92
- → │ contents-team을 설치해주셔서 감사합니다! │
93
- → │ 💬 카카오톡 📧 이메일 │
94
- → └─────────────────────────────────────────┘
119
+ 제작자 메시지 및 연락처 표시
95
120
  → "@haemin을 팔로우할까요?" → Yes → ✓ 팔로우 완료
96
121
  → "바로 /cardnews를 사용해볼까요?"`,
97
122
  },
123
+ {
124
+ id: 'relay-list',
125
+ description: '설치된 에이전트 팀 목록을 확인합니다',
126
+ body: `현재 설치된 에이전트 팀 목록을 보여줍니다.
127
+
128
+ ## 실행 방법
129
+
130
+ 1. \`relay list --json\` 명령어를 실행합니다.
131
+ 2. 결과를 사용자에게 보기 좋게 정리하여 보여줍니다:
132
+ - 팀 이름과 버전
133
+ - 설치 날짜
134
+ - 사용 가능한 커맨드
135
+ 3. 설치된 팀이 없으면 \`/relay-explore\`로 팀을 탐색해보라고 안내합니다.
136
+
137
+ ## 예시
138
+
139
+ 사용자: /relay-list
140
+ → relay list --json 실행
141
+ → "2개 팀이 설치되어 있어요:"
142
+ → " contents-team v1.2.0 (2일 전 설치) - /cardnews, /pdf-report"
143
+ → " qa-team v0.5.1 (1주 전 설치) - /qa, /qa-only"`,
144
+ },
145
+ {
146
+ id: 'relay-update',
147
+ description: '설치된 에이전트 팀을 최신 버전으로 업데이트합니다',
148
+ body: `설치된 에이전트 팀의 업데이트를 확인하고 적용합니다.
149
+
150
+ ## 실행 방법
151
+
152
+ ### 특정 팀 업데이트
153
+ - 사용자가 팀 이름을 지정한 경우: \`relay update <slug> --json\` 실행
154
+ - 업데이트 결과를 보여줍니다 (이전 버전 → 새 버전)
155
+
156
+ ### 전체 업데이트 확인
157
+ - 팀 이름을 지정하지 않은 경우:
158
+ 1. \`relay outdated --json\`으로 업데이트 가능한 팀 목록을 확인합니다.
159
+ 2. 업데이트 가능한 팀이 있으면 목록을 보여주고 어떤 팀을 업데이트할지 물어봅니다.
160
+ 3. 선택된 팀에 대해 \`relay update <slug> --json\`을 실행합니다.
161
+ 4. 모두 최신이면 "모든 팀이 최신 버전입니다"라고 안내합니다.
162
+
163
+ ## 예시
164
+
165
+ 사용자: /relay-update
166
+ → relay outdated --json 실행
167
+ → "1개 팀 업데이트 가능:"
168
+ → " contents-team: v1.2.0 → v1.3.0"
169
+ → "업데이트할까요?"
170
+ → relay update contents-team --json 실행
171
+ → "✓ contents-team v1.3.0으로 업데이트 완료"`,
172
+ },
173
+ {
174
+ id: 'relay-uninstall',
175
+ description: '설치된 에이전트 팀을 삭제합니다',
176
+ body: `설치된 에이전트 팀을 프로젝트에서 제거합니다.
177
+
178
+ ## 실행 방법
179
+
180
+ 1. \`relay uninstall <slug> --json\` 명령어를 실행합니다.
181
+ 2. 삭제 결과를 보여줍니다 (팀 이름, 제거된 파일 수).
182
+ 3. 삭제 완료 후 남아있는 팀 목록을 간단히 안내합니다.
183
+
184
+ ## 예시
185
+
186
+ 사용자: /relay-uninstall contents-team
187
+ → relay uninstall contents-team --json 실행
188
+ → "✓ contents-team 삭제 완료 (12개 파일 제거)"`,
189
+ },
190
+ ];
191
+ // ─── Builder Commands (로컬 설치) ───
192
+ exports.BUILDER_COMMANDS = [
98
193
  {
99
194
  id: 'relay-publish',
100
195
  description: '현재 팀 패키지를 포트폴리오와 함께 relay 마켓플레이스에 배포합니다',
@@ -106,6 +201,7 @@ exports.RELAY_COMMANDS = [
106
201
  - \`cat ~/.relay/token\` 또는 환경변수 RELAY_TOKEN으로 토큰 존재 여부를 확인합니다.
107
202
  - 미인증이면 즉시 안내: "먼저 \`relay login\`으로 로그인이 필요합니다." → 로그인 후 재실행 안내.
108
203
  - 인증되어 있으면 다음 단계로 진행합니다.
204
+ ${LOGIN_JIT_GUIDE}
109
205
 
110
206
  ### 2. 팀 구조 분석
111
207
  - skills/, agents/, rules/, commands/ 디렉토리를 탐색합니다.
@@ -156,3 +252,5 @@ exports.RELAY_COMMANDS = [
156
252
  → "배포 완료! URL: https://relayax.com/teams/contents-team"`,
157
253
  },
158
254
  ];
255
+ /** 하위 호환 — 기존 코드에서 RELAY_COMMANDS를 참조하는 경우 */
256
+ exports.RELAY_COMMANDS = [...exports.USER_COMMANDS, ...exports.BUILDER_COMMANDS];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.1.91",
3
+ "version": "0.1.93",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {