Skip to content

Commit 03994d3

Browse files
committed
Add Codex worktree setup and run scripts
1 parent 9bc81ea commit 03994d3

7 files changed

Lines changed: 542 additions & 0 deletions

File tree

.codex/local-environment.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version = 1
2+
name = "SimDeck"
3+
4+
[setup]
5+
script = '''
6+
cd "$CODEX_WORKTREE_PATH"
7+
npm run codex:setup
8+
'''
9+
10+
[cleanup]
11+
script = '''
12+
cd "$CODEX_WORKTREE_PATH"
13+
npm run codex:cache:save
14+
'''
15+
16+
[[actions]]
17+
name = "Build and Restart Daemon"
18+
icon = "run"
19+
command = '''
20+
cd "$CODEX_WORKTREE_PATH"
21+
npm run codex:run
22+
'''
23+
platform = "darwin"

AGENTS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ npm run package:vscode
102102

103103
This now builds the Rust server in `server/` and copies the resulting binary to `build/simdeck`.
104104

105+
Codex worktrees can use the checked-in local environment config at
106+
`.codex/local-environment.toml`. Its setup runs `npm run codex:setup`, which
107+
hydrates root/client `node_modules` and `server/target` from the shared cache
108+
under `~/.cache/simdeck/codex-worktree-cache` or from another SimDeck checkout
109+
before falling back to `npm ci` for missing package installs and ensuring
110+
Homebrew `pkgconf`/`x264` are available for native builds. Its Run action
111+
executes `npm run codex:run`, which builds the CLI and client, saves fresh
112+
caches, and restarts the workspace-local daemon.
113+
105114
Run the local daemon:
106115

107116
```sh

CONTRIBUTING.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,40 @@ npx skills add NativeScript/SimDeck --skill simdeck -a codex -g
138138

139139
The npm postinstall message also prints this command after a global install.
140140

141+
## Codex local worktrees
142+
143+
This repo includes a Codex local environment at
144+
`.codex/local-environment.toml`. Use it when creating Codex worktrees for
145+
SimDeck. The setup script runs:
146+
147+
```sh
148+
npm run codex:setup
149+
```
150+
151+
That hydrates the root `node_modules`, `client/node_modules`, and
152+
`server/target` from `~/.cache/simdeck/codex-worktree-cache` or a matching
153+
existing SimDeck checkout. If either `node_modules` directory is still missing,
154+
it falls back to `npm ci` for that package so lockfiles stay unchanged. On
155+
macOS it also ensures the Homebrew `pkgconf` and `x264` packages are available
156+
for the native Rust build. Set
157+
`SIMDECK_CODEX_SKIP_BREW=1` if you want setup to report missing Homebrew
158+
packages instead of installing them.
159+
160+
The cleanup script saves fresh caches with:
161+
162+
```sh
163+
npm run codex:cache:save
164+
```
165+
166+
The environment also exposes a **Build and Restart Daemon** Run action:
167+
168+
```sh
169+
npm run codex:run
170+
```
171+
172+
It builds the Rust CLI and React client, saves the refreshed caches, and runs
173+
`./build/simdeck daemon restart` for the current workspace.
174+
141175
## Releasing
142176

143177
Releases are published from the `Release` GitHub Actions workflow at

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
"test:studio-provider": "node --test scripts/studio-provider-bridge.test.mjs scripts/studio-host-provider.test.mjs",
8080
"test:stress": "node scripts/stress/simdeck.mjs",
8181
"bench:encoder:build": "scripts/bench/build-encoder-benchmark.sh",
82+
"codex:setup": "node scripts/codex-setup.mjs",
83+
"codex:cache:save": "node scripts/codex-worktree-cache.mjs save --best-effort",
84+
"codex:run": "node scripts/codex-run.mjs",
8285
"ci": "npm run lint && npm run build:all && npm run test && npm run package:vscode-extension",
8386
"dev": "npm run build:cli && node scripts/dev.mjs",
8487
"preview:swiftui": "node scripts/experimental/swiftui-preview.mjs",

scripts/codex-run.mjs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env node
2+
import { spawnSync } from "node:child_process";
3+
import { dirname, resolve } from "node:path";
4+
import { fileURLToPath } from "node:url";
5+
6+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7+
8+
run("node", ["scripts/codex-setup.mjs", "--skip-npm"]);
9+
run("npm", ["run", "build:cli"]);
10+
run("npm", ["run", "build:client"]);
11+
run("node", ["scripts/codex-worktree-cache.mjs", "save", "--best-effort"]);
12+
13+
const daemonArgs = ["daemon", "restart"];
14+
pushOptionalEnv(daemonArgs, "--port", "SIMDECK_DAEMON_PORT");
15+
pushOptionalEnv(daemonArgs, "--bind", "SIMDECK_DAEMON_BIND");
16+
pushOptionalEnv(daemonArgs, "--advertise-host", "SIMDECK_ADVERTISE_HOST");
17+
pushOptionalEnv(daemonArgs, "--video-codec", "SIMDECK_VIDEO_CODEC");
18+
pushOptionalEnv(daemonArgs, "--stream-quality", "SIMDECK_STREAM_QUALITY");
19+
pushOptionalEnv(daemonArgs, "--local-stream-fps", "SIMDECK_LOCAL_STREAM_FPS");
20+
if (truthy(process.env.SIMDECK_LOW_LATENCY)) {
21+
daemonArgs.push("--low-latency");
22+
}
23+
24+
run("./build/simdeck", daemonArgs);
25+
26+
function run(command, args) {
27+
console.log(`\n$ ${[command, ...args].join(" ")}`);
28+
const result = spawnSync(command, args, {
29+
cwd: ROOT,
30+
stdio: "inherit",
31+
env: process.env,
32+
});
33+
if (result.error) {
34+
throw result.error;
35+
}
36+
if (result.status !== 0) {
37+
process.exit(result.status ?? 1);
38+
}
39+
}
40+
41+
function pushOptionalEnv(args, flag, envName) {
42+
const value = process.env[envName]?.trim();
43+
if (value) {
44+
args.push(flag, value);
45+
}
46+
}
47+
48+
function truthy(value) {
49+
return value === "1" || value === "true" || value === "yes";
50+
}

scripts/codex-setup.mjs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env node
2+
import { spawnSync } from "node:child_process";
3+
import { readdirSync, statSync } from "node:fs";
4+
import { dirname, resolve } from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8+
const args = process.argv.slice(2);
9+
const skipNpm = args.includes("--skip-npm");
10+
const skipCache = args.includes("--skip-cache");
11+
12+
if (!skipCache) {
13+
run("node", ["scripts/codex-worktree-cache.mjs", "hydrate"]);
14+
}
15+
16+
ensureNativeBuildDependencies();
17+
18+
if (!skipNpm) {
19+
ensureNodeModules(".", "root");
20+
ensureNodeModules("client", "client");
21+
}
22+
23+
function ensureNativeBuildDependencies() {
24+
if (process.platform !== "darwin") {
25+
return;
26+
}
27+
28+
if (!commandSucceeds("pkg-config", ["--version"])) {
29+
installBrewPackage("pkgconf", "pkg-config");
30+
}
31+
32+
if (!commandSucceeds("pkg-config", ["--exists", "x264"])) {
33+
installBrewPackage("x264", "x264 pkg-config metadata");
34+
}
35+
}
36+
37+
function installBrewPackage(formula, label) {
38+
if (truthy(process.env.SIMDECK_CODEX_SKIP_BREW)) {
39+
throw new Error(
40+
`Missing ${label}. Install it with \`brew install ${formula}\` or unset SIMDECK_CODEX_SKIP_BREW.`,
41+
);
42+
}
43+
if (!commandSucceeds("brew", ["--version"])) {
44+
throw new Error(`Missing ${label}, and Homebrew is not available to install ${formula}.`);
45+
}
46+
run("brew", ["install", formula]);
47+
}
48+
49+
function ensureNodeModules(prefix, label) {
50+
const modulesPath =
51+
prefix === "." ? resolve(ROOT, "node_modules") : resolve(ROOT, prefix, "node_modules");
52+
if (existsAndHasContent(modulesPath)) {
53+
console.log(`[setup] skip ${label} npm install; node_modules already exists`);
54+
return;
55+
}
56+
57+
const args = prefix === "." ? ["ci"] : ["ci", "--prefix", prefix];
58+
run("npm", args);
59+
}
60+
61+
function existsAndHasContent(path) {
62+
try {
63+
const stats = statSync(path);
64+
if (stats.isDirectory()) {
65+
return readdirSync(path).length > 0;
66+
}
67+
return stats.size > 0;
68+
} catch {
69+
return false;
70+
}
71+
}
72+
73+
function commandSucceeds(command, args) {
74+
const result = spawnSync(command, args, {
75+
cwd: ROOT,
76+
stdio: "ignore",
77+
env: process.env,
78+
});
79+
return result.status === 0;
80+
}
81+
82+
function run(command, args) {
83+
console.log(`\n$ ${[command, ...args].join(" ")}`);
84+
const result = spawnSync(command, args, {
85+
cwd: ROOT,
86+
stdio: "inherit",
87+
env: process.env,
88+
});
89+
if (result.error) {
90+
throw result.error;
91+
}
92+
if (result.status !== 0) {
93+
process.exit(result.status ?? 1);
94+
}
95+
}
96+
97+
function truthy(value) {
98+
return value === "1" || value === "true" || value === "yes";
99+
}

0 commit comments

Comments
 (0)