@ondrej-svec/hog 1.7.2 → 1.8.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 +448 -303
- 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
|
|
|
@@ -2579,19 +2570,6 @@ function useToast() {
|
|
|
2579
2570
|
[addToast, removeToast]
|
|
2580
2571
|
)
|
|
2581
2572
|
};
|
|
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
2573
|
const handleErrorAction = useCallback7(
|
|
2596
2574
|
(action) => {
|
|
2597
2575
|
const errorToast = toasts.find((t) => t.type === "error");
|
|
@@ -2609,7 +2587,7 @@ function useToast() {
|
|
|
2609
2587
|
},
|
|
2610
2588
|
[toasts, removeToast]
|
|
2611
2589
|
);
|
|
2612
|
-
return { toasts, toast,
|
|
2590
|
+
return { toasts, toast, handleErrorAction };
|
|
2613
2591
|
}
|
|
2614
2592
|
var MAX_VISIBLE, AUTO_DISMISS_MS, nextId;
|
|
2615
2593
|
var init_use_toast = __esm({
|
|
@@ -3002,7 +2980,14 @@ var init_detail_panel = __esm({
|
|
|
3002
2980
|
// src/board/components/hint-bar.tsx
|
|
3003
2981
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
3004
2982
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3005
|
-
function HintBar({
|
|
2983
|
+
function HintBar({
|
|
2984
|
+
uiMode,
|
|
2985
|
+
multiSelectCount,
|
|
2986
|
+
searchQuery,
|
|
2987
|
+
mineOnly,
|
|
2988
|
+
hasUndoable,
|
|
2989
|
+
onHeader
|
|
2990
|
+
}) {
|
|
3006
2991
|
if (uiMode === "multiSelect") {
|
|
3007
2992
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3008
2993
|
/* @__PURE__ */ jsxs3(Text3, { color: "cyan", bold: true, children: [
|
|
@@ -3033,6 +3018,17 @@ function HintBar({ uiMode, multiSelectCount, searchQuery, mineOnly, hasUndoable
|
|
|
3033
3018
|
if (uiMode.startsWith("overlay:")) {
|
|
3034
3019
|
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter:select Esc:cancel" }) });
|
|
3035
3020
|
}
|
|
3021
|
+
if (onHeader) {
|
|
3022
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3023
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter/Space:expand-collapse Tab:next-section C:collapse-all ?:more q:quit" }),
|
|
3024
|
+
mineOnly ? /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: " filter:@me" }) : null,
|
|
3025
|
+
searchQuery ? /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
|
|
3026
|
+
' filter:"',
|
|
3027
|
+
searchQuery,
|
|
3028
|
+
'"'
|
|
3029
|
+
] }) : null
|
|
3030
|
+
] });
|
|
3031
|
+
}
|
|
3036
3032
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
3037
3033
|
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
3038
3034
|
"j/k:nav Enter:open m:status c:comment F:find t:@me e:edit L:log",
|
|
@@ -3495,11 +3491,10 @@ var init_create_issue_form = __esm({
|
|
|
3495
3491
|
});
|
|
3496
3492
|
|
|
3497
3493
|
// src/board/components/edit-issue-overlay.tsx
|
|
3498
|
-
import {
|
|
3494
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3499
3495
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3500
3496
|
import { tmpdir as tmpdir2 } from "os";
|
|
3501
3497
|
import { join as join5 } from "path";
|
|
3502
|
-
import { promisify as promisify3 } from "util";
|
|
3503
3498
|
import { Box as Box9, Text as Text9, useStdin as useStdin2 } from "ink";
|
|
3504
3499
|
import { useEffect as useEffect6, useRef as useRef9, useState as useState10 } from "react";
|
|
3505
3500
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
@@ -3651,11 +3646,7 @@ function EditIssueOverlay({
|
|
|
3651
3646
|
const changedFields = [];
|
|
3652
3647
|
if (parsed.title !== issue.title) {
|
|
3653
3648
|
try {
|
|
3654
|
-
await
|
|
3655
|
-
"gh",
|
|
3656
|
-
["issue", "edit", String(issue.number), "--repo", repoName, "--title", parsed.title],
|
|
3657
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3658
|
-
);
|
|
3649
|
+
await editIssueTitleAsync(repoName, issue.number, parsed.title);
|
|
3659
3650
|
changedFields.push("title");
|
|
3660
3651
|
} catch {
|
|
3661
3652
|
onToastError(`Failed to update title on #${issue.number}`);
|
|
@@ -3663,11 +3654,7 @@ function EditIssueOverlay({
|
|
|
3663
3654
|
}
|
|
3664
3655
|
if (parsed.body !== (issue.body ?? "").trim()) {
|
|
3665
3656
|
try {
|
|
3666
|
-
await
|
|
3667
|
-
"gh",
|
|
3668
|
-
["issue", "edit", String(issue.number), "--repo", repoName, "--body", parsed.body],
|
|
3669
|
-
{ encoding: "utf-8", timeout: 3e4 }
|
|
3670
|
-
);
|
|
3657
|
+
await editIssueBodyAsync(repoName, issue.number, parsed.body);
|
|
3671
3658
|
changedFields.push("body");
|
|
3672
3659
|
} catch {
|
|
3673
3660
|
onToastError(`Failed to update body on #${issue.number}`);
|
|
@@ -3691,11 +3678,8 @@ function EditIssueOverlay({
|
|
|
3691
3678
|
const addLabels = parsed.labels.filter((l) => !origLabels.includes(l));
|
|
3692
3679
|
const removeLabels = origLabels.filter((l) => !parsed.labels.includes(l));
|
|
3693
3680
|
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
3681
|
try {
|
|
3698
|
-
await
|
|
3682
|
+
await updateLabelsAsync(repoName, issue.number, addLabels, removeLabels);
|
|
3699
3683
|
changedFields.push("labels");
|
|
3700
3684
|
} catch {
|
|
3701
3685
|
onToastError(`Failed to update labels on #${issue.number}`);
|
|
@@ -3704,34 +3688,10 @@ function EditIssueOverlay({
|
|
|
3704
3688
|
if (parsed.assignee !== origAssignee) {
|
|
3705
3689
|
try {
|
|
3706
3690
|
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
|
-
);
|
|
3691
|
+
await assignIssueToAsync(repoName, issue.number, parsed.assignee);
|
|
3720
3692
|
}
|
|
3721
3693
|
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
|
-
);
|
|
3694
|
+
await unassignIssueAsync(repoName, issue.number, origAssignee);
|
|
3735
3695
|
}
|
|
3736
3696
|
changedFields.push("assignee");
|
|
3737
3697
|
} catch {
|
|
@@ -3785,14 +3745,12 @@ function EditIssueOverlay({
|
|
|
3785
3745
|
"\u2026"
|
|
3786
3746
|
] }) });
|
|
3787
3747
|
}
|
|
3788
|
-
var execFileAsync3;
|
|
3789
3748
|
var init_edit_issue_overlay = __esm({
|
|
3790
3749
|
"src/board/components/edit-issue-overlay.tsx"() {
|
|
3791
3750
|
"use strict";
|
|
3792
3751
|
init_github();
|
|
3793
3752
|
init_use_action_log();
|
|
3794
3753
|
init_ink_instance();
|
|
3795
|
-
execFileAsync3 = promisify3(execFile3);
|
|
3796
3754
|
}
|
|
3797
3755
|
});
|
|
3798
3756
|
|
|
@@ -4439,7 +4397,7 @@ import { Box as Box15, Text as Text15, useInput as useInput11 } from "ink";
|
|
|
4439
4397
|
import { useRef as useRef12, useState as useState14 } from "react";
|
|
4440
4398
|
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4441
4399
|
function isTerminal(name) {
|
|
4442
|
-
return
|
|
4400
|
+
return TERMINAL_STATUS_RE.test(name);
|
|
4443
4401
|
}
|
|
4444
4402
|
function handlePickerInput(input2, key, state) {
|
|
4445
4403
|
if (key.escape) {
|
|
@@ -4548,11 +4506,10 @@ function StatusPicker({
|
|
|
4548
4506
|
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "j/k:navigate Enter:select Esc:cancel" })
|
|
4549
4507
|
] });
|
|
4550
4508
|
}
|
|
4551
|
-
var TERMINAL_STATUS_RE2;
|
|
4552
4509
|
var init_status_picker = __esm({
|
|
4553
4510
|
"src/board/components/status-picker.tsx"() {
|
|
4554
4511
|
"use strict";
|
|
4555
|
-
|
|
4512
|
+
init_constants();
|
|
4556
4513
|
}
|
|
4557
4514
|
});
|
|
4558
4515
|
|
|
@@ -4737,7 +4694,7 @@ function formatTargetDate(dateStr) {
|
|
|
4737
4694
|
color: "gray"
|
|
4738
4695
|
};
|
|
4739
4696
|
}
|
|
4740
|
-
function
|
|
4697
|
+
function timeAgo2(dateStr) {
|
|
4741
4698
|
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
|
|
4742
4699
|
if (seconds < 60) return "now";
|
|
4743
4700
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -4781,7 +4738,7 @@ function IssueRow({ issue, selfLogin, isSelected }) {
|
|
|
4781
4738
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4782
4739
|
/* @__PURE__ */ jsx17(Text16, { color: assigneeColor, children: assigneeText.padEnd(14) }),
|
|
4783
4740
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4784
|
-
/* @__PURE__ */ jsx17(Text16, { color: "gray", children:
|
|
4741
|
+
/* @__PURE__ */ jsx17(Text16, { color: "gray", children: timeAgo2(issue.updatedAt).padStart(4) }),
|
|
4785
4742
|
target.text ? /* @__PURE__ */ jsxs17(Fragment3, { children: [
|
|
4786
4743
|
/* @__PURE__ */ jsx17(Text16, { children: " " }),
|
|
4787
4744
|
/* @__PURE__ */ jsx17(Text16, { color: target.color, children: target.text })
|
|
@@ -4916,7 +4873,7 @@ function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
|
4916
4873
|
] });
|
|
4917
4874
|
}
|
|
4918
4875
|
case "activity": {
|
|
4919
|
-
const ago =
|
|
4876
|
+
const ago = timeAgo(row.event.timestamp);
|
|
4920
4877
|
return /* @__PURE__ */ jsxs19(Text18, { dimColor: true, children: [
|
|
4921
4878
|
" ",
|
|
4922
4879
|
ago,
|
|
@@ -4944,16 +4901,10 @@ function RowRenderer({ row, selectedId, selfLogin, isMultiSelected }) {
|
|
|
4944
4901
|
return /* @__PURE__ */ jsx19(Text18, { children: "" });
|
|
4945
4902
|
}
|
|
4946
4903
|
}
|
|
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
4904
|
var init_row_renderer = __esm({
|
|
4955
4905
|
"src/board/components/row-renderer.tsx"() {
|
|
4956
4906
|
"use strict";
|
|
4907
|
+
init_constants();
|
|
4957
4908
|
init_issue_row();
|
|
4958
4909
|
init_task_row();
|
|
4959
4910
|
}
|
|
@@ -5002,9 +4953,6 @@ import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
|
5002
4953
|
import { Box as Box20, Text as Text20, useApp, useStdout } from "ink";
|
|
5003
4954
|
import { useCallback as useCallback11, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef13, useState as useState15 } from "react";
|
|
5004
4955
|
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
4956
|
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
5009
4957
|
if (configuredGroups && configuredGroups.length > 0) {
|
|
5010
4958
|
return configuredGroups.map((entry) => {
|
|
@@ -5226,28 +5174,13 @@ function buildFlatRows(repos, tasks, activity, isCollapsed) {
|
|
|
5226
5174
|
}
|
|
5227
5175
|
return rows;
|
|
5228
5176
|
}
|
|
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
5177
|
function openInBrowser(url) {
|
|
5178
|
+
if (!(url.startsWith("https://") || url.startsWith("http://"))) return;
|
|
5237
5179
|
try {
|
|
5238
5180
|
execFileSync3("open", [url], { stdio: "ignore" });
|
|
5239
5181
|
} catch {
|
|
5240
5182
|
}
|
|
5241
5183
|
}
|
|
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
5184
|
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
5252
5185
|
if (!selectedId?.startsWith("gh:")) return null;
|
|
5253
5186
|
for (const rd of repos) {
|
|
@@ -5258,8 +5191,17 @@ function findSelectedIssueWithRepo(repos, selectedId) {
|
|
|
5258
5191
|
}
|
|
5259
5192
|
return null;
|
|
5260
5193
|
}
|
|
5261
|
-
function
|
|
5262
|
-
|
|
5194
|
+
function RefreshAge({ lastRefresh }) {
|
|
5195
|
+
const [, setTick] = useState15(0);
|
|
5196
|
+
useEffect9(() => {
|
|
5197
|
+
const id = setInterval(() => setTick((t) => t + 1), 1e4);
|
|
5198
|
+
return () => clearInterval(id);
|
|
5199
|
+
}, []);
|
|
5200
|
+
if (!lastRefresh) return null;
|
|
5201
|
+
return /* @__PURE__ */ jsxs21(Text20, { color: refreshAgeColor(lastRefresh), children: [
|
|
5202
|
+
"Updated ",
|
|
5203
|
+
timeAgo(lastRefresh)
|
|
5204
|
+
] });
|
|
5263
5205
|
}
|
|
5264
5206
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
5265
5207
|
const { exit } = useApp();
|
|
@@ -5298,11 +5240,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5298
5240
|
const last = logEntries[logEntries.length - 1];
|
|
5299
5241
|
if (last?.status === "error") setLogVisible(true);
|
|
5300
5242
|
}, [logEntries]);
|
|
5301
|
-
const [, setTick] = useState15(0);
|
|
5302
5243
|
useEffect9(() => {
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5244
|
+
if (data?.ticktickError) {
|
|
5245
|
+
toast.error(`TickTick sync failed: ${data.ticktickError}`);
|
|
5246
|
+
}
|
|
5247
|
+
}, [data?.ticktickError, toast.error]);
|
|
5306
5248
|
const repos = useMemo3(() => {
|
|
5307
5249
|
let filtered = allRepos;
|
|
5308
5250
|
if (mineOnly) {
|
|
@@ -5405,7 +5347,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5405
5347
|
const [focusLabel, setFocusLabel] = useState15(null);
|
|
5406
5348
|
const handleEnterFocus = useCallback11(() => {
|
|
5407
5349
|
const id = nav.selectedId;
|
|
5408
|
-
if (!id ||
|
|
5350
|
+
if (!id || isHeaderId(id)) return;
|
|
5409
5351
|
let label = "";
|
|
5410
5352
|
if (id.startsWith("gh:")) {
|
|
5411
5353
|
const found = findSelectedIssueWithRepo(repos, id);
|
|
@@ -5480,7 +5422,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5480
5422
|
[repos, tasks, allActivity, nav.isCollapsed]
|
|
5481
5423
|
);
|
|
5482
5424
|
const scrollRef = useRef13(0);
|
|
5483
|
-
const selectedRowIdx =
|
|
5425
|
+
const selectedRowIdx = useMemo3(
|
|
5426
|
+
() => flatRows.findIndex((r) => r.navId === nav.selectedId),
|
|
5427
|
+
[flatRows, nav.selectedId]
|
|
5428
|
+
);
|
|
5484
5429
|
if (selectedRowIdx >= 0) {
|
|
5485
5430
|
if (selectedRowIdx < scrollRef.current) {
|
|
5486
5431
|
scrollRef.current = selectedRowIdx;
|
|
@@ -5497,7 +5442,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5497
5442
|
const belowCount = flatRows.length - scrollRef.current - viewportHeight;
|
|
5498
5443
|
const selectedItem = useMemo3(() => {
|
|
5499
5444
|
const id = nav.selectedId;
|
|
5500
|
-
if (!id ||
|
|
5445
|
+
if (!id || isHeaderId(id)) return { issue: null, task: null, repoName: null };
|
|
5501
5446
|
if (id.startsWith("gh:")) {
|
|
5502
5447
|
for (const rd of repos) {
|
|
5503
5448
|
for (const issue of rd.issues) {
|
|
@@ -5528,8 +5473,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5528
5473
|
return rd?.statusOptions ?? [];
|
|
5529
5474
|
}, [selectedItem.repoName, repos, multiSelect.count, multiSelect.constrainedRepo]);
|
|
5530
5475
|
const handleOpen = useCallback11(() => {
|
|
5531
|
-
const
|
|
5532
|
-
if (
|
|
5476
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
5477
|
+
if (found) openInBrowser(found.issue.url);
|
|
5533
5478
|
}, [repos, nav.selectedId]);
|
|
5534
5479
|
const handleSlack = useCallback11(() => {
|
|
5535
5480
|
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
@@ -5697,13 +5642,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5697
5642
|
isRefreshing ? /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5698
5643
|
/* @__PURE__ */ jsx21(Spinner4, { label: "" }),
|
|
5699
5644
|
/* @__PURE__ */ jsx21(Text20, { color: "cyan", children: " Refreshing..." })
|
|
5700
|
-
] }) :
|
|
5701
|
-
/* @__PURE__ */
|
|
5702
|
-
"Updated ",
|
|
5703
|
-
timeAgo3(lastRefresh)
|
|
5704
|
-
] }),
|
|
5645
|
+
] }) : /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5646
|
+
/* @__PURE__ */ jsx21(RefreshAge, { lastRefresh }),
|
|
5705
5647
|
consecutiveFailures > 0 ? /* @__PURE__ */ jsx21(Text20, { color: "red", children: " (!)" }) : null
|
|
5706
|
-
] })
|
|
5648
|
+
] }),
|
|
5707
5649
|
autoRefreshPaused ? /* @__PURE__ */ jsx21(Text20, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
|
|
5708
5650
|
] }),
|
|
5709
5651
|
error ? /* @__PURE__ */ jsxs21(Text20, { color: "red", children: [
|
|
@@ -5800,17 +5742,19 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5800
5742
|
multiSelectCount: multiSelect.count,
|
|
5801
5743
|
searchQuery,
|
|
5802
5744
|
mineOnly,
|
|
5803
|
-
hasUndoable
|
|
5745
|
+
hasUndoable,
|
|
5746
|
+
onHeader: isHeaderId(nav.selectedId)
|
|
5804
5747
|
}
|
|
5805
5748
|
)
|
|
5806
5749
|
] });
|
|
5807
5750
|
}
|
|
5808
|
-
var
|
|
5751
|
+
var PRIORITY_RANK, CHROME_ROWS;
|
|
5809
5752
|
var init_dashboard = __esm({
|
|
5810
5753
|
"src/board/components/dashboard.tsx"() {
|
|
5811
5754
|
"use strict";
|
|
5812
5755
|
init_clipboard();
|
|
5813
5756
|
init_github();
|
|
5757
|
+
init_constants();
|
|
5814
5758
|
init_use_action_log();
|
|
5815
5759
|
init_use_actions();
|
|
5816
5760
|
init_use_data();
|
|
@@ -5825,7 +5769,6 @@ var init_dashboard = __esm({
|
|
|
5825
5769
|
init_overlay_renderer();
|
|
5826
5770
|
init_row_renderer();
|
|
5827
5771
|
init_toast_container();
|
|
5828
|
-
TERMINAL_STATUS_RE3 = /^(done|shipped|won't|wont|closed|complete|completed)$/i;
|
|
5829
5772
|
PRIORITY_RANK = {
|
|
5830
5773
|
"priority:critical": 0,
|
|
5831
5774
|
"priority:high": 1,
|
|
@@ -5872,9 +5815,6 @@ function extractSlackUrl(body) {
|
|
|
5872
5815
|
const match = body.match(SLACK_URL_RE2);
|
|
5873
5816
|
return match?.[0];
|
|
5874
5817
|
}
|
|
5875
|
-
function formatError2(err) {
|
|
5876
|
-
return err instanceof Error ? err.message : String(err);
|
|
5877
|
-
}
|
|
5878
5818
|
function fetchRecentActivity(repoName, shortName) {
|
|
5879
5819
|
try {
|
|
5880
5820
|
const output = execFileSync4(
|
|
@@ -5954,28 +5894,34 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
5954
5894
|
fetchOpts.assignee = config2.board.assignee;
|
|
5955
5895
|
}
|
|
5956
5896
|
const issues = fetchRepoIssues(repo.name, fetchOpts);
|
|
5897
|
+
let enrichedIssues = issues;
|
|
5957
5898
|
let statusOptions = [];
|
|
5958
5899
|
try {
|
|
5959
5900
|
const enrichMap = fetchProjectEnrichment(repo.name, repo.projectNumber);
|
|
5960
|
-
|
|
5901
|
+
enrichedIssues = issues.map((issue) => {
|
|
5961
5902
|
const e = enrichMap.get(issue.number);
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5903
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
5904
|
+
return {
|
|
5905
|
+
...issue,
|
|
5906
|
+
...e?.targetDate !== void 0 ? { targetDate: e.targetDate } : {},
|
|
5907
|
+
...e?.projectStatus !== void 0 ? { projectStatus: e.projectStatus } : {},
|
|
5908
|
+
...slackUrl ? { slackThreadUrl: slackUrl } : {}
|
|
5909
|
+
};
|
|
5910
|
+
});
|
|
5965
5911
|
statusOptions = fetchProjectStatusOptions(
|
|
5966
5912
|
repo.name,
|
|
5967
5913
|
repo.projectNumber,
|
|
5968
5914
|
repo.statusFieldId
|
|
5969
5915
|
);
|
|
5970
5916
|
} catch {
|
|
5917
|
+
enrichedIssues = issues.map((issue) => {
|
|
5918
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
5919
|
+
return slackUrl ? { ...issue, slackThreadUrl: slackUrl } : issue;
|
|
5920
|
+
});
|
|
5971
5921
|
}
|
|
5972
|
-
|
|
5973
|
-
const slackUrl = extractSlackUrl(issue.body);
|
|
5974
|
-
if (slackUrl) issue.slackThreadUrl = slackUrl;
|
|
5975
|
-
}
|
|
5976
|
-
return { repo, issues, statusOptions, error: null };
|
|
5922
|
+
return { repo, issues: enrichedIssues, statusOptions, error: null };
|
|
5977
5923
|
} catch (err) {
|
|
5978
|
-
return { repo, issues: [], statusOptions: [], error:
|
|
5924
|
+
return { repo, issues: [], statusOptions: [], error: formatError(err) };
|
|
5979
5925
|
}
|
|
5980
5926
|
});
|
|
5981
5927
|
let ticktick = [];
|
|
@@ -5990,7 +5936,7 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
5990
5936
|
ticktick = tasks.filter((t) => t.status !== 2 /* Completed */);
|
|
5991
5937
|
}
|
|
5992
5938
|
} catch (err) {
|
|
5993
|
-
ticktickError =
|
|
5939
|
+
ticktickError = formatError(err);
|
|
5994
5940
|
}
|
|
5995
5941
|
}
|
|
5996
5942
|
const activity = [];
|
|
@@ -6015,6 +5961,7 @@ var init_fetch = __esm({
|
|
|
6015
5961
|
init_config();
|
|
6016
5962
|
init_github();
|
|
6017
5963
|
init_types();
|
|
5964
|
+
init_constants();
|
|
6018
5965
|
SLACK_URL_RE2 = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/i;
|
|
6019
5966
|
}
|
|
6020
5967
|
});
|
|
@@ -6193,7 +6140,9 @@ function renderBoardJson(data, selfLogin) {
|
|
|
6193
6140
|
labels: i.labels.map((l) => l.name),
|
|
6194
6141
|
updatedAt: i.updatedAt,
|
|
6195
6142
|
isMine: (i.assignees ?? []).some((a) => a.login === selfLogin),
|
|
6196
|
-
slackThreadUrl: i.slackThreadUrl ?? null
|
|
6143
|
+
slackThreadUrl: i.slackThreadUrl ?? null,
|
|
6144
|
+
projectStatus: i.projectStatus ?? null,
|
|
6145
|
+
targetDate: i.targetDate ?? null
|
|
6197
6146
|
}))
|
|
6198
6147
|
})),
|
|
6199
6148
|
ticktick: {
|
|
@@ -6206,6 +6155,7 @@ function renderBoardJson(data, selfLogin) {
|
|
|
6206
6155
|
tags: t.tags
|
|
6207
6156
|
}))
|
|
6208
6157
|
},
|
|
6158
|
+
activity: data.activity,
|
|
6209
6159
|
fetchedAt: data.fetchedAt.toISOString()
|
|
6210
6160
|
}
|
|
6211
6161
|
};
|
|
@@ -6224,8 +6174,8 @@ var init_format_static = __esm({
|
|
|
6224
6174
|
init_ai();
|
|
6225
6175
|
init_api();
|
|
6226
6176
|
init_config();
|
|
6227
|
-
import { execFile as
|
|
6228
|
-
import { promisify as
|
|
6177
|
+
import { execFile as execFile2, execFileSync as execFileSync5 } from "child_process";
|
|
6178
|
+
import { promisify as promisify2 } from "util";
|
|
6229
6179
|
import { Command } from "commander";
|
|
6230
6180
|
|
|
6231
6181
|
// src/init.ts
|
|
@@ -6710,6 +6660,7 @@ function printSyncStatus(state, repos) {
|
|
|
6710
6660
|
|
|
6711
6661
|
// src/sync.ts
|
|
6712
6662
|
init_api();
|
|
6663
|
+
init_constants();
|
|
6713
6664
|
init_config();
|
|
6714
6665
|
init_github();
|
|
6715
6666
|
init_sync_state();
|
|
@@ -6717,9 +6668,6 @@ init_types();
|
|
|
6717
6668
|
function emptySyncResult() {
|
|
6718
6669
|
return { created: [], updated: [], completed: [], ghUpdated: [], errors: [] };
|
|
6719
6670
|
}
|
|
6720
|
-
function formatError(err) {
|
|
6721
|
-
return err instanceof Error ? err.message : String(err);
|
|
6722
|
-
}
|
|
6723
6671
|
function repoShortName(repo) {
|
|
6724
6672
|
return repo.split("/")[1] ?? repo;
|
|
6725
6673
|
}
|
|
@@ -6778,23 +6726,29 @@ async function syncGitHubToTickTick(config2, state, api, result, dryRun) {
|
|
|
6778
6726
|
failedRepos.add(repoConfig.name);
|
|
6779
6727
|
continue;
|
|
6780
6728
|
}
|
|
6729
|
+
let enrichMap;
|
|
6730
|
+
try {
|
|
6731
|
+
enrichMap = fetchProjectEnrichment(repoConfig.name, repoConfig.projectNumber);
|
|
6732
|
+
} catch {
|
|
6733
|
+
enrichMap = /* @__PURE__ */ new Map();
|
|
6734
|
+
}
|
|
6781
6735
|
for (const issue of issues) {
|
|
6782
6736
|
const key = `${repoConfig.name}#${issue.number}`;
|
|
6783
6737
|
openIssueKeys.add(key);
|
|
6784
|
-
await syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key);
|
|
6738
|
+
await syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key, enrichMap);
|
|
6785
6739
|
}
|
|
6786
6740
|
}
|
|
6787
6741
|
return { openIssueKeys, failedRepos };
|
|
6788
6742
|
}
|
|
6789
|
-
async function syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key) {
|
|
6743
|
+
async function syncSingleIssue(state, api, result, dryRun, repoConfig, issue, key, enrichMap) {
|
|
6790
6744
|
try {
|
|
6791
6745
|
const existing = findMapping(state, repoConfig.name, issue.number);
|
|
6792
6746
|
if (existing && existing.githubUpdatedAt === issue.updatedAt) return;
|
|
6793
|
-
const
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6747
|
+
const enrichment = enrichMap.get(issue.number);
|
|
6748
|
+
const projectFields = {
|
|
6749
|
+
...enrichment?.targetDate !== void 0 && { targetDate: enrichment.targetDate },
|
|
6750
|
+
...enrichment?.projectStatus !== void 0 && { status: enrichment.projectStatus }
|
|
6751
|
+
};
|
|
6798
6752
|
if (!existing) {
|
|
6799
6753
|
await createTickTickTask(
|
|
6800
6754
|
state,
|
|
@@ -6953,7 +6907,7 @@ if (major < 22) {
|
|
|
6953
6907
|
);
|
|
6954
6908
|
process.exit(1);
|
|
6955
6909
|
}
|
|
6956
|
-
var
|
|
6910
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
6957
6911
|
async function resolveRef(ref, config2) {
|
|
6958
6912
|
const { parseIssueRef: parseIssueRef2 } = await Promise.resolve().then(() => (init_pick(), pick_exports));
|
|
6959
6913
|
try {
|
|
@@ -6981,8 +6935,7 @@ var PRIORITY_MAP = {
|
|
|
6981
6935
|
function parsePriority(value) {
|
|
6982
6936
|
const p = PRIORITY_MAP[value.toLowerCase()];
|
|
6983
6937
|
if (p === void 0) {
|
|
6984
|
-
|
|
6985
|
-
process.exit(1);
|
|
6938
|
+
errorOut(`Invalid priority: "${value}". Valid values: none, low, medium, high`);
|
|
6986
6939
|
}
|
|
6987
6940
|
return p;
|
|
6988
6941
|
}
|
|
@@ -6998,7 +6951,7 @@ function resolveProjectId(projectId) {
|
|
|
6998
6951
|
process.exit(1);
|
|
6999
6952
|
}
|
|
7000
6953
|
var program = new Command();
|
|
7001
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
6954
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.8.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
7002
6955
|
const opts = thisCommand.opts();
|
|
7003
6956
|
if (opts.json) setFormat("json");
|
|
7004
6957
|
if (opts.human) setFormat("human");
|
|
@@ -7020,9 +6973,7 @@ task.command("add <title>").description("Create a new task").option("-p, --prior
|
|
|
7020
6973
|
if (opts.tags) input2.tags = opts.tags.split(",").map((t) => t.trim());
|
|
7021
6974
|
if (opts.allDay) input2.isAllDay = true;
|
|
7022
6975
|
const created = await api.createTask(input2);
|
|
7023
|
-
printSuccess(`Created: ${created.title}`, {
|
|
7024
|
-
task: created
|
|
7025
|
-
});
|
|
6976
|
+
printSuccess(`Created: ${created.title}`, { task: created });
|
|
7026
6977
|
});
|
|
7027
6978
|
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
6979
|
const api = createClient();
|
|
@@ -7063,9 +7014,7 @@ task.command("update <taskId>").description("Update a task").option("--title <ti
|
|
|
7063
7014
|
if (opts.content) input2.content = opts.content;
|
|
7064
7015
|
if (opts.tags) input2.tags = opts.tags.split(",").map((t) => t.trim());
|
|
7065
7016
|
const updated = await api.updateTask(input2);
|
|
7066
|
-
printSuccess(`Updated: ${updated.title}`, {
|
|
7067
|
-
task: updated
|
|
7068
|
-
});
|
|
7017
|
+
printSuccess(`Updated: ${updated.title}`, { task: updated });
|
|
7069
7018
|
});
|
|
7070
7019
|
task.command("delete <taskId>").description("Delete a task").option("--project <id>", "Project ID (overrides default)").action(async (taskId, opts) => {
|
|
7071
7020
|
const api = createClient();
|
|
@@ -7442,7 +7391,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
|
|
|
7442
7391
|
const repoArg = repo;
|
|
7443
7392
|
try {
|
|
7444
7393
|
if (useJson()) {
|
|
7445
|
-
const output = await
|
|
7394
|
+
const output = await execFileAsync2("gh", args, { encoding: "utf-8", timeout: 6e4 });
|
|
7446
7395
|
const url = output.stdout.trim();
|
|
7447
7396
|
const issueNumber = Number.parseInt(url.split("/").pop() ?? "0", 10);
|
|
7448
7397
|
jsonOut({ ok: true, data: { url, issueNumber, repo: repoArg } });
|
|
@@ -7494,7 +7443,20 @@ issueCommand.command("move <issueRef> <status>").description("Change project sta
|
|
|
7494
7443
|
errorOut(`Invalid status "${status}". Valid: ${valid}`, { status, validStatuses: valid });
|
|
7495
7444
|
}
|
|
7496
7445
|
if (opts.dryRun) {
|
|
7497
|
-
|
|
7446
|
+
if (useJson()) {
|
|
7447
|
+
jsonOut({
|
|
7448
|
+
ok: true,
|
|
7449
|
+
dryRun: true,
|
|
7450
|
+
would: {
|
|
7451
|
+
action: "move",
|
|
7452
|
+
issue: ref.issueNumber,
|
|
7453
|
+
repo: rc.shortName,
|
|
7454
|
+
status: target.name
|
|
7455
|
+
}
|
|
7456
|
+
});
|
|
7457
|
+
} else {
|
|
7458
|
+
console.log(`[dry-run] Would move ${rc.shortName}#${ref.issueNumber} \u2192 "${target.name}"`);
|
|
7459
|
+
}
|
|
7498
7460
|
return;
|
|
7499
7461
|
}
|
|
7500
7462
|
await updateProjectItemStatusAsync2(rc.name, ref.issueNumber, {
|
|
@@ -7517,7 +7479,15 @@ issueCommand.command("assign <issueRef>").description("Assign issue to self or a
|
|
|
7517
7479
|
process.exit(1);
|
|
7518
7480
|
}
|
|
7519
7481
|
if (opts.dryRun) {
|
|
7520
|
-
|
|
7482
|
+
if (useJson()) {
|
|
7483
|
+
jsonOut({
|
|
7484
|
+
ok: true,
|
|
7485
|
+
dryRun: true,
|
|
7486
|
+
would: { action: "assign", issue: ref.issueNumber, repo: ref.repo.shortName, user }
|
|
7487
|
+
});
|
|
7488
|
+
} else {
|
|
7489
|
+
console.log(`[dry-run] Would assign ${ref.repo.shortName}#${ref.issueNumber} to @${user}`);
|
|
7490
|
+
}
|
|
7521
7491
|
return;
|
|
7522
7492
|
}
|
|
7523
7493
|
const { assignIssueToAsync: assignIssueToAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7537,7 +7507,17 @@ issueCommand.command("unassign <issueRef>").description("Remove assignee from is
|
|
|
7537
7507
|
process.exit(1);
|
|
7538
7508
|
}
|
|
7539
7509
|
if (opts.dryRun) {
|
|
7540
|
-
|
|
7510
|
+
if (useJson()) {
|
|
7511
|
+
jsonOut({
|
|
7512
|
+
ok: true,
|
|
7513
|
+
dryRun: true,
|
|
7514
|
+
would: { action: "unassign", issue: ref.issueNumber, repo: ref.repo.shortName, user }
|
|
7515
|
+
});
|
|
7516
|
+
} else {
|
|
7517
|
+
console.log(
|
|
7518
|
+
`[dry-run] Would remove @${user} from ${ref.repo.shortName}#${ref.issueNumber}`
|
|
7519
|
+
);
|
|
7520
|
+
}
|
|
7541
7521
|
return;
|
|
7542
7522
|
}
|
|
7543
7523
|
const { unassignIssueAsync: unassignIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7552,7 +7532,17 @@ issueCommand.command("comment <issueRef> <text>").description("Post a comment on
|
|
|
7552
7532
|
const cfg = loadFullConfig();
|
|
7553
7533
|
const ref = await resolveRef(issueRef, cfg);
|
|
7554
7534
|
if (opts.dryRun) {
|
|
7555
|
-
|
|
7535
|
+
if (useJson()) {
|
|
7536
|
+
jsonOut({
|
|
7537
|
+
ok: true,
|
|
7538
|
+
dryRun: true,
|
|
7539
|
+
would: { action: "comment", issue: ref.issueNumber, repo: ref.repo.shortName, text }
|
|
7540
|
+
});
|
|
7541
|
+
} else {
|
|
7542
|
+
console.log(
|
|
7543
|
+
`[dry-run] Would comment on ${ref.repo.shortName}#${ref.issueNumber}: "${text}"`
|
|
7544
|
+
);
|
|
7545
|
+
}
|
|
7556
7546
|
return;
|
|
7557
7547
|
}
|
|
7558
7548
|
const { addCommentAsync: addCommentAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
@@ -7588,9 +7578,17 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
7588
7578
|
process.exit(1);
|
|
7589
7579
|
}
|
|
7590
7580
|
if (opts.dryRun) {
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7581
|
+
if (useJson()) {
|
|
7582
|
+
jsonOut({
|
|
7583
|
+
ok: true,
|
|
7584
|
+
dryRun: true,
|
|
7585
|
+
would: { action: "edit", issue: ref.issueNumber, repo: ref.repo.shortName, changes }
|
|
7586
|
+
});
|
|
7587
|
+
} else {
|
|
7588
|
+
console.log(
|
|
7589
|
+
`[dry-run] Would edit ${ref.repo.shortName}#${ref.issueNumber}: ${changes.join("; ")}`
|
|
7590
|
+
);
|
|
7591
|
+
}
|
|
7594
7592
|
return;
|
|
7595
7593
|
}
|
|
7596
7594
|
const ghArgs = ["issue", "edit", String(ref.issueNumber), "--repo", ref.repo.name];
|
|
@@ -7601,7 +7599,7 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
7601
7599
|
if (opts.assignee) ghArgs.push("--add-assignee", opts.assignee);
|
|
7602
7600
|
if (opts.removeAssignee) ghArgs.push("--remove-assignee", opts.removeAssignee);
|
|
7603
7601
|
if (useJson()) {
|
|
7604
|
-
await
|
|
7602
|
+
await execFileAsync2("gh", ghArgs, { encoding: "utf-8", timeout: 3e4 });
|
|
7605
7603
|
jsonOut({ ok: true, data: { issue: ref.issueNumber, changes } });
|
|
7606
7604
|
} else {
|
|
7607
7605
|
execFileSync5("gh", ghArgs, { stdio: "inherit" });
|
|
@@ -7613,9 +7611,22 @@ issueCommand.command("label <issueRef> <label>").description("Add or remove a la
|
|
|
7613
7611
|
const ref = await resolveRef(issueRef, cfg);
|
|
7614
7612
|
const verb = opts.remove ? "remove" : "add";
|
|
7615
7613
|
if (opts.dryRun) {
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7614
|
+
if (useJson()) {
|
|
7615
|
+
jsonOut({
|
|
7616
|
+
ok: true,
|
|
7617
|
+
dryRun: true,
|
|
7618
|
+
would: {
|
|
7619
|
+
action: `${verb}Label`,
|
|
7620
|
+
issue: ref.issueNumber,
|
|
7621
|
+
repo: ref.repo.shortName,
|
|
7622
|
+
label
|
|
7623
|
+
}
|
|
7624
|
+
});
|
|
7625
|
+
} else {
|
|
7626
|
+
console.log(
|
|
7627
|
+
`[dry-run] Would ${verb} label "${label}" on ${ref.repo.shortName}#${ref.issueNumber}`
|
|
7628
|
+
);
|
|
7629
|
+
}
|
|
7619
7630
|
return;
|
|
7620
7631
|
}
|
|
7621
7632
|
if (opts.remove) {
|
|
@@ -7651,6 +7662,140 @@ issueCommand.command("statuses").description("List available project statuses fo
|
|
|
7651
7662
|
console.log(`Available statuses for ${repo}: ${statuses.map((s) => s.name).join(", ")}`);
|
|
7652
7663
|
}
|
|
7653
7664
|
});
|
|
7665
|
+
async function moveSingleIssue(r, status, cfg) {
|
|
7666
|
+
try {
|
|
7667
|
+
const ref = await resolveRef(r, cfg);
|
|
7668
|
+
const rc = ref.repo;
|
|
7669
|
+
if (!(rc.statusFieldId && rc.projectNumber)) {
|
|
7670
|
+
throw new Error(`${rc.name} is not configured with a project board. Run: hog init`);
|
|
7671
|
+
}
|
|
7672
|
+
const { fetchProjectStatusOptions: fetchProjectStatusOptions2, updateProjectItemStatusAsync: updateProjectItemStatusAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7673
|
+
const options = fetchProjectStatusOptions2(rc.name, rc.projectNumber, rc.statusFieldId);
|
|
7674
|
+
const target = options.find((o) => o.name.toLowerCase() === status.toLowerCase());
|
|
7675
|
+
if (!target) {
|
|
7676
|
+
const valid = options.map((o) => o.name).join(", ");
|
|
7677
|
+
throw new Error(`Invalid status "${status}". Valid: ${valid}`);
|
|
7678
|
+
}
|
|
7679
|
+
await updateProjectItemStatusAsync2(rc.name, ref.issueNumber, {
|
|
7680
|
+
projectNumber: rc.projectNumber,
|
|
7681
|
+
statusFieldId: rc.statusFieldId,
|
|
7682
|
+
optionId: target.id
|
|
7683
|
+
});
|
|
7684
|
+
return { ref: r, success: true };
|
|
7685
|
+
} catch (err) {
|
|
7686
|
+
return { ref: r, success: false, error: err instanceof Error ? err.message : String(err) };
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
function outputBulkResults(results) {
|
|
7690
|
+
const allOk = results.every((r) => r.success);
|
|
7691
|
+
if (useJson()) {
|
|
7692
|
+
jsonOut({ ok: allOk, results });
|
|
7693
|
+
} else {
|
|
7694
|
+
for (const r of results) {
|
|
7695
|
+
if (!r.success) {
|
|
7696
|
+
console.error(
|
|
7697
|
+
`Failed ${r.ref}: ${r.error}`
|
|
7698
|
+
);
|
|
7699
|
+
}
|
|
7700
|
+
}
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
issueCommand.command("bulk-assign <refs...>").description(
|
|
7704
|
+
"Assign multiple issues to self or a specific user (e.g., hog issue bulk-assign myrepo/42 myrepo/43)"
|
|
7705
|
+
).option("--user <username>", "GitHub username to assign (default: configured assignee)").option("--dry-run", "Print what would change without mutating").action(async (refs, opts) => {
|
|
7706
|
+
const cfg = loadFullConfig();
|
|
7707
|
+
const user = opts.user ?? cfg.board.assignee;
|
|
7708
|
+
if (!user) {
|
|
7709
|
+
errorOut("no user specified. Use --user or configure board.assignee in hog init");
|
|
7710
|
+
}
|
|
7711
|
+
if (opts.dryRun) {
|
|
7712
|
+
if (useJson()) {
|
|
7713
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-assign", refs, user } });
|
|
7714
|
+
} else {
|
|
7715
|
+
for (const r of refs) {
|
|
7716
|
+
console.log(`[dry-run] Would assign ${r} to @${user}`);
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
return;
|
|
7720
|
+
}
|
|
7721
|
+
const { assignIssueToAsync: assignIssueToAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7722
|
+
const results = [];
|
|
7723
|
+
for (const r of refs) {
|
|
7724
|
+
try {
|
|
7725
|
+
const ref = await resolveRef(r, cfg);
|
|
7726
|
+
await assignIssueToAsync2(ref.repo.name, ref.issueNumber, user);
|
|
7727
|
+
results.push({ ref: r, success: true });
|
|
7728
|
+
if (!useJson()) console.log(`Assigned ${r} to @${user}`);
|
|
7729
|
+
} catch (err) {
|
|
7730
|
+
results.push({
|
|
7731
|
+
ref: r,
|
|
7732
|
+
success: false,
|
|
7733
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7734
|
+
});
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
outputBulkResults(results);
|
|
7738
|
+
});
|
|
7739
|
+
issueCommand.command("bulk-unassign <refs...>").description(
|
|
7740
|
+
"Remove assignee from multiple issues (e.g., hog issue bulk-unassign myrepo/42 myrepo/43)"
|
|
7741
|
+
).option("--user <username>", "GitHub username to remove (default: configured assignee)").option("--dry-run", "Print what would change without mutating").action(async (refs, opts) => {
|
|
7742
|
+
const cfg = loadFullConfig();
|
|
7743
|
+
const user = opts.user ?? cfg.board.assignee;
|
|
7744
|
+
if (!user) {
|
|
7745
|
+
errorOut("no user specified. Use --user or configure board.assignee in hog init");
|
|
7746
|
+
}
|
|
7747
|
+
if (opts.dryRun) {
|
|
7748
|
+
if (useJson()) {
|
|
7749
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-unassign", refs, user } });
|
|
7750
|
+
} else {
|
|
7751
|
+
for (const r of refs) {
|
|
7752
|
+
console.log(`[dry-run] Would remove @${user} from ${r}`);
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
return;
|
|
7756
|
+
}
|
|
7757
|
+
const { unassignIssueAsync: unassignIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7758
|
+
const results = [];
|
|
7759
|
+
for (const r of refs) {
|
|
7760
|
+
try {
|
|
7761
|
+
const ref = await resolveRef(r, cfg);
|
|
7762
|
+
await unassignIssueAsync2(ref.repo.name, ref.issueNumber, user);
|
|
7763
|
+
results.push({ ref: r, success: true });
|
|
7764
|
+
if (!useJson()) console.log(`Removed @${user} from ${r}`);
|
|
7765
|
+
} catch (err) {
|
|
7766
|
+
results.push({
|
|
7767
|
+
ref: r,
|
|
7768
|
+
success: false,
|
|
7769
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7770
|
+
});
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
outputBulkResults(results);
|
|
7774
|
+
});
|
|
7775
|
+
issueCommand.command("bulk-move <status> <refs...>").description(
|
|
7776
|
+
"Move multiple issues to a project status (e.g., hog issue bulk-move 'In Review' myrepo/42 myrepo/43)"
|
|
7777
|
+
).option("--dry-run", "Print what would change without mutating").action(async (status, refs, opts) => {
|
|
7778
|
+
const cfg = loadFullConfig();
|
|
7779
|
+
if (opts.dryRun) {
|
|
7780
|
+
if (useJson()) {
|
|
7781
|
+
jsonOut({ ok: true, dryRun: true, would: { action: "bulk-move", refs, status } });
|
|
7782
|
+
} else {
|
|
7783
|
+
for (const r of refs) {
|
|
7784
|
+
console.log(`[dry-run] Would move ${r} \u2192 "${status}"`);
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
return;
|
|
7788
|
+
}
|
|
7789
|
+
const results = await Promise.all(
|
|
7790
|
+
refs.map((r) => moveSingleIssue(r, status, cfg))
|
|
7791
|
+
);
|
|
7792
|
+
if (!useJson()) {
|
|
7793
|
+
for (const r of results) {
|
|
7794
|
+
if (r.success) console.log(`Moved ${r.ref} \u2192 ${status}`);
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7797
|
+
outputBulkResults(results);
|
|
7798
|
+
});
|
|
7654
7799
|
program.addCommand(issueCommand);
|
|
7655
7800
|
var logCommand = program.command("log").description("Action log commands");
|
|
7656
7801
|
logCommand.command("show").description("Show recent action log entries").option("--limit <n>", "number of entries to show", "50").action((opts) => {
|