@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
@@ -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' },
@@ -20,15 +21,52 @@ export default class OrgListCommand extends BaseCommand {
20
21
  static flags = {
21
22
  ...BaseCommand.baseFlags,
22
23
  owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
24
+ filter: Flags.integer({ description: 'Filter by saved filter ID' }),
25
+ ids: Flags.string({
26
+ description: 'Comma-separated IDs to fetch (max 100)',
27
+ // The API silently drops `ids` when filter_id is present — refuse
28
+ // the combination instead (matches deal bulk-update).
29
+ exclusive: ['filter'],
30
+ }),
31
+ 'sort-by': Flags.string({
32
+ description: 'Sort field',
33
+ options: ['id', 'update_time', 'add_time'],
34
+ }),
35
+ 'sort-direction': Flags.string({
36
+ description: 'Sort direction',
37
+ options: ['asc', 'desc'],
38
+ }),
39
+ 'updated-since': Flags.string({
40
+ description:
41
+ 'Only items updated at/after this RFC3339 time (no fractional seconds)',
42
+ }),
43
+ 'updated-until': Flags.string({
44
+ description:
45
+ 'Only items updated before this RFC3339 time (no fractional seconds)',
46
+ }),
23
47
  }
24
48
 
25
49
  async run() {
26
50
  const { flags } = await this.parse(OrgListCommand)
27
- const limit = flags.limit ?? 100
51
+ const limit = flags.limit ?? 500
52
+
53
+ const idList = flags.ids
54
+ ?.split(',')
55
+ .map((v) => v.trim())
56
+ .filter(Boolean)
57
+ if (idList && idList.length > 100) {
58
+ throw new CliError('--ids accepts at most 100 IDs', { exitCode: 64 })
59
+ }
28
60
 
29
61
  const query = {
30
62
  owner_id: flags.owner,
31
- limit: Math.min(limit, 100),
63
+ filter_id: flags.filter,
64
+ ids: idList?.join(','),
65
+ sort_by: flags['sort-by'],
66
+ sort_direction: flags['sort-direction'],
67
+ updated_since: flags['updated-since'],
68
+ updated_until: flags['updated-until'],
69
+ limit: Math.min(limit, 500),
32
70
  }
33
71
 
34
72
  const items = await collectPages(
@@ -0,0 +1,44 @@
1
+ import { Flags } from '@oclif/core'
2
+ import BaseCommand from '../../../base-command.js'
3
+ import { outputRecord } from '../../../lib/entity-view.js'
4
+
5
+ export default class OrgRelationshipAddCommand extends BaseCommand {
6
+ static description =
7
+ 'Create an organization relationship. For a parent relationship the ' +
8
+ '--owner organization is the parent and --linked is the daughter.'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> org relationship add --type parent --owner 1481 --linked 1480',
12
+ '<%= config.bin %> org relationship add --type related --owner 1 --linked 2',
13
+ ]
14
+
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ type: Flags.string({
18
+ required: true,
19
+ description: 'Relationship type',
20
+ options: ['parent', 'related'],
21
+ }),
22
+ owner: Flags.integer({
23
+ required: true,
24
+ description: 'Owner organization ID (the parent for type parent)',
25
+ }),
26
+ linked: Flags.integer({
27
+ required: true,
28
+ description: 'Linked organization ID (the daughter for type parent)',
29
+ }),
30
+ }
31
+
32
+ async run() {
33
+ const { flags } = await this.parse(OrgRelationshipAddCommand)
34
+
35
+ const res = await this.apiClient.post('/api/v1/organizationRelationships', {
36
+ body: {
37
+ type: flags.type,
38
+ rel_owner_org_id: flags.owner,
39
+ rel_linked_org_id: flags.linked,
40
+ },
41
+ })
42
+ await outputRecord(this, res.data)
43
+ }
44
+ }
@@ -0,0 +1,52 @@
1
+ import { Flags } 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
+ type: { header: 'Type' },
8
+ owner: {
9
+ header: 'Owner Org',
10
+ get: (row) => row.rel_owner_org_id?.name ?? '',
11
+ },
12
+ linked: {
13
+ header: 'Linked Org',
14
+ get: (row) => row.rel_linked_org_id?.name ?? '',
15
+ },
16
+ related: {
17
+ header: 'Related',
18
+ get: (row) => row.related_organization_name ?? '',
19
+ },
20
+ }
21
+
22
+ export default class OrgRelationshipListCommand extends BaseCommand {
23
+ static description = 'List relationships for an organization'
24
+
25
+ static examples = [
26
+ '<%= config.bin %> org relationship list --org 1481',
27
+ '<%= config.bin %> org relationship list --org 1481 --output json',
28
+ ]
29
+
30
+ static flags = {
31
+ ...BaseCommand.baseFlags,
32
+ org: Flags.integer({
33
+ required: true,
34
+ description: 'Organization ID to list relationships for',
35
+ }),
36
+ }
37
+
38
+ async run() {
39
+ const { flags } = await this.parse(OrgRelationshipListCommand)
40
+ const limit = flags.limit ?? 500
41
+
42
+ // The endpoint takes only org_id (no server-side pagination) — the
43
+ // client-side cap below is what --limit actually controls.
44
+ const query = { org_id: flags.org }
45
+
46
+ const items = await collectPages(
47
+ this.apiClient.pageV1('/api/v1/organizationRelationships', query),
48
+ limit,
49
+ )
50
+ await this.outputResults(items, columns)
51
+ }
52
+ }
@@ -0,0 +1,46 @@
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 OrgRelationshipRemoveCommand extends BaseCommand {
8
+ static description = 'Delete an organization relationship'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> org relationship remove 7',
12
+ '<%= config.bin %> org relationship remove 7 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({
17
+ required: true,
18
+ description: 'Organization relationship ID',
19
+ }),
20
+ }
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ yes: Flags.boolean({
25
+ char: 'y',
26
+ description: 'Skip the confirmation prompt',
27
+ default: false,
28
+ }),
29
+ }
30
+
31
+ async run() {
32
+ const { args, flags } = await this.parse(OrgRelationshipRemoveCommand)
33
+
34
+ const ok = await confirmAction(
35
+ `Delete organization relationship ${args.id}?`,
36
+ flags.yes,
37
+ { default: false },
38
+ )
39
+ if (!ok) {
40
+ throw new CliError('Aborted', { exitCode: 1 })
41
+ }
42
+
43
+ await this.apiClient.del(`/api/v1/organizationRelationships/${args.id}`)
44
+ this.log(chalk.green(`Deleted organization relationship ${args.id}`))
45
+ }
46
+ }
@@ -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 PersonFollowerAddCommand extends BaseCommand {
6
+ static description = 'Add a follower (user) to a person'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> person follower add 42 --user 5',
10
+ '<%= config.bin %> person follower add 42 --user 5 --output json',
11
+ ]
12
+
13
+ static args = {
14
+ id: Args.integer({ required: true, description: 'Person 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(PersonFollowerAddCommand)
24
+
25
+ const res = await this.apiClient.post(
26
+ `/api/v2/persons/${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 PersonFollowerListCommand extends BaseCommand {
11
+ static description = 'List followers of a person'
12
+
13
+ static examples = [
14
+ '<%= config.bin %> person follower list 42',
15
+ '<%= config.bin %> person follower list 42 --output json',
16
+ ]
17
+
18
+ static args = {
19
+ id: Args.integer({ required: true, description: 'Person ID' }),
20
+ }
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ }
25
+
26
+ async run() {
27
+ const { args, flags } = await this.parse(PersonFollowerListCommand)
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/persons/${args.id}/followers`, query),
34
+ limit,
35
+ )
36
+ await this.outputResults(items, columns)
37
+ }
38
+ }
@@ -0,0 +1,48 @@
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 PersonFollowerRemoveCommand extends BaseCommand {
8
+ static description = 'Remove a follower from a person'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> person follower remove 42 --user 5',
12
+ '<%= config.bin %> person follower remove 42 --user 5 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Person 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(PersonFollowerRemoveCommand)
31
+
32
+ const ok = await confirmAction(
33
+ `Remove follower ${flags.user} from person ${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(
42
+ `/api/v2/persons/${args.id}/followers/${flags.user}`,
43
+ )
44
+ this.log(
45
+ chalk.green(`Removed follower ${flags.user} from person ${args.id}`),
46
+ )
47
+ }
48
+ }
@@ -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
  function primary(list) {
6
7
  if (!Array.isArray(list) || list.length === 0) return ''
@@ -28,16 +29,53 @@ export default class PersonListCommand extends BaseCommand {
28
29
  ...BaseCommand.baseFlags,
29
30
  owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
30
31
  org: Flags.integer({ description: 'Filter by organization ID' }),
32
+ filter: Flags.integer({ description: 'Filter by saved filter ID' }),
33
+ ids: Flags.string({
34
+ description: 'Comma-separated IDs to fetch (max 100)',
35
+ // The API silently drops `ids` when filter_id is present — refuse
36
+ // the combination instead (matches deal bulk-update).
37
+ exclusive: ['filter'],
38
+ }),
39
+ 'sort-by': Flags.string({
40
+ description: 'Sort field',
41
+ options: ['id', 'update_time', 'add_time'],
42
+ }),
43
+ 'sort-direction': Flags.string({
44
+ description: 'Sort direction',
45
+ options: ['asc', 'desc'],
46
+ }),
47
+ 'updated-since': Flags.string({
48
+ description:
49
+ 'Only items updated at/after this RFC3339 time (no fractional seconds)',
50
+ }),
51
+ 'updated-until': Flags.string({
52
+ description:
53
+ 'Only items updated before this RFC3339 time (no fractional seconds)',
54
+ }),
31
55
  }
32
56
 
33
57
  async run() {
34
58
  const { flags } = await this.parse(PersonListCommand)
35
- const limit = flags.limit ?? 100
59
+ const limit = flags.limit ?? 500
60
+
61
+ const idList = flags.ids
62
+ ?.split(',')
63
+ .map((v) => v.trim())
64
+ .filter(Boolean)
65
+ if (idList && idList.length > 100) {
66
+ throw new CliError('--ids accepts at most 100 IDs', { exitCode: 64 })
67
+ }
36
68
 
37
69
  const query = {
38
70
  owner_id: flags.owner,
39
71
  org_id: flags.org,
40
- limit: Math.min(limit, 100),
72
+ filter_id: flags.filter,
73
+ ids: idList?.join(','),
74
+ sort_by: flags['sort-by'],
75
+ sort_direction: flags['sort-direction'],
76
+ updated_since: flags['updated-since'],
77
+ updated_until: flags['updated-until'],
78
+ limit: Math.min(limit, 500),
41
79
  }
42
80
 
43
81
  const items = await collectPages(
@@ -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' },
@@ -25,15 +26,47 @@ export default class ProductListCommand extends BaseCommand {
25
26
  static flags = {
26
27
  ...BaseCommand.baseFlags,
27
28
  owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
29
+ filter: Flags.integer({ description: 'Filter by saved filter ID' }),
30
+ ids: Flags.string({
31
+ description: 'Comma-separated IDs to fetch (max 100)',
32
+ // The API silently drops `ids` when filter_id is present — refuse
33
+ // the combination instead (matches deal bulk-update).
34
+ exclusive: ['filter'],
35
+ }),
36
+ 'sort-by': Flags.string({
37
+ description: 'Sort field',
38
+ options: ['id', 'name', 'add_time', 'update_time'],
39
+ }),
40
+ 'sort-direction': Flags.string({
41
+ description: 'Sort direction',
42
+ options: ['asc', 'desc'],
43
+ }),
44
+ 'updated-since': Flags.string({
45
+ description:
46
+ 'Only items updated at/after this RFC3339 time (no fractional seconds)',
47
+ }),
28
48
  }
29
49
 
30
50
  async run() {
31
51
  const { flags } = await this.parse(ProductListCommand)
32
- const limit = flags.limit ?? 100
52
+ const limit = flags.limit ?? 500
53
+
54
+ const idList = flags.ids
55
+ ?.split(',')
56
+ .map((v) => v.trim())
57
+ .filter(Boolean)
58
+ if (idList && idList.length > 100) {
59
+ throw new CliError('--ids accepts at most 100 IDs', { exitCode: 64 })
60
+ }
33
61
 
34
62
  const query = {
35
63
  owner_id: flags.owner,
36
- limit: Math.min(limit, 100),
64
+ filter_id: flags.filter,
65
+ ids: idList?.join(','),
66
+ sort_by: flags['sort-by'],
67
+ sort_direction: flags['sort-direction'],
68
+ updated_since: flags['updated-since'],
69
+ limit: Math.min(limit, 500),
37
70
  }
38
71
 
39
72
  const items = await collectPages(
@@ -1,3 +1,4 @@
1
+ import { Flags } from '@oclif/core'
1
2
  import BaseCommand from '../../base-command.js'
2
3
  import { collectPages } from '../../lib/pagination.js'
3
4
 
@@ -20,6 +21,10 @@ export default class ProjectListCommand extends BaseCommand {
20
21
 
21
22
  static flags = {
22
23
  ...BaseCommand.baseFlags,
24
+ archived: Flags.boolean({
25
+ description: 'List archived projects instead of active ones',
26
+ default: false,
27
+ }),
23
28
  }
24
29
 
25
30
  async run() {
@@ -30,10 +35,11 @@ export default class ProjectListCommand extends BaseCommand {
30
35
  limit: Math.min(limit, 100),
31
36
  }
32
37
 
33
- const items = await collectPages(
34
- this.apiClient.pageV2('/api/v2/projects', query),
35
- limit,
36
- )
38
+ // Archived projects share the same cursor pager as active projects.
39
+ const path = flags.archived
40
+ ? '/api/v2/projects/archived'
41
+ : '/api/v2/projects'
42
+ const items = await collectPages(this.apiClient.pageV2(path, query), limit)
37
43
  await this.outputResults(items, columns)
38
44
  }
39
45
  }
@@ -11,6 +11,15 @@ const columns = {
11
11
  },
12
12
  }
13
13
 
14
+ // Item types that have a dedicated v2 scoped-search endpoint (narrower OAuth
15
+ // scope, narrower scope + 500-row pages vs itemSearch). Singular type → scoped endpoint path.
16
+ const SCOPED_PATHS = {
17
+ deal: '/api/v2/deals/search',
18
+ person: '/api/v2/persons/search',
19
+ organization: '/api/v2/organizations/search',
20
+ product: '/api/v2/products/search',
21
+ }
22
+
14
23
  export default class SearchCommand extends BaseCommand {
15
24
  static description =
16
25
  'Search across deals, persons, organizations, products, leads, files, and projects'
@@ -18,6 +27,7 @@ export default class SearchCommand extends BaseCommand {
18
27
  static examples = [
19
28
  '<%= config.bin %> search "acme"',
20
29
  '<%= config.bin %> search "acme" --item-types deal,person --output json',
30
+ '<%= config.bin %> search "acme" --item-types deal --status open',
21
31
  ]
22
32
 
23
33
  static args = {
@@ -34,6 +44,16 @@ export default class SearchCommand extends BaseCommand {
34
44
  description: 'Exact match (allows 1-character terms)',
35
45
  default: false,
36
46
  }),
47
+ status: Flags.string({
48
+ description: 'Filter by deal status (only with --item-types deal)',
49
+ options: ['open', 'won', 'lost'],
50
+ }),
51
+ person: Flags.integer({
52
+ description: 'Filter by person ID (only with --item-types deal)',
53
+ }),
54
+ org: Flags.integer({
55
+ description: 'Filter by organization ID (only with --item-types deal)',
56
+ }),
37
57
  }
38
58
 
39
59
  async run() {
@@ -47,15 +67,52 @@ export default class SearchCommand extends BaseCommand {
47
67
  )
48
68
  }
49
69
 
50
- // Search costs 40 rate-limit tokens single request, no auto-paging.
51
- const body = await this.apiClient.get('/api/v2/itemSearch', {
52
- query: {
53
- term: args.term,
54
- item_types: flags['item-types'],
55
- exact_match: flags.exact ? true : undefined,
56
- limit: flags.limit,
57
- },
58
- })
70
+ // A single routable item type uses the scoped endpoint; everything else
71
+ // (multi-type, no type, or a non-routable type like lead/file) stays on
72
+ // the generic itemSearch wrapper.
73
+ const types = flags['item-types']
74
+ ?.split(',')
75
+ .map((t) => t.trim())
76
+ .filter(Boolean)
77
+ const scopedType =
78
+ types?.length === 1 && SCOPED_PATHS[types[0]] ? types[0] : undefined
79
+
80
+ // --status/--person/--org narrow deal searches only and are accepted only
81
+ // when routing to the deals/search endpoint.
82
+ if (
83
+ (flags.status != null || flags.person != null || flags.org != null) &&
84
+ scopedType !== 'deal'
85
+ ) {
86
+ throw new CliError(
87
+ '--status, --person, and --org are valid only with --item-types deal',
88
+ { exitCode: 64 },
89
+ )
90
+ }
91
+
92
+ let body
93
+ if (scopedType) {
94
+ // Search costs 20 rate-limit tokens — single request, no auto-paging.
95
+ body = await this.apiClient.get(SCOPED_PATHS[scopedType], {
96
+ query: {
97
+ term: args.term,
98
+ exact_match: flags.exact ? true : undefined,
99
+ status: scopedType === 'deal' ? flags.status : undefined,
100
+ person_id: scopedType === 'deal' ? flags.person : undefined,
101
+ organization_id: scopedType === 'deal' ? flags.org : undefined,
102
+ limit: flags.limit,
103
+ },
104
+ })
105
+ } else {
106
+ // Search costs 20 rate-limit tokens — single request, no auto-paging.
107
+ body = await this.apiClient.get('/api/v2/itemSearch', {
108
+ query: {
109
+ term: args.term,
110
+ item_types: flags['item-types'],
111
+ exact_match: flags.exact ? true : undefined,
112
+ limit: flags.limit,
113
+ },
114
+ })
115
+ }
59
116
 
60
117
  const items = (body.data?.items ?? []).map((entry) => ({
61
118
  ...entry.item,
@@ -0,0 +1,44 @@
1
+ import { Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { buildWriteBody } from '../../lib/input.js'
4
+ import { outputRecord } from '../../lib/entity-view.js'
5
+
6
+ export default class TaskCreateCommand extends BaseCommand {
7
+ static description = 'Create a task'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> task create --title "Write spec" --project 3',
11
+ '<%= config.bin %> task create --title "Subtask" --project 3 --parent 5 --assignee 7',
12
+ '<%= config.bin %> task create --title "Raw" --project 3 --body \'{"priority":5}\'',
13
+ ]
14
+
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ title: Flags.string({ required: true, description: 'Task title' }),
18
+ project: Flags.integer({ required: true, description: 'Project ID' }),
19
+ description: Flags.string({ description: 'Task description' }),
20
+ assignee: Flags.integer({ description: 'Assignee (user) ID' }),
21
+ 'due-date': Flags.string({ description: 'Due date (YYYY-MM-DD)' }),
22
+ parent: Flags.integer({ description: 'Parent task ID' }),
23
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
24
+ }
25
+
26
+ async run() {
27
+ const { flags } = await this.parse(TaskCreateCommand)
28
+
29
+ const body = buildWriteBody({
30
+ typed: {
31
+ title: flags.title,
32
+ project_id: flags.project,
33
+ description: flags.description,
34
+ assignee_id: flags.assignee,
35
+ due_date: flags['due-date'],
36
+ parent_task_id: flags.parent,
37
+ },
38
+ rawBody: flags.body,
39
+ })
40
+
41
+ const res = await this.apiClient.post('/api/v2/tasks', { body })
42
+ await outputRecord(this, res.data)
43
+ }
44
+ }
@@ -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 TaskDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a task'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> task delete 7',
12
+ '<%= config.bin %> task delete 7 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Task 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(TaskDeleteCommand)
30
+
31
+ const ok = await confirmAction(`Delete task ${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/v2/tasks/${args.id}`)
39
+ this.log(chalk.green(`Deleted task ${args.id}`))
40
+ }
41
+ }