Skip to content
19 changes: 19 additions & 0 deletions apps/ade-cli/src/bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { describe, expect, it, vi } from "vitest";
import { createEventBuffer, type BufferedEvent } from "./eventBuffer";
import { createPrEventFanout } from "./prEventFanout";
import { isSourceCheckoutRuntimeModule } from "./runtimePackaging";

describe("isSourceCheckoutRuntimeModule", () => {
it.each([
"/Users/developer/ADE/apps/ade-cli/src/bootstrap.ts",
"/Users/developer/ADE/apps/ade-cli/dist/cli.cjs",
"/Users/developer/ADE/apps/ade-cli/dist/bootstrap.cjs",
"/Users/developer/ADE/apps/desktop/dist/main/main.cjs",
])("classifies a source-checkout module as development: %s", (modulePath) => {
expect(isSourceCheckoutRuntimeModule(modulePath)).toBe(true);
});

it.each([
"/Applications/ADE.app/Contents/Resources/app.asar/dist/main/main.cjs",
"/Applications/ADE.app/Contents/Resources/ade-cli/cli.cjs",
])("classifies a packaged module as packaged: %s", (modulePath) => {
expect(isSourceCheckoutRuntimeModule(modulePath)).toBe(false);
});
});

describe("createPrEventFanout", () => {
const event = {
Expand Down
12 changes: 8 additions & 4 deletions apps/ade-cli/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import * as nodePty from "node-pty";
import { isSourceCheckoutRuntimeModule } from "./runtimePackaging";
import { createFileLogger, type Logger } from "../../desktop/src/main/services/logging/logger";
import { classifySqliteOpenError, openKvDb, type AdeDb } from "../../desktop/src/main/services/state/kvDb";
import {
Expand Down Expand Up @@ -307,13 +308,16 @@ export function ensureAdePaths(projectRoot: string): AdeRuntimePaths {
};
}

function isSourceCheckoutRuntimeModule(modulePath: string): boolean {
return /[/\\]apps[/\\]ade-cli[/\\](?:src|dist)[/\\]bootstrap\.(?:ts|js|cjs)$/i.test(modulePath);
}

const currentModulePath =
typeof __filename === "string" ? __filename : fileURLToPath(import.meta.url);

if (
!isSourceCheckoutRuntimeModule(currentModulePath)
&& process.env.ADE_RUNTIME_PACKAGED === undefined
) {
process.env.ADE_RUNTIME_PACKAGED = "1";
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function automationsEnabledForHeadlessRuntime(): boolean {
const override = readAutomationsEnvOverride(process.env);
if (override !== null) return override;
Expand Down
39 changes: 39 additions & 0 deletions apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import {
summarizeExecution,
unwrapToolResult,
} from "./cli";
import {
DEVELOPMENT_ADE_CLERK_ISSUER,
DEVELOPMENT_ADE_CLERK_OAUTH_CLIENT_ID,
} from "../../desktop/src/shared/accountDirectory";
import { generateRpcAuthToken } from "./rpcAuth";
import { JsonRpcClient } from "./tuiClient/jsonRpcClient";
import { EncryptedFileCredentialStore } from "./services/credentials/credentialStore";
Expand Down Expand Up @@ -344,6 +348,41 @@ describe("ADE CLI", () => {
.toBe("loopback");
});

it("treats rejected packaged development env credentials as absent when selecting login mode", () => {
const developmentAccessToken = [
Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url"),
Buffer.from(JSON.stringify({
iss: DEVELOPMENT_ADE_CLERK_ISSUER,
sub: "development-user",
exp: Math.floor(Date.now() / 1000) + 3_600,
})).toString("base64url"),
"signature",
].join(".");
const developmentRefreshToken = `ade_account_v1.${Buffer.from(JSON.stringify({
version: 1,
refreshToken: "development-refresh-token",
issuer: DEVELOPMENT_ADE_CLERK_ISSUER,
clientId: DEVELOPMENT_ADE_CLERK_OAUTH_CLIENT_ID,
}), "utf8").toString("base64url")}`;

for (const credential of [developmentAccessToken, developmentRefreshToken]) {
const env = {
ADE_RUNTIME_PACKAGED: "1",
ADE_ACCOUNT_TOKEN: credential,
DISPLAY: ":0",
} as NodeJS.ProcessEnv;
expect(detectAccountLoginMode({ env, platform: "linux" })).toBe("loopback");
expect(detectAccountLoginMode({
env: { ...env, SSH_CONNECTION: "host details" },
platform: "linux",
})).toBe("device");
expect(detectAccountLoginMode({
env: { ...env, ADE_ALLOW_DEVELOPMENT_CLERK: "1" },
platform: "linux",
})).toBe("env-token");
}
});

it("formats account auth sources and durable-token provisioning guidance", () => {
expect(formatOutput({
signedIn: true,
Expand Down
9 changes: 8 additions & 1 deletion apps/ade-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import type { AdeRuntime } from "./bootstrap";
import { reseedBundledAdeSkillsForCli } from "./bootstrap";
import { EncryptedFileCredentialStore } from "./services/credentials/credentialStore";
import type { AccountMachinePublisherService } from "./services/account/accountMachinePublisherService";
import { shouldRejectDevelopmentEnvCredential } from "./services/account/accountAuthService";
import { DEFAULT_SYNC_HOST_PORT } from "./services/sync/syncProtocol";
import {
runAdeCodeRemote,
Expand Down Expand Up @@ -18213,7 +18214,13 @@ export function detectAccountLoginMode(args: {
} = {}): AccountLoginMode {
const env = args.env ?? process.env;
if (args.explicitHeadless) return "device";
if (env.ADE_ACCOUNT_TOKEN?.trim()) return "env-token";
const envCredential = env.ADE_ACCOUNT_TOKEN?.trim();
if (
envCredential
&& !shouldRejectDevelopmentEnvCredential(env, envCredential)
) {
return "env-token";
}
if (args.browserOpenFailed) return "device";
if (env.SSH_TTY?.trim() || env.SSH_CONNECTION?.trim() || env.SSH_CLIENT?.trim()) {
return "device";
Expand Down
5 changes: 5 additions & 0 deletions apps/ade-cli/src/runtimePackaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function isSourceCheckoutRuntimeModule(modulePath: string): boolean {
return /(?:^|[/\\])apps[/\\](?:ade-cli|desktop)[/\\](?:src|dist)[/\\]/i.test(
modulePath,
);
}
Loading