tokenphage 2026.7.1-BETA → 2026.7.2-BETA
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/auth.js +0 -11
- package/src/index.js +5 -9
- package/src/lib/config-schema.js +0 -2
- package/src/lib/update-check.js +19 -66
- package/src/ui.js +5 -4
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ npm install -g tokenphage@latest
|
|
|
54
54
|
|
|
55
55
|
### ⬆️ 업데이트
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
대시보드를 열면 새 버전이 있는지 확인합니다. 새 버전이 확인되면 대시보드 맨 위에 안내 배너가 뜨고, 메뉴에 **⬆ Update to …** 항목이 추가됩니다. 이 항목을 고르면 최신 버전이 자동으로 설치됩니다(전역 설치가 아니면 정확한 수동 명령을 안내).
|
|
58
58
|
|
|
59
59
|
직접 올리고 싶다면 언제든 아래 명령을 실행하면 됩니다.
|
|
60
60
|
|
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -93,17 +93,6 @@ export function clearLastSyncDate() {
|
|
|
93
93
|
saveConfig(rest);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** 업데이트 캐시(npm 최신버전·마지막 조회시각)를 반환한다. 없으면 각각 null. */
|
|
97
|
-
export function getUpdateCache() {
|
|
98
|
-
const cfg = loadConfig();
|
|
99
|
-
return { latestVersion: cfg.latestVersion ?? null, lastUpdateCheck: cfg.lastUpdateCheck ?? null };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** npm 최신버전과 조회시각(ISO)을 다른 설정을 보존한 채 갱신 저장한다. */
|
|
103
|
-
export function saveUpdateCache(latestVersion, lastUpdateCheck) {
|
|
104
|
-
saveConfig({ ...loadConfig(), latestVersion, lastUpdateCheck });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
96
|
// Gist 소유권 검증용 파일명 — 사용자가 이 이름으로 Gist를 만들어야 한다.
|
|
108
97
|
export const VERIFICATION_FILE = 'tokenphage.txt';
|
|
109
98
|
|
package/src/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { accent } from './gradient.js';
|
|
|
14
14
|
import { localDateOf } from './lib/dates.js';
|
|
15
15
|
import { claudeDirCandidates } from './parser.js';
|
|
16
16
|
import { checkClaudeRetention, applyRetentionSetting, DEFAULT_CLEANUP_DAYS } from './lib/retention.js';
|
|
17
|
-
import {
|
|
17
|
+
import { checkLatestVersion, getUpdateInfo } from './lib/update-check.js';
|
|
18
18
|
import { runUpdate } from './lib/updater.js';
|
|
19
19
|
import { confirm } from '@inquirer/prompts';
|
|
20
20
|
import chalk from 'chalk';
|
|
@@ -149,10 +149,6 @@ async function runSync() {
|
|
|
149
149
|
await syncRecords(API_BASE, token, deviceId, records);
|
|
150
150
|
saveLastSyncDate(today);
|
|
151
151
|
console.log('Done! Badge will update shortly.');
|
|
152
|
-
|
|
153
|
-
// 이미 네트워크를 쓰는 지점이므로 여기서 npm 최신버전 캐시를 갱신한다(TTL 게이트 내장, 실패 무해).
|
|
154
|
-
// 04:00 자동 sync가 이 경로로 캐시를 채워, 다음 대시보드 진입 시 업데이트 배너가 뜬다.
|
|
155
|
-
await refreshCache(Date.now());
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
/**
|
|
@@ -245,12 +241,12 @@ program.action(async () => {
|
|
|
245
241
|
|
|
246
242
|
// 인증 이후 메뉴는 최신 설정이 필요하므로 여기서 동적 import (상단 정적 import과 별개)
|
|
247
243
|
const { loadConfig } = await import('./auth.js');
|
|
248
|
-
//
|
|
249
|
-
|
|
244
|
+
// TUI 진입 시 npm 최신버전을 1회 조회(짧은 타임아웃). 세션 동안 재사용해 배너/메뉴에 반영한다.
|
|
245
|
+
const latest = await checkLatestVersion();
|
|
250
246
|
// 대시보드 루프: 액션 수행 후 메인으로 복귀, Exit/Esc 에서만 종료
|
|
251
247
|
while (true) {
|
|
252
248
|
const cfg = loadConfig();
|
|
253
|
-
const action = await promptDashboard(cfg);
|
|
249
|
+
const action = await promptDashboard(cfg, latest);
|
|
254
250
|
if (action === 'exit') break;
|
|
255
251
|
if (action === 'sync') await runSync();
|
|
256
252
|
else if (action === 'install-hook') await runInstallHook();
|
|
@@ -258,7 +254,7 @@ program.action(async () => {
|
|
|
258
254
|
else if (action === 'advanced') await runAdvanced(API_BASE, cfg);
|
|
259
255
|
else if (action === 'update') {
|
|
260
256
|
// 업데이트 성공 시 현재 프로세스는 여전히 구버전 코드 → 재실행 안내 후 종료.
|
|
261
|
-
const { hasUpdate
|
|
257
|
+
const { hasUpdate } = getUpdateInfo(version, latest);
|
|
262
258
|
if (hasUpdate) {
|
|
263
259
|
const result = await runUpdate(latest);
|
|
264
260
|
await pauseForEnter();
|
package/src/lib/config-schema.js
CHANGED
|
@@ -84,9 +84,7 @@ export function normalizeConfig(raw, today) {
|
|
|
84
84
|
dropIf('deviceId', isUuid); // 없으면 getOrCreateDeviceId가 재발급
|
|
85
85
|
dropIf('lastSyncDate', (v) => isYmdDate(v, today)); // 없으면 전체 재파싱
|
|
86
86
|
dropIf('hookInstalledAt', isIsoDate);
|
|
87
|
-
dropIf('lastUpdateCheck', isIsoDate);
|
|
88
87
|
dropIf('hookInstalled', (v) => typeof v === 'boolean');
|
|
89
|
-
dropIf('latestVersion', (v) => typeof v === 'string');
|
|
90
88
|
dropIf('claudePaths', isStringArray);
|
|
91
89
|
dropIf('hookMeta', (v) => v != null && typeof v === 'object' && !Array.isArray(v));
|
|
92
90
|
|
package/src/lib/update-check.js
CHANGED
|
@@ -1,53 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI 업데이트 검사: npm 레지스트리에서
|
|
3
|
-
* 현재 버전과
|
|
2
|
+
* CLI 업데이트 검사: TUI(대시보드) 진입 시 npm 레지스트리에서 latest를 1회 조회하고,
|
|
3
|
+
* 현재 버전과 문자열이 다르면 업데이트가 있다고 판정한다.
|
|
4
4
|
*
|
|
5
5
|
* 설계 원칙
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - 어떤 실패(네트워크·404·파싱·옵트아웃)도 조용히 흘려보내
|
|
6
|
+
* - update 제안은 대화형 TUI에서만 뜬다. 비대화형(sync·스크립트)은 대상 아님 → 캐시/TTL 불필요.
|
|
7
|
+
* - checkLatestVersion()을 TUI 진입 시 1회 await로 호출(짧은 타임아웃), 결과를 세션 메모리에 담아 배너 판정에 쓴다.
|
|
8
|
+
* - 어떤 실패(네트워크·404·파싱·옵트아웃)도 조용히 null로 흘려보내 대시보드를 절대 막지 않는다.
|
|
9
9
|
*/
|
|
10
|
-
import { loadConfig, saveUpdateCache } from '../auth.js';
|
|
11
10
|
|
|
12
11
|
// 배포 패키지명 — 레지스트리 URL과 `npm i -g` 대상에서 공유한다.
|
|
13
12
|
export const PACKAGE_NAME = 'tokenphage';
|
|
14
13
|
// npm 레지스트리 latest 매니페스트(공개·무인증). { "version": "", ... } 반환, 미배포면 404.
|
|
15
14
|
export const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
const FETCH_TIMEOUT_MS = 2500; // 레지스트리 지연이 sync를 끌지 않도록 상한
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* CalVer "YYYY.M.PATCH" 두 버전을 숫자 3파트로 비교한다.
|
|
22
|
-
* @returns {-1|0|1} a<b → -1, a==b → 0, a>b → 1. 어느 한쪽이라도 비숫자(dev 등)면 0(=차이 없음)으로 안전 폴백.
|
|
23
|
-
*/
|
|
24
|
-
export function compareVersion(a, b) {
|
|
25
|
-
const pa = String(a).split('.').map(Number);
|
|
26
|
-
const pb = String(b).split('.').map(Number);
|
|
27
|
-
// CalVer는 정확히 3파트여야 한다. 형식이 어긋나면(2파트/4파트/dev 등) 비교 불가로 보고 0(=업데이트 없음) 폴백.
|
|
28
|
-
if (pa.length !== 3 || pb.length !== 3) return 0;
|
|
29
|
-
for (let i = 0; i < 3; i++) {
|
|
30
|
-
const x = pa[i];
|
|
31
|
-
const y = pb[i];
|
|
32
|
-
if (Number.isNaN(x) || Number.isNaN(y)) return 0; // 비CalVer → 비교 불가 → 업데이트 없음
|
|
33
|
-
if (x > y) return 1;
|
|
34
|
-
if (x < y) return -1;
|
|
35
|
-
}
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 마지막 조회 이후 TTL(24h)이 지났는지 판정한다. now는 인자로 주입해 테스트 결정성을 확보한다.
|
|
41
|
-
* @param {string|null} lastUpdateCheck 마지막 조회 ISO 시각 (없으면 null)
|
|
42
|
-
* @param {number} now 현재 epoch ms
|
|
43
|
-
* @returns {boolean} 조회가 필요하면 true
|
|
44
|
-
*/
|
|
45
|
-
export function shouldCheck(lastUpdateCheck, now) {
|
|
46
|
-
if (!lastUpdateCheck) return true;
|
|
47
|
-
const last = Date.parse(lastUpdateCheck);
|
|
48
|
-
if (Number.isNaN(last)) return true; // 손상된 시각 → 재조회
|
|
49
|
-
return now - last >= CHECK_INTERVAL_MS;
|
|
50
|
-
}
|
|
16
|
+
const FETCH_TIMEOUT_MS = 1500; // TUI 진입 조회 상한 — 오프라인/지연 시 이 시간 후 배너 없이 진행
|
|
51
17
|
|
|
52
18
|
/** 업데이트 알림을 끄는 환경인지. 옵트아웃 env 또는 dev(TOKENPHAGE_API 세팅)면 조회·표시 모두 스킵. */
|
|
53
19
|
export function isUpdateNotifierDisabled() {
|
|
@@ -55,23 +21,27 @@ export function isUpdateNotifierDisabled() {
|
|
|
55
21
|
}
|
|
56
22
|
|
|
57
23
|
/**
|
|
58
|
-
*
|
|
24
|
+
* 조회한 latest와 현재 버전을 비교해 업데이트 정보를 반환한다. 순수 함수(네트워크·env·캐시 없음).
|
|
25
|
+
* update 액션은 항상 `@latest`를 설치하므로 "설치본 ≠ latest"가 곧 "업데이트하면 바뀜"과 일치한다.
|
|
26
|
+
* 프리릴리스 접미사(-BETA)도 파싱 없이 문자열 비교라 안전하다.
|
|
59
27
|
* @param {string} currentVersion 현재 설치 버전(package.json)
|
|
60
|
-
* @param {
|
|
28
|
+
* @param {string|null|undefined} latest checkLatestVersion() 결과(조회 실패/옵트아웃 시 null)
|
|
61
29
|
* @returns {{hasUpdate:boolean, latest?:string, current?:string}}
|
|
62
30
|
*/
|
|
63
|
-
export function getUpdateInfo(currentVersion,
|
|
64
|
-
if (
|
|
65
|
-
const latest = cfg?.latestVersion;
|
|
66
|
-
if (!latest) return { hasUpdate: false };
|
|
67
|
-
if (compareVersion(latest, currentVersion) > 0) {
|
|
31
|
+
export function getUpdateInfo(currentVersion, latest) {
|
|
32
|
+
if (latest && latest !== currentVersion) {
|
|
68
33
|
return { hasUpdate: true, latest, current: currentVersion };
|
|
69
34
|
}
|
|
70
35
|
return { hasUpdate: false };
|
|
71
36
|
}
|
|
72
37
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
38
|
+
/**
|
|
39
|
+
* npm 레지스트리에서 latest 버전 문자열을 1회 조회한다. TUI 진입 시 한 번 await로 호출해 세션 동안 재사용한다.
|
|
40
|
+
* 옵트아웃/실패(타임아웃·404·파싱)는 모두 null — 절대 throw하지 않는다.
|
|
41
|
+
* @returns {Promise<string|null>}
|
|
42
|
+
*/
|
|
43
|
+
export async function checkLatestVersion() {
|
|
44
|
+
if (isUpdateNotifierDisabled()) return null; // 옵트아웃/dev → 조회 스킵
|
|
75
45
|
const controller = new AbortController();
|
|
76
46
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
77
47
|
try {
|
|
@@ -85,20 +55,3 @@ async function fetchLatestFromNpm() {
|
|
|
85
55
|
clearTimeout(timer);
|
|
86
56
|
}
|
|
87
57
|
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* TTL이 지났으면 npm 최신버전을 조회해 config 캐시를 갱신한다. 절대 throw하지 않는다.
|
|
91
|
-
* @param {number} now 현재 epoch ms (Date.now()를 주입)
|
|
92
|
-
*/
|
|
93
|
-
export async function refreshCache(now) {
|
|
94
|
-
try {
|
|
95
|
-
if (isUpdateNotifierDisabled()) return;
|
|
96
|
-
const cfg = loadConfig();
|
|
97
|
-
if (!shouldCheck(cfg.lastUpdateCheck, now)) return;
|
|
98
|
-
const latest = await fetchLatestFromNpm();
|
|
99
|
-
if (!latest) return;
|
|
100
|
-
saveUpdateCache(latest, new Date(now).toISOString());
|
|
101
|
-
} catch {
|
|
102
|
-
// 캐시 갱신 실패는 무해 — 다음 기회에 재시도한다.
|
|
103
|
-
}
|
|
104
|
-
}
|
package/src/ui.js
CHANGED
|
@@ -127,14 +127,15 @@ export async function promptFirstRun() {
|
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
129
|
* 인증된 사용자를 위한 대시보드 메뉴.
|
|
130
|
-
* @param {{ token: string, lastSyncDate: string|null, hookInstalled: boolean, hookMeta: object|null
|
|
130
|
+
* @param {{ token: string, lastSyncDate: string|null, hookInstalled: boolean, hookMeta: object|null }} cfg
|
|
131
|
+
* @param {string|null} latest TUI 진입 시 조회한 npm 최신버전(조회 실패/옵트아웃 시 null)
|
|
131
132
|
* @returns {'update' | 'sync' | 'install-hook' | 'uninstall-hook' | 'advanced' | 'exit'}
|
|
132
133
|
*/
|
|
133
|
-
export async function promptDashboard(cfg) {
|
|
134
|
+
export async function promptDashboard(cfg, latest) {
|
|
134
135
|
printWelcome();
|
|
135
136
|
|
|
136
|
-
//
|
|
137
|
-
const update = getUpdateInfo(version,
|
|
137
|
+
// TUI 진입 시 조회한 npm 최신버전과 현재 버전을 비교해, 다를 때만 민트 배너를 출력한다.
|
|
138
|
+
const update = getUpdateInfo(version, latest);
|
|
138
139
|
if (update.hasUpdate) {
|
|
139
140
|
const bar = accent(' ┃ ');
|
|
140
141
|
console.log(bar + accent(`⬆ Update available v${update.current} → v${update.latest}`));
|