staklink 0.4.9 → 0.4.11

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.
@@ -60768,6 +60768,102 @@ async function NewRepo(url3) {
60768
60768
  }
60769
60769
  return new Repo(repoLocation);
60770
60770
  }
60771
+ function buildAuthenticatedUrl(remoteUrl, credentials) {
60772
+ let url3 = remoteUrl;
60773
+ switch (credentials.provider) {
60774
+ case "github":
60775
+ url3 = url3.replace(
60776
+ "https://github.com/",
60777
+ `https://${credentials.auth_data.token}@github.com/`
60778
+ ).replace(
60779
+ /https:\/\/.*@github\.com\//,
60780
+ `https://${credentials.auth_data.token}@github.com/`
60781
+ );
60782
+ if (url3.startsWith("git@github.com:")) {
60783
+ const repoPath = url3.replace("git@github.com:", "").replace(/\.git$/, "");
60784
+ url3 = `https://${credentials.auth_data.token}@github.com/${repoPath}.git`;
60785
+ }
60786
+ break;
60787
+ case "gitlab":
60788
+ url3 = url3.replace(
60789
+ "https://gitlab.com/",
60790
+ `https://oauth2:${credentials.auth_data.token}@gitlab.com/`
60791
+ ).replace(
60792
+ /https:\/\/.*@gitlab\.com\//,
60793
+ `https://oauth2:${credentials.auth_data.token}@gitlab.com/`
60794
+ );
60795
+ if (url3.startsWith("git@gitlab.com:")) {
60796
+ const repoPath = url3.replace("git@gitlab.com:", "").replace(/\.git$/, "");
60797
+ url3 = `https://oauth2:${credentials.auth_data.token}@gitlab.com/${repoPath}.git`;
60798
+ }
60799
+ break;
60800
+ case "bitbucket":
60801
+ url3 = url3.replace(
60802
+ "https://bitbucket.org/",
60803
+ `https://${credentials.auth_data.username}:${credentials.auth_data.token}@bitbucket.org/`
60804
+ ).replace(
60805
+ /https:\/\/.*@bitbucket\.org\//,
60806
+ `https://${credentials.auth_data.username}:${credentials.auth_data.token}@bitbucket.org/`
60807
+ );
60808
+ if (url3.startsWith("git@bitbucket.org:")) {
60809
+ const repoPath = url3.replace("git@bitbucket.org:", "").replace(/\.git$/, "");
60810
+ url3 = `https://${credentials.auth_data.username}:${credentials.auth_data.token}@bitbucket.org/${repoPath}.git`;
60811
+ }
60812
+ break;
60813
+ }
60814
+ return url3;
60815
+ }
60816
+ async function setupGlobalGitCredentials(repoCwd, credentials) {
60817
+ const run = (cmd) => exec4(cmd, {
60818
+ cwd: repoCwd,
60819
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
60820
+ });
60821
+ const { stdout: origUrl } = await run(
60822
+ "git config --get remote.origin.url"
60823
+ );
60824
+ let origName = null;
60825
+ let origEmail = null;
60826
+ try {
60827
+ origName = (await run("git config --global user.name")).stdout.trim();
60828
+ } catch {
60829
+ }
60830
+ try {
60831
+ origEmail = (await run("git config --global user.email")).stdout.trim();
60832
+ } catch {
60833
+ }
60834
+ const authUrl = buildAuthenticatedUrl(origUrl.trim(), credentials);
60835
+ await run(`git remote set-url origin "${authUrl}"`);
60836
+ await run(
60837
+ `git config --global user.name "${credentials.auth_data.username}"`
60838
+ );
60839
+ await run(
60840
+ `git config --global user.email "${credentials.auth_data.username}@users.noreply.github.com"`
60841
+ );
60842
+ const env2 = {
60843
+ GH_TOKEN: credentials.auth_data.token,
60844
+ GITHUB_TOKEN: credentials.auth_data.token
60845
+ };
60846
+ const teardown = async () => {
60847
+ try {
60848
+ await run(`git remote set-url origin "${origUrl.trim()}"`);
60849
+ if (origName) {
60850
+ await run(`git config --global user.name "${origName}"`);
60851
+ } else {
60852
+ await run("git config --global --unset user.name").catch(() => {
60853
+ });
60854
+ }
60855
+ if (origEmail) {
60856
+ await run(`git config --global user.email "${origEmail}"`);
60857
+ } else {
60858
+ await run("git config --global --unset user.email").catch(() => {
60859
+ });
60860
+ }
60861
+ } catch (e) {
60862
+ error("Error tearing down global git credentials:", e);
60863
+ }
60864
+ };
60865
+ return { env: env2, teardown };
60866
+ }
60771
60867
 
60772
60868
  // src/proxy/sse.ts
60773
60869
  var SSEManager = class {
@@ -60814,7 +60910,7 @@ var SSEManager = class {
60814
60910
  var sseManager = new SSEManager();
60815
60911
 
60816
60912
  // src/proxy/version.ts
60817
- var VERSION = "0.4.9";
60913
+ var VERSION = "0.4.11";
60818
60914
 
60819
60915
  // node_modules/uuid/dist/esm/stringify.js
60820
60916
  var byteToHex = [];
@@ -137544,7 +137640,9 @@ async function runAgent({
137544
137640
  createGooseConfig(searchApiKey);
137545
137641
  const env2 = goose_env(apiKey, model);
137546
137642
  console.log("RUN goose with env", env2);
137547
- const cleanPrompt = sanitizeShellArg(prompt);
137643
+ const safePrompt = prompt.startsWith("-") ? `
137644
+ ${prompt}` : prompt;
137645
+ const cleanPrompt = sanitizeShellArg(safePrompt);
137548
137646
  let system = SYSTEM_CORE;
137549
137647
  system += system_prompt ? sanitizeShellArg(system_prompt) + "\n\n" : "";
137550
137648
  system += SYSTEM_SUFFIX;
@@ -139220,7 +139318,17 @@ var makeRepairPrompt = (podConfigPath, history, initialPrompt) => {
139220
139318
 
139221
139319
  The config files can be read at ${podConfigPath}. You must find out how to edit the pm2.config.js and the docker-compose.yaml in order to get the project to run successfully (there is also a Dockerfile which may also be changed if absolutely needed). Remember that you may need to add or alter env vars in the pm2.config.js file. Focus on the frontend app defined in the pm2 config file, don't worry about other services unless they are needed to run the frontend. For example, if its a next.js app, you can read the package.json to see what SHOULD be in the pm2 "script" command or the special install/build/etc commands. REMEMBER: this is a development environment, so the pm2 script should be something like "npm run dev" or something similar... we want to default to whatever start script is best for development (like whatever has hot-reload etc). If its a simple backend api project, then thats fine to be defined as the "frontend". If there really is no public-facing frontend or api, then STILL INCLUDE A pm2 service called "frontend", which can just be this dummy server: "npx -y hell0-w0rld". Its important that we have at least SOMETHING running as the "frontend" so we can verify that the pod is running smoothly!
139222
139320
 
139223
- IMPORTANT: You may be in a multi-repo workspace! If so, make sure the "cwd" field for each appin the pm2.config.js specifies different paths to each repo! Like "/workspaces/repo1", "/workspaces/repo2", etc.
139321
+ If this is an Android project, prefer Android conventions:
139322
+ - The frontend pm2 service can use a placeholder script (for example, "echo android-frontend") and do real setup via env commands.
139323
+ - Set PORT to "8000" (screen stream port).
139324
+ - Use PRE_START_COMMAND to ensure adb is ready (for example, "adb wait-for-device").
139325
+ - Use INSTALL_COMMAND/BUILD_COMMAND for dependency fetch + APK build (for example, "./gradlew dependencies" and "./gradlew assembleDebug").
139326
+ - Use POST_START_COMMAND to install/launch the app when needed.
139327
+ - Set REBUILD_COMMAND to APK build command and RESTART to "true" so code updates trigger rebuild/reinstall flow.
139328
+ - Derive package/activity from AndroidManifest or Gradle config when launching with adb shell am start.
139329
+ - Use ghcr.io/stakwork/staklink-android:latest as the base image in Dockerfile.
139330
+
139331
+ IMPORTANT: You may be in a multi-repo workspace! If so, make sure the "cwd" field for each app in the pm2.config.js specifies different paths to each repo! Like "/workspaces/repo1", "/workspaces/repo2", etc.
139224
139332
 
139225
139333
  This project is currently running a remote code-server instance. Docker is optional, but may be used. The docker-compose file has already been started. You can use the regular docker commands (docker ps, docker logs, etc) to inspect the running containers.
139226
139334
 
@@ -141400,10 +141508,12 @@ ${diff.trim()}`);
141400
141508
  }
141401
141509
  }
141402
141510
  }
141403
- const response = { success: true, commits, branches };
141511
+ const hasPrErrors = Object.keys(prErrors).length > 0;
141512
+ const success3 = !(createPR && hasPrErrors);
141513
+ const response = { success: success3, commits, branches };
141404
141514
  if (createPR) {
141405
141515
  response.prs = prs;
141406
- if (Object.keys(prErrors).length > 0) {
141516
+ if (hasPrErrors) {
141407
141517
  response.prErrors = prErrors;
141408
141518
  }
141409
141519
  }
@@ -141513,6 +141623,152 @@ async function handleCreateRepo(req, res) {
141513
141623
  });
141514
141624
  }
141515
141625
  }
141626
+ function buildPushPrompt(opts) {
141627
+ const lines = [
141628
+ `You are in the git repo at: ${opts.repoPath}`,
141629
+ "",
141630
+ "Perform the following git operations. If any step fails, diagnose the problem and fix it (resolve merge conflicts, pull and rebase, etc). Be resilient - the goal is to get code pushed successfully.",
141631
+ "",
141632
+ "1. Check the current branch. If you are on main or master, create a new feature branch and switch to it. Otherwise stay on the current branch.",
141633
+ "2. Check git status. If there are uncommitted changes, stage them (git add .) and commit with a short descriptive message. If everything is already committed, skip this step.",
141634
+ "3. Push the current branch to origin. If the push is rejected (non-fast-forward, remote has new work, etc), pull with rebase and try again. If there are merge conflicts during rebase, resolve them sensibly and continue."
141635
+ ];
141636
+ if (opts.createPR) {
141637
+ let step = 4;
141638
+ lines.push(
141639
+ `${step}. Create a pull request from the current branch to "${opts.baseBranch}" using the gh CLI. The PR title should be derived from the commit message. If a PR already exists for this branch, skip creation.`
141640
+ );
141641
+ if (opts.label) {
141642
+ step++;
141643
+ lines.push(
141644
+ `${step}. Add the label "${opts.label}" to the PR.`
141645
+ );
141646
+ }
141647
+ step++;
141648
+ lines.push(
141649
+ `${step}. Ensure the PR branch is up to date with "${opts.baseBranch}". Run: git fetch origin ${opts.baseBranch} && git merge origin/${opts.baseBranch}. If there are conflicts, resolve them, commit, and push again. This is equivalent to GitHub's "Update branch" button.`
141650
+ );
141651
+ if (opts.autoMerge) {
141652
+ step++;
141653
+ lines.push(
141654
+ `${step}. Enable auto-merge (squash) on the PR using: gh pr merge <number> --auto --squash --delete-branch`
141655
+ );
141656
+ }
141657
+ }
141658
+ lines.push(
141659
+ "",
141660
+ "IMPORTANT:",
141661
+ "- Do NOT ask for confirmation. Just do it.",
141662
+ "- If push fails, try pulling with rebase first. If that fails with conflicts, resolve them and continue.",
141663
+ "- Output the PR URL if you created one.",
141664
+ "- Output the branch name you ended up on.",
141665
+ "- Output the commit hash after pushing."
141666
+ );
141667
+ return lines.join("\n");
141668
+ }
141669
+ async function handleAgentPush(req, res) {
141670
+ const request_id = startReq();
141671
+ try {
141672
+ const code = req.body;
141673
+ const repos = getReposWithChangedFiles(code);
141674
+ const createPR = req.query.pr === "true";
141675
+ const autoMerge = req.query.automerge === "true";
141676
+ const label = req.query.label;
141677
+ if (!repos.length) {
141678
+ finishReq(request_id, {
141679
+ success: true,
141680
+ commits: [],
141681
+ branches: {}
141682
+ });
141683
+ res.json({ request_id, status: "pending" });
141684
+ return;
141685
+ }
141686
+ if (!code.git_credentials?.auth_data?.token) {
141687
+ failReq(request_id, new Error("git_credentials with token required"));
141688
+ res.json({ request_id, status: "pending" });
141689
+ return;
141690
+ }
141691
+ const apiKey = code.apiKey || process.env.ANTHROPIC_API_KEY || "";
141692
+ (async () => {
141693
+ const results = { commits: [], prs: {}, branches: {} };
141694
+ for (const r of repos) {
141695
+ const repo = await NewRepo(r.url);
141696
+ const repoPath = repo.printcwd();
141697
+ const repoName = getRepoNameFromUrl(r.url);
141698
+ const baseBranch = r.base_branch || "main";
141699
+ const { env: credEnv, teardown } = await setupGlobalGitCredentials(
141700
+ repoPath,
141701
+ code.git_credentials
141702
+ );
141703
+ try {
141704
+ const prompt = buildPushPrompt({
141705
+ repoPath,
141706
+ createPR,
141707
+ autoMerge,
141708
+ label,
141709
+ baseBranch
141710
+ });
141711
+ log(`=> agent/push prompt for ${repoName}:
141712
+ ${prompt}`);
141713
+ const prevGhToken = process.env.GH_TOKEN;
141714
+ const prevGithubToken = process.env.GITHUB_TOKEN;
141715
+ process.env.GH_TOKEN = credEnv.GH_TOKEN;
141716
+ process.env.GITHUB_TOKEN = credEnv.GITHUB_TOKEN;
141717
+ const agentFn = chooseAgent("goose");
141718
+ let result;
141719
+ try {
141720
+ result = await agentFn({
141721
+ prompt,
141722
+ apiKey,
141723
+ cwd: repoPath,
141724
+ session: code.session,
141725
+ model: code.model
141726
+ });
141727
+ } finally {
141728
+ if (prevGhToken !== void 0) {
141729
+ process.env.GH_TOKEN = prevGhToken;
141730
+ } else {
141731
+ delete process.env.GH_TOKEN;
141732
+ }
141733
+ if (prevGithubToken !== void 0) {
141734
+ process.env.GITHUB_TOKEN = prevGithubToken;
141735
+ } else {
141736
+ delete process.env.GITHUB_TOKEN;
141737
+ }
141738
+ }
141739
+ const output = typeof result === "string" ? result : result.output;
141740
+ results.commits.push(output);
141741
+ const branchMatch = output.match(
141742
+ /(?:on branch|switched to|branch)\s+[`'"]?([^\s`'"]+)/i
141743
+ );
141744
+ if (branchMatch) {
141745
+ results.branches[repoName] = branchMatch[1];
141746
+ }
141747
+ const prMatch = output.match(
141748
+ /https:\/\/github\.com\/[^\s]+\/pull\/\d+/
141749
+ );
141750
+ if (prMatch) {
141751
+ results.prs[repoName] = prMatch[0];
141752
+ }
141753
+ } finally {
141754
+ await teardown();
141755
+ }
141756
+ }
141757
+ finishReq(request_id, {
141758
+ success: true,
141759
+ ...results
141760
+ });
141761
+ })().catch((error88) => {
141762
+ error("agent/push error:", error88);
141763
+ failReq(request_id, error88);
141764
+ });
141765
+ res.json({ request_id, status: "pending" });
141766
+ } catch (e) {
141767
+ error("agent/push setup error:", e);
141768
+ failReq(request_id, e);
141769
+ res.json({ request_id, status: "pending" });
141770
+ }
141771
+ }
141516
141772
 
141517
141773
  // src/proxy/code_actions.ts
141518
141774
  var fs14 = __toESM(require("fs/promises"), 1);
@@ -142251,6 +142507,35 @@ async function startProxyServer() {
142251
142507
  }
142252
142508
  clearInterval(keepAliveInterval);
142253
142509
  });
142510
+ app.get("/ide-auth", async (req, res) => {
142511
+ const fallback = () => res.redirect(302, "/");
142512
+ try {
142513
+ const { token, expires } = req.query;
142514
+ const password = process.env.CODE_SERVER_PASSWORD;
142515
+ const port = process.env.CODE_SERVER_PORT || "8444";
142516
+ if (!token || !expires || !password) return fallback();
142517
+ const expiresNum = parseInt(expires, 10);
142518
+ if (isNaN(expiresNum) || Math.floor(Date.now() / 1e3) > expiresNum) return fallback();
142519
+ const crypto3 = await import("crypto");
142520
+ const expected = crypto3.createHmac("sha256", password).update(`ide-auth:${expires}`).digest("hex");
142521
+ const tA = Buffer.from(token);
142522
+ const tB = Buffer.from(expected);
142523
+ if (tA.length !== tB.length || !crypto3.timingSafeEqual(tA, tB)) return fallback();
142524
+ const loginRes = await fetch(`http://localhost:${port}/login`, {
142525
+ method: "POST",
142526
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
142527
+ body: new URLSearchParams({ password }).toString(),
142528
+ redirect: "manual"
142529
+ });
142530
+ const setCookieHeader = loginRes.headers.get("set-cookie") || "";
142531
+ const keyMatch = setCookieHeader.match(/(?:^|,)\s*key=([^;,]+)/);
142532
+ if (!keyMatch) return fallback();
142533
+ res.setHeader("Set-Cookie", `key=${keyMatch[1]}; Path=/; SameSite=Lax; HttpOnly`);
142534
+ res.redirect(302, "/");
142535
+ } catch {
142536
+ fallback();
142537
+ }
142538
+ });
142254
142539
  app.use(requireAuth);
142255
142540
  app.post("/session", async (req, res) => {
142256
142541
  const { sessionId, webhookUrl, apiKey, searchApiKey, model, agent } = req.body;
@@ -142307,6 +142592,10 @@ async function startProxyServer() {
142307
142592
  system: req.body.system
142308
142593
  }))
142309
142594
  );
142595
+ app.post("/agent/push", async (req, res) => {
142596
+ log("===> POST /agent/push");
142597
+ await handleAgentPush(req, res);
142598
+ });
142310
142599
  app.post(
142311
142600
  "/repair",
142312
142601
  createAsyncAgentHandler(
@@ -10987,7 +10987,7 @@ var glob = Object.assign(glob_, {
10987
10987
  glob.glob = glob;
10988
10988
 
10989
10989
  // src/proxy/version.ts
10990
- var VERSION = "0.4.9";
10990
+ var VERSION = "0.4.11";
10991
10991
 
10992
10992
  // src/deps.ts
10993
10993
  var import_child_process = require("child_process");
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "staklink",
3
3
  "displayName": "staklink",
4
4
  "description": "staklink process manager",
5
- "version": "0.4.9",
5
+ "version": "0.4.11",
6
6
  "type": "module",
7
7
  "publisher": "stakwork",
8
8
  "engines": {
@@ -108,6 +108,7 @@
108
108
  "ask-gemini": "ts-node ./src/scripts/ask-gemini.ts",
109
109
  "test-git": "ts-node ./src/test/test-git.ts",
110
110
  "test-pm2": "ts-node ./src/test/test-pm2.ts",
111
+ "test-ide-auth": "npx tsx ./src/test/test-ide-auth.ts",
111
112
  "try-parse": "ts-node ./src/scripts/try-parse.ts",
112
113
  "try-gemini": "ts-node ./src/try/try-gemini.ts",
113
114
  "try-cc": "ts-node ./src/try/try-cc.ts",