ework-aio 0.1.17 → 0.2.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/README.md CHANGED
@@ -48,7 +48,8 @@ The install command checks for these and aborts with a hint if any are missing:
48
48
  | `opencode` | 1.14 | always | https://opencode.ai |
49
49
  | `npm` | any | always | ships with bun or node |
50
50
  | `systemctl` | any | only `install systemd` (not default) | systemd-based Linux |
51
- | `openssl`/`curl`/`jq`/`awk` | any | always | your distro package manager |
51
+
52
+ Everything else (HTTP requests, AES/HMAC, JSON parsing, env-file editing) is done in-process via Bun — no `curl`/`openssl`/`jq`/`awk` needed.
52
53
 
53
54
  ### Install variants
54
55
 
@@ -101,7 +102,7 @@ Keeping these in a separate, explicitly-invoked step is intentional:
101
102
 
102
103
  ## What it does
103
104
 
104
- 1. **Verifies prerequisites** (bun, npm, opencode, systemctl, openssl, curl, jq).
105
+ 1. **Verifies prerequisites** (bun, npm, opencode; systemctl only if `install systemd`).
105
106
  2. **Installs the 3 npm packages** globally (if not already present).
106
107
  3. **Generates `.env`** with random tokens at `~/.local/share/ework-aio/{ework-web,ework-daemon}/.env` (preserved across re-runs).
107
108
  4. **Writes systemd units** (`ework-web.service`, `ework-daemon.service`) — user-level by default, system-level when run as root.
@@ -116,7 +117,7 @@ Keeping these in a separate, explicitly-invoked step is intentional:
116
117
  ```
117
118
  ework-aio install [options] Install or upgrade the stack
118
119
  ework-aio uninstall Stop services and remove units (data preserved)
119
- ework-aio status Show service status (systemd mode)
120
+ ework-aio status Show service status (works in PID-file and systemd mode)
120
121
  ework-aio logs [web|daemon] Tail logs
121
122
  ework-aio env Print key paths (no secrets)
122
123
  ework-aio config <subcommand> Read / change runtime .env keys
@@ -127,24 +128,25 @@ ework-aio config <subcommand> Read / change runtime .env keys
127
128
  config restart <web|daemon|both> Restart one or both services
128
129
 
129
130
  # PID-file mode (no systemd required — see below)
130
- ework-aio start [web|daemon|both] [--foreground] Start services in background
131
- ework-aio stop [web|daemon|both] Stop services (SIGTERM, 5s, SIGKILL)
132
- ework-aio restart [web|daemon|both] Stop + start
133
- ework-aio ps Show PID-file mode status
131
+ ework-aio start [web|daemon|both] Start services in background (detached)
132
+ ework-aio stop [web|daemon|both] Stop services (SIGTERM, 5s, SIGKILL)
133
+ ework-aio restart [web|daemon|both] Stop + start
134
+ ework-aio ps Show PID-file mode status
134
135
  ```
135
136
 
136
137
  ### Install options
137
138
 
138
- | Flag | Default | Description |
139
- | ----------------------- | ------------------------------------ | ------------------------------------------ |
140
- | `--user` | (auto: `--user` unless EUID=0) | Use `systemctl --user` |
141
- | `--system` | (auto: `--system` if EUID=0) | Use `systemctl` (system-level, needs root) |
142
- | `--data-dir <path>` | `~/.local/share/ework-aio` | Override data root |
143
- | `--port <n>` | `3002` | ework-web port |
144
- | `--daemon-port <n>` | `3101` | ework-daemon port |
145
- | `--bot-name <login>` | `ework-daemon` | Bot username in ework-web |
146
- | `--no-start` | (off) | Install units but don't start |
147
- | `--yes` | (off) | Skip prompts (use generated defaults) |
139
+ | Flag | Default | Description |
140
+ | ----------------------- | ------------------------------------ | ----------------------------------------------------------------- |
141
+ | `--user` | default when EUID0 | Use `systemctl --user` (only with `install systemd` subcommand) |
142
+ | `--system` | default when EUID=0 | Use `systemctl` system-level (only with `install systemd`, sudo) |
143
+ | `--data-dir <path>` | `~/.local/share/ework-aio` | Override data root |
144
+ | `--port <n>` | `3002` | ework-web port |
145
+ | `--daemon-port <n>` | `3101` | ework-daemon port |
146
+ | `--bot-name <login>` | `ework-daemon` | Bot username in ework-web |
147
+ | `--no-start` | (off) | Install but don't start services |
148
+ | `--yes` / `-y` | (off) | Skip prompts (use generated defaults) |
149
+ | `--as-user <login>` | (off) | **sudo only:** drop privileges — re-exec install as `<login>` |
148
150
 
149
151
  ## File layout
150
152
 
@@ -258,15 +260,14 @@ PID-file mode writes per-service PID + log under `~/.local/share/ework-aio/run/`
258
260
  ework-aio install --no-start
259
261
 
260
262
  # Then control with PID-file mode:
261
- ework-aio start # start web + daemon in background
263
+ ework-aio start # start web + daemon in background (detached)
262
264
  ework-aio start web # just web
263
- ework-aio start daemon --foreground # run in current terminal
264
265
  ework-aio stop # SIGTERM, 5s grace, SIGKILL if needed
265
266
  ework-aio restart web # stop + start
266
267
  ework-aio ps # show PID + log path for each service
267
268
  ```
268
269
 
269
- Override the data dir with `EWORK_AIO_DATA_DIR=/path ework-aio start`.
270
+ Override the data dir with `ework-aio start --data-dir /path` (or pass `--data-dir` to `install`).
270
271
 
271
272
  **Notes**:
272
273
  - `install` (with `--no-start`) is still required once to scaffold `.env` and the data directory. The systemd units it writes are inert when nothing manages them.
package/bin/ework-aio CHANGED
@@ -1,441 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { spawn, spawnSync } from "bun";
3
- import {
4
- chmodSync,
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- rmSync,
9
- writeFileSync,
10
- } from "fs";
11
- import { dirname, join, resolve } from "path";
12
- import { fileURLToPath } from "url";
2
+ import { main } from "../src/cli.ts";
13
3
 
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
-
16
- const args = process.argv.slice(2);
17
- const subcommand = args[0] || "install";
18
-
19
- function usage() {
20
- console.log(`ework-aio — installer and controller for the ework stack
21
-
22
- Usage:
23
- ework-aio install [options] Install and start ework-web + ework-daemon
24
- (uses systemd; see start/stop for non-systemd)
25
- ework-aio uninstall Stop services and remove units (keeps data)
26
- ework-aio status Show service status (systemd mode)
27
- ework-aio logs [svc] Tail logs (svc: web | daemon)
28
- ework-aio env Print the .env path and key names (no values)
29
- ework-aio config <subcommand> Read / change runtime .env keys
30
- config list List settable keys + current values
31
- config get <KEY> Print one key's value
32
- config set <KEY> <VALUE> Set a key, then restart affected service
33
- (use --no-restart to defer)
34
- config restart <web|daemon|both>
35
- ework-aio start [svc] Start services in background WITHOUT systemd
36
- (PID-file mode, works on WSL/macOS/Alpine).
37
- svc: web | daemon | both (default both).
38
- --foreground runs in current terminal.
39
- ework-aio stop [svc] Stop services started via 'start'
40
- (SIGTERM, 5s grace, then SIGKILL)
41
- ework-aio restart [svc] Stop + start
42
- ework-aio ps Show PID-file mode status (web+daemon)
43
- ework-aio migrate [options] Migrate issues from a Gitea instance into
44
- ework-web via the Gitea REST API. Idempotent.
45
- Run \`ework-aio migrate --help\` for details.
46
- ework-aio backfill-timestamps Fix created_at/updated_at on already-migrated
47
- data. Use after migrate when ework.db is local.
48
- ework-aio --version Print version
49
-
50
- Install options:
51
- --user Use user-level systemd units (default if non-root)
52
- --system Use system-level systemd units (default if root)
53
- --data-dir <path> Override data directory (default: ~/.local/share/ework-aio)
54
- --port <n> ework-web port (default 3002)
55
- --daemon-port <n> ework-daemon port (default 3101)
56
- --bot-name <login> Bot username (default: ework-daemon)
57
- --no-start Install units but don't start services
58
- --yes Skip all prompts (use generated defaults)
59
-
60
- PID-file mode (--foreground works with start):
61
- --data-dir <path> Same as install option
62
- `);
63
- }
64
-
65
- if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
66
- usage();
67
- process.exit(0);
68
- }
69
- if (subcommand === "--version" || subcommand === "-v") {
70
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
71
- console.log(`ework-aio ${pkg.version}`);
72
- process.exit(0);
73
- }
74
-
75
- const installSh = resolve(__dirname, "install.sh");
76
- if (!existsSync(installSh)) {
77
- console.error(`Cannot find install.sh next to ework-aio bin (looked at ${installSh}).`);
78
- console.error("This usually means the package was installed incorrectly.");
79
- process.exit(1);
80
- }
81
-
82
- const knownSubs = ["install", "uninstall", "status", "logs", "env", "config"];
83
-
84
- // ─── PID-file mode (no systemd required) ───────────────────────────────────
85
- //
86
- // start/stop/restart/ps bypass install.sh and the systemd dependency entirely.
87
- // Each service writes its PID + log under $DATA_DIR/run/, managed via the
88
- // standard node:child_process detached-spawn pattern (sets the child free of
89
- // the parent's process group so the parent can exit while the service keeps
90
- // running). This is the path for systems without systemd: macOS, WSL1, Alpine,
91
- // containers, dev laptops.
92
-
93
- const SVC_CONFIG = {
94
- web: {
95
- binName: "ework-web",
96
- binFallback: "ework-web/bin/ework-web.js",
97
- dataSubdir: "ework-web",
98
- label: "ework-web",
99
- },
100
- daemon: {
101
- binName: "ework-daemon-server",
102
- binFallback: "ework-daemon/bin/ework-daemon-server.js",
103
- dataSubdir: "ework-daemon",
104
- label: "ework-daemon",
105
- },
106
- } as const;
107
-
108
- type SvcName = keyof typeof SVC_CONFIG;
109
-
110
- function resolveDataDir(): string {
111
- const xdg = process.env.XDG_DATA_HOME || `${process.env.HOME}/.local/share`;
112
- return process.env.EWORK_AIO_DATA_DIR || `${xdg}/ework-aio`;
113
- }
114
-
115
- function runDir(): string {
116
- const d = join(resolveDataDir(), "run");
117
- if (!existsSync(d)) mkdirSync(d, { recursive: true });
118
- return d;
119
- }
120
-
121
- function svcEnvPath(svc: SvcName): string {
122
- return join(resolveDataDir(), SVC_CONFIG[svc].dataSubdir, ".env");
123
- }
124
-
125
- function svcPidPath(svc: SvcName): string {
126
- return join(runDir(), `${svc}.pid`);
127
- }
128
-
129
- function svcLogPath(svc: SvcName): string {
130
- return join(runDir(), `${svc}.log`);
131
- }
132
-
133
- // KEY=VALUE parser, ignores comments + blanks, never throws.
134
- function parseEnvFile(path: string): Record<string, string> {
135
- const out: Record<string, string> = {};
136
- if (!existsSync(path)) return out;
137
- for (const line of readFileSync(path, "utf8").split("\n")) {
138
- const trimmed = line.trim();
139
- if (!trimmed || trimmed.startsWith("#")) continue;
140
- const eq = trimmed.indexOf("=");
141
- if (eq < 0) continue;
142
- const k = trimmed.slice(0, eq).trim();
143
- let v = trimmed.slice(eq + 1).trim();
144
- if (
145
- (v.startsWith('"') && v.endsWith('"')) ||
146
- (v.startsWith("'") && v.endsWith("'"))
147
- ) {
148
- v = v.slice(1, -1);
149
- }
150
- if (k) out[k] = v;
151
- }
152
- return out;
153
- }
154
-
155
- function resolveSvcBin(svc: SvcName): string | null {
156
- const cfg = SVC_CONFIG[svc];
157
- // PATH lookup first (covers PATH-installed global bins). `command -v` is a
158
- // shell builtin so we have to invoke via sh -c.
159
- const lookup = spawnSync(["sh", "-c", `command -v ${cfg.binName}`], {
160
- env: process.env,
161
- stdout: "pipe",
162
- stderr: "ignore",
163
- });
164
- if (lookup.success) {
165
- const p = lookup.stdout.toString().trim();
166
- if (p && existsSync(p)) return p;
167
- }
168
- // npm global root fallback — covers npm-installed packages whose bin dir
169
- // isn't on PATH (common with nvm-style installs).
170
- const npmRoot = spawnSync(["npm", "root", "-g"], {
171
- env: process.env,
172
- stdout: "pipe",
173
- stderr: "ignore",
174
- });
175
- if (npmRoot.success) {
176
- const fallback = join(npmRoot.stdout.toString().trim(), cfg.binFallback);
177
- if (existsSync(fallback)) return fallback;
178
- }
179
- return null;
180
- }
181
-
182
- function pidAlive(pid: number): boolean {
183
- if (!pid || pid <= 0) return false;
184
- try {
185
- process.kill(pid, 0);
186
- return true;
187
- } catch {
188
- return false;
189
- }
190
- }
191
-
192
- function readPid(svc: SvcName): number | null {
193
- const p = svcPidPath(svc);
194
- if (!existsSync(p)) return null;
195
- const raw = readFileSync(p, "utf8").trim();
196
- const n = Number(raw);
197
- return Number.isFinite(n) && n > 0 ? n : null;
198
- }
199
-
200
- function writePid(svc: SvcName, pid: number): void {
201
- writeFileSync(svcPidPath(svc), `${pid}\n`, { encoding: "utf8" });
202
- }
203
-
204
- function clearPid(svc: SvcName): void {
205
- const p = svcPidPath(svc);
206
- if (existsSync(p)) rmSync(p);
207
-
208
- const staleSocket = join(runDir(), `${svc}.port`);
209
- if (existsSync(staleSocket)) rmSync(staleSocket);
210
- }
211
-
212
- function waitForExit(pid: number, timeoutMs: number): boolean {
213
- const deadline = Date.now() + timeoutMs;
214
- while (Date.now() < deadline) {
215
- if (!pidAlive(pid)) return true;
216
- const slept = Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
217
- void slept;
218
- }
219
- return !pidAlive(pid);
220
- }
221
-
222
- function parseSvcArg(argv: string[]): { svc: "both" | SvcName; foreground: boolean } {
223
- let svc: "both" | SvcName = "both";
224
- let foreground = false;
225
- for (const a of argv.slice(1)) {
226
- if (a === "--foreground" || a === "-f") foreground = true;
227
- else if (a === "web" || a === "daemon") svc = a;
228
- else if (a === "both") svc = "both";
229
- else {
230
- console.error(`Unknown argument: ${a}`);
231
- console.error("Usage: ework-aio <start|stop|restart> [web|daemon|both] [--foreground]");
232
- process.exit(2);
233
- }
234
- }
235
- return { svc, foreground };
236
- }
237
-
238
- function startOne(svc: SvcName, foreground: boolean): boolean {
239
- const existing = readPid(svc);
240
- if (existing && pidAlive(existing)) {
241
- console.log(`${SVC_CONFIG[svc].label} already running (pid ${existing})`);
242
- return true;
243
- }
244
- if (existing) clearPid(svc);
245
-
246
- const bin = resolveSvcBin(svc);
247
- if (!bin) {
248
- console.error(
249
- `${SVC_CONFIG[svc].label}: cannot find bin '${SVC_CONFIG[svc].binName}' in PATH or npm global root.`
250
- );
251
- console.error(` Install with: npm i -g ${SVC_CONFIG[svc].binName.split("-")[0]}`);
252
- return false;
253
- }
254
- const envPath = svcEnvPath(svc);
255
- if (!existsSync(envPath)) {
256
- console.error(`${SVC_CONFIG[svc].label}: missing .env at ${envPath}.`);
257
- console.error(" Run `ework-aio install` first to scaffold config + data dir.");
258
- console.error(" If install fails with npm ENOTEMPTY, a stale temp dir is blocking it.");
259
- console.error(" Manual fix: sudo rm -rf \"$(npm root -g)/.ework-*\" \"$(npm root -g)\"/*.Trash*");
260
- console.error(" Then re-run: ework-aio install");
261
- return false;
262
- }
263
-
264
- // Sane perms on the bin (npm sometimes ships without +x).
265
- try {
266
- chmodSync(bin, 0o755);
267
- } catch {
268
- // best-effort
269
- }
270
-
271
- const mergedEnv = { ...process.env, ...parseEnvFile(envPath) };
272
- const logPath = svcLogPath(svc);
273
-
274
- if (foreground) {
275
- console.log(`Starting ${SVC_CONFIG[svc].label} in foreground (Ctrl+C to stop)`);
276
- const r = spawnSync([bin], {
277
- stdio: ["inherit", "inherit", "inherit"],
278
- env: mergedEnv,
279
- });
280
- return r.status === 0;
281
- }
282
-
283
- // Background: detached spawn via node:child_process so the parent can exit
284
- // cleanly while the service keeps running. stdout+stderr append to logPath.
285
- const { spawn: nodeSpawn } = require("node:child_process");
286
- const { openSync } = require("node:fs");
287
- const logFd = openSync(logPath, "a");
288
- const child = nodeSpawn(bin, [], {
289
- env: mergedEnv,
290
- detached: true,
291
- stdio: ["ignore", logFd, logFd],
292
- });
293
- child.unref();
294
- if (!child.pid) {
295
- console.error(`${SVC_CONFIG[svc].label}: spawn failed`);
296
- return false;
297
- }
298
- writePid(svc, child.pid);
299
- console.log(`${SVC_CONFIG[svc].label} started (pid ${child.pid}, log ${logPath})`);
300
-
301
- // Grace window: if the process dies within 800ms it almost certainly failed
302
- // to start (bad env, port in use, missing dep). Surface the last log lines.
303
- const start = Date.now();
304
- while (Date.now() - start < 800) {
305
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 80);
306
- }
307
- if (!pidAlive(child.pid)) {
308
- console.error(`${SVC_CONFIG[svc].label}: exited within 800ms — likely a startup error.`);
309
- console.error(` Tail of ${logPath}:`);
310
- const tail = spawnSync(["tail", "-n", "20", logPath], { stdout: "pipe" });
311
- if (tail.status === 0) console.error(tail.stdout.toString());
312
- clearPid(svc);
313
- return false;
314
- }
315
- return true;
316
- }
317
-
318
- function stopOne(svc: SvcName): boolean {
319
- const pid = readPid(svc);
320
- if (!pid) {
321
- console.log(`${SVC_CONFIG[svc].label} not running (no PID file)`);
322
- return true;
323
- }
324
- if (!pidAlive(pid)) {
325
- console.log(`${SVC_CONFIG[svc].label} not running (stale PID file, removing)`);
326
- clearPid(svc);
327
- return true;
328
- }
329
- try {
330
- process.kill(pid, "SIGTERM");
331
- } catch (e) {
332
- console.error(`${SVC_CONFIG[svc].label}: SIGTERM failed: ${(e as Error).message}`);
333
- return false;
334
- }
335
- console.log(`${SVC_CONFIG[svc].label}: SIGTERM sent (pid ${pid})`);
336
- if (waitForExit(pid, 5000)) {
337
- clearPid(svc);
338
- console.log(`${SVC_CONFIG[svc].label} stopped`);
339
- return true;
340
- }
341
- console.warn(`${SVC_CONFIG[svc].label}: did not exit in 5s, sending SIGKILL`);
342
- try {
343
- process.kill(pid, "SIGKILL");
344
- } catch {
345
- // best-effort
346
- }
347
- if (waitForExit(pid, 2000)) {
348
- clearPid(svc);
349
- console.log(`${SVC_CONFIG[svc].label} killed`);
350
- return true;
351
- }
352
- console.error(`${SVC_CONFIG[svc].label}: failed to kill pid ${pid}`);
353
- return false;
354
- }
355
-
356
- function psAll(): void {
357
- console.log("ework-aio PID-file mode status:");
358
- for (const svc of Object.keys(SVC_CONFIG) as SvcName[]) {
359
- const pid = readPid(svc);
360
- const alive = pid !== null && pidAlive(pid);
361
- const cfg = SVC_CONFIG[svc];
362
- if (alive) {
363
- console.log(` ${cfg.label.padEnd(18)} running pid ${pid} log ${svcLogPath(svc)}`);
364
- } else if (pid !== null) {
365
- console.log(` ${cfg.label.padEnd(18)} dead stale pid ${pid} (run: ework-aio stop ${svc})`);
366
- } else {
367
- console.log(` ${cfg.label.padEnd(18)} stopped`);
368
- }
369
- }
370
- }
371
-
372
- function runPidMode(command: "start" | "stop" | "restart", argv: string[]): number {
373
- const { svc, foreground } = parseSvcArg(argv);
374
- if (command === "ps") {
375
- psAll();
376
- return 0;
377
- }
378
- const targets: SvcName[] = svc === "both" ? ["web", "daemon"] : [svc];
379
- let rc = 0;
380
- if (command === "stop") {
381
- for (const s of targets) if (!stopOne(s)) rc = 1;
382
- return rc;
383
- }
384
- if (command === "start") {
385
- for (const s of targets) if (!startOne(s, foreground)) rc = 1;
386
- return rc;
387
- }
388
- // restart
389
- for (const s of targets) stopOne(s);
390
- for (const s of targets) if (!startOne(s, foreground)) rc = 1;
391
- return rc;
392
- }
393
-
394
- if (
395
- subcommand === "start" ||
396
- subcommand === "stop" ||
397
- subcommand === "restart" ||
398
- subcommand === "ps"
399
- ) {
400
- process.exit(runPidMode(subcommand as "start" | "stop" | "restart", args));
401
- }
402
-
403
- if (subcommand === "migrate") {
404
- const migrateScript = resolve(__dirname, "..", "scripts", "migrate-from-gitea.ts");
405
- if (!existsSync(migrateScript)) {
406
- console.error(`Cannot find migrate script (looked at ${migrateScript}).`);
407
- console.error("This usually means the package was installed incorrectly.");
408
- process.exit(1);
409
- }
410
- const rest = args.slice(1);
411
- const result = spawnSync(["bun", migrateScript, ...rest], {
412
- stdio: ["inherit", "inherit", "inherit"],
413
- env: process.env,
414
- });
415
- if (result.status !== null) process.exit(result.status);
416
- process.exit(1);
417
- }
418
-
419
- if (subcommand === "backfill-timestamps") {
420
- const script = resolve(__dirname, "..", "scripts", "backfill-timestamps.ts");
421
- if (!existsSync(script)) {
422
- console.error(`Cannot find backfill-timestamps script (looked at ${script}).`);
423
- process.exit(1);
424
- }
425
- const rest = args.slice(1);
426
- const result = spawnSync(["bun", script, ...rest], {
427
- stdio: ["inherit", "inherit", "inherit"],
428
- env: process.env,
429
- });
430
- if (result.status !== null) process.exit(result.status);
431
- process.exit(1);
432
- }
433
-
434
- const finalArgs = knownSubs.includes(subcommand) ? args : ["install", ...args];
435
-
436
- const result = spawnSync(["bash", installSh, ...finalArgs], {
437
- stdio: ["inherit", "inherit", "inherit"],
438
- env: process.env,
439
- });
440
- if (result.status !== null) process.exit(result.status);
441
- process.exit(1);
4
+ const argv = process.argv.slice(2);
5
+ main(argv).then((code) => process.exit(code));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.1.17",
3
+ "version": "0.2.1",
4
4
  "description": "All-in-one installer for ework (issue tracker) + ework-daemon (AI bridge) + opencode-ework (plugin). One command: npm i -g ework-aio && ework-aio install.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,14 +40,17 @@
40
40
  "scripts": {
41
41
  "start": "bun bin/ework-aio.js",
42
42
  "check": "tsc --noEmit",
43
+ "test": "bun test",
43
44
  "postinstall": "echo '\\n ework-aio installed. Run \\033[1mework-aio install\\033[0m to set up services.\\n Help: ework-aio --help\\n'"
44
45
  },
45
46
  "dependencies": {
46
47
  "ework-web": "^0.1.0",
47
48
  "ework-daemon": "^0.1.0",
48
- "opencode-ework": "^0.1.0"
49
+ "opencode-ework": "^0.1.0",
50
+ "zod": "^3.23.0"
49
51
  },
50
52
  "devDependencies": {
51
- "@types/bun": "latest"
53
+ "@types/bun": "latest",
54
+ "typescript": "^5.4.0"
52
55
  }
53
56
  }