relayax-cli 0.3.41 → 0.3.43

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.
Files changed (59) hide show
  1. package/dist/commands/access.js +12 -12
  2. package/dist/commands/changelog.js +2 -2
  3. package/dist/commands/check-update.js +12 -12
  4. package/dist/commands/create.js +46 -19
  5. package/dist/commands/deploy-record.js +2 -2
  6. package/dist/commands/diff.js +2 -2
  7. package/dist/commands/grant.d.ts +33 -0
  8. package/dist/commands/grant.js +190 -0
  9. package/dist/commands/init.js +13 -15
  10. package/dist/commands/install.js +69 -68
  11. package/dist/commands/join.js +3 -3
  12. package/dist/commands/list.js +15 -15
  13. package/dist/commands/login.js +10 -3
  14. package/dist/commands/orgs.js +1 -1
  15. package/dist/commands/outdated.js +7 -7
  16. package/dist/commands/package.d.ts +18 -0
  17. package/dist/commands/package.js +355 -146
  18. package/dist/commands/ping.js +5 -5
  19. package/dist/commands/publish.d.ts +1 -1
  20. package/dist/commands/publish.js +56 -48
  21. package/dist/commands/search.js +2 -2
  22. package/dist/commands/status.js +11 -11
  23. package/dist/commands/uninstall.js +27 -9
  24. package/dist/commands/update.js +22 -22
  25. package/dist/commands/versions.js +2 -2
  26. package/dist/index.js +2 -0
  27. package/dist/lib/ai-tools.d.ts +15 -0
  28. package/dist/lib/ai-tools.js +48 -1
  29. package/dist/lib/api.d.ts +7 -7
  30. package/dist/lib/api.js +11 -11
  31. package/dist/lib/command-adapter.d.ts +4 -3
  32. package/dist/lib/command-adapter.js +37 -688
  33. package/dist/lib/config.d.ts +1 -1
  34. package/dist/lib/config.js +2 -2
  35. package/dist/lib/guide.js +34 -79
  36. package/dist/lib/installer.d.ts +2 -2
  37. package/dist/lib/installer.js +4 -4
  38. package/dist/lib/preamble.d.ts +4 -4
  39. package/dist/lib/preamble.js +14 -14
  40. package/dist/lib/slug.d.ts +5 -0
  41. package/dist/lib/slug.js +49 -2
  42. package/dist/lib/update-cache.js +4 -4
  43. package/dist/lib/version-check.d.ts +3 -3
  44. package/dist/lib/version-check.js +13 -13
  45. package/dist/prompts/_business-card.md +41 -0
  46. package/dist/prompts/_error-handling.md +38 -0
  47. package/dist/prompts/_requirements-check.md +59 -0
  48. package/dist/prompts/_setup-cli.md +19 -0
  49. package/dist/prompts/_setup-login.md +7 -0
  50. package/dist/prompts/_setup-org.md +27 -0
  51. package/dist/prompts/business-card.md +41 -0
  52. package/dist/prompts/error-handling.md +38 -0
  53. package/dist/prompts/index.d.ts +7 -0
  54. package/dist/prompts/index.js +28 -0
  55. package/dist/prompts/install.md +191 -0
  56. package/dist/prompts/publish.md +448 -0
  57. package/dist/prompts/requirements-check.md +59 -0
  58. package/dist/types.d.ts +9 -9
  59. package/package.json +3 -3
@@ -8,7 +8,7 @@ async function claimAccess(slug, code) {
8
8
  if (!token) {
9
9
  throw new Error('LOGIN_REQUIRED');
10
10
  }
11
- const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/claim-access`, {
11
+ const res = await fetch(`${config_js_1.API_URL}/api/agents/${slug}/claim-access`, {
12
12
  method: 'POST',
13
13
  headers: {
14
14
  'Content-Type': 'application/json',
@@ -24,7 +24,7 @@ async function claimAccess(slug, code) {
24
24
  case 'INVALID_LINK':
25
25
  throw new Error('초대 링크가 유효하지 않거나 만료되었습니다.');
26
26
  case 'NOT_FOUND':
27
- throw new Error('팀을 찾을 수 없습니다.');
27
+ throw new Error('에이전트를 찾을 수 없습니다.');
28
28
  case 'UNAUTHORIZED':
29
29
  throw new Error('LOGIN_REQUIRED');
30
30
  default:
@@ -36,31 +36,31 @@ async function claimAccess(slug, code) {
36
36
  function registerAccess(program) {
37
37
  program
38
38
  .command('access <slug>')
39
- .description('초대 코드로 팀에 접근 권한을 얻고 바로 설치합니다')
40
- .requiredOption('--code <code>', ' 초대 코드')
39
+ .description('초대 코드로 에이전트에 접근 권한을 얻고 바로 설치합니다')
40
+ .requiredOption('--code <code>', '에이전트 초대 코드')
41
41
  .action(async (slug, opts) => {
42
42
  const json = program.opts().json ?? false;
43
43
  try {
44
44
  const result = await claimAccess(slug, opts.code);
45
- if (!result.success || !result.team) {
45
+ if (!result.success || !result.agent) {
46
46
  throw new Error('서버 응답이 올바르지 않습니다.');
47
47
  }
48
- const teamSlug = result.team.slug;
48
+ const agentSlug = result.agent.slug;
49
49
  if (json) {
50
- console.log(JSON.stringify({ status: 'ok', team: result.team }));
50
+ console.log(JSON.stringify({ status: 'ok', agent: result.agent }));
51
51
  }
52
52
  else {
53
- console.log(`\x1b[32m접근 권한이 부여되었습니다: ${result.team.name}\x1b[0m`);
54
- console.log(`\x1b[33m팀을 설치합니다: relay install ${teamSlug}\x1b[0m\n`);
53
+ console.log(`\x1b[32m접근 권한이 부여되었습니다: ${result.agent.name}\x1b[0m`);
54
+ console.log(`\x1b[33m에이전트를 설치합니다: relay install ${agentSlug}\x1b[0m\n`);
55
55
  }
56
- // Automatically install the team
56
+ // Automatically install the agent
57
57
  const { registerInstall } = await import('./install.js');
58
58
  const subProgram = new commander_1.Command();
59
59
  subProgram.option('--json', '구조화된 JSON 출력');
60
60
  if (json)
61
61
  subProgram.setOptionValue('json', true);
62
62
  registerInstall(subProgram);
63
- await subProgram.parseAsync(['node', 'relay', 'install', teamSlug]);
63
+ await subProgram.parseAsync(['node', 'relay', 'install', agentSlug]);
64
64
  }
65
65
  catch (err) {
66
66
  const message = err instanceof Error ? err.message : String(err);
@@ -79,7 +79,7 @@ function registerAccess(program) {
79
79
  process.exit(1);
80
80
  }
81
81
  if (json) {
82
- console.error(JSON.stringify({ error: 'ACCESS_FAILED', message, fix: '접근 링크 코드를 확인하거나 제작자에게 문의하세요.' }));
82
+ console.error(JSON.stringify({ error: 'ACCESS_FAILED', message, fix: '접근 링크 코드를 확인하거나 에이전트 제작자에게 문의하세요.' }));
83
83
  }
84
84
  else {
85
85
  console.error(`\x1b[31m오류: ${message}\x1b[0m`);
@@ -10,7 +10,7 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
10
10
  function registerChangelog(program) {
11
11
  const changelog = program
12
12
  .command('changelog')
13
- .description(' 패키지의 changelog를 관리합니다');
13
+ .description('에이전트 패키지의 changelog를 관리합니다');
14
14
  changelog
15
15
  .command('add')
16
16
  .description('relay.yaml에 changelog 엔트리를 추가합니다')
@@ -18,7 +18,7 @@ function registerChangelog(program) {
18
18
  .action(async (message) => {
19
19
  const yamlPath = path_1.default.resolve('relay.yaml');
20
20
  if (!fs_1.default.existsSync(yamlPath)) {
21
- console.error('relay.yaml을 찾을 수 없습니다. 패키지 디렉토리에서 실행하세요.');
21
+ console.error('relay.yaml을 찾을 수 없습니다. 에이전트 패키지 디렉토리에서 실행하세요.');
22
22
  process.exit(1);
23
23
  }
24
24
  const content = fs_1.default.readFileSync(yamlPath, 'utf-8');
@@ -6,7 +6,7 @@ const slug_js_1 = require("../lib/slug.js");
6
6
  function registerCheckUpdate(program) {
7
7
  program
8
8
  .command('check-update [slug]')
9
- .description('CLI 및 설치된 팀의 업데이트를 확인합니다')
9
+ .description('CLI 및 설치된 에이전트의 업데이트를 확인합니다')
10
10
  .option('--quiet', '업데이트가 있을 때만 머신 리더블 출력')
11
11
  .option('--force', '캐시를 무시하고 강제 체크')
12
12
  .action(async (slug, opts) => {
@@ -23,7 +23,7 @@ function registerCheckUpdate(program) {
23
23
  console.log(` 실행: npm update -g relayax-cli\n`);
24
24
  }
25
25
  }
26
- // Team version check
26
+ // Agent version check
27
27
  if (slug) {
28
28
  // Resolve to scoped slug
29
29
  let scopedSlug;
@@ -39,15 +39,15 @@ function registerCheckUpdate(program) {
39
39
  scopedSlug = slug;
40
40
  }
41
41
  }
42
- const teamResult = await (0, version_check_js_1.checkTeamVersion)(scopedSlug, force);
43
- if (teamResult) {
42
+ const agentResult = await (0, version_check_js_1.checkAgentVersion)(scopedSlug, force);
43
+ if (agentResult) {
44
44
  if (quiet) {
45
- const byAuthor = teamResult.author ? ` ${teamResult.author}` : '';
46
- console.log(`TEAM_UPGRADE_AVAILABLE ${slug} ${teamResult.current} ${teamResult.latest}${byAuthor}`);
45
+ const byAuthor = agentResult.author ? ` ${agentResult.author}` : '';
46
+ console.log(`AGENT_UPGRADE_AVAILABLE ${slug} ${agentResult.current} ${agentResult.latest}${byAuthor}`);
47
47
  }
48
48
  else {
49
- const byAuthor = teamResult.author ? ` \x1b[90m(by @${teamResult.author})\x1b[0m` : '';
50
- console.log(`\x1b[33m⚠ ${slug} v${teamResult.latest} available\x1b[0m${byAuthor} (현재 v${teamResult.current})`);
49
+ const byAuthor = agentResult.author ? ` \x1b[90m(by @${agentResult.author})\x1b[0m` : '';
50
+ console.log(`\x1b[33m⚠ ${slug} v${agentResult.latest} available\x1b[0m${byAuthor} (현재 v${agentResult.current})`);
51
51
  console.log(` 실행: relay update ${slug}`);
52
52
  }
53
53
  }
@@ -56,11 +56,11 @@ function registerCheckUpdate(program) {
56
56
  }
57
57
  }
58
58
  else {
59
- const teamResults = await (0, version_check_js_1.checkAllTeams)(force);
60
- for (const result of teamResults) {
59
+ const agentResults = await (0, version_check_js_1.checkAllAgents)(force);
60
+ for (const result of agentResults) {
61
61
  if (quiet) {
62
62
  const byAuthor = result.author ? ` ${result.author}` : '';
63
- console.log(`TEAM_UPGRADE_AVAILABLE ${result.slug} ${result.current} ${result.latest}${byAuthor}`);
63
+ console.log(`AGENT_UPGRADE_AVAILABLE ${result.slug} ${result.current} ${result.latest}${byAuthor}`);
64
64
  }
65
65
  else {
66
66
  const byAuthor = result.author ? ` \x1b[90m(by @${result.author})\x1b[0m` : '';
@@ -68,7 +68,7 @@ function registerCheckUpdate(program) {
68
68
  console.log(` 실행: relay update ${result.slug}`);
69
69
  }
70
70
  }
71
- if (!quiet && !cliResult && teamResults.length === 0) {
71
+ if (!quiet && !cliResult && agentResults.length === 0) {
72
72
  console.log('모든 것이 최신 상태입니다.');
73
73
  }
74
74
  }
@@ -10,6 +10,7 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
10
10
  const ai_tools_js_1 = require("../lib/ai-tools.js");
11
11
  const command_adapter_js_1 = require("../lib/command-adapter.js");
12
12
  const init_js_1 = require("./init.js");
13
+ const slug_js_1 = require("../lib/slug.js");
13
14
  const DEFAULT_DIRS = ['.relay/skills', '.relay/commands'];
14
15
  /**
15
16
  * 글로벌 User 커맨드가 없으면 설치한다.
@@ -23,10 +24,11 @@ function ensureGlobalUserCommands() {
23
24
  function registerCreate(program) {
24
25
  program
25
26
  .command('create <name>')
26
- .description('새 에이전트 프로젝트를 생성합니다')
27
- .option('--description <desc>', ' 설명')
27
+ .description('새 에이전트 프로젝트를 생성합니다')
28
+ .option('--description <desc>', '에이전트 설명')
29
+ .option('--slug <slug>', 'URL용 식별자 (영문 소문자, 숫자, 하이픈)')
28
30
  .option('--tags <tags>', '태그 (쉼표 구분)')
29
- .option('--visibility <visibility>', '공개 범위 (public, gated, private)')
31
+ .option('--visibility <visibility>', '공개 범위 (public, private, internal)')
30
32
  .action(async (name, opts) => {
31
33
  const json = program.opts().json ?? false;
32
34
  const projectPath = process.cwd();
@@ -39,21 +41,30 @@ function registerCreate(program) {
39
41
  console.error(JSON.stringify({ error: 'ALREADY_EXISTS', message: '.relay/relay.yaml이 이미 존재합니다.', fix: '기존 .relay/relay.yaml을 확인하세요. 새로 시작하려면 삭제 후 재시도.' }));
40
42
  }
41
43
  else {
42
- console.error('.relay/relay.yaml이 이미 존재합니다. 기존 프로젝트에서는 `relay init`을 사용하세요.');
44
+ console.error('.relay/relay.yaml이 이미 존재합니다. 기존 에이전트 프로젝트에서는 `relay init`을 사용하세요.');
43
45
  }
44
46
  process.exit(1);
45
47
  }
46
48
  // 2. 메타데이터 수집
47
- const defaultSlug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
49
+ let slug = opts.slug ?? (0, slug_js_1.slugify)(name);
48
50
  let description = opts.description ?? '';
49
51
  let tags = opts.tags ? opts.tags.split(',').map((t) => t.trim()).filter(Boolean) : [];
50
52
  let visibility = opts.visibility ?? 'public';
51
53
  if (json) {
54
+ // --json 모드: slug가 비어있으면 에러
55
+ if (!slug) {
56
+ console.error(JSON.stringify({
57
+ error: 'INVALID_SLUG',
58
+ message: '이름에서 유효한 slug를 생성할 수 없습니다. 영문 이름을 사용하거나 --slug 옵션을 지정하세요.',
59
+ fix: `relay create "${name}" --slug <영문-slug> --description <설명> --json`,
60
+ }));
61
+ process.exit(1);
62
+ }
52
63
  // --json 모드: 필수 값 부족 시 에러 반환 (프롬프트 없음)
53
64
  if (!opts.description) {
54
65
  console.error(JSON.stringify({
55
66
  error: 'MISSING_FIELD',
56
- message: ' 설명이 필요합니다.',
67
+ message: '에이전트 설명이 필요합니다.',
57
68
  fix: `relay create ${name} --description <설명> --json`,
58
69
  field: 'description',
59
70
  }));
@@ -66,21 +77,21 @@ function registerCreate(program) {
66
77
  fix: `relay create ${name} --description "${description}" --visibility <visibility> --json`,
67
78
  options: [
68
79
  { value: 'public', label: '공개 — 누구나 설치' },
69
- { value: 'gated', label: '링크 공유 — 접근 링크가 있는 사람만' },
70
- { value: 'private', label: '비공개 — Space 멤버만' },
80
+ { value: 'private', label: '링크 공유 — 접근 링크가 있는 사람만' },
81
+ { value: 'internal', label: '비공개 — Org 멤버만' },
71
82
  ],
72
83
  }));
73
84
  process.exit(1);
74
85
  }
75
- if (!['public', 'gated', 'private'].includes(opts.visibility)) {
86
+ if (!['public', 'private', 'internal'].includes(opts.visibility)) {
76
87
  console.error(JSON.stringify({
77
88
  error: 'INVALID_FIELD',
78
89
  message: `유효하지 않은 visibility 값: ${opts.visibility}`,
79
- fix: `visibility는 public, gated, private 중 하나여야 합니다.`,
90
+ fix: `visibility는 public, private, internal 중 하나여야 합니다.`,
80
91
  options: [
81
92
  { value: 'public', label: '공개' },
82
- { value: 'gated', label: '링크 공유' },
83
- { value: 'private', label: '비공개' },
93
+ { value: 'private', label: '링크 공유' },
94
+ { value: 'internal', label: '비공개' },
84
95
  ],
85
96
  }));
86
97
  process.exit(1);
@@ -88,10 +99,25 @@ function registerCreate(program) {
88
99
  }
89
100
  else if (isTTY) {
90
101
  const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
91
- console.log(`\n \x1b[33m⚡\x1b[0m \x1b[1mrelay create\x1b[0m — 새 프로젝트\n`);
102
+ console.log(`\n \x1b[33m⚡\x1b[0m \x1b[1mrelay create\x1b[0m — 새 에이전트 프로젝트\n`);
103
+ // slug가 비어있으면 (한국어 등 비ASCII 이름) slug를 직접 입력받음
104
+ if (!slug) {
105
+ slug = await promptInput({
106
+ message: 'Slug (URL/설치에 사용되는 영문 식별자):',
107
+ validate: (v) => {
108
+ const trimmed = v.trim();
109
+ if (!trimmed)
110
+ return 'slug를 입력해주세요.';
111
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(trimmed))
112
+ return '소문자, 숫자, 하이픈만 사용 가능합니다.';
113
+ return true;
114
+ },
115
+ });
116
+ slug = slug.trim();
117
+ }
92
118
  if (!description) {
93
119
  description = await promptInput({
94
- message: ' 설명:',
120
+ message: '에이전트 설명:',
95
121
  validate: (v) => v.trim().length > 0 ? true : '설명을 입력해주세요.',
96
122
  });
97
123
  }
@@ -107,8 +133,8 @@ function registerCreate(program) {
107
133
  message: '공개 범위:',
108
134
  choices: [
109
135
  { name: '공개', value: 'public' },
110
- { name: '링크 공유 (접근 링크 필요)', value: 'gated' },
111
- { name: '비공개 (Space 멤버만)', value: 'private' },
136
+ { name: '링크 공유 (접근 링크 필요)', value: 'private' },
137
+ { name: '비공개 (Org 멤버만)', value: 'internal' },
112
138
  ],
113
139
  });
114
140
  }
@@ -117,12 +143,13 @@ function registerCreate(program) {
117
143
  fs_1.default.mkdirSync(relayDir, { recursive: true });
118
144
  const yamlData = {
119
145
  name,
120
- slug: defaultSlug,
146
+ slug: slug,
121
147
  description,
122
148
  version: '1.0.0',
123
149
  type: 'hybrid',
124
150
  tags,
125
151
  visibility,
152
+ contents: [],
126
153
  };
127
154
  fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
128
155
  // 4. 디렉토리 구조 생성
@@ -155,7 +182,7 @@ function registerCreate(program) {
155
182
  console.log(JSON.stringify({
156
183
  status: 'ok',
157
184
  name,
158
- slug: defaultSlug,
185
+ slug: slug,
159
186
  relay_yaml: 'created',
160
187
  directories: createdDirs,
161
188
  local_commands: localResults,
@@ -163,7 +190,7 @@ function registerCreate(program) {
163
190
  }));
164
191
  }
165
192
  else {
166
- console.log(`\n\x1b[32m✓ ${name} 프로젝트 생성 완료\x1b[0m\n`);
193
+ console.log(`\n\x1b[32m✓ ${name} 에이전트 프로젝트 생성 완료\x1b[0m\n`);
167
194
  console.log(` .relay/relay.yaml 생성됨`);
168
195
  if (createdDirs.length > 0) {
169
196
  console.log(` 디렉토리 생성: ${createdDirs.join(', ')}`);
@@ -31,7 +31,7 @@ function registerDeployRecord(program) {
31
31
  const resolvedFiles = files.map((f) => f.startsWith('/') || f.startsWith('~')
32
32
  ? f
33
33
  : path_1.default.resolve(f));
34
- // Find the team in the appropriate registry
34
+ // Find the agent in the appropriate registry
35
35
  const localRegistry = (0, config_js_1.loadInstalled)();
36
36
  const globalRegistry = (0, config_js_1.loadGlobalInstalled)();
37
37
  // Resolve slug — check both registries for short name match
@@ -47,7 +47,7 @@ function registerDeployRecord(program) {
47
47
  });
48
48
  slug = match ?? slugInput;
49
49
  }
50
- // Check if team exists in either registry
50
+ // Check if agent exists in either registry
51
51
  const entry = localRegistry[slug] ?? globalRegistry[slug];
52
52
  if (!entry) {
53
53
  const msg = { error: 'NOT_INSTALLED', message: `'${slugInput}'는 설치되어 있지 않습니다.` };
@@ -12,7 +12,7 @@ function registerDiff(program) {
12
12
  const json = program.opts().json ?? false;
13
13
  try {
14
14
  const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
15
- const versions = await (0, api_js_1.fetchTeamVersions)(resolved.full);
15
+ const versions = await (0, api_js_1.fetchAgentVersions)(resolved.full);
16
16
  const ver1 = versions.find((v) => v.version === v1);
17
17
  const ver2 = versions.find((v) => v.version === v2);
18
18
  if (!ver1 || !ver2) {
@@ -29,7 +29,7 @@ function registerDiff(program) {
29
29
  // For now, we use the current version's package_url as fallback
30
30
  // The registry API returns the latest version; for specific versions,
31
31
  // we'd need a version-specific endpoint
32
- const info = await (0, api_js_1.fetchTeamInfo)(resolved.full);
32
+ const info = await (0, api_js_1.fetchAgentInfo)(resolved.full);
33
33
  if (!info.package_url) {
34
34
  throw new Error('패키지 URL을 가져올 수 없습니다');
35
35
  }
@@ -0,0 +1,33 @@
1
+ import { Command } from 'commander';
2
+ interface AccessCodeResult {
3
+ status: string;
4
+ type: 'org' | 'agent';
5
+ org_id?: string;
6
+ agent_id?: string;
7
+ role?: string;
8
+ }
9
+ /**
10
+ * Use an access code — the code type (org/agent) is resolved server-side.
11
+ * For org codes: joins the org as member.
12
+ * For agent codes: grants agent access (+ auto org join for org private agents).
13
+ */
14
+ export declare function useAccessCode(code: string): Promise<AccessCodeResult>;
15
+ interface CreateAccessCodeResult {
16
+ id: string;
17
+ code: string;
18
+ type: string;
19
+ max_uses: number | null;
20
+ expires_at: string | null;
21
+ }
22
+ /**
23
+ * Create a new access code for an agent or org.
24
+ */
25
+ export declare function createAccessCode(opts: {
26
+ type: 'org' | 'agent';
27
+ org_id?: string;
28
+ agent_id?: string;
29
+ max_uses?: number;
30
+ expires_at?: string;
31
+ }): Promise<CreateAccessCodeResult>;
32
+ export declare function registerGrant(program: Command): void;
33
+ export {};
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAccessCode = useAccessCode;
4
+ exports.createAccessCode = createAccessCode;
5
+ exports.registerGrant = registerGrant;
6
+ const config_js_1 = require("../lib/config.js");
7
+ const init_js_1 = require("./init.js");
8
+ /**
9
+ * Use an access code — the code type (org/agent) is resolved server-side.
10
+ * For org codes: joins the org as member.
11
+ * For agent codes: grants agent access (+ auto org join for org private agents).
12
+ */
13
+ async function useAccessCode(code) {
14
+ const token = await (0, config_js_1.getValidToken)();
15
+ if (!token) {
16
+ throw new Error('LOGIN_REQUIRED');
17
+ }
18
+ const res = await fetch(`${config_js_1.API_URL}/api/access-codes/${code}/use`, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ Authorization: `Bearer ${token}`,
23
+ },
24
+ });
25
+ if (!res.ok) {
26
+ const body = await res.json().catch(() => ({}));
27
+ const errCode = body.error ?? String(res.status);
28
+ switch (errCode) {
29
+ case 'INVALID_LINK':
30
+ throw new Error('접근 코드가 유효하지 않거나 만료되었습니다.');
31
+ default:
32
+ throw new Error(body.message ?? `접근 코드 사용 실패 (${res.status})`);
33
+ }
34
+ }
35
+ return res.json();
36
+ }
37
+ /**
38
+ * Create a new access code for an agent or org.
39
+ */
40
+ async function createAccessCode(opts) {
41
+ const token = await (0, config_js_1.getValidToken)();
42
+ if (!token) {
43
+ throw new Error('LOGIN_REQUIRED');
44
+ }
45
+ const res = await fetch(`${config_js_1.API_URL}/api/access-codes`, {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ Authorization: `Bearer ${token}`,
50
+ },
51
+ body: JSON.stringify(opts),
52
+ });
53
+ if (!res.ok) {
54
+ const body = await res.json().catch(() => ({}));
55
+ throw new Error(body.message ?? `접근 코드 생성 실패 (${res.status})`);
56
+ }
57
+ return res.json();
58
+ }
59
+ function ensureInit(json) {
60
+ if (!(0, init_js_1.hasGlobalUserCommands)()) {
61
+ if (json) {
62
+ console.error(JSON.stringify({ error: 'NOT_INITIALIZED', message: 'relay init을 먼저 실행하세요.', fix: 'relay init 실행하세요.' }));
63
+ }
64
+ else {
65
+ console.error('\x1b[33m⚠ relay init이 실행되지 않았습니다. 먼저 relay init을 실행하세요.\x1b[0m');
66
+ }
67
+ process.exit(1);
68
+ }
69
+ }
70
+ function handleError(err, json) {
71
+ const message = err instanceof Error ? err.message : String(err);
72
+ if (message === 'LOGIN_REQUIRED') {
73
+ if (json) {
74
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다.', fix: 'relay login 실행 후 재시도하세요.' }));
75
+ }
76
+ else {
77
+ console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
78
+ console.error(' relay login 을 먼저 실행하세요.');
79
+ }
80
+ process.exit(1);
81
+ }
82
+ if (json) {
83
+ console.error(JSON.stringify({ error: 'GRANT_FAILED', message, fix: '접근 코드를 확인 후 재시도하세요.' }));
84
+ }
85
+ else {
86
+ console.error(`\x1b[31m오류: ${message}\x1b[0m`);
87
+ }
88
+ process.exit(1);
89
+ }
90
+ function registerGrant(program) {
91
+ const grant = program
92
+ .command('grant')
93
+ .description('접근 코드를 사용하거나 생성합니다');
94
+ // relay grant --code <code> (use an access code)
95
+ grant
96
+ .command('use')
97
+ .description('접근 코드를 사용하여 org 가입 또는 에이전트 접근 권한을 획득합니다')
98
+ .requiredOption('--code <code>', '접근 코드')
99
+ .action(async (opts) => {
100
+ const json = program.opts().json ?? false;
101
+ ensureInit(json);
102
+ try {
103
+ const result = await useAccessCode(opts.code);
104
+ if (json) {
105
+ console.log(JSON.stringify({ ...result, status: 'ok' }));
106
+ }
107
+ else {
108
+ if (result.type === 'org') {
109
+ console.log(`\x1b[32m✅ Organization에 가입했습니다 (역할: ${result.role ?? 'member'})\x1b[0m`);
110
+ }
111
+ else {
112
+ console.log(`\x1b[32m✅ 에이전트 접근 권한이 부여되었습니다\x1b[0m`);
113
+ }
114
+ }
115
+ }
116
+ catch (err) {
117
+ handleError(err, json);
118
+ }
119
+ });
120
+ // relay grant create --agent <slug> [--max-uses N] [--expires-at DATE]
121
+ grant
122
+ .command('create')
123
+ .description('에이전트 또는 org의 접근 코드를 생성합니다')
124
+ .option('--agent <slug>', '에이전트 slug')
125
+ .option('--org <slug>', 'Organization slug')
126
+ .option('--max-uses <n>', '최대 사용 횟수', parseInt)
127
+ .option('--expires-at <date>', '만료일 (ISO 8601)')
128
+ .action(async (opts) => {
129
+ const json = program.opts().json ?? false;
130
+ ensureInit(json);
131
+ if (!opts.agent && !opts.org) {
132
+ const msg = '--agent 또는 --org 옵션이 필요합니다.';
133
+ if (json) {
134
+ console.error(JSON.stringify({ error: 'MISSING_OPTION', message: msg }));
135
+ }
136
+ else {
137
+ console.error(`\x1b[31m오류: ${msg}\x1b[0m`);
138
+ }
139
+ process.exit(1);
140
+ }
141
+ try {
142
+ const token = await (0, config_js_1.getValidToken)();
143
+ if (!token)
144
+ throw new Error('LOGIN_REQUIRED');
145
+ // Resolve agent/org ID from slug
146
+ let agentId;
147
+ let orgId;
148
+ if (opts.agent) {
149
+ const res = await fetch(`${config_js_1.API_URL}/api/agents/${opts.agent}`, {
150
+ headers: { Authorization: `Bearer ${token}` },
151
+ });
152
+ if (!res.ok)
153
+ throw new Error('에이전트를 찾을 수 없습니다.');
154
+ const agent = await res.json();
155
+ agentId = agent.id;
156
+ }
157
+ if (opts.org) {
158
+ const res = await fetch(`${config_js_1.API_URL}/api/orgs/${opts.org}`, {
159
+ headers: { Authorization: `Bearer ${token}` },
160
+ });
161
+ if (!res.ok)
162
+ throw new Error('Organization을 찾을 수 없습니다.');
163
+ const org = await res.json();
164
+ orgId = org.id;
165
+ }
166
+ const result = await createAccessCode({
167
+ type: agentId ? 'agent' : 'org',
168
+ agent_id: agentId,
169
+ org_id: orgId,
170
+ max_uses: opts.maxUses,
171
+ expires_at: opts.expiresAt,
172
+ });
173
+ if (json) {
174
+ console.log(JSON.stringify({ status: 'created', ...result }));
175
+ }
176
+ else {
177
+ console.log(`\x1b[32m✅ 접근 코드가 생성되었습니다\x1b[0m`);
178
+ console.log(`\n 코드: \x1b[36m${result.code}\x1b[0m`);
179
+ if (result.max_uses)
180
+ console.log(` 최대 사용: ${result.max_uses}회`);
181
+ if (result.expires_at)
182
+ console.log(` 만료: ${new Date(result.expires_at).toLocaleDateString('ko-KR')}`);
183
+ console.log(`\n \x1b[90m사용 방법: relay grant use --code ${result.code}\x1b[0m`);
184
+ }
185
+ }
186
+ catch (err) {
187
+ handleError(err, json);
188
+ }
189
+ });
190
+ }