pinglet-cli 0.5.0 → 0.5.2
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 +3 -0
- package/README.ko.md +3 -0
- package/README.md +3 -0
- package/dist/adapters/claude.js +58 -24
- package/dist/cli.js +6 -0
- package/dist/commands/login.js +9 -1
- package/dist/commands/logout.js +19 -0
- package/dist/commands/post.js +6 -2
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -36,7 +36,9 @@ npm install -g pinglet-cli && pinglet install
|
|
|
36
36
|
Claude Code セッションの中からそのまま:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
+
> /pinglet-login # 初回のみ — ブラウザで GitHub または Google ログイン
|
|
39
40
|
> /pinglet 今日もビルドが緑でありますように
|
|
41
|
+
> /pinglet-logout # このマシンのログアウト
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
またはターミナルから:
|
|
@@ -68,6 +70,7 @@ statusline の言語はシステム言語に応じて自動選択されます(
|
|
|
68
70
|
|---|---|
|
|
69
71
|
| `pinglet install` | Claude Code に接続(Codex は `--codex` でオプトイン) |
|
|
70
72
|
| `pinglet login [--github \| --google]` | GitHub または Google アカウントを連携(投稿に必要)。フラグ省略時はブラウザで選択 |
|
|
73
|
+
| `pinglet logout` | ログアウト(連携設定・キャッシュは維持) |
|
|
71
74
|
| `pinglet post "メッセージ"` | Ping を送る |
|
|
72
75
|
| `pinglet ping` | いま表示されるメッセージをプレビュー |
|
|
73
76
|
| `pinglet doctor` | インストール・接続状態を診断 |
|
package/README.ko.md
CHANGED
|
@@ -36,7 +36,9 @@ npm install -g pinglet-cli && pinglet install
|
|
|
36
36
|
Claude Code 세션 안에서 바로:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
+
> /pinglet-login # 최초 1회 — 브라우저에서 GitHub 또는 Google 로그인
|
|
39
40
|
> /pinglet 오늘도 빌드가 초록불이길
|
|
41
|
+
> /pinglet-logout # 이 기기의 로그인 해제
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
또는 터미널에서:
|
|
@@ -68,6 +70,7 @@ statusline 언어는 시스템 언어에 따라 자동 선택됩니다(한국어
|
|
|
68
70
|
|---|---|
|
|
69
71
|
| `pinglet install` | Claude Code 연결 (Codex는 `--codex`로 opt-in) |
|
|
70
72
|
| `pinglet login [--github \| --google]` | GitHub 또는 Google 계정 연결 (메시지 작성에 필요). 플래그가 없으면 브라우저에서 선택 |
|
|
73
|
+
| `pinglet logout` | 로그인 해제 (설치·캐시는 유지) |
|
|
71
74
|
| `pinglet post "메시지"` | Ping 보내기 |
|
|
72
75
|
| `pinglet ping` | 지금 표시될 메시지 미리보기 |
|
|
73
76
|
| `pinglet doctor` | 설치·연결 상태 진단 |
|
package/README.md
CHANGED
|
@@ -36,7 +36,9 @@ That's it. Just run `claude` as usual.
|
|
|
36
36
|
Right from a Claude Code session:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
+
> /pinglet-login # once — GitHub or Google login in the browser
|
|
39
40
|
> /pinglet may your build stay green today
|
|
41
|
+
> /pinglet-logout # sign out on this machine
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
Or from the terminal:
|
|
@@ -68,6 +70,7 @@ left untouched). To opt out, add `"autoUpdate": false` to `~/.pinglet/config.jso
|
|
|
68
70
|
|---|---|
|
|
69
71
|
| `pinglet install` | Connect to Claude Code (opt in to Codex with `--codex`) |
|
|
70
72
|
| `pinglet login [--github \| --google]` | Link your GitHub or Google account (required for posting). Without a flag you pick in the browser |
|
|
73
|
+
| `pinglet logout` | Sign out on this machine (keeps the integration and cache) |
|
|
71
74
|
| `pinglet post "message"` | Send a Ping |
|
|
72
75
|
| `pinglet ping` | Preview the message that would be shown now |
|
|
73
76
|
| `pinglet doctor` | Diagnose installation and connection status |
|
package/dist/adapters/claude.js
CHANGED
|
@@ -49,8 +49,10 @@ const render_1 = require("../render");
|
|
|
49
49
|
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
50
50
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
|
51
51
|
const COMMANDS_DIR = path.join(CLAUDE_DIR, "commands");
|
|
52
|
-
const COMMAND_PATH = path.join(COMMANDS_DIR, "pinglet.md");
|
|
53
52
|
const COMMAND_MARKER = "generated by pinglet";
|
|
53
|
+
/** 슬래시 명령이 호출하는 CLI 서브커맨드 — 권한 규칙과 command 파일 모두 이 목록 기준. */
|
|
54
|
+
const SLASH_SUBCOMMANDS = ["post", "login", "logout"];
|
|
55
|
+
const COMMAND_FILES = ["pinglet.md", "pinglet-login.md", "pinglet-logout.md"];
|
|
54
56
|
/**
|
|
55
57
|
* Claude Adapter.
|
|
56
58
|
* Claude Code의 statusLine hook에 `pinglet statusline`을 연결한다.
|
|
@@ -83,53 +85,85 @@ function isPingletHookEntry(entry) {
|
|
|
83
85
|
return (entry.hooks?.some((h) => typeof h.command === "string" && h.command.includes("hook-prompt")) ?? false);
|
|
84
86
|
}
|
|
85
87
|
/**
|
|
86
|
-
* `/pinglet` 인라인 bash를 허용하는 settings.json 권한 규칙.
|
|
88
|
+
* `/pinglet`, `/pinglet-login`, `/pinglet-logout` 인라인 bash를 허용하는 settings.json 권한 규칙.
|
|
87
89
|
* auto 권한 모드에서는 allow 규칙에 없는 Bash 명령을 LLM 분류기가 심사하는데,
|
|
88
90
|
* 메시지 내용("~해라" 같은 명령문)에 따라 차단될 수 있어 규칙으로 명시한다.
|
|
89
91
|
*/
|
|
90
|
-
function
|
|
91
|
-
return `Bash(node "${(0, config_1.cliPath)()}"
|
|
92
|
+
function slashPermissionRules() {
|
|
93
|
+
return SLASH_SUBCOMMANDS.map((sub) => `Bash(node "${(0, config_1.cliPath)()}" ${sub}:*)`);
|
|
92
94
|
}
|
|
93
|
-
/** Pinglet이 등록한
|
|
95
|
+
/** Pinglet이 등록한 권한 규칙인지 식별 (경로가 바뀐 옛 규칙 포함). */
|
|
94
96
|
function isPingletPermissionRule(rule) {
|
|
95
|
-
return (rule
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
rule.endsWith('" post:*)')));
|
|
97
|
+
return (rule.startsWith('Bash(node "') &&
|
|
98
|
+
rule.includes("pinglet") &&
|
|
99
|
+
SLASH_SUBCOMMANDS.some((sub) => rule.endsWith(`" ${sub}:*)`)));
|
|
99
100
|
}
|
|
100
101
|
/**
|
|
101
|
-
* `/pinglet` slash command 파일을 설치한다.
|
|
102
|
-
*
|
|
103
|
-
* 모델은 그
|
|
102
|
+
* `/pinglet`, `/pinglet-login`, `/pinglet-logout` slash command 파일을 설치한다.
|
|
103
|
+
* 실제 동작은 command 안의 인라인 bash(`!` 실행)가 수행하고,
|
|
104
|
+
* 모델은 그 결과만 전달하므로 토큰 사용이 매우 적다.
|
|
104
105
|
* $ARGUMENTS는 따옴표로 감싸 메시지 전체가 하나의 인자로 전달되게 한다
|
|
105
106
|
* (메시지 속 단어가 셸/CLI 플래그로 해석되는 것을 방지).
|
|
106
107
|
*/
|
|
107
108
|
function installSlashCommand() {
|
|
108
109
|
fs.mkdirSync(COMMANDS_DIR, { recursive: true });
|
|
109
|
-
|
|
110
|
+
const cli = (0, config_1.cliPath)();
|
|
111
|
+
const files = {
|
|
112
|
+
"pinglet.md": `---
|
|
110
113
|
description: 메시지를 Pinglet에 등록해 다른 개발자들의 터미널에 표시
|
|
111
114
|
allowed-tools: Bash(node:*)
|
|
112
115
|
---
|
|
113
116
|
<!-- ${COMMAND_MARKER} -->
|
|
114
117
|
|
|
115
|
-
등록 결과: !\`node "${
|
|
118
|
+
등록 결과: !\`node "${cli}" post --quiet "$ARGUMENTS"\`
|
|
116
119
|
|
|
117
120
|
위 등록 결과 줄을 아무것도 덧붙이거나 바꾸지 말고 그대로 한 줄만 출력하세요.
|
|
118
121
|
다른 설명, 안내, 작업을 일절 하지 마세요.
|
|
119
|
-
|
|
122
|
+
`,
|
|
123
|
+
"pinglet-login.md": `---
|
|
124
|
+
description: Pinglet 계정 연결 (브라우저에서 GitHub 또는 Google 로그인, --github/--google로 지정 가능)
|
|
125
|
+
allowed-tools: Bash(node:*)
|
|
126
|
+
---
|
|
127
|
+
<!-- ${COMMAND_MARKER} -->
|
|
128
|
+
|
|
129
|
+
로그인 결과:
|
|
130
|
+
!\`node "${cli}" login --quiet $ARGUMENTS\`
|
|
131
|
+
|
|
132
|
+
위 로그인 결과를 아무것도 덧붙이거나 바꾸지 말고 그대로 출력하세요.
|
|
133
|
+
다른 설명, 안내, 작업을 일절 하지 마세요.
|
|
134
|
+
`,
|
|
135
|
+
"pinglet-logout.md": `---
|
|
136
|
+
description: Pinglet 로그인 해제 (이 기기의 로그인 토큰만 제거)
|
|
137
|
+
allowed-tools: Bash(node:*)
|
|
138
|
+
---
|
|
139
|
+
<!-- ${COMMAND_MARKER} -->
|
|
140
|
+
|
|
141
|
+
로그아웃 결과: !\`node "${cli}" logout\`
|
|
142
|
+
|
|
143
|
+
위 로그아웃 결과 줄을 아무것도 덧붙이거나 바꾸지 말고 그대로 한 줄만 출력하세요.
|
|
144
|
+
다른 설명, 안내, 작업을 일절 하지 마세요.
|
|
145
|
+
`,
|
|
146
|
+
};
|
|
147
|
+
for (const [name, body] of Object.entries(files)) {
|
|
148
|
+
fs.writeFileSync(path.join(COMMANDS_DIR, name), body);
|
|
149
|
+
}
|
|
120
150
|
}
|
|
121
151
|
/** Pinglet이 생성한 command 파일만 삭제한다 (마커로 확인). */
|
|
122
152
|
function uninstallSlashCommand() {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
153
|
+
let removed = false;
|
|
154
|
+
for (const name of COMMAND_FILES) {
|
|
155
|
+
const file = path.join(COMMANDS_DIR, name);
|
|
156
|
+
try {
|
|
157
|
+
if (!fs.readFileSync(file, "utf8").includes(COMMAND_MARKER))
|
|
158
|
+
continue;
|
|
159
|
+
fs.unlinkSync(file);
|
|
160
|
+
removed = true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// 없거나 읽을 수 없는 파일은 건너뛴다.
|
|
126
164
|
}
|
|
127
|
-
fs.unlinkSync(COMMAND_PATH);
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return false;
|
|
132
165
|
}
|
|
166
|
+
return removed;
|
|
133
167
|
}
|
|
134
168
|
/** 예전 버전이 등록한 UserPromptSubmit hook을 정리한다 (hook 방식은 폐기됨). */
|
|
135
169
|
function uninstallPromptHook(settings) {
|
|
@@ -352,7 +386,7 @@ function installClaudeIntegration(config, options = {}) {
|
|
|
352
386
|
const permissions = (settings.permissions ??= {});
|
|
353
387
|
const allow = (permissions.allow ??= []);
|
|
354
388
|
const kept = allow.filter((rule) => !isPingletPermissionRule(rule));
|
|
355
|
-
permissions.allow = [...kept,
|
|
389
|
+
permissions.allow = [...kept, ...slashPermissionRules()];
|
|
356
390
|
if (!writeSettings(settings, mtimeMs))
|
|
357
391
|
continue; // 다른 프로세스와 충돌 → 재읽기
|
|
358
392
|
installSlashCommand();
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const install_1 = require("./commands/install");
|
|
|
6
6
|
const uninstall_1 = require("./commands/uninstall");
|
|
7
7
|
const doctor_1 = require("./commands/doctor");
|
|
8
8
|
const login_1 = require("./commands/login");
|
|
9
|
+
const logout_1 = require("./commands/logout");
|
|
9
10
|
const statusline_1 = require("./commands/statusline");
|
|
10
11
|
const notify_1 = require("./commands/notify");
|
|
11
12
|
const flush_1 = require("./commands/flush");
|
|
@@ -21,6 +22,8 @@ const HELP = `pinglet v${config_1.VERSION} — AI가 생각하는 동안 만나
|
|
|
21
22
|
uninstall [--purge] integration 제거 (--purge: ~/.pinglet까지 삭제)
|
|
22
23
|
login [--github | --google] 계정 연결 (GitHub 또는 Google, 생략 시 브라우저에서 선택)
|
|
23
24
|
[--token <jwt>]
|
|
25
|
+
logout 로그인 해제 (설치·캐시는 유지)
|
|
26
|
+
(Claude Code 안에서는 /pinglet-login, /pinglet-logout)
|
|
24
27
|
post "메시지" [--category <c>] 메시지 작성 (로그인 필요, 읽기는 익명 가능)
|
|
25
28
|
doctor 설치/캐시/서버 상태 진단
|
|
26
29
|
ping 메시지 미리보기
|
|
@@ -43,6 +46,9 @@ async function main() {
|
|
|
43
46
|
case "login":
|
|
44
47
|
await (0, login_1.runLogin)(rest);
|
|
45
48
|
break;
|
|
49
|
+
case "logout":
|
|
50
|
+
(0, logout_1.runLogout)();
|
|
51
|
+
break;
|
|
46
52
|
case "doctor":
|
|
47
53
|
await (0, doctor_1.runDoctor)();
|
|
48
54
|
break;
|
package/dist/commands/login.js
CHANGED
|
@@ -119,9 +119,12 @@ function loginViaLoopback(apiBaseUrl, provider) {
|
|
|
119
119
|
/**
|
|
120
120
|
* 소셜 로그인(GitHub/Google) — 루프백 자동 수신, 실패 시 수동 붙여넣기 폴백.
|
|
121
121
|
* `--github` / `--google`로 방식을 고정하고, 없으면 브라우저에서 고른다.
|
|
122
|
+
* `--quiet`: /pinglet-login slash command용 — 터미널 입력을 받을 수 없으므로
|
|
123
|
+
* 붙여넣기 폴백 대신 안내만 출력하고, 실패해도 exit 0 (에러 덤프 방지).
|
|
122
124
|
*/
|
|
123
125
|
async function runLogin(args) {
|
|
124
126
|
const config = (0, config_1.loadConfig)();
|
|
127
|
+
const quiet = args.includes("--quiet");
|
|
125
128
|
let token;
|
|
126
129
|
const tokenIndex = args.indexOf("--token");
|
|
127
130
|
if (tokenIndex !== -1 && args[tokenIndex + 1]) {
|
|
@@ -135,6 +138,10 @@ async function runLogin(args) {
|
|
|
135
138
|
if (!token) {
|
|
136
139
|
console.log(`\n로그인하면 이용약관(${config.apiBaseUrl}/terms)과 개인정보처리방침(${config.apiBaseUrl}/privacy)에 동의하는 것으로 간주됩니다.`);
|
|
137
140
|
token = (await loginViaLoopback(config.apiBaseUrl, provider)) ?? undefined;
|
|
141
|
+
if (!token && quiet) {
|
|
142
|
+
console.log("✗ 로그인이 완료되지 않았습니다 (제한 시간 3분). 터미널에서 `pinglet login`으로 다시 시도하세요.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
138
145
|
if (!token) {
|
|
139
146
|
// SSH 등 브라우저와 터미널이 다른 기계인 경우를 위한 폴백.
|
|
140
147
|
console.log("자동 로그인에 실패했습니다. 아래 URL에서 로그인 후 응답 JSON의 token 값을 붙여넣어 주세요.");
|
|
@@ -144,7 +151,8 @@ async function runLogin(args) {
|
|
|
144
151
|
}
|
|
145
152
|
if (!token) {
|
|
146
153
|
console.log("✗ token이 비어 있습니다.");
|
|
147
|
-
|
|
154
|
+
if (!quiet)
|
|
155
|
+
process.exitCode = 1;
|
|
148
156
|
return;
|
|
149
157
|
}
|
|
150
158
|
config.userToken = token;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runLogout = runLogout;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
/**
|
|
6
|
+
* `pinglet logout` — 로컬에 저장된 로그인 토큰만 지운다.
|
|
7
|
+
* 통합 설정·installation·캐시는 그대로라 읽기는 계속 되고, 다시 `pinglet login`으로
|
|
8
|
+
* 다른 계정(GitHub/Google)을 연결할 수 있다. 서버 측 토큰 폐기는 하지 않는다 (JWT 만료까지 유효).
|
|
9
|
+
*/
|
|
10
|
+
function runLogout() {
|
|
11
|
+
const config = (0, config_1.loadConfig)();
|
|
12
|
+
if (!config.userToken) {
|
|
13
|
+
console.log("○ 로그인 상태가 아닙니다.");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
delete config.userToken;
|
|
17
|
+
(0, config_1.saveConfig)(config);
|
|
18
|
+
console.log("✓ 로그아웃했습니다. 읽기는 계속 되고, 메시지 작성은 `pinglet login` 후 가능합니다.");
|
|
19
|
+
}
|
package/dist/commands/post.js
CHANGED
|
@@ -45,10 +45,14 @@ async function runPost(args) {
|
|
|
45
45
|
if (!result.ok) {
|
|
46
46
|
switch (result.error) {
|
|
47
47
|
case "LOGIN_REQUIRED":
|
|
48
|
-
console.log(
|
|
48
|
+
console.log(quiet
|
|
49
|
+
? "✗ 메시지 작성에는 로그인(GitHub 또는 Google)이 필요합니다. `/pinglet-login`을 실행하세요 (읽기는 로그인 없이 가능)."
|
|
50
|
+
: "✗ 메시지 작성에는 로그인(GitHub 또는 Google)이 필요합니다. `pinglet login`을 실행하세요 (읽기는 로그인 없이 가능).");
|
|
49
51
|
break;
|
|
50
52
|
case "UNAUTHORIZED":
|
|
51
|
-
console.log(
|
|
53
|
+
console.log(quiet
|
|
54
|
+
? "✗ 로그인이 만료됐습니다. `/pinglet-login`으로 다시 로그인하세요."
|
|
55
|
+
: "✗ 로그인이 만료됐습니다. `pinglet login`으로 다시 로그인하세요.");
|
|
52
56
|
break;
|
|
53
57
|
case "RATE_LIMITED":
|
|
54
58
|
console.log("✗ 작성 제한(분당 5개)에 걸렸습니다. 잠시 후 다시 시도하세요.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinglet-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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": {
|