Skip to content

Commit c2c5bb3

Browse files
authored
merge apps/server + apps/web into apps/local (#83)
- @executor/server + @executor/web → @executor/local - src/server/ has executor creation, API composition, MCP - src/web/ has React frontend (pages, shell, router) - CLI and desktop import from @executor/local - Update all references, remove old directories
2 parents 2b792ba + 21d2d04 commit c2c5bb3

35 files changed

Lines changed: 670 additions & 513 deletions

‎.changeset/config.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
"baseBranch": "main",
99
"updateInternalDependencies": "patch",
1010
"ignore": [
11-
"@executor/server",
12-
"@executor/web",
11+
"@executor/local",
1312
"@executor/react",
1413
"@executor/api",
1514
"@executor/config",

‎apps/cli/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@effect/platform-bun": "catalog:",
2424
"@executor/api": "workspace:*",
2525
"@executor/runtime-quickjs": "workspace:*",
26-
"@executor/server": "workspace:*",
26+
"@executor/local": "workspace:*",
2727
"@jitl/quickjs-wasmfile-release-sync": "*",
2828
"quickjs-emscripten": "*",
2929
"effect": "catalog:"

‎apps/cli/src/build.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { $ } from "bun";
77

88
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
99
const cliRoot = resolve(repoRoot, "apps/cli");
10-
const webRoot = resolve(repoRoot, "apps/web");
10+
const webRoot = resolve(repoRoot, "apps/local");
1111
const distDir = resolve(cliRoot, "dist");
1212

1313
const resolveQuickJsWasmPath = (): string => {

‎apps/cli/src/main.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import * as Option from "effect/Option";
2828
import * as Cause from "effect/Cause";
2929

3030
import { ExecutorApi } from "@executor/api";
31-
import { createServerHandlers, runMcpStdioServer, getExecutor } from "@executor/server";
31+
import { createServerHandlers, runMcpStdioServer, getExecutor } from "@executor/local";
3232

3333
// ---------------------------------------------------------------------------
3434
// Constants
@@ -141,7 +141,7 @@ const makeApiClient = (baseUrl: string) =>
141141
// Static file serving from embedded web UI
142142
// ---------------------------------------------------------------------------
143143

144-
const WEB_DIST_DIR = resolve(import.meta.dirname, "../../web/dist");
144+
const WEB_DIST_DIR = resolve(import.meta.dirname, "../../local/dist");
145145

146146
const serveStatic = async (pathname: string): Promise<Response | null> => {
147147
const key = pathname.replace(/^\//, "");
@@ -156,7 +156,7 @@ const serveStatic = async (pathname: string): Promise<Response | null> => {
156156
});
157157
}
158158

159-
// Dev mode: serve from apps/web/dist on disk
159+
// Dev mode: serve from apps/local/dist on disk
160160
const filePath = resolve(WEB_DIST_DIR, key);
161161
if (!filePath.startsWith(WEB_DIST_DIR)) return null;
162162

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
</head>
1111
<body>
1212
<div id="root"></div>
13-
<script type="module" src="/src/main.tsx"></script>
13+
<script type="module" src="/src/web/main.tsx"></script>
1414
</body>
1515
</html>
Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@executor/server",
2+
"name": "@executor/local",
33
"version": "1.4.0",
44
"private": true,
55
"type": "module",
@@ -11,28 +11,45 @@
1111
"@effect/platform-node": "catalog:",
1212
"@effect/sql": "catalog:",
1313
"@effect/sql-sqlite-bun": "catalog:",
14-
"@executor/config": "workspace:*",
1514
"@executor/api": "workspace:*",
15+
"@executor/config": "workspace:*",
1616
"@executor/execution": "workspace:*",
17+
"@executor/host-mcp": "workspace:*",
1718
"@executor/plugin-file-secrets": "workspace:*",
1819
"@executor/plugin-google-discovery": "workspace:*",
20+
"@executor/plugin-graphql": "workspace:*",
1921
"@executor/plugin-keychain": "workspace:*",
20-
"@executor/plugin-onepassword": "workspace:*",
2122
"@executor/plugin-mcp": "workspace:*",
22-
"@executor/plugin-graphql": "workspace:*",
23+
"@executor/plugin-onepassword": "workspace:*",
2324
"@executor/plugin-openapi": "workspace:*",
25+
"@executor/react": "workspace:*",
2426
"@executor/sdk": "workspace:*",
25-
"@executor/host-mcp": "workspace:*",
26-
"@modelcontextprotocol/sdk": "^1.12.1",
2727
"@executor/storage-file": "workspace:*",
28-
"effect": "catalog:"
28+
"@executor/ui": "workspace:*",
29+
"@modelcontextprotocol/sdk": "^1.12.1",
30+
"@shikijs/langs": "^4.0.2",
31+
"@shikijs/themes": "^4.0.2",
32+
"@tanstack/react-router": "^1.168.10",
33+
"effect": "catalog:",
34+
"react": "^19.1.0",
35+
"react-dom": "^19.1.0",
36+
"shiki": "^4.0.2",
37+
"streamdown": "^2.5.0",
38+
"tailwindcss": "^4.1.10"
2939
},
3040
"devDependencies": {
41+
"@tailwindcss/vite": "^4.1.10",
3142
"@types/node": "catalog:",
43+
"@types/react": "^19.1.0",
44+
"@types/react-dom": "^19.1.0",
45+
"@vitejs/plugin-react": "^4.5.2",
3246
"bun-types": "catalog:",
33-
"typescript": "^5.9.3"
47+
"typescript": "^5.9.3",
48+
"vite": "^6.3.5"
3449
},
3550
"scripts": {
51+
"dev": "bunx --bun vite",
52+
"build": "vite build",
3653
"typecheck": "tsc --noEmit"
3754
}
3855
}

‎apps/local/src/index.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { createServerHandlers, type ServerHandlers } from "./server/main";
2+
export { createExecutorHandle, disposeExecutor, getExecutor, reloadExecutor, type ExecutorHandle, type LocalExecutor } from "./server/executor";
3+
export { createMcpRequestHandler, runMcpStdioServer, type McpRequestHandler } from "./server/mcp";
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1-
import { createServerHandlersWithExecutor, type ServerHandlers } from "./main";
2-
import { createServerExecutorHandle } from "./services/executor";
1+
import { createServerHandlers, type ServerHandlers } from "./main";
32

43
export type HotBackend = ServerHandlers & {
54
readonly dispose: () => Promise<void>;
65
};
76

87
export const createHotBackend = async (): Promise<HotBackend> => {
9-
const handle = await createServerExecutorHandle();
10-
const handlers = await createServerHandlersWithExecutor(handle.executor);
8+
const handlers = await createServerHandlers();
119

1210
return {
1311
...handlers,
1412
dispose: async () => {
1513
await handlers.api.dispose().catch(() => undefined);
1614
await handlers.mcp.close().catch(() => undefined);
17-
await handle.dispose();
1815
},
1916
};
2017
};
Lines changed: 84 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Effect } from "effect";
1+
import { Effect, Layer, ManagedRuntime, Context } from "effect";
22
import { SqliteClient } from "@effect/sql-sqlite-bun";
33
import * as SqlClient from "@effect/sql/SqlClient";
44
import { NodeFileSystem } from "@effect/platform-node";
@@ -45,81 +45,103 @@ const resolveDbPath = (): string => {
4545
};
4646

4747
// ---------------------------------------------------------------------------
48-
// Create a local executor — returns the full typed executor
48+
// Local plugins — defined once, used for both the layer and type inference
4949
// ---------------------------------------------------------------------------
5050

51-
export const createLocalExecutor = async () => {
51+
const createLocalPlugins = (
52+
scopedKv: ReturnType<typeof makeScopedKv>,
53+
configPath: string,
54+
fsLayer: typeof NodeFileSystem.layer,
55+
) =>
56+
[
57+
openApiPlugin({
58+
operationStore: withConfigFile.openapi(
59+
makeKvOperationStore(scopedKv, "openapi"),
60+
configPath,
61+
fsLayer,
62+
),
63+
}),
64+
mcpPlugin({
65+
bindingStore: withConfigFile.mcp(
66+
makeKvBindingStore(scopedKv, "mcp"),
67+
configPath,
68+
fsLayer,
69+
),
70+
}),
71+
googleDiscoveryPlugin({
72+
bindingStore: makeKvGoogleDiscoveryBindingStore(
73+
scopedKv,
74+
"google-discovery",
75+
),
76+
}),
77+
graphqlPlugin({
78+
operationStore: withConfigFile.graphql(
79+
makeKvGraphqlOperationStore(scopedKv, "graphql"),
80+
configPath,
81+
fsLayer,
82+
),
83+
}),
84+
keychainPlugin(),
85+
fileSecretsPlugin(),
86+
onepasswordPlugin({
87+
kv: scopeKv(scopedKv, "onepassword"),
88+
}),
89+
] as const;
90+
91+
// Full typed executor — inferred from plugin list
92+
type LocalPlugins = ReturnType<typeof createLocalPlugins>;
93+
94+
// Private tag preserving the full plugin type
95+
class LocalExecutorTag extends Context.Tag("@executor/local/Executor")<
96+
LocalExecutorTag,
97+
Effect.Effect.Success<ReturnType<typeof createExecutor<LocalPlugins>>>
98+
>() {}
99+
100+
export type LocalExecutor = Context.Tag.Service<typeof LocalExecutorTag>;
101+
102+
// ---------------------------------------------------------------------------
103+
// Layer — SQLite-backed, keeps connection alive via ManagedRuntime
104+
// ---------------------------------------------------------------------------
105+
106+
const createLocalExecutorLayer = () => {
52107
const dbPath = resolveDbPath();
53108

54-
const program = Effect.gen(function* () {
55-
const sql = yield* SqlClient.SqlClient;
56-
57-
yield* migrate.pipe(Effect.catchAll((e) => Effect.die(e)));
58-
59-
const cwd = process.env.EXECUTOR_SCOPE_DIR || process.cwd();
60-
const kv = makeSqliteKv(sql);
61-
const config = makeKvConfig(kv, { cwd });
62-
const scopedKv = makeScopedKv(kv, cwd);
63-
64-
const configPath = join(cwd, "executor.jsonc");
65-
const fsLayer = NodeFileSystem.layer;
66-
67-
return yield* createExecutor({
68-
...config,
69-
plugins: [
70-
openApiPlugin({
71-
operationStore: withConfigFile.openapi(
72-
makeKvOperationStore(scopedKv, "openapi"),
73-
configPath,
74-
fsLayer,
75-
),
76-
}),
77-
mcpPlugin({
78-
bindingStore: withConfigFile.mcp(
79-
makeKvBindingStore(scopedKv, "mcp"),
80-
configPath,
81-
fsLayer,
82-
),
83-
}),
84-
googleDiscoveryPlugin({
85-
bindingStore: makeKvGoogleDiscoveryBindingStore(
86-
scopedKv,
87-
"google-discovery",
88-
),
89-
}),
90-
graphqlPlugin({
91-
operationStore: withConfigFile.graphql(
92-
makeKvGraphqlOperationStore(scopedKv, "graphql"),
93-
configPath,
94-
fsLayer,
95-
),
96-
}),
97-
keychainPlugin(),
98-
fileSecretsPlugin(),
99-
onepasswordPlugin({
100-
kv: scopeKv(scopedKv, "onepassword"),
101-
}),
102-
] as const,
103-
});
104-
}).pipe(
105-
Effect.provide(SqliteClient.layer({ filename: dbPath })),
106-
);
107-
108-
return Effect.runPromise(program);
109+
return Layer.effect(
110+
LocalExecutorTag,
111+
Effect.gen(function* () {
112+
const sql = yield* SqlClient.SqlClient;
113+
114+
yield* migrate.pipe(Effect.catchAll((e) => Effect.die(e)));
115+
116+
const cwd = process.env.EXECUTOR_SCOPE_DIR || process.cwd();
117+
const kv = makeSqliteKv(sql);
118+
const config = makeKvConfig(kv, { cwd });
119+
const scopedKv = makeScopedKv(kv, cwd);
120+
const configPath = join(cwd, "executor.jsonc");
121+
const fsLayer = NodeFileSystem.layer;
122+
123+
return yield* createExecutor({
124+
...config,
125+
plugins: createLocalPlugins(scopedKv, configPath, fsLayer),
126+
});
127+
}),
128+
).pipe(Layer.provide(SqliteClient.layer({ filename: dbPath })));
109129
};
110130

111131
// ---------------------------------------------------------------------------
112-
// Shared singleton for production, scoped handles for dev HMR
132+
// Handle — keeps runtime alive, returns fully typed executor
113133
// ---------------------------------------------------------------------------
114134

115-
export type LocalExecutor = Awaited<ReturnType<typeof createLocalExecutor>>;
116-
117135
export const createExecutorHandle = async () => {
118-
const executor = await createLocalExecutor();
136+
const layer = createLocalExecutorLayer();
137+
const runtime = ManagedRuntime.make(layer);
138+
const executor = await runtime.runPromise(LocalExecutorTag);
139+
119140
return {
120141
executor,
121142
dispose: async () => {
122143
await Effect.runPromise(executor.close()).catch(() => undefined);
144+
await runtime.dispose().catch(() => undefined);
123145
},
124146
};
125147
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { McpGroup, McpHandlers, McpExtensionService } from "@executor/plugin-mcp
1313
import { GoogleDiscoveryGroup, GoogleDiscoveryHandlers, GoogleDiscoveryExtensionService } from "@executor/plugin-google-discovery/api";
1414
import { OnePasswordGroup, OnePasswordHandlers, OnePasswordExtensionService } from "@executor/plugin-onepassword/api";
1515
import { GraphqlGroup, GraphqlHandlers, GraphqlExtensionService } from "@executor/plugin-graphql/api";
16-
import { createLocalExecutor } from "./services/executor";
16+
import { getExecutor } from "./executor";
1717
import { createMcpRequestHandler, type McpRequestHandler } from "./mcp";
1818

1919
// ---------------------------------------------------------------------------
@@ -50,7 +50,7 @@ export type ServerHandlers = {
5050
};
5151

5252
export const createServerHandlers = async (): Promise<ServerHandlers> => {
53-
const executor = await createLocalExecutor();
53+
const executor = await getExecutor();
5454
const engine = createExecutionEngine({ executor });
5555

5656
const pluginExtensions = Layer.mergeAll(

0 commit comments

Comments
 (0)