@corners/cli 0.0.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/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1284 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +162 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +100 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/support.d.ts +21 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/support.js +105 -0
- package/dist/support.js.map +1 -0
- package/package.json +35 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1284 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { CornersApiClient as DefaultCornersApiClient, } from "./client.js";
|
|
6
|
+
import { ConfigStore } from "./config.js";
|
|
7
|
+
import { CLIError, getPackageVersion, openUrlInBrowser, printJson, printLine, readTextFromStdin, sleep, toGraphQLAttachmentKind, toGraphQLWorkstreamUpdateType, toIsoString, } from "./support.js";
|
|
8
|
+
const LIST_WORKSTREAMS_QUERY = `
|
|
9
|
+
query CliListWorkstreams {
|
|
10
|
+
workstreams(filter: { status: ACTIVE }, first: 100) {
|
|
11
|
+
edges {
|
|
12
|
+
node {
|
|
13
|
+
id
|
|
14
|
+
cornerId
|
|
15
|
+
name
|
|
16
|
+
summary
|
|
17
|
+
category
|
|
18
|
+
status
|
|
19
|
+
updatedAt
|
|
20
|
+
topic {
|
|
21
|
+
id
|
|
22
|
+
name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
const WORKSTREAM_LOOKUP_QUERY = `
|
|
30
|
+
query CliWorkstreamLookup($id: ID!) {
|
|
31
|
+
workstream(id: $id) {
|
|
32
|
+
id
|
|
33
|
+
accountId
|
|
34
|
+
cornerId
|
|
35
|
+
name
|
|
36
|
+
summary
|
|
37
|
+
category
|
|
38
|
+
status
|
|
39
|
+
updatedAt
|
|
40
|
+
topic {
|
|
41
|
+
id
|
|
42
|
+
name
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
const WORKSTREAM_PULL_QUERY = `
|
|
48
|
+
query CliWorkstreamPull($id: ID!, $attachmentsFirst: Int!, $feedFirst: Int!) {
|
|
49
|
+
workstream(id: $id) {
|
|
50
|
+
id
|
|
51
|
+
accountId
|
|
52
|
+
cornerId
|
|
53
|
+
name
|
|
54
|
+
summary
|
|
55
|
+
category
|
|
56
|
+
status
|
|
57
|
+
createdAt
|
|
58
|
+
updatedAt
|
|
59
|
+
firstAttachmentActivityAt
|
|
60
|
+
lastAttachmentActivityAt
|
|
61
|
+
topic {
|
|
62
|
+
id
|
|
63
|
+
name
|
|
64
|
+
summary
|
|
65
|
+
}
|
|
66
|
+
topicPath {
|
|
67
|
+
topicId
|
|
68
|
+
name
|
|
69
|
+
}
|
|
70
|
+
openQuestions: questions(status: OPEN) {
|
|
71
|
+
id
|
|
72
|
+
workstreamId
|
|
73
|
+
status
|
|
74
|
+
question
|
|
75
|
+
rationale
|
|
76
|
+
askOf {
|
|
77
|
+
actorId
|
|
78
|
+
externalId
|
|
79
|
+
userId
|
|
80
|
+
}
|
|
81
|
+
evidenceRefs
|
|
82
|
+
suggestedAnswers
|
|
83
|
+
answerText
|
|
84
|
+
answeredAt
|
|
85
|
+
answeredByUserId
|
|
86
|
+
createdAt
|
|
87
|
+
updatedAt
|
|
88
|
+
}
|
|
89
|
+
answeredQuestions: questions(status: ANSWERED) {
|
|
90
|
+
id
|
|
91
|
+
workstreamId
|
|
92
|
+
status
|
|
93
|
+
question
|
|
94
|
+
rationale
|
|
95
|
+
askOf {
|
|
96
|
+
actorId
|
|
97
|
+
externalId
|
|
98
|
+
userId
|
|
99
|
+
}
|
|
100
|
+
evidenceRefs
|
|
101
|
+
suggestedAnswers
|
|
102
|
+
answerText
|
|
103
|
+
answeredAt
|
|
104
|
+
answeredByUserId
|
|
105
|
+
createdAt
|
|
106
|
+
updatedAt
|
|
107
|
+
}
|
|
108
|
+
attachments(first: $attachmentsFirst) {
|
|
109
|
+
totalCount
|
|
110
|
+
edges {
|
|
111
|
+
node {
|
|
112
|
+
__typename
|
|
113
|
+
attachmentId
|
|
114
|
+
entityId
|
|
115
|
+
kind
|
|
116
|
+
title
|
|
117
|
+
summary
|
|
118
|
+
metadata
|
|
119
|
+
createdAt
|
|
120
|
+
updatedAt
|
|
121
|
+
lastActivityAt
|
|
122
|
+
... on ArtifactThread {
|
|
123
|
+
id
|
|
124
|
+
providerType
|
|
125
|
+
visibility
|
|
126
|
+
topic
|
|
127
|
+
lastMessageAt
|
|
128
|
+
messages {
|
|
129
|
+
id
|
|
130
|
+
senderRef
|
|
131
|
+
content
|
|
132
|
+
contentType
|
|
133
|
+
sentAt
|
|
134
|
+
editedAt
|
|
135
|
+
deletedAt
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
... on WorkstreamDocumentAttachment {
|
|
139
|
+
document {
|
|
140
|
+
id
|
|
141
|
+
title
|
|
142
|
+
content
|
|
143
|
+
updatedAt
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
feed(first: $feedFirst) {
|
|
150
|
+
totalCount
|
|
151
|
+
edges {
|
|
152
|
+
node {
|
|
153
|
+
id
|
|
154
|
+
eventType
|
|
155
|
+
sourceEntityType
|
|
156
|
+
sourceEntityId
|
|
157
|
+
payload
|
|
158
|
+
createdAt
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
const WORKSTREAM_QUESTION_LIST_QUERY = `
|
|
166
|
+
query CliWorkstreamQuestions($id: ID!) {
|
|
167
|
+
workstream(id: $id) {
|
|
168
|
+
id
|
|
169
|
+
name
|
|
170
|
+
openQuestions: questions(status: OPEN) {
|
|
171
|
+
id
|
|
172
|
+
status
|
|
173
|
+
question
|
|
174
|
+
rationale
|
|
175
|
+
askOf {
|
|
176
|
+
actorId
|
|
177
|
+
externalId
|
|
178
|
+
userId
|
|
179
|
+
}
|
|
180
|
+
suggestedAnswers
|
|
181
|
+
answerText
|
|
182
|
+
answeredAt
|
|
183
|
+
answeredByUserId
|
|
184
|
+
createdAt
|
|
185
|
+
updatedAt
|
|
186
|
+
}
|
|
187
|
+
answeredQuestions: questions(status: ANSWERED) {
|
|
188
|
+
id
|
|
189
|
+
status
|
|
190
|
+
question
|
|
191
|
+
rationale
|
|
192
|
+
askOf {
|
|
193
|
+
actorId
|
|
194
|
+
externalId
|
|
195
|
+
userId
|
|
196
|
+
}
|
|
197
|
+
suggestedAnswers
|
|
198
|
+
answerText
|
|
199
|
+
answeredAt
|
|
200
|
+
answeredByUserId
|
|
201
|
+
createdAt
|
|
202
|
+
updatedAt
|
|
203
|
+
}
|
|
204
|
+
supersededQuestions: questions(status: SUPERSEDED) {
|
|
205
|
+
id
|
|
206
|
+
status
|
|
207
|
+
question
|
|
208
|
+
rationale
|
|
209
|
+
askOf {
|
|
210
|
+
actorId
|
|
211
|
+
externalId
|
|
212
|
+
userId
|
|
213
|
+
}
|
|
214
|
+
suggestedAnswers
|
|
215
|
+
answerText
|
|
216
|
+
answeredAt
|
|
217
|
+
answeredByUserId
|
|
218
|
+
createdAt
|
|
219
|
+
updatedAt
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
const CREATE_WORKSTREAM_QUESTION_MUTATION = `
|
|
225
|
+
mutation CliCreateWorkstreamQuestion($input: CreateWorkstreamQuestionInput!) {
|
|
226
|
+
createWorkstreamQuestion(input: $input) {
|
|
227
|
+
id
|
|
228
|
+
workstreamId
|
|
229
|
+
status
|
|
230
|
+
question
|
|
231
|
+
rationale
|
|
232
|
+
askOf {
|
|
233
|
+
actorId
|
|
234
|
+
externalId
|
|
235
|
+
userId
|
|
236
|
+
}
|
|
237
|
+
suggestedAnswers
|
|
238
|
+
createdAt
|
|
239
|
+
updatedAt
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
const ANSWER_WORKSTREAM_QUESTION_MUTATION = `
|
|
244
|
+
mutation CliAnswerWorkstreamQuestion($questionId: ID!, $answerText: String!) {
|
|
245
|
+
answerWorkstreamQuestion(questionId: $questionId, answerText: $answerText) {
|
|
246
|
+
id
|
|
247
|
+
workstreamId
|
|
248
|
+
status
|
|
249
|
+
question
|
|
250
|
+
answerText
|
|
251
|
+
answeredAt
|
|
252
|
+
answeredByUserId
|
|
253
|
+
updatedAt
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const ADD_WORKSTREAM_ATTACHMENT_MUTATION = `
|
|
258
|
+
mutation CliAddWorkstreamAttachment(
|
|
259
|
+
$workstreamId: ID!
|
|
260
|
+
$kind: WorkstreamAttachmentKind!
|
|
261
|
+
$entityId: ID!
|
|
262
|
+
) {
|
|
263
|
+
addWorkstreamAttachment(
|
|
264
|
+
workstreamId: $workstreamId
|
|
265
|
+
kind: $kind
|
|
266
|
+
entityId: $entityId
|
|
267
|
+
) {
|
|
268
|
+
id
|
|
269
|
+
cornerId
|
|
270
|
+
updatedAt
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
const REPLY_ARTIFACT_THREAD_MUTATION = `
|
|
275
|
+
mutation CliReplyArtifactThread($id: ID!, $input: ReplyArtifactThreadInput!) {
|
|
276
|
+
replyArtifactThread(id: $id, input: $input) {
|
|
277
|
+
ok
|
|
278
|
+
providerType
|
|
279
|
+
threadContainerId
|
|
280
|
+
channelContainerId
|
|
281
|
+
dispatchedExternalId
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
`;
|
|
285
|
+
const CREATE_DOCUMENT_MUTATION = `
|
|
286
|
+
mutation CliCreateDocument($input: CreateDocumentInput!) {
|
|
287
|
+
createDocument(input: $input) {
|
|
288
|
+
id
|
|
289
|
+
title
|
|
290
|
+
updatedAt
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
`;
|
|
294
|
+
const RECORD_WORKSTREAM_UPDATE_MUTATION = `
|
|
295
|
+
mutation CliRecordWorkstreamUpdate($input: RecordWorkstreamUpdateInput!) {
|
|
296
|
+
recordWorkstreamUpdate(input: $input) {
|
|
297
|
+
id
|
|
298
|
+
eventType
|
|
299
|
+
sourceEntityType
|
|
300
|
+
sourceEntityId
|
|
301
|
+
payload
|
|
302
|
+
createdAt
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
`;
|
|
306
|
+
const REVOKE_MCP_SESSION_MUTATION = `
|
|
307
|
+
mutation CliRevokeMcpSession($id: ID!) {
|
|
308
|
+
revokeMCPSession(id: $id)
|
|
309
|
+
}
|
|
310
|
+
`;
|
|
311
|
+
function createRuntime() {
|
|
312
|
+
return {
|
|
313
|
+
config: new ConfigStore(),
|
|
314
|
+
stdout: process.stdout,
|
|
315
|
+
stderr: process.stderr,
|
|
316
|
+
stdin: process.stdin,
|
|
317
|
+
cwd: process.cwd(),
|
|
318
|
+
openUrl: openUrlInBrowser,
|
|
319
|
+
createClient: (input) => new DefaultCornersApiClient(input),
|
|
320
|
+
getVersion: getPackageVersion,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function printMainHelp(runtime) {
|
|
324
|
+
printLine(runtime.stdout, [
|
|
325
|
+
"corners",
|
|
326
|
+
"",
|
|
327
|
+
"Usage:",
|
|
328
|
+
" corners [--profile <name>] [--api-url <url>] <command> [options]",
|
|
329
|
+
"",
|
|
330
|
+
"Commands:",
|
|
331
|
+
" auth login|logout|status",
|
|
332
|
+
" workstream (ws) list|use|current|pull|push|question|attach|reply-thread",
|
|
333
|
+
" help",
|
|
334
|
+
" version",
|
|
335
|
+
"",
|
|
336
|
+
"Global options:",
|
|
337
|
+
" --help, -h",
|
|
338
|
+
" --version, -v",
|
|
339
|
+
" --json",
|
|
340
|
+
" --profile <name>",
|
|
341
|
+
" --api-url <url>",
|
|
342
|
+
" --no-browser",
|
|
343
|
+
].join("\n"));
|
|
344
|
+
}
|
|
345
|
+
function printAuthHelp(runtime) {
|
|
346
|
+
printLine(runtime.stdout, [
|
|
347
|
+
"corners auth",
|
|
348
|
+
"",
|
|
349
|
+
"Usage:",
|
|
350
|
+
" corners auth login [--profile <name>] [--api-url <url>] [--no-browser]",
|
|
351
|
+
" corners auth logout [--profile <name>] [--json]",
|
|
352
|
+
" corners auth status [--profile <name>] [--json]",
|
|
353
|
+
].join("\n"));
|
|
354
|
+
}
|
|
355
|
+
function printWorkstreamHelp(runtime) {
|
|
356
|
+
printLine(runtime.stdout, [
|
|
357
|
+
"corners workstream",
|
|
358
|
+
"",
|
|
359
|
+
"Usage:",
|
|
360
|
+
" corners workstream list [--json]",
|
|
361
|
+
" corners workstream use <workstreamId> [--json]",
|
|
362
|
+
" corners workstream current [--json]",
|
|
363
|
+
" corners workstream pull [workstreamId] [--json]",
|
|
364
|
+
" corners workstream push [workstreamId] [--type <type>] [--message <text>] [--summary <text>] [--file <path>] [--title <title>] [--json]",
|
|
365
|
+
" corners workstream question list [workstreamId] [--json]",
|
|
366
|
+
" corners workstream question ask [workstreamId] [--question <text>] [--rationale <text>] [--suggested-answer <text>]... [--json]",
|
|
367
|
+
" corners workstream question answer <questionId> [--text <text>] [--json]",
|
|
368
|
+
" corners workstream attach [workstreamId] --kind <kind> --entity-id <id> [--json]",
|
|
369
|
+
" corners workstream reply-thread <threadId> [--text <text>] [--json]",
|
|
370
|
+
"",
|
|
371
|
+
"Update types:",
|
|
372
|
+
" status | blocker | learning | outcome",
|
|
373
|
+
].join("\n"));
|
|
374
|
+
}
|
|
375
|
+
function parseLeadingCommonOptions(args) {
|
|
376
|
+
const common = {
|
|
377
|
+
json: false,
|
|
378
|
+
noBrowser: false,
|
|
379
|
+
};
|
|
380
|
+
let help = false;
|
|
381
|
+
let version = false;
|
|
382
|
+
let index = 0;
|
|
383
|
+
while (index < args.length) {
|
|
384
|
+
const value = args[index];
|
|
385
|
+
if (!value?.startsWith("-")) {
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
switch (value) {
|
|
389
|
+
case "--json":
|
|
390
|
+
common.json = true;
|
|
391
|
+
index += 1;
|
|
392
|
+
break;
|
|
393
|
+
case "--no-browser":
|
|
394
|
+
common.noBrowser = true;
|
|
395
|
+
index += 1;
|
|
396
|
+
break;
|
|
397
|
+
case "--api-url":
|
|
398
|
+
if (!args[index + 1]) {
|
|
399
|
+
throw new CLIError("Missing value for --api-url");
|
|
400
|
+
}
|
|
401
|
+
common.apiUrl = args[index + 1];
|
|
402
|
+
index += 2;
|
|
403
|
+
break;
|
|
404
|
+
case "--profile":
|
|
405
|
+
if (!args[index + 1]) {
|
|
406
|
+
throw new CLIError("Missing value for --profile");
|
|
407
|
+
}
|
|
408
|
+
common.profile = args[index + 1];
|
|
409
|
+
index += 2;
|
|
410
|
+
break;
|
|
411
|
+
case "--help":
|
|
412
|
+
case "-h":
|
|
413
|
+
help = true;
|
|
414
|
+
index += 1;
|
|
415
|
+
break;
|
|
416
|
+
case "--version":
|
|
417
|
+
case "-v":
|
|
418
|
+
version = true;
|
|
419
|
+
index += 1;
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
return {
|
|
423
|
+
common,
|
|
424
|
+
rest: args.slice(index),
|
|
425
|
+
help,
|
|
426
|
+
version,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
common,
|
|
432
|
+
rest: args.slice(index),
|
|
433
|
+
help,
|
|
434
|
+
version,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function mergeCommonOptions(base, values) {
|
|
438
|
+
return {
|
|
439
|
+
json: values.json ?? base.json,
|
|
440
|
+
apiUrl: values.apiUrl ?? base.apiUrl,
|
|
441
|
+
profile: values.profile ?? base.profile,
|
|
442
|
+
noBrowser: values.noBrowser ?? base.noBrowser,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async function requireStoredProfile(runtime, common) {
|
|
446
|
+
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
447
|
+
if (!selected) {
|
|
448
|
+
throw new CLIError("Not logged in. Run `corners auth login` first.", {
|
|
449
|
+
json: common.json,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
const profile = {
|
|
453
|
+
...selected.profile,
|
|
454
|
+
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
455
|
+
};
|
|
456
|
+
return {
|
|
457
|
+
profileName: selected.name,
|
|
458
|
+
profile,
|
|
459
|
+
client: runtime.createClient({
|
|
460
|
+
apiUrl: profile.apiUrl,
|
|
461
|
+
accessToken: profile.accessToken,
|
|
462
|
+
}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async function pollForDeviceToken(input) {
|
|
466
|
+
const deadline = Date.now() + input.device.expires_in * 1000;
|
|
467
|
+
let intervalMs = input.device.interval * 1000;
|
|
468
|
+
while (Date.now() < deadline) {
|
|
469
|
+
const result = await input.client.exchangeDeviceCode(input.device.device_code, input.clientId);
|
|
470
|
+
if (result.access_token) {
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
switch (result.error) {
|
|
474
|
+
case "authorization_pending":
|
|
475
|
+
await sleep(intervalMs);
|
|
476
|
+
continue;
|
|
477
|
+
case "slow_down":
|
|
478
|
+
intervalMs += 5000;
|
|
479
|
+
await sleep(intervalMs);
|
|
480
|
+
continue;
|
|
481
|
+
case "access_denied":
|
|
482
|
+
throw new CLIError(result.error_description || "Authorization denied");
|
|
483
|
+
case "expired_token":
|
|
484
|
+
throw new CLIError(result.error_description || "Device code expired");
|
|
485
|
+
default:
|
|
486
|
+
throw new CLIError(result.error_description || "Failed to exchange device code");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
printLine(input.stderr, "Device code expired before authorization completed.");
|
|
490
|
+
throw new CLIError("Device code expired");
|
|
491
|
+
}
|
|
492
|
+
async function resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, explicitWorkstreamId) {
|
|
493
|
+
const workstreamId = explicitWorkstreamId ||
|
|
494
|
+
(await resolveBoundWorkstreamId(runtime, profileName));
|
|
495
|
+
const data = await client.graphql(WORKSTREAM_LOOKUP_QUERY, { id: workstreamId });
|
|
496
|
+
if (!data.workstream) {
|
|
497
|
+
throw new CLIError("Workstream not found", { json: common.json });
|
|
498
|
+
}
|
|
499
|
+
return data.workstream;
|
|
500
|
+
}
|
|
501
|
+
async function resolveBoundWorkstreamId(runtime, profileName) {
|
|
502
|
+
const binding = await runtime.config.getBinding(runtime.cwd);
|
|
503
|
+
if (!binding) {
|
|
504
|
+
throw new CLIError("This directory is not bound to a workstream. Run `corners workstream use <workstreamId>` first.");
|
|
505
|
+
}
|
|
506
|
+
if (binding.profile && binding.profile !== profileName) {
|
|
507
|
+
throw new CLIError(`This directory is bound to profile ${binding.profile}. Switch profiles or rebind the directory.`);
|
|
508
|
+
}
|
|
509
|
+
return binding.workstreamId;
|
|
510
|
+
}
|
|
511
|
+
function maybeTakeWorkstreamId(positionals) {
|
|
512
|
+
if (positionals[0]?.startsWith("ws_")) {
|
|
513
|
+
return {
|
|
514
|
+
workstreamId: positionals[0],
|
|
515
|
+
rest: positionals.slice(1),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
rest: positionals,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async function handleAuth(args, runtime, inherited) {
|
|
523
|
+
const subcommand = args[0];
|
|
524
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
525
|
+
printAuthHelp(runtime);
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
switch (subcommand) {
|
|
529
|
+
case "login": {
|
|
530
|
+
const parsed = parseArgs({
|
|
531
|
+
args: args.slice(1),
|
|
532
|
+
allowPositionals: false,
|
|
533
|
+
options: {
|
|
534
|
+
json: { type: "boolean" },
|
|
535
|
+
help: { type: "boolean", short: "h" },
|
|
536
|
+
profile: { type: "string" },
|
|
537
|
+
"api-url": { type: "string" },
|
|
538
|
+
"no-browser": { type: "boolean" },
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
const common = mergeCommonOptions(inherited, {
|
|
542
|
+
json: parsed.values.json,
|
|
543
|
+
profile: parsed.values.profile,
|
|
544
|
+
apiUrl: parsed.values["api-url"],
|
|
545
|
+
noBrowser: parsed.values["no-browser"],
|
|
546
|
+
});
|
|
547
|
+
if (parsed.values.help) {
|
|
548
|
+
printAuthHelp(runtime);
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
const existing = await runtime.config.getProfile(common.profile ?? null);
|
|
552
|
+
const profileName = common.profile ?? existing?.name ?? "default";
|
|
553
|
+
const clientId = `Corners CLI (${hostname()})`;
|
|
554
|
+
const client = runtime.createClient({
|
|
555
|
+
apiUrl: common.apiUrl ?? existing?.profile.apiUrl,
|
|
556
|
+
});
|
|
557
|
+
if (!common.json) {
|
|
558
|
+
printLine(runtime.stderr, `Requesting device authorization from ${client.apiUrl}...`);
|
|
559
|
+
}
|
|
560
|
+
const device = await client.createDeviceAuthorization(clientId);
|
|
561
|
+
if (!common.json) {
|
|
562
|
+
printLine(runtime.stderr, `Approve this login at ${device.verification_uri} with code ${device.user_code}`);
|
|
563
|
+
}
|
|
564
|
+
if (!common.noBrowser) {
|
|
565
|
+
const opened = await runtime.openUrl(device.verification_uri_complete);
|
|
566
|
+
if (!common.json && opened) {
|
|
567
|
+
printLine(runtime.stderr, "Opened the verification page in your browser.");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!common.json) {
|
|
571
|
+
printLine(runtime.stderr, "Waiting for device approval...");
|
|
572
|
+
}
|
|
573
|
+
const token = await pollForDeviceToken({
|
|
574
|
+
client,
|
|
575
|
+
device,
|
|
576
|
+
clientId,
|
|
577
|
+
stderr: runtime.stderr,
|
|
578
|
+
});
|
|
579
|
+
if (!token.access_token) {
|
|
580
|
+
throw new CLIError("Login completed without an access token");
|
|
581
|
+
}
|
|
582
|
+
const profile = {
|
|
583
|
+
apiUrl: client.apiUrl,
|
|
584
|
+
accessToken: token.access_token,
|
|
585
|
+
sessionId: token.session_id ?? null,
|
|
586
|
+
workspace: token.workspace ?? null,
|
|
587
|
+
accountId: token.account_id ?? null,
|
|
588
|
+
userId: token.user_id ?? null,
|
|
589
|
+
createdAt: toIsoString(),
|
|
590
|
+
};
|
|
591
|
+
await runtime.config.saveProfile(profileName, profile, {
|
|
592
|
+
setActive: true,
|
|
593
|
+
});
|
|
594
|
+
if (common.json) {
|
|
595
|
+
printJson(runtime.stdout, {
|
|
596
|
+
ok: true,
|
|
597
|
+
profile: profileName,
|
|
598
|
+
apiUrl: profile.apiUrl,
|
|
599
|
+
sessionId: profile.sessionId,
|
|
600
|
+
workspace: profile.workspace,
|
|
601
|
+
accountId: profile.accountId,
|
|
602
|
+
userId: profile.userId,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
printLine(runtime.stdout, `Logged in as profile ${profileName}${profile.workspace ? ` (${profile.workspace})` : ""}.`);
|
|
607
|
+
}
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
case "logout": {
|
|
611
|
+
const parsed = parseArgs({
|
|
612
|
+
args: args.slice(1),
|
|
613
|
+
allowPositionals: false,
|
|
614
|
+
options: {
|
|
615
|
+
json: { type: "boolean" },
|
|
616
|
+
help: { type: "boolean", short: "h" },
|
|
617
|
+
profile: { type: "string" },
|
|
618
|
+
"api-url": { type: "string" },
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
const common = mergeCommonOptions(inherited, {
|
|
622
|
+
json: parsed.values.json,
|
|
623
|
+
profile: parsed.values.profile,
|
|
624
|
+
apiUrl: parsed.values["api-url"],
|
|
625
|
+
});
|
|
626
|
+
if (parsed.values.help) {
|
|
627
|
+
printAuthHelp(runtime);
|
|
628
|
+
return 0;
|
|
629
|
+
}
|
|
630
|
+
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
631
|
+
if (!selected) {
|
|
632
|
+
if (common.json) {
|
|
633
|
+
printJson(runtime.stdout, {
|
|
634
|
+
ok: true,
|
|
635
|
+
revoked: false,
|
|
636
|
+
loggedIn: false,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
printLine(runtime.stdout, "No stored profile is logged in.");
|
|
641
|
+
}
|
|
642
|
+
return 0;
|
|
643
|
+
}
|
|
644
|
+
let revoked = false;
|
|
645
|
+
if (selected.profile.sessionId && selected.profile.accessToken) {
|
|
646
|
+
try {
|
|
647
|
+
const client = runtime.createClient({
|
|
648
|
+
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
649
|
+
accessToken: selected.profile.accessToken,
|
|
650
|
+
});
|
|
651
|
+
const data = await client.graphql(REVOKE_MCP_SESSION_MUTATION, {
|
|
652
|
+
id: selected.profile.sessionId,
|
|
653
|
+
});
|
|
654
|
+
revoked = data.revokeMCPSession;
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
if (!common.json) {
|
|
658
|
+
printLine(runtime.stderr, `Warning: failed to revoke the remote session (${error.message}). Clearing local credentials anyway.`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
await runtime.config.removeProfile(selected.name);
|
|
663
|
+
await runtime.config.clearBindingsForProfile(selected.name);
|
|
664
|
+
if (common.json) {
|
|
665
|
+
printJson(runtime.stdout, {
|
|
666
|
+
ok: true,
|
|
667
|
+
revoked,
|
|
668
|
+
profile: selected.name,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
printLine(runtime.stdout, `Logged out profile ${selected.name}.`);
|
|
673
|
+
}
|
|
674
|
+
return 0;
|
|
675
|
+
}
|
|
676
|
+
case "status": {
|
|
677
|
+
const parsed = parseArgs({
|
|
678
|
+
args: args.slice(1),
|
|
679
|
+
allowPositionals: false,
|
|
680
|
+
options: {
|
|
681
|
+
json: { type: "boolean" },
|
|
682
|
+
help: { type: "boolean", short: "h" },
|
|
683
|
+
profile: { type: "string" },
|
|
684
|
+
"api-url": { type: "string" },
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
const common = mergeCommonOptions(inherited, {
|
|
688
|
+
json: parsed.values.json,
|
|
689
|
+
profile: parsed.values.profile,
|
|
690
|
+
apiUrl: parsed.values["api-url"],
|
|
691
|
+
});
|
|
692
|
+
if (parsed.values.help) {
|
|
693
|
+
printAuthHelp(runtime);
|
|
694
|
+
return 0;
|
|
695
|
+
}
|
|
696
|
+
const selected = await runtime.config.getProfile(common.profile ?? null);
|
|
697
|
+
if (!selected) {
|
|
698
|
+
const status = {
|
|
699
|
+
loggedIn: false,
|
|
700
|
+
profile: common.profile ?? null,
|
|
701
|
+
};
|
|
702
|
+
if (common.json) {
|
|
703
|
+
printJson(runtime.stdout, status);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
printLine(runtime.stdout, "Not logged in.");
|
|
707
|
+
}
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
710
|
+
const client = runtime.createClient({
|
|
711
|
+
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
712
|
+
accessToken: selected.profile.accessToken,
|
|
713
|
+
});
|
|
714
|
+
let remote;
|
|
715
|
+
try {
|
|
716
|
+
remote = await client.validateSession();
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
remote = {
|
|
720
|
+
valid: false,
|
|
721
|
+
error: error.message,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const status = {
|
|
725
|
+
loggedIn: true,
|
|
726
|
+
profile: selected.name,
|
|
727
|
+
apiUrl: common.apiUrl ?? selected.profile.apiUrl,
|
|
728
|
+
sessionId: selected.profile.sessionId,
|
|
729
|
+
workspace: selected.profile.workspace,
|
|
730
|
+
accountId: selected.profile.accountId,
|
|
731
|
+
userId: selected.profile.userId,
|
|
732
|
+
remoteValid: remote.valid,
|
|
733
|
+
remoteError: remote.valid ? null : (remote.error ?? null),
|
|
734
|
+
};
|
|
735
|
+
if (common.json) {
|
|
736
|
+
printJson(runtime.stdout, status);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
printLine(runtime.stdout, [
|
|
740
|
+
`Profile: ${status.profile}`,
|
|
741
|
+
`API URL: ${status.apiUrl}`,
|
|
742
|
+
`Workspace: ${status.workspace ?? "-"}`,
|
|
743
|
+
`Account: ${status.accountId ?? "-"}`,
|
|
744
|
+
`User: ${status.userId ?? "-"}`,
|
|
745
|
+
`Session: ${status.sessionId ?? "-"}`,
|
|
746
|
+
`Remote session: ${status.remoteValid ? "valid" : `invalid (${status.remoteError ?? "unknown error"})`}`,
|
|
747
|
+
].join("\n"));
|
|
748
|
+
}
|
|
749
|
+
return 0;
|
|
750
|
+
}
|
|
751
|
+
default:
|
|
752
|
+
throw new CLIError(`Unknown auth command: ${subcommand}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function handleWorkstream(args, runtime, inherited) {
|
|
756
|
+
const subcommand = args[0];
|
|
757
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
758
|
+
printWorkstreamHelp(runtime);
|
|
759
|
+
return 0;
|
|
760
|
+
}
|
|
761
|
+
switch (subcommand) {
|
|
762
|
+
case "list": {
|
|
763
|
+
const parsed = parseArgs({
|
|
764
|
+
args: args.slice(1),
|
|
765
|
+
allowPositionals: false,
|
|
766
|
+
options: {
|
|
767
|
+
json: { type: "boolean" },
|
|
768
|
+
help: { type: "boolean", short: "h" },
|
|
769
|
+
profile: { type: "string" },
|
|
770
|
+
"api-url": { type: "string" },
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
const common = mergeCommonOptions(inherited, {
|
|
774
|
+
json: parsed.values.json,
|
|
775
|
+
profile: parsed.values.profile,
|
|
776
|
+
apiUrl: parsed.values["api-url"],
|
|
777
|
+
});
|
|
778
|
+
if (parsed.values.help) {
|
|
779
|
+
printWorkstreamHelp(runtime);
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
const { client } = await requireStoredProfile(runtime, common);
|
|
783
|
+
const data = await client.graphql(LIST_WORKSTREAMS_QUERY);
|
|
784
|
+
const workstreams = data.workstreams.edges.map((edge) => edge.node);
|
|
785
|
+
if (common.json) {
|
|
786
|
+
printJson(runtime.stdout, workstreams);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
printLine(runtime.stdout, workstreams
|
|
790
|
+
.map((workstream) => `${workstream.id} ${workstream.name} ${workstream.status.toLowerCase()}${workstream.summary ? ` ${workstream.summary}` : ""}`)
|
|
791
|
+
.join("\n"));
|
|
792
|
+
}
|
|
793
|
+
return 0;
|
|
794
|
+
}
|
|
795
|
+
case "use": {
|
|
796
|
+
const parsed = parseArgs({
|
|
797
|
+
args: args.slice(1),
|
|
798
|
+
allowPositionals: true,
|
|
799
|
+
options: {
|
|
800
|
+
json: { type: "boolean" },
|
|
801
|
+
help: { type: "boolean", short: "h" },
|
|
802
|
+
profile: { type: "string" },
|
|
803
|
+
"api-url": { type: "string" },
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
const common = mergeCommonOptions(inherited, {
|
|
807
|
+
json: parsed.values.json,
|
|
808
|
+
profile: parsed.values.profile,
|
|
809
|
+
apiUrl: parsed.values["api-url"],
|
|
810
|
+
});
|
|
811
|
+
if (parsed.values.help) {
|
|
812
|
+
printWorkstreamHelp(runtime);
|
|
813
|
+
return 0;
|
|
814
|
+
}
|
|
815
|
+
const workstreamId = parsed.positionals[0];
|
|
816
|
+
if (!workstreamId) {
|
|
817
|
+
throw new CLIError("Usage: corners workstream use <workstreamId>", {
|
|
818
|
+
json: common.json,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
const { profileName, profile, client } = await requireStoredProfile(runtime, common);
|
|
822
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
823
|
+
await runtime.config.setBinding(runtime.cwd, {
|
|
824
|
+
profile: profileName,
|
|
825
|
+
workspace: profile.workspace ?? "",
|
|
826
|
+
workstreamId: workstream.id,
|
|
827
|
+
cornerId: workstream.cornerId,
|
|
828
|
+
boundAt: toIsoString(),
|
|
829
|
+
});
|
|
830
|
+
if (common.json) {
|
|
831
|
+
printJson(runtime.stdout, {
|
|
832
|
+
ok: true,
|
|
833
|
+
cwd: runtime.cwd,
|
|
834
|
+
profile: profileName,
|
|
835
|
+
workspace: profile.workspace,
|
|
836
|
+
workstream,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
printLine(runtime.stdout, `Bound ${runtime.cwd} to ${workstream.id} (${workstream.name}).`);
|
|
841
|
+
}
|
|
842
|
+
return 0;
|
|
843
|
+
}
|
|
844
|
+
case "current": {
|
|
845
|
+
const parsed = parseArgs({
|
|
846
|
+
args: args.slice(1),
|
|
847
|
+
allowPositionals: false,
|
|
848
|
+
options: {
|
|
849
|
+
json: { type: "boolean" },
|
|
850
|
+
help: { type: "boolean", short: "h" },
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
const common = mergeCommonOptions(inherited, {
|
|
854
|
+
json: parsed.values.json,
|
|
855
|
+
});
|
|
856
|
+
if (parsed.values.help) {
|
|
857
|
+
printWorkstreamHelp(runtime);
|
|
858
|
+
return 0;
|
|
859
|
+
}
|
|
860
|
+
const binding = await runtime.config.getBinding(runtime.cwd);
|
|
861
|
+
if (!binding) {
|
|
862
|
+
if (common.json) {
|
|
863
|
+
printJson(runtime.stdout, { cwd: runtime.cwd, binding: null });
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
printLine(runtime.stdout, "No workstream is bound to this directory.");
|
|
867
|
+
}
|
|
868
|
+
return 0;
|
|
869
|
+
}
|
|
870
|
+
if (common.json) {
|
|
871
|
+
printJson(runtime.stdout, {
|
|
872
|
+
cwd: runtime.cwd,
|
|
873
|
+
binding,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
printLine(runtime.stdout, [
|
|
878
|
+
`Directory: ${runtime.cwd}`,
|
|
879
|
+
`Profile: ${binding.profile ?? "-"}`,
|
|
880
|
+
`Workspace: ${binding.workspace || "-"}`,
|
|
881
|
+
`Workstream: ${binding.workstreamId}`,
|
|
882
|
+
`Corner: ${binding.cornerId}`,
|
|
883
|
+
].join("\n"));
|
|
884
|
+
}
|
|
885
|
+
return 0;
|
|
886
|
+
}
|
|
887
|
+
case "pull": {
|
|
888
|
+
const parsed = parseArgs({
|
|
889
|
+
args: args.slice(1),
|
|
890
|
+
allowPositionals: true,
|
|
891
|
+
options: {
|
|
892
|
+
json: { type: "boolean" },
|
|
893
|
+
help: { type: "boolean", short: "h" },
|
|
894
|
+
profile: { type: "string" },
|
|
895
|
+
"api-url": { type: "string" },
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
const common = mergeCommonOptions(inherited, {
|
|
899
|
+
json: parsed.values.json,
|
|
900
|
+
profile: parsed.values.profile,
|
|
901
|
+
apiUrl: parsed.values["api-url"],
|
|
902
|
+
});
|
|
903
|
+
if (parsed.values.help) {
|
|
904
|
+
printWorkstreamHelp(runtime);
|
|
905
|
+
return 0;
|
|
906
|
+
}
|
|
907
|
+
const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
|
|
908
|
+
const { profileName, client } = await requireStoredProfile(runtime, common);
|
|
909
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
910
|
+
const data = await client.graphql(WORKSTREAM_PULL_QUERY, {
|
|
911
|
+
id: workstream.id,
|
|
912
|
+
attachmentsFirst: 50,
|
|
913
|
+
feedFirst: 20,
|
|
914
|
+
});
|
|
915
|
+
if (common.json) {
|
|
916
|
+
printJson(runtime.stdout, data.workstream);
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
const snapshot = data.workstream;
|
|
920
|
+
printLine(runtime.stdout, [
|
|
921
|
+
`${snapshot.name} (${snapshot.id})`,
|
|
922
|
+
snapshot.summary ? `Summary: ${snapshot.summary}` : "Summary: -",
|
|
923
|
+
`Open questions: ${snapshot.openQuestions?.length ?? 0}`,
|
|
924
|
+
`Answered questions: ${snapshot.answeredQuestions?.length ?? 0}`,
|
|
925
|
+
`Attachments: ${snapshot.attachments?.totalCount ?? 0}`,
|
|
926
|
+
`Feed items returned: ${snapshot.feed?.totalCount ?? 0}`,
|
|
927
|
+
].join("\n"));
|
|
928
|
+
}
|
|
929
|
+
return 0;
|
|
930
|
+
}
|
|
931
|
+
case "push": {
|
|
932
|
+
const parsed = parseArgs({
|
|
933
|
+
args: args.slice(1),
|
|
934
|
+
allowPositionals: true,
|
|
935
|
+
options: {
|
|
936
|
+
json: { type: "boolean" },
|
|
937
|
+
help: { type: "boolean", short: "h" },
|
|
938
|
+
profile: { type: "string" },
|
|
939
|
+
"api-url": { type: "string" },
|
|
940
|
+
type: { type: "string" },
|
|
941
|
+
message: { type: "string" },
|
|
942
|
+
summary: { type: "string" },
|
|
943
|
+
file: { type: "string" },
|
|
944
|
+
title: { type: "string" },
|
|
945
|
+
},
|
|
946
|
+
});
|
|
947
|
+
const common = mergeCommonOptions(inherited, {
|
|
948
|
+
json: parsed.values.json,
|
|
949
|
+
profile: parsed.values.profile,
|
|
950
|
+
apiUrl: parsed.values["api-url"],
|
|
951
|
+
});
|
|
952
|
+
if (parsed.values.help) {
|
|
953
|
+
printWorkstreamHelp(runtime);
|
|
954
|
+
return 0;
|
|
955
|
+
}
|
|
956
|
+
const { workstreamId, rest } = maybeTakeWorkstreamId(parsed.positionals);
|
|
957
|
+
const message = parsed.values.message ??
|
|
958
|
+
(rest.length > 0
|
|
959
|
+
? rest.join(" ")
|
|
960
|
+
: await readTextFromStdin(runtime.stdin));
|
|
961
|
+
if (!message) {
|
|
962
|
+
throw new CLIError("Workstream updates need a message. Use --message or pipe text on stdin.", { json: common.json });
|
|
963
|
+
}
|
|
964
|
+
const { profileName, client } = await requireStoredProfile(runtime, common);
|
|
965
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
966
|
+
let createdDocument = null;
|
|
967
|
+
if (parsed.values.file) {
|
|
968
|
+
const documentContent = await readFile(parsed.values.file, "utf8");
|
|
969
|
+
const title = parsed.values.title ?? basename(parsed.values.file);
|
|
970
|
+
const documentResult = await client.graphql(CREATE_DOCUMENT_MUTATION, {
|
|
971
|
+
input: {
|
|
972
|
+
title,
|
|
973
|
+
content: documentContent,
|
|
974
|
+
privacy: "ACCOUNT",
|
|
975
|
+
},
|
|
976
|
+
});
|
|
977
|
+
createdDocument = documentResult.createDocument;
|
|
978
|
+
}
|
|
979
|
+
const updateResult = await client.graphql(RECORD_WORKSTREAM_UPDATE_MUTATION, {
|
|
980
|
+
input: {
|
|
981
|
+
workstreamId: workstream.id,
|
|
982
|
+
updateType: toGraphQLWorkstreamUpdateType(parsed.values.type ?? "status"),
|
|
983
|
+
content: message,
|
|
984
|
+
summary: parsed.values.summary,
|
|
985
|
+
documentId: createdDocument?.id ?? null,
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
const payload = {
|
|
989
|
+
ok: true,
|
|
990
|
+
workstreamId: workstream.id,
|
|
991
|
+
update: updateResult.recordWorkstreamUpdate,
|
|
992
|
+
document: createdDocument,
|
|
993
|
+
};
|
|
994
|
+
if (common.json) {
|
|
995
|
+
printJson(runtime.stdout, payload);
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
printLine(runtime.stdout, `Recorded ${String(parsed.values.type ?? "status")} update on ${workstream.id}.`);
|
|
999
|
+
}
|
|
1000
|
+
return 0;
|
|
1001
|
+
}
|
|
1002
|
+
case "question": {
|
|
1003
|
+
const nested = args[1];
|
|
1004
|
+
if (!nested || nested === "help" || nested === "--help") {
|
|
1005
|
+
printWorkstreamHelp(runtime);
|
|
1006
|
+
return 0;
|
|
1007
|
+
}
|
|
1008
|
+
switch (nested) {
|
|
1009
|
+
case "list": {
|
|
1010
|
+
const parsed = parseArgs({
|
|
1011
|
+
args: args.slice(2),
|
|
1012
|
+
allowPositionals: true,
|
|
1013
|
+
options: {
|
|
1014
|
+
json: { type: "boolean" },
|
|
1015
|
+
help: { type: "boolean", short: "h" },
|
|
1016
|
+
profile: { type: "string" },
|
|
1017
|
+
"api-url": { type: "string" },
|
|
1018
|
+
},
|
|
1019
|
+
});
|
|
1020
|
+
const common = mergeCommonOptions(inherited, {
|
|
1021
|
+
json: parsed.values.json,
|
|
1022
|
+
profile: parsed.values.profile,
|
|
1023
|
+
apiUrl: parsed.values["api-url"],
|
|
1024
|
+
});
|
|
1025
|
+
if (parsed.values.help) {
|
|
1026
|
+
printWorkstreamHelp(runtime);
|
|
1027
|
+
return 0;
|
|
1028
|
+
}
|
|
1029
|
+
const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
|
|
1030
|
+
const { profileName, client } = await requireStoredProfile(runtime, common);
|
|
1031
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
1032
|
+
const data = await client.graphql(WORKSTREAM_QUESTION_LIST_QUERY, {
|
|
1033
|
+
id: workstream.id,
|
|
1034
|
+
});
|
|
1035
|
+
if (!data.workstream) {
|
|
1036
|
+
throw new CLIError("Workstream not found", { json: common.json });
|
|
1037
|
+
}
|
|
1038
|
+
if (common.json) {
|
|
1039
|
+
printJson(runtime.stdout, data.workstream);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
printLine(runtime.stdout, [
|
|
1043
|
+
`${data.workstream.name} (${data.workstream.id})`,
|
|
1044
|
+
`Open: ${data.workstream.openQuestions.length}`,
|
|
1045
|
+
`Answered: ${data.workstream.answeredQuestions.length}`,
|
|
1046
|
+
`Superseded: ${data.workstream.supersededQuestions.length}`,
|
|
1047
|
+
].join("\n"));
|
|
1048
|
+
}
|
|
1049
|
+
return 0;
|
|
1050
|
+
}
|
|
1051
|
+
case "ask": {
|
|
1052
|
+
const parsed = parseArgs({
|
|
1053
|
+
args: args.slice(2),
|
|
1054
|
+
allowPositionals: true,
|
|
1055
|
+
options: {
|
|
1056
|
+
json: { type: "boolean" },
|
|
1057
|
+
help: { type: "boolean", short: "h" },
|
|
1058
|
+
profile: { type: "string" },
|
|
1059
|
+
"api-url": { type: "string" },
|
|
1060
|
+
question: { type: "string" },
|
|
1061
|
+
rationale: { type: "string" },
|
|
1062
|
+
"suggested-answer": { type: "string", multiple: true },
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
1065
|
+
const common = mergeCommonOptions(inherited, {
|
|
1066
|
+
json: parsed.values.json,
|
|
1067
|
+
profile: parsed.values.profile,
|
|
1068
|
+
apiUrl: parsed.values["api-url"],
|
|
1069
|
+
});
|
|
1070
|
+
if (parsed.values.help) {
|
|
1071
|
+
printWorkstreamHelp(runtime);
|
|
1072
|
+
return 0;
|
|
1073
|
+
}
|
|
1074
|
+
const { workstreamId, rest } = maybeTakeWorkstreamId(parsed.positionals);
|
|
1075
|
+
const question = parsed.values.question ??
|
|
1076
|
+
(rest.length > 0
|
|
1077
|
+
? rest.join(" ")
|
|
1078
|
+
: await readTextFromStdin(runtime.stdin));
|
|
1079
|
+
if (!question) {
|
|
1080
|
+
throw new CLIError("Question text is required. Use --question or pipe text on stdin.", { json: common.json });
|
|
1081
|
+
}
|
|
1082
|
+
const { profileName, client } = await requireStoredProfile(runtime, common);
|
|
1083
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
1084
|
+
const result = await client.graphql(CREATE_WORKSTREAM_QUESTION_MUTATION, {
|
|
1085
|
+
input: {
|
|
1086
|
+
workstreamId: workstream.id,
|
|
1087
|
+
question,
|
|
1088
|
+
rationale: parsed.values.rationale,
|
|
1089
|
+
suggestedAnswers: parsed.values["suggested-answer"] ?? [],
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
if (common.json) {
|
|
1093
|
+
printJson(runtime.stdout, result.createWorkstreamQuestion);
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
printLine(runtime.stdout, `Created question on ${workstream.id}.`);
|
|
1097
|
+
}
|
|
1098
|
+
return 0;
|
|
1099
|
+
}
|
|
1100
|
+
case "answer": {
|
|
1101
|
+
const parsed = parseArgs({
|
|
1102
|
+
args: args.slice(2),
|
|
1103
|
+
allowPositionals: true,
|
|
1104
|
+
options: {
|
|
1105
|
+
json: { type: "boolean" },
|
|
1106
|
+
help: { type: "boolean", short: "h" },
|
|
1107
|
+
profile: { type: "string" },
|
|
1108
|
+
"api-url": { type: "string" },
|
|
1109
|
+
text: { type: "string" },
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
const common = mergeCommonOptions(inherited, {
|
|
1113
|
+
json: parsed.values.json,
|
|
1114
|
+
profile: parsed.values.profile,
|
|
1115
|
+
apiUrl: parsed.values["api-url"],
|
|
1116
|
+
});
|
|
1117
|
+
if (parsed.values.help) {
|
|
1118
|
+
printWorkstreamHelp(runtime);
|
|
1119
|
+
return 0;
|
|
1120
|
+
}
|
|
1121
|
+
const questionId = parsed.positionals[0];
|
|
1122
|
+
const answer = parsed.values.text ??
|
|
1123
|
+
(parsed.positionals.length > 1
|
|
1124
|
+
? parsed.positionals.slice(1).join(" ")
|
|
1125
|
+
: await readTextFromStdin(runtime.stdin));
|
|
1126
|
+
if (!questionId) {
|
|
1127
|
+
throw new CLIError("Usage: corners workstream question answer <questionId> [--text <text>]", { json: common.json });
|
|
1128
|
+
}
|
|
1129
|
+
if (!answer) {
|
|
1130
|
+
throw new CLIError("Answer text is required.", {
|
|
1131
|
+
json: common.json,
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
const { client } = await requireStoredProfile(runtime, common);
|
|
1135
|
+
const result = await client.graphql(ANSWER_WORKSTREAM_QUESTION_MUTATION, {
|
|
1136
|
+
questionId,
|
|
1137
|
+
answerText: answer,
|
|
1138
|
+
});
|
|
1139
|
+
if (common.json) {
|
|
1140
|
+
printJson(runtime.stdout, result.answerWorkstreamQuestion);
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
printLine(runtime.stdout, `Answered ${questionId}.`);
|
|
1144
|
+
}
|
|
1145
|
+
return 0;
|
|
1146
|
+
}
|
|
1147
|
+
default:
|
|
1148
|
+
throw new CLIError(`Unknown workstream question command: ${nested}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
case "attach": {
|
|
1152
|
+
const parsed = parseArgs({
|
|
1153
|
+
args: args.slice(1),
|
|
1154
|
+
allowPositionals: true,
|
|
1155
|
+
options: {
|
|
1156
|
+
json: { type: "boolean" },
|
|
1157
|
+
help: { type: "boolean", short: "h" },
|
|
1158
|
+
profile: { type: "string" },
|
|
1159
|
+
"api-url": { type: "string" },
|
|
1160
|
+
kind: { type: "string" },
|
|
1161
|
+
"entity-id": { type: "string" },
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
const common = mergeCommonOptions(inherited, {
|
|
1165
|
+
json: parsed.values.json,
|
|
1166
|
+
profile: parsed.values.profile,
|
|
1167
|
+
apiUrl: parsed.values["api-url"],
|
|
1168
|
+
});
|
|
1169
|
+
if (parsed.values.help) {
|
|
1170
|
+
printWorkstreamHelp(runtime);
|
|
1171
|
+
return 0;
|
|
1172
|
+
}
|
|
1173
|
+
const { workstreamId } = maybeTakeWorkstreamId(parsed.positionals);
|
|
1174
|
+
const kind = parsed.values.kind;
|
|
1175
|
+
const entityId = parsed.values["entity-id"];
|
|
1176
|
+
if (!kind || !entityId) {
|
|
1177
|
+
throw new CLIError("Usage: corners workstream attach [workstreamId] --kind <kind> --entity-id <id>", { json: common.json });
|
|
1178
|
+
}
|
|
1179
|
+
const { profileName, client } = await requireStoredProfile(runtime, common);
|
|
1180
|
+
const workstream = await resolveExplicitOrBoundWorkstream(runtime, common, client, profileName, workstreamId);
|
|
1181
|
+
const result = await client.graphql(ADD_WORKSTREAM_ATTACHMENT_MUTATION, {
|
|
1182
|
+
workstreamId: workstream.id,
|
|
1183
|
+
kind: toGraphQLAttachmentKind(kind),
|
|
1184
|
+
entityId,
|
|
1185
|
+
});
|
|
1186
|
+
if (common.json) {
|
|
1187
|
+
printJson(runtime.stdout, result.addWorkstreamAttachment);
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
printLine(runtime.stdout, `Attached ${kind}:${entityId} to ${workstream.id}.`);
|
|
1191
|
+
}
|
|
1192
|
+
return 0;
|
|
1193
|
+
}
|
|
1194
|
+
case "reply-thread": {
|
|
1195
|
+
const parsed = parseArgs({
|
|
1196
|
+
args: args.slice(1),
|
|
1197
|
+
allowPositionals: true,
|
|
1198
|
+
options: {
|
|
1199
|
+
json: { type: "boolean" },
|
|
1200
|
+
help: { type: "boolean", short: "h" },
|
|
1201
|
+
profile: { type: "string" },
|
|
1202
|
+
"api-url": { type: "string" },
|
|
1203
|
+
text: { type: "string" },
|
|
1204
|
+
},
|
|
1205
|
+
});
|
|
1206
|
+
const common = mergeCommonOptions(inherited, {
|
|
1207
|
+
json: parsed.values.json,
|
|
1208
|
+
profile: parsed.values.profile,
|
|
1209
|
+
apiUrl: parsed.values["api-url"],
|
|
1210
|
+
});
|
|
1211
|
+
if (parsed.values.help) {
|
|
1212
|
+
printWorkstreamHelp(runtime);
|
|
1213
|
+
return 0;
|
|
1214
|
+
}
|
|
1215
|
+
const threadId = parsed.positionals[0];
|
|
1216
|
+
const text = parsed.values.text ??
|
|
1217
|
+
(parsed.positionals.length > 1
|
|
1218
|
+
? parsed.positionals.slice(1).join(" ")
|
|
1219
|
+
: await readTextFromStdin(runtime.stdin));
|
|
1220
|
+
if (!threadId || !text) {
|
|
1221
|
+
throw new CLIError("Usage: corners workstream reply-thread <threadId> [--text <text>]", { json: common.json });
|
|
1222
|
+
}
|
|
1223
|
+
const { client } = await requireStoredProfile(runtime, common);
|
|
1224
|
+
const result = await client.graphql(REPLY_ARTIFACT_THREAD_MUTATION, {
|
|
1225
|
+
id: threadId,
|
|
1226
|
+
input: {
|
|
1227
|
+
text,
|
|
1228
|
+
},
|
|
1229
|
+
});
|
|
1230
|
+
if (common.json) {
|
|
1231
|
+
printJson(runtime.stdout, result.replyArtifactThread);
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
printLine(runtime.stdout, `Replied to ${threadId}.`);
|
|
1235
|
+
}
|
|
1236
|
+
return 0;
|
|
1237
|
+
}
|
|
1238
|
+
default:
|
|
1239
|
+
throw new CLIError(`Unknown workstream command: ${subcommand}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
export async function runCli(argv, runtime = createRuntime()) {
|
|
1243
|
+
const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv;
|
|
1244
|
+
const parsed = parseLeadingCommonOptions(normalizedArgv);
|
|
1245
|
+
if (parsed.version) {
|
|
1246
|
+
printLine(runtime.stdout, runtime.getVersion());
|
|
1247
|
+
return 0;
|
|
1248
|
+
}
|
|
1249
|
+
const command = parsed.rest[0];
|
|
1250
|
+
if (!command) {
|
|
1251
|
+
printMainHelp(runtime);
|
|
1252
|
+
return 0;
|
|
1253
|
+
}
|
|
1254
|
+
if (parsed.help && !command) {
|
|
1255
|
+
printMainHelp(runtime);
|
|
1256
|
+
return 0;
|
|
1257
|
+
}
|
|
1258
|
+
switch (command) {
|
|
1259
|
+
case "help":
|
|
1260
|
+
if (parsed.rest[1] === "auth") {
|
|
1261
|
+
printAuthHelp(runtime);
|
|
1262
|
+
}
|
|
1263
|
+
else if (parsed.rest[1] === "workstream" || parsed.rest[1] === "ws") {
|
|
1264
|
+
printWorkstreamHelp(runtime);
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
printMainHelp(runtime);
|
|
1268
|
+
}
|
|
1269
|
+
return 0;
|
|
1270
|
+
case "version":
|
|
1271
|
+
printLine(runtime.stdout, runtime.getVersion());
|
|
1272
|
+
return 0;
|
|
1273
|
+
case "auth":
|
|
1274
|
+
return handleAuth(parsed.rest.slice(1), runtime, parsed.common);
|
|
1275
|
+
case "workstream":
|
|
1276
|
+
case "ws":
|
|
1277
|
+
return handleWorkstream(parsed.rest.slice(1), runtime, parsed.common);
|
|
1278
|
+
default:
|
|
1279
|
+
throw new CLIError(`Unknown command: ${command}`, {
|
|
1280
|
+
json: parsed.common.json,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
//# sourceMappingURL=cli.js.map
|