relayax-cli 0.4.25 → 0.4.27
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/package.js +0 -12
- package/dist/commands/publish.js +1 -38
- package/dist/index.js +0 -2
- package/dist/lib/ai-tools.d.ts +0 -16
- package/dist/lib/ai-tools.js +0 -34
- package/dist/lib/command-adapter.js +2 -34
- package/dist/lib/preamble.d.ts +2 -2
- package/dist/lib/preamble.js +3 -43
- package/dist/mcp/server.js +7 -677
- package/dist/prompts/create.md +3 -6
- package/dist/prompts/explore.md +2 -0
- package/package.json +1 -1
- package/dist/commands/export.d.ts +0 -2
- package/dist/commands/export.js +0 -106
- package/dist/commands/follow.d.ts +0 -2
- package/dist/commands/follow.js +0 -45
- package/dist/commands/invite.d.ts +0 -2
- package/dist/commands/invite.js +0 -135
- package/dist/commands/spaces.d.ts +0 -11
- package/dist/commands/spaces.js +0 -69
- package/dist/lib/config.js.bak +0 -75
- package/dist/lib/guide.d.ts +0 -12
- package/dist/lib/guide.js +0 -60
- package/dist/lib/manifest-generator.d.ts +0 -20
- package/dist/lib/manifest-generator.js +0 -144
- package/dist/lib/security-scan.d.ts +0 -19
- package/dist/lib/security-scan.js +0 -114
- package/dist/prompts/_business-card.md +0 -41
- package/dist/prompts/_guide-instruction.md +0 -2
- package/dist/prompts/_requirements-check.md +0 -59
- package/dist/prompts/_setup-cli.md +0 -36
- package/dist/prompts/_setup-environment.md +0 -75
- package/dist/prompts/_setup-login.md +0 -31
- package/dist/prompts/_setup-org.md +0 -25
- package/dist/prompts/business-card.md +0 -41
- package/dist/prompts/error-handling.md +0 -38
- package/dist/prompts/install.md +0 -178
- package/dist/prompts/publish.md +0 -505
- package/dist/prompts/requirements-check.md +0 -59
|
@@ -1,144 +0,0 @@
|
|
|
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.SUPPORTED_PLATFORMS = void 0;
|
|
7
|
-
exports.generateManifests = generateManifests;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
exports.SUPPORTED_PLATFORMS = ['claude-code', 'codex', 'antigravity'];
|
|
11
|
-
// ─── Claude Code Generator ───
|
|
12
|
-
function generateClaudeCodeManifest(yaml, agentDir) {
|
|
13
|
-
const files = [];
|
|
14
|
-
// .claude-plugin/plugin.json
|
|
15
|
-
const pluginJson = {
|
|
16
|
-
name: yaml.slug.replace(/^@/, ''),
|
|
17
|
-
description: yaml.description,
|
|
18
|
-
version: yaml.version,
|
|
19
|
-
};
|
|
20
|
-
if (yaml.source) {
|
|
21
|
-
pluginJson.repository = yaml.source;
|
|
22
|
-
}
|
|
23
|
-
if (yaml.org_slug) {
|
|
24
|
-
pluginJson.author = { name: yaml.org_slug };
|
|
25
|
-
}
|
|
26
|
-
files.push({
|
|
27
|
-
relativePath: '.claude-plugin/plugin.json',
|
|
28
|
-
content: JSON.stringify(pluginJson, null, 2),
|
|
29
|
-
});
|
|
30
|
-
// marketplace.json (self-contained marketplace with single plugin entry)
|
|
31
|
-
const slug = yaml.slug.startsWith('@') ? yaml.slug.slice(1) : yaml.slug;
|
|
32
|
-
const parts = slug.split('/');
|
|
33
|
-
const owner = parts[0] ?? slug;
|
|
34
|
-
const pluginName = parts[1] ?? slug;
|
|
35
|
-
const marketplaceJson = {
|
|
36
|
-
name: `@${slug}`,
|
|
37
|
-
owner: { name: owner },
|
|
38
|
-
plugins: [
|
|
39
|
-
{
|
|
40
|
-
name: pluginName,
|
|
41
|
-
source: {
|
|
42
|
-
source: 'url',
|
|
43
|
-
url: './',
|
|
44
|
-
},
|
|
45
|
-
version: yaml.version,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
files.push({
|
|
50
|
-
relativePath: 'marketplace.json',
|
|
51
|
-
content: JSON.stringify(marketplaceJson, null, 2),
|
|
52
|
-
});
|
|
53
|
-
return files;
|
|
54
|
-
}
|
|
55
|
-
// ─── Codex Generator ───
|
|
56
|
-
function generateCodexManifest(yaml, agentDir) {
|
|
57
|
-
const pluginJson = {
|
|
58
|
-
name: yaml.slug.replace(/^@/, ''),
|
|
59
|
-
description: yaml.description,
|
|
60
|
-
version: yaml.version,
|
|
61
|
-
};
|
|
62
|
-
// Check if skills/ directory exists
|
|
63
|
-
const skillsDir = path_1.default.join(agentDir, 'skills');
|
|
64
|
-
if (fs_1.default.existsSync(skillsDir)) {
|
|
65
|
-
pluginJson.skills = './skills/';
|
|
66
|
-
}
|
|
67
|
-
return [
|
|
68
|
-
{
|
|
69
|
-
relativePath: '.codex-plugin/plugin.json',
|
|
70
|
-
content: JSON.stringify(pluginJson, null, 2),
|
|
71
|
-
},
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
// ─── Antigravity Generator ───
|
|
75
|
-
function generateAntigravityManifest(yaml, agentDir) {
|
|
76
|
-
// Antigravity uses .agent/skills/ structure
|
|
77
|
-
// Only generate if skills/ directory exists
|
|
78
|
-
const skillsDir = path_1.default.join(agentDir, 'skills');
|
|
79
|
-
if (!fs_1.default.existsSync(skillsDir)) {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
// Map skills/ to .agent/skills/ — no content changes, just path mapping
|
|
83
|
-
const files = [];
|
|
84
|
-
const skillEntries = fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
|
|
85
|
-
for (const entry of skillEntries) {
|
|
86
|
-
if (entry.isDirectory()) {
|
|
87
|
-
const skillMd = path_1.default.join(skillsDir, entry.name, 'SKILL.md');
|
|
88
|
-
if (fs_1.default.existsSync(skillMd)) {
|
|
89
|
-
const content = fs_1.default.readFileSync(skillMd, 'utf-8');
|
|
90
|
-
files.push({
|
|
91
|
-
relativePath: `.agent/skills/${entry.name}/SKILL.md`,
|
|
92
|
-
content,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return files;
|
|
98
|
-
}
|
|
99
|
-
// ─── Platform Registry ───
|
|
100
|
-
const GENERATORS = {
|
|
101
|
-
'claude-code': generateClaudeCodeManifest,
|
|
102
|
-
'codex': generateCodexManifest,
|
|
103
|
-
'antigravity': generateAntigravityManifest,
|
|
104
|
-
};
|
|
105
|
-
// ─── Public API ───
|
|
106
|
-
/**
|
|
107
|
-
* Generate platform-native manifests from relay.yaml metadata.
|
|
108
|
-
* Used by both `relay publish` and `relay export`.
|
|
109
|
-
*/
|
|
110
|
-
function generateManifests(yaml, agentDir) {
|
|
111
|
-
const platforms = resolvePlatforms(yaml.platforms);
|
|
112
|
-
const files = [];
|
|
113
|
-
for (const platform of platforms) {
|
|
114
|
-
const generator = GENERATORS[platform];
|
|
115
|
-
if (generator) {
|
|
116
|
-
try {
|
|
117
|
-
files.push(...generator(yaml, agentDir));
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
-
console.error(`\x1b[33m⚠ ${platform} 매니페스트 생성 실패: ${msg}\x1b[0m`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return files;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Resolve platforms list: filter valid platforms, warn on invalid ones.
|
|
129
|
-
*/
|
|
130
|
-
function resolvePlatforms(platforms) {
|
|
131
|
-
if (!platforms || platforms.length === 0) {
|
|
132
|
-
return [...exports.SUPPORTED_PLATFORMS];
|
|
133
|
-
}
|
|
134
|
-
const valid = [];
|
|
135
|
-
for (const p of platforms) {
|
|
136
|
-
if (exports.SUPPORTED_PLATFORMS.includes(p)) {
|
|
137
|
-
valid.push(p);
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
console.error(`\x1b[33m⚠ 지원하지 않는 플랫폼: ${p} (지원: ${exports.SUPPORTED_PLATFORMS.join(', ')})\x1b[0m`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return valid;
|
|
144
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface SecretFinding {
|
|
2
|
-
file: string;
|
|
3
|
-
line: number;
|
|
4
|
-
label: string;
|
|
5
|
-
snippet: string;
|
|
6
|
-
}
|
|
7
|
-
export interface EnvRequirement {
|
|
8
|
-
name: string;
|
|
9
|
-
optional: boolean;
|
|
10
|
-
description: string;
|
|
11
|
-
}
|
|
12
|
-
export interface ScanResult {
|
|
13
|
-
secrets: SecretFinding[];
|
|
14
|
-
envRequirements: EnvRequirement[];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* .relay/ 디렉토리 내 모든 텍스트 파일을 스캔한다.
|
|
18
|
-
*/
|
|
19
|
-
export declare function scanForSecrets(relayDir: string): ScanResult;
|
|
@@ -1,114 +0,0 @@
|
|
|
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.scanForSecrets = scanForSecrets;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
/** 시크릿으로 의심되는 하드코딩 값 패턴 */
|
|
10
|
-
const SECRET_VALUE_PATTERNS = [
|
|
11
|
-
{ pattern: /sk-[a-zA-Z0-9_-]{20,}/, label: 'OpenAI API Key' },
|
|
12
|
-
{ pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/, label: 'Anthropic API Key' },
|
|
13
|
-
{ pattern: /xoxb-[0-9]+-[a-zA-Z0-9]+/, label: 'Slack Bot Token' },
|
|
14
|
-
{ pattern: /xoxp-[0-9]+-[a-zA-Z0-9]+/, label: 'Slack User Token' },
|
|
15
|
-
{ pattern: /ghp_[a-zA-Z0-9]{36,}/, label: 'GitHub Personal Access Token' },
|
|
16
|
-
{ pattern: /gho_[a-zA-Z0-9]{36,}/, label: 'GitHub OAuth Token' },
|
|
17
|
-
{ pattern: /glpat-[a-zA-Z0-9\-_]{20,}/, label: 'GitLab Personal Access Token' },
|
|
18
|
-
{ pattern: /AKIA[0-9A-Z]{16}/, label: 'AWS Access Key ID' },
|
|
19
|
-
{ pattern: /eyJ[a-zA-Z0-9_-]{50,}\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/, label: 'JWT Token' },
|
|
20
|
-
{ pattern: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/, label: 'Private Key' },
|
|
21
|
-
{ pattern: /AIza[a-zA-Z0-9_-]{35}/, label: 'Google API Key' },
|
|
22
|
-
{ pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/, label: 'SendGrid API Key' },
|
|
23
|
-
{ pattern: /pk_live_[a-zA-Z0-9]{24,}/, label: 'Stripe Publishable Key' },
|
|
24
|
-
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, label: 'Stripe Secret Key' },
|
|
25
|
-
{ pattern: /sq0atp-[a-zA-Z0-9_-]{22}/, label: 'Square Access Token' },
|
|
26
|
-
];
|
|
27
|
-
/** 환경변수 참조 패턴 */
|
|
28
|
-
const ENV_REF_PATTERNS = [
|
|
29
|
-
// process.env.VAR_NAME or process.env['VAR_NAME']
|
|
30
|
-
/process\.env\.([A-Z][A-Z0-9_]+)/g,
|
|
31
|
-
/process\.env\[['"]([A-Z][A-Z0-9_]+)['"]\]/g,
|
|
32
|
-
// $VAR_NAME or ${VAR_NAME} in shell/template contexts
|
|
33
|
-
/\$\{([A-Z][A-Z0-9_]+)\}/g,
|
|
34
|
-
/\$([A-Z][A-Z0-9_]{2,})/g,
|
|
35
|
-
// os.environ['VAR'] or os.environ.get('VAR') (Python)
|
|
36
|
-
/os\.environ(?:\.get)?\(?['"]([A-Z][A-Z0-9_]+)['"]/g,
|
|
37
|
-
// ENV['VAR'] or ENV.fetch('VAR') (Ruby)
|
|
38
|
-
/ENV(?:\.fetch)?\(?['"]([A-Z][A-Z0-9_]+)['"]/g,
|
|
39
|
-
];
|
|
40
|
-
/** 시크릿성 환경변수 이름 키워드 */
|
|
41
|
-
const SECRET_ENV_KEYWORDS = [
|
|
42
|
-
'KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'CREDENTIAL', 'AUTH',
|
|
43
|
-
'API_KEY', 'ACCESS_KEY', 'PRIVATE', 'SIGNING',
|
|
44
|
-
];
|
|
45
|
-
function isSecretEnvName(name) {
|
|
46
|
-
const upper = name.toUpperCase();
|
|
47
|
-
return SECRET_ENV_KEYWORDS.some((kw) => upper.includes(kw));
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* .relay/ 디렉토리 내 모든 텍스트 파일을 스캔한다.
|
|
51
|
-
*/
|
|
52
|
-
function scanForSecrets(relayDir) {
|
|
53
|
-
const secrets = [];
|
|
54
|
-
const envNames = new Set();
|
|
55
|
-
const textExts = ['.md', '.txt', '.yaml', '.yml', '.json', '.ts', '.js', '.py', '.sh', '.rb', '.toml'];
|
|
56
|
-
function walkDir(dir) {
|
|
57
|
-
if (!fs_1.default.existsSync(dir))
|
|
58
|
-
return;
|
|
59
|
-
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
60
|
-
const fullPath = path_1.default.join(dir, entry.name);
|
|
61
|
-
if (entry.isDirectory()) {
|
|
62
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
63
|
-
continue;
|
|
64
|
-
walkDir(fullPath);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
const ext = path_1.default.extname(entry.name).toLowerCase();
|
|
68
|
-
if (!textExts.includes(ext))
|
|
69
|
-
continue;
|
|
70
|
-
scanFile(fullPath, relayDir);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function scanFile(filePath, baseDir) {
|
|
75
|
-
let content;
|
|
76
|
-
try {
|
|
77
|
-
content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const relPath = path_1.default.relative(baseDir, filePath);
|
|
83
|
-
const lines = content.split('\n');
|
|
84
|
-
// 1. 하드코딩된 시크릿 검사
|
|
85
|
-
for (let i = 0; i < lines.length; i++) {
|
|
86
|
-
const line = lines[i];
|
|
87
|
-
for (const { pattern, label } of SECRET_VALUE_PATTERNS) {
|
|
88
|
-
if (pattern.test(line)) {
|
|
89
|
-
const snippet = line.trim().length > 80 ? line.trim().slice(0, 77) + '...' : line.trim();
|
|
90
|
-
secrets.push({ file: relPath, line: i + 1, label, snippet });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// 2. 환경변수 참조 감지
|
|
95
|
-
for (const pattern of ENV_REF_PATTERNS) {
|
|
96
|
-
const regex = new RegExp(pattern.source, pattern.flags);
|
|
97
|
-
let match;
|
|
98
|
-
while ((match = regex.exec(content)) !== null) {
|
|
99
|
-
if (match[1])
|
|
100
|
-
envNames.add(match[1]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
walkDir(relayDir);
|
|
105
|
-
// 환경변수를 requirements로 변환
|
|
106
|
-
const envRequirements = [...envNames]
|
|
107
|
-
.sort()
|
|
108
|
-
.map((name) => ({
|
|
109
|
-
name,
|
|
110
|
-
optional: !isSecretEnvName(name),
|
|
111
|
-
description: isSecretEnvName(name) ? `${name} (필수 — 시크릿)` : name,
|
|
112
|
-
}));
|
|
113
|
-
return { secrets, envRequirements };
|
|
114
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
### 빌더 명함 표시
|
|
2
|
-
JSON 결과의 `author`, `welcome` 필드를 사용하여 명함을 표시합니다.
|
|
3
|
-
불릿 리스트(- 또는 *)로 나열하지 마세요. 반드시 인용 블록(>) 안에 넣어야 합니다.
|
|
4
|
-
|
|
5
|
-
**JSON 결과에서 사용할 필드:**
|
|
6
|
-
- `author.display_name` 또는 `author.username` → 명함 제목
|
|
7
|
-
- `welcome` → 환영 메시지 (💬)
|
|
8
|
-
- `author.contact_links` → 연락처 배열 (`[{type, label, value}]`)
|
|
9
|
-
- `author.username` → 프로필 링크 (👤)
|
|
10
|
-
|
|
11
|
-
**예시 (이 형태를 그대로 따르세요):**
|
|
12
|
-
|
|
13
|
-
JSON 결과 예시:
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"author": { "username": "alice", "display_name": "Alice Kim", "contact_links": [
|
|
17
|
-
{"type": "email", "label": "이메일", "value": "alice@example.com"},
|
|
18
|
-
{"type": "website", "label": "블로그", "value": "https://alice.dev"},
|
|
19
|
-
{"type": "kakao", "label": "카카오", "value": "https://open.kakao.com/o/abc123"}
|
|
20
|
-
]},
|
|
21
|
-
"welcome": "안녕하세요!\n에이전트 빌더 Alice입니다.\n설치해주셔서 감사합니다."
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
출력:
|
|
26
|
-
|
|
27
|
-
> **🪪 Alice Kim의 명함**
|
|
28
|
-
>
|
|
29
|
-
> 💬 "안녕하세요!
|
|
30
|
-
> 에이전트 빌더 Alice입니다.
|
|
31
|
-
> 설치해주셔서 감사합니다."
|
|
32
|
-
>
|
|
33
|
-
> 📧 alice@example.com
|
|
34
|
-
> 🔗 블로그: alice.dev
|
|
35
|
-
> 💬 카카오: open.kakao.com/o/abc123
|
|
36
|
-
> 👤 relayax.com/@alice
|
|
37
|
-
|
|
38
|
-
- `welcome`이 없으면 💬 줄을 생략합니다.
|
|
39
|
-
- 연락처의 type에 맞는 이모지: 📧 email, 💬 kakao, 🐦 x, 💼 linkedin, 💻 github, 🔗 website/custom
|
|
40
|
-
- 연락처가 여러 개면 각각 한 줄씩 표시합니다.
|
|
41
|
-
- `author`가 null이면 명함 블록 전체를 생략합니다.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
### Requirements 체크리스트 (필수 — 항목이 있으면 반드시 수행)
|
|
2
|
-
|
|
3
|
-
`relay.yaml`의 `requires` 섹션을 읽고, **각 항목을 하나씩 확인하여 체크리스트로 표시**합니다.
|
|
4
|
-
requires 섹션이 없거나 비어있으면 이 단계를 건너뜁니다.
|
|
5
|
-
|
|
6
|
-
**출력 형식** (반드시 이 형식으로 사용자에게 보여줍니다):
|
|
7
|
-
```
|
|
8
|
-
📋 Requirements 확인
|
|
9
|
-
|
|
10
|
-
[runtime]
|
|
11
|
-
✅ Node.js >=18 — v20.11.0 확인됨
|
|
12
|
-
❌ Python >=3.10 — v3.8.5 (업그레이드 필요)
|
|
13
|
-
|
|
14
|
-
[cli]
|
|
15
|
-
✅ playwright — 설치됨
|
|
16
|
-
❌ ffmpeg — 미설치 → 설치 명령: brew install ffmpeg
|
|
17
|
-
|
|
18
|
-
[npm]
|
|
19
|
-
✅ sharp — 설치됨
|
|
20
|
-
❌ puppeteer — 미설치 → 설치 중...
|
|
21
|
-
|
|
22
|
-
[env]
|
|
23
|
-
✅ OPENAI_API_KEY — 설정됨
|
|
24
|
-
❌ SLACK_WEBHOOK_URL (선택) — 미설정. 알림 전송에 필요
|
|
25
|
-
|
|
26
|
-
[mcp]
|
|
27
|
-
⚙️ supabase — MCP 서버 설정 필요 (아래 안내 참고)
|
|
28
|
-
|
|
29
|
-
[agents]
|
|
30
|
-
✅ @alice/doc-writer — 이미 설치됨
|
|
31
|
-
📦 @bob/utils — 미설치 → 설치 중...
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**처리 규칙 (각 카테고리별):**
|
|
35
|
-
|
|
36
|
-
1. **runtime**: `node --version`, `python3 --version`으로 확인. 버전 미달이면 ❌ 표시 후 업그레이드 안내.
|
|
37
|
-
2. **cli**: `which <name>`으로 확인.
|
|
38
|
-
- 설치됨 → ✅
|
|
39
|
-
- 미설치 + `install` 필드 있음 → 사용자에게 설치할지 물어본 후 실행
|
|
40
|
-
- 미설치 + `install` 필드 없음 → ❌ 표시 후 수동 설치 안내
|
|
41
|
-
3. **npm**: `npm list <package> 2>/dev/null`으로 확인.
|
|
42
|
-
- 설치됨 → ✅
|
|
43
|
-
- 미설치 + required → `npm install <package>` 실행
|
|
44
|
-
- 미설치 + optional → ❌ 표시 후 안내만
|
|
45
|
-
4. **env**: `echo $<NAME>`으로 확인.
|
|
46
|
-
- 설정됨 → ✅
|
|
47
|
-
- 미설정 + required → ❌ 표시 후 `description`과 함께 설정 방법 안내
|
|
48
|
-
- 미설정 + optional → ⚠️ 표시 후 용도 안내
|
|
49
|
-
5. **mcp**: MCP 서버 설정이 필요한 경우 ⚙️ 표시 후 설정 방법을 상세히 안내.
|
|
50
|
-
- `config` 필드가 있으면 settings.json에 추가할 JSON 블록을 보여줍니다.
|
|
51
|
-
- `env` 필드가 있으면 필요한 환경변수도 함께 안내합니다.
|
|
52
|
-
6. **agents**: `relay list --json`으로 설치 여부 확인.
|
|
53
|
-
- 설치됨 → ✅
|
|
54
|
-
- 미설치 → `relay install <@author/agent> --json` 실행하여 재귀 설치
|
|
55
|
-
|
|
56
|
-
**중요**: 모든 required 항목이 ❌인 경우, 체크리스트 끝에 경고를 표시합니다:
|
|
57
|
-
```
|
|
58
|
-
⚠️ 필수 요구사항이 충족되지 않았습니다. 에이전트 기능이 제한될 수 있습니다.
|
|
59
|
-
```
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
## Step 1. Relay CLI 준비 및 업데이트
|
|
2
|
-
|
|
3
|
-
### 환경 A (터미널)
|
|
4
|
-
|
|
5
|
-
CLI가 설치되어 있지 않다면:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g relayax-cli
|
|
9
|
-
relay init
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
이미 설치되어 있다면 **반드시 최신 버전으로 업데이트**합니다:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm update -g relayax-cli
|
|
16
|
-
relay init
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
`relay init`은 slash command를 설치/업데이트합니다. 업데이트 후 반드시 실행하세요.
|
|
20
|
-
|
|
21
|
-
Node.js가 없는 경우:
|
|
22
|
-
- macOS: `brew install node`
|
|
23
|
-
- Windows: `winget install OpenJS.NodeJS.LTS`
|
|
24
|
-
- Linux: `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && source ~/.bashrc && nvm install --lts`
|
|
25
|
-
|
|
26
|
-
### 환경 B (MCP)
|
|
27
|
-
|
|
28
|
-
**Step 1-1. 버전 확인**: `relay_check_update` tool을 호출하세요.
|
|
29
|
-
- tool이 없으면 `relay_status`로 대체하고, 응답의 `cli.update_available` 또는 `cli_update` 필드를 확인하세요.
|
|
30
|
-
- `status: "up_to_date"` (또는 `update_available: null`) → 최신 버전. Step 1-2로.
|
|
31
|
-
- `status: "updates_available"` (또는 `update_available`에 버전이 있음) → 사용자에게 업데이트 안내:
|
|
32
|
-
> CLI 업데이트가 있습니다. 터미널에서 `npm update -g relayax-cli`를 실행한 후 Claude를 재시작해주세요.
|
|
33
|
-
|
|
34
|
-
**Step 1-2. slash command 설치**: `relay_init` tool을 반드시 호출하세요.
|
|
35
|
-
- `/relay-install`, `/relay-publish` 등 slash command가 설치/업데이트됩니다.
|
|
36
|
-
- 이미 최신이면 자동으로 건너뜁니다.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
## Step 0. 환경 감지
|
|
2
|
-
|
|
3
|
-
이 가이드는 여러 환경에서 실행될 수 있습니다. 아래 환경 감지를 먼저 수행하고, 이후 모든 Step에서 감지된 환경에 맞는 방법을 사용하세요.
|
|
4
|
-
|
|
5
|
-
### 환경 판별
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
which relay 2>/dev/null && echo "cli-ok" || echo "cli-missing"
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
- `cli-ok` → **환경 A (터미널 + CLI 설치됨)** — 바로 Step 1로
|
|
12
|
-
- `cli-missing` → 아래 확인:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm install -g relayax-cli 2>/dev/null && echo "install-ok" || echo "sandbox"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
- `install-ok` → **환경 A (터미널)** (`relay init` 실행 후 Step 1로)
|
|
19
|
-
- `sandbox` → **환경 B (샌드박스)** — 아래 MCP 설정으로 진행
|
|
20
|
-
|
|
21
|
-
### 환경 B: MCP Connector 자동 설정 (샌드박스)
|
|
22
|
-
|
|
23
|
-
샌드박스 환경(Cowork 등)에서는 네트워크가 제한되어 CLI로 직접 API 호출이 불가합니다.
|
|
24
|
-
대신 relay MCP 서버를 설정하면, 호스트 머신에서 API를 호출할 수 있습니다.
|
|
25
|
-
|
|
26
|
-
**relay MCP tool(`relay_status` 등)을 사용할 수 있는지 확인하세요.**
|
|
27
|
-
사용할 수 있다면 MCP가 이미 설정되어 있으므로 바로 Step 1로 진행합니다.
|
|
28
|
-
|
|
29
|
-
사용할 수 없다면 아래 절차로 MCP를 설정합니다:
|
|
30
|
-
|
|
31
|
-
1. Claude Desktop 설정 파일을 찾아 읽습니다:
|
|
32
|
-
```bash
|
|
33
|
-
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json 2>/dev/null || echo "{}"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
2. 설정 파일에 아래 relay MCP 서버를 추가합니다 (기존 mcpServers가 있으면 거기에 추가, 없으면 새로 생성):
|
|
37
|
-
```json
|
|
38
|
-
{
|
|
39
|
-
"mcpServers": {
|
|
40
|
-
"relay": {
|
|
41
|
-
"command": "npx",
|
|
42
|
-
"args": ["relayax-cli@latest", "mcp"]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
3. 사용자에게 **Claude Desktop을 재시작**해달라고 안내합니다.
|
|
49
|
-
|
|
50
|
-
4. 재시작 후 `relay_status` MCP tool이 사용 가능한지 확인합니다. 사용 가능하면 Step 1로 진행합니다.
|
|
51
|
-
|
|
52
|
-
### 환경별 실행 방법
|
|
53
|
-
|
|
54
|
-
이후 가이드의 모든 `relay <명령어>`는 감지된 환경에 따라 실행합니다:
|
|
55
|
-
|
|
56
|
-
| 가이드 지시 | A. 터미널 | B. 샌드박스 (MCP) |
|
|
57
|
-
|---|---|---|
|
|
58
|
-
| "relay status" | `relay status` | `relay_status` tool 호출 |
|
|
59
|
-
| "relay install X" | `relay install X` | `relay_install` tool 호출 (`slug: "X"`) |
|
|
60
|
-
| "relay publish" | `relay publish` | `relay_publish` tool 호출 |
|
|
61
|
-
| "relay login" | `relay login` | `relay_login` tool 호출 |
|
|
62
|
-
| "relay package --init" | `relay package --init --json` | `relay_package` tool 호출 (`mode: "init"`) |
|
|
63
|
-
| "relay package --sync" | `relay package --sync --json` | `relay_package` tool 호출 (`mode: "sync"`) |
|
|
64
|
-
| "relay package --migrate" | `relay package --migrate --json` | `relay_package` tool 호출 (`mode: "migrate"`) |
|
|
65
|
-
| "relay scan" | `relay package --init` | `relay_scan` tool 호출 |
|
|
66
|
-
| "relay check-update X" | `relay check-update X` | `relay_check_update` tool 호출 (`slug: "X"`) |
|
|
67
|
-
| "relay orgs list" | `relay orgs list --json` | `relay_org_list` tool 호출 |
|
|
68
|
-
| "relay orgs create" | `relay orgs create "이름" --json` | `relay_org_create` tool 호출 (`name: "이름"`) |
|
|
69
|
-
| "relay grant create" | `relay grant create --agent <slug>` | `relay_grant_create` tool 호출 (`agent_slug: "<slug>"`) |
|
|
70
|
-
| "relay grant use" | `relay grant use --code <code>` | `relay_grant_use` tool 호출 (`code: "<code>"`) |
|
|
71
|
-
| "relay access" | `relay access <slug> --code <code>` | `relay_access` tool 호출 (`slug`, `code`) |
|
|
72
|
-
| "relay join" | `relay join <slug> --code <code>` | `relay_join` tool 호출 (`code: "<code>"`) |
|
|
73
|
-
| "relay deploy-record" | `relay deploy-record <slug> --scope <scope> --files ...` | `relay_deploy_record` tool 호출 |
|
|
74
|
-
|
|
75
|
-
처음 판별한 환경을 이후 계속 사용합니다.
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
## Step 2. 로그인
|
|
2
|
-
|
|
3
|
-
**중요: 브라우저에서 직접 relayax.com에 접속하거나 웹 로그인 페이지를 여는 것은 올바른 방법이 아닙니다.**
|
|
4
|
-
|
|
5
|
-
먼저 로그인 상태를 확인합니다. 이미 로그인되어 있으면 이 단계를 건너뛰세요.
|
|
6
|
-
|
|
7
|
-
- 환경 A: `relay status`
|
|
8
|
-
- 환경 B (MCP): `relay_status` tool 호출
|
|
9
|
-
|
|
10
|
-
로그인이 필요하면 아래 환경에 맞는 방법을 사용하세요.
|
|
11
|
-
|
|
12
|
-
### 환경 A (터미널)
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
relay login
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
- 브라우저가 자동으로 열리면 GitHub 또는 카카오 계정으로 로그인합니다.
|
|
19
|
-
- 브라우저가 열리지 않으면 출력된 URL을 별도로 엽니다: `open <URL>` (macOS) / `xdg-open <URL>` (Linux)
|
|
20
|
-
|
|
21
|
-
"✓ 로그인 완료"가 출력되면 다음 단계로 진행합니다.
|
|
22
|
-
|
|
23
|
-
### 환경 B (MCP)
|
|
24
|
-
|
|
25
|
-
`relay_login` tool을 호출하세요. 이 tool이:
|
|
26
|
-
1. Device code와 URL을 자동으로 발급합니다.
|
|
27
|
-
2. 브라우저를 자동으로 엽니다.
|
|
28
|
-
3. 사용자가 브라우저에서 코드를 입력하면 자동으로 감지합니다.
|
|
29
|
-
4. 로그인 완료 후 결과를 반환합니다.
|
|
30
|
-
|
|
31
|
-
브라우저가 자동으로 열리지 않으면, 응답에 포함된 URL과 코드를 사용자에게 보여주세요.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
먼저 Organization이 있는지 확인합니다:
|
|
2
|
-
|
|
3
|
-
```bash
|
|
4
|
-
relay orgs list --json
|
|
5
|
-
```
|
|
6
|
-
|
|
7
|
-
Organization이 없으면 새로 생성합니다:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
relay orgs create "조직 이름"
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
생성 후 접근 코드를 만들어 사용자를 초대할 수 있습니다:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
relay grant create --org <org-slug>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
접근 코드를 받은 사용자는 아래 명령으로 Organization에 가입합니다:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
relay grant use --code <접근코드>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
또는 웹에서 직접 관리할 수도 있습니다: relayax.com/orgs
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
### 빌더 명함 표시
|
|
2
|
-
JSON 결과의 `author`, `welcome` 필드를 사용하여 명함을 표시합니다.
|
|
3
|
-
불릿 리스트(- 또는 *)로 나열하지 마세요. 반드시 인용 블록(>) 안에 넣어야 합니다.
|
|
4
|
-
|
|
5
|
-
**JSON 결과에서 사용할 필드:**
|
|
6
|
-
- `author.display_name` 또는 `author.username` → 명함 제목
|
|
7
|
-
- `welcome` → 환영 메시지 (💬)
|
|
8
|
-
- `author.contact_links` → 연락처 배열 (`[{type, label, value}]`)
|
|
9
|
-
- `author.username` → 프로필 링크 (👤)
|
|
10
|
-
|
|
11
|
-
**예시 (이 형태를 그대로 따르세요):**
|
|
12
|
-
|
|
13
|
-
JSON 결과 예시:
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"author": { "username": "alice", "display_name": "Alice Kim", "contact_links": [
|
|
17
|
-
{"type": "email", "label": "이메일", "value": "alice@example.com"},
|
|
18
|
-
{"type": "website", "label": "블로그", "value": "https://alice.dev"},
|
|
19
|
-
{"type": "kakao", "label": "카카오", "value": "https://open.kakao.com/o/abc123"}
|
|
20
|
-
]},
|
|
21
|
-
"welcome": "안녕하세요!\n에이전트 빌더 Alice입니다.\n설치해주셔서 감사합니다."
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
출력:
|
|
26
|
-
|
|
27
|
-
> **🪪 Alice Kim의 명함**
|
|
28
|
-
>
|
|
29
|
-
> 💬 "안녕하세요!
|
|
30
|
-
> 에이전트 빌더 Alice입니다.
|
|
31
|
-
> 설치해주셔서 감사합니다."
|
|
32
|
-
>
|
|
33
|
-
> 📧 alice@example.com
|
|
34
|
-
> 🔗 블로그: alice.dev
|
|
35
|
-
> 💬 카카오: open.kakao.com/o/abc123
|
|
36
|
-
> 👤 relayax.com/@alice
|
|
37
|
-
|
|
38
|
-
- `welcome`이 없으면 💬 줄을 생략합니다.
|
|
39
|
-
- 연락처의 type에 맞는 이모지: 📧 email, 💬 kakao, 🐦 x, 💼 linkedin, 💻 github, 🔗 website/custom
|
|
40
|
-
- 연락처가 여러 개면 각각 한 줄씩 표시합니다.
|
|
41
|
-
- `author`가 null이면 명함 블록 전체를 생략합니다.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
### 에러 처리 가이드
|
|
2
|
-
|
|
3
|
-
CLI 명령 실행 후 JSON 에러가 반환되면 아래 기준에 따라 처리합니다.
|
|
4
|
-
**원칙: "되돌릴 수 없는 영향이 있는가?"로 판단합니다.**
|
|
5
|
-
|
|
6
|
-
#### 1. 자동 해결 (사용자에게 물어보지 않음)
|
|
7
|
-
되돌릴 수 있고, 부작용 없는 에러:
|
|
8
|
-
|
|
9
|
-
| 에러 코드 | 행동 |
|
|
10
|
-
|-----------|------|
|
|
11
|
-
| `LOGIN_REQUIRED` / `NO_TOKEN` | `relay login` 실행 (timeout 300초, 브라우저 자동 열림) → 성공 후 원래 명령 재시도 |
|
|
12
|
-
| `NOT_INITIALIZED` | `relay init --all --json` 실행 → 원래 명령 재시도 |
|
|
13
|
-
| `FETCH_FAILED` | 3초 대기 후 원래 명령 재시도 (최대 2회). 2회 실패 시 사용자에게 안내 |
|
|
14
|
-
|
|
15
|
-
#### 2. 사용자에게 선택지 제시 (AskUserQuestion)
|
|
16
|
-
`options` 필드가 있는 에러:
|
|
17
|
-
|
|
18
|
-
| 에러 코드 | 행동 |
|
|
19
|
-
|-----------|------|
|
|
20
|
-
| `MISSING_VISIBILITY` | options의 label을 선택지로 AskUserQuestion 호출 |
|
|
21
|
-
| `MISSING_FIELD` | fix 안내 + 사용자에게 값 입력 요청 |
|
|
22
|
-
| `MISSING_TOOLS` | options의 감지된 도구 목록을 선택지로 AskUserQuestion 호출 |
|
|
23
|
-
| `MISSING_SPACE` | options의 Space 목록을 선택지로 AskUserQuestion 호출 |
|
|
24
|
-
|
|
25
|
-
사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
|
|
26
|
-
|
|
27
|
-
#### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
|
|
28
|
-
구매, 접근 권한, 보안 관련:
|
|
29
|
-
|
|
30
|
-
| 에러 코드 | 행동 |
|
|
31
|
-
|-----------|------|
|
|
32
|
-
| `GATED_ACCESS_REQUIRED` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" AskUserQuestion |
|
|
33
|
-
| `SPACE_ONLY` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" AskUserQuestion |
|
|
34
|
-
| `APPROVAL_REQUIRED` | 승인 대기 안내 |
|
|
35
|
-
| `NO_ACCESS` | 접근 방법 안내 |
|
|
36
|
-
|
|
37
|
-
#### 4. 그 외 에러
|
|
38
|
-
`fix` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.
|