approval-box 0.1.9 → 0.1.11
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 +2 -2
- package/dist/cli.mjs +137 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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, 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
|
|
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`. If an instructions file is a symlink (for example, generated by a dotfiles repo), setup leaves it alone and asks you to add the paragraph to the source instead. Skip with `--no-instructions`.
|
|
18
|
+
AIが毎回読む全体の指示に「判断・承認・操作が要る時は request_decision でも申請する」という一節を足すかを聞きます。Cursor はファイルから読まないので、会話の始まりのhook(`~/.cursor/hooks.json` の sessionStart)で渡します。指示ファイルが別の所への参照(symlink、dotfilesの生成物など)の時は書かず、管理元の規範に足すよう案内します。足さない時は `--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.
|
package/dist/cli.mjs
CHANGED
|
@@ -4435,10 +4435,10 @@ var require_browser = __commonJS({
|
|
|
4435
4435
|
text2 = canvas;
|
|
4436
4436
|
canvas = void 0;
|
|
4437
4437
|
}
|
|
4438
|
-
return new Promise(function(
|
|
4438
|
+
return new Promise(function(resolve2, reject) {
|
|
4439
4439
|
try {
|
|
4440
4440
|
const data = QRCode2.create(text2, opts);
|
|
4441
|
-
|
|
4441
|
+
resolve2(renderFunc(data, canvas, opts));
|
|
4442
4442
|
} catch (e) {
|
|
4443
4443
|
reject(e);
|
|
4444
4444
|
}
|
|
@@ -4519,11 +4519,11 @@ var require_server = __commonJS({
|
|
|
4519
4519
|
}
|
|
4520
4520
|
function render(renderFunc, text2, params) {
|
|
4521
4521
|
if (!params.cb) {
|
|
4522
|
-
return new Promise(function(
|
|
4522
|
+
return new Promise(function(resolve2, reject) {
|
|
4523
4523
|
try {
|
|
4524
4524
|
const data = QRCode2.create(text2, params.opts);
|
|
4525
4525
|
return renderFunc(data, params.opts, function(err, data2) {
|
|
4526
|
-
return err ? reject(err) :
|
|
4526
|
+
return err ? reject(err) : resolve2(data2);
|
|
4527
4527
|
});
|
|
4528
4528
|
} catch (e) {
|
|
4529
4529
|
reject(e);
|
|
@@ -7538,7 +7538,7 @@ var require_compile = __commonJS({
|
|
|
7538
7538
|
const schOrFunc = root.refs[ref];
|
|
7539
7539
|
if (schOrFunc)
|
|
7540
7540
|
return schOrFunc;
|
|
7541
|
-
let _sch =
|
|
7541
|
+
let _sch = resolve2.call(this, root, ref);
|
|
7542
7542
|
if (_sch === void 0) {
|
|
7543
7543
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
7544
7544
|
const { schemaId } = this.opts;
|
|
@@ -7565,7 +7565,7 @@ var require_compile = __commonJS({
|
|
|
7565
7565
|
function sameSchemaEnv(s1, s2) {
|
|
7566
7566
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
7567
7567
|
}
|
|
7568
|
-
function
|
|
7568
|
+
function resolve2(root, ref) {
|
|
7569
7569
|
let sch;
|
|
7570
7570
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
7571
7571
|
ref = sch;
|
|
@@ -8395,7 +8395,7 @@ var require_fast_uri = __commonJS({
|
|
|
8395
8395
|
}
|
|
8396
8396
|
return uri;
|
|
8397
8397
|
}
|
|
8398
|
-
function
|
|
8398
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
8399
8399
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
8400
8400
|
const {
|
|
8401
8401
|
parsed: baseParsed,
|
|
@@ -8764,7 +8764,7 @@ var require_fast_uri = __commonJS({
|
|
|
8764
8764
|
var fastUri = {
|
|
8765
8765
|
SCHEMES,
|
|
8766
8766
|
normalize,
|
|
8767
|
-
resolve,
|
|
8767
|
+
resolve: resolve2,
|
|
8768
8768
|
resolveComponent,
|
|
8769
8769
|
equal,
|
|
8770
8770
|
serialize,
|
|
@@ -11757,7 +11757,7 @@ var require_dist = __commonJS({
|
|
|
11757
11757
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
11758
11758
|
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync5, rmSync as rmSync3 } from "node:fs";
|
|
11759
11759
|
import { hostname } from "node:os";
|
|
11760
|
-
import { join as
|
|
11760
|
+
import { join as join8 } from "node:path";
|
|
11761
11761
|
import { createInterface } from "node:readline/promises";
|
|
11762
11762
|
|
|
11763
11763
|
// src/api.ts
|
|
@@ -11948,11 +11948,23 @@ function daemonStatus() {
|
|
|
11948
11948
|
return { running: false };
|
|
11949
11949
|
}
|
|
11950
11950
|
}
|
|
11951
|
+
function versionOfEntry(entry) {
|
|
11952
|
+
const match = /[\\/]runtime[\\/](\d+)\.(\d+)\.(\d+)[\\/]/.exec(entry);
|
|
11953
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
11954
|
+
}
|
|
11955
|
+
function shouldReplaceDaemon(runningEntry, myEntry) {
|
|
11956
|
+
if (runningEntry === myEntry) return false;
|
|
11957
|
+
const running = runningEntry ? versionOfEntry(runningEntry) : null;
|
|
11958
|
+
const mine = versionOfEntry(myEntry);
|
|
11959
|
+
if (!running || !mine) return true;
|
|
11960
|
+
for (let i = 0; i < 3; i++) if (running[i] !== mine[i]) return running[i] < mine[i];
|
|
11961
|
+
return true;
|
|
11962
|
+
}
|
|
11951
11963
|
function ensureDaemon() {
|
|
11952
11964
|
try {
|
|
11953
11965
|
if (!readConfig()?.token) return;
|
|
11954
11966
|
const status2 = daemonStatus();
|
|
11955
|
-
if (status2.running && status2.entry
|
|
11967
|
+
if (status2.running && !shouldReplaceDaemon(status2.entry, runtimeEntry("cli"))) return;
|
|
11956
11968
|
if (status2.running && status2.pid) {
|
|
11957
11969
|
try {
|
|
11958
11970
|
process.kill(status2.pid);
|
|
@@ -11967,8 +11979,13 @@ function ensureDaemon() {
|
|
|
11967
11979
|
`);
|
|
11968
11980
|
}
|
|
11969
11981
|
}
|
|
11970
|
-
var sleep = (ms) => new Promise((
|
|
11982
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
11971
11983
|
async function runDaemon() {
|
|
11984
|
+
for (let i = 0; i < 50; i++) {
|
|
11985
|
+
const other = daemonStatus();
|
|
11986
|
+
if (!other.running || other.pid === process.pid) break;
|
|
11987
|
+
await sleep(200);
|
|
11988
|
+
}
|
|
11972
11989
|
const status2 = daemonStatus();
|
|
11973
11990
|
if (status2.running && status2.pid !== process.pid) return;
|
|
11974
11991
|
writeJsonFile(lockFile(), { pid: process.pid, entry: runtimeEntry("cli"), started_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -12071,8 +12088,16 @@ async function runDaemon() {
|
|
|
12071
12088
|
}
|
|
12072
12089
|
const controller = new AbortController();
|
|
12073
12090
|
let stopping = false;
|
|
12091
|
+
const releaseLock = () => {
|
|
12092
|
+
try {
|
|
12093
|
+
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
12094
|
+
if (lock.pid === process.pid) rmSync2(lockFile(), { force: true });
|
|
12095
|
+
} catch {
|
|
12096
|
+
}
|
|
12097
|
+
};
|
|
12074
12098
|
const stop = () => {
|
|
12075
12099
|
stopping = true;
|
|
12100
|
+
releaseLock();
|
|
12076
12101
|
controller.abort();
|
|
12077
12102
|
};
|
|
12078
12103
|
process.on("SIGTERM", stop);
|
|
@@ -12110,23 +12135,20 @@ async function runDaemon() {
|
|
|
12110
12135
|
}
|
|
12111
12136
|
}
|
|
12112
12137
|
}
|
|
12113
|
-
|
|
12114
|
-
const lock = JSON.parse(readFileSync3(lockFile(), "utf8"));
|
|
12115
|
-
if (lock.pid === process.pid) rmSync2(lockFile(), { force: true });
|
|
12116
|
-
} catch {
|
|
12117
|
-
}
|
|
12138
|
+
releaseLock();
|
|
12118
12139
|
process.exit(0);
|
|
12119
12140
|
}
|
|
12120
12141
|
|
|
12121
12142
|
// src/harness.ts
|
|
12122
12143
|
import { execFileSync } from "node:child_process";
|
|
12123
12144
|
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
12124
|
-
import { homedir } from "node:os";
|
|
12125
|
-
import { delimiter, dirname as dirname4, join as
|
|
12145
|
+
import { homedir as homedir2 } from "node:os";
|
|
12146
|
+
import { delimiter, dirname as dirname4, join as join6 } from "node:path";
|
|
12126
12147
|
|
|
12127
12148
|
// src/instructions.ts
|
|
12128
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12129
|
-
import {
|
|
12149
|
+
import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, readlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12150
|
+
import { homedir } from "node:os";
|
|
12151
|
+
import { dirname as dirname3, join as join5, resolve } from "node:path";
|
|
12130
12152
|
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";
|
|
12131
12153
|
var START = "<!-- approval-box:start -->";
|
|
12132
12154
|
var END = "<!-- approval-box:end -->";
|
|
@@ -12137,18 +12159,48 @@ ${INSTRUCTION_TEXT}
|
|
|
12137
12159
|
${END}
|
|
12138
12160
|
`;
|
|
12139
12161
|
var PATTERN = new RegExp(`\\n*${START}[\\s\\S]*?${END}\\n?`, "g");
|
|
12162
|
+
var SAME_MEANING = /Approval Box\s*の\s*`?request_decision`?\s*でも申請/;
|
|
12140
12163
|
function hasInstructions(file) {
|
|
12141
|
-
|
|
12164
|
+
if (!existsSync4(file)) return false;
|
|
12165
|
+
const text2 = readFileSync4(file, "utf8");
|
|
12166
|
+
if (text2.includes(START) || SAME_MEANING.test(text2)) return true;
|
|
12167
|
+
for (const [, ref] of text2.matchAll(/^@(\S+)\s*$/gm)) {
|
|
12168
|
+
const target = ref.startsWith("~/") ? resolve(homedir(), ref.slice(2)) : resolve(dirname3(file), ref);
|
|
12169
|
+
if (existsSync4(target) && SAME_MEANING.test(readFileSync4(target, "utf8"))) return true;
|
|
12170
|
+
}
|
|
12171
|
+
return false;
|
|
12172
|
+
}
|
|
12173
|
+
function hasInstructionsInDir(dir) {
|
|
12174
|
+
try {
|
|
12175
|
+
return readdirSync2(dir).some((name) => {
|
|
12176
|
+
try {
|
|
12177
|
+
return SAME_MEANING.test(readFileSync4(join5(dir, name), "utf8"));
|
|
12178
|
+
} catch {
|
|
12179
|
+
return false;
|
|
12180
|
+
}
|
|
12181
|
+
});
|
|
12182
|
+
} catch {
|
|
12183
|
+
return false;
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
function managedElsewhere(file) {
|
|
12187
|
+
try {
|
|
12188
|
+
return lstatSync(file).isSymbolicLink() ? readlinkSync(file) : null;
|
|
12189
|
+
} catch {
|
|
12190
|
+
return null;
|
|
12191
|
+
}
|
|
12142
12192
|
}
|
|
12143
12193
|
function writeInstructions(file) {
|
|
12194
|
+
if (managedElsewhere(file)) return "managed_elsewhere";
|
|
12144
12195
|
const current = existsSync4(file) ? readFileSync4(file, "utf8").replace(PATTERN, "\n").trimEnd() : "";
|
|
12145
12196
|
mkdirSync4(dirname3(file), { recursive: true, mode: 448 });
|
|
12146
12197
|
writeFileSync3(file, current ? `${current}
|
|
12147
12198
|
|
|
12148
12199
|
${BLOCK}` : BLOCK);
|
|
12200
|
+
return "written";
|
|
12149
12201
|
}
|
|
12150
12202
|
function removeInstructions(file) {
|
|
12151
|
-
if (!
|
|
12203
|
+
if (!existsSync4(file) || !readFileSync4(file, "utf8").includes(START)) return;
|
|
12152
12204
|
const rest = readFileSync4(file, "utf8").replace(PATTERN, "\n").trimEnd();
|
|
12153
12205
|
writeFileSync3(file, rest ? `${rest}
|
|
12154
12206
|
` : "");
|
|
@@ -12158,27 +12210,27 @@ function removeInstructions(file) {
|
|
|
12158
12210
|
var HARNESSES = ["claude", "codex", "cursor", "grok"];
|
|
12159
12211
|
var LABEL = { claude: "Claude Code", codex: "Codex", cursor: "Cursor", grok: "Grok" };
|
|
12160
12212
|
var CLIENT = { claude: "claude-code", codex: "codex", cursor: "cursor", grok: "grok" };
|
|
12161
|
-
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ??
|
|
12162
|
-
var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ?
|
|
12163
|
-
var cursorDir = () => process.env.CURSOR_HOME ??
|
|
12164
|
-
var codexHome = () => process.env.CODEX_HOME ??
|
|
12165
|
-
var grokDir = () => process.env.GROK_HOME ??
|
|
12213
|
+
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ?? join6(homedir2(), ".claude");
|
|
12214
|
+
var claudeJson = () => process.env.CLAUDE_CONFIG_DIR ? join6(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join6(homedir2(), ".claude.json");
|
|
12215
|
+
var cursorDir = () => process.env.CURSOR_HOME ?? join6(homedir2(), ".cursor");
|
|
12216
|
+
var codexHome = () => process.env.CODEX_HOME ?? join6(homedir2(), ".codex");
|
|
12217
|
+
var grokDir = () => process.env.GROK_HOME ?? join6(homedir2(), ".grok");
|
|
12166
12218
|
function filesOf(target) {
|
|
12167
|
-
if (target === "claude") return [claudeJson(),
|
|
12168
|
-
if (target === "cursor") return [
|
|
12169
|
-
if (target === "codex") return [
|
|
12170
|
-
return [
|
|
12219
|
+
if (target === "claude") return [claudeJson(), join6(claudeDir(), "settings.json")];
|
|
12220
|
+
if (target === "cursor") return [join6(cursorDir(), "mcp.json"), join6(cursorDir(), "hooks.json"), cursorCliConfig()];
|
|
12221
|
+
if (target === "codex") return [join6(codexHome(), "config.toml"), join6(codexHome(), "hooks.json")];
|
|
12222
|
+
return [join6(grokDir(), "config.toml")];
|
|
12171
12223
|
}
|
|
12172
12224
|
function instructionsFileOf(target) {
|
|
12173
|
-
if (target === "claude") return
|
|
12174
|
-
if (target === "codex") return
|
|
12175
|
-
if (target === "grok") return
|
|
12225
|
+
if (target === "claude") return join6(claudeDir(), "CLAUDE.md");
|
|
12226
|
+
if (target === "codex") return join6(codexHome(), "AGENTS.md");
|
|
12227
|
+
if (target === "grok") return join6(grokDir(), "AGENTS.md");
|
|
12176
12228
|
return null;
|
|
12177
12229
|
}
|
|
12178
12230
|
function onPath(command2) {
|
|
12179
12231
|
const names = process.platform === "win32" ? [`${command2}.exe`, `${command2}.cmd`, command2] : [command2];
|
|
12180
12232
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
12181
|
-
for (const name of names) if (dir && existsSync5(
|
|
12233
|
+
for (const name of names) if (dir && existsSync5(join6(dir, name))) return join6(dir, name);
|
|
12182
12234
|
}
|
|
12183
12235
|
return null;
|
|
12184
12236
|
}
|
|
@@ -12196,7 +12248,7 @@ function stableNode() {
|
|
|
12196
12248
|
}
|
|
12197
12249
|
}
|
|
12198
12250
|
var CURSOR_TOOLS = ["list_my_decisions", "request_decision", "amend_decision", "cancel_decision", "get_decision", "get_attachment", "setup_test", "confirm_setup_test"];
|
|
12199
|
-
var cursorCliConfig = () =>
|
|
12251
|
+
var cursorCliConfig = () => join6(cursorDir(), "cli-config.json");
|
|
12200
12252
|
function setCursorPermissions(enable) {
|
|
12201
12253
|
const file = cursorCliConfig();
|
|
12202
12254
|
if (!existsSync5(file) && !enable) return;
|
|
@@ -12210,7 +12262,7 @@ function setCursorPermissions(enable) {
|
|
|
12210
12262
|
}
|
|
12211
12263
|
var CURSOR_CONTEXT = "cursor-context";
|
|
12212
12264
|
function setCursorContext(enable) {
|
|
12213
|
-
const file =
|
|
12265
|
+
const file = join6(cursorDir(), "hooks.json");
|
|
12214
12266
|
if (!existsSync5(file) && !enable) return;
|
|
12215
12267
|
const target = existsSync5(file) ? realpathSync(file) : file;
|
|
12216
12268
|
backupOnce(target);
|
|
@@ -12223,7 +12275,7 @@ function setCursorContext(enable) {
|
|
|
12223
12275
|
else delete hooks.sessionStart;
|
|
12224
12276
|
writeJsonFile(target, { version: 1, ...current, hooks });
|
|
12225
12277
|
}
|
|
12226
|
-
var cursorContextRegistered = () => JSON.stringify(readJson(
|
|
12278
|
+
var cursorContextRegistered = () => JSON.stringify(readJson(join6(cursorDir(), "hooks.json")).hooks ?? {}).includes(` ${CURSOR_CONTEXT}"`);
|
|
12227
12279
|
var registration = () => ({ command: stableNode(), args: [runtimeEntry("cli"), "mcp"] });
|
|
12228
12280
|
function readJson(file) {
|
|
12229
12281
|
if (!existsSync5(file)) return {};
|
|
@@ -12246,29 +12298,34 @@ async function withCodexConfig(fn) {
|
|
|
12246
12298
|
}
|
|
12247
12299
|
async function register(target, options = {}) {
|
|
12248
12300
|
const file = instructionsFileOf(target);
|
|
12301
|
+
let note;
|
|
12249
12302
|
if (options.instructions && file) {
|
|
12250
12303
|
try {
|
|
12251
|
-
writeInstructions(file)
|
|
12304
|
+
if (writeInstructions(file) === "managed_elsewhere") note = `\u5168\u4F53\u306E\u6307\u793A\uFF08${file} \u2192 ${managedElsewhere(file)}\uFF09\u306F\u5225\u306E\u6240\u3067\u7BA1\u7406\u3055\u308C\u3066\u3044\u308B\u306E\u3067\u66F8\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u7BA1\u7406\u5143\u306E\u898F\u7BC4\u306B\u4E00\u7BC0\u3092\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044`;
|
|
12252
12305
|
} catch (error) {
|
|
12253
12306
|
return { target, status: "failed", detail: `${file}: ${error.message}` };
|
|
12254
12307
|
}
|
|
12255
12308
|
}
|
|
12309
|
+
const result = await registerTools(target, options);
|
|
12310
|
+
return note && result.status === "registered" ? { ...result, note } : result;
|
|
12311
|
+
}
|
|
12312
|
+
async function registerTools(target, options) {
|
|
12256
12313
|
const hook = (kind) => ({ command: stableNode(), script: runtimeEntry(kind) });
|
|
12257
12314
|
try {
|
|
12258
12315
|
if (target === "claude") {
|
|
12259
12316
|
setMcpJson(claudeJson(), { type: "stdio", ...registration() });
|
|
12260
|
-
mergeClaudeParentHooks(PROFILE,
|
|
12317
|
+
mergeClaudeParentHooks(PROFILE, join6(claudeDir(), "settings.json"), hook("claude"));
|
|
12261
12318
|
return { target, status: "registered" };
|
|
12262
12319
|
}
|
|
12263
12320
|
if (target === "cursor") {
|
|
12264
|
-
setMcpJson(
|
|
12265
|
-
mergeCursorParentHooks(PROFILE,
|
|
12321
|
+
setMcpJson(join6(cursorDir(), "mcp.json"), registration());
|
|
12322
|
+
mergeCursorParentHooks(PROFILE, join6(cursorDir(), "hooks.json"), hook("cursor"));
|
|
12266
12323
|
setCursorPermissions(true);
|
|
12267
12324
|
setCursorContext(!!options.instructions);
|
|
12268
12325
|
return { target, status: "registered" };
|
|
12269
12326
|
}
|
|
12270
12327
|
if (target === "codex") {
|
|
12271
|
-
const config =
|
|
12328
|
+
const config = join6(codexHome(), "config.toml");
|
|
12272
12329
|
backupOnce(config);
|
|
12273
12330
|
await withCodexConfig(async (request) => {
|
|
12274
12331
|
await request("config/batchWrite", { filePath: config, edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: { ...registration(), env_vars: ["CODEX_HOME"] }, mergeStrategy: "replace" }] });
|
|
@@ -12284,7 +12341,7 @@ async function register(target, options = {}) {
|
|
|
12284
12341
|
}
|
|
12285
12342
|
const grok = onPath("grok");
|
|
12286
12343
|
if (!grok) throw new Error("grok \u30B3\u30DE\u30F3\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
12287
|
-
backupOnce(
|
|
12344
|
+
backupOnce(join6(grokDir(), "config.toml"));
|
|
12288
12345
|
const reg = registration();
|
|
12289
12346
|
execFileSync(grok, ["mcp", "add", MCP_SERVER, "--scope", "user", reg.command, "--", ...reg.args], { stdio: "pipe" });
|
|
12290
12347
|
return { target, status: "registered" };
|
|
@@ -12298,16 +12355,16 @@ async function unregister(target) {
|
|
|
12298
12355
|
if (file) removeInstructions(file);
|
|
12299
12356
|
if (target === "claude") {
|
|
12300
12357
|
if (existsSync5(claudeJson())) setMcpJson(claudeJson(), null);
|
|
12301
|
-
removeClaudeParentHooks(PROFILE,
|
|
12358
|
+
removeClaudeParentHooks(PROFILE, join6(claudeDir(), "settings.json"));
|
|
12302
12359
|
} else if (target === "cursor") {
|
|
12303
|
-
if (existsSync5(
|
|
12304
|
-
removeCursorParentHooks(PROFILE,
|
|
12360
|
+
if (existsSync5(join6(cursorDir(), "mcp.json"))) setMcpJson(join6(cursorDir(), "mcp.json"), null);
|
|
12361
|
+
removeCursorParentHooks(PROFILE, join6(cursorDir(), "hooks.json"));
|
|
12305
12362
|
setCursorPermissions(false);
|
|
12306
12363
|
setCursorContext(false);
|
|
12307
12364
|
} else if (target === "codex") {
|
|
12308
|
-
if (existsSync5(
|
|
12365
|
+
if (existsSync5(join6(codexHome(), "config.toml"))) {
|
|
12309
12366
|
await withCodexConfig(async (request) => {
|
|
12310
|
-
await request("config/batchWrite", { filePath:
|
|
12367
|
+
await request("config/batchWrite", { filePath: join6(codexHome(), "config.toml"), edits: [{ keyPath: `mcp_servers.${MCP_SERVER}`, value: null, mergeStrategy: "replace" }] });
|
|
12311
12368
|
});
|
|
12312
12369
|
}
|
|
12313
12370
|
try {
|
|
@@ -12325,26 +12382,27 @@ async function unregister(target) {
|
|
|
12325
12382
|
}
|
|
12326
12383
|
function registered(target) {
|
|
12327
12384
|
const file = instructionsFileOf(target);
|
|
12328
|
-
|
|
12385
|
+
const instructions = target === "cursor" ? cursorContextRegistered() || hasInstructions(join6(cursorDir(), "AGENTS.md")) || hasInstructionsInDir(join6(cursorDir(), "rules")) : file ? hasInstructions(file) || target === "grok" && hasInstructionsInDir(join6(grokDir(), "rules")) : null;
|
|
12386
|
+
return { ...registeredTools(target), instructions };
|
|
12329
12387
|
}
|
|
12330
12388
|
function registeredTools(target) {
|
|
12331
12389
|
const entryOf = (value) => (value?.args ?? [])[0];
|
|
12332
12390
|
if (target === "claude") {
|
|
12333
12391
|
const entry = readJson(claudeJson()).mcpServers?.[MCP_SERVER];
|
|
12334
|
-
const settings = JSON.stringify(readJson(
|
|
12392
|
+
const settings = JSON.stringify(readJson(join6(claudeDir(), "settings.json")));
|
|
12335
12393
|
const e = entryOf(entry);
|
|
12336
12394
|
return { mcp: !!entry, hooks: settings.includes(PROFILE.hooks.claude), ...e ? { entry: e } : {} };
|
|
12337
12395
|
}
|
|
12338
12396
|
if (target === "cursor") {
|
|
12339
|
-
const entry = readJson(
|
|
12397
|
+
const entry = readJson(join6(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
|
|
12340
12398
|
const e = entryOf(entry);
|
|
12341
|
-
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(
|
|
12399
|
+
return { mcp: !!entry, hooks: cursorParentHooksRegistered(PROFILE, readJson(join6(cursorDir(), "hooks.json"))), ...e ? { entry: e } : {} };
|
|
12342
12400
|
}
|
|
12343
12401
|
if (target === "codex") {
|
|
12344
|
-
const toml2 = existsSync5(
|
|
12402
|
+
const toml2 = existsSync5(join6(codexHome(), "config.toml")) ? readFileSync5(join6(codexHome(), "config.toml"), "utf8") : "";
|
|
12345
12403
|
return { mcp: toml2.includes(`[mcp_servers.${MCP_SERVER}]`), hooks: null };
|
|
12346
12404
|
}
|
|
12347
|
-
const toml = existsSync5(
|
|
12405
|
+
const toml = existsSync5(join6(grokDir(), "config.toml")) ? readFileSync5(join6(grokDir(), "config.toml"), "utf8") : "";
|
|
12348
12406
|
return { mcp: toml.includes(MCP_SERVER), hooks: null };
|
|
12349
12407
|
}
|
|
12350
12408
|
|
|
@@ -14459,7 +14517,7 @@ var Protocol = class {
|
|
|
14459
14517
|
return;
|
|
14460
14518
|
}
|
|
14461
14519
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
14462
|
-
await new Promise((
|
|
14520
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
14463
14521
|
options?.signal?.throwIfAborted();
|
|
14464
14522
|
}
|
|
14465
14523
|
} catch (error) {
|
|
@@ -14476,7 +14534,7 @@ var Protocol = class {
|
|
|
14476
14534
|
*/
|
|
14477
14535
|
request(request, resultSchema, options) {
|
|
14478
14536
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
14479
|
-
return new Promise((
|
|
14537
|
+
return new Promise((resolve2, reject) => {
|
|
14480
14538
|
const earlyReject = (error) => {
|
|
14481
14539
|
reject(error);
|
|
14482
14540
|
};
|
|
@@ -14554,7 +14612,7 @@ var Protocol = class {
|
|
|
14554
14612
|
if (!parseResult.success) {
|
|
14555
14613
|
reject(parseResult.error);
|
|
14556
14614
|
} else {
|
|
14557
|
-
|
|
14615
|
+
resolve2(parseResult.data);
|
|
14558
14616
|
}
|
|
14559
14617
|
} catch (error) {
|
|
14560
14618
|
reject(error);
|
|
@@ -14815,12 +14873,12 @@ var Protocol = class {
|
|
|
14815
14873
|
}
|
|
14816
14874
|
} catch {
|
|
14817
14875
|
}
|
|
14818
|
-
return new Promise((
|
|
14876
|
+
return new Promise((resolve2, reject) => {
|
|
14819
14877
|
if (signal.aborted) {
|
|
14820
14878
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
14821
14879
|
return;
|
|
14822
14880
|
}
|
|
14823
|
-
const timeoutId = setTimeout(
|
|
14881
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
14824
14882
|
signal.addEventListener("abort", () => {
|
|
14825
14883
|
clearTimeout(timeoutId);
|
|
14826
14884
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -15696,12 +15754,12 @@ var StdioServerTransport = class {
|
|
|
15696
15754
|
this.onclose?.();
|
|
15697
15755
|
}
|
|
15698
15756
|
send(message) {
|
|
15699
|
-
return new Promise((
|
|
15757
|
+
return new Promise((resolve2) => {
|
|
15700
15758
|
const json = serializeMessage(message);
|
|
15701
15759
|
if (this._stdout.write(json)) {
|
|
15702
|
-
|
|
15760
|
+
resolve2();
|
|
15703
15761
|
} else {
|
|
15704
|
-
this._stdout.once("drain",
|
|
15762
|
+
this._stdout.once("drain", resolve2);
|
|
15705
15763
|
}
|
|
15706
15764
|
});
|
|
15707
15765
|
}
|
|
@@ -15709,10 +15767,10 @@ var StdioServerTransport = class {
|
|
|
15709
15767
|
|
|
15710
15768
|
// src/mcp.ts
|
|
15711
15769
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
15712
|
-
import { basename as basename2, join as
|
|
15770
|
+
import { basename as basename2, join as join7 } from "node:path";
|
|
15713
15771
|
|
|
15714
15772
|
// src/version.ts
|
|
15715
|
-
var VERSION = true ? "0.1.
|
|
15773
|
+
var VERSION = true ? "0.1.11" : "0.0.0-dev";
|
|
15716
15774
|
function osName() {
|
|
15717
15775
|
return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
|
|
15718
15776
|
}
|
|
@@ -15814,7 +15872,7 @@ var TOOLS = [
|
|
|
15814
15872
|
];
|
|
15815
15873
|
var VIEWABLE_IMAGES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
15816
15874
|
var MAX_INLINE_IMAGE = 5 * 1024 * 1024;
|
|
15817
|
-
var parentsFile = () =>
|
|
15875
|
+
var parentsFile = () => join7(stateRoot(), "parents.json");
|
|
15818
15876
|
var processChannels = /* @__PURE__ */ new Map();
|
|
15819
15877
|
var lastChannel;
|
|
15820
15878
|
function savedParents() {
|
|
@@ -16073,13 +16131,13 @@ async function ask(question) {
|
|
|
16073
16131
|
}
|
|
16074
16132
|
}
|
|
16075
16133
|
function installRuntime() {
|
|
16076
|
-
const target =
|
|
16134
|
+
const target = join8(home(), "runtime", VERSION);
|
|
16077
16135
|
if (runtimeDir() !== target) {
|
|
16078
16136
|
rmSync3(target, { recursive: true, force: true });
|
|
16079
16137
|
mkdirSync5(target, { recursive: true });
|
|
16080
16138
|
cpSync(runtimeDir(), target, { recursive: true });
|
|
16081
16139
|
}
|
|
16082
|
-
return
|
|
16140
|
+
return join8(target, "cli.mjs");
|
|
16083
16141
|
}
|
|
16084
16142
|
function selectedTargets() {
|
|
16085
16143
|
const only = option("only");
|
|
@@ -16110,7 +16168,7 @@ async function pair(server, targets) {
|
|
|
16110
16168
|
if (result.status === "claimed" && result.token) return { token: result.token, connection_id: result.connection_id };
|
|
16111
16169
|
if (result.status === "rejected") throw new Error("\u30A2\u30D7\u30EA\u3067\u300C\u5FC3\u5F53\u305F\u308A\u304C\u306A\u3044\u300D\u304C\u62BC\u3055\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6 npx approval-box setup \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
16112
16170
|
if (result.status === "expired" || result.status === "delivered") throw new Error("\u30DA\u30A2\u30EA\u30F3\u30B0\u306E\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6 npx approval-box setup \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
16113
|
-
await new Promise((
|
|
16171
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
16114
16172
|
}
|
|
16115
16173
|
}
|
|
16116
16174
|
async function waitForChecks(api, clients) {
|
|
@@ -16134,7 +16192,7 @@ async function waitForChecks(api, clients) {
|
|
|
16134
16192
|
out("\n\u3059\u3079\u3066\u306EAI\u3067\u78BA\u8A8D\u304C\u6E08\u307F\u307E\u3057\u305F\u3002Approval Box\u3092\u4F7F\u3048\u307E\u3059\u3002");
|
|
16135
16193
|
return;
|
|
16136
16194
|
}
|
|
16137
|
-
await new Promise((
|
|
16195
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
16138
16196
|
}
|
|
16139
16197
|
}
|
|
16140
16198
|
async function setup() {
|
|
@@ -16161,7 +16219,8 @@ async function setup() {
|
|
|
16161
16219
|
out(` \u300C${INSTRUCTION_TEXT}\u300D`);
|
|
16162
16220
|
for (const target of targets) {
|
|
16163
16221
|
const file = instructionsFileOf(target);
|
|
16164
|
-
|
|
16222
|
+
const link = file ? managedElsewhere(file) : null;
|
|
16223
|
+
out(` ${LABEL[target]}: ${!file ? "\u4F1A\u8A71\u306E\u59CB\u307E\u308A\u306Ehook\uFF08hooks.json \u306E sessionStart\uFF09\u3067\u6E21\u3057\u307E\u3059" : link ? `${file} \u306F ${link} \u3078\u306E\u53C2\u7167\u3067\u3001\u5225\u306E\u6240\u3067\u7BA1\u7406\u3055\u308C\u3066\u3044\u308B\u306E\u3067\u66F8\u304D\u307E\u305B\u3093\uFF08\u7BA1\u7406\u5143\u306E\u898F\u7BC4\u306B\u4E00\u7BC0\u3092\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044\uFF09` : file}`);
|
|
16165
16224
|
}
|
|
16166
16225
|
const instructions = !flag("no-instructions") && (flag("yes") || !/^n(o)?$/i.test(await ask("\u8DB3\u3057\u307E\u3059\u304B\uFF1F [Y/n] ")));
|
|
16167
16226
|
const cli = installRuntime();
|
|
@@ -16194,6 +16253,7 @@ async function registerTargets() {
|
|
|
16194
16253
|
const result = await register(target, { instructions: flag("instructions") });
|
|
16195
16254
|
const extra = result.steer ? `\uFF08${STEER_LABEL[result.steer.split(":")[0] ?? ""] ?? `\u4F5C\u696D\u4E2D\u306E\u5272\u308A\u8FBC\u307F: ${result.steer}`}\uFF09` : "";
|
|
16196
16255
|
out(` ${LABEL[target]}: ${result.status === "registered" ? "\u767B\u9332\u3057\u307E\u3057\u305F" : `\u5931\u6557 \u2014 ${result.detail}`}${extra}`);
|
|
16256
|
+
if (result.note) out(` \u30FB${result.note}`);
|
|
16197
16257
|
if (result.status === "failed") failed = true;
|
|
16198
16258
|
}
|
|
16199
16259
|
ensureDaemon();
|
|
@@ -16223,7 +16283,7 @@ async function uninstall() {
|
|
|
16223
16283
|
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}`);
|
|
16224
16284
|
}
|
|
16225
16285
|
}
|
|
16226
|
-
rmSync3(
|
|
16286
|
+
rmSync3(join8(home(), "config.json"), { force: true });
|
|
16227
16287
|
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");
|
|
16228
16288
|
}
|
|
16229
16289
|
async function status() {
|
|
@@ -16268,7 +16328,11 @@ async function doctor() {
|
|
|
16268
16328
|
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}`);
|
|
16269
16329
|
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}`);
|
|
16270
16330
|
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`);
|
|
16271
|
-
if (r.mcp && r.instructions === false)
|
|
16331
|
+
if (r.mcp && r.instructions === false) {
|
|
16332
|
+
const file = instructionsFileOf(target);
|
|
16333
|
+
const link = file ? managedElsewhere(file) : null;
|
|
16334
|
+
hints.push(link ? `${LABEL[target]}: \u5168\u4F53\u306E\u6307\u793A\uFF08${file} \u2192 ${link}\uFF09\u306BApproval Box\u306E\u4E00\u7BC0\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u5225\u306E\u6240\u3067\u7BA1\u7406\u3055\u308C\u3066\u3044\u308B\u306E\u3067\u3001\u7BA1\u7406\u5143\u306E\u898F\u7BC4\u306B\u300C${INSTRUCTION_TEXT}\u300D\u3092\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044` : `${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}`);
|
|
16335
|
+
}
|
|
16272
16336
|
}
|
|
16273
16337
|
for (const hint of hints) out(`\u30FB${hint}`);
|
|
16274
16338
|
if (!problems.length) out("\u554F\u984C\u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5C4A\u304B\u306A\u3044\u6642\u306F\u3001AI\u306B\u300CApproval Box\u306Esetup_test\u3092\u5B9F\u884C\u3057\u3066\u300D\u3068\u8A00\u3063\u3066\u3001\u3069\u3053\u3067\u6B62\u307E\u308B\u304B\u3092\u78BA\u304B\u3081\u3066\u304F\u3060\u3055\u3044\u3002");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "approval-box",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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",
|