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.
- package/LICENSE +176 -0
- package/NOTICE +5 -0
- package/README.md +227 -0
- package/bin/not-manage.js +9 -0
- package/package.json +55 -0
- package/src/cli.js +668 -0
- package/src/clio-api.js +384 -0
- package/src/commands-activities.js +356 -0
- package/src/commands-auth.js +465 -0
- package/src/commands-billable-clients.js +152 -0
- package/src/commands-billable-matters.js +150 -0
- package/src/commands-bills.js +250 -0
- package/src/commands-contacts.js +179 -0
- package/src/commands-matters.js +214 -0
- package/src/commands-practice-areas.js +249 -0
- package/src/commands-tasks.js +213 -0
- package/src/commands-users.js +192 -0
- package/src/constants.js +50 -0
- package/src/keychain.js +63 -0
- package/src/oauth-callback.js +107 -0
- package/src/open-browser.js +33 -0
- package/src/postinstall.js +140 -0
- package/src/prompt.js +103 -0
- package/src/redaction.js +568 -0
- package/src/redirect-uri.js +53 -0
- package/src/resource-utils.js +141 -0
- package/src/store.js +178 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const {
|
|
2
|
+
fetchActivitiesPage,
|
|
3
|
+
fetchActivity,
|
|
4
|
+
fetchMattersPage,
|
|
5
|
+
getValidAccessToken,
|
|
6
|
+
} = require("./clio-api");
|
|
7
|
+
const { getConfig, getTokenSet } = require("./store");
|
|
8
|
+
const {
|
|
9
|
+
clip,
|
|
10
|
+
compactQuery,
|
|
11
|
+
fetchPages,
|
|
12
|
+
formatBoolean,
|
|
13
|
+
formatMoney,
|
|
14
|
+
parseLimit,
|
|
15
|
+
printKeyValueRows,
|
|
16
|
+
readMatterLabel,
|
|
17
|
+
readUserName,
|
|
18
|
+
} = require("./resource-utils");
|
|
19
|
+
const { maybeRedactData, maybeRedactPayload } = require("./redaction");
|
|
20
|
+
|
|
21
|
+
const DEFAULT_LIST_FIELDS =
|
|
22
|
+
"id,type,date,quantity,quantity_in_hours,rounded_quantity,rounded_quantity_in_hours,price,total,billed,on_bill,non_billable,no_charge,flat_rate,contingency_fee,note,reference,created_at,updated_at,activity_description{id,name},bill{id,number,state},matter{id,display_number,number,description},user{id,name,first_name,last_name,email}";
|
|
23
|
+
const DEFAULT_GET_FIELDS =
|
|
24
|
+
"id,type,date,quantity,quantity_in_hours,rounded_quantity,rounded_quantity_in_hours,price,total,billed,on_bill,non_billable,no_charge,flat_rate,contingency_fee,note,reference,created_at,updated_at,activity_description{id,name},bill{id,number,state},matter{id,display_number,number,description},user{id,name,first_name,last_name,email}";
|
|
25
|
+
|
|
26
|
+
function readHours(activity) {
|
|
27
|
+
const quantityInHours = Number(activity?.quantity_in_hours);
|
|
28
|
+
if (Number.isFinite(quantityInHours)) {
|
|
29
|
+
return quantityInHours.toFixed(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const quantity = Number(activity?.quantity);
|
|
33
|
+
if (Number.isFinite(quantity)) {
|
|
34
|
+
return (quantity / 3600).toFixed(2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return "-";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildActivityQuery(options) {
|
|
41
|
+
return compactQuery({
|
|
42
|
+
activity_description_id:
|
|
43
|
+
options.activityDescriptionId || undefined,
|
|
44
|
+
created_since: options.createdSince || undefined,
|
|
45
|
+
end_date: options.endDate || undefined,
|
|
46
|
+
fields: options.fields || DEFAULT_LIST_FIELDS,
|
|
47
|
+
flat_rate:
|
|
48
|
+
options.flatRate === undefined || options.flatRate === null
|
|
49
|
+
? undefined
|
|
50
|
+
: Boolean(options.flatRate),
|
|
51
|
+
limit: parseLimit(options.limit),
|
|
52
|
+
matter_id: options.matterId || undefined,
|
|
53
|
+
only_unaccounted_for: options.onlyUnaccountedFor ? true : undefined,
|
|
54
|
+
order: options.order || undefined,
|
|
55
|
+
page_token: options.pageToken || undefined,
|
|
56
|
+
query: options.query || undefined,
|
|
57
|
+
start_date: options.startDate || undefined,
|
|
58
|
+
status: options.status || undefined,
|
|
59
|
+
task_id: options.taskId || undefined,
|
|
60
|
+
type: options.type || undefined,
|
|
61
|
+
updated_since: options.updatedSince || undefined,
|
|
62
|
+
user_id: options.userId || undefined,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatActivityRow(activity) {
|
|
67
|
+
return {
|
|
68
|
+
id: String(activity.id || "-"),
|
|
69
|
+
type: String(activity.type || "-"),
|
|
70
|
+
date: String(activity.date || "-"),
|
|
71
|
+
hours: readHours(activity),
|
|
72
|
+
total: formatMoney(activity.total),
|
|
73
|
+
billed: formatBoolean(activity.billed),
|
|
74
|
+
matter: readMatterLabel(activity.matter),
|
|
75
|
+
note: String(activity.note || "-"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printActivityList(rows, options) {
|
|
80
|
+
if (rows.length === 0) {
|
|
81
|
+
console.log("No activities found for the selected filters.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const visibleRows = rows.slice(0, 50);
|
|
86
|
+
console.log("ID TYPE DATE HOURS TOTAL BILLED MATTER NOTE");
|
|
87
|
+
console.log("-------- ----------- ---------- ----- ---------- ------ -------------------- ------------------------------");
|
|
88
|
+
|
|
89
|
+
visibleRows.forEach((row) => {
|
|
90
|
+
const line = [
|
|
91
|
+
clip(row.id, 8).padEnd(8, " "),
|
|
92
|
+
clip(row.type, 11).padEnd(11, " "),
|
|
93
|
+
clip(row.date, 10).padEnd(10, " "),
|
|
94
|
+
clip(row.hours, 5).padEnd(5, " "),
|
|
95
|
+
clip(row.total, 10).padEnd(10, " "),
|
|
96
|
+
clip(row.billed, 6).padEnd(6, " "),
|
|
97
|
+
clip(row.matter, 20).padEnd(20, " "),
|
|
98
|
+
clip(row.note, 30),
|
|
99
|
+
].join(" ");
|
|
100
|
+
|
|
101
|
+
console.log(line);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (rows.length > visibleRows.length) {
|
|
105
|
+
console.log(`Showing ${visibleRows.length} of ${rows.length} activities. Use --json for full output.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!options.all && options.nextPageUrl) {
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log("More results are available.");
|
|
111
|
+
if (options.pageTokenSupported === false) {
|
|
112
|
+
console.log("Run again with `--all` to fetch every matching activity.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log("Run again with `--all` or pass `--page-token` from `--json` output.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function printActivity(activity) {
|
|
120
|
+
printKeyValueRows([
|
|
121
|
+
["ID", activity.id],
|
|
122
|
+
["Type", activity.type],
|
|
123
|
+
["Date", activity.date],
|
|
124
|
+
["Hours", readHours(activity)],
|
|
125
|
+
["Price", formatMoney(activity.price)],
|
|
126
|
+
["Total", formatMoney(activity.total)],
|
|
127
|
+
["Billed", formatBoolean(activity.billed)],
|
|
128
|
+
["On Bill", formatBoolean(activity.on_bill)],
|
|
129
|
+
["Non-Billable", formatBoolean(activity.non_billable)],
|
|
130
|
+
["No Charge", formatBoolean(activity.no_charge)],
|
|
131
|
+
["Flat Rate", formatBoolean(activity.flat_rate)],
|
|
132
|
+
["Contingency Fee", formatBoolean(activity.contingency_fee)],
|
|
133
|
+
["User", readUserName(activity.user)],
|
|
134
|
+
["Matter", readMatterLabel(activity.matter)],
|
|
135
|
+
["Activity Description", activity.activity_description?.name],
|
|
136
|
+
["Bill", activity.bill?.number],
|
|
137
|
+
["Bill State", activity.bill?.state],
|
|
138
|
+
["Reference", activity.reference],
|
|
139
|
+
["Note", activity.note],
|
|
140
|
+
["Created", activity.created_at],
|
|
141
|
+
["Updated", activity.updated_at],
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function getAuthContext() {
|
|
146
|
+
const config = await getConfig();
|
|
147
|
+
const tokenSet = await getTokenSet();
|
|
148
|
+
const accessToken = await getValidAccessToken(config, tokenSet);
|
|
149
|
+
return { config, accessToken };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function fetchMatterIdsForClient(config, accessToken, clientId) {
|
|
153
|
+
const matterQuery = {
|
|
154
|
+
client_id: clientId,
|
|
155
|
+
fields: "id",
|
|
156
|
+
limit: 200,
|
|
157
|
+
};
|
|
158
|
+
const result = await fetchPages(
|
|
159
|
+
(pageQuery, nextPageUrl) => fetchMattersPage(config, accessToken, pageQuery, nextPageUrl),
|
|
160
|
+
matterQuery,
|
|
161
|
+
true
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const matterIds = result.data
|
|
165
|
+
.map((matter) => matter?.id)
|
|
166
|
+
.filter((id) => id !== undefined && id !== null && id !== "");
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
matterIds,
|
|
170
|
+
pagesFetched: result.pagesFetched,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function fetchActivitiesForClient(config, accessToken, query, options) {
|
|
175
|
+
if (query.page_token) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
"`--page-token` is not supported with `activities list --client-id`. Use `--all` or filter by `--matter-id`."
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { matterIds, pagesFetched: matterPagesFetched } = await fetchMatterIdsForClient(
|
|
182
|
+
config,
|
|
183
|
+
accessToken,
|
|
184
|
+
options.clientId
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (matterIds.length === 0) {
|
|
188
|
+
return {
|
|
189
|
+
activityPagesFetched: 0,
|
|
190
|
+
data: [],
|
|
191
|
+
matterCount: 0,
|
|
192
|
+
matterLookupPagesFetched: matterPagesFetched,
|
|
193
|
+
moreResultsAvailable: false,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const aggregateLimit = query.limit || 200;
|
|
198
|
+
let remaining = options.all ? Number.POSITIVE_INFINITY : aggregateLimit;
|
|
199
|
+
let activityPagesFetched = 0;
|
|
200
|
+
const data = [];
|
|
201
|
+
let moreResultsAvailable = false;
|
|
202
|
+
|
|
203
|
+
for (let index = 0; index < matterIds.length; index += 1) {
|
|
204
|
+
const matterId = matterIds[index];
|
|
205
|
+
|
|
206
|
+
if (!options.all && remaining <= 0) {
|
|
207
|
+
moreResultsAvailable = true;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const matterQuery = compactQuery({
|
|
212
|
+
...query,
|
|
213
|
+
limit: options.all ? query.limit : Math.min(remaining, 200),
|
|
214
|
+
matter_id: matterId,
|
|
215
|
+
page_token: undefined,
|
|
216
|
+
});
|
|
217
|
+
const result = await fetchPages(
|
|
218
|
+
(pageQuery, nextPageUrl) => fetchActivitiesPage(config, accessToken, pageQuery, nextPageUrl),
|
|
219
|
+
matterQuery,
|
|
220
|
+
Boolean(options.all)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
data.push(...result.data);
|
|
224
|
+
activityPagesFetched += result.pagesFetched;
|
|
225
|
+
|
|
226
|
+
if (!options.all) {
|
|
227
|
+
remaining -= result.data.length;
|
|
228
|
+
if (result.nextPageUrl || (remaining <= 0 && index < matterIds.length - 1)) {
|
|
229
|
+
moreResultsAvailable = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
activityPagesFetched,
|
|
236
|
+
data,
|
|
237
|
+
matterCount: matterIds.length,
|
|
238
|
+
matterLookupPagesFetched: matterPagesFetched,
|
|
239
|
+
moreResultsAvailable,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function activitiesList(options = {}) {
|
|
244
|
+
const query = buildActivityQuery(options);
|
|
245
|
+
const { config, accessToken } = await getAuthContext();
|
|
246
|
+
|
|
247
|
+
if (options.clientId && !options.matterId) {
|
|
248
|
+
const clientResult = await fetchActivitiesForClient(config, accessToken, query, options);
|
|
249
|
+
const data = maybeRedactData(clientResult.data, options, "activity");
|
|
250
|
+
|
|
251
|
+
if (options.json) {
|
|
252
|
+
console.log(
|
|
253
|
+
JSON.stringify(
|
|
254
|
+
{
|
|
255
|
+
data,
|
|
256
|
+
meta: {
|
|
257
|
+
client_id: options.clientId,
|
|
258
|
+
activity_pages_fetched: clientResult.activityPagesFetched,
|
|
259
|
+
matter_count: clientResult.matterCount,
|
|
260
|
+
matter_pages_fetched: clientResult.matterLookupPagesFetched,
|
|
261
|
+
more_results_available: clientResult.moreResultsAvailable,
|
|
262
|
+
returned_count: data.length,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
null,
|
|
266
|
+
2
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const rows = data.map(formatActivityRow);
|
|
273
|
+
printActivityList(rows, {
|
|
274
|
+
all: Boolean(options.all),
|
|
275
|
+
nextPageUrl: clientResult.moreResultsAvailable ? "client-derived" : null,
|
|
276
|
+
pageTokenSupported: false,
|
|
277
|
+
});
|
|
278
|
+
console.log("");
|
|
279
|
+
console.log(
|
|
280
|
+
`Returned ${rows.length} activit${rows.length === 1 ? "y" : "ies"} across ${clientResult.activityPagesFetched} activity page${clientResult.activityPagesFetched === 1 ? "" : "s"} for ${clientResult.matterCount} matter${clientResult.matterCount === 1 ? "" : "s"}.`
|
|
281
|
+
);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const result = await fetchPages(
|
|
286
|
+
(pageQuery, nextPageUrl) => fetchActivitiesPage(config, accessToken, pageQuery, nextPageUrl),
|
|
287
|
+
query,
|
|
288
|
+
Boolean(options.all)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (options.json) {
|
|
292
|
+
const firstPage = maybeRedactPayload(result.firstPage, options, "activity");
|
|
293
|
+
if (!options.all) {
|
|
294
|
+
console.log(JSON.stringify(firstPage, null, 2));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const data = maybeRedactData(result.data, options, "activity");
|
|
299
|
+
console.log(
|
|
300
|
+
JSON.stringify(
|
|
301
|
+
{
|
|
302
|
+
data,
|
|
303
|
+
meta: {
|
|
304
|
+
pages_fetched: result.pagesFetched,
|
|
305
|
+
returned_count: data.length,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
null,
|
|
309
|
+
2
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const rows = maybeRedactData(result.data, options, "activity").map(formatActivityRow);
|
|
316
|
+
printActivityList(rows, {
|
|
317
|
+
all: Boolean(options.all),
|
|
318
|
+
nextPageUrl: result.nextPageUrl,
|
|
319
|
+
pageTokenSupported: true,
|
|
320
|
+
});
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log(
|
|
323
|
+
`Returned ${rows.length} activit${rows.length === 1 ? "y" : "ies"} across ${result.pagesFetched} page${result.pagesFetched === 1 ? "" : "s"}.`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function activitiesGet(options = {}) {
|
|
328
|
+
if (!options.id) {
|
|
329
|
+
throw new Error("Usage: not-manage activities get <id> [--fields ...] [--json]");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const { config, accessToken } = await getAuthContext();
|
|
333
|
+
const payload = await fetchActivity(config, accessToken, options.id, {
|
|
334
|
+
fields: options.fields || DEFAULT_GET_FIELDS,
|
|
335
|
+
});
|
|
336
|
+
const redactedPayload = maybeRedactPayload(payload, options, "activity");
|
|
337
|
+
|
|
338
|
+
if (options.json) {
|
|
339
|
+
console.log(JSON.stringify(redactedPayload, null, 2));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
printActivity(redactedPayload?.data || {});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
module.exports = {
|
|
347
|
+
activitiesGet,
|
|
348
|
+
activitiesList,
|
|
349
|
+
__private: {
|
|
350
|
+
buildActivityQuery,
|
|
351
|
+
formatActivityRow,
|
|
352
|
+
printActivity,
|
|
353
|
+
printActivityList,
|
|
354
|
+
readHours,
|
|
355
|
+
},
|
|
356
|
+
};
|