relayax-cli 0.1.1 → 0.1.3

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.
@@ -6,71 +6,127 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.registerInit = registerInit;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const os_1 = __importDefault(require("os"));
10
9
  const readline_1 = __importDefault(require("readline"));
11
- const config_js_1 = require("../lib/config.js");
12
- const DEFAULT_API_URL = 'https://relayax.com';
13
- const PRESET_PATHS = {
14
- '1': path_1.default.join(os_1.default.homedir(), '.claude'),
15
- '2': path_1.default.join(os_1.default.homedir(), '.gemini'),
16
- };
17
- function prompt(rl, question) {
18
- return new Promise((resolve) => rl.question(question, resolve));
10
+ const DIRS = ['skills', 'commands', 'agents', 'rules'];
11
+ function prompt(rl, question, defaultVal) {
12
+ const hint = defaultVal ? ` (${defaultVal})` : '';
13
+ return new Promise((resolve) => {
14
+ rl.question(`${question}${hint}: `, (answer) => {
15
+ resolve(answer.trim() || defaultVal || '');
16
+ });
17
+ });
18
+ }
19
+ function slugify(str) {
20
+ return str
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9-]/g, '-')
23
+ .replace(/-+/g, '-')
24
+ .replace(/^-|-$/g, '');
25
+ }
26
+ function toYaml(data) {
27
+ const lines = [
28
+ `name: "${data.name}"`,
29
+ `slug: "${data.slug}"`,
30
+ `description: "${data.description}"`,
31
+ `version: "${data.version}"`,
32
+ ];
33
+ if (data.tags.length > 0) {
34
+ lines.push('tags:');
35
+ for (const tag of data.tags) {
36
+ lines.push(` - "${tag}"`);
37
+ }
38
+ }
39
+ else {
40
+ lines.push('tags: []');
41
+ }
42
+ return lines.join('\n') + '\n';
19
43
  }
20
44
  function registerInit(program) {
21
45
  program
22
46
  .command('init')
23
- .description('relayax 초기화 설치 경로 설정')
24
- .option('--path <install_path>', '설치 경로 직접 지정')
25
- .option('--api-url <url>', `API URL (기본값: ${DEFAULT_API_URL})`)
47
+ .description('현재 디렉토리를 RelayAX 패키지로 초기화합니다')
48
+ .option('--name <name>', ' 이름')
49
+ .option('--slug <slug>', 'URL 슬러그')
50
+ .option('--description <desc>', '한 줄 설명')
26
51
  .action(async (opts) => {
27
52
  const pretty = program.opts().pretty ?? false;
28
- const api_url = opts.apiUrl ?? DEFAULT_API_URL;
29
- let install_path;
30
- if (opts.path) {
31
- install_path = opts.path;
53
+ const cwd = process.cwd();
54
+ const relayYamlPath = path_1.default.join(cwd, 'relay.yaml');
55
+ // Check if already initialized
56
+ if (fs_1.default.existsSync(relayYamlPath)) {
57
+ if (pretty) {
58
+ console.log('\x1b[33m이미 초기화되어 있습니다.\x1b[0m relay.yaml이 존재합니다.');
59
+ }
60
+ else {
61
+ console.log(JSON.stringify({ status: 'already_initialized', path: relayYamlPath }));
62
+ }
63
+ return;
32
64
  }
33
- else if (!pretty) {
34
- // non-pretty / agent mode: use default
35
- install_path = PRESET_PATHS['1'];
65
+ // Detect existing directories
66
+ const existing = DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(cwd, d)));
67
+ const missing = DIRS.filter((d) => !fs_1.default.existsSync(path_1.default.join(cwd, d)));
68
+ const dirName = path_1.default.basename(cwd);
69
+ const autoSlug = slugify(dirName);
70
+ let name;
71
+ let slug;
72
+ let description;
73
+ let tags = [];
74
+ if (opts.name && opts.slug && opts.description) {
75
+ // Non-interactive
76
+ name = opts.name;
77
+ slug = opts.slug;
78
+ description = opts.description;
36
79
  }
37
80
  else {
38
- const rl = readline_1.default.createInterface({
39
- input: process.stdin,
40
- output: process.stdout,
41
- });
42
- console.log('\n설치 경로를 선택하세요:');
43
- console.log(' 1) ~/.claude/ (Claude Code) [기본값]');
44
- console.log(' 2) ~/.gemini/ (Gemini CLI)');
45
- console.log(' 3) 직접 입력');
46
- const choice = (await prompt(rl, '\n선택 [1]: ')).trim() || '1';
47
- if (choice === '3') {
48
- install_path = (await prompt(rl, '경로 입력: ')).trim();
49
- }
50
- else {
51
- install_path = PRESET_PATHS[choice] ?? PRESET_PATHS['1'];
81
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
82
+ if (pretty) {
83
+ console.log('\n\x1b[1mRelayAX 팀 패키지 초기화\x1b[0m\n');
84
+ if (existing.length > 0) {
85
+ console.log(` 감지된 디렉토리: \x1b[36m${existing.join(', ')}\x1b[0m`);
86
+ }
87
+ console.log('');
52
88
  }
89
+ name = opts.name ?? await prompt(rl, '팀 이름', dirName);
90
+ slug = opts.slug ?? await prompt(rl, '슬러그', autoSlug);
91
+ description = opts.description ?? await prompt(rl, '한 줄 설명');
92
+ const tagsInput = await prompt(rl, '태그 (쉼표 구분)', '');
93
+ tags = tagsInput ? tagsInput.split(',').map((t) => t.trim()).filter(Boolean) : [];
53
94
  rl.close();
54
95
  }
55
- // Resolve ~ in custom paths
56
- if (install_path.startsWith('~')) {
57
- install_path = path_1.default.join(os_1.default.homedir(), install_path.slice(1));
96
+ if (!description) {
97
+ console.error(JSON.stringify({ error: 'MISSING_DESCRIPTION', message: 'description은 필수입니다' }));
98
+ process.exit(1);
58
99
  }
59
- // Ensure install path exists
60
- if (!fs_1.default.existsSync(install_path)) {
61
- fs_1.default.mkdirSync(install_path, { recursive: true });
100
+ // Create missing directories
101
+ const created = [];
102
+ for (const dir of missing) {
103
+ fs_1.default.mkdirSync(path_1.default.join(cwd, dir), { recursive: true });
104
+ // Add .gitkeep so empty dirs are tracked
105
+ fs_1.default.writeFileSync(path_1.default.join(cwd, dir, '.gitkeep'), '');
106
+ created.push(dir);
62
107
  }
63
- (0, config_js_1.ensureRelayDir)();
64
- (0, config_js_1.saveConfig)({ install_path, api_url });
108
+ // Write relay.yaml
109
+ const yamlData = { name, slug, description, version: '1.0.0', tags };
110
+ fs_1.default.writeFileSync(relayYamlPath, toYaml(yamlData));
65
111
  const result = {
66
112
  status: 'ok',
67
- install_path,
68
- api_url,
113
+ slug,
114
+ name,
115
+ description,
116
+ existing_dirs: existing,
117
+ created_dirs: created,
69
118
  };
70
119
  if (pretty) {
71
- console.log('\n\x1b[32m✓ relay 초기화 완료\x1b[0m');
72
- console.log(` 설치 경로: \x1b[36m${install_path}\x1b[0m`);
73
- console.log(` API URL: \x1b[36m${api_url}\x1b[0m`);
120
+ console.log(`\n\x1b[32m✓ 초기화 완료\x1b[0m\n`);
121
+ console.log(` 팀: \x1b[36m${name}\x1b[0m (${slug})`);
122
+ console.log(` 설명: ${description}`);
123
+ if (created.length > 0) {
124
+ console.log(` 생성됨: \x1b[33m${created.join(', ')}\x1b[0m`);
125
+ }
126
+ console.log(` 설정: \x1b[36mrelay.yaml\x1b[0m`);
127
+ console.log('\n 다음 단계:');
128
+ console.log(' 1. skills/, commands/ 등에 파일 추가');
129
+ console.log(' 2. \x1b[36mrelay publish\x1b[0m 로 마켓에 배포');
74
130
  }
75
131
  else {
76
132
  console.log(JSON.stringify(result));
@@ -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 사용 가능한 커맨드:');
@@ -24,9 +24,9 @@ function openBrowser(url) {
24
24
  // ignore browser open errors
25
25
  }
26
26
  }
27
- async function verifyToken(apiUrl, token) {
27
+ async function verifyToken(token) {
28
28
  try {
29
- const res = await fetch(`${apiUrl}/api/auth/me`, {
29
+ const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
30
30
  headers: { Authorization: `Bearer ${token}` },
31
31
  });
32
32
  if (!res.ok)
@@ -43,7 +43,6 @@ function waitForToken(port) {
43
43
  const url = new URL(req.url ?? '/', `http://localhost:${port}`);
44
44
  if (url.pathname === '/callback') {
45
45
  const token = url.searchParams.get('token');
46
- // Send a nice HTML response that auto-closes
47
46
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
48
47
  res.end(`<!DOCTYPE html>
49
48
  <html><head><title>RelayAX</title></head>
@@ -98,13 +97,11 @@ function registerLogin(program) {
98
97
  .action(async (opts) => {
99
98
  const pretty = program.opts().pretty ?? false;
100
99
  (0, config_js_1.ensureRelayDir)();
101
- const config = (0, config_js_1.loadConfig)();
102
- const apiUrl = config?.api_url ?? 'https://relayax.com';
103
100
  let token = opts.token;
104
101
  if (!token) {
105
102
  try {
106
103
  const port = await findAvailablePort();
107
- const loginUrl = `${apiUrl}/auth/cli-login?port=${port}`;
104
+ const loginUrl = `${config_js_1.API_URL}/auth/cli-login?port=${port}`;
108
105
  console.error('브라우저에서 로그인을 진행합니다...');
109
106
  openBrowser(loginUrl);
110
107
  token = await waitForToken(port);
@@ -115,13 +112,8 @@ function registerLogin(program) {
115
112
  process.exit(1);
116
113
  }
117
114
  }
118
- const user = await verifyToken(apiUrl, token);
119
- const updatedConfig = {
120
- install_path: config?.install_path ?? `${process.env.HOME}/.claude`,
121
- api_url: apiUrl,
122
- token,
123
- };
124
- (0, config_js_1.saveConfig)(updatedConfig);
115
+ const user = await verifyToken(token);
116
+ (0, config_js_1.saveToken)(token);
125
117
  const result = {
126
118
  status: 'ok',
127
119
  message: '로그인 성공',
@@ -7,16 +7,44 @@ exports.registerPublish = registerPublish;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const os_1 = __importDefault(require("os"));
10
- const readline_1 = require("readline");
11
10
  const tar_1 = require("tar");
12
11
  const config_js_1 = require("../lib/config.js");
13
12
  const VALID_DIRS = ['skills', 'agents', 'rules', 'commands'];
14
- function slugify(str) {
15
- return str
16
- .toLowerCase()
17
- .replace(/[^a-z0-9-]/g, '-')
18
- .replace(/-+/g, '-')
19
- .replace(/^-|-$/g, '');
13
+ function parseRelayYaml(content) {
14
+ const result = {};
15
+ const tags = [];
16
+ let inTags = false;
17
+ for (const line of content.split('\n')) {
18
+ const trimmed = line.trim();
19
+ if (inTags) {
20
+ if (trimmed.startsWith('- ')) {
21
+ tags.push(trimmed.slice(2).replace(/^["']|["']$/g, ''));
22
+ continue;
23
+ }
24
+ else {
25
+ inTags = false;
26
+ }
27
+ }
28
+ if (trimmed === 'tags: []') {
29
+ result.tags = [];
30
+ continue;
31
+ }
32
+ if (trimmed === 'tags:') {
33
+ inTags = true;
34
+ continue;
35
+ }
36
+ const match = trimmed.match(/^(\w+):\s*["']?(.+?)["']?$/);
37
+ if (match) {
38
+ result[match[1]] = match[2];
39
+ }
40
+ }
41
+ return {
42
+ name: String(result.name ?? ''),
43
+ slug: String(result.slug ?? ''),
44
+ description: String(result.description ?? ''),
45
+ version: String(result.version ?? '1.0.0'),
46
+ tags,
47
+ };
20
48
  }
21
49
  function detectCommands(teamDir) {
22
50
  const cmdDir = path_1.default.join(teamDir, 'commands');
@@ -30,7 +58,6 @@ function detectCommands(teamDir) {
30
58
  try {
31
59
  const content = fs_1.default.readFileSync(path_1.default.join(cmdDir, file), 'utf-8');
32
60
  const lines = content.split('\n').map((l) => l.trim()).filter(Boolean);
33
- // Check frontmatter for description
34
61
  if (lines[0] === '---') {
35
62
  const endIdx = lines.indexOf('---', 1);
36
63
  if (endIdx > 0) {
@@ -41,7 +68,6 @@ function detectCommands(teamDir) {
41
68
  }
42
69
  }
43
70
  else if (lines[0]) {
44
- // Use first line, strip leading # if heading
45
71
  description = lines[0].replace(/^#+\s*/, '');
46
72
  }
47
73
  }
@@ -58,34 +84,24 @@ function countDir(teamDir, dirName) {
58
84
  return 0;
59
85
  return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length;
60
86
  }
61
- async function prompt(question, defaultVal) {
62
- const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stderr });
63
- const hint = defaultVal ? ` (기본값: ${defaultVal})` : '';
64
- return new Promise((resolve) => {
65
- rl.question(`${question}${hint}: `, (answer) => {
66
- rl.close();
67
- resolve(answer.trim() || defaultVal || '');
68
- });
69
- });
70
- }
71
87
  async function createTarball(teamDir) {
72
88
  const tmpFile = path_1.default.join(os_1.default.tmpdir(), `relay-publish-${Date.now()}.tar.gz`);
73
- const parent = path_1.default.dirname(teamDir);
74
- const dirName = path_1.default.basename(teamDir);
89
+ // Only include valid dirs that exist
90
+ const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(teamDir, d)));
75
91
  await (0, tar_1.create)({
76
92
  gzip: true,
77
93
  file: tmpFile,
78
- cwd: parent,
79
- }, [dirName]);
94
+ cwd: teamDir,
95
+ }, [...dirsToInclude]);
80
96
  return tmpFile;
81
97
  }
82
- async function publishToApi(apiUrl, token, tarPath, metadata) {
98
+ async function publishToApi(token, tarPath, metadata) {
83
99
  const fileBuffer = fs_1.default.readFileSync(tarPath);
84
100
  const blob = new Blob([fileBuffer], { type: 'application/gzip' });
85
101
  const form = new FormData();
86
102
  form.append('package', blob, `${metadata.slug}-${metadata.version}.tar.gz`);
87
103
  form.append('metadata', JSON.stringify(metadata));
88
- const res = await fetch(`${apiUrl}/api/publish`, {
104
+ const res = await fetch(`${config_js_1.API_URL}/api/publish`, {
89
105
  method: 'POST',
90
106
  headers: { Authorization: `Bearer ${token}` },
91
107
  body: form,
@@ -99,73 +115,71 @@ async function publishToApi(apiUrl, token, tarPath, metadata) {
99
115
  }
100
116
  function registerPublish(program) {
101
117
  program
102
- .command('publish [dir]')
103
- .description('에이전트 팀을 마켓플레이스에 배포합니다')
104
- .option('--name <name>', ' 표시명')
105
- .option('--description <desc>', '한 줄 설명')
106
- .option('--tag <tag>', '태그 (여러 번 사용 가능)', (val, prev) => [...prev, val], [])
107
- .option('--token <token>', 'Supabase 인증 토큰')
108
- .option('--slug <slug>', 'URL용 슬러그 (기본: 디렉토리명)')
109
- .option('--version <ver>', '버전 (기본: 1.0.0)', '1.0.0')
110
- .action(async (dir, opts) => {
118
+ .command('publish')
119
+ .description('현재 패키지를 마켓플레이스에 배포합니다 (relay.yaml 필요)')
120
+ .option('--token <token>', '인증 토큰')
121
+ .action(async (opts) => {
111
122
  const pretty = program.opts().pretty ?? false;
112
- const teamDir = path_1.default.resolve(dir ?? process.cwd());
113
- if (!fs_1.default.existsSync(teamDir) || !fs_1.default.statSync(teamDir).isDirectory()) {
114
- console.error(JSON.stringify({ error: 'INVALID_DIR', message: `디렉토리를 찾을 수 없습니다: ${teamDir}` }));
123
+ const teamDir = process.cwd();
124
+ const relayYamlPath = path_1.default.join(teamDir, 'relay.yaml');
125
+ // Check relay.yaml exists
126
+ if (!fs_1.default.existsSync(relayYamlPath)) {
127
+ console.error(JSON.stringify({
128
+ error: 'NOT_INITIALIZED',
129
+ message: 'relay.yaml이 없습니다. 먼저 `relay init`을 실행하세요.',
130
+ }));
131
+ process.exit(1);
132
+ }
133
+ // Parse relay.yaml
134
+ const yamlContent = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
135
+ const config = parseRelayYaml(yamlContent);
136
+ if (!config.slug || !config.name || !config.description) {
137
+ console.error(JSON.stringify({
138
+ error: 'INVALID_CONFIG',
139
+ message: 'relay.yaml에 name, slug, description이 필요합니다.',
140
+ }));
115
141
  process.exit(1);
116
142
  }
117
143
  // Validate structure
118
- const hasDirs = VALID_DIRS.some((d) => fs_1.default.existsSync(path_1.default.join(teamDir, d)));
144
+ const hasDirs = VALID_DIRS.some((d) => {
145
+ const dirPath = path_1.default.join(teamDir, d);
146
+ if (!fs_1.default.existsSync(dirPath))
147
+ return false;
148
+ return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
149
+ });
119
150
  if (!hasDirs) {
120
151
  console.error(JSON.stringify({
121
- error: 'INVALID_STRUCTURE',
122
- message: `팀 디렉토리에는 skills/, agents/, rules/, commands/ 중 하나 이상이 있어야 합니다`,
152
+ error: 'EMPTY_PACKAGE',
153
+ message: 'skills/, agents/, rules/, commands/ 중 하나 이상에 파일이 있어야 합니다.',
123
154
  }));
124
155
  process.exit(1);
125
156
  }
126
157
  // Get token
127
- const config = (0, config_js_1.ensureConfig)();
128
- const token = opts.token ?? process.env.RELAY_TOKEN ?? config.token;
158
+ const token = opts.token ?? process.env.RELAY_TOKEN ?? (0, config_js_1.loadToken)();
129
159
  if (!token) {
130
160
  console.error(JSON.stringify({
131
161
  error: 'NO_TOKEN',
132
- message: '인증 토큰이 필요합니다. --token 플래그, RELAY_TOKEN 환경변수, 또는 `relay login`을 사용하세요.',
162
+ message: '인증이 필요합니다. `relay login`을 먼저 실행하세요.',
133
163
  }));
134
164
  process.exit(1);
135
165
  }
136
- // Auto-detect
137
- const autoSlug = slugify(path_1.default.basename(teamDir));
138
166
  const detectedCommands = detectCommands(teamDir);
139
167
  const components = {
140
168
  agents: countDir(teamDir, 'agents'),
141
169
  rules: countDir(teamDir, 'rules'),
142
170
  skills: countDir(teamDir, 'skills'),
143
171
  };
144
- // Gather metadata (prompt if not provided)
145
- const isInteractive = process.stdin.isTTY;
146
- const slug = opts.slug ?? (isInteractive ? await prompt('슬러그', autoSlug) : autoSlug);
147
- const name = opts.name ?? (isInteractive ? await prompt('팀 이름', path_1.default.basename(teamDir)) : path_1.default.basename(teamDir));
148
- const description = opts.description ?? (isInteractive ? await prompt('한 줄 설명') : '');
149
- let tags = opts.tag;
150
- if (tags.length === 0 && isInteractive) {
151
- const tagsInput = await prompt('태그 (쉼표 구분)', '');
152
- tags = tagsInput ? tagsInput.split(',').map((t) => t.trim()).filter(Boolean) : [];
153
- }
154
- if (!description) {
155
- console.error(JSON.stringify({ error: 'MISSING_DESCRIPTION', message: 'description은 필수입니다' }));
156
- process.exit(1);
157
- }
158
172
  const metadata = {
159
- slug,
160
- name,
161
- description,
162
- tags,
173
+ slug: config.slug,
174
+ name: config.name,
175
+ description: config.description,
176
+ tags: config.tags,
163
177
  commands: detectedCommands,
164
178
  components,
165
- version: opts.version,
179
+ version: config.version,
166
180
  };
167
181
  if (pretty) {
168
- console.error(`패키지 생성 중...`);
182
+ console.error(`패키지 생성 중... (${config.name} v${config.version})`);
169
183
  }
170
184
  let tarPath = null;
171
185
  try {
@@ -173,9 +187,9 @@ function registerPublish(program) {
173
187
  if (pretty) {
174
188
  console.error(`업로드 중...`);
175
189
  }
176
- const result = await publishToApi(config.api_url, token, tarPath, metadata);
190
+ const result = await publishToApi(token, tarPath, metadata);
177
191
  if (pretty) {
178
- console.log(`\n\x1b[32m✓ ${name} 배포 완료\x1b[0m v${result.version}`);
192
+ console.log(`\n\x1b[32m✓ ${config.name} 배포 완료\x1b[0m v${result.version}`);
179
193
  console.log(` 슬러그: \x1b[36m${result.slug}\x1b[0m`);
180
194
  console.log(` URL: \x1b[36m${result.url}\x1b[0m`);
181
195
  }
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.1",
3
+ "version": "0.1.3",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {