@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 +16 -0
- package/dist/format.js +49 -0
- package/dist/muster.js +18 -7
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
+
print(await muster.stop(id));
|
|
176
187
|
else
|
|
177
188
|
process.stdout.write(await muster.output(id));
|
|
178
189
|
}
|