@koda-sl/baker-bridge 0.68.1 → 0.68.3
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/README.md +20 -5
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +4 -23
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +69 -50
- package/dist/hono/agent.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +8 -2
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +3 -12
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/openrouter-session.d.ts +29 -0
- package/dist/hono/openrouter-session.d.ts.map +1 -0
- package/dist/hono/openrouter-session.js +48 -0
- package/dist/hono/openrouter-session.js.map +1 -0
- package/dist/hono/openrouter-session.test.d.ts +2 -0
- package/dist/hono/openrouter-session.test.d.ts.map +1 -0
- package/dist/hono/openrouter-session.test.js +37 -0
- package/dist/hono/openrouter-session.test.js.map +1 -0
- package/dist/hono/slash-command-allowlist.d.ts +5 -0
- package/dist/hono/slash-command-allowlist.d.ts.map +1 -0
- package/dist/hono/slash-command-allowlist.js +42 -0
- package/dist/hono/slash-command-allowlist.js.map +1 -0
- package/dist/hono/slash-command-allowlist.test.d.ts +2 -0
- package/dist/hono/slash-command-allowlist.test.d.ts.map +1 -0
- package/dist/hono/slash-command-allowlist.test.js +44 -0
- package/dist/hono/slash-command-allowlist.test.js.map +1 -0
- package/dist/hono/slash-commands.d.ts +2 -0
- package/dist/hono/slash-commands.d.ts.map +1 -1
- package/dist/hono/slash-commands.js +4 -0
- package/dist/hono/slash-commands.js.map +1 -1
- package/package.json +5 -6
- package/dist/hono/plugins.d.ts +0 -3
- package/dist/hono/plugins.d.ts.map +0 -1
- package/dist/hono/plugins.js +0 -7
- package/dist/hono/plugins.js.map +0 -1
- package/dist/hono/plugins.test.d.ts +0 -2
- package/dist/hono/plugins.test.d.ts.map +0 -1
- package/dist/hono/plugins.test.js +0 -98
- package/dist/hono/plugins.test.js.map +0 -1
- package/plugins/reports/.claude-plugin/plugin.json +0 -7
- package/plugins/reports/skills/build/SKILL.md +0 -73
- package/plugins/reports/skills/build/references/report-contracts.md +0 -103
- package/plugins/reports/skills/build/scripts/report-paths.mjs +0 -19
- package/plugins/reports/skills/build/scripts/scaffold-report.mjs +0 -108
- package/plugins/reports/skills/build/templates/report-definition.md +0 -22
- package/plugins/reports/skills/build/templates/report-index.html +0 -20
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-session.d.ts","sourceRoot":"","sources":["../../src/hono/openrouter-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAmBvD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter sticky-routing wiring for the agent's model calls.
|
|
3
|
+
*
|
|
4
|
+
* The agent reaches the model through OpenRouter's Anthropic-compatible
|
|
5
|
+
* passthrough (`ANTHROPIC_BASE_URL`), so the Claude Code CLI owns the request
|
|
6
|
+
* body and we cannot add OpenRouter fields to it directly. The CLI merges the
|
|
7
|
+
* JSON in `CLAUDE_CODE_EXTRA_BODY` into every model request body — that is our
|
|
8
|
+
* one injection point.
|
|
9
|
+
*
|
|
10
|
+
* Passing a stable per-conversation `session_id` pins the whole conversation to
|
|
11
|
+
* one upstream provider instance. OpenRouter's prompt cache is instance-scoped
|
|
12
|
+
* and its default sticky routing keys on the opening messages; those shift every
|
|
13
|
+
* turn on a live agent session (growing history, auto-compaction, injected
|
|
14
|
+
* context), so without a stable `session_id` OpenRouter re-routes across (e.g.)
|
|
15
|
+
* Vertex instances and the cache is never found — the observed ~2% hit rate on
|
|
16
|
+
* long sessions versus an ~88% account average. A stable `session_id` keeps the
|
|
17
|
+
* conversation on its warm instance "even if the opening messages change".
|
|
18
|
+
*
|
|
19
|
+
* Reference: OpenRouter → Prompt Caching (best practices) / Provider Selection.
|
|
20
|
+
* `session_id` alone does NOT disable fallbacks, so provider resilience is kept.
|
|
21
|
+
*/
|
|
22
|
+
export const EXTRA_BODY_ENV = "CLAUDE_CODE_EXTRA_BODY";
|
|
23
|
+
function parseInheritedExtraBody(inherited) {
|
|
24
|
+
if (!inherited) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(inherited);
|
|
29
|
+
// Only a plain object can be merged; arrays and scalars are dropped so a
|
|
30
|
+
// malformed inherited value can never strip our `session_id` pin.
|
|
31
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// A bad inherited value must never fail the turn — fall through to the pin.
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the `CLAUDE_CODE_EXTRA_BODY` JSON that pins this conversation to one
|
|
42
|
+
* OpenRouter provider instance. Our per-thread `session_id` is authoritative and
|
|
43
|
+
* overrides any inherited one; other inherited fields (e.g. `provider`) are kept.
|
|
44
|
+
*/
|
|
45
|
+
export function buildAgentExtraBody(threadId, inherited) {
|
|
46
|
+
return JSON.stringify({ ...parseInheritedExtraBody(inherited), session_id: threadId });
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=openrouter-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-session.js","sourceRoot":"","sources":["../../src/hono/openrouter-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEvD,SAAS,uBAAuB,CAAC,SAA6B;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,yEAAyE;QACzE,kEAAkE;QAClE,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,MAAiC,CAAC;QAC3C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;IAC9E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,SAAkB;IACtE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,uBAAuB,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;AACzF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-session.test.d.ts","sourceRoot":"","sources":["../../src/hono/openrouter-session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildAgentExtraBody, EXTRA_BODY_ENV } from "./openrouter-session.js";
|
|
3
|
+
describe("buildAgentExtraBody", () => {
|
|
4
|
+
it("pins the conversation with a stable session_id equal to the thread id", () => {
|
|
5
|
+
const body = JSON.parse(buildAgentExtraBody("thread_abc123"));
|
|
6
|
+
expect(body.session_id).toBe("thread_abc123");
|
|
7
|
+
});
|
|
8
|
+
it("is deterministic for the same thread so it never busts the prefix itself", () => {
|
|
9
|
+
expect(buildAgentExtraBody("thread_abc123")).toBe(buildAgentExtraBody("thread_abc123"));
|
|
10
|
+
});
|
|
11
|
+
it("differs per conversation so two threads route independently", () => {
|
|
12
|
+
expect(buildAgentExtraBody("thread_a")).not.toBe(buildAgentExtraBody("thread_b"));
|
|
13
|
+
});
|
|
14
|
+
it("merges an inherited extra-body value instead of dropping it", () => {
|
|
15
|
+
const inherited = JSON.stringify({ provider: { order: ["google-vertex"] } });
|
|
16
|
+
const body = JSON.parse(buildAgentExtraBody("thread_abc123", inherited));
|
|
17
|
+
expect(body.provider).toEqual({ order: ["google-vertex"] });
|
|
18
|
+
expect(body.session_id).toBe("thread_abc123");
|
|
19
|
+
});
|
|
20
|
+
it("wins over an inherited session_id so our per-thread pin is authoritative", () => {
|
|
21
|
+
const inherited = JSON.stringify({ session_id: "stale-session" });
|
|
22
|
+
const body = JSON.parse(buildAgentExtraBody("thread_abc123", inherited));
|
|
23
|
+
expect(body.session_id).toBe("thread_abc123");
|
|
24
|
+
});
|
|
25
|
+
it("ignores an unparseable inherited value rather than failing the turn", () => {
|
|
26
|
+
const body = JSON.parse(buildAgentExtraBody("thread_abc123", "{not json"));
|
|
27
|
+
expect(body.session_id).toBe("thread_abc123");
|
|
28
|
+
});
|
|
29
|
+
it("ignores an inherited non-object (e.g. a JSON array or scalar)", () => {
|
|
30
|
+
const body = JSON.parse(buildAgentExtraBody("thread_abc123", "[1,2,3]"));
|
|
31
|
+
expect(body).toEqual({ session_id: "thread_abc123" });
|
|
32
|
+
});
|
|
33
|
+
it("exposes the CLI env-var name the value is injected under", () => {
|
|
34
|
+
expect(EXTRA_BODY_ENV).toBe("CLAUDE_CODE_EXTRA_BODY");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=openrouter-session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-session.test.js","sourceRoot":"","sources":["../../src/hono/openrouter-session.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SlashCommand } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/** Names the sandbox picker is allowed to show: the workspace's own skills plus the bridge's own commands. */
|
|
3
|
+
export declare function readAllowedSlashCommandNames(projectRoot: string): Promise<Set<string>>;
|
|
4
|
+
export declare function filterAllowedSlashCommands(commands: SlashCommand[], allowedNames: ReadonlySet<string>): SlashCommand[];
|
|
5
|
+
//# sourceMappingURL=slash-command-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-command-allowlist.d.ts","sourceRoot":"","sources":["../../src/hono/slash-command-allowlist.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AA8BnE,8GAA8G;AAC9G,wBAAsB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAU5F;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,YAAY,EAAE,EACxB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,YAAY,EAAE,CAEhB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { bridgeSlashCommandNames } from "./slash-commands.js";
|
|
5
|
+
// Format renderers that the document-build skill routes to on the user's behalf.
|
|
6
|
+
// They are real workspace skills, but nobody invokes them by name.
|
|
7
|
+
const ROUTED_ONLY_SKILLS = new Set(["docx", "pdf", "pptx", "xlsx"]);
|
|
8
|
+
const errnoSchema = z.object({ code: z.string() });
|
|
9
|
+
/**
|
|
10
|
+
* Directory names are the command names: the SDK names a command from SKILL.md
|
|
11
|
+
* frontmatter, and scripts/check-skills.mjs fails the build when that `name`
|
|
12
|
+
* differs from its directory, so reading the directory avoids parsing 24 files.
|
|
13
|
+
*
|
|
14
|
+
* An absent directory means "no skills"; any other failure must propagate so a
|
|
15
|
+
* stale-but-good list is kept.
|
|
16
|
+
*/
|
|
17
|
+
async function readSkillDirNames(skillsDir) {
|
|
18
|
+
try {
|
|
19
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
20
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (errnoSchema.safeParse(error).data?.code === "ENOENT") {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Names the sandbox picker is allowed to show: the workspace's own skills plus the bridge's own commands. */
|
|
30
|
+
export async function readAllowedSlashCommandNames(projectRoot) {
|
|
31
|
+
const allowed = new Set(bridgeSlashCommandNames());
|
|
32
|
+
for (const name of await readSkillDirNames(join(projectRoot, ".claude", "skills"))) {
|
|
33
|
+
if (!ROUTED_ONLY_SKILLS.has(name)) {
|
|
34
|
+
allowed.add(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return allowed;
|
|
38
|
+
}
|
|
39
|
+
export function filterAllowedSlashCommands(commands, allowedNames) {
|
|
40
|
+
return commands.filter((command) => allowedNames.has(command.name));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=slash-command-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-command-allowlist.js","sourceRoot":"","sources":["../../src/hono/slash-command-allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,iFAAiF;AACjF,mEAAmE;AACnE,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzF,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEnD;;;;;;;GAOG;AACH,KAAK,UAAU,iBAAiB,CAAC,SAAiB;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,8GAA8G;AAC9G,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,WAAmB;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,uBAAuB,EAAE,CAAC,CAAC;IAE3D,KAAK,MAAM,IAAI,IAAI,MAAM,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QACnF,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,QAAwB,EACxB,YAAiC;IAEjC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-command-allowlist.test.d.ts","sourceRoot":"","sources":["../../src/hono/slash-command-allowlist.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { filterAllowedSlashCommands, readAllowedSlashCommandNames } from "./slash-command-allowlist.js";
|
|
6
|
+
async function makeProjectRoot(skillNames) {
|
|
7
|
+
const root = await mkdtemp(join(tmpdir(), "baker-allowlist-"));
|
|
8
|
+
for (const name of skillNames) {
|
|
9
|
+
await mkdir(join(root, ".claude", "skills", name), { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
return root;
|
|
12
|
+
}
|
|
13
|
+
const command = (name) => ({ name, description: name, argumentHint: "" });
|
|
14
|
+
describe("filterAllowedSlashCommands", () => {
|
|
15
|
+
it("drops commands whose name is not allowed", () => {
|
|
16
|
+
const landing = command("landing");
|
|
17
|
+
const commands = [command("model"), command("clear"), command("doctor"), landing];
|
|
18
|
+
expect(filterAllowedSlashCommands(commands, new Set(["landing"]))).toEqual([landing]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("readAllowedSlashCommandNames", () => {
|
|
22
|
+
it("allows workspace skills found on disk plus the bridge's own commands", async () => {
|
|
23
|
+
const projectRoot = await makeProjectRoot(["landing", "brand-build"]);
|
|
24
|
+
const allowed = await readAllowedSlashCommandNames(projectRoot);
|
|
25
|
+
expect(allowed).toEqual(new Set(["landing", "brand-build", "compact", "publish"]));
|
|
26
|
+
});
|
|
27
|
+
it("hides workspace skills the user never invokes directly", async () => {
|
|
28
|
+
const projectRoot = await makeProjectRoot(["landing", "docx", "pdf", "pptx", "xlsx"]);
|
|
29
|
+
const allowed = await readAllowedSlashCommandNames(projectRoot);
|
|
30
|
+
expect(allowed).toEqual(new Set(["landing", "compact", "publish"]));
|
|
31
|
+
});
|
|
32
|
+
it("fails closed when the skills directory is missing", async () => {
|
|
33
|
+
const projectRoot = await mkdtemp(join(tmpdir(), "baker-no-skills-"));
|
|
34
|
+
const allowed = await readAllowedSlashCommandNames(projectRoot);
|
|
35
|
+
expect(allowed).toEqual(new Set(["compact", "publish"]));
|
|
36
|
+
});
|
|
37
|
+
it("rejects when the skills directory cannot be read for a reason other than absence", async () => {
|
|
38
|
+
const projectRoot = await mkdtemp(join(tmpdir(), "baker-unreadable-skills-"));
|
|
39
|
+
await mkdir(join(projectRoot, ".claude"), { recursive: true });
|
|
40
|
+
await writeFile(join(projectRoot, ".claude", "skills"), "not a directory");
|
|
41
|
+
await expect(readAllowedSlashCommandNames(projectRoot)).rejects.toThrow();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=slash-command-allowlist.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-command-allowlist.test.js","sourceRoot":"","sources":["../../src/hono/slash-command-allowlist.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ export declare enum BridgeSlashCommands {
|
|
|
2
2
|
Publish = "/publish",
|
|
3
3
|
Compact = "/compact"
|
|
4
4
|
}
|
|
5
|
+
/** Bare names (no leading slash) of the commands the bridge itself handles. */
|
|
6
|
+
export declare function bridgeSlashCommandNames(): string[];
|
|
5
7
|
export declare function verbatimSlashCommand(prompt: string): BridgeSlashCommands | null;
|
|
6
8
|
export declare function isVerbatimSlashCommandPrompt(prompt: string): boolean;
|
|
7
9
|
export declare function isPublishCommandPrompt(prompt: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../src/hono/slash-commands.ts"],"names":[],"mappings":"AAAA,oBAAY,mBAAmB;IAC7B,OAAO,aAAa;IACpB,OAAO,aAAa;CACrB;AASD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAG/E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D"}
|
|
1
|
+
{"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../src/hono/slash-commands.ts"],"names":[],"mappings":"AAAA,oBAAY,mBAAmB;IAC7B,OAAO,aAAa;IACpB,OAAO,aAAa;CACrB;AASD,+EAA+E;AAC/E,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAG/E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D"}
|
|
@@ -9,6 +9,10 @@ export var BridgeSlashCommands;
|
|
|
9
9
|
// rehydration — any of which would push the command off the front and turn it
|
|
10
10
|
// into ordinary message text the model just echoes.
|
|
11
11
|
const VERBATIM_COMMANDS = [BridgeSlashCommands.Publish, BridgeSlashCommands.Compact];
|
|
12
|
+
/** Bare names (no leading slash) of the commands the bridge itself handles. */
|
|
13
|
+
export function bridgeSlashCommandNames() {
|
|
14
|
+
return VERBATIM_COMMANDS.map((command) => command.replace("/", ""));
|
|
15
|
+
}
|
|
12
16
|
export function verbatimSlashCommand(prompt) {
|
|
13
17
|
const trimmedPrompt = prompt.trim();
|
|
14
18
|
return VERBATIM_COMMANDS.find((command) => command === trimmedPrompt) ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/hono/slash-commands.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,mBAGX;AAHD,WAAY,mBAAmB;IAC7B,2CAAoB,CAAA;IACpB,2CAAoB,CAAA;AACtB,CAAC,EAHW,mBAAmB,KAAnB,mBAAmB,QAG9B;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,oDAAoD;AACpD,MAAM,iBAAiB,GAAmC,CAAC,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAErH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IACpC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,aAAa,CAAC,IAAI,IAAI,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,MAAc;IACzD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,mBAAmB,CAAC,OAAO,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,mBAAmB,CAAC,OAAO,CAAC;AACtE,CAAC"}
|
|
1
|
+
{"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/hono/slash-commands.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,mBAGX;AAHD,WAAY,mBAAmB;IAC7B,2CAAoB,CAAA;IACpB,2CAAoB,CAAA;AACtB,CAAC,EAHW,mBAAmB,KAAnB,mBAAmB,QAG9B;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,oDAAoD;AACpD,MAAM,iBAAiB,GAAmC,CAAC,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAErH,+EAA+E;AAC/E,MAAM,UAAU,uBAAuB;IACrC,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IACpC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,aAAa,CAAC,IAAI,IAAI,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,MAAc;IACzD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,mBAAmB,CAAC,OAAO,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,oBAAoB,CAAC,MAAM,CAAC,KAAK,mBAAmB,CAAC,OAAO,CAAC;AACtE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koda-sl/baker-bridge",
|
|
3
|
-
"version": "0.68.
|
|
3
|
+
"version": "0.68.3",
|
|
4
4
|
"description": "HTTP server wrapping the Claude Agent SDK. Persists chat events to Convex; the dashboard subscribes via Convex realtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
18
|
-
"plugins"
|
|
17
|
+
"dist"
|
|
19
18
|
],
|
|
20
19
|
"engines": {
|
|
21
20
|
"node": ">=18"
|
|
@@ -38,9 +37,9 @@
|
|
|
38
37
|
"typecheck": "tsc --noEmit",
|
|
39
38
|
"link:local": "bash scripts/link-local.sh",
|
|
40
39
|
"unlink:local": "bash scripts/unlink-local.sh",
|
|
41
|
-
"lint": "biome check src scripts
|
|
42
|
-
"format:check": "biome format src scripts
|
|
43
|
-
"format": "biome format --write src scripts
|
|
40
|
+
"lint": "biome check src scripts vitest.config.mts",
|
|
41
|
+
"format:check": "biome format src scripts vitest.config.mts",
|
|
42
|
+
"format": "biome format --write src scripts vitest.config.mts"
|
|
44
43
|
},
|
|
45
44
|
"dependencies": {
|
|
46
45
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
package/dist/hono/plugins.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/hono/plugins.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,wBAAgB,YAAY,IAAI,eAAe,EAAE,CAEhD"}
|
package/dist/hono/plugins.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
const bridgePackageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
4
|
-
export function bakerPlugins() {
|
|
5
|
-
return [{ type: "local", path: join(bridgePackageRoot, "plugins", "reports") }];
|
|
6
|
-
}
|
|
7
|
-
//# sourceMappingURL=plugins.js.map
|
package/dist/hono/plugins.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/hono/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEpF,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.test.d.ts","sourceRoot":"","sources":["../../src/hono/plugins.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
12
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
-
import { describe, expect, it } from "vitest";
|
|
14
|
-
import { bakerPlugins } from "./plugins.js";
|
|
15
|
-
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
16
|
-
const repoRoot = join(packageRoot, "..", "..");
|
|
17
|
-
const reportsRoot = join(packageRoot, "plugins", "reports");
|
|
18
|
-
const skillRoot = join(reportsRoot, "skills", "build");
|
|
19
|
-
function reportsManifest() {
|
|
20
|
-
return JSON.parse(readFileSync(join(reportsRoot, ".claude-plugin", "plugin.json"), "utf8"));
|
|
21
|
-
}
|
|
22
|
-
function bridgePackageFiles() {
|
|
23
|
-
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
24
|
-
return Array.isArray(packageJson.files) ? packageJson.files.filter((file) => typeof file === "string") : [];
|
|
25
|
-
}
|
|
26
|
-
describe("bakerPlugins", () => {
|
|
27
|
-
it("resolves the reports plugin from the bundled Bridge plugins", () => {
|
|
28
|
-
const plugins = bakerPlugins();
|
|
29
|
-
expect(plugins).toHaveLength(1);
|
|
30
|
-
expect(plugins[0]).toEqual({ type: "local", path: reportsRoot });
|
|
31
|
-
expect(existsSync(plugins[0]?.path ?? "")).toBe(true);
|
|
32
|
-
expect(reportsManifest().name).toBe("reports");
|
|
33
|
-
});
|
|
34
|
-
it("declares the reports plugin metadata and /reports:build command", () => {
|
|
35
|
-
const manifest = reportsManifest();
|
|
36
|
-
const skill = readFileSync(join(skillRoot, "SKILL.md"), "utf8");
|
|
37
|
-
const skillName = skill.match(/^name: (.+)$/m)?.[1];
|
|
38
|
-
expect(manifest).toMatchObject({
|
|
39
|
-
name: "reports",
|
|
40
|
-
displayName: "Baker Reports",
|
|
41
|
-
version: "0.1.0",
|
|
42
|
-
description: "Build and repair Baker company Reports.",
|
|
43
|
-
keywords: ["baker", "reports", "marketing"],
|
|
44
|
-
});
|
|
45
|
-
expect(`${manifest.name}:${skillName}`).toBe("reports:build");
|
|
46
|
-
});
|
|
47
|
-
it("includes the full reports capability bundle", () => {
|
|
48
|
-
expect(existsSync(join(skillRoot, "references", "report-contracts.md"))).toBe(true);
|
|
49
|
-
expect(existsSync(join(skillRoot, "scripts", "scaffold-report.mjs"))).toBe(true);
|
|
50
|
-
expect(existsSync(join(skillRoot, "scripts", "report-paths.mjs"))).toBe(true);
|
|
51
|
-
expect(existsSync(join(skillRoot, "templates", "report-definition.md"))).toBe(true);
|
|
52
|
-
expect(existsSync(join(skillRoot, "templates", "report-index.html"))).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
it("includes the bundled reports plugin payload in Bridge package files", () => {
|
|
55
|
-
expect(bridgePackageFiles()).toContain("plugins");
|
|
56
|
-
expect(existsSync(join(reportsRoot, ".claude-plugin", "plugin.json"))).toBe(true);
|
|
57
|
-
expect(existsSync(join(skillRoot, "SKILL.md"))).toBe(true);
|
|
58
|
-
expect(existsSync(join(skillRoot, "references", "report-contracts.md"))).toBe(true);
|
|
59
|
-
expect(existsSync(join(skillRoot, "scripts", "scaffold-report.mjs"))).toBe(true);
|
|
60
|
-
expect(existsSync(join(skillRoot, "scripts", "report-paths.mjs"))).toBe(true);
|
|
61
|
-
expect(existsSync(join(skillRoot, "templates", "report-definition.md"))).toBe(true);
|
|
62
|
-
expect(existsSync(join(skillRoot, "templates", "report-index.html"))).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
it("keeps the vendored scaffold path contract in sync", async () => {
|
|
65
|
-
const pluginContract = (await import(__rewriteRelativeImportExtension(pathToFileURL(join(skillRoot, "scripts", "report-paths.mjs")).href)));
|
|
66
|
-
const scaffoldContract = (await import(__rewriteRelativeImportExtension(pathToFileURL(join(repoRoot, "apps", "scaffold-template", "__tooling__", "watcher", "base", "report-contract.mjs")).href)));
|
|
67
|
-
expect(pluginContract.ReportFileContract.isValidSlug("weekly-summary")).toBe(scaffoldContract.ReportFileContract.isValidSlug("weekly-summary"));
|
|
68
|
-
expect(pluginContract.ReportFileContract.isValidSlug("Weekly Summary")).toBe(scaffoldContract.ReportFileContract.isValidSlug("Weekly Summary"));
|
|
69
|
-
expect(pluginContract.ReportFileContract.titleFromSlug("weekly-summary")).toBe(scaffoldContract.ReportFileContract.titleFromSlug("weekly-summary"));
|
|
70
|
-
expect(pluginContract.ReportFileContract.paths("weekly-summary")).toEqual(scaffoldContract.ReportFileContract.paths("weekly-summary"));
|
|
71
|
-
});
|
|
72
|
-
it("scaffolds report files in a company repo root", async () => {
|
|
73
|
-
const tempDir = mkdtempSync(join(tmpdir(), "baker-reports-plugin-"));
|
|
74
|
-
try {
|
|
75
|
-
const script = (await import(__rewriteRelativeImportExtension(pathToFileURL(join(skillRoot, "scripts", "scaffold-report.mjs")).href)));
|
|
76
|
-
const result = script.createReportFiles("weekly-summary", tempDir);
|
|
77
|
-
expect(result.slug).toBe("weekly-summary");
|
|
78
|
-
expect(result.files).toEqual([
|
|
79
|
-
join(tempDir, "reports/server/routes/reports/data/weekly-summary.get.ts"),
|
|
80
|
-
join(tempDir, "reports/public/weekly-summary/index.html"),
|
|
81
|
-
join(tempDir, "reports/public/weekly-summary/_definition.md"),
|
|
82
|
-
]);
|
|
83
|
-
const endpointPath = join(tempDir, "reports/server/routes/reports/data/weekly-summary.get.ts");
|
|
84
|
-
const dashboardPath = join(tempDir, "reports/public/weekly-summary/index.html");
|
|
85
|
-
const definitionPath = join(tempDir, "reports/public/weekly-summary/_definition.md");
|
|
86
|
-
expect(existsSync(endpointPath)).toBe(true);
|
|
87
|
-
expect(existsSync(dashboardPath)).toBe(true);
|
|
88
|
-
expect(existsSync(definitionPath)).toBe(true);
|
|
89
|
-
expect(readFileSync(dashboardPath, "utf8")).not.toMatch(/__[A-Z0-9_]+__/);
|
|
90
|
-
expect(readFileSync(definitionPath, "utf8")).toContain("/reports/data/weekly-summary");
|
|
91
|
-
expect(readFileSync(definitionPath, "utf8")).not.toMatch(/__[A-Z0-9_]+__/);
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
//# sourceMappingURL=plugins.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.test.js","sourceRoot":"","sources":["../../src/hono/plugins.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: build
|
|
3
|
-
description: Create or edit company Reports in the current workspace. Use it when user ask for reports, dashboards.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Reports
|
|
7
|
-
|
|
8
|
-
Reports are internal dashboard/sandbox live assets authored in `reports/` from the Company Repo root. A company Report is first-class backend state; scaffold files are the authored source that the watcher upserts. The sandbox builds Reports through the locked build wrapper and serves promoted built Nitro output on port `3001`; do not use or fall back to `nitro dev`.
|
|
9
|
-
|
|
10
|
-
## Start here
|
|
11
|
-
|
|
12
|
-
1. Clarify the business question and reader.
|
|
13
|
-
2. If the domain is specialized, consult the relevant domain/playbook skill before choosing metrics.
|
|
14
|
-
3. Read `src/brand/BRAND.md` and make the dashboard match the client brand. If it is empty, use neutral defaults and leave a TODO comment.
|
|
15
|
-
4. Create new reports with the scaffold script bundled with this skill. Resolve `scripts/scaffold-report.mjs` from this `SKILL.md` file's directory, then run it from the Company Repo root:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
node /absolute/path/to/this/skill/scripts/scaffold-report.mjs <slug>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
5. Replace the generated placeholder `index.html` with the real Report Dashboard. Author HTML/CSS/JS as a real file; do not pass report HTML through a JS function/string API.
|
|
22
|
-
6. Edit only the generated report files unless the user explicitly asks for infrastructure work.
|
|
23
|
-
|
|
24
|
-
## Report files
|
|
25
|
-
|
|
26
|
-
Each report slug owns exactly these authored files:
|
|
27
|
-
|
|
28
|
-
- `reports/server/routes/reports/data/{slug}.get.ts` - JSON endpoint.
|
|
29
|
-
- `reports/public/{slug}/index.html` - HTML dashboard rendered by the generic report route.
|
|
30
|
-
- `reports/public/{slug}/_definition.md` - metadata contract watched by backend upsert.
|
|
31
|
-
|
|
32
|
-
Do not edit report runtime infrastructure (`nitro.config.ts`, generic routes, utils, package config) for normal report creation.
|
|
33
|
-
|
|
34
|
-
## V1 contracts
|
|
35
|
-
|
|
36
|
-
Read `references/report-contracts.md` before creating or repairing endpoint/refresh/metadata behavior.
|
|
37
|
-
|
|
38
|
-
Core rules:
|
|
39
|
-
|
|
40
|
-
- Status values: `draft`, `active`, `error`, `archived`.
|
|
41
|
-
- `_definition.md` frontmatter is the metadata source for title, description, status, and optional `errorMessage`.
|
|
42
|
-
- Set `status: active` only after the dashboard, endpoint, metadata, and live smoke test pass.
|
|
43
|
-
- Endpoints return `{ ok: true, data, meta }` or `{ ok: false, error }`.
|
|
44
|
-
- Use `runReportBaker(event, args)` for Baker CLI reads that support no-cache refresh, or `runBaker(args, { noCache })` with the v1 `fresh=true` convention when orchestration requires it.
|
|
45
|
-
- For new or repaired Google Ads query rows, adapt Baker's flat dotted keys with `googleAdsRow(raw)` before returning report data.
|
|
46
|
-
- HTML pages should fetch `/reports/data/{slug}` normally; never fetch `/reports/{slug}` for JSON because that route serves the authored HTML dashboard. The generic `/reports/{slug}/` route injects the Refresh control and propagates `?fresh=true` to same-origin report fetches.
|
|
47
|
-
- Refresh invalidation is whole-report: the injected Refresh control reloads `/reports/{slug}/?fresh=true`, the runtime propagates `fresh=true` to same-origin `/reports/data/{slug}` fetches, and `runReportBaker(...)` passes `--no-cache` to Baker CLI. HTTP responses stay `no-store`; the Baker CLI cache is the cache being bypassed.
|
|
48
|
-
- Reports are not public generated-site deployment output in v1.
|
|
49
|
-
|
|
50
|
-
## Validate
|
|
51
|
-
|
|
52
|
-
For report changes, run the scaffold verification gate from the Company Repo root:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
pnpm run verify
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Do not invoke `__tooling__/lefthook/base/validate-reports.mjs` directly. `pnpm run verify` is the supported contract; it runs report contract validation from the correct root along with typecheck and Nitro build checks.
|
|
59
|
-
|
|
60
|
-
`verify` proves contracts, typecheck, and Nitro build. It does not prove live data correctness. For a new or active report, smoke-test the built runtime before claiming success:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
node __tooling__/scripts/smoke-reports-runtime.mjs --slug {slug}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
In a running company sandbox, the supervisor serves the built Reports runtime at `http://localhost:3001`; you may also smoke the live URLs directly:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
curl -fsS http://localhost:3001/reports/{slug}/ >/tmp/report.html
|
|
70
|
-
curl -fsS http://localhost:3001/reports/data/{slug} | tee /tmp/report-data.json
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Treat endpoint `{ "ok": false, ... }`, visible dashboard error cards, failed Baker CLI calls, provider API errors, or smoke script failures as blocking. Fix the endpoint/query and rerun the smoke test. For a new report, also visually open `http://localhost:3001/reports/{slug}/` in the sandbox and verify the dashboard renders with real data or a deliberate empty state.
|