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 CHANGED
@@ -24,7 +24,13 @@ npm install -g mulmoterminal
24
24
  mulmoterminal
25
25
  ```
26
26
 
27
- Options: `--port <n>` (default 3456), `--no-open`, `--version`, `--help`.
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` | `$HOME` | Working directory each `claude` PTY runs in. Determines which project's sessions the sidebar lists. **Must be an absolute path** `~` is not expanded for values read from `.env`. |
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
 
@@ -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((resolve) => {
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
- resolve();
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmoterminal",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MulmoTerminal — browser terminal + GUI panel for Claude Code. One command to start.",
5
5
  "type": "module",
6
6
  "license": "MIT",