Skip to content

Commit 954e49f

Browse files
committed
feat(tui): register launch directory instances
Made-with: Cursor
1 parent 53d458f commit 954e49f

3 files changed

Lines changed: 118 additions & 0 deletions

File tree

apps/tui/src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createCliRenderer } from "@opentui/core";
22
import { createRoot } from "@opentui/react";
33
import { App } from "./components/app";
44
import { OnboardingError } from "./components/onboarding";
5+
import { ensureDirectoryInstance } from "./lib/instance";
56
import { runOnboardingChecks } from "./lib/onboarding";
67

78
const runtime = {
@@ -33,6 +34,7 @@ export async function runTui(): Promise<void> {
3334
};
3435

3536
if (onboarding.ok) {
37+
await ensureDirectoryInstance();
3638
root.render(<App onQuit={close} />);
3739
} else {
3840
root.render(<OnboardingError checks={onboarding.checks} onQuit={close} />);

apps/tui/src/lib/instance.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { basename, resolve } from "node:path";
3+
import type { ManagedInstance } from "@techatnyu/ralphd";
4+
import { ensureDirectoryInstance } from "./instance";
5+
6+
function instance(overrides: Partial<ManagedInstance>): ManagedInstance {
7+
const now = new Date("2026-01-01T00:00:00.000Z").toISOString();
8+
return {
9+
id: "instance-1",
10+
name: "project",
11+
directory: "/tmp/project",
12+
status: "stopped",
13+
maxConcurrency: 1,
14+
createdAt: now,
15+
updatedAt: now,
16+
...overrides,
17+
};
18+
}
19+
20+
describe("ensureDirectoryInstance", () => {
21+
test("returns an existing instance for the launch directory", async () => {
22+
const cwd = resolve("/tmp/project");
23+
const existing = instance({ directory: cwd });
24+
let creates = 0;
25+
26+
const result = await ensureDirectoryInstance(cwd, {
27+
listInstances: async () => ({ instances: [existing] }),
28+
createInstance: async () => {
29+
creates += 1;
30+
return { instance: existing };
31+
},
32+
});
33+
34+
expect(result).toBe(existing);
35+
expect(creates).toBe(0);
36+
});
37+
38+
test("creates an instance named after the launch directory", async () => {
39+
const cwd = resolve("/tmp/project-two");
40+
let createInput:
41+
| { name: string; directory: string; maxConcurrency?: number }
42+
| undefined;
43+
const created = instance({
44+
id: "instance-2",
45+
name: basename(cwd),
46+
directory: cwd,
47+
});
48+
49+
const result = await ensureDirectoryInstance(cwd, {
50+
listInstances: async () => ({ instances: [] }),
51+
createInstance: async (input) => {
52+
createInput = input;
53+
return { instance: created };
54+
},
55+
});
56+
57+
expect(result).toBe(created);
58+
expect(createInput).toEqual({ name: "project-two", directory: cwd });
59+
});
60+
});

apps/tui/src/lib/instance.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { basename, resolve } from "node:path";
2+
import type { ManagedInstance } from "@techatnyu/ralphd";
3+
import { daemon } from "@techatnyu/ralphd";
4+
5+
type InstanceClient = {
6+
listInstances(): Promise<{ instances: ManagedInstance[] }>;
7+
createInstance(input: {
8+
name: string;
9+
directory: string;
10+
maxConcurrency?: number;
11+
}): Promise<{ instance: ManagedInstance }>;
12+
};
13+
14+
function instanceNameForDirectory(directory: string): string {
15+
return basename(directory) || directory;
16+
}
17+
18+
async function findInstanceByDirectory(
19+
client: InstanceClient,
20+
directory: string,
21+
): Promise<ManagedInstance | undefined> {
22+
const result = await client.listInstances();
23+
return result.instances.find(
24+
(instance) => resolve(instance.directory) === directory,
25+
);
26+
}
27+
28+
export async function ensureDirectoryInstance(
29+
directory = process.cwd(),
30+
client: InstanceClient = daemon,
31+
): Promise<ManagedInstance> {
32+
const normalizedDirectory = resolve(directory);
33+
const existing = await findInstanceByDirectory(client, normalizedDirectory);
34+
if (existing) {
35+
return existing;
36+
}
37+
38+
try {
39+
const created = await client.createInstance({
40+
name: instanceNameForDirectory(normalizedDirectory),
41+
directory: normalizedDirectory,
42+
});
43+
return created.instance;
44+
} catch (error) {
45+
const code = (error as { code?: string } | undefined)?.code;
46+
if (code !== "conflict") {
47+
throw error;
48+
}
49+
50+
const raced = await findInstanceByDirectory(client, normalizedDirectory);
51+
if (raced) {
52+
return raced;
53+
}
54+
throw error;
55+
}
56+
}

0 commit comments

Comments
 (0)