relayax-cli 0.3.66 → 0.4.12

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.
@@ -0,0 +1,144 @@
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
+ }
@@ -2,3 +2,8 @@ export declare function downloadPackage(url: string, destDir: string): Promise<s
2
2
  export declare function extractPackage(tarPath: string, destDir: string): Promise<void>;
3
3
  export declare function makeTempDir(): string;
4
4
  export declare function removeTempDir(dir: string): void;
5
+ /**
6
+ * Clone an agent from git URL to destination directory.
7
+ * Replaces downloadPackage() + extractPackage() for git-based agents.
8
+ */
9
+ export declare function clonePackage(gitUrl: string, destDir: string, version?: string): Promise<void>;
@@ -7,6 +7,7 @@ exports.downloadPackage = downloadPackage;
7
7
  exports.extractPackage = extractPackage;
8
8
  exports.makeTempDir = makeTempDir;
9
9
  exports.removeTempDir = removeTempDir;
10
+ exports.clonePackage = clonePackage;
10
11
  const fs_1 = __importDefault(require("fs"));
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const os_1 = __importDefault(require("os"));
@@ -14,6 +15,7 @@ const fs_2 = require("fs");
14
15
  const promises_1 = require("stream/promises");
15
16
  const stream_1 = require("stream");
16
17
  const tar_1 = require("tar");
18
+ const git_operations_js_1 = require("./git-operations.js");
17
19
  async function downloadPackage(url, destDir) {
18
20
  const res = await fetch(url);
19
21
  if (!res.ok) {
@@ -41,3 +43,10 @@ function makeTempDir() {
41
43
  function removeTempDir(dir) {
42
44
  fs_1.default.rmSync(dir, { recursive: true, force: true });
43
45
  }
46
+ /**
47
+ * Clone an agent from git URL to destination directory.
48
+ * Replaces downloadPackage() + extractPackage() for git-based agents.
49
+ */
50
+ async function clonePackage(gitUrl, destDir, version) {
51
+ await (0, git_operations_js_1.gitInstall)(gitUrl, destDir, version);
52
+ }
@@ -12,6 +12,7 @@ const config_js_1 = require("../lib/config.js");
12
12
  const api_js_1 = require("../lib/api.js");
13
13
  const slug_js_1 = require("../lib/slug.js");
14
14
  const storage_js_1 = require("../lib/storage.js");
15
+ const git_operations_js_1 = require("../lib/git-operations.js");
15
16
  const ai_tools_js_1 = require("../lib/ai-tools.js");
16
17
  const preamble_js_1 = require("../lib/preamble.js");
17
18
  const installer_js_1 = require("../lib/installer.js");
@@ -124,38 +125,56 @@ function createMcpServer() {
124
125
  }
125
126
  const tempDir = (0, storage_js_1.makeTempDir)();
126
127
  try {
127
- const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
128
128
  const agentDir = path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
129
- if (fs_1.default.existsSync(agentDir))
130
- fs_1.default.rmSync(agentDir, { recursive: true, force: true });
131
- fs_1.default.mkdirSync(agentDir, { recursive: true });
132
- await (0, storage_js_1.extractPackage)(tarPath, agentDir);
129
+ if (agent.git_url) {
130
+ // Git clone path
131
+ (0, git_operations_js_1.checkGitInstalled)();
132
+ await (0, storage_js_1.clonePackage)(agent.git_url, agentDir);
133
+ }
134
+ else {
135
+ // Legacy tar.gz path
136
+ const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
137
+ if (fs_1.default.existsSync(agentDir))
138
+ fs_1.default.rmSync(agentDir, { recursive: true, force: true });
139
+ fs_1.default.mkdirSync(agentDir, { recursive: true });
140
+ await (0, storage_js_1.extractPackage)(tarPath, agentDir);
141
+ }
133
142
  (0, preamble_js_1.injectPreambleToAgent)(agentDir, fullSlug);
134
143
  const installed = (0, config_js_1.loadInstalled)();
135
144
  installed[fullSlug] = { agent_id: agent.id, version: agent.version, installed_at: new Date().toISOString(), files: [agentDir] };
136
145
  (0, config_js_1.saveInstalled)(installed);
137
146
  await (0, api_js_1.reportInstall)(agent.id, fullSlug, agent.version);
138
147
  (0, api_js_1.sendUsagePing)(agent.id, fullSlug, agent.version);
139
- // relay.yaml에서 tags, requires 읽기 (scope 판단용)
148
+ // relay.yaml에서 tags, requires, recommended_scope 읽기
140
149
  let agentTags = [];
141
150
  let agentRequires = null;
142
151
  let hasRules = false;
152
+ let recommendedScope;
143
153
  try {
144
154
  const relayYamlPath = path_1.default.join(agentDir, 'relay.yaml');
145
155
  if (fs_1.default.existsSync(relayYamlPath)) {
146
156
  const cfg = js_yaml_1.default.load(fs_1.default.readFileSync(relayYamlPath, 'utf-8'));
147
157
  agentTags = cfg.tags ?? [];
148
158
  agentRequires = cfg.requires ?? null;
159
+ if (cfg.recommended_scope === 'global' || cfg.recommended_scope === 'local') {
160
+ recommendedScope = cfg.recommended_scope;
161
+ }
149
162
  }
150
163
  hasRules = fs_1.default.existsSync(path_1.default.join(agentDir, 'rules')) && fs_1.default.readdirSync(path_1.default.join(agentDir, 'rules')).length > 0;
151
164
  }
152
165
  catch { /* non-critical */ }
166
+ // recommended_scope가 relay.yaml에 없으면 휴리스틱으로 추론
167
+ if (!recommendedScope) {
168
+ const frameworkTags = ['nextjs', 'react', 'vue', 'angular', 'svelte', 'nuxt', 'remix', 'astro', 'django', 'rails', 'laravel', 'spring', 'express', 'fastapi', 'flask'];
169
+ recommendedScope = (hasRules || agentTags.some((t) => frameworkTags.includes(t.toLowerCase()))) ? 'local' : 'global';
170
+ }
153
171
  const cliUpdate = await getCliUpdateWarning();
154
172
  return { content: [jsonTextWithUpdate({
155
173
  status: 'ok', agent: agent.name, slug: fullSlug, version: agent.version,
156
174
  description: agent.description ?? '', tags: agentTags, requires: agentRequires, has_rules: hasRules,
175
+ recommended_scope: recommendedScope,
157
176
  files: countFiles(agentDir), install_path: agentDir,
158
- scope_hint: '설치 에이전트 성격에 따라 글로벌/로컬 배치를 사용자에게 물어보세요. tags에 특정 프레임워크가 있거나 rules/가 있으면 로컬 추천, 범용이면 글로벌 추천.',
177
+ scope_hint: `이 에이전트의 권장 배치 범위는 "${recommendedScope}"입니다. 사용자에게 확인 relay deploy --scope ${recommendedScope}로 배치하세요.`,
159
178
  }, cliUpdate)] };
160
179
  }
161
180
  finally {
@@ -1,9 +1,20 @@
1
- 에이전트 패키지를 새로 만듭니다.
2
- 콘텐츠를 분석하고, 포지셔닝하고, relay.yaml 작성한 배포합니다.
1
+ 에이전트를 만들거나 업데이트하여 relay에 배포합니다.
2
+ relay.yaml 없으면 새로 만들고, 있으면 변경사항을 반영합니다.
3
3
 
4
4
  > 빌더는 터미널 환경에서 작업합니다. CLI 명령어를 직접 실행하세요.
5
5
 
6
- ## 1. 콘텐츠 파악
6
+ ## 분기: 최초 생성 vs 업데이트
7
+
8
+ `.relay/relay.yaml`이 있는지 확인합니다.
9
+
10
+ - **없음** → 아래 "최초 생성" 플로우
11
+ - **있음** → 아래 "업데이트" 플로우
12
+
13
+ ---
14
+
15
+ ## 최초 생성 (relay.yaml 없음)
16
+
17
+ ### 1. 콘텐츠 파악
7
18
 
8
19
  `relay package --init --json`으로 소스를 스캔합니다.
9
20
  결과의 `sources[]`에서 사용자에게 어떤 콘텐츠를 포함할지 물어봅니다.
@@ -12,7 +23,7 @@
12
23
  - SKILL.md, 에이전트 파일, 커맨드 파일의 내용
13
24
  - 참조하는 스킬/에이전트 의존성
14
25
 
15
- ## 2. 포지셔닝
26
+ ### 2. 포지셔닝
16
27
 
17
28
  콘텐츠 분석을 기반으로 에이전트를 하나의 "제품"으로 포지셔닝합니다.
18
29
 
@@ -24,7 +35,7 @@
24
35
  이름(name)은 한국어 가능. slug는 영문 소문자+하이픈.
25
36
  설명은 설치자 관점으로 ("~를 자동화합니다").
26
37
 
27
- ## 3. requires 판단
38
+ ### 3. requires 판단 + 보안 점검
28
39
 
29
40
  콘텐츠 파일을 읽고 requires를 판단합니다:
30
41
 
@@ -37,26 +48,61 @@
37
48
  - **runtime**: Node.js/Python 최소 버전
38
49
  - **agents**: 의존하는 다른 relay 에이전트
39
50
 
40
- ## 4. 보안 점검
41
-
42
- 콘텐츠 파일에서 시크릿/개인정보를 확인합니다:
51
+ 보안 점검:
43
52
  - 하드코딩된 API 키, 토큰 (sk-*, ghp_*, AKIA* 등)
44
- - 이메일, 전화번호 등 개인정보
45
- - 발견 시 **반드시 경고**하고 환경변수 대체 안내
46
53
  - 파일 컨텍스트를 읽어 실제 시크릿 vs 예시 코드를 구분
54
+ - 발견 시 **반드시 경고**하고 환경변수 대체 안내
47
55
 
48
- ## 5. relay.yaml 작성 & 배포
56
+ ### 4. relay.yaml 작성 & 배포
49
57
 
50
58
  판단 결과를 relay.yaml에 반영합니다:
51
59
  - name, slug, description, version, tags
52
60
  - requires (판단 결과)
53
- - visibility, org (사용자에게 확인)
61
+ - org: `relay orgs list --json`으로 Org 목록을 조회합니다.
62
+ - Org가 있으면: 개인 배포 vs Org 배포를 사용자에게 물어봅니다.
63
+ - Org가 없으면: 개인 배포로 진행합니다.
64
+ - visibility: Org 선택 결과에 따라 옵션이 달라집니다:
65
+ - **Org 없이 배포**: `public`, `private` (2개)
66
+ - **Org에 배포**: `public`, `private`, `internal` (3개)
67
+ - `public` — 누구나 설치
68
+ - `private` — 접근 링크가 있는 사람만 설치
69
+ - `internal` — Org 멤버만 설치 (Org 배포 시에만 선택 가능)
54
70
 
55
71
  `relay publish --json`으로 배포합니다.
56
72
 
57
- ## 6. 공유 문구
73
+ ---
74
+
75
+ ## 업데이트 (relay.yaml 있음)
76
+
77
+ ### 1. 변경 사항 확인
78
+
79
+ `relay package --json`으로 현재 상태를 확인합니다.
80
+ - 변경된 콘텐츠 (modified)
81
+ - 새로 추가된 콘텐츠 (new_items)
82
+
83
+ 사용자에게 어떤 부분을 변경하려는지 물어봅니다:
84
+ - 콘텐츠 변경 반영 (sync)
85
+ - 새 스킬/커맨드 추가
86
+ - 설명/태그 개선
87
+ - requires 재분석
88
+
89
+ ### 2. 필요한 부분만 업데이트
90
+
91
+ 사용자 요청에 따라:
92
+ - **콘텐츠 추가**: 새 콘텐츠의 파일을 읽고 기능 파악 → relay.yaml의 contents에 추가
93
+ - **requires 변경**: 콘텐츠를 다시 읽고 requires 재판단
94
+ - **설명 개선**: 현재 포지셔닝을 분석하고 개선안 제안
95
+ - **보안 재점검**: 시크릿/개인정보 확인
96
+
97
+ ### 3. 배포
98
+
99
+ `relay publish --json`으로 배포합니다.
100
+ 버전 범프가 필요하면 사용자에게 patch/minor/major 중 확인합니다.
101
+
102
+ ---
103
+
104
+ ## 공유 문구
58
105
 
59
106
  배포 완료 후 `relay publish` 출력에 포함된 공유 문구를 보여줍니다.
60
- CLI가 이미 설치된 사용자를 위한 짧은 버전도 함께 표시합니다.
61
107
 
62
108
  {{ERROR_HANDLING_GUIDE}}
package/dist/types.d.ts CHANGED
@@ -32,11 +32,14 @@ export interface AgentRegistryInfo {
32
32
  description?: string;
33
33
  version: string;
34
34
  package_url: string;
35
+ git_url?: string;
35
36
  commands: {
36
37
  name: string;
37
38
  description: string;
38
39
  }[];
39
40
  type?: 'command' | 'passive' | 'hybrid';
41
+ /** 에이전트 제작자가 권장하는 배치 범위 */
42
+ recommended_scope?: 'global' | 'local';
40
43
  agent_details?: {
41
44
  name: string;
42
45
  description: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.3.66",
3
+ "version": "0.4.12",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {