not-manage 0.1.17

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.
@@ -0,0 +1,214 @@
1
+ const {
2
+ fetchMatter,
3
+ fetchMattersPage,
4
+ getValidAccessToken,
5
+ } = require("./clio-api");
6
+ const { getConfig, getTokenSet } = require("./store");
7
+ const {
8
+ clip,
9
+ compactQuery,
10
+ fetchPages,
11
+ formatBoolean,
12
+ parseLimit,
13
+ printKeyValueRows,
14
+ readContactName,
15
+ readUserName,
16
+ } = require("./resource-utils");
17
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
18
+
19
+ const DEFAULT_LIST_FIELDS =
20
+ "id,display_number,number,description,status,billable,open_date,close_date,pending_date,client{id,name,first_name,last_name},practice_area{id,name},responsible_attorney{id,name,email},responsible_staff{id,name,email},originating_attorney{id,name,email},created_at,updated_at";
21
+ const DEFAULT_GET_FIELDS =
22
+ "id,display_number,number,description,status,billable,open_date,close_date,pending_date,client{id,name,first_name,last_name},practice_area{id,name},responsible_attorney{id,name,email},responsible_staff{id,name,email},originating_attorney{id,name,email},created_at,updated_at";
23
+
24
+ function readStatus(status) {
25
+ if (!status) {
26
+ return "-";
27
+ }
28
+ if (typeof status === "string") {
29
+ return status;
30
+ }
31
+ return status.name || status.value || status.state || "-";
32
+ }
33
+
34
+ function readClientName(matter) {
35
+ const single = readContactName(matter.client);
36
+ if (single !== "-") {
37
+ return single;
38
+ }
39
+
40
+ const list = Array.isArray(matter.clients) ? matter.clients : [];
41
+ if (list.length > 0) {
42
+ return readContactName(list[0]);
43
+ }
44
+
45
+ return "-";
46
+ }
47
+
48
+ function formatMatterRow(matter) {
49
+ const id = matter.id || "-";
50
+ const displayNumber = matter.display_number || matter.number || "-";
51
+ const status = readStatus(matter.status);
52
+ const client = readClientName(matter);
53
+ const description = matter.description || "-";
54
+
55
+ return {
56
+ id: String(id),
57
+ displayNumber: String(displayNumber),
58
+ status: String(status),
59
+ client: String(client),
60
+ description: String(description),
61
+ };
62
+ }
63
+
64
+ function buildMatterQuery(options) {
65
+ return compactQuery({
66
+ client_id: options.clientId || undefined,
67
+ created_since: options.createdSince || undefined,
68
+ fields: options.fields || DEFAULT_LIST_FIELDS,
69
+ limit: parseLimit(options.limit),
70
+ order: options.order || undefined,
71
+ originating_attorney_id: options.originatingAttorneyId || undefined,
72
+ page_token: options.pageToken || undefined,
73
+ practice_area_id: options.practiceAreaId || undefined,
74
+ query: options.query || undefined,
75
+ responsible_attorney_id: options.responsibleAttorneyId || undefined,
76
+ responsible_staff_id: options.responsibleStaffId || undefined,
77
+ status: options.status || undefined,
78
+ updated_since: options.updatedSince || undefined,
79
+ });
80
+ }
81
+
82
+ function printMatterList(matterRows, options) {
83
+ if (matterRows.length === 0) {
84
+ console.log("No matters found for the selected filters.");
85
+ return;
86
+ }
87
+
88
+ const rows = matterRows.slice(0, 50);
89
+
90
+ console.log("ID MATTER STATUS CLIENT DESCRIPTION");
91
+ console.log("-------- --------------------- --------- -------------------- ------------------------------");
92
+
93
+ rows.forEach((row) => {
94
+ const line = [
95
+ clip(row.id, 8).padEnd(8, " "),
96
+ clip(row.displayNumber, 21).padEnd(21, " "),
97
+ clip(row.status, 9).padEnd(9, " "),
98
+ clip(row.client, 20).padEnd(20, " "),
99
+ clip(row.description, 30),
100
+ ].join(" ");
101
+
102
+ console.log(line);
103
+ });
104
+
105
+ if (matterRows.length > rows.length) {
106
+ console.log(
107
+ `Showing ${rows.length} of ${matterRows.length} matters. Use --json for full output.`
108
+ );
109
+ }
110
+
111
+ if (!options.all && options.nextPageUrl) {
112
+ console.log("");
113
+ console.log("More results are available.");
114
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
115
+ }
116
+ }
117
+
118
+ function printMatter(matter) {
119
+ printKeyValueRows([
120
+ ["ID", matter.id],
121
+ ["Matter", matter.display_number || matter.number],
122
+ ["Description", matter.description],
123
+ ["Status", readStatus(matter.status)],
124
+ ["Client", readClientName(matter)],
125
+ ["Practice Area", matter.practice_area?.name],
126
+ ["Responsible Attorney", readUserName(matter.responsible_attorney)],
127
+ ["Responsible Staff", readUserName(matter.responsible_staff)],
128
+ ["Originating Attorney", readUserName(matter.originating_attorney)],
129
+ ["Billable", formatBoolean(matter.billable)],
130
+ ["Open Date", matter.open_date],
131
+ ["Pending Date", matter.pending_date],
132
+ ["Close Date", matter.close_date],
133
+ ["Created", matter.created_at],
134
+ ["Updated", matter.updated_at],
135
+ ]);
136
+ }
137
+
138
+ async function getAuthContext() {
139
+ const config = await getConfig();
140
+ const tokenSet = await getTokenSet();
141
+ const accessToken = await getValidAccessToken(config, tokenSet);
142
+ return { config, accessToken };
143
+ }
144
+
145
+ async function mattersList(options = {}) {
146
+ const query = buildMatterQuery(options);
147
+ const { config, accessToken } = await getAuthContext();
148
+ const result = await fetchPages(
149
+ (pageQuery, nextPageUrl) => fetchMattersPage(config, accessToken, pageQuery, nextPageUrl),
150
+ query,
151
+ Boolean(options.all)
152
+ );
153
+
154
+ if (options.json) {
155
+ const firstPage = maybeRedactPayload(result.firstPage, options, "matter");
156
+ if (!options.all) {
157
+ console.log(JSON.stringify(firstPage, null, 2));
158
+ return;
159
+ }
160
+
161
+ const data = maybeRedactData(result.data, options, "matter");
162
+ console.log(
163
+ JSON.stringify(
164
+ {
165
+ data,
166
+ meta: {
167
+ pages_fetched: result.pagesFetched,
168
+ returned_count: data.length,
169
+ },
170
+ },
171
+ null,
172
+ 2
173
+ )
174
+ );
175
+ return;
176
+ }
177
+
178
+ const rows = maybeRedactData(result.data, options, "matter").map(formatMatterRow);
179
+ printMatterList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
180
+ console.log("");
181
+ console.log(
182
+ `Returned ${rows.length} matter${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
183
+ );
184
+ }
185
+
186
+ async function mattersGet(options = {}) {
187
+ if (!options.id) {
188
+ throw new Error("Usage: not-manage matters get <id> [--fields ...] [--json]");
189
+ }
190
+
191
+ const { config, accessToken } = await getAuthContext();
192
+ const payload = await fetchMatter(config, accessToken, options.id, {
193
+ fields: options.fields || DEFAULT_GET_FIELDS,
194
+ });
195
+ const redactedPayload = maybeRedactPayload(payload, options, "matter");
196
+
197
+ if (options.json) {
198
+ console.log(JSON.stringify(redactedPayload, null, 2));
199
+ return;
200
+ }
201
+
202
+ printMatter(redactedPayload?.data || {});
203
+ }
204
+
205
+ module.exports = {
206
+ mattersGet,
207
+ mattersList,
208
+ __private: {
209
+ buildMatterQuery,
210
+ formatMatterRow,
211
+ printMatter,
212
+ printMatterList,
213
+ },
214
+ };
@@ -0,0 +1,249 @@
1
+ const {
2
+ fetchMatter,
3
+ fetchPracticeArea,
4
+ fetchPracticeAreasPage,
5
+ getValidAccessToken,
6
+ } = require("./clio-api");
7
+ const { getConfig, getTokenSet } = require("./store");
8
+ const {
9
+ clip,
10
+ compactQuery,
11
+ fetchPages,
12
+ parseLimit,
13
+ printKeyValueRows,
14
+ } = require("./resource-utils");
15
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
16
+
17
+ const DEFAULT_LIST_FIELDS = "id,code,name,category,created_at,updated_at";
18
+ const DEFAULT_GET_FIELDS = "id,code,name,category,created_at,updated_at";
19
+ const MATTER_LOOKUP_FIELDS = "id,practice_area{id}";
20
+
21
+ function buildPracticeAreaQuery(options) {
22
+ return compactQuery({
23
+ code: options.code || undefined,
24
+ created_since: options.createdSince || undefined,
25
+ fields: options.fields || DEFAULT_LIST_FIELDS,
26
+ limit: parseLimit(options.limit),
27
+ name: options.name || undefined,
28
+ order: options.order || undefined,
29
+ page_token: options.pageToken || undefined,
30
+ updated_since: options.updatedSince || undefined,
31
+ });
32
+ }
33
+
34
+ function matchesTimestampOnOrAfter(value, threshold) {
35
+ if (!threshold) {
36
+ return true;
37
+ }
38
+
39
+ const valueTime = Date.parse(value);
40
+ const thresholdTime = Date.parse(threshold);
41
+
42
+ if (Number.isNaN(valueTime) || Number.isNaN(thresholdTime)) {
43
+ return true;
44
+ }
45
+
46
+ return valueTime >= thresholdTime;
47
+ }
48
+
49
+ function practiceAreaMatchesOptions(practiceArea, options = {}) {
50
+ const code = String(practiceArea?.code || "");
51
+ const name = String(practiceArea?.name || "");
52
+
53
+ if (options.code && code.toLowerCase() !== String(options.code).toLowerCase()) {
54
+ return false;
55
+ }
56
+
57
+ if (
58
+ options.name &&
59
+ !name.toLowerCase().includes(String(options.name).trim().toLowerCase())
60
+ ) {
61
+ return false;
62
+ }
63
+
64
+ if (!matchesTimestampOnOrAfter(practiceArea?.created_at, options.createdSince)) {
65
+ return false;
66
+ }
67
+
68
+ if (!matchesTimestampOnOrAfter(practiceArea?.updated_at, options.updatedSince)) {
69
+ return false;
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ function buildSinglePageResult(data) {
76
+ return {
77
+ data,
78
+ firstPage: {
79
+ data,
80
+ meta: {
81
+ paging: {},
82
+ records: data.length,
83
+ },
84
+ },
85
+ nextPageUrl: null,
86
+ pagesFetched: 1,
87
+ };
88
+ }
89
+
90
+ async function practiceAreasListForMatter(config, accessToken, options = {}) {
91
+ const matterPayload = await fetchMatter(config, accessToken, options.matterId, {
92
+ fields: MATTER_LOOKUP_FIELDS,
93
+ });
94
+ const practiceAreaId = matterPayload?.data?.practice_area?.id;
95
+
96
+ if (!practiceAreaId) {
97
+ return buildSinglePageResult([]);
98
+ }
99
+
100
+ const payload = await fetchPracticeArea(config, accessToken, practiceAreaId, {
101
+ fields: options.fields || DEFAULT_GET_FIELDS,
102
+ });
103
+ const practiceArea = payload?.data;
104
+ const data =
105
+ practiceArea && practiceAreaMatchesOptions(practiceArea, options)
106
+ ? [practiceArea]
107
+ : [];
108
+
109
+ return buildSinglePageResult(data);
110
+ }
111
+
112
+ function formatPracticeAreaRow(practiceArea) {
113
+ return {
114
+ id: String(practiceArea.id || "-"),
115
+ code: String(practiceArea.code || "-"),
116
+ name: String(practiceArea.name || "-"),
117
+ category: String(practiceArea.category || "-"),
118
+ };
119
+ }
120
+
121
+ function printPracticeAreaList(rows, options) {
122
+ if (rows.length === 0) {
123
+ console.log("No practice areas found for the selected filters.");
124
+ return;
125
+ }
126
+
127
+ const visibleRows = rows.slice(0, 50);
128
+ console.log("ID CODE NAME CATEGORY");
129
+ console.log("-------- ------------ ---------------------------- ------------------------------");
130
+
131
+ visibleRows.forEach((row) => {
132
+ const line = [
133
+ clip(row.id, 8).padEnd(8, " "),
134
+ clip(row.code, 12).padEnd(12, " "),
135
+ clip(row.name, 28).padEnd(28, " "),
136
+ clip(row.category, 30),
137
+ ].join(" ");
138
+
139
+ console.log(line);
140
+ });
141
+
142
+ if (rows.length > visibleRows.length) {
143
+ console.log(
144
+ `Showing ${visibleRows.length} of ${rows.length} practice areas. Use --json for full output.`
145
+ );
146
+ }
147
+
148
+ if (!options.all && options.nextPageUrl) {
149
+ console.log("");
150
+ console.log("More results are available.");
151
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
152
+ }
153
+ }
154
+
155
+ function printPracticeArea(practiceArea) {
156
+ printKeyValueRows([
157
+ ["ID", practiceArea.id],
158
+ ["Code", practiceArea.code],
159
+ ["Name", practiceArea.name],
160
+ ["Category", practiceArea.category],
161
+ ["Created", practiceArea.created_at],
162
+ ["Updated", practiceArea.updated_at],
163
+ ]);
164
+ }
165
+
166
+ async function getAuthContext() {
167
+ const config = await getConfig();
168
+ const tokenSet = await getTokenSet();
169
+ const accessToken = await getValidAccessToken(config, tokenSet);
170
+ return { config, accessToken };
171
+ }
172
+
173
+ async function practiceAreasList(options = {}) {
174
+ const { config, accessToken } = await getAuthContext();
175
+ const result = options.matterId
176
+ ? await practiceAreasListForMatter(config, accessToken, options)
177
+ : await fetchPages(
178
+ (pageQuery, nextPageUrl) =>
179
+ fetchPracticeAreasPage(config, accessToken, pageQuery, nextPageUrl),
180
+ buildPracticeAreaQuery(options),
181
+ Boolean(options.all)
182
+ );
183
+
184
+ if (options.json) {
185
+ const firstPage = maybeRedactPayload(result.firstPage, options, "practice-area");
186
+ if (!options.all) {
187
+ console.log(JSON.stringify(firstPage, null, 2));
188
+ return;
189
+ }
190
+
191
+ const data = maybeRedactData(result.data, options, "practice-area");
192
+ console.log(
193
+ JSON.stringify(
194
+ {
195
+ data,
196
+ meta: {
197
+ pages_fetched: result.pagesFetched,
198
+ returned_count: data.length,
199
+ },
200
+ },
201
+ null,
202
+ 2
203
+ )
204
+ );
205
+ return;
206
+ }
207
+
208
+ const rows = maybeRedactData(result.data, options, "practice-area").map(
209
+ formatPracticeAreaRow
210
+ );
211
+ printPracticeAreaList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
212
+ console.log("");
213
+ console.log(
214
+ `Returned ${rows.length} practice area${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
215
+ );
216
+ }
217
+
218
+ async function practiceAreasGet(options = {}) {
219
+ if (!options.id) {
220
+ throw new Error("Usage: not-manage practice-areas get <id> [--fields ...] [--json]");
221
+ }
222
+
223
+ const { config, accessToken } = await getAuthContext();
224
+ const payload = await fetchPracticeArea(config, accessToken, options.id, {
225
+ fields: options.fields || DEFAULT_GET_FIELDS,
226
+ });
227
+ const redactedPayload = maybeRedactPayload(payload, options, "practice-area");
228
+
229
+ if (options.json) {
230
+ console.log(JSON.stringify(redactedPayload, null, 2));
231
+ return;
232
+ }
233
+
234
+ printPracticeArea(redactedPayload?.data || {});
235
+ }
236
+
237
+ module.exports = {
238
+ practiceAreasGet,
239
+ practiceAreasList,
240
+ __private: {
241
+ buildSinglePageResult,
242
+ buildPracticeAreaQuery,
243
+ formatPracticeAreaRow,
244
+ practiceAreaMatchesOptions,
245
+ practiceAreasListForMatter,
246
+ printPracticeArea,
247
+ printPracticeAreaList,
248
+ },
249
+ };
@@ -0,0 +1,213 @@
1
+ const {
2
+ fetchTask,
3
+ fetchTasksPage,
4
+ getValidAccessToken,
5
+ } = require("./clio-api");
6
+ const { getConfig, getTokenSet } = require("./store");
7
+ const {
8
+ clip,
9
+ compactQuery,
10
+ fetchPages,
11
+ formatBoolean,
12
+ parseLimit,
13
+ printKeyValueRows,
14
+ readMatterLabel,
15
+ readUserName,
16
+ } = require("./resource-utils");
17
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
18
+
19
+ const DEFAULT_LIST_FIELDS =
20
+ "id,name,description,status,priority,due_at,created_at,updated_at,matter{id,display_number,number,description,client},assignee{id,name},assigner{id,name},task_type{id,name}";
21
+ const DEFAULT_GET_FIELDS =
22
+ "id,name,description,status,priority,due_at,created_at,updated_at,matter{id,display_number,number,description,client},assignee{id,name},assigner{id,name},task_type{id,name}";
23
+
24
+ function readTaskStatus(status) {
25
+ if (!status) {
26
+ return "-";
27
+ }
28
+
29
+ if (typeof status === "string") {
30
+ return status;
31
+ }
32
+
33
+ return status.name || status.value || status.state || "-";
34
+ }
35
+
36
+ function readTaskComplete(task) {
37
+ if (typeof task?.complete === "boolean") {
38
+ return task.complete;
39
+ }
40
+
41
+ if (typeof task?.status === "string") {
42
+ return task.status.toLowerCase() === "complete";
43
+ }
44
+
45
+ return undefined;
46
+ }
47
+
48
+ function buildTaskQuery(options) {
49
+ return compactQuery({
50
+ client_id: options.clientId || undefined,
51
+ complete:
52
+ options.complete === undefined || options.complete === null
53
+ ? undefined
54
+ : Boolean(options.complete),
55
+ created_since: options.createdSince || undefined,
56
+ due_at_from: options.dueAtFrom || undefined,
57
+ due_at_to: options.dueAtTo || undefined,
58
+ fields: options.fields || DEFAULT_LIST_FIELDS,
59
+ limit: parseLimit(options.limit),
60
+ matter_id: options.matterId || undefined,
61
+ order: options.order || undefined,
62
+ page_token: options.pageToken || undefined,
63
+ priority: options.priority || undefined,
64
+ query: options.query || undefined,
65
+ responsible_attorney_id: options.responsibleAttorneyId || undefined,
66
+ status: options.status || undefined,
67
+ task_type_id: options.taskTypeId || undefined,
68
+ updated_since: options.updatedSince || undefined,
69
+ });
70
+ }
71
+
72
+ function formatTaskRow(task) {
73
+ return {
74
+ id: String(task.id || "-"),
75
+ status: String(readTaskStatus(task.status)),
76
+ dueAt: String(task.due_at || "-"),
77
+ priority: String(task.priority || "-"),
78
+ matter: readMatterLabel(task.matter),
79
+ task: String(task.name || "-"),
80
+ };
81
+ }
82
+
83
+ function printTaskList(rows, options) {
84
+ if (rows.length === 0) {
85
+ console.log("No tasks found for the selected filters.");
86
+ return;
87
+ }
88
+
89
+ const visibleRows = rows.slice(0, 50);
90
+ console.log("ID STATUS DUE PRIORITY MATTER TASK");
91
+ console.log("-------- ------------ ------------ -------- -------------------- ------------------------------");
92
+
93
+ visibleRows.forEach((row) => {
94
+ const line = [
95
+ clip(row.id, 8).padEnd(8, " "),
96
+ clip(row.status, 12).padEnd(12, " "),
97
+ clip(row.dueAt, 12).padEnd(12, " "),
98
+ clip(row.priority, 8).padEnd(8, " "),
99
+ clip(row.matter, 20).padEnd(20, " "),
100
+ clip(row.task, 30),
101
+ ].join(" ");
102
+
103
+ console.log(line);
104
+ });
105
+
106
+ if (rows.length > visibleRows.length) {
107
+ console.log(`Showing ${visibleRows.length} of ${rows.length} tasks. Use --json for full output.`);
108
+ }
109
+
110
+ if (!options.all && options.nextPageUrl) {
111
+ console.log("");
112
+ console.log("More results are available.");
113
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
114
+ }
115
+ }
116
+
117
+ function printTask(task) {
118
+ printKeyValueRows([
119
+ ["ID", task.id],
120
+ ["Name", task.name],
121
+ ["Description", task.description],
122
+ ["Status", readTaskStatus(task.status)],
123
+ ["Priority", task.priority],
124
+ ["Due", task.due_at],
125
+ ["Complete", formatBoolean(readTaskComplete(task))],
126
+ ["Matter", readMatterLabel(task.matter)],
127
+ ["Assignee", readUserName(task.assignee)],
128
+ ["Assigner", readUserName(task.assigner)],
129
+ ["Task Type", task.task_type?.name],
130
+ ["Created", task.created_at],
131
+ ["Updated", task.updated_at],
132
+ ]);
133
+ }
134
+
135
+ async function getAuthContext() {
136
+ const config = await getConfig();
137
+ const tokenSet = await getTokenSet();
138
+ const accessToken = await getValidAccessToken(config, tokenSet);
139
+ return { config, accessToken };
140
+ }
141
+
142
+ async function tasksList(options = {}) {
143
+ const query = buildTaskQuery(options);
144
+ const { config, accessToken } = await getAuthContext();
145
+ const result = await fetchPages(
146
+ (pageQuery, nextPageUrl) => fetchTasksPage(config, accessToken, pageQuery, nextPageUrl),
147
+ query,
148
+ Boolean(options.all)
149
+ );
150
+
151
+ if (options.json) {
152
+ const firstPage = maybeRedactPayload(result.firstPage, options, "task");
153
+ if (!options.all) {
154
+ console.log(JSON.stringify(firstPage, null, 2));
155
+ return;
156
+ }
157
+
158
+ const data = maybeRedactData(result.data, options, "task");
159
+ console.log(
160
+ JSON.stringify(
161
+ {
162
+ data,
163
+ meta: {
164
+ pages_fetched: result.pagesFetched,
165
+ returned_count: data.length,
166
+ },
167
+ },
168
+ null,
169
+ 2
170
+ )
171
+ );
172
+ return;
173
+ }
174
+
175
+ const rows = maybeRedactData(result.data, options, "task").map(formatTaskRow);
176
+ printTaskList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
177
+ console.log("");
178
+ console.log(
179
+ `Returned ${rows.length} task${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
180
+ );
181
+ }
182
+
183
+ async function tasksGet(options = {}) {
184
+ if (!options.id) {
185
+ throw new Error("Usage: not-manage tasks get <id> [--fields ...] [--json]");
186
+ }
187
+
188
+ const { config, accessToken } = await getAuthContext();
189
+ const payload = await fetchTask(config, accessToken, options.id, {
190
+ fields: options.fields || DEFAULT_GET_FIELDS,
191
+ });
192
+ const redactedPayload = maybeRedactPayload(payload, options, "task");
193
+
194
+ if (options.json) {
195
+ console.log(JSON.stringify(redactedPayload, null, 2));
196
+ return;
197
+ }
198
+
199
+ printTask(redactedPayload?.data || {});
200
+ }
201
+
202
+ module.exports = {
203
+ tasksGet,
204
+ tasksList,
205
+ __private: {
206
+ buildTaskQuery,
207
+ formatTaskRow,
208
+ readTaskComplete,
209
+ printTask,
210
+ printTaskList,
211
+ readTaskStatus,
212
+ },
213
+ };