terminalhire 0.42.12 → 0.42.13

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.
@@ -0,0 +1,138 @@
1
+ // bin/index-revalidate.js
2
+ var PULSE_TIMEOUT_MS = 2e3;
3
+ var INDEX_TIMEOUT_WARM_MS = 5e3;
4
+ var INDEX_TIMEOUT_COLD_MS = 1e4;
5
+ function usableMember(member) {
6
+ return typeof member === "object" && member !== null && !Array.isArray(member) && Array.isArray(member.tags);
7
+ }
8
+ function usableIndex(index) {
9
+ if (!index || typeof index !== "object" || Array.isArray(index)) return false;
10
+ if (!Array.isArray(index.jobs) || !index.jobs.every(usableMember)) return false;
11
+ if (index.contribute !== void 0) {
12
+ if (!Array.isArray(index.contribute) || !index.contribute.every(usableMember)) return false;
13
+ }
14
+ return true;
15
+ }
16
+ function deadlineFetch(fetchImpl, url, { headers, timeoutMs }) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(new Error("deadline exceeded")), timeoutMs);
19
+ timer.unref?.();
20
+ const promise = Promise.resolve(fetchImpl(url, { headers, signal: controller.signal })).finally(
21
+ () => clearTimeout(timer)
22
+ );
23
+ return { promise, abort: () => controller.abort(new Error("superseded")) };
24
+ }
25
+ function cacheFallbackNotice(source, cachedAt, now = Date.now()) {
26
+ if (source !== "cache-fallback") return null;
27
+ const base = "terminalhire: could not refresh the list \u2014 showing cached results";
28
+ if (typeof cachedAt !== "number" || !Number.isFinite(cachedAt)) return `${base}.`;
29
+ const minutes = Math.floor(Math.max(0, now - cachedAt) / 6e4);
30
+ if (minutes < 1) return `${base} from moments ago.`;
31
+ if (minutes < 60) return `${base} from ${minutes}m ago.`;
32
+ const hours = Math.floor(minutes / 60);
33
+ return `${base} from ${hours}h ago.`;
34
+ }
35
+ async function revalidateIndex({
36
+ apiUrl,
37
+ fetchImpl,
38
+ readEntry,
39
+ writeEntry,
40
+ pulseTimeoutMs = PULSE_TIMEOUT_MS,
41
+ indexTimeoutWarmMs = INDEX_TIMEOUT_WARM_MS,
42
+ indexTimeoutColdMs = INDEX_TIMEOUT_COLD_MS
43
+ }) {
44
+ let entry = {};
45
+ try {
46
+ entry = readEntry() ?? {};
47
+ } catch {
48
+ }
49
+ const cached = usableIndex(entry.index) ? entry.index : null;
50
+ const cachedAt = cached && typeof entry.ts === "number" ? entry.ts : null;
51
+ const indexTimeoutMs = cached ? indexTimeoutWarmMs : indexTimeoutColdMs;
52
+ const headers = { Accept: "application/json" };
53
+ if (cached && typeof entry.indexETag === "string" && entry.indexETag !== "") {
54
+ headers["If-None-Match"] = entry.indexETag;
55
+ }
56
+ const pulseCall = deadlineFetch(fetchImpl, `${apiUrl}/api/pulse`, {
57
+ headers: { Accept: "application/json" },
58
+ timeoutMs: pulseTimeoutMs
59
+ });
60
+ const speculative = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
61
+ headers,
62
+ timeoutMs: indexTimeoutMs
63
+ });
64
+ const speculativeSettled = speculative.promise.then(
65
+ (res2) => ({ ok: true, res: res2 }),
66
+ (err) => ({ ok: false, err })
67
+ );
68
+ let latestPostedAt = null;
69
+ try {
70
+ const res2 = await pulseCall.promise;
71
+ if (res2.ok) {
72
+ const pulse = await res2.json();
73
+ latestPostedAt = pulse?.latestPostedAt ?? null;
74
+ }
75
+ } catch {
76
+ }
77
+ const bust = latestPostedAt !== null && entry.latestPostedAt !== latestPostedAt;
78
+ let outcome;
79
+ let reachedOrigin = false;
80
+ if (bust) {
81
+ speculative.abort();
82
+ const bustCall = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
83
+ // `no-cache` forces revalidation against the origin; the validator rides along
84
+ // so a genuinely unchanged body still costs a 304 rather than a full download.
85
+ headers: { ...headers, "Cache-Control": "no-cache" },
86
+ timeoutMs: indexTimeoutMs
87
+ });
88
+ try {
89
+ outcome = { ok: true, res: await bustCall.promise };
90
+ reachedOrigin = true;
91
+ } catch (err) {
92
+ const spec = await speculativeSettled;
93
+ outcome = spec.ok ? spec : { ok: false, err };
94
+ }
95
+ } else {
96
+ outcome = await speculativeSettled;
97
+ }
98
+ const fellBack = (reason) => cached ? { index: cached, source: "cache-fallback", reason, cachedAt } : { index: null, source: "none", reason, cachedAt };
99
+ if (!outcome.ok) {
100
+ const err = outcome.err;
101
+ return fellBack(err instanceof Error ? err.message : String(err));
102
+ }
103
+ const res = outcome.res;
104
+ try {
105
+ if (res.status === 304 && cached) {
106
+ if (reachedOrigin && latestPostedAt && latestPostedAt !== entry.latestPostedAt) {
107
+ try {
108
+ writeEntry({ latestPostedAt });
109
+ } catch {
110
+ }
111
+ }
112
+ return { index: cached, source: "cache-304", reason: null, cachedAt };
113
+ }
114
+ if (!res.ok) {
115
+ return fellBack(`/api/index returned ${res.status}`);
116
+ }
117
+ const body = await res.json();
118
+ if (!usableIndex(body)) {
119
+ return fellBack("/api/index returned a body that will not render");
120
+ }
121
+ const patch = { index: body, indexETag: res.headers.get("etag") ?? "" };
122
+ if (reachedOrigin && latestPostedAt) patch.latestPostedAt = latestPostedAt;
123
+ try {
124
+ writeEntry(patch);
125
+ } catch {
126
+ }
127
+ return { index: body, source: "fresh", reason: null, cachedAt };
128
+ } catch (err) {
129
+ return fellBack(err instanceof Error ? err.message : String(err));
130
+ }
131
+ }
132
+ export {
133
+ INDEX_TIMEOUT_COLD_MS,
134
+ INDEX_TIMEOUT_WARM_MS,
135
+ PULSE_TIMEOUT_MS,
136
+ cacheFallbackNotice,
137
+ revalidateIndex
138
+ };
@@ -10550,52 +10550,6 @@ var init_state_dir = __esm({
10550
10550
  }
10551
10551
  });
10552
10552
 
10553
- // bin/cache-store.js
10554
- var cache_store_exports = {};
10555
- __export(cache_store_exports, {
10556
- readCacheEntry: () => readCacheEntry,
10557
- updateIndexCache: () => updateIndexCache,
10558
- writeIndexCache: () => writeIndexCache
10559
- });
10560
- import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
10561
- import { join as join2 } from "path";
10562
- import { homedir } from "os";
10563
- function readCacheEntry() {
10564
- try {
10565
- return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
10566
- } catch {
10567
- return null;
10568
- }
10569
- }
10570
- function updateIndexCache(patch) {
10571
- ensureStateDir(TERMINALHIRE_DIR);
10572
- const existing = readCacheEntry() ?? {};
10573
- const entry = {
10574
- ...existing,
10575
- ...patch,
10576
- schemaVersion: SCHEMA_VERSION2,
10577
- ts: Date.now()
10578
- };
10579
- const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
10580
- writeFileSync(tmp, JSON.stringify(entry), "utf8");
10581
- renameSync(tmp, INDEX_CACHE_FILE);
10582
- return entry;
10583
- }
10584
- function writeIndexCache(index) {
10585
- return updateIndexCache({ index, indexETag: "" });
10586
- }
10587
- var TERMINALHIRE_DIR, INDEX_CACHE_FILE, SCHEMA_VERSION2, tmpCounter;
10588
- var init_cache_store = __esm({
10589
- "bin/cache-store.js"() {
10590
- "use strict";
10591
- init_state_dir();
10592
- TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
10593
- INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
10594
- SCHEMA_VERSION2 = 1;
10595
- tmpCounter = 0;
10596
- }
10597
- });
10598
-
10599
10553
  // bin/founder-pin.js
10600
10554
  function isPinnedFounderBounty(j) {
10601
10555
  return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
@@ -12021,8 +11975,168 @@ function formatCents(cents) {
12021
11975
  return cents % 100 === 0 ? `$${cents / 100}` : `$${(cents / 100).toFixed(2)}`;
12022
11976
  }
12023
11977
 
12024
- // bin/jpi-bounties.js
12025
- init_cache_store();
11978
+ // bin/cache-store.js
11979
+ init_state_dir();
11980
+ import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
11981
+ import { join as join2 } from "path";
11982
+ import { homedir } from "os";
11983
+ var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join2(homedir(), ".terminalhire");
11984
+ var INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR, "index-cache.json");
11985
+ var SCHEMA_VERSION2 = 1;
11986
+ var tmpCounter = 0;
11987
+ function readCacheEntry() {
11988
+ try {
11989
+ return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
11990
+ } catch {
11991
+ return null;
11992
+ }
11993
+ }
11994
+ function updateIndexCache(patch) {
11995
+ ensureStateDir(TERMINALHIRE_DIR);
11996
+ const existing = readCacheEntry() ?? {};
11997
+ const entry = {
11998
+ ...existing,
11999
+ ...patch,
12000
+ schemaVersion: SCHEMA_VERSION2,
12001
+ ts: Date.now()
12002
+ };
12003
+ const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
12004
+ writeFileSync(tmp, JSON.stringify(entry), "utf8");
12005
+ renameSync(tmp, INDEX_CACHE_FILE);
12006
+ return entry;
12007
+ }
12008
+
12009
+ // bin/index-revalidate.js
12010
+ var PULSE_TIMEOUT_MS = 2e3;
12011
+ var INDEX_TIMEOUT_WARM_MS = 5e3;
12012
+ var INDEX_TIMEOUT_COLD_MS = 1e4;
12013
+ function usableMember(member) {
12014
+ return typeof member === "object" && member !== null && !Array.isArray(member) && Array.isArray(member.tags);
12015
+ }
12016
+ function usableIndex(index) {
12017
+ if (!index || typeof index !== "object" || Array.isArray(index)) return false;
12018
+ if (!Array.isArray(index.jobs) || !index.jobs.every(usableMember)) return false;
12019
+ if (index.contribute !== void 0) {
12020
+ if (!Array.isArray(index.contribute) || !index.contribute.every(usableMember)) return false;
12021
+ }
12022
+ return true;
12023
+ }
12024
+ function deadlineFetch(fetchImpl, url, { headers, timeoutMs }) {
12025
+ const controller = new AbortController();
12026
+ const timer = setTimeout(() => controller.abort(new Error("deadline exceeded")), timeoutMs);
12027
+ timer.unref?.();
12028
+ const promise = Promise.resolve(fetchImpl(url, { headers, signal: controller.signal })).finally(
12029
+ () => clearTimeout(timer)
12030
+ );
12031
+ return { promise, abort: () => controller.abort(new Error("superseded")) };
12032
+ }
12033
+ function cacheFallbackNotice(source, cachedAt, now = Date.now()) {
12034
+ if (source !== "cache-fallback") return null;
12035
+ const base = "terminalhire: could not refresh the list \u2014 showing cached results";
12036
+ if (typeof cachedAt !== "number" || !Number.isFinite(cachedAt)) return `${base}.`;
12037
+ const minutes = Math.floor(Math.max(0, now - cachedAt) / 6e4);
12038
+ if (minutes < 1) return `${base} from moments ago.`;
12039
+ if (minutes < 60) return `${base} from ${minutes}m ago.`;
12040
+ const hours = Math.floor(minutes / 60);
12041
+ return `${base} from ${hours}h ago.`;
12042
+ }
12043
+ async function revalidateIndex({
12044
+ apiUrl,
12045
+ fetchImpl,
12046
+ readEntry,
12047
+ writeEntry,
12048
+ pulseTimeoutMs = PULSE_TIMEOUT_MS,
12049
+ indexTimeoutWarmMs = INDEX_TIMEOUT_WARM_MS,
12050
+ indexTimeoutColdMs = INDEX_TIMEOUT_COLD_MS
12051
+ }) {
12052
+ let entry = {};
12053
+ try {
12054
+ entry = readEntry() ?? {};
12055
+ } catch {
12056
+ }
12057
+ const cached = usableIndex(entry.index) ? entry.index : null;
12058
+ const cachedAt = cached && typeof entry.ts === "number" ? entry.ts : null;
12059
+ const indexTimeoutMs = cached ? indexTimeoutWarmMs : indexTimeoutColdMs;
12060
+ const headers = { Accept: "application/json" };
12061
+ if (cached && typeof entry.indexETag === "string" && entry.indexETag !== "") {
12062
+ headers["If-None-Match"] = entry.indexETag;
12063
+ }
12064
+ const pulseCall = deadlineFetch(fetchImpl, `${apiUrl}/api/pulse`, {
12065
+ headers: { Accept: "application/json" },
12066
+ timeoutMs: pulseTimeoutMs
12067
+ });
12068
+ const speculative = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
12069
+ headers,
12070
+ timeoutMs: indexTimeoutMs
12071
+ });
12072
+ const speculativeSettled = speculative.promise.then(
12073
+ (res2) => ({ ok: true, res: res2 }),
12074
+ (err) => ({ ok: false, err })
12075
+ );
12076
+ let latestPostedAt = null;
12077
+ try {
12078
+ const res2 = await pulseCall.promise;
12079
+ if (res2.ok) {
12080
+ const pulse = await res2.json();
12081
+ latestPostedAt = pulse?.latestPostedAt ?? null;
12082
+ }
12083
+ } catch {
12084
+ }
12085
+ const bust = latestPostedAt !== null && entry.latestPostedAt !== latestPostedAt;
12086
+ let outcome;
12087
+ let reachedOrigin = false;
12088
+ if (bust) {
12089
+ speculative.abort();
12090
+ const bustCall = deadlineFetch(fetchImpl, `${apiUrl}/api/index`, {
12091
+ // `no-cache` forces revalidation against the origin; the validator rides along
12092
+ // so a genuinely unchanged body still costs a 304 rather than a full download.
12093
+ headers: { ...headers, "Cache-Control": "no-cache" },
12094
+ timeoutMs: indexTimeoutMs
12095
+ });
12096
+ try {
12097
+ outcome = { ok: true, res: await bustCall.promise };
12098
+ reachedOrigin = true;
12099
+ } catch (err) {
12100
+ const spec = await speculativeSettled;
12101
+ outcome = spec.ok ? spec : { ok: false, err };
12102
+ }
12103
+ } else {
12104
+ outcome = await speculativeSettled;
12105
+ }
12106
+ const fellBack = (reason) => cached ? { index: cached, source: "cache-fallback", reason, cachedAt } : { index: null, source: "none", reason, cachedAt };
12107
+ if (!outcome.ok) {
12108
+ const err = outcome.err;
12109
+ return fellBack(err instanceof Error ? err.message : String(err));
12110
+ }
12111
+ const res = outcome.res;
12112
+ try {
12113
+ if (res.status === 304 && cached) {
12114
+ if (reachedOrigin && latestPostedAt && latestPostedAt !== entry.latestPostedAt) {
12115
+ try {
12116
+ writeEntry({ latestPostedAt });
12117
+ } catch {
12118
+ }
12119
+ }
12120
+ return { index: cached, source: "cache-304", reason: null, cachedAt };
12121
+ }
12122
+ if (!res.ok) {
12123
+ return fellBack(`/api/index returned ${res.status}`);
12124
+ }
12125
+ const body = await res.json();
12126
+ if (!usableIndex(body)) {
12127
+ return fellBack("/api/index returned a body that will not render");
12128
+ }
12129
+ const patch = { index: body, indexETag: res.headers.get("etag") ?? "" };
12130
+ if (reachedOrigin && latestPostedAt) patch.latestPostedAt = latestPostedAt;
12131
+ try {
12132
+ writeEntry(patch);
12133
+ } catch {
12134
+ }
12135
+ return { index: body, source: "fresh", reason: null, cachedAt };
12136
+ } catch (err) {
12137
+ return fellBack(err instanceof Error ? err.message : String(err));
12138
+ }
12139
+ }
12026
12140
 
12027
12141
  // bin/sanitize.js
12028
12142
  var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
@@ -12178,14 +12292,20 @@ function readIndexCache() {
12178
12292
  return null;
12179
12293
  }
12180
12294
  }
12181
- async function fetchIndex() {
12182
- const cached = readIndexCache();
12183
- if (cached) return cached;
12184
- const res = await fetch(`${API_URL2}/api/index`, { signal: AbortSignal.timeout(1e4) });
12185
- if (!res.ok) throw new Error(`/api/index returned ${res.status}`);
12186
- const index = await res.json();
12187
- writeIndexCache(index);
12188
- return index;
12295
+ async function fetchIndex(quiet = false) {
12296
+ const { index, reason, source, cachedAt } = await revalidateIndex({
12297
+ apiUrl: API_URL2,
12298
+ fetchImpl: (url, init) => fetch(url, init),
12299
+ readEntry: readCacheEntry,
12300
+ writeEntry: updateIndexCache
12301
+ });
12302
+ if (!quiet) {
12303
+ const notice = cacheFallbackNotice(source, cachedAt);
12304
+ if (notice) process.stderr.write(`${notice}
12305
+ `);
12306
+ }
12307
+ if (index) return index;
12308
+ throw new Error(reason ?? "/api/index returned no usable index");
12189
12309
  }
12190
12310
  function prompt2(question) {
12191
12311
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -12326,7 +12446,7 @@ function classifyEmptyStatus({ preFilterCount, postFilterCount, priced }) {
12326
12446
  }
12327
12447
  async function getBounties({ quiet = false, offline = false, priced = PRICED_ONLY } = {}) {
12328
12448
  if (!quiet) console.log(`Fetching bounty index from ${API_URL2}/api/index...`);
12329
- const index = offline ? readIndexCache() : await fetchIndex();
12449
+ const index = offline ? readIndexCache() : await fetchIndex(quiet);
12330
12450
  if (offline && !index) return { status: "no-cache" };
12331
12451
  let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
12332
12452
  if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
@@ -12378,10 +12498,9 @@ async function run() {
12378
12498
  try {
12379
12499
  if (args.length === 0) {
12380
12500
  try {
12381
- const { readCacheEntry: readCacheEntry2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
12382
12501
  const { getSurfaceLeadOverride: getSurfaceLeadOverride2 } = await Promise.resolve().then(() => (init_config(), config_exports));
12383
12502
  const { founderRows: founderRows2, resolveSurfaceLead: resolveSurfaceLead2 } = await Promise.resolve().then(() => (init_founder_surface(), founder_surface_exports));
12384
- const surface = (readCacheEntry2() ?? {}).founderSurface;
12503
+ const surface = (readCacheEntry() ?? {}).founderSurface;
12385
12504
  if (resolveSurfaceLead2(getSurfaceLeadOverride2(), surface) === "founder") {
12386
12505
  const rows = founderRows2(surface, "bounties");
12387
12506
  console.log("\n\u26A1 Your TerminalHire postings\n");
@@ -12449,12 +12568,11 @@ async function run() {
12449
12568
  }
12450
12569
  try {
12451
12570
  const { acknowledgeFounderPaid: acknowledgeFounderPaid2 } = await Promise.resolve().then(() => (init_founder_paid_badge(), founder_paid_badge_exports));
12452
- const { readCacheEntry: readCacheEntry2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
12453
12571
  updateIndexCache({
12454
12572
  founderPaid: acknowledgeFounderPaid2({
12455
12573
  shown,
12456
12574
  open: bounties,
12457
- previous: (readCacheEntry2() ?? {}).founderPaid
12575
+ previous: (readCacheEntry() ?? {}).founderPaid
12458
12576
  })
12459
12577
  });
12460
12578
  } catch {
@@ -33910,6 +33910,7 @@ import {
33910
33910
  mkdirSync as mkdirSync8,
33911
33911
  existsSync as existsSync13,
33912
33912
  lstatSync as lstatSync3,
33913
+ realpathSync as realpathSync2,
33913
33914
  rmSync as rmSync9,
33914
33915
  readdirSync as readdirSync3
33915
33916
  } from "fs";
@@ -34503,6 +34504,47 @@ function resolveSubmitWorktree({ override, cwdToplevel, recorded, recordedIsGit
34503
34504
  terminalhire claim attach <id> --worktree ${recorded} --branch <branch>`
34504
34505
  };
34505
34506
  }
34507
+ function inferSubmitClaim({
34508
+ claims,
34509
+ activeIds,
34510
+ cwdToplevel,
34511
+ cwd,
34512
+ branch,
34513
+ canonicalize: canonicalize2 = (p) => p
34514
+ }) {
34515
+ const here = [cwdToplevel, cwd].filter((d) => typeof d === "string" && d !== "").map((d) => canonicalize2(d));
34516
+ const isHere = (dir) => typeof dir === "string" && dir !== "" && here.includes(canonicalize2(dir));
34517
+ const tiers = [
34518
+ (c) => isHere(c.worktreePath),
34519
+ (c) => isHere(sliceWorkDirFor(c.id)),
34520
+ (c) => typeof branch === "string" && branch !== "" && c.branch === branch
34521
+ ];
34522
+ for (const matches of tiers) {
34523
+ const hits = (Array.isArray(claims) ? claims : []).filter(matches);
34524
+ if (hits.length === 0) continue;
34525
+ if (hits.length === 1) return { claim: hits[0] };
34526
+ const live = hits.filter((c) => activeIds.has(c.id));
34527
+ if (live.length === 1) return { claim: live[0] };
34528
+ return { candidates: live.length > 0 ? live : hits };
34529
+ }
34530
+ return { candidates: [] };
34531
+ }
34532
+ function canonicalPath(p) {
34533
+ if (typeof p !== "string" || p === "") return p;
34534
+ const resolved = pathResolve(p);
34535
+ try {
34536
+ return realpathSync2.native(resolved);
34537
+ } catch {
34538
+ return resolved;
34539
+ }
34540
+ }
34541
+ async function gitToplevelOf(dir) {
34542
+ try {
34543
+ return await sh("git", ["-C", dir, "rev-parse", "--show-toplevel"]);
34544
+ } catch {
34545
+ return null;
34546
+ }
34547
+ }
34506
34548
  function pickBodySource({ bodyFileFlag, noBody, prBodyExists }) {
34507
34549
  if (bodyFileFlag) return "body-file";
34508
34550
  if (!noBody && prBodyExists) return "pr-body";
@@ -37374,10 +37416,44 @@ async function cmdSubmit(id, flags = {}) {
37374
37416
  const worktreeOverride = flags.worktree;
37375
37417
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
37376
37418
  if (!id) {
37377
- console.error(
37378
- "Usage: terminalhire claim submit <id> [--worktree <path>] [--yes] [--body-file <path>] [--no-body] [--title <t>] [--no-closes]"
37379
- );
37380
- process.exit(1);
37419
+ const dir = worktreeOverride ?? process.cwd();
37420
+ const cwdToplevel2 = await gitToplevelOf(dir);
37421
+ let branch = null;
37422
+ if (cwdToplevel2 !== null) {
37423
+ try {
37424
+ branch = await sh("git", ["-C", cwdToplevel2, "rev-parse", "--abbrev-ref", "HEAD"]);
37425
+ } catch {
37426
+ }
37427
+ }
37428
+ const inferred = inferSubmitClaim({
37429
+ claims: claims.readClaims(),
37430
+ activeIds: new Set(claims.listClaims({ active: true }).map((c) => c.id)),
37431
+ cwdToplevel: cwdToplevel2,
37432
+ cwd: pathResolve(dir),
37433
+ branch,
37434
+ canonicalize: canonicalPath
37435
+ });
37436
+ if (inferred.claim) {
37437
+ id = inferred.claim.id;
37438
+ console.log(` claim inferred from ${cwdToplevel2 ?? pathResolve(dir)}: ${id}`);
37439
+ } else if (inferred.candidates.length > 0) {
37440
+ console.error(
37441
+ `terminalhire claim: ${inferred.candidates.length} claims match this directory or branch \u2014 say which one.
37442
+ ` + inferred.candidates.map((c) => ` ${c.id} ${c.state} ${c.title}`).join("\n") + // No `--yes` in this hint, deliberately. The one place that names the flag
37443
+ // says in the same breath that a human must type it and an agent never may;
37444
+ // a bare copyable line here would hand out the flag without the sentence.
37445
+ `
37446
+ terminalhire claim submit <id>`
37447
+ );
37448
+ process.exit(1);
37449
+ } else {
37450
+ console.error(
37451
+ `terminalhire claim: no claim is recorded for this directory or branch, so there is nothing to infer.
37452
+ Usage: terminalhire claim submit <id> [--worktree <path>] [--yes] [--body-file <path>] [--no-body] [--title <t>] [--no-closes]
37453
+ Your claims: terminalhire claim list --active`
37454
+ );
37455
+ process.exit(1);
37456
+ }
37381
37457
  }
37382
37458
  const claim = claims.findClaim(id);
37383
37459
  if (!claim) {
@@ -37396,17 +37472,10 @@ async function cmdSubmit(id, flags = {}) {
37396
37472
  );
37397
37473
  process.exit(1);
37398
37474
  }
37399
- const toplevelOf = async (dir) => {
37400
- try {
37401
- return await sh("git", ["-C", dir, "rev-parse", "--show-toplevel"]);
37402
- } catch {
37403
- return null;
37404
- }
37405
- };
37406
37475
  const recorded = claim.worktreePath;
37407
- const override = worktreeOverride === void 0 ? void 0 : await toplevelOf(worktreeOverride);
37408
- const cwdToplevel = await toplevelOf(process.cwd());
37409
- const recordedIsGit = await toplevelOf(recorded) === recorded;
37476
+ const override = worktreeOverride === void 0 ? void 0 : await gitToplevelOf(worktreeOverride);
37477
+ const cwdToplevel = await gitToplevelOf(process.cwd());
37478
+ const recordedIsGit = await gitToplevelOf(recorded) === recorded;
37410
37479
  const resolved = resolveSubmitWorktree({ override, cwdToplevel, recorded, recordedIsGit });
37411
37480
  if (resolved.error) {
37412
37481
  console.error(`terminalhire claim: ${resolved.error}`);
@@ -38466,6 +38535,7 @@ export {
38466
38535
  fmtContestedWarning,
38467
38536
  founderClaimStanding,
38468
38537
  founderPostingIdOf,
38538
+ inferSubmitClaim,
38469
38539
  isContested,
38470
38540
  isStrayArgShortRefClaim,
38471
38541
  isTerminalRunStatus,