@datename/fastcode-termux 0.0.0-stage → 0.1.6-beta
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/LICENSE +21 -0
- package/README.md +418 -2
- package/bin/fastcode.mjs +59 -0
- package/dist/agent/approval.d.ts +50 -0
- package/dist/agent/approval.js +7 -0
- package/dist/agent/compaction.d.ts +39 -0
- package/dist/agent/compaction.js +227 -0
- package/dist/agent/context.d.ts +29 -0
- package/dist/agent/context.js +62 -0
- package/dist/agent/guards.d.ts +43 -0
- package/dist/agent/guards.js +269 -0
- package/dist/agent/ignore.d.ts +11 -0
- package/dist/agent/ignore.js +129 -0
- package/dist/agent/limits.d.ts +24 -0
- package/dist/agent/limits.js +71 -0
- package/dist/agent/process-tree.d.ts +2 -0
- package/dist/agent/process-tree.js +31 -0
- package/dist/agent/protocol-mode.d.ts +6 -0
- package/dist/agent/protocol-mode.js +51 -0
- package/dist/agent/protocol.d.ts +4 -0
- package/dist/agent/protocol.js +129 -0
- package/dist/agent/registry.d.ts +17 -0
- package/dist/agent/registry.js +90 -0
- package/dist/agent/runner.d.ts +106 -0
- package/dist/agent/runner.js +352 -0
- package/dist/agent/shell-session.d.ts +37 -0
- package/dist/agent/shell-session.js +400 -0
- package/dist/agent/stream.d.ts +29 -0
- package/dist/agent/stream.js +103 -0
- package/dist/agent/tool-schemas.d.ts +2 -0
- package/dist/agent/tool-schemas.js +63 -0
- package/dist/agent/tools/append_file.d.ts +2 -0
- package/dist/agent/tools/append_file.js +26 -0
- package/dist/agent/tools/apply_patch.d.ts +10 -0
- package/dist/agent/tools/apply_patch.js +52 -0
- package/dist/agent/tools/copy_file.d.ts +2 -0
- package/dist/agent/tools/copy_file.js +30 -0
- package/dist/agent/tools/delete_file.d.ts +2 -0
- package/dist/agent/tools/delete_file.js +24 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +23 -0
- package/dist/agent/tools/index.d.ts +2 -0
- package/dist/agent/tools/index.js +32 -0
- package/dist/agent/tools/list_files.d.ts +2 -0
- package/dist/agent/tools/list_files.js +17 -0
- package/dist/agent/tools/make_dir.d.ts +2 -0
- package/dist/agent/tools/make_dir.js +24 -0
- package/dist/agent/tools/move_path.d.ts +2 -0
- package/dist/agent/tools/move_path.js +47 -0
- package/dist/agent/tools/paths.d.ts +7 -0
- package/dist/agent/tools/paths.js +84 -0
- package/dist/agent/tools/pwd.d.ts +2 -0
- package/dist/agent/tools/pwd.js +10 -0
- package/dist/agent/tools/read_file.d.ts +2 -0
- package/dist/agent/tools/read_file.js +21 -0
- package/dist/agent/tools/read_many.d.ts +2 -0
- package/dist/agent/tools/read_many.js +25 -0
- package/dist/agent/tools/readers.d.ts +6 -0
- package/dist/agent/tools/readers.js +216 -0
- package/dist/agent/tools/replace_in_file.d.ts +2 -0
- package/dist/agent/tools/replace_in_file.js +30 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +48 -0
- package/dist/agent/tools/stat_path.d.ts +2 -0
- package/dist/agent/tools/stat_path.js +31 -0
- package/dist/agent/tools/types.d.ts +39 -0
- package/dist/agent/tools/types.js +1 -0
- package/dist/agent/tools/validate.d.ts +6 -0
- package/dist/agent/tools/validate.js +29 -0
- package/dist/agent/tools/write_file.d.ts +2 -0
- package/dist/agent/tools/write_file.js +26 -0
- package/dist/agent/tools.d.ts +3 -0
- package/dist/agent/tools.js +2 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +708 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +569 -0
- package/dist/commands/about.d.ts +8 -0
- package/dist/commands/about.js +16 -0
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +18 -0
- package/dist/commands/attach.d.ts +9 -0
- package/dist/commands/attach.js +15 -0
- package/dist/commands/bg.d.ts +7 -0
- package/dist/commands/bg.js +9 -0
- package/dist/commands/cancel.d.ts +10 -0
- package/dist/commands/cancel.js +10 -0
- package/dist/commands/clear.d.ts +11 -0
- package/dist/commands/clear.js +9 -0
- package/dist/commands/compact.d.ts +10 -0
- package/dist/commands/compact.js +22 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.js +24 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.js +24 -0
- package/dist/commands/cost.d.ts +9 -0
- package/dist/commands/cost.js +13 -0
- package/dist/commands/cwd.d.ts +7 -0
- package/dist/commands/cwd.js +7 -0
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +36 -0
- package/dist/commands/effort.d.ts +25 -0
- package/dist/commands/effort.js +48 -0
- package/dist/commands/exit.d.ts +9 -0
- package/dist/commands/exit.js +8 -0
- package/dist/commands/expand.d.ts +10 -0
- package/dist/commands/expand.js +8 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.js +18 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +80 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.js +16 -0
- package/dist/commands/kill.d.ts +7 -0
- package/dist/commands/kill.js +11 -0
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +63 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +45 -0
- package/dist/commands/model.d.ts +24 -0
- package/dist/commands/model.js +50 -0
- package/dist/commands/plugin.d.ts +11 -0
- package/dist/commands/plugin.js +74 -0
- package/dist/commands/provider.d.ts +24 -0
- package/dist/commands/provider.js +39 -0
- package/dist/commands/reset.d.ts +9 -0
- package/dist/commands/reset.js +13 -0
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.js +106 -0
- package/dist/commands/steer.d.ts +11 -0
- package/dist/commands/steer.js +13 -0
- package/dist/commands/system.d.ts +11 -0
- package/dist/commands/system.js +20 -0
- package/dist/commands/theme.d.ts +11 -0
- package/dist/commands/theme.js +22 -0
- package/dist/commands/tools.d.ts +7 -0
- package/dist/commands/tools.js +29 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +52 -0
- package/dist/config/paths.d.ts +4 -0
- package/dist/config/paths.js +59 -0
- package/dist/config/store.d.ts +89 -0
- package/dist/config/store.js +260 -0
- package/dist/config/themes.d.ts +73 -0
- package/dist/config/themes.js +77 -0
- package/dist/config/trust.d.ts +22 -0
- package/dist/config/trust.js +153 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31 -0
- package/dist/lib/diagnostics.d.ts +66 -0
- package/dist/lib/diagnostics.js +195 -0
- package/dist/lib/http.d.ts +23 -0
- package/dist/lib/http.js +128 -0
- package/dist/lib/markdown.d.ts +1 -0
- package/dist/lib/markdown.js +32 -0
- package/dist/lib/model-meta.d.ts +19 -0
- package/dist/lib/model-meta.js +113 -0
- package/dist/lib/native-tools.d.ts +12 -0
- package/dist/lib/native-tools.js +21 -0
- package/dist/lib/net.d.ts +33 -0
- package/dist/lib/net.js +168 -0
- package/dist/lib/pkg.d.ts +5 -0
- package/dist/lib/pkg.js +27 -0
- package/dist/lib/platform.d.ts +7 -0
- package/dist/lib/platform.js +57 -0
- package/dist/lib/startup.d.ts +6 -0
- package/dist/lib/startup.js +25 -0
- package/dist/lib/uiMode.d.ts +2 -0
- package/dist/lib/uiMode.js +7 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +172 -0
- package/dist/patch/parse.d.ts +29 -0
- package/dist/patch/parse.js +112 -0
- package/dist/patch/plan.d.ts +49 -0
- package/dist/patch/plan.js +273 -0
- package/dist/plugins/manager.d.ts +23 -0
- package/dist/plugins/manager.js +101 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +119 -0
- package/dist/providers/custom.d.ts +33 -0
- package/dist/providers/custom.js +25 -0
- package/dist/providers/deepseek.d.ts +34 -0
- package/dist/providers/deepseek.js +16 -0
- package/dist/providers/google.d.ts +33 -0
- package/dist/providers/google.js +19 -0
- package/dist/providers/groq.d.ts +34 -0
- package/dist/providers/groq.js +21 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +67 -0
- package/dist/providers/mistral.d.ts +34 -0
- package/dist/providers/mistral.js +20 -0
- package/dist/providers/moonshot.d.ts +34 -0
- package/dist/providers/moonshot.js +23 -0
- package/dist/providers/ollama.d.ts +33 -0
- package/dist/providers/ollama.js +22 -0
- package/dist/providers/openai.d.ts +34 -0
- package/dist/providers/openai.js +26 -0
- package/dist/providers/openaiCompatible.d.ts +26 -0
- package/dist/providers/openaiCompatible.js +158 -0
- package/dist/providers/openrouter.d.ts +34 -0
- package/dist/providers/openrouter.js +27 -0
- package/dist/providers/qwen.d.ts +34 -0
- package/dist/providers/qwen.js +24 -0
- package/dist/providers/xai.d.ts +34 -0
- package/dist/providers/xai.js +19 -0
- package/dist/providers/zhipu.d.ts +34 -0
- package/dist/providers/zhipu.js +24 -0
- package/dist/session/log.d.ts +52 -0
- package/dist/session/log.js +331 -0
- package/dist/session/projection.d.ts +43 -0
- package/dist/session/projection.js +257 -0
- package/dist/skills/manager.d.ts +18 -0
- package/dist/skills/manager.js +201 -0
- package/dist/skills/parser.d.ts +18 -0
- package/dist/skills/parser.js +65 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.js +1 -0
- package/dist/ui/Banner.d.ts +5 -0
- package/dist/ui/Banner.js +65 -0
- package/dist/ui/Message.d.ts +1 -0
- package/dist/ui/Message.js +24 -0
- package/dist/ui/Picker.d.ts +8 -0
- package/dist/ui/Picker.js +24 -0
- package/dist/ui/Prompt.d.ts +9 -0
- package/dist/ui/Prompt.js +38 -0
- package/dist/ui/Spinner.d.ts +5 -0
- package/dist/ui/Spinner.js +39 -0
- package/dist/ui/StatusBar.d.ts +11 -0
- package/dist/ui/StatusBar.js +20 -0
- package/dist/ui/TrustGate.d.ts +5 -0
- package/dist/ui/TrustGate.js +26 -0
- package/package.json +96 -4
- package/src/agent/approval.ts +47 -0
- package/src/agent/compaction.ts +276 -0
- package/src/agent/context.ts +97 -0
- package/src/agent/guards.ts +270 -0
- package/src/agent/ignore.ts +147 -0
- package/src/agent/limits.ts +71 -0
- package/src/agent/process-tree.ts +32 -0
- package/src/agent/protocol-mode.ts +59 -0
- package/src/agent/protocol.ts +129 -0
- package/src/agent/registry.ts +98 -0
- package/src/agent/runner.ts +443 -0
- package/src/agent/shell-session.ts +449 -0
- package/src/agent/stream.ts +143 -0
- package/src/agent/tool-schemas.ts +67 -0
- package/src/agent/tools/append_file.ts +28 -0
- package/src/agent/tools/apply_patch.ts +54 -0
- package/src/agent/tools/copy_file.ts +32 -0
- package/src/agent/tools/delete_file.ts +26 -0
- package/src/agent/tools/grep.ts +25 -0
- package/src/agent/tools/index.ts +34 -0
- package/src/agent/tools/list_files.ts +19 -0
- package/src/agent/tools/make_dir.ts +26 -0
- package/src/agent/tools/move_path.ts +50 -0
- package/src/agent/tools/paths.ts +92 -0
- package/src/agent/tools/pwd.ts +12 -0
- package/src/agent/tools/read_file.ts +23 -0
- package/src/agent/tools/read_many.ts +27 -0
- package/src/agent/tools/readers.ts +228 -0
- package/src/agent/tools/replace_in_file.ts +32 -0
- package/src/agent/tools/shell.ts +53 -0
- package/src/agent/tools/stat_path.ts +33 -0
- package/src/agent/tools/types.ts +42 -0
- package/src/agent/tools/validate.ts +35 -0
- package/src/agent/tools/write_file.ts +28 -0
- package/src/agent/tools.ts +3 -0
- package/src/app.ts +708 -0
- package/src/cli.ts +335 -0
- package/src/commands/about.ts +16 -0
- package/src/commands/agent.ts +18 -0
- package/src/commands/attach.ts +16 -0
- package/src/commands/bg.ts +9 -0
- package/src/commands/cancel.ts +10 -0
- package/src/commands/clear.ts +9 -0
- package/src/commands/compact.ts +23 -0
- package/src/commands/config.ts +24 -0
- package/src/commands/context.ts +25 -0
- package/src/commands/cost.ts +13 -0
- package/src/commands/cwd.ts +7 -0
- package/src/commands/edit.ts +37 -0
- package/src/commands/effort.ts +49 -0
- package/src/commands/exit.ts +8 -0
- package/src/commands/expand.ts +8 -0
- package/src/commands/help.ts +18 -0
- package/src/commands/index.ts +80 -0
- package/src/commands/jobs.ts +17 -0
- package/src/commands/kill.ts +12 -0
- package/src/commands/login.ts +63 -0
- package/src/commands/logout.ts +45 -0
- package/src/commands/model.ts +50 -0
- package/src/commands/plugin.ts +74 -0
- package/src/commands/provider.ts +39 -0
- package/src/commands/reset.ts +13 -0
- package/src/commands/skills.ts +106 -0
- package/src/commands/steer.ts +13 -0
- package/src/commands/system.ts +20 -0
- package/src/commands/theme.ts +22 -0
- package/src/commands/tools.ts +29 -0
- package/src/commands/whoami.ts +52 -0
- package/src/config/paths.ts +59 -0
- package/src/config/store.ts +293 -0
- package/src/config/themes.ts +77 -0
- package/src/config/trust.ts +183 -0
- package/src/index.ts +31 -0
- package/src/lib/diagnostics.ts +195 -0
- package/src/lib/http.ts +132 -0
- package/src/lib/markdown.ts +32 -0
- package/src/lib/model-meta.ts +138 -0
- package/src/lib/native-tools.ts +32 -0
- package/src/lib/net.ts +204 -0
- package/src/lib/pkg.ts +35 -0
- package/src/lib/platform.ts +57 -0
- package/src/lib/startup.ts +25 -0
- package/src/lib/uiMode.ts +7 -0
- package/src/patch/apply.ts +194 -0
- package/src/patch/parse.ts +131 -0
- package/src/patch/plan.ts +317 -0
- package/src/plugins/manager.ts +101 -0
- package/src/providers/anthropic.ts +119 -0
- package/src/providers/custom.ts +25 -0
- package/src/providers/deepseek.ts +16 -0
- package/src/providers/google.ts +19 -0
- package/src/providers/groq.ts +21 -0
- package/src/providers/index.ts +68 -0
- package/src/providers/mistral.ts +20 -0
- package/src/providers/moonshot.ts +23 -0
- package/src/providers/ollama.ts +22 -0
- package/src/providers/openai.ts +26 -0
- package/src/providers/openaiCompatible.ts +158 -0
- package/src/providers/openrouter.ts +27 -0
- package/src/providers/qwen.ts +24 -0
- package/src/providers/xai.ts +19 -0
- package/src/providers/zhipu.ts +24 -0
- package/src/session/log.ts +374 -0
- package/src/session/projection.ts +308 -0
- package/src/skills/manager.ts +201 -0
- package/src/skills/parser.ts +65 -0
- package/src/types.ts +205 -0
- package/src/ui/Banner.ts +68 -0
- package/src/ui/Message.ts +24 -0
- package/src/ui/Picker.ts +24 -0
- package/src/ui/Prompt.ts +38 -0
- package/src/ui/Spinner.ts +39 -0
- package/src/ui/StatusBar.ts +20 -0
- package/src/ui/TrustGate.ts +26 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { closeSync, constants as fsConstants, fchmodSync, lstatSync, mkdirSync, openSync, renameSync, rmSync, unlinkSync, writeSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export function applyPatchPlan(plan) {
|
|
4
|
+
const warnings = [...plan.warnings];
|
|
5
|
+
const applied = [];
|
|
6
|
+
const touched = [];
|
|
7
|
+
const removed = [];
|
|
8
|
+
const temps = [];
|
|
9
|
+
try {
|
|
10
|
+
for (const file of plan.files) {
|
|
11
|
+
commit(file, warnings, temps);
|
|
12
|
+
applied.push(file);
|
|
13
|
+
if (file.kind === "delete") {
|
|
14
|
+
removed.push(file.path);
|
|
15
|
+
}
|
|
16
|
+
else if (file.kind === "add") {
|
|
17
|
+
touched.push(file.path);
|
|
18
|
+
}
|
|
19
|
+
else if (file.moveTo) {
|
|
20
|
+
removed.push(file.path);
|
|
21
|
+
touched.push(file.moveTo);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
touched.push(file.path);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
for (const tmp of temps) {
|
|
30
|
+
try {
|
|
31
|
+
rmSync(tmp, { force: true });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// best effort
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
rollback(applied, warnings);
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
output: `patch failed: ${err?.message ?? String(err)}; rolled back ${applied.length} file(s)`,
|
|
41
|
+
files: [],
|
|
42
|
+
touched: [],
|
|
43
|
+
removed: [],
|
|
44
|
+
warnings,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const files = plan.files.map((file) => ({
|
|
48
|
+
path: file.moveTo ?? file.path,
|
|
49
|
+
kind: file.kind,
|
|
50
|
+
diff: file.diff,
|
|
51
|
+
}));
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
output: [`applied ${plan.files.length} file(s)`, ...files.map((file) => file.diff)].join("\n\n"),
|
|
55
|
+
files,
|
|
56
|
+
touched,
|
|
57
|
+
removed,
|
|
58
|
+
warnings,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function commit(file, warnings, temps) {
|
|
62
|
+
if (file.kind === "delete") {
|
|
63
|
+
rejectSymlink(file.path);
|
|
64
|
+
unlinkSync(file.path);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const target = file.moveTo ?? file.path;
|
|
68
|
+
if (file.kind === "add" || file.moveTo) {
|
|
69
|
+
if (lexists(target))
|
|
70
|
+
throw new Error(`target already exists: ${target}`);
|
|
71
|
+
}
|
|
72
|
+
rejectSymlink(target, true);
|
|
73
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
74
|
+
const tmp = `${target}.fastcode-patch-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
75
|
+
temps.push(tmp);
|
|
76
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | (fsConstants.O_NOFOLLOW ?? 0);
|
|
77
|
+
const fd = openSync(tmp, flags, 0o600);
|
|
78
|
+
try {
|
|
79
|
+
writeSync(fd, file.newContent ?? "");
|
|
80
|
+
try {
|
|
81
|
+
fchmodSync(fd, file.origMode & 0o777);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
warnings.push(`could not restore mode ${(file.origMode & 0o777).toString(8)} on ${target}; check permissions`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
closeSync(fd);
|
|
89
|
+
}
|
|
90
|
+
rejectSymlink(target, true);
|
|
91
|
+
renameSync(tmp, target);
|
|
92
|
+
const index = temps.indexOf(tmp);
|
|
93
|
+
if (index >= 0)
|
|
94
|
+
temps.splice(index, 1);
|
|
95
|
+
if (file.moveTo) {
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(file.path);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
try {
|
|
101
|
+
rmSync(target, { force: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// best effort
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function rejectSymlink(path, allowMissing = false) {
|
|
111
|
+
try {
|
|
112
|
+
if (lstatSync(path).isSymbolicLink())
|
|
113
|
+
throw new Error(`refusing to write through a symbolic link: ${path}`);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err?.code === "ENOENT" && allowMissing)
|
|
117
|
+
return;
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function lexists(path) {
|
|
122
|
+
try {
|
|
123
|
+
lstatSync(path);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function rollback(applied, warnings) {
|
|
131
|
+
for (let i = applied.length - 1; i >= 0; i -= 1) {
|
|
132
|
+
const file = applied[i];
|
|
133
|
+
try {
|
|
134
|
+
if (file.kind === "add") {
|
|
135
|
+
rmSync(file.path, { force: true });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (file.kind === "delete") {
|
|
139
|
+
restore(file.path, file.oldContent ?? "", file.origMode);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (file.moveTo) {
|
|
143
|
+
rmSync(file.moveTo, { force: true });
|
|
144
|
+
restore(file.path, file.oldContent ?? "", file.origMode);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
restore(file.path, file.oldContent ?? "", file.origMode);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
warnings.push(`rollback failed for ${file.path}: ${err?.message ?? String(err)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function restore(path, content, mode) {
|
|
155
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
156
|
+
const tmp = `${path}.fastcode-restore-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
157
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | (fsConstants.O_NOFOLLOW ?? 0);
|
|
158
|
+
const fd = openSync(tmp, flags, 0o600);
|
|
159
|
+
try {
|
|
160
|
+
writeSync(fd, content);
|
|
161
|
+
try {
|
|
162
|
+
fchmodSync(fd, mode & 0o777);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// best effort during rollback
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
closeSync(fd);
|
|
170
|
+
}
|
|
171
|
+
renameSync(tmp, path);
|
|
172
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type PatchLineType = " " | "-" | "+";
|
|
2
|
+
export interface PatchLine {
|
|
3
|
+
type: PatchLineType;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PatchHunk {
|
|
7
|
+
lines: PatchLine[];
|
|
8
|
+
}
|
|
9
|
+
export type PatchOp = {
|
|
10
|
+
kind: "add";
|
|
11
|
+
path: string;
|
|
12
|
+
lines: string[];
|
|
13
|
+
} | {
|
|
14
|
+
kind: "delete";
|
|
15
|
+
path: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "update";
|
|
18
|
+
path: string;
|
|
19
|
+
hunks: PatchHunk[];
|
|
20
|
+
moveTo?: string;
|
|
21
|
+
};
|
|
22
|
+
export type ParseResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
ops: PatchOp[];
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
output: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function parsePatch(patch: string): ParseResult;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function parsePatch(patch) {
|
|
2
|
+
const lines = String(patch ?? "").split(/\r?\n/);
|
|
3
|
+
if (lines[0] !== "*** Begin Patch")
|
|
4
|
+
return fail("patch must start with *** Begin Patch");
|
|
5
|
+
const ops = [];
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const claim = (path) => {
|
|
8
|
+
if (!path)
|
|
9
|
+
return "empty file path in patch";
|
|
10
|
+
if (seen.has(path))
|
|
11
|
+
return `duplicate file in patch: ${path}`;
|
|
12
|
+
seen.add(path);
|
|
13
|
+
return undefined;
|
|
14
|
+
};
|
|
15
|
+
let i = 1;
|
|
16
|
+
let foundEnd = false;
|
|
17
|
+
while (i < lines.length) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
if (line === "*** End Patch") {
|
|
20
|
+
foundEnd = true;
|
|
21
|
+
i += 1;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (line === "" || line === undefined) {
|
|
25
|
+
i += 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (line.startsWith("*** Add File: ")) {
|
|
29
|
+
const path = line.slice("*** Add File: ".length).trim();
|
|
30
|
+
const duplicate = claim(path);
|
|
31
|
+
if (duplicate)
|
|
32
|
+
return fail(duplicate);
|
|
33
|
+
i += 1;
|
|
34
|
+
const content = [];
|
|
35
|
+
while (i < lines.length && !lines[i].startsWith("*** ")) {
|
|
36
|
+
const row = lines[i];
|
|
37
|
+
if (row === "") {
|
|
38
|
+
content.push("");
|
|
39
|
+
i += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!row.startsWith("+"))
|
|
43
|
+
return fail(`Add File body must start with '+': ${row}`);
|
|
44
|
+
content.push(row.slice(1));
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
ops.push({ kind: "add", path, lines: content });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (line.startsWith("*** Delete File: ")) {
|
|
51
|
+
const path = line.slice("*** Delete File: ".length).trim();
|
|
52
|
+
const duplicate = claim(path);
|
|
53
|
+
if (duplicate)
|
|
54
|
+
return fail(duplicate);
|
|
55
|
+
ops.push({ kind: "delete", path });
|
|
56
|
+
i += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (line.startsWith("*** Update File: ")) {
|
|
60
|
+
const path = line.slice("*** Update File: ".length).trim();
|
|
61
|
+
const duplicate = claim(path);
|
|
62
|
+
if (duplicate)
|
|
63
|
+
return fail(duplicate);
|
|
64
|
+
i += 1;
|
|
65
|
+
let moveTo;
|
|
66
|
+
if (i < lines.length && lines[i].startsWith("*** Move to: ")) {
|
|
67
|
+
moveTo = lines[i].slice("*** Move to: ".length).trim();
|
|
68
|
+
const movedDuplicate = claim(moveTo);
|
|
69
|
+
if (movedDuplicate)
|
|
70
|
+
return fail(movedDuplicate);
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
const hunks = [];
|
|
74
|
+
let current;
|
|
75
|
+
while (i < lines.length && !lines[i].startsWith("*** ")) {
|
|
76
|
+
const row = lines[i];
|
|
77
|
+
if (row === "") {
|
|
78
|
+
i += 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (row.startsWith("@@")) {
|
|
82
|
+
current = { lines: [] };
|
|
83
|
+
hunks.push(current);
|
|
84
|
+
i += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const type = row[0];
|
|
88
|
+
if (type !== " " && type !== "-" && type !== "+")
|
|
89
|
+
return fail(`invalid hunk line (must start with ' ', '-', or '+'): ${row}`);
|
|
90
|
+
if (!current) {
|
|
91
|
+
current = { lines: [] };
|
|
92
|
+
hunks.push(current);
|
|
93
|
+
}
|
|
94
|
+
current.lines.push({ type: type, text: row.slice(1) });
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
if (hunks.length === 0 && !moveTo)
|
|
98
|
+
return fail(`Update File has no hunks: ${path}`);
|
|
99
|
+
ops.push({ kind: "update", path, hunks, moveTo });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
return fail(`unsupported patch directive: ${line}`);
|
|
103
|
+
}
|
|
104
|
+
if (!foundEnd)
|
|
105
|
+
return fail("patch must end with *** End Patch");
|
|
106
|
+
if (ops.length === 0)
|
|
107
|
+
return fail("patch contains no file operations");
|
|
108
|
+
return { ok: true, ops };
|
|
109
|
+
}
|
|
110
|
+
function fail(output) {
|
|
111
|
+
return { ok: false, output };
|
|
112
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type PatchHunk } from "./parse.js";
|
|
2
|
+
export interface PlannedFile {
|
|
3
|
+
kind: "add" | "delete" | "update";
|
|
4
|
+
relPath: string;
|
|
5
|
+
path: string;
|
|
6
|
+
moveRel?: string;
|
|
7
|
+
moveTo?: string;
|
|
8
|
+
existed: boolean;
|
|
9
|
+
origMode: number;
|
|
10
|
+
eol: "\n" | "\r\n";
|
|
11
|
+
oldContent?: string;
|
|
12
|
+
newContent?: string;
|
|
13
|
+
diff: string;
|
|
14
|
+
changedLines: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PatchPlan {
|
|
17
|
+
files: PlannedFile[];
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export type PlanResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
plan: PatchPlan;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
output: string;
|
|
26
|
+
};
|
|
27
|
+
export type PatchReview = {
|
|
28
|
+
kind: "allow";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "deny";
|
|
31
|
+
reason: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "pending";
|
|
34
|
+
reason: string;
|
|
35
|
+
changedLines: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function planPatch(patch: string, cwd: string): PlanResult;
|
|
38
|
+
export declare function reviewPatchPlan(plan: PatchPlan): PatchReview;
|
|
39
|
+
export declare function detectEol(content: string): "\n" | "\r\n";
|
|
40
|
+
type ApplyHunksResult = {
|
|
41
|
+
ok: true;
|
|
42
|
+
content: string;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
output: string;
|
|
46
|
+
};
|
|
47
|
+
export declare function applyHunks(oldContent: string, hunks: PatchHunk[]): ApplyHunksResult;
|
|
48
|
+
export declare function makeDiff(oldPath: string | null, newPath: string | null, oldContent: string, newContent: string, kind: "add" | "delete" | "update"): string;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { closeSync, constants as fsConstants, lstatSync, openSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { sandboxPath } from "../agent/tools/paths.js";
|
|
4
|
+
import { parsePatch } from "./parse.js";
|
|
5
|
+
export function planPatch(patch, cwd) {
|
|
6
|
+
const parsed = parsePatch(patch);
|
|
7
|
+
if (parsed.ok === false)
|
|
8
|
+
return { ok: false, output: parsed.output };
|
|
9
|
+
const files = [];
|
|
10
|
+
const warnings = [];
|
|
11
|
+
for (const op of parsed.ops) {
|
|
12
|
+
const planned = planOp(op, cwd, warnings);
|
|
13
|
+
if (planned.ok === false)
|
|
14
|
+
return { ok: false, output: planned.output };
|
|
15
|
+
files.push(planned.file);
|
|
16
|
+
}
|
|
17
|
+
return { ok: true, plan: { files, warnings } };
|
|
18
|
+
}
|
|
19
|
+
function planOp(op, cwd, warnings) {
|
|
20
|
+
try {
|
|
21
|
+
const path = sandboxPath(cwd, op.path);
|
|
22
|
+
if (op.kind === "add") {
|
|
23
|
+
if (lexists(path))
|
|
24
|
+
return { ok: false, output: `Add File target already exists: ${op.path}` };
|
|
25
|
+
const newContent = op.lines.length > 0 ? op.lines.join("\n") + "\n" : "";
|
|
26
|
+
return {
|
|
27
|
+
ok: true,
|
|
28
|
+
file: {
|
|
29
|
+
kind: "add",
|
|
30
|
+
relPath: op.path,
|
|
31
|
+
path,
|
|
32
|
+
existed: false,
|
|
33
|
+
origMode: 0o644,
|
|
34
|
+
eol: "\n",
|
|
35
|
+
newContent,
|
|
36
|
+
diff: makeDiff(null, op.path, "", newContent, "add"),
|
|
37
|
+
changedLines: newContent.split("\n").filter(Boolean).length,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!lexists(path))
|
|
42
|
+
return { ok: false, output: `file not found: ${op.path}` };
|
|
43
|
+
const info = lstatSync(path);
|
|
44
|
+
if (info.isSymbolicLink())
|
|
45
|
+
return { ok: false, output: `refusing to patch a symbolic link: ${op.path}` };
|
|
46
|
+
if (!info.isFile())
|
|
47
|
+
return { ok: false, output: `not a regular file: ${op.path}` };
|
|
48
|
+
const oldContent = readNoFollow(path);
|
|
49
|
+
if (op.kind === "delete") {
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
file: {
|
|
53
|
+
kind: "delete",
|
|
54
|
+
relPath: op.path,
|
|
55
|
+
path,
|
|
56
|
+
existed: true,
|
|
57
|
+
origMode: info.mode & 0o777,
|
|
58
|
+
eol: detectEol(oldContent),
|
|
59
|
+
oldContent,
|
|
60
|
+
diff: makeDiff(op.path, null, oldContent, "", "delete"),
|
|
61
|
+
changedLines: oldContent.split(/\r?\n/).length,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const eol = detectEol(oldContent);
|
|
66
|
+
if (eol === "\n" && /\r\n/.test(oldContent))
|
|
67
|
+
warnings.push(`mixed line endings in ${op.path}; writing LF`);
|
|
68
|
+
let newContent = oldContent;
|
|
69
|
+
if (op.hunks.length > 0) {
|
|
70
|
+
const applied = applyHunks(oldContent, op.hunks);
|
|
71
|
+
if (applied.ok === false)
|
|
72
|
+
return applied;
|
|
73
|
+
newContent = applied.content;
|
|
74
|
+
if (eol === "\r\n")
|
|
75
|
+
newContent = newContent.replace(/\n/g, "\r\n");
|
|
76
|
+
}
|
|
77
|
+
let moveTo;
|
|
78
|
+
let moveRel;
|
|
79
|
+
if (op.moveTo) {
|
|
80
|
+
moveTo = sandboxPath(cwd, op.moveTo);
|
|
81
|
+
if (lexists(moveTo))
|
|
82
|
+
return { ok: false, output: `Move target already exists: ${op.moveTo}` };
|
|
83
|
+
moveRel = op.moveTo;
|
|
84
|
+
}
|
|
85
|
+
const diff = makeDiff(op.path, op.moveTo ?? op.path, oldContent, newContent, "update");
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
file: {
|
|
89
|
+
kind: "update",
|
|
90
|
+
relPath: op.path,
|
|
91
|
+
path,
|
|
92
|
+
moveRel,
|
|
93
|
+
moveTo,
|
|
94
|
+
existed: true,
|
|
95
|
+
origMode: info.mode & 0o777,
|
|
96
|
+
eol,
|
|
97
|
+
oldContent,
|
|
98
|
+
newContent,
|
|
99
|
+
diff,
|
|
100
|
+
changedLines: countChangedLines(oldContent, newContent),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return { ok: false, output: err?.message ?? String(err) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function reviewPatchPlan(plan) {
|
|
109
|
+
let changed = 0;
|
|
110
|
+
for (const file of plan.files) {
|
|
111
|
+
changed += file.changedLines;
|
|
112
|
+
if (isHardDenied(file.path))
|
|
113
|
+
return { kind: "deny", reason: `patch targets a protected path: ${file.relPath}` };
|
|
114
|
+
if (isSensitivePath(file.path))
|
|
115
|
+
return { kind: "pending", reason: `patch targets a sensitive path: ${file.relPath}`, changedLines: changed };
|
|
116
|
+
}
|
|
117
|
+
if (changed > 200)
|
|
118
|
+
return { kind: "pending", reason: `patch changes ${changed} lines (limit 200)`, changedLines: changed };
|
|
119
|
+
return { kind: "allow" };
|
|
120
|
+
}
|
|
121
|
+
function isHardDenied(path) {
|
|
122
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase();
|
|
123
|
+
const segments = normalized.split("/");
|
|
124
|
+
if (segments.includes(".ssh") || segments.includes(".gnupg"))
|
|
125
|
+
return true;
|
|
126
|
+
if (normalized.includes("/.git/hooks/"))
|
|
127
|
+
return true;
|
|
128
|
+
if (normalized.includes("/android/data/"))
|
|
129
|
+
return true;
|
|
130
|
+
const name = basename(normalized);
|
|
131
|
+
return name === "auth.json" || name === "trust.json";
|
|
132
|
+
}
|
|
133
|
+
function isSensitivePath(path) {
|
|
134
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase();
|
|
135
|
+
const name = basename(normalized);
|
|
136
|
+
return name === ".env" || name.startsWith(".env.") || /[.](pem|key|p12|pfx)$/.test(name) || normalized.includes("/.git/hooks/");
|
|
137
|
+
}
|
|
138
|
+
function lexists(path) {
|
|
139
|
+
try {
|
|
140
|
+
lstatSync(path);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function readNoFollow(path) {
|
|
148
|
+
const flags = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
|
|
149
|
+
const fd = openSync(path, flags);
|
|
150
|
+
try {
|
|
151
|
+
return readFileSync(fd, "utf8");
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
closeSync(fd);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function detectEol(content) {
|
|
158
|
+
const crlf = (content.match(/\r\n/g) ?? []).length;
|
|
159
|
+
const lf = (content.match(/\n/g) ?? []).length;
|
|
160
|
+
return crlf > 0 && crlf === lf ? "\r\n" : "\n";
|
|
161
|
+
}
|
|
162
|
+
export function applyHunks(oldContent, hunks) {
|
|
163
|
+
const lines = oldContent.split(/\r?\n/);
|
|
164
|
+
let changed = false;
|
|
165
|
+
for (const hunk of hunks) {
|
|
166
|
+
const needle = hunk.lines.filter((line) => line.type !== "+").map((line) => line.text);
|
|
167
|
+
const replacement = hunk.lines.filter((line) => line.type !== "-").map((line) => line.text);
|
|
168
|
+
if (needle.length === 0) {
|
|
169
|
+
const at = lines.length > 0 && lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
170
|
+
lines.splice(at, 0, ...replacement);
|
|
171
|
+
changed = true;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const verdict = findHunk(lines, needle);
|
|
175
|
+
if (verdict.ok === false)
|
|
176
|
+
return verdict;
|
|
177
|
+
lines.splice(verdict.index, needle.length, ...replacement);
|
|
178
|
+
changed = true;
|
|
179
|
+
}
|
|
180
|
+
if (!changed)
|
|
181
|
+
return { ok: false, output: "patch made no changes" };
|
|
182
|
+
return { ok: true, content: lines.join("\n") };
|
|
183
|
+
}
|
|
184
|
+
function findHunk(lines, needle) {
|
|
185
|
+
for (let level = 0; level < 3; level += 1) {
|
|
186
|
+
const matches = matchAll(lines, needle, level);
|
|
187
|
+
if (matches.length === 0)
|
|
188
|
+
continue;
|
|
189
|
+
if (matches.length > 1) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
output: `ambiguous patch context (${matches.length} candidates at lines ${matches.map((index) => index + 1).join(", ")}); add surrounding context`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { ok: true, index: matches[0] };
|
|
196
|
+
}
|
|
197
|
+
return { ok: false, output: `patch context not found: ${JSON.stringify(needle.slice(0, 4))}` };
|
|
198
|
+
}
|
|
199
|
+
function matchAll(lines, needle, level) {
|
|
200
|
+
const found = [];
|
|
201
|
+
const wanted = needle.map((line) => normalizeLine(line, level));
|
|
202
|
+
for (let i = 0; i + needle.length <= lines.length; i += 1) {
|
|
203
|
+
let match = true;
|
|
204
|
+
for (let j = 0; j < needle.length; j += 1) {
|
|
205
|
+
if (normalizeLine(lines[i + j], level) !== wanted[j]) {
|
|
206
|
+
match = false;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (match)
|
|
211
|
+
found.push(i);
|
|
212
|
+
}
|
|
213
|
+
return found;
|
|
214
|
+
}
|
|
215
|
+
function normalizeLine(line, level) {
|
|
216
|
+
if (level === 0)
|
|
217
|
+
return line;
|
|
218
|
+
if (level === 1)
|
|
219
|
+
return line.replace(/\s+$/, "");
|
|
220
|
+
return line.replace(/\s+/g, " ").trim();
|
|
221
|
+
}
|
|
222
|
+
export function makeDiff(oldPath, newPath, oldContent, newContent, kind) {
|
|
223
|
+
const oldLines = splitDiffLines(oldContent);
|
|
224
|
+
const newLines = splitDiffLines(newContent);
|
|
225
|
+
let prefix = 0;
|
|
226
|
+
while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix])
|
|
227
|
+
prefix += 1;
|
|
228
|
+
let suffix = 0;
|
|
229
|
+
while (suffix < oldLines.length - prefix && suffix < newLines.length - prefix &&
|
|
230
|
+
oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix])
|
|
231
|
+
suffix += 1;
|
|
232
|
+
const oldMiddle = oldLines.slice(prefix, oldLines.length - suffix);
|
|
233
|
+
const newMiddle = newLines.slice(prefix, newLines.length - suffix);
|
|
234
|
+
const oldStart = prefix + 1;
|
|
235
|
+
const newStart = prefix + 1;
|
|
236
|
+
const header = kind === "add"
|
|
237
|
+
? `--- /dev/null\n+++ b/${newPath ?? ""}`
|
|
238
|
+
: kind === "delete"
|
|
239
|
+
? `--- a/${oldPath ?? ""}\n+++ /dev/null`
|
|
240
|
+
: `--- a/${oldPath ?? ""}\n+++ b/${newPath ?? oldPath ?? ""}`;
|
|
241
|
+
const body = [
|
|
242
|
+
`@@ -${oldStart},${oldMiddle.length} +${newStart},${newMiddle.length} @@`,
|
|
243
|
+
...oldMiddle.map((line) => "-" + line),
|
|
244
|
+
...newMiddle.map((line) => "+" + line),
|
|
245
|
+
];
|
|
246
|
+
return [header, ...body].join("\n");
|
|
247
|
+
}
|
|
248
|
+
function splitDiffLines(content) {
|
|
249
|
+
if (content === "")
|
|
250
|
+
return [];
|
|
251
|
+
const lines = content.split(/\r?\n/);
|
|
252
|
+
if (lines.length > 0 && lines[lines.length - 1] === "")
|
|
253
|
+
lines.pop();
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
function countChangedLines(oldContent, newContent) {
|
|
257
|
+
const oldLines = oldContent.split(/\r?\n/);
|
|
258
|
+
const newLines = newContent.split(/\r?\n/);
|
|
259
|
+
let changed = 0;
|
|
260
|
+
let i = 0;
|
|
261
|
+
let j = 0;
|
|
262
|
+
while (i < oldLines.length && j < newLines.length) {
|
|
263
|
+
if (oldLines[i] === newLines[j]) {
|
|
264
|
+
i += 1;
|
|
265
|
+
j += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
changed += 1;
|
|
269
|
+
i += 1;
|
|
270
|
+
j += 1;
|
|
271
|
+
}
|
|
272
|
+
return changed + Math.abs(oldLines.length - newLines.length);
|
|
273
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function listInstalledPlugins(): {
|
|
2
|
+
name: any;
|
|
3
|
+
description: any;
|
|
4
|
+
path: any;
|
|
5
|
+
body: string;
|
|
6
|
+
skills: any[];
|
|
7
|
+
}[];
|
|
8
|
+
export declare function getPlugin(name: any): {
|
|
9
|
+
name: any;
|
|
10
|
+
description: any;
|
|
11
|
+
path: any;
|
|
12
|
+
body: string;
|
|
13
|
+
skills: any[];
|
|
14
|
+
};
|
|
15
|
+
export declare function importPlugin(source: any, name: any): {
|
|
16
|
+
name: any;
|
|
17
|
+
description: any;
|
|
18
|
+
path: any;
|
|
19
|
+
body: string;
|
|
20
|
+
skills: any[];
|
|
21
|
+
};
|
|
22
|
+
export declare function removePlugin(name: any): boolean;
|
|
23
|
+
export declare function buildPluginsPrompt(enabled: any): string;
|