approval-box 0.1.8 → 0.1.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/cli.mjs +166 -81
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -11755,9 +11755,9 @@ var require_dist = __commonJS({
|
|
|
11755
11755
|
|
|
11756
11756
|
// src/cli.ts
|
|
11757
11757
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
11758
|
-
import { cpSync, existsSync as
|
|
11758
|
+
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync5, rmSync as rmSync3 } from "node:fs";
|
|
11759
11759
|
import { hostname } from "node:os";
|
|
11760
|
-
import { join as
|
|
11760
|
+
import { join as join7 } from "node:path";
|
|
11761
11761
|
import { createInterface } from "node:readline/promises";
|
|
11762
11762
|
|
|
11763
11763
|
// src/api.ts
|
|
@@ -11810,6 +11810,18 @@ var Api = class {
|
|
|
11810
11810
|
}
|
|
11811
11811
|
return json;
|
|
11812
11812
|
}
|
|
11813
|
+
/** ファイルを取る(添付)。誤りはJSONの本文から読む。 */
|
|
11814
|
+
async download(path) {
|
|
11815
|
+
let response;
|
|
11816
|
+
try {
|
|
11817
|
+
response = await fetch(this.url(path), { headers: this.token ? { authorization: `Bearer ${this.token}` } : {} });
|
|
11818
|
+
} catch (error) {
|
|
11819
|
+
throw new ServerError(0, "network", `Approval Box\u30B5\u30FC\u30D0\u30FC\uFF08${this.server}\uFF09\u306B\u3064\u306A\u304C\u308A\u307E\u305B\u3093: ${error.message}`, {});
|
|
11820
|
+
}
|
|
11821
|
+
if (response.ok) return Buffer.from(await response.arrayBuffer());
|
|
11822
|
+
const json = await response.json().catch(() => ({}));
|
|
11823
|
+
throw new ServerError(response.status, json.error?.code ?? "internal", json.error?.message ?? `HTTP ${response.status}`, json.error ?? {});
|
|
11824
|
+
}
|
|
11813
11825
|
/** コネクタ用SSE。届いたイベント名ごとに onEvent を呼ぶ。切れたら戻る。 */
|
|
11814
11826
|
async stream(onEvent, signal) {
|
|
11815
11827
|
const response = await fetch(this.url("/stream"), { headers: { authorization: `Bearer ${this.token}`, accept: "text/event-stream" }, signal });
|
|
@@ -11856,23 +11868,68 @@ function writeConfig(config) {
|
|
|
11856
11868
|
|
|
11857
11869
|
// src/daemon.ts
|
|
11858
11870
|
import { spawn } from "node:child_process";
|
|
11859
|
-
import { existsSync as
|
|
11860
|
-
import { join as
|
|
11871
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
11872
|
+
import { join as join4 } from "node:path";
|
|
11873
|
+
|
|
11874
|
+
// src/attachments.ts
|
|
11875
|
+
import { createHash } from "node:crypto";
|
|
11876
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11877
|
+
import { join as join2 } from "node:path";
|
|
11878
|
+
var KEEP_DAYS = 30;
|
|
11879
|
+
var attachmentsRoot = () => join2(home(), "attachments");
|
|
11880
|
+
function safeFileName(name) {
|
|
11881
|
+
const cleaned = name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/, "").trim();
|
|
11882
|
+
return cleaned.slice(0, 150) || "file";
|
|
11883
|
+
}
|
|
11884
|
+
async function saveAttachment(api, decisionId, meta, index) {
|
|
11885
|
+
const dir = join2(attachmentsRoot(), safeFileName(decisionId));
|
|
11886
|
+
const file = join2(dir, `${index + 1}_${safeFileName(meta.name)}`);
|
|
11887
|
+
if (existsSync2(file) && sha256(readFileSync2(file)) === meta.sha256) return file;
|
|
11888
|
+
const data = await api.download(`/decisions/${encodeURIComponent(decisionId)}/attachments/${encodeURIComponent(meta.id)}`);
|
|
11889
|
+
if (sha256(data) !== meta.sha256) throw new Error(`${meta.name} \u306E\u4E2D\u8EAB\u304C\u30B5\u30FC\u30D0\u30FC\u306E\u8A18\u9332\u3068\u5408\u3044\u307E\u305B\u3093\uFF08\u53D6\u308A\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\uFF09`);
|
|
11890
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
11891
|
+
writeFileSync2(file, data, { mode: 384 });
|
|
11892
|
+
return file;
|
|
11893
|
+
}
|
|
11894
|
+
async function saveAll(api, decisionId, items) {
|
|
11895
|
+
const lines = ["", "\u6DFB\u4ED8\u306F\u3053\u306E\u7AEF\u672B\u306B\u4FDD\u5B58\u3057\u307E\u3057\u305F:"];
|
|
11896
|
+
for (const [i, meta] of items.entries()) {
|
|
11897
|
+
try {
|
|
11898
|
+
lines.push(`${i + 1}. ${await saveAttachment(api, decisionId, meta, i)}`);
|
|
11899
|
+
} catch (error) {
|
|
11900
|
+
lines.push(`${i + 1}. ${meta.name}: \u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08${error.message}\uFF09\u3002get_attachment \u3067\u53D6\u3063\u3066\u304F\u3060\u3055\u3044\u3002`);
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
return lines.join("\n");
|
|
11904
|
+
}
|
|
11905
|
+
function pruneAttachments() {
|
|
11906
|
+
const root = attachmentsRoot();
|
|
11907
|
+
if (!existsSync2(root)) return;
|
|
11908
|
+
const before = Date.now() - KEEP_DAYS * 864e5;
|
|
11909
|
+
for (const name of readdirSync(root)) {
|
|
11910
|
+
const dir = join2(root, name);
|
|
11911
|
+
try {
|
|
11912
|
+
if (statSync(dir).mtimeMs < before) rmSync(dir, { recursive: true, force: true });
|
|
11913
|
+
} catch {
|
|
11914
|
+
}
|
|
11915
|
+
}
|
|
11916
|
+
}
|
|
11917
|
+
var sha256 = (data) => createHash("sha256").update(data).digest("hex");
|
|
11861
11918
|
|
|
11862
11919
|
// src/runtime.ts
|
|
11863
|
-
import { basename, dirname as dirname2, join as
|
|
11920
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
11864
11921
|
import { fileURLToPath } from "node:url";
|
|
11865
11922
|
var runtimeDir = () => {
|
|
11866
11923
|
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
11867
11924
|
return basename(dir) === "chunks" ? dirname2(dir) : dir;
|
|
11868
11925
|
};
|
|
11869
11926
|
function runtimeEntry(kind) {
|
|
11870
|
-
return
|
|
11927
|
+
return join3(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
|
|
11871
11928
|
}
|
|
11872
11929
|
|
|
11873
11930
|
// src/daemon.ts
|
|
11874
|
-
var lockFile = () =>
|
|
11875
|
-
var journalFile = () =>
|
|
11931
|
+
var lockFile = () => join4(stateRoot(), "daemon.json");
|
|
11932
|
+
var journalFile = () => join4(stateRoot(), "deliveries.json");
|
|
11876
11933
|
var IDLE_EXIT_MS = 30 * 6e4;
|
|
11877
11934
|
function alive(pid) {
|
|
11878
11935
|
try {
|
|
@@ -11883,9 +11940,9 @@ function alive(pid) {
|
|
|
11883
11940
|
}
|
|
11884
11941
|
}
|
|
11885
11942
|
function daemonStatus() {
|
|
11886
|
-
if (!
|
|
11943
|
+
if (!existsSync3(lockFile())) return { running: false };
|
|
11887
11944
|
try {
|
|
11888
|
-
const lock = JSON.parse(
|
|
11945
|
+
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
11889
11946
|
return alive(lock.pid) ? { running: true, pid: lock.pid, entry: lock.entry } : { running: false };
|
|
11890
11947
|
} catch {
|
|
11891
11948
|
return { running: false };
|
|
@@ -11902,8 +11959,8 @@ function ensureDaemon() {
|
|
|
11902
11959
|
} catch {
|
|
11903
11960
|
}
|
|
11904
11961
|
}
|
|
11905
|
-
|
|
11906
|
-
const log = openSync(
|
|
11962
|
+
mkdirSync3(stateRoot(), { recursive: true, mode: 448 });
|
|
11963
|
+
const log = openSync(join4(stateRoot(), "daemon.log"), "a", 384);
|
|
11907
11964
|
spawn(process.execPath, [runtimeEntry("cli"), "daemon"], { detached: true, stdio: ["ignore", "ignore", log], windowsHide: true }).unref();
|
|
11908
11965
|
} catch (error) {
|
|
11909
11966
|
process.stderr.write(`approval-box: \u914D\u9001\u30C7\u30FC\u30E2\u30F3\u3092\u8D77\u52D5\u3067\u304D\u307E\u305B\u3093: ${error.message}
|
|
@@ -11916,7 +11973,7 @@ async function runDaemon() {
|
|
|
11916
11973
|
if (status2.running && status2.pid !== process.pid) return;
|
|
11917
11974
|
writeJsonFile(lockFile(), { pid: process.pid, entry: runtimeEntry("cli"), started_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11918
11975
|
const api = new Api(requireConfig());
|
|
11919
|
-
let journal =
|
|
11976
|
+
let journal = existsSync3(journalFile()) ? JSON.parse(readFileSync3(journalFile(), "utf8")) : {};
|
|
11920
11977
|
const save = () => writeJsonFile(journalFile(), journal);
|
|
11921
11978
|
const log = (message) => process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}
|
|
11922
11979
|
`);
|
|
@@ -11928,6 +11985,11 @@ async function runDaemon() {
|
|
|
11928
11985
|
journal[id] = entry;
|
|
11929
11986
|
}
|
|
11930
11987
|
save();
|
|
11988
|
+
try {
|
|
11989
|
+
pruneAttachments();
|
|
11990
|
+
} catch (error) {
|
|
11991
|
+
log(`\u53E4\u3044\u6DFB\u4ED8\u306E\u7247\u4ED8\u3051\u306B\u5931\u6557: ${error.message}`);
|
|
11992
|
+
}
|
|
11931
11993
|
async function report(id) {
|
|
11932
11994
|
const entry = journal[id];
|
|
11933
11995
|
try {
|
|
@@ -11941,12 +12003,13 @@ async function runDaemon() {
|
|
|
11941
12003
|
async function deliver(item) {
|
|
11942
12004
|
const existing = journal[item.decision_id];
|
|
11943
12005
|
if (existing) return;
|
|
12006
|
+
const text2 = item.attachments?.length ? item.text + await saveAll(api, item.decision_id, item.attachments) : item.text;
|
|
11944
12007
|
journal[item.decision_id] = { delivery_id: item.delivery_id, channel_id: item.route.channel_id, harness: item.route.harness, state: "sending", at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11945
12008
|
save();
|
|
11946
12009
|
const entry = journal[item.decision_id];
|
|
11947
12010
|
try {
|
|
11948
12011
|
if (channelClosed(PROFILE, item.route.channel_id)) throw Object.assign(new Error("\u7533\u8ACB\u3092\u51FA\u3057\u305FAI\u306E\u4F1A\u8A71\u304C\u7D42\u308F\u3063\u3066\u3044\u307E\u3059"), { closed: true });
|
|
11949
|
-
const result = await sendToChannel(PROFILE, item.route.channel_id, item.delivery_id,
|
|
12012
|
+
const result = await sendToChannel(PROFILE, item.route.channel_id, item.delivery_id, text2);
|
|
11950
12013
|
if (result.state === "submitted") Object.assign(entry, { state: "report", result: "delivered" });
|
|
11951
12014
|
else entry.state = "queued";
|
|
11952
12015
|
} catch (error) {
|
|
@@ -12048,8 +12111,8 @@ async function runDaemon() {
|
|
|
12048
12111
|
}
|
|
12049
12112
|
}
|
|
12050
12113
|
try {
|
|
12051
|
-
const lock = JSON.parse(
|
|
12052
|
-
if (lock.pid === process.pid)
|
|
12114
|
+
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
12115
|
+
if (lock.pid === process.pid) rmSync2(lockFile(), { force: true });
|
|
12053
12116
|
} catch {
|
|
12054
12117
|
}
|
|
12055
12118
|
process.exit(0);
|
|
@@ -12057,12 +12120,12 @@ async function runDaemon() {
|
|
|
12057
12120
|
|
|
12058
12121
|
// src/harness.ts
|
|
12059
12122
|
import { execFileSync } from "node:child_process";
|
|
12060
|
-
import { copyFileSync, existsSync as
|
|
12123
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
12061
12124
|
import { homedir } from "node:os";
|
|
12062
|
-
import { delimiter, dirname as dirname4, join as
|
|
12125
|
+
import { delimiter, dirname as dirname4, join as join5 } from "node:path";
|
|
12063
12126
|
|
|
12064
12127
|
// src/instructions.ts
|
|
12065
|
-
import { existsSync as
|
|
12128
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12066
12129
|
import { dirname as dirname3 } from "node:path";
|
|
12067
12130
|
var INSTRUCTION_TEXT = "\u30E6\u30FC\u30B6\u30FC\u306E\u5224\u65AD\u30FB\u627F\u8A8D\u30FB\u64CD\u4F5C\u304C\u5FC5\u8981\u306A\u3068\u304D\u306F\u3001\u6C7A\u88C1\u30B7\u30B9\u30C6\u30E0\u3067\u3042\u308B Approval Box \u306E request_decision \u3067\u3082\u7533\u8ACB\u3059\u308B\u3002\u3053\u308C\u3092\u4F7F\u3048\u3070\u30E6\u30FC\u30B6\u30FC\u304C\u7533\u8ACB\u306B\u6C17\u3065\u304D\u3084\u3059\u3044\u306E\u3067\u3001\u5F37\u304F\u63A8\u5968\u3059\u308B\u3002";
|
|
12068
12131
|
var START = "<!-- approval-box:start -->";
|
|
@@ -12075,19 +12138,19 @@ ${END}
|
|
|
12075
12138
|
`;
|
|
12076
12139
|
var PATTERN = new RegExp(`\\n*${START}[\\s\\S]*?${END}\\n?`, "g");
|
|
12077
12140
|
function hasInstructions(file) {
|
|
12078
|
-
return
|
|
12141
|
+
return existsSync4(file) && readFileSync4(file, "utf8").includes(START);
|
|
12079
12142
|
}
|
|
12080
12143
|
function writeInstructions(file) {
|
|
12081
|
-
const current =
|
|
12082
|
-
|
|
12083
|
-
|
|
12144
|
+
const current = existsSync4(file) ? readFileSync4(file, "utf8").replace(PATTERN, "\n").trimEnd() : "";
|
|
12145
|
+
mkdirSync4(dirname3(file), { recursive: true, mode: 448 });
|
|
12146
|
+
writeFileSync3(file, current ? `${current}
|
|
12084
12147
|
|
|
12085
12148
|
${BLOCK}` : BLOCK);
|
|
12086
12149
|
}
|
|
12087
12150
|
function removeInstructions(file) {
|
|
12088
12151
|
if (!hasInstructions(file)) return;
|
|
12089
|
-
const rest =
|
|
12090
|
-
|
|
12152
|
+
const rest = readFileSync4(file, "utf8").replace(PATTERN, "\n").trimEnd();
|
|
12153
|
+
writeFileSync3(file, rest ? `${rest}
|
|
12091
12154
|
` : "");
|
|
12092
12155
|
}
|
|
12093
12156
|
|
|
@@ -12095,35 +12158,35 @@ function removeInstructions(file) {
|
|
|
12095
12158
|
var HARNESSES = ["claude", "codex", "cursor", "grok"];
|
|
12096
12159
|
var LABEL = { claude: "Claude Code", codex: "Codex", cursor: "Cursor", grok: "Grok" };
|
|
12097
12160
|
var CLIENT = { claude: "claude-code", codex: "codex", cursor: "cursor", grok: "grok" };
|
|
12098
|
-
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ??
|
|
12099
|
-
var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ?
|
|
12100
|
-
var cursorDir = () => process.env.CURSOR_HOME ??
|
|
12101
|
-
var codexHome = () => process.env.CODEX_HOME ??
|
|
12102
|
-
var grokDir = () => process.env.GROK_HOME ??
|
|
12161
|
+
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ?? join5(homedir(), ".claude");
|
|
12162
|
+
var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ? join5(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join5(homedir(), ".claude.json");
|
|
12163
|
+
var cursorDir = () => process.env.CURSOR_HOME ?? join5(homedir(), ".cursor");
|
|
12164
|
+
var codexHome = () => process.env.CODEX_HOME ?? join5(homedir(), ".codex");
|
|
12165
|
+
var grokDir = () => process.env.GROK_HOME ?? join5(homedir(), ".grok");
|
|
12103
12166
|
function filesOf(target) {
|
|
12104
|
-
if (target === "claude") return [claudeJson(),
|
|
12105
|
-
if (target === "cursor") return [
|
|
12106
|
-
if (target === "codex") return [
|
|
12107
|
-
return [
|
|
12167
|
+
if (target === "claude") return [claudeJson(), join5(claudeDir(), "settings.json")];
|
|
12168
|
+
if (target === "cursor") return [join5(cursorDir(), "mcp.json"), join5(cursorDir(), "hooks.json"), cursorCliConfig()];
|
|
12169
|
+
if (target === "codex") return [join5(codexHome(), "config.toml"), join5(codexHome(), "hooks.json")];
|
|
12170
|
+
return [join5(grokDir(), "config.toml")];
|
|
12108
12171
|
}
|
|
12109
12172
|
function instructionsFileOf(target) {
|
|
12110
|
-
if (target === "claude") return
|
|
12111
|
-
if (target === "codex") return
|
|
12112
|
-
if (target === "grok") return
|
|
12173
|
+
if (target === "claude") return join5(claudeDir(), "CLAUDE.md");
|
|
12174
|
+
if (target === "codex") return join5(codexHome(), "AGENTS.md");
|
|
12175
|
+
if (target === "grok") return join5(grokDir(), "AGENTS.md");
|
|
12113
12176
|
return null;
|
|
12114
12177
|
}
|
|
12115
12178
|
function onPath(command2) {
|
|
12116
12179
|
const names = process.platform === "win32" ? [`${command2}.exe`, `${command2}.cmd`, command2] : [command2];
|
|
12117
12180
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
12118
|
-
for (const name of names) if (dir &&
|
|
12181
|
+
for (const name of names) if (dir && existsSync5(join5(dir, name))) return join5(dir, name);
|
|
12119
12182
|
}
|
|
12120
12183
|
return null;
|
|
12121
12184
|
}
|
|
12122
12185
|
function detect(target) {
|
|
12123
|
-
if (target === "claude") return
|
|
12124
|
-
if (target === "codex") return
|
|
12125
|
-
if (target === "cursor") return
|
|
12126
|
-
return
|
|
12186
|
+
if (target === "claude") return existsSync5(claudeDir()) || !!onPath("claude");
|
|
12187
|
+
if (target === "codex") return existsSync5(codexHome()) || !!onPath("codex");
|
|
12188
|
+
if (target === "cursor") return existsSync5(cursorDir()) || !!onPath("cursor-agent");
|
|
12189
|
+
return existsSync5(grokDir()) || !!onPath("grok");
|
|
12127
12190
|
}
|
|
12128
12191
|
function stableNode() {
|
|
12129
12192
|
try {
|
|
@@ -12132,11 +12195,11 @@ function stableNode() {
|
|
|
12132
12195
|
return process.execPath;
|
|
12133
12196
|
}
|
|
12134
12197
|
}
|
|
12135
|
-
var CURSOR_TOOLS = ["list_my_decisions", "request_decision", "amend_decision", "cancel_decision", "get_decision", "setup_test", "confirm_setup_test"];
|
|
12136
|
-
var cursorCliConfig = () =>
|
|
12198
|
+
var CURSOR_TOOLS = ["list_my_decisions", "request_decision", "amend_decision", "cancel_decision", "get_decision", "get_attachment", "setup_test", "confirm_setup_test"];
|
|
12199
|
+
var cursorCliConfig = () => join5(cursorDir(), "cli-config.json");
|
|
12137
12200
|
function setCursorPermissions(enable) {
|
|
12138
12201
|
const file = cursorCliConfig();
|
|
12139
|
-
if (!
|
|
12202
|
+
if (!existsSync5(file) && !enable) return;
|
|
12140
12203
|
backupOnce(file);
|
|
12141
12204
|
const current = readJson(file);
|
|
12142
12205
|
const permissions = current.permissions ?? {};
|
|
@@ -12147,11 +12210,11 @@ function setCursorPermissions(enable) {
|
|
|
12147
12210
|
}
|
|
12148
12211
|
var CURSOR_CONTEXT = "cursor-context";
|
|
12149
12212
|
function setCursorContext(enable) {
|
|
12150
|
-
const file =
|
|
12151
|
-
if (!
|
|
12152
|
-
const target =
|
|
12213
|
+
const file = join5(cursorDir(), "hooks.json");
|
|
12214
|
+
if (!existsSync5(file) && !enable) return;
|
|
12215
|
+
const target = existsSync5(file) ? realpathSync(file) : file;
|
|
12153
12216
|
backupOnce(target);
|
|
12154
|
-
const current =
|
|
12217
|
+
const current = existsSync5(target) ? readJson(target) : { version: 1 };
|
|
12155
12218
|
const hooks = { ...current.hooks ?? {} };
|
|
12156
12219
|
const ours = (entry) => typeof entry?.command === "string" && entry.command.includes(MCP_SERVER) && entry.command.endsWith(` ${CURSOR_CONTEXT}`);
|
|
12157
12220
|
const rest = (hooks.sessionStart ?? []).filter((entry) => !ours(entry));
|
|
@@ -12160,15 +12223,15 @@ function setCursorContext(enable) {
|
|
|
12160
12223
|
else delete hooks.sessionStart;
|
|
12161
12224
|
writeJsonFile(target, { version: 1, ...current, hooks });
|
|
12162
12225
|
}
|
|
12163
|
-
var cursorContextRegistered = () => JSON.stringify(readJson(
|
|
12226
|
+
var cursorContextRegistered = () => JSON.stringify(readJson(join5(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
|
|
12164
12227
|
var registration = () => ({ command: stableNode(), args: [runtimeEntry("cli"), "mcp"] });
|
|
12165
12228
|
function readJson(file) {
|
|
12166
|
-
if (!
|
|
12167
|
-
return JSON.parse(
|
|
12229
|
+
if (!existsSync5(file)) return {};
|
|
12230
|
+
return JSON.parse(readFileSync5(file, "utf8").replace(/^/, ""));
|
|
12168
12231
|
}
|
|
12169
12232
|
function backupOnce(file) {
|
|
12170
12233
|
const copy = `${file}${PROFILE.backup_suffix}`;
|
|
12171
|
-
if (
|
|
12234
|
+
if (existsSync5(file) && !existsSync5(copy)) copyFileSync(file, copy);
|
|
12172
12235
|
}
|
|
12173
12236
|
function setMcpJson(file, value) {
|
|
12174
12237
|
backupOnce(file);
|
|
@@ -12194,18 +12257,18 @@ async function register(target, options = {}) {
|
|
|
12194
12257
|
try {
|
|
12195
12258
|
if (target === "claude") {
|
|
12196
12259
|
setMcpJson(claudeJson(), { type: "stdio", ...registration() });
|
|
12197
|
-
mergeClaudeParentHooks(PROFILE,
|
|
12260
|
+
mergeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"), hook("claude"));
|
|
12198
12261
|
return { target, status: "registered" };
|
|
12199
12262
|
}
|
|
12200
12263
|
if (target === "cursor") {
|
|
12201
|
-
setMcpJson(
|
|
12202
|
-
mergeCursorParentHooks(PROFILE,
|
|
12264
|
+
setMcpJson(join5(cursorDir(), "mcp.json"), registration());
|
|
12265
|
+
mergeCursorParentHooks(PROFILE, join5(cursorDir(), "hooks.json"), hook("cursor"));
|
|
12203
12266
|
setCursorPermissions(true);
|
|
12204
12267
|
setCursorContext(!!options.instructions);
|
|
12205
12268
|
return { target, status: "registered" };
|
|
12206
12269
|
}
|
|
12207
12270
|
if (target === "codex") {
|
|
12208
|
-
const config =
|
|
12271
|
+
const config = join5(codexHome(), "config.toml");
|
|
12209
12272
|
backupOnce(config);
|
|
12210
12273
|
await withCodexConfig(async (request) => {
|
|
12211
12274
|
await request("config/batchWrite", { filePath: config, edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: { ...registration(), env_vars: ["CODEX_HOME"] }, mergeStrategy: "replace" }] });
|
|
@@ -12221,7 +12284,7 @@ async function register(target, options = {}) {
|
|
|
12221
12284
|
}
|
|
12222
12285
|
const grok = onPath("grok");
|
|
12223
12286
|
if (!grok) throw new Error("grok \u30B3\u30DE\u30F3\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
12224
|
-
backupOnce(
|
|
12287
|
+
backupOnce(join5(grokDir(), "config.toml"));
|
|
12225
12288
|
const reg = registration();
|
|
12226
12289
|
execFileSync(grok, ["mcp", "add", MCP_SERVER, "--scope", "user", reg.command, "--", ...reg.args], { stdio: "pipe" });
|
|
12227
12290
|
return { target, status: "registered" };
|
|
@@ -12234,17 +12297,17 @@ async function unregister(target) {
|
|
|
12234
12297
|
const file = instructionsFileOf(target);
|
|
12235
12298
|
if (file) removeInstructions(file);
|
|
12236
12299
|
if (target === "claude") {
|
|
12237
|
-
if (
|
|
12238
|
-
removeClaudeParentHooks(PROFILE,
|
|
12300
|
+
if (existsSync5(claudeJson())) setMcpJson(claudeJson(), null);
|
|
12301
|
+
removeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"));
|
|
12239
12302
|
} else if (target === "cursor") {
|
|
12240
|
-
if (
|
|
12241
|
-
removeCursorParentHooks(PROFILE,
|
|
12303
|
+
if (existsSync5(join5(cursorDir(), "mcp.json"))) setMcpJson(join5(cursorDir(), "mcp.json"), null);
|
|
12304
|
+
removeCursorParentHooks(PROFILE, join5(cursorDir(), "hooks.json"));
|
|
12242
12305
|
setCursorPermissions(false);
|
|
12243
12306
|
setCursorContext(false);
|
|
12244
12307
|
} else if (target === "codex") {
|
|
12245
|
-
if (
|
|
12308
|
+
if (existsSync5(join5(codexHome(), "config.toml"))) {
|
|
12246
12309
|
await withCodexConfig(async (request) => {
|
|
12247
|
-
await request("config/batchWrite", { filePath:
|
|
12310
|
+
await request("config/batchWrite", { filePath: join5(codexHome(), "config.toml"), edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: null, mergeStrategy: "replace" }] });
|
|
12248
12311
|
});
|
|
12249
12312
|
}
|
|
12250
12313
|
try {
|
|
@@ -12268,20 +12331,20 @@ function registeredTools(target) {
|
|
|
12268
12331
|
const entryOf = (value) => (value?.args ?? [])[0];
|
|
12269
12332
|
if (target === "claude") {
|
|
12270
12333
|
const entry = readJson(claudeJson()).mcpServers?.[MCP_SERVER];
|
|
12271
|
-
const settings = JSON.stringify(readJson(
|
|
12334
|
+
const settings = JSON.stringify(readJson(join5(claudeDir(), "settings.json")));
|
|
12272
12335
|
const e = entryOf(entry);
|
|
12273
12336
|
return { mcp: !!entry, hooks: settings.includes(PROFILE.hooks.claude), ...e ? { entry: e } : {} };
|
|
12274
12337
|
}
|
|
12275
12338
|
if (target === "cursor") {
|
|
12276
|
-
const entry = readJson(
|
|
12339
|
+
const entry = readJson(join5(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
|
|
12277
12340
|
const e = entryOf(entry);
|
|
12278
|
-
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(
|
|
12341
|
+
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(join5(cursorDir(), "hooks.json"))), ...e ? { entry: e } : {} };
|
|
12279
12342
|
}
|
|
12280
12343
|
if (target === "codex") {
|
|
12281
|
-
const toml2 =
|
|
12344
|
+
const toml2 = existsSync5(join5(codexHome(), "config.toml")) ? readFileSync5(join5(codexHome(), "config.toml"), "utf8") : "";
|
|
12282
12345
|
return { mcp: toml2.includes(`[mcp_servers.${MCP_SERVER}]`), hooks: null };
|
|
12283
12346
|
}
|
|
12284
|
-
const toml =
|
|
12347
|
+
const toml = existsSync5(join5(grokDir(), "config.toml")) ? readFileSync5(join5(grokDir(), "config.toml"), "utf8") : "";
|
|
12285
12348
|
return { mcp: toml.includes(MCP_SERVER), hooks: null };
|
|
12286
12349
|
}
|
|
12287
12350
|
|
|
@@ -15645,11 +15708,11 @@ var StdioServerTransport = class {
|
|
|
15645
15708
|
};
|
|
15646
15709
|
|
|
15647
15710
|
// src/mcp.ts
|
|
15648
|
-
import { existsSync as
|
|
15649
|
-
import { basename as basename2, join as
|
|
15711
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
15712
|
+
import { basename as basename2, join as join6 } from "node:path";
|
|
15650
15713
|
|
|
15651
15714
|
// src/version.ts
|
|
15652
|
-
var VERSION = true ? "0.1.
|
|
15715
|
+
var VERSION = true ? "0.1.9" : "0.0.0-dev";
|
|
15653
15716
|
function osName() {
|
|
15654
15717
|
return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
|
|
15655
15718
|
}
|
|
@@ -15733,6 +15796,11 @@ var TOOLS = [
|
|
|
15733
15796
|
description: "\u7533\u8ACB\u306E\u4ECA\u306E\u72B6\u614B\u3068\u7B54\u3048\u3092\u898B\u308B\u3002\u7B54\u3048\u306F\u3075\u3064\u3046\u81EA\u52D5\u3067\u5C4A\u304F\u306E\u3067\u3001\u5C4A\u3044\u305F\u7B54\u3048\u3092\u898B\u5931\u3063\u305F\u6642\u3060\u3051\u4F7F\u3046\u3002",
|
|
15734
15797
|
inputSchema: { type: "object", properties: { decision_id: { type: "string" } }, required: ["decision_id"], additionalProperties: false }
|
|
15735
15798
|
},
|
|
15799
|
+
{
|
|
15800
|
+
name: "get_attachment",
|
|
15801
|
+
description: "\u5229\u7528\u8005\u304C\u7B54\u3048\u306B\u4ED8\u3051\u305F\u6DFB\u4ED8\uFF08\u753B\u50CF\u30FB\u66F8\u985E\uFF09\u3092\u53D6\u308B\u3002\u3053\u306E\u7AEF\u672B\u306B\u4FDD\u5B58\u3057\u3066\u5834\u6240\u3092\u8FD4\u3057\u3001\u753B\u50CF\u306F\u305D\u306E\u307E\u307E\u898B\u3089\u308C\u308B\u5F62\u3067\u3082\u8FD4\u3059\u3002attachment_id \u306F\u5C4A\u3044\u305F\u7B54\u3048\u304B get_decision \u306E answer.attachments \u306B\u3042\u308B\u3002",
|
|
15802
|
+
inputSchema: { type: "object", properties: { decision_id: { type: "string" }, attachment_id: { type: "string" } }, required: ["decision_id", "attachment_id"], additionalProperties: false }
|
|
15803
|
+
},
|
|
15736
15804
|
{
|
|
15737
15805
|
name: "setup_test",
|
|
15738
15806
|
description: 'Approval Box\u306E\u63A5\u7D9A\u30C6\u30B9\u30C8\uFF08\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u78BA\u8A8D\uFF09\u3092\u59CB\u3081\u308B\u3002\u5229\u7528\u8005\u306B\u300CApproval Box\u306Esetup_test\u3092\u5B9F\u884C\u3057\u3066\u300D\u300CApproval Box\u306E\u30C6\u30B9\u30C8\u3092\u3057\u3066\u300D\u300C\u63A5\u7D9A\u30C6\u30B9\u30C8\u3092\u3057\u3066\u300D\u3068\u8A00\u308F\u308C\u305F\u3089\u3001\u4ED6\u306E\u65B9\u6CD5\u3092\u8ABF\u3079\u305A\u306B\u3053\u306Etool\u3092\u547C\u3076\u3002Start the Approval Box connection test when the user says "test Approval Box". \u30C6\u30B9\u30C8\u306E\u7533\u8ACB\u304C\u5229\u7528\u8005\u306B\u5C4A\u304D\u3001\u7B54\u3048\u304C\u3053\u306E\u4F1A\u8A71\u3078\u5C4A\u304F\u3002',
|
|
@@ -15744,12 +15812,14 @@ var TOOLS = [
|
|
|
15744
15812
|
inputSchema: { type: "object", properties: { decision_id: { type: "string" }, code: { type: "string" } }, required: ["decision_id", "code"], additionalProperties: false }
|
|
15745
15813
|
}
|
|
15746
15814
|
];
|
|
15747
|
-
var
|
|
15815
|
+
var VIEWABLE_IMAGES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
15816
|
+
var MAX_INLINE_IMAGE = 5 * 1024 * 1024;
|
|
15817
|
+
var parentsFile = () => join6(stateRoot(), "parents.json");
|
|
15748
15818
|
var processChannels = /* @__PURE__ */ new Map();
|
|
15749
15819
|
var lastChannel;
|
|
15750
15820
|
function savedParents() {
|
|
15751
15821
|
try {
|
|
15752
|
-
return
|
|
15822
|
+
return existsSync6(parentsFile()) ? JSON.parse(readFileSync6(parentsFile(), "utf8")) : {};
|
|
15753
15823
|
} catch {
|
|
15754
15824
|
return {};
|
|
15755
15825
|
}
|
|
@@ -15920,6 +15990,21 @@ ${JSON.stringify(decision)}`, decision);
|
|
|
15920
15990
|
|
|
15921
15991
|
${JSON.stringify(decision)}`, decision);
|
|
15922
15992
|
}
|
|
15993
|
+
if (name === "get_attachment") {
|
|
15994
|
+
const decisionId = String(args.decision_id);
|
|
15995
|
+
const decision = await api.call("GET", `/decisions/${encodeURIComponent(decisionId)}`);
|
|
15996
|
+
const list = decision.answer?.attachments ?? [];
|
|
15997
|
+
const index = list.findIndex((a) => a.id === args.attachment_id);
|
|
15998
|
+
if (index < 0) return text(`Approval Box: ${decisionId} \u306E\u7B54\u3048\u306B\u3001\u305D\u306E\u6DFB\u4ED8\u306F\u3042\u308A\u307E\u305B\u3093\u3002get_decision \u306E answer.attachments \u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044\u3002`, { error: "not_found" }, true);
|
|
15999
|
+
const meta = list[index];
|
|
16000
|
+
const path = await saveAttachment(api, decisionId, meta, index);
|
|
16001
|
+
const head = `${decisionId} \u306E\u6DFB\u4ED8 ${index + 1}: ${meta.name}\uFF08${meta.content_type}\u3001${meta.size}\u30D0\u30A4\u30C8\uFF09\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F: ${path}`;
|
|
16002
|
+
const structured = { decision_id: decisionId, attachment: meta, path };
|
|
16003
|
+
if (VIEWABLE_IMAGES.includes(meta.content_type) && meta.size <= MAX_INLINE_IMAGE) {
|
|
16004
|
+
return { content: [{ type: "text", text: head }, { type: "image", data: readFileSync6(path).toString("base64"), mimeType: meta.content_type }], structuredContent: structured };
|
|
16005
|
+
}
|
|
16006
|
+
return text(head, structured);
|
|
16007
|
+
}
|
|
15923
16008
|
if (name === "setup_test") {
|
|
15924
16009
|
const channelId = await channelFor(harness, clientName, request.params._meta);
|
|
15925
16010
|
lastChannel = channelId;
|
|
@@ -15988,13 +16073,13 @@ async function ask(question) {
|
|
|
15988
16073
|
}
|
|
15989
16074
|
}
|
|
15990
16075
|
function installRuntime() {
|
|
15991
|
-
const target =
|
|
16076
|
+
const target = join7(home(), "runtime", VERSION);
|
|
15992
16077
|
if (runtimeDir() !== target) {
|
|
15993
|
-
|
|
15994
|
-
|
|
16078
|
+
rmSync3(target, { recursive: true, force: true });
|
|
16079
|
+
mkdirSync5(target, { recursive: true });
|
|
15995
16080
|
cpSync(runtimeDir(), target, { recursive: true });
|
|
15996
16081
|
}
|
|
15997
|
-
return
|
|
16082
|
+
return join7(target, "cli.mjs");
|
|
15998
16083
|
}
|
|
15999
16084
|
function selectedTargets() {
|
|
16000
16085
|
const only = option("only");
|
|
@@ -16138,7 +16223,7 @@ async function uninstall() {
|
|
|
16138
16223
|
out(` \u30B5\u30FC\u30D0\u30FC: \u63A5\u7D9A\u306E\u89E3\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30A2\u30D7\u30EA\u306E\u300C\u63A5\u7D9A\u300D\u304B\u3089\u5916\u3057\u3066\u304F\u3060\u3055\u3044\uFF09: ${error.message}`);
|
|
16139
16224
|
}
|
|
16140
16225
|
}
|
|
16141
|
-
|
|
16226
|
+
rmSync3(join7(home(), "config.json"), { force: true });
|
|
16142
16227
|
out("\nApproval Box\u3092\u5916\u3057\u307E\u3057\u305F\u3002npm install -g \u3067\u5165\u308C\u305F\u5834\u5408\u306F\u3001npm uninstall -g approval-box \u3067\u672C\u4F53\u3082\u6D88\u305B\u307E\u3059\u3002");
|
|
16143
16228
|
}
|
|
16144
16229
|
async function status() {
|
|
@@ -16176,13 +16261,13 @@ async function doctor() {
|
|
|
16176
16261
|
problems.push(error instanceof ServerError && error.status === 401 ? "\u3053\u306E\u7AEF\u672B\u306E\u63A5\u7D9A\u304C\u5916\u3055\u308C\u3066\u3044\u307E\u3059 \u2192 npx approval-box setup" : `\u30B5\u30FC\u30D0\u30FC\u306B\u3064\u306A\u304C\u308A\u307E\u305B\u3093: ${error.message}`);
|
|
16177
16262
|
}
|
|
16178
16263
|
}
|
|
16179
|
-
if (!
|
|
16264
|
+
if (!existsSync7(process.execPath)) problems.push(`node \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${process.execPath}\uFF09`);
|
|
16180
16265
|
for (const target of HARNESSES) {
|
|
16181
16266
|
if (!detect(target)) continue;
|
|
16182
16267
|
const r = registered(target);
|
|
16183
16268
|
if (!r.mcp) problems.push(`${LABEL[target]}: MCP\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093 \u2192 npx approval-box setup --only ${target}`);
|
|
16184
16269
|
if (r.hooks === false) problems.push(`${LABEL[target]}: hook\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093 \u2192 npx approval-box setup --only ${target}`);
|
|
16185
|
-
if (r.entry && !
|
|
16270
|
+
if (r.entry && !existsSync7(r.entry)) problems.push(`${LABEL[target]}: \u767B\u9332\u5148\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\uFF08${r.entry}\uFF09\u3002node \u3084Approval Box\u3092\u5165\u308C\u76F4\u3057\u305F\u6642\u306B\u8D77\u304D\u307E\u3059 \u2192 npx approval-box setup`);
|
|
16186
16271
|
if (r.mcp && r.instructions === false) hints.push(`${LABEL[target]}: \u5168\u4F53\u306E\u6307\u793A\u306BApproval Box\u306E\u4E00\u7BC0\u304C\u3042\u308A\u307E\u305B\u3093\u3002AI\u304C\u7533\u8ACB\u3092\u51FA\u3055\u306A\u3044\u6642\u306F \u2192 npx approval-box setup --only ${target}`);
|
|
16187
16272
|
}
|
|
16188
16273
|
for (const hint of hints) out(`\u30FB${hint}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "approval-box",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Approval Box connector: lets Claude Code, Codex, Cursor and Grok ask you for a decision, and steers your answer back into the same session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://approval-box.kitepon.dev",
|