mulmoterminal 0.1.2 → 0.1.3
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 +8 -2
- package/bin/mulmoterminal.js +33 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,13 @@ npm install -g mulmoterminal
|
|
|
24
24
|
mulmoterminal
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Options: `--
|
|
27
|
+
Options: `--cwd <dir>` (working directory — relative paths allowed; defaults to the
|
|
28
|
+
directory you run the command from), `--port <n>` (default 3456), `--no-open`,
|
|
29
|
+
`--version`, `--help`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx mulmoterminal --cwd ./my-project # work in a specific directory
|
|
33
|
+
```
|
|
28
34
|
|
|
29
35
|
The published package ships the server (run via `tsx`) plus the pre-built web UI;
|
|
30
36
|
`npx mulmoterminal` checks for the `claude` CLI, picks a free port, starts the
|
|
@@ -118,7 +124,7 @@ the server runs without one.
|
|
|
118
124
|
| ------------ | -------------- | ----------- |
|
|
119
125
|
| `PORT` | `3456` | HTTP/WebSocket port. |
|
|
120
126
|
| `CLAUDE_BIN` | `claude` | The Claude Code binary to spawn. |
|
|
121
|
-
| `CLAUDE_CWD` |
|
|
127
|
+
| `CLAUDE_CWD` | current dir | Working directory each `claude` PTY runs in; determines which project's sessions the sidebar lists. Via `npx mulmoterminal` it defaults to the directory you ran the command from (override with `--cwd <dir>`, relative allowed); when the server is run directly it falls back to `~/mulmoclaude`. A value read from `.env` must be an absolute path (`~` is not expanded). |
|
|
122
128
|
|
|
123
129
|
Example `.env` (gitignored):
|
|
124
130
|
|
package/bin/mulmoterminal.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// runs the server via tsx. Mirrors the mulmoclaude launcher.
|
|
7
7
|
|
|
8
8
|
import { execSync, spawn } from "node:child_process";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
9
|
+
import { existsSync, statSync } from "node:fs";
|
|
10
10
|
import { get as httpGet } from "node:http";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { createServer } from "node:net";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -129,6 +129,29 @@ function parsePortArg(args) {
|
|
|
129
129
|
return { requestedPort: parsed, portExplicit: true };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Resolve the workspace directory claude runs in (and whose sessions the sidebar
|
|
133
|
+
// lists). Precedence: --cwd (relative paths allowed) > CLAUDE_CWD env > the
|
|
134
|
+
// directory npx was run from. Always returned absolute. An explicit --cwd that
|
|
135
|
+
// isn't an existing directory is a hard error (catches typos before launch).
|
|
136
|
+
function resolveCwd(args) {
|
|
137
|
+
const idx = args.indexOf("--cwd");
|
|
138
|
+
let flagValue;
|
|
139
|
+
if (idx !== -1) {
|
|
140
|
+
flagValue = args[idx + 1];
|
|
141
|
+
if (flagValue === undefined || flagValue.startsWith("-")) {
|
|
142
|
+
error("--cwd requires a directory path");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const chosen = flagValue ?? process.env.CLAUDE_CWD ?? ".";
|
|
147
|
+
const abs = resolve(process.cwd(), chosen);
|
|
148
|
+
if (idx !== -1 && (!existsSync(abs) || !statSync(abs).isDirectory())) {
|
|
149
|
+
error(`--cwd is not a directory: ${abs}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
return abs;
|
|
153
|
+
}
|
|
154
|
+
|
|
132
155
|
async function choosePort(requested, explicit) {
|
|
133
156
|
if (await isPortFree(requested)) return requested;
|
|
134
157
|
if (explicit) {
|
|
@@ -149,12 +172,12 @@ async function choosePort(requested, explicit) {
|
|
|
149
172
|
// the port was taken at bind time before it became ready — the caller then
|
|
150
173
|
// retries on a fresh port. In every other case (clean shutdown, fatal error,
|
|
151
174
|
// or the server simply running) the process exits with the server's code.
|
|
152
|
-
function runServer(port, noOpen, onChild) {
|
|
153
|
-
return new Promise((
|
|
175
|
+
function runServer(port, noOpen, cwd, onChild) {
|
|
176
|
+
return new Promise((resolveExit) => {
|
|
154
177
|
log(`Starting MulmoTerminal on port ${port}...`);
|
|
155
178
|
const server = spawn(process.execPath, ["--import", "tsx", SERVER_ENTRY], {
|
|
156
179
|
cwd: PKG_DIR,
|
|
157
|
-
env: { ...process.env, NODE_ENV: "production", PORT: String(port) },
|
|
180
|
+
env: { ...process.env, NODE_ENV: "production", PORT: String(port), CLAUDE_CWD: cwd },
|
|
158
181
|
stdio: "inherit",
|
|
159
182
|
});
|
|
160
183
|
onChild(server);
|
|
@@ -178,7 +201,7 @@ function runServer(port, noOpen, onChild) {
|
|
|
178
201
|
// served — always retriable, regardless of what a probe to the port saw
|
|
179
202
|
// (another process could have answered it). Other exits are terminal.
|
|
180
203
|
if (code === PORT_IN_USE_EXIT_CODE) {
|
|
181
|
-
|
|
204
|
+
resolveExit();
|
|
182
205
|
return;
|
|
183
206
|
}
|
|
184
207
|
process.exit(code ?? 1);
|
|
@@ -194,6 +217,7 @@ async function main() {
|
|
|
194
217
|
Usage: npx mulmoterminal [options]
|
|
195
218
|
|
|
196
219
|
Options:
|
|
220
|
+
--cwd <dir> Working directory claude runs in (default: current directory; relative paths allowed)
|
|
197
221
|
--port <number> Server port (default: ${DEFAULT_PORT}; a free port is chosen if it's busy)
|
|
198
222
|
--no-open Don't open the browser automatically
|
|
199
223
|
--version Show version
|
|
@@ -220,6 +244,8 @@ Options:
|
|
|
220
244
|
|
|
221
245
|
const { requestedPort, portExplicit } = parsePortArg(args);
|
|
222
246
|
const noOpen = args.includes("--no-open");
|
|
247
|
+
const cwd = resolveCwd(args);
|
|
248
|
+
log(`Workspace: ${cwd}`);
|
|
223
249
|
|
|
224
250
|
// Registered once; always targets the live child across bind-retries.
|
|
225
251
|
let child = null;
|
|
@@ -235,7 +261,7 @@ Options:
|
|
|
235
261
|
// second-guessed. `runServer` only returns when the port was raced.
|
|
236
262
|
let port = await choosePort(requestedPort, portExplicit);
|
|
237
263
|
for (let attempt = 0; attempt <= MAX_BIND_RETRIES; attempt++) {
|
|
238
|
-
await runServer(port, noOpen, (c) => {
|
|
264
|
+
await runServer(port, noOpen, cwd, (c) => {
|
|
239
265
|
child = c;
|
|
240
266
|
});
|
|
241
267
|
if (portExplicit) {
|