@pitcher/cli-scripts 2.7.0 → 2.8.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 CHANGED
@@ -414,3 +414,25 @@ p metadata-template upsert --instance-id 01... --sections-file ./schema.json --a
414
414
  ```
415
415
 
416
416
  `upsert` merges the incoming sections by section `label` — a section whose label matches an existing one is replaced, other sections are preserved. It **cannot remove** a section or field (that's a UI operation). `options` for `select`/`multi-select` fields are `{ label, value }` objects (bare strings are rejected). `upsert` requires an instance- or org-admin key; a non-admin member key is rejected (400 → `VALIDATION`).
417
+
418
+ ## To run the `dsr` command:
419
+
420
+ Create and share a Digital Sales Room (a canvas share link) and get a link back in one command. Three subcommands: `create` (build + share, with optional landing-page binding), `bind-landing` (bind/unbind a landing template on an existing share), and `scaffold` (print a starter `{source,security,landing}` spec for `--config-file`).
421
+
422
+ ```bash
423
+ # simplest — email-gated link (server default auth_strategy=any_email)
424
+ p dsr create --instance-id 01... --canvas-id 01... --api-key myapikey
425
+ # open link
426
+ p dsr create --instance-id 01... --canvas-id 01... --auth-strategy none
427
+ # ShareBox + security + branded landing
428
+ p dsr create --instance-id 01... --sharebox-file-ids 01A,01B --account-id 01... \
429
+ --auth-strategy specified_domains --allowed-domains acme.com --valid-until 2026-09-01T00:00:00Z \
430
+ --landing-template-id 01... --landing-context '{"rep":"Sam"}'
431
+ # bind/unbind a landing template on an existing share
432
+ p dsr bind-landing --id 01... --landing-template-id 01...
433
+ p dsr bind-landing --id 01... --landing-template-id none
434
+ # scaffold a spec for --config-file
435
+ p dsr scaffold --out share.json
436
+ ```
437
+
438
+ Creating needs a share-capable key; **binding a landing page currently needs an editor key** (PIT-7525 will open it to reps for their own DSRs) — a denied bind returns the link with a warning. Discover landing template IDs via `p dsr-landing list`.
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { createMembershipsCommand } from './src/commands/memberships.js';
24
24
  import { createGroupsCommand } from './src/commands/groups.js';
25
25
  import { createRolesCommand } from './src/commands/roles.js';
26
26
  import { createMetadataTemplateCommand } from './src/commands/metadataTemplate.js';
27
+ import { createDsrCommand } from './src/commands/dsr.js';
27
28
  const __filename = fileURLToPath(import.meta.url);
28
29
  const __dirname = dirname(__filename);
29
30
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
@@ -53,6 +54,7 @@ program.addCommand(createMembershipsCommand());
53
54
  program.addCommand(createGroupsCommand());
54
55
  program.addCommand(createRolesCommand());
55
56
  program.addCommand(createMetadataTemplateCommand());
57
+ program.addCommand(createDsrCommand());
56
58
  program.addCommand(createOrgCommand());
57
59
  program.addCommand(createAnalyticsCommand());
58
60
  program.parseAsync(process.argv);
@@ -35,7 +35,7 @@ export function createAnalyticsCommand() {
35
35
  emitEnvelope(await runAnalyticsOrg({ ...options, quiet: globals.quiet }), globals);
36
36
  });
37
37
  withAuthFlags(cmd.command('instance'))
38
- .requiredOption('--instance-id <id>', 'Instance ID')
38
+ .option('--instance-id <id>', 'Instance ID (required)')
39
39
  .description('instance rollups (instance-admin or org-admin key required)')
40
40
  .action(async (options, command) => {
41
41
  const globals = command.optsWithGlobals();
@@ -50,7 +50,7 @@ export async function runCanvasesGet(opts, deps = {}) {
50
50
  export function createCanvasesCommand() {
51
51
  const cmd = new Command('canvases').description('read Pitcher canvases (presentations, sections, blocks)');
52
52
  withAuthFlags(cmd.command('list'))
53
- .requiredOption('--instance-id <id>', 'Instance ID')
53
+ .option('--instance-id <id>', 'Instance ID (required)')
54
54
  .option('--is-template', 'Only templates')
55
55
  .option('--is-section', 'Only sections')
56
56
  .option('--is-block', 'Only blocks (blocks are hidden unless this flag is set)')
@@ -0,0 +1,36 @@
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 DsrDeps {
6
+ client?: AxiosInstance;
7
+ }
8
+ export declare function runDsrScaffold(opts: CommonOpts & {
9
+ out?: string;
10
+ }, _deps?: DsrDeps): Promise<Envelope<unknown>>;
11
+ export declare function runDsrBindLanding(opts: CommonOpts & {
12
+ id?: string;
13
+ landingTemplateId?: string;
14
+ landingContext?: string;
15
+ instanceId?: string;
16
+ }, deps?: DsrDeps): Promise<Envelope<unknown>>;
17
+ export interface DsrCreateOpts extends CommonOpts {
18
+ instanceId?: string;
19
+ canvasId?: string;
20
+ shareboxFileIds?: string[];
21
+ canvasName?: string;
22
+ accountId?: string;
23
+ templateId?: string;
24
+ authStrategy?: string;
25
+ allowedEmails?: string[];
26
+ allowedDomains?: string[];
27
+ validUntil?: string;
28
+ allowForward?: boolean;
29
+ allowDownload?: boolean;
30
+ tags?: string[];
31
+ landingTemplateId?: string;
32
+ landingContext?: string;
33
+ configFile?: string;
34
+ }
35
+ export declare function runDsrCreate(opts: DsrCreateOpts, deps?: DsrDeps): Promise<Envelope<unknown>>;
36
+ export declare function createDsrCommand(): Command;
@@ -0,0 +1,229 @@
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 { parseJsonObject, parseTags, isPlainObject } from '../schema/content.js';
7
+ import { readTextFile } from '../core/readInput.js';
8
+ import { withAuthFlags } from './withAuth.js';
9
+ import { emitScaffold, DSR_SHARE_SCAFFOLD_JSON } from './scaffolds.js';
10
+ const ENDPOINT = '/core/api/protected/shared-links';
11
+ export async function runDsrScaffold(opts, _deps = {}) {
12
+ return emitScaffold(DSR_SHARE_SCAFFOLD_JSON, opts.out);
13
+ }
14
+ // resolve --landing-template-id text into the PUT value: undefined = not given; '' / 'none' = unbind (null); else the id
15
+ function resolveTemplateId(raw) {
16
+ if (raw === undefined)
17
+ return undefined;
18
+ if (raw === '' || raw.toLowerCase() === 'none')
19
+ return null;
20
+ return raw;
21
+ }
22
+ async function bindLandingRequest(client, id, body, instanceId) {
23
+ const url = `${ENDPOINT}/${encodeURIComponent(id)}/landing-page`;
24
+ const res = await client.put(url, body, instanceId ? { params: { instance_id: instanceId } } : undefined);
25
+ return res.data;
26
+ }
27
+ export async function runDsrBindLanding(opts, deps = {}) {
28
+ try {
29
+ if (!opts.id)
30
+ throw createCliError('VALIDATION', '--id is required for dsr bind-landing');
31
+ const templateId = resolveTemplateId(opts.landingTemplateId);
32
+ if (templateId === undefined)
33
+ throw createCliError('VALIDATION', '--landing-template-id is required (use "none" to unbind)');
34
+ const body = { landing_page_template_id: templateId };
35
+ if (opts.landingContext !== undefined)
36
+ body.landing_page_context = parseJsonObject(opts.landingContext, 'landing-context');
37
+ const client = clientFor(opts, deps);
38
+ const data = await bindLandingRequest(client, opts.id, body, opts.instanceId);
39
+ return ok(data);
40
+ }
41
+ catch (e) {
42
+ return fail(toCliError(e));
43
+ }
44
+ }
45
+ const AUTH_STRATEGIES = [
46
+ 'none', 'any_email', 'specified_emails', 'specified_domains', 'verified_emails', 'verified_domains',
47
+ ];
48
+ const SPEC_FLAG_KEYS = [
49
+ 'canvasId', 'shareboxFileIds', 'canvasName', 'accountId', 'templateId', 'authStrategy', 'allowedEmails',
50
+ 'allowedDomains', 'validUntil', 'allowForward', 'allowDownload', 'tags', 'landingTemplateId', 'landingContext',
51
+ ];
52
+ // config-file blocks must be JSON objects — a bare cast would silently spread a string/array into the request body
53
+ function requirePlainObjectField(v, field) {
54
+ if (v === undefined)
55
+ return {};
56
+ if (!isPlainObject(v))
57
+ throw createCliError('VALIDATION', `config "${field}" must be a JSON object`);
58
+ return v;
59
+ }
60
+ function assembleSpec(opts) {
61
+ if (opts.configFile !== undefined) {
62
+ if (SPEC_FLAG_KEYS.some((k) => opts[k] !== undefined))
63
+ throw createCliError('VALIDATION', 'pass individual flags or --config-file, not both');
64
+ const cfg = parseJsonObject(readTextFile(opts.configFile), 'config');
65
+ const source = requirePlainObjectField(cfg.source, 'source');
66
+ const security = requirePlainObjectField(cfg.security, 'security');
67
+ // JSON null is a valid "no landing" opt-out, same as omitting the block
68
+ const landing = cfg.landing == null ? null : requirePlainObjectField(cfg.landing, 'landing');
69
+ const body = { ...source, ...security };
70
+ if (opts.instanceId !== undefined)
71
+ body.instance_id = opts.instanceId;
72
+ return { body, landing };
73
+ }
74
+ const body = {};
75
+ if (opts.instanceId !== undefined)
76
+ body.instance_id = opts.instanceId;
77
+ if (opts.canvasId !== undefined)
78
+ body.canvas_id = opts.canvasId;
79
+ if (opts.shareboxFileIds !== undefined)
80
+ body.sharebox_items = opts.shareboxFileIds.map((file_id) => ({ file_id }));
81
+ if (opts.canvasName !== undefined)
82
+ body.canvas_name = opts.canvasName;
83
+ if (opts.accountId !== undefined)
84
+ body.account_id = opts.accountId;
85
+ if (opts.templateId !== undefined)
86
+ body.template_id = opts.templateId;
87
+ if (opts.authStrategy !== undefined)
88
+ body.auth_strategy = opts.authStrategy;
89
+ if (opts.allowedEmails !== undefined)
90
+ body.allowed_emails = opts.allowedEmails;
91
+ if (opts.allowedDomains !== undefined)
92
+ body.allowed_domains = opts.allowedDomains;
93
+ if (opts.validUntil !== undefined)
94
+ body.valid_until = opts.validUntil;
95
+ if (opts.allowForward !== undefined)
96
+ body.allow_forward = opts.allowForward;
97
+ if (opts.allowDownload !== undefined)
98
+ body.allow_download = opts.allowDownload;
99
+ if (opts.tags !== undefined)
100
+ body.tags = opts.tags;
101
+ return { body, landing: null };
102
+ }
103
+ function assertCreateBody(body, instanceId) {
104
+ const hasCanvas = body.canvas_id !== undefined && body.canvas_id !== '';
105
+ const items = body.sharebox_items;
106
+ const hasSharebox = Array.isArray(items) && items.length > 0;
107
+ if (hasCanvas === hasSharebox)
108
+ throw createCliError('VALIDATION', 'pass exactly one source: --canvas-id or --sharebox-file-ids');
109
+ if (hasSharebox && !instanceId && body.instance_id === undefined)
110
+ throw createCliError('VALIDATION', '--instance-id is required for --sharebox-file-ids');
111
+ const strat = body.auth_strategy;
112
+ if (strat !== undefined) {
113
+ if (!AUTH_STRATEGIES.includes(strat))
114
+ throw createCliError('VALIDATION', `--auth-strategy must be one of: ${AUTH_STRATEGIES.join(', ')}`);
115
+ const emails = body.allowed_emails;
116
+ const domains = body.allowed_domains;
117
+ if ((strat === 'specified_emails' || strat === 'verified_emails') && !(emails && emails.length))
118
+ throw createCliError('VALIDATION', `--allowed-emails is required for --auth-strategy ${strat}`);
119
+ if ((strat === 'specified_domains' || strat === 'verified_domains') && !(domains && domains.length))
120
+ throw createCliError('VALIDATION', `--allowed-domains is required for --auth-strategy ${strat}`);
121
+ }
122
+ }
123
+ // binding a landing page on create needs a real template id — unlike bind-landing, there is nothing to unbind on a
124
+ // brand-new share, so 'none'/''/null/missing (and a non-string config value) are all rejected as VALIDATION
125
+ function requireCreateTemplateId(tid) {
126
+ const resolved = typeof tid === 'string' ? resolveTemplateId(tid) : tid;
127
+ if (typeof resolved !== 'string')
128
+ throw createCliError('VALIDATION', 'a landing template id is required to bind a landing page on create (use `dsr bind-landing` to unbind an existing share)');
129
+ return resolved;
130
+ }
131
+ // derive the landing PUT body from flags or from the config-file landing block; null if none requested
132
+ function resolveLandingBody(opts, cfgLanding) {
133
+ if (opts.configFile !== undefined) {
134
+ if (!cfgLanding)
135
+ return null;
136
+ const tid = cfgLanding.landing_page_template_id;
137
+ const ctx = cfgLanding.landing_page_context;
138
+ // a blank/null template id with no (or empty) context = the untouched `dsr scaffold` default → no landing bind
139
+ const templateBlank = tid === undefined || tid === null || tid === '' || (typeof tid === 'string' && tid.toLowerCase() === 'none');
140
+ const contextEmpty = ctx === undefined || (isPlainObject(ctx) && Object.keys(ctx).length === 0);
141
+ if (templateBlank && contextEmpty)
142
+ return null;
143
+ const b = { landing_page_template_id: requireCreateTemplateId(tid) };
144
+ if (ctx !== undefined) {
145
+ if (!isPlainObject(ctx))
146
+ throw createCliError('VALIDATION', 'landing.landing_page_context must be a JSON object');
147
+ if (Object.keys(ctx).length > 0)
148
+ b.landing_page_context = ctx;
149
+ }
150
+ return b;
151
+ }
152
+ if (opts.landingTemplateId === undefined && opts.landingContext === undefined)
153
+ return null;
154
+ const b = { landing_page_template_id: requireCreateTemplateId(opts.landingTemplateId) };
155
+ if (opts.landingContext !== undefined)
156
+ b.landing_page_context = parseJsonObject(opts.landingContext, 'landing-context');
157
+ return b;
158
+ }
159
+ export async function runDsrCreate(opts, deps = {}) {
160
+ try {
161
+ const { body, landing: cfgLanding } = assembleSpec(opts);
162
+ assertCreateBody(body, opts.instanceId);
163
+ const landingBody = resolveLandingBody(opts, cfgLanding); // parse/validate context BEFORE the network call
164
+ // bind against the same instance the share was created in — a config-file source.instance_id must reach the PUT too
165
+ const effectiveInstanceId = body.instance_id ?? opts.instanceId;
166
+ const client = clientFor(opts, deps);
167
+ const res = await client.post(ENDPOINT, body);
168
+ const share = res.data;
169
+ if (!landingBody)
170
+ return ok(share);
171
+ const shareId = share?.id;
172
+ if (!shareId)
173
+ return ok({ ...share, landing: null }, ['landing not attached: no share id in create response']);
174
+ try {
175
+ const landing = await bindLandingRequest(client, shareId, landingBody, effectiveInstanceId);
176
+ return ok({ ...share, landing });
177
+ }
178
+ catch (e) {
179
+ return ok({ ...share, landing: null }, [`landing not attached: ${toCliError(e).message}`]);
180
+ }
181
+ }
182
+ catch (e) {
183
+ return fail(toCliError(e));
184
+ }
185
+ }
186
+ export function createDsrCommand() {
187
+ const cmd = new Command('dsr').description('create and share a Digital Sales Room (a canvas share link)');
188
+ withAuthFlags(cmd.command('scaffold'))
189
+ .description('print a {source,security,landing} share spec to fill in and pass to `dsr create --config-file`')
190
+ .option('--out <path>', 'Write the scaffold to a file instead of stdout')
191
+ .action(async (options, command) => {
192
+ const globals = command.optsWithGlobals();
193
+ emitEnvelope(await runDsrScaffold({ ...options, quiet: globals.quiet }), globals);
194
+ });
195
+ withAuthFlags(cmd.command('create'))
196
+ .description('create + share a DSR (canvas share link) and return the link')
197
+ .option('--instance-id <id>', 'Instance ID (required for --sharebox-file-ids)')
198
+ .option('--canvas-id <id>', 'Existing canvas to share (one source required)')
199
+ .option('--sharebox-file-ids <csv>', 'Build a canvas from these file IDs (one source required)', parseTags)
200
+ .option('--canvas-name <name>', 'Name for the ShareBox-built canvas')
201
+ .option('--account-id <id>', 'Account for the ShareBox-built canvas')
202
+ .option('--template-id <id>', 'Canvas template for the ShareBox-built canvas')
203
+ .option('--auth-strategy <v>', 'none|any_email|specified_emails|specified_domains|verified_emails|verified_domains')
204
+ .option('--allowed-emails <csv>', 'Allowed emails (required for specified/verified_emails)', parseTags)
205
+ .option('--allowed-domains <csv>', 'Allowed domains (required for specified/verified_domains)', parseTags)
206
+ .option('--valid-until <iso>', 'Expiry (ISO datetime)')
207
+ .option('--allow-forward', 'Allow recipients to forward')
208
+ .option('--no-allow-forward', 'Disallow forwarding')
209
+ .option('--allow-download', 'Allow download')
210
+ .option('--tags <csv>', 'Tags', parseTags)
211
+ .option('--landing-template-id <id>', 'Landing template to bind (optional)')
212
+ .option('--landing-context <json>', 'Landing personalization merge fields (JSON object)')
213
+ .option('--config-file <path>', 'Full {source,security,landing} spec JSON (mutually exclusive with the flags above)')
214
+ .action(async (options, command) => {
215
+ const globals = command.optsWithGlobals();
216
+ emitEnvelope(await runDsrCreate({ ...options, quiet: globals.quiet }), globals);
217
+ });
218
+ withAuthFlags(cmd.command('bind-landing'))
219
+ .description("bind (or unbind with 'none') a landing-page template on an existing share")
220
+ .option('--id <id>', 'Shared link ID (required)')
221
+ .option('--landing-template-id <id>', "Landing template ID, or 'none' to unbind (required)")
222
+ .option('--landing-context <json>', 'Personalization merge fields (JSON object)')
223
+ .option('--instance-id <id>', 'Instance ID (optional)')
224
+ .action(async (options, command) => {
225
+ const globals = command.optsWithGlobals();
226
+ emitEnvelope(await runDsrBindLanding({ ...options, quiet: globals.quiet }), globals);
227
+ });
228
+ return cmd;
229
+ }
@@ -116,8 +116,8 @@ export async function runDsrLandingScaffold(opts, _deps = {}) {
116
116
  export function createDsrLandingCommand() {
117
117
  const cmd = new Command('dsr-landing').description('create/list DSR (Digital Sales Room) landing-page templates, and set one as default');
118
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})`)
119
+ .option('--name <name>', 'Template name (required)')
120
+ .option('--html-file <path>', `Path to the landing HTML (required; must contain ${REQUIRED_MARKER})`)
121
121
  .option('--css-file <path>', 'Path to a CSS file for the template')
122
122
  .option('--merge-schema <json>', 'Merge schema array as inline JSON')
123
123
  .option('--language <lang>', 'Language code (max 10 chars)')
@@ -129,7 +129,7 @@ export function createDsrLandingCommand() {
129
129
  emitEnvelope(await runDsrLandingCreate({ ...options, quiet: globals.quiet }), globals);
130
130
  });
131
131
  withInstanceFlag(withAuthFlags(cmd.command('set-default')))
132
- .requiredOption('--id <id>', 'Template ID')
132
+ .option('--id <id>', 'Template ID (required)')
133
133
  .action(async (options, command) => {
134
134
  const globals = command.optsWithGlobals();
135
135
  emitEnvelope(await runDsrLandingSetDefault({ ...options, quiet: globals.quiet }), globals);
@@ -85,7 +85,7 @@ export async function runEmailTemplateScaffold(opts, _deps = {}) {
85
85
  export function createEmailTemplateCommand() {
86
86
  const cmd = new Command('email-template').description('create/list/get HTML email templates for the emails app');
87
87
  withInstanceFlag(withAuthFlags(cmd.command('create')))
88
- .requiredOption('--name <name>', 'Template name')
88
+ .option('--name <name>', 'Template name (required)')
89
89
  .option('--body-file <path>', 'Path to an HTML file (inline CSS) used as the template body')
90
90
  .option('--subject <text>', 'Email subject (supports {{tokens}})')
91
91
  .option('--tags <json>', 'Tags object as inline JSON')
@@ -107,7 +107,7 @@ export function createEmailTemplateCommand() {
107
107
  emitEnvelope(await runEmailTemplateList({ ...options, quiet: globals.quiet }), globals);
108
108
  });
109
109
  withInstanceFlag(withAuthFlags(cmd.command('get')))
110
- .requiredOption('--id <id>', 'Template ID')
110
+ .option('--id <id>', 'Template ID (required)')
111
111
  .action(async (options, command) => {
112
112
  const globals = command.optsWithGlobals();
113
113
  emitEnvelope(await runEmailTemplateGet({ ...options, quiet: globals.quiet }), globals);
@@ -119,7 +119,7 @@ export async function runFilesUpdate(opts, deps = {}) {
119
119
  export function createFilesCommand() {
120
120
  const cmd = new Command('files').description('manage Pitcher content files');
121
121
  withAuthFlags(cmd.command('list'))
122
- .requiredOption('--instance-id <id>', 'Instance ID')
122
+ .option('--instance-id <id>', 'Instance ID (required)')
123
123
  .option('--id-in <ids>', 'Comma-separated file IDs')
124
124
  .option('--tags <tags>', 'Comma-separated tags (filters via tags__in)')
125
125
  .option('--page <n>', 'Page number', (v) => parseInt(v, 10))
@@ -135,7 +135,7 @@ export function createFilesCommand() {
135
135
  emitEnvelope(await runFilesGet({ ...options, id, quiet: globals.quiet }), globals);
136
136
  });
137
137
  withAuthFlags(cmd.command('create'))
138
- .requiredOption('--instance-id <id>', 'Instance ID')
138
+ .option('--instance-id <id>', 'Instance ID (required)')
139
139
  .option('--name <name>', 'File name')
140
140
  .option('--folder-id <id>', 'Destination folder')
141
141
  .option('--file <path>', 'Local file to upload')
@@ -83,7 +83,7 @@ export async function runFoldersUpdate(opts, deps = {}) {
83
83
  export function createFoldersCommand() {
84
84
  const cmd = new Command('folders').description('manage Pitcher content folders');
85
85
  withAuthFlags(cmd.command('list'))
86
- .requiredOption('--instance-id <id>', 'Instance ID')
86
+ .option('--instance-id <id>', 'Instance ID (required)')
87
87
  .option('--page <n>', 'Page number', (v) => parseInt(v, 10))
88
88
  .option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
89
89
  .action(async (options, command) => {
@@ -99,8 +99,8 @@ export function createFoldersCommand() {
99
99
  emitEnvelope(await runFoldersGet({ ...options, id, quiet: globals.quiet }), globals);
100
100
  });
101
101
  withAuthFlags(cmd.command('create'))
102
- .requiredOption('--name <name>', 'Folder name')
103
- .requiredOption('--parent-folder-id <id>', 'Parent folder ID')
102
+ .option('--name <name>', 'Folder name (required)')
103
+ .option('--parent-folder-id <id>', 'Parent folder ID (required)')
104
104
  .option('--access-type <type>', 'public | restricted | personal')
105
105
  .option('--rules <json>', 'Smart-folder rules JSON array')
106
106
  .action(async (options, command) => {
@@ -18,3 +18,4 @@ export declare const EMAIL_SCAFFOLD_HTML = "<!doctype html>\n<html lang=\"en\">\
18
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
19
  export declare const ANNOUNCEMENT_SCAFFOLD_JSON: string;
20
20
  export declare const METADATA_TEMPLATE_SCAFFOLD_JSON: string;
21
+ export declare const DSR_SHARE_SCAFFOLD_JSON: string;
@@ -269,3 +269,27 @@ const METADATA_TEMPLATE_SCAFFOLD_DATA = [
269
269
  },
270
270
  ];
271
271
  export const METADATA_TEMPLATE_SCAFFOLD_JSON = JSON.stringify(METADATA_TEMPLATE_SCAFFOLD_DATA, null, 2);
272
+ const DSR_SHARE_SCAFFOLD_DATA = {
273
+ source: {
274
+ // provide ONE of the following:
275
+ canvas_id: '01EXAMPLECANVASID000000000',
276
+ // sharebox_items: [{ file_id: '01EXAMPLEFILEID0000000000', page_index: 0 }],
277
+ // canvas_name: 'Acme QBR',
278
+ // account_id: '01EXAMPLEACCOUNTID00000000',
279
+ // template_id: '01EXAMPLECANVASTEMPLATE0000',
280
+ },
281
+ security: {
282
+ auth_strategy: 'any_email', // none|any_email|specified_emails|specified_domains|verified_emails|verified_domains
283
+ allowed_emails: [],
284
+ allowed_domains: [],
285
+ valid_until: null,
286
+ allow_forward: true,
287
+ allow_download: false,
288
+ tags: [],
289
+ },
290
+ landing: {
291
+ landing_page_template_id: null, // discover ids via `p dsr-landing list --instance-id <id>`
292
+ landing_page_context: {},
293
+ },
294
+ };
295
+ export const DSR_SHARE_SCAFFOLD_JSON = JSON.stringify(DSR_SHARE_SCAFFOLD_DATA, null, 2);
@@ -25,8 +25,8 @@ export async function runSearch(opts, deps = {}) {
25
25
  }
26
26
  export function createSearchCommand() {
27
27
  return withAuthFlags(new Command('search').description('search content across an instance (files, canvases, users, apps)'))
28
- .requiredOption('--instance-id <id>', 'Instance ID')
29
- .requiredOption('--query <text>', 'Search query')
28
+ .option('--instance-id <id>', 'Instance ID (required)')
29
+ .option('--query <text>', 'Search query (required)')
30
30
  .option('--include-expired', 'Include expired content')
31
31
  .action(async (options, command) => {
32
32
  const globals = command.optsWithGlobals();
@@ -37,8 +37,8 @@ export async function runTagsList(opts, deps = {}) {
37
37
  export function createTagsCommand() {
38
38
  const cmd = new Command('tags').description('list tags in an instance (search term of >=3 chars required)');
39
39
  withAuthFlags(cmd.command('list'))
40
- .requiredOption('--instance-id <id>', 'Instance ID')
41
- .requiredOption('--search <text>', 'Search term (server requires at least 3 characters)')
40
+ .option('--instance-id <id>', 'Instance ID (required)')
41
+ .option('--search <text>', 'Search term (required; at least 3 characters)')
42
42
  .option('--type <type>', 'files | canvases')
43
43
  .option('--canvas-type <type>', 'Canvas type filter')
44
44
  .option('--include-hidden', 'Include hidden tags')
@@ -1,3 +1,8 @@
1
1
  import { Command } from 'commander';
2
- /** Attach the shared --instance-id flag to a (sub)command. */
2
+ /**
3
+ * Attach the shared --instance-id flag to a (sub)command.
4
+ * Uses .option (not .requiredOption) so a missing flag flows through the
5
+ * command's in-run VALIDATION guard and yields the JSON envelope (exit 3),
6
+ * instead of commander aborting with plain text on stderr (exit 1).
7
+ */
3
8
  export declare function withInstanceFlag(c: Command): Command;
@@ -1,4 +1,9 @@
1
- /** Attach the shared --instance-id flag to a (sub)command. */
1
+ /**
2
+ * Attach the shared --instance-id flag to a (sub)command.
3
+ * Uses .option (not .requiredOption) so a missing flag flows through the
4
+ * command's in-run VALIDATION guard and yields the JSON envelope (exit 3),
5
+ * instead of commander aborting with plain text on stderr (exit 1).
6
+ */
2
7
  export function withInstanceFlag(c) {
3
- return c.requiredOption('--instance-id <id>', 'Instance ID');
8
+ return c.option('--instance-id <id>', 'Instance ID (required)');
4
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pitcher/cli-scripts",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "The Pitcher CLI for various tasks in its ecosystem.",
5
5
  "author": "Pitcher AG",
6
6
  "license": "MIT",