@harborppm/cli 0.45.0 → 0.46.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 +4 -0
- package/dist/app.mjs +7539 -122
- package/lib/cli.mjs +128 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -78,6 +78,13 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
78
78
|
projects with health and open work
|
|
79
79
|
(--json adds each board's limits)
|
|
80
80
|
harbor forecast [PROJECT] [--program P] when projects will likely finish
|
|
81
|
+
harbor try PROJECT [--drop KEYS] [--add "TITLE" [--count N]]
|
|
82
|
+
[--due KEY=DATE] [--give KEY=NAME] [--end DATE]
|
|
83
|
+
[--borrow NAME[=DAYS]] [--release NAME]
|
|
84
|
+
[--saved] [--apply|--save|--discard]
|
|
85
|
+
dry dock: the forecast with plan
|
|
86
|
+
changes tried on a copy; nothing
|
|
87
|
+
changes without --apply
|
|
81
88
|
harbor hill [--program P] where each project is on its hill
|
|
82
89
|
harbor hill PROJECT --at 0-100|top|done move it: up to 50 is figuring it
|
|
83
90
|
out, past 50 is getting it done
|
|
@@ -537,6 +544,33 @@ const DETAIL = {
|
|
|
537
544
|
" than the pace would finish sets the dates, and the forecast names it. It",
|
|
538
545
|
" warns when under two weeks of pace is all there is.",
|
|
539
546
|
],
|
|
547
|
+
try: [
|
|
548
|
+
" harbor try PROJECT [changes] reads the forecast with the changes",
|
|
549
|
+
" tried on a copy of the plan",
|
|
550
|
+
" harbor try PROJECT [changes] --apply writes: makes the changes for real",
|
|
551
|
+
" harbor try PROJECT [changes] --save writes: keeps them on the project to",
|
|
552
|
+
" show someone (Forecast tab, Open dry",
|
|
553
|
+
" dock)",
|
|
554
|
+
" harbor try PROJECT --saved reads the saved ones (add changes,",
|
|
555
|
+
" --apply or --save as above)",
|
|
556
|
+
" harbor try PROJECT --discard writes: throws the saved ones away",
|
|
557
|
+
"",
|
|
558
|
+
" Changes, each taking several with commas between:",
|
|
559
|
+
" --drop RL-4,RL-5 take tasks out of the plan (--apply removes them)",
|
|
560
|
+
' --add "TITLE" --count N new work, N tasks called TITLE',
|
|
561
|
+
" --due RL-4=fri move a due date (none takes it off)",
|
|
562
|
+
" --give RL-4=Sam give a task to someone (none: nobody)",
|
|
563
|
+
" --end 2026-12-19 move the project's end date",
|
|
564
|
+
" --borrow Sam=3 someone joins 3 days a week (5 if left out);",
|
|
565
|
+
" --apply raises their share of the program",
|
|
566
|
+
" --release Priya someone leaves: their open tasks lose their owner",
|
|
567
|
+
"",
|
|
568
|
+
" It prints the real plan and the changed one side by side: when it's",
|
|
569
|
+
" probably and very likely done, open tasks, the chance of making the end",
|
|
570
|
+
" date, the crew's pace, and the load of everyone the changes touch. A",
|
|
571
|
+
" borrowed person adds an average crew member's pace for each day a week",
|
|
572
|
+
" they give; a released one takes away their share of what was finished.",
|
|
573
|
+
],
|
|
540
574
|
report: [
|
|
541
575
|
" harbor report PROGRAM reads the program's status report",
|
|
542
576
|
"",
|
|
@@ -1082,6 +1116,100 @@ export async function run(argv, io = {}) {
|
|
|
1082
1116
|
return 0;
|
|
1083
1117
|
}
|
|
1084
1118
|
|
|
1119
|
+
if (command === "try") {
|
|
1120
|
+
const { values, positionals } = parseArgs(rest, {
|
|
1121
|
+
flags: ["apply", "save", "discard", "saved", "json"],
|
|
1122
|
+
options: ["harbor", "drop", "add", "count", "due", "give", "end", "borrow", "release"],
|
|
1123
|
+
});
|
|
1124
|
+
if (positionals.length !== 1)
|
|
1125
|
+
throw new HarborError(
|
|
1126
|
+
positionals.length
|
|
1127
|
+
? "harbor try takes one project; put a name with spaces in quotes."
|
|
1128
|
+
: "Say which project: harbor try PROJECT --drop RL-4. harbor help try has the rest.",
|
|
1129
|
+
);
|
|
1130
|
+
if (["apply", "save", "discard"].filter((k) => values[k]).length > 1)
|
|
1131
|
+
throw new HarborError("Use one of --apply, --save or --discard.");
|
|
1132
|
+
const creds = await readCredentials(env);
|
|
1133
|
+
if (!hasFullAccess(creds))
|
|
1134
|
+
throw new HarborError(
|
|
1135
|
+
creds
|
|
1136
|
+
? "This sign-in can only read. Run harbor login (without --read-only) to use dry dock."
|
|
1137
|
+
: "Not signed in. Run harbor login, or harbor signup if you're new.",
|
|
1138
|
+
);
|
|
1139
|
+
const app = await loadApp();
|
|
1140
|
+
const notes = [];
|
|
1141
|
+
const say = values.json ? (line) => notes.push(line) : (line) => out(plain(line));
|
|
1142
|
+
const result = await app.tryHere({
|
|
1143
|
+
transport: app.planTransport(
|
|
1144
|
+
tokenSource({ creds, save: (c) => writeCredentials(c, env), fetcher }),
|
|
1145
|
+
fetcher,
|
|
1146
|
+
),
|
|
1147
|
+
project: positionals[0],
|
|
1148
|
+
flags: values,
|
|
1149
|
+
apply: Boolean(values.apply),
|
|
1150
|
+
save: Boolean(values.save),
|
|
1151
|
+
discard: Boolean(values.discard),
|
|
1152
|
+
say,
|
|
1153
|
+
pickHarbor: (list) => {
|
|
1154
|
+
if (values.harbor) return app.harborNamed(list, values.harbor);
|
|
1155
|
+
const saved = list.find((h) => h.id === creds?.harbor);
|
|
1156
|
+
if (saved) return saved;
|
|
1157
|
+
if (list.length === 1) return list[0];
|
|
1158
|
+
throw new HarborError(
|
|
1159
|
+
`You're in ${list.length} harbors (${list.map((h) => h.name).join(", ")}). Add --harbor NAME, or run harbor use NAME first.`,
|
|
1160
|
+
);
|
|
1161
|
+
},
|
|
1162
|
+
});
|
|
1163
|
+
if (!result) {
|
|
1164
|
+
if (values.json) out(jsonText({ notes }));
|
|
1165
|
+
return 0;
|
|
1166
|
+
}
|
|
1167
|
+
if (values.json) {
|
|
1168
|
+
const side = (s) => ({
|
|
1169
|
+
endDate: s.project.endDate,
|
|
1170
|
+
open: s.open,
|
|
1171
|
+
likely: s.tide.kind === "forecast" ? s.tide.likely : null,
|
|
1172
|
+
veryLikely: s.tide.kind === "forecast" ? s.tide.veryLikely : null,
|
|
1173
|
+
chanceByEnd: s.tide.kind === "forecast" ? s.tide.chanceByEnd : null,
|
|
1174
|
+
forecast: s.tide.kind,
|
|
1175
|
+
});
|
|
1176
|
+
const c = result.comparison;
|
|
1177
|
+
out(
|
|
1178
|
+
jsonText({
|
|
1179
|
+
project: { id: result.project.id, key: result.project.key, name: result.project.name },
|
|
1180
|
+
changes: result.changes,
|
|
1181
|
+
real: side(c.real),
|
|
1182
|
+
dryDock: side(c.docked),
|
|
1183
|
+
shiftDays: c.shift,
|
|
1184
|
+
pace: c.pace,
|
|
1185
|
+
...(result.applied !== undefined ? { applied: result.applied } : {}),
|
|
1186
|
+
...(result.saved ? { saved: true } : {}),
|
|
1187
|
+
notes,
|
|
1188
|
+
}),
|
|
1189
|
+
);
|
|
1190
|
+
return 0;
|
|
1191
|
+
}
|
|
1192
|
+
for (const line of result.lines) out(plain(line));
|
|
1193
|
+
out("");
|
|
1194
|
+
if (result.applied !== undefined)
|
|
1195
|
+
out(
|
|
1196
|
+
plain(
|
|
1197
|
+
`⚓ Applied ${result.applied} change${result.applied === 1 ? "" : "s"} to ${result.project.name}.`,
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
else if (result.saved)
|
|
1201
|
+
out(
|
|
1202
|
+
plain(
|
|
1203
|
+
`⚓ Saved on ${result.project.name}. It's on the Forecast tab under Open dry dock; --apply makes it real.`,
|
|
1204
|
+
),
|
|
1205
|
+
);
|
|
1206
|
+
else if (result.changes.length)
|
|
1207
|
+
out(
|
|
1208
|
+
"Nothing was changed. Add --apply to make these changes, or --save to keep them to show someone.",
|
|
1209
|
+
);
|
|
1210
|
+
return 0;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1085
1213
|
if (command === "init" || command === "sync") {
|
|
1086
1214
|
const init = command === "init";
|
|
1087
1215
|
const { values, positionals } = parseArgs(rest, {
|