approval-box 0.1.19 → 0.1.20

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/cli.mjs +79 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -50,3 +50,7 @@ Requires Node.js 20 or later. License: MIT.
50
50
 
51
51
  この機能はコネクタ0.1.13以降と対応サーバーを使います。会話を開き直しても、
52
52
  再開を指定するまでは元の会話が配送先です。答えは `get_decision` でも取得できます。
53
+
54
+ ### 申請に画像を添える
55
+
56
+ `request_decision` に `image_paths: ["/absolute/path/screenshot.png"]` を指定すると、AIの画像を本文下に添えて申請します。10件、単体20MiB、合計50MiBまで。全画像を上げてから申請と通知を確定します。最初の確認の札を付けた出し直しでも同じ画像パスを渡してください。`amend_decision.changes.image_paths` は全置換、空配列で削除です。回答添付とは別の欄です。リモートMCPは `upload_request_image` のIDを `request_attachment_ids` に渡します。
package/dist/cli.mjs CHANGED
@@ -1336,11 +1336,11 @@ var require_dijkstra = __commonJS({
1336
1336
  var predecessors = {};
1337
1337
  var costs = {};
1338
1338
  costs[s] = 0;
1339
- var open = dijkstra.PriorityQueue.make();
1340
- open.push(s, 0);
1339
+ var open2 = dijkstra.PriorityQueue.make();
1340
+ open2.push(s, 0);
1341
1341
  var closest, u, v, cost_of_s_to_u, adjacent_nodes, cost_of_e, cost_of_s_to_u_plus_cost_of_e, cost_of_s_to_v, first_visit;
1342
- while (!open.empty()) {
1343
- closest = open.pop();
1342
+ while (!open2.empty()) {
1343
+ closest = open2.pop();
1344
1344
  u = closest.value;
1345
1345
  cost_of_s_to_u = closest.cost;
1346
1346
  adjacent_nodes = graph[u] || {};
@@ -1352,7 +1352,7 @@ var require_dijkstra = __commonJS({
1352
1352
  first_visit = typeof costs[v] === "undefined";
1353
1353
  if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
1354
1354
  costs[v] = cost_of_s_to_u_plus_cost_of_e;
1355
- open.push(v, cost_of_s_to_u_plus_cost_of_e);
1355
+ open2.push(v, cost_of_s_to_u_plus_cost_of_e);
1356
1356
  predecessors[v] = u;
1357
1357
  }
1358
1358
  }
@@ -11814,6 +11814,17 @@ var Api = class {
11814
11814
  }
11815
11815
  return json;
11816
11816
  }
11817
+ /** 申請前画像をバイナリで上げる。 */
11818
+ async uploadRequestImage(data, name, contentType) {
11819
+ const response = await fetch(this.url(`/request-images?name=${encodeURIComponent(name)}`), {
11820
+ method: "POST",
11821
+ headers: { authorization: `Bearer ${this.token}`, "content-type": contentType },
11822
+ body: new Uint8Array(data)
11823
+ });
11824
+ const json = await response.json();
11825
+ if (!response.ok) throw new ServerError(response.status, json.error?.code ?? "internal", json.error?.message ?? `HTTP ${response.status}`, json);
11826
+ return json;
11827
+ }
11817
11828
  /** ファイルを取る(添付)。誤りはJSONの本文から読む。 */
11818
11829
  async download(path) {
11819
11830
  let response;
@@ -12192,8 +12203,8 @@ async function runDaemon() {
12192
12203
  await sync();
12193
12204
  try {
12194
12205
  const { items } = await api.call("GET", "/decisions");
12195
- const open = items.some((d) => d.status === "pending" || d.status === "held");
12196
- if (open || Object.keys(journal).length) idleSince = Date.now();
12206
+ const open2 = items.some((d) => d.status === "pending" || d.status === "held");
12207
+ if (open2 || Object.keys(journal).length) idleSince = Date.now();
12197
12208
  else if (Date.now() - idleSince > IDLE_EXIT_MS) stop();
12198
12209
  } catch {
12199
12210
  idleSince = Date.now();
@@ -15826,10 +15837,53 @@ var StdioServerTransport = class {
15826
15837
 
15827
15838
  // src/mcp.ts
15828
15839
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
15829
- import { basename as basename2, join as join8 } from "node:path";
15840
+ import { basename as basename3, join as join8 } from "node:path";
15841
+
15842
+ // src/request-images.ts
15843
+ import { open } from "node:fs/promises";
15844
+ import { basename as basename2 } from "node:path";
15845
+ var MAX_FILE = 20 * 1024 * 1024;
15846
+ var MAX_TOTAL = 50 * 1024 * 1024;
15847
+ function imageType(b) {
15848
+ if (b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) return "image/png";
15849
+ if (b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255) return "image/jpeg";
15850
+ if (b.subarray(0, 4).toString() === "GIF8") return "image/gif";
15851
+ if (b.subarray(0, 4).toString() === "RIFF" && b.subarray(8, 12).toString() === "WEBP") return "image/webp";
15852
+ if (b.subarray(4, 8).toString() === "ftyp" && ["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1", "heif"].includes(b.subarray(8, 12).toString())) return "image/heic";
15853
+ throw new Error("\u7533\u8ACB\u306B\u4ED8\u3051\u3089\u308C\u308B\u306E\u306FJPEG\u30FBPNG\u30FBGIF\u30FBWebP\u30FBHEIC\u753B\u50CF\u3067\u3059\u3002");
15854
+ }
15855
+ async function uploadImagePaths(api, paths) {
15856
+ if (!Array.isArray(paths) || paths.length > 10 || paths.some((p) => typeof p !== "string" || !p.trim())) throw new Error("image_paths\u306F10\u4EF6\u307E\u3067\u306E\u753B\u50CF\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u914D\u5217\u3067\u3059\u3002");
15857
+ const images = [];
15858
+ let total = 0;
15859
+ for (const path of paths) {
15860
+ const file = await open(path, "r");
15861
+ try {
15862
+ const stat = await file.stat();
15863
+ if (!stat.isFile() || !stat.size || stat.size > MAX_FILE) throw new Error(`${basename2(path)}: \u7A7A\u3067\u306A\u3044\u901A\u5E38\u30D5\u30A1\u30A4\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF0820MiB\u307E\u3067\uFF09\u3002`);
15864
+ total += stat.size;
15865
+ if (total > MAX_TOTAL) throw new Error("\u7533\u8ACB\u753B\u50CF\u306F\u5408\u8A0850MiB\u307E\u3067\u3067\u3059\u3002");
15866
+ const data = Buffer.alloc(stat.size + 1);
15867
+ let n = 0;
15868
+ while (n < data.length) {
15869
+ const r = await file.read(data, n, data.length - n, n);
15870
+ if (!r.bytesRead) break;
15871
+ n += r.bytesRead;
15872
+ }
15873
+ if (n !== stat.size) throw new Error(`${basename2(path)}: \u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u753B\u50CF\u304C\u5909\u308F\u308A\u307E\u3057\u305F\u3002`);
15874
+ const bytes = data.subarray(0, n);
15875
+ images.push({ name: basename2(path), data: bytes, type: imageType(bytes) });
15876
+ } finally {
15877
+ await file.close();
15878
+ }
15879
+ }
15880
+ const ids = [];
15881
+ for (const image of images) ids.push((await api.uploadRequestImage(image.data, image.name, image.type)).id);
15882
+ return ids;
15883
+ }
15830
15884
 
15831
15885
  // src/version.ts
15832
- var VERSION = true ? "0.1.19" : "0.0.0-dev";
15886
+ var VERSION = true ? "0.1.20" : "0.0.0-dev";
15833
15887
  function osName() {
15834
15888
  return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
15835
15889
  }
@@ -15872,6 +15926,8 @@ var TOOLS = [
15872
15926
  deadline: { type: "string", description: "\u671F\u9650\uFF08ISO 8601\uFF09" },
15873
15927
  session_label: { type: "string", description: "\u3069\u306E\u4F5C\u696D\u306E\u8A71\u304B\uFF08\u4F8B: \u30EA\u30DD\u30B8\u30C8\u30EA\u540D / \u4F5C\u696D\u540D\uFF09\u3002\u7701\u7565\u3059\u308B\u3068\u4F5C\u696D\u30D5\u30A9\u30EB\u30C0\u540D" },
15874
15928
  distinct_reason: { type: "string", description: "\u540C\u3058\u4EF6\u540D\u306E\u672A\u6C7A\u304C\u3042\u308B\u306E\u306B\u5225\u4EF6\u3068\u3057\u3066\u51FA\u3059\u6642\u3060\u3051\u3001\u305D\u306E\u7406\u7531" },
15929
+ image_paths: { type: "array", items: { type: "string" }, maxItems: 10, description: "\u7533\u8ACB\u306B\u6DFB\u3048\u308B\u30ED\u30FC\u30AB\u30EB\u753B\u50CF\u30D1\u30B9\u300210\u4EF6/\u5358\u4F5320MiB/\u5408\u8A0850MiB\u307E\u3067\u3002\u30D1\u30B9\u81EA\u4F53\u306F\u4EBA\u3078\u9001\u3089\u306A\u3044" },
15930
+ request_attachment_ids: { type: "array", items: { type: "string" }, maxItems: 10 },
15875
15931
  check_token: { type: "string", description: "1\u56DE\u76EE\u306E request_decision \u3067\u8FD4\u3063\u305F\u78BA\u8A8D\u306E\u672D\u3002\u4E00\u89A7\u3092\u78BA\u304B\u3081\u3001\u76F4\u3059\u30FB\u53D6\u308A\u4E0B\u3052\u308B\u7533\u8ACB\u304C\u7121\u3044\u3068\u5224\u65AD\u3057\u305F\u6642\u306B\u4ED8\u3051\u308B" }
15876
15932
  },
15877
15933
  required: ["title", "options"],
@@ -15890,6 +15946,8 @@ var TOOLS = [
15890
15946
  changes: {
15891
15947
  type: "object",
15892
15948
  properties: {
15949
+ image_paths: { type: "array", items: { type: "string" }, maxItems: 10 },
15950
+ request_attachment_ids: { type: "array", items: { type: "string" }, maxItems: 10 },
15893
15951
  title: { type: "string" },
15894
15952
  context: { type: "string" },
15895
15953
  options: { type: "array", items: optionSchema, minItems: 2, maxItems: 6 },
@@ -16046,10 +16104,14 @@ ${JSON.stringify({ items })}`, { items });
16046
16104
  if (name === "request_decision") {
16047
16105
  const channelId = await channelFor(harness, clientName, request.params._meta);
16048
16106
  lastChannel = channelId;
16107
+ const { image_paths, ...rest } = args;
16108
+ if (image_paths !== void 0 && rest.request_attachment_ids !== void 0) throw new Error("image_paths\u3068request_attachment_ids\u306F\u7247\u65B9\u3060\u3051\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
16109
+ const request_attachment_ids = image_paths !== void 0 ? await uploadImagePaths(api, image_paths) : rest.request_attachment_ids;
16049
16110
  const body = {
16050
- ...args,
16111
+ ...rest,
16112
+ ...request_attachment_ids !== void 0 ? { request_attachment_ids } : {},
16051
16113
  client: CLIENT_OF[harness],
16052
- session_label: args.session_label || `${basename2(process.cwd())} (${CLIENT_OF[harness]})`,
16114
+ session_label: args.session_label || `${basename3(process.cwd())} (${CLIENT_OF[harness]})`,
16053
16115
  route: { channel_id: channelId, harness }
16054
16116
  };
16055
16117
  try {
@@ -16088,6 +16150,10 @@ ${JSON.stringify({ error: "duplicate_suspected", existing })}`, { error: "duplic
16088
16150
  }
16089
16151
  if (name === "amend_decision") {
16090
16152
  const { decision_id, ...rest } = args;
16153
+ const { image_paths, ...changes } = rest.changes ?? {};
16154
+ if (image_paths !== void 0 && changes.request_attachment_ids !== void 0) throw new Error("image_paths\u3068request_attachment_ids\u306F\u7247\u65B9\u3060\u3051\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
16155
+ if (image_paths !== void 0) changes.request_attachment_ids = await uploadImagePaths(api, image_paths);
16156
+ rest.changes = changes;
16091
16157
  const channelId = await channelFor(harness, clientName, request.params._meta);
16092
16158
  const decision = await api.call("POST", `/decisions/${encodeURIComponent(String(decision_id))}/amend`, { ...rest, route: { channel_id: channelId, harness } });
16093
16159
  lastChannel = channelId;
@@ -16122,7 +16188,7 @@ ${JSON.stringify(decision)}`, decision);
16122
16188
  if (name === "get_attachment") {
16123
16189
  const decisionId = String(args.decision_id);
16124
16190
  const decision = await api.call("GET", `/decisions/${encodeURIComponent(decisionId)}`);
16125
- const list = decision.answer?.attachments ?? [];
16191
+ const list = [...decision.request_attachments ?? [], ...decision.answer?.attachments ?? []];
16126
16192
  const index = list.findIndex((a) => a.id === args.attachment_id);
16127
16193
  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);
16128
16194
  const meta = list[index];
@@ -16140,7 +16206,7 @@ ${JSON.stringify(decision)}`, decision);
16140
16206
  const decision = await api.call("POST", "/setup-test", {
16141
16207
  client: CLIENT_OF[harness],
16142
16208
  os: osName(),
16143
- session_label: `\u63A5\u7D9A\u30C6\u30B9\u30C8 ${basename2(process.cwd())} (${CLIENT_OF[harness]})`,
16209
+ session_label: `\u63A5\u7D9A\u30C6\u30B9\u30C8 ${basename3(process.cwd())} (${CLIENT_OF[harness]})`,
16144
16210
  route: { channel_id: channelId, harness }
16145
16211
  });
16146
16212
  const message = `\u63A5\u7D9A\u30C6\u30B9\u30C8\u306E\u7533\u8ACB\u3092\u9001\u308A\u307E\u3057\u305F\uFF08${decision.decision_id}\uFF09\u3002\u5229\u7528\u8005\u306B\u30A2\u30D7\u30EA\u304BWeb\u7248\u3067\u7B54\u3048\u308B\u3088\u3046\u4F1D\u3048\u3066\u304F\u3060\u3055\u3044\u3002\u7B54\u3048\u304C\u3053\u306E\u4F1A\u8A71\u3078\u5C4A\u3044\u305F\u3089\u3001\u305D\u3053\u306B\u66F8\u304B\u308C\u305F\u78BA\u8A8D\u30B3\u30FC\u30C9\u3067 confirm_setup_test \u3092\u547C\u3093\u3067\u304F\u3060\u3055\u3044\u3002`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "approval-box",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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",