relayax-cli 0.1.0 → 0.1.2

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.
@@ -8,10 +8,11 @@ const config_js_1 = require("../lib/config.js");
8
8
  function registerInstall(program) {
9
9
  program
10
10
  .command('install <slug>')
11
- .description('에이전트 팀 설치')
12
- .action(async (slug) => {
11
+ .description('에이전트 팀 설치 (현재 디렉토리의 .claude/에 설치)')
12
+ .option('--path <install_path>', '설치 경로 지정 (기본: ./.claude)')
13
+ .action(async (slug, opts) => {
13
14
  const pretty = program.opts().pretty ?? false;
14
- const config = (0, config_js_1.ensureConfig)();
15
+ const installPath = (0, config_js_1.getInstallPath)(opts.path);
15
16
  const tempDir = (0, storage_js_1.makeTempDir)();
16
17
  try {
17
18
  // 1. Fetch team metadata
@@ -22,7 +23,7 @@ function registerInstall(program) {
22
23
  const extractDir = `${tempDir}/extracted`;
23
24
  await (0, storage_js_1.extractPackage)(tarPath, extractDir);
24
25
  // 4. Copy files to install_path
25
- const files = (0, installer_js_1.installTeam)(extractDir, config.install_path);
26
+ const files = (0, installer_js_1.installTeam)(extractDir, installPath);
26
27
  // 5. Record in installed.json
27
28
  const installed = (0, config_js_1.loadInstalled)();
28
29
  installed[slug] = {
@@ -40,10 +41,11 @@ function registerInstall(program) {
40
41
  version: team.version,
41
42
  commands: team.commands,
42
43
  files_installed: files.length,
44
+ install_path: installPath,
43
45
  };
44
46
  if (pretty) {
45
47
  console.log(`\n\x1b[32m✓ ${team.name} 설치 완료\x1b[0m v${team.version}`);
46
- console.log(` 설치 위치: \x1b[36m${config.install_path}\x1b[0m`);
48
+ console.log(` 설치 위치: \x1b[36m${installPath}\x1b[0m`);
47
49
  console.log(` 파일 수: ${files.length}개`);
48
50
  if (team.commands.length > 0) {
49
51
  console.log('\n 사용 가능한 커맨드:');
@@ -1,7 +1,10 @@
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.registerLogin = registerLogin;
4
- const readline_1 = require("readline");
7
+ const http_1 = __importDefault(require("http"));
5
8
  const child_process_1 = require("child_process");
6
9
  const config_js_1 = require("../lib/config.js");
7
10
  function openBrowser(url) {
@@ -21,18 +24,9 @@ function openBrowser(url) {
21
24
  // ignore browser open errors
22
25
  }
23
26
  }
24
- async function readLine(prompt) {
25
- const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stderr });
26
- return new Promise((resolve) => {
27
- rl.question(prompt, (answer) => {
28
- rl.close();
29
- resolve(answer.trim());
30
- });
31
- });
32
- }
33
- async function verifyToken(apiUrl, token) {
27
+ async function verifyToken(token) {
34
28
  try {
35
- const res = await fetch(`${apiUrl}/api/auth/me`, {
29
+ const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
36
30
  headers: { Authorization: `Bearer ${token}` },
37
31
  });
38
32
  if (!res.ok)
@@ -43,35 +37,83 @@ async function verifyToken(apiUrl, token) {
43
37
  return null;
44
38
  }
45
39
  }
40
+ function waitForToken(port) {
41
+ return new Promise((resolve, reject) => {
42
+ const server = http_1.default.createServer((req, res) => {
43
+ const url = new URL(req.url ?? '/', `http://localhost:${port}`);
44
+ if (url.pathname === '/callback') {
45
+ const token = url.searchParams.get('token');
46
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
47
+ res.end(`<!DOCTYPE html>
48
+ <html><head><title>RelayAX</title></head>
49
+ <body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#f6f5f2;color:#111318">
50
+ <div style="text-align:center">
51
+ <h2>로그인 완료!</h2>
52
+ <p>터미널로 돌아가세요. 이 창은 닫아도 됩니다.</p>
53
+ <script>setTimeout(()=>window.close(),2000)</script>
54
+ </div>
55
+ </body></html>`);
56
+ server.close();
57
+ if (token) {
58
+ resolve(token);
59
+ }
60
+ else {
61
+ reject(new Error('토큰이 전달되지 않았습니다'));
62
+ }
63
+ }
64
+ else {
65
+ res.writeHead(404);
66
+ res.end('Not found');
67
+ }
68
+ });
69
+ server.listen(port, '127.0.0.1');
70
+ // Timeout after 5 minutes
71
+ setTimeout(() => {
72
+ server.close();
73
+ reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
74
+ }, 5 * 60 * 1000);
75
+ });
76
+ }
77
+ function findAvailablePort() {
78
+ return new Promise((resolve, reject) => {
79
+ const server = http_1.default.createServer();
80
+ server.listen(0, '127.0.0.1', () => {
81
+ const addr = server.address();
82
+ if (addr && typeof addr !== 'string') {
83
+ const port = addr.port;
84
+ server.close(() => resolve(port));
85
+ }
86
+ else {
87
+ server.close(() => reject(new Error('포트를 찾을 수 없습니다')));
88
+ }
89
+ });
90
+ });
91
+ }
46
92
  function registerLogin(program) {
47
93
  program
48
94
  .command('login')
49
- .description('Relay 계정에 로그인합니다')
95
+ .description('RelayAX 계정에 로그인합니다')
50
96
  .option('--token <token>', '직접 토큰 입력 (브라우저 없이)')
51
97
  .action(async (opts) => {
52
98
  const pretty = program.opts().pretty ?? false;
53
99
  (0, config_js_1.ensureRelayDir)();
54
- const config = (0, config_js_1.loadConfig)();
55
- const apiUrl = config?.api_url ?? 'https://relayax.com';
56
100
  let token = opts.token;
57
101
  if (!token) {
58
- const tokenUrl = `${apiUrl}/auth/token`;
59
- console.error('브라우저에서 로그인 표시되는 토큰을 복사하세요.');
60
- console.error(`\n ${tokenUrl}\n`);
61
- openBrowser(tokenUrl);
62
- token = await readLine('토큰 붙여넣기: ');
102
+ try {
103
+ const port = await findAvailablePort();
104
+ const loginUrl = `${config_js_1.API_URL}/auth/cli-login?port=${port}`;
105
+ console.error('브라우저에서 로그인을 진행합니다...');
106
+ openBrowser(loginUrl);
107
+ token = await waitForToken(port);
108
+ }
109
+ catch (err) {
110
+ const msg = err instanceof Error ? err.message : '로그인 실패';
111
+ console.error(JSON.stringify({ error: 'LOGIN_FAILED', message: msg }));
112
+ process.exit(1);
113
+ }
63
114
  }
64
- if (!token) {
65
- console.error(JSON.stringify({ error: 'NO_TOKEN', message: '토큰이 입력되지 않았습니다' }));
66
- process.exit(1);
67
- }
68
- const user = await verifyToken(apiUrl, token);
69
- const updatedConfig = {
70
- install_path: config?.install_path ?? `${process.env.HOME}/.claude`,
71
- api_url: apiUrl,
72
- token,
73
- };
74
- (0, config_js_1.saveConfig)(updatedConfig);
115
+ const user = await verifyToken(token);
116
+ (0, config_js_1.saveToken)(token);
75
117
  const result = {
76
118
  status: 'ok',
77
119
  message: '로그인 성공',
@@ -81,7 +123,6 @@ function registerLogin(program) {
81
123
  console.log(`\x1b[32m✓ 로그인 완료\x1b[0m`);
82
124
  if (user?.email)
83
125
  console.log(` 계정: \x1b[36m${user.email}\x1b[0m`);
84
- console.log(` 토큰이 ~/.relay/config.json에 저장되었습니다.`);
85
126
  }
86
127
  else {
87
128
  console.log(JSON.stringify(result));
@@ -124,8 +124,7 @@ function registerPublish(program) {
124
124
  process.exit(1);
125
125
  }
126
126
  // Get token
127
- const config = (0, config_js_1.ensureConfig)();
128
- const token = opts.token ?? process.env.RELAY_TOKEN ?? config.token;
127
+ const token = opts.token ?? process.env.RELAY_TOKEN ?? (0, config_js_1.loadToken)();
129
128
  if (!token) {
130
129
  console.error(JSON.stringify({
131
130
  error: 'NO_TOKEN',
@@ -173,7 +172,7 @@ function registerPublish(program) {
173
172
  if (pretty) {
174
173
  console.error(`업로드 중...`);
175
174
  }
176
- const result = await publishToApi(config.api_url, token, tarPath, metadata);
175
+ const result = await publishToApi(config_js_1.API_URL, token, tarPath, metadata);
177
176
  if (pretty) {
178
177
  console.log(`\n\x1b[32m✓ ${name} 배포 완료\x1b[0m v${result.version}`);
179
178
  console.log(` 슬러그: \x1b[36m${result.slug}\x1b[0m`);
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
- const init_js_1 = require("./commands/init.js");
6
5
  const search_js_1 = require("./commands/search.js");
7
6
  const install_js_1 = require("./commands/install.js");
8
7
  const list_js_1 = require("./commands/list.js");
@@ -17,7 +16,6 @@ program
17
16
  .description('RelayAX Agent Team Marketplace CLI')
18
17
  .version(pkg.version)
19
18
  .option('--pretty', '인간 친화적 출력 (기본값: JSON)');
20
- (0, init_js_1.registerInit)(program);
21
19
  (0, search_js_1.registerSearch)(program);
22
20
  (0, install_js_1.registerInstall)(program);
23
21
  (0, list_js_1.registerList)(program);
package/dist/lib/api.js CHANGED
@@ -5,8 +5,7 @@ exports.searchTeams = searchTeams;
5
5
  exports.reportInstall = reportInstall;
6
6
  const config_js_1 = require("./config.js");
7
7
  async function fetchTeamInfo(slug) {
8
- const config = (0, config_js_1.ensureConfig)();
9
- const url = `${config.api_url}/api/registry/${slug}`;
8
+ const url = `${config_js_1.API_URL}/api/registry/${slug}`;
10
9
  const res = await fetch(url);
11
10
  if (!res.ok) {
12
11
  const body = await res.text();
@@ -15,11 +14,10 @@ async function fetchTeamInfo(slug) {
15
14
  return res.json();
16
15
  }
17
16
  async function searchTeams(query, tag) {
18
- const config = (0, config_js_1.ensureConfig)();
19
17
  const params = new URLSearchParams({ q: query });
20
18
  if (tag)
21
19
  params.set('tag', tag);
22
- const url = `${config.api_url}/api/registry/search?${params.toString()}`;
20
+ const url = `${config_js_1.API_URL}/api/registry/search?${params.toString()}`;
23
21
  const res = await fetch(url);
24
22
  if (!res.ok) {
25
23
  const body = await res.text();
@@ -29,8 +27,7 @@ async function searchTeams(query, tag) {
29
27
  return data.results;
30
28
  }
31
29
  async function reportInstall(slug) {
32
- const config = (0, config_js_1.ensureConfig)();
33
- const url = `${config.api_url}/api/registry/${slug}/install`;
30
+ const url = `${config_js_1.API_URL}/api/registry/${slug}/install`;
34
31
  await fetch(url, { method: 'POST' }).catch(() => {
35
32
  // non-critical: ignore errors
36
33
  });
@@ -1,13 +1,12 @@
1
- import type { RelayConfig, InstalledRegistry } from '../types.js';
2
- export declare function getRelayDir(): string;
3
- export declare function ensureRelayDir(): void;
4
- export declare function loadConfig(): RelayConfig | null;
5
- export declare function saveConfig(config: RelayConfig): void;
6
- export declare function requireConfig(): RelayConfig;
1
+ import type { InstalledRegistry } from '../types.js';
2
+ export declare const API_URL = "https://relayax.com";
7
3
  /**
8
- * npx 실행처럼 config가 없을 기본값으로 자동 초기화한다.
9
- * 이미 config가 있으면 그대로 반환한다.
4
+ * 현재 디렉토리의 .claude/ 기본 설치 경로로 사용한다.
5
+ * --path 옵션으로 오버라이드 가능.
10
6
  */
11
- export declare function ensureConfig(): RelayConfig;
7
+ export declare function getInstallPath(override?: string): string;
8
+ export declare function ensureRelayDir(): void;
9
+ export declare function loadToken(): string | undefined;
10
+ export declare function saveToken(token: string): void;
12
11
  export declare function loadInstalled(): InstalledRegistry;
13
12
  export declare function saveInstalled(registry: InstalledRegistry): void;
@@ -3,75 +3,51 @@ 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.getRelayDir = getRelayDir;
6
+ exports.API_URL = void 0;
7
+ exports.getInstallPath = getInstallPath;
7
8
  exports.ensureRelayDir = ensureRelayDir;
8
- exports.loadConfig = loadConfig;
9
- exports.saveConfig = saveConfig;
10
- exports.requireConfig = requireConfig;
11
- exports.ensureConfig = ensureConfig;
9
+ exports.loadToken = loadToken;
10
+ exports.saveToken = saveToken;
12
11
  exports.loadInstalled = loadInstalled;
13
12
  exports.saveInstalled = saveInstalled;
14
13
  const fs_1 = __importDefault(require("fs"));
15
14
  const path_1 = __importDefault(require("path"));
16
15
  const os_1 = __importDefault(require("os"));
17
- const DEFAULT_API_URL = 'https://relayax.com';
18
- const DEFAULT_INSTALL_PATH = path_1.default.join(os_1.default.homedir(), '.claude');
16
+ exports.API_URL = 'https://relayax.com';
19
17
  const RELAY_DIR = path_1.default.join(os_1.default.homedir(), '.relay');
20
- const CONFIG_FILE = path_1.default.join(RELAY_DIR, 'config.json');
21
18
  const INSTALLED_FILE = path_1.default.join(RELAY_DIR, 'installed.json');
22
- function getRelayDir() {
23
- return RELAY_DIR;
19
+ /**
20
+ * 현재 디렉토리의 .claude/ 를 기본 설치 경로로 사용한다.
21
+ * --path 옵션으로 오버라이드 가능.
22
+ */
23
+ function getInstallPath(override) {
24
+ if (override) {
25
+ const resolved = override.startsWith('~')
26
+ ? path_1.default.join(os_1.default.homedir(), override.slice(1))
27
+ : path_1.default.resolve(override);
28
+ return resolved;
29
+ }
30
+ return path_1.default.join(process.cwd(), '.claude');
24
31
  }
25
32
  function ensureRelayDir() {
26
33
  if (!fs_1.default.existsSync(RELAY_DIR)) {
27
34
  fs_1.default.mkdirSync(RELAY_DIR, { recursive: true });
28
35
  }
29
36
  }
30
- function loadConfig() {
31
- if (!fs_1.default.existsSync(CONFIG_FILE)) {
32
- return null;
33
- }
37
+ function loadToken() {
38
+ const tokenFile = path_1.default.join(RELAY_DIR, 'token');
39
+ if (!fs_1.default.existsSync(tokenFile))
40
+ return undefined;
34
41
  try {
35
- const raw = fs_1.default.readFileSync(CONFIG_FILE, 'utf-8');
36
- return JSON.parse(raw);
42
+ return fs_1.default.readFileSync(tokenFile, 'utf-8').trim() || undefined;
37
43
  }
38
44
  catch {
39
- return null;
45
+ return undefined;
40
46
  }
41
47
  }
42
- function saveConfig(config) {
48
+ function saveToken(token) {
43
49
  ensureRelayDir();
44
- fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
45
- }
46
- function requireConfig() {
47
- const config = loadConfig();
48
- if (!config) {
49
- console.error(JSON.stringify({
50
- error: 'NOT_INITIALIZED',
51
- message: 'relay가 초기화되지 않았습니다. `relay init`을 먼저 실행하세요.',
52
- }));
53
- process.exit(1);
54
- }
55
- return config;
56
- }
57
- /**
58
- * npx 첫 실행처럼 config가 없을 때 기본값으로 자동 초기화한다.
59
- * 이미 config가 있으면 그대로 반환한다.
60
- */
61
- function ensureConfig() {
62
- const existing = loadConfig();
63
- if (existing)
64
- return existing;
65
- const config = {
66
- install_path: DEFAULT_INSTALL_PATH,
67
- api_url: DEFAULT_API_URL,
68
- };
69
- if (!fs_1.default.existsSync(config.install_path)) {
70
- fs_1.default.mkdirSync(config.install_path, { recursive: true });
71
- }
72
- saveConfig(config);
73
- console.error(`relay 초기 설정 완료 (${config.install_path}에 설치)`);
74
- return config;
50
+ fs_1.default.writeFileSync(path_1.default.join(RELAY_DIR, 'token'), token);
75
51
  }
76
52
  function loadInstalled() {
77
53
  if (!fs_1.default.existsSync(INSTALLED_FILE)) {
package/dist/types.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- export interface RelayConfig {
2
- install_path: string;
3
- api_url: string;
4
- token?: string;
5
- }
6
1
  export interface InstalledTeam {
7
2
  version: string;
8
3
  installed_at: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {