pinglet-cli 0.3.0 → 0.4.0
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.ja.md +7 -1
- package/README.ko.md +7 -1
- package/README.md +7 -1
- package/dist/commands/refresh.js +3 -0
- package/dist/config.js +3 -1
- package/dist/render.js +14 -14
- package/dist/update.js +122 -0
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -54,7 +54,13 @@ pinglet post "メッセージ"
|
|
|
54
54
|
🟢 いま41個のターミナルと一緒にコーディング中
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
statusline
|
|
57
|
+
statusline の言語はシステム言語に応じて自動選択されます(韓国語/日本語、それ以外は英語)。言語で判定できない場合はタイムゾーン(ソウル/東京)で推定します。
|
|
58
|
+
|
|
59
|
+
## 自動アップデート
|
|
60
|
+
|
|
61
|
+
新しいバージョンが出ると、バックグラウンドで1日1回チェックして自動的に
|
|
62
|
+
アップデートします(npm グローバルインストールのみ — pnpm/yarn は対象外)。
|
|
63
|
+
無効にするには `~/.pinglet/config.json` に `"autoUpdate": false` を追加してください。
|
|
58
64
|
|
|
59
65
|
## コマンド
|
|
60
66
|
|
package/README.ko.md
CHANGED
|
@@ -54,7 +54,13 @@ pinglet post "메시지"
|
|
|
54
54
|
🟢 지금 41개 터미널과 함께 코딩 중
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
statusline 언어는
|
|
57
|
+
statusline 언어는 시스템 언어에 따라 자동 선택됩니다(한국어/일본어, 그 외는 영어). 시스템 언어로 판정이 안 되면 타임존(서울/도쿄)으로 추정합니다.
|
|
58
|
+
|
|
59
|
+
## 자동 업데이트
|
|
60
|
+
|
|
61
|
+
새 버전이 나오면 백그라운드에서 하루 1회 확인해 자동으로 업데이트합니다
|
|
62
|
+
(npm 전역 설치일 때만 — pnpm/yarn 설치는 건드리지 않습니다).
|
|
63
|
+
끄려면 `~/.pinglet/config.json`에 `"autoUpdate": false`를 추가하세요.
|
|
58
64
|
|
|
59
65
|
## 명령어
|
|
60
66
|
|
package/README.md
CHANGED
|
@@ -54,7 +54,13 @@ The statusline shows how many terminals are coding alongside you right now:
|
|
|
54
54
|
🟢 coding along with 41 terminals right now
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
The statusline picks its language from your
|
|
57
|
+
The statusline picks its language from your system language (Korean/Japanese, English otherwise), falling back to your timezone (Seoul/Tokyo) when the language doesn't decide it.
|
|
58
|
+
|
|
59
|
+
## Auto-update
|
|
60
|
+
|
|
61
|
+
Pinglet checks for a new version once a day in the background and updates
|
|
62
|
+
itself automatically (only for npm global installs — pnpm/yarn installs are
|
|
63
|
+
left untouched). To opt out, add `"autoUpdate": false` to `~/.pinglet/config.json`.
|
|
58
64
|
|
|
59
65
|
## Commands
|
|
60
66
|
|
package/dist/commands/refresh.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runRefresh = runRefresh;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
5
|
const api_1 = require("../api");
|
|
6
|
+
const update_1 = require("../update");
|
|
6
7
|
const cache_1 = require("../cache");
|
|
7
8
|
const claude_1 = require("../adapters/claude");
|
|
8
9
|
/**
|
|
@@ -46,4 +47,6 @@ async function runRefresh() {
|
|
|
46
47
|
(0, cache_1.saveState)(state);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
// 하루 1회 새 버전 확인 후 npm 전역 설치면 백그라운드 자동 업데이트.
|
|
51
|
+
await (0, update_1.maybeSelfUpdate)(config);
|
|
49
52
|
}
|
package/dist/config.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DEFAULT_API_BASE_URL = exports.ONLINE_PATH = exports.POSTS_PATH = exports.EVENTS_PATH = exports.STATE_PATH = exports.FEED_PATH = exports.CONFIG_PATH = exports.PINGLET_DIR = exports.VERSION = void 0;
|
|
36
|
+
exports.DEFAULT_API_BASE_URL = exports.UPDATE_PATH = exports.ONLINE_PATH = exports.POSTS_PATH = exports.EVENTS_PATH = exports.STATE_PATH = exports.FEED_PATH = exports.CONFIG_PATH = exports.PINGLET_DIR = exports.VERSION = void 0;
|
|
37
37
|
exports.ensurePingletDir = ensurePingletDir;
|
|
38
38
|
exports.readJsonFile = readJsonFile;
|
|
39
39
|
exports.writeJsonFile = writeJsonFile;
|
|
@@ -62,6 +62,8 @@ exports.EVENTS_PATH = path.join(exports.PINGLET_DIR, "events.jsonl");
|
|
|
62
62
|
exports.POSTS_PATH = path.join(exports.PINGLET_DIR, "posts.json");
|
|
63
63
|
/** 지금 켜져 있는 터미널 수 캐시 — heartbeat 응답으로 갱신, statusline이 읽는다 */
|
|
64
64
|
exports.ONLINE_PATH = path.join(exports.PINGLET_DIR, "online.json");
|
|
65
|
+
/** 자동 업데이트 진행 기록 — refresh가 하루 1회 갱신 */
|
|
66
|
+
exports.UPDATE_PATH = path.join(exports.PINGLET_DIR, "update.json");
|
|
65
67
|
exports.DEFAULT_API_BASE_URL = process.env.PINGLET_API_URL ?? "https://pinglet.halluci.co.kr";
|
|
66
68
|
function ensurePingletDir() {
|
|
67
69
|
fs.mkdirSync(exports.PINGLET_DIR, { recursive: true });
|
package/dist/render.js
CHANGED
|
@@ -6,31 +6,31 @@ exports.sanitizeForTerminal = sanitizeForTerminal;
|
|
|
6
6
|
exports.formatPing = formatPing;
|
|
7
7
|
exports.formatOnlineNow = formatOnlineNow;
|
|
8
8
|
/**
|
|
9
|
-
* statusline 표시
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* statusline 표시 언어. 시스템 언어를 먼저 보고(사용자가 실제로 읽는
|
|
10
|
+
* 언어라 가장 강한 신호), 언어로 판정이 안 되면 타임존을 위치의 근사로
|
|
11
|
+
* 쓴다. 한국·일본은 둘 다 UTC+9라 타임존만으로는 오판하는 기기가 있다
|
|
12
|
+
* (예: 한국에서 Asia/Tokyo 타임존을 쓰는 기기 — 시계가 맞아서 티가 안 남).
|
|
13
|
+
* 네트워크 없이 판정한다 (오프라인 동작 원칙).
|
|
12
14
|
*/
|
|
13
15
|
function detectLocale() {
|
|
16
|
+
const lang = (process.env.LC_ALL ||
|
|
17
|
+
process.env.LC_MESSAGES ||
|
|
18
|
+
process.env.LANG ||
|
|
19
|
+
"").toLowerCase();
|
|
20
|
+
if (lang.startsWith("ko"))
|
|
21
|
+
return "ko";
|
|
22
|
+
if (lang.startsWith("ja"))
|
|
23
|
+
return "ja";
|
|
14
24
|
try {
|
|
15
25
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "";
|
|
16
26
|
if (tz === "Asia/Seoul")
|
|
17
27
|
return "ko";
|
|
18
28
|
if (tz === "Asia/Tokyo")
|
|
19
29
|
return "ja";
|
|
20
|
-
if (tz)
|
|
21
|
-
return "en";
|
|
22
30
|
}
|
|
23
31
|
catch {
|
|
24
|
-
// Intl이 없는 환경 —
|
|
32
|
+
// Intl이 없는 환경 — 영어로.
|
|
25
33
|
}
|
|
26
|
-
const lang = (process.env.LC_ALL ||
|
|
27
|
-
process.env.LC_MESSAGES ||
|
|
28
|
-
process.env.LANG ||
|
|
29
|
-
"").toLowerCase();
|
|
30
|
-
if (lang.startsWith("ko"))
|
|
31
|
-
return "ko";
|
|
32
|
-
if (lang.startsWith("ja"))
|
|
33
|
-
return "ja";
|
|
34
34
|
return "en";
|
|
35
35
|
}
|
|
36
36
|
/** 터미널 독립 원칙: unicode가 불확실하면 plain ASCII로 fallback (아키텍처 §5). */
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isNewerVersion = isNewerVersion;
|
|
37
|
+
exports.maybeSelfUpdate = maybeSelfUpdate;
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
const REGISTRY_LATEST_URL = "https://registry.npmjs.org/pinglet-cli/latest";
|
|
42
|
+
const CHECK_INTERVAL_MS = 24 * 3600_000; // 하루 1회 확인
|
|
43
|
+
const RETRY_INTERVAL_MS = 7 * 24 * 3600_000; // 같은 버전 재시도는 주 1회
|
|
44
|
+
/** Windows에서 npm은 npm.cmd라 shell 경유가 필요하다 (Node의 .cmd spawn 제한). */
|
|
45
|
+
function npmSpawnOptions() {
|
|
46
|
+
return process.platform === "win32" ? { shell: true } : {};
|
|
47
|
+
}
|
|
48
|
+
async function fetchLatestVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(REGISTRY_LATEST_URL, {
|
|
51
|
+
signal: AbortSignal.timeout(5000),
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok)
|
|
54
|
+
return null;
|
|
55
|
+
const body = (await res.json());
|
|
56
|
+
return typeof body.version === "string" ? body.version : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 단순 semver(major.minor.patch) 비교. 형식이 어긋나면 업데이트하지 않는 쪽으로 판정한다. */
|
|
63
|
+
function isNewerVersion(latest, current) {
|
|
64
|
+
const parse = (v) => v.trim().split(".").map((n) => Number(n));
|
|
65
|
+
const a = parse(latest);
|
|
66
|
+
const b = parse(current);
|
|
67
|
+
if (a.length !== 3 || b.length !== 3)
|
|
68
|
+
return false;
|
|
69
|
+
if ([...a, ...b].some((n) => !Number.isInteger(n) || n < 0))
|
|
70
|
+
return false;
|
|
71
|
+
for (let i = 0; i < 3; i++) {
|
|
72
|
+
if (a[i] !== b[i])
|
|
73
|
+
return a[i] > b[i];
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
/** npm 전역 루트. npm이 없거나 실패하면 null — 자동 업데이트를 포기한다. */
|
|
78
|
+
function npmGlobalRoot() {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
(0, child_process_1.execFile)("npm", ["root", "-g"], { timeout: 10_000, windowsHide: true, ...npmSpawnOptions() }, (err, stdout) => resolve(err ? null : stdout.trim() || null));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 새 버전이 있으면 백그라운드로 자기 자신을 업데이트한다.
|
|
85
|
+
* - npm 전역 설치일 때만 실행한다 (cliPath가 `npm root -g` 아래인지 확인).
|
|
86
|
+
* pnpm/yarn/링크 설치에 npm을 덧씌우면 다른 복사본만 생기고 적용도 안 된다.
|
|
87
|
+
* - statusline은 매 틱 새 프로세스로 뜨므로, 설치가 끝나면 다음 틱부터
|
|
88
|
+
* 새 버전이 실행된다. 전역 경로는 버전과 무관해 설정 변경도 필요 없다.
|
|
89
|
+
* - 실패해도 조용히 넘어간다 (렌더링·refresh에 영향 금지). 재시도는 주 1회.
|
|
90
|
+
*/
|
|
91
|
+
async function maybeSelfUpdate(config) {
|
|
92
|
+
if (config.autoUpdate === false)
|
|
93
|
+
return;
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const state = (0, config_1.readJsonFile)(config_1.UPDATE_PATH) ?? {};
|
|
96
|
+
const lastCheck = state.lastCheckAt ? Date.parse(state.lastCheckAt) : NaN;
|
|
97
|
+
if (!Number.isNaN(lastCheck) && now - lastCheck < CHECK_INTERVAL_MS)
|
|
98
|
+
return;
|
|
99
|
+
// 확인 시각을 먼저 기록 — 레지스트리 장애 시에도 하루 1회 이상 두드리지 않는다.
|
|
100
|
+
state.lastCheckAt = new Date(now).toISOString();
|
|
101
|
+
(0, config_1.writeJsonFile)(config_1.UPDATE_PATH, state);
|
|
102
|
+
const latest = await fetchLatestVersion();
|
|
103
|
+
if (!latest || !isNewerVersion(latest, config_1.VERSION))
|
|
104
|
+
return;
|
|
105
|
+
if (state.lastAttempt?.version === latest) {
|
|
106
|
+
const at = Date.parse(state.lastAttempt.at);
|
|
107
|
+
if (!Number.isNaN(at) && now - at < RETRY_INTERVAL_MS)
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const root = await npmGlobalRoot();
|
|
111
|
+
if (!root || !(0, config_1.cliPath)().startsWith(root + path.sep))
|
|
112
|
+
return;
|
|
113
|
+
state.lastAttempt = { version: latest, at: new Date(now).toISOString() };
|
|
114
|
+
(0, config_1.writeJsonFile)(config_1.UPDATE_PATH, state);
|
|
115
|
+
try {
|
|
116
|
+
const child = (0, child_process_1.spawn)("npm", ["install", "-g", `pinglet-cli@${latest}`], { detached: true, stdio: "ignore", windowsHide: true, ...npmSpawnOptions() });
|
|
117
|
+
child.unref();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// npm 실행 실패 — 다음 주기에 재시도된다.
|
|
121
|
+
}
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinglet-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Short messages from other developers shown in your Claude Code spinner while the AI thinks — zero tokens, never reads your code. Claude Code / Codex integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|