@moor-sh/cli 0.1.0 → 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 +75 -0
- package/package.json +2 -2
- package/src/commands/mcp.ts +136 -0
- package/src/index.ts +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @moor-sh/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [moor](https://github.com/caiopizzol/moor) - manage your moor server's projects, logs, env vars, and container lifecycle from a terminal. Ships a `moor` binary.
|
|
4
|
+
|
|
5
|
+
Requires [Bun](https://bun.sh) on the machine running the CLI.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
**One-shot** (no install):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bunx @moor-sh/cli status
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Global install** (puts `moor` on PATH):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add -g @moor-sh/cli
|
|
19
|
+
moor status
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Don't use `bunx moor` (without the scope) - `moor` on npm is an unrelated package.
|
|
23
|
+
|
|
24
|
+
## Configure
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export MOOR_URL=https://moor.example.com # or http://127.0.0.1:8080 via SSH tunnel
|
|
28
|
+
export MOOR_API_KEY=your-api-key
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`MOOR_API_KEY` grants admin-equivalent control of the moor host. See the [self-hosting guide](https://github.com/caiopizzol/moor/blob/main/docs/self-hosting.md#api-keys) for how to generate and rotate it.
|
|
32
|
+
|
|
33
|
+
For a remote moor with private admin (the default), open an SSH tunnel from your laptop before running CLI commands:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ssh -L 8080:127.0.0.1:3000 your-server
|
|
37
|
+
export MOOR_URL=http://127.0.0.1:8080
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
moor status # list all projects
|
|
44
|
+
moor logs <project> [-f] [-n 100] # view container logs
|
|
45
|
+
moor rebuild <project> # rebuild from source
|
|
46
|
+
moor restart <project> # stop + start
|
|
47
|
+
moor exec <project> <command> # run a command in the container
|
|
48
|
+
moor env list <project> # list environment variables
|
|
49
|
+
moor env set <project> KEY=VALUE # set environment variables and restart
|
|
50
|
+
moor stats # server resource usage
|
|
51
|
+
moor mcp config --client <name> # generate MCP client config snippet
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## `moor mcp config`
|
|
55
|
+
|
|
56
|
+
Generates a ready-to-paste config snippet for an MCP-compatible AI client. Removes the "open a doc, copy a JSON block, fill in the blanks" step from MCP setup.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
moor mcp config --client claude # or --client claude-code (alias)
|
|
60
|
+
moor mcp config --client codex
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Output is JSON for `claude` / `claude-code` and TOML for `codex`. Prints to stdout - redirect or paste into `~/.claude.json` or `~/.codex/config.toml`. Optional flags: `--url <url>` (default `http://127.0.0.1:8080`), `--api-key <key>` (else read from `MOOR_API_KEY` env, then cwd `.env`, then a placeholder).
|
|
64
|
+
|
|
65
|
+
See [`@moor-sh/mcp`](https://www.npmjs.com/package/@moor-sh/mcp) for the MCP server itself.
|
|
66
|
+
|
|
67
|
+
## Links
|
|
68
|
+
|
|
69
|
+
- [moor repo](https://github.com/caiopizzol/moor) - main project, install instructions
|
|
70
|
+
- [Self-hosting guide](https://github.com/caiopizzol/moor/blob/main/docs/self-hosting.md) - first boot, API keys, admin domain, port model
|
|
71
|
+
- [`@moor-sh/mcp`](https://www.npmjs.com/package/@moor-sh/mcp) - MCP server for AI agent integration
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moor-sh/cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.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": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/caiopizzol/moor.git",
|
|
9
9
|
"directory": "packages/cli"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/caiopizzol/moor
|
|
11
|
+
"homepage": "https://github.com/caiopizzol/moor/tree/main/packages/cli",
|
|
12
12
|
"bugs": "https://github.com/caiopizzol/moor/issues",
|
|
13
13
|
"keywords": [
|
|
14
14
|
"moor",
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { envCommand } from "./commands/env";
|
|
5
5
|
import { execCommand } from "./commands/exec";
|
|
6
6
|
import { logsCommand } from "./commands/logs";
|
|
7
|
+
import { mcpCommand } from "./commands/mcp";
|
|
7
8
|
import { rebuildCommand } from "./commands/rebuild";
|
|
8
9
|
import { restartCommand } from "./commands/restart";
|
|
9
10
|
import { statsCommand } from "./commands/stats";
|
|
@@ -33,6 +34,7 @@ Commands:
|
|
|
33
34
|
env list <project> List environment variables
|
|
34
35
|
env set <project> K=V [K=V ...] Set environment variables
|
|
35
36
|
stats Show server resource usage
|
|
37
|
+
mcp config --client <name> Generate MCP client config snippet
|
|
36
38
|
|
|
37
39
|
Environment:
|
|
38
40
|
MOOR_URL Server URL (e.g. https://moor.example.com)
|
|
@@ -64,6 +66,9 @@ switch (command) {
|
|
|
64
66
|
case "stats":
|
|
65
67
|
await statsCommand();
|
|
66
68
|
break;
|
|
69
|
+
case "mcp":
|
|
70
|
+
mcpCommand(args.slice(1));
|
|
71
|
+
break;
|
|
67
72
|
case "--help":
|
|
68
73
|
case "-h":
|
|
69
74
|
case "help":
|