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.
Files changed (2) hide show
  1. package/dist/cli.mjs +175 -82
  2. 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 existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync2 } from "node:fs";
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 join6 } from "node:path";
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
- const json = text2 ? JSON.parse(text2) : {};
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 existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync } from "node:fs";
11852
- import { join as join3 } from "node:path";
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 join2 } from "node:path";
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 join2(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
11927
+ return join3(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
11863
11928
  }
11864
11929
 
11865
11930
  // src/daemon.ts
11866
- var lockFile = () => join3(stateRoot(), "daemon.json");
11867
- var journalFile = () => join3(stateRoot(), "deliveries.json");
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 (!existsSync2(lockFile())) return { running: false };
11943
+ if (!existsSync3(lockFile())) return { running: false };
11879
11944
  try {
11880
- const lock = JSON.parse(readFileSync2(lockFile(), "utf8"));
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
- mkdirSync2(stateRoot(), { recursive: true, mode: 448 });
11898
- const log = openSync(join3(stateRoot(), "daemon.log"), "a", 384);
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 = existsSync2(journalFile()) ? JSON.parse(readFileSync2(journalFile(), "utf8")) : {};
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, item.text);
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(readFileSync2(lockFile(), "utf8"));
12044
- if (lock.pid === process.pid) rmSync(lockFile(), { force: true });
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 existsSync4, readFileSync as readFileSync4, realpathSync } from "node:fs";
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 join4 } from "node:path";
12125
+ import { delimiter, dirname as dirname4, join as join5 } from "node:path";
12055
12126
 
12056
12127
  // src/instructions.ts
12057
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
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 existsSync3(file) && readFileSync3(file, "utf8").includes(START);
12141
+ return existsSync4(file) && readFileSync4(file, "utf8").includes(START);
12071
12142
  }
12072
12143
  function writeInstructions(file) {
12073
- const current = existsSync3(file) ? readFileSync3(file, "utf8").replace(PATTERN, "\n").trimEnd() : "";
12074
- mkdirSync3(dirname3(file), { recursive: true, mode: 448 });
12075
- writeFileSync2(file, current ? `${current}
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 = readFileSync3(file, "utf8").replace(PATTERN, "\n").trimEnd();
12082
- writeFileSync2(file, rest ? `${rest}
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 ?? join4(homedir(), ".claude");
12091
- var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ? join4(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join4(homedir(), ".claude.json");
12092
- var cursorDir = () => process.env.CURSOR_HOME ?? join4(homedir(), ".cursor");
12093
- var codexHome = () => process.env.CODEX_HOME ?? join4(homedir(), ".codex");
12094
- var grokDir = () => process.env.GROK_HOME ?? join4(homedir(), ".grok");
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(), join4(claudeDir(), "settings.json")];
12097
- if (target === "cursor") return [join4(cursorDir(), "mcp.json"), join4(cursorDir(), "hooks.json"), cursorCliConfig()];
12098
- if (target === "codex") return [join4(codexHome(), "config.toml"), join4(codexHome(), "hooks.json")];
12099
- return [join4(grokDir(), "config.toml")];
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 join4(claudeDir(), "CLAUDE.md");
12103
- if (target === "codex") return join4(codexHome(), "AGENTS.md");
12104
- if (target === "grok") return join4(grokDir(), "AGENTS.md");
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 && existsSync4(join4(dir, name))) return join4(dir, name);
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 existsSync4(claudeDir()) || !!onPath("claude");
12116
- if (target === "codex") return existsSync4(codexHome()) || !!onPath("codex");
12117
- if (target === "cursor") return existsSync4(cursorDir()) || !!onPath("cursor-agent");
12118
- return existsSync4(grokDir()) || !!onPath("grok");
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 = () => join4(cursorDir(), "cli-config.json");
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 (!existsSync4(file) && !enable) return;
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 = join4(cursorDir(), "hooks.json");
12143
- if (!existsSync4(file) && !enable) return;
12144
- const target = existsSync4(file) ? realpathSync(file) : file;
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 = existsSync4(target) ? readJson(target) : { version: 1 };
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(join4(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
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 (!existsSync4(file)) return {};
12159
- return JSON.parse(readFileSync4(file, "utf8").replace(/^/, ""));
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 (existsSync4(file) && !existsSync4(copy)) copyFileSync(file, copy);
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, join4(claudeDir(), "settings.json"), hook("claude"));
12260
+ mergeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"), hook("claude"));
12190
12261
  return { target, status: "registered" };
12191
12262
  }
12192
12263
  if (target === "cursor") {
12193
- setMcpJson(join4(cursorDir(), "mcp.json"), registration());
12194
- mergeCursorParentHooks(PROFILE, join4(cursorDir(), "hooks.json"), hook("cursor"));
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 = join4(codexHome(), "config.toml");
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(join4(grokDir(), "config.toml"));
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 (existsSync4(claudeJson())) setMcpJson(claudeJson(), null);
12230
- removeClaudeParentHooks(PROFILE, join4(claudeDir(), "settings.json"));
12300
+ if (existsSync5(claudeJson())) setMcpJson(claudeJson(), null);
12301
+ removeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"));
12231
12302
  } else if (target === "cursor") {
12232
- if (existsSync4(join4(cursorDir(), "mcp.json"))) setMcpJson(join4(cursorDir(), "mcp.json"), null);
12233
- removeCursorParentHooks(PROFILE, join4(cursorDir(), "hooks.json"));
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 (existsSync4(join4(codexHome(), "config.toml"))) {
12308
+ if (existsSync5(join5(codexHome(), "config.toml"))) {
12238
12309
  await withCodexConfig(async (request) => {
12239
- await request("config/batchWrite", { filePath: join4(codexHome(), "config.toml"), edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: null, mergeStrategy: "replace" }] });
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(join4(claudeDir(), "settings.json")));
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(join4(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
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(join4(cursorDir(), "hooks.json"))), ...e ? { entry: e } : {} };
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 = existsSync4(join4(codexHome(), "config.toml")) ? readFileSync4(join4(codexHome(), "config.toml"), "utf8") : "";
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 = existsSync4(join4(grokDir(), "config.toml")) ? readFileSync4(join4(grokDir(), "config.toml"), "utf8") : "";
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 existsSync5, readFileSync as readFileSync5 } from "node:fs";
15641
- import { basename as basename2, join as join5 } from "node:path";
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.7" : "0.0.0-dev";
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 parentsFile = () => join5(stateRoot(), "parents.json");
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 existsSync5(parentsFile()) ? JSON.parse(readFileSync5(parentsFile(), "utf8")) : {};
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 = join6(home(), "runtime", VERSION);
16076
+ const target = join7(home(), "runtime", VERSION);
15984
16077
  if (runtimeDir() !== target) {
15985
- rmSync2(target, { recursive: true, force: true });
15986
- mkdirSync4(target, { recursive: true });
16078
+ rmSync3(target, { recursive: true, force: true });
16079
+ mkdirSync5(target, { recursive: true });
15987
16080
  cpSync(runtimeDir(), target, { recursive: true });
15988
16081
  }
15989
- return join6(target, "cli.mjs");
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
- rmSync2(join6(home(), "config.json"), { force: true });
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 (!existsSync6(process.execPath)) problems.push(`node \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${process.execPath}\uFF09`);
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 && !existsSync6(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`);
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.7",
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",