@kici-dev/compiler 0.4.0 → 0.6.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.
Files changed (83) hide show
  1. package/dist/cli.js +37 -7
  2. package/dist/commands/approve.d.ts +12 -0
  3. package/dist/commands/approve.js +5 -2
  4. package/dist/commands/compile.js +5 -15
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/commands/endpoints.js +4 -6
  7. package/dist/commands/held-run-client.d.ts +21 -1
  8. package/dist/commands/held-run-client.js +34 -15
  9. package/dist/commands/hook.js +22 -20
  10. package/dist/commands/index.d.ts +2 -0
  11. package/dist/commands/index.js +2 -1
  12. package/dist/commands/init.d.ts +9 -2
  13. package/dist/commands/init.js +43 -16
  14. package/dist/commands/login.js +1 -1
  15. package/dist/commands/orchestrators.js +3 -2
  16. package/dist/commands/preview.js +1 -8
  17. package/dist/commands/reject.d.ts +12 -0
  18. package/dist/commands/reject.js +5 -2
  19. package/dist/commands/report/collect.d.ts +82 -0
  20. package/dist/commands/report/collect.js +234 -0
  21. package/dist/commands/report/identity.d.ts +48 -0
  22. package/dist/commands/report/identity.js +49 -0
  23. package/dist/commands/report/index.d.ts +63 -0
  24. package/dist/commands/report/index.js +119 -0
  25. package/dist/commands/report/upload.d.ts +38 -0
  26. package/dist/commands/report/upload.js +64 -0
  27. package/dist/commands/run-hold-watch.js +2 -2
  28. package/dist/commands/run.js +6 -3
  29. package/dist/commands/runs/show.js +80 -1
  30. package/dist/commands/types.js +51 -9
  31. package/dist/errors/formatter.d.ts +2 -4
  32. package/dist/errors/formatter.js +1 -3
  33. package/dist/errors/index.d.ts +1 -1
  34. package/dist/errors/index.js +2 -2
  35. package/dist/execution/sdk-alias.js +4 -2
  36. package/dist/fixtures/compiler.js +2 -1
  37. package/dist/format.js +3 -3
  38. package/dist/generators/secrets-dts.d.ts +8 -3
  39. package/dist/generators/secrets-dts.js +3 -3
  40. package/dist/hooks/installer.js +2 -1
  41. package/dist/llm-context/llms-architecture.txt +59 -23
  42. package/dist/llm-context/llms-cli.txt +168 -34
  43. package/dist/llm-context/llms-features-execution.txt +2017 -0
  44. package/dist/llm-context/llms-features.txt +96 -1100
  45. package/dist/llm-context/llms-full.txt +3131 -1407
  46. package/dist/llm-context/llms-getting-started.txt +164 -31
  47. package/dist/llm-context/llms-patterns.txt +186 -7
  48. package/dist/llm-context/llms-providers.txt +15 -33
  49. package/dist/llm-context/llms-sdk-runtime.txt +62 -40
  50. package/dist/llm-context/llms-sdk.txt +284 -58
  51. package/dist/llm-context/llms.txt +23 -15
  52. package/dist/local-plane/paths.d.ts +15 -0
  53. package/dist/local-plane/paths.js +22 -1
  54. package/dist/local-plane/plane-manager.js +2 -2
  55. package/dist/local-plane/port-holder.js +1 -1
  56. package/dist/local-plane/postgres.d.ts +3 -16
  57. package/dist/local-plane/postgres.js +10 -15
  58. package/dist/lockfile/generator.d.ts +12 -0
  59. package/dist/lockfile/generator.js +182 -54
  60. package/dist/lockfile/index.d.ts +0 -2
  61. package/dist/lockfile/index.js +1 -2
  62. package/dist/postinstall.js +2 -1
  63. package/dist/remote/config.d.ts +2 -15
  64. package/dist/remote/config.js +2 -16
  65. package/dist/remote/dashboard-client.d.ts +39 -0
  66. package/dist/remote/dashboard-client.js +41 -0
  67. package/dist/remote/oauth.js +7 -5
  68. package/dist/remote/uploader.js +2 -2
  69. package/dist/templates/package-json.js +1 -1
  70. package/dist/test-runner/dry-run.d.ts +1 -2
  71. package/dist/test-runner/dry-run.js +5 -20
  72. package/dist/test-runner/git-detector.js +2 -1
  73. package/dist/test-runner/job-executor.js +2 -1
  74. package/dist/test-runner/payload-builder.js +11 -17
  75. package/dist/types.d.ts +64 -10
  76. package/dist/types.js +7 -1
  77. package/dist/validation/validator.js +63 -6
  78. package/package.json +16 -11
  79. package/sbom.spdx.json +957 -900
  80. package/dist/lockfile/purity-analyzer.d.ts +0 -25
  81. package/dist/lockfile/purity-analyzer.js +0 -204
  82. package/dist/lockfile/purity-diagnostics.d.ts +0 -31
  83. package/dist/lockfile/purity-diagnostics.js +0 -52
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Build the `kici report` bundle.
3
+ *
4
+ * Writes the same archive layout the orchestrator's debug bundle uses, so
5
+ * `kici-admin inspect-bundle` reads either one, but collects from the workflow
6
+ * author's side: their config, their project, and the run they are reporting
7
+ * about — rather than an orchestrator's own logs and state.
8
+ *
9
+ * Every collector runs through `runCollector`, which turns a throw into a
10
+ * recorded `error` entry instead of losing the whole bundle. That matters twice
11
+ * over: a customer with one broken collector still gets a shareable report, and
12
+ * the per-collector outcome is itself a product feature — the manifest's
13
+ * `collectionReport` tells the reader which sections are trustworthy.
14
+ */
15
+ import { z } from 'zod';
16
+ import type { ProbeOutcome } from '../doctor.js';
17
+ /** Outcome of one collector. */
18
+ export declare const CollectionStatus: z.ZodEnum<{
19
+ empty: "empty";
20
+ error: "error";
21
+ ok: "ok";
22
+ skipped: "skipped";
23
+ }>;
24
+ export type CollectionStatus = z.infer<typeof CollectionStatus>;
25
+ export interface CollectionEntry {
26
+ collector: string;
27
+ status: CollectionStatus;
28
+ note?: string;
29
+ }
30
+ /** Bundle-format version, bumped when the archive layout changes. */
31
+ export declare const REPORT_BUNDLE_VERSION = "1.0";
32
+ export interface ReportManifest {
33
+ version: string;
34
+ generated_at: string;
35
+ bundle_id: string;
36
+ node_version: string;
37
+ platform: string;
38
+ metadata: Record<string, string>;
39
+ redacted: boolean;
40
+ collectionReport: CollectionEntry[];
41
+ }
42
+ /** One run's diagnostic material, as the dashboard client returns it. */
43
+ export interface RunMaterial {
44
+ detail: unknown;
45
+ logs: string;
46
+ }
47
+ /**
48
+ * Injectable seams, mirroring `DoctorDeps`, so every path is unit-testable
49
+ * without real IO or network.
50
+ */
51
+ export interface ReportBundleDeps {
52
+ loadConfig: () => Promise<Record<string, unknown>>;
53
+ probe: () => Promise<ProbeOutcome | null>;
54
+ readProject: (kiciDir: string) => Promise<Record<string, unknown>>;
55
+ fetchRun: (runId: string) => Promise<RunMaterial>;
56
+ now: () => Date;
57
+ }
58
+ export interface ReportBundleOptions {
59
+ outputPath: string;
60
+ kiciDir: string;
61
+ runId?: string;
62
+ metadata: Record<string, string>;
63
+ /** When false the bundle is assembled without scrubbing free text. */
64
+ redact: boolean;
65
+ deps?: Partial<ReportBundleDeps>;
66
+ }
67
+ export interface ReportBundleResult {
68
+ path: string;
69
+ sha256: string;
70
+ bundleId: string;
71
+ fileCount: number;
72
+ collectionReport: CollectionEntry[];
73
+ }
74
+ /**
75
+ * Collect and write one report bundle.
76
+ *
77
+ * The body stays a narrative caller: each section is one `runCollector` call
78
+ * whose result is appended to the archive, so adding a collector never grows
79
+ * the control flow.
80
+ */
81
+ export declare function createReportBundle(options: ReportBundleOptions): Promise<ReportBundleResult>;
82
+ //# sourceMappingURL=collect.d.ts.map
@@ -0,0 +1,234 @@
1
+ import "../../rolldown-runtime-ClRpJifh.js";
2
+ import { collectIdentity } from "./identity.js";
3
+ import * as path$1 from "node:path";
4
+ import * as fs$1 from "node:fs";
5
+ import * as os$1 from "node:os";
6
+ import { z } from "zod";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { ZipArchive } from "archiver";
9
+ import { redactConfig, scrubText } from "@kici-dev/core/diagnostics-redaction";
10
+ //#region src/commands/report/collect.ts
11
+ /**
12
+ * Build the `kici report` bundle.
13
+ *
14
+ * Writes the same archive layout the orchestrator's debug bundle uses, so
15
+ * `kici-admin inspect-bundle` reads either one, but collects from the workflow
16
+ * author's side: their config, their project, and the run they are reporting
17
+ * about — rather than an orchestrator's own logs and state.
18
+ *
19
+ * Every collector runs through `runCollector`, which turns a throw into a
20
+ * recorded `error` entry instead of losing the whole bundle. That matters twice
21
+ * over: a customer with one broken collector still gets a shareable report, and
22
+ * the per-collector outcome is itself a product feature — the manifest's
23
+ * `collectionReport` tells the reader which sections are trustworthy.
24
+ */
25
+ /** Outcome of one collector. */
26
+ const CollectionStatus = z.enum([
27
+ "ok",
28
+ "empty",
29
+ "error",
30
+ "skipped"
31
+ ]);
32
+ /** Bundle-format version, bumped when the archive layout changes. */
33
+ const REPORT_BUNDLE_VERSION = "1.0";
34
+ /**
35
+ * Read the project's own KiCI state: which workflows exist and what the lock
36
+ * file says. Deliberately reads metadata rather than workflow source — the
37
+ * source is the customer's code, and a diagnostic bundle should not ship it.
38
+ */
39
+ async function defaultReadProject(kiciDir) {
40
+ const workflowsDir = path$1.join(kiciDir, "workflows");
41
+ const workflows = fs$1.existsSync(workflowsDir) ? fs$1.readdirSync(workflowsDir).sort() : [];
42
+ const lockPath = path$1.join(kiciDir, "kici.lock.json");
43
+ let lock = null;
44
+ if (fs$1.existsSync(lockPath)) {
45
+ const parsed = JSON.parse(fs$1.readFileSync(lockPath, "utf-8"));
46
+ lock = {
47
+ schemaVersion: parsed.schemaVersion ?? null,
48
+ generatedAt: parsed.generatedAt ?? null,
49
+ workflowCount: Array.isArray(parsed.workflows) ? parsed.workflows.length : null
50
+ };
51
+ }
52
+ return {
53
+ kiciDir,
54
+ exists: fs$1.existsSync(kiciDir),
55
+ workflows,
56
+ lock
57
+ };
58
+ }
59
+ const DEFAULT_DEPS = {
60
+ loadConfig: async () => {
61
+ const { loadGlobalConfig } = await import("../../remote/config.js");
62
+ return await loadGlobalConfig();
63
+ },
64
+ probe: async () => {
65
+ const { loadGlobalConfig } = await import("../../remote/config.js");
66
+ const { DashboardClient, DashboardClientError } = await import("../../remote/dashboard-client.js");
67
+ const config = await loadGlobalConfig();
68
+ if (!config.activeOrgId || !(config.pat ?? config.token)) return null;
69
+ try {
70
+ return {
71
+ ok: true,
72
+ infra: await DashboardClient.fromConfig(config).getInfrastructure()
73
+ };
74
+ } catch (err) {
75
+ if (err instanceof DashboardClientError) return {
76
+ ok: false,
77
+ kind: err.kind,
78
+ message: err.message
79
+ };
80
+ throw err;
81
+ }
82
+ },
83
+ readProject: defaultReadProject,
84
+ fetchRun: async (runId) => {
85
+ const { loadGlobalConfig } = await import("../../remote/config.js");
86
+ const { DashboardClient } = await import("../../remote/dashboard-client.js");
87
+ const detail = await DashboardClient.fromConfig(await loadGlobalConfig()).getRunDetail(runId);
88
+ return {
89
+ detail,
90
+ logs: JSON.stringify(detail, null, 2)
91
+ };
92
+ },
93
+ now: () => /* @__PURE__ */ new Date()
94
+ };
95
+ /**
96
+ * Run one collector, recording its outcome instead of letting it abort the
97
+ * bundle. `empty` and `error` are the two signals the dev-side gap filer reads.
98
+ */
99
+ async function runCollector(report, collector, fn, scrub) {
100
+ try {
101
+ const value = await fn();
102
+ const isEmpty = value === null || value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "string" && value.length === 0;
103
+ report.push({
104
+ collector,
105
+ status: isEmpty ? "empty" : "ok"
106
+ });
107
+ return value;
108
+ } catch (err) {
109
+ report.push({
110
+ collector,
111
+ status: "error",
112
+ note: scrub(err instanceof Error ? err.message : String(err))
113
+ });
114
+ return;
115
+ }
116
+ }
117
+ /** Machine facts about the host the report was produced on. */
118
+ function systemInfo() {
119
+ return {
120
+ platform: os$1.platform(),
121
+ release: os$1.release(),
122
+ arch: os$1.arch(),
123
+ nodeVersion: process.version,
124
+ cpuCount: os$1.cpus().length,
125
+ totalMemory: os$1.totalmem(),
126
+ freeMemory: os$1.freemem(),
127
+ hostname: os$1.hostname()
128
+ };
129
+ }
130
+ /** sha256 over the finished file, so the customer can quote a digest. */
131
+ async function digestOf(file) {
132
+ const hash = createHash("sha256");
133
+ await new Promise((resolve, reject) => {
134
+ const stream = fs$1.createReadStream(file);
135
+ stream.on("data", (chunk) => hash.update(chunk));
136
+ stream.on("end", resolve);
137
+ stream.on("error", reject);
138
+ });
139
+ return hash.digest("hex");
140
+ }
141
+ /**
142
+ * Collect and write one report bundle.
143
+ *
144
+ * The body stays a narrative caller: each section is one `runCollector` call
145
+ * whose result is appended to the archive, so adding a collector never grows
146
+ * the control flow.
147
+ */
148
+ async function createReportBundle(options) {
149
+ const deps = {
150
+ ...DEFAULT_DEPS,
151
+ ...options.deps
152
+ };
153
+ const scrub = options.redact ? scrubText : (s) => s;
154
+ const report = [];
155
+ const bundleId = randomUUID().replace(/-/g, "").slice(0, 24);
156
+ const dir = path$1.dirname(options.outputPath);
157
+ if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
158
+ const output = fs$1.createWriteStream(options.outputPath);
159
+ const archive = new ZipArchive({ zlib: { level: 6 } });
160
+ const finalized = new Promise((resolve, reject) => {
161
+ output.on("close", resolve);
162
+ archive.on("error", reject);
163
+ output.on("error", reject);
164
+ });
165
+ archive.pipe(output);
166
+ let fileCount = 0;
167
+ const append = (body, name) => {
168
+ archive.append(scrub(JSON.stringify(body, null, 2)), { name });
169
+ fileCount += 1;
170
+ };
171
+ let probe;
172
+ try {
173
+ probe = await deps.probe();
174
+ report.push(probe === null ? {
175
+ collector: "probe",
176
+ status: "skipped",
177
+ note: "not authenticated"
178
+ } : {
179
+ collector: "probe",
180
+ status: "ok"
181
+ });
182
+ } catch (err) {
183
+ report.push({
184
+ collector: "probe",
185
+ status: "error",
186
+ note: scrub(err instanceof Error ? err.message : String(err))
187
+ });
188
+ }
189
+ const identity = await runCollector(report, "identity", () => Promise.resolve(collectIdentity(probe ?? null)), scrub);
190
+ if (identity) append(identity, "identity.json");
191
+ const config = await runCollector(report, "config", () => deps.loadConfig(), scrub);
192
+ if (config !== void 0) append(options.redact ? redactConfig(config) : config, "config/config.json");
193
+ const project = await runCollector(report, "project", () => deps.readProject(options.kiciDir), scrub);
194
+ if (project !== void 0) append(project, "project/project.json");
195
+ const system = await runCollector(report, "system", () => Promise.resolve(systemInfo()), scrub);
196
+ if (system !== void 0) append(system, "system/info.json");
197
+ if (options.runId) {
198
+ const run = await runCollector(report, "run", () => deps.fetchRun(options.runId), scrub);
199
+ if (run !== void 0) {
200
+ append(run.detail, `runs/${options.runId}/detail.json`);
201
+ archive.append(scrub(run.logs), { name: `runs/${options.runId}/logs.txt` });
202
+ fileCount += 1;
203
+ }
204
+ } else report.push({
205
+ collector: "run",
206
+ status: "skipped",
207
+ note: "no --run given"
208
+ });
209
+ const manifest = {
210
+ version: "1.0",
211
+ generated_at: deps.now().toISOString(),
212
+ bundle_id: bundleId,
213
+ node_version: process.version,
214
+ platform: process.platform,
215
+ metadata: options.metadata,
216
+ redacted: options.redact,
217
+ collectionReport: report
218
+ };
219
+ archive.append(scrub(JSON.stringify(manifest, null, 2)), { name: "manifest.json" });
220
+ fileCount += 1;
221
+ await archive.finalize();
222
+ await finalized;
223
+ return {
224
+ path: options.outputPath,
225
+ sha256: await digestOf(options.outputPath),
226
+ bundleId,
227
+ fileCount,
228
+ collectionReport: report
229
+ };
230
+ }
231
+ //#endregion
232
+ export { CollectionStatus, REPORT_BUNDLE_VERSION, createReportBundle };
233
+
234
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Version and environment context for an issue report.
3
+ *
4
+ * A report without version context costs a support round-trip before anyone can
5
+ * even reproduce the problem, so this is collected first and never fails: the
6
+ * client-side half always resolves, and the component versions are folded in
7
+ * only when the authenticated probe actually reached the Platform.
8
+ */
9
+ import type { ProbeOutcome } from '../doctor.js';
10
+ /** One orchestrator the caller's org has connected, and what it is running. */
11
+ export interface OrchestratorIdentity {
12
+ clusterName: string;
13
+ version: string | null;
14
+ mode: string | null;
15
+ connected: boolean;
16
+ }
17
+ export interface ReportIdentity {
18
+ kiciCliVersion: string;
19
+ nodeVersion: string;
20
+ platform: string;
21
+ arch: string;
22
+ /** The wire protocol this CLI speaks. */
23
+ protocolVersion: number;
24
+ /**
25
+ * Every orchestrator the probe saw, not just the first: a report about a
26
+ * routing problem is usually about which of several answered.
27
+ */
28
+ orchestrators: OrchestratorIdentity[];
29
+ /**
30
+ * The newest KiCI version the Platform knows about, when it told us. This is
31
+ * an upgrade hint, NOT the Platform's own version — the diagnostics response
32
+ * carries no such field, and inventing one would put a fabricated value in a
33
+ * bundle a human will read as fact.
34
+ */
35
+ latestKnownVersion?: string;
36
+ /** Why component versions are missing, when the probe could not run. */
37
+ probeError?: string;
38
+ }
39
+ /**
40
+ * Build the identity block.
41
+ *
42
+ * `probe` is the same `ProbeOutcome` `kici doctor` already performs — one
43
+ * authenticated infrastructure read — passed in rather than re-fetched so a
44
+ * report never issues a second round trip for data the caller may already
45
+ * hold, and so every path is testable without network.
46
+ */
47
+ export declare function collectIdentity(probe: ProbeOutcome | null): ReportIdentity;
48
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1,49 @@
1
+ import "../../rolldown-runtime-ClRpJifh.js";
2
+ import { PROTOCOL_VERSION } from "@kici-dev/engine";
3
+ //#region src/commands/report/identity.ts
4
+ /**
5
+ * Version and environment context for an issue report.
6
+ *
7
+ * A report without version context costs a support round-trip before anyone can
8
+ * even reproduce the problem, so this is collected first and never fails: the
9
+ * client-side half always resolves, and the component versions are folded in
10
+ * only when the authenticated probe actually reached the Platform.
11
+ */
12
+ /**
13
+ * Build the identity block.
14
+ *
15
+ * `probe` is the same `ProbeOutcome` `kici doctor` already performs — one
16
+ * authenticated infrastructure read — passed in rather than re-fetched so a
17
+ * report never issues a second round trip for data the caller may already
18
+ * hold, and so every path is testable without network.
19
+ */
20
+ function collectIdentity(probe) {
21
+ const identity = {
22
+ kiciCliVersion: "0.6.0",
23
+ nodeVersion: process.version,
24
+ platform: process.platform,
25
+ arch: process.arch,
26
+ protocolVersion: PROTOCOL_VERSION,
27
+ orchestrators: []
28
+ };
29
+ if (probe === null) {
30
+ identity.probeError = "not authenticated; component versions unavailable";
31
+ return identity;
32
+ }
33
+ if (!probe.ok) {
34
+ identity.probeError = `${probe.kind}: ${probe.message}`;
35
+ return identity;
36
+ }
37
+ identity.orchestrators = probe.infra.orchestrators.map((o) => ({
38
+ clusterName: o.clusterName ?? "(unnamed)",
39
+ version: o.version ?? null,
40
+ mode: o.mode ?? null,
41
+ connected: o.connected
42
+ }));
43
+ if (probe.infra.latestVersion) identity.latestKnownVersion = probe.infra.latestVersion;
44
+ return identity;
45
+ }
46
+ //#endregion
47
+ export { collectIdentity };
48
+
49
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `kici report` — produce a redacted diagnostic bundle to share when reporting
3
+ * an issue, and optionally upload it privately to KiCI.
4
+ *
5
+ * Writing and sending are separate acts. The default writes a ZIP next to you
6
+ * and tells you where it is, so you can open it and see exactly what you would
7
+ * be sharing; `--upload` is the explicit second step.
8
+ */
9
+ import { type ReportBundleDeps } from './collect.js';
10
+ import { type UploadDeps } from './upload.js';
11
+ export interface ReportOptions {
12
+ output?: string;
13
+ run?: string;
14
+ /** Repeatable `key=value` pairs. */
15
+ metadata: string[];
16
+ redact: boolean;
17
+ upload?: boolean;
18
+ message?: string;
19
+ email?: string;
20
+ kiciDir: string;
21
+ deps?: Partial<ReportBundleDeps>;
22
+ uploadDeps?: UploadDeps;
23
+ }
24
+ export interface ReportListOptions {
25
+ json?: boolean;
26
+ deps?: {
27
+ listIssueReports: () => Promise<{
28
+ reports: ReportRow[];
29
+ }>;
30
+ };
31
+ }
32
+ export interface ReportRow {
33
+ ref: string;
34
+ bundleId: string;
35
+ byteSize: number;
36
+ status: string;
37
+ createdAt: string;
38
+ userId: string;
39
+ message: string | null;
40
+ }
41
+ export interface ReportWithdrawOptions {
42
+ ref: string;
43
+ deps?: {
44
+ withdrawIssueReport: (ref: string) => Promise<{
45
+ ref: string;
46
+ deleted: boolean;
47
+ }>;
48
+ };
49
+ }
50
+ /**
51
+ * Parse repeatable `--metadata key=value` into a record.
52
+ *
53
+ * The accumulator is null-prototyped so a `__proto__=x` pair is stored as an
54
+ * ordinary key rather than silently vanishing into the prototype — a dropped
55
+ * pair in a diagnostic bundle is a lie about what the reporter attached.
56
+ */
57
+ export declare function parseMetadata(pairs: string[]): Record<string, string>;
58
+ /** Default bundle path: timestamped, in the working directory. */
59
+ export declare function defaultOutputPath(now: Date): string;
60
+ export declare function reportCommand(options: ReportOptions): Promise<boolean>;
61
+ export declare function reportListCommand(options: ReportListOptions): Promise<boolean>;
62
+ export declare function reportWithdrawCommand(options: ReportWithdrawOptions): Promise<boolean>;
63
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,119 @@
1
+ import "../../rolldown-runtime-ClRpJifh.js";
2
+ import { DashboardClientError } from "../../remote/dashboard-client.js";
3
+ import { createReportBundle } from "./collect.js";
4
+ import { uploadReportBundle } from "./upload.js";
5
+ import * as path$1 from "node:path";
6
+ import pc from "picocolors";
7
+ import { createLogger, toErrorMessage } from "@kici-dev/core";
8
+ //#region src/commands/report/index.ts
9
+ /**
10
+ * `kici report` — produce a redacted diagnostic bundle to share when reporting
11
+ * an issue, and optionally upload it privately to KiCI.
12
+ *
13
+ * Writing and sending are separate acts. The default writes a ZIP next to you
14
+ * and tells you where it is, so you can open it and see exactly what you would
15
+ * be sharing; `--upload` is the explicit second step.
16
+ */
17
+ const logger$1 = createLogger({ prefix: "report" });
18
+ /**
19
+ * Parse repeatable `--metadata key=value` into a record.
20
+ *
21
+ * The accumulator is null-prototyped so a `__proto__=x` pair is stored as an
22
+ * ordinary key rather than silently vanishing into the prototype — a dropped
23
+ * pair in a diagnostic bundle is a lie about what the reporter attached.
24
+ */
25
+ function parseMetadata(pairs) {
26
+ const out = Object.create(null);
27
+ for (const pair of pairs) {
28
+ const eq = pair.indexOf("=");
29
+ if (eq <= 0) throw new Error(`Invalid --metadata "${pair}": expected key=value.`);
30
+ out[pair.slice(0, eq)] = pair.slice(eq + 1);
31
+ }
32
+ return { ...out };
33
+ }
34
+ /** Default bundle path: timestamped, in the working directory. */
35
+ function defaultOutputPath(now) {
36
+ const stamp = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
37
+ return path$1.resolve(`./kici-report-${stamp}.zip`);
38
+ }
39
+ async function reportCommand(options) {
40
+ try {
41
+ const metadata = parseMetadata(options.metadata);
42
+ const now = options.deps?.now?.() ?? /* @__PURE__ */ new Date();
43
+ const outputPath = options.output ? path$1.resolve(options.output) : defaultOutputPath(now);
44
+ if (!options.redact && options.upload) throw new Error("--no-redact cannot be combined with --upload: that would send unredacted secrets to KiCI. Write the bundle locally, review it, and share it yourself.");
45
+ if (!options.redact) console.warn(pc.yellow("WARNING: --no-redact was given. This bundle keeps secrets in plain text. Review it carefully and do not share it unless you are certain."));
46
+ const result = await createReportBundle({
47
+ outputPath,
48
+ kiciDir: options.kiciDir,
49
+ runId: options.run,
50
+ metadata,
51
+ redact: options.redact,
52
+ deps: options.deps
53
+ });
54
+ const problems = result.collectionReport.filter((e) => e.status === "error" || e.status === "empty");
55
+ console.log(`Report bundle written to ${result.path}`);
56
+ console.log(` sha256: ${result.sha256}`);
57
+ console.log(` ${result.fileCount} file(s) collected`);
58
+ if (problems.length > 0) console.log(` incomplete: ${problems.map((p) => `${p.collector} (${p.status})`).join(", ")}`);
59
+ if (options.redact) console.log(pc.dim(" Redaction is best effort. Review the bundle before sharing it — unknown secret formats can survive."));
60
+ if (options.upload) {
61
+ const upload = await uploadReportBundle(result.path, {
62
+ bundleId: result.bundleId,
63
+ message: options.message,
64
+ email: options.email
65
+ }, options.uploadDeps);
66
+ console.log("");
67
+ console.log(`Report uploaded. Quote reference ${pc.bold(upload.ref)} to KiCI support.`);
68
+ } else {
69
+ console.log("");
70
+ console.log(pc.dim("Send it with `kici report --upload`, or attach the file yourself."));
71
+ }
72
+ return true;
73
+ } catch (err) {
74
+ logger$1.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
75
+ return false;
76
+ }
77
+ }
78
+ async function reportListCommand(options) {
79
+ try {
80
+ const list = options.deps ? await options.deps.listIssueReports() : await (async () => {
81
+ const { DashboardClient } = await import("../../remote/dashboard-client.js");
82
+ return (await DashboardClient.load()).listIssueReports();
83
+ })();
84
+ if (options.json) {
85
+ console.log(JSON.stringify(list, null, 2));
86
+ return true;
87
+ }
88
+ if (list.reports.length === 0) {
89
+ console.log("No issue reports uploaded.");
90
+ return true;
91
+ }
92
+ for (const r of list.reports) console.log(`${r.ref} ${r.status.padEnd(8)} ${r.byteSize} bytes ${r.createdAt} ${r.bundleId}`);
93
+ return true;
94
+ } catch (err) {
95
+ logger$1.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
96
+ return false;
97
+ }
98
+ }
99
+ async function reportWithdrawCommand(options) {
100
+ try {
101
+ if (!(await (options.deps ? options.deps.withdrawIssueReport : await (async () => {
102
+ const { DashboardClient } = await import("../../remote/dashboard-client.js");
103
+ const client = await DashboardClient.load();
104
+ return client.withdrawIssueReport.bind(client);
105
+ })())(options.ref)).deleted) {
106
+ logger$1.error(pc.red(`Report ${options.ref} was NOT deleted. Try again, or contact KiCI.`));
107
+ return false;
108
+ }
109
+ console.log(`Report ${options.ref} withdrawn. The uploaded bundle has been deleted.`);
110
+ return true;
111
+ } catch (err) {
112
+ logger$1.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
113
+ return false;
114
+ }
115
+ }
116
+ //#endregion
117
+ export { defaultOutputPath, parseMetadata, reportCommand, reportListCommand, reportWithdrawCommand };
118
+
119
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Upload a report bundle privately to KiCI.
3
+ *
4
+ * Three steps, and the split is the point: the Platform mints a presigned PUT,
5
+ * the bytes go straight from this machine to object storage, and a confirm call
6
+ * lets the Platform verify what actually landed. The bundle never transits the
7
+ * control plane, and the Platform never takes the client's word for the size.
8
+ *
9
+ * Upload is always opt-in (`--upload`). A bundle contains diagnostic data from
10
+ * the customer's own machine, so sending it is a separate, explicit act from
11
+ * producing it.
12
+ */
13
+ import { DashboardClient } from '../../remote/dashboard-client.js';
14
+ export interface UploadMeta {
15
+ bundleId: string;
16
+ message?: string;
17
+ email?: string;
18
+ }
19
+ export interface UploadDeps {
20
+ createIssueReport: DashboardClient['createIssueReport'];
21
+ confirmIssueReport: DashboardClient['confirmIssueReport'];
22
+ /** PUT the bundle bytes at the presigned URL. */
23
+ putBytes: (url: string, body: Buffer) => Promise<void>;
24
+ }
25
+ /** sha256 of the file, which the Platform records alongside the report. */
26
+ export declare function sha256Of(body: Buffer): string;
27
+ /**
28
+ * Upload one bundle and return the reference id to quote to support.
29
+ *
30
+ * Reads the file once and derives both the size and the digest from those same
31
+ * bytes, so what is promised at presign time is necessarily what is uploaded —
32
+ * a stat-then-read would race a file still being written.
33
+ */
34
+ export declare function uploadReportBundle(zipPath: string, meta: UploadMeta, deps?: UploadDeps): Promise<{
35
+ ref: string;
36
+ status: string;
37
+ }>;
38
+ //# sourceMappingURL=upload.d.ts.map