@diologue/local-agent 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +246 -2
- package/dist/cli.mjs.map +4 -4
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -726,9 +726,9 @@ var getDiff = async (cwd) => {
|
|
|
726
726
|
return [trackedDiff, ...untrackedDiffs].filter((part) => part.trim().length > 0).join("\n");
|
|
727
727
|
};
|
|
728
728
|
var runGitWithStdin = async (cwd, args, input) => {
|
|
729
|
-
const { execFile:
|
|
729
|
+
const { execFile: execFile4 } = await import("node:child_process");
|
|
730
730
|
return new Promise((resolve, reject) => {
|
|
731
|
-
const child =
|
|
731
|
+
const child = execFile4(
|
|
732
732
|
"git",
|
|
733
733
|
args,
|
|
734
734
|
{
|
|
@@ -787,6 +787,40 @@ var parseDiffPaths = (unified) => {
|
|
|
787
787
|
}
|
|
788
788
|
return paths;
|
|
789
789
|
};
|
|
790
|
+
var getRemoteUrl = async (cwd) => {
|
|
791
|
+
try {
|
|
792
|
+
return (await runGit(cwd, ["remote", "get-url", "origin"])).trim() || null;
|
|
793
|
+
} catch (err) {
|
|
794
|
+
if (err instanceof GitCommandError) return null;
|
|
795
|
+
throw err;
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
var getDefaultBranch = async (cwd) => {
|
|
799
|
+
try {
|
|
800
|
+
const ref = (await runGit(cwd, ["rev-parse", "--abbrev-ref", "origin/HEAD"])).trim();
|
|
801
|
+
const name = ref.replace(/^origin\//, "");
|
|
802
|
+
return name || "main";
|
|
803
|
+
} catch (err) {
|
|
804
|
+
if (err instanceof GitCommandError) return "main";
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
var createBranch = async (cwd, name) => {
|
|
809
|
+
await runGit(cwd, ["checkout", "-b", name]);
|
|
810
|
+
};
|
|
811
|
+
var commitAll = async (cwd, message) => {
|
|
812
|
+
await runGit(cwd, ["add", "-A"]);
|
|
813
|
+
await runGit(cwd, ["commit", "-m", message]);
|
|
814
|
+
return (await runGit(cwd, ["rev-parse", "--short", "HEAD"])).trim();
|
|
815
|
+
};
|
|
816
|
+
var pushBranch = async (cwd, branch) => {
|
|
817
|
+
await runGit(cwd, ["push", "-u", "origin", branch]);
|
|
818
|
+
};
|
|
819
|
+
var branchNameForTitle = (title) => {
|
|
820
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/g, "");
|
|
821
|
+
const rand = Math.random().toString(36).slice(2, 7);
|
|
822
|
+
return `diologue/${slug || "change"}-${rand}`;
|
|
823
|
+
};
|
|
790
824
|
|
|
791
825
|
// src/lib/paths.ts
|
|
792
826
|
import { access, lstat, realpath, stat } from "node:fs/promises";
|
|
@@ -848,6 +882,138 @@ var validateRepoPath = async (raw) => {
|
|
|
848
882
|
return resolved;
|
|
849
883
|
};
|
|
850
884
|
|
|
885
|
+
// src/lib/pick-directory.ts
|
|
886
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
887
|
+
import { homedir } from "node:os";
|
|
888
|
+
var PROMPT = "Select a git repository";
|
|
889
|
+
var dialogSpec = (platform) => {
|
|
890
|
+
switch (platform) {
|
|
891
|
+
case "darwin":
|
|
892
|
+
return {
|
|
893
|
+
cmd: "osascript",
|
|
894
|
+
args: [
|
|
895
|
+
"-e",
|
|
896
|
+
`set theFolder to choose folder with prompt "${PROMPT}"`,
|
|
897
|
+
"-e",
|
|
898
|
+
"POSIX path of theFolder"
|
|
899
|
+
]
|
|
900
|
+
};
|
|
901
|
+
case "linux":
|
|
902
|
+
return {
|
|
903
|
+
cmd: "zenity",
|
|
904
|
+
args: ["--file-selection", "--directory", `--title=${PROMPT}`]
|
|
905
|
+
};
|
|
906
|
+
case "win32":
|
|
907
|
+
return {
|
|
908
|
+
cmd: "powershell",
|
|
909
|
+
args: [
|
|
910
|
+
"-NoProfile",
|
|
911
|
+
"-Command",
|
|
912
|
+
`Add-Type -AssemblyName System.Windows.Forms;$d = New-Object System.Windows.Forms.FolderBrowserDialog;$d.Description = '${PROMPT}';if ($d.ShowDialog() -eq 'OK') { Write-Output $d.SelectedPath }`
|
|
913
|
+
]
|
|
914
|
+
};
|
|
915
|
+
default:
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
var run = (cmd, args) => new Promise((resolve, reject) => {
|
|
920
|
+
execFile2(
|
|
921
|
+
cmd,
|
|
922
|
+
args,
|
|
923
|
+
// Folder dialogs can sit open a while; cap it so the request can't hang
|
|
924
|
+
// forever if the user wanders off. Killing it reads as "cancelled".
|
|
925
|
+
{ timeout: 12e4, windowsHide: true },
|
|
926
|
+
(err, stdout) => {
|
|
927
|
+
if (err) reject(err);
|
|
928
|
+
else resolve(stdout);
|
|
929
|
+
}
|
|
930
|
+
);
|
|
931
|
+
});
|
|
932
|
+
var pickDirectory = async () => {
|
|
933
|
+
const spec = dialogSpec(process.platform);
|
|
934
|
+
if (!spec) return { ok: false, reason: "unsupported" };
|
|
935
|
+
const attempt = async (cmd, args) => {
|
|
936
|
+
try {
|
|
937
|
+
const out = (await run(cmd, args)).trim();
|
|
938
|
+
return out ? { ok: true, path: out } : { ok: false, reason: "cancelled" };
|
|
939
|
+
} catch (err) {
|
|
940
|
+
const code = err.code;
|
|
941
|
+
if (code === "ENOENT") return { ok: false, reason: "no_gui" };
|
|
942
|
+
return { ok: false, reason: "cancelled" };
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const result = await attempt(spec.cmd, spec.args);
|
|
946
|
+
if (!result.ok && result.reason === "no_gui" && process.platform === "linux") {
|
|
947
|
+
return attempt("kdialog", ["--getexistingdirectory", homedir()]);
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/lib/github.ts
|
|
953
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
954
|
+
var GhError = class extends Error {
|
|
955
|
+
constructor(message, code, stderr) {
|
|
956
|
+
super(message);
|
|
957
|
+
this.code = code;
|
|
958
|
+
this.stderr = stderr;
|
|
959
|
+
this.name = "GhError";
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
var run2 = (args, cwd) => new Promise((resolve, reject) => {
|
|
963
|
+
execFile3(
|
|
964
|
+
"gh",
|
|
965
|
+
args,
|
|
966
|
+
{ cwd, timeout: 6e4, windowsHide: true },
|
|
967
|
+
(err, stdout, stderr) => {
|
|
968
|
+
if (err) {
|
|
969
|
+
const code = err.code;
|
|
970
|
+
if (code === "ENOENT") {
|
|
971
|
+
reject(
|
|
972
|
+
new GhError(
|
|
973
|
+
"The GitHub CLI (gh) isn't installed on this machine.",
|
|
974
|
+
"gh_unavailable"
|
|
975
|
+
)
|
|
976
|
+
);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
reject(new GhError(stderr || err.message, "gh_failed", stderr));
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
resolve({ stdout, stderr });
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
});
|
|
986
|
+
var ghReady = async () => {
|
|
987
|
+
try {
|
|
988
|
+
await run2(["auth", "status"]);
|
|
989
|
+
return true;
|
|
990
|
+
} catch {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
var createPullRequest = async (opts) => {
|
|
995
|
+
const { stdout } = await run2(
|
|
996
|
+
[
|
|
997
|
+
"pr",
|
|
998
|
+
"create",
|
|
999
|
+
"--base",
|
|
1000
|
+
opts.base,
|
|
1001
|
+
"--head",
|
|
1002
|
+
opts.head,
|
|
1003
|
+
"--title",
|
|
1004
|
+
opts.title,
|
|
1005
|
+
"--body",
|
|
1006
|
+
opts.body
|
|
1007
|
+
],
|
|
1008
|
+
opts.cwd
|
|
1009
|
+
);
|
|
1010
|
+
const url = stdout.split("\n").map((l) => l.trim()).filter(Boolean).reverse().find((l) => l.startsWith("http"));
|
|
1011
|
+
if (!url) {
|
|
1012
|
+
throw new GhError("gh pr create didn't return a PR URL.", "gh_failed", stdout);
|
|
1013
|
+
}
|
|
1014
|
+
return url;
|
|
1015
|
+
};
|
|
1016
|
+
|
|
851
1017
|
// src/routes/repo.ts
|
|
852
1018
|
var selectRepoSchema = z.object({
|
|
853
1019
|
path: z.string().min(1)
|
|
@@ -859,6 +1025,10 @@ var applyPatchSchema = z.object({
|
|
|
859
1025
|
var revertPatchSchema = z.object({
|
|
860
1026
|
unified: z.string().min(1).max(8 * 1024 * 1024)
|
|
861
1027
|
});
|
|
1028
|
+
var createPrSchema = z.object({
|
|
1029
|
+
title: z.string().min(1).max(200),
|
|
1030
|
+
body: z.string().max(2e4).optional()
|
|
1031
|
+
});
|
|
862
1032
|
var buildRepoStatus = async (resolvedPath) => {
|
|
863
1033
|
const [branch, head, dirty] = await Promise.all([
|
|
864
1034
|
getBranch(resolvedPath),
|
|
@@ -917,6 +1087,80 @@ var createRepoRouter = (state) => {
|
|
|
917
1087
|
throw err;
|
|
918
1088
|
}
|
|
919
1089
|
});
|
|
1090
|
+
router.post("/browse", async (_req, res) => {
|
|
1091
|
+
const result = await pickDirectory();
|
|
1092
|
+
if (result.ok) {
|
|
1093
|
+
const body = { path: result.path };
|
|
1094
|
+
res.json(body);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (result.reason === "cancelled") {
|
|
1098
|
+
const body = { cancelled: true };
|
|
1099
|
+
res.json(body);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
res.status(501).json({
|
|
1103
|
+
error: result.reason,
|
|
1104
|
+
message: "No desktop folder dialog is available on this machine. Type the repo path instead."
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
router.post("/create-pr", async (req, res) => {
|
|
1108
|
+
const repo = requireSelectedRepo(state, res);
|
|
1109
|
+
if (!repo) return;
|
|
1110
|
+
const parsed = createPrSchema.safeParse(req.body);
|
|
1111
|
+
if (!parsed.success) {
|
|
1112
|
+
res.status(400).json({ error: "invalid_body", issues: parsed.error.issues });
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const { title } = parsed.data;
|
|
1116
|
+
const body = parsed.data.body ?? "Opened from the Diologue coding agent.";
|
|
1117
|
+
const cwd = repo.path;
|
|
1118
|
+
try {
|
|
1119
|
+
if (!await ghReady()) {
|
|
1120
|
+
res.status(501).json({
|
|
1121
|
+
error: "gh_unavailable",
|
|
1122
|
+
message: "GitHub CLI (gh) isn't installed or authenticated on this machine. Run `gh auth login`, or push the branch and open the PR manually."
|
|
1123
|
+
});
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (!await getRemoteUrl(cwd)) {
|
|
1127
|
+
res.status(400).json({
|
|
1128
|
+
error: "no_remote",
|
|
1129
|
+
message: "This repo has no `origin` remote. Add one (git remote add origin \u2026) before creating a PR."
|
|
1130
|
+
});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (!await isDirty(cwd)) {
|
|
1134
|
+
res.status(409).json({
|
|
1135
|
+
error: "nothing_to_commit",
|
|
1136
|
+
message: "There are no changes in the working tree to open a PR for."
|
|
1137
|
+
});
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const current = await getBranch(cwd) ?? "";
|
|
1141
|
+
const onAgentBranch = current.startsWith("diologue/");
|
|
1142
|
+
const base = onAgentBranch ? await getDefaultBranch(cwd) : current || "main";
|
|
1143
|
+
const branch = onAgentBranch ? current : branchNameForTitle(title);
|
|
1144
|
+
if (!onAgentBranch) {
|
|
1145
|
+
await createBranch(cwd, branch);
|
|
1146
|
+
}
|
|
1147
|
+
await commitAll(cwd, title);
|
|
1148
|
+
await pushBranch(cwd, branch);
|
|
1149
|
+
const url = await createPullRequest({ cwd, base, head: branch, title, body });
|
|
1150
|
+
const payload = { url, branch, base };
|
|
1151
|
+
res.json(payload);
|
|
1152
|
+
} catch (err) {
|
|
1153
|
+
if (err instanceof GhError) {
|
|
1154
|
+
res.status(502).json({ error: err.code, message: err.message });
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
if (err instanceof GitCommandError) {
|
|
1158
|
+
sendGitError(res, err);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
throw err;
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
920
1164
|
router.get("/status", async (_req, res) => {
|
|
921
1165
|
const repo = state.getSelectedRepo();
|
|
922
1166
|
if (!repo) {
|