approval-box 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # approval-box
2
2
 
3
- Approval Box connector. When an AI coding agent needs a human decision, it files a request to Approval Box instead of asking in chat. You answer from the Approval Box app or web, and the answer is delivered straight back into the same agent session — mid-turn if the agent is working, as a new turn if it is idle. The agent never polls.
3
+ Approval Box connector. When an AI coding agent needs a human decision, approval or action, it also files a request to Approval Box. You answer from the Approval Box app or web, and the answer is delivered straight back into the same agent session — mid-turn if the agent is working, as a new turn if it is idle. The agent never polls.
4
4
 
5
5
  Supported agents: Claude Code, Codex, Cursor, Grok — on macOS, Windows and Linux.
6
6
 
7
- AIが「人間の判断が要る」と思った時に、チャットで聞く代わりにApproval Boxへ申請します。アプリかWeb版で答えると、その答えが申請したAIの会話へそのまま届きます。
7
+ AIが人間の判断・承認・操作を求める時に、Approval Boxへも申請します。アプリかWeb版で答えると、その答えが申請したAIの会話へそのまま届きます。
8
8
 
9
9
  ## Setup / 使い方
10
10
 
@@ -14,8 +14,8 @@ npx -y approval-box@latest setup
14
14
 
15
15
  1. `setup` finds the agents on this machine and registers Approval Box (MCP server and hooks). It shows the files it will change and keeps a backup (`*.approval-box-backup`) before writing.
16
16
  PCにあるAIを見つけて登録します。書き換えるファイルを先に見せ、控えを残してから書き換えます。
17
- It also offers to add one short paragraph to each agent's global instructions (`~/.claude/CLAUDE.md`, `$CODEX_HOME/AGENTS.md`, `~/.grok/AGENTS.md`) so the agent also files decisions with `request_decision`. Cursor reads these from Settings → Rules → User Rules, so setup shows the text to paste. Skip with `--no-instructions`.
18
- AIが毎回読む全体の指示に「判断が要る時は request_decision でも申請する」という一節を足すかを聞きます。Cursor は Settings → Rules → User Rules に貼る一文を案内します。足さない時は `--no-instructions`。
17
+ It also offers to add one short paragraph to each agent's global instructions (`~/.claude/CLAUDE.md`, `$CODEX_HOME/AGENTS.md`, `~/.grok/AGENTS.md`) so the agent also files decisions, approvals and actions with `request_decision`. Cursor does not read a global file, so setup passes the same text through a `sessionStart` hook in `~/.cursor/hooks.json`. Skip with `--no-instructions`.
18
+ AIが毎回読む全体の指示に「判断・承認・操作が要る時は request_decision でも申請する」という一節を足すかを聞きます。Cursor はファイルから読まないので、会話の始まりのhook(`~/.cursor/hooks.json` の sessionStart)で渡します。足さない時は `--no-instructions`。
19
19
  2. Scan the QR code or enter the code in “Add device” in the app or web.
20
20
  画面のQRコードかコードを、アプリかWeb版の「端末を追加」で読み取ります。
21
21
  3. Tell your agent: `Approval Boxのsetup_testを実行して` (or “run Approval Box setup_test”). Answer the test request; when the answer reaches the agent, you are ready.
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runClaudeHookMain
5
- } from "./chunks/chunk-EZ7CZDGK.mjs";
5
+ } from "./chunks/chunk-LEWADASG.mjs";
6
6
 
7
7
  // src/hooks/claude.ts
8
8
  await runClaudeHookMain(PROFILE);
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runCodexHookMain
5
- } from "./chunks/chunk-EZ7CZDGK.mjs";
5
+ } from "./chunks/chunk-LEWADASG.mjs";
6
6
 
7
7
  // src/hooks/codex.ts
8
8
  await runCodexHookMain(PROFILE, process.argv[2]);
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runCursorHookMain
5
- } from "./chunks/chunk-EZ7CZDGK.mjs";
5
+ } from "./chunks/chunk-LEWADASG.mjs";
6
6
 
7
7
  // src/hooks/cursor.ts
8
8
  await runCursorHookMain(PROFILE);
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runChannelReceiveMain
5
- } from "./chunks/chunk-EZ7CZDGK.mjs";
5
+ } from "./chunks/chunk-LEWADASG.mjs";
6
6
 
7
7
  // src/hooks/receive.ts
8
8
  await runChannelReceiveMain(PROFILE);
@@ -21949,6 +21949,7 @@ export {
21949
21949
  waitProcessCommandLine,
21950
21950
  mergeClaudeParentHooks,
21951
21951
  removeClaudeParentHooks,
21952
+ cursorParentHookCommand,
21952
21953
  mergeCursorParentHooks,
21953
21954
  removeCursorParentHooks,
21954
21955
  openChannel,
package/dist/cli.mjs CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  codexParentFromRequest,
21
21
  configureCodexSteer,
22
22
  cursorHookRoot,
23
+ cursorParentHookCommand,
23
24
  cursorParentHooksRegistered,
24
25
  custom,
25
26
  discriminatedUnion,
@@ -51,7 +52,7 @@ import {
51
52
  verifyCursorParent,
52
53
  waitProcessCommandLine,
53
54
  withCodexReceiver
54
- } from "./chunks/chunk-EZ7CZDGK.mjs";
55
+ } from "./chunks/chunk-LEWADASG.mjs";
55
56
 
56
57
  // ../../node_modules/qrcode/lib/can-promise.js
57
58
  var require_can_promise = __commonJS({
@@ -12048,14 +12049,14 @@ async function runDaemon() {
12048
12049
 
12049
12050
  // src/harness.ts
12050
12051
  import { execFileSync } from "node:child_process";
12051
- import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
12052
+ import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4, realpathSync } from "node:fs";
12052
12053
  import { homedir } from "node:os";
12053
12054
  import { delimiter, dirname as dirname4, join as join4 } from "node:path";
12054
12055
 
12055
12056
  // src/instructions.ts
12056
12057
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
12057
12058
  import { dirname as dirname3 } from "node:path";
12058
- var INSTRUCTION_TEXT = "\u30E6\u30FC\u30B6\u30FC\u306E\u5224\u65AD\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";
12059
+ 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";
12059
12060
  var START = "<!-- approval-box:start -->";
12060
12061
  var END = "<!-- approval-box:end -->";
12061
12062
  var BLOCK = `${START}
@@ -12136,6 +12137,22 @@ function setCursorPermissions(enable) {
12136
12137
  if (enable) allow.push(...ours);
12137
12138
  writeJsonFile(file, { ...current, permissions: { ...permissions, allow, deny: permissions.deny ?? [] } });
12138
12139
  }
12140
+ var CURSOR_CONTEXT = "cursor-context";
12141
+ function setCursorContext(enable) {
12142
+ const file = join4(cursorDir(), "hooks.json");
12143
+ if (!existsSync4(file) && !enable) return;
12144
+ const target = existsSync4(file) ? realpathSync(file) : file;
12145
+ backupOnce(target);
12146
+ const current = existsSync4(target) ? readJson(target) : { version: 1 };
12147
+ const hooks = { ...current.hooks ?? {} };
12148
+ const ours = (entry) => typeof entry?.command === "string" && entry.command.includes(MCP_SERVER) && entry.command.endsWith(` ${CURSOR_CONTEXT}`);
12149
+ const rest = (hooks.sessionStart ?? []).filter((entry) => !ours(entry));
12150
+ if (enable) rest.push({ command: `${cursorParentHookCommand({ command: stableNode(), script: runtimeEntry("cli") })} ${CURSOR_CONTEXT}`, timeout: 10 });
12151
+ if (rest.length) hooks.sessionStart = rest;
12152
+ else delete hooks.sessionStart;
12153
+ writeJsonFile(target, { version: 1, ...current, hooks });
12154
+ }
12155
+ var cursorContextRegistered = () => JSON.stringify(readJson(join4(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
12139
12156
  var registration = () => ({ command: stableNode(), args: [runtimeEntry("cli"), "mcp"] });
12140
12157
  function readJson(file) {
12141
12158
  if (!existsSync4(file)) return {};
@@ -12176,6 +12193,7 @@ async function register(target, options = {}) {
12176
12193
  setMcpJson(join4(cursorDir(), "mcp.json"), registration());
12177
12194
  mergeCursorParentHooks(PROFILE, join4(cursorDir(), "hooks.json"), hook("cursor"));
12178
12195
  setCursorPermissions(true);
12196
+ setCursorContext(!!options.instructions);
12179
12197
  return { target, status: "registered" };
12180
12198
  }
12181
12199
  if (target === "codex") {
@@ -12214,6 +12232,7 @@ async function unregister(target) {
12214
12232
  if (existsSync4(join4(cursorDir(), "mcp.json"))) setMcpJson(join4(cursorDir(), "mcp.json"), null);
12215
12233
  removeCursorParentHooks(PROFILE, join4(cursorDir(), "hooks.json"));
12216
12234
  setCursorPermissions(false);
12235
+ setCursorContext(false);
12217
12236
  } else if (target === "codex") {
12218
12237
  if (existsSync4(join4(codexHome(), "config.toml"))) {
12219
12238
  await withCodexConfig(async (request) => {
@@ -12235,7 +12254,7 @@ async function unregister(target) {
12235
12254
  }
12236
12255
  function registered(target) {
12237
12256
  const file = instructionsFileOf(target);
12238
- return { ...registeredTools(target), instructions: file ? hasInstructions(file) : null };
12257
+ return { ...registeredTools(target), instructions: file ? hasInstructions(file) : target === "cursor" ? cursorContextRegistered() : null };
12239
12258
  }
12240
12259
  function registeredTools(target) {
12241
12260
  const entryOf = (value) => (value?.args ?? [])[0];
@@ -15622,7 +15641,7 @@ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:f
15622
15641
  import { basename as basename2, join as join5 } from "node:path";
15623
15642
 
15624
15643
  // src/version.ts
15625
- var VERSION = true ? "0.1.3" : "0.0.0-dev";
15644
+ var VERSION = true ? "0.1.5" : "0.0.0-dev";
15626
15645
  function osName() {
15627
15646
  return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
15628
15647
  }
@@ -15638,7 +15657,7 @@ function harnessOf(clientName) {
15638
15657
  return "other";
15639
15658
  }
15640
15659
  var INSTRUCTIONS = [
15641
- "Approval Box\u306F\u3001\u5229\u7528\u8005\uFF08\u4EBA\u9593\uFF09\u306E\u5224\u65AD\u304C\u8981\u308B\u6642\u306B\u4F7F\u3046\u3002\u5224\u65AD\u304C\u8981\u308B\u6642\u306F request_decision \u3067\u3082\u7533\u8ACB\u3059\u308B\u3002\u5229\u7528\u8005\u304C\u7533\u8ACB\u306B\u6C17\u3065\u304D\u3084\u3059\u3044\u306E\u3067\u3001\u5F37\u304F\u63A8\u5968\u3059\u308B\u3002\u7B54\u3048\u306F\u5229\u7528\u8005\u304C\u30B9\u30DE\u30DB\u3084Web\u3067\u51FA\u3057\u3001\u3053\u306E\u4F1A\u8A71\u3078\u81EA\u52D5\u3067\u5C4A\u304F\u3002",
15660
+ "Approval Box\u306F\u3001\u5229\u7528\u8005\uFF08\u4EBA\u9593\uFF09\u306E\u5224\u65AD\u30FB\u627F\u8A8D\u30FB\u64CD\u4F5C\u304C\u8981\u308B\u6642\u306B\u4F7F\u3046\u3002URL\u3092\u958B\u3044\u3066\u306E\u627F\u8A8D\u3084\u3001\u5229\u7528\u8005\u306E\u624B\u3067\u884C\u3046\u4F5C\u696D\u3092\u983C\u3080\u6642\u3082\u542B\u3080\u3002\u305D\u306E\u6642\u306F request_decision \u3067\u3082\u7533\u8ACB\u3059\u308B\u3002\u5229\u7528\u8005\u304C\u7533\u8ACB\u306B\u6C17\u3065\u304D\u3084\u3059\u3044\u306E\u3067\u3001\u5F37\u304F\u63A8\u5968\u3059\u308B\u3002\u7B54\u3048\u306F\u5229\u7528\u8005\u304C\u30B9\u30DE\u30DB\u3084Web\u3067\u51FA\u3057\u3001\u3053\u306E\u4F1A\u8A71\u3078\u81EA\u52D5\u3067\u5C4A\u304F\u3002",
15642
15661
  "\u7533\u8ACB\u306E\u524D\u306B\u5FC5\u305A list_my_decisions \u3067\u81EA\u5206\u306E\u7533\u8ACB\u3092\u898B\u308B\u3002\u540C\u3058\u4EF6\u304C\u672A\u6C7A\u30FB\u4FDD\u7559\u306B\u3042\u308C\u3070\u65B0\u3057\u304F\u51FA\u3055\u306A\u3044\u3002\u5185\u5BB9\u304C\u5909\u308F\u3063\u305F\u306A\u3089 amend_decision\u3001\u8981\u3089\u306A\u304F\u306A\u3063\u305F\u306A\u3089 cancel_decision\u3002\u7B54\u3048\u304C\u51FA\u3066\u3044\u308C\u3070\u3001\u305D\u306E\u7B54\u3048\u306B\u5F93\u3046\u3002\u307E\u3060\u7121\u3044\u4EF6\u3060\u3051\u3092 request_decision \u3067\u7533\u8ACB\u3059\u308B\u3002",
15643
15662
  "\u7533\u8ACB\u3057\u305F\u3089\u7B54\u3048\u3092\u5F85\u3063\u3066\u4F55\u5EA6\u3082\u547C\u3070\u306A\u3044\u3002\u7B54\u3048\u306F\u5C4A\u304F\u306E\u3067\u3001\u4ED6\u306E\u4F5C\u696D\u3092\u7D9A\u3051\u308B\u304B\u30BF\u30FC\u30F3\u3092\u7D42\u3048\u3066\u3088\u3044\u3002",
15644
15663
  "\u5229\u7528\u8005\u306B\u300CApproval Box\u306Esetup_test\u3092\u5B9F\u884C\u3057\u3066\u300D\u3068\u8A00\u308F\u308C\u305F\u3089 setup_test \u3092\u547C\u3076\u3002\u5C4A\u3044\u305F\u7B54\u3048\u306B\u66F8\u304B\u308C\u305F\u78BA\u8A8D\u30B3\u30FC\u30C9\u3067 confirm_setup_test \u3092\u547C\u3076\u3002"
@@ -16032,7 +16051,7 @@ async function setup() {
16032
16051
  out(` \u300C${INSTRUCTION_TEXT}\u300D`);
16033
16052
  for (const target of targets) {
16034
16053
  const file = instructionsFileOf(target);
16035
- out(` ${LABEL[target]}: ${file ?? "\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u306F\u8AAD\u307E\u306A\u3044\u305F\u3081\u3001\u767B\u9332\u5F8C\u306B\u8CBC\u308B\u5834\u6240\u3092\u6848\u5185\u3057\u307E\u3059"}`);
16054
+ out(` ${LABEL[target]}: ${file ?? "\u4F1A\u8A71\u306E\u59CB\u307E\u308A\u306Ehook\uFF08hooks.json \u306E sessionStart\uFF09\u3067\u6E21\u3057\u307E\u3059"}`);
16036
16055
  }
16037
16056
  const instructions = !flag("no-instructions") && (flag("yes") || !/^n(o)?$/i.test(await ask("\u8DB3\u3057\u307E\u3059\u304B\uFF1F [Y/n] ")));
16038
16057
  const cli = installRuntime();
@@ -16067,11 +16086,6 @@ async function registerTargets() {
16067
16086
  out(` ${LABEL[target]}: ${result.status === "registered" ? "\u767B\u9332\u3057\u307E\u3057\u305F" : `\u5931\u6557 \u2014 ${result.detail}`}${extra}`);
16068
16087
  if (result.status === "failed") failed = true;
16069
16088
  }
16070
- if (flag("instructions") && targets.includes("cursor")) {
16071
- out(`
16072
- Cursor \u306F\u5168\u4F53\u306E\u6307\u793A\u3092\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u8AAD\u307F\u307E\u305B\u3093\u3002Cursor \u306E Settings \u2192 Rules \u2192 User Rules \u306B\u3001\u6B21\u306E\u4E00\u6587\u3092\u8CBC\u3063\u3066\u304F\u3060\u3055\u3044\u3002
16073
- ${INSTRUCTION_TEXT}`);
16074
- }
16075
16089
  ensureDaemon();
16076
16090
  out("\n\u767B\u9332\u3057\u305FAI\u306F\u3001\u958B\u3044\u3066\u3044\u308B\u4F1A\u8A71\u3092\u9589\u3058\u3066\u958B\u304D\u76F4\u3059\u3068Approval Box\u3092\u4F7F\u3048\u307E\u3059\u3002");
16077
16091
  if (failed) process.exit(1);
@@ -16165,6 +16179,13 @@ function help() {
16165
16179
  npx approval-box doctor \u5C4A\u304B\u306A\u3044\u6642\u306E\u539F\u56E0\u3092\u8ABF\u3079\u308B
16166
16180
  npx approval-box uninstall \u767B\u9332\u3092\u5168\u90E8\u5916\u3057\u3001\u3053\u306E\u7AEF\u672B\u306E\u63A5\u7D9A\u3092\u89E3\u9664\u3059\u308B`);
16167
16181
  }
16182
+ async function cursorContext() {
16183
+ for await (const _ of process.stdin) {
16184
+ }
16185
+ out(JSON.stringify({ additional_context: `## Approval Box
16186
+
16187
+ ${INSTRUCTION_TEXT}` }));
16188
+ }
16168
16189
  var commands = {
16169
16190
  setup,
16170
16191
  test,
@@ -16173,6 +16194,7 @@ var commands = {
16173
16194
  uninstall,
16174
16195
  help,
16175
16196
  register: registerTargets,
16197
+ "cursor-context": cursorContext,
16176
16198
  mcp: runMcp,
16177
16199
  daemon: runDaemon,
16178
16200
  version: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "approval-box",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",