@diologue/local-agent 0.6.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 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: execFile3 } = await import("node:child_process");
729
+ const { execFile: execFile4 } = await import("node:child_process");
730
730
  return new Promise((resolve, reject) => {
731
- const child = execFile3(
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";
@@ -915,6 +949,71 @@ var pickDirectory = async () => {
915
949
  return result;
916
950
  };
917
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
+
918
1017
  // src/routes/repo.ts
919
1018
  var selectRepoSchema = z.object({
920
1019
  path: z.string().min(1)
@@ -926,6 +1025,10 @@ var applyPatchSchema = z.object({
926
1025
  var revertPatchSchema = z.object({
927
1026
  unified: z.string().min(1).max(8 * 1024 * 1024)
928
1027
  });
1028
+ var createPrSchema = z.object({
1029
+ title: z.string().min(1).max(200),
1030
+ body: z.string().max(2e4).optional()
1031
+ });
929
1032
  var buildRepoStatus = async (resolvedPath) => {
930
1033
  const [branch, head, dirty] = await Promise.all([
931
1034
  getBranch(resolvedPath),
@@ -1001,6 +1104,63 @@ var createRepoRouter = (state) => {
1001
1104
  message: "No desktop folder dialog is available on this machine. Type the repo path instead."
1002
1105
  });
1003
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
+ });
1004
1164
  router.get("/status", async (_req, res) => {
1005
1165
  const repo = state.getSelectedRepo();
1006
1166
  if (!repo) {