@wavyx/pdcli 0.10.0 → 0.11.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +7 -1
  3. package/oclif.manifest.json +6369 -1598
  4. package/package.json +29 -2
  5. package/src/commands/activity/type/list.js +36 -0
  6. package/src/commands/deal/convert.js +112 -0
  7. package/src/commands/deal/follower/add.js +31 -0
  8. package/src/commands/deal/follower/list.js +38 -0
  9. package/src/commands/deal/follower/remove.js +44 -0
  10. package/src/commands/deal/list.js +7 -4
  11. package/src/commands/deal/participant/add.js +31 -0
  12. package/src/commands/deal/participant/list.js +46 -0
  13. package/src/commands/deal/participant/remove.js +53 -0
  14. package/src/commands/deal/summary.js +69 -0
  15. package/src/commands/field/create.js +72 -0
  16. package/src/commands/field/delete.js +56 -0
  17. package/src/commands/field/get.js +10 -1
  18. package/src/commands/field/list.js +10 -1
  19. package/src/commands/field/option/add.js +49 -0
  20. package/src/commands/field/option/remove.js +72 -0
  21. package/src/commands/field/update.js +53 -0
  22. package/src/commands/file/delete.js +41 -0
  23. package/src/commands/file/update.js +39 -0
  24. package/src/commands/filter/delete.js +41 -0
  25. package/src/commands/lead/convert.js +103 -0
  26. package/src/commands/note/comment/add.js +31 -0
  27. package/src/commands/note/comment/delete.js +51 -0
  28. package/src/commands/note/comment/list.js +43 -0
  29. package/src/commands/note/comment/update.js +37 -0
  30. package/src/commands/org/follower/add.js +31 -0
  31. package/src/commands/org/follower/list.js +41 -0
  32. package/src/commands/org/follower/remove.js +50 -0
  33. package/src/commands/org/relationship/add.js +44 -0
  34. package/src/commands/org/relationship/list.js +52 -0
  35. package/src/commands/org/relationship/remove.js +46 -0
  36. package/src/commands/person/follower/add.js +31 -0
  37. package/src/commands/person/follower/list.js +38 -0
  38. package/src/commands/person/follower/remove.js +48 -0
  39. package/src/commands/project/list.js +10 -4
  40. package/src/commands/search.js +66 -9
  41. package/src/commands/task/create.js +44 -0
  42. package/src/commands/task/delete.js +41 -0
  43. package/src/commands/task/get.js +26 -0
  44. package/src/commands/task/list.js +60 -0
  45. package/src/commands/task/update.js +72 -0
  46. package/src/lib/client.js +19 -0
  47. package/src/lib/fields.js +43 -12
@@ -15,7 +15,16 @@ export default class FieldGetCommand extends BaseCommand {
15
15
  entity: Args.string({
16
16
  required: true,
17
17
  description: 'Entity type',
18
- options: ['deal', 'person', 'org', 'organization', 'product', 'activity'],
18
+ options: [
19
+ 'deal',
20
+ 'person',
21
+ 'org',
22
+ 'organization',
23
+ 'product',
24
+ 'activity',
25
+ 'lead',
26
+ 'note',
27
+ ],
19
28
  }),
20
29
  field: Args.string({
21
30
  required: true,
@@ -15,7 +15,16 @@ export default class FieldListCommand extends BaseCommand {
15
15
  entity: Args.string({
16
16
  required: true,
17
17
  description: 'Entity type',
18
- options: ['deal', 'person', 'org', 'organization', 'product', 'activity'],
18
+ options: [
19
+ 'deal',
20
+ 'person',
21
+ 'org',
22
+ 'organization',
23
+ 'product',
24
+ 'activity',
25
+ 'lead',
26
+ 'note',
27
+ ],
19
28
  }),
20
29
  }
21
30
 
@@ -0,0 +1,49 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { clearFieldsCache, entityToFieldsPath } from '../../../lib/fields.js'
4
+
5
+ export default class FieldOptionAddCommand extends BaseCommand {
6
+ static description = 'Add an option to an enum/set custom field'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> field option add deal dcf558aac1ae4e8c4f849ba5e668430d8df9be12 --label "Critical"',
10
+ ]
11
+
12
+ static args = {
13
+ entity: Args.string({
14
+ required: true,
15
+ description: 'Entity type',
16
+ options: ['deal', 'person', 'org', 'organization', 'product'],
17
+ }),
18
+ field: Args.string({
19
+ required: true,
20
+ description: 'Field code (hashed key)',
21
+ }),
22
+ }
23
+
24
+ static flags = {
25
+ ...BaseCommand.baseFlags,
26
+ label: Flags.string({
27
+ required: true,
28
+ description: 'Label for the new option',
29
+ }),
30
+ }
31
+
32
+ async run() {
33
+ const { args, flags } = await this.parse(FieldOptionAddCommand)
34
+
35
+ const res = await this.apiClient.post(
36
+ `${entityToFieldsPath(args.entity)}/${args.field}/options`,
37
+ { body: [{ label: flags.label }] },
38
+ )
39
+
40
+ // Option ids feed the per-run resolver — invalidate so the new option
41
+ // resolves immediately within this process.
42
+ clearFieldsCache()
43
+
44
+ await this.outputResults(res.data ?? [], {
45
+ id: { header: 'ID' },
46
+ label: { header: 'Label' },
47
+ })
48
+ }
49
+ }
@@ -0,0 +1,72 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { confirmAction } from '../../../lib/confirm.js'
4
+ import { clearFieldsCache, entityToFieldsPath } from '../../../lib/fields.js'
5
+ import { CliError } from '../../../lib/errors.js'
6
+
7
+ export default class FieldOptionRemoveCommand extends BaseCommand {
8
+ static description =
9
+ 'Remove an option from an enum/set custom field (records lose the value)'
10
+
11
+ static examples = [
12
+ '<%= config.bin %> field option remove deal dcf558aac1ae4e8c4f849ba5e668430d8df9be12 --option 4',
13
+ ]
14
+
15
+ static args = {
16
+ entity: Args.string({
17
+ required: true,
18
+ description: 'Entity type',
19
+ options: ['deal', 'person', 'org', 'organization', 'product'],
20
+ }),
21
+ field: Args.string({
22
+ required: true,
23
+ description: 'Field code (hashed key)',
24
+ }),
25
+ }
26
+
27
+ static flags = {
28
+ ...BaseCommand.baseFlags,
29
+ option: Flags.string({
30
+ required: true,
31
+ description: 'Option ID to remove (see field get)',
32
+ }),
33
+ yes: Flags.boolean({
34
+ char: 'y',
35
+ description: 'Skip the confirmation prompt',
36
+ default: false,
37
+ }),
38
+ }
39
+
40
+ async run() {
41
+ const { args, flags } = await this.parse(FieldOptionRemoveCommand)
42
+
43
+ const optionId = Number(flags.option)
44
+ if (!Number.isInteger(optionId)) {
45
+ throw new CliError(`Invalid option id: "${flags.option}"`, {
46
+ exitCode: 64,
47
+ })
48
+ }
49
+
50
+ const ok = await confirmAction(
51
+ `Remove option ${optionId} from field ${args.field} on ${args.entity}? ` +
52
+ `Records using it lose the value.`,
53
+ flags.yes,
54
+ { default: false },
55
+ )
56
+ if (!ok) {
57
+ throw new CliError('Aborted', { exitCode: 1 })
58
+ }
59
+
60
+ const res = await this.apiClient.del(
61
+ `${entityToFieldsPath(args.entity)}/${args.field}/options`,
62
+ { body: [{ id: optionId }] },
63
+ )
64
+
65
+ clearFieldsCache()
66
+
67
+ await this.outputResults(res.data ?? [], {
68
+ id: { header: 'ID' },
69
+ label: { header: 'Label' },
70
+ })
71
+ }
72
+ }
@@ -0,0 +1,53 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { clearFieldsCache, entityToFieldsPath } from '../../lib/fields.js'
4
+
5
+ export default class FieldUpdateCommand extends BaseCommand {
6
+ static description =
7
+ 'Update a custom field (field_code and field_type cannot change)'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> field update deal dcf558aac1ae4e8c4f849ba5e668430d8df9be12 --name "New name"',
11
+ ]
12
+
13
+ static args = {
14
+ entity: Args.string({
15
+ required: true,
16
+ description: 'Entity type',
17
+ options: ['deal', 'person', 'org', 'organization', 'product'],
18
+ }),
19
+ field: Args.string({
20
+ required: true,
21
+ description: 'Field code (hashed key)',
22
+ }),
23
+ }
24
+
25
+ static flags = {
26
+ ...BaseCommand.baseFlags,
27
+ name: Flags.string({
28
+ required: true,
29
+ description: 'New field name (label)',
30
+ }),
31
+ }
32
+
33
+ async run() {
34
+ const { args, flags } = await this.parse(FieldUpdateCommand)
35
+
36
+ const path = `${entityToFieldsPath(args.entity)}/${args.field}`
37
+ const res = await this.apiClient.patch(path, {
38
+ body: { field_name: flags.name },
39
+ })
40
+
41
+ clearFieldsCache()
42
+
43
+ await this.outputResults(res.data, {
44
+ field_code: { header: 'Key' },
45
+ field_name: { header: 'Name' },
46
+ field_type: { header: 'Type' },
47
+ options: {
48
+ header: 'Options',
49
+ get: (row) => row.options?.map((o) => `${o.id}=${o.label}`).join(', '),
50
+ },
51
+ })
52
+ }
53
+ }
@@ -0,0 +1,41 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import BaseCommand from '../../base-command.js'
4
+ import { confirmAction } from '../../lib/confirm.js'
5
+ import { CliError } from '../../lib/errors.js'
6
+
7
+ export default class FileDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a file'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> file delete 5',
12
+ '<%= config.bin %> file delete 5 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'File ID' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ yes: Flags.boolean({
22
+ char: 'y',
23
+ description: 'Skip the confirmation prompt',
24
+ default: false,
25
+ }),
26
+ }
27
+
28
+ async run() {
29
+ const { args, flags } = await this.parse(FileDeleteCommand)
30
+
31
+ const ok = await confirmAction(`Delete file ${args.id}?`, flags.yes, {
32
+ default: false,
33
+ })
34
+ if (!ok) {
35
+ throw new CliError('Aborted', { exitCode: 1 })
36
+ }
37
+
38
+ await this.apiClient.del(`/api/v1/files/${args.id}`)
39
+ this.log(chalk.green(`Deleted file ${args.id}`))
40
+ }
41
+ }
@@ -0,0 +1,39 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { outputRecord } from '../../lib/entity-view.js'
4
+ import { CliError } from '../../lib/errors.js'
5
+
6
+ export default class FileUpdateCommand extends BaseCommand {
7
+ static description = 'Update a file name and/or description'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> file update 5 --name report.pdf',
11
+ '<%= config.bin %> file update 5 --description "Signed contract"',
12
+ ]
13
+
14
+ static args = {
15
+ id: Args.integer({ required: true, description: 'File ID' }),
16
+ }
17
+
18
+ static flags = {
19
+ ...BaseCommand.baseFlags,
20
+ name: Flags.string({ description: 'The visible name of the file' }),
21
+ description: Flags.string({ description: 'The description of the file' }),
22
+ }
23
+
24
+ async run() {
25
+ const { args, flags } = await this.parse(FileUpdateCommand)
26
+
27
+ if (flags.name == null && flags.description == null) {
28
+ throw new CliError('Pass at least one of --name or --description', {
29
+ exitCode: 64,
30
+ })
31
+ }
32
+
33
+ const res = await this.apiClient.putForm(`/api/v1/files/${args.id}`, {
34
+ name: flags.name,
35
+ description: flags.description,
36
+ })
37
+ await outputRecord(this, res.data)
38
+ }
39
+ }
@@ -0,0 +1,41 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import BaseCommand from '../../base-command.js'
4
+ import { confirmAction } from '../../lib/confirm.js'
5
+ import { CliError } from '../../lib/errors.js'
6
+
7
+ export default class FilterDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a filter'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> filter delete 5',
12
+ '<%= config.bin %> filter delete 5 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Filter ID' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ yes: Flags.boolean({
22
+ char: 'y',
23
+ description: 'Skip the confirmation prompt',
24
+ default: false,
25
+ }),
26
+ }
27
+
28
+ async run() {
29
+ const { args, flags } = await this.parse(FilterDeleteCommand)
30
+
31
+ const ok = await confirmAction(`Delete filter ${args.id}?`, flags.yes, {
32
+ default: false,
33
+ })
34
+ if (!ok) {
35
+ throw new CliError('Aborted', { exitCode: 1 })
36
+ }
37
+
38
+ await this.apiClient.del(`/api/v1/filters/${args.id}`)
39
+ this.log(chalk.green(`Deleted filter ${args.id}`))
40
+ }
41
+ }
@@ -0,0 +1,103 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import BaseCommand from '../../base-command.js'
4
+ import { CliError } from '../../lib/errors.js'
5
+
6
+ const POLL_INTERVAL_MS = 2000
7
+
8
+ function defaultSleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms))
10
+ }
11
+
12
+ export default class LeadConvertCommand extends BaseCommand {
13
+ static description =
14
+ 'Convert a lead to a deal. The conversion runs as an async job; ' +
15
+ 'use --wait to poll until it finishes. On success the lead is deleted.'
16
+
17
+ static examples = [
18
+ '<%= config.bin %> lead convert adf21080-0e10-11eb-879b-05d71fb426ec',
19
+ '<%= config.bin %> lead convert adf21080-0e10-11eb-879b-05d71fb426ec --stage 7',
20
+ '<%= config.bin %> lead convert adf21080-0e10-11eb-879b-05d71fb426ec --wait',
21
+ ]
22
+
23
+ static args = {
24
+ id: Args.string({ required: true, description: 'Lead ID (UUID)' }),
25
+ }
26
+
27
+ static flags = {
28
+ ...BaseCommand.baseFlags,
29
+ stage: Flags.integer({
30
+ description: 'Stage ID for the new deal (a pipeline is inferred from it)',
31
+ }),
32
+ pipeline: Flags.integer({
33
+ description: 'Pipeline ID for the new deal (ignored when --stage is set)',
34
+ }),
35
+ wait: Flags.boolean({
36
+ description: 'Poll the conversion status until it finishes',
37
+ default: false,
38
+ }),
39
+ 'timeout-secs': Flags.integer({
40
+ description: 'Max seconds to poll when --wait is set',
41
+ default: 30,
42
+ }),
43
+ }
44
+
45
+ /** Overridable in tests so polling never waits in real time. */
46
+ static sleepFn = defaultSleep
47
+
48
+ async run() {
49
+ const { args, flags } = await this.parse(LeadConvertCommand)
50
+
51
+ const body = {}
52
+ if (flags.stage !== undefined) body.stage_id = flags.stage
53
+ if (flags.pipeline !== undefined) body.pipeline_id = flags.pipeline
54
+
55
+ const res = await this.apiClient.post(
56
+ `/api/v2/leads/${args.id}/convert/deal`,
57
+ { body },
58
+ )
59
+ const conversionId = res.data?.conversion_id
60
+
61
+ if (!flags.wait) {
62
+ this.log(chalk.green(`Conversion started: ${conversionId}`))
63
+ this.log(
64
+ `Check status: ${this.config.bin} api GET ` +
65
+ `/api/v2/leads/${args.id}/convert/status/${conversionId}`,
66
+ )
67
+ return
68
+ }
69
+
70
+ const timeoutMs = flags['timeout-secs'] * 1000
71
+ const sleep = LeadConvertCommand.sleepFn
72
+ let elapsed = 0
73
+ while (true) {
74
+ const status = await this.apiClient.get(
75
+ `/api/v2/leads/${args.id}/convert/status/${conversionId}`,
76
+ )
77
+ const state = status.data?.status
78
+ if (state === 'completed') {
79
+ this.log(
80
+ chalk.green(
81
+ `Conversion completed: lead ${args.id} → deal ${status.data?.deal_id}`,
82
+ ),
83
+ )
84
+ return
85
+ }
86
+ if (state === 'failed' || state === 'rejected') {
87
+ throw new CliError(`Conversion ${state} for lead ${args.id}`, {
88
+ exitCode: 70,
89
+ })
90
+ }
91
+ if (elapsed + POLL_INTERVAL_MS > timeoutMs) {
92
+ throw new CliError(
93
+ `Timed out after ${flags['timeout-secs']}s waiting for conversion ` +
94
+ `${conversionId} (last status: ${state}). ` +
95
+ `Check status: ${this.config.bin} api GET /api/v2/leads/${args.id}/convert/status/${conversionId}`,
96
+ { exitCode: 1 },
97
+ )
98
+ }
99
+ await sleep(POLL_INTERVAL_MS)
100
+ elapsed += POLL_INTERVAL_MS
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,31 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { outputRecord } from '../../../lib/entity-view.js'
4
+
5
+ export default class NoteCommentAddCommand extends BaseCommand {
6
+ static description = 'Add a comment to a note'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> note comment add 5 --content "Nice work"',
10
+ '<%= config.bin %> note comment add 5 --content "Reviewed" --output json',
11
+ ]
12
+
13
+ static args = {
14
+ noteId: Args.integer({ required: true, description: 'Note ID' }),
15
+ }
16
+
17
+ static flags = {
18
+ ...BaseCommand.baseFlags,
19
+ content: Flags.string({ required: true, description: 'Comment content' }),
20
+ }
21
+
22
+ async run() {
23
+ const { args, flags } = await this.parse(NoteCommentAddCommand)
24
+
25
+ const res = await this.apiClient.post(
26
+ `/api/v1/notes/${args.noteId}/comments`,
27
+ { body: { content: flags.content } },
28
+ )
29
+ await outputRecord(this, res.data)
30
+ }
31
+ }
@@ -0,0 +1,51 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import BaseCommand from '../../../base-command.js'
4
+ import { confirmAction } from '../../../lib/confirm.js'
5
+ import { CliError } from '../../../lib/errors.js'
6
+
7
+ export default class NoteCommentDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a comment from a note'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> note comment delete 5 --comment <uuid>',
12
+ '<%= config.bin %> note comment delete 5 --comment <uuid> --yes',
13
+ ]
14
+
15
+ static args = {
16
+ noteId: Args.integer({ required: true, description: 'Note ID' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ comment: Flags.string({
22
+ required: true,
23
+ description: 'Comment ID (UUID)',
24
+ }),
25
+ yes: Flags.boolean({
26
+ char: 'y',
27
+ description: 'Skip the confirmation prompt',
28
+ default: false,
29
+ }),
30
+ }
31
+
32
+ async run() {
33
+ const { args, flags } = await this.parse(NoteCommentDeleteCommand)
34
+
35
+ const ok = await confirmAction(
36
+ `Delete comment ${flags.comment} from note ${args.noteId}?`,
37
+ flags.yes,
38
+ { default: false },
39
+ )
40
+ if (!ok) {
41
+ throw new CliError('Aborted', { exitCode: 1 })
42
+ }
43
+
44
+ await this.apiClient.del(
45
+ `/api/v1/notes/${args.noteId}/comments/${flags.comment}`,
46
+ )
47
+ this.log(
48
+ chalk.green(`Deleted comment ${flags.comment} from note ${args.noteId}`),
49
+ )
50
+ }
51
+ }
@@ -0,0 +1,43 @@
1
+ import { Args } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { collectPages } from '../../../lib/pagination.js'
4
+
5
+ const columns = {
6
+ uuid: { header: 'ID' },
7
+ content: {
8
+ header: 'Content',
9
+ get: (row) => (row.content ?? '').slice(0, 60),
10
+ },
11
+ user_id: { header: 'User' },
12
+ add_time: { header: 'Created' },
13
+ }
14
+
15
+ export default class NoteCommentListCommand extends BaseCommand {
16
+ static description = 'List comments on a note'
17
+
18
+ static examples = [
19
+ '<%= config.bin %> note comment list 5',
20
+ '<%= config.bin %> note comment list 5 --output json',
21
+ ]
22
+
23
+ static args = {
24
+ noteId: Args.integer({ required: true, description: 'Note ID' }),
25
+ }
26
+
27
+ static flags = {
28
+ ...BaseCommand.baseFlags,
29
+ }
30
+
31
+ async run() {
32
+ const { args, flags } = await this.parse(NoteCommentListCommand)
33
+ const limit = flags.limit ?? 100
34
+
35
+ const query = { limit: Math.min(limit, 100) }
36
+
37
+ const items = await collectPages(
38
+ this.apiClient.pageV1(`/api/v1/notes/${args.noteId}/comments`, query),
39
+ limit,
40
+ )
41
+ await this.outputResults(items, columns)
42
+ }
43
+ }
@@ -0,0 +1,37 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { outputRecord } from '../../../lib/entity-view.js'
4
+
5
+ export default class NoteCommentUpdateCommand extends BaseCommand {
6
+ static description = 'Update a comment on a note'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> note comment update 5 --comment <uuid> --content "Edited"',
10
+ ]
11
+
12
+ static args = {
13
+ noteId: Args.integer({ required: true, description: 'Note ID' }),
14
+ }
15
+
16
+ static flags = {
17
+ ...BaseCommand.baseFlags,
18
+ comment: Flags.string({
19
+ required: true,
20
+ description: 'Comment ID (UUID)',
21
+ }),
22
+ content: Flags.string({
23
+ required: true,
24
+ description: 'New comment content',
25
+ }),
26
+ }
27
+
28
+ async run() {
29
+ const { args, flags } = await this.parse(NoteCommentUpdateCommand)
30
+
31
+ const res = await this.apiClient.put(
32
+ `/api/v1/notes/${args.noteId}/comments/${flags.comment}`,
33
+ { body: { content: flags.content } },
34
+ )
35
+ await outputRecord(this, res.data)
36
+ }
37
+ }
@@ -0,0 +1,31 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { outputRecord } from '../../../lib/entity-view.js'
4
+
5
+ export default class OrgFollowerAddCommand extends BaseCommand {
6
+ static description = 'Add a follower (user) to an organization'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> org follower add 42 --user 5',
10
+ '<%= config.bin %> org follower add 42 --user 5 --output json',
11
+ ]
12
+
13
+ static args = {
14
+ id: Args.integer({ required: true, description: 'Organization ID' }),
15
+ }
16
+
17
+ static flags = {
18
+ ...BaseCommand.baseFlags,
19
+ user: Flags.integer({ required: true, description: 'User ID' }),
20
+ }
21
+
22
+ async run() {
23
+ const { args, flags } = await this.parse(OrgFollowerAddCommand)
24
+
25
+ const res = await this.apiClient.post(
26
+ `/api/v2/organizations/${args.id}/followers`,
27
+ { body: { user_id: flags.user } },
28
+ )
29
+ await outputRecord(this, res.data)
30
+ }
31
+ }
@@ -0,0 +1,41 @@
1
+ import { Args } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { collectPages } from '../../../lib/pagination.js'
4
+
5
+ const columns = {
6
+ user_id: { header: 'User' },
7
+ add_time: { header: 'Added' },
8
+ }
9
+
10
+ export default class OrgFollowerListCommand extends BaseCommand {
11
+ static description = 'List followers of an organization'
12
+
13
+ static examples = [
14
+ '<%= config.bin %> org follower list 42',
15
+ '<%= config.bin %> org follower list 42 --output json',
16
+ ]
17
+
18
+ static args = {
19
+ id: Args.integer({ required: true, description: 'Organization ID' }),
20
+ }
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ }
25
+
26
+ async run() {
27
+ const { args, flags } = await this.parse(OrgFollowerListCommand)
28
+ const limit = flags.limit ?? 500
29
+
30
+ const query = { limit: Math.min(limit, 500) }
31
+
32
+ const items = await collectPages(
33
+ this.apiClient.pageV2(
34
+ `/api/v2/organizations/${args.id}/followers`,
35
+ query,
36
+ ),
37
+ limit,
38
+ )
39
+ await this.outputResults(items, columns)
40
+ }
41
+ }