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,150 @@
1
+ const {
2
+ fetchBillableMattersPage,
3
+ getValidAccessToken,
4
+ } = require("./clio-api");
5
+ const { getConfig, getTokenSet } = require("./store");
6
+ const {
7
+ clip,
8
+ compactQuery,
9
+ fetchPages,
10
+ formatMoney,
11
+ parseLimit,
12
+ printKeyValueRows,
13
+ readContactName,
14
+ } = require("./resource-utils");
15
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
16
+
17
+ const DEFAULT_LIST_FIELDS =
18
+ "id,display_number,unbilled_hours,unbilled_amount,amount_in_trust,client{id,name,first_name,last_name}";
19
+
20
+ function buildBillableMatterQuery(options) {
21
+ return compactQuery({
22
+ client_id: options.clientId || undefined,
23
+ end_date: options.endDate || undefined,
24
+ fields: options.fields || DEFAULT_LIST_FIELDS,
25
+ limit: parseLimit(options.limit, 1000),
26
+ matter_id: options.matterId || undefined,
27
+ originating_attorney_id: options.originatingAttorneyId || undefined,
28
+ page_token: options.pageToken || undefined,
29
+ query: options.query || undefined,
30
+ responsible_attorney_id: options.responsibleAttorneyId || undefined,
31
+ start_date: options.startDate || undefined,
32
+ });
33
+ }
34
+
35
+ function formatBillableMatterRow(record) {
36
+ return {
37
+ id: String(record.id || "-"),
38
+ matter: String(record.display_number || "-"),
39
+ client: readContactName(record.client),
40
+ hours: record.unbilled_hours === undefined || record.unbilled_hours === null ? "-" : Number(record.unbilled_hours).toFixed(2),
41
+ amount: formatMoney(record.unbilled_amount),
42
+ trust: formatMoney(record.amount_in_trust),
43
+ };
44
+ }
45
+
46
+ function printBillableMatterList(rows, options) {
47
+ if (rows.length === 0) {
48
+ console.log("No billable matters found for the selected filters.");
49
+ return;
50
+ }
51
+
52
+ const visibleRows = rows.slice(0, 50);
53
+ console.log("ID MATTER CLIENT HOURS AMOUNT TRUST");
54
+ console.log("-------- --------------------- -------------------- ----- ---------- ----------");
55
+
56
+ visibleRows.forEach((row) => {
57
+ const line = [
58
+ clip(row.id, 8).padEnd(8, " "),
59
+ clip(row.matter, 21).padEnd(21, " "),
60
+ clip(row.client, 20).padEnd(20, " "),
61
+ clip(row.hours, 5).padEnd(5, " "),
62
+ clip(row.amount, 10).padEnd(10, " "),
63
+ clip(row.trust, 10),
64
+ ].join(" ");
65
+
66
+ console.log(line);
67
+ });
68
+
69
+ if (rows.length > visibleRows.length) {
70
+ console.log(`Showing ${visibleRows.length} of ${rows.length} billable matters. Use --json for full output.`);
71
+ }
72
+
73
+ if (!options.all && options.nextPageUrl) {
74
+ console.log("");
75
+ console.log("More results are available.");
76
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
77
+ }
78
+ }
79
+
80
+ function printBillableMatter(record) {
81
+ printKeyValueRows([
82
+ ["ID", record.id],
83
+ ["Matter", record.display_number],
84
+ ["Client", readContactName(record.client)],
85
+ ["Unbilled Hours", record.unbilled_hours],
86
+ ["Unbilled Amount", formatMoney(record.unbilled_amount)],
87
+ ["Amount In Trust", formatMoney(record.amount_in_trust)],
88
+ ]);
89
+ }
90
+
91
+ async function getAuthContext() {
92
+ const config = await getConfig();
93
+ const tokenSet = await getTokenSet();
94
+ const accessToken = await getValidAccessToken(config, tokenSet);
95
+ return { config, accessToken };
96
+ }
97
+
98
+ async function billableMattersList(options = {}) {
99
+ const query = buildBillableMatterQuery(options);
100
+ const { config, accessToken } = await getAuthContext();
101
+ const result = await fetchPages(
102
+ (pageQuery, nextPageUrl) =>
103
+ fetchBillableMattersPage(config, accessToken, pageQuery, nextPageUrl),
104
+ query,
105
+ Boolean(options.all)
106
+ );
107
+
108
+ if (options.json) {
109
+ const firstPage = maybeRedactPayload(result.firstPage, options, "billable-matter");
110
+ if (!options.all) {
111
+ console.log(JSON.stringify(firstPage, null, 2));
112
+ return;
113
+ }
114
+
115
+ const data = maybeRedactData(result.data, options, "billable-matter");
116
+ console.log(
117
+ JSON.stringify(
118
+ {
119
+ data,
120
+ meta: {
121
+ pages_fetched: result.pagesFetched,
122
+ returned_count: data.length,
123
+ },
124
+ },
125
+ null,
126
+ 2
127
+ )
128
+ );
129
+ return;
130
+ }
131
+
132
+ const rows = maybeRedactData(result.data, options, "billable-matter").map(
133
+ formatBillableMatterRow
134
+ );
135
+ printBillableMatterList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
136
+ console.log("");
137
+ console.log(
138
+ `Returned ${rows.length} billable matter${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
139
+ );
140
+ }
141
+
142
+ module.exports = {
143
+ billableMattersList,
144
+ __private: {
145
+ buildBillableMatterQuery,
146
+ formatBillableMatterRow,
147
+ printBillableMatter,
148
+ printBillableMatterList,
149
+ },
150
+ };
@@ -0,0 +1,250 @@
1
+ const {
2
+ fetchBill,
3
+ fetchBillsPage,
4
+ getValidAccessToken,
5
+ } = require("./clio-api");
6
+ const { getConfig, getTokenSet } = require("./store");
7
+ const {
8
+ clip,
9
+ compactQuery,
10
+ fetchPages,
11
+ formatMoney,
12
+ parseLimit,
13
+ printKeyValueRows,
14
+ readContactName,
15
+ readMatterLabel,
16
+ } = require("./resource-utils");
17
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
18
+
19
+ const DEFAULT_LIST_FIELDS =
20
+ "id,number,state,type,kind,subject,memo,issued_at,due_at,paid,paid_at,pending,due,total,balance,created_at,updated_at,client{id,name,first_name,last_name},matters{id,display_number,number,description}";
21
+ const DEFAULT_GET_FIELDS =
22
+ "id,number,state,type,kind,subject,memo,issued_at,due_at,paid,paid_at,pending,due,total,balance,created_at,updated_at,client{id,name,first_name,last_name},matters{id,display_number,number,description}";
23
+ const VALID_BILL_STATUSES = new Set(["all", "overdue"]);
24
+
25
+ function normalizeBillStatusFilters(options = {}) {
26
+ const state =
27
+ typeof options.state === "string"
28
+ ? options.state.trim() || undefined
29
+ : options.state || undefined;
30
+
31
+ if (options.status === undefined || options.status === null || options.status === "") {
32
+ return { state, status: undefined };
33
+ }
34
+
35
+ if (typeof options.status !== "string") {
36
+ throw new Error(
37
+ "Invalid value for --status on bills/invoices. Use `all`, `overdue`, or `unpaid`."
38
+ );
39
+ }
40
+
41
+ const status = options.status.trim().toLowerCase();
42
+
43
+ if (!status) {
44
+ throw new Error(
45
+ "Invalid value for --status on bills/invoices. Use `all`, `overdue`, or `unpaid`."
46
+ );
47
+ }
48
+
49
+ if (status === "unpaid") {
50
+ if (state && state !== "awaiting_payment") {
51
+ throw new Error(
52
+ "`--status unpaid` conflicts with `--state`. Use `--state awaiting_payment` or remove one of the filters."
53
+ );
54
+ }
55
+
56
+ return {
57
+ state: state || "awaiting_payment",
58
+ status: undefined,
59
+ };
60
+ }
61
+
62
+ if (!VALID_BILL_STATUSES.has(status)) {
63
+ throw new Error(
64
+ "Invalid value for --status on bills/invoices. Use `all`, `overdue`, or `unpaid`."
65
+ );
66
+ }
67
+
68
+ return { state, status };
69
+ }
70
+
71
+ function buildBillQuery(options) {
72
+ const filters = normalizeBillStatusFilters(options);
73
+
74
+ return compactQuery({
75
+ client_id: options.clientId || undefined,
76
+ created_since: options.createdSince || undefined,
77
+ due_after: options.dueAfter || undefined,
78
+ due_before: options.dueBefore || undefined,
79
+ fields: options.fields || DEFAULT_LIST_FIELDS,
80
+ issued_after: options.issuedAfter || undefined,
81
+ issued_before: options.issuedBefore || undefined,
82
+ limit: parseLimit(options.limit),
83
+ matter_id: options.matterId || undefined,
84
+ order: options.order || undefined,
85
+ overdue_only: options.overdueOnly ? true : undefined,
86
+ page_token: options.pageToken || undefined,
87
+ query: options.query || undefined,
88
+ state: filters.state,
89
+ status: filters.status,
90
+ type: options.type || undefined,
91
+ updated_since: options.updatedSince || undefined,
92
+ });
93
+ }
94
+
95
+ function readFirstMatterLabel(bill) {
96
+ const matters = Array.isArray(bill.matters) ? bill.matters : [];
97
+ if (matters.length === 0) {
98
+ return "-";
99
+ }
100
+
101
+ return readMatterLabel(matters[0]);
102
+ }
103
+
104
+ function formatBillRow(bill) {
105
+ return {
106
+ id: String(bill.id || "-"),
107
+ number: String(bill.number || "-"),
108
+ state: String(bill.state || "-"),
109
+ client: readContactName(bill.client),
110
+ dueAt: String(bill.due_at || "-"),
111
+ balance: formatMoney(bill.balance),
112
+ };
113
+ }
114
+
115
+ function printBillList(rows, options) {
116
+ if (rows.length === 0) {
117
+ console.log("No bills found for the selected filters.");
118
+ return;
119
+ }
120
+
121
+ const visibleRows = rows.slice(0, 50);
122
+ console.log("ID BILL STATE CLIENT DUE BALANCE");
123
+ console.log("-------- -------------- ------------ ---------------------------- ------------ ----------");
124
+
125
+ visibleRows.forEach((row) => {
126
+ const line = [
127
+ clip(row.id, 8).padEnd(8, " "),
128
+ clip(row.number, 14).padEnd(14, " "),
129
+ clip(row.state, 12).padEnd(12, " "),
130
+ clip(row.client, 28).padEnd(28, " "),
131
+ clip(row.dueAt, 12).padEnd(12, " "),
132
+ clip(row.balance, 10),
133
+ ].join(" ");
134
+
135
+ console.log(line);
136
+ });
137
+
138
+ if (rows.length > visibleRows.length) {
139
+ console.log(`Showing ${visibleRows.length} of ${rows.length} bills. Use --json for full output.`);
140
+ }
141
+
142
+ if (!options.all && options.nextPageUrl) {
143
+ console.log("");
144
+ console.log("More results are available.");
145
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
146
+ }
147
+ }
148
+
149
+ function printBill(bill) {
150
+ printKeyValueRows([
151
+ ["ID", bill.id],
152
+ ["Number", bill.number],
153
+ ["State", bill.state],
154
+ ["Type", bill.type],
155
+ ["Kind", bill.kind],
156
+ ["Client", readContactName(bill.client)],
157
+ ["Matter", readFirstMatterLabel(bill)],
158
+ ["Issued", bill.issued_at],
159
+ ["Due", bill.due_at],
160
+ ["Total", formatMoney(bill.total)],
161
+ ["Balance", formatMoney(bill.balance)],
162
+ ["Paid", formatMoney(bill.paid)],
163
+ ["Paid At", bill.paid_at],
164
+ ["Pending", formatMoney(bill.pending)],
165
+ ["Due Amount", formatMoney(bill.due)],
166
+ ["Subject", bill.subject],
167
+ ["Memo", bill.memo],
168
+ ["Created", bill.created_at],
169
+ ["Updated", bill.updated_at],
170
+ ]);
171
+ }
172
+
173
+ async function getAuthContext() {
174
+ const config = await getConfig();
175
+ const tokenSet = await getTokenSet();
176
+ const accessToken = await getValidAccessToken(config, tokenSet);
177
+ return { config, accessToken };
178
+ }
179
+
180
+ async function billsList(options = {}) {
181
+ const query = buildBillQuery(options);
182
+ const { config, accessToken } = await getAuthContext();
183
+ const result = await fetchPages(
184
+ (pageQuery, nextPageUrl) => fetchBillsPage(config, accessToken, pageQuery, nextPageUrl),
185
+ query,
186
+ Boolean(options.all)
187
+ );
188
+
189
+ if (options.json) {
190
+ const firstPage = maybeRedactPayload(result.firstPage, options, "bill");
191
+ if (!options.all) {
192
+ console.log(JSON.stringify(firstPage, null, 2));
193
+ return;
194
+ }
195
+
196
+ const data = maybeRedactData(result.data, options, "bill");
197
+ console.log(
198
+ JSON.stringify(
199
+ {
200
+ data,
201
+ meta: {
202
+ pages_fetched: result.pagesFetched,
203
+ returned_count: data.length,
204
+ },
205
+ },
206
+ null,
207
+ 2
208
+ )
209
+ );
210
+ return;
211
+ }
212
+
213
+ const rows = maybeRedactData(result.data, options, "bill").map(formatBillRow);
214
+ printBillList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
215
+ console.log("");
216
+ console.log(
217
+ `Returned ${rows.length} bill${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
218
+ );
219
+ }
220
+
221
+ async function billsGet(options = {}) {
222
+ if (!options.id) {
223
+ throw new Error("Usage: not-manage bills get <id> [--fields ...] [--json]");
224
+ }
225
+
226
+ const { config, accessToken } = await getAuthContext();
227
+ const payload = await fetchBill(config, accessToken, options.id, {
228
+ fields: options.fields || DEFAULT_GET_FIELDS,
229
+ });
230
+ const redactedPayload = maybeRedactPayload(payload, options, "bill");
231
+
232
+ if (options.json) {
233
+ console.log(JSON.stringify(redactedPayload, null, 2));
234
+ return;
235
+ }
236
+
237
+ printBill(redactedPayload?.data || {});
238
+ }
239
+
240
+ module.exports = {
241
+ billsGet,
242
+ billsList,
243
+ __private: {
244
+ buildBillQuery,
245
+ formatBillRow,
246
+ normalizeBillStatusFilters,
247
+ printBill,
248
+ printBillList,
249
+ },
250
+ };
@@ -0,0 +1,179 @@
1
+ const {
2
+ fetchContact,
3
+ fetchContactsPage,
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
+ } = require("./resource-utils");
16
+ const { maybeRedactData, maybeRedactPayload } = require("./redaction");
17
+
18
+ const DEFAULT_LIST_FIELDS =
19
+ "id,name,first_name,last_name,type,is_client,primary_email_address,secondary_email_address,primary_phone_number,secondary_phone_number,clio_connect_email,title,prefix,created_at,updated_at";
20
+ const DEFAULT_GET_FIELDS =
21
+ "id,name,first_name,last_name,type,is_client,primary_email_address,secondary_email_address,primary_phone_number,secondary_phone_number,clio_connect_email,title,prefix,created_at,updated_at";
22
+
23
+ function buildContactQuery(options) {
24
+ return compactQuery({
25
+ client_only: options.clientOnly ? true : undefined,
26
+ clio_connect_only: options.clioConnectOnly ? true : undefined,
27
+ created_since: options.createdSince || undefined,
28
+ email_only: options.emailOnly ? true : undefined,
29
+ fields: options.fields || DEFAULT_LIST_FIELDS,
30
+ initial: options.initial || undefined,
31
+ limit: parseLimit(options.limit),
32
+ order: options.order || undefined,
33
+ page_token: options.pageToken || undefined,
34
+ query: options.query || undefined,
35
+ type: options.type || undefined,
36
+ updated_since: options.updatedSince || undefined,
37
+ });
38
+ }
39
+
40
+ function formatContactRow(contact) {
41
+ return {
42
+ id: String(contact.id || "-"),
43
+ name: readContactName(contact),
44
+ type: String(contact.type || "-"),
45
+ client: formatBoolean(contact.is_client),
46
+ email: String(contact.primary_email_address || "-"),
47
+ phone: String(contact.primary_phone_number || "-"),
48
+ };
49
+ }
50
+
51
+ function printContactList(rows, options) {
52
+ if (rows.length === 0) {
53
+ console.log("No contacts found for the selected filters.");
54
+ return;
55
+ }
56
+
57
+ const visibleRows = rows.slice(0, 50);
58
+ console.log("ID NAME TYPE CLIENT EMAIL PHONE");
59
+ console.log("-------- ---------------------------- ------------ ------ ---------------------------- ------------------");
60
+
61
+ visibleRows.forEach((row) => {
62
+ const line = [
63
+ clip(row.id, 8).padEnd(8, " "),
64
+ clip(row.name, 28).padEnd(28, " "),
65
+ clip(row.type, 12).padEnd(12, " "),
66
+ clip(row.client, 6).padEnd(6, " "),
67
+ clip(row.email, 28).padEnd(28, " "),
68
+ clip(row.phone, 18),
69
+ ].join(" ");
70
+
71
+ console.log(line);
72
+ });
73
+
74
+ if (rows.length > visibleRows.length) {
75
+ console.log(`Showing ${visibleRows.length} of ${rows.length} contacts. Use --json for full output.`);
76
+ }
77
+
78
+ if (!options.all && options.nextPageUrl) {
79
+ console.log("");
80
+ console.log("More results are available.");
81
+ console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
82
+ }
83
+ }
84
+
85
+ function printContact(contact) {
86
+ printKeyValueRows([
87
+ ["ID", contact.id],
88
+ ["Name", readContactName(contact)],
89
+ ["Type", contact.type],
90
+ ["Client", formatBoolean(contact.is_client)],
91
+ ["Primary Email", contact.primary_email_address],
92
+ ["Secondary Email", contact.secondary_email_address],
93
+ ["Primary Phone", contact.primary_phone_number],
94
+ ["Secondary Phone", contact.secondary_phone_number],
95
+ ["Clio Connect Email", contact.clio_connect_email],
96
+ ["Title", contact.title],
97
+ ["Prefix", contact.prefix],
98
+ ["Created", contact.created_at],
99
+ ["Updated", contact.updated_at],
100
+ ]);
101
+ }
102
+
103
+ async function getAuthContext() {
104
+ const config = await getConfig();
105
+ const tokenSet = await getTokenSet();
106
+ const accessToken = await getValidAccessToken(config, tokenSet);
107
+ return { config, accessToken };
108
+ }
109
+
110
+ async function contactsList(options = {}) {
111
+ const query = buildContactQuery(options);
112
+ const { config, accessToken } = await getAuthContext();
113
+ const result = await fetchPages(
114
+ (pageQuery, nextPageUrl) => fetchContactsPage(config, accessToken, pageQuery, nextPageUrl),
115
+ query,
116
+ Boolean(options.all)
117
+ );
118
+
119
+ if (options.json) {
120
+ const firstPage = maybeRedactPayload(result.firstPage, options, "contact");
121
+ if (!options.all) {
122
+ console.log(JSON.stringify(firstPage, null, 2));
123
+ return;
124
+ }
125
+
126
+ const data = maybeRedactData(result.data, options, "contact");
127
+ console.log(
128
+ JSON.stringify(
129
+ {
130
+ data,
131
+ meta: {
132
+ pages_fetched: result.pagesFetched,
133
+ returned_count: data.length,
134
+ },
135
+ },
136
+ null,
137
+ 2
138
+ )
139
+ );
140
+ return;
141
+ }
142
+
143
+ const rows = maybeRedactData(result.data, options, "contact").map(formatContactRow);
144
+ printContactList(rows, { all: Boolean(options.all), nextPageUrl: result.nextPageUrl });
145
+ console.log("");
146
+ console.log(
147
+ `Returned ${rows.length} contact${rows.length === 1 ? "" : "s"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
148
+ );
149
+ }
150
+
151
+ async function contactsGet(options = {}) {
152
+ if (!options.id) {
153
+ throw new Error("Usage: not-manage contacts get <id> [--fields ...] [--json]");
154
+ }
155
+
156
+ const { config, accessToken } = await getAuthContext();
157
+ const payload = await fetchContact(config, accessToken, options.id, {
158
+ fields: options.fields || DEFAULT_GET_FIELDS,
159
+ });
160
+ const redactedPayload = maybeRedactPayload(payload, options, "contact");
161
+
162
+ if (options.json) {
163
+ console.log(JSON.stringify(redactedPayload, null, 2));
164
+ return;
165
+ }
166
+
167
+ printContact(redactedPayload?.data || {});
168
+ }
169
+
170
+ module.exports = {
171
+ contactsGet,
172
+ contactsList,
173
+ __private: {
174
+ buildContactQuery,
175
+ formatContactRow,
176
+ printContact,
177
+ printContactList,
178
+ },
179
+ };