@runcofactor/cli 0.15.2 → 0.16.0
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/dist/{account-62I7FG5G.js → account-ZMV7NEFM.js} +4 -4
- package/dist/account-ZMV7NEFM.js.map +1 -0
- package/dist/{api-catalog-CAJUASVC.js → api-catalog-6HNOGVPA.js} +3 -3
- package/dist/{attachment-RL4AZUJE.js → attachment-QAHR54HQ.js} +3 -3
- package/dist/{augmentor-GAF3MFY6.js → augmentor-7T3L6ZR4.js} +3 -3
- package/dist/{chunk-26U3DGUM.js → chunk-7YJTQCPX.js} +3 -5
- package/dist/chunk-7YJTQCPX.js.map +1 -0
- package/dist/{chunk-PXFDH6YL.js → chunk-A4ISG3VX.js} +55 -15
- package/dist/{chunk-PXFDH6YL.js.map → chunk-A4ISG3VX.js.map} +1 -1
- package/dist/{chunk-5R4HMSNX.js → chunk-AI3Y3AZH.js} +24834 -24813
- package/dist/chunk-AI3Y3AZH.js.map +1 -0
- package/dist/{chunk-HKQ5XQAQ.js → chunk-CEXV3MOC.js} +3 -3
- package/dist/{chunk-VKBTI5EF.js → chunk-D6AVOPOS.js} +2 -2
- package/dist/{chunk-ZXRVFFCY.js → chunk-DT2CEH2V.js} +2 -2
- package/dist/{chunk-IWKJQZJQ.js → chunk-GZVCNYR4.js} +55 -55
- package/dist/chunk-GZVCNYR4.js.map +1 -0
- package/dist/{chunk-CJEKEW7R.js → chunk-LFFWD5JZ.js} +2 -2
- package/dist/{chunk-UZ2ZA5A6.js → chunk-RGDIVW3O.js} +2 -2
- package/dist/{chunk-CFJXYPTF.js → chunk-UAJUZLDE.js} +11 -3
- package/dist/chunk-UAJUZLDE.js.map +1 -0
- package/dist/{credential-JGVSEGVH.js → credential-X6QKX473.js} +3 -3
- package/dist/{credential-template-CWWMOKB2.js → credential-template-GD3D4MBI.js} +3 -3
- package/dist/{debug-workflow-BXRU72YZ.js → debug-workflow-AAEOAME2.js} +6 -6
- package/dist/{src-CV57ZD6I.js → dist-QLTVBUMD.js} +7 -9
- package/dist/{docs-PVBXPTGR.js → docs-BVUEXBY3.js} +3 -3
- package/dist/{environment-WZKIO3R3.js → environment-WE7VVE6I.js} +3 -3
- package/dist/{feature-flag-K5REGGDY.js → feature-flag-VJO2TJFT.js} +9 -8
- package/dist/feature-flag-VJO2TJFT.js.map +1 -0
- package/dist/index.js +129 -44
- package/dist/index.js.map +1 -1
- package/dist/{init-FFUZO5D7.js → init-RQHHKJJD.js} +8 -8
- package/dist/{install-XRKEQAFZ.js → install-CQLHNZ6U.js} +3 -3
- package/dist/{issues-I3V6GEXU.js → issues-HK2JA5JS.js} +3 -3
- package/dist/{knobs-6WLJMB44.js → knobs-ETJAV6YM.js} +3 -3
- package/dist/{list-PHC7VYNS.js → list-F6DGGW3Z.js} +3 -3
- package/dist/{local-VGYRSW22.js → local-CMAY54I5.js} +5 -5
- package/dist/{login-QPU2BKG2.js → login-6CKG664C.js} +8 -8
- package/dist/{mcp-LA452TOA.js → mcp-GC2WOOZO.js} +5 -6
- package/dist/{mcp-LA452TOA.js.map → mcp-GC2WOOZO.js.map} +1 -1
- package/dist/metrics-IXICURAA.js +367 -0
- package/dist/metrics-IXICURAA.js.map +1 -0
- package/dist/{product-RSZV5HMB.js → product-BUJWPYKB.js} +4 -3
- package/dist/product-BUJWPYKB.js.map +1 -0
- package/dist/{property-YP4GHQAR.js → property-4EXPWRNT.js} +3 -3
- package/dist/{record-KWKHP2ZY.js → record-HL4QA7OX.js} +4 -4
- package/dist/{record-KWKHP2ZY.js.map → record-HL4QA7OX.js.map} +1 -1
- package/dist/{release-NDRHPW6V.js → release-2SMK5Y3V.js} +3 -3
- package/dist/{sandbox-OOCANWOG.js → sandbox-ORAPKOH3.js} +3 -3
- package/dist/{scenarios-2HRLCGFY.js → scenarios-DM5FDCAF.js} +3 -3
- package/dist/{session-M4ULYDYP.js → session-X6IYGWYG.js} +14 -16
- package/dist/{session-M4ULYDYP.js.map → session-X6IYGWYG.js.map} +1 -1
- package/dist/{skill-4DGPRDUC.js → skill-ZU5V2EE5.js} +24 -14
- package/dist/skill-ZU5V2EE5.js.map +1 -0
- package/dist/{spec-XZ2TKUFZ.js → spec-KO5QD6AY.js} +3 -3
- package/dist/{src-HL7HEJIW.js → src-DSD34JIX.js} +2 -2
- package/dist/{src-7LAXXTVG.js → src-HTGNKBUF.js} +5 -7
- package/dist/{src-7LAXXTVG.js.map → src-HTGNKBUF.js.map} +1 -1
- package/dist/{src-OU7544PM.js → src-PDYXACRP.js} +2 -2
- package/dist/{start-IYKF3C3X.js → start-2ECN4LX5.js} +6 -6
- package/dist/{tag-T45MMSCH.js → tag-R5I72HJ6.js} +4 -4
- package/dist/{task-MCUHAVTD.js → task-V3JMSJIM.js} +3 -3
- package/dist/{test-suite-BICBRSBP.js → test-suite-NOYVK6YX.js} +5 -5
- package/dist/{trace-2BAPMYAS.js → trace-NOYEKNO4.js} +4 -5
- package/dist/{triage-M3RZ2ASB.js → triage-4RFATHBE.js} +4 -4
- package/dist/utilities-3WMXE743.js +441 -0
- package/dist/utilities-3WMXE743.js.map +1 -0
- package/dist/{workflow-ZGJU4QCV.js → workflow-DC7WOTTW.js} +13 -17
- package/dist/workflow-DC7WOTTW.js.map +1 -0
- package/dist/{workflow-cache-M4N325UT.js → workflow-cache-SDSI6SMF.js} +6 -6
- package/dist/{workflow-cache-M4N325UT.js.map → workflow-cache-SDSI6SMF.js.map} +1 -1
- package/dist/{workflow-debug-W4USZH35.js → workflow-debug-6UQYP67Q.js} +6 -7
- package/dist/{workflow-debug-W4USZH35.js.map → workflow-debug-6UQYP67Q.js.map} +1 -1
- package/dist/{workflow-proposals-5E3DTYZB.js → workflow-proposals-67AVGMHX.js} +6 -6
- package/package.json +1 -1
- package/dist/account-62I7FG5G.js.map +0 -1
- package/dist/chunk-26U3DGUM.js.map +0 -1
- package/dist/chunk-5R4HMSNX.js.map +0 -1
- package/dist/chunk-CFJXYPTF.js.map +0 -1
- package/dist/chunk-IWKJQZJQ.js.map +0 -1
- package/dist/chunk-KRWRUU6R.js +0 -114
- package/dist/chunk-KRWRUU6R.js.map +0 -1
- package/dist/feature-flag-K5REGGDY.js.map +0 -1
- package/dist/pdf-extract-YHE54XU4.js +0 -13
- package/dist/product-RSZV5HMB.js.map +0 -1
- package/dist/skill-4DGPRDUC.js.map +0 -1
- package/dist/triage-M3RZ2ASB.js.map +0 -1
- package/dist/workflow-ZGJU4QCV.js.map +0 -1
- /package/dist/{api-catalog-CAJUASVC.js.map → api-catalog-6HNOGVPA.js.map} +0 -0
- /package/dist/{attachment-RL4AZUJE.js.map → attachment-QAHR54HQ.js.map} +0 -0
- /package/dist/{augmentor-GAF3MFY6.js.map → augmentor-7T3L6ZR4.js.map} +0 -0
- /package/dist/{chunk-HKQ5XQAQ.js.map → chunk-CEXV3MOC.js.map} +0 -0
- /package/dist/{chunk-VKBTI5EF.js.map → chunk-D6AVOPOS.js.map} +0 -0
- /package/dist/{chunk-ZXRVFFCY.js.map → chunk-DT2CEH2V.js.map} +0 -0
- /package/dist/{chunk-CJEKEW7R.js.map → chunk-LFFWD5JZ.js.map} +0 -0
- /package/dist/{chunk-UZ2ZA5A6.js.map → chunk-RGDIVW3O.js.map} +0 -0
- /package/dist/{credential-JGVSEGVH.js.map → credential-X6QKX473.js.map} +0 -0
- /package/dist/{credential-template-CWWMOKB2.js.map → credential-template-GD3D4MBI.js.map} +0 -0
- /package/dist/{debug-workflow-BXRU72YZ.js.map → debug-workflow-AAEOAME2.js.map} +0 -0
- /package/dist/{pdf-extract-YHE54XU4.js.map → dist-QLTVBUMD.js.map} +0 -0
- /package/dist/{docs-PVBXPTGR.js.map → docs-BVUEXBY3.js.map} +0 -0
- /package/dist/{environment-WZKIO3R3.js.map → environment-WE7VVE6I.js.map} +0 -0
- /package/dist/{init-FFUZO5D7.js.map → init-RQHHKJJD.js.map} +0 -0
- /package/dist/{install-XRKEQAFZ.js.map → install-CQLHNZ6U.js.map} +0 -0
- /package/dist/{issues-I3V6GEXU.js.map → issues-HK2JA5JS.js.map} +0 -0
- /package/dist/{knobs-6WLJMB44.js.map → knobs-ETJAV6YM.js.map} +0 -0
- /package/dist/{list-PHC7VYNS.js.map → list-F6DGGW3Z.js.map} +0 -0
- /package/dist/{local-VGYRSW22.js.map → local-CMAY54I5.js.map} +0 -0
- /package/dist/{login-QPU2BKG2.js.map → login-6CKG664C.js.map} +0 -0
- /package/dist/{property-YP4GHQAR.js.map → property-4EXPWRNT.js.map} +0 -0
- /package/dist/{release-NDRHPW6V.js.map → release-2SMK5Y3V.js.map} +0 -0
- /package/dist/{sandbox-OOCANWOG.js.map → sandbox-ORAPKOH3.js.map} +0 -0
- /package/dist/{scenarios-2HRLCGFY.js.map → scenarios-DM5FDCAF.js.map} +0 -0
- /package/dist/{spec-XZ2TKUFZ.js.map → spec-KO5QD6AY.js.map} +0 -0
- /package/dist/{src-HL7HEJIW.js.map → src-DSD34JIX.js.map} +0 -0
- /package/dist/{src-CV57ZD6I.js.map → src-PDYXACRP.js.map} +0 -0
- /package/dist/{start-IYKF3C3X.js.map → start-2ECN4LX5.js.map} +0 -0
- /package/dist/{src-OU7544PM.js.map → tag-R5I72HJ6.js.map} +0 -0
- /package/dist/{task-MCUHAVTD.js.map → task-V3JMSJIM.js.map} +0 -0
- /package/dist/{test-suite-BICBRSBP.js.map → test-suite-NOYVK6YX.js.map} +0 -0
- /package/dist/{tag-T45MMSCH.js.map → trace-NOYEKNO4.js.map} +0 -0
- /package/dist/{trace-2BAPMYAS.js.map → triage-4RFATHBE.js.map} +0 -0
- /package/dist/{workflow-proposals-5E3DTYZB.js.map → workflow-proposals-67AVGMHX.js.map} +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
apiRequest,
|
|
4
|
+
fetchList
|
|
5
|
+
} from "./chunk-UAJUZLDE.js";
|
|
6
|
+
import "./chunk-3CZU7XCS.js";
|
|
7
|
+
import {
|
|
8
|
+
getArgValue,
|
|
9
|
+
hasFlag,
|
|
10
|
+
resolveConfig
|
|
11
|
+
} from "./chunk-IGHDIWZU.js";
|
|
12
|
+
import "./chunk-32P26BZT.js";
|
|
13
|
+
import "./chunk-JWHYXEA2.js";
|
|
14
|
+
import "./chunk-GZVCNYR4.js";
|
|
15
|
+
import "./chunk-VKVL7WBN.js";
|
|
16
|
+
|
|
17
|
+
// src/metrics.ts
|
|
18
|
+
import process from "process";
|
|
19
|
+
function fmtValue(metric) {
|
|
20
|
+
if (metric.lastError) return `error: ${metric.lastError}`;
|
|
21
|
+
if (metric.lastValue === null || metric.lastValue === void 0) return "\u2014";
|
|
22
|
+
return `${metric.lastValue}${metric.unit ? ` ${metric.unit}` : ""}`;
|
|
23
|
+
}
|
|
24
|
+
function formatMetric(metric) {
|
|
25
|
+
const status = metric.enabled ? "" : " [disabled]";
|
|
26
|
+
const cadence = `${metric.cadenceSeconds}s`;
|
|
27
|
+
const cards = metric.cardIds.length > 0 ? ` cards=${metric.cardIds.length}` : "";
|
|
28
|
+
return ` ${metric.name} (${metric.source}, every ${cadence}, latest=${fmtValue(metric)})${cards}${status}`;
|
|
29
|
+
}
|
|
30
|
+
async function listMetrics(apiUrl, token) {
|
|
31
|
+
return fetchList(apiUrl, token, "/superadmin/product-metrics", "metrics");
|
|
32
|
+
}
|
|
33
|
+
async function handleList(argv, apiUrl, token) {
|
|
34
|
+
const metrics = await listMetrics(apiUrl, token);
|
|
35
|
+
if (hasFlag(argv, "--json")) {
|
|
36
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (metrics.length === 0) {
|
|
40
|
+
console.log("No metrics found.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
for (const metric of metrics) console.log(formatMetric(metric));
|
|
44
|
+
}
|
|
45
|
+
async function handleGet(argv, apiUrl, token) {
|
|
46
|
+
const name = argv[0];
|
|
47
|
+
if (!name || name.startsWith("--")) {
|
|
48
|
+
console.error("Usage: cofactor metrics get <name> [--datapoints N] [--json]");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const metrics = await listMetrics(apiUrl, token);
|
|
52
|
+
const metric = metrics.find((mt) => mt.name === name);
|
|
53
|
+
if (!metric) {
|
|
54
|
+
console.error(`Error: metric '${name}' not found`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const nRaw = getArgValue(argv, "--datapoints");
|
|
58
|
+
const n = nRaw ? Math.max(1, parseInt(nRaw, 10) || 0) : 0;
|
|
59
|
+
const datapoints = n > 0 ? await fetchList(
|
|
60
|
+
apiUrl,
|
|
61
|
+
token,
|
|
62
|
+
`/superadmin/product-metrics/${encodeURIComponent(name)}/datapoints`,
|
|
63
|
+
"datapoints"
|
|
64
|
+
) : [];
|
|
65
|
+
if (hasFlag(argv, "--json")) {
|
|
66
|
+
console.log(JSON.stringify({ metric, datapoints: datapoints.slice(-n) }, null, 2));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.log(`${metric.name}${metric.enabled ? "" : " [disabled]"}`);
|
|
70
|
+
if (metric.description) console.log(` ${metric.description}`);
|
|
71
|
+
console.log(` source: ${metric.source}`);
|
|
72
|
+
if (metric.source === "postgres") console.log(` query: ${metric.query ?? "(none)"}`);
|
|
73
|
+
if (metric.source === "cloudwatch") {
|
|
74
|
+
console.log(` cloudwatch: ${JSON.stringify(metric.cloudwatchConfig)}`);
|
|
75
|
+
}
|
|
76
|
+
console.log(` cadence: every ${metric.cadenceSeconds}s`);
|
|
77
|
+
console.log(` latest: ${fmtValue(metric)}`);
|
|
78
|
+
console.log(` updated: ${metric.lastCollectedAt ?? "never"}`);
|
|
79
|
+
if (metric.cardIds.length > 0) console.log(` cards: ${metric.cardIds.join(", ")}`);
|
|
80
|
+
if (n > 0) {
|
|
81
|
+
console.log(` recent datapoints (last ${n}):`);
|
|
82
|
+
for (const dp of datapoints.slice(-n)) {
|
|
83
|
+
const v = dp.error ? `error: ${dp.error}` : dp.value;
|
|
84
|
+
console.log(` ${dp.capturedAt} ${v}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function parseCloudwatchConfig(argv) {
|
|
89
|
+
const namespace = getArgValue(argv, "--namespace");
|
|
90
|
+
const metricName = getArgValue(argv, "--metric");
|
|
91
|
+
const stat = getArgValue(argv, "--stat") ?? "Average";
|
|
92
|
+
const period = getArgValue(argv, "--period");
|
|
93
|
+
if (!namespace || !metricName) {
|
|
94
|
+
console.error("Error: cloudwatch metrics need --namespace and --metric.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const dimsRaw = getArgValue(argv, "--dimensions");
|
|
98
|
+
let dimensions;
|
|
99
|
+
if (dimsRaw) {
|
|
100
|
+
dimensions = {};
|
|
101
|
+
for (const pair of dimsRaw.split(",")) {
|
|
102
|
+
const [k, ...rest] = pair.split("=");
|
|
103
|
+
if (k && rest.length > 0) dimensions[k.trim()] = rest.join("=").trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
namespace,
|
|
108
|
+
metricName,
|
|
109
|
+
stat,
|
|
110
|
+
periodSeconds: period ? parseInt(period, 10) : 300,
|
|
111
|
+
...dimensions ? { dimensions } : {}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function handleCreate(argv, apiUrl, token) {
|
|
115
|
+
const name = argv[0];
|
|
116
|
+
if (!name || name.startsWith("--")) {
|
|
117
|
+
console.error("Error: Missing metric name.");
|
|
118
|
+
console.error(
|
|
119
|
+
"Usage: cofactor metrics create <name> --source <postgres|cloudwatch> --cadence <seconds> [...]"
|
|
120
|
+
);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const source = getArgValue(argv, "--source");
|
|
124
|
+
if (source !== "postgres" && source !== "cloudwatch") {
|
|
125
|
+
console.error("Error: --source must be postgres or cloudwatch.");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const cadenceRaw = getArgValue(argv, "--cadence");
|
|
129
|
+
const cadenceSeconds = cadenceRaw ? parseInt(cadenceRaw, 10) : NaN;
|
|
130
|
+
if (!Number.isInteger(cadenceSeconds) || cadenceSeconds < 60) {
|
|
131
|
+
console.error("Error: --cadence <seconds> is required (minimum 60).");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
const description = getArgValue(argv, "--description");
|
|
135
|
+
const unit = getArgValue(argv, "--unit");
|
|
136
|
+
const body = {
|
|
137
|
+
name,
|
|
138
|
+
source,
|
|
139
|
+
cadenceSeconds,
|
|
140
|
+
...description !== void 0 ? { description } : {},
|
|
141
|
+
...unit !== void 0 ? { unit } : {}
|
|
142
|
+
};
|
|
143
|
+
if (source === "postgres") {
|
|
144
|
+
const query = getArgValue(argv, "--query");
|
|
145
|
+
if (!query) {
|
|
146
|
+
console.error('Error: postgres metrics need --query "<read-only SQL returning one number>".');
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
body.query = query;
|
|
150
|
+
} else {
|
|
151
|
+
body.cloudwatchConfig = parseCloudwatchConfig(argv);
|
|
152
|
+
}
|
|
153
|
+
const result = await apiRequest(
|
|
154
|
+
apiUrl,
|
|
155
|
+
token,
|
|
156
|
+
"POST",
|
|
157
|
+
"/superadmin/product-metrics",
|
|
158
|
+
body
|
|
159
|
+
);
|
|
160
|
+
if (!result.ok || !result.metric) {
|
|
161
|
+
console.error(`Error: ${result.error ?? "Failed to create metric"}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
console.log(`Created metric ${result.metric.name} (${result.metric.source}, every ${result.metric.cadenceSeconds}s)`);
|
|
165
|
+
}
|
|
166
|
+
async function handleSet(argv, apiUrl, token) {
|
|
167
|
+
const name = argv[0];
|
|
168
|
+
if (!name || name.startsWith("--")) {
|
|
169
|
+
console.error(
|
|
170
|
+
"Usage: cofactor metrics set <name> [--query <sql>] [--cadence <s>] [--unit <u>] [--description <text>] [--enable|--disable]"
|
|
171
|
+
);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const body = {};
|
|
175
|
+
const query = getArgValue(argv, "--query");
|
|
176
|
+
if (query !== void 0) body.query = query;
|
|
177
|
+
const cadenceRaw = getArgValue(argv, "--cadence");
|
|
178
|
+
if (cadenceRaw !== void 0) body.cadenceSeconds = parseInt(cadenceRaw, 10);
|
|
179
|
+
const unit = getArgValue(argv, "--unit");
|
|
180
|
+
if (unit !== void 0) body.unit = unit;
|
|
181
|
+
const description = getArgValue(argv, "--description");
|
|
182
|
+
if (description !== void 0) body.description = description;
|
|
183
|
+
if (hasFlag(argv, "--enable")) body.enabled = true;
|
|
184
|
+
if (hasFlag(argv, "--disable")) body.enabled = false;
|
|
185
|
+
if (Object.keys(body).length === 0) {
|
|
186
|
+
console.error("Error: provide at least one field to update.");
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const result = await apiRequest(
|
|
190
|
+
apiUrl,
|
|
191
|
+
token,
|
|
192
|
+
"PATCH",
|
|
193
|
+
`/superadmin/product-metrics/${encodeURIComponent(name)}`,
|
|
194
|
+
body
|
|
195
|
+
);
|
|
196
|
+
if (!result.ok || !result.metric) {
|
|
197
|
+
console.error(`Error: ${result.error ?? "Failed to update metric"}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
console.log(`Updated metric ${result.metric.name}`);
|
|
201
|
+
}
|
|
202
|
+
async function handleDelete(argv, apiUrl, token) {
|
|
203
|
+
const name = argv[0];
|
|
204
|
+
if (!name || name.startsWith("--")) {
|
|
205
|
+
console.error("Usage: cofactor metrics delete <name>");
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const result = await apiRequest(
|
|
209
|
+
apiUrl,
|
|
210
|
+
token,
|
|
211
|
+
"DELETE",
|
|
212
|
+
`/superadmin/product-metrics/${encodeURIComponent(name)}`
|
|
213
|
+
);
|
|
214
|
+
if (!result.ok) {
|
|
215
|
+
console.error(`Error: ${result.error}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
console.log(`Deleted metric ${name}`);
|
|
219
|
+
}
|
|
220
|
+
async function handleCollect(argv, apiUrl, token) {
|
|
221
|
+
const name = argv[0];
|
|
222
|
+
if (!name || name.startsWith("--")) {
|
|
223
|
+
console.error("Usage: cofactor metrics collect <name>");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
const result = await apiRequest(
|
|
227
|
+
apiUrl,
|
|
228
|
+
token,
|
|
229
|
+
"POST",
|
|
230
|
+
`/superadmin/product-metrics/${encodeURIComponent(name)}/collect`
|
|
231
|
+
);
|
|
232
|
+
if (!result.ok) {
|
|
233
|
+
console.error(`Error: ${result.error ?? "Failed to collect metric"}`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
if (result.skipped) {
|
|
237
|
+
console.log(`Skipped ${name}: ${result.reason ?? "collector unavailable in this environment"}`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const dp = result.datapoint;
|
|
241
|
+
if (!dp) {
|
|
242
|
+
console.error("Error: collect returned no datapoint");
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
if (dp.error) {
|
|
246
|
+
console.log(`Collected ${name}: error \u2014 ${dp.error}`);
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`Collected ${name}: ${dp.value} (at ${dp.capturedAt})`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function handleLink(argv, apiUrl, token) {
|
|
252
|
+
const name = argv[0];
|
|
253
|
+
const cardId = getArgValue(argv, "--card");
|
|
254
|
+
const clear = hasFlag(argv, "--clear");
|
|
255
|
+
if (!name || name.startsWith("--") || !cardId) {
|
|
256
|
+
console.error("Usage: cofactor metrics link <name> --card <cardId> [--clear]");
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
const result = await apiRequest(
|
|
260
|
+
apiUrl,
|
|
261
|
+
token,
|
|
262
|
+
"POST",
|
|
263
|
+
`/superadmin/product-metrics/${encodeURIComponent(name)}/cards`,
|
|
264
|
+
{ cardId, action: clear ? "unlink" : "link" }
|
|
265
|
+
);
|
|
266
|
+
if (!result.ok) {
|
|
267
|
+
console.error(`Error: ${result.error}`);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
console.log(
|
|
271
|
+
clear ? `Unlinked metric ${name} from card ${cardId}` : `Linked metric ${name} to card ${cardId}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
async function handleDatapoints(argv, apiUrl, token) {
|
|
275
|
+
const name = argv[0];
|
|
276
|
+
if (!name || name.startsWith("--")) {
|
|
277
|
+
console.error("Usage: cofactor metrics datapoints <name> [--since <ISO>] [--json]");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
const since = getArgValue(argv, "--since");
|
|
281
|
+
const path = `/superadmin/product-metrics/${encodeURIComponent(name)}/datapoints${since ? `?since=${encodeURIComponent(since)}` : ""}`;
|
|
282
|
+
const datapoints = await fetchList(apiUrl, token, path, "datapoints");
|
|
283
|
+
if (hasFlag(argv, "--json")) {
|
|
284
|
+
console.log(JSON.stringify(datapoints, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (datapoints.length === 0) {
|
|
288
|
+
console.log("No datapoints.");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
for (const dp of datapoints) {
|
|
292
|
+
const v = dp.error ? `error: ${dp.error}` : dp.value;
|
|
293
|
+
console.log(` ${dp.capturedAt} ${v}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function printMetricsHelp() {
|
|
297
|
+
console.log(
|
|
298
|
+
[
|
|
299
|
+
"Usage: cofactor metrics <sub-command> [options]",
|
|
300
|
+
"",
|
|
301
|
+
"Manage first-class product metrics for the org you are logged into.",
|
|
302
|
+
"",
|
|
303
|
+
"Sub-commands:",
|
|
304
|
+
" list [--json] List metrics with latest value",
|
|
305
|
+
" get <name> [--datapoints N] [--json] Show a metric + recent values",
|
|
306
|
+
' create <name> --source postgres --query "<SQL>" --cadence <s> [--unit <u>] [--description <t>]',
|
|
307
|
+
" create <name> --source cloudwatch --namespace <ns> --metric <m> [--stat Average] [--period 300] [--dimensions K=V,K=V] --cadence <s>",
|
|
308
|
+
" Create a metric",
|
|
309
|
+
" set <name> [--query <sql>] [--cadence <s>] [--unit <u>] [--description <t>] [--enable|--disable]",
|
|
310
|
+
" Update a metric",
|
|
311
|
+
" delete <name> Delete a metric (and its datapoints)",
|
|
312
|
+
" collect <name> Pull the metric value now",
|
|
313
|
+
" link <name> --card <cardId> [--clear] Link/unlink a product card",
|
|
314
|
+
" datapoints <name> [--since <ISO>] [--json] Show the metric time series",
|
|
315
|
+
"",
|
|
316
|
+
"Sources: postgres (read-only SQL returning one number), cloudwatch (GetMetricData)",
|
|
317
|
+
"",
|
|
318
|
+
"Options:",
|
|
319
|
+
" --env <env> Target environment (prod, dev, local)",
|
|
320
|
+
" --json Output as JSON (list/get/datapoints)",
|
|
321
|
+
" --api-url <url> API URL override (takes precedence over --env)",
|
|
322
|
+
" --token <key> API token override"
|
|
323
|
+
].join("\n")
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
async function runMetrics(argv) {
|
|
327
|
+
const [subCommand, ...rest] = argv;
|
|
328
|
+
if (!subCommand || subCommand === "help" || hasFlag(argv, "--help", "-h")) {
|
|
329
|
+
printMetricsHelp();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const { apiUrl, token } = await resolveConfig(argv);
|
|
333
|
+
switch (subCommand) {
|
|
334
|
+
case "list":
|
|
335
|
+
await handleList(rest, apiUrl, token);
|
|
336
|
+
break;
|
|
337
|
+
case "get":
|
|
338
|
+
await handleGet(rest, apiUrl, token);
|
|
339
|
+
break;
|
|
340
|
+
case "create":
|
|
341
|
+
await handleCreate(rest, apiUrl, token);
|
|
342
|
+
break;
|
|
343
|
+
case "set":
|
|
344
|
+
await handleSet(rest, apiUrl, token);
|
|
345
|
+
break;
|
|
346
|
+
case "delete":
|
|
347
|
+
await handleDelete(rest, apiUrl, token);
|
|
348
|
+
break;
|
|
349
|
+
case "collect":
|
|
350
|
+
await handleCollect(rest, apiUrl, token);
|
|
351
|
+
break;
|
|
352
|
+
case "link":
|
|
353
|
+
await handleLink(rest, apiUrl, token);
|
|
354
|
+
break;
|
|
355
|
+
case "datapoints":
|
|
356
|
+
await handleDatapoints(rest, apiUrl, token);
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
console.error(`Unknown sub-command: ${subCommand}`);
|
|
360
|
+
printMetricsHelp();
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export {
|
|
365
|
+
runMetrics
|
|
366
|
+
};
|
|
367
|
+
//# sourceMappingURL=metrics-IXICURAA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * CLI Product Metrics Management\n *\n * Superadmin CRUD + read for first-class product metrics (metric_definitions),\n * scoped to the org the CLI user is logged into. Backed by `/superadmin/product-metrics/*`.\n * Lets an agent define a metric (postgres SQL or cloudwatch query), pull it on a\n * cadence, review recent values, and associate it with product board cards.\n */\n\nimport process from 'node:process';\nimport { resolveConfig, getArgValue, hasFlag } from './auth.js';\nimport { apiRequest, fetchList } from './cli-helpers.js';\n\ntype MetricSource = 'postgres' | 'cloudwatch';\n\ntype CloudwatchConfig = {\n namespace: string;\n metricName: string;\n stat: string;\n periodSeconds: number;\n dimensions?: Record<string, string>;\n};\n\ntype Metric = {\n id: string;\n name: string;\n description: string | null;\n source: MetricSource;\n query: string | null;\n cloudwatchConfig: CloudwatchConfig | null;\n unit: string | null;\n cadenceSeconds: number;\n enabled: boolean;\n lastValue: number | null;\n lastCollectedAt: string | null;\n lastError: string | null;\n cardIds: string[];\n};\n\ntype DataPoint = { value: number | null; error: string | null; capturedAt: string };\n\ntype MetricApiResponse = {\n ok: boolean;\n error?: string;\n metric?: Metric;\n datapoint?: DataPoint | null;\n /** collect: true when the collector couldn't run in this environment. */\n skipped?: boolean;\n reason?: string;\n};\n\nfunction fmtValue(metric: Pick<Metric, 'lastValue' | 'unit' | 'lastError'>): string {\n if (metric.lastError) return `error: ${metric.lastError}`;\n if (metric.lastValue === null || metric.lastValue === undefined) return '—';\n return `${metric.lastValue}${metric.unit ? ` ${metric.unit}` : ''}`;\n}\n\nfunction formatMetric(metric: Metric): string {\n const status = metric.enabled ? '' : ' [disabled]';\n const cadence = `${metric.cadenceSeconds}s`;\n const cards = metric.cardIds.length > 0 ? ` cards=${metric.cardIds.length}` : '';\n return ` ${metric.name} (${metric.source}, every ${cadence}, latest=${fmtValue(metric)})${cards}${status}`;\n}\n\nasync function listMetrics(apiUrl: string, token: string): Promise<Metric[]> {\n return fetchList<Metric>(apiUrl, token, '/superadmin/product-metrics', 'metrics');\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const metrics = await listMetrics(apiUrl, token);\n if (hasFlag(argv, '--json')) {\n console.log(JSON.stringify(metrics, null, 2));\n return;\n }\n if (metrics.length === 0) {\n console.log('No metrics found.');\n return;\n }\n for (const metric of metrics) console.log(formatMetric(metric));\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error('Usage: cofactor metrics get <name> [--datapoints N] [--json]');\n process.exit(1);\n }\n const metrics = await listMetrics(apiUrl, token);\n const metric = metrics.find((mt) => mt.name === name);\n if (!metric) {\n console.error(`Error: metric '${name}' not found`);\n process.exit(1);\n }\n\n const nRaw = getArgValue(argv, '--datapoints');\n const n = nRaw ? Math.max(1, parseInt(nRaw, 10) || 0) : 0;\n const datapoints = n > 0\n ? await fetchList<DataPoint>(\n apiUrl,\n token,\n `/superadmin/product-metrics/${encodeURIComponent(name)}/datapoints`,\n 'datapoints'\n )\n : [];\n\n if (hasFlag(argv, '--json')) {\n console.log(JSON.stringify({ metric, datapoints: datapoints.slice(-n) }, null, 2));\n return;\n }\n\n console.log(`${metric.name}${metric.enabled ? '' : ' [disabled]'}`);\n if (metric.description) console.log(` ${metric.description}`);\n console.log(` source: ${metric.source}`);\n if (metric.source === 'postgres') console.log(` query: ${metric.query ?? '(none)'}`);\n if (metric.source === 'cloudwatch') {\n console.log(` cloudwatch: ${JSON.stringify(metric.cloudwatchConfig)}`);\n }\n console.log(` cadence: every ${metric.cadenceSeconds}s`);\n console.log(` latest: ${fmtValue(metric)}`);\n console.log(` updated: ${metric.lastCollectedAt ?? 'never'}`);\n if (metric.cardIds.length > 0) console.log(` cards: ${metric.cardIds.join(', ')}`);\n\n if (n > 0) {\n console.log(` recent datapoints (last ${n}):`);\n for (const dp of datapoints.slice(-n)) {\n const v = dp.error ? `error: ${dp.error}` : dp.value;\n console.log(` ${dp.capturedAt} ${v}`);\n }\n }\n}\n\n/** Parse the cloudwatch flags into a config (or exit with a usage error). */\nfunction parseCloudwatchConfig(argv: string[]): CloudwatchConfig {\n const namespace = getArgValue(argv, '--namespace');\n const metricName = getArgValue(argv, '--metric');\n const stat = getArgValue(argv, '--stat') ?? 'Average';\n const period = getArgValue(argv, '--period');\n if (!namespace || !metricName) {\n console.error('Error: cloudwatch metrics need --namespace and --metric.');\n process.exit(1);\n }\n const dimsRaw = getArgValue(argv, '--dimensions');\n let dimensions: Record<string, string> | undefined;\n if (dimsRaw) {\n dimensions = {};\n // Format: Name1=Value1,Name2=Value2\n for (const pair of dimsRaw.split(',')) {\n const [k, ...rest] = pair.split('=');\n if (k && rest.length > 0) dimensions[k.trim()] = rest.join('=').trim();\n }\n }\n return {\n namespace,\n metricName,\n stat,\n periodSeconds: period ? parseInt(period, 10) : 300,\n ...(dimensions ? { dimensions } : {}),\n };\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error('Error: Missing metric name.');\n console.error(\n 'Usage: cofactor metrics create <name> --source <postgres|cloudwatch> --cadence <seconds> [...]'\n );\n process.exit(1);\n }\n const source = getArgValue(argv, '--source') as MetricSource | undefined;\n if (source !== 'postgres' && source !== 'cloudwatch') {\n console.error('Error: --source must be postgres or cloudwatch.');\n process.exit(1);\n }\n const cadenceRaw = getArgValue(argv, '--cadence');\n const cadenceSeconds = cadenceRaw ? parseInt(cadenceRaw, 10) : NaN;\n if (!Number.isInteger(cadenceSeconds) || cadenceSeconds < 60) {\n console.error('Error: --cadence <seconds> is required (minimum 60).');\n process.exit(1);\n }\n const description = getArgValue(argv, '--description');\n const unit = getArgValue(argv, '--unit');\n\n const body: Record<string, unknown> = {\n name,\n source,\n cadenceSeconds,\n ...(description !== undefined ? { description } : {}),\n ...(unit !== undefined ? { unit } : {}),\n };\n if (source === 'postgres') {\n const query = getArgValue(argv, '--query');\n if (!query) {\n console.error('Error: postgres metrics need --query \"<read-only SQL returning one number>\".');\n process.exit(1);\n }\n body.query = query;\n } else {\n body.cloudwatchConfig = parseCloudwatchConfig(argv);\n }\n\n const result = await apiRequest<MetricApiResponse>(\n apiUrl,\n token,\n 'POST',\n '/superadmin/product-metrics',\n body\n );\n if (!result.ok || !result.metric) {\n console.error(`Error: ${result.error ?? 'Failed to create metric'}`);\n process.exit(1);\n }\n console.log(`Created metric ${result.metric.name} (${result.metric.source}, every ${result.metric.cadenceSeconds}s)`);\n}\n\nasync function handleSet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error(\n 'Usage: cofactor metrics set <name> [--query <sql>] [--cadence <s>] [--unit <u>] [--description <text>] [--enable|--disable]'\n );\n process.exit(1);\n }\n const body: Record<string, unknown> = {};\n const query = getArgValue(argv, '--query');\n if (query !== undefined) body.query = query;\n const cadenceRaw = getArgValue(argv, '--cadence');\n if (cadenceRaw !== undefined) body.cadenceSeconds = parseInt(cadenceRaw, 10);\n const unit = getArgValue(argv, '--unit');\n if (unit !== undefined) body.unit = unit;\n const description = getArgValue(argv, '--description');\n if (description !== undefined) body.description = description;\n if (hasFlag(argv, '--enable')) body.enabled = true;\n if (hasFlag(argv, '--disable')) body.enabled = false;\n\n if (Object.keys(body).length === 0) {\n console.error('Error: provide at least one field to update.');\n process.exit(1);\n }\n\n const result = await apiRequest<MetricApiResponse>(\n apiUrl,\n token,\n 'PATCH',\n `/superadmin/product-metrics/${encodeURIComponent(name)}`,\n body\n );\n if (!result.ok || !result.metric) {\n console.error(`Error: ${result.error ?? 'Failed to update metric'}`);\n process.exit(1);\n }\n console.log(`Updated metric ${result.metric.name}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error('Usage: cofactor metrics delete <name>');\n process.exit(1);\n }\n const result = await apiRequest<MetricApiResponse>(\n apiUrl,\n token,\n 'DELETE',\n `/superadmin/product-metrics/${encodeURIComponent(name)}`\n );\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n console.log(`Deleted metric ${name}`);\n}\n\nasync function handleCollect(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error('Usage: cofactor metrics collect <name>');\n process.exit(1);\n }\n const result = await apiRequest<MetricApiResponse>(\n apiUrl,\n token,\n 'POST',\n `/superadmin/product-metrics/${encodeURIComponent(name)}/collect`\n );\n if (!result.ok) {\n console.error(`Error: ${result.error ?? 'Failed to collect metric'}`);\n process.exit(1);\n }\n if (result.skipped) {\n // Collector unavailable in this environment — metric state left untouched.\n console.log(`Skipped ${name}: ${result.reason ?? 'collector unavailable in this environment'}`);\n return;\n }\n const dp = result.datapoint;\n if (!dp) {\n console.error('Error: collect returned no datapoint');\n process.exit(1);\n }\n if (dp.error) {\n console.log(`Collected ${name}: error — ${dp.error}`);\n } else {\n console.log(`Collected ${name}: ${dp.value} (at ${dp.capturedAt})`);\n }\n}\n\nasync function handleLink(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const cardId = getArgValue(argv, '--card');\n const clear = hasFlag(argv, '--clear');\n if (!name || name.startsWith('--') || !cardId) {\n console.error('Usage: cofactor metrics link <name> --card <cardId> [--clear]');\n process.exit(1);\n }\n const result = await apiRequest<MetricApiResponse>(\n apiUrl,\n token,\n 'POST',\n `/superadmin/product-metrics/${encodeURIComponent(name)}/cards`,\n { cardId, action: clear ? 'unlink' : 'link' }\n );\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n console.log(\n clear ? `Unlinked metric ${name} from card ${cardId}` : `Linked metric ${name} to card ${cardId}`\n );\n}\n\nasync function handleDatapoints(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith('--')) {\n console.error('Usage: cofactor metrics datapoints <name> [--since <ISO>] [--json]');\n process.exit(1);\n }\n const since = getArgValue(argv, '--since');\n const path = `/superadmin/product-metrics/${encodeURIComponent(name)}/datapoints${\n since ? `?since=${encodeURIComponent(since)}` : ''\n }`;\n const datapoints = await fetchList<DataPoint>(apiUrl, token, path, 'datapoints');\n if (hasFlag(argv, '--json')) {\n console.log(JSON.stringify(datapoints, null, 2));\n return;\n }\n if (datapoints.length === 0) {\n console.log('No datapoints.');\n return;\n }\n for (const dp of datapoints) {\n const v = dp.error ? `error: ${dp.error}` : dp.value;\n console.log(` ${dp.capturedAt} ${v}`);\n }\n}\n\nfunction printMetricsHelp(): void {\n console.log(\n [\n 'Usage: cofactor metrics <sub-command> [options]',\n '',\n 'Manage first-class product metrics for the org you are logged into.',\n '',\n 'Sub-commands:',\n ' list [--json] List metrics with latest value',\n ' get <name> [--datapoints N] [--json] Show a metric + recent values',\n ' create <name> --source postgres --query \"<SQL>\" --cadence <s> [--unit <u>] [--description <t>]',\n ' create <name> --source cloudwatch --namespace <ns> --metric <m> [--stat Average] [--period 300] [--dimensions K=V,K=V] --cadence <s>',\n ' Create a metric',\n ' set <name> [--query <sql>] [--cadence <s>] [--unit <u>] [--description <t>] [--enable|--disable]',\n ' Update a metric',\n ' delete <name> Delete a metric (and its datapoints)',\n ' collect <name> Pull the metric value now',\n ' link <name> --card <cardId> [--clear] Link/unlink a product card',\n ' datapoints <name> [--since <ISO>] [--json] Show the metric time series',\n '',\n 'Sources: postgres (read-only SQL returning one number), cloudwatch (GetMetricData)',\n '',\n 'Options:',\n ' --env <env> Target environment (prod, dev, local)',\n ' --json Output as JSON (list/get/datapoints)',\n ' --api-url <url> API URL override (takes precedence over --env)',\n ' --token <key> API token override',\n ].join('\\n')\n );\n}\n\nexport async function runMetrics(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === 'help' || hasFlag(argv, '--help', '-h')) {\n printMetricsHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case 'list':\n await handleList(rest, apiUrl, token);\n break;\n case 'get':\n await handleGet(rest, apiUrl, token);\n break;\n case 'create':\n await handleCreate(rest, apiUrl, token);\n break;\n case 'set':\n await handleSet(rest, apiUrl, token);\n break;\n case 'delete':\n await handleDelete(rest, apiUrl, token);\n break;\n case 'collect':\n await handleCollect(rest, apiUrl, token);\n break;\n case 'link':\n await handleLink(rest, apiUrl, token);\n break;\n case 'datapoints':\n await handleDatapoints(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printMetricsHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchList
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UAJUZLDE.js";
|
|
6
6
|
import "./chunk-3CZU7XCS.js";
|
|
7
7
|
import {
|
|
8
8
|
getArgValue,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./chunk-IGHDIWZU.js";
|
|
12
12
|
import "./chunk-32P26BZT.js";
|
|
13
13
|
import "./chunk-JWHYXEA2.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-GZVCNYR4.js";
|
|
15
15
|
import "./chunk-VKVL7WBN.js";
|
|
16
16
|
|
|
17
17
|
// src/product.ts
|
|
@@ -24,6 +24,7 @@ var STAGES = [
|
|
|
24
24
|
"beta",
|
|
25
25
|
"on_hold",
|
|
26
26
|
"ga",
|
|
27
|
+
"internal",
|
|
27
28
|
"deprecated"
|
|
28
29
|
];
|
|
29
30
|
function formatItem(item) {
|
|
@@ -343,4 +344,4 @@ async function runProduct(argv) {
|
|
|
343
344
|
export {
|
|
344
345
|
runProduct
|
|
345
346
|
};
|
|
346
|
-
//# sourceMappingURL=product-
|
|
347
|
+
//# sourceMappingURL=product-BUJWPYKB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/product.ts"],"sourcesContent":["/**\n * CLI Product Board Management\n *\n * Superadmin CRUD for the Product board (cards.boardType='product_item'),\n * scoped to the org the CLI user is logged into. Backed by\n * `/superadmin/product-board/*`. Flag↔card linking re-surfaces the existing\n * `/superadmin/feature-flags/:name/product` endpoint for convenience.\n */\n\nimport process from 'node:process';\nimport { resolveConfig, getArgValue, hasFlag } from './auth.js';\nimport { apiRequest, fetchList } from './cli-helpers.js';\n\ntype ProductItemCustomer = {\n id: string;\n name: string;\n orgId: string | null;\n accountCardId: string | null;\n};\n\ntype ProductItem = {\n id: string;\n title: string;\n description: string;\n stage: string;\n priority: string | null;\n sortOrder: number;\n customers: ProductItemCustomer[];\n flagCount: number;\n flagDriven: boolean;\n};\n\ntype ProductApiResponse = {\n ok: boolean;\n error?: string;\n item?: ProductItem;\n customer?: ProductItemCustomer;\n};\n\ntype ProductCardFlag = {\n id: string;\n name: string;\n lifecycleStage: string;\n activeOrgGateCount: number;\n};\n\nconst STAGES = [\n 'ideas',\n 'prioritized',\n 'in_development',\n 'dogfooding',\n 'beta',\n 'on_hold',\n 'ga',\n 'internal',\n 'deprecated',\n];\n\nfunction formatItem(item: ProductItem): string {\n const priority = item.priority ? ` ${item.priority}` : '';\n const flags = item.flagCount > 0 ? ` flags=${item.flagCount}${item.flagDriven ? '(driven)' : ''}` : '';\n const customers = item.customers.length > 0 ? ` customers=${item.customers.length}` : '';\n return ` ${item.id} ${item.title} (stage=${item.stage}${priority})${flags}${customers}`;\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, '--json');\n const params = new URLSearchParams();\n const search = getArgValue(argv, '--search');\n if (search) params.set('search', search);\n const priority = getArgValue(argv, '--priority');\n if (priority) params.set('priority', priority);\n if (hasFlag(argv, '--include-deprecated')) params.set('includeDeprecated', 'true');\n const query = params.toString();\n const path = `/superadmin/product-board/items${query ? `?${query}` : ''}`;\n const items = await fetchList<ProductItem>(apiUrl, token, path, 'items');\n\n if (jsonOutput) {\n console.log(JSON.stringify(items, null, 2));\n return;\n }\n if (items.length === 0) {\n console.log('No product items found.');\n return;\n }\n for (const item of items) console.log(formatItem(item));\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const title = argv[0];\n if (!title || title.startsWith('--')) {\n console.error('Error: Missing title.');\n console.error(\n 'Usage: canary product create <title> [--description <text>] [--stage <stage>] [--priority <p0-p3>]'\n );\n process.exit(1);\n }\n\n const description = getArgValue(argv, '--description');\n const stage = getArgValue(argv, '--stage');\n const priority = getArgValue(argv, '--priority');\n\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'POST',\n '/superadmin/product-board/items',\n {\n title,\n ...(description !== undefined ? { description } : {}),\n ...(stage !== undefined ? { stage } : {}),\n ...(priority !== undefined ? { priority } : {}),\n }\n );\n\n if (!result.ok || !result.item) {\n console.error(`Error: ${result.error ?? 'Failed to create product item'}`);\n process.exit(1);\n }\n console.log(`Created product item ${result.item.id}: ${result.item.title} (stage=${result.item.stage})`);\n}\n\nasync function handleSet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const id = argv[0];\n if (!id || id.startsWith('--')) {\n console.error('Error: Missing product item id.');\n console.error('Usage: canary product set <id> [--title <text>] [--description <text>] [--priority <p0-p3|null>]');\n process.exit(1);\n }\n\n const title = getArgValue(argv, '--title');\n const description = getArgValue(argv, '--description');\n const rawPriority = getArgValue(argv, '--priority');\n\n if (title === undefined && description === undefined && rawPriority === undefined) {\n console.error('Error: Provide at least one of --title, --description, --priority.');\n process.exit(1);\n }\n\n // `--priority null` clears the priority; otherwise pass the value through.\n const priority = rawPriority === undefined ? undefined : rawPriority === 'null' ? null : rawPriority;\n\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'PATCH',\n `/superadmin/product-board/items/${encodeURIComponent(id)}`,\n {\n ...(title !== undefined ? { title } : {}),\n ...(description !== undefined ? { description } : {}),\n ...(priority !== undefined ? { priority } : {}),\n }\n );\n\n if (!result.ok || !result.item) {\n console.error(`Error: ${result.error ?? 'Failed to update product item'}`);\n process.exit(1);\n }\n console.log(`Updated product item ${result.item.id}: ${result.item.title}`);\n}\n\nasync function handleMove(argv: string[], apiUrl: string, token: string): Promise<void> {\n const id = argv[0];\n const stage = getArgValue(argv, '--stage');\n if (!id || id.startsWith('--')) {\n console.error('Error: Missing product item id.');\n console.error('Usage: canary product move <id> --stage <stage> [--before <id>] [--after <id>]');\n process.exit(1);\n }\n if (!stage) {\n console.error(`Error: --stage is required (one of: ${STAGES.join(', ')}).`);\n process.exit(1);\n }\n\n const beforeItemId = getArgValue(argv, '--before') ?? null;\n const afterItemId = getArgValue(argv, '--after') ?? null;\n\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'POST',\n `/superadmin/product-board/items/${encodeURIComponent(id)}/move`,\n { stage, beforeItemId, afterItemId }\n );\n\n if (!result.ok || !result.item) {\n console.error(`Error: ${result.error ?? 'Failed to move product item'}`);\n process.exit(1);\n }\n console.log(`Moved product item ${result.item.id} to stage=${result.item.stage}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const id = argv[0];\n if (!id || id.startsWith('--')) {\n console.error('Error: Missing product item id.');\n console.error('Usage: canary product delete <id>');\n process.exit(1);\n }\n\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'DELETE',\n `/superadmin/product-board/items/${encodeURIComponent(id)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n console.log(`Deleted product item ${id}`);\n}\n\nasync function handleCustomer(argv: string[], apiUrl: string, token: string): Promise<void> {\n const [action, id, ...rest] = argv;\n if (action === 'add') {\n if (!id) {\n console.error('Usage: canary product customer add <id> (--account-card <id> | --org <orgId>) [--name <text>]');\n process.exit(1);\n }\n const accountCardId = getArgValue(rest, '--account-card');\n const orgId = getArgValue(rest, '--org');\n const name = getArgValue(rest, '--name');\n if (!accountCardId && !orgId && !name) {\n console.error('Error: Provide --account-card, --org, or --name.');\n process.exit(1);\n }\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'POST',\n `/superadmin/product-board/items/${encodeURIComponent(id)}/customers`,\n {\n ...(accountCardId !== undefined ? { accountCardId } : {}),\n ...(orgId !== undefined ? { orgId } : {}),\n ...(name !== undefined ? { name } : {}),\n }\n );\n if (!result.ok || !result.customer) {\n console.error(`Error: ${result.error ?? 'Failed to add customer'}`);\n process.exit(1);\n }\n console.log(`Linked customer ${result.customer.name} to product item ${id}`);\n return;\n }\n\n if (action === 'remove') {\n const customerId = rest[0] ?? getArgValue(rest, '--customer');\n if (!id || !customerId) {\n console.error('Usage: canary product customer remove <id> <customerId>');\n process.exit(1);\n }\n const result = await apiRequest<ProductApiResponse>(\n apiUrl,\n token,\n 'DELETE',\n `/superadmin/product-board/items/${encodeURIComponent(id)}/customers/${encodeURIComponent(customerId)}`\n );\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n console.log(`Removed customer ${customerId} from product item ${id}`);\n return;\n }\n\n console.error('Usage: canary product customer <add|remove> ...');\n process.exit(1);\n}\n\nasync function handleLinkFlag(argv: string[], apiUrl: string, token: string): Promise<void> {\n const cardId = argv[0] && !argv[0].startsWith('--') ? argv[0] : undefined;\n const flagName = getArgValue(argv, '--flag');\n const clear = hasFlag(argv, '--clear');\n\n if (!flagName) {\n console.error('Error: --flag <name> is required.');\n console.error('Usage: canary product link-flag <cardId> --flag <name> | canary product link-flag --flag <name> --clear');\n process.exit(1);\n }\n if (clear === Boolean(cardId)) {\n console.error('Error: Provide either a <cardId> to link or --clear to unlink (not both/neither).');\n process.exit(1);\n }\n\n const result = await apiRequest<{ ok: boolean; error?: string; flag?: { productTitle?: string | null } }>(\n apiUrl,\n token,\n 'POST',\n `/superadmin/feature-flags/${encodeURIComponent(flagName)}/product`,\n { productCardId: clear ? null : cardId }\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n console.log(\n clear\n ? `Cleared product link for flag ${flagName}`\n : `Linked flag ${flagName} to product ${result.flag?.productTitle ?? cardId}`\n );\n}\n\nasync function handleFlags(argv: string[], apiUrl: string, token: string): Promise<void> {\n const cardId = argv[0];\n const jsonOutput = hasFlag(argv, '--json');\n if (!cardId || cardId.startsWith('--')) {\n console.error('Usage: canary product flags <cardId>');\n process.exit(1);\n }\n const flags = await fetchList<ProductCardFlag>(\n apiUrl,\n token,\n `/superadmin/feature-flags/by-product-card/${encodeURIComponent(cardId)}`,\n 'flags'\n );\n if (jsonOutput) {\n console.log(JSON.stringify(flags, null, 2));\n return;\n }\n if (flags.length === 0) {\n console.log('No feature flags linked to this product card.');\n return;\n }\n for (const flag of flags) {\n console.log(` ${flag.name} (lifecycle=${flag.lifecycleStage}, orgGates=${flag.activeOrgGateCount})`);\n }\n}\n\nfunction printProductHelp(): void {\n console.log(\n [\n 'Usage: canary product <sub-command> [options]',\n '',\n 'Manage the Product board (cards.boardType=product_item) for the org you are logged into.',\n '',\n 'Sub-commands:',\n ' list [--search <text>] [--priority p0,p1] [--include-deprecated] [--json]',\n ' List product items',\n ' create <title> [--description <text>] [--stage <stage>] [--priority <p0-p3>]',\n ' Create a product item',\n ' set <id> [--title <text>] [--description <text>] [--priority <p0-p3|null>]',\n ' Update a product item\\'s fields',\n ' move <id> --stage <stage> [--before <id>] [--after <id>]',\n ' Move a card to another column (blocked when flag-driven)',\n ' delete <id> Delete a product item',\n ' customer add <id> (--account-card <id> | --org <orgId>) [--name <text>]',\n ' customer remove <id> <customerId> Link/unlink a customer (account card or org)',\n ' link-flag <cardId> --flag <name> Link a feature flag to this product card',\n ' link-flag --flag <name> --clear Clear a flag\\'s product link',\n ' flags <cardId> [--json] List feature flags linked to a product card',\n '',\n `Stages: ${STAGES.join(', ')}`,\n 'Priorities: p0, p1, p2, p3',\n '',\n 'Options:',\n ' --env <env> Target environment (prod, dev, local)',\n ' --json Output as JSON (list/flags only)',\n ' --api-url <url> API URL override (takes precedence over --env)',\n ' --token <key> API token override',\n ].join('\\n')\n );\n}\n\nexport async function runProduct(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === 'help' || hasFlag(argv, '--help', '-h')) {\n printProductHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case 'list':\n await handleList(rest, apiUrl, token);\n break;\n case 'create':\n await handleCreate(rest, apiUrl, token);\n break;\n case 'set':\n await handleSet(rest, apiUrl, token);\n break;\n case 'move':\n await handleMove(rest, apiUrl, token);\n break;\n case 'delete':\n await handleDelete(rest, apiUrl, token);\n break;\n case 'customer':\n await handleCustomer(rest, apiUrl, token);\n break;\n case 'link-flag':\n await handleLinkFlag(rest, apiUrl, token);\n break;\n case 'flags':\n await handleFlags(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printProductHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchProperties,
|
|
5
5
|
getApplicationArg
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-UAJUZLDE.js";
|
|
7
7
|
import "./chunk-3CZU7XCS.js";
|
|
8
8
|
import {
|
|
9
9
|
getArgValue,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-IGHDIWZU.js";
|
|
13
13
|
import "./chunk-32P26BZT.js";
|
|
14
14
|
import "./chunk-JWHYXEA2.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-GZVCNYR4.js";
|
|
16
16
|
import "./chunk-VKVL7WBN.js";
|
|
17
17
|
|
|
18
18
|
// src/property.ts
|
|
@@ -770,4 +770,4 @@ async function runProperty(argv) {
|
|
|
770
770
|
export {
|
|
771
771
|
runProperty
|
|
772
772
|
};
|
|
773
|
-
//# sourceMappingURL=property-
|
|
773
|
+
//# sourceMappingURL=property-4EXPWRNT.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
apiRequest,
|
|
8
8
|
downloadStorageState,
|
|
9
9
|
selectCredential
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UAJUZLDE.js";
|
|
11
11
|
import "./chunk-3CZU7XCS.js";
|
|
12
12
|
import {
|
|
13
13
|
getArgValue,
|
|
@@ -18,7 +18,7 @@ import "./chunk-32P26BZT.js";
|
|
|
18
18
|
import {
|
|
19
19
|
getCanaryTmpDir
|
|
20
20
|
} from "./chunk-JWHYXEA2.js";
|
|
21
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-GZVCNYR4.js";
|
|
22
22
|
import "./chunk-VKVL7WBN.js";
|
|
23
23
|
|
|
24
24
|
// src/record.ts
|
|
@@ -86,7 +86,7 @@ async function runRecord(argv) {
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
console.log("Launching browser...");
|
|
89
|
-
const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import("./
|
|
89
|
+
const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import("./dist-QLTVBUMD.js");
|
|
90
90
|
const client = new PlaywrightClient({ logger: consoleLogger });
|
|
91
91
|
await client.connect({
|
|
92
92
|
browserMode: "headed",
|
|
@@ -229,4 +229,4 @@ Output directory: ${outDir}`);
|
|
|
229
229
|
export {
|
|
230
230
|
runRecord
|
|
231
231
|
};
|
|
232
|
-
//# sourceMappingURL=record-
|
|
232
|
+
//# sourceMappingURL=record-HL4QA7OX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/record.ts"],"sourcesContent":["/**\n * `canary record` — Record browser interactions for flow creation.\n *\n * Launches a headed browser, injects an in-page capture script,\n * and records user interactions (clicks, inputs, navigation) enriched\n * with accessibility snapshots and element info. On stop, bundles\n * everything as JSONL + video and uploads to the API.\n *\n * @module record\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { getCanaryTmpDir } from '@chatsdet/tmp';\nimport process from 'node:process';\nimport { resolveConfig, getArgValue, hasFlag } from './auth.js';\nimport { apiRequest, selectCredential, downloadStorageState } from './cli-helpers.js';\n\nimport { INTERACTION_CAPTURE_SCRIPT } from './record-interaction-script.js';\nimport type { CapturedInteractionEvent } from './session/types.js';\n\n/** Recording event — uses the shared CapturedInteractionEvent type. */\ntype RecordingEvent = CapturedInteractionEvent;\n\n/** Click event narrowing helper for enrichment. */\ntype RecordingClickEvent = Extract<CapturedInteractionEvent, { type: 'click' }>;\n\ninterface CredentialDetail {\n id: string;\n name: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\nexport async function runRecord(argv: string[]): Promise<void> {\n if (hasFlag(argv, '--help', '-h')) {\n console.log(\n [\n 'Usage: canary record [options]',\n '',\n 'Record browser interactions for flow creation.',\n '',\n 'Options:',\n ' --credential <id> Credential ID (skip interactive selection)',\n ' --url <startUrl> URL to navigate to after launch',\n ' --output <dir> Local output directory (default: temp dir)',\n ' --env <env> Environment (local, dev, prod)',\n ' --no-upload Save locally only, skip API upload',\n '',\n 'Press Ctrl+C to stop recording.',\n ].join('\\n')\n );\n return;\n }\n\n const config = await resolveConfig(argv);\n const credentialArg = getArgValue(argv, '--credential');\n const startUrl = getArgValue(argv, '--url');\n const outputDir = getArgValue(argv, '--output');\n const skipUpload = hasFlag(argv, '--no-upload');\n\n // 1. Fetch and select credential\n console.log('Fetching credentials...');\n const credential = await selectCredential(config.apiUrl, config.token, credentialArg);\n if (!credential) {\n process.exit(1);\n }\n\n console.log(`Using credential: ${credential.name}`);\n\n const propertyId = credential.propertyId;\n\n // 3. Download storage state if available\n let storageStatePath: string | undefined;\n if (credential.storageStateS3Key) {\n console.log('Downloading storage state...');\n storageStatePath = await downloadStorageState({\n apiUrl: config.apiUrl,\n token: config.token,\n propertyId: credential.propertyId,\n credentialId: credential.id,\n });\n if (storageStatePath) {\n console.log('Storage state loaded.');\n } else {\n console.warn('Could not download storage state, continuing without it.');\n }\n }\n\n // 4. Get credential detail — its loginUrl is the resolved login start URL\n // (environment URL + the application's relative login path).\n const detail = await apiRequest<{ ok: boolean; credential?: CredentialDetail }>(\n config.apiUrl,\n config.token,\n 'GET',\n `/org/properties/${propertyId}/credentials/${credential.id}`\n );\n const loginUrl = detail.credential?.loginUrl ?? undefined;\n\n // 5. Determine start URL\n const navigateUrl = startUrl ?? loginUrl;\n if (!navigateUrl) {\n console.warn(\n 'No start URL provided and the application has no environment URL. Browser will open to about:blank.'\n );\n }\n\n // 6. Launch browser\n console.log('Launching browser...');\n\n // Lazy-load playwright-dependent modules\n const { PlaywrightClient, consoleLogger, captureElementAtPoint } =\n await import('@chatsdet/browser-core');\n\n const client = new PlaywrightClient({ logger: consoleLogger });\n await client.connect({\n browserMode: 'headed',\n storageStatePath,\n recordVideo: true,\n });\n\n const page = await client.getPageForReplay();\n if (!page) {\n console.error('Failed to get browser page.');\n await client.disconnect();\n process.exit(1);\n }\n\n // 7. Inject interaction capture script\n await page.addInitScript(INTERACTION_CAPTURE_SCRIPT);\n await page.evaluate(INTERACTION_CAPTURE_SCRIPT);\n\n // 8. Navigate\n if (navigateUrl) {\n console.log(`Navigating to ${navigateUrl}`);\n await page.goto(navigateUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 }).catch(() => {\n console.warn('Navigation timed out or failed, continuing anyway.');\n });\n }\n\n const events: RecordingEvent[] = [];\n let running = true;\n\n // Emit initial-url event so we know the starting page\n const currentUrl = page.url();\n const initialUrlEvent: RecordingEvent = {\n type: 'initial-url',\n ts: Date.now(),\n url: currentUrl,\n };\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n (initialUrlEvent as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail\n }\n events.push(initialUrlEvent);\n process.stdout.write(` [${events.length}] initial-url → ${currentUrl}\\n`);\n\n console.log('\\nRecording started. Interact with the browser.');\n console.log('Press Ctrl+C to stop recording.\\n');\n\n // 9. Poll loop — collect events from the page\n const pollInterval = setInterval(async () => {\n if (!running) return;\n try {\n const raw = await page.evaluate(() => {\n const evts =\n (window as unknown as { __canaryRecordedEvents?: unknown[] }).__canaryRecordedEvents ??\n [];\n (window as unknown as { __canaryRecordedEvents: unknown[] }).__canaryRecordedEvents = [];\n return evts;\n });\n\n if (!Array.isArray(raw) || raw.length === 0) return;\n\n for (const evt of raw as RecordingEvent[]) {\n // Enrich click events with element info and periodic snapshots\n if (evt.type === 'click') {\n const clickEvt = evt as RecordingClickEvent;\n try {\n const info = await captureElementAtPoint(page, clickEvt.x, clickEvt.y);\n if (info) {\n clickEvt.elementInfo = info as unknown as Record<string, unknown>;\n }\n } catch {\n // Element may have disappeared\n }\n\n // Snapshot every click for full page context\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n clickEvt.snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n // Enrich navigation events with snapshot\n if (evt.type === 'navigation') {\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n (evt as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n events.push(evt);\n const label =\n evt.type === 'click'\n ? `click (${(evt as RecordingClickEvent).tagName})`\n : evt.type === 'input'\n ? `input`\n : evt.type === 'change'\n ? `change (${(evt as { tagName: string }).tagName})`\n : evt.type === 'navigation'\n ? `navigation → ${(evt as { url: string }).url}`\n : evt.type === 'initial-url'\n ? `initial-url → ${(evt as { url: string }).url}`\n : `keydown: ${(evt as { key: string }).key}`;\n process.stdout.write(` [${events.length}] ${label}\\n`);\n }\n } catch {\n // Page may have been closed\n }\n }, 500);\n\n // 10. Handle Ctrl+C\n const cleanup = async () => {\n if (!running) return;\n running = false;\n clearInterval(pollInterval);\n\n console.log(`\\nRecording stopped. ${events.length} events captured.`);\n\n // Save video\n console.log('Saving video...');\n const videoResult = await client.saveVideo();\n await client.disconnect();\n\n // Clean up temp storage state\n if (storageStatePath) {\n await fs.unlink(storageStatePath).catch(() => {});\n }\n\n // Prepare output directory\n const outDir = outputDir ?? path.join(getCanaryTmpDir(), `canary-recording-${Date.now()}`);\n await fs.mkdir(outDir, { recursive: true });\n\n // Write events JSONL\n const eventsPath = path.join(outDir, 'events.jsonl');\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n await fs.writeFile(eventsPath, lines, 'utf-8');\n console.log(`Events saved: ${eventsPath}`);\n\n // Copy video if available\n let videoPath: string | undefined;\n if (videoResult?.videoPath) {\n videoPath = path.join(outDir, 'video.webm');\n await fs.copyFile(videoResult.videoPath, videoPath);\n console.log(`Video saved: ${videoPath}`);\n }\n\n // Upload to API\n if (!skipUpload && events.length > 0) {\n console.log('Uploading recording...');\n try {\n const formData = new FormData();\n formData.append('propertyId', propertyId);\n formData.append('credentialId', credential.id);\n\n const eventsBlob = new Blob([lines], { type: 'application/x-ndjson' });\n formData.append('events', eventsBlob, 'events.jsonl');\n\n if (videoPath) {\n const videoBuffer = await fs.readFile(videoPath);\n const videoBlob = new Blob([videoBuffer], { type: 'video/webm' });\n formData.append('video', videoBlob, 'video.webm');\n }\n\n const res = await fetch(`${config.apiUrl}/org/recordings/upload`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${config.token}` },\n body: formData,\n });\n\n const json = (await res.json()) as {\n ok: boolean;\n recordingId?: string;\n error?: string;\n };\n\n if (json.ok) {\n console.log(`Recording uploaded. ID: ${json.recordingId}`);\n } else {\n console.error(`Upload failed: ${json.error ?? 'Unknown error'}`);\n }\n } catch (err) {\n console.error('Upload failed:', err instanceof Error ? err.message : err);\n }\n }\n\n console.log(`\\nOutput directory: ${outDir}`);\n process.exit(0);\n };\n\n process.on('SIGINT', () => void cleanup());\n process.on('SIGTERM', () => void cleanup());\n\n // Also stop if the page is closed\n page.on('close', () => void cleanup());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAWA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,OAAO,aAAa;AAoBpB,eAAsB,UAAU,MAA+B;AAC7D,MAAI,QAAQ,MAAM,UAAU,IAAI,GAAG;AACjC,YAAQ;AAAA,MACN;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AACA;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,cAAc,IAAI;AACvC,QAAM,gBAAgB,YAAY,MAAM,cAAc;AACtD,QAAM,WAAW,YAAY,MAAM,OAAO;AAC1C,QAAM,YAAY,YAAY,MAAM,UAAU;AAC9C,QAAM,aAAa,QAAQ,MAAM,aAAa;AAG9C,UAAQ,IAAI,yBAAyB;AACrC,QAAM,aAAa,MAAM,iBAAiB,OAAO,QAAQ,OAAO,OAAO,aAAa;AACpF,MAAI,CAAC,YAAY;AACf,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,qBAAqB,WAAW,IAAI,EAAE;AAElD,QAAM,aAAa,WAAW;AAG9B,MAAI;AACJ,MAAI,WAAW,mBAAmB;AAChC,YAAQ,IAAI,8BAA8B;AAC1C,uBAAmB,MAAM,qBAAqB;AAAA,MAC5C,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,YAAY,WAAW;AAAA,MACvB,cAAc,WAAW;AAAA,IAC3B,CAAC;AACD,QAAI,kBAAkB;AACpB,cAAQ,IAAI,uBAAuB;AAAA,IACrC,OAAO;AACL,cAAQ,KAAK,0DAA0D;AAAA,IACzE;AAAA,EACF;AAIA,QAAM,SAAS,MAAM;AAAA,IACnB,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,IACA,mBAAmB,UAAU,gBAAgB,WAAW,EAAE;AAAA,EAC5D;AACA,QAAM,WAAW,OAAO,YAAY,YAAY;AAGhD,QAAM,cAAc,YAAY;AAChC,MAAI,CAAC,aAAa;AAChB,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,IAAI,sBAAsB;AAGlC,QAAM,EAAE,kBAAkB,eAAe,sBAAsB,IAC7D,MAAM,OAAO,mBAAwB;AAEvC,QAAM,SAAS,IAAI,iBAAiB,EAAE,QAAQ,cAAc,CAAC;AAC7D,QAAM,OAAO,QAAQ;AAAA,IACnB,aAAa;AAAA,IACb;AAAA,IACA,aAAa;AAAA,EACf,CAAC;AAED,QAAM,OAAO,MAAM,OAAO,iBAAiB;AAC3C,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,6BAA6B;AAC3C,UAAM,OAAO,WAAW;AACxB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,KAAK,cAAc,0BAA0B;AACnD,QAAM,KAAK,SAAS,0BAA0B;AAG9C,MAAI,aAAa;AACf,YAAQ,IAAI,iBAAiB,WAAW,EAAE;AAC1C,UAAM,KAAK,KAAK,aAAa,EAAE,WAAW,oBAAoB,SAAS,IAAO,CAAC,EAAE,MAAM,MAAM;AAC3F,cAAQ,KAAK,oDAAoD;AAAA,IACnE,CAAC;AAAA,EACH;AAEA,QAAM,SAA2B,CAAC;AAClC,MAAI,UAAU;AAGd,QAAM,aAAa,KAAK,IAAI;AAC5B,QAAM,kBAAkC;AAAA,IACtC,MAAM;AAAA,IACN,IAAI,KAAK,IAAI;AAAA,IACb,KAAK;AAAA,EACP;AACA,MAAI;AACF,UAAM,WAAW,MAAM,KAAK,aAAa,EAAE,MAAM,KAAK,CAAC;AACvD,IAAC,gBAA0C,WAAW;AAAA,EACxD,QAAQ;AAAA,EAER;AACA,SAAO,KAAK,eAAe;AAC3B,UAAQ,OAAO,MAAM,MAAM,OAAO,MAAM,wBAAmB,UAAU;AAAA,CAAI;AAEzE,UAAQ,IAAI,iDAAiD;AAC7D,UAAQ,IAAI,mCAAmC;AAG/C,QAAM,eAAe,YAAY,YAAY;AAC3C,QAAI,CAAC,QAAS;AACd,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,SAAS,MAAM;AACpC,cAAM,OACH,OAA6D,0BAC9D,CAAC;AACH,QAAC,OAA4D,yBAAyB,CAAC;AACvF,eAAO;AAAA,MACT,CAAC;AAED,UAAI,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,WAAW,EAAG;AAE7C,iBAAW,OAAO,KAAyB;AAEzC,YAAI,IAAI,SAAS,SAAS;AACxB,gBAAM,WAAW;AACjB,cAAI;AACF,kBAAM,OAAO,MAAM,sBAAsB,MAAM,SAAS,GAAG,SAAS,CAAC;AACrE,gBAAI,MAAM;AACR,uBAAS,cAAc;AAAA,YACzB;AAAA,UACF,QAAQ;AAAA,UAER;AAGA,cAAI;AACF,kBAAM,WAAW,MAAM,KAAK,aAAa,EAAE,MAAM,KAAK,CAAC;AACvD,qBAAS,WAAW;AAAA,UACtB,QAAQ;AAAA,UAER;AAAA,QACF;AAGA,YAAI,IAAI,SAAS,cAAc;AAC7B,cAAI;AACF,kBAAM,WAAW,MAAM,KAAK,aAAa,EAAE,MAAM,KAAK,CAAC;AACvD,YAAC,IAA8B,WAAW;AAAA,UAC5C,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,eAAO,KAAK,GAAG;AACf,cAAM,QACJ,IAAI,SAAS,UACT,UAAW,IAA4B,OAAO,MAC9C,IAAI,SAAS,UACX,UACA,IAAI,SAAS,WACX,WAAY,IAA4B,OAAO,MAC/C,IAAI,SAAS,eACX,qBAAiB,IAAwB,GAAG,KAC5C,IAAI,SAAS,gBACX,sBAAkB,IAAwB,GAAG,KAC7C,YAAa,IAAwB,GAAG;AACtD,gBAAQ,OAAO,MAAM,MAAM,OAAO,MAAM,KAAK,KAAK;AAAA,CAAI;AAAA,MACxD;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,GAAG;AAGN,QAAM,UAAU,YAAY;AAC1B,QAAI,CAAC,QAAS;AACd,cAAU;AACV,kBAAc,YAAY;AAE1B,YAAQ,IAAI;AAAA,qBAAwB,OAAO,MAAM,mBAAmB;AAGpE,YAAQ,IAAI,iBAAiB;AAC7B,UAAM,cAAc,MAAM,OAAO,UAAU;AAC3C,UAAM,OAAO,WAAW;AAGxB,QAAI,kBAAkB;AACpB,YAAM,GAAG,OAAO,gBAAgB,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAClD;AAGA,UAAM,SAAS,aAAa,KAAK,KAAK,gBAAgB,GAAG,oBAAoB,KAAK,IAAI,CAAC,EAAE;AACzF,UAAM,GAAG,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAG1C,UAAM,aAAa,KAAK,KAAK,QAAQ,cAAc;AACnD,UAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI;AAC5D,UAAM,GAAG,UAAU,YAAY,OAAO,OAAO;AAC7C,YAAQ,IAAI,iBAAiB,UAAU,EAAE;AAGzC,QAAI;AACJ,QAAI,aAAa,WAAW;AAC1B,kBAAY,KAAK,KAAK,QAAQ,YAAY;AAC1C,YAAM,GAAG,SAAS,YAAY,WAAW,SAAS;AAClD,cAAQ,IAAI,gBAAgB,SAAS,EAAE;AAAA,IACzC;AAGA,QAAI,CAAC,cAAc,OAAO,SAAS,GAAG;AACpC,cAAQ,IAAI,wBAAwB;AACpC,UAAI;AACF,cAAM,WAAW,IAAI,SAAS;AAC9B,iBAAS,OAAO,cAAc,UAAU;AACxC,iBAAS,OAAO,gBAAgB,WAAW,EAAE;AAE7C,cAAM,aAAa,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACrE,iBAAS,OAAO,UAAU,YAAY,cAAc;AAEpD,YAAI,WAAW;AACb,gBAAM,cAAc,MAAM,GAAG,SAAS,SAAS;AAC/C,gBAAM,YAAY,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,MAAM,aAAa,CAAC;AAChE,mBAAS,OAAO,SAAS,WAAW,YAAY;AAAA,QAClD;AAEA,cAAM,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,0BAA0B;AAAA,UAChE,QAAQ;AAAA,UACR,SAAS,EAAE,eAAe,UAAU,OAAO,KAAK,GAAG;AAAA,UACnD,MAAM;AAAA,QACR,CAAC;AAED,cAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,YAAI,KAAK,IAAI;AACX,kBAAQ,IAAI,2BAA2B,KAAK,WAAW,EAAE;AAAA,QAC3D,OAAO;AACL,kBAAQ,MAAM,kBAAkB,KAAK,SAAS,eAAe,EAAE;AAAA,QACjE;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,kBAAkB,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,MAC1E;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,oBAAuB,MAAM,EAAE;AAC3C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,MAAM,KAAK,QAAQ,CAAC;AACzC,UAAQ,GAAG,WAAW,MAAM,KAAK,QAAQ,CAAC;AAG1C,OAAK,GAAG,SAAS,MAAM,KAAK,QAAQ,CAAC;AACvC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/record.ts"],"sourcesContent":["/**\n * `canary record` — Record browser interactions for flow creation.\n *\n * Launches a headed browser, injects an in-page capture script,\n * and records user interactions (clicks, inputs, navigation) enriched\n * with accessibility snapshots and element info. On stop, bundles\n * everything as JSONL + video and uploads to the API.\n *\n * @module record\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { getCanaryTmpDir } from '@chatsdet/tmp';\nimport process from 'node:process';\nimport { resolveConfig, getArgValue, hasFlag } from './auth.js';\nimport { apiRequest, selectCredential, downloadStorageState } from './cli-helpers.js';\n\nimport { INTERACTION_CAPTURE_SCRIPT } from './record-interaction-script.js';\nimport type { CapturedInteractionEvent } from './session/types.js';\n\n/** Recording event — uses the shared CapturedInteractionEvent type. */\ntype RecordingEvent = CapturedInteractionEvent;\n\n/** Click event narrowing helper for enrichment. */\ntype RecordingClickEvent = Extract<CapturedInteractionEvent, { type: 'click' }>;\n\ninterface CredentialDetail {\n id: string;\n name: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\nexport async function runRecord(argv: string[]): Promise<void> {\n if (hasFlag(argv, '--help', '-h')) {\n console.log(\n [\n 'Usage: canary record [options]',\n '',\n 'Record browser interactions for flow creation.',\n '',\n 'Options:',\n ' --credential <id> Credential ID (skip interactive selection)',\n ' --url <startUrl> URL to navigate to after launch',\n ' --output <dir> Local output directory (default: temp dir)',\n ' --env <env> Environment (local, dev, prod)',\n ' --no-upload Save locally only, skip API upload',\n '',\n 'Press Ctrl+C to stop recording.',\n ].join('\\n')\n );\n return;\n }\n\n const config = await resolveConfig(argv);\n const credentialArg = getArgValue(argv, '--credential');\n const startUrl = getArgValue(argv, '--url');\n const outputDir = getArgValue(argv, '--output');\n const skipUpload = hasFlag(argv, '--no-upload');\n\n // 1. Fetch and select credential\n console.log('Fetching credentials...');\n const credential = await selectCredential(config.apiUrl, config.token, credentialArg);\n if (!credential) {\n process.exit(1);\n }\n\n console.log(`Using credential: ${credential.name}`);\n\n const propertyId = credential.propertyId;\n\n // 3. Download storage state if available\n let storageStatePath: string | undefined;\n if (credential.storageStateS3Key) {\n console.log('Downloading storage state...');\n storageStatePath = await downloadStorageState({\n apiUrl: config.apiUrl,\n token: config.token,\n propertyId: credential.propertyId,\n credentialId: credential.id,\n });\n if (storageStatePath) {\n console.log('Storage state loaded.');\n } else {\n console.warn('Could not download storage state, continuing without it.');\n }\n }\n\n // 4. Get credential detail — its loginUrl is the resolved login start URL\n // (environment URL + the application's relative login path).\n const detail = await apiRequest<{ ok: boolean; credential?: CredentialDetail }>(\n config.apiUrl,\n config.token,\n 'GET',\n `/org/properties/${propertyId}/credentials/${credential.id}`\n );\n const loginUrl = detail.credential?.loginUrl ?? undefined;\n\n // 5. Determine start URL\n const navigateUrl = startUrl ?? loginUrl;\n if (!navigateUrl) {\n console.warn(\n 'No start URL provided and the application has no environment URL. Browser will open to about:blank.'\n );\n }\n\n // 6. Launch browser\n console.log('Launching browser...');\n\n // Lazy-load playwright-dependent modules\n const { PlaywrightClient, consoleLogger, captureElementAtPoint } =\n await import('@chatsdet/browser-core');\n\n const client = new PlaywrightClient({ logger: consoleLogger });\n await client.connect({\n browserMode: 'headed',\n storageStatePath,\n recordVideo: true,\n });\n\n const page = await client.getPageForReplay();\n if (!page) {\n console.error('Failed to get browser page.');\n await client.disconnect();\n process.exit(1);\n }\n\n // 7. Inject interaction capture script\n await page.addInitScript(INTERACTION_CAPTURE_SCRIPT);\n await page.evaluate(INTERACTION_CAPTURE_SCRIPT);\n\n // 8. Navigate\n if (navigateUrl) {\n console.log(`Navigating to ${navigateUrl}`);\n await page.goto(navigateUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 }).catch(() => {\n console.warn('Navigation timed out or failed, continuing anyway.');\n });\n }\n\n const events: RecordingEvent[] = [];\n let running = true;\n\n // Emit initial-url event so we know the starting page\n const currentUrl = page.url();\n const initialUrlEvent: RecordingEvent = {\n type: 'initial-url',\n ts: Date.now(),\n url: currentUrl,\n };\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n (initialUrlEvent as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail\n }\n events.push(initialUrlEvent);\n process.stdout.write(` [${events.length}] initial-url → ${currentUrl}\\n`);\n\n console.log('\\nRecording started. Interact with the browser.');\n console.log('Press Ctrl+C to stop recording.\\n');\n\n // 9. Poll loop — collect events from the page\n const pollInterval = setInterval(async () => {\n if (!running) return;\n try {\n const raw = await page.evaluate(() => {\n const evts =\n (window as unknown as { __canaryRecordedEvents?: unknown[] }).__canaryRecordedEvents ??\n [];\n (window as unknown as { __canaryRecordedEvents: unknown[] }).__canaryRecordedEvents = [];\n return evts;\n });\n\n if (!Array.isArray(raw) || raw.length === 0) return;\n\n for (const evt of raw as RecordingEvent[]) {\n // Enrich click events with element info and periodic snapshots\n if (evt.type === 'click') {\n const clickEvt = evt as RecordingClickEvent;\n try {\n const info = await captureElementAtPoint(page, clickEvt.x, clickEvt.y);\n if (info) {\n clickEvt.elementInfo = info as unknown as Record<string, unknown>;\n }\n } catch {\n // Element may have disappeared\n }\n\n // Snapshot every click for full page context\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n clickEvt.snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n // Enrich navigation events with snapshot\n if (evt.type === 'navigation') {\n try {\n const snapshot = await page.ariaSnapshot({ mode: 'ai' });\n (evt as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n events.push(evt);\n const label =\n evt.type === 'click'\n ? `click (${(evt as RecordingClickEvent).tagName})`\n : evt.type === 'input'\n ? `input`\n : evt.type === 'change'\n ? `change (${(evt as { tagName: string }).tagName})`\n : evt.type === 'navigation'\n ? `navigation → ${(evt as { url: string }).url}`\n : evt.type === 'initial-url'\n ? `initial-url → ${(evt as { url: string }).url}`\n : `keydown: ${(evt as { key: string }).key}`;\n process.stdout.write(` [${events.length}] ${label}\\n`);\n }\n } catch {\n // Page may have been closed\n }\n }, 500);\n\n // 10. Handle Ctrl+C\n const cleanup = async () => {\n if (!running) return;\n running = false;\n clearInterval(pollInterval);\n\n console.log(`\\nRecording stopped. ${events.length} events captured.`);\n\n // Save video\n console.log('Saving video...');\n const videoResult = await client.saveVideo();\n await client.disconnect();\n\n // Clean up temp storage state\n if (storageStatePath) {\n await fs.unlink(storageStatePath).catch(() => {});\n }\n\n // Prepare output directory\n const outDir = outputDir ?? path.join(getCanaryTmpDir(), `canary-recording-${Date.now()}`);\n await fs.mkdir(outDir, { recursive: true });\n\n // Write events JSONL\n const eventsPath = path.join(outDir, 'events.jsonl');\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n await fs.writeFile(eventsPath, lines, 'utf-8');\n console.log(`Events saved: ${eventsPath}`);\n\n // Copy video if available\n let videoPath: string | undefined;\n if (videoResult?.videoPath) {\n videoPath = path.join(outDir, 'video.webm');\n await fs.copyFile(videoResult.videoPath, videoPath);\n console.log(`Video saved: ${videoPath}`);\n }\n\n // Upload to API\n if (!skipUpload && events.length > 0) {\n console.log('Uploading recording...');\n try {\n const formData = new FormData();\n formData.append('propertyId', propertyId);\n formData.append('credentialId', credential.id);\n\n const eventsBlob = new Blob([lines], { type: 'application/x-ndjson' });\n formData.append('events', eventsBlob, 'events.jsonl');\n\n if (videoPath) {\n const videoBuffer = await fs.readFile(videoPath);\n const videoBlob = new Blob([videoBuffer], { type: 'video/webm' });\n formData.append('video', videoBlob, 'video.webm');\n }\n\n const res = await fetch(`${config.apiUrl}/org/recordings/upload`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${config.token}` },\n body: formData,\n });\n\n const json = (await res.json()) as {\n ok: boolean;\n recordingId?: string;\n error?: string;\n };\n\n if (json.ok) {\n console.log(`Recording uploaded. ID: ${json.recordingId}`);\n } else {\n console.error(`Upload failed: ${json.error ?? 'Unknown error'}`);\n }\n } catch (err) {\n console.error('Upload failed:', err instanceof Error ? err.message : err);\n }\n }\n\n console.log(`\\nOutput directory: ${outDir}`);\n process.exit(0);\n };\n\n process.on('SIGINT', () => void cleanup());\n process.on('SIGTERM', () => void cleanup());\n\n // Also stop if the page is closed\n page.on('close', () => void cleanup());\n}\n"],"mappings":"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","names":[]}
|