@tenderprompt/cli 0.1.19 → 0.1.20

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.
Files changed (2) hide show
  1. package/dist/index.js +391 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -110,6 +110,10 @@ Commands:
110
110
  Show publish job status by job id
111
111
  tender app publish watch <app-id>
112
112
  Stream or print lifecycle events for a publish job
113
+ tender app outbound-auth list <app-id>
114
+ List pending and approved outbound auth grants
115
+ tender app outbound-auth approve <app-id>
116
+ Approve manifest-declared outbound auth grants
113
117
  tender app analytics summary <app-id>
114
118
  Read artifact analytics totals and trend points
115
119
  tender app analytics query <app-id> --spec <file|->
@@ -155,6 +159,8 @@ Examples:
155
159
  tender app preview status artifact_123 --commit $(git rev-parse HEAD) --json
156
160
  tender app preview watch artifact_123 --job job_123 --stream --json
157
161
  tender app publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json
162
+ tender app outbound-auth list artifact_123 --json
163
+ tender app outbound-auth approve artifact_123 --all --confirm approve --json
158
164
  tender app analytics summary artifact_123 --range 30d --json
159
165
  tender app analytics query artifact_123 --spec chart.json --json
160
166
  tender app analytics suggestions artifact_123 --range 30d --json
@@ -293,6 +299,10 @@ Commands:
293
299
  Show publish job status by job id
294
300
  tender app publish watch <app-id>
295
301
  Replay lifecycle events for a publish job id
302
+ tender app outbound-auth list <app-id>
303
+ List pending and approved outbound auth grants
304
+ tender app outbound-auth approve <app-id>
305
+ Approve manifest-declared outbound auth grants
296
306
  tender app analytics summary <app-id>
297
307
  Read artifact analytics totals and trend points
298
308
  tender app analytics query <app-id> --spec <file|->
@@ -355,6 +365,8 @@ Examples:
355
365
  tender app publish artifact_123 --dry-run --json
356
366
  tender app publish status artifact_123 --job job_123 --json
357
367
  tender app publish watch artifact_123 --job job_123 --stream --json
368
+ tender app outbound-auth list artifact_123 --json
369
+ tender app outbound-auth approve artifact_123 --all --confirm approve --json
358
370
  tender app analytics summary artifact_123 --range 30d --json
359
371
  tender app analytics query artifact_123 --spec chart.json --json
360
372
  tender app analytics suggestions artifact_123 --range 30d --json
@@ -1024,6 +1036,32 @@ Options:
1024
1036
  Examples:
1025
1037
  tender app publish watch artifact_123 --job job_123 --stream --json`;
1026
1038
  }
1039
+ function outboundAuthHelp() {
1040
+ return `Usage:
1041
+ tender app outbound-auth list <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
1042
+ tender app outbound-auth approve <app-id> (--all | --binding <id> --secret <key> --host <host> --location <header|query> --name <name>) --confirm approve [--path-prefix <path>] [--scheme <scheme>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
1043
+
1044
+ Commands:
1045
+ list List manifest-declared outbound auth grants and approval status.
1046
+ approve Approve one grant, or all currently pending grants with --all.
1047
+
1048
+ Options:
1049
+ --all Approve every pending outbound auth grant declared by the current manifest.
1050
+ --binding <id> Binding id from bindings/<id>/binding.json.
1051
+ --secret <key> Secret key backing the injected credential.
1052
+ --host <host> Exact outbound host.
1053
+ --location <value> header or query.
1054
+ --name <name> Header name or query parameter name.
1055
+ --path-prefix <path> Optional path prefix from outboundAuth.
1056
+ --scheme <scheme> Optional header auth scheme from outboundAuth.
1057
+ --confirm approve Required for approval.
1058
+ --json Emit stable JSON for coding agents.
1059
+
1060
+ Examples:
1061
+ tender app outbound-auth list artifact_123 --json
1062
+ tender app outbound-auth approve artifact_123 --all --confirm approve --json
1063
+ tender app outbound-auth approve artifact_123 --binding google-chat --secret GOOGLE_CHAT_KEY --host chat.googleapis.com --location query --name key --path-prefix /v1/spaces/ --confirm approve --json`;
1064
+ }
1027
1065
  function tenderCliCapabilities() {
1028
1066
  return {
1029
1067
  ok: true,
@@ -1057,6 +1095,7 @@ function tenderCliCapabilities() {
1057
1095
  'tender app analytics --help',
1058
1096
  'tender app db --help',
1059
1097
  'tender app bindings outbound --help',
1098
+ 'tender app outbound-auth --help',
1060
1099
  ],
1061
1100
  },
1062
1101
  workflows: [
@@ -1130,11 +1169,13 @@ function tenderCliCapabilities() {
1130
1169
  'git push tender main',
1131
1170
  'tender app agent heartbeat <artifact-id> --source codex --status working --phase building_preview --summary "Waiting for preview build" --commit $(git rev-parse HEAD) --json',
1132
1171
  'tender app preview <artifact-id> --commit $(git rev-parse HEAD) --stream --json',
1172
+ 'tender app outbound-auth list <artifact-id> --json',
1133
1173
  'tender app publish <artifact-id> --dry-run --json',
1134
1174
  'tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json',
1135
1175
  ],
1136
1176
  notes: [
1137
1177
  'Send heartbeat updates only at meaningful milestones or when waiting for user input; do not send rapid polling updates.',
1178
+ 'If preview or publish reports outbound auth grant approval required, run tender app outbound-auth approve <artifact-id> --all --confirm approve --json, then retry.',
1138
1179
  'Publishing is production-facing and requires explicit user intent.',
1139
1180
  ],
1140
1181
  },
@@ -2260,6 +2301,201 @@ function parsePublishWatchArgs(args) {
2260
2301
  json: options.json,
2261
2302
  };
2262
2303
  }
2304
+ function parseOutboundAuthLocation(value) {
2305
+ if (value !== 'header' && value !== 'query') {
2306
+ throw new TenderCliUsageError('--location must be one of header or query.');
2307
+ }
2308
+ return value;
2309
+ }
2310
+ function parseOutboundAuthListArgs(args) {
2311
+ if (args.includes('--help') || args.includes('-h')) {
2312
+ return { command: 'help', topic: 'app outbound-auth' };
2313
+ }
2314
+ const artifactId = args[0];
2315
+ if (!artifactId || artifactId.startsWith('-')) {
2316
+ throw new TenderCliUsageError('app id is required. Example: tender app outbound-auth list <app-id> --json');
2317
+ }
2318
+ let baseUrl = null;
2319
+ let flagToken = null;
2320
+ let profileName = null;
2321
+ let json = false;
2322
+ for (let index = 1; index < args.length; index += 1) {
2323
+ const arg = args[index];
2324
+ if (arg === '--json') {
2325
+ json = true;
2326
+ continue;
2327
+ }
2328
+ if (arg === '--base-url') {
2329
+ baseUrl = parseBaseUrlFlag(args, index);
2330
+ index += 1;
2331
+ continue;
2332
+ }
2333
+ if (arg === '--token') {
2334
+ flagToken = parseTokenFlag(args, index);
2335
+ index += 1;
2336
+ continue;
2337
+ }
2338
+ if (arg === '--profile') {
2339
+ profileName = parseProfileName(args[index + 1], '--profile');
2340
+ index += 1;
2341
+ continue;
2342
+ }
2343
+ throw new TenderCliUsageError(`Unknown outbound-auth list option: ${arg}`);
2344
+ }
2345
+ return {
2346
+ command: 'app outbound-auth list',
2347
+ artifactId,
2348
+ baseUrl,
2349
+ flagToken,
2350
+ profileName,
2351
+ json,
2352
+ };
2353
+ }
2354
+ function parseOutboundAuthApproveArgs(args) {
2355
+ if (args.includes('--help') || args.includes('-h')) {
2356
+ return { command: 'help', topic: 'app outbound-auth' };
2357
+ }
2358
+ const artifactId = args[0];
2359
+ if (!artifactId || artifactId.startsWith('-')) {
2360
+ throw new TenderCliUsageError('app id is required. Example: tender app outbound-auth approve <app-id> --all --confirm approve --json');
2361
+ }
2362
+ let baseUrl = null;
2363
+ let flagToken = null;
2364
+ let profileName = null;
2365
+ let confirm = null;
2366
+ let approveAll = false;
2367
+ let bindingId = null;
2368
+ let secret = null;
2369
+ let host = null;
2370
+ let location = null;
2371
+ let name = null;
2372
+ let scheme = null;
2373
+ let pathPrefix = null;
2374
+ let json = false;
2375
+ for (let index = 1; index < args.length; index += 1) {
2376
+ const arg = args[index];
2377
+ if (arg === '--json') {
2378
+ json = true;
2379
+ continue;
2380
+ }
2381
+ if (arg === '--all') {
2382
+ approveAll = true;
2383
+ continue;
2384
+ }
2385
+ if (arg === '--confirm') {
2386
+ const value = args[index + 1];
2387
+ if (!value || value.startsWith('-')) {
2388
+ throw new TenderCliUsageError('--confirm requires the value approve.');
2389
+ }
2390
+ confirm = value;
2391
+ index += 1;
2392
+ continue;
2393
+ }
2394
+ if (arg === '--binding') {
2395
+ const value = args[index + 1];
2396
+ if (!value || value.startsWith('-')) {
2397
+ throw new TenderCliUsageError('--binding requires a binding id.');
2398
+ }
2399
+ bindingId = value;
2400
+ index += 1;
2401
+ continue;
2402
+ }
2403
+ if (arg === '--secret') {
2404
+ const value = args[index + 1];
2405
+ if (!value || value.startsWith('-')) {
2406
+ throw new TenderCliUsageError('--secret requires a secret key.');
2407
+ }
2408
+ secret = value;
2409
+ index += 1;
2410
+ continue;
2411
+ }
2412
+ if (arg === '--host') {
2413
+ const value = args[index + 1];
2414
+ if (!value || value.startsWith('-')) {
2415
+ throw new TenderCliUsageError('--host requires a host.');
2416
+ }
2417
+ host = value;
2418
+ index += 1;
2419
+ continue;
2420
+ }
2421
+ if (arg === '--location') {
2422
+ location = parseOutboundAuthLocation(args[index + 1]);
2423
+ index += 1;
2424
+ continue;
2425
+ }
2426
+ if (arg === '--name') {
2427
+ const value = args[index + 1];
2428
+ if (!value || value.startsWith('-')) {
2429
+ throw new TenderCliUsageError('--name requires a header or query parameter name.');
2430
+ }
2431
+ name = value;
2432
+ index += 1;
2433
+ continue;
2434
+ }
2435
+ if (arg === '--scheme') {
2436
+ const value = args[index + 1];
2437
+ if (!value || value.startsWith('-')) {
2438
+ throw new TenderCliUsageError('--scheme requires an auth scheme.');
2439
+ }
2440
+ scheme = value;
2441
+ index += 1;
2442
+ continue;
2443
+ }
2444
+ if (arg === '--path-prefix') {
2445
+ const value = args[index + 1];
2446
+ if (!value || value.startsWith('-')) {
2447
+ throw new TenderCliUsageError('--path-prefix requires a path prefix.');
2448
+ }
2449
+ pathPrefix = value;
2450
+ index += 1;
2451
+ continue;
2452
+ }
2453
+ if (arg === '--base-url') {
2454
+ baseUrl = parseBaseUrlFlag(args, index);
2455
+ index += 1;
2456
+ continue;
2457
+ }
2458
+ if (arg === '--token') {
2459
+ flagToken = parseTokenFlag(args, index);
2460
+ index += 1;
2461
+ continue;
2462
+ }
2463
+ if (arg === '--profile') {
2464
+ profileName = parseProfileName(args[index + 1], '--profile');
2465
+ index += 1;
2466
+ continue;
2467
+ }
2468
+ throw new TenderCliUsageError(`Unknown outbound-auth approve option: ${arg}`);
2469
+ }
2470
+ if (confirm !== 'approve') {
2471
+ throw new TenderCliUsageError('outbound-auth approve requires --confirm approve.');
2472
+ }
2473
+ if (approveAll && (bindingId || secret || host || location || name || scheme || pathPrefix)) {
2474
+ throw new TenderCliUsageError('Use either --all or one explicit grant, not both.');
2475
+ }
2476
+ if (!approveAll && (!bindingId || !secret || !host || !location || !name)) {
2477
+ throw new TenderCliUsageError('Explicit approval requires --binding, --secret, --host, --location, and --name. Example: tender app outbound-auth approve <app-id> --binding google-chat --secret GOOGLE_CHAT_KEY --host chat.googleapis.com --location query --name key --confirm approve --json');
2478
+ }
2479
+ return {
2480
+ command: 'app outbound-auth approve',
2481
+ artifactId,
2482
+ approveAll,
2483
+ confirm,
2484
+ grant: {
2485
+ bindingId,
2486
+ secret,
2487
+ host,
2488
+ location,
2489
+ name,
2490
+ scheme,
2491
+ pathPrefix,
2492
+ },
2493
+ baseUrl,
2494
+ flagToken,
2495
+ profileName,
2496
+ json,
2497
+ };
2498
+ }
2263
2499
  function parseArtifactsListArgs(args) {
2264
2500
  if (args.includes('--help') || args.includes('-h')) {
2265
2501
  return { command: 'help', topic: 'app list' };
@@ -4692,6 +4928,18 @@ function parseTenderArgs(args) {
4692
4928
  if (resourceArgs[1] === 'bindings' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
4693
4929
  return { command: 'help', topic: 'app bindings outbound' };
4694
4930
  }
4931
+ if (resourceArgs[1] === 'outbound-auth' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
4932
+ return { command: 'help', topic: 'app outbound-auth' };
4933
+ }
4934
+ if (resourceArgs[1] === 'outbound-auth' && resourceArgs[2] === 'list') {
4935
+ return parseOutboundAuthListArgs(resourceArgs.slice(3));
4936
+ }
4937
+ if (resourceArgs[1] === 'outbound-auth' && resourceArgs[2] === 'approve') {
4938
+ return parseOutboundAuthApproveArgs(resourceArgs.slice(3));
4939
+ }
4940
+ if (resourceArgs[1] === 'outbound-auth') {
4941
+ throw new TenderCliUsageError(`Unknown app outbound-auth command: ${resourceArgs[2]}`);
4942
+ }
4695
4943
  if (resourceArgs[1] === 'analytics' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
4696
4944
  return { command: 'help', topic: 'app analytics' };
4697
4945
  }
@@ -7451,7 +7699,7 @@ async function postLifecycle(input) {
7451
7699
  }
7452
7700
  const payload = await parseJsonResponse(response);
7453
7701
  if (!response.ok || !payload?.ok) {
7454
- throw new Error(payload?.message ?? payload?.reasonCode ?? `Artifact ${input.operation} request failed with HTTP ${response.status}.`);
7702
+ throw new Error(formatApiFailureMessage(payload, `Artifact ${input.operation} request failed with HTTP ${response.status}.`));
7455
7703
  }
7456
7704
  const result = {
7457
7705
  ok: true,
@@ -7473,6 +7721,18 @@ async function postLifecycle(input) {
7473
7721
  }
7474
7722
  return 0;
7475
7723
  }
7724
+ function readNextCommand(next) {
7725
+ if (!next || typeof next !== 'object' || Array.isArray(next)) {
7726
+ return null;
7727
+ }
7728
+ const command = next.command;
7729
+ return typeof command === 'string' && command.trim() ? command.trim() : null;
7730
+ }
7731
+ function formatApiFailureMessage(payload, fallback) {
7732
+ const message = payload?.message ?? payload?.reasonCode ?? fallback;
7733
+ const command = readNextCommand(payload?.next);
7734
+ return command ? `${message}\nNext: ${command}` : message;
7735
+ }
7476
7736
  async function runValidate(command, io, runtime) {
7477
7737
  return await postLifecycle({
7478
7738
  command,
@@ -7671,6 +7931,127 @@ async function runPublish(command, io, runtime) {
7671
7931
  runtime,
7672
7932
  });
7673
7933
  }
7934
+ async function requestOutboundAuthList(input) {
7935
+ const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/outbound-auth/grants`, {
7936
+ headers: {
7937
+ authorization: `Bearer ${input.credentials.token}`,
7938
+ },
7939
+ });
7940
+ const payload = await parseJsonResponse(response);
7941
+ if (!response.ok || !payload?.ok) {
7942
+ throw new Error(formatApiFailureMessage(payload, `Outbound auth grant lookup failed with HTTP ${response.status}.`));
7943
+ }
7944
+ return payload;
7945
+ }
7946
+ function formatOutboundAuthGrant(grant) {
7947
+ return `${grant.bindingId} -> ${grant.secret} -> ${grant.host} -> ${grant.location}:${grant.name}${grant.pathPrefix ? ` -> ${grant.pathPrefix}` : ''}${grant.scheme ? ` -> ${grant.scheme}` : ''}`;
7948
+ }
7949
+ function readExplicitOutboundAuthGrant(command) {
7950
+ const { bindingId, secret, host, location, name, scheme, pathPrefix } = command.grant;
7951
+ if (!bindingId || !secret || !host || !location || !name) {
7952
+ throw new TenderCliUsageError('Explicit approval requires --binding, --secret, --host, --location, and --name.');
7953
+ }
7954
+ return {
7955
+ bindingId,
7956
+ secret,
7957
+ host,
7958
+ location,
7959
+ name,
7960
+ ...(scheme ? { scheme } : {}),
7961
+ ...(pathPrefix ? { pathPrefix } : {}),
7962
+ };
7963
+ }
7964
+ async function approveOutboundAuthGrant(input) {
7965
+ const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/outbound-auth/grants/approve`, {
7966
+ method: 'POST',
7967
+ headers: {
7968
+ authorization: `Bearer ${input.credentials.token}`,
7969
+ 'content-type': 'application/json',
7970
+ },
7971
+ body: JSON.stringify(input.grant),
7972
+ });
7973
+ const payload = await parseJsonResponse(response);
7974
+ if (!response.ok || !payload?.ok || !payload.approved) {
7975
+ throw new Error(formatApiFailureMessage(payload, `Outbound auth grant approval failed with HTTP ${response.status}.`));
7976
+ }
7977
+ return payload.approved;
7978
+ }
7979
+ async function runOutboundAuthList(command, io, runtime) {
7980
+ const credentials = await resolveApiCredentials({
7981
+ baseUrl: command.baseUrl,
7982
+ flagToken: command.flagToken,
7983
+ profileName: command.profileName,
7984
+ env: runtime.env ?? process.env,
7985
+ });
7986
+ const payload = await requestOutboundAuthList({
7987
+ command,
7988
+ credentials,
7989
+ fetcher: runtime.fetcher ?? fetch,
7990
+ });
7991
+ const result = {
7992
+ ok: true,
7993
+ command: command.command,
7994
+ baseUrl: credentials.baseUrl,
7995
+ authSource: credentials.source,
7996
+ profile: credentials.profileName,
7997
+ artifactId: command.artifactId,
7998
+ grants: payload.grants ?? [],
7999
+ pendingCount: payload.pendingCount ?? 0,
8000
+ approvedCount: payload.approvedCount ?? 0,
8001
+ next: payload.next ?? null,
8002
+ };
8003
+ if (command.json) {
8004
+ io.stdout(JSON.stringify(result, null, 2));
8005
+ }
8006
+ else {
8007
+ const grants = result.grants.map((grant) => `${grant.approved ? 'approved' : 'pending'}: ${formatOutboundAuthGrant(grant)}`);
8008
+ io.stdout([
8009
+ `app_id: ${command.artifactId}`,
8010
+ `pending: ${result.pendingCount}`,
8011
+ `approved: ${result.approvedCount}`,
8012
+ ...grants,
8013
+ ...(readNextCommand(result.next) ? [`next: ${readNextCommand(result.next)}`] : []),
8014
+ ].join('\n'));
8015
+ }
8016
+ return 0;
8017
+ }
8018
+ async function runOutboundAuthApprove(command, io, runtime) {
8019
+ const credentials = await resolveApiCredentials({
8020
+ baseUrl: command.baseUrl,
8021
+ flagToken: command.flagToken,
8022
+ profileName: command.profileName,
8023
+ env: runtime.env ?? process.env,
8024
+ });
8025
+ const fetcher = runtime.fetcher ?? fetch;
8026
+ const grants = command.approveAll
8027
+ ? (await requestOutboundAuthList({ command, credentials, fetcher })).grants?.filter((grant) => !grant.approved) ?? []
8028
+ : [readExplicitOutboundAuthGrant(command)];
8029
+ const approved = [];
8030
+ for (const grant of grants) {
8031
+ approved.push(await approveOutboundAuthGrant({ command, credentials, fetcher, grant }));
8032
+ }
8033
+ const result = {
8034
+ ok: true,
8035
+ command: command.command,
8036
+ baseUrl: credentials.baseUrl,
8037
+ authSource: credentials.source,
8038
+ profile: credentials.profileName,
8039
+ artifactId: command.artifactId,
8040
+ approvedCount: approved.length,
8041
+ approved,
8042
+ };
8043
+ if (command.json) {
8044
+ io.stdout(JSON.stringify(result, null, 2));
8045
+ }
8046
+ else {
8047
+ io.stdout([
8048
+ `app_id: ${command.artifactId}`,
8049
+ `approved: ${approved.length}`,
8050
+ ...approved.map((grant) => formatOutboundAuthGrant(grant)),
8051
+ ].join('\n'));
8052
+ }
8053
+ return 0;
8054
+ }
7674
8055
  async function requestPublishJob(input) {
7675
8056
  const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/jobs/${encodeURIComponent(input.command.jobId)}`, {
7676
8057
  headers: {
@@ -8702,6 +9083,9 @@ export async function runTenderCli(args, io = {
8702
9083
  else if (command.topic === 'app publish watch') {
8703
9084
  io.stdout(publishWatchHelp());
8704
9085
  }
9086
+ else if (command.topic === 'app outbound-auth') {
9087
+ io.stdout(outboundAuthHelp());
9088
+ }
8705
9089
  else if (command.topic === 'app analytics') {
8706
9090
  io.stdout(analyticsHelp());
8707
9091
  }
@@ -8854,6 +9238,12 @@ export async function runTenderCli(args, io = {
8854
9238
  if (command.command === 'app publish watch') {
8855
9239
  return await runPublishWatch(command, io, runtime);
8856
9240
  }
9241
+ if (command.command === 'app outbound-auth list') {
9242
+ return await runOutboundAuthList(command, io, runtime);
9243
+ }
9244
+ if (command.command === 'app outbound-auth approve') {
9245
+ return await runOutboundAuthApprove(command, io, runtime);
9246
+ }
8857
9247
  if (command.command === 'app analytics summary') {
8858
9248
  return await runAnalyticsSummary(command, io, runtime);
8859
9249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"