relayax-cli 0.2.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.d.ts +2 -0
- package/dist/commands/diff.js +72 -0
- 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 +125 -256
- package/dist/commands/join.d.ts +3 -2
- package/dist/commands/join.js +18 -69
- package/dist/commands/list.js +23 -26
- package/dist/commands/login.js +10 -3
- package/dist/commands/orgs.d.ts +10 -0
- package/dist/commands/orgs.js +128 -0
- 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 +105 -103
- 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.d.ts +2 -0
- package/dist/commands/versions.js +44 -0
- package/dist/index.js +8 -2
- package/dist/lib/ai-tools.d.ts +15 -0
- package/dist/lib/ai-tools.js +48 -1
- package/dist/lib/api.d.ts +13 -12
- package/dist/lib/api.js +24 -39
- package/dist/lib/command-adapter.js +41 -693
- package/dist/lib/config.d.ts +10 -5
- package/dist/lib/config.js +106 -24
- 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 -15
- package/dist/lib/slug.d.ts +5 -1
- package/dist/lib/slug.js +52 -9
- 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 +10 -10
- package/package.json +3 -3
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를 재시작하면 슬래시 커맨드가 활성화됩니다.');
|
package/dist/commands/install.js
CHANGED
|
@@ -12,31 +12,13 @@ const config_js_1 = require("../lib/config.js");
|
|
|
12
12
|
const slug_js_1 = require("../lib/slug.js");
|
|
13
13
|
const contact_format_js_1 = require("../lib/contact-format.js");
|
|
14
14
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
15
|
-
const join_js_1 = require("./join.js");
|
|
16
15
|
const init_js_1 = require("./init.js");
|
|
17
|
-
/**
|
|
18
|
-
* slugInput이 "@spaces/{spaceSlug}/{teamSlug}" 또는 "@{spaceSlug}/{teamSlug}" 형식이면 파싱해 반환.
|
|
19
|
-
* 아니면 null.
|
|
20
|
-
*/
|
|
21
|
-
function parseSpaceTarget(slugInput) {
|
|
22
|
-
// @spaces/{spaceSlug}/{teamSlug} 형식 (기존)
|
|
23
|
-
const m1 = slugInput.match(/^@spaces\/([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
|
|
24
|
-
if (m1) {
|
|
25
|
-
return { spaceSlug: m1[1], rawTeamSlug: m1[2], teamSlug: `@${m1[1]}/${m1[2]}` };
|
|
26
|
-
}
|
|
27
|
-
// @{spaceSlug}/{teamSlug} 형식 (신규)
|
|
28
|
-
const m2 = slugInput.match(/^@([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
|
|
29
|
-
if (m2) {
|
|
30
|
-
return { spaceSlug: m2[1], rawTeamSlug: m2[2], teamSlug: `@${m2[1]}/${m2[2]}` };
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
16
|
function registerInstall(program) {
|
|
35
17
|
program
|
|
36
18
|
.command('install <slug>')
|
|
37
|
-
.description('에이전트
|
|
38
|
-
.option('--join-code <code>', '
|
|
39
|
-
.action(async (slugInput,
|
|
19
|
+
.description('에이전트 패키지를 .relay/agents/에 다운로드합니다')
|
|
20
|
+
.option('--join-code <code>', '초대 코드 (Organization 에이전트 설치 시 자동 가입)')
|
|
21
|
+
.action(async (slugInput, _opts) => {
|
|
40
22
|
const json = program.opts().json ?? false;
|
|
41
23
|
const projectPath = process.cwd();
|
|
42
24
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
@@ -48,214 +30,108 @@ function registerInstall(program) {
|
|
|
48
30
|
(0, init_js_1.installGlobalUserCommands)();
|
|
49
31
|
}
|
|
50
32
|
try {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// 0a. --join-code가 있으면 먼저 Space 가입 시도
|
|
54
|
-
if (opts.joinCode && spaceTarget) {
|
|
55
|
-
try {
|
|
56
|
-
const { spaceName } = await (0, join_js_1.joinSpace)(spaceTarget.spaceSlug, opts.joinCode);
|
|
57
|
-
if (!json) {
|
|
58
|
-
console.log(`\x1b[32m✅ ${spaceName} Space에 가입했습니다\x1b[0m`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch (joinErr) {
|
|
62
|
-
const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
|
|
63
|
-
// 이미 멤버인 경우 설치 계속 진행
|
|
64
|
-
if (joinMsg !== 'ALREADY_MEMBER') {
|
|
65
|
-
if (!json) {
|
|
66
|
-
console.error(`\x1b[33m경고: Space 가입 실패 — ${joinMsg}\x1b[0m`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// 0b. Resolve scoped slug and fetch team metadata
|
|
72
|
-
let team;
|
|
33
|
+
// Resolve scoped slug and fetch agent metadata
|
|
34
|
+
let agent;
|
|
73
35
|
let slug;
|
|
74
36
|
let parsed;
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
37
|
+
// Extract version from @owner/agent@version syntax (e.g. acme/writer@1.2.0)
|
|
38
|
+
// Version-specific install is not yet supported by the registry API;
|
|
39
|
+
// the match is kept for future use when per-version package URLs are available.
|
|
40
|
+
const versionMatch = slugInput.match(/^(.+)@(\d+\.\d+\.\d+.*)$/);
|
|
41
|
+
const actualSlugInput = versionMatch ? versionMatch[1] : slugInput;
|
|
42
|
+
parsed = await (0, slug_js_1.resolveSlug)(actualSlugInput);
|
|
43
|
+
slug = parsed.full;
|
|
44
|
+
try {
|
|
45
|
+
agent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
46
|
+
}
|
|
47
|
+
catch (fetchErr) {
|
|
48
|
+
const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
49
|
+
if (fetchMsg.includes('403')) {
|
|
50
|
+
// Parse error body for membership_status, visibility, purchase_info
|
|
51
|
+
let membershipStatus;
|
|
52
|
+
let errorVisibility;
|
|
53
|
+
let purchaseInfo;
|
|
54
|
+
try {
|
|
55
|
+
const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
|
|
56
|
+
membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
|
|
57
|
+
errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
|
|
58
|
+
if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
|
|
59
|
+
purchaseInfo = errBody.purchase_info;
|
|
60
|
+
}
|
|
90
61
|
}
|
|
91
|
-
|
|
62
|
+
catch { /* ignore parse errors */ }
|
|
63
|
+
// Private agent: show purchase info + relay access hint
|
|
64
|
+
if (errorVisibility === 'private' || purchaseInfo) {
|
|
92
65
|
if (json) {
|
|
93
66
|
console.error(JSON.stringify({
|
|
94
|
-
error: '
|
|
95
|
-
message: '이
|
|
96
|
-
|
|
97
|
-
|
|
67
|
+
error: 'ACCESS_REQUIRED',
|
|
68
|
+
message: '이 에이전트는 접근 권한이 필요합니다.',
|
|
69
|
+
slug,
|
|
70
|
+
purchase_info: purchaseInfo ?? null,
|
|
71
|
+
fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
|
|
98
72
|
}));
|
|
99
73
|
}
|
|
100
74
|
else {
|
|
101
|
-
console.error('\x1b[31m이
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
throw fetchErr;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (!usedSpacePath) {
|
|
111
|
-
// Fallback: treat as normal registry install
|
|
112
|
-
parsed = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
113
|
-
slug = parsed.full;
|
|
114
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
// slug from server is "@spaces/{spaceSlug}/{teamSlug}" — derive local path parts
|
|
118
|
-
// team is guaranteed assigned by installSpaceTeam above (usedSpacePath === true)
|
|
119
|
-
parsed = { owner: spaceTarget.spaceSlug, name: spaceTarget.rawTeamSlug, full: team.slug };
|
|
120
|
-
slug = team.slug;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// Normal registry install
|
|
125
|
-
parsed = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
126
|
-
slug = parsed.full;
|
|
127
|
-
try {
|
|
128
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
129
|
-
}
|
|
130
|
-
catch (fetchErr) {
|
|
131
|
-
const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
132
|
-
if (fetchMsg.includes('403')) {
|
|
133
|
-
// Parse error body for join_policy, membership_status, visibility, purchase_info
|
|
134
|
-
let joinPolicy;
|
|
135
|
-
let membershipStatus;
|
|
136
|
-
let errorVisibility;
|
|
137
|
-
let purchaseInfo;
|
|
138
|
-
try {
|
|
139
|
-
const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
|
|
140
|
-
joinPolicy = typeof errBody.join_policy === 'string' ? errBody.join_policy : undefined;
|
|
141
|
-
membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
|
|
142
|
-
errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
|
|
143
|
-
if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
|
|
144
|
-
purchaseInfo = errBody.purchase_info;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
catch { /* ignore parse errors */ }
|
|
148
|
-
// Gated team: show purchase info + relay access hint
|
|
149
|
-
if (errorVisibility === 'gated' || purchaseInfo) {
|
|
150
|
-
if (json) {
|
|
151
|
-
console.error(JSON.stringify({
|
|
152
|
-
error: 'GATED_ACCESS_REQUIRED',
|
|
153
|
-
message: '이 팀은 접근 권한이 필요합니다.',
|
|
154
|
-
slug,
|
|
155
|
-
purchase_info: purchaseInfo ?? null,
|
|
156
|
-
fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
console.error('\x1b[31m🔒 이 팀은 접근 권한이 필요합니다.\x1b[0m');
|
|
161
|
-
if (purchaseInfo?.message) {
|
|
162
|
-
console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
|
|
163
|
-
}
|
|
164
|
-
if (purchaseInfo?.url) {
|
|
165
|
-
console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
|
|
166
|
-
}
|
|
167
|
-
console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
|
|
168
|
-
}
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
if (joinPolicy === 'auto') {
|
|
172
|
-
// Auto-join the Space then retry install
|
|
173
|
-
if (!json) {
|
|
174
|
-
console.error(`\x1b[33m⚙ Space에 자동 가입합니다...\x1b[0m`);
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
const spaceSlug = parsed.owner;
|
|
178
|
-
await (0, join_js_1.joinSpace)(spaceSlug, '');
|
|
179
|
-
if (!json) {
|
|
180
|
-
console.error(`\x1b[32m✓ Space에 가입했습니다\x1b[0m`);
|
|
181
|
-
}
|
|
182
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
183
|
-
}
|
|
184
|
-
catch (joinErr) {
|
|
185
|
-
const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
|
|
186
|
-
if (json) {
|
|
187
|
-
console.error(JSON.stringify({ error: 'JOIN_FAILED', message: joinMsg, slug, fix: 'Space slug와 초대 코드를 확인 후 재시도하세요.' }));
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.error(`\x1b[31mSpace 가입 실패: ${joinMsg}\x1b[0m`);
|
|
191
|
-
}
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else if (joinPolicy === 'approval') {
|
|
196
|
-
const spaceSlug = parsed.owner;
|
|
197
|
-
if (json) {
|
|
198
|
-
console.error(JSON.stringify({
|
|
199
|
-
error: 'APPROVAL_REQUIRED',
|
|
200
|
-
message: `가입 신청이 필요합니다.`,
|
|
201
|
-
slug,
|
|
202
|
-
spaceSlug,
|
|
203
|
-
fix: `relay join @${spaceSlug} --code <초대코드>로 Space에 가입한 후 재시도하세요. 초대 코드는 Space 관리자에게 요청하세요.`,
|
|
204
|
-
}));
|
|
75
|
+
console.error('\x1b[31m이 에이전트는 접근 권한이 필요합니다.\x1b[0m');
|
|
76
|
+
if (purchaseInfo?.message) {
|
|
77
|
+
console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
|
|
205
78
|
}
|
|
206
|
-
|
|
207
|
-
console.error(
|
|
79
|
+
if (purchaseInfo?.url) {
|
|
80
|
+
console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
|
|
208
81
|
}
|
|
209
|
-
|
|
82
|
+
console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
|
|
210
83
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
console.error('\x1b[31m이 팀에 대한 접근 권한이 없습니다.\x1b[0m');
|
|
223
|
-
}
|
|
224
|
-
process.exit(1);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (membershipStatus === 'member') {
|
|
87
|
+
// Member but no access to this specific agent
|
|
88
|
+
if (json) {
|
|
89
|
+
console.error(JSON.stringify({
|
|
90
|
+
error: 'NO_ACCESS',
|
|
91
|
+
message: '이 에이전트에 대한 접근 권한이 없습니다.',
|
|
92
|
+
slug,
|
|
93
|
+
fix: '이 에이전트의 접근 링크 코드가 있으면 `relay access ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 에이전트 제작자에게 문의하세요.',
|
|
94
|
+
}));
|
|
225
95
|
}
|
|
226
96
|
else {
|
|
227
|
-
|
|
228
|
-
console.error(JSON.stringify({
|
|
229
|
-
error: 'SPACE_ONLY',
|
|
230
|
-
message: '이 팀은 Space 멤버만 설치 가능합니다.',
|
|
231
|
-
slug,
|
|
232
|
-
fix: 'Space 관리자에게 초대 코드를 요청한 후 `relay join <space-slug> --code <코드>`로 가입하세요.',
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
console.error('\x1b[31m이 팀은 Space 멤버만 설치 가능합니다.\x1b[0m');
|
|
237
|
-
console.error('\x1b[33mSpace 관리자에게 초대 코드를 요청하세요.\x1b[0m');
|
|
238
|
-
}
|
|
239
|
-
process.exit(1);
|
|
97
|
+
console.error('\x1b[31m이 에이전트에 대한 접근 권한이 없습니다.\x1b[0m');
|
|
240
98
|
}
|
|
99
|
+
process.exit(1);
|
|
241
100
|
}
|
|
242
101
|
else {
|
|
243
|
-
|
|
102
|
+
if (json) {
|
|
103
|
+
console.error(JSON.stringify({
|
|
104
|
+
error: 'ACCESS_REQUIRED',
|
|
105
|
+
message: '이 에이전트는 접근 권한이 필요합니다.',
|
|
106
|
+
slug,
|
|
107
|
+
fix: '초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.',
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.error('\x1b[31m이 에이전트는 접근 권한이 필요합니다.\x1b[0m');
|
|
112
|
+
console.error('\x1b[33m초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.\x1b[0m');
|
|
113
|
+
}
|
|
114
|
+
process.exit(1);
|
|
244
115
|
}
|
|
245
116
|
}
|
|
117
|
+
else {
|
|
118
|
+
throw fetchErr;
|
|
119
|
+
}
|
|
246
120
|
}
|
|
247
|
-
if (!
|
|
248
|
-
throw new Error('
|
|
249
|
-
|
|
121
|
+
if (!agent)
|
|
122
|
+
throw new Error('에이전트 정보를 가져오지 못했습니다.');
|
|
123
|
+
// Re-bind as non-optional so TypeScript tracks the narrowing through nested scopes
|
|
124
|
+
let resolvedAgent = agent;
|
|
125
|
+
const agentDir = path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
250
126
|
// 2. Visibility check + auto-login
|
|
251
|
-
const visibility =
|
|
252
|
-
if (visibility === '
|
|
127
|
+
const visibility = resolvedAgent.visibility ?? 'public';
|
|
128
|
+
if (visibility === 'internal') {
|
|
253
129
|
let token = await (0, config_js_1.getValidToken)();
|
|
254
130
|
if (!token) {
|
|
255
131
|
const isTTY = Boolean(process.stdin.isTTY);
|
|
256
132
|
if (isTTY && !json) {
|
|
257
133
|
// Auto-login: TTY 환경에서 자동으로 login 플로우 트리거
|
|
258
|
-
console.error('\x1b[33m⚙ 이
|
|
134
|
+
console.error('\x1b[33m⚙ 이 에이전트는 로그인이 필요합니다. 로그인을 시작합니다...\x1b[0m');
|
|
259
135
|
const { runLogin } = await import('./login.js');
|
|
260
136
|
await runLogin();
|
|
261
137
|
token = await (0, config_js_1.getValidToken)();
|
|
@@ -266,12 +142,12 @@ function registerInstall(program) {
|
|
|
266
142
|
error: 'LOGIN_REQUIRED',
|
|
267
143
|
visibility,
|
|
268
144
|
slug,
|
|
269
|
-
message: '이
|
|
145
|
+
message: '이 에이전트는 로그인이 필요합니다. relay login을 먼저 실행하세요.',
|
|
270
146
|
fix: 'relay login 실행 후 재시도하세요.',
|
|
271
147
|
}));
|
|
272
148
|
}
|
|
273
149
|
else {
|
|
274
|
-
console.error('\x1b[31m이
|
|
150
|
+
console.error('\x1b[31m이 에이전트는 로그인이 필요합니다. relay login 을 먼저 실행하세요.\x1b[0m');
|
|
275
151
|
}
|
|
276
152
|
process.exit(1);
|
|
277
153
|
}
|
|
@@ -280,35 +156,30 @@ function registerInstall(program) {
|
|
|
280
156
|
// 3. Download package (retry once if signed URL expired)
|
|
281
157
|
let tarPath;
|
|
282
158
|
try {
|
|
283
|
-
tarPath = await (0, storage_js_1.downloadPackage)(
|
|
159
|
+
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
284
160
|
}
|
|
285
161
|
catch (dlErr) {
|
|
286
162
|
const dlMsg = dlErr instanceof Error ? dlErr.message : String(dlErr);
|
|
287
163
|
if (dlMsg.includes('403') || dlMsg.includes('expired')) {
|
|
288
|
-
// Signed URL expired — re-fetch
|
|
164
|
+
// Signed URL expired — re-fetch agent info for new URL and retry
|
|
289
165
|
if (!json) {
|
|
290
166
|
console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
|
|
291
167
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
297
|
-
}
|
|
298
|
-
tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
|
|
168
|
+
resolvedAgent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
169
|
+
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
299
170
|
}
|
|
300
171
|
else {
|
|
301
172
|
throw dlErr;
|
|
302
173
|
}
|
|
303
174
|
}
|
|
304
|
-
// 4. Extract to .relay/
|
|
305
|
-
if (fs_1.default.existsSync(
|
|
306
|
-
fs_1.default.rmSync(
|
|
175
|
+
// 4. Extract to .relay/agents/<slug>/
|
|
176
|
+
if (fs_1.default.existsSync(agentDir)) {
|
|
177
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
307
178
|
}
|
|
308
|
-
fs_1.default.mkdirSync(
|
|
309
|
-
await (0, storage_js_1.extractPackage)(tarPath,
|
|
179
|
+
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
180
|
+
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
310
181
|
// 4.5. Inject preamble (update check) into SKILL.md and commands
|
|
311
|
-
(0, preamble_js_1.
|
|
182
|
+
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
312
183
|
// 5. Count extracted files
|
|
313
184
|
function countFiles(dir) {
|
|
314
185
|
let count = 0;
|
|
@@ -324,59 +195,57 @@ function registerInstall(program) {
|
|
|
324
195
|
}
|
|
325
196
|
return count;
|
|
326
197
|
}
|
|
327
|
-
const fileCount = countFiles(
|
|
328
|
-
// 6. Record in installed.json
|
|
198
|
+
const fileCount = countFiles(agentDir);
|
|
199
|
+
// 6. Record in installed.json
|
|
329
200
|
const installed = (0, config_js_1.loadInstalled)();
|
|
330
201
|
installed[slug] = {
|
|
331
|
-
|
|
332
|
-
version:
|
|
202
|
+
agent_id: resolvedAgent.id,
|
|
203
|
+
version: resolvedAgent.version,
|
|
333
204
|
installed_at: new Date().toISOString(),
|
|
334
|
-
files: [
|
|
335
|
-
...(spaceTarget ? { space_slug: spaceTarget.spaceSlug } : {}),
|
|
205
|
+
files: [agentDir],
|
|
336
206
|
};
|
|
337
207
|
(0, config_js_1.saveInstalled)(installed);
|
|
338
|
-
// 7. Report install + usage ping (non-blocking,
|
|
339
|
-
await (0, api_js_1.reportInstall)(
|
|
340
|
-
(0, api_js_1.sendUsagePing)(
|
|
208
|
+
// 7. Report install + usage ping (non-blocking, agent_id 기반)
|
|
209
|
+
await (0, api_js_1.reportInstall)(resolvedAgent.id, slug, resolvedAgent.version);
|
|
210
|
+
(0, api_js_1.sendUsagePing)(resolvedAgent.id, slug, resolvedAgent.version);
|
|
341
211
|
const result = {
|
|
342
212
|
status: 'ok',
|
|
343
|
-
|
|
213
|
+
agent: resolvedAgent.name,
|
|
344
214
|
slug,
|
|
345
|
-
version:
|
|
346
|
-
commands:
|
|
215
|
+
version: resolvedAgent.version,
|
|
216
|
+
commands: resolvedAgent.commands,
|
|
347
217
|
files: fileCount,
|
|
348
|
-
install_path:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
contact_links: team.author.contact_links ?? [],
|
|
218
|
+
install_path: agentDir,
|
|
219
|
+
author: resolvedAgent.author ? {
|
|
220
|
+
username: resolvedAgent.author.username,
|
|
221
|
+
display_name: resolvedAgent.author.display_name ?? null,
|
|
222
|
+
contact_links: resolvedAgent.author.contact_links ?? [],
|
|
354
223
|
} : null,
|
|
355
|
-
welcome:
|
|
224
|
+
welcome: resolvedAgent.welcome ?? null,
|
|
356
225
|
};
|
|
357
226
|
if (json) {
|
|
358
227
|
console.log(JSON.stringify(result));
|
|
359
228
|
}
|
|
360
229
|
else {
|
|
361
|
-
const authorUsername =
|
|
362
|
-
const authorDisplayName =
|
|
230
|
+
const authorUsername = resolvedAgent.author?.username;
|
|
231
|
+
const authorDisplayName = resolvedAgent.author?.display_name ?? authorUsername ?? '';
|
|
363
232
|
const authorSuffix = authorUsername ? ` \x1b[90mby @${authorUsername}\x1b[0m` : '';
|
|
364
|
-
console.log(`\n\x1b[32m✓ ${
|
|
365
|
-
console.log(` 위치: \x1b[36m${
|
|
233
|
+
console.log(`\n\x1b[32m✓ ${resolvedAgent.name} 다운로드 완료\x1b[0m v${resolvedAgent.version}${authorSuffix}`);
|
|
234
|
+
console.log(` 위치: \x1b[36m${agentDir}\x1b[0m`);
|
|
366
235
|
console.log(` 파일: ${fileCount}개`);
|
|
367
|
-
if (
|
|
236
|
+
if (resolvedAgent.commands.length > 0) {
|
|
368
237
|
console.log('\n 포함된 커맨드:');
|
|
369
|
-
for (const cmd of
|
|
238
|
+
for (const cmd of resolvedAgent.commands) {
|
|
370
239
|
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
371
240
|
}
|
|
372
241
|
}
|
|
373
242
|
// Builder business card
|
|
374
|
-
const contactParts = (0, contact_format_js_1.formatContactParts)(
|
|
375
|
-
const hasCard =
|
|
243
|
+
const contactParts = (0, contact_format_js_1.formatContactParts)(resolvedAgent.author?.contact_links);
|
|
244
|
+
const hasCard = resolvedAgent.welcome || contactParts.length > 0 || authorUsername;
|
|
376
245
|
if (hasCard) {
|
|
377
246
|
console.log(`\n \x1b[90m┌─ ${authorDisplayName || authorUsername || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || authorUsername || '빌더').length))}┐\x1b[0m`);
|
|
378
|
-
if (
|
|
379
|
-
const truncated =
|
|
247
|
+
if (resolvedAgent.welcome) {
|
|
248
|
+
const truncated = resolvedAgent.welcome.length > 45 ? resolvedAgent.welcome.slice(0, 45) + '...' : resolvedAgent.welcome;
|
|
380
249
|
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
381
250
|
}
|
|
382
251
|
if (contactParts.length > 0) {
|
|
@@ -388,15 +257,15 @@ function registerInstall(program) {
|
|
|
388
257
|
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
389
258
|
}
|
|
390
259
|
// Usage hint (type-aware)
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
260
|
+
const agentType = resolvedAgent.type;
|
|
261
|
+
if (agentType === 'passive') {
|
|
393
262
|
console.log(`\n\x1b[33m💡 자동 적용됩니다. 별도 실행 없이 동작합니다.\x1b[0m`);
|
|
394
263
|
}
|
|
395
|
-
else if (
|
|
396
|
-
console.log(`\n\x1b[33m💡 자동 적용 + \x1b[1m/${
|
|
264
|
+
else if (agentType === 'hybrid' && resolvedAgent.commands && resolvedAgent.commands.length > 0) {
|
|
265
|
+
console.log(`\n\x1b[33m💡 자동 적용 + \x1b[1m/${resolvedAgent.commands[0].name}\x1b[0m\x1b[33m 으로 추가 기능을 사용할 수 있습니다.\x1b[0m`);
|
|
397
266
|
}
|
|
398
|
-
else if (
|
|
399
|
-
console.log(`\n\x1b[33m💡 사용법: \x1b[1m/${
|
|
267
|
+
else if (resolvedAgent.commands && resolvedAgent.commands.length > 0) {
|
|
268
|
+
console.log(`\n\x1b[33m💡 사용법: \x1b[1m/${resolvedAgent.commands[0].name}\x1b[0m`);
|
|
400
269
|
}
|
|
401
270
|
else {
|
|
402
271
|
console.log(`\n\x1b[33m💡 설치 완료! AI 에이전트에서 사용할 수 있습니다.\x1b[0m`);
|
package/dist/commands/join.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
2
|
+
export declare function joinOrg(orgSlug: string, code: string): Promise<{
|
|
3
|
+
type: string;
|
|
4
|
+
role?: string;
|
|
4
5
|
}>;
|
|
5
6
|
export declare function registerJoin(program: Command): void;
|