relayax-cli 0.1.995 → 0.1.996

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.
@@ -28,7 +28,7 @@ function registerInstall(program) {
28
28
  // 2. Visibility check
29
29
  const visibility = team.visibility ?? 'public';
30
30
  if (visibility === 'login-only' || visibility === 'invite-only') {
31
- const token = (0, config_js_1.loadToken)();
31
+ const token = await (0, config_js_1.getValidToken)();
32
32
  if (!token) {
33
33
  console.error(JSON.stringify({
34
34
  error: 'LOGIN_REQUIRED',
@@ -123,7 +123,7 @@ function registerInstall(program) {
123
123
  console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
124
124
  }
125
125
  // Follow prompt (only when logged in)
126
- const token = (0, config_js_1.loadToken)();
126
+ const token = await (0, config_js_1.getValidToken)();
127
127
  if (authorUsername && token) {
128
128
  try {
129
129
  const { confirm } = await import('@inquirer/prompts');
@@ -43,6 +43,9 @@ 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
+ const refresh_token = url.searchParams.get('refresh_token') ?? undefined;
47
+ const expires_at_raw = url.searchParams.get('expires_at');
48
+ const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
46
49
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
47
50
  res.end(`<!DOCTYPE html>
48
51
  <html><head><title>RelayAX</title></head>
@@ -55,7 +58,7 @@ function waitForToken(port) {
55
58
  </body></html>`);
56
59
  server.close();
57
60
  if (token) {
58
- resolve(token);
61
+ resolve({ token, refresh_token, expires_at });
59
62
  }
60
63
  else {
61
64
  reject(new Error('토큰이 전달되지 않았습니다'));
@@ -97,14 +100,19 @@ function registerLogin(program) {
97
100
  .action(async (opts) => {
98
101
  const json = program.opts().json ?? false;
99
102
  (0, config_js_1.ensureGlobalRelayDir)();
100
- let token = opts.token;
101
- if (!token) {
103
+ let accessToken = opts.token;
104
+ let refreshToken;
105
+ let expiresAt;
106
+ if (!accessToken) {
102
107
  try {
103
108
  const port = await findAvailablePort();
104
109
  const loginUrl = `${config_js_1.API_URL}/auth/cli-login?port=${port}`;
105
110
  console.error('브라우저에서 로그인을 진행합니다...');
106
111
  openBrowser(loginUrl);
107
- token = await waitForToken(port);
112
+ const loginResult = await waitForToken(port);
113
+ accessToken = loginResult.token;
114
+ refreshToken = loginResult.refresh_token;
115
+ expiresAt = loginResult.expires_at;
108
116
  }
109
117
  catch (err) {
110
118
  const msg = err instanceof Error ? err.message : '로그인 실패';
@@ -112,8 +120,12 @@ function registerLogin(program) {
112
120
  process.exit(1);
113
121
  }
114
122
  }
115
- const user = await verifyToken(token);
116
- (0, config_js_1.saveToken)(token);
123
+ const user = await verifyToken(accessToken);
124
+ (0, config_js_1.saveTokenData)({
125
+ access_token: accessToken,
126
+ ...(refreshToken ? { refresh_token: refreshToken } : {}),
127
+ ...(expiresAt ? { expires_at: expiresAt } : {}),
128
+ });
117
129
  const result = {
118
130
  status: 'ok',
119
131
  message: '로그인 성공',
@@ -379,7 +379,7 @@ function registerPublish(program) {
379
379
  process.exit(1);
380
380
  }
381
381
  // Get token (checked before tarball creation)
382
- const token = opts.token ?? process.env.RELAY_TOKEN ?? (0, config_js_1.loadToken)();
382
+ const token = opts.token ?? process.env.RELAY_TOKEN ?? await (0, config_js_1.getValidToken)();
383
383
  if (!token) {
384
384
  console.error(JSON.stringify({
385
385
  error: 'NO_TOKEN',
@@ -31,7 +31,7 @@ function registerStatus(program) {
31
31
  const json = program.opts().json ?? false;
32
32
  const projectPath = process.cwd();
33
33
  // 1. 로그인 상태
34
- const token = (0, config_js_1.loadToken)();
34
+ const token = await (0, config_js_1.getValidToken)();
35
35
  let username;
36
36
  if (token) {
37
37
  username = await resolveUsername(token);
@@ -51,7 +51,7 @@ function registerUpdate(program) {
51
51
  // Visibility check
52
52
  const visibility = team.visibility ?? 'public';
53
53
  if (visibility === 'login-only') {
54
- const token = (0, config_js_1.loadToken)();
54
+ const token = await (0, config_js_1.getValidToken)();
55
55
  if (!token) {
56
56
  console.error('이 팀은 로그인이 필요합니다. `relay login`을 먼저 실행하세요.');
57
57
  process.exit(1);
package/dist/lib/api.js CHANGED
@@ -54,7 +54,7 @@ async function resolveSlugFromServer(name) {
54
54
  return data.results;
55
55
  }
56
56
  async function followBuilder(username) {
57
- const token = (0, config_js_1.loadToken)();
57
+ const token = await (0, config_js_1.getValidToken)();
58
58
  const headers = {
59
59
  'Content-Type': 'application/json',
60
60
  };
@@ -11,8 +11,23 @@ export declare function getInstallPath(override?: string): string;
11
11
  export declare function ensureGlobalRelayDir(): void;
12
12
  /** cwd/.relay/ — 프로젝트 로컬 (installed.json, teams/) */
13
13
  export declare function ensureProjectRelayDir(): void;
14
+ export interface TokenData {
15
+ access_token: string;
16
+ refresh_token?: string;
17
+ expires_at?: number;
18
+ }
19
+ export declare function loadTokenData(): TokenData | undefined;
14
20
  export declare function loadToken(): string | undefined;
21
+ export declare function saveTokenData(data: TokenData): void;
15
22
  export declare function saveToken(token: string): void;
23
+ /**
24
+ * 유효한 access_token을 반환한다.
25
+ * 1. 저장된 토큰이 없으면 undefined
26
+ * 2. expires_at이 아직 유효하면 access_token 반환
27
+ * 3. 만료되었으면 refresh_token으로 갱신 시도
28
+ * 4. 갱신 실패 시 undefined (재로그인 필요)
29
+ */
30
+ export declare function getValidToken(): Promise<string | undefined>;
16
31
  /** 프로젝트 로컬 installed.json 읽기 (unscoped 키 자동 마이그레이션) */
17
32
  export declare function loadInstalled(): InstalledRegistry;
18
33
  /**
@@ -7,8 +7,11 @@ exports.API_URL = void 0;
7
7
  exports.getInstallPath = getInstallPath;
8
8
  exports.ensureGlobalRelayDir = ensureGlobalRelayDir;
9
9
  exports.ensureProjectRelayDir = ensureProjectRelayDir;
10
+ exports.loadTokenData = loadTokenData;
10
11
  exports.loadToken = loadToken;
12
+ exports.saveTokenData = saveTokenData;
11
13
  exports.saveToken = saveToken;
14
+ exports.getValidToken = getValidToken;
12
15
  exports.loadInstalled = loadInstalled;
13
16
  exports.migrateInstalled = migrateInstalled;
14
17
  exports.saveInstalled = saveInstalled;
@@ -52,20 +55,69 @@ function ensureProjectRelayDir() {
52
55
  fs_1.default.mkdirSync(dir, { recursive: true });
53
56
  }
54
57
  }
55
- function loadToken() {
58
+ function loadTokenData() {
56
59
  const tokenFile = path_1.default.join(GLOBAL_RELAY_DIR, 'token');
57
60
  if (!fs_1.default.existsSync(tokenFile))
58
61
  return undefined;
59
62
  try {
60
- return fs_1.default.readFileSync(tokenFile, 'utf-8').trim() || undefined;
63
+ const raw = fs_1.default.readFileSync(tokenFile, 'utf-8').trim();
64
+ if (!raw)
65
+ return undefined;
66
+ // JSON 형식 (새 포맷)
67
+ if (raw.startsWith('{')) {
68
+ return JSON.parse(raw);
69
+ }
70
+ // plain text (기존 포맷) — 호환성 유지
71
+ return { access_token: raw };
61
72
  }
62
73
  catch {
63
74
  return undefined;
64
75
  }
65
76
  }
77
+ function loadToken() {
78
+ return loadTokenData()?.access_token;
79
+ }
80
+ function saveTokenData(data) {
81
+ ensureGlobalRelayDir();
82
+ fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), JSON.stringify(data));
83
+ }
66
84
  function saveToken(token) {
67
85
  ensureGlobalRelayDir();
68
- fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), token);
86
+ fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), JSON.stringify({ access_token: token }));
87
+ }
88
+ /**
89
+ * 유효한 access_token을 반환한다.
90
+ * 1. 저장된 토큰이 없으면 undefined
91
+ * 2. expires_at이 아직 유효하면 access_token 반환
92
+ * 3. 만료되었으면 refresh_token으로 갱신 시도
93
+ * 4. 갱신 실패 시 undefined (재로그인 필요)
94
+ */
95
+ async function getValidToken() {
96
+ const data = loadTokenData();
97
+ if (!data)
98
+ return undefined;
99
+ // expires_at이 없거나 아직 유효하면 (30초 여유) 그대로 사용
100
+ if (!data.expires_at || data.expires_at > Date.now() / 1000 + 30) {
101
+ return data.access_token;
102
+ }
103
+ // 만료됨 — refresh 시도
104
+ if (!data.refresh_token)
105
+ return undefined;
106
+ try {
107
+ const res = await fetch(`${exports.API_URL}/api/auth/refresh`, {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ refresh_token: data.refresh_token }),
111
+ });
112
+ if (!res.ok)
113
+ return undefined;
114
+ const refreshed = (await res.json());
115
+ saveTokenData(refreshed);
116
+ return refreshed.access_token;
117
+ }
118
+ catch {
119
+ return undefined;
120
+ }
69
121
  }
70
122
  /** 프로젝트 로컬 installed.json 읽기 (unscoped 키 자동 마이그레이션) */
71
123
  function loadInstalled() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.1.995",
3
+ "version": "0.1.996",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {