workplane 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/cli.js +340 -0
- package/dist/cli.js.map +1 -0
- package/dist/migrate.js +120 -0
- package/dist/migrate.js.map +1 -0
- package/dist/node.js +606 -0
- package/dist/node.js.map +1 -0
- package/dist/schema.sql +62 -0
- package/dist/server.js +851 -0
- package/dist/server.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MindStaq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# workplane
|
|
2
|
+
|
|
3
|
+
Route durable work—shell commands, local inference (Ollama), and batch agent harness jobs (Aider, Codex, Claude Code)—to capable machines on your private network.
|
|
4
|
+
|
|
5
|
+
Workplane is a control plane + polling node runtime backed by Postgres and [DBOS](https://www.dbos.dev/). You submit tasks from a CLI; nodes pull work, run it in isolated workspaces, and report logs and artifacts.
|
|
6
|
+
|
|
7
|
+
**Requires Node.js 20+** and **Postgres**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g workplane
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Binaries
|
|
16
|
+
|
|
17
|
+
| Command | Purpose |
|
|
18
|
+
|---------|---------|
|
|
19
|
+
| `workplane` | CLI — submit and inspect tasks |
|
|
20
|
+
| `workplane-server` | Control plane API |
|
|
21
|
+
| `workplane-node` | Worker that polls and executes tasks |
|
|
22
|
+
| `workplane-db-migrate` | Apply database schema |
|
|
23
|
+
|
|
24
|
+
## Quick start (single machine)
|
|
25
|
+
|
|
26
|
+
**1. Postgres** — running locally or reachable via `DATABASE_URL`.
|
|
27
|
+
|
|
28
|
+
**2. Environment** — create a `.env` file or export:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/workplane
|
|
32
|
+
export WORKPLANE_SERVER_URL=http://localhost:8787
|
|
33
|
+
|
|
34
|
+
# Recommended when the server is not localhost-only:
|
|
35
|
+
export WORKPLANE_NODE_TOKEN=your-node-secret
|
|
36
|
+
export WORKPLANE_OPERATOR_TOKEN=your-operator-secret
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**3. Migrate, server, node** (three terminals):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
workplane-db-migrate
|
|
43
|
+
workplane-server
|
|
44
|
+
workplane-node
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**4. Submit a task** (fourth terminal):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export WORKPLANE_OPERATOR_TOKEN=your-operator-secret # if auth enabled on server
|
|
51
|
+
|
|
52
|
+
workplane task submit shell --command "echo hello"
|
|
53
|
+
workplane tasks
|
|
54
|
+
workplane runs
|
|
55
|
+
workplane logs <runId>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
When `WORKPLANE_NODE_TOKEN` is set on the **server**, every node must use the same value. When `WORKPLANE_OPERATOR_TOKEN` is set, the CLI must send it for `task submit`, `retry`, and `cancel`.
|
|
61
|
+
|
|
62
|
+
| Variable | Used by |
|
|
63
|
+
|----------|---------|
|
|
64
|
+
| `WORKPLANE_NODE_TOKEN` | `workplane-node` |
|
|
65
|
+
| `WORKPLANE_OPERATOR_TOKEN` | `workplane` CLI (mutating commands) |
|
|
66
|
+
|
|
67
|
+
Read-only CLI commands (`tasks`, `logs`, `runs`, …) work without the operator token unless you add stricter rules in your deployment.
|
|
68
|
+
|
|
69
|
+
## CLI examples
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Shell
|
|
73
|
+
workplane task submit shell --command "npm test" \
|
|
74
|
+
--repo https://github.com/you/your-repo.git
|
|
75
|
+
|
|
76
|
+
# Local inference (node must advertise ollama capability)
|
|
77
|
+
workplane task submit inference --model llama3.2 --prompt "Say hello"
|
|
78
|
+
|
|
79
|
+
# Agent harness (node must have codex or claude-code installed)
|
|
80
|
+
workplane task submit harness --harness codex \
|
|
81
|
+
--repo git@github.com:you/app.git \
|
|
82
|
+
--prompt "Fix failing tests"
|
|
83
|
+
|
|
84
|
+
workplane task submit aider \
|
|
85
|
+
--repo git@github.com:you/app.git \
|
|
86
|
+
--prompt "Fix the bug" \
|
|
87
|
+
--test-command "npm test"
|
|
88
|
+
|
|
89
|
+
workplane task retry <taskId>
|
|
90
|
+
workplane task cancel <taskId>
|
|
91
|
+
workplane artifacts <runId>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Node capabilities
|
|
95
|
+
|
|
96
|
+
Register capabilities when starting a node:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
export WORKPLANE_NODE_CAPABILITIES=shell,git,ollama,aider,codex,claude-code
|
|
100
|
+
workplane-node
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Tasks declare `requires`; the control plane assigns work to a node whose capabilities are a superset.
|
|
104
|
+
|
|
105
|
+
| Adapter | Typical capabilities |
|
|
106
|
+
|---------|----------------------|
|
|
107
|
+
| Shell | `shell` |
|
|
108
|
+
| Ollama | `ollama` |
|
|
109
|
+
| Aider | `git`, `aider` |
|
|
110
|
+
| Codex | `git`, `codex` |
|
|
111
|
+
| Claude Code | `git`, `claude-code` |
|
|
112
|
+
|
|
113
|
+
Override harness binaries: `WORKPLANE_CODEX_BIN`, `WORKPLANE_CLAUDE_CODE_BIN`.
|
|
114
|
+
|
|
115
|
+
## Personal fleet (home + office)
|
|
116
|
+
|
|
117
|
+
Run the control plane on an always-on host (VPS, NAS, or home server). Run nodes on each machine where tools or GPUs live. Connect machines with Tailscale, WireGuard, or a trusted LAN.
|
|
118
|
+
|
|
119
|
+
Set `WORKPLANE_SERVER_URL` to the control plane’s reachable URL on every node and CLI client.
|
|
120
|
+
|
|
121
|
+
Full deployment guide: [github.com/MindStaq/workplane/blob/main/docs/deployment/FLEET.md](https://github.com/MindStaq/workplane/blob/main/docs/deployment/FLEET.md)
|
|
122
|
+
|
|
123
|
+
## Documentation
|
|
124
|
+
|
|
125
|
+
- [v0.1.0 specification](https://github.com/MindStaq/workplane/blob/main/docs/specs/v0.1.0/WORKPLANE_SPEC.md)
|
|
126
|
+
- [Source repository](https://github.com/MindStaq/workplane)
|
|
127
|
+
- [Issues](https://github.com/MindStaq/workplane/issues)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../core/src/env.ts
|
|
4
|
+
import { config as dotenvConfig } from "dotenv";
|
|
5
|
+
var envLoaded = false;
|
|
6
|
+
function loadLocalEnv() {
|
|
7
|
+
if (envLoaded) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
dotenvConfig({ path: ".env" });
|
|
11
|
+
dotenvConfig({ path: ".env.local", override: true });
|
|
12
|
+
envLoaded = true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ../core/src/config.ts
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
loadLocalEnv();
|
|
18
|
+
var serverEnvSchema = z.object({
|
|
19
|
+
DATABASE_URL: z.string().min(1),
|
|
20
|
+
WORKPLANE_SERVER_PORT: z.string().optional().default("8787").transform((value) => Number.parseInt(value, 10)),
|
|
21
|
+
WORKPLANE_NODE_TOKEN: z.string().optional(),
|
|
22
|
+
WORKPLANE_OPERATOR_TOKEN: z.string().optional(),
|
|
23
|
+
WORKPLANE_ENV_ALLOWLIST: z.string().optional()
|
|
24
|
+
});
|
|
25
|
+
var nodeEnvSchema = z.object({
|
|
26
|
+
WORKPLANE_SERVER_URL: z.string().url().optional().default("http://localhost:8787"),
|
|
27
|
+
WORKPLANE_NODE_NAME: z.string().optional().default("local-node-1"),
|
|
28
|
+
WORKPLANE_NODE_ID: z.string().optional(),
|
|
29
|
+
WORKPLANE_NODE_CAPABILITIES: z.string().optional().default("shell,git,node,typescript,aider,ollama,codex,claude-code"),
|
|
30
|
+
WORKPLANE_NODE_TOKEN: z.string().optional(),
|
|
31
|
+
WORKPLANE_POLL_INTERVAL_MS: z.string().optional().default("3000").transform((value) => Number.parseInt(value, 10)),
|
|
32
|
+
WORKPLANE_ENV_ALLOWLIST: z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
function parseCsv(value) {
|
|
35
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ../core/src/auth.ts
|
|
39
|
+
function authHeaders(token) {
|
|
40
|
+
if (!token) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
return { authorization: `Bearer ${token}` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ../core/src/http-client.ts
|
|
47
|
+
function buildWorkplaneHeaders(options = {}) {
|
|
48
|
+
const token = options.nodeToken ?? options.operatorToken;
|
|
49
|
+
return {
|
|
50
|
+
"content-type": "application/json",
|
|
51
|
+
...authHeaders(token)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function workplaneFetch(serverUrl2, path, options = {}) {
|
|
55
|
+
const response = await fetch(`${serverUrl2}${path}`, {
|
|
56
|
+
method: options.method ?? "GET",
|
|
57
|
+
headers: buildWorkplaneHeaders(options),
|
|
58
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const text = await response.text();
|
|
62
|
+
throw new Error(`${response.status} ${response.statusText} from ${path}: ${text}`);
|
|
63
|
+
}
|
|
64
|
+
return await response.json();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ../cli/src/index.ts
|
|
68
|
+
loadLocalEnv();
|
|
69
|
+
var serverUrl = process.env.WORKPLANE_SERVER_URL ?? "http://localhost:8787";
|
|
70
|
+
var operatorToken = process.env.WORKPLANE_OPERATOR_TOKEN;
|
|
71
|
+
async function httpJson(path, options) {
|
|
72
|
+
return workplaneFetch(serverUrl, path, {
|
|
73
|
+
method: options?.method,
|
|
74
|
+
body: options?.body,
|
|
75
|
+
operatorToken: options?.operator ? operatorToken : void 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function valueArg(args, flag) {
|
|
79
|
+
const index = args.indexOf(flag);
|
|
80
|
+
if (index === -1) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
return args[index + 1];
|
|
84
|
+
}
|
|
85
|
+
function printJson(payload) {
|
|
86
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
async function resolveRunIdFromTaskId(taskId) {
|
|
90
|
+
const runs = await httpJson(`/runs?taskId=${encodeURIComponent(taskId)}`);
|
|
91
|
+
const runId = runs.runs[0]?.id;
|
|
92
|
+
if (!runId) {
|
|
93
|
+
throw new Error(`no runs found for task ${taskId}`);
|
|
94
|
+
}
|
|
95
|
+
return runId;
|
|
96
|
+
}
|
|
97
|
+
async function showLogsById(id) {
|
|
98
|
+
const runId = id.startsWith("task_") ? await resolveRunIdFromTaskId(id) : id;
|
|
99
|
+
const logs = await httpJson(`/runs/${runId}/logs`);
|
|
100
|
+
printJson(logs);
|
|
101
|
+
}
|
|
102
|
+
function usage() {
|
|
103
|
+
process.stdout.write(
|
|
104
|
+
[
|
|
105
|
+
"workplane CLI",
|
|
106
|
+
"",
|
|
107
|
+
"Env: WORKPLANE_SERVER_URL, WORKPLANE_OPERATOR_TOKEN (for task submit/retry/cancel)",
|
|
108
|
+
"",
|
|
109
|
+
"Commands:",
|
|
110
|
+
" server start / node start # use pnpm dev:server / dev:node",
|
|
111
|
+
" task submit shell --command <cmd> [--repo <url>] [--branch <b>] [--cwd <path>]",
|
|
112
|
+
" task submit aider --prompt <text> --repo <url> [--model <m>] [--test-command <cmd>]",
|
|
113
|
+
" task submit inference --model <m> --prompt <text> [--requires ollama]",
|
|
114
|
+
" task submit harness --harness <codex|claude-code> --repo <url> --prompt <text>",
|
|
115
|
+
" tasks [--status <status>]",
|
|
116
|
+
" task show|logs|retry|cancel <taskId>",
|
|
117
|
+
" runs [--task-id <id>] [--status <status>]",
|
|
118
|
+
" run show <runId>",
|
|
119
|
+
" logs <runId|taskId>",
|
|
120
|
+
" artifacts <runId>"
|
|
121
|
+
].join("\n")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
async function submitShell(args) {
|
|
125
|
+
const command = valueArg(args, "--command");
|
|
126
|
+
if (!command) {
|
|
127
|
+
throw new Error("--command is required");
|
|
128
|
+
}
|
|
129
|
+
const requires = parseCsv(valueArg(args, "--requires") ?? "shell");
|
|
130
|
+
const repo = valueArg(args, "--repo");
|
|
131
|
+
const branch = valueArg(args, "--branch");
|
|
132
|
+
const cwd = valueArg(args, "--cwd");
|
|
133
|
+
const task = await httpJson("/tasks", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
operator: true,
|
|
136
|
+
body: {
|
|
137
|
+
kind: "shell.exec",
|
|
138
|
+
adapter: "shell",
|
|
139
|
+
requires,
|
|
140
|
+
payload: { command, repo, branch, cwd }
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
printJson(task);
|
|
144
|
+
}
|
|
145
|
+
async function submitAider(args) {
|
|
146
|
+
const prompt = valueArg(args, "--prompt");
|
|
147
|
+
if (!prompt) {
|
|
148
|
+
throw new Error("--prompt is required");
|
|
149
|
+
}
|
|
150
|
+
const model = valueArg(args, "--model");
|
|
151
|
+
const repo = valueArg(args, "--repo");
|
|
152
|
+
if (!repo) {
|
|
153
|
+
throw new Error("--repo is required");
|
|
154
|
+
}
|
|
155
|
+
const branch = valueArg(args, "--branch");
|
|
156
|
+
const testCommand = valueArg(args, "--test-command");
|
|
157
|
+
const requires = parseCsv(valueArg(args, "--requires") ?? "git,aider");
|
|
158
|
+
const task = await httpJson("/tasks", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
operator: true,
|
|
161
|
+
body: {
|
|
162
|
+
kind: "agent.run",
|
|
163
|
+
adapter: "aider",
|
|
164
|
+
requires,
|
|
165
|
+
payload: { prompt, model, repo, branch, testCommand }
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
printJson(task);
|
|
169
|
+
}
|
|
170
|
+
async function submitInference(args) {
|
|
171
|
+
const model = valueArg(args, "--model");
|
|
172
|
+
const prompt = valueArg(args, "--prompt");
|
|
173
|
+
if (!model || !prompt) {
|
|
174
|
+
throw new Error("--model and --prompt are required");
|
|
175
|
+
}
|
|
176
|
+
const adapter = valueArg(args, "--adapter") ?? "ollama";
|
|
177
|
+
const requires = parseCsv(valueArg(args, "--requires") ?? adapter);
|
|
178
|
+
const inputFile = valueArg(args, "--input-file");
|
|
179
|
+
const task = await httpJson("/tasks", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
operator: true,
|
|
182
|
+
body: {
|
|
183
|
+
kind: "inference.batch",
|
|
184
|
+
adapter,
|
|
185
|
+
requires,
|
|
186
|
+
payload: { model, prompt, inputFile }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
printJson(task);
|
|
190
|
+
}
|
|
191
|
+
async function submitHarness(args) {
|
|
192
|
+
const harness = valueArg(args, "--harness");
|
|
193
|
+
const prompt = valueArg(args, "--prompt");
|
|
194
|
+
const repo = valueArg(args, "--repo");
|
|
195
|
+
if (!harness || !prompt || !repo) {
|
|
196
|
+
throw new Error("--harness, --prompt, and --repo are required");
|
|
197
|
+
}
|
|
198
|
+
if (harness !== "codex" && harness !== "claude-code") {
|
|
199
|
+
throw new Error("--harness must be codex or claude-code");
|
|
200
|
+
}
|
|
201
|
+
const model = valueArg(args, "--model");
|
|
202
|
+
const branch = valueArg(args, "--branch");
|
|
203
|
+
const testCommand = valueArg(args, "--test-command");
|
|
204
|
+
const requires = parseCsv(valueArg(args, "--requires") ?? `${harness},git`);
|
|
205
|
+
const task = await httpJson("/tasks", {
|
|
206
|
+
method: "POST",
|
|
207
|
+
operator: true,
|
|
208
|
+
body: {
|
|
209
|
+
kind: "agent.run",
|
|
210
|
+
adapter: harness,
|
|
211
|
+
requires,
|
|
212
|
+
payload: { prompt, repo, model, branch, testCommand }
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
printJson(task);
|
|
216
|
+
}
|
|
217
|
+
async function main() {
|
|
218
|
+
const [command, subcommand, ...rest] = process.argv.slice(2);
|
|
219
|
+
if (!command) {
|
|
220
|
+
usage();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (command === "server" && subcommand === "start") {
|
|
224
|
+
process.stdout.write("Use: pnpm dev:server\n");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (command === "node" && subcommand === "start") {
|
|
228
|
+
process.stdout.write("Use: pnpm dev:node\n");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (command === "task" && subcommand === "submit") {
|
|
232
|
+
const adapter = rest[0];
|
|
233
|
+
const adapterArgs = rest.slice(1);
|
|
234
|
+
if (adapter === "shell") {
|
|
235
|
+
await submitShell(adapterArgs);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (adapter === "aider") {
|
|
239
|
+
await submitAider(adapterArgs);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (adapter === "inference") {
|
|
243
|
+
await submitInference(adapterArgs);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (adapter === "harness") {
|
|
247
|
+
await submitHarness(adapterArgs);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
throw new Error(`unsupported adapter submit: ${adapter}`);
|
|
251
|
+
}
|
|
252
|
+
if (command === "tasks") {
|
|
253
|
+
const status = valueArg([subcommand, ...rest].filter(Boolean), "--status");
|
|
254
|
+
const tasks = await httpJson(status ? `/tasks?status=${encodeURIComponent(status)}` : "/tasks");
|
|
255
|
+
printJson(tasks);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (command === "task" && subcommand === "show") {
|
|
259
|
+
const taskId = rest[0];
|
|
260
|
+
if (!taskId) {
|
|
261
|
+
throw new Error("task id is required");
|
|
262
|
+
}
|
|
263
|
+
const task = await httpJson(`/tasks/${taskId}`);
|
|
264
|
+
printJson(task);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (command === "task" && subcommand === "retry") {
|
|
268
|
+
const taskId = rest[0];
|
|
269
|
+
if (!taskId) {
|
|
270
|
+
throw new Error("task id is required");
|
|
271
|
+
}
|
|
272
|
+
const task = await httpJson(`/tasks/${taskId}/retry`, { method: "POST", operator: true });
|
|
273
|
+
printJson(task);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (command === "task" && subcommand === "logs") {
|
|
277
|
+
const taskId = rest[0];
|
|
278
|
+
if (!taskId) {
|
|
279
|
+
throw new Error("task id is required");
|
|
280
|
+
}
|
|
281
|
+
await showLogsById(taskId);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (command === "task" && subcommand === "cancel") {
|
|
285
|
+
const taskId = rest[0];
|
|
286
|
+
if (!taskId) {
|
|
287
|
+
throw new Error("task id is required");
|
|
288
|
+
}
|
|
289
|
+
const task = await httpJson(`/tasks/${taskId}/cancel`, { method: "POST", operator: true });
|
|
290
|
+
printJson(task);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (command === "runs") {
|
|
294
|
+
const taskId = valueArg(rest, "--task-id");
|
|
295
|
+
const status = valueArg(rest, "--status");
|
|
296
|
+
const query = new URLSearchParams();
|
|
297
|
+
if (taskId) {
|
|
298
|
+
query.set("taskId", taskId);
|
|
299
|
+
}
|
|
300
|
+
if (status) {
|
|
301
|
+
query.set("status", status);
|
|
302
|
+
}
|
|
303
|
+
const runs = await httpJson(query.size > 0 ? `/runs?${query.toString()}` : "/runs");
|
|
304
|
+
printJson(runs);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (command === "run" && subcommand === "show") {
|
|
308
|
+
const runId = rest[0];
|
|
309
|
+
if (!runId) {
|
|
310
|
+
throw new Error("run id is required");
|
|
311
|
+
}
|
|
312
|
+
const run = await httpJson(`/runs/${runId}`);
|
|
313
|
+
printJson(run);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (command === "logs") {
|
|
317
|
+
const id = subcommand;
|
|
318
|
+
if (!id) {
|
|
319
|
+
throw new Error("run id or task id is required");
|
|
320
|
+
}
|
|
321
|
+
await showLogsById(id);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (command === "artifacts") {
|
|
325
|
+
const runId = subcommand;
|
|
326
|
+
if (!runId) {
|
|
327
|
+
throw new Error("run id is required");
|
|
328
|
+
}
|
|
329
|
+
const artifacts = await httpJson(`/runs/${runId}/artifacts`);
|
|
330
|
+
printJson(artifacts);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
usage();
|
|
334
|
+
}
|
|
335
|
+
main().catch((error) => {
|
|
336
|
+
process.stderr.write(`${String(error)}
|
|
337
|
+
`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
});
|
|
340
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/env.ts","../../core/src/config.ts","../../core/src/auth.ts","../../core/src/http-client.ts","../../cli/src/index.ts"],"sourcesContent":["import { config as dotenvConfig } from \"dotenv\";\n\nlet envLoaded = false;\n\nexport function loadLocalEnv(): void {\n if (envLoaded) {\n return;\n }\n\n dotenvConfig({ path: \".env\" });\n dotenvConfig({ path: \".env.local\", override: true });\n\n envLoaded = true;\n}\n\n","import { z } from \"zod\";\nimport { loadLocalEnv } from \"./env.js\";\n\nloadLocalEnv();\n\nconst serverEnvSchema = z.object({\n DATABASE_URL: z.string().min(1),\n WORKPLANE_SERVER_PORT: z\n .string()\n .optional()\n .default(\"8787\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_OPERATOR_TOKEN: z.string().optional(),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nconst nodeEnvSchema = z.object({\n WORKPLANE_SERVER_URL: z.string().url().optional().default(\"http://localhost:8787\"),\n WORKPLANE_NODE_NAME: z.string().optional().default(\"local-node-1\"),\n WORKPLANE_NODE_ID: z.string().optional(),\n WORKPLANE_NODE_CAPABILITIES: z\n .string()\n .optional()\n .default(\"shell,git,node,typescript,aider,ollama,codex,claude-code\"),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_POLL_INTERVAL_MS: z\n .string()\n .optional()\n .default(\"3000\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nexport interface ServerConfig {\n databaseUrl: string;\n port: number;\n nodeToken?: string;\n operatorToken?: string;\n envAllowlist: string[];\n}\n\nexport interface NodeConfig {\n serverUrl: string;\n nodeName: string;\n nodeId?: string;\n nodeCapabilities: string[];\n nodeToken?: string;\n pollIntervalMs: number;\n envAllowlist: string[];\n}\n\nexport function loadServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const parsed = serverEnvSchema.parse(env);\n\n return {\n databaseUrl: parsed.DATABASE_URL,\n port: parsed.WORKPLANE_SERVER_PORT,\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n operatorToken: parsed.WORKPLANE_OPERATOR_TOKEN,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function loadNodeConfig(env: NodeJS.ProcessEnv = process.env): NodeConfig {\n const parsed = nodeEnvSchema.parse(env);\n\n return {\n serverUrl: parsed.WORKPLANE_SERVER_URL,\n nodeName: parsed.WORKPLANE_NODE_NAME,\n nodeId: parsed.WORKPLANE_NODE_ID,\n nodeCapabilities: parseCsv(parsed.WORKPLANE_NODE_CAPABILITIES),\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n pollIntervalMs: parsed.WORKPLANE_POLL_INTERVAL_MS,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function parseCsv(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n","import type { IncomingMessage } from \"node:http\";\n\nexport function parseBearerToken(authorizationHeader: string | undefined): string | undefined {\n if (!authorizationHeader) {\n return undefined;\n }\n const match = /^Bearer\\s+(.+)$/i.exec(authorizationHeader.trim());\n return match?.[1];\n}\n\nexport function isAuthorizedBearer(provided: string | undefined, expected: string | undefined): boolean {\n if (!expected) {\n return true;\n }\n if (!provided) {\n return false;\n }\n return provided === expected;\n}\n\nexport function getRequestBearer(req: IncomingMessage): string | undefined {\n const header = req.headers.authorization;\n const value = Array.isArray(header) ? header[0] : header;\n return parseBearerToken(value);\n}\n\nexport function authHeaders(token: string | undefined): Record<string, string> {\n if (!token) {\n return {};\n }\n return { authorization: `Bearer ${token}` };\n}\n","import { authHeaders } from \"./auth.js\";\n\nexport interface WorkplaneHttpOptions {\n method?: string;\n body?: unknown;\n nodeToken?: string;\n operatorToken?: string;\n}\n\nexport function buildWorkplaneHeaders(options: WorkplaneHttpOptions = {}): Record<string, string> {\n const token = options.nodeToken ?? options.operatorToken;\n return {\n \"content-type\": \"application/json\",\n ...authHeaders(token),\n };\n}\n\nexport async function workplaneFetch<T>(\n serverUrl: string,\n path: string,\n options: WorkplaneHttpOptions = {},\n): Promise<T> {\n const response = await fetch(`${serverUrl}${path}`, {\n method: options.method ?? \"GET\",\n headers: buildWorkplaneHeaders(options),\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`${response.status} ${response.statusText} from ${path}: ${text}`);\n }\n\n return (await response.json()) as T;\n}\n","import { loadLocalEnv } from \"../../core/src/env.js\";\nimport { parseCsv } from \"../../core/src/config.js\";\nimport { workplaneFetch } from \"../../core/src/http-client.js\";\n\nloadLocalEnv();\n\nconst serverUrl = process.env.WORKPLANE_SERVER_URL ?? \"http://localhost:8787\";\nconst operatorToken = process.env.WORKPLANE_OPERATOR_TOKEN;\n\nasync function httpJson<T>(\n path: string,\n options?: { method?: string; body?: Record<string, unknown>; operator?: boolean },\n): Promise<T> {\n return workplaneFetch<T>(serverUrl, path, {\n method: options?.method,\n body: options?.body,\n operatorToken: options?.operator ? operatorToken : undefined,\n });\n}\n\ninterface RunListResponse {\n runs: Array<{ id: string }>;\n}\n\nfunction valueArg(args: string[], flag: string): string | undefined {\n const index = args.indexOf(flag);\n if (index === -1) {\n return undefined;\n }\n return args[index + 1];\n}\n\nfunction printJson(payload: unknown): void {\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n}\n\nasync function resolveRunIdFromTaskId(taskId: string): Promise<string> {\n const runs = await httpJson<RunListResponse>(`/runs?taskId=${encodeURIComponent(taskId)}`);\n const runId = runs.runs[0]?.id;\n if (!runId) {\n throw new Error(`no runs found for task ${taskId}`);\n }\n return runId;\n}\n\nasync function showLogsById(id: string): Promise<void> {\n const runId = id.startsWith(\"task_\") ? await resolveRunIdFromTaskId(id) : id;\n const logs = await httpJson(`/runs/${runId}/logs`);\n printJson(logs);\n}\n\nfunction usage(): void {\n process.stdout.write(\n [\n \"workplane CLI\",\n \"\",\n \"Env: WORKPLANE_SERVER_URL, WORKPLANE_OPERATOR_TOKEN (for task submit/retry/cancel)\",\n \"\",\n \"Commands:\",\n \" server start / node start # use pnpm dev:server / dev:node\",\n \" task submit shell --command <cmd> [--repo <url>] [--branch <b>] [--cwd <path>]\",\n \" task submit aider --prompt <text> --repo <url> [--model <m>] [--test-command <cmd>]\",\n \" task submit inference --model <m> --prompt <text> [--requires ollama]\",\n \" task submit harness --harness <codex|claude-code> --repo <url> --prompt <text>\",\n \" tasks [--status <status>]\",\n \" task show|logs|retry|cancel <taskId>\",\n \" runs [--task-id <id>] [--status <status>]\",\n \" run show <runId>\",\n \" logs <runId|taskId>\",\n \" artifacts <runId>\",\n ].join(\"\\n\"),\n );\n}\n\nasync function submitShell(args: string[]): Promise<void> {\n const command = valueArg(args, \"--command\");\n if (!command) {\n throw new Error(\"--command is required\");\n }\n\n const requires = parseCsv(valueArg(args, \"--requires\") ?? \"shell\");\n const repo = valueArg(args, \"--repo\");\n const branch = valueArg(args, \"--branch\");\n const cwd = valueArg(args, \"--cwd\");\n const task = await httpJson(\"/tasks\", {\n method: \"POST\",\n operator: true,\n body: {\n kind: \"shell.exec\",\n adapter: \"shell\",\n requires,\n payload: { command, repo, branch, cwd },\n },\n });\n\n printJson(task);\n}\n\nasync function submitAider(args: string[]): Promise<void> {\n const prompt = valueArg(args, \"--prompt\");\n if (!prompt) {\n throw new Error(\"--prompt is required\");\n }\n\n const model = valueArg(args, \"--model\");\n const repo = valueArg(args, \"--repo\");\n if (!repo) {\n throw new Error(\"--repo is required\");\n }\n const branch = valueArg(args, \"--branch\");\n const testCommand = valueArg(args, \"--test-command\");\n const requires = parseCsv(valueArg(args, \"--requires\") ?? \"git,aider\");\n const task = await httpJson(\"/tasks\", {\n method: \"POST\",\n operator: true,\n body: {\n kind: \"agent.run\",\n adapter: \"aider\",\n requires,\n payload: { prompt, model, repo, branch, testCommand },\n },\n });\n\n printJson(task);\n}\n\nasync function submitInference(args: string[]): Promise<void> {\n const model = valueArg(args, \"--model\");\n const prompt = valueArg(args, \"--prompt\");\n if (!model || !prompt) {\n throw new Error(\"--model and --prompt are required\");\n }\n\n const adapter = valueArg(args, \"--adapter\") ?? \"ollama\";\n const requires = parseCsv(valueArg(args, \"--requires\") ?? adapter);\n const inputFile = valueArg(args, \"--input-file\");\n\n const task = await httpJson(\"/tasks\", {\n method: \"POST\",\n operator: true,\n body: {\n kind: \"inference.batch\",\n adapter,\n requires,\n payload: { model, prompt, inputFile },\n },\n });\n\n printJson(task);\n}\n\nasync function submitHarness(args: string[]): Promise<void> {\n const harness = valueArg(args, \"--harness\");\n const prompt = valueArg(args, \"--prompt\");\n const repo = valueArg(args, \"--repo\");\n if (!harness || !prompt || !repo) {\n throw new Error(\"--harness, --prompt, and --repo are required\");\n }\n\n if (harness !== \"codex\" && harness !== \"claude-code\") {\n throw new Error(\"--harness must be codex or claude-code\");\n }\n\n const model = valueArg(args, \"--model\");\n const branch = valueArg(args, \"--branch\");\n const testCommand = valueArg(args, \"--test-command\");\n const requires = parseCsv(valueArg(args, \"--requires\") ?? `${harness},git`);\n\n const task = await httpJson(\"/tasks\", {\n method: \"POST\",\n operator: true,\n body: {\n kind: \"agent.run\",\n adapter: harness,\n requires,\n payload: { prompt, repo, model, branch, testCommand },\n },\n });\n\n printJson(task);\n}\n\nasync function main(): Promise<void> {\n const [command, subcommand, ...rest] = process.argv.slice(2);\n\n if (!command) {\n usage();\n return;\n }\n\n if (command === \"server\" && subcommand === \"start\") {\n process.stdout.write(\"Use: pnpm dev:server\\n\");\n return;\n }\n\n if (command === \"node\" && subcommand === \"start\") {\n process.stdout.write(\"Use: pnpm dev:node\\n\");\n return;\n }\n\n if (command === \"task\" && subcommand === \"submit\") {\n const adapter = rest[0];\n const adapterArgs = rest.slice(1);\n if (adapter === \"shell\") {\n await submitShell(adapterArgs);\n return;\n }\n if (adapter === \"aider\") {\n await submitAider(adapterArgs);\n return;\n }\n if (adapter === \"inference\") {\n await submitInference(adapterArgs);\n return;\n }\n if (adapter === \"harness\") {\n await submitHarness(adapterArgs);\n return;\n }\n throw new Error(`unsupported adapter submit: ${adapter}`);\n }\n\n if (command === \"tasks\") {\n const status = valueArg([subcommand, ...rest].filter(Boolean) as string[], \"--status\");\n const tasks = await httpJson(status ? `/tasks?status=${encodeURIComponent(status)}` : \"/tasks\");\n printJson(tasks);\n return;\n }\n\n if (command === \"task\" && subcommand === \"show\") {\n const taskId = rest[0];\n if (!taskId) {\n throw new Error(\"task id is required\");\n }\n const task = await httpJson(`/tasks/${taskId}`);\n printJson(task);\n return;\n }\n\n if (command === \"task\" && subcommand === \"retry\") {\n const taskId = rest[0];\n if (!taskId) {\n throw new Error(\"task id is required\");\n }\n const task = await httpJson(`/tasks/${taskId}/retry`, { method: \"POST\", operator: true });\n printJson(task);\n return;\n }\n\n if (command === \"task\" && subcommand === \"logs\") {\n const taskId = rest[0];\n if (!taskId) {\n throw new Error(\"task id is required\");\n }\n await showLogsById(taskId);\n return;\n }\n\n if (command === \"task\" && subcommand === \"cancel\") {\n const taskId = rest[0];\n if (!taskId) {\n throw new Error(\"task id is required\");\n }\n const task = await httpJson(`/tasks/${taskId}/cancel`, { method: \"POST\", operator: true });\n printJson(task);\n return;\n }\n\n if (command === \"runs\") {\n const taskId = valueArg(rest, \"--task-id\");\n const status = valueArg(rest, \"--status\");\n const query = new URLSearchParams();\n if (taskId) {\n query.set(\"taskId\", taskId);\n }\n if (status) {\n query.set(\"status\", status);\n }\n const runs = await httpJson(query.size > 0 ? `/runs?${query.toString()}` : \"/runs\");\n printJson(runs);\n return;\n }\n\n if (command === \"run\" && subcommand === \"show\") {\n const runId = rest[0];\n if (!runId) {\n throw new Error(\"run id is required\");\n }\n const run = await httpJson(`/runs/${runId}`);\n printJson(run);\n return;\n }\n\n if (command === \"logs\") {\n const id = subcommand;\n if (!id) {\n throw new Error(\"run id or task id is required\");\n }\n await showLogsById(id);\n return;\n }\n\n if (command === \"artifacts\") {\n const runId = subcommand;\n if (!runId) {\n throw new Error(\"run id is required\");\n }\n const artifacts = await httpJson(`/runs/${runId}/artifacts`);\n printJson(artifacts);\n return;\n }\n\n usage();\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["serverUrl"]}
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../db/src/migrate.ts
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
// ../core/src/config.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
// ../core/src/env.ts
|
|
12
|
+
import { config as dotenvConfig } from "dotenv";
|
|
13
|
+
var envLoaded = false;
|
|
14
|
+
function loadLocalEnv() {
|
|
15
|
+
if (envLoaded) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
dotenvConfig({ path: ".env" });
|
|
19
|
+
dotenvConfig({ path: ".env.local", override: true });
|
|
20
|
+
envLoaded = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ../core/src/config.ts
|
|
24
|
+
loadLocalEnv();
|
|
25
|
+
var serverEnvSchema = z.object({
|
|
26
|
+
DATABASE_URL: z.string().min(1),
|
|
27
|
+
WORKPLANE_SERVER_PORT: z.string().optional().default("8787").transform((value) => Number.parseInt(value, 10)),
|
|
28
|
+
WORKPLANE_NODE_TOKEN: z.string().optional(),
|
|
29
|
+
WORKPLANE_OPERATOR_TOKEN: z.string().optional(),
|
|
30
|
+
WORKPLANE_ENV_ALLOWLIST: z.string().optional()
|
|
31
|
+
});
|
|
32
|
+
var nodeEnvSchema = z.object({
|
|
33
|
+
WORKPLANE_SERVER_URL: z.string().url().optional().default("http://localhost:8787"),
|
|
34
|
+
WORKPLANE_NODE_NAME: z.string().optional().default("local-node-1"),
|
|
35
|
+
WORKPLANE_NODE_ID: z.string().optional(),
|
|
36
|
+
WORKPLANE_NODE_CAPABILITIES: z.string().optional().default("shell,git,node,typescript,aider,ollama,codex,claude-code"),
|
|
37
|
+
WORKPLANE_NODE_TOKEN: z.string().optional(),
|
|
38
|
+
WORKPLANE_POLL_INTERVAL_MS: z.string().optional().default("3000").transform((value) => Number.parseInt(value, 10)),
|
|
39
|
+
WORKPLANE_ENV_ALLOWLIST: z.string().optional()
|
|
40
|
+
});
|
|
41
|
+
function loadServerConfig(env = process.env) {
|
|
42
|
+
const parsed = serverEnvSchema.parse(env);
|
|
43
|
+
return {
|
|
44
|
+
databaseUrl: parsed.DATABASE_URL,
|
|
45
|
+
port: parsed.WORKPLANE_SERVER_PORT,
|
|
46
|
+
nodeToken: parsed.WORKPLANE_NODE_TOKEN,
|
|
47
|
+
operatorToken: parsed.WORKPLANE_OPERATOR_TOKEN,
|
|
48
|
+
envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? "")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function parseCsv(value) {
|
|
52
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ../db/src/client.ts
|
|
56
|
+
import { Pool } from "pg";
|
|
57
|
+
var pools = /* @__PURE__ */ new Map();
|
|
58
|
+
function getPool(databaseUrl) {
|
|
59
|
+
const existing = pools.get(databaseUrl);
|
|
60
|
+
if (existing) {
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
const pool = new Pool({ connectionString: databaseUrl });
|
|
64
|
+
pools.set(databaseUrl, pool);
|
|
65
|
+
return pool;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ../db/src/migrate.ts
|
|
69
|
+
function quoteIdentifier(value) {
|
|
70
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
71
|
+
}
|
|
72
|
+
function parseDatabaseName(databaseUrl) {
|
|
73
|
+
const url = new URL(databaseUrl);
|
|
74
|
+
const name = url.pathname.replace(/^\//, "");
|
|
75
|
+
if (!name) {
|
|
76
|
+
throw new Error("DATABASE_URL must include a database name");
|
|
77
|
+
}
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
80
|
+
async function ensureDatabaseExists(databaseUrl) {
|
|
81
|
+
const databaseName = parseDatabaseName(databaseUrl);
|
|
82
|
+
const adminUrl = new URL(databaseUrl);
|
|
83
|
+
adminUrl.pathname = "/postgres";
|
|
84
|
+
const adminPool = getPool(adminUrl.toString());
|
|
85
|
+
try {
|
|
86
|
+
const existsResult = await adminPool.query("select 1 from pg_database where datname = $1", [databaseName]);
|
|
87
|
+
if (existsResult.rowCount !== 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await adminPool.query(`create database ${quoteIdentifier(databaseName)}`);
|
|
91
|
+
process.stdout.write(`Created database: ${databaseName}
|
|
92
|
+
`);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const pgError = error;
|
|
95
|
+
if (pgError.code === "42501") {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Database "${databaseName}" does not exist and the current role lacks CREATE DATABASE permissions.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
} finally {
|
|
102
|
+
await adminPool.end();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function main() {
|
|
106
|
+
const config = loadServerConfig();
|
|
107
|
+
await ensureDatabaseExists(config.databaseUrl);
|
|
108
|
+
const pool = getPool(config.databaseUrl);
|
|
109
|
+
const filePath = resolve(dirname(fileURLToPath(import.meta.url)), "schema.sql");
|
|
110
|
+
const sql = await readFile(filePath, "utf8");
|
|
111
|
+
await pool.query(sql);
|
|
112
|
+
await pool.end();
|
|
113
|
+
process.stdout.write("Migrations applied.\n");
|
|
114
|
+
}
|
|
115
|
+
main().catch((error) => {
|
|
116
|
+
process.stderr.write(`${String(error)}
|
|
117
|
+
`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../db/src/migrate.ts","../../core/src/config.ts","../../core/src/env.ts","../../db/src/client.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { loadServerConfig } from \"../../core/src/config.js\";\nimport { getPool } from \"./client.js\";\n\nfunction quoteIdentifier(value: string): string {\n return `\"${value.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction parseDatabaseName(databaseUrl: string): string {\n const url = new URL(databaseUrl);\n const name = url.pathname.replace(/^\\//, \"\");\n if (!name) {\n throw new Error(\"DATABASE_URL must include a database name\");\n }\n return name;\n}\n\nasync function ensureDatabaseExists(databaseUrl: string): Promise<void> {\n const databaseName = parseDatabaseName(databaseUrl);\n const adminUrl = new URL(databaseUrl);\n adminUrl.pathname = \"/postgres\";\n const adminPool = getPool(adminUrl.toString());\n\n try {\n const existsResult = await adminPool.query(\"select 1 from pg_database where datname = $1\", [databaseName]);\n if (existsResult.rowCount !== 0) {\n return;\n }\n await adminPool.query(`create database ${quoteIdentifier(databaseName)}`);\n process.stdout.write(`Created database: ${databaseName}\\n`);\n } catch (error) {\n const pgError = error as { code?: string; message?: string };\n if (pgError.code === \"42501\") {\n throw new Error(\n `Database \"${databaseName}\" does not exist and the current role lacks CREATE DATABASE permissions.`,\n );\n }\n throw error;\n } finally {\n await adminPool.end();\n }\n}\n\nasync function main(): Promise<void> {\n const config = loadServerConfig();\n await ensureDatabaseExists(config.databaseUrl);\n const pool = getPool(config.databaseUrl);\n const filePath = resolve(dirname(fileURLToPath(import.meta.url)), \"schema.sql\");\n const sql = await readFile(filePath, \"utf8\");\n await pool.query(sql);\n await pool.end();\n process.stdout.write(\"Migrations applied.\\n\");\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n","import { z } from \"zod\";\nimport { loadLocalEnv } from \"./env.js\";\n\nloadLocalEnv();\n\nconst serverEnvSchema = z.object({\n DATABASE_URL: z.string().min(1),\n WORKPLANE_SERVER_PORT: z\n .string()\n .optional()\n .default(\"8787\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_OPERATOR_TOKEN: z.string().optional(),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nconst nodeEnvSchema = z.object({\n WORKPLANE_SERVER_URL: z.string().url().optional().default(\"http://localhost:8787\"),\n WORKPLANE_NODE_NAME: z.string().optional().default(\"local-node-1\"),\n WORKPLANE_NODE_ID: z.string().optional(),\n WORKPLANE_NODE_CAPABILITIES: z\n .string()\n .optional()\n .default(\"shell,git,node,typescript,aider,ollama,codex,claude-code\"),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_POLL_INTERVAL_MS: z\n .string()\n .optional()\n .default(\"3000\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nexport interface ServerConfig {\n databaseUrl: string;\n port: number;\n nodeToken?: string;\n operatorToken?: string;\n envAllowlist: string[];\n}\n\nexport interface NodeConfig {\n serverUrl: string;\n nodeName: string;\n nodeId?: string;\n nodeCapabilities: string[];\n nodeToken?: string;\n pollIntervalMs: number;\n envAllowlist: string[];\n}\n\nexport function loadServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const parsed = serverEnvSchema.parse(env);\n\n return {\n databaseUrl: parsed.DATABASE_URL,\n port: parsed.WORKPLANE_SERVER_PORT,\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n operatorToken: parsed.WORKPLANE_OPERATOR_TOKEN,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function loadNodeConfig(env: NodeJS.ProcessEnv = process.env): NodeConfig {\n const parsed = nodeEnvSchema.parse(env);\n\n return {\n serverUrl: parsed.WORKPLANE_SERVER_URL,\n nodeName: parsed.WORKPLANE_NODE_NAME,\n nodeId: parsed.WORKPLANE_NODE_ID,\n nodeCapabilities: parseCsv(parsed.WORKPLANE_NODE_CAPABILITIES),\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n pollIntervalMs: parsed.WORKPLANE_POLL_INTERVAL_MS,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function parseCsv(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n","import { config as dotenvConfig } from \"dotenv\";\n\nlet envLoaded = false;\n\nexport function loadLocalEnv(): void {\n if (envLoaded) {\n return;\n }\n\n dotenvConfig({ path: \".env\" });\n dotenvConfig({ path: \".env.local\", override: true });\n\n envLoaded = true;\n}\n\n","import { Pool } from \"pg\";\n\nconst pools = new Map<string, Pool>();\n\nexport function getPool(databaseUrl: string): Pool {\n const existing = pools.get(databaseUrl);\n if (existing) {\n return existing;\n }\n\n const pool = new Pool({ connectionString: databaseUrl });\n pools.set(databaseUrl, pool);\n return pool;\n}\n"],"mappings":"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","names":[]}
|