relayax-cli 0.3.43 → 0.3.45
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/create.js +3 -1
- package/dist/commands/init.js +3 -1
- package/dist/commands/install.js +3 -20
- package/dist/commands/login.js +47 -2
- package/dist/commands/package.js +8 -4
- package/dist/commands/publish.js +3 -28
- package/dist/commands/status.js +4 -2
- package/dist/commands/uninstall.js +4 -2
- package/dist/commands/update.js +0 -20
- package/dist/lib/ai-tools.d.ts +2 -2
- package/dist/lib/ai-tools.js +5 -5
- package/dist/lib/command-adapter.js +1 -1
- package/dist/lib/guide.js +5 -2
- package/dist/lib/paths.d.ts +10 -0
- package/dist/lib/paths.js +22 -0
- package/dist/prompts/_setup-cli.md +7 -0
- package/dist/prompts/_setup-login.md +29 -1
- package/dist/prompts/_setup-org.md +0 -2
- package/dist/prompts/index.d.ts +0 -1
- package/dist/prompts/index.js +1 -3
- package/dist/prompts/install.md +1 -5
- package/dist/prompts/publish.md +0 -1
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -11,6 +11,7 @@ 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
13
|
const slug_js_1 = require("../lib/slug.js");
|
|
14
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
14
15
|
const DEFAULT_DIRS = ['.relay/skills', '.relay/commands'];
|
|
15
16
|
/**
|
|
16
17
|
* 글로벌 User 커맨드가 없으면 설치한다.
|
|
@@ -29,9 +30,10 @@ function registerCreate(program) {
|
|
|
29
30
|
.option('--slug <slug>', 'URL용 식별자 (영문 소문자, 숫자, 하이픈)')
|
|
30
31
|
.option('--tags <tags>', '태그 (쉼표 구분)')
|
|
31
32
|
.option('--visibility <visibility>', '공개 범위 (public, private, internal)')
|
|
33
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
32
34
|
.action(async (name, opts) => {
|
|
33
35
|
const json = program.opts().json ?? false;
|
|
34
|
-
const projectPath =
|
|
36
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
35
37
|
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
36
38
|
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
37
39
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
package/dist/commands/init.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.registerInit = registerInit;
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
12
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
12
13
|
const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
13
14
|
const config_js_1 = require("../lib/config.js");
|
|
14
15
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -122,11 +123,12 @@ function registerInit(program) {
|
|
|
122
123
|
.option('--tools <tools>', '설치할 에이전트 CLI 지정 (쉼표 구분)')
|
|
123
124
|
.option('--all', '감지된 모든 에이전트 CLI에 설치')
|
|
124
125
|
.option('--auto', '대화형 프롬프트 없이 자동으로 모든 감지된 CLI에 설치')
|
|
126
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
125
127
|
.action(async (opts) => {
|
|
126
128
|
const json = program.opts().json ?? false;
|
|
127
129
|
// auto mode: --auto flag, --all flag, or stdin is not a TTY (but NOT --json alone)
|
|
128
130
|
const autoMode = opts.auto === true || opts.all === true || !process.stdin.isTTY;
|
|
129
|
-
const projectPath =
|
|
131
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
130
132
|
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
131
133
|
const detectedIds = new Set(detected.map((t) => t.value));
|
|
132
134
|
const isBuilder = isAgentProject(projectPath);
|
package/dist/commands/install.js
CHANGED
|
@@ -10,17 +10,18 @@ const api_js_1 = require("../lib/api.js");
|
|
|
10
10
|
const storage_js_1 = require("../lib/storage.js");
|
|
11
11
|
const config_js_1 = require("../lib/config.js");
|
|
12
12
|
const slug_js_1 = require("../lib/slug.js");
|
|
13
|
-
const contact_format_js_1 = require("../lib/contact-format.js");
|
|
14
13
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
15
14
|
const init_js_1 = require("./init.js");
|
|
15
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
16
16
|
function registerInstall(program) {
|
|
17
17
|
program
|
|
18
18
|
.command('install <slug>')
|
|
19
19
|
.description('에이전트 패키지를 .relay/agents/에 다운로드합니다')
|
|
20
20
|
.option('--join-code <code>', '초대 코드 (Organization 에이전트 설치 시 자동 가입)')
|
|
21
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
21
22
|
.action(async (slugInput, _opts) => {
|
|
22
23
|
const json = program.opts().json ?? false;
|
|
23
|
-
const projectPath =
|
|
24
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(_opts.project);
|
|
24
25
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
25
26
|
// Auto-init: 글로벌 커맨드가 없으면 자동 설치
|
|
26
27
|
if (!(0, init_js_1.hasGlobalUserCommands)()) {
|
|
@@ -228,7 +229,6 @@ function registerInstall(program) {
|
|
|
228
229
|
}
|
|
229
230
|
else {
|
|
230
231
|
const authorUsername = resolvedAgent.author?.username;
|
|
231
|
-
const authorDisplayName = resolvedAgent.author?.display_name ?? authorUsername ?? '';
|
|
232
232
|
const authorSuffix = authorUsername ? ` \x1b[90mby @${authorUsername}\x1b[0m` : '';
|
|
233
233
|
console.log(`\n\x1b[32m✓ ${resolvedAgent.name} 다운로드 완료\x1b[0m v${resolvedAgent.version}${authorSuffix}`);
|
|
234
234
|
console.log(` 위치: \x1b[36m${agentDir}\x1b[0m`);
|
|
@@ -239,23 +239,6 @@ function registerInstall(program) {
|
|
|
239
239
|
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
// Builder business card
|
|
243
|
-
const contactParts = (0, contact_format_js_1.formatContactParts)(resolvedAgent.author?.contact_links);
|
|
244
|
-
const hasCard = resolvedAgent.welcome || contactParts.length > 0 || authorUsername;
|
|
245
|
-
if (hasCard) {
|
|
246
|
-
console.log(`\n \x1b[90m┌─ ${authorDisplayName || authorUsername || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || authorUsername || '빌더').length))}┐\x1b[0m`);
|
|
247
|
-
if (resolvedAgent.welcome) {
|
|
248
|
-
const truncated = resolvedAgent.welcome.length > 45 ? resolvedAgent.welcome.slice(0, 45) + '...' : resolvedAgent.welcome;
|
|
249
|
-
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
250
|
-
}
|
|
251
|
-
if (contactParts.length > 0) {
|
|
252
|
-
console.log(` \x1b[90m│\x1b[0m 📇 ${contactParts.join(' ')}`);
|
|
253
|
-
}
|
|
254
|
-
if (authorUsername) {
|
|
255
|
-
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${authorUsername}`);
|
|
256
|
-
}
|
|
257
|
-
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
258
|
-
}
|
|
259
242
|
// Usage hint (type-aware)
|
|
260
243
|
const agentType = resolvedAgent.type;
|
|
261
244
|
if (agentType === 'passive') {
|
package/dist/commands/login.js
CHANGED
|
@@ -119,6 +119,46 @@ async function loginWithBrowser(json) {
|
|
|
119
119
|
}
|
|
120
120
|
return waitForToken(port);
|
|
121
121
|
}
|
|
122
|
+
async function loginWithDevice(json) {
|
|
123
|
+
const res = await fetch(`${config_js_1.API_URL}/api/auth/device/request`, { method: 'POST' });
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
throw new Error('Device code 발급에 실패했습니다');
|
|
126
|
+
}
|
|
127
|
+
const { device_code, user_code, verification_url, expires_in } = await res.json();
|
|
128
|
+
if (json) {
|
|
129
|
+
console.error(JSON.stringify({ status: 'waiting', verification_url, user_code, expires_in }));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.error(`\n아래 URL에서 코드를 입력하세요:\n`);
|
|
133
|
+
console.error(` ${verification_url}`);
|
|
134
|
+
console.error(`\n 코드: \x1b[1m${user_code}\x1b[0m\n`);
|
|
135
|
+
}
|
|
136
|
+
openBrowser(`${verification_url}?user_code=${user_code}`);
|
|
137
|
+
const deadline = Date.now() + expires_in * 1000;
|
|
138
|
+
while (Date.now() < deadline) {
|
|
139
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
140
|
+
const pollRes = await fetch(`${config_js_1.API_URL}/api/auth/device/poll`, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: { 'Content-Type': 'application/json' },
|
|
143
|
+
body: JSON.stringify({ device_code }),
|
|
144
|
+
});
|
|
145
|
+
if (!pollRes.ok)
|
|
146
|
+
continue;
|
|
147
|
+
const data = await pollRes.json();
|
|
148
|
+
if (data.status === 'approved' && data.token) {
|
|
149
|
+
return {
|
|
150
|
+
token: data.token,
|
|
151
|
+
refresh_token: data.refresh_token,
|
|
152
|
+
expires_at: data.expires_at ? Number(data.expires_at) : undefined,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (data.status === 'expired') {
|
|
156
|
+
throw new Error('코드가 만료되었습니다. 다시 시도하세요.');
|
|
157
|
+
}
|
|
158
|
+
// pending — continue polling
|
|
159
|
+
}
|
|
160
|
+
throw new Error('로그인 시간이 초과되었습니다 (5분)');
|
|
161
|
+
}
|
|
122
162
|
/**
|
|
123
163
|
* 대화형 로그인 플로우 실행 (auto-login에서 호출).
|
|
124
164
|
* 브라우저에서 로그인 페이지를 열고 토큰을 받아 저장.
|
|
@@ -139,6 +179,7 @@ function registerLogin(program) {
|
|
|
139
179
|
.command('login')
|
|
140
180
|
.description('RelayAX 계정에 로그인합니다')
|
|
141
181
|
.option('--token <token>', '직접 토큰 입력 (브라우저 없이)')
|
|
182
|
+
.option('--device', 'Device code 방식으로 로그인 (샌드박스/원격 환경용)')
|
|
142
183
|
.action(async (opts) => {
|
|
143
184
|
const json = program.opts().json ?? false;
|
|
144
185
|
(0, config_js_1.ensureGlobalRelayDir)();
|
|
@@ -146,8 +187,9 @@ function registerLogin(program) {
|
|
|
146
187
|
let refreshToken;
|
|
147
188
|
let expiresAt;
|
|
148
189
|
if (!accessToken) {
|
|
190
|
+
const loginFn = opts.device ? loginWithDevice : loginWithBrowser;
|
|
149
191
|
try {
|
|
150
|
-
const loginResult = await
|
|
192
|
+
const loginResult = await loginFn(json);
|
|
151
193
|
accessToken = loginResult.token;
|
|
152
194
|
refreshToken = loginResult.refresh_token;
|
|
153
195
|
expiresAt = loginResult.expires_at;
|
|
@@ -155,10 +197,13 @@ function registerLogin(program) {
|
|
|
155
197
|
catch (err) {
|
|
156
198
|
const msg = err instanceof Error ? err.message : '로그인 실패';
|
|
157
199
|
if (json) {
|
|
158
|
-
console.error(JSON.stringify({ error: 'LOGIN_FAILED', message: msg, fix: '
|
|
200
|
+
console.error(JSON.stringify({ error: 'LOGIN_FAILED', message: msg, fix: opts.device ? '다시 시도하세요.' : 'relay login --device를 시도하세요.' }));
|
|
159
201
|
}
|
|
160
202
|
else {
|
|
161
203
|
console.error(`\x1b[31m오류: ${msg}\x1b[0m`);
|
|
204
|
+
if (!opts.device) {
|
|
205
|
+
console.error(`\n\x1b[33m팁: 브라우저 콜백이 안 되는 환경이라면 relay login --device를 시도하세요.\x1b[0m`);
|
|
206
|
+
}
|
|
162
207
|
}
|
|
163
208
|
process.exit(1);
|
|
164
209
|
}
|
package/dist/commands/package.js
CHANGED
|
@@ -12,6 +12,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
12
12
|
const crypto_1 = __importDefault(require("crypto"));
|
|
13
13
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
14
14
|
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
15
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
15
16
|
const SYNC_DIRS = ['skills', 'commands', 'agents', 'rules'];
|
|
16
17
|
// ─── Helpers ───
|
|
17
18
|
function fileHash(filePath) {
|
|
@@ -261,16 +262,19 @@ function registerPackage(program) {
|
|
|
261
262
|
.option('--sync', '변경사항을 .relay/에 즉시 반영', false)
|
|
262
263
|
.option('--init', '최초 패키징: 소스 감지 → .relay/ 초기화', false)
|
|
263
264
|
.option('--migrate', '기존 source 필드를 contents로 마이그레이션', false)
|
|
265
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
266
|
+
.option('--home <dir>', '홈 디렉토리 경로 (기본: os.homedir(), 환경변수: RELAY_HOME)')
|
|
264
267
|
.action(async (opts) => {
|
|
265
268
|
const json = program.opts().json ?? false;
|
|
266
|
-
const projectPath =
|
|
269
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
270
|
+
const homeDir = (0, paths_js_1.resolveHome)(opts.home);
|
|
267
271
|
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
268
272
|
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
269
273
|
// ─── 최초 패키징 (--init) ───
|
|
270
274
|
if (opts.init || !fs_1.default.existsSync(relayYamlPath)) {
|
|
271
275
|
// 로컬 + 글로벌 소스를 모두 스캔하여 개별 항목 목록 생성
|
|
272
276
|
const localTools = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
273
|
-
const globalTools = (0, ai_tools_js_1.detectGlobalCLIs)();
|
|
277
|
+
const globalTools = (0, ai_tools_js_1.detectGlobalCLIs)(homeDir);
|
|
274
278
|
const sources = [];
|
|
275
279
|
for (const tool of localTools) {
|
|
276
280
|
const items = (0, ai_tools_js_1.scanLocalItems)(projectPath, tool);
|
|
@@ -284,7 +288,7 @@ function registerPackage(program) {
|
|
|
284
288
|
}
|
|
285
289
|
}
|
|
286
290
|
for (const tool of globalTools) {
|
|
287
|
-
const items = (0, ai_tools_js_1.scanGlobalItems)(tool);
|
|
291
|
+
const items = (0, ai_tools_js_1.scanGlobalItems)(tool, homeDir);
|
|
288
292
|
if (items.length > 0) {
|
|
289
293
|
sources.push({
|
|
290
294
|
path: `~/${tool.skillsDir}`,
|
|
@@ -295,7 +299,7 @@ function registerPackage(program) {
|
|
|
295
299
|
}
|
|
296
300
|
}
|
|
297
301
|
// ~/.relay/agents/ 에 기존 에이전트 패키지가 있는지 스캔
|
|
298
|
-
const globalAgentsDir = path_1.default.join(os_1.default.homedir(), '.relay', 'agents');
|
|
302
|
+
const globalAgentsDir = path_1.default.join(homeDir ?? os_1.default.homedir(), '.relay', 'agents');
|
|
299
303
|
const existingAgents = [];
|
|
300
304
|
if (fs_1.default.existsSync(globalAgentsDir)) {
|
|
301
305
|
for (const entry of fs_1.default.readdirSync(globalAgentsDir, { withFileTypes: true })) {
|
package/dist/commands/publish.js
CHANGED
|
@@ -10,9 +10,9 @@ const os_1 = __importDefault(require("os"));
|
|
|
10
10
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
11
|
const tar_1 = require("tar");
|
|
12
12
|
const config_js_1 = require("../lib/config.js");
|
|
13
|
-
const contact_format_js_1 = require("../lib/contact-format.js");
|
|
14
13
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
15
14
|
const version_check_js_1 = require("../lib/version-check.js");
|
|
15
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
17
17
|
const cliPkg = require('../../package.json');
|
|
18
18
|
const VALID_DIRS = ['skills', 'agents', 'rules', 'commands', 'bin'];
|
|
@@ -285,9 +285,10 @@ function registerPublish(program) {
|
|
|
285
285
|
.option('--token <token>', '인증 토큰')
|
|
286
286
|
.option('--space <slug>', '배포할 Space 지정')
|
|
287
287
|
.option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
|
|
288
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
288
289
|
.action(async (opts) => {
|
|
289
290
|
const json = program.opts().json ?? false;
|
|
290
|
-
const agentDir =
|
|
291
|
+
const agentDir = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
291
292
|
const relayDir = path_1.default.join(agentDir, '.relay');
|
|
292
293
|
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
293
294
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
@@ -355,7 +356,6 @@ function registerPublish(program) {
|
|
|
355
356
|
{ name: '비공개 — Org 멤버만', value: 'internal' },
|
|
356
357
|
],
|
|
357
358
|
});
|
|
358
|
-
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile\x1b[0m');
|
|
359
359
|
if (visibility === 'private') {
|
|
360
360
|
console.error('\x1b[2m💡 링크 공유 에이전트는 웹 대시보드에서 접근 링크와 구매 안내를 설정하세요: www.relayax.com/dashboard\x1b[0m');
|
|
361
361
|
}
|
|
@@ -601,10 +601,6 @@ function registerPublish(program) {
|
|
|
601
601
|
console.error(` → relay.yaml에 visibility: ${config.visibility} 저장됨 (${visLabelMap[config.visibility]})\n`);
|
|
602
602
|
}
|
|
603
603
|
}
|
|
604
|
-
// Profile hint
|
|
605
|
-
if (isTTY) {
|
|
606
|
-
console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile');
|
|
607
|
-
}
|
|
608
604
|
const detectedCommands = detectCommands(relayDir);
|
|
609
605
|
const components = {
|
|
610
606
|
agents: countDir(relayDir, 'agents'),
|
|
@@ -687,27 +683,6 @@ function registerPublish(program) {
|
|
|
687
683
|
console.log(`\n\x1b[32m✓ ${config.name} 배포 완료\x1b[0m v${result.version}`);
|
|
688
684
|
console.log(` 슬러그: \x1b[36m${result.slug}\x1b[0m`);
|
|
689
685
|
console.log(` URL: \x1b[36m${result.url}\x1b[0m`);
|
|
690
|
-
// Show business card preview
|
|
691
|
-
const profile = result.profile;
|
|
692
|
-
if (profile) {
|
|
693
|
-
const contactParts = (0, contact_format_js_1.formatContactParts)(profile.contact_links);
|
|
694
|
-
const welcome = profile.default_welcome ?? '';
|
|
695
|
-
console.log(`\n \x1b[90m┌─ 설치자에게 보이는 명함 ${'─'.repeat(24)}┐\x1b[0m`);
|
|
696
|
-
if (welcome) {
|
|
697
|
-
console.log(` \x1b[90m│\x1b[0m 💬 "${welcome.length > 45 ? welcome.slice(0, 45) + '...' : welcome}"`);
|
|
698
|
-
}
|
|
699
|
-
if (contactParts.length > 0) {
|
|
700
|
-
console.log(` \x1b[90m│\x1b[0m 📇 ${contactParts.join(' ')}`);
|
|
701
|
-
}
|
|
702
|
-
if (profile.username) {
|
|
703
|
-
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${profile.username}`);
|
|
704
|
-
}
|
|
705
|
-
if (!welcome && contactParts.length === 0) {
|
|
706
|
-
console.log(` \x1b[90m│\x1b[0m \x1b[2m명함이 비어있습니다\x1b[0m`);
|
|
707
|
-
}
|
|
708
|
-
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
709
|
-
console.log(`\n \x1b[90m명함 수정: \x1b[36mwww.relayax.com/dashboard/profile\x1b[0m`);
|
|
710
|
-
}
|
|
711
686
|
// Show shareable onboarding guide as a plain copyable block
|
|
712
687
|
if (isTTY) {
|
|
713
688
|
const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
|
package/dist/commands/status.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.registerStatus = registerStatus;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
10
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
10
11
|
const config_js_1 = require("../lib/config.js");
|
|
11
12
|
const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
12
13
|
async function resolveUsername(token) {
|
|
@@ -27,9 +28,10 @@ function registerStatus(program) {
|
|
|
27
28
|
program
|
|
28
29
|
.command('status')
|
|
29
30
|
.description('현재 relay 환경 상태를 표시합니다')
|
|
30
|
-
.
|
|
31
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
32
|
+
.action(async (opts) => {
|
|
31
33
|
const json = program.opts().json ?? false;
|
|
32
|
-
const projectPath =
|
|
34
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
33
35
|
// 1. 로그인 상태
|
|
34
36
|
const token = await (0, config_js_1.getValidToken)();
|
|
35
37
|
let username;
|
|
@@ -10,6 +10,7 @@ const config_js_1 = require("../lib/config.js");
|
|
|
10
10
|
const installer_js_1 = require("../lib/installer.js");
|
|
11
11
|
const slug_js_1 = require("../lib/slug.js");
|
|
12
12
|
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
13
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
13
14
|
/**
|
|
14
15
|
* deployed_files에서 에이전트 설정 디렉토리(skillsDir) 기반 boundary를 추론한다.
|
|
15
16
|
* 예: deployed_files에 '~/.cursor/commands/relay/x.md'가 있으면 boundary는 basePath/.cursor
|
|
@@ -31,7 +32,8 @@ function registerUninstall(program) {
|
|
|
31
32
|
program
|
|
32
33
|
.command('uninstall <slug>')
|
|
33
34
|
.description('에이전트 제거')
|
|
34
|
-
.
|
|
35
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
36
|
+
.action((slugInput, _opts) => {
|
|
35
37
|
const json = program.opts().json ?? false;
|
|
36
38
|
const localInstalled = (0, config_js_1.loadInstalled)();
|
|
37
39
|
const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
|
|
@@ -70,7 +72,7 @@ function registerUninstall(program) {
|
|
|
70
72
|
const deployedRemoved = (0, installer_js_1.uninstallAgent)(localEntry.deployed_files);
|
|
71
73
|
totalRemoved += deployedRemoved.length;
|
|
72
74
|
// Clean empty parent directories
|
|
73
|
-
const boundary = inferBoundary(localEntry.deployed_files,
|
|
75
|
+
const boundary = inferBoundary(localEntry.deployed_files, (0, paths_js_1.resolveProjectPath)(_opts.project));
|
|
74
76
|
for (const f of deployedRemoved) {
|
|
75
77
|
(0, installer_js_1.cleanEmptyParents)(f, boundary);
|
|
76
78
|
}
|
package/dist/commands/update.js
CHANGED
|
@@ -6,7 +6,6 @@ const storage_js_1 = require("../lib/storage.js");
|
|
|
6
6
|
const installer_js_1 = require("../lib/installer.js");
|
|
7
7
|
const config_js_1 = require("../lib/config.js");
|
|
8
8
|
const slug_js_1 = require("../lib/slug.js");
|
|
9
|
-
const contact_format_js_1 = require("../lib/contact-format.js");
|
|
10
9
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
11
10
|
function registerUpdate(program) {
|
|
12
11
|
program
|
|
@@ -95,11 +94,6 @@ function registerUpdate(program) {
|
|
|
95
94
|
console.log(`\n\x1b[32m✓ ${agent.name} ${fromLabel}v${latestVersion} 업데이트 완료\x1b[0m`);
|
|
96
95
|
console.log(` 설치 위치: \x1b[36m${installPath}\x1b[0m`);
|
|
97
96
|
console.log(` 파일 수: ${files.length}개`);
|
|
98
|
-
// Builder business card
|
|
99
|
-
const authorUsername = agent.author?.username;
|
|
100
|
-
const authorDisplayName = agent.author?.display_name ?? authorUsername ?? '';
|
|
101
|
-
const contactParts = (0, contact_format_js_1.formatContactParts)(agent.author?.contact_links);
|
|
102
|
-
const hasCard = agent.welcome || contactParts.length > 0 || authorUsername;
|
|
103
97
|
// Show changelog for this version
|
|
104
98
|
try {
|
|
105
99
|
const versions = await (0, api_js_1.fetchAgentVersions)(slug);
|
|
@@ -115,20 +109,6 @@ function registerUpdate(program) {
|
|
|
115
109
|
catch {
|
|
116
110
|
// Non-critical: skip changelog display
|
|
117
111
|
}
|
|
118
|
-
if (hasCard) {
|
|
119
|
-
console.log(`\n \x1b[90m┌─ ${authorDisplayName || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || '빌더').length))}┐\x1b[0m`);
|
|
120
|
-
if (agent.welcome) {
|
|
121
|
-
const truncated = agent.welcome.length > 45 ? agent.welcome.slice(0, 45) + '...' : agent.welcome;
|
|
122
|
-
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
123
|
-
}
|
|
124
|
-
if (contactParts.length > 0) {
|
|
125
|
-
console.log(` \x1b[90m│\x1b[0m 📇 ${contactParts.join(' ')}`);
|
|
126
|
-
}
|
|
127
|
-
if (authorUsername) {
|
|
128
|
-
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${authorUsername}`);
|
|
129
|
-
}
|
|
130
|
-
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
131
|
-
}
|
|
132
112
|
}
|
|
133
113
|
}
|
|
134
114
|
catch (err) {
|
package/dist/lib/ai-tools.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function detectAgentCLIs(projectPath: string): AITool[];
|
|
|
16
16
|
* 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
|
|
17
17
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
18
18
|
*/
|
|
19
|
-
export declare function detectGlobalCLIs(): AITool[];
|
|
19
|
+
export declare function detectGlobalCLIs(home?: string): AITool[];
|
|
20
20
|
export type ContentType = 'skill' | 'agent' | 'command' | 'rule';
|
|
21
21
|
export interface ContentItem {
|
|
22
22
|
name: string;
|
|
@@ -31,4 +31,4 @@ export declare function scanLocalItems(projectPath: string, tool: AITool): Conte
|
|
|
31
31
|
/**
|
|
32
32
|
* 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
33
33
|
*/
|
|
34
|
-
export declare function scanGlobalItems(tool: AITool): ContentItem[];
|
|
34
|
+
export declare function scanGlobalItems(tool: AITool, home?: string): ContentItem[];
|
package/dist/lib/ai-tools.js
CHANGED
|
@@ -51,9 +51,9 @@ function detectAgentCLIs(projectPath) {
|
|
|
51
51
|
* 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
|
|
52
52
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
53
53
|
*/
|
|
54
|
-
function detectGlobalCLIs() {
|
|
55
|
-
const
|
|
56
|
-
return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(
|
|
54
|
+
function detectGlobalCLIs(home) {
|
|
55
|
+
const homeDir = home ?? os_1.default.homedir();
|
|
56
|
+
return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(homeDir, tool.skillsDir)));
|
|
57
57
|
}
|
|
58
58
|
const CONTENT_DIRS = [
|
|
59
59
|
{ dir: 'skills', type: 'skill' },
|
|
@@ -96,7 +96,7 @@ function scanLocalItems(projectPath, tool) {
|
|
|
96
96
|
/**
|
|
97
97
|
* 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
98
98
|
*/
|
|
99
|
-
function scanGlobalItems(tool) {
|
|
100
|
-
const basePath = path_1.default.join(os_1.default.homedir(), tool.skillsDir);
|
|
99
|
+
function scanGlobalItems(tool, home) {
|
|
100
|
+
const basePath = path_1.default.join(home ?? os_1.default.homedir(), tool.skillsDir);
|
|
101
101
|
return scanItemsIn(basePath);
|
|
102
102
|
}
|
|
@@ -60,7 +60,7 @@ function formatCommandFile(content) {
|
|
|
60
60
|
return `---\ndescription: ${content.description}\n---\n\n${content.body}\n`;
|
|
61
61
|
}
|
|
62
62
|
// ─── 프롬프트 조각은 cli/src/prompts/*.md에서 관리 (SSOT) ───
|
|
63
|
-
// REQUIREMENTS_CHECK, ERROR_HANDLING_GUIDE
|
|
63
|
+
// REQUIREMENTS_CHECK, ERROR_HANDLING_GUIDE → import from '../prompts/index.js'
|
|
64
64
|
// ─── User Commands (글로벌 설치) ───
|
|
65
65
|
exports.USER_COMMANDS = [
|
|
66
66
|
{
|
package/dist/lib/guide.js
CHANGED
|
@@ -79,8 +79,11 @@ ${commands.map((cmd) => `- \`/${cmd.name}\`: ${cmd.description}`).join('\n')}
|
|
|
79
79
|
|
|
80
80
|
> ${config.description}
|
|
81
81
|
|
|
82
|
-
이 가이드는
|
|
83
|
-
|
|
82
|
+
이 가이드는 Relay 에이전트를 설치하는 절차입니다.
|
|
83
|
+
각 Step의 명령어를 순서대로 실행하세요. 요약하지 말고 바로 Step 1부터 시작하세요.
|
|
84
|
+
|
|
85
|
+
터미널(bash)을 실행할 수 없는 환경이라면, 아래 도구에서 이 URL을 열고 실행하세요:
|
|
86
|
+
Claude Code · Claude Cowork · Codex · Cursor · Antigravity
|
|
84
87
|
|
|
85
88
|
${setupSection}
|
|
86
89
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 프로젝트 루트 경로를 결정한다.
|
|
3
|
+
* 우선순위: --project 옵션 > RELAY_PROJECT_PATH 환경변수 > process.cwd()
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveProjectPath(optProject?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* 홈 디렉토리 경로를 결정한다.
|
|
8
|
+
* 우선순위: --home 옵션 > RELAY_HOME 환경변수 > os.homedir()
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveHome(optHome?: string): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveProjectPath = resolveProjectPath;
|
|
7
|
+
exports.resolveHome = resolveHome;
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
/**
|
|
10
|
+
* 프로젝트 루트 경로를 결정한다.
|
|
11
|
+
* 우선순위: --project 옵션 > RELAY_PROJECT_PATH 환경변수 > process.cwd()
|
|
12
|
+
*/
|
|
13
|
+
function resolveProjectPath(optProject) {
|
|
14
|
+
return optProject ?? process.env.RELAY_PROJECT_PATH ?? process.cwd();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 홈 디렉토리 경로를 결정한다.
|
|
18
|
+
* 우선순위: --home 옵션 > RELAY_HOME 환경변수 > os.homedir()
|
|
19
|
+
*/
|
|
20
|
+
function resolveHome(optHome) {
|
|
21
|
+
return optHome ?? process.env.RELAY_HOME ?? os_1.default.homedir();
|
|
22
|
+
}
|
|
@@ -17,3 +17,10 @@ mkdir -p ~/.npm-global && npm config set prefix '~/.npm-global'
|
|
|
17
17
|
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc && source ~/.zshrc
|
|
18
18
|
npm install -g relayax-cli
|
|
19
19
|
```
|
|
20
|
+
|
|
21
|
+
이미 설치되어 있다면 최신 버전으로 업데이트하고 init을 재실행합니다:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm update -g relayax-cli
|
|
25
|
+
relay init
|
|
26
|
+
```
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
## Step 2. 로그인
|
|
2
2
|
|
|
3
|
+
먼저 `relay whoami`로 로그인 상태를 확인합니다. 이미 로그인되어 있으면 이 단계를 건너뛰세요.
|
|
4
|
+
|
|
5
|
+
로그인이 필요하면 아래 두 방법 중 하나를 사용하세요.
|
|
6
|
+
|
|
7
|
+
### 방법 A: 브라우저 콜백 (로컬 터미널)
|
|
8
|
+
|
|
9
|
+
로컬 머신에서 직접 터미널을 사용하는 경우 이 방법을 사용합니다.
|
|
10
|
+
`relay login`은 로컬 콜백 서버를 띄우고 브라우저 로그인 완료를 기다립니다.
|
|
11
|
+
반드시 포그라운드에서 실행하고, timeout을 300초(5분) 이상으로 설정하세요.
|
|
12
|
+
|
|
3
13
|
```bash
|
|
4
14
|
relay login
|
|
5
15
|
```
|
|
6
16
|
|
|
7
|
-
브라우저가 열리면 GitHub 또는 카카오 계정으로
|
|
17
|
+
- 브라우저가 자동으로 열리면 GitHub 또는 카카오 계정으로 로그인합니다.
|
|
18
|
+
- 브라우저가 열리지 않으면 CLI 출력에서 로그인 URL을 찾아 별도 명령어로 엽니다: `open <URL>` (macOS) / `xdg-open <URL>` (Linux)
|
|
19
|
+
|
|
20
|
+
"✓ 로그인 완료"가 출력되면 다음 단계로 진행합니다.
|
|
21
|
+
|
|
22
|
+
### 방법 B: Device Code (샌드박스/원격 환경)
|
|
23
|
+
|
|
24
|
+
Cowork, 원격 서버 등 브라우저 콜백이 localhost에 도달할 수 없는 환경에서는 이 방법을 사용합니다.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
relay login --device
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- 화면에 URL과 8자리 코드가 표시됩니다.
|
|
31
|
+
- 브라우저가 자동으로 열립니다. 열리지 않으면 표시된 URL을 별도 명령어로 엽니다: `open <URL>` (macOS) / `xdg-open <URL>` (Linux)
|
|
32
|
+
- 브라우저에서 코드를 입력하고 로그인을 승인합니다.
|
|
33
|
+
- CLI가 자동으로 승인을 감지하고 "✓ 로그인 완료"를 출력합니다.
|
|
34
|
+
|
|
35
|
+
`relay whoami`로 로그인 성공을 확인한 후 다음 단계로 진행합니다.
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare const REQUIREMENTS_CHECK: string;
|
|
2
2
|
export declare const ERROR_HANDLING_GUIDE: string;
|
|
3
|
-
export declare const BUSINESS_CARD_FORMAT: string;
|
|
4
3
|
export declare const SETUP_CLI: string;
|
|
5
4
|
export declare const SETUP_LOGIN: string;
|
|
6
5
|
export declare const INSTALL_PROMPT: string;
|
package/dist/prompts/index.js
CHANGED
|
@@ -3,7 +3,7 @@ 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.PUBLISH_PROMPT = exports.INSTALL_PROMPT = exports.SETUP_LOGIN = exports.SETUP_CLI = exports.
|
|
6
|
+
exports.PUBLISH_PROMPT = exports.INSTALL_PROMPT = exports.SETUP_LOGIN = exports.SETUP_CLI = exports.ERROR_HANDLING_GUIDE = exports.REQUIREMENTS_CHECK = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
function readPrompt(filename) {
|
|
@@ -15,13 +15,11 @@ function interpolate(template, vars) {
|
|
|
15
15
|
// ─── 공유 조각 ───
|
|
16
16
|
exports.REQUIREMENTS_CHECK = readPrompt('_requirements-check.md');
|
|
17
17
|
exports.ERROR_HANDLING_GUIDE = readPrompt('_error-handling.md');
|
|
18
|
-
exports.BUSINESS_CARD_FORMAT = readPrompt('_business-card.md');
|
|
19
18
|
exports.SETUP_CLI = readPrompt('_setup-cli.md');
|
|
20
19
|
exports.SETUP_LOGIN = readPrompt('_setup-login.md');
|
|
21
20
|
const fragments = {
|
|
22
21
|
REQUIREMENTS_CHECK: exports.REQUIREMENTS_CHECK,
|
|
23
22
|
ERROR_HANDLING_GUIDE: exports.ERROR_HANDLING_GUIDE,
|
|
24
|
-
BUSINESS_CARD_FORMAT: exports.BUSINESS_CARD_FORMAT,
|
|
25
23
|
};
|
|
26
24
|
// ─── 전체 프롬프트 (조각 합성 완료) ───
|
|
27
25
|
exports.INSTALL_PROMPT = interpolate(readPrompt('install.md'), fragments);
|
package/dist/prompts/install.md
CHANGED
|
@@ -132,10 +132,8 @@ relay deploy-record <slug> --scope <global|local> --files <배치된_파일1> <
|
|
|
132
132
|
|
|
133
133
|
#### 3-1. 완료 안내
|
|
134
134
|
- 배치된 파일과 활성화된 커맨드 목록을 보여줍니다.
|
|
135
|
-
{{BUSINESS_CARD_FORMAT}}
|
|
136
|
-
|
|
137
135
|
#### 3-2. 팔로우 제안 (필수 — 이 단계를 절대 건너뛰지 마세요)
|
|
138
|
-
|
|
136
|
+
빌더의 username이 JSON 결과에 있으면 **반드시** 사용자 질문 도구를 호출하세요.
|
|
139
137
|
|
|
140
138
|
**사용자 질문 도구 호출:**
|
|
141
139
|
- question: `@{username}을 팔로우할까요? 새 버전 알림을 받을 수 있습니다.`
|
|
@@ -178,7 +176,6 @@ https://relayax.com/api/registry/{owner}/{slug}/guide.md
|
|
|
178
176
|
→ 사용자 질문 도구: "어디에 설치할까요?" → ["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만)"]
|
|
179
177
|
→ "글로벌" 선택
|
|
180
178
|
→ 설치 + 배치 + deploy-record
|
|
181
|
-
→ 명함 표시
|
|
182
179
|
→ 사용자 질문 도구: "@alice을 팔로우할까요?" → ["팔로우", "건너뛰기"]
|
|
183
180
|
→ "✓ 설치 완료! /write-doc를 사용해볼까요?"
|
|
184
181
|
|
|
@@ -186,6 +183,5 @@ https://relayax.com/api/registry/{owner}/{slug}/guide.md
|
|
|
186
183
|
→ relay install @alice/doc-writer --json 실행 (Step 1 건너뜀)
|
|
187
184
|
→ 사용자 질문 도구: "어디에 설치할까요?" → ["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만)"]
|
|
188
185
|
→ 설치 + 배치 + deploy-record
|
|
189
|
-
→ 명함 표시
|
|
190
186
|
→ 사용자 질문 도구: "@alice을 팔로우할까요?" → ["팔로우", "건너뛰기"]
|
|
191
187
|
→ "✓ 설치 완료! /write-doc를 사용해볼까요?"
|
package/dist/prompts/publish.md
CHANGED
|
@@ -429,7 +429,6 @@ https://relayax.com/api/registry/{owner}/{slug}/guide.md
|
|
|
429
429
|
- `{owner}`과 `{slug}`는 배포된 에이전트의 실제 슬러그에서 추출합니다 (`@owner/slug` → `owner`, `slug`).
|
|
430
430
|
- "이 블록을 동료에게 공유하면 AI 에이전트가 환경 체크부터 설치까지 자동으로 해줍니다"라고 안내합니다.
|
|
431
431
|
- CLI가 이미 설치된 사용자를 위한 짧은 버전도 함께 표시: `/relay:relay-install <slug>`
|
|
432
|
-
{{BUSINESS_CARD_FORMAT}}
|
|
433
432
|
|
|
434
433
|
## 예시
|
|
435
434
|
|