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.
Files changed (2) hide show
  1. package/dist/cli.mjs +166 -81
  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
@@ -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 existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync } from "node:fs";
11860
- 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");
11861
11918
 
11862
11919
  // src/runtime.ts
11863
- import { basename, dirname as dirname2, join as join2 } from "node:path";
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 join2(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
11927
+ return join3(runtimeDir(), kind === "cli" ? "cli.mjs" : HOOK_FILES[kind]);
11871
11928
  }
11872
11929
 
11873
11930
  // src/daemon.ts
11874
- var lockFile = () => join3(stateRoot(), "daemon.json");
11875
- var journalFile = () => join3(stateRoot(), "deliveries.json");
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 (!existsSync2(lockFile())) return { running: false };
11943
+ if (!existsSync3(lockFile())) return { running: false };
11887
11944
  try {
11888
- const lock = JSON.parse(readFileSync2(lockFile(), "utf8"));
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
- mkdirSync2(stateRoot(), { recursive: true, mode: 448 });
11906
- 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);
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 = existsSync2(journalFile()) ? JSON.parse(readFileSync2(journalFile(), "utf8")) : {};
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, item.text);
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(readFileSync2(lockFile(), "utf8"));
12052
- 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 });
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 existsSync4, readFileSync as readFileSync4, realpathSync } from "node:fs";
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 join4 } from "node:path";
12125
+ import { delimiter, dirname as dirname4, join as join5 } from "node:path";
12063
12126
 
12064
12127
  // src/instructions.ts
12065
- 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";
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 existsSync3(file) && readFileSync3(file, "utf8").includes(START);
12141
+ return existsSync4(file) && readFileSync4(file, "utf8").includes(START);
12079
12142
  }
12080
12143
  function writeInstructions(file) {
12081
- const current = existsSync3(file) ? readFileSync3(file, "utf8").replace(PATTERN, "\n").trimEnd() : "";
12082
- mkdirSync3(dirname3(file), { recursive: true, mode: 448 });
12083
- 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}
12084
12147
 
12085
12148
  ${BLOCK}` : BLOCK);
12086
12149
  }
12087
12150
  function removeInstructions(file) {
12088
12151
  if (!hasInstructions(file)) return;
12089
- const rest = readFileSync3(file, "utf8").replace(PATTERN, "\n").trimEnd();
12090
- writeFileSync2(file, rest ? `${rest}
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 ?? join4(homedir(), ".claude");
12099
- var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ? join4(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join4(homedir(), ".claude.json");
12100
- var cursorDir = () => process.env.CURSOR_HOME ?? join4(homedir(), ".cursor");
12101
- var codexHome = () => process.env.CODEX_HOME ?? join4(homedir(), ".codex");
12102
- 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");
12103
12166
  function filesOf(target) {
12104
- if (target === "claude") return [claudeJson(), join4(claudeDir(), "settings.json")];
12105
- if (target === "cursor") return [join4(cursorDir(), "mcp.json"), join4(cursorDir(), "hooks.json"), cursorCliConfig()];
12106
- if (target === "codex") return [join4(codexHome(), "config.toml"), join4(codexHome(), "hooks.json")];
12107
- 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")];
12108
12171
  }
12109
12172
  function instructionsFileOf(target) {
12110
- if (target === "claude") return join4(claudeDir(), "CLAUDE.md");
12111
- if (target === "codex") return join4(codexHome(), "AGENTS.md");
12112
- 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");
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 && existsSync4(join4(dir, name))) return join4(dir, name);
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 existsSync4(claudeDir()) || !!onPath("claude");
12124
- if (target === "codex") return existsSync4(codexHome()) || !!onPath("codex");
12125
- if (target === "cursor") return existsSync4(cursorDir()) || !!onPath("cursor-agent");
12126
- 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");
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 = () => 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");
12137
12200
  function setCursorPermissions(enable) {
12138
12201
  const file = cursorCliConfig();
12139
- if (!existsSync4(file) && !enable) return;
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 = join4(cursorDir(), "hooks.json");
12151
- if (!existsSync4(file) && !enable) return;
12152
- 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;
12153
12216
  backupOnce(target);
12154
- const current = existsSync4(target) ? readJson(target) : { version: 1 };
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(join4(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
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 (!existsSync4(file)) return {};
12167
- return JSON.parse(readFileSync4(file, "utf8").replace(/^/, ""));
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 (existsSync4(file) && !existsSync4(copy)) copyFileSync(file, copy);
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, join4(claudeDir(), "settings.json"), hook("claude"));
12260
+ mergeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"), hook("claude"));
12198
12261
  return { target, status: "registered" };
12199
12262
  }
12200
12263
  if (target === "cursor") {
12201
- setMcpJson(join4(cursorDir(), "mcp.json"), registration());
12202
- mergeCursorParentHooks(PROFILE, join4(cursorDir(), "hooks.json"), hook("cursor"));
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 = join4(codexHome(), "config.toml");
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(join4(grokDir(), "config.toml"));
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 (existsSync4(claudeJson())) setMcpJson(claudeJson(), null);
12238
- removeClaudeParentHooks(PROFILE, join4(claudeDir(), "settings.json"));
12300
+ if (existsSync5(claudeJson())) setMcpJson(claudeJson(), null);
12301
+ removeClaudeParentHooks(PROFILE, join5(claudeDir(), "settings.json"));
12239
12302
  } else if (target === "cursor") {
12240
- if (existsSync4(join4(cursorDir(), "mcp.json"))) setMcpJson(join4(cursorDir(), "mcp.json"), null);
12241
- 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"));
12242
12305
  setCursorPermissions(false);
12243
12306
  setCursorContext(false);
12244
12307
  } else if (target === "codex") {
12245
- if (existsSync4(join4(codexHome(), "config.toml"))) {
12308
+ if (existsSync5(join5(codexHome(), "config.toml"))) {
12246
12309
  await withCodexConfig(async (request) => {
12247
- 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" }] });
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(join4(claudeDir(), "settings.json")));
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(join4(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
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(join4(cursorDir(), "hooks.json"))), ...e ? { entry: e } : {} };
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 = 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") : "";
12282
12345
  return { mcp: toml2.includes(`[mcp_servers.${MCP_SERVER}]`), hooks: null };
12283
12346
  }
12284
- 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") : "";
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 existsSync5, readFileSync as readFileSync5 } from "node:fs";
15649
- 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";
15650
15713
 
15651
15714
  // src/version.ts
15652
- var VERSION = true ? "0.1.8" : "0.0.0-dev";
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 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");
15748
15818
  var processChannels = /* @__PURE__ */ new Map();
15749
15819
  var lastChannel;
15750
15820
  function savedParents() {
15751
15821
  try {
15752
- return existsSync5(parentsFile()) ? JSON.parse(readFileSync5(parentsFile(), "utf8")) : {};
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 = join6(home(), "runtime", VERSION);
16076
+ const target = join7(home(), "runtime", VERSION);
15992
16077
  if (runtimeDir() !== target) {
15993
- rmSync2(target, { recursive: true, force: true });
15994
- mkdirSync4(target, { recursive: true });
16078
+ rmSync3(target, { recursive: true, force: true });
16079
+ mkdirSync5(target, { recursive: true });
15995
16080
  cpSync(runtimeDir(), target, { recursive: true });
15996
16081
  }
15997
- return join6(target, "cli.mjs");
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
- rmSync2(join6(home(), "config.json"), { force: true });
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 (!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`);
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 && !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`);
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.8",
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",