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
@@ -9,7 +9,7 @@ export declare const API_URL = "https://www.relayax.com";
9
9
  export declare function getInstallPath(override?: string): string;
10
10
  /** ~/.relay/ — 글로벌 (token, CLI cache) */
11
11
  export declare function ensureGlobalRelayDir(): void;
12
- /** cwd/.relay/ — 프로젝트 로컬 (installed.json, teams/) */
12
+ /** cwd/.relay/ — 프로젝트 로컬 (installed.json, agents/) */
13
13
  export declare function ensureProjectRelayDir(): void;
14
14
  export interface TokenData {
15
15
  access_token: string;
@@ -49,7 +49,7 @@ function ensureGlobalRelayDir() {
49
49
  fs_1.default.mkdirSync(GLOBAL_RELAY_DIR, { recursive: true });
50
50
  }
51
51
  }
52
- /** cwd/.relay/ — 프로젝트 로컬 (installed.json, teams/) */
52
+ /** cwd/.relay/ — 프로젝트 로컬 (installed.json, agents/) */
53
53
  function ensureProjectRelayDir() {
54
54
  const dir = path_1.default.join(process.cwd(), '.relay');
55
55
  if (!fs_1.default.existsSync(dir)) {
@@ -196,7 +196,7 @@ async function getValidToken() {
196
196
  }
197
197
  /**
198
198
  * 레거시 키 정규화:
199
- * - `@spaces/{slug}/{team}` → `@{slug}/{team}` (Space 레거시)
199
+ * - `@spaces/{slug}/{agent}` → `@{slug}/{agent}` (Space 레거시)
200
200
  * - `space_slug` → `org_slug` (필드명 마이그레이션)
201
201
  */
202
202
  function normalizeInstalledRegistry(raw) {
package/dist/lib/guide.js CHANGED
@@ -1,108 +1,63 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateGuide = generateGuide;
4
+ const index_js_1 = require("../prompts/index.js");
4
5
  function buildSetupSection(needsLogin) {
5
- const installStep = `## Step 1. Relay CLI 설치
6
-
7
- relay CLI가 설치되어 있지 않다면:
8
-
9
- \`\`\`bash
10
- npm install -g relayax-cli
11
- \`\`\`
12
-
13
- Node.js가 없는 경우:
14
- - macOS: \`brew install node\`
15
- - Windows: \`winget install OpenJS.NodeJS.LTS\`
16
- - Linux: \`curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && source ~/.bashrc && nvm install --lts\`
17
-
18
- npm 권한 오류 시:
19
- \`\`\`bash
20
- mkdir -p ~/.npm-global && npm config set prefix '~/.npm-global'
21
- echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc && source ~/.zshrc
22
- npm install -g relayax-cli
23
- \`\`\``;
24
6
  if (!needsLogin)
25
- return installStep;
26
- return `${installStep}
27
-
28
- ## Step 2. 로그인
29
-
30
- \`\`\`bash
31
- relay login
32
- \`\`\`
33
-
34
- 브라우저가 열리면 GitHub 또는 카카오 계정으로 로그인하세요.`;
7
+ return index_js_1.SETUP_CLI;
8
+ return `${index_js_1.SETUP_CLI}\n\n${index_js_1.SETUP_LOGIN}`;
35
9
  }
36
- function buildRequiresSection(requires) {
37
- const sections = [];
10
+ function buildRequiresSummary(requires) {
11
+ const lines = [];
38
12
  if (requires.cli && requires.cli.length > 0) {
39
- sections.push('### CLI 도구 설치\n');
40
13
  for (const cli of requires.cli) {
41
- const label = cli.required === false ? '(선택)' : '(필수)';
42
- if (cli.install) {
43
- sections.push(`- **${cli.name}** ${label}: \`${cli.install}\``);
44
- }
45
- else {
46
- sections.push(`- **${cli.name}** ${label}: 설치 후 \`which ${cli.name}\`으로 확인`);
47
- }
14
+ const label = cli.required === false ? '선택' : '필수';
15
+ lines.push(`- cli: **${cli.name}** (${label})${cli.install ? ` — \`${cli.install}\`` : ''}`);
48
16
  }
49
- sections.push('');
50
17
  }
51
18
  if (requires.npm && requires.npm.length > 0) {
52
- sections.push('### npm 패키지 설치\n');
53
- sections.push('```bash');
54
19
  const pkgNames = requires.npm.map((p) => typeof p === 'string' ? p : p.name);
55
- sections.push(`npm install ${pkgNames.join(' ')}`);
56
- sections.push('```\n');
20
+ lines.push(`- npm: ${pkgNames.map((n) => `**${n}**`).join(', ')}`);
57
21
  }
58
22
  if (requires.env && requires.env.length > 0) {
59
- sections.push('### 환경변수 설정\n');
60
- sections.push('```bash');
61
23
  for (const env of requires.env) {
62
- const label = env.required === false ? '# (선택)' : '# (필수)';
24
+ const label = env.required === false ? '선택' : '필수';
63
25
  const desc = env.description ? ` — ${env.description}` : '';
64
- sections.push(`${env.name}=your_value_here ${label}${desc}`);
26
+ lines.push(`- env: **${env.name}** (${label})${desc}`);
65
27
  }
66
- sections.push('```\n');
67
28
  }
68
29
  if (requires.mcp && requires.mcp.length > 0) {
69
- sections.push('### MCP 서버 설정\n');
70
30
  for (const mcp of requires.mcp) {
71
- sections.push(`**${mcp.name}:**`);
72
- if (mcp.package)
73
- sections.push(`- 패키지: \`${mcp.package}\``);
74
- if (mcp.config) {
75
- sections.push(`- 실행: \`${mcp.config.command}${mcp.config.args ? ' ' + mcp.config.args.join(' ') : ''}\``);
76
- }
77
- if (mcp.env && mcp.env.length > 0) {
78
- sections.push(`- 필요한 환경변수: ${mcp.env.map((e) => `\`${e}\``).join(', ')}`);
79
- }
80
- sections.push('');
31
+ const pkg = mcp.package ? ` — \`${mcp.package}\`` : '';
32
+ lines.push(`- mcp: **${mcp.name}**${pkg}`);
81
33
  }
82
34
  }
83
- if (requires.teams && requires.teams.length > 0) {
84
- sections.push('### 의존 설치\n');
85
- sections.push('```bash');
86
- for (const team of requires.teams) {
87
- sections.push(`relay install ${team}`);
35
+ if (requires.agents && requires.agents.length > 0) {
36
+ for (const agent of requires.agents) {
37
+ lines.push(`- agents: **${agent}**`);
88
38
  }
89
- sections.push('```\n');
90
39
  }
91
40
  if (requires.permissions && requires.permissions.length > 0) {
92
- sections.push('### 권한 설정\n');
93
- sections.push('아래 도구 사용을 허용해야 합니다:\n');
94
- for (const perm of requires.permissions) {
95
- sections.push(`- \`${perm}\``);
96
- }
97
- sections.push('');
41
+ lines.push(`- permissions: ${requires.permissions.map((p) => `\`${p}\``).join(', ')}`);
98
42
  }
99
- if (sections.length === 0)
43
+ return lines.join('\n');
44
+ }
45
+ function buildRequiresSection(requires) {
46
+ const summary = buildRequiresSummary(requires);
47
+ if (!summary)
100
48
  return '';
101
- return '## Step 4. 환경 구성\n\n' + sections.join('\n');
49
+ return `## Step 4. 의존성 확인 설치
50
+
51
+ 이 에이전트는 아래 의존성이 필요합니다:
52
+
53
+ ${summary}
54
+
55
+ ${index_js_1.REQUIREMENTS_CHECK}
56
+ `;
102
57
  }
103
58
  function generateGuide(config, commands, requires) {
104
59
  const scopedSlug = config.slug.startsWith('@') ? config.slug : `@${config.slug}`;
105
- const needsLogin = config.visibility === 'private' || config.visibility === 'gated';
60
+ const needsLogin = config.visibility === 'internal' || config.visibility === 'private';
106
61
  const setupSection = buildSetupSection(needsLogin);
107
62
  const installStepNum = needsLogin ? 3 : 2;
108
63
  const requiresSection = requires ? buildRequiresSection(requires) : '';
@@ -125,20 +80,20 @@ ${commands.map((cmd) => `- \`/${cmd.name}\`: ${cmd.description}`).join('\n')}
125
80
  > ${config.description}
126
81
 
127
82
  이 가이드는 [RelayAX](https://relayax.com)에서 자동 생성되었습니다.
128
- 아래 단계를 순서대로 실행하면 Agent 팀이 설치됩니다.
83
+ 아래 단계를 순서대로 실행하면 에이전트가 설치됩니다.
129
84
 
130
85
  ${setupSection}
131
86
 
132
- ## Step ${installStepNum}. 설치
87
+ ## Step ${installStepNum}. 에이전트 설치
133
88
 
134
89
  \`\`\`bash
135
90
  relay install ${scopedSlug}
136
91
  \`\`\`
137
92
 
138
- 설치 후 Agent가 자동으로 의존성을 확인하고 환경을 구성합니다.
93
+ 설치 후 아래 의존성을 확인하고 환경을 구성합니다.
139
94
 
140
95
  ${requiresSectionFinal}${usageHint}---
141
- *출처: https://relayax.com — Agent Team Marketplace*
96
+ *출처: https://relayax.com — Agent Marketplace*
142
97
  *설치: \`relay install ${scopedSlug}\`*
143
98
  `;
144
99
  }
@@ -1,5 +1,5 @@
1
- export declare function installTeam(extractedDir: string, installPath: string): string[];
2
- export declare function uninstallTeam(files: string[]): string[];
1
+ export declare function installAgent(extractedDir: string, installPath: string): string[];
2
+ export declare function uninstallAgent(files: string[]): string[];
3
3
  /**
4
4
  * 빈 상위 디렉토리를 boundary까지 정리한다.
5
5
  * 예: /home/.claude/skills/cardnews/ 가 비었으면 삭제, /home/.claude/skills/는 유지
@@ -3,8 +3,8 @@ 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.installTeam = installTeam;
7
- exports.uninstallTeam = uninstallTeam;
6
+ exports.installAgent = installAgent;
7
+ exports.uninstallAgent = uninstallAgent;
8
8
  exports.cleanEmptyParents = cleanEmptyParents;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
@@ -27,7 +27,7 @@ function copyDirRecursive(src, dest) {
27
27
  }
28
28
  return copiedFiles;
29
29
  }
30
- function installTeam(extractedDir, installPath) {
30
+ function installAgent(extractedDir, installPath) {
31
31
  const installedFiles = [];
32
32
  for (const dir of COPY_DIRS) {
33
33
  const srcDir = path_1.default.join(extractedDir, dir);
@@ -36,7 +36,7 @@ function installTeam(extractedDir, installPath) {
36
36
  }
37
37
  return installedFiles;
38
38
  }
39
- function uninstallTeam(files) {
39
+ function uninstallAgent(files) {
40
40
  const removed = [];
41
41
  for (const file of files) {
42
42
  try {
@@ -9,14 +9,14 @@ export declare function generatePreambleScript(slug: string, apiUrl: string): st
9
9
  */
10
10
  export declare function generatePreamble(slug: string): string;
11
11
  /**
12
- * teamDir에 bin/relay-preamble.sh를 생성한다.
12
+ * agentDir에 bin/relay-preamble.sh를 생성한다.
13
13
  */
14
- export declare function generatePreambleBin(teamDir: string, slug: string, apiUrl: string): void;
14
+ export declare function generatePreambleBin(agentDir: string, slug: string, apiUrl: string): void;
15
15
  /**
16
16
  * frontmatter(---...---) 뒤에 preamble을 삽입한다.
17
17
  */
18
18
  export declare function injectPreamble(filePath: string, slug: string): void;
19
19
  /**
20
- * 팀의 사용자 진입점 파일에 preamble을 주입한다.
20
+ * 에이전트의 사용자 진입점 파일에 preamble을 주입한다.
21
21
  */
22
- export declare function injectPreambleToTeam(teamDir: string, slug: string): number;
22
+ export declare function injectPreambleToAgent(agentDir: string, slug: string): number;
@@ -7,7 +7,7 @@ exports.generatePreambleScript = generatePreambleScript;
7
7
  exports.generatePreamble = generatePreamble;
8
8
  exports.generatePreambleBin = generatePreambleBin;
9
9
  exports.injectPreamble = injectPreamble;
10
- exports.injectPreambleToTeam = injectPreambleToTeam;
10
+ exports.injectPreambleToAgent = injectPreambleToAgent;
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const PREAMBLE_START = '<!-- RELAY_PREAMBLE_START - DO NOT EDIT -->';
@@ -17,10 +17,10 @@ const PREAMBLE_END = '<!-- RELAY_PREAMBLE_END -->';
17
17
  * relay CLI가 있으면 사용, 없으면 curl fallback.
18
18
  */
19
19
  function generatePreambleScript(slug, apiUrl) {
20
- // slug format: @owner/team → extract team slug for ping
20
+ // slug format: @owner/agent → extract agent slug for ping
21
21
  const stripped = slug.startsWith('@') ? slug.slice(1) : slug;
22
22
  const slashIdx = stripped.indexOf('/');
23
- const teamSlug = slashIdx !== -1 ? stripped.slice(slashIdx + 1) : stripped;
23
+ const agentSlug = slashIdx !== -1 ? stripped.slice(slashIdx + 1) : stripped;
24
24
  return `#!/usr/bin/env bash
25
25
  # relay-preamble.sh — auto-generated by relay publish
26
26
  set +e
@@ -31,7 +31,7 @@ DEVICE_HASH=$(echo "$HOSTNAME:$USER" | shasum -a 256 | cut -d' ' -f1)
31
31
  if command -v relay &>/dev/null; then
32
32
  relay ping "${slug}" --quiet 2>/dev/null &
33
33
  else
34
- curl -sf -X POST "${apiUrl}/api/teams/${teamSlug}/ping" \\
34
+ curl -sf -X POST "${apiUrl}/api/agents/${agentSlug}/ping" \\
35
35
  -H "Content-Type: application/json" \\
36
36
  -d "{\\"device_hash\\":\\"$DEVICE_HASH\\"}" \\
37
37
  2>/dev/null &
@@ -51,23 +51,23 @@ echo "RELAY_READY: ${slug}"
51
51
  * 단순히 relay ping을 호출한다.
52
52
  */
53
53
  function generatePreamble(slug) {
54
- const teamPath = slug.startsWith('@') ? slug.slice(1) : slug;
54
+ const agentPath = slug.startsWith('@') ? slug.slice(1) : slug;
55
55
  return `${PREAMBLE_START}
56
56
  ## Preamble (run first)
57
57
 
58
58
  \`\`\`bash
59
- bash "$HOME/.relay/teams/${teamPath}/bin/relay-preamble.sh" 2>/dev/null || true
59
+ bash "$HOME/.relay/agents/${agentPath}/bin/relay-preamble.sh" 2>/dev/null || true
60
60
  \`\`\`
61
61
 
62
62
  If output shows \`CLI_UPGRADE_AVAILABLE <old> <new>\`: tell user "relay v{new} available. Run: npm update -g relayax-cli"
63
- If output shows \`TEAM_UPGRADE_AVAILABLE\`: tell user "${slug} v{new} available (by @{author}). Run: relay update ${slug}"
63
+ If output shows \`AGENT_UPGRADE_AVAILABLE\`: tell user "${slug} v{new} available (by @{author}). Run: relay update ${slug}"
64
64
  ${PREAMBLE_END}`;
65
65
  }
66
66
  /**
67
- * teamDir에 bin/relay-preamble.sh를 생성한다.
67
+ * agentDir에 bin/relay-preamble.sh를 생성한다.
68
68
  */
69
- function generatePreambleBin(teamDir, slug, apiUrl) {
70
- const binDir = path_1.default.join(teamDir, 'bin');
69
+ function generatePreambleBin(agentDir, slug, apiUrl) {
70
+ const binDir = path_1.default.join(agentDir, 'bin');
71
71
  if (!fs_1.default.existsSync(binDir)) {
72
72
  fs_1.default.mkdirSync(binDir, { recursive: true });
73
73
  }
@@ -106,12 +106,12 @@ function injectPreamble(filePath, slug) {
106
106
  fs_1.default.writeFileSync(filePath, cleaned);
107
107
  }
108
108
  /**
109
- * 팀의 사용자 진입점 파일에 preamble을 주입한다.
109
+ * 에이전트의 사용자 진입점 파일에 preamble을 주입한다.
110
110
  */
111
- function injectPreambleToTeam(teamDir, slug) {
111
+ function injectPreambleToAgent(agentDir, slug) {
112
112
  let count = 0;
113
113
  // 1. user-invocable 서브 스킬 SKILL.md
114
- const skillsDir = path_1.default.join(teamDir, 'skills');
114
+ const skillsDir = path_1.default.join(agentDir, 'skills');
115
115
  if (fs_1.default.existsSync(skillsDir)) {
116
116
  function walkSkills(dir) {
117
117
  for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
@@ -131,7 +131,7 @@ function injectPreambleToTeam(teamDir, slug) {
131
131
  walkSkills(skillsDir);
132
132
  }
133
133
  // 2. commands/*.md
134
- const commandsDir = path_1.default.join(teamDir, 'commands');
134
+ const commandsDir = path_1.default.join(agentDir, 'commands');
135
135
  if (fs_1.default.existsSync(commandsDir)) {
136
136
  for (const entry of fs_1.default.readdirSync(commandsDir, { withFileTypes: true })) {
137
137
  if (!entry.isFile() || !entry.name.endsWith('.md'))
@@ -3,6 +3,11 @@ export interface ParsedSlug {
3
3
  name: string;
4
4
  full: string;
5
5
  }
6
+ /**
7
+ * 임의의 문자열을 slug로 변환한다.
8
+ * 한글은 로마자로 변환된다 (예: "콘텐츠 에이전트" → "kontencheu-eijenteu").
9
+ */
10
+ export declare function slugify(input: string): string;
6
11
  /**
7
12
  * Scoped slug(`@owner/name`)를 동기적으로 파싱한다.
8
13
  * 단순 slug는 파싱할 수 없으므로 null을 반환한다.
package/dist/lib/slug.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.slugify = slugify;
3
4
  exports.parseSlug = parseSlug;
4
5
  exports.isScopedSlug = isScopedSlug;
5
6
  exports.isSimpleSlug = isSimpleSlug;
@@ -7,6 +8,52 @@ exports.resolveSlug = resolveSlug;
7
8
  const api_js_1 = require("./api.js");
8
9
  const SCOPED_SLUG_RE = /^@([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/;
9
10
  const SIMPLE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
11
+ // ── 한글 로마자 변환 (Revised Romanization) ──
12
+ const INITIALS = [
13
+ 'g', 'kk', 'n', 'd', 'tt', 'r', 'm', 'b', 'pp',
14
+ 's', 'ss', '', 'j', 'jj', 'ch', 'k', 't', 'p', 'h',
15
+ ];
16
+ const MEDIALS = [
17
+ 'a', 'ae', 'ya', 'yae', 'eo', 'e', 'yeo', 'ye', 'o',
18
+ 'wa', 'wae', 'oe', 'yo', 'u', 'wo', 'we', 'wi', 'yu',
19
+ 'eu', 'ui', 'i',
20
+ ];
21
+ const FINALS = [
22
+ '', 'k', 'k', 'k', 'n', 'n', 'n', 't', 'l',
23
+ 'l', 'l', 'l', 'l', 'l', 'l', 'l', 'm', 'p',
24
+ 'p', 't', 't', 'ng', 't', 't', 'k', 't', 'p', 't',
25
+ ];
26
+ const HANGUL_BASE = 0xAC00;
27
+ function romanize(input) {
28
+ let result = '';
29
+ for (const ch of input) {
30
+ const code = ch.codePointAt(0);
31
+ if (code >= HANGUL_BASE && code < HANGUL_BASE + 11172) {
32
+ const offset = code - HANGUL_BASE;
33
+ const initial = Math.floor(offset / (21 * 28));
34
+ const medial = Math.floor((offset % (21 * 28)) / 28);
35
+ const final = offset % 28;
36
+ result += INITIALS[initial] + MEDIALS[medial] + FINALS[final];
37
+ }
38
+ else {
39
+ result += ch;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ /**
45
+ * 임의의 문자열을 slug로 변환한다.
46
+ * 한글은 로마자로 변환된다 (예: "콘텐츠 에이전트" → "kontencheu-eijenteu").
47
+ */
48
+ function slugify(input) {
49
+ return romanize(input)
50
+ .toLowerCase()
51
+ .replace(/[^a-z0-9\s-]/g, '')
52
+ .trim()
53
+ .replace(/\s+/g, '-')
54
+ .replace(/-+/g, '-')
55
+ .slice(0, 50);
56
+ }
10
57
  /**
11
58
  * Scoped slug(`@owner/name`)를 동기적으로 파싱한다.
12
59
  * 단순 slug는 파싱할 수 없으므로 null을 반환한다.
@@ -41,7 +88,7 @@ async function resolveSlug(input) {
41
88
  // 서버에 resolve 요청
42
89
  const results = await (0, api_js_1.resolveSlugFromServer)(input);
43
90
  if (results.length === 0) {
44
- throw new Error(`'${input}' 팀을 찾을 수 없습니다.`);
91
+ throw new Error(`'${input}' 에이전트를 찾을 수 없습니다.`);
45
92
  }
46
93
  if (results.length === 1) {
47
94
  const r = results[0];
@@ -49,5 +96,5 @@ async function resolveSlug(input) {
49
96
  }
50
97
  // 여러 개 매칭
51
98
  const list = results.map((r) => ` ${r.full}`).join('\n');
52
- throw new Error(`'${input}'에 해당하는 팀이 여러 개입니다. 전체 slug를 지정해주세요:\n${list}`);
99
+ throw new Error(`'${input}'에 해당하는 에이전트가 여러 개입니다. 전체 slug를 지정해주세요:\n${list}`);
53
100
  }
@@ -31,7 +31,7 @@ function isCacheValid(key, force) {
31
31
  if (force)
32
32
  return false;
33
33
  const cache = loadCache();
34
- const timestamp = key === 'cli' ? cache.cli : cache.teams?.[key];
34
+ const timestamp = key === 'cli' ? cache.cli : cache.agents?.[key];
35
35
  if (!timestamp)
36
36
  return false;
37
37
  return Date.now() - new Date(timestamp).getTime() < CACHE_TTL_MS;
@@ -43,9 +43,9 @@ function updateCacheTimestamp(key) {
43
43
  cache.cli = now;
44
44
  }
45
45
  else {
46
- if (!cache.teams)
47
- cache.teams = {};
48
- cache.teams[key] = now;
46
+ if (!cache.agents)
47
+ cache.agents = {};
48
+ cache.agents[key] = now;
49
49
  }
50
50
  saveCache(cache);
51
51
  }
@@ -1,10 +1,10 @@
1
1
  export interface UpdateResult {
2
- type: 'cli' | 'team';
2
+ type: 'cli' | 'agent';
3
3
  slug?: string;
4
4
  current: string;
5
5
  latest: string;
6
6
  author?: string;
7
7
  }
8
8
  export declare function checkCliVersion(force?: boolean): Promise<UpdateResult | null>;
9
- export declare function checkTeamVersion(slug: string, force?: boolean): Promise<UpdateResult | null>;
10
- export declare function checkAllTeams(force?: boolean): Promise<UpdateResult[]>;
9
+ export declare function checkAgentVersion(slug: string, force?: boolean): Promise<UpdateResult | null>;
10
+ export declare function checkAllAgents(force?: boolean): Promise<UpdateResult[]>;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkCliVersion = checkCliVersion;
4
- exports.checkTeamVersion = checkTeamVersion;
5
- exports.checkAllTeams = checkAllTeams;
4
+ exports.checkAgentVersion = checkAgentVersion;
5
+ exports.checkAllAgents = checkAllAgents;
6
6
  const config_js_1 = require("./config.js");
7
7
  const api_js_1 = require("./api.js");
8
8
  const update_cache_js_1 = require("./update-cache.js");
@@ -28,7 +28,7 @@ async function checkCliVersion(force) {
28
28
  }
29
29
  return null;
30
30
  }
31
- async function checkTeamVersion(slug, force) {
31
+ async function checkAgentVersion(slug, force) {
32
32
  if ((0, update_cache_js_1.isCacheValid)(slug, force))
33
33
  return null;
34
34
  try {
@@ -40,20 +40,20 @@ async function checkTeamVersion(slug, force) {
40
40
  if (entry.type === 'system') {
41
41
  return null;
42
42
  }
43
- const team = await (0, api_js_1.fetchTeamInfo)(slug);
43
+ const agent = await (0, api_js_1.fetchAgentInfo)(slug);
44
44
  (0, update_cache_js_1.updateCacheTimestamp)(slug);
45
45
  // Fire-and-forget usage ping (only when cache expired = actual API call happened)
46
- const teamId = entry.team_id ?? team.id;
47
- if (teamId) {
48
- (0, api_js_1.sendUsagePing)(teamId, slug, entry.version);
46
+ const agentId = entry.agent_id ?? agent.id;
47
+ if (agentId) {
48
+ (0, api_js_1.sendUsagePing)(agentId, slug, entry.version);
49
49
  }
50
- if (team.version !== entry.version) {
50
+ if (agent.version !== entry.version) {
51
51
  return {
52
- type: 'team',
52
+ type: 'agent',
53
53
  slug,
54
54
  current: entry.version,
55
- latest: team.version,
56
- author: team.author?.username,
55
+ latest: agent.version,
56
+ author: agent.author?.username,
57
57
  };
58
58
  }
59
59
  }
@@ -62,12 +62,12 @@ async function checkTeamVersion(slug, force) {
62
62
  }
63
63
  return null;
64
64
  }
65
- async function checkAllTeams(force) {
65
+ async function checkAllAgents(force) {
66
66
  const installed = (0, config_js_1.loadInstalled)();
67
67
  const slugs = Object.keys(installed);
68
68
  const results = [];
69
69
  for (const slug of slugs) {
70
- const result = await checkTeamVersion(slug, force);
70
+ const result = await checkAgentVersion(slug, force);
71
71
  if (result)
72
72
  results.push(result);
73
73
  }
@@ -0,0 +1,41 @@
1
+ ### 빌더 명함 표시
2
+ JSON 결과의 `author`, `welcome` 필드를 사용하여 명함을 표시합니다.
3
+ 불릿 리스트(- 또는 *)로 나열하지 마세요. 반드시 인용 블록(>) 안에 넣어야 합니다.
4
+
5
+ **JSON 결과에서 사용할 필드:**
6
+ - `author.display_name` 또는 `author.username` → 명함 제목
7
+ - `welcome` → 환영 메시지 (💬)
8
+ - `author.contact_links` → 연락처 배열 (`[{type, label, value}]`)
9
+ - `author.username` → 프로필 링크 (👤)
10
+
11
+ **예시 (이 형태를 그대로 따르세요):**
12
+
13
+ JSON 결과 예시:
14
+ ```json
15
+ {
16
+ "author": { "username": "alice", "display_name": "Alice Kim", "contact_links": [
17
+ {"type": "email", "label": "이메일", "value": "alice@example.com"},
18
+ {"type": "website", "label": "블로그", "value": "https://alice.dev"},
19
+ {"type": "kakao", "label": "카카오", "value": "https://open.kakao.com/o/abc123"}
20
+ ]},
21
+ "welcome": "안녕하세요!\n에이전트 빌더 Alice입니다.\n설치해주셔서 감사합니다."
22
+ }
23
+ ```
24
+
25
+ 출력:
26
+
27
+ > **🪪 Alice Kim의 명함**
28
+ >
29
+ > 💬 "안녕하세요!
30
+ > 에이전트 빌더 Alice입니다.
31
+ > 설치해주셔서 감사합니다."
32
+ >
33
+ > 📧 alice@example.com
34
+ > 🔗 블로그: alice.dev
35
+ > 💬 카카오: open.kakao.com/o/abc123
36
+ > 👤 relayax.com/@alice
37
+
38
+ - `welcome`이 없으면 💬 줄을 생략합니다.
39
+ - 연락처의 type에 맞는 이모지: 📧 email, 💬 kakao, 🐦 x, 💼 linkedin, 💻 github, 🔗 website/custom
40
+ - 연락처가 여러 개면 각각 한 줄씩 표시합니다.
41
+ - `author`가 null이면 명함 블록 전체를 생략합니다.
@@ -0,0 +1,38 @@
1
+ ### 에러 처리 가이드
2
+
3
+ CLI 명령 실행 후 JSON 에러가 반환되면 아래 기준에 따라 처리합니다.
4
+ **원칙: "되돌릴 수 없는 영향이 있는가?"로 판단합니다.**
5
+
6
+ #### 1. 자동 해결 (사용자에게 물어보지 않음)
7
+ 되돌릴 수 있고, 부작용 없는 에러:
8
+
9
+ | 에러 코드 | 행동 |
10
+ |-----------|------|
11
+ | `LOGIN_REQUIRED` / `NO_TOKEN` | `relay login` 실행 (timeout 300초, 브라우저 자동 열림) → 성공 후 원래 명령 재시도 |
12
+ | `NOT_INITIALIZED` | `relay init --all --json` 실행 → 원래 명령 재시도 |
13
+ | `FETCH_FAILED` | 3초 대기 후 원래 명령 재시도 (최대 2회). 2회 실패 시 사용자에게 안내 |
14
+
15
+ #### 2. 사용자에게 선택지 제시 (사용자 질문 도구)
16
+ `options` 필드가 있는 에러:
17
+
18
+ | 에러 코드 | 행동 |
19
+ |-----------|------|
20
+ | `MISSING_VISIBILITY` | options의 label을 선택지로 사용자 질문 도구 호출 |
21
+ | `MISSING_FIELD` | fix 안내 + 사용자에게 값 입력 요청 |
22
+ | `MISSING_TOOLS` | options의 감지된 도구 목록을 선택지로 사용자 질문 도구 호출 |
23
+ | `MISSING_SPACE` | options의 Space 목록을 선택지로 사용자 질문 도구 호출 |
24
+
25
+ 사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
26
+
27
+ #### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
28
+ 구매, 접근 권한, 보안 관련:
29
+
30
+ | 에러 코드 | 행동 |
31
+ |-----------|------|
32
+ | `GATED_ACCESS_REQUIRED` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" 사용자 질문 도구 호출 |
33
+ | `SPACE_ONLY` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" 사용자 질문 도구 호출 |
34
+ | `APPROVAL_REQUIRED` | 승인 대기 안내 |
35
+ | `NO_ACCESS` | 접근 방법 안내 |
36
+
37
+ #### 4. 그 외 에러
38
+ `fix` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.