@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
@@ -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
+ }
@@ -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 NoteDeleteCommand extends BaseCommand {
8
+ static description = 'Delete a note'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> note delete 5',
12
+ '<%= config.bin %> note delete 5 --yes',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Note 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(NoteDeleteCommand)
30
+
31
+ const ok = await confirmAction(`Delete note ${args.id}?`, flags.yes)
32
+ if (!ok) {
33
+ throw new CliError('Aborted', { exitCode: 1 })
34
+ }
35
+
36
+ await this.apiClient.del(`/api/v1/notes/${args.id}`)
37
+ this.log(chalk.green(`Deleted note ${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 NoteGetCommand extends BaseCommand {
6
+ static description = 'Get a note by ID'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> note get 5',
10
+ '<%= config.bin %> note get 5 --output json',
11
+ ]
12
+
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ }
16
+
17
+ static args = {
18
+ id: Args.integer({ required: true, description: 'Note ID' }),
19
+ }
20
+
21
+ async run() {
22
+ const { args } = await this.parse(NoteGetCommand)
23
+ const body = await this.apiClient.get(`/api/v1/notes/${args.id}`)
24
+ await outputRecord(this, body.data)
25
+ }
26
+ }
@@ -0,0 +1,49 @@
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
+ content: {
8
+ header: 'Content',
9
+ get: (row) => (row.content ?? '').slice(0, 60),
10
+ },
11
+ deal_id: { header: 'Deal' },
12
+ person_id: { header: 'Person' },
13
+ org_id: { header: 'Org' },
14
+ add_time: { header: 'Created' },
15
+ }
16
+
17
+ export default class NoteListCommand extends BaseCommand {
18
+ static description = 'List notes'
19
+
20
+ static examples = [
21
+ '<%= config.bin %> note list',
22
+ '<%= config.bin %> note list --deal 42 --output json',
23
+ ]
24
+
25
+ static flags = {
26
+ ...BaseCommand.baseFlags,
27
+ deal: Flags.integer({ description: 'Filter by deal ID' }),
28
+ person: Flags.integer({ description: 'Filter by person ID' }),
29
+ org: Flags.integer({ description: 'Filter by organization ID' }),
30
+ }
31
+
32
+ async run() {
33
+ const { flags } = await this.parse(NoteListCommand)
34
+ const limit = flags.limit ?? 100
35
+
36
+ const query = {
37
+ deal_id: flags.deal,
38
+ person_id: flags.person,
39
+ org_id: flags.org,
40
+ limit: Math.min(limit, 100),
41
+ }
42
+
43
+ const items = await collectPages(
44
+ this.apiClient.pageV1('/api/v1/notes', query),
45
+ limit,
46
+ )
47
+ await this.outputResults(items, columns)
48
+ }
49
+ }
@@ -0,0 +1,45 @@
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 NoteUpdateCommand extends BaseCommand {
8
+ static description = 'Update a note (only provided fields change)'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> note update 5 --content "Revised note"',
12
+ '<%= config.bin %> note update 5 --body \'{"pinned_to_deal_flag":1}\'',
13
+ ]
14
+
15
+ static args = {
16
+ id: Args.integer({ required: true, description: 'Note ID' }),
17
+ }
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ content: Flags.string({ description: 'Note content' }),
22
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
23
+ }
24
+
25
+ async run() {
26
+ const { args, flags } = await this.parse(NoteUpdateCommand)
27
+
28
+ const body = buildWriteBody({
29
+ typed: {
30
+ content: flags.content,
31
+ },
32
+ rawBody: flags.body,
33
+ })
34
+
35
+ if (Object.keys(body).length === 0) {
36
+ throw new CliError(
37
+ 'Nothing to update — pass at least one field flag, --field, or --body',
38
+ { exitCode: 64 },
39
+ )
40
+ }
41
+
42
+ const res = await this.apiClient.put(`/api/v1/notes/${args.id}`, { body })
43
+ await outputRecord(this, res.data)
44
+ }
45
+ }
@@ -0,0 +1,109 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { Args, Flags } from '@oclif/core'
3
+ import chalk from 'chalk'
4
+ import ora from 'ora'
5
+ import BaseCommand from '../../base-command.js'
6
+ import { parseCsv } from '../../lib/csv-parse.js'
7
+ import { prepareImportBodies } from '../../lib/import.js'
8
+ import { bulkRun } from '../../lib/bulk.js'
9
+ import { getFields } from '../../lib/fields.js'
10
+ import { confirmAction } from '../../lib/confirm.js'
11
+ import { CliError } from '../../lib/errors.js'
12
+
13
+ const SPECIAL_COLUMNS = {
14
+ name: (typed, value) => {
15
+ typed.name = value
16
+ },
17
+ owner_id: (typed, value) => {
18
+ typed.owner_id = Number(value)
19
+ },
20
+ }
21
+
22
+ export default class OrgImportCommand extends BaseCommand {
23
+ static description =
24
+ 'Bulk-create organizations from a CSV (headers map to fields, custom fields by name)'
25
+
26
+ static examples = [
27
+ '<%= config.bin %> org import orgs.csv',
28
+ '<%= config.bin %> org import orgs.csv --dry-run',
29
+ ]
30
+
31
+ static args = {
32
+ file: Args.string({ required: true, description: 'CSV file path' }),
33
+ }
34
+
35
+ static flags = {
36
+ ...BaseCommand.baseFlags,
37
+ 'dry-run': Flags.boolean({
38
+ description: 'Validate every row without creating anything',
39
+ default: false,
40
+ }),
41
+ yes: Flags.boolean({
42
+ char: 'y',
43
+ description: 'Skip the confirmation prompt',
44
+ default: false,
45
+ }),
46
+ }
47
+
48
+ async run() {
49
+ const { args, flags } = await this.parse(OrgImportCommand)
50
+
51
+ const { headers, rows } = parseCsv(readFileSync(args.file, 'utf8'))
52
+ if (!headers.some((h) => h.toLowerCase() === 'name')) {
53
+ throw new CliError('CSV must include a "name" column', { exitCode: 64 })
54
+ }
55
+
56
+ const needsDefs = headers.some((h) => !(h.toLowerCase() in SPECIAL_COLUMNS))
57
+ const bodies = prepareImportBodies({
58
+ headers,
59
+ rows,
60
+ specialColumns: SPECIAL_COLUMNS,
61
+ defs: needsDefs ? await getFields(this.apiClient, 'org') : [],
62
+ })
63
+
64
+ if (flags['dry-run']) {
65
+ this.log(chalk.green(`${bodies.length} rows valid — nothing created`))
66
+ return
67
+ }
68
+
69
+ const ok = await confirmAction(
70
+ `Create ${bodies.length} organizations from ${args.file}?`,
71
+ flags.yes,
72
+ )
73
+ if (!ok) {
74
+ throw new CliError('Aborted', { exitCode: 1 })
75
+ }
76
+
77
+ const spinner = ora(`Importing ${bodies.length} organizations...`).start()
78
+ let summary
79
+ try {
80
+ summary = await bulkRun(
81
+ bodies,
82
+ (body) => this.apiClient.post('/api/v2/organizations', { body }),
83
+ {
84
+ onProgress: (done, total) => {
85
+ spinner.text = `Importing organizations ${done}/${total}`
86
+ },
87
+ },
88
+ )
89
+ } finally {
90
+ spinner.stop()
91
+ }
92
+
93
+ this.log(
94
+ chalk.green(
95
+ `Imported ${summary.succeeded.length}/${bodies.length} organizations`,
96
+ ),
97
+ )
98
+
99
+ if (summary.failed.length > 0) {
100
+ for (const { item, error } of summary.failed) {
101
+ this.log(chalk.red(` ✘ ${item.name ?? '(unnamed)'}: ${error}`))
102
+ }
103
+ throw new CliError(
104
+ `${summary.failed.length} of ${bodies.length} rows failed`,
105
+ { exitCode: 1 },
106
+ )
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,118 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { Args, Flags } from '@oclif/core'
3
+ import chalk from 'chalk'
4
+ import ora from 'ora'
5
+ import BaseCommand from '../../base-command.js'
6
+ import { parseCsv } from '../../lib/csv-parse.js'
7
+ import { prepareImportBodies } from '../../lib/import.js'
8
+ import { bulkRun } from '../../lib/bulk.js'
9
+ import { getFields } from '../../lib/fields.js'
10
+ import { confirmAction } from '../../lib/confirm.js'
11
+ import { CliError } from '../../lib/errors.js'
12
+
13
+ const SPECIAL_COLUMNS = {
14
+ name: (typed, value) => {
15
+ typed.name = value
16
+ },
17
+ email: (typed, value) => {
18
+ typed.emails = [{ value, primary: true }]
19
+ },
20
+ phone: (typed, value) => {
21
+ typed.phones = [{ value, primary: true }]
22
+ },
23
+ org_id: (typed, value) => {
24
+ typed.org_id = Number(value)
25
+ },
26
+ owner_id: (typed, value) => {
27
+ typed.owner_id = Number(value)
28
+ },
29
+ }
30
+
31
+ export default class PersonImportCommand extends BaseCommand {
32
+ static description =
33
+ 'Bulk-create persons from a CSV (headers map to fields, custom fields by name)'
34
+
35
+ static examples = [
36
+ '<%= config.bin %> person import people.csv',
37
+ '<%= config.bin %> person import people.csv --dry-run',
38
+ ]
39
+
40
+ static args = {
41
+ file: Args.string({ required: true, description: 'CSV file path' }),
42
+ }
43
+
44
+ static flags = {
45
+ ...BaseCommand.baseFlags,
46
+ 'dry-run': Flags.boolean({
47
+ description: 'Validate every row without creating anything',
48
+ default: false,
49
+ }),
50
+ yes: Flags.boolean({
51
+ char: 'y',
52
+ description: 'Skip the confirmation prompt',
53
+ default: false,
54
+ }),
55
+ }
56
+
57
+ async run() {
58
+ const { args, flags } = await this.parse(PersonImportCommand)
59
+
60
+ const { headers, rows } = parseCsv(readFileSync(args.file, 'utf8'))
61
+ if (!headers.some((h) => h.toLowerCase() === 'name')) {
62
+ throw new CliError('CSV must include a "name" column', { exitCode: 64 })
63
+ }
64
+
65
+ const needsDefs = headers.some((h) => !(h.toLowerCase() in SPECIAL_COLUMNS))
66
+ const bodies = prepareImportBodies({
67
+ headers,
68
+ rows,
69
+ specialColumns: SPECIAL_COLUMNS,
70
+ defs: needsDefs ? await getFields(this.apiClient, 'person') : [],
71
+ })
72
+
73
+ if (flags['dry-run']) {
74
+ this.log(chalk.green(`${bodies.length} rows valid — nothing created`))
75
+ return
76
+ }
77
+
78
+ const ok = await confirmAction(
79
+ `Create ${bodies.length} persons from ${args.file}?`,
80
+ flags.yes,
81
+ )
82
+ if (!ok) {
83
+ throw new CliError('Aborted', { exitCode: 1 })
84
+ }
85
+
86
+ const spinner = ora(`Importing ${bodies.length} persons...`).start()
87
+ let summary
88
+ try {
89
+ summary = await bulkRun(
90
+ bodies,
91
+ (body) => this.apiClient.post('/api/v2/persons', { body }),
92
+ {
93
+ onProgress: (done, total) => {
94
+ spinner.text = `Importing persons ${done}/${total}`
95
+ },
96
+ },
97
+ )
98
+ } finally {
99
+ spinner.stop()
100
+ }
101
+
102
+ this.log(
103
+ chalk.green(
104
+ `Imported ${summary.succeeded.length}/${bodies.length} persons`,
105
+ ),
106
+ )
107
+
108
+ if (summary.failed.length > 0) {
109
+ for (const { item, error } of summary.failed) {
110
+ this.log(chalk.red(` ✘ ${item.name ?? '(unnamed)'}: ${error}`))
111
+ }
112
+ throw new CliError(
113
+ `${summary.failed.length} of ${bodies.length} rows failed`,
114
+ { exitCode: 1 },
115
+ )
116
+ }
117
+ }
118
+ }
@@ -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 PipelineGetCommand extends BaseCommand {
6
+ static description = 'Get a pipeline by ID'
7
+
8
+ static examples = [
9
+ '<%= config.bin %> pipeline get 1',
10
+ '<%= config.bin %> pipeline get 1 --output json',
11
+ ]
12
+
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ }
16
+
17
+ static args = {
18
+ id: Args.integer({ required: true, description: 'Pipeline ID' }),
19
+ }
20
+
21
+ async run() {
22
+ const { args } = await this.parse(PipelineGetCommand)
23
+ const body = await this.apiClient.get(`/api/v2/pipelines/${args.id}`)
24
+ await outputRecord(this, body.data)
25
+ }
26
+ }
@@ -0,0 +1,37 @@
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
+ is_deal_probability_enabled: { header: 'Probability' },
8
+ order_nr: { header: 'Order' },
9
+ }
10
+
11
+ export default class PipelineListCommand extends BaseCommand {
12
+ static description = 'List pipelines'
13
+
14
+ static examples = [
15
+ '<%= config.bin %> pipeline list',
16
+ '<%= config.bin %> pipeline list --output json',
17
+ ]
18
+
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ }
22
+
23
+ async run() {
24
+ const { flags } = await this.parse(PipelineListCommand)
25
+ const limit = flags.limit ?? 100
26
+
27
+ const query = {
28
+ limit: Math.min(limit, 100),
29
+ }
30
+
31
+ const items = await collectPages(
32
+ this.apiClient.pageV2('/api/v2/pipelines', query),
33
+ limit,
34
+ )
35
+ await this.outputResults(items, columns)
36
+ }
37
+ }
@@ -0,0 +1,48 @@
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 ProjectCreateCommand extends BaseCommand {
7
+ static description = 'Create a project'
8
+
9
+ static examples = [
10
+ '<%= config.bin %> project create --title "Launch"',
11
+ '<%= config.bin %> project create --title "Launch" --owner 3 --status open',
12
+ '<%= config.bin %> project create --title "Raw" --body \'{"deal_ids":[1,2]}\'',
13
+ ]
14
+
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ title: Flags.string({ required: true, description: 'Project title' }),
18
+ description: Flags.string({ description: 'Project description' }),
19
+ status: Flags.string({ description: 'Project status' }),
20
+ 'start-date': Flags.string({ description: 'Start date (YYYY-MM-DD)' }),
21
+ 'end-date': Flags.string({ description: 'End date (YYYY-MM-DD)' }),
22
+ owner: Flags.integer({ description: 'Owner (user) ID' }),
23
+ board: Flags.integer({ description: 'Board ID' }),
24
+ phase: Flags.integer({ description: 'Phase ID' }),
25
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
26
+ }
27
+
28
+ async run() {
29
+ const { flags } = await this.parse(ProjectCreateCommand)
30
+
31
+ const body = buildWriteBody({
32
+ typed: {
33
+ title: flags.title,
34
+ description: flags.description,
35
+ status: flags.status,
36
+ start_date: flags['start-date'],
37
+ end_date: flags['end-date'],
38
+ owner_id: flags.owner,
39
+ board_id: flags.board,
40
+ phase_id: flags.phase,
41
+ },
42
+ rawBody: flags.body,
43
+ })
44
+
45
+ const res = await this.apiClient.post('/api/v2/projects', { body })
46
+ await outputRecord(this, res.data)
47
+ }
48
+ }