@pitcher/cli-scripts 2.3.0 → 2.5.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/README.md +220 -0
- package/dist/index.js +24 -2
- package/dist/src/commands/analytics.d.ts +12 -0
- package/dist/src/commands/analytics.js +45 -0
- package/dist/src/commands/announcements.d.ts +23 -0
- package/dist/src/commands/announcements.js +131 -0
- package/dist/src/commands/canvases.d.ts +25 -0
- package/dist/src/commands/canvases.js +73 -0
- package/dist/src/commands/dsrLanding.d.ts +28 -0
- package/dist/src/commands/dsrLanding.js +150 -0
- package/dist/src/commands/emailTemplate.d.ts +32 -0
- package/dist/src/commands/emailTemplate.js +124 -0
- package/dist/src/commands/files.js +5 -8
- package/dist/src/commands/folders.js +5 -8
- package/dist/src/commands/instances.d.ts +21 -0
- package/dist/src/commands/instances.js +58 -0
- package/dist/src/commands/org.d.ts +9 -0
- package/dist/src/commands/org.js +26 -0
- package/dist/src/commands/scaffolds.d.ts +19 -0
- package/dist/src/commands/scaffolds.js +176 -0
- package/dist/src/commands/search.d.ts +13 -0
- package/dist/src/commands/search.js +35 -0
- package/dist/src/commands/sharedLinks.d.ts +16 -0
- package/dist/src/commands/sharedLinks.js +61 -0
- package/dist/src/commands/tags.d.ts +20 -0
- package/dist/src/commands/tags.js +52 -0
- package/dist/src/commands/themes.d.ts +17 -0
- package/dist/src/commands/themes.js +82 -0
- package/dist/src/commands/withAuth.d.ts +3 -0
- package/dist/src/commands/withAuth.js +7 -0
- package/dist/src/commands/withInstance.d.ts +3 -0
- package/dist/src/commands/withInstance.js +4 -0
- package/dist/src/core/pagination.d.ts +11 -0
- package/dist/src/core/pagination.js +14 -0
- package/dist/src/core/readInput.d.ts +2 -0
- package/dist/src/core/readInput.js +23 -0
- package/dist/src/schema/content.d.ts +3 -0
- package/dist/src/schema/content.js +13 -7
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { readTextFile } from '../core/readInput.js';
|
|
7
|
+
import { parseJsonArray } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
import { withInstanceFlag } from './withInstance.js';
|
|
10
|
+
import { DSR_SCAFFOLD_HTML, emitScaffold } from './scaffolds.js';
|
|
11
|
+
const DSR_ENDPOINT = '/core/api/protected/dsr-landing-templates';
|
|
12
|
+
const REQUIRED_MARKER = 'data-pitcher-email-gate';
|
|
13
|
+
const TOKEN_HELP = `
|
|
14
|
+
Required marker (the platform injects the email-gate form here at render time):
|
|
15
|
+
data-pitcher-email-gate must be present verbatim in --html-file, or create fails client-side
|
|
16
|
+
|
|
17
|
+
Editable regions (optional, edited in the DSR landing editor without touching markup):
|
|
18
|
+
data-field-id="..." unique id for the editable block
|
|
19
|
+
data-field-editable="true" marks the block as editable
|
|
20
|
+
data-field-char-limit="N" max character length enforced by the editor
|
|
21
|
+
|
|
22
|
+
Tokens (resolved by the platform at render time; unknown tokens render blank):
|
|
23
|
+
{{recipient.firstName}}, {{recipient.lastName}}, {{recipient.email}} recipient fields
|
|
24
|
+
{{customer.name}} recipient's company
|
|
25
|
+
{{organization.name}} sharing org's display name
|
|
26
|
+
{{rep.name}} sharing rep's display name
|
|
27
|
+
{{sharedLink.valid_until}} share expiry, if set
|
|
28
|
+
|
|
29
|
+
Run "p dsr-landing scaffold" for a full working example.`;
|
|
30
|
+
export async function runDsrLandingCreate(opts, deps = {}) {
|
|
31
|
+
try {
|
|
32
|
+
if (!opts.name)
|
|
33
|
+
throw createCliError('VALIDATION', '--name is required for dsr-landing create');
|
|
34
|
+
if (!opts.instanceId)
|
|
35
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing create');
|
|
36
|
+
if (!opts.htmlFile)
|
|
37
|
+
throw createCliError('VALIDATION', '--html-file is required for dsr-landing create');
|
|
38
|
+
// readTextFile throws VALIDATION on a missing/unreadable file, before any request is made.
|
|
39
|
+
const html = readTextFile(opts.htmlFile);
|
|
40
|
+
if (!html.includes(REQUIRED_MARKER)) {
|
|
41
|
+
throw createCliError('VALIDATION', `landing HTML must contain the ${REQUIRED_MARKER} marker`);
|
|
42
|
+
}
|
|
43
|
+
if (opts.language && opts.language.length > 10) {
|
|
44
|
+
throw createCliError('VALIDATION', '--language must be at most 10 characters');
|
|
45
|
+
}
|
|
46
|
+
const body = { instance_id: opts.instanceId, name: opts.name, html };
|
|
47
|
+
if (opts.cssFile)
|
|
48
|
+
body.css = readTextFile(opts.cssFile);
|
|
49
|
+
if (opts.mergeSchema)
|
|
50
|
+
body.merge_schema = parseJsonArray(opts.mergeSchema, 'merge-schema');
|
|
51
|
+
if (opts.thumbnailUrl)
|
|
52
|
+
body.thumbnail_url = opts.thumbnailUrl;
|
|
53
|
+
if (opts.language)
|
|
54
|
+
body.language = opts.language;
|
|
55
|
+
const client = clientFor(opts, deps);
|
|
56
|
+
const res = await client.post(DSR_ENDPOINT, body, { params: { instance_id: opts.instanceId } });
|
|
57
|
+
const created = res.data;
|
|
58
|
+
if (opts.default) {
|
|
59
|
+
// The template was created, but --default asked for a compound operation:
|
|
60
|
+
// if set-default can't complete, the instance default is NOT this template.
|
|
61
|
+
// Report that as a failure (non-ok, non-zero exit) so an agent keying on the
|
|
62
|
+
// envelope doesn't believe the default changed. The created id is surfaced in
|
|
63
|
+
// the message so the caller can retry `set-default` without re-creating.
|
|
64
|
+
const id = created?.id;
|
|
65
|
+
if (!id) {
|
|
66
|
+
return fail(createCliError('UNKNOWN', 'template created, but --default could not be applied: server response had no id'));
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await client.post(`${DSR_ENDPOINT}/${encodeURIComponent(id)}/set-default`, undefined, {
|
|
70
|
+
params: { instance_id: opts.instanceId },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
const err = toCliError(e);
|
|
75
|
+
return fail(createCliError(err.code, `template created (id ${id}), but set-default failed: ${err.message}. ` +
|
|
76
|
+
`Retry with: p dsr-landing set-default --instance-id ${opts.instanceId} --id ${id}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return ok(created);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return fail(toCliError(e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function runDsrLandingSetDefault(opts, deps = {}) {
|
|
86
|
+
try {
|
|
87
|
+
if (!opts.id)
|
|
88
|
+
throw createCliError('VALIDATION', '--id is required for dsr-landing set-default');
|
|
89
|
+
if (!opts.instanceId)
|
|
90
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing set-default');
|
|
91
|
+
const client = clientFor(opts, deps);
|
|
92
|
+
const res = await client.post(`${DSR_ENDPOINT}/${encodeURIComponent(opts.id)}/set-default`, undefined, {
|
|
93
|
+
params: { instance_id: opts.instanceId },
|
|
94
|
+
});
|
|
95
|
+
return ok(res.data);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return fail(toCliError(e));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function runDsrLandingList(opts, deps = {}) {
|
|
102
|
+
try {
|
|
103
|
+
if (!opts.instanceId)
|
|
104
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing list');
|
|
105
|
+
const client = clientFor(opts, deps);
|
|
106
|
+
const res = await client.get(DSR_ENDPOINT, { params: { instance_id: opts.instanceId } });
|
|
107
|
+
return ok(res.data);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return fail(toCliError(e));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function runDsrLandingScaffold(opts, _deps = {}) {
|
|
114
|
+
return emitScaffold(DSR_SCAFFOLD_HTML, opts.out);
|
|
115
|
+
}
|
|
116
|
+
export function createDsrLandingCommand() {
|
|
117
|
+
const cmd = new Command('dsr-landing').description('create/list DSR (Digital Sales Room) landing-page templates, and set one as default');
|
|
118
|
+
withInstanceFlag(withAuthFlags(cmd.command('create')))
|
|
119
|
+
.requiredOption('--name <name>', 'Template name')
|
|
120
|
+
.requiredOption('--html-file <path>', `Path to the landing HTML (must contain ${REQUIRED_MARKER})`)
|
|
121
|
+
.option('--css-file <path>', 'Path to a CSS file for the template')
|
|
122
|
+
.option('--merge-schema <json>', 'Merge schema array as inline JSON')
|
|
123
|
+
.option('--language <lang>', 'Language code (max 10 chars)')
|
|
124
|
+
.option('--thumbnail-url <url>', 'Thumbnail image URL')
|
|
125
|
+
.option('--default', 'Also set this template as the instance-scope default after creating it')
|
|
126
|
+
.addHelpText('after', TOKEN_HELP)
|
|
127
|
+
.action(async (options, command) => {
|
|
128
|
+
const globals = command.optsWithGlobals();
|
|
129
|
+
emitEnvelope(await runDsrLandingCreate({ ...options, quiet: globals.quiet }), globals);
|
|
130
|
+
});
|
|
131
|
+
withInstanceFlag(withAuthFlags(cmd.command('set-default')))
|
|
132
|
+
.requiredOption('--id <id>', 'Template ID')
|
|
133
|
+
.action(async (options, command) => {
|
|
134
|
+
const globals = command.optsWithGlobals();
|
|
135
|
+
emitEnvelope(await runDsrLandingSetDefault({ ...options, quiet: globals.quiet }), globals);
|
|
136
|
+
});
|
|
137
|
+
withInstanceFlag(withAuthFlags(cmd.command('list'))).action(async (options, command) => {
|
|
138
|
+
const globals = command.optsWithGlobals();
|
|
139
|
+
emitEnvelope(await runDsrLandingList({ ...options, quiet: globals.quiet }), globals);
|
|
140
|
+
});
|
|
141
|
+
withAuthFlags(cmd.command('scaffold'))
|
|
142
|
+
.description('generate a starter DSR landing page HTML with the required marker and tokens in context')
|
|
143
|
+
.option('--out <path>', 'Write the scaffold HTML to a file instead of returning it inline')
|
|
144
|
+
.addHelpText('after', TOKEN_HELP)
|
|
145
|
+
.action(async (options, command) => {
|
|
146
|
+
const globals = command.optsWithGlobals();
|
|
147
|
+
emitEnvelope(await runDsrLandingScaffold({ ...options, quiet: globals.quiet }), globals);
|
|
148
|
+
});
|
|
149
|
+
return cmd;
|
|
150
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface EmailTemplateDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runEmailTemplateCreate(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
bodyFile?: string;
|
|
12
|
+
subject?: string;
|
|
13
|
+
tags?: string;
|
|
14
|
+
metadata?: string;
|
|
15
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
16
|
+
export declare function runEmailTemplateList(opts: CommonOpts & {
|
|
17
|
+
instanceId?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
offset?: number;
|
|
24
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
25
|
+
export declare function runEmailTemplateGet(opts: CommonOpts & {
|
|
26
|
+
instanceId?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
29
|
+
export declare function runEmailTemplateScaffold(opts: CommonOpts & {
|
|
30
|
+
out?: string;
|
|
31
|
+
}, _deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
32
|
+
export declare function createEmailTemplateCommand(): Command;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { readTextFile } from '../core/readInput.js';
|
|
7
|
+
import { listLimitOffsetParams } from '../core/pagination.js';
|
|
8
|
+
import { assertIntFlags, parseJsonObject } from '../schema/content.js';
|
|
9
|
+
import { withAuthFlags } from './withAuth.js';
|
|
10
|
+
import { withInstanceFlag } from './withInstance.js';
|
|
11
|
+
import { EMAIL_SCAFFOLD_HTML, emitScaffold } from './scaffolds.js';
|
|
12
|
+
const TEMPLATES_ENDPOINT = '/core/api/protected/emails/templates';
|
|
13
|
+
const TOKEN_HELP = `
|
|
14
|
+
Tokens (resolved by the emails app at send time; unknown tokens render blank):
|
|
15
|
+
{{recipient.firstName}} recipient's first name
|
|
16
|
+
{{recipient.lastName}} recipient's last name
|
|
17
|
+
{{recipient.email}} recipient's email address
|
|
18
|
+
|
|
19
|
+
Run "p email-template scaffold" for a full working example.`;
|
|
20
|
+
export async function runEmailTemplateCreate(opts, deps = {}) {
|
|
21
|
+
try {
|
|
22
|
+
if (!opts.name)
|
|
23
|
+
throw createCliError('VALIDATION', '--name is required for email-template create');
|
|
24
|
+
if (!opts.instanceId)
|
|
25
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template create');
|
|
26
|
+
const body = { name: opts.name };
|
|
27
|
+
if (opts.subject)
|
|
28
|
+
body.subject = opts.subject;
|
|
29
|
+
// readTextFile throws VALIDATION on a missing/unreadable file, before any request is made.
|
|
30
|
+
if (opts.bodyFile)
|
|
31
|
+
body.body = readTextFile(opts.bodyFile);
|
|
32
|
+
if (opts.tags)
|
|
33
|
+
body.tags = parseJsonObject(opts.tags, 'tags');
|
|
34
|
+
if (opts.metadata)
|
|
35
|
+
body.metadata = parseJsonObject(opts.metadata, 'metadata');
|
|
36
|
+
const client = clientFor(opts, deps);
|
|
37
|
+
const res = await client.post(TEMPLATES_ENDPOINT, body, { headers: { 'x-instance-id': opts.instanceId } });
|
|
38
|
+
return ok(res.data);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return fail(toCliError(e));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function runEmailTemplateList(opts, deps = {}) {
|
|
45
|
+
try {
|
|
46
|
+
if (!opts.instanceId)
|
|
47
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template list');
|
|
48
|
+
assertIntFlags(opts);
|
|
49
|
+
const client = clientFor(opts, deps);
|
|
50
|
+
const params = { ...listLimitOffsetParams(opts) };
|
|
51
|
+
if (opts.name)
|
|
52
|
+
params.name = opts.name;
|
|
53
|
+
if (opts.status)
|
|
54
|
+
params.status = opts.status;
|
|
55
|
+
if (opts.category)
|
|
56
|
+
params.category = opts.category;
|
|
57
|
+
if (opts.target)
|
|
58
|
+
params.target = opts.target;
|
|
59
|
+
const res = await client.get(TEMPLATES_ENDPOINT, { params, headers: { 'x-instance-id': opts.instanceId } });
|
|
60
|
+
return ok(res.data);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return fail(toCliError(e));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function runEmailTemplateGet(opts, deps = {}) {
|
|
67
|
+
try {
|
|
68
|
+
if (!opts.instanceId)
|
|
69
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template get');
|
|
70
|
+
if (!opts.id)
|
|
71
|
+
throw createCliError('VALIDATION', '--id is required for email-template get');
|
|
72
|
+
const client = clientFor(opts, deps);
|
|
73
|
+
const res = await client.get(`${TEMPLATES_ENDPOINT}/${encodeURIComponent(opts.id)}`, {
|
|
74
|
+
headers: { 'x-instance-id': opts.instanceId },
|
|
75
|
+
});
|
|
76
|
+
return ok(res.data);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return fail(toCliError(e));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function runEmailTemplateScaffold(opts, _deps = {}) {
|
|
83
|
+
return emitScaffold(EMAIL_SCAFFOLD_HTML, opts.out);
|
|
84
|
+
}
|
|
85
|
+
export function createEmailTemplateCommand() {
|
|
86
|
+
const cmd = new Command('email-template').description('create/list/get HTML email templates for the emails app');
|
|
87
|
+
withInstanceFlag(withAuthFlags(cmd.command('create')))
|
|
88
|
+
.requiredOption('--name <name>', 'Template name')
|
|
89
|
+
.option('--body-file <path>', 'Path to an HTML file (inline CSS) used as the template body')
|
|
90
|
+
.option('--subject <text>', 'Email subject (supports {{tokens}})')
|
|
91
|
+
.option('--tags <json>', 'Tags object as inline JSON')
|
|
92
|
+
.option('--metadata <json>', 'Metadata object as inline JSON')
|
|
93
|
+
.addHelpText('after', TOKEN_HELP)
|
|
94
|
+
.action(async (options, command) => {
|
|
95
|
+
const globals = command.optsWithGlobals();
|
|
96
|
+
emitEnvelope(await runEmailTemplateCreate({ ...options, quiet: globals.quiet }), globals);
|
|
97
|
+
});
|
|
98
|
+
withInstanceFlag(withAuthFlags(cmd.command('list')))
|
|
99
|
+
.option('--name <name>', 'Case-insensitive substring filter, matched against name OR subject (not exact match)')
|
|
100
|
+
.option('--status <status>', 'Status filter')
|
|
101
|
+
.option('--category <category>', 'Category filter')
|
|
102
|
+
.option('--target <target>', 'Target filter')
|
|
103
|
+
.option('--limit <n>', 'Limit', (v) => parseInt(v, 10))
|
|
104
|
+
.option('--offset <n>', 'Offset', (v) => parseInt(v, 10))
|
|
105
|
+
.action(async (options, command) => {
|
|
106
|
+
const globals = command.optsWithGlobals();
|
|
107
|
+
emitEnvelope(await runEmailTemplateList({ ...options, quiet: globals.quiet }), globals);
|
|
108
|
+
});
|
|
109
|
+
withInstanceFlag(withAuthFlags(cmd.command('get')))
|
|
110
|
+
.requiredOption('--id <id>', 'Template ID')
|
|
111
|
+
.action(async (options, command) => {
|
|
112
|
+
const globals = command.optsWithGlobals();
|
|
113
|
+
emitEnvelope(await runEmailTemplateGet({ ...options, quiet: globals.quiet }), globals);
|
|
114
|
+
});
|
|
115
|
+
withAuthFlags(cmd.command('scaffold'))
|
|
116
|
+
.description('generate a starter HTML email template with tokens in context')
|
|
117
|
+
.option('--out <path>', 'Write the scaffold HTML to a file instead of returning it inline')
|
|
118
|
+
.addHelpText('after', TOKEN_HELP)
|
|
119
|
+
.action(async (options, command) => {
|
|
120
|
+
const globals = command.optsWithGlobals();
|
|
121
|
+
emitEnvelope(await runEmailTemplateScaffold({ ...options, quiet: globals.quiet }), globals);
|
|
122
|
+
});
|
|
123
|
+
return cmd;
|
|
124
|
+
}
|
|
@@ -6,6 +6,7 @@ import { emitEnvelope } from '../core/output.js';
|
|
|
6
6
|
import { uploadFile } from '../core/upload.js';
|
|
7
7
|
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
8
8
|
import { parseTags, parseJsonObject, validateAccessType, deepMerge, assertIntFlags } from '../schema/content.js';
|
|
9
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
10
|
export async function runFilesList(
|
|
10
11
|
// NOTE: no folderId — the Django files list has no folder filter (listing a
|
|
11
12
|
// folder's files is a folders-retrieve concern). tags filters via `tags__in`.
|
|
@@ -117,11 +118,7 @@ export async function runFilesUpdate(opts, deps = {}) {
|
|
|
117
118
|
}
|
|
118
119
|
export function createFilesCommand() {
|
|
119
120
|
const cmd = new Command('files').description('manage Pitcher content files');
|
|
120
|
-
|
|
121
|
-
.option('--api-key <apiKey>', 'API Key for authentication')
|
|
122
|
-
.option('--org <organization>', 'Organization subdomain', 'dev')
|
|
123
|
-
.option('--url <url>', 'Explicit base URL override');
|
|
124
|
-
withAuth(cmd.command('list'))
|
|
121
|
+
withAuthFlags(cmd.command('list'))
|
|
125
122
|
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
126
123
|
.option('--id-in <ids>', 'Comma-separated file IDs')
|
|
127
124
|
.option('--tags <tags>', 'Comma-separated tags (filters via tags__in)')
|
|
@@ -131,13 +128,13 @@ export function createFilesCommand() {
|
|
|
131
128
|
const globals = command.optsWithGlobals();
|
|
132
129
|
emitEnvelope(await runFilesList({ ...options, quiet: globals.quiet }), globals);
|
|
133
130
|
});
|
|
134
|
-
|
|
131
|
+
withAuthFlags(cmd.command('get'))
|
|
135
132
|
.argument('<id>', 'File ID')
|
|
136
133
|
.action(async (id, options, command) => {
|
|
137
134
|
const globals = command.optsWithGlobals();
|
|
138
135
|
emitEnvelope(await runFilesGet({ ...options, id, quiet: globals.quiet }), globals);
|
|
139
136
|
});
|
|
140
|
-
|
|
137
|
+
withAuthFlags(cmd.command('create'))
|
|
141
138
|
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
142
139
|
.option('--name <name>', 'File name')
|
|
143
140
|
.option('--folder-id <id>', 'Destination folder')
|
|
@@ -151,7 +148,7 @@ export function createFilesCommand() {
|
|
|
151
148
|
const globals = command.optsWithGlobals();
|
|
152
149
|
emitEnvelope(await runFilesCreate({ ...options, filePath: options.file, quiet: globals.quiet }), globals);
|
|
153
150
|
});
|
|
154
|
-
|
|
151
|
+
withAuthFlags(cmd.command('update'))
|
|
155
152
|
.argument('<id>', 'File ID')
|
|
156
153
|
.option('--instance-id <id>', 'Instance ID (required only with --file)')
|
|
157
154
|
.option('--name <name>', 'File name')
|
|
@@ -5,6 +5,7 @@ import { createCliError } from '../core/errors.js';
|
|
|
5
5
|
import { emitEnvelope } from '../core/output.js';
|
|
6
6
|
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
7
|
import { parseJsonArray, validateAccessType, assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
8
9
|
export async function runFoldersList(opts, deps = {}) {
|
|
9
10
|
try {
|
|
10
11
|
if (!opts.instanceId)
|
|
@@ -81,11 +82,7 @@ export async function runFoldersUpdate(opts, deps = {}) {
|
|
|
81
82
|
}
|
|
82
83
|
export function createFoldersCommand() {
|
|
83
84
|
const cmd = new Command('folders').description('manage Pitcher content folders');
|
|
84
|
-
|
|
85
|
-
.option('--api-key <apiKey>', 'API Key for authentication')
|
|
86
|
-
.option('--org <organization>', 'Organization subdomain', 'dev')
|
|
87
|
-
.option('--url <url>', 'Explicit base URL override');
|
|
88
|
-
withAuth(cmd.command('list'))
|
|
85
|
+
withAuthFlags(cmd.command('list'))
|
|
89
86
|
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
90
87
|
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
91
88
|
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
@@ -93,7 +90,7 @@ export function createFoldersCommand() {
|
|
|
93
90
|
const globals = command.optsWithGlobals();
|
|
94
91
|
emitEnvelope(await runFoldersList({ ...options, quiet: globals.quiet }), globals);
|
|
95
92
|
});
|
|
96
|
-
|
|
93
|
+
withAuthFlags(cmd.command('get'))
|
|
97
94
|
.argument('[id]', 'Folder ID (omit and use --root for the instance root)')
|
|
98
95
|
.option('--root', 'Fetch the instance root folder')
|
|
99
96
|
.option('--instance-id <id>', 'Instance ID (required with --root)')
|
|
@@ -101,7 +98,7 @@ export function createFoldersCommand() {
|
|
|
101
98
|
const globals = command.optsWithGlobals();
|
|
102
99
|
emitEnvelope(await runFoldersGet({ ...options, id, quiet: globals.quiet }), globals);
|
|
103
100
|
});
|
|
104
|
-
|
|
101
|
+
withAuthFlags(cmd.command('create'))
|
|
105
102
|
.requiredOption('--name <name>', 'Folder name')
|
|
106
103
|
.requiredOption('--parent-folder-id <id>', 'Parent folder ID')
|
|
107
104
|
.option('--access-type <type>', 'public | restricted | personal')
|
|
@@ -110,7 +107,7 @@ export function createFoldersCommand() {
|
|
|
110
107
|
const globals = command.optsWithGlobals();
|
|
111
108
|
emitEnvelope(await runFoldersCreate({ ...options, quiet: globals.quiet }), globals);
|
|
112
109
|
});
|
|
113
|
-
|
|
110
|
+
withAuthFlags(cmd.command('update'))
|
|
114
111
|
.argument('<id>', 'Folder ID')
|
|
115
112
|
.option('--name <name>', 'Folder name')
|
|
116
113
|
.option('--access-type <type>', 'public | restricted | personal')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface InstancesDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runInstancesList(opts: CommonOpts & {
|
|
9
|
+
region?: string;
|
|
10
|
+
storageRegion?: string;
|
|
11
|
+
environment?: string;
|
|
12
|
+
page?: number;
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
}, deps?: InstancesDeps): Promise<Envelope<{
|
|
15
|
+
results: unknown[];
|
|
16
|
+
pagination: Record<string, unknown>;
|
|
17
|
+
}>>;
|
|
18
|
+
export declare function runInstancesInfo(opts: CommonOpts & {
|
|
19
|
+
id?: string;
|
|
20
|
+
}, deps?: InstancesDeps): Promise<Envelope<unknown>>;
|
|
21
|
+
export declare function createInstancesCommand(): Command;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
export async function runInstancesList(opts, deps = {}) {
|
|
10
|
+
try {
|
|
11
|
+
assertIntFlags(opts);
|
|
12
|
+
const client = clientFor(opts, deps);
|
|
13
|
+
const params = { ...listPageParams(opts) };
|
|
14
|
+
if (opts.region)
|
|
15
|
+
params.region = opts.region;
|
|
16
|
+
if (opts.storageRegion)
|
|
17
|
+
params.storage_region = opts.storageRegion;
|
|
18
|
+
if (opts.environment)
|
|
19
|
+
params.environment = opts.environment;
|
|
20
|
+
const res = await client.get('/api/v1/instances/', { params });
|
|
21
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
return fail(toCliError(e));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function runInstancesInfo(opts, deps = {}) {
|
|
28
|
+
try {
|
|
29
|
+
if (!opts.id)
|
|
30
|
+
throw createCliError('VALIDATION', 'instance id is required');
|
|
31
|
+
const client = clientFor(opts, deps);
|
|
32
|
+
const res = await client.get(`/api/v1/instances/${encodeURIComponent(opts.id)}`);
|
|
33
|
+
return ok(res.data);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
return fail(toCliError(e));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function createInstancesCommand() {
|
|
40
|
+
const cmd = new Command('instances').description('discover Pitcher instances (resolve names to instance IDs)');
|
|
41
|
+
withAuthFlags(cmd.command('list'))
|
|
42
|
+
.option('--region <r>', 'Filter by region')
|
|
43
|
+
.option('--storage-region <r>', 'Filter by storage region')
|
|
44
|
+
.option('--environment <e>', 'Filter by environment')
|
|
45
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
46
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
47
|
+
.action(async (options, command) => {
|
|
48
|
+
const globals = command.optsWithGlobals();
|
|
49
|
+
emitEnvelope(await runInstancesList({ ...options, quiet: globals.quiet }), globals);
|
|
50
|
+
});
|
|
51
|
+
withAuthFlags(cmd.command('info'))
|
|
52
|
+
.argument('<id>', 'Instance ID')
|
|
53
|
+
.action(async (id, options, command) => {
|
|
54
|
+
const globals = command.optsWithGlobals();
|
|
55
|
+
emitEnvelope(await runInstancesInfo({ ...options, id, quiet: globals.quiet }), globals);
|
|
56
|
+
});
|
|
57
|
+
return cmd;
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface OrgDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runOrgGet(opts: CommonOpts, deps?: OrgDeps): Promise<Envelope<unknown>>;
|
|
9
|
+
export declare function createOrgCommand(): Command;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { emitEnvelope } from '../core/output.js';
|
|
5
|
+
import { withAuthFlags } from './withAuth.js';
|
|
6
|
+
export async function runOrgGet(opts, deps = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const client = clientFor(opts, deps);
|
|
9
|
+
// Returns raw settings/metadata (no server allow-list) — may include
|
|
10
|
+
// integration credentials; treat JSON output as sensitive.
|
|
11
|
+
const res = await client.get('/api/v1/organization/');
|
|
12
|
+
return ok(res.data);
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return fail(toCliError(e));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function createOrgCommand() {
|
|
19
|
+
const cmd = new Command('org').description('read the organization this API key belongs to');
|
|
20
|
+
withAuthFlags(cmd.command('get'))
|
|
21
|
+
.action(async (options, command) => {
|
|
22
|
+
const globals = command.optsWithGlobals();
|
|
23
|
+
emitEnvelope(await runOrgGet({ ...options, quiet: globals.quiet }), globals);
|
|
24
|
+
});
|
|
25
|
+
return cmd;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Envelope } from '../core/envelope.js';
|
|
2
|
+
export declare function emitScaffold(content: string, out?: string): Envelope<unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Starter HTML email (inline CSS, table layout) for `p email-template scaffold`.
|
|
5
|
+
* Contains the common {{recipient.*}} tokens in context, each documented with
|
|
6
|
+
* an HTML comment. The emails app resolves tokens at send time; unknown
|
|
7
|
+
* tokens render blank (no server-side validation).
|
|
8
|
+
*/
|
|
9
|
+
export declare const EMAIL_SCAFFOLD_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Sample Email Template</title>\n </head>\n <body style=\"margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,Helvetica,sans-serif;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#f4f4f4;padding:24px 0;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff;border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"background-color:#29335C;padding:24px;text-align:center;\">\n <span style=\"color:#ffffff;font-size:20px;font-weight:bold;\">Pitcher</span>\n </td>\n </tr>\n <tr>\n <td style=\"padding:32px 24px;color:#212121;font-size:15px;line-height:1.5;\">\n <!-- token: recipient.firstName -> the recipient's first name -->\n <!-- token: recipient.lastName -> the recipient's last name -->\n <p style=\"margin:0 0 16px 0;\">Dear {{recipient.firstName}} {{recipient.lastName}},</p>\n <p style=\"margin:0 0 16px 0;\">\n Thanks for your time today. As promised, here is the material we discussed.\n Reach out any time if you have questions.\n </p>\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:24px 0;\">\n <tr>\n <td style=\"background-color:#29335C;border-radius:4px;\">\n <a href=\"https://example.com\" style=\"display:inline-block;padding:12px 24px;color:#ffffff;text-decoration:none;font-size:14px;font-weight:bold;\">\n View content\n </a>\n </td>\n </tr>\n </table>\n <!-- token: recipient.email -> the recipient's email address, e.g. for a footer mailto: link -->\n <p style=\"margin:0;color:#757575;font-size:12px;\">\n Sent to {{recipient.email}}. If you'd rather not receive these, let your rep know.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>\n";
|
|
10
|
+
/**
|
|
11
|
+
* Starter DSR (Digital Sales Room) landing page HTML for `p dsr-landing scaffold`.
|
|
12
|
+
* Contains the required `data-pitcher-email-gate` marker, an editable-region
|
|
13
|
+
* example via `data-field-*` attributes, a themeable accent color via the
|
|
14
|
+
* `--p-primary` CSS variable, and the Handlebars tokens the platform resolves
|
|
15
|
+
* at render time, each documented with an HTML comment. No `<script>` or
|
|
16
|
+
* external `<link>` tags — the server strips both via DOMPurify.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DSR_SCAFFOLD_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Sample DSR Landing Page</title>\n </head>\n <body style=\"margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,Helvetica,sans-serif;color:#212121;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding:32px 0;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" width=\"640\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff;border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"background-color:var(--p-primary, #29335C);padding:28px;text-align:center;\">\n <!-- token: organization.name -> the sharing org's display name -->\n <span style=\"color:#ffffff;font-size:20px;font-weight:bold;\">{{organization.name}}</span>\n </td>\n </tr>\n <tr>\n <td style=\"padding:32px;\">\n <!-- token: recipient.firstName -> the recipient's first name -->\n <!-- token: customer.name -> the recipient's company name -->\n <h1 style=\"margin:0 0 8px 0;font-size:22px;\">Welcome, {{recipient.firstName}}</h1>\n <p style=\"margin:0 0 24px 0;color:#757575;font-size:14px;\">Prepared for {{customer.name}}</p>\n\n <!--\n Editable region: admins can edit this block's text in the DSR\n landing editor without touching markup. data-field-char-limit\n enforces a max length in that editor.\n -->\n <div\n data-field-id=\"welcome-message\"\n data-field-editable=\"true\"\n data-field-char-limit=\"120\"\n style=\"padding:16px;margin:0 0 24px 0;border-left:3px solid var(--p-primary, #29335C);background-color:#f7f8fb;font-size:15px;line-height:1.5;\"\n >\n Thanks for stopping by. Everything you need for this conversation is right here.\n </div>\n\n <!-- token: rep.name -> the sharing rep's display name -->\n <p style=\"margin:0 0 24px 0;font-size:14px;color:#757575;\">Shared by {{rep.name}}</p>\n\n <!--\n Required marker: the platform injects the email-gate form\n into this div at render time. It MUST be present verbatim\n (a literal data-pitcher-email-gate attribute) or the\n server rejects the template on create/update.\n -->\n <div data-pitcher-email-gate></div>\n </td>\n </tr>\n <tr>\n <td style=\"padding:16px 32px;border-top:1px solid #e0e2ed;text-align:center;color:#757575;font-size:12px;\">\n <!-- token: sharedLink.valid_until -> when this share expires, if the share sets an expiry -->\n This link is valid until {{sharedLink.valid_until}}.\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>\n";
|
|
19
|
+
export declare const ANNOUNCEMENT_SCAFFOLD_JSON: string;
|