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/commands/access.js
CHANGED
|
@@ -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/
|
|
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.
|
|
45
|
+
if (!result.success || !result.agent) {
|
|
46
46
|
throw new Error('서버 응답이 올바르지 않습니다.');
|
|
47
47
|
}
|
|
48
|
-
const
|
|
48
|
+
const agentSlug = result.agent.slug;
|
|
49
49
|
if (json) {
|
|
50
|
-
console.log(JSON.stringify({ status: 'ok',
|
|
50
|
+
console.log(JSON.stringify({ status: 'ok', agent: result.agent }));
|
|
51
51
|
}
|
|
52
52
|
else {
|
|
53
|
-
console.log(`\x1b[32m접근 권한이 부여되었습니다: ${result.
|
|
54
|
-
console.log(`\x1b[33m
|
|
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
|
|
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',
|
|
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('
|
|
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
|
-
//
|
|
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
|
|
43
|
-
if (
|
|
42
|
+
const agentResult = await (0, version_check_js_1.checkAgentVersion)(scopedSlug, force);
|
|
43
|
+
if (agentResult) {
|
|
44
44
|
if (quiet) {
|
|
45
|
-
const byAuthor =
|
|
46
|
-
console.log(`
|
|
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 =
|
|
50
|
-
console.log(`\x1b[33m⚠ ${slug} v${
|
|
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
|
|
60
|
-
for (const result of
|
|
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(`
|
|
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 &&
|
|
71
|
+
if (!quiet && !cliResult && agentResults.length === 0) {
|
|
72
72
|
console.log('모든 것이 최신 상태입니다.');
|
|
73
73
|
}
|
|
74
74
|
}
|
package/dist/commands/create.js
CHANGED
|
@@ -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,
|
|
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이 이미 존재합니다. 기존
|
|
44
|
+
console.error('.relay/relay.yaml이 이미 존재합니다. 기존 에이전트 프로젝트에서는 `relay init`을 사용하세요.');
|
|
43
45
|
}
|
|
44
46
|
process.exit(1);
|
|
45
47
|
}
|
|
46
48
|
// 2. 메타데이터 수집
|
|
47
|
-
|
|
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: '
|
|
70
|
-
{ value: '
|
|
80
|
+
{ value: 'private', label: '링크 공유 — 접근 링크가 있는 사람만' },
|
|
81
|
+
{ value: 'internal', label: '비공개 — Org 멤버만' },
|
|
71
82
|
],
|
|
72
83
|
}));
|
|
73
84
|
process.exit(1);
|
|
74
85
|
}
|
|
75
|
-
if (!['public', '
|
|
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,
|
|
90
|
+
fix: `visibility는 public, private, internal 중 하나여야 합니다.`,
|
|
80
91
|
options: [
|
|
81
92
|
{ value: 'public', label: '공개' },
|
|
82
|
-
{ value: '
|
|
83
|
-
{ value: '
|
|
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 — 새
|
|
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: '
|
|
111
|
-
{ name: '비공개 (
|
|
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:
|
|
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:
|
|
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}
|
|
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
|
|
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
|
|
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}'는 설치되어 있지 않습니다.` };
|
package/dist/commands/diff.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -13,7 +13,7 @@ const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
|
13
13
|
const config_js_1 = require("../lib/config.js");
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
15
|
const pkg = require('../../package.json');
|
|
16
|
-
const
|
|
16
|
+
const VALID_AGENT_DIRS = ['skills', 'agents', 'rules', 'commands'];
|
|
17
17
|
function resolveTools(toolsArg) {
|
|
18
18
|
const raw = toolsArg.trim().toLowerCase();
|
|
19
19
|
if (raw === 'all') {
|
|
@@ -30,14 +30,14 @@ function resolveTools(toolsArg) {
|
|
|
30
30
|
function showWelcome() {
|
|
31
31
|
const lines = [
|
|
32
32
|
'',
|
|
33
|
-
' \x1b[33m⚡\x1b[0m \x1b[1mrelay\x1b[0m — Agent
|
|
33
|
+
' \x1b[33m⚡\x1b[0m \x1b[1mrelay\x1b[0m — Agent Marketplace',
|
|
34
34
|
'',
|
|
35
35
|
' 에이전트 CLI에 relay 커맨드를 연결합니다.',
|
|
36
36
|
'',
|
|
37
37
|
' \x1b[2mUser 커맨드 (글로벌)\x1b[0m',
|
|
38
|
-
' /relay-install
|
|
38
|
+
' /relay-install 에이전트 탐색 & 설치',
|
|
39
39
|
' /relay-status 설치 현황 & Space',
|
|
40
|
-
' /relay-uninstall
|
|
40
|
+
' /relay-uninstall 에이전트 삭제',
|
|
41
41
|
'',
|
|
42
42
|
];
|
|
43
43
|
console.log(lines.join('\n'));
|
|
@@ -101,16 +101,16 @@ function hasGlobalUserCommands() {
|
|
|
101
101
|
return command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
104
|
+
* 에이전트 프로젝트인지 감지한다 (.relay/ 디렉토리 내 relay.yaml 또는 에이전트 디렉토리 구조).
|
|
105
105
|
*/
|
|
106
|
-
function
|
|
106
|
+
function isAgentProject(projectPath) {
|
|
107
107
|
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
108
108
|
if (!fs_1.default.existsSync(relayDir))
|
|
109
109
|
return false;
|
|
110
110
|
if (fs_1.default.existsSync(path_1.default.join(relayDir, 'relay.yaml'))) {
|
|
111
111
|
return true;
|
|
112
112
|
}
|
|
113
|
-
return
|
|
113
|
+
return VALID_AGENT_DIRS.some((d) => {
|
|
114
114
|
const dirPath = path_1.default.join(relayDir, d);
|
|
115
115
|
if (!fs_1.default.existsSync(dirPath))
|
|
116
116
|
return false;
|
|
@@ -131,7 +131,7 @@ function registerInit(program) {
|
|
|
131
131
|
const projectPath = process.cwd();
|
|
132
132
|
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
133
133
|
const detectedIds = new Set(detected.map((t) => t.value));
|
|
134
|
-
const isBuilder =
|
|
134
|
+
const isBuilder = isAgentProject(projectPath);
|
|
135
135
|
// ── 0. --json 모드에서 --tools/--all 없으면 MISSING_TOOLS 에러 ──
|
|
136
136
|
if (json && !opts.tools && !opts.all && !opts.auto) {
|
|
137
137
|
const detectedOptions = detected.map((t) => ({ value: t.value, label: t.name }));
|
|
@@ -163,7 +163,7 @@ function registerInit(program) {
|
|
|
163
163
|
};
|
|
164
164
|
(0, config_js_1.saveInstalled)(installed);
|
|
165
165
|
}
|
|
166
|
-
// ── 2. 로컬 Builder 커맨드 (
|
|
166
|
+
// ── 2. 로컬 Builder 커맨드 (에이전트 프로젝트인 경우) ──
|
|
167
167
|
// relay-publish가 글로벌로 승격되어 BUILDER_COMMANDS가 비어있으면 스킵
|
|
168
168
|
const localResults = [];
|
|
169
169
|
if (isBuilder && command_adapter_js_1.BUILDER_COMMANDS.length > 0) {
|
|
@@ -267,7 +267,7 @@ function registerInit(program) {
|
|
|
267
267
|
console.log();
|
|
268
268
|
}
|
|
269
269
|
if (!isBuilder) {
|
|
270
|
-
console.log('
|
|
270
|
+
console.log(' 에이전트를 만들려면 \x1b[33mrelay create <name>\x1b[0m을 사용하세요.');
|
|
271
271
|
console.log();
|
|
272
272
|
}
|
|
273
273
|
console.log(' IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.');
|