@ondrej-svec/hog 1.2.0 → 1.4.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/cli.js +2032 -956
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -9,70 +9,6 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/api.ts
|
|
13
|
-
var BASE_URL, TickTickClient;
|
|
14
|
-
var init_api = __esm({
|
|
15
|
-
"src/api.ts"() {
|
|
16
|
-
"use strict";
|
|
17
|
-
BASE_URL = "https://api.ticktick.com/open/v1";
|
|
18
|
-
TickTickClient = class {
|
|
19
|
-
token;
|
|
20
|
-
constructor(token) {
|
|
21
|
-
this.token = token;
|
|
22
|
-
}
|
|
23
|
-
async request(method, path, body) {
|
|
24
|
-
const url = `${BASE_URL}${path}`;
|
|
25
|
-
const init = {
|
|
26
|
-
method,
|
|
27
|
-
headers: {
|
|
28
|
-
Authorization: `Bearer ${this.token}`,
|
|
29
|
-
"Content-Type": "application/json"
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
if (body !== void 0) {
|
|
33
|
-
init.body = JSON.stringify(body);
|
|
34
|
-
}
|
|
35
|
-
const res = await fetch(url, init);
|
|
36
|
-
if (!res.ok) {
|
|
37
|
-
const text2 = await res.text();
|
|
38
|
-
throw new Error(`TickTick API error ${res.status}: ${text2}`);
|
|
39
|
-
}
|
|
40
|
-
const text = await res.text();
|
|
41
|
-
if (!text) return void 0;
|
|
42
|
-
return JSON.parse(text);
|
|
43
|
-
}
|
|
44
|
-
async listProjects() {
|
|
45
|
-
return this.request("GET", "/project");
|
|
46
|
-
}
|
|
47
|
-
async getProject(projectId) {
|
|
48
|
-
return this.request("GET", `/project/${projectId}`);
|
|
49
|
-
}
|
|
50
|
-
async getProjectData(projectId) {
|
|
51
|
-
return this.request("GET", `/project/${projectId}/data`);
|
|
52
|
-
}
|
|
53
|
-
async listTasks(projectId) {
|
|
54
|
-
const data = await this.getProjectData(projectId);
|
|
55
|
-
return data.tasks ?? [];
|
|
56
|
-
}
|
|
57
|
-
async getTask(projectId, taskId) {
|
|
58
|
-
return this.request("GET", `/project/${projectId}/task/${taskId}`);
|
|
59
|
-
}
|
|
60
|
-
async createTask(input2) {
|
|
61
|
-
return this.request("POST", "/task", input2);
|
|
62
|
-
}
|
|
63
|
-
async updateTask(input2) {
|
|
64
|
-
return this.request("POST", `/task/${input2.id}`, input2);
|
|
65
|
-
}
|
|
66
|
-
async completeTask(projectId, taskId) {
|
|
67
|
-
await this.request("POST", `/project/${projectId}/task/${taskId}/complete`);
|
|
68
|
-
}
|
|
69
|
-
async deleteTask(projectId, taskId) {
|
|
70
|
-
await this.request("DELETE", `/project/${projectId}/task/${taskId}`);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
12
|
// src/config.ts
|
|
77
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
78
14
|
import { homedir } from "os";
|
|
@@ -164,6 +100,29 @@ function getAuth() {
|
|
|
164
100
|
return null;
|
|
165
101
|
}
|
|
166
102
|
}
|
|
103
|
+
function saveAuth(data) {
|
|
104
|
+
ensureDir();
|
|
105
|
+
writeFileSync(AUTH_FILE, `${JSON.stringify(data, null, 2)}
|
|
106
|
+
`, {
|
|
107
|
+
mode: 384
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function getLlmAuth() {
|
|
111
|
+
const auth = getAuth();
|
|
112
|
+
if (auth?.openrouterApiKey) return { provider: "openrouter", apiKey: auth.openrouterApiKey };
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function saveLlmAuth(openrouterApiKey) {
|
|
116
|
+
const existing = getAuth();
|
|
117
|
+
const updated = existing ? { ...existing, openrouterApiKey } : { accessToken: "", clientId: "", clientSecret: "", openrouterApiKey };
|
|
118
|
+
saveAuth(updated);
|
|
119
|
+
}
|
|
120
|
+
function clearLlmAuth() {
|
|
121
|
+
const existing = getAuth();
|
|
122
|
+
if (!existing) return;
|
|
123
|
+
const { openrouterApiKey: _, ...rest } = existing;
|
|
124
|
+
saveAuth(rest);
|
|
125
|
+
}
|
|
167
126
|
function getConfig() {
|
|
168
127
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
169
128
|
try {
|
|
@@ -235,6 +194,231 @@ var init_config = __esm({
|
|
|
235
194
|
}
|
|
236
195
|
});
|
|
237
196
|
|
|
197
|
+
// src/ai.ts
|
|
198
|
+
async function parseHeuristic(input2, today = /* @__PURE__ */ new Date()) {
|
|
199
|
+
let remaining = input2;
|
|
200
|
+
const labelMatches = [...remaining.matchAll(/#([\w:/-]+)/g)];
|
|
201
|
+
const rawLabels = labelMatches.map((m) => (m[1] ?? "").toLowerCase());
|
|
202
|
+
remaining = remaining.replace(/#[\w:/-]+/g, "").trim();
|
|
203
|
+
const assigneeMatches = [...remaining.matchAll(/@([\w-]+)/g)];
|
|
204
|
+
const assignee = assigneeMatches.length > 0 ? assigneeMatches[assigneeMatches.length - 1]?.[1] ?? null : null;
|
|
205
|
+
remaining = remaining.replace(/@[\w-]+/g, "").trim();
|
|
206
|
+
let dueDate = null;
|
|
207
|
+
const dueMatch = remaining.match(/\bdue\s+(.+?)(?:\s+#|\s+@|$)/i);
|
|
208
|
+
if (dueMatch?.[1]) {
|
|
209
|
+
const { parse } = await import("chrono-node");
|
|
210
|
+
const results = parse(dueMatch[1], { instant: today }, { forwardDate: true });
|
|
211
|
+
const first = results[0];
|
|
212
|
+
if (first) {
|
|
213
|
+
let date = first.date();
|
|
214
|
+
if (date < today) {
|
|
215
|
+
date = new Date(date);
|
|
216
|
+
date.setFullYear(date.getFullYear() + 1);
|
|
217
|
+
}
|
|
218
|
+
const yyyy = date.getFullYear();
|
|
219
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
220
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
221
|
+
dueDate = `${yyyy}-${mm}-${dd}`;
|
|
222
|
+
}
|
|
223
|
+
remaining = remaining.slice(0, dueMatch.index ?? 0).trim();
|
|
224
|
+
}
|
|
225
|
+
const title = remaining.replace(/\s+/g, " ").trim();
|
|
226
|
+
if (!title) return null;
|
|
227
|
+
return { title, labels: rawLabels, assignee, dueDate };
|
|
228
|
+
}
|
|
229
|
+
function detectProvider() {
|
|
230
|
+
const orKey = process.env["OPENROUTER_API_KEY"];
|
|
231
|
+
if (orKey) return { provider: "openrouter", apiKey: orKey };
|
|
232
|
+
const antKey = process.env["ANTHROPIC_API_KEY"];
|
|
233
|
+
if (antKey) return { provider: "anthropic", apiKey: antKey };
|
|
234
|
+
return getLlmAuth();
|
|
235
|
+
}
|
|
236
|
+
async function callLLM(userText, validLabels, today, providerConfig) {
|
|
237
|
+
const { provider, apiKey } = providerConfig;
|
|
238
|
+
const todayStr = today.toISOString().slice(0, 10);
|
|
239
|
+
const systemPrompt = `Extract GitHub issue fields. Today is ${todayStr}. Return JSON with: title (string), labels (string[]), due_date (YYYY-MM-DD or null), assignee (string or null).`;
|
|
240
|
+
const escapedText = userText.replace(/<\/input>/gi, "< /input>");
|
|
241
|
+
const userContent = `<input>${escapedText}</input>
|
|
242
|
+
<valid_labels>${validLabels.join(",")}</valid_labels>`;
|
|
243
|
+
const jsonSchema = {
|
|
244
|
+
name: "issue",
|
|
245
|
+
schema: {
|
|
246
|
+
type: "object",
|
|
247
|
+
properties: {
|
|
248
|
+
title: { type: "string" },
|
|
249
|
+
labels: { type: "array", items: { type: "string" } },
|
|
250
|
+
due_date: { type: ["string", "null"] },
|
|
251
|
+
assignee: { type: ["string", "null"] }
|
|
252
|
+
},
|
|
253
|
+
required: ["title", "labels", "due_date", "assignee"],
|
|
254
|
+
additionalProperties: false
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
let response;
|
|
259
|
+
if (provider === "openrouter") {
|
|
260
|
+
response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Authorization: `Bearer ${apiKey}`
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({
|
|
267
|
+
model: "google/gemini-2.5-flash",
|
|
268
|
+
messages: [
|
|
269
|
+
{ role: "system", content: systemPrompt },
|
|
270
|
+
{ role: "user", content: userContent }
|
|
271
|
+
],
|
|
272
|
+
response_format: { type: "json_schema", json_schema: jsonSchema },
|
|
273
|
+
max_tokens: 256,
|
|
274
|
+
temperature: 0
|
|
275
|
+
}),
|
|
276
|
+
signal: AbortSignal.timeout(5e3)
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
"x-api-key": apiKey,
|
|
284
|
+
"anthropic-version": "2023-06-01"
|
|
285
|
+
},
|
|
286
|
+
body: JSON.stringify({
|
|
287
|
+
model: "claude-haiku-4-5-20251001",
|
|
288
|
+
system: systemPrompt,
|
|
289
|
+
messages: [{ role: "user", content: userContent }],
|
|
290
|
+
max_tokens: 256
|
|
291
|
+
}),
|
|
292
|
+
signal: AbortSignal.timeout(5e3)
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (!response.ok) return null;
|
|
296
|
+
const json = await response.json();
|
|
297
|
+
let raw;
|
|
298
|
+
if (provider === "openrouter") {
|
|
299
|
+
const choicesRaw = json["choices"];
|
|
300
|
+
if (!Array.isArray(choicesRaw)) return null;
|
|
301
|
+
const firstChoice = choicesRaw[0];
|
|
302
|
+
const content = firstChoice?.message?.content;
|
|
303
|
+
if (!content) return null;
|
|
304
|
+
raw = JSON.parse(content);
|
|
305
|
+
} else {
|
|
306
|
+
const contentRaw = json["content"];
|
|
307
|
+
if (!Array.isArray(contentRaw)) return null;
|
|
308
|
+
const firstItem = contentRaw[0];
|
|
309
|
+
const text = firstItem?.text;
|
|
310
|
+
if (!text) return null;
|
|
311
|
+
raw = JSON.parse(text);
|
|
312
|
+
}
|
|
313
|
+
if (!raw || typeof raw !== "object") return null;
|
|
314
|
+
const r = raw;
|
|
315
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
316
|
+
return {
|
|
317
|
+
title: typeof r["title"] === "string" ? r["title"] : "",
|
|
318
|
+
labels: Array.isArray(r["labels"]) ? r["labels"].filter((l) => typeof l === "string") : [],
|
|
319
|
+
due_date: typeof r["due_date"] === "string" && ISO_DATE_RE.test(r["due_date"]) ? r["due_date"] : null,
|
|
320
|
+
assignee: typeof r["assignee"] === "string" ? r["assignee"] : null
|
|
321
|
+
};
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function extractIssueFields(input2, options = {}) {
|
|
327
|
+
const today = options.today ?? /* @__PURE__ */ new Date();
|
|
328
|
+
const heuristic = await parseHeuristic(input2, today);
|
|
329
|
+
if (!heuristic) return null;
|
|
330
|
+
const providerConfig = detectProvider();
|
|
331
|
+
if (!providerConfig) return heuristic;
|
|
332
|
+
const llmResult = await callLLM(input2, options.validLabels ?? [], today, providerConfig);
|
|
333
|
+
if (!llmResult) {
|
|
334
|
+
options.onLlmFallback?.("AI parsing unavailable, used keyword matching");
|
|
335
|
+
return heuristic;
|
|
336
|
+
}
|
|
337
|
+
const merged = {
|
|
338
|
+
...llmResult,
|
|
339
|
+
// Heuristic explicit tokens always win
|
|
340
|
+
labels: heuristic.labels.length > 0 ? heuristic.labels : llmResult.labels,
|
|
341
|
+
assignee: heuristic.assignee ?? llmResult.assignee,
|
|
342
|
+
dueDate: heuristic.dueDate ?? llmResult.due_date,
|
|
343
|
+
// LLM title is used only if heuristic left explicit tokens
|
|
344
|
+
title: heuristic.labels.length > 0 || heuristic.assignee || heuristic.dueDate ? llmResult.title || heuristic.title : heuristic.title
|
|
345
|
+
};
|
|
346
|
+
return merged;
|
|
347
|
+
}
|
|
348
|
+
function hasLlmApiKey() {
|
|
349
|
+
return detectProvider() !== null;
|
|
350
|
+
}
|
|
351
|
+
var init_ai = __esm({
|
|
352
|
+
"src/ai.ts"() {
|
|
353
|
+
"use strict";
|
|
354
|
+
init_config();
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// src/api.ts
|
|
359
|
+
var BASE_URL, TickTickClient;
|
|
360
|
+
var init_api = __esm({
|
|
361
|
+
"src/api.ts"() {
|
|
362
|
+
"use strict";
|
|
363
|
+
BASE_URL = "https://api.ticktick.com/open/v1";
|
|
364
|
+
TickTickClient = class {
|
|
365
|
+
token;
|
|
366
|
+
constructor(token) {
|
|
367
|
+
this.token = token;
|
|
368
|
+
}
|
|
369
|
+
async request(method, path, body) {
|
|
370
|
+
const url = `${BASE_URL}${path}`;
|
|
371
|
+
const init = {
|
|
372
|
+
method,
|
|
373
|
+
headers: {
|
|
374
|
+
Authorization: `Bearer ${this.token}`,
|
|
375
|
+
"Content-Type": "application/json"
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
if (body !== void 0) {
|
|
379
|
+
init.body = JSON.stringify(body);
|
|
380
|
+
}
|
|
381
|
+
const res = await fetch(url, init);
|
|
382
|
+
if (!res.ok) {
|
|
383
|
+
const text2 = await res.text();
|
|
384
|
+
throw new Error(`TickTick API error ${res.status}: ${text2}`);
|
|
385
|
+
}
|
|
386
|
+
const text = await res.text();
|
|
387
|
+
if (!text) return void 0;
|
|
388
|
+
return JSON.parse(text);
|
|
389
|
+
}
|
|
390
|
+
async listProjects() {
|
|
391
|
+
return this.request("GET", "/project");
|
|
392
|
+
}
|
|
393
|
+
async getProject(projectId) {
|
|
394
|
+
return this.request("GET", `/project/${projectId}`);
|
|
395
|
+
}
|
|
396
|
+
async getProjectData(projectId) {
|
|
397
|
+
return this.request("GET", `/project/${projectId}/data`);
|
|
398
|
+
}
|
|
399
|
+
async listTasks(projectId) {
|
|
400
|
+
const data = await this.getProjectData(projectId);
|
|
401
|
+
return data.tasks ?? [];
|
|
402
|
+
}
|
|
403
|
+
async getTask(projectId, taskId) {
|
|
404
|
+
return this.request("GET", `/project/${projectId}/task/${taskId}`);
|
|
405
|
+
}
|
|
406
|
+
async createTask(input2) {
|
|
407
|
+
return this.request("POST", "/task", input2);
|
|
408
|
+
}
|
|
409
|
+
async updateTask(input2) {
|
|
410
|
+
return this.request("POST", `/task/${input2.id}`, input2);
|
|
411
|
+
}
|
|
412
|
+
async completeTask(projectId, taskId) {
|
|
413
|
+
await this.request("POST", `/project/${projectId}/task/${taskId}/complete`);
|
|
414
|
+
}
|
|
415
|
+
async deleteTask(projectId, taskId) {
|
|
416
|
+
await this.request("DELETE", `/project/${projectId}/task/${taskId}`);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
238
422
|
// src/types.ts
|
|
239
423
|
var init_types = __esm({
|
|
240
424
|
"src/types.ts"() {
|
|
@@ -461,6 +645,21 @@ function fetchProjectStatusOptions(repo, projectNumber, _statusFieldId) {
|
|
|
461
645
|
function addLabel(repo, issueNumber, label) {
|
|
462
646
|
runGh(["issue", "edit", String(issueNumber), "--repo", repo, "--add-label", label]);
|
|
463
647
|
}
|
|
648
|
+
async function fetchRepoLabelsAsync(repo) {
|
|
649
|
+
try {
|
|
650
|
+
const result = await runGhJsonAsync([
|
|
651
|
+
"label",
|
|
652
|
+
"list",
|
|
653
|
+
"--repo",
|
|
654
|
+
repo,
|
|
655
|
+
"--json",
|
|
656
|
+
"name,color"
|
|
657
|
+
]);
|
|
658
|
+
return Array.isArray(result) ? result : [];
|
|
659
|
+
} catch {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
662
|
+
}
|
|
464
663
|
function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
465
664
|
const [owner, repoName] = repo.split("/");
|
|
466
665
|
const findItemQuery = `
|
|
@@ -678,6 +877,21 @@ var init_sync_state = __esm({
|
|
|
678
877
|
}
|
|
679
878
|
});
|
|
680
879
|
|
|
880
|
+
// src/clipboard.ts
|
|
881
|
+
function getClipboardArgs() {
|
|
882
|
+
if (process.platform === "darwin") return ["pbcopy"];
|
|
883
|
+
if (process.platform === "win32") return ["clip"];
|
|
884
|
+
if (process.env["WSL_DISTRO_NAME"] ?? process.env["WSL_INTEROP"]) return ["clip.exe"];
|
|
885
|
+
if (process.env["WAYLAND_DISPLAY"]) return ["wl-copy"];
|
|
886
|
+
if (process.env["DISPLAY"]) return ["xsel", "--clipboard", "--input"];
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
var init_clipboard = __esm({
|
|
890
|
+
"src/clipboard.ts"() {
|
|
891
|
+
"use strict";
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
681
895
|
// src/pick.ts
|
|
682
896
|
var pick_exports = {};
|
|
683
897
|
__export(pick_exports, {
|
|
@@ -1034,6 +1248,26 @@ function useActions({
|
|
|
1034
1248
|
},
|
|
1035
1249
|
[toast, refresh, onOverlayDone]
|
|
1036
1250
|
);
|
|
1251
|
+
const handleLabelChange = useCallback(
|
|
1252
|
+
(addLabels, removeLabels) => {
|
|
1253
|
+
const ctx = findIssueContext(reposRef.current, selectedIdRef.current, configRef.current);
|
|
1254
|
+
if (!(ctx.issue && ctx.repoName)) return;
|
|
1255
|
+
const { issue, repoName } = ctx;
|
|
1256
|
+
const args = ["issue", "edit", String(issue.number), "--repo", repoName];
|
|
1257
|
+
for (const label of addLabels) args.push("--add-label", label);
|
|
1258
|
+
for (const label of removeLabels) args.push("--remove-label", label);
|
|
1259
|
+
const t = toast.loading("Updating labels...");
|
|
1260
|
+
execFileAsync2("gh", args, { encoding: "utf-8", timeout: 3e4 }).then(() => {
|
|
1261
|
+
t.resolve(`Labels updated on #${issue.number}`);
|
|
1262
|
+
refresh();
|
|
1263
|
+
onOverlayDone();
|
|
1264
|
+
}).catch((err) => {
|
|
1265
|
+
t.reject(`Label update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1266
|
+
onOverlayDone();
|
|
1267
|
+
});
|
|
1268
|
+
},
|
|
1269
|
+
[toast, refresh, onOverlayDone]
|
|
1270
|
+
);
|
|
1037
1271
|
const handleBulkAssign = useCallback(
|
|
1038
1272
|
async (ids) => {
|
|
1039
1273
|
const failed = [];
|
|
@@ -1164,6 +1398,7 @@ function useActions({
|
|
|
1164
1398
|
handleStatusChange,
|
|
1165
1399
|
handleAssign,
|
|
1166
1400
|
handleUnassign,
|
|
1401
|
+
handleLabelChange,
|
|
1167
1402
|
handleCreateIssue,
|
|
1168
1403
|
handleBulkAssign,
|
|
1169
1404
|
handleBulkUnassign,
|
|
@@ -1299,7 +1534,13 @@ function useData(config2, options, refreshIntervalMs) {
|
|
|
1299
1534
|
return { ...prev, data: fn(prev.data) };
|
|
1300
1535
|
});
|
|
1301
1536
|
}, []);
|
|
1302
|
-
|
|
1537
|
+
const pauseAutoRefresh = useCallback2(() => {
|
|
1538
|
+
setState((prev) => ({ ...prev, autoRefreshPaused: true }));
|
|
1539
|
+
}, []);
|
|
1540
|
+
const resumeAutoRefresh = useCallback2(() => {
|
|
1541
|
+
setState((prev) => ({ ...prev, autoRefreshPaused: false }));
|
|
1542
|
+
}, []);
|
|
1543
|
+
return { ...state, refresh, mutateData, pauseAutoRefresh, resumeAutoRefresh };
|
|
1303
1544
|
}
|
|
1304
1545
|
var INITIAL_STATE, STALE_THRESHOLDS, MAX_REFRESH_FAILURES;
|
|
1305
1546
|
var init_use_data = __esm({
|
|
@@ -1325,47 +1566,267 @@ var init_use_data = __esm({
|
|
|
1325
1566
|
}
|
|
1326
1567
|
});
|
|
1327
1568
|
|
|
1328
|
-
// src/board/hooks/use-
|
|
1329
|
-
import {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1569
|
+
// src/board/hooks/use-keyboard.ts
|
|
1570
|
+
import { useInput } from "ink";
|
|
1571
|
+
import { useCallback as useCallback3 } from "react";
|
|
1572
|
+
function isHeaderId(id) {
|
|
1573
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
1574
|
+
}
|
|
1575
|
+
function useKeyboard({
|
|
1576
|
+
ui,
|
|
1577
|
+
nav,
|
|
1578
|
+
multiSelect,
|
|
1579
|
+
selectedIssue,
|
|
1580
|
+
selectedRepoStatusOptionsLength,
|
|
1581
|
+
actions,
|
|
1582
|
+
onSearchEscape
|
|
1583
|
+
}) {
|
|
1584
|
+
const {
|
|
1585
|
+
exit,
|
|
1586
|
+
refresh,
|
|
1587
|
+
handleSlack,
|
|
1588
|
+
handleCopyLink,
|
|
1589
|
+
handleOpen,
|
|
1590
|
+
handleEnterFocus,
|
|
1591
|
+
handlePick,
|
|
1592
|
+
handleAssign,
|
|
1593
|
+
handleUnassign,
|
|
1594
|
+
handleEnterLabel,
|
|
1595
|
+
handleEnterCreateNl,
|
|
1596
|
+
handleErrorAction,
|
|
1597
|
+
toastInfo
|
|
1598
|
+
} = actions;
|
|
1599
|
+
const handleInput = useCallback3(
|
|
1600
|
+
(input2, key) => {
|
|
1601
|
+
if (input2 === "?") {
|
|
1602
|
+
ui.toggleHelp();
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (key.escape && ui.state.mode !== "focus") {
|
|
1606
|
+
if (ui.state.mode === "multiSelect") {
|
|
1607
|
+
multiSelect.clear();
|
|
1346
1608
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1609
|
+
ui.exitOverlay();
|
|
1610
|
+
return;
|
|
1349
1611
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1612
|
+
if (ui.canNavigate) {
|
|
1613
|
+
if (input2 === "j" || key.downArrow) {
|
|
1614
|
+
nav.moveDown();
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
if (input2 === "k" || key.upArrow) {
|
|
1618
|
+
nav.moveUp();
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
if (key.tab) {
|
|
1622
|
+
if (ui.state.mode === "multiSelect") {
|
|
1623
|
+
multiSelect.clear();
|
|
1624
|
+
ui.clearMultiSelect();
|
|
1625
|
+
}
|
|
1626
|
+
key.shift ? nav.prevSection() : nav.nextSection();
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1362
1629
|
}
|
|
1363
|
-
if (
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1630
|
+
if (ui.state.mode === "multiSelect") {
|
|
1631
|
+
if (input2 === " ") {
|
|
1632
|
+
const id = nav.selectedId;
|
|
1633
|
+
if (id && !isHeaderId(id)) {
|
|
1634
|
+
multiSelect.toggle(id);
|
|
1635
|
+
}
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
if (key.return) {
|
|
1639
|
+
if (multiSelect.count > 0) {
|
|
1640
|
+
ui.enterBulkAction();
|
|
1641
|
+
}
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (input2 === "m" && multiSelect.count > 0) {
|
|
1645
|
+
ui.enterBulkAction();
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (input2 === "d") {
|
|
1651
|
+
if (handleErrorAction("dismiss")) return;
|
|
1652
|
+
}
|
|
1653
|
+
if (input2 === "r" && handleErrorAction("retry")) return;
|
|
1654
|
+
if (ui.canAct) {
|
|
1655
|
+
if (input2 === "/") {
|
|
1656
|
+
multiSelect.clear();
|
|
1657
|
+
ui.enterSearch();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (input2 === "q") {
|
|
1661
|
+
exit();
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (input2 === "r" || input2 === "R") {
|
|
1665
|
+
multiSelect.clear();
|
|
1666
|
+
refresh();
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (input2 === "s") {
|
|
1670
|
+
handleSlack();
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (input2 === "y") {
|
|
1674
|
+
handleCopyLink();
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (input2 === "p") {
|
|
1678
|
+
handlePick();
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (input2 === "a") {
|
|
1682
|
+
handleAssign();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
if (input2 === "u") {
|
|
1686
|
+
handleUnassign();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (input2 === "c") {
|
|
1690
|
+
if (selectedIssue) {
|
|
1691
|
+
multiSelect.clear();
|
|
1692
|
+
ui.enterComment();
|
|
1693
|
+
}
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (input2 === "m") {
|
|
1697
|
+
if (selectedIssue && selectedRepoStatusOptionsLength > 0) {
|
|
1698
|
+
multiSelect.clear();
|
|
1699
|
+
ui.enterStatus();
|
|
1700
|
+
} else if (selectedIssue) {
|
|
1701
|
+
toastInfo("Issue not in a project board");
|
|
1702
|
+
}
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (input2 === "n") {
|
|
1706
|
+
multiSelect.clear();
|
|
1707
|
+
ui.enterCreate();
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (input2 === "f") {
|
|
1711
|
+
handleEnterFocus();
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (input2 === "C") {
|
|
1715
|
+
nav.collapseAll();
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
if (input2 === "l") {
|
|
1719
|
+
if (selectedIssue) {
|
|
1720
|
+
multiSelect.clear();
|
|
1721
|
+
handleEnterLabel();
|
|
1722
|
+
}
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (input2 === "I") {
|
|
1726
|
+
handleEnterCreateNl();
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (input2 === " ") {
|
|
1730
|
+
const id = nav.selectedId;
|
|
1731
|
+
if (id && !isHeaderId(id)) {
|
|
1732
|
+
multiSelect.toggle(id);
|
|
1733
|
+
ui.enterMultiSelect();
|
|
1734
|
+
} else if (isHeaderId(nav.selectedId)) {
|
|
1735
|
+
nav.toggleSection();
|
|
1736
|
+
}
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
if (key.return) {
|
|
1740
|
+
if (isHeaderId(nav.selectedId)) {
|
|
1741
|
+
nav.toggleSection();
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
handleOpen();
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
},
|
|
1749
|
+
[
|
|
1750
|
+
ui,
|
|
1751
|
+
nav,
|
|
1752
|
+
exit,
|
|
1753
|
+
refresh,
|
|
1754
|
+
handleSlack,
|
|
1755
|
+
handleCopyLink,
|
|
1756
|
+
handleOpen,
|
|
1757
|
+
handlePick,
|
|
1758
|
+
handleAssign,
|
|
1759
|
+
handleUnassign,
|
|
1760
|
+
handleEnterLabel,
|
|
1761
|
+
handleEnterCreateNl,
|
|
1762
|
+
selectedIssue,
|
|
1763
|
+
selectedRepoStatusOptionsLength,
|
|
1764
|
+
toastInfo,
|
|
1765
|
+
nav.selectedId,
|
|
1766
|
+
multiSelect,
|
|
1767
|
+
handleEnterFocus,
|
|
1768
|
+
handleErrorAction
|
|
1769
|
+
]
|
|
1770
|
+
);
|
|
1771
|
+
const inputActive = ui.state.mode === "normal" || ui.state.mode === "multiSelect" || ui.state.mode === "focus";
|
|
1772
|
+
useInput(handleInput, { isActive: inputActive });
|
|
1773
|
+
const handleSearchEscape = useCallback3(
|
|
1774
|
+
(_input, key) => {
|
|
1775
|
+
if (key.escape) {
|
|
1776
|
+
onSearchEscape();
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
[onSearchEscape]
|
|
1780
|
+
);
|
|
1781
|
+
useInput(handleSearchEscape, { isActive: ui.state.mode === "search" });
|
|
1782
|
+
}
|
|
1783
|
+
var init_use_keyboard = __esm({
|
|
1784
|
+
"src/board/hooks/use-keyboard.ts"() {
|
|
1785
|
+
"use strict";
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
// src/board/hooks/use-multi-select.ts
|
|
1790
|
+
import { useCallback as useCallback4, useRef as useRef3, useState as useState2 } from "react";
|
|
1791
|
+
function useMultiSelect(getRepoForId) {
|
|
1792
|
+
const [selected, setSelected] = useState2(/* @__PURE__ */ new Set());
|
|
1793
|
+
const repoRef = useRef3(null);
|
|
1794
|
+
const getRepoRef = useRef3(getRepoForId);
|
|
1795
|
+
getRepoRef.current = getRepoForId;
|
|
1796
|
+
const toggle = useCallback4((id) => {
|
|
1797
|
+
setSelected((prev) => {
|
|
1798
|
+
const repo = getRepoRef.current(id);
|
|
1799
|
+
if (!repo) return prev;
|
|
1800
|
+
const next = new Set(prev);
|
|
1801
|
+
if (next.has(id)) {
|
|
1802
|
+
next.delete(id);
|
|
1803
|
+
if (next.size === 0) repoRef.current = null;
|
|
1804
|
+
} else {
|
|
1805
|
+
if (repoRef.current && repoRef.current !== repo) {
|
|
1806
|
+
next.clear();
|
|
1807
|
+
}
|
|
1808
|
+
repoRef.current = repo;
|
|
1809
|
+
next.add(id);
|
|
1810
|
+
}
|
|
1811
|
+
return next;
|
|
1812
|
+
});
|
|
1813
|
+
}, []);
|
|
1814
|
+
const clear = useCallback4(() => {
|
|
1815
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
1816
|
+
repoRef.current = null;
|
|
1817
|
+
}, []);
|
|
1818
|
+
const prune = useCallback4((validIds) => {
|
|
1819
|
+
setSelected((prev) => {
|
|
1820
|
+
const next = /* @__PURE__ */ new Set();
|
|
1821
|
+
for (const id of prev) {
|
|
1822
|
+
if (validIds.has(id)) next.add(id);
|
|
1823
|
+
}
|
|
1824
|
+
if (next.size === prev.size) return prev;
|
|
1825
|
+
if (next.size === 0) repoRef.current = null;
|
|
1826
|
+
return next;
|
|
1827
|
+
});
|
|
1367
1828
|
}, []);
|
|
1368
|
-
const isSelected =
|
|
1829
|
+
const isSelected = useCallback4((id) => selected.has(id), [selected]);
|
|
1369
1830
|
return {
|
|
1370
1831
|
selected,
|
|
1371
1832
|
count: selected.size,
|
|
@@ -1383,7 +1844,7 @@ var init_use_multi_select = __esm({
|
|
|
1383
1844
|
});
|
|
1384
1845
|
|
|
1385
1846
|
// src/board/hooks/use-navigation.ts
|
|
1386
|
-
import { useCallback as
|
|
1847
|
+
import { useCallback as useCallback5, useMemo, useReducer, useRef as useRef4 } from "react";
|
|
1387
1848
|
function arraysEqual(a, b) {
|
|
1388
1849
|
if (a.length !== b.length) return false;
|
|
1389
1850
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -1405,7 +1866,7 @@ function navReducer(state, action) {
|
|
|
1405
1866
|
case "SET_ITEMS": {
|
|
1406
1867
|
const sections = [...new Set(action.items.map((i) => i.section))];
|
|
1407
1868
|
const isFirstLoad = state.sections.length === 0;
|
|
1408
|
-
const collapsedSections = isFirstLoad ? new Set(sections) : state.collapsedSections;
|
|
1869
|
+
const collapsedSections = isFirstLoad ? new Set(sections.filter((s) => s === "activity")) : state.collapsedSections;
|
|
1409
1870
|
const selectionValid = state.selectedId != null && action.items.some((i) => i.id === state.selectedId);
|
|
1410
1871
|
if (!isFirstLoad && selectionValid && arraysEqual(sections, state.sections)) {
|
|
1411
1872
|
return state;
|
|
@@ -1443,6 +1904,9 @@ function navReducer(state, action) {
|
|
|
1443
1904
|
}
|
|
1444
1905
|
return { ...state, collapsedSections: next };
|
|
1445
1906
|
}
|
|
1907
|
+
case "COLLAPSE_ALL": {
|
|
1908
|
+
return { ...state, collapsedSections: new Set(state.sections) };
|
|
1909
|
+
}
|
|
1446
1910
|
default:
|
|
1447
1911
|
return state;
|
|
1448
1912
|
}
|
|
@@ -1477,17 +1941,17 @@ function useNavigation(allItems) {
|
|
|
1477
1941
|
const idx = visibleItems.findIndex((i) => i.id === state.selectedId);
|
|
1478
1942
|
return idx >= 0 ? idx : 0;
|
|
1479
1943
|
}, [state.selectedId, visibleItems]);
|
|
1480
|
-
const moveUp =
|
|
1944
|
+
const moveUp = useCallback5(() => {
|
|
1481
1945
|
const newIdx = Math.max(0, selectedIndex - 1);
|
|
1482
1946
|
const item = visibleItems[newIdx];
|
|
1483
1947
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1484
1948
|
}, [selectedIndex, visibleItems]);
|
|
1485
|
-
const moveDown =
|
|
1949
|
+
const moveDown = useCallback5(() => {
|
|
1486
1950
|
const newIdx = Math.min(visibleItems.length - 1, selectedIndex + 1);
|
|
1487
1951
|
const item = visibleItems[newIdx];
|
|
1488
1952
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
1489
1953
|
}, [selectedIndex, visibleItems]);
|
|
1490
|
-
const nextSection =
|
|
1954
|
+
const nextSection = useCallback5(() => {
|
|
1491
1955
|
const currentItem = visibleItems[selectedIndex];
|
|
1492
1956
|
if (!currentItem) return;
|
|
1493
1957
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1496,7 +1960,7 @@ function useNavigation(allItems) {
|
|
|
1496
1960
|
const header = visibleItems.find((i) => i.section === nextSectionId && i.type === "header");
|
|
1497
1961
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1498
1962
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1499
|
-
const prevSection =
|
|
1963
|
+
const prevSection = useCallback5(() => {
|
|
1500
1964
|
const currentItem = visibleItems[selectedIndex];
|
|
1501
1965
|
if (!currentItem) return;
|
|
1502
1966
|
const currentSectionIdx = state.sections.indexOf(currentItem.section);
|
|
@@ -1505,19 +1969,22 @@ function useNavigation(allItems) {
|
|
|
1505
1969
|
const header = visibleItems.find((i) => i.section === prevSectionId && i.type === "header");
|
|
1506
1970
|
if (header) dispatch({ type: "SELECT", id: header.id, section: header.section });
|
|
1507
1971
|
}, [selectedIndex, visibleItems, state.sections]);
|
|
1508
|
-
const toggleSection =
|
|
1972
|
+
const toggleSection = useCallback5(() => {
|
|
1509
1973
|
const currentItem = visibleItems[selectedIndex];
|
|
1510
1974
|
if (!currentItem) return;
|
|
1511
1975
|
const key = currentItem.type === "subHeader" ? currentItem.id : currentItem.section;
|
|
1512
1976
|
dispatch({ type: "TOGGLE_SECTION", section: key });
|
|
1513
1977
|
}, [selectedIndex, visibleItems]);
|
|
1978
|
+
const collapseAll = useCallback5(() => {
|
|
1979
|
+
dispatch({ type: "COLLAPSE_ALL" });
|
|
1980
|
+
}, []);
|
|
1514
1981
|
const allItemsRef = useRef4(allItems);
|
|
1515
1982
|
allItemsRef.current = allItems;
|
|
1516
|
-
const select2 =
|
|
1983
|
+
const select2 = useCallback5((id) => {
|
|
1517
1984
|
const item = allItemsRef.current.find((i) => i.id === id);
|
|
1518
1985
|
dispatch({ type: "SELECT", id, section: item?.section });
|
|
1519
1986
|
}, []);
|
|
1520
|
-
const isCollapsed =
|
|
1987
|
+
const isCollapsed = useCallback5(
|
|
1521
1988
|
(section) => state.collapsedSections.has(section),
|
|
1522
1989
|
[state.collapsedSections]
|
|
1523
1990
|
);
|
|
@@ -1530,6 +1997,7 @@ function useNavigation(allItems) {
|
|
|
1530
1997
|
nextSection,
|
|
1531
1998
|
prevSection,
|
|
1532
1999
|
toggleSection,
|
|
2000
|
+
collapseAll,
|
|
1533
2001
|
select: select2,
|
|
1534
2002
|
isCollapsed
|
|
1535
2003
|
};
|
|
@@ -1541,25 +2009,25 @@ var init_use_navigation = __esm({
|
|
|
1541
2009
|
});
|
|
1542
2010
|
|
|
1543
2011
|
// src/board/hooks/use-toast.ts
|
|
1544
|
-
import { useCallback as
|
|
2012
|
+
import { useCallback as useCallback6, useRef as useRef5, useState as useState3 } from "react";
|
|
1545
2013
|
function useToast() {
|
|
1546
2014
|
const [toasts, setToasts] = useState3([]);
|
|
1547
2015
|
const timersRef = useRef5(/* @__PURE__ */ new Map());
|
|
1548
|
-
const clearTimer =
|
|
2016
|
+
const clearTimer = useCallback6((id) => {
|
|
1549
2017
|
const timer = timersRef.current.get(id);
|
|
1550
2018
|
if (timer) {
|
|
1551
2019
|
clearTimeout(timer);
|
|
1552
2020
|
timersRef.current.delete(id);
|
|
1553
2021
|
}
|
|
1554
2022
|
}, []);
|
|
1555
|
-
const removeToast =
|
|
2023
|
+
const removeToast = useCallback6(
|
|
1556
2024
|
(id) => {
|
|
1557
2025
|
clearTimer(id);
|
|
1558
2026
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1559
2027
|
},
|
|
1560
2028
|
[clearTimer]
|
|
1561
2029
|
);
|
|
1562
|
-
const addToast =
|
|
2030
|
+
const addToast = useCallback6(
|
|
1563
2031
|
(t) => {
|
|
1564
2032
|
const id = `toast-${++nextId}`;
|
|
1565
2033
|
const newToast = { ...t, id, createdAt: Date.now() };
|
|
@@ -1588,25 +2056,25 @@ function useToast() {
|
|
|
1588
2056
|
[removeToast, clearTimer]
|
|
1589
2057
|
);
|
|
1590
2058
|
const toast = {
|
|
1591
|
-
info:
|
|
2059
|
+
info: useCallback6(
|
|
1592
2060
|
(message) => {
|
|
1593
2061
|
addToast({ type: "info", message });
|
|
1594
2062
|
},
|
|
1595
2063
|
[addToast]
|
|
1596
2064
|
),
|
|
1597
|
-
success:
|
|
2065
|
+
success: useCallback6(
|
|
1598
2066
|
(message) => {
|
|
1599
2067
|
addToast({ type: "success", message });
|
|
1600
2068
|
},
|
|
1601
2069
|
[addToast]
|
|
1602
2070
|
),
|
|
1603
|
-
error:
|
|
2071
|
+
error: useCallback6(
|
|
1604
2072
|
(message, retry) => {
|
|
1605
2073
|
addToast(retry ? { type: "error", message, retry } : { type: "error", message });
|
|
1606
2074
|
},
|
|
1607
2075
|
[addToast]
|
|
1608
2076
|
),
|
|
1609
|
-
loading:
|
|
2077
|
+
loading: useCallback6(
|
|
1610
2078
|
(message) => {
|
|
1611
2079
|
const id = addToast({ type: "loading", message });
|
|
1612
2080
|
return {
|
|
@@ -1623,20 +2091,20 @@ function useToast() {
|
|
|
1623
2091
|
[addToast, removeToast]
|
|
1624
2092
|
)
|
|
1625
2093
|
};
|
|
1626
|
-
const dismiss =
|
|
2094
|
+
const dismiss = useCallback6(
|
|
1627
2095
|
(id) => {
|
|
1628
2096
|
removeToast(id);
|
|
1629
2097
|
},
|
|
1630
2098
|
[removeToast]
|
|
1631
2099
|
);
|
|
1632
|
-
const dismissAll =
|
|
2100
|
+
const dismissAll = useCallback6(() => {
|
|
1633
2101
|
for (const timer of timersRef.current.values()) {
|
|
1634
2102
|
clearTimeout(timer);
|
|
1635
2103
|
}
|
|
1636
2104
|
timersRef.current.clear();
|
|
1637
2105
|
setToasts([]);
|
|
1638
2106
|
}, []);
|
|
1639
|
-
const handleErrorAction =
|
|
2107
|
+
const handleErrorAction = useCallback6(
|
|
1640
2108
|
(action) => {
|
|
1641
2109
|
const errorToast = toasts.find((t) => t.type === "error");
|
|
1642
2110
|
if (!errorToast) return false;
|
|
@@ -1666,7 +2134,7 @@ var init_use_toast = __esm({
|
|
|
1666
2134
|
});
|
|
1667
2135
|
|
|
1668
2136
|
// src/board/hooks/use-ui-state.ts
|
|
1669
|
-
import { useCallback as
|
|
2137
|
+
import { useCallback as useCallback7, useReducer as useReducer2 } from "react";
|
|
1670
2138
|
function uiReducer(state, action) {
|
|
1671
2139
|
switch (action.type) {
|
|
1672
2140
|
case "ENTER_SEARCH":
|
|
@@ -1685,6 +2153,12 @@ function uiReducer(state, action) {
|
|
|
1685
2153
|
case "ENTER_CREATE":
|
|
1686
2154
|
if (state.mode !== "normal") return state;
|
|
1687
2155
|
return { ...state, mode: "overlay:create", previousMode: "normal" };
|
|
2156
|
+
case "ENTER_CREATE_NL":
|
|
2157
|
+
if (state.mode !== "normal") return state;
|
|
2158
|
+
return { ...state, mode: "overlay:createNl", previousMode: "normal" };
|
|
2159
|
+
case "ENTER_LABEL":
|
|
2160
|
+
if (state.mode !== "normal") return state;
|
|
2161
|
+
return { ...state, mode: "overlay:label", previousMode: "normal" };
|
|
1688
2162
|
case "ENTER_MULTI_SELECT":
|
|
1689
2163
|
if (state.mode !== "normal" && state.mode !== "multiSelect") return state;
|
|
1690
2164
|
return { ...state, mode: "multiSelect", previousMode: "normal" };
|
|
@@ -1728,18 +2202,20 @@ function useUIState() {
|
|
|
1728
2202
|
const [state, dispatch] = useReducer2(uiReducer, INITIAL_STATE2);
|
|
1729
2203
|
return {
|
|
1730
2204
|
state,
|
|
1731
|
-
enterSearch:
|
|
1732
|
-
enterComment:
|
|
1733
|
-
enterStatus:
|
|
1734
|
-
enterCreate:
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2205
|
+
enterSearch: useCallback7(() => dispatch({ type: "ENTER_SEARCH" }), []),
|
|
2206
|
+
enterComment: useCallback7(() => dispatch({ type: "ENTER_COMMENT" }), []),
|
|
2207
|
+
enterStatus: useCallback7(() => dispatch({ type: "ENTER_STATUS" }), []),
|
|
2208
|
+
enterCreate: useCallback7(() => dispatch({ type: "ENTER_CREATE" }), []),
|
|
2209
|
+
enterCreateNl: useCallback7(() => dispatch({ type: "ENTER_CREATE_NL" }), []),
|
|
2210
|
+
enterLabel: useCallback7(() => dispatch({ type: "ENTER_LABEL" }), []),
|
|
2211
|
+
enterMultiSelect: useCallback7(() => dispatch({ type: "ENTER_MULTI_SELECT" }), []),
|
|
2212
|
+
enterBulkAction: useCallback7(() => dispatch({ type: "ENTER_BULK_ACTION" }), []),
|
|
2213
|
+
enterConfirmPick: useCallback7(() => dispatch({ type: "ENTER_CONFIRM_PICK" }), []),
|
|
2214
|
+
enterFocus: useCallback7(() => dispatch({ type: "ENTER_FOCUS" }), []),
|
|
2215
|
+
toggleHelp: useCallback7(() => dispatch({ type: "TOGGLE_HELP" }), []),
|
|
2216
|
+
exitOverlay: useCallback7(() => dispatch({ type: "EXIT_OVERLAY" }), []),
|
|
2217
|
+
exitToNormal: useCallback7(() => dispatch({ type: "EXIT_TO_NORMAL" }), []),
|
|
2218
|
+
clearMultiSelect: useCallback7(() => dispatch({ type: "CLEAR_MULTI_SELECT" }), []),
|
|
1743
2219
|
canNavigate: canNavigate(state),
|
|
1744
2220
|
canAct: canAct(state),
|
|
1745
2221
|
isOverlay: isOverlay(state)
|
|
@@ -1757,93 +2233,347 @@ var init_use_ui_state = __esm({
|
|
|
1757
2233
|
}
|
|
1758
2234
|
});
|
|
1759
2235
|
|
|
1760
|
-
// src/board/components/
|
|
1761
|
-
import { Box, Text
|
|
1762
|
-
import {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
return [
|
|
1767
|
-
{ label: "Assign all to me", action: { type: "assign" } },
|
|
1768
|
-
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
1769
|
-
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
1770
|
-
];
|
|
1771
|
-
}
|
|
1772
|
-
if (selectionType === "ticktick") {
|
|
1773
|
-
return [
|
|
1774
|
-
{ label: "Complete all", action: { type: "complete" } },
|
|
1775
|
-
{ label: "Delete all", action: { type: "delete" } }
|
|
1776
|
-
];
|
|
1777
|
-
}
|
|
1778
|
-
return [];
|
|
2236
|
+
// src/board/components/detail-panel.tsx
|
|
2237
|
+
import { Box, Text } from "ink";
|
|
2238
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2239
|
+
function truncateLines(text, maxLines) {
|
|
2240
|
+
const lines = text.split("\n").slice(0, maxLines);
|
|
2241
|
+
return lines.join("\n");
|
|
1779
2242
|
}
|
|
1780
|
-
function
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
"
|
|
1806
|
-
|
|
1807
|
-
"
|
|
1808
|
-
] })
|
|
1809
|
-
items.map((item, i) => {
|
|
1810
|
-
const isSelected = i === selectedIdx;
|
|
1811
|
-
const prefix = isSelected ? "> " : " ";
|
|
1812
|
-
return /* @__PURE__ */ jsxs(Text, { ...isSelected ? { color: "cyan" } : {}, children: [
|
|
1813
|
-
prefix,
|
|
1814
|
-
item.label
|
|
1815
|
-
] }, item.action.type);
|
|
1816
|
-
}),
|
|
1817
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
2243
|
+
function stripMarkdown(text) {
|
|
2244
|
+
return text.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, (m) => m.replace(/`/g, "")).replace(/^\s*[-*+]\s+/gm, " - ").replace(/^\s*\d+\.\s+/gm, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "[$1]").replace(/^>\s+/gm, " ").replace(/---+/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
2245
|
+
}
|
|
2246
|
+
function formatBody(body, maxLines) {
|
|
2247
|
+
const plain = stripMarkdown(body);
|
|
2248
|
+
const lines = plain.split("\n");
|
|
2249
|
+
const truncated = lines.slice(0, maxLines).join("\n");
|
|
2250
|
+
return { text: truncated, remaining: Math.max(0, lines.length - maxLines) };
|
|
2251
|
+
}
|
|
2252
|
+
function countSlackLinks(body) {
|
|
2253
|
+
if (!body) return 0;
|
|
2254
|
+
return (body.match(SLACK_URL_RE) ?? []).length;
|
|
2255
|
+
}
|
|
2256
|
+
function BodySection({
|
|
2257
|
+
body,
|
|
2258
|
+
issueNumber
|
|
2259
|
+
}) {
|
|
2260
|
+
const { text, remaining } = formatBody(body, 15);
|
|
2261
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2262
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2263
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "--- Description ---" }),
|
|
2264
|
+
/* @__PURE__ */ jsx(Text, { wrap: "wrap", children: text }),
|
|
2265
|
+
remaining > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2266
|
+
"... (",
|
|
2267
|
+
remaining,
|
|
2268
|
+
" more lines \u2014 gh issue view ",
|
|
2269
|
+
issueNumber,
|
|
2270
|
+
" for full)"
|
|
2271
|
+
] }) : null
|
|
1818
2272
|
] });
|
|
1819
2273
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2274
|
+
function DetailPanel({ issue, task: task2, width }) {
|
|
2275
|
+
if (!(issue || task2)) {
|
|
2276
|
+
return /* @__PURE__ */ jsx(
|
|
2277
|
+
Box,
|
|
2278
|
+
{
|
|
2279
|
+
width,
|
|
2280
|
+
borderStyle: "single",
|
|
2281
|
+
borderColor: "gray",
|
|
2282
|
+
flexDirection: "column",
|
|
2283
|
+
paddingX: 1,
|
|
2284
|
+
children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "No item selected" })
|
|
2285
|
+
}
|
|
2286
|
+
);
|
|
1823
2287
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2288
|
+
if (issue) {
|
|
2289
|
+
return /* @__PURE__ */ jsxs(
|
|
2290
|
+
Box,
|
|
2291
|
+
{
|
|
2292
|
+
width,
|
|
2293
|
+
borderStyle: "single",
|
|
2294
|
+
borderColor: "cyan",
|
|
2295
|
+
flexDirection: "column",
|
|
2296
|
+
paddingX: 1,
|
|
2297
|
+
children: [
|
|
2298
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
2299
|
+
"#",
|
|
2300
|
+
issue.number,
|
|
2301
|
+
" ",
|
|
2302
|
+
issue.title
|
|
2303
|
+
] }),
|
|
2304
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2305
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2306
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "State: " }),
|
|
2307
|
+
/* @__PURE__ */ jsx(Text, { color: issue.state === "open" ? "green" : "red", children: issue.state })
|
|
2308
|
+
] }),
|
|
2309
|
+
(issue.assignees ?? []).length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2310
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Assignees: " }),
|
|
2311
|
+
/* @__PURE__ */ jsx(Text, { children: (issue.assignees ?? []).map((a) => a.login).join(", ") })
|
|
2312
|
+
] }) : null,
|
|
2313
|
+
issue.labels.length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2314
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Labels: " }),
|
|
2315
|
+
/* @__PURE__ */ jsx(Text, { children: issue.labels.map((l) => l.name).join(", ") })
|
|
2316
|
+
] }) : null,
|
|
2317
|
+
issue.projectStatus ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2318
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
|
|
2319
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: issue.projectStatus })
|
|
2320
|
+
] }) : null,
|
|
2321
|
+
issue.targetDate ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2322
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Target: " }),
|
|
2323
|
+
/* @__PURE__ */ jsx(Text, { children: issue.targetDate })
|
|
2324
|
+
] }) : null,
|
|
2325
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2326
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Updated: " }),
|
|
2327
|
+
/* @__PURE__ */ jsx(Text, { children: new Date(issue.updatedAt).toLocaleString() })
|
|
2328
|
+
] }),
|
|
2329
|
+
issue.slackThreadUrl ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2330
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Slack: " }),
|
|
2331
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", children: countSlackLinks(issue.body) > 1 ? `${countSlackLinks(issue.body)} links (s opens first)` : "thread (s to open)" })
|
|
2332
|
+
] }) : null,
|
|
2333
|
+
issue.body ? /* @__PURE__ */ jsx(BodySection, { body: issue.body, issueNumber: issue.number }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2334
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2335
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "(no description)" })
|
|
2336
|
+
] }),
|
|
2337
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2338
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: issue.url })
|
|
2339
|
+
]
|
|
2340
|
+
}
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
const t = task2;
|
|
2344
|
+
return /* @__PURE__ */ jsxs(
|
|
2345
|
+
Box,
|
|
2346
|
+
{
|
|
2347
|
+
width,
|
|
2348
|
+
borderStyle: "single",
|
|
2349
|
+
borderColor: "yellow",
|
|
2350
|
+
flexDirection: "column",
|
|
2351
|
+
paddingX: 1,
|
|
2352
|
+
children: [
|
|
2353
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: t.title }),
|
|
2354
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2355
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2356
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Priority: " }),
|
|
2357
|
+
/* @__PURE__ */ jsx(Text, { children: PRIORITY_LABELS2[t.priority] ?? "None" })
|
|
2358
|
+
] }),
|
|
2359
|
+
t.dueDate ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2360
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Due: " }),
|
|
2361
|
+
/* @__PURE__ */ jsx(Text, { children: new Date(t.dueDate).toLocaleDateString() })
|
|
2362
|
+
] }) : null,
|
|
2363
|
+
(t.tags ?? []).length > 0 ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2364
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Tags: " }),
|
|
2365
|
+
/* @__PURE__ */ jsx(Text, { children: t.tags.join(", ") })
|
|
2366
|
+
] }) : null,
|
|
2367
|
+
t.content ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2368
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2369
|
+
/* @__PURE__ */ jsx(Text, { children: truncateLines(t.content, 8) })
|
|
2370
|
+
] }) : null,
|
|
2371
|
+
(t.items ?? []).length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2372
|
+
/* @__PURE__ */ jsx(Text, { children: "" }),
|
|
2373
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Checklist:" }),
|
|
2374
|
+
t.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
2375
|
+
item.status === 2 ? "\u2611" : "\u2610",
|
|
2376
|
+
" ",
|
|
2377
|
+
item.title
|
|
2378
|
+
] }, item.id)),
|
|
2379
|
+
t.items.length > 5 ? /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2380
|
+
"...and ",
|
|
2381
|
+
t.items.length - 5,
|
|
2382
|
+
" more"
|
|
2383
|
+
] }) : null
|
|
2384
|
+
] }) : null
|
|
2385
|
+
]
|
|
2386
|
+
}
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
var SLACK_URL_RE, PRIORITY_LABELS2;
|
|
2390
|
+
var init_detail_panel = __esm({
|
|
2391
|
+
"src/board/components/detail-panel.tsx"() {
|
|
2392
|
+
"use strict";
|
|
2393
|
+
init_types();
|
|
2394
|
+
SLACK_URL_RE = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/gi;
|
|
2395
|
+
PRIORITY_LABELS2 = {
|
|
2396
|
+
[5 /* High */]: "High",
|
|
2397
|
+
[3 /* Medium */]: "Medium",
|
|
2398
|
+
[1 /* Low */]: "Low",
|
|
2399
|
+
[0 /* None */]: "None"
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2403
|
+
|
|
2404
|
+
// src/board/components/bulk-action-menu.tsx
|
|
1828
2405
|
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
1829
|
-
import { useState as
|
|
2406
|
+
import { useState as useState4 } from "react";
|
|
1830
2407
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1831
|
-
function
|
|
2408
|
+
function getMenuItems(selectionType) {
|
|
2409
|
+
if (selectionType === "github") {
|
|
2410
|
+
return [
|
|
2411
|
+
{ label: "Assign all to me", action: { type: "assign" } },
|
|
2412
|
+
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
2413
|
+
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
2414
|
+
];
|
|
2415
|
+
}
|
|
2416
|
+
if (selectionType === "ticktick") {
|
|
2417
|
+
return [
|
|
2418
|
+
{ label: "Complete all", action: { type: "complete" } },
|
|
2419
|
+
{ label: "Delete all", action: { type: "delete" } }
|
|
2420
|
+
];
|
|
2421
|
+
}
|
|
2422
|
+
return [];
|
|
2423
|
+
}
|
|
2424
|
+
function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
2425
|
+
const items = getMenuItems(selectionType);
|
|
2426
|
+
const [selectedIdx, setSelectedIdx] = useState4(0);
|
|
2427
|
+
useInput2((input2, key) => {
|
|
2428
|
+
if (key.escape) return onCancel();
|
|
2429
|
+
if (key.return) {
|
|
2430
|
+
const item = items[selectedIdx];
|
|
2431
|
+
if (item) onSelect(item.action);
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
if (input2 === "j" || key.downArrow) {
|
|
2435
|
+
setSelectedIdx((i) => Math.min(i + 1, items.length - 1));
|
|
2436
|
+
}
|
|
2437
|
+
if (input2 === "k" || key.upArrow) {
|
|
2438
|
+
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
if (items.length === 0) {
|
|
2442
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
2443
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "No bulk actions for mixed selection types." }),
|
|
2444
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Esc to cancel" })
|
|
2445
|
+
] });
|
|
2446
|
+
}
|
|
2447
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
2448
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
|
|
2449
|
+
"Bulk action (",
|
|
2450
|
+
count,
|
|
2451
|
+
" selected):"
|
|
2452
|
+
] }),
|
|
2453
|
+
items.map((item, i) => {
|
|
2454
|
+
const isSelected = i === selectedIdx;
|
|
2455
|
+
const prefix = isSelected ? "> " : " ";
|
|
2456
|
+
return /* @__PURE__ */ jsxs2(Text2, { ...isSelected ? { color: "cyan" } : {}, children: [
|
|
2457
|
+
prefix,
|
|
2458
|
+
item.label
|
|
2459
|
+
] }, item.action.type);
|
|
2460
|
+
}),
|
|
2461
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
2462
|
+
] });
|
|
2463
|
+
}
|
|
2464
|
+
var init_bulk_action_menu = __esm({
|
|
2465
|
+
"src/board/components/bulk-action-menu.tsx"() {
|
|
2466
|
+
"use strict";
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
|
|
2470
|
+
// src/board/ink-instance.ts
|
|
2471
|
+
function setInkInstance(instance) {
|
|
2472
|
+
_instance = instance;
|
|
2473
|
+
}
|
|
2474
|
+
function getInkInstance() {
|
|
2475
|
+
return _instance;
|
|
2476
|
+
}
|
|
2477
|
+
var _instance;
|
|
2478
|
+
var init_ink_instance = __esm({
|
|
2479
|
+
"src/board/ink-instance.ts"() {
|
|
2480
|
+
"use strict";
|
|
2481
|
+
_instance = null;
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
// src/board/components/comment-input.tsx
|
|
2486
|
+
import { spawnSync } from "child_process";
|
|
2487
|
+
import { mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2488
|
+
import { tmpdir } from "os";
|
|
2489
|
+
import { join as join3 } from "path";
|
|
2490
|
+
import { TextInput } from "@inkjs/ui";
|
|
2491
|
+
import { Box as Box3, Text as Text3, useInput as useInput3, useStdin } from "ink";
|
|
2492
|
+
import { useEffect as useEffect2, useRef as useRef6, useState as useState5 } from "react";
|
|
2493
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2494
|
+
function CommentInput({
|
|
2495
|
+
issueNumber,
|
|
2496
|
+
onSubmit,
|
|
2497
|
+
onCancel,
|
|
2498
|
+
onPauseRefresh,
|
|
2499
|
+
onResumeRefresh
|
|
2500
|
+
}) {
|
|
1832
2501
|
const [value, setValue] = useState5("");
|
|
1833
|
-
|
|
1834
|
-
|
|
2502
|
+
const [editing, setEditing] = useState5(false);
|
|
2503
|
+
const { setRawMode } = useStdin();
|
|
2504
|
+
const onSubmitRef = useRef6(onSubmit);
|
|
2505
|
+
const onCancelRef = useRef6(onCancel);
|
|
2506
|
+
const onPauseRef = useRef6(onPauseRefresh);
|
|
2507
|
+
const onResumeRef = useRef6(onResumeRefresh);
|
|
2508
|
+
onSubmitRef.current = onSubmit;
|
|
2509
|
+
onCancelRef.current = onCancel;
|
|
2510
|
+
onPauseRef.current = onPauseRefresh;
|
|
2511
|
+
onResumeRef.current = onResumeRefresh;
|
|
2512
|
+
useInput3((_input, key) => {
|
|
2513
|
+
if (editing) return;
|
|
2514
|
+
if (key.escape) {
|
|
2515
|
+
onCancel();
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
if (_input === "") {
|
|
2519
|
+
setEditing(true);
|
|
2520
|
+
}
|
|
1835
2521
|
});
|
|
1836
|
-
|
|
1837
|
-
|
|
2522
|
+
useEffect2(() => {
|
|
2523
|
+
if (!editing) return;
|
|
2524
|
+
const editorEnv = process.env["VISUAL"] ?? process.env["EDITOR"] ?? "vi";
|
|
2525
|
+
const [cmd, ...extraArgs] = editorEnv.split(" ").filter(Boolean);
|
|
2526
|
+
if (!cmd) {
|
|
2527
|
+
setEditing(false);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
let tmpDir = null;
|
|
2531
|
+
let tmpFile = null;
|
|
2532
|
+
try {
|
|
2533
|
+
onPauseRef.current?.();
|
|
2534
|
+
tmpDir = mkdtempSync(join3(tmpdir(), "hog-comment-"));
|
|
2535
|
+
tmpFile = join3(tmpDir, "comment.md");
|
|
2536
|
+
writeFileSync3(tmpFile, value);
|
|
2537
|
+
const inkInstance = getInkInstance();
|
|
2538
|
+
inkInstance?.clear();
|
|
2539
|
+
setRawMode(false);
|
|
2540
|
+
spawnSync(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
2541
|
+
const content = readFileSync3(tmpFile, "utf-8").trim();
|
|
2542
|
+
setRawMode(true);
|
|
2543
|
+
if (content) {
|
|
2544
|
+
onSubmitRef.current(content);
|
|
2545
|
+
} else {
|
|
2546
|
+
onCancelRef.current();
|
|
2547
|
+
}
|
|
2548
|
+
} finally {
|
|
2549
|
+
onResumeRef.current?.();
|
|
2550
|
+
if (tmpFile) {
|
|
2551
|
+
try {
|
|
2552
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
setEditing(false);
|
|
2557
|
+
}
|
|
2558
|
+
}, [editing, value, setRawMode]);
|
|
2559
|
+
if (editing) {
|
|
2560
|
+
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
2561
|
+
"Opening editor for #",
|
|
2562
|
+
issueNumber,
|
|
2563
|
+
"\u2026"
|
|
2564
|
+
] }) });
|
|
2565
|
+
}
|
|
2566
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
2567
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
|
|
1838
2568
|
"comment #",
|
|
1839
2569
|
issueNumber,
|
|
1840
2570
|
": "
|
|
1841
2571
|
] }),
|
|
1842
|
-
/* @__PURE__ */
|
|
2572
|
+
/* @__PURE__ */ jsx3(
|
|
1843
2573
|
TextInput,
|
|
1844
2574
|
{
|
|
1845
2575
|
defaultValue: value,
|
|
1846
|
-
placeholder: "type comment, Enter to post...",
|
|
2576
|
+
placeholder: "type comment (ctrl+e for editor), Enter to post...",
|
|
1847
2577
|
onChange: setValue,
|
|
1848
2578
|
onSubmit: (text) => {
|
|
1849
2579
|
if (text.trim()) onSubmit(text.trim());
|
|
@@ -1856,20 +2586,21 @@ function CommentInput({ issueNumber, onSubmit, onCancel }) {
|
|
|
1856
2586
|
var init_comment_input = __esm({
|
|
1857
2587
|
"src/board/components/comment-input.tsx"() {
|
|
1858
2588
|
"use strict";
|
|
2589
|
+
init_ink_instance();
|
|
1859
2590
|
}
|
|
1860
2591
|
});
|
|
1861
2592
|
|
|
1862
2593
|
// src/board/components/confirm-prompt.tsx
|
|
1863
|
-
import { Box as
|
|
1864
|
-
import { jsx as
|
|
2594
|
+
import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
|
|
2595
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1865
2596
|
function ConfirmPrompt({ message, onConfirm, onCancel }) {
|
|
1866
|
-
|
|
2597
|
+
useInput4((input2, key) => {
|
|
1867
2598
|
if (input2 === "y" || input2 === "Y") return onConfirm();
|
|
1868
2599
|
if (input2 === "n" || input2 === "N" || key.escape) return onCancel();
|
|
1869
2600
|
});
|
|
1870
|
-
return /* @__PURE__ */
|
|
1871
|
-
/* @__PURE__ */
|
|
1872
|
-
/* @__PURE__ */
|
|
2601
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
2602
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: message }),
|
|
2603
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " (y/n)" })
|
|
1873
2604
|
] });
|
|
1874
2605
|
}
|
|
1875
2606
|
var init_confirm_prompt = __esm({
|
|
@@ -1878,20 +2609,146 @@ var init_confirm_prompt = __esm({
|
|
|
1878
2609
|
}
|
|
1879
2610
|
});
|
|
1880
2611
|
|
|
2612
|
+
// src/board/components/label-picker.tsx
|
|
2613
|
+
import { Spinner } from "@inkjs/ui";
|
|
2614
|
+
import { Box as Box5, Text as Text5, useInput as useInput5 } from "ink";
|
|
2615
|
+
import { useEffect as useEffect3, useRef as useRef7, useState as useState6 } from "react";
|
|
2616
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2617
|
+
function LabelPicker({
|
|
2618
|
+
repo,
|
|
2619
|
+
currentLabels,
|
|
2620
|
+
labelCache,
|
|
2621
|
+
onConfirm,
|
|
2622
|
+
onCancel,
|
|
2623
|
+
onError
|
|
2624
|
+
}) {
|
|
2625
|
+
const [labels, setLabels] = useState6(labelCache[repo] ?? null);
|
|
2626
|
+
const [loading, setLoading] = useState6(labels === null);
|
|
2627
|
+
const [fetchAttempted, setFetchAttempted] = useState6(false);
|
|
2628
|
+
const [selected, setSelected] = useState6(new Set(currentLabels));
|
|
2629
|
+
const [cursor, setCursor] = useState6(0);
|
|
2630
|
+
const submittedRef = useRef7(false);
|
|
2631
|
+
useEffect3(() => {
|
|
2632
|
+
if (labels !== null || fetchAttempted) return;
|
|
2633
|
+
setFetchAttempted(true);
|
|
2634
|
+
setLoading(true);
|
|
2635
|
+
let canceled = false;
|
|
2636
|
+
fetchRepoLabelsAsync(repo).then((fetched) => {
|
|
2637
|
+
if (canceled) return;
|
|
2638
|
+
labelCache[repo] = fetched;
|
|
2639
|
+
setLabels(fetched);
|
|
2640
|
+
setLoading(false);
|
|
2641
|
+
}).catch(() => {
|
|
2642
|
+
if (canceled) return;
|
|
2643
|
+
setLoading(false);
|
|
2644
|
+
onError(`Could not fetch labels for ${repo}`);
|
|
2645
|
+
});
|
|
2646
|
+
return () => {
|
|
2647
|
+
canceled = true;
|
|
2648
|
+
};
|
|
2649
|
+
}, [repo, fetchAttempted, labelCache, onError]);
|
|
2650
|
+
useInput5((input2, key) => {
|
|
2651
|
+
if (loading) return;
|
|
2652
|
+
if (key.escape) {
|
|
2653
|
+
onCancel();
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (key.return) {
|
|
2657
|
+
if (submittedRef.current) return;
|
|
2658
|
+
submittedRef.current = true;
|
|
2659
|
+
const allLabels2 = labels ?? [];
|
|
2660
|
+
const add = [...selected].filter((l) => !currentLabels.includes(l));
|
|
2661
|
+
const remove = currentLabels.filter((l) => {
|
|
2662
|
+
const exists = allLabels2.some((rl) => rl.name === l);
|
|
2663
|
+
return exists && !selected.has(l);
|
|
2664
|
+
});
|
|
2665
|
+
onConfirm(add, remove);
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
if (input2 === " ") {
|
|
2669
|
+
const allLabels2 = labels ?? [];
|
|
2670
|
+
const item = allLabels2[cursor];
|
|
2671
|
+
if (!item) return;
|
|
2672
|
+
setSelected((prev) => {
|
|
2673
|
+
const next = new Set(prev);
|
|
2674
|
+
if (next.has(item.name)) {
|
|
2675
|
+
next.delete(item.name);
|
|
2676
|
+
} else {
|
|
2677
|
+
next.add(item.name);
|
|
2678
|
+
}
|
|
2679
|
+
return next;
|
|
2680
|
+
});
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
if (input2 === "j" || key.downArrow) {
|
|
2684
|
+
setCursor((i) => Math.min(i + 1, (labels?.length ?? 1) - 1));
|
|
2685
|
+
}
|
|
2686
|
+
if (input2 === "k" || key.upArrow) {
|
|
2687
|
+
setCursor((i) => Math.max(i - 1, 0));
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
if (loading) {
|
|
2691
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Spinner, { label: "Fetching labels..." }) });
|
|
2692
|
+
}
|
|
2693
|
+
const allLabels = labels ?? [];
|
|
2694
|
+
if (allLabels.length === 0) {
|
|
2695
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2696
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "Labels:" }),
|
|
2697
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No labels in this repo" }),
|
|
2698
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Esc:cancel" })
|
|
2699
|
+
] });
|
|
2700
|
+
}
|
|
2701
|
+
const repoLabelNames = new Set(allLabels.map((l) => l.name));
|
|
2702
|
+
const orphanedLabels = currentLabels.filter((l) => !repoLabelNames.has(l));
|
|
2703
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2704
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "Labels (Space:toggle Enter:confirm Esc:cancel):" }),
|
|
2705
|
+
orphanedLabels.map((name) => /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
2706
|
+
selected.has(name) ? "[x]" : "[ ]",
|
|
2707
|
+
" ",
|
|
2708
|
+
name,
|
|
2709
|
+
" (orphaned)"
|
|
2710
|
+
] }, `orphan:${name}`)),
|
|
2711
|
+
allLabels.map((label, i) => {
|
|
2712
|
+
const isSel = i === cursor;
|
|
2713
|
+
const isChecked = selected.has(label.name);
|
|
2714
|
+
return /* @__PURE__ */ jsxs5(Text5, { ...isSel ? { color: "cyan" } : {}, children: [
|
|
2715
|
+
isSel ? ">" : " ",
|
|
2716
|
+
" ",
|
|
2717
|
+
isChecked ? "[x]" : "[ ]",
|
|
2718
|
+
" ",
|
|
2719
|
+
label.name
|
|
2720
|
+
] }, label.name);
|
|
2721
|
+
})
|
|
2722
|
+
] });
|
|
2723
|
+
}
|
|
2724
|
+
var init_label_picker = __esm({
|
|
2725
|
+
"src/board/components/label-picker.tsx"() {
|
|
2726
|
+
"use strict";
|
|
2727
|
+
init_github();
|
|
2728
|
+
}
|
|
2729
|
+
});
|
|
2730
|
+
|
|
1881
2731
|
// src/board/components/create-issue-form.tsx
|
|
1882
2732
|
import { TextInput as TextInput2 } from "@inkjs/ui";
|
|
1883
|
-
import { Box as
|
|
1884
|
-
import { useState as
|
|
1885
|
-
import { jsx as
|
|
1886
|
-
function CreateIssueForm({
|
|
2733
|
+
import { Box as Box6, Text as Text6, useInput as useInput6 } from "ink";
|
|
2734
|
+
import { useState as useState7 } from "react";
|
|
2735
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2736
|
+
function CreateIssueForm({
|
|
2737
|
+
repos,
|
|
2738
|
+
defaultRepo,
|
|
2739
|
+
onSubmit,
|
|
2740
|
+
onCancel,
|
|
2741
|
+
labelCache
|
|
2742
|
+
}) {
|
|
1887
2743
|
const defaultRepoIdx = defaultRepo ? Math.max(
|
|
1888
2744
|
0,
|
|
1889
2745
|
repos.findIndex((r) => r.name === defaultRepo)
|
|
1890
2746
|
) : 0;
|
|
1891
|
-
const [repoIdx, setRepoIdx] =
|
|
1892
|
-
const [title, setTitle] =
|
|
1893
|
-
const [field, setField] =
|
|
1894
|
-
|
|
2747
|
+
const [repoIdx, setRepoIdx] = useState7(defaultRepoIdx);
|
|
2748
|
+
const [title, setTitle] = useState7("");
|
|
2749
|
+
const [field, setField] = useState7("title");
|
|
2750
|
+
useInput6((input2, key) => {
|
|
2751
|
+
if (field === "labels") return;
|
|
1895
2752
|
if (key.escape) return onCancel();
|
|
1896
2753
|
if (field === "repo") {
|
|
1897
2754
|
if (input2 === "j" || key.downArrow) {
|
|
@@ -1905,12 +2762,40 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1905
2762
|
}
|
|
1906
2763
|
});
|
|
1907
2764
|
const selectedRepo = repos[repoIdx];
|
|
1908
|
-
|
|
1909
|
-
/* @__PURE__ */
|
|
1910
|
-
|
|
1911
|
-
/* @__PURE__ */
|
|
1912
|
-
|
|
1913
|
-
|
|
2765
|
+
if (field === "labels" && selectedRepo) {
|
|
2766
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2767
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Create Issue \u2014 Add Labels (optional)" }),
|
|
2768
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2769
|
+
"Repo: ",
|
|
2770
|
+
selectedRepo.shortName,
|
|
2771
|
+
" Title: ",
|
|
2772
|
+
title
|
|
2773
|
+
] }),
|
|
2774
|
+
/* @__PURE__ */ jsx6(
|
|
2775
|
+
LabelPicker,
|
|
2776
|
+
{
|
|
2777
|
+
repo: selectedRepo.name,
|
|
2778
|
+
currentLabels: [],
|
|
2779
|
+
labelCache: labelCache ?? {},
|
|
2780
|
+
onConfirm: (addLabels) => {
|
|
2781
|
+
onSubmit(selectedRepo.name, title, addLabels.length > 0 ? addLabels : void 0);
|
|
2782
|
+
},
|
|
2783
|
+
onCancel: () => {
|
|
2784
|
+
onSubmit(selectedRepo.name, title);
|
|
2785
|
+
},
|
|
2786
|
+
onError: () => {
|
|
2787
|
+
onSubmit(selectedRepo.name, title);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
)
|
|
2791
|
+
] });
|
|
2792
|
+
}
|
|
2793
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2794
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Create Issue" }),
|
|
2795
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2796
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: field !== "repo", children: "Repo: " }),
|
|
2797
|
+
repos.map((r, i) => /* @__PURE__ */ jsx6(
|
|
2798
|
+
Text6,
|
|
1914
2799
|
{
|
|
1915
2800
|
...i === repoIdx ? { color: "cyan", bold: true } : {},
|
|
1916
2801
|
dimColor: field !== "repo",
|
|
@@ -1918,215 +2803,53 @@ function CreateIssueForm({ repos, defaultRepo, onSubmit, onCancel }) {
|
|
|
1918
2803
|
},
|
|
1919
2804
|
r.name
|
|
1920
2805
|
)),
|
|
1921
|
-
field === "repo" ? /* @__PURE__ */
|
|
2806
|
+
field === "repo" ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " j/k:select Tab:next" }) : null
|
|
1922
2807
|
] }),
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
1925
|
-
field === "title" ? /* @__PURE__ */
|
|
2808
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2809
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: field !== "title", children: "Title: " }),
|
|
2810
|
+
field === "title" ? /* @__PURE__ */ jsx6(
|
|
1926
2811
|
TextInput2,
|
|
1927
2812
|
{
|
|
1928
2813
|
defaultValue: title,
|
|
1929
2814
|
placeholder: "issue title...",
|
|
1930
2815
|
onChange: setTitle,
|
|
1931
2816
|
onSubmit: (text) => {
|
|
1932
|
-
|
|
1933
|
-
|
|
2817
|
+
const trimmed = text.trim();
|
|
2818
|
+
if (!(trimmed && selectedRepo)) return;
|
|
2819
|
+
if (labelCache !== void 0) {
|
|
2820
|
+
setTitle(trimmed);
|
|
2821
|
+
setField("labels");
|
|
2822
|
+
} else {
|
|
2823
|
+
onSubmit(selectedRepo.name, trimmed);
|
|
1934
2824
|
}
|
|
1935
2825
|
}
|
|
1936
2826
|
}
|
|
1937
|
-
) : /* @__PURE__ */
|
|
2827
|
+
) : /* @__PURE__ */ jsx6(Text6, { children: title || "(empty)" })
|
|
1938
2828
|
] }),
|
|
1939
|
-
/* @__PURE__ */
|
|
2829
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab:switch fields Enter:next Esc:cancel" })
|
|
1940
2830
|
] });
|
|
1941
2831
|
}
|
|
1942
2832
|
var init_create_issue_form = __esm({
|
|
1943
2833
|
"src/board/components/create-issue-form.tsx"() {
|
|
1944
2834
|
"use strict";
|
|
1945
|
-
|
|
1946
|
-
});
|
|
1947
|
-
|
|
1948
|
-
// src/board/components/detail-panel.tsx
|
|
1949
|
-
import { Box as Box5, Text as Text5 } from "ink";
|
|
1950
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1951
|
-
function truncateLines(text, maxLines) {
|
|
1952
|
-
const lines = text.split("\n").slice(0, maxLines);
|
|
1953
|
-
return lines.join("\n");
|
|
1954
|
-
}
|
|
1955
|
-
function stripMarkdown(text) {
|
|
1956
|
-
return text.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, (m) => m.replace(/`/g, "")).replace(/^\s*[-*+]\s+/gm, " - ").replace(/^\s*\d+\.\s+/gm, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "[$1]").replace(/^>\s+/gm, " ").replace(/---+/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
1957
|
-
}
|
|
1958
|
-
function formatBody(body, maxLines) {
|
|
1959
|
-
const plain = stripMarkdown(body);
|
|
1960
|
-
const lines = plain.split("\n");
|
|
1961
|
-
const truncated = lines.slice(0, maxLines).join("\n");
|
|
1962
|
-
return { text: truncated, remaining: Math.max(0, lines.length - maxLines) };
|
|
1963
|
-
}
|
|
1964
|
-
function countSlackLinks(body) {
|
|
1965
|
-
if (!body) return 0;
|
|
1966
|
-
return (body.match(SLACK_URL_RE) ?? []).length;
|
|
1967
|
-
}
|
|
1968
|
-
function BodySection({
|
|
1969
|
-
body,
|
|
1970
|
-
issueNumber
|
|
1971
|
-
}) {
|
|
1972
|
-
const { text, remaining } = formatBody(body, 15);
|
|
1973
|
-
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1974
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
1975
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "--- Description ---" }),
|
|
1976
|
-
/* @__PURE__ */ jsx5(Text5, { wrap: "wrap", children: text }),
|
|
1977
|
-
remaining > 0 ? /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1978
|
-
"... (",
|
|
1979
|
-
remaining,
|
|
1980
|
-
" more lines \u2014 gh issue view ",
|
|
1981
|
-
issueNumber,
|
|
1982
|
-
" for full)"
|
|
1983
|
-
] }) : null
|
|
1984
|
-
] });
|
|
1985
|
-
}
|
|
1986
|
-
function DetailPanel({ issue, task: task2, width }) {
|
|
1987
|
-
if (!(issue || task2)) {
|
|
1988
|
-
return /* @__PURE__ */ jsx5(
|
|
1989
|
-
Box5,
|
|
1990
|
-
{
|
|
1991
|
-
width,
|
|
1992
|
-
borderStyle: "single",
|
|
1993
|
-
borderColor: "gray",
|
|
1994
|
-
flexDirection: "column",
|
|
1995
|
-
paddingX: 1,
|
|
1996
|
-
children: /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "No item selected" })
|
|
1997
|
-
}
|
|
1998
|
-
);
|
|
1999
|
-
}
|
|
2000
|
-
if (issue) {
|
|
2001
|
-
return /* @__PURE__ */ jsxs5(
|
|
2002
|
-
Box5,
|
|
2003
|
-
{
|
|
2004
|
-
width,
|
|
2005
|
-
borderStyle: "single",
|
|
2006
|
-
borderColor: "cyan",
|
|
2007
|
-
flexDirection: "column",
|
|
2008
|
-
paddingX: 1,
|
|
2009
|
-
children: [
|
|
2010
|
-
/* @__PURE__ */ jsxs5(Text5, { color: "cyan", bold: true, children: [
|
|
2011
|
-
"#",
|
|
2012
|
-
issue.number,
|
|
2013
|
-
" ",
|
|
2014
|
-
issue.title
|
|
2015
|
-
] }),
|
|
2016
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2017
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2018
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "State: " }),
|
|
2019
|
-
/* @__PURE__ */ jsx5(Text5, { color: issue.state === "open" ? "green" : "red", children: issue.state })
|
|
2020
|
-
] }),
|
|
2021
|
-
(issue.assignees ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2022
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Assignees: " }),
|
|
2023
|
-
/* @__PURE__ */ jsx5(Text5, { children: (issue.assignees ?? []).map((a) => a.login).join(", ") })
|
|
2024
|
-
] }) : null,
|
|
2025
|
-
issue.labels.length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2026
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Labels: " }),
|
|
2027
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.labels.map((l) => l.name).join(", ") })
|
|
2028
|
-
] }) : null,
|
|
2029
|
-
issue.projectStatus ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2030
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Status: " }),
|
|
2031
|
-
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: issue.projectStatus })
|
|
2032
|
-
] }) : null,
|
|
2033
|
-
issue.targetDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2034
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Target: " }),
|
|
2035
|
-
/* @__PURE__ */ jsx5(Text5, { children: issue.targetDate })
|
|
2036
|
-
] }) : null,
|
|
2037
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2038
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Updated: " }),
|
|
2039
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(issue.updatedAt).toLocaleString() })
|
|
2040
|
-
] }),
|
|
2041
|
-
issue.slackThreadUrl ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2042
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Slack: " }),
|
|
2043
|
-
/* @__PURE__ */ jsx5(Text5, { color: "blue", children: countSlackLinks(issue.body) > 1 ? `${countSlackLinks(issue.body)} links (s opens first)` : "thread (s to open)" })
|
|
2044
|
-
] }) : null,
|
|
2045
|
-
issue.body ? /* @__PURE__ */ jsx5(BodySection, { body: issue.body, issueNumber: issue.number }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2046
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2047
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "(no description)" })
|
|
2048
|
-
] }),
|
|
2049
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2050
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: issue.url })
|
|
2051
|
-
]
|
|
2052
|
-
}
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
const t = task2;
|
|
2056
|
-
return /* @__PURE__ */ jsxs5(
|
|
2057
|
-
Box5,
|
|
2058
|
-
{
|
|
2059
|
-
width,
|
|
2060
|
-
borderStyle: "single",
|
|
2061
|
-
borderColor: "yellow",
|
|
2062
|
-
flexDirection: "column",
|
|
2063
|
-
paddingX: 1,
|
|
2064
|
-
children: [
|
|
2065
|
-
/* @__PURE__ */ jsx5(Text5, { color: "yellow", bold: true, children: t.title }),
|
|
2066
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2067
|
-
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2068
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Priority: " }),
|
|
2069
|
-
/* @__PURE__ */ jsx5(Text5, { children: PRIORITY_LABELS2[t.priority] ?? "None" })
|
|
2070
|
-
] }),
|
|
2071
|
-
t.dueDate ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2072
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Due: " }),
|
|
2073
|
-
/* @__PURE__ */ jsx5(Text5, { children: new Date(t.dueDate).toLocaleDateString() })
|
|
2074
|
-
] }) : null,
|
|
2075
|
-
(t.tags ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2076
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Tags: " }),
|
|
2077
|
-
/* @__PURE__ */ jsx5(Text5, { children: t.tags.join(", ") })
|
|
2078
|
-
] }) : null,
|
|
2079
|
-
t.content ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2080
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2081
|
-
/* @__PURE__ */ jsx5(Text5, { children: truncateLines(t.content, 8) })
|
|
2082
|
-
] }) : null,
|
|
2083
|
-
(t.items ?? []).length > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2084
|
-
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
2085
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "Checklist:" }),
|
|
2086
|
-
t.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
2087
|
-
item.status === 2 ? "\u2611" : "\u2610",
|
|
2088
|
-
" ",
|
|
2089
|
-
item.title
|
|
2090
|
-
] }, item.id)),
|
|
2091
|
-
t.items.length > 5 ? /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
2092
|
-
"...and ",
|
|
2093
|
-
t.items.length - 5,
|
|
2094
|
-
" more"
|
|
2095
|
-
] }) : null
|
|
2096
|
-
] }) : null
|
|
2097
|
-
]
|
|
2098
|
-
}
|
|
2099
|
-
);
|
|
2100
|
-
}
|
|
2101
|
-
var SLACK_URL_RE, PRIORITY_LABELS2;
|
|
2102
|
-
var init_detail_panel = __esm({
|
|
2103
|
-
"src/board/components/detail-panel.tsx"() {
|
|
2104
|
-
"use strict";
|
|
2105
|
-
init_types();
|
|
2106
|
-
SLACK_URL_RE = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/gi;
|
|
2107
|
-
PRIORITY_LABELS2 = {
|
|
2108
|
-
[5 /* High */]: "High",
|
|
2109
|
-
[3 /* Medium */]: "Medium",
|
|
2110
|
-
[1 /* Low */]: "Low",
|
|
2111
|
-
[0 /* None */]: "None"
|
|
2112
|
-
};
|
|
2835
|
+
init_label_picker();
|
|
2113
2836
|
}
|
|
2114
2837
|
});
|
|
2115
2838
|
|
|
2116
2839
|
// src/board/components/focus-mode.tsx
|
|
2117
|
-
import { Box as
|
|
2118
|
-
import { useCallback as
|
|
2119
|
-
import { jsx as
|
|
2840
|
+
import { Box as Box7, Text as Text7, useInput as useInput7 } from "ink";
|
|
2841
|
+
import { useCallback as useCallback8, useEffect as useEffect4, useRef as useRef8, useState as useState8 } from "react";
|
|
2842
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2120
2843
|
function formatTime(secs) {
|
|
2121
2844
|
const m = Math.floor(secs / 60);
|
|
2122
2845
|
const s = secs % 60;
|
|
2123
2846
|
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2124
2847
|
}
|
|
2125
2848
|
function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
2126
|
-
const [remaining, setRemaining] =
|
|
2127
|
-
const [timerDone, setTimerDone] =
|
|
2128
|
-
const bellSentRef =
|
|
2129
|
-
|
|
2849
|
+
const [remaining, setRemaining] = useState8(durationSec);
|
|
2850
|
+
const [timerDone, setTimerDone] = useState8(false);
|
|
2851
|
+
const bellSentRef = useRef8(false);
|
|
2852
|
+
useEffect4(() => {
|
|
2130
2853
|
if (timerDone) return;
|
|
2131
2854
|
const interval = setInterval(() => {
|
|
2132
2855
|
setRemaining((prev) => {
|
|
@@ -2140,13 +2863,13 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2140
2863
|
}, 1e3);
|
|
2141
2864
|
return () => clearInterval(interval);
|
|
2142
2865
|
}, [timerDone]);
|
|
2143
|
-
|
|
2866
|
+
useEffect4(() => {
|
|
2144
2867
|
if (timerDone && !bellSentRef.current) {
|
|
2145
2868
|
bellSentRef.current = true;
|
|
2146
2869
|
process.stdout.write("\x07");
|
|
2147
2870
|
}
|
|
2148
2871
|
}, [timerDone]);
|
|
2149
|
-
const handleInput =
|
|
2872
|
+
const handleInput = useCallback8(
|
|
2150
2873
|
(input2, key) => {
|
|
2151
2874
|
if (key.escape) {
|
|
2152
2875
|
if (timerDone) {
|
|
@@ -2171,25 +2894,25 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2171
2894
|
},
|
|
2172
2895
|
[timerDone, onExit, onEndAction]
|
|
2173
2896
|
);
|
|
2174
|
-
|
|
2897
|
+
useInput7(handleInput);
|
|
2175
2898
|
if (timerDone) {
|
|
2176
|
-
return /* @__PURE__ */
|
|
2177
|
-
/* @__PURE__ */
|
|
2178
|
-
/* @__PURE__ */
|
|
2179
|
-
/* @__PURE__ */
|
|
2899
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2900
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2901
|
+
/* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "Focus complete!" }),
|
|
2902
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
|
|
2180
2903
|
" ",
|
|
2181
2904
|
label
|
|
2182
2905
|
] })
|
|
2183
2906
|
] }),
|
|
2184
|
-
/* @__PURE__ */
|
|
2185
|
-
/* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */
|
|
2188
|
-
/* @__PURE__ */
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
/* @__PURE__ */
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__PURE__ */
|
|
2907
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
2908
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[c]" }),
|
|
2909
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Continue " }),
|
|
2910
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[b]" }),
|
|
2911
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Break " }),
|
|
2912
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "[d]" }),
|
|
2913
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Done " }),
|
|
2914
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: "[Esc]" }),
|
|
2915
|
+
/* @__PURE__ */ jsx7(Text7, { children: " Exit" })
|
|
2193
2916
|
] })
|
|
2194
2917
|
] });
|
|
2195
2918
|
}
|
|
@@ -2197,21 +2920,21 @@ function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
|
2197
2920
|
const barWidth = 20;
|
|
2198
2921
|
const filled = Math.round(progress * barWidth);
|
|
2199
2922
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
2200
|
-
return /* @__PURE__ */
|
|
2201
|
-
/* @__PURE__ */
|
|
2202
|
-
/* @__PURE__ */
|
|
2923
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2924
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2925
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "magenta", bold: true, children: [
|
|
2203
2926
|
"Focus:",
|
|
2204
2927
|
" "
|
|
2205
2928
|
] }),
|
|
2206
|
-
/* @__PURE__ */
|
|
2929
|
+
/* @__PURE__ */ jsx7(Text7, { children: label })
|
|
2207
2930
|
] }),
|
|
2208
|
-
/* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2210
|
-
/* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__PURE__ */
|
|
2931
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2932
|
+
/* @__PURE__ */ jsx7(Text7, { color: "magenta", children: bar }),
|
|
2933
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
2934
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: formatTime(remaining) }),
|
|
2935
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", children: " remaining" })
|
|
2213
2936
|
] }),
|
|
2214
|
-
/* @__PURE__ */
|
|
2937
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "Esc to exit focus" })
|
|
2215
2938
|
] });
|
|
2216
2939
|
}
|
|
2217
2940
|
var init_focus_mode = __esm({
|
|
@@ -2221,29 +2944,29 @@ var init_focus_mode = __esm({
|
|
|
2221
2944
|
});
|
|
2222
2945
|
|
|
2223
2946
|
// src/board/components/help-overlay.tsx
|
|
2224
|
-
import { Box as
|
|
2225
|
-
import { jsx as
|
|
2947
|
+
import { Box as Box8, Text as Text8, useInput as useInput8 } from "ink";
|
|
2948
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2226
2949
|
function HelpOverlay({ currentMode, onClose }) {
|
|
2227
|
-
|
|
2950
|
+
useInput8((_input, key) => {
|
|
2228
2951
|
if (key.escape) onClose();
|
|
2229
2952
|
});
|
|
2230
|
-
return /* @__PURE__ */
|
|
2231
|
-
/* @__PURE__ */
|
|
2232
|
-
/* @__PURE__ */
|
|
2233
|
-
/* @__PURE__ */
|
|
2953
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
2954
|
+
/* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", children: [
|
|
2955
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: "Keyboard Shortcuts" }),
|
|
2956
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
2234
2957
|
"mode: ",
|
|
2235
2958
|
currentMode
|
|
2236
2959
|
] })
|
|
2237
2960
|
] }),
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
SHORTCUTS.map((group) => /* @__PURE__ */
|
|
2240
|
-
/* @__PURE__ */
|
|
2241
|
-
group.items.map((item) => /* @__PURE__ */
|
|
2242
|
-
/* @__PURE__ */
|
|
2243
|
-
/* @__PURE__ */
|
|
2961
|
+
/* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
2962
|
+
SHORTCUTS.map((group) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
2963
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", bold: true, children: group.category }),
|
|
2964
|
+
group.items.map((item) => /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2965
|
+
/* @__PURE__ */ jsx8(Box8, { width: 16, children: /* @__PURE__ */ jsx8(Text8, { color: "green", children: item.key }) }),
|
|
2966
|
+
/* @__PURE__ */ jsx8(Text8, { children: item.desc })
|
|
2244
2967
|
] }, item.key))
|
|
2245
2968
|
] }, group.category)),
|
|
2246
|
-
/* @__PURE__ */
|
|
2969
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press ? or Esc to close" })
|
|
2247
2970
|
] });
|
|
2248
2971
|
}
|
|
2249
2972
|
var SHORTCUTS;
|
|
@@ -2271,31 +2994,447 @@ var init_help_overlay = __esm({
|
|
|
2271
2994
|
]
|
|
2272
2995
|
},
|
|
2273
2996
|
{
|
|
2274
|
-
category: "Actions",
|
|
2275
|
-
items: [
|
|
2276
|
-
{ key: "p", desc: "Pick issue (assign + TickTick)" },
|
|
2277
|
-
{ key: "a", desc: "Assign to self" },
|
|
2278
|
-
{ key: "u", desc: "Unassign self" },
|
|
2279
|
-
{ key: "c", desc: "Comment on issue" },
|
|
2280
|
-
{ key: "m", desc: "Move status" },
|
|
2281
|
-
{ key: "s", desc: "Open Slack thread" },
|
|
2282
|
-
{ key: "
|
|
2283
|
-
|
|
2284
|
-
|
|
2997
|
+
category: "Actions",
|
|
2998
|
+
items: [
|
|
2999
|
+
{ key: "p", desc: "Pick issue (assign + TickTick)" },
|
|
3000
|
+
{ key: "a", desc: "Assign to self" },
|
|
3001
|
+
{ key: "u", desc: "Unassign self" },
|
|
3002
|
+
{ key: "c", desc: "Comment on issue" },
|
|
3003
|
+
{ key: "m", desc: "Move status" },
|
|
3004
|
+
{ key: "s", desc: "Open Slack thread" },
|
|
3005
|
+
{ key: "y", desc: "Copy issue link to clipboard" },
|
|
3006
|
+
{ key: "n", desc: "Create new issue" }
|
|
3007
|
+
]
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
category: "Board",
|
|
3011
|
+
items: [
|
|
3012
|
+
{ key: "r", desc: "Refresh data" },
|
|
3013
|
+
{ key: "q", desc: "Quit" }
|
|
3014
|
+
]
|
|
3015
|
+
}
|
|
3016
|
+
];
|
|
3017
|
+
}
|
|
3018
|
+
});
|
|
3019
|
+
|
|
3020
|
+
// src/board/components/nl-create-overlay.tsx
|
|
3021
|
+
import { Spinner as Spinner2, TextInput as TextInput3 } from "@inkjs/ui";
|
|
3022
|
+
import { Box as Box9, Text as Text9, useInput as useInput9 } from "ink";
|
|
3023
|
+
import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef9, useState as useState9 } from "react";
|
|
3024
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3025
|
+
function NlCreateOverlay({
|
|
3026
|
+
repos,
|
|
3027
|
+
defaultRepoName,
|
|
3028
|
+
labelCache,
|
|
3029
|
+
onSubmit,
|
|
3030
|
+
onCancel,
|
|
3031
|
+
onLlmFallback
|
|
3032
|
+
}) {
|
|
3033
|
+
const [, setInput] = useState9("");
|
|
3034
|
+
const [isParsing, setIsParsing] = useState9(false);
|
|
3035
|
+
const [parsed, setParsed] = useState9(null);
|
|
3036
|
+
const [parseError, setParseError] = useState9(null);
|
|
3037
|
+
const [createError, setCreateError] = useState9(null);
|
|
3038
|
+
const submittedRef = useRef9(false);
|
|
3039
|
+
const parseParamsRef = useRef9(null);
|
|
3040
|
+
const defaultRepoIdx = defaultRepoName ? Math.max(
|
|
3041
|
+
0,
|
|
3042
|
+
repos.findIndex((r) => r.name === defaultRepoName)
|
|
3043
|
+
) : 0;
|
|
3044
|
+
const [repoIdx, setRepoIdx] = useState9(defaultRepoIdx);
|
|
3045
|
+
const selectedRepo = repos[repoIdx];
|
|
3046
|
+
useInput9((inputChar, key) => {
|
|
3047
|
+
if (isParsing) return;
|
|
3048
|
+
if (key.escape) {
|
|
3049
|
+
onCancel();
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
if (parsed) {
|
|
3053
|
+
if (key.return) {
|
|
3054
|
+
if (submittedRef.current) return;
|
|
3055
|
+
submittedRef.current = true;
|
|
3056
|
+
if (!selectedRepo) return;
|
|
3057
|
+
setCreateError(null);
|
|
3058
|
+
const labels = buildLabelList(parsed);
|
|
3059
|
+
onSubmit(selectedRepo.name, parsed.title, labels.length > 0 ? labels : void 0);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
if (inputChar === "r") {
|
|
3063
|
+
setRepoIdx((i) => (i + 1) % repos.length);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
const handleInputSubmit = useCallback9(
|
|
3069
|
+
(text) => {
|
|
3070
|
+
const trimmed = text.trim();
|
|
3071
|
+
if (!trimmed) return;
|
|
3072
|
+
const validLabels = selectedRepo ? (labelCache[selectedRepo.name] ?? []).map((l) => l.name) : [];
|
|
3073
|
+
parseParamsRef.current = { input: trimmed, validLabels };
|
|
3074
|
+
setInput(trimmed);
|
|
3075
|
+
setParseError(null);
|
|
3076
|
+
setIsParsing(true);
|
|
3077
|
+
},
|
|
3078
|
+
[selectedRepo, labelCache]
|
|
3079
|
+
);
|
|
3080
|
+
useEffect5(() => {
|
|
3081
|
+
if (!(isParsing && parseParamsRef.current)) return;
|
|
3082
|
+
const { input: capturedInput, validLabels } = parseParamsRef.current;
|
|
3083
|
+
extractIssueFields(capturedInput, {
|
|
3084
|
+
validLabels,
|
|
3085
|
+
onLlmFallback
|
|
3086
|
+
}).then((result) => {
|
|
3087
|
+
if (!result) {
|
|
3088
|
+
setParseError("Title is required");
|
|
3089
|
+
setIsParsing(false);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const filteredLabels = validLabels.length > 0 ? result.labels.filter((l) => validLabels.includes(l)) : result.labels;
|
|
3093
|
+
setParsed({ ...result, labels: filteredLabels });
|
|
3094
|
+
setIsParsing(false);
|
|
3095
|
+
}).catch(() => {
|
|
3096
|
+
setParseError("Parsing failed \u2014 please try again");
|
|
3097
|
+
setIsParsing(false);
|
|
3098
|
+
});
|
|
3099
|
+
}, [isParsing, onLlmFallback]);
|
|
3100
|
+
if (isParsing) {
|
|
3101
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3102
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3103
|
+
/* @__PURE__ */ jsx9(Spinner2, { label: "Parsing..." })
|
|
3104
|
+
] });
|
|
3105
|
+
}
|
|
3106
|
+
if (parsed) {
|
|
3107
|
+
const labels = buildLabelList(parsed);
|
|
3108
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3109
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
|
|
3110
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3111
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Repo: " }),
|
|
3112
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: selectedRepo?.shortName ?? "(none)" }),
|
|
3113
|
+
repos.length > 1 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " r:cycle" }) : null
|
|
3114
|
+
] }),
|
|
3115
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3116
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Title: " }),
|
|
3117
|
+
/* @__PURE__ */ jsx9(Text9, { children: parsed.title })
|
|
3118
|
+
] }),
|
|
3119
|
+
labels.length > 0 ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3120
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Labels: " }),
|
|
3121
|
+
/* @__PURE__ */ jsx9(Text9, { children: labels.join(", ") })
|
|
3122
|
+
] }) : null,
|
|
3123
|
+
parsed.assignee ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3124
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Assignee: " }),
|
|
3125
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3126
|
+
"@",
|
|
3127
|
+
parsed.assignee
|
|
3128
|
+
] })
|
|
3129
|
+
] }) : null,
|
|
3130
|
+
parsed.dueDate ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3131
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Due: " }),
|
|
3132
|
+
/* @__PURE__ */ jsx9(Text9, { children: formatDue(parsed.dueDate) })
|
|
3133
|
+
] }) : null,
|
|
3134
|
+
parsed.dueDate && selectedRepo && !hasDueLabelInCache(labelCache, selectedRepo.name) ? /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u26A0 No due:* label in this repo \u2014 will try to create label on submit" }) : null,
|
|
3135
|
+
createError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: createError }) : null,
|
|
3136
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create Esc:cancel" })
|
|
3137
|
+
] });
|
|
3138
|
+
}
|
|
3139
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3140
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 What do you need to do?" }),
|
|
3141
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3142
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "> " }),
|
|
3143
|
+
/* @__PURE__ */ jsx9(
|
|
3144
|
+
TextInput3,
|
|
3145
|
+
{
|
|
3146
|
+
placeholder: "fix login bug #bug #priority:high @me due friday",
|
|
3147
|
+
onChange: setInput,
|
|
3148
|
+
onSubmit: handleInputSubmit
|
|
3149
|
+
}
|
|
3150
|
+
)
|
|
3151
|
+
] }),
|
|
3152
|
+
parseError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: parseError }) : null,
|
|
3153
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Tip: #label @user due <date> Enter:parse Esc:cancel" })
|
|
3154
|
+
] });
|
|
3155
|
+
}
|
|
3156
|
+
function buildLabelList(parsed) {
|
|
3157
|
+
const labels = [...parsed.labels];
|
|
3158
|
+
if (parsed.dueDate) {
|
|
3159
|
+
labels.push(`due:${parsed.dueDate}`);
|
|
3160
|
+
}
|
|
3161
|
+
return labels;
|
|
3162
|
+
}
|
|
3163
|
+
function hasDueLabelInCache(labelCache, repoName) {
|
|
3164
|
+
return (labelCache[repoName] ?? []).some((l) => l.name.startsWith("due:"));
|
|
3165
|
+
}
|
|
3166
|
+
function formatDue(dueDate) {
|
|
3167
|
+
const d = /* @__PURE__ */ new Date(`${dueDate}T12:00:00`);
|
|
3168
|
+
const human = d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
|
|
3169
|
+
return `${human} (label: due:${dueDate})`;
|
|
3170
|
+
}
|
|
3171
|
+
var init_nl_create_overlay = __esm({
|
|
3172
|
+
"src/board/components/nl-create-overlay.tsx"() {
|
|
3173
|
+
"use strict";
|
|
3174
|
+
init_ai();
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
|
|
3178
|
+
// src/board/components/search-bar.tsx
|
|
3179
|
+
import { TextInput as TextInput4 } from "@inkjs/ui";
|
|
3180
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
3181
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3182
|
+
function SearchBar({ defaultValue, onChange, onSubmit }) {
|
|
3183
|
+
return /* @__PURE__ */ jsxs10(Box10, { children: [
|
|
3184
|
+
/* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "/" }),
|
|
3185
|
+
/* @__PURE__ */ jsx10(
|
|
3186
|
+
TextInput4,
|
|
3187
|
+
{
|
|
3188
|
+
defaultValue,
|
|
3189
|
+
placeholder: "search...",
|
|
3190
|
+
onChange,
|
|
3191
|
+
onSubmit
|
|
3192
|
+
}
|
|
3193
|
+
)
|
|
3194
|
+
] });
|
|
3195
|
+
}
|
|
3196
|
+
var init_search_bar = __esm({
|
|
3197
|
+
"src/board/components/search-bar.tsx"() {
|
|
3198
|
+
"use strict";
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3202
|
+
// src/board/components/status-picker.tsx
|
|
3203
|
+
import { Box as Box11, Text as Text11, useInput as useInput10 } from "ink";
|
|
3204
|
+
import { useRef as useRef10, useState as useState10 } from "react";
|
|
3205
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3206
|
+
function isTerminal(name) {
|
|
3207
|
+
return TERMINAL_STATUS_RE2.test(name);
|
|
3208
|
+
}
|
|
3209
|
+
function StatusPicker({
|
|
3210
|
+
options,
|
|
3211
|
+
currentStatus,
|
|
3212
|
+
onSelect,
|
|
3213
|
+
onCancel,
|
|
3214
|
+
showTerminalStatuses = true
|
|
3215
|
+
}) {
|
|
3216
|
+
const [selectedIdx, setSelectedIdx] = useState10(() => {
|
|
3217
|
+
const idx = options.findIndex((o) => o.name === currentStatus);
|
|
3218
|
+
return idx >= 0 ? idx : 0;
|
|
3219
|
+
});
|
|
3220
|
+
const [confirmingTerminal, setConfirmingTerminal] = useState10(false);
|
|
3221
|
+
const submittedRef = useRef10(false);
|
|
3222
|
+
useInput10((input2, key) => {
|
|
3223
|
+
if (confirmingTerminal) {
|
|
3224
|
+
if (input2 === "y" || input2 === "Y") {
|
|
3225
|
+
if (submittedRef.current) return;
|
|
3226
|
+
submittedRef.current = true;
|
|
3227
|
+
const opt = options[selectedIdx];
|
|
3228
|
+
if (opt) onSelect(opt.id);
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
if (input2 === "n" || input2 === "N" || key.escape) {
|
|
3232
|
+
setConfirmingTerminal(false);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
if (key.escape) return onCancel();
|
|
3238
|
+
if (key.return) {
|
|
3239
|
+
if (submittedRef.current) return;
|
|
3240
|
+
const opt = options[selectedIdx];
|
|
3241
|
+
if (!opt) return;
|
|
3242
|
+
if (isTerminal(opt.name) && showTerminalStatuses) {
|
|
3243
|
+
setConfirmingTerminal(true);
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
submittedRef.current = true;
|
|
3247
|
+
onSelect(opt.id);
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
if (input2 === "j" || key.downArrow) {
|
|
3251
|
+
setSelectedIdx((i) => Math.min(i + 1, options.length - 1));
|
|
3252
|
+
}
|
|
3253
|
+
if (input2 === "k" || key.upArrow) {
|
|
3254
|
+
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
if (confirmingTerminal) {
|
|
3258
|
+
const opt = options[selectedIdx];
|
|
3259
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
3260
|
+
/* @__PURE__ */ jsxs11(Text11, { color: "yellow", bold: true, children: [
|
|
3261
|
+
"Mark as ",
|
|
3262
|
+
opt?.name,
|
|
3263
|
+
"?"
|
|
3264
|
+
] }),
|
|
3265
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "This will close the issue on GitHub." }),
|
|
3266
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Continue? [y/n]" })
|
|
3267
|
+
] });
|
|
3268
|
+
}
|
|
3269
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
3270
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: "Move to status:" }),
|
|
3271
|
+
options.map((opt, i) => {
|
|
3272
|
+
const isCurrent = opt.name === currentStatus;
|
|
3273
|
+
const isSelected = i === selectedIdx;
|
|
3274
|
+
const terminal = isTerminal(opt.name) && showTerminalStatuses;
|
|
3275
|
+
const prefix = isSelected ? "> " : " ";
|
|
3276
|
+
const suffix = isCurrent ? " (current)" : terminal ? " (Done)" : "";
|
|
3277
|
+
return /* @__PURE__ */ jsxs11(
|
|
3278
|
+
Text11,
|
|
3279
|
+
{
|
|
3280
|
+
...isSelected ? { color: "cyan" } : terminal ? { color: "yellow" } : {},
|
|
3281
|
+
dimColor: isCurrent,
|
|
3282
|
+
children: [
|
|
3283
|
+
prefix,
|
|
3284
|
+
opt.name,
|
|
3285
|
+
suffix
|
|
3286
|
+
]
|
|
3287
|
+
},
|
|
3288
|
+
opt.id
|
|
3289
|
+
);
|
|
3290
|
+
}),
|
|
3291
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
3292
|
+
] });
|
|
3293
|
+
}
|
|
3294
|
+
var TERMINAL_STATUS_RE2;
|
|
3295
|
+
var init_status_picker = __esm({
|
|
3296
|
+
"src/board/components/status-picker.tsx"() {
|
|
3297
|
+
"use strict";
|
|
3298
|
+
TERMINAL_STATUS_RE2 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
|
|
3302
|
+
// src/board/components/overlay-renderer.tsx
|
|
3303
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3304
|
+
function OverlayRenderer({
|
|
3305
|
+
uiState,
|
|
3306
|
+
config: config2,
|
|
3307
|
+
selectedRepoStatusOptions,
|
|
3308
|
+
currentStatus,
|
|
3309
|
+
onStatusSelect,
|
|
3310
|
+
onExitOverlay,
|
|
3311
|
+
defaultRepo,
|
|
3312
|
+
onCreateIssue,
|
|
3313
|
+
onConfirmPick,
|
|
3314
|
+
onCancelPick,
|
|
3315
|
+
multiSelectCount,
|
|
3316
|
+
multiSelectType,
|
|
3317
|
+
onBulkAction,
|
|
3318
|
+
focusLabel,
|
|
3319
|
+
focusKey,
|
|
3320
|
+
onFocusExit,
|
|
3321
|
+
onFocusEndAction,
|
|
3322
|
+
searchQuery,
|
|
3323
|
+
onSearchChange,
|
|
3324
|
+
onSearchSubmit,
|
|
3325
|
+
selectedIssue,
|
|
3326
|
+
onComment,
|
|
3327
|
+
onPauseRefresh,
|
|
3328
|
+
onResumeRefresh,
|
|
3329
|
+
onToggleHelp,
|
|
3330
|
+
labelCache,
|
|
3331
|
+
onLabelConfirm,
|
|
3332
|
+
onLabelError,
|
|
3333
|
+
onLlmFallback
|
|
3334
|
+
}) {
|
|
3335
|
+
const { mode, helpVisible } = uiState;
|
|
3336
|
+
return /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
3337
|
+
helpVisible ? /* @__PURE__ */ jsx12(HelpOverlay, { currentMode: mode, onClose: onToggleHelp }) : null,
|
|
3338
|
+
mode === "overlay:status" && selectedRepoStatusOptions.length > 0 ? /* @__PURE__ */ jsx12(
|
|
3339
|
+
StatusPicker,
|
|
3340
|
+
{
|
|
3341
|
+
options: selectedRepoStatusOptions,
|
|
3342
|
+
currentStatus,
|
|
3343
|
+
onSelect: onStatusSelect,
|
|
3344
|
+
onCancel: onExitOverlay
|
|
3345
|
+
}
|
|
3346
|
+
) : null,
|
|
3347
|
+
mode === "overlay:create" ? /* @__PURE__ */ jsx12(
|
|
3348
|
+
CreateIssueForm,
|
|
3349
|
+
{
|
|
3350
|
+
repos: config2.repos,
|
|
3351
|
+
defaultRepo,
|
|
3352
|
+
onSubmit: onCreateIssue,
|
|
3353
|
+
onCancel: onExitOverlay,
|
|
3354
|
+
labelCache
|
|
3355
|
+
}
|
|
3356
|
+
) : null,
|
|
3357
|
+
mode === "overlay:confirmPick" ? /* @__PURE__ */ jsx12(
|
|
3358
|
+
ConfirmPrompt,
|
|
3359
|
+
{
|
|
3360
|
+
message: "Pick this issue?",
|
|
3361
|
+
onConfirm: onConfirmPick,
|
|
3362
|
+
onCancel: onCancelPick
|
|
3363
|
+
}
|
|
3364
|
+
) : null,
|
|
3365
|
+
mode === "overlay:bulkAction" ? /* @__PURE__ */ jsx12(
|
|
3366
|
+
BulkActionMenu,
|
|
3367
|
+
{
|
|
3368
|
+
count: multiSelectCount,
|
|
3369
|
+
selectionType: multiSelectType,
|
|
3370
|
+
onSelect: onBulkAction,
|
|
3371
|
+
onCancel: onExitOverlay
|
|
3372
|
+
}
|
|
3373
|
+
) : null,
|
|
3374
|
+
mode === "focus" && focusLabel ? /* @__PURE__ */ jsx12(
|
|
3375
|
+
FocusMode,
|
|
3376
|
+
{
|
|
3377
|
+
label: focusLabel,
|
|
3378
|
+
durationSec: config2.board.focusDuration ?? 1500,
|
|
3379
|
+
onExit: onFocusExit,
|
|
3380
|
+
onEndAction: onFocusEndAction
|
|
3381
|
+
},
|
|
3382
|
+
focusKey
|
|
3383
|
+
) : null,
|
|
3384
|
+
mode === "overlay:label" && selectedIssue && defaultRepo ? /* @__PURE__ */ jsx12(
|
|
3385
|
+
LabelPicker,
|
|
3386
|
+
{
|
|
3387
|
+
repo: defaultRepo,
|
|
3388
|
+
currentLabels: selectedIssue.labels.map((l) => l.name),
|
|
3389
|
+
labelCache,
|
|
3390
|
+
onConfirm: onLabelConfirm,
|
|
3391
|
+
onCancel: onExitOverlay,
|
|
3392
|
+
onError: onLabelError
|
|
3393
|
+
}
|
|
3394
|
+
) : null,
|
|
3395
|
+
mode === "search" ? /* @__PURE__ */ jsx12(SearchBar, { defaultValue: searchQuery, onChange: onSearchChange, onSubmit: onSearchSubmit }) : null,
|
|
3396
|
+
mode === "overlay:comment" && selectedIssue ? /* @__PURE__ */ jsx12(
|
|
3397
|
+
CommentInput,
|
|
2285
3398
|
{
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
3399
|
+
issueNumber: selectedIssue.number,
|
|
3400
|
+
onSubmit: onComment,
|
|
3401
|
+
onCancel: onExitOverlay,
|
|
3402
|
+
onPauseRefresh,
|
|
3403
|
+
onResumeRefresh
|
|
2291
3404
|
}
|
|
2292
|
-
|
|
3405
|
+
) : null,
|
|
3406
|
+
mode === "overlay:createNl" ? /* @__PURE__ */ jsx12(
|
|
3407
|
+
NlCreateOverlay,
|
|
3408
|
+
{
|
|
3409
|
+
repos: config2.repos,
|
|
3410
|
+
defaultRepoName: defaultRepo,
|
|
3411
|
+
labelCache,
|
|
3412
|
+
onSubmit: onCreateIssue,
|
|
3413
|
+
onCancel: onExitOverlay,
|
|
3414
|
+
onLlmFallback
|
|
3415
|
+
}
|
|
3416
|
+
) : null
|
|
3417
|
+
] });
|
|
3418
|
+
}
|
|
3419
|
+
var init_overlay_renderer = __esm({
|
|
3420
|
+
"src/board/components/overlay-renderer.tsx"() {
|
|
3421
|
+
"use strict";
|
|
3422
|
+
init_bulk_action_menu();
|
|
3423
|
+
init_comment_input();
|
|
3424
|
+
init_confirm_prompt();
|
|
3425
|
+
init_create_issue_form();
|
|
3426
|
+
init_focus_mode();
|
|
3427
|
+
init_help_overlay();
|
|
3428
|
+
init_label_picker();
|
|
3429
|
+
init_nl_create_overlay();
|
|
3430
|
+
init_search_bar();
|
|
3431
|
+
init_status_picker();
|
|
2293
3432
|
}
|
|
2294
3433
|
});
|
|
2295
3434
|
|
|
2296
3435
|
// src/board/components/issue-row.tsx
|
|
2297
|
-
import { Box as
|
|
2298
|
-
import { Fragment as
|
|
3436
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
3437
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2299
3438
|
function truncate(s, max) {
|
|
2300
3439
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2301
3440
|
}
|
|
@@ -2336,30 +3475,30 @@ function IssueRow({ issue, selfLogin, isSelected }) {
|
|
|
2336
3475
|
const labels = (issue.labels ?? []).slice(0, 2);
|
|
2337
3476
|
const target = formatTargetDate(issue.targetDate);
|
|
2338
3477
|
const titleStr = truncate(issue.title, 42).padEnd(42);
|
|
2339
|
-
return /* @__PURE__ */
|
|
2340
|
-
isSelected ? /* @__PURE__ */
|
|
2341
|
-
/* @__PURE__ */
|
|
3478
|
+
return /* @__PURE__ */ jsxs13(Box12, { children: [
|
|
3479
|
+
isSelected ? /* @__PURE__ */ jsx13(Text12, { color: "cyan", bold: true, children: "\u25B6 " }) : /* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3480
|
+
/* @__PURE__ */ jsxs13(Text12, { color: "cyan", children: [
|
|
2342
3481
|
"#",
|
|
2343
3482
|
String(issue.number).padEnd(5)
|
|
2344
3483
|
] }),
|
|
2345
|
-
/* @__PURE__ */
|
|
2346
|
-
isSelected ? /* @__PURE__ */
|
|
2347
|
-
/* @__PURE__ */
|
|
2348
|
-
/* @__PURE__ */
|
|
3484
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3485
|
+
isSelected ? /* @__PURE__ */ jsx13(Text12, { color: "white", bold: true, children: titleStr }) : /* @__PURE__ */ jsx13(Text12, { children: titleStr }),
|
|
3486
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3487
|
+
/* @__PURE__ */ jsx13(Box12, { width: LABEL_COL_WIDTH, children: labels.map((l, i) => /* @__PURE__ */ jsxs13(Text12, { children: [
|
|
2349
3488
|
i > 0 ? " " : "",
|
|
2350
|
-
/* @__PURE__ */
|
|
3489
|
+
/* @__PURE__ */ jsxs13(Text12, { color: labelColor(l.name), children: [
|
|
2351
3490
|
"[",
|
|
2352
3491
|
truncate(l.name, 12),
|
|
2353
3492
|
"]"
|
|
2354
3493
|
] })
|
|
2355
3494
|
] }, l.name)) }),
|
|
2356
|
-
/* @__PURE__ */
|
|
2357
|
-
/* @__PURE__ */
|
|
2358
|
-
/* @__PURE__ */
|
|
2359
|
-
/* @__PURE__ */
|
|
2360
|
-
target.text ? /* @__PURE__ */
|
|
2361
|
-
/* @__PURE__ */
|
|
2362
|
-
/* @__PURE__ */
|
|
3495
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3496
|
+
/* @__PURE__ */ jsx13(Text12, { color: assigneeColor, children: assigneeText.padEnd(14) }),
|
|
3497
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3498
|
+
/* @__PURE__ */ jsx13(Text12, { color: "gray", children: timeAgo(issue.updatedAt).padStart(4) }),
|
|
3499
|
+
target.text ? /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
3500
|
+
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3501
|
+
/* @__PURE__ */ jsx13(Text12, { color: target.color, children: target.text })
|
|
2363
3502
|
] }) : null
|
|
2364
3503
|
] });
|
|
2365
3504
|
}
|
|
@@ -2382,90 +3521,13 @@ var init_issue_row = __esm({
|
|
|
2382
3521
|
}
|
|
2383
3522
|
});
|
|
2384
3523
|
|
|
2385
|
-
// src/board/components/search-bar.tsx
|
|
2386
|
-
import { TextInput as TextInput3 } from "@inkjs/ui";
|
|
2387
|
-
import { Box as Box9, Text as Text9 } from "ink";
|
|
2388
|
-
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2389
|
-
function SearchBar({ defaultValue, onChange, onSubmit }) {
|
|
2390
|
-
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2391
|
-
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "/" }),
|
|
2392
|
-
/* @__PURE__ */ jsx9(
|
|
2393
|
-
TextInput3,
|
|
2394
|
-
{
|
|
2395
|
-
defaultValue,
|
|
2396
|
-
placeholder: "search...",
|
|
2397
|
-
onChange,
|
|
2398
|
-
onSubmit
|
|
2399
|
-
}
|
|
2400
|
-
)
|
|
2401
|
-
] });
|
|
2402
|
-
}
|
|
2403
|
-
var init_search_bar = __esm({
|
|
2404
|
-
"src/board/components/search-bar.tsx"() {
|
|
2405
|
-
"use strict";
|
|
2406
|
-
}
|
|
2407
|
-
});
|
|
2408
|
-
|
|
2409
|
-
// src/board/components/status-picker.tsx
|
|
2410
|
-
import { Box as Box10, Text as Text10, useInput as useInput7 } from "ink";
|
|
2411
|
-
import { useState as useState8 } from "react";
|
|
2412
|
-
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2413
|
-
function StatusPicker({ options, currentStatus, onSelect, onCancel }) {
|
|
2414
|
-
const [selectedIdx, setSelectedIdx] = useState8(() => {
|
|
2415
|
-
const idx = options.findIndex((o) => o.name === currentStatus);
|
|
2416
|
-
return idx >= 0 ? idx : 0;
|
|
2417
|
-
});
|
|
2418
|
-
useInput7((input2, key) => {
|
|
2419
|
-
if (key.escape) return onCancel();
|
|
2420
|
-
if (key.return) {
|
|
2421
|
-
const opt = options[selectedIdx];
|
|
2422
|
-
if (opt) onSelect(opt.id);
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
if (input2 === "j" || key.downArrow) {
|
|
2426
|
-
setSelectedIdx((i) => Math.min(i + 1, options.length - 1));
|
|
2427
|
-
}
|
|
2428
|
-
if (input2 === "k" || key.upArrow) {
|
|
2429
|
-
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
2430
|
-
}
|
|
2431
|
-
});
|
|
2432
|
-
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2433
|
-
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "Move to status:" }),
|
|
2434
|
-
options.map((opt, i) => {
|
|
2435
|
-
const isCurrent = opt.name === currentStatus;
|
|
2436
|
-
const isSelected = i === selectedIdx;
|
|
2437
|
-
const prefix = isSelected ? "> " : " ";
|
|
2438
|
-
const suffix = isCurrent ? " (current)" : "";
|
|
2439
|
-
return /* @__PURE__ */ jsxs10(
|
|
2440
|
-
Text10,
|
|
2441
|
-
{
|
|
2442
|
-
...isSelected ? { color: "cyan" } : {},
|
|
2443
|
-
dimColor: isCurrent,
|
|
2444
|
-
children: [
|
|
2445
|
-
prefix,
|
|
2446
|
-
opt.name,
|
|
2447
|
-
suffix
|
|
2448
|
-
]
|
|
2449
|
-
},
|
|
2450
|
-
opt.id
|
|
2451
|
-
);
|
|
2452
|
-
}),
|
|
2453
|
-
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
2454
|
-
] });
|
|
2455
|
-
}
|
|
2456
|
-
var init_status_picker = __esm({
|
|
2457
|
-
"src/board/components/status-picker.tsx"() {
|
|
2458
|
-
"use strict";
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2461
|
-
|
|
2462
3524
|
// src/board/components/task-row.tsx
|
|
2463
|
-
import { Box as
|
|
2464
|
-
import { jsx as
|
|
3525
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
3526
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2465
3527
|
function truncate2(s, max) {
|
|
2466
3528
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2467
3529
|
}
|
|
2468
|
-
function
|
|
3530
|
+
function formatDue2(dateStr) {
|
|
2469
3531
|
if (!dateStr) return { text: "", color: "gray" };
|
|
2470
3532
|
const d = new Date(dateStr);
|
|
2471
3533
|
const days = Math.ceil((d.getTime() - Date.now()) / 864e5);
|
|
@@ -2480,15 +3542,15 @@ function formatDue(dateStr) {
|
|
|
2480
3542
|
}
|
|
2481
3543
|
function TaskRow({ task: task2, isSelected }) {
|
|
2482
3544
|
const pri = PRIORITY_INDICATORS[task2.priority] ?? DEFAULT_PRIORITY;
|
|
2483
|
-
const due =
|
|
3545
|
+
const due = formatDue2(task2.dueDate);
|
|
2484
3546
|
const titleStr = truncate2(task2.title, 45).padEnd(45);
|
|
2485
|
-
return /* @__PURE__ */
|
|
2486
|
-
isSelected ? /* @__PURE__ */
|
|
2487
|
-
/* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
isSelected ? /* @__PURE__ */
|
|
2490
|
-
/* @__PURE__ */
|
|
2491
|
-
/* @__PURE__ */
|
|
3547
|
+
return /* @__PURE__ */ jsxs14(Box13, { children: [
|
|
3548
|
+
isSelected ? /* @__PURE__ */ jsx14(Text13, { color: "cyan", bold: true, children: "\u25B6 " }) : /* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3549
|
+
/* @__PURE__ */ jsx14(Text13, { color: pri.color, children: pri.text }),
|
|
3550
|
+
/* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3551
|
+
isSelected ? /* @__PURE__ */ jsx14(Text13, { color: "white", bold: true, children: titleStr }) : /* @__PURE__ */ jsx14(Text13, { children: titleStr }),
|
|
3552
|
+
/* @__PURE__ */ jsx14(Text13, { children: " " }),
|
|
3553
|
+
/* @__PURE__ */ jsx14(Text13, { color: due.color, children: due.text })
|
|
2492
3554
|
] });
|
|
2493
3555
|
}
|
|
2494
3556
|
var PRIORITY_INDICATORS, DEFAULT_PRIORITY;
|
|
@@ -2506,23 +3568,128 @@ var init_task_row = __esm({
|
|
|
2506
3568
|
}
|
|
2507
3569
|
});
|
|
2508
3570
|
|
|
3571
|
+
// src/board/components/row-renderer.tsx
|
|
3572
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
3573
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3574
|
+
function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
3575
|
+
switch (row.type) {
|
|
3576
|
+
case "sectionHeader": {
|
|
3577
|
+
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
3578
|
+
const isSel = selectedId === row.navId;
|
|
3579
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3580
|
+
/* @__PURE__ */ jsxs15(Text14, { color: isSel ? "cyan" : "white", bold: true, children: [
|
|
3581
|
+
arrow,
|
|
3582
|
+
" ",
|
|
3583
|
+
row.label
|
|
3584
|
+
] }),
|
|
3585
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3586
|
+
" ",
|
|
3587
|
+
"(",
|
|
3588
|
+
row.count,
|
|
3589
|
+
" ",
|
|
3590
|
+
row.countLabel,
|
|
3591
|
+
")"
|
|
3592
|
+
] })
|
|
3593
|
+
] });
|
|
3594
|
+
}
|
|
3595
|
+
case "subHeader": {
|
|
3596
|
+
if (row.navId) {
|
|
3597
|
+
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
3598
|
+
const isSel = selectedId === row.navId;
|
|
3599
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3600
|
+
/* @__PURE__ */ jsxs15(Text14, { color: isSel ? "cyan" : "gray", children: [
|
|
3601
|
+
" ",
|
|
3602
|
+
arrow,
|
|
3603
|
+
" ",
|
|
3604
|
+
row.text
|
|
3605
|
+
] }),
|
|
3606
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3607
|
+
" (",
|
|
3608
|
+
row.count,
|
|
3609
|
+
")"
|
|
3610
|
+
] })
|
|
3611
|
+
] });
|
|
3612
|
+
}
|
|
3613
|
+
return /* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3614
|
+
" ",
|
|
3615
|
+
row.text
|
|
3616
|
+
] });
|
|
3617
|
+
}
|
|
3618
|
+
case "issue": {
|
|
3619
|
+
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
3620
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3621
|
+
checkbox2 ? /* @__PURE__ */ jsx15(Text14, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
3622
|
+
/* @__PURE__ */ jsx15(IssueRow, { issue: row.issue, selfLogin, isSelected: selectedId === row.navId })
|
|
3623
|
+
] });
|
|
3624
|
+
}
|
|
3625
|
+
case "task": {
|
|
3626
|
+
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
3627
|
+
return /* @__PURE__ */ jsxs15(Box14, { children: [
|
|
3628
|
+
checkbox2 ? /* @__PURE__ */ jsx15(Text14, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
3629
|
+
/* @__PURE__ */ jsx15(TaskRow, { task: row.task, isSelected: selectedId === row.navId })
|
|
3630
|
+
] });
|
|
3631
|
+
}
|
|
3632
|
+
case "activity": {
|
|
3633
|
+
const ago = timeAgo2(row.event.timestamp);
|
|
3634
|
+
return /* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
3635
|
+
" ",
|
|
3636
|
+
ago,
|
|
3637
|
+
": ",
|
|
3638
|
+
/* @__PURE__ */ jsxs15(Text14, { color: "gray", children: [
|
|
3639
|
+
"@",
|
|
3640
|
+
row.event.actor
|
|
3641
|
+
] }),
|
|
3642
|
+
" ",
|
|
3643
|
+
row.event.summary,
|
|
3644
|
+
" ",
|
|
3645
|
+
/* @__PURE__ */ jsxs15(Text14, { dimColor: true, children: [
|
|
3646
|
+
"(",
|
|
3647
|
+
row.event.repoShortName,
|
|
3648
|
+
")"
|
|
3649
|
+
] })
|
|
3650
|
+
] });
|
|
3651
|
+
}
|
|
3652
|
+
case "error":
|
|
3653
|
+
return /* @__PURE__ */ jsxs15(Text14, { color: "red", children: [
|
|
3654
|
+
" Error: ",
|
|
3655
|
+
row.text
|
|
3656
|
+
] });
|
|
3657
|
+
case "gap":
|
|
3658
|
+
return /* @__PURE__ */ jsx15(Text14, { children: "" });
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
function timeAgo2(date) {
|
|
3662
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
3663
|
+
if (seconds < 10) return "just now";
|
|
3664
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
3665
|
+
const minutes = Math.floor(seconds / 60);
|
|
3666
|
+
return `${minutes}m ago`;
|
|
3667
|
+
}
|
|
3668
|
+
var init_row_renderer = __esm({
|
|
3669
|
+
"src/board/components/row-renderer.tsx"() {
|
|
3670
|
+
"use strict";
|
|
3671
|
+
init_issue_row();
|
|
3672
|
+
init_task_row();
|
|
3673
|
+
}
|
|
3674
|
+
});
|
|
3675
|
+
|
|
2509
3676
|
// src/board/components/toast-container.tsx
|
|
2510
|
-
import { Spinner } from "@inkjs/ui";
|
|
2511
|
-
import { Box as
|
|
2512
|
-
import { Fragment as
|
|
3677
|
+
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
3678
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
3679
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2513
3680
|
function ToastContainer({ toasts }) {
|
|
2514
3681
|
if (toasts.length === 0) return null;
|
|
2515
|
-
return /* @__PURE__ */
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
/* @__PURE__ */
|
|
3682
|
+
return /* @__PURE__ */ jsx16(Box15, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx16(Box15, { children: t.type === "loading" ? /* @__PURE__ */ jsxs16(Fragment4, { children: [
|
|
3683
|
+
/* @__PURE__ */ jsx16(Spinner3, { label: "" }),
|
|
3684
|
+
/* @__PURE__ */ jsxs16(Text15, { color: "cyan", children: [
|
|
2518
3685
|
" ",
|
|
2519
3686
|
t.message
|
|
2520
3687
|
] })
|
|
2521
|
-
] }) : /* @__PURE__ */
|
|
3688
|
+
] }) : /* @__PURE__ */ jsxs16(Text15, { color: TYPE_COLORS[t.type], children: [
|
|
2522
3689
|
TYPE_PREFIXES[t.type],
|
|
2523
3690
|
" ",
|
|
2524
3691
|
t.message,
|
|
2525
|
-
t.type === "error" ? /* @__PURE__ */
|
|
3692
|
+
t.type === "error" ? /* @__PURE__ */ jsx16(Text15, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
2526
3693
|
] }) }, t.id)) });
|
|
2527
3694
|
}
|
|
2528
3695
|
var TYPE_COLORS, TYPE_PREFIXES;
|
|
@@ -2544,13 +3711,13 @@ var init_toast_container = __esm({
|
|
|
2544
3711
|
});
|
|
2545
3712
|
|
|
2546
3713
|
// src/board/components/dashboard.tsx
|
|
2547
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
2548
|
-
import { Spinner as
|
|
2549
|
-
import { Box as
|
|
2550
|
-
import { useCallback as
|
|
2551
|
-
import { Fragment as
|
|
3714
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync2 } from "child_process";
|
|
3715
|
+
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
3716
|
+
import { Box as Box16, Text as Text16, useApp, useStdout } from "ink";
|
|
3717
|
+
import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef11, useState as useState11 } from "react";
|
|
3718
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2552
3719
|
function isTerminalStatus(status) {
|
|
2553
|
-
return
|
|
3720
|
+
return TERMINAL_STATUS_RE3.test(status);
|
|
2554
3721
|
}
|
|
2555
3722
|
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
2556
3723
|
if (configuredGroups && configuredGroups.length > 0) {
|
|
@@ -2773,7 +3940,7 @@ function buildFlatRows(repos, tasks, activity, isCollapsed) {
|
|
|
2773
3940
|
}
|
|
2774
3941
|
return rows;
|
|
2775
3942
|
}
|
|
2776
|
-
function
|
|
3943
|
+
function timeAgo3(date) {
|
|
2777
3944
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
2778
3945
|
if (seconds < 10) return "just now";
|
|
2779
3946
|
if (seconds < 60) return `${seconds}s ago`;
|
|
@@ -2799,101 +3966,14 @@ function findSelectedIssueWithRepo(repos, selectedId) {
|
|
|
2799
3966
|
if (!selectedId?.startsWith("gh:")) return null;
|
|
2800
3967
|
for (const rd of repos) {
|
|
2801
3968
|
for (const issue of rd.issues) {
|
|
2802
|
-
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
2803
|
-
return { issue, repoName: rd.repo.name };
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
return null;
|
|
2807
|
-
}
|
|
2808
|
-
function isHeaderId(id) {
|
|
2809
|
-
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2810
|
-
}
|
|
2811
|
-
function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
2812
|
-
switch (row.type) {
|
|
2813
|
-
case "sectionHeader": {
|
|
2814
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2815
|
-
const isSel = selectedId === row.navId;
|
|
2816
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2817
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "white", bold: true, children: [
|
|
2818
|
-
arrow,
|
|
2819
|
-
" ",
|
|
2820
|
-
row.label
|
|
2821
|
-
] }),
|
|
2822
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2823
|
-
" ",
|
|
2824
|
-
"(",
|
|
2825
|
-
row.count,
|
|
2826
|
-
" ",
|
|
2827
|
-
row.countLabel,
|
|
2828
|
-
")"
|
|
2829
|
-
] })
|
|
2830
|
-
] });
|
|
2831
|
-
}
|
|
2832
|
-
case "subHeader": {
|
|
2833
|
-
if (row.navId) {
|
|
2834
|
-
const arrow = row.isCollapsed ? "\u25B6" : "\u25BC";
|
|
2835
|
-
const isSel = selectedId === row.navId;
|
|
2836
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2837
|
-
/* @__PURE__ */ jsxs13(Text13, { color: isSel ? "cyan" : "gray", children: [
|
|
2838
|
-
" ",
|
|
2839
|
-
arrow,
|
|
2840
|
-
" ",
|
|
2841
|
-
row.text
|
|
2842
|
-
] }),
|
|
2843
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2844
|
-
" (",
|
|
2845
|
-
row.count,
|
|
2846
|
-
")"
|
|
2847
|
-
] })
|
|
2848
|
-
] });
|
|
2849
|
-
}
|
|
2850
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2851
|
-
" ",
|
|
2852
|
-
row.text
|
|
2853
|
-
] });
|
|
2854
|
-
}
|
|
2855
|
-
case "issue": {
|
|
2856
|
-
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
2857
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2858
|
-
checkbox2 ? /* @__PURE__ */ jsx13(Text13, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
2859
|
-
/* @__PURE__ */ jsx13(IssueRow, { issue: row.issue, selfLogin, isSelected: selectedId === row.navId })
|
|
2860
|
-
] });
|
|
2861
|
-
}
|
|
2862
|
-
case "task": {
|
|
2863
|
-
const checkbox2 = isMultiSelected != null ? isMultiSelected ? "\u2611 " : "\u2610 " : "";
|
|
2864
|
-
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
2865
|
-
checkbox2 ? /* @__PURE__ */ jsx13(Text13, { color: isMultiSelected ? "cyan" : "gray", children: checkbox2 }) : null,
|
|
2866
|
-
/* @__PURE__ */ jsx13(TaskRow, { task: row.task, isSelected: selectedId === row.navId })
|
|
2867
|
-
] });
|
|
2868
|
-
}
|
|
2869
|
-
case "activity": {
|
|
2870
|
-
const ago = timeAgo2(row.event.timestamp);
|
|
2871
|
-
return /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
2872
|
-
" ",
|
|
2873
|
-
ago,
|
|
2874
|
-
": ",
|
|
2875
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
|
|
2876
|
-
"@",
|
|
2877
|
-
row.event.actor
|
|
2878
|
-
] }),
|
|
2879
|
-
" ",
|
|
2880
|
-
row.event.summary,
|
|
2881
|
-
" ",
|
|
2882
|
-
/* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
2883
|
-
"(",
|
|
2884
|
-
row.event.repoShortName,
|
|
2885
|
-
")"
|
|
2886
|
-
] })
|
|
2887
|
-
] });
|
|
3969
|
+
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
3970
|
+
return { issue, repoName: rd.repo.name };
|
|
2888
3971
|
}
|
|
2889
|
-
case "error":
|
|
2890
|
-
return /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
|
|
2891
|
-
" Error: ",
|
|
2892
|
-
row.text
|
|
2893
|
-
] });
|
|
2894
|
-
case "gap":
|
|
2895
|
-
return /* @__PURE__ */ jsx13(Text13, { children: "" });
|
|
2896
3972
|
}
|
|
3973
|
+
return null;
|
|
3974
|
+
}
|
|
3975
|
+
function isHeaderId2(id) {
|
|
3976
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2897
3977
|
}
|
|
2898
3978
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
2899
3979
|
const { exit } = useApp();
|
|
@@ -2907,7 +3987,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2907
3987
|
consecutiveFailures,
|
|
2908
3988
|
autoRefreshPaused,
|
|
2909
3989
|
refresh,
|
|
2910
|
-
mutateData
|
|
3990
|
+
mutateData,
|
|
3991
|
+
pauseAutoRefresh,
|
|
3992
|
+
resumeAutoRefresh
|
|
2911
3993
|
} = useData(config2, options, refreshMs);
|
|
2912
3994
|
const allRepos = useMemo2(() => data?.repos ?? [], [data?.repos]);
|
|
2913
3995
|
const allTasks = useMemo2(
|
|
@@ -2916,10 +3998,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2916
3998
|
);
|
|
2917
3999
|
const allActivity = useMemo2(() => data?.activity ?? [], [data?.activity]);
|
|
2918
4000
|
const ui = useUIState();
|
|
2919
|
-
const [searchQuery, setSearchQuery] =
|
|
4001
|
+
const [searchQuery, setSearchQuery] = useState11("");
|
|
2920
4002
|
const { toasts, toast, handleErrorAction } = useToast();
|
|
2921
|
-
const [, setTick] =
|
|
2922
|
-
|
|
4003
|
+
const [, setTick] = useState11(0);
|
|
4004
|
+
useEffect6(() => {
|
|
2923
4005
|
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
2924
4006
|
return () => clearInterval(id);
|
|
2925
4007
|
}, []);
|
|
@@ -2938,7 +4020,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2938
4020
|
[repos, tasks, allActivity.length]
|
|
2939
4021
|
);
|
|
2940
4022
|
const nav = useNavigation(navItems);
|
|
2941
|
-
const getRepoForId =
|
|
4023
|
+
const getRepoForId = useCallback10((id) => {
|
|
2942
4024
|
if (id.startsWith("gh:")) {
|
|
2943
4025
|
const parts = id.split(":");
|
|
2944
4026
|
return parts.length >= 3 ? `${parts[1]}` : null;
|
|
@@ -2947,7 +4029,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2947
4029
|
return null;
|
|
2948
4030
|
}, []);
|
|
2949
4031
|
const multiSelect = useMultiSelect(getRepoForId);
|
|
2950
|
-
|
|
4032
|
+
useEffect6(() => {
|
|
2951
4033
|
if (multiSelect.count === 0) return;
|
|
2952
4034
|
const validIds = new Set(navItems.map((i) => i.id));
|
|
2953
4035
|
multiSelect.prune(validIds);
|
|
@@ -2961,8 +4043,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2961
4043
|
mutateData,
|
|
2962
4044
|
onOverlayDone: ui.exitOverlay
|
|
2963
4045
|
});
|
|
2964
|
-
const pendingPickRef =
|
|
2965
|
-
const
|
|
4046
|
+
const pendingPickRef = useRef11(null);
|
|
4047
|
+
const labelCacheRef = useRef11({});
|
|
4048
|
+
const handleCreateIssueWithPrompt = useCallback10(
|
|
2966
4049
|
(repo, title, labels) => {
|
|
2967
4050
|
actions.handleCreateIssue(repo, title, labels).then((result) => {
|
|
2968
4051
|
if (result) {
|
|
@@ -2973,7 +4056,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2973
4056
|
},
|
|
2974
4057
|
[actions, ui]
|
|
2975
4058
|
);
|
|
2976
|
-
const handleConfirmPick =
|
|
4059
|
+
const handleConfirmPick = useCallback10(() => {
|
|
2977
4060
|
const pending = pendingPickRef.current;
|
|
2978
4061
|
pendingPickRef.current = null;
|
|
2979
4062
|
ui.exitOverlay();
|
|
@@ -2991,14 +4074,14 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
2991
4074
|
})
|
|
2992
4075
|
);
|
|
2993
4076
|
}, [config2, toast, refresh, ui]);
|
|
2994
|
-
const handleCancelPick =
|
|
4077
|
+
const handleCancelPick = useCallback10(() => {
|
|
2995
4078
|
pendingPickRef.current = null;
|
|
2996
4079
|
ui.exitOverlay();
|
|
2997
4080
|
}, [ui]);
|
|
2998
|
-
const [focusLabel, setFocusLabel] =
|
|
2999
|
-
const handleEnterFocus =
|
|
4081
|
+
const [focusLabel, setFocusLabel] = useState11(null);
|
|
4082
|
+
const handleEnterFocus = useCallback10(() => {
|
|
3000
4083
|
const id = nav.selectedId;
|
|
3001
|
-
if (!id ||
|
|
4084
|
+
if (!id || isHeaderId2(id)) return;
|
|
3002
4085
|
let label = "";
|
|
3003
4086
|
if (id.startsWith("gh:")) {
|
|
3004
4087
|
const found = findSelectedIssueWithRepo(repos, id);
|
|
@@ -3015,11 +4098,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3015
4098
|
setFocusLabel(label);
|
|
3016
4099
|
ui.enterFocus();
|
|
3017
4100
|
}, [nav.selectedId, repos, tasks, config2.repos, ui]);
|
|
3018
|
-
const handleFocusExit =
|
|
4101
|
+
const handleFocusExit = useCallback10(() => {
|
|
3019
4102
|
setFocusLabel(null);
|
|
3020
4103
|
ui.exitToNormal();
|
|
3021
4104
|
}, [ui]);
|
|
3022
|
-
const handleFocusEndAction =
|
|
4105
|
+
const handleFocusEndAction = useCallback10(
|
|
3023
4106
|
(action) => {
|
|
3024
4107
|
switch (action) {
|
|
3025
4108
|
case "restart":
|
|
@@ -3045,13 +4128,13 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3045
4128
|
},
|
|
3046
4129
|
[toast, ui]
|
|
3047
4130
|
);
|
|
3048
|
-
const [focusKey, setFocusKey] =
|
|
4131
|
+
const [focusKey, setFocusKey] = useState11(0);
|
|
3049
4132
|
const { stdout } = useStdout();
|
|
3050
|
-
const [termSize, setTermSize] =
|
|
4133
|
+
const [termSize, setTermSize] = useState11({
|
|
3051
4134
|
cols: stdout?.columns ?? 80,
|
|
3052
4135
|
rows: stdout?.rows ?? 24
|
|
3053
4136
|
});
|
|
3054
|
-
|
|
4137
|
+
useEffect6(() => {
|
|
3055
4138
|
if (!stdout) return;
|
|
3056
4139
|
const onResize = () => setTermSize({ cols: stdout.columns, rows: stdout.rows });
|
|
3057
4140
|
stdout.on("resize", onResize);
|
|
@@ -3068,7 +4151,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3068
4151
|
() => buildFlatRows(repos, tasks, allActivity, nav.isCollapsed),
|
|
3069
4152
|
[repos, tasks, allActivity, nav.isCollapsed]
|
|
3070
4153
|
);
|
|
3071
|
-
const scrollRef =
|
|
4154
|
+
const scrollRef = useRef11(0);
|
|
3072
4155
|
const selectedRowIdx = flatRows.findIndex((r) => r.navId === nav.selectedId);
|
|
3073
4156
|
if (selectedRowIdx >= 0) {
|
|
3074
4157
|
if (selectedRowIdx < scrollRef.current) {
|
|
@@ -3086,7 +4169,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3086
4169
|
const belowCount = flatRows.length - scrollRef.current - viewportHeight;
|
|
3087
4170
|
const selectedItem = useMemo2(() => {
|
|
3088
4171
|
const id = nav.selectedId;
|
|
3089
|
-
if (!id ||
|
|
4172
|
+
if (!id || isHeaderId2(id)) return { issue: null, task: null, repoName: null };
|
|
3090
4173
|
if (id.startsWith("gh:")) {
|
|
3091
4174
|
for (const rd of repos) {
|
|
3092
4175
|
for (const issue of rd.issues) {
|
|
@@ -3106,17 +4189,42 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3106
4189
|
const repoName = multiSelect.count > 0 ? multiSelect.constrainedRepo : selectedItem.repoName;
|
|
3107
4190
|
if (!repoName || repoName === "ticktick") return [];
|
|
3108
4191
|
const rd = repos.find((r) => r.repo.name === repoName);
|
|
3109
|
-
return rd?.statusOptions
|
|
4192
|
+
return rd?.statusOptions ?? [];
|
|
3110
4193
|
}, [selectedItem.repoName, repos, multiSelect.count, multiSelect.constrainedRepo]);
|
|
3111
|
-
const handleOpen =
|
|
4194
|
+
const handleOpen = useCallback10(() => {
|
|
3112
4195
|
const url = findSelectedUrl(repos, nav.selectedId);
|
|
3113
4196
|
if (url) openInBrowser(url);
|
|
3114
4197
|
}, [repos, nav.selectedId]);
|
|
3115
|
-
const handleSlack =
|
|
4198
|
+
const handleSlack = useCallback10(() => {
|
|
3116
4199
|
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
3117
4200
|
if (!found?.issue.slackThreadUrl) return;
|
|
3118
4201
|
openInBrowser(found.issue.slackThreadUrl);
|
|
3119
4202
|
}, [repos, nav.selectedId]);
|
|
4203
|
+
const handleCopyLink = useCallback10(() => {
|
|
4204
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
4205
|
+
if (!found) return;
|
|
4206
|
+
const rc = config2.repos.find((r) => r.name === found.repoName);
|
|
4207
|
+
const label = `${rc?.shortName ?? found.repoName}#${found.issue.number}`;
|
|
4208
|
+
const clipArgs = getClipboardArgs();
|
|
4209
|
+
if (clipArgs) {
|
|
4210
|
+
const [cmd, ...args] = clipArgs;
|
|
4211
|
+
if (!cmd) {
|
|
4212
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
const result = spawnSync2(cmd, args, {
|
|
4216
|
+
input: found.issue.url,
|
|
4217
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4218
|
+
});
|
|
4219
|
+
if (result.status === 0) {
|
|
4220
|
+
toast.success(`Copied ${label} to clipboard`);
|
|
4221
|
+
} else {
|
|
4222
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4223
|
+
}
|
|
4224
|
+
} else {
|
|
4225
|
+
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
4226
|
+
}
|
|
4227
|
+
}, [repos, nav.selectedId, config2.repos, toast]);
|
|
3120
4228
|
const multiSelectType = useMemo2(() => {
|
|
3121
4229
|
let hasGh = false;
|
|
3122
4230
|
let hasTt = false;
|
|
@@ -3128,7 +4236,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3128
4236
|
if (hasTt) return "ticktick";
|
|
3129
4237
|
return "github";
|
|
3130
4238
|
}, [multiSelect.selected]);
|
|
3131
|
-
const handleBulkAction =
|
|
4239
|
+
const handleBulkAction = useCallback10(
|
|
3132
4240
|
(action) => {
|
|
3133
4241
|
const ids = multiSelect.selected;
|
|
3134
4242
|
switch (action.type) {
|
|
@@ -3172,7 +4280,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3172
4280
|
},
|
|
3173
4281
|
[multiSelect, actions, ui, toast]
|
|
3174
4282
|
);
|
|
3175
|
-
const handleBulkStatusSelect =
|
|
4283
|
+
const handleBulkStatusSelect = useCallback10(
|
|
3176
4284
|
(optionId) => {
|
|
3177
4285
|
const ids = multiSelect.selected;
|
|
3178
4286
|
ui.exitOverlay();
|
|
@@ -3188,168 +4296,35 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3188
4296
|
},
|
|
3189
4297
|
[multiSelect, actions, ui]
|
|
3190
4298
|
);
|
|
3191
|
-
const
|
|
3192
|
-
(
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
return;
|
|
3203
|
-
}
|
|
3204
|
-
if (ui.canNavigate) {
|
|
3205
|
-
if (input2 === "j" || key.downArrow) {
|
|
3206
|
-
nav.moveDown();
|
|
3207
|
-
return;
|
|
3208
|
-
}
|
|
3209
|
-
if (input2 === "k" || key.upArrow) {
|
|
3210
|
-
nav.moveUp();
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
if (key.tab) {
|
|
3214
|
-
if (ui.state.mode === "multiSelect") {
|
|
3215
|
-
multiSelect.clear();
|
|
3216
|
-
ui.clearMultiSelect();
|
|
3217
|
-
}
|
|
3218
|
-
key.shift ? nav.prevSection() : nav.nextSection();
|
|
3219
|
-
return;
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
if (ui.state.mode === "multiSelect") {
|
|
3223
|
-
if (input2 === " ") {
|
|
3224
|
-
const id = nav.selectedId;
|
|
3225
|
-
if (id && !isHeaderId(id)) {
|
|
3226
|
-
multiSelect.toggle(id);
|
|
3227
|
-
}
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
if (key.return) {
|
|
3231
|
-
if (multiSelect.count > 0) {
|
|
3232
|
-
ui.enterBulkAction();
|
|
3233
|
-
}
|
|
3234
|
-
return;
|
|
3235
|
-
}
|
|
3236
|
-
if (input2 === "m" && multiSelect.count > 0) {
|
|
3237
|
-
ui.enterBulkAction();
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
if (input2 === "d") {
|
|
3243
|
-
if (handleErrorAction("dismiss")) return;
|
|
3244
|
-
}
|
|
3245
|
-
if (input2 === "r" && handleErrorAction("retry")) return;
|
|
3246
|
-
if (ui.canAct) {
|
|
3247
|
-
if (input2 === "/") {
|
|
3248
|
-
multiSelect.clear();
|
|
3249
|
-
ui.enterSearch();
|
|
3250
|
-
return;
|
|
3251
|
-
}
|
|
3252
|
-
if (input2 === "q") {
|
|
3253
|
-
exit();
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
if (input2 === "r" || input2 === "R") {
|
|
3257
|
-
multiSelect.clear();
|
|
3258
|
-
refresh();
|
|
3259
|
-
return;
|
|
3260
|
-
}
|
|
3261
|
-
if (input2 === "s") {
|
|
3262
|
-
handleSlack();
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
if (input2 === "p") {
|
|
3266
|
-
actions.handlePick();
|
|
3267
|
-
return;
|
|
3268
|
-
}
|
|
3269
|
-
if (input2 === "a") {
|
|
3270
|
-
actions.handleAssign();
|
|
3271
|
-
return;
|
|
3272
|
-
}
|
|
3273
|
-
if (input2 === "u") {
|
|
3274
|
-
actions.handleUnassign();
|
|
3275
|
-
return;
|
|
3276
|
-
}
|
|
3277
|
-
if (input2 === "c") {
|
|
3278
|
-
if (selectedItem.issue) {
|
|
3279
|
-
multiSelect.clear();
|
|
3280
|
-
ui.enterComment();
|
|
3281
|
-
}
|
|
3282
|
-
return;
|
|
3283
|
-
}
|
|
3284
|
-
if (input2 === "m") {
|
|
3285
|
-
if (selectedItem.issue && selectedRepoStatusOptions.length > 0) {
|
|
3286
|
-
multiSelect.clear();
|
|
3287
|
-
ui.enterStatus();
|
|
3288
|
-
} else if (selectedItem.issue) {
|
|
3289
|
-
toast.info("Issue not in a project board");
|
|
3290
|
-
}
|
|
3291
|
-
return;
|
|
3292
|
-
}
|
|
3293
|
-
if (input2 === "n") {
|
|
3294
|
-
multiSelect.clear();
|
|
3295
|
-
ui.enterCreate();
|
|
3296
|
-
return;
|
|
3297
|
-
}
|
|
3298
|
-
if (input2 === "f") {
|
|
3299
|
-
handleEnterFocus();
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
|
-
if (input2 === " ") {
|
|
3303
|
-
const id = nav.selectedId;
|
|
3304
|
-
if (id && !isHeaderId(id)) {
|
|
3305
|
-
multiSelect.toggle(id);
|
|
3306
|
-
ui.enterMultiSelect();
|
|
3307
|
-
} else if (isHeaderId(nav.selectedId)) {
|
|
3308
|
-
nav.toggleSection();
|
|
3309
|
-
}
|
|
3310
|
-
return;
|
|
3311
|
-
}
|
|
3312
|
-
if (key.return) {
|
|
3313
|
-
if (isHeaderId(nav.selectedId)) {
|
|
3314
|
-
nav.toggleSection();
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
handleOpen();
|
|
3318
|
-
return;
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
},
|
|
3322
|
-
[
|
|
3323
|
-
ui,
|
|
3324
|
-
nav,
|
|
4299
|
+
const onSearchEscape = useCallback10(() => {
|
|
4300
|
+
ui.exitOverlay();
|
|
4301
|
+
setSearchQuery("");
|
|
4302
|
+
}, [ui]);
|
|
4303
|
+
useKeyboard({
|
|
4304
|
+
ui,
|
|
4305
|
+
nav,
|
|
4306
|
+
multiSelect,
|
|
4307
|
+
selectedIssue: selectedItem.issue,
|
|
4308
|
+
selectedRepoStatusOptionsLength: selectedRepoStatusOptions.length,
|
|
4309
|
+
actions: {
|
|
3325
4310
|
exit,
|
|
3326
4311
|
refresh,
|
|
3327
4312
|
handleSlack,
|
|
4313
|
+
handleCopyLink,
|
|
3328
4314
|
handleOpen,
|
|
3329
|
-
actions,
|
|
3330
|
-
selectedItem.issue,
|
|
3331
|
-
selectedRepoStatusOptions.length,
|
|
3332
|
-
toast,
|
|
3333
|
-
nav.selectedId,
|
|
3334
|
-
multiSelect,
|
|
3335
4315
|
handleEnterFocus,
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
if (key.escape) {
|
|
3344
|
-
ui.exitOverlay();
|
|
3345
|
-
setSearchQuery("");
|
|
3346
|
-
}
|
|
4316
|
+
handlePick: actions.handlePick,
|
|
4317
|
+
handleAssign: actions.handleAssign,
|
|
4318
|
+
handleUnassign: actions.handleUnassign,
|
|
4319
|
+
handleEnterLabel: ui.enterLabel,
|
|
4320
|
+
handleEnterCreateNl: ui.enterCreateNl,
|
|
4321
|
+
handleErrorAction,
|
|
4322
|
+
toastInfo: toast.info
|
|
3347
4323
|
},
|
|
3348
|
-
|
|
3349
|
-
);
|
|
3350
|
-
useInput8(handleSearchEscape, { isActive: ui.state.mode === "search" });
|
|
4324
|
+
onSearchEscape
|
|
4325
|
+
});
|
|
3351
4326
|
if (status === "loading" && !data) {
|
|
3352
|
-
return /* @__PURE__ */
|
|
4327
|
+
return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx17(Spinner4, { label: "Loading dashboard..." }) });
|
|
3353
4328
|
}
|
|
3354
4329
|
const now = data?.fetchedAt ?? /* @__PURE__ */ new Date();
|
|
3355
4330
|
const dateStr = now.toLocaleDateString("en-US", {
|
|
@@ -3357,93 +4332,81 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3357
4332
|
day: "numeric",
|
|
3358
4333
|
year: "numeric"
|
|
3359
4334
|
});
|
|
3360
|
-
return /* @__PURE__ */
|
|
3361
|
-
/* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
3363
|
-
activeProfile ? /* @__PURE__ */
|
|
4335
|
+
return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", paddingX: 1, children: [
|
|
4336
|
+
/* @__PURE__ */ jsxs17(Box16, { children: [
|
|
4337
|
+
/* @__PURE__ */ jsx17(Text16, { color: "cyan", bold: true, children: "HOG BOARD" }),
|
|
4338
|
+
activeProfile ? /* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
3364
4339
|
" [",
|
|
3365
4340
|
activeProfile,
|
|
3366
4341
|
"]"
|
|
3367
4342
|
] }) : null,
|
|
3368
|
-
/* @__PURE__ */
|
|
4343
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "gray", children: [
|
|
3369
4344
|
" ",
|
|
3370
4345
|
"\u2014",
|
|
3371
4346
|
" ",
|
|
3372
4347
|
dateStr
|
|
3373
4348
|
] }),
|
|
3374
|
-
/* @__PURE__ */
|
|
3375
|
-
isRefreshing ? /* @__PURE__ */
|
|
3376
|
-
/* @__PURE__ */
|
|
3377
|
-
/* @__PURE__ */
|
|
3378
|
-
] }) : lastRefresh ? /* @__PURE__ */
|
|
3379
|
-
/* @__PURE__ */
|
|
4349
|
+
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4350
|
+
isRefreshing ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4351
|
+
/* @__PURE__ */ jsx17(Spinner4, { label: "" }),
|
|
4352
|
+
/* @__PURE__ */ jsx17(Text16, { color: "cyan", children: " Refreshing..." })
|
|
4353
|
+
] }) : lastRefresh ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4354
|
+
/* @__PURE__ */ jsxs17(Text16, { color: refreshAgeColor(lastRefresh), children: [
|
|
3380
4355
|
"Updated ",
|
|
3381
|
-
|
|
4356
|
+
timeAgo3(lastRefresh)
|
|
3382
4357
|
] }),
|
|
3383
|
-
consecutiveFailures > 0 ? /* @__PURE__ */
|
|
4358
|
+
consecutiveFailures > 0 ? /* @__PURE__ */ jsx17(Text16, { color: "red", children: " (!)" }) : null
|
|
3384
4359
|
] }) : null,
|
|
3385
|
-
autoRefreshPaused ? /* @__PURE__ */
|
|
4360
|
+
autoRefreshPaused ? /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
|
|
3386
4361
|
] }),
|
|
3387
|
-
error ? /* @__PURE__ */
|
|
4362
|
+
error ? /* @__PURE__ */ jsxs17(Text16, { color: "red", children: [
|
|
3388
4363
|
"Error: ",
|
|
3389
4364
|
error
|
|
3390
4365
|
] }) : null,
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
StatusPicker,
|
|
4366
|
+
/* @__PURE__ */ jsx17(
|
|
4367
|
+
OverlayRenderer,
|
|
3394
4368
|
{
|
|
3395
|
-
|
|
4369
|
+
uiState: ui.state,
|
|
4370
|
+
config: config2,
|
|
4371
|
+
selectedRepoStatusOptions,
|
|
3396
4372
|
currentStatus: multiSelect.count > 0 ? void 0 : selectedItem.issue?.projectStatus,
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
}
|
|
3400
|
-
) : null,
|
|
3401
|
-
ui.state.mode === "overlay:create" ? /* @__PURE__ */ jsx13(
|
|
3402
|
-
CreateIssueForm,
|
|
3403
|
-
{
|
|
3404
|
-
repos: config2.repos,
|
|
4373
|
+
onStatusSelect: multiSelect.count > 0 ? handleBulkStatusSelect : actions.handleStatusChange,
|
|
4374
|
+
onExitOverlay: ui.exitOverlay,
|
|
3405
4375
|
defaultRepo: selectedItem.repoName,
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
4376
|
+
onCreateIssue: handleCreateIssueWithPrompt,
|
|
4377
|
+
onConfirmPick: handleConfirmPick,
|
|
4378
|
+
onCancelPick: handleCancelPick,
|
|
4379
|
+
multiSelectCount: multiSelect.count,
|
|
4380
|
+
multiSelectType,
|
|
4381
|
+
onBulkAction: handleBulkAction,
|
|
4382
|
+
focusLabel,
|
|
4383
|
+
focusKey,
|
|
4384
|
+
onFocusExit: handleFocusExit,
|
|
4385
|
+
onFocusEndAction: handleFocusEndAction,
|
|
4386
|
+
searchQuery,
|
|
4387
|
+
onSearchChange: setSearchQuery,
|
|
4388
|
+
onSearchSubmit: ui.exitOverlay,
|
|
4389
|
+
selectedIssue: selectedItem.issue,
|
|
4390
|
+
onComment: actions.handleComment,
|
|
4391
|
+
onPauseRefresh: pauseAutoRefresh,
|
|
4392
|
+
onResumeRefresh: resumeAutoRefresh,
|
|
4393
|
+
onToggleHelp: ui.toggleHelp,
|
|
4394
|
+
labelCache: labelCacheRef.current,
|
|
4395
|
+
onLabelConfirm: actions.handleLabelChange,
|
|
4396
|
+
onLabelError: (msg) => toast.error(msg),
|
|
4397
|
+
onLlmFallback: (msg) => toast.info(msg)
|
|
3425
4398
|
}
|
|
3426
|
-
)
|
|
3427
|
-
ui.state.mode
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
label: focusLabel,
|
|
3431
|
-
durationSec: config2.board.focusDuration ?? 1500,
|
|
3432
|
-
onExit: handleFocusExit,
|
|
3433
|
-
onEndAction: handleFocusEndAction
|
|
3434
|
-
},
|
|
3435
|
-
focusKey
|
|
3436
|
-
) : null,
|
|
3437
|
-
!ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs13(Box13, { height: viewportHeight, children: [
|
|
3438
|
-
/* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
|
|
3439
|
-
hasMoreAbove ? /* @__PURE__ */ jsxs13(Text13, { color: "gray", dimColor: true, children: [
|
|
4399
|
+
),
|
|
4400
|
+
!ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:createNl" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs17(Box16, { height: viewportHeight, children: [
|
|
4401
|
+
/* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexGrow: 1, children: [
|
|
4402
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs17(Text16, { color: "gray", dimColor: true, children: [
|
|
3440
4403
|
" ",
|
|
3441
4404
|
"\u25B2",
|
|
3442
4405
|
" ",
|
|
3443
4406
|
aboveCount,
|
|
3444
4407
|
" more above"
|
|
3445
4408
|
] }) : null,
|
|
3446
|
-
visibleRows.map((row) => /* @__PURE__ */
|
|
4409
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx17(
|
|
3447
4410
|
RowRenderer,
|
|
3448
4411
|
{
|
|
3449
4412
|
row,
|
|
@@ -3453,7 +4416,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3453
4416
|
},
|
|
3454
4417
|
row.key
|
|
3455
4418
|
)),
|
|
3456
|
-
hasMoreBelow ? /* @__PURE__ */
|
|
4419
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs17(Text16, { color: "gray", dimColor: true, children: [
|
|
3457
4420
|
" ",
|
|
3458
4421
|
"\u25BC",
|
|
3459
4422
|
" ",
|
|
@@ -3461,7 +4424,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3461
4424
|
" more below"
|
|
3462
4425
|
] }) : null
|
|
3463
4426
|
] }),
|
|
3464
|
-
showDetailPanel ? /* @__PURE__ */
|
|
4427
|
+
showDetailPanel ? /* @__PURE__ */ jsx17(Box16, { marginLeft: 1, width: detailPanelWidth, children: /* @__PURE__ */ jsx17(
|
|
3465
4428
|
DetailPanel,
|
|
3466
4429
|
{
|
|
3467
4430
|
issue: selectedItem.issue,
|
|
@@ -3470,25 +4433,16 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3470
4433
|
}
|
|
3471
4434
|
) }) : null
|
|
3472
4435
|
] }) : null,
|
|
3473
|
-
|
|
3474
|
-
ui.state.mode === "
|
|
3475
|
-
|
|
3476
|
-
{
|
|
3477
|
-
issueNumber: selectedItem.issue.number,
|
|
3478
|
-
onSubmit: actions.handleComment,
|
|
3479
|
-
onCancel: ui.exitOverlay
|
|
3480
|
-
}
|
|
3481
|
-
) : null,
|
|
3482
|
-
/* @__PURE__ */ jsx13(ToastContainer, { toasts }),
|
|
3483
|
-
/* @__PURE__ */ jsx13(Box13, { children: ui.state.mode === "multiSelect" ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
3484
|
-
/* @__PURE__ */ jsxs13(Text13, { color: "cyan", bold: true, children: [
|
|
4436
|
+
/* @__PURE__ */ jsx17(ToastContainer, { toasts }),
|
|
4437
|
+
/* @__PURE__ */ jsx17(Box16, { children: ui.state.mode === "multiSelect" ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4438
|
+
/* @__PURE__ */ jsxs17(Text16, { color: "cyan", bold: true, children: [
|
|
3485
4439
|
multiSelect.count,
|
|
3486
4440
|
" selected"
|
|
3487
4441
|
] }),
|
|
3488
|
-
/* @__PURE__ */
|
|
3489
|
-
] }) : ui.state.mode === "focus" ? /* @__PURE__ */
|
|
3490
|
-
/* @__PURE__ */
|
|
3491
|
-
searchQuery && ui.state.mode !== "search" ? /* @__PURE__ */
|
|
4442
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: " Space:toggle Enter:actions Esc:cancel" })
|
|
4443
|
+
] }) : ui.state.mode === "focus" ? /* @__PURE__ */ jsx17(Text16, { color: "magenta", bold: true, children: "Focus mode \u2014 Esc to exit" }) : /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
4444
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: "j/k:nav Tab:section Enter:open Space:select /:search p:pick c:comment m:status a/u:assign s:slack y:copy l:labels n:new I:nlcreate C:collapse f:focus ?:help q:quit" }),
|
|
4445
|
+
searchQuery && ui.state.mode !== "search" ? /* @__PURE__ */ jsxs17(Text16, { color: "yellow", children: [
|
|
3492
4446
|
' filter: "',
|
|
3493
4447
|
searchQuery,
|
|
3494
4448
|
'"'
|
|
@@ -3496,29 +4450,23 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
3496
4450
|
] }) })
|
|
3497
4451
|
] });
|
|
3498
4452
|
}
|
|
3499
|
-
var
|
|
4453
|
+
var TERMINAL_STATUS_RE3, PRIORITY_RANK, CHROME_ROWS;
|
|
3500
4454
|
var init_dashboard = __esm({
|
|
3501
4455
|
"src/board/components/dashboard.tsx"() {
|
|
3502
4456
|
"use strict";
|
|
4457
|
+
init_clipboard();
|
|
3503
4458
|
init_use_actions();
|
|
3504
4459
|
init_use_data();
|
|
4460
|
+
init_use_keyboard();
|
|
3505
4461
|
init_use_multi_select();
|
|
3506
4462
|
init_use_navigation();
|
|
3507
4463
|
init_use_toast();
|
|
3508
4464
|
init_use_ui_state();
|
|
3509
|
-
init_bulk_action_menu();
|
|
3510
|
-
init_comment_input();
|
|
3511
|
-
init_confirm_prompt();
|
|
3512
|
-
init_create_issue_form();
|
|
3513
4465
|
init_detail_panel();
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
init_issue_row();
|
|
3517
|
-
init_search_bar();
|
|
3518
|
-
init_status_picker();
|
|
3519
|
-
init_task_row();
|
|
4466
|
+
init_overlay_renderer();
|
|
4467
|
+
init_row_renderer();
|
|
3520
4468
|
init_toast_container();
|
|
3521
|
-
|
|
4469
|
+
TERMINAL_STATUS_RE3 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
3522
4470
|
PRIORITY_RANK = {
|
|
3523
4471
|
"priority:critical": 0,
|
|
3524
4472
|
"priority:high": 1,
|
|
@@ -3535,17 +4483,19 @@ __export(live_exports, {
|
|
|
3535
4483
|
runLiveDashboard: () => runLiveDashboard
|
|
3536
4484
|
});
|
|
3537
4485
|
import { render } from "ink";
|
|
3538
|
-
import { jsx as
|
|
4486
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
3539
4487
|
async function runLiveDashboard(config2, options, activeProfile) {
|
|
3540
|
-
const
|
|
3541
|
-
/* @__PURE__ */
|
|
4488
|
+
const instance = render(
|
|
4489
|
+
/* @__PURE__ */ jsx18(Dashboard, { config: config2, options, activeProfile: activeProfile ?? null })
|
|
3542
4490
|
);
|
|
3543
|
-
|
|
4491
|
+
setInkInstance(instance);
|
|
4492
|
+
await instance.waitUntilExit();
|
|
3544
4493
|
}
|
|
3545
4494
|
var init_live = __esm({
|
|
3546
4495
|
"src/board/live.tsx"() {
|
|
3547
4496
|
"use strict";
|
|
3548
4497
|
init_dashboard();
|
|
4498
|
+
init_ink_instance();
|
|
3549
4499
|
}
|
|
3550
4500
|
});
|
|
3551
4501
|
|
|
@@ -3911,8 +4861,10 @@ var init_format_static = __esm({
|
|
|
3911
4861
|
});
|
|
3912
4862
|
|
|
3913
4863
|
// src/cli.ts
|
|
4864
|
+
init_ai();
|
|
3914
4865
|
init_api();
|
|
3915
4866
|
init_config();
|
|
4867
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
3916
4868
|
import { Command } from "commander";
|
|
3917
4869
|
|
|
3918
4870
|
// src/init.ts
|
|
@@ -4160,6 +5112,28 @@ Configuring ${repoName}...`);
|
|
|
4160
5112
|
message: " Focus timer duration (seconds):",
|
|
4161
5113
|
default: "1500"
|
|
4162
5114
|
});
|
|
5115
|
+
console.log("\nAI-enhanced issue creation (optional):");
|
|
5116
|
+
console.log(
|
|
5117
|
+
' Press I on the board to create issues with natural language (e.g. "fix login bug #backend @alice due friday").'
|
|
5118
|
+
);
|
|
5119
|
+
console.log(" Without a key the heuristic parser still works \u2014 labels, assignee, and due dates");
|
|
5120
|
+
console.log(" are extracted from #, @, and due tokens. An OpenRouter key enables richer title");
|
|
5121
|
+
console.log(" cleanup and inference for ambiguous input.");
|
|
5122
|
+
const setupLlm = await confirm({
|
|
5123
|
+
message: " Set up an OpenRouter API key now?",
|
|
5124
|
+
default: false
|
|
5125
|
+
});
|
|
5126
|
+
if (setupLlm) {
|
|
5127
|
+
console.log(" Get a free key at https://openrouter.ai/keys");
|
|
5128
|
+
const llmKey = await input({
|
|
5129
|
+
message: " OpenRouter API key:",
|
|
5130
|
+
validate: (v) => v.trim().startsWith("sk-or-") ? true : 'Key must start with "sk-or-"'
|
|
5131
|
+
});
|
|
5132
|
+
saveLlmAuth(llmKey.trim());
|
|
5133
|
+
console.log(" OpenRouter key saved to ~/.config/hog/auth.json");
|
|
5134
|
+
} else {
|
|
5135
|
+
console.log(" Skipped. You can add it later: hog config ai:set-key");
|
|
5136
|
+
}
|
|
4163
5137
|
const existingConfig = configExists ? loadFullConfig() : void 0;
|
|
4164
5138
|
const config2 = {
|
|
4165
5139
|
version: 3,
|
|
@@ -4582,7 +5556,7 @@ function resolveProjectId(projectId) {
|
|
|
4582
5556
|
process.exit(1);
|
|
4583
5557
|
}
|
|
4584
5558
|
var program = new Command();
|
|
4585
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
5559
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.4.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
4586
5560
|
const opts = thisCommand.opts();
|
|
4587
5561
|
if (opts.json) setFormat("json");
|
|
4588
5562
|
if (opts.human) setFormat("human");
|
|
@@ -4861,6 +5835,61 @@ config.command("ticktick:disable").description("Disable TickTick integration in
|
|
|
4861
5835
|
printSuccess("TickTick integration disabled. Board will no longer show TickTick tasks.");
|
|
4862
5836
|
}
|
|
4863
5837
|
});
|
|
5838
|
+
config.command("ai:set-key <key>").description("Store an OpenRouter API key for AI-enhanced issue creation (I key on board)").action((key) => {
|
|
5839
|
+
if (!key.startsWith("sk-or-")) {
|
|
5840
|
+
console.error('Error: key must start with "sk-or-". Get one at https://openrouter.ai/keys');
|
|
5841
|
+
process.exit(1);
|
|
5842
|
+
}
|
|
5843
|
+
saveLlmAuth(key);
|
|
5844
|
+
if (useJson()) {
|
|
5845
|
+
jsonOut({ ok: true, message: "OpenRouter key saved" });
|
|
5846
|
+
} else {
|
|
5847
|
+
printSuccess("OpenRouter key saved to ~/.config/hog/auth.json");
|
|
5848
|
+
console.log(" Press I on the board to create issues with natural language.");
|
|
5849
|
+
}
|
|
5850
|
+
});
|
|
5851
|
+
config.command("ai:clear-key").description("Remove the stored OpenRouter API key").action(() => {
|
|
5852
|
+
const existing = getLlmAuth();
|
|
5853
|
+
if (!existing) {
|
|
5854
|
+
if (useJson()) {
|
|
5855
|
+
jsonOut({ ok: true, message: "No key was stored" });
|
|
5856
|
+
} else {
|
|
5857
|
+
console.log("No OpenRouter key stored.");
|
|
5858
|
+
}
|
|
5859
|
+
return;
|
|
5860
|
+
}
|
|
5861
|
+
clearLlmAuth();
|
|
5862
|
+
if (useJson()) {
|
|
5863
|
+
jsonOut({ ok: true, message: "OpenRouter key removed" });
|
|
5864
|
+
} else {
|
|
5865
|
+
printSuccess("OpenRouter key removed from ~/.config/hog/auth.json");
|
|
5866
|
+
}
|
|
5867
|
+
});
|
|
5868
|
+
config.command("ai:status").description("Show whether AI-enhanced issue creation is available and which source provides it").action(() => {
|
|
5869
|
+
const envOr = process.env["OPENROUTER_API_KEY"];
|
|
5870
|
+
const envAnt = process.env["ANTHROPIC_API_KEY"];
|
|
5871
|
+
const stored = getLlmAuth();
|
|
5872
|
+
if (useJson()) {
|
|
5873
|
+
jsonOut({
|
|
5874
|
+
ok: true,
|
|
5875
|
+
data: {
|
|
5876
|
+
active: !!(envOr ?? envAnt ?? stored),
|
|
5877
|
+
source: envOr ? "env:OPENROUTER_API_KEY" : envAnt ? "env:ANTHROPIC_API_KEY" : stored ? "config:auth.json" : null,
|
|
5878
|
+
provider: envOr ? "openrouter" : envAnt ? "anthropic" : stored ? "openrouter" : null
|
|
5879
|
+
}
|
|
5880
|
+
});
|
|
5881
|
+
} else if (envOr) {
|
|
5882
|
+
console.log("AI: active (source: OPENROUTER_API_KEY env var, provider: openrouter)");
|
|
5883
|
+
} else if (envAnt) {
|
|
5884
|
+
console.log("AI: active (source: ANTHROPIC_API_KEY env var, provider: anthropic)");
|
|
5885
|
+
} else if (stored) {
|
|
5886
|
+
console.log("AI: active (source: ~/.config/hog/auth.json, provider: openrouter)");
|
|
5887
|
+
} else {
|
|
5888
|
+
console.log("AI: off \u2014 heuristic-only mode");
|
|
5889
|
+
console.log(" Enable with: hog config ai:set-key <sk-or-...>");
|
|
5890
|
+
console.log(" Or set env: export OPENROUTER_API_KEY=sk-or-...");
|
|
5891
|
+
}
|
|
5892
|
+
});
|
|
4864
5893
|
config.command("profile:create <name>").description("Create a board profile (copies current top-level config)").action((name) => {
|
|
4865
5894
|
const cfg = loadFullConfig();
|
|
4866
5895
|
if (cfg.profiles[name]) {
|
|
@@ -4931,6 +5960,53 @@ config.command("profile:default [name]").description("Set or show the default bo
|
|
|
4931
5960
|
printSuccess(`Default profile set to "${name}".`);
|
|
4932
5961
|
}
|
|
4933
5962
|
});
|
|
5963
|
+
var issueCommand = new Command("issue").description("GitHub issue utilities");
|
|
5964
|
+
issueCommand.command("create <text>").description("Create a GitHub issue from natural language text").option("--repo <repo>", "Target repository (owner/name)").option("--dry-run", "Print parsed fields without creating the issue").action(async (text, opts) => {
|
|
5965
|
+
const config2 = loadFullConfig();
|
|
5966
|
+
const repo = opts.repo ?? config2.repos[0]?.name;
|
|
5967
|
+
if (!repo) {
|
|
5968
|
+
console.error(
|
|
5969
|
+
"Error: no repo specified. Use --repo owner/name or configure repos in hog init."
|
|
5970
|
+
);
|
|
5971
|
+
process.exit(1);
|
|
5972
|
+
}
|
|
5973
|
+
if (hasLlmApiKey()) {
|
|
5974
|
+
console.error("[info] LLM parsing enabled");
|
|
5975
|
+
}
|
|
5976
|
+
const parsed = await extractIssueFields(text, {
|
|
5977
|
+
onLlmFallback: (msg) => console.error(`[warn] ${msg}`)
|
|
5978
|
+
});
|
|
5979
|
+
if (!parsed) {
|
|
5980
|
+
console.error(
|
|
5981
|
+
"Error: could not parse a title from input. Ensure your text has a non-empty title."
|
|
5982
|
+
);
|
|
5983
|
+
process.exit(1);
|
|
5984
|
+
}
|
|
5985
|
+
const labels = [...parsed.labels];
|
|
5986
|
+
if (parsed.dueDate) labels.push(`due:${parsed.dueDate}`);
|
|
5987
|
+
console.error(`Title: ${parsed.title}`);
|
|
5988
|
+
if (labels.length > 0) console.error(`Labels: ${labels.join(", ")}`);
|
|
5989
|
+
if (parsed.assignee) console.error(`Assignee: @${parsed.assignee}`);
|
|
5990
|
+
if (parsed.dueDate) console.error(`Due: ${parsed.dueDate}`);
|
|
5991
|
+
console.error(`Repo: ${repo}`);
|
|
5992
|
+
if (opts.dryRun) {
|
|
5993
|
+
console.error("[dry-run] Skipping issue creation.");
|
|
5994
|
+
return;
|
|
5995
|
+
}
|
|
5996
|
+
const args = ["issue", "create", "--repo", repo, "--title", parsed.title];
|
|
5997
|
+
for (const label of labels) {
|
|
5998
|
+
args.push("--label", label);
|
|
5999
|
+
}
|
|
6000
|
+
try {
|
|
6001
|
+
execFileSync5("gh", args, { stdio: "inherit" });
|
|
6002
|
+
} catch (err) {
|
|
6003
|
+
console.error(
|
|
6004
|
+
`Error: gh issue create failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6005
|
+
);
|
|
6006
|
+
process.exit(1);
|
|
6007
|
+
}
|
|
6008
|
+
});
|
|
6009
|
+
program.addCommand(issueCommand);
|
|
4934
6010
|
program.parseAsync().catch((err) => {
|
|
4935
6011
|
const message = err instanceof Error ? err.message : String(err);
|
|
4936
6012
|
console.error(`Error: ${message}`);
|