@groundctl/cli 0.3.1 → 0.3.2
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/index.js +245 -78
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -98,6 +98,15 @@ function applySchema(db) {
|
|
|
98
98
|
db.run("CREATE INDEX IF NOT EXISTS idx_claims_active ON claims(feature_id) WHERE released_at IS NULL");
|
|
99
99
|
db.run("CREATE INDEX IF NOT EXISTS idx_files_session ON files_modified(session_id)");
|
|
100
100
|
db.run("CREATE INDEX IF NOT EXISTS idx_decisions_session ON decisions(session_id)");
|
|
101
|
+
const tryAlter = (sql) => {
|
|
102
|
+
try {
|
|
103
|
+
db.run(sql);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
tryAlter("ALTER TABLE features ADD COLUMN progress_done INTEGER");
|
|
108
|
+
tryAlter("ALTER TABLE features ADD COLUMN progress_total INTEGER");
|
|
109
|
+
tryAlter("ALTER TABLE features ADD COLUMN items TEXT");
|
|
101
110
|
db.run(
|
|
102
111
|
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)",
|
|
103
112
|
[String(SCHEMA_VERSION)]
|
|
@@ -628,49 +637,87 @@ groundctl init \u2014 ${projectName}
|
|
|
628
637
|
|
|
629
638
|
// src/commands/status.ts
|
|
630
639
|
import chalk2 from "chalk";
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
640
|
+
var BAR_W = 14;
|
|
641
|
+
var NAME_W = 22;
|
|
642
|
+
var PROG_W = 6;
|
|
643
|
+
function progressBar(done, total, width) {
|
|
644
|
+
if (total <= 0) return chalk2.gray("\u2591".repeat(width));
|
|
645
|
+
const filled = Math.min(width, Math.round(done / total * width));
|
|
646
|
+
return chalk2.green("\u2588".repeat(filled)) + chalk2.gray("\u2591".repeat(width - filled));
|
|
647
|
+
}
|
|
648
|
+
function featureBar(status, progressDone, progressTotal) {
|
|
649
|
+
if (progressTotal != null && progressTotal > 0) {
|
|
650
|
+
return progressBar(progressDone ?? 0, progressTotal, BAR_W);
|
|
651
|
+
}
|
|
652
|
+
switch (status) {
|
|
653
|
+
case "done":
|
|
654
|
+
return progressBar(1, 1, BAR_W);
|
|
655
|
+
case "in_progress":
|
|
656
|
+
return progressBar(1, 2, BAR_W);
|
|
657
|
+
case "blocked":
|
|
658
|
+
return chalk2.red("\u2591".repeat(BAR_W));
|
|
659
|
+
default:
|
|
660
|
+
return chalk2.gray("\u2591".repeat(BAR_W));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function featureProgress(progressDone, progressTotal) {
|
|
664
|
+
if (progressDone != null && progressTotal != null) {
|
|
665
|
+
return `${progressDone}/${progressTotal}`;
|
|
666
|
+
}
|
|
667
|
+
return "";
|
|
668
|
+
}
|
|
669
|
+
function wrapItems(itemsCsv, maxWidth) {
|
|
670
|
+
const items = itemsCsv.split(",").map((s) => s.trim()).filter(Boolean);
|
|
671
|
+
const lines = [];
|
|
672
|
+
let current = "";
|
|
673
|
+
for (const item of items) {
|
|
674
|
+
const next = current ? `${current} \xB7 ${item}` : item;
|
|
675
|
+
if (next.length > maxWidth && current.length > 0) {
|
|
676
|
+
lines.push(current);
|
|
677
|
+
current = item;
|
|
678
|
+
} else {
|
|
679
|
+
current = next;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (current) lines.push(current);
|
|
683
|
+
return lines;
|
|
684
|
+
}
|
|
685
|
+
function timeSince(isoDate) {
|
|
686
|
+
const then = (/* @__PURE__ */ new Date(isoDate + "Z")).getTime();
|
|
687
|
+
const ms = Date.now() - then;
|
|
688
|
+
const mins = Math.floor(ms / 6e4);
|
|
689
|
+
if (mins < 60) return `${mins}m`;
|
|
690
|
+
const h = Math.floor(mins / 60);
|
|
691
|
+
const m = mins % 60;
|
|
692
|
+
return `${h}h${m > 0 ? String(m).padStart(2, "0") : ""}`;
|
|
636
693
|
}
|
|
637
694
|
async function statusCommand() {
|
|
638
695
|
const db = await openDb();
|
|
639
696
|
const projectName = process.cwd().split("/").pop() ?? "unknown";
|
|
640
|
-
const
|
|
641
|
-
db,
|
|
642
|
-
"SELECT status, COUNT(*) as count FROM features GROUP BY status"
|
|
643
|
-
);
|
|
644
|
-
const counts = {
|
|
645
|
-
pending: 0,
|
|
646
|
-
in_progress: 0,
|
|
647
|
-
done: 0,
|
|
648
|
-
blocked: 0
|
|
649
|
-
};
|
|
650
|
-
for (const row of statusCounts) {
|
|
651
|
-
counts[row.status] = row.count;
|
|
652
|
-
}
|
|
653
|
-
const total = counts.pending + counts.in_progress + counts.done + counts.blocked;
|
|
654
|
-
const activeClaims = query(
|
|
655
|
-
db,
|
|
656
|
-
`SELECT c.feature_id, f.name as feature_name, c.session_id, c.claimed_at
|
|
657
|
-
FROM claims c
|
|
658
|
-
JOIN features f ON c.feature_id = f.id
|
|
659
|
-
WHERE c.released_at IS NULL`
|
|
660
|
-
);
|
|
661
|
-
const available = query(
|
|
697
|
+
const features = query(
|
|
662
698
|
db,
|
|
663
|
-
`SELECT
|
|
699
|
+
`SELECT
|
|
700
|
+
f.id, f.name, f.status, f.priority,
|
|
701
|
+
f.description, f.progress_done, f.progress_total, f.items,
|
|
702
|
+
c.session_id AS claimed_session,
|
|
703
|
+
c.claimed_at AS claimed_at
|
|
664
704
|
FROM features f
|
|
665
|
-
|
|
666
|
-
|
|
705
|
+
LEFT JOIN claims c
|
|
706
|
+
ON c.feature_id = f.id AND c.released_at IS NULL
|
|
667
707
|
ORDER BY
|
|
708
|
+
CASE f.status
|
|
709
|
+
WHEN 'in_progress' THEN 0
|
|
710
|
+
WHEN 'blocked' THEN 1
|
|
711
|
+
WHEN 'pending' THEN 2
|
|
712
|
+
WHEN 'done' THEN 3
|
|
713
|
+
END,
|
|
668
714
|
CASE f.priority
|
|
669
715
|
WHEN 'critical' THEN 0
|
|
670
|
-
WHEN 'high'
|
|
671
|
-
WHEN 'medium'
|
|
672
|
-
WHEN 'low'
|
|
673
|
-
END
|
|
716
|
+
WHEN 'high' THEN 1
|
|
717
|
+
WHEN 'medium' THEN 2
|
|
718
|
+
WHEN 'low' THEN 3
|
|
719
|
+
END,
|
|
720
|
+
f.created_at`
|
|
674
721
|
);
|
|
675
722
|
const sessionCount = queryOne(
|
|
676
723
|
db,
|
|
@@ -678,59 +725,63 @@ async function statusCommand() {
|
|
|
678
725
|
)?.count ?? 0;
|
|
679
726
|
closeDb();
|
|
680
727
|
console.log("");
|
|
681
|
-
if (
|
|
728
|
+
if (features.length === 0) {
|
|
682
729
|
console.log(chalk2.bold(` ${projectName} \u2014 no features tracked yet
|
|
683
730
|
`));
|
|
684
731
|
console.log(chalk2.gray(" Add features with: groundctl add feature -n 'my-feature'"));
|
|
685
732
|
console.log(chalk2.gray(" Then run: groundctl status\n"));
|
|
686
733
|
return;
|
|
687
734
|
}
|
|
688
|
-
const
|
|
735
|
+
const total = features.length;
|
|
736
|
+
const done = features.filter((f) => f.status === "done").length;
|
|
737
|
+
const inProg = features.filter((f) => f.status === "in_progress").length;
|
|
738
|
+
const blocked = features.filter((f) => f.status === "blocked").length;
|
|
739
|
+
const pct = Math.round(done / total * 100);
|
|
689
740
|
console.log(
|
|
690
|
-
chalk2.bold(` ${projectName} \u2014 ${pct}% implemented`) + chalk2.gray(` (${sessionCount}
|
|
741
|
+
chalk2.bold(` ${projectName} \u2014 ${pct}% implemented`) + chalk2.gray(` (${sessionCount} session${sessionCount !== 1 ? "s" : ""})`)
|
|
691
742
|
);
|
|
692
743
|
console.log("");
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
);
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
if (counts.blocked > 0) {
|
|
700
|
-
console.log(chalk2.red(` ${counts.blocked} blocked`));
|
|
701
|
-
}
|
|
744
|
+
const aggBar = progressBar(done, total, 20);
|
|
745
|
+
let aggSuffix = chalk2.white(` ${done}/${total} done`);
|
|
746
|
+
if (inProg > 0) aggSuffix += chalk2.yellow(` ${inProg} in progress`);
|
|
747
|
+
if (blocked > 0) aggSuffix += chalk2.red(` ${blocked} blocked`);
|
|
748
|
+
console.log(` Features ${aggBar}${aggSuffix}`);
|
|
702
749
|
console.log("");
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
750
|
+
const maxNameLen = Math.min(NAME_W, Math.max(...features.map((f) => f.name.length)));
|
|
751
|
+
const nameW = Math.max(maxNameLen, 12);
|
|
752
|
+
const contIndent = " ".repeat(4 + nameW + 1);
|
|
753
|
+
const itemsMaxW = Math.max(40, 76 - contIndent.length);
|
|
754
|
+
for (const f of features) {
|
|
755
|
+
const isDone = f.status === "done";
|
|
756
|
+
const isActive = f.status === "in_progress";
|
|
757
|
+
const isBlocked = f.status === "blocked";
|
|
758
|
+
const icon = isDone ? "\u2713" : isActive ? "\u25CF" : isBlocked ? "\u2717" : "\u25CB";
|
|
759
|
+
const iconChalk = isDone ? chalk2.green : isActive ? chalk2.yellow : isBlocked ? chalk2.red : chalk2.gray;
|
|
760
|
+
const nameRaw = f.name.slice(0, nameW).padEnd(nameW);
|
|
761
|
+
const nameChalk = isDone ? chalk2.dim : isActive ? chalk2.white : isBlocked ? chalk2.red : chalk2.gray;
|
|
762
|
+
const pd = f.progress_done ?? null;
|
|
763
|
+
const pt = f.progress_total ?? null;
|
|
764
|
+
const bar2 = featureBar(f.status, pd, pt);
|
|
765
|
+
const prog = featureProgress(pd, pt).padEnd(PROG_W);
|
|
766
|
+
const descRaw = f.description ?? "";
|
|
767
|
+
const descTrunc = descRaw.length > 38 ? descRaw.slice(0, 36) + "\u2026" : descRaw;
|
|
768
|
+
const descStr = descTrunc ? chalk2.gray(` ${descTrunc}`) : "";
|
|
769
|
+
let claimedStr = "";
|
|
770
|
+
if (isActive && f.claimed_session) {
|
|
771
|
+
const elapsed = f.claimed_at ? timeSince(f.claimed_at) : "";
|
|
772
|
+
claimedStr = chalk2.yellow(` \u2192 ${f.claimed_session}${elapsed ? ` (${elapsed})` : ""}`);
|
|
710
773
|
}
|
|
711
|
-
console.log(
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (available.length > 5) {
|
|
720
|
-
console.log(chalk2.gray(` ... and ${available.length - 5} more`));
|
|
774
|
+
console.log(
|
|
775
|
+
` ${iconChalk(icon)} ${nameChalk(nameRaw)} ${bar2} ${prog}${descStr}${claimedStr}`
|
|
776
|
+
);
|
|
777
|
+
if (f.items) {
|
|
778
|
+
const lines = wrapItems(f.items, itemsMaxW);
|
|
779
|
+
for (const line of lines) {
|
|
780
|
+
console.log(chalk2.dim(`${contIndent}${line}`));
|
|
781
|
+
}
|
|
721
782
|
}
|
|
722
|
-
console.log("");
|
|
723
783
|
}
|
|
724
|
-
|
|
725
|
-
function timeSince(isoDate) {
|
|
726
|
-
const then = (/* @__PURE__ */ new Date(isoDate + "Z")).getTime();
|
|
727
|
-
const now = Date.now();
|
|
728
|
-
const diffMs = now - then;
|
|
729
|
-
const mins = Math.floor(diffMs / 6e4);
|
|
730
|
-
if (mins < 60) return `${mins}m`;
|
|
731
|
-
const hours = Math.floor(mins / 60);
|
|
732
|
-
const remainMins = mins % 60;
|
|
733
|
-
return `${hours}h${remainMins > 0 ? String(remainMins).padStart(2, "0") : ""}`;
|
|
784
|
+
console.log("");
|
|
734
785
|
}
|
|
735
786
|
|
|
736
787
|
// src/commands/claim.ts
|
|
@@ -1008,6 +1059,11 @@ async function logCommand(options) {
|
|
|
1008
1059
|
// src/commands/add.ts
|
|
1009
1060
|
import chalk7 from "chalk";
|
|
1010
1061
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1062
|
+
function parseProgress(s) {
|
|
1063
|
+
const m = s.match(/^(\d+)\/(\d+)$/);
|
|
1064
|
+
if (!m) return null;
|
|
1065
|
+
return { done: parseInt(m[1], 10), total: parseInt(m[2], 10) };
|
|
1066
|
+
}
|
|
1011
1067
|
async function addCommand(type, options) {
|
|
1012
1068
|
const db = await openDb();
|
|
1013
1069
|
if (type === "feature") {
|
|
@@ -1018,14 +1074,40 @@ async function addCommand(type, options) {
|
|
|
1018
1074
|
}
|
|
1019
1075
|
const id = options.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1020
1076
|
const priority = options.priority ?? "medium";
|
|
1077
|
+
let progressDone = null;
|
|
1078
|
+
let progressTotal = null;
|
|
1079
|
+
if (options.progress) {
|
|
1080
|
+
const p = parseProgress(options.progress);
|
|
1081
|
+
if (p) {
|
|
1082
|
+
progressDone = p.done;
|
|
1083
|
+
progressTotal = p.total;
|
|
1084
|
+
} else {
|
|
1085
|
+
console.log(chalk7.yellow(` \u26A0 --progress "${options.progress}" ignored (expected N/N format)`));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const items = options.items ? options.items.split(",").map((s) => s.trim()).filter(Boolean).join(",") : null;
|
|
1021
1089
|
db.run(
|
|
1022
|
-
|
|
1023
|
-
|
|
1090
|
+
`INSERT INTO features
|
|
1091
|
+
(id, name, priority, description, progress_done, progress_total, items)
|
|
1092
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1093
|
+
[
|
|
1094
|
+
id,
|
|
1095
|
+
options.name,
|
|
1096
|
+
priority,
|
|
1097
|
+
options.description ?? null,
|
|
1098
|
+
progressDone,
|
|
1099
|
+
progressTotal,
|
|
1100
|
+
items
|
|
1101
|
+
]
|
|
1024
1102
|
);
|
|
1025
1103
|
saveDb();
|
|
1026
1104
|
closeDb();
|
|
1105
|
+
const extras = [];
|
|
1106
|
+
if (progressDone !== null) extras.push(`${progressDone}/${progressTotal}`);
|
|
1107
|
+
if (items) extras.push(`${items.split(",").length} items`);
|
|
1108
|
+
const suffix = extras.length ? chalk7.gray(` \u2014 ${extras.join(", ")}`) : "";
|
|
1027
1109
|
console.log(chalk7.green(`
|
|
1028
|
-
\u2713 Feature added: ${options.name} (${priority})
|
|
1110
|
+
\u2713 Feature added: ${options.name} (${priority})${suffix}
|
|
1029
1111
|
`));
|
|
1030
1112
|
} else if (type === "session") {
|
|
1031
1113
|
const id = options.name ?? randomUUID2().slice(0, 8);
|
|
@@ -2092,6 +2174,82 @@ async function watchCommand(options) {
|
|
|
2092
2174
|
});
|
|
2093
2175
|
}
|
|
2094
2176
|
|
|
2177
|
+
// src/commands/update.ts
|
|
2178
|
+
import chalk13 from "chalk";
|
|
2179
|
+
function parseProgress2(s) {
|
|
2180
|
+
const m = s.match(/^(\d+)\/(\d+)$/);
|
|
2181
|
+
if (!m) return null;
|
|
2182
|
+
return { done: parseInt(m[1], 10), total: parseInt(m[2], 10) };
|
|
2183
|
+
}
|
|
2184
|
+
async function updateCommand(type, nameOrId, options) {
|
|
2185
|
+
if (type !== "feature") {
|
|
2186
|
+
console.log(chalk13.red(`
|
|
2187
|
+
Unknown type "${type}". Use "feature".
|
|
2188
|
+
`));
|
|
2189
|
+
process.exit(1);
|
|
2190
|
+
}
|
|
2191
|
+
const db = await openDb();
|
|
2192
|
+
const feature = queryOne(
|
|
2193
|
+
db,
|
|
2194
|
+
`SELECT id, name FROM features
|
|
2195
|
+
WHERE id = ?1 OR name = ?1
|
|
2196
|
+
OR id LIKE ?2 OR name LIKE ?2
|
|
2197
|
+
ORDER BY CASE WHEN id = ?1 OR name = ?1 THEN 0 ELSE 1 END
|
|
2198
|
+
LIMIT 1`,
|
|
2199
|
+
[nameOrId, `%${nameOrId}%`]
|
|
2200
|
+
);
|
|
2201
|
+
if (!feature) {
|
|
2202
|
+
console.log(chalk13.red(`
|
|
2203
|
+
Feature "${nameOrId}" not found.
|
|
2204
|
+
`));
|
|
2205
|
+
closeDb();
|
|
2206
|
+
process.exit(1);
|
|
2207
|
+
}
|
|
2208
|
+
const sets = [];
|
|
2209
|
+
const params = [];
|
|
2210
|
+
if (options.description !== void 0) {
|
|
2211
|
+
sets.push("description = ?");
|
|
2212
|
+
params.push(options.description);
|
|
2213
|
+
}
|
|
2214
|
+
if (options.items !== void 0) {
|
|
2215
|
+
const items = options.items.split(",").map((s) => s.trim()).filter(Boolean).join(",");
|
|
2216
|
+
sets.push("items = ?");
|
|
2217
|
+
params.push(items);
|
|
2218
|
+
}
|
|
2219
|
+
if (options.progress !== void 0) {
|
|
2220
|
+
const p = parseProgress2(options.progress);
|
|
2221
|
+
if (!p) {
|
|
2222
|
+
console.log(chalk13.yellow(` \u26A0 --progress "${options.progress}" ignored (expected N/N format)
|
|
2223
|
+
`));
|
|
2224
|
+
} else {
|
|
2225
|
+
sets.push("progress_done = ?", "progress_total = ?");
|
|
2226
|
+
params.push(p.done, p.total);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
if (options.priority !== void 0) {
|
|
2230
|
+
sets.push("priority = ?");
|
|
2231
|
+
params.push(options.priority);
|
|
2232
|
+
}
|
|
2233
|
+
if (options.status !== void 0) {
|
|
2234
|
+
sets.push("status = ?");
|
|
2235
|
+
params.push(options.status);
|
|
2236
|
+
}
|
|
2237
|
+
if (sets.length === 0) {
|
|
2238
|
+
console.log(chalk13.yellow("\n Nothing to update \u2014 pass at least one option.\n"));
|
|
2239
|
+
closeDb();
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
sets.push("updated_at = datetime('now')");
|
|
2243
|
+
params.push(feature.id);
|
|
2244
|
+
db.run(
|
|
2245
|
+
`UPDATE features SET ${sets.join(", ")} WHERE id = ?`,
|
|
2246
|
+
params
|
|
2247
|
+
);
|
|
2248
|
+
saveDb();
|
|
2249
|
+
closeDb();
|
|
2250
|
+
console.log(chalk13.green(` \u2713 Updated: ${feature.name}`));
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2095
2253
|
// src/index.ts
|
|
2096
2254
|
var require2 = createRequire(import.meta.url);
|
|
2097
2255
|
var pkg = require2("../package.json");
|
|
@@ -2104,7 +2262,7 @@ program.command("complete <feature>").description("Mark a feature as done and re
|
|
|
2104
2262
|
program.command("sync").description("Regenerate PROJECT_STATE.md and AGENTS.md from SQLite").action(syncCommand);
|
|
2105
2263
|
program.command("next").description("Show next available (unclaimed) feature").action(nextCommand);
|
|
2106
2264
|
program.command("log").description("Show session timeline").option("-s, --session <id>", "Show details for a specific session").action(logCommand);
|
|
2107
|
-
program.command("add <type>").description("Add a feature or session (type: feature, session)").option("-n, --name <name>", "Name").option("-p, --priority <priority>", "Priority (critical, high, medium, low)").option("-d, --description <desc>", "Description").option("--agent <agent>", "Agent type for sessions").action(addCommand);
|
|
2265
|
+
program.command("add <type>").description("Add a feature or session (type: feature, session)").option("-n, --name <name>", "Name").option("-p, --priority <priority>", "Priority (critical, high, medium, low)").option("-d, --description <desc>", "Description").option("--agent <agent>", "Agent type for sessions").option("--items <items>", "Comma-separated list of sub-items (features only)").option("--progress <N/N>", "Progress fraction e.g. 11/11 (features only)").action(addCommand);
|
|
2108
2266
|
program.command("ingest").description("Parse a transcript and write session data to SQLite").option("--source <source>", "Source agent (claude-code, codex)", "claude-code").option("--session-id <id>", "Session ID").option("--transcript <path>", "Path to transcript JSONL file (auto-detected if omitted)").option("--project-path <path>", "Project path (defaults to cwd)").option("--no-sync", "Skip regenerating markdown after ingest").action(
|
|
2109
2267
|
(opts) => ingestCommand({
|
|
2110
2268
|
source: opts.source,
|
|
@@ -2123,4 +2281,13 @@ program.command("watch").description("Watch for session end and auto-ingest tran
|
|
|
2123
2281
|
projectPath: opts.projectPath
|
|
2124
2282
|
})
|
|
2125
2283
|
);
|
|
2284
|
+
program.command("update <type> <name>").description("Update a feature's description, items, progress, or priority").option("-d, --description <desc>", "New description").option("--items <items>", "Comma-separated sub-items").option("--progress <N/N>", "Progress fraction e.g. 3/5").option("-p, --priority <priority>", "New priority").option("--status <status>", "New status (pending|in_progress|done|blocked)").action(
|
|
2285
|
+
(type, name, opts) => updateCommand(type, name, {
|
|
2286
|
+
description: opts.description,
|
|
2287
|
+
items: opts.items,
|
|
2288
|
+
progress: opts.progress,
|
|
2289
|
+
priority: opts.priority,
|
|
2290
|
+
status: opts.status
|
|
2291
|
+
})
|
|
2292
|
+
);
|
|
2126
2293
|
program.parse();
|