maesttro 0.4.3 → 0.4.4
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 +3 -0
- package/dist/maesttro.js +120 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,8 +61,11 @@ maesttro opencode # same, with opencode
|
|
|
61
61
|
maesttro --strict [client] # same, with no GitHub token in the runner (no `gh`)
|
|
62
62
|
maesttro bash # plain shell inside the runner, for debugging
|
|
63
63
|
maesttro sessions # list this repo's kept sessions
|
|
64
|
+
maesttro sessions remove <id> # drop one (preserves branches; --force discards unbranched commits)
|
|
65
|
+
maesttro sessions remove --all # drop all of this repo's sessions
|
|
64
66
|
maesttro resume <id> # continue one, in the mode it started with
|
|
65
67
|
maesttro push [branch] # push a branch a session created (shows what goes out, asks first)
|
|
68
|
+
maesttro push [branch] --force # same, as a force push
|
|
66
69
|
|
|
67
70
|
maesttro update # bump the package + rebuild image with refreshed configs
|
|
68
71
|
```
|
package/dist/maesttro.js
CHANGED
|
@@ -1265,8 +1265,14 @@ Usage:
|
|
|
1265
1265
|
Same, in strict mode: no GitHub token reaches the runner, so \`gh\` is off
|
|
1266
1266
|
maesttro resume <id> Continue a kept session in its clone, in the mode it started with
|
|
1267
1267
|
(\`--strict\` or \`--no-strict\` after the id changes it)
|
|
1268
|
-
maesttro sessions List this repo's sessions
|
|
1269
|
-
maesttro
|
|
1268
|
+
maesttro sessions List this repo's sessions
|
|
1269
|
+
maesttro sessions remove <id>
|
|
1270
|
+
Drop one. Preserves its branches; asks before discarding commits that
|
|
1271
|
+
aren't on a branch (--force skips the ask)
|
|
1272
|
+
maesttro sessions remove --all
|
|
1273
|
+
Drop all of this repo's sessions
|
|
1274
|
+
maesttro push [branch] [--force]
|
|
1275
|
+
Push a branch a session created, after showing what goes out
|
|
1270
1276
|
maesttro list Show available clients
|
|
1271
1277
|
maesttro setup One-time: build image, create volume, log in to GitHub
|
|
1272
1278
|
maesttro login Log in to GitHub (device flow) so sessions get repo access
|
|
@@ -2277,6 +2283,32 @@ async function launch({
|
|
|
2277
2283
|
|
|
2278
2284
|
// src/push.ts
|
|
2279
2285
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2286
|
+
var CONFIRM_FLAGS = new Set(["--yes", "-y"]);
|
|
2287
|
+
var FORWARDED_GIT_FLAGS = new Set(["--force"]);
|
|
2288
|
+
function parsePushArgs(args) {
|
|
2289
|
+
const positionals = args.filter((arg) => !arg.startsWith("-"));
|
|
2290
|
+
if (positionals.length > 1) {
|
|
2291
|
+
return {
|
|
2292
|
+
ok: false,
|
|
2293
|
+
reason: `Expected at most one branch, got ${positionals.join(", ")}.`
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
const flags = args.filter((arg) => arg.startsWith("-"));
|
|
2297
|
+
const unsupported = flags.filter((flag) => !CONFIRM_FLAGS.has(flag) && !FORWARDED_GIT_FLAGS.has(flag));
|
|
2298
|
+
if (unsupported.length > 0) {
|
|
2299
|
+
return {
|
|
2300
|
+
ok: false,
|
|
2301
|
+
reason: `maesttro push doesn't accept ${unsupported.join(", ")}; it takes --yes and --force.`
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
const [requested] = positionals;
|
|
2305
|
+
return {
|
|
2306
|
+
ok: true,
|
|
2307
|
+
...requested === undefined ? {} : { requested },
|
|
2308
|
+
yes: flags.some((flag) => CONFIRM_FLAGS.has(flag)),
|
|
2309
|
+
gitFlags: flags.filter((flag) => FORWARDED_GIT_FLAGS.has(flag))
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2280
2312
|
function pushCandidates(sessions) {
|
|
2281
2313
|
const candidates = new Map;
|
|
2282
2314
|
for (const session of sessions) {
|
|
@@ -2328,7 +2360,7 @@ async function choose(candidates) {
|
|
|
2328
2360
|
const answer = await ask("Branch to push: ");
|
|
2329
2361
|
return candidates[Number(answer) - 1];
|
|
2330
2362
|
}
|
|
2331
|
-
function describe(run, root, branch, base, tip) {
|
|
2363
|
+
function describe(run, root, branch, base, tip, gitFlags) {
|
|
2332
2364
|
const commits = git(run, root, [
|
|
2333
2365
|
"log",
|
|
2334
2366
|
"--format= %h %s (%an / %cn)",
|
|
@@ -2336,7 +2368,7 @@ function describe(run, root, branch, base, tip) {
|
|
|
2336
2368
|
]).out;
|
|
2337
2369
|
const stat = git(run, root, ["diff", "--stat", `${base}...${tip}`]).out;
|
|
2338
2370
|
return [
|
|
2339
|
-
`Pushing ${branch} to origin (compared with ${base}):`,
|
|
2371
|
+
`Pushing ${branch} to origin${gitFlags.length ? ` with ${gitFlags.join(" ")}` : ""} (compared with ${base}):`,
|
|
2340
2372
|
stripControl(commits) || " (no new commits)",
|
|
2341
2373
|
stat ? `
|
|
2342
2374
|
${stripControl(stat)}` : ""
|
|
@@ -2345,8 +2377,13 @@ ${stripControl(stat)}` : ""
|
|
|
2345
2377
|
}
|
|
2346
2378
|
async function push({ args }) {
|
|
2347
2379
|
const run = runCommand;
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2380
|
+
const parsed = parsePushArgs(args);
|
|
2381
|
+
if (!parsed.ok) {
|
|
2382
|
+
console.error(parsed.reason);
|
|
2383
|
+
process.exitCode = 1;
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
const { requested, yes, gitFlags } = parsed;
|
|
2350
2387
|
let root;
|
|
2351
2388
|
try {
|
|
2352
2389
|
root = repoRoot(run, process.cwd());
|
|
@@ -2373,7 +2410,7 @@ async function push({ args }) {
|
|
|
2373
2410
|
return;
|
|
2374
2411
|
}
|
|
2375
2412
|
git(run, root, ["update-ref", `refs/maesttro/${sessionId}/${branch}`, tip]);
|
|
2376
|
-
console.log(describe(run, root, branch, base, tip));
|
|
2413
|
+
console.log(describe(run, root, branch, base, tip, gitFlags));
|
|
2377
2414
|
if (!yes) {
|
|
2378
2415
|
if (!process.stdin.isTTY) {
|
|
2379
2416
|
console.error("Not a terminal; re-run with --yes to push without confirming.");
|
|
@@ -2387,7 +2424,7 @@ Push? [y/N] `);
|
|
|
2387
2424
|
return;
|
|
2388
2425
|
}
|
|
2389
2426
|
}
|
|
2390
|
-
const pushed = spawnSync3("git", ["-C", root, "push", "origin", `${tip}:refs/heads/${branch}`], { stdio: "inherit" });
|
|
2427
|
+
const pushed = spawnSync3("git", ["-C", root, "push", ...gitFlags, "origin", `${tip}:refs/heads/${branch}`], { stdio: "inherit" });
|
|
2391
2428
|
if (pushed.status === 0) {
|
|
2392
2429
|
console.log(`Pushed ${branch}. Base for comparison was ${baseSha.slice(0, 8)}.`);
|
|
2393
2430
|
}
|
|
@@ -2396,32 +2433,88 @@ Push? [y/N] `);
|
|
|
2396
2433
|
|
|
2397
2434
|
// src/sessions-command.ts
|
|
2398
2435
|
import { existsSync as existsSync5, rmSync as rmSync7 } from "node:fs";
|
|
2399
|
-
function
|
|
2436
|
+
function parseRemoveArgs(args) {
|
|
2437
|
+
let all = false;
|
|
2438
|
+
let force = false;
|
|
2439
|
+
let id;
|
|
2440
|
+
for (const arg of args) {
|
|
2441
|
+
if (arg === "--all")
|
|
2442
|
+
all = true;
|
|
2443
|
+
else if (arg === "--force")
|
|
2444
|
+
force = true;
|
|
2445
|
+
else if (id === undefined)
|
|
2446
|
+
id = arg;
|
|
2447
|
+
}
|
|
2448
|
+
return { all, force, id };
|
|
2449
|
+
}
|
|
2450
|
+
async function removeSession(run, session, force, ask, interactive) {
|
|
2451
|
+
if (existsSync5(session.worktree)) {
|
|
2452
|
+
const preserved = preserveBranches(run, session);
|
|
2453
|
+
const head = cloneHead(session.worktree);
|
|
2454
|
+
const unbranched = preserved.length === 0 && head?.detached !== undefined && head.detached !== session.baseSha;
|
|
2455
|
+
if (unbranched && !force) {
|
|
2456
|
+
let proceed;
|
|
2457
|
+
if (interactive) {
|
|
2458
|
+
const answer = await ask(`Session ${session.id} has commits on a detached HEAD not on any branch. Removing it discards them. Remove anyway? [y/N] `);
|
|
2459
|
+
proceed = /^(y(es)?)?$/i.test(answer);
|
|
2460
|
+
} else {
|
|
2461
|
+
proceed = false;
|
|
2462
|
+
console.error(`Session ${session.id} has commits on a detached HEAD not on any branch. Resume and create a branch, or re-run with --force.`);
|
|
2463
|
+
}
|
|
2464
|
+
if (!proceed) {
|
|
2465
|
+
console.log(`Kept session ${session.id}.`);
|
|
2466
|
+
return "kept";
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
rmSync7(session.worktree, { recursive: true, force: true });
|
|
2470
|
+
const kept = preserved.length > 0 ? ` Its branches are kept as refs/maesttro/${session.id}/*.` : "";
|
|
2471
|
+
console.log(`Removed session ${session.id}.${kept}`);
|
|
2472
|
+
} else {
|
|
2473
|
+
console.log(`Removed session ${session.id}.`);
|
|
2474
|
+
}
|
|
2475
|
+
deleteSession(session.id);
|
|
2476
|
+
return "removed";
|
|
2477
|
+
}
|
|
2478
|
+
async function sessionsCommand({
|
|
2479
|
+
args,
|
|
2480
|
+
ask: ask2 = ask,
|
|
2481
|
+
interactive = process.stdin.isTTY === true
|
|
2482
|
+
}) {
|
|
2400
2483
|
const run = runCommand;
|
|
2401
2484
|
const root = repoRoot(run, process.cwd());
|
|
2402
|
-
const [action,
|
|
2485
|
+
const [action, ...rest] = args;
|
|
2403
2486
|
if (action === "remove") {
|
|
2404
|
-
const
|
|
2405
|
-
if (
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
if (existsSync5(session.worktree)) {
|
|
2411
|
-
const preserved = preserveBranches(run, session);
|
|
2412
|
-
const head = cloneHead(session.worktree);
|
|
2413
|
-
if (preserved.length === 0 && head?.detached !== undefined && head.detached !== session.baseSha) {
|
|
2414
|
-
console.error(`${session.id} has commits on a detached HEAD not on any branch; resume and create a branch first.`);
|
|
2487
|
+
const { all, force, id } = parseRemoveArgs(rest);
|
|
2488
|
+
if (all) {
|
|
2489
|
+
if (id !== undefined) {
|
|
2490
|
+
console.error("`maesttro sessions remove` takes either `--all` or a session id, not both.");
|
|
2415
2491
|
process.exitCode = 1;
|
|
2416
2492
|
return;
|
|
2417
2493
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2494
|
+
const sessions = sessionsForRepo(root);
|
|
2495
|
+
if (sessions.length === 0) {
|
|
2496
|
+
console.log("No maesttro sessions in this repo.");
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
for (const session of sessions) {
|
|
2500
|
+
await removeSession(run, session, force, ask2, interactive);
|
|
2501
|
+
}
|
|
2502
|
+
return;
|
|
2423
2503
|
}
|
|
2424
|
-
|
|
2504
|
+
if (id === undefined) {
|
|
2505
|
+
console.error("Usage: maesttro sessions remove <id> | maesttro sessions remove --all");
|
|
2506
|
+
process.exitCode = 1;
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
const session = readSession(id);
|
|
2510
|
+
if (!session || session.repoRoot !== root) {
|
|
2511
|
+
console.error(`No session ${id} in this repo.`);
|
|
2512
|
+
process.exitCode = 1;
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
const result = await removeSession(run, session, force, ask2, interactive);
|
|
2516
|
+
if (result === "kept")
|
|
2517
|
+
process.exitCode = 1;
|
|
2425
2518
|
return;
|
|
2426
2519
|
}
|
|
2427
2520
|
const sessions = sessionsForRepo(root);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maesttro",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Isolated Docker runner for local coding agents (opencode, claude, ...) under a dedicated bot GitHub identity, with curated configs baked in per client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|