terminalhire 0.26.1 → 0.27.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/dist/bin/jpi-claim.js +207 -9
- package/dist/bin/jpi-dispatch.js +208 -9
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -8796,6 +8796,9 @@ function looksLikeShortRef(arg) {
|
|
|
8796
8796
|
function isVerblessShortRefClaim(verb, positional) {
|
|
8797
8797
|
return looksLikeShortRef(verb) && positional.length === 0;
|
|
8798
8798
|
}
|
|
8799
|
+
function isStrayArgShortRefClaim(verb, positional) {
|
|
8800
|
+
return looksLikeShortRef(verb) && positional.length > 0;
|
|
8801
|
+
}
|
|
8799
8802
|
function findClaimableByShortRef(ref) {
|
|
8800
8803
|
try {
|
|
8801
8804
|
return findByShortRefInPool(readClaimablePool(), ref);
|
|
@@ -9159,10 +9162,19 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
9159
9162
|
console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
|
|
9160
9163
|
console.log(" \u2022 clone + static analysis + patch only; NO test/build execution without explicit approval");
|
|
9161
9164
|
console.log(" \u2022 no access to ~/.terminalhire (the executor never needs your profile)");
|
|
9162
|
-
console.log("\n Next:");
|
|
9163
|
-
console.log("
|
|
9164
|
-
console.log("
|
|
9165
|
-
console.log("
|
|
9165
|
+
console.log("\n Next \u2014 start work (forks + clones into an isolated worktree; your terminal stays put):");
|
|
9166
|
+
console.log(" terminalhire claim start " + claim.id);
|
|
9167
|
+
console.log(" Then publish when it is done (the only step that pushes + opens the PR):");
|
|
9168
|
+
console.log(" terminalhire claim submit " + claim.id);
|
|
9169
|
+
if (flags.start) {
|
|
9170
|
+
await cmdStart(claim.id, { start: true });
|
|
9171
|
+
} else if (process.stdin.isTTY && !flags["no-start"]) {
|
|
9172
|
+
const go = await confirm(`
|
|
9173
|
+
Fork ${claim.repoFullName} and start now? (y/N) `);
|
|
9174
|
+
if (go) await cmdStart(claim.id, { start: true });
|
|
9175
|
+
else console.log(`
|
|
9176
|
+
Saved. Start anytime: terminalhire claim start ${claim.id}`);
|
|
9177
|
+
}
|
|
9166
9178
|
}
|
|
9167
9179
|
async function cmdPreview(arg, { json } = {}) {
|
|
9168
9180
|
if (!arg) {
|
|
@@ -9359,6 +9371,179 @@ async function cmdAttach(id, worktree, branch) {
|
|
|
9359
9371
|
claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
9360
9372
|
console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
|
|
9361
9373
|
}
|
|
9374
|
+
function workDirFor(repoFullName, issueNumber) {
|
|
9375
|
+
const [owner, repo] = String(repoFullName).split("/");
|
|
9376
|
+
const suffix = issueNumber ? `-${issueNumber}` : "";
|
|
9377
|
+
return join7(homedir6(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
|
|
9378
|
+
}
|
|
9379
|
+
function startBranchFor(repoFullName, issueNumber) {
|
|
9380
|
+
const repo = String(repoFullName).split("/")[1] || "claim";
|
|
9381
|
+
return `th/${repo}-${issueNumber || "work"}`;
|
|
9382
|
+
}
|
|
9383
|
+
function explicitForkConsent(flags = {}) {
|
|
9384
|
+
return Boolean(flags.start) || Boolean(flags.fork);
|
|
9385
|
+
}
|
|
9386
|
+
async function ensureForkExists(repoFullName, ghUser) {
|
|
9387
|
+
const repoShort = repoFullName.split("/")[1];
|
|
9388
|
+
const forkFullName = `${ghUser}/${repoShort}`;
|
|
9389
|
+
await sh("gh", ["repo", "fork", repoFullName, "--clone=false"]);
|
|
9390
|
+
let isFork = false;
|
|
9391
|
+
try {
|
|
9392
|
+
isFork = await sh("gh", ["api", `repos/${forkFullName}`, "--jq", ".fork"]) === "true";
|
|
9393
|
+
} catch {
|
|
9394
|
+
isFork = false;
|
|
9395
|
+
}
|
|
9396
|
+
if (!isFork) throw new Error(`fork ${forkFullName} created but could not be verified as a fork`);
|
|
9397
|
+
return forkFullName;
|
|
9398
|
+
}
|
|
9399
|
+
async function cmdStart(id, flags = {}) {
|
|
9400
|
+
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
9401
|
+
if (!id) {
|
|
9402
|
+
const startable = claims.listClaims({ active: true }).filter((c) => c.state === "claimed");
|
|
9403
|
+
if (startable.length === 0) {
|
|
9404
|
+
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
9405
|
+
return;
|
|
9406
|
+
}
|
|
9407
|
+
console.log(`
|
|
9408
|
+
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
9409
|
+
`);
|
|
9410
|
+
for (const c of startable) {
|
|
9411
|
+
console.log(` ${c.title}`);
|
|
9412
|
+
console.log(` terminalhire claim start ${c.id}`);
|
|
9413
|
+
}
|
|
9414
|
+
console.log("\nRun the command under the one you want to start.");
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
const claim = claims.findClaim(id);
|
|
9418
|
+
if (!claim) {
|
|
9419
|
+
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
9420
|
+
process.exit(1);
|
|
9421
|
+
}
|
|
9422
|
+
if (claim.worktreePath) {
|
|
9423
|
+
let stillThere = false;
|
|
9424
|
+
try {
|
|
9425
|
+
stillThere = await sh("git", ["-C", claim.worktreePath, "rev-parse", "--show-toplevel"]) === claim.worktreePath;
|
|
9426
|
+
} catch {
|
|
9427
|
+
stillThere = false;
|
|
9428
|
+
}
|
|
9429
|
+
if (stillThere) {
|
|
9430
|
+
console.log("Already started \u2014 your worktree is ready (your terminal stays put):");
|
|
9431
|
+
console.log(` cd ${claim.worktreePath}`);
|
|
9432
|
+
if (claim.branch) console.log(` branch: ${claim.branch}`);
|
|
9433
|
+
console.log(`
|
|
9434
|
+
When it's done: terminalhire claim submit ${id}`);
|
|
9435
|
+
return;
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
if (flags.here) {
|
|
9439
|
+
await cmdStartHere(claims, claim);
|
|
9440
|
+
return;
|
|
9441
|
+
}
|
|
9442
|
+
let ghUser;
|
|
9443
|
+
try {
|
|
9444
|
+
ghUser = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
9445
|
+
} catch {
|
|
9446
|
+
console.error("terminalhire claim: 'gh' CLI not available or not authenticated. Run 'gh auth login'.");
|
|
9447
|
+
process.exit(1);
|
|
9448
|
+
}
|
|
9449
|
+
let consented = explicitForkConsent(flags);
|
|
9450
|
+
if (!consented && process.stdin.isTTY) {
|
|
9451
|
+
consented = await confirm(`
|
|
9452
|
+
Fork ${claim.repoFullName} to @${ghUser} and clone it to start? (y/N) `);
|
|
9453
|
+
}
|
|
9454
|
+
if (!consented) {
|
|
9455
|
+
console.error(
|
|
9456
|
+
`
|
|
9457
|
+
terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to your GitHub account (a network write).
|
|
9458
|
+
Re-run in a terminal to confirm interactively, or pass --fork to consent non-interactively.`
|
|
9459
|
+
);
|
|
9460
|
+
process.exit(1);
|
|
9461
|
+
}
|
|
9462
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9463
|
+
const destDir = workDirFor(claim.repoFullName, issueNumber);
|
|
9464
|
+
if (existsSync6(destDir)) {
|
|
9465
|
+
console.error(
|
|
9466
|
+
`terminalhire claim: ${destDir} already exists \u2014 refusing to clobber it.
|
|
9467
|
+
Remove it and retry, or attach it: terminalhire claim attach ${id} --worktree ${destDir} --branch <branch>`
|
|
9468
|
+
);
|
|
9469
|
+
process.exit(1);
|
|
9470
|
+
}
|
|
9471
|
+
mkdirSync6(join7(homedir6(), "terminalhire", "work"), { recursive: true });
|
|
9472
|
+
let forkFullName;
|
|
9473
|
+
try {
|
|
9474
|
+
forkFullName = await ensureForkExists(claim.repoFullName, ghUser);
|
|
9475
|
+
} catch (err) {
|
|
9476
|
+
console.error(`terminalhire claim: could not create your fork of ${claim.repoFullName}. ${err.message ?? err}`);
|
|
9477
|
+
process.exit(1);
|
|
9478
|
+
}
|
|
9479
|
+
try {
|
|
9480
|
+
await sh("gh", ["repo", "clone", forkFullName, destDir]);
|
|
9481
|
+
} catch (err) {
|
|
9482
|
+
try {
|
|
9483
|
+
rmSync3(destDir, { recursive: true, force: true });
|
|
9484
|
+
} catch {
|
|
9485
|
+
}
|
|
9486
|
+
console.error(`terminalhire claim: clone of ${forkFullName} failed. ${err.stderr || err.message || err}`);
|
|
9487
|
+
process.exit(1);
|
|
9488
|
+
}
|
|
9489
|
+
try {
|
|
9490
|
+
await sh("git", ["-C", destDir, "remote", "get-url", "upstream"]);
|
|
9491
|
+
} catch {
|
|
9492
|
+
try {
|
|
9493
|
+
await sh("git", ["-C", destDir, "remote", "add", "upstream", `https://github.com/${claim.repoFullName}.git`]);
|
|
9494
|
+
} catch {
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
9498
|
+
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
9499
|
+
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
9500
|
+
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
9501
|
+
console.log(`
|
|
9502
|
+
\u2713 Started: ${claim.title}`);
|
|
9503
|
+
console.log(` fork: ${forkFullName}`);
|
|
9504
|
+
console.log(` branch: ${branch}`);
|
|
9505
|
+
console.log("\n Your isolated worktree is ready \u2014 your current terminal is untouched:");
|
|
9506
|
+
console.log(` cd ${toplevel}`);
|
|
9507
|
+
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
9508
|
+
console.log(` terminalhire claim submit ${id}`);
|
|
9509
|
+
}
|
|
9510
|
+
async function cmdStartHere(claims, claim) {
|
|
9511
|
+
let toplevel;
|
|
9512
|
+
try {
|
|
9513
|
+
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
9514
|
+
} catch {
|
|
9515
|
+
console.error("terminalhire claim: --here must be run inside a git repository.");
|
|
9516
|
+
process.exit(1);
|
|
9517
|
+
}
|
|
9518
|
+
const remotesOut = await sh("git", ["-C", toplevel, "remote", "-v"]).catch(() => "");
|
|
9519
|
+
const repos = new Set(
|
|
9520
|
+
remotesOut.split("\n").map((l) => parseRepoFromRemote(l.split(/\s+/)[1])).filter(Boolean).map((r) => r.toLowerCase())
|
|
9521
|
+
);
|
|
9522
|
+
if (!repos.has(claim.repoFullName.toLowerCase())) {
|
|
9523
|
+
console.error(
|
|
9524
|
+
`terminalhire claim: --here expects a clone of ${claim.repoFullName}, but no remote here points there.
|
|
9525
|
+
Use \`terminalhire claim start ${claim.id}\` (no --here) to fork + clone it into a fresh worktree.`
|
|
9526
|
+
);
|
|
9527
|
+
process.exit(1);
|
|
9528
|
+
}
|
|
9529
|
+
const dirty = await sh("git", ["-C", toplevel, "status", "--porcelain", "--untracked-files=no"]).catch(() => "");
|
|
9530
|
+
if (dirty) {
|
|
9531
|
+
console.error(
|
|
9532
|
+
"terminalhire claim: --here needs a clean working tree (claim work must not mix with your current changes).\n Commit or stash first, or use `terminalhire claim start` for an isolated worktree."
|
|
9533
|
+
);
|
|
9534
|
+
process.exit(1);
|
|
9535
|
+
}
|
|
9536
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9537
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
9538
|
+
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
9539
|
+
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
9540
|
+
console.log(`
|
|
9541
|
+
\u2713 Started here: ${claim.title}`);
|
|
9542
|
+
console.log(` worktree: ${toplevel}`);
|
|
9543
|
+
console.log(` branch: ${branch}`);
|
|
9544
|
+
console.log(`
|
|
9545
|
+
When the work is done: terminalhire claim submit ${claim.id}`);
|
|
9546
|
+
}
|
|
9362
9547
|
async function cmdSubmit(id, flags = {}) {
|
|
9363
9548
|
const worktreeOverride = flags.worktree;
|
|
9364
9549
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -9373,10 +9558,10 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
9373
9558
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
9374
9559
|
process.exit(1);
|
|
9375
9560
|
}
|
|
9376
|
-
if (claim.state !== "ready") {
|
|
9561
|
+
if (claim.state !== "working" && claim.state !== "ready") {
|
|
9377
9562
|
console.error(
|
|
9378
|
-
`terminalhire claim: ${id} is '${claim.state}'
|
|
9379
|
-
terminalhire claim
|
|
9563
|
+
`terminalhire claim: ${id} is '${claim.state}'. Submit runs once work has started ('working', via 'claim start') or the review gate cleared it ('ready'). Start it first:
|
|
9564
|
+
terminalhire claim start ${id}`
|
|
9380
9565
|
);
|
|
9381
9566
|
process.exit(1);
|
|
9382
9567
|
}
|
|
@@ -9954,6 +10139,12 @@ async function run() {
|
|
|
9954
10139
|
await cmdRecord(verb, flags);
|
|
9955
10140
|
return;
|
|
9956
10141
|
}
|
|
10142
|
+
if (isStrayArgShortRefClaim(verb, positional)) {
|
|
10143
|
+
console.error(
|
|
10144
|
+
`terminalhire claim: '${verb}' is a short ref and takes no extra arguments (got: ${positional.join(" ")}). Did you mean \`terminalhire claim ${verb}\`?`
|
|
10145
|
+
);
|
|
10146
|
+
process.exit(1);
|
|
10147
|
+
}
|
|
9957
10148
|
try {
|
|
9958
10149
|
switch (verb) {
|
|
9959
10150
|
case "preview":
|
|
@@ -9962,6 +10153,9 @@ async function run() {
|
|
|
9962
10153
|
case "record":
|
|
9963
10154
|
await cmdRecord(positional[0], flags);
|
|
9964
10155
|
break;
|
|
10156
|
+
case "start":
|
|
10157
|
+
await cmdStart(positional[0], flags);
|
|
10158
|
+
break;
|
|
9965
10159
|
case "list":
|
|
9966
10160
|
await cmdList(active);
|
|
9967
10161
|
break;
|
|
@@ -9981,7 +10175,7 @@ async function run() {
|
|
|
9981
10175
|
await cmdRelease(positional[0]);
|
|
9982
10176
|
break;
|
|
9983
10177
|
default:
|
|
9984
|
-
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | attach | list | status | update | submit | release`);
|
|
10178
|
+
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | release`);
|
|
9985
10179
|
process.exit(1);
|
|
9986
10180
|
}
|
|
9987
10181
|
} catch (err) {
|
|
@@ -9997,11 +10191,13 @@ export {
|
|
|
9997
10191
|
cmdRecord,
|
|
9998
10192
|
countOpenPRsReferencingIssue,
|
|
9999
10193
|
diffContention,
|
|
10194
|
+
explicitForkConsent,
|
|
10000
10195
|
findClaimableByShortRef,
|
|
10001
10196
|
findClaimableInCache,
|
|
10002
10197
|
fmtAge,
|
|
10003
10198
|
fmtContestedWarning,
|
|
10004
10199
|
isContested,
|
|
10200
|
+
isStrayArgShortRefClaim,
|
|
10005
10201
|
isVerblessShortRefClaim,
|
|
10006
10202
|
listOpenPRsReferencingIssue,
|
|
10007
10203
|
matchReferencingPrs,
|
|
@@ -10011,7 +10207,9 @@ export {
|
|
|
10011
10207
|
resolveSubmitWorktree,
|
|
10012
10208
|
revokeFailureAction,
|
|
10013
10209
|
run,
|
|
10014
|
-
selectPushRemote
|
|
10210
|
+
selectPushRemote,
|
|
10211
|
+
startBranchFor,
|
|
10212
|
+
workDirFor
|
|
10015
10213
|
};
|
|
10016
10214
|
/*! Bundled license information:
|
|
10017
10215
|
|
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -11039,11 +11039,13 @@ __export(jpi_claim_exports, {
|
|
|
11039
11039
|
cmdRecord: () => cmdRecord,
|
|
11040
11040
|
countOpenPRsReferencingIssue: () => countOpenPRsReferencingIssue,
|
|
11041
11041
|
diffContention: () => diffContention,
|
|
11042
|
+
explicitForkConsent: () => explicitForkConsent,
|
|
11042
11043
|
findClaimableByShortRef: () => findClaimableByShortRef,
|
|
11043
11044
|
findClaimableInCache: () => findClaimableInCache,
|
|
11044
11045
|
fmtAge: () => fmtAge,
|
|
11045
11046
|
fmtContestedWarning: () => fmtContestedWarning,
|
|
11046
11047
|
isContested: () => isContested,
|
|
11048
|
+
isStrayArgShortRefClaim: () => isStrayArgShortRefClaim,
|
|
11047
11049
|
isVerblessShortRefClaim: () => isVerblessShortRefClaim,
|
|
11048
11050
|
listOpenPRsReferencingIssue: () => listOpenPRsReferencingIssue,
|
|
11049
11051
|
matchReferencingPrs: () => matchReferencingPrs,
|
|
@@ -11053,7 +11055,9 @@ __export(jpi_claim_exports, {
|
|
|
11053
11055
|
resolveSubmitWorktree: () => resolveSubmitWorktree,
|
|
11054
11056
|
revokeFailureAction: () => revokeFailureAction,
|
|
11055
11057
|
run: () => run7,
|
|
11056
|
-
selectPushRemote: () => selectPushRemote
|
|
11058
|
+
selectPushRemote: () => selectPushRemote,
|
|
11059
|
+
startBranchFor: () => startBranchFor,
|
|
11060
|
+
workDirFor: () => workDirFor
|
|
11057
11061
|
});
|
|
11058
11062
|
import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, existsSync as existsSync10, rmSync as rmSync4 } from "fs";
|
|
11059
11063
|
import { join as join17 } from "path";
|
|
@@ -11198,6 +11202,9 @@ function looksLikeShortRef(arg) {
|
|
|
11198
11202
|
function isVerblessShortRefClaim(verb, positional) {
|
|
11199
11203
|
return looksLikeShortRef(verb) && positional.length === 0;
|
|
11200
11204
|
}
|
|
11205
|
+
function isStrayArgShortRefClaim(verb, positional) {
|
|
11206
|
+
return looksLikeShortRef(verb) && positional.length > 0;
|
|
11207
|
+
}
|
|
11201
11208
|
function findClaimableByShortRef(ref) {
|
|
11202
11209
|
try {
|
|
11203
11210
|
return findByShortRefInPool(readClaimablePool(), ref);
|
|
@@ -11561,10 +11568,19 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
11561
11568
|
console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
|
|
11562
11569
|
console.log(" \u2022 clone + static analysis + patch only; NO test/build execution without explicit approval");
|
|
11563
11570
|
console.log(" \u2022 no access to ~/.terminalhire (the executor never needs your profile)");
|
|
11564
|
-
console.log("\n Next:");
|
|
11565
|
-
console.log("
|
|
11566
|
-
console.log("
|
|
11567
|
-
console.log("
|
|
11571
|
+
console.log("\n Next \u2014 start work (forks + clones into an isolated worktree; your terminal stays put):");
|
|
11572
|
+
console.log(" terminalhire claim start " + claim.id);
|
|
11573
|
+
console.log(" Then publish when it is done (the only step that pushes + opens the PR):");
|
|
11574
|
+
console.log(" terminalhire claim submit " + claim.id);
|
|
11575
|
+
if (flags.start) {
|
|
11576
|
+
await cmdStart(claim.id, { start: true });
|
|
11577
|
+
} else if (process.stdin.isTTY && !flags["no-start"]) {
|
|
11578
|
+
const go = await confirm(`
|
|
11579
|
+
Fork ${claim.repoFullName} and start now? (y/N) `);
|
|
11580
|
+
if (go) await cmdStart(claim.id, { start: true });
|
|
11581
|
+
else console.log(`
|
|
11582
|
+
Saved. Start anytime: terminalhire claim start ${claim.id}`);
|
|
11583
|
+
}
|
|
11568
11584
|
}
|
|
11569
11585
|
async function cmdPreview(arg, { json } = {}) {
|
|
11570
11586
|
if (!arg) {
|
|
@@ -11761,6 +11777,179 @@ async function cmdAttach(id, worktree, branch) {
|
|
|
11761
11777
|
claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
11762
11778
|
console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
|
|
11763
11779
|
}
|
|
11780
|
+
function workDirFor(repoFullName, issueNumber) {
|
|
11781
|
+
const [owner, repo] = String(repoFullName).split("/");
|
|
11782
|
+
const suffix = issueNumber ? `-${issueNumber}` : "";
|
|
11783
|
+
return join17(homedir15(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
|
|
11784
|
+
}
|
|
11785
|
+
function startBranchFor(repoFullName, issueNumber) {
|
|
11786
|
+
const repo = String(repoFullName).split("/")[1] || "claim";
|
|
11787
|
+
return `th/${repo}-${issueNumber || "work"}`;
|
|
11788
|
+
}
|
|
11789
|
+
function explicitForkConsent(flags = {}) {
|
|
11790
|
+
return Boolean(flags.start) || Boolean(flags.fork);
|
|
11791
|
+
}
|
|
11792
|
+
async function ensureForkExists(repoFullName, ghUser) {
|
|
11793
|
+
const repoShort = repoFullName.split("/")[1];
|
|
11794
|
+
const forkFullName = `${ghUser}/${repoShort}`;
|
|
11795
|
+
await sh("gh", ["repo", "fork", repoFullName, "--clone=false"]);
|
|
11796
|
+
let isFork = false;
|
|
11797
|
+
try {
|
|
11798
|
+
isFork = await sh("gh", ["api", `repos/${forkFullName}`, "--jq", ".fork"]) === "true";
|
|
11799
|
+
} catch {
|
|
11800
|
+
isFork = false;
|
|
11801
|
+
}
|
|
11802
|
+
if (!isFork) throw new Error(`fork ${forkFullName} created but could not be verified as a fork`);
|
|
11803
|
+
return forkFullName;
|
|
11804
|
+
}
|
|
11805
|
+
async function cmdStart(id, flags = {}) {
|
|
11806
|
+
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
11807
|
+
if (!id) {
|
|
11808
|
+
const startable = claims.listClaims({ active: true }).filter((c) => c.state === "claimed");
|
|
11809
|
+
if (startable.length === 0) {
|
|
11810
|
+
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
11811
|
+
return;
|
|
11812
|
+
}
|
|
11813
|
+
console.log(`
|
|
11814
|
+
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
11815
|
+
`);
|
|
11816
|
+
for (const c of startable) {
|
|
11817
|
+
console.log(` ${c.title}`);
|
|
11818
|
+
console.log(` terminalhire claim start ${c.id}`);
|
|
11819
|
+
}
|
|
11820
|
+
console.log("\nRun the command under the one you want to start.");
|
|
11821
|
+
return;
|
|
11822
|
+
}
|
|
11823
|
+
const claim = claims.findClaim(id);
|
|
11824
|
+
if (!claim) {
|
|
11825
|
+
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
11826
|
+
process.exit(1);
|
|
11827
|
+
}
|
|
11828
|
+
if (claim.worktreePath) {
|
|
11829
|
+
let stillThere = false;
|
|
11830
|
+
try {
|
|
11831
|
+
stillThere = await sh("git", ["-C", claim.worktreePath, "rev-parse", "--show-toplevel"]) === claim.worktreePath;
|
|
11832
|
+
} catch {
|
|
11833
|
+
stillThere = false;
|
|
11834
|
+
}
|
|
11835
|
+
if (stillThere) {
|
|
11836
|
+
console.log("Already started \u2014 your worktree is ready (your terminal stays put):");
|
|
11837
|
+
console.log(` cd ${claim.worktreePath}`);
|
|
11838
|
+
if (claim.branch) console.log(` branch: ${claim.branch}`);
|
|
11839
|
+
console.log(`
|
|
11840
|
+
When it's done: terminalhire claim submit ${id}`);
|
|
11841
|
+
return;
|
|
11842
|
+
}
|
|
11843
|
+
}
|
|
11844
|
+
if (flags.here) {
|
|
11845
|
+
await cmdStartHere(claims, claim);
|
|
11846
|
+
return;
|
|
11847
|
+
}
|
|
11848
|
+
let ghUser;
|
|
11849
|
+
try {
|
|
11850
|
+
ghUser = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
11851
|
+
} catch {
|
|
11852
|
+
console.error("terminalhire claim: 'gh' CLI not available or not authenticated. Run 'gh auth login'.");
|
|
11853
|
+
process.exit(1);
|
|
11854
|
+
}
|
|
11855
|
+
let consented = explicitForkConsent(flags);
|
|
11856
|
+
if (!consented && process.stdin.isTTY) {
|
|
11857
|
+
consented = await confirm(`
|
|
11858
|
+
Fork ${claim.repoFullName} to @${ghUser} and clone it to start? (y/N) `);
|
|
11859
|
+
}
|
|
11860
|
+
if (!consented) {
|
|
11861
|
+
console.error(
|
|
11862
|
+
`
|
|
11863
|
+
terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to your GitHub account (a network write).
|
|
11864
|
+
Re-run in a terminal to confirm interactively, or pass --fork to consent non-interactively.`
|
|
11865
|
+
);
|
|
11866
|
+
process.exit(1);
|
|
11867
|
+
}
|
|
11868
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
11869
|
+
const destDir = workDirFor(claim.repoFullName, issueNumber);
|
|
11870
|
+
if (existsSync10(destDir)) {
|
|
11871
|
+
console.error(
|
|
11872
|
+
`terminalhire claim: ${destDir} already exists \u2014 refusing to clobber it.
|
|
11873
|
+
Remove it and retry, or attach it: terminalhire claim attach ${id} --worktree ${destDir} --branch <branch>`
|
|
11874
|
+
);
|
|
11875
|
+
process.exit(1);
|
|
11876
|
+
}
|
|
11877
|
+
mkdirSync11(join17(homedir15(), "terminalhire", "work"), { recursive: true });
|
|
11878
|
+
let forkFullName;
|
|
11879
|
+
try {
|
|
11880
|
+
forkFullName = await ensureForkExists(claim.repoFullName, ghUser);
|
|
11881
|
+
} catch (err) {
|
|
11882
|
+
console.error(`terminalhire claim: could not create your fork of ${claim.repoFullName}. ${err.message ?? err}`);
|
|
11883
|
+
process.exit(1);
|
|
11884
|
+
}
|
|
11885
|
+
try {
|
|
11886
|
+
await sh("gh", ["repo", "clone", forkFullName, destDir]);
|
|
11887
|
+
} catch (err) {
|
|
11888
|
+
try {
|
|
11889
|
+
rmSync4(destDir, { recursive: true, force: true });
|
|
11890
|
+
} catch {
|
|
11891
|
+
}
|
|
11892
|
+
console.error(`terminalhire claim: clone of ${forkFullName} failed. ${err.stderr || err.message || err}`);
|
|
11893
|
+
process.exit(1);
|
|
11894
|
+
}
|
|
11895
|
+
try {
|
|
11896
|
+
await sh("git", ["-C", destDir, "remote", "get-url", "upstream"]);
|
|
11897
|
+
} catch {
|
|
11898
|
+
try {
|
|
11899
|
+
await sh("git", ["-C", destDir, "remote", "add", "upstream", `https://github.com/${claim.repoFullName}.git`]);
|
|
11900
|
+
} catch {
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
11904
|
+
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
11905
|
+
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
11906
|
+
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
11907
|
+
console.log(`
|
|
11908
|
+
\u2713 Started: ${claim.title}`);
|
|
11909
|
+
console.log(` fork: ${forkFullName}`);
|
|
11910
|
+
console.log(` branch: ${branch}`);
|
|
11911
|
+
console.log("\n Your isolated worktree is ready \u2014 your current terminal is untouched:");
|
|
11912
|
+
console.log(` cd ${toplevel}`);
|
|
11913
|
+
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
11914
|
+
console.log(` terminalhire claim submit ${id}`);
|
|
11915
|
+
}
|
|
11916
|
+
async function cmdStartHere(claims, claim) {
|
|
11917
|
+
let toplevel;
|
|
11918
|
+
try {
|
|
11919
|
+
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
11920
|
+
} catch {
|
|
11921
|
+
console.error("terminalhire claim: --here must be run inside a git repository.");
|
|
11922
|
+
process.exit(1);
|
|
11923
|
+
}
|
|
11924
|
+
const remotesOut = await sh("git", ["-C", toplevel, "remote", "-v"]).catch(() => "");
|
|
11925
|
+
const repos = new Set(
|
|
11926
|
+
remotesOut.split("\n").map((l) => parseRepoFromRemote(l.split(/\s+/)[1])).filter(Boolean).map((r) => r.toLowerCase())
|
|
11927
|
+
);
|
|
11928
|
+
if (!repos.has(claim.repoFullName.toLowerCase())) {
|
|
11929
|
+
console.error(
|
|
11930
|
+
`terminalhire claim: --here expects a clone of ${claim.repoFullName}, but no remote here points there.
|
|
11931
|
+
Use \`terminalhire claim start ${claim.id}\` (no --here) to fork + clone it into a fresh worktree.`
|
|
11932
|
+
);
|
|
11933
|
+
process.exit(1);
|
|
11934
|
+
}
|
|
11935
|
+
const dirty = await sh("git", ["-C", toplevel, "status", "--porcelain", "--untracked-files=no"]).catch(() => "");
|
|
11936
|
+
if (dirty) {
|
|
11937
|
+
console.error(
|
|
11938
|
+
"terminalhire claim: --here needs a clean working tree (claim work must not mix with your current changes).\n Commit or stash first, or use `terminalhire claim start` for an isolated worktree."
|
|
11939
|
+
);
|
|
11940
|
+
process.exit(1);
|
|
11941
|
+
}
|
|
11942
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
11943
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
11944
|
+
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
11945
|
+
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
11946
|
+
console.log(`
|
|
11947
|
+
\u2713 Started here: ${claim.title}`);
|
|
11948
|
+
console.log(` worktree: ${toplevel}`);
|
|
11949
|
+
console.log(` branch: ${branch}`);
|
|
11950
|
+
console.log(`
|
|
11951
|
+
When the work is done: terminalhire claim submit ${claim.id}`);
|
|
11952
|
+
}
|
|
11764
11953
|
async function cmdSubmit(id, flags = {}) {
|
|
11765
11954
|
const worktreeOverride = flags.worktree;
|
|
11766
11955
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -11775,10 +11964,10 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
11775
11964
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
11776
11965
|
process.exit(1);
|
|
11777
11966
|
}
|
|
11778
|
-
if (claim.state !== "ready") {
|
|
11967
|
+
if (claim.state !== "working" && claim.state !== "ready") {
|
|
11779
11968
|
console.error(
|
|
11780
|
-
`terminalhire claim: ${id} is '${claim.state}'
|
|
11781
|
-
terminalhire claim
|
|
11969
|
+
`terminalhire claim: ${id} is '${claim.state}'. Submit runs once work has started ('working', via 'claim start') or the review gate cleared it ('ready'). Start it first:
|
|
11970
|
+
terminalhire claim start ${id}`
|
|
11782
11971
|
);
|
|
11783
11972
|
process.exit(1);
|
|
11784
11973
|
}
|
|
@@ -12356,6 +12545,12 @@ async function run7() {
|
|
|
12356
12545
|
await cmdRecord(verb, flags);
|
|
12357
12546
|
return;
|
|
12358
12547
|
}
|
|
12548
|
+
if (isStrayArgShortRefClaim(verb, positional)) {
|
|
12549
|
+
console.error(
|
|
12550
|
+
`terminalhire claim: '${verb}' is a short ref and takes no extra arguments (got: ${positional.join(" ")}). Did you mean \`terminalhire claim ${verb}\`?`
|
|
12551
|
+
);
|
|
12552
|
+
process.exit(1);
|
|
12553
|
+
}
|
|
12359
12554
|
try {
|
|
12360
12555
|
switch (verb) {
|
|
12361
12556
|
case "preview":
|
|
@@ -12364,6 +12559,9 @@ async function run7() {
|
|
|
12364
12559
|
case "record":
|
|
12365
12560
|
await cmdRecord(positional[0], flags);
|
|
12366
12561
|
break;
|
|
12562
|
+
case "start":
|
|
12563
|
+
await cmdStart(positional[0], flags);
|
|
12564
|
+
break;
|
|
12367
12565
|
case "list":
|
|
12368
12566
|
await cmdList(active);
|
|
12369
12567
|
break;
|
|
@@ -12383,7 +12581,7 @@ async function run7() {
|
|
|
12383
12581
|
await cmdRelease(positional[0]);
|
|
12384
12582
|
break;
|
|
12385
12583
|
default:
|
|
12386
|
-
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | attach | list | status | update | submit | release`);
|
|
12584
|
+
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | release`);
|
|
12387
12585
|
process.exit(1);
|
|
12388
12586
|
}
|
|
12389
12587
|
} catch (err) {
|
|
@@ -42859,6 +43057,7 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
42859
43057
|
console.log(" terminalhire bounties --priced Only bounties with a known $ amount");
|
|
42860
43058
|
console.log(" terminalhire contribute Open issues where a merged PR counts toward your r\xE9sum\xE9");
|
|
42861
43059
|
console.log(" terminalhire claim record <id|issueUrl> Claim a bounty locally + print the executor brief");
|
|
43060
|
+
console.log(" terminalhire claim start [<id>] Fork + clone + branch into an isolated worktree (never cd's you)");
|
|
42862
43061
|
console.log(" terminalhire claim list [--active] List your claims + accepted-PR rate");
|
|
42863
43062
|
console.log(" terminalhire claim status [<id>] Poll source PR merge state (updates the metric)");
|
|
42864
43063
|
console.log(" terminalhire claim --push Opt-in: show your claims on your dashboard (typed-yes + browser confirm)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|