@indigoai-us/hq-cli 5.25.1 → 5.26.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.
@@ -0,0 +1,41 @@
1
+ import { Command } from "commander";
2
+ export interface DmRecipient {
3
+ toEmail?: string;
4
+ toPersonUid?: string;
5
+ }
6
+ /**
7
+ * Classify a recipient arg as an email or a personUid. Mirrors the
8
+ * email/prs_ heuristic used by `hq members`. Returns null for neither.
9
+ */
10
+ export declare function detectRecipient(recipient: string): DmRecipient | null;
11
+ /**
12
+ * Parse a relative duration like `30s`, `10m`, `2h`, `1d` into milliseconds.
13
+ * Returns null on anything that doesn't match. Pure → unit-testable.
14
+ */
15
+ export declare function parseDuration(input: string): number | null;
16
+ export interface DmSendBody {
17
+ toEmail?: string;
18
+ toPersonUid?: string;
19
+ body: string;
20
+ prompt?: string;
21
+ details?: string;
22
+ deliverAt?: string;
23
+ }
24
+ /**
25
+ * Build the POST /v1/notify/dm request body from CLI inputs. Pure (no I/O,
26
+ * no clock) so the option-resolution logic is unit-testable; the caller
27
+ * supplies `now` for the `--in` relative-delay computation.
28
+ *
29
+ * Throws Error with a user-facing message on invalid input.
30
+ */
31
+ export declare function buildDmBody(args: {
32
+ recipient: string;
33
+ message: string;
34
+ prompt?: string;
35
+ details?: string;
36
+ at?: string;
37
+ inDelay?: string;
38
+ now: number;
39
+ }): DmSendBody;
40
+ export declare function registerDmCommand(program: Command): void;
41
+ //# sourceMappingURL=dm.d.ts.map
@@ -0,0 +1,148 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="69d48194-d5ae-508e-b614-68f2066db6ee")}catch(e){}}();
3
+ import chalk from "chalk";
4
+ import { readFileSync } from "node:fs";
5
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
6
+ import { vaultApiFetch } from "../utils/vault-api.js";
7
+ const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
8
+ const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
9
+ /**
10
+ * Classify a recipient arg as an email or a personUid. Mirrors the
11
+ * email/prs_ heuristic used by `hq members`. Returns null for neither.
12
+ */
13
+ export function detectRecipient(recipient) {
14
+ const r = recipient.trim();
15
+ if (EMAIL_PATTERN.test(r))
16
+ return { toEmail: r.toLowerCase() };
17
+ if (PERSON_UID_PATTERN.test(r))
18
+ return { toPersonUid: r };
19
+ return null;
20
+ }
21
+ /**
22
+ * Parse a relative duration like `30s`, `10m`, `2h`, `1d` into milliseconds.
23
+ * Returns null on anything that doesn't match. Pure → unit-testable.
24
+ */
25
+ export function parseDuration(input) {
26
+ const m = /^(\d+)\s*(s|m|h|d)$/.exec(input.trim());
27
+ if (!m)
28
+ return null;
29
+ const n = parseInt(m[1], 10);
30
+ const mult = {
31
+ s: 1000,
32
+ m: 60_000,
33
+ h: 3_600_000,
34
+ d: 86_400_000,
35
+ };
36
+ return n * mult[m[2]];
37
+ }
38
+ /**
39
+ * Build the POST /v1/notify/dm request body from CLI inputs. Pure (no I/O,
40
+ * no clock) so the option-resolution logic is unit-testable; the caller
41
+ * supplies `now` for the `--in` relative-delay computation.
42
+ *
43
+ * Throws Error with a user-facing message on invalid input.
44
+ */
45
+ export function buildDmBody(args) {
46
+ const rcpt = detectRecipient(args.recipient);
47
+ if (!rcpt) {
48
+ throw new Error(`Invalid recipient '${args.recipient}': must be an email address or a personUid (prs_…).`);
49
+ }
50
+ const body = (args.message ?? "").trim();
51
+ if (!body) {
52
+ throw new Error("A message body is required: hq dm <recipient> <message>");
53
+ }
54
+ if (args.at && args.inDelay) {
55
+ throw new Error("Use only one of --at or --in, not both.");
56
+ }
57
+ let deliverAt;
58
+ if (args.at) {
59
+ const when = new Date(args.at);
60
+ if (isNaN(when.getTime())) {
61
+ throw new Error(`Invalid --at '${args.at}': must be an ISO8601 date.`);
62
+ }
63
+ deliverAt = when.toISOString();
64
+ }
65
+ else if (args.inDelay) {
66
+ const ms = parseDuration(args.inDelay);
67
+ if (ms === null) {
68
+ throw new Error(`Invalid --in '${args.inDelay}': use a relative delay like 30s, 10m, 2h, 1d.`);
69
+ }
70
+ deliverAt = new Date(args.now + ms).toISOString();
71
+ }
72
+ const prompt = args.prompt?.trim();
73
+ const details = args.details?.trim();
74
+ return {
75
+ ...rcpt,
76
+ body,
77
+ ...(prompt ? { prompt } : {}),
78
+ ...(details ? { details } : {}),
79
+ ...(deliverAt ? { deliverAt } : {}),
80
+ };
81
+ }
82
+ function friendlyDmError(status, code, fallback) {
83
+ if (status === 401)
84
+ return "Not authenticated — run `hq login` and try again.";
85
+ if (status === 404 || code === "RECIPIENT_NOT_FOUND") {
86
+ return "Recipient not found or not reachable — you can only DM someone you share an active company with.";
87
+ }
88
+ if (status >= 500)
89
+ return `Server error: ${fallback}`;
90
+ return fallback;
91
+ }
92
+ export function registerDmCommand(program) {
93
+ program
94
+ .command("dm <recipient> [message]")
95
+ .description("Send a direct message to a teammate (email or personUid). They receive it as an HQ Sync notification.")
96
+ .option("--prompt <text>", "Agent-context prompt the recipient can one-click copy into their agent")
97
+ .option("--prompt-file <path>", "Read the agent prompt from a file")
98
+ .option("--details <text>", "Longer detail shown in the recipient's DM detail window")
99
+ .option("--details-file <path>", "Read the details from a file")
100
+ .option("--at <iso>", "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time)")
101
+ .option("--in <duration>", "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d")
102
+ .action(async (recipient, message, opts) => {
103
+ try {
104
+ // Resolve prompt/details from inline text or a file.
105
+ let prompt = opts.prompt;
106
+ if (opts.promptFile)
107
+ prompt = readFileSync(opts.promptFile, "utf8");
108
+ let details = opts.details;
109
+ if (opts.detailsFile)
110
+ details = readFileSync(opts.detailsFile, "utf8");
111
+ const reqBody = buildDmBody({
112
+ recipient,
113
+ message: message ?? "",
114
+ prompt,
115
+ details,
116
+ at: opts.at,
117
+ inDelay: opts.in,
118
+ now: Date.now(),
119
+ });
120
+ const token = await ensureCognitoToken();
121
+ const res = await vaultApiFetch({
122
+ token,
123
+ path: "/v1/notify/dm",
124
+ method: "POST",
125
+ body: reqBody,
126
+ });
127
+ if (!res.ok) {
128
+ const err = (await res.json().catch(() => ({})));
129
+ console.error(chalk.red(friendlyDmError(res.status, err.code, err.error ?? err.message ?? res.statusText)));
130
+ process.exit(1);
131
+ }
132
+ const data = (await res.json());
133
+ if (data.scheduled) {
134
+ console.log(chalk.green(`Scheduled DM to ${recipient} for ${data.deliverAt} (eventId ${data.eventId}).`));
135
+ console.log(chalk.dim("It delivers within ~60s of that time, even if you're offline."));
136
+ }
137
+ else {
138
+ console.log(chalk.green(`DM sent to ${recipient} (eventId ${data.eventId}).`));
139
+ }
140
+ }
141
+ catch (err) {
142
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
143
+ process.exit(1);
144
+ }
145
+ });
146
+ }
147
+ //# sourceMappingURL=dm.js.map
148
+ //# debugId=69d48194-d5ae-508e-b614-68f2066db6ee
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0ed956d3-5fde-54aa-96cb-1859a15f7723")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d7693093-4011-58fa-b5be-805b5f9f5421")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -31,6 +31,7 @@ import { registerGroupsCommand } from "./commands/groups.js";
31
31
  import { registerFilesCommand } from "./commands/files.js";
32
32
  import { registerFilesBrowseCommands } from "./commands/files-browse.js";
33
33
  import { registerMembersCommand } from "./commands/members.js";
34
+ import { registerDmCommand } from "./commands/dm.js";
34
35
  import { registerFeedbackCommand } from "./commands/feedback.js";
35
36
  import { registerMeetingsCommand } from "./commands/meetings.js";
36
37
  import { registerSourcesCommand } from "./commands/sources.js";
@@ -110,6 +111,7 @@ const filesCmd = registerFilesCommand(program);
110
111
  registerFilesBrowseCommands(filesCmd);
111
112
  // Membership management (subcommand group — hq members invite|list|revoke)
112
113
  registerMembersCommand(program);
114
+ registerDmCommand(program);
113
115
  // Onboarding (top-level — Cognito + vault-service provisioning)
114
116
  registerOnboardCommand(program);
115
117
  // Feedback (subcommand group — hq feedback bug|feature)
@@ -146,4 +148,4 @@ registerSignalsCommand(program);
146
148
  }
147
149
  })();
148
150
  //# sourceMappingURL=index.js.map
149
- //# debugId=0ed956d3-5fde-54aa-96cb-1859a15f7723
151
+ //# debugId=d7693093-4011-58fa-b5be-805b5f9f5421
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.25.1",
3
+ "version": "5.26.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { detectRecipient, parseDuration, buildDmBody } from "./dm.js";
3
+
4
+ describe("detectRecipient", () => {
5
+ it("classifies an email", () => {
6
+ expect(detectRecipient("Stefan@Getindigo.ai")).toEqual({
7
+ toEmail: "stefan@getindigo.ai",
8
+ });
9
+ });
10
+ it("classifies a personUid", () => {
11
+ expect(detectRecipient("prs_01ABC")).toEqual({ toPersonUid: "prs_01ABC" });
12
+ });
13
+ it("rejects anything else", () => {
14
+ expect(detectRecipient("not-an-email")).toBeNull();
15
+ expect(detectRecipient("")).toBeNull();
16
+ });
17
+ });
18
+
19
+ describe("parseDuration", () => {
20
+ it("parses units", () => {
21
+ expect(parseDuration("30s")).toBe(30_000);
22
+ expect(parseDuration("10m")).toBe(600_000);
23
+ expect(parseDuration("2h")).toBe(7_200_000);
24
+ expect(parseDuration("1d")).toBe(86_400_000);
25
+ expect(parseDuration(" 5m ")).toBe(300_000);
26
+ });
27
+ it("returns null on garbage", () => {
28
+ expect(parseDuration("soon")).toBeNull();
29
+ expect(parseDuration("10")).toBeNull();
30
+ expect(parseDuration("10x")).toBeNull();
31
+ });
32
+ });
33
+
34
+ describe("buildDmBody", () => {
35
+ const now = Date.parse("2026-05-29T00:00:00.000Z");
36
+
37
+ it("builds an email DM with body", () => {
38
+ expect(
39
+ buildDmBody({ recipient: "a@b.com", message: " hi ", now }),
40
+ ).toEqual({ toEmail: "a@b.com", body: "hi" });
41
+ });
42
+
43
+ it("includes prompt + details when present, omits when blank", () => {
44
+ expect(
45
+ buildDmBody({
46
+ recipient: "prs_x",
47
+ message: "m",
48
+ prompt: "do the thing",
49
+ details: " ",
50
+ now,
51
+ }),
52
+ ).toEqual({ toPersonUid: "prs_x", body: "m", prompt: "do the thing" });
53
+ });
54
+
55
+ it("resolves --in to a future deliverAt", () => {
56
+ const out = buildDmBody({ recipient: "a@b.com", message: "m", inDelay: "10m", now });
57
+ expect(out.deliverAt).toBe("2026-05-29T00:10:00.000Z");
58
+ });
59
+
60
+ it("resolves --at to a normalized ISO deliverAt", () => {
61
+ const out = buildDmBody({
62
+ recipient: "a@b.com",
63
+ message: "m",
64
+ at: "2026-06-01T12:00:00Z",
65
+ now,
66
+ });
67
+ expect(out.deliverAt).toBe("2026-06-01T12:00:00.000Z");
68
+ });
69
+
70
+ it("rejects an invalid recipient", () => {
71
+ expect(() => buildDmBody({ recipient: "nope", message: "m", now })).toThrow(/Invalid recipient/);
72
+ });
73
+
74
+ it("requires a body", () => {
75
+ expect(() => buildDmBody({ recipient: "a@b.com", message: " ", now })).toThrow(/body is required/);
76
+ });
77
+
78
+ it("rejects both --at and --in", () => {
79
+ expect(() =>
80
+ buildDmBody({ recipient: "a@b.com", message: "m", at: "2026-06-01T12:00:00Z", inDelay: "10m", now }),
81
+ ).toThrow(/only one of --at or --in/);
82
+ });
83
+
84
+ it("rejects an invalid --at and --in", () => {
85
+ expect(() => buildDmBody({ recipient: "a@b.com", message: "m", at: "nope", now })).toThrow(/Invalid --at/);
86
+ expect(() => buildDmBody({ recipient: "a@b.com", message: "m", inDelay: "soon", now })).toThrow(/Invalid --in/);
87
+ });
88
+ });
@@ -0,0 +1,222 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { readFileSync } from "node:fs";
4
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
+ import { vaultApiFetch } from "../utils/vault-api.js";
6
+
7
+ const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
8
+ const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
9
+
10
+ export interface DmRecipient {
11
+ toEmail?: string;
12
+ toPersonUid?: string;
13
+ }
14
+
15
+ /**
16
+ * Classify a recipient arg as an email or a personUid. Mirrors the
17
+ * email/prs_ heuristic used by `hq members`. Returns null for neither.
18
+ */
19
+ export function detectRecipient(recipient: string): DmRecipient | null {
20
+ const r = recipient.trim();
21
+ if (EMAIL_PATTERN.test(r)) return { toEmail: r.toLowerCase() };
22
+ if (PERSON_UID_PATTERN.test(r)) return { toPersonUid: r };
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * Parse a relative duration like `30s`, `10m`, `2h`, `1d` into milliseconds.
28
+ * Returns null on anything that doesn't match. Pure → unit-testable.
29
+ */
30
+ export function parseDuration(input: string): number | null {
31
+ const m = /^(\d+)\s*(s|m|h|d)$/.exec(input.trim());
32
+ if (!m) return null;
33
+ const n = parseInt(m[1], 10);
34
+ const mult: Record<string, number> = {
35
+ s: 1000,
36
+ m: 60_000,
37
+ h: 3_600_000,
38
+ d: 86_400_000,
39
+ };
40
+ return n * mult[m[2]];
41
+ }
42
+
43
+ export interface DmSendBody {
44
+ toEmail?: string;
45
+ toPersonUid?: string;
46
+ body: string;
47
+ prompt?: string;
48
+ details?: string;
49
+ deliverAt?: string;
50
+ }
51
+
52
+ /**
53
+ * Build the POST /v1/notify/dm request body from CLI inputs. Pure (no I/O,
54
+ * no clock) so the option-resolution logic is unit-testable; the caller
55
+ * supplies `now` for the `--in` relative-delay computation.
56
+ *
57
+ * Throws Error with a user-facing message on invalid input.
58
+ */
59
+ export function buildDmBody(args: {
60
+ recipient: string;
61
+ message: string;
62
+ prompt?: string;
63
+ details?: string;
64
+ at?: string;
65
+ inDelay?: string;
66
+ now: number;
67
+ }): DmSendBody {
68
+ const rcpt = detectRecipient(args.recipient);
69
+ if (!rcpt) {
70
+ throw new Error(
71
+ `Invalid recipient '${args.recipient}': must be an email address or a personUid (prs_…).`,
72
+ );
73
+ }
74
+ const body = (args.message ?? "").trim();
75
+ if (!body) {
76
+ throw new Error("A message body is required: hq dm <recipient> <message>");
77
+ }
78
+
79
+ if (args.at && args.inDelay) {
80
+ throw new Error("Use only one of --at or --in, not both.");
81
+ }
82
+ let deliverAt: string | undefined;
83
+ if (args.at) {
84
+ const when = new Date(args.at);
85
+ if (isNaN(when.getTime())) {
86
+ throw new Error(`Invalid --at '${args.at}': must be an ISO8601 date.`);
87
+ }
88
+ deliverAt = when.toISOString();
89
+ } else if (args.inDelay) {
90
+ const ms = parseDuration(args.inDelay);
91
+ if (ms === null) {
92
+ throw new Error(
93
+ `Invalid --in '${args.inDelay}': use a relative delay like 30s, 10m, 2h, 1d.`,
94
+ );
95
+ }
96
+ deliverAt = new Date(args.now + ms).toISOString();
97
+ }
98
+
99
+ const prompt = args.prompt?.trim();
100
+ const details = args.details?.trim();
101
+
102
+ return {
103
+ ...rcpt,
104
+ body,
105
+ ...(prompt ? { prompt } : {}),
106
+ ...(details ? { details } : {}),
107
+ ...(deliverAt ? { deliverAt } : {}),
108
+ };
109
+ }
110
+
111
+ function friendlyDmError(status: number, code: string | undefined, fallback: string): string {
112
+ if (status === 401) return "Not authenticated — run `hq login` and try again.";
113
+ if (status === 404 || code === "RECIPIENT_NOT_FOUND") {
114
+ return "Recipient not found or not reachable — you can only DM someone you share an active company with.";
115
+ }
116
+ if (status >= 500) return `Server error: ${fallback}`;
117
+ return fallback;
118
+ }
119
+
120
+ export function registerDmCommand(program: Command): void {
121
+ program
122
+ .command("dm <recipient> [message]")
123
+ .description(
124
+ "Send a direct message to a teammate (email or personUid). They receive it as an HQ Sync notification.",
125
+ )
126
+ .option(
127
+ "--prompt <text>",
128
+ "Agent-context prompt the recipient can one-click copy into their agent",
129
+ )
130
+ .option("--prompt-file <path>", "Read the agent prompt from a file")
131
+ .option(
132
+ "--details <text>",
133
+ "Longer detail shown in the recipient's DM detail window",
134
+ )
135
+ .option("--details-file <path>", "Read the details from a file")
136
+ .option(
137
+ "--at <iso>",
138
+ "Schedule delivery at an ISO8601 time (store-and-forward; delivered within ~60s of the time)",
139
+ )
140
+ .option(
141
+ "--in <duration>",
142
+ "Schedule delivery after a relative delay: 30s, 10m, 2h, 1d",
143
+ )
144
+ .action(
145
+ async (
146
+ recipient: string,
147
+ message: string | undefined,
148
+ opts: {
149
+ prompt?: string;
150
+ promptFile?: string;
151
+ details?: string;
152
+ detailsFile?: string;
153
+ at?: string;
154
+ in?: string;
155
+ },
156
+ ) => {
157
+ try {
158
+ // Resolve prompt/details from inline text or a file.
159
+ let prompt = opts.prompt;
160
+ if (opts.promptFile) prompt = readFileSync(opts.promptFile, "utf8");
161
+ let details = opts.details;
162
+ if (opts.detailsFile) details = readFileSync(opts.detailsFile, "utf8");
163
+
164
+ const reqBody = buildDmBody({
165
+ recipient,
166
+ message: message ?? "",
167
+ prompt,
168
+ details,
169
+ at: opts.at,
170
+ inDelay: opts.in,
171
+ now: Date.now(),
172
+ });
173
+
174
+ const token = await ensureCognitoToken();
175
+ const res = await vaultApiFetch({
176
+ token,
177
+ path: "/v1/notify/dm",
178
+ method: "POST",
179
+ body: reqBody as unknown as Record<string, unknown>,
180
+ });
181
+
182
+ if (!res.ok) {
183
+ const err = (await res.json().catch(() => ({}))) as Record<string, string>;
184
+ console.error(
185
+ chalk.red(
186
+ friendlyDmError(res.status, err.code, err.error ?? err.message ?? res.statusText),
187
+ ),
188
+ );
189
+ process.exit(1);
190
+ }
191
+
192
+ const data = (await res.json()) as {
193
+ eventId?: string;
194
+ createdAt?: string;
195
+ scheduled?: boolean;
196
+ deliverAt?: string;
197
+ };
198
+
199
+ if (data.scheduled) {
200
+ console.log(
201
+ chalk.green(
202
+ `Scheduled DM to ${recipient} for ${data.deliverAt} (eventId ${data.eventId}).`,
203
+ ),
204
+ );
205
+ console.log(
206
+ chalk.dim("It delivers within ~60s of that time, even if you're offline."),
207
+ );
208
+ } else {
209
+ console.log(
210
+ chalk.green(`DM sent to ${recipient} (eventId ${data.eventId}).`),
211
+ );
212
+ }
213
+ } catch (err) {
214
+ console.error(
215
+ chalk.red("Error:"),
216
+ err instanceof Error ? err.message : String(err),
217
+ );
218
+ process.exit(1);
219
+ }
220
+ },
221
+ );
222
+ }
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ import { registerGroupsCommand } from "./commands/groups.js";
31
31
  import { registerFilesCommand } from "./commands/files.js";
32
32
  import { registerFilesBrowseCommands } from "./commands/files-browse.js";
33
33
  import { registerMembersCommand } from "./commands/members.js";
34
+ import { registerDmCommand } from "./commands/dm.js";
34
35
  import { registerFeedbackCommand } from "./commands/feedback.js";
35
36
  import { registerMeetingsCommand } from "./commands/meetings.js";
36
37
  import { registerSourcesCommand } from "./commands/sources.js";
@@ -138,6 +139,7 @@ registerFilesBrowseCommands(filesCmd);
138
139
 
139
140
  // Membership management (subcommand group — hq members invite|list|revoke)
140
141
  registerMembersCommand(program);
142
+ registerDmCommand(program);
141
143
 
142
144
  // Onboarding (top-level — Cognito + vault-service provisioning)
143
145
  registerOnboardCommand(program);