relayax-cli 0.3.67 → 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.
- package/README.md +156 -24
- package/dist/commands/create.js +13 -1
- package/dist/commands/diff.js +38 -18
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +106 -0
- package/dist/commands/install.js +31 -23
- package/dist/commands/publish.js +77 -0
- package/dist/commands/update.js +24 -6
- package/dist/index.js +2 -0
- package/dist/lib/git-operations.d.ts +38 -0
- package/dist/lib/git-operations.js +168 -0
- package/dist/lib/guide.js +9 -0
- package/dist/lib/manifest-generator.d.ts +20 -0
- package/dist/lib/manifest-generator.js +144 -0
- package/dist/lib/storage.d.ts +5 -0
- package/dist/lib/storage.js +9 -0
- package/dist/mcp/server.js +26 -7
- package/dist/types.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,42 +1,174 @@
|
|
|
1
|
-
# relay
|
|
1
|
+
# relay
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The package manager for AI agents.**
|
|
4
|
+
|
|
5
|
+
Write once, run on any harness. One command install. Built-in usage analytics.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx relayax-cli install @gstack/code-review
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
╭──────────────────────────────────────────────╮
|
|
13
|
+
│ │
|
|
14
|
+
│ relay — AI agent distribution for humans │
|
|
15
|
+
│ and machines. │
|
|
16
|
+
│ │
|
|
17
|
+
│ ✓ installed @gstack/code-review (v2.1.0) │
|
|
18
|
+
│ 3 skills, 1 agent ready │
|
|
19
|
+
│ │
|
|
20
|
+
╰──────────────────────────────────────────────╯
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
AI agents are stuck in silos. You build an agent for Claude Code — it doesn't work on OpenClaw. You share it on GitHub — no one knows it exists. You have no idea if anyone actually uses it.
|
|
28
|
+
|
|
29
|
+
**Relay fixes this.**
|
|
30
|
+
|
|
31
|
+
| Problem | Relay |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Agents locked to one harness | Cross-harness compatibility (Claude, OpenClaw, nanoclaw) |
|
|
34
|
+
| No distribution channel | `relay install` — one command, done |
|
|
35
|
+
| Zero feedback from users | Built-in analytics — see which skills get used |
|
|
36
|
+
|
|
37
|
+
---
|
|
4
38
|
|
|
5
39
|
## Quick Start
|
|
6
40
|
|
|
7
41
|
```bash
|
|
8
|
-
#
|
|
9
|
-
npx
|
|
42
|
+
# Install an agent — no setup required
|
|
43
|
+
npx relayax-cli install @author/agent-name
|
|
10
44
|
|
|
11
|
-
#
|
|
12
|
-
npm
|
|
13
|
-
relay install @author/
|
|
45
|
+
# Or install globally
|
|
46
|
+
npm i -g relayax-cli
|
|
47
|
+
relay install @author/agent-name
|
|
14
48
|
```
|
|
15
49
|
|
|
50
|
+
That's it. The agent is ready in your `.relay/agents/` directory, compatible with your harness.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## For Agent Builders
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Publish your agent to the registry
|
|
58
|
+
relay publish
|
|
59
|
+
|
|
60
|
+
# See who's using it
|
|
61
|
+
relay status --analytics
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Relay tracks skill-level usage out of the box. No extra setup. You'll know exactly which skills land and which don't — so you can ship better agents, faster.
|
|
65
|
+
|
|
66
|
+
### Package Format
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# team.yaml
|
|
70
|
+
name: code-review
|
|
71
|
+
version: 2.1.0
|
|
72
|
+
harness:
|
|
73
|
+
- claude
|
|
74
|
+
- openclaw
|
|
75
|
+
- nanoclaw
|
|
76
|
+
agents:
|
|
77
|
+
- name: reviewer
|
|
78
|
+
type: passive
|
|
79
|
+
skills:
|
|
80
|
+
- name: review-pr
|
|
81
|
+
- name: security-check
|
|
82
|
+
- name: style-lint
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
One spec. Every harness.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
16
89
|
## Commands
|
|
17
90
|
|
|
18
|
-
| Command |
|
|
19
|
-
|
|
20
|
-
| `relay
|
|
21
|
-
| `relay search <keyword>` |
|
|
22
|
-
| `relay
|
|
23
|
-
| `relay list` |
|
|
24
|
-
| `relay
|
|
91
|
+
| Command | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `relay install <name>` | Install an agent |
|
|
94
|
+
| `relay search <keyword>` | Find agents in the registry |
|
|
95
|
+
| `relay publish` | Publish your agent |
|
|
96
|
+
| `relay list` | List installed agents |
|
|
97
|
+
| `relay status` | Check environment + analytics |
|
|
98
|
+
| `relay update` | Update agents to latest |
|
|
99
|
+
| `relay uninstall <name>` | Remove an agent |
|
|
100
|
+
| `relay diff <name>` | See what changed between versions |
|
|
25
101
|
|
|
26
|
-
|
|
102
|
+
All output is JSON by default (for AI agents). Add `--pretty` for human-readable format.
|
|
27
103
|
|
|
28
|
-
|
|
29
|
-
- `--json` - JSON 출력 (기본값, 에이전트 친화적)
|
|
104
|
+
---
|
|
30
105
|
|
|
31
|
-
##
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
relay install @team/agent
|
|
110
|
+
│
|
|
111
|
+
╭──────────┴──────────╮
|
|
112
|
+
│ Relay Registry │
|
|
113
|
+
│ (relay.ax cloud) │
|
|
114
|
+
╰──────────┬──────────╯
|
|
115
|
+
│
|
|
116
|
+
╭──────────┴──────────╮
|
|
117
|
+
│ relay agent spec │
|
|
118
|
+
│ (universal format) │
|
|
119
|
+
╰──┬───────┬───────┬──╯
|
|
120
|
+
│ │ │
|
|
121
|
+
┌─────┴─┐ ┌──┴───┐ ┌─┴──────┐
|
|
122
|
+
│Claude │ │Open │ │nano │
|
|
123
|
+
│ Code │ │Claw │ │claw │
|
|
124
|
+
└───────┘ └──────┘ └────────┘
|
|
125
|
+
```
|
|
32
126
|
|
|
33
|
-
|
|
127
|
+
Relay resolves the right format for your harness automatically. Builders write one spec, users install with one command.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## AI-Native
|
|
132
|
+
|
|
133
|
+
Relay is built for AI agents as first-class users. The CLI outputs structured JSON so agents can search, install, and manage other agents autonomously.
|
|
34
134
|
|
|
35
135
|
```bash
|
|
36
|
-
#
|
|
37
|
-
relay search "
|
|
136
|
+
# An agent searching for tools
|
|
137
|
+
relay search "database migration" | jq '.results[].slug'
|
|
138
|
+
|
|
139
|
+
# An agent installing what it needs
|
|
140
|
+
relay install @tools/db-migrate
|
|
141
|
+
# → {"status":"ok","agent":"db-migrate","skills":["migrate","rollback","seed"]}
|
|
142
|
+
```
|
|
38
143
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
144
|
+
Relay also ships as an **MCP server**, so any MCP-compatible agent can use it directly:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
relay mcp
|
|
42
148
|
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Open Core
|
|
153
|
+
|
|
154
|
+
The CLI and agent spec are open source (MIT). Build agents, publish them, self-host your own registry — no vendor lock-in.
|
|
155
|
+
|
|
156
|
+
[relay.ax](https://relayax.com) provides the hosted registry with:
|
|
157
|
+
- Private agent hosting
|
|
158
|
+
- Organization management & access control
|
|
159
|
+
- Usage analytics dashboard
|
|
160
|
+
- Enterprise SSO & audit logs
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Community
|
|
165
|
+
|
|
166
|
+
- [25+ production agents](https://relayax.com) ready to install
|
|
167
|
+
- [Builder docs](https://relayax.com/docs) for creating your own
|
|
168
|
+
- [Discord](#) for help and discussion
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
<p align="center">
|
|
173
|
+
<sub>Built by <a href="https://relayax.com">RelayAX</a></sub>
|
|
174
|
+
</p>
|
package/dist/commands/create.js
CHANGED
|
@@ -143,7 +143,14 @@ function registerCreate(program) {
|
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
// 3.
|
|
146
|
+
// 3. recommended_scope 자동 추천
|
|
147
|
+
// rules/ 존재 or 프레임워크 태그 → local, 그 외 → global
|
|
148
|
+
const frameworkTags = ['nextjs', 'react', 'vue', 'angular', 'svelte', 'nuxt', 'remix', 'astro', 'django', 'rails', 'laravel', 'spring', 'express', 'fastapi', 'flask'];
|
|
149
|
+
const hasRules = fs_1.default.existsSync(path_1.default.join(projectPath, '.relay', 'rules'))
|
|
150
|
+
|| fs_1.default.existsSync(path_1.default.join(projectPath, 'rules'));
|
|
151
|
+
const hasFrameworkTag = tags.some((t) => frameworkTags.includes(t.toLowerCase()));
|
|
152
|
+
const recommendedScope = (hasRules || hasFrameworkTag) ? 'local' : 'global';
|
|
153
|
+
// 4. .relay/relay.yaml 생성
|
|
147
154
|
fs_1.default.mkdirSync(relayDir, { recursive: true });
|
|
148
155
|
const yamlData = {
|
|
149
156
|
name,
|
|
@@ -151,6 +158,7 @@ function registerCreate(program) {
|
|
|
151
158
|
description,
|
|
152
159
|
version: '1.0.0',
|
|
153
160
|
type: 'hybrid',
|
|
161
|
+
recommended_scope: recommendedScope,
|
|
154
162
|
tags,
|
|
155
163
|
visibility,
|
|
156
164
|
contents: [],
|
|
@@ -187,6 +195,7 @@ function registerCreate(program) {
|
|
|
187
195
|
status: 'ok',
|
|
188
196
|
name,
|
|
189
197
|
slug: slug,
|
|
198
|
+
recommended_scope: recommendedScope,
|
|
190
199
|
relay_yaml: 'created',
|
|
191
200
|
directories: createdDirs,
|
|
192
201
|
local_commands: localResults,
|
|
@@ -194,8 +203,11 @@ function registerCreate(program) {
|
|
|
194
203
|
}));
|
|
195
204
|
}
|
|
196
205
|
else {
|
|
206
|
+
const scopeLabel = recommendedScope === 'global' ? '\x1b[32m글로벌\x1b[0m' : '\x1b[33m로컬\x1b[0m';
|
|
207
|
+
const scopeReason = hasRules ? 'rules/ 감지' : hasFrameworkTag ? '프레임워크 태그 감지' : '범용 에이전트';
|
|
197
208
|
console.log(`\n\x1b[32m✓ ${name} 에이전트 프로젝트 생성 완료\x1b[0m\n`);
|
|
198
209
|
console.log(` .relay/relay.yaml 생성됨`);
|
|
210
|
+
console.log(` recommended_scope: ${scopeLabel} (${scopeReason})`);
|
|
199
211
|
if (createdDirs.length > 0) {
|
|
200
212
|
console.log(` 디렉토리 생성: ${createdDirs.join(', ')}`);
|
|
201
213
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.registerDiff = registerDiff;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
4
10
|
const api_js_1 = require("../lib/api.js");
|
|
5
11
|
const slug_js_1 = require("../lib/slug.js");
|
|
6
|
-
const
|
|
12
|
+
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
7
13
|
function registerDiff(program) {
|
|
8
14
|
program
|
|
9
15
|
.command('diff <slug> <v1> <v2>')
|
|
@@ -13,6 +19,7 @@ function registerDiff(program) {
|
|
|
13
19
|
try {
|
|
14
20
|
const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
15
21
|
const versions = await (0, api_js_1.fetchAgentVersions)(resolved.full);
|
|
22
|
+
const info = await (0, api_js_1.fetchAgentInfo)(resolved.full);
|
|
16
23
|
const ver1 = versions.find((v) => v.version === v1);
|
|
17
24
|
const ver2 = versions.find((v) => v.version === v2);
|
|
18
25
|
if (!ver1 || !ver2) {
|
|
@@ -21,19 +28,36 @@ function registerDiff(program) {
|
|
|
21
28
|
if (!json) {
|
|
22
29
|
console.log(`\n\x1b[1m${resolved.full}\x1b[0m v${v1} ↔ v${v2} 비교 중...\n`);
|
|
23
30
|
}
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// Use git diff if git_url is available
|
|
32
|
+
if (info.git_url) {
|
|
33
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
34
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-diff-'));
|
|
35
|
+
try {
|
|
36
|
+
(0, git_operations_js_1.gitClone)(info.git_url, tempDir);
|
|
37
|
+
const diffOutput = (0, git_operations_js_1.gitDiff)(tempDir, `v${v1}`, `v${v2}`);
|
|
38
|
+
if (json) {
|
|
39
|
+
console.log(JSON.stringify({
|
|
40
|
+
slug: resolved.full,
|
|
41
|
+
v1: { version: v1, created_at: ver1.created_at, changelog: ver1.changelog },
|
|
42
|
+
v2: { version: v2, created_at: ver2.created_at, changelog: ver2.changelog },
|
|
43
|
+
diff: diffOutput,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (diffOutput.trim()) {
|
|
48
|
+
console.log(diffOutput);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(' 변경 사항이 없습니다.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
|
-
|
|
55
|
+
finally {
|
|
56
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Fallback: show version info only (no git URL available)
|
|
37
61
|
if (json) {
|
|
38
62
|
console.log(JSON.stringify({
|
|
39
63
|
slug: resolved.full,
|
|
@@ -50,13 +74,9 @@ function registerDiff(program) {
|
|
|
50
74
|
if (ver2.changelog)
|
|
51
75
|
console.log(` ${ver2.changelog}`);
|
|
52
76
|
console.log();
|
|
53
|
-
console.log(`\x1b[33m
|
|
77
|
+
console.log(`\x1b[33m git 기반 diff는 새로 배포된 에이전트에서만 지원됩니다.\x1b[0m`);
|
|
54
78
|
}
|
|
55
79
|
}
|
|
56
|
-
finally {
|
|
57
|
-
(0, storage_js_1.removeTempDir)(tempDir1);
|
|
58
|
-
(0, storage_js_1.removeTempDir)(tempDir2);
|
|
59
|
-
}
|
|
60
80
|
}
|
|
61
81
|
catch (err) {
|
|
62
82
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -0,0 +1,106 @@
|
|
|
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.registerExport = registerExport;
|
|
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 manifest_generator_js_1 = require("../lib/manifest-generator.js");
|
|
11
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
12
|
+
function registerExport(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('export <platform>')
|
|
15
|
+
.description('로컬에서 플랫폼 네이티브 매니페스트를 생성합니다')
|
|
16
|
+
.option('--out <dir>', '출력 디렉토리 (기본: .relay/export/<platform>/)')
|
|
17
|
+
.option('--project <dir>', '프로젝트 루트 경로')
|
|
18
|
+
.action(async (platform, opts) => {
|
|
19
|
+
const json = program.opts().json ?? false;
|
|
20
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
21
|
+
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
22
|
+
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
23
|
+
// Check relay.yaml exists
|
|
24
|
+
if (!fs_1.default.existsSync(relayYamlPath)) {
|
|
25
|
+
const msg = 'relay.yaml not found';
|
|
26
|
+
if (json) {
|
|
27
|
+
console.error(JSON.stringify({ error: 'NOT_FOUND', message: msg }));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.error(`\x1b[31m${msg}\x1b[0m`);
|
|
31
|
+
console.error(' relay.yaml이 있는 에이전트 디렉토리에서 실행하세요.');
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// Validate platform
|
|
36
|
+
const validPlatforms = [...manifest_generator_js_1.SUPPORTED_PLATFORMS, 'all'];
|
|
37
|
+
if (!validPlatforms.includes(platform)) {
|
|
38
|
+
const msg = `지원하지 않는 플랫폼: ${platform}`;
|
|
39
|
+
if (json) {
|
|
40
|
+
console.error(JSON.stringify({ error: 'INVALID_PLATFORM', message: msg, supported: manifest_generator_js_1.SUPPORTED_PLATFORMS }));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(`\x1b[31m${msg}\x1b[0m`);
|
|
44
|
+
console.error(` 지원 플랫폼: ${manifest_generator_js_1.SUPPORTED_PLATFORMS.join(', ')}, all`);
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Parse relay.yaml
|
|
49
|
+
const yamlContent = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
|
|
50
|
+
const raw = js_yaml_1.default.load(yamlContent) ?? {};
|
|
51
|
+
const manifestYaml = {
|
|
52
|
+
name: String(raw.name ?? ''),
|
|
53
|
+
slug: String(raw.slug ?? ''),
|
|
54
|
+
description: String(raw.description ?? ''),
|
|
55
|
+
version: String(raw.version ?? '1.0.0'),
|
|
56
|
+
source: raw.source ? String(raw.source) : undefined,
|
|
57
|
+
org_slug: raw.org_slug ? String(raw.org_slug) : undefined,
|
|
58
|
+
platforms: platform === 'all' ? undefined : [platform],
|
|
59
|
+
};
|
|
60
|
+
// Generate manifests
|
|
61
|
+
const files = (0, manifest_generator_js_1.generateManifests)(manifestYaml, relayDir);
|
|
62
|
+
if (files.length === 0) {
|
|
63
|
+
if (json) {
|
|
64
|
+
console.log(JSON.stringify({ status: 'empty', message: '생성할 매니페스트가 없습니다.' }));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log('생성할 매니페스트가 없습니다.');
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Determine output directory
|
|
72
|
+
const outDir = opts.out
|
|
73
|
+
? path_1.default.resolve(opts.out)
|
|
74
|
+
: path_1.default.join(relayDir, 'export', platform);
|
|
75
|
+
fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
76
|
+
// Write files
|
|
77
|
+
const written = [];
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
const filePath = path_1.default.join(outDir, file.relativePath);
|
|
80
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
81
|
+
fs_1.default.writeFileSync(filePath, file.content);
|
|
82
|
+
written.push(file.relativePath);
|
|
83
|
+
}
|
|
84
|
+
if (json) {
|
|
85
|
+
console.log(JSON.stringify({ status: 'ok', platform, output_dir: outDir, files: written }));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(`\n\x1b[32m✓ 매니페스트 생성 완료\x1b[0m (${platform})`);
|
|
89
|
+
console.log(` 출력: \x1b[36m${outDir}\x1b[0m\n`);
|
|
90
|
+
for (const f of written) {
|
|
91
|
+
console.log(` \x1b[90m•\x1b[0m ${f}`);
|
|
92
|
+
}
|
|
93
|
+
// Platform-specific usage hints
|
|
94
|
+
console.log('');
|
|
95
|
+
if (platform === 'claude-code' || platform === 'all') {
|
|
96
|
+
console.log(' \x1b[90mClaude Code:\x1b[0m /plugin marketplace add <marketplace.json URL>');
|
|
97
|
+
}
|
|
98
|
+
if (platform === 'codex' || platform === 'all') {
|
|
99
|
+
console.log(' \x1b[90mCodex:\x1b[0m .codex-plugin/plugin.json을 Codex에 등록하세요');
|
|
100
|
+
}
|
|
101
|
+
if (platform === 'antigravity' || platform === 'all') {
|
|
102
|
+
console.log(' \x1b[90mAntigravity:\x1b[0m .agent/skills/를 프로젝트에 복사하세요');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -9,6 +9,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const api_js_1 = require("../lib/api.js");
|
|
11
11
|
const storage_js_1 = require("../lib/storage.js");
|
|
12
|
+
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
12
13
|
const config_js_1 = require("../lib/config.js");
|
|
13
14
|
const slug_js_1 = require("../lib/slug.js");
|
|
14
15
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
@@ -212,11 +213,10 @@ function registerInstall(program) {
|
|
|
212
213
|
throw new Error('에이전트 정보를 가져오지 못했습니다.');
|
|
213
214
|
// Re-bind as non-optional so TypeScript tracks the narrowing through nested scopes
|
|
214
215
|
let resolvedAgent = agent;
|
|
215
|
-
// Scope 자동결정: --global/--local 플래그 > agent_type 기반
|
|
216
|
+
// Scope 자동결정: --global/--local 플래그 > recommended_scope > agent_type 기반
|
|
216
217
|
const scope = _opts.global ? 'global'
|
|
217
218
|
: _opts.local ? 'local'
|
|
218
|
-
: resolvedAgent.type === 'passive' ? 'local'
|
|
219
|
-
: 'global';
|
|
219
|
+
: resolvedAgent.recommended_scope ?? (resolvedAgent.type === 'passive' ? 'local' : 'global');
|
|
220
220
|
const agentDir = scope === 'global'
|
|
221
221
|
? path_1.default.join(os_1.default.homedir(), '.relay', 'agents', parsed.owner, parsed.name)
|
|
222
222
|
: path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
@@ -240,31 +240,39 @@ function registerInstall(program) {
|
|
|
240
240
|
process.exit(1);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
// 3. Download package
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
243
|
+
// 3. Download package: prefer git clone, fallback to tar.gz
|
|
244
|
+
const requestedVersion = versionMatch ? versionMatch[2] : undefined;
|
|
245
|
+
if (resolvedAgent.git_url) {
|
|
246
|
+
// Git clone path
|
|
247
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
248
|
+
const gitUrl = (0, git_operations_js_1.buildGitUrl)(resolvedAgent.git_url, { code: _opts.code });
|
|
249
|
+
await (0, storage_js_1.clonePackage)(gitUrl, agentDir, requestedVersion);
|
|
247
250
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (!json) {
|
|
253
|
-
console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
|
|
254
|
-
}
|
|
255
|
-
resolvedAgent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
251
|
+
else {
|
|
252
|
+
// Legacy tar.gz path (retry once if signed URL expired)
|
|
253
|
+
let tarPath;
|
|
254
|
+
try {
|
|
256
255
|
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
257
256
|
}
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
catch (dlErr) {
|
|
258
|
+
const dlMsg = dlErr instanceof Error ? dlErr.message : String(dlErr);
|
|
259
|
+
if (dlMsg.includes('403') || dlMsg.includes('expired')) {
|
|
260
|
+
if (!json) {
|
|
261
|
+
console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
|
|
262
|
+
}
|
|
263
|
+
resolvedAgent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
264
|
+
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
throw dlErr;
|
|
268
|
+
}
|
|
260
269
|
}
|
|
270
|
+
if (fs_1.default.existsSync(agentDir)) {
|
|
271
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
272
|
+
}
|
|
273
|
+
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
274
|
+
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
261
275
|
}
|
|
262
|
-
// 4. Extract to .relay/agents/<slug>/
|
|
263
|
-
if (fs_1.default.existsSync(agentDir)) {
|
|
264
|
-
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
265
|
-
}
|
|
266
|
-
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
267
|
-
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
268
276
|
// 4.5. Inject preamble (update check) into SKILL.md and commands
|
|
269
277
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
270
278
|
// 5. Deploy symlinks to detected AI tool directories
|
package/dist/commands/publish.js
CHANGED
|
@@ -17,6 +17,8 @@ const version_check_js_1 = require("../lib/version-check.js");
|
|
|
17
17
|
const paths_js_1 = require("../lib/paths.js");
|
|
18
18
|
const error_report_js_1 = require("../lib/error-report.js");
|
|
19
19
|
const step_tracker_js_1 = require("../lib/step-tracker.js");
|
|
20
|
+
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
21
|
+
const manifest_generator_js_1 = require("../lib/manifest-generator.js");
|
|
20
22
|
// GUIDE_INSTRUCTION removed — share text now uses npx install command directly
|
|
21
23
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
22
24
|
const cliPkg = require('../../package.json');
|
|
@@ -50,6 +52,7 @@ function parseRelayYaml(content) {
|
|
|
50
52
|
type,
|
|
51
53
|
source: raw.source ? String(raw.source) : undefined,
|
|
52
54
|
org_slug: raw.org_slug ? String(raw.org_slug) : undefined,
|
|
55
|
+
platforms: Array.isArray(raw.platforms) ? raw.platforms.map((p) => String(p)) : undefined,
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
function detectCommands(agentDir) {
|
|
@@ -696,6 +699,46 @@ function registerPublish(program) {
|
|
|
696
699
|
const entrySlug = config.slug.startsWith('@') ? config.slug.slice(1) : config.slug;
|
|
697
700
|
const entryFileName = entrySlug.replace('/', '-') + '.md';
|
|
698
701
|
fs_1.default.writeFileSync(path_1.default.join(commandsDir, entryFileName), entryContent);
|
|
702
|
+
// Check git is available
|
|
703
|
+
try {
|
|
704
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
705
|
+
}
|
|
706
|
+
catch (gitErr) {
|
|
707
|
+
const gitMsg = gitErr instanceof Error ? gitErr.message : String(gitErr);
|
|
708
|
+
(0, error_report_js_1.reportCliError)('publish', 'GIT_NOT_FOUND', gitMsg);
|
|
709
|
+
if (json) {
|
|
710
|
+
console.error(JSON.stringify({ error: 'GIT_NOT_FOUND', message: gitMsg }));
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
console.error(`\x1b[31m${gitMsg}\x1b[0m`);
|
|
714
|
+
}
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
// Generate platform manifests (after preamble/command, before git commit)
|
|
718
|
+
const manifestYaml = {
|
|
719
|
+
name: config.name,
|
|
720
|
+
slug: config.slug,
|
|
721
|
+
description: config.description,
|
|
722
|
+
version: config.version,
|
|
723
|
+
source: config.source,
|
|
724
|
+
org_slug: config.org_slug ?? selectedOrgSlug,
|
|
725
|
+
platforms: config.platforms,
|
|
726
|
+
};
|
|
727
|
+
const manifestFiles = (0, manifest_generator_js_1.generateManifests)(manifestYaml, relayDir);
|
|
728
|
+
for (const mf of manifestFiles) {
|
|
729
|
+
const mfPath = path_1.default.join(relayDir, mf.relativePath);
|
|
730
|
+
fs_1.default.mkdirSync(path_1.default.dirname(mfPath), { recursive: true });
|
|
731
|
+
fs_1.default.writeFileSync(mfPath, mf.content);
|
|
732
|
+
}
|
|
733
|
+
const generatedPlatforms = [...new Set(manifestFiles.map((f) => {
|
|
734
|
+
if (f.relativePath.startsWith('.claude-plugin/') || f.relativePath === 'marketplace.json')
|
|
735
|
+
return 'claude-code';
|
|
736
|
+
if (f.relativePath.startsWith('.codex-plugin/'))
|
|
737
|
+
return 'codex';
|
|
738
|
+
if (f.relativePath.startsWith('.agent/'))
|
|
739
|
+
return 'antigravity';
|
|
740
|
+
return 'unknown';
|
|
741
|
+
}))];
|
|
699
742
|
let tarPath = null;
|
|
700
743
|
try {
|
|
701
744
|
tarPath = await createTarball(relayDir);
|
|
@@ -703,6 +746,29 @@ function registerPublish(program) {
|
|
|
703
746
|
console.error(`업로드 중...`);
|
|
704
747
|
}
|
|
705
748
|
const result = await publishToApi(token, tarPath, metadata);
|
|
749
|
+
// Git push: commit and push to git server (non-fatal if git server unavailable)
|
|
750
|
+
try {
|
|
751
|
+
const gitUrl = result.git_url;
|
|
752
|
+
if (gitUrl) {
|
|
753
|
+
if (!json) {
|
|
754
|
+
console.error('git 저장소에 푸시 중...');
|
|
755
|
+
}
|
|
756
|
+
const isFirstPublish = !result.is_update;
|
|
757
|
+
if (isFirstPublish) {
|
|
758
|
+
await (0, git_operations_js_1.gitPublishInit)(relayDir, gitUrl, config.version);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
await (0, git_operations_js_1.gitPublishUpdate)(relayDir, gitUrl, config.version);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
catch (gitPushErr) {
|
|
766
|
+
// Git push failure is non-fatal — tar.gz upload already succeeded
|
|
767
|
+
if (!json) {
|
|
768
|
+
const gpMsg = gitPushErr instanceof Error ? gitPushErr.message : String(gitPushErr);
|
|
769
|
+
console.error(`\x1b[33m⚠ git push 실패 (배포는 완료됨): ${gpMsg}\x1b[0m`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
706
772
|
// Update entry command preamble with scoped slug from server (non-fatal)
|
|
707
773
|
try {
|
|
708
774
|
if (result.slug && result.slug !== config.slug) {
|
|
@@ -724,6 +790,17 @@ function registerPublish(program) {
|
|
|
724
790
|
console.log(`\n\x1b[32m✓ ${config.name} 배포 완료\x1b[0m v${result.version}`);
|
|
725
791
|
console.log(` 슬러그: \x1b[36m${result.slug}\x1b[0m`);
|
|
726
792
|
console.log(` URL: \x1b[36m${result.url}\x1b[0m`);
|
|
793
|
+
// Show generated platform manifests
|
|
794
|
+
if (generatedPlatforms.length > 0) {
|
|
795
|
+
console.log(`\n \x1b[90m플랫폼 매니페스트:\x1b[0m ${generatedPlatforms.join(', ')}`);
|
|
796
|
+
}
|
|
797
|
+
// Show Claude Code plugin install command if claude-code manifest was generated
|
|
798
|
+
if (generatedPlatforms.includes('claude-code')) {
|
|
799
|
+
const pluginSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
|
|
800
|
+
const pluginUrl = `${config_js_1.API_URL}/api/registry/@${pluginSlug}/plugin`;
|
|
801
|
+
console.log(`\n \x1b[90mClaude Code 플러그인:\x1b[0m`);
|
|
802
|
+
console.log(` \x1b[36m/plugin marketplace add ${pluginUrl}\x1b[0m`);
|
|
803
|
+
}
|
|
727
804
|
// Show shareable onboarding guide as a plain copyable block
|
|
728
805
|
if (isTTY) {
|
|
729
806
|
const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
|
package/dist/commands/update.js
CHANGED
|
@@ -9,6 +9,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const api_js_1 = require("../lib/api.js");
|
|
11
11
|
const storage_js_1 = require("../lib/storage.js");
|
|
12
|
+
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
12
13
|
const installer_js_1 = require("../lib/installer.js");
|
|
13
14
|
const config_js_1 = require("../lib/config.js");
|
|
14
15
|
const slug_js_1 = require("../lib/slug.js");
|
|
@@ -76,13 +77,30 @@ function registerUpdate(program) {
|
|
|
76
77
|
const agentDir = currentScope === 'global'
|
|
77
78
|
? path_1.default.join(os_1.default.homedir(), '.relay', 'agents', owner, name)
|
|
78
79
|
: path_1.default.join(projectPath, '.relay', 'agents', owner, name);
|
|
79
|
-
// Download & extract
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
// Download & extract: prefer git, fallback to tar.gz
|
|
81
|
+
if (agent.git_url && fs_1.default.existsSync(path_1.default.join(agentDir, '.git'))) {
|
|
82
|
+
// Existing git clone — fetch + checkout latest tag
|
|
83
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
84
|
+
(0, git_operations_js_1.gitFetch)(agentDir);
|
|
85
|
+
const latestTag = (0, git_operations_js_1.gitLatestTag)(agentDir);
|
|
86
|
+
if (latestTag) {
|
|
87
|
+
(0, git_operations_js_1.gitCheckout)(agentDir, latestTag);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (agent.git_url) {
|
|
91
|
+
// No existing clone — fresh git clone
|
|
92
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
93
|
+
await (0, storage_js_1.clonePackage)(agent.git_url, agentDir);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Legacy tar.gz path
|
|
97
|
+
const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
|
|
98
|
+
if (fs_1.default.existsSync(agentDir)) {
|
|
99
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
102
|
+
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
83
103
|
}
|
|
84
|
-
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
85
|
-
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
86
104
|
// Inject preamble
|
|
87
105
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
88
106
|
// Deploy symlinks (always — handles migration from legacy deployed_files)
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const access_js_1 = require("./commands/access.js");
|
|
|
24
24
|
const grant_js_1 = require("./commands/grant.js");
|
|
25
25
|
const versions_js_1 = require("./commands/versions.js");
|
|
26
26
|
const diff_js_1 = require("./commands/diff.js");
|
|
27
|
+
const export_js_1 = require("./commands/export.js");
|
|
27
28
|
const feedback_js_1 = require("./commands/feedback.js");
|
|
28
29
|
const server_js_1 = require("./mcp/server.js");
|
|
29
30
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -56,6 +57,7 @@ program
|
|
|
56
57
|
(0, grant_js_1.registerGrant)(program);
|
|
57
58
|
(0, versions_js_1.registerVersions)(program);
|
|
58
59
|
(0, diff_js_1.registerDiff)(program);
|
|
60
|
+
(0, export_js_1.registerExport)(program);
|
|
59
61
|
(0, feedback_js_1.registerFeedback)(program);
|
|
60
62
|
program
|
|
61
63
|
.command('mcp')
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare function checkGitInstalled(): void;
|
|
2
|
+
export declare function gitInit(dir: string): void;
|
|
3
|
+
export declare function gitClone(url: string, destDir: string, opts?: {
|
|
4
|
+
depth?: number;
|
|
5
|
+
}): void;
|
|
6
|
+
export declare function gitAdd(dir: string, files?: string): void;
|
|
7
|
+
export declare function gitCommit(dir: string, message: string): void;
|
|
8
|
+
export declare function gitTag(dir: string, tag: string): void;
|
|
9
|
+
export declare function gitPush(dir: string, remote: string, refspec?: string): void;
|
|
10
|
+
export declare function gitFetch(dir: string): void;
|
|
11
|
+
export declare function gitCheckout(dir: string, ref: string): void;
|
|
12
|
+
export declare function gitDiff(dir: string, from: string, to: string): string;
|
|
13
|
+
export declare function gitLatestTag(dir: string): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Build an authenticated git URL.
|
|
16
|
+
* For public repos: https://git.relayax.com/@owner/agent.git
|
|
17
|
+
* For gated/private: https://TOKEN:x@git.relayax.com/@owner/agent.git
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildGitUrl(baseUrl: string, auth?: {
|
|
20
|
+
token?: string;
|
|
21
|
+
code?: string;
|
|
22
|
+
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* First-time publish: init → add → commit → tag → push
|
|
25
|
+
*/
|
|
26
|
+
export declare function gitPublishInit(sourceDir: string, remoteUrl: string, version: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Re-publish: clone → replace files → commit → tag → push
|
|
29
|
+
*/
|
|
30
|
+
export declare function gitPublishUpdate(sourceDir: string, remoteUrl: string, version: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Install: git clone to destination, optionally checkout a specific version.
|
|
33
|
+
*/
|
|
34
|
+
export declare function gitInstall(gitUrl: string, destDir: string, version?: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Update: fetch latest then checkout the newest tag.
|
|
37
|
+
*/
|
|
38
|
+
export declare function gitUpdate(destDir: string): Promise<string | null>;
|
|
@@ -0,0 +1,168 @@
|
|
|
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.checkGitInstalled = checkGitInstalled;
|
|
7
|
+
exports.gitInit = gitInit;
|
|
8
|
+
exports.gitClone = gitClone;
|
|
9
|
+
exports.gitAdd = gitAdd;
|
|
10
|
+
exports.gitCommit = gitCommit;
|
|
11
|
+
exports.gitTag = gitTag;
|
|
12
|
+
exports.gitPush = gitPush;
|
|
13
|
+
exports.gitFetch = gitFetch;
|
|
14
|
+
exports.gitCheckout = gitCheckout;
|
|
15
|
+
exports.gitDiff = gitDiff;
|
|
16
|
+
exports.gitLatestTag = gitLatestTag;
|
|
17
|
+
exports.buildGitUrl = buildGitUrl;
|
|
18
|
+
exports.gitPublishInit = gitPublishInit;
|
|
19
|
+
exports.gitPublishUpdate = gitPublishUpdate;
|
|
20
|
+
exports.gitInstall = gitInstall;
|
|
21
|
+
exports.gitUpdate = gitUpdate;
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
23
|
+
const fs_1 = __importDefault(require("fs"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const os_1 = __importDefault(require("os"));
|
|
26
|
+
// ─── Git Binary Check ───
|
|
27
|
+
function checkGitInstalled() {
|
|
28
|
+
try {
|
|
29
|
+
(0, child_process_1.execFileSync)('git', ['--version'], { stdio: 'pipe' });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error('git이 설치되어 있지 않습니다.\n' +
|
|
33
|
+
' macOS: xcode-select --install\n' +
|
|
34
|
+
' Ubuntu/Debian: sudo apt install git\n' +
|
|
35
|
+
' Windows: https://git-scm.com/download/win');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ─── Core Git Operations ───
|
|
39
|
+
function gitInit(dir) {
|
|
40
|
+
(0, child_process_1.execFileSync)('git', ['init'], { cwd: dir, stdio: 'pipe' });
|
|
41
|
+
}
|
|
42
|
+
function gitClone(url, destDir, opts) {
|
|
43
|
+
const args = ['clone'];
|
|
44
|
+
if (opts?.depth) {
|
|
45
|
+
args.push('--depth', String(opts.depth));
|
|
46
|
+
}
|
|
47
|
+
args.push(url, destDir);
|
|
48
|
+
(0, child_process_1.execFileSync)('git', args, { stdio: 'pipe' });
|
|
49
|
+
}
|
|
50
|
+
function gitAdd(dir, files = '.') {
|
|
51
|
+
(0, child_process_1.execFileSync)('git', ['add', files], { cwd: dir, stdio: 'pipe' });
|
|
52
|
+
}
|
|
53
|
+
function gitCommit(dir, message) {
|
|
54
|
+
// Configure committer for the temp repo
|
|
55
|
+
(0, child_process_1.execFileSync)('git', ['config', 'user.email', 'relay@relayax.com'], { cwd: dir, stdio: 'pipe' });
|
|
56
|
+
(0, child_process_1.execFileSync)('git', ['config', 'user.name', 'Relay CLI'], { cwd: dir, stdio: 'pipe' });
|
|
57
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-m', message], { cwd: dir, stdio: 'pipe' });
|
|
58
|
+
}
|
|
59
|
+
function gitTag(dir, tag) {
|
|
60
|
+
(0, child_process_1.execFileSync)('git', ['tag', tag], { cwd: dir, stdio: 'pipe' });
|
|
61
|
+
}
|
|
62
|
+
function gitPush(dir, remote, refspec) {
|
|
63
|
+
const args = ['push', remote];
|
|
64
|
+
if (refspec)
|
|
65
|
+
args.push(refspec);
|
|
66
|
+
args.push('--tags');
|
|
67
|
+
(0, child_process_1.execFileSync)('git', args, { cwd: dir, stdio: 'pipe' });
|
|
68
|
+
}
|
|
69
|
+
function gitFetch(dir) {
|
|
70
|
+
(0, child_process_1.execFileSync)('git', ['fetch', '--tags'], { cwd: dir, stdio: 'pipe' });
|
|
71
|
+
}
|
|
72
|
+
function gitCheckout(dir, ref) {
|
|
73
|
+
(0, child_process_1.execFileSync)('git', ['checkout', ref], { cwd: dir, stdio: 'pipe' });
|
|
74
|
+
}
|
|
75
|
+
function gitDiff(dir, from, to) {
|
|
76
|
+
return (0, child_process_1.execFileSync)('git', ['diff', `${from}..${to}`], {
|
|
77
|
+
cwd: dir,
|
|
78
|
+
encoding: 'utf-8',
|
|
79
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function gitLatestTag(dir) {
|
|
83
|
+
try {
|
|
84
|
+
const result = (0, child_process_1.execFileSync)('git', ['describe', '--tags', '--abbrev=0'], {
|
|
85
|
+
cwd: dir,
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
}).trim();
|
|
89
|
+
return result || null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ─── High-level Operations ───
|
|
96
|
+
/**
|
|
97
|
+
* Build an authenticated git URL.
|
|
98
|
+
* For public repos: https://git.relayax.com/@owner/agent.git
|
|
99
|
+
* For gated/private: https://TOKEN:x@git.relayax.com/@owner/agent.git
|
|
100
|
+
*/
|
|
101
|
+
function buildGitUrl(baseUrl, auth) {
|
|
102
|
+
if (!auth?.token && !auth?.code)
|
|
103
|
+
return baseUrl;
|
|
104
|
+
const url = new URL(baseUrl);
|
|
105
|
+
const credential = auth.token ?? auth.code ?? '';
|
|
106
|
+
url.username = credential;
|
|
107
|
+
url.password = 'x';
|
|
108
|
+
return url.toString();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* First-time publish: init → add → commit → tag → push
|
|
112
|
+
*/
|
|
113
|
+
async function gitPublishInit(sourceDir, remoteUrl, version) {
|
|
114
|
+
gitInit(sourceDir);
|
|
115
|
+
gitAdd(sourceDir);
|
|
116
|
+
gitCommit(sourceDir, `v${version}`);
|
|
117
|
+
gitTag(sourceDir, `v${version}`);
|
|
118
|
+
gitPush(sourceDir, remoteUrl, 'HEAD:main');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Re-publish: clone → replace files → commit → tag → push
|
|
122
|
+
*/
|
|
123
|
+
async function gitPublishUpdate(sourceDir, remoteUrl, version) {
|
|
124
|
+
const tempCloneDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-git-'));
|
|
125
|
+
try {
|
|
126
|
+
// Clone existing repo
|
|
127
|
+
gitClone(remoteUrl, tempCloneDir);
|
|
128
|
+
// Copy .git directory to source dir
|
|
129
|
+
const gitDir = path_1.default.join(tempCloneDir, '.git');
|
|
130
|
+
const destGitDir = path_1.default.join(sourceDir, '.git');
|
|
131
|
+
fs_1.default.cpSync(gitDir, destGitDir, { recursive: true });
|
|
132
|
+
// Add all files (including new/changed, removing deleted)
|
|
133
|
+
gitAdd(sourceDir);
|
|
134
|
+
gitCommit(sourceDir, `v${version}`);
|
|
135
|
+
gitTag(sourceDir, `v${version}`);
|
|
136
|
+
gitPush(sourceDir, 'origin');
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
fs_1.default.rmSync(tempCloneDir, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Install: git clone to destination, optionally checkout a specific version.
|
|
144
|
+
*/
|
|
145
|
+
async function gitInstall(gitUrl, destDir, version) {
|
|
146
|
+
// Remove existing directory if present
|
|
147
|
+
if (fs_1.default.existsSync(destDir)) {
|
|
148
|
+
fs_1.default.rmSync(destDir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destDir), { recursive: true });
|
|
151
|
+
gitClone(gitUrl, destDir, { depth: 1 });
|
|
152
|
+
if (version) {
|
|
153
|
+
// Fetch all tags then checkout the specific version
|
|
154
|
+
gitFetch(destDir);
|
|
155
|
+
gitCheckout(destDir, `v${version}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Update: fetch latest then checkout the newest tag.
|
|
160
|
+
*/
|
|
161
|
+
async function gitUpdate(destDir) {
|
|
162
|
+
gitFetch(destDir);
|
|
163
|
+
const latestTag = gitLatestTag(destDir);
|
|
164
|
+
if (latestTag) {
|
|
165
|
+
gitCheckout(destDir, latestTag);
|
|
166
|
+
}
|
|
167
|
+
return latestTag;
|
|
168
|
+
}
|
package/dist/lib/guide.js
CHANGED
|
@@ -29,16 +29,25 @@ function generateGuide(config, commands, requires) {
|
|
|
29
29
|
const scopedSlug = config.slug.startsWith('@') ? config.slug : `@${config.slug}`;
|
|
30
30
|
const installCmd = `npx relayax-cli install ${scopedSlug}`;
|
|
31
31
|
const requiresSummary = requires ? buildRequiresSummary(requires) : '';
|
|
32
|
+
const slugPart = scopedSlug.startsWith('@') ? scopedSlug.slice(1) : scopedSlug;
|
|
33
|
+
const pluginUrl = `https://www.relayax.com/api/registry/${slugPart}/plugin`;
|
|
32
34
|
return `# ${config.name}
|
|
33
35
|
|
|
34
36
|
> ${config.description}
|
|
35
37
|
|
|
36
38
|
## 설치
|
|
37
39
|
|
|
40
|
+
### CLI
|
|
38
41
|
\`\`\`bash
|
|
39
42
|
${installCmd}
|
|
40
43
|
\`\`\`
|
|
41
44
|
|
|
45
|
+
### Claude Code Plugin
|
|
46
|
+
\`\`\`
|
|
47
|
+
/plugin marketplace add ${pluginUrl}
|
|
48
|
+
/plugin install ${config.slug.split('/').pop() ?? config.slug}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
42
51
|
${commands.length > 0 ? `## 포함된 커맨드
|
|
43
52
|
|
|
44
53
|
${commands.map((cmd) => `- \`/${cmd.name}\`: ${cmd.description}`).join('\n')}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface GeneratedFile {
|
|
2
|
+
relativePath: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ManifestRelayYaml {
|
|
6
|
+
name: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
description: string;
|
|
9
|
+
version: string;
|
|
10
|
+
source?: string;
|
|
11
|
+
org_slug?: string;
|
|
12
|
+
platforms?: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare const SUPPORTED_PLATFORMS: readonly ["claude-code", "codex", "antigravity"];
|
|
15
|
+
export type Platform = typeof SUPPORTED_PLATFORMS[number];
|
|
16
|
+
/**
|
|
17
|
+
* Generate platform-native manifests from relay.yaml metadata.
|
|
18
|
+
* Used by both `relay publish` and `relay export`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateManifests(yaml: ManifestRelayYaml, agentDir: string): GeneratedFile[];
|
|
@@ -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
|
+
}
|
package/dist/lib/storage.d.ts
CHANGED
|
@@ -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>;
|
package/dist/lib/storage.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -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 (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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 읽기
|
|
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:
|
|
177
|
+
scope_hint: `이 에이전트의 권장 배치 범위는 "${recommendedScope}"입니다. 사용자에게 확인 후 relay deploy --scope ${recommendedScope}로 배치하세요.`,
|
|
159
178
|
}, cliUpdate)] };
|
|
160
179
|
}
|
|
161
180
|
finally {
|
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;
|