@tenderprompt/cli 0.1.5 → 0.1.7

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 +102 -2
  2. package/dist/index.js +419 -1
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -16,15 +16,41 @@ npm install -g @tenderprompt/cli
16
16
  Then run:
17
17
 
18
18
  ```bash
19
- tender --help
19
+ tender capabilities --json
20
20
  ```
21
21
 
22
+ ## What You Can Do
23
+
24
+ Use the CLI to work with Tender Apps from a local checkout or coding-agent
25
+ session:
26
+
27
+ - sign in with a bounded device token and optional artifact scope
28
+ - list, create, and rename apps
29
+ - bootstrap a local app checkout with Tender project context
30
+ - configure the Tender artifact Git remote and credential helper
31
+ - discover the current CLI command surface with `tender capabilities --json`
32
+ - preview outbound HTTP binding files without writing them
33
+ - validate app source locally before pushing
34
+ - build and preview a commit through the Tender API
35
+ - publish only after a dry run or explicit `--confirm publish`
36
+ - inspect artifact-scoped analytics without SQL or platform credentials
37
+ - run server-validated chart specs and export aggregate rows
38
+ - generate starter analytics suggestions
39
+ - create saved analytics dashboards and charts, with `--dry-run` available for
40
+ write previews
41
+
42
+ The CLI is deterministic by design: every workflow has a non-interactive flag
43
+ path, supports `--json` where automation needs structured output, and avoids
44
+ interactive prompts unless the command is explicitly an auth flow.
45
+
22
46
  ## Typical Flow
23
47
 
24
48
  ```bash
49
+ tender capabilities --json
50
+
25
51
  tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
26
52
 
27
- tender app git setup artifact_123 --dir ./widget
53
+ tender app init artifact_123 --dir ./widget --json
28
54
 
29
55
  cd ./widget
30
56
  npm run check
@@ -35,6 +61,16 @@ tender app preview artifact_123 --commit "$(git rev-parse HEAD)" --stream --json
35
61
  tender app publish artifact_123 --commit "$(git rev-parse HEAD)" --confirm publish --stream --json
36
62
  ```
37
63
 
64
+ For agents, keep the pattern simple:
65
+
66
+ 1. start with `tender capabilities --json`
67
+ 2. use `tender app init` to clone/bootstrap an existing app checkout
68
+ 3. inspect and edit files locally
69
+ 4. run `tender app doctor` and local project checks
70
+ 5. push source with Git
71
+ 6. call `preview`, `publish --dry-run`, then `publish --confirm publish` only
72
+ when the user asked to publish
73
+
38
74
  ## Auth
39
75
 
40
76
  Use the device login flow to mint a local token:
@@ -71,10 +107,12 @@ indexing the accepted commit after upstream Git accepts the push.
71
107
  ## Useful Commands
72
108
 
73
109
  ```bash
110
+ tender capabilities --json
74
111
  tender app list --json
75
112
  tender app create --name "Exit Intent Widget" --json
76
113
  tender app update artifact_123 --name "Sale Widget" --json
77
114
  tender app init artifact_123 --dir ./widget --json
115
+ tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
78
116
  tender app context status artifact_123 --dir ./widget --json
79
117
  tender app doctor --dir ./widget --json
80
118
  tender app validate artifact_123 --json
@@ -83,6 +121,64 @@ tender app preview artifact_123 --commit "$(git rev-parse HEAD)" --stream --json
83
121
  tender app publish artifact_123 --dry-run --json
84
122
  ```
85
123
 
124
+ ## Analytics
125
+
126
+ Analytics commands are artifact-scoped and go through Tender's server-side
127
+ validation. The CLI never exposes raw query credentials, raw SQL, or cross-app
128
+ data.
129
+
130
+ Start with capabilities. This is the self-documenting entrypoint for coding
131
+ agents:
132
+
133
+ ```bash
134
+ tender app analytics capabilities artifact_123 --include-catalog --range 30d --json
135
+ ```
136
+
137
+ That response tells the agent:
138
+
139
+ - which analytics commands are supported
140
+ - the safe chart-spec shape
141
+ - retention, range, quota, and security limits
142
+ - existing saved dashboards and charts
143
+ - bounded observed event names, request paths, property keys, rough types, and
144
+ counts from artifact-scoped catalog samples
145
+
146
+ Common read-only commands:
147
+
148
+ ```bash
149
+ tender app analytics summary artifact_123 --range 30d --json
150
+
151
+ tender app analytics query artifact_123 --spec chart.json --json
152
+
153
+ tender app analytics query artifact_123 --spec - --json < chart.json
154
+
155
+ tender app analytics export catalog artifact_123 --range 30d --json
156
+
157
+ tender app analytics export query artifact_123 --spec chart.json --format csv
158
+
159
+ tender app analytics suggestions artifact_123 --range 30d --json
160
+ ```
161
+
162
+ Safe write previews:
163
+
164
+ ```bash
165
+ tender app analytics dashboards artifact_123 --create "Agent dashboard" --dry-run --json
166
+
167
+ tender app analytics charts create artifact_123 \
168
+ --dashboard analytics_dashboard_123 \
169
+ --title "Signup funnel" \
170
+ --spec funnel.json \
171
+ --dry-run \
172
+ --json
173
+ ```
174
+
175
+ Real saved-dashboard writes use the same commands without `--dry-run`.
176
+
177
+ Agents should combine `capabilities --include-catalog` with local source-code
178
+ inspection. For example, inspect where the app calls
179
+ `__TP_ANALYTICS.invoke(...)`, propose a chart spec, validate it with
180
+ `analytics query`, and only then create a saved chart.
181
+
86
182
  ## Safety Model
87
183
 
88
184
  - Local agents run in the user's environment, not in Tender.
@@ -93,6 +189,10 @@ tender app publish artifact_123 --dry-run --json
93
189
  - `tender app publish` requires `--dry-run` or `--confirm publish`.
94
190
  - Tokens are stored outside the project checkout and are not written into Git
95
191
  remote URLs.
192
+ - Analytics commands use tenant/artifact-scoped API routes. They do not expose
193
+ raw query credentials or raw SQL.
194
+ - Raw analytics event export is intentionally not available; exports are bounded
195
+ catalog metadata or validated aggregate query rows.
96
196
 
97
197
  ## Related Package
98
198
 
package/dist/index.js CHANGED
@@ -20,6 +20,8 @@ function rootHelp() {
20
20
  return `Usage: tender <command> [options]
21
21
 
22
22
  Commands:
23
+ tender capabilities --json
24
+ Describe the CLI command surface for coding agents
23
25
  tender auth login --device
24
26
  Authorize this machine with a browser-approved device flow
25
27
  tender auth status Show whether a Tender API token is available
@@ -58,6 +60,8 @@ Commands:
58
60
  Report whether managed context is stale
59
61
  tender app context refresh <app-id>
60
62
  Refresh managed context files
63
+ tender app bindings outbound --id <id> --host <host> --dry-run
64
+ Preview files for an outbound HTTP binding
61
65
  tender app git setup <app-id>
62
66
  Configure a checkout to push to the app remote
63
67
  tender app doctor Validate a local Tender App checkout
@@ -68,6 +72,7 @@ Compatibility:
68
72
  tender artifacts ... Legacy alias for Tender App commands
69
73
 
70
74
  Examples:
75
+ tender capabilities --json
71
76
  tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
72
77
  tender auth status --json
73
78
  tender app list --json
@@ -86,6 +91,7 @@ Examples:
86
91
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
87
92
  tender app context status artifact_123 --dir ./widget --json
88
93
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
94
+ tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
89
95
  tender app git setup artifact_123 --dir ./widget --json
90
96
  tender app doctor --dir ./widget
91
97
  tender app generate-env-types --dir ./widget --json
@@ -175,6 +181,8 @@ Commands:
175
181
  Show managed context freshness for a checkout
176
182
  tender app context refresh <app-id>
177
183
  Refresh managed context files in a checkout
184
+ tender app bindings outbound --id <id> --host <host> --dry-run
185
+ Preview files for an outbound HTTP binding
178
186
  tender app git setup <app-id>
179
187
  Configure the tender Git remote and credential helper
180
188
  tender app doctor Validate local context, TypeScript scaffold, and Tender App rules
@@ -205,6 +213,7 @@ Examples:
205
213
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
206
214
  tender app context status artifact_123 --dir ./widget --json
207
215
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
216
+ tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
208
217
  tender app git setup artifact_123 --dir ./widget --json
209
218
  tender app doctor --dir ./widget
210
219
  tender app generate-env-types --dir ./widget --json
@@ -217,6 +226,15 @@ function artifactsHelp() {
217
226
  Legacy alias:
218
227
  tender artifacts ... continues to work for existing automation. Prefer tender app ... for new workflows.`;
219
228
  }
229
+ function capabilitiesHelp() {
230
+ return `Usage: tender capabilities [--json]
231
+
232
+ Describes the Tender CLI surface for coding agents. Use this first instead of
233
+ copying long command lists into skill prompts.
234
+
235
+ Examples:
236
+ tender capabilities --json`;
237
+ }
220
238
  function analyticsHelp() {
221
239
  return `Usage: tender app analytics <command> <app-id> [options]
222
240
 
@@ -441,6 +459,26 @@ Examples:
441
459
  tender app generate-env-types --dir ./widget
442
460
  tender app generate-env-types --dir ./widget --json`;
443
461
  }
462
+ function bindingsOutboundHelp() {
463
+ return `Usage: tender app bindings outbound --id <id> --host <host> --dry-run [--name <ENV_NAME>] [--label <label>] [--interface <TypeName>] [--json]
464
+
465
+ Preview the file shape for an outbound HTTP binding. App server code cannot call
466
+ third-party internet URLs directly; external APIs, OAuth/token exchanges,
467
+ webhooks, and secrets must go through bindings.
468
+
469
+ Options:
470
+ --id <id> Binding id and directory name, for example shopify-api.
471
+ --host <host> Explicit lowercase host without protocol, wildcard, path, query, or port.
472
+ --name <ENV_NAME> App env binding name. Defaults to the id converted to UPPER_SNAKE.
473
+ --label <label> Human label for app.json. Defaults to a title-cased id.
474
+ --interface <TypeName> TypeScript interface and worker class name. Defaults to a PascalCase id plus Binding.
475
+ --dry-run Required. Prints files and app.json binding entry without writing.
476
+ --json Emit stable JSON for coding agents.
477
+
478
+ Examples:
479
+ tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
480
+ tender app bindings outbound --id yotpo-api --host api.yotpo.com --name YOTPO --label "Yotpo API" --dry-run --json`;
481
+ }
444
482
  function gitRemoteHelp() {
445
483
  return `Usage: tender app git remote <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--json]
446
484
 
@@ -605,6 +643,149 @@ Options:
605
643
  Examples:
606
644
  tender app publish watch artifact_123 --job job_123 --stream --json`;
607
645
  }
646
+ function tenderCliCapabilities() {
647
+ return {
648
+ ok: true,
649
+ surface: "tender_cli",
650
+ description: "Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.",
651
+ discovery: {
652
+ primary: "tender capabilities --json",
653
+ layeredHelp: [
654
+ "tender --help",
655
+ "tender auth --help",
656
+ "tender app --help",
657
+ "tender app analytics --help",
658
+ "tender app bindings outbound --help",
659
+ ],
660
+ },
661
+ workflows: [
662
+ {
663
+ name: "authenticate",
664
+ when: "Before reading or changing remote Tender App records.",
665
+ commands: [
666
+ "tender auth status --json",
667
+ "tender auth login --device --profile edit-preview --artifact <artifact-id> --ttl 7d --json",
668
+ ],
669
+ },
670
+ {
671
+ name: "clone_or_bootstrap_existing_app",
672
+ when: "When the user wants to download or work on an existing app locally.",
673
+ commands: [
674
+ "tender app init <artifact-id> --dir <dir> --dry-run --json",
675
+ "tender app init <artifact-id> --dir <dir> --json",
676
+ ],
677
+ notes: [
678
+ "init is the normal clone/bootstrap command.",
679
+ "If the app already has artifact Git source, init fetches it and configures the tender remote.",
680
+ "If no artifact Git source exists yet, init writes managed context/scaffold files and configures the tender remote.",
681
+ "Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
682
+ ],
683
+ },
684
+ {
685
+ name: "local_edit_preview_publish",
686
+ when: "When editing app source from a local checkout.",
687
+ commands: [
688
+ "tender app doctor --dir <dir> --json",
689
+ "tender app git setup <artifact-id> --dir <dir> --dry-run --json",
690
+ "git push tender main",
691
+ "tender app preview <artifact-id> --commit $(git rev-parse HEAD) --stream --json",
692
+ "tender app publish <artifact-id> --dry-run --json",
693
+ "tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json",
694
+ ],
695
+ notes: [
696
+ "Publishing is production-facing and requires explicit user intent.",
697
+ ],
698
+ },
699
+ {
700
+ name: "analytics_agent_loop",
701
+ when: "When understanding app analytics or creating saved charts.",
702
+ commands: [
703
+ "tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json",
704
+ "tender app analytics query <artifact-id> --spec chart.json --json",
705
+ "tender app analytics export query <artifact-id> --spec chart.json --format csv",
706
+ "tender app analytics dashboards <artifact-id> --create \"Agent dashboard\" --dry-run --json",
707
+ "tender app analytics charts create <artifact-id> --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json",
708
+ ],
709
+ notes: [
710
+ "Combine CLI capabilities with local source inspection of analytics calls.",
711
+ "Validate chart specs with query before saving charts.",
712
+ ],
713
+ },
714
+ {
715
+ name: "outbound_http_binding",
716
+ when: "When app code needs external APIs, OAuth/token exchange, webhooks, provider SDKs, or secrets.",
717
+ commands: [
718
+ "tender app bindings outbound --id <id> --host <host> --dry-run --json",
719
+ "tender app generate-env-types --dir <dir> --json",
720
+ "tender app doctor --dir <dir> --json",
721
+ ],
722
+ notes: [
723
+ "src/server.ts cannot call third-party internet URLs directly.",
724
+ "Outbound calls are blocked unless they go through a binding with outbound:http and explicit allowedHosts.",
725
+ ],
726
+ },
727
+ ],
728
+ commands: [
729
+ {
730
+ command: "tender capabilities --json",
731
+ purpose: "Discover this CLI surface in a machine-readable shape.",
732
+ requiresAuth: false,
733
+ writes: false,
734
+ },
735
+ {
736
+ command: "tender app init <artifact-id> --dir <dir> --json",
737
+ purpose: "Clone/bootstrap an existing Tender App checkout and configure context plus the tender Git remote.",
738
+ requiresAuth: true,
739
+ writes: true,
740
+ dryRun: true,
741
+ },
742
+ {
743
+ command: "tender app context fetch <artifact-id> --dir <dir> --json",
744
+ purpose: "Fetch managed AGENTS.md, skills, and Tender context into an existing checkout.",
745
+ requiresAuth: true,
746
+ writes: true,
747
+ dryRun: true,
748
+ },
749
+ {
750
+ command: "tender app bindings outbound --id <id> --host <host> --dry-run --json",
751
+ purpose: "Preview app.json and binding files for an outbound HTTP integration.",
752
+ requiresAuth: false,
753
+ writes: false,
754
+ },
755
+ {
756
+ command: "tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json",
757
+ purpose: "Discover artifact-scoped analytics commands, chart specs, saved dashboards, and bounded observed catalog.",
758
+ requiresAuth: true,
759
+ writes: false,
760
+ },
761
+ ],
762
+ security: [
763
+ "Prefer artifact-scoped device tokens.",
764
+ "Do not write Tender tokens into the project checkout.",
765
+ "Do not call Tender HTTP APIs directly from agent workflows.",
766
+ "Do not put third-party fetch calls in src/server.ts; use outbound bindings.",
767
+ "Do not request direct query credentials or raw SQL for analytics.",
768
+ ],
769
+ };
770
+ }
771
+ function runCapabilities(command, io) {
772
+ const payload = tenderCliCapabilities();
773
+ if (command.json) {
774
+ io.stdout(JSON.stringify(payload, null, 2));
775
+ }
776
+ else {
777
+ io.stdout([
778
+ "Tender CLI capabilities",
779
+ "",
780
+ "Start with:",
781
+ " tender capabilities --json",
782
+ "",
783
+ "Useful next help topics:",
784
+ ...payload.discovery.layeredHelp.map((topic) => ` ${topic}`),
785
+ ].join("\n"));
786
+ }
787
+ return 0;
788
+ }
608
789
  function normalizeBaseUrl(value) {
609
790
  return (value ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
610
791
  }
@@ -2071,6 +2252,127 @@ function parseRequiredFlagValue(value, flag) {
2071
2252
  }
2072
2253
  return value;
2073
2254
  }
2255
+ function parseCapabilitiesArgs(args) {
2256
+ if (args.includes("--help") || args.includes("-h")) {
2257
+ return { command: "help", topic: "capabilities" };
2258
+ }
2259
+ let json = false;
2260
+ for (const arg of args) {
2261
+ if (arg === "--json") {
2262
+ json = true;
2263
+ continue;
2264
+ }
2265
+ throw new TenderCliUsageError(`Unknown capabilities option: ${arg}`);
2266
+ }
2267
+ return { command: "capabilities", json };
2268
+ }
2269
+ function parseBindingId(value) {
2270
+ if (!value || value.startsWith("-")) {
2271
+ throw new TenderCliUsageError("--id requires a binding id.");
2272
+ }
2273
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
2274
+ throw new TenderCliUsageError("--id must start with a lowercase letter and contain only lowercase letters, numbers, and dashes. Example: shopify-api");
2275
+ }
2276
+ return value;
2277
+ }
2278
+ function parseAllowedHost(value) {
2279
+ if (!value || value.startsWith("-")) {
2280
+ throw new TenderCliUsageError("--host requires a hostname.");
2281
+ }
2282
+ if (value.length === 0 ||
2283
+ value.includes("/") ||
2284
+ value.includes(":") ||
2285
+ value.includes("?") ||
2286
+ value.includes("#") ||
2287
+ value.includes("*") ||
2288
+ value !== value.toLowerCase()) {
2289
+ throw new TenderCliUsageError("--host must be an explicit lowercase hostname without protocol, wildcard, path, query, or port. Example: api.example.com");
2290
+ }
2291
+ return value;
2292
+ }
2293
+ function parseEnvBindingName(value) {
2294
+ if (!value || value.startsWith("-")) {
2295
+ throw new TenderCliUsageError("--name requires an env binding name.");
2296
+ }
2297
+ if (!/^[A-Z][A-Z0-9_]*$/.test(value)) {
2298
+ throw new TenderCliUsageError("--name must be UPPER_SNAKE_CASE. Example: SHOPIFY_API");
2299
+ }
2300
+ return value;
2301
+ }
2302
+ function parseTypeName(value, flag) {
2303
+ if (!value || value.startsWith("-")) {
2304
+ throw new TenderCliUsageError(`${flag} requires a TypeScript type name.`);
2305
+ }
2306
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(value)) {
2307
+ throw new TenderCliUsageError(`${flag} must be a PascalCase TypeScript type name. Example: ShopifyApiBinding`);
2308
+ }
2309
+ return value;
2310
+ }
2311
+ function parseBindingsOutboundArgs(args) {
2312
+ if (args.includes("--help") || args.includes("-h")) {
2313
+ return { command: "help", topic: "artifacts bindings outbound" };
2314
+ }
2315
+ let id = null;
2316
+ let host = null;
2317
+ let name = null;
2318
+ let label = null;
2319
+ let interfaceName = null;
2320
+ let dryRun = false;
2321
+ let json = false;
2322
+ for (let index = 0; index < args.length; index += 1) {
2323
+ const arg = args[index];
2324
+ if (arg === "--json") {
2325
+ json = true;
2326
+ continue;
2327
+ }
2328
+ if (arg === "--dry-run") {
2329
+ dryRun = true;
2330
+ continue;
2331
+ }
2332
+ if (arg === "--id") {
2333
+ id = parseBindingId(args[index + 1]);
2334
+ index += 1;
2335
+ continue;
2336
+ }
2337
+ if (arg === "--host") {
2338
+ host = parseAllowedHost(args[index + 1]);
2339
+ index += 1;
2340
+ continue;
2341
+ }
2342
+ if (arg === "--name") {
2343
+ name = parseEnvBindingName(args[index + 1]);
2344
+ index += 1;
2345
+ continue;
2346
+ }
2347
+ if (arg === "--label") {
2348
+ label = parseRequiredFlagValue(args[index + 1], "--label");
2349
+ index += 1;
2350
+ continue;
2351
+ }
2352
+ if (arg === "--interface") {
2353
+ interfaceName = parseTypeName(args[index + 1], "--interface");
2354
+ index += 1;
2355
+ continue;
2356
+ }
2357
+ throw new TenderCliUsageError(`Unknown bindings outbound option: ${arg}`);
2358
+ }
2359
+ if (!id || !host) {
2360
+ throw new TenderCliUsageError("--id and --host are required. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json");
2361
+ }
2362
+ if (!dryRun) {
2363
+ throw new TenderCliUsageError("bindings outbound currently requires --dry-run. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json");
2364
+ }
2365
+ return {
2366
+ command: "artifacts bindings outbound",
2367
+ id,
2368
+ host,
2369
+ name,
2370
+ label,
2371
+ interfaceName,
2372
+ dryRun,
2373
+ json,
2374
+ };
2375
+ }
2074
2376
  function parseAnalyticsCommonArgs(args, startIndex) {
2075
2377
  let baseUrl = null;
2076
2378
  let flagToken = null;
@@ -2359,6 +2661,9 @@ function parseTenderArgs(args) {
2359
2661
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
2360
2662
  return { command: "help", topic: "root" };
2361
2663
  }
2664
+ if (args[0] === "capabilities") {
2665
+ return parseCapabilitiesArgs(args.slice(1));
2666
+ }
2362
2667
  if (args[0] === "auth") {
2363
2668
  if (args[1] === undefined || args[1] === "--help" || args[1] === "-h") {
2364
2669
  return { command: "help", topic: "auth" };
@@ -2406,6 +2711,15 @@ function parseTenderArgs(args) {
2406
2711
  if (resourceArgs[1] === "context" && resourceArgs[2] === "refresh") {
2407
2712
  return parseContextRefreshArgs(resourceArgs.slice(3));
2408
2713
  }
2714
+ if (resourceArgs[1] === "bindings" && resourceArgs[2] === "outbound") {
2715
+ return parseBindingsOutboundArgs(resourceArgs.slice(3));
2716
+ }
2717
+ if (resourceArgs[1] === "bindings" &&
2718
+ (resourceArgs[2] === undefined ||
2719
+ resourceArgs[2] === "--help" ||
2720
+ resourceArgs[2] === "-h")) {
2721
+ return { command: "help", topic: "artifacts bindings outbound" };
2722
+ }
2409
2723
  if (resourceArgs[1] === "analytics" &&
2410
2724
  (resourceArgs[2] === undefined ||
2411
2725
  resourceArgs[2] === "--help" ||
@@ -2659,6 +2973,98 @@ async function runGenerateEnvTypes(command, io) {
2659
2973
  }
2660
2974
  return 0;
2661
2975
  }
2976
+ function titleCaseBindingId(id) {
2977
+ return id
2978
+ .split("-")
2979
+ .filter(Boolean)
2980
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
2981
+ .join(" ");
2982
+ }
2983
+ function pascalCaseBindingId(id) {
2984
+ return id
2985
+ .split("-")
2986
+ .filter(Boolean)
2987
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
2988
+ .join("");
2989
+ }
2990
+ function envNameFromBindingId(id) {
2991
+ return id.toUpperCase().replaceAll("-", "_");
2992
+ }
2993
+ function createOutboundBindingShape(command) {
2994
+ const interfaceName = command.interfaceName ?? `${pascalCaseBindingId(command.id)}Binding`;
2995
+ const label = command.label ?? titleCaseBindingId(command.id);
2996
+ const name = command.name ?? envNameFromBindingId(command.id);
2997
+ const bindingDirectory = `bindings/${command.id}`;
2998
+ const bindingJson = {
2999
+ id: command.id,
3000
+ entry: "src/worker.ts",
3001
+ entrypoint: interfaceName,
3002
+ interface: interfaceName,
3003
+ permissions: ["outbound:http"],
3004
+ allowedHosts: [command.host],
3005
+ };
3006
+ const appJsonBinding = {
3007
+ id: command.id,
3008
+ label,
3009
+ name,
3010
+ source: bindingDirectory,
3011
+ interface: interfaceName,
3012
+ };
3013
+ const interfaceSource = `export interface ${interfaceName} {\n request(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n}\n`;
3014
+ const workerSource = `import { WorkerEntrypoint } from "cloudflare:workers";\n\nexport class ${interfaceName} extends WorkerEntrypoint {\n async request(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = typeof input === "string" || input instanceof URL ? new URL(input) : new URL(input.url);\n if (url.hostname !== "${command.host}") {\n throw new Error("Host is not allowed for this binding.");\n }\n return fetch(url, init);\n }\n}\n`;
3015
+ return {
3016
+ ok: true,
3017
+ command: command.command,
3018
+ dryRun: command.dryRun,
3019
+ binding: {
3020
+ id: command.id,
3021
+ name,
3022
+ label,
3023
+ interface: interfaceName,
3024
+ allowedHosts: [command.host],
3025
+ permissions: ["outbound:http"],
3026
+ },
3027
+ appJsonBinding,
3028
+ files: [
3029
+ {
3030
+ path: `${bindingDirectory}/binding.json`,
3031
+ content: `${JSON.stringify(bindingJson, null, 2)}\n`,
3032
+ },
3033
+ {
3034
+ path: `${bindingDirectory}/interface.d.ts`,
3035
+ content: interfaceSource,
3036
+ },
3037
+ {
3038
+ path: `${bindingDirectory}/src/worker.ts`,
3039
+ content: workerSource,
3040
+ },
3041
+ ],
3042
+ next: [
3043
+ "Add appJsonBinding to app.json bindings[].",
3044
+ `Call this binding from src/server.ts through env.${name}; do not fetch third-party URLs directly from src/server.ts.`,
3045
+ "Run tender app generate-env-types --dir . --json.",
3046
+ "Run tender app doctor --dir . --json.",
3047
+ ],
3048
+ };
3049
+ }
3050
+ function runBindingsOutbound(command, io) {
3051
+ const payload = createOutboundBindingShape(command);
3052
+ if (command.json) {
3053
+ io.stdout(JSON.stringify(payload, null, 2));
3054
+ }
3055
+ else {
3056
+ io.stdout([
3057
+ `Outbound binding dry run: ${payload.binding.id}`,
3058
+ `app.json binding name: ${payload.binding.name}`,
3059
+ `allowed host: ${payload.binding.allowedHosts.join(", ")}`,
3060
+ "files:",
3061
+ ...payload.files.map((file) => ` ${file.path}`),
3062
+ "next:",
3063
+ ...payload.next.map((step) => ` ${step}`),
3064
+ ].join("\n"));
3065
+ }
3066
+ return 0;
3067
+ }
2662
3068
  async function parseJsonResponse(response) {
2663
3069
  return (await response.json().catch(() => null));
2664
3070
  }
@@ -4972,7 +5378,10 @@ export async function runTenderCli(args, io = {
4972
5378
  try {
4973
5379
  const command = parseTenderArgs(args);
4974
5380
  if (command.command === "help") {
4975
- if (command.topic === "auth login") {
5381
+ if (command.topic === "capabilities") {
5382
+ io.stdout(capabilitiesHelp());
5383
+ }
5384
+ else if (command.topic === "auth login") {
4976
5385
  io.stdout(authLoginHelp());
4977
5386
  }
4978
5387
  else if (command.topic === "auth status") {
@@ -5008,6 +5417,9 @@ export async function runTenderCli(args, io = {
5008
5417
  else if (command.topic === "artifacts context refresh") {
5009
5418
  io.stdout(artifactsContextRefreshHelp());
5010
5419
  }
5420
+ else if (command.topic === "artifacts bindings outbound") {
5421
+ io.stdout(bindingsOutboundHelp());
5422
+ }
5011
5423
  else if (command.topic === "artifacts git setup") {
5012
5424
  io.stdout(gitSetupHelp());
5013
5425
  }
@@ -5079,6 +5491,9 @@ export async function runTenderCli(args, io = {
5079
5491
  }
5080
5492
  return 0;
5081
5493
  }
5494
+ if (command.command === "capabilities") {
5495
+ return runCapabilities(command, io);
5496
+ }
5082
5497
  if (command.command === "auth login") {
5083
5498
  return await runAuthLogin(command, io, runtime);
5084
5499
  }
@@ -5109,6 +5524,9 @@ export async function runTenderCli(args, io = {
5109
5524
  if (command.command === "artifacts context refresh") {
5110
5525
  return await runContextFetch(command, io, runtime);
5111
5526
  }
5527
+ if (command.command === "artifacts bindings outbound") {
5528
+ return runBindingsOutbound(command, io);
5529
+ }
5112
5530
  if (command.command === "artifacts git setup") {
5113
5531
  return await runGitSetup(command, io, runtime);
5114
5532
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "bin": {
6
- "tender": "./bin/tender.js"
6
+ "tender": "bin/tender.js"
7
7
  },
8
8
  "main": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",