relayax-cli 0.1.91 → 0.1.93
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.d.ts +2 -0
- package/dist/commands/create.js +138 -0
- package/dist/commands/init.js +144 -99
- package/dist/commands/install.js +7 -8
- package/dist/commands/invite.d.ts +2 -0
- package/dist/commands/invite.js +135 -0
- package/dist/commands/publish.d.ts +0 -7
- package/dist/commands/publish.js +7 -54
- package/dist/commands/search.js +1 -1
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +116 -0
- package/dist/commands/update.js +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/api.js +2 -3
- package/dist/lib/command-adapter.d.ts +16 -4
- package/dist/lib/command-adapter.js +108 -10
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
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.registerCreate = registerCreate;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
11
|
+
const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
12
|
+
const DEFAULT_DIRS = ['skills', 'commands'];
|
|
13
|
+
/**
|
|
14
|
+
* 글로벌 User 커맨드가 없으면 설치한다.
|
|
15
|
+
*/
|
|
16
|
+
function ensureGlobalUserCommands() {
|
|
17
|
+
const allExist = command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
18
|
+
if (allExist)
|
|
19
|
+
return false;
|
|
20
|
+
const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
|
|
21
|
+
fs_1.default.mkdirSync(globalDir, { recursive: true });
|
|
22
|
+
for (const cmd of command_adapter_js_1.USER_COMMANDS) {
|
|
23
|
+
fs_1.default.writeFileSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id), (0, command_adapter_js_1.formatCommandFile)(cmd));
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function registerCreate(program) {
|
|
28
|
+
program
|
|
29
|
+
.command('create <name>')
|
|
30
|
+
.description('새 에이전트 팀 프로젝트를 생성합니다')
|
|
31
|
+
.action(async (name) => {
|
|
32
|
+
const json = program.opts().json ?? false;
|
|
33
|
+
const projectPath = process.cwd();
|
|
34
|
+
const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
|
|
35
|
+
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
36
|
+
// 1. relay.yaml 이미 존재하면 에러
|
|
37
|
+
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
38
|
+
if (json) {
|
|
39
|
+
console.error(JSON.stringify({ error: 'ALREADY_EXISTS', message: 'relay.yaml이 이미 존재합니다.' }));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error('relay.yaml이 이미 존재합니다. 기존 팀 프로젝트에서는 `relay init`을 사용하세요.');
|
|
43
|
+
}
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 2. 메타데이터 수집
|
|
47
|
+
const defaultSlug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
48
|
+
let description = '';
|
|
49
|
+
let tags = [];
|
|
50
|
+
let visibility = 'public';
|
|
51
|
+
if (isTTY) {
|
|
52
|
+
const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
|
|
53
|
+
console.log(`\n \x1b[33m⚡\x1b[0m \x1b[1mrelay create\x1b[0m — 새 팀 프로젝트\n`);
|
|
54
|
+
description = await promptInput({
|
|
55
|
+
message: '팀 설명:',
|
|
56
|
+
validate: (v) => v.trim().length > 0 ? true : '설명을 입력해주세요.',
|
|
57
|
+
});
|
|
58
|
+
const tagsRaw = await promptInput({
|
|
59
|
+
message: '태그 (쉼표로 구분, 선택):',
|
|
60
|
+
default: '',
|
|
61
|
+
});
|
|
62
|
+
tags = tagsRaw.split(',').map((t) => t.trim()).filter(Boolean);
|
|
63
|
+
visibility = await promptSelect({
|
|
64
|
+
message: '공개 범위:',
|
|
65
|
+
choices: [
|
|
66
|
+
{ name: '전체 공개', value: 'public' },
|
|
67
|
+
{ name: '로그인 사용자만', value: 'login-only' },
|
|
68
|
+
{ name: '초대 코드 필요', value: 'invite-only' },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// 3. relay.yaml 생성
|
|
73
|
+
const yamlData = {
|
|
74
|
+
name,
|
|
75
|
+
slug: defaultSlug,
|
|
76
|
+
description,
|
|
77
|
+
version: '1.0.0',
|
|
78
|
+
tags,
|
|
79
|
+
visibility,
|
|
80
|
+
};
|
|
81
|
+
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
82
|
+
// 4. 디렉토리 구조 생성
|
|
83
|
+
const createdDirs = [];
|
|
84
|
+
for (const dir of DEFAULT_DIRS) {
|
|
85
|
+
const dirPath = path_1.default.join(projectPath, dir);
|
|
86
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
87
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
88
|
+
createdDirs.push(dir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 5. 로컬 Builder 슬래시 커맨드 설치
|
|
92
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
93
|
+
const localResults = [];
|
|
94
|
+
for (const tool of detected) {
|
|
95
|
+
const adapter = (0, command_adapter_js_1.createAdapter)(tool);
|
|
96
|
+
const installed = [];
|
|
97
|
+
for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
|
|
98
|
+
const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
|
|
99
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
100
|
+
fs_1.default.writeFileSync(filePath, adapter.formatFile(cmd));
|
|
101
|
+
installed.push(cmd.id);
|
|
102
|
+
}
|
|
103
|
+
localResults.push({ tool: tool.name, commands: installed });
|
|
104
|
+
}
|
|
105
|
+
// 6. 글로벌 User 커맨드 (없으면 설치)
|
|
106
|
+
const globalInstalled = ensureGlobalUserCommands();
|
|
107
|
+
// 7. 출력
|
|
108
|
+
if (json) {
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
status: 'ok',
|
|
111
|
+
name,
|
|
112
|
+
slug: defaultSlug,
|
|
113
|
+
relay_yaml: 'created',
|
|
114
|
+
directories: createdDirs,
|
|
115
|
+
local_commands: localResults,
|
|
116
|
+
global_commands: globalInstalled ? 'installed' : 'already',
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(`\n\x1b[32m✓ ${name} 팀 프로젝트 생성 완료\x1b[0m\n`);
|
|
121
|
+
console.log(` relay.yaml 생성됨`);
|
|
122
|
+
if (createdDirs.length > 0) {
|
|
123
|
+
console.log(` 디렉토리 생성: ${createdDirs.join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
if (localResults.length > 0) {
|
|
126
|
+
console.log(`\n \x1b[36mBuilder 커맨드 (로컬)\x1b[0m`);
|
|
127
|
+
for (const r of localResults) {
|
|
128
|
+
console.log(` ${r.tool}: ${r.commands.map((c) => `/${c}`).join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (globalInstalled) {
|
|
132
|
+
console.log(`\n \x1b[36mUser 커맨드 (글로벌)\x1b[0m — 설치됨`);
|
|
133
|
+
}
|
|
134
|
+
console.log(`\n 다음 단계: \x1b[33m/relay-publish\x1b[0m로 마켓플레이스에 배포`);
|
|
135
|
+
console.log(' IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.\n');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -28,11 +28,13 @@ function showWelcome() {
|
|
|
28
28
|
' \x1b[33m⚡\x1b[0m \x1b[1mrelay\x1b[0m — Agent Team Marketplace',
|
|
29
29
|
'',
|
|
30
30
|
' 에이전트 CLI에 relay 커맨드를 연결합니다.',
|
|
31
|
-
' 설치 후 에이전트가 팀을 탐색하고 설치할 수 있습니다.',
|
|
32
31
|
'',
|
|
33
|
-
' \x1b[
|
|
34
|
-
'
|
|
35
|
-
'
|
|
32
|
+
' \x1b[2mUser 커맨드 (글로벌)\x1b[0m',
|
|
33
|
+
' /relay-explore 마켓플레이스 탐색',
|
|
34
|
+
' /relay-install 팀 설치',
|
|
35
|
+
' /relay-list 설치된 팀 목록',
|
|
36
|
+
' /relay-update 팀 업데이트',
|
|
37
|
+
' /relay-uninstall 팀 삭제',
|
|
36
38
|
'',
|
|
37
39
|
];
|
|
38
40
|
console.log(lines.join('\n'));
|
|
@@ -54,10 +56,44 @@ async function selectToolsInteractively(detectedIds) {
|
|
|
54
56
|
});
|
|
55
57
|
return selected;
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 글로벌 User 커맨드를 ~/.claude/commands/relay/에 설치한다.
|
|
61
|
+
*/
|
|
62
|
+
function installGlobalUserCommands() {
|
|
63
|
+
const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
|
|
64
|
+
fs_1.default.mkdirSync(globalDir, { recursive: true });
|
|
65
|
+
const commands = [];
|
|
66
|
+
for (const cmd of command_adapter_js_1.USER_COMMANDS) {
|
|
67
|
+
const filePath = (0, command_adapter_js_1.getGlobalCommandPath)(cmd.id);
|
|
68
|
+
fs_1.default.writeFileSync(filePath, (0, command_adapter_js_1.formatCommandFile)(cmd));
|
|
69
|
+
commands.push(cmd.id);
|
|
70
|
+
}
|
|
71
|
+
return { installed: true, commands };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 글로벌 User 커맨드가 이미 설치되어 있는지 확인한다.
|
|
75
|
+
*/
|
|
76
|
+
function hasGlobalUserCommands() {
|
|
77
|
+
return command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 팀 프로젝트인지 감지한다 (relay.yaml 또는 팀 디렉토리 구조).
|
|
81
|
+
*/
|
|
82
|
+
function isTeamProject(projectPath) {
|
|
83
|
+
if (fs_1.default.existsSync(path_1.default.join(projectPath, 'relay.yaml'))) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return VALID_TEAM_DIRS.some((d) => {
|
|
87
|
+
const dirPath = path_1.default.join(projectPath, d);
|
|
88
|
+
if (!fs_1.default.existsSync(dirPath))
|
|
89
|
+
return false;
|
|
90
|
+
return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
57
93
|
function registerInit(program) {
|
|
58
94
|
program
|
|
59
95
|
.command('init')
|
|
60
|
-
.description('에이전트 CLI
|
|
96
|
+
.description('에이전트 CLI에 relay 슬래시 커맨드를 설치합니다')
|
|
61
97
|
.option('--tools <tools>', '설치할 에이전트 CLI 지정 (all 또는 쉼표 구분)')
|
|
62
98
|
.option('--update', '이미 설치된 슬래시 커맨드를 최신 버전으로 업데이트')
|
|
63
99
|
.action(async (opts) => {
|
|
@@ -65,118 +101,127 @@ function registerInit(program) {
|
|
|
65
101
|
const projectPath = process.cwd();
|
|
66
102
|
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
67
103
|
const detectedIds = new Set(detected.map((t) => t.value));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
const isBuilder = isTeamProject(projectPath);
|
|
105
|
+
// ── 1. 글로벌 User 커맨드 설치 ──
|
|
106
|
+
let globalStatus = 'already';
|
|
107
|
+
if (opts.update || !hasGlobalUserCommands()) {
|
|
108
|
+
installGlobalUserCommands();
|
|
109
|
+
globalStatus = opts.update ? 'updated' : 'installed';
|
|
110
|
+
}
|
|
111
|
+
// ── 2. 로컬 Builder 커맨드 (팀 프로젝트인 경우) ──
|
|
112
|
+
const localResults = [];
|
|
113
|
+
if (isBuilder) {
|
|
114
|
+
// 도구 선택
|
|
115
|
+
let targetToolIds;
|
|
116
|
+
if (opts.update) {
|
|
117
|
+
const installed = detected.filter((tool) => {
|
|
118
|
+
const cmdDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
|
|
119
|
+
return fs_1.default.existsSync(cmdDir);
|
|
120
|
+
});
|
|
121
|
+
if (installed.length === 0 && !json) {
|
|
122
|
+
console.error('로컬에 설치된 relay Builder 커맨드가 없습니다.');
|
|
82
123
|
}
|
|
83
|
-
|
|
124
|
+
targetToolIds = installed.map((t) => t.value);
|
|
84
125
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else if (opts.tools) {
|
|
88
|
-
// --tools 옵션: 비대화형
|
|
89
|
-
targetToolIds = resolveTools(opts.tools);
|
|
90
|
-
}
|
|
91
|
-
else if (!json && process.stdin.isTTY) {
|
|
92
|
-
// 기본(human) + TTY: 대화형 UI
|
|
93
|
-
showWelcome();
|
|
94
|
-
if (detected.length > 0) {
|
|
95
|
-
console.log(` 감지된 에이전트 CLI: \x1b[36m${detected.map((t) => t.name).join(', ')}\x1b[0m\n`);
|
|
126
|
+
else if (opts.tools) {
|
|
127
|
+
targetToolIds = resolveTools(opts.tools);
|
|
96
128
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
else if (!json && process.stdin.isTTY) {
|
|
130
|
+
showWelcome();
|
|
131
|
+
if (detected.length > 0) {
|
|
132
|
+
console.log(` 감지된 에이전트 CLI: \x1b[36m${detected.map((t) => t.name).join(', ')}\x1b[0m\n`);
|
|
133
|
+
}
|
|
134
|
+
console.log(' \x1b[2mBuilder 프로젝트 감지 → 로컬 Builder 커맨드도 설치합니다.\x1b[0m\n');
|
|
135
|
+
targetToolIds = await selectToolsInteractively(detectedIds);
|
|
136
|
+
if (targetToolIds.length === 0) {
|
|
137
|
+
console.log('\n 선택된 도구가 없습니다.');
|
|
138
|
+
// 글로벌은 이미 설치됨
|
|
139
|
+
if (globalStatus === 'installed') {
|
|
140
|
+
console.log(' 글로벌 User 커맨드는 설치되었습니다.\n');
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
101
144
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
145
|
+
else {
|
|
146
|
+
if (detected.length === 0) {
|
|
147
|
+
if (json) {
|
|
148
|
+
console.error(JSON.stringify({
|
|
149
|
+
error: 'NO_AGENT_CLI',
|
|
150
|
+
message: '에이전트 CLI 디렉토리를 찾을 수 없습니다. --tools 옵션으로 지정하세요.',
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
// 글로벌은 설치했으므로 에러로 종료하지 않음
|
|
154
|
+
targetToolIds = [];
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
targetToolIds = detected.map((t) => t.value);
|
|
158
|
+
}
|
|
111
159
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
installedCommands.push(cmd.id);
|
|
160
|
+
// Builder 커맨드 설치
|
|
161
|
+
for (const toolId of targetToolIds) {
|
|
162
|
+
const tool = ai_tools_js_1.AI_TOOLS.find((t) => t.value === toolId);
|
|
163
|
+
if (!tool)
|
|
164
|
+
continue;
|
|
165
|
+
const adapter = (0, command_adapter_js_1.createAdapter)(tool);
|
|
166
|
+
const installedCommands = [];
|
|
167
|
+
for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
|
|
168
|
+
const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
|
|
169
|
+
const fileContent = adapter.formatFile(cmd);
|
|
170
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
171
|
+
fs_1.default.writeFileSync(filePath, fileContent);
|
|
172
|
+
installedCommands.push(cmd.id);
|
|
173
|
+
}
|
|
174
|
+
localResults.push({ tool: tool.name, commands: installedCommands });
|
|
128
175
|
}
|
|
129
|
-
results.push({ tool: tool.name, commands: installedCommands });
|
|
130
176
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
135
|
-
relayYamlStatus = 'exists';
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
const hasTeamDirs = VALID_TEAM_DIRS.some((d) => {
|
|
139
|
-
const dirPath = path_1.default.join(projectPath, d);
|
|
140
|
-
if (!fs_1.default.existsSync(dirPath))
|
|
141
|
-
return false;
|
|
142
|
-
return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
|
|
143
|
-
});
|
|
144
|
-
if (hasTeamDirs) {
|
|
145
|
-
const dirName = path_1.default.basename(projectPath);
|
|
146
|
-
const yaml = [
|
|
147
|
-
`name: "${dirName}"`,
|
|
148
|
-
`slug: "${dirName}"`,
|
|
149
|
-
`description: ""`,
|
|
150
|
-
`version: "1.0.0"`,
|
|
151
|
-
`tags: []`,
|
|
152
|
-
].join('\n') + '\n';
|
|
153
|
-
fs_1.default.writeFileSync(relayYamlPath, yaml);
|
|
154
|
-
relayYamlStatus = 'created';
|
|
155
|
-
}
|
|
177
|
+
else if (!json && process.stdin.isTTY) {
|
|
178
|
+
// User 모드: 글로벌만 설치, 안내 표시
|
|
179
|
+
showWelcome();
|
|
156
180
|
}
|
|
157
|
-
//
|
|
181
|
+
// ── 3. 출력 ──
|
|
158
182
|
if (json) {
|
|
159
183
|
console.log(JSON.stringify({
|
|
160
184
|
status: 'ok',
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
mode: isBuilder ? 'builder' : 'user',
|
|
186
|
+
global: {
|
|
187
|
+
status: globalStatus,
|
|
188
|
+
path: (0, command_adapter_js_1.getGlobalCommandDir)(),
|
|
189
|
+
commands: command_adapter_js_1.USER_COMMANDS.map((c) => c.id),
|
|
190
|
+
},
|
|
191
|
+
local: isBuilder ? localResults : undefined,
|
|
163
192
|
}));
|
|
164
193
|
}
|
|
165
194
|
else {
|
|
166
|
-
console.log(`\n\x1b[32m✓ relay ${opts.update ? '
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
195
|
+
console.log(`\n\x1b[32m✓ relay ${opts.update ? '업데이트' : '초기화'} 완료\x1b[0m\n`);
|
|
196
|
+
// 글로벌
|
|
197
|
+
if (globalStatus !== 'already') {
|
|
198
|
+
console.log(` \x1b[36mUser 커맨드 (글로벌)\x1b[0m — ${globalStatus === 'updated' ? '업데이트됨' : '설치됨'}`);
|
|
199
|
+
console.log(` ${(0, command_adapter_js_1.getGlobalCommandDir)()}`);
|
|
200
|
+
for (const cmd of command_adapter_js_1.USER_COMMANDS) {
|
|
201
|
+
console.log(` /${cmd.id}`);
|
|
171
202
|
}
|
|
203
|
+
console.log();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(` \x1b[36mUser 커맨드 (글로벌)\x1b[0m — 이미 설치됨`);
|
|
207
|
+
console.log();
|
|
172
208
|
}
|
|
173
|
-
|
|
174
|
-
|
|
209
|
+
// 로컬 Builder
|
|
210
|
+
if (localResults.length > 0) {
|
|
211
|
+
console.log(` \x1b[36mBuilder 커맨드 (로컬)\x1b[0m`);
|
|
212
|
+
for (const r of localResults) {
|
|
213
|
+
console.log(` ${r.tool}`);
|
|
214
|
+
for (const cmd of r.commands) {
|
|
215
|
+
console.log(` /${cmd}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log();
|
|
175
219
|
}
|
|
176
|
-
|
|
177
|
-
console.log(
|
|
220
|
+
if (!isBuilder) {
|
|
221
|
+
console.log(' 팀을 만들려면 \x1b[33mrelay create <name>\x1b[0m을 사용하세요.');
|
|
222
|
+
console.log();
|
|
178
223
|
}
|
|
179
|
-
console.log('
|
|
224
|
+
console.log(' IDE를 재시작하면 슬래시 커맨드가 활성화됩니다.');
|
|
180
225
|
}
|
|
181
226
|
});
|
|
182
227
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -9,8 +9,7 @@ function registerInstall(program) {
|
|
|
9
9
|
program
|
|
10
10
|
.command('install <slug>')
|
|
11
11
|
.description('에이전트 팀 설치 (감지된 에이전트 CLI에 설치)')
|
|
12
|
-
.option('--path <install_path>', '설치 경로 지정
|
|
13
|
-
.option('--code <code>', '초대 코드 (invite-only 팀 설치 시 필요)')
|
|
12
|
+
.option('--path <install_path>', '설치 경로 지정')
|
|
14
13
|
.action(async (slug, opts) => {
|
|
15
14
|
const json = program.opts().json ?? false;
|
|
16
15
|
const installPath = (0, config_js_1.getInstallPath)(opts.path);
|
|
@@ -23,17 +22,17 @@ function registerInstall(program) {
|
|
|
23
22
|
if (visibility === 'login-only') {
|
|
24
23
|
const token = (0, config_js_1.loadToken)();
|
|
25
24
|
if (!token) {
|
|
26
|
-
|
|
27
|
-
console.error(JSON.stringify(err));
|
|
25
|
+
console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'login-only', slug, message: '이 팀은 로그인이 필요합니다.' }));
|
|
28
26
|
process.exit(1);
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
else if (visibility === 'invite-only') {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.error(JSON.stringify(
|
|
30
|
+
const token = (0, config_js_1.loadToken)();
|
|
31
|
+
if (!token) {
|
|
32
|
+
console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'invite-only', slug, message: '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.' }));
|
|
35
33
|
process.exit(1);
|
|
36
34
|
}
|
|
35
|
+
// 실제 초대 여부는 서버(install API)에서 체크
|
|
37
36
|
}
|
|
38
37
|
// 3. Download package
|
|
39
38
|
const tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
|
|
@@ -51,7 +50,7 @@ function registerInstall(program) {
|
|
|
51
50
|
};
|
|
52
51
|
(0, config_js_1.saveInstalled)(installed);
|
|
53
52
|
// 7. Report install (non-blocking)
|
|
54
|
-
await (0, api_js_1.reportInstall)(slug
|
|
53
|
+
await (0, api_js_1.reportInstall)(slug);
|
|
55
54
|
const result = {
|
|
56
55
|
status: 'ok',
|
|
57
56
|
team: team.name,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerInvite = registerInvite;
|
|
4
|
+
const config_js_1 = require("../lib/config.js");
|
|
5
|
+
function registerInvite(program) {
|
|
6
|
+
const inviteCmd = program
|
|
7
|
+
.command('invite <slug>')
|
|
8
|
+
.description('invite-only 팀에 사용자를 초대합니다');
|
|
9
|
+
inviteCmd
|
|
10
|
+
.command('add <username>')
|
|
11
|
+
.description('사용자 초대')
|
|
12
|
+
.action(async (username) => {
|
|
13
|
+
const json = program.opts().json ?? false;
|
|
14
|
+
const slug = inviteCmd.args[0];
|
|
15
|
+
const token = (0, config_js_1.loadToken)();
|
|
16
|
+
if (!token) {
|
|
17
|
+
console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다. `relay login`을 먼저 실행하세요.' }));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Authorization': `Bearer ${token}`,
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({ username }),
|
|
28
|
+
});
|
|
29
|
+
const body = await res.json();
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
if (json) {
|
|
32
|
+
console.error(JSON.stringify(body));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.error(`오류: ${body.message ?? '초대에 실패했습니다'}`);
|
|
36
|
+
}
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (json) {
|
|
40
|
+
console.log(JSON.stringify(body));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(`✓ @${username}을(를) ${slug}에 초대했습니다.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
console.error(JSON.stringify({ error: 'INVITE_FAILED', message }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
inviteCmd
|
|
53
|
+
.command('remove <username>')
|
|
54
|
+
.description('초대 취소')
|
|
55
|
+
.action(async (username) => {
|
|
56
|
+
const json = program.opts().json ?? false;
|
|
57
|
+
const slug = inviteCmd.args[0];
|
|
58
|
+
const token = (0, config_js_1.loadToken)();
|
|
59
|
+
if (!token) {
|
|
60
|
+
console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다.' }));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
|
|
65
|
+
method: 'DELETE',
|
|
66
|
+
headers: {
|
|
67
|
+
'Authorization': `Bearer ${token}`,
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({ username }),
|
|
71
|
+
});
|
|
72
|
+
const body = await res.json();
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
if (json) {
|
|
75
|
+
console.error(JSON.stringify(body));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.error(`오류: ${body.message ?? '초대 취소에 실패했습니다'}`);
|
|
79
|
+
}
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
if (json) {
|
|
83
|
+
console.log(JSON.stringify(body));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(`✓ @${username}의 초대를 취소했습니다.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
console.error(JSON.stringify({ error: 'REVOKE_FAILED', message }));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
inviteCmd
|
|
96
|
+
.command('list')
|
|
97
|
+
.description('초대 목록 조회')
|
|
98
|
+
.action(async () => {
|
|
99
|
+
const json = program.opts().json ?? false;
|
|
100
|
+
const slug = inviteCmd.args[0];
|
|
101
|
+
const token = (0, config_js_1.loadToken)();
|
|
102
|
+
if (!token) {
|
|
103
|
+
console.error(JSON.stringify({ error: 'NO_TOKEN', message: '인증이 필요합니다.' }));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(`${config_js_1.API_URL}/api/teams/${slug}/invites`, {
|
|
108
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
109
|
+
});
|
|
110
|
+
const body = await res.json();
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
console.error(JSON.stringify(body));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const invites = body.invites ?? [];
|
|
116
|
+
if (json) {
|
|
117
|
+
console.log(JSON.stringify(body));
|
|
118
|
+
}
|
|
119
|
+
else if (invites.length === 0) {
|
|
120
|
+
console.log('초대된 사용자가 없습니다.');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`초대된 사용자 (${invites.length}명):`);
|
|
124
|
+
for (const inv of invites) {
|
|
125
|
+
console.log(` @${inv.profiles?.username ?? '(알 수 없음)'}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
131
|
+
console.error(JSON.stringify({ error: 'LIST_FAILED', message }));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -19,11 +19,4 @@ export interface Requires {
|
|
|
19
19
|
env?: RequiresEnv[];
|
|
20
20
|
teams?: string[];
|
|
21
21
|
}
|
|
22
|
-
export interface ContactInfo {
|
|
23
|
-
email?: string;
|
|
24
|
-
kakao?: string;
|
|
25
|
-
x?: string;
|
|
26
|
-
linkedin?: string;
|
|
27
|
-
website?: string;
|
|
28
|
-
}
|
|
29
22
|
export declare function registerPublish(program: Command): void;
|
package/dist/commands/publish.js
CHANGED
|
@@ -40,10 +40,7 @@ function parseRelayYaml(content) {
|
|
|
40
40
|
tags,
|
|
41
41
|
portfolio,
|
|
42
42
|
requires,
|
|
43
|
-
welcome: raw.welcome ? String(raw.welcome) : undefined,
|
|
44
|
-
contact: raw.contact,
|
|
45
43
|
visibility,
|
|
46
|
-
invite_code: raw.invite_code ? String(raw.invite_code) : undefined,
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
46
|
function detectCommands(teamDir) {
|
|
@@ -194,7 +191,7 @@ function registerPublish(program) {
|
|
|
194
191
|
process.exit(1);
|
|
195
192
|
}
|
|
196
193
|
// Interactive onboarding: create relay.yaml
|
|
197
|
-
const { input: promptInput, select: promptSelect
|
|
194
|
+
const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
|
|
198
195
|
const dirName = path_1.default.basename(teamDir);
|
|
199
196
|
const defaultSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
200
197
|
console.error('\n\x1b[36m릴레이 팀 패키지를 초기화합니다.\x1b[0m');
|
|
@@ -223,40 +220,11 @@ function registerPublish(program) {
|
|
|
223
220
|
{ name: '초대 코드 필요', value: 'invite-only' },
|
|
224
221
|
],
|
|
225
222
|
});
|
|
226
|
-
|
|
223
|
+
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: relayax.com/dashboard/edit\x1b[0m');
|
|
227
224
|
if (visibility === 'invite-only') {
|
|
228
|
-
|
|
229
|
-
message: '초대 코드:',
|
|
230
|
-
validate: (v) => v.trim().length > 0 ? true : '초대 코드를 입력해주세요.',
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
// Welcome message section
|
|
234
|
-
console.error('\n\x1b[33m┌─────────────────────────────────────────────────────────────┐\x1b[0m');
|
|
235
|
-
console.error('\x1b[33m│ welcome 메시지란? │\x1b[0m');
|
|
236
|
-
console.error('\x1b[33m│ 설치할 때마다 이 메시지와 연락처가 설치자에게 전달됩니다. │\x1b[0m');
|
|
237
|
-
console.error('\x1b[33m│ (설치 = 명함 전달) │\x1b[0m');
|
|
238
|
-
console.error('\x1b[33m└─────────────────────────────────────────────────────────────┘\x1b[0m\n');
|
|
239
|
-
const wantsWelcome = await promptConfirm({
|
|
240
|
-
message: 'welcome 메시지를 작성하시겠습니까?',
|
|
241
|
-
default: false,
|
|
242
|
-
});
|
|
243
|
-
let welcome;
|
|
244
|
-
let contactEmail;
|
|
245
|
-
let contactKakao;
|
|
246
|
-
if (wantsWelcome) {
|
|
247
|
-
welcome = await promptInput({
|
|
248
|
-
message: 'welcome 메시지:',
|
|
249
|
-
validate: (v) => v.trim().length > 0 ? true : '메시지를 입력해주세요.',
|
|
250
|
-
});
|
|
251
|
-
contactEmail = await promptInput({
|
|
252
|
-
message: '이메일 (선택):',
|
|
253
|
-
default: '',
|
|
254
|
-
});
|
|
255
|
-
contactKakao = await promptInput({
|
|
256
|
-
message: '카카오 오픈채팅 링크 (선택):',
|
|
257
|
-
default: '',
|
|
258
|
-
});
|
|
225
|
+
console.error('\x1b[2m💡 invite-only 팀은 `relay invite <slug> add @username`으로 사용자를 초대하세요.\x1b[0m');
|
|
259
226
|
}
|
|
227
|
+
console.error('');
|
|
260
228
|
const tags = tagsRaw
|
|
261
229
|
.split(',')
|
|
262
230
|
.map((t) => t.trim())
|
|
@@ -269,18 +237,6 @@ function registerPublish(program) {
|
|
|
269
237
|
tags,
|
|
270
238
|
visibility,
|
|
271
239
|
};
|
|
272
|
-
if (invite_code)
|
|
273
|
-
yamlData.invite_code = invite_code;
|
|
274
|
-
if (welcome)
|
|
275
|
-
yamlData.welcome = welcome;
|
|
276
|
-
if (contactEmail || contactKakao) {
|
|
277
|
-
const contact = {};
|
|
278
|
-
if (contactEmail)
|
|
279
|
-
contact.email = contactEmail;
|
|
280
|
-
if (contactKakao)
|
|
281
|
-
contact.kakao = contactKakao;
|
|
282
|
-
yamlData.contact = contact;
|
|
283
|
-
}
|
|
284
240
|
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
285
241
|
console.error(`\n\x1b[32m✓ relay.yaml이 생성되었습니다.\x1b[0m\n`);
|
|
286
242
|
}
|
|
@@ -294,9 +250,9 @@ function registerPublish(program) {
|
|
|
294
250
|
}));
|
|
295
251
|
process.exit(1);
|
|
296
252
|
}
|
|
297
|
-
//
|
|
298
|
-
if (isTTY
|
|
299
|
-
console.error('💡
|
|
253
|
+
// Profile hint
|
|
254
|
+
if (isTTY) {
|
|
255
|
+
console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: relayax.com/dashboard/edit');
|
|
300
256
|
}
|
|
301
257
|
// Validate structure
|
|
302
258
|
const hasDirs = VALID_DIRS.some((d) => {
|
|
@@ -341,10 +297,7 @@ function registerPublish(program) {
|
|
|
341
297
|
version: config.version,
|
|
342
298
|
changelog: config.changelog,
|
|
343
299
|
requires: config.requires,
|
|
344
|
-
welcome: config.welcome,
|
|
345
|
-
contact: config.contact,
|
|
346
300
|
visibility: config.visibility,
|
|
347
|
-
invite_code: config.invite_code,
|
|
348
301
|
};
|
|
349
302
|
if (!json) {
|
|
350
303
|
console.error(`패키지 생성 중... (${config.name} v${config.version})`);
|
package/dist/commands/search.js
CHANGED
|
@@ -12,7 +12,7 @@ function formatTable(results) {
|
|
|
12
12
|
? r.description.slice(0, 47) + '...'
|
|
13
13
|
: r.description,
|
|
14
14
|
installs: String(r.install_count),
|
|
15
|
-
commands: r.commands.join(', ') || '-',
|
|
15
|
+
commands: r.commands.map((c) => typeof c === 'string' ? c : c.name).join(', ') || '-',
|
|
16
16
|
}));
|
|
17
17
|
const cols = ['slug', 'name', 'description', 'installs', 'commands'];
|
|
18
18
|
const widths = cols.map((col) => Math.max(col.length, ...rows.map((r) => r[col].length)));
|
|
@@ -0,0 +1,116 @@
|
|
|
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.registerStatus = registerStatus;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
10
|
+
const config_js_1 = require("../lib/config.js");
|
|
11
|
+
const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
12
|
+
async function resolveUsername(token) {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
|
|
15
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return undefined;
|
|
19
|
+
const body = await res.json();
|
|
20
|
+
return body.username;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function registerStatus(program) {
|
|
27
|
+
program
|
|
28
|
+
.command('status')
|
|
29
|
+
.description('현재 relay 환경 상태를 표시합니다')
|
|
30
|
+
.action(async () => {
|
|
31
|
+
const json = program.opts().json ?? false;
|
|
32
|
+
const projectPath = process.cwd();
|
|
33
|
+
// 1. 로그인 상태
|
|
34
|
+
const token = (0, config_js_1.loadToken)();
|
|
35
|
+
let username;
|
|
36
|
+
if (token) {
|
|
37
|
+
username = await resolveUsername(token);
|
|
38
|
+
}
|
|
39
|
+
// 2. 에이전트 감지
|
|
40
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
41
|
+
const primaryAgent = detected.length > 0 ? detected[0] : null;
|
|
42
|
+
// 글로벌 커맨드 상태
|
|
43
|
+
const hasGlobal = command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
44
|
+
// 로컬 Builder 커맨드 상태
|
|
45
|
+
let hasLocal = false;
|
|
46
|
+
if (primaryAgent) {
|
|
47
|
+
const localDir = path_1.default.join(projectPath, primaryAgent.skillsDir, 'commands', 'relay');
|
|
48
|
+
hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
|
|
49
|
+
}
|
|
50
|
+
// 3. 팀 정보
|
|
51
|
+
const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
|
|
52
|
+
let team = null;
|
|
53
|
+
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const yaml = await import('js-yaml');
|
|
56
|
+
const content = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
|
|
57
|
+
const raw = yaml.load(content);
|
|
58
|
+
team = {
|
|
59
|
+
is_team: true,
|
|
60
|
+
name: String(raw.name ?? ''),
|
|
61
|
+
slug: String(raw.slug ?? ''),
|
|
62
|
+
version: String(raw.version ?? ''),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
team = { is_team: true };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
team = { is_team: false };
|
|
71
|
+
}
|
|
72
|
+
// 4. 출력
|
|
73
|
+
if (json) {
|
|
74
|
+
const result = {
|
|
75
|
+
login: { authenticated: !!token, username },
|
|
76
|
+
agent: {
|
|
77
|
+
detected: primaryAgent?.name ?? null,
|
|
78
|
+
global_commands: hasGlobal,
|
|
79
|
+
local_commands: hasLocal,
|
|
80
|
+
},
|
|
81
|
+
team,
|
|
82
|
+
};
|
|
83
|
+
console.log(JSON.stringify(result));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log('');
|
|
87
|
+
// 로그인
|
|
88
|
+
if (token && username) {
|
|
89
|
+
console.log(` \x1b[32m✓\x1b[0m 로그인: \x1b[36m${username}\x1b[0m`);
|
|
90
|
+
}
|
|
91
|
+
else if (token) {
|
|
92
|
+
console.log(` \x1b[32m✓\x1b[0m 로그인: 인증됨`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(` \x1b[31m✗\x1b[0m 로그인: 미인증 (\x1b[33mrelay login\x1b[0m으로 로그인)`);
|
|
96
|
+
}
|
|
97
|
+
// 에이전트
|
|
98
|
+
if (primaryAgent) {
|
|
99
|
+
const globalLabel = hasGlobal ? '\x1b[32m글로벌 ✓\x1b[0m' : '\x1b[31m글로벌 ✗\x1b[0m';
|
|
100
|
+
const localLabel = hasLocal ? '\x1b[32m로컬 ✓\x1b[0m' : '\x1b[2m로컬 —\x1b[0m';
|
|
101
|
+
console.log(` \x1b[32m✓\x1b[0m 에이전트: \x1b[36m${primaryAgent.name}\x1b[0m (${globalLabel} ${localLabel})`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(` \x1b[31m✗\x1b[0m 에이전트: 감지 안 됨`);
|
|
105
|
+
}
|
|
106
|
+
// 팀
|
|
107
|
+
if (team?.is_team && team.name) {
|
|
108
|
+
console.log(` \x1b[32m✓\x1b[0m 현재 팀: \x1b[36m${team.name}\x1b[0m v${team.version}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log(` \x1b[2m—\x1b[0m 현재 프로젝트: 팀 아님`);
|
|
112
|
+
}
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
package/dist/commands/update.js
CHANGED
|
@@ -62,7 +62,7 @@ function registerUpdate(program) {
|
|
|
62
62
|
};
|
|
63
63
|
(0, config_js_1.saveInstalled)(installed);
|
|
64
64
|
// Report install (non-blocking)
|
|
65
|
-
await (0, api_js_1.reportInstall)(slug
|
|
65
|
+
await (0, api_js_1.reportInstall)(slug);
|
|
66
66
|
const result = {
|
|
67
67
|
status: 'updated',
|
|
68
68
|
slug,
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const init_js_1 = require("./commands/init.js");
|
|
6
|
+
const create_js_1 = require("./commands/create.js");
|
|
7
|
+
const status_js_1 = require("./commands/status.js");
|
|
6
8
|
const search_js_1 = require("./commands/search.js");
|
|
7
9
|
const install_js_1 = require("./commands/install.js");
|
|
8
10
|
const list_js_1 = require("./commands/list.js");
|
|
@@ -20,6 +22,8 @@ program
|
|
|
20
22
|
.version(pkg.version)
|
|
21
23
|
.option('--json', '구조화된 JSON 출력');
|
|
22
24
|
(0, init_js_1.registerInit)(program);
|
|
25
|
+
(0, create_js_1.registerCreate)(program);
|
|
26
|
+
(0, status_js_1.registerStatus)(program);
|
|
23
27
|
(0, search_js_1.registerSearch)(program);
|
|
24
28
|
(0, install_js_1.registerInstall)(program);
|
|
25
29
|
(0, list_js_1.registerList)(program);
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export interface TeamVersionInfo {
|
|
|
7
7
|
created_at: string;
|
|
8
8
|
}
|
|
9
9
|
export declare function fetchTeamVersions(slug: string): Promise<TeamVersionInfo[]>;
|
|
10
|
-
export declare function reportInstall(slug: string
|
|
10
|
+
export declare function reportInstall(slug: string): Promise<void>;
|
package/dist/lib/api.js
CHANGED
|
@@ -36,9 +36,8 @@ async function fetchTeamVersions(slug) {
|
|
|
36
36
|
}
|
|
37
37
|
return res.json();
|
|
38
38
|
}
|
|
39
|
-
async function reportInstall(slug
|
|
40
|
-
const
|
|
41
|
-
const url = code ? `${base}?code=${encodeURIComponent(code)}` : base;
|
|
39
|
+
async function reportInstall(slug) {
|
|
40
|
+
const url = `${config_js_1.API_URL}/api/registry/${slug}/install`;
|
|
42
41
|
await fetch(url, { method: 'POST' }).catch(() => {
|
|
43
42
|
// non-critical: ignore errors
|
|
44
43
|
});
|
|
@@ -10,12 +10,24 @@ export interface ToolCommandAdapter {
|
|
|
10
10
|
formatFile(content: CommandContent): string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* {skillsDir}/commands/relay/{id}.md
|
|
13
|
+
* 로컬 어댑터 — 프로젝트 디렉토리 기준.
|
|
14
|
+
* {projectPath}/{skillsDir}/commands/relay/{id}.md
|
|
15
15
|
*/
|
|
16
16
|
export declare function createAdapter(tool: AITool): ToolCommandAdapter;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* 글로벌 슬래시 커맨드 파일 경로.
|
|
19
|
+
* ~/.claude/commands/relay/{id}.md
|
|
20
20
|
*/
|
|
21
|
+
export declare function getGlobalCommandPath(commandId: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* 글로벌 슬래시 커맨드 디렉토리.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getGlobalCommandDir(): string;
|
|
26
|
+
/**
|
|
27
|
+
* 커맨드 콘텐츠를 파일 형식으로 포맷.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatCommandFile(content: CommandContent): string;
|
|
30
|
+
export declare const USER_COMMANDS: CommandContent[];
|
|
31
|
+
export declare const BUILDER_COMMANDS: CommandContent[];
|
|
32
|
+
/** 하위 호환 — 기존 코드에서 RELAY_COMMANDS를 참조하는 경우 */
|
|
21
33
|
export declare const RELAY_COMMANDS: CommandContent[];
|
|
@@ -3,12 +3,16 @@ 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.RELAY_COMMANDS = void 0;
|
|
6
|
+
exports.RELAY_COMMANDS = exports.BUILDER_COMMANDS = exports.USER_COMMANDS = void 0;
|
|
7
7
|
exports.createAdapter = createAdapter;
|
|
8
|
+
exports.getGlobalCommandPath = getGlobalCommandPath;
|
|
9
|
+
exports.getGlobalCommandDir = getGlobalCommandDir;
|
|
10
|
+
exports.formatCommandFile = formatCommandFile;
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
8
12
|
const path_1 = __importDefault(require("path"));
|
|
9
13
|
/**
|
|
10
|
-
*
|
|
11
|
-
* {skillsDir}/commands/relay/{id}.md
|
|
14
|
+
* 로컬 어댑터 — 프로젝트 디렉토리 기준.
|
|
15
|
+
* {projectPath}/{skillsDir}/commands/relay/{id}.md
|
|
12
16
|
*/
|
|
13
17
|
function createAdapter(tool) {
|
|
14
18
|
return {
|
|
@@ -22,10 +26,33 @@ function createAdapter(tool) {
|
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
29
|
+
* 글로벌 슬래시 커맨드 파일 경로.
|
|
30
|
+
* ~/.claude/commands/relay/{id}.md
|
|
27
31
|
*/
|
|
28
|
-
|
|
32
|
+
function getGlobalCommandPath(commandId) {
|
|
33
|
+
return path_1.default.join(os_1.default.homedir(), '.claude', 'commands', 'relay', `${commandId}.md`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 글로벌 슬래시 커맨드 디렉토리.
|
|
37
|
+
*/
|
|
38
|
+
function getGlobalCommandDir() {
|
|
39
|
+
return path_1.default.join(os_1.default.homedir(), '.claude', 'commands', 'relay');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 커맨드 콘텐츠를 파일 형식으로 포맷.
|
|
43
|
+
*/
|
|
44
|
+
function formatCommandFile(content) {
|
|
45
|
+
return `---\ndescription: ${content.description}\n---\n\n${content.body}\n`;
|
|
46
|
+
}
|
|
47
|
+
// ─── Login JIT 공통 안내 ───
|
|
48
|
+
const LOGIN_JIT_GUIDE = `
|
|
49
|
+
### 인증 오류 처리
|
|
50
|
+
- 커맨드 실행 결과에 \`LOGIN_REQUIRED\` 에러가 포함되면:
|
|
51
|
+
1. 사용자에게 "이 팀은 로그인이 필요합니다"라고 안내합니다.
|
|
52
|
+
2. \`relay login\` 명령어를 실행합니다.
|
|
53
|
+
3. 로그인 완료 후 원래 커맨드를 재실행합니다.`;
|
|
54
|
+
// ─── User Commands (글로벌 설치) ───
|
|
55
|
+
exports.USER_COMMANDS = [
|
|
29
56
|
{
|
|
30
57
|
id: 'relay-explore',
|
|
31
58
|
description: 'relay 마켓플레이스를 탐색하고 프로젝트에 맞는 팀을 찾습니다',
|
|
@@ -81,6 +108,7 @@ exports.RELAY_COMMANDS = [
|
|
|
81
108
|
### 5. 완료 안내
|
|
82
109
|
- 사용 가능한 커맨드 안내
|
|
83
110
|
- "바로 사용해볼까요?" 제안
|
|
111
|
+
${LOGIN_JIT_GUIDE}
|
|
84
112
|
|
|
85
113
|
## 예시
|
|
86
114
|
|
|
@@ -88,13 +116,80 @@ exports.RELAY_COMMANDS = [
|
|
|
88
116
|
→ relay install contents-team 실행
|
|
89
117
|
→ requires 확인: ✓ playwright, ✓ sharp 설치됨
|
|
90
118
|
→ ✓ 설치 완료!
|
|
91
|
-
→
|
|
92
|
-
→ │ contents-team을 설치해주셔서 감사합니다! │
|
|
93
|
-
→ │ 💬 카카오톡 📧 이메일 │
|
|
94
|
-
→ └─────────────────────────────────────────┘
|
|
119
|
+
→ 제작자 메시지 및 연락처 표시
|
|
95
120
|
→ "@haemin을 팔로우할까요?" → Yes → ✓ 팔로우 완료
|
|
96
121
|
→ "바로 /cardnews를 사용해볼까요?"`,
|
|
97
122
|
},
|
|
123
|
+
{
|
|
124
|
+
id: 'relay-list',
|
|
125
|
+
description: '설치된 에이전트 팀 목록을 확인합니다',
|
|
126
|
+
body: `현재 설치된 에이전트 팀 목록을 보여줍니다.
|
|
127
|
+
|
|
128
|
+
## 실행 방법
|
|
129
|
+
|
|
130
|
+
1. \`relay list --json\` 명령어를 실행합니다.
|
|
131
|
+
2. 결과를 사용자에게 보기 좋게 정리하여 보여줍니다:
|
|
132
|
+
- 팀 이름과 버전
|
|
133
|
+
- 설치 날짜
|
|
134
|
+
- 사용 가능한 커맨드
|
|
135
|
+
3. 설치된 팀이 없으면 \`/relay-explore\`로 팀을 탐색해보라고 안내합니다.
|
|
136
|
+
|
|
137
|
+
## 예시
|
|
138
|
+
|
|
139
|
+
사용자: /relay-list
|
|
140
|
+
→ relay list --json 실행
|
|
141
|
+
→ "2개 팀이 설치되어 있어요:"
|
|
142
|
+
→ " contents-team v1.2.0 (2일 전 설치) - /cardnews, /pdf-report"
|
|
143
|
+
→ " qa-team v0.5.1 (1주 전 설치) - /qa, /qa-only"`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'relay-update',
|
|
147
|
+
description: '설치된 에이전트 팀을 최신 버전으로 업데이트합니다',
|
|
148
|
+
body: `설치된 에이전트 팀의 업데이트를 확인하고 적용합니다.
|
|
149
|
+
|
|
150
|
+
## 실행 방법
|
|
151
|
+
|
|
152
|
+
### 특정 팀 업데이트
|
|
153
|
+
- 사용자가 팀 이름을 지정한 경우: \`relay update <slug> --json\` 실행
|
|
154
|
+
- 업데이트 결과를 보여줍니다 (이전 버전 → 새 버전)
|
|
155
|
+
|
|
156
|
+
### 전체 업데이트 확인
|
|
157
|
+
- 팀 이름을 지정하지 않은 경우:
|
|
158
|
+
1. \`relay outdated --json\`으로 업데이트 가능한 팀 목록을 확인합니다.
|
|
159
|
+
2. 업데이트 가능한 팀이 있으면 목록을 보여주고 어떤 팀을 업데이트할지 물어봅니다.
|
|
160
|
+
3. 선택된 팀에 대해 \`relay update <slug> --json\`을 실행합니다.
|
|
161
|
+
4. 모두 최신이면 "모든 팀이 최신 버전입니다"라고 안내합니다.
|
|
162
|
+
|
|
163
|
+
## 예시
|
|
164
|
+
|
|
165
|
+
사용자: /relay-update
|
|
166
|
+
→ relay outdated --json 실행
|
|
167
|
+
→ "1개 팀 업데이트 가능:"
|
|
168
|
+
→ " contents-team: v1.2.0 → v1.3.0"
|
|
169
|
+
→ "업데이트할까요?"
|
|
170
|
+
→ relay update contents-team --json 실행
|
|
171
|
+
→ "✓ contents-team v1.3.0으로 업데이트 완료"`,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'relay-uninstall',
|
|
175
|
+
description: '설치된 에이전트 팀을 삭제합니다',
|
|
176
|
+
body: `설치된 에이전트 팀을 프로젝트에서 제거합니다.
|
|
177
|
+
|
|
178
|
+
## 실행 방법
|
|
179
|
+
|
|
180
|
+
1. \`relay uninstall <slug> --json\` 명령어를 실행합니다.
|
|
181
|
+
2. 삭제 결과를 보여줍니다 (팀 이름, 제거된 파일 수).
|
|
182
|
+
3. 삭제 완료 후 남아있는 팀 목록을 간단히 안내합니다.
|
|
183
|
+
|
|
184
|
+
## 예시
|
|
185
|
+
|
|
186
|
+
사용자: /relay-uninstall contents-team
|
|
187
|
+
→ relay uninstall contents-team --json 실행
|
|
188
|
+
→ "✓ contents-team 삭제 완료 (12개 파일 제거)"`,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
// ─── Builder Commands (로컬 설치) ───
|
|
192
|
+
exports.BUILDER_COMMANDS = [
|
|
98
193
|
{
|
|
99
194
|
id: 'relay-publish',
|
|
100
195
|
description: '현재 팀 패키지를 포트폴리오와 함께 relay 마켓플레이스에 배포합니다',
|
|
@@ -106,6 +201,7 @@ exports.RELAY_COMMANDS = [
|
|
|
106
201
|
- \`cat ~/.relay/token\` 또는 환경변수 RELAY_TOKEN으로 토큰 존재 여부를 확인합니다.
|
|
107
202
|
- 미인증이면 즉시 안내: "먼저 \`relay login\`으로 로그인이 필요합니다." → 로그인 후 재실행 안내.
|
|
108
203
|
- 인증되어 있으면 다음 단계로 진행합니다.
|
|
204
|
+
${LOGIN_JIT_GUIDE}
|
|
109
205
|
|
|
110
206
|
### 2. 팀 구조 분석
|
|
111
207
|
- skills/, agents/, rules/, commands/ 디렉토리를 탐색합니다.
|
|
@@ -156,3 +252,5 @@ exports.RELAY_COMMANDS = [
|
|
|
156
252
|
→ "배포 완료! URL: https://relayax.com/teams/contents-team"`,
|
|
157
253
|
},
|
|
158
254
|
];
|
|
255
|
+
/** 하위 호환 — 기존 코드에서 RELAY_COMMANDS를 참조하는 경우 */
|
|
256
|
+
exports.RELAY_COMMANDS = [...exports.USER_COMMANDS, ...exports.BUILDER_COMMANDS];
|