@wavyx/pdcli 0.2.0 → 0.3.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 (42) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +16 -2
  3. package/oclif.manifest.json +5232 -768
  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/file/download.js +35 -0
  9. package/src/commands/file/get.js +26 -0
  10. package/src/commands/file/list.js +40 -0
  11. package/src/commands/file/upload.js +42 -0
  12. package/src/commands/filter/get.js +26 -0
  13. package/src/commands/filter/list.js +43 -0
  14. package/src/commands/goal/list.js +37 -0
  15. package/src/commands/lead/create.js +58 -0
  16. package/src/commands/lead/delete.js +39 -0
  17. package/src/commands/lead/get.js +26 -0
  18. package/src/commands/lead/list.js +50 -0
  19. package/src/commands/lead/update.js +71 -0
  20. package/src/commands/note/create.js +42 -0
  21. package/src/commands/note/delete.js +39 -0
  22. package/src/commands/note/get.js +26 -0
  23. package/src/commands/note/list.js +49 -0
  24. package/src/commands/note/update.js +45 -0
  25. package/src/commands/pipeline/get.js +26 -0
  26. package/src/commands/pipeline/list.js +37 -0
  27. package/src/commands/project/create.js +48 -0
  28. package/src/commands/project/delete.js +39 -0
  29. package/src/commands/project/get.js +26 -0
  30. package/src/commands/project/list.js +39 -0
  31. package/src/commands/project/update.js +63 -0
  32. package/src/commands/stage/get.js +26 -0
  33. package/src/commands/stage/list.js +41 -0
  34. package/src/commands/webhook/create.js +75 -0
  35. package/src/commands/webhook/delete.js +39 -0
  36. package/src/commands/webhook/list.js +33 -0
  37. package/src/lib/backup.js +122 -0
  38. package/src/lib/client.js +67 -0
  39. package/src/lib/entity-view.js +7 -2
  40. package/src/lib/output/csv.js +26 -0
  41. package/src/lib/output/index.js +9 -1
  42. 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.3.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,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
+ }
@@ -0,0 +1,50 @@
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
+ title: { header: 'Title' },
8
+ person_id: { header: 'Person' },
9
+ organization_id: { header: 'Org' },
10
+ value: {
11
+ header: 'Value',
12
+ get: (row) =>
13
+ row.value ? `${row.value.amount} ${row.value.currency}` : '',
14
+ },
15
+ add_time: { header: 'Created' },
16
+ }
17
+
18
+ export default class LeadListCommand extends BaseCommand {
19
+ static description = 'List leads'
20
+
21
+ static examples = [
22
+ '<%= config.bin %> lead list',
23
+ '<%= config.bin %> lead list --owner 3 --output json',
24
+ ]
25
+
26
+ static flags = {
27
+ ...BaseCommand.baseFlags,
28
+ owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
29
+ person: Flags.integer({ description: 'Filter by person ID' }),
30
+ org: Flags.integer({ description: 'Filter by organization ID' }),
31
+ }
32
+
33
+ async run() {
34
+ const { flags } = await this.parse(LeadListCommand)
35
+ const limit = flags.limit ?? 100
36
+
37
+ const query = {
38
+ owner_id: flags.owner,
39
+ person_id: flags.person,
40
+ organization_id: flags.org,
41
+ limit: Math.min(limit, 100),
42
+ }
43
+
44
+ const items = await collectPages(
45
+ this.apiClient.pageV1('/api/v1/leads', query),
46
+ limit,
47
+ )
48
+ await this.outputResults(items, columns)
49
+ }
50
+ }
@@ -0,0 +1,71 @@
1
+ import { Args, 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
+ import { CliError } from '../../lib/errors.js'
6
+
7
+ export default class LeadUpdateCommand extends BaseCommand {
8
+ static description = 'Update a lead (v1 PATCH — only provided fields change)'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> lead update adf21080-0e10-11eb-879b-05d71fb426ec --title "Renamed"',
12
+ '<%= config.bin %> lead update adf21080-0e10-11eb-879b-05d71fb426ec --value 7500 --currency USD',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.string({ required: true, description: 'Lead ID (UUID)' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ title: Flags.string({ description: 'Lead title' }),
22
+ person: Flags.integer({ description: 'Linked person ID' }),
23
+ org: Flags.integer({ description: 'Linked organization ID' }),
24
+ owner: Flags.integer({ description: 'Owner (user) ID' }),
25
+ value: Flags.string({
26
+ description: 'Lead value amount (requires --currency)',
27
+ dependsOn: ['currency'],
28
+ }),
29
+ currency: Flags.string({
30
+ description: 'Lead value currency (requires --value)',
31
+ dependsOn: ['value'],
32
+ }),
33
+ 'expected-close-date': Flags.string({
34
+ description: 'Expected close date (YYYY-MM-DD)',
35
+ }),
36
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
37
+ }
38
+
39
+ async run() {
40
+ const { args, flags } = await this.parse(LeadUpdateCommand)
41
+
42
+ const value =
43
+ flags.value !== undefined && flags.currency !== undefined
44
+ ? { amount: Number(flags.value), currency: flags.currency }
45
+ : undefined
46
+
47
+ const body = buildWriteBody({
48
+ typed: {
49
+ title: flags.title,
50
+ person_id: flags.person,
51
+ organization_id: flags.org,
52
+ owner_id: flags.owner,
53
+ value,
54
+ expected_close_date: flags['expected-close-date'],
55
+ },
56
+ rawBody: flags.body,
57
+ })
58
+
59
+ if (Object.keys(body).length === 0) {
60
+ throw new CliError(
61
+ 'Nothing to update — pass at least one field flag or --body',
62
+ { exitCode: 64 },
63
+ )
64
+ }
65
+
66
+ const res = await this.apiClient.patch(`/api/v1/leads/${args.id}`, {
67
+ body,
68
+ })
69
+ await outputRecord(this, res.data, 'deal')
70
+ }
71
+ }
@@ -0,0 +1,42 @@
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 NoteCreateCommand extends BaseCommand {
7
+ static description = 'Create a note'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> note create --content "Called the lead"',
11
+ '<%= config.bin %> note create --content "Follow up" --deal 42',
12
+ '<%= config.bin %> note create --content "Pinned" --body \'{"pinned_to_deal_flag":1}\'',
13
+ ]
14
+
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ content: Flags.string({ required: true, description: 'Note content' }),
18
+ deal: Flags.integer({ description: 'Attach to deal ID' }),
19
+ person: Flags.integer({ description: 'Attach to person ID' }),
20
+ org: Flags.integer({ description: 'Attach to organization ID' }),
21
+ lead: Flags.string({ description: 'Attach to lead ID (UUID)' }),
22
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
23
+ }
24
+
25
+ async run() {
26
+ const { flags } = await this.parse(NoteCreateCommand)
27
+
28
+ const body = buildWriteBody({
29
+ typed: {
30
+ content: flags.content,
31
+ deal_id: flags.deal,
32
+ person_id: flags.person,
33
+ org_id: flags.org,
34
+ lead_id: flags.lead,
35
+ },
36
+ rawBody: flags.body,
37
+ })
38
+
39
+ const res = await this.apiClient.post('/api/v1/notes', { body })
40
+ await outputRecord(this, res.data)
41
+ }
42
+ }