@lchase/kiln 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/redact.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Masks secrets in text Kiln writes to the event log, the ledger, and a
3
+ * pull request body. One function owns that. A log or a PR is public-shaped
4
+ * even when a test command or a check excerpt is not.
5
+ *
6
+ * Token shapes cover the common issued-credential prefixes. A value is also
7
+ * masked when it belongs to an env assignment, a JSON field, or an auth
8
+ * header whose name contains TOKEN, KEY, SECRET, or PASSWORD.
9
+ */
10
+ const SECRET_NAME = /token|key|secret|password/i;
11
+ const TOKEN_SHAPES = [
12
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g,
13
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
14
+ /\bglpat-[A-Za-z0-9\-_]{16,}\b/g,
15
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}/g,
16
+ /\bAKIA[0-9A-Z]{16}\b/g,
17
+ /\bsk-ant-[A-Za-z0-9\-_]{10,}\b/g,
18
+ /\bsk-[A-Za-z0-9]{20,}\b/g,
19
+ /\bnpm_[A-Za-z0-9]{20,}\b/g,
20
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
21
+ ];
22
+ const AUTH_HEADER = /\b(authorization|proxy-authorization|x-api-key|x-auth-token)\s*:[^\n]*/gi;
23
+ const BEARER = /\bBearer\s+[A-Za-z0-9\-._~+/]{8,}={0,2}/g;
24
+ const ENV_ASSIGN = /(^|[\n\r;\s])((?:export[ \t]+)?)([A-Za-z_][A-Za-z0-9_]*)([ \t]*=[ \t]*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s#]+)/g;
25
+ const JSON_FIELD = /"([A-Za-z_][A-Za-z0-9_]*)"(\s*:\s*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g;
26
+ /** Replace secret-shaped text with `[redacted]`. Idempotent on its own output. */
27
+ export function redact(text) {
28
+ let out = text.replace(ENV_ASSIGN, (full, lead, prefix, name, eq, value) => {
29
+ if (!SECRET_NAME.test(name))
30
+ return full;
31
+ return `${lead}${prefix}${name}${eq}[redacted]`;
32
+ });
33
+ out = out.replace(JSON_FIELD, (full, name, mid, value) => {
34
+ if (!SECRET_NAME.test(name))
35
+ return full;
36
+ const quote = value.startsWith("'") ? "'" : '"';
37
+ return `"${name}"${mid}${quote}[redacted]${quote}`;
38
+ });
39
+ out = out.replace(AUTH_HEADER, (_full, name) => `${name}: [redacted]`);
40
+ out = out.replace(BEARER, "Bearer [redacted]");
41
+ for (const pattern of TOKEN_SHAPES) {
42
+ out = out.replace(pattern, "[redacted]");
43
+ }
44
+ return out;
45
+ }
46
+ /** Walk a JSON value and redact every string. Object keys that name a secret drop the value. */
47
+ export function redactDeep(value) {
48
+ if (typeof value === "string")
49
+ return redact(value);
50
+ if (typeof value !== "object" || value === null)
51
+ return value;
52
+ if (Array.isArray(value))
53
+ return value.map((item) => redactDeep(item));
54
+ const out = {};
55
+ for (const [key, inner] of Object.entries(value)) {
56
+ if (typeof inner === "string" && SECRET_NAME.test(key)) {
57
+ out[key] = "[redacted]";
58
+ continue;
59
+ }
60
+ out[key] = redactDeep(inner);
61
+ }
62
+ return out;
63
+ }
@@ -4,6 +4,7 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync
4
4
  import { dirname, join, resolve, sep } from "node:path";
5
5
  import { z } from "zod";
6
6
  import { authenticationFailureLine } from "./auth.js";
7
+ import { appendEvent } from "./events.js";
7
8
  import { jobBasename } from "./paths.js";
8
9
  import { FAILURE_REASONS } from "./reconcile.js";
9
10
  /**
@@ -105,6 +106,17 @@ export function recoverRuns(config, jobs, options) {
105
106
  const recorded = recordFailure(config, job.jobId, existing, pending, options.now);
106
107
  if (recorded.created) {
107
108
  appendLog(job.logPath, recorded.ledger.attempts.at(-1));
109
+ const runId = pending.runId ?? job.run?.runId;
110
+ appendEvent(config.kilnDir, {
111
+ job_id: job.jobId,
112
+ run_id: runId ?? null,
113
+ event: "run.failed",
114
+ properties: {
115
+ reason: pending.reason,
116
+ ...(pending.detail ? { detail: pending.detail } : {}),
117
+ once: runId ? `run.failed:${runId}` : `run.failed:launch:${recorded.ledger.attempts.length}`,
118
+ },
119
+ });
108
120
  notes.push({
109
121
  jobId: job.jobId,
110
122
  kind: kindFor(pending.reason),
@@ -115,8 +127,17 @@ export function recoverRuns(config, jobs, options) {
115
127
  }
116
128
  const checkout = job.run?.checkout ?? config.repos[job.repo]?.path;
117
129
  const runId = pending.runId ?? job.run?.runId;
118
- if (checkout && runId)
119
- cleanupRunWorktree(checkout, runId);
130
+ if (checkout && runId) {
131
+ const outcome = cleanupRunWorktree(checkout, runId);
132
+ if (outcome === "removed" || outcome === "kept") {
133
+ appendEvent(config.kilnDir, {
134
+ job_id: job.jobId,
135
+ run_id: runId,
136
+ event: outcome === "removed" ? "worktree.cleaned" : "worktree.kept",
137
+ properties: { once: `worktree.${outcome}:${runId}` },
138
+ });
139
+ }
140
+ }
120
141
  }
121
142
  return notes;
122
143
  }
package/dist/serve.js CHANGED
@@ -7,8 +7,9 @@ import { AUTH_PREFLIGHT_PREFIX } from "./auth.js";
7
7
  import { loadKilnConfig } from "./config.js";
8
8
  import { answerJob, readStatus, retryJob, runTick, startupRecovery } from "./driver.js";
9
9
  import { browse } from "./fs-browse.js";
10
- import { defaultHost } from "./host.js";
10
+ import { appendEvent, eventsForJob } from "./events.js";
11
11
  import { INVESTIGATION_WORKFLOW } from "./findings.js";
12
+ import { defaultHost } from "./host.js";
12
13
  import { intakeJobs } from "./intake-http.js";
13
14
  import { packageRoot } from "./paths.js";
14
15
  import { SANDBOX_PREFLIGHT_PREFIX } from "./sandbox.js";
@@ -189,6 +190,15 @@ async function handle(req, res, state, boundPort, css, csrf) {
189
190
  sendJson(res, 200, statusDocument(state), secrets);
190
191
  return;
191
192
  }
193
+ if (path === "/api/events") {
194
+ const jobId = url.searchParams.get("job_id");
195
+ if (!jobId || !JOB_ID.test(jobId)) {
196
+ sendJson(res, 400, { error: "job_id is required." }, secrets);
197
+ return;
198
+ }
199
+ sendJson(res, 200, { job_id: jobId, events: eventsForJob(config.kilnDir, jobId) }, secrets);
200
+ return;
201
+ }
192
202
  const apiJob = /^\/api\/jobs\/([^/]+)$/.exec(path);
193
203
  if (apiJob?.[1]) {
194
204
  const jobId = parseJobId(apiJob[1]);
@@ -503,6 +513,11 @@ function refreshLiveConfig(state) {
503
513
  state.configError = undefined;
504
514
  state.mtimeMs = file.mtimeMs;
505
515
  state.size = file.size;
516
+ appendEvent(next.kilnDir, {
517
+ job_id: null,
518
+ event: "config.reloaded",
519
+ properties: {},
520
+ });
506
521
  }
507
522
  catch (error) {
508
523
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lchase/kiln",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Run a workflow on any registered repo. A markdown spec is the input.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Lawrence Chase
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.