@harborppm/cli 0.9.0 → 0.9.1
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/app.mjs +24 -7
- package/lib/cli.mjs +9 -4
- package/lib/format.mjs +21 -3
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Built from src/tui by cli/build.mjs. Edit the source, not this file.
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
|
|
4
|
-
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/lib/clock.ts
|
|
@@ -2157,15 +2157,23 @@ applyTheme("dark");
|
|
|
2157
2157
|
*/
|
|
2158
2158
|
/** One line of text. Enter keeps it, Esc leaves it as it was. */
|
|
2159
2159
|
function TextField({ label, initial = "", placeholder, hint, onSubmit, onCancel }) {
|
|
2160
|
-
const [value,
|
|
2160
|
+
const [value, setShown] = useState(initial);
|
|
2161
|
+
const typed = useRef(initial);
|
|
2162
|
+
const setValue = (next) => {
|
|
2163
|
+
typed.current = next;
|
|
2164
|
+
setShown(next);
|
|
2165
|
+
};
|
|
2161
2166
|
useInput((input, key) => {
|
|
2162
2167
|
if (key.escape) return onCancel();
|
|
2163
|
-
if (key.return) return onSubmit(
|
|
2164
|
-
if (key.backspace || key.delete) return setValue(
|
|
2168
|
+
if (key.return) return onSubmit(typed.current);
|
|
2169
|
+
if (key.backspace || key.delete) return setValue(typed.current.slice(0, -1));
|
|
2165
2170
|
if (key.ctrl && input === "u") return setValue("");
|
|
2166
2171
|
if (key.ctrl || key.meta || key.tab || key.upArrow || key.downArrow) return;
|
|
2167
2172
|
if (key.leftArrow || key.rightArrow) return;
|
|
2168
|
-
if (input)
|
|
2173
|
+
if (!input) return;
|
|
2174
|
+
const ends = /[\r\n]+$/.test(input);
|
|
2175
|
+
setValue(typed.current + input.replace(/[\r\n]+$/, "").replace(/[\r\n]+/g, " "));
|
|
2176
|
+
if (ends) onSubmit(typed.current);
|
|
2169
2177
|
});
|
|
2170
2178
|
return /* @__PURE__ */ jsxs(Box, {
|
|
2171
2179
|
flexDirection: "column",
|
|
@@ -7022,6 +7030,13 @@ function viewerUpdate(state, partial) {
|
|
|
7022
7030
|
blocked
|
|
7023
7031
|
};
|
|
7024
7032
|
}
|
|
7033
|
+
/**
|
|
7034
|
+
* Whether this saved harbor can't be changed from here: the person is a
|
|
7035
|
+
* viewer, or its free trial has ended with nobody keeping it (keep.ts).
|
|
7036
|
+
*/
|
|
7037
|
+
function readOnlyHarbor(s) {
|
|
7038
|
+
return s.cloud && (s.myRole === "viewer" || Boolean(s.keepPaused));
|
|
7039
|
+
}
|
|
7025
7040
|
|
|
7026
7041
|
//#endregion
|
|
7027
7042
|
//#region src/lib/demo-seed.ts
|
|
@@ -7117,7 +7132,9 @@ function sessionSlice({ set, get, write }) {
|
|
|
7117
7132
|
setKeep: (k) => set({
|
|
7118
7133
|
kept: k.kept,
|
|
7119
7134
|
keepInterval: k.interval,
|
|
7120
|
-
stripeReady: k.stripe
|
|
7135
|
+
stripeReady: k.stripe,
|
|
7136
|
+
trialEndsAt: k.trialEndsAt ?? null,
|
|
7137
|
+
keepPaused: Boolean(k.paused)
|
|
7121
7138
|
}),
|
|
7122
7139
|
ensureAccountPerson: ({ accountId, name, personId }) => {
|
|
7123
7140
|
const hit = get().people.find((p) => p.accountId === accountId);
|
|
@@ -7189,7 +7206,7 @@ function sessionSlice({ set, get, write }) {
|
|
|
7189
7206
|
set({ selectedTaskId: id });
|
|
7190
7207
|
},
|
|
7191
7208
|
log: (text, href) => {
|
|
7192
|
-
if (
|
|
7209
|
+
if (readOnlyHarbor(get())) return;
|
|
7193
7210
|
set({ activity: [{
|
|
7194
7211
|
id: uid("a"),
|
|
7195
7212
|
personId: get().currentUserId,
|
package/lib/cli.mjs
CHANGED
|
@@ -174,8 +174,9 @@ export function hint(message) {
|
|
|
174
174
|
// zod's "Invalid option: expected one of "low"|"high"" reads as the choices.
|
|
175
175
|
const said = why
|
|
176
176
|
.join(": ")
|
|
177
|
-
.replace(
|
|
178
|
-
|
|
177
|
+
.replace(
|
|
178
|
+
/^Invalid option: expected one of (.*)$/,
|
|
179
|
+
(_, list) => `pick one of ${list.replace(/"/g, "").split("|").join(", ")}`,
|
|
179
180
|
);
|
|
180
181
|
if (!seen.has(flag)) seen.set(flag, said);
|
|
181
182
|
}
|
|
@@ -436,7 +437,11 @@ export async function run(argv, io = {}) {
|
|
|
436
437
|
if (!creds)
|
|
437
438
|
throw new HarborError("Not signed in. Run harbor login, or harbor signup if you're new.");
|
|
438
439
|
const client = createClient({ creds, save: (c) => writeCredentials(c, env), fetcher });
|
|
439
|
-
|
|
440
|
+
// A terminal's lines fit its width; piped output stays whole for scripts.
|
|
441
|
+
const opts = {
|
|
442
|
+
today: todayLocal(),
|
|
443
|
+
width: process.stdout.isTTY ? process.stdout.columns : undefined,
|
|
444
|
+
};
|
|
440
445
|
|
|
441
446
|
const call = async (name, args, show) => {
|
|
442
447
|
const result = await client.call(name, args).catch((err) => {
|
|
@@ -530,7 +535,7 @@ export async function run(argv, io = {}) {
|
|
|
530
535
|
const lines = [
|
|
531
536
|
taskLine(
|
|
532
537
|
{ ...t, waitingOn: t.waitingOn.filter((w) => w.status !== "done").length },
|
|
533
|
-
opts,
|
|
538
|
+
{ ...opts, width: undefined },
|
|
534
539
|
),
|
|
535
540
|
];
|
|
536
541
|
if (t.description) lines.push("", t.description);
|
package/lib/format.mjs
CHANGED
|
@@ -8,8 +8,12 @@ const STATUS = {
|
|
|
8
8
|
done: "done",
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `mine` leaves out whose each task is: every one is yours. `width` (the
|
|
13
|
+
* terminal's, when printing to one) shortens a long title so the line fits;
|
|
14
|
+
* piped output keeps every title whole.
|
|
15
|
+
*/
|
|
16
|
+
export function taskLine(t, { today, mine = false, width } = {}) {
|
|
13
17
|
const late = t.due && today && t.due < today && t.status !== "done";
|
|
14
18
|
const bits = [
|
|
15
19
|
STATUS[t.status] ?? t.status,
|
|
@@ -19,7 +23,21 @@ export function taskLine(t, { today, mine = false } = {}) {
|
|
|
19
23
|
t.project ?? null,
|
|
20
24
|
t.waitingOn ? `waits on ${t.waitingOn}` : null,
|
|
21
25
|
].filter(Boolean);
|
|
22
|
-
|
|
26
|
+
const key = `${t.key ?? t.id} `;
|
|
27
|
+
const tail = ` (${bits.join(" · ")})`;
|
|
28
|
+
return key + fitTitle(t.title, width ? width - 1 - key.length - tail.length : Infinity) + tail;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The title cut to `room` characters with an ellipsis, but never below 20. */
|
|
32
|
+
function fitTitle(title, room) {
|
|
33
|
+
const chars = [...title];
|
|
34
|
+
if (chars.length <= room) return title;
|
|
35
|
+
return (
|
|
36
|
+
chars
|
|
37
|
+
.slice(0, Math.max(20, room) - 1)
|
|
38
|
+
.join("")
|
|
39
|
+
.trimEnd() + "…"
|
|
40
|
+
);
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
export function myWork(w, opts) {
|