relayax-cli 0.3.41 → 0.3.42

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 (58) 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 +10 -10
  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 +7 -7
  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.js +30 -682
  32. package/dist/lib/config.d.ts +1 -1
  33. package/dist/lib/config.js +2 -2
  34. package/dist/lib/guide.js +34 -79
  35. package/dist/lib/installer.d.ts +2 -2
  36. package/dist/lib/installer.js +4 -4
  37. package/dist/lib/preamble.d.ts +4 -4
  38. package/dist/lib/preamble.js +14 -14
  39. package/dist/lib/slug.d.ts +5 -0
  40. package/dist/lib/slug.js +49 -2
  41. package/dist/lib/update-cache.js +4 -4
  42. package/dist/lib/version-check.d.ts +3 -3
  43. package/dist/lib/version-check.js +13 -13
  44. package/dist/prompts/_business-card.md +41 -0
  45. package/dist/prompts/_error-handling.md +38 -0
  46. package/dist/prompts/_requirements-check.md +59 -0
  47. package/dist/prompts/_setup-cli.md +19 -0
  48. package/dist/prompts/_setup-login.md +7 -0
  49. package/dist/prompts/_setup-org.md +27 -0
  50. package/dist/prompts/business-card.md +41 -0
  51. package/dist/prompts/error-handling.md +38 -0
  52. package/dist/prompts/index.d.ts +7 -0
  53. package/dist/prompts/index.js +28 -0
  54. package/dist/prompts/install.md +187 -0
  55. package/dist/prompts/publish.md +444 -0
  56. package/dist/prompts/requirements-check.md +59 -0
  57. package/dist/types.d.ts +9 -9
  58. package/package.json +3 -3
@@ -23,8 +23,8 @@ function parseRelayYaml(content) {
23
23
  : [];
24
24
  const requires = raw.requires;
25
25
  const rawVisibility = String(raw.visibility ?? '');
26
- const visibility = rawVisibility === 'private' ? 'private'
27
- : rawVisibility === 'gated' ? 'gated'
26
+ const visibility = rawVisibility === 'internal' ? 'internal'
27
+ : rawVisibility === 'private' ? 'private'
28
28
  : rawVisibility === 'public' ? 'public'
29
29
  : undefined;
30
30
  const rawType = String(raw.type ?? '');
@@ -46,8 +46,8 @@ function parseRelayYaml(content) {
46
46
  source: raw.source ? String(raw.source) : undefined,
47
47
  };
48
48
  }
49
- function detectCommands(teamDir) {
50
- const cmdDir = path_1.default.join(teamDir, 'commands');
49
+ function detectCommands(agentDir) {
50
+ const cmdDir = path_1.default.join(agentDir, 'commands');
51
51
  if (!fs_1.default.existsSync(cmdDir))
52
52
  return [];
53
53
  const entries = [];
@@ -78,8 +78,8 @@ function detectCommands(teamDir) {
78
78
  }
79
79
  return entries;
80
80
  }
81
- function detectSkills(teamDir) {
82
- const skillsDir = path_1.default.join(teamDir, 'skills');
81
+ function detectSkills(agentDir) {
82
+ const skillsDir = path_1.default.join(agentDir, 'skills');
83
83
  if (!fs_1.default.existsSync(skillsDir))
84
84
  return [];
85
85
  const entries = [];
@@ -118,8 +118,8 @@ function detectSkills(teamDir) {
118
118
  return entries;
119
119
  }
120
120
  const MCP_KEYWORDS = ['mcp', 'supabase', 'github', 'slack', 'notion', 'linear', 'jira', 'figma', 'stripe', 'openai', 'anthropic', 'postgres', 'mysql', 'redis', 'mongodb', 'firebase', 'aws', 'gcp', 'azure', 'vercel', 'netlify', 'docker', 'kubernetes'];
121
- function detectAgentDetails(teamDir, requires) {
122
- const agentsDir = path_1.default.join(teamDir, 'agents');
121
+ function detectAgentDetails(agentDir, requires) {
122
+ const agentsDir = path_1.default.join(agentDir, 'agents');
123
123
  if (!fs_1.default.existsSync(agentsDir))
124
124
  return [];
125
125
  const mcpNames = new Set((requires?.mcp ?? []).map((m) => m.name.toLowerCase()));
@@ -168,8 +168,8 @@ function detectAgentDetails(teamDir, requires) {
168
168
  return entries;
169
169
  }
170
170
  /**
171
- * 진입점 커맨드(commands/{author}-{name}.md)를 생성한다.
172
- * root SKILL.md를 대체하여 팀의 얼굴 역할을 한다.
171
+ * 에이전트 진입점 커맨드(commands/{author}-{name}.md)를 생성한다.
172
+ * root SKILL.md를 대체하여 에이전트의 얼굴 역할을 한다.
173
173
  */
174
174
  function generateEntryCommand(config, commands, skills, scopedSlug) {
175
175
  const lines = [];
@@ -181,7 +181,7 @@ function generateEntryCommand(config, commands, skills, scopedSlug) {
181
181
  // Preamble
182
182
  lines.push((0, preamble_js_1.generatePreamble)(scopedSlug));
183
183
  lines.push('');
184
- // Team header
184
+ // Agent header
185
185
  lines.push(`## ${config.name}`);
186
186
  lines.push('');
187
187
  lines.push(`v${config.version} — ${scopedSlug}`);
@@ -210,14 +210,14 @@ function generateEntryCommand(config, commands, skills, scopedSlug) {
210
210
  lines.push('');
211
211
  return lines.join('\n');
212
212
  }
213
- function countDir(teamDir, dirName) {
214
- const dirPath = path_1.default.join(teamDir, dirName);
213
+ function countDir(agentDir, dirName) {
214
+ const dirPath = path_1.default.join(agentDir, dirName);
215
215
  if (!fs_1.default.existsSync(dirPath))
216
216
  return 0;
217
217
  return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length;
218
218
  }
219
- function listDir(teamDir, dirName) {
220
- const dirPath = path_1.default.join(teamDir, dirName);
219
+ function listDir(agentDir, dirName) {
220
+ const dirPath = path_1.default.join(agentDir, dirName);
221
221
  if (!fs_1.default.existsSync(dirPath))
222
222
  return [];
223
223
  return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.'));
@@ -227,10 +227,10 @@ function listDir(teamDir, dirName) {
227
227
  * 1. relay.yaml에 있으면 사용
228
228
  * 2. README.md가 있으면 fallback
229
229
  */
230
- function resolveLongDescription(teamDir, yamlValue) {
230
+ function resolveLongDescription(agentDir, yamlValue) {
231
231
  if (yamlValue)
232
232
  return yamlValue;
233
- const readmePath = path_1.default.join(teamDir, 'README.md');
233
+ const readmePath = path_1.default.join(agentDir, 'README.md');
234
234
  if (fs_1.default.existsSync(readmePath)) {
235
235
  try {
236
236
  return fs_1.default.readFileSync(readmePath, 'utf-8').trim() || undefined;
@@ -241,21 +241,21 @@ function resolveLongDescription(teamDir, yamlValue) {
241
241
  }
242
242
  return undefined;
243
243
  }
244
- async function createTarball(teamDir) {
244
+ async function createTarball(agentDir) {
245
245
  const tmpFile = path_1.default.join(os_1.default.tmpdir(), `relay-publish-${Date.now()}.tar.gz`);
246
- const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(teamDir, d)));
246
+ const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(agentDir, d)));
247
247
  // Include root-level files if they exist
248
248
  const entries = [...dirsToInclude];
249
249
  const rootFiles = ['relay.yaml', 'SKILL.md', 'guide.md'];
250
250
  for (const file of rootFiles) {
251
- if (fs_1.default.existsSync(path_1.default.join(teamDir, file))) {
251
+ if (fs_1.default.existsSync(path_1.default.join(agentDir, file))) {
252
252
  entries.push(file);
253
253
  }
254
254
  }
255
255
  await (0, tar_1.create)({
256
256
  gzip: true,
257
257
  file: tmpFile,
258
- cwd: teamDir,
258
+ cwd: agentDir,
259
259
  }, entries);
260
260
  return tmpFile;
261
261
  }
@@ -281,14 +281,14 @@ async function publishToApi(token, tarPath, metadata) {
281
281
  function registerPublish(program) {
282
282
  program
283
283
  .command('publish')
284
- .description('현재 패키지를 Space에 배포합니다 (relay.yaml 필요)')
284
+ .description('현재 에이전트 패키지를 Space에 배포합니다 (relay.yaml 필요)')
285
285
  .option('--token <token>', '인증 토큰')
286
286
  .option('--space <slug>', '배포할 Space 지정')
287
287
  .option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
288
288
  .action(async (opts) => {
289
289
  const json = program.opts().json ?? false;
290
- const teamDir = process.cwd();
291
- const relayDir = path_1.default.join(teamDir, '.relay');
290
+ const agentDir = process.cwd();
291
+ const relayDir = path_1.default.join(agentDir, '.relay');
292
292
  const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
293
293
  const isTTY = Boolean(process.stdin.isTTY) && !json;
294
294
  // CLI update check before publish
@@ -327,12 +327,12 @@ function registerPublish(program) {
327
327
  }
328
328
  // Interactive onboarding: create relay.yaml
329
329
  const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
330
- const dirName = path_1.default.basename(teamDir);
330
+ const dirName = path_1.default.basename(agentDir);
331
331
  const defaultSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
332
- console.error('\n\x1b[36m릴레이 패키지를 초기화합니다.\x1b[0m');
332
+ console.error('\n\x1b[36m릴레이 에이전트 패키지를 초기화합니다.\x1b[0m');
333
333
  console.error('.relay/relay.yaml을 생성하기 위해 몇 가지 정보를 입력해주세요.\n');
334
334
  const name = await promptInput({
335
- message: ' 이름:',
335
+ message: '에이전트 이름:',
336
336
  default: dirName,
337
337
  });
338
338
  const slug = await promptInput({
@@ -340,7 +340,7 @@ function registerPublish(program) {
340
340
  default: defaultSlug,
341
341
  });
342
342
  const description = await promptInput({
343
- message: ' 설명 (필수):',
343
+ message: '에이전트 설명 (필수):',
344
344
  validate: (v) => v.trim().length > 0 ? true : '설명을 입력해주세요.',
345
345
  });
346
346
  const tagsRaw = await promptInput({
@@ -351,16 +351,16 @@ function registerPublish(program) {
351
351
  message: '공개 범위:',
352
352
  choices: [
353
353
  { name: '공개 — 누구나 설치', value: 'public' },
354
- { name: '링크 공유 — 접근 링크가 있는 사람만 설치', value: 'gated' },
355
- { name: '비공개 — Space 멤버만', value: 'private' },
354
+ { name: '링크 공유 — 접근 링크가 있는 사람만 설치', value: 'private' },
355
+ { name: '비공개 — Org 멤버만', value: 'internal' },
356
356
  ],
357
357
  });
358
358
  console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile\x1b[0m');
359
- if (visibility === 'gated') {
360
- console.error('\x1b[2m💡 링크 공유 팀은 웹 대시보드에서 접근 링크와 구매 안내를 설정하세요: www.relayax.com/dashboard\x1b[0m');
359
+ if (visibility === 'private') {
360
+ console.error('\x1b[2m💡 링크 공유 에이전트는 웹 대시보드에서 접근 링크와 구매 안내를 설정하세요: www.relayax.com/dashboard\x1b[0m');
361
361
  }
362
- else if (visibility === 'private') {
363
- console.error('\x1b[2m💡 비공개 팀은 Space를 통해 멤버를 관리하세요: www.relayax.com/dashboard/teams\x1b[0m');
362
+ else if (visibility === 'internal') {
363
+ console.error('\x1b[2m💡 비공개 에이전트는 Org를 통해 멤버를 관리하세요: www.relayax.com/dashboard/agents\x1b[0m');
364
364
  }
365
365
  console.error('');
366
366
  const tags = tagsRaw
@@ -537,11 +537,11 @@ function registerPublish(program) {
537
537
  },
538
538
  {
539
539
  name: '링크 공유 — 접근 링크가 있는 사람만 설치',
540
- value: 'gated',
540
+ value: 'private',
541
541
  },
542
542
  {
543
- name: `비공개 — Space 멤버만 접근`,
544
- value: 'private',
543
+ name: `비공개 — Org 멤버만 접근`,
544
+ value: 'internal',
545
545
  },
546
546
  ],
547
547
  default: defaultVisibility,
@@ -558,8 +558,8 @@ function registerPublish(program) {
558
558
  message: 'relay.yaml에 visibility를 설정해주세요.',
559
559
  options: [
560
560
  { value: 'public', label: '공개 — 누구나 설치' },
561
- { value: 'gated', label: '링크 공유 — 접근 링크가 있는 사람만 설치' },
562
- { value: 'private', label: '비공개 — Space 멤버만 접근' },
561
+ { value: 'private', label: '링크 공유 — 접근 링크가 있는 사람만 설치' },
562
+ { value: 'internal', label: '비공개 — Org 멤버만 접근' },
563
563
  ],
564
564
  fix: 'relay.yaml의 visibility 필드를 위 옵션 중 하나로 설정하세요.',
565
565
  }));
@@ -571,8 +571,8 @@ function registerPublish(program) {
571
571
  const { select: promptConfirmVis } = await import('@inquirer/prompts');
572
572
  const visLabelMap = {
573
573
  public: '공개',
574
- gated: '링크공유',
575
- private: '비공개',
574
+ private: '링크공유',
575
+ internal: '비공개',
576
576
  };
577
577
  const currentVisLabel = visLabelMap[config.visibility ?? 'public'] ?? config.visibility;
578
578
  const newVisibility = await promptConfirmVis({
@@ -584,11 +584,11 @@ function registerPublish(program) {
584
584
  },
585
585
  {
586
586
  name: '링크공유 — 접근 링크가 있는 사람만 설치',
587
- value: 'gated',
587
+ value: 'private',
588
588
  },
589
589
  {
590
- name: `비공개 — Space 멤버만 접근`,
591
- value: 'private',
590
+ name: `비공개 — Org 멤버만 접근`,
591
+ value: 'internal',
592
592
  },
593
593
  ],
594
594
  default: config.visibility ?? defaultVisibility,
@@ -641,7 +641,7 @@ function registerPublish(program) {
641
641
  // GUIDE.html deprecation warning
642
642
  if (fs_1.default.existsSync(path_1.default.join(relayDir, 'GUIDE.html'))) {
643
643
  console.error('\x1b[33m⚠ GUIDE.html은 더 이상 지원되지 않습니다. 상세페이지가 가이드 역할을 합니다.\x1b[0m');
644
- console.error(' long_description을 활용하거나 relayax.com에서 정보를 편집하세요.\n');
644
+ console.error(' long_description을 활용하거나 relayax.com에서 에이전트 정보를 편집하세요.\n');
645
645
  }
646
646
  // Generate guide.md (consumer install guide)
647
647
  const { generateGuide } = await import('../lib/guide.js');
@@ -711,7 +711,10 @@ function registerPublish(program) {
711
711
  // Show shareable onboarding guide as a plain copyable block
712
712
  if (isTTY) {
713
713
  const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
714
- const guideUrl = `https://relayax.com/api/registry/${detailSlug}/guide.md`;
714
+ const accessCode = result.access_code;
715
+ const guideUrl = accessCode
716
+ ? `https://relayax.com/api/registry/${detailSlug}/guide.md?code=${accessCode}`
717
+ : `https://relayax.com/api/registry/${detailSlug}/guide.md`;
715
718
  console.log(`\n \x1b[90m주변인에게 공유하세요:\x1b[0m\n`);
716
719
  console.log('```');
717
720
  console.log('아래 가이드를 읽고 그대로 따라해줘:');
@@ -719,9 +722,14 @@ function registerPublish(program) {
719
722
  console.log('```');
720
723
  console.log(`\n \x1b[90mCLI 설치된 사용자용 (짧은 버전):\x1b[0m`);
721
724
  console.log(` /relay:relay-install ${result.slug}`);
722
- if (config.visibility !== 'private') {
725
+ if (config.visibility === 'private') {
726
+ console.log(`\n \x1b[90mprivate 에이전트:\x1b[0m`);
727
+ console.log(` 접근 링크를 생성한 뒤 guide.md?code={agent_code}로 공유하세요.`);
728
+ console.log(` 접근 링크 관리: \x1b[36mrelayax.com/dashboard/agent-access/${config.slug}\x1b[0m`);
729
+ }
730
+ else if (config.visibility !== 'internal') {
723
731
  console.log(`\n \x1b[90m유료 판매하려면:\x1b[0m`);
724
- console.log(` 1. 가시성을 "링크 공유"로 변경: \x1b[36mrelayax.com/dashboard\x1b[0m`);
732
+ console.log(` 1. 가시성을 "private"로 변경: \x1b[36mrelayax.com/dashboard\x1b[0m`);
725
733
  console.log(` 2. API 키 발급: \x1b[36mrelayax.com/dashboard/keys\x1b[0m`);
726
734
  console.log(` 3. 웹훅 연동 가이드: \x1b[36mrelayax.com/docs/webhook-guide.md\x1b[0m`);
727
735
  }
@@ -26,13 +26,13 @@ function formatTable(results) {
26
26
  function registerSearch(program) {
27
27
  program
28
28
  .command('search <keyword>')
29
- .description('Space에서 에이전트 검색 (공개 + 내 Space )')
29
+ .description('Space에서 에이전트 검색 (공개 에이전트 + 내 Space 에이전트)')
30
30
  .option('--tag <tag>', '태그로 필터링')
31
31
  .option('--space <space>', '특정 Space 내에서 검색')
32
32
  .action(async (keyword, opts) => {
33
33
  const json = program.opts().json ?? false;
34
34
  try {
35
- const results = await (0, api_js_1.searchTeams)(keyword, opts.tag);
35
+ const results = await (0, api_js_1.searchAgents)(keyword, opts.tag);
36
36
  if (json) {
37
37
  console.log(JSON.stringify({ results }));
38
38
  }
@@ -47,27 +47,27 @@ function registerStatus(program) {
47
47
  const localDir = path_1.default.join(projectPath, primaryAgent.skillsDir, 'commands', 'relay');
48
48
  hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
49
49
  }
50
- // 3. 정보
50
+ // 3. 에이전트 프로젝트 정보
51
51
  const relayYamlPath = path_1.default.join(projectPath, '.relay', 'relay.yaml');
52
- let team = null;
52
+ let project = null;
53
53
  if (fs_1.default.existsSync(relayYamlPath)) {
54
54
  try {
55
55
  const yaml = await import('js-yaml');
56
56
  const content = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
57
57
  const raw = yaml.load(content);
58
- team = {
59
- is_team: true,
58
+ project = {
59
+ is_agent: true,
60
60
  name: String(raw.name ?? ''),
61
61
  slug: String(raw.slug ?? ''),
62
62
  version: String(raw.version ?? ''),
63
63
  };
64
64
  }
65
65
  catch {
66
- team = { is_team: true };
66
+ project = { is_agent: true };
67
67
  }
68
68
  }
69
69
  else {
70
- team = { is_team: false };
70
+ project = { is_agent: false };
71
71
  }
72
72
  // 4. 출력
73
73
  if (json) {
@@ -78,7 +78,7 @@ function registerStatus(program) {
78
78
  global_commands: hasGlobal,
79
79
  local_commands: hasLocal,
80
80
  },
81
- team,
81
+ project,
82
82
  };
83
83
  console.log(JSON.stringify(result));
84
84
  }
@@ -103,12 +103,12 @@ function registerStatus(program) {
103
103
  else {
104
104
  console.log(` \x1b[31m✗\x1b[0m 에이전트: 감지 안 됨`);
105
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}`);
106
+ // 에이전트 프로젝트
107
+ if (project?.is_agent && project.name) {
108
+ console.log(` \x1b[32m✓\x1b[0m 현재 에이전트: \x1b[36m${project.name}\x1b[0m v${project.version}`);
109
109
  }
110
110
  else {
111
- console.log(` \x1b[2m—\x1b[0m 현재 프로젝트: 아님`);
111
+ console.log(` \x1b[2m—\x1b[0m 현재 프로젝트: 에이전트 아님`);
112
112
  }
113
113
  console.log('');
114
114
  }
@@ -12,12 +12,12 @@ const slug_js_1 = require("../lib/slug.js");
12
12
  function registerUninstall(program) {
13
13
  program
14
14
  .command('uninstall <slug>')
15
- .description('에이전트 제거')
15
+ .description('에이전트 제거')
16
16
  .action((slugInput) => {
17
17
  const json = program.opts().json ?? false;
18
18
  const localInstalled = (0, config_js_1.loadInstalled)();
19
19
  const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
20
- // Resolve slug — support short names like "cardnews-team"
20
+ // Resolve slug — support short names like "cardnews-agent"
21
21
  let slug;
22
22
  if ((0, slug_js_1.isScopedSlug)(slugInput)) {
23
23
  slug = slugInput;
@@ -45,11 +45,11 @@ function registerUninstall(program) {
45
45
  let totalRemoved = 0;
46
46
  // Remove from local registry
47
47
  if (localEntry) {
48
- const removed = (0, installer_js_1.uninstallTeam)(localEntry.files);
48
+ const removed = (0, installer_js_1.uninstallAgent)(localEntry.files);
49
49
  totalRemoved += removed.length;
50
50
  // Remove deployed files
51
51
  if (localEntry.deployed_files && localEntry.deployed_files.length > 0) {
52
- const deployedRemoved = (0, installer_js_1.uninstallTeam)(localEntry.deployed_files);
52
+ const deployedRemoved = (0, installer_js_1.uninstallAgent)(localEntry.deployed_files);
53
53
  totalRemoved += deployedRemoved.length;
54
54
  // Clean empty parent directories
55
55
  const boundary = path_1.default.join(process.cwd(), '.claude');
@@ -64,12 +64,12 @@ function registerUninstall(program) {
64
64
  if (globalEntry) {
65
65
  // Only remove files if not already handled by local entry
66
66
  if (!localEntry) {
67
- const removed = (0, installer_js_1.uninstallTeam)(globalEntry.files);
67
+ const removed = (0, installer_js_1.uninstallAgent)(globalEntry.files);
68
68
  totalRemoved += removed.length;
69
69
  }
70
70
  // Remove globally deployed files
71
71
  if (globalEntry.deployed_files && globalEntry.deployed_files.length > 0) {
72
- const deployedRemoved = (0, installer_js_1.uninstallTeam)(globalEntry.deployed_files);
72
+ const deployedRemoved = (0, installer_js_1.uninstallAgent)(globalEntry.deployed_files);
73
73
  totalRemoved += deployedRemoved.length;
74
74
  // Clean empty parent directories
75
75
  const boundary = path_1.default.join(os_1.default.homedir(), '.claude');
@@ -82,7 +82,7 @@ function registerUninstall(program) {
82
82
  }
83
83
  const result = {
84
84
  status: 'ok',
85
- team: slug,
85
+ agent: slug,
86
86
  files_removed: totalRemoved,
87
87
  };
88
88
  if (json) {
@@ -11,9 +11,9 @@ const preamble_js_1 = require("../lib/preamble.js");
11
11
  function registerUpdate(program) {
12
12
  program
13
13
  .command('update <slug>')
14
- .description('설치된 에이전트 팀을 최신 버전으로 업데이트합니다')
14
+ .description('설치된 에이전트를 최신 버전으로 업데이트합니다')
15
15
  .option('--path <install_path>', '설치 경로 지정 (기본: ./.claude)')
16
- .option('--code <code>', '초대 코드 (비공개 업데이트 시 필요)')
16
+ .option('--code <code>', '초대 코드 (비공개 에이전트 업데이트 시 필요)')
17
17
  .action(async (slugInput, opts) => {
18
18
  const json = program.opts().json ?? false;
19
19
  const installPath = (0, config_js_1.getInstallPath)(opts.path);
@@ -32,9 +32,9 @@ function registerUpdate(program) {
32
32
  // Check installed.json for current version
33
33
  const currentEntry = installed[slug];
34
34
  const currentVersion = currentEntry?.version ?? null;
35
- // Fetch latest team metadata
36
- const team = await (0, api_js_1.fetchTeamInfo)(slug);
37
- const latestVersion = team.version;
35
+ // Fetch latest agent metadata
36
+ const agent = await (0, api_js_1.fetchAgentInfo)(slug);
37
+ const latestVersion = agent.version;
38
38
  if (currentVersion && currentVersion === latestVersion) {
39
39
  if (json) {
40
40
  console.log(JSON.stringify({ status: 'up-to-date', slug, version: latestVersion }));
@@ -45,29 +45,29 @@ function registerUpdate(program) {
45
45
  return;
46
46
  }
47
47
  // Visibility check
48
- const visibility = team.visibility ?? 'public';
49
- if (visibility === 'private') {
48
+ const visibility = agent.visibility ?? 'public';
49
+ if (visibility === 'internal') {
50
50
  const token = await (0, config_js_1.getValidToken)();
51
51
  if (!token) {
52
- console.error('이 팀은 Space 멤버만 업데이트할 수 있습니다. `relay login`을 먼저 실행하세요.');
52
+ console.error('이 에이전트는 Org 멤버만 업데이트할 수 있습니다. `relay login`을 먼저 실행하세요.');
53
53
  process.exit(1);
54
54
  }
55
55
  }
56
56
  // Download package
57
- const tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
57
+ const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
58
58
  // Extract
59
59
  const extractDir = `${tempDir}/extracted`;
60
60
  await (0, storage_js_1.extractPackage)(tarPath, extractDir);
61
61
  // Inject preamble (update check) before copying
62
- (0, preamble_js_1.injectPreambleToTeam)(extractDir, slug);
62
+ (0, preamble_js_1.injectPreambleToAgent)(extractDir, slug);
63
63
  // Copy files to install_path
64
- const files = (0, installer_js_1.installTeam)(extractDir, installPath);
64
+ const files = (0, installer_js_1.installAgent)(extractDir, installPath);
65
65
  // Preserve deploy info but clear deployed_files (agent needs to re-deploy)
66
66
  const previousDeployScope = currentEntry?.deploy_scope;
67
67
  const hadDeployedFiles = (currentEntry?.deployed_files?.length ?? 0) > 0;
68
68
  // Update installed.json with new version
69
69
  installed[slug] = {
70
- team_id: team.id,
70
+ agent_id: agent.id,
71
71
  version: latestVersion,
72
72
  installed_at: new Date().toISOString(),
73
73
  files,
@@ -76,8 +76,8 @@ function registerUpdate(program) {
76
76
  // Clear deployed_files — agent must re-deploy and call deploy-record
77
77
  };
78
78
  (0, config_js_1.saveInstalled)(installed);
79
- // Report install (non-blocking, team_id 기반)
80
- await (0, api_js_1.reportInstall)(team.id, slug, latestVersion);
79
+ // Report install (non-blocking, agent_id 기반)
80
+ await (0, api_js_1.reportInstall)(agent.id, slug, latestVersion);
81
81
  const result = {
82
82
  status: 'updated',
83
83
  slug,
@@ -92,17 +92,17 @@ function registerUpdate(program) {
92
92
  }
93
93
  else {
94
94
  const fromLabel = currentVersion ? `v${currentVersion} → ` : '';
95
- console.log(`\n\x1b[32m✓ ${team.name} ${fromLabel}v${latestVersion} 업데이트 완료\x1b[0m`);
95
+ console.log(`\n\x1b[32m✓ ${agent.name} ${fromLabel}v${latestVersion} 업데이트 완료\x1b[0m`);
96
96
  console.log(` 설치 위치: \x1b[36m${installPath}\x1b[0m`);
97
97
  console.log(` 파일 수: ${files.length}개`);
98
98
  // Builder business card
99
- const authorUsername = team.author?.username;
100
- const authorDisplayName = team.author?.display_name ?? authorUsername ?? '';
101
- const contactParts = (0, contact_format_js_1.formatContactParts)(team.author?.contact_links);
102
- const hasCard = team.welcome || contactParts.length > 0 || authorUsername;
99
+ const authorUsername = agent.author?.username;
100
+ const authorDisplayName = agent.author?.display_name ?? authorUsername ?? '';
101
+ const contactParts = (0, contact_format_js_1.formatContactParts)(agent.author?.contact_links);
102
+ const hasCard = agent.welcome || contactParts.length > 0 || authorUsername;
103
103
  // Show changelog for this version
104
104
  try {
105
- const versions = await (0, api_js_1.fetchTeamVersions)(slug);
105
+ const versions = await (0, api_js_1.fetchAgentVersions)(slug);
106
106
  const thisVersion = versions.find((v) => v.version === latestVersion);
107
107
  if (thisVersion?.changelog) {
108
108
  console.log(`\n \x1b[90m── Changelog ──────────────────────────────\x1b[0m`);
@@ -117,8 +117,8 @@ function registerUpdate(program) {
117
117
  }
118
118
  if (hasCard) {
119
119
  console.log(`\n \x1b[90m┌─ ${authorDisplayName || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || '빌더').length))}┐\x1b[0m`);
120
- if (team.welcome) {
121
- const truncated = team.welcome.length > 45 ? team.welcome.slice(0, 45) + '...' : team.welcome;
120
+ if (agent.welcome) {
121
+ const truncated = agent.welcome.length > 45 ? agent.welcome.slice(0, 45) + '...' : agent.welcome;
122
122
  console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
123
123
  }
124
124
  if (contactParts.length > 0) {
@@ -6,12 +6,12 @@ const slug_js_1 = require("../lib/slug.js");
6
6
  function registerVersions(program) {
7
7
  program
8
8
  .command('versions <slug>')
9
- .description(' 버전 목록과 릴리즈 노트를 확인합니다')
9
+ .description('에이전트 버전 목록과 릴리즈 노트를 확인합니다')
10
10
  .action(async (slugInput) => {
11
11
  const json = program.opts().json ?? false;
12
12
  try {
13
13
  const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
14
- const versions = await (0, api_js_1.fetchTeamVersions)(resolved.full);
14
+ const versions = await (0, api_js_1.fetchAgentVersions)(resolved.full);
15
15
  if (json) {
16
16
  console.log(JSON.stringify({ slug: resolved.full, versions }));
17
17
  return;
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ const orgs_js_1 = require("./commands/orgs.js");
22
22
  const deploy_record_js_1 = require("./commands/deploy-record.js");
23
23
  const ping_js_1 = require("./commands/ping.js");
24
24
  const access_js_1 = require("./commands/access.js");
25
+ const grant_js_1 = require("./commands/grant.js");
25
26
  const versions_js_1 = require("./commands/versions.js");
26
27
  const diff_js_1 = require("./commands/diff.js");
27
28
  // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -52,6 +53,7 @@ program
52
53
  (0, deploy_record_js_1.registerDeployRecord)(program);
53
54
  (0, ping_js_1.registerPing)(program);
54
55
  (0, access_js_1.registerAccess)(program);
56
+ (0, grant_js_1.registerGrant)(program);
55
57
  (0, versions_js_1.registerVersions)(program);
56
58
  (0, diff_js_1.registerDiff)(program);
57
59
  program.parse();
@@ -17,3 +17,18 @@ export declare function detectAgentCLIs(projectPath: string): AITool[];
17
17
  * ~/{skillsDir}/ 가 존재하는 CLI를 반환.
18
18
  */
19
19
  export declare function detectGlobalCLIs(): AITool[];
20
+ export type ContentType = 'skill' | 'agent' | 'command' | 'rule';
21
+ export interface ContentItem {
22
+ name: string;
23
+ type: ContentType;
24
+ /** 소스 디렉토리 기준 상대 경로 (예: skills/code-review) */
25
+ relativePath: string;
26
+ }
27
+ /**
28
+ * 프로젝트 로컬 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
29
+ */
30
+ export declare function scanLocalItems(projectPath: string, tool: AITool): ContentItem[];
31
+ /**
32
+ * 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
33
+ */
34
+ export declare function scanGlobalItems(tool: AITool): ContentItem[];
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AI_TOOLS = void 0;
7
7
  exports.detectAgentCLIs = detectAgentCLIs;
8
8
  exports.detectGlobalCLIs = detectGlobalCLIs;
9
+ exports.scanLocalItems = scanLocalItems;
10
+ exports.scanGlobalItems = scanGlobalItems;
9
11
  const fs_1 = __importDefault(require("fs"));
10
12
  const os_1 = __importDefault(require("os"));
11
13
  const path_1 = __importDefault(require("path"));
@@ -50,6 +52,51 @@ function detectAgentCLIs(projectPath) {
50
52
  * ~/{skillsDir}/ 가 존재하는 CLI를 반환.
51
53
  */
52
54
  function detectGlobalCLIs() {
53
- const home = path_1.default.join(os_1.default.homedir());
55
+ const home = os_1.default.homedir();
54
56
  return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(home, tool.skillsDir)));
55
57
  }
58
+ const CONTENT_DIRS = [
59
+ { dir: 'skills', type: 'skill' },
60
+ { dir: 'agents', type: 'agent' },
61
+ { dir: 'commands', type: 'command' },
62
+ { dir: 'rules', type: 'rule' },
63
+ ];
64
+ const EXCLUDE_SUBDIRS = ['relay'];
65
+ /**
66
+ * 소스 디렉토리(basePath) 안의 skills/, agents/, commands/, rules/에서
67
+ * 개별 항목을 스캔하여 반환한다.
68
+ */
69
+ function scanItemsIn(basePath) {
70
+ const items = [];
71
+ for (const { dir, type } of CONTENT_DIRS) {
72
+ const fullDir = path_1.default.join(basePath, dir);
73
+ if (!fs_1.default.existsSync(fullDir))
74
+ continue;
75
+ for (const entry of fs_1.default.readdirSync(fullDir, { withFileTypes: true })) {
76
+ if (entry.name.startsWith('.'))
77
+ continue;
78
+ if (entry.isDirectory() && EXCLUDE_SUBDIRS.includes(entry.name))
79
+ continue;
80
+ items.push({
81
+ name: entry.name.replace(/\.\w+$/, ''), // 파일이면 확장자 제거
82
+ type,
83
+ relativePath: path_1.default.join(dir, entry.name),
84
+ });
85
+ }
86
+ }
87
+ return items;
88
+ }
89
+ /**
90
+ * 프로젝트 로컬 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
91
+ */
92
+ function scanLocalItems(projectPath, tool) {
93
+ const basePath = path_1.default.join(projectPath, tool.skillsDir);
94
+ return scanItemsIn(basePath);
95
+ }
96
+ /**
97
+ * 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
98
+ */
99
+ function scanGlobalItems(tool) {
100
+ const basePath = path_1.default.join(os_1.default.homedir(), tool.skillsDir);
101
+ return scanItemsIn(basePath);
102
+ }