@tenderprompt/cli 0.1.17 → 0.1.18

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 (3) hide show
  1. package/README.md +29 -0
  2. package/dist/index.js +559 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -39,6 +39,8 @@ session:
39
39
  - inspect artifact-scoped analytics without SQL or platform credentials
40
40
  - inspect artifact-scoped app database tables, schema, and bounded read-only
41
41
  query output
42
+ - share app setup between Tender accounts without copying customer data,
43
+ analytics history, app database rows, secrets, or release history
42
44
  - run server-validated chart specs and export aggregate rows
43
45
  - generate starter analytics suggestions
44
46
  - create, list, update, and delete saved analytics dashboards and charts, with
@@ -129,6 +131,9 @@ tender capabilities --json
129
131
  tender app list --json
130
132
  tender app create --name "Exit Intent Widget" --json
131
133
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
134
+ tender app share create artifact_123 --expires 7d --claims 1 --json
135
+ tender app share inspect tok_share_123 --json
136
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
132
137
  tender app update artifact_123 --name "Sale Widget" --json
133
138
  tender app init artifact_123 --dir ./widget --preview --json
134
139
  tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
@@ -145,6 +150,30 @@ tender app db query artifact_123 --env published --sql "select * from votes limi
145
150
  tender app db query artifact_123 --env published --sql "select * from votes order by created_at" --limit 50 --offset 50 --json
146
151
  ```
147
152
 
153
+ ## Shared app setup
154
+
155
+ Use this for agency-to-customer or collaborator handoffs where the recipient
156
+ should get the source and reusable setup, not the source account's data.
157
+
158
+ Agency side:
159
+
160
+ ```bash
161
+ tender app share create artifact_123 --expires 7d --claims 1 --json
162
+ ```
163
+
164
+ Recipient side:
165
+
166
+ ```bash
167
+ tender app share inspect tok_share_123 --json
168
+ tender app claim tok_share_123 --dir ./exit-intent-widget
169
+ tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json
170
+ ```
171
+
172
+ Claim copies source code, app setup, and analytics dashboard/chart definitions.
173
+ It does not copy analytics events, aggregates, exports, app database rows,
174
+ secrets, environment values, release history, or ongoing access to the source
175
+ artifact.
176
+
148
177
  ## Analytics
149
178
 
150
179
  Analytics commands are artifact-scoped and go through Tender's server-side
package/dist/index.js CHANGED
@@ -91,6 +91,10 @@ Commands:
91
91
  Create a new Tender App in the current account
92
92
  tender app update <app-id> --name <name>
93
93
  Update app metadata
94
+ tender app share create <app-id>
95
+ Create a claimable app setup share token
96
+ tender app claim <token>
97
+ Claim shared app setup into the current account
94
98
  tender app init <app-id>
95
99
  Bootstrap a checkout for local agent work
96
100
  tender app validate <app-id>
@@ -145,6 +149,8 @@ Examples:
145
149
  tender app create --name "Exit Intent Widget" --json
146
150
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
147
151
  tender app update artifact_123 --name "Sale Widget" --json
152
+ tender app share create artifact_123 --expires 7d --claims 1 --json
153
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
148
154
  tender app init artifact_123 --dir ./widget --preview --json
149
155
  tender app validate artifact_123 --json
150
156
  tender app build artifact_123 --commit $(git rev-parse HEAD) --json
@@ -262,6 +268,14 @@ Commands:
262
268
  Create a new Tender App, optionally initialize source, and preview
263
269
  tender app update <app-id> --name <name>
264
270
  Update app metadata
271
+ tender app share create <app-id>
272
+ Create a claimable app setup share token
273
+ tender app share inspect <token>
274
+ Inspect a shared app setup token without claiming it
275
+ tender app share revoke <share-id>
276
+ Revoke a shared app setup token
277
+ tender app claim <token>
278
+ Claim shared app setup into the current account
265
279
  tender app init <app-id>
266
280
  Bootstrap context files and the tender Git remote
267
281
  tender app validate <app-id>
@@ -331,6 +345,9 @@ Examples:
331
345
  tender app create --name "Exit Intent Widget" --json
332
346
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
333
347
  tender app update artifact_123 --name "Sale Widget" --json
348
+ tender app share create artifact_123 --expires 7d --claims 1 --json
349
+ tender app share inspect tok_share_123 --json
350
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
334
351
  tender app init artifact_123 --dir ./widget --preview --json
335
352
  tender app validate artifact_123 --json
336
353
  tender app build artifact_123 --commit $(git rev-parse HEAD) --json
@@ -366,6 +383,53 @@ Examples:
366
383
  tender app doctor --json
367
384
  tender app git remote artifact_123 --ttl 7d --json`;
368
385
  }
386
+ function appShareHelp() {
387
+ return `Usage: tender app share <command> [options]
388
+
389
+ Commands:
390
+ tender app share create <app-id> Create a claimable app setup token
391
+ tender app share inspect <token> Inspect token metadata before claiming
392
+ tender app share revoke <share-id> Revoke a share token
393
+
394
+ Examples:
395
+ tender app share create artifact_123 --expires 7d --claims 1 --json
396
+ tender app share inspect tok_share_123 --json
397
+ tender app share revoke app_setup_share_123 --json`;
398
+ }
399
+ function appShareCreateHelp() {
400
+ return `Usage: tender app share create <app-id> [--expires <7d|24h>] [--claims <n>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
401
+
402
+ Creates a claimable app setup token. The share includes source code, app setup,
403
+ and analytics dashboard/chart definitions. It does not include customer data,
404
+ analytics history, app database rows, secrets, or release history.
405
+
406
+ Examples:
407
+ tender app share create artifact_123 --expires 7d --claims 1 --json`;
408
+ }
409
+ function appShareInspectHelp() {
410
+ return `Usage: tender app share inspect <token> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
411
+
412
+ Examples:
413
+ tender app share inspect tok_share_123 --json`;
414
+ }
415
+ function appShareRevokeHelp() {
416
+ return `Usage: tender app share revoke <share-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
417
+
418
+ Examples:
419
+ tender app share revoke app_setup_share_123 --json`;
420
+ }
421
+ function appClaimHelp() {
422
+ return `Usage: tender app claim <token> --dir <path> [--name <name>] [--confirm source-and-setup] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
423
+
424
+ Claims shared app setup into the authenticated Tender account, creates a new app,
425
+ materializes source files into the local directory, and configures the Tender Git
426
+ remote. Without --confirm source-and-setup, the command only previews what will
427
+ happen.
428
+
429
+ Examples:
430
+ tender app claim tok_share_123 --dir ./exit-intent-widget
431
+ tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json`;
432
+ }
369
433
  function capabilitiesHelp() {
370
434
  return `Usage: tender capabilities [--json]
371
435
 
@@ -772,7 +836,7 @@ Options:
772
836
  --token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
773
837
  --profile <name> Local config profile name. Defaults to the saved default profile.
774
838
  --ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
775
- --json Emit stable JSON for coding agents.
839
+ --json Emit stable JSON for coding agents without printing Git passwords.
776
840
 
777
841
  Examples:
778
842
  tender app git remote artifact_123 --ttl 7d --json
@@ -798,11 +862,12 @@ Examples:
798
862
  tender app git setup artifact_123 --dir ./widget --dry-run --json`;
799
863
  }
800
864
  function gitCredentialHelp() {
801
- return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>]
865
+ return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>] [--json]
802
866
 
803
867
  This command is intended for Git's credential.helper protocol. It prints a
804
- Tender API-token username/password pair only for matching get requests. Do not
805
- run it manually unless you are debugging Git credential-helper behavior.
868
+ username/password pair only for matching get requests. Do not run it manually
869
+ unless you are debugging Git credential-helper behavior.
870
+ JSON output reports whether a password is available without printing it.
806
871
 
807
872
  Examples:
808
873
  tender app git credential artifact_123 get
@@ -984,6 +1049,8 @@ function tenderCliCapabilities() {
984
1049
  "tender playbooks --help",
985
1050
  "tender app --help",
986
1051
  "tender app agent heartbeat --help",
1052
+ "tender app share --help",
1053
+ "tender app claim --help",
987
1054
  "tender app analytics --help",
988
1055
  "tender app db --help",
989
1056
  "tender app bindings outbound --help",
@@ -1037,6 +1104,20 @@ function tenderCliCapabilities() {
1037
1104
  "Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
1038
1105
  ],
1039
1106
  },
1107
+ {
1108
+ name: "share_app_setup_handoff",
1109
+ when: "When handing off source code and reusable app setup between Tender accounts without copying data.",
1110
+ commands: [
1111
+ "tender app share create <artifact-id> --expires 7d --claims 1 --json",
1112
+ "tender app share inspect <token> --json",
1113
+ "tender app claim <token> --dir <dir> --confirm source-and-setup --json",
1114
+ ],
1115
+ notes: [
1116
+ "Share app setup copies source files, app setup, and analytics dashboard/chart definitions.",
1117
+ "It never copies customer data, analytics history, app database rows, secrets, release history, or source account access.",
1118
+ "Run claim without --confirm first when the user wants a preview of what will happen.",
1119
+ ],
1120
+ },
1040
1121
  {
1041
1122
  name: "local_edit_preview_publish",
1042
1123
  when: "When editing app source from a local checkout.",
@@ -1162,6 +1243,24 @@ function tenderCliCapabilities() {
1162
1243
  writes: true,
1163
1244
  dryRun: true,
1164
1245
  },
1246
+ {
1247
+ command: "tender app share create <artifact-id> --expires 7d --claims 1 --json",
1248
+ purpose: "Create a claimable app setup token that includes source/setup definitions but no app data.",
1249
+ requiresAuth: true,
1250
+ writes: true,
1251
+ },
1252
+ {
1253
+ command: "tender app share inspect <token> --json",
1254
+ purpose: "Inspect a shared app setup token before claiming it.",
1255
+ requiresAuth: true,
1256
+ writes: false,
1257
+ },
1258
+ {
1259
+ command: "tender app claim <token> --dir <dir> --confirm source-and-setup --json",
1260
+ purpose: "Create a new account-owned app from shared source/setup and materialize its source locally.",
1261
+ requiresAuth: true,
1262
+ writes: true,
1263
+ },
1165
1264
  {
1166
1265
  command: "tender app agent heartbeat <artifact-id> --source codex --status working --summary <text> --json",
1167
1266
  purpose: "Send a concise workspace feedback update from a coding agent.",
@@ -2417,6 +2516,128 @@ function parseArtifactsUpdateArgs(args) {
2417
2516
  json,
2418
2517
  };
2419
2518
  }
2519
+ function parseShareCommonArgs(args, startIndex) {
2520
+ let baseUrl = null;
2521
+ let flagToken = null;
2522
+ let profileName = null;
2523
+ let json = false;
2524
+ const remaining = [];
2525
+ for (let index = startIndex; index < args.length; index += 1) {
2526
+ const arg = args[index];
2527
+ if (arg === "--json") {
2528
+ json = true;
2529
+ continue;
2530
+ }
2531
+ if (arg === "--base-url") {
2532
+ baseUrl = parseBaseUrlFlag(args, index);
2533
+ index += 1;
2534
+ continue;
2535
+ }
2536
+ if (arg === "--token") {
2537
+ flagToken = parseTokenFlag(args, index);
2538
+ index += 1;
2539
+ continue;
2540
+ }
2541
+ if (arg === "--profile") {
2542
+ profileName = parseProfileName(args[index + 1], "--profile");
2543
+ index += 1;
2544
+ continue;
2545
+ }
2546
+ remaining.push(arg);
2547
+ }
2548
+ return { baseUrl, flagToken, profileName, json, remaining };
2549
+ }
2550
+ function parseShareCreateArgs(args) {
2551
+ if (args.includes("--help") || args.includes("-h")) {
2552
+ return { command: "help", topic: "app share create" };
2553
+ }
2554
+ const artifactId = args[0];
2555
+ if (!artifactId || artifactId.startsWith("-")) {
2556
+ throw new TenderCliUsageError("app id is required. Example: tender app share create <app-id> --json");
2557
+ }
2558
+ const parsed = parseShareCommonArgs(args, 1);
2559
+ let expires = "7d";
2560
+ let claims = 1;
2561
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
2562
+ const arg = parsed.remaining[index];
2563
+ if (arg === "--expires") {
2564
+ expires = parseRequiredFlagValue(parsed.remaining[index + 1], "--expires");
2565
+ index += 1;
2566
+ continue;
2567
+ }
2568
+ if (arg === "--claims") {
2569
+ claims = parsePositiveIntegerFlag(parsed.remaining[index + 1], "--claims");
2570
+ index += 1;
2571
+ continue;
2572
+ }
2573
+ throw new TenderCliUsageError(`Unknown app share create option: ${arg}`);
2574
+ }
2575
+ return { command: "app share create", artifactId, expires, claims, ...parsed };
2576
+ }
2577
+ function parseShareInspectArgs(args) {
2578
+ if (args.includes("--help") || args.includes("-h")) {
2579
+ return { command: "help", topic: "app share inspect" };
2580
+ }
2581
+ const token = args[0];
2582
+ if (!token || token.startsWith("-")) {
2583
+ throw new TenderCliUsageError("share token is required. Example: tender app share inspect <token> --json");
2584
+ }
2585
+ const parsed = parseShareCommonArgs(args, 1);
2586
+ if (parsed.remaining.length > 0) {
2587
+ throw new TenderCliUsageError(`Unknown app share inspect option: ${parsed.remaining[0]}`);
2588
+ }
2589
+ return { command: "app share inspect", token, ...parsed };
2590
+ }
2591
+ function parseShareRevokeArgs(args) {
2592
+ if (args.includes("--help") || args.includes("-h")) {
2593
+ return { command: "help", topic: "app share revoke" };
2594
+ }
2595
+ const shareId = args[0];
2596
+ if (!shareId || shareId.startsWith("-")) {
2597
+ throw new TenderCliUsageError("share id is required. Example: tender app share revoke <share-id> --json");
2598
+ }
2599
+ const parsed = parseShareCommonArgs(args, 1);
2600
+ if (parsed.remaining.length > 0) {
2601
+ throw new TenderCliUsageError(`Unknown app share revoke option: ${parsed.remaining[0]}`);
2602
+ }
2603
+ return { command: "app share revoke", shareId, ...parsed };
2604
+ }
2605
+ function parseClaimArgs(args) {
2606
+ if (args.includes("--help") || args.includes("-h")) {
2607
+ return { command: "help", topic: "app claim" };
2608
+ }
2609
+ const token = args[0];
2610
+ if (!token || token.startsWith("-")) {
2611
+ throw new TenderCliUsageError("share token is required. Example: tender app claim <token> --dir ./widget");
2612
+ }
2613
+ const parsed = parseShareCommonArgs(args, 1);
2614
+ let dir = null;
2615
+ let name = null;
2616
+ let confirm = null;
2617
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
2618
+ const arg = parsed.remaining[index];
2619
+ if (arg === "--dir") {
2620
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], "--dir");
2621
+ index += 1;
2622
+ continue;
2623
+ }
2624
+ if (arg === "--name") {
2625
+ name = parseRequiredFlagValue(parsed.remaining[index + 1], "--name");
2626
+ index += 1;
2627
+ continue;
2628
+ }
2629
+ if (arg === "--confirm") {
2630
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], "--confirm");
2631
+ index += 1;
2632
+ continue;
2633
+ }
2634
+ throw new TenderCliUsageError(`Unknown app claim option: ${arg}`);
2635
+ }
2636
+ if (!dir) {
2637
+ throw new TenderCliUsageError("--dir is required. Example: tender app claim tok_share_123 --dir ./widget");
2638
+ }
2639
+ return { command: "app claim", token, dir, name, confirm, ...parsed };
2640
+ }
2420
2641
  function parseArtifactsInitArgs(args) {
2421
2642
  if (args.includes("--help") || args.includes("-h")) {
2422
2643
  return { command: "help", topic: "app init" };
@@ -4429,6 +4650,27 @@ function parseTenderArgs(args) {
4429
4650
  if (resourceArgs[1] === "update") {
4430
4651
  return parseArtifactsUpdateArgs(resourceArgs.slice(2));
4431
4652
  }
4653
+ if (resourceArgs[1] === "share" &&
4654
+ (resourceArgs[2] === undefined ||
4655
+ resourceArgs[2] === "--help" ||
4656
+ resourceArgs[2] === "-h")) {
4657
+ return { command: "help", topic: "app share" };
4658
+ }
4659
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "create") {
4660
+ return parseShareCreateArgs(resourceArgs.slice(3));
4661
+ }
4662
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "inspect") {
4663
+ return parseShareInspectArgs(resourceArgs.slice(3));
4664
+ }
4665
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "revoke") {
4666
+ return parseShareRevokeArgs(resourceArgs.slice(3));
4667
+ }
4668
+ if (resourceArgs[1] === "share") {
4669
+ throw new TenderCliUsageError(`Unknown app share command: ${resourceArgs[2]}`);
4670
+ }
4671
+ if (resourceArgs[1] === "claim") {
4672
+ return parseClaimArgs(resourceArgs.slice(2));
4673
+ }
4432
4674
  if (resourceArgs[1] === "init") {
4433
4675
  return parseArtifactsInitArgs(resourceArgs.slice(2));
4434
4676
  }
@@ -6209,6 +6451,262 @@ async function runArtifactsUpdate(command, io, runtime) {
6209
6451
  }
6210
6452
  return 0;
6211
6453
  }
6454
+ async function runShareCreate(command, io, runtime) {
6455
+ const credentials = await resolveApiCredentials({
6456
+ baseUrl: command.baseUrl,
6457
+ flagToken: command.flagToken,
6458
+ profileName: command.profileName,
6459
+ env: runtime.env ?? process.env,
6460
+ });
6461
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/share`, {
6462
+ method: "POST",
6463
+ headers: {
6464
+ authorization: `Bearer ${credentials.token}`,
6465
+ "content-type": "application/json",
6466
+ },
6467
+ body: JSON.stringify({
6468
+ expiresIn: command.expires,
6469
+ claims: command.claims,
6470
+ }),
6471
+ });
6472
+ const payload = await parseJsonResponse(response);
6473
+ if (!response.ok || !payload?.ok || !payload.share || !payload.token) {
6474
+ throw new Error(payload?.message ??
6475
+ payload?.reasonCode ??
6476
+ `App setup share request failed with HTTP ${response.status}.`);
6477
+ }
6478
+ const result = {
6479
+ ok: true,
6480
+ command: command.command,
6481
+ baseUrl: credentials.baseUrl,
6482
+ authSource: credentials.source,
6483
+ profile: credentials.profileName,
6484
+ share: payload.share,
6485
+ token: payload.token,
6486
+ claimCommand: payload.claimCommand,
6487
+ };
6488
+ if (command.json) {
6489
+ io.stdout(JSON.stringify(result, null, 2));
6490
+ }
6491
+ else {
6492
+ io.stdout([
6493
+ `Created share token for "${payload.share.sourceAppName ?? command.artifactId}".`,
6494
+ "",
6495
+ "Includes:",
6496
+ "- Source code",
6497
+ "- App setup",
6498
+ "- Analytics dashboard and chart definitions",
6499
+ "",
6500
+ "Excludes:",
6501
+ "- Customer data",
6502
+ "- Analytics history",
6503
+ "- App database data",
6504
+ "- Secrets",
6505
+ "- Release history",
6506
+ "",
6507
+ "Share this command:",
6508
+ "",
6509
+ ` ${payload.claimCommand ?? `tender app claim ${payload.token} --dir .`}`,
6510
+ "",
6511
+ `Expires: ${payload.share.expiresAt ?? ""}`,
6512
+ `Claims remaining: ${payload.share.claimsRemaining ?? command.claims}`,
6513
+ ].join("\n"));
6514
+ }
6515
+ return 0;
6516
+ }
6517
+ async function runShareInspect(command, io, runtime) {
6518
+ const credentials = await resolveApiCredentials({
6519
+ baseUrl: command.baseUrl,
6520
+ flagToken: command.flagToken,
6521
+ profileName: command.profileName,
6522
+ env: runtime.env ?? process.env,
6523
+ });
6524
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}`, {
6525
+ headers: {
6526
+ authorization: `Bearer ${credentials.token}`,
6527
+ },
6528
+ });
6529
+ const payload = await parseJsonResponse(response);
6530
+ if (!response.ok || !payload?.ok || !payload.share) {
6531
+ throw new Error(payload?.message ??
6532
+ payload?.reasonCode ??
6533
+ `App setup share inspect failed with HTTP ${response.status}.`);
6534
+ }
6535
+ if (command.json) {
6536
+ io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
6537
+ }
6538
+ else {
6539
+ io.stdout([
6540
+ `App: ${payload.share.sourceAppName ?? payload.share.sourceArtifactId ?? "Shared app"}`,
6541
+ `Expires: ${payload.share.expiresAt ?? ""}`,
6542
+ `Claims remaining: ${payload.share.claimsRemaining ?? 0}`,
6543
+ `Claimable: ${payload.claimable ? "yes" : "no"}`,
6544
+ ].join("\n"));
6545
+ }
6546
+ return 0;
6547
+ }
6548
+ async function runShareRevoke(command, io, runtime) {
6549
+ const credentials = await resolveApiCredentials({
6550
+ baseUrl: command.baseUrl,
6551
+ flagToken: command.flagToken,
6552
+ profileName: command.profileName,
6553
+ env: runtime.env ?? process.env,
6554
+ });
6555
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.shareId)}/revoke`, {
6556
+ method: "POST",
6557
+ headers: {
6558
+ authorization: `Bearer ${credentials.token}`,
6559
+ },
6560
+ });
6561
+ const payload = await parseJsonResponse(response);
6562
+ if (!response.ok || !payload?.ok || !payload.share) {
6563
+ throw new Error(payload?.message ??
6564
+ payload?.reasonCode ??
6565
+ `App setup share revoke failed with HTTP ${response.status}.`);
6566
+ }
6567
+ if (command.json) {
6568
+ io.stdout(JSON.stringify({ ok: true, command: command.command, share: payload.share }, null, 2));
6569
+ }
6570
+ else {
6571
+ io.stdout(`revoked: ${payload.share.id}`);
6572
+ }
6573
+ return 0;
6574
+ }
6575
+ async function runClaim(command, io, runtime) {
6576
+ const credentials = await resolveApiCredentials({
6577
+ baseUrl: command.baseUrl,
6578
+ flagToken: command.flagToken,
6579
+ profileName: command.profileName,
6580
+ env: runtime.env ?? process.env,
6581
+ });
6582
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}/claim`, {
6583
+ method: "POST",
6584
+ headers: {
6585
+ authorization: `Bearer ${credentials.token}`,
6586
+ "content-type": "application/json",
6587
+ },
6588
+ body: JSON.stringify({
6589
+ name: command.name,
6590
+ confirm: command.confirm,
6591
+ }),
6592
+ });
6593
+ const payload = await parseJsonResponse(response);
6594
+ if (!response.ok || !payload?.ok) {
6595
+ throw new Error(payload?.message ??
6596
+ payload?.reasonCode ??
6597
+ `App setup claim failed with HTTP ${response.status}.`);
6598
+ }
6599
+ if (payload.requiresConfirmation) {
6600
+ if (command.json) {
6601
+ io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
6602
+ }
6603
+ else {
6604
+ io.stdout([
6605
+ `Claim "${payload.share?.sourceAppName ?? "shared app setup"}"?`,
6606
+ "",
6607
+ "This creates a new app owned by the current Tender account.",
6608
+ "",
6609
+ "Will copy:",
6610
+ "- Source code",
6611
+ "- App setup",
6612
+ "- Analytics dashboard and chart definitions",
6613
+ "",
6614
+ "Will not copy:",
6615
+ "- Customer data",
6616
+ "- Analytics history",
6617
+ "- App database data",
6618
+ "- Secrets",
6619
+ "- Release history",
6620
+ "",
6621
+ `Continue with: tender app claim ${command.token} --dir ${command.dir} --confirm source-and-setup`,
6622
+ ].join("\n"));
6623
+ }
6624
+ return 0;
6625
+ }
6626
+ if (!payload.artifact) {
6627
+ throw new Error("App setup claim response did not include a destination app.");
6628
+ }
6629
+ const root = path.resolve(command.dir);
6630
+ await mkdir(root, { recursive: true });
6631
+ const plannedFiles = await planSourceFiles({
6632
+ root,
6633
+ files: payload.sourceFiles ?? [],
6634
+ force: false,
6635
+ });
6636
+ const conflicts = plannedFiles.filter((file) => file.action === "conflict");
6637
+ if (conflicts.length > 0) {
6638
+ throw new TenderCliUsageError(`Claim target has ${conflicts.length} conflicting files. Choose an empty --dir or resolve: ${conflicts.map((file) => file.path).join(", ")}`);
6639
+ }
6640
+ for (const file of plannedFiles) {
6641
+ if (file.action !== "create" && file.action !== "overwrite") {
6642
+ continue;
6643
+ }
6644
+ await mkdir(path.dirname(file.absolutePath), { recursive: true });
6645
+ await writeFile(file.absolutePath, file.content);
6646
+ }
6647
+ const gitStdout = [];
6648
+ await runGitSetup({
6649
+ command: "app git setup",
6650
+ artifactId: payload.artifact.artifactId,
6651
+ dir: root,
6652
+ remoteName: "tender",
6653
+ baseUrl: credentials.baseUrl,
6654
+ flagToken: command.flagToken,
6655
+ profileName: credentials.profileName,
6656
+ ttl: "7d",
6657
+ dryRun: false,
6658
+ json: true,
6659
+ }, {
6660
+ stdout: (message) => gitStdout.push(message),
6661
+ stderr: io.stderr,
6662
+ }, runtime);
6663
+ const result = {
6664
+ ok: true,
6665
+ command: command.command,
6666
+ baseUrl: credentials.baseUrl,
6667
+ authSource: credentials.source,
6668
+ profile: credentials.profileName,
6669
+ appId: payload.artifact.artifactId,
6670
+ artifact: payload.artifact,
6671
+ dir: root,
6672
+ copied: payload.copied,
6673
+ notCopied: payload.notCopied,
6674
+ files: plannedFiles.map((file) => ({ path: file.path, action: file.action })),
6675
+ git: gitStdout.join("\n").trim() ? JSON.parse(gitStdout.join("\n")) : null,
6676
+ nextCommands: payload.nextCommands,
6677
+ };
6678
+ if (command.json) {
6679
+ io.stdout(JSON.stringify(result, null, 2));
6680
+ }
6681
+ else {
6682
+ io.stdout([
6683
+ "Claimed shared app setup.",
6684
+ "",
6685
+ "Created:",
6686
+ ` App: ${payload.artifact.name}`,
6687
+ ` ID: ${payload.artifact.artifactId}`,
6688
+ ` Local checkout: ${root}`,
6689
+ "",
6690
+ "Copied:",
6691
+ ` Source files: ${payload.copied?.sourceFiles ?? plannedFiles.length}`,
6692
+ ` Analytics dashboards: ${payload.copied?.analyticsDashboards ?? 0}`,
6693
+ ` Analytics charts: ${payload.copied?.analyticsCharts ?? 0}`,
6694
+ "",
6695
+ "Not copied:",
6696
+ " Usage data",
6697
+ " App database rows",
6698
+ " Secrets",
6699
+ " Release history",
6700
+ "",
6701
+ "Next:",
6702
+ ` cd ${command.dir}`,
6703
+ ` tender app doctor --dir .`,
6704
+ ` git push tender main`,
6705
+ ` tender app preview ${payload.artifact.artifactId} --stream`,
6706
+ ].join("\n"));
6707
+ }
6708
+ return 0;
6709
+ }
6212
6710
  function defaultCommandRunner(command, args, options) {
6213
6711
  return new Promise((resolve, reject) => {
6214
6712
  const child = spawn(command, args, {
@@ -6489,7 +6987,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
6489
6987
  };
6490
6988
  }
6491
6989
  async function requestGitRemoteLease(input) {
6492
- const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?ttl=${encodeURIComponent(input.ttl)}`;
6990
+ const params = new URLSearchParams({ ttl: input.ttl });
6991
+ if (input.includeCredential) {
6992
+ params.set("credential", "1");
6993
+ }
6994
+ const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?${params.toString()}`;
6493
6995
  const response = await input.fetcher(url, {
6494
6996
  headers: {
6495
6997
  authorization: `Bearer ${input.token}`,
@@ -6652,28 +7154,41 @@ async function runGitCredential(command, io, runtime) {
6652
7154
  baseUrl: credentials.baseUrl,
6653
7155
  token: credentials.token,
6654
7156
  fetcher: runtime.fetcher ?? fetch,
7157
+ includeCredential: true,
6655
7158
  });
7159
+ const credentialPassword = git.token?.password ?? null;
7160
+ const credentialUsername = git.token?.username ?? git.authentication?.username ?? "tender";
7161
+ if (!credentialPassword) {
7162
+ if (command.json) {
7163
+ io.stdout(JSON.stringify({
7164
+ ok: false,
7165
+ command: command.command,
7166
+ artifactId: command.artifactId,
7167
+ remoteUrl: git.remoteUrl,
7168
+ reasonCode: "git_credential_unavailable",
7169
+ passwordAvailable: false,
7170
+ }, null, 2));
7171
+ }
7172
+ else {
7173
+ io.stderr("Tender Git credential is unavailable. Refresh the Git setup so the helper can request a scoped Git credential.");
7174
+ }
7175
+ return 1;
7176
+ }
6656
7177
  if (command.json) {
6657
7178
  io.stdout(JSON.stringify({
6658
7179
  ok: true,
6659
7180
  command: command.command,
6660
7181
  artifactId: command.artifactId,
6661
7182
  remoteUrl: git.remoteUrl,
6662
- username: git.token?.username ?? git.authentication?.username ?? "tender",
6663
- password: git.token?.password ??
6664
- (git.authentication?.mode === "tender-api-token"
6665
- ? credentials.token
6666
- : ""),
7183
+ username: credentialUsername,
7184
+ passwordAvailable: true,
6667
7185
  expiresAt: git.token?.expiresAt ?? null,
6668
7186
  }, null, 2));
6669
7187
  }
6670
7188
  else {
6671
7189
  io.stdout([
6672
- `username=${git.token?.username ?? git.authentication?.username ?? "tender"}`,
6673
- `password=${git.token?.password ??
6674
- (git.authentication?.mode === "tender-api-token"
6675
- ? credentials.token
6676
- : "")}`,
7190
+ `username=${credentialUsername}`,
7191
+ `password=${credentialPassword}`,
6677
7192
  ].join("\n"));
6678
7193
  }
6679
7194
  return 0;
@@ -7606,7 +8121,7 @@ async function runGitRemote(command, io, runtime) {
7606
8121
  scope: git.scope,
7607
8122
  authentication: git.authentication?.mode ?? "artifacts-token",
7608
8123
  username: git.token?.username ?? git.authentication?.username ?? "tender",
7609
- password: git.token?.password ?? null,
8124
+ passwordAvailable: Boolean(git.token?.password),
7610
8125
  expiresAt: git.token?.expiresAt ?? null,
7611
8126
  };
7612
8127
  if (command.json) {
@@ -7618,10 +8133,8 @@ async function runGitRemote(command, io, runtime) {
7618
8133
  `default_branch: ${result.defaultBranch}`,
7619
8134
  `authentication: ${result.authentication}`,
7620
8135
  `username: ${result.username}`,
8136
+ `password_available: ${result.passwordAvailable ? "true" : "false"}`,
7621
8137
  ];
7622
- if (result.password) {
7623
- lines.push(`password: ${result.password}`);
7624
- }
7625
8138
  if (result.expiresAt) {
7626
8139
  lines.push(`expires_at: ${result.expiresAt}`);
7627
8140
  }
@@ -8382,6 +8895,21 @@ export async function runTenderCli(args, io = {
8382
8895
  else if (command.topic === "app update") {
8383
8896
  io.stdout(artifactsUpdateHelp());
8384
8897
  }
8898
+ else if (command.topic === "app share") {
8899
+ io.stdout(appShareHelp());
8900
+ }
8901
+ else if (command.topic === "app share create") {
8902
+ io.stdout(appShareCreateHelp());
8903
+ }
8904
+ else if (command.topic === "app share inspect") {
8905
+ io.stdout(appShareInspectHelp());
8906
+ }
8907
+ else if (command.topic === "app share revoke") {
8908
+ io.stdout(appShareRevokeHelp());
8909
+ }
8910
+ else if (command.topic === "app claim") {
8911
+ io.stdout(appClaimHelp());
8912
+ }
8385
8913
  else if (command.topic === "app init") {
8386
8914
  io.stdout(artifactsInitHelp());
8387
8915
  }
@@ -8600,6 +9128,18 @@ export async function runTenderCli(args, io = {
8600
9128
  if (command.command === "app analytics query") {
8601
9129
  return await runAnalyticsQuery(command, io, runtime);
8602
9130
  }
9131
+ if (command.command === "app share create") {
9132
+ return await runShareCreate(command, io, runtime);
9133
+ }
9134
+ if (command.command === "app share inspect") {
9135
+ return await runShareInspect(command, io, runtime);
9136
+ }
9137
+ if (command.command === "app share revoke") {
9138
+ return await runShareRevoke(command, io, runtime);
9139
+ }
9140
+ if (command.command === "app claim") {
9141
+ return await runClaim(command, io, runtime);
9142
+ }
8603
9143
  if (command.command === "app analytics capabilities") {
8604
9144
  return await runAnalyticsCapabilities(command, io, runtime);
8605
9145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"