@moor-sh/cli 0.5.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moor-sh/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Command-line interface for moor - manage your moor server's projects, logs, env vars, and container lifecycle.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,16 +17,19 @@
17
17
  ],
18
18
  "type": "module",
19
19
  "bin": {
20
- "moor": "src/index.ts"
20
+ "moor": "bin/moor.js"
21
21
  },
22
22
  "files": [
23
- "src/"
23
+ "bin/",
24
+ "dist/"
24
25
  ],
25
26
  "publishConfig": {
26
27
  "access": "public"
27
28
  },
28
29
  "scripts": {
29
30
  "dev": "bun run src/index.ts",
30
- "build": "bun build src/index.ts --compile --outfile moor"
31
+ "build": "bun build src/index.ts --compile --outfile moor",
32
+ "build:package": "bun build src/index.ts --target bun --outfile dist/index.js",
33
+ "prepack": "bun run build:package"
31
34
  }
32
35
  }
package/src/client.ts DELETED
@@ -1,106 +0,0 @@
1
- function getConfig(): { baseUrl: string; apiKey: string } {
2
- const baseUrl = process.env.MOOR_URL;
3
- const apiKey = process.env.MOOR_API_KEY;
4
- if (!baseUrl) {
5
- console.error("Error: MOOR_URL is not set");
6
- process.exit(1);
7
- }
8
- if (!apiKey) {
9
- console.error("Error: MOOR_API_KEY is not set");
10
- process.exit(1);
11
- }
12
- return { baseUrl: baseUrl.replace(/\/$/, ""), apiKey };
13
- }
14
-
15
- function headers(apiKey: string, json = false): Record<string, string> {
16
- const h: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
17
- if (json) h["Content-Type"] = "application/json";
18
- return h;
19
- }
20
-
21
- export async function apiGet(path: string): Promise<Response> {
22
- const { baseUrl, apiKey } = getConfig();
23
- return fetch(`${baseUrl}${path}`, { headers: headers(apiKey) });
24
- }
25
-
26
- export async function apiPost(path: string, body?: unknown): Promise<Response> {
27
- const { baseUrl, apiKey } = getConfig();
28
- return fetch(`${baseUrl}${path}`, {
29
- method: "POST",
30
- headers: headers(apiKey, body !== undefined),
31
- body: body !== undefined ? JSON.stringify(body) : undefined,
32
- });
33
- }
34
-
35
- export async function apiPut(path: string, body: unknown): Promise<Response> {
36
- const { baseUrl, apiKey } = getConfig();
37
- return fetch(`${baseUrl}${path}`, {
38
- method: "PUT",
39
- headers: headers(apiKey, true),
40
- body: JSON.stringify(body),
41
- });
42
- }
43
-
44
- type Project = {
45
- id: number;
46
- name: string;
47
- status: string;
48
- container_id: string | null;
49
- image_tag: string | null;
50
- domain: string | null;
51
- docker_image: string | null;
52
- github_url: string | null;
53
- };
54
-
55
- export async function resolveProject(nameOrId: string): Promise<Project> {
56
- const res = await apiGet("/api/projects");
57
- if (!res.ok) {
58
- console.error(`Failed to list projects: ${res.status}`);
59
- process.exit(1);
60
- }
61
- const projects = (await res.json()) as Project[];
62
- const match = projects.find((p) => p.name === nameOrId || String(p.id) === nameOrId);
63
- if (!match) {
64
- console.error(`Project "${nameOrId}" not found`);
65
- process.exit(1);
66
- }
67
- return match;
68
- }
69
-
70
- export async function streamSSE(
71
- res: Response,
72
- handlers: {
73
- onLog?: (text: string) => void;
74
- onError?: (text: string) => void;
75
- onDone?: (text: string) => void;
76
- },
77
- ): Promise<void> {
78
- const reader = res.body?.getReader();
79
- if (!reader) throw new Error("No response body");
80
-
81
- const decoder = new TextDecoder();
82
- let buffer = "";
83
- let currentEvent = "";
84
-
85
- while (true) {
86
- const { done, value } = await reader.read();
87
- if (done) break;
88
- buffer += decoder.decode(value, { stream: true });
89
-
90
- const lines = buffer.split("\n");
91
- buffer = lines.pop() || "";
92
-
93
- for (const line of lines) {
94
- if (line.startsWith("event: ")) {
95
- currentEvent = line.slice(7).trim();
96
- } else if (line.startsWith("data: ")) {
97
- const data = JSON.parse(line.slice(6));
98
- if (currentEvent === "log") handlers.onLog?.(data);
99
- else if (currentEvent === "error") handlers.onError?.(data);
100
- else if (currentEvent === "done") handlers.onDone?.(data);
101
- currentEvent = "";
102
- }
103
- // Ignore keepalive comments starting with ":"
104
- }
105
- }
106
- }
@@ -1,97 +0,0 @@
1
- import { apiGet, apiPost, apiPut, resolveProject } from "../client";
2
-
3
- type EnvVar = { key: string; value: string };
4
-
5
- export async function envCommand(args: string[]) {
6
- const subcommand = args[0];
7
-
8
- if (subcommand === "list") return envList(args.slice(1));
9
- if (subcommand === "set") return envSet(args.slice(1));
10
-
11
- console.error("Usage: moor env <list|set> <project> [K=V ...]");
12
- process.exit(1);
13
- }
14
-
15
- async function envList(args: string[]) {
16
- const projectName = args[0];
17
- if (!projectName) {
18
- console.error("Usage: moor env list <project>");
19
- process.exit(1);
20
- }
21
-
22
- const project = await resolveProject(projectName);
23
- const res = await apiGet(`/api/projects/${project.id}/envs`);
24
- if (!res.ok) {
25
- console.error(`Failed: ${await res.text()}`);
26
- process.exit(1);
27
- }
28
-
29
- const vars = (await res.json()) as EnvVar[];
30
- if (vars.length === 0) {
31
- console.log("No environment variables set.");
32
- return;
33
- }
34
- for (const v of vars) {
35
- console.log(`${v.key}=${v.value}`);
36
- }
37
- }
38
-
39
- async function envSet(args: string[]) {
40
- const projectName = args[0];
41
- const pairs = args.slice(1);
42
-
43
- if (!projectName || pairs.length === 0) {
44
- console.error("Usage: moor env set <project> KEY=VALUE [KEY=VALUE ...]");
45
- process.exit(1);
46
- }
47
-
48
- // Parse K=V pairs
49
- const newVars: EnvVar[] = [];
50
- for (const pair of pairs) {
51
- const eq = pair.indexOf("=");
52
- if (eq === -1) {
53
- console.error(`Invalid format: "${pair}" (expected KEY=VALUE)`);
54
- process.exit(1);
55
- }
56
- newVars.push({ key: pair.slice(0, eq), value: pair.slice(eq + 1) });
57
- }
58
-
59
- const project = await resolveProject(projectName);
60
-
61
- // Fetch existing env vars and merge
62
- const existingRes = await apiGet(`/api/projects/${project.id}/envs`);
63
- if (!existingRes.ok) {
64
- console.error(`Failed to get env vars: ${await existingRes.text()}`);
65
- process.exit(1);
66
- }
67
- const existing = (await existingRes.json()) as EnvVar[];
68
-
69
- // Merge: new values overwrite existing keys
70
- const merged = new Map(existing.map((v) => [v.key, v.value]));
71
- for (const v of newVars) {
72
- merged.set(v.key, v.value);
73
- }
74
- const allVars = Array.from(merged, ([key, value]) => ({ key, value }));
75
-
76
- const setRes = await apiPut(`/api/projects/${project.id}/envs`, allVars);
77
- if (!setRes.ok) {
78
- console.error(`Failed to set env vars: ${await setRes.text()}`);
79
- process.exit(1);
80
- }
81
-
82
- for (const v of newVars) {
83
- console.log(`Set ${v.key}`);
84
- }
85
-
86
- // Restart if container is running
87
- if (project.status === "running") {
88
- console.log(`Restarting ${project.name}...`);
89
- await apiPost(`/api/projects/${project.id}/stop`);
90
- const startRes = await apiPost(`/api/projects/${project.id}/start`);
91
- if (!startRes.ok) {
92
- console.error(`Warning: failed to restart: ${await startRes.text()}`);
93
- process.exit(1);
94
- }
95
- console.log(`${project.name} restarted.`);
96
- }
97
- }
@@ -1,29 +0,0 @@
1
- import { apiPost, resolveProject } from "../client";
2
-
3
- export async function execCommand(args: string[]) {
4
- const projectName = args[0];
5
- const command = args.slice(1).join(" ");
6
-
7
- if (!projectName || !command) {
8
- console.error("Usage: moor exec <project> <command>");
9
- process.exit(1);
10
- }
11
-
12
- const project = await resolveProject(projectName);
13
- const res = await apiPost(`/api/projects/${project.id}/exec`, { command });
14
-
15
- if (!res.ok) {
16
- console.error(`Failed: ${await res.text()}`);
17
- process.exit(1);
18
- }
19
-
20
- const result = (await res.json()) as {
21
- exitCode: number;
22
- stdout: string;
23
- stderr: string;
24
- };
25
-
26
- if (result.stdout) process.stdout.write(result.stdout);
27
- if (result.stderr) process.stderr.write(result.stderr);
28
- process.exit(result.exitCode);
29
- }
@@ -1,37 +0,0 @@
1
- // #133: regression test for the history arg parser. The original took the
2
- // first non-flag token as the project, so `--hours 1 fipe-pg` silently used
3
- // the hours value ("1") as the project. Parsing must be order-independent.
4
-
5
- import { describe, expect, test } from "bun:test";
6
- import { parseHistoryArgs } from "./history";
7
-
8
- describe("parseHistoryArgs", () => {
9
- test("project then flag", () => {
10
- expect(parseHistoryArgs(["fipe-pg", "--hours", "1"])).toEqual({ project: "fipe-pg", hours: 1 });
11
- });
12
-
13
- test("flag then project (regression: was mis-parsed as project '1')", () => {
14
- expect(parseHistoryArgs(["--hours", "1", "fipe-pg"])).toEqual({ project: "fipe-pg", hours: 1 });
15
- });
16
-
17
- test("--hours=N form", () => {
18
- expect(parseHistoryArgs(["--hours=6", "fipe-pg"])).toEqual({ project: "fipe-pg", hours: 6 });
19
- });
20
-
21
- test("default hours is 24 when omitted", () => {
22
- expect(parseHistoryArgs(["fipe-pg"])).toEqual({ project: "fipe-pg", hours: 24 });
23
- });
24
-
25
- test("missing project → error, no project", () => {
26
- const r = parseHistoryArgs(["--hours", "1"]);
27
- expect(r.project).toBeUndefined();
28
- expect(r.error).toBeTruthy();
29
- });
30
-
31
- test("invalid hours → error", () => {
32
- expect(parseHistoryArgs(["fipe-pg", "--hours", "0"]).error).toBeTruthy();
33
- expect(parseHistoryArgs(["fipe-pg", "--hours", "-3"]).error).toBeTruthy();
34
- expect(parseHistoryArgs(["fipe-pg", "--hours", "abc"]).error).toBeTruthy();
35
- expect(parseHistoryArgs(["fipe-pg", "--hours"]).error).toBeTruthy(); // no value
36
- });
37
- });
@@ -1,117 +0,0 @@
1
- import { apiGet, resolveProject } from "../client";
2
-
3
- type HistoryResponse = {
4
- from_ms: number;
5
- to_ms: number;
6
- events: Array<{ occurred_at_ms: number; source: string; action: string }>;
7
- summary: {
8
- sample_count: number;
9
- running_sample_count: number;
10
- cpu_percent_avg: number | null;
11
- cpu_percent_max: number | null;
12
- mem_bytes_max: number | null;
13
- net_rx_bytes_total: number;
14
- net_tx_bytes_total: number;
15
- event_counts: Record<string, number>;
16
- has_gap: boolean;
17
- };
18
- };
19
-
20
- function formatBytes(bytes: number): string {
21
- if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
22
- const units = ["B", "KB", "MB", "GB", "TB"];
23
- const i = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
24
- const val = bytes / 1024 ** i;
25
- return `${val.toFixed(val < 10 ? 1 : 0)} ${units[i]}`;
26
- }
27
-
28
- // Parse `<project> [--hours N]` in any order. Critically, `--hours`'s value is
29
- // consumed as the flag's argument, not mistaken for the project — so both
30
- // `history p --hours 1` and `history --hours 1 p` resolve `p` (the old
31
- // first-non-flag-token approach silently took the hours value as the project).
32
- // Also accepts `--hours=N`. Pure + exported for tests.
33
- export function parseHistoryArgs(args: string[]): {
34
- project?: string;
35
- hours: number;
36
- error?: string;
37
- } {
38
- let project: string | undefined;
39
- let hours = 24;
40
- for (let i = 0; i < args.length; i++) {
41
- const a = args[i];
42
- if (a === "--hours") {
43
- const n = Number(args[++i]); // consume the next token as the value
44
- if (!Number.isFinite(n) || n <= 0)
45
- return { hours, error: "--hours must be a positive number" };
46
- hours = n;
47
- } else if (a.startsWith("--hours=")) {
48
- const n = Number(a.slice("--hours=".length));
49
- if (!Number.isFinite(n) || n <= 0)
50
- return { hours, error: "--hours must be a positive number" };
51
- hours = n;
52
- } else if (!a.startsWith("-") && project === undefined) {
53
- project = a;
54
- }
55
- // other flags / extra positionals are ignored
56
- }
57
- if (!project) return { hours, error: "missing project" };
58
- return { project, hours };
59
- }
60
-
61
- // Stored resource history + lifecycle events for one project (not live — use
62
- // `moor stats` for the host snapshot). Mirrors the moor_project_history MCP
63
- // tool: a window summary plus recent events.
64
- export async function historyCommand(args: string[]) {
65
- const parsed = parseHistoryArgs(args);
66
- if (parsed.error || !parsed.project) {
67
- console.error(
68
- parsed.error && parsed.error !== "missing project"
69
- ? parsed.error
70
- : "Usage: moor history <project> [--hours N]",
71
- );
72
- process.exit(1);
73
- }
74
-
75
- const project = await resolveProject(parsed.project);
76
- const to = Date.now();
77
- const from = to - parsed.hours * 3_600_000;
78
- const res = await apiGet(`/api/projects/${project.id}/stats/history?from=${from}&to=${to}`);
79
- if (!res.ok) {
80
- console.error(`Failed to get history: ${res.status} ${await res.text()}`);
81
- process.exit(1);
82
- }
83
-
84
- const h = (await res.json()) as HistoryResponse;
85
- const s = h.summary;
86
- const windowH = Math.round(((h.to_ms - h.from_ms) / 3_600_000) * 10) / 10;
87
-
88
- console.log(`${project.name} — history over ~${windowH}h`);
89
- if (s.has_gap) {
90
- console.log(" \x1b[33m⚠ event gap recorded: events may be incomplete\x1b[0m");
91
- }
92
- console.log(` Samples: ${s.sample_count} total, ${s.running_sample_count} running`);
93
- console.log(
94
- ` CPU: avg ${s.cpu_percent_avg ?? "n/a"}% / max ${s.cpu_percent_max ?? "n/a"}%`,
95
- );
96
- console.log(
97
- ` Memory: max ${s.mem_bytes_max !== null ? formatBytes(s.mem_bytes_max) : "n/a"}`,
98
- );
99
- console.log(
100
- ` Network: in ${formatBytes(s.net_rx_bytes_total)} / out ${formatBytes(s.net_tx_bytes_total)}`,
101
- );
102
- const counts = Object.entries(s.event_counts);
103
- if (counts.length > 0) {
104
- console.log(` Events: ${counts.map(([a, n]) => `${a} ${n}`).join(", ")}`);
105
- }
106
-
107
- const recent = h.events.slice(-10);
108
- if (recent.length > 0) {
109
- console.log("\nRecent events:");
110
- for (const e of recent) {
111
- console.log(` ${new Date(e.occurred_at_ms).toISOString()} ${e.action} (${e.source})`);
112
- }
113
- }
114
- if (s.sample_count === 0 && h.events.length === 0) {
115
- console.log(" (no stored history in this window)");
116
- }
117
- }
@@ -1,62 +0,0 @@
1
- import { apiGet, resolveProject } from "../client";
2
-
3
- export async function logsCommand(args: string[]) {
4
- let follow = false;
5
- let tail = 100;
6
- let projectName: string | undefined;
7
-
8
- for (let i = 0; i < args.length; i++) {
9
- if (args[i] === "-f" || args[i] === "--follow") {
10
- follow = true;
11
- } else if (args[i] === "-n" || args[i] === "--lines") {
12
- tail = Number(args[++i]);
13
- } else if (!projectName) {
14
- projectName = args[i];
15
- }
16
- }
17
-
18
- if (!projectName) {
19
- console.error("Usage: moor logs <project> [-f] [-n <lines>]");
20
- process.exit(1);
21
- }
22
-
23
- const project = await resolveProject(projectName);
24
-
25
- // Initial fetch with tail
26
- const res = await apiGet(`/api/projects/${project.id}/logs?tail=${tail}`);
27
- if (!res.ok) {
28
- console.error(`Failed to get logs: ${res.status}`);
29
- process.exit(1);
30
- }
31
-
32
- const data = (await res.json()) as { logs: string; lastTimestamp: number };
33
- if (data.logs) process.stdout.write(data.logs);
34
-
35
- if (!follow) return;
36
-
37
- // Poll for new logs
38
- let since = data.lastTimestamp;
39
- const poll = async () => {
40
- try {
41
- const res = await apiGet(`/api/projects/${project.id}/logs?since=${since}`);
42
- if (!res.ok) return;
43
- const data = (await res.json()) as { logs: string; lastTimestamp: number };
44
- if (data.logs?.trim()) {
45
- process.stdout.write(data.logs);
46
- since = data.lastTimestamp;
47
- }
48
- } catch {
49
- // Connection error, keep polling
50
- }
51
- };
52
-
53
- const interval = setInterval(poll, 2000);
54
-
55
- process.on("SIGINT", () => {
56
- clearInterval(interval);
57
- process.exit(0);
58
- });
59
-
60
- // Keep the process alive
61
- await new Promise(() => {});
62
- }
@@ -1,136 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- type Client = "claude" | "codex";
5
-
6
- const CLIENT_ALIASES: Record<string, Client> = {
7
- claude: "claude",
8
- "claude-code": "claude",
9
- codex: "codex",
10
- };
11
-
12
- const DEFAULT_URL = "http://127.0.0.1:8080";
13
- const PLACEHOLDER_KEY = "<your-api-key>";
14
-
15
- /** Parse cwd's .env conservatively. Skips blank/comment lines, strips one
16
- * matching pair of surrounding quotes, does not interpolate or expand. */
17
- function parseDotEnv(path: string): Record<string, string> {
18
- if (!existsSync(path)) return {};
19
- const content = readFileSync(path, "utf8");
20
- const env: Record<string, string> = {};
21
- for (const rawLine of content.split("\n")) {
22
- const line = rawLine.trim();
23
- if (!line || line.startsWith("#")) continue;
24
- const eq = line.indexOf("=");
25
- if (eq === -1) continue;
26
- const key = line.slice(0, eq).trim();
27
- let value = line.slice(eq + 1).trim();
28
- if (
29
- value.length >= 2 &&
30
- ((value.startsWith('"') && value.endsWith('"')) ||
31
- (value.startsWith("'") && value.endsWith("'")))
32
- ) {
33
- value = value.slice(1, -1);
34
- }
35
- env[key] = value;
36
- }
37
- return env;
38
- }
39
-
40
- function resolveApiKey(flagValue: string | undefined): string {
41
- if (flagValue) return flagValue;
42
- if (process.env.MOOR_API_KEY) return process.env.MOOR_API_KEY;
43
- const dotenv = parseDotEnv(join(process.cwd(), ".env"));
44
- if (dotenv.MOOR_API_KEY) return dotenv.MOOR_API_KEY;
45
- return PLACEHOLDER_KEY;
46
- }
47
-
48
- function configFor(client: Client, url: string, apiKey: string): string {
49
- if (client === "claude") {
50
- return JSON.stringify(
51
- {
52
- mcpServers: {
53
- moor: {
54
- command: "bunx",
55
- args: ["@moor-sh/mcp"],
56
- env: { MOOR_URL: url, MOOR_API_KEY: apiKey },
57
- },
58
- },
59
- },
60
- null,
61
- 2,
62
- );
63
- }
64
- // codex TOML. TOML basic-string escape rules (" and \) are a subset of
65
- // JSON's, so JSON.stringify produces a valid quoted TOML string for any
66
- // value - including URLs/keys that contain quotes or backslashes.
67
- return [
68
- "[mcp_servers.moor]",
69
- 'command = "bunx"',
70
- 'args = ["@moor-sh/mcp"]',
71
- "",
72
- "[mcp_servers.moor.env]",
73
- `MOOR_URL = ${JSON.stringify(url)}`,
74
- `MOOR_API_KEY = ${JSON.stringify(apiKey)}`,
75
- ].join("\n");
76
- }
77
-
78
- function parseFlags(args: string[]): { client?: string; url?: string; apiKey?: string } {
79
- const flags: { client?: string; url?: string; apiKey?: string } = {};
80
- for (let i = 0; i < args.length; i++) {
81
- const a = args[i];
82
- if (a === "--client" && i + 1 < args.length) flags.client = args[++i];
83
- else if (a === "--url" && i + 1 < args.length) flags.url = args[++i];
84
- else if (a === "--api-key" && i + 1 < args.length) flags.apiKey = args[++i];
85
- }
86
- return flags;
87
- }
88
-
89
- function printHelp() {
90
- console.log(`moor mcp config - Generate MCP client config snippet for moor
91
-
92
- Usage: moor mcp config --client <name> [--url <url>] [--api-key <key>]
93
-
94
- Required:
95
- --client <name> One of: claude, claude-code, codex
96
-
97
- Optional:
98
- --url <url> moor URL the MCP server should reach (default: ${DEFAULT_URL})
99
- --api-key <key> bearer token. Falls back to MOOR_API_KEY env, then
100
- cwd's .env, then a placeholder.
101
-
102
- Output is printed to stdout for you to paste into your client's config file:
103
- Claude Code: ~/.claude.json
104
- Codex: ~/.codex/config.toml`);
105
- }
106
-
107
- export function mcpCommand(args: string[]) {
108
- const subcommand = args[0];
109
-
110
- if (subcommand === "--help" || subcommand === "-h" || subcommand === "help" || !subcommand) {
111
- printHelp();
112
- return;
113
- }
114
-
115
- if (subcommand !== "config") {
116
- console.error(`Unknown mcp subcommand: ${subcommand}\n`);
117
- printHelp();
118
- process.exit(1);
119
- }
120
-
121
- const flags = parseFlags(args.slice(1));
122
- if (!flags.client) {
123
- console.error("moor mcp config requires --client <claude|claude-code|codex>\n");
124
- printHelp();
125
- process.exit(1);
126
- }
127
- const client = CLIENT_ALIASES[flags.client];
128
- if (!client) {
129
- console.error(`Unknown client: ${flags.client}. Expected one of: claude, claude-code, codex`);
130
- process.exit(1);
131
- }
132
-
133
- const url = flags.url || DEFAULT_URL;
134
- const apiKey = resolveApiKey(flags.apiKey);
135
- console.log(configFor(client, url, apiKey));
136
- }
@@ -1,40 +0,0 @@
1
- import { apiPost, resolveProject, streamSSE } from "../client";
2
-
3
- export async function rebuildCommand(args: string[]) {
4
- let noCache = false;
5
- let projectName: string | undefined;
6
-
7
- for (const arg of args) {
8
- if (arg === "--no-cache") noCache = true;
9
- else if (!projectName) projectName = arg;
10
- }
11
-
12
- if (!projectName) {
13
- console.error("Usage: moor rebuild <project> [--no-cache]");
14
- process.exit(1);
15
- }
16
-
17
- const project = await resolveProject(projectName);
18
- const query = noCache ? "?nocache=true" : "";
19
-
20
- console.log(`Rebuilding ${project.name}...`);
21
- const res = await apiPost(`/api/projects/${project.id}/run${query}`);
22
-
23
- if (!res.ok && res.headers.get("content-type")?.includes("text/event-stream") === false) {
24
- const body = await res.text();
25
- console.error(`Failed: ${body}`);
26
- process.exit(1);
27
- }
28
-
29
- let failed = false;
30
- await streamSSE(res, {
31
- onLog: (text) => process.stdout.write(text),
32
- onError: (text) => {
33
- process.stderr.write(`Error: ${text}\n`);
34
- failed = true;
35
- },
36
- onDone: (text) => console.log(text),
37
- });
38
-
39
- process.exit(failed ? 1 : 0);
40
- }
@@ -1,27 +0,0 @@
1
- import { apiPost, resolveProject } from "../client";
2
-
3
- export async function restartCommand(args: string[]) {
4
- const projectName = args[0];
5
- if (!projectName) {
6
- console.error("Usage: moor restart <project>");
7
- process.exit(1);
8
- }
9
-
10
- const project = await resolveProject(projectName);
11
-
12
- console.log(`Stopping ${project.name}...`);
13
- const stopRes = await apiPost(`/api/projects/${project.id}/stop`);
14
- if (!stopRes.ok) {
15
- console.error(`Failed to stop: ${await stopRes.text()}`);
16
- process.exit(1);
17
- }
18
-
19
- console.log(`Starting ${project.name}...`);
20
- const startRes = await apiPost(`/api/projects/${project.id}/start`);
21
- if (!startRes.ok) {
22
- console.error(`Failed to start: ${await startRes.text()}`);
23
- process.exit(1);
24
- }
25
-
26
- console.log(`${project.name} restarted.`);
27
- }