@wavyx/pdcli 0.1.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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bin/run.js +20 -0
- package/oclif.manifest.json +2308 -0
- package/package.json +128 -0
- package/src/base-command.js +99 -0
- package/src/commands/activity/get.js +41 -0
- package/src/commands/activity/list.js +61 -0
- package/src/commands/api.js +60 -0
- package/src/commands/auth/login.js +79 -0
- package/src/commands/auth/logout.js +24 -0
- package/src/commands/auth/status.js +62 -0
- package/src/commands/config/get.js +30 -0
- package/src/commands/config/list.js +25 -0
- package/src/commands/config/set.js +29 -0
- package/src/commands/deal/get.js +43 -0
- package/src/commands/deal/list.js +62 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/field/get.js +58 -0
- package/src/commands/field/list.js +41 -0
- package/src/commands/org/get.js +41 -0
- package/src/commands/org/list.js +40 -0
- package/src/commands/person/get.js +41 -0
- package/src/commands/person/list.js +49 -0
- package/src/commands/profile/current.js +16 -0
- package/src/commands/profile/list.js +36 -0
- package/src/commands/profile/use.js +26 -0
- package/src/commands/search.js +67 -0
- package/src/commands/user/me.js +26 -0
- package/src/commands/version.js +24 -0
- package/src/hooks/command-not-found.js +15 -0
- package/src/hooks/init.js +7 -0
- package/src/hooks/prerun.js +7 -0
- package/src/lib/auth.js +95 -0
- package/src/lib/body.js +26 -0
- package/src/lib/client.js +184 -0
- package/src/lib/config.js +71 -0
- package/src/lib/errors.js +118 -0
- package/src/lib/fields.js +120 -0
- package/src/lib/keychain.js +69 -0
- package/src/lib/output/index.js +22 -0
- package/src/lib/output/json.js +7 -0
- package/src/lib/output/record.js +27 -0
- package/src/lib/output/table.js +40 -0
- package/src/lib/pagination.js +14 -0
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
value: {
|
|
9
|
+
header: 'Value',
|
|
10
|
+
get: (row) =>
|
|
11
|
+
row.value != null ? `${row.value} ${row.currency ?? ''}`.trim() : '',
|
|
12
|
+
},
|
|
13
|
+
status: { header: 'Status' },
|
|
14
|
+
stage_id: { header: 'Stage' },
|
|
15
|
+
person_id: { header: 'Person' },
|
|
16
|
+
org_id: { header: 'Org' },
|
|
17
|
+
owner_id: { header: 'Owner' },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default class DealListCommand extends BaseCommand {
|
|
21
|
+
static description = 'List deals'
|
|
22
|
+
|
|
23
|
+
static examples = [
|
|
24
|
+
'<%= config.bin %> deal list',
|
|
25
|
+
'<%= config.bin %> deal list --status won --limit 50',
|
|
26
|
+
'<%= config.bin %> deal list --stage 3 --output json',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
static flags = {
|
|
30
|
+
...BaseCommand.baseFlags,
|
|
31
|
+
status: Flags.string({
|
|
32
|
+
description: 'Filter by status',
|
|
33
|
+
options: ['open', 'won', 'lost', 'deleted'],
|
|
34
|
+
}),
|
|
35
|
+
stage: Flags.integer({ description: 'Filter by stage ID' }),
|
|
36
|
+
pipeline: Flags.integer({ description: 'Filter by pipeline ID' }),
|
|
37
|
+
owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
|
|
38
|
+
person: Flags.integer({ description: 'Filter by person ID' }),
|
|
39
|
+
org: Flags.integer({ description: 'Filter by organization ID' }),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async run() {
|
|
43
|
+
const { flags } = await this.parse(DealListCommand)
|
|
44
|
+
const limit = flags.limit ?? 100
|
|
45
|
+
|
|
46
|
+
const query = {
|
|
47
|
+
status: flags.status,
|
|
48
|
+
stage_id: flags.stage,
|
|
49
|
+
pipeline_id: flags.pipeline,
|
|
50
|
+
owner_id: flags.owner,
|
|
51
|
+
person_id: flags.person,
|
|
52
|
+
org_id: flags.org,
|
|
53
|
+
limit: Math.min(limit, 100),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const items = await collectPages(
|
|
57
|
+
this.apiClient.pageV2('/api/v2/deals', query),
|
|
58
|
+
limit,
|
|
59
|
+
)
|
|
60
|
+
await this.outputResults(items, columns)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import BaseCommand from '../base-command.js'
|
|
4
|
+
import { getConf, getActiveProfile, getProfileConfig } from '../lib/config.js'
|
|
5
|
+
import { getToken, isKeychainAvailable } from '../lib/keychain.js'
|
|
6
|
+
import { companyDomainToBaseOrigin } from '../lib/auth.js'
|
|
7
|
+
|
|
8
|
+
const PASS = chalk.green('✔')
|
|
9
|
+
const FAIL = chalk.red('✘')
|
|
10
|
+
|
|
11
|
+
export default class DoctorCommand extends BaseCommand {
|
|
12
|
+
static skipAuth = true
|
|
13
|
+
|
|
14
|
+
static description = 'Run diagnostic checks on the CLI environment'
|
|
15
|
+
|
|
16
|
+
static examples = ['<%= config.bin %> doctor']
|
|
17
|
+
|
|
18
|
+
async run() {
|
|
19
|
+
const spinner = ora('Running diagnostics...').start()
|
|
20
|
+
const results = []
|
|
21
|
+
|
|
22
|
+
// 1. Config directory accessible
|
|
23
|
+
try {
|
|
24
|
+
getConf()
|
|
25
|
+
results.push({ label: 'Config directory accessible', ok: true })
|
|
26
|
+
} catch {
|
|
27
|
+
results.push({
|
|
28
|
+
label: 'Config directory accessible',
|
|
29
|
+
ok: false,
|
|
30
|
+
detail: 'Cannot access config store',
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Keychain available
|
|
35
|
+
const keychainOk = isKeychainAvailable()
|
|
36
|
+
results.push({
|
|
37
|
+
label: 'Keychain available',
|
|
38
|
+
ok: keychainOk,
|
|
39
|
+
detail: keychainOk
|
|
40
|
+
? undefined
|
|
41
|
+
: 'OS keychain unavailable; pdcli cannot store credentials',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// 3. Active profile set
|
|
45
|
+
let profile
|
|
46
|
+
try {
|
|
47
|
+
profile = getActiveProfile()
|
|
48
|
+
results.push({ label: 'Active profile set', ok: true, detail: profile })
|
|
49
|
+
} catch {
|
|
50
|
+
results.push({ label: 'Active profile set', ok: false })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Company domain set
|
|
54
|
+
const domain = profile
|
|
55
|
+
? getProfileConfig(profile, 'company_domain')
|
|
56
|
+
: undefined
|
|
57
|
+
results.push({
|
|
58
|
+
label: 'Company domain set',
|
|
59
|
+
ok: Boolean(domain),
|
|
60
|
+
detail: domain ?? 'Run: pdcli auth login',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// 5. Token present
|
|
64
|
+
if (profile) {
|
|
65
|
+
const token = await getToken(profile)
|
|
66
|
+
results.push({
|
|
67
|
+
label: 'API token present',
|
|
68
|
+
ok: token !== null,
|
|
69
|
+
detail: token ? undefined : 'Run: pdcli auth login',
|
|
70
|
+
})
|
|
71
|
+
} else {
|
|
72
|
+
results.push({
|
|
73
|
+
label: 'API token present',
|
|
74
|
+
ok: false,
|
|
75
|
+
detail: 'No active profile',
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 6. API reachable (any HTTP response from the company host counts —
|
|
80
|
+
// a 401 still proves the host resolves and answers)
|
|
81
|
+
if (domain) {
|
|
82
|
+
try {
|
|
83
|
+
await fetch(`${companyDomainToBaseOrigin(domain)}/api/v1/users/me`, {
|
|
84
|
+
signal: AbortSignal.timeout(5000),
|
|
85
|
+
})
|
|
86
|
+
results.push({ label: 'API reachable', ok: true })
|
|
87
|
+
} catch {
|
|
88
|
+
results.push({
|
|
89
|
+
label: 'API reachable',
|
|
90
|
+
ok: false,
|
|
91
|
+
detail: `Could not reach ${domain}.pipedrive.com`,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
results.push({
|
|
96
|
+
label: 'API reachable',
|
|
97
|
+
ok: false,
|
|
98
|
+
detail: 'No company domain configured',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
spinner.stop()
|
|
103
|
+
|
|
104
|
+
this.log('')
|
|
105
|
+
this.log(chalk.bold('Pipedrive CLI Diagnostics'))
|
|
106
|
+
this.log('')
|
|
107
|
+
|
|
108
|
+
for (const { label, ok, detail } of results) {
|
|
109
|
+
const icon = ok ? PASS : FAIL
|
|
110
|
+
const suffix = detail ? chalk.dim(` (${detail})`) : ''
|
|
111
|
+
this.log(` ${icon} ${label}${suffix}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.log('')
|
|
115
|
+
|
|
116
|
+
const failed = results.filter((r) => !r.ok).length
|
|
117
|
+
if (failed > 0) {
|
|
118
|
+
this.log(chalk.yellow(`${failed} check${failed > 1 ? 's' : ''} failed`))
|
|
119
|
+
} else {
|
|
120
|
+
this.log(chalk.green('All checks passed'))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Args } from '@oclif/core'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getFields, makeResolver } from '../../lib/fields.js'
|
|
4
|
+
import { CliError } from '../../lib/errors.js'
|
|
5
|
+
|
|
6
|
+
export default class FieldGetCommand extends BaseCommand {
|
|
7
|
+
static description = 'Show one field by human name or hashed key'
|
|
8
|
+
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> field get deal "Deal Size"',
|
|
11
|
+
'<%= config.bin %> field get deal dcf558aac1ae4e8c4f849ba5e668430d8df9be12',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
static args = {
|
|
15
|
+
entity: Args.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Entity type',
|
|
18
|
+
options: ['deal', 'person', 'org', 'organization', 'product', 'activity'],
|
|
19
|
+
}),
|
|
20
|
+
field: Args.string({
|
|
21
|
+
required: true,
|
|
22
|
+
description: 'Field name (human label) or hashed key',
|
|
23
|
+
}),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static flags = {
|
|
27
|
+
...BaseCommand.baseFlags,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async run() {
|
|
31
|
+
const { args } = await this.parse(FieldGetCommand)
|
|
32
|
+
|
|
33
|
+
const defs = await getFields(this.apiClient, args.entity)
|
|
34
|
+
const resolver = makeResolver(defs)
|
|
35
|
+
|
|
36
|
+
const key = resolver.nameToKey(args.field) ?? args.field
|
|
37
|
+
const def = defs.find((d) => d.field_code === key)
|
|
38
|
+
|
|
39
|
+
if (!def) {
|
|
40
|
+
throw new CliError(
|
|
41
|
+
`No field named or keyed "${args.field}" on ${args.entity}. ` +
|
|
42
|
+
`Run: pdcli field list ${args.entity}`,
|
|
43
|
+
{ exitCode: 65 },
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await this.outputResults(def, {
|
|
48
|
+
field_code: { header: 'Key' },
|
|
49
|
+
field_name: { header: 'Name' },
|
|
50
|
+
field_type: { header: 'Type' },
|
|
51
|
+
options: {
|
|
52
|
+
header: 'Options',
|
|
53
|
+
get: (row) =>
|
|
54
|
+
row.options?.map((o) => `${o.id}=${o.label}`).join(', ') ?? '',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Args } from '@oclif/core'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getFields } from '../../lib/fields.js'
|
|
4
|
+
|
|
5
|
+
export default class FieldListCommand extends BaseCommand {
|
|
6
|
+
static description =
|
|
7
|
+
'List fields for an entity, including custom-field hash keys'
|
|
8
|
+
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> field list deal',
|
|
11
|
+
'<%= config.bin %> field list person --output json',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
static args = {
|
|
15
|
+
entity: Args.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Entity type',
|
|
18
|
+
options: ['deal', 'person', 'org', 'organization', 'product', 'activity'],
|
|
19
|
+
}),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static flags = {
|
|
23
|
+
...BaseCommand.baseFlags,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async run() {
|
|
27
|
+
const { args } = await this.parse(FieldListCommand)
|
|
28
|
+
|
|
29
|
+
const defs = await getFields(this.apiClient, args.entity)
|
|
30
|
+
|
|
31
|
+
await this.outputResults(defs, {
|
|
32
|
+
field_code: { header: 'Key' },
|
|
33
|
+
field_name: { header: 'Name' },
|
|
34
|
+
field_type: { header: 'Type' },
|
|
35
|
+
options: {
|
|
36
|
+
header: 'Options',
|
|
37
|
+
get: (row) => row.options?.map((o) => o.label).join(', ') ?? '',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Args } from '@oclif/core'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getFields, makeResolver } from '../../lib/fields.js'
|
|
4
|
+
import { flattenRecord } from '../../lib/output/record.js'
|
|
5
|
+
|
|
6
|
+
export default class OrgGetCommand extends BaseCommand {
|
|
7
|
+
static description = 'Get an organization by ID'
|
|
8
|
+
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> org get 7',
|
|
11
|
+
'<%= config.bin %> org get 7 --output json',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
static flags = {
|
|
15
|
+
...BaseCommand.baseFlags,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static args = {
|
|
19
|
+
id: Args.integer({ required: true, description: 'Organization ID' }),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async run() {
|
|
23
|
+
const { args } = await this.parse(OrgGetCommand)
|
|
24
|
+
const body = await this.apiClient.get(`/api/v2/organizations/${args.id}`)
|
|
25
|
+
let record = body.data
|
|
26
|
+
|
|
27
|
+
if (this.resolveFormat() === 'table') {
|
|
28
|
+
if (record.custom_fields && Object.keys(record.custom_fields).length) {
|
|
29
|
+
const defs = await getFields(this.apiClient, 'org')
|
|
30
|
+
record = makeResolver(defs).resolveCustomFields(record)
|
|
31
|
+
}
|
|
32
|
+
await this.outputResults(flattenRecord(record), {
|
|
33
|
+
field: { header: 'Field' },
|
|
34
|
+
value: { header: 'Value' },
|
|
35
|
+
})
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await this.outputResults(record, {})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
name: { header: 'Name' },
|
|
8
|
+
owner_id: { header: 'Owner' },
|
|
9
|
+
add_time: { header: 'Created' },
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class OrgListCommand extends BaseCommand {
|
|
13
|
+
static description = 'List organizations'
|
|
14
|
+
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> org list',
|
|
17
|
+
'<%= config.bin %> org list --owner 3 --output json',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
static flags = {
|
|
21
|
+
...BaseCommand.baseFlags,
|
|
22
|
+
owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async run() {
|
|
26
|
+
const { flags } = await this.parse(OrgListCommand)
|
|
27
|
+
const limit = flags.limit ?? 100
|
|
28
|
+
|
|
29
|
+
const query = {
|
|
30
|
+
owner_id: flags.owner,
|
|
31
|
+
limit: Math.min(limit, 100),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const items = await collectPages(
|
|
35
|
+
this.apiClient.pageV2('/api/v2/organizations', query),
|
|
36
|
+
limit,
|
|
37
|
+
)
|
|
38
|
+
await this.outputResults(items, columns)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Args } from '@oclif/core'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getFields, makeResolver } from '../../lib/fields.js'
|
|
4
|
+
import { flattenRecord } from '../../lib/output/record.js'
|
|
5
|
+
|
|
6
|
+
export default class PersonGetCommand extends BaseCommand {
|
|
7
|
+
static description = 'Get a person by ID'
|
|
8
|
+
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> person get 5',
|
|
11
|
+
'<%= config.bin %> person get 5 --output json',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
static flags = {
|
|
15
|
+
...BaseCommand.baseFlags,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static args = {
|
|
19
|
+
id: Args.integer({ required: true, description: 'Person ID' }),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async run() {
|
|
23
|
+
const { args } = await this.parse(PersonGetCommand)
|
|
24
|
+
const body = await this.apiClient.get(`/api/v2/persons/${args.id}`)
|
|
25
|
+
let record = body.data
|
|
26
|
+
|
|
27
|
+
if (this.resolveFormat() === 'table') {
|
|
28
|
+
if (record.custom_fields && Object.keys(record.custom_fields).length) {
|
|
29
|
+
const defs = await getFields(this.apiClient, 'person')
|
|
30
|
+
record = makeResolver(defs).resolveCustomFields(record)
|
|
31
|
+
}
|
|
32
|
+
await this.outputResults(flattenRecord(record), {
|
|
33
|
+
field: { header: 'Field' },
|
|
34
|
+
value: { header: 'Value' },
|
|
35
|
+
})
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await this.outputResults(record, {})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -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
|
+
function primary(list) {
|
|
6
|
+
if (!Array.isArray(list) || list.length === 0) return ''
|
|
7
|
+
return (list.find((e) => e.primary) ?? list[0]).value ?? ''
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const columns = {
|
|
11
|
+
id: { header: 'ID' },
|
|
12
|
+
name: { header: 'Name' },
|
|
13
|
+
email: { header: 'Email', get: (row) => primary(row.emails) },
|
|
14
|
+
phone: { header: 'Phone', get: (row) => primary(row.phones) },
|
|
15
|
+
org_id: { header: 'Org' },
|
|
16
|
+
owner_id: { header: 'Owner' },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default class PersonListCommand extends BaseCommand {
|
|
20
|
+
static description = 'List persons (contacts)'
|
|
21
|
+
|
|
22
|
+
static examples = [
|
|
23
|
+
'<%= config.bin %> person list',
|
|
24
|
+
'<%= config.bin %> person list --org 7 --output json',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
static flags = {
|
|
28
|
+
...BaseCommand.baseFlags,
|
|
29
|
+
owner: Flags.integer({ description: 'Filter by owner (user) ID' }),
|
|
30
|
+
org: Flags.integer({ description: 'Filter by organization ID' }),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async run() {
|
|
34
|
+
const { flags } = await this.parse(PersonListCommand)
|
|
35
|
+
const limit = flags.limit ?? 100
|
|
36
|
+
|
|
37
|
+
const query = {
|
|
38
|
+
owner_id: flags.owner,
|
|
39
|
+
org_id: flags.org,
|
|
40
|
+
limit: Math.min(limit, 100),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const items = await collectPages(
|
|
44
|
+
this.apiClient.pageV2('/api/v2/persons', query),
|
|
45
|
+
limit,
|
|
46
|
+
)
|
|
47
|
+
await this.outputResults(items, columns)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getActiveProfile } from '../../lib/config.js'
|
|
4
|
+
|
|
5
|
+
export default class ProfileCurrentCommand extends BaseCommand {
|
|
6
|
+
static skipAuth = true
|
|
7
|
+
|
|
8
|
+
static description = 'Show the active profile'
|
|
9
|
+
|
|
10
|
+
static examples = ['<%= config.bin %> profile current']
|
|
11
|
+
|
|
12
|
+
async run() {
|
|
13
|
+
const active = getActiveProfile()
|
|
14
|
+
this.log(chalk.cyan(active))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import BaseCommand from '../../base-command.js'
|
|
3
|
+
import { getAllProfiles, getActiveProfile } from '../../lib/config.js'
|
|
4
|
+
import { getToken } from '../../lib/keychain.js'
|
|
5
|
+
|
|
6
|
+
export default class ProfileListCommand extends BaseCommand {
|
|
7
|
+
static skipAuth = true
|
|
8
|
+
|
|
9
|
+
static description = 'List all configured profiles'
|
|
10
|
+
|
|
11
|
+
static examples = ['<%= config.bin %> profile list']
|
|
12
|
+
|
|
13
|
+
async run() {
|
|
14
|
+
const profiles = getAllProfiles()
|
|
15
|
+
const active = getActiveProfile()
|
|
16
|
+
const names = new Set(Object.keys(profiles))
|
|
17
|
+
|
|
18
|
+
const activeToken = await getToken(active)
|
|
19
|
+
if (activeToken) names.add(active)
|
|
20
|
+
|
|
21
|
+
if (names.size === 0) {
|
|
22
|
+
this.log('No profiles configured. Run: pdcli auth login')
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
const token = await getToken(name)
|
|
28
|
+
const status = token ? chalk.dim(' (authenticated)') : ''
|
|
29
|
+
if (name === active) {
|
|
30
|
+
this.log(chalk.green(`* ${name}`) + status)
|
|
31
|
+
} else {
|
|
32
|
+
this.log(` ${name}${status}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args } from '@oclif/core'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import BaseCommand from '../../base-command.js'
|
|
4
|
+
import { setActiveProfile } from '../../lib/config.js'
|
|
5
|
+
|
|
6
|
+
export default class ProfileUseCommand extends BaseCommand {
|
|
7
|
+
static skipAuth = true
|
|
8
|
+
|
|
9
|
+
static description = 'Switch the active profile'
|
|
10
|
+
|
|
11
|
+
static examples = ['<%= config.bin %> profile use work']
|
|
12
|
+
|
|
13
|
+
static args = {
|
|
14
|
+
name: Args.string({
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'Profile name to activate',
|
|
17
|
+
}),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run() {
|
|
21
|
+
const { args } = await this.parse(ProfileUseCommand)
|
|
22
|
+
|
|
23
|
+
setActiveProfile(args.name)
|
|
24
|
+
this.log(`Switched to profile ${chalk.cyan(args.name)}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core'
|
|
2
|
+
import BaseCommand from '../base-command.js'
|
|
3
|
+
import { CliError } from '../lib/errors.js'
|
|
4
|
+
|
|
5
|
+
const columns = {
|
|
6
|
+
type: { header: 'Type' },
|
|
7
|
+
id: { header: 'ID' },
|
|
8
|
+
name: {
|
|
9
|
+
header: 'Name / Title',
|
|
10
|
+
get: (row) => row.title ?? row.name ?? '',
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default class SearchCommand extends BaseCommand {
|
|
15
|
+
static description =
|
|
16
|
+
'Search across deals, persons, organizations, products, leads, files, and projects'
|
|
17
|
+
|
|
18
|
+
static examples = [
|
|
19
|
+
'<%= config.bin %> search "acme"',
|
|
20
|
+
'<%= config.bin %> search "acme" --item-types deal,person --output json',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
static args = {
|
|
24
|
+
term: Args.string({ required: true, description: 'Search term' }),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static flags = {
|
|
28
|
+
...BaseCommand.baseFlags,
|
|
29
|
+
'item-types': Flags.string({
|
|
30
|
+
description:
|
|
31
|
+
'Comma-separated item types (deal,person,organization,product,lead,file,mail_attachment,project)',
|
|
32
|
+
}),
|
|
33
|
+
exact: Flags.boolean({
|
|
34
|
+
description: 'Exact match (allows 1-character terms)',
|
|
35
|
+
default: false,
|
|
36
|
+
}),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async run() {
|
|
40
|
+
const { args, flags } = await this.parse(SearchCommand)
|
|
41
|
+
|
|
42
|
+
const minLength = flags.exact ? 1 : 2
|
|
43
|
+
if (args.term.length < minLength) {
|
|
44
|
+
throw new CliError(
|
|
45
|
+
`Search term must be at least 2 characters (1 with --exact)`,
|
|
46
|
+
{ exitCode: 64 },
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
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
|
+
})
|
|
59
|
+
|
|
60
|
+
const items = (body.data?.items ?? []).map((entry) => ({
|
|
61
|
+
...entry.item,
|
|
62
|
+
result_score: entry.result_score,
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
await this.outputResults(items, columns)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import BaseCommand from '../../base-command.js'
|
|
2
|
+
|
|
3
|
+
export default class UserMeCommand extends BaseCommand {
|
|
4
|
+
static description = 'Show the authenticated user'
|
|
5
|
+
|
|
6
|
+
static examples = ['<%= config.bin %> user me']
|
|
7
|
+
|
|
8
|
+
static flags = {
|
|
9
|
+
...BaseCommand.baseFlags,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run() {
|
|
13
|
+
await this.parse(UserMeCommand)
|
|
14
|
+
|
|
15
|
+
// Users API is v1-only (no v2 equivalent as of June 2026).
|
|
16
|
+
const body = await this.apiClient.get('/api/v1/users/me')
|
|
17
|
+
|
|
18
|
+
await this.outputResults(body.data, {
|
|
19
|
+
id: { header: 'ID' },
|
|
20
|
+
name: { header: 'Name' },
|
|
21
|
+
email: { header: 'Email' },
|
|
22
|
+
is_admin: { header: 'Admin' },
|
|
23
|
+
timezone_name: { header: 'Timezone' },
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import BaseCommand from '../base-command.js'
|
|
3
|
+
import { getProfileConfig } from '../lib/config.js'
|
|
4
|
+
import { companyDomainToBaseOrigin } from '../lib/auth.js'
|
|
5
|
+
|
|
6
|
+
export default class VersionCommand extends BaseCommand {
|
|
7
|
+
static skipAuth = true
|
|
8
|
+
|
|
9
|
+
static description = 'Show CLI version and environment info'
|
|
10
|
+
|
|
11
|
+
static examples = ['<%= config.bin %> version']
|
|
12
|
+
|
|
13
|
+
async run() {
|
|
14
|
+
const domain = getProfileConfig(this.activeProfile, 'company_domain')
|
|
15
|
+
const apiBase = domain
|
|
16
|
+
? companyDomainToBaseOrigin(domain)
|
|
17
|
+
: chalk.dim('(not set)')
|
|
18
|
+
|
|
19
|
+
this.log(`${chalk.bold('pdcli')} ${chalk.cyan(this.config.version)}`)
|
|
20
|
+
this.log(`Node: ${process.version}`)
|
|
21
|
+
this.log(`API base: ${apiBase}`)
|
|
22
|
+
this.log(`Platform: ${process.platform}-${process.arch}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import createDebug from 'debug'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
|
|
4
|
+
const debug = createDebug('pd:command-not-found')
|
|
5
|
+
|
|
6
|
+
export default async function commandNotFound(options) {
|
|
7
|
+
debug('command not found: %s', options.id)
|
|
8
|
+
process.stderr.write(
|
|
9
|
+
`${chalk.red('Error:')} ${chalk.yellow(options.id)} is not a pdcli command.\n`,
|
|
10
|
+
)
|
|
11
|
+
process.stderr.write(
|
|
12
|
+
`Run ${chalk.cyan('pdcli help')} for a list of available commands.\n`,
|
|
13
|
+
)
|
|
14
|
+
process.exit(127)
|
|
15
|
+
}
|