@pikaa-ai/pikaa 0.3.6 → 0.3.8
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/bin/pikaa.js +3 -3
- package/dist/cli.js +460 -128
- package/dist/index.js +96 -4
- package/package.json +1 -1
- package/templates/base/groupy_prompt.md +67 -1
package/bin/pikaa.js
CHANGED
|
@@ -26,10 +26,10 @@ const rootDir = join(__dirname, "..");
|
|
|
26
26
|
const platform = process.platform;
|
|
27
27
|
const arch = process.arch;
|
|
28
28
|
|
|
29
|
-
let version = "
|
|
29
|
+
let version = "";
|
|
30
30
|
try {
|
|
31
31
|
const pkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
|
|
32
|
-
version = pkg.version;
|
|
32
|
+
version = pkg.version || "";
|
|
33
33
|
} catch {}
|
|
34
34
|
|
|
35
35
|
function getBinaryName() {
|
|
@@ -41,7 +41,7 @@ function getBinaryName() {
|
|
|
41
41
|
|
|
42
42
|
const binaryName = getBinaryName();
|
|
43
43
|
const userBinDir = join(homedir(), ".pikaa", "bin");
|
|
44
|
-
const userBinaryPath = binaryName ? join(userBinDir, `pikaa-v${version}-${binaryName}`) : null;
|
|
44
|
+
const userBinaryPath = binaryName && version ? join(userBinDir, `pikaa-v${version}-${binaryName}`) : null;
|
|
45
45
|
|
|
46
46
|
// Look in package directory or user cache directory
|
|
47
47
|
function findExistingBinary() {
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
|
-
import { resolve as
|
|
6
|
-
import { existsSync as
|
|
5
|
+
import { resolve as resolve21 } from "path";
|
|
6
|
+
import { existsSync as existsSync19 } from "fs";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
|
|
9
9
|
// src/auth/store.ts
|
|
@@ -975,6 +975,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
975
975
|
signal,
|
|
976
976
|
execPolicy: session.execPolicy,
|
|
977
977
|
mode: session.collaborationMode,
|
|
978
|
+
permissionMode: session.permissionMode,
|
|
978
979
|
onPlanUpdate: (plan, explanation) => {
|
|
979
980
|
session.emitEvent({
|
|
980
981
|
type: "PlanUpdated",
|
|
@@ -1125,9 +1126,17 @@ async function submissionLoop(session, queue) {
|
|
|
1125
1126
|
// src/security/exec-policy.ts
|
|
1126
1127
|
class ExecPolicy {
|
|
1127
1128
|
rules = [];
|
|
1128
|
-
|
|
1129
|
+
mode = "auto";
|
|
1130
|
+
constructor(initialMode = "auto") {
|
|
1131
|
+
this.mode = initialMode;
|
|
1129
1132
|
this.initDefaultRules();
|
|
1130
1133
|
}
|
|
1134
|
+
getMode() {
|
|
1135
|
+
return this.mode;
|
|
1136
|
+
}
|
|
1137
|
+
setMode(mode) {
|
|
1138
|
+
this.mode = mode;
|
|
1139
|
+
}
|
|
1131
1140
|
initDefaultRules() {
|
|
1132
1141
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
1133
1142
|
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
@@ -1139,8 +1148,47 @@ class ExecPolicy {
|
|
|
1139
1148
|
addRule(pattern, decision, description) {
|
|
1140
1149
|
this.rules.unshift({ pattern, decision, description });
|
|
1141
1150
|
}
|
|
1151
|
+
shouldPromptFileEdit(filePath) {
|
|
1152
|
+
if (this.mode === "plan") {
|
|
1153
|
+
return {
|
|
1154
|
+
prompt: false,
|
|
1155
|
+
isPlanBlocked: true,
|
|
1156
|
+
reason: "Plan Mode is active. Mutating files is not allowed while planning."
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
if (this.mode === "manual") {
|
|
1160
|
+
return {
|
|
1161
|
+
prompt: true,
|
|
1162
|
+
reason: `Manual mode requires approval to modify '${filePath || "file"}'`
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return { prompt: false };
|
|
1166
|
+
}
|
|
1142
1167
|
evaluate(command) {
|
|
1143
1168
|
const trimmed = command.trim();
|
|
1169
|
+
if (this.mode === "plan") {
|
|
1170
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1171
|
+
if (isReadOnly) {
|
|
1172
|
+
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1173
|
+
}
|
|
1174
|
+
return { decision: "deny", reason: "Cannot execute mutating shell commands in Plan mode" };
|
|
1175
|
+
}
|
|
1176
|
+
if (this.mode === "manual") {
|
|
1177
|
+
return {
|
|
1178
|
+
decision: "prompt",
|
|
1179
|
+
reason: "Manual mode requires confirmation for all shell commands"
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
if (this.mode === "accept-edits") {
|
|
1183
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
|
|
1184
|
+
if (isReadOnly) {
|
|
1185
|
+
return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
decision: "prompt",
|
|
1189
|
+
reason: "Accept-edits mode requires approval for active shell commands"
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1144
1192
|
for (const rule of this.rules) {
|
|
1145
1193
|
if (rule.pattern.test(trimmed)) {
|
|
1146
1194
|
return {
|
|
@@ -1150,8 +1198,8 @@ class ExecPolicy {
|
|
|
1150
1198
|
}
|
|
1151
1199
|
}
|
|
1152
1200
|
return {
|
|
1153
|
-
decision: "
|
|
1154
|
-
reason: "
|
|
1201
|
+
decision: "allow",
|
|
1202
|
+
reason: "Auto mode allows execution"
|
|
1155
1203
|
};
|
|
1156
1204
|
}
|
|
1157
1205
|
}
|
|
@@ -1169,6 +1217,17 @@ class Session {
|
|
|
1169
1217
|
mcpManager;
|
|
1170
1218
|
execPolicy;
|
|
1171
1219
|
collaborationMode = "default";
|
|
1220
|
+
get permissionMode() {
|
|
1221
|
+
return this.execPolicy.getMode();
|
|
1222
|
+
}
|
|
1223
|
+
setPermissionMode(mode) {
|
|
1224
|
+
this.execPolicy.setMode(mode);
|
|
1225
|
+
if (mode === "plan") {
|
|
1226
|
+
this.collaborationMode = "plan";
|
|
1227
|
+
} else if (this.collaborationMode === "plan") {
|
|
1228
|
+
this.collaborationMode = "default";
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1172
1231
|
history = [];
|
|
1173
1232
|
activeTurn = null;
|
|
1174
1233
|
status = "idle";
|
|
@@ -1415,6 +1474,22 @@ var applyPatchTool = {
|
|
|
1415
1474
|
const filePath = resolve4(ctx.cwd, rawPath);
|
|
1416
1475
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1417
1476
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1477
|
+
if (ctx.execPolicy) {
|
|
1478
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1479
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1480
|
+
return {
|
|
1481
|
+
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1482
|
+
isError: true
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
1486
|
+
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1487
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1488
|
+
if (!allowed) {
|
|
1489
|
+
return { output: `Action rejected by user: apply_patch '${rawPath}'`, isError: true };
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1418
1493
|
if (!existsSync4(filePath)) {
|
|
1419
1494
|
if (targetContent) {
|
|
1420
1495
|
return {
|
|
@@ -2049,7 +2124,24 @@ var writeFileTool = {
|
|
|
2049
2124
|
required: ["path", "content"]
|
|
2050
2125
|
},
|
|
2051
2126
|
async execute(args, ctx) {
|
|
2052
|
-
const
|
|
2127
|
+
const rawPath = String(args.path || "");
|
|
2128
|
+
const filePath = resolve7(ctx.cwd, rawPath);
|
|
2129
|
+
if (ctx.execPolicy) {
|
|
2130
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2131
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2132
|
+
return {
|
|
2133
|
+
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2134
|
+
isError: true
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
2138
|
+
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2139
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2140
|
+
if (!allowed) {
|
|
2141
|
+
return { output: `Action rejected by user: write_file '${rawPath}'`, isError: true };
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2053
2145
|
try {
|
|
2054
2146
|
mkdirSync4(dirname4(filePath), { recursive: true });
|
|
2055
2147
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
@@ -5938,12 +6030,63 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
|
|
|
5938
6030
|
}
|
|
5939
6031
|
return result;
|
|
5940
6032
|
}
|
|
6033
|
+
// package.json
|
|
6034
|
+
var package_default = {
|
|
6035
|
+
name: "@pikaa-ai/pikaa",
|
|
6036
|
+
version: "0.3.8",
|
|
6037
|
+
description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
|
|
6038
|
+
main: "./dist/index.js",
|
|
6039
|
+
module: "./dist/index.js",
|
|
6040
|
+
types: "./dist/index.d.ts",
|
|
6041
|
+
bin: {
|
|
6042
|
+
pikaa: "./bin/pikaa.js",
|
|
6043
|
+
groupy: "./bin/pikaa.js"
|
|
6044
|
+
},
|
|
6045
|
+
type: "module",
|
|
6046
|
+
files: [
|
|
6047
|
+
"dist/index.js",
|
|
6048
|
+
"dist/cli.js",
|
|
6049
|
+
"bin",
|
|
6050
|
+
"templates",
|
|
6051
|
+
"assets",
|
|
6052
|
+
"skills/**/SKILL.md",
|
|
6053
|
+
"README.md",
|
|
6054
|
+
"LICENSE"
|
|
6055
|
+
],
|
|
6056
|
+
scripts: {
|
|
6057
|
+
start: "bun run src/cli/index.ts",
|
|
6058
|
+
test: "bun test --timeout 30000",
|
|
6059
|
+
typecheck: "tsc --noEmit",
|
|
6060
|
+
"build:js": "bun build ./src/index.ts --outdir ./dist --target=bun && bun build ./src/cli/index.ts --outfile ./dist/cli.js --target=bun",
|
|
6061
|
+
"build:exe": "bun build ./src/cli/index.ts --compile --outfile pikaa.exe",
|
|
6062
|
+
"build:binaries": "bun run scripts/build-binaries.ts",
|
|
6063
|
+
"release:prepare": "bun run scripts/prepare-release.ts",
|
|
6064
|
+
build: "bun run build:js && bun run build:exe",
|
|
6065
|
+
prepublishOnly: "bun run build:js"
|
|
6066
|
+
},
|
|
6067
|
+
keywords: [
|
|
6068
|
+
"ai",
|
|
6069
|
+
"coding-agent",
|
|
6070
|
+
"codex",
|
|
6071
|
+
"claude-code",
|
|
6072
|
+
"cli",
|
|
6073
|
+
"agentic",
|
|
6074
|
+
"sub-agents",
|
|
6075
|
+
"mcp",
|
|
6076
|
+
"worktree"
|
|
6077
|
+
],
|
|
6078
|
+
author: "Mesosfer",
|
|
6079
|
+
license: "MIT",
|
|
6080
|
+
devDependencies: {
|
|
6081
|
+
"@types/bun": "latest",
|
|
6082
|
+
"@types/node": "^22.0.0",
|
|
6083
|
+
"@types/react": "^19.2.18",
|
|
6084
|
+
react: "^19.2.8",
|
|
6085
|
+
typescript: "^5.7.0"
|
|
6086
|
+
}
|
|
6087
|
+
};
|
|
5941
6088
|
|
|
5942
6089
|
// src/cli/version.ts
|
|
5943
|
-
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
|
|
5944
|
-
import { dirname as dirname8, join as join8, resolve as resolve17 } from "path";
|
|
5945
|
-
import { fileURLToPath } from "url";
|
|
5946
|
-
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/cli";
|
|
5947
6090
|
var cachedMetadata = null;
|
|
5948
6091
|
function getPackageMetadata() {
|
|
5949
6092
|
if (cachedMetadata) {
|
|
@@ -5951,59 +6094,13 @@ function getPackageMetadata() {
|
|
|
5951
6094
|
}
|
|
5952
6095
|
const envVersion = process.env.PIKAA_VERSION || process.env.GROUPY_VERSION;
|
|
5953
6096
|
const envName = process.env.PIKAA_NAME || process.env.GROUPY_NAME;
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
version: envVersion
|
|
5958
|
-
};
|
|
5959
|
-
return cachedMetadata;
|
|
5960
|
-
}
|
|
5961
|
-
const searchDirs = [];
|
|
5962
|
-
try {
|
|
5963
|
-
const currentDir = typeof __dirname !== "undefined" ? __dirname : dirname8(fileURLToPath(import.meta.url));
|
|
5964
|
-
searchDirs.push(currentDir);
|
|
5965
|
-
} catch {}
|
|
5966
|
-
for (const startDir of searchDirs) {
|
|
5967
|
-
let current = resolve17(startDir);
|
|
5968
|
-
for (let depth = 0;depth < 5; depth++) {
|
|
5969
|
-
const candidate = join8(current, "package.json");
|
|
5970
|
-
if (existsSync17(candidate)) {
|
|
5971
|
-
try {
|
|
5972
|
-
const content = JSON.parse(readFileSync12(candidate, "utf8"));
|
|
5973
|
-
if (content && typeof content.version === "string" && (content.name === "@pikaa-ai/pikaa" || content.name === "pikaa" || content.bin?.pikaa || content.bin?.groupy)) {
|
|
5974
|
-
const cleanName = content.name?.startsWith("@") ? content.name.split("/")[1] || content.name : content.name || "pikaa";
|
|
5975
|
-
cachedMetadata = {
|
|
5976
|
-
name: cleanName,
|
|
5977
|
-
version: content.version,
|
|
5978
|
-
description: content.description
|
|
5979
|
-
};
|
|
5980
|
-
return cachedMetadata;
|
|
5981
|
-
}
|
|
5982
|
-
} catch {}
|
|
5983
|
-
}
|
|
5984
|
-
const parent = dirname8(current);
|
|
5985
|
-
if (parent === current)
|
|
5986
|
-
break;
|
|
5987
|
-
current = parent;
|
|
5988
|
-
}
|
|
5989
|
-
}
|
|
5990
|
-
try {
|
|
5991
|
-
const cwdCandidate = join8(process.cwd(), "package.json");
|
|
5992
|
-
if (existsSync17(cwdCandidate)) {
|
|
5993
|
-
const content = JSON.parse(readFileSync12(cwdCandidate, "utf8"));
|
|
5994
|
-
if (content && typeof content.version === "string" && (content.name === "@pikaa-ai/pikaa" || content.name === "pikaa" || content.bin?.pikaa || content.bin?.groupy)) {
|
|
5995
|
-
cachedMetadata = {
|
|
5996
|
-
name: "pikaa",
|
|
5997
|
-
version: content.version,
|
|
5998
|
-
description: content.description
|
|
5999
|
-
};
|
|
6000
|
-
return cachedMetadata;
|
|
6001
|
-
}
|
|
6002
|
-
}
|
|
6003
|
-
} catch {}
|
|
6097
|
+
const rawName = package_default?.name || "pikaa";
|
|
6098
|
+
const cleanName = rawName.startsWith("@") ? rawName.split("/")[1] || rawName : rawName;
|
|
6099
|
+
const autoVersion = package_default && typeof package_default.version === "string" ? package_default.version : "0.0.0";
|
|
6004
6100
|
cachedMetadata = {
|
|
6005
|
-
name:
|
|
6006
|
-
version:
|
|
6101
|
+
name: envName || cleanName,
|
|
6102
|
+
version: envVersion || autoVersion,
|
|
6103
|
+
description: package_default?.description
|
|
6007
6104
|
};
|
|
6008
6105
|
return cachedMetadata;
|
|
6009
6106
|
}
|
|
@@ -6445,10 +6542,41 @@ ${preview}${more}`);
|
|
|
6445
6542
|
console.log();
|
|
6446
6543
|
}
|
|
6447
6544
|
static formatClaudeUserPrompt(text) {
|
|
6448
|
-
const cols = typeof process.stdout?.columns === "number" ? process.stdout.columns : 80;
|
|
6449
|
-
const
|
|
6450
|
-
const
|
|
6451
|
-
|
|
6545
|
+
const cols = typeof process.stdout?.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
6546
|
+
const boxWidth = Math.max(20, cols - 2);
|
|
6547
|
+
const contentWidth = Math.max(10, boxWidth - 3);
|
|
6548
|
+
const BG = "\x1B[48;2;43;43;45m";
|
|
6549
|
+
const CHEVRON = "\x1B[38;2;128;128;133m";
|
|
6550
|
+
const TEXT = "\x1B[38;2;240;240;242m";
|
|
6551
|
+
const RESET2 = "\x1B[0m";
|
|
6552
|
+
const rawLines = text.split(`
|
|
6553
|
+
`);
|
|
6554
|
+
const wrappedLines = [];
|
|
6555
|
+
for (const rawLine of rawLines) {
|
|
6556
|
+
if (rawLine.length === 0) {
|
|
6557
|
+
wrappedLines.push("");
|
|
6558
|
+
continue;
|
|
6559
|
+
}
|
|
6560
|
+
let current = rawLine;
|
|
6561
|
+
while (current.length > contentWidth) {
|
|
6562
|
+
let breakIdx = current.lastIndexOf(" ", contentWidth);
|
|
6563
|
+
if (breakIdx <= 0) {
|
|
6564
|
+
breakIdx = contentWidth;
|
|
6565
|
+
}
|
|
6566
|
+
wrappedLines.push(current.slice(0, breakIdx));
|
|
6567
|
+
current = current.slice(breakIdx).trimStart();
|
|
6568
|
+
}
|
|
6569
|
+
if (current.length > 0) {
|
|
6570
|
+
wrappedLines.push(current);
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
const formatted = wrappedLines.map((line, idx) => {
|
|
6574
|
+
const prefix = idx === 0 ? `${CHEVRON} \u276F ` : `${CHEVRON} `;
|
|
6575
|
+
const padLen = Math.max(0, contentWidth - line.length);
|
|
6576
|
+
return ` ${BG}${prefix}${TEXT}${line}${" ".repeat(padLen)}${RESET2}`;
|
|
6577
|
+
});
|
|
6578
|
+
return formatted.join(`
|
|
6579
|
+
`);
|
|
6452
6580
|
}
|
|
6453
6581
|
static formatClaudeAssistantResponse(text) {
|
|
6454
6582
|
return ` \x1B[38;2;192;202;245m${text}\x1B[0m`;
|
|
@@ -6481,25 +6609,62 @@ function ensureKeypressInitialized() {
|
|
|
6481
6609
|
class InteractiveLineEditor {
|
|
6482
6610
|
promptSymbol;
|
|
6483
6611
|
cwd;
|
|
6612
|
+
mode;
|
|
6484
6613
|
onInterrupt;
|
|
6614
|
+
onModeChange;
|
|
6485
6615
|
searchEngine = new FileSearchEngine;
|
|
6486
6616
|
constructor(options = {}) {
|
|
6487
6617
|
this.promptSymbol = options.promptSymbol || " \x1B[38;2;192;202;245m\u276F\x1B[0m ";
|
|
6488
6618
|
this.cwd = options.cwd || process.cwd();
|
|
6619
|
+
this.mode = options.initialMode || "auto";
|
|
6489
6620
|
this.onInterrupt = options.onInterrupt;
|
|
6621
|
+
this.onModeChange = options.onModeChange;
|
|
6622
|
+
}
|
|
6623
|
+
getMode() {
|
|
6624
|
+
return this.mode;
|
|
6625
|
+
}
|
|
6626
|
+
setMode(mode) {
|
|
6627
|
+
this.mode = mode;
|
|
6628
|
+
this.onModeChange?.(mode);
|
|
6629
|
+
}
|
|
6630
|
+
cycleMode() {
|
|
6631
|
+
const modes = ["auto", "manual", "accept-edits", "plan"];
|
|
6632
|
+
const nextIdx = (modes.indexOf(this.mode) + 1) % modes.length;
|
|
6633
|
+
const next = modes[nextIdx];
|
|
6634
|
+
this.setMode(next);
|
|
6635
|
+
return next;
|
|
6636
|
+
}
|
|
6637
|
+
cycleModeReverse() {
|
|
6638
|
+
const modes = ["auto", "manual", "accept-edits", "plan"];
|
|
6639
|
+
const nextIdx = (modes.indexOf(this.mode) - 1 + modes.length) % modes.length;
|
|
6640
|
+
const next = modes[nextIdx];
|
|
6641
|
+
this.setMode(next);
|
|
6642
|
+
return next;
|
|
6643
|
+
}
|
|
6644
|
+
getModeLine() {
|
|
6645
|
+
switch (this.mode) {
|
|
6646
|
+
case "auto":
|
|
6647
|
+
return ` \x1B[38;2;255;215;0m\u23F5\u23F5 auto mode on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6648
|
+
case "manual":
|
|
6649
|
+
return ` \x1B[38;2;148;148;148m\u23F8 manual mode on \xB7 ? for shortcuts \xB7 \u21E0 for agents\x1B[0m`;
|
|
6650
|
+
case "accept-edits":
|
|
6651
|
+
return ` \x1B[38;2;175;175;215m\u23F5\u23F5 accept edits on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6652
|
+
case "plan":
|
|
6653
|
+
return ` \x1B[38;2;95;175;175m\u23F8 plan mode on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6654
|
+
}
|
|
6490
6655
|
}
|
|
6491
6656
|
async readLine() {
|
|
6492
6657
|
if (!process.stdin.isTTY) {
|
|
6493
|
-
return new Promise((
|
|
6658
|
+
return new Promise((resolve17) => {
|
|
6494
6659
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6495
6660
|
rl.question(this.promptSymbol, (answer) => {
|
|
6496
6661
|
rl.close();
|
|
6497
|
-
|
|
6662
|
+
resolve17(answer);
|
|
6498
6663
|
});
|
|
6499
6664
|
});
|
|
6500
6665
|
}
|
|
6501
6666
|
ensureKeypressInitialized();
|
|
6502
|
-
return new Promise((
|
|
6667
|
+
return new Promise((resolve17) => {
|
|
6503
6668
|
let buffer = "";
|
|
6504
6669
|
let cursor = 0;
|
|
6505
6670
|
let selectedIndex = 0;
|
|
@@ -6540,6 +6705,14 @@ class InteractiveLineEditor {
|
|
|
6540
6705
|
return [];
|
|
6541
6706
|
}
|
|
6542
6707
|
};
|
|
6708
|
+
const getTerminalCols = () => {
|
|
6709
|
+
return typeof process.stdout?.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
6710
|
+
};
|
|
6711
|
+
const getRule = () => {
|
|
6712
|
+
const cols = getTerminalCols();
|
|
6713
|
+
const ruleLen = Math.max(10, cols - 4);
|
|
6714
|
+
return "\u2500".repeat(ruleLen);
|
|
6715
|
+
};
|
|
6543
6716
|
const ensureVisible = (totalItems, visibleRows) => {
|
|
6544
6717
|
if (totalItems === 0 || visibleRows === 0) {
|
|
6545
6718
|
scrollTop = 0;
|
|
@@ -6567,37 +6740,39 @@ class InteractiveLineEditor {
|
|
|
6567
6740
|
}
|
|
6568
6741
|
};
|
|
6569
6742
|
const redraw = () => {
|
|
6570
|
-
|
|
6743
|
+
process.stdout.write("\x1B[J");
|
|
6571
6744
|
process.stdout.write(`\r\x1B[2K${this.promptSymbol}${buffer}`);
|
|
6572
6745
|
const slashMatches = getMatchingCommands();
|
|
6573
6746
|
const activeFile = getActiveFileQuery();
|
|
6574
6747
|
const fileMatches = activeFile ? getMatchingFiles(activeFile.query) : [];
|
|
6575
6748
|
if (buffer.startsWith("/") && !popupDismissed && slashMatches.length > 0) {
|
|
6576
|
-
const
|
|
6749
|
+
const termCols = getTerminalCols();
|
|
6750
|
+
const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
|
|
6577
6751
|
const maxVisible = Math.min(slashMatches.length, 7);
|
|
6578
6752
|
ensureVisible(slashMatches.length, maxVisible);
|
|
6579
6753
|
const visibleMatches = slashMatches.slice(scrollTop, scrollTop + maxVisible);
|
|
6580
6754
|
const menuLines = [];
|
|
6581
|
-
const rule = "\u2500".repeat(
|
|
6582
|
-
const
|
|
6755
|
+
const rule = "\u2500".repeat(BOX_WIDTH);
|
|
6756
|
+
const RULE_COLOR2 = "\x1B[38;2;80;80;88m";
|
|
6583
6757
|
const ACTIVE_COLOR = "\x1B[38;2;225;225;225m";
|
|
6584
6758
|
const INACTIVE_COLOR = "\x1B[38;2;139;139;144m";
|
|
6585
6759
|
const RESET2 = "\x1B[0m";
|
|
6586
|
-
menuLines.push(` ${
|
|
6760
|
+
menuLines.push(` ${RULE_COLOR2}${rule}${RESET2}`);
|
|
6761
|
+
const maxDescLen = Math.max(10, BOX_WIDTH - 22);
|
|
6587
6762
|
for (let i = 0;i < visibleMatches.length; i++) {
|
|
6588
6763
|
const cmd = visibleMatches[i];
|
|
6589
6764
|
const actualIdx = scrollTop + i;
|
|
6590
6765
|
const isSelected = actualIdx === selectedIndex;
|
|
6591
6766
|
const marker = isSelected ? `${ACTIVE_COLOR}\u276F${RESET2}` : " ";
|
|
6592
6767
|
const rawName = cmd.name.padEnd(16).slice(0, 16);
|
|
6593
|
-
const rawDesc = cmd.description.length >
|
|
6768
|
+
const rawDesc = cmd.description.length > maxDescLen ? cmd.description.slice(0, maxDescLen - 3) + "..." : cmd.description;
|
|
6594
6769
|
if (isSelected) {
|
|
6595
6770
|
menuLines.push(` ${marker} \x1B[1m${ACTIVE_COLOR}${rawName}${RESET2} \x1B[1m${ACTIVE_COLOR}${rawDesc}${RESET2}`);
|
|
6596
6771
|
} else {
|
|
6597
6772
|
menuLines.push(` ${marker} ${INACTIVE_COLOR}${rawName}${RESET2} ${INACTIVE_COLOR}${rawDesc}${RESET2}`);
|
|
6598
6773
|
}
|
|
6599
6774
|
}
|
|
6600
|
-
menuLines.push(` ${
|
|
6775
|
+
menuLines.push(` ${RULE_COLOR2}${rule}${RESET2}`);
|
|
6601
6776
|
for (const line of menuLines) {
|
|
6602
6777
|
process.stdout.write(`
|
|
6603
6778
|
\x1B[2K${line}`);
|
|
@@ -6605,18 +6780,20 @@ class InteractiveLineEditor {
|
|
|
6605
6780
|
renderedMenuLines = menuLines.length;
|
|
6606
6781
|
process.stdout.write(`\x1B[${renderedMenuLines}A`);
|
|
6607
6782
|
} else if (activeFile && !popupDismissed && fileMatches.length > 0) {
|
|
6608
|
-
const
|
|
6783
|
+
const termCols = getTerminalCols();
|
|
6784
|
+
const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
|
|
6609
6785
|
const maxVisible = Math.min(fileMatches.length, 7);
|
|
6610
6786
|
ensureVisible(fileMatches.length, maxVisible);
|
|
6611
6787
|
const visibleMatches = fileMatches.slice(scrollTop, scrollTop + maxVisible);
|
|
6612
6788
|
const menuLines = [];
|
|
6613
6789
|
menuLines.push(` ${style.dim("\u250C\u2500\u2500")} ${style.brandBold("Files")} ${style.dim("\u2500".repeat(Math.max(10, BOX_WIDTH - 9)) + "\u2510")}`);
|
|
6790
|
+
const maxPathLen = Math.max(10, BOX_WIDTH - 8);
|
|
6614
6791
|
for (let i = 0;i < visibleMatches.length; i++) {
|
|
6615
6792
|
const filePath = visibleMatches[i];
|
|
6616
6793
|
const actualIdx = scrollTop + i;
|
|
6617
6794
|
const isSelected = actualIdx === selectedIndex;
|
|
6618
6795
|
const marker = isSelected ? style.brand("\u276F") : " ";
|
|
6619
|
-
const rawPath = filePath.length >
|
|
6796
|
+
const rawPath = filePath.length > maxPathLen ? "..." + filePath.slice(filePath.length - (maxPathLen - 3)) : filePath.padEnd(maxPathLen);
|
|
6620
6797
|
const coloredPath = isSelected ? style.brandBold(rawPath) : style.cyan(rawPath);
|
|
6621
6798
|
menuLines.push(` ${style.dim("\u2502")} ${marker} ${coloredPath} ${style.dim("\u2502")}`);
|
|
6622
6799
|
}
|
|
@@ -6641,6 +6818,15 @@ class InteractiveLineEditor {
|
|
|
6641
6818
|
}
|
|
6642
6819
|
renderedMenuLines = menuLines.length;
|
|
6643
6820
|
process.stdout.write(`\x1B[${renderedMenuLines}A`);
|
|
6821
|
+
} else {
|
|
6822
|
+
const RULE_COLOR2 = "\x1B[38;2;60;60;68m";
|
|
6823
|
+
const bottomRule = ` ${RULE_COLOR2}${getRule()}\x1B[0m`;
|
|
6824
|
+
const modeLine = this.getModeLine();
|
|
6825
|
+
process.stdout.write(`
|
|
6826
|
+
\x1B[2K${bottomRule}
|
|
6827
|
+
\x1B[2K${modeLine}`);
|
|
6828
|
+
renderedMenuLines = 2;
|
|
6829
|
+
process.stdout.write(`\x1B[2A`);
|
|
6644
6830
|
}
|
|
6645
6831
|
const visiblePromptLength = this.promptSymbol.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
6646
6832
|
const cursorCol = visiblePromptLength + cursor;
|
|
@@ -6650,7 +6836,16 @@ class InteractiveLineEditor {
|
|
|
6650
6836
|
process.stdout.write("\r");
|
|
6651
6837
|
}
|
|
6652
6838
|
};
|
|
6839
|
+
const onResize = () => {
|
|
6840
|
+
redraw();
|
|
6841
|
+
};
|
|
6842
|
+
if (process.stdout && typeof process.stdout.on === "function") {
|
|
6843
|
+
process.stdout.on("resize", onResize);
|
|
6844
|
+
}
|
|
6653
6845
|
const cleanupAndResolve = (result) => {
|
|
6846
|
+
if (process.stdout && typeof process.stdout.removeListener === "function") {
|
|
6847
|
+
process.stdout.removeListener("resize", onResize);
|
|
6848
|
+
}
|
|
6654
6849
|
clearMenu();
|
|
6655
6850
|
process.stdin.removeListener("keypress", onKeypress);
|
|
6656
6851
|
if (process.stdin.isTTY) {
|
|
@@ -6659,14 +6854,14 @@ class InteractiveLineEditor {
|
|
|
6659
6854
|
} catch {}
|
|
6660
6855
|
}
|
|
6661
6856
|
if (result.trim().length > 0 && !result.startsWith("/")) {
|
|
6662
|
-
process.stdout.write(`\r\x1B[2K${CliFormatter.formatClaudeUserPrompt(result)}
|
|
6857
|
+
process.stdout.write(`\x1B[1A\r\x1B[2K${CliFormatter.formatClaudeUserPrompt(result)}
|
|
6663
6858
|
|
|
6664
6859
|
`);
|
|
6665
6860
|
} else {
|
|
6666
|
-
process.stdout.write(
|
|
6861
|
+
process.stdout.write(`\x1B[1A\r\x1B[2K
|
|
6667
6862
|
`);
|
|
6668
6863
|
}
|
|
6669
|
-
|
|
6864
|
+
resolve17(result);
|
|
6670
6865
|
};
|
|
6671
6866
|
const onKeypress = (_str, key) => {
|
|
6672
6867
|
if (!key)
|
|
@@ -6681,6 +6876,9 @@ class InteractiveLineEditor {
|
|
|
6681
6876
|
redraw();
|
|
6682
6877
|
return;
|
|
6683
6878
|
}
|
|
6879
|
+
if (process.stdout && typeof process.stdout.removeListener === "function") {
|
|
6880
|
+
process.stdout.removeListener("resize", onResize);
|
|
6881
|
+
}
|
|
6684
6882
|
clearMenu();
|
|
6685
6883
|
if (process.stdin.isTTY) {
|
|
6686
6884
|
try {
|
|
@@ -6731,6 +6929,12 @@ class InteractiveLineEditor {
|
|
|
6731
6929
|
cleanupAndResolve(buffer);
|
|
6732
6930
|
return;
|
|
6733
6931
|
}
|
|
6932
|
+
const isShiftTab = key.name === "tab" && Boolean(key.shift) || key.name === "backtab" || key.sequence === "\x1B[Z" || _str === "\x1B[Z";
|
|
6933
|
+
if (isShiftTab) {
|
|
6934
|
+
this.cycleMode();
|
|
6935
|
+
redraw();
|
|
6936
|
+
return;
|
|
6937
|
+
}
|
|
6734
6938
|
if (key.name === "tab") {
|
|
6735
6939
|
if (activeFile && !popupDismissed && fileMatches.length > 0) {
|
|
6736
6940
|
const chosen = fileMatches[selectedIndex] || fileMatches[0];
|
|
@@ -6753,8 +6957,14 @@ class InteractiveLineEditor {
|
|
|
6753
6957
|
scrollTop = 0;
|
|
6754
6958
|
popupDismissed = false;
|
|
6755
6959
|
redraw();
|
|
6960
|
+
return;
|
|
6756
6961
|
}
|
|
6757
6962
|
}
|
|
6963
|
+
if (buffer.trim().length === 0) {
|
|
6964
|
+
this.cycleMode();
|
|
6965
|
+
redraw();
|
|
6966
|
+
return;
|
|
6967
|
+
}
|
|
6758
6968
|
return;
|
|
6759
6969
|
}
|
|
6760
6970
|
if (key.name === "up") {
|
|
@@ -6818,7 +7028,11 @@ class InteractiveLineEditor {
|
|
|
6818
7028
|
}
|
|
6819
7029
|
};
|
|
6820
7030
|
process.stdin.on("keypress", onKeypress);
|
|
6821
|
-
|
|
7031
|
+
const RULE_COLOR = "\x1B[38;2;60;60;68m";
|
|
7032
|
+
process.stdout.write(`
|
|
7033
|
+
${RULE_COLOR}${getRule()}\x1B[0m
|
|
7034
|
+
`);
|
|
7035
|
+
redraw();
|
|
6822
7036
|
});
|
|
6823
7037
|
}
|
|
6824
7038
|
}
|
|
@@ -6831,7 +7045,7 @@ async function promptChoice(config) {
|
|
|
6831
7045
|
if (!process.stdin.readable) {
|
|
6832
7046
|
return choices[defaultIndex]?.value ?? choices[0].value;
|
|
6833
7047
|
}
|
|
6834
|
-
return new Promise((
|
|
7048
|
+
return new Promise((resolve17) => {
|
|
6835
7049
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
6836
7050
|
const hint = choices.map((c3) => c3.isDefault ? `[${c3.key.toUpperCase()}]` : `[${c3.key}]`).join("/");
|
|
6837
7051
|
let resolved = false;
|
|
@@ -6839,7 +7053,7 @@ async function promptChoice(config) {
|
|
|
6839
7053
|
if (!resolved) {
|
|
6840
7054
|
resolved = true;
|
|
6841
7055
|
rl.close();
|
|
6842
|
-
|
|
7056
|
+
resolve17(val);
|
|
6843
7057
|
}
|
|
6844
7058
|
};
|
|
6845
7059
|
rl.question(`${message} ${hint}: `, (answer) => {
|
|
@@ -6852,7 +7066,7 @@ async function promptChoice(config) {
|
|
|
6852
7066
|
});
|
|
6853
7067
|
});
|
|
6854
7068
|
}
|
|
6855
|
-
return new Promise((
|
|
7069
|
+
return new Promise((resolve17) => {
|
|
6856
7070
|
let selectedIndex = defaultIndex;
|
|
6857
7071
|
if (selectedIndex < 0 || selectedIndex >= choices.length)
|
|
6858
7072
|
selectedIndex = 0;
|
|
@@ -6881,7 +7095,7 @@ async function promptChoice(config) {
|
|
|
6881
7095
|
} catch {}
|
|
6882
7096
|
process.stdout.write(`\r\x1B[1A\r\x1B[2K ${style.bold(message)} ${style.cyan(`[${confirmedChoice.key}] ${confirmedChoice.label}`)}
|
|
6883
7097
|
\r\x1B[2K`);
|
|
6884
|
-
|
|
7098
|
+
resolve17(confirmedChoice.value);
|
|
6885
7099
|
};
|
|
6886
7100
|
const onKeypress = (_str, key) => {
|
|
6887
7101
|
if (!key)
|
|
@@ -6956,7 +7170,7 @@ async function promptToolApproval(params) {
|
|
|
6956
7170
|
renderCard(1);
|
|
6957
7171
|
return "yes";
|
|
6958
7172
|
}
|
|
6959
|
-
return new Promise((
|
|
7173
|
+
return new Promise((resolve17) => {
|
|
6960
7174
|
let selectedIndex = 1;
|
|
6961
7175
|
readline2.emitKeypressEvents(process.stdin);
|
|
6962
7176
|
const wasRaw = process.stdin.isRaw;
|
|
@@ -6973,7 +7187,7 @@ async function promptToolApproval(params) {
|
|
|
6973
7187
|
process.stdin.setRawMode(wasRaw ?? false);
|
|
6974
7188
|
} catch {}
|
|
6975
7189
|
console.log();
|
|
6976
|
-
|
|
7190
|
+
resolve17(val);
|
|
6977
7191
|
};
|
|
6978
7192
|
const onKeypress = (_str, key) => {
|
|
6979
7193
|
if (!key)
|
|
@@ -7056,7 +7270,7 @@ async function promptUserQuestion(params) {
|
|
|
7056
7270
|
if (!process.stdin.isTTY || false || !process.stdin.readable) {
|
|
7057
7271
|
return options[0] || "yes";
|
|
7058
7272
|
}
|
|
7059
|
-
return new Promise((
|
|
7273
|
+
return new Promise((resolve17) => {
|
|
7060
7274
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
7061
7275
|
const promptLabel = options.length > 0 ? `Select [1-${options.length}] or type custom response: ` : `Your response: `;
|
|
7062
7276
|
rl.question(` ${style.bold(promptLabel)}`, (answer) => {
|
|
@@ -7066,7 +7280,7 @@ async function promptUserQuestion(params) {
|
|
|
7066
7280
|
const fallback = options[0] || "";
|
|
7067
7281
|
console.log(style.dim(` \u21B3 Default: ${fallback || "(empty)"}
|
|
7068
7282
|
`));
|
|
7069
|
-
|
|
7283
|
+
resolve17(fallback);
|
|
7070
7284
|
return;
|
|
7071
7285
|
}
|
|
7072
7286
|
const num = parseInt(trimmed, 10);
|
|
@@ -7074,26 +7288,26 @@ async function promptUserQuestion(params) {
|
|
|
7074
7288
|
const picked = options[num - 1];
|
|
7075
7289
|
console.log(style.green(` \u2714 Selected: ${picked}
|
|
7076
7290
|
`));
|
|
7077
|
-
|
|
7291
|
+
resolve17(picked);
|
|
7078
7292
|
return;
|
|
7079
7293
|
}
|
|
7080
7294
|
if (options.length === 2) {
|
|
7081
7295
|
if (/^(y|yes)$/i.test(trimmed)) {
|
|
7082
7296
|
console.log(style.green(` \u2714 Selected: ${options[0]}
|
|
7083
7297
|
`));
|
|
7084
|
-
|
|
7298
|
+
resolve17(options[0]);
|
|
7085
7299
|
return;
|
|
7086
7300
|
}
|
|
7087
7301
|
if (/^(n|no)$/i.test(trimmed)) {
|
|
7088
7302
|
console.log(style.green(` \u2714 Selected: ${options[1]}
|
|
7089
7303
|
`));
|
|
7090
|
-
|
|
7304
|
+
resolve17(options[1]);
|
|
7091
7305
|
return;
|
|
7092
7306
|
}
|
|
7093
7307
|
}
|
|
7094
7308
|
console.log(style.green(` \u2714 Answer: ${trimmed}
|
|
7095
7309
|
`));
|
|
7096
|
-
|
|
7310
|
+
resolve17(trimmed);
|
|
7097
7311
|
});
|
|
7098
7312
|
});
|
|
7099
7313
|
}
|
|
@@ -7122,7 +7336,7 @@ async function promptInteractiveList(config) {
|
|
|
7122
7336
|
`);
|
|
7123
7337
|
return { selectedIndex: -1, action: "close" };
|
|
7124
7338
|
}
|
|
7125
|
-
return new Promise((
|
|
7339
|
+
return new Promise((resolve17) => {
|
|
7126
7340
|
let selectedIndex = Math.max(0, Math.min(defaultIndex, items.length - 1));
|
|
7127
7341
|
let scrollTop = 0;
|
|
7128
7342
|
let renderedLines = 0;
|
|
@@ -7214,7 +7428,7 @@ async function promptInteractiveList(config) {
|
|
|
7214
7428
|
try {
|
|
7215
7429
|
process.stdin.setRawMode(wasRaw ?? false);
|
|
7216
7430
|
} catch {}
|
|
7217
|
-
|
|
7431
|
+
resolve17(res);
|
|
7218
7432
|
};
|
|
7219
7433
|
const onKeypress = async (_str, key) => {
|
|
7220
7434
|
if (!key)
|
|
@@ -7285,8 +7499,8 @@ async function promptInteractiveList(config) {
|
|
|
7285
7499
|
}
|
|
7286
7500
|
|
|
7287
7501
|
// src/security/scanner.ts
|
|
7288
|
-
import { existsSync as
|
|
7289
|
-
import { join as
|
|
7502
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
7503
|
+
import { join as join8, relative as relative2, resolve as resolve17 } from "path";
|
|
7290
7504
|
var SECURITY_RULES = [
|
|
7291
7505
|
{
|
|
7292
7506
|
id: "SEC-001",
|
|
@@ -7418,13 +7632,13 @@ var IGNORED_FILES = new Set([
|
|
|
7418
7632
|
]);
|
|
7419
7633
|
async function runSecurityScan(targetDir, options = {}) {
|
|
7420
7634
|
const startTime = performance.now();
|
|
7421
|
-
const root =
|
|
7635
|
+
const root = resolve17(targetDir);
|
|
7422
7636
|
const isDirectTestDir = targetDir.includes("test") || Boolean(options.includeTests);
|
|
7423
7637
|
const maxFiles = options.maxFiles || 2000;
|
|
7424
7638
|
const findings = [];
|
|
7425
7639
|
let scannedCount = 0;
|
|
7426
7640
|
function walk(current) {
|
|
7427
|
-
if (scannedCount >= maxFiles || !
|
|
7641
|
+
if (scannedCount >= maxFiles || !existsSync17(current))
|
|
7428
7642
|
return;
|
|
7429
7643
|
let entries;
|
|
7430
7644
|
try {
|
|
@@ -7435,7 +7649,7 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
7435
7649
|
for (const entry of entries) {
|
|
7436
7650
|
if (scannedCount >= maxFiles)
|
|
7437
7651
|
break;
|
|
7438
|
-
const fullPath =
|
|
7652
|
+
const fullPath = join8(current, entry);
|
|
7439
7653
|
let stat;
|
|
7440
7654
|
try {
|
|
7441
7655
|
stat = statSync3(fullPath);
|
|
@@ -7466,7 +7680,7 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
7466
7680
|
function scanFile(filePath, baseRoot, ext, out) {
|
|
7467
7681
|
let content;
|
|
7468
7682
|
try {
|
|
7469
|
-
content =
|
|
7683
|
+
content = readFileSync12(filePath, "utf8");
|
|
7470
7684
|
} catch {
|
|
7471
7685
|
return;
|
|
7472
7686
|
}
|
|
@@ -7513,34 +7727,34 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
7513
7727
|
}
|
|
7514
7728
|
|
|
7515
7729
|
// src/mcp/servers/chrome-devtools/index.ts
|
|
7516
|
-
import { resolve as
|
|
7730
|
+
import { resolve as resolve18 } from "path";
|
|
7517
7731
|
|
|
7518
7732
|
// src/mcp/servers/chrome-devtools/server.ts
|
|
7519
7733
|
if (false) {}
|
|
7520
7734
|
|
|
7521
7735
|
// src/mcp/servers/chrome-devtools/index.ts
|
|
7522
7736
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
|
|
7523
|
-
var CHROME_DEVTOOLS_MCP_SERVER_PATH =
|
|
7737
|
+
var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve18(__dirname, "server.ts");
|
|
7524
7738
|
|
|
7525
7739
|
// src/mcp/servers/web-search/index.ts
|
|
7526
|
-
import { resolve as
|
|
7740
|
+
import { resolve as resolve19 } from "path";
|
|
7527
7741
|
|
|
7528
7742
|
// src/mcp/servers/web-search/server.ts
|
|
7529
7743
|
if (false) {}
|
|
7530
7744
|
|
|
7531
7745
|
// src/mcp/servers/web-search/index.ts
|
|
7532
7746
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
|
|
7533
|
-
var WEB_SEARCH_MCP_SERVER_PATH =
|
|
7747
|
+
var WEB_SEARCH_MCP_SERVER_PATH = resolve19(__dirname, "server.ts");
|
|
7534
7748
|
|
|
7535
7749
|
// src/mcp/servers/sqlite/index.ts
|
|
7536
|
-
import { resolve as
|
|
7750
|
+
import { resolve as resolve20 } from "path";
|
|
7537
7751
|
|
|
7538
7752
|
// src/mcp/servers/sqlite/server.ts
|
|
7539
7753
|
if (false) {}
|
|
7540
7754
|
|
|
7541
7755
|
// src/mcp/servers/sqlite/index.ts
|
|
7542
7756
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
7543
|
-
var SQLITE_MCP_SERVER_PATH =
|
|
7757
|
+
var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
|
|
7544
7758
|
|
|
7545
7759
|
// src/cli/commands.ts
|
|
7546
7760
|
var AVAILABLE_SLASH_COMMANDS = [
|
|
@@ -7550,6 +7764,11 @@ var AVAILABLE_SLASH_COMMANDS = [
|
|
|
7550
7764
|
{ name: "/reasoning", description: "Toggle internal reasoning chain visibility" },
|
|
7551
7765
|
{ name: "/login", description: "Authenticate with backend provider" },
|
|
7552
7766
|
{ name: "/whoami", description: "Check backend authentication status" },
|
|
7767
|
+
{ name: "/mode", description: "Cycle or set execution permission mode (auto, manual, accept-edits, plan)" },
|
|
7768
|
+
{ name: "/auto", description: "Switch to Auto Mode (tools execute automatically)" },
|
|
7769
|
+
{ name: "/manual", description: "Switch to Manual Mode (all tools require user confirmation)" },
|
|
7770
|
+
{ name: "/accept-edits", description: "Switch to Accept Edits Mode (file edits auto-approved, shell prompts)" },
|
|
7771
|
+
{ name: "/plan", description: "Switch to Plan Mode (read-only planning, mutations blocked)" },
|
|
7553
7772
|
{ name: "/skills", description: "List domain skills in workspace & global" },
|
|
7554
7773
|
{ name: "/memories", description: "View learned preferences & memories" },
|
|
7555
7774
|
{ name: "/worktrees", description: "List active isolated Git Worktrees" },
|
|
@@ -7601,6 +7820,37 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7601
7820
|
case "/roles":
|
|
7602
7821
|
await handleRolesCommand(ctx, args[0]);
|
|
7603
7822
|
return true;
|
|
7823
|
+
case "/mode":
|
|
7824
|
+
case "/modes":
|
|
7825
|
+
case "/permission":
|
|
7826
|
+
case "/permissions":
|
|
7827
|
+
await handleModeCommand(ctx, args[0]);
|
|
7828
|
+
return true;
|
|
7829
|
+
case "/auto":
|
|
7830
|
+
ctx.session.setPermissionMode("auto");
|
|
7831
|
+
console.log(`
|
|
7832
|
+
\x1B[38;2;255;215;0m\u23F5\u23F5 Switched to Auto Mode (tools execute automatically)\x1B[0m
|
|
7833
|
+
`);
|
|
7834
|
+
return true;
|
|
7835
|
+
case "/manual":
|
|
7836
|
+
ctx.session.setPermissionMode("manual");
|
|
7837
|
+
console.log(`
|
|
7838
|
+
\x1B[38;2;148;148;148m\u23F8 Switched to Manual Mode (all tools require user confirmation)\x1B[0m
|
|
7839
|
+
`);
|
|
7840
|
+
return true;
|
|
7841
|
+
case "/accept-edits":
|
|
7842
|
+
case "/acceptedits":
|
|
7843
|
+
ctx.session.setPermissionMode("accept-edits");
|
|
7844
|
+
console.log(`
|
|
7845
|
+
\x1B[38;2;175;175;215m\u23F5\u23F5 Switched to Accept Edits Mode (file edits auto-approved, shell prompts)\x1B[0m
|
|
7846
|
+
`);
|
|
7847
|
+
return true;
|
|
7848
|
+
case "/plan":
|
|
7849
|
+
ctx.session.setPermissionMode("plan");
|
|
7850
|
+
console.log(`
|
|
7851
|
+
\x1B[38;2;95;175;175m\u23F8 Switched to Plan Mode (read-only planning, mutations blocked)\x1B[0m
|
|
7852
|
+
`);
|
|
7853
|
+
return true;
|
|
7604
7854
|
case "/agents":
|
|
7605
7855
|
printAgents(ctx);
|
|
7606
7856
|
return true;
|
|
@@ -8517,6 +8767,84 @@ function printReleaseNotes() {
|
|
|
8517
8767
|
console.log(" " + ROSE2 + "\u2514" + "\u2500".repeat(totalInnerWidth) + "\u2518" + RESET2);
|
|
8518
8768
|
console.log("");
|
|
8519
8769
|
}
|
|
8770
|
+
async function handleModeCommand(ctx, arg) {
|
|
8771
|
+
const validModes = [
|
|
8772
|
+
{
|
|
8773
|
+
mode: "auto",
|
|
8774
|
+
title: "Auto Mode",
|
|
8775
|
+
desc: "Tools run automatically without confirmation (fastest autonomous workflow)",
|
|
8776
|
+
glyph: "\u23F5\u23F5",
|
|
8777
|
+
color: "\x1B[38;2;255;215;0m"
|
|
8778
|
+
},
|
|
8779
|
+
{
|
|
8780
|
+
mode: "manual",
|
|
8781
|
+
title: "Manual Mode",
|
|
8782
|
+
desc: "Every file edit and shell command asks for user confirmation",
|
|
8783
|
+
glyph: "\u23F8",
|
|
8784
|
+
color: "\x1B[38;2;148;148;148m"
|
|
8785
|
+
},
|
|
8786
|
+
{
|
|
8787
|
+
mode: "accept-edits",
|
|
8788
|
+
title: "Accept Edits Mode",
|
|
8789
|
+
desc: "File edits are auto-approved, but shell commands require approval",
|
|
8790
|
+
glyph: "\u23F5\u23F5",
|
|
8791
|
+
color: "\x1B[38;2;175;175;215m"
|
|
8792
|
+
},
|
|
8793
|
+
{
|
|
8794
|
+
mode: "plan",
|
|
8795
|
+
title: "Plan Mode",
|
|
8796
|
+
desc: "Read-only mode. Blocks all mutations and focuses strictly on planning",
|
|
8797
|
+
glyph: "\u23F8",
|
|
8798
|
+
color: "\x1B[38;2;95;175;175m"
|
|
8799
|
+
}
|
|
8800
|
+
];
|
|
8801
|
+
const currentMode = ctx.session.permissionMode;
|
|
8802
|
+
if (arg) {
|
|
8803
|
+
const normalized = arg.trim().toLowerCase();
|
|
8804
|
+
const match = validModes.find((m) => m.mode === normalized || m.mode.replace("-", "") === normalized);
|
|
8805
|
+
if (match) {
|
|
8806
|
+
ctx.session.setPermissionMode(match.mode);
|
|
8807
|
+
console.log(`
|
|
8808
|
+
${match.color}${match.glyph} Switched to ${match.title}\x1B[0m
|
|
8809
|
+
${style.dim(match.desc)}
|
|
8810
|
+
`);
|
|
8811
|
+
return;
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
if (!process.stdin.isTTY || false || !process.stdin.readable) {
|
|
8815
|
+
console.log(`
|
|
8816
|
+
Current Permission Mode: ${style.bold(currentMode)}`);
|
|
8817
|
+
for (const m of validModes) {
|
|
8818
|
+
const active = m.mode === currentMode ? " (active)" : "";
|
|
8819
|
+
console.log(` \u2022 ${m.title}${active}: ${m.desc}`);
|
|
8820
|
+
}
|
|
8821
|
+
console.log();
|
|
8822
|
+
return;
|
|
8823
|
+
}
|
|
8824
|
+
const items = validModes.map((m) => ({
|
|
8825
|
+
id: m.mode,
|
|
8826
|
+
label: `${m.glyph} ${m.title}`,
|
|
8827
|
+
badge: m.mode === currentMode ? "ACTIVE" : undefined,
|
|
8828
|
+
description: m.desc,
|
|
8829
|
+
checked: m.mode === currentMode
|
|
8830
|
+
}));
|
|
8831
|
+
const res = await promptInteractiveList({
|
|
8832
|
+
title: "\uD83C\uDF9B\uFE0F Select Execution Permission Mode (Shift+Tab in prompt to cycle)",
|
|
8833
|
+
items,
|
|
8834
|
+
mode: "select",
|
|
8835
|
+
customKeyHints: "\u2191/\u2193: navigate \xB7 Enter: switch mode \xB7 Esc: cancel"
|
|
8836
|
+
});
|
|
8837
|
+
if (res.action === "select" && res.selectedItem) {
|
|
8838
|
+
const chosen = validModes.find((m) => m.mode === res.selectedItem?.id);
|
|
8839
|
+
if (chosen) {
|
|
8840
|
+
ctx.session.setPermissionMode(chosen.mode);
|
|
8841
|
+
console.log(`
|
|
8842
|
+
${chosen.color}${chosen.glyph} Switched to ${chosen.title}\x1B[0m
|
|
8843
|
+
${style.dim(chosen.desc)}
|
|
8844
|
+
`);
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8520
8848
|
|
|
8521
8849
|
// src/cli/ui/markdown.ts
|
|
8522
8850
|
class MarkdownHighlighter {
|
|
@@ -8788,9 +9116,9 @@ class MarkdownHighlighter {
|
|
|
8788
9116
|
}
|
|
8789
9117
|
|
|
8790
9118
|
// src/cli/update-checker.ts
|
|
8791
|
-
import { existsSync as
|
|
9119
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
8792
9120
|
import { homedir as homedir9 } from "os";
|
|
8793
|
-
import { join as
|
|
9121
|
+
import { join as join9 } from "path";
|
|
8794
9122
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
8795
9123
|
function parseSemver(v) {
|
|
8796
9124
|
const clean = v.replace(/^v/, "").trim();
|
|
@@ -8811,8 +9139,8 @@ function isNewerVersion(current, remote) {
|
|
|
8811
9139
|
return remPatch > curPatch;
|
|
8812
9140
|
}
|
|
8813
9141
|
function getUpdateCachePath() {
|
|
8814
|
-
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME ||
|
|
8815
|
-
return
|
|
9142
|
+
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join9(homedir9(), ".pikaa");
|
|
9143
|
+
return join9(baseDir, "update-cache.json");
|
|
8816
9144
|
}
|
|
8817
9145
|
async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
|
|
8818
9146
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
@@ -8845,9 +9173,9 @@ async function checkForUpdates(options = {}) {
|
|
|
8845
9173
|
const cachePath = options.cachePath || getUpdateCachePath();
|
|
8846
9174
|
const now = Date.now();
|
|
8847
9175
|
let cached = null;
|
|
8848
|
-
if (!options.force &&
|
|
9176
|
+
if (!options.force && existsSync18(cachePath)) {
|
|
8849
9177
|
try {
|
|
8850
|
-
const raw = JSON.parse(
|
|
9178
|
+
const raw = JSON.parse(readFileSync13(cachePath, "utf8"));
|
|
8851
9179
|
if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
|
|
8852
9180
|
cached = raw;
|
|
8853
9181
|
if (now - cached.lastChecked < CHECK_INTERVAL_MS) {
|
|
@@ -8875,8 +9203,8 @@ async function checkForUpdates(options = {}) {
|
|
|
8875
9203
|
return null;
|
|
8876
9204
|
}
|
|
8877
9205
|
try {
|
|
8878
|
-
const parentDir =
|
|
8879
|
-
if (!
|
|
9206
|
+
const parentDir = join9(cachePath, "..");
|
|
9207
|
+
if (!existsSync18(parentDir)) {
|
|
8880
9208
|
mkdirSync10(parentDir, { recursive: true });
|
|
8881
9209
|
}
|
|
8882
9210
|
const cacheData = {
|
|
@@ -9049,9 +9377,9 @@ class CliRepl {
|
|
|
9049
9377
|
filesModified: Array.from(this.turnFilesModified)
|
|
9050
9378
|
});
|
|
9051
9379
|
if (this.turnDoneResolver) {
|
|
9052
|
-
const
|
|
9380
|
+
const resolve21 = this.turnDoneResolver;
|
|
9053
9381
|
this.turnDoneResolver = undefined;
|
|
9054
|
-
|
|
9382
|
+
resolve21();
|
|
9055
9383
|
}
|
|
9056
9384
|
break;
|
|
9057
9385
|
case "Error":
|
|
@@ -9067,9 +9395,9 @@ class CliRepl {
|
|
|
9067
9395
|
console.error(style.red(`Error: ${msg.message}
|
|
9068
9396
|
`));
|
|
9069
9397
|
if (this.turnDoneResolver) {
|
|
9070
|
-
const
|
|
9398
|
+
const resolve21 = this.turnDoneResolver;
|
|
9071
9399
|
this.turnDoneResolver = undefined;
|
|
9072
|
-
|
|
9400
|
+
resolve21();
|
|
9073
9401
|
}
|
|
9074
9402
|
break;
|
|
9075
9403
|
}
|
|
@@ -9124,6 +9452,10 @@ class CliRepl {
|
|
|
9124
9452
|
}).catch(() => {});
|
|
9125
9453
|
const editor = new InteractiveLineEditor({
|
|
9126
9454
|
cwd: this.session.cwd,
|
|
9455
|
+
initialMode: this.session.permissionMode,
|
|
9456
|
+
onModeChange: (newMode) => {
|
|
9457
|
+
this.session.setPermissionMode(newMode);
|
|
9458
|
+
},
|
|
9127
9459
|
onInterrupt: () => {
|
|
9128
9460
|
if (this.isProcessing) {
|
|
9129
9461
|
const activeTurn = this.session.getActiveTurn();
|
|
@@ -9135,9 +9467,9 @@ class CliRepl {
|
|
|
9135
9467
|
`));
|
|
9136
9468
|
this.isProcessing = false;
|
|
9137
9469
|
if (this.turnDoneResolver) {
|
|
9138
|
-
const
|
|
9470
|
+
const resolve21 = this.turnDoneResolver;
|
|
9139
9471
|
this.turnDoneResolver = undefined;
|
|
9140
|
-
|
|
9472
|
+
resolve21();
|
|
9141
9473
|
}
|
|
9142
9474
|
}
|
|
9143
9475
|
}
|
|
@@ -9169,8 +9501,8 @@ class CliRepl {
|
|
|
9169
9501
|
}
|
|
9170
9502
|
}
|
|
9171
9503
|
try {
|
|
9172
|
-
const turnPromise = new Promise((
|
|
9173
|
-
this.turnDoneResolver =
|
|
9504
|
+
const turnPromise = new Promise((resolve21) => {
|
|
9505
|
+
this.turnDoneResolver = resolve21;
|
|
9174
9506
|
});
|
|
9175
9507
|
await this.session.submit({
|
|
9176
9508
|
type: "TurnInput",
|
|
@@ -9258,7 +9590,7 @@ async function main() {
|
|
|
9258
9590
|
explicitApiKey = args[++i];
|
|
9259
9591
|
apiKey = explicitApiKey || apiKey;
|
|
9260
9592
|
} else if (arg === "--cwd" || arg === "-C") {
|
|
9261
|
-
cwd =
|
|
9593
|
+
cwd = resolve21(args[++i] || cwd);
|
|
9262
9594
|
} else if (arg === "--role" || arg === "-r") {
|
|
9263
9595
|
role = args[++i] || role;
|
|
9264
9596
|
} else if (arg === "--mcp") {
|
|
@@ -9284,11 +9616,11 @@ async function main() {
|
|
|
9284
9616
|
const mcpManager = new McpManager;
|
|
9285
9617
|
const candidateConfigs = [
|
|
9286
9618
|
mcpConfigFile,
|
|
9287
|
-
|
|
9288
|
-
|
|
9619
|
+
resolve21(cwd, ".mcp.json"),
|
|
9620
|
+
resolve21(cwd, "mcp_config.json")
|
|
9289
9621
|
].filter(Boolean);
|
|
9290
9622
|
for (const cfg of candidateConfigs) {
|
|
9291
|
-
if (
|
|
9623
|
+
if (existsSync19(cfg)) {
|
|
9292
9624
|
try {
|
|
9293
9625
|
await mcpManager.loadConfigFile(cfg);
|
|
9294
9626
|
mcpManager.registerToolsIntoRouter(tools4);
|
package/dist/index.js
CHANGED
|
@@ -1648,6 +1648,22 @@ var applyPatchTool = {
|
|
|
1648
1648
|
const filePath = resolve2(ctx.cwd, rawPath);
|
|
1649
1649
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1650
1650
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1651
|
+
if (ctx.execPolicy) {
|
|
1652
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1653
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1654
|
+
return {
|
|
1655
|
+
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1656
|
+
isError: true
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
1660
|
+
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1661
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1662
|
+
if (!allowed) {
|
|
1663
|
+
return { output: `Action rejected by user: apply_patch '${rawPath}'`, isError: true };
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1651
1667
|
if (!existsSync2(filePath)) {
|
|
1652
1668
|
if (targetContent) {
|
|
1653
1669
|
return {
|
|
@@ -1704,9 +1720,17 @@ var applyPatchTool = {
|
|
|
1704
1720
|
// src/security/exec-policy.ts
|
|
1705
1721
|
class ExecPolicy {
|
|
1706
1722
|
rules = [];
|
|
1707
|
-
|
|
1723
|
+
mode = "auto";
|
|
1724
|
+
constructor(initialMode = "auto") {
|
|
1725
|
+
this.mode = initialMode;
|
|
1708
1726
|
this.initDefaultRules();
|
|
1709
1727
|
}
|
|
1728
|
+
getMode() {
|
|
1729
|
+
return this.mode;
|
|
1730
|
+
}
|
|
1731
|
+
setMode(mode) {
|
|
1732
|
+
this.mode = mode;
|
|
1733
|
+
}
|
|
1710
1734
|
initDefaultRules() {
|
|
1711
1735
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
1712
1736
|
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
@@ -1718,8 +1742,47 @@ class ExecPolicy {
|
|
|
1718
1742
|
addRule(pattern, decision, description) {
|
|
1719
1743
|
this.rules.unshift({ pattern, decision, description });
|
|
1720
1744
|
}
|
|
1745
|
+
shouldPromptFileEdit(filePath) {
|
|
1746
|
+
if (this.mode === "plan") {
|
|
1747
|
+
return {
|
|
1748
|
+
prompt: false,
|
|
1749
|
+
isPlanBlocked: true,
|
|
1750
|
+
reason: "Plan Mode is active. Mutating files is not allowed while planning."
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
if (this.mode === "manual") {
|
|
1754
|
+
return {
|
|
1755
|
+
prompt: true,
|
|
1756
|
+
reason: `Manual mode requires approval to modify '${filePath || "file"}'`
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return { prompt: false };
|
|
1760
|
+
}
|
|
1721
1761
|
evaluate(command) {
|
|
1722
1762
|
const trimmed = command.trim();
|
|
1763
|
+
if (this.mode === "plan") {
|
|
1764
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1765
|
+
if (isReadOnly) {
|
|
1766
|
+
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1767
|
+
}
|
|
1768
|
+
return { decision: "deny", reason: "Cannot execute mutating shell commands in Plan mode" };
|
|
1769
|
+
}
|
|
1770
|
+
if (this.mode === "manual") {
|
|
1771
|
+
return {
|
|
1772
|
+
decision: "prompt",
|
|
1773
|
+
reason: "Manual mode requires confirmation for all shell commands"
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
if (this.mode === "accept-edits") {
|
|
1777
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
|
|
1778
|
+
if (isReadOnly) {
|
|
1779
|
+
return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
decision: "prompt",
|
|
1783
|
+
reason: "Accept-edits mode requires approval for active shell commands"
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1723
1786
|
for (const rule of this.rules) {
|
|
1724
1787
|
if (rule.pattern.test(trimmed)) {
|
|
1725
1788
|
return {
|
|
@@ -1729,8 +1792,8 @@ class ExecPolicy {
|
|
|
1729
1792
|
}
|
|
1730
1793
|
}
|
|
1731
1794
|
return {
|
|
1732
|
-
decision: "
|
|
1733
|
-
reason: "
|
|
1795
|
+
decision: "allow",
|
|
1796
|
+
reason: "Auto mode allows execution"
|
|
1734
1797
|
};
|
|
1735
1798
|
}
|
|
1736
1799
|
}
|
|
@@ -2316,7 +2379,24 @@ var writeFileTool = {
|
|
|
2316
2379
|
required: ["path", "content"]
|
|
2317
2380
|
},
|
|
2318
2381
|
async execute(args, ctx) {
|
|
2319
|
-
const
|
|
2382
|
+
const rawPath = String(args.path || "");
|
|
2383
|
+
const filePath = resolve5(ctx.cwd, rawPath);
|
|
2384
|
+
if (ctx.execPolicy) {
|
|
2385
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2386
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2387
|
+
return {
|
|
2388
|
+
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2389
|
+
isError: true
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
2393
|
+
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2394
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2395
|
+
if (!allowed) {
|
|
2396
|
+
return { output: `Action rejected by user: write_file '${rawPath}'`, isError: true };
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2320
2400
|
try {
|
|
2321
2401
|
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
2322
2402
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
@@ -3881,6 +3961,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
3881
3961
|
signal,
|
|
3882
3962
|
execPolicy: session.execPolicy,
|
|
3883
3963
|
mode: session.collaborationMode,
|
|
3964
|
+
permissionMode: session.permissionMode,
|
|
3884
3965
|
onPlanUpdate: (plan2, explanation) => {
|
|
3885
3966
|
session.emitEvent({
|
|
3886
3967
|
type: "PlanUpdated",
|
|
@@ -4041,6 +4122,17 @@ class Session {
|
|
|
4041
4122
|
mcpManager;
|
|
4042
4123
|
execPolicy;
|
|
4043
4124
|
collaborationMode = "default";
|
|
4125
|
+
get permissionMode() {
|
|
4126
|
+
return this.execPolicy.getMode();
|
|
4127
|
+
}
|
|
4128
|
+
setPermissionMode(mode) {
|
|
4129
|
+
this.execPolicy.setMode(mode);
|
|
4130
|
+
if (mode === "plan") {
|
|
4131
|
+
this.collaborationMode = "plan";
|
|
4132
|
+
} else if (this.collaborationMode === "plan") {
|
|
4133
|
+
this.collaborationMode = "default";
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4044
4136
|
history = [];
|
|
4045
4137
|
activeTurn = null;
|
|
4046
4138
|
status = "idle";
|
package/package.json
CHANGED
|
@@ -15,9 +15,75 @@ You are Groupy, an expert autonomous AI coding assistant. You are running as a c
|
|
|
15
15
|
* If the changes are in files you've touched recently, you should read carefully and understand how you can work with the changes rather than reverting them.
|
|
16
16
|
* If the changes are in unrelated files, just ignore them and don't revert them.
|
|
17
17
|
- Do not amend a commit unless explicitly requested to do so.
|
|
18
|
-
- While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed.
|
|
19
18
|
- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.
|
|
20
19
|
|
|
20
|
+
## Adaptive Task Routing (Automatic Complexity Detection)
|
|
21
|
+
|
|
22
|
+
1. **Small / Standard Tasks (Single component, straightforward, < 3 files)**:
|
|
23
|
+
- Proceed directly to execution with zero overhead. Do not create heavy plans or ask trivial confirmation questions.
|
|
24
|
+
- Code surgically and verify with automated tests.
|
|
25
|
+
|
|
26
|
+
2. **Large / Complex / Ambiguous Tasks (> 3 files, architectural overhaul, new subsystem)**:
|
|
27
|
+
- **Step 1 (Auto-Plan)**: Outline a brief step-by-step Implementation Plan before modifying files.
|
|
28
|
+
- **Step 2 (Clarify Ambiguity & Present Choices)**: If there are architectural trade-offs or underspecified requirements, pause and present numbered choices with Option 1 marked as `(Recommended)`.
|
|
29
|
+
- **Step 3 (Execute & Delegate)**: Once the user confirms, execute systematically. Autonomously spawn specialized sub-agents (`spawn_agent`) for independent sub-tasks (e.g. `security-auditor`, `frontend-designer`, `tester`, `researcher`).
|
|
30
|
+
- **Step 4 (Verify)**: Run full build and test suites, automatically repairing any failures before concluding.
|
|
31
|
+
|
|
32
|
+
## Clarifications, Decision Branching & Recommendations
|
|
33
|
+
|
|
34
|
+
1. **Avoid Trivial Questions**:
|
|
35
|
+
- For routine decisions (naming, syntax, sensible standard defaults), use industry best practices and proceed autonomously without bothering the user.
|
|
36
|
+
|
|
37
|
+
2. **When to Pause & Clarify (Ambiguity & Trade-offs)**:
|
|
38
|
+
- Pause and ask if you encounter:
|
|
39
|
+
* Significantly underspecified requirements (e.g., storage driver, auth strategy, deployment target).
|
|
40
|
+
* Potential breaking changes affecting existing modules.
|
|
41
|
+
* Large architectural choices with distinct trade-offs.
|
|
42
|
+
|
|
43
|
+
3. **Recommendation Format**:
|
|
44
|
+
- Always format choices as clear, numbered options (`1.`, `2.`, `3.`).
|
|
45
|
+
- Place your best technical recommendation as Option 1 prefixed with `(Recommended)`.
|
|
46
|
+
- Keep options concise so the user can reply instantly with a single number.
|
|
47
|
+
|
|
48
|
+
## Codebase Discovery & Execution Strategy
|
|
49
|
+
|
|
50
|
+
1. **Broad Exploration ("pelajari project ini / repo ini tentang apa")**:
|
|
51
|
+
- Inspect ONLY root configs (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, etc.), `README.md`, and top-level directory tree.
|
|
52
|
+
- Deliver a concise Architecture Overview (Tech stack, folder hierarchy, main entry points).
|
|
53
|
+
- Do NOT read full component or implementation files during initial reconnaissance. Stop and ask the user what to build or investigate next.
|
|
54
|
+
|
|
55
|
+
2. **Direct Feature Requests ("buat fitur X")**:
|
|
56
|
+
- Do NOT scan or read unrelated files across the repo.
|
|
57
|
+
- Use `grep_search` or `find_files` to pinpoint the exact target area.
|
|
58
|
+
- Read ONLY 1-2 existing reference files to understand established patterns, naming conventions, and shared utilities (avoid reinventing the wheel).
|
|
59
|
+
- Implement the minimal, clean, and robust code needed to satisfy the request.
|
|
60
|
+
|
|
61
|
+
3. **Bug Fixes & Diagnostics ("kenapa error X / perbaiki bug Y")**:
|
|
62
|
+
- Trace from the reported symptom using `grep_search` to find all callers and the shared function.
|
|
63
|
+
- Fix the root cause in the shared module once, rather than applying band-aid patches across callers.
|
|
64
|
+
|
|
65
|
+
## Mandatory Verification, Testing & Self-Repair Loop
|
|
66
|
+
|
|
67
|
+
Before considering any task complete, you MUST execute the following verification steps:
|
|
68
|
+
|
|
69
|
+
1. **Write Automated Tests**:
|
|
70
|
+
- For any non-trivial logic, new feature, or bug fix, write a clean and targeted test suite (or update existing tests).
|
|
71
|
+
- Ensure the test covers edge cases and specifically verifies that the bug cannot regress.
|
|
72
|
+
|
|
73
|
+
2. **Run Build & Test Validation (Platform & Stack Specific)**:
|
|
74
|
+
Detect the project type and execute the appropriate validation command via terminal:
|
|
75
|
+
- **TypeScript / JavaScript (Node, Bun, Deno)**: Run `bun test` / `npm test`, `tsc --noEmit`, or `npm run build`.
|
|
76
|
+
- **Rust**: Run `cargo check`, `cargo test`, and `cargo build`.
|
|
77
|
+
- **Go**: Run `go test ./...` and `go build`.
|
|
78
|
+
- **Python**: Run `pytest` or `python -m unittest`, and verify syntax with `python -m py_compile <files>` or `mypy`.
|
|
79
|
+
- **Java / Kotlin**: Run `./gradlew test` / `mvn test` and verify compile.
|
|
80
|
+
- **C / C++ / C#**: Run project build / test targets (`dotnet test`, `cmake --build`, `make test`).
|
|
81
|
+
|
|
82
|
+
3. **Autonomous Self-Repair (Do Not Stop on Error)**:
|
|
83
|
+
- If tests fail, types mismatch, or the build produces compilation errors, do NOT stop and report failure immediately.
|
|
84
|
+
- Inspect the compiler/runtime stack trace, diagnose the exact failure, apply the fix, and re-run verification until all checks pass cleanly.
|
|
85
|
+
- Only conclude your turn once the code compiles, builds, and passes all tests.
|
|
86
|
+
|
|
21
87
|
## Skills & Autonomous Domain Knowledge
|
|
22
88
|
|
|
23
89
|
You have access to specialized domain skills listed in `<available_skills>`.
|