@brutalsystems/muster 0.1.0 → 0.2.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/README.md CHANGED
@@ -31,6 +31,7 @@ node dist/muster.js run codex --prompt 'Review the authentication flow'
31
31
  node dist/muster.js run claude --prompt 'Summarize the project' --host tmux
32
32
  node dist/muster.js run codex --kind task --prompt 'Explain the test layout'
33
33
  node dist/muster.js list
34
+ node dist/muster.js list --format human
34
35
  node dist/muster.js list --kind task
35
36
  node dist/muster.js output RUN_ID
36
37
  node dist/muster.js stop THREAD_OR_SESSION_OR_RUN_ID
@@ -42,6 +43,21 @@ emit JSON; `output` prints the captured task output. `stop` also accepts an
42
43
  unambiguous peer name or canonical address, refusing ambiguity with candidates.
43
44
  Use the durable ID to stop a session whose runtime has renamed it.
44
45
 
46
+ For readable terminal output, add `--format human` to `run`, `list`, or `stop`:
47
+
48
+ ```sh
49
+ muster run codex --prompt 'Review this project' --format human
50
+ muster list --format human
51
+ muster stop THREAD_OR_SESSION_OR_RUN_ID --format human
52
+ ```
53
+
54
+ Human output keeps full IDs and shows directory, host, state, and relevant
55
+ attach/output/stop commands with actual IDs. pty sessions are labeled as not
56
+ watchable or attachable. `--format json` is explicit JSON; omitting the flag
57
+ still returns JSON. Put Muster options before any `-- RUNTIME_OPTIONS`.
58
+ `output` always prints captured task text and does not accept `--format`.
59
+ MCP tools retain their existing JSON responses.
60
+
45
61
  `run` accepts an optional `--` followed by runtime arguments. The normal allowlist
46
62
  is `--model` / `-m`, plus Claude's `--effort`. Unknown options, bundled short
47
63
  options, config injection and permission overrides are refused. Prompt strings
package/dist/format.js ADDED
@@ -0,0 +1,49 @@
1
+ function clean(value) {
2
+ return String(value ?? "-").replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, (c) => c === "\n"
3
+ ? "\\n"
4
+ : c === "\r"
5
+ ? "\\r"
6
+ : c === "\t"
7
+ ? "\\t"
8
+ : "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
9
+ }
10
+ function argument(value) {
11
+ return /^[a-zA-Z0-9_.:-]+$/.test(value)
12
+ ? value
13
+ : "'" + value.replaceAll("'", "'\\''") + "'";
14
+ }
15
+ function recordHuman(record) {
16
+ const id = record.id ?? record.thread_id ?? record.session_id;
17
+ if (record.stopped === true)
18
+ return `Stopped ${clean(id)}.`;
19
+ const lines = [
20
+ `${clean(record.runtime)} · ${clean(record.kind)} · ${clean(record.state)}`,
21
+ ` ID: ${clean(id)}`,
22
+ ];
23
+ if (record.canonical_id)
24
+ lines.push(` Address: ${clean(record.canonical_id)}`);
25
+ lines.push(` Directory: ${clean(record.cwd)}`);
26
+ if (record.host)
27
+ lines.push(` Host: ${clean(record.host)}${record.host === "pty" ? " (not watchable or attachable)" : ""}`);
28
+ if (record.exit_code !== undefined)
29
+ lines.push(` Exit code: ${clean(record.exit_code)}`);
30
+ if (record.signal)
31
+ lines.push(` Signal: ${clean(record.signal)}`);
32
+ if (record.error)
33
+ lines.push(` Error: ${clean(record.error)}`);
34
+ const live = ["starting", "running", "idle", "busy"].includes(record.state ?? "");
35
+ if (live && record.attach_hint)
36
+ lines.push(` Attach: ${clean(record.attach_hint)}`);
37
+ if (record.kind === "task" && id)
38
+ lines.push(` Output: muster output ${argument(clean(id))}`);
39
+ if (live && id)
40
+ lines.push(` Stop: muster stop ${argument(clean(id))}`);
41
+ return lines.join("\n");
42
+ }
43
+ export function formatHuman(value) {
44
+ if (Array.isArray(value))
45
+ return value.length
46
+ ? value.map(recordHuman).join("\n\n") + "\n"
47
+ : "No Muster runs.\n";
48
+ return recordHuman(value) + "\n";
49
+ }
package/dist/muster.js CHANGED
@@ -7,17 +7,21 @@ import { z } from "zod";
7
7
  import { Muster } from "./run.js";
8
8
  import { runSchema } from "./guard.js";
9
9
  import { delay } from "./identity/processes.js";
10
+ import { formatHuman } from "./format.js";
10
11
  const listSchema = z
11
12
  .object({ kind: z.enum(["session", "task"]).optional() })
12
13
  .strict();
13
14
  const idSchema = z.object({ id: z.string().min(1) }).strict();
14
15
  const help = `Usage:
15
16
  muster run <codex|claude> --prompt TEXT [--cwd DIR] [--kind session|task] [--host ID] [-- RUNTIME_OPTIONS]
16
- muster list [--kind session|task]
17
- muster stop <id>
17
+ muster list [--kind session|task] [--format json|human]
18
+ muster stop <id> [--format json|human]
18
19
  muster output <id>
19
20
  muster mcp
20
21
 
22
+ run also accepts --format json|human before -- RUNTIME_OPTIONS.
23
+ run, list and stop default to JSON. output prints raw task text.
24
+
21
25
  pty launches print their record and keep this process running. Ctrl-C stops the
22
26
  owned pty session. tmux launches survive this process exiting.
23
27
  MCP: install deliberately in the one authorized session, never at user scope.
@@ -47,7 +51,7 @@ async function main() {
47
51
  });
48
52
  });
49
53
  if (mcp) {
50
- const server = new Server({ name: "muster", version: "0.1.0" }, { capabilities: { tools: {} } });
54
+ const server = new Server({ name: "muster", version: "0.2.0" }, { capabilities: { tools: {} } });
51
55
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
52
56
  tools: [
53
57
  {
@@ -140,7 +144,7 @@ async function main() {
140
144
  }
141
145
  try {
142
146
  const command = argv.shift();
143
- const { values, positionals } = parseArgs({
147
+ const { values: parsed, positionals } = parseArgs({
144
148
  args: argv,
145
149
  allowPositionals: true,
146
150
  strict: true,
@@ -149,13 +153,20 @@ async function main() {
149
153
  cwd: { type: "string" },
150
154
  kind: { type: "string" },
151
155
  host: { type: "string" },
156
+ format: { type: "string" },
152
157
  },
153
158
  });
159
+ const { format = "json", ...values } = parsed;
160
+ if (format !== "json" && format !== "human")
161
+ throw new Error("format must be json or human");
162
+ if (command === "output" && parsed.format !== undefined)
163
+ throw new Error("output prints raw task text; --format applies to run, list and stop");
164
+ const print = (value) => process.stdout.write(format === "human" ? formatHuman(value) : JSON.stringify(value) + "\n");
154
165
  if (command === "run") {
155
166
  const runtime = positionals[0];
156
167
  const args = positionals.slice(1);
157
168
  const record = await muster.run({ runtime, ...values, args });
158
- process.stdout.write(JSON.stringify(record) + "\n");
169
+ print(record);
159
170
  if (record.kind === "session" && record.host === "pty") {
160
171
  process.stderr.write("[muster] pty: not watchable or attachable; this process owns the session. Ctrl-C stops it.\n");
161
172
  while (await muster.hasOwnedPty())
@@ -165,14 +176,14 @@ async function main() {
165
176
  else if (command === "list") {
166
177
  if (positionals.length)
167
178
  throw new Error("list takes no positional arguments");
168
- process.stdout.write(JSON.stringify(await muster.list(listSchema.parse(values).kind)) + "\n");
179
+ print(await muster.list(listSchema.parse(values).kind));
169
180
  }
170
181
  else if (command === "stop" || command === "output") {
171
182
  if (positionals.length !== 1 || Object.keys(values).length)
172
183
  throw new Error(`${command} requires exactly one id`);
173
184
  const { id } = idSchema.parse({ id: positionals[0] });
174
185
  if (command === "stop")
175
- process.stdout.write(JSON.stringify(await muster.stop(id)) + "\n");
186
+ print(await muster.stop(id));
176
187
  else
177
188
  process.stdout.write(await muster.output(id));
178
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brutalsystems/muster",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Launch instructed, reachable local agent sessions",
5
5
  "type": "module",
6
6
  "engines": {