pinglet-cli 0.2.8 → 0.2.9
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/dist/adapters/claude.js +38 -1
- package/dist/adapters/codex.js +45 -7
- package/package.json +1 -1
package/dist/adapters/claude.js
CHANGED
|
@@ -77,10 +77,27 @@ function statusLineCommand() {
|
|
|
77
77
|
function isPingletHookEntry(entry) {
|
|
78
78
|
return (entry.hooks?.some((h) => typeof h.command === "string" && h.command.includes("hook-prompt")) ?? false);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* `/pinglet` 인라인 bash를 허용하는 settings.json 권한 규칙.
|
|
82
|
+
* auto 권한 모드에서는 allow 규칙에 없는 Bash 명령을 LLM 분류기가 심사하는데,
|
|
83
|
+
* 메시지 내용("~해라" 같은 명령문)에 따라 차단될 수 있어 규칙으로 명시한다.
|
|
84
|
+
*/
|
|
85
|
+
function postPermissionRule() {
|
|
86
|
+
return `Bash(node "${(0, config_1.cliPath)()}" post:*)`;
|
|
87
|
+
}
|
|
88
|
+
/** Pinglet이 등록한 post 권한 규칙인지 식별 (경로가 바뀐 옛 규칙 포함). */
|
|
89
|
+
function isPingletPermissionRule(rule) {
|
|
90
|
+
return (rule === postPermissionRule() ||
|
|
91
|
+
(rule.startsWith('Bash(node "') &&
|
|
92
|
+
rule.includes("pinglet") &&
|
|
93
|
+
rule.endsWith('" post:*)')));
|
|
94
|
+
}
|
|
80
95
|
/**
|
|
81
96
|
* `/pinglet` slash command 파일을 설치한다.
|
|
82
97
|
* 등록 자체는 command 안의 인라인 bash(`!` 실행)가 수행하고,
|
|
83
98
|
* 모델은 그 결과 한 줄만 전달하므로 토큰 사용이 매우 적다.
|
|
99
|
+
* $ARGUMENTS는 따옴표로 감싸 메시지 전체가 하나의 인자로 전달되게 한다
|
|
100
|
+
* (메시지 속 단어가 셸/CLI 플래그로 해석되는 것을 방지).
|
|
84
101
|
*/
|
|
85
102
|
function installSlashCommand() {
|
|
86
103
|
fs.mkdirSync(COMMANDS_DIR, { recursive: true });
|
|
@@ -90,7 +107,7 @@ allowed-tools: Bash(node:*)
|
|
|
90
107
|
---
|
|
91
108
|
<!-- ${COMMAND_MARKER} -->
|
|
92
109
|
|
|
93
|
-
등록 결과: !\`node "${(0, config_1.cliPath)()}" post --quiet $ARGUMENTS\`
|
|
110
|
+
등록 결과: !\`node "${(0, config_1.cliPath)()}" post --quiet "$ARGUMENTS"\`
|
|
94
111
|
|
|
95
112
|
위 등록 결과 줄을 아무것도 덧붙이거나 바꾸지 말고 그대로 한 줄만 출력하세요.
|
|
96
113
|
다른 설명, 안내, 작업을 일절 하지 마세요.
|
|
@@ -305,6 +322,12 @@ function installClaudeIntegration(config, options = {}) {
|
|
|
305
322
|
(0, config_1.saveConfig)(config);
|
|
306
323
|
settings.statusLine = { type: "command", command: statusLineCommand() };
|
|
307
324
|
uninstallPromptHook(settings); // 구버전 hook 방식 흔적 정리
|
|
325
|
+
// /pinglet 인라인 bash가 auto 모드 분류기에 막히지 않도록 allow 규칙 등록.
|
|
326
|
+
// 경로가 바뀐(예: node 버전 업) 옛 규칙은 정리하고 현재 규칙 하나만 둔다.
|
|
327
|
+
const permissions = (settings.permissions ??= {});
|
|
328
|
+
const allow = (permissions.allow ??= []);
|
|
329
|
+
const kept = allow.filter((rule) => !isPingletPermissionRule(rule));
|
|
330
|
+
permissions.allow = [...kept, postPermissionRule()];
|
|
308
331
|
if (!writeSettings(settings, mtimeMs))
|
|
309
332
|
continue; // 다른 프로세스와 충돌 → 재읽기
|
|
310
333
|
installSlashCommand();
|
|
@@ -338,6 +361,20 @@ function uninstallClaudeIntegration(config) {
|
|
|
338
361
|
if (uninstallPromptHook(settings)) {
|
|
339
362
|
settingsChanged = true;
|
|
340
363
|
}
|
|
364
|
+
const allow = settings.permissions?.allow;
|
|
365
|
+
if (allow?.some(isPingletPermissionRule)) {
|
|
366
|
+
const kept = allow.filter((rule) => !isPingletPermissionRule(rule));
|
|
367
|
+
if (kept.length > 0) {
|
|
368
|
+
settings.permissions.allow = kept;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
delete settings.permissions.allow;
|
|
372
|
+
if (Object.keys(settings.permissions).length === 0) {
|
|
373
|
+
delete settings.permissions;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
settingsChanged = true;
|
|
377
|
+
}
|
|
341
378
|
const verbs = settings.spinnerVerbs;
|
|
342
379
|
if (verbs?.verbs?.some(isPingletTip)) {
|
|
343
380
|
const verbsBackup = config.adapters.claude?.spinnerVerbsBackup;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -74,17 +74,53 @@ function readToml() {
|
|
|
74
74
|
return "";
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
/**
|
|
78
|
+
* config.toml에서 notify 항목 **전체**를 찾는다. TOML 배열은 여러 줄에
|
|
79
|
+
* 걸칠 수 있으므로 (Codex Computer Use 앱 등이 그렇게 쓴다) 값이 `[`로
|
|
80
|
+
* 시작하면 문자열 리터럴을 건너뛰며 짝이 맞는 `]`까지를 항목으로 본다.
|
|
81
|
+
* 한 줄만 매칭하면 교체/삭제 시 배열 본문이 고아로 남아 TOML이 깨진다.
|
|
82
|
+
*/
|
|
83
|
+
function findNotifyEntry(toml) {
|
|
84
|
+
const head = toml.match(/^[ \t]*notify[ \t]*=[ \t]*/m);
|
|
85
|
+
if (!head || head.index === undefined)
|
|
86
|
+
return null;
|
|
87
|
+
const start = head.index;
|
|
88
|
+
let i = start + head[0].length;
|
|
89
|
+
if (toml[i] !== "[") {
|
|
90
|
+
const lineEnd = toml.indexOf("\n", i);
|
|
91
|
+
return toml.slice(start, lineEnd === -1 ? toml.length : lineEnd);
|
|
92
|
+
}
|
|
93
|
+
let depth = 0;
|
|
94
|
+
for (; i < toml.length; i++) {
|
|
95
|
+
const ch = toml[i];
|
|
96
|
+
if (ch === '"' || ch === "'") {
|
|
97
|
+
const quote = ch;
|
|
98
|
+
i++;
|
|
99
|
+
while (i < toml.length && toml[i] !== quote) {
|
|
100
|
+
if (quote === '"' && toml[i] === "\\")
|
|
101
|
+
i++; // basic string escape
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (ch === "[") {
|
|
106
|
+
depth++;
|
|
107
|
+
}
|
|
108
|
+
else if (ch === "]") {
|
|
109
|
+
depth--;
|
|
110
|
+
if (depth === 0)
|
|
111
|
+
return toml.slice(start, i + 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 괄호 짝이 안 맞음 — 이미 깨진 파일이므로 건드리지 않는다.
|
|
115
|
+
return null;
|
|
80
116
|
}
|
|
81
117
|
function isCodexIntegrationInstalled() {
|
|
82
|
-
const
|
|
83
|
-
return (
|
|
118
|
+
const entry = findNotifyEntry(readToml());
|
|
119
|
+
return (entry !== null && (entry.includes("pinglet") || entry.includes((0, config_1.cliPath)())));
|
|
84
120
|
}
|
|
85
121
|
function installCodexIntegration(config, options = {}) {
|
|
86
122
|
const toml = readToml();
|
|
87
|
-
const existing =
|
|
123
|
+
const existing = findNotifyEntry(toml);
|
|
88
124
|
const isOurs = existing !== null &&
|
|
89
125
|
(existing.includes("pinglet") || existing.includes((0, config_1.cliPath)()));
|
|
90
126
|
if (existing && !isOurs && !options.force) {
|
|
@@ -95,6 +131,8 @@ function installCodexIntegration(config, options = {}) {
|
|
|
95
131
|
};
|
|
96
132
|
}
|
|
97
133
|
config.adapters.codex = {
|
|
134
|
+
// 재설치 시 기존 백업(notifyBackup)이 날아가지 않게 유지한다.
|
|
135
|
+
...config.adapters.codex,
|
|
98
136
|
installedAt: new Date().toISOString(),
|
|
99
137
|
configPath: CONFIG_TOML_PATH,
|
|
100
138
|
...(existing && !isOurs && { notifyBackup: existing }),
|
|
@@ -114,7 +152,7 @@ function installCodexIntegration(config, options = {}) {
|
|
|
114
152
|
}
|
|
115
153
|
function uninstallCodexIntegration(config) {
|
|
116
154
|
const toml = readToml();
|
|
117
|
-
const existing =
|
|
155
|
+
const existing = findNotifyEntry(toml);
|
|
118
156
|
const isOurs = existing !== null &&
|
|
119
157
|
(existing.includes("pinglet") || existing.includes((0, config_1.cliPath)()));
|
|
120
158
|
if (!existing || !isOurs)
|