deepline 0.3.293 → 0.3.295
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/bundling-sources/sdk/src/compat.ts +35 -7
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +269 -267
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +3 -4
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +14 -1
- package/dist/cli/auth-EDTRERZP.mjs +31 -0
- package/dist/cli/auth-TDHKB277.js +31 -0
- package/dist/cli/billing-ELIDPILY.js +1749 -0
- package/dist/cli/billing-EXGXJVAU.mjs +1749 -0
- package/dist/cli/chunk-2S5Y3TZP.js +5688 -0
- package/dist/cli/chunk-3KC45UVF.js +150 -0
- package/dist/cli/chunk-3XX5TEA4.js +15 -0
- package/dist/cli/chunk-4MBOJQPL.mjs +0 -0
- package/dist/cli/chunk-4NEFYBF7.js +2240 -0
- package/dist/cli/chunk-5JLQPVNX.js +3056 -0
- package/dist/cli/chunk-5KDKFHZI.js +1269 -0
- package/dist/cli/chunk-6A3TJUWC.js +76 -0
- package/dist/cli/chunk-6O5YZOVN.mjs +890 -0
- package/dist/cli/chunk-7P5AA3SN.js +344 -0
- package/dist/cli/chunk-AYHLUHE5.js +52 -0
- package/dist/cli/chunk-CBVIJSHF.mjs +560 -0
- package/dist/cli/chunk-CFVVCN4A.js +735 -0
- package/dist/cli/chunk-COGTRVWW.js +30 -0
- package/dist/cli/chunk-D4AZUZO4.js +190 -0
- package/dist/cli/chunk-DLIRSHV6.mjs +28 -0
- package/dist/cli/chunk-EACVZI6N.mjs +1995 -0
- package/dist/cli/chunk-EDWJY6T4.mjs +3056 -0
- package/dist/cli/chunk-EXMLR72R.mjs +0 -0
- package/dist/cli/chunk-F35VOIQL.js +890 -0
- package/dist/cli/chunk-GAMFK2ZP.mjs +148 -0
- package/dist/cli/chunk-GHRQQPW2.mjs +2240 -0
- package/dist/cli/chunk-GONRLVV3.js +3440 -0
- package/dist/cli/chunk-GVUJJJ5G.mjs +76 -0
- package/dist/cli/chunk-H7HIDGZ7.mjs +150 -0
- package/dist/cli/chunk-HUZXWOSK.mjs +503 -0
- package/dist/cli/chunk-I5YZLGMG.js +1 -0
- package/dist/cli/chunk-ITOFSXIU.mjs +15 -0
- package/dist/cli/chunk-J5NA2TTX.js +503 -0
- package/dist/cli/chunk-JCS5TPNY.mjs +3440 -0
- package/dist/cli/chunk-JNNOCMQY.js +47 -0
- package/dist/cli/chunk-KRD6H754.mjs +30 -0
- package/dist/cli/chunk-LA46MMTL.js +560 -0
- package/dist/cli/chunk-LDWT7RJK.mjs +190 -0
- package/dist/cli/chunk-M2V26OR5.js +499 -0
- package/dist/cli/chunk-MIMASXO4.mjs +389 -0
- package/dist/cli/chunk-MROPCKW2.mjs +190 -0
- package/dist/cli/chunk-NXDRDJ4K.js +389 -0
- package/dist/cli/chunk-NXGCO4JB.mjs +344 -0
- package/dist/cli/chunk-OG64RBAC.js +6 -0
- package/dist/cli/chunk-OQYEC4GL.mjs +34 -0
- package/dist/cli/chunk-OWJZUE3Y.mjs +103 -0
- package/dist/cli/chunk-SDK3ZA2C.js +103 -0
- package/dist/cli/chunk-SFLARO4Y.mjs +47 -0
- package/dist/cli/chunk-SVIIIYIQ.js +923 -0
- package/dist/cli/chunk-TAC3JIFE.mjs +354 -0
- package/dist/cli/chunk-U3PI7HBM.js +34 -0
- package/dist/cli/chunk-UFS752TU.mjs +52 -0
- package/dist/cli/chunk-UI37DQE7.mjs +499 -0
- package/dist/cli/chunk-UNEMDKXN.js +1 -0
- package/dist/cli/chunk-WB6Z2Q44.mjs +1622 -0
- package/dist/cli/chunk-WJYPBHTE.js +148 -0
- package/dist/cli/chunk-WOHBA5PV.js +354 -0
- package/dist/cli/chunk-WQICC2B3.js +190 -0
- package/dist/cli/chunk-WZYKRD7T.mjs +6 -0
- package/dist/cli/chunk-XL2FO6CW.mjs +5688 -0
- package/dist/cli/chunk-XNYIZMNQ.js +28 -0
- package/dist/cli/chunk-XRK2TZT5.js +1995 -0
- package/dist/cli/chunk-YJYPL5OK.mjs +1269 -0
- package/dist/cli/chunk-YWNDYCXR.mjs +735 -0
- package/dist/cli/chunk-YWWAF7KZ.mjs +923 -0
- package/dist/cli/chunk-ZDO3TO3M.js +1622 -0
- package/dist/cli/client-4JVOYVC5.js +20 -0
- package/dist/cli/client-EEN2IJN2.mjs +20 -0
- package/dist/cli/csv-BOHBEP2I.mjs +585 -0
- package/dist/cli/csv-S5NYRUEX.js +585 -0
- package/dist/cli/db-CLODWCOB.mjs +486 -0
- package/dist/cli/db-F6PKFOOV.js +486 -0
- package/dist/cli/executions-5TQAKLJU.js +28 -0
- package/dist/cli/executions-MAKW6QOY.mjs +28 -0
- package/dist/cli/feedback-GV4QJFEN.js +107 -0
- package/dist/cli/feedback-XY24TW7H.mjs +107 -0
- package/dist/cli/getting-started.js +18 -76
- package/dist/cli/getting-started.mjs +10 -37
- package/dist/cli/git-6S2C22T5.js +23 -0
- package/dist/cli/git-OZLBD5HH.mjs +23 -0
- package/dist/cli/index.js +291 -54294
- package/dist/cli/index.mjs +209 -54352
- package/dist/cli/legacy-noops-LLCGDPQE.js +157 -0
- package/dist/cli/legacy-noops-TYH3TW2D.mjs +157 -0
- package/dist/cli/monitors-CABMB4DW.js +3251 -0
- package/dist/cli/monitors-TTD55HMK.mjs +3251 -0
- package/dist/cli/org-MSF3RG76.js +849 -0
- package/dist/cli/org-SFJ77MWY.mjs +849 -0
- package/dist/cli/play-NXHJSCKL.mjs +18439 -0
- package/dist/cli/play-OKSFNNO4.js +18439 -0
- package/dist/cli/providers-QC2ERERL.js +95 -0
- package/dist/cli/providers-TIIAZEUA.mjs +95 -0
- package/dist/cli/proxy-env-EBANZHPV.js +161 -0
- package/dist/cli/proxy-env-M4ZHXHRL.mjs +161 -0
- package/dist/cli/quickstart-ICANHLMI.js +69 -0
- package/dist/cli/quickstart-KWUOP3FI.mjs +69 -0
- package/dist/cli/search-U5H2FDOO.mjs +18 -0
- package/dist/cli/search-XQRQOLWS.js +18 -0
- package/dist/cli/secrets-3J6YTWFA.mjs +400 -0
- package/dist/cli/secrets-YNMNZNOM.js +400 -0
- package/dist/cli/sessions-CLEES4L6.mjs +15 -0
- package/dist/cli/sessions-J6VR47FG.js +15 -0
- package/dist/cli/settings-EEKZMTY4.js +846 -0
- package/dist/cli/settings-UVLOQZRH.mjs +846 -0
- package/dist/cli/setup-DLMDXPGJ.mjs +1257 -0
- package/dist/cli/setup-HC4Q7QM4.js +1257 -0
- package/dist/cli/skills-33PHFBSB.mjs +28 -0
- package/dist/cli/skills-LFRY3KPX.js +28 -0
- package/dist/cli/tools-3COEPKML.mjs +2955 -0
- package/dist/cli/tools-QXEVCN45.js +2955 -0
- package/dist/cli/update-LSAC4FF5.mjs +28 -0
- package/dist/cli/update-NXHSPYXA.js +28 -0
- package/dist/cli/workflow-7S44OECF.mjs +1936 -0
- package/dist/cli/workflow-QPZWYSZE.js +1936 -0
- package/dist/helpers.js +6 -32
- package/dist/helpers.mjs +2 -0
- package/dist/index.js +1972 -12825
- package/dist/index.mjs +1910 -12672
- package/dist/install-integrity.json +110 -0
- package/dist/monitor-fleet-contract.js +31 -730
- package/dist/monitor-fleet-contract.mjs +18 -667
- package/dist/monitor-input-contract.js +10 -390
- package/dist/monitor-input-contract.mjs +7 -348
- package/dist/plays/bundle-play-file.mjs +13 -5178
- package/dist/plays/csv-data-input.js +5 -3132
- package/dist/plays/csv-data-input.mjs +5 -3105
- package/dist/plays/run-request.js +56 -664
- package/dist/plays/run-request.mjs +30 -588
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +5 -212
- package/dist/release.mjs +5 -185
- package/package.json +1 -1
|
@@ -0,0 +1,3251 @@
|
|
|
1
|
+
import "./chunk-EXMLR72R.mjs";
|
|
2
|
+
import {
|
|
3
|
+
DeeplineClient
|
|
4
|
+
} from "./chunk-XL2FO6CW.mjs";
|
|
5
|
+
import "./chunk-CBVIJSHF.mjs";
|
|
6
|
+
import {
|
|
7
|
+
MONITOR_FLEET_DOCUMENTATION,
|
|
8
|
+
lintMonitorFleetInput
|
|
9
|
+
} from "./chunk-HUZXWOSK.mjs";
|
|
10
|
+
import "./chunk-4MBOJQPL.mjs";
|
|
11
|
+
import {
|
|
12
|
+
lintMonitorBatchInput
|
|
13
|
+
} from "./chunk-TAC3JIFE.mjs";
|
|
14
|
+
import {
|
|
15
|
+
errorToJsonPayload,
|
|
16
|
+
printCommandEnvelope,
|
|
17
|
+
printJson,
|
|
18
|
+
shouldEmitJson
|
|
19
|
+
} from "./chunk-6O5YZOVN.mjs";
|
|
20
|
+
import {
|
|
21
|
+
AuthError
|
|
22
|
+
} from "./chunk-EACVZI6N.mjs";
|
|
23
|
+
import "./chunk-DLIRSHV6.mjs";
|
|
24
|
+
import "./chunk-LDWT7RJK.mjs";
|
|
25
|
+
import "./chunk-GVUJJJ5G.mjs";
|
|
26
|
+
import {
|
|
27
|
+
DeeplineError
|
|
28
|
+
} from "./chunk-NXGCO4JB.mjs";
|
|
29
|
+
import "./chunk-WZYKRD7T.mjs";
|
|
30
|
+
import "./chunk-KRD6H754.mjs";
|
|
31
|
+
|
|
32
|
+
// src/cli/commands/monitors.ts
|
|
33
|
+
import { randomUUID } from "crypto";
|
|
34
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
35
|
+
import { resolve } from "path";
|
|
36
|
+
import { createInterface } from "readline/promises";
|
|
37
|
+
var JSON_OPTION_DESCRIPTION = "Emit JSON output. Also automatic when stdout is piped";
|
|
38
|
+
function withJsonOption(command) {
|
|
39
|
+
return command.option("--json", JSON_OPTION_DESCRIPTION);
|
|
40
|
+
}
|
|
41
|
+
var COMPACT_OPTION_DESCRIPTION = "Ask the server for high-signal fields only (smaller output for agent loops)";
|
|
42
|
+
var MonitorsUsageError = class extends Error {
|
|
43
|
+
code = "MONITORS_USAGE_ERROR";
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "MonitorsUsageError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var MonitorDryRunUnsupportedError = class extends Error {
|
|
50
|
+
code = "MONITOR_DRY_RUN_UNSUPPORTED";
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = "MonitorDryRunUnsupportedError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function monitorsErrorExitCode(error) {
|
|
57
|
+
if (error instanceof MonitorsUsageError) return 2;
|
|
58
|
+
if (error instanceof MonitorFleetStateError) return 6;
|
|
59
|
+
if (error instanceof AuthError) return 3;
|
|
60
|
+
if (error instanceof DeeplineError) {
|
|
61
|
+
const status = error.statusCode;
|
|
62
|
+
if (status === 401 || status === 403) return 3;
|
|
63
|
+
if (status === 404) return 4;
|
|
64
|
+
if (status === 400 || status === 409 || status === 422) return 7;
|
|
65
|
+
return 5;
|
|
66
|
+
}
|
|
67
|
+
return 5;
|
|
68
|
+
}
|
|
69
|
+
function monitorsFailureNextCommand(error, exitCode, options) {
|
|
70
|
+
if (error instanceof MonitorFleetStateError) return error.next;
|
|
71
|
+
if (error instanceof DeeplineError) {
|
|
72
|
+
const response = asRecord(asRecord(error.details)?.response);
|
|
73
|
+
const serverNext = asString(response?.next) ?? asString(response?.next_action);
|
|
74
|
+
if (serverNext) return serverNext;
|
|
75
|
+
}
|
|
76
|
+
if (exitCode === 5 && options?.retryCommand) return options.retryCommand;
|
|
77
|
+
if (error instanceof DeeplineError && error.code === "monitor_access_required") {
|
|
78
|
+
return "deepline monitors status";
|
|
79
|
+
}
|
|
80
|
+
if (error instanceof DeeplineError && error.code === "monitor_tool_not_found") {
|
|
81
|
+
return "deepline monitors available --json";
|
|
82
|
+
}
|
|
83
|
+
if (exitCode === 3) return "deepline auth status --json";
|
|
84
|
+
if (exitCode === 4) return "deepline monitors list --status all --json";
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
function readValidationIssues(error) {
|
|
88
|
+
if (!(error instanceof DeeplineError)) return [];
|
|
89
|
+
const response = asRecord(asRecord(error.details)?.response);
|
|
90
|
+
const issues = Array.isArray(response?.issues) ? response.issues : [];
|
|
91
|
+
return issues.flatMap((raw) => {
|
|
92
|
+
const issue = asRecord(raw);
|
|
93
|
+
if (!issue) return [];
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
...asString(issue.path) ? { path: asString(issue.path) } : {},
|
|
97
|
+
...asString(issue.message) ? { message: asString(issue.message) } : {}
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function reportMonitorsFailure(error, options) {
|
|
103
|
+
const exitCode = monitorsErrorExitCode(error);
|
|
104
|
+
const next = monitorsFailureNextCommand(error, exitCode, options);
|
|
105
|
+
const retrySafe = exitCode === 5 && options?.retryCommand !== void 0;
|
|
106
|
+
const wantsJson = shouldEmitJson(process.argv.includes("--json"));
|
|
107
|
+
if (wantsJson) {
|
|
108
|
+
const payload = errorToJsonPayload(error);
|
|
109
|
+
printJson({
|
|
110
|
+
ok: false,
|
|
111
|
+
exitCode,
|
|
112
|
+
...next ? { next } : {},
|
|
113
|
+
...retrySafe ? { retry_safe: true } : {},
|
|
114
|
+
error: retrySafe ? {
|
|
115
|
+
...payload.error,
|
|
116
|
+
message: `${payload.error.message} ${FLEET_RETRY_SAFE_NOTE}`
|
|
117
|
+
} : payload.error
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
const base = error instanceof Error ? error.message : String(error);
|
|
121
|
+
const message = retrySafe ? `${base} ${FLEET_RETRY_SAFE_NOTE}` : base;
|
|
122
|
+
process.stderr.write(`Error: ${message}
|
|
123
|
+
`);
|
|
124
|
+
for (const issue of readValidationIssues(error)) {
|
|
125
|
+
process.stderr.write(
|
|
126
|
+
` - ${issue.path ? `${issue.path}: ` : ""}${issue.message ?? ""}
|
|
127
|
+
`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (next) process.stderr.write(`Next: ${next}
|
|
131
|
+
`);
|
|
132
|
+
}
|
|
133
|
+
process.exitCode = exitCode;
|
|
134
|
+
}
|
|
135
|
+
function monitorsAction(handler) {
|
|
136
|
+
return async (...args) => {
|
|
137
|
+
try {
|
|
138
|
+
await handler(...args);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
reportMonitorsFailure(error);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function parseJsonObjectArg(raw, argLabel) {
|
|
145
|
+
let parsed;
|
|
146
|
+
try {
|
|
147
|
+
parsed = JSON.parse(raw);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new MonitorsUsageError(
|
|
150
|
+
`${argLabel} must be a JSON object. ${error instanceof Error ? error.message : String(error)}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
154
|
+
throw new MonitorsUsageError(`${argLabel} must be a JSON object.`);
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
function resolveMonitorJsonBody(input) {
|
|
159
|
+
const readFile = input.readFile ?? ((path) => readFileSync(path, "utf-8"));
|
|
160
|
+
const readStdin = input.readStdin ?? (() => readFileSync(0, "utf-8"));
|
|
161
|
+
if (input.positional !== void 0 && input.file !== void 0) {
|
|
162
|
+
throw new MonitorsUsageError(
|
|
163
|
+
`Pass exactly one source for ${input.argLabel}: the positional JSON or --file, not both.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (input.positional !== void 0) {
|
|
167
|
+
return parseJsonObjectArg(input.positional, input.argLabel);
|
|
168
|
+
}
|
|
169
|
+
if (input.file !== void 0) {
|
|
170
|
+
if (input.file === "-") {
|
|
171
|
+
return parseJsonObjectArg(readStdin(), `${input.argLabel} (stdin)`);
|
|
172
|
+
}
|
|
173
|
+
let raw;
|
|
174
|
+
try {
|
|
175
|
+
raw = readFile(input.file);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new MonitorsUsageError(
|
|
178
|
+
`Could not read --file ${input.file}: ${error instanceof Error ? error.message : String(error)}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return parseJsonObjectArg(raw, `${input.argLabel} (--file ${input.file})`);
|
|
182
|
+
}
|
|
183
|
+
throw new MonitorsUsageError(
|
|
184
|
+
`${input.command} needs ${input.argLabel} (a JSON object). Pass it positionally:
|
|
185
|
+
${input.command} '{"key":"job-openings","tool":"deepline_native.company_radar","payload":{"domain":"stripe.com","radar_type":"company_job_openings"}}'
|
|
186
|
+
or from a file / stdin:
|
|
187
|
+
${input.command} --file monitor.json
|
|
188
|
+
cat monitor.json | ${input.command} --file -`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function asRecord(value) {
|
|
192
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
193
|
+
}
|
|
194
|
+
function asString(value) {
|
|
195
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
196
|
+
}
|
|
197
|
+
var MONITOR_STATES = /* @__PURE__ */ new Set([
|
|
198
|
+
"active",
|
|
199
|
+
"billing_suspended",
|
|
200
|
+
"disabled"
|
|
201
|
+
]);
|
|
202
|
+
function customerMonitorState(value) {
|
|
203
|
+
if (typeof value === "string" && MONITOR_STATES.has(value)) {
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
function readMonitorState(record) {
|
|
209
|
+
const state = customerMonitorState(record.state);
|
|
210
|
+
if (state) return state;
|
|
211
|
+
const legacyStatus = asString(record.status);
|
|
212
|
+
if (legacyStatus === "active") return "active";
|
|
213
|
+
if (legacyStatus === "billing_suspended") return "billing_suspended";
|
|
214
|
+
if (legacyStatus === "disabled") return "disabled";
|
|
215
|
+
if (legacyStatus === "inactive") return "disabled";
|
|
216
|
+
if (typeof record.active === "boolean") {
|
|
217
|
+
return record.active ? "active" : "disabled";
|
|
218
|
+
}
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
function monitorStateFilter(value) {
|
|
222
|
+
if (value === "all") return "all";
|
|
223
|
+
return customerMonitorState(value);
|
|
224
|
+
}
|
|
225
|
+
function monitorStateFilterLabel(value, fallback = "active") {
|
|
226
|
+
const canonical = monitorStateFilter(value);
|
|
227
|
+
if (canonical) return canonical;
|
|
228
|
+
if (value === "inactive") return "disabled";
|
|
229
|
+
return fallback;
|
|
230
|
+
}
|
|
231
|
+
function asFiniteNumber(value) {
|
|
232
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
233
|
+
}
|
|
234
|
+
function monitorPricingLine(rawPricing) {
|
|
235
|
+
const pricing = asRecord(rawPricing);
|
|
236
|
+
if (!pricing) return void 0;
|
|
237
|
+
const display = asString(pricing.display);
|
|
238
|
+
if (display) return display;
|
|
239
|
+
const credits = asFiniteNumber(pricing.deepline_credits);
|
|
240
|
+
const basis = asString(pricing.billing_basis);
|
|
241
|
+
if (credits === void 0 || !basis) return void 0;
|
|
242
|
+
return `${credits} Deepline credits (${basis.replaceAll("_", " ")})`;
|
|
243
|
+
}
|
|
244
|
+
function yesNo(value) {
|
|
245
|
+
if (value === true) return "yes";
|
|
246
|
+
if (value === false) return "no";
|
|
247
|
+
return "unknown";
|
|
248
|
+
}
|
|
249
|
+
function readDeployOutputContract(payload) {
|
|
250
|
+
const contract = asRecord(payload.output_contract);
|
|
251
|
+
if (!contract) return { streams: [] };
|
|
252
|
+
const rawOutputs = Array.isArray(contract.outputs) ? contract.outputs : [];
|
|
253
|
+
const streams = rawOutputs.flatMap((raw) => {
|
|
254
|
+
const output = asRecord(raw);
|
|
255
|
+
const stream = output ? asString(output.stream) : void 0;
|
|
256
|
+
const table = output ? asString(output.table) : void 0;
|
|
257
|
+
if (!output || !stream || !table) return [];
|
|
258
|
+
const columns = Array.isArray(output.columns) ? output.columns.flatMap((rawColumn) => {
|
|
259
|
+
const column = asRecord(rawColumn);
|
|
260
|
+
const name = column ? asString(column.name) : void 0;
|
|
261
|
+
return name ? [name] : [];
|
|
262
|
+
}) : [];
|
|
263
|
+
return [
|
|
264
|
+
{
|
|
265
|
+
stream,
|
|
266
|
+
table,
|
|
267
|
+
columns,
|
|
268
|
+
isEvent: output.is_event_stream === true,
|
|
269
|
+
matchesPayload: output.matches_payload === true
|
|
270
|
+
}
|
|
271
|
+
];
|
|
272
|
+
});
|
|
273
|
+
return { tool: asString(contract.tool), streams };
|
|
274
|
+
}
|
|
275
|
+
function renderDeployReplacementWarning(input) {
|
|
276
|
+
const summary = asRecord(input.payload.change_summary);
|
|
277
|
+
const upstream = summary ? asRecord(summary.upstream) : void 0;
|
|
278
|
+
if (!summary || !upstream || upstream.resource_replaced !== true) return [];
|
|
279
|
+
const definition = asRecord(summary.definition);
|
|
280
|
+
const changed = definition && Array.isArray(definition.changed) ? definition.changed : [];
|
|
281
|
+
const lines = [
|
|
282
|
+
input.completed ? "WARNING: this deploy replaced the existing upstream monitor." : "WARNING: this deploy replaces the existing upstream monitor.",
|
|
283
|
+
"Existing Customer DB rows are preserved. Provider backfill is not implied."
|
|
284
|
+
];
|
|
285
|
+
for (const raw of changed) {
|
|
286
|
+
const item = asRecord(raw);
|
|
287
|
+
const path = item ? asString(item.path) : void 0;
|
|
288
|
+
if (!item || !path) continue;
|
|
289
|
+
lines.push(
|
|
290
|
+
` ${path}: ${JSON.stringify(item.before ?? null)} \u2192 ${JSON.stringify(
|
|
291
|
+
item.after ?? null
|
|
292
|
+
)}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
lines.push(
|
|
296
|
+
input.monitorKey ? `Use \`deepline monitors update ${input.monitorKey} <patch>\` for a patch-style change.` : "Use `deepline monitors update <key> <patch>` for a patch-style change."
|
|
297
|
+
);
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
function renderDeployFilterRemovalWarning(payload) {
|
|
301
|
+
const summary = asRecord(payload.change_summary);
|
|
302
|
+
const definition = summary ? asRecord(summary.definition) : void 0;
|
|
303
|
+
const changed = definition && Array.isArray(definition.changed) ? definition.changed : [];
|
|
304
|
+
const removed = changed.some((raw) => {
|
|
305
|
+
const item = asRecord(raw);
|
|
306
|
+
return asString(item?.path) === "payload.job_titles" && item?.before != null && item.after == null;
|
|
307
|
+
});
|
|
308
|
+
return removed ? [
|
|
309
|
+
"WARNING: this full deploy removes the job_titles filter.",
|
|
310
|
+
"Future findings will be unfiltered. Existing Customer DB rows are preserved.",
|
|
311
|
+
'Use `deepline monitors update <key> {"payload":{"job_titles":"..."}}` to change only the filter.'
|
|
312
|
+
] : [];
|
|
313
|
+
}
|
|
314
|
+
function renderMonitorDeployCompletion(payload) {
|
|
315
|
+
const monitor = asRecord(payload.monitor);
|
|
316
|
+
const key = monitor ? asString(monitor.key) : void 0;
|
|
317
|
+
const { tool, streams } = readDeployOutputContract(payload);
|
|
318
|
+
if (!key || streams.length === 0) return void 0;
|
|
319
|
+
const toolId = tool ?? (monitor ? asString(monitor.tool) : void 0) ?? "<tool>";
|
|
320
|
+
const name = monitor ? asString(monitor.name) : void 0;
|
|
321
|
+
const state = monitor ? readMonitorState(monitor) : void 0;
|
|
322
|
+
const pricingLine = monitorPricingLine(monitor?.pricing ?? payload.pricing);
|
|
323
|
+
const eventStreams = streams.filter((stream) => stream.isEvent);
|
|
324
|
+
const matched = eventStreams.filter((stream) => stream.matchesPayload);
|
|
325
|
+
const shown = matched.length ? matched : eventStreams.length ? eventStreams : streams;
|
|
326
|
+
const primary = shown[0];
|
|
327
|
+
const lines = [
|
|
328
|
+
`\u2713 Deployed monitor "${key}"${name ? ` (${name})` : ""} \u2014 tool ${toolId}${state ? `, status ${state}` : ""}`,
|
|
329
|
+
"",
|
|
330
|
+
"Output \u2192 Customer DB:"
|
|
331
|
+
];
|
|
332
|
+
for (const stream of shown) {
|
|
333
|
+
lines.push(` ${stream.table} (stream: ${stream.stream})`);
|
|
334
|
+
lines.push(
|
|
335
|
+
` columns: ${stream.columns.length ? stream.columns.join(", ") : "(promoted dynamically as events arrive)"}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (pricingLine) {
|
|
339
|
+
lines.push("", `Pricing: ${pricingLine}`);
|
|
340
|
+
}
|
|
341
|
+
const replacementWarning = renderDeployReplacementWarning({
|
|
342
|
+
payload,
|
|
343
|
+
completed: true,
|
|
344
|
+
monitorKey: key
|
|
345
|
+
});
|
|
346
|
+
if (replacementWarning.length) lines.push("", ...replacementWarning);
|
|
347
|
+
const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
|
|
348
|
+
if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
|
|
349
|
+
const guidance = asRecord(payload.setup_guidance);
|
|
350
|
+
if (guidance) {
|
|
351
|
+
const callbackUrl = asString(guidance.callback_url);
|
|
352
|
+
const steps = Array.isArray(guidance.steps) ? guidance.steps : [];
|
|
353
|
+
const docs = Array.isArray(guidance.documentation) ? guidance.documentation : [];
|
|
354
|
+
lines.push("", asString(guidance.title) ?? "Provider setup:");
|
|
355
|
+
if (callbackUrl) lines.push(` Callback URL: ${callbackUrl}`);
|
|
356
|
+
for (const step of steps)
|
|
357
|
+
if (asString(step)) lines.push(` \u2022 ${asString(step)}`);
|
|
358
|
+
for (const raw of docs) {
|
|
359
|
+
const doc = asRecord(raw);
|
|
360
|
+
const label = asString(doc?.label);
|
|
361
|
+
const url = asString(doc?.url);
|
|
362
|
+
if (label && url) lines.push(` Docs: ${label} \u2014 ${url}`);
|
|
363
|
+
}
|
|
364
|
+
const payloadExample = asRecord(guidance.payload_example);
|
|
365
|
+
if (payloadExample) {
|
|
366
|
+
lines.push(` Payload example: ${JSON.stringify(payloadExample)}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
lines.push(
|
|
370
|
+
"",
|
|
371
|
+
"This monitor streams new rows into your Customer DB \u2014 there is no manual run.",
|
|
372
|
+
"Next:",
|
|
373
|
+
" \u2022 Query the table above directly, or",
|
|
374
|
+
" \u2022 Run a play on each new row (third arg to definePlay in your .play.ts):",
|
|
375
|
+
` sqlListeners: [{ id: '${primary.stream}', tool: '${toolId}', stream: '${primary.stream}', operations: ['INSERT'] }]`,
|
|
376
|
+
" The changed row arrives to your handler as the sqlListener event's `after`.",
|
|
377
|
+
"",
|
|
378
|
+
"Reuse: many plays can bind to this one stream \u2014 you do NOT need a separate",
|
|
379
|
+
"monitor per play. Before deploying another monitor on this tool, run",
|
|
380
|
+
"`deepline monitors list` and reuse an existing one that covers your scope."
|
|
381
|
+
);
|
|
382
|
+
return `${lines.join("\n")}
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
function renderMonitorDeployPlan(payload) {
|
|
386
|
+
const valid = payload.valid !== false;
|
|
387
|
+
const lines = [
|
|
388
|
+
"DRY RUN \u2014 nothing was deployed.",
|
|
389
|
+
valid ? "Definition: valid" : "Definition: INVALID"
|
|
390
|
+
];
|
|
391
|
+
if (!valid) {
|
|
392
|
+
const issues = Array.isArray(payload.issues) ? payload.issues : [];
|
|
393
|
+
for (const raw of issues) {
|
|
394
|
+
const issue = asRecord(raw);
|
|
395
|
+
const path = issue ? asString(issue.path) : void 0;
|
|
396
|
+
const message = issue ? asString(issue.message) : void 0;
|
|
397
|
+
if (message) lines.push(` - ${path ? `${path}: ` : ""}${message}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const replacementWarning = renderDeployReplacementWarning({
|
|
401
|
+
payload,
|
|
402
|
+
completed: false
|
|
403
|
+
});
|
|
404
|
+
if (replacementWarning.length) lines.push("", ...replacementWarning);
|
|
405
|
+
const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
|
|
406
|
+
if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
|
|
407
|
+
const estimate = asRecord(payload.deploy_cost_estimate);
|
|
408
|
+
const credits = estimate ? asFiniteNumber(estimate.credits) : void 0;
|
|
409
|
+
if (credits !== void 0) {
|
|
410
|
+
const note = estimate ? asString(estimate.note) : void 0;
|
|
411
|
+
lines.push(
|
|
412
|
+
`Deploy cost: would cost ${credits} Deepline credits${note ? ` \u2014 ${note}` : ""}.`
|
|
413
|
+
);
|
|
414
|
+
} else {
|
|
415
|
+
lines.push(
|
|
416
|
+
"Deploy cost: estimate unavailable from this server (validation only)."
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const pricingLine = monitorPricingLine(payload.pricing);
|
|
420
|
+
if (pricingLine) {
|
|
421
|
+
lines.push(`Selected pricing: ${pricingLine}`);
|
|
422
|
+
}
|
|
423
|
+
const candidates = Array.isArray(payload.reuse_candidates) ? payload.reuse_candidates : [];
|
|
424
|
+
const candidateLines = candidates.flatMap((raw) => {
|
|
425
|
+
const candidate = asRecord(raw);
|
|
426
|
+
const key = candidate ? asString(candidate.key) : void 0;
|
|
427
|
+
if (!candidate || !key) return [];
|
|
428
|
+
const name = asString(candidate.name);
|
|
429
|
+
const state = readMonitorState(candidate);
|
|
430
|
+
const covers = asString(candidate.covers);
|
|
431
|
+
return [
|
|
432
|
+
` ${key}${name ? ` (${name})` : ""}${state ? ` \u2014 ${state}` : ""}${covers ? `; covers ${covers}` : ""}`
|
|
433
|
+
];
|
|
434
|
+
});
|
|
435
|
+
if (candidateLines.length > 0) {
|
|
436
|
+
lines.push(
|
|
437
|
+
"",
|
|
438
|
+
"You may already have a monitor covering this \u2014 check before deploying:",
|
|
439
|
+
...candidateLines,
|
|
440
|
+
" Inspect: deepline monitors get <key> --json"
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const identityConflict = asRecord(payload.identity_conflict);
|
|
444
|
+
const identityOwner = identityConflict ? asRecord(identityConflict.existing_monitor) : null;
|
|
445
|
+
const identityOwnerKey = identityOwner ? asString(identityOwner.key) : void 0;
|
|
446
|
+
const identityOwnerStatus = identityOwner ? asString(identityOwner.status) : void 0;
|
|
447
|
+
if (identityConflict) {
|
|
448
|
+
if (identityOwnerStatus === "provisioning") {
|
|
449
|
+
lines.push(
|
|
450
|
+
"",
|
|
451
|
+
"An in-progress provider operation owns this upstream identity.",
|
|
452
|
+
"Do not deploy this key yet. Wait, then rerun this dry run."
|
|
453
|
+
);
|
|
454
|
+
} else if (identityOwnerKey) {
|
|
455
|
+
lines.push(
|
|
456
|
+
"",
|
|
457
|
+
`An existing monitor owns this upstream identity: ${identityOwnerKey}.`,
|
|
458
|
+
`Inspect: deepline monitors get ${identityOwnerKey} --json`,
|
|
459
|
+
"Do not deploy this key unless that conflict is resolved."
|
|
460
|
+
);
|
|
461
|
+
} else {
|
|
462
|
+
lines.push(
|
|
463
|
+
"",
|
|
464
|
+
"An existing monitor owns this upstream identity.",
|
|
465
|
+
"Do not deploy this key until that conflict is resolved."
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
lines.push(
|
|
470
|
+
"",
|
|
471
|
+
identityConflict ? "Do not deploy for real until this identity conflict clears." : valid ? "Deploy for real: re-run this command without --dry-run." : "Fix the definition, then re-run this command."
|
|
472
|
+
);
|
|
473
|
+
return `${lines.join("\n")}
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
function renderMonitorDeletePlan(payload, fallbackKey) {
|
|
477
|
+
const plan = asRecord(payload.plan);
|
|
478
|
+
const key = (plan ? asString(plan.monitor_key) : void 0) ?? fallbackKey;
|
|
479
|
+
const lines = [
|
|
480
|
+
"DRY RUN \u2014 nothing was deleted.",
|
|
481
|
+
`Delete plan for monitor "${key}":`,
|
|
482
|
+
` delete Deepline record: ${yesNo(plan?.would_delete_record)}`,
|
|
483
|
+
` deprovision upstream provider resource: ${yesNo(plan?.would_deprovision_upstream)}`,
|
|
484
|
+
"",
|
|
485
|
+
`Delete for real: deepline monitors delete ${key} --yes`
|
|
486
|
+
];
|
|
487
|
+
return `${lines.join("\n")}
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
function renderMonitorReactivatePlan(payload, key) {
|
|
491
|
+
const estimate = asRecord(payload.cost_estimate);
|
|
492
|
+
const credits = estimate ? asFiniteNumber(estimate.credits) : void 0;
|
|
493
|
+
const monitor = asRecord(payload.monitor);
|
|
494
|
+
const tool = monitor ? asString(monitor.tool) : void 0;
|
|
495
|
+
const hasTamradarHistoricalCatchup = tool === "deepline_native.company_radar" || tool === "deepline_native.contact_radar" || tool === "deepline_native.industry_radar";
|
|
496
|
+
const lines = [
|
|
497
|
+
"DRY RUN \u2014 nothing was reactivated.",
|
|
498
|
+
credits !== void 0 ? `Reactivate cost: would cost ${credits} Deepline credits.` : "Reactivate cost: estimate unavailable from this server.",
|
|
499
|
+
...hasTamradarHistoricalCatchup ? [
|
|
500
|
+
"Source boundary: ordinary reactivation starts at provider creation. Saved updates_since values are not forwarded during reactivation."
|
|
501
|
+
] : [],
|
|
502
|
+
"",
|
|
503
|
+
`Reactivate for real: deepline monitors reactivate ${key}`
|
|
504
|
+
];
|
|
505
|
+
return `${lines.join("\n")}
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
function assertMonitorDryRunAcknowledged(payload, context) {
|
|
509
|
+
if (payload.dry_run === true) return;
|
|
510
|
+
throw new MonitorDryRunUnsupportedError(
|
|
511
|
+
`${context.command} --dry-run: the server response did not acknowledge dry-run mode (missing "dry_run": true). This Deepline server may not support --dry-run for ${context.mutation}, so the response cannot be trusted as a plan. Nothing was rendered as a plan. Verify current state with \`deepline monitors get <key> --json\`, and re-run without --dry-run only when you intend the real ${context.mutation}.`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function monitorDeleteRequiresYesError(key) {
|
|
515
|
+
return new MonitorsUsageError(
|
|
516
|
+
`monitors delete is destructive: it deletes monitor "${key}" and deprovisions its upstream provider resource. Non-interactive runs must confirm with --yes:
|
|
517
|
+
deepline monitors delete ${key} --yes
|
|
518
|
+
Preview the plan first with:
|
|
519
|
+
deepline monitors delete ${key} --dry-run`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
async function handleMonitorsStatus(options) {
|
|
523
|
+
const payload = await new DeeplineClient().monitors.status();
|
|
524
|
+
const hasAccess = payload.has_access === true;
|
|
525
|
+
const reason = typeof payload.reason === "string" && payload.reason.trim() ? payload.reason.trim() : void 0;
|
|
526
|
+
const text = hasAccess ? `\u2713 You have access to Deepline Monitors${reason ? `
|
|
527
|
+
${reason}` : ""}
|
|
528
|
+
` : `\u2717 No access to Deepline Monitors${reason ? ` \u2014 ${reason}` : ""}
|
|
529
|
+
`;
|
|
530
|
+
printCommandEnvelope(
|
|
531
|
+
{ has_access: hasAccess, ...reason ? { reason } : {} },
|
|
532
|
+
{ json: options.json, text }
|
|
533
|
+
);
|
|
534
|
+
if (!hasAccess) {
|
|
535
|
+
process.exitCode = 1;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function renderAvailableToolsText(payload) {
|
|
539
|
+
const tools = Array.isArray(payload.tools) ? payload.tools : null;
|
|
540
|
+
if (!tools) return void 0;
|
|
541
|
+
const returned = asFiniteNumber(payload.returned) ?? tools.length;
|
|
542
|
+
const total = asFiniteNumber(payload.total);
|
|
543
|
+
const lines = [
|
|
544
|
+
total !== void 0 ? `Monitor types you can deploy (${returned} of ${total}):` : "Monitor types you can deploy:"
|
|
545
|
+
];
|
|
546
|
+
let currentProvider;
|
|
547
|
+
for (const raw of tools) {
|
|
548
|
+
const entry = asRecord(raw);
|
|
549
|
+
const id = entry ? asString(entry.tool) : void 0;
|
|
550
|
+
if (!entry || !id) continue;
|
|
551
|
+
const name = asString(entry.name) ?? asString(entry.display_name);
|
|
552
|
+
const deployedCount = asFiniteNumber(entry.deployed_count);
|
|
553
|
+
const description = asString(entry.description);
|
|
554
|
+
const streams = Array.isArray(entry.streams) ? entry.streams.map((stream) => asString(stream)).filter((stream) => Boolean(stream)) : [];
|
|
555
|
+
const provider = id.split(".")[0];
|
|
556
|
+
if (provider && provider !== currentProvider) {
|
|
557
|
+
currentProvider = provider;
|
|
558
|
+
lines.push("", ` ${provider}:`);
|
|
559
|
+
}
|
|
560
|
+
lines.push(
|
|
561
|
+
` ${id.split(".").slice(1).join(".") || id}${name ? ` \u2014 ${name}` : ""}${deployedCount !== void 0 ? ` (${deployedCount} deployed)` : ""}`
|
|
562
|
+
);
|
|
563
|
+
if (description) lines.push(` ${description}`);
|
|
564
|
+
if (streams.length > 0) {
|
|
565
|
+
lines.push(` emits: ${streams.join(", ")}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (payload.is_truncated === true) {
|
|
569
|
+
lines.push("List truncated; raise --limit to see more.");
|
|
570
|
+
}
|
|
571
|
+
lines.push(
|
|
572
|
+
"",
|
|
573
|
+
"Next:",
|
|
574
|
+
" Inspect payload fields, output columns, and a play binding:",
|
|
575
|
+
" deepline monitors available <provider.tool> --json",
|
|
576
|
+
" See monitors already deployed in this workspace:",
|
|
577
|
+
" deepline monitors list",
|
|
578
|
+
" Validate a monitor definition before deploying:",
|
|
579
|
+
` deepline monitors check '{"key":"my-monitor","tool":"<provider.tool>","payload":{...}}'`
|
|
580
|
+
);
|
|
581
|
+
return `${lines.join("\n")}
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
async function handleMonitorsAvailable(toolId, options) {
|
|
585
|
+
process.stderr.write(
|
|
586
|
+
"`deepline monitors available` is deprecated.\n Browse types: deepline tools list --categories monitors\n Inspect a type: deepline tools get <monitor-type>\n"
|
|
587
|
+
);
|
|
588
|
+
if (toolId !== void 0 && options.tool !== void 0) {
|
|
589
|
+
if (toolId !== options.tool) {
|
|
590
|
+
throw new MonitorsUsageError(
|
|
591
|
+
`Pass the tool id once: got positional "${toolId}" and --tool "${options.tool}". Use: deepline monitors available ${toolId}`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const tool = toolId ?? options.tool;
|
|
596
|
+
const payload = await new DeeplineClient().monitors.available(tool, {
|
|
597
|
+
...options.provider ? { provider: options.provider } : {},
|
|
598
|
+
...options.search ? { search: options.search } : {},
|
|
599
|
+
...options.limit ? { limit: options.limit } : {},
|
|
600
|
+
...options.full ? { full: true } : {},
|
|
601
|
+
...options.compact ? { compact: true } : {}
|
|
602
|
+
});
|
|
603
|
+
printCommandEnvelope(payload, {
|
|
604
|
+
json: options.json,
|
|
605
|
+
// Human list view shows id + name + deployed_count ("deployed: N") so
|
|
606
|
+
// can-deploy vs already-deployed is visible in one view. Describing a
|
|
607
|
+
// single tool keeps the full JSON contract (payload schema, streams).
|
|
608
|
+
text: tool ? void 0 : renderAvailableToolsText(payload)
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function renderDeployedListText(payload, requestedStatus, filterKind = "status") {
|
|
612
|
+
const monitors = Array.isArray(payload.monitors) ? payload.monitors : null;
|
|
613
|
+
if (!monitors) return void 0;
|
|
614
|
+
const total = asFiniteNumber(payload.total);
|
|
615
|
+
const header = monitors.length === 0 ? "No deployed monitors matched." : total !== void 0 && total > monitors.length ? `Deployed monitors (${monitors.length} of ${total}):` : `Deployed monitors (${monitors.length}):`;
|
|
616
|
+
const lines = [header];
|
|
617
|
+
for (const raw of monitors) {
|
|
618
|
+
const entry = asRecord(raw);
|
|
619
|
+
const key = entry ? asString(entry.key) ?? asString(entry.monitor_key) : void 0;
|
|
620
|
+
if (!entry || !key) continue;
|
|
621
|
+
const state = readMonitorState(entry);
|
|
622
|
+
const tool = asString(entry.tool);
|
|
623
|
+
const monitorType = asString(entry.type);
|
|
624
|
+
const name = asString(entry.name);
|
|
625
|
+
const outputTable = asString(entry.output_table);
|
|
626
|
+
const webhookState = asString(entry.webhook_state);
|
|
627
|
+
const executionType = asString(entry.execution_type);
|
|
628
|
+
const hasLastReceivedEvent = "last_received_event" in entry;
|
|
629
|
+
const lastReceivedEvent = asString(entry.last_received_event);
|
|
630
|
+
const boundPlays = Array.isArray(entry.bound_plays) ? entry.bound_plays.length : void 0;
|
|
631
|
+
lines.push(
|
|
632
|
+
` ${key}${state ? ` ${state}` : ""}${tool ? ` ${tool}` : ""}${name ? ` (${name})` : ""}`
|
|
633
|
+
);
|
|
634
|
+
if (outputTable || monitorType || webhookState || executionType || boundPlays !== void 0) {
|
|
635
|
+
lines.push(
|
|
636
|
+
` ${[
|
|
637
|
+
outputTable ? `table: ${outputTable}` : null,
|
|
638
|
+
monitorType ? `type: ${monitorType}` : null,
|
|
639
|
+
webhookState ? `webhook: ${webhookState}` : null,
|
|
640
|
+
executionType ? `execution: ${executionType}` : null,
|
|
641
|
+
boundPlays !== void 0 ? `bound Plays: ${boundPlays}` : null
|
|
642
|
+
].filter(Boolean).join(" ")}`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (hasLastReceivedEvent) {
|
|
646
|
+
lines.push(` last received: ${lastReceivedEvent ?? "never"}`);
|
|
647
|
+
}
|
|
648
|
+
if (Array.isArray(entry.bound_plays)) {
|
|
649
|
+
for (const rawPlay of entry.bound_plays) {
|
|
650
|
+
const play = asRecord(rawPlay);
|
|
651
|
+
const playName = asString(play?.name) ?? "unnamed Play";
|
|
652
|
+
const health = asRecord(play?.consumer_health);
|
|
653
|
+
const healthRequested = Boolean(play && "consumer_health" in play);
|
|
654
|
+
const state2 = healthRequested ? asString(health?.last_run_status) ?? "no delivery yet" : "health not requested";
|
|
655
|
+
const lastError = asString(health?.last_error);
|
|
656
|
+
lines.push(
|
|
657
|
+
` consumer: ${playName} \u2014 ${state2}${lastError ? ` (${lastError})` : ""}`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const applied = monitorStateFilterLabel(
|
|
663
|
+
payload.state_filter_applied ?? payload.status_filter_applied,
|
|
664
|
+
monitorStateFilterLabel(requestedStatus)
|
|
665
|
+
);
|
|
666
|
+
lines.push(
|
|
667
|
+
`State filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --state all to include all monitors.`
|
|
668
|
+
);
|
|
669
|
+
const rawSummary = Array.isArray(payload.state_summary) ? payload.state_summary : Array.isArray(payload.status_summary) ? payload.status_summary : [];
|
|
670
|
+
const summary = rawSummary.flatMap((raw) => {
|
|
671
|
+
const item = asRecord(raw);
|
|
672
|
+
const state = item ? readMonitorState(item) : void 0;
|
|
673
|
+
const count = item ? asFiniteNumber(item.count) : void 0;
|
|
674
|
+
return state && count !== void 0 ? [`${state}: ${count}`] : [];
|
|
675
|
+
}).join(" ");
|
|
676
|
+
if (summary) {
|
|
677
|
+
lines.push(
|
|
678
|
+
`${payload.state_summary_scope === "filter_total" ? "Matching monitors" : "Current page states"}: ${summary}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
if (payload.has_more === true || payload.is_truncated === true) {
|
|
682
|
+
const nextCursor = asString(payload.next_cursor);
|
|
683
|
+
lines.push(
|
|
684
|
+
total !== void 0 ? `List truncated \u2014 showing ${monitors.length} of ${total}. Before concluding "no matching monitor exists," page the full registry:` : 'List truncated. Before concluding "no matching monitor exists," page the full registry:',
|
|
685
|
+
nextCursor ? ` deepline monitors list${requestedStatus ? ` --${filterKind} ${filterKind === "state" ? monitorStateFilterLabel(requestedStatus) : requestedStatus}` : ""} --cursor ${nextCursor}` : " raise --limit above the reported total."
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (monitors.length > 0) {
|
|
689
|
+
lines.push("", "Inspect one: deepline monitors get <key> --json");
|
|
690
|
+
}
|
|
691
|
+
return `${lines.join("\n")}
|
|
692
|
+
`;
|
|
693
|
+
}
|
|
694
|
+
async function handleMonitorsList(options) {
|
|
695
|
+
const state = options.state ? monitorStateFilter(options.state) : void 0;
|
|
696
|
+
if (options.state !== void 0 && !state) {
|
|
697
|
+
throw new MonitorsUsageError(
|
|
698
|
+
`--state must be active, billing_suspended, disabled, or all; received ${options.state}.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
if (options.compact) {
|
|
702
|
+
process.stderr.write(
|
|
703
|
+
"`--compact` is deprecated and now does nothing; returning the complete monitor records.\n"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const payload = await new DeeplineClient().monitors.list({
|
|
707
|
+
...state ? { state } : {},
|
|
708
|
+
...options.status ? { status: options.status } : {},
|
|
709
|
+
...options.limit ? { limit: options.limit } : {},
|
|
710
|
+
...options.cursor ? { cursor: options.cursor } : {},
|
|
711
|
+
...options.includeConsumers ? { includeConsumers: true } : {}
|
|
712
|
+
});
|
|
713
|
+
printCommandEnvelope(payload, {
|
|
714
|
+
json: options.json,
|
|
715
|
+
text: renderDeployedListText(
|
|
716
|
+
payload,
|
|
717
|
+
options.state ?? options.status,
|
|
718
|
+
options.state !== void 0 ? "state" : "status"
|
|
719
|
+
)
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function handleMonitorsCheck(definition, options) {
|
|
723
|
+
const body = resolveMonitorJsonBody({
|
|
724
|
+
positional: definition,
|
|
725
|
+
file: options.file,
|
|
726
|
+
argLabel: "<definition>",
|
|
727
|
+
command: "deepline monitors check"
|
|
728
|
+
});
|
|
729
|
+
const payload = await new DeeplineClient().monitors.check(
|
|
730
|
+
body
|
|
731
|
+
);
|
|
732
|
+
printCommandEnvelope(payload, { json: options.json });
|
|
733
|
+
}
|
|
734
|
+
async function handleMonitorsDeploy(definition, options) {
|
|
735
|
+
const body = resolveMonitorJsonBody({
|
|
736
|
+
positional: definition,
|
|
737
|
+
file: options.file,
|
|
738
|
+
argLabel: "<definition>",
|
|
739
|
+
command: "deepline monitors deploy"
|
|
740
|
+
});
|
|
741
|
+
const client = new DeeplineClient();
|
|
742
|
+
const definitionArg = body;
|
|
743
|
+
if (options.dryRun) {
|
|
744
|
+
const payload2 = await client.monitors.deploy(definitionArg, {
|
|
745
|
+
dryRun: true
|
|
746
|
+
});
|
|
747
|
+
const valid = payload2.valid !== false;
|
|
748
|
+
const hasIdentityConflict = Boolean(asRecord(payload2)?.identity_conflict);
|
|
749
|
+
printCommandEnvelope(
|
|
750
|
+
{ dry_run: true, ...payload2 },
|
|
751
|
+
{ json: options.json, text: renderMonitorDeployPlan(payload2) }
|
|
752
|
+
);
|
|
753
|
+
if (!valid || hasIdentityConflict) {
|
|
754
|
+
process.exitCode = 7;
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const payload = await client.monitors.deploy(definitionArg);
|
|
759
|
+
printCommandEnvelope(payload, {
|
|
760
|
+
json: options.json,
|
|
761
|
+
text: renderMonitorDeployCompletion(payload)
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
async function handleMonitorsSync(definition, options) {
|
|
765
|
+
const body = resolveMonitorJsonBody({
|
|
766
|
+
positional: definition,
|
|
767
|
+
file: options.file,
|
|
768
|
+
argLabel: "<definition>",
|
|
769
|
+
command: "deepline monitors sync"
|
|
770
|
+
});
|
|
771
|
+
const payload = await new DeeplineClient().monitors.sync(body, {
|
|
772
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
773
|
+
idempotencyKey: options.idempotencyKey ?? monitorJobIdempotencyKey("scalar-sync", void 0)
|
|
774
|
+
});
|
|
775
|
+
if (options.dryRun) {
|
|
776
|
+
const plan = payload;
|
|
777
|
+
printCommandEnvelope(
|
|
778
|
+
{ dry_run: true, ...plan },
|
|
779
|
+
{ json: options.json, text: renderMonitorDeployPlan(plan) }
|
|
780
|
+
);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
printCommandEnvelope(payload, {
|
|
784
|
+
json: options.json,
|
|
785
|
+
text: renderMonitorJob(payload)
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
async function handleScalarMonitorLifecycle(action, key, options) {
|
|
789
|
+
const payload = await new DeeplineClient().monitors[action](key, {
|
|
790
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
791
|
+
idempotencyKey: options.idempotencyKey ?? monitorJobIdempotencyKey(`${action}-${key}`, void 0)
|
|
792
|
+
});
|
|
793
|
+
printCommandEnvelope(payload, {
|
|
794
|
+
json: options.json,
|
|
795
|
+
text: options.dryRun ? void 0 : renderMonitorJob(payload)
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
function fleetIntegerOption(raw, flag) {
|
|
799
|
+
if (raw === void 0) return void 0;
|
|
800
|
+
const value = Number(raw);
|
|
801
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
802
|
+
throw new MonitorsUsageError(`${flag} must be a non-negative integer.`);
|
|
803
|
+
}
|
|
804
|
+
return value;
|
|
805
|
+
}
|
|
806
|
+
function fleetIdempotencyKey(operation, supplied) {
|
|
807
|
+
const normalized = supplied?.trim();
|
|
808
|
+
if (supplied !== void 0 && !normalized) {
|
|
809
|
+
throw new MonitorsUsageError("--idempotency-key must not be empty.");
|
|
810
|
+
}
|
|
811
|
+
return normalized ?? `monitor-fleet-${operation}-${randomUUID()}`;
|
|
812
|
+
}
|
|
813
|
+
function monitorJobIdempotencyKey(operation, supplied) {
|
|
814
|
+
const normalized = supplied?.trim();
|
|
815
|
+
if (supplied !== void 0 && !normalized) {
|
|
816
|
+
throw new MonitorsUsageError("--idempotency-key must not be empty.");
|
|
817
|
+
}
|
|
818
|
+
return normalized ?? `monitor-job-${operation}-${randomUUID()}`;
|
|
819
|
+
}
|
|
820
|
+
function readMonitorDefinitionsJsonFile(file) {
|
|
821
|
+
let raw;
|
|
822
|
+
try {
|
|
823
|
+
raw = file === "-" ? readFileSync(0, "utf8") : readFileSync(file, "utf8");
|
|
824
|
+
} catch (error) {
|
|
825
|
+
throw new MonitorsUsageError(
|
|
826
|
+
`Could not read --file ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
return JSON.parse(raw);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
throw new MonitorsUsageError(
|
|
833
|
+
`Monitor definitions must be valid JSON. ${error instanceof Error ? error.message : String(error)}`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function renderMonitorJob(payload) {
|
|
838
|
+
const job = asRecord(payload.job);
|
|
839
|
+
if (!job) return void 0;
|
|
840
|
+
const id = asString(job.id) ?? "<unknown>";
|
|
841
|
+
const state = asString(job.state) ?? "unknown";
|
|
842
|
+
const outcome = asString(job.outcome);
|
|
843
|
+
const phase = asString(job.phase);
|
|
844
|
+
const counts = asRecord(job.counts);
|
|
845
|
+
const reconciliation = asRecord(job.reconciliation);
|
|
846
|
+
const lines = [
|
|
847
|
+
`Monitor job: ${id}`,
|
|
848
|
+
`State: ${state}${phase ? ` (${phase})` : ""}`
|
|
849
|
+
];
|
|
850
|
+
if (outcome) lines.push(`Outcome: ${outcome}`);
|
|
851
|
+
const operationId = asString(job.operation_id);
|
|
852
|
+
if (operationId) lines.push(`Operation: ${operationId}`);
|
|
853
|
+
if (counts) {
|
|
854
|
+
lines.push(
|
|
855
|
+
`Counts: requested ${asFiniteNumber(counts.requested) ?? 0}, created ${asFiniteNumber(counts.created) ?? 0}, updated ${asFiniteNumber(counts.updated) ?? 0}, removed ${asFiniteNumber(counts.removed) ?? 0}, unchanged ${asFiniteNumber(counts.unchanged) ?? 0}, verified ${asFiniteNumber(counts.verified) ?? 0}, failed ${asFiniteNumber(counts.failed) ?? 0}`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
if (reconciliation) {
|
|
859
|
+
lines.push(
|
|
860
|
+
`Reconciliation: ${asString(reconciliation.state) ?? "pending"} (${asFiniteNumber(reconciliation.observed_bindings) ?? 0}/${asFiniteNumber(reconciliation.expected_bindings) ?? 0} bindings observed)`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
const next = asString(payload.next);
|
|
864
|
+
if (next) lines.push(`Next: ${next}`);
|
|
865
|
+
return `${lines.join("\n")}
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
async function handleMonitorJobBatchDeploy(options, idempotencyKey) {
|
|
869
|
+
if (!options.file) {
|
|
870
|
+
throw new MonitorsUsageError(
|
|
871
|
+
"A Batch file is required. Pass --file batch.json (or --file - for stdin)."
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const lint = lintMonitorBatchInput(
|
|
875
|
+
readMonitorDefinitionsJsonFile(options.file)
|
|
876
|
+
);
|
|
877
|
+
if (!lint.normalized) {
|
|
878
|
+
throw new MonitorsUsageError(
|
|
879
|
+
lint.issues.filter((issue) => issue.severity === "error").map((issue) => `${issue.path || "batch"}: ${issue.message}`).join("; ")
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
const payload = await new DeeplineClient().monitors.batch.deploy(
|
|
883
|
+
lint.normalized.monitors,
|
|
884
|
+
{ idempotencyKey }
|
|
885
|
+
);
|
|
886
|
+
printCommandEnvelope(
|
|
887
|
+
{
|
|
888
|
+
...payload,
|
|
889
|
+
idempotency_key: idempotencyKey,
|
|
890
|
+
...lint.issues.length ? { lint: { issues: lint.issues } } : {}
|
|
891
|
+
},
|
|
892
|
+
{ json: options.json, text: renderMonitorJob(payload) }
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
function namedBatchIdempotencyKey(batchId, intent, supplied) {
|
|
896
|
+
return supplied?.trim() || `monitor-batch-${intent}-${batchId}-${randomUUID()}`;
|
|
897
|
+
}
|
|
898
|
+
async function handleNamedMonitorBatchSync(batchId, options) {
|
|
899
|
+
if (!options.file) {
|
|
900
|
+
throw new MonitorsUsageError(
|
|
901
|
+
"monitors batches sync requires --file <batch.json>."
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
const lint = lintMonitorBatchInput(
|
|
905
|
+
readMonitorDefinitionsJsonFile(options.file)
|
|
906
|
+
);
|
|
907
|
+
if (!lint.normalized) {
|
|
908
|
+
throw new MonitorsUsageError(
|
|
909
|
+
lint.issues.filter((issue) => issue.severity === "error").map((issue) => `${issue.path || "batch"}: ${issue.message}`).join("; ")
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const parsedGeneration = options.expectedGeneration === void 0 ? void 0 : Number(options.expectedGeneration);
|
|
913
|
+
if (parsedGeneration !== void 0 && (!Number.isSafeInteger(parsedGeneration) || parsedGeneration < 0)) {
|
|
914
|
+
throw new MonitorsUsageError(
|
|
915
|
+
"--expected-generation must be a non-negative integer."
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (options.dryRun) {
|
|
919
|
+
printCommandEnvelope(
|
|
920
|
+
{
|
|
921
|
+
ok: true,
|
|
922
|
+
dry_run: true,
|
|
923
|
+
batch: { id: batchId, desired_count: lint.normalized.monitors.length },
|
|
924
|
+
issues: lint.issues,
|
|
925
|
+
next: `deepline monitors batches sync ${batchId} --file ${options.file} --idempotency-key <stable-key> --json`
|
|
926
|
+
},
|
|
927
|
+
{ json: options.json }
|
|
928
|
+
);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const idempotencyKey = namedBatchIdempotencyKey(
|
|
932
|
+
batchId,
|
|
933
|
+
"sync",
|
|
934
|
+
options.idempotencyKey
|
|
935
|
+
);
|
|
936
|
+
const payload = await new DeeplineClient().monitors.batches.sync(
|
|
937
|
+
batchId,
|
|
938
|
+
lint.normalized.monitors,
|
|
939
|
+
{
|
|
940
|
+
idempotencyKey,
|
|
941
|
+
...parsedGeneration === void 0 ? {} : { expectedGeneration: parsedGeneration }
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
printCommandEnvelope(
|
|
945
|
+
{ ...payload, idempotency_key: idempotencyKey },
|
|
946
|
+
{ json: options.json, text: renderMonitorJob(payload) }
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
async function handleNamedMonitorBatchLifecycle(batchId, intent, options) {
|
|
950
|
+
if (options.dryRun) {
|
|
951
|
+
printCommandEnvelope(
|
|
952
|
+
{
|
|
953
|
+
ok: true,
|
|
954
|
+
dry_run: true,
|
|
955
|
+
batch: { id: batchId, intent },
|
|
956
|
+
next: `deepline monitors batches ${intent} ${batchId} --idempotency-key <stable-key> --json`
|
|
957
|
+
},
|
|
958
|
+
{ json: options.json }
|
|
959
|
+
);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const idempotencyKey = namedBatchIdempotencyKey(
|
|
963
|
+
batchId,
|
|
964
|
+
intent,
|
|
965
|
+
options.idempotencyKey
|
|
966
|
+
);
|
|
967
|
+
const payload = await new DeeplineClient().monitors.batches[intent](batchId, {
|
|
968
|
+
idempotencyKey
|
|
969
|
+
});
|
|
970
|
+
printCommandEnvelope(
|
|
971
|
+
{ ...payload, idempotency_key: idempotencyKey },
|
|
972
|
+
{ json: options.json, text: renderMonitorJob(payload) }
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
async function handleNamedMonitorBatchGet(batchId, options) {
|
|
976
|
+
const payload = await new DeeplineClient().monitors.batches.get(batchId);
|
|
977
|
+
printCommandEnvelope(payload, {
|
|
978
|
+
json: options.json,
|
|
979
|
+
text: renderMonitorJob(payload)
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
async function handleMonitorBatchLint(options) {
|
|
983
|
+
if (!options.file) {
|
|
984
|
+
throw new MonitorsUsageError(
|
|
985
|
+
"monitors batch lint requires --file <batch.json>."
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
const result = lintMonitorBatchInput(
|
|
989
|
+
readMonitorDefinitionsJsonFile(options.file)
|
|
990
|
+
);
|
|
991
|
+
printCommandEnvelope(
|
|
992
|
+
{
|
|
993
|
+
ok: !result.issues.some((issue) => issue.severity === "error"),
|
|
994
|
+
...result.normalized ? { normalized: result.normalized } : {},
|
|
995
|
+
issues: result.issues
|
|
996
|
+
},
|
|
997
|
+
{ json: options.json }
|
|
998
|
+
);
|
|
999
|
+
if (result.issues.some((issue) => issue.severity === "error"))
|
|
1000
|
+
process.exitCode = 2;
|
|
1001
|
+
}
|
|
1002
|
+
async function handleMonitorFleetLint(options) {
|
|
1003
|
+
if (!options.file) {
|
|
1004
|
+
throw new MonitorsUsageError(
|
|
1005
|
+
"monitors fleets lint requires --file <fleet.json>."
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
const result = lintMonitorFleetInput(
|
|
1009
|
+
readMonitorDefinitionsJsonFile(options.file)
|
|
1010
|
+
);
|
|
1011
|
+
printCommandEnvelope(
|
|
1012
|
+
{
|
|
1013
|
+
ok: !result.issues.some((issue) => issue.severity === "error"),
|
|
1014
|
+
...result.normalized ? { normalized: result.normalized } : {},
|
|
1015
|
+
issues: result.issues
|
|
1016
|
+
},
|
|
1017
|
+
{ json: options.json }
|
|
1018
|
+
);
|
|
1019
|
+
if (result.issues.some((issue) => issue.severity === "error"))
|
|
1020
|
+
process.exitCode = 2;
|
|
1021
|
+
}
|
|
1022
|
+
async function handleMonitorJobGet(jobId, options) {
|
|
1023
|
+
const payload = await new DeeplineClient().monitors.jobs.get(jobId);
|
|
1024
|
+
printCommandEnvelope(payload, {
|
|
1025
|
+
json: options.json,
|
|
1026
|
+
text: renderMonitorJob(payload)
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async function handleMonitorJobWait(jobId, options) {
|
|
1030
|
+
const payload = await new DeeplineClient().monitors.jobs.wait(jobId, {
|
|
1031
|
+
timeoutMs: fleetTimeoutMs(options.timeout),
|
|
1032
|
+
pollIntervalMs: FLEET_POLL_INTERVAL_MS
|
|
1033
|
+
});
|
|
1034
|
+
printCommandEnvelope(payload, {
|
|
1035
|
+
json: options.json,
|
|
1036
|
+
text: renderMonitorJob(payload)
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
async function handleMonitorJobLogs(jobId, options) {
|
|
1040
|
+
const limit = options.limit ? fleetIntegerOption(options.limit, "--limit") : void 0;
|
|
1041
|
+
const payload = await new DeeplineClient().monitors.jobs.logs(jobId, {
|
|
1042
|
+
...limit === void 0 ? {} : { limit }
|
|
1043
|
+
});
|
|
1044
|
+
printCommandEnvelope(payload, { json: options.json });
|
|
1045
|
+
}
|
|
1046
|
+
async function handleMonitorJobCancel(jobId, options) {
|
|
1047
|
+
const payload = await new DeeplineClient().monitors.jobs.cancel(jobId);
|
|
1048
|
+
printCommandEnvelope(payload, {
|
|
1049
|
+
json: options.json,
|
|
1050
|
+
text: renderMonitorJob(payload)
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
var FLEET_COUNT_KEYS = [
|
|
1054
|
+
"pending",
|
|
1055
|
+
"submitting",
|
|
1056
|
+
"rate_limited",
|
|
1057
|
+
"settled",
|
|
1058
|
+
"blocked",
|
|
1059
|
+
"failed"
|
|
1060
|
+
];
|
|
1061
|
+
var FLEET_DEFAULT_WAIT_MS = 10 * 6e4;
|
|
1062
|
+
var FLEET_POLL_INTERVAL_MS = 2e3;
|
|
1063
|
+
var FLEET_PLAIN_PROGRESS_INTERVAL_MS = 15e3;
|
|
1064
|
+
var FLEET_RETRY_SAFE_NOTE = "Safe to retry \u2014 every fleet command is idempotent.";
|
|
1065
|
+
var MONITOR_FLEET_LIFECYCLE = [
|
|
1066
|
+
{
|
|
1067
|
+
step: 1,
|
|
1068
|
+
what: "Write a definition file you can read and edit.",
|
|
1069
|
+
command: "deepline monitors fleets init <fleet-id> --source public.accounts --key account_id --tool <tool> --payload '<json>' --out fleet.json"
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
step: 2,
|
|
1073
|
+
what: "See the plan and the credits before anything changes.",
|
|
1074
|
+
command: "deepline monitors fleets deploy --file fleet.json --dry-run"
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
step: 3,
|
|
1078
|
+
what: "Apply it and wait for the server to call it converged.",
|
|
1079
|
+
command: "deepline monitors fleets deploy --file fleet.json --wait"
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
step: 4,
|
|
1083
|
+
what: "Read live state; add --drift to see what has not settled.",
|
|
1084
|
+
command: "deepline monitors fleets get <fleet-id>"
|
|
1085
|
+
}
|
|
1086
|
+
];
|
|
1087
|
+
var MONITOR_FLEET_COMMANDS = [
|
|
1088
|
+
{
|
|
1089
|
+
name: "deploy",
|
|
1090
|
+
summary: "Create or update one table-backed Fleet from Customer DB rows."
|
|
1091
|
+
},
|
|
1092
|
+
{ name: "lint", summary: "Validate and normalize Fleet JSON locally." },
|
|
1093
|
+
{ name: "init", summary: "Write a Fleet definition JSON file." },
|
|
1094
|
+
{
|
|
1095
|
+
name: "sync",
|
|
1096
|
+
summary: "Create or re-plan a fleet from a definition file or its stored config."
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
name: "get",
|
|
1100
|
+
summary: "Read one fleet, list every fleet, or inspect drift."
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "deactivate",
|
|
1104
|
+
summary: "Deactivate a fleet and the monitors it owns."
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: "reactivate",
|
|
1108
|
+
summary: "Explicitly recreate a fleet after a credit-triggered suspension."
|
|
1109
|
+
}
|
|
1110
|
+
];
|
|
1111
|
+
var MONITOR_FLEET_AGENT_NOTES = [
|
|
1112
|
+
"Add --json for stable output; stdout is always exactly one JSON object.",
|
|
1113
|
+
"Every error includes a runnable `next` command \u2014 run it, do not guess.",
|
|
1114
|
+
"Exit 5 = retry the same command; 6 = inspect counts with `get --drift`; 7 = change the request."
|
|
1115
|
+
];
|
|
1116
|
+
var MONITOR_FLEET_HELP_EPILOG = `
|
|
1117
|
+
Agent notes:
|
|
1118
|
+
${MONITOR_FLEET_AGENT_NOTES.join("\n ")}
|
|
1119
|
+
`;
|
|
1120
|
+
var MonitorFleetStateError = class extends Error {
|
|
1121
|
+
code;
|
|
1122
|
+
next;
|
|
1123
|
+
constructor(input) {
|
|
1124
|
+
super(input.message);
|
|
1125
|
+
this.name = "MonitorFleetStateError";
|
|
1126
|
+
this.code = input.code;
|
|
1127
|
+
this.next = input.next;
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
function fleetCounts(payload) {
|
|
1131
|
+
const raw = asRecord(payload.counts) ?? {};
|
|
1132
|
+
const counts = {};
|
|
1133
|
+
for (const key of FLEET_COUNT_KEYS) {
|
|
1134
|
+
counts[key] = asFiniteNumber(raw[key]) ?? 0;
|
|
1135
|
+
}
|
|
1136
|
+
return counts;
|
|
1137
|
+
}
|
|
1138
|
+
function fleetShellArg(value) {
|
|
1139
|
+
return /^[A-Za-z0-9._/@:=-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
1140
|
+
}
|
|
1141
|
+
function fleetJsonFlag(options) {
|
|
1142
|
+
return options.json ? ["--json"] : [];
|
|
1143
|
+
}
|
|
1144
|
+
function fleetTimeoutMs(raw) {
|
|
1145
|
+
if (raw === void 0) return FLEET_DEFAULT_WAIT_MS;
|
|
1146
|
+
const match = /^(\d+)(ms|s|m|h)?$/.exec(raw.trim());
|
|
1147
|
+
if (!match) {
|
|
1148
|
+
throw new MonitorsUsageError(
|
|
1149
|
+
"--timeout must be a duration like 500ms, 90s, 10m, or 1h (a bare number is seconds)."
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
const value = Number(match[1]);
|
|
1153
|
+
if (!value) {
|
|
1154
|
+
throw new MonitorsUsageError("--timeout must be greater than zero.");
|
|
1155
|
+
}
|
|
1156
|
+
const unit = match[2] ?? "s";
|
|
1157
|
+
const factor = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
|
|
1158
|
+
return value * factor;
|
|
1159
|
+
}
|
|
1160
|
+
function fleetServerNext(payload) {
|
|
1161
|
+
return asString(payload.next) ?? asString(payload.next_action);
|
|
1162
|
+
}
|
|
1163
|
+
function fleetStatus(payload) {
|
|
1164
|
+
return asString(payload.status);
|
|
1165
|
+
}
|
|
1166
|
+
function fleetProgressReporter() {
|
|
1167
|
+
const interactive = process.stderr.isTTY === true;
|
|
1168
|
+
let wrote = false;
|
|
1169
|
+
let lastPlainAt = 0;
|
|
1170
|
+
return {
|
|
1171
|
+
report: (payload) => {
|
|
1172
|
+
const counts = fleetCounts(payload);
|
|
1173
|
+
const expected = asFiniteNumber(payload.expected) ?? FLEET_COUNT_KEYS.reduce((sum, key) => sum + counts[key], 0);
|
|
1174
|
+
const line = `settled ${counts.settled}/${expected} \xB7 rate_limited ${counts.rate_limited} \xB7 blocked ${counts.blocked}`;
|
|
1175
|
+
if (interactive) {
|
|
1176
|
+
process.stderr.write(`\r\x1B[2K${line}`);
|
|
1177
|
+
wrote = true;
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const now = Date.now();
|
|
1181
|
+
if (wrote && now - lastPlainAt < FLEET_PLAIN_PROGRESS_INTERVAL_MS) return;
|
|
1182
|
+
lastPlainAt = now;
|
|
1183
|
+
wrote = true;
|
|
1184
|
+
process.stderr.write(`${line}
|
|
1185
|
+
`);
|
|
1186
|
+
},
|
|
1187
|
+
finish: () => {
|
|
1188
|
+
if (interactive && wrote) process.stderr.write("\n");
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function fleetPlanLine(payload) {
|
|
1193
|
+
const plan = asRecord(payload.plan) ?? {};
|
|
1194
|
+
const blocked = asRecord(plan.blocked) ?? {};
|
|
1195
|
+
const entries = Object.entries(blocked).flatMap(([limitId, raw]) => {
|
|
1196
|
+
const count = asFiniteNumber(raw);
|
|
1197
|
+
return count === void 0 ? [] : [[limitId, count]];
|
|
1198
|
+
}).sort(([left], [right]) => left.localeCompare(right));
|
|
1199
|
+
const blockedTotal = entries.reduce((sum, [, count]) => sum + count, 0);
|
|
1200
|
+
const detail = entries.length === 0 ? "" : entries.length === 1 ? ` (${entries[0][0]})` : ` (${entries.map(([id, count]) => `${id}: ${count}`).join(", ")})`;
|
|
1201
|
+
return [
|
|
1202
|
+
`creates ${asFiniteNumber(plan.creates) ?? 0}`,
|
|
1203
|
+
`updates ${asFiniteNumber(plan.updates) ?? 0}`,
|
|
1204
|
+
`removes ${asFiniteNumber(plan.removes) ?? 0}`,
|
|
1205
|
+
`blocked ${blockedTotal}${detail}`
|
|
1206
|
+
].join(" \xB7 ");
|
|
1207
|
+
}
|
|
1208
|
+
function fleetCountsLine(payload) {
|
|
1209
|
+
const counts = fleetCounts(payload);
|
|
1210
|
+
return FLEET_COUNT_KEYS.map((key) => `${key} ${counts[key]}`).join(" \xB7 ");
|
|
1211
|
+
}
|
|
1212
|
+
function renderFleetSync(payload, input) {
|
|
1213
|
+
const lines = [
|
|
1214
|
+
input.dryRun ? `Fleet ${input.fleetId} \u2014 plan only, nothing changed` : `Fleet ${input.fleetId} \u2014 accepted`,
|
|
1215
|
+
` plan ${fleetPlanLine(payload)}`
|
|
1216
|
+
];
|
|
1217
|
+
const credits = asFiniteNumber(payload.credits_due_now);
|
|
1218
|
+
lines.push(` credits due now ${credits ?? 0}`);
|
|
1219
|
+
lines.push(
|
|
1220
|
+
` generation ${asFiniteNumber(payload.generation) ?? "unknown"} \xB7 replayed ${yesNo(
|
|
1221
|
+
payload.replayed
|
|
1222
|
+
)} \xB7 idempotency key ${input.idempotencyKey}`
|
|
1223
|
+
);
|
|
1224
|
+
const next = fleetServerNext(payload);
|
|
1225
|
+
if (next) lines.push(`Next: ${next}`);
|
|
1226
|
+
return `${lines.join("\n")}
|
|
1227
|
+
`;
|
|
1228
|
+
}
|
|
1229
|
+
function renderFleetGet(payload) {
|
|
1230
|
+
const id = asString(payload.id) ?? "unknown";
|
|
1231
|
+
const state = readMonitorState(payload) ?? "unknown";
|
|
1232
|
+
const status = fleetStatus(payload);
|
|
1233
|
+
const lines = [
|
|
1234
|
+
`Fleet ${id} \u2014 state ${state}`,
|
|
1235
|
+
` monitors ${asFiniteNumber(payload.live) ?? 0} live / ${asFiniteNumber(payload.expected) ?? 0} expected`,
|
|
1236
|
+
` counts ${fleetCountsLine(payload)}`,
|
|
1237
|
+
` generation ${asFiniteNumber(payload.generation) ?? "unknown"} \xB7 converged generation ${asFiniteNumber(payload.converged_generation) ?? "none"}`
|
|
1238
|
+
];
|
|
1239
|
+
const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays.flatMap((value) => {
|
|
1240
|
+
const play = asRecord(value);
|
|
1241
|
+
return play ? [play] : [];
|
|
1242
|
+
}) : [];
|
|
1243
|
+
lines.push("", `Observed Play bindings (${boundPlays.length}):`);
|
|
1244
|
+
if (boundPlays.length === 0) {
|
|
1245
|
+
lines.push(" none");
|
|
1246
|
+
} else {
|
|
1247
|
+
for (const play of boundPlays) {
|
|
1248
|
+
const name = asString(play.name) ?? "unnamed Play";
|
|
1249
|
+
const listeners = asFiniteNumber(play.listener_count) ?? 0;
|
|
1250
|
+
const status2 = asString(play.status);
|
|
1251
|
+
lines.push(
|
|
1252
|
+
` ${name}${listeners ? ` (${listeners} listener${listeners === 1 ? "" : "s"})` : ""}${status2 ? ` \u2014 ${status2}` : ""}`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
lines.push(
|
|
1257
|
+
" Read-only observation. Define or change bindings in the Play with sqlListeners."
|
|
1258
|
+
);
|
|
1259
|
+
lines.push(...renderFleetDriftRows(payload));
|
|
1260
|
+
const next = fleetServerNext(payload) ?? (status === "degraded" ? `deepline monitors fleets get ${fleetShellArg(id)} --drift` : void 0);
|
|
1261
|
+
if (next) lines.push(`Next: ${next}`);
|
|
1262
|
+
return `${lines.join("\n")}
|
|
1263
|
+
`;
|
|
1264
|
+
}
|
|
1265
|
+
function renderFleetDriftRows(payload) {
|
|
1266
|
+
const drift = Array.isArray(payload.drift) ? payload.drift : void 0;
|
|
1267
|
+
if (!drift) return [];
|
|
1268
|
+
const rows = drift.flatMap((raw) => {
|
|
1269
|
+
const row = asRecord(raw);
|
|
1270
|
+
return row ? [row] : [];
|
|
1271
|
+
}).map((row) => ({
|
|
1272
|
+
monitorKey: asString(row.monitor_key) ?? "-",
|
|
1273
|
+
entityKey: asString(row.entity_key) ?? "-",
|
|
1274
|
+
convergence: asString(row.convergence) ?? "-",
|
|
1275
|
+
since: asString(row.since) ?? "-",
|
|
1276
|
+
detail: asString(row.error) ?? (asString(row.blocked_by) ? `blocked_by ${asString(row.blocked_by)}` : asString(row.next_attempt_at) ? `next_attempt_at ${asString(row.next_attempt_at)}` : "")
|
|
1277
|
+
})).sort(
|
|
1278
|
+
(left, right) => left.monitorKey.localeCompare(right.monitorKey) || left.entityKey.localeCompare(right.entityKey)
|
|
1279
|
+
);
|
|
1280
|
+
if (rows.length === 0) {
|
|
1281
|
+
return [` drift none${payload.truncated === true ? " (truncated)" : ""}`];
|
|
1282
|
+
}
|
|
1283
|
+
const width = (values) => values.reduce((max, value) => Math.max(max, value.length), 0);
|
|
1284
|
+
const monitorWidth = width(rows.map((row) => row.monitorKey));
|
|
1285
|
+
const entityWidth = width(rows.map((row) => row.entityKey));
|
|
1286
|
+
const convergenceWidth = width(rows.map((row) => row.convergence));
|
|
1287
|
+
const lines = [
|
|
1288
|
+
` drift (${rows.length}${payload.truncated === true ? ", truncated" : ""})`
|
|
1289
|
+
];
|
|
1290
|
+
for (const row of rows) {
|
|
1291
|
+
lines.push(
|
|
1292
|
+
` ${row.monitorKey.padEnd(monitorWidth)} ${row.entityKey.padEnd(
|
|
1293
|
+
entityWidth
|
|
1294
|
+
)} ${row.convergence.padEnd(convergenceWidth)} ${row.since}${row.detail ? ` ${row.detail}` : ""}`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
return lines;
|
|
1298
|
+
}
|
|
1299
|
+
function renderFleetList(payload) {
|
|
1300
|
+
const fleets = (Array.isArray(payload.fleets) ? payload.fleets : []).flatMap((raw) => {
|
|
1301
|
+
const fleet = asRecord(raw);
|
|
1302
|
+
return fleet ? [fleet] : [];
|
|
1303
|
+
}).sort(
|
|
1304
|
+
(left, right) => (asString(left.id) ?? "").localeCompare(asString(right.id) ?? "")
|
|
1305
|
+
);
|
|
1306
|
+
if (fleets.length === 0) {
|
|
1307
|
+
return "No Monitor Fleets in this workspace.\nNext: deepline monitors fleets init <fleet-id> --help\n";
|
|
1308
|
+
}
|
|
1309
|
+
const idWidth = fleets.reduce(
|
|
1310
|
+
(max, fleet) => Math.max(max, (asString(fleet.id) ?? "").length),
|
|
1311
|
+
0
|
|
1312
|
+
);
|
|
1313
|
+
const stateWidth = fleets.reduce(
|
|
1314
|
+
(max, fleet) => Math.max(max, (readMonitorState(fleet) ?? "unknown").length),
|
|
1315
|
+
0
|
|
1316
|
+
);
|
|
1317
|
+
const lines = [`Fleets (${fleets.length})`];
|
|
1318
|
+
for (const fleet of fleets) {
|
|
1319
|
+
const counts = fleetCounts(fleet);
|
|
1320
|
+
const trouble = [
|
|
1321
|
+
counts.blocked ? `blocked ${counts.blocked}` : "",
|
|
1322
|
+
counts.failed ? `failed ${counts.failed}` : ""
|
|
1323
|
+
].filter(Boolean);
|
|
1324
|
+
lines.push(
|
|
1325
|
+
` ${(asString(fleet.id) ?? "-").padEnd(idWidth)} ${(readMonitorState(fleet) ?? "unknown").padEnd(stateWidth)} ${asFiniteNumber(fleet.live) ?? 0}/${asFiniteNumber(fleet.expected) ?? 0}${trouble.length ? ` ${trouble.join(" \xB7 ")}` : ""}`
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
return `${lines.join("\n")}
|
|
1329
|
+
`;
|
|
1330
|
+
}
|
|
1331
|
+
function renderFleetDeactivatePlan(payload, fleetId) {
|
|
1332
|
+
const radius = asRecord(payload.would_deactivate) ?? {};
|
|
1333
|
+
const lines = [
|
|
1334
|
+
`Deactivate fleet ${fleetId} \u2014 plan only, nothing changed`,
|
|
1335
|
+
` would deactivate ${asFiniteNumber(radius.monitors) ?? 0} monitors \xB7 ${asFiniteNumber(radius.accounts) ?? 0} accounts`
|
|
1336
|
+
];
|
|
1337
|
+
const note = asString(payload.note);
|
|
1338
|
+
if (note) lines.push(` ${note}`);
|
|
1339
|
+
lines.push(
|
|
1340
|
+
`Next: ${fleetServerNext(payload) ?? `deepline monitors fleets deactivate ${fleetShellArg(fleetId)} --yes`}`
|
|
1341
|
+
);
|
|
1342
|
+
return `${lines.join("\n")}
|
|
1343
|
+
`;
|
|
1344
|
+
}
|
|
1345
|
+
function reportFleetState(payload, error, options) {
|
|
1346
|
+
printCommandEnvelope(
|
|
1347
|
+
{
|
|
1348
|
+
...payload,
|
|
1349
|
+
code: error.code,
|
|
1350
|
+
message: error.message,
|
|
1351
|
+
next: error.next
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
json: options.json,
|
|
1355
|
+
text: `${options.text}${error.message}
|
|
1356
|
+
Next: ${error.next}
|
|
1357
|
+
`
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
process.exitCode = monitorsErrorExitCode(error);
|
|
1361
|
+
}
|
|
1362
|
+
function fleetSourceTable(value) {
|
|
1363
|
+
const [schema, table, extra] = value?.trim().split(".") ?? [];
|
|
1364
|
+
if (!schema || !table || extra || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(schema) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
|
|
1365
|
+
throw new MonitorsUsageError(
|
|
1366
|
+
"--source must be a Customer DB table in schema.table form."
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
return { schema, table };
|
|
1370
|
+
}
|
|
1371
|
+
function fleetColumnName(value, flag) {
|
|
1372
|
+
const name = value?.trim();
|
|
1373
|
+
if (!name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
1374
|
+
throw new MonitorsUsageError(`${flag} must be a SQL column identifier.`);
|
|
1375
|
+
}
|
|
1376
|
+
return name;
|
|
1377
|
+
}
|
|
1378
|
+
function fleetMemberLimit(value) {
|
|
1379
|
+
if (value === void 0) return 1e3;
|
|
1380
|
+
const limit = fleetIntegerOption(value, "--limit");
|
|
1381
|
+
if (!limit || limit > 1e3) {
|
|
1382
|
+
throw new MonitorsUsageError("--limit must be between 1 and 1,000.");
|
|
1383
|
+
}
|
|
1384
|
+
return limit;
|
|
1385
|
+
}
|
|
1386
|
+
function fleetOrderBy(values, sourceKey) {
|
|
1387
|
+
const parsed = (values ?? []).map((value) => {
|
|
1388
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*):(asc|desc)$/.exec(value.trim());
|
|
1389
|
+
if (!match) {
|
|
1390
|
+
throw new MonitorsUsageError(
|
|
1391
|
+
"--order-by must use column:asc or column:desc."
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
return { column: match[1], direction: match[2] };
|
|
1395
|
+
});
|
|
1396
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1397
|
+
for (const order of parsed) {
|
|
1398
|
+
if (seen.has(order.column)) {
|
|
1399
|
+
throw new MonitorsUsageError(
|
|
1400
|
+
`--order-by repeats '${order.column}'. Each column may appear once.`
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
seen.add(order.column);
|
|
1404
|
+
}
|
|
1405
|
+
const sourceOrder = parsed.find((order) => order.column === sourceKey);
|
|
1406
|
+
return [
|
|
1407
|
+
...parsed.filter((order) => order.column !== sourceKey),
|
|
1408
|
+
sourceOrder ?? { column: sourceKey, direction: "asc" }
|
|
1409
|
+
];
|
|
1410
|
+
}
|
|
1411
|
+
async function handleMonitorFleetsInit(fleetId, options) {
|
|
1412
|
+
if (!/^[a-z][a-z0-9-]{0,199}$/.test(fleetId)) {
|
|
1413
|
+
throw new MonitorsUsageError("<fleet-id> must be lowercase kebab-case.");
|
|
1414
|
+
}
|
|
1415
|
+
const source = fleetSourceTable(options.source);
|
|
1416
|
+
const key = fleetColumnName(options.key, "--key");
|
|
1417
|
+
const keyType = options.keyType?.trim() ?? "text";
|
|
1418
|
+
if (!["text", "uuid", "int4", "int8"].includes(keyType)) {
|
|
1419
|
+
throw new MonitorsUsageError(
|
|
1420
|
+
"--key-type must be text, uuid, int4, or int8."
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
const tool = options.tool?.trim();
|
|
1424
|
+
if (!tool) throw new MonitorsUsageError("--tool is required.");
|
|
1425
|
+
if (!options.payload) {
|
|
1426
|
+
throw new MonitorsUsageError(
|
|
1427
|
+
"--payload is required and must be the monitor payload JSON for --tool."
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
const payload = parseJsonObjectArg(options.payload, "--payload");
|
|
1431
|
+
const where = options.where ? parseJsonObjectArg(options.where, "--where") : void 0;
|
|
1432
|
+
const out = options.out?.trim();
|
|
1433
|
+
if (!out) throw new MonitorsUsageError("--out is required.");
|
|
1434
|
+
const authoredDefinition = {
|
|
1435
|
+
version: 2,
|
|
1436
|
+
kind: "fleet",
|
|
1437
|
+
id: fleetId,
|
|
1438
|
+
source: {
|
|
1439
|
+
kind: "customer_db_table",
|
|
1440
|
+
schema: source.schema,
|
|
1441
|
+
table: source.table,
|
|
1442
|
+
key: { column: key, type: keyType },
|
|
1443
|
+
...where ? { where } : {}
|
|
1444
|
+
},
|
|
1445
|
+
monitor: {
|
|
1446
|
+
tool,
|
|
1447
|
+
key: `${fleetId}-{{${key}}}`,
|
|
1448
|
+
payload
|
|
1449
|
+
},
|
|
1450
|
+
selection: {
|
|
1451
|
+
limit: fleetMemberLimit(options.limit),
|
|
1452
|
+
orderBy: fleetOrderBy(options.orderBy, key),
|
|
1453
|
+
membership: "sticky"
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
const definition = requireCompiledFleetDefinition(authoredDefinition);
|
|
1457
|
+
const file = resolve(out);
|
|
1458
|
+
writeFileSync(file, `${JSON.stringify(definition, null, 2)}
|
|
1459
|
+
`, "utf8");
|
|
1460
|
+
const next = `deepline monitors fleets deploy --file ${fleetShellArg(file)} --dry-run --json`;
|
|
1461
|
+
printCommandEnvelope(
|
|
1462
|
+
{ schemaVersion: 2, file, definition, next },
|
|
1463
|
+
{
|
|
1464
|
+
json: options.json,
|
|
1465
|
+
text: `Wrote ${file}
|
|
1466
|
+
Next: ${next}
|
|
1467
|
+
`
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
function requireCompiledFleetDefinition(definition) {
|
|
1472
|
+
const result = lintMonitorFleetInput(definition);
|
|
1473
|
+
if (result.normalized) return result.normalized;
|
|
1474
|
+
const messages = result.issues.map((issue) => `${issue.path || "definition"}: ${issue.message}`).join("; ");
|
|
1475
|
+
throw new MonitorsUsageError(
|
|
1476
|
+
`Monitor Fleet definition failed local contract check. ${messages}
|
|
1477
|
+
Fix the file, then re-run: deepline monitors fleets sync --file <fleet.json> --dry-run`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
function fleetSyncCommandLine(target, options) {
|
|
1481
|
+
const parts = ["deepline monitors fleets sync"];
|
|
1482
|
+
if (options.file) parts.push(`--file ${fleetShellArg(options.file)}`);
|
|
1483
|
+
else if (target) parts.push(fleetShellArg(target));
|
|
1484
|
+
if (options.dryRun) parts.push("--dry-run");
|
|
1485
|
+
if (options.wait) parts.push("--wait");
|
|
1486
|
+
if (options.timeout)
|
|
1487
|
+
parts.push(`--timeout ${fleetShellArg(options.timeout)}`);
|
|
1488
|
+
if (options.expectedGeneration !== void 0) {
|
|
1489
|
+
parts.push(
|
|
1490
|
+
`--expected-generation ${fleetShellArg(options.expectedGeneration)}`
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
if (options.idempotencyKey) {
|
|
1494
|
+
parts.push(`--idempotency-key ${fleetShellArg(options.idempotencyKey)}`);
|
|
1495
|
+
}
|
|
1496
|
+
parts.push(...fleetJsonFlag(options));
|
|
1497
|
+
return parts.join(" ");
|
|
1498
|
+
}
|
|
1499
|
+
async function handleMonitorFleetsSync(target, options, idempotencyKey) {
|
|
1500
|
+
const fileDefinitions = options.file ? readMonitorDefinitionsJsonFile(options.file) : void 0;
|
|
1501
|
+
const usesDefinition = options.file !== void 0 || target?.trim().startsWith("{") === true;
|
|
1502
|
+
if (!usesDefinition && !target?.trim()) {
|
|
1503
|
+
throw new MonitorsUsageError(
|
|
1504
|
+
"sync needs a fleet: pass --file fleet.json to apply a definition, or <fleet-id> to re-plan the stored one.\n deepline monitors fleets sync --file fleet.json --dry-run\n deepline monitors fleets sync <fleet-id> --dry-run"
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
const definition = usesDefinition ? requireCompiledFleetDefinition(
|
|
1508
|
+
asRecord(
|
|
1509
|
+
fileDefinitions ?? resolveMonitorJsonBody({
|
|
1510
|
+
positional: target,
|
|
1511
|
+
file: options.file,
|
|
1512
|
+
argLabel: "<definition>",
|
|
1513
|
+
command: "deepline monitors fleets sync"
|
|
1514
|
+
})
|
|
1515
|
+
) ?? {}
|
|
1516
|
+
) : void 0;
|
|
1517
|
+
const fleetId = definition ? asString(definition.id) ?? "" : target.trim();
|
|
1518
|
+
if (!fleetId) {
|
|
1519
|
+
throw new MonitorsUsageError(
|
|
1520
|
+
'The Fleet definition has no id. Add a top-level "id" and re-run.'
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
const client = new DeeplineClient();
|
|
1524
|
+
const payload = await client.monitors.fleets.sync(
|
|
1525
|
+
definition ?? fleetId,
|
|
1526
|
+
{
|
|
1527
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
1528
|
+
...options.expectedGeneration !== void 0 ? {
|
|
1529
|
+
expectedGeneration: fleetIntegerOption(
|
|
1530
|
+
options.expectedGeneration,
|
|
1531
|
+
"--expected-generation"
|
|
1532
|
+
)
|
|
1533
|
+
} : {},
|
|
1534
|
+
idempotencyKey
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
if (options.dryRun) {
|
|
1538
|
+
assertMonitorDryRunAcknowledged(payload, {
|
|
1539
|
+
command: "deepline monitors fleets sync",
|
|
1540
|
+
mutation: "fleet sync"
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
const accepted = { ...payload, idempotency_key: idempotencyKey };
|
|
1544
|
+
if (!options.wait || options.dryRun) {
|
|
1545
|
+
printCommandEnvelope(accepted, {
|
|
1546
|
+
json: options.json,
|
|
1547
|
+
text: renderFleetSync(payload, {
|
|
1548
|
+
fleetId,
|
|
1549
|
+
dryRun: options.dryRun === true,
|
|
1550
|
+
idempotencyKey
|
|
1551
|
+
})
|
|
1552
|
+
});
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const timeoutMs = fleetTimeoutMs(options.timeout);
|
|
1556
|
+
const progress = fleetProgressReporter();
|
|
1557
|
+
let final;
|
|
1558
|
+
try {
|
|
1559
|
+
final = await client.monitors.fleets.waitForConvergence(fleetId, {
|
|
1560
|
+
timeoutMs,
|
|
1561
|
+
pollIntervalMs: FLEET_POLL_INTERVAL_MS,
|
|
1562
|
+
onProgress: progress.report
|
|
1563
|
+
});
|
|
1564
|
+
} finally {
|
|
1565
|
+
progress.finish();
|
|
1566
|
+
}
|
|
1567
|
+
const envelope = { ...accepted, ...final };
|
|
1568
|
+
const status = fleetStatus(final);
|
|
1569
|
+
const summary = renderFleetGet(final);
|
|
1570
|
+
if (status === "converged") {
|
|
1571
|
+
printCommandEnvelope(envelope, { json: options.json, text: summary });
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const driftCommand = `deepline monitors fleets get ${fleetShellArg(fleetId)} --drift --json`;
|
|
1575
|
+
if (status === "degraded") {
|
|
1576
|
+
reportFleetState(
|
|
1577
|
+
envelope,
|
|
1578
|
+
new MonitorFleetStateError({
|
|
1579
|
+
code: "MONITOR_FLEET_DEGRADED",
|
|
1580
|
+
message: `Fleet ${fleetId} is degraded: some members will not converge without a change. Retrying sync will not fix it.`,
|
|
1581
|
+
next: fleetServerNext(final) ?? driftCommand
|
|
1582
|
+
}),
|
|
1583
|
+
{ json: options.json, text: summary }
|
|
1584
|
+
);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
reportFleetState(
|
|
1588
|
+
envelope,
|
|
1589
|
+
new MonitorFleetStateError({
|
|
1590
|
+
code: "MONITOR_FLEET_WAIT_TIMEOUT",
|
|
1591
|
+
message: `Stopped waiting after ${timeoutMs}ms. Nothing is broken and nothing was lost \u2014 fleet ${fleetId} is still ${status ?? "working"} on the server.`,
|
|
1592
|
+
next: fleetServerNext(final) ?? driftCommand
|
|
1593
|
+
}),
|
|
1594
|
+
{ json: options.json, text: summary }
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
async function handleMonitorFleetsGet(fleetId, options) {
|
|
1598
|
+
if (options.drift && !fleetId) {
|
|
1599
|
+
throw new MonitorsUsageError(
|
|
1600
|
+
"--drift needs a fleet id: deepline monitors fleets get <fleet-id> --drift"
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const limit = options.limit === void 0 ? void 0 : fleetIntegerOption(options.limit, "--limit");
|
|
1604
|
+
const client = new DeeplineClient();
|
|
1605
|
+
const payload = await client.monitors.fleets.get(fleetId, {
|
|
1606
|
+
...options.drift ? { drift: true } : {},
|
|
1607
|
+
...limit !== void 0 ? { limit } : {}
|
|
1608
|
+
});
|
|
1609
|
+
const text = fleetId ? renderFleetGet(payload) : renderFleetList(payload);
|
|
1610
|
+
if (!options.check) {
|
|
1611
|
+
printCommandEnvelope(payload, { json: options.json, text });
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const statuses = fleetId ? [fleetStatus(payload)] : (Array.isArray(payload.fleets) ? payload.fleets : []).map(
|
|
1615
|
+
(raw) => fleetStatus(asRecord(raw) ?? {})
|
|
1616
|
+
);
|
|
1617
|
+
const unconverged = statuses.filter((status) => status !== "converged");
|
|
1618
|
+
if (unconverged.length === 0) {
|
|
1619
|
+
printCommandEnvelope(payload, { json: options.json, text });
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const target = fleetId ?? "<fleet-id>";
|
|
1623
|
+
const degraded = unconverged.includes("degraded");
|
|
1624
|
+
reportFleetState(
|
|
1625
|
+
payload,
|
|
1626
|
+
new MonitorFleetStateError({
|
|
1627
|
+
code: degraded ? "MONITOR_FLEET_DEGRADED" : "MONITOR_FLEET_NOT_CONVERGED",
|
|
1628
|
+
message: degraded ? `Fleet ${target} is degraded: members are stuck and retrying will not move them.` : `Fleet ${target} has not converged yet.`,
|
|
1629
|
+
next: fleetServerNext(payload) ?? `deepline monitors fleets get ${fleetShellArg(target)} --drift --json`
|
|
1630
|
+
}),
|
|
1631
|
+
{ json: options.json, text }
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
function fleetReactivateCommandLine(fleetId, options) {
|
|
1635
|
+
const parts = [
|
|
1636
|
+
"deepline",
|
|
1637
|
+
"monitors",
|
|
1638
|
+
"fleets",
|
|
1639
|
+
"reactivate",
|
|
1640
|
+
fleetShellArg(fleetId),
|
|
1641
|
+
"--yes"
|
|
1642
|
+
];
|
|
1643
|
+
if (options.wait) parts.push("--wait");
|
|
1644
|
+
if (options.timeout)
|
|
1645
|
+
parts.push(`--timeout ${fleetShellArg(options.timeout)}`);
|
|
1646
|
+
if (options.idempotencyKey)
|
|
1647
|
+
parts.push(`--idempotency-key ${fleetShellArg(options.idempotencyKey)}`);
|
|
1648
|
+
parts.push(...fleetJsonFlag(options));
|
|
1649
|
+
return parts.join(" ");
|
|
1650
|
+
}
|
|
1651
|
+
async function handleMonitorFleetsReactivate(fleetId, options, idempotencyKey) {
|
|
1652
|
+
if (!options.yes) {
|
|
1653
|
+
throw new MonitorsUsageError(
|
|
1654
|
+
`reactivate can recreate every suspended provider monitor and restart lifecycle billing. Confirm with --yes:
|
|
1655
|
+
${fleetReactivateCommandLine(fleetId, options)}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
const client = new DeeplineClient();
|
|
1659
|
+
const payload = await client.monitors.fleets.reactivate(fleetId, {
|
|
1660
|
+
idempotencyKey
|
|
1661
|
+
});
|
|
1662
|
+
const accepted = { ...payload, idempotency_key: idempotencyKey };
|
|
1663
|
+
if (!options.wait) {
|
|
1664
|
+
printCommandEnvelope(accepted, {
|
|
1665
|
+
json: options.json,
|
|
1666
|
+
text: `Fleet ${fleetId} \u2014 ${readMonitorState(payload) ? `state ${readMonitorState(payload)}` : "reactivation accepted"}
|
|
1667
|
+
idempotency key ${idempotencyKey}
|
|
1668
|
+
`
|
|
1669
|
+
});
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
const timeoutMs = fleetTimeoutMs(options.timeout);
|
|
1673
|
+
const progress = fleetProgressReporter();
|
|
1674
|
+
let final;
|
|
1675
|
+
try {
|
|
1676
|
+
final = await client.monitors.fleets.waitForConvergence(fleetId, {
|
|
1677
|
+
timeoutMs,
|
|
1678
|
+
pollIntervalMs: FLEET_POLL_INTERVAL_MS,
|
|
1679
|
+
onProgress: progress.report
|
|
1680
|
+
});
|
|
1681
|
+
} finally {
|
|
1682
|
+
progress.finish();
|
|
1683
|
+
}
|
|
1684
|
+
const envelope = { ...accepted, ...final };
|
|
1685
|
+
const text = renderFleetGet(final);
|
|
1686
|
+
if (fleetStatus(final) === "converged") {
|
|
1687
|
+
printCommandEnvelope(envelope, { json: options.json, text });
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
reportFleetState(
|
|
1691
|
+
envelope,
|
|
1692
|
+
new MonitorFleetStateError({
|
|
1693
|
+
code: "MONITOR_FLEET_WAIT_TIMEOUT",
|
|
1694
|
+
message: `Stopped waiting after ${timeoutMs}ms. Fleet ${fleetId} recovery is still in progress on the server.`,
|
|
1695
|
+
next: fleetServerNext(final) ?? `deepline monitors fleets get ${fleetShellArg(fleetId)} --json`
|
|
1696
|
+
}),
|
|
1697
|
+
{ json: options.json, text }
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
function fleetDeactivateCommandLine(fleetId, options) {
|
|
1701
|
+
const parts = [
|
|
1702
|
+
"deepline monitors fleets deactivate",
|
|
1703
|
+
fleetShellArg(fleetId),
|
|
1704
|
+
"--yes"
|
|
1705
|
+
];
|
|
1706
|
+
if (options.dryRun) parts.push("--dry-run");
|
|
1707
|
+
if (options.wait) parts.push("--wait");
|
|
1708
|
+
if (options.timeout)
|
|
1709
|
+
parts.push(`--timeout ${fleetShellArg(options.timeout)}`);
|
|
1710
|
+
if (options.idempotencyKey) {
|
|
1711
|
+
parts.push(`--idempotency-key ${fleetShellArg(options.idempotencyKey)}`);
|
|
1712
|
+
}
|
|
1713
|
+
parts.push(...fleetJsonFlag(options));
|
|
1714
|
+
return parts.join(" ");
|
|
1715
|
+
}
|
|
1716
|
+
async function confirmFleetDeactivate(fleetId, plan) {
|
|
1717
|
+
process.stderr.write(renderFleetDeactivatePlan(plan, fleetId));
|
|
1718
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1719
|
+
try {
|
|
1720
|
+
const answer = await rl.question(
|
|
1721
|
+
`Deactivate fleet "${fleetId}" and the monitors it owns? [y/N] `
|
|
1722
|
+
);
|
|
1723
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1724
|
+
} finally {
|
|
1725
|
+
rl.close();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
async function handleMonitorFleetsDeactivate(fleetId, options, idempotencyKey) {
|
|
1729
|
+
const client = new DeeplineClient();
|
|
1730
|
+
if (options.dryRun) {
|
|
1731
|
+
const plan = await client.monitors.fleets.deactivate(fleetId, {
|
|
1732
|
+
dryRun: true,
|
|
1733
|
+
idempotencyKey
|
|
1734
|
+
});
|
|
1735
|
+
assertMonitorDryRunAcknowledged(plan, {
|
|
1736
|
+
command: "deepline monitors fleets deactivate",
|
|
1737
|
+
mutation: "fleet deactivate"
|
|
1738
|
+
});
|
|
1739
|
+
printCommandEnvelope(plan, {
|
|
1740
|
+
json: options.json,
|
|
1741
|
+
text: renderFleetDeactivatePlan(plan, fleetId)
|
|
1742
|
+
});
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (!options.yes) {
|
|
1746
|
+
const interactive = process.stdout.isTTY === true && process.stdin.isTTY === true;
|
|
1747
|
+
if (!interactive) {
|
|
1748
|
+
throw new MonitorsUsageError(
|
|
1749
|
+
`deactivate is destructive: it deactivates fleet "${fleetId}" and every monitor it owns. Non-interactive runs must confirm with --yes:
|
|
1750
|
+
${fleetDeactivateCommandLine(fleetId, options)}
|
|
1751
|
+
Preview the blast radius first:
|
|
1752
|
+
deepline monitors fleets deactivate ${fleetShellArg(fleetId)} --dry-run`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
const plan = await client.monitors.fleets.deactivate(fleetId, {
|
|
1756
|
+
dryRun: true,
|
|
1757
|
+
idempotencyKey: `${idempotencyKey}-plan`
|
|
1758
|
+
});
|
|
1759
|
+
assertMonitorDryRunAcknowledged(plan, {
|
|
1760
|
+
command: "deepline monitors fleets deactivate",
|
|
1761
|
+
mutation: "fleet deactivate"
|
|
1762
|
+
});
|
|
1763
|
+
if (!await confirmFleetDeactivate(fleetId, plan)) {
|
|
1764
|
+
process.stderr.write(
|
|
1765
|
+
`Aborted. Fleet "${fleetId}" was not deactivated.
|
|
1766
|
+
`
|
|
1767
|
+
);
|
|
1768
|
+
process.exitCode = 2;
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
const payload = await client.monitors.fleets.deactivate(fleetId, {
|
|
1773
|
+
idempotencyKey
|
|
1774
|
+
});
|
|
1775
|
+
const accepted = { ...payload, idempotency_key: idempotencyKey };
|
|
1776
|
+
if (payload.already === true) {
|
|
1777
|
+
printCommandEnvelope(accepted, {
|
|
1778
|
+
json: options.json,
|
|
1779
|
+
text: `Fleet ${fleetId} is already deactivated.
|
|
1780
|
+
`
|
|
1781
|
+
});
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
const acceptedText = `Fleet ${fleetId} \u2014 ${readMonitorState(payload) ? `state ${readMonitorState(payload)}` : "deactivation accepted"}
|
|
1785
|
+
idempotency key ${idempotencyKey}
|
|
1786
|
+
` + (fleetServerNext(payload) ? `Next: ${fleetServerNext(payload)}
|
|
1787
|
+
` : "");
|
|
1788
|
+
if (!options.wait) {
|
|
1789
|
+
printCommandEnvelope(accepted, {
|
|
1790
|
+
json: options.json,
|
|
1791
|
+
text: acceptedText
|
|
1792
|
+
});
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const timeoutMs = fleetTimeoutMs(options.timeout);
|
|
1796
|
+
const progress = fleetProgressReporter();
|
|
1797
|
+
let final;
|
|
1798
|
+
try {
|
|
1799
|
+
final = await client.monitors.fleets.waitForConvergence(fleetId, {
|
|
1800
|
+
timeoutMs,
|
|
1801
|
+
pollIntervalMs: FLEET_POLL_INTERVAL_MS,
|
|
1802
|
+
onProgress: progress.report,
|
|
1803
|
+
until: "deactivated"
|
|
1804
|
+
});
|
|
1805
|
+
} finally {
|
|
1806
|
+
progress.finish();
|
|
1807
|
+
}
|
|
1808
|
+
const envelope = { ...accepted, ...final };
|
|
1809
|
+
const summary = renderFleetGet(final);
|
|
1810
|
+
if (fleetStatus(final) === "deactivated") {
|
|
1811
|
+
printCommandEnvelope(envelope, { json: options.json, text: summary });
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
reportFleetState(
|
|
1815
|
+
envelope,
|
|
1816
|
+
new MonitorFleetStateError({
|
|
1817
|
+
code: "MONITOR_FLEET_WAIT_TIMEOUT",
|
|
1818
|
+
message: `Stopped waiting after ${timeoutMs}ms. Deactivation of fleet ${fleetId} is still in progress on the server; nothing was lost.`,
|
|
1819
|
+
next: fleetServerNext(final) ?? `deepline monitors fleets get ${fleetShellArg(fleetId)} --json`
|
|
1820
|
+
}),
|
|
1821
|
+
{ json: options.json, text: summary }
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
function handleMonitorFleetsOverview(options) {
|
|
1825
|
+
const lines = [
|
|
1826
|
+
"Monitor Fleets \u2014 one Customer DB table row, one ordinary monitor.",
|
|
1827
|
+
"",
|
|
1828
|
+
"Lifecycle:",
|
|
1829
|
+
...MONITOR_FLEET_LIFECYCLE.flatMap((entry) => [
|
|
1830
|
+
` ${entry.step}. ${entry.what}`,
|
|
1831
|
+
` ${entry.command}`
|
|
1832
|
+
]),
|
|
1833
|
+
"",
|
|
1834
|
+
"Commands:",
|
|
1835
|
+
...MONITOR_FLEET_COMMANDS.map(
|
|
1836
|
+
(command) => ` ${command.name.padEnd(12)}${command.summary}`
|
|
1837
|
+
),
|
|
1838
|
+
"",
|
|
1839
|
+
"Agent notes:",
|
|
1840
|
+
...MONITOR_FLEET_AGENT_NOTES.map((note) => ` ${note}`)
|
|
1841
|
+
];
|
|
1842
|
+
printCommandEnvelope(
|
|
1843
|
+
{
|
|
1844
|
+
schemaVersion: 1,
|
|
1845
|
+
lifecycle: MONITOR_FLEET_LIFECYCLE,
|
|
1846
|
+
commands: MONITOR_FLEET_COMMANDS,
|
|
1847
|
+
agent_notes: MONITOR_FLEET_AGENT_NOTES,
|
|
1848
|
+
next: MONITOR_FLEET_LIFECYCLE[0].command
|
|
1849
|
+
},
|
|
1850
|
+
{ json: options.json, text: `${lines.join("\n")}
|
|
1851
|
+
` }
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
function renderMonitorGet(payload) {
|
|
1855
|
+
const key = asString(payload.key);
|
|
1856
|
+
const tool = asString(payload.tool);
|
|
1857
|
+
if (!key || !tool) return void 0;
|
|
1858
|
+
const state = readMonitorState(payload);
|
|
1859
|
+
const monitorType = asString(payload.type);
|
|
1860
|
+
const hasLastReceivedEvent = "last_received_event" in payload;
|
|
1861
|
+
const lastReceivedEvent = asString(payload.last_received_event);
|
|
1862
|
+
const definition = asRecord(payload.definition);
|
|
1863
|
+
const pricingLine = monitorPricingLine(payload.pricing);
|
|
1864
|
+
const billing = asRecord(payload.billing);
|
|
1865
|
+
const nextRenewalAt = billing ? asString(billing.next_renewal_at) : void 0;
|
|
1866
|
+
const dependents = asRecord(payload.dependents);
|
|
1867
|
+
const webhook = asRecord(payload.webhook);
|
|
1868
|
+
const guidance = asRecord(payload.setup_guidance);
|
|
1869
|
+
const plays = dependents && Array.isArray(dependents.plays) ? dependents.plays : [];
|
|
1870
|
+
const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays.flatMap((value) => {
|
|
1871
|
+
const play = asRecord(value);
|
|
1872
|
+
return play ? [play] : [];
|
|
1873
|
+
}) : [];
|
|
1874
|
+
const lines = [
|
|
1875
|
+
`Monitor: ${key}`,
|
|
1876
|
+
`Tool: ${tool}`,
|
|
1877
|
+
...monitorType ? [`Type: ${monitorType}`] : [],
|
|
1878
|
+
...state ? [`Status: ${state}`] : [],
|
|
1879
|
+
...hasLastReceivedEvent ? [`Latest inbound event: ${lastReceivedEvent ?? "never"}`] : [],
|
|
1880
|
+
...pricingLine ? [`Pricing: ${pricingLine}`] : [],
|
|
1881
|
+
...nextRenewalAt ? [`Next renewal: ${nextRenewalAt}`] : []
|
|
1882
|
+
];
|
|
1883
|
+
if (definition) {
|
|
1884
|
+
lines.push("", "Current definition:", ` ${JSON.stringify(definition)}`);
|
|
1885
|
+
}
|
|
1886
|
+
if (webhook) {
|
|
1887
|
+
lines.push("", `Webhook: ${asString(webhook.state) ?? "unknown"}`);
|
|
1888
|
+
const callbackUrl = asString(webhook.callback_url);
|
|
1889
|
+
if (callbackUrl) lines.push(` Callback URL (sensitive): ${callbackUrl}`);
|
|
1890
|
+
}
|
|
1891
|
+
if (guidance) {
|
|
1892
|
+
lines.push("", asString(guidance.title) ?? "Provider setup:");
|
|
1893
|
+
for (const step of Array.isArray(guidance.steps) ? guidance.steps : []) {
|
|
1894
|
+
const text = asString(step);
|
|
1895
|
+
if (text) lines.push(` \u2022 ${text}`);
|
|
1896
|
+
}
|
|
1897
|
+
for (const raw of Array.isArray(guidance.documentation) ? guidance.documentation : []) {
|
|
1898
|
+
const doc = asRecord(raw);
|
|
1899
|
+
const label = asString(doc?.label);
|
|
1900
|
+
const url = asString(doc?.url);
|
|
1901
|
+
if (label && url) lines.push(` Docs: ${label} \u2014 ${url}`);
|
|
1902
|
+
}
|
|
1903
|
+
const payloadExample = asRecord(guidance.payload_example);
|
|
1904
|
+
if (payloadExample) {
|
|
1905
|
+
lines.push(` Payload example: ${JSON.stringify(payloadExample)}`);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
const samplePayload = asRecord(payload.sample_payload);
|
|
1909
|
+
if (samplePayload) {
|
|
1910
|
+
lines.push(
|
|
1911
|
+
"",
|
|
1912
|
+
`Sample test payload: ${JSON.stringify(samplePayload)}`,
|
|
1913
|
+
` deepline monitors test ${key} '${JSON.stringify(samplePayload)}' --json`
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
const monitorSpec = asRecord(payload.monitor_spec);
|
|
1917
|
+
if (monitorSpec?.available === false) {
|
|
1918
|
+
lines.push(
|
|
1919
|
+
"",
|
|
1920
|
+
`Monitor spec: unavailable \u2014 ${asString(monitorSpec.message) ?? "use the stored definition and check before an update."}`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
const specFields = Array.isArray(monitorSpec?.fields) ? monitorSpec.fields.flatMap((value) => {
|
|
1924
|
+
const field = asRecord(value);
|
|
1925
|
+
const path = asString(field?.path);
|
|
1926
|
+
const description = asString(field?.description);
|
|
1927
|
+
const semantics = asString(field?.semantics);
|
|
1928
|
+
const applicability = asString(field?.applicability);
|
|
1929
|
+
const grammar = asRecord(field?.grammar);
|
|
1930
|
+
const grammarSyntax = asString(grammar?.syntax);
|
|
1931
|
+
return path ? [
|
|
1932
|
+
{
|
|
1933
|
+
path,
|
|
1934
|
+
description,
|
|
1935
|
+
semantics,
|
|
1936
|
+
applicability,
|
|
1937
|
+
grammarSyntax
|
|
1938
|
+
}
|
|
1939
|
+
] : [];
|
|
1940
|
+
}) : [];
|
|
1941
|
+
if (specFields.length > 0) {
|
|
1942
|
+
lines.push("", "Monitor spec:");
|
|
1943
|
+
for (const field of specFields) {
|
|
1944
|
+
lines.push(
|
|
1945
|
+
` ${field.path}${field.description ? ` \u2014 ${field.description}` : ""}`
|
|
1946
|
+
);
|
|
1947
|
+
if (field.semantics && field.semantics !== field.description) {
|
|
1948
|
+
lines.push(` Behavior: ${field.semantics}`);
|
|
1949
|
+
}
|
|
1950
|
+
if (field.applicability) {
|
|
1951
|
+
lines.push(` Applies to: ${field.applicability}`);
|
|
1952
|
+
}
|
|
1953
|
+
if (field.grammarSyntax) {
|
|
1954
|
+
lines.push(` Format: ${field.grammarSyntax}`);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
lines.push("", `Current SQL listener consumers (${boundPlays.length}):`);
|
|
1959
|
+
if (boundPlays.length === 0) {
|
|
1960
|
+
lines.push(" none");
|
|
1961
|
+
} else {
|
|
1962
|
+
for (const play of boundPlays) {
|
|
1963
|
+
const name = asString(play.name) ?? "unnamed Play";
|
|
1964
|
+
const listener = asString(play.listener_key);
|
|
1965
|
+
const health = asRecord(play.consumer_health);
|
|
1966
|
+
const runStatus = asString(health?.last_run_status);
|
|
1967
|
+
const lastDelivery = asFiniteNumber(health?.last_delivery_at);
|
|
1968
|
+
const runId = asString(health?.last_run_id);
|
|
1969
|
+
const error = asString(health?.last_error);
|
|
1970
|
+
lines.push(` ${name}${listener ? ` (listener: ${listener})` : ""}:`);
|
|
1971
|
+
lines.push(
|
|
1972
|
+
` Latest delivered event: ${lastDelivery !== void 0 ? new Date(lastDelivery).toISOString() : "never"}`,
|
|
1973
|
+
` Latest consumer run: ${runId ?? "never"}${runStatus ? ` (${runStatus})` : ""}`
|
|
1974
|
+
);
|
|
1975
|
+
if (error) lines.push(` error: ${error}`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (payload.consumer_health_truncated === true) {
|
|
1979
|
+
lines.push(" Results are truncated; use --json for the returned detail.");
|
|
1980
|
+
}
|
|
1981
|
+
lines.push("", `Dependent published plays (${plays.length}):`);
|
|
1982
|
+
if (plays.length === 0) {
|
|
1983
|
+
lines.push(" none");
|
|
1984
|
+
} else {
|
|
1985
|
+
for (const raw of plays) {
|
|
1986
|
+
const play = asRecord(raw);
|
|
1987
|
+
const name = play ? asString(play.name) : void 0;
|
|
1988
|
+
if (!play || !name) continue;
|
|
1989
|
+
const listener = asString(play.listener_key);
|
|
1990
|
+
const output = asString(play.output_key);
|
|
1991
|
+
lines.push(
|
|
1992
|
+
` ${name}${listener ? ` (listener: ${listener})` : ""}${output ? ` \u2014 stream: ${output}` : ""}`
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (dependents?.truncated === true) {
|
|
1997
|
+
lines.push(" Results are truncated; use --json for the returned detail.");
|
|
1998
|
+
}
|
|
1999
|
+
return `${lines.join("\n")}
|
|
2000
|
+
`;
|
|
2001
|
+
}
|
|
2002
|
+
async function handleMonitorsGet(key, options) {
|
|
2003
|
+
const client = new DeeplineClient();
|
|
2004
|
+
const payload = await client.monitors.get(key);
|
|
2005
|
+
const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays : null;
|
|
2006
|
+
const dependents = boundPlays ? {
|
|
2007
|
+
plays: boundPlays,
|
|
2008
|
+
truncated: payload.consumer_health_truncated === true
|
|
2009
|
+
} : await client.monitors.dependents(key);
|
|
2010
|
+
const detail = { ...payload, dependents };
|
|
2011
|
+
printCommandEnvelope(detail, {
|
|
2012
|
+
json: options.json,
|
|
2013
|
+
text: renderMonitorGet(detail)
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
async function handleMonitorsTest(key, payload, options) {
|
|
2017
|
+
const explicitPayload = parseJsonObjectArg(payload, "<payload>");
|
|
2018
|
+
const result = await new DeeplineClient().monitors.test(key, explicitPayload);
|
|
2019
|
+
const text = `Monitor diagnostic for ${key}: ${result.accepted === true ? "accepted" : "rejected"}
|
|
2020
|
+
mode: validation_only (no rows written, credits spent, or Plays dispatched)
|
|
2021
|
+
persisted rows: ${asFiniteNumber(result.persisted_rows) ?? 0}
|
|
2022
|
+
dispatched bound Plays: ${asFiniteNumber(result.dispatched_bound_plays) ?? 0}
|
|
2023
|
+
`;
|
|
2024
|
+
printCommandEnvelope(result, { json: options.json, text });
|
|
2025
|
+
}
|
|
2026
|
+
async function handleMonitorsValidate(key, options) {
|
|
2027
|
+
const result = await new DeeplineClient().monitors.validate(key);
|
|
2028
|
+
printCommandEnvelope(result, { json: options.json });
|
|
2029
|
+
if (result.valid === false) process.exitCode = 7;
|
|
2030
|
+
}
|
|
2031
|
+
async function confirmMonitorDelete(key) {
|
|
2032
|
+
const rl = createInterface({
|
|
2033
|
+
input: process.stdin,
|
|
2034
|
+
output: process.stderr
|
|
2035
|
+
});
|
|
2036
|
+
try {
|
|
2037
|
+
const answer = await rl.question(
|
|
2038
|
+
`Delete monitor "${key}"? This deprovisions the upstream provider resource. [y/N] `
|
|
2039
|
+
);
|
|
2040
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
2041
|
+
} finally {
|
|
2042
|
+
rl.close();
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
async function handleMonitorsDelete(key, options) {
|
|
2046
|
+
const client = new DeeplineClient();
|
|
2047
|
+
if (options.dryRun) {
|
|
2048
|
+
const payload2 = await client.monitors.delete(key, { dryRun: true });
|
|
2049
|
+
assertMonitorDryRunAcknowledged(payload2, {
|
|
2050
|
+
command: "deepline monitors delete",
|
|
2051
|
+
mutation: "delete"
|
|
2052
|
+
});
|
|
2053
|
+
printCommandEnvelope(payload2, {
|
|
2054
|
+
json: options.json,
|
|
2055
|
+
text: renderMonitorDeletePlan(payload2, key)
|
|
2056
|
+
});
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
if (!options.yes) {
|
|
2060
|
+
const interactive = process.stdout.isTTY === true && process.stdin.isTTY === true;
|
|
2061
|
+
if (!interactive) {
|
|
2062
|
+
throw monitorDeleteRequiresYesError(key);
|
|
2063
|
+
}
|
|
2064
|
+
const confirmed = await confirmMonitorDelete(key);
|
|
2065
|
+
if (!confirmed) {
|
|
2066
|
+
process.stderr.write(`Aborted. Monitor "${key}" was not deleted.
|
|
2067
|
+
`);
|
|
2068
|
+
process.exitCode = 2;
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
const payload = await client.monitors.delete(key);
|
|
2073
|
+
printCommandEnvelope(payload, { json: options.json });
|
|
2074
|
+
}
|
|
2075
|
+
async function handleMonitorsUpdate(key, patch, options) {
|
|
2076
|
+
const body = resolveMonitorJsonBody({
|
|
2077
|
+
positional: patch,
|
|
2078
|
+
file: options.file,
|
|
2079
|
+
argLabel: "<patch>",
|
|
2080
|
+
command: `deepline monitors update ${key}`
|
|
2081
|
+
});
|
|
2082
|
+
const payload = await new DeeplineClient().monitors.update(key, body);
|
|
2083
|
+
printCommandEnvelope(payload, { json: options.json });
|
|
2084
|
+
}
|
|
2085
|
+
async function handleMonitorsReactivate(key, options) {
|
|
2086
|
+
const client = new DeeplineClient();
|
|
2087
|
+
if (options.dryRun) {
|
|
2088
|
+
const payload2 = await client.monitors.reactivate(key, { dryRun: true });
|
|
2089
|
+
assertMonitorDryRunAcknowledged(payload2, {
|
|
2090
|
+
command: "deepline monitors reactivate",
|
|
2091
|
+
mutation: "reactivate"
|
|
2092
|
+
});
|
|
2093
|
+
printCommandEnvelope(payload2, {
|
|
2094
|
+
json: options.json,
|
|
2095
|
+
text: renderMonitorReactivatePlan(payload2, key)
|
|
2096
|
+
});
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
const payload = await client.monitors.reactivate(key);
|
|
2100
|
+
printCommandEnvelope(payload, { json: options.json });
|
|
2101
|
+
}
|
|
2102
|
+
var MONITOR_VERB_POLL_INTERVAL_MS = 2e3;
|
|
2103
|
+
function monitorHealthLine(health) {
|
|
2104
|
+
if (health.refresh?.state === "running") {
|
|
2105
|
+
return `refreshing whole scope \xB7 scanned ${health.scanned} so far`;
|
|
2106
|
+
}
|
|
2107
|
+
const alignmentRequired = health.alignment_required ?? 0;
|
|
2108
|
+
return [
|
|
2109
|
+
`converged ${health.converged}/${health.scanned}`,
|
|
2110
|
+
`needs_repair ${health.needs_repair}`,
|
|
2111
|
+
`missing ${health.observed.missing}`,
|
|
2112
|
+
`unaudited ${health.observed.unknown + health.observed.unset}`,
|
|
2113
|
+
...alignmentRequired > 0 ? [`needs_alignment ${alignmentRequired}`] : [],
|
|
2114
|
+
`silent ${health.silent}`
|
|
2115
|
+
].join(" \xB7 ");
|
|
2116
|
+
}
|
|
2117
|
+
function monitorVerbProgressReporter() {
|
|
2118
|
+
const interactive = process.stderr.isTTY === true;
|
|
2119
|
+
let wrote = false;
|
|
2120
|
+
let lastPlainAt = 0;
|
|
2121
|
+
return {
|
|
2122
|
+
report: (health) => {
|
|
2123
|
+
const line = monitorHealthLine(health);
|
|
2124
|
+
if (interactive) {
|
|
2125
|
+
process.stderr.write(`\r\x1B[2K${line}`);
|
|
2126
|
+
wrote = true;
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const now = Date.now();
|
|
2130
|
+
if (wrote && now - lastPlainAt < FLEET_PLAIN_PROGRESS_INTERVAL_MS) return;
|
|
2131
|
+
lastPlainAt = now;
|
|
2132
|
+
wrote = true;
|
|
2133
|
+
process.stderr.write(`${line}
|
|
2134
|
+
`);
|
|
2135
|
+
},
|
|
2136
|
+
finish: () => {
|
|
2137
|
+
if (interactive && wrote) process.stderr.write("\n");
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
async function watchMonitorHealth(input) {
|
|
2142
|
+
const deadlineAt = Date.now() + input.timeoutMs;
|
|
2143
|
+
const scope = input.fleetId ? { fleetId: input.fleetId } : {};
|
|
2144
|
+
let last = await input.client.monitors.health(scope);
|
|
2145
|
+
input.onProgress(last);
|
|
2146
|
+
while (Date.now() < deadlineAt) {
|
|
2147
|
+
const unresolved = last.observed.unknown + last.observed.unset;
|
|
2148
|
+
const alignmentRequired = last.alignment_required ?? 0;
|
|
2149
|
+
if (last.refresh?.state !== "running" && !last.truncated && last.needs_repair === 0 && (unresolved === 0 || alignmentRequired === unresolved)) {
|
|
2150
|
+
return last;
|
|
2151
|
+
}
|
|
2152
|
+
await new Promise(
|
|
2153
|
+
(resolve2) => setTimeout(resolve2, MONITOR_VERB_POLL_INTERVAL_MS)
|
|
2154
|
+
);
|
|
2155
|
+
last = await input.client.monitors.health(scope);
|
|
2156
|
+
input.onProgress(last);
|
|
2157
|
+
}
|
|
2158
|
+
return last;
|
|
2159
|
+
}
|
|
2160
|
+
function monitorHealthText(health) {
|
|
2161
|
+
const partial = health.refresh?.state === "running" ? " (refreshing \u2014 counts below are not final)" : health.truncated ? " (partial \u2014 scope larger than one read)" : "";
|
|
2162
|
+
const unauditedMonitors = health.unaudited_monitors ?? [];
|
|
2163
|
+
const lines = [
|
|
2164
|
+
`scope: ${health.scope.fleet_id ?? "workspace"}${partial}`,
|
|
2165
|
+
monitorHealthLine(health)
|
|
2166
|
+
];
|
|
2167
|
+
if ((health.batch_operations?.length ?? 0) > 0) {
|
|
2168
|
+
lines.push("");
|
|
2169
|
+
lines.push("batch work in progress:");
|
|
2170
|
+
for (const operation of health.batch_operations ?? []) {
|
|
2171
|
+
const retry = operation.next_attempt_at === void 0 ? "" : ` \xB7 next ${new Date(operation.next_attempt_at).toISOString()}`;
|
|
2172
|
+
lines.push(
|
|
2173
|
+
` ${operation.kind} ${operation.run_id} \xB7 ${operation.state}${retry}`
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (health.deactivation_reasons.length > 0) {
|
|
2178
|
+
lines.push("");
|
|
2179
|
+
lines.push("deactivated by Deepline:");
|
|
2180
|
+
for (const entry of health.deactivation_reasons) {
|
|
2181
|
+
const how = entry.resolution ? ` \u2014 ${entry.resolution}` : "";
|
|
2182
|
+
lines.push(` ${entry.reason} (${entry.count})${how}`);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
if (unauditedMonitors.length > 0) {
|
|
2186
|
+
const reason = {
|
|
2187
|
+
not_yet_audited: "has not been observed yet",
|
|
2188
|
+
missing_provider_resource_id: "has no stored upstream resource id",
|
|
2189
|
+
provider_observation_unknown: "the provider observation is still unknown"
|
|
2190
|
+
};
|
|
2191
|
+
lines.push("");
|
|
2192
|
+
lines.push("unresolved monitor observations:");
|
|
2193
|
+
for (const entry of unauditedMonitors) {
|
|
2194
|
+
lines.push(
|
|
2195
|
+
` ${entry.monitor_key} ${entry.tool} \u2014 ${reason[entry.reason]}`
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
if ((health.alignment_required ?? 0) > 0) {
|
|
2200
|
+
lines.push("");
|
|
2201
|
+
lines.push(
|
|
2202
|
+
`${health.alignment_required} monitor(s) need provider identity alignment; normal repair intentionally will not guess or create a paid replacement.`
|
|
2203
|
+
);
|
|
2204
|
+
lines.push(
|
|
2205
|
+
`Run the exact inventory audit, then review one safe adoption candidate: deepline-admin monitors reconcile ${health.scope.org_id} --monitor <monitor-key> --environment production --json`
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
if (health.worst_offenders.length > 0) {
|
|
2209
|
+
lines.push("");
|
|
2210
|
+
lines.push(
|
|
2211
|
+
`silent longest (no delivery in ${health.silent_window_days}d):`
|
|
2212
|
+
);
|
|
2213
|
+
for (const entry of health.worst_offenders) {
|
|
2214
|
+
const when = entry.last_event_at === null ? "never" : new Date(entry.last_event_at).toISOString();
|
|
2215
|
+
lines.push(` ${entry.monitor_key} ${entry.tool} last event ${when}`);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
return `${lines.join("\n")}
|
|
2219
|
+
`;
|
|
2220
|
+
}
|
|
2221
|
+
async function handleMonitorsAudit(options) {
|
|
2222
|
+
const client = new DeeplineClient();
|
|
2223
|
+
const fleetId = options.fleet?.trim() || void 0;
|
|
2224
|
+
const scope = fleetId ? { fleetId } : {};
|
|
2225
|
+
let result = await client.monitors.audit(scope);
|
|
2226
|
+
const passes = [result.audit];
|
|
2227
|
+
while (result.audit.cursor !== null) {
|
|
2228
|
+
result = await client.monitors.audit({
|
|
2229
|
+
...scope,
|
|
2230
|
+
cursor: result.audit.cursor
|
|
2231
|
+
});
|
|
2232
|
+
passes.push(result.audit);
|
|
2233
|
+
}
|
|
2234
|
+
const totals = passes.reduce(
|
|
2235
|
+
(sum, pass) => ({
|
|
2236
|
+
scanned: sum.scanned + pass.scanned,
|
|
2237
|
+
asked: sum.asked + pass.asked,
|
|
2238
|
+
stale: sum.stale + pass.stale,
|
|
2239
|
+
missing: sum.missing + pass.observed.missing
|
|
2240
|
+
}),
|
|
2241
|
+
{ scanned: 0, asked: 0, stale: 0, missing: 0 }
|
|
2242
|
+
);
|
|
2243
|
+
let health = result.health;
|
|
2244
|
+
if (options.watch) {
|
|
2245
|
+
const progress = monitorVerbProgressReporter();
|
|
2246
|
+
try {
|
|
2247
|
+
health = await watchMonitorHealth({
|
|
2248
|
+
client,
|
|
2249
|
+
...scope,
|
|
2250
|
+
timeoutMs: fleetTimeoutMs(options.timeout),
|
|
2251
|
+
onProgress: progress.report
|
|
2252
|
+
});
|
|
2253
|
+
} finally {
|
|
2254
|
+
progress.finish();
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
const unauditable = result.audit.unauditable_capabilities;
|
|
2258
|
+
const overtaken = totals.stale > 0 ? ` \xB7 ${totals.stale} observations overtaken by a concurrent settle` : "";
|
|
2259
|
+
const text = [
|
|
2260
|
+
`audited ${totals.scanned} monitors \xB7 asked the provider about ${totals.asked}`,
|
|
2261
|
+
`missing ${totals.missing}${overtaken}`,
|
|
2262
|
+
...unauditable.length > 0 ? [`not auditable (no batch-read contract): ${unauditable.join(", ")}`] : [],
|
|
2263
|
+
"",
|
|
2264
|
+
monitorHealthText(health)
|
|
2265
|
+
].join("\n");
|
|
2266
|
+
printCommandEnvelope(
|
|
2267
|
+
{ ...result, audit: { ...result.audit, totals }, health },
|
|
2268
|
+
{ json: options.json, text }
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
async function handleMonitorsRepair(options) {
|
|
2272
|
+
return await handleMonitorBatch("repair", {
|
|
2273
|
+
...options,
|
|
2274
|
+
...options.fleet?.trim() ? {} : { target: "needs-repair" }
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
async function handleMonitorsHealth(options) {
|
|
2278
|
+
const fleetId = options.fleet?.trim() || void 0;
|
|
2279
|
+
const health = await new DeeplineClient().monitors.health(
|
|
2280
|
+
fleetId ? { fleetId } : {}
|
|
2281
|
+
);
|
|
2282
|
+
printCommandEnvelope(health, {
|
|
2283
|
+
json: options.json,
|
|
2284
|
+
text: monitorHealthText(health)
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
function monitorBatchScope(options, kind) {
|
|
2288
|
+
const selectors = [
|
|
2289
|
+
options.target ? "target" : null,
|
|
2290
|
+
options.fleet?.trim() ? "fleet" : null,
|
|
2291
|
+
options.keys?.trim() ? "keys" : null
|
|
2292
|
+
].filter((value) => value !== null);
|
|
2293
|
+
if (selectors.length === 0 && kind === "audit") {
|
|
2294
|
+
return { kind: "target", target: "all" };
|
|
2295
|
+
}
|
|
2296
|
+
if (selectors.length !== 1) {
|
|
2297
|
+
throw new MonitorsUsageError(
|
|
2298
|
+
`monitors batch ${kind} needs exactly one selector: --target, --fleet, or --keys.`
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (options.target) return { kind: "target", target: options.target };
|
|
2302
|
+
if (options.fleet?.trim())
|
|
2303
|
+
return { kind: "fleet", fleetId: options.fleet.trim() };
|
|
2304
|
+
const monitorKeys = (options.keys ?? "").split(",").map((key) => key.trim()).filter(Boolean);
|
|
2305
|
+
if (monitorKeys.length === 0) {
|
|
2306
|
+
throw new MonitorsUsageError(
|
|
2307
|
+
"--keys needs one or more comma-separated monitor keys."
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
return { kind: "keys", monitorKeys };
|
|
2311
|
+
}
|
|
2312
|
+
function monitorBatchIdempotencyKey(kind, value) {
|
|
2313
|
+
return value?.trim() || `monitor-batch-${kind}-${randomUUID()}`;
|
|
2314
|
+
}
|
|
2315
|
+
function isCanonicalBatchJsonDocument(raw) {
|
|
2316
|
+
try {
|
|
2317
|
+
const parsed = JSON.parse(raw);
|
|
2318
|
+
if (Array.isArray(parsed)) return true;
|
|
2319
|
+
const input = asRecord(parsed);
|
|
2320
|
+
return input !== null && (input.kind === "batch" || Object.prototype.hasOwnProperty.call(input, "monitors") || Object.prototype.hasOwnProperty.call(input, "version"));
|
|
2321
|
+
} catch {
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
async function handleMonitorBatchDeploy(options) {
|
|
2326
|
+
if (!options.file?.trim()) {
|
|
2327
|
+
throw new MonitorsUsageError(
|
|
2328
|
+
"monitors batch deploy requires --file <monitors.ndjson>."
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
if (options.dryRun !== true && options.yes !== true) {
|
|
2332
|
+
throw new MonitorsUsageError(
|
|
2333
|
+
"monitors batch deploy changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes."
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
let manifestNdjson;
|
|
2337
|
+
try {
|
|
2338
|
+
manifestNdjson = readFileSync(resolve(options.file), "utf8");
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
throw new MonitorsUsageError(
|
|
2341
|
+
`Could not read monitor batch manifest '${options.file}': ${error instanceof Error ? error.message : String(error)}`
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
if (!manifestNdjson.trim()) {
|
|
2345
|
+
throw new MonitorsUsageError("Monitor batch manifest is empty.");
|
|
2346
|
+
}
|
|
2347
|
+
if (isCanonicalBatchJsonDocument(manifestNdjson)) {
|
|
2348
|
+
throw new MonitorsUsageError(
|
|
2349
|
+
"Canonical Batch JSON cannot use --dry-run or --yes; those flags select the retained legacy NDJSON operation. Run `deepline monitors batch lint --file <batch.json> --json`, then submit the Monitor Job with `deepline monitors batch deploy --file <batch.json> --idempotency-key <key> --json`."
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
const idempotencyKey = monitorBatchIdempotencyKey(
|
|
2353
|
+
"deploy",
|
|
2354
|
+
options.idempotencyKey
|
|
2355
|
+
);
|
|
2356
|
+
const result = await new DeeplineClient().monitors.batch.submit({
|
|
2357
|
+
kind: "deploy",
|
|
2358
|
+
manifestNdjson,
|
|
2359
|
+
dryRun: options.dryRun === true,
|
|
2360
|
+
idempotencyKey
|
|
2361
|
+
});
|
|
2362
|
+
const operation = result.operation;
|
|
2363
|
+
printCommandEnvelope(
|
|
2364
|
+
{ ...result, idempotency_key: idempotencyKey },
|
|
2365
|
+
{
|
|
2366
|
+
json: options.json,
|
|
2367
|
+
text: [
|
|
2368
|
+
`${result.rejoined ? "rejoined" : "submitted"} deploy batch ${operation.runId}`,
|
|
2369
|
+
`state ${operation.state} \xB7 retry key ${idempotencyKey}`,
|
|
2370
|
+
`inspect: deepline monitors batch get ${operation.runId} --json`
|
|
2371
|
+
].join("\n") + "\n"
|
|
2372
|
+
}
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
async function handleMonitorBatch(kind, options) {
|
|
2376
|
+
if ((kind === "deactivate" || kind === "reactivate" || kind === "repair") && options.dryRun !== true && options.yes !== true) {
|
|
2377
|
+
throw new MonitorsUsageError(
|
|
2378
|
+
`monitors batch ${kind} changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes.`
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
const scope = monitorBatchScope(options, kind);
|
|
2382
|
+
if (kind === "deactivate" && scope.kind === "target" && !options.reason?.trim()) {
|
|
2383
|
+
throw new MonitorsUsageError(
|
|
2384
|
+
"monitors batch deactivate --target requires --reason (for example insufficient_balance or billing_failure). Omit --reason with --keys for a customer-requested teardown."
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
const idempotencyKey = monitorBatchIdempotencyKey(
|
|
2388
|
+
kind,
|
|
2389
|
+
options.idempotencyKey
|
|
2390
|
+
);
|
|
2391
|
+
const result = await new DeeplineClient().monitors.batch.submit({
|
|
2392
|
+
kind,
|
|
2393
|
+
scope,
|
|
2394
|
+
dryRun: options.dryRun === true,
|
|
2395
|
+
...options.reason?.trim() ? { deactivationReason: options.reason.trim() } : {},
|
|
2396
|
+
idempotencyKey
|
|
2397
|
+
});
|
|
2398
|
+
const operation = result.operation;
|
|
2399
|
+
const text = [
|
|
2400
|
+
`${result.rejoined ? "rejoined" : "submitted"} ${operation.kind} batch ${operation.runId}`,
|
|
2401
|
+
`state ${operation.state} \xB7 retry key ${idempotencyKey}`,
|
|
2402
|
+
`inspect: deepline monitors batch get ${operation.runId} --json`
|
|
2403
|
+
].join("\n");
|
|
2404
|
+
printCommandEnvelope(
|
|
2405
|
+
{ ...result, idempotency_key: idempotencyKey },
|
|
2406
|
+
{ json: options.json, text: `${text}
|
|
2407
|
+
` }
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
async function handleMonitorBatchGet(operationId, options) {
|
|
2411
|
+
const result = await new DeeplineClient().monitors.batch.get(operationId, {
|
|
2412
|
+
...options.cursor ? { cursor: options.cursor } : {}
|
|
2413
|
+
});
|
|
2414
|
+
const summary = result.operation.summary ?? {};
|
|
2415
|
+
const text = [
|
|
2416
|
+
`${result.operation.kind} batch ${result.operation.runId}`,
|
|
2417
|
+
`state ${result.operation.state}`,
|
|
2418
|
+
`progress ${JSON.stringify(summary)}`,
|
|
2419
|
+
...result.results ? [
|
|
2420
|
+
`results ${result.results.page.length}${result.results.isDone ? "" : " (more; pass --cursor)"}`
|
|
2421
|
+
] : []
|
|
2422
|
+
].join("\n");
|
|
2423
|
+
printCommandEnvelope(result, { json: options.json, text: `${text}
|
|
2424
|
+
` });
|
|
2425
|
+
}
|
|
2426
|
+
function registerMonitorsCommands(program) {
|
|
2427
|
+
const monitors = program.command("monitors").description("Discover, deploy, and manage Deepline monitors.").showSuggestionAfterError(true).addHelpText(
|
|
2428
|
+
"after",
|
|
2429
|
+
`
|
|
2430
|
+
Notes:
|
|
2431
|
+
Deepline monitors are Deepline-native signal feeds: a monitor writes events
|
|
2432
|
+
into a Customer DB table; a play reacts to each new row via a
|
|
2433
|
+
sqlListeners trigger \u2014 see \`deepline plays bootstrap monitor-triggered\`.
|
|
2434
|
+
Monitor JSON declares only what to watch (key, tool, optional name, payload).
|
|
2435
|
+
Play bindings belong in the Play; monitor deployment never changes them.
|
|
2436
|
+
The Monitors rollout controls access to the live provider catalog; use
|
|
2437
|
+
\`deepline tools list --categories monitors\` for the exact tool ids available to you.
|
|
2438
|
+
Contact the Deepline team to request access; until access is enabled, these
|
|
2439
|
+
commands return a clear monitor_access_required error.
|
|
2440
|
+
|
|
2441
|
+
Exit codes:
|
|
2442
|
+
0 success; 2 usage/local input; 3 auth or permission; 4 not found;
|
|
2443
|
+
5 server failure (retry the same command); 6 not at the desired state
|
|
2444
|
+
(fleets: degraded, or --wait/--check stopped while healthy \u2014 retrying
|
|
2445
|
+
changes nothing); 7 refused, change the request.
|
|
2446
|
+
\`monitors status\` exits 1 when you lack monitor access (documented contract).
|
|
2447
|
+
|
|
2448
|
+
Examples:
|
|
2449
|
+
deepline monitors status --json
|
|
2450
|
+
deepline tools list --categories monitors --json
|
|
2451
|
+
deepline tools get deepline_native.company_radar --json
|
|
2452
|
+
deepline monitors check '{"key":"job-openings","tool":"deepline_native.company_radar","payload":{"domain":"stripe.com","radar_type":"company_job_openings"}}'
|
|
2453
|
+
deepline monitors deploy --dry-run --file monitor.json
|
|
2454
|
+
deepline monitors deploy --file monitor.json
|
|
2455
|
+
deepline monitors list --status all --json
|
|
2456
|
+
deepline monitors get my-monitor --json
|
|
2457
|
+
deepline monitors update my-monitor '{"name":"Interested replies"}' --json
|
|
2458
|
+
deepline monitors delete my-monitor --dry-run
|
|
2459
|
+
deepline monitors reactivate my-monitor --dry-run
|
|
2460
|
+
`
|
|
2461
|
+
);
|
|
2462
|
+
withJsonOption(
|
|
2463
|
+
monitors.command("audit").description(
|
|
2464
|
+
"Re-read what the provider actually holds for your monitors."
|
|
2465
|
+
).option("--fleet <fleet-id>", "Audit one fleet instead of every monitor").option("--watch", "Keep polling health until the scope settles").option(
|
|
2466
|
+
"--timeout <duration>",
|
|
2467
|
+
"How long --watch waits, e.g. 90s, 5m, 1h (default 10m)"
|
|
2468
|
+
).addHelpText(
|
|
2469
|
+
"after",
|
|
2470
|
+
`
|
|
2471
|
+
Notes:
|
|
2472
|
+
Asks the provider, by resource id, what it still holds, and records the answer
|
|
2473
|
+
on each monitor. It changes nothing upstream: no creates, no deletes, no
|
|
2474
|
+
intent. Safe to run at any time, as often as you like \u2014 running it twice is
|
|
2475
|
+
running it once, twice.
|
|
2476
|
+
|
|
2477
|
+
A monitor the provider does not return is recorded "missing". Use
|
|
2478
|
+
'deepline monitors repair' to act on that.
|
|
2479
|
+
|
|
2480
|
+
Examples:
|
|
2481
|
+
deepline monitors audit
|
|
2482
|
+
deepline monitors audit --fleet accounts-eu --json
|
|
2483
|
+
deepline monitors audit --fleet accounts-eu --watch --timeout 5m
|
|
2484
|
+
`
|
|
2485
|
+
)
|
|
2486
|
+
).action(monitorsAction(handleMonitorsAudit));
|
|
2487
|
+
withJsonOption(
|
|
2488
|
+
monitors.command("repair").description(
|
|
2489
|
+
"Converge the monitors whose desired and observed states disagree."
|
|
2490
|
+
).option("--fleet <fleet-id>", "Repair one fleet instead of every monitor").option(
|
|
2491
|
+
"--dry-run",
|
|
2492
|
+
"Record the bounded repair plan without provider changes"
|
|
2493
|
+
).option("--yes", "Confirm the asynchronous provider repair").option(
|
|
2494
|
+
"--idempotency-key <key>",
|
|
2495
|
+
"Stable retry key (generated when omitted)"
|
|
2496
|
+
).addHelpText(
|
|
2497
|
+
"after",
|
|
2498
|
+
`
|
|
2499
|
+
Notes:
|
|
2500
|
+
This is the short spelling of 'monitors batch repair --target needs-repair'.
|
|
2501
|
+
It handles both direct monitors and Fleet members through one durable
|
|
2502
|
+
service-account worker. An active historical monitor without an upstream id
|
|
2503
|
+
is selected for repair; current Convex writes cannot create that state.
|
|
2504
|
+
|
|
2505
|
+
It returns a run id immediately. Inspect retained results with
|
|
2506
|
+
'deepline monitors batch get <run-id> --json'. Reuse --idempotency-key after
|
|
2507
|
+
a lost response to rejoin the exact run without another provider request.
|
|
2508
|
+
|
|
2509
|
+
Examples:
|
|
2510
|
+
deepline monitors repair --dry-run
|
|
2511
|
+
deepline monitors repair --yes
|
|
2512
|
+
deepline monitors repair --fleet accounts-eu --yes --idempotency-key repair-eu-2026-09-09
|
|
2513
|
+
`
|
|
2514
|
+
)
|
|
2515
|
+
).action(monitorsAction(handleMonitorsRepair));
|
|
2516
|
+
withJsonOption(
|
|
2517
|
+
monitors.command("health").description("Delivery and convergence health for your monitors.").option(
|
|
2518
|
+
"--fleet <fleet-id>",
|
|
2519
|
+
"Report on one fleet instead of every monitor"
|
|
2520
|
+
).addHelpText(
|
|
2521
|
+
"after",
|
|
2522
|
+
`
|
|
2523
|
+
Notes:
|
|
2524
|
+
Read-only. Reports how many monitors the provider holds as asked, how many
|
|
2525
|
+
disagree, how many Deepline deactivated and why, and how many are "silent" \u2014
|
|
2526
|
+
the provider holds them but nothing has been delivered recently. The worst
|
|
2527
|
+
offenders are listed longest-quiet first.
|
|
2528
|
+
|
|
2529
|
+
Examples:
|
|
2530
|
+
deepline monitors health
|
|
2531
|
+
deepline monitors health --fleet accounts-eu --json
|
|
2532
|
+
`
|
|
2533
|
+
)
|
|
2534
|
+
).action(monitorsAction(handleMonitorsHealth));
|
|
2535
|
+
const batch = monitors.command("batch").description("Submit or inspect durable bulk monitor operations.");
|
|
2536
|
+
const batchSelectorOptions = (command) => command.option(
|
|
2537
|
+
"--target <target>",
|
|
2538
|
+
"One of: all, all-active, billing-suspended, needs-repair"
|
|
2539
|
+
).option("--fleet <fleet-id>", "Select one Fleet").option("--keys <keys>", "Up to 100 comma-separated monitor keys").option(
|
|
2540
|
+
"--dry-run",
|
|
2541
|
+
"Record the same bounded plan without provider changes"
|
|
2542
|
+
).option(
|
|
2543
|
+
"--idempotency-key <key>",
|
|
2544
|
+
"Stable retry key (generated when omitted)"
|
|
2545
|
+
);
|
|
2546
|
+
withJsonOption(
|
|
2547
|
+
batch.command("deploy").description(
|
|
2548
|
+
"Create a Monitor Job from canonical Batch JSON; legacy NDJSON remains available with --yes or --dry-run."
|
|
2549
|
+
).requiredOption(
|
|
2550
|
+
"--file <path>",
|
|
2551
|
+
'Batch JSON ({"version":1,"kind":"batch","monitors":[...]}), or legacy NDJSON with --yes/--dry-run'
|
|
2552
|
+
).option("--dry-run", "Preview a retained legacy NDJSON batch operation").option("--yes", "Confirm a retained legacy NDJSON batch operation").option(
|
|
2553
|
+
"--idempotency-key <key>",
|
|
2554
|
+
"Stable retry key (generated when omitted)"
|
|
2555
|
+
)
|
|
2556
|
+
).action(
|
|
2557
|
+
monitorsAction(async (options) => {
|
|
2558
|
+
if (options.dryRun === true || options.yes === true) {
|
|
2559
|
+
return await handleMonitorBatchDeploy(options);
|
|
2560
|
+
}
|
|
2561
|
+
const idempotencyKey = monitorJobIdempotencyKey(
|
|
2562
|
+
"batch-deploy",
|
|
2563
|
+
options.idempotencyKey
|
|
2564
|
+
);
|
|
2565
|
+
return await handleMonitorJobBatchDeploy(options, idempotencyKey);
|
|
2566
|
+
})
|
|
2567
|
+
);
|
|
2568
|
+
withJsonOption(
|
|
2569
|
+
batch.command("lint").description(
|
|
2570
|
+
"Validate and normalize Batch JSON without network or provider work."
|
|
2571
|
+
).requiredOption("--file <path>", "Batch JSON file, or - for stdin")
|
|
2572
|
+
).action(monitorsAction(handleMonitorBatchLint));
|
|
2573
|
+
withJsonOption(
|
|
2574
|
+
batchSelectorOptions(
|
|
2575
|
+
batch.command("audit").description("Durably audit a selection; defaults to --target all.")
|
|
2576
|
+
)
|
|
2577
|
+
).action(
|
|
2578
|
+
monitorsAction(
|
|
2579
|
+
(options) => handleMonitorBatch("audit", options)
|
|
2580
|
+
)
|
|
2581
|
+
);
|
|
2582
|
+
withJsonOption(
|
|
2583
|
+
batchSelectorOptions(
|
|
2584
|
+
batch.command("repair").description(
|
|
2585
|
+
"Durably re-audit and converge a selection through the existing lifecycle fences."
|
|
2586
|
+
)
|
|
2587
|
+
).option("--yes", "Confirm the asynchronous provider repair")
|
|
2588
|
+
).action(
|
|
2589
|
+
monitorsAction(
|
|
2590
|
+
(options) => handleMonitorBatch("repair", options)
|
|
2591
|
+
)
|
|
2592
|
+
);
|
|
2593
|
+
withJsonOption(
|
|
2594
|
+
batchSelectorOptions(
|
|
2595
|
+
batch.command("deactivate").description(
|
|
2596
|
+
"Durably deactivate a selection through provider batch deletes."
|
|
2597
|
+
)
|
|
2598
|
+
).option(
|
|
2599
|
+
"--reason <reason>",
|
|
2600
|
+
"Declared policy reason; required with --target and omitted for a customer-requested --keys teardown"
|
|
2601
|
+
).option("--yes", "Confirm the asynchronous provider deactivation")
|
|
2602
|
+
).action(
|
|
2603
|
+
monitorsAction(
|
|
2604
|
+
(options) => handleMonitorBatch("deactivate", options)
|
|
2605
|
+
)
|
|
2606
|
+
);
|
|
2607
|
+
withJsonOption(
|
|
2608
|
+
batchSelectorOptions(
|
|
2609
|
+
batch.command("reactivate").description("Durably reactivate a selection after a suspension.")
|
|
2610
|
+
).option("--yes", "Confirm provider reactivation and possible billing")
|
|
2611
|
+
).action(
|
|
2612
|
+
monitorsAction(
|
|
2613
|
+
(options) => handleMonitorBatch("reactivate", options)
|
|
2614
|
+
)
|
|
2615
|
+
);
|
|
2616
|
+
withJsonOption(
|
|
2617
|
+
batch.command("get <operation-id>").description("Read batch progress and one bounded evidence page.").option("--cursor <cursor>", "Read the next retained evidence page")
|
|
2618
|
+
).action(monitorsAction(handleMonitorBatchGet));
|
|
2619
|
+
const namedBatches = monitors.command("batches").description(
|
|
2620
|
+
"Manage stable named Batches whose omitted members are stopped."
|
|
2621
|
+
);
|
|
2622
|
+
withJsonOption(namedBatches);
|
|
2623
|
+
withJsonOption(
|
|
2624
|
+
namedBatches.command("sync <batch-id>").description(
|
|
2625
|
+
"Publish the complete desired member set and return one Monitor Job."
|
|
2626
|
+
).requiredOption(
|
|
2627
|
+
"--file <path>",
|
|
2628
|
+
"Canonical Batch JSON file, or - for stdin"
|
|
2629
|
+
).option(
|
|
2630
|
+
"--dry-run",
|
|
2631
|
+
"Validate and show the desired count without publishing"
|
|
2632
|
+
).option("--expected-generation <n>", "Reject a stale concurrent update").option("--idempotency-key <key>", "Stable retry key")
|
|
2633
|
+
).action(monitorsAction(handleNamedMonitorBatchSync));
|
|
2634
|
+
withJsonOption(
|
|
2635
|
+
namedBatches.command("start <batch-id>").description("Reapply the retained member set through one Monitor Job.").option(
|
|
2636
|
+
"--dry-run",
|
|
2637
|
+
"Show the retained-intent operation without publishing"
|
|
2638
|
+
).option("--idempotency-key <key>", "Stable retry key")
|
|
2639
|
+
).action(
|
|
2640
|
+
monitorsAction(
|
|
2641
|
+
(batchId, options) => handleNamedMonitorBatchLifecycle(batchId, "start", options)
|
|
2642
|
+
)
|
|
2643
|
+
);
|
|
2644
|
+
withJsonOption(
|
|
2645
|
+
namedBatches.command("stop <batch-id>").description(
|
|
2646
|
+
"Publish an empty desired set while retaining members for start."
|
|
2647
|
+
).option(
|
|
2648
|
+
"--dry-run",
|
|
2649
|
+
"Show the retained-intent operation without publishing"
|
|
2650
|
+
).option("--idempotency-key <key>", "Stable retry key")
|
|
2651
|
+
).action(
|
|
2652
|
+
monitorsAction(
|
|
2653
|
+
(batchId, options) => handleNamedMonitorBatchLifecycle(batchId, "stop", options)
|
|
2654
|
+
)
|
|
2655
|
+
);
|
|
2656
|
+
withJsonOption(
|
|
2657
|
+
namedBatches.command("get <batch-id>").description("Read named Batch intent and its latest Monitor Job.")
|
|
2658
|
+
).action(monitorsAction(handleNamedMonitorBatchGet));
|
|
2659
|
+
withJsonOption(
|
|
2660
|
+
monitors.command("status").description("Check whether you have access to Deepline monitors.").addHelpText(
|
|
2661
|
+
"after",
|
|
2662
|
+
`
|
|
2663
|
+
Notes:
|
|
2664
|
+
Read-only access check. Requires authentication but NOT monitor access, so it
|
|
2665
|
+
is safe to run first to confirm whether you can use the monitor commands. Exits
|
|
2666
|
+
0 when you have access and 1 when you do not, so agents can branch on it (this
|
|
2667
|
+
exit-1 denial contract predates the typed monitor exit codes and is kept).
|
|
2668
|
+
|
|
2669
|
+
Examples:
|
|
2670
|
+
deepline monitors status
|
|
2671
|
+
deepline monitors status --json
|
|
2672
|
+
`
|
|
2673
|
+
)
|
|
2674
|
+
).action(monitorsAction(handleMonitorsStatus));
|
|
2675
|
+
withJsonOption(
|
|
2676
|
+
monitors.command("available [tool-id]", { hidden: true }).alias("tools").description(
|
|
2677
|
+
"List the monitor types you can deploy, or describe one by tool id."
|
|
2678
|
+
).addHelpText(
|
|
2679
|
+
"after",
|
|
2680
|
+
`
|
|
2681
|
+
Notes:
|
|
2682
|
+
Read-only catalog of deployable monitor types. Pass a tool id positionally to
|
|
2683
|
+
describe one (payload schema + output streams). Filter the list with
|
|
2684
|
+
--provider, --search, or --limit. List entries include how many monitors you
|
|
2685
|
+
already have deployed on each tool ("deployed: N") when the server reports it.
|
|
2686
|
+
The list is compact by default (id + name + deployed count); pass --full to
|
|
2687
|
+
get the complete catalog (payload schemas + output streams for every tool).
|
|
2688
|
+
Describing a single tool by id always returns the full contract.
|
|
2689
|
+
|
|
2690
|
+
Examples:
|
|
2691
|
+
deepline monitors available
|
|
2692
|
+
deepline monitors available --full --json
|
|
2693
|
+
deepline monitors available --provider deepline_native --json
|
|
2694
|
+
deepline monitors available deepline_native.company_radar --json
|
|
2695
|
+
deepline monitors available --search jobs --json
|
|
2696
|
+
`
|
|
2697
|
+
).option("--provider <provider>", "Filter by provider").option(
|
|
2698
|
+
"--tool <tool>",
|
|
2699
|
+
"Describe a single monitor tool by id (same as the positional tool-id)"
|
|
2700
|
+
).option("--search <query>", "Search monitor tools by text").option("--limit <n>", "Limit the number of monitor tools returned").option(
|
|
2701
|
+
"--full",
|
|
2702
|
+
"Return the complete monitor catalog (payload schemas + output streams). The list is compact by default."
|
|
2703
|
+
).option("--compact", COMPACT_OPTION_DESCRIPTION)
|
|
2704
|
+
).action(monitorsAction(handleMonitorsAvailable));
|
|
2705
|
+
withJsonOption(
|
|
2706
|
+
monitors.command("list").description("List the monitors you have deployed.").addHelpText(
|
|
2707
|
+
"after",
|
|
2708
|
+
`
|
|
2709
|
+
Notes:
|
|
2710
|
+
Read-only. A monitor has one of three customer states: active,
|
|
2711
|
+
billing_suspended, or disabled. Provisioning, cleanup, retries, and failures
|
|
2712
|
+
are reported by Monitor Jobs; provider disagreement is reported separately by
|
|
2713
|
+
audit/health. The output echoes the canonical state filter that was applied.
|
|
2714
|
+
The legacy --status flag remains accepted for installed clients. The response reports
|
|
2715
|
+
total (the true registry count, not the page size), returned, has_more, and
|
|
2716
|
+
next_cursor. When has_more is true, page with --cursor <next_cursor> until it
|
|
2717
|
+
is false \u2014 a "no matching
|
|
2718
|
+
monitor" reuse conclusion off a truncated page deploys a duplicate paid feed.
|
|
2719
|
+
The default list stays cheap: it includes the current downstream Play binding
|
|
2720
|
+
metadata but does not resolve per-monitor delivery/run health. Pass
|
|
2721
|
+
--include-consumers with --limit 20 or fewer to include the current
|
|
2722
|
+
SQL-listener delivery/run health for each returned monitor.
|
|
2723
|
+
|
|
2724
|
+
Examples:
|
|
2725
|
+
deepline monitors list
|
|
2726
|
+
deepline monitors list --state billing_suspended --json
|
|
2727
|
+
deepline monitors list --state all --json
|
|
2728
|
+
deepline monitors list --status all --json
|
|
2729
|
+
deepline monitors list --status all --cursor <next_cursor> --json
|
|
2730
|
+
deepline monitors list --include-consumers --limit 20 --json
|
|
2731
|
+
`
|
|
2732
|
+
).option(
|
|
2733
|
+
"--state <state>",
|
|
2734
|
+
"Filter by customer state: active (default), billing_suspended, disabled, or all"
|
|
2735
|
+
).option(
|
|
2736
|
+
"--status <status>",
|
|
2737
|
+
"Deprecated compatibility filter; use --state (active, billing_suspended, disabled, or all)"
|
|
2738
|
+
).option("--limit <n>", "Limit the number of deployed monitors returned").option(
|
|
2739
|
+
"--cursor <cursor>",
|
|
2740
|
+
"Page past a truncated result using the next_cursor from a prior list response"
|
|
2741
|
+
).option(
|
|
2742
|
+
"--compact",
|
|
2743
|
+
"Deprecated no-op; complete records are always returned"
|
|
2744
|
+
).option(
|
|
2745
|
+
"--include-consumers",
|
|
2746
|
+
"Include current SQL-listener consumer health (requires --limit 20 or fewer)"
|
|
2747
|
+
)
|
|
2748
|
+
).action(monitorsAction(handleMonitorsList));
|
|
2749
|
+
withJsonOption(
|
|
2750
|
+
monitors.command("get <key>").description("Show a single deployed monitor by its public key.").addHelpText(
|
|
2751
|
+
"after",
|
|
2752
|
+
`
|
|
2753
|
+
Notes:
|
|
2754
|
+
Read-only. Returns the exact editable definition, selected Deepline pricing,
|
|
2755
|
+
billing state, output streams, dependent published plays, and monitor_spec.
|
|
2756
|
+
When monitor_spec.available is true, its fields describe the deployed type's
|
|
2757
|
+
editable fields, constraints, and provider-specific behavior. For TAMRadar,
|
|
2758
|
+
this includes the effective updates_since precision and engagement timing
|
|
2759
|
+
rules. Otherwise use the stored definition and check before proposing an
|
|
2760
|
+
update so scope and downstream effects are visible.
|
|
2761
|
+
|
|
2762
|
+
Examples:
|
|
2763
|
+
deepline monitors get my-monitor --json
|
|
2764
|
+
`
|
|
2765
|
+
)
|
|
2766
|
+
).action(monitorsAction(handleMonitorsGet));
|
|
2767
|
+
withJsonOption(
|
|
2768
|
+
monitors.command("test <key> <payload>").description(
|
|
2769
|
+
"Validate an explicit payload against a monitor\u2019s webhook contract."
|
|
2770
|
+
).addHelpText(
|
|
2771
|
+
"after",
|
|
2772
|
+
`
|
|
2773
|
+
Notes:
|
|
2774
|
+
<payload> must be an explicit JSON object. The command uses the deployed
|
|
2775
|
+
monitor\u2019s real binding and payload validation, but is a side-effect-free
|
|
2776
|
+
diagnostic: it does not persist rows, spend credits, dispatch Plays, or alter
|
|
2777
|
+
monitor state. It does not synthesize a provider event or accept an omitted
|
|
2778
|
+
payload.
|
|
2779
|
+
|
|
2780
|
+
Examples:
|
|
2781
|
+
deepline monitors test rb2b-website-visitors '{"LinkedIn URL":"https://www.linkedin.com/in/example","Website":"https://example.com"}' --json
|
|
2782
|
+
`
|
|
2783
|
+
)
|
|
2784
|
+
).action(monitorsAction(handleMonitorsTest));
|
|
2785
|
+
withJsonOption(
|
|
2786
|
+
monitors.command("validate <key>").description("Validate a deployed monitor\u2019s provider configuration.")
|
|
2787
|
+
).action(monitorsAction(handleMonitorsValidate));
|
|
2788
|
+
withJsonOption(
|
|
2789
|
+
monitors.command("check [definition]").description("Validate a monitor definition without deploying it.").addHelpText(
|
|
2790
|
+
"after",
|
|
2791
|
+
`
|
|
2792
|
+
Notes:
|
|
2793
|
+
Read-only validation. <definition> is a JSON object with key, tool, optional
|
|
2794
|
+
name, and payload. Pass it positionally, via --file <path>, or
|
|
2795
|
+
from stdin with --file -. Does not deploy or spend credits.
|
|
2796
|
+
Check validates the local definition only. It does not contact an existing
|
|
2797
|
+
upstream resource, prove that future events will arrive, or verify downstream
|
|
2798
|
+
Play delivery. The JSON response reports this validation scope explicitly.
|
|
2799
|
+
Provider-specific grammar, precedence, enum applicability, examples, and
|
|
2800
|
+
update semantics are returned by the live monitor contract. Inspect it with
|
|
2801
|
+
\`deepline tools get deepline_native.company_radar --json\`.
|
|
2802
|
+
Operational controls are Deepline-managed. Bind downstream automation in a
|
|
2803
|
+
Play with sqlListeners; monitor definitions never name Plays.
|
|
2804
|
+
|
|
2805
|
+
Examples:
|
|
2806
|
+
deepline monitors check '{"key":"job-openings","tool":"deepline_native.company_radar","payload":{"domain":"stripe.com","radar_type":"company_job_openings"}}'
|
|
2807
|
+
deepline monitors check --file monitor.json
|
|
2808
|
+
cat monitor.json | deepline monitors check --file -
|
|
2809
|
+
`
|
|
2810
|
+
).option(
|
|
2811
|
+
"-f, --file <path>",
|
|
2812
|
+
"Read the definition JSON from a file, or - for stdin"
|
|
2813
|
+
)
|
|
2814
|
+
).action(monitorsAction(handleMonitorsCheck));
|
|
2815
|
+
withJsonOption(
|
|
2816
|
+
monitors.command("deploy [definition]").description("Deploy a monitor from a definition.").addHelpText(
|
|
2817
|
+
"after",
|
|
2818
|
+
`
|
|
2819
|
+
Notes:
|
|
2820
|
+
Mutates workspace state and may spend Deepline credits. <definition> is a JSON
|
|
2821
|
+
object with key, tool, optional name, and payload. Pass it positionally, via
|
|
2822
|
+
--file <path>, or from stdin with --file -.
|
|
2823
|
+
--dry-run validates the definition and shows the plan (deploy cost in Deepline
|
|
2824
|
+
credits when the server reports it, plus any existing monitors that may
|
|
2825
|
+
already cover this scope) WITHOUT deploying. Exits 0 when valid, 7 when not.
|
|
2826
|
+
Deploy is a full desired definition for its key: omitting a previously stored
|
|
2827
|
+
field removes it and can replace the upstream resource. Use \`monitors update\`
|
|
2828
|
+
for a patch-style change.
|
|
2829
|
+
Repeating the exact saved definition resumes an incomplete deploy cleanup:
|
|
2830
|
+
Deepline keeps the replacement, removes only the stored previous binding after
|
|
2831
|
+
provider confirmation, and never creates or charges another monitor. There is
|
|
2832
|
+
no separate repair command or retry queue: rerun the same deploy/update.
|
|
2833
|
+
Scheduled reconciliation can also finish that exact saved cleanup; it never
|
|
2834
|
+
creates another monitor.
|
|
2835
|
+
For TamRadar, deliberately changing payload.updates_since is an explicit
|
|
2836
|
+
historical replay request, including when redeploying a disabled monitor.
|
|
2837
|
+
Deepline preserves that selected boundary; filter-only deploys and ordinary
|
|
2838
|
+
reactivation start from a fresh future-only source boundary instead.
|
|
2839
|
+
Operational controls are Deepline-managed. A monitor writes rows; downstream
|
|
2840
|
+
automation belongs in a Play with sqlListeners.
|
|
2841
|
+
|
|
2842
|
+
Examples:
|
|
2843
|
+
deepline monitors deploy '{"key":"job-openings","tool":"deepline_native.company_radar","payload":{"domain":"stripe.com","radar_type":"company_job_openings"}}'
|
|
2844
|
+
deepline monitors deploy --dry-run --file monitor.json
|
|
2845
|
+
deepline monitors deploy --file monitor.json
|
|
2846
|
+
`
|
|
2847
|
+
).option(
|
|
2848
|
+
"-f, --file <path>",
|
|
2849
|
+
"Read the definition JSON from a file, or - for stdin"
|
|
2850
|
+
).option(
|
|
2851
|
+
"--dry-run",
|
|
2852
|
+
"Validate and show the deploy plan (cost, reuse candidates) without deploying"
|
|
2853
|
+
)
|
|
2854
|
+
).action(monitorsAction(handleMonitorsDeploy));
|
|
2855
|
+
withJsonOption(
|
|
2856
|
+
monitors.command("sync [definition]").description(
|
|
2857
|
+
"Publish one complete desired monitor definition and return a Monitor Job."
|
|
2858
|
+
).option(
|
|
2859
|
+
"-f, --file <path>",
|
|
2860
|
+
"Read definition JSON from a file, or - for stdin"
|
|
2861
|
+
).option(
|
|
2862
|
+
"--dry-run",
|
|
2863
|
+
"Validate and show the deploy plan without publishing"
|
|
2864
|
+
).option("--idempotency-key <key>", "Stable retry key")
|
|
2865
|
+
).action(monitorsAction(handleMonitorsSync));
|
|
2866
|
+
withJsonOption(
|
|
2867
|
+
monitors.command("update <key> [patch]").description("Update a deployed monitor by its public key.").addHelpText(
|
|
2868
|
+
"after",
|
|
2869
|
+
`
|
|
2870
|
+
Notes:
|
|
2871
|
+
Mutates workspace state. <patch> is a JSON object of fields to update. Pass it
|
|
2872
|
+
positionally, via --file <path>, or from stdin with --file -.
|
|
2873
|
+
|
|
2874
|
+
When <patch> includes payload, its top-level fields are merged with the stored
|
|
2875
|
+
payload before validation. This lets you change one filter without resending
|
|
2876
|
+
domain or radar_type; nested values and arrays replace atomically.
|
|
2877
|
+
For TamRadar, changing payload.updates_since on an initial or active definition
|
|
2878
|
+
is an explicit historical replay request. Deepline passes that requested boundary upstream, but previously
|
|
2879
|
+
delivered canonical findings are deduplicated before dispatch and billing; only
|
|
2880
|
+
previously unseen historical findings are chargeable. Filter-only updates,
|
|
2881
|
+
repair, and reactivation instead use a fresh future-only source boundary.
|
|
2882
|
+
|
|
2883
|
+
Examples:
|
|
2884
|
+
deepline monitors update my-monitor '{"name":"Interested replies"}' --json
|
|
2885
|
+
deepline monitors update job-openings '{"payload":{"job_titles":"CEO OR CFO"}}' --json
|
|
2886
|
+
deepline monitors update my-monitor --file patch.json
|
|
2887
|
+
`
|
|
2888
|
+
).option(
|
|
2889
|
+
"-f, --file <path>",
|
|
2890
|
+
"Read the patch JSON from a file, or - for stdin"
|
|
2891
|
+
)
|
|
2892
|
+
).action(monitorsAction(handleMonitorsUpdate));
|
|
2893
|
+
withJsonOption(
|
|
2894
|
+
monitors.command("delete <key>").description("Delete a deployed monitor by its public key.").addHelpText(
|
|
2895
|
+
"after",
|
|
2896
|
+
`
|
|
2897
|
+
Notes:
|
|
2898
|
+
DESTRUCTIVE. Deprovisions the upstream provider resource and removes its
|
|
2899
|
+
Deepline-managed monitor. Retries are safe: a delete that already reached its requested terminal state
|
|
2900
|
+
returns already_deleted without making another upstream call.
|
|
2901
|
+
--dry-run shows the delete plan without deleting anything.
|
|
2902
|
+
Interactive terminals get a y/N confirmation; non-interactive runs (agents,
|
|
2903
|
+
scripts, pipes) must pass --yes.
|
|
2904
|
+
|
|
2905
|
+
Examples:
|
|
2906
|
+
deepline monitors delete my-monitor --dry-run
|
|
2907
|
+
deepline monitors delete my-monitor --yes --json
|
|
2908
|
+
`
|
|
2909
|
+
).option("--dry-run", "Show the delete plan without deleting anything").option(
|
|
2910
|
+
"--yes",
|
|
2911
|
+
"Skip the confirmation prompt (required non-interactively)"
|
|
2912
|
+
)
|
|
2913
|
+
).action(monitorsAction(handleMonitorsDelete));
|
|
2914
|
+
withJsonOption(
|
|
2915
|
+
monitors.command("reactivate <key>").description("Reactivate a previously disabled deployed monitor.").addHelpText(
|
|
2916
|
+
"after",
|
|
2917
|
+
`
|
|
2918
|
+
Notes:
|
|
2919
|
+
Mutates workspace state and may spend Deepline credits.
|
|
2920
|
+
--dry-run shows the reactivation cost (in Deepline credits) without changing
|
|
2921
|
+
anything.
|
|
2922
|
+
Deepline Native radars create a replacement upstream radar from the saved
|
|
2923
|
+
filters. Reactivation always omits updates_since and starts at provider
|
|
2924
|
+
creation; a disabled monitor cannot stage an updates_since change. New Hire
|
|
2925
|
+
and Promotion source dates use calendar-month precision upstream, so
|
|
2926
|
+
current-month updates_since values are rejected for those radar types.
|
|
2927
|
+
|
|
2928
|
+
Examples:
|
|
2929
|
+
deepline monitors reactivate my-monitor --dry-run
|
|
2930
|
+
deepline monitors reactivate my-monitor --json
|
|
2931
|
+
`
|
|
2932
|
+
).option("--dry-run", "Show the reactivation cost without reactivating")
|
|
2933
|
+
).action(monitorsAction(handleMonitorsReactivate));
|
|
2934
|
+
withJsonOption(
|
|
2935
|
+
monitors.command("start <key>").description("Apply the retained definition for a stopped monitor.").option("--dry-run", "Show the start plan without changing anything").option("--idempotency-key <key>", "Stable retry key")
|
|
2936
|
+
).action(
|
|
2937
|
+
monitorsAction(
|
|
2938
|
+
(key, options) => handleScalarMonitorLifecycle("start", key, options)
|
|
2939
|
+
)
|
|
2940
|
+
);
|
|
2941
|
+
withJsonOption(
|
|
2942
|
+
monitors.command("stop <key>").description(
|
|
2943
|
+
"Stop a monitor upstream while retaining its definition for `start`."
|
|
2944
|
+
).option("--dry-run", "Show the stop plan without changing anything").option("--idempotency-key <key>", "Stable retry key")
|
|
2945
|
+
).action(
|
|
2946
|
+
monitorsAction(
|
|
2947
|
+
(key, options) => handleScalarMonitorLifecycle("stop", key, options)
|
|
2948
|
+
)
|
|
2949
|
+
);
|
|
2950
|
+
const jobs = monitors.command("jobs").description("Read, wait for, inspect, or cancel Monitor Jobs.");
|
|
2951
|
+
withJsonOption(jobs);
|
|
2952
|
+
withJsonOption(
|
|
2953
|
+
jobs.command("get <job-id>").description("Read the durable Monitor Job state.")
|
|
2954
|
+
).action(monitorsAction(handleMonitorJobGet));
|
|
2955
|
+
withJsonOption(
|
|
2956
|
+
jobs.command("wait <job-id>").description(
|
|
2957
|
+
"Wait for real provider reconciliation, not request acceptance."
|
|
2958
|
+
).option("--timeout <duration>", "How long to wait (default 10m)")
|
|
2959
|
+
).action(monitorsAction(handleMonitorJobWait));
|
|
2960
|
+
withJsonOption(
|
|
2961
|
+
jobs.command("logs <job-id>").description("Read bounded operator logs for one Monitor Job.").option("--limit <n>", "Maximum log entries (1..200)")
|
|
2962
|
+
).action(monitorsAction(handleMonitorJobLogs));
|
|
2963
|
+
withJsonOption(
|
|
2964
|
+
jobs.command("cancel <job-id>").description("Stop unscheduled work and reconcile the partial result.")
|
|
2965
|
+
).action(monitorsAction(handleMonitorJobCancel));
|
|
2966
|
+
const fleets = monitors.command("fleets").description("Manage table-backed Fleets.").addHelpText(
|
|
2967
|
+
"after",
|
|
2968
|
+
`
|
|
2969
|
+
Notes:
|
|
2970
|
+
A Monitor Fleet maps eligible rows in one Customer DB table to ordinary
|
|
2971
|
+
monitors. Fleet definitions are canonical JSON, and \`deploy\` applies one;
|
|
2972
|
+
\`sync\` remains its exact compatibility alias. Applying the same definition
|
|
2973
|
+
twice is the same operation,
|
|
2974
|
+
so the server answers \`replayed: true\` instead of building a second fleet.
|
|
2975
|
+
The fleet's status is computed by Deepline; this CLI reports it and never
|
|
2976
|
+
recomputes a verdict from the counts.
|
|
2977
|
+
|
|
2978
|
+
Exit codes:
|
|
2979
|
+
0 converged or done; 2 usage or local validation; 3 auth; 4 not found;
|
|
2980
|
+
5 server failure (retry the same command); 6 not at the desired state
|
|
2981
|
+
(degraded, or --wait/--check stopped while healthy); 7 refused (change the
|
|
2982
|
+
request).
|
|
2983
|
+
|
|
2984
|
+
Lifecycle:
|
|
2985
|
+
${MONITOR_FLEET_LIFECYCLE.map(
|
|
2986
|
+
(entry) => ` ${entry.step}. ${entry.command}`
|
|
2987
|
+
).join("\n")}
|
|
2988
|
+
${MONITOR_FLEET_HELP_EPILOG}`
|
|
2989
|
+
).action(
|
|
2990
|
+
monitorsAction(async (options) => {
|
|
2991
|
+
handleMonitorFleetsOverview(options);
|
|
2992
|
+
})
|
|
2993
|
+
);
|
|
2994
|
+
withJsonOption(fleets);
|
|
2995
|
+
withJsonOption(
|
|
2996
|
+
fleets.command("deploy [fleet-id]").description(
|
|
2997
|
+
"Apply a table-backed Fleet definition; sync remains an exact alias."
|
|
2998
|
+
).option("-f, --file <path>", "Fleet JSON file, or - for stdin").option("--dry-run", "Show the table-backed convergence plan").option("--wait", "Wait for the current Fleet generation to converge").option("--timeout <duration>", "How long --wait may poll (default 10m)").option(
|
|
2999
|
+
"--expected-generation <generation>",
|
|
3000
|
+
"Optimistic generation fence"
|
|
3001
|
+
).option("--idempotency-key <key>", "Stable retry key")
|
|
3002
|
+
).action(
|
|
3003
|
+
monitorsAction(
|
|
3004
|
+
async (fleetId, options) => {
|
|
3005
|
+
const idempotencyKey = fleetIdempotencyKey(
|
|
3006
|
+
"deploy",
|
|
3007
|
+
options.idempotencyKey
|
|
3008
|
+
);
|
|
3009
|
+
await handleMonitorFleetsSync(fleetId, options, idempotencyKey);
|
|
3010
|
+
}
|
|
3011
|
+
)
|
|
3012
|
+
);
|
|
3013
|
+
withJsonOption(
|
|
3014
|
+
fleets.command("lint").description(
|
|
3015
|
+
"Validate and normalize Fleet JSON without network or provider work."
|
|
3016
|
+
).requiredOption("--file <path>", "Fleet JSON file, or - for stdin")
|
|
3017
|
+
).action(monitorsAction(handleMonitorFleetLint));
|
|
3018
|
+
withJsonOption(
|
|
3019
|
+
fleets.command("init <fleet-id>").description("Write an opinionated Fleet definition JSON file.").addHelpText(
|
|
3020
|
+
"after",
|
|
3021
|
+
`
|
|
3022
|
+
Creates a ready-to-review JSON definition. Fleets always sync ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.cadence} and retain
|
|
3023
|
+
a missing member for ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.removalGrace} before it is ${MONITOR_FLEET_DOCUMENTATION.fixedPolicy.onRemoved}d; those
|
|
3024
|
+
policies are not authored in the file. ${MONITOR_FLEET_DOCUMENTATION.stickyMembership} The payload remains the actual monitor payload for the chosen tool, so use
|
|
3025
|
+
\`deepline tools list --categories monitors\` to discover a tool first.
|
|
3026
|
+
|
|
3027
|
+
Example:
|
|
3028
|
+
deepline monitors fleets init account-job-openings \\
|
|
3029
|
+
--source public.accounts --key account_id --key-type uuid \\
|
|
3030
|
+
--tool deepline_native.company_radar \\
|
|
3031
|
+
--payload '{"domain":"{{domain}}","radar_type":"company_job_openings"}' \\
|
|
3032
|
+
--order-by intent_score:desc \\
|
|
3033
|
+
--out fleet.json
|
|
3034
|
+
`
|
|
3035
|
+
).requiredOption("--source <schema.table>", "Customer DB source table").requiredOption("--key <column>", "Unique source-row key column").option("--key-type <type>", "text, uuid, int4, or int8 (default text)").requiredOption("--tool <tool>", "Monitor tool to create per source row").requiredOption(
|
|
3036
|
+
"--payload <json>",
|
|
3037
|
+
"Monitor payload JSON for --tool; use Handlebars fields such as {{domain}} for row values"
|
|
3038
|
+
).option(
|
|
3039
|
+
"--where <json>",
|
|
3040
|
+
"Optional source-row equality filter JSON; not arbitrary SQL"
|
|
3041
|
+
).option(
|
|
3042
|
+
"--order-by <column:direction>",
|
|
3043
|
+
"Rank candidates; repeatable, with the source key appended for stability",
|
|
3044
|
+
(value, previous = []) => [...previous, value],
|
|
3045
|
+
[]
|
|
3046
|
+
).option(
|
|
3047
|
+
"--limit <n>",
|
|
3048
|
+
"Maximum active members; ranking changes do not churn sticky members (default 1,000)"
|
|
3049
|
+
).requiredOption("--out <path>", "Write the definition JSON to this path")
|
|
3050
|
+
).action(monitorsAction(handleMonitorFleetsInit));
|
|
3051
|
+
withJsonOption(
|
|
3052
|
+
fleets.command("sync [fleet-id]").description(
|
|
3053
|
+
"Apply a Fleet definition, or re-plan an existing fleet from its stored one."
|
|
3054
|
+
).addHelpText(
|
|
3055
|
+
"after",
|
|
3056
|
+
`
|
|
3057
|
+
Notes:
|
|
3058
|
+
--file (or an inline definition, or --file - for stdin) is compiled locally
|
|
3059
|
+
BEFORE any network call: a definition that fails the contract exits 2 and
|
|
3060
|
+
never becomes a request. A bare <fleet-id> re-plans the stored definition.
|
|
3061
|
+
The plan always shows blocked members; they are never hidden.
|
|
3062
|
+
--wait polls until Deepline reports a terminal status: converged exits 0,
|
|
3063
|
+
degraded exits 6, and a timeout exits 6 without implying anything broke.
|
|
3064
|
+
Progress goes to stderr, so stdout stays exactly one JSON object.
|
|
3065
|
+
|
|
3066
|
+
Examples:
|
|
3067
|
+
deepline monitors fleets sync --file fleet.json --dry-run
|
|
3068
|
+
deepline monitors fleets sync --file fleet.json --wait --timeout 20m
|
|
3069
|
+
deepline monitors fleets sync account-job-openings --wait --json
|
|
3070
|
+
cat fleet.json | deepline monitors fleets sync --file - --dry-run --json
|
|
3071
|
+
`
|
|
3072
|
+
).option(
|
|
3073
|
+
"-f, --file <path>",
|
|
3074
|
+
"Apply definition JSON from a file, or - for stdin"
|
|
3075
|
+
).option(
|
|
3076
|
+
"--dry-run",
|
|
3077
|
+
"Show the plan and the credits due without changing state"
|
|
3078
|
+
).option("--wait", "Poll until the fleet reaches a terminal status").option(
|
|
3079
|
+
"--timeout <duration>",
|
|
3080
|
+
"How long --wait may poll: 500ms, 90s, 10m, 1h (default 10m)"
|
|
3081
|
+
).option(
|
|
3082
|
+
"--expected-generation <generation>",
|
|
3083
|
+
"Refuse the write unless the fleet is at this generation"
|
|
3084
|
+
).option(
|
|
3085
|
+
"--idempotency-key <key>",
|
|
3086
|
+
"Stable retry key (generated and returned when omitted)"
|
|
3087
|
+
)
|
|
3088
|
+
).action(async (target, options) => {
|
|
3089
|
+
let retryCommand = fleetSyncCommandLine(target, options);
|
|
3090
|
+
try {
|
|
3091
|
+
const idempotencyKey = fleetIdempotencyKey(
|
|
3092
|
+
"sync",
|
|
3093
|
+
options.idempotencyKey
|
|
3094
|
+
);
|
|
3095
|
+
retryCommand = fleetSyncCommandLine(target, {
|
|
3096
|
+
...options,
|
|
3097
|
+
idempotencyKey
|
|
3098
|
+
});
|
|
3099
|
+
await handleMonitorFleetsSync(target, options, idempotencyKey);
|
|
3100
|
+
} catch (error) {
|
|
3101
|
+
reportMonitorsFailure(error, { retryCommand });
|
|
3102
|
+
}
|
|
3103
|
+
});
|
|
3104
|
+
withJsonOption(
|
|
3105
|
+
fleets.command("get [fleet-id]").description(
|
|
3106
|
+
"Read one fleet, or list every fleet when the id is omitted."
|
|
3107
|
+
).addHelpText(
|
|
3108
|
+
"after",
|
|
3109
|
+
`
|
|
3110
|
+
Notes:
|
|
3111
|
+
Status is the server's verdict. --check turns it into an exit code so a
|
|
3112
|
+
script can branch without parsing output: 0 converged, 6 anything else.
|
|
3113
|
+
--drift lists the members that have not settled and why. The detail also
|
|
3114
|
+
shows the current Plays that observe this Fleet's output; those bindings are
|
|
3115
|
+
read-only here and are defined by each Play's sqlListeners.
|
|
3116
|
+
|
|
3117
|
+
Examples:
|
|
3118
|
+
deepline monitors fleets get --json
|
|
3119
|
+
deepline monitors fleets get account-job-openings --json
|
|
3120
|
+
deepline monitors fleets get account-job-openings --drift --limit 50 --json
|
|
3121
|
+
deepline monitors fleets get account-job-openings --check
|
|
3122
|
+
`
|
|
3123
|
+
).option("--drift", "Include the members that have not settled, and why").option("--limit <n>", "Maximum drift rows to return").option(
|
|
3124
|
+
"--check",
|
|
3125
|
+
"Exit 0 when converged and 6 otherwise, for scripts and agents"
|
|
3126
|
+
)
|
|
3127
|
+
).action(monitorsAction(handleMonitorFleetsGet));
|
|
3128
|
+
withJsonOption(
|
|
3129
|
+
fleets.command("deactivate <fleet-id>").alias("stop").description("Deactivate a fleet and the monitors it owns.").addHelpText(
|
|
3130
|
+
"after",
|
|
3131
|
+
`
|
|
3132
|
+
Notes:
|
|
3133
|
+
Destructive. --dry-run shows the blast radius without changing anything.
|
|
3134
|
+
An interactive terminal is shown that blast radius and asked to confirm;
|
|
3135
|
+
a non-interactive run must pass --yes. Re-running after a failure is safe.
|
|
3136
|
+
|
|
3137
|
+
Examples:
|
|
3138
|
+
deepline monitors fleets deactivate account-job-openings --dry-run
|
|
3139
|
+
deepline monitors fleets deactivate account-job-openings --yes --wait --json
|
|
3140
|
+
`
|
|
3141
|
+
).option("--dry-run", "Show the blast radius without deactivating").option("--wait", "Poll until the fleet reports deactivated").option(
|
|
3142
|
+
"--timeout <duration>",
|
|
3143
|
+
"How long --wait may poll: 500ms, 90s, 10m, 1h (default 10m)"
|
|
3144
|
+
).option(
|
|
3145
|
+
"--yes",
|
|
3146
|
+
"Skip the confirmation prompt (required non-interactively)"
|
|
3147
|
+
).option(
|
|
3148
|
+
"--idempotency-key <key>",
|
|
3149
|
+
"Stable retry key (generated and returned when omitted)"
|
|
3150
|
+
)
|
|
3151
|
+
).action(async (fleetId, options) => {
|
|
3152
|
+
let retryCommand = fleetDeactivateCommandLine(fleetId, options);
|
|
3153
|
+
try {
|
|
3154
|
+
const idempotencyKey = fleetIdempotencyKey(
|
|
3155
|
+
"deactivate",
|
|
3156
|
+
options.idempotencyKey
|
|
3157
|
+
);
|
|
3158
|
+
retryCommand = fleetDeactivateCommandLine(fleetId, {
|
|
3159
|
+
...options,
|
|
3160
|
+
idempotencyKey
|
|
3161
|
+
});
|
|
3162
|
+
await handleMonitorFleetsDeactivate(fleetId, options, idempotencyKey);
|
|
3163
|
+
} catch (error) {
|
|
3164
|
+
reportMonitorsFailure(error, { retryCommand });
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
withJsonOption(
|
|
3168
|
+
fleets.command("reactivate <fleet-id>").alias("start").description(
|
|
3169
|
+
"Explicitly recreate a fleet after a credit-triggered suspension."
|
|
3170
|
+
).addHelpText(
|
|
3171
|
+
"after",
|
|
3172
|
+
`
|
|
3173
|
+
Notes:
|
|
3174
|
+
TAMRadar cannot reactivate a deleted radar. This command starts normal,
|
|
3175
|
+
fenced provider creates from the fleet's retained desired definitions and
|
|
3176
|
+
may restart lifecycle billing. A credit top-up alone never does this.
|
|
3177
|
+
|
|
3178
|
+
Example:
|
|
3179
|
+
deepline monitors fleets reactivate account-job-openings --yes --wait --json
|
|
3180
|
+
`
|
|
3181
|
+
).option("--wait", "Poll until the fleet reports converged").option(
|
|
3182
|
+
"--timeout <duration>",
|
|
3183
|
+
"How long --wait may poll: 500ms, 90s, 10m, 1h (default 10m)"
|
|
3184
|
+
).option("--yes", "Confirm recreating suspended provider monitors").option("--idempotency-key <key>", "Stable retry key")
|
|
3185
|
+
).action(async (fleetId, options) => {
|
|
3186
|
+
let retryCommand = fleetReactivateCommandLine(fleetId, options);
|
|
3187
|
+
try {
|
|
3188
|
+
const idempotencyKey = fleetIdempotencyKey(
|
|
3189
|
+
"reactivate",
|
|
3190
|
+
options.idempotencyKey
|
|
3191
|
+
);
|
|
3192
|
+
retryCommand = fleetReactivateCommandLine(fleetId, {
|
|
3193
|
+
...options,
|
|
3194
|
+
idempotencyKey
|
|
3195
|
+
});
|
|
3196
|
+
await handleMonitorFleetsReactivate(fleetId, options, idempotencyKey);
|
|
3197
|
+
} catch (error) {
|
|
3198
|
+
reportMonitorsFailure(error, { retryCommand });
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3201
|
+
const deployed = withJsonOption(
|
|
3202
|
+
monitors.command("deployed", { hidden: true }).description("Alias of `monitors list` (plus get/update/delete aliases).").option(
|
|
3203
|
+
"--state <state>",
|
|
3204
|
+
"Filter by customer state: active (default), billing_suspended, disabled, or all"
|
|
3205
|
+
).option(
|
|
3206
|
+
"--status <status>",
|
|
3207
|
+
"Deprecated compatibility filter; use --state (active, billing_suspended, disabled, or all)"
|
|
3208
|
+
).option("--limit <n>", "Limit the number of deployed monitors returned").option(
|
|
3209
|
+
"--cursor <cursor>",
|
|
3210
|
+
"Page past a truncated result using the next_cursor from a prior list response"
|
|
3211
|
+
).option(
|
|
3212
|
+
"--compact",
|
|
3213
|
+
"Deprecated no-op; complete records are always returned"
|
|
3214
|
+
).option(
|
|
3215
|
+
"--include-consumers",
|
|
3216
|
+
"Include current SQL-listener consumer health (requires --limit 20 or fewer)"
|
|
3217
|
+
)
|
|
3218
|
+
).action(monitorsAction(handleMonitorsList));
|
|
3219
|
+
withJsonOption(
|
|
3220
|
+
deployed.command("get <key>").description("Alias of `monitors get <key>`.")
|
|
3221
|
+
).action(monitorsAction(handleMonitorsGet));
|
|
3222
|
+
withJsonOption(
|
|
3223
|
+
deployed.command("update <key> [patch]").description("Alias of `monitors update <key>`.").option(
|
|
3224
|
+
"-f, --file <path>",
|
|
3225
|
+
"Read the patch JSON from a file, or - for stdin"
|
|
3226
|
+
)
|
|
3227
|
+
).action(monitorsAction(handleMonitorsUpdate));
|
|
3228
|
+
withJsonOption(
|
|
3229
|
+
deployed.command("delete <key>").description("Alias of `monitors delete <key>`.").option("--dry-run", "Show the delete plan without deleting anything").option(
|
|
3230
|
+
"--yes",
|
|
3231
|
+
"Skip the confirmation prompt (required non-interactively)"
|
|
3232
|
+
)
|
|
3233
|
+
).action(monitorsAction(handleMonitorsDelete));
|
|
3234
|
+
}
|
|
3235
|
+
export {
|
|
3236
|
+
MonitorDryRunUnsupportedError,
|
|
3237
|
+
MonitorFleetStateError,
|
|
3238
|
+
MonitorsUsageError,
|
|
3239
|
+
assertMonitorDryRunAcknowledged,
|
|
3240
|
+
monitorDeleteRequiresYesError,
|
|
3241
|
+
monitorsErrorExitCode,
|
|
3242
|
+
registerMonitorsCommands,
|
|
3243
|
+
renderAvailableToolsText,
|
|
3244
|
+
renderDeployedListText,
|
|
3245
|
+
renderMonitorDeletePlan,
|
|
3246
|
+
renderMonitorDeployCompletion,
|
|
3247
|
+
renderMonitorDeployPlan,
|
|
3248
|
+
renderMonitorGet,
|
|
3249
|
+
renderMonitorReactivatePlan,
|
|
3250
|
+
resolveMonitorJsonBody
|
|
3251
|
+
};
|