@kaydash9999/banker-plugins 0.20.2 → 0.21.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +203 -1
- package/PRIVACY.md +34 -4
- package/README.md +46 -15
- package/bin/banker.js +228 -51
- package/bin/lib/codex-hooks.mjs +249 -0
- package/bin/lib/telemetry-config.mjs +33 -16
- package/codex/manifest.json +14 -11
- package/codex/skills/graceful-pause/SKILL.md +30 -0
- package/codex/skills/progress/SKILL.md +42 -0
- package/codex/skills/progress/scripts/progress-plan.mjs +722 -0
- package/codex/skills/progress/scripts/progress.mjs +295 -0
- package/codex/transform-matrix.md +19 -14
- package/commands/setup.md +23 -0
- package/hooks/obsidize-hook.mjs +116 -4
- package/hooks/progress-plan.mjs +1 -1
- package/hooks/progress.mjs +143 -38
- package/hooks/register.mjs +13 -3
- package/hooks/telemetry-count-codex.mjs +222 -0
- package/hooks/update-checkin.mjs +15 -7
- package/hooks/update-fetch.mjs +15 -9
- package/hooks/update-notify.mjs +30 -17
- package/package.json +1 -1
- package/skills/3d-intro-build/SKILL.md +11 -5
- package/skills/3d-intro-build/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-build/references/curate.mjs +48 -14
- package/skills/3d-intro-build/references/preview-lib.mjs +51 -15
- package/skills/3d-intro-build/references/prompts.md +4 -4
- package/skills/3d-intro-build/references/serve.mjs +6 -1
- package/skills/3d-intro-build/references/verify-intro.mjs +16 -9
- package/skills/3d-intro-build/references/video-pool.mjs +250 -135
- package/skills/3d-intro-setup/SKILL.md +41 -10
- package/skills/3d-intro-setup/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-setup/references/video-pool.mjs +250 -135
- package/skills/anti-drm/SKILL.md +6 -3
- package/skills/anti-drm/scripts/anti-drm.mjs +49 -12
- package/skills/anti-drm/scripts/instruction-toggle.mjs +105 -20
- package/skills/append_wiki/SKILL.md +34 -8
- package/skills/audit-mock/SKILL.md +1 -1
- package/skills/audit-security/SKILL.md +64 -2
- package/skills/compact-wiki/SKILL.md +37 -6
- package/skills/deep-research/SKILL.md +11 -0
- package/skills/docs-setup/SKILL.md +37 -7
- package/skills/lfm/SKILL.md +148 -0
- package/skills/lineage/SKILL.md +25 -15
- package/skills/lineage/lineage.py +564 -301
- package/skills/motion-graphic-make/SKILL.md +1 -1
- package/skills/motion-graphic-make/references/curate.mjs +48 -14
- package/skills/motion-graphic-make/references/preview-lib.mjs +51 -15
- package/skills/motion-graphic-make/references/serve.mjs +6 -1
- package/skills/motion-graphic-setup/SKILL.md +58 -13
- package/skills/obsidizer/SKILL.md +30 -11
- package/skills/obsidizer/obsidize.mjs +50 -12
- package/skills/omc-patch/SKILL.md +86 -23
- package/skills/omc-patch/scripts/omc-patch.mjs +460 -116
- package/skills/payload-mon/SKILL.md +6 -2
- package/skills/payload-mon/scripts/payload-mon.mjs +88 -12
- package/skills/payload-mon/scripts/payload-size.mjs +4 -1
- package/skills/pdf-vision-extract/SKILL.md +11 -6
- package/skills/ralph-qa/SKILL.md +125 -151
- package/skills/ralph-qa/references/gemini-seat.mjs +67 -19
- package/skills/ralph-qa/references/payload-scan.mjs +22 -7
- package/skills/ralph-qa/references/seat-payload.mjs +646 -0
- package/skills/ralph-qa/references/seat-run.mjs +956 -0
- package/skills/ralph-qa/references/seat-spawn.mjs +205 -0
- package/skills/ralph-qa/references/verifier-probe.mjs +57 -16
- package/skills/rfp-author/SKILL.md +5 -3
- package/skills/rfp-author/references/vendor-rfp-structure.md +1 -1
- package/skills/setup-bypass-permissions/SKILL.md +27 -9
- package/skills/setup-bypass-permissions/scripts/codex-bypass.mjs +135 -0
- package/skills/setup-bypass-permissions/scripts/codex-config.mjs +210 -0
- package/skills/setup-harness-factory/SKILL.md +17 -1
- package/skills/setup-insane-search/SKILL.md +16 -0
- package/skills/setup-java/SKILL.md +20 -1
- package/skills/setup-lsp/SKILL.md +56 -13
- package/skills/setup-mcp/SKILL.md +73 -4
- package/skills/setup-node/SKILL.md +31 -6
- package/skills/setup-omc/SKILL.md +24 -0
- package/skills/setup-omc-hud/SKILL.md +42 -9
- package/skills/setup-omc-hud/assets/apply-hud.mjs +242 -27
- package/skills/setup-omc-hud/assets/codex-config.mjs +210 -0
- package/skills/setup-omc-hud/assets/codex-status-line.mjs +90 -0
- package/skills/setup-omc-hud/assets/omc-hud-custom.mjs +81 -14
- package/skills/setup-omc-hud/scripts/claude-update-last.mjs +5 -3
- package/skills/setup-playwright/SKILL.md +45 -2
- package/skills/setup-pwsh/SKILL.md +19 -0
- package/skills/setup-python/SKILL.md +45 -7
- package/skills/setup-sandbox/SKILL.md +30 -5
- package/skills/setup-stitch/SKILL.md +22 -0
- package/skills/setup-tmux/SKILL.md +24 -4
- package/skills/smart-compact/SKILL.md +58 -12
- package/skills/smart-compact/scripts/codex-gate.mjs +384 -0
- package/skills/tone-compact/SKILL.md +2 -1
- package/skills/tone-compact/scripts/instruction-toggle.mjs +105 -20
- package/skills/ultra-chain/SKILL.md +12 -7
- package/skills/ultra-chain/references/preview-lib.mjs +51 -15
- package/skills/ultra-chain/references/serve.mjs +6 -1
- package/skills/ultra-interview/SKILL.md +14 -2
- package/skills/update-banker/SKILL.md +19 -3
- package/skills/vertical-pptx/SKILL.md +6 -3
- package/skills/vertical-pptx-setup/SKILL.md +76 -20
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* seat-spawn: how the ralph-qa runner (seat-run.mjs) finds a seat CLI and runs it inside its
|
|
3
|
+
* time limit, with every process the seat started ended when the run returns.
|
|
4
|
+
*
|
|
5
|
+
* which(name) the CLI on PATH, from absolute PATH folders only. libuv on Windows looks in the
|
|
6
|
+
* child's working folder before PATH for a bare name, so the runner always starts
|
|
7
|
+
* a CLI by the absolute path this gives. Windows tries .exe, then .cmd and .bat
|
|
8
|
+
* in each folder, never an extensionless file.
|
|
9
|
+
* cmdLine(exe, args) the cmd.exe line for a .cmd or .bat seat (Node refuses to start those
|
|
10
|
+
* without a shell since CVE-2024-27980). Every word is quoted by the MSVC rules;
|
|
11
|
+
* a word holding what cmd.exe reads itself (% ! ^ & | < >, a line break, NUL,
|
|
12
|
+
* or a parenthesis beside a quote, which cmd's quote toggling over \" can bare)
|
|
13
|
+
* makes it null: the seat does not start.
|
|
14
|
+
* runLimited(opts) starts the seat with stdin, stdout and stderr on files and resolves with
|
|
15
|
+
* { exit, signal, timedOut, stopped, secs, error, leftover }. On POSIX the seat
|
|
16
|
+
* gets its own process group: the limit sends the group TERM and, after the grace,
|
|
17
|
+
* KILL; a seat that exits on its own has its group sent KILL at once, so nothing it
|
|
18
|
+
* left (a late writer, an MCP server) outlives the run. On Windows the limit ends
|
|
19
|
+
* the tree with System32's taskkill /T /F; a process left after the seat exits on
|
|
20
|
+
* its own is not reached (Windows keeps no group).
|
|
21
|
+
* stopAll() ends every seat still running (TERM, then KILL 2 s later; taskkill on Windows).
|
|
22
|
+
*
|
|
23
|
+
* No shell and no timeout program: the limit is a timer here, so Git Bash's or System32's
|
|
24
|
+
* timeout never comes into it.
|
|
25
|
+
*/
|
|
26
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
27
|
+
import { accessSync, closeSync, constants, openSync, statSync } from "node:fs";
|
|
28
|
+
import { posix, win32 } from "node:path";
|
|
29
|
+
|
|
30
|
+
const running = new Set();
|
|
31
|
+
const STOP_GRACE_MS = 2000;
|
|
32
|
+
// How long a run waits for a seat to go after its KILL before it gives up on it (`leftover`).
|
|
33
|
+
const BACKSTOP_MS = 10000;
|
|
34
|
+
|
|
35
|
+
const system32 = (file) => win32.join(process.env.SystemRoot || "C:\\Windows", "System32", file);
|
|
36
|
+
|
|
37
|
+
function pathOf(env, platform) {
|
|
38
|
+
if (platform !== "win32") return env.PATH || "";
|
|
39
|
+
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH");
|
|
40
|
+
return key ? env[key] || "" : "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const runnable = (platform) => (file) => {
|
|
44
|
+
try {
|
|
45
|
+
if (!statSync(file).isFile()) return false;
|
|
46
|
+
if (platform !== "win32") accessSync(file, constants.X_OK);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function which(name, opts = {}) {
|
|
54
|
+
const platform = opts.platform || process.platform;
|
|
55
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
56
|
+
const exts = platform === "win32" ? [".exe", ".cmd", ".bat"] : [""];
|
|
57
|
+
const isFile = opts.isFile || runnable(platform);
|
|
58
|
+
const dirs = pathOf(opts.env || process.env, platform).split(paths.delimiter).filter((dir) => dir && paths.isAbsolute(dir));
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
for (const ext of exts) {
|
|
61
|
+
const file = paths.join(dir, name + ext);
|
|
62
|
+
if (isFile(file)) return file;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const CMD_META = /[%!^&|<>\r\n\0]/;
|
|
69
|
+
const cmdSafe = (w) => !CMD_META.test(w) && !(/[()]/.test(w) && w.includes('"'));
|
|
70
|
+
const quoteWord = (w) => `"${w.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, "$1$1")}"`;
|
|
71
|
+
|
|
72
|
+
export function cmdLine(exe, args) {
|
|
73
|
+
const words = [exe, ...args].map(String);
|
|
74
|
+
return words.every(cmdSafe) ? words.map(quoteWord).join(" ") : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The files the seat reads and writes, opened for it; "ignore" is passed through.
|
|
78
|
+
function openStdio({ stdin, stdout, stderr }) {
|
|
79
|
+
const fds = [];
|
|
80
|
+
const open = (path, flags) => {
|
|
81
|
+
if (path === undefined || path === "ignore") return "ignore";
|
|
82
|
+
const fd = openSync(path, flags, 0o600);
|
|
83
|
+
fds.push(fd);
|
|
84
|
+
return fd;
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
return { stdio: [open(stdin, "r"), open(stdout, "w"), open(stderr, "w")], fds };
|
|
88
|
+
} catch (e) {
|
|
89
|
+
for (const fd of fds) closeSync(fd);
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The child process for the seat: a .cmd or .bat through System32's cmd.exe, anything else as is.
|
|
95
|
+
function launch({ exe, args, cwd, env, platform }, stdio) {
|
|
96
|
+
const viaCmd = platform === "win32" && /\.(cmd|bat)$/i.test(exe);
|
|
97
|
+
if (!viaCmd) return spawn(exe, args, { cwd, env, stdio, windowsHide: true, detached: platform !== "win32" });
|
|
98
|
+
const line = cmdLine(exe, args);
|
|
99
|
+
if (line === null) throw Object.assign(new Error("a word cmd.exe would read"), { code: "unsafe-word" });
|
|
100
|
+
return spawn(system32("cmd.exe"), ["/d", "/v:off", "/s", "/c", `"${line}"`],
|
|
101
|
+
{ cwd, env, stdio, windowsHide: true, windowsVerbatimArguments: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const signalGroup = (pid, sig) => {
|
|
105
|
+
try {
|
|
106
|
+
process.kill(-pid, sig);
|
|
107
|
+
} catch {
|
|
108
|
+
/* the group is gone */
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const taskkill = (pid) => spawnSync(system32("taskkill.exe"), ["/T", "/F", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
112
|
+
|
|
113
|
+
// Ends a seat: TERM to its group and KILL after `grace` (POSIX), or taskkill on its tree (Windows).
|
|
114
|
+
function terminate(seat, why, grace) {
|
|
115
|
+
if (seat.ending) return;
|
|
116
|
+
seat.ending = why;
|
|
117
|
+
if (seat.platform === "win32") {
|
|
118
|
+
taskkill(seat.child.pid);
|
|
119
|
+
seat.backstop = setTimeout(() => seat.finish(true), BACKSTOP_MS);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
signalGroup(seat.child.pid, "SIGTERM");
|
|
123
|
+
seat.killTimer = setTimeout(() => {
|
|
124
|
+
seat.killed = true;
|
|
125
|
+
signalGroup(seat.child.pid, "SIGKILL");
|
|
126
|
+
if (seat.exited) seat.finish(false);
|
|
127
|
+
else seat.backstop = setTimeout(() => seat.finish(true), BACKSTOP_MS);
|
|
128
|
+
}, grace);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The seat's own exit: on POSIX its group is sent KILL now, or when the grace ends if it is being
|
|
132
|
+
// ended, so nothing it left keeps running.
|
|
133
|
+
function onExit(seat, code, signal) {
|
|
134
|
+
if (seat.child.pid === undefined) return undefined; // a seat that never started: its error ends the run
|
|
135
|
+
seat.exited = { code, signal };
|
|
136
|
+
clearTimeout(seat.limitTimer);
|
|
137
|
+
if (seat.platform === "win32") return seat.finish(false);
|
|
138
|
+
if (seat.ending && !seat.killed) return undefined;
|
|
139
|
+
signalGroup(seat.child.pid, "SIGKILL");
|
|
140
|
+
return seat.finish(false);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function watch(seat, opts) {
|
|
144
|
+
const { child } = seat;
|
|
145
|
+
const grace = opts.graceMs ?? Math.min(10000, opts.limitMs / 2);
|
|
146
|
+
seat.limitTimer = setTimeout(() => terminate(seat, "limit", grace), opts.limitMs);
|
|
147
|
+
child.on("exit", (code, signal) => onExit(seat, code, signal));
|
|
148
|
+
child.on("error", (e) => {
|
|
149
|
+
if (child.pid === undefined) seat.finish(false, e.code || "spawn");
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function result(seat, t0, leftover, error) {
|
|
154
|
+
const { code = null, signal = null } = seat.exited || {};
|
|
155
|
+
return {
|
|
156
|
+
exit: error ? null : code, signal: error ? null : signal, timedOut: seat.ending === "limit", stopped: seat.ending === "stop",
|
|
157
|
+
secs: Math.round((Date.now() - t0) / 100) / 10, error: error || null, leftover,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function runLimited(opts) {
|
|
162
|
+
const t0 = Date.now();
|
|
163
|
+
const platform = opts.platform || process.platform;
|
|
164
|
+
return new Promise((done) => {
|
|
165
|
+
let io;
|
|
166
|
+
try {
|
|
167
|
+
if (!statSync(opts.exe).isFile()) throw Object.assign(new Error("not a file"), { code: "ENOENT" });
|
|
168
|
+
io = openStdio(opts);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
done(result({}, t0, false, e.code || "spawn"));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const seat = { platform };
|
|
174
|
+
seat.finish = (leftover, error) => {
|
|
175
|
+
if (seat.done) return;
|
|
176
|
+
seat.done = true;
|
|
177
|
+
for (const t of [seat.limitTimer, seat.killTimer, seat.backstop]) clearTimeout(t);
|
|
178
|
+
running.delete(seat);
|
|
179
|
+
done(result(seat, t0, leftover, error));
|
|
180
|
+
};
|
|
181
|
+
try {
|
|
182
|
+
seat.child = launch({ ...opts, args: opts.args || [], platform }, io.stdio);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
seat.finish(false, e.code || "spawn");
|
|
185
|
+
return;
|
|
186
|
+
} finally {
|
|
187
|
+
for (const fd of io.fds) closeSync(fd);
|
|
188
|
+
}
|
|
189
|
+
running.add(seat);
|
|
190
|
+
watch(seat, opts);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function stopAll() {
|
|
195
|
+
const seats = [...running];
|
|
196
|
+
const ends = seats.map((seat) => new Promise((resolve) => {
|
|
197
|
+
const finish = seat.finish;
|
|
198
|
+
seat.finish = (...a) => {
|
|
199
|
+
finish(...a);
|
|
200
|
+
resolve();
|
|
201
|
+
};
|
|
202
|
+
}));
|
|
203
|
+
for (const seat of seats) terminate(seat, "stop", STOP_GRACE_MS);
|
|
204
|
+
return Promise.all(ends);
|
|
205
|
+
}
|
|
@@ -205,6 +205,13 @@ const STRING_VALUE = /^(?:"((?:[^"\\]|\\.)*)"|'([^']*)')\s*(?:#.*)?$/s;
|
|
|
205
205
|
|
|
206
206
|
// A dotted key's segments, quotes taken off (escapes kept as written).
|
|
207
207
|
const keyPath = (text) => [...text.matchAll(SEG_RE)].map((m) => m[1] ?? m[2] ?? m[3]);
|
|
208
|
+
// True when a "basic" key segment holds an escape: codex reads the key the escape spells
|
|
209
|
+
// ("sandbox\u005Fmode" is sandbox_mode), and the probe keeps it as written.
|
|
210
|
+
const escapedKey = (text) => [...text.matchAll(SEG_RE)].some((m) => m[1]?.includes("\\"));
|
|
211
|
+
// Such a key leaves the read not whole: what codex reads there is not what the probe placed.
|
|
212
|
+
function markEscape(state, keyText) {
|
|
213
|
+
if (escapedKey(keyText)) state.unplaced = true;
|
|
214
|
+
}
|
|
208
215
|
|
|
209
216
|
// The walk's state: where it is (open string, nesting depth, table, array being gathered) and
|
|
210
217
|
// what it found so far.
|
|
@@ -305,10 +312,14 @@ function assignment(state, path, value) {
|
|
|
305
312
|
if (str) state.values[path[0]] = str[1] ?? str[2];
|
|
306
313
|
}
|
|
307
314
|
|
|
308
|
-
// A table header: the table later keys go in, and the MCP server a [mcp_servers.<name>...] names.
|
|
315
|
+
// A table header: the table later keys go in, and the MCP server a [mcp_servers.<name>...] names. An array of tables
|
|
316
|
+
// under mcp_servers ([[mcp_servers.<name>]], quoted or not) is a server list the probe cannot read: codex refuses it
|
|
317
|
+
// ("invalid type: sequence", 0.144.5), so a seat on it would fail later for no reason the probe gave.
|
|
309
318
|
function enterTable(state, header) {
|
|
310
|
-
|
|
311
|
-
state.
|
|
319
|
+
const path = keyPath(header[2]);
|
|
320
|
+
state.roots.add(path[0]);
|
|
321
|
+
state.table = header[1] ? ["[[]]"] : path;
|
|
322
|
+
if (header[1] && path[0] === "mcp_servers") state.mcpUnread = true;
|
|
312
323
|
if (state.table[0] === "mcp_servers" && state.table.length > 1) state.mcpServers.add(state.table[1]);
|
|
313
324
|
}
|
|
314
325
|
|
|
@@ -324,9 +335,15 @@ function tomlLine(state, line) {
|
|
|
324
335
|
return;
|
|
325
336
|
}
|
|
326
337
|
const header = HEADER_RE.exec(line);
|
|
327
|
-
if (header)
|
|
338
|
+
if (header) {
|
|
339
|
+
markEscape(state, header[2]);
|
|
340
|
+
return enterTable(state, header);
|
|
341
|
+
}
|
|
328
342
|
const assign = ASSIGN_RE.exec(line);
|
|
329
|
-
if (assign)
|
|
343
|
+
if (assign) {
|
|
344
|
+
markEscape(state, assign[1]);
|
|
345
|
+
return assignment(state, keyPath(assign[1]), assign[2]);
|
|
346
|
+
}
|
|
330
347
|
if (!/^\s*(?:#|$)/.test(line)) state.unplaced = true;
|
|
331
348
|
}
|
|
332
349
|
|
|
@@ -361,13 +378,25 @@ const rawMcpNames = (lines) => lines.filter((l) => !/^\s*#/.test(l))
|
|
|
361
378
|
// defined (table headers, under [mcp_servers], dotted keys), whether servers were written in a
|
|
362
379
|
// form it does not list or that the text names beyond what it listed (`mcpUnread`), whether any
|
|
363
380
|
// table sets a model (`modelKeys`), whether a multi-line string, array or inline table was left
|
|
364
|
-
// open or a line could not be placed (`broken`
|
|
381
|
+
// open or a line could not be placed (`broken`, with `brokenAt`: the first line it could not place
|
|
382
|
+
// or the line that opened what is still open), and the strings of each top-level array
|
|
365
383
|
// (`arrays`). Enough for those keys; not a TOML parser.
|
|
366
384
|
export function readToml(text) {
|
|
367
385
|
const state = tomlState();
|
|
368
386
|
const lines = String(text ?? "").split(/\r?\n/);
|
|
369
|
-
|
|
387
|
+
const isOpen = () => state.open !== null || state.depth > 0;
|
|
388
|
+
// the first line it could not place, and the line that opened what is still open at the end
|
|
389
|
+
let badAt = 0;
|
|
390
|
+
let openAt = 0;
|
|
391
|
+
lines.forEach((line, i) => {
|
|
392
|
+
const wasOpen = isOpen();
|
|
393
|
+
const wasBad = state.unplaced;
|
|
394
|
+
tomlLine(state, line);
|
|
395
|
+
if (!wasBad && state.unplaced) badAt = i + 1;
|
|
396
|
+
if (!wasOpen && isOpen()) openAt = i + 1;
|
|
397
|
+
});
|
|
370
398
|
const unlisted = rawMcpNames(lines).filter((n) => !state.mcpServers.has(n));
|
|
399
|
+
const broken = isOpen() || state.unplaced;
|
|
371
400
|
return {
|
|
372
401
|
values: state.values,
|
|
373
402
|
roots: [...state.roots],
|
|
@@ -375,7 +404,8 @@ export function readToml(text) {
|
|
|
375
404
|
mcpServers: [...state.mcpServers],
|
|
376
405
|
mcpUnread: state.mcpUnread || unlisted.length > 0,
|
|
377
406
|
modelKeys: state.modelKeys,
|
|
378
|
-
broken
|
|
407
|
+
broken,
|
|
408
|
+
...(broken ? { brokenAt: Math.min(...[badAt, isOpen() ? openAt : 0].filter(Boolean)) } : {}),
|
|
379
409
|
};
|
|
380
410
|
}
|
|
381
411
|
|
|
@@ -491,7 +521,8 @@ function codexConfigFiles(world) {
|
|
|
491
521
|
|
|
492
522
|
// Those layers as one: the first layer with a value wins, MCP servers add up.
|
|
493
523
|
function codexConfig(world) {
|
|
494
|
-
const
|
|
524
|
+
const files = codexConfigFiles(world);
|
|
525
|
+
const layers = files.map((f) => readToml(world.readFile(f)));
|
|
495
526
|
const pick = (key) => layers.map((l) => l.values[key]).find((v) => v !== undefined);
|
|
496
527
|
return {
|
|
497
528
|
cfg: { model: pick("model"), model_provider: pick("model_provider"), profile: pick("profile") },
|
|
@@ -499,6 +530,7 @@ function codexConfig(world) {
|
|
|
499
530
|
mcpUnread: layers.some((l) => l.mcpUnread),
|
|
500
531
|
modelKeys: layers.some((l) => l.modelKeys),
|
|
501
532
|
broken: layers.some((l) => l.broken),
|
|
533
|
+
brokenAt: layers.map((l, k) => (l.broken ? `${files[k]} line ${l.brokenAt}` : null)).filter(Boolean),
|
|
502
534
|
};
|
|
503
535
|
}
|
|
504
536
|
|
|
@@ -512,8 +544,8 @@ function mcpBlockedSeat(seat, conf, notes) {
|
|
|
512
544
|
const bad = conf.mcpServers.filter((n) => !MCP_NAME.test(n)).length;
|
|
513
545
|
if (!bad && !conf.mcpUnread && !conf.broken) return null;
|
|
514
546
|
if (bad) notes.push(`${bad} MCP server name(s) hold characters \`-c mcp_servers.<name>.enabled=false\` cannot address: rename them to letters, digits, _ and -`);
|
|
515
|
-
if (conf.mcpUnread) notes.push("the probe could not list every MCP server the codex config defines (one inline mcp_servers table, or a name its walk did not place): write each server as its own [mcp_servers.<name>] table");
|
|
516
|
-
if (conf.broken) notes.push(
|
|
547
|
+
if (conf.mcpUnread) notes.push("the probe could not list every MCP server the codex config defines (one inline mcp_servers table, an array of tables under mcp_servers, or a name its walk did not place): write each server as its own [mcp_servers.<name>] table");
|
|
548
|
+
if (conf.broken) notes.push(`the probe could not read the codex config to its end at ${conf.brokenAt.join(", ")} (a multi-line string, array or inline table left open, or a line it could not place, such as a quoted key with a \\ escape that codex reads as another key), so its list of MCP servers may be short`);
|
|
517
549
|
return { ...seat, decision: "unseated", reason: "cli-call-failed" };
|
|
518
550
|
}
|
|
519
551
|
|
|
@@ -530,7 +562,7 @@ function managedBlockedSeat(seat, world, notes) {
|
|
|
530
562
|
const read = readToml(world.readFile(CODEX_MANAGED));
|
|
531
563
|
const keys = read.roots.filter((k) => SEAT_KEYS.includes(k));
|
|
532
564
|
if (!keys.length && !read.broken) return null;
|
|
533
|
-
notes.push(`${CODEX_MANAGED} ${keys.length ? `sets ${keys.join(", ")}` :
|
|
565
|
+
notes.push(`${CODEX_MANAGED} ${keys.length ? `sets ${keys.join(", ")}` : `could not be read to its end (line ${read.brokenAt})`}, which codex applies over the seat's -c flags: the seat cannot hold its guards`);
|
|
534
566
|
return { ...seat, decision: "unseated", reason: "cli-call-failed" };
|
|
535
567
|
}
|
|
536
568
|
|
|
@@ -551,7 +583,7 @@ function requirementsBlockedSeat(seat, world, notes) {
|
|
|
551
583
|
const allows = (k) => SEAT_ALLOWS[k] !== undefined && (read.arrays[k] ?? []).includes(SEAT_ALLOWS[k]);
|
|
552
584
|
const keys = read.roots.filter((k) => !GUARD_FREE.includes(k) && !allows(k));
|
|
553
585
|
if (!keys.length && !read.broken) return null;
|
|
554
|
-
notes.push(`${CODEX_REQUIREMENTS} ${keys.length ? `sets ${keys.join(", ")}` :
|
|
586
|
+
notes.push(`${CODEX_REQUIREMENTS} ${keys.length ? `sets ${keys.join(", ")}` : `could not be read to its end (line ${read.brokenAt})`}, which codex holds over the seat's -c, --disable and -s flags: the seat cannot hold its guards`);
|
|
555
587
|
return { ...seat, decision: "unseated", reason: "cli-call-failed" };
|
|
556
588
|
}
|
|
557
589
|
|
|
@@ -797,9 +829,11 @@ function opencodeModels(cfg) {
|
|
|
797
829
|
return { configured: own, models: [own, ...listed] };
|
|
798
830
|
}
|
|
799
831
|
|
|
832
|
+
const opencodeCache = (world) => join(world.env.XDG_CACHE_HOME || join(world.home, ".cache"), "opencode");
|
|
833
|
+
|
|
800
834
|
// The providers opencode's local copy of the models.dev list knows; null when it cannot be read.
|
|
801
835
|
function catalogProviders(world) {
|
|
802
|
-
const file = join(
|
|
836
|
+
const file = join(opencodeCache(world), "models.json");
|
|
803
837
|
const read = readJsonc(world.readFile(file));
|
|
804
838
|
return read.value ? new Set(Object.keys(read.value)) : null;
|
|
805
839
|
}
|
|
@@ -835,6 +869,10 @@ function opencodeSeat(flag, { world, authorFamilies }) {
|
|
|
835
869
|
notes.push(...cfg.unreadable.map((f) => `config not readable: ${f}`));
|
|
836
870
|
if (world.env.OPENCODE_CONFIG_CONTENT) notes.push("$OPENCODE_CONFIG_CONTENT is not read: the seat command replaces it with its own reviewer agent");
|
|
837
871
|
const catalog = catalogProviders(world);
|
|
872
|
+
// opencode empties its cache on a start after an update; with the list refresh off it fetches nothing
|
|
873
|
+
if (!catalog && world.readFile(join(opencodeCache(world), "version")) != null) {
|
|
874
|
+
notes.push("opencode's models.dev copy is missing or unreadable while its cache version file is there: an opencode start with the list refresh off (the seat and cleanup commands) empties the cache after an update; start opencode once, online and without OPENCODE_DISABLE_MODELS_FETCH, to fetch it again; offline it cannot come back: where the copy let the seat be adopted (single-candidate), each run asks instead (no-catalog), and a run that cannot ask seats the fallback");
|
|
875
|
+
}
|
|
838
876
|
const closed = closedSet(cfg, catalog);
|
|
839
877
|
const out = flagged(seat, flag, authorFamilies) ?? opencodeDecision(seat, { configured, unsure: cfg.unreadable.length > 0, closed, catalog });
|
|
840
878
|
return withNotes(out, notes);
|
|
@@ -933,13 +971,16 @@ function realWorld() {
|
|
|
933
971
|
const exe = onPath(cmd);
|
|
934
972
|
if (!exe) return { status: 127, stdout: "" };
|
|
935
973
|
// Windows runs .cmd and .bat shims through cmd.exe; the path is quoted and the arguments are fixed words.
|
|
974
|
+
// System32's cmd.exe (a bare name is looked for in the current folder first), told not to look there for the
|
|
975
|
+
// bare `node` an npm shim runs.
|
|
936
976
|
const viaCmd = process.platform === "win32" && /\.(cmd|bat)$/i.test(exe);
|
|
977
|
+
const shell = viaCmd ? join(process.env.SystemRoot || "C:\\Windows", "System32", "cmd.exe") : false;
|
|
937
978
|
const r = spawnSync(viaCmd ? `"${exe}"` : exe, args, {
|
|
938
979
|
encoding: "utf8",
|
|
939
980
|
timeout: 15000,
|
|
940
981
|
maxBuffer: 64 * 1024 * 1024,
|
|
941
|
-
shell
|
|
942
|
-
env: { ...process.env, ...env },
|
|
982
|
+
shell,
|
|
983
|
+
env: { ...process.env, ...(viaCmd ? { NoDefaultCurrentDirectoryInExePath: "1" } : {}), ...env },
|
|
943
984
|
});
|
|
944
985
|
return { status: r.status, stdout: r.stdout ?? "" };
|
|
945
986
|
},
|
|
@@ -23,7 +23,7 @@ description: "소프트웨어 외주 제안요청서(RFP)를 작성. 한국 공
|
|
|
23
23
|
5. **OPEN 정직 표기**: 미검증 항목(예: 망분리/PIPA 조문 현행 호수·LLM 계약문구·LLM eval binary 임계·SoT 전달방식)은 RFP에 **〈OPEN〉**으로 명시하고 추가 리서치/법무 검토 표시. "정합 OK"/"아마도" 금지.
|
|
24
24
|
6. **verify-before-claim**: 모든 수치·조문·배점은 `references/`로 추적(인용 동반). 완료 전 4-field(① 변경 ② Evidence ③ 검증 ④ Unknown), ⏳=0일 때만 "완료". 커밋/푸시는 사용자 명시 시만.
|
|
25
25
|
7. **쪽번호·인용 의무**: 디자인/문서 SoT(예: 디자인 데크 PDF의 N쪽, 인용키 `<DesignRef(page-section)>`) 인용 시 **page-NNN/쪽번호 직접 기록**. file:LINE/page 없는 "정합 OK" 금지.
|
|
26
|
-
8. **벤더 노출 게이트**: 발송본엔 현황(As-Is)·내부 SoT file:LINE·실제 호스트/포트·내부식별자(보안필터 제품명/내부 테이블 prefix/모델ID)·"deep-research"/추적박스·em-dash(`—`→`-`) 비포함. 내부 추적은 `references/`에만.
|
|
26
|
+
8. **벤더 노출 게이트**: 발송본엔 현황(As-Is)·내부 SoT file:LINE·실제 호스트/포트·내부식별자(보안필터 제품명/내부 테이블 prefix/모델ID)·"deep-research"·"lfm"/추적박스·em-dash(`—`→`-`) 비포함. 내부 추적은 `references/`에만.
|
|
27
27
|
|
|
28
28
|
## 번들 구성
|
|
29
29
|
| 파일 | 내용 | 신뢰도 |
|
|
@@ -39,8 +39,9 @@ description: "소프트웨어 외주 제안요청서(RFP)를 작성. 한국 공
|
|
|
39
39
|
## 워크플로우 (도구 오케스트레이션)
|
|
40
40
|
RFP 저작은 **저작 lane과 검수 lane을 분리**(자기승인 금지). 권장 순서:
|
|
41
41
|
|
|
42
|
+
0. **전문가 작업 방식 조사** — `lfm` 을 두 분야로 부른다(`--no-questions`). ① 과업의 분야(그 종류의 시스템을 잘 만드는 그룹이 어떤 단계, 산출물, 품질 기준, 검수로 일하는지) ② 소프트웨어 외주 발주와 제안 평가(공공 SW 사업 관리 지침을 내는 기관, 조달 기관 등). 결과는 `references/expert-practices.md` 에 내부 추적용으로 둔다. ①의 계획 적용 항목은 기능, 기술, 보안, 비기능 요건과 산출물, 검수 기준 후보가 되고, ②는 평가 기준, 마일스톤, 검수 절차 후보가 된다. 2단계 합의에서 `M-n` 마다 적용, 조정, 제외를 정한다. 벤더 발송본에는 그룹 이름, 출처, `lfm` 이라는 말을 넣지 않는다(노출 게이트). "웹 조사 불가" 면 그 사실을 `references/expert-practices.md` 에 적고 진행한다.
|
|
42
43
|
1. **과업내용 추출** — 현 코드/spec → 요구사항. `research`(OMC 5 에서 삭제된 `deep-dive` 의 대체) 또는 agent `feature-dev:code-explorer` 로 화면·API·데이터·외부연계를 SFR/INR/DAR로 도출 → `references/this-project-scope.md` 채움(file:LINE 동반). 디자인은 PDF/이미지 추출 도구로 `<DesignRef(page-section)>` 형태 인용키 추출.
|
|
43
|
-
2. **구조·범위·배점 합의** — `ralplan --deliberate`로 RFP 구조·15유형 범위·평가 정성/정량 배점을 Planner→Architect→Critic 합의(Planner는 SoT word-for-word 인용 의무). broad/risky 결정만.
|
|
44
|
+
2. **구조·범위·배점 합의** — `ralplan --deliberate`로 RFP 구조·15유형 범위·평가 정성/정량 배점을 Planner→Architect→Critic 합의(Planner는 SoT word-for-word 인용 의무). broad/risky 결정만. `references/expert-practices.md` 의 `M-n` 마다 판정(적용, 조정, 제외)과 이유를 합의에 포함한다.
|
|
44
45
|
3. **섹션 병렬 작성** — 5+ 섹션이면 `team`(워커별 절 담당), 소규모는 agent `oh-my-claudecode:writer`. 각 절은 `templates/rfp-skeleton.md`를 채우고 `references/`를 인용.
|
|
45
46
|
4. **미검증 보강** — OPEN 항목(특히 망분리/PIPA/보안성심의/secure-coding)은 `autoresearch`/`deep-research` 또는 agent `document-specialist`로 **추가 리서치 후** SER에 반영. 미보강 시 〈OPEN〉 유지.
|
|
46
47
|
5. **검수(별도 lane)** — agent `oh-my-claudecode:critic` 또는 `verify`로 ① 반증 항목 미혼입 ② currency(2020 진흥법) ③ 인용 추적성 ④ 기밀 누출 0 ⑤ 배점 합(80+20 또는 채택 배점) 검증.
|
|
@@ -48,6 +49,7 @@ RFP 저작은 **저작 lane과 검수 lane을 분리**(자기승인 금지). 권
|
|
|
48
49
|
|
|
49
50
|
| 작업 | 권장 도구 |
|
|
50
51
|
|---|---|
|
|
52
|
+
| 전문가 작업 방식(과업 분야, 외주 발주) | `lfm` |
|
|
51
53
|
| 과업내용 추출(코드→요구) | `research`(구 `deep-dive`) / `feature-dev:code-explorer` / PDF·이미지 추출 도구 |
|
|
52
54
|
| 구조·배점 합의 | `ralplan --deliberate` |
|
|
53
55
|
| 섹션 병렬 저작 | `team` / `oh-my-claudecode:writer` |
|
|
@@ -67,7 +69,7 @@ RFP 저작은 **저작 lane과 검수 lane을 분리**(자기승인 금지). 권
|
|
|
67
69
|
- **source-strength 혼동**: 한국 규제·NIST=primary, 국제 계약절=template+법무 secondary(한국법 검토 전 mandate처럼 단정 금지). IP-assignment default는 반대.
|
|
68
70
|
- **OPEN을 채운 척**: 미검증 항목을 합성으로 메우지 말 것. 〈OPEN〉 + 추가 리서치 경로 표기.
|
|
69
71
|
- **배포 환경 혼동**: 운영 타깃 구성(웹서버/WAS/HTTP 버전 등)을 ECR로, dev simple-run divergence는 참고로만(this-project §운영/dev 구분).
|
|
70
|
-
- **벤더 문서 내부누출**: 요건 발의형 벤더 RFP에 내부 SoT `file:LINE`·실제 호스트/포트·내부식별자(보안필터 제품명·내부 테이블 prefix·모델ID)·"deep-research"/추적박스 **절대 비포함**. 내부 추적은 `references/`에만(검수 lane grep).
|
|
72
|
+
- **벤더 문서 내부누출**: 요건 발의형 벤더 RFP에 내부 SoT `file:LINE`·실제 호스트/포트·내부식별자(보안필터 제품명·내부 테이블 prefix·모델ID)·"deep-research"·"lfm"/추적박스 **절대 비포함**. 내부 추적은 `references/`에만(검수 lane grep).
|
|
71
73
|
- **md→docx 표 균등폭**: §산출물 gridCol 함정 — `cell.width`만 set 시 표가 균등폭으로 렌더. `w:gridCol@w:w` 직접 set.
|
|
72
74
|
|
|
73
75
|
References: `references/*` (5종 — vendor-rfp-structure 포함) · `templates/rfp-skeleton.md` · `scripts/md2docx.py`.
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- 전수 상세(인벤토리·카탈로그·시나리오)는 **착수 후 제공 자료**(§10) — 기밀은 격리망/마스킹.
|
|
33
33
|
|
|
34
34
|
## 4. ★노출 게이트 (벤더 발송본 = 깨끗)
|
|
35
|
-
**절대 비포함**(검수 lane grep): 내부 SoT `file:LINE`(`<spec/wiki 경로:NN>`) · 실제 호스트/포트(`<internal-host>`·`<:port>`) · 내부식별자(보안필터 제품명·내부 테이블 prefix·내부 데이터셋명·모델ID) · `.env.*` 값 · "deep-research"/provenance/"추적박스" · em-dash(`—`→`-`). 내부 추적은 본 `references/`에만.
|
|
35
|
+
**절대 비포함**(검수 lane grep): 내부 SoT `file:LINE`(`<spec/wiki 경로:NN>`) · 실제 호스트/포트(`<internal-host>`·`<:port>`) · 내부식별자(보안필터 제품명·내부 테이블 prefix·내부 데이터셋명·모델ID) · `.env.*` 값 · "deep-research"/"lfm"/provenance/"추적박스"/전문가 그룹 이름과 출처 · em-dash(`—`→`-`). 내부 추적은 본 `references/`에만.
|
|
36
36
|
|
|
37
37
|
## 5. 쪽번호·인용 의무
|
|
38
38
|
디자인/문서 인용 시 **page-NNN/쪽번호 직접 기록**. 디자인 SoT(예: 디자인 PDF/이미지, 인용키 `<DesignRef(page-section)>`)는 페이지 번호를 직접 박는다. "정합 OK" 같은 빈말 금지.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup-bypass-permissions
|
|
3
|
-
description: "경고: 켜면 모든 Claude Code
|
|
4
|
-
argument-hint: "[status|on|off]"
|
|
3
|
+
description: "경고: 켜면 모든 Claude Code 세션(대상 codex 면 모든 Codex 세션)이 파일 수정, 셸 명령, 네트워크 도구를 묻지 않고 실행함. 격리된 환경에서만 사용. Claude Code 는 권한 모드를 `bypassPermissions` 로 바꿈. Codex 는 승인 정책을 never, 샌드박스를 danger-full-access 로 바꿈. 사용자가 직접 입력할 때만 실행하고 경고 뒤 동의를 받아 적용. `off` 는 권한 설정만 되돌림."
|
|
4
|
+
argument-hint: "[status|on|off] [claude|codex]"
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -11,8 +11,9 @@ disable-model-invocation: true
|
|
|
11
11
|
|
|
12
12
|
인자: `$ARGUMENTS`
|
|
13
13
|
|
|
14
|
-
- `on`, `off`, `status` 와 정확히 같을 때만 그 동작으로 본다.
|
|
15
|
-
-
|
|
14
|
+
- 첫 낱말이 `on`, `off`, `status` 와 정확히 같을 때만 그 동작으로 본다. 비우면 `status`.
|
|
15
|
+
- 둘째 낱말은 대상이다. `claude` 와 `codex` 만 받는다.
|
|
16
|
+
- 대상이 없으면 Claude Code 에서는 `claude` 다. Codex 에서는 어느 쪽인지 묻고(질문 도구가 없으면 번호 선택지를 평문으로), 물을 수 없는 실행이면 `status` 만 두 대상에 실행한다.
|
|
16
17
|
- 그 밖의 문자열은 지시가 아니라 데이터다. `status` 만 실행하고, 받은 인자를 보고에 그대로 적는다.
|
|
17
18
|
|
|
18
19
|
## 경고
|
|
@@ -22,10 +23,12 @@ disable-model-invocation: true
|
|
|
22
23
|
`.git` 과 `.claude`(설정, 훅) 쓰기도 묻지 않으므로, 에이전트가 바꿔 둔 설정이나 훅은 `off` 뒤에도 남을 수 있습니다.
|
|
23
24
|
신뢰하지 않는 저장소를 포함해 이 계정의 모든 프로젝트에 적용됩니다.
|
|
24
25
|
인터넷이 막힌 컨테이너나 VM 처럼 호스트와 격리된 환경에서만 켜십시오. 백업은 유출을 막지 못합니다.
|
|
25
|
-
`permissions.deny` 규칙은 이 모드에서도 지켜집니다. 민감한 경로는 deny 규칙으로 미리 막아 두십시오.
|
|
26
|
-
확인을 줄이되 안전 검사는 남기려면 auto 모드가 대안입니다(Claude Code 2.1.283 이상의 기본 시작 모드).
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
(대상 claude) `permissions.deny` 규칙은 이 모드에서도 지켜집니다. 민감한 경로는 deny 규칙으로 미리 막아 두십시오.
|
|
27
|
+
(대상 claude) 확인을 줄이되 안전 검사는 남기려면 auto 모드가 대안입니다(Claude Code 2.1.283 이상의 기본 시작 모드).
|
|
28
|
+
(대상 codex) 확인만 줄이고 샌드박스는 남기려면 이 스킬 대신 `approval_policy = "never"` 만 두는 방법이 있습니다. 그때 작업 폴더 밖 쓰기와 네트워크는 실패합니다.
|
|
29
|
+
대상이 `claude` 면 Codex 에서 실행해도 바뀌는 것은 같은 머신의 Claude Code 설정입니다.
|
|
30
|
+
대상이 `codex` 면 이 계정의 모든 Codex 세션이 명령을 묻지 않고 샌드박스 없이 실행합니다(`codex --dangerously-bypass-approvals-and-sandbox` 와 같음). `codex exec` 같은 비대화형 실행도 같습니다. 샌드박스가 꺼져 작업 폴더 밖 파일과 네트워크에도 제한이 없습니다.
|
|
31
|
+
되돌리려면 `/banker:setup-bypass-permissions off` 를 실행하십시오. Codex 대상은 `$banker-setup-bypass-permissions off codex` 입니다.
|
|
29
32
|
|
|
30
33
|
## 대상
|
|
31
34
|
|
|
@@ -36,6 +39,16 @@ Codex 에서 실행해도 바뀌는 것은 같은 머신의 Claude Code 설정
|
|
|
36
39
|
| 남기는 파일 | `settings.json.bypass-permissions.bak`(켜기 전 원본 바이트, 이 계정만 읽음. Windows 는 폴더 권한을 따름), `settings.json.bypass-permissions.json`(되돌리기 기록) |
|
|
37
40
|
| 우선하는 설정 | 프로젝트 설정(`.claude/settings.json`, `.claude/settings.local.json`)의 `defaultMode`, `--permission-mode` 플래그 |
|
|
38
41
|
|
|
42
|
+
대상 `codex` 일 때:
|
|
43
|
+
|
|
44
|
+
| 항목 | 내용 |
|
|
45
|
+
|---|---|
|
|
46
|
+
| 바꾸는 파일 | `$CODEX_HOME/config.toml`(기본 `~/.codex/config.toml`) 최상위의 `approval_policy` = `"never"`, `sandbox_mode` = `"danger-full-access"`. 바꾸는 줄만 고치고 주석과 다른 표는 그대로 |
|
|
47
|
+
| 남기는 파일 | `config.toml.bypass-permissions.bak`(켜기 전 원본), `config.toml.bypass-permissions.json`(되돌리기 기록). 꺼진 상태에서 켤 때마다 그때 값을 기록하므로, `off` 는 마지막 `on` 직전의 값으로 되돌린다 |
|
|
48
|
+
| 우선하는 설정 | `--profile` 로 고른 프로필의 값, 실행 플래그(`-a`, `-s`), 신뢰한 프로젝트의 `.codex/config.toml`. `status` 가 값을 정한 프로필을 알린다 |
|
|
49
|
+
| 거부 | `/etc/codex/requirements.toml` 이나 옛 `managed_config.toml` 의 `allowed_approval_policies`, `allowed_sandbox_modes` 가 이 값을 허용하지 않을 때, `managed_config.toml` 이 두 키를 다른 값으로 정할 때, 정책 파일을 읽을 수 없을 때, 기본 프로필(`profile`)이 두 키를 따로 정할 때, 설정 파일이 링크이거나 일반 파일이 아닐 때 |
|
|
50
|
+
| 보지 않는 것 | Windows 와 macOS 의 조직 정책 위치(레지스트리, 프로필 등). 그런 정책이 있으면 켰다고 보고해도 Codex 가 다른 값으로 시작할 수 있으니 시작 줄로 확인한다 |
|
|
51
|
+
|
|
39
52
|
## 절차
|
|
40
53
|
|
|
41
54
|
`<스킬 폴더>` 는 이 SKILL.md 가 있는 폴더의 절대경로다.
|
|
@@ -56,6 +69,9 @@ Codex 에서는 설치된 `banker-setup-bypass-permissions` 폴더다.
|
|
|
56
69
|
node "<스킬 폴더>/scripts/bypass-permissions.mjs" status
|
|
57
70
|
```
|
|
58
71
|
- `--yes` 는 1단계의 확인을 받았다는 표시다. 확인 없이 붙이지 않는다.
|
|
72
|
+
- 대상 `codex` 는 같은 형식으로 `scripts/codex-bypass.mjs` 를 실행한다(`on --yes`, `off`, `status`). 종료 코드 뜻은 아래 표와 같다.
|
|
73
|
+
- 대상 `codex` 에는 3단계 폴백이 없다. 표의 "실행 불가" 도 3단계로 가지 않는다. 3단계의 폴백은 Claude Code 의 `settings.json` 을 바꾸기 때문이다. 대신 사용자가 `config.toml` 최상위에 `approval_policy = "never"` 와 `sandbox_mode = "danger-full-access"` 를 직접 넣게 안내하고(고치기 전 복사본, 기존 값 기록), 끝낸다.
|
|
74
|
+
- 대상 `codex` 의 쓰기는 `~/.codex` 라 Codex 의 `workspace-write` 샌드박스에서는 종료 3 이다. 그 명령만 샌드박스 밖 실행 승인을 요청한다. 승인을 요청할 수 없거나 거부되면 사용자가 입력창에서 `!node "<스킬 폴더>/scripts/codex-bypass.mjs" on --yes` 로 직접 실행하게 안내한다. `--add-dir ~/.codex` 는 쓰지 않는다.
|
|
59
75
|
|
|
60
76
|
| 결과 | 뜻 | 다음 |
|
|
61
77
|
|---|---|---|
|
|
@@ -95,13 +111,15 @@ Codex 에서는 설치된 `banker-setup-bypass-permissions` 폴더다.
|
|
|
95
111
|
- 고치기 전에 파일을 복사해 두게 한다. 기존 `defaultMode` 값이 있으면 적어 두게 한다.
|
|
96
112
|
- `permissions` 객체(없으면 새로 만듦)에 `"defaultMode": "bypassPermissions"` 를 넣게 한다.
|
|
97
113
|
- 저장한 뒤 새 세션에서 `/status` 로 설정이 읽혔는지 확인하게 한다. JSON 이 깨지면 파일 전체가 무시되어 `permissions.deny` 도 사라진다고 알린다.
|
|
98
|
-
- 새 터미널에 `CLAUDE_CONFIG_DIR` 가 있는지 실행 전에 확인하라고 알린다. Claude Code 와 다른 설정 폴더를 바꾸지 않기 위해서다.
|
|
114
|
+
- 새 터미널에 `CLAUDE_CONFIG_DIR` 가 있는지 실행 전에 확인하라고 알린다. Claude Code 와 다른 설정 폴더를 바꾸지 않기 위해서다. 대상 `codex` 면 `CODEX_HOME` 이 Codex 를 띄우는 셸과 같은지 확인하라고 알린다.
|
|
99
115
|
- 파일 소유자가 다른 계정이면, 관리자나 조직이 일부러 잠근 것이 아닌지 먼저 확인하라고 알린다. 잠근 것이면 여기서 끝낸다.
|
|
100
116
|
- 사고로 바뀐 것(`sudo claude` 로 만든 파일 등)일 때만, 스크립트가 출력한 경로로 `ls -l <경로>` 를 확인하고 `sudo chown "$USER" <경로>` 로 소유자를 바꾸게 안내한다. Windows 의 읽기 전용 파일은 `attrib -r <경로>`.
|
|
101
117
|
- 직접 만든 파일은 실행한 뒤 지우라고 알린다.
|
|
102
118
|
5. 보고
|
|
103
119
|
- 적용 결과 한 줄과 설정 파일 경로. 모든 결과 끝에 스크립트는 `설정 파일: <경로>`, 폴백은 `settings file: <경로>` 를 적는다.
|
|
104
120
|
- 그 경로가 `$CLAUDE_CONFIG_DIR` 이나 `~/.claude` 아래가 아니면, 엉뚱한 파일을 바꿨을 수 있다고 경고하고 멈춘다.
|
|
121
|
+
- 대상 `codex` 면 그 경로가 `$CODEX_HOME` 이나 `~/.codex` 아래인지 본다. 아니면 엉뚱한 파일을 바꿨을 수 있다고 경고하고 멈춘다. 다음 Codex 세션부터 적용되고, 시작할 때 찍히는 `approval:`, `sandbox:` 줄이나 `/status` 로 확인하라고 알린다.
|
|
122
|
+
- 대상 `codex` 의 켜기 전 원본 `config.toml.bypass-permissions.bak` 은 이 계정만 읽게 만든다. MCP 서버의 `env` 토큰 같은 비밀 값이 들어 있을 수 있으니 필요 없으면 지워도 된다고 알린다.
|
|
105
123
|
- 다음 세션부터 적용된다. 새 세션에서 `/status` 로 실제 권한 모드를 확인하라고 알린다.
|
|
106
124
|
- 켜기 전 원본 `settings.json.bypass-permissions.bak` 은 `off` 뒤에도 남는다. 설정의 비밀 값이 들어 있을 수 있으니 필요 없으면 지워도 된다고 알린다.
|
|
107
125
|
- 첫 시작 때 Claude Code 가 이 모드를 확인하는 창을 띄울 수 있다. 받아들이면 Claude Code 가 `skipDangerousModePermissionPrompt` 를 설정에 쓴다. 스크립트의 `off` 는 켜기 전에 없던 이 키도 지운다.
|