@wavyx/pdcli 0.2.0 → 0.4.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 (48) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +30 -2
  3. package/oclif.manifest.json +5579 -686
  4. package/package.json +3 -1
  5. package/src/base-command.js +30 -2
  6. package/src/commands/api.js +6 -2
  7. package/src/commands/backup.js +53 -0
  8. package/src/commands/deal/bulk-update.js +131 -0
  9. package/src/commands/file/download.js +35 -0
  10. package/src/commands/file/get.js +26 -0
  11. package/src/commands/file/list.js +40 -0
  12. package/src/commands/file/upload.js +42 -0
  13. package/src/commands/filter/get.js +26 -0
  14. package/src/commands/filter/list.js +43 -0
  15. package/src/commands/goal/list.js +37 -0
  16. package/src/commands/lead/create.js +58 -0
  17. package/src/commands/lead/delete.js +39 -0
  18. package/src/commands/lead/get.js +26 -0
  19. package/src/commands/lead/list.js +50 -0
  20. package/src/commands/lead/update.js +71 -0
  21. package/src/commands/note/create.js +42 -0
  22. package/src/commands/note/delete.js +39 -0
  23. package/src/commands/note/get.js +26 -0
  24. package/src/commands/note/list.js +49 -0
  25. package/src/commands/note/update.js +45 -0
  26. package/src/commands/org/import.js +109 -0
  27. package/src/commands/person/import.js +118 -0
  28. package/src/commands/pipeline/get.js +26 -0
  29. package/src/commands/pipeline/list.js +37 -0
  30. package/src/commands/project/create.js +48 -0
  31. package/src/commands/project/delete.js +39 -0
  32. package/src/commands/project/get.js +26 -0
  33. package/src/commands/project/list.js +39 -0
  34. package/src/commands/project/update.js +63 -0
  35. package/src/commands/stage/get.js +26 -0
  36. package/src/commands/stage/list.js +41 -0
  37. package/src/commands/webhook/create.js +75 -0
  38. package/src/commands/webhook/delete.js +39 -0
  39. package/src/commands/webhook/list.js +33 -0
  40. package/src/lib/backup.js +122 -0
  41. package/src/lib/bulk.js +106 -0
  42. package/src/lib/client.js +67 -0
  43. package/src/lib/csv-parse.js +88 -0
  44. package/src/lib/entity-view.js +7 -2
  45. package/src/lib/import.js +49 -0
  46. package/src/lib/output/csv.js +26 -0
  47. package/src/lib/output/index.js +9 -1
  48. package/src/lib/output/yaml.js +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavyx/pdcli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -100,6 +100,8 @@
100
100
  "cli-table3": "0.6.5",
101
101
  "conf": "15.1.0",
102
102
  "debug": "4.4.3",
103
+ "js-yaml": "^4.1.1",
104
+ "node-jq": "^6.3.1",
103
105
  "open": "^11.0.0",
104
106
  "ora": "9.4.0",
105
107
  "undici": "8.3.0"
@@ -12,7 +12,15 @@ export default class BaseCommand extends Command {
12
12
  char: 'o',
13
13
  description: 'Output format',
14
14
  helpGroup: 'GLOBAL',
15
- options: ['table', 'json'],
15
+ options: ['table', 'json', 'yaml', 'csv'],
16
+ }),
17
+ jq: Flags.string({
18
+ description: 'jq expression to filter JSON output',
19
+ helpGroup: 'GLOBAL',
20
+ }),
21
+ fields: Flags.string({
22
+ description: 'Comma-separated fields to display',
23
+ helpGroup: 'GLOBAL',
16
24
  }),
17
25
  profile: Flags.string({
18
26
  description: 'Named auth profile to use',
@@ -120,7 +128,27 @@ export default class BaseCommand extends Command {
120
128
  * @param {Record<string, import('./lib/output/table.js').Column>} columns
121
129
  */
122
130
  async outputResults(data, columns) {
123
- formatOutput(data, columns, this.resolveFormat(), this)
131
+ if (this.flags.jq) {
132
+ // node-jq ships a native binary — load it only when actually used.
133
+ const { run } = await import('node-jq')
134
+ const input = JSON.stringify(Array.isArray(data) ? data : [data])
135
+ const result = await run(this.flags.jq, input, {
136
+ input: 'string',
137
+ output: 'pretty',
138
+ })
139
+ this.log(result)
140
+ return
141
+ }
142
+
143
+ let filteredColumns = columns
144
+ if (this.flags.fields && columns) {
145
+ const requested = this.flags.fields.split(',').map((f) => f.trim())
146
+ filteredColumns = Object.fromEntries(
147
+ Object.entries(columns).filter(([key]) => requested.includes(key)),
148
+ )
149
+ }
150
+
151
+ formatOutput(data, filteredColumns, this.resolveFormat(), this)
124
152
  }
125
153
 
126
154
  async catch(err) {
@@ -53,8 +53,12 @@ export default class ApiCommand extends BaseCommand {
53
53
 
54
54
  const data = await this.apiClient[method](args.path, opts)
55
55
 
56
- if (data !== null) {
57
- this.log(JSON.stringify(data, null, 2))
56
+ if (data === null) return
57
+
58
+ if (this.flags.jq) {
59
+ await this.outputResults(data, {})
60
+ return
58
61
  }
62
+ this.log(JSON.stringify(data, null, 2))
59
63
  }
60
64
  }
@@ -0,0 +1,53 @@
1
+ import { Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import ora from 'ora'
4
+ import BaseCommand from '../base-command.js'
5
+ import { runBackup } from '../lib/backup.js'
6
+
7
+ export default class BackupCommand extends BaseCommand {
8
+ static description =
9
+ 'Export the whole account to a JSON tree (resumable, one file per resource)'
10
+
11
+ static examples = [
12
+ '<%= config.bin %> backup',
13
+ '<%= config.bin %> backup --dir ./my-backup',
14
+ '<%= config.bin %> backup --dir ./my-backup --resume',
15
+ ]
16
+
17
+ static flags = {
18
+ ...BaseCommand.baseFlags,
19
+ dir: Flags.string({
20
+ description: 'Target directory for the export',
21
+ default: 'pipedrive-backup',
22
+ }),
23
+ resume: Flags.boolean({
24
+ description: 'Skip resources already completed in a previous run',
25
+ default: false,
26
+ }),
27
+ }
28
+
29
+ async run() {
30
+ const { flags } = await this.parse(BackupCommand)
31
+
32
+ const spinner = ora('Starting backup...').start()
33
+ let summary
34
+ try {
35
+ summary = await runBackup(this.apiClient, flags.dir, {
36
+ resume: flags.resume,
37
+ onProgress: (resource, count) => {
38
+ spinner.text = `Exported ${resource} (${count})`
39
+ },
40
+ })
41
+ } finally {
42
+ spinner.stop()
43
+ }
44
+
45
+ this.log(
46
+ chalk.green(
47
+ `Backup complete: ${summary.exported}/${summary.total} resources ` +
48
+ `exported to ${chalk.cyan(flags.dir)}` +
49
+ (summary.skipped ? chalk.dim(` (${summary.skipped} skipped)`) : ''),
50
+ ),
51
+ )
52
+ }
53
+ }
@@ -0,0 +1,131 @@
1
+ import { Flags } from '@oclif/core'
2
+ import chalk from 'chalk'
3
+ import ora from 'ora'
4
+ import BaseCommand from '../../base-command.js'
5
+ import { resolveTargets, bulkRun } from '../../lib/bulk.js'
6
+ import { buildWriteBody } from '../../lib/input.js'
7
+ import { defsForFields } from '../../lib/entity-view.js'
8
+ import { confirmAction } from '../../lib/confirm.js'
9
+ import { CliError } from '../../lib/errors.js'
10
+
11
+ export default class DealBulkUpdateCommand extends BaseCommand {
12
+ static description =
13
+ 'Update many deals at once (by --ids, a saved --filter, or ids piped on stdin)'
14
+
15
+ static examples = [
16
+ '<%= config.bin %> deal bulk-update --ids 1,2,3 --stage 5',
17
+ '<%= config.bin %> deal bulk-update --filter 9 --status won',
18
+ "<%= config.bin %> deal list --status open --jq '.[].id' | <%= config.bin %> deal bulk-update --owner 42",
19
+ '<%= config.bin %> deal bulk-update --filter 9 --stage 5 --dry-run',
20
+ ]
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ ids: Flags.string({
25
+ description: 'Comma-separated deal IDs',
26
+ exclusive: ['filter'],
27
+ }),
28
+ filter: Flags.integer({
29
+ description: 'Pipedrive saved filter ID to select deals',
30
+ exclusive: ['ids'],
31
+ }),
32
+ stage: Flags.integer({ description: 'Move to stage ID' }),
33
+ pipeline: Flags.integer({ description: 'Move to pipeline ID' }),
34
+ status: Flags.string({
35
+ description: 'Set status',
36
+ options: ['open', 'won', 'lost'],
37
+ }),
38
+ owner: Flags.integer({ description: 'Assign owner (user) ID' }),
39
+ field: Flags.string({
40
+ multiple: true,
41
+ description: 'Custom/standard field as "Name=Value" (repeatable)',
42
+ }),
43
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
44
+ 'dry-run': Flags.boolean({
45
+ description: 'List the targets without updating anything',
46
+ default: false,
47
+ }),
48
+ yes: Flags.boolean({
49
+ char: 'y',
50
+ description: 'Skip the confirmation prompt',
51
+ default: false,
52
+ }),
53
+ }
54
+
55
+ async run() {
56
+ const { flags } = await this.parse(DealBulkUpdateCommand)
57
+
58
+ const body = buildWriteBody({
59
+ typed: {
60
+ stage_id: flags.stage,
61
+ pipeline_id: flags.pipeline,
62
+ status: flags.status,
63
+ owner_id: flags.owner,
64
+ },
65
+ fields: flags.field,
66
+ rawBody: flags.body,
67
+ defs: await defsForFields(this, 'deal', flags.field),
68
+ })
69
+
70
+ if (Object.keys(body).length === 0) {
71
+ throw new CliError(
72
+ 'Nothing to update — pass at least one change flag, --field, or --body',
73
+ { exitCode: 64 },
74
+ )
75
+ }
76
+
77
+ const targets = await resolveTargets(
78
+ { ids: flags.ids, filter: flags.filter },
79
+ this.apiClient,
80
+ '/api/v2/deals',
81
+ )
82
+
83
+ if (flags['dry-run']) {
84
+ this.log(
85
+ `Would update ${chalk.bold(targets.length)} deals: ${targets.join(', ')}`,
86
+ )
87
+ this.log(chalk.dim(`Change: ${JSON.stringify(body)}`))
88
+ return
89
+ }
90
+
91
+ const ok = await confirmAction(
92
+ `Update ${targets.length} deals with ${JSON.stringify(body)}?`,
93
+ flags.yes,
94
+ )
95
+ if (!ok) {
96
+ throw new CliError('Aborted', { exitCode: 1 })
97
+ }
98
+
99
+ const spinner = ora(`Updating ${targets.length} deals...`).start()
100
+ let summary
101
+ try {
102
+ summary = await bulkRun(
103
+ targets,
104
+ (id) => this.apiClient.patch(`/api/v2/deals/${id}`, { body }),
105
+ {
106
+ onProgress: (done, total) => {
107
+ spinner.text = `Updating deals ${done}/${total}`
108
+ },
109
+ },
110
+ )
111
+ } finally {
112
+ spinner.stop()
113
+ }
114
+
115
+ this.log(
116
+ chalk.green(
117
+ `Updated ${summary.succeeded.length}/${targets.length} deals`,
118
+ ),
119
+ )
120
+
121
+ if (summary.failed.length > 0) {
122
+ for (const { item, error } of summary.failed) {
123
+ this.log(chalk.red(` ✘ deal ${item}: ${error}`))
124
+ }
125
+ throw new CliError(
126
+ `${summary.failed.length} of ${targets.length} updates failed`,
127
+ { exitCode: 1 },
128
+ )
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,35 @@
1
+ import { writeFileSync } from 'node:fs'
2
+ import { Args, Flags } from '@oclif/core'
3
+ import chalk from 'chalk'
4
+ import BaseCommand from '../../base-command.js'
5
+
6
+ export default class FileDownloadCommand extends BaseCommand {
7
+ static description = 'Download a file by ID'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> file download 5',
11
+ '<%= config.bin %> file download 5 --out ./report.pdf',
12
+ ]
13
+
14
+ static args = {
15
+ id: Args.integer({ required: true, description: 'File ID' }),
16
+ }
17
+
18
+ static flags = {
19
+ ...BaseCommand.baseFlags,
20
+ out: Flags.string({ description: 'Path to write to (default: file name)' }),
21
+ }
22
+
23
+ async run() {
24
+ const { args, flags } = await this.parse(FileDownloadCommand)
25
+
26
+ const body = await this.apiClient.get(`/api/v1/files/${args.id}`)
27
+ const { buffer } = await this.apiClient.download(
28
+ `/api/v1/files/${args.id}/download`,
29
+ )
30
+ const bytes = Buffer.from(buffer)
31
+ const out = flags.out ?? body.data.name
32
+ writeFileSync(out, bytes)
33
+ this.log(chalk.green(`Saved ${out} (${bytes.length} bytes)`))
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ import { Args } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { outputRecord } from '../../lib/entity-view.js'
4
+
5
+ export default class FileGetCommand extends BaseCommand {
6
+ static description = 'Get a file by ID'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> file get 5',
10
+ '<%= config.bin %> file get 5 --output json',
11
+ ]
12
+
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ }
16
+
17
+ static args = {
18
+ id: Args.integer({ required: true, description: 'File ID' }),
19
+ }
20
+
21
+ async run() {
22
+ const { args } = await this.parse(FileGetCommand)
23
+ const body = await this.apiClient.get(`/api/v1/files/${args.id}`)
24
+ await outputRecord(this, body.data)
25
+ }
26
+ }
@@ -0,0 +1,40 @@
1
+ import BaseCommand from '../../base-command.js'
2
+ import { collectPages } from '../../lib/pagination.js'
3
+
4
+ const columns = {
5
+ id: { header: 'ID' },
6
+ name: { header: 'Name' },
7
+ file_type: { header: 'Type' },
8
+ file_size: { header: 'Size' },
9
+ deal_id: { header: 'Deal' },
10
+ person_id: { header: 'Person' },
11
+ add_time: { header: 'Created' },
12
+ }
13
+
14
+ export default class FileListCommand extends BaseCommand {
15
+ static description = 'List files'
16
+
17
+ static examples = [
18
+ '<%= config.bin %> file list',
19
+ '<%= config.bin %> file list --limit 50 --output json',
20
+ ]
21
+
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ }
25
+
26
+ async run() {
27
+ const { flags } = await this.parse(FileListCommand)
28
+ const limit = flags.limit ?? 100
29
+
30
+ const query = {
31
+ limit: Math.min(limit, 100),
32
+ }
33
+
34
+ const items = await collectPages(
35
+ this.apiClient.pageV1('/api/v1/files', query),
36
+ limit,
37
+ )
38
+ await this.outputResults(items, columns)
39
+ }
40
+ }
@@ -0,0 +1,42 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { basename } from 'node:path'
3
+ import { Args, Flags } from '@oclif/core'
4
+ import BaseCommand from '../../base-command.js'
5
+ import { outputRecord } from '../../lib/entity-view.js'
6
+
7
+ export default class FileUploadCommand extends BaseCommand {
8
+ static description = 'Upload a file'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> file upload ./report.pdf',
12
+ '<%= config.bin %> file upload ./report.pdf --deal 42',
13
+ ]
14
+
15
+ static args = {
16
+ path: Args.string({ required: true, description: 'Path to the file' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ deal: Flags.integer({ description: 'Associate with a deal ID' }),
22
+ person: Flags.integer({ description: 'Associate with a person ID' }),
23
+ org: Flags.integer({ description: 'Associate with an organization ID' }),
24
+ }
25
+
26
+ async run() {
27
+ const { args, flags } = await this.parse(FileUploadCommand)
28
+
29
+ const data = readFileSync(args.path)
30
+ const name = basename(args.path)
31
+
32
+ const res = await this.apiClient.postMultipart('/api/v1/files', {
33
+ file: { name, data },
34
+ fields: {
35
+ deal_id: flags.deal,
36
+ person_id: flags.person,
37
+ org_id: flags.org,
38
+ },
39
+ })
40
+ await outputRecord(this, res.data)
41
+ }
42
+ }
@@ -0,0 +1,26 @@
1
+ import { Args } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { outputRecord } from '../../lib/entity-view.js'
4
+
5
+ export default class FilterGetCommand extends BaseCommand {
6
+ static description = 'Get a filter by ID'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> filter get 5',
10
+ '<%= config.bin %> filter get 5 --output json',
11
+ ]
12
+
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ }
16
+
17
+ static args = {
18
+ id: Args.integer({ required: true, description: 'Filter ID' }),
19
+ }
20
+
21
+ async run() {
22
+ const { args } = await this.parse(FilterGetCommand)
23
+ const body = await this.apiClient.get(`/api/v1/filters/${args.id}`)
24
+ await outputRecord(this, body.data)
25
+ }
26
+ }
@@ -0,0 +1,43 @@
1
+ import { Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+
4
+ const columns = {
5
+ id: { header: 'ID' },
6
+ name: { header: 'Name' },
7
+ type: { header: 'Type' },
8
+ active_flag: { header: 'Active' },
9
+ }
10
+
11
+ export default class FilterListCommand extends BaseCommand {
12
+ static description = 'List filters'
13
+
14
+ static examples = [
15
+ '<%= config.bin %> filter list',
16
+ '<%= config.bin %> filter list --type deals --output json',
17
+ ]
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ type: Flags.string({
22
+ description: 'Filter by type',
23
+ options: [
24
+ 'deals',
25
+ 'leads',
26
+ 'org',
27
+ 'people',
28
+ 'products',
29
+ 'activity',
30
+ 'projects',
31
+ ],
32
+ }),
33
+ }
34
+
35
+ async run() {
36
+ const { flags } = await this.parse(FilterListCommand)
37
+
38
+ const body = await this.apiClient.get('/api/v1/filters', {
39
+ query: { type: flags.type },
40
+ })
41
+ await this.outputResults(body.data, columns)
42
+ }
43
+ }
@@ -0,0 +1,37 @@
1
+ import { Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+
4
+ const columns = {
5
+ id: { header: 'ID' },
6
+ title: { header: 'Title', get: (row) => row.title ?? '' },
7
+ type: { header: 'Type', get: (row) => row.type?.name ?? '' },
8
+ interval: { header: 'Interval', get: (row) => row.interval ?? '' },
9
+ owner_id: { header: 'Owner' },
10
+ }
11
+
12
+ export default class GoalListCommand extends BaseCommand {
13
+ static description = 'List goals'
14
+
15
+ static examples = [
16
+ '<%= config.bin %> goal list',
17
+ '<%= config.bin %> goal list --assignee 7 --type deals_won --output json',
18
+ ]
19
+
20
+ static flags = {
21
+ ...BaseCommand.baseFlags,
22
+ assignee: Flags.integer({ description: 'Filter by assignee (user) ID' }),
23
+ type: Flags.string({ description: 'Filter by goal type name' }),
24
+ }
25
+
26
+ async run() {
27
+ const { flags } = await this.parse(GoalListCommand)
28
+
29
+ const body = await this.apiClient.get('/api/v1/goals/find', {
30
+ query: {
31
+ 'assignee.id': flags.assignee,
32
+ 'type.name': flags.type,
33
+ },
34
+ })
35
+ await this.outputResults(body.data?.goals ?? [], columns)
36
+ }
37
+ }
@@ -0,0 +1,58 @@
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 LeadCreateCommand extends BaseCommand {
7
+ static description = 'Create a lead'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> lead create --title "Acme renewal" --value 5000 --currency EUR',
11
+ '<%= config.bin %> lead create --title "Linked" --person 4 --org 5',
12
+ '<%= config.bin %> lead create --title "Raw" --body \'{"visible_to":"3"}\'',
13
+ ]
14
+
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ title: Flags.string({ required: true, description: 'Lead title' }),
18
+ person: Flags.integer({ description: 'Linked person ID' }),
19
+ org: Flags.integer({ description: 'Linked organization ID' }),
20
+ owner: Flags.integer({ description: 'Owner (user) ID' }),
21
+ value: Flags.string({
22
+ description: 'Lead value amount (requires --currency)',
23
+ dependsOn: ['currency'],
24
+ }),
25
+ currency: Flags.string({
26
+ description: 'Lead value currency (requires --value)',
27
+ dependsOn: ['value'],
28
+ }),
29
+ 'expected-close-date': Flags.string({
30
+ description: 'Expected close date (YYYY-MM-DD)',
31
+ }),
32
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
33
+ }
34
+
35
+ async run() {
36
+ const { flags } = await this.parse(LeadCreateCommand)
37
+
38
+ const value =
39
+ flags.value !== undefined && flags.currency !== undefined
40
+ ? { amount: Number(flags.value), currency: flags.currency }
41
+ : undefined
42
+
43
+ const body = buildWriteBody({
44
+ typed: {
45
+ title: flags.title,
46
+ person_id: flags.person,
47
+ organization_id: flags.org,
48
+ owner_id: flags.owner,
49
+ value,
50
+ expected_close_date: flags['expected-close-date'],
51
+ },
52
+ rawBody: flags.body,
53
+ })
54
+
55
+ const res = await this.apiClient.post('/api/v1/leads', { body })
56
+ await outputRecord(this, res.data, 'deal')
57
+ }
58
+ }
@@ -0,0 +1,39 @@
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 LeadDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a lead'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> lead delete adf21080-0e10-11eb-879b-05d71fb426ec',
12
+ '<%= config.bin %> lead delete adf21080-0e10-11eb-879b-05d71fb426ec --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.string({ required: true, description: 'Lead ID (UUID)' }),
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(LeadDeleteCommand)
30
+
31
+ const ok = await confirmAction(`Delete lead ${args.id}?`, flags.yes)
32
+ if (!ok) {
33
+ throw new CliError('Aborted', { exitCode: 1 })
34
+ }
35
+
36
+ await this.apiClient.del(`/api/v1/leads/${args.id}`)
37
+ this.log(chalk.green(`Deleted lead ${args.id}`))
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ import { Args } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { outputRecord } from '../../lib/entity-view.js'
4
+
5
+ export default class LeadGetCommand extends BaseCommand {
6
+ static description = 'Get a lead by ID'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> lead get adf21080-0e10-11eb-879b-05d71fb426ec',
10
+ '<%= config.bin %> lead get adf21080-0e10-11eb-879b-05d71fb426ec --output json',
11
+ ]
12
+
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ }
16
+
17
+ static args = {
18
+ id: Args.string({ required: true, description: 'Lead ID (UUID)' }),
19
+ }
20
+
21
+ async run() {
22
+ const { args } = await this.parse(LeadGetCommand)
23
+ const body = await this.apiClient.get(`/api/v1/leads/${args.id}`)
24
+ await outputRecord(this, body.data, 'deal')
25
+ }
26
+ }