Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/oauth/devin-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { posix, win32 } from "node:path";
import { DEVIN_CLI_INSTALL_HINT } from "../adapters/devin-cli/binary";
import { identityFromApiKey } from "./devin";
import { resolveDevinApiBaseUrl } from "./devin/api-base";
Expand Down Expand Up @@ -58,12 +58,13 @@ export function devinCliCredentialsPath(
// Absolute only. A relative override would resolve against whatever directory
// the proxy happens to be running in, which is not a location a user can mean.
if (override && (override.startsWith("/") || /^[A-Za-z]:[\\/]/.test(override))) return override;
const paths = platform === "win32" ? win32 : posix;
if (platform === "win32") {
const appData = env.APPDATA ?? join(homedir(), "AppData", "Roaming");
return join(appData, "devin", "credentials.toml");
const appData = env.APPDATA ?? paths.join(homedir(), "AppData", "Roaming");
return paths.join(appData, "devin", "credentials.toml");
}
const dataHome = env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
return join(dataHome, "devin", "credentials.toml");
const dataHome = env.XDG_DATA_HOME ?? paths.join(homedir(), ".local", "share");
return paths.join(dataHome, "devin", "credentials.toml");
}

export interface DevinCliCredentialFile {
Expand Down
2 changes: 2 additions & 0 deletions structure/providers/xai-grok.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi

Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch)
privately to final dispatch; preliminary route selection does not inject Go-only headers.

Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged.
2 changes: 2 additions & 0 deletions structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,5 @@ Cline CLI joins the existing export/client integration registries. Explicit CLI
Config JSON preserves the boolean; only literal true activates the role-changing transform.

The lightweight top-level CLI help counts Cline CLI among the fifteen registered export clients; registry parity remains covered by the client help and integration tests.

Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged.
2 changes: 2 additions & 0 deletions structure/transports/inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi

Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch)
privately to final dispatch; preliminary route selection does not inject Go-only headers.

Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged.
12 changes: 10 additions & 2 deletions tests/providers/devin-cli-adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { join } from "node:path";
import { describe, expect, test } from "bun:test";
import {
ACP_SESSION_NEW_ID,
Expand Down Expand Up @@ -213,13 +214,20 @@ describe("devin cli discovery", () => {

test("known install paths are preferred over a shadowed PATH entry, and absence is undefined", () => {
const previous = process.env[DEVIN_CLI_BIN_ENV];
const previousPath = process.env.PATH;
delete process.env[DEVIN_CLI_BIN_ENV];
process.env.PATH = join("/shadow", "bin");
try {
const only = (p: string) => p === "/home/u/.local/bin/devin";
expect(resolveDevinCliBinary({ exists: only, home: "/home/u", useCache: false })).toBe("/home/u/.local/bin/devin");
const expected = join("/home/u", ".local", "bin", "devin");
const shadowed = join("/shadow", "bin", "devin");
const exists = (p: string) => p === expected || p === shadowed;
expect(resolveDevinCliBinary({ exists, home: "/home/u", useCache: false })).toBe(expected);
expect(resolveDevinCliBinary({ exists: p => p === shadowed, home: "/home/u", useCache: false })).toBe(shadowed);
expect(resolveDevinCliBinary({ exists: () => false, home: "/home/u", useCache: false })).toBeUndefined();
} finally {
if (previous !== undefined) process.env[DEVIN_CLI_BIN_ENV] = previous;
if (previousPath === undefined) delete process.env.PATH;
else process.env.PATH = previousPath;
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion tests/providers/devin-cli-login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("devin-cli credentials path", () => {

test("Windows uses APPDATA", () => {
expect(devinCliCredentialsPath({ APPDATA: "C:\\Users\\u\\AppData\\Roaming" }, "win32"))
.toContain("devin");
.toBe("C:\\Users\\u\\AppData\\Roaming\\devin\\credentials.toml");
});

test("the override must be absolute", () => {
Expand Down
Loading