@indigoai-us/hq-cli 5.128.0 → 5.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/command-catalog.generated.d.ts +169 -0
- package/dist/command-catalog.generated.js +221 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/report.d.ts +16 -0
- package/dist/commands/report.js +310 -0
- package/dist/lib/report-publish.d.ts +173 -0
- package/dist/lib/report-publish.js +381 -0
- package/dist/lib/report-run.d.ts +61 -0
- package/dist/lib/report-run.js +123 -0
- package/dist/lib/report-templates.d.ts +10 -0
- package/dist/lib/report-templates.js +24 -0
- package/package.json +2 -1
- package/templates/reports/blank.md +3 -0
- package/templates/reports/daily-digest.md +11 -0
- package/templates/runbooks/update-report.md +21 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { ensureCognitoToken, } from "../utils/cognito-session.js";
|
|
2
|
+
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
3
|
+
import { listReportTemplates } from "../lib/report-templates.js";
|
|
4
|
+
import { adoptReport, buildReportTarball, conventionReportDir, deployOrgContext, ReportPublishError, plainFailureReason, publishReport, } from "../lib/report-publish.js";
|
|
5
|
+
const DEPLOY_API_URL = process.env.HQ_DEPLOY_API_URL ?? "https://api.indigo-hq.com";
|
|
6
|
+
function reportPath(channelId, reportId) {
|
|
7
|
+
return `/v1/notify/channels/${encodeURIComponent(channelId)}/reports/${encodeURIComponent(reportId)}`;
|
|
8
|
+
}
|
|
9
|
+
function errorMessage(error) {
|
|
10
|
+
if (error instanceof ReportPublishError)
|
|
11
|
+
return plainFailureReason(error);
|
|
12
|
+
return "Report command failed. Check the report context and try again.";
|
|
13
|
+
}
|
|
14
|
+
function print(value, json) {
|
|
15
|
+
if (json)
|
|
16
|
+
console.log(JSON.stringify(value, null, 2));
|
|
17
|
+
else
|
|
18
|
+
console.log(typeof value === "string" ? value : JSON.stringify(value, null, 2));
|
|
19
|
+
}
|
|
20
|
+
/** Explicit context has priority; discovery is the authenticated fallback for normal CLI use. */
|
|
21
|
+
export async function resolveReport(token, reportId, requestedChannelId) {
|
|
22
|
+
const explicit = requestedChannelId ?? process.env.HQ_REPORT_CHANNEL_ID;
|
|
23
|
+
if (explicit) {
|
|
24
|
+
const res = await vaultApiFetch({ token, path: reportPath(explicit, reportId) });
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw new ReportPublishError("Report was not found in the supplied channel context.");
|
|
27
|
+
const body = await res.json();
|
|
28
|
+
if (!body.report)
|
|
29
|
+
throw new ReportPublishError("Report response was incomplete.");
|
|
30
|
+
return body.report;
|
|
31
|
+
}
|
|
32
|
+
// The report API is intentionally channel-scoped. Discover only among the
|
|
33
|
+
// caller's existing channels rather than inventing an unscoped report route.
|
|
34
|
+
const channels = await vaultApiFetch({ token, path: "/v1/notify/channels" });
|
|
35
|
+
if (!channels.ok)
|
|
36
|
+
throw new ReportPublishError("Could not resolve the report channel.");
|
|
37
|
+
const listed = await channels.json();
|
|
38
|
+
const matches = [];
|
|
39
|
+
for (const row of listed.channels ?? []) {
|
|
40
|
+
if (!row.channelId)
|
|
41
|
+
continue;
|
|
42
|
+
const res = await vaultApiFetch({ token, path: reportPath(row.channelId, reportId) });
|
|
43
|
+
if (res.status === 404)
|
|
44
|
+
continue;
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
continue;
|
|
47
|
+
const body = await res.json();
|
|
48
|
+
if (body.report)
|
|
49
|
+
matches.push(body.report);
|
|
50
|
+
}
|
|
51
|
+
if (matches.length === 1)
|
|
52
|
+
return matches[0];
|
|
53
|
+
if (matches.length > 1)
|
|
54
|
+
throw new ReportPublishError("Report ID is ambiguous; pass --channel or set HQ_REPORT_CHANNEL_ID.");
|
|
55
|
+
throw new ReportPublishError("Report not found. Pass --channel or set HQ_REPORT_CHANNEL_ID.");
|
|
56
|
+
}
|
|
57
|
+
export async function patchReport(token, report, patch) {
|
|
58
|
+
const res = await vaultApiFetch({ token, path: reportPath(report.channelId, report.reportId), method: "PATCH", body: patch });
|
|
59
|
+
if (res.status === 409) {
|
|
60
|
+
const body = await res.json().catch(() => ({}));
|
|
61
|
+
if (body.error === "already-running")
|
|
62
|
+
throw new ReportPublishError("already running", "already-running");
|
|
63
|
+
throw new ReportPublishError("Report changed while it was being updated.");
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok)
|
|
66
|
+
throw new ReportPublishError("Could not update the report record.");
|
|
67
|
+
const body = await res.json();
|
|
68
|
+
if (!body.report)
|
|
69
|
+
throw new ReportPublishError("Report update response was incomplete.");
|
|
70
|
+
return body.report;
|
|
71
|
+
}
|
|
72
|
+
function deployHeaders(token, json = true, extra = {}) {
|
|
73
|
+
return {
|
|
74
|
+
Authorization: `Bearer ${token}`,
|
|
75
|
+
Accept: "application/json",
|
|
76
|
+
...(json ? { "Content-Type": "application/json" } : {}),
|
|
77
|
+
...extra,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function deployJson(token, route, method, body, extraHeaders = {}) {
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await fetch(`${DEPLOY_API_URL.replace(/\/+$/, "")}${route}`, {
|
|
84
|
+
method,
|
|
85
|
+
headers: deployHeaders(token, body !== undefined, extraHeaders),
|
|
86
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new ReportPublishError("Could not reach hq-deploy.");
|
|
91
|
+
}
|
|
92
|
+
if (!res.ok)
|
|
93
|
+
throw new ReportPublishError("hq-deploy rejected the report deployment.");
|
|
94
|
+
return await res.json();
|
|
95
|
+
}
|
|
96
|
+
export async function resolveCompanySlug(token, companyUid) {
|
|
97
|
+
const res = await vaultApiFetch({ token, path: `/entity/${encodeURIComponent(companyUid)}` });
|
|
98
|
+
if (!res.ok)
|
|
99
|
+
throw new ReportPublishError("Could not resolve the report's company for deploy.");
|
|
100
|
+
const body = await res.json();
|
|
101
|
+
const slug = body.entity?.slug;
|
|
102
|
+
if (!slug)
|
|
103
|
+
throw new ReportPublishError("Could not resolve the report's company for deploy.");
|
|
104
|
+
return slug;
|
|
105
|
+
}
|
|
106
|
+
async function orgFor(token, report) {
|
|
107
|
+
const slug = report.companyUid ? await resolveCompanySlug(token, report.companyUid) : undefined;
|
|
108
|
+
return deployOrgContext(report, slug);
|
|
109
|
+
}
|
|
110
|
+
async function findOrCreateApp(token, slug, org) {
|
|
111
|
+
const listed = await deployJson(token, "/api/apps", "GET", undefined, org.headers);
|
|
112
|
+
const existing = Array.isArray(listed.apps)
|
|
113
|
+
? listed.apps.find((app) => !!app && typeof app === "object" && (app.subdomain === slug || app.name === slug))
|
|
114
|
+
: undefined;
|
|
115
|
+
const app = existing ?? await deployJson(token, "/api/apps", "POST", { name: slug, type: "static" }, org.headers);
|
|
116
|
+
const id = typeof app.id === "string" ? app.id : undefined;
|
|
117
|
+
const subdomain = typeof app.subdomain === "string" ? app.subdomain : undefined;
|
|
118
|
+
if (!id || !subdomain)
|
|
119
|
+
throw new ReportPublishError("hq-deploy returned an incomplete app record.");
|
|
120
|
+
return { id, subdomain };
|
|
121
|
+
}
|
|
122
|
+
export async function deployReportStatic(token, input) {
|
|
123
|
+
const app = await findOrCreateApp(token, input.slug, input.org);
|
|
124
|
+
await deployJson(token, `/api/apps/${encodeURIComponent(app.id)}/access-policy`, "PUT", input.access, input.org.headers);
|
|
125
|
+
const tarball = buildReportTarball(input.files);
|
|
126
|
+
const deployment = await deployJson(token, "/api/deploys", "POST", {
|
|
127
|
+
appSlug: app.subdomain,
|
|
128
|
+
...(input.org.org ? { org: input.org.org } : {}),
|
|
129
|
+
manifest: { files: [], size: tarball.sizeBytes, sha256: tarball.sha256 },
|
|
130
|
+
}, input.org.headers);
|
|
131
|
+
const deployId = typeof deployment.deployId === "string" ? deployment.deployId : undefined;
|
|
132
|
+
const presignedUrl = typeof deployment.presignedUrl === "string" ? deployment.presignedUrl : undefined;
|
|
133
|
+
if (!deployId || !presignedUrl)
|
|
134
|
+
throw new ReportPublishError("hq-deploy did not create an upload.");
|
|
135
|
+
let upload;
|
|
136
|
+
try {
|
|
137
|
+
upload = await fetch(presignedUrl, { method: "PUT", headers: { "Content-Type": "application/gzip" }, body: tarball.bytes });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new ReportPublishError("Report upload failed.");
|
|
141
|
+
}
|
|
142
|
+
if (!upload.ok)
|
|
143
|
+
throw new ReportPublishError("Report upload failed.");
|
|
144
|
+
const completed = await deployJson(token, `/api/deploys/${encodeURIComponent(deployId)}/complete`, "POST", { appSlug: app.subdomain }, input.org.headers);
|
|
145
|
+
const url = typeof completed.url === "string" ? completed.url : `https://${app.subdomain}.indigo-hq.com`;
|
|
146
|
+
return { appId: app.id, url, deployId };
|
|
147
|
+
}
|
|
148
|
+
async function publishWithOrg(token, report, directory, draft) {
|
|
149
|
+
return publishReport(report, directory, draft, {
|
|
150
|
+
patch: (current, patch) => patchReport(token, current, patch),
|
|
151
|
+
deploy: async (input) => {
|
|
152
|
+
const org = await orgFor(token, report);
|
|
153
|
+
return deployReportStatic(token, { ...input, org });
|
|
154
|
+
},
|
|
155
|
+
now: () => new Date().toISOString(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function publishAction(reportId, options) {
|
|
159
|
+
const token = await ensureCognitoToken();
|
|
160
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
161
|
+
print(await publishWithOrg(token, report, options.dir, options.draft === true), options.json === true);
|
|
162
|
+
}
|
|
163
|
+
async function promoteAction(reportId, options) {
|
|
164
|
+
const token = await ensureCognitoToken();
|
|
165
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
166
|
+
const directory = options.dir?.trim() ? options.dir : conventionReportDir(reportId);
|
|
167
|
+
const published = await publishWithOrg(token, report, directory, false);
|
|
168
|
+
// A promotion makes the freshly deployed live page authoritative. Null clears
|
|
169
|
+
// the draft pointer; omitting the field would leave the old draft in place.
|
|
170
|
+
const updated = published.draft
|
|
171
|
+
? await patchReport(token, published, { draft: null, state: "live" })
|
|
172
|
+
: published;
|
|
173
|
+
print(updated, options.json === true);
|
|
174
|
+
}
|
|
175
|
+
async function getAction(reportId, options) {
|
|
176
|
+
const report = await resolveReport(await ensureCognitoToken(), reportId, options.channel);
|
|
177
|
+
print(report, options.json === true);
|
|
178
|
+
}
|
|
179
|
+
async function discardDraftAction(reportId, options) {
|
|
180
|
+
const token = await ensureCognitoToken();
|
|
181
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
182
|
+
const org = await orgFor(token, report);
|
|
183
|
+
if (report.draft?.appId) {
|
|
184
|
+
const res = await fetch(`${DEPLOY_API_URL.replace(/\/+$/, "")}/api/apps/${encodeURIComponent(report.draft.appId)}`, {
|
|
185
|
+
method: "DELETE",
|
|
186
|
+
headers: deployHeaders(token, false, org.headers),
|
|
187
|
+
});
|
|
188
|
+
if (!res.ok && res.status !== 404)
|
|
189
|
+
throw new ReportPublishError("Could not remove the draft deployment.");
|
|
190
|
+
}
|
|
191
|
+
const updated = await patchReport(token, report, { draft: null, state: report.url ? "live" : "draft-pending" });
|
|
192
|
+
print(updated, options.json === true);
|
|
193
|
+
}
|
|
194
|
+
async function accessAction(reportId, options) {
|
|
195
|
+
if (!["company", "selected", "password", "public"].includes(options.mode)) {
|
|
196
|
+
throw new ReportPublishError("Access mode must be company, selected, password, or public.");
|
|
197
|
+
}
|
|
198
|
+
if (options.mode === "password" && !options.password) {
|
|
199
|
+
throw new ReportPublishError("A password is required for password access mode.");
|
|
200
|
+
}
|
|
201
|
+
const token = await ensureCognitoToken();
|
|
202
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
203
|
+
const appId = report.deployAppId ?? report.draft?.appId;
|
|
204
|
+
if (!appId)
|
|
205
|
+
throw new ReportPublishError("Publish the report before changing its access policy.");
|
|
206
|
+
const companyUid = report.companyUid ?? "personal";
|
|
207
|
+
const policy = {
|
|
208
|
+
mode: options.mode,
|
|
209
|
+
companyUid,
|
|
210
|
+
users: options.mode === "selected" ? [{ id: report.createdBy }] : [],
|
|
211
|
+
groups: [],
|
|
212
|
+
...(options.mode === "password" && options.password ? { password: options.password } : {}),
|
|
213
|
+
};
|
|
214
|
+
const org = await orgFor(token, report);
|
|
215
|
+
const result = await deployJson(token, `/api/apps/${encodeURIComponent(appId)}/access-policy`, "PUT", policy, org.headers);
|
|
216
|
+
print(result, options.json === true);
|
|
217
|
+
}
|
|
218
|
+
async function cancelOwnedJob(token, agentUid, jobId) {
|
|
219
|
+
const res = await vaultApiFetch({
|
|
220
|
+
token,
|
|
221
|
+
path: `/v1/agents/${encodeURIComponent(agentUid)}/jobs/${encodeURIComponent(jobId)}/cancel`,
|
|
222
|
+
method: "POST",
|
|
223
|
+
});
|
|
224
|
+
if (!res.ok && res.status !== 404)
|
|
225
|
+
throw new ReportPublishError("Could not remove the report job.");
|
|
226
|
+
}
|
|
227
|
+
async function adoptAction(reportId, options) {
|
|
228
|
+
const token = await ensureCognitoToken();
|
|
229
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
230
|
+
const updated = await adoptReport(report, {
|
|
231
|
+
cancelJob: (jobId) => cancelOwnedJob(token, report.ownerAgentUid, jobId),
|
|
232
|
+
createJob: async (input) => {
|
|
233
|
+
const res = await vaultApiFetch({
|
|
234
|
+
token,
|
|
235
|
+
path: `/v1/agents/${encodeURIComponent(report.ownerAgentUid)}/jobs`,
|
|
236
|
+
method: "POST",
|
|
237
|
+
body: input,
|
|
238
|
+
});
|
|
239
|
+
if (!res.ok)
|
|
240
|
+
throw new ReportPublishError("Could not install the report job.");
|
|
241
|
+
const body = await res.json();
|
|
242
|
+
const jobId = body.job?.jobId ?? body.jobId;
|
|
243
|
+
if (!jobId)
|
|
244
|
+
throw new ReportPublishError("Jobs API returned an incomplete job.");
|
|
245
|
+
return { jobId };
|
|
246
|
+
},
|
|
247
|
+
patch: (current, patch) => patchReport(token, current, patch),
|
|
248
|
+
});
|
|
249
|
+
print(updated, options.json === true);
|
|
250
|
+
}
|
|
251
|
+
async function decommissionAction(reportId, options) {
|
|
252
|
+
const token = await ensureCognitoToken();
|
|
253
|
+
const report = await resolveReport(token, reportId, options.channel);
|
|
254
|
+
const org = await orgFor(token, report);
|
|
255
|
+
const leftover = [];
|
|
256
|
+
for (const id of [report.deployAppId, report.draft?.appId]) {
|
|
257
|
+
if (!id)
|
|
258
|
+
continue;
|
|
259
|
+
try {
|
|
260
|
+
const res = await fetch(`${DEPLOY_API_URL.replace(/\/+$/, "")}/api/apps/${encodeURIComponent(id)}`, {
|
|
261
|
+
method: "DELETE",
|
|
262
|
+
headers: deployHeaders(token, false, org.headers),
|
|
263
|
+
});
|
|
264
|
+
if (res.status === 404)
|
|
265
|
+
leftover.push("The published page was already gone.");
|
|
266
|
+
else if (!res.ok)
|
|
267
|
+
leftover.push("A deployment could not be deactivated.");
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
leftover.push("A deployment could not be reached.");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// The durable report is marked decommissioned by hq-pro before this prompt.
|
|
274
|
+
// Cleanup gaps stay on the result so a missing app or offline bot does not roll the record back.
|
|
275
|
+
if (report.jobId) {
|
|
276
|
+
try {
|
|
277
|
+
await cancelOwnedJob(token, report.ownerAgentUid, report.jobId);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
leftover.push("The report job is still installed.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const note = leftover.length ? [...new Set(leftover)].join(" ") : null;
|
|
284
|
+
print({ reportId: report.reportId, decommissioned: true, note }, options.json === true);
|
|
285
|
+
}
|
|
286
|
+
export function registerReportCommand(program) {
|
|
287
|
+
const report = program.command("report").description("Publish and manage a channel report owned by this bot.");
|
|
288
|
+
const context = (command) => command.option("--channel <channelId>", "report channel context (or set HQ_REPORT_CHANNEL_ID)");
|
|
289
|
+
context(report.command("get <reportId>").option("--json").action((reportId, options) => runReportCommand(() => getAction(reportId, options))));
|
|
290
|
+
context(report.command("publish <reportId>").requiredOption("--dir <path>").option("--draft").option("--json").action((reportId, options) => runReportCommand(() => publishAction(reportId, options))));
|
|
291
|
+
context(report.command("promote <reportId>").option("--dir <path>", "report folder; defaults to <workerDir>/reports/<reportId>").option("--json").action((reportId, options) => runReportCommand(() => promoteAction(reportId, options))));
|
|
292
|
+
context(report.command("discard-draft <reportId>").option("--json").action((reportId, options) => runReportCommand(() => discardDraftAction(reportId, options))));
|
|
293
|
+
context(report.command("access <reportId>").requiredOption("--mode <mode>", "company|selected|password|public").option("--password <password>").option("--json").action((reportId, options) => runReportCommand(() => accessAction(reportId, options))));
|
|
294
|
+
context(report.command("decommission <reportId>").option("--json").action((reportId, options) => runReportCommand(() => decommissionAction(reportId, options))));
|
|
295
|
+
context(report.command("adopt <reportId>").option("--json").action((reportId, options) => runReportCommand(() => adoptAction(reportId, options))));
|
|
296
|
+
report.command("templates").option("--json").action((options) => {
|
|
297
|
+
const templates = listReportTemplates().map(({ name, path: templatePath }) => ({ name, path: templatePath }));
|
|
298
|
+
print(templates, options.json === true);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
export async function runReportCommand(action) {
|
|
302
|
+
try {
|
|
303
|
+
await action();
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error(errorMessage(error));
|
|
307
|
+
process.exitCode = 1;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/** Mirrors the deploy skill's static-report guardrails. */
|
|
2
|
+
export declare const REPORT_SOURCE_MAX_BYTES: number;
|
|
3
|
+
export declare const REPORT_BUNDLE_MAX_BYTES: number;
|
|
4
|
+
export declare const REPORT_SOURCE_MAX_FILES = 100;
|
|
5
|
+
export declare const REPORT_CSP = "default-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'none'; frame-src 'none'; form-action 'none'";
|
|
6
|
+
export type ReportLastRun = {
|
|
7
|
+
at: string;
|
|
8
|
+
status: "running" | "succeeded" | "failed";
|
|
9
|
+
reason?: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
};
|
|
12
|
+
export type ReportSchedule = {
|
|
13
|
+
kind: "daily" | "weekly" | "manual";
|
|
14
|
+
time: string;
|
|
15
|
+
timezone: string;
|
|
16
|
+
weekday?: number;
|
|
17
|
+
};
|
|
18
|
+
export type ChannelReport = {
|
|
19
|
+
reportId: string;
|
|
20
|
+
channelId: string;
|
|
21
|
+
companyUid?: string;
|
|
22
|
+
createdBy: string;
|
|
23
|
+
ownerAgentUid: string;
|
|
24
|
+
deploySlug: string;
|
|
25
|
+
state: "draft-pending" | "draft-ready" | "live" | "decommissioned";
|
|
26
|
+
instructions: string;
|
|
27
|
+
revision: number;
|
|
28
|
+
deployAppId?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
draft?: {
|
|
31
|
+
url: string;
|
|
32
|
+
appId: string;
|
|
33
|
+
generatedAt: string;
|
|
34
|
+
note?: string;
|
|
35
|
+
};
|
|
36
|
+
lastRun?: ReportLastRun;
|
|
37
|
+
jobId?: string;
|
|
38
|
+
jobState?: "pending" | "adopted" | "failed";
|
|
39
|
+
jobStateReason?: string;
|
|
40
|
+
schedule?: ReportSchedule;
|
|
41
|
+
};
|
|
42
|
+
export type ReportPatch = Partial<Pick<ChannelReport, "deployAppId" | "url" | "state" | "lastRun">> & {
|
|
43
|
+
previewImage?: string;
|
|
44
|
+
previewTitle?: string;
|
|
45
|
+
jobState?: "pending" | "adopted" | "failed";
|
|
46
|
+
jobStateReason?: string | null;
|
|
47
|
+
/** Null clears the live draft pointer or the adopted job id. */
|
|
48
|
+
draft?: ChannelReport["draft"] | null;
|
|
49
|
+
jobId?: string | null;
|
|
50
|
+
};
|
|
51
|
+
export type DeployOrgContext = {
|
|
52
|
+
headers: Record<string, string>;
|
|
53
|
+
org?: string;
|
|
54
|
+
};
|
|
55
|
+
export type StaticFile = {
|
|
56
|
+
path: string;
|
|
57
|
+
content: string;
|
|
58
|
+
encoding?: "utf8" | "base64";
|
|
59
|
+
};
|
|
60
|
+
export type ReportSource = {
|
|
61
|
+
files: StaticFile[];
|
|
62
|
+
previewTitle?: string;
|
|
63
|
+
previewImage?: string;
|
|
64
|
+
};
|
|
65
|
+
export type DeployAccessPolicy = {
|
|
66
|
+
mode: "company" | "selected" | "password" | "public";
|
|
67
|
+
companyUid: string;
|
|
68
|
+
users: Array<{
|
|
69
|
+
id: string;
|
|
70
|
+
}>;
|
|
71
|
+
groups: Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
}>;
|
|
74
|
+
password?: string;
|
|
75
|
+
};
|
|
76
|
+
export type ReportDeployment = {
|
|
77
|
+
appId: string;
|
|
78
|
+
url: string;
|
|
79
|
+
deployId: string;
|
|
80
|
+
};
|
|
81
|
+
export interface ReportPublishDependencies {
|
|
82
|
+
patch(report: ChannelReport, patch: ReportPatch): Promise<ChannelReport>;
|
|
83
|
+
deploy(input: {
|
|
84
|
+
slug: string;
|
|
85
|
+
files: StaticFile[];
|
|
86
|
+
access: DeployAccessPolicy;
|
|
87
|
+
}): Promise<ReportDeployment>;
|
|
88
|
+
now(): string;
|
|
89
|
+
/** Owner DM for a failed run. Omitted by callers that only record lastRun. */
|
|
90
|
+
notifyOwner?(notice: {
|
|
91
|
+
to: string;
|
|
92
|
+
body: string;
|
|
93
|
+
}): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Plain owner DM after a failed run. Suppressed for the publish lock
|
|
97
|
+
* ("already running") so a second run does not page the owner.
|
|
98
|
+
*/
|
|
99
|
+
export declare function ownerFailureNotice(input: {
|
|
100
|
+
code?: string | null;
|
|
101
|
+
reason: string;
|
|
102
|
+
reportUrl?: string | null;
|
|
103
|
+
ownerPersonUid: string;
|
|
104
|
+
}): {
|
|
105
|
+
to: string;
|
|
106
|
+
body: string;
|
|
107
|
+
} | null;
|
|
108
|
+
export interface ReportAdoptDependencies {
|
|
109
|
+
createJob(input: {
|
|
110
|
+
prompt: string;
|
|
111
|
+
schedule: {
|
|
112
|
+
kind: "recurring";
|
|
113
|
+
cron: string;
|
|
114
|
+
timezone: string;
|
|
115
|
+
};
|
|
116
|
+
context: {
|
|
117
|
+
reportId: string;
|
|
118
|
+
channelId: string;
|
|
119
|
+
};
|
|
120
|
+
}): Promise<{
|
|
121
|
+
jobId: string;
|
|
122
|
+
}>;
|
|
123
|
+
cancelJob(jobId: string): Promise<void>;
|
|
124
|
+
patch(report: ChannelReport, patch: ReportPatch): Promise<ChannelReport>;
|
|
125
|
+
}
|
|
126
|
+
export declare class ReportPublishError extends Error {
|
|
127
|
+
readonly code: "already-running" | "source-invalid" | "deploy-failed" | "job-failed";
|
|
128
|
+
constructor(message: string, code?: "already-running" | "source-invalid" | "deploy-failed" | "job-failed");
|
|
129
|
+
}
|
|
130
|
+
/** A deliberately boring, token-safe failure reason stored on the report. */
|
|
131
|
+
export declare function plainFailureReason(error: unknown): string;
|
|
132
|
+
export declare function injectReportCsp(html: string): string;
|
|
133
|
+
/** Read a deployable static report and inject its non-negotiable CSP. */
|
|
134
|
+
export declare function loadReportSource(directory: string): ReportSource;
|
|
135
|
+
/** Access policy defaults deliberately keep personal reports owner-only. */
|
|
136
|
+
export declare function defaultAccessPolicy(report: ChannelReport): DeployAccessPolicy;
|
|
137
|
+
export declare function reportDeploySlug(report: ChannelReport, draft: boolean): string;
|
|
138
|
+
/** Source folder convention: `<bot workerDir>/reports/<reportId>/`. */
|
|
139
|
+
export declare function conventionReportDir(reportId: string, workerDir?: string): string;
|
|
140
|
+
/** Company deploys send X-Org-Slug; personal-channel reports have no company to gate on. */
|
|
141
|
+
export declare function deployOrgContext(report: ChannelReport, companySlug?: string): DeployOrgContext;
|
|
142
|
+
/**
|
|
143
|
+
* EventBridge Scheduler cron for the agent jobs API.
|
|
144
|
+
* Daily uses `?` on the weekday field; weekly pins the weekday and `?` on the day-of-month.
|
|
145
|
+
*/
|
|
146
|
+
export declare function reportJobCron(schedule: ReportSchedule): string | null;
|
|
147
|
+
/** Job prompt is the stored update-report runbook with this report id filled in. */
|
|
148
|
+
export declare function reportJobPrompt(reportId: string): string;
|
|
149
|
+
/**
|
|
150
|
+
* Install, replace, or delete this bot's own scheduled job for a report.
|
|
151
|
+
* Jobs-API failures are recorded as jobState=failed with a plain reason.
|
|
152
|
+
*/
|
|
153
|
+
export declare function adoptReport(report: ChannelReport, deps: ReportAdoptDependencies): Promise<ChannelReport>;
|
|
154
|
+
/**
|
|
155
|
+
* Lock, deploy, then persist a report run. Every known failure after lock
|
|
156
|
+
* acquisition writes a terminal lastRun so the 15-minute server TTL is a
|
|
157
|
+
* crash-only fallback rather than normal control flow.
|
|
158
|
+
*/
|
|
159
|
+
export declare function publishReport(report: ChannelReport, directory: string, draft: boolean, deps: ReportPublishDependencies): Promise<ChannelReport>;
|
|
160
|
+
type BuiltTarball = {
|
|
161
|
+
bytes: Buffer;
|
|
162
|
+
sha256: string;
|
|
163
|
+
sizeBytes: number;
|
|
164
|
+
};
|
|
165
|
+
/** hq-deploy static payload format: index.html stays at the archive root. */
|
|
166
|
+
export declare function buildReportTarball(files: StaticFile[]): BuiltTarball;
|
|
167
|
+
/** Isolated test helper for creating a compliant report source. */
|
|
168
|
+
export declare function makeReportFixture(html: string): {
|
|
169
|
+
directory: string;
|
|
170
|
+
cleanup: () => void;
|
|
171
|
+
};
|
|
172
|
+
export {};
|
|
173
|
+
//# sourceMappingURL=report-publish.d.ts.map
|