@castlemilk/omega 0.2.0 → 0.3.0
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/dist/cli.js +104 -26
- package/dist/proto/tasks.proto +37 -0
- package/dist/server.js +120 -0
- package/package.json +7 -4
package/dist/cli.js
CHANGED
|
@@ -12,8 +12,8 @@ function getApiUrl() {
|
|
|
12
12
|
const opts = program.opts();
|
|
13
13
|
return opts.api ?? "http://localhost:4000";
|
|
14
14
|
}
|
|
15
|
-
async function apiFetch(
|
|
16
|
-
const url = `${getApiUrl()}${
|
|
15
|
+
async function apiFetch(path4, init) {
|
|
16
|
+
const url = `${getApiUrl()}${path4}`;
|
|
17
17
|
const res = await fetch(url, init);
|
|
18
18
|
const data = await res.json().catch(() => ({}));
|
|
19
19
|
if (!res.ok) {
|
|
@@ -48,8 +48,85 @@ projectCmd.command("remove").description("Remove a project").argument("<id>", "p
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// ../../apps/cli/src/commands/task.ts
|
|
51
|
+
import { Command as Command3 } from "commander";
|
|
52
|
+
|
|
53
|
+
// ../../apps/cli/src/commands/task-feed.ts
|
|
51
54
|
import { Command as Command2 } from "commander";
|
|
52
|
-
|
|
55
|
+
import * as grpc from "@grpc/grpc-js";
|
|
56
|
+
import * as protoLoader from "@grpc/proto-loader";
|
|
57
|
+
|
|
58
|
+
// ../../apps/cli/src/lib/proto-path.ts
|
|
59
|
+
import { existsSync } from "fs";
|
|
60
|
+
import path from "path";
|
|
61
|
+
import { fileURLToPath } from "url";
|
|
62
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
63
|
+
var __dirname = path.dirname(__filename);
|
|
64
|
+
function findProtoPath() {
|
|
65
|
+
const candidates = [
|
|
66
|
+
// Bundled / installed package layout: dist/proto/tasks.proto
|
|
67
|
+
path.join(__dirname, "proto/tasks.proto"),
|
|
68
|
+
// Development layout from apps/cli/src/lib: ../../../../proto/tasks.proto
|
|
69
|
+
path.resolve(__dirname, "../../../../proto/tasks.proto")
|
|
70
|
+
];
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (existsSync(candidate)) return candidate;
|
|
73
|
+
}
|
|
74
|
+
return candidates[0] ?? "proto/tasks.proto";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ../../apps/cli/src/commands/task-feed.ts
|
|
78
|
+
var GRPC_TARGET = process.env.HARNESS_GRPC_TARGET ?? "localhost:50051";
|
|
79
|
+
var packageDefinition = protoLoader.loadSync(findProtoPath(), {
|
|
80
|
+
keepCase: true,
|
|
81
|
+
longs: String,
|
|
82
|
+
enums: String,
|
|
83
|
+
defaults: true,
|
|
84
|
+
oneofs: true
|
|
85
|
+
});
|
|
86
|
+
var proto = grpc.loadPackageDefinition(packageDefinition);
|
|
87
|
+
var taskFeedCmd = new Command2("feed").description("Feed a task into the harness via gRPC").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "simple").option("--tags <tags>", "comma-separated tags").option("--auto-run", "run the task immediately after submission", false).option("--stream", "stream task updates after submission", false).action(
|
|
88
|
+
(opts) => {
|
|
89
|
+
const client = new proto.omega.TaskIngestion(
|
|
90
|
+
GRPC_TARGET,
|
|
91
|
+
grpc.credentials.createInsecure()
|
|
92
|
+
);
|
|
93
|
+
const request = {
|
|
94
|
+
project_id: opts.project,
|
|
95
|
+
title: opts.title,
|
|
96
|
+
description: opts.description ?? "",
|
|
97
|
+
complexity: opts.complexity,
|
|
98
|
+
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
|
|
99
|
+
auto_run: opts.autoRun
|
|
100
|
+
};
|
|
101
|
+
if (opts.stream) {
|
|
102
|
+
const call = client.streamTasks();
|
|
103
|
+
call.on("data", (event) => {
|
|
104
|
+
console.log(JSON.stringify(event, null, 2));
|
|
105
|
+
});
|
|
106
|
+
call.on("error", (err) => {
|
|
107
|
+
console.error("Stream error:", err.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|
|
110
|
+
call.on("end", () => {
|
|
111
|
+
process.exit(0);
|
|
112
|
+
});
|
|
113
|
+
call.write({ project_id: opts.project });
|
|
114
|
+
}
|
|
115
|
+
client.submitTask(request, (err, response) => {
|
|
116
|
+
if (err) {
|
|
117
|
+
console.error("gRPC error:", err.message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
console.log(JSON.stringify(response, null, 2));
|
|
121
|
+
if (!opts.stream) {
|
|
122
|
+
client.close();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// ../../apps/cli/src/commands/task.ts
|
|
129
|
+
var taskCmd = new Command3("task").description("Manage tasks");
|
|
53
130
|
taskCmd.command("create").description("Create a task").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "simple").option("--tags <tags>", "comma-separated tags").action(async (opts) => {
|
|
54
131
|
const task = await apiFetch("/tasks", {
|
|
55
132
|
method: "POST",
|
|
@@ -73,13 +150,14 @@ taskCmd.command("run").description("Run a task through the router").argument("<i
|
|
|
73
150
|
const result = await apiFetch(`/tasks/${id}/run`, { method: "POST" });
|
|
74
151
|
console.log(JSON.stringify(result, null, 2));
|
|
75
152
|
});
|
|
153
|
+
taskCmd.addCommand(taskFeedCmd);
|
|
76
154
|
|
|
77
155
|
// ../../apps/cli/src/commands/ui.ts
|
|
78
|
-
import { Command as
|
|
156
|
+
import { Command as Command4 } from "commander";
|
|
79
157
|
import { spawn } from "child_process";
|
|
80
|
-
import { existsSync } from "fs";
|
|
81
|
-
import
|
|
82
|
-
import { fileURLToPath } from "url";
|
|
158
|
+
import { existsSync as existsSync2 } from "fs";
|
|
159
|
+
import path2 from "path";
|
|
160
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
83
161
|
import open from "open";
|
|
84
162
|
import { render } from "ink";
|
|
85
163
|
import React2 from "react";
|
|
@@ -338,12 +416,12 @@ function TuiApp() {
|
|
|
338
416
|
}
|
|
339
417
|
|
|
340
418
|
// ../../apps/cli/src/commands/ui.ts
|
|
341
|
-
var
|
|
342
|
-
var
|
|
419
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
420
|
+
var __dirname2 = path2.dirname(__filename2);
|
|
343
421
|
var API2 = process.env.HARNESS_API_URL ?? "http://localhost:4000";
|
|
344
422
|
function startBundledServer() {
|
|
345
|
-
const serverPath =
|
|
346
|
-
if (
|
|
423
|
+
const serverPath = path2.resolve(__dirname2, "server.js");
|
|
424
|
+
if (existsSync2(serverPath)) {
|
|
347
425
|
return spawn(process.execPath, [serverPath], { stdio: "inherit" });
|
|
348
426
|
}
|
|
349
427
|
return void 0;
|
|
@@ -366,7 +444,7 @@ async function waitForApi(maxMs = 15e3) {
|
|
|
366
444
|
}
|
|
367
445
|
async function ensureProject() {
|
|
368
446
|
const cwd = process.cwd();
|
|
369
|
-
const name =
|
|
447
|
+
const name = path2.basename(cwd);
|
|
370
448
|
try {
|
|
371
449
|
const listRes = await fetch(`${API2}/projects`);
|
|
372
450
|
const projects = await listRes.json();
|
|
@@ -389,7 +467,7 @@ async function ensureProject() {
|
|
|
389
467
|
console.warn("Could not auto-detect project:", err);
|
|
390
468
|
}
|
|
391
469
|
}
|
|
392
|
-
var uiCmd = new
|
|
470
|
+
var uiCmd = new Command4("ui").description("Open the harness web UI").option("--no-tui", "Do not open the terminal console alongside the web UI").action(async (options) => {
|
|
393
471
|
let server = void 0;
|
|
394
472
|
const alreadyRunning = await isApiReady();
|
|
395
473
|
const useTui = options.tui;
|
|
@@ -427,10 +505,10 @@ var uiCmd = new Command3("ui").description("Open the harness web UI").option("--
|
|
|
427
505
|
});
|
|
428
506
|
|
|
429
507
|
// ../../apps/cli/src/commands/console.ts
|
|
430
|
-
import { Command as
|
|
508
|
+
import { Command as Command5 } from "commander";
|
|
431
509
|
import { render as render2 } from "ink";
|
|
432
510
|
import React3 from "react";
|
|
433
|
-
var consoleCmd = new
|
|
511
|
+
var consoleCmd = new Command5("console").description("Open the harness TUI console").action(() => {
|
|
434
512
|
if (!process.stdin.isTTY) {
|
|
435
513
|
console.error("The harness console requires an interactive terminal (TTY).");
|
|
436
514
|
process.exit(1);
|
|
@@ -439,9 +517,9 @@ var consoleCmd = new Command4("console").description("Open the harness TUI conso
|
|
|
439
517
|
});
|
|
440
518
|
|
|
441
519
|
// ../../apps/cli/src/commands/skill.ts
|
|
442
|
-
import { Command as
|
|
443
|
-
import
|
|
444
|
-
import { fileURLToPath as
|
|
520
|
+
import { Command as Command6 } from "commander";
|
|
521
|
+
import path3 from "path";
|
|
522
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
445
523
|
|
|
446
524
|
// ../skills/src/parse.ts
|
|
447
525
|
import matter from "gray-matter";
|
|
@@ -542,22 +620,22 @@ async function registerSkill(sourcePath, outputDir) {
|
|
|
542
620
|
// ../../apps/cli/src/commands/skill.ts
|
|
543
621
|
function getOutputDir() {
|
|
544
622
|
if (process.env.OMEGA_HARNESS_ROOT) {
|
|
545
|
-
return
|
|
623
|
+
return path3.join(path3.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
|
|
546
624
|
}
|
|
547
625
|
try {
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
return
|
|
626
|
+
const __filename3 = fileURLToPath3(import.meta.url);
|
|
627
|
+
const __dirname3 = path3.dirname(__filename3);
|
|
628
|
+
return path3.join(path3.resolve(__dirname3, "../../../.."), "packages/skills/src");
|
|
551
629
|
} catch {
|
|
552
|
-
return
|
|
630
|
+
return path3.join(process.cwd(), "harness-skills");
|
|
553
631
|
}
|
|
554
632
|
}
|
|
555
|
-
var skillCmd = new
|
|
633
|
+
var skillCmd = new Command6("skill").description("Manage skill artifacts");
|
|
556
634
|
skillCmd.command("generate").description("Generate a harness adapter from a SKILL.md file").argument("<source>", "path to SKILL.md").action(async (source) => {
|
|
557
635
|
const outputDir = getOutputDir();
|
|
558
|
-
const manifest = await registerSkill(
|
|
636
|
+
const manifest = await registerSkill(path3.resolve(source), outputDir);
|
|
559
637
|
console.log(`Generated adapter for skill: ${manifest.name}`);
|
|
560
|
-
console.log(`Output directory: ${
|
|
638
|
+
console.log(`Output directory: ${path3.join(outputDir, "generated")}`);
|
|
561
639
|
});
|
|
562
640
|
|
|
563
641
|
// ../../apps/cli/src/index.ts
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package omega;
|
|
4
|
+
|
|
5
|
+
service TaskIngestion {
|
|
6
|
+
rpc SubmitTask (TaskRequest) returns (TaskResponse);
|
|
7
|
+
rpc StreamTasks (TaskStreamRequest) returns (stream TaskEvent);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
message TaskRequest {
|
|
11
|
+
string project_id = 1;
|
|
12
|
+
string title = 2;
|
|
13
|
+
string description = 3;
|
|
14
|
+
string complexity = 4;
|
|
15
|
+
repeated string tags = 5;
|
|
16
|
+
bool auto_run = 6;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
message TaskResponse {
|
|
20
|
+
string id = 1;
|
|
21
|
+
string status = 2;
|
|
22
|
+
string error = 3;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
message TaskStreamRequest {
|
|
26
|
+
string project_id = 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
message TaskEvent {
|
|
30
|
+
string id = 1;
|
|
31
|
+
string title = 2;
|
|
32
|
+
string status = 3;
|
|
33
|
+
string provider = 4;
|
|
34
|
+
string model = 5;
|
|
35
|
+
string result = 6;
|
|
36
|
+
string error = 7;
|
|
37
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -6,6 +6,8 @@ import cors from "cors";
|
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
+
import * as grpc from "@grpc/grpc-js";
|
|
10
|
+
import * as protoLoader from "@grpc/proto-loader";
|
|
9
11
|
|
|
10
12
|
// ../providers/src/openai.ts
|
|
11
13
|
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -472,3 +474,121 @@ var PORT = Number(process.env.PORT ?? 4e3);
|
|
|
472
474
|
app.listen(PORT, () => {
|
|
473
475
|
console.log(`Omega harness server on http://localhost:${PORT.toString()}`);
|
|
474
476
|
});
|
|
477
|
+
var GRPC_PORT = Number(process.env.GRPC_PORT ?? 50051);
|
|
478
|
+
var GRPC_PROTO_PATH = path.join(__dirname, "proto/tasks.proto");
|
|
479
|
+
var grpcPackageDef = protoLoader.loadSync(GRPC_PROTO_PATH, {
|
|
480
|
+
keepCase: true,
|
|
481
|
+
longs: String,
|
|
482
|
+
enums: String,
|
|
483
|
+
defaults: true,
|
|
484
|
+
oneofs: true
|
|
485
|
+
});
|
|
486
|
+
var grpcProto = grpc.loadPackageDefinition(grpcPackageDef);
|
|
487
|
+
function parseGrpcRequest(req) {
|
|
488
|
+
const r = req;
|
|
489
|
+
return {
|
|
490
|
+
project_id: typeof r.project_id === "string" ? r.project_id : "",
|
|
491
|
+
title: typeof r.title === "string" ? r.title : "",
|
|
492
|
+
description: typeof r.description === "string" ? r.description : void 0,
|
|
493
|
+
complexity: typeof r.complexity === "string" ? r.complexity : void 0,
|
|
494
|
+
tags: Array.isArray(r.tags) ? r.tags.filter((t) => typeof t === "string") : void 0,
|
|
495
|
+
auto_run: typeof r.auto_run === "boolean" ? r.auto_run : void 0
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function isValidComplexity(value) {
|
|
499
|
+
return ["simple", "medium", "complex"].includes(value);
|
|
500
|
+
}
|
|
501
|
+
async function executeTask(task) {
|
|
502
|
+
task.status = "in_progress";
|
|
503
|
+
task.error = void 0;
|
|
504
|
+
task.result = void 0;
|
|
505
|
+
const selection = selectProvider(providerConfigs, rules, task);
|
|
506
|
+
if (!selection) {
|
|
507
|
+
task.status = "failed";
|
|
508
|
+
task.error = "No provider available for this task";
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
const provider = createProvider(selection.provider);
|
|
513
|
+
const prompt = [task.title, task.description].filter(Boolean).join("\n\n");
|
|
514
|
+
const result = await provider.send(prompt, { model: selection.model });
|
|
515
|
+
task.status = "done";
|
|
516
|
+
task.result = result;
|
|
517
|
+
task.assignedModel = { provider: selection.provider.name, model: selection.model };
|
|
518
|
+
} catch (err) {
|
|
519
|
+
task.status = "failed";
|
|
520
|
+
task.error = err instanceof Error ? err.message : String(err);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
var grpcServer = new grpc.Server();
|
|
524
|
+
grpcServer.addService(grpcProto.omega.TaskIngestion.service, {
|
|
525
|
+
submitTask: (call, callback) => {
|
|
526
|
+
void (async () => {
|
|
527
|
+
try {
|
|
528
|
+
const req = parseGrpcRequest(call.request);
|
|
529
|
+
if (!req.project_id || !req.title) {
|
|
530
|
+
callback(
|
|
531
|
+
{ code: grpc.status.INVALID_ARGUMENT, message: "project_id and title are required" },
|
|
532
|
+
null
|
|
533
|
+
);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const complexity = isValidComplexity(req.complexity ?? "") ? req.complexity : "simple";
|
|
537
|
+
const task = {
|
|
538
|
+
id: newId(),
|
|
539
|
+
projectId: req.project_id,
|
|
540
|
+
title: req.title,
|
|
541
|
+
description: req.description ?? void 0,
|
|
542
|
+
status: "todo",
|
|
543
|
+
complexity,
|
|
544
|
+
tags: req.tags ?? [],
|
|
545
|
+
createdAt: now(),
|
|
546
|
+
updatedAt: now()
|
|
547
|
+
};
|
|
548
|
+
tasks.push(task);
|
|
549
|
+
if (req.auto_run) {
|
|
550
|
+
await executeTask(task);
|
|
551
|
+
}
|
|
552
|
+
callback(null, { id: task.id, status: task.status, error: task.error ?? "" });
|
|
553
|
+
} catch (err) {
|
|
554
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
555
|
+
callback({ code: grpc.status.INTERNAL, message }, null);
|
|
556
|
+
}
|
|
557
|
+
})();
|
|
558
|
+
},
|
|
559
|
+
streamTasks: (call) => {
|
|
560
|
+
const req = call.request;
|
|
561
|
+
const projectId = req.project_id;
|
|
562
|
+
let cancelled = false;
|
|
563
|
+
const interval = setInterval(() => {
|
|
564
|
+
if (cancelled) return;
|
|
565
|
+
const filtered = projectId ? tasks.filter((t) => t.projectId === projectId) : tasks;
|
|
566
|
+
for (const task of filtered.slice(-20)) {
|
|
567
|
+
call.write({
|
|
568
|
+
id: task.id,
|
|
569
|
+
title: task.title,
|
|
570
|
+
status: task.status,
|
|
571
|
+
provider: task.assignedModel?.provider ?? "",
|
|
572
|
+
model: task.assignedModel?.model ?? "",
|
|
573
|
+
result: task.result ?? "",
|
|
574
|
+
error: task.error ?? ""
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}, 1e3);
|
|
578
|
+
call.on("cancelled", () => {
|
|
579
|
+
cancelled = true;
|
|
580
|
+
clearInterval(interval);
|
|
581
|
+
});
|
|
582
|
+
call.on("error", () => {
|
|
583
|
+
cancelled = true;
|
|
584
|
+
clearInterval(interval);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
grpcServer.bindAsync(`0.0.0.0:${GRPC_PORT.toString()}`, grpc.ServerCredentials.createInsecure(), (err) => {
|
|
589
|
+
if (err) {
|
|
590
|
+
console.error("gRPC server failed to start:", err);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
console.log(`gRPC task ingestion on 0.0.0.0:${GRPC_PORT.toString()}`);
|
|
594
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@castlemilk/omega",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Omega Harness CLI - installable via npx",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@grpc/grpc-js": "^1.14.4",
|
|
16
|
+
"@grpc/proto-loader": "^0.8.1",
|
|
15
17
|
"commander": "^12.1.0",
|
|
16
18
|
"cors": "^2.8.5",
|
|
17
19
|
"express": "^4.19.2",
|
|
@@ -40,9 +42,10 @@
|
|
|
40
42
|
"url": "git+https://github.com/castlemilk/harness.git"
|
|
41
43
|
},
|
|
42
44
|
"scripts": {
|
|
43
|
-
"build:cli": "esbuild ../../apps/cli/src/index.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:commander --external:open --external:gray-matter --external:yaml --external:ink --external:react --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/skills=../../packages/skills/src/index.ts",
|
|
44
|
-
"build:server": "esbuild src/server.ts --bundle --platform=node --format=esm --outfile=dist/server.js --external:express --external:cors --external:zod --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/providers=../../packages/providers/src/index.ts --alias:@omega/router=../../packages/router/src/index.ts",
|
|
45
|
+
"build:cli": "esbuild ../../apps/cli/src/index.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:commander --external:open --external:gray-matter --external:yaml --external:ink --external:react --external:@grpc/grpc-js --external:@grpc/proto-loader --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/skills=../../packages/skills/src/index.ts",
|
|
46
|
+
"build:server": "esbuild src/server.ts --bundle --platform=node --format=esm --outfile=dist/server.js --external:express --external:cors --external:zod --external:@grpc/grpc-js --external:@grpc/proto-loader --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/providers=../../packages/providers/src/index.ts --alias:@omega/router=../../packages/router/src/index.ts",
|
|
45
47
|
"copy:web": "cp -R ../../apps/web/dist dist/web",
|
|
46
|
-
"
|
|
48
|
+
"copy:proto": "mkdir -p dist/proto && cp ../../proto/tasks.proto dist/proto/tasks.proto",
|
|
49
|
+
"build": "pnpm build:cli && pnpm build:server && pnpm copy:web && pnpm copy:proto"
|
|
47
50
|
}
|
|
48
51
|
}
|