@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,64 @@
1
+ import "../../rolldown-runtime-ClRpJifh.js";
2
+ import { DashboardClient } from "../../remote/dashboard-client.js";
3
+ import * as fs$1 from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/commands/report/upload.ts
6
+ /**
7
+ * Upload a report bundle privately to KiCI.
8
+ *
9
+ * Three steps, and the split is the point: the Platform mints a presigned PUT,
10
+ * the bytes go straight from this machine to object storage, and a confirm call
11
+ * lets the Platform verify what actually landed. The bundle never transits the
12
+ * control plane, and the Platform never takes the client's word for the size.
13
+ *
14
+ * Upload is always opt-in (`--upload`). A bundle contains diagnostic data from
15
+ * the customer's own machine, so sending it is a separate, explicit act from
16
+ * producing it.
17
+ */
18
+ /** sha256 of the file, which the Platform records alongside the report. */
19
+ function sha256Of(body) {
20
+ return createHash("sha256").update(body).digest("hex");
21
+ }
22
+ async function defaultPutBytes(url, body) {
23
+ const res = await fetch(url, {
24
+ method: "PUT",
25
+ body: new Uint8Array(body),
26
+ headers: { "Content-Length": String(body.byteLength) }
27
+ });
28
+ if (!res.ok) throw new Error(`Bundle upload failed (${res.status} ${res.statusText}).`);
29
+ }
30
+ async function defaultDeps() {
31
+ const client = await DashboardClient.load();
32
+ return {
33
+ createIssueReport: client.createIssueReport.bind(client),
34
+ confirmIssueReport: client.confirmIssueReport.bind(client),
35
+ putBytes: defaultPutBytes
36
+ };
37
+ }
38
+ /**
39
+ * Upload one bundle and return the reference id to quote to support.
40
+ *
41
+ * Reads the file once and derives both the size and the digest from those same
42
+ * bytes, so what is promised at presign time is necessarily what is uploaded —
43
+ * a stat-then-read would race a file still being written.
44
+ */
45
+ async function uploadReportBundle(zipPath, meta, deps) {
46
+ const resolved = deps ?? await defaultDeps();
47
+ const body = fs$1.readFileSync(zipPath);
48
+ const { ref, uploadUrl } = await resolved.createIssueReport({
49
+ bundleId: meta.bundleId,
50
+ byteSize: body.byteLength,
51
+ sha256: sha256Of(body),
52
+ message: meta.message,
53
+ email: meta.email
54
+ });
55
+ await resolved.putBytes(uploadUrl, body);
56
+ return {
57
+ ref,
58
+ status: (await resolved.confirmIssueReport(ref)).status
59
+ };
60
+ }
61
+ //#endregion
62
+ export { sha256Of, uploadReportBundle };
63
+
64
+ //# sourceMappingURL=upload.js.map
@@ -66,7 +66,7 @@ async function handleNewHolds(args) {
66
66
  printHold(action.hold, out);
67
67
  ctx = ctx ?? await resolveCtx();
68
68
  if (!ctx) {
69
- out(pc.dim(`Run held; approve via \`kici approve ${action.hold.runId}\`.`));
69
+ out(pc.dim(`Run held; approve via \`kici approve ${action.hold.runId} --hold ${action.hold.id}\`.`));
70
70
  continue;
71
71
  }
72
72
  if (args.approveAll) {
@@ -74,7 +74,7 @@ async function handleNewHolds(args) {
74
74
  continue;
75
75
  }
76
76
  if (action.kind === "notify") {
77
- out(pc.dim(`Run held; approve via the dashboard or \`kici approve ${action.hold.runId}\`.`));
77
+ out(pc.dim(`Run held; approve via the dashboard or \`kici approve ${action.hold.runId} --hold ${action.hold.id}\`.`));
78
78
  continue;
79
79
  }
80
80
  const approved = await args.confirm("Approve this gate?");
@@ -303,8 +303,10 @@ async function renderResults(results, options) {
303
303
  const junitXml = formatJunitResult(runResults);
304
304
  await writeFile(options.junit, junitXml);
305
305
  if (!options.quiet) logger.info(pc.green(`JUnit XML written to ${options.junit}`));
306
- } else if (!options.quiet || results.some((r) => r.status !== "success" && r.status !== "accepted")) if (runResults.length > 1) logger.info(formatMultiFixtureSummary(runResults));
307
- else displayRemoteResults(results);
306
+ } else if (!options.quiet || results.some((r) => r.status !== "success" && r.status !== "accepted")) {
307
+ if (runResults.length > 1) logger.info(formatMultiFixtureSummary(runResults));
308
+ else displayRemoteResults(results);
309
+ }
308
310
  return results.every((r) => r.status === "accepted" || r.status === "success");
309
311
  }
310
312
  /**
@@ -537,10 +539,11 @@ async function watchRunHolds(runId, seenHolds, getCtx, setCtx, options) {
537
539
  const holds = await listHeldRunsForRun(ctx, runId);
538
540
  if (holds.length === 0) return;
539
541
  const quiet = Boolean(options.quiet);
542
+ const isTty = quiet ? false : Boolean(process.stdin.isTTY && process.stdout.isTTY);
540
543
  await handleNewHolds({
541
544
  holds,
542
545
  seen: seenHolds,
543
- isTty: quiet ? false : Boolean(process.stdin.isTTY && process.stdout.isTTY),
546
+ isTty,
544
547
  output: quiet ? (line) => void process.stderr.write(line + "\n") : (line) => logger.info(line),
545
548
  approveAll: Boolean(options.approveAll),
546
549
  confirm: (message) => confirm({
@@ -2,6 +2,7 @@ import "../../rolldown-runtime-ClRpJifh.js";
2
2
  import { DashboardClient, DashboardClientError } from "../../remote/dashboard-client.js";
3
3
  import { RunHistory } from "../../remote/history.js";
4
4
  import { colorStatus, formatDuration as formatDuration$1, relativeTime } from "../../remote/render.js";
5
+ import { HeldRunRequestError, listHeldRunsForRun, resolveHeldRunContext } from "../held-run-client.js";
5
6
  import pc from "picocolors";
6
7
  import { logger, toErrorMessage } from "@kici-dev/core";
7
8
  //#region src/commands/runs/show.ts
@@ -17,33 +18,111 @@ async function runsShowCommand(runId, options = {}) {
17
18
  throw err;
18
19
  }
19
20
  const detail = await client.getRunDetail(runId);
21
+ const holds = await loadHolds(runId);
20
22
  if (options.json) {
21
23
  console.log(JSON.stringify({
22
24
  run,
23
- detail
25
+ detail,
26
+ heldRuns: holds.holds,
27
+ ...holds.unavailable && { heldRunsUnavailable: holds.unavailable }
24
28
  }, null, 2));
25
29
  return true;
26
30
  }
27
31
  printHeader(run);
32
+ printInitFailure(detail.initFailure);
28
33
  printJobs(detail.jobs);
34
+ printHolds(holds);
29
35
  return true;
30
36
  } catch (err) {
31
37
  logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
32
38
  return false;
33
39
  }
34
40
  }
41
+ /**
42
+ * Read the approval holds recorded for a run.
43
+ *
44
+ * The holds are extra detail on a run this command can already display, so a
45
+ * failure here degrades to a note rather than failing the command: an operator
46
+ * asking why a job did not run still gets the run, its jobs and its init
47
+ * failure. The auth context is resolved quietly for the same reason — a config
48
+ * that cannot reach the held-runs API is not an error for `runs show`.
49
+ */
50
+ async function loadHolds(runId) {
51
+ try {
52
+ const ctx = await resolveHeldRunContext({ quiet: true });
53
+ if (!ctx) return {
54
+ holds: [],
55
+ unavailable: "not authenticated for held-run lookup",
56
+ unavailableSilently: true
57
+ };
58
+ return { holds: await listHeldRunsForRun(ctx, runId) };
59
+ } catch (err) {
60
+ const denied = err instanceof HeldRunRequestError && err.isPermissionDenied;
61
+ return {
62
+ holds: [],
63
+ unavailable: toErrorMessage(err),
64
+ ...denied && { unavailableSilently: true }
65
+ };
66
+ }
67
+ }
35
68
  function printHeader(run) {
36
69
  const f = (v) => v == null ? "—" : v;
37
70
  console.log(pc.bold(`\nRun ${run.runId}`) + ` ${colorStatus(run.status)}`);
38
71
  console.log(pc.gray(` workflow=${f(run.workflowName)} repo=${f(run.repoIdentifier)} branch=${f(run.ref)} sha=${f(run.sha)} trigger=${f(run.triggerEvent)} by=${f(run.triggeredBy)}`));
39
72
  console.log(pc.gray(` started=${relativeTime(run.startedAt ?? void 0)}`));
40
73
  }
74
+ /**
75
+ * The run-scoped init failure — why the run never started a step (an
76
+ * unresolvable lock file, a secret it could not read, a gate it never passed).
77
+ * Nothing is printed when the run started normally.
78
+ */
79
+ function printInitFailure(initFailure) {
80
+ if (!initFailure) return;
81
+ console.log(`\n ${pc.yellow("Init failure")} ${pc.gray(`(${initFailure.category})`)}`);
82
+ console.log(` ${initFailure.message}`);
83
+ }
41
84
  function printJobs(jobs) {
42
85
  for (const j of jobs) {
43
86
  console.log(`\n ${pc.bold(j.jobName)} ${colorStatus(j.status)} ` + pc.gray(j.durationMs ? formatDuration$1(j.durationMs) : ""));
87
+ printJobFailure(j);
44
88
  for (const s of j.steps ?? []) console.log(pc.gray(" └─ ") + `${s.stepName} ${colorStatus(s.status)} ` + pc.gray(`${s.durationMs ? formatDuration$1(s.durationMs) : ""}${s.exitCode != null ? ` exit=${s.exitCode}` : ""}`));
45
89
  }
46
90
  }
91
+ /**
92
+ * Why a single job failed: its job-scoped init failure (a job the context rules
93
+ * rejected carries `context_rules` plus the context and rule that rejected it),
94
+ * or its plain error message. The init failure wins when both are present —
95
+ * the two carry the same text on a rejected job, and the category is the part
96
+ * that tells the reader whether a gate or a step failed.
97
+ */
98
+ function printJobFailure(job) {
99
+ if (job.initFailure) {
100
+ console.log(` ${pc.yellow("init failure")} ${pc.gray(`(${job.initFailure.category})`)}: ` + job.initFailure.message);
101
+ return;
102
+ }
103
+ if (job.errorMessage) console.log(` ${pc.red("error")}: ${job.errorMessage}`);
104
+ }
105
+ /** The approval holds recorded against the run, or a note when unreadable. */
106
+ function printHolds(result) {
107
+ if (result.unavailable) {
108
+ if (!result.unavailableSilently) console.log(pc.gray(`\n Approval-hold detail unavailable: ${result.unavailable}`));
109
+ return;
110
+ }
111
+ if (result.holds.length === 0) return;
112
+ console.log(`\n ${pc.bold(`Approval holds (${result.holds.length})`)}`);
113
+ for (const h of result.holds) {
114
+ const parts = [
115
+ h.jobId || "(workflow)",
116
+ h.holdType ?? "—",
117
+ h.contextName ? `context=${h.contextName}` : void 0,
118
+ h.queueType ? `queue=${h.queueType}` : void 0,
119
+ colorStatus(h.status)
120
+ ].filter((p) => p != null);
121
+ console.log(` ${parts.join(" ")}`);
122
+ if (h.reason) console.log(pc.gray(` reason: ${h.reason}`));
123
+ if (h.expiresAt) console.log(pc.gray(` expires: ${relativeTime(h.expiresAt)}`));
124
+ }
125
+ }
47
126
  async function showLocalFallback(runId, json) {
48
127
  const history = new RunHistory();
49
128
  await history.load();
@@ -8,6 +8,32 @@ import fs from "node:fs/promises";
8
8
  import { toErrorMessage } from "@kici-dev/core";
9
9
  //#region src/commands/types.ts
10
10
  /**
11
+ * Error kinds meaning the Platform is genuinely unreachable or the CLI is not
12
+ * configured to reach it — not authenticated, no active org, network down, or
13
+ * orchestrator offline. For these, `kici types` writes a valid empty stub so
14
+ * the declaration file always exists (typecheck degrades to "no known keys"
15
+ * rather than "module has no exported member") instead of failing.
16
+ *
17
+ * An authenticated-but-rejected response (`unauthorized`, `forbidden`, …) is
18
+ * deliberately excluded: that is a real credential/permission problem the user
19
+ * must see, and writing a stub would mask it.
20
+ */
21
+ const UNREACHABLE_ERROR_KINDS = [
22
+ "not_logged_in",
23
+ "no_active_org",
24
+ "orchestrator_offline",
25
+ "http"
26
+ ];
27
+ /** True if `p` exists (any type). Used to avoid clobbering a real snapshot. */
28
+ async function fileExists(p) {
29
+ try {
30
+ await fs.access(p);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ /**
11
37
  * Generate TypeScript declarations for context secrets.
12
38
  *
13
39
  * Fetches context metadata (with secret key names) through the Platform
@@ -18,24 +44,40 @@ import { toErrorMessage } from "@kici-dev/core";
18
44
  * @returns true on success, false on error
19
45
  */
20
46
  async function typesCommand(options = {}) {
47
+ const kiciDir = options.kiciDir ?? ".kici";
48
+ const typesDir = path.join(kiciDir, "types");
49
+ const outputPath = path.join(typesDir, "secrets.d.ts");
21
50
  try {
22
51
  const config = await loadGlobalConfig();
52
+ const metadata = (await DashboardClient.fromConfig(config).listContexts(true)).map((e) => ({
53
+ name: e.name,
54
+ keys: e.secretKeys ?? []
55
+ }));
56
+ const source = config.platformEndpoint ?? config.endpoint ?? "kici Platform";
23
57
  const dtsContent = generateSecretsDts({
24
- contexts: (await DashboardClient.fromConfig(config).listContexts(true)).map((e) => ({
25
- name: e.name,
26
- keys: e.secretKeys ?? []
27
- })),
28
- endpoint: (config.platformEndpoint ?? config.endpoint ?? "kici Platform").replace(/\/+$/, ""),
29
- generatedAt: /* @__PURE__ */ new Date()
58
+ contexts: metadata,
59
+ endpoint: source.replace(/\/+$/, "")
30
60
  });
31
- const kiciDir = options.kiciDir ?? ".kici";
32
- const typesDir = path.join(kiciDir, "types");
33
61
  await fs.mkdir(typesDir, { recursive: true });
34
- const outputPath = path.join(typesDir, "secrets.d.ts");
35
62
  await fs.writeFile(outputPath, dtsContent, "utf-8");
36
63
  if (!options.quiet) console.log(pc.green("Types generated") + pc.dim(` ${outputPath}`));
37
64
  return true;
38
65
  } catch (err) {
66
+ if (err instanceof DashboardClientError && UNREACHABLE_ERROR_KINDS.includes(err.kind)) {
67
+ if (await fileExists(outputPath)) {
68
+ console.error(pc.yellow(`${err.message} Keeping the existing ${outputPath}; run \`kici types\` when authenticated to refresh it.`));
69
+ return true;
70
+ }
71
+ const stub = generateSecretsDts({
72
+ contexts: [],
73
+ endpoint: "",
74
+ offline: true
75
+ });
76
+ await fs.mkdir(typesDir, { recursive: true });
77
+ await fs.writeFile(outputPath, stub, "utf-8");
78
+ console.error(pc.yellow(`${err.message} Wrote an offline type stub to ${outputPath}; run \`kici types\` when authenticated to populate it.`));
79
+ return true;
80
+ }
39
81
  if (err instanceof DashboardClientError) {
40
82
  console.error(pc.red(err.message));
41
83
  return false;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Error code string. Thrown compile errors use `E<digits>` (e.g. 'E001', 'E102');
3
- * advisory, render-only diagnostics use `W<digits>` (e.g. 'W101'). `isCompilerError`
4
- * only matches the `E<digits>` family — warning codes are never thrown, only rendered.
3
+ * advisory, render-only diagnostics use `W<digits>`. `isCompilerError` only
4
+ * matches the `E<digits>` family — warning codes are never thrown, only rendered.
5
5
  */
6
6
  type ErrorCode = string;
7
7
  /** Diagnostic severity. Errors abort compilation; warnings are advisory (compile still succeeds). */
@@ -9,8 +9,6 @@ export declare enum DiagnosticSeverity {
9
9
  Error = "error",
10
10
  Warning = "warning"
11
11
  }
12
- /** Warning code for the impure dynamic-value → init-job fallback. */
13
- export declare const PURITY_FALLBACK_CODE = "W101";
14
12
  /** Source location for error reporting */
15
13
  export interface SourceLocation {
16
14
  readonly file: string;
@@ -7,8 +7,6 @@ let DiagnosticSeverity = /* @__PURE__ */ function(DiagnosticSeverity) {
7
7
  DiagnosticSeverity["Warning"] = "warning";
8
8
  return DiagnosticSeverity;
9
9
  }({});
10
- /** Warning code for the impure dynamic-value → init-job fallback. */
11
- const PURITY_FALLBACK_CODE = "W101";
12
10
  /**
13
11
  * Format error in GNU standard format: file:line:column: error [CODE]: message
14
12
  *
@@ -40,6 +38,6 @@ function isCompilerError(error) {
40
38
  return typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && /^E\d+$/.test(error.code);
41
39
  }
42
40
  //#endregion
43
- export { DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, formatError, isCompilerError };
41
+ export { DiagnosticSeverity, compilerError, formatError, isCompilerError };
44
42
 
45
43
  //# sourceMappingURL=formatter.js.map
@@ -1,4 +1,4 @@
1
- export { formatError, compilerError, isCompilerError, DiagnosticSeverity, PURITY_FALLBACK_CODE, } from './formatter.js';
1
+ export { formatError, compilerError, isCompilerError, DiagnosticSeverity } from './formatter.js';
2
2
  export type { SourceLocation, CompilerError } from './formatter.js';
3
3
  export { CapabilityGapError, formatCapabilityGapError } from './capability-gap.js';
4
4
  export type { CapabilityGapInfo } from './capability-gap.js';
@@ -1,5 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, formatError, isCompilerError } from "./formatter.js";
2
+ import { DiagnosticSeverity, compilerError, formatError, isCompilerError } from "./formatter.js";
3
3
  import { CapabilityGapError, formatCapabilityGapError } from "./capability-gap.js";
4
4
  import { firstStepLocation, locationForJob, locationForWorkflow } from "./source-location.js";
5
- export { CapabilityGapError, DiagnosticSeverity, PURITY_FALLBACK_CODE, compilerError, firstStepLocation, formatCapabilityGapError, formatError, isCompilerError, locationForJob, locationForWorkflow };
5
+ export { CapabilityGapError, DiagnosticSeverity, compilerError, firstStepLocation, formatCapabilityGapError, formatError, isCompilerError, locationForJob, locationForWorkflow };
@@ -19,7 +19,8 @@ import { readFile } from "node:fs/promises";
19
19
  */
20
20
  async function getSdkPathFromPackageJson() {
21
21
  try {
22
- const content = await readFile(path.resolve(process.cwd(), ".kici", "package.json"), "utf-8");
22
+ const pkgPath = path.resolve(process.cwd(), ".kici", "package.json");
23
+ const content = await readFile(pkgPath, "utf-8");
23
24
  const sdkPath = JSON.parse(content).kici?.sdkPath;
24
25
  if (!sdkPath) return null;
25
26
  return path.resolve(sdkPath);
@@ -36,7 +37,8 @@ async function getSdkPathFromPackageJson() {
36
37
  */
37
38
  async function isInKiciRepo() {
38
39
  try {
39
- const content = await readFile(path.resolve(process.cwd(), "package.json"), "utf-8");
40
+ const rootPkgPath = path.resolve(process.cwd(), "package.json");
41
+ const content = await readFile(rootPkgPath, "utf-8");
40
42
  return JSON.parse(content).kici?.development === true;
41
43
  } catch {
42
44
  return false;
@@ -119,7 +119,8 @@ async function ensureRuntimeSymlinks(filePath) {
119
119
  continue;
120
120
  } catch {}
121
121
  try {
122
- const pkgDir = await findPackageRoot(fileURLToPath(import.meta.resolve(pkg)));
122
+ const entry = import.meta.resolve(pkg);
123
+ const pkgDir = await findPackageRoot(fileURLToPath(entry));
123
124
  const linkParent = path.dirname(linkPath);
124
125
  if (linkParent !== nodeModulesDir) await fs.mkdir(linkParent, { recursive: true });
125
126
  await fs.symlink(pkgDir, linkPath);
package/dist/format.js CHANGED
@@ -6,12 +6,12 @@ import "./rolldown-runtime-ClRpJifh.js";
6
6
  function formatRelativeTime(isoDate) {
7
7
  const ms = Date.now() - new Date(isoDate).getTime();
8
8
  if (ms < 0) return "just now";
9
- const minutes = Math.floor(ms / (60 * 1e3));
9
+ const minutes = Math.floor(ms / 6e4);
10
10
  if (minutes < 1) return "just now";
11
11
  if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
12
- const hours = Math.floor(ms / (3600 * 1e3));
12
+ const hours = Math.floor(ms / 36e5);
13
13
  if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
14
- const days = Math.floor(ms / (1440 * 60 * 1e3));
14
+ const days = Math.floor(ms / 864e5);
15
15
  return `${days} day${days === 1 ? "" : "s"} ago`;
16
16
  }
17
17
  //#endregion
@@ -10,10 +10,16 @@ export interface ContextMetadata {
10
10
  name: string;
11
11
  keys: string[];
12
12
  }
13
- interface GenerateSecretsDtsOptions {
13
+ export interface GenerateSecretsDtsOptions {
14
14
  contexts: ContextMetadata[];
15
15
  endpoint: string;
16
- generatedAt: Date;
16
+ /**
17
+ * Mark the output as an offline stub written when the Platform could not be
18
+ * reached (unauthenticated, offline, or no active org). The header names it a
19
+ * stub instead of a source URL so a reader can tell it from a real snapshot,
20
+ * and the caller passes `contexts: []` so it augments no known keys.
21
+ */
22
+ offline?: boolean;
17
23
  }
18
24
  /**
19
25
  * Generate a .d.ts string from context metadata.
@@ -25,5 +31,4 @@ interface GenerateSecretsDtsOptions {
25
31
  * @returns The .d.ts file content as a string
26
32
  */
27
33
  export declare function generateSecretsDts(options: GenerateSecretsDtsOptions): string;
28
- export {};
29
34
  //# sourceMappingURL=secrets-dts.d.ts.map
@@ -34,11 +34,11 @@ function buildKeySourceMap(contexts) {
34
34
  * @returns The .d.ts file content as a string
35
35
  */
36
36
  function generateSecretsDts(options) {
37
- const { contexts, endpoint, generatedAt } = options;
37
+ const { contexts, endpoint, offline } = options;
38
38
  const lines = [];
39
39
  lines.push("// @generated by kici types -- DO NOT EDIT");
40
- lines.push(`// Source: ${endpoint}`);
41
- lines.push(`// Generated: ${generatedAt.toISOString()}`);
40
+ if (offline) lines.push("// Source: offline stub -- Platform unreachable, no known secret keys");
41
+ else lines.push(`// Source: ${endpoint}`);
42
42
  lines.push("// Run `kici types` to refresh");
43
43
  lines.push("");
44
44
  lines.push("declare module '@kici-dev/sdk' {");
@@ -252,7 +252,8 @@ repos:
252
252
  */
253
253
  async function resolveCommonGitDir(gitDir) {
254
254
  try {
255
- const commondir = (await readFile(path.join(gitDir, "commondir"), "utf-8")).trim();
255
+ const commondirPath = path.join(gitDir, "commondir");
256
+ const commondir = (await readFile(commondirPath, "utf-8")).trim();
256
257
  return path.resolve(gitDir, commondir);
257
258
  } catch {
258
259
  return gitDir;