@ondrej-svec/hog 1.7.2 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +533 -416
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js +46 -19
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ function migrateConfig(raw) {
|
|
|
20
20
|
raw = {
|
|
21
21
|
...raw,
|
|
22
22
|
version: 2,
|
|
23
|
-
repos:
|
|
23
|
+
repos: [],
|
|
24
24
|
board: {
|
|
25
25
|
refreshInterval: 60,
|
|
26
26
|
backlogLimit: 20,
|
|
@@ -135,7 +135,9 @@ function saveConfig(data) {
|
|
|
135
135
|
ensureDir();
|
|
136
136
|
const existing = getConfig();
|
|
137
137
|
writeFileSync(CONFIG_FILE, `${JSON.stringify({ ...existing, ...data }, null, 2)}
|
|
138
|
-
|
|
138
|
+
`, {
|
|
139
|
+
mode: 384
|
|
140
|
+
});
|
|
139
141
|
}
|
|
140
142
|
function requireAuth() {
|
|
141
143
|
const auth = getAuth();
|
|
@@ -145,7 +147,7 @@ function requireAuth() {
|
|
|
145
147
|
}
|
|
146
148
|
return auth;
|
|
147
149
|
}
|
|
148
|
-
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA
|
|
150
|
+
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA;
|
|
149
151
|
var init_config = __esm({
|
|
150
152
|
"src/config.ts"() {
|
|
151
153
|
"use strict";
|
|
@@ -191,7 +193,6 @@ var init_config = __esm({
|
|
|
191
193
|
profiles: z.record(z.string(), PROFILE_SCHEMA).default({}),
|
|
192
194
|
defaultProfile: z.string().optional()
|
|
193
195
|
});
|
|
194
|
-
LEGACY_REPOS = [];
|
|
195
196
|
}
|
|
196
197
|
});
|
|
197
198
|
|
|
@@ -335,10 +336,11 @@ async function extractIssueFields(input2, options = {}) {
|
|
|
335
336
|
options.onLlmFallback?.("AI parsing unavailable, used keyword matching");
|
|
336
337
|
return heuristic;
|
|
337
338
|
}
|
|
339
|
+
const safeLabels = options.validLabels && options.validLabels.length > 0 ? llmResult.labels.filter((l) => (options.validLabels ?? []).includes(l)) : llmResult.labels;
|
|
338
340
|
const merged = {
|
|
339
341
|
...llmResult,
|
|
340
342
|
// Heuristic explicit tokens always win
|
|
341
|
-
labels: heuristic.labels.length > 0 ? heuristic.labels :
|
|
343
|
+
labels: heuristic.labels.length > 0 ? heuristic.labels : safeLabels,
|
|
342
344
|
assignee: heuristic.assignee ?? llmResult.assignee,
|
|
343
345
|
dueDate: heuristic.dueDate ?? llmResult.due_date,
|
|
344
346
|
// LLM title is used only if heuristic left explicit tokens
|
|
@@ -385,30 +387,41 @@ var init_api = __esm({
|
|
|
385
387
|
throw new Error(`TickTick API error ${res.status}: ${text2}`);
|
|
386
388
|
}
|
|
387
389
|
const text = await res.text();
|
|
388
|
-
if (!text) return
|
|
390
|
+
if (!text) return null;
|
|
389
391
|
return JSON.parse(text);
|
|
390
392
|
}
|
|
391
393
|
async listProjects() {
|
|
392
|
-
return this.request("GET", "/project");
|
|
394
|
+
return await this.request("GET", "/project") ?? [];
|
|
393
395
|
}
|
|
394
396
|
async getProject(projectId) {
|
|
395
|
-
|
|
397
|
+
const result = await this.request("GET", `/project/${projectId}`);
|
|
398
|
+
if (!result) throw new Error(`TickTick API returned empty response for project ${projectId}`);
|
|
399
|
+
return result;
|
|
396
400
|
}
|
|
397
401
|
async getProjectData(projectId) {
|
|
398
|
-
|
|
402
|
+
const result = await this.request("GET", `/project/${projectId}/data`);
|
|
403
|
+
if (!result)
|
|
404
|
+
throw new Error(`TickTick API returned empty response for project data ${projectId}`);
|
|
405
|
+
return result;
|
|
399
406
|
}
|
|
400
407
|
async listTasks(projectId) {
|
|
401
408
|
const data = await this.getProjectData(projectId);
|
|
402
409
|
return data.tasks ?? [];
|
|
403
410
|
}
|
|
404
411
|
async getTask(projectId, taskId) {
|
|
405
|
-
|
|
412
|
+
const result = await this.request("GET", `/project/${projectId}/task/${taskId}`);
|
|
413
|
+
if (!result) throw new Error(`TickTick API returned empty response for task ${taskId}`);
|
|
414
|
+
return result;
|
|
406
415
|
}
|
|
407
416
|
async createTask(input2) {
|
|
408
|
-
|
|
417
|
+
const result = await this.request("POST", "/task", input2);
|
|
418
|
+
if (!result) throw new Error("TickTick API returned empty response for createTask");
|
|
419
|
+
return result;
|
|
409
420
|
}
|
|
410
421
|
async updateTask(input2) {
|
|
411
|
-
|
|
422
|
+
const result = await this.request("POST", `/task/${input2.id}`, input2);
|
|
423
|
+
if (!result) throw new Error(`TickTick API returned empty response for updateTask ${input2.id}`);
|
|
424
|
+
return result;
|
|
412
425
|
}
|
|
413
426
|
async completeTask(projectId, taskId) {
|
|
414
427
|
await this.request("POST", `/project/${projectId}/task/${taskId}/complete`);
|
|
@@ -480,6 +493,31 @@ var init_types = __esm({
|
|
|
480
493
|
}
|
|
481
494
|
});
|
|
482
495
|
|
|
496
|
+
// src/board/constants.ts
|
|
497
|
+
function isTerminalStatus(status) {
|
|
498
|
+
return TERMINAL_STATUS_RE.test(status);
|
|
499
|
+
}
|
|
500
|
+
function isHeaderId(id) {
|
|
501
|
+
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
502
|
+
}
|
|
503
|
+
function timeAgo(date) {
|
|
504
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
505
|
+
if (seconds < 10) return "just now";
|
|
506
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
507
|
+
const minutes = Math.floor(seconds / 60);
|
|
508
|
+
return `${minutes}m ago`;
|
|
509
|
+
}
|
|
510
|
+
function formatError(err) {
|
|
511
|
+
return err instanceof Error ? err.message : String(err);
|
|
512
|
+
}
|
|
513
|
+
var TERMINAL_STATUS_RE;
|
|
514
|
+
var init_constants = __esm({
|
|
515
|
+
"src/board/constants.ts"() {
|
|
516
|
+
"use strict";
|
|
517
|
+
TERMINAL_STATUS_RE = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
483
521
|
// src/github.ts
|
|
484
522
|
var github_exports = {};
|
|
485
523
|
__export(github_exports, {
|
|
@@ -489,6 +527,11 @@ __export(github_exports, {
|
|
|
489
527
|
assignIssue: () => assignIssue,
|
|
490
528
|
assignIssueAsync: () => assignIssueAsync,
|
|
491
529
|
assignIssueToAsync: () => assignIssueToAsync,
|
|
530
|
+
clearProjectNodeIdCache: () => clearProjectNodeIdCache,
|
|
531
|
+
closeIssueAsync: () => closeIssueAsync,
|
|
532
|
+
createIssueAsync: () => createIssueAsync,
|
|
533
|
+
editIssueBodyAsync: () => editIssueBodyAsync,
|
|
534
|
+
editIssueTitleAsync: () => editIssueTitleAsync,
|
|
492
535
|
fetchAssignedIssues: () => fetchAssignedIssues,
|
|
493
536
|
fetchIssueAsync: () => fetchIssueAsync,
|
|
494
537
|
fetchIssueCommentsAsync: () => fetchIssueCommentsAsync,
|
|
@@ -500,6 +543,7 @@ __export(github_exports, {
|
|
|
500
543
|
fetchRepoLabelsAsync: () => fetchRepoLabelsAsync,
|
|
501
544
|
removeLabelAsync: () => removeLabelAsync,
|
|
502
545
|
unassignIssueAsync: () => unassignIssueAsync,
|
|
546
|
+
updateLabelsAsync: () => updateLabelsAsync,
|
|
503
547
|
updateProjectItemDateAsync: () => updateProjectItemDateAsync,
|
|
504
548
|
updateProjectItemStatus: () => updateProjectItemStatus,
|
|
505
549
|
updateProjectItemStatusAsync: () => updateProjectItemStatusAsync
|
|
@@ -587,6 +631,24 @@ async function fetchIssueAsync(repo, issueNumber) {
|
|
|
587
631
|
"number,title,url,state,updatedAt,labels,assignees,body,projectStatus"
|
|
588
632
|
]);
|
|
589
633
|
}
|
|
634
|
+
async function closeIssueAsync(repo, issueNumber) {
|
|
635
|
+
await runGhAsync(["issue", "close", String(issueNumber), "--repo", repo]);
|
|
636
|
+
}
|
|
637
|
+
async function createIssueAsync(repo, title, body, labels) {
|
|
638
|
+
const args = ["issue", "create", "--repo", repo, "--title", title, "--body", body];
|
|
639
|
+
if (labels && labels.length > 0) {
|
|
640
|
+
for (const label of labels) {
|
|
641
|
+
args.push("--label", label);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return runGhAsync(args);
|
|
645
|
+
}
|
|
646
|
+
async function editIssueTitleAsync(repo, issueNumber, title) {
|
|
647
|
+
await runGhAsync(["issue", "edit", String(issueNumber), "--repo", repo, "--title", title]);
|
|
648
|
+
}
|
|
649
|
+
async function editIssueBodyAsync(repo, issueNumber, body) {
|
|
650
|
+
await runGhAsync(["issue", "edit", String(issueNumber), "--repo", repo, "--body", body]);
|
|
651
|
+
}
|
|
590
652
|
async function addCommentAsync(repo, issueNumber, body) {
|
|
591
653
|
await runGhAsync(["issue", "comment", String(issueNumber), "--repo", repo, "--body", body]);
|
|
592
654
|
}
|
|
@@ -596,6 +658,12 @@ async function addLabelAsync(repo, issueNumber, label) {
|
|
|
596
658
|
async function removeLabelAsync(repo, issueNumber, label) {
|
|
597
659
|
await runGhAsync(["issue", "edit", String(issueNumber), "--repo", repo, "--remove-label", label]);
|
|
598
660
|
}
|
|
661
|
+
async function updateLabelsAsync(repo, issueNumber, addLabels, removeLabels) {
|
|
662
|
+
const args = ["issue", "edit", String(issueNumber), "--repo", repo];
|
|
663
|
+
for (const label of addLabels) args.push("--add-label", label);
|
|
664
|
+
for (const label of removeLabels) args.push("--remove-label", label);
|
|
665
|
+
await runGhAsync(args);
|
|
666
|
+
}
|
|
599
667
|
async function fetchIssueCommentsAsync(repo, issueNumber) {
|
|
600
668
|
const result = await runGhJsonAsync([
|
|
601
669
|
"issue",
|
|
@@ -635,6 +703,7 @@ function fetchProjectFields(repo, issueNumber, projectNumber) {
|
|
|
635
703
|
}
|
|
636
704
|
`;
|
|
637
705
|
const [owner, repoName] = repo.split("/");
|
|
706
|
+
if (!(owner && repoName)) return {};
|
|
638
707
|
try {
|
|
639
708
|
const result = runGhJson([
|
|
640
709
|
"api",
|
|
@@ -669,6 +738,7 @@ function fetchProjectFields(repo, issueNumber, projectNumber) {
|
|
|
669
738
|
}
|
|
670
739
|
function fetchProjectEnrichment(repo, projectNumber) {
|
|
671
740
|
const [owner] = repo.split("/");
|
|
741
|
+
if (!owner) return /* @__PURE__ */ new Map();
|
|
672
742
|
const query = `
|
|
673
743
|
query($owner: String!, $projectNumber: Int!) {
|
|
674
744
|
organization(login: $owner) {
|
|
@@ -741,6 +811,7 @@ function fetchProjectTargetDates(repo, projectNumber) {
|
|
|
741
811
|
}
|
|
742
812
|
function fetchProjectStatusOptions(repo, projectNumber, _statusFieldId) {
|
|
743
813
|
const [owner] = repo.split("/");
|
|
814
|
+
if (!owner) return [];
|
|
744
815
|
const query = `
|
|
745
816
|
query($owner: String!, $projectNumber: Int!) {
|
|
746
817
|
organization(login: $owner) {
|
|
@@ -791,8 +862,38 @@ async function fetchRepoLabelsAsync(repo) {
|
|
|
791
862
|
return [];
|
|
792
863
|
}
|
|
793
864
|
}
|
|
865
|
+
function clearProjectNodeIdCache() {
|
|
866
|
+
projectNodeIdCache.clear();
|
|
867
|
+
}
|
|
868
|
+
async function getProjectNodeId(owner, projectNumber) {
|
|
869
|
+
const key = `${owner}/${String(projectNumber)}`;
|
|
870
|
+
const cached = projectNodeIdCache.get(key);
|
|
871
|
+
if (cached !== void 0) return cached;
|
|
872
|
+
const projectQuery = `
|
|
873
|
+
query($owner: String!) {
|
|
874
|
+
organization(login: $owner) {
|
|
875
|
+
projectV2(number: ${projectNumber}) {
|
|
876
|
+
id
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
`;
|
|
881
|
+
const projectResult = await runGhJsonAsync([
|
|
882
|
+
"api",
|
|
883
|
+
"graphql",
|
|
884
|
+
"-f",
|
|
885
|
+
`query=${projectQuery}`,
|
|
886
|
+
"-F",
|
|
887
|
+
`owner=${owner}`
|
|
888
|
+
]);
|
|
889
|
+
const projectId = projectResult?.data?.organization?.projectV2?.id;
|
|
890
|
+
if (!projectId) return null;
|
|
891
|
+
projectNodeIdCache.set(key, projectId);
|
|
892
|
+
return projectId;
|
|
893
|
+
}
|
|
794
894
|
function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
795
895
|
const [owner, repoName] = repo.split("/");
|
|
896
|
+
if (!(owner && repoName)) return;
|
|
796
897
|
const findItemQuery = `
|
|
797
898
|
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
798
899
|
repository(owner: $owner, name: $repo) {
|
|
@@ -875,6 +976,7 @@ function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
|
875
976
|
}
|
|
876
977
|
async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
877
978
|
const [owner, repoName] = repo.split("/");
|
|
979
|
+
if (!(owner && repoName)) return;
|
|
878
980
|
const findItemQuery = `
|
|
879
981
|
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
880
982
|
repository(owner: $owner, name: $repo) {
|
|
@@ -905,24 +1007,7 @@ async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
|
905
1007
|
const projectNumber = projectConfig.projectNumber;
|
|
906
1008
|
const projectItem = items.find((item) => item?.project?.number === projectNumber);
|
|
907
1009
|
if (!projectItem?.id) return;
|
|
908
|
-
const
|
|
909
|
-
query($owner: String!) {
|
|
910
|
-
organization(login: $owner) {
|
|
911
|
-
projectV2(number: ${projectNumber}) {
|
|
912
|
-
id
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
`;
|
|
917
|
-
const projectResult = await runGhJsonAsync([
|
|
918
|
-
"api",
|
|
919
|
-
"graphql",
|
|
920
|
-
"-f",
|
|
921
|
-
`query=${projectQuery}`,
|
|
922
|
-
"-F",
|
|
923
|
-
`owner=${owner}`
|
|
924
|
-
]);
|
|
925
|
-
const projectId = projectResult?.data?.organization?.projectV2?.id;
|
|
1010
|
+
const projectId = await getProjectNodeId(owner, projectNumber);
|
|
926
1011
|
if (!projectId) return;
|
|
927
1012
|
const statusFieldId = projectConfig.statusFieldId;
|
|
928
1013
|
const optionId = projectConfig.optionId;
|
|
@@ -957,6 +1042,7 @@ async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
|
957
1042
|
}
|
|
958
1043
|
async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueDate) {
|
|
959
1044
|
const [owner, repoName] = repo.split("/");
|
|
1045
|
+
if (!(owner && repoName)) return;
|
|
960
1046
|
const findItemQuery = `
|
|
961
1047
|
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
962
1048
|
repository(owner: $owner, name: $repo) {
|
|
@@ -986,24 +1072,7 @@ async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueD
|
|
|
986
1072
|
const items = findResult?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
987
1073
|
const projectItem = items.find((item) => item?.project?.number === projectConfig.projectNumber);
|
|
988
1074
|
if (!projectItem?.id) return;
|
|
989
|
-
const
|
|
990
|
-
query($owner: String!) {
|
|
991
|
-
organization(login: $owner) {
|
|
992
|
-
projectV2(number: ${projectConfig.projectNumber}) {
|
|
993
|
-
id
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
`;
|
|
998
|
-
const projectResult = await runGhJsonAsync([
|
|
999
|
-
"api",
|
|
1000
|
-
"graphql",
|
|
1001
|
-
"-f",
|
|
1002
|
-
`query=${projectQuery}`,
|
|
1003
|
-
"-F",
|
|
1004
|
-
`owner=${owner}`
|
|
1005
|
-
]);
|
|
1006
|
-
const projectId = projectResult?.data?.organization?.projectV2?.id;
|
|
1075
|
+
const projectId = await getProjectNodeId(owner, projectConfig.projectNumber);
|
|
1007
1076
|
if (!projectId) return;
|
|
1008
1077
|
const mutation = `
|
|
1009
1078
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) {
|
|
@@ -1034,12 +1103,13 @@ async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueD
|
|
|
1034
1103
|
`date=${dueDate}`
|
|
1035
1104
|
]);
|
|
1036
1105
|
}
|
|
1037
|
-
var execFileAsync, DATE_FIELD_NAME_RE2;
|
|
1106
|
+
var execFileAsync, DATE_FIELD_NAME_RE2, projectNodeIdCache;
|
|
1038
1107
|
var init_github = __esm({
|
|
1039
1108
|
"src/github.ts"() {
|
|
1040
1109
|
"use strict";
|
|
1041
1110
|
execFileAsync = promisify(execFile);
|
|
1042
1111
|
DATE_FIELD_NAME_RE2 = /^(target\s*date|due\s*date|due|deadline)$/i;
|
|
1112
|
+
projectNodeIdCache = /* @__PURE__ */ new Map();
|
|
1043
1113
|
}
|
|
1044
1114
|
});
|
|
1045
1115
|
|
|
@@ -1057,7 +1127,7 @@ function loadSyncState() {
|
|
|
1057
1127
|
}
|
|
1058
1128
|
function saveSyncState(state) {
|
|
1059
1129
|
writeFileSync3(STATE_FILE, `${JSON.stringify(state, null, 2)}
|
|
1060
|
-
|
|
1130
|
+
`, { mode: 384 });
|
|
1061
1131
|
}
|
|
1062
1132
|
function findMapping(state, githubRepo, issueNumber) {
|
|
1063
1133
|
return state.mappings.find(
|
|
@@ -1281,8 +1351,6 @@ var init_use_action_log = __esm({
|
|
|
1281
1351
|
});
|
|
1282
1352
|
|
|
1283
1353
|
// src/board/hooks/use-actions.ts
|
|
1284
|
-
import { execFile as execFile2 } from "child_process";
|
|
1285
|
-
import { promisify as promisify2 } from "util";
|
|
1286
1354
|
import { useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
1287
1355
|
function findIssueContext(repos, selectedId, config2) {
|
|
1288
1356
|
if (!selectedId?.startsWith("gh:")) {
|
|
@@ -1301,17 +1369,10 @@ function findIssueContext(repos, selectedId, config2) {
|
|
|
1301
1369
|
async function triggerCompletionActionAsync(action, repoName, issueNumber) {
|
|
1302
1370
|
switch (action.type) {
|
|
1303
1371
|
case "closeIssue":
|
|
1304
|
-
await
|
|
1305
|
-
encoding: "utf-8",
|
|
1306
|
-
timeout: 3e4
|
|
1307
|
-
});
|
|
1372
|
+
await closeIssueAsync(repoName, issueNumber);
|
|
1308
1373
|
break;
|
|
1309
1374
|
case "addLabel":
|
|
1310
|
-
await
|
|
1311
|
-
"gh",
|
|
1312
|
-
["issue", "edit", String(issueNumber), "--repo", repoName, "--add-label", action.label],
|
|
1313
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
1314
|
-
);
|
|
1375
|
+
await addLabelAsync(repoName, issueNumber, action.label);
|
|
1315
1376
|
break;
|
|
1316
1377
|
case "updateProjectStatus":
|
|
1317
1378
|
break;
|
|
@@ -1419,11 +1480,7 @@ function useActions({
|
|
|
1419
1480
|
}
|
|
1420
1481
|
const { issue, repoName } = ctx;
|
|
1421
1482
|
const t = toast.loading("Commenting...");
|
|
1422
|
-
|
|
1423
|
-
"gh",
|
|
1424
|
-
["issue", "comment", String(issue.number), "--repo", repoName, "--body", body],
|
|
1425
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
1426
|
-
).then(() => {
|
|
1483
|
+
addCommentAsync(repoName, issue.number, body).then(() => {
|
|
1427
1484
|
t.resolve(`Comment posted on #${issue.number}`);
|
|
1428
1485
|
pushEntryRef.current?.({
|
|
1429
1486
|
id: nextEntryId(),
|
|
@@ -1432,6 +1489,7 @@ function useActions({
|
|
|
1432
1489
|
ago: Date.now()
|
|
1433
1490
|
});
|
|
1434
1491
|
refresh();
|
|
1492
|
+
onOverlayDone();
|
|
1435
1493
|
}).catch((err) => {
|
|
1436
1494
|
t.reject(`Comment failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1437
1495
|
pushEntryRef.current?.({
|
|
@@ -1440,8 +1498,6 @@ function useActions({
|
|
|
1440
1498
|
status: "error",
|
|
1441
1499
|
ago: Date.now()
|
|
1442
1500
|
});
|
|
1443
|
-
}).finally(() => {
|
|
1444
|
-
onOverlayDone();
|
|
1445
1501
|
});
|
|
1446
1502
|
},
|
|
1447
1503
|
[toast, refresh, onOverlayDone]
|
|
@@ -1545,19 +1601,7 @@ function useActions({
|
|
|
1545
1601
|
status: "success",
|
|
1546
1602
|
ago: Date.now(),
|
|
1547
1603
|
undo: async () => {
|
|
1548
|
-
await
|
|
1549
|
-
"gh",
|
|
1550
|
-
[
|
|
1551
|
-
"issue",
|
|
1552
|
-
"edit",
|
|
1553
|
-
String(issue.number),
|
|
1554
|
-
"--repo",
|
|
1555
|
-
repoName,
|
|
1556
|
-
"--remove-assignee",
|
|
1557
|
-
"@me"
|
|
1558
|
-
],
|
|
1559
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
1560
|
-
);
|
|
1604
|
+
await unassignIssueAsync(repoName, issue.number, "@me");
|
|
1561
1605
|
}
|
|
1562
1606
|
});
|
|
1563
1607
|
refresh();
|
|
@@ -1571,33 +1615,6 @@ function useActions({
|
|
|
1571
1615
|
});
|
|
1572
1616
|
});
|
|
1573
1617
|
}, [toast, refresh]);
|
|
1574
|
-
const handleUnassign = useCallback2(() => {
|
|
1575
|
-
const ctx = findIssueContext(reposRef.current, selectedIdRef.current, configRef.current);
|
|
1576
|
-
if (!(ctx.issue && ctx.repoName)) return;
|
|
1577
|
-
const { issue, repoName } = ctx;
|
|
1578
|
-
const assignees = issue.assignees ?? [];
|
|
1579
|
-
const selfAssigned = assignees.some((a) => a.login === configRef.current.board.assignee);
|
|
1580
|
-
if (!selfAssigned) {
|
|
1581
|
-
const firstAssignee = assignees[0];
|
|
1582
|
-
if (firstAssignee) {
|
|
1583
|
-
toast.info(`Assigned to @${firstAssignee.login} \u2014 can only unassign self`);
|
|
1584
|
-
} else {
|
|
1585
|
-
toast.info("Not assigned");
|
|
1586
|
-
}
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
const t = toast.loading("Unassigning...");
|
|
1590
|
-
execFileAsync2(
|
|
1591
|
-
"gh",
|
|
1592
|
-
["issue", "edit", String(issue.number), "--repo", repoName, "--remove-assignee", "@me"],
|
|
1593
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
1594
|
-
).then(() => {
|
|
1595
|
-
t.resolve(`Unassigned #${issue.number} from @${configRef.current.board.assignee}`);
|
|
1596
|
-
refresh();
|
|
1597
|
-
}).catch((err) => {
|
|
1598
|
-
t.reject(`Unassign failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1599
|
-
});
|
|
1600
|
-
}, [toast, refresh]);
|
|
1601
1618
|
const handleCreateIssue = useCallback2(
|
|
1602
1619
|
async (repo, title, body, dueDate, labels) => {
|
|
1603
1620
|
const repoConfig = configRef.current.repos.find((r) => r.name === repo);
|
|
@@ -1608,16 +1625,9 @@ function useActions({
|
|
|
1608
1625
|
|
|
1609
1626
|
${dueLine}` : dueLine;
|
|
1610
1627
|
}
|
|
1611
|
-
const args = ["issue", "create", "--repo", repo, "--title", title, "--body", effectiveBody];
|
|
1612
|
-
if (labels && labels.length > 0) {
|
|
1613
|
-
for (const label of labels) {
|
|
1614
|
-
args.push("--label", label);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
1628
|
const t = toast.loading("Creating...");
|
|
1618
1629
|
try {
|
|
1619
|
-
const
|
|
1620
|
-
const output = stdout.trim();
|
|
1630
|
+
const output = await createIssueAsync(repo, title, effectiveBody, labels);
|
|
1621
1631
|
const match = output.match(/\/(\d+)$/);
|
|
1622
1632
|
const issueNumber = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
1623
1633
|
const shortName = repoConfig?.shortName ?? repo;
|
|
@@ -1646,11 +1656,8 @@ ${dueLine}` : dueLine;
|
|
|
1646
1656
|
const ctx = findIssueContext(reposRef.current, selectedIdRef.current, configRef.current);
|
|
1647
1657
|
if (!(ctx.issue && ctx.repoName)) return;
|
|
1648
1658
|
const { issue, repoName } = ctx;
|
|
1649
|
-
const args = ["issue", "edit", String(issue.number), "--repo", repoName];
|
|
1650
|
-
for (const label of addLabels) args.push("--add-label", label);
|
|
1651
|
-
for (const label of removeLabels) args.push("--remove-label", label);
|
|
1652
1659
|
const t = toast.loading("Updating labels...");
|
|
1653
|
-
|
|
1660
|
+
updateLabelsAsync(repoName, issue.number, addLabels, removeLabels).then(() => {
|
|
1654
1661
|
t.resolve(`Labels updated on #${issue.number}`);
|
|
1655
1662
|
refresh();
|
|
1656
1663
|
onOverlayDone();
|
|
@@ -1706,19 +1713,7 @@ ${dueLine}` : dueLine;
|
|
|
1706
1713
|
const assignees = ctx.issue.assignees ?? [];
|
|
1707
1714
|
if (!assignees.some((a) => a.login === configRef.current.board.assignee)) continue;
|
|
1708
1715
|
try {
|
|
1709
|
-
await
|
|
1710
|
-
"gh",
|
|
1711
|
-
[
|
|
1712
|
-
"issue",
|
|
1713
|
-
"edit",
|
|
1714
|
-
String(ctx.issue.number),
|
|
1715
|
-
"--repo",
|
|
1716
|
-
ctx.repoName,
|
|
1717
|
-
"--remove-assignee",
|
|
1718
|
-
"@me"
|
|
1719
|
-
],
|
|
1720
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
1721
|
-
);
|
|
1716
|
+
await unassignIssueAsync(ctx.repoName, ctx.issue.number, "@me");
|
|
1722
1717
|
} catch {
|
|
1723
1718
|
failed.push(id);
|
|
1724
1719
|
}
|
|
@@ -1783,7 +1778,6 @@ ${dueLine}` : dueLine;
|
|
|
1783
1778
|
handleComment,
|
|
1784
1779
|
handleStatusChange,
|
|
1785
1780
|
handleAssign,
|
|
1786
|
-
handleUnassign,
|
|
1787
1781
|
handleLabelChange,
|
|
1788
1782
|
handleCreateIssue,
|
|
1789
1783
|
handleBulkAssign,
|
|
@@ -1791,15 +1785,13 @@ ${dueLine}` : dueLine;
|
|
|
1791
1785
|
handleBulkStatusChange
|
|
1792
1786
|
};
|
|
1793
1787
|
}
|
|
1794
|
-
var execFileAsync2, TERMINAL_STATUS_RE;
|
|
1795
1788
|
var init_use_actions = __esm({
|
|
1796
1789
|
"src/board/hooks/use-actions.ts"() {
|
|
1797
1790
|
"use strict";
|
|
1798
1791
|
init_github();
|
|
1799
1792
|
init_pick();
|
|
1793
|
+
init_constants();
|
|
1800
1794
|
init_use_action_log();
|
|
1801
|
-
execFileAsync2 = promisify2(execFile2);
|
|
1802
|
-
TERMINAL_STATUS_RE = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
1803
1795
|
}
|
|
1804
1796
|
});
|
|
1805
1797
|
|
|
@@ -1860,7 +1852,8 @@ function useData(config2, options, refreshIntervalMs) {
|
|
|
1860
1852
|
{ workerData: { config: configRef.current, options: optionsRef.current } }
|
|
1861
1853
|
);
|
|
1862
1854
|
workerRef.current = worker;
|
|
1863
|
-
worker.on("message", (
|
|
1855
|
+
worker.on("message", (rawMsg) => {
|
|
1856
|
+
const msg = rawMsg;
|
|
1864
1857
|
if (token.canceled) {
|
|
1865
1858
|
worker.terminate();
|
|
1866
1859
|
return;
|
|
@@ -1881,13 +1874,13 @@ function useData(config2, options, refreshIntervalMs) {
|
|
|
1881
1874
|
consecutiveFailures: 0,
|
|
1882
1875
|
autoRefreshPaused: false
|
|
1883
1876
|
});
|
|
1884
|
-
} else {
|
|
1877
|
+
} else if (msg.type === "error") {
|
|
1885
1878
|
setState((prev) => {
|
|
1886
1879
|
const failures = prev.consecutiveFailures + 1;
|
|
1887
1880
|
return {
|
|
1888
1881
|
...prev,
|
|
1889
1882
|
status: prev.data ? "success" : "error",
|
|
1890
|
-
error: msg.error
|
|
1883
|
+
error: msg.error,
|
|
1891
1884
|
isRefreshing: false,
|
|
1892
1885
|
consecutiveFailures: failures,
|
|
1893
1886
|
autoRefreshPaused: failures >= MAX_REFRESH_FAILURES
|
|
@@ -1998,9 +1991,6 @@ var init_use_data = __esm({
|
|
|
1998
1991
|
// src/board/hooks/use-keyboard.ts
|
|
1999
1992
|
import { useInput } from "ink";
|
|
2000
1993
|
import { useCallback as useCallback4 } from "react";
|
|
2001
|
-
function isHeaderId(id) {
|
|
2002
|
-
return id != null && (id.startsWith("header:") || id.startsWith("sub:"));
|
|
2003
|
-
}
|
|
2004
1994
|
function useKeyboard({
|
|
2005
1995
|
ui,
|
|
2006
1996
|
nav,
|
|
@@ -2238,6 +2228,7 @@ function useKeyboard({
|
|
|
2238
2228
|
var init_use_keyboard = __esm({
|
|
2239
2229
|
"src/board/hooks/use-keyboard.ts"() {
|
|
2240
2230
|
"use strict";
|
|
2231
|
+
init_constants();
|
|
2241
2232
|
}
|
|
2242
2233
|
});
|
|
2243
2234
|
|
|
@@ -2346,7 +2337,16 @@ function navReducer(state, action) {
|
|
|
2346
2337
|
case "SET_ITEMS": {
|
|
2347
2338
|
const sections = [...new Set(action.items.map((i) => i.section))];
|
|
2348
2339
|
const isFirstLoad = state.sections.length === 0;
|
|
2349
|
-
|
|
2340
|
+
let collapsedSections;
|
|
2341
|
+
if (isFirstLoad) {
|
|
2342
|
+
collapsedSections = new Set(sections.filter((s) => s === "activity"));
|
|
2343
|
+
} else {
|
|
2344
|
+
const validIds = /* @__PURE__ */ new Set([
|
|
2345
|
+
...sections,
|
|
2346
|
+
...action.items.filter((i) => i.type === "subHeader").map((i) => i.id)
|
|
2347
|
+
]);
|
|
2348
|
+
collapsedSections = new Set([...state.collapsedSections].filter((id) => validIds.has(id)));
|
|
2349
|
+
}
|
|
2350
2350
|
const selectionValid = state.selectedId != null && action.items.some((i) => i.id === state.selectedId);
|
|
2351
2351
|
if (!isFirstLoad && selectionValid && arraysEqual(sections, state.sections)) {
|
|
2352
2352
|
return state.allItems === action.items ? state : { ...state, allItems: action.items };
|
|
@@ -2579,19 +2579,6 @@ function useToast() {
|
|
|
2579
2579
|
[addToast, removeToast]
|
|
2580
2580
|
)
|
|
2581
2581
|
};
|
|
2582
|
-
const dismiss = useCallback7(
|
|
2583
|
-
(id) => {
|
|
2584
|
-
removeToast(id);
|
|
2585
|
-
},
|
|
2586
|
-
[removeToast]
|
|
2587
|
-
);
|
|
2588
|
-
const dismissAll = useCallback7(() => {
|
|
2589
|
-
for (const timer of timersRef.current.values()) {
|
|
2590
|
-
clearTimeout(timer);
|
|
2591
|
-
}
|
|
2592
|
-
timersRef.current.clear();
|
|
2593
|
-
setToasts([]);
|
|
2594
|
-
}, []);
|
|
2595
2582
|
const handleErrorAction = useCallback7(
|
|
2596
2583
|
(action) => {
|
|
2597
2584
|
const errorToast = toasts.find((t) => t.type === "error");
|
|
@@ -2609,7 +2596,7 @@ function useToast() {
|
|
|
2609
2596
|
},
|
|
2610
2597
|
[toasts, removeToast]
|
|
2611
2598
|
);
|
|
2612
|
-
return { toasts, toast,
|
|
2599
|
+
return { toasts, toast, handleErrorAction };
|
|
2613
2600
|
}
|
|
2614
2601
|
var MAX_VISIBLE, AUTO_DISMISS_MS, nextId;
|
|
2615
2602
|
var init_use_toast = __esm({
|
|
@@ -3002,7 +2989,14 @@ var init_detail_panel = __esm({
|
|
|
3002
2989
|
// src/board/components/hint-bar.tsx
|
|
3003
2990
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
3004
2991
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3005
|
-
function HintBar({
|
|
2992
|
+
function HintBar({
|
|
2993
|
+
uiMode,
|
|
2994
|
+
multiSelectCount,
|
|
2995
|
+
searchQuery,
|
|
2996
|
+
mineOnly,
|
|
2997
|
+
hasUndoable,
|
|
2998
|
+
onHeader
|
|
2999
|
+
}) {
|
|
3006
3000
|
if (uiMode === "multiSelect") {
|
|
3007
3001
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3008
3002
|
/* @__PURE__ */ jsxs3(Text3, { color: "cyan", bold: true, children: [
|
|
@@ -3033,6 +3027,17 @@ function HintBar({ uiMode, multiSelectCount, searchQuery, mineOnly, hasUndoable
|
|
|
3033
3027
|
if (uiMode.startsWith("overlay:")) {
|
|
3034
3028
|
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter:select Esc:cancel" }) });
|
|
3035
3029
|
}
|
|
3030
|
+
if (onHeader) {
|
|
3031
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3032
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter/Space:expand-collapse Tab:next-section C:collapse-all ?:more q:quit" }),
|
|
3033
|
+
mineOnly ? /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: " filter:@me" }) : null,
|
|
3034
|
+
searchQuery ? /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
|
|
3035
|
+
' filter:"',
|
|
3036
|
+
searchQuery,
|
|
3037
|
+
'"'
|
|
3038
|
+
] }) : null
|
|
3039
|
+
] });
|
|
3040
|
+
}
|
|
3036
3041
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3037
3042
|
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
3038
3043
|
"j/k:nav Enter:open m:status c:comment F:find t:@me e:edit L:log",
|
|
@@ -3495,11 +3500,10 @@ var init_create_issue_form = __esm({
|
|
|
3495
3500
|
});
|
|
3496
3501
|
|
|
3497
3502
|
// src/board/components/edit-issue-overlay.tsx
|
|
3498
|
-
import {
|
|
3503
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3499
3504
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3500
3505
|
import { tmpdir as tmpdir2 } from "os";
|
|
3501
3506
|
import { join as join5 } from "path";
|
|
3502
|
-
import { promisify as promisify3 } from "util";
|
|
3503
3507
|
import { Box as Box9, Text as Text9, useStdin as useStdin2 } from "ink";
|
|
3504
3508
|
import { useEffect as useEffect6, useRef as useRef9, useState as useState10 } from "react";
|
|
3505
3509
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
@@ -3651,11 +3655,7 @@ function EditIssueOverlay({
|
|
|
3651
3655
|
const changedFields = [];
|
|
3652
3656
|
if (parsed.title !== issue.title) {
|
|
3653
3657
|
try {
|
|
3654
|
-
await
|
|
3655
|
-
"gh",
|
|
3656
|
-
["issue", "edit", String(issue.number), "--repo", repoName, "--title", parsed.title],
|
|
3657
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3658
|
-
);
|
|
3658
|
+
await editIssueTitleAsync(repoName, issue.number, parsed.title);
|
|
3659
3659
|
changedFields.push("title");
|
|
3660
3660
|
} catch {
|
|
3661
3661
|
onToastError(`Failed to update title on #${issue.number}`);
|
|
@@ -3663,11 +3663,7 @@ function EditIssueOverlay({
|
|
|
3663
3663
|
}
|
|
3664
3664
|
if (parsed.body !== (issue.body ?? "").trim()) {
|
|
3665
3665
|
try {
|
|
3666
|
-
await
|
|
3667
|
-
"gh",
|
|
3668
|
-
["issue", "edit", String(issue.number), "--repo", repoName, "--body", parsed.body],
|
|
3669
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3670
|
-
);
|
|
3666
|
+
await editIssueBodyAsync(repoName, issue.number, parsed.body);
|
|
3671
3667
|
changedFields.push("body");
|
|
3672
3668
|
} catch {
|
|
3673
3669
|
onToastError(`Failed to update body on #${issue.number}`);
|
|
@@ -3691,11 +3687,8 @@ function EditIssueOverlay({
|
|
|
3691
3687
|
const addLabels = parsed.labels.filter((l) => !origLabels.includes(l));
|
|
3692
3688
|
const removeLabels = origLabels.filter((l) => !parsed.labels.includes(l));
|
|
3693
3689
|
if (addLabels.length > 0 || removeLabels.length > 0) {
|
|
3694
|
-
const args = ["issue", "edit", String(issue.number), "--repo", repoName];
|
|
3695
|
-
for (const l of addLabels) args.push("--add-label", l);
|
|
3696
|
-
for (const l of removeLabels) args.push("--remove-label", l);
|
|
3697
3690
|
try {
|
|
3698
|
-
await
|
|
3691
|
+
await updateLabelsAsync(repoName, issue.number, addLabels, removeLabels);
|
|
3699
3692
|
changedFields.push("labels");
|
|
3700
3693
|
} catch {
|
|
3701
3694
|
onToastError(`Failed to update labels on #${issue.number}`);
|
|
@@ -3704,34 +3697,10 @@ function EditIssueOverlay({
|
|
|
3704
3697
|
if (parsed.assignee !== origAssignee) {
|
|
3705
3698
|
try {
|
|
3706
3699
|
if (parsed.assignee) {
|
|
3707
|
-
await
|
|
3708
|
-
"gh",
|
|
3709
|
-
[
|
|
3710
|
-
"issue",
|
|
3711
|
-
"edit",
|
|
3712
|
-
String(issue.number),
|
|
3713
|
-
"--repo",
|
|
3714
|
-
repoName,
|
|
3715
|
-
"--add-assignee",
|
|
3716
|
-
parsed.assignee
|
|
3717
|
-
],
|
|
3718
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3719
|
-
);
|
|
3700
|
+
await assignIssueToAsync(repoName, issue.number, parsed.assignee);
|
|
3720
3701
|
}
|
|
3721
3702
|
if (origAssignee) {
|
|
3722
|
-
await
|
|
3723
|
-
"gh",
|
|
3724
|
-
[
|
|
3725
|
-
"issue",
|
|
3726
|
-
"edit",
|
|
3727
|
-
String(issue.number),
|
|
3728
|
-
"--repo",
|
|
3729
|
-
repoName,
|
|
3730
|
-
"--remove-assignee",
|
|
3731
|
-
origAssignee
|
|
3732
|
-
],
|
|
3733
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3734
|
-
);
|
|
3703
|
+
await unassignIssueAsync(repoName, issue.number, origAssignee);
|
|
3735
3704
|
}
|
|
3736
3705
|
changedFields.push("assignee");
|
|
3737
3706
|
} catch {
|
|
@@ -3785,14 +3754,12 @@ function EditIssueOverlay({
|
|
|
3785
3754
|
"\u2026"
|
|
3786
3755
|
] }) });
|
|
3787
3756
|
}
|
|
3788
|
-
var execFileAsync3;
|
|
3789
3757
|
var init_edit_issue_overlay = __esm({
|
|
3790
3758
|
"src/board/components/edit-issue-overlay.tsx"() {
|
|
3791
3759
|
"use strict";
|
|
3792
3760
|
init_github();
|
|
3793
3761
|
init_use_action_log();
|
|
3794
3762
|
init_ink_instance();
|
|
3795
|
-
execFileAsync3 = promisify3(execFile3);
|
|
3796
3763
|
}
|
|
3797
3764
|
});
|
|
3798
3765
|
|
|
@@ -4439,7 +4406,7 @@ import { Box as Box15, Text as Text15, useInput as useInput11 } from "ink";
|
|
|
4439
4406
|
import { useRef as useRef12, useState as useState14 } from "react";
|
|
4440
4407
|
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4441
4408
|
function isTerminal(name) {
|
|
4442
|
-
return
|
|
4409
|
+
return TERMINAL_STATUS_RE.test(name);
|
|
4443
4410
|
}
|
|
4444
4411
|
function handlePickerInput(input2, key, state) {
|
|
4445
4412
|
if (key.escape) {
|
|
@@ -4548,11 +4515,10 @@ function StatusPicker({
|
|
|
4548
4515
|
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
4549
4516
|
] });
|
|
4550
4517
|
}
|
|
4551
|
-
var TERMINAL_STATUS_RE2;
|
|
4552
4518
|
var init_status_picker = __esm({
|
|
4553
4519
|
"src/board/components/status-picker.tsx"() {
|
|
4554
4520
|
"use strict";
|
|
4555
|
-
|
|
4521
|
+
init_constants();
|
|
4556
4522
|
}
|
|
4557
4523
|
});
|
|
4558
4524
|
|
|
@@ -4737,7 +4703,7 @@ function formatTargetDate(dateStr) {
|
|
|
4737
4703
|
color: "gray"
|
|
4738
4704
|
};
|
|
4739
4705
|
}
|
|
4740
|
-
function
|
|
4706
|
+
function timeAgo2(dateStr) {
|
|
4741
4707
|
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
|
|
4742
4708
|
if (seconds < 60) return "now";
|
|
4743
4709
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -4781,7 +4747,7 @@ function IssueRow({ issue, selfLogin, isSelected }) {
|
|
|
4781
4747
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4782
4748
|
/* @__PURE__ */ jsx17(Text16, { color: assigneeColor, children: assigneeText.padEnd(14) }),
|
|
4783
4749
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4784
|
-
/* @__PURE__ */ jsx17(Text16, { color: "gray", children:
|
|
4750
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: timeAgo2(issue.updatedAt).padStart(4) }),
|
|
4785
4751
|
target.text ? /* @__PURE__ */ jsxs17(Fragment3, { children: [
|
|
4786
4752
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4787
4753
|
/* @__PURE__ */ jsx17(Text16, { color: target.color, children: target.text })
|
|
@@ -4916,7 +4882,7 @@ function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
|
4916
4882
|
] });
|
|
4917
4883
|
}
|
|
4918
4884
|
case "activity": {
|
|
4919
|
-
const ago =
|
|
4885
|
+
const ago = timeAgo(row.event.timestamp);
|
|
4920
4886
|
return /* @__PURE__ */ jsxs19(Text18, { dimColor: true, children: [
|
|
4921
4887
|
" ",
|
|
4922
4888
|
ago,
|
|
@@ -4944,16 +4910,10 @@ function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
|
4944
4910
|
return /* @__PURE__ */ jsx19(Text18, { children: "" });
|
|
4945
4911
|
}
|
|
4946
4912
|
}
|
|
4947
|
-
function timeAgo2(date) {
|
|
4948
|
-
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
4949
|
-
if (seconds < 10) return "just now";
|
|
4950
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
4951
|
-
const minutes = Math.floor(seconds / 60);
|
|
4952
|
-
return `${minutes}m ago`;
|
|
4953
|
-
}
|
|
4954
4913
|
var init_row_renderer = __esm({
|
|
4955
4914
|
"src/board/components/row-renderer.tsx"() {
|
|
4956
4915
|
"use strict";
|
|
4916
|
+
init_constants();
|
|
4957
4917
|
init_issue_row();
|
|
4958
4918
|
init_task_row();
|
|
4959
4919
|
}
|
|
@@ -5002,9 +4962,6 @@ import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
|
5002
4962
|
import { Box as Box20, Text as Text20, useApp, useStdout } from "ink";
|
|
5003
4963
|
import { useCallback as useCallback11, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef13, useState as useState15 } from "react";
|
|
5004
4964
|
import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
5005
|
-
function isTerminalStatus(status) {
|
|
5006
|
-
return TERMINAL_STATUS_RE3.test(status);
|
|
5007
|
-
}
|
|
5008
4965
|
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
5009
4966
|
if (configuredGroups && configuredGroups.length > 0) {
|
|
5010
4967
|
return configuredGroups.map((entry) => {
|
|
@@ -5042,128 +4999,121 @@ function groupByStatus(issues) {
|
|
|
5042
4999
|
}
|
|
5043
5000
|
return groups;
|
|
5044
5001
|
}
|
|
5045
|
-
function
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
issues.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
5052
|
-
return issues;
|
|
5053
|
-
}
|
|
5054
|
-
function buildNavItems(repos, tasks, activityCount) {
|
|
5055
|
-
const items = [];
|
|
5056
|
-
if (activityCount > 0) {
|
|
5057
|
-
items.push({ id: "header:activity", section: "activity", type: "header" });
|
|
5058
|
-
}
|
|
5059
|
-
for (const rd of repos) {
|
|
5060
|
-
items.push({ id: `header:${rd.repo.shortName}`, section: rd.repo.shortName, type: "header" });
|
|
5002
|
+
function buildBoardTree(repos, tasks, activity) {
|
|
5003
|
+
const sections = repos.map((rd) => {
|
|
5004
|
+
const sectionId = rd.repo.name;
|
|
5005
|
+
if (rd.error) {
|
|
5006
|
+
return { repo: rd.repo, sectionId, groups: [], error: rd.error };
|
|
5007
|
+
}
|
|
5061
5008
|
const statusGroupDefs = resolveStatusGroups(rd.statusOptions, rd.repo.statusGroups);
|
|
5062
5009
|
const byStatus = groupByStatus(rd.issues);
|
|
5063
|
-
const
|
|
5010
|
+
const coveredKeys = /* @__PURE__ */ new Set();
|
|
5011
|
+
const groups = [];
|
|
5064
5012
|
for (const sg of statusGroupDefs) {
|
|
5065
|
-
const
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5013
|
+
const issues = [];
|
|
5014
|
+
for (const [status, statusIssues] of byStatus) {
|
|
5015
|
+
if (sg.statuses.some((s) => s.toLowerCase().trim() === status.toLowerCase().trim())) {
|
|
5016
|
+
issues.push(...statusIssues);
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (issues.length === 0) continue;
|
|
5020
|
+
issues.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
5021
|
+
groups.push({ label: sg.label, subId: `sub:${sectionId}:${sg.label}`, issues });
|
|
5022
|
+
for (const s of sg.statuses) coveredKeys.add(s.toLowerCase().trim());
|
|
5023
|
+
}
|
|
5024
|
+
for (const [status, statusIssues] of byStatus) {
|
|
5025
|
+
if (!(coveredKeys.has(status.toLowerCase().trim()) || isTerminalStatus(status))) {
|
|
5026
|
+
groups.push({ label: status, subId: `sub:${sectionId}:${status}`, issues: statusIssues });
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
return { repo: rd.repo, sectionId, groups, error: null };
|
|
5030
|
+
});
|
|
5031
|
+
return { activity, sections, tasks };
|
|
5032
|
+
}
|
|
5033
|
+
function buildNavItems(tree) {
|
|
5034
|
+
const items = [];
|
|
5035
|
+
if (tree.activity.length > 0)
|
|
5036
|
+
items.push({ id: "header:activity", section: "activity", type: "header" });
|
|
5037
|
+
for (const { repo, sectionId, groups } of tree.sections) {
|
|
5038
|
+
items.push({ id: `header:${sectionId}`, section: sectionId, type: "header" });
|
|
5039
|
+
for (const group of groups) {
|
|
5040
|
+
items.push({ id: group.subId, section: sectionId, type: "subHeader" });
|
|
5041
|
+
for (const issue of group.issues) {
|
|
5070
5042
|
items.push({
|
|
5071
|
-
id: `gh:${
|
|
5072
|
-
section:
|
|
5043
|
+
id: `gh:${repo.name}:${issue.number}`,
|
|
5044
|
+
section: sectionId,
|
|
5073
5045
|
type: "item",
|
|
5074
|
-
subSection: subId
|
|
5046
|
+
subSection: group.subId
|
|
5075
5047
|
});
|
|
5076
5048
|
}
|
|
5077
|
-
for (const s of sg.statuses) coveredStatuses.add(s);
|
|
5078
|
-
}
|
|
5079
|
-
for (const [status, issues] of byStatus) {
|
|
5080
|
-
if (!(coveredStatuses.has(status) || isTerminalStatus(status)) && issues.length > 0) {
|
|
5081
|
-
const subId = `sub:${rd.repo.shortName}:${status}`;
|
|
5082
|
-
items.push({ id: subId, section: rd.repo.shortName, type: "subHeader" });
|
|
5083
|
-
for (const issue of issues) {
|
|
5084
|
-
items.push({
|
|
5085
|
-
id: `gh:${rd.repo.name}:${issue.number}`,
|
|
5086
|
-
section: rd.repo.shortName,
|
|
5087
|
-
type: "item",
|
|
5088
|
-
subSection: subId
|
|
5089
|
-
});
|
|
5090
|
-
}
|
|
5091
|
-
}
|
|
5092
5049
|
}
|
|
5093
5050
|
}
|
|
5094
|
-
if (tasks.length > 0) {
|
|
5051
|
+
if (tree.tasks.length > 0) {
|
|
5095
5052
|
items.push({ id: "header:ticktick", section: "ticktick", type: "header" });
|
|
5096
|
-
for (const task2 of tasks)
|
|
5053
|
+
for (const task2 of tree.tasks)
|
|
5097
5054
|
items.push({ id: `tt:${task2.id}`, section: "ticktick", type: "item" });
|
|
5098
|
-
}
|
|
5099
5055
|
}
|
|
5100
5056
|
return items;
|
|
5101
5057
|
}
|
|
5102
|
-
function buildFlatRows(
|
|
5058
|
+
function buildFlatRows(tree, isCollapsed) {
|
|
5103
5059
|
const rows = [];
|
|
5104
|
-
if (activity.length > 0) {
|
|
5060
|
+
if (tree.activity.length > 0) {
|
|
5105
5061
|
const collapsed = isCollapsed("activity");
|
|
5106
5062
|
rows.push({
|
|
5107
5063
|
type: "sectionHeader",
|
|
5108
5064
|
key: "header:activity",
|
|
5109
5065
|
navId: "header:activity",
|
|
5110
5066
|
label: "Recent Activity (24h)",
|
|
5111
|
-
count: activity.length,
|
|
5067
|
+
count: tree.activity.length,
|
|
5112
5068
|
countLabel: "events",
|
|
5113
5069
|
isCollapsed: collapsed
|
|
5114
5070
|
});
|
|
5115
5071
|
if (!collapsed) {
|
|
5116
|
-
for (const [i, event] of activity.entries()) {
|
|
5072
|
+
for (const [i, event] of tree.activity.entries()) {
|
|
5117
5073
|
rows.push({ type: "activity", key: `act:${i}`, navId: null, event });
|
|
5118
5074
|
}
|
|
5119
5075
|
}
|
|
5120
5076
|
}
|
|
5121
|
-
for (const
|
|
5122
|
-
const
|
|
5123
|
-
const
|
|
5077
|
+
for (const { repo, sectionId, groups, error } of tree.sections) {
|
|
5078
|
+
const collapsed = isCollapsed(sectionId);
|
|
5079
|
+
const totalIssues = groups.reduce((s, g) => s + g.issues.length, 0);
|
|
5124
5080
|
rows.push({
|
|
5125
5081
|
type: "sectionHeader",
|
|
5126
|
-
key: `header:${
|
|
5127
|
-
navId: `header:${
|
|
5082
|
+
key: `header:${sectionId}`,
|
|
5083
|
+
navId: `header:${sectionId}`,
|
|
5128
5084
|
label: repo.shortName,
|
|
5129
|
-
|
|
5085
|
+
// display label still shows shortName
|
|
5086
|
+
count: totalIssues,
|
|
5130
5087
|
countLabel: "issues",
|
|
5131
5088
|
isCollapsed: collapsed
|
|
5132
5089
|
});
|
|
5133
5090
|
if (!collapsed) {
|
|
5134
|
-
if (
|
|
5135
|
-
rows.push({ type: "error", key: `error:${
|
|
5136
|
-
} else if (
|
|
5091
|
+
if (error) {
|
|
5092
|
+
rows.push({ type: "error", key: `error:${sectionId}`, navId: null, text: error });
|
|
5093
|
+
} else if (groups.length === 0) {
|
|
5137
5094
|
rows.push({
|
|
5138
5095
|
type: "subHeader",
|
|
5139
|
-
key: `empty:${
|
|
5096
|
+
key: `empty:${sectionId}`,
|
|
5140
5097
|
navId: null,
|
|
5141
5098
|
text: "No open issues"
|
|
5142
5099
|
});
|
|
5143
5100
|
} else {
|
|
5144
|
-
const statusGroupDefs = resolveStatusGroups(rd.statusOptions, rd.repo.statusGroups);
|
|
5145
|
-
const byStatus = groupByStatus(issues);
|
|
5146
|
-
const coveredStatuses = /* @__PURE__ */ new Set();
|
|
5147
5101
|
let isFirstGroup = true;
|
|
5148
|
-
for (const
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
if (!isFirstGroup) {
|
|
5152
|
-
rows.push({ type: "gap", key: `gap:${repo.shortName}:${sg.label}`, navId: null });
|
|
5153
|
-
}
|
|
5102
|
+
for (const group of groups) {
|
|
5103
|
+
if (!isFirstGroup)
|
|
5104
|
+
rows.push({ type: "gap", key: `gap:${sectionId}:${group.label}`, navId: null });
|
|
5154
5105
|
isFirstGroup = false;
|
|
5155
|
-
const
|
|
5156
|
-
const subCollapsed = isCollapsed(subId);
|
|
5106
|
+
const subCollapsed = isCollapsed(group.subId);
|
|
5157
5107
|
rows.push({
|
|
5158
5108
|
type: "subHeader",
|
|
5159
|
-
key: subId,
|
|
5160
|
-
navId: subId,
|
|
5161
|
-
text:
|
|
5162
|
-
count:
|
|
5109
|
+
key: group.subId,
|
|
5110
|
+
navId: group.subId,
|
|
5111
|
+
text: group.label,
|
|
5112
|
+
count: group.issues.length,
|
|
5163
5113
|
isCollapsed: subCollapsed
|
|
5164
5114
|
});
|
|
5165
5115
|
if (!subCollapsed) {
|
|
5166
|
-
for (const issue of
|
|
5116
|
+
for (const issue of group.issues) {
|
|
5167
5117
|
rows.push({
|
|
5168
5118
|
type: "issue",
|
|
5169
5119
|
key: `gh:${repo.name}:${issue.number}`,
|
|
@@ -5173,81 +5123,35 @@ function buildFlatRows(repos, tasks, activity, isCollapsed) {
|
|
|
5173
5123
|
});
|
|
5174
5124
|
}
|
|
5175
5125
|
}
|
|
5176
|
-
for (const s of sg.statuses) coveredStatuses.add(s);
|
|
5177
|
-
}
|
|
5178
|
-
for (const [status, groupIssues] of byStatus) {
|
|
5179
|
-
if (!(coveredStatuses.has(status) || isTerminalStatus(status)) && groupIssues.length > 0) {
|
|
5180
|
-
if (!isFirstGroup) {
|
|
5181
|
-
rows.push({ type: "gap", key: `gap:${repo.shortName}:${status}`, navId: null });
|
|
5182
|
-
}
|
|
5183
|
-
isFirstGroup = false;
|
|
5184
|
-
const subId = `sub:${repo.shortName}:${status}`;
|
|
5185
|
-
const subCollapsed = isCollapsed(subId);
|
|
5186
|
-
rows.push({
|
|
5187
|
-
type: "subHeader",
|
|
5188
|
-
key: subId,
|
|
5189
|
-
navId: subId,
|
|
5190
|
-
text: status,
|
|
5191
|
-
count: groupIssues.length,
|
|
5192
|
-
isCollapsed: subCollapsed
|
|
5193
|
-
});
|
|
5194
|
-
if (!subCollapsed) {
|
|
5195
|
-
for (const issue of groupIssues) {
|
|
5196
|
-
rows.push({
|
|
5197
|
-
type: "issue",
|
|
5198
|
-
key: `gh:${repo.name}:${issue.number}`,
|
|
5199
|
-
navId: `gh:${repo.name}:${issue.number}`,
|
|
5200
|
-
issue,
|
|
5201
|
-
repoName: repo.name
|
|
5202
|
-
});
|
|
5203
|
-
}
|
|
5204
|
-
}
|
|
5205
|
-
}
|
|
5206
5126
|
}
|
|
5207
5127
|
}
|
|
5208
5128
|
}
|
|
5209
5129
|
}
|
|
5210
|
-
if (tasks.length > 0) {
|
|
5130
|
+
if (tree.tasks.length > 0) {
|
|
5211
5131
|
const collapsed = isCollapsed("ticktick");
|
|
5212
5132
|
rows.push({
|
|
5213
5133
|
type: "sectionHeader",
|
|
5214
5134
|
key: "header:ticktick",
|
|
5215
5135
|
navId: "header:ticktick",
|
|
5216
5136
|
label: "Personal (TickTick)",
|
|
5217
|
-
count: tasks.length,
|
|
5137
|
+
count: tree.tasks.length,
|
|
5218
5138
|
countLabel: "tasks",
|
|
5219
5139
|
isCollapsed: collapsed
|
|
5220
5140
|
});
|
|
5221
5141
|
if (!collapsed) {
|
|
5222
|
-
for (const task2 of tasks)
|
|
5142
|
+
for (const task2 of tree.tasks)
|
|
5223
5143
|
rows.push({ type: "task", key: `tt:${task2.id}`, navId: `tt:${task2.id}`, task: task2 });
|
|
5224
|
-
}
|
|
5225
5144
|
}
|
|
5226
5145
|
}
|
|
5227
5146
|
return rows;
|
|
5228
5147
|
}
|
|
5229
|
-
function timeAgo3(date) {
|
|
5230
|
-
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
5231
|
-
if (seconds < 10) return "just now";
|
|
5232
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
5233
|
-
const minutes = Math.floor(seconds / 60);
|
|
5234
|
-
return `${minutes}m ago`;
|
|
5235
|
-
}
|
|
5236
5148
|
function openInBrowser(url) {
|
|
5149
|
+
if (!(url.startsWith("https://") || url.startsWith("http://"))) return;
|
|
5237
5150
|
try {
|
|
5238
5151
|
execFileSync3("open", [url], { stdio: "ignore" });
|
|
5239
5152
|
} catch {
|
|
5240
5153
|
}
|
|
5241
5154
|
}
|
|
5242
|
-
function findSelectedUrl(repos, selectedId) {
|
|
5243
|
-
if (!selectedId?.startsWith("gh:")) return null;
|
|
5244
|
-
for (const rd of repos) {
|
|
5245
|
-
for (const issue of rd.issues) {
|
|
5246
|
-
if (`gh:${rd.repo.name}:${issue.number}` === selectedId) return issue.url;
|
|
5247
|
-
}
|
|
5248
|
-
}
|
|
5249
|
-
return null;
|
|
5250
|
-
}
|
|
5251
5155
|
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
5252
5156
|
if (!selectedId?.startsWith("gh:")) return null;
|
|
5253
5157
|
for (const rd of repos) {
|
|
@@ -5258,8 +5162,17 @@ function findSelectedIssueWithRepo(repos, selectedId) {
|
|
|
5258
5162
|
}
|
|
5259
5163
|
return null;
|
|
5260
5164
|
}
|
|
5261
|
-
function
|
|
5262
|
-
|
|
5165
|
+
function RefreshAge({ lastRefresh }) {
|
|
5166
|
+
const [, setTick] = useState15(0);
|
|
5167
|
+
useEffect9(() => {
|
|
5168
|
+
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
5169
|
+
return () => clearInterval(id);
|
|
5170
|
+
}, []);
|
|
5171
|
+
if (!lastRefresh) return null;
|
|
5172
|
+
return /* @__PURE__ */ jsxs21(Text20, { color: refreshAgeColor(lastRefresh), children: [
|
|
5173
|
+
"Updated ",
|
|
5174
|
+
timeAgo(lastRefresh)
|
|
5175
|
+
] });
|
|
5263
5176
|
}
|
|
5264
5177
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
5265
5178
|
const { exit } = useApp();
|
|
@@ -5298,11 +5211,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5298
5211
|
const last = logEntries[logEntries.length - 1];
|
|
5299
5212
|
if (last?.status === "error") setLogVisible(true);
|
|
5300
5213
|
}, [logEntries]);
|
|
5301
|
-
const [, setTick] = useState15(0);
|
|
5302
5214
|
useEffect9(() => {
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5215
|
+
if (data?.ticktickError) {
|
|
5216
|
+
toast.error(`TickTick sync failed: ${data.ticktickError}`);
|
|
5217
|
+
}
|
|
5218
|
+
}, [data?.ticktickError, toast.error]);
|
|
5306
5219
|
const repos = useMemo3(() => {
|
|
5307
5220
|
let filtered = allRepos;
|
|
5308
5221
|
if (mineOnly) {
|
|
@@ -5321,10 +5234,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5321
5234
|
const q = searchQuery.toLowerCase();
|
|
5322
5235
|
return allTasks.filter((t) => t.title.toLowerCase().includes(q));
|
|
5323
5236
|
}, [allTasks, searchQuery]);
|
|
5324
|
-
const
|
|
5325
|
-
() =>
|
|
5326
|
-
[repos, tasks, allActivity
|
|
5237
|
+
const boardTree = useMemo3(
|
|
5238
|
+
() => buildBoardTree(repos, tasks, allActivity),
|
|
5239
|
+
[repos, tasks, allActivity]
|
|
5327
5240
|
);
|
|
5241
|
+
const navItems = useMemo3(() => buildNavItems(boardTree), [boardTree]);
|
|
5328
5242
|
const nav = useNavigation(navItems);
|
|
5329
5243
|
const getRepoForId = useCallback11((id) => {
|
|
5330
5244
|
if (id.startsWith("gh:")) {
|
|
@@ -5405,7 +5319,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5405
5319
|
const [focusLabel, setFocusLabel] = useState15(null);
|
|
5406
5320
|
const handleEnterFocus = useCallback11(() => {
|
|
5407
5321
|
const id = nav.selectedId;
|
|
5408
|
-
if (!id ||
|
|
5322
|
+
if (!id || isHeaderId(id)) return;
|
|
5409
5323
|
let label = "";
|
|
5410
5324
|
if (id.startsWith("gh:")) {
|
|
5411
5325
|
const found = findSelectedIssueWithRepo(repos, id);
|
|
@@ -5476,11 +5390,14 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5476
5390
|
termSize.rows - CHROME_ROWS - overlayBarRows - toastRows - logPaneRows
|
|
5477
5391
|
);
|
|
5478
5392
|
const flatRows = useMemo3(
|
|
5479
|
-
() => buildFlatRows(
|
|
5480
|
-
[
|
|
5393
|
+
() => buildFlatRows(boardTree, nav.isCollapsed),
|
|
5394
|
+
[boardTree, nav.isCollapsed]
|
|
5481
5395
|
);
|
|
5482
5396
|
const scrollRef = useRef13(0);
|
|
5483
|
-
const selectedRowIdx =
|
|
5397
|
+
const selectedRowIdx = useMemo3(
|
|
5398
|
+
() => flatRows.findIndex((r) => r.navId === nav.selectedId),
|
|
5399
|
+
[flatRows, nav.selectedId]
|
|
5400
|
+
);
|
|
5484
5401
|
if (selectedRowIdx >= 0) {
|
|
5485
5402
|
if (selectedRowIdx < scrollRef.current) {
|
|
5486
5403
|
scrollRef.current = selectedRowIdx;
|
|
@@ -5497,7 +5414,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5497
5414
|
const belowCount = flatRows.length - scrollRef.current - viewportHeight;
|
|
5498
5415
|
const selectedItem = useMemo3(() => {
|
|
5499
5416
|
const id = nav.selectedId;
|
|
5500
|
-
if (!id ||
|
|
5417
|
+
if (!id || isHeaderId(id)) return { issue: null, task: null, repoName: null };
|
|
5501
5418
|
if (id.startsWith("gh:")) {
|
|
5502
5419
|
for (const rd of repos) {
|
|
5503
5420
|
for (const issue of rd.issues) {
|
|
@@ -5528,8 +5445,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5528
5445
|
return rd?.statusOptions ?? [];
|
|
5529
5446
|
}, [selectedItem.repoName, repos, multiSelect.count, multiSelect.constrainedRepo]);
|
|
5530
5447
|
const handleOpen = useCallback11(() => {
|
|
5531
|
-
const
|
|
5532
|
-
if (
|
|
5448
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
5449
|
+
if (found) openInBrowser(found.issue.url);
|
|
5533
5450
|
}, [repos, nav.selectedId]);
|
|
5534
5451
|
const handleSlack = useCallback11(() => {
|
|
5535
5452
|
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
@@ -5697,13 +5614,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5697
5614
|
isRefreshing ? /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5698
5615
|
/* @__PURE__ */ jsx21(Spinner4, { label: "" }),
|
|
5699
5616
|
/* @__PURE__ */ jsx21(Text20, { color: "cyan", children: " Refreshing..." })
|
|
5700
|
-
] }) :
|
|
5701
|
-
/* @__PURE__ */
|
|
5702
|
-
"Updated ",
|
|
5703
|
-
timeAgo3(lastRefresh)
|
|
5704
|
-
] }),
|
|
5617
|
+
] }) : /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5618
|
+
/* @__PURE__ */ jsx21(RefreshAge, { lastRefresh }),
|
|
5705
5619
|
consecutiveFailures > 0 ? /* @__PURE__ */ jsx21(Text20, { color: "red", children: " (!)" }) : null
|
|
5706
|
-
] })
|
|
5620
|
+
] }),
|
|
5707
5621
|
autoRefreshPaused ? /* @__PURE__ */ jsx21(Text20, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
|
|
5708
5622
|
] }),
|
|
5709
5623
|
error ? /* @__PURE__ */ jsxs21(Text20, { color: "red", children: [
|
|
@@ -5800,17 +5714,19 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5800
5714
|
multiSelectCount: multiSelect.count,
|
|
5801
5715
|
searchQuery,
|
|
5802
5716
|
mineOnly,
|
|
5803
|
-
hasUndoable
|
|
5717
|
+
hasUndoable,
|
|
5718
|
+
onHeader: isHeaderId(nav.selectedId)
|
|
5804
5719
|
}
|
|
5805
5720
|
)
|
|
5806
5721
|
] });
|
|
5807
5722
|
}
|
|
5808
|
-
var
|
|
5723
|
+
var PRIORITY_RANK, CHROME_ROWS;
|
|
5809
5724
|
var init_dashboard = __esm({
|
|
5810
5725
|
"src/board/components/dashboard.tsx"() {
|
|
5811
5726
|
"use strict";
|
|
5812
5727
|
init_clipboard();
|
|
5813
5728
|
init_github();
|
|
5729
|
+
init_constants();
|
|
5814
5730
|
init_use_action_log();
|
|
5815
5731
|
init_use_actions();
|
|
5816
5732
|
init_use_data();
|
|
@@ -5825,7 +5741,6 @@ var init_dashboard = __esm({
|
|
|
5825
5741
|
init_overlay_renderer();
|
|
5826
5742
|
init_row_renderer();
|
|
5827
5743
|
init_toast_container();
|
|
5828
|
-
TERMINAL_STATUS_RE3 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
5829
5744
|
PRIORITY_RANK = {
|
|
5830
5745
|
"priority:critical": 0,
|
|
5831
5746
|
"priority:high": 1,
|
|
@@ -5872,9 +5787,6 @@ function extractSlackUrl(body) {
|
|
|
5872
5787
|
const match = body.match(SLACK_URL_RE2);
|
|
5873
5788
|
return match?.[0];
|
|
5874
5789
|
}
|
|
5875
|
-
function formatError2(err) {
|
|
5876
|
-
return err instanceof Error ? err.message : String(err);
|
|
5877
|
-
}
|
|
5878
5790
|
function fetchRecentActivity(repoName, shortName) {
|
|
5879
5791
|
try {
|
|
5880
5792
|
const output = execFileSync4(
|
|
@@ -5954,28 +5866,34 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
5954
5866
|
fetchOpts.assignee = config2.board.assignee;
|
|
5955
5867
|
}
|
|
5956
5868
|
const issues = fetchRepoIssues(repo.name, fetchOpts);
|
|
5869
|
+
let enrichedIssues = issues;
|
|
5957
5870
|
let statusOptions = [];
|
|
5958
5871
|
try {
|
|
5959
5872
|
const enrichMap = fetchProjectEnrichment(repo.name, repo.projectNumber);
|
|
5960
|
-
|
|
5873
|
+
enrichedIssues = issues.map((issue) => {
|
|
5961
5874
|
const e = enrichMap.get(issue.number);
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5875
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
5876
|
+
return {
|
|
5877
|
+
...issue,
|
|
5878
|
+
...e?.targetDate !== void 0 ? { targetDate: e.targetDate } : {},
|
|
5879
|
+
...e?.projectStatus !== void 0 ? { projectStatus: e.projectStatus } : {},
|
|
5880
|
+
...slackUrl ? { slackThreadUrl: slackUrl } : {}
|
|
5881
|
+
};
|
|
5882
|
+
});
|
|
5965
5883
|
statusOptions = fetchProjectStatusOptions(
|
|
5966
5884
|
repo.name,
|
|
5967
5885
|
repo.projectNumber,
|
|
5968
5886
|
repo.statusFieldId
|
|
5969
5887
|
);
|
|
5970
5888
|
} catch {
|
|
5889
|
+
enrichedIssues = issues.map((issue) => {
|
|
5890
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
5891
|
+
return slackUrl ? { ...issue, slackThreadUrl: slackUrl } : issue;
|
|
5892
|
+
});
|
|
5971
5893
|
}
|
|
5972
|
-
|
|
5973
|
-
const slackUrl = extractSlackUrl(issue.body);
|
|
5974
|
-
if (slackUrl) issue.slackThreadUrl = slackUrl;
|
|
5975
|
-
}
|
|
5976
|
-
return { repo, issues, statusOptions, error: null };
|
|
5894
|
+
return { repo, issues: enrichedIssues, statusOptions, error: null };
|
|
5977
5895
|
} catch (err) {
|
|
5978
|
-
return { repo, issues: [], statusOptions: [], error:
|
|
5896
|
+
return { repo, issues: [], statusOptions: [], error: formatError(err) };
|
|
5979
5897
|
}
|
|
5980
5898
|
});
|
|
5981
5899
|
let ticktick = [];
|
|
@@ -5990,7 +5908,7 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
5990
5908
|
ticktick = tasks.filter((t) => t.status !== 2 /* Completed */);
|
|
5991
5909
|
}
|
|
5992
5910
|
} catch (err) {
|
|
5993
|
-
ticktickError =
|
|
5911
|
+
ticktickError = formatError(err);
|
|
5994
5912
|
}
|
|
5995
5913
|
}
|
|
5996
5914
|
const activity = [];
|
|
@@ -6015,6 +5933,7 @@ var init_fetch = __esm({
|
|
|
6015
5933
|
init_config();
|
|
6016
5934
|
init_github();
|
|
6017
5935
|
init_types();
|
|
5936
|
+
init_constants();
|
|
6018
5937
|
SLACK_URL_RE2 = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/i;
|
|
6019
5938
|
}
|
|
6020
5939
|
});
|
|
@@ -6193,7 +6112,9 @@ function renderBoardJson(data, selfLogin) {
|
|
|
6193
6112
|
labels: i.labels.map((l) => l.name),
|
|
6194
6113
|
updatedAt: i.updatedAt,
|
|
6195
6114
|
isMine: (i.assignees ?? []).some((a) => a.login === selfLogin),
|
|
6196
|
-
slackThreadUrl: i.slackThreadUrl ?? null
|
|
6115
|
+
slackThreadUrl: i.slackThreadUrl ?? null,
|
|
6116
|
+
projectStatus: i.projectStatus ?? null,
|
|
6117
|
+
targetDate: i.targetDate ?? null
|
|
6197
6118
|
}))
|
|
6198
6119
|
})),
|
|
6199
6120
|
ticktick: {
|
|
@@ -6206,6 +6127,7 @@ function renderBoardJson(data, selfLogin) {
|
|
|
6206
6127
|
tags: t.tags
|
|
6207
6128
|
}))
|
|
6208
6129
|
},
|
|
6130
|
+
activity: data.activity,
|
|
6209
6131
|
fetchedAt: data.fetchedAt.toISOString()
|
|
6210
6132
|
}
|
|
6211
6133
|
};
|
|
@@ -6224,8 +6146,8 @@ var init_format_static = __esm({
|
|
|
6224
6146
|
init_ai();
|
|
6225
6147
|
init_api();
|
|
6226
6148
|
init_config();
|
|
6227
|
-
import { execFile as
|
|
6228
|
-
import { promisify as
|
|
6149
|
+
import { execFile as execFile2, execFileSync as execFileSync5 } from "child_process";
|
|
6150
|
+
import { promisify as promisify2 } from "util";
|
|
6229
6151
|
import { Command } from "commander";
|
|
6230
6152
|
|
|
6231
6153
|
// src/init.ts
|
|
@@ -6710,6 +6632,7 @@ function printSyncStatus(state, repos) {
|
|
|
6710
6632
|
|
|
6711
6633
|
// src/sync.ts
|
|
6712
6634
|
init_api();
|
|
6635
|
+
init_constants();
|
|
6713
6636
|
init_config();
|
|
6714
6637
|
init_github();
|
|
6715
6638
|
init_sync_state();
|
|
@@ -6717,9 +6640,6 @@ init_types();
|
|
|
6717
6640
|
function emptySyncResult() {
|
|
6718
6641
|
return { created: [], updated: [], completed: [], ghUpdated: [], errors: [] };
|
|
6719
6642
|
}
|
|
6720
|
-
function formatError(err) {
|
|
6721
|
-
return err instanceof Error ? err.message : String(err);
|
|
6722
|
-
}
|
|
6723
6643
|
function repoShortName(repo) {
|
|
6724
6644
|
return repo.split("/")[1] ?? repo;
|
|
6725
6645
|
}
|
|
@@ -6778,23 +6698,29 @@ async function syncGitHubToTickTick(config2, state, api, result, dryRun) {
|
|
|
6778
6698
|
failedRepos.add(repoConfig.name);
|
|
6779
6699
|
continue;
|
|
6780
6700
|
}
|
|
6701
|
+
let enrichMap;
|
|
6702
|
+
try {
|
|
6703
|
+
enrichMap = fetchProjectEnrichment(repoConfig.name, repoConfig.projectNumber);
|
|
6704
|
+
} catch {
|
|
6705
|
+
enrichMap = /* @__PURE__ */ new Map();
|
|
6706
|
+
}
|
|
6781
6707
|
for (const issue of issues) {
|
|
6782
6708
|
const key = `${repoConfig.name}#${issue.number}`;
|
|
6783
6709
|
openIssueKeys.add(key);
|
|
6784
|
-
await syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key);
|
|
6710
|
+
await syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key, enrichMap);
|
|
6785
6711
|
}
|
|
6786
6712
|
}
|
|
6787
6713
|
return { openIssueKeys, failedRepos };
|
|
6788
6714
|
}
|
|
6789
|
-
async function syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key) {
|
|
6715
|
+
async function syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key, enrichMap) {
|
|
6790
6716
|
try {
|
|
6791
6717
|
const existing = findMapping(state, repoConfig.name, issue.number);
|
|
6792
6718
|
if (existing && existing.githubUpdatedAt === issue.updatedAt) return;
|
|
6793
|
-
const
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6719
|
+
const enrichment = enrichMap.get(issue.number);
|
|
6720
|
+
const projectFields = {
|
|
6721
|
+
...enrichment?.targetDate !== void 0 && { targetDate: enrichment.targetDate },
|
|
6722
|
+
...enrichment?.projectStatus !== void 0 && { status: enrichment.projectStatus }
|
|
6723
|
+
};
|
|
6798
6724
|
if (!existing) {
|
|
6799
6725
|
await createTickTickTask(
|
|
6800
6726
|
state,
|
|
@@ -6953,7 +6879,7 @@ if (major < 22) {
|
|
|
6953
6879
|
);
|
|
6954
6880
|
process.exit(1);
|
|
6955
6881
|
}
|
|
6956
|
-
var
|
|
6882
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
6957
6883
|
async function resolveRef(ref, config2) {
|
|
6958
6884
|
const { parseIssueRef: parseIssueRef2 } = await Promise.resolve().then(() => (init_pick(), pick_exports));
|
|
6959
6885
|
try {
|
|
@@ -6981,8 +6907,7 @@ var PRIORITY_MAP = {
|
|
|
6981
6907
|
function parsePriority(value) {
|
|
6982
6908
|
const p = PRIORITY_MAP[value.toLowerCase()];
|
|
6983
6909
|
if (p === void 0) {
|
|
6984
|
-
|
|
6985
|
-
process.exit(1);
|
|
6910
|
+
errorOut(`Invalid priority: "${value}". Valid values: none, low, medium, high`);
|
|
6986
6911
|
}
|
|
6987
6912
|
return p;
|
|
6988
6913
|
}
|
|
@@ -6998,7 +6923,7 @@ function resolveProjectId(projectId) {
|
|
|
6998
6923
|
process.exit(1);
|
|
6999
6924
|
}
|
|
7000
6925
|
var program = new Command();
|
|
7001
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
6926
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.8.1").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
7002
6927
|
const opts = thisCommand.opts();
|
|
7003
6928
|
if (opts.json) setFormat("json");
|
|
7004
6929
|
if (opts.human) setFormat("human");
|
|
@@ -7020,9 +6945,7 @@ task.command("add <title>").description("Create a new task").option("-p, --prior
|
|
|
7020
6945
|
if (opts.tags) input2.tags = opts.tags.split(",").map((t) => t.trim());
|
|
7021
6946
|
if (opts.allDay) input2.isAllDay = true;
|
|
7022
6947
|
const created = await api.createTask(input2);
|
|
7023
|
-
printSuccess(`Created: ${created.title}`, {
|
|
7024
|
-
task: created
|
|
7025
|
-
});
|
|
6948
|
+
printSuccess(`Created: ${created.title}`, { task: created });
|
|
7026
6949
|
});
|
|
7027
6950
|
task.command("list").description("List tasks in a project").option("--project <id>", "Project ID (overrides default)").option("--all", "Include completed tasks").option("-p, --priority <level>", "Filter by minimum priority").option("-t, --tag <tag>", "Filter by tag").action(async (opts) => {
|
|
7028
6951
|
const api = createClient();
|
|
@@ -7063,9 +6986,7 @@ task.command("update <taskId>").description("Update a task").option("--title <ti
|
|
|
7063
6986
|
if (opts.content) input2.content = opts.content;
|
|
7064
6987
|
if (opts.tags) input2.tags = opts.tags.split(",").map((t) => t.trim());
|
|
7065
6988
|
const updated = await api.updateTask(input2);
|
|
7066
|
-
printSuccess(`Updated: ${updated.title}`, {
|
|
7067
|
-
task: updated
|
|
7068
|
-
});
|
|
6989
|
+
printSuccess(`Updated: ${updated.title}`, { task: updated });
|
|
7069
6990
|
});
|
|
7070
6991
|
task.command("delete <taskId>").description("Delete a task").option("--project <id>", "Project ID (overrides default)").action(async (taskId, opts) => {
|
|
7071
6992
|
const api = createClient();
|
|
@@ -7442,7 +7363,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
|
|
|
7442
7363
|
const repoArg = repo;
|
|
7443
7364
|
try {
|
|
7444
7365
|
if (useJson()) {
|
|
7445
|
-
const output = await
|
|
7366
|
+
const output = await execFileAsync2("gh", args, { encoding: "utf-8", timeout: 6e4 });
|
|
7446
7367
|
const url = output.stdout.trim();
|
|
7447
7368
|
const issueNumber = Number.parseInt(url.split("/").pop() ?? "0", 10);
|
|
7448
7369
|
jsonOut({ ok: true, data: { url, issueNumber, repo: repoArg } });
|
|
@@ -7494,7 +7415,20 @@ issueCommand.command("move <issueRef> <status>").description("Change project sta
|
|
|
7494
7415
|
errorOut(`Invalid status "${status}". Valid: ${valid}`, { status, validStatuses: valid });
|
|
7495
7416
|
}
|
|
7496
7417
|
if (opts.dryRun) {
|
|
7497
|
-
|
|
7418
|
+
if (useJson()) {
|
|
7419
|
+
jsonOut({
|
|
7420
|
+
ok: true,
|
|
7421
|
+
dryRun: true,
|
|
7422
|
+
would: {
|
|
7423
|
+
action: "move",
|
|
7424
|
+
issue: ref.issueNumber,
|
|
7425
|
+
repo: rc.shortName,
|
|
7426
|
+
status: target.name
|
|
7427
|
+
}
|
|
7428
|
+
});
|
|
7429
|
+
} else {
|
|
7430
|
+
console.log(`[dry-run] Would move ${rc.shortName}#${ref.issueNumber} \u2192 "${target.name}"`);
|
|
7431
|
+
}
|
|
7498
7432
|
return;
|
|
7499
7433
|
}
|
|
7500
7434
|
await updateProjectItemStatusAsync2(rc.name, ref.issueNumber, {
|
|
@@ -7517,7 +7451,15 @@ issueCommand.command("assign <issueRef>").description("Assign issue to self or a
|
|
|
7517
7451
|
process.exit(1);
|
|
7518
7452
|
}
|
|
7519
7453
|
if (opts.dryRun) {
|
|
7520
|
-
|
|
7454
|
+
if (useJson()) {
|
|
7455
|
+
jsonOut({
|
|
7456
|
+
ok: true,
|
|
7457
|
+
dryRun: true,
|
|
7458
|
+
would: { action: "assign", issue: ref.issueNumber, repo: ref.repo.shortName, user }
|
|
7459
|
+
});
|
|
7460
|
+
} else {
|
|
7461
|
+
console.log(`[dry-run] Would assign ${ref.repo.shortName}#${ref.issueNumber} to @${user}`);
|
|
7462
|
+
}
|
|
7521
7463
|
return;
|
|
7522
7464
|
}
|
|
7523
7465
|
const { assignIssueToAsync: assignIssueToAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7537,7 +7479,17 @@ issueCommand.command("unassign <issueRef>").description("Remove assignee from is
|
|
|
7537
7479
|
process.exit(1);
|
|
7538
7480
|
}
|
|
7539
7481
|
if (opts.dryRun) {
|
|
7540
|
-
|
|
7482
|
+
if (useJson()) {
|
|
7483
|
+
jsonOut({
|
|
7484
|
+
ok: true,
|
|
7485
|
+
dryRun: true,
|
|
7486
|
+
would: { action: "unassign", issue: ref.issueNumber, repo: ref.repo.shortName, user }
|
|
7487
|
+
});
|
|
7488
|
+
} else {
|
|
7489
|
+
console.log(
|
|
7490
|
+
`[dry-run] Would remove @${user} from ${ref.repo.shortName}#${ref.issueNumber}`
|
|
7491
|
+
);
|
|
7492
|
+
}
|
|
7541
7493
|
return;
|
|
7542
7494
|
}
|
|
7543
7495
|
const { unassignIssueAsync: unassignIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7552,7 +7504,17 @@ issueCommand.command("comment <issueRef> <text>").description("Post a comment on
|
|
|
7552
7504
|
const cfg = loadFullConfig();
|
|
7553
7505
|
const ref = await resolveRef(issueRef, cfg);
|
|
7554
7506
|
if (opts.dryRun) {
|
|
7555
|
-
|
|
7507
|
+
if (useJson()) {
|
|
7508
|
+
jsonOut({
|
|
7509
|
+
ok: true,
|
|
7510
|
+
dryRun: true,
|
|
7511
|
+
would: { action: "comment", issue: ref.issueNumber, repo: ref.repo.shortName, text }
|
|
7512
|
+
});
|
|
7513
|
+
} else {
|
|
7514
|
+
console.log(
|
|
7515
|
+
`[dry-run] Would comment on ${ref.repo.shortName}#${ref.issueNumber}: "${text}"`
|
|
7516
|
+
);
|
|
7517
|
+
}
|
|
7556
7518
|
return;
|
|
7557
7519
|
}
|
|
7558
7520
|
const { addCommentAsync: addCommentAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7588,9 +7550,17 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
7588
7550
|
process.exit(1);
|
|
7589
7551
|
}
|
|
7590
7552
|
if (opts.dryRun) {
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7553
|
+
if (useJson()) {
|
|
7554
|
+
jsonOut({
|
|
7555
|
+
ok: true,
|
|
7556
|
+
dryRun: true,
|
|
7557
|
+
would: { action: "edit", issue: ref.issueNumber, repo: ref.repo.shortName, changes }
|
|
7558
|
+
});
|
|
7559
|
+
} else {
|
|
7560
|
+
console.log(
|
|
7561
|
+
`[dry-run] Would edit ${ref.repo.shortName}#${ref.issueNumber}: ${changes.join("; ")}`
|
|
7562
|
+
);
|
|
7563
|
+
}
|
|
7594
7564
|
return;
|
|
7595
7565
|
}
|
|
7596
7566
|
const ghArgs = ["issue", "edit", String(ref.issueNumber), "--repo", ref.repo.name];
|
|
@@ -7601,7 +7571,7 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
7601
7571
|
if (opts.assignee) ghArgs.push("--add-assignee", opts.assignee);
|
|
7602
7572
|
if (opts.removeAssignee) ghArgs.push("--remove-assignee", opts.removeAssignee);
|
|
7603
7573
|
if (useJson()) {
|
|
7604
|
-
await
|
|
7574
|
+
await execFileAsync2("gh", ghArgs, { encoding: "utf-8", timeout: 3e4 });
|
|
7605
7575
|
jsonOut({ ok: true, data: { issue: ref.issueNumber, changes } });
|
|
7606
7576
|
} else {
|
|
7607
7577
|
execFileSync5("gh", ghArgs, { stdio: "inherit" });
|
|
@@ -7613,9 +7583,22 @@ issueCommand.command("label <issueRef> <label>").description("Add or remove a la
|
|
|
7613
7583
|
const ref = await resolveRef(issueRef, cfg);
|
|
7614
7584
|
const verb = opts.remove ? "remove" : "add";
|
|
7615
7585
|
if (opts.dryRun) {
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7586
|
+
if (useJson()) {
|
|
7587
|
+
jsonOut({
|
|
7588
|
+
ok: true,
|
|
7589
|
+
dryRun: true,
|
|
7590
|
+
would: {
|
|
7591
|
+
action: `${verb}Label`,
|
|
7592
|
+
issue: ref.issueNumber,
|
|
7593
|
+
repo: ref.repo.shortName,
|
|
7594
|
+
label
|
|
7595
|
+
}
|
|
7596
|
+
});
|
|
7597
|
+
} else {
|
|
7598
|
+
console.log(
|
|
7599
|
+
`[dry-run] Would ${verb} label "${label}" on ${ref.repo.shortName}#${ref.issueNumber}`
|
|
7600
|
+
);
|
|
7601
|
+
}
|
|
7619
7602
|
return;
|
|
7620
7603
|
}
|
|
7621
7604
|
if (opts.remove) {
|
|
@@ -7651,6 +7634,140 @@ issueCommand.command("statuses").description("List available project statuses fo
|
|
|
7651
7634
|
console.log(`Available statuses for ${repo}: ${statuses.map((s) => s.name).join(", ")}`);
|
|
7652
7635
|
}
|
|
7653
7636
|
});
|
|
7637
|
+
async function moveSingleIssue(r, status, cfg) {
|
|
7638
|
+
try {
|
|
7639
|
+
const ref = await resolveRef(r, cfg);
|
|
7640
|
+
const rc = ref.repo;
|
|
7641
|
+
if (!(rc.statusFieldId && rc.projectNumber)) {
|
|
7642
|
+
throw new Error(`${rc.name} is not configured with a project board. Run: hog init`);
|
|
7643
|
+
}
|
|
7644
|
+
const { fetchProjectStatusOptions: fetchProjectStatusOptions2, updateProjectItemStatusAsync: updateProjectItemStatusAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7645
|
+
const options = fetchProjectStatusOptions2(rc.name, rc.projectNumber, rc.statusFieldId);
|
|
7646
|
+
const target = options.find((o) => o.name.toLowerCase() === status.toLowerCase());
|
|
7647
|
+
if (!target) {
|
|
7648
|
+
const valid = options.map((o) => o.name).join(", ");
|
|
7649
|
+
throw new Error(`Invalid status "${status}". Valid: ${valid}`);
|
|
7650
|
+
}
|
|
7651
|
+
await updateProjectItemStatusAsync2(rc.name, ref.issueNumber, {
|
|
7652
|
+
projectNumber: rc.projectNumber,
|
|
7653
|
+
statusFieldId: rc.statusFieldId,
|
|
7654
|
+
optionId: target.id
|
|
7655
|
+
});
|
|
7656
|
+
return { ref: r, success: true };
|
|
7657
|
+
} catch (err) {
|
|
7658
|
+
return { ref: r, success: false, error: err instanceof Error ? err.message : String(err) };
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7661
|
+
function outputBulkResults(results) {
|
|
7662
|
+
const allOk = results.every((r) => r.success);
|
|
7663
|
+
if (useJson()) {
|
|
7664
|
+
jsonOut({ ok: allOk, results });
|
|
7665
|
+
} else {
|
|
7666
|
+
for (const r of results) {
|
|
7667
|
+
if (!r.success) {
|
|
7668
|
+
console.error(
|
|
7669
|
+
`Failed ${r.ref}: ${r.error}`
|
|
7670
|
+
);
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
issueCommand.command("bulk-assign <refs...>").description(
|
|
7676
|
+
"Assign multiple issues to self or a specific user (e.g., hog issue bulk-assign myrepo/42 myrepo/43)"
|
|
7677
|
+
).option("--user <username>", "GitHub username to assign (default: configured assignee)").option("--dry-run", "Print what would change without mutating").action(async (refs, opts) => {
|
|
7678
|
+
const cfg = loadFullConfig();
|
|
7679
|
+
const user = opts.user ?? cfg.board.assignee;
|
|
7680
|
+
if (!user) {
|
|
7681
|
+
errorOut("no user specified. Use --user or configure board.assignee in hog init");
|
|
7682
|
+
}
|
|
7683
|
+
if (opts.dryRun) {
|
|
7684
|
+
if (useJson()) {
|
|
7685
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-assign", refs, user } });
|
|
7686
|
+
} else {
|
|
7687
|
+
for (const r of refs) {
|
|
7688
|
+
console.log(`[dry-run] Would assign ${r} to @${user}`);
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
return;
|
|
7692
|
+
}
|
|
7693
|
+
const { assignIssueToAsync: assignIssueToAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7694
|
+
const results = [];
|
|
7695
|
+
for (const r of refs) {
|
|
7696
|
+
try {
|
|
7697
|
+
const ref = await resolveRef(r, cfg);
|
|
7698
|
+
await assignIssueToAsync2(ref.repo.name, ref.issueNumber, user);
|
|
7699
|
+
results.push({ ref: r, success: true });
|
|
7700
|
+
if (!useJson()) console.log(`Assigned ${r} to @${user}`);
|
|
7701
|
+
} catch (err) {
|
|
7702
|
+
results.push({
|
|
7703
|
+
ref: r,
|
|
7704
|
+
success: false,
|
|
7705
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
outputBulkResults(results);
|
|
7710
|
+
});
|
|
7711
|
+
issueCommand.command("bulk-unassign <refs...>").description(
|
|
7712
|
+
"Remove assignee from multiple issues (e.g., hog issue bulk-unassign myrepo/42 myrepo/43)"
|
|
7713
|
+
).option("--user <username>", "GitHub username to remove (default: configured assignee)").option("--dry-run", "Print what would change without mutating").action(async (refs, opts) => {
|
|
7714
|
+
const cfg = loadFullConfig();
|
|
7715
|
+
const user = opts.user ?? cfg.board.assignee;
|
|
7716
|
+
if (!user) {
|
|
7717
|
+
errorOut("no user specified. Use --user or configure board.assignee in hog init");
|
|
7718
|
+
}
|
|
7719
|
+
if (opts.dryRun) {
|
|
7720
|
+
if (useJson()) {
|
|
7721
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-unassign", refs, user } });
|
|
7722
|
+
} else {
|
|
7723
|
+
for (const r of refs) {
|
|
7724
|
+
console.log(`[dry-run] Would remove @${user} from ${r}`);
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
return;
|
|
7728
|
+
}
|
|
7729
|
+
const { unassignIssueAsync: unassignIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7730
|
+
const results = [];
|
|
7731
|
+
for (const r of refs) {
|
|
7732
|
+
try {
|
|
7733
|
+
const ref = await resolveRef(r, cfg);
|
|
7734
|
+
await unassignIssueAsync2(ref.repo.name, ref.issueNumber, user);
|
|
7735
|
+
results.push({ ref: r, success: true });
|
|
7736
|
+
if (!useJson()) console.log(`Removed @${user} from ${r}`);
|
|
7737
|
+
} catch (err) {
|
|
7738
|
+
results.push({
|
|
7739
|
+
ref: r,
|
|
7740
|
+
success: false,
|
|
7741
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7742
|
+
});
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7745
|
+
outputBulkResults(results);
|
|
7746
|
+
});
|
|
7747
|
+
issueCommand.command("bulk-move <status> <refs...>").description(
|
|
7748
|
+
"Move multiple issues to a project status (e.g., hog issue bulk-move 'In Review' myrepo/42 myrepo/43)"
|
|
7749
|
+
).option("--dry-run", "Print what would change without mutating").action(async (status, refs, opts) => {
|
|
7750
|
+
const cfg = loadFullConfig();
|
|
7751
|
+
if (opts.dryRun) {
|
|
7752
|
+
if (useJson()) {
|
|
7753
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-move", refs, status } });
|
|
7754
|
+
} else {
|
|
7755
|
+
for (const r of refs) {
|
|
7756
|
+
console.log(`[dry-run] Would move ${r} \u2192 "${status}"`);
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
return;
|
|
7760
|
+
}
|
|
7761
|
+
const results = await Promise.all(
|
|
7762
|
+
refs.map((r) => moveSingleIssue(r, status, cfg))
|
|
7763
|
+
);
|
|
7764
|
+
if (!useJson()) {
|
|
7765
|
+
for (const r of results) {
|
|
7766
|
+
if (r.success) console.log(`Moved ${r.ref} \u2192 ${status}`);
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
outputBulkResults(results);
|
|
7770
|
+
});
|
|
7654
7771
|
program.addCommand(issueCommand);
|
|
7655
7772
|
var logCommand = program.command("log").description("Action log commands");
|
|
7656
7773
|
logCommand.command("show").description("Show recent action log entries").option("--limit <n>", "number of entries to show", "50").action((opts) => {
|