Skip to content
Draft
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
1 change: 1 addition & 0 deletions apps/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hotkeys-hook": "^4.4.4",
"react-scan": "^0.5.6",
"reflect-metadata": "^0.2.2",
"semver": "^7.6.0",
"shadcn": "^4.1.2",
Expand Down
26 changes: 26 additions & 0 deletions apps/code/src/main/di/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@ import type {
} from "@posthog/platform/analytics";
import type { APP_LIFECYCLE_SERVICE } from "@posthog/platform/app-lifecycle";
import type { APP_META_SERVICE } from "@posthog/platform/app-meta";
import type { APP_METRICS_SERVICE } from "@posthog/platform/app-metrics";
import type { BUNDLED_RESOURCES_SERVICE } from "@posthog/platform/bundled-resources";
import type { CLIPBOARD_SERVICE } from "@posthog/platform/clipboard";
import type { CONTEXT_MENU_SERVICE } from "@posthog/platform/context-menu";
import type { CRYPTO_SERVICE } from "@posthog/platform/crypto";
import type { DEEP_LINK_SERVICE } from "@posthog/platform/deep-link";
import type { DEV_HOST_ACTIONS_SERVICE } from "@posthog/platform/dev-host-actions";
import type { DIALOG_SERVICE } from "@posthog/platform/dialog";
import type { FILE_ICON_SERVICE } from "@posthog/platform/file-icon";
import type { IMAGE_PROCESSOR_SERVICE } from "@posthog/platform/image-processor";
Expand All @@ -122,11 +124,13 @@ import type { WORKSPACE_SETTINGS_SERVICE } from "@posthog/platform/workspace-set
import type { WorkspaceClient } from "@posthog/workspace-client/client";
import type { DatabaseService } from "@posthog/workspace-server/db/service";
import type { GIT_SERVICE as WS_GIT_SERVICE } from "@posthog/workspace-server/di/tokens";
import type { AgentService } from "@posthog/workspace-server/services/agent/agent";
import type {
AGENT_AUTH,
AGENT_LOGGER,
AGENT_MCP_APPS,
AGENT_REPO_FILES,
AGENT_SERVICE,
AGENT_SLEEP_COORDINATOR,
} from "@posthog/workspace-server/services/agent/identifiers";
import type {
Expand Down Expand Up @@ -203,10 +207,12 @@ import type { WorkspaceService } from "@posthog/workspace-server/services/worksp
import type { FileWatcherBridge } from "../index";
import type { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
import type { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
import type { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
import type { ElectronBundledResources } from "../platform-adapters/electron-bundled-resources";
import type { ElectronClipboard } from "../platform-adapters/electron-clipboard";
import type { ElectronContextMenu } from "../platform-adapters/electron-context-menu";
import type { ElectronCrypto } from "../platform-adapters/electron-crypto";
import type { ElectronDevHostActions } from "../platform-adapters/electron-dev-host-actions";
import type { ElectronDialog } from "../platform-adapters/electron-dialog";
import type { ElectronFileIcon } from "../platform-adapters/electron-file-icon";
import type { ElectronImageProcessor } from "../platform-adapters/electron-image-processor";
Expand All @@ -227,6 +233,11 @@ import type {
TokenCipherPortAdapter,
} from "../services/auth/port-adapters";
import type { DeepLinkService } from "../services/deep-link/service";
import type { DevActionsService } from "../services/dev-actions/service";
import type { DevFlagsService } from "../services/dev-flags/service";
import type { DevLogsService } from "../services/dev-logs/service";
import type { DevMetricsService } from "../services/dev-metrics/service";
import type { DevNetworkService } from "../services/dev-network/service";
import type { EncryptionService } from "../services/encryption/service";
import type { SecureStoreService } from "../services/secure-store/service";
import type { settingsStore } from "../services/settingsStore";
Expand All @@ -243,6 +254,11 @@ import type {
DATABASE_SERVICE as MAIN_DATABASE_SERVICE,
DEEP_LINK_SERVICE as MAIN_DEEP_LINK_SERVICE,
DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY as MAIN_DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY,
DEV_ACTIONS_SERVICE as MAIN_DEV_ACTIONS_SERVICE,
DEV_FLAGS_SERVICE as MAIN_DEV_FLAGS_SERVICE,
DEV_LOGS_SERVICE as MAIN_DEV_LOGS_SERVICE,
DEV_METRICS_SERVICE as MAIN_DEV_METRICS_SERVICE,
DEV_NETWORK_SERVICE as MAIN_DEV_NETWORK_SERVICE,
ENCRYPTION_SERVICE as MAIN_ENCRYPTION_SERVICE,
EXTERNAL_APPS_SERVICE as MAIN_EXTERNAL_APPS_SERVICE,
FILE_WATCHER_SERVICE as MAIN_FILE_WATCHER_SERVICE,
Expand Down Expand Up @@ -292,6 +308,8 @@ export interface MainBindings {
[BUNDLED_RESOURCES_SERVICE]: ElectronBundledResources;
[IMAGE_PROCESSOR_SERVICE]: ElectronImageProcessor;
[WORKSPACE_SETTINGS_SERVICE]: ElectronWorkspaceSettings;
[APP_METRICS_SERVICE]: ElectronAppMetrics;
[DEV_HOST_ACTIONS_SERVICE]: ElectronDevHostActions;

// Database (main aliases + ws-server source tokens via toService)
[MAIN_DATABASE_SERVICE]: DatabaseService;
Expand Down Expand Up @@ -426,6 +444,13 @@ export interface MainBindings {
[MAIN_ENCRYPTION_SERVICE]: EncryptionService;
[CANVAS_GEN_SERVICE]: CanvasGenService;

// Dev toolbar diagnostics
[MAIN_DEV_FLAGS_SERVICE]: DevFlagsService;
[MAIN_DEV_METRICS_SERVICE]: DevMetricsService;
[MAIN_DEV_NETWORK_SERVICE]: DevNetworkService;
[MAIN_DEV_LOGS_SERVICE]: DevLogsService;
[MAIN_DEV_ACTIONS_SERVICE]: DevActionsService;

// ws-server git service (bound to(GitService))
[WS_GIT_SERVICE]: GitService;

Expand All @@ -443,6 +468,7 @@ export interface MainBindings {
[FS_SERVICE]: FsCapability;

// Typed container.get-only tokens (bound via loaded modules)
[AGENT_SERVICE]: AgentService;
[OAUTH_SERVICE]: OAuthService;
[GITHUB_INTEGRATION_SERVICE]: GitHubIntegrationService;
[SLACK_INTEGRATION_SERVICE]: SlackIntegrationService;
Expand Down
22 changes: 22 additions & 0 deletions apps/code/src/main/di/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ import { CanvasGenService } from "@posthog/host-router/services/canvas-gen.servi
import { ANALYTICS_SERVICE } from "@posthog/platform/analytics";
import { APP_LIFECYCLE_SERVICE } from "@posthog/platform/app-lifecycle";
import { APP_META_SERVICE } from "@posthog/platform/app-meta";
import { APP_METRICS_SERVICE } from "@posthog/platform/app-metrics";
import { BUNDLED_RESOURCES_SERVICE } from "@posthog/platform/bundled-resources";
import { CLIPBOARD_SERVICE } from "@posthog/platform/clipboard";
import { CONTEXT_MENU_SERVICE } from "@posthog/platform/context-menu";
import { CRYPTO_SERVICE } from "@posthog/platform/crypto";
import { DEEP_LINK_SERVICE } from "@posthog/platform/deep-link";
import { DEV_HOST_ACTIONS_SERVICE } from "@posthog/platform/dev-host-actions";
import { DIALOG_SERVICE } from "@posthog/platform/dialog";
import { FILE_ICON_SERVICE } from "@posthog/platform/file-icon";
import { IMAGE_PROCESSOR_SERVICE } from "@posthog/platform/image-processor";
Expand Down Expand Up @@ -210,10 +212,12 @@ import ExternalAppsStoreImpl from "electron-store";
import type { FileWatcherBridge } from "../index";
import { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
import { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
import { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
import { ElectronBundledResources } from "../platform-adapters/electron-bundled-resources";
import { ElectronClipboard } from "../platform-adapters/electron-clipboard";
import { ElectronContextMenu } from "../platform-adapters/electron-context-menu";
import { ElectronCrypto } from "../platform-adapters/electron-crypto";
import { ElectronDevHostActions } from "../platform-adapters/electron-dev-host-actions";
import { ElectronDialog } from "../platform-adapters/electron-dialog";
import { ElectronFileIcon } from "../platform-adapters/electron-file-icon";
import { ElectronImageProcessor } from "../platform-adapters/electron-image-processor";
Expand All @@ -236,6 +240,11 @@ import {
TokenCipherPortAdapter,
} from "../services/auth/port-adapters";
import { DeepLinkService } from "../services/deep-link/service";
import { DevActionsService } from "../services/dev-actions/service";
import { DevFlagsService } from "../services/dev-flags/service";
import { DevLogsService } from "../services/dev-logs/service";
import { DevMetricsService } from "../services/dev-metrics/service";
import { DevNetworkService } from "../services/dev-network/service";
import { EncryptionService } from "../services/encryption/service";
import { SecureStoreService } from "../services/secure-store/service";
import { settingsStore } from "../services/settingsStore";
Expand All @@ -255,6 +264,11 @@ import {
DATABASE_SERVICE as MAIN_DATABASE_SERVICE,
DEEP_LINK_SERVICE as MAIN_DEEP_LINK_SERVICE,
DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY as MAIN_DEFAULT_ADDITIONAL_DIRECTORY_REPOSITORY,
DEV_ACTIONS_SERVICE as MAIN_DEV_ACTIONS_SERVICE,
DEV_FLAGS_SERVICE as MAIN_DEV_FLAGS_SERVICE,
DEV_LOGS_SERVICE as MAIN_DEV_LOGS_SERVICE,
DEV_METRICS_SERVICE as MAIN_DEV_METRICS_SERVICE,
DEV_NETWORK_SERVICE as MAIN_DEV_NETWORK_SERVICE,
ENCRYPTION_SERVICE as MAIN_ENCRYPTION_SERVICE,
EXTERNAL_APPS_SERVICE as MAIN_EXTERNAL_APPS_SERVICE,
FS_SERVICE as MAIN_FS_SERVICE,
Expand Down Expand Up @@ -305,6 +319,8 @@ container.bind(CONTEXT_MENU_SERVICE).to(ElectronContextMenu);
container.bind(BUNDLED_RESOURCES_SERVICE).to(ElectronBundledResources);
container.bind(IMAGE_PROCESSOR_SERVICE).to(ElectronImageProcessor);
container.bind(WORKSPACE_SETTINGS_SERVICE).to(ElectronWorkspaceSettings);
container.bind(APP_METRICS_SERVICE).to(ElectronAppMetrics);
container.bind(DEV_HOST_ACTIONS_SERVICE).to(ElectronDevHostActions);

container.load(databaseModule);
container.load(repositoriesModule);
Expand Down Expand Up @@ -708,3 +724,9 @@ container.bind(MAIN_ENCRYPTION_SERVICE).to(EncryptionService);
// host-router routers.
container.load(canvasCoreModule);
container.bind(CANVAS_GEN_SERVICE).to(CanvasGenService).inSingletonScope();

container.bind(MAIN_DEV_FLAGS_SERVICE).to(DevFlagsService);
container.bind(MAIN_DEV_METRICS_SERVICE).to(DevMetricsService);
container.bind(MAIN_DEV_NETWORK_SERVICE).to(DevNetworkService);
container.bind(MAIN_DEV_LOGS_SERVICE).to(DevLogsService);
container.bind(MAIN_DEV_ACTIONS_SERVICE).to(DevActionsService);
20 changes: 20 additions & 0 deletions apps/code/src/main/di/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ export const WORKSPACE_SERVICE = Symbol.for(
export const WORKSPACE_SERVER_SERVICE = Symbol.for(
"posthog.host.main.workspace-server.service",
);
export const DEV_FLAGS_SERVICE = Symbol.for(
"posthog.host.main.dev-flags.service",
);
export const DEV_METRICS_SERVICE = Symbol.for(
"posthog.host.main.dev-metrics.service",
);
export const DEV_NETWORK_SERVICE = Symbol.for(
"posthog.host.main.dev-network.service",
);
export const DEV_LOGS_SERVICE = Symbol.for(
"posthog.host.main.dev-logs.service",
);
export const DEV_ACTIONS_SERVICE = Symbol.for(
"posthog.host.main.dev-actions.service",
);

export const MAIN_TOKENS = Object.freeze({
WorkspaceClient: WORKSPACE_CLIENT,
Expand Down Expand Up @@ -159,4 +174,9 @@ export const MAIN_TOKENS = Object.freeze({
ProvisioningService: PROVISIONING_SERVICE,
WorkspaceService: WORKSPACE_SERVICE,
WorkspaceServerService: WORKSPACE_SERVER_SERVICE,
DevFlagsService: DEV_FLAGS_SERVICE,
DevMetricsService: DEV_METRICS_SERVICE,
DevNetworkService: DEV_NETWORK_SERVICE,
DevLogsService: DEV_LOGS_SERVICE,
DevActionsService: DEV_ACTIONS_SERVICE,
});
3 changes: 3 additions & 0 deletions apps/code/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { MAIN_TOKENS } from "./di/tokens";
import { posthogNodeAnalytics } from "./platform-adapters/posthog-analytics";
import { registerMcpSandboxProtocol } from "./protocols/mcp-sandbox";
import type { AppLifecycleService } from "./services/app-lifecycle/service";
import { initDevToolbar } from "./services/dev-toolbar";
import {
focusSessionStore,
focusWorktreePaths,
Expand Down Expand Up @@ -217,6 +218,8 @@ app.on("child-process-gone", (_event, details) => {
});

async function initializeServices(): Promise<void> {
initDevToolbar();

container.get<DatabaseService>(MAIN_TOKENS.DatabaseService);
container.get<OAuthService>(OAUTH_SERVICE);
const authService = container.get<AuthService>(MAIN_TOKENS.AuthService);
Expand Down
21 changes: 21 additions & 0 deletions apps/code/src/main/platform-adapters/electron-app-metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {
AppProcessMetric,
IAppMetrics,
} from "@posthog/platform/app-metrics";
import { app } from "electron";
import { injectable } from "inversify";

@injectable()
export class ElectronAppMetrics implements IAppMetrics {
public getAppMetrics(): AppProcessMetric[] {
return app.getAppMetrics().map((m) => ({
pid: m.pid,
type: m.type,
name: m.name,
cpu: m.cpu ? { percentCPUUsage: m.cpu.percentCPUUsage } : undefined,
memory: m.memory
? { workingSetSize: m.memory.workingSetSize }
: undefined,
}));
}
}
25 changes: 25 additions & 0 deletions apps/code/src/main/platform-adapters/electron-dev-host-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { IDevHostActions } from "@posthog/platform/dev-host-actions";
import { app, BrowserWindow, shell } from "electron";
import { injectable } from "inversify";

@injectable()
export class ElectronDevHostActions implements IDevHostActions {
public async openPath(path: string): Promise<void> {
await shell.openPath(path);
}

public reloadAllWindows(): void {
for (const window of BrowserWindow.getAllWindows()) {
window.webContents.reload();
}
}

public relaunch(): void {
app.relaunch();
app.exit(0);
}

public crash(): void {
process.crash();
}
}
18 changes: 18 additions & 0 deletions apps/code/src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ import { contextBridge, webUtils } from "electron";
import "electron-log/preload";
import { parseSessionIdArg } from "./posthog-session-arg";

const DEV_FLAGS_CLI_PREFIX = "--posthog-code-flags=";

function readDevFlags(): { devMode: boolean } {
const arg = process.argv.find((a) => a.startsWith(DEV_FLAGS_CLI_PREFIX));
if (!arg) return { devMode: false };
try {
const payload = decodeURIComponent(arg.slice(DEV_FLAGS_CLI_PREFIX.length));
const parsed = JSON.parse(payload);
return { devMode: parsed?.devMode === true };
} catch {
return { devMode: false };
}
}

const devFlags = readDevFlags();

contextBridge.exposeInMainWorld("electronUtils", {
getPathForFile: (file: File) => webUtils.getPathForFile(file),
});
Expand All @@ -11,6 +27,8 @@ contextBridge.exposeInMainWorld("__posthogBootstrap", {
sessionId: parseSessionIdArg(process.argv),
});

contextBridge.exposeInMainWorld("__posthogCodeDevFlags", devFlags);

if (process.argv.includes("--posthog-code-dev")) {
contextBridge.exposeInMainWorld("__posthogCodeTest", {
crash: () => {
Expand Down
22 changes: 22 additions & 0 deletions apps/code/src/main/services/dev-actions/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from "zod";

export const devToastInput = z.object({
variant: z.enum(["info", "error"]),
message: z.string(),
});

export const devToastSchema = z.object({
id: z.number(),
variant: z.enum(["info", "error"]),
message: z.string(),
});

export type DevToast = z.infer<typeof devToastSchema>;

export const DevActionsEvent = {
Toast: "toast",
} as const;

export interface DevActionsEvents {
[DevActionsEvent.Toast]: DevToast;
}
71 changes: 71 additions & 0 deletions apps/code/src/main/services/dev-actions/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
DEV_HOST_ACTIONS_SERVICE,
type IDevHostActions,
} from "@posthog/platform/dev-host-actions";
import { TypedEventEmitter } from "@posthog/shared";
import { inject, injectable } from "inversify";
import { MAIN_TOKENS } from "../../di/tokens";
import { getUserDataDir } from "../../utils/env";
import { getLogFilePath, logger } from "../../utils/logger";
import type { DevNetworkService } from "../dev-network/service";
import {
DevActionsEvent,
type DevActionsEvents,
type DevToast,
} from "./schemas";

const log = logger.scope("dev-actions");

@injectable()
export class DevActionsService extends TypedEventEmitter<DevActionsEvents> {
private nextToastId = 1;

constructor(
@inject(MAIN_TOKENS.DevNetworkService)
private readonly network: DevNetworkService,
@inject(DEV_HOST_ACTIONS_SERVICE)
private readonly host: IDevHostActions,
) {
super();
}

async openUserDataDir(): Promise<void> {
await this.host.openPath(getUserDataDir());
}

async openLogFile(): Promise<void> {
await this.host.openPath(getLogFilePath());
}

reloadRenderer(): void {
this.host.reloadAllWindows();
}

restartMain(): void {
log.warn("Restarting main process from dev toolbar");
this.host.relaunch();
}

crashMain(): void {
log.warn("Crashing main process from dev toolbar");
this.host.crash();
}

triggerToast(variant: "info" | "error", message: string): DevToast {
const toast: DevToast = {
id: this.nextToastId++,
variant,
message,
};
this.emit(DevActionsEvent.Toast, toast);
return toast;
}

setOffline(offline: boolean): void {
this.network.setSim({ offline });
}

setSlowDelay(slowDelayMs: number): void {
this.network.setSim({ slowDelayMs });
}
}
Loading
Loading