@tenderprompt/cli 0.1.3 → 0.1.5
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 +6 -1
- package/dist/index.js +990 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,6 +64,10 @@ A push updates app source and can trigger preview work. Publishing remains
|
|
|
64
64
|
an explicit lifecycle command so local agents do not accidentally publish
|
|
65
65
|
production changes.
|
|
66
66
|
|
|
67
|
+
`tender app git setup` configures the checkout to avoid thin/external-delta
|
|
68
|
+
push packs when possible. The server also handles valid external-delta packs by
|
|
69
|
+
indexing the accepted commit after upstream Git accepts the push.
|
|
70
|
+
|
|
67
71
|
## Useful Commands
|
|
68
72
|
|
|
69
73
|
```bash
|
|
@@ -84,7 +88,8 @@ tender app publish artifact_123 --dry-run --json
|
|
|
84
88
|
- Local agents run in the user's environment, not in Tender.
|
|
85
89
|
- Source moves through artifact Git.
|
|
86
90
|
- Preview, build, and publish are explicit API lifecycle operations.
|
|
87
|
-
- `write` lets a token edit and
|
|
91
|
+
- `write` lets a token edit, preview, push source, and publish. `publish`
|
|
92
|
+
remains accepted as an explicit scope for descriptive or older token grants.
|
|
88
93
|
- `tender app publish` requires `--dry-run` or `--confirm publish`.
|
|
89
94
|
- Tokens are stored outside the project checkout and are not written into Git
|
|
90
95
|
remote URLs.
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { validateTenderApp, } from "@tenderprompt/tender-app-validator";
|
|
7
|
+
import { generateTenderAppEnvTypes, validateTenderApp, } from "@tenderprompt/tender-app-validator";
|
|
8
8
|
import { collectTenderAppFiles } from "./local-files.js";
|
|
9
9
|
class TenderCliUsageError extends Error {
|
|
10
10
|
constructor(message) {
|
|
@@ -46,6 +46,12 @@ Commands:
|
|
|
46
46
|
Show publish job status by job id
|
|
47
47
|
tender app publish watch <app-id>
|
|
48
48
|
Stream or print lifecycle events for a publish job
|
|
49
|
+
tender app analytics summary <app-id>
|
|
50
|
+
Read artifact analytics totals and trend points
|
|
51
|
+
tender app analytics query <app-id> --spec <file|->
|
|
52
|
+
Run a server-validated chart spec
|
|
53
|
+
tender app analytics suggestions <app-id>
|
|
54
|
+
Generate useful starter chart suggestions
|
|
49
55
|
tender app context fetch <app-id>
|
|
50
56
|
Fetch managed agent context into a checkout
|
|
51
57
|
tender app context status <app-id>
|
|
@@ -55,6 +61,8 @@ Commands:
|
|
|
55
61
|
tender app git setup <app-id>
|
|
56
62
|
Configure a checkout to push to the app remote
|
|
57
63
|
tender app doctor Validate a local Tender App checkout
|
|
64
|
+
tender app generate-env-types
|
|
65
|
+
Regenerate src/env.d.ts from app.json and bindings
|
|
58
66
|
|
|
59
67
|
Compatibility:
|
|
60
68
|
tender artifacts ... Legacy alias for Tender App commands
|
|
@@ -72,11 +80,15 @@ Examples:
|
|
|
72
80
|
tender app preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
73
81
|
tender app preview watch artifact_123 --job job_123 --stream --json
|
|
74
82
|
tender app publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json
|
|
83
|
+
tender app analytics summary artifact_123 --range 30d --json
|
|
84
|
+
tender app analytics query artifact_123 --spec chart.json --json
|
|
85
|
+
tender app analytics suggestions artifact_123 --range 30d --json
|
|
75
86
|
tender app context fetch artifact_123 --dir ./widget --dry-run --json
|
|
76
87
|
tender app context status artifact_123 --dir ./widget --json
|
|
77
88
|
tender app context refresh artifact_123 --dir ./widget --dry-run --json
|
|
78
89
|
tender app git setup artifact_123 --dir ./widget --json
|
|
79
90
|
tender app doctor --dir ./widget
|
|
91
|
+
tender app generate-env-types --dir ./widget --json
|
|
80
92
|
tender app doctor --dir ./widget --json`;
|
|
81
93
|
}
|
|
82
94
|
function authHelp() {
|
|
@@ -147,6 +159,16 @@ Commands:
|
|
|
147
159
|
Show publish job status by job id
|
|
148
160
|
tender app publish watch <app-id>
|
|
149
161
|
Replay lifecycle events for a publish job id
|
|
162
|
+
tender app analytics summary <app-id>
|
|
163
|
+
Read artifact analytics totals and trend points
|
|
164
|
+
tender app analytics query <app-id> --spec <file|->
|
|
165
|
+
Run a server-validated chart spec
|
|
166
|
+
tender app analytics suggestions <app-id>
|
|
167
|
+
Generate useful starter chart suggestions
|
|
168
|
+
tender app analytics dashboards <app-id>
|
|
169
|
+
List or create saved dashboards
|
|
170
|
+
tender app analytics charts create <app-id>
|
|
171
|
+
Create a saved chart from a chart spec
|
|
150
172
|
tender app context fetch <app-id>
|
|
151
173
|
Fetch AGENTS.md, skills, and Tender App scaffold files
|
|
152
174
|
tender app context status <app-id>
|
|
@@ -156,6 +178,8 @@ Commands:
|
|
|
156
178
|
tender app git setup <app-id>
|
|
157
179
|
Configure the tender Git remote and credential helper
|
|
158
180
|
tender app doctor Validate local context, TypeScript scaffold, and Tender App rules
|
|
181
|
+
tender app generate-env-types
|
|
182
|
+
Regenerate src/env.d.ts from app.json and bindings
|
|
159
183
|
tender app git remote <app-id>
|
|
160
184
|
Show Tender-hosted Git remote metadata
|
|
161
185
|
|
|
@@ -172,11 +196,18 @@ Examples:
|
|
|
172
196
|
tender app publish artifact_123 --dry-run --json
|
|
173
197
|
tender app publish status artifact_123 --job job_123 --json
|
|
174
198
|
tender app publish watch artifact_123 --job job_123 --stream --json
|
|
199
|
+
tender app analytics summary artifact_123 --range 30d --json
|
|
200
|
+
tender app analytics query artifact_123 --spec chart.json --json
|
|
201
|
+
tender app analytics suggestions artifact_123 --range 30d --json
|
|
202
|
+
tender app analytics dashboards artifact_123 --json
|
|
203
|
+
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
|
|
204
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
|
|
175
205
|
tender app context fetch artifact_123 --dir ./widget --dry-run --json
|
|
176
206
|
tender app context status artifact_123 --dir ./widget --json
|
|
177
207
|
tender app context refresh artifact_123 --dir ./widget --dry-run --json
|
|
178
208
|
tender app git setup artifact_123 --dir ./widget --json
|
|
179
209
|
tender app doctor --dir ./widget
|
|
210
|
+
tender app generate-env-types --dir ./widget --json
|
|
180
211
|
tender app doctor --json
|
|
181
212
|
tender app git remote artifact_123 --ttl 7d --json`;
|
|
182
213
|
}
|
|
@@ -186,6 +217,100 @@ function artifactsHelp() {
|
|
|
186
217
|
Legacy alias:
|
|
187
218
|
tender artifacts ... continues to work for existing automation. Prefer tender app ... for new workflows.`;
|
|
188
219
|
}
|
|
220
|
+
function analyticsHelp() {
|
|
221
|
+
return `Usage: tender app analytics <command> <app-id> [options]
|
|
222
|
+
|
|
223
|
+
Start here for agents:
|
|
224
|
+
tender app analytics capabilities <app-id> --include-catalog --range 30d --json
|
|
225
|
+
|
|
226
|
+
Commands:
|
|
227
|
+
tender app analytics summary <app-id> Read totals and trend points
|
|
228
|
+
tender app analytics query <app-id> Run one chart spec
|
|
229
|
+
tender app analytics capabilities <app-id> Describe agent-safe analytics capabilities
|
|
230
|
+
tender app analytics suggestions <app-id> Generate starter chart suggestions
|
|
231
|
+
tender app analytics dashboards <app-id> List or create dashboards
|
|
232
|
+
tender app analytics export <kind> <app-id> Export catalog metadata or query rows
|
|
233
|
+
tender app analytics charts create <app-id> Create a saved chart
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
tender app analytics summary artifact_123 --range 30d --json
|
|
237
|
+
tender app analytics capabilities artifact_123 --include-catalog --json
|
|
238
|
+
tender app analytics query artifact_123 --spec chart.json --json
|
|
239
|
+
tender app analytics query artifact_123 --spec - --json < chart.json
|
|
240
|
+
tender app analytics export catalog artifact_123 --range 30d --json
|
|
241
|
+
tender app analytics export query artifact_123 --spec chart.json --format csv
|
|
242
|
+
tender app analytics suggestions artifact_123 --range 2026-04-01..2026-04-30 --json
|
|
243
|
+
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
|
|
244
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json`;
|
|
245
|
+
}
|
|
246
|
+
function analyticsSummaryHelp() {
|
|
247
|
+
return `Usage: tender app analytics summary <app-id> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
tender app analytics summary artifact_123 --range 30d --json
|
|
251
|
+
tender app analytics summary artifact_123 --range 2026-04-01..2026-04-30 --json`;
|
|
252
|
+
}
|
|
253
|
+
function analyticsQueryHelp() {
|
|
254
|
+
return `Usage: tender app analytics query <app-id> --spec <file|-> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
255
|
+
|
|
256
|
+
Examples:
|
|
257
|
+
tender app analytics query artifact_123 --spec chart.json --json
|
|
258
|
+
tender app analytics query artifact_123 --spec - --json < chart.json`;
|
|
259
|
+
}
|
|
260
|
+
function analyticsCapabilitiesHelp() {
|
|
261
|
+
return `Usage: tender app analytics capabilities <app-id> [--include-catalog] [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
262
|
+
|
|
263
|
+
Use this as the self-documenting entrypoint for coding agents. With --include-catalog,
|
|
264
|
+
the response includes observed events, paths, rough property types, current saved
|
|
265
|
+
dashboards, supported chart spec shapes, retention limits, and safe next commands.
|
|
266
|
+
|
|
267
|
+
Examples:
|
|
268
|
+
tender app analytics capabilities artifact_123 --json
|
|
269
|
+
tender app analytics capabilities artifact_123 --include-catalog --range 30d --json`;
|
|
270
|
+
}
|
|
271
|
+
function analyticsExportHelp() {
|
|
272
|
+
return `Usage: tender app analytics export <catalog|query> <app-id> [options]
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
tender app analytics export catalog artifact_123 --range 30d --json
|
|
276
|
+
tender app analytics export query artifact_123 --spec chart.json --format json
|
|
277
|
+
tender app analytics export query artifact_123 --spec chart.json --format csv`;
|
|
278
|
+
}
|
|
279
|
+
function analyticsExportCatalogHelp() {
|
|
280
|
+
return `Usage: tender app analytics export catalog <app-id> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--format json] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
tender app analytics export catalog artifact_123 --range 30d --json`;
|
|
284
|
+
}
|
|
285
|
+
function analyticsExportQueryHelp() {
|
|
286
|
+
return `Usage: tender app analytics export query <app-id> --spec <file|-> [--format <json|csv>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
287
|
+
|
|
288
|
+
Examples:
|
|
289
|
+
tender app analytics export query artifact_123 --spec chart.json --format json
|
|
290
|
+
tender app analytics export query artifact_123 --spec chart.json --format csv
|
|
291
|
+
tender app analytics export query artifact_123 --spec - --format csv < chart.json`;
|
|
292
|
+
}
|
|
293
|
+
function analyticsSuggestionsHelp() {
|
|
294
|
+
return `Usage: tender app analytics suggestions <app-id> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
tender app analytics suggestions artifact_123 --range 30d --json`;
|
|
298
|
+
}
|
|
299
|
+
function analyticsDashboardsHelp() {
|
|
300
|
+
return `Usage: tender app analytics dashboards <app-id> [--create <name>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
301
|
+
|
|
302
|
+
Examples:
|
|
303
|
+
tender app analytics dashboards artifact_123 --json
|
|
304
|
+
tender app analytics dashboards artifact_123 --create "Agent dashboard" --dry-run --json
|
|
305
|
+
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json`;
|
|
306
|
+
}
|
|
307
|
+
function analyticsChartsCreateHelp() {
|
|
308
|
+
return `Usage: tender app analytics charts create <app-id> --dashboard <dashboard-id> --title <title> --spec <file|-> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
309
|
+
|
|
310
|
+
Examples:
|
|
311
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --dry-run --json
|
|
312
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json`;
|
|
313
|
+
}
|
|
189
314
|
function artifactsListHelp() {
|
|
190
315
|
return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
191
316
|
|
|
@@ -305,6 +430,17 @@ Examples:
|
|
|
305
430
|
tender app doctor --dir ./widget
|
|
306
431
|
tender app doctor --dir ./widget --json`;
|
|
307
432
|
}
|
|
433
|
+
function generateEnvTypesHelp() {
|
|
434
|
+
return `Usage: tender app generate-env-types [--dir <path>] [--json]
|
|
435
|
+
|
|
436
|
+
Options:
|
|
437
|
+
--dir <path> Local Tender App checkout to update. Defaults to current directory.
|
|
438
|
+
--json Emit stable JSON for coding agents.
|
|
439
|
+
|
|
440
|
+
Examples:
|
|
441
|
+
tender app generate-env-types --dir ./widget
|
|
442
|
+
tender app generate-env-types --dir ./widget --json`;
|
|
443
|
+
}
|
|
308
444
|
function gitRemoteHelp() {
|
|
309
445
|
return `Usage: tender app git remote <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--json]
|
|
310
446
|
|
|
@@ -1882,6 +2018,343 @@ function parseAuthStatusArgs(args) {
|
|
|
1882
2018
|
json,
|
|
1883
2019
|
};
|
|
1884
2020
|
}
|
|
2021
|
+
function parseGenerateEnvTypesArgs(args) {
|
|
2022
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2023
|
+
return { command: "help", topic: "artifacts generate-env-types" };
|
|
2024
|
+
}
|
|
2025
|
+
let dir = ".";
|
|
2026
|
+
let json = false;
|
|
2027
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2028
|
+
const arg = args[index];
|
|
2029
|
+
if (arg === "--json") {
|
|
2030
|
+
json = true;
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
if (arg === "--dir") {
|
|
2034
|
+
const value = args[index + 1];
|
|
2035
|
+
if (!value) {
|
|
2036
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
2037
|
+
}
|
|
2038
|
+
dir = value;
|
|
2039
|
+
index += 1;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
throw new TenderCliUsageError(`Unknown generate-env-types option: ${arg}`);
|
|
2043
|
+
}
|
|
2044
|
+
return {
|
|
2045
|
+
command: "artifacts generate-env-types",
|
|
2046
|
+
dir,
|
|
2047
|
+
json,
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
function parseAnalyticsRange(value) {
|
|
2051
|
+
if (!value || value.startsWith("-")) {
|
|
2052
|
+
throw new TenderCliUsageError("--range requires 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.");
|
|
2053
|
+
}
|
|
2054
|
+
if (value === "7d" || value === "30d" || value === "90d") {
|
|
2055
|
+
return value;
|
|
2056
|
+
}
|
|
2057
|
+
if (/^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
2058
|
+
return value;
|
|
2059
|
+
}
|
|
2060
|
+
throw new TenderCliUsageError("--range must be 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.");
|
|
2061
|
+
}
|
|
2062
|
+
function parseSpecPath(value) {
|
|
2063
|
+
if (!value || (value !== "-" && value.startsWith("-"))) {
|
|
2064
|
+
throw new TenderCliUsageError("--spec requires a JSON file path or - for stdin.");
|
|
2065
|
+
}
|
|
2066
|
+
return value;
|
|
2067
|
+
}
|
|
2068
|
+
function parseRequiredFlagValue(value, flag) {
|
|
2069
|
+
if (!value || value.startsWith("-")) {
|
|
2070
|
+
throw new TenderCliUsageError(`${flag} requires a value.`);
|
|
2071
|
+
}
|
|
2072
|
+
return value;
|
|
2073
|
+
}
|
|
2074
|
+
function parseAnalyticsCommonArgs(args, startIndex) {
|
|
2075
|
+
let baseUrl = null;
|
|
2076
|
+
let flagToken = null;
|
|
2077
|
+
let profileName = null;
|
|
2078
|
+
let json = false;
|
|
2079
|
+
const remaining = [];
|
|
2080
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
2081
|
+
const arg = args[index];
|
|
2082
|
+
if (arg === "--json") {
|
|
2083
|
+
json = true;
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
if (arg === "--base-url") {
|
|
2087
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
2088
|
+
index += 1;
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
if (arg === "--token") {
|
|
2092
|
+
flagToken = parseTokenFlag(args, index);
|
|
2093
|
+
index += 1;
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
if (arg === "--profile") {
|
|
2097
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
2098
|
+
index += 1;
|
|
2099
|
+
continue;
|
|
2100
|
+
}
|
|
2101
|
+
remaining.push(arg);
|
|
2102
|
+
}
|
|
2103
|
+
return { baseUrl, flagToken, profileName, json, remaining };
|
|
2104
|
+
}
|
|
2105
|
+
function parseAnalyticsSummaryArgs(args) {
|
|
2106
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2107
|
+
return { command: "help", topic: "artifacts analytics summary" };
|
|
2108
|
+
}
|
|
2109
|
+
const artifactId = args[0];
|
|
2110
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2111
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics summary <app-id> --range 30d --json");
|
|
2112
|
+
}
|
|
2113
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2114
|
+
let range = "7d";
|
|
2115
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2116
|
+
const arg = parsed.remaining[index];
|
|
2117
|
+
if (arg === "--range") {
|
|
2118
|
+
range = parseAnalyticsRange(parsed.remaining[index + 1]);
|
|
2119
|
+
index += 1;
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
throw new TenderCliUsageError(`Unknown analytics summary option: ${arg}`);
|
|
2123
|
+
}
|
|
2124
|
+
return { command: "artifacts analytics summary", artifactId, range, ...parsed };
|
|
2125
|
+
}
|
|
2126
|
+
function parseAnalyticsQueryArgs(args) {
|
|
2127
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2128
|
+
return { command: "help", topic: "artifacts analytics query" };
|
|
2129
|
+
}
|
|
2130
|
+
const artifactId = args[0];
|
|
2131
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2132
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics query <app-id> --spec chart.json --json");
|
|
2133
|
+
}
|
|
2134
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2135
|
+
let specPath = null;
|
|
2136
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2137
|
+
const arg = parsed.remaining[index];
|
|
2138
|
+
if (arg === "--spec") {
|
|
2139
|
+
specPath = parseSpecPath(parsed.remaining[index + 1]);
|
|
2140
|
+
index += 1;
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
throw new TenderCliUsageError(`Unknown analytics query option: ${arg}`);
|
|
2144
|
+
}
|
|
2145
|
+
if (!specPath) {
|
|
2146
|
+
throw new TenderCliUsageError("--spec is required. Example: tender app analytics query artifact_123 --spec chart.json --json");
|
|
2147
|
+
}
|
|
2148
|
+
return { command: "artifacts analytics query", artifactId, specPath, ...parsed };
|
|
2149
|
+
}
|
|
2150
|
+
function parseAnalyticsCapabilitiesArgs(args) {
|
|
2151
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2152
|
+
return { command: "help", topic: "artifacts analytics capabilities" };
|
|
2153
|
+
}
|
|
2154
|
+
const artifactId = args[0];
|
|
2155
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2156
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics capabilities <app-id> --include-catalog --json");
|
|
2157
|
+
}
|
|
2158
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2159
|
+
let range = "7d";
|
|
2160
|
+
let includeCatalog = false;
|
|
2161
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2162
|
+
const arg = parsed.remaining[index];
|
|
2163
|
+
if (arg === "--include-catalog") {
|
|
2164
|
+
includeCatalog = true;
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (arg === "--range") {
|
|
2168
|
+
range = parseAnalyticsRange(parsed.remaining[index + 1]);
|
|
2169
|
+
index += 1;
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
throw new TenderCliUsageError(`Unknown analytics capabilities option: ${arg}`);
|
|
2173
|
+
}
|
|
2174
|
+
return {
|
|
2175
|
+
command: "artifacts analytics capabilities",
|
|
2176
|
+
artifactId,
|
|
2177
|
+
includeCatalog,
|
|
2178
|
+
range,
|
|
2179
|
+
...parsed,
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
function parseAnalyticsSuggestionsArgs(args) {
|
|
2183
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2184
|
+
return { command: "help", topic: "artifacts analytics suggestions" };
|
|
2185
|
+
}
|
|
2186
|
+
const artifactId = args[0];
|
|
2187
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2188
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics suggestions <app-id> --range 30d --json");
|
|
2189
|
+
}
|
|
2190
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2191
|
+
let range = "7d";
|
|
2192
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2193
|
+
const arg = parsed.remaining[index];
|
|
2194
|
+
if (arg === "--range") {
|
|
2195
|
+
range = parseAnalyticsRange(parsed.remaining[index + 1]);
|
|
2196
|
+
index += 1;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
throw new TenderCliUsageError(`Unknown analytics suggestions option: ${arg}`);
|
|
2200
|
+
}
|
|
2201
|
+
return { command: "artifacts analytics suggestions", artifactId, range, ...parsed };
|
|
2202
|
+
}
|
|
2203
|
+
function parseAnalyticsDashboardsArgs(args) {
|
|
2204
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2205
|
+
return { command: "help", topic: "artifacts analytics dashboards" };
|
|
2206
|
+
}
|
|
2207
|
+
const artifactId = args[0];
|
|
2208
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2209
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics dashboards <app-id> --json");
|
|
2210
|
+
}
|
|
2211
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2212
|
+
let name = null;
|
|
2213
|
+
let dryRun = false;
|
|
2214
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2215
|
+
const arg = parsed.remaining[index];
|
|
2216
|
+
if (arg === "--create") {
|
|
2217
|
+
name = parseRequiredFlagValue(parsed.remaining[index + 1], "--create");
|
|
2218
|
+
index += 1;
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
if (arg === "--dry-run") {
|
|
2222
|
+
dryRun = true;
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
throw new TenderCliUsageError(`Unknown analytics dashboards option: ${arg}`);
|
|
2226
|
+
}
|
|
2227
|
+
if (dryRun && !name) {
|
|
2228
|
+
throw new TenderCliUsageError("--dry-run is only valid with --create. Example: tender app analytics dashboards artifact_123 --create \"Agent dashboard\" --dry-run --json");
|
|
2229
|
+
}
|
|
2230
|
+
return { command: "artifacts analytics dashboards", artifactId, name, dryRun, ...parsed };
|
|
2231
|
+
}
|
|
2232
|
+
function parseAnalyticsExportCatalogArgs(args) {
|
|
2233
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2234
|
+
return { command: "help", topic: "artifacts analytics export catalog" };
|
|
2235
|
+
}
|
|
2236
|
+
const artifactId = args[0];
|
|
2237
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2238
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics export catalog <app-id> --range 30d --json");
|
|
2239
|
+
}
|
|
2240
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2241
|
+
let range = "7d";
|
|
2242
|
+
let format = "json";
|
|
2243
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2244
|
+
const arg = parsed.remaining[index];
|
|
2245
|
+
if (arg === "--range") {
|
|
2246
|
+
range = parseAnalyticsRange(parsed.remaining[index + 1]);
|
|
2247
|
+
index += 1;
|
|
2248
|
+
continue;
|
|
2249
|
+
}
|
|
2250
|
+
if (arg === "--format") {
|
|
2251
|
+
const value = parseRequiredFlagValue(parsed.remaining[index + 1], "--format");
|
|
2252
|
+
if (value !== "json") {
|
|
2253
|
+
throw new TenderCliUsageError("--format for export catalog must be json.");
|
|
2254
|
+
}
|
|
2255
|
+
format = value;
|
|
2256
|
+
index += 1;
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
throw new TenderCliUsageError(`Unknown analytics export catalog option: ${arg}`);
|
|
2260
|
+
}
|
|
2261
|
+
return {
|
|
2262
|
+
command: "artifacts analytics export catalog",
|
|
2263
|
+
artifactId,
|
|
2264
|
+
format,
|
|
2265
|
+
range,
|
|
2266
|
+
...parsed,
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
function parseAnalyticsExportQueryArgs(args) {
|
|
2270
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2271
|
+
return { command: "help", topic: "artifacts analytics export query" };
|
|
2272
|
+
}
|
|
2273
|
+
const artifactId = args[0];
|
|
2274
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2275
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics export query <app-id> --spec chart.json --format csv");
|
|
2276
|
+
}
|
|
2277
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2278
|
+
let specPath = null;
|
|
2279
|
+
let format = "json";
|
|
2280
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2281
|
+
const arg = parsed.remaining[index];
|
|
2282
|
+
if (arg === "--spec") {
|
|
2283
|
+
specPath = parseSpecPath(parsed.remaining[index + 1]);
|
|
2284
|
+
index += 1;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (arg === "--format") {
|
|
2288
|
+
const value = parseRequiredFlagValue(parsed.remaining[index + 1], "--format");
|
|
2289
|
+
if (value !== "json" && value !== "csv") {
|
|
2290
|
+
throw new TenderCliUsageError("--format must be json or csv.");
|
|
2291
|
+
}
|
|
2292
|
+
format = value;
|
|
2293
|
+
index += 1;
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
throw new TenderCliUsageError(`Unknown analytics export query option: ${arg}`);
|
|
2297
|
+
}
|
|
2298
|
+
if (!specPath) {
|
|
2299
|
+
throw new TenderCliUsageError("--spec is required. Example: tender app analytics export query artifact_123 --spec chart.json --format csv");
|
|
2300
|
+
}
|
|
2301
|
+
return {
|
|
2302
|
+
command: "artifacts analytics export query",
|
|
2303
|
+
artifactId,
|
|
2304
|
+
specPath,
|
|
2305
|
+
format,
|
|
2306
|
+
...parsed,
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
function parseAnalyticsChartsCreateArgs(args) {
|
|
2310
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2311
|
+
return { command: "help", topic: "artifacts analytics charts create" };
|
|
2312
|
+
}
|
|
2313
|
+
const artifactId = args[0];
|
|
2314
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2315
|
+
throw new TenderCliUsageError("app id is required. Example: tender app analytics charts create <app-id> --dashboard <dashboard-id> --title <title> --spec chart.json --json");
|
|
2316
|
+
}
|
|
2317
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
2318
|
+
let dashboardId = null;
|
|
2319
|
+
let title = null;
|
|
2320
|
+
let specPath = null;
|
|
2321
|
+
let dryRun = false;
|
|
2322
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2323
|
+
const arg = parsed.remaining[index];
|
|
2324
|
+
if (arg === "--dashboard") {
|
|
2325
|
+
dashboardId = parseRequiredFlagValue(parsed.remaining[index + 1], "--dashboard");
|
|
2326
|
+
index += 1;
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
if (arg === "--title") {
|
|
2330
|
+
title = parseRequiredFlagValue(parsed.remaining[index + 1], "--title");
|
|
2331
|
+
index += 1;
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
if (arg === "--spec") {
|
|
2335
|
+
specPath = parseSpecPath(parsed.remaining[index + 1]);
|
|
2336
|
+
index += 1;
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
if (arg === "--dry-run") {
|
|
2340
|
+
dryRun = true;
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
throw new TenderCliUsageError(`Unknown analytics charts create option: ${arg}`);
|
|
2344
|
+
}
|
|
2345
|
+
if (!dashboardId || !title || !specPath) {
|
|
2346
|
+
throw new TenderCliUsageError("--dashboard, --title, and --spec are required. Example: tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title \"Signup funnel\" --spec chart.json --json");
|
|
2347
|
+
}
|
|
2348
|
+
return {
|
|
2349
|
+
command: "artifacts analytics charts create",
|
|
2350
|
+
artifactId,
|
|
2351
|
+
dashboardId,
|
|
2352
|
+
title,
|
|
2353
|
+
specPath,
|
|
2354
|
+
dryRun,
|
|
2355
|
+
...parsed,
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
1885
2358
|
function parseTenderArgs(args) {
|
|
1886
2359
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
1887
2360
|
return { command: "help", topic: "root" };
|
|
@@ -1933,6 +2406,49 @@ function parseTenderArgs(args) {
|
|
|
1933
2406
|
if (resourceArgs[1] === "context" && resourceArgs[2] === "refresh") {
|
|
1934
2407
|
return parseContextRefreshArgs(resourceArgs.slice(3));
|
|
1935
2408
|
}
|
|
2409
|
+
if (resourceArgs[1] === "analytics" &&
|
|
2410
|
+
(resourceArgs[2] === undefined ||
|
|
2411
|
+
resourceArgs[2] === "--help" ||
|
|
2412
|
+
resourceArgs[2] === "-h")) {
|
|
2413
|
+
return { command: "help", topic: "artifacts analytics" };
|
|
2414
|
+
}
|
|
2415
|
+
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "summary") {
|
|
2416
|
+
return parseAnalyticsSummaryArgs(resourceArgs.slice(3));
|
|
2417
|
+
}
|
|
2418
|
+
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "query") {
|
|
2419
|
+
return parseAnalyticsQueryArgs(resourceArgs.slice(3));
|
|
2420
|
+
}
|
|
2421
|
+
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "capabilities") {
|
|
2422
|
+
return parseAnalyticsCapabilitiesArgs(resourceArgs.slice(3));
|
|
2423
|
+
}
|
|
2424
|
+
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "suggestions") {
|
|
2425
|
+
return parseAnalyticsSuggestionsArgs(resourceArgs.slice(3));
|
|
2426
|
+
}
|
|
2427
|
+
if (resourceArgs[1] === "analytics" && resourceArgs[2] === "dashboards") {
|
|
2428
|
+
return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
|
|
2429
|
+
}
|
|
2430
|
+
if (resourceArgs[1] === "analytics" &&
|
|
2431
|
+
resourceArgs[2] === "export" &&
|
|
2432
|
+
(resourceArgs[3] === undefined ||
|
|
2433
|
+
resourceArgs[3] === "--help" ||
|
|
2434
|
+
resourceArgs[3] === "-h")) {
|
|
2435
|
+
return { command: "help", topic: "artifacts analytics export" };
|
|
2436
|
+
}
|
|
2437
|
+
if (resourceArgs[1] === "analytics" &&
|
|
2438
|
+
resourceArgs[2] === "export" &&
|
|
2439
|
+
resourceArgs[3] === "catalog") {
|
|
2440
|
+
return parseAnalyticsExportCatalogArgs(resourceArgs.slice(4));
|
|
2441
|
+
}
|
|
2442
|
+
if (resourceArgs[1] === "analytics" &&
|
|
2443
|
+
resourceArgs[2] === "export" &&
|
|
2444
|
+
resourceArgs[3] === "query") {
|
|
2445
|
+
return parseAnalyticsExportQueryArgs(resourceArgs.slice(4));
|
|
2446
|
+
}
|
|
2447
|
+
if (resourceArgs[1] === "analytics" &&
|
|
2448
|
+
resourceArgs[2] === "charts" &&
|
|
2449
|
+
resourceArgs[3] === "create") {
|
|
2450
|
+
return parseAnalyticsChartsCreateArgs(resourceArgs.slice(4));
|
|
2451
|
+
}
|
|
1936
2452
|
if (resourceArgs[1] === "context" &&
|
|
1937
2453
|
(resourceArgs[2] === undefined ||
|
|
1938
2454
|
resourceArgs[2] === "--help" ||
|
|
@@ -1948,6 +2464,9 @@ function parseTenderArgs(args) {
|
|
|
1948
2464
|
if (resourceArgs[1] === "git" && resourceArgs[2] === "credential") {
|
|
1949
2465
|
return parseGitCredentialArgs(resourceArgs.slice(3));
|
|
1950
2466
|
}
|
|
2467
|
+
if (resourceArgs[1] === "generate-env-types") {
|
|
2468
|
+
return parseGenerateEnvTypesArgs(resourceArgs.slice(2));
|
|
2469
|
+
}
|
|
1951
2470
|
if (resourceArgs[1] === "validate") {
|
|
1952
2471
|
return parseValidateArgs(resourceArgs.slice(2));
|
|
1953
2472
|
}
|
|
@@ -2077,6 +2596,69 @@ async function runDoctor(command, io) {
|
|
|
2077
2596
|
}
|
|
2078
2597
|
return result.ok ? 0 : 1;
|
|
2079
2598
|
}
|
|
2599
|
+
function formatEnvTypesGenerationFailure(result) {
|
|
2600
|
+
if (result.ok || result.diagnostics.length === 0) {
|
|
2601
|
+
return "Could not generate src/env.d.ts.";
|
|
2602
|
+
}
|
|
2603
|
+
return formatDoctorHuman({
|
|
2604
|
+
ok: false,
|
|
2605
|
+
diagnostics: result.diagnostics,
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
async function runGenerateEnvTypes(command, io) {
|
|
2609
|
+
const root = path.resolve(command.dir);
|
|
2610
|
+
const files = await collectTenderAppFiles({ root, current: root });
|
|
2611
|
+
const result = generateTenderAppEnvTypes({
|
|
2612
|
+
files,
|
|
2613
|
+
mode: "local",
|
|
2614
|
+
});
|
|
2615
|
+
if (!result.ok) {
|
|
2616
|
+
if (command.json) {
|
|
2617
|
+
io.stdout(JSON.stringify({
|
|
2618
|
+
ok: false,
|
|
2619
|
+
command: command.command,
|
|
2620
|
+
dir: root,
|
|
2621
|
+
path: result.path,
|
|
2622
|
+
diagnostics: result.diagnostics,
|
|
2623
|
+
}, null, 2));
|
|
2624
|
+
}
|
|
2625
|
+
else {
|
|
2626
|
+
io.stdout(formatEnvTypesGenerationFailure(result));
|
|
2627
|
+
}
|
|
2628
|
+
return 1;
|
|
2629
|
+
}
|
|
2630
|
+
const absolutePath = path.join(root, result.path);
|
|
2631
|
+
const previous = await readFile(absolutePath, "utf8").catch((error) => {
|
|
2632
|
+
if (error &&
|
|
2633
|
+
typeof error === "object" &&
|
|
2634
|
+
error.code === "ENOENT") {
|
|
2635
|
+
return null;
|
|
2636
|
+
}
|
|
2637
|
+
throw error;
|
|
2638
|
+
});
|
|
2639
|
+
const changed = previous !== result.content;
|
|
2640
|
+
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
2641
|
+
await writeFile(absolutePath, result.content);
|
|
2642
|
+
if (command.json) {
|
|
2643
|
+
io.stdout(JSON.stringify({
|
|
2644
|
+
ok: true,
|
|
2645
|
+
command: command.command,
|
|
2646
|
+
dir: root,
|
|
2647
|
+
path: result.path,
|
|
2648
|
+
absolutePath,
|
|
2649
|
+
changed,
|
|
2650
|
+
bytes: Buffer.byteLength(result.content),
|
|
2651
|
+
diagnostics: result.diagnostics,
|
|
2652
|
+
}, null, 2));
|
|
2653
|
+
}
|
|
2654
|
+
else {
|
|
2655
|
+
io.stdout([
|
|
2656
|
+
`generated: ${path.join(command.dir, result.path)}`,
|
|
2657
|
+
`changed: ${changed ? "yes" : "no"}`,
|
|
2658
|
+
].join("\n"));
|
|
2659
|
+
}
|
|
2660
|
+
return 0;
|
|
2661
|
+
}
|
|
2080
2662
|
async function parseJsonResponse(response) {
|
|
2081
2663
|
return (await response.json().catch(() => null));
|
|
2082
2664
|
}
|
|
@@ -2114,6 +2696,119 @@ function parseSseLifecycleBlock(block) {
|
|
|
2114
2696
|
return { raw: dataLines.join("\n") };
|
|
2115
2697
|
}
|
|
2116
2698
|
}
|
|
2699
|
+
function appendAnalyticsRange(params, range) {
|
|
2700
|
+
if (range === "7d" || range === "30d" || range === "90d") {
|
|
2701
|
+
params.set("rangePreset", range);
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
const [startDate, endDate] = range.split("..");
|
|
2705
|
+
if (startDate && endDate) {
|
|
2706
|
+
params.set("startDate", startDate);
|
|
2707
|
+
params.set("endDate", endDate);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
async function readAnalyticsSpec(input) {
|
|
2711
|
+
const raw = input.specPath === "-"
|
|
2712
|
+
? input.stdin
|
|
2713
|
+
: await readFile(path.resolve(input.specPath), "utf8");
|
|
2714
|
+
if (!raw) {
|
|
2715
|
+
throw new TenderCliUsageError("--spec - requires JSON on stdin.");
|
|
2716
|
+
}
|
|
2717
|
+
try {
|
|
2718
|
+
return JSON.parse(raw);
|
|
2719
|
+
}
|
|
2720
|
+
catch {
|
|
2721
|
+
throw new TenderCliUsageError("--spec must contain valid JSON.");
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
async function requestAnalyticsApi(input) {
|
|
2725
|
+
const params = new URLSearchParams();
|
|
2726
|
+
if (input.range) {
|
|
2727
|
+
appendAnalyticsRange(params, input.range);
|
|
2728
|
+
}
|
|
2729
|
+
if (input.includeCatalog) {
|
|
2730
|
+
params.set("includeCatalog", "1");
|
|
2731
|
+
}
|
|
2732
|
+
const suffix = input.analyticsPath ? `/${input.analyticsPath}` : "";
|
|
2733
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
2734
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/analytics${suffix}${query}`, {
|
|
2735
|
+
method: input.method,
|
|
2736
|
+
headers: {
|
|
2737
|
+
authorization: `Bearer ${input.token}`,
|
|
2738
|
+
accept: "application/json",
|
|
2739
|
+
...(input.body === undefined ? {} : { "content-type": "application/json" }),
|
|
2740
|
+
},
|
|
2741
|
+
body: input.body === undefined ? undefined : JSON.stringify(input.body),
|
|
2742
|
+
});
|
|
2743
|
+
const payload = await parseJsonResponse(response);
|
|
2744
|
+
if (!response.ok || !payload?.ok) {
|
|
2745
|
+
const reasonCode = (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
2746
|
+
`http_${response.status}`;
|
|
2747
|
+
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
2748
|
+
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
2749
|
+
`Analytics request failed with HTTP ${response.status}.`;
|
|
2750
|
+
throw new Error([
|
|
2751
|
+
message,
|
|
2752
|
+
`Scope: artifact ${input.artifactId}, analytics/${input.analyticsPath || "summary"}.`,
|
|
2753
|
+
`Reason: ${reasonCode}.`,
|
|
2754
|
+
`Example: ${analyticsCorrectedExample(input.artifactId, input.analyticsPath)}.`,
|
|
2755
|
+
].join("\n"));
|
|
2756
|
+
}
|
|
2757
|
+
return payload;
|
|
2758
|
+
}
|
|
2759
|
+
function analyticsCorrectedExample(artifactId, analyticsPath) {
|
|
2760
|
+
if (analyticsPath === "capabilities") {
|
|
2761
|
+
return `tender app analytics capabilities ${artifactId} --include-catalog --range 30d --json`;
|
|
2762
|
+
}
|
|
2763
|
+
if (analyticsPath === "query") {
|
|
2764
|
+
return `tender app analytics query ${artifactId} --spec chart.json --json`;
|
|
2765
|
+
}
|
|
2766
|
+
if (analyticsPath === "suggestions") {
|
|
2767
|
+
return `tender app analytics suggestions ${artifactId} --range 30d --json`;
|
|
2768
|
+
}
|
|
2769
|
+
if (analyticsPath === "dashboards") {
|
|
2770
|
+
return `tender app analytics dashboards ${artifactId} --json`;
|
|
2771
|
+
}
|
|
2772
|
+
if (analyticsPath === "charts") {
|
|
2773
|
+
return `tender app analytics charts create ${artifactId} --dashboard analytics_dashboard_123 --title "Signup funnel" --spec chart.json --json`;
|
|
2774
|
+
}
|
|
2775
|
+
return `tender app analytics summary ${artifactId} --range 30d --json`;
|
|
2776
|
+
}
|
|
2777
|
+
function printAnalyticsPayload(command, io, payload, fallbackLines) {
|
|
2778
|
+
if (command.json) {
|
|
2779
|
+
io.stdout(JSON.stringify(payload, null, 2));
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
io.stdout(fallbackLines.join("\n"));
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
function csvCell(value) {
|
|
2786
|
+
if (value === null || value === undefined) {
|
|
2787
|
+
return "";
|
|
2788
|
+
}
|
|
2789
|
+
const json = JSON.stringify(value);
|
|
2790
|
+
const text = typeof value === "string" ? value : typeof json === "string" ? json : String(value);
|
|
2791
|
+
if (/[",\n\r]/.test(text)) {
|
|
2792
|
+
return `"${text.replaceAll("\"", "\"\"")}"`;
|
|
2793
|
+
}
|
|
2794
|
+
return text;
|
|
2795
|
+
}
|
|
2796
|
+
function rowsToCsv(rows) {
|
|
2797
|
+
const records = rows.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
|
|
2798
|
+
const headers = Array.from(records.reduce((set, row) => {
|
|
2799
|
+
for (const key of Object.keys(row)) {
|
|
2800
|
+
set.add(key);
|
|
2801
|
+
}
|
|
2802
|
+
return set;
|
|
2803
|
+
}, new Set()));
|
|
2804
|
+
if (headers.length === 0) {
|
|
2805
|
+
return "";
|
|
2806
|
+
}
|
|
2807
|
+
return [
|
|
2808
|
+
headers.map(csvCell).join(","),
|
|
2809
|
+
...records.map((row) => headers.map((header) => csvCell(row[header])).join(",")),
|
|
2810
|
+
].join("\n");
|
|
2811
|
+
}
|
|
2117
2812
|
function findSseBlockSeparator(buffer) {
|
|
2118
2813
|
const unixIndex = buffer.indexOf("\n\n");
|
|
2119
2814
|
const windowsIndex = buffer.indexOf("\r\n\r\n");
|
|
@@ -2943,6 +3638,8 @@ function createGitSetupCommandPlan(input) {
|
|
|
2943
3638
|
input.credentialHelper,
|
|
2944
3639
|
],
|
|
2945
3640
|
["git", "config", "--local", "credential.useHttpPath", "true"],
|
|
3641
|
+
["git", "config", "--local", "pack.window", "0"],
|
|
3642
|
+
["git", "config", "--local", "pack.depth", "0"],
|
|
2946
3643
|
];
|
|
2947
3644
|
}
|
|
2948
3645
|
async function applyGitSetup(input) {
|
|
@@ -2998,6 +3695,16 @@ async function applyGitSetup(input) {
|
|
|
2998
3695
|
cwd: input.root,
|
|
2999
3696
|
args: ["config", "--local", "credential.useHttpPath", "true"],
|
|
3000
3697
|
});
|
|
3698
|
+
await runGitCommand({
|
|
3699
|
+
runtime: input.runtime,
|
|
3700
|
+
cwd: input.root,
|
|
3701
|
+
args: ["config", "--local", "pack.window", "0"],
|
|
3702
|
+
});
|
|
3703
|
+
await runGitCommand({
|
|
3704
|
+
runtime: input.runtime,
|
|
3705
|
+
cwd: input.root,
|
|
3706
|
+
args: ["config", "--local", "pack.depth", "0"],
|
|
3707
|
+
});
|
|
3001
3708
|
}
|
|
3002
3709
|
async function maybeCheckoutInitialArtifactSource(input) {
|
|
3003
3710
|
const localHead = await runGitCommand({
|
|
@@ -4038,6 +4745,226 @@ async function runGitRemote(command, io, runtime) {
|
|
|
4038
4745
|
}
|
|
4039
4746
|
return 0;
|
|
4040
4747
|
}
|
|
4748
|
+
async function resolveAnalyticsCredentials(command, runtime) {
|
|
4749
|
+
return await resolveApiCredentials({
|
|
4750
|
+
baseUrl: command.baseUrl,
|
|
4751
|
+
flagToken: command.flagToken,
|
|
4752
|
+
profileName: command.profileName,
|
|
4753
|
+
env: runtime.env ?? process.env,
|
|
4754
|
+
});
|
|
4755
|
+
}
|
|
4756
|
+
async function runAnalyticsSummary(command, io, runtime) {
|
|
4757
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4758
|
+
const payload = await requestAnalyticsApi({
|
|
4759
|
+
artifactId: command.artifactId,
|
|
4760
|
+
analyticsPath: "",
|
|
4761
|
+
method: "GET",
|
|
4762
|
+
baseUrl: credentials.baseUrl,
|
|
4763
|
+
token: credentials.token,
|
|
4764
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4765
|
+
range: command.range,
|
|
4766
|
+
});
|
|
4767
|
+
const points = Array.isArray(payload.points) ? payload.points : [];
|
|
4768
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4769
|
+
`app_id: ${command.artifactId}`,
|
|
4770
|
+
`source: ${String(payload.source ?? "unknown")}`,
|
|
4771
|
+
`points: ${points.length}`,
|
|
4772
|
+
]);
|
|
4773
|
+
return 0;
|
|
4774
|
+
}
|
|
4775
|
+
async function runAnalyticsQuery(command, io, runtime) {
|
|
4776
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4777
|
+
const spec = await readAnalyticsSpec({
|
|
4778
|
+
specPath: command.specPath,
|
|
4779
|
+
stdin: runtime.stdin,
|
|
4780
|
+
});
|
|
4781
|
+
const payload = await requestAnalyticsApi({
|
|
4782
|
+
artifactId: command.artifactId,
|
|
4783
|
+
analyticsPath: "query",
|
|
4784
|
+
method: "POST",
|
|
4785
|
+
baseUrl: credentials.baseUrl,
|
|
4786
|
+
token: credentials.token,
|
|
4787
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4788
|
+
body: { spec },
|
|
4789
|
+
});
|
|
4790
|
+
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
4791
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4792
|
+
`app_id: ${command.artifactId}`,
|
|
4793
|
+
`source: ${String(payload.source ?? "unknown")}`,
|
|
4794
|
+
`rows: ${rows.length}`,
|
|
4795
|
+
]);
|
|
4796
|
+
return 0;
|
|
4797
|
+
}
|
|
4798
|
+
async function runAnalyticsCapabilities(command, io, runtime) {
|
|
4799
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4800
|
+
const payload = await requestAnalyticsApi({
|
|
4801
|
+
artifactId: command.artifactId,
|
|
4802
|
+
analyticsPath: "capabilities",
|
|
4803
|
+
method: "GET",
|
|
4804
|
+
baseUrl: credentials.baseUrl,
|
|
4805
|
+
token: credentials.token,
|
|
4806
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4807
|
+
range: command.range,
|
|
4808
|
+
includeCatalog: command.includeCatalog,
|
|
4809
|
+
});
|
|
4810
|
+
const commands = Array.isArray(payload.commands) ? payload.commands : [];
|
|
4811
|
+
const catalog = payload.catalog && typeof payload.catalog === "object"
|
|
4812
|
+
? payload.catalog
|
|
4813
|
+
: null;
|
|
4814
|
+
const observedEvents = Array.isArray(catalog?.observedEvents)
|
|
4815
|
+
? catalog.observedEvents
|
|
4816
|
+
: [];
|
|
4817
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4818
|
+
`app_id: ${command.artifactId}`,
|
|
4819
|
+
`commands: ${commands.length}`,
|
|
4820
|
+
`catalog_events: ${observedEvents.length}`,
|
|
4821
|
+
]);
|
|
4822
|
+
return 0;
|
|
4823
|
+
}
|
|
4824
|
+
async function runAnalyticsSuggestions(command, io, runtime) {
|
|
4825
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4826
|
+
const payload = await requestAnalyticsApi({
|
|
4827
|
+
artifactId: command.artifactId,
|
|
4828
|
+
analyticsPath: "suggestions",
|
|
4829
|
+
method: "POST",
|
|
4830
|
+
baseUrl: credentials.baseUrl,
|
|
4831
|
+
token: credentials.token,
|
|
4832
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4833
|
+
range: command.range,
|
|
4834
|
+
body: {},
|
|
4835
|
+
});
|
|
4836
|
+
const suggestions = Array.isArray(payload.suggestions) ? payload.suggestions : [];
|
|
4837
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4838
|
+
`app_id: ${command.artifactId}`,
|
|
4839
|
+
`suggestions: ${suggestions.length}`,
|
|
4840
|
+
]);
|
|
4841
|
+
return 0;
|
|
4842
|
+
}
|
|
4843
|
+
async function runAnalyticsDashboards(command, io, runtime) {
|
|
4844
|
+
if (command.name && command.dryRun) {
|
|
4845
|
+
const payload = {
|
|
4846
|
+
ok: true,
|
|
4847
|
+
dryRun: true,
|
|
4848
|
+
artifactId: command.artifactId,
|
|
4849
|
+
request: {
|
|
4850
|
+
name: command.name,
|
|
4851
|
+
},
|
|
4852
|
+
};
|
|
4853
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4854
|
+
`app_id: ${command.artifactId}`,
|
|
4855
|
+
`name: ${command.name}`,
|
|
4856
|
+
"dry_run: true",
|
|
4857
|
+
]);
|
|
4858
|
+
return 0;
|
|
4859
|
+
}
|
|
4860
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4861
|
+
const payload = await requestAnalyticsApi({
|
|
4862
|
+
artifactId: command.artifactId,
|
|
4863
|
+
analyticsPath: "dashboards",
|
|
4864
|
+
method: command.name ? "POST" : "GET",
|
|
4865
|
+
baseUrl: credentials.baseUrl,
|
|
4866
|
+
token: credentials.token,
|
|
4867
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4868
|
+
body: command.name ? { name: command.name } : undefined,
|
|
4869
|
+
});
|
|
4870
|
+
const dashboards = Array.isArray(payload.dashboards) ? payload.dashboards : [];
|
|
4871
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4872
|
+
`app_id: ${command.artifactId}`,
|
|
4873
|
+
command.name
|
|
4874
|
+
? `dashboard_id: ${String(payload.dashboard?.id ?? "")}`
|
|
4875
|
+
: `dashboards: ${dashboards.length}`,
|
|
4876
|
+
]);
|
|
4877
|
+
return 0;
|
|
4878
|
+
}
|
|
4879
|
+
async function runAnalyticsExportCatalog(command, io, runtime) {
|
|
4880
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4881
|
+
const payload = await requestAnalyticsApi({
|
|
4882
|
+
artifactId: command.artifactId,
|
|
4883
|
+
analyticsPath: "capabilities",
|
|
4884
|
+
method: "GET",
|
|
4885
|
+
baseUrl: credentials.baseUrl,
|
|
4886
|
+
token: credentials.token,
|
|
4887
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4888
|
+
range: command.range,
|
|
4889
|
+
includeCatalog: true,
|
|
4890
|
+
});
|
|
4891
|
+
const catalog = payload.catalog && typeof payload.catalog === "object"
|
|
4892
|
+
? payload.catalog
|
|
4893
|
+
: null;
|
|
4894
|
+
const exportPayload = {
|
|
4895
|
+
ok: true,
|
|
4896
|
+
artifactId: command.artifactId,
|
|
4897
|
+
format: command.format,
|
|
4898
|
+
catalog,
|
|
4899
|
+
};
|
|
4900
|
+
io.stdout(JSON.stringify(exportPayload, null, 2));
|
|
4901
|
+
return 0;
|
|
4902
|
+
}
|
|
4903
|
+
async function runAnalyticsExportQuery(command, io, runtime) {
|
|
4904
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4905
|
+
const spec = await readAnalyticsSpec({
|
|
4906
|
+
specPath: command.specPath,
|
|
4907
|
+
stdin: runtime.stdin,
|
|
4908
|
+
});
|
|
4909
|
+
const payload = await requestAnalyticsApi({
|
|
4910
|
+
artifactId: command.artifactId,
|
|
4911
|
+
analyticsPath: "query",
|
|
4912
|
+
method: "POST",
|
|
4913
|
+
baseUrl: credentials.baseUrl,
|
|
4914
|
+
token: credentials.token,
|
|
4915
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4916
|
+
body: { spec },
|
|
4917
|
+
});
|
|
4918
|
+
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
4919
|
+
if (command.format === "csv") {
|
|
4920
|
+
io.stdout(rowsToCsv(rows));
|
|
4921
|
+
return 0;
|
|
4922
|
+
}
|
|
4923
|
+
io.stdout(JSON.stringify(payload, null, 2));
|
|
4924
|
+
return 0;
|
|
4925
|
+
}
|
|
4926
|
+
async function runAnalyticsChartsCreate(command, io, runtime) {
|
|
4927
|
+
const spec = await readAnalyticsSpec({
|
|
4928
|
+
specPath: command.specPath,
|
|
4929
|
+
stdin: runtime.stdin,
|
|
4930
|
+
});
|
|
4931
|
+
const body = {
|
|
4932
|
+
dashboardId: command.dashboardId,
|
|
4933
|
+
title: command.title,
|
|
4934
|
+
spec,
|
|
4935
|
+
layout: {},
|
|
4936
|
+
};
|
|
4937
|
+
if (command.dryRun) {
|
|
4938
|
+
const payload = {
|
|
4939
|
+
ok: true,
|
|
4940
|
+
dryRun: true,
|
|
4941
|
+
artifactId: command.artifactId,
|
|
4942
|
+
request: body,
|
|
4943
|
+
};
|
|
4944
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4945
|
+
`app_id: ${command.artifactId}`,
|
|
4946
|
+
`dashboard_id: ${command.dashboardId}`,
|
|
4947
|
+
`title: ${command.title}`,
|
|
4948
|
+
"dry_run: true",
|
|
4949
|
+
]);
|
|
4950
|
+
return 0;
|
|
4951
|
+
}
|
|
4952
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
4953
|
+
const payload = await requestAnalyticsApi({
|
|
4954
|
+
artifactId: command.artifactId,
|
|
4955
|
+
analyticsPath: "charts",
|
|
4956
|
+
method: "POST",
|
|
4957
|
+
baseUrl: credentials.baseUrl,
|
|
4958
|
+
token: credentials.token,
|
|
4959
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
4960
|
+
body,
|
|
4961
|
+
});
|
|
4962
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4963
|
+
`app_id: ${command.artifactId}`,
|
|
4964
|
+
`chart_id: ${String(payload.chart?.id ?? "")}`,
|
|
4965
|
+
]);
|
|
4966
|
+
return 0;
|
|
4967
|
+
}
|
|
4041
4968
|
export async function runTenderCli(args, io = {
|
|
4042
4969
|
stdout: (message) => console.log(message),
|
|
4043
4970
|
stderr: (message) => console.error(message),
|
|
@@ -4066,6 +4993,9 @@ export async function runTenderCli(args, io = {
|
|
|
4066
4993
|
else if (command.topic === "artifacts doctor") {
|
|
4067
4994
|
io.stdout(doctorHelp());
|
|
4068
4995
|
}
|
|
4996
|
+
else if (command.topic === "artifacts generate-env-types") {
|
|
4997
|
+
io.stdout(generateEnvTypesHelp());
|
|
4998
|
+
}
|
|
4069
4999
|
else if (command.topic === "artifacts list") {
|
|
4070
5000
|
io.stdout(artifactsListHelp());
|
|
4071
5001
|
}
|
|
@@ -4111,6 +5041,36 @@ export async function runTenderCli(args, io = {
|
|
|
4111
5041
|
else if (command.topic === "artifacts publish watch") {
|
|
4112
5042
|
io.stdout(publishWatchHelp());
|
|
4113
5043
|
}
|
|
5044
|
+
else if (command.topic === "artifacts analytics") {
|
|
5045
|
+
io.stdout(analyticsHelp());
|
|
5046
|
+
}
|
|
5047
|
+
else if (command.topic === "artifacts analytics summary") {
|
|
5048
|
+
io.stdout(analyticsSummaryHelp());
|
|
5049
|
+
}
|
|
5050
|
+
else if (command.topic === "artifacts analytics query") {
|
|
5051
|
+
io.stdout(analyticsQueryHelp());
|
|
5052
|
+
}
|
|
5053
|
+
else if (command.topic === "artifacts analytics capabilities") {
|
|
5054
|
+
io.stdout(analyticsCapabilitiesHelp());
|
|
5055
|
+
}
|
|
5056
|
+
else if (command.topic === "artifacts analytics suggestions") {
|
|
5057
|
+
io.stdout(analyticsSuggestionsHelp());
|
|
5058
|
+
}
|
|
5059
|
+
else if (command.topic === "artifacts analytics dashboards") {
|
|
5060
|
+
io.stdout(analyticsDashboardsHelp());
|
|
5061
|
+
}
|
|
5062
|
+
else if (command.topic === "artifacts analytics export") {
|
|
5063
|
+
io.stdout(analyticsExportHelp());
|
|
5064
|
+
}
|
|
5065
|
+
else if (command.topic === "artifacts analytics export catalog") {
|
|
5066
|
+
io.stdout(analyticsExportCatalogHelp());
|
|
5067
|
+
}
|
|
5068
|
+
else if (command.topic === "artifacts analytics export query") {
|
|
5069
|
+
io.stdout(analyticsExportQueryHelp());
|
|
5070
|
+
}
|
|
5071
|
+
else if (command.topic === "artifacts analytics charts create") {
|
|
5072
|
+
io.stdout(analyticsChartsCreateHelp());
|
|
5073
|
+
}
|
|
4114
5074
|
else if (command.topic === "artifacts") {
|
|
4115
5075
|
io.stdout(artifactsHelp());
|
|
4116
5076
|
}
|
|
@@ -4137,6 +5097,9 @@ export async function runTenderCli(args, io = {
|
|
|
4137
5097
|
if (command.command === "artifacts init") {
|
|
4138
5098
|
return await runArtifactsInit(command, io, runtime);
|
|
4139
5099
|
}
|
|
5100
|
+
if (command.command === "artifacts generate-env-types") {
|
|
5101
|
+
return await runGenerateEnvTypes(command, io);
|
|
5102
|
+
}
|
|
4140
5103
|
if (command.command === "artifacts context fetch") {
|
|
4141
5104
|
return await runContextFetch(command, io, runtime);
|
|
4142
5105
|
}
|
|
@@ -4176,6 +5139,30 @@ export async function runTenderCli(args, io = {
|
|
|
4176
5139
|
if (command.command === "artifacts publish watch") {
|
|
4177
5140
|
return await runPublishWatch(command, io, runtime);
|
|
4178
5141
|
}
|
|
5142
|
+
if (command.command === "artifacts analytics summary") {
|
|
5143
|
+
return await runAnalyticsSummary(command, io, runtime);
|
|
5144
|
+
}
|
|
5145
|
+
if (command.command === "artifacts analytics query") {
|
|
5146
|
+
return await runAnalyticsQuery(command, io, runtime);
|
|
5147
|
+
}
|
|
5148
|
+
if (command.command === "artifacts analytics capabilities") {
|
|
5149
|
+
return await runAnalyticsCapabilities(command, io, runtime);
|
|
5150
|
+
}
|
|
5151
|
+
if (command.command === "artifacts analytics suggestions") {
|
|
5152
|
+
return await runAnalyticsSuggestions(command, io, runtime);
|
|
5153
|
+
}
|
|
5154
|
+
if (command.command === "artifacts analytics dashboards") {
|
|
5155
|
+
return await runAnalyticsDashboards(command, io, runtime);
|
|
5156
|
+
}
|
|
5157
|
+
if (command.command === "artifacts analytics export catalog") {
|
|
5158
|
+
return await runAnalyticsExportCatalog(command, io, runtime);
|
|
5159
|
+
}
|
|
5160
|
+
if (command.command === "artifacts analytics export query") {
|
|
5161
|
+
return await runAnalyticsExportQuery(command, io, runtime);
|
|
5162
|
+
}
|
|
5163
|
+
if (command.command === "artifacts analytics charts create") {
|
|
5164
|
+
return await runAnalyticsChartsCreate(command, io, runtime);
|
|
5165
|
+
}
|
|
4179
5166
|
if (command.command === "artifacts git remote") {
|
|
4180
5167
|
return await runGitRemote(command, io, runtime);
|
|
4181
5168
|
}
|
|
@@ -4190,8 +5177,8 @@ export async function runTenderCli(args, io = {
|
|
|
4190
5177
|
const currentFile = fileURLToPath(import.meta.url);
|
|
4191
5178
|
function shouldReadEntrypointStdin(args) {
|
|
4192
5179
|
return ((args[0] === "artifacts" || args[0] === "app") &&
|
|
4193
|
-
args[1] === "git" &&
|
|
4194
|
-
|
|
5180
|
+
((args[1] === "git" && args[2] === "credential") ||
|
|
5181
|
+
(args[1] === "analytics" && args.includes("--spec") && args.includes("-"))));
|
|
4195
5182
|
}
|
|
4196
5183
|
async function readEntrypointStdin() {
|
|
4197
5184
|
if (process.stdin.isTTY) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenderprompt/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tender": "./bin/tender.js"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"prepack": "yarn build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tenderprompt/tender-app-validator": "0.1.
|
|
26
|
+
"@tenderprompt/tender-app-validator": "0.1.3"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=20"
|