relayax-cli 0.4.24 → 0.4.25

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.
@@ -27,6 +27,7 @@ export interface RequiresEnv {
27
27
  name: string;
28
28
  required?: boolean;
29
29
  description?: string;
30
+ setup_hint?: string;
30
31
  }
31
32
  export interface RequiresNpm {
32
33
  name: string;
@@ -74,7 +75,6 @@ export interface PublishMetadata {
74
75
  skill_details?: SkillDetail[];
75
76
  org_slug?: string;
76
77
  }
77
- export declare function createTarball(agentDir: string): Promise<string>;
78
78
  interface PublishResult {
79
79
  status: string;
80
80
  slug: string;
@@ -88,6 +88,6 @@ interface PublishResult {
88
88
  default_welcome?: string;
89
89
  } | null;
90
90
  }
91
- export declare function publishToApi(token: string, tarPath: string, metadata: PublishMetadata): Promise<PublishResult>;
91
+ export declare function publishToApi(token: string, metadata: PublishMetadata): Promise<PublishResult>;
92
92
  export declare function registerPublish(program: Command): void;
93
93
  export {};
@@ -3,14 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createTarball = createTarball;
7
6
  exports.publishToApi = publishToApi;
8
7
  exports.registerPublish = registerPublish;
9
8
  const fs_1 = __importDefault(require("fs"));
10
9
  const path_1 = __importDefault(require("path"));
11
- const os_1 = __importDefault(require("os"));
12
10
  const js_yaml_1 = __importDefault(require("js-yaml"));
13
- const tar_1 = require("tar");
14
11
  const config_js_1 = require("../lib/config.js");
15
12
  const preamble_js_1 = require("../lib/preamble.js");
16
13
  const version_check_js_1 = require("../lib/version-check.js");
@@ -250,34 +247,14 @@ function resolveLongDescription(agentDir, yamlValue) {
250
247
  }
251
248
  return undefined;
252
249
  }
253
- async function createTarball(agentDir) {
254
- const tmpFile = path_1.default.join(os_1.default.tmpdir(), `relay-publish-${Date.now()}.tar.gz`);
255
- const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(agentDir, d)));
256
- // Include root-level files if they exist
257
- const entries = [...dirsToInclude];
258
- const rootFiles = ['relay.yaml', 'SKILL.md', 'guide.md'];
259
- for (const file of rootFiles) {
260
- if (fs_1.default.existsSync(path_1.default.join(agentDir, file))) {
261
- entries.push(file);
262
- }
263
- }
264
- await (0, tar_1.create)({
265
- gzip: true,
266
- file: tmpFile,
267
- cwd: agentDir,
268
- }, entries);
269
- return tmpFile;
270
- }
271
- async function publishToApi(token, tarPath, metadata) {
272
- const fileBuffer = fs_1.default.readFileSync(tarPath);
273
- const blob = new Blob([fileBuffer], { type: 'application/gzip' });
274
- const form = new FormData();
275
- form.append('package', blob, `${metadata.slug}-${metadata.version}.tar.gz`);
276
- form.append('metadata', JSON.stringify(metadata));
250
+ async function publishToApi(token, metadata) {
277
251
  const res = await fetch(`${config_js_1.API_URL}/api/publish`, {
278
252
  method: 'POST',
279
- headers: { Authorization: `Bearer ${token}` },
280
- body: form,
253
+ headers: {
254
+ Authorization: `Bearer ${token}`,
255
+ 'Content-Type': 'application/json',
256
+ },
257
+ body: JSON.stringify(metadata),
281
258
  redirect: 'error',
282
259
  });
283
260
  const body = await res.json();
@@ -442,6 +419,27 @@ function registerPublish(program) {
442
419
  console.error(` → relay.yaml에 version: ${newVersion} 저장됨\n`);
443
420
  }
444
421
  }
422
+ // Auto-sync: relay.yaml의 contents에 정의된 소스를 .relay/에 동기화
423
+ try {
424
+ const yamlContent = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
425
+ const yamlConfig = js_yaml_1.default.load(yamlContent);
426
+ const contents = yamlConfig.contents ?? [];
427
+ if (contents.length > 0) {
428
+ const { computeContentsDiff, syncContentsToRelay } = await import('./package.js');
429
+ const { diff: contentsDiff } = computeContentsDiff(contents, relayDir, agentDir);
430
+ const hasChanges = contentsDiff.some((d) => d.status === 'modified');
431
+ if (hasChanges) {
432
+ syncContentsToRelay(contents, contentsDiff, relayDir, agentDir);
433
+ if (!json) {
434
+ const changedNames = contentsDiff.filter((d) => d.status === 'modified').map((d) => d.name);
435
+ console.error(`\x1b[36m⚙ 소스 동기화:\x1b[0m ${changedNames.join(', ')}`);
436
+ }
437
+ }
438
+ }
439
+ }
440
+ catch {
441
+ // sync 실패는 non-fatal — 기존 .relay/ 내용으로 publish 진행
442
+ }
445
443
  // Validate structure (콘텐츠는 .relay/ 안에 있음)
446
444
  const hasDirs = VALID_DIRS.some((d) => {
447
445
  const dirPath = path_1.default.join(relayDir, d);
@@ -709,10 +707,7 @@ function registerPublish(program) {
709
707
  console.error('\x1b[33m⚠ GUIDE.html은 더 이상 지원되지 않습니다. 상세페이지가 가이드 역할을 합니다.\x1b[0m');
710
708
  console.error(' long_description을 활용하거나 relayax.com에서 에이전트 정보를 편집하세요.\n');
711
709
  }
712
- // Generate guide.md (consumer install guide)
713
- const { generateGuide } = await import('../lib/guide.js');
714
- const guideContent = generateGuide(config, detectedCommands, config.requires);
715
- fs_1.default.writeFileSync(path_1.default.join(relayDir, 'guide.md'), guideContent);
710
+ // guide.md API route에서 동적 생성 (/api/registry/{owner}/{slug}/guide.md)
716
711
  // Generate bin/relay-preamble.sh (self-contained tracking + update check)
717
712
  (0, preamble_js_1.generatePreambleBin)(relayDir, config.slug, config_js_1.API_URL);
718
713
  // Generate entry command (commands/{author}-{name}.md)
@@ -765,13 +760,11 @@ function registerPublish(program) {
765
760
  return 'antigravity';
766
761
  return 'unknown';
767
762
  }))];
768
- let tarPath = null;
769
763
  try {
770
- tarPath = await createTarball(relayDir);
771
764
  if (!json) {
772
765
  console.error(`업로드 중...`);
773
766
  }
774
- const result = await publishToApi(token, tarPath, metadata);
767
+ const result = await publishToApi(token, metadata);
775
768
  // Git push: commit and push to git server (required)
776
769
  const gitUrlRaw = result.git_url;
777
770
  if (gitUrlRaw) {
@@ -865,10 +858,5 @@ function registerPublish(program) {
865
858
  console.error(JSON.stringify({ error: 'PUBLISH_FAILED', message, fix: message }));
866
859
  process.exit(1);
867
860
  }
868
- finally {
869
- if (tarPath && fs_1.default.existsSync(tarPath)) {
870
- fs_1.default.unlinkSync(tarPath);
871
- }
872
- }
873
861
  });
874
862
  }
@@ -93,13 +93,7 @@ function registerUpdate(program) {
93
93
  await (0, storage_js_1.clonePackage)(agent.git_url, agentDir);
94
94
  }
95
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);
96
+ throw new Error('이 에이전트는 재설치가 필요합니다. relay install로 다시 설치하세요.');
103
97
  }
104
98
  // Inject preamble
105
99
  (0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
package/dist/lib/api.js CHANGED
@@ -110,10 +110,12 @@ async function sendUsagePing(agentId, slug, version) {
110
110
  const deviceHash = createHash('sha256')
111
111
  .update(`${hostname()}:${userInfo().username}`)
112
112
  .digest('hex');
113
+ // CLI version
114
+ const pkg = require('../../package.json');
113
115
  // agentId(UUID)가 있으면 UUID 경로, 없으면 slug name으로 fallback
114
116
  const pathParam = agentId || slug.replace(/^@/, '').split('/').pop() || slug;
115
117
  const url = `${config_js_1.API_URL}/api/agents/${pathParam}/ping`;
116
- const payload = { device_hash: deviceHash, slug };
118
+ const payload = { device_hash: deviceHash, slug, cli_version: pkg.version };
117
119
  if (version)
118
120
  payload.installed_version = version;
119
121
  const headers = { 'Content-Type': 'application/json' };
@@ -148,7 +148,7 @@ async function gitPublishUpdate(sourceDir, remoteUrl, version) {
148
148
  gitAdd(sourceDir);
149
149
  gitCommit(sourceDir, `v${version}`);
150
150
  gitTag(sourceDir, `v${version}`);
151
- gitPush(sourceDir, 'origin');
151
+ gitPush(sourceDir, 'origin', 'HEAD:main');
152
152
  }
153
153
  finally {
154
154
  fs_1.default.rmSync(tempCloneDir, { recursive: true, force: true });
@@ -190,10 +190,11 @@ function checkRequires(agentDir) {
190
190
  }
191
191
  else {
192
192
  const desc = env.description ? ` (${env.description})` : '';
193
+ const hint = env.setup_hint ? `\n 설정 방법:\n${env.setup_hint.split('\n').map((l) => ` ${l}`).join('\n')}` : '';
193
194
  results.push({
194
195
  label: 'env',
195
196
  status: env.required !== false ? 'missing' : 'warn',
196
- message: `${env.name} — 미설정${desc}`,
197
+ message: `${env.name} — 미설정${desc}${hint}`,
197
198
  });
198
199
  }
199
200
  }
@@ -43,16 +43,24 @@ if [ -f "$HOME/.relay/token.json" ]; then
43
43
  _RELAY_TOKEN=$(grep -o '"access_token":"[^"]*"' "$HOME/.relay/token.json" 2>/dev/null | head -1 | cut -d'"' -f4)
44
44
  fi
45
45
 
46
+ # CLI version (for usage tracking)
47
+ _CLI_VERSION=""
48
+ if command -v relay &>/dev/null; then
49
+ _CLI_VERSION=$(relay --version 2>/dev/null | head -1 | grep -o '[0-9][0-9.]*' || true)
50
+ fi
51
+
46
52
  # Usage ping (with user identity if logged in)
47
53
  if command -v relay &>/dev/null; then
48
54
  relay ping "${slug}" --quiet 2>/dev/null &
49
55
  elif command -v curl &>/dev/null; then
50
56
  _AUTH_HEADER=""
51
57
  [ -n "$_RELAY_TOKEN" ] && _AUTH_HEADER="-H \\"Authorization: Bearer $_RELAY_TOKEN\\""
58
+ _CLI_VER_FIELD=""
59
+ [ -n "$_CLI_VERSION" ] && _CLI_VER_FIELD=",\\"cli_version\\":\\"$_CLI_VERSION\\""
52
60
  eval curl -sf --max-time 5 -X POST "${apiUrl}/api/agents/${agentSlug}/ping" \\
53
61
  -H "Content-Type: application/json" \\
54
62
  $_AUTH_HEADER \\
55
- -d "{\\"device_hash\\":\\"$DEVICE_HASH\\",\\"slug\\":\\"${slug}\\"}" \\
63
+ -d "{\\"device_hash\\":\\"$DEVICE_HASH\\",\\"slug\\":\\"${slug}\\"$_CLI_VER_FIELD}" \\
56
64
  2>/dev/null &
57
65
  fi
58
66
 
@@ -1,9 +1,6 @@
1
- export declare function downloadPackage(url: string, destDir: string): Promise<string>;
2
- export declare function extractPackage(tarPath: string, destDir: string): Promise<void>;
3
1
  export declare function makeTempDir(): string;
4
2
  export declare function removeTempDir(dir: string): void;
5
3
  /**
6
4
  * Clone an agent from git URL to destination directory.
7
- * Replaces downloadPackage() + extractPackage() for git-based agents.
8
5
  */
9
6
  export declare function clonePackage(gitUrl: string, destDir: string, version?: string): Promise<void>;
@@ -3,49 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.downloadPackage = downloadPackage;
7
- exports.extractPackage = extractPackage;
8
6
  exports.makeTempDir = makeTempDir;
9
7
  exports.removeTempDir = removeTempDir;
10
8
  exports.clonePackage = clonePackage;
11
9
  const fs_1 = __importDefault(require("fs"));
12
- const path_1 = __importDefault(require("path"));
13
10
  const os_1 = __importDefault(require("os"));
14
- const fs_2 = require("fs");
15
- const promises_1 = require("stream/promises");
16
- const stream_1 = require("stream");
17
- const tar_1 = require("tar");
18
11
  const git_operations_js_1 = require("./git-operations.js");
19
- async function downloadPackage(url, destDir) {
20
- const res = await fetch(url);
21
- if (!res.ok) {
22
- throw new Error(`패키지 다운로드 실패 (${res.status}): ${url}`);
23
- }
24
- const fileName = path_1.default.basename(new URL(url).pathname) || 'package.tar.gz';
25
- const destPath = path_1.default.join(destDir, fileName);
26
- const body = res.body;
27
- if (!body) {
28
- throw new Error('응답 본문이 비어 있습니다');
29
- }
30
- const nodeReadable = stream_1.Readable.fromWeb(body);
31
- await (0, promises_1.pipeline)(nodeReadable, (0, fs_2.createWriteStream)(destPath));
32
- return destPath;
33
- }
34
- async function extractPackage(tarPath, destDir) {
35
- if (!fs_1.default.existsSync(destDir)) {
36
- fs_1.default.mkdirSync(destDir, { recursive: true });
37
- }
38
- await (0, tar_1.extract)({ file: tarPath, cwd: destDir });
39
- }
40
12
  function makeTempDir() {
41
- return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-'));
13
+ return fs_1.default.mkdtempSync(os_1.default.tmpdir() + '/relay-');
42
14
  }
43
15
  function removeTempDir(dir) {
44
16
  fs_1.default.rmSync(dir, { recursive: true, force: true });
45
17
  }
46
18
  /**
47
19
  * Clone an agent from git URL to destination directory.
48
- * Replaces downloadPackage() + extractPackage() for git-based agents.
49
20
  */
50
21
  async function clonePackage(gitUrl, destDir, version) {
51
22
  await (0, git_operations_js_1.gitInstall)(gitUrl, destDir, version);
@@ -460,49 +460,6 @@ function createMcpServer() {
460
460
  return { content: [jsonText({ error: String(err) })], isError: true };
461
461
  }
462
462
  });
463
- server.tool('relay_publish', '에이전트를 마켓플레이스에 배포합니다 (.relay/ 디렉토리를 tar로 패키징하여 업로드)', {
464
- project_path: zod_1.z.string().optional().describe('프로젝트 경로 (.relay/relay.yaml이 있는 디렉토리)'),
465
- }, async ({ project_path }) => {
466
- try {
467
- const projectPath = resolveMcpProjectPath(project_path);
468
- const relayDir = path_1.default.join(projectPath, '.relay');
469
- const relayYaml = path_1.default.join(relayDir, 'relay.yaml');
470
- if (!fs_1.default.existsSync(relayYaml)) {
471
- return { content: [jsonText({ error: 'NOT_INITIALIZED', message: '.relay/relay.yaml이 없습니다.' })], isError: true };
472
- }
473
- const token = await (0, config_js_1.getValidToken)();
474
- if (!token) {
475
- return { content: [jsonText({ error: 'LOGIN_REQUIRED', message: '배포하려면 로그인이 필요합니다.' })], isError: true };
476
- }
477
- const cfg = js_yaml_1.default.load(fs_1.default.readFileSync(relayYaml, 'utf-8'));
478
- const { createTarball, publishToApi } = await import('../commands/publish.js');
479
- // Generate bin/relay-preamble.sh (CLI publish와 동일하게)
480
- (0, preamble_js_1.generatePreambleBin)(relayDir, cfg.slug, config_js_1.API_URL);
481
- const tarPath = await createTarball(relayDir);
482
- try {
483
- const metadata = {
484
- slug: cfg.slug,
485
- name: cfg.name,
486
- description: cfg.description ?? '',
487
- tags: cfg.tags ?? [],
488
- commands: [],
489
- components: { skills: 0, agents: 0, rules: 0, commands: 0 },
490
- version: cfg.version,
491
- visibility: cfg.visibility ?? 'public',
492
- cli_version: pkg.version,
493
- };
494
- const result = await publishToApi(token, tarPath, metadata);
495
- const cliUpdate = await getCliUpdateWarning();
496
- return { content: [jsonTextWithUpdate(result, cliUpdate)] };
497
- }
498
- finally {
499
- fs_1.default.unlinkSync(tarPath);
500
- }
501
- }
502
- catch (err) {
503
- return { content: [jsonText({ error: String(err) })], isError: true };
504
- }
505
- });
506
463
  // ═══ grant / access / join ═══
507
464
  server.tool('relay_grant_create', '에이전트 또는 Organization의 접근 코드를 생성합니다', {
508
465
  agent_slug: zod_1.z.string().optional().describe('에이전트 slug (agent 접근 코드 생성 시)'),
@@ -68,6 +68,9 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
68
68
  - **env**: 환경변수 참조를 찾고 맥락에서 필수/선택 판단
69
69
  - 핵심 로직에서 사용 → `required: true`
70
70
  - 테스트/선택 기능에서 사용 → `required: false`
71
+ - cookie, token 등 비표준적 env는 `setup_hint`에 획득 방법을 기술 권장
72
+ - 예: `setup_hint: "1. klingai.com 로그인\n2. DevTools → Cookies\n3. cookie 문자열 복사"`
73
+ - 일반 API 키(OPENAI_API_KEY 등)는 description만으로 충분
71
74
  - **cli**: 외부 CLI 도구 참조 (playwright, ffmpeg 등)
72
75
  - **npm**: import/require 패키지
73
76
  - **mcp**: MCP 서버 참조 (supabase, github 등)
@@ -76,6 +79,10 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
76
79
 
77
80
  보안 점검:
78
81
  - 하드코딩된 API 키, 토큰 (sk-*, ghp_*, AKIA* 등)
82
+ - 하드코딩된 cookie 값 (Cookie:, Set-Cookie, session_id=, _ga= 등)
83
+ - 하드코딩된 Bearer/JWT 토큰 (Bearer ey..., Authorization: 등)
84
+ - 100자 이상의 연속 alphanumeric/base64 문자열 (시크릿 의심)
85
+ - 단, placeholder는 무시: YOUR_XXX, <your-xxx>, sk-xxx, PASTE_HERE 등 명백한 예시값
79
86
  - 파일 컨텍스트를 읽어 실제 시크릿 vs 예시 코드를 구분
80
87
  - 발견 시 **반드시 경고**하고 환경변수 대체 안내
81
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.4.24",
3
+ "version": "0.4.25",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -37,9 +37,7 @@
37
37
  "@inquirer/prompts": "^8.3.2",
38
38
  "@modelcontextprotocol/sdk": "^1.29.0",
39
39
  "commander": "^13.1.0",
40
- "form-data": "^4.0.5",
41
- "js-yaml": "^4.1.1",
42
- "tar": "^7.4.0"
40
+ "js-yaml": "^4.1.1"
43
41
  },
44
42
  "devDependencies": {
45
43
  "@types/js-yaml": "^4.0.9",