@harborppm/cli 0.58.0 → 0.60.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 +5 -2
- package/dist/app.mjs +1323 -428
- package/lib/cli.mjs +135 -36
- package/lib/format.mjs +44 -2
- package/lib/mcp.mjs +9 -2
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -39,8 +39,10 @@ import {
|
|
|
39
39
|
lifeboatsText,
|
|
40
40
|
followedText,
|
|
41
41
|
soundingsText,
|
|
42
|
+
ashoreText,
|
|
42
43
|
voyageText,
|
|
43
44
|
whyText,
|
|
45
|
+
barnaclesText,
|
|
44
46
|
handoverText,
|
|
45
47
|
fogText,
|
|
46
48
|
agentWorkText,
|
|
@@ -96,6 +98,9 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
96
98
|
changes without --apply
|
|
97
99
|
harbor why PROJECT|TASK|MILESTONE what's holding it up: the line of
|
|
98
100
|
task waits that sets its date
|
|
101
|
+
harbor barnacles PROJECT work added since the plan was set:
|
|
102
|
+
by week, by who, and the days of
|
|
103
|
+
slip it explains
|
|
99
104
|
harbor hill [--program P] where each project is on its hill
|
|
100
105
|
harbor hill PROJECT --at 0-100|top|done move it: up to 50 is figuring it
|
|
101
106
|
out, past 50 is getting it done
|
|
@@ -213,12 +218,16 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
213
218
|
harbor note PROJECT ["NOTE"] [--opens DATE|--milestone NAME|--off-track]
|
|
214
219
|
a note for later: sealed until
|
|
215
220
|
its day, milestone, or Off track
|
|
216
|
-
harbor sound TASK [
|
|
221
|
+
harbor sound TASK [--answer DAYS|--ask NAMES|--settle DAYS] [--why TEXT]
|
|
217
222
|
a sounding: ask a few people how
|
|
218
223
|
many days a task will take, give
|
|
219
224
|
your answer, or read the spread
|
|
220
225
|
harbor soundings [PROJECT] open tasks with a sounding, and
|
|
221
226
|
the ones waiting on your answer
|
|
227
|
+
harbor ashore [--weeks N] [--country CODES|none] [--person NAME]
|
|
228
|
+
public holidays and time away
|
|
229
|
+
coming up, and whose holidays
|
|
230
|
+
the harbor (or someone) takes
|
|
222
231
|
harbor handover PERSON [--to NAME] [--until DATE] [--note TEXT]
|
|
223
232
|
[--keep ID,ID] [--confirm]
|
|
224
233
|
hand someone's work to the crew
|
|
@@ -449,6 +458,8 @@ const FLAG = {
|
|
|
449
458
|
days: "the days",
|
|
450
459
|
why: "--why",
|
|
451
460
|
people: "--ask",
|
|
461
|
+
countries: "--country",
|
|
462
|
+
country: "--country",
|
|
452
463
|
};
|
|
453
464
|
|
|
454
465
|
/** Harbor's refusals are worded for agents; say them as the commands and flags that answer them. */
|
|
@@ -731,7 +742,8 @@ const DETAIL = {
|
|
|
731
742
|
" harbor sound TASK --ask crew writes: asks you, its owner and whoever",
|
|
732
743
|
" has open work on its project",
|
|
733
744
|
' harbor sound TASK --ask "Sam,Priya" writes: asks those people',
|
|
734
|
-
' harbor sound TASK 4 [--why "TEXT"]
|
|
745
|
+
' harbor sound TASK --answer 4 [--why "TEXT"]',
|
|
746
|
+
" writes: your answer, in working days",
|
|
735
747
|
" harbor sound TASK --settle 6 writes: the days you agreed on after",
|
|
736
748
|
" talking a wide spread through",
|
|
737
749
|
"",
|
|
@@ -739,6 +751,19 @@ const DETAIL = {
|
|
|
739
751
|
" everyone has. Close guesses give the task an estimate; far-apart ones",
|
|
740
752
|
" say talk first. The forecast draws on every answer.",
|
|
741
753
|
],
|
|
754
|
+
ashore: [
|
|
755
|
+
" harbor ashore [--weeks N] reads: holidays and time away in the",
|
|
756
|
+
" next N weeks (8 unless you say)",
|
|
757
|
+
" harbor ashore --country US,GB writes: whose public holidays the",
|
|
758
|
+
" harbor takes (none for no holidays)",
|
|
759
|
+
" harbor ashore --person NAME --country GB",
|
|
760
|
+
" writes: one person's own country",
|
|
761
|
+
" (harbor to take the harbor's again)",
|
|
762
|
+
"",
|
|
763
|
+
" The forecast, the weeks ahead, the timeline and calendar feeds count",
|
|
764
|
+
" these as days nobody works. Countries: AU BR CA DE ES FR GB IE IT MX NL",
|
|
765
|
+
" NZ US (national holidays only).",
|
|
766
|
+
],
|
|
742
767
|
soundings: [
|
|
743
768
|
" harbor soundings [PROJECT] reads: open tasks with a sounding",
|
|
744
769
|
" (all projects when left out), and the",
|
|
@@ -852,9 +877,23 @@ const DETAIL = {
|
|
|
852
877
|
" Tasks have due dates, not lengths, so a task can finish on its due date",
|
|
853
878
|
" at the soonest, never before what it waits on, and never in the past.",
|
|
854
879
|
" Each link's slack is the days it can slip before it moves the next one",
|
|
855
|
-
" (or the end); a negative one already does.
|
|
880
|
+
" (or the end); a negative one already does. Open tasks with no due date",
|
|
881
|
+
" can't hold a date, so they're left out and counted. In --json, endDate",
|
|
882
|
+
" (dueDate for a task or milestone) is the date held and daysSpare the",
|
|
883
|
+
" days before it (negative: that many over). PROJECT is a name, a key",
|
|
856
884
|
" like RR, or an id.",
|
|
857
885
|
],
|
|
886
|
+
barnacles: [
|
|
887
|
+
" harbor barnacles PROJECT reads how much the plan has grown",
|
|
888
|
+
"",
|
|
889
|
+
" Counts the tasks made after the baseline was set (without a baseline",
|
|
890
|
+
" time, after the project's first week of tasks), as a",
|
|
891
|
+
" share of what was planned, by week and by who added them. At the",
|
|
892
|
+
" crew's pace (tasks finished a week over the last six) it says how many",
|
|
893
|
+
" days of work that is, and how much of the slip against the planned end",
|
|
894
|
+
" it explains. The weekly brief names projects that grew a lot since",
|
|
895
|
+
" their baseline.",
|
|
896
|
+
],
|
|
858
897
|
voyage: [
|
|
859
898
|
" harbor voyage PROJECT reads a Done project's voyage log",
|
|
860
899
|
' harbor voyage PROJECT "YOUR LINE" writes: adds or changes your line in it',
|
|
@@ -2024,7 +2063,8 @@ export async function run(argv, io = {}) {
|
|
|
2024
2063
|
);
|
|
2025
2064
|
if (!values.project) throw new HarborError("Say which project: --project NAME.");
|
|
2026
2065
|
// Several at once: one title a line on stdin, with no title (or -) after add.
|
|
2027
|
-
const piped =
|
|
2066
|
+
const piped =
|
|
2067
|
+
positionals.length === 0 || (positionals.length === 1 && positionals[0] === "-");
|
|
2028
2068
|
const stdin = io.stdin ?? process.stdin;
|
|
2029
2069
|
const titles =
|
|
2030
2070
|
piped && (io.stdin || !process.stdin.isTTY)
|
|
@@ -2033,28 +2073,34 @@ export async function run(argv, io = {}) {
|
|
|
2033
2073
|
.map((l) => l.trim())
|
|
2034
2074
|
.filter(Boolean)
|
|
2035
2075
|
: [text(positionals, "the task")];
|
|
2036
|
-
if (titles.length === 0)
|
|
2037
|
-
|
|
2076
|
+
if (titles.length === 0)
|
|
2077
|
+
throw new HarborError("Say the task, or give one title a line on stdin.");
|
|
2078
|
+
if (titles.length > 100)
|
|
2079
|
+
throw new HarborError(
|
|
2080
|
+
"That's more than 100 tasks; harbor import FILE takes a spreadsheet.",
|
|
2081
|
+
);
|
|
2038
2082
|
const added = [];
|
|
2039
2083
|
for (const title of titles) {
|
|
2040
|
-
const r = await client
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2084
|
+
const r = await client
|
|
2085
|
+
.call("create_task", {
|
|
2086
|
+
harbor,
|
|
2087
|
+
project: values.project,
|
|
2088
|
+
title,
|
|
2089
|
+
// Yours unless you say otherwise, as with n in the app.
|
|
2090
|
+
assignee: values.assignee ?? "me",
|
|
2091
|
+
due: duePilot(values.due) ? undefined : dueDate(values.due),
|
|
2092
|
+
suggestedDue: duePilot(values.due),
|
|
2093
|
+
priority: values.priority,
|
|
2094
|
+
status: values.status === undefined ? undefined : statusWord(values.status),
|
|
2095
|
+
overLimit: values["over-limit"] || undefined,
|
|
2096
|
+
})
|
|
2097
|
+
.catch((err) => {
|
|
2098
|
+
if (err instanceof HarborError) {
|
|
2099
|
+
const done = added.length ? ` (added ${added.length} before it)` : "";
|
|
2100
|
+
throw new HarborError(`${hint(err.message, values.project)}${done}`);
|
|
2101
|
+
}
|
|
2102
|
+
throw err;
|
|
2103
|
+
});
|
|
2058
2104
|
if (values.comment)
|
|
2059
2105
|
r.comment = (
|
|
2060
2106
|
await client.call("add_comment", {
|
|
@@ -2209,19 +2255,26 @@ export async function run(argv, io = {}) {
|
|
|
2209
2255
|
const number = (v, name) => {
|
|
2210
2256
|
if (v === undefined) return undefined;
|
|
2211
2257
|
const n = Number(v);
|
|
2212
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
2258
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
2259
|
+
throw new HarborError(`--${name} is a number of hours, like 2 or 1.5.`);
|
|
2213
2260
|
return n;
|
|
2214
2261
|
};
|
|
2215
2262
|
const [task, day, ...extra] = positionals;
|
|
2216
2263
|
if (extra.length) throw new HarborError("Give a task key and a day: harbor week RR-42 wed.");
|
|
2217
2264
|
const dayHours = number(values["day-hours"], "day-hours");
|
|
2218
|
-
const show = (r) =>
|
|
2265
|
+
const show = (r) =>
|
|
2266
|
+
[r.done, weekText(r, { ...opts, review: Boolean(values.review) })]
|
|
2267
|
+
.filter(Boolean)
|
|
2268
|
+
.join("\n\n");
|
|
2219
2269
|
if (!task && dayHours === undefined) {
|
|
2220
|
-
if (values.off || values.hours)
|
|
2270
|
+
if (values.off || values.hours)
|
|
2271
|
+
throw new HarborError("Say which task: harbor week RR-42 wed --hours 3.");
|
|
2221
2272
|
return call("get_week", { harbor, week }, show);
|
|
2222
2273
|
}
|
|
2223
2274
|
if (task && !day && !values.off)
|
|
2224
|
-
throw new HarborError(
|
|
2275
|
+
throw new HarborError(
|
|
2276
|
+
`Say which day for ${task}: harbor week ${task} wed, or --off to take it off the week.`,
|
|
2277
|
+
);
|
|
2225
2278
|
return call(
|
|
2226
2279
|
"plan_week",
|
|
2227
2280
|
{
|
|
@@ -2485,9 +2538,14 @@ export async function run(argv, io = {}) {
|
|
|
2485
2538
|
);
|
|
2486
2539
|
}
|
|
2487
2540
|
case "sound": {
|
|
2488
|
-
const { positionals, values, harbor } = parse(spec([], ["why", "ask", "settle"]));
|
|
2489
|
-
const [task,
|
|
2541
|
+
const { positionals, values, harbor } = parse(spec([], ["answer", "why", "ask", "settle"]));
|
|
2542
|
+
const [task, ...extra] = positionals;
|
|
2490
2543
|
if (!task) throw new HarborError("Give a task's key, like harbor sound RR-4.");
|
|
2544
|
+
// A bare number used to be the answer; it writes, so it now needs its flag.
|
|
2545
|
+
if (extra.length === 1 && /^\d+(\.\d+)?(d|days?)?$/i.test(extra[0]))
|
|
2546
|
+
throw new HarborError(
|
|
2547
|
+
`To give your answer, say so: harbor sound ${task} --answer ${extra[0]}.`,
|
|
2548
|
+
);
|
|
2491
2549
|
if (extra.length) throw new HarborError(`Unexpected "${extra.join(" ")}".`);
|
|
2492
2550
|
const number = (text) => {
|
|
2493
2551
|
const n = Number(String(text).replace(/d(ays?)?$/i, ""));
|
|
@@ -2495,11 +2553,14 @@ export async function run(argv, io = {}) {
|
|
|
2495
2553
|
throw new HarborError(`Give days as a number, like 3 or 2.5 (not "${text}").`);
|
|
2496
2554
|
return n;
|
|
2497
2555
|
};
|
|
2556
|
+
const days = values.answer;
|
|
2498
2557
|
const given = [days !== undefined, values.ask !== undefined, values.settle !== undefined];
|
|
2499
2558
|
if (given.filter(Boolean).length > 1)
|
|
2500
2559
|
throw new HarborError("Answer, ask or settle: one at a time.");
|
|
2501
2560
|
if (values.why !== undefined && days === undefined)
|
|
2502
|
-
throw new HarborError(
|
|
2561
|
+
throw new HarborError(
|
|
2562
|
+
'--why goes with your answer: harbor sound TASK --answer 4 --why "TEXT".',
|
|
2563
|
+
);
|
|
2503
2564
|
if (values.ask !== undefined) {
|
|
2504
2565
|
const crew = /^crew$/i.test(values.ask.trim())
|
|
2505
2566
|
? undefined
|
|
@@ -2507,11 +2568,10 @@ export async function run(argv, io = {}) {
|
|
|
2507
2568
|
.split(",")
|
|
2508
2569
|
.map((x) => x.trim())
|
|
2509
2570
|
.filter(Boolean);
|
|
2510
|
-
return call(
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
`Asked ${r.asked.people.join(", ")} how many days ${r.asked.key ?? r.asked.title} will take. The answers show together once everyone's in.`,
|
|
2571
|
+
return call("take_sounding", { harbor, task, ...(crew ? { people: crew } : {}) }, (r) =>
|
|
2572
|
+
!crew && r.asked.people.length === 1
|
|
2573
|
+
? `Only you are on this crew, so the sounding on ${r.asked.key ?? r.asked.title} is just your guess. Ask others by name: harbor sound ${task} --ask "Sam,Priya".`
|
|
2574
|
+
: `Asked ${r.asked.people.join(", ")} how many days ${r.asked.key ?? r.asked.title} will take. The answers show together once everyone's in.`,
|
|
2515
2575
|
);
|
|
2516
2576
|
}
|
|
2517
2577
|
if (values.settle !== undefined) {
|
|
@@ -2543,12 +2603,51 @@ export async function run(argv, io = {}) {
|
|
|
2543
2603
|
soundingsText(r),
|
|
2544
2604
|
);
|
|
2545
2605
|
}
|
|
2606
|
+
case "ashore": {
|
|
2607
|
+
const { positionals, values, harbor } = parse(spec([], ["weeks", "country", "person"]));
|
|
2608
|
+
none(positionals);
|
|
2609
|
+
if (values.person && values.country === undefined)
|
|
2610
|
+
throw new HarborError("Give their country too: --person NAME --country GB (or harbor).");
|
|
2611
|
+
if (values.country !== undefined) {
|
|
2612
|
+
if (values.person) {
|
|
2613
|
+
return call(
|
|
2614
|
+
"set_holidays",
|
|
2615
|
+
{ harbor, person: values.person, country: values.country },
|
|
2616
|
+
(r) =>
|
|
2617
|
+
r.country === "harbor"
|
|
2618
|
+
? `${r.person} takes the harbor's holidays.`
|
|
2619
|
+
: `${r.person} takes ${r.country} holidays.`,
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
const countries = /^none$/i.test(values.country.trim())
|
|
2623
|
+
? []
|
|
2624
|
+
: values.country
|
|
2625
|
+
.split(",")
|
|
2626
|
+
.map((c) => c.trim())
|
|
2627
|
+
.filter(Boolean);
|
|
2628
|
+
return call("set_holidays", { harbor, countries }, (r) =>
|
|
2629
|
+
r.harbor.length
|
|
2630
|
+
? `The harbor takes public holidays from ${r.harbor.map((c) => c.name).join(", ")}.`
|
|
2631
|
+
: "The harbor takes no public holidays now.",
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
const weeks = values.weeks === undefined ? 8 : Number(values.weeks);
|
|
2635
|
+
if (!Number.isInteger(weeks) || weeks < 1 || weeks > 52)
|
|
2636
|
+
throw new HarborError("--weeks is a whole number from 1 to 52.");
|
|
2637
|
+
return call("time_ashore", { harbor, days: weeks * 7 }, (r) => ashoreText(r, weeks));
|
|
2638
|
+
}
|
|
2546
2639
|
case "why": {
|
|
2547
2640
|
const { positionals, harbor } = parse(spec());
|
|
2548
2641
|
const of = positionals.join(" ").trim();
|
|
2549
2642
|
if (!of) throw new HarborError("Say what: a project, a task key like RR-42, or a milestone.");
|
|
2550
2643
|
return call("long_pole", { harbor, of }, whyText);
|
|
2551
2644
|
}
|
|
2645
|
+
case "barnacles": {
|
|
2646
|
+
const { positionals, harbor } = parse(spec());
|
|
2647
|
+
const project = positionals.join(" ").trim();
|
|
2648
|
+
if (!project) throw new HarborError("Say which project: harbor barnacles PROJECT.");
|
|
2649
|
+
return call("scope_growth", { harbor, project }, barnaclesText);
|
|
2650
|
+
}
|
|
2552
2651
|
case "voyage": {
|
|
2553
2652
|
const { positionals, harbor } = parse(spec());
|
|
2554
2653
|
const [project, ...words] = positionals;
|
package/lib/format.mjs
CHANGED
|
@@ -571,7 +571,29 @@ export function whyText(r) {
|
|
|
571
571
|
.join(", ")}.`,
|
|
572
572
|
]
|
|
573
573
|
: [];
|
|
574
|
-
|
|
574
|
+
const note = r.note ? ["", r.note] : [];
|
|
575
|
+
return [...head, "", ...via, ...lines, ...others, ...note].join("\n");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** A project's barnacles, as scope_growth answers it: the growth, its cost in days, by week and by who. */
|
|
579
|
+
export function barnaclesText(r) {
|
|
580
|
+
const head = [`Barnacles · ${r.project}`];
|
|
581
|
+
if (!r.added) {
|
|
582
|
+
head.push(`Nothing added since the plan was set (${r.since.slice(0, 10)}): ${r.planned} task${r.planned === 1 ? "" : "s"}, as planned.`);
|
|
583
|
+
return head.join("\n");
|
|
584
|
+
}
|
|
585
|
+
head.push(`${r.headline} (${r.planned} planned, ${r.addedThisWeek} added this week).`);
|
|
586
|
+
if (r.daysLine) head.push(r.daysLine);
|
|
587
|
+
const top = Math.max(1, ...r.byWeek.map((w) => w.added));
|
|
588
|
+
const weeks = r.byWeek.map(
|
|
589
|
+
(w) => ` w/c ${w.weekOf} ${"█".repeat(Math.max(1, Math.round((w.added / top) * 20)))} ${w.added}`,
|
|
590
|
+
);
|
|
591
|
+
const who = r.byWho.map((w) => `${w.who ?? "not recorded"} ${w.added}`).join(", ");
|
|
592
|
+
const from =
|
|
593
|
+
r.measuredFrom === "when the baseline was set"
|
|
594
|
+
? `Counted from when the baseline was set, ${r.since.slice(0, 10)}.`
|
|
595
|
+
: `Counted from a week after its first task was made, ${r.since.slice(0, 10)}; setting a baseline counts from then instead.`;
|
|
596
|
+
return [...head, "", "Added by week:", ...weeks, "", `By who: ${who}`, "", from].join("\n");
|
|
575
597
|
}
|
|
576
598
|
|
|
577
599
|
/** A position on a 21-wide track, the top of the hill marked in the middle. */
|
|
@@ -752,7 +774,7 @@ export function soundingText(task, s, { answered = false } = {}) {
|
|
|
752
774
|
for (const a of s.answers)
|
|
753
775
|
lines.push(` You said ${daysText(a.days)}${a.why ? ` · "${a.why}"` : ""}`);
|
|
754
776
|
if (s.youWereAsked && !s.answers.length)
|
|
755
|
-
lines.push(` Give yours: harbor sound ${task.key ?? task.id} DAYS`);
|
|
777
|
+
lines.push(` Give yours: harbor sound ${task.key ?? task.id} --answer DAYS`);
|
|
756
778
|
return lines.join("\n");
|
|
757
779
|
}
|
|
758
780
|
lines.push(` ${spreadLine(s.answers)}`);
|
|
@@ -768,6 +790,26 @@ export function soundingText(task, s, { answered = false } = {}) {
|
|
|
768
790
|
return lines.join("\n");
|
|
769
791
|
}
|
|
770
792
|
|
|
793
|
+
/** time_ashore: whose holidays count, then the days off coming up, soonest first. */
|
|
794
|
+
export function ashoreText(r, weeks = 8) {
|
|
795
|
+
const lines = [];
|
|
796
|
+
const countries = r.countries ?? [];
|
|
797
|
+
lines.push(
|
|
798
|
+
countries.length
|
|
799
|
+
? `Time ashore · holidays from ${countries.map((c) => c.name).join(", ")}`
|
|
800
|
+
: "Time ashore · no holiday country set (harbor ashore --country US)",
|
|
801
|
+
);
|
|
802
|
+
for (const p of r.people ?? []) lines.push(` ${p.name} takes ${p.country} holidays`);
|
|
803
|
+
const rows = [
|
|
804
|
+
...(r.holidays ?? []).map((h) => [h.date, h.name]),
|
|
805
|
+
...(r.away ?? []).map((a) => [a.from, `${a.name} away, back ${a.back}`]),
|
|
806
|
+
].sort((a, b) => a[0].localeCompare(b[0]));
|
|
807
|
+
lines.push("");
|
|
808
|
+
if (!rows.length) lines.push(` Nothing in the next ${weeks} weeks.`);
|
|
809
|
+
for (const [day, what] of rows) lines.push(` ${day} ${what}`);
|
|
810
|
+
return lines.join("\n");
|
|
811
|
+
}
|
|
812
|
+
|
|
771
813
|
/** list_soundings: the ones waiting on you first, then the rest, wide ones flagged. */
|
|
772
814
|
export function soundingsText(r) {
|
|
773
815
|
const all = r.soundings ?? [];
|
package/lib/mcp.mjs
CHANGED
|
@@ -53,8 +53,15 @@ export function createClient({ creds, save, fetcher = fetch, now = Date.now }) {
|
|
|
53
53
|
const msg = await res.json();
|
|
54
54
|
if (msg.error) throw new HarborError(msg.error.message);
|
|
55
55
|
const result = msg.result ?? {};
|
|
56
|
-
if (result.isError)
|
|
57
|
-
|
|
56
|
+
if (result.isError) {
|
|
57
|
+
const said = result.content?.map((c) => c.text).join("\n") || "Harbor said no.";
|
|
58
|
+
// A command newer than the site it talks to: the tool ships with the next deploy.
|
|
59
|
+
if (/^No tool named "/.test(said))
|
|
60
|
+
throw new HarborError(
|
|
61
|
+
`${new URL(current.server).host} doesn't have this yet. Try again after the next site update.`,
|
|
62
|
+
);
|
|
63
|
+
throw new HarborError(said);
|
|
64
|
+
}
|
|
58
65
|
return result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
59
66
|
},
|
|
60
67
|
|