@shzlwio/windrunner-cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +513 -3
- package/package.json +1 -1
- package/src/index.ts +784 -4
- package/src/types.ts +80 -0
package/dist/index.js
CHANGED
|
@@ -54,6 +54,22 @@ function queryString(parameters) {
|
|
|
54
54
|
function collectOption(value, previous = []) {
|
|
55
55
|
return [...previous, value];
|
|
56
56
|
}
|
|
57
|
+
function collectRequiredOption(values, optionName) {
|
|
58
|
+
if (!values || values.length === 0) {
|
|
59
|
+
throw new CliError(`At least one ${optionName} is required.`);
|
|
60
|
+
}
|
|
61
|
+
return values.map((value) => requireText(value, `--${optionName}`));
|
|
62
|
+
}
|
|
63
|
+
function parseEntityReference(value, optionName) {
|
|
64
|
+
const separator = value.indexOf(":");
|
|
65
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
66
|
+
throw new CliError(`Invalid ${optionName} '${value}'. Use TYPE:<id>.`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
entityType: requireText(value.slice(0, separator), `--${optionName}`).toUpperCase(),
|
|
70
|
+
entityId: requireText(value.slice(separator + 1), `--${optionName}`),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
57
73
|
function parseAssignees(values) {
|
|
58
74
|
if (values === undefined)
|
|
59
75
|
return undefined;
|
|
@@ -132,6 +148,11 @@ function addPagination(command) {
|
|
|
132
148
|
.option("--size <number>", "Items per page; the server caps this at 100", "50")
|
|
133
149
|
.option("--updated-after <timestamp>", "Only return records updated after an ISO-8601 timestamp (UTC recommended)");
|
|
134
150
|
}
|
|
151
|
+
function addPageOptions(command, defaultSize = "50") {
|
|
152
|
+
return command
|
|
153
|
+
.option("--page <number>", "Page number (zero-based)", "0")
|
|
154
|
+
.option("--size <number>", "Items per page; the server caps this at 100", defaultSize);
|
|
155
|
+
}
|
|
135
156
|
const sharedHelp = `
|
|
136
157
|
Environment:
|
|
137
158
|
WINDRUNNER_URL Server URL (default: http://localhost:8080)
|
|
@@ -155,7 +176,7 @@ const program = new Command();
|
|
|
155
176
|
program
|
|
156
177
|
.name("windrunner")
|
|
157
178
|
.description("Command-line interface for Windrunner")
|
|
158
|
-
.version("
|
|
179
|
+
.version("1.1.0")
|
|
159
180
|
.option("--url <url>", "Windrunner server URL", process.env.WINDRUNNER_URL || "http://localhost:8080")
|
|
160
181
|
.option("--json", "Print compact JSON output")
|
|
161
182
|
.option("--dry-run", "Preview mutations without sending them")
|
|
@@ -167,7 +188,7 @@ addAgentHelp(program, `Quick start:
|
|
|
167
188
|
|
|
168
189
|
Use '<command> --help' for command-specific arguments and examples.`);
|
|
169
190
|
const projects = program.command("projects").description("Manage projects");
|
|
170
|
-
addAgentHelp(projects, "
|
|
191
|
+
addAgentHelp(projects, "Use command-specific help for the required API-key scope.");
|
|
171
192
|
addAgentHelp(projects
|
|
172
193
|
.command("list")
|
|
173
194
|
.description("List projects visible to the API key")
|
|
@@ -195,6 +216,173 @@ Example:
|
|
|
195
216
|
const client = new WindrunnerClient(globalOptions);
|
|
196
217
|
printResponse(await client.get(`/projects/${encode(projectId)}`), globalOptions);
|
|
197
218
|
});
|
|
219
|
+
addAgentHelp(projects
|
|
220
|
+
.command("create")
|
|
221
|
+
.description("Create a project")
|
|
222
|
+
.requiredOption("--name <name>", "Project name")
|
|
223
|
+
.option("--owner-user <userId>", "Project owner user id; repeat for multiple owners", collectOption)
|
|
224
|
+
.option("--owner-team <teamId>", "Project owner team id; repeat for multiple owners", collectOption), `Permissions: projects:write
|
|
225
|
+
At least one --owner-user or --owner-team is required.
|
|
226
|
+
|
|
227
|
+
Examples:
|
|
228
|
+
windrunner projects create --name "Platform work" --owner-user user-1
|
|
229
|
+
windrunner projects create --name "Shared work" --owner-team team-1 --dry-run --json`).action(async (options, command) => {
|
|
230
|
+
const globalOptions = getGlobalOptions(command);
|
|
231
|
+
const ownerUserIds = options.ownerUser?.map((value) => requireText(value, "--owner-user")) ?? [];
|
|
232
|
+
const ownerTeamIds = options.ownerTeam?.map((value) => requireText(value, "--owner-team")) ?? [];
|
|
233
|
+
if (ownerUserIds.length === 0 && ownerTeamIds.length === 0) {
|
|
234
|
+
throw new CliError("At least one --owner-user or --owner-team is required.");
|
|
235
|
+
}
|
|
236
|
+
const client = new WindrunnerClient(globalOptions);
|
|
237
|
+
printResponse(await client.post("/projects", {
|
|
238
|
+
name: requireText(options.name, "--name"),
|
|
239
|
+
ownerUserIds,
|
|
240
|
+
ownerTeamIds,
|
|
241
|
+
}), globalOptions);
|
|
242
|
+
});
|
|
243
|
+
addAgentHelp(projects
|
|
244
|
+
.command("update")
|
|
245
|
+
.description("Update a project")
|
|
246
|
+
.argument("<projectId>", "Project id")
|
|
247
|
+
.requiredOption("--name <name>", "Project name"), `Permissions: projects:write
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
windrunner projects update PROJECT_ID --name "Updated project"`).action(async (projectId, options, command) => {
|
|
251
|
+
const globalOptions = getGlobalOptions(command);
|
|
252
|
+
const client = new WindrunnerClient(globalOptions);
|
|
253
|
+
printResponse(await client.put(`/projects/${encode(projectId)}`, {
|
|
254
|
+
name: requireText(options.name, "--name"),
|
|
255
|
+
}), globalOptions);
|
|
256
|
+
});
|
|
257
|
+
addAgentHelp(projects
|
|
258
|
+
.command("delete")
|
|
259
|
+
.description("Delete a project and all of its content")
|
|
260
|
+
.argument("<projectId>", "Project id"), `Permissions: projects:write
|
|
261
|
+
This permanently deletes the project, work items, entries, relationships, and access links.
|
|
262
|
+
Use --dry-run to preview the request. Use --yes only when deletion is explicitly intended.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
windrunner projects delete PROJECT_ID --yes`).action(async (projectId, _options, command) => {
|
|
266
|
+
const globalOptions = getGlobalOptions(command);
|
|
267
|
+
const client = new WindrunnerClient(globalOptions);
|
|
268
|
+
if (!globalOptions.dryRun) {
|
|
269
|
+
await confirmDelete(`Delete project ${projectId}? This cannot be undone.`, globalOptions);
|
|
270
|
+
}
|
|
271
|
+
printResponse(await client.delete(`/projects/${encode(projectId)}`), globalOptions);
|
|
272
|
+
});
|
|
273
|
+
const projectMembers = projects.command("members").description("Manage project user access");
|
|
274
|
+
addAgentHelp(projectMembers, "Project membership changes require project owner access.");
|
|
275
|
+
addAgentHelp(addPageOptions(projectMembers
|
|
276
|
+
.command("list")
|
|
277
|
+
.description("List users with access to a project")
|
|
278
|
+
.argument("<projectId>", "Project id")), `Permissions: project_access:read
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
windrunner projects members list PROJECT_ID --json`).action(async (projectId, options, command) => {
|
|
282
|
+
const globalOptions = getGlobalOptions(command);
|
|
283
|
+
const client = new WindrunnerClient(globalOptions);
|
|
284
|
+
printResponse(await client.get(`/projects/${encode(projectId)}/members${queryString({
|
|
285
|
+
page: numberValue(options.page, "page"),
|
|
286
|
+
size: numberValue(options.size, "size"),
|
|
287
|
+
})}`), globalOptions);
|
|
288
|
+
});
|
|
289
|
+
addAgentHelp(projectMembers
|
|
290
|
+
.command("add")
|
|
291
|
+
.description("Add or update a project user")
|
|
292
|
+
.argument("<projectId>", "Project id")
|
|
293
|
+
.requiredOption("--user-id <userId>", "User id")
|
|
294
|
+
.option("--role <role>", "Project role: OWNER, EDITOR, or VIEWER", "VIEWER"), `Permissions: project_access:write
|
|
295
|
+
|
|
296
|
+
Example:
|
|
297
|
+
windrunner projects members add PROJECT_ID --user-id USER_ID --role EDITOR`).action(async (projectId, options, command) => {
|
|
298
|
+
const globalOptions = getGlobalOptions(command);
|
|
299
|
+
const client = new WindrunnerClient(globalOptions);
|
|
300
|
+
printResponse(await client.post(`/projects/${encode(projectId)}/members`, {
|
|
301
|
+
userId: requireText(options.userId, "--user-id"),
|
|
302
|
+
role: options.role,
|
|
303
|
+
}), globalOptions);
|
|
304
|
+
});
|
|
305
|
+
addAgentHelp(projectMembers
|
|
306
|
+
.command("remove")
|
|
307
|
+
.description("Remove a user from a project")
|
|
308
|
+
.argument("<projectId>", "Project id")
|
|
309
|
+
.argument("<userId>", "User id"), `Permissions: project_access:write
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
windrunner projects members remove PROJECT_ID USER_ID --yes`).action(async (projectId, userId, _options, command) => {
|
|
313
|
+
const globalOptions = getGlobalOptions(command);
|
|
314
|
+
const client = new WindrunnerClient(globalOptions);
|
|
315
|
+
if (!globalOptions.dryRun) {
|
|
316
|
+
await confirmDelete(`Remove user ${userId} from project ${projectId}?`, globalOptions);
|
|
317
|
+
}
|
|
318
|
+
printResponse(await client.delete(`/projects/${encode(projectId)}/members/${encode(userId)}`), globalOptions);
|
|
319
|
+
});
|
|
320
|
+
const projectTeams = projects.command("teams").description("Manage project team access");
|
|
321
|
+
addAgentHelp(projectTeams, "Project team links require project owner access.");
|
|
322
|
+
addAgentHelp(addPageOptions(projectTeams
|
|
323
|
+
.command("list")
|
|
324
|
+
.description("List teams linked to a project")
|
|
325
|
+
.argument("<projectId>", "Project id")), `Permissions: project_access:read
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
windrunner projects teams list PROJECT_ID --json`).action(async (projectId, options, command) => {
|
|
329
|
+
const globalOptions = getGlobalOptions(command);
|
|
330
|
+
const client = new WindrunnerClient(globalOptions);
|
|
331
|
+
printResponse(await client.get(`/projects/${encode(projectId)}/teams${queryString({
|
|
332
|
+
page: numberValue(options.page, "page"),
|
|
333
|
+
size: numberValue(options.size, "size"),
|
|
334
|
+
})}`), globalOptions);
|
|
335
|
+
});
|
|
336
|
+
addAgentHelp(projectTeams
|
|
337
|
+
.command("add")
|
|
338
|
+
.description("Add or update a project team")
|
|
339
|
+
.argument("<projectId>", "Project id")
|
|
340
|
+
.requiredOption("--team-id <teamId>", "Team id")
|
|
341
|
+
.option("--role <role>", "Project role: OWNER, EDITOR, or VIEWER", "VIEWER"), `Permissions: project_access:write
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
windrunner projects teams add PROJECT_ID --team-id TEAM_ID --role EDITOR`).action(async (projectId, options, command) => {
|
|
345
|
+
const globalOptions = getGlobalOptions(command);
|
|
346
|
+
const client = new WindrunnerClient(globalOptions);
|
|
347
|
+
printResponse(await client.post(`/projects/${encode(projectId)}/teams`, {
|
|
348
|
+
teamId: requireText(options.teamId, "--team-id"),
|
|
349
|
+
role: options.role,
|
|
350
|
+
}), globalOptions);
|
|
351
|
+
});
|
|
352
|
+
addAgentHelp(projectTeams
|
|
353
|
+
.command("remove")
|
|
354
|
+
.description("Unlink a team from a project")
|
|
355
|
+
.argument("<projectId>", "Project id")
|
|
356
|
+
.argument("<teamId>", "Team id"), `Permissions: project_access:write
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
windrunner projects teams remove PROJECT_ID TEAM_ID --yes`).action(async (projectId, teamId, _options, command) => {
|
|
360
|
+
const globalOptions = getGlobalOptions(command);
|
|
361
|
+
const client = new WindrunnerClient(globalOptions);
|
|
362
|
+
if (!globalOptions.dryRun) {
|
|
363
|
+
await confirmDelete(`Unlink team ${teamId} from project ${projectId}?`, globalOptions);
|
|
364
|
+
}
|
|
365
|
+
printResponse(await client.delete(`/projects/${encode(projectId)}/teams/${encode(teamId)}`), globalOptions);
|
|
366
|
+
});
|
|
367
|
+
addAgentHelp(projects
|
|
368
|
+
.command("reorder")
|
|
369
|
+
.description("Reorder work items and entries in a project content stream")
|
|
370
|
+
.argument("<projectId>", "Project id")
|
|
371
|
+
.requiredOption("--item <type:id>", "Ordered item in WORK_ITEM:<id> or ENTRY:<id> format", collectOption)
|
|
372
|
+
.option("--parent-id <workItemId>", "Parent work item id; omit for the project root"), `Permissions: work_items:write and entries:write
|
|
373
|
+
Repeat --item in the desired order.
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
windrunner projects reorder PROJECT_ID \\
|
|
377
|
+
--item WORK_ITEM:item-1 --item ENTRY:entry-1 --item WORK_ITEM:item-2`).action(async (projectId, options, command) => {
|
|
378
|
+
const globalOptions = getGlobalOptions(command);
|
|
379
|
+
const client = new WindrunnerClient(globalOptions);
|
|
380
|
+
const items = options.item.map((value) => parseEntityReference(value, "item"));
|
|
381
|
+
printResponse(await client.put(`/projects/${encode(projectId)}/content-order`, {
|
|
382
|
+
...(options.parentId === undefined ? {} : { parentWorkItemId: options.parentId }),
|
|
383
|
+
items,
|
|
384
|
+
}), globalOptions);
|
|
385
|
+
});
|
|
198
386
|
const workItems = program.command("work-items").description("Manage work items");
|
|
199
387
|
addAgentHelp(workItems, "Use --json for machine-readable results. Work item type and status values are validated by the server.");
|
|
200
388
|
addAgentHelp(addPagination(workItems
|
|
@@ -309,6 +497,26 @@ Examples:
|
|
|
309
497
|
}
|
|
310
498
|
printResponse(await client.delete(`/work-items/${encode(workItemId)}`), globalOptions);
|
|
311
499
|
});
|
|
500
|
+
addAgentHelp(workItems
|
|
501
|
+
.command("move")
|
|
502
|
+
.description("Move a work item to a different content position")
|
|
503
|
+
.argument("<workItemId>", "Work item id")
|
|
504
|
+
.option("--parent-id <workItemId>", "Destination parent work item id; omit for the project root")
|
|
505
|
+
.option("--before <type:id>", "Place before WORK_ITEM:<id> or ENTRY:<id> in the destination stream"), `Permissions: work_items:write
|
|
506
|
+
|
|
507
|
+
Examples:
|
|
508
|
+
windrunner work-items move WORK_ITEM_ID --parent-id PARENT_ID
|
|
509
|
+
windrunner work-items move WORK_ITEM_ID --before ENTRY:entry-1`).action(async (workItemId, options, command) => {
|
|
510
|
+
const globalOptions = getGlobalOptions(command);
|
|
511
|
+
const client = new WindrunnerClient(globalOptions);
|
|
512
|
+
const before = options.before === undefined ? undefined : parseEntityReference(options.before, "before");
|
|
513
|
+
printResponse(await client.put(`/work-items/${encode(workItemId)}/move`, {
|
|
514
|
+
...(options.parentId === undefined ? {} : { parentWorkItemId: options.parentId }),
|
|
515
|
+
...(before === undefined
|
|
516
|
+
? {}
|
|
517
|
+
: { beforeEntityType: before.entityType, beforeEntityId: before.entityId }),
|
|
518
|
+
}), globalOptions);
|
|
519
|
+
});
|
|
312
520
|
const entries = program.command("entries").description("Manage entries");
|
|
313
521
|
addAgentHelp(entries, "Entries are attached to work items. Use --json for machine-readable results.");
|
|
314
522
|
addAgentHelp(addPagination(entries
|
|
@@ -347,12 +555,56 @@ Examples:
|
|
|
347
555
|
};
|
|
348
556
|
printResponse(await client.post(`/work-items/${encode(workItemId)}/entries`, body), globalOptions);
|
|
349
557
|
});
|
|
558
|
+
addAgentHelp(entries
|
|
559
|
+
.command("get")
|
|
560
|
+
.description("Get an entry")
|
|
561
|
+
.argument("<entryId>", "Entry id"), `Permissions: entries:read
|
|
562
|
+
|
|
563
|
+
Example:
|
|
564
|
+
windrunner entries get ENTRY_ID --json`).action(async (entryId, _options, command) => {
|
|
565
|
+
const globalOptions = getGlobalOptions(command);
|
|
566
|
+
const client = new WindrunnerClient(globalOptions);
|
|
567
|
+
printResponse(await client.get(`/entries/${encode(entryId)}`), globalOptions);
|
|
568
|
+
});
|
|
569
|
+
addAgentHelp(entries
|
|
570
|
+
.command("update")
|
|
571
|
+
.description("Update an entry")
|
|
572
|
+
.argument("<entryId>", "Entry id")
|
|
573
|
+
.requiredOption("--body <body>", "Entry body")
|
|
574
|
+
.option("--type <type>", "Entry type; defaults to COMMENT when omitted"), `Permissions: entries:write
|
|
575
|
+
Required: --body. The API treats an omitted type as COMMENT.
|
|
576
|
+
|
|
577
|
+
Example:
|
|
578
|
+
windrunner entries update ENTRY_ID --body "Updated context" --type EVIDENCE`).action(async (entryId, options, command) => {
|
|
579
|
+
const globalOptions = getGlobalOptions(command);
|
|
580
|
+
const client = new WindrunnerClient(globalOptions);
|
|
581
|
+
printResponse(await client.put(`/entries/${encode(entryId)}`, {
|
|
582
|
+
body: requireText(options.body, "--body"),
|
|
583
|
+
...(options.type === undefined ? {} : { type: options.type }),
|
|
584
|
+
}), globalOptions);
|
|
585
|
+
});
|
|
586
|
+
addAgentHelp(entries
|
|
587
|
+
.command("delete")
|
|
588
|
+
.description("Delete an entry")
|
|
589
|
+
.argument("<entryId>", "Entry id"), `Permissions: entries:write
|
|
590
|
+
This permanently deletes the entry and its relationships.
|
|
591
|
+
Use --dry-run to preview the request.
|
|
592
|
+
|
|
593
|
+
Example:
|
|
594
|
+
windrunner entries delete ENTRY_ID --yes`).action(async (entryId, _options, command) => {
|
|
595
|
+
const globalOptions = getGlobalOptions(command);
|
|
596
|
+
const client = new WindrunnerClient(globalOptions);
|
|
597
|
+
if (!globalOptions.dryRun) {
|
|
598
|
+
await confirmDelete(`Delete entry ${entryId}? This cannot be undone.`, globalOptions);
|
|
599
|
+
}
|
|
600
|
+
printResponse(await client.delete(`/entries/${encode(entryId)}`), globalOptions);
|
|
601
|
+
});
|
|
350
602
|
addAgentHelp(program
|
|
351
603
|
.command("search")
|
|
352
604
|
.description("Search project work items, entries, and relationships")
|
|
353
605
|
.argument("<projectId>", "Project id")
|
|
354
606
|
.argument("<query>", "Search query")
|
|
355
|
-
.option("--limit <number>", "Maximum number of matches"), `Permissions: work_items:read
|
|
607
|
+
.option("--limit <number>", "Maximum number of matches"), `Permissions: work_items:read, entries:read, and relationships:read
|
|
356
608
|
|
|
357
609
|
Example:
|
|
358
610
|
windrunner search PROJECT_ID "login failure" --limit 20 --json`)
|
|
@@ -362,6 +614,264 @@ Example:
|
|
|
362
614
|
const limit = numberValue(options.limit, "limit");
|
|
363
615
|
printResponse(await client.get(`/projects/${encode(projectId)}/search${queryString({ q: query, limit })}`), globalOptions);
|
|
364
616
|
});
|
|
617
|
+
const relationships = program.command("relationships").description("Manage work item relationships");
|
|
618
|
+
addAgentHelp(relationships, "Relationships connect work items and entries with a type and optional reason.");
|
|
619
|
+
addAgentHelp(addPageOptions(relationships
|
|
620
|
+
.command("list")
|
|
621
|
+
.description("List relationships in a project")
|
|
622
|
+
.argument("<projectId>", "Project id")
|
|
623
|
+
.option("--type <type>", "Filter by relationship type")
|
|
624
|
+
.option("--created-after <timestamp>", "Only return relationships created after an ISO-8601 timestamp")), `Permissions: relationships:read
|
|
625
|
+
|
|
626
|
+
Example:
|
|
627
|
+
windrunner relationships list PROJECT_ID --type BLOCKED_BY --json`).action(async (projectId, options, command) => {
|
|
628
|
+
const globalOptions = getGlobalOptions(command);
|
|
629
|
+
const client = new WindrunnerClient(globalOptions);
|
|
630
|
+
printResponse(await client.get(`/projects/${encode(projectId)}/relationships${queryString({
|
|
631
|
+
page: numberValue(options.page, "page"),
|
|
632
|
+
size: numberValue(options.size, "size"),
|
|
633
|
+
type: options.type,
|
|
634
|
+
created_after: options.createdAfter,
|
|
635
|
+
})}`), globalOptions);
|
|
636
|
+
});
|
|
637
|
+
addAgentHelp(relationships
|
|
638
|
+
.command("create")
|
|
639
|
+
.description("Create a relationship")
|
|
640
|
+
.argument("<projectId>", "Project id")
|
|
641
|
+
.requiredOption("--from <type:id>", "Source entity in WORK_ITEM:<id> or ENTRY:<id> format")
|
|
642
|
+
.requiredOption("--to <type:id>", "Target entity in WORK_ITEM:<id> or ENTRY:<id> format")
|
|
643
|
+
.requiredOption("--type <type>", "Relationship type")
|
|
644
|
+
.option("--reason <reason>", "Relationship reason")
|
|
645
|
+
.option("--source-entry-id <entryId>", "Entry supporting the relationship"), `Permissions: relationships:write
|
|
646
|
+
|
|
647
|
+
Example:
|
|
648
|
+
windrunner relationships create PROJECT_ID \\
|
|
649
|
+
--from WORK_ITEM:item-1 --to WORK_ITEM:item-2 --type BLOCKED_BY \\
|
|
650
|
+
--reason "Waiting on the database migration"`).action(async (projectId, options, command) => {
|
|
651
|
+
const globalOptions = getGlobalOptions(command);
|
|
652
|
+
const client = new WindrunnerClient(globalOptions);
|
|
653
|
+
const from = parseEntityReference(options.from, "from");
|
|
654
|
+
const to = parseEntityReference(options.to, "to");
|
|
655
|
+
printResponse(await client.post(`/projects/${encode(projectId)}/relationships`, {
|
|
656
|
+
fromEntityType: from.entityType,
|
|
657
|
+
fromEntityId: from.entityId,
|
|
658
|
+
toEntityType: to.entityType,
|
|
659
|
+
toEntityId: to.entityId,
|
|
660
|
+
type: requireText(options.type, "--type"),
|
|
661
|
+
...(options.reason === undefined ? {} : { reason: options.reason }),
|
|
662
|
+
...(options.sourceEntryId === undefined ? {} : { sourceEntryId: options.sourceEntryId }),
|
|
663
|
+
}), globalOptions);
|
|
664
|
+
});
|
|
665
|
+
addAgentHelp(relationships
|
|
666
|
+
.command("update-reason")
|
|
667
|
+
.description("Update or clear a relationship reason")
|
|
668
|
+
.argument("<relationshipId>", "Relationship id")
|
|
669
|
+
.option("--reason <reason>", "New reason; omit to clear the reason"), `Permissions: relationships:write
|
|
670
|
+
|
|
671
|
+
Examples:
|
|
672
|
+
windrunner relationships update-reason RELATIONSHIP_ID --reason "New explanation"
|
|
673
|
+
windrunner relationships update-reason RELATIONSHIP_ID --dry-run`).action(async (relationshipId, options, command) => {
|
|
674
|
+
const globalOptions = getGlobalOptions(command);
|
|
675
|
+
const client = new WindrunnerClient(globalOptions);
|
|
676
|
+
printResponse(await client.put(`/relationships/${encode(relationshipId)}/reason`, {
|
|
677
|
+
reason: options.reason ?? null,
|
|
678
|
+
}), globalOptions);
|
|
679
|
+
});
|
|
680
|
+
addAgentHelp(relationships
|
|
681
|
+
.command("delete")
|
|
682
|
+
.description("Delete a relationship")
|
|
683
|
+
.argument("<relationshipId>", "Relationship id"), `Permissions: relationships:write
|
|
684
|
+
Use --dry-run to preview the request.
|
|
685
|
+
|
|
686
|
+
Example:
|
|
687
|
+
windrunner relationships delete RELATIONSHIP_ID --yes`).action(async (relationshipId, _options, command) => {
|
|
688
|
+
const globalOptions = getGlobalOptions(command);
|
|
689
|
+
const client = new WindrunnerClient(globalOptions);
|
|
690
|
+
if (!globalOptions.dryRun) {
|
|
691
|
+
await confirmDelete(`Delete relationship ${relationshipId}? This cannot be undone.`, globalOptions);
|
|
692
|
+
}
|
|
693
|
+
printResponse(await client.delete(`/relationships/${encode(relationshipId)}`), globalOptions);
|
|
694
|
+
});
|
|
695
|
+
const teams = program.command("teams").description("Manage teams");
|
|
696
|
+
addAgentHelp(teams, "Team creation, updates, deletion, and membership changes require an admin-like API-key owner.");
|
|
697
|
+
addAgentHelp(addPageOptions(teams
|
|
698
|
+
.command("list")
|
|
699
|
+
.description("List teams")), `Permissions: teams:read
|
|
700
|
+
|
|
701
|
+
Example:
|
|
702
|
+
windrunner teams list --json`).action(async (options, command) => {
|
|
703
|
+
const globalOptions = getGlobalOptions(command);
|
|
704
|
+
const client = new WindrunnerClient(globalOptions);
|
|
705
|
+
printResponse(await client.get(`/teams${queryString({
|
|
706
|
+
page: numberValue(options.page, "page"),
|
|
707
|
+
size: numberValue(options.size, "size"),
|
|
708
|
+
})}`), globalOptions);
|
|
709
|
+
});
|
|
710
|
+
addAgentHelp(teams
|
|
711
|
+
.command("get")
|
|
712
|
+
.description("Get a team")
|
|
713
|
+
.argument("<teamId>", "Team id"), `Permissions: teams:read
|
|
714
|
+
|
|
715
|
+
Example:
|
|
716
|
+
windrunner teams get TEAM_ID --json`).action(async (teamId, _options, command) => {
|
|
717
|
+
const globalOptions = getGlobalOptions(command);
|
|
718
|
+
const client = new WindrunnerClient(globalOptions);
|
|
719
|
+
printResponse(await client.get(`/teams/${encode(teamId)}`), globalOptions);
|
|
720
|
+
});
|
|
721
|
+
addAgentHelp(teams
|
|
722
|
+
.command("create")
|
|
723
|
+
.description("Create a team")
|
|
724
|
+
.requiredOption("--name <name>", "Team name")
|
|
725
|
+
.requiredOption("--owner-user <userId>", "Team owner user id; repeat for multiple owners", collectOption)
|
|
726
|
+
.option("--description <description>", "Team description"), `Permissions: teams:write
|
|
727
|
+
At least one --owner-user is required.
|
|
728
|
+
|
|
729
|
+
Example:
|
|
730
|
+
windrunner teams create --name "Platform" --owner-user user-1 --description "Platform team"`).action(async (options, command) => {
|
|
731
|
+
const globalOptions = getGlobalOptions(command);
|
|
732
|
+
const client = new WindrunnerClient(globalOptions);
|
|
733
|
+
printResponse(await client.post("/teams", {
|
|
734
|
+
name: requireText(options.name, "--name"),
|
|
735
|
+
ownerUserIds: collectRequiredOption(options.ownerUser, "--owner-user"),
|
|
736
|
+
...(options.description === undefined ? {} : { description: options.description }),
|
|
737
|
+
}), globalOptions);
|
|
738
|
+
});
|
|
739
|
+
addAgentHelp(teams
|
|
740
|
+
.command("update")
|
|
741
|
+
.description("Update a team")
|
|
742
|
+
.argument("<teamId>", "Team id")
|
|
743
|
+
.requiredOption("--name <name>", "Team name")
|
|
744
|
+
.requiredOption("--description <description>", "Team description; use an empty value to clear it"), `Permissions: teams:write
|
|
745
|
+
Both fields are required because the API accepts a full team representation.
|
|
746
|
+
|
|
747
|
+
Example:
|
|
748
|
+
windrunner teams update TEAM_ID --name "Platform engineering" --description "Owns platform services"`).action(async (teamId, options, command) => {
|
|
749
|
+
const globalOptions = getGlobalOptions(command);
|
|
750
|
+
const client = new WindrunnerClient(globalOptions);
|
|
751
|
+
printResponse(await client.put(`/teams/${encode(teamId)}`, {
|
|
752
|
+
name: requireText(options.name, "--name"),
|
|
753
|
+
description: options.description,
|
|
754
|
+
}), globalOptions);
|
|
755
|
+
});
|
|
756
|
+
addAgentHelp(teams
|
|
757
|
+
.command("delete")
|
|
758
|
+
.description("Delete a team")
|
|
759
|
+
.argument("<teamId>", "Team id"), `Permissions: teams:write
|
|
760
|
+
This permanently deletes the team and removes its memberships and project links.
|
|
761
|
+
Use --dry-run to preview the request.
|
|
762
|
+
|
|
763
|
+
Example:
|
|
764
|
+
windrunner teams delete TEAM_ID --yes`).action(async (teamId, _options, command) => {
|
|
765
|
+
const globalOptions = getGlobalOptions(command);
|
|
766
|
+
const client = new WindrunnerClient(globalOptions);
|
|
767
|
+
if (!globalOptions.dryRun) {
|
|
768
|
+
await confirmDelete(`Delete team ${teamId}? This cannot be undone.`, globalOptions);
|
|
769
|
+
}
|
|
770
|
+
printResponse(await client.delete(`/teams/${encode(teamId)}`), globalOptions);
|
|
771
|
+
});
|
|
772
|
+
const teamMembers = teams.command("members").description("Manage team membership");
|
|
773
|
+
addAgentHelp(teamMembers, "Team membership changes require an admin-like API-key owner.");
|
|
774
|
+
addAgentHelp(addPageOptions(teamMembers
|
|
775
|
+
.command("list")
|
|
776
|
+
.description("List team members")
|
|
777
|
+
.argument("<teamId>", "Team id")), `Permissions: team_members:read
|
|
778
|
+
|
|
779
|
+
Example:
|
|
780
|
+
windrunner teams members list TEAM_ID --json`).action(async (teamId, options, command) => {
|
|
781
|
+
const globalOptions = getGlobalOptions(command);
|
|
782
|
+
const client = new WindrunnerClient(globalOptions);
|
|
783
|
+
printResponse(await client.get(`/teams/${encode(teamId)}/members${queryString({
|
|
784
|
+
page: numberValue(options.page, "page"),
|
|
785
|
+
size: numberValue(options.size, "size"),
|
|
786
|
+
})}`), globalOptions);
|
|
787
|
+
});
|
|
788
|
+
addAgentHelp(teamMembers
|
|
789
|
+
.command("add")
|
|
790
|
+
.description("Add a user to a team")
|
|
791
|
+
.argument("<teamId>", "Team id")
|
|
792
|
+
.requiredOption("--user-id <userId>", "User id")
|
|
793
|
+
.option("--role <role>", "Team role: TEAM_OWNER or TEAM_MEMBER", "TEAM_MEMBER"), `Permissions: team_members:write
|
|
794
|
+
|
|
795
|
+
Example:
|
|
796
|
+
windrunner teams members add TEAM_ID --user-id USER_ID --role TEAM_MEMBER`).action(async (teamId, options, command) => {
|
|
797
|
+
const globalOptions = getGlobalOptions(command);
|
|
798
|
+
const client = new WindrunnerClient(globalOptions);
|
|
799
|
+
printResponse(await client.post(`/teams/${encode(teamId)}/members`, {
|
|
800
|
+
userId: requireText(options.userId, "--user-id"),
|
|
801
|
+
role: options.role,
|
|
802
|
+
}), globalOptions);
|
|
803
|
+
});
|
|
804
|
+
addAgentHelp(teamMembers
|
|
805
|
+
.command("remove")
|
|
806
|
+
.description("Remove a user from a team")
|
|
807
|
+
.argument("<teamId>", "Team id")
|
|
808
|
+
.argument("<userId>", "User id"), `Permissions: team_members:write
|
|
809
|
+
|
|
810
|
+
Example:
|
|
811
|
+
windrunner teams members remove TEAM_ID USER_ID --yes`).action(async (teamId, userId, _options, command) => {
|
|
812
|
+
const globalOptions = getGlobalOptions(command);
|
|
813
|
+
const client = new WindrunnerClient(globalOptions);
|
|
814
|
+
if (!globalOptions.dryRun) {
|
|
815
|
+
await confirmDelete(`Remove user ${userId} from team ${teamId}?`, globalOptions);
|
|
816
|
+
}
|
|
817
|
+
printResponse(await client.delete(`/teams/${encode(teamId)}/members/${encode(userId)}`), globalOptions);
|
|
818
|
+
});
|
|
819
|
+
addAgentHelp(addPageOptions(teams
|
|
820
|
+
.command("projects")
|
|
821
|
+
.description("List projects linked to a team")
|
|
822
|
+
.argument("<teamId>", "Team id")), `Permissions: team_projects:read
|
|
823
|
+
|
|
824
|
+
Example:
|
|
825
|
+
windrunner teams projects TEAM_ID --json`).action(async (teamId, options, command) => {
|
|
826
|
+
const globalOptions = getGlobalOptions(command);
|
|
827
|
+
const client = new WindrunnerClient(globalOptions);
|
|
828
|
+
printResponse(await client.get(`/teams/${encode(teamId)}/projects${queryString({
|
|
829
|
+
page: numberValue(options.page, "page"),
|
|
830
|
+
size: numberValue(options.size, "size"),
|
|
831
|
+
})}`), globalOptions);
|
|
832
|
+
});
|
|
833
|
+
const users = program.command("users").description("Resolve users");
|
|
834
|
+
addAgentHelp(users, "Only limited identity fields are returned by the external API.");
|
|
835
|
+
addAgentHelp(users
|
|
836
|
+
.command("get")
|
|
837
|
+
.description("Get limited user identity information")
|
|
838
|
+
.argument("<userId>", "User id"), `Permissions: users:read
|
|
839
|
+
|
|
840
|
+
Example:
|
|
841
|
+
windrunner users get USER_ID --json`).action(async (userId, _options, command) => {
|
|
842
|
+
const globalOptions = getGlobalOptions(command);
|
|
843
|
+
const client = new WindrunnerClient(globalOptions);
|
|
844
|
+
printResponse(await client.get(`/users/${encode(userId)}`), globalOptions);
|
|
845
|
+
});
|
|
846
|
+
const auditLogs = program.command("audit-logs").description("Read audit logs");
|
|
847
|
+
addAgentHelp(auditLogs, "Audit log access requires an administrator or superadministrator API-key owner.");
|
|
848
|
+
addAgentHelp(addPageOptions(auditLogs
|
|
849
|
+
.command("list")
|
|
850
|
+
.description("List platform audit logs"), "20"), `Permissions: audit_logs:read
|
|
851
|
+
|
|
852
|
+
Example:
|
|
853
|
+
windrunner audit-logs list --json`).action(async (options, command) => {
|
|
854
|
+
const globalOptions = getGlobalOptions(command);
|
|
855
|
+
const client = new WindrunnerClient(globalOptions);
|
|
856
|
+
printResponse(await client.get(`/audit-logs${queryString({
|
|
857
|
+
page: numberValue(options.page, "page"),
|
|
858
|
+
size: numberValue(options.size, "size"),
|
|
859
|
+
})}`), globalOptions);
|
|
860
|
+
});
|
|
861
|
+
addAgentHelp(addPageOptions(auditLogs
|
|
862
|
+
.command("project")
|
|
863
|
+
.description("List audit logs for a project")
|
|
864
|
+
.argument("<projectId>", "Project id"), "20"), `Permissions: audit_logs:read
|
|
865
|
+
|
|
866
|
+
Example:
|
|
867
|
+
windrunner audit-logs project PROJECT_ID --json`).action(async (projectId, options, command) => {
|
|
868
|
+
const globalOptions = getGlobalOptions(command);
|
|
869
|
+
const client = new WindrunnerClient(globalOptions);
|
|
870
|
+
printResponse(await client.get(`/projects/${encode(projectId)}/audit-logs${queryString({
|
|
871
|
+
page: numberValue(options.page, "page"),
|
|
872
|
+
size: numberValue(options.size, "size"),
|
|
873
|
+
})}`), globalOptions);
|
|
874
|
+
});
|
|
365
875
|
try {
|
|
366
876
|
await program.parseAsync(process.argv);
|
|
367
877
|
}
|