@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 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("0.1.0")
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, "Permissions: projects:read for both project commands.");
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shzlwio/windrunner-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Command-line interface for Windrunner.",
5
5
  "type": "module",
6
6
  "publishConfig": {