approval-box 0.1.7 → 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 +175 -82
- 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
|
|
@@ -11795,13 +11795,33 @@ var Api = class {
|
|
|
11795
11795
|
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}`, {});
|
|
11796
11796
|
}
|
|
11797
11797
|
const text2 = await response.text();
|
|
11798
|
-
|
|
11798
|
+
let json;
|
|
11799
|
+
try {
|
|
11800
|
+
json = text2 ? JSON.parse(text2) : {};
|
|
11801
|
+
} catch {
|
|
11802
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
11803
|
+
const head = `Approval Box\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u3001JSON\u3067\u306A\u3044\u5FDC\u7B54\u304C\u8FD4\u308A\u307E\u3057\u305F\uFF08HTTP ${response.status}\u3001Content-Type: ${contentType || "\u306A\u3057"}\uFF09\u3002\u524D\u6BB5\uFF08Cloudflare\u306A\u3069\uFF09\u306E\u30A8\u30E9\u30FC\u30DA\u30FC\u30B8\u3067\u3001\u30B5\u30FC\u30D0\u30FC\u304C\u4E00\u6642\u7684\u306B\u6B62\u307E\u3063\u3066\u3044\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`;
|
|
11804
|
+
const tail = method === "GET" ? "\u5C11\u3057\u5F85\u3063\u3066\u304B\u3089\u3001\u540C\u3058\u64CD\u4F5C\u3092\u3084\u308A\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "\u3053\u306E\u64CD\u4F5C\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u884C\u308F\u308C\u305F\u304B\u306F\u5206\u304B\u308A\u307E\u305B\u3093\u3002\u3084\u308A\u76F4\u3059\u524D\u306B list_my_decisions \u3067\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u7533\u8ACB\u306A\u3089\u3001\u4E00\u89A7\u306B\u7121\u3051\u308C\u3070\u51FA\u3057\u76F4\u3057\u3001\u3042\u308C\u3070\u305D\u308C\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002";
|
|
11805
|
+
throw new ServerError(response.status, "non_json_response", `${head}${tail}`, { http_status: response.status, content_type: contentType, outcome: method === "GET" ? "not_applied" : "unknown" });
|
|
11806
|
+
}
|
|
11799
11807
|
if (!response.ok) {
|
|
11800
11808
|
const error = json.error ?? {};
|
|
11801
11809
|
throw new ServerError(response.status, error.code ?? "internal", error.message ?? `HTTP ${response.status}`, error);
|
|
11802
11810
|
}
|
|
11803
11811
|
return json;
|
|
11804
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
|
+
}
|
|
11805
11825
|
/** コネクタ用SSE。届いたイベント名ごとに onEvent を呼ぶ。切れたら戻る。 */
|
|
11806
11826
|
async stream(onEvent, signal) {
|
|
11807
11827
|
const response = await fetch(this.url("/stream"), { headers: { authorization: `Bearer ${this.token}`, accept: "text/event-stream" }, signal });
|
|
@@ -11848,23 +11868,68 @@ function writeConfig(config) {
|
|
|
11848
11868
|
|
|
11849
11869
|
// src/daemon.ts
|
|
11850
11870
|
import { spawn } from "node:child_process";
|
|
11851
|
-
import { existsSync as
|
|
11852
|
-
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");
|
|
11853
11918
|
|
|
11854
11919
|
// src/runtime.ts
|
|
11855
|
-
import { basename, dirname as dirname2, join as
|
|
11920
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
11856
11921
|
import { fileURLToPath } from "node:url";
|
|
11857
11922
|
var runtimeDir = () => {
|
|
11858
11923
|
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
11859
11924
|
return basename(dir) === "chunks" ? dirname2(dir) : dir;
|
|
11860
11925
|
};
|
|
11861
11926
|
function runtimeEntry(kind) {
|
|
11862
|
-
return
|
|
11927
|
+
return join3(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
|
|
11863
11928
|
}
|
|
11864
11929
|
|
|
11865
11930
|
// src/daemon.ts
|
|
11866
|
-
var lockFile = () =>
|
|
11867
|
-
var journalFile = () =>
|
|
11931
|
+
var lockFile = () => join4(stateRoot(), "daemon.json");
|
|
11932
|
+
var journalFile = () => join4(stateRoot(), "deliveries.json");
|
|
11868
11933
|
var IDLE_EXIT_MS = 30 * 6e4;
|
|
11869
11934
|
function alive(pid) {
|
|
11870
11935
|
try {
|
|
@@ -11875,9 +11940,9 @@ function alive(pid) {
|
|
|
11875
11940
|
}
|
|
11876
11941
|
}
|
|
11877
11942
|
function daemonStatus() {
|
|
11878
|
-
if (!
|
|
11943
|
+
if (!existsSync3(lockFile())) return { running: false };
|
|
11879
11944
|
try {
|
|
11880
|
-
const lock = JSON.parse(
|
|
11945
|
+
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
11881
11946
|
return alive(lock.pid) ? { running: true, pid: lock.pid, entry: lock.entry } : { running: false };
|
|
11882
11947
|
} catch {
|
|
11883
11948
|
return { running: false };
|
|
@@ -11894,8 +11959,8 @@ function ensureDaemon() {
|
|
|
11894
11959
|
} catch {
|
|
11895
11960
|
}
|
|
11896
11961
|
}
|
|
11897
|
-
|
|
11898
|
-
const log = openSync(
|
|
11962
|
+
mkdirSync3(stateRoot(), { recursive: true, mode: 448 });
|
|
11963
|
+
const log = openSync(join4(stateRoot(), "daemon.log"), "a", 384);
|
|
11899
11964
|
spawn(process.execPath, [runtimeEntry("cli"), "daemon"], { detached: true, stdio: ["ignore", "ignore", log], windowsHide: true }).unref();
|
|
11900
11965
|
} catch (error) {
|
|
11901
11966
|
process.stderr.write(`approval-box: \u914D\u9001\u30C7\u30FC\u30E2\u30F3\u3092\u8D77\u52D5\u3067\u304D\u307E\u305B\u3093: ${error.message}
|
|
@@ -11908,7 +11973,7 @@ async function runDaemon() {
|
|
|
11908
11973
|
if (status2.running && status2.pid !== process.pid) return;
|
|
11909
11974
|
writeJsonFile(lockFile(), { pid: process.pid, entry: runtimeEntry("cli"), started_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11910
11975
|
const api = new Api(requireConfig());
|
|
11911
|
-
let journal =
|
|
11976
|
+
let journal = existsSync3(journalFile()) ? JSON.parse(readFileSync3(journalFile(), "utf8")) : {};
|
|
11912
11977
|
const save = () => writeJsonFile(journalFile(), journal);
|
|
11913
11978
|
const log = (message) => process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}
|
|
11914
11979
|
`);
|
|
@@ -11920,6 +11985,11 @@ async function runDaemon() {
|
|
|
11920
11985
|
journal[id] = entry;
|
|
11921
11986
|
}
|
|
11922
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
|
+
}
|
|
11923
11993
|
async function report(id) {
|
|
11924
11994
|
const entry = journal[id];
|
|
11925
11995
|
try {
|
|
@@ -11933,12 +12003,13 @@ async function runDaemon() {
|
|
|
11933
12003
|
async function deliver(item) {
|
|
11934
12004
|
const existing = journal[item.decision_id];
|
|
11935
12005
|
if (existing) return;
|
|
12006
|
+
const text2 = item.attachments?.length ? item.text + await saveAll(api, item.decision_id, item.attachments) : item.text;
|
|
11936
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() };
|
|
11937
12008
|
save();
|
|
11938
12009
|
const entry = journal[item.decision_id];
|
|
11939
12010
|
try {
|
|
11940
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 });
|
|
11941
|
-
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);
|
|
11942
12013
|
if (result.state === "submitted") Object.assign(entry, { state: "report", result: "delivered" });
|
|
11943
12014
|
else entry.state = "queued";
|
|
11944
12015
|
} catch (error) {
|
|
@@ -12040,8 +12111,8 @@ async function runDaemon() {
|
|
|
12040
12111
|
}
|
|
12041
12112
|
}
|
|
12042
12113
|
try {
|
|
12043
|
-
const lock = JSON.parse(
|
|
12044
|
-
if (lock.pid === process.pid)
|
|
12114
|
+
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
12115
|
+
if (lock.pid === process.pid) rmSync2(lockFile(), { force: true });
|
|
12045
12116
|
} catch {
|
|
12046
12117
|
}
|
|
12047
12118
|
process.exit(0);
|
|
@@ -12049,12 +12120,12 @@ async function runDaemon() {
|
|
|
12049
12120
|
|
|
12050
12121
|
// src/harness.ts
|
|
12051
12122
|
import { execFileSync } from "node:child_process";
|
|
12052
|
-
import { copyFileSync, existsSync as
|
|
12123
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
12053
12124
|
import { homedir } from "node:os";
|
|
12054
|
-
import { delimiter, dirname as dirname4, join as
|
|
12125
|
+
import { delimiter, dirname as dirname4, join as join5 } from "node:path";
|
|
12055
12126
|
|
|
12056
12127
|
// src/instructions.ts
|
|
12057
|
-
import { existsSync as
|
|
12128
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12058
12129
|
import { dirname as dirname3 } from "node:path";
|
|
12059
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";
|
|
12060
12131
|
var START = "<!-- approval-box:start -->";
|
|
@@ -12067,19 +12138,19 @@ ${END}
|
|
|
12067
12138
|
`;
|
|
12068
12139
|
var PATTERN = new RegExp(`\\n*${START}[\\s\\S]*?${END}\\n?`, "g");
|
|
12069
12140
|
function hasInstructions(file) {
|
|
12070
|
-
return
|
|
12141
|
+
return existsSync4(file) && readFileSync4(file, "utf8").includes(START);
|
|
12071
12142
|
}
|
|
12072
12143
|
function writeInstructions(file) {
|
|
12073
|
-
const current =
|
|
12074
|
-
|
|
12075
|
-
|
|
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}
|
|
12076
12147
|
|
|
12077
12148
|
${BLOCK}` : BLOCK);
|
|
12078
12149
|
}
|
|
12079
12150
|
function removeInstructions(file) {
|
|
12080
12151
|
if (!hasInstructions(file)) return;
|
|
12081
|
-
const rest =
|
|
12082
|
-
|
|
12152
|
+
const rest = readFileSync4(file, "utf8").replace(PATTERN, "\n").trimEnd();
|
|
12153
|
+
writeFileSync3(file, rest ? `${rest}
|
|
12083
12154
|
` : "");
|
|
12084
12155
|
}
|
|
12085
12156
|
|
|
@@ -12087,35 +12158,35 @@ function removeInstructions(file) {
|
|
|
12087
12158
|
var HARNESSES = ["claude", "codex", "cursor", "grok"];
|
|
12088
12159
|
var LABEL = { claude: "Claude Code", codex: "Codex", cursor: "Cursor", grok: "Grok" };
|
|
12089
12160
|
var CLIENT = { claude: "claude-code", codex: "codex", cursor: "cursor", grok: "grok" };
|
|
12090
|
-
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ??
|
|
12091
|
-
var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ?
|
|
12092
|
-
var cursorDir = () => process.env.CURSOR_HOME ??
|
|
12093
|
-
var codexHome = () => process.env.CODEX_HOME ??
|
|
12094
|
-
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");
|
|
12095
12166
|
function filesOf(target) {
|
|
12096
|
-
if (target === "claude") return [claudeJson(),
|
|
12097
|
-
if (target === "cursor") return [
|
|
12098
|
-
if (target === "codex") return [
|
|
12099
|
-
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")];
|
|
12100
12171
|
}
|
|
12101
12172
|
function instructionsFileOf(target) {
|
|
12102
|
-
if (target === "claude") return
|
|
12103
|
-
if (target === "codex") return
|
|
12104
|
-
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");
|
|
12105
12176
|
return null;
|
|
12106
12177
|
}
|
|
12107
12178
|
function onPath(command2) {
|
|
12108
12179
|
const names = process.platform === "win32" ? [`${command2}.exe`, `${command2}.cmd`, command2] : [command2];
|
|
12109
12180
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
12110
|
-
for (const name of names) if (dir &&
|
|
12181
|
+
for (const name of names) if (dir && existsSync5(join5(dir, name))) return join5(dir, name);
|
|
12111
12182
|
}
|
|
12112
12183
|
return null;
|
|
12113
12184
|
}
|
|
12114
12185
|
function detect(target) {
|
|
12115
|
-
if (target === "claude") return
|
|
12116
|
-
if (target === "codex") return
|
|
12117
|
-
if (target === "cursor") return
|
|
12118
|
-
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");
|
|
12119
12190
|
}
|
|
12120
12191
|
function stableNode() {
|
|
12121
12192
|
try {
|
|
@@ -12124,11 +12195,11 @@ function stableNode() {
|
|
|
12124
12195
|
return process.execPath;
|
|
12125
12196
|
}
|
|
12126
12197
|
}
|
|
12127
|
-
var CURSOR_TOOLS = ["list_my_decisions", "request_decision", "amend_decision", "cancel_decision", "get_decision", "setup_test", "confirm_setup_test"];
|
|
12128
|
-
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");
|
|
12129
12200
|
function setCursorPermissions(enable) {
|
|
12130
12201
|
const file = cursorCliConfig();
|
|
12131
|
-
if (!
|
|
12202
|
+
if (!existsSync5(file) && !enable) return;
|
|
12132
12203
|
backupOnce(file);
|
|
12133
12204
|
const current = readJson(file);
|
|
12134
12205
|
const permissions = current.permissions ?? {};
|
|
@@ -12139,11 +12210,11 @@ function setCursorPermissions(enable) {
|
|
|
12139
12210
|
}
|
|
12140
12211
|
var CURSOR_CONTEXT = "cursor-context";
|
|
12141
12212
|
function setCursorContext(enable) {
|
|
12142
|
-
const file =
|
|
12143
|
-
if (!
|
|
12144
|
-
const target =
|
|
12213
|
+
const file = join5(cursorDir(), "hooks.json");
|
|
12214
|
+
if (!existsSync5(file) && !enable) return;
|
|
12215
|
+
const target = existsSync5(file) ? realpathSync(file) : file;
|
|
12145
12216
|
backupOnce(target);
|
|
12146
|
-
const current =
|
|
12217
|
+
const current = existsSync5(target) ? readJson(target) : { version: 1 };
|
|
12147
12218
|
const hooks = { ...current.hooks ?? {} };
|
|
12148
12219
|
const ours = (entry) => typeof entry?.command === "string" && entry.command.includes(MCP_SERVER) && entry.command.endsWith(` ${CURSOR_CONTEXT}`);
|
|
12149
12220
|
const rest = (hooks.sessionStart ?? []).filter((entry) => !ours(entry));
|
|
@@ -12152,15 +12223,15 @@ function setCursorContext(enable) {
|
|
|
12152
12223
|
else delete hooks.sessionStart;
|
|
12153
12224
|
writeJsonFile(target, { version: 1, ...current, hooks });
|
|
12154
12225
|
}
|
|
12155
|
-
var cursorContextRegistered = () => JSON.stringify(readJson(
|
|
12226
|
+
var cursorContextRegistered = () => JSON.stringify(readJson(join5(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
|
|
12156
12227
|
var registration = () => ({ command: stableNode(), args: [runtimeEntry("cli"), "mcp"] });
|
|
12157
12228
|
function readJson(file) {
|
|
12158
|
-
if (!
|
|
12159
|
-
return JSON.parse(
|
|
12229
|
+
if (!existsSync5(file)) return {};
|
|
12230
|
+
return JSON.parse(readFileSync5(file, "utf8").replace(/^/, ""));
|
|
12160
12231
|
}
|
|
12161
12232
|
function backupOnce(file) {
|
|
12162
12233
|
const copy = `${file}${PROFILE.backup_suffix}`;
|
|
12163
|
-
if (
|
|
12234
|
+
if (existsSync5(file) && !existsSync5(copy)) copyFileSync(file, copy);
|
|
12164
12235
|
}
|
|
12165
12236
|
function setMcpJson(file, value) {
|
|
12166
12237
|
backupOnce(file);
|
|
@@ -12186,18 +12257,18 @@ async function register(target, options = {}) {
|
|
|
12186
12257
|
try {
|
|
12187
12258
|
if (target === "claude") {
|
|
12188
12259
|
setMcpJson(claudeJson(), { type: "stdio", ...registration() });
|
|
12189
|
-
mergeClaudeParentHooks(PROFILE,
|
|
12260
|
+
mergeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"), hook("claude"));
|
|
12190
12261
|
return { target, status: "registered" };
|
|
12191
12262
|
}
|
|
12192
12263
|
if (target === "cursor") {
|
|
12193
|
-
setMcpJson(
|
|
12194
|
-
mergeCursorParentHooks(PROFILE,
|
|
12264
|
+
setMcpJson(join5(cursorDir(), "mcp.json"), registration());
|
|
12265
|
+
mergeCursorParentHooks(PROFILE, join5(cursorDir(), "hooks.json"), hook("cursor"));
|
|
12195
12266
|
setCursorPermissions(true);
|
|
12196
12267
|
setCursorContext(!!options.instructions);
|
|
12197
12268
|
return { target, status: "registered" };
|
|
12198
12269
|
}
|
|
12199
12270
|
if (target === "codex") {
|
|
12200
|
-
const config =
|
|
12271
|
+
const config = join5(codexHome(), "config.toml");
|
|
12201
12272
|
backupOnce(config);
|
|
12202
12273
|
await withCodexConfig(async (request) => {
|
|
12203
12274
|
await request("config/batchWrite", { filePath: config, edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: { ...registration(), env_vars: ["CODEX_HOME"] }, mergeStrategy: "replace" }] });
|
|
@@ -12213,7 +12284,7 @@ async function register(target, options = {}) {
|
|
|
12213
12284
|
}
|
|
12214
12285
|
const grok = onPath("grok");
|
|
12215
12286
|
if (!grok) throw new Error("grok \u30B3\u30DE\u30F3\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
12216
|
-
backupOnce(
|
|
12287
|
+
backupOnce(join5(grokDir(), "config.toml"));
|
|
12217
12288
|
const reg = registration();
|
|
12218
12289
|
execFileSync(grok, ["mcp", "add", MCP_SERVER, "--scope", "user", reg.command, "--", ...reg.args], { stdio: "pipe" });
|
|
12219
12290
|
return { target, status: "registered" };
|
|
@@ -12226,17 +12297,17 @@ async function unregister(target) {
|
|
|
12226
12297
|
const file = instructionsFileOf(target);
|
|
12227
12298
|
if (file) removeInstructions(file);
|
|
12228
12299
|
if (target === "claude") {
|
|
12229
|
-
if (
|
|
12230
|
-
removeClaudeParentHooks(PROFILE,
|
|
12300
|
+
if (existsSync5(claudeJson())) setMcpJson(claudeJson(), null);
|
|
12301
|
+
removeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"));
|
|
12231
12302
|
} else if (target === "cursor") {
|
|
12232
|
-
if (
|
|
12233
|
-
removeCursorParentHooks(PROFILE,
|
|
12303
|
+
if (existsSync5(join5(cursorDir(), "mcp.json"))) setMcpJson(join5(cursorDir(), "mcp.json"), null);
|
|
12304
|
+
removeCursorParentHooks(PROFILE, join5(cursorDir(), "hooks.json"));
|
|
12234
12305
|
setCursorPermissions(false);
|
|
12235
12306
|
setCursorContext(false);
|
|
12236
12307
|
} else if (target === "codex") {
|
|
12237
|
-
if (
|
|
12308
|
+
if (existsSync5(join5(codexHome(), "config.toml"))) {
|
|
12238
12309
|
await withCodexConfig(async (request) => {
|
|
12239
|
-
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" }] });
|
|
12240
12311
|
});
|
|
12241
12312
|
}
|
|
12242
12313
|
try {
|
|
@@ -12260,20 +12331,20 @@ function registeredTools(target) {
|
|
|
12260
12331
|
const entryOf = (value) => (value?.args ?? [])[0];
|
|
12261
12332
|
if (target === "claude") {
|
|
12262
12333
|
const entry = readJson(claudeJson()).mcpServers?.[MCP_SERVER];
|
|
12263
|
-
const settings = JSON.stringify(readJson(
|
|
12334
|
+
const settings = JSON.stringify(readJson(join5(claudeDir(), "settings.json")));
|
|
12264
12335
|
const e = entryOf(entry);
|
|
12265
12336
|
return { mcp: !!entry, hooks: settings.includes(PROFILE.hooks.claude), ...e ? { entry: e } : {} };
|
|
12266
12337
|
}
|
|
12267
12338
|
if (target === "cursor") {
|
|
12268
|
-
const entry = readJson(
|
|
12339
|
+
const entry = readJson(join5(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
|
|
12269
12340
|
const e = entryOf(entry);
|
|
12270
|
-
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(
|
|
12341
|
+
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(join5(cursorDir(), "hooks.json"))), ...e ? { entry: e } : {} };
|
|
12271
12342
|
}
|
|
12272
12343
|
if (target === "codex") {
|
|
12273
|
-
const toml2 =
|
|
12344
|
+
const toml2 = existsSync5(join5(codexHome(), "config.toml")) ? readFileSync5(join5(codexHome(), "config.toml"), "utf8") : "";
|
|
12274
12345
|
return { mcp: toml2.includes(`[mcp_servers.${MCP_SERVER}]`), hooks: null };
|
|
12275
12346
|
}
|
|
12276
|
-
const toml =
|
|
12347
|
+
const toml = existsSync5(join5(grokDir(), "config.toml")) ? readFileSync5(join5(grokDir(), "config.toml"), "utf8") : "";
|
|
12277
12348
|
return { mcp: toml.includes(MCP_SERVER), hooks: null };
|
|
12278
12349
|
}
|
|
12279
12350
|
|
|
@@ -15637,11 +15708,11 @@ var StdioServerTransport = class {
|
|
|
15637
15708
|
};
|
|
15638
15709
|
|
|
15639
15710
|
// src/mcp.ts
|
|
15640
|
-
import { existsSync as
|
|
15641
|
-
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";
|
|
15642
15713
|
|
|
15643
15714
|
// src/version.ts
|
|
15644
|
-
var VERSION = true ? "0.1.
|
|
15715
|
+
var VERSION = true ? "0.1.9" : "0.0.0-dev";
|
|
15645
15716
|
function osName() {
|
|
15646
15717
|
return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
|
|
15647
15718
|
}
|
|
@@ -15725,6 +15796,11 @@ var TOOLS = [
|
|
|
15725
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",
|
|
15726
15797
|
inputSchema: { type: "object", properties: { decision_id: { type: "string" } }, required: ["decision_id"], additionalProperties: false }
|
|
15727
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
|
+
},
|
|
15728
15804
|
{
|
|
15729
15805
|
name: "setup_test",
|
|
15730
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',
|
|
@@ -15736,12 +15812,14 @@ var TOOLS = [
|
|
|
15736
15812
|
inputSchema: { type: "object", properties: { decision_id: { type: "string" }, code: { type: "string" } }, required: ["decision_id", "code"], additionalProperties: false }
|
|
15737
15813
|
}
|
|
15738
15814
|
];
|
|
15739
|
-
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");
|
|
15740
15818
|
var processChannels = /* @__PURE__ */ new Map();
|
|
15741
15819
|
var lastChannel;
|
|
15742
15820
|
function savedParents() {
|
|
15743
15821
|
try {
|
|
15744
|
-
return
|
|
15822
|
+
return existsSync6(parentsFile()) ? JSON.parse(readFileSync6(parentsFile(), "utf8")) : {};
|
|
15745
15823
|
} catch {
|
|
15746
15824
|
return {};
|
|
15747
15825
|
}
|
|
@@ -15912,6 +15990,21 @@ ${JSON.stringify(decision)}`, decision);
|
|
|
15912
15990
|
|
|
15913
15991
|
${JSON.stringify(decision)}`, decision);
|
|
15914
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
|
+
}
|
|
15915
16008
|
if (name === "setup_test") {
|
|
15916
16009
|
const channelId = await channelFor(harness, clientName, request.params._meta);
|
|
15917
16010
|
lastChannel = channelId;
|
|
@@ -15980,13 +16073,13 @@ async function ask(question) {
|
|
|
15980
16073
|
}
|
|
15981
16074
|
}
|
|
15982
16075
|
function installRuntime() {
|
|
15983
|
-
const target =
|
|
16076
|
+
const target = join7(home(), "runtime", VERSION);
|
|
15984
16077
|
if (runtimeDir() !== target) {
|
|
15985
|
-
|
|
15986
|
-
|
|
16078
|
+
rmSync3(target, { recursive: true, force: true });
|
|
16079
|
+
mkdirSync5(target, { recursive: true });
|
|
15987
16080
|
cpSync(runtimeDir(), target, { recursive: true });
|
|
15988
16081
|
}
|
|
15989
|
-
return
|
|
16082
|
+
return join7(target, "cli.mjs");
|
|
15990
16083
|
}
|
|
15991
16084
|
function selectedTargets() {
|
|
15992
16085
|
const only = option("only");
|
|
@@ -16130,7 +16223,7 @@ async function uninstall() {
|
|
|
16130
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}`);
|
|
16131
16224
|
}
|
|
16132
16225
|
}
|
|
16133
|
-
|
|
16226
|
+
rmSync3(join7(home(), "config.json"), { force: true });
|
|
16134
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");
|
|
16135
16228
|
}
|
|
16136
16229
|
async function status() {
|
|
@@ -16168,13 +16261,13 @@ async function doctor() {
|
|
|
16168
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}`);
|
|
16169
16262
|
}
|
|
16170
16263
|
}
|
|
16171
|
-
if (!
|
|
16264
|
+
if (!existsSync7(process.execPath)) problems.push(`node \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${process.execPath}\uFF09`);
|
|
16172
16265
|
for (const target of HARNESSES) {
|
|
16173
16266
|
if (!detect(target)) continue;
|
|
16174
16267
|
const r = registered(target);
|
|
16175
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}`);
|
|
16176
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}`);
|
|
16177
|
-
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`);
|
|
16178
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}`);
|
|
16179
16272
|
}
|
|
16180
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",
|