@wavyx/pdcli 0.9.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 (61) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +15 -0
  3. package/oclif.manifest.json +7590 -1565
  4. package/package.json +32 -2
  5. package/src/commands/activity/list.js +47 -5
  6. package/src/commands/activity/type/list.js +36 -0
  7. package/src/commands/deal/convert.js +112 -0
  8. package/src/commands/deal/follower/add.js +31 -0
  9. package/src/commands/deal/follower/list.js +38 -0
  10. package/src/commands/deal/follower/remove.js +44 -0
  11. package/src/commands/deal/history.js +73 -0
  12. package/src/commands/deal/list.js +47 -6
  13. package/src/commands/deal/participant/add.js +31 -0
  14. package/src/commands/deal/participant/list.js +46 -0
  15. package/src/commands/deal/participant/remove.js +53 -0
  16. package/src/commands/deal/product/add.js +69 -0
  17. package/src/commands/deal/product/list.js +56 -0
  18. package/src/commands/deal/product/remove.js +52 -0
  19. package/src/commands/deal/product/update.js +78 -0
  20. package/src/commands/deal/summary.js +69 -0
  21. package/src/commands/field/create.js +72 -0
  22. package/src/commands/field/delete.js +56 -0
  23. package/src/commands/field/get.js +10 -1
  24. package/src/commands/field/list.js +10 -1
  25. package/src/commands/field/option/add.js +49 -0
  26. package/src/commands/field/option/remove.js +72 -0
  27. package/src/commands/field/update.js +53 -0
  28. package/src/commands/file/delete.js +41 -0
  29. package/src/commands/file/update.js +39 -0
  30. package/src/commands/filter/delete.js +41 -0
  31. package/src/commands/funnel.js +7 -49
  32. package/src/commands/lead/convert.js +103 -0
  33. package/src/commands/note/comment/add.js +31 -0
  34. package/src/commands/note/comment/delete.js +51 -0
  35. package/src/commands/note/comment/list.js +43 -0
  36. package/src/commands/note/comment/update.js +37 -0
  37. package/src/commands/org/follower/add.js +31 -0
  38. package/src/commands/org/follower/list.js +41 -0
  39. package/src/commands/org/follower/remove.js +50 -0
  40. package/src/commands/org/list.js +40 -2
  41. package/src/commands/org/relationship/add.js +44 -0
  42. package/src/commands/org/relationship/list.js +52 -0
  43. package/src/commands/org/relationship/remove.js +46 -0
  44. package/src/commands/person/follower/add.js +31 -0
  45. package/src/commands/person/follower/list.js +38 -0
  46. package/src/commands/person/follower/remove.js +48 -0
  47. package/src/commands/person/list.js +40 -2
  48. package/src/commands/product/list.js +35 -2
  49. package/src/commands/project/list.js +10 -4
  50. package/src/commands/search.js +66 -9
  51. package/src/commands/task/create.js +44 -0
  52. package/src/commands/task/delete.js +41 -0
  53. package/src/commands/task/get.js +26 -0
  54. package/src/commands/task/list.js +60 -0
  55. package/src/commands/task/update.js +72 -0
  56. package/src/commands/user/find.js +50 -0
  57. package/src/commands/user/list.js +36 -0
  58. package/src/commands/webhook/create.js +8 -2
  59. package/src/lib/changelog.js +85 -0
  60. package/src/lib/client.js +44 -0
  61. package/src/lib/fields.js +43 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavyx/pdcli",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -65,8 +65,14 @@
65
65
  "activity": {
66
66
  "description": "Activities (calls, meetings, tasks)"
67
67
  },
68
+ "activity:type": {
69
+ "description": "Activity types (key_string is what activity --type takes)"
70
+ },
71
+ "task": {
72
+ "description": "Tasks (project action items)"
73
+ },
68
74
  "field": {
69
- "description": "Custom-field discovery and name/key resolution"
75
+ "description": "Custom-field management: discovery, name/key resolution, create/update/delete"
70
76
  },
71
77
  "metrics": {
72
78
  "description": "Sales metrics and KPIs"
@@ -82,6 +88,30 @@
82
88
  },
83
89
  "alias": {
84
90
  "description": "Command shortcuts (alias set/list/unset)"
91
+ },
92
+ "deal:product": {
93
+ "description": "Manage products (line items) on a deal"
94
+ },
95
+ "deal:participant": {
96
+ "description": "Manage extra people on a deal (buying committee)"
97
+ },
98
+ "deal:follower": {
99
+ "description": "Manage deal followers"
100
+ },
101
+ "person:follower": {
102
+ "description": "Manage person followers"
103
+ },
104
+ "org:follower": {
105
+ "description": "Manage organization followers"
106
+ },
107
+ "org:relationship": {
108
+ "description": "Manage parent/related links between organizations"
109
+ },
110
+ "field:option": {
111
+ "description": "Manage enum/set field options"
112
+ },
113
+ "note:comment": {
114
+ "description": "Manage comments on a note"
85
115
  }
86
116
  },
87
117
  "hooks": {
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core'
2
2
  import BaseCommand from '../../base-command.js'
3
3
  import { collectPages } from '../../lib/pagination.js'
4
+ import { CliError } from '../../lib/errors.js'
4
5
 
5
6
  const columns = {
6
7
  id: { header: 'ID' },
@@ -27,7 +28,9 @@ export default class ActivityListCommand extends BaseCommand {
27
28
  deal: Flags.integer({ description: 'Filter by deal ID' }),
28
29
  person: Flags.integer({ description: 'Filter by person ID' }),
29
30
  org: Flags.integer({ description: 'Filter by organization ID' }),
30
- type: Flags.string({ description: 'Filter by activity type key' }),
31
+ type: Flags.string({
32
+ description: 'Filter by activity type key (applied client-side)',
33
+ }),
31
34
  done: Flags.boolean({
32
35
  description: 'Only completed activities',
33
36
  exclusive: ['todo'],
@@ -36,26 +39,65 @@ export default class ActivityListCommand extends BaseCommand {
36
39
  description: 'Only open (not done) activities',
37
40
  exclusive: ['done'],
38
41
  }),
42
+ filter: Flags.integer({ description: 'Filter by saved filter ID' }),
43
+ ids: Flags.string({
44
+ description: 'Comma-separated IDs to fetch (max 100)',
45
+ // The API silently drops `ids` when filter_id is present — refuse
46
+ // the combination instead (matches deal bulk-update).
47
+ exclusive: ['filter'],
48
+ }),
49
+ 'sort-by': Flags.string({
50
+ description: 'Sort field',
51
+ options: ['id', 'update_time', 'add_time', 'due_date'],
52
+ }),
53
+ 'sort-direction': Flags.string({
54
+ description: 'Sort direction',
55
+ options: ['asc', 'desc'],
56
+ }),
57
+ 'updated-since': Flags.string({
58
+ description:
59
+ 'Only items updated at/after this RFC3339 time (no fractional seconds)',
60
+ }),
61
+ 'updated-until': Flags.string({
62
+ description:
63
+ 'Only items updated before this RFC3339 time (no fractional seconds)',
64
+ }),
39
65
  }
40
66
 
41
67
  async run() {
42
68
  const { flags } = await this.parse(ActivityListCommand)
43
- const limit = flags.limit ?? 100
69
+ const limit = flags.limit ?? 500
70
+
71
+ const idList = flags.ids
72
+ ?.split(',')
73
+ .map((v) => v.trim())
74
+ .filter(Boolean)
75
+ if (idList && idList.length > 100) {
76
+ throw new CliError('--ids accepts at most 100 IDs', { exitCode: 64 })
77
+ }
44
78
 
45
79
  const query = {
46
80
  owner_id: flags.owner,
47
81
  deal_id: flags.deal,
48
82
  person_id: flags.person,
49
83
  org_id: flags.org,
50
- type: flags.type,
51
84
  done: flags.done ? true : flags.todo ? false : undefined,
52
- limit: Math.min(limit, 100),
85
+ filter_id: flags.filter,
86
+ ids: idList?.join(','),
87
+ sort_by: flags['sort-by'],
88
+ sort_direction: flags['sort-direction'],
89
+ updated_since: flags['updated-since'],
90
+ updated_until: flags['updated-until'],
91
+ limit: Math.min(limit, 500),
53
92
  }
54
93
 
55
- const items = await collectPages(
94
+ let items = await collectPages(
56
95
  this.apiClient.pageV2('/api/v2/activities', query),
57
96
  limit,
58
97
  )
98
+ // The v2 activities endpoint has no `type` query param (it rejects
99
+ // unknown params with a 400) — filter client-side instead.
100
+ if (flags.type) items = items.filter((a) => a.type === flags.type)
59
101
  await this.outputResults(items, columns, { entity: 'activity' })
60
102
  }
61
103
  }
@@ -0,0 +1,36 @@
1
+ import BaseCommand from '../../../base-command.js'
2
+
3
+ const columns = {
4
+ id: { header: 'ID' },
5
+ key_string: { header: 'Key' },
6
+ name: { header: 'Name' },
7
+ active: {
8
+ header: 'Active',
9
+ get: (row) => (row.active_flag ? 'yes' : 'no'),
10
+ },
11
+ }
12
+
13
+ export default class ActivityTypeListCommand extends BaseCommand {
14
+ static description =
15
+ 'List activity types. The Key (key_string) is what `activity --type` takes.'
16
+
17
+ static examples = [
18
+ '<%= config.bin %> activity type list',
19
+ '<%= config.bin %> activity type list --output json',
20
+ ]
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ }
25
+
26
+ async run() {
27
+ const { flags } = await this.parse(ActivityTypeListCommand)
28
+
29
+ // Activity types are v1-only and returned in one unpaginated array.
30
+ const body = await this.apiClient.get('/api/v1/activityTypes')
31
+ let types = body.data ?? []
32
+ if (flags.limit != null) types = types.slice(0, flags.limit)
33
+
34
+ await this.outputResults(types, columns)
35
+ }
36
+ }
@@ -0,0 +1,112 @@
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
+ const POLL_INTERVAL_MS = 2000
8
+
9
+ function defaultSleep(ms) {
10
+ return new Promise((resolve) => setTimeout(resolve, ms))
11
+ }
12
+
13
+ export default class DealConvertCommand extends BaseCommand {
14
+ static description =
15
+ 'Convert a deal to a lead. The conversion runs as an async job; ' +
16
+ 'use --wait to poll until it finishes. WARNING: on success the source ' +
17
+ 'deal is deleted.'
18
+
19
+ static examples = [
20
+ '<%= config.bin %> deal convert 42',
21
+ '<%= config.bin %> deal convert 42 --yes',
22
+ '<%= config.bin %> deal convert 42 --wait',
23
+ ]
24
+
25
+ static args = {
26
+ id: Args.integer({ required: true, description: 'Deal ID' }),
27
+ }
28
+
29
+ static flags = {
30
+ ...BaseCommand.baseFlags,
31
+ wait: Flags.boolean({
32
+ description: 'Poll the conversion status until it finishes',
33
+ default: false,
34
+ }),
35
+ 'timeout-secs': Flags.integer({
36
+ description: 'Max seconds to poll when --wait is set',
37
+ default: 30,
38
+ }),
39
+ yes: Flags.boolean({
40
+ char: 'y',
41
+ description: 'Skip the confirmation prompt',
42
+ default: false,
43
+ }),
44
+ }
45
+
46
+ /** Overridable in tests so polling never waits in real time. */
47
+ static sleepFn = defaultSleep
48
+
49
+ async run() {
50
+ const { args, flags } = await this.parse(DealConvertCommand)
51
+
52
+ const ok = await confirmAction(
53
+ `Convert deal ${args.id} to a lead? Deal ${args.id} will be DELETED on success.`,
54
+ flags.yes,
55
+ { default: false },
56
+ )
57
+ if (!ok) {
58
+ throw new CliError('Aborted', { exitCode: 1 })
59
+ }
60
+
61
+ const res = await this.apiClient.post(
62
+ `/api/v2/deals/${args.id}/convert/lead`,
63
+ // The endpoint takes no parameters, but the client sets a JSON
64
+ // content-type — the API 400s on an empty body, so send {}.
65
+ { body: {} },
66
+ )
67
+ const conversionId = res.data?.conversion_id
68
+
69
+ if (!flags.wait) {
70
+ this.log(chalk.green(`Conversion started: ${conversionId}`))
71
+ this.log(
72
+ `Check status: ${this.config.bin} api GET ` +
73
+ `/api/v2/deals/${args.id}/convert/status/${conversionId}`,
74
+ )
75
+ return
76
+ }
77
+
78
+ const timeoutMs = flags['timeout-secs'] * 1000
79
+ const sleep = DealConvertCommand.sleepFn
80
+ let elapsed = 0
81
+ while (true) {
82
+ const status = await this.apiClient.get(
83
+ `/api/v2/deals/${args.id}/convert/status/${conversionId}`,
84
+ )
85
+ const state = status.data?.status
86
+ if (state === 'completed') {
87
+ this.log(
88
+ chalk.green(
89
+ `Conversion completed: deal ${args.id} → lead ${status.data?.lead_id}`,
90
+ ),
91
+ )
92
+ return
93
+ }
94
+ if (state === 'failed' || state === 'rejected') {
95
+ throw new CliError(`Conversion ${state} for deal ${args.id}`, {
96
+ exitCode: 70,
97
+ })
98
+ }
99
+ if (elapsed + POLL_INTERVAL_MS > timeoutMs) {
100
+ throw new CliError(
101
+ `Timed out after ${flags['timeout-secs']}s waiting for conversion ` +
102
+ `${conversionId} (last status: ${state}). ` +
103
+ `Check status: ${this.config.bin} api GET ` +
104
+ `/api/v2/deals/${args.id}/convert/status/${conversionId}`,
105
+ { exitCode: 1 },
106
+ )
107
+ }
108
+ await sleep(POLL_INTERVAL_MS)
109
+ elapsed += POLL_INTERVAL_MS
110
+ }
111
+ }
112
+ }
@@ -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 DealFollowerAddCommand extends BaseCommand {
6
+ static description = 'Add a follower (user) to a deal'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> deal follower add 42 --user 5',
10
+ '<%= config.bin %> deal follower add 42 --user 5 --output json',
11
+ ]
12
+
13
+ static args = {
14
+ id: Args.integer({ required: true, description: 'Deal 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(DealFollowerAddCommand)
24
+
25
+ const res = await this.apiClient.post(
26
+ `/api/v2/deals/${args.id}/followers`,
27
+ { body: { user_id: flags.user } },
28
+ )
29
+ await outputRecord(this, res.data)
30
+ }
31
+ }
@@ -0,0 +1,38 @@
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 DealFollowerListCommand extends BaseCommand {
11
+ static description = 'List followers of a deal'
12
+
13
+ static examples = [
14
+ '<%= config.bin %> deal follower list 42',
15
+ '<%= config.bin %> deal follower list 42 --output json',
16
+ ]
17
+
18
+ static args = {
19
+ id: Args.integer({ required: true, description: 'Deal ID' }),
20
+ }
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ }
25
+
26
+ async run() {
27
+ const { args, flags } = await this.parse(DealFollowerListCommand)
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(`/api/v2/deals/${args.id}/followers`, query),
34
+ limit,
35
+ )
36
+ await this.outputResults(items, columns)
37
+ }
38
+ }
@@ -0,0 +1,44 @@
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 DealFollowerRemoveCommand extends BaseCommand {
8
+ static description = 'Remove a follower from a deal'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> deal follower remove 42 --user 5',
12
+ '<%= config.bin %> deal follower remove 42 --user 5 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Deal ID' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ user: Flags.integer({ required: true, description: 'User ID' }),
22
+ yes: Flags.boolean({
23
+ char: 'y',
24
+ description: 'Skip the confirmation prompt',
25
+ default: false,
26
+ }),
27
+ }
28
+
29
+ async run() {
30
+ const { args, flags } = await this.parse(DealFollowerRemoveCommand)
31
+
32
+ const ok = await confirmAction(
33
+ `Remove follower ${flags.user} from deal ${args.id}?`,
34
+ flags.yes,
35
+ { default: false },
36
+ )
37
+ if (!ok) {
38
+ throw new CliError('Aborted', { exitCode: 1 })
39
+ }
40
+
41
+ await this.apiClient.del(`/api/v2/deals/${args.id}/followers/${flags.user}`)
42
+ this.log(chalk.green(`Removed follower ${flags.user} from deal ${args.id}`))
43
+ }
44
+ }
@@ -0,0 +1,73 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { fetchChangelog } from '../../lib/changelog.js'
4
+ import { getFields, makeResolver } from '../../lib/fields.js'
5
+
6
+ const columns = {
7
+ time: { header: 'Time' },
8
+ field_key: { header: 'Field' },
9
+ old_value: { header: 'Old' },
10
+ new_value: { header: 'New' },
11
+ actor_user_id: { header: 'Actor' },
12
+ }
13
+
14
+ export default class DealHistoryCommand extends BaseCommand {
15
+ static description =
16
+ 'Field-change history for a deal, newest-first (the API’s native order)'
17
+
18
+ static examples = [
19
+ '<%= config.bin %> deal history 42',
20
+ '<%= config.bin %> deal history 42 --field stage_id',
21
+ '<%= config.bin %> deal history 42 --limit 20 --resolve-fields',
22
+ ]
23
+
24
+ static args = {
25
+ id: Args.integer({ required: true, description: 'Deal ID' }),
26
+ }
27
+
28
+ static flags = {
29
+ ...BaseCommand.baseFlags,
30
+ field: Flags.string({
31
+ description: 'Show only changes to this field key (e.g. stage_id)',
32
+ }),
33
+ }
34
+
35
+ async run() {
36
+ const { args, flags } = await this.parse(DealHistoryCommand)
37
+
38
+ // A field filter drops rows client-side, so the fetch can't be bounded
39
+ // by --limit in that case; otherwise cap the API page directly.
40
+ const fetchLimit = flags.field ? undefined : flags.limit
41
+ let rows = await fetchChangelog(this.apiClient, args.id, {
42
+ limit: fetchLimit,
43
+ })
44
+
45
+ if (flags.field) {
46
+ rows = rows.filter((r) => r.field_key === flags.field)
47
+ }
48
+ if (flags.limit != null) {
49
+ rows = rows.slice(0, flags.limit)
50
+ }
51
+
52
+ // The Field column is the one place hash keys appear as DATA — resolve
53
+ // them (and enum/set option ids) to names under --resolve-fields.
54
+ if (flags['resolve-fields'] && rows.length > 0) {
55
+ const resolver = makeResolver(await getFields(this.apiClient, 'deal'))
56
+ // changelog values are stringified — option ids resolve numerically
57
+ const label = (key, value) =>
58
+ resolver.optionIdToLabel(key, Number(value)) ?? value
59
+ rows = rows.map((r) => {
60
+ const name = resolver.keyToName(r.field_key)
61
+ if (!name) return r
62
+ return {
63
+ ...r,
64
+ field_key: name,
65
+ old_value: label(r.field_key, r.old_value),
66
+ new_value: label(r.field_key, r.new_value),
67
+ }
68
+ })
69
+ }
70
+
71
+ await this.outputResults(rows, columns)
72
+ }
73
+ }
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core'
2
2
  import BaseCommand from '../../base-command.js'
3
3
  import { collectPages } from '../../lib/pagination.js'
4
+ import { CliError } from '../../lib/errors.js'
4
5
 
5
6
  const columns = {
6
7
  id: { header: 'ID' },
@@ -28,6 +29,10 @@ export default class DealListCommand extends BaseCommand {
28
29
 
29
30
  static flags = {
30
31
  ...BaseCommand.baseFlags,
32
+ archived: Flags.boolean({
33
+ description: 'List archived deals instead of active ones',
34
+ default: false,
35
+ }),
31
36
  status: Flags.string({
32
37
  description: 'Filter by status',
33
38
  options: ['open', 'won', 'lost', 'deleted'],
@@ -37,11 +42,42 @@ export default class DealListCommand extends BaseCommand {
37
42
  owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
38
43
  person: Flags.integer({ description: 'Filter by person ID' }),
39
44
  org: Flags.integer({ description: 'Filter by organization ID' }),
45
+ filter: Flags.integer({ description: 'Filter by saved filter ID' }),
46
+ ids: Flags.string({
47
+ description: 'Comma-separated IDs to fetch (max 100)',
48
+ // The API silently drops `ids` when filter_id is present — refuse
49
+ // the combination instead (matches deal bulk-update).
50
+ exclusive: ['filter'],
51
+ }),
52
+ 'sort-by': Flags.string({
53
+ description: 'Sort field',
54
+ options: ['id', 'update_time', 'add_time'],
55
+ }),
56
+ 'sort-direction': Flags.string({
57
+ description: 'Sort direction',
58
+ options: ['asc', 'desc'],
59
+ }),
60
+ 'updated-since': Flags.string({
61
+ description:
62
+ 'Only items updated at/after this RFC3339 time (no fractional seconds)',
63
+ }),
64
+ 'updated-until': Flags.string({
65
+ description:
66
+ 'Only items updated before this RFC3339 time (no fractional seconds)',
67
+ }),
40
68
  }
41
69
 
42
70
  async run() {
43
71
  const { flags } = await this.parse(DealListCommand)
44
- const limit = flags.limit ?? 100
72
+ const limit = flags.limit ?? 500
73
+
74
+ const idList = flags.ids
75
+ ?.split(',')
76
+ .map((v) => v.trim())
77
+ .filter(Boolean)
78
+ if (idList && idList.length > 100) {
79
+ throw new CliError('--ids accepts at most 100 IDs', { exitCode: 64 })
80
+ }
45
81
 
46
82
  const query = {
47
83
  status: flags.status,
@@ -50,13 +86,18 @@ export default class DealListCommand extends BaseCommand {
50
86
  owner_id: flags.owner,
51
87
  person_id: flags.person,
52
88
  org_id: flags.org,
53
- limit: Math.min(limit, 100),
89
+ filter_id: flags.filter,
90
+ ids: idList?.join(','),
91
+ sort_by: flags['sort-by'],
92
+ sort_direction: flags['sort-direction'],
93
+ updated_since: flags['updated-since'],
94
+ updated_until: flags['updated-until'],
95
+ limit: Math.min(limit, 500),
54
96
  }
55
97
 
56
- const items = await collectPages(
57
- this.apiClient.pageV2('/api/v2/deals', query),
58
- limit,
59
- )
98
+ // Archived deals share the same params and cursor pager as active deals.
99
+ const path = flags.archived ? '/api/v2/deals/archived' : '/api/v2/deals'
100
+ const items = await collectPages(this.apiClient.pageV2(path, query), limit)
60
101
  await this.outputResults(items, columns, { entity: 'deal' })
61
102
  }
62
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 DealParticipantAddCommand extends BaseCommand {
6
+ static description = 'Add a participant (person) to a deal'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> deal participant add 42 --person 10',
10
+ '<%= config.bin %> deal participant add 42 --person 10 --output json',
11
+ ]
12
+
13
+ static args = {
14
+ id: Args.integer({ required: true, description: 'Deal ID' }),
15
+ }
16
+
17
+ static flags = {
18
+ ...BaseCommand.baseFlags,
19
+ person: Flags.integer({ required: true, description: 'Person ID' }),
20
+ }
21
+
22
+ async run() {
23
+ const { args, flags } = await this.parse(DealParticipantAddCommand)
24
+
25
+ const res = await this.apiClient.post(
26
+ `/api/v1/deals/${args.id}/participants`,
27
+ { body: { person_id: flags.person } },
28
+ )
29
+ await outputRecord(this, res.data)
30
+ }
31
+ }
@@ -0,0 +1,46 @@
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
+ id: { header: 'ID' },
7
+ person_id: {
8
+ header: 'Person',
9
+ get: (row) => row.person_id?.value ?? '',
10
+ },
11
+ name: {
12
+ header: 'Name',
13
+ get: (row) => row.person_id?.name ?? '',
14
+ },
15
+ add_time: { header: 'Added' },
16
+ }
17
+
18
+ export default class DealParticipantListCommand extends BaseCommand {
19
+ static description = 'List participants of a deal'
20
+
21
+ static examples = [
22
+ '<%= config.bin %> deal participant list 42',
23
+ '<%= config.bin %> deal participant list 42 --output json',
24
+ ]
25
+
26
+ static args = {
27
+ id: Args.integer({ required: true, description: 'Deal ID' }),
28
+ }
29
+
30
+ static flags = {
31
+ ...BaseCommand.baseFlags,
32
+ }
33
+
34
+ async run() {
35
+ const { args, flags } = await this.parse(DealParticipantListCommand)
36
+ const limit = flags.limit ?? 100
37
+
38
+ const query = { limit: Math.min(limit, 500) }
39
+
40
+ const items = await collectPages(
41
+ this.apiClient.pageV1(`/api/v1/deals/${args.id}/participants`, query),
42
+ limit,
43
+ )
44
+ await this.outputResults(items, columns)
45
+ }
46
+ }