@ouro.bot/cli 0.1.0-alpha.892 → 0.1.0-alpha.894

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.
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CmuxSocketError = void 0;
37
+ exports.createCmuxClient = createCmuxClient;
38
+ const node_crypto_1 = require("node:crypto");
39
+ const net = __importStar(require("node:net"));
40
+ const cmux_config_1 = require("../../heart/cmux-config");
41
+ const runtime_1 = require("../../nerves/runtime");
42
+ class CmuxSocketError extends Error {
43
+ code;
44
+ constructor(code, message) {
45
+ super(message);
46
+ this.code = code;
47
+ this.name = "CmuxSocketError";
48
+ }
49
+ }
50
+ exports.CmuxSocketError = CmuxSocketError;
51
+ const ACCESS_REFUSED = /access denied|authentication required|invalid password|not authorized|unauthori[sz]ed/i;
52
+ function isRecord(value) {
53
+ return !!value && typeof value === "object" && !Array.isArray(value);
54
+ }
55
+ function parseRecord(line) {
56
+ try {
57
+ const value = JSON.parse(line);
58
+ return isRecord(value) ? value : null;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function createCmuxClient(connection, options = {}) {
65
+ const timeoutMs = options.timeoutMs ?? 5_000;
66
+ const repair = (0, cmux_config_1.cmuxRepairHint)(options.agentName ?? "<agent>");
67
+ const wrap = (line) => connection.auth.kind === "capability"
68
+ ? `_cmux_capability_v1 ${connection.auth.token} ${line}`
69
+ : line;
70
+ const refusal = (text) => text.startsWith("ERROR:") && ACCESS_REFUSED.test(text)
71
+ ? new CmuxSocketError("auth", `cmux refused the socket connection (${text.slice(0, 200)}); ${repair}`)
72
+ : null;
73
+ const connectError = (error) => new CmuxSocketError("unavailable", `cmux socket is not reachable at ${connection.socketPath} (${String(error.code)}); is cmux running?`);
74
+ /** Opens a connection, runs the password handshake when needed, and hands back a line reader. */
75
+ function open(onLine, onEnd) {
76
+ return new Promise((resolve) => {
77
+ const socket = net.createConnection(connection.socketPath);
78
+ let buffer = "";
79
+ let handshake = connection.auth.kind === "password";
80
+ socket.setEncoding("utf-8");
81
+ socket.on("error", (error) => onEnd(connectError(error)));
82
+ socket.on("close", () => onEnd());
83
+ socket.on("data", (chunk) => {
84
+ buffer += chunk;
85
+ let index = buffer.indexOf("\n");
86
+ while (index >= 0) {
87
+ const line = buffer.slice(0, index).trim();
88
+ buffer = buffer.slice(index + 1);
89
+ index = buffer.indexOf("\n");
90
+ if (!line)
91
+ continue;
92
+ if (handshake) {
93
+ handshake = false;
94
+ if (line.startsWith("ERROR:") && !line.includes("Unknown command 'auth'")) {
95
+ socket.destroy();
96
+ onEnd(new CmuxSocketError("auth", `cmux rejected the socket password (${line.slice(0, 200)}); ${repair}`));
97
+ continue;
98
+ }
99
+ resolve(socket);
100
+ continue;
101
+ }
102
+ onLine(line);
103
+ }
104
+ });
105
+ socket.on("connect", () => {
106
+ if (connection.auth.kind === "password")
107
+ socket.write(`auth ${connection.auth.password}\n`);
108
+ else
109
+ resolve(socket);
110
+ });
111
+ });
112
+ }
113
+ function request(line) {
114
+ return new Promise((resolve, reject) => {
115
+ let settled = false;
116
+ let socket = null;
117
+ const finish = (error, value) => {
118
+ if (settled)
119
+ return;
120
+ settled = true;
121
+ clearTimeout(timer);
122
+ socket?.destroy();
123
+ if (error)
124
+ reject(error);
125
+ else
126
+ resolve(value);
127
+ };
128
+ const timer = setTimeout(() => finish(new CmuxSocketError("timeout", `cmux did not answer within ${timeoutMs} ms`)), timeoutMs);
129
+ void open((response) => finish(refusal(response), response), (error) => finish(error ?? new CmuxSocketError("unavailable", "cmux closed the socket before answering"))).then((opened) => {
130
+ socket = opened;
131
+ opened.write(`${wrap(line)}\n`);
132
+ });
133
+ });
134
+ }
135
+ const v2Line = (method, params) => JSON.stringify({ id: (0, node_crypto_1.randomUUID)(), method, params });
136
+ function parseV2(text) {
137
+ const parsed = parseRecord(text);
138
+ if (!parsed)
139
+ throw new CmuxSocketError(text.startsWith("ERROR:") ? "command_failed" : "protocol", `unexpected cmux response: ${text.slice(0, 200)}`);
140
+ if (parsed.ok === true)
141
+ return isRecord(parsed.result) ? parsed.result : {};
142
+ const error = isRecord(parsed.error) ? parsed.error : {};
143
+ throw new CmuxSocketError(typeof error.code === "string" ? error.code : "error", typeof error.message === "string" && error.message ? error.message : "cmux error");
144
+ }
145
+ return {
146
+ async call(method, params = {}) {
147
+ const text = await request(v2Line(method, params));
148
+ const result = parseV2(text);
149
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_call", message: "cmux socket call answered", meta: { method } });
150
+ return result;
151
+ },
152
+ async command(line) {
153
+ const text = await request(line);
154
+ if (text.startsWith("ERROR:"))
155
+ throw new CmuxSocketError("command_failed", text.slice(0, 500));
156
+ return text;
157
+ },
158
+ stream(params, handlers) {
159
+ let closed = false;
160
+ let socket = null;
161
+ const end = (error) => {
162
+ if (closed)
163
+ return;
164
+ closed = true;
165
+ socket?.destroy();
166
+ handlers.onClose(error);
167
+ };
168
+ void open((line) => {
169
+ if (closed)
170
+ return;
171
+ const refused = refusal(line);
172
+ if (refused)
173
+ return end(refused);
174
+ const frame = parseRecord(line);
175
+ if (!frame)
176
+ return end(new CmuxSocketError("protocol", `unexpected cmux event frame: ${line.slice(0, 200)}`));
177
+ if (frame.ok === false) {
178
+ try {
179
+ parseV2(line);
180
+ }
181
+ catch (error) {
182
+ return end(error);
183
+ }
184
+ }
185
+ handlers.onFrame(frame);
186
+ }, end).then((opened) => {
187
+ socket = opened;
188
+ if (closed)
189
+ opened.destroy();
190
+ else
191
+ opened.write(`${wrap(v2Line("events.stream", params))}\n`);
192
+ });
193
+ return { close: () => end() };
194
+ },
195
+ };
196
+ }
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findRepoRoot = findRepoRoot;
37
+ exports.resolveInsideRepo = resolveInsideRepo;
38
+ exports.tokenizeCommand = tokenizeCommand;
39
+ exports.gitConfigHazard = gitConfigHazard;
40
+ exports.evaluateFloor = evaluateFloor;
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ const realFs = {
45
+ exists: (target) => fs.existsSync(target),
46
+ realpath: (target) => fs.realpathSync(target),
47
+ read: (target) => {
48
+ try {
49
+ return fs.readFileSync(target, "utf-8");
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ },
55
+ };
56
+ const MAX_COMMAND_CHARS = 2_000;
57
+ const EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
58
+ const READ_TOOLS = new Set(["Read", "Glob", "Grep", "LS"]);
59
+ /** Characters that chain, pipe, redirect, substitute, expand or escape anywhere in the command, quoted or not. */
60
+ const SHELL_CONTROL = /[\n\r;&|`$<>(){}\\!#]/;
61
+ /** Characters the shell expands as a glob when they appear outside quotes. */
62
+ const GLOB = /[*?[\]]/;
63
+ const PROTECTED_SEGMENTS = new Set([".git", ".claude", ".github", ".husky", ".vscode", ".idea"]);
64
+ const PROTECTED_NAMES = new Set([
65
+ "package.json", ".npmrc", ".yarnrc", ".yarnrc.yml", ".mcp.json", ".gitmodules", ".gitattributes", ".gitconfig",
66
+ ".bashrc", ".bash_profile", ".bash_login", ".profile", ".zshrc", ".zshenv", ".zprofile", ".zlogin", ".envrc",
67
+ "claude.md", "agents.md", "copilot-instructions.md",
68
+ ]);
69
+ /** Protected paths that can hold credentials: even reading them escalates. */
70
+ const CREDENTIAL_TOUCHES = new Set(["touches an environment file", "touches .git", "touches .npmrc", "touches .yarnrc", "touches .yarnrc.yml", "touches .gitconfig", "touches .envrc", "touches .claude"]);
71
+ /** Programs never answered for the human, even with a precedent: they delete, escalate, reach the network, wrap or run other programs. */
72
+ const HARD_COMMANDS = new Set([
73
+ "rm", "rmdir", "unlink", "shred", "dd", "mkfs", "diskutil", "chmod", "chown", "chgrp", "chflags", "xattr", "mv", "cp", "ln", "install", "tee", "truncate",
74
+ "sudo", "su", "doas", "kill", "killall", "pkill", "launchctl", "shutdown", "reboot", "halt", "crontab", "defaults", "security", "osascript", "open",
75
+ "curl", "wget", "ssh", "scp", "sftp", "rsync", "nc", "ncat", "netcat", "telnet", "ftp", "socat", "gh", "az", "aws", "gcloud", "kubectl", "terraform", "docker", "fly", "vercel", "heroku",
76
+ "sh", "bash", "zsh", "fish", "dash", "ksh", "csh", "tcsh", "eval", "exec", "source", ".", "env", "xargs", "nohup", "time", "timeout", "nice", "caffeinate", "watch", "command", "builtin", "script", "expect", "at", "batch",
77
+ "npx", "pnpx", "bunx", "ouro", "cmux", "claude", "codex", "copilot", "base64", "openssl", "gpg", "date", "less", "more", "vi", "vim", "nano", "emacs",
78
+ ]);
79
+ /** Programs that run code they are handed: a precedent may answer them only with no options at all. */
80
+ const INTERPRETERS = new Set(["python", "python3", "node", "ruby", "perl", "php", "deno", "lua", "swift", "awk", "gawk", "sed", "jq", "make"]);
81
+ const PACKAGE_MANAGERS = new Set(["npm", "pnpm", "yarn", "bun", "pip", "pip3", "pipx", "brew", "gem", "cargo", "go", "uv", "poetry", "composer"]);
82
+ /** The repository's own checks, allowed only as these exact words. */
83
+ const TEST_COMMANDS = [
84
+ ["npm", "test"], ["npm", "run", "test"], ["npm", "run", "lint"], ["npm", "run", "typecheck"],
85
+ ["swift", "test"], ["swift", "build"], ["cargo", "test"], ["cargo", "check"], ["go", "test"], ["go", "vet"],
86
+ ];
87
+ const flags = (letters, values = {}) => ({ ...Object.fromEntries([...letters].map((letter) => [letter, "flag"])), ...values });
88
+ const longFlags = (names, values = {}) => ({ ...Object.fromEntries(names.map((name) => [name, "flag"])), ...values });
89
+ const GREP = {
90
+ short: flags("rRnilvwcEFHhosx", { e: "text", m: "num", A: "num", B: "num", C: "num" }),
91
+ long: longFlags(["--line-number", "--ignore-case", "--recursive", "--count", "--files-with-matches", "--fixed-strings", "--word-regexp"], { "--include": "text", "--exclude": "text", "--exclude-dir": "text" }),
92
+ };
93
+ const COMMANDS = {
94
+ ls: { short: flags("1aAlhRtrSdF"), long: longFlags(["--all", "--human-readable", "--recursive"]) },
95
+ pwd: { noPositional: true },
96
+ cat: { short: flags("n") },
97
+ head: { short: { n: "num", c: "num" }, digits: true },
98
+ tail: { short: { n: "num", c: "num" }, digits: true },
99
+ wc: { short: flags("lwcm") },
100
+ grep: GREP, egrep: GREP, fgrep: GREP,
101
+ rg: {
102
+ short: flags("inlwcFSuvHo", { g: "text", t: "text", e: "text", m: "num", A: "num", B: "num", C: "num" }),
103
+ long: longFlags(["--hidden", "--files", "--fixed-strings", "--ignore-case", "--line-number", "--count", "--files-with-matches", "--no-ignore"], { "--glob": "text", "--type": "text" }),
104
+ },
105
+ find: { long: longFlags(["-print", "-empty"], { "-name": "text", "-iname": "text", "-type": "text", "-maxdepth": "num", "-mindepth": "num" }) },
106
+ tree: { short: flags("adf", { L: "num" }) },
107
+ file: { short: flags("b") },
108
+ stat: {},
109
+ diff: { short: flags("uqrN") },
110
+ which: { short: flags("a") },
111
+ echo: { short: flags("n") },
112
+ du: { short: flags("sh", { d: "num" }) },
113
+ true: { noPositional: true },
114
+ };
115
+ /** Read-only git subcommands and their options. Any other git subcommand escalates. */
116
+ const GIT = {
117
+ status: { short: flags("sb"), long: longFlags(["--short", "--branch", "--porcelain"]) },
118
+ log: { short: { n: "num" }, digits: true, long: longFlags(["--oneline", "--decorate", "--graph"], { "--max-count": "num" }) },
119
+ diff: { long: longFlags(["--name-only", "--name-status", "--cached", "--staged", "--no-ext-diff", "--no-textconv"]) },
120
+ show: { long: longFlags(["--name-only", "--name-status", "--oneline", "--no-ext-diff", "--no-textconv"]) },
121
+ "rev-parse": { long: longFlags(["--show-toplevel", "--abbrev-ref", "--short", "--is-inside-work-tree"]) },
122
+ "ls-files": { long: longFlags(["--others", "--exclude-standard", "--cached", "--modified"]) },
123
+ branch: { short: flags("arv"), long: longFlags(["--all", "--remotes", "--verbose", "--show-current"]), noPositional: true },
124
+ "merge-base": {},
125
+ "rev-list": { long: longFlags(["--count"]) },
126
+ describe: { long: longFlags(["--tags", "--always"]) },
127
+ };
128
+ /** Repository git config keys and sections that make read-only git commands run a program. */
129
+ const GIT_CONFIG_HAZARD = /^\s*(?:fsmonitor|external|textconv|pager|sshcommand|hookspath|askpass|editor|clean|smudge|process|command|cmd|helper|program|tool)\s*=|^\s*\[\s*(?:include|includeif|filter|alias)\b/im;
130
+ function hard(reason) {
131
+ return { verdict: "hard", reason };
132
+ }
133
+ function parseObject(text) {
134
+ if (text === null)
135
+ return null;
136
+ try {
137
+ const value = JSON.parse(text);
138
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ /** The nearest ancestor of `cwd` (after resolving symlinks) that holds a `.git` entry. */
145
+ function findRepoRoot(cwd, fsx = realFs) {
146
+ if (!path.isAbsolute(cwd) || !fsx.exists(cwd))
147
+ return null;
148
+ let dir = fsx.realpath(cwd);
149
+ for (;;) {
150
+ if (fsx.exists(path.join(dir, ".git")))
151
+ return dir;
152
+ const parent = path.dirname(dir);
153
+ if (parent === dir)
154
+ return null;
155
+ dir = parent;
156
+ }
157
+ }
158
+ /**
159
+ * The repo-relative path of `target` after resolving `..` and every symlink on the part of the path
160
+ * that exists, or null when it lands outside the repository. A `~` path is never resolved.
161
+ */
162
+ function resolveInsideRepo(repoRoot, cwd, target, fsx = realFs) {
163
+ if (target.startsWith("~"))
164
+ return null;
165
+ let existing = path.resolve(cwd, target);
166
+ const rest = [];
167
+ while (!fsx.exists(existing)) {
168
+ rest.unshift(path.basename(existing));
169
+ existing = path.dirname(existing);
170
+ }
171
+ const resolved = path.join(fsx.realpath(existing), ...rest);
172
+ if (resolved === repoRoot)
173
+ return ".";
174
+ if (!resolved.startsWith(`${repoRoot}${path.sep}`))
175
+ return null;
176
+ return path.relative(repoRoot, resolved);
177
+ }
178
+ function protectedPath(relative) {
179
+ const segments = relative.split(path.sep).map((segment) => segment.toLowerCase());
180
+ const protectedSegment = segments.find((segment) => PROTECTED_SEGMENTS.has(segment));
181
+ if (protectedSegment)
182
+ return `touches ${protectedSegment}`;
183
+ const name = segments[segments.length - 1];
184
+ if (PROTECTED_NAMES.has(name))
185
+ return `touches ${name}`;
186
+ if (name.startsWith(".env"))
187
+ return "touches an environment file";
188
+ return null;
189
+ }
190
+ /**
191
+ * Shell words for a single simple command, or null when quoting is unbalanced. `globbed` is true when
192
+ * a glob character appears outside quotes. Escapes and expansions are refused before this runs.
193
+ */
194
+ function tokenizeCommand(command) {
195
+ const tokens = [];
196
+ let current = "";
197
+ let started = false;
198
+ let quote = null;
199
+ let globbed = false;
200
+ for (const char of command) {
201
+ if (quote) {
202
+ if (char === quote)
203
+ quote = null;
204
+ else
205
+ current += char;
206
+ continue;
207
+ }
208
+ if (char === "'" || char === "\"") {
209
+ quote = char;
210
+ started = true;
211
+ continue;
212
+ }
213
+ if (/\s/.test(char)) {
214
+ if (started)
215
+ tokens.push(current);
216
+ current = "";
217
+ started = false;
218
+ continue;
219
+ }
220
+ if (GLOB.test(char))
221
+ globbed = true;
222
+ current += char;
223
+ started = true;
224
+ }
225
+ if (quote)
226
+ return null;
227
+ if (started)
228
+ tokens.push(current);
229
+ return { tokens, globbed };
230
+ }
231
+ /** Whether repository git config could make a read-only git command run a program. Unreadable config counts. */
232
+ function gitConfigHazard(repoRoot, fsx = realFs) {
233
+ const pointer = fsx.read(path.join(repoRoot, ".git"));
234
+ const gitDir = pointer === null ? path.join(repoRoot, ".git") : path.resolve(repoRoot, pointer.replace(/^gitdir:\s*/, "").trim());
235
+ const common = fsx.read(path.join(gitDir, "commondir"));
236
+ const configs = [path.join(gitDir, "config"), path.join(gitDir, "config.worktree"), ...(common ? [path.join(path.resolve(gitDir, common.trim()), "config")] : [])];
237
+ const texts = configs.map((file) => fsx.read(file));
238
+ if (texts[0] === null && (common === null || texts[2] === null))
239
+ return true;
240
+ return texts.some((text) => text !== null && GIT_CONFIG_HAZARD.test(text));
241
+ }
242
+ function checkWord(repoRoot, cwd, word, fsx) {
243
+ const relative = resolveInsideRepo(repoRoot, cwd, word, fsx);
244
+ return relative === null ? { outside: true, touches: null } : { outside: false, touches: protectedPath(relative) };
245
+ }
246
+ /** Null when every option is on the command's list and every value fits its kind; otherwise the reason. */
247
+ function checkOptions(spec, args, positional) {
248
+ const value = (kind, text, name) => {
249
+ if (text === undefined)
250
+ return `${name} is missing its value`;
251
+ if (kind === "num" ? !/^\d+$/.test(text) : text.includes("/") || text.startsWith("~"))
252
+ return `${name} has a value the floor does not accept`;
253
+ return null;
254
+ };
255
+ let endOfOptions = false;
256
+ for (let index = 0; index < args.length; index += 1) {
257
+ const arg = args[index];
258
+ if (endOfOptions || !arg.startsWith("-") || arg === "-") {
259
+ const reason = spec.noPositional ? "takes no arguments here" : positional(arg);
260
+ if (reason)
261
+ return reason;
262
+ continue;
263
+ }
264
+ if (arg === "--") {
265
+ endOfOptions = true;
266
+ continue;
267
+ }
268
+ const equals = arg.indexOf("=");
269
+ const name = equals > 0 ? arg.slice(0, equals) : arg;
270
+ const longKind = spec.long?.[name];
271
+ if (longKind) {
272
+ if (longKind === "flag") {
273
+ if (equals > 0)
274
+ return `${name} takes no value`;
275
+ continue;
276
+ }
277
+ const reason = value(longKind, equals > 0 ? arg.slice(equals + 1) : args[++index], name);
278
+ if (reason)
279
+ return reason;
280
+ continue;
281
+ }
282
+ if (arg.startsWith("--"))
283
+ return `${name} is not on the allowlist`;
284
+ if (spec.digits && /^-\d+$/.test(arg))
285
+ continue;
286
+ for (let at = 1; at < arg.length; at += 1) {
287
+ const letter = arg[at];
288
+ const kind = spec.short?.[letter];
289
+ if (!kind)
290
+ return `-${letter} is not on the allowlist`;
291
+ if (kind === "flag")
292
+ continue;
293
+ const attached = arg.slice(at + 1);
294
+ const reason = value(kind, attached || args[++index], `-${letter}`);
295
+ if (reason)
296
+ return reason;
297
+ break;
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+ function bashVerdict(command, repoRoot, cwd, fsx) {
303
+ if (typeof command !== "string" || !command.trim())
304
+ return hard("no command to judge");
305
+ if (command.length > MAX_COMMAND_CHARS)
306
+ return hard("command is too long to judge");
307
+ if (SHELL_CONTROL.test(command))
308
+ return hard("command chains, pipes, redirects, substitutes or expands");
309
+ const parsed = tokenizeCommand(command);
310
+ if (!parsed || parsed.tokens.length === 0)
311
+ return hard("command quoting does not parse");
312
+ if (parsed.globbed)
313
+ return hard("command has an unquoted glob");
314
+ const tokens = parsed.tokens;
315
+ const [program, ...args] = tokens;
316
+ const name = program.toLowerCase();
317
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(program))
318
+ return hard("command sets environment variables");
319
+ if (HARD_COMMANDS.has(name))
320
+ return hard(`${name} is never answered for the human`);
321
+ // Every word, and every value glued to an option, must stay inside the repository; credential paths escalate.
322
+ let touches = null;
323
+ for (const arg of args) {
324
+ const values = arg.startsWith("-") ? [arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : arg.slice(2)] : [arg];
325
+ for (const word of values.filter((entry) => entry.includes("/") || entry.startsWith("~") || !arg.startsWith("-"))) {
326
+ const check = checkWord(repoRoot, cwd, word, fsx);
327
+ if (check.outside)
328
+ return hard("command reaches outside the repository");
329
+ if (!arg.startsWith("-"))
330
+ touches ??= check.touches;
331
+ }
332
+ }
333
+ if (touches && CREDENTIAL_TOUCHES.has(touches))
334
+ return hard(`command ${touches}`);
335
+ const shape = { repoRoot, tool: "Bash", tokens };
336
+ if (TEST_COMMANDS.some((entry) => entry.length === tokens.length && entry.every((token, index) => token === tokens[index])))
337
+ return { verdict: "allow", reason: "runs the repository's own checks", shape };
338
+ if (name === "git") {
339
+ const subcommand = args[0] ?? "";
340
+ const spec = GIT[subcommand];
341
+ if (!spec)
342
+ return hard(`git ${subcommand || "with global options"} is never answered for the human`);
343
+ if (gitConfigHazard(repoRoot, fsx))
344
+ return hard("the repository's git config can run programs");
345
+ const reason = checkOptions(spec, args.slice(1), () => null);
346
+ return reason ? hard(`git ${subcommand}: ${reason}`) : { verdict: "allow", reason: `git ${subcommand} only reads`, shape };
347
+ }
348
+ const spec = COMMANDS[name];
349
+ if (spec && !program.includes("/")) {
350
+ const reason = checkOptions(spec, args, () => null);
351
+ return reason ? hard(`${name}: ${reason}`) : { verdict: "allow", reason: `${name} only reads`, shape };
352
+ }
353
+ if (PACKAGE_MANAGERS.has(name)) {
354
+ const script = (args[0] === "run" && args.length === 2) || (args[0] === "test" && args.length === 1);
355
+ if (!script)
356
+ return hard(`${name} ${args[0] ?? ""} is never answered for the human`.trim());
357
+ }
358
+ if ((INTERPRETERS.has(name) || /^python\d/.test(name)) && args.some((arg) => arg.startsWith("-")))
359
+ return hard(`${name} with options can run inline code`);
360
+ if (touches)
361
+ return hard(`command ${touches}`);
362
+ return { verdict: "soft", reason: program.includes("/") ? "runs a program by path" : `${name} is not on the allowlist`, shape };
363
+ }
364
+ function pathVerdict(tool, input, repoRoot, cwd, fsx) {
365
+ const raw = input.file_path ?? input.notebook_path ?? input.path ?? (READ_TOOLS.has(tool) ? cwd : undefined);
366
+ if (typeof raw !== "string" || !raw.trim())
367
+ return hard(`${tool} names no path`);
368
+ const relative = resolveInsideRepo(repoRoot, cwd, raw, fsx);
369
+ if (relative === null)
370
+ return hard(`${tool} reaches outside the repository`);
371
+ const blocked = protectedPath(relative);
372
+ if (blocked && (EDIT_TOOLS.has(tool) || CREDENTIAL_TOUCHES.has(blocked)))
373
+ return hard(`${tool} ${blocked}`);
374
+ return { verdict: "allow", reason: EDIT_TOOLS.has(tool) ? `${tool} inside the repository` : `${tool} only reads inside the repository`, shape: { repoRoot, tool, tokens: [relative] } };
375
+ }
376
+ function evaluateFloor(item, fsx = realFs) {
377
+ const verdict = judge(item, fsx);
378
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_floor_judged", message: "judged a cmux Feed item against the floor", meta: { tool: item.toolName, verdict: verdict.verdict, reason: verdict.reason } });
379
+ return verdict;
380
+ }
381
+ function judge(item, fsx) {
382
+ if (item.kind !== "permissionRequest")
383
+ return hard("questions and plan approvals always go to the human");
384
+ if (item.source !== "claude")
385
+ return hard(`${item.source} sessions are observe-only`);
386
+ if (item.toolInputTruncated)
387
+ return hard("the request was truncated");
388
+ const tool = item.toolName;
389
+ if (!tool)
390
+ return hard("the request names no tool");
391
+ if (tool.startsWith("mcp__"))
392
+ return hard("MCP tools always go to the human");
393
+ if (!item.cwd)
394
+ return hard("the request has no working directory");
395
+ const repoRoot = findRepoRoot(item.cwd, fsx);
396
+ if (!repoRoot)
397
+ return hard("the working directory is not inside a repository");
398
+ const cwd = fsx.realpath(item.cwd);
399
+ const input = parseObject(item.toolInput);
400
+ if (!input)
401
+ return hard("the request input is not readable");
402
+ if (tool === "Bash")
403
+ return bashVerdict(input.command, repoRoot, cwd, fsx);
404
+ if (EDIT_TOOLS.has(tool) || READ_TOOLS.has(tool))
405
+ return pathVerdict(tool, input, repoRoot, cwd, fsx);
406
+ return hard(`${tool} always goes to the human`);
407
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SEED_CMUX_PRINCIPLES = exports.CMUX_PRINCIPLES_FILE = void 0;
37
+ exports.readCmuxPrinciples = readCmuxPrinciples;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ /**
42
+ * Judgment guidance for coding-agent prompts, written by the human. It is guidance only: it never
43
+ * authorizes a reply. Live replies come from the code floor, the human's exact precedents and a
44
+ * standing owner grant. The agent reads these when it judges an escalated prompt.
45
+ *
46
+ * The bundle may hold its own `cmux-principles.md` (synced, human-authored); otherwise the seed
47
+ * below applies. The seed is drawn from the operator's standing desk instructions.
48
+ */
49
+ exports.CMUX_PRINCIPLES_FILE = "cmux-principles.md";
50
+ exports.SEED_CMUX_PRINCIPLES = [
51
+ "Keep routine work moving: a reversible step inside the session's own repository is the kind of thing the human wants answered without being woken.",
52
+ "Never act in the human's voice: anything that sends, posts, comments, merges or messages as them goes to them.",
53
+ "Never cause loss that cannot be undone: deleting files, force-pushing, rewriting shared history or wiping data goes to the human.",
54
+ "Credentials, sign-in, two-factor codes, payments and accepting terms always belong to the human.",
55
+ "Stay in scope: actions outside the session's repository, or beyond the task the session was given, go to the human.",
56
+ "Never widen permissions: no 'always', 'all' or bypass answers, and no edits to agent settings, hooks or CI.",
57
+ "Production, publishing and shared infrastructure go to the human unless they granted that exact action.",
58
+ "When unsure, ask: a blocked routine prompt costs the human a minute; a wrong approval can cost far more.",
59
+ ];
60
+ function readCmuxPrinciples(agentRoot) {
61
+ try {
62
+ const text = fs.readFileSync(path.join(agentRoot, exports.CMUX_PRINCIPLES_FILE), "utf-8");
63
+ const principles = text.split("\n").map((line) => line.replace(/^\s*(?:[-*]|\d+[.)])\s+/, "").trim()).filter((line) => line && !line.startsWith("#"));
64
+ if (principles.length > 0)
65
+ return { source: "bundle", principles };
66
+ }
67
+ catch {
68
+ // No bundle file: use the seed.
69
+ }
70
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_principles_seeded", message: "using the seed cmux principles", meta: { agentRoot } });
71
+ return { source: "seed", principles: [...exports.SEED_CMUX_PRINCIPLES] };
72
+ }