trekoon 0.1.9 → 0.2.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/.agents/skills/trekoon/SKILL.md +176 -230
- package/README.md +299 -7
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +198 -0
- package/src/commands/dep.ts +197 -25
- package/src/commands/epic.ts +674 -28
- package/src/commands/error-utils.ts +111 -0
- package/src/commands/events.ts +23 -3
- package/src/commands/help.ts +66 -4
- package/src/commands/init.ts +11 -3
- package/src/commands/migrate.ts +11 -4
- package/src/commands/subtask.ts +408 -26
- package/src/commands/sync.ts +7 -1
- package/src/commands/task.ts +381 -26
- package/src/domain/mutation-service.ts +394 -1
- package/src/domain/tracker-domain.ts +674 -0
- package/src/domain/types.ts +107 -0
- package/src/sync/event-writes.ts +21 -1
- package/src/sync/service.ts +42 -0
package/src/commands/task.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
findUnknownOption,
|
|
2
4
|
hasFlag,
|
|
5
|
+
isValidCompactTempKey,
|
|
3
6
|
parseArgs,
|
|
7
|
+
parseCompactFields,
|
|
8
|
+
parseCsvEnumOption,
|
|
4
9
|
parseStrictNonNegativeInt,
|
|
5
10
|
parseStrictPositiveInt,
|
|
6
11
|
readEnumOption,
|
|
7
12
|
readMissingOptionValue,
|
|
8
13
|
readOption,
|
|
14
|
+
readOptions,
|
|
15
|
+
readUnexpectedPositionals,
|
|
16
|
+
resolvePreviewApplyMode,
|
|
17
|
+
suggestOptions,
|
|
9
18
|
} from "./arg-parser";
|
|
19
|
+
import { unexpectedFailureResult } from "./error-utils";
|
|
10
20
|
|
|
11
21
|
import { MutationService } from "../domain/mutation-service";
|
|
12
22
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
13
|
-
import {
|
|
23
|
+
import { type CompactBatchResultContract, type CompactTaskSpec, type SearchEntityMatch, type TaskRecord } from "../domain/types";
|
|
14
24
|
import { formatHumanTable } from "../io/human-table";
|
|
15
25
|
import { failResult, okResult } from "../io/output";
|
|
16
26
|
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
17
|
-
import { openTrekoonDatabase } from "../storage/database";
|
|
27
|
+
import { openTrekoonDatabase, type TrekoonDatabase } from "../storage/database";
|
|
18
28
|
|
|
19
29
|
function formatTask(task: TaskRecord): string {
|
|
20
30
|
return `${task.id} | epic=${task.epicId} | ${task.title} | ${task.status}`;
|
|
@@ -26,6 +36,9 @@ const DEFAULT_TASK_LIST_LIMIT = 10;
|
|
|
26
36
|
const DEFAULT_OPEN_TASK_STATUSES = ["in_progress", "in-progress", "todo"] as const;
|
|
27
37
|
const READY_REASON_READY = "all_dependencies_done";
|
|
28
38
|
const READY_REASON_BLOCKED = "blocked_by_dependencies";
|
|
39
|
+
const SEARCH_OPTIONS = ["fields", "preview"] as const;
|
|
40
|
+
const REPLACE_OPTIONS = ["search", "replace", "fields", "preview", "apply"] as const;
|
|
41
|
+
const CREATE_MANY_OPTIONS = ["epic", "e", "task"] as const;
|
|
29
42
|
|
|
30
43
|
interface DependencyBlocker {
|
|
31
44
|
readonly id: string;
|
|
@@ -81,6 +94,55 @@ function parseIdsOption(rawIds: string | undefined): string[] {
|
|
|
81
94
|
.filter((value) => value.length > 0);
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
function prefixedOptions(options: readonly string[]): string[] {
|
|
98
|
+
return options.map((option) => `--${option}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function unknownOption(command: string, option: string, allowedOptions: readonly string[]): CliResult {
|
|
102
|
+
const suggestions = suggestOptions(option, allowedOptions).map((suggestion) => `--${suggestion}`);
|
|
103
|
+
const suggestionMessage = suggestions.length > 0 ? ` Did you mean ${suggestions.join(" or ")}?` : "";
|
|
104
|
+
return failResult({
|
|
105
|
+
command,
|
|
106
|
+
human: `Unknown option --${option}.${suggestionMessage}`,
|
|
107
|
+
data: {
|
|
108
|
+
option: `--${option}`,
|
|
109
|
+
allowedOptions: prefixedOptions(allowedOptions),
|
|
110
|
+
suggestions,
|
|
111
|
+
},
|
|
112
|
+
error: {
|
|
113
|
+
code: "unknown_option",
|
|
114
|
+
message: `Unknown option --${option}`,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function invalidSearchInput(command: string, human: string, message: string, data: Record<string, unknown>): CliResult {
|
|
120
|
+
return failResult({
|
|
121
|
+
command,
|
|
122
|
+
human,
|
|
123
|
+
data,
|
|
124
|
+
error: {
|
|
125
|
+
code: "invalid_input",
|
|
126
|
+
message,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatSearchHuman(matches: readonly SearchEntityMatch[], emptyMessage: string): string {
|
|
132
|
+
if (matches.length === 0) {
|
|
133
|
+
return emptyMessage;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return matches
|
|
137
|
+
.map(
|
|
138
|
+
(match) =>
|
|
139
|
+
`${match.kind} ${match.id}: ${match.fields
|
|
140
|
+
.map((field) => `${field.field}(${field.count}) "${field.snippet}"`)
|
|
141
|
+
.join(", ")}`,
|
|
142
|
+
)
|
|
143
|
+
.join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
84
146
|
function parseStatusCsv(rawStatuses: string | undefined): string[] | undefined {
|
|
85
147
|
if (rawStatuses === undefined) {
|
|
86
148
|
return undefined;
|
|
@@ -327,29 +389,9 @@ function formatTaskShowTable(taskTree: {
|
|
|
327
389
|
}
|
|
328
390
|
|
|
329
391
|
function failFromError(error: unknown): CliResult {
|
|
330
|
-
|
|
331
|
-
return failResult({
|
|
332
|
-
command: "task",
|
|
333
|
-
human: error.message,
|
|
334
|
-
data: {
|
|
335
|
-
code: error.code,
|
|
336
|
-
...(error.details ?? {}),
|
|
337
|
-
},
|
|
338
|
-
error: {
|
|
339
|
-
code: error.code,
|
|
340
|
-
message: error.message,
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return failResult({
|
|
392
|
+
return unexpectedFailureResult(error, {
|
|
346
393
|
command: "task",
|
|
347
394
|
human: "Unexpected task command failure",
|
|
348
|
-
data: {},
|
|
349
|
-
error: {
|
|
350
|
-
code: "internal_error",
|
|
351
|
-
message: "Unexpected task command failure",
|
|
352
|
-
},
|
|
353
395
|
});
|
|
354
396
|
}
|
|
355
397
|
|
|
@@ -368,10 +410,145 @@ function failMissingOptionValue(command: string, option: string): CliResult {
|
|
|
368
410
|
});
|
|
369
411
|
}
|
|
370
412
|
|
|
413
|
+
function failBatchSpec(command: string, human: string, data: Record<string, unknown>): CliResult {
|
|
414
|
+
return failResult({
|
|
415
|
+
command,
|
|
416
|
+
human,
|
|
417
|
+
data,
|
|
418
|
+
error: {
|
|
419
|
+
code: "invalid_input",
|
|
420
|
+
message: human,
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function failUnexpectedPositionals(command: string, unexpected: readonly string[]): CliResult {
|
|
426
|
+
return failBatchSpec(command, `Unexpected positional arguments: ${unexpected.join(", ")}.`, {
|
|
427
|
+
unexpectedPositionals: unexpected,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function failEmptyCompactField(command: string, option: string, index: number, rawSpec: string, field: string): CliResult {
|
|
432
|
+
return failBatchSpec(command, `${option === "task" ? "Task" : "Spec"} spec ${index + 1} is missing a ${field}.`, {
|
|
433
|
+
option,
|
|
434
|
+
index,
|
|
435
|
+
rawSpec,
|
|
436
|
+
field,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function parseTaskCreateManySpecs(rawSpecs: readonly string[]): { specs: CompactTaskSpec[]; error?: CliResult } {
|
|
441
|
+
const specs: CompactTaskSpec[] = [];
|
|
442
|
+
const seenTempKeys = new Set<string>();
|
|
443
|
+
|
|
444
|
+
for (const [index, rawSpec] of rawSpecs.entries()) {
|
|
445
|
+
const parsed = parseCompactFields(rawSpec);
|
|
446
|
+
if (parsed.invalidEscape !== null) {
|
|
447
|
+
return {
|
|
448
|
+
specs: [],
|
|
449
|
+
error: failBatchSpec("task.create-many", `Invalid escape sequence ${parsed.invalidEscape} in --task spec ${index + 1}.`, {
|
|
450
|
+
option: "task",
|
|
451
|
+
index,
|
|
452
|
+
rawSpec,
|
|
453
|
+
invalidEscape: parsed.invalidEscape,
|
|
454
|
+
}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (parsed.hasDanglingEscape) {
|
|
459
|
+
return {
|
|
460
|
+
specs: [],
|
|
461
|
+
error: failBatchSpec("task.create-many", `Trailing escape in --task spec ${index + 1}.`, {
|
|
462
|
+
option: "task",
|
|
463
|
+
index,
|
|
464
|
+
rawSpec,
|
|
465
|
+
}),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (parsed.fields.length !== 4) {
|
|
470
|
+
return {
|
|
471
|
+
specs: [],
|
|
472
|
+
error: failBatchSpec("task.create-many", `Task specs must use <temp-key>|<title>|<description>|<status> in --task spec ${index + 1}.`, {
|
|
473
|
+
option: "task",
|
|
474
|
+
index,
|
|
475
|
+
rawSpec,
|
|
476
|
+
fields: parsed.fields,
|
|
477
|
+
}),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const tempKey = parsed.fields[0] ?? "";
|
|
482
|
+
const title = parsed.fields[1] ?? "";
|
|
483
|
+
const description = parsed.fields[2] ?? "";
|
|
484
|
+
const status = parsed.fields[3] ?? "";
|
|
485
|
+
if (!tempKey || !isValidCompactTempKey(tempKey)) {
|
|
486
|
+
return {
|
|
487
|
+
specs: [],
|
|
488
|
+
error: failBatchSpec("task.create-many", `Task spec ${index + 1} must start with a temp key like seed-1.`, {
|
|
489
|
+
option: "task",
|
|
490
|
+
index,
|
|
491
|
+
rawSpec,
|
|
492
|
+
tempKey,
|
|
493
|
+
}),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (seenTempKeys.has(tempKey)) {
|
|
498
|
+
return {
|
|
499
|
+
specs: [],
|
|
500
|
+
error: failBatchSpec("task.create-many", `Duplicate temp key '${tempKey}' in --task specs.`, {
|
|
501
|
+
option: "task",
|
|
502
|
+
index,
|
|
503
|
+
rawSpec,
|
|
504
|
+
tempKey,
|
|
505
|
+
}),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!title || title.trim().length === 0) {
|
|
510
|
+
return {
|
|
511
|
+
specs: [],
|
|
512
|
+
error: failBatchSpec("task.create-many", `Task spec ${index + 1} is missing a title.`, {
|
|
513
|
+
option: "task",
|
|
514
|
+
index,
|
|
515
|
+
rawSpec,
|
|
516
|
+
}),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (description.trim().length === 0) {
|
|
521
|
+
return {
|
|
522
|
+
specs: [],
|
|
523
|
+
error: failEmptyCompactField("task.create-many", "task", index, rawSpec, "description"),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
seenTempKeys.add(tempKey);
|
|
528
|
+
const spec: CompactTaskSpec = status.length > 0
|
|
529
|
+
? {
|
|
530
|
+
tempKey,
|
|
531
|
+
title,
|
|
532
|
+
description,
|
|
533
|
+
status,
|
|
534
|
+
}
|
|
535
|
+
: {
|
|
536
|
+
tempKey,
|
|
537
|
+
title,
|
|
538
|
+
description,
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
specs.push(spec);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return { specs };
|
|
545
|
+
}
|
|
546
|
+
|
|
371
547
|
export async function runTask(context: CliContext): Promise<CliResult> {
|
|
372
|
-
|
|
548
|
+
let database: TrekoonDatabase | undefined;
|
|
373
549
|
|
|
374
550
|
try {
|
|
551
|
+
database = openTrekoonDatabase(context.cwd);
|
|
375
552
|
const parsed = parseArgs(context.args);
|
|
376
553
|
const subcommand: string | undefined = parsed.positional[0];
|
|
377
554
|
const domain = new TrackerDomain(database.db);
|
|
@@ -404,6 +581,56 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
404
581
|
data: { task },
|
|
405
582
|
});
|
|
406
583
|
}
|
|
584
|
+
case "create-many": {
|
|
585
|
+
const createManyUnknownOption = findUnknownOption(parsed, CREATE_MANY_OPTIONS);
|
|
586
|
+
if (createManyUnknownOption !== undefined) {
|
|
587
|
+
return unknownOption("task.create-many", createManyUnknownOption, CREATE_MANY_OPTIONS);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const missingCreateManyOption = readMissingOptionValue(parsed.missingOptionValues, "epic", "e", "task");
|
|
591
|
+
if (missingCreateManyOption !== undefined) {
|
|
592
|
+
return failMissingOptionValue("task.create-many", missingCreateManyOption);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const unexpectedPositionals = readUnexpectedPositionals(parsed, 1);
|
|
596
|
+
if (unexpectedPositionals.length > 0) {
|
|
597
|
+
return failUnexpectedPositionals("task.create-many", unexpectedPositionals);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const epicId = readOption(parsed.options, "epic", "e");
|
|
601
|
+
if (epicId === undefined || epicId.trim().length === 0) {
|
|
602
|
+
return failBatchSpec("task.create-many", "Provide --epic for task create-many.", {
|
|
603
|
+
option: "epic",
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const rawSpecs = readOptions(parsed.optionEntries, "task");
|
|
608
|
+
if (rawSpecs.length === 0) {
|
|
609
|
+
return failBatchSpec("task.create-many", "Provide at least one --task spec.", {
|
|
610
|
+
option: "task",
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const specResult = parseTaskCreateManySpecs(rawSpecs);
|
|
615
|
+
if (specResult.error !== undefined) {
|
|
616
|
+
return specResult.error;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const created = mutations.createTaskBatch({
|
|
620
|
+
epicId,
|
|
621
|
+
specs: specResult.specs,
|
|
622
|
+
});
|
|
623
|
+
const result: CompactBatchResultContract = created.result;
|
|
624
|
+
return okResult({
|
|
625
|
+
command: "task.create-many",
|
|
626
|
+
human: `Created ${created.tasks.length} task(s): ${created.tasks.map(formatTask).join("\n")}`,
|
|
627
|
+
data: {
|
|
628
|
+
epicId,
|
|
629
|
+
tasks: created.tasks,
|
|
630
|
+
result,
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
}
|
|
407
634
|
case "list": {
|
|
408
635
|
const missingListOption =
|
|
409
636
|
readMissingOptionValue(parsed.missingOptionValues, "view") ??
|
|
@@ -763,6 +990,134 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
763
990
|
},
|
|
764
991
|
});
|
|
765
992
|
}
|
|
993
|
+
case "search": {
|
|
994
|
+
const searchUnknownOption = findUnknownOption(parsed, SEARCH_OPTIONS);
|
|
995
|
+
if (searchUnknownOption !== undefined) {
|
|
996
|
+
return unknownOption("task.search", searchUnknownOption, SEARCH_OPTIONS);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const missingSearchOption = readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
1000
|
+
if (missingSearchOption !== undefined) {
|
|
1001
|
+
return failMissingOptionValue("task.search", missingSearchOption);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const taskId: string = parsed.positional[1] ?? "";
|
|
1005
|
+
const searchText: string = parsed.positional[2] ?? "";
|
|
1006
|
+
if (taskId.length === 0 || searchText.trim().length === 0) {
|
|
1007
|
+
return invalidSearchInput(
|
|
1008
|
+
"task.search",
|
|
1009
|
+
"Usage: trekoon task search <task-id> \"search text\" [--fields <csv>] [--preview]",
|
|
1010
|
+
"Missing search target",
|
|
1011
|
+
{
|
|
1012
|
+
taskId,
|
|
1013
|
+
},
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const parsedFields = parseCsvEnumOption(readOption(parsed.options, "fields"), SEARCH_REPLACE_FIELDS);
|
|
1018
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
1019
|
+
return invalidSearchInput("task.search", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
1020
|
+
fields: readOption(parsed.options, "fields"),
|
|
1021
|
+
invalidFields: parsedFields.invalidValues,
|
|
1022
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const { matches, summary } = domain.searchTaskScope(taskId, searchText, parsedFields.values);
|
|
1027
|
+
|
|
1028
|
+
return okResult({
|
|
1029
|
+
command: "task.search",
|
|
1030
|
+
human: formatSearchHuman(matches, "No matches found."),
|
|
1031
|
+
data: {
|
|
1032
|
+
scope: {
|
|
1033
|
+
kind: "task",
|
|
1034
|
+
id: taskId,
|
|
1035
|
+
},
|
|
1036
|
+
query: {
|
|
1037
|
+
search: searchText,
|
|
1038
|
+
fields: parsedFields.values,
|
|
1039
|
+
mode: "preview",
|
|
1040
|
+
},
|
|
1041
|
+
summary,
|
|
1042
|
+
matches,
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
case "replace": {
|
|
1047
|
+
const replaceUnknownOption = findUnknownOption(parsed, REPLACE_OPTIONS);
|
|
1048
|
+
if (replaceUnknownOption !== undefined) {
|
|
1049
|
+
return unknownOption("task.replace", replaceUnknownOption, REPLACE_OPTIONS);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const missingReplaceOption =
|
|
1053
|
+
readMissingOptionValue(parsed.missingOptionValues, "search") ??
|
|
1054
|
+
readMissingOptionValue(parsed.missingOptionValues, "replace") ??
|
|
1055
|
+
readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
1056
|
+
if (missingReplaceOption !== undefined) {
|
|
1057
|
+
return failMissingOptionValue("task.replace", missingReplaceOption);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const taskId: string = parsed.positional[1] ?? "";
|
|
1061
|
+
const searchText = readOption(parsed.options, "search") ?? "";
|
|
1062
|
+
const replacementText = readOption(parsed.options, "replace") ?? "";
|
|
1063
|
+
if (taskId.length === 0 || searchText.trim().length === 0) {
|
|
1064
|
+
return invalidSearchInput(
|
|
1065
|
+
"task.replace",
|
|
1066
|
+
"Usage: trekoon task replace <task-id> --search \"text\" --replace \"text\" [--fields <csv>] [--preview|--apply]",
|
|
1067
|
+
"Missing replace target",
|
|
1068
|
+
{
|
|
1069
|
+
taskId,
|
|
1070
|
+
search: searchText,
|
|
1071
|
+
},
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const rawFields = readOption(parsed.options, "fields");
|
|
1076
|
+
const parsedFields = parseCsvEnumOption(rawFields, SEARCH_REPLACE_FIELDS);
|
|
1077
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
1078
|
+
return invalidSearchInput("task.replace", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
1079
|
+
fields: rawFields,
|
|
1080
|
+
invalidFields: parsedFields.invalidValues,
|
|
1081
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const previewMode = resolvePreviewApplyMode(parsed.flags);
|
|
1086
|
+
if (previewMode.conflict) {
|
|
1087
|
+
return invalidSearchInput("task.replace", "Use either --preview or --apply, not both.", "Conflicting mode flags", {
|
|
1088
|
+
flags: ["preview", "apply"],
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const replacementSummary = previewMode.mode === "apply"
|
|
1093
|
+
? mutations.applyTaskReplacement(taskId, searchText, replacementText, parsedFields.values)
|
|
1094
|
+
: mutations.previewTaskReplacement(taskId, searchText, replacementText, parsedFields.values);
|
|
1095
|
+
const { matches, summary: matchSummary } = replacementSummary;
|
|
1096
|
+
|
|
1097
|
+
const summary = {
|
|
1098
|
+
...matchSummary,
|
|
1099
|
+
mode: previewMode.mode,
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
return okResult({
|
|
1103
|
+
command: "task.replace",
|
|
1104
|
+
human: formatSearchHuman(matches, `No ${previewMode.mode === "apply" ? "replacements" : "matches"} found.`),
|
|
1105
|
+
data: {
|
|
1106
|
+
scope: {
|
|
1107
|
+
kind: "task",
|
|
1108
|
+
id: taskId,
|
|
1109
|
+
},
|
|
1110
|
+
query: {
|
|
1111
|
+
search: searchText,
|
|
1112
|
+
replace: replacementText,
|
|
1113
|
+
fields: parsedFields.values,
|
|
1114
|
+
mode: previewMode.mode,
|
|
1115
|
+
},
|
|
1116
|
+
summary,
|
|
1117
|
+
matches,
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
766
1121
|
case "update": {
|
|
767
1122
|
const missingUpdateOption =
|
|
768
1123
|
readMissingOptionValue(parsed.missingOptionValues, "ids") ??
|
|
@@ -900,7 +1255,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
900
1255
|
default:
|
|
901
1256
|
return failResult({
|
|
902
1257
|
command: "task",
|
|
903
|
-
human: "Usage: trekoon task <create|list|show|ready|next|update|delete>",
|
|
1258
|
+
human: "Usage: trekoon task <create|create-many|list|show|ready|next|search|replace|update|delete>",
|
|
904
1259
|
data: {
|
|
905
1260
|
args: context.args,
|
|
906
1261
|
},
|
|
@@ -913,6 +1268,6 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
913
1268
|
} catch (error: unknown) {
|
|
914
1269
|
return failFromError(error);
|
|
915
1270
|
} finally {
|
|
916
|
-
database
|
|
1271
|
+
database?.close();
|
|
917
1272
|
}
|
|
918
1273
|
}
|