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.
- package/dist/commands/access.js +12 -12
- package/dist/commands/changelog.js +2 -2
- package/dist/commands/check-update.js +12 -12
- package/dist/commands/create.js +46 -19
- package/dist/commands/deploy-record.js +2 -2
- package/dist/commands/diff.js +2 -2
- package/dist/commands/grant.d.ts +33 -0
- package/dist/commands/grant.js +190 -0
- package/dist/commands/init.js +10 -10
- package/dist/commands/install.js +69 -68
- package/dist/commands/join.js +3 -3
- package/dist/commands/list.js +15 -15
- package/dist/commands/login.js +10 -3
- package/dist/commands/orgs.js +1 -1
- package/dist/commands/outdated.js +7 -7
- package/dist/commands/package.d.ts +18 -0
- package/dist/commands/package.js +355 -146
- package/dist/commands/ping.js +5 -5
- package/dist/commands/publish.d.ts +1 -1
- package/dist/commands/publish.js +56 -48
- package/dist/commands/search.js +2 -2
- package/dist/commands/status.js +11 -11
- package/dist/commands/uninstall.js +7 -7
- package/dist/commands/update.js +22 -22
- package/dist/commands/versions.js +2 -2
- package/dist/index.js +2 -0
- package/dist/lib/ai-tools.d.ts +15 -0
- package/dist/lib/ai-tools.js +48 -1
- package/dist/lib/api.d.ts +7 -7
- package/dist/lib/api.js +11 -11
- package/dist/lib/command-adapter.js +30 -682
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/guide.js +34 -79
- package/dist/lib/installer.d.ts +2 -2
- package/dist/lib/installer.js +4 -4
- package/dist/lib/preamble.d.ts +4 -4
- package/dist/lib/preamble.js +14 -14
- package/dist/lib/slug.d.ts +5 -0
- package/dist/lib/slug.js +49 -2
- package/dist/lib/update-cache.js +4 -4
- package/dist/lib/version-check.d.ts +3 -3
- package/dist/lib/version-check.js +13 -13
- package/dist/prompts/_business-card.md +41 -0
- package/dist/prompts/_error-handling.md +38 -0
- package/dist/prompts/_requirements-check.md +59 -0
- package/dist/prompts/_setup-cli.md +19 -0
- package/dist/prompts/_setup-login.md +7 -0
- package/dist/prompts/_setup-org.md +27 -0
- package/dist/prompts/business-card.md +41 -0
- package/dist/prompts/error-handling.md +38 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.js +28 -0
- package/dist/prompts/install.md +187 -0
- package/dist/prompts/publish.md +444 -0
- package/dist/prompts/requirements-check.md +59 -0
- package/dist/types.d.ts +9 -9
- package/package.json +3 -3
package/dist/lib/config.d.ts
CHANGED
|
@@ -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,
|
|
12
|
+
/** cwd/.relay/ — 프로젝트 로컬 (installed.json, agents/) */
|
|
13
13
|
export declare function ensureProjectRelayDir(): void;
|
|
14
14
|
export interface TokenData {
|
|
15
15
|
access_token: string;
|
package/dist/lib/config.js
CHANGED
|
@@ -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,
|
|
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}/{
|
|
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
|
|
26
|
-
return `${
|
|
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
|
|
37
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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.
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
return lines.join('\n');
|
|
44
|
+
}
|
|
45
|
+
function buildRequiresSection(requires) {
|
|
46
|
+
const summary = buildRequiresSummary(requires);
|
|
47
|
+
if (!summary)
|
|
100
48
|
return '';
|
|
101
|
-
return
|
|
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 === '
|
|
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
|
-
아래 단계를 순서대로 실행하면
|
|
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
|
-
설치 후
|
|
93
|
+
설치 후 아래 의존성을 확인하고 환경을 구성합니다.
|
|
139
94
|
|
|
140
95
|
${requiresSectionFinal}${usageHint}---
|
|
141
|
-
*출처: https://relayax.com — Agent
|
|
96
|
+
*출처: https://relayax.com — Agent Marketplace*
|
|
142
97
|
*설치: \`relay install ${scopedSlug}\`*
|
|
143
98
|
`;
|
|
144
99
|
}
|
package/dist/lib/installer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
export declare function
|
|
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/는 유지
|
package/dist/lib/installer.js
CHANGED
|
@@ -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.
|
|
7
|
-
exports.
|
|
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
|
|
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
|
|
39
|
+
function uninstallAgent(files) {
|
|
40
40
|
const removed = [];
|
|
41
41
|
for (const file of files) {
|
|
42
42
|
try {
|
package/dist/lib/preamble.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
12
|
+
* agentDir에 bin/relay-preamble.sh를 생성한다.
|
|
13
13
|
*/
|
|
14
|
-
export declare function generatePreambleBin(
|
|
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
|
-
*
|
|
20
|
+
* 에이전트의 사용자 진입점 파일에 preamble을 주입한다.
|
|
21
21
|
*/
|
|
22
|
-
export declare function
|
|
22
|
+
export declare function injectPreambleToAgent(agentDir: string, slug: string): number;
|
package/dist/lib/preamble.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.generatePreambleScript = generatePreambleScript;
|
|
|
7
7
|
exports.generatePreamble = generatePreamble;
|
|
8
8
|
exports.generatePreambleBin = generatePreambleBin;
|
|
9
9
|
exports.injectPreamble = injectPreamble;
|
|
10
|
-
exports.
|
|
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/
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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 \`
|
|
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
|
-
*
|
|
67
|
+
* agentDir에 bin/relay-preamble.sh를 생성한다.
|
|
68
68
|
*/
|
|
69
|
-
function generatePreambleBin(
|
|
70
|
-
const binDir = path_1.default.join(
|
|
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
|
-
*
|
|
109
|
+
* 에이전트의 사용자 진입점 파일에 preamble을 주입한다.
|
|
110
110
|
*/
|
|
111
|
-
function
|
|
111
|
+
function injectPreambleToAgent(agentDir, slug) {
|
|
112
112
|
let count = 0;
|
|
113
113
|
// 1. user-invocable 서브 스킬 SKILL.md
|
|
114
|
-
const skillsDir = path_1.default.join(
|
|
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(
|
|
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'))
|
package/dist/lib/slug.d.ts
CHANGED
|
@@ -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}'에 해당하는
|
|
99
|
+
throw new Error(`'${input}'에 해당하는 에이전트가 여러 개입니다. 전체 slug를 지정해주세요:\n${list}`);
|
|
53
100
|
}
|
package/dist/lib/update-cache.js
CHANGED
|
@@ -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.
|
|
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.
|
|
47
|
-
cache.
|
|
48
|
-
cache.
|
|
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' | '
|
|
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
|
|
10
|
-
export declare function
|
|
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.
|
|
5
|
-
exports.
|
|
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
|
|
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
|
|
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
|
|
47
|
-
if (
|
|
48
|
-
(0, api_js_1.sendUsagePing)(
|
|
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 (
|
|
50
|
+
if (agent.version !== entry.version) {
|
|
51
51
|
return {
|
|
52
|
-
type: '
|
|
52
|
+
type: 'agent',
|
|
53
53
|
slug,
|
|
54
54
|
current: entry.version,
|
|
55
|
-
latest:
|
|
56
|
-
author:
|
|
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
|
|
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
|
|
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. 사용자에게 선택지 제시 (AskUserQuestion)
|
|
16
|
+
`options` 필드가 있는 에러:
|
|
17
|
+
|
|
18
|
+
| 에러 코드 | 행동 |
|
|
19
|
+
|-----------|------|
|
|
20
|
+
| `MISSING_VISIBILITY` | options의 label을 선택지로 AskUserQuestion 호출 |
|
|
21
|
+
| `MISSING_FIELD` | fix 안내 + 사용자에게 값 입력 요청 |
|
|
22
|
+
| `MISSING_TOOLS` | options의 감지된 도구 목록을 선택지로 AskUserQuestion 호출 |
|
|
23
|
+
| `MISSING_SPACE` | options의 Space 목록을 선택지로 AskUserQuestion 호출 |
|
|
24
|
+
|
|
25
|
+
사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
|
|
26
|
+
|
|
27
|
+
#### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
|
|
28
|
+
구매, 접근 권한, 보안 관련:
|
|
29
|
+
|
|
30
|
+
| 에러 코드 | 행동 |
|
|
31
|
+
|-----------|------|
|
|
32
|
+
| `GATED_ACCESS_REQUIRED` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" AskUserQuestion |
|
|
33
|
+
| `SPACE_ONLY` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" AskUserQuestion |
|
|
34
|
+
| `APPROVAL_REQUIRED` | 승인 대기 안내 |
|
|
35
|
+
| `NO_ACCESS` | 접근 방법 안내 |
|
|
36
|
+
|
|
37
|
+
#### 4. 그 외 에러
|
|
38
|
+
`fix` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.
|