relayax-cli 0.2.40 → 0.3.41

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 registerDiff(program: Command): void;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDiff = registerDiff;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const slug_js_1 = require("../lib/slug.js");
6
+ const storage_js_1 = require("../lib/storage.js");
7
+ function registerDiff(program) {
8
+ program
9
+ .command('diff <slug> <v1> <v2>')
10
+ .description('두 버전의 패키지를 비교합니다')
11
+ .action(async (slugInput, v1, v2) => {
12
+ const json = program.opts().json ?? false;
13
+ try {
14
+ const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
15
+ const versions = await (0, api_js_1.fetchTeamVersions)(resolved.full);
16
+ const ver1 = versions.find((v) => v.version === v1);
17
+ const ver2 = versions.find((v) => v.version === v2);
18
+ if (!ver1 || !ver2) {
19
+ throw new Error(`버전을 찾을 수 없습니다. 사용 가능: ${versions.map((v) => v.version).join(', ')}`);
20
+ }
21
+ if (!json) {
22
+ console.log(`\n\x1b[1m${resolved.full}\x1b[0m v${v1} ↔ v${v2} 비교 중...\n`);
23
+ }
24
+ // Download both versions to temp dirs
25
+ const tempDir1 = (0, storage_js_1.makeTempDir)();
26
+ const tempDir2 = (0, storage_js_1.makeTempDir)();
27
+ try {
28
+ // Get download URLs for both versions via registry API
29
+ // For now, we use the current version's package_url as fallback
30
+ // The registry API returns the latest version; for specific versions,
31
+ // we'd need a version-specific endpoint
32
+ const info = await (0, api_js_1.fetchTeamInfo)(resolved.full);
33
+ if (!info.package_url) {
34
+ throw new Error('패키지 URL을 가져올 수 없습니다');
35
+ }
36
+ // Since version-specific download isn't available yet, show versions info
37
+ if (json) {
38
+ console.log(JSON.stringify({
39
+ slug: resolved.full,
40
+ v1: { version: v1, created_at: ver1.created_at, changelog: ver1.changelog },
41
+ v2: { version: v2, created_at: ver2.created_at, changelog: ver2.changelog },
42
+ }));
43
+ }
44
+ else {
45
+ console.log(` v${v1} (${new Date(ver1.created_at).toLocaleDateString('ko-KR')})`);
46
+ if (ver1.changelog)
47
+ console.log(` ${ver1.changelog}`);
48
+ console.log();
49
+ console.log(` v${v2} (${new Date(ver2.created_at).toLocaleDateString('ko-KR')})`);
50
+ if (ver2.changelog)
51
+ console.log(` ${ver2.changelog}`);
52
+ console.log();
53
+ console.log(`\x1b[33m 버전별 패키지 다운로드 비교는 추후 지원 예정입니다.\x1b[0m`);
54
+ }
55
+ }
56
+ finally {
57
+ (0, storage_js_1.removeTempDir)(tempDir1);
58
+ (0, storage_js_1.removeTempDir)(tempDir2);
59
+ }
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ if (json) {
64
+ console.error(JSON.stringify({ error: 'DIFF_FAILED', message }));
65
+ }
66
+ else {
67
+ console.error(`\x1b[31m오류: ${message}\x1b[0m`);
68
+ }
69
+ process.exit(1);
70
+ }
71
+ });
72
+ }
@@ -12,31 +12,13 @@ const config_js_1 = require("../lib/config.js");
12
12
  const slug_js_1 = require("../lib/slug.js");
13
13
  const contact_format_js_1 = require("../lib/contact-format.js");
14
14
  const preamble_js_1 = require("../lib/preamble.js");
15
- const join_js_1 = require("./join.js");
16
15
  const init_js_1 = require("./init.js");
17
- /**
18
- * slugInput이 "@spaces/{spaceSlug}/{teamSlug}" 또는 "@{spaceSlug}/{teamSlug}" 형식이면 파싱해 반환.
19
- * 아니면 null.
20
- */
21
- function parseSpaceTarget(slugInput) {
22
- // @spaces/{spaceSlug}/{teamSlug} 형식 (기존)
23
- const m1 = slugInput.match(/^@spaces\/([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
24
- if (m1) {
25
- return { spaceSlug: m1[1], rawTeamSlug: m1[2], teamSlug: `@${m1[1]}/${m1[2]}` };
26
- }
27
- // @{spaceSlug}/{teamSlug} 형식 (신규)
28
- const m2 = slugInput.match(/^@([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
29
- if (m2) {
30
- return { spaceSlug: m2[1], rawTeamSlug: m2[2], teamSlug: `@${m2[1]}/${m2[2]}` };
31
- }
32
- return null;
33
- }
34
16
  function registerInstall(program) {
35
17
  program
36
18
  .command('install <slug>')
37
19
  .description('에이전트 팀 패키지를 .relay/teams/에 다운로드합니다')
38
- .option('--join-code <code>', 'Space 초대 코드 (Space 팀 설치 시 자동 가입)')
39
- .action(async (slugInput, opts) => {
20
+ .option('--join-code <code>', '초대 코드 (Organization 팀 설치 시 자동 가입)')
21
+ .action(async (slugInput, _opts) => {
40
22
  const json = program.opts().json ?? false;
41
23
  const projectPath = process.cwd();
42
24
  const tempDir = (0, storage_js_1.makeTempDir)();
@@ -48,201 +30,94 @@ function registerInstall(program) {
48
30
  (0, init_js_1.installGlobalUserCommands)();
49
31
  }
50
32
  try {
51
- // 0. @spaces/{spaceSlug}/{teamSlug} 형식 감지 파싱
52
- const spaceTarget = parseSpaceTarget(slugInput);
53
- // 0a. --join-code가 있으면 먼저 Space 가입 시도
54
- if (opts.joinCode && spaceTarget) {
55
- try {
56
- const { spaceName } = await (0, join_js_1.joinSpace)(spaceTarget.spaceSlug, opts.joinCode);
57
- if (!json) {
58
- console.log(`\x1b[32m✅ ${spaceName} Space에 가입했습니다\x1b[0m`);
59
- }
60
- }
61
- catch (joinErr) {
62
- const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
63
- // 이미 멤버인 경우 설치 계속 진행
64
- if (joinMsg !== 'ALREADY_MEMBER') {
65
- if (!json) {
66
- console.error(`\x1b[33m경고: Space 가입 실패 — ${joinMsg}\x1b[0m`);
67
- }
68
- }
69
- }
70
- }
71
- // 0b. Resolve scoped slug and fetch team metadata
33
+ // Resolve scoped slug and fetch team metadata
72
34
  let team;
73
35
  let slug;
74
36
  let parsed;
75
- // Whether the spaceTarget was matched via the ambiguous @{slug}/{team} pattern
76
- // (i.e. NOT the explicit @spaces/... prefix). Used for 404 fallback below.
77
- const isAmbiguousSpaceTarget = spaceTarget !== null && !slugInput.startsWith('@spaces/');
78
- if (spaceTarget) {
79
- // Space 팀: POST /api/spaces/{spaceSlug}/teams/{teamSlug}/install
80
- // This verifies membership, increments install count, and returns metadata.
81
- let usedSpacePath = true;
82
- try {
83
- team = await (0, api_js_1.installSpaceTeam)(spaceTarget.spaceSlug, spaceTarget.rawTeamSlug);
84
- }
85
- catch (fetchErr) {
86
- const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
87
- if (fetchMsg.includes('404') && isAmbiguousSpaceTarget) {
88
- // Space not found — @{owner}/{team} is actually a normal registry slug, fall back
89
- usedSpacePath = false;
37
+ // Extract version from @owner/team@version syntax
38
+ // Extract version from @owner/team@version syntax (e.g. acme/writer@1.2.0)
39
+ // Version-specific install is not yet supported by the registry API;
40
+ // the match is kept for future use when per-version package URLs are available.
41
+ const versionMatch = slugInput.match(/^(.+)@(\d+\.\d+\.\d+.*)$/);
42
+ const actualSlugInput = versionMatch ? versionMatch[1] : slugInput;
43
+ parsed = await (0, slug_js_1.resolveSlug)(actualSlugInput);
44
+ slug = parsed.full;
45
+ try {
46
+ team = await (0, api_js_1.fetchTeamInfo)(slug);
47
+ }
48
+ catch (fetchErr) {
49
+ const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
50
+ if (fetchMsg.includes('403')) {
51
+ // Parse error body for membership_status, visibility, purchase_info
52
+ let membershipStatus;
53
+ let errorVisibility;
54
+ let purchaseInfo;
55
+ try {
56
+ const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
57
+ membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
58
+ errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
59
+ if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
60
+ purchaseInfo = errBody.purchase_info;
61
+ }
90
62
  }
91
- else if (fetchMsg.includes('403')) {
63
+ catch { /* ignore parse errors */ }
64
+ // Gated team: show purchase info + relay access hint
65
+ if (errorVisibility === 'gated' || purchaseInfo) {
92
66
  if (json) {
93
67
  console.error(JSON.stringify({
94
- error: 'SPACE_ONLY',
95
- message: '이 팀은 Space 멤버만 설치 가능합니다.',
96
- spaceSlug: spaceTarget.spaceSlug,
97
- fix: `relay join ${spaceTarget.spaceSlug} --code <초대코드>로 Space에 가입한 후 재시도하세요.`,
68
+ error: 'GATED_ACCESS_REQUIRED',
69
+ message: '이 팀은 접근 권한이 필요합니다.',
70
+ slug,
71
+ purchase_info: purchaseInfo ?? null,
72
+ fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
98
73
  }));
99
74
  }
100
75
  else {
101
- console.error('\x1b[31m이 팀은 Space 멤버만 설치 가능합니다.\x1b[0m');
102
- console.error(`\x1b[33mSpace 관리자에게 초대 코드를 요청하세요: relay join ${spaceTarget.spaceSlug} --code <코드>\x1b[0m`);
103
- }
104
- process.exit(1);
105
- }
106
- else {
107
- throw fetchErr;
108
- }
109
- }
110
- if (!usedSpacePath) {
111
- // Fallback: treat as normal registry install
112
- parsed = await (0, slug_js_1.resolveSlug)(slugInput);
113
- slug = parsed.full;
114
- team = await (0, api_js_1.fetchTeamInfo)(slug);
115
- }
116
- else {
117
- // slug from server is "@spaces/{spaceSlug}/{teamSlug}" — derive local path parts
118
- // team is guaranteed assigned by installSpaceTeam above (usedSpacePath === true)
119
- parsed = { owner: spaceTarget.spaceSlug, name: spaceTarget.rawTeamSlug, full: team.slug };
120
- slug = team.slug;
121
- }
122
- }
123
- else {
124
- // Normal registry install
125
- parsed = await (0, slug_js_1.resolveSlug)(slugInput);
126
- slug = parsed.full;
127
- try {
128
- team = await (0, api_js_1.fetchTeamInfo)(slug);
129
- }
130
- catch (fetchErr) {
131
- const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
132
- if (fetchMsg.includes('403')) {
133
- // Parse error body for join_policy, membership_status, visibility, purchase_info
134
- let joinPolicy;
135
- let membershipStatus;
136
- let errorVisibility;
137
- let purchaseInfo;
138
- try {
139
- const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
140
- joinPolicy = typeof errBody.join_policy === 'string' ? errBody.join_policy : undefined;
141
- membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
142
- errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
143
- if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
144
- purchaseInfo = errBody.purchase_info;
76
+ console.error('\x1b[31m이 팀은 접근 권한이 필요합니다.\x1b[0m');
77
+ if (purchaseInfo?.message) {
78
+ console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
145
79
  }
146
- }
147
- catch { /* ignore parse errors */ }
148
- // Gated team: show purchase info + relay access hint
149
- if (errorVisibility === 'gated' || purchaseInfo) {
150
- if (json) {
151
- console.error(JSON.stringify({
152
- error: 'GATED_ACCESS_REQUIRED',
153
- message: '이 팀은 접근 권한이 필요합니다.',
154
- slug,
155
- purchase_info: purchaseInfo ?? null,
156
- fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
157
- }));
80
+ if (purchaseInfo?.url) {
81
+ console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
158
82
  }
159
- else {
160
- console.error('\x1b[31m🔒 이 팀은 접근 권한이 필요합니다.\x1b[0m');
161
- if (purchaseInfo?.message) {
162
- console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
163
- }
164
- if (purchaseInfo?.url) {
165
- console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
166
- }
167
- console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
168
- }
169
- process.exit(1);
83
+ console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
170
84
  }
171
- if (joinPolicy === 'auto') {
172
- // Auto-join the Space then retry install
173
- if (!json) {
174
- console.error(`\x1b[33m⚙ Space에 자동 가입합니다...\x1b[0m`);
175
- }
176
- try {
177
- const spaceSlug = parsed.owner;
178
- await (0, join_js_1.joinSpace)(spaceSlug, '');
179
- if (!json) {
180
- console.error(`\x1b[32m✓ Space에 가입했습니다\x1b[0m`);
181
- }
182
- team = await (0, api_js_1.fetchTeamInfo)(slug);
183
- }
184
- catch (joinErr) {
185
- const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
186
- if (json) {
187
- console.error(JSON.stringify({ error: 'JOIN_FAILED', message: joinMsg, slug, fix: 'Space slug와 초대 코드를 확인 후 재시도하세요.' }));
188
- }
189
- else {
190
- console.error(`\x1b[31mSpace 가입 실패: ${joinMsg}\x1b[0m`);
191
- }
192
- process.exit(1);
193
- }
194
- }
195
- else if (joinPolicy === 'approval') {
196
- const spaceSlug = parsed.owner;
197
- if (json) {
198
- console.error(JSON.stringify({
199
- error: 'APPROVAL_REQUIRED',
200
- message: `가입 신청이 필요합니다.`,
201
- slug,
202
- spaceSlug,
203
- fix: `relay join @${spaceSlug} --code <초대코드>로 Space에 가입한 후 재시도하세요. 초대 코드는 Space 관리자에게 요청하세요.`,
204
- }));
205
- }
206
- else {
207
- console.error(`\x1b[33m가입 신청이 필요합니다. \`relay join @${spaceSlug}\`로 가입하세요.\x1b[0m`);
208
- }
209
- process.exit(1);
210
- }
211
- else if (membershipStatus === 'member') {
212
- // Member but no access to this specific team
213
- if (json) {
214
- console.error(JSON.stringify({
215
- error: 'NO_ACCESS',
216
- message: '이 팀에 대한 접근 권한이 없습니다.',
217
- slug,
218
- fix: '이 팀의 접근 링크 코드가 있으면 `relay access ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 팀 제작자에게 문의하세요.',
219
- }));
220
- }
221
- else {
222
- console.error('\x1b[31m이 팀에 대한 접근 권한이 없습니다.\x1b[0m');
223
- }
224
- process.exit(1);
85
+ process.exit(1);
86
+ }
87
+ if (membershipStatus === 'member') {
88
+ // Member but no access to this specific team
89
+ if (json) {
90
+ console.error(JSON.stringify({
91
+ error: 'NO_ACCESS',
92
+ message: '이 팀에 대한 접근 권한이 없습니다.',
93
+ slug,
94
+ fix: '이 팀의 접근 링크 코드가 있으면 `relay access ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 팀 제작자에게 문의하세요.',
95
+ }));
225
96
  }
226
97
  else {
227
- if (json) {
228
- console.error(JSON.stringify({
229
- error: 'SPACE_ONLY',
230
- message: '이 팀은 Space 멤버만 설치 가능합니다.',
231
- slug,
232
- fix: 'Space 관리자에게 초대 코드를 요청한 후 `relay join <space-slug> --code <코드>`로 가입하세요.',
233
- }));
234
- }
235
- else {
236
- console.error('\x1b[31m이 팀은 Space 멤버만 설치 가능합니다.\x1b[0m');
237
- console.error('\x1b[33mSpace 관리자에게 초대 코드를 요청하세요.\x1b[0m');
238
- }
239
- process.exit(1);
98
+ console.error('\x1b[31m이 팀에 대한 접근 권한이 없습니다.\x1b[0m');
240
99
  }
100
+ process.exit(1);
241
101
  }
242
102
  else {
243
- throw fetchErr;
103
+ if (json) {
104
+ console.error(JSON.stringify({
105
+ error: 'ACCESS_REQUIRED',
106
+ message: '이 팀은 접근 권한이 필요합니다.',
107
+ slug,
108
+ fix: '초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.',
109
+ }));
110
+ }
111
+ else {
112
+ console.error('\x1b[31m이 팀은 접근 권한이 필요합니다.\x1b[0m');
113
+ console.error('\x1b[33m초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.\x1b[0m');
114
+ }
115
+ process.exit(1);
244
116
  }
245
117
  }
118
+ else {
119
+ throw fetchErr;
120
+ }
246
121
  }
247
122
  if (!team)
248
123
  throw new Error('팀 정보를 가져오지 못했습니다.');
@@ -289,12 +164,7 @@ function registerInstall(program) {
289
164
  if (!json) {
290
165
  console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
291
166
  }
292
- if (spaceTarget) {
293
- team = await (0, api_js_1.installSpaceTeam)(spaceTarget.spaceSlug, spaceTarget.rawTeamSlug);
294
- }
295
- else {
296
- team = await (0, api_js_1.fetchTeamInfo)(slug);
297
- }
167
+ team = await (0, api_js_1.fetchTeamInfo)(slug);
298
168
  tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
299
169
  }
300
170
  else {
@@ -325,14 +195,13 @@ function registerInstall(program) {
325
195
  return count;
326
196
  }
327
197
  const fileCount = countFiles(teamDir);
328
- // 6. Record in installed.json (team_id, space_slug 포함)
198
+ // 6. Record in installed.json
329
199
  const installed = (0, config_js_1.loadInstalled)();
330
200
  installed[slug] = {
331
201
  team_id: team.id,
332
202
  version: team.version,
333
203
  installed_at: new Date().toISOString(),
334
204
  files: [teamDir],
335
- ...(spaceTarget ? { space_slug: spaceTarget.spaceSlug } : {}),
336
205
  };
337
206
  (0, config_js_1.saveInstalled)(installed);
338
207
  // 7. Report install + usage ping (non-blocking, team_id 기반)
@@ -346,7 +215,6 @@ function registerInstall(program) {
346
215
  commands: team.commands,
347
216
  files: fileCount,
348
217
  install_path: teamDir,
349
- ...(spaceTarget ? { space_slug: spaceTarget.spaceSlug } : {}),
350
218
  author: team.author ? {
351
219
  username: team.author.username,
352
220
  display_name: team.author.display_name ?? null,
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
- export declare function joinSpace(spaceSlug: string, code: string): Promise<{
3
- spaceName: string;
2
+ export declare function joinOrg(orgSlug: string, code: string): Promise<{
3
+ type: string;
4
+ role?: string;
4
5
  }>;
5
6
  export declare function registerJoin(program: Command): void;
@@ -1,54 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.joinSpace = joinSpace;
3
+ exports.joinOrg = joinOrg;
4
4
  exports.registerJoin = registerJoin;
5
5
  const config_js_1 = require("../lib/config.js");
6
6
  const init_js_1 = require("./init.js");
7
- async function fetchSpaceTeams(spaceSlug, token) {
8
- const res = await fetch(`${config_js_1.API_URL}/api/spaces/${spaceSlug}/teams`, {
9
- headers: { Authorization: `Bearer ${token}` },
10
- signal: AbortSignal.timeout(5000),
11
- });
12
- if (!res.ok)
13
- throw new Error(`${res.status}`);
14
- const data = (await res.json());
15
- if (Array.isArray(data))
16
- return data;
17
- return data.teams ?? [];
18
- }
19
- async function joinSpace(spaceSlug, code) {
7
+ async function joinOrg(orgSlug, code) {
20
8
  const token = await (0, config_js_1.getValidToken)();
21
9
  if (!token) {
22
10
  throw new Error('LOGIN_REQUIRED');
23
11
  }
24
- const res = await fetch(`${config_js_1.API_URL}/api/spaces/${spaceSlug}/join`, {
12
+ // Use the invite link via API
13
+ const res = await fetch(`${config_js_1.API_URL}/api/invite-links/${code}/use`, {
25
14
  method: 'POST',
26
15
  headers: {
27
16
  'Content-Type': 'application/json',
28
17
  Authorization: `Bearer ${token}`,
29
18
  },
30
- body: JSON.stringify({ code }),
31
19
  });
32
- const body = (await res.json().catch(() => ({})));
33
20
  if (!res.ok) {
21
+ const body = await res.json().catch(() => ({}));
34
22
  const errCode = body.error ?? String(res.status);
35
23
  switch (errCode) {
36
- case 'INVALID_CODE':
37
- throw new Error('초대 코드가 올바르지 않습니다.');
38
- case 'EXPIRED_CODE':
39
- throw new Error('초대 코드가 만료되었습니다.');
40
- case 'ALREADY_MEMBER':
41
- throw new Error('ALREADY_MEMBER');
24
+ case 'INVALID_LINK':
25
+ throw new Error('초대 코드가 유효하지 않거나 만료되었습니다.');
42
26
  default:
43
- throw new Error(body.message ?? `Space 가입 실패 (${res.status})`);
27
+ throw new Error(body.message ?? `가입 실패 (${res.status})`);
44
28
  }
45
29
  }
46
- return { spaceName: body.space_name ?? spaceSlug };
30
+ return res.json();
47
31
  }
48
32
  function registerJoin(program) {
49
33
  program
50
34
  .command('join <slug>')
51
- .description('Space에 초대 코드로 가입합니다')
35
+ .description('Organization에 초대 코드로 가입합니다')
52
36
  .requiredOption('--code <code>', '초대 코드 (UUID)')
53
37
  .action(async (slug, opts) => {
54
38
  const json = program.opts().json ?? false;
@@ -62,57 +46,22 @@ function registerJoin(program) {
62
46
  process.exit(1);
63
47
  }
64
48
  try {
65
- const { spaceName } = await joinSpace(slug, opts.code);
49
+ const result = await joinOrg(slug, opts.code);
66
50
  if (json) {
67
- // best-effort: fetch teams for JSON response
68
- let teams = [];
69
- try {
70
- const token = await (0, config_js_1.getValidToken)();
71
- if (token)
72
- teams = await fetchSpaceTeams(slug, token);
73
- }
74
- catch {
75
- // ignore
76
- }
77
- console.log(JSON.stringify({ status: 'ok', space: slug, space_name: spaceName, teams: teams.map((t) => ({ slug: t.slug, name: t.name })) }));
51
+ console.log(JSON.stringify({ status: 'ok', ...result }));
78
52
  }
79
53
  else {
80
- console.log(`\x1b[32m✅ ${spaceName} Space에 가입했습니다\x1b[0m`);
81
- // best-effort: show available teams
82
- try {
83
- const token = await (0, config_js_1.getValidToken)();
84
- if (token) {
85
- const teams = await fetchSpaceTeams(slug, token);
86
- if (teams.length === 0) {
87
- console.log('\n아직 추가된 팀이 없습니다.');
88
- }
89
- else {
90
- console.log('\n\x1b[1m📦 사용 가능한 팀:\x1b[0m');
91
- for (const t of teams) {
92
- const desc = t.description ? ` \x1b[90m— ${t.description}\x1b[0m` : '';
93
- console.log(` \x1b[36m•\x1b[0m \x1b[1m${t.slug}\x1b[0m${desc}`);
94
- }
95
- console.log(`\n\x1b[33m💡 전체 설치: relay install @spaces/${slug}/<팀슬러그>\x1b[0m`);
96
- console.log(`\x1b[33m💡 가이드 URL 공유: https://relayax.com/api/spaces/${slug}/guide.md\x1b[0m`);
97
- }
98
- }
54
+ if (result.type === 'org') {
55
+ console.log(`\x1b[32m✅ @${slug} Organization에 가입했습니다 (역할: ${result.role ?? 'member'})\x1b[0m`);
56
+ console.log(`\n\x1b[33m 대시보드: www.relayax.com/orgs/${slug}\x1b[0m`);
99
57
  }
100
- catch {
101
- // ignore success message was already shown
58
+ else {
59
+ console.log(`\x1b[32m✅ 접근 권한이 부여되었습니다\x1b[0m`);
102
60
  }
103
61
  }
104
62
  }
105
63
  catch (err) {
106
64
  const message = err instanceof Error ? err.message : String(err);
107
- if (message === 'ALREADY_MEMBER') {
108
- if (json) {
109
- console.log(JSON.stringify({ status: 'already_member', space: slug }));
110
- }
111
- else {
112
- console.log(`\x1b[33m이미 ${slug} Space의 멤버입니다.\x1b[0m`);
113
- }
114
- return;
115
- }
116
65
  if (message === 'LOGIN_REQUIRED') {
117
66
  if (json) {
118
67
  console.error(JSON.stringify({
@@ -128,7 +77,7 @@ function registerJoin(program) {
128
77
  process.exit(1);
129
78
  }
130
79
  if (json) {
131
- console.error(JSON.stringify({ error: 'JOIN_FAILED', message, fix: 'Space slug와 초대 코드를 확인 후 재시도하세요.' }));
80
+ console.error(JSON.stringify({ error: 'JOIN_FAILED', message, fix: 'slug와 초대 코드를 확인 후 재시도하세요.' }));
132
81
  }
133
82
  else {
134
83
  console.error(`\x1b[31m오류: ${message}\x1b[0m`);