relayax-cli 0.2.22 → 0.2.23

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.
@@ -59,6 +59,10 @@ const SUCCESS_HTML = `<!DOCTYPE html>
59
59
  </body></html>`;
60
60
  function waitForToken(port) {
61
61
  return new Promise((resolve, reject) => {
62
+ const timeout = setTimeout(() => {
63
+ server.close();
64
+ reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
65
+ }, 5 * 60 * 1000);
62
66
  const server = http_1.default.createServer(async (req, res) => {
63
67
  const url = new URL(req.url ?? '/', `http://localhost:${port}`);
64
68
  if (url.pathname === '/callback') {
@@ -77,6 +81,7 @@ function waitForToken(port) {
77
81
  const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
78
82
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
79
83
  res.end(SUCCESS_HTML);
84
+ clearTimeout(timeout);
80
85
  server.close();
81
86
  if (token) {
82
87
  resolve({ token, refresh_token, expires_at });
@@ -91,11 +96,6 @@ function waitForToken(port) {
91
96
  }
92
97
  });
93
98
  server.listen(port, '127.0.0.1');
94
- // Timeout after 5 minutes
95
- setTimeout(() => {
96
- server.close();
97
- reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
98
- }, 5 * 60 * 1000);
99
99
  });
100
100
  }
101
101
  function findAvailablePort() {
@@ -481,6 +481,58 @@ function registerPublish(program) {
481
481
  }));
482
482
  process.exit(1);
483
483
  }
484
+ // Visibility validation: must be explicitly set
485
+ if (!config.visibility) {
486
+ if (isTTY) {
487
+ const { select: promptSelect } = await import('@inquirer/prompts');
488
+ console.error('\n\x1b[33m⚠ relay.yaml에 visibility가 설정되지 않았습니다.\x1b[0m');
489
+ // Show user's spaces to help decide
490
+ try {
491
+ const { fetchMySpaces } = await import('./spaces.js');
492
+ const spaceToken = opts.token ?? process.env.RELAY_TOKEN ?? await (0, config_js_1.getValidToken)();
493
+ if (spaceToken) {
494
+ const spaces = await fetchMySpaces(spaceToken);
495
+ const nonPersonal = spaces.filter((s) => !s.is_personal);
496
+ if (nonPersonal.length > 0) {
497
+ console.error(`\n 내 Space:`);
498
+ for (const s of nonPersonal) {
499
+ console.error(` \x1b[36m${s.slug}\x1b[0m — ${s.name}`);
500
+ }
501
+ console.error(`\n 비공개로 설정하면 위 Space 멤버만 접근할 수 있습니다.\n`);
502
+ }
503
+ }
504
+ }
505
+ catch {
506
+ // Space 조회 실패는 무시 — visibility 선택은 계속 진행
507
+ }
508
+ config.visibility = await promptSelect({
509
+ message: '공개 범위를 선택하세요:',
510
+ choices: [
511
+ { name: '공개 — 마켓플레이스에 누구나 검색·설치', value: 'public' },
512
+ { name: '비공개 — Space 멤버만 접근', value: 'private' },
513
+ ],
514
+ });
515
+ // Save back to relay.yaml
516
+ const yamlData = js_yaml_1.default.load(yamlContent);
517
+ yamlData.visibility = config.visibility;
518
+ fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
519
+ console.error(` → relay.yaml에 visibility: ${config.visibility} 저장됨\n`);
520
+ }
521
+ else {
522
+ console.error(JSON.stringify({
523
+ error: 'MISSING_VISIBILITY',
524
+ message: 'relay.yaml에 visibility (public 또는 private)를 설정해주세요.',
525
+ }));
526
+ process.exit(1);
527
+ }
528
+ }
529
+ // Confirm visibility before publish
530
+ if (isTTY) {
531
+ const visLabel = config.visibility === 'public'
532
+ ? '\x1b[32m공개\x1b[0m (마켓플레이스에 누구나 검색·설치)'
533
+ : '\x1b[33m비공개\x1b[0m (Space 멤버만 접근)';
534
+ console.error(`공개 범위: ${visLabel}`);
535
+ }
484
536
  // Profile hint
485
537
  if (isTTY) {
486
538
  console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile');
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ export interface SpaceInfo {
3
+ id: string;
4
+ slug: string;
5
+ name: string;
6
+ description: string | null;
7
+ is_personal: boolean;
8
+ role: string;
9
+ }
10
+ export declare function fetchMySpaces(token: string): Promise<SpaceInfo[]>;
11
+ export declare function registerSpaces(program: Command): void;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchMySpaces = fetchMySpaces;
4
+ exports.registerSpaces = registerSpaces;
5
+ const config_js_1 = require("../lib/config.js");
6
+ async function fetchMySpaces(token) {
7
+ const res = await fetch(`${config_js_1.API_URL}/api/spaces`, {
8
+ headers: { Authorization: `Bearer ${token}` },
9
+ signal: AbortSignal.timeout(8000),
10
+ });
11
+ if (!res.ok) {
12
+ throw new Error(`Space 목록 조회 실패 (${res.status})`);
13
+ }
14
+ return (await res.json());
15
+ }
16
+ function registerSpaces(program) {
17
+ program
18
+ .command('spaces')
19
+ .description('내 Space 목록을 확인합니다')
20
+ .action(async () => {
21
+ const json = program.opts().json ?? false;
22
+ const token = await (0, config_js_1.getValidToken)();
23
+ if (!token) {
24
+ if (json) {
25
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다. relay login을 먼저 실행하세요.' }));
26
+ }
27
+ else {
28
+ console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
29
+ console.error(' relay login을 먼저 실행하세요.');
30
+ }
31
+ process.exit(1);
32
+ }
33
+ try {
34
+ const spaces = await fetchMySpaces(token);
35
+ if (json) {
36
+ console.log(JSON.stringify({ spaces }));
37
+ return;
38
+ }
39
+ const personal = spaces.find((s) => s.is_personal);
40
+ const others = spaces.filter((s) => !s.is_personal);
41
+ if (personal) {
42
+ console.log(`\n\x1b[90m개인 스페이스:\x1b[0m`);
43
+ console.log(` \x1b[36m${personal.slug}\x1b[0m \x1b[1m${personal.name}\x1b[0m`);
44
+ }
45
+ if (others.length > 0) {
46
+ console.log(`\n\x1b[1m내 Space\x1b[0m (${others.length}개):\n`);
47
+ for (const s of others) {
48
+ const role = s.role === 'owner' ? '\x1b[33m소유자\x1b[0m'
49
+ : s.role === 'admin' ? '\x1b[36m관리자\x1b[0m'
50
+ : '\x1b[90m멤버\x1b[0m';
51
+ const desc = s.description
52
+ ? ` \x1b[90m${s.description.length > 40 ? s.description.slice(0, 40) + '...' : s.description}\x1b[0m`
53
+ : '';
54
+ console.log(` \x1b[36m${s.slug}\x1b[0m \x1b[1m${s.name}\x1b[0m ${role}${desc}`);
55
+ }
56
+ }
57
+ if (!personal && others.length === 0) {
58
+ console.log('\nSpace가 없습니다.');
59
+ console.log('\x1b[33m💡 Space를 만들려면: www.relayax.com/spaces/new\x1b[0m');
60
+ }
61
+ if (others.length > 0) {
62
+ console.log(`\n\x1b[33m💡 Space 팀 목록: relay list --space <slug>\x1b[0m`);
63
+ console.log(`\x1b[33m💡 비공개 배포: relay.yaml에 visibility: private 설정 후 relay publish\x1b[0m`);
64
+ }
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ if (json) {
69
+ console.error(JSON.stringify({ error: 'FETCH_FAILED', message }));
70
+ }
71
+ else {
72
+ console.error(`\x1b[31m오류: ${message}\x1b[0m`);
73
+ }
74
+ process.exit(1);
75
+ }
76
+ });
77
+ }
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ const check_update_js_1 = require("./commands/check-update.js");
17
17
  const follow_js_1 = require("./commands/follow.js");
18
18
  const changelog_js_1 = require("./commands/changelog.js");
19
19
  const join_js_1 = require("./commands/join.js");
20
+ const spaces_js_1 = require("./commands/spaces.js");
20
21
  // eslint-disable-next-line @typescript-eslint/no-var-requires
21
22
  const pkg = require('../package.json');
22
23
  const program = new commander_1.Command();
@@ -40,4 +41,5 @@ program
40
41
  (0, follow_js_1.registerFollow)(program);
41
42
  (0, changelog_js_1.registerChangelog)(program);
42
43
  (0, join_js_1.registerJoin)(program);
44
+ (0, spaces_js_1.registerSpaces)(program);
43
45
  program.parse();
@@ -391,7 +391,17 @@ requires:
391
391
  - tags: 팀 특성에 맞는 태그를 추천합니다.
392
392
  - 사용자에게 확인: "이대로 배포할까요?"
393
393
 
394
- ### 7. .relay/relay.yaml 업데이트
394
+ ### 7. 공개 범위 확인 (필수)
395
+ - .relay/relay.yaml에 \`visibility\`가 반드시 설정되어 있어야 합니다.
396
+ - 설정되어 있지 않으면 빌더에게 반드시 물어봅니다:
397
+ - "공개 (마켓플레이스에 누구나 검색·설치 가능)" vs "비공개 (Space 멤버만 접근)"
398
+ - 선택 결과를 relay.yaml에 저장합니다.
399
+ - 이미 설정되어 있으면 현재 값을 확인합니다:
400
+ - 공개인 경우: "⚠ 이 팀은 **공개**로 설정되어 있어 마켓플레이스에 노출됩니다. 맞나요?"
401
+ - 비공개인 경우: "이 팀은 **비공개**로 설정되어 Space 멤버만 접근 가능합니다."
402
+ - 빌더가 변경을 원하면 relay.yaml을 업데이트합니다.
403
+
404
+ ### 8. .relay/relay.yaml 업데이트
395
405
  - 메타데이터, requires, 포트폴리오 슬롯을 .relay/relay.yaml에 반영합니다.
396
406
 
397
407
  \`\`\`yaml
@@ -406,7 +416,7 @@ portfolio:
406
416
  title: "카드뉴스 예시"
407
417
  \`\`\`
408
418
 
409
- ### 8. 배포
419
+ ### 9. 배포
410
420
  - \`relay publish\` 명령어를 실행합니다.
411
421
  - 배포 결과와 마켓플레이스 URL을 보여줍니다.
412
422
  ${BUSINESS_CARD_FORMAT}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {