@vorlek/cli 0.1.0 → 0.2.1
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 +5 -1
- package/dist/cli.js +1 -1
- package/dist/client.js +17 -3
- package/dist/commands/connect.js +69 -12
- package/dist/commands/contact.js +101 -22
- package/dist/commands/status.js +3 -0
- package/dist/run-context.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,8 +18,10 @@ the CLI.
|
|
|
18
18
|
vorlek auth signup
|
|
19
19
|
vorlek auth use --api-key vk_live_...
|
|
20
20
|
vorlek connect sendgrid --api-key SG....
|
|
21
|
+
vorlek connect reconnect klaviyo --api-key pk_...
|
|
21
22
|
vorlek catalog
|
|
22
23
|
vorlek contact upsert --provider sendgrid --email person@example.com --detail full
|
|
24
|
+
vorlek contact upsert-all --email person@example.com --providers sendgrid,mailchimp,klaviyo --detail full
|
|
23
25
|
vorlek contact get --provider sendgrid --email person@example.com
|
|
24
26
|
vorlek operation get 01HV0000000000000000000000
|
|
25
27
|
vorlek template list --provider sendgrid
|
|
@@ -30,4 +32,6 @@ vorlek status --check
|
|
|
30
32
|
|
|
31
33
|
Tool commands accept `--detail minimal|standard|full` where the API supports
|
|
32
34
|
response verbosity. Use `operation get` with the `meta.request_id` returned by
|
|
33
|
-
API responses to inspect a stored operation receipt.
|
|
35
|
+
API and CLI JSON responses to inspect a stored operation receipt. `contact
|
|
36
|
+
upsert-all` writes the same contact to every connected provider by default, or
|
|
37
|
+
to the comma-separated `--providers` list when provided.
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ import { defaultDeps } from './deps.js';
|
|
|
23
23
|
*/
|
|
24
24
|
const cli = cac('vorlek');
|
|
25
25
|
cli.help();
|
|
26
|
-
cli.version('0.1
|
|
26
|
+
cli.version('0.2.1');
|
|
27
27
|
cli.option('--api-base <url>', 'Override API base URL (default: from config or https://api.vorlek.com)');
|
|
28
28
|
cli.option('--json', 'Output raw JSON (machine-readable)');
|
|
29
29
|
cli.option('-v, --verbose', 'Verbose logging (includes stack traces on errors)');
|
package/dist/client.js
CHANGED
|
@@ -42,7 +42,7 @@ export function createApiClient(opts) {
|
|
|
42
42
|
],
|
|
43
43
|
},
|
|
44
44
|
});
|
|
45
|
-
async function
|
|
45
|
+
async function callResult(path, init) {
|
|
46
46
|
const res = await http(path, init);
|
|
47
47
|
const text = await res.text();
|
|
48
48
|
let body;
|
|
@@ -63,17 +63,22 @@ export function createApiClient(opts) {
|
|
|
63
63
|
throw new ApiError(res.status, body);
|
|
64
64
|
}
|
|
65
65
|
if (obj.status === 'success' && 'data' in obj) {
|
|
66
|
-
|
|
66
|
+
const envelope = body;
|
|
67
|
+
return { data: envelope.data, meta: envelope.meta, tip: envelope.tip };
|
|
67
68
|
}
|
|
68
69
|
// Non-2xx without an error envelope = something unusual; surface it.
|
|
69
70
|
if (res.status < 200 || res.status >= 300) {
|
|
70
71
|
throw new Error(`Server returned status ${res.status} without an error envelope: ${text.slice(0, 200)}`);
|
|
71
72
|
}
|
|
72
73
|
// Flat success — body itself is the response.
|
|
73
|
-
return body;
|
|
74
|
+
return { data: body };
|
|
74
75
|
}
|
|
75
76
|
throw new Error(`Server returned a non-object response: ${text.slice(0, 200)}`);
|
|
76
77
|
}
|
|
78
|
+
async function call(path, init) {
|
|
79
|
+
const result = await callResult(path, init);
|
|
80
|
+
return result.data;
|
|
81
|
+
}
|
|
77
82
|
function toolPostInit(input) {
|
|
78
83
|
const { idempotencyKey, detail, ...json } = input;
|
|
79
84
|
return {
|
|
@@ -96,6 +101,12 @@ export function createApiClient(opts) {
|
|
|
96
101
|
createConnection(input) {
|
|
97
102
|
return call('v1/connections', { method: 'POST', json: input });
|
|
98
103
|
},
|
|
104
|
+
updateConnection(provider, input) {
|
|
105
|
+
return call(`v1/connections/${provider}`, {
|
|
106
|
+
method: 'PATCH',
|
|
107
|
+
json: input,
|
|
108
|
+
});
|
|
109
|
+
},
|
|
99
110
|
listConnections() {
|
|
100
111
|
return call('v1/connections', { method: 'GET' });
|
|
101
112
|
},
|
|
@@ -115,6 +126,9 @@ export function createApiClient(opts) {
|
|
|
115
126
|
upsertContact(input) {
|
|
116
127
|
return call('v1/tools/upsert_contact', toolPostInit(input));
|
|
117
128
|
},
|
|
129
|
+
upsertContactWithMeta(input) {
|
|
130
|
+
return callResult('v1/tools/upsert_contact', toolPostInit(input));
|
|
131
|
+
},
|
|
118
132
|
getContact(input) {
|
|
119
133
|
return call('v1/tools/get_contact', toolPostInit(input));
|
|
120
134
|
},
|
package/dist/commands/connect.js
CHANGED
|
@@ -20,24 +20,37 @@ function availableAudiencesFromError(err) {
|
|
|
20
20
|
function connectionPayload(provider, opts, listId) {
|
|
21
21
|
return {
|
|
22
22
|
provider,
|
|
23
|
+
...credentialPayload(provider, opts, listId),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function credentialPayload(provider, opts, listId) {
|
|
27
|
+
return {
|
|
23
28
|
api_key: opts.apiKey ?? '',
|
|
24
29
|
...(provider === 'mailchimp' && listId ? { list_id: listId } : {}),
|
|
25
30
|
};
|
|
26
31
|
}
|
|
27
|
-
function renderConnectSuccess(deps, ctx, provider, conn) {
|
|
28
|
-
renderSuccess(deps, ctx, conn, () => `${pc.green('✓')}
|
|
32
|
+
function renderConnectSuccess(deps, ctx, provider, conn, verb = 'Connected') {
|
|
33
|
+
renderSuccess(deps, ctx, conn, () => `${pc.green('✓')} ${verb} ${pc.bold(provider)} ${conn.account_name ? `(${pc.dim(conn.account_name)}) ` : ''}— connection_id ${pc.dim(conn.connection_id)}`);
|
|
34
|
+
}
|
|
35
|
+
function validateConnectInput(provider, opts) {
|
|
36
|
+
if (!opts.apiKey)
|
|
37
|
+
throw new Error('Missing --api-key flag.');
|
|
38
|
+
if (provider === 'klaviyo') {
|
|
39
|
+
if (opts.listId)
|
|
40
|
+
throw new Error('Klaviyo connections do not use --list-id.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function klaviyoPublicKeyWarning(opts, ctx) {
|
|
44
|
+
return !ctx.json && opts.apiKey?.startsWith('pub_')
|
|
45
|
+
? `${pc.yellow('warning')} Klaviyo public keys start with pub_; use a private key starting with pk_ for write operations.\n`
|
|
46
|
+
: undefined;
|
|
29
47
|
}
|
|
30
48
|
export async function runConnect(deps, ctx, provider, opts) {
|
|
31
49
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
throw new Error('Klaviyo connections do not use --list-id.');
|
|
37
|
-
if (opts.apiKey.startsWith('pub_') && !ctx.json) {
|
|
38
|
-
deps.stderr.write(`${pc.yellow('warning')} Klaviyo public keys start with pub_; use a private key starting with pk_ for write operations.\n`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
50
|
+
validateConnectInput(provider, opts);
|
|
51
|
+
const warning = provider === 'klaviyo' ? klaviyoPublicKeyWarning(opts, ctx) : undefined;
|
|
52
|
+
if (warning)
|
|
53
|
+
deps.stderr.write(warning);
|
|
41
54
|
const cfg = await deps.loadConfig();
|
|
42
55
|
if (!cfg)
|
|
43
56
|
throw new ConfigMissingError();
|
|
@@ -69,6 +82,48 @@ export async function runConnect(deps, ctx, provider, opts) {
|
|
|
69
82
|
deps.exit(renderError(deps, ctx, err));
|
|
70
83
|
}
|
|
71
84
|
}
|
|
85
|
+
export async function runReconnect(deps, ctx, provider, opts) {
|
|
86
|
+
try {
|
|
87
|
+
if (!provider)
|
|
88
|
+
throw new Error('Missing <provider> argument: `vorlek connect reconnect <provider>`.');
|
|
89
|
+
validateConnectInput(provider, opts);
|
|
90
|
+
const warning = provider === 'klaviyo' ? klaviyoPublicKeyWarning(opts, ctx) : undefined;
|
|
91
|
+
if (warning)
|
|
92
|
+
deps.stderr.write(warning);
|
|
93
|
+
const cfg = await deps.loadConfig();
|
|
94
|
+
if (!cfg)
|
|
95
|
+
throw new ConfigMissingError();
|
|
96
|
+
const apiBase = ctx.apiBaseOverride ?? cfg.api_base;
|
|
97
|
+
const client = deps.createApiClient({ apiBase, apiKey: cfg.api_key });
|
|
98
|
+
if (!client.updateConnection) {
|
|
99
|
+
throw new Error('Configured API client does not support connection reconnect.');
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const conn = await client.updateConnection(provider, credentialPayload(provider, opts, opts.listId));
|
|
103
|
+
renderConnectSuccess(deps, ctx, provider, conn, 'Reconnected');
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const audiences = availableAudiencesFromError(err);
|
|
107
|
+
if (provider !== 'mailchimp' || ctx.json || opts.listId || audiences.length === 0)
|
|
108
|
+
throw err;
|
|
109
|
+
const selected = await deps.prompts.select({
|
|
110
|
+
message: 'Select Mailchimp audience',
|
|
111
|
+
options: audiences.map((audience) => ({
|
|
112
|
+
value: audience.id,
|
|
113
|
+
label: audience.name,
|
|
114
|
+
hint: audience.id,
|
|
115
|
+
})),
|
|
116
|
+
});
|
|
117
|
+
if (deps.prompts.isCancel(selected))
|
|
118
|
+
throw new Error('Audience selection cancelled.');
|
|
119
|
+
const conn = await client.updateConnection(provider, credentialPayload(provider, opts, String(selected)));
|
|
120
|
+
renderConnectSuccess(deps, ctx, provider, conn, 'Reconnected');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
deps.exit(renderError(deps, ctx, err));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
72
127
|
export async function runList(deps, ctx) {
|
|
73
128
|
try {
|
|
74
129
|
const cfg = await deps.loadConfig();
|
|
@@ -111,7 +166,7 @@ export async function runRemove(deps, ctx, provider) {
|
|
|
111
166
|
}
|
|
112
167
|
export function registerConnectCommands(cli, deps, ctx) {
|
|
113
168
|
cli
|
|
114
|
-
.command('connect <action> [target]', 'Connect: <provider> --api-key <k> | list | remove <provider>')
|
|
169
|
+
.command('connect <action> [target]', 'Connect: <provider> --api-key <k> | reconnect <provider> --api-key <k> | list | remove <provider>')
|
|
115
170
|
.option('--api-key <key>', 'Provider API key (required for connect <provider>)')
|
|
116
171
|
.option('--list-id <id>', 'Mailchimp audience/list id')
|
|
117
172
|
.action(async (action, target, opts) => {
|
|
@@ -119,6 +174,8 @@ export function registerConnectCommands(cli, deps, ctx) {
|
|
|
119
174
|
return runList(deps, ctx);
|
|
120
175
|
if (action === 'remove')
|
|
121
176
|
return runRemove(deps, ctx, target);
|
|
177
|
+
if (action === 'reconnect')
|
|
178
|
+
return runReconnect(deps, ctx, target, opts);
|
|
122
179
|
// Otherwise treat `action` as the provider name → create.
|
|
123
180
|
return runConnect(deps, ctx, action, opts);
|
|
124
181
|
});
|
package/dist/commands/contact.js
CHANGED
|
@@ -22,22 +22,9 @@ export async function runUpsertContact(deps, ctx, opts) {
|
|
|
22
22
|
}
|
|
23
23
|
provider = conns[0]?.provider;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
if (opts.properties) {
|
|
27
|
-
try {
|
|
28
|
-
const parsed = JSON.parse(opts.properties);
|
|
29
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
30
|
-
throw new Error('--properties must be a JSON object.');
|
|
31
|
-
}
|
|
32
|
-
properties = parsed;
|
|
33
|
-
}
|
|
34
|
-
catch (parseErr) {
|
|
35
|
-
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
36
|
-
throw new Error(`--properties: ${msg}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
25
|
+
const properties = parseProperties(opts.properties);
|
|
39
26
|
const detail = parseDetail(opts.detail);
|
|
40
|
-
const result = await client
|
|
27
|
+
const result = await upsertContactWithResult(client, {
|
|
41
28
|
provider,
|
|
42
29
|
email: opts.email,
|
|
43
30
|
first_name: opts.firstName,
|
|
@@ -49,12 +36,47 @@ export async function runUpsertContact(deps, ctx, opts) {
|
|
|
49
36
|
...(detail ? { detail } : {}),
|
|
50
37
|
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
51
38
|
});
|
|
52
|
-
renderSuccess(deps, ctx, result, () => {
|
|
53
|
-
const fieldsHint = result.fields_auto_created.length > 0
|
|
54
|
-
? ` ${pc.dim(`(auto-created fields: ${result.fields_auto_created.join(', ')})`)}`
|
|
39
|
+
renderSuccess(deps, ctx, result.data, () => {
|
|
40
|
+
const fieldsHint = result.data.fields_auto_created.length > 0
|
|
41
|
+
? ` ${pc.dim(`(auto-created fields: ${result.data.fields_auto_created.join(', ')})`)}`
|
|
55
42
|
: '';
|
|
56
|
-
return `${pc.green('✓')} Upserted contact ${pc.bold(result.contact_id)} (${result.action}) via ${pc.bold(result.provider)}.${fieldsHint}`;
|
|
57
|
-
});
|
|
43
|
+
return `${pc.green('✓')} Upserted contact ${pc.bold(result.data.contact_id)} (${result.data.action}) via ${pc.bold(result.data.provider)}.${fieldsHint}`;
|
|
44
|
+
}, result.meta);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
deps.exit(renderError(deps, ctx, err));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function runUpsertAllContacts(deps, ctx, opts) {
|
|
51
|
+
try {
|
|
52
|
+
if (!opts.email)
|
|
53
|
+
throw new Error('Missing --email flag.');
|
|
54
|
+
const cfg = await deps.loadConfig();
|
|
55
|
+
if (!cfg)
|
|
56
|
+
throw new ConfigMissingError();
|
|
57
|
+
const apiBase = ctx.apiBaseOverride ?? cfg.api_base;
|
|
58
|
+
const client = deps.createApiClient({ apiBase, apiKey: cfg.api_key });
|
|
59
|
+
const providers = await resolveTargetProviders(client, opts);
|
|
60
|
+
if (opts.idempotencyKey && providers.length > 1) {
|
|
61
|
+
throw new Error('--idempotency-key cannot be reused across multiple providers. Omit it or pass a single --provider/--providers value.');
|
|
62
|
+
}
|
|
63
|
+
const properties = parseProperties(opts.properties);
|
|
64
|
+
const detail = parseDetail(opts.detail);
|
|
65
|
+
const results = [];
|
|
66
|
+
for (const provider of providers) {
|
|
67
|
+
const result = await upsertContactWithResult(client, {
|
|
68
|
+
provider,
|
|
69
|
+
email: opts.email,
|
|
70
|
+
first_name: opts.firstName,
|
|
71
|
+
last_name: opts.lastName,
|
|
72
|
+
phone: opts.phone === undefined ? undefined : String(opts.phone),
|
|
73
|
+
properties,
|
|
74
|
+
...(detail ? { detail } : {}),
|
|
75
|
+
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
76
|
+
});
|
|
77
|
+
results.push({ ...result.data, ...(result.meta ? { meta: result.meta } : {}) });
|
|
78
|
+
}
|
|
79
|
+
renderSuccess(deps, ctx, { email: opts.email, results }, () => renderUpsertAllContacts(opts.email ?? '', results));
|
|
58
80
|
}
|
|
59
81
|
catch (err) {
|
|
60
82
|
deps.exit(renderError(deps, ctx, err));
|
|
@@ -93,21 +115,24 @@ export async function runGetContact(deps, ctx, opts) {
|
|
|
93
115
|
}
|
|
94
116
|
export function registerContactCommands(cli, deps, ctx) {
|
|
95
117
|
cli
|
|
96
|
-
.command('contact <action>', 'Contact operations: upsert, get')
|
|
118
|
+
.command('contact <action>', 'Contact operations: upsert, upsert-all, get')
|
|
97
119
|
.option('--email <email>', 'Contact email (required for upsert and get)')
|
|
98
120
|
.option('--first-name <name>', 'First name')
|
|
99
121
|
.option('--last-name <name>', 'Last name')
|
|
100
122
|
.option('--phone <phone>', 'Phone number')
|
|
101
123
|
.option('--properties <json>', 'Custom properties as JSON, e.g. \'{"plan":"gold","tier":"silver"}\'')
|
|
102
124
|
.option('--provider <name>', 'Provider override (defaults to auto-detect)')
|
|
125
|
+
.option('--providers <list>', 'Comma-separated providers for upsert-all')
|
|
103
126
|
.option('--detail <level>', 'Response detail: minimal, standard, full')
|
|
104
127
|
.option('--idempotency-key <key>', 'Override the auto-generated idempotency key')
|
|
105
128
|
.action(async (action, opts) => {
|
|
106
129
|
if (action === 'upsert')
|
|
107
130
|
return runUpsertContact(deps, ctx, opts);
|
|
131
|
+
if (action === 'upsert-all')
|
|
132
|
+
return runUpsertAllContacts(deps, ctx, opts);
|
|
108
133
|
if (action === 'get')
|
|
109
134
|
return runGetContact(deps, ctx, opts);
|
|
110
|
-
deps.stderr.write(`${pc.red('error')} Unknown contact action: ${action}. Try: upsert, get\n`);
|
|
135
|
+
deps.stderr.write(`${pc.red('error')} Unknown contact action: ${action}. Try: upsert, upsert-all, get\n`);
|
|
111
136
|
deps.exit(1);
|
|
112
137
|
});
|
|
113
138
|
}
|
|
@@ -117,4 +142,58 @@ function contactIdFromResult(contact) {
|
|
|
117
142
|
const contactId = contact.contact_id;
|
|
118
143
|
return typeof contactId === 'string' ? contactId : null;
|
|
119
144
|
}
|
|
145
|
+
function parseProperties(raw) {
|
|
146
|
+
if (!raw)
|
|
147
|
+
return undefined;
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
151
|
+
throw new Error('--properties must be a JSON object.');
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
catch (parseErr) {
|
|
156
|
+
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
157
|
+
throw new Error(`--properties: ${msg}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function upsertContactWithResult(client, input) {
|
|
161
|
+
if (client.upsertContactWithMeta)
|
|
162
|
+
return client.upsertContactWithMeta(input);
|
|
163
|
+
return { data: await client.upsertContact(input) };
|
|
164
|
+
}
|
|
165
|
+
async function resolveTargetProviders(client, opts) {
|
|
166
|
+
const listed = parseProviderList(opts.providers);
|
|
167
|
+
if (listed.length > 0)
|
|
168
|
+
return listed;
|
|
169
|
+
if (opts.provider)
|
|
170
|
+
return [opts.provider];
|
|
171
|
+
const providers = [...new Set((await client.listConnections()).map((conn) => conn.provider))];
|
|
172
|
+
if (providers.length === 0) {
|
|
173
|
+
throw new Error('No connections found. Run `vorlek connect <provider> --api-key ...` first.');
|
|
174
|
+
}
|
|
175
|
+
return providers;
|
|
176
|
+
}
|
|
177
|
+
function parseProviderList(raw) {
|
|
178
|
+
if (!raw)
|
|
179
|
+
return [];
|
|
180
|
+
const providers = raw
|
|
181
|
+
.split(',')
|
|
182
|
+
.map((provider) => provider.trim())
|
|
183
|
+
.filter((provider) => provider.length > 0);
|
|
184
|
+
if (providers.length === 0)
|
|
185
|
+
throw new Error('--providers must include at least one provider.');
|
|
186
|
+
return [...new Set(providers)];
|
|
187
|
+
}
|
|
188
|
+
function renderUpsertAllContacts(email, results) {
|
|
189
|
+
const lines = results.map((result) => {
|
|
190
|
+
const { meta } = result;
|
|
191
|
+
const receipt = meta?.request_id ? ` ${pc.dim(`request_id: ${meta.request_id}`)}` : '';
|
|
192
|
+
return ` ${pc.green('✓')} ${result.provider}: ${result.contact_id} (${result.action})${receipt}`;
|
|
193
|
+
});
|
|
194
|
+
return [
|
|
195
|
+
`${pc.green('✓')} Upserted ${pc.bold(email)} across ${results.length} provider${results.length === 1 ? '' : 's'}.`,
|
|
196
|
+
...lines,
|
|
197
|
+
].join('\n');
|
|
198
|
+
}
|
|
120
199
|
//# sourceMappingURL=contact.js.map
|
package/dist/commands/status.js
CHANGED
|
@@ -46,6 +46,9 @@ export function registerStatusCommand(cli, deps, ctx) {
|
|
|
46
46
|
const fresh = checksByProvider.get(c.provider);
|
|
47
47
|
const checkText = fresh ? `, checked: ${fresh.status}` : '';
|
|
48
48
|
lines.push(` ${pc.green('●')} ${c.provider} (${c.status}${checkText})${name}`);
|
|
49
|
+
if (fresh?.error) {
|
|
50
|
+
lines.push(` ${pc.yellow(fresh.error.code)}: ${fresh.error.message}`);
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
lines.push('');
|
package/dist/run-context.js
CHANGED
|
@@ -4,6 +4,8 @@ import { ApiError } from './client.js';
|
|
|
4
4
|
export function renderSuccess(deps, ctx, data, human, meta) {
|
|
5
5
|
if (ctx.json) {
|
|
6
6
|
const out = { status: 'success', data };
|
|
7
|
+
if (meta)
|
|
8
|
+
out.meta = meta;
|
|
7
9
|
if (meta?.request_id)
|
|
8
10
|
out.request_id = meta.request_id;
|
|
9
11
|
deps.stdout.write(`${JSON.stringify(out)}\n`);
|