Save SKILL.md directories to a workspace, personally or org-shared, and serve them to every connected agent through the MCP skills tool and the MCP Skills Extension. Adds the skill table, the skills HTTP API, console pages, and executor skills list/pull.
Unified · Split
| @@ -0,0 +1,5 @@ | |||
| 1 | --- | ||
| 2 | "executor": minor | ||
| 3 | --- | ||
| 4 | |||
| 5 | Add Agent Skills. Save a SKILL.md directory to Executor, personally or shared with the whole workspace, and every connected agent can load it: the MCP `skills` tool lists and serves workspace skills next to Executor's own docs, and the server speaks the MCP Skills Extension (`skills/list`, `skills/get`, `skill://` resources). The console gets a Skills page for adding, editing, and removing them, and `executor skills pull` syncs them to local agent skill directories. | ||
| @@ -86,6 +86,7 @@ import { · 85 unmodified lines | |||
| 86 | normalizeExecutorServerOrigin, | 86 | normalizeExecutorServerOrigin, |
| 87 | resolveExecutorServerConfiguredHeaders, | 87 | resolveExecutorServerConfiguredHeaders, |
| 88 | resolveExecutorServerRequestHeaders, | 88 | resolveExecutorServerRequestHeaders, |
| 89 | SkillName, | ||
| 89 | type ExecutorLocalServerKind, | 90 | type ExecutorLocalServerKind, |
| 90 | type ExecutorLocalServerManifest, | 91 | type ExecutorLocalServerManifest, |
| 91 | type ExecutorServerConnection, | 92 | type ExecutorServerConnection, |
| @@ -184,6 +185,21 @@ import { · 92 unmodified lines | |||
| 184 | sanitizeCliOutputText, | 185 | sanitizeCliOutputText, |
| 185 | shellQuoteArg, | 186 | shellQuoteArg, |
| 186 | } from "./tooling"; | 187 | } from "./tooling"; |
| 188 | import { | ||
| 189 | defaultClaudeSkillsDir, | ||
| 190 | defaultSkillsDir, | ||
| 191 | formatSkillPullSummaryLine, | ||
| 192 | formatSkillsTable, | ||
| 193 | planSkillsPull, | ||
| 194 | readExistingSkillEntries, | ||
| 195 | removeSkillDirectory, | ||
| 196 | resolveEffectiveSkills, | ||
| 197 | summarizeSkillPullActions, | ||
| 198 | writeSkillDirectory, | ||
| 199 | type SkillDetail, | ||
| 200 | type SkillPullAction, | ||
| 201 | type SkillSummary, | ||
| 202 | } from "./skills"; | ||
| 187 | 203 | ||
| 188 | // Embedded web UI — baked into compiled binaries via `with { type: "file" }` | 204 | // Embedded web UI — baked into compiled binaries via `with { type: "file" }` |
| 189 | import embeddedWebUI from "./embedded-web-ui.gen"; | 205 | import embeddedWebUI from "./embedded-web-ui.gen"; |
| @@ -2209,6 +2225,125 @@ const toolsCommand = Command.make("tools").pipe( · 2019 unmodified lines | |||
| 2209 | Command.withDescription("Discover available tools and integrations"), | 2225 | Command.withDescription("Discover available tools and integrations"), |
| 2210 | ); | 2226 | ); |
| 2211 | 2227 | ||
| 2228 | // --------------------------------------------------------------------------- | ||
| 2229 | // Skills — `executor skills list` / `executor skills pull` | ||
| 2230 | // --------------------------------------------------------------------------- | ||
| 2231 | |||
| 2232 | const skillsListCommand = Command.make( | ||
| 2233 | "list", | ||
| 2234 | { | ||
| 2235 | baseUrl: serverBaseUrl, | ||
| 2236 | server: serverProfile, | ||
| 2237 | scope, | ||
| 2238 | }, | ||
| 2239 | ({ baseUrl, server, scope }) => | ||
| 2240 | Effect.gen(function* () { | ||
| 2241 | applyScope(scope); | ||
| 2242 | const target = serverTargetFromOptions({ baseUrl, server }); | ||
| 2243 | const connection = yield* resolveExecutorServerConnection(target); | ||
| 2244 | const client = yield* makeApiClient(connection, target); | ||
| 2245 | const skills = yield* client.skills.list(); | ||
| 2246 | for (const line of formatSkillsTable(skills)) { | ||
| 2247 | console.log(line); | ||
| 2248 | } | ||
| 2249 | }).pipe(Effect.mapError(toError)), | ||
| 2250 | ).pipe(Command.withDescription("List Agent Skills visible to this owner scope")); | ||
| 2251 | |||
| 2252 | const skillsPullDirOption = Options.string("dir").pipe( | ||
| 2253 | Options.optional, | ||
| 2254 | Options.withDescription("Directory to write skills into. Defaults to ~/.agents/skills."), | ||
| 2255 | ); | ||
| 2256 | |||
| 2257 | const skillsPullNoClaudeOption = Options.boolean("no-claude").pipe( | ||
| 2258 | Options.withDefault(false), | ||
| 2259 | Options.withDescription("Skip mirroring skills into ~/.claude/skills."), | ||
| 2260 | ); | ||
| 2261 | |||
| 2262 | /** Pull every skill this owner scope can see into `root`, applying the | ||
| 2263 | * marker-file safety rules from `planSkillsPull`. Returns the actions taken | ||
| 2264 | * so the caller can print one combined summary across every target root. */ | ||
| 2265 | const pullSkillsIntoRoot = (input: { | ||
| 2266 | readonly root: string; | ||
| 2267 | readonly origin: string; | ||
| 2268 | readonly effectiveSkills: readonly SkillSummary[]; | ||
| 2269 | readonly fetchSkill: (skill: SkillSummary) => Effect.Effect<SkillDetail, Error>; | ||
| 2270 | }) => | ||
| 2271 | Effect.gen(function* () { | ||
| 2272 | const existing = yield* readExistingSkillEntries(input.root).pipe(Effect.mapError(toError)); | ||
| 2273 | const actions = planSkillsPull({ | ||
| 2274 | origin: input.origin, | ||
| 2275 | skills: input.effectiveSkills, | ||
| 2276 | existing, | ||
| 2277 | }); | ||
| 2278 | const skillsByName = new Map(input.effectiveSkills.map((skill) => [skill.name, skill])); | ||
| 2279 | |||
| 2280 | for (const action of actions) { | ||
| 2281 | if (action.kind === "add" || action.kind === "update") { | ||
| 2282 | const skill = skillsByName.get(action.name); | ||
| 2283 | if (!skill) continue; | ||
| 2284 | const detail = yield* input.fetchSkill(skill); | ||
| 2285 | yield* writeSkillDirectory({ root: input.root, origin: input.origin, skill: detail }).pipe( | ||
| 2286 | Effect.mapError(toError), | ||
| 2287 | ); | ||
| 2288 | } else if (action.kind === "remove") { | ||
| 2289 | yield* removeSkillDirectory(input.root, action.name).pipe(Effect.mapError(toError)); | ||
| 2290 | } | ||
| 2291 | } | ||
| 2292 | |||
| 2293 | return actions; | ||
| 2294 | }); | ||
| 2295 | |||
| 2296 | const skillsPullCommand = Command.make( | ||
| 2297 | "pull", | ||
| 2298 | { | ||
| 2299 | dir: skillsPullDirOption, | ||
| 2300 | noClaude: skillsPullNoClaudeOption, | ||
| 2301 | baseUrl: serverBaseUrl, | ||
| 2302 | server: serverProfile, | ||
| 2303 | scope, | ||
| 2304 | }, | ||
| 2305 | ({ dir, noClaude, baseUrl, server, scope }) => | ||
| 2306 | Effect.gen(function* () { | ||
| 2307 | applyScope(scope); | ||
| 2308 | const target = serverTargetFromOptions({ baseUrl, server }); | ||
| 2309 | const connection = yield* resolveExecutorServerConnection(target); | ||
| 2310 | const client = yield* makeApiClient(connection, target); | ||
| 2311 | |||
| 2312 | const skills = yield* client.skills.list(); | ||
| 2313 | const effectiveSkills = resolveEffectiveSkills(skills); | ||
| 2314 | const fetchSkill = (skill: SkillSummary) => | ||
| 2315 | client.skills | ||
| 2316 | .get({ params: { owner: skill.owner, name: SkillName.make(skill.name) } }) | ||
| 2317 | .pipe(Effect.mapError(toError)); | ||
| 2318 | |||
| 2319 | const targetRoot = Option.getOrElse(dir, defaultSkillsDir); | ||
| 2320 | const roots = noClaude ? [targetRoot] : [targetRoot, defaultClaudeSkillsDir()]; | ||
| 2321 | |||
| 2322 | const allActions: SkillPullAction[] = []; | ||
| 2323 | for (const root of roots) { | ||
| 2324 | const actions = yield* pullSkillsIntoRoot({ | ||
| 2325 | root, | ||
| 2326 | origin: connection.origin, | ||
| 2327 | effectiveSkills, | ||
| 2328 | fetchSkill, | ||
| 2329 | }); | ||
| 2330 | allActions.push(...actions); | ||
| 2331 | } | ||
| 2332 | |||
| 2333 | const summary = summarizeSkillPullActions(allActions); | ||
| 2334 | console.log(`Pulled ${effectiveSkills.length} skill(s) into ${roots.join(", ")}.`); | ||
| 2335 | console.log(formatSkillPullSummaryLine(summary)); | ||
| 2336 | for (const action of allActions) { | ||
| 2337 | if (action.reason) console.log(` ${action.kind}: ${action.name} (${action.reason})`); | ||
| 2338 | } | ||
| 2339 | }).pipe(Effect.mapError(toError)), | ||
| 2340 | ).pipe(Command.withDescription("Pull Agent Skills from the server into a local directory")); | ||
| 2341 | |||
| 2342 | const skillsCommand = Command.make("skills").pipe( | ||
| 2343 | Command.withSubcommands([skillsListCommand, skillsPullCommand] as const), | ||
| 2344 | Command.withDescription("List and pull Agent Skills from the connected server"), | ||
| 2345 | ); | ||
| 2346 | |||
| 2212 | const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/; | 2347 | const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/; |
| 2213 | const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/; | 2348 | const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/; |
| 2214 | 2349 | ||
| @@ -3299,6 +3434,7 @@ const root = Command.make("executor").pipe( · 1084 unmodified lines | |||
| 3299 | callCommand, | 3434 | callCommand, |
| 3300 | resumeCommand, | 3435 | resumeCommand, |
| 3301 | toolsCommand, | 3436 | toolsCommand, |
| 3437 | skillsCommand, | ||
| 3302 | installCommand, | 3438 | installCommand, |
| 3303 | loginCommand, | 3439 | loginCommand, |
| 3304 | logoutCommand, | 3440 | logoutCommand, |
| @@ -0,0 +1,325 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import { BunServices } from "@effect/platform-bun"; | ||
| 3 | import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; | ||
| 4 | import { tmpdir } from "node:os"; | ||
| 5 | import { join } from "node:path"; | ||
| 6 | import * as Effect from "effect/Effect"; | ||
| 7 | |||
| 8 | import { | ||
| 9 | formatSkillPullSummaryLine, | ||
| 10 | formatSkillsTable, | ||
| 11 | parseSkillMarker, | ||
| 12 | planSkillsPull, | ||
| 13 | readExistingSkillEntries, | ||
| 14 | removeSkillDirectory, | ||
| 15 | resolveEffectiveSkills, | ||
| 16 | serializeSkillMarker, | ||
| 17 | skillMarkerFor, | ||
| 18 | summarizeSkillPullActions, | ||
| 19 | writeSkillDirectory, | ||
| 20 | SKILL_MARKER_FILENAME, | ||
| 21 | type SkillDetail, | ||
| 22 | type SkillSummary, | ||
| 23 | } from "./skills"; | ||
| 24 | |||
| 25 | const withTmp = <A, E, R>(body: (dir: string) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => | ||
| 26 | Effect.acquireUseRelease( | ||
| 27 | Effect.sync(() => mkdtempSync(join(tmpdir(), "exec-skills-"))), | ||
| 28 | body, | ||
| 29 | (dir) => Effect.sync(() => rmSync(dir, { recursive: true, force: true })), | ||
| 30 | ); | ||
| 31 | |||
| 32 | const ORIGIN = "https://example.executor.sh"; | ||
| 33 | |||
| 34 | const summary = ( | ||
| 35 | input: Partial<SkillSummary> & { owner: "org" | "user"; name: string }, | ||
| 36 | ): SkillSummary => ({ | ||
| 37 | description: "A test skill.", | ||
| 38 | files: [{ path: "SKILL.md", size: 10, digest: "sha256:aaa" }], | ||
| 39 | updatedAt: Date.parse("2026-01-01T00:00:00.000Z"), | ||
| 40 | ...input, | ||
| 41 | }); | ||
| 42 | |||
| 43 | describe("resolveEffectiveSkills", () => { | ||
| 44 | it("keeps a single skill unaffected", () => { | ||
| 45 | const skills = [summary({ owner: "org", name: "docs" })]; | ||
| 46 | expect(resolveEffectiveSkills(skills)).toEqual(skills); | ||
| 47 | }); | ||
| 48 | |||
| 49 | it("prefers the user copy over an org copy of the same name", () => { | ||
| 50 | const org = summary({ owner: "org", name: "docs", description: "org copy" }); | ||
| 51 | const user = summary({ owner: "user", name: "docs", description: "user copy" }); | ||
| 52 | expect(resolveEffectiveSkills([org, user])).toEqual([user]); | ||
| 53 | // Order shouldn't matter. | ||
| 54 | expect(resolveEffectiveSkills([user, org])).toEqual([user]); | ||
| 55 | }); | ||
| 56 | |||
| 57 | it("keeps distinct names separate", () => { | ||
| 58 | const a = summary({ owner: "org", name: "docs" }); | ||
| 59 | const b = summary({ owner: "user", name: "release-notes" }); | ||
| 60 | expect(resolveEffectiveSkills([a, b])).toEqual([a, b]); | ||
| 61 | }); | ||
| 62 | }); | ||
| 63 | |||
| 64 | describe("formatSkillsTable", () => { | ||
| 65 | it("reports an empty table", () => { | ||
| 66 | expect(formatSkillsTable([])).toEqual(["No skills found."]); | ||
| 67 | }); | ||
| 68 | |||
| 69 | it("includes a header and one row per skill", () => { | ||
| 70 | const lines = formatSkillsTable([summary({ owner: "org", name: "docs" })]); | ||
| 71 | expect(lines).toHaveLength(2); | ||
| 72 | expect(lines[0]).toContain("OWNER"); | ||
| 73 | expect(lines[1]).toContain("org"); | ||
| 74 | expect(lines[1]).toContain("docs"); | ||
| 75 | }); | ||
| 76 | }); | ||
| 77 | |||
| 78 | describe("skill markers", () => { | ||
| 79 | it("round-trips through serialize/parse", () => { | ||
| 80 | const marker = skillMarkerFor({ | ||
| 81 | origin: ORIGIN, | ||
| 82 | skill: { owner: "user", name: "docs" }, | ||
| 83 | files: [{ path: "SKILL.md", size: 10, digest: "sha256:aaa" }], | ||
| 84 | }); | ||
| 85 | const parsed = parseSkillMarker(serializeSkillMarker(marker)); | ||
| 86 | expect(parsed).toEqual(marker); | ||
| 87 | }); | ||
| 88 | |||
| 89 | it("rejects malformed JSON", () => { | ||
| 90 | expect(parseSkillMarker("not json")).toBeUndefined(); | ||
| 91 | }); | ||
| 92 | |||
| 93 | it("rejects an object missing required fields", () => { | ||
| 94 | expect(parseSkillMarker(JSON.stringify({ origin: ORIGIN }))).toBeUndefined(); | ||
| 95 | }); | ||
| 96 | |||
| 97 | it("rejects an invalid owner", () => { | ||
| 98 | expect( | ||
| 99 | parseSkillMarker( | ||
| 100 | JSON.stringify({ origin: ORIGIN, owner: "nobody", name: "docs", digests: {} }), | ||
| 101 | ), | ||
| 102 | ).toBeUndefined(); | ||
| 103 | }); | ||
| 104 | |||
| 105 | it("rejects non-string digest values", () => { | ||
| 106 | expect( | ||
| 107 | parseSkillMarker( | ||
| 108 | JSON.stringify({ origin: ORIGIN, owner: "user", name: "docs", digests: { "SKILL.md": 1 } }), | ||
| 109 | ), | ||
| 110 | ).toBeUndefined(); | ||
| 111 | }); | ||
| 112 | }); | ||
| 113 | |||
| 114 | describe("planSkillsPull", () => { | ||
| 115 | it("adds a skill with no existing directory", () => { | ||
| 116 | const actions = planSkillsPull({ | ||
| 117 | origin: ORIGIN, | ||
| 118 | skills: [summary({ owner: "org", name: "docs" })], | ||
| 119 | existing: [], | ||
| 120 | }); | ||
| 121 | expect(actions).toEqual([{ name: "docs", kind: "add" }]); | ||
| 122 | }); | ||
| 123 | |||
| 124 | it("marks a matching-digest skill unchanged", () => { | ||
| 125 | const skill = summary({ owner: "org", name: "docs" }); | ||
| 126 | const actions = planSkillsPull({ | ||
| 127 | origin: ORIGIN, | ||
| 128 | skills: [skill], | ||
| 129 | existing: [ | ||
| 130 | { | ||
| 131 | name: "docs", | ||
| 132 | marker: skillMarkerFor({ origin: ORIGIN, skill, files: skill.files }), | ||
| 133 | }, | ||
| 134 | ], | ||
| 135 | }); | ||
| 136 | expect(actions).toEqual([{ name: "docs", kind: "unchanged" }]); | ||
| 137 | }); | ||
| 138 | |||
| 139 | it("updates a skill whose digests changed", () => { | ||
| 140 | const skill = summary({ owner: "org", name: "docs" }); | ||
| 141 | const staleMarker = skillMarkerFor({ | ||
| 142 | origin: ORIGIN, | ||
| 143 | skill, | ||
| 144 | files: [{ path: "SKILL.md", size: 5, digest: "sha256:old" }], | ||
| 145 | }); | ||
| 146 | const actions = planSkillsPull({ | ||
| 147 | origin: ORIGIN, | ||
| 148 | skills: [skill], | ||
| 149 | existing: [{ name: "docs", marker: staleMarker }], | ||
| 150 | }); | ||
| 151 | expect(actions).toEqual([{ name: "docs", kind: "update" }]); | ||
| 152 | }); | ||
| 153 | |||
| 154 | it("never overwrites a directory with no marker", () => { | ||
| 155 | const skill = summary({ owner: "org", name: "docs" }); | ||
| 156 | const actions = planSkillsPull({ | ||
| 157 | origin: ORIGIN, | ||
| 158 | skills: [skill], | ||
| 159 | existing: [{ name: "docs", marker: undefined }], | ||
| 160 | }); | ||
| 161 | expect(actions).toEqual([ | ||
| 162 | { name: "docs", kind: "skip", reason: expect.stringContaining(SKILL_MARKER_FILENAME) }, | ||
| 163 | ]); | ||
| 164 | }); | ||
| 165 | |||
| 166 | it("skips a directory owned by a different server", () => { | ||
| 167 | const skill = summary({ owner: "org", name: "docs" }); | ||
| 168 | const otherOriginMarker = skillMarkerFor({ | ||
| 169 | origin: "https://other.example", | ||
| 170 | skill, | ||
| 171 | files: skill.files, | ||
| 172 | }); | ||
| 173 | const actions = planSkillsPull({ | ||
| 174 | origin: ORIGIN, | ||
| 175 | skills: [skill], | ||
| 176 | existing: [{ name: "docs", marker: otherOriginMarker }], | ||
| 177 | }); | ||
| 178 | expect(actions).toEqual([ | ||
| 179 | { name: "docs", kind: "skip", reason: expect.stringContaining("different server") }, | ||
| 180 | ]); | ||
| 181 | }); | ||
| 182 | |||
| 183 | it("removes a marker-bearing directory whose skill no longer exists upstream", () => { | ||
| 184 | const marker = skillMarkerFor({ | ||
| 185 | origin: ORIGIN, | ||
| 186 | skill: { owner: "org", name: "gone" }, | ||
| 187 | files: [{ path: "SKILL.md", size: 1, digest: "sha256:x" }], | ||
| 188 | }); | ||
| 189 | const actions = planSkillsPull({ | ||
| 190 | origin: ORIGIN, | ||
| 191 | skills: [], | ||
| 192 | existing: [{ name: "gone", marker }], | ||
| 193 | }); | ||
| 194 | expect(actions).toEqual([{ name: "gone", kind: "remove", reason: expect.any(String) }]); | ||
| 195 | }); | ||
| 196 | |||
| 197 | it("never removes an unmanaged directory that has no matching upstream skill", () => { | ||
| 198 | const actions = planSkillsPull({ | ||
| 199 | origin: ORIGIN, | ||
| 200 | skills: [], | ||
| 201 | existing: [{ name: "my-notes", marker: undefined }], | ||
| 202 | }); | ||
| 203 | expect(actions).toEqual([]); | ||
| 204 | }); | ||
| 205 | |||
| 206 | it("never removes a directory owned by a different server", () => { | ||
| 207 | const marker = skillMarkerFor({ | ||
| 208 | origin: "https://other.example", | ||
| 209 | skill: { owner: "org", name: "gone" }, | ||
| 210 | files: [], | ||
| 211 | }); | ||
| 212 | const actions = planSkillsPull({ | ||
| 213 | origin: ORIGIN, | ||
| 214 | skills: [], | ||
| 215 | existing: [{ name: "gone", marker }], | ||
| 216 | }); | ||
| 217 | expect(actions).toEqual([]); | ||
| 218 | }); | ||
| 219 | }); | ||
| 220 | |||
| 221 | describe("summarizeSkillPullActions / formatSkillPullSummaryLine", () => { | ||
| 222 | it("counts each action kind", () => { | ||
| 223 | const totals = summarizeSkillPullActions([ | ||
| 224 | { name: "a", kind: "add" }, | ||
| 225 | { name: "b", kind: "update" }, | ||
| 226 | { name: "c", kind: "unchanged" }, | ||
| 227 | { name: "d", kind: "remove" }, | ||
| 228 | { name: "e", kind: "skip" }, | ||
| 229 | { name: "f", kind: "add" }, | ||
| 230 | ]); | ||
| 231 | expect(totals).toEqual({ added: 2, updated: 1, unchanged: 1, removed: 1, skipped: 1 }); | ||
| 232 | expect(formatSkillPullSummaryLine(totals)).toBe( | ||
| 233 | "2 added, 1 updated, 1 removed, 1 skipped (1 unchanged)", | ||
| 234 | ); | ||
| 235 | }); | ||
| 236 | |||
| 237 | it("omits the unchanged suffix when there are none", () => { | ||
| 238 | const totals = summarizeSkillPullActions([{ name: "a", kind: "add" }]); | ||
| 239 | expect(formatSkillPullSummaryLine(totals)).toBe("1 added, 0 updated, 0 removed, 0 skipped"); | ||
| 240 | }); | ||
| 241 | }); | ||
| 242 | |||
| 243 | describe("filesystem I/O", () => { | ||
| 244 | it.effect("readExistingSkillEntries returns [] for a missing root", () => | ||
| 245 | withTmp((dir) => | ||
| 246 | Effect.gen(function* () { | ||
| 247 | const entries = yield* readExistingSkillEntries(join(dir, "does-not-exist")); | ||
| 248 | expect(entries).toEqual([]); | ||
| 249 | }), | ||
| 250 | ).pipe(Effect.provide(BunServices.layer)), | ||
| 251 | ); | ||
| 252 | |||
| 253 | it.effect("readExistingSkillEntries reports marker and unmarked directories", () => | ||
| 254 | withTmp((dir) => | ||
| 255 | Effect.gen(function* () { | ||
| 256 | const managed = join(dir, "docs"); | ||
| 257 | mkdirSync(managed, { recursive: true }); | ||
| 258 | const marker = skillMarkerFor({ | ||
| 259 | origin: ORIGIN, | ||
| 260 | skill: { owner: "org", name: "docs" }, | ||
| 261 | files: [{ path: "SKILL.md", size: 3, digest: "sha256:aaa" }], | ||
| 262 | }); | ||
| 263 | writeFileSync(join(managed, SKILL_MARKER_FILENAME), serializeSkillMarker(marker)); | ||
| 264 | |||
| 265 | const unmanaged = join(dir, "my-notes"); | ||
| 266 | mkdirSync(unmanaged, { recursive: true }); | ||
| 267 | |||
| 268 | // A plain file at the root must not be mistaken for a skill directory. | ||
| 269 | writeFileSync(join(dir, "readme.txt"), "hi"); | ||
| 270 | |||
| 271 | const entries = yield* readExistingSkillEntries(dir); | ||
| 272 | const byName = new Map(entries.map((entry) => [entry.name, entry])); | ||
| 273 | expect(byName.size).toBe(2); | ||
| 274 | expect(byName.get("docs")?.marker).toEqual(marker); | ||
| 275 | expect(byName.get("my-notes")?.marker).toBeUndefined(); | ||
| 276 | }), | ||
| 277 | ).pipe(Effect.provide(BunServices.layer)), | ||
| 278 | ); | ||
| 279 | |||
| 280 | it.effect("writeSkillDirectory writes nested files and a marker", () => | ||
| 281 | withTmp((dir) => | ||
| 282 | Effect.gen(function* () { | ||
| 283 | const detail: SkillDetail = { | ||
| 284 | owner: "user", | ||
| 285 | name: "docs", | ||
| 286 | description: "A test skill.", | ||
| 287 | updatedAt: Date.now(), | ||
| 288 | files: [ | ||
| 289 | { path: "SKILL.md", size: 5, digest: "sha256:aaa", content: "# Docs" }, | ||
| 290 | { | ||
| 291 | path: "references/guide.md", | ||
| 292 | size: 6, | ||
| 293 | digest: "sha256:bbb", | ||
| 294 | content: "Guide.", | ||
| 295 | }, | ||
| 296 | ], | ||
| 297 | }; | ||
| 298 | yield* writeSkillDirectory({ root: dir, origin: ORIGIN, skill: detail }); | ||
| 299 | |||
| 300 | const entries = yield* readExistingSkillEntries(dir); | ||
| 301 | expect(entries).toHaveLength(1); | ||
| 302 | expect(entries[0]?.marker?.origin).toBe(ORIGIN); | ||
| 303 | expect(entries[0]?.marker?.digests).toEqual({ | ||
| 304 | "SKILL.md": "sha256:aaa", | ||
| 305 | "references/guide.md": "sha256:bbb", | ||
| 306 | }); | ||
| 307 | }), | ||
| 308 | ).pipe(Effect.provide(BunServices.layer)), | ||
| 309 | ); | ||
| 310 | |||
| 311 | it.effect("removeSkillDirectory deletes the directory recursively", () => | ||
| 312 | withTmp((dir) => | ||
| 313 | Effect.gen(function* () { | ||
| 314 | const target = join(dir, "docs", "references"); | ||
| 315 | mkdirSync(target, { recursive: true }); | ||
| 316 | writeFileSync(join(target, "guide.md"), "Guide."); | ||
| 317 | |||
| 318 | yield* removeSkillDirectory(dir, "docs"); | ||
| 319 | |||
| 320 | const entries = yield* readExistingSkillEntries(dir); | ||
| 321 | expect(entries).toEqual([]); | ||
| 322 | }), | ||
| 323 | ).pipe(Effect.provide(BunServices.layer)), | ||
| 324 | ); | ||
| 325 | }); | ||
| @@ -0,0 +1,354 @@ | |||
| 1 | import { homedir } from "node:os"; | ||
| 2 | import { FileSystem, Path } from "effect"; | ||
| 3 | import type { PlatformError } from "effect/PlatformError"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | |||
| 6 | import type { Owner } from "@executor-js/sdk/shared"; | ||
| 7 | |||
| 8 | // --------------------------------------------------------------------------- | ||
| 9 | // `executor skills` — list and pull Agent Skills from the connected server. | ||
| 10 | // | ||
| 11 | // Pure planning lives here, separated from the filesystem/network I/O that | ||
| 12 | // drives it, so the marker-file safety rules (never touch a directory that | ||
| 13 | // isn't ours) are unit-testable without a server or real disk writes. See | ||
| 14 | // plans/agent-skills.md, "CLI (apps/cli)" for the contract, and | ||
| 15 | // packages/core/api/src/skills/api.ts for the response shapes this mirrors. | ||
| 16 | // --------------------------------------------------------------------------- | ||
| 17 | |||
| 18 | // --------------------------------------------------------------------------- | ||
| 19 | // Types mirroring the skills HTTP API responses. | ||
| 20 | // --------------------------------------------------------------------------- | ||
| 21 | |||
| 22 | export interface SkillFileEntry { | ||
| 23 | readonly path: string; | ||
| 24 | readonly size: number; | ||
| 25 | readonly digest: string; | ||
| 26 | } | ||
| 27 | |||
| 28 | export interface SkillFile extends SkillFileEntry { | ||
| 29 | readonly content: string; | ||
| 30 | } | ||
| 31 | |||
| 32 | /** What `GET /skills` returns per skill: the manifest, no file contents. */ | ||
| 33 | export interface SkillSummary { | ||
| 34 | readonly owner: Owner; | ||
| 35 | readonly name: string; | ||
| 36 | readonly description: string; | ||
| 37 | readonly files: readonly SkillFileEntry[]; | ||
| 38 | readonly updatedAt: number; | ||
| 39 | } | ||
| 40 | |||
| 41 | /** What `GET /skills/:owner/:name` returns: the summary plus file contents. */ | ||
| 42 | export interface SkillDetail extends Omit<SkillSummary, "files"> { | ||
| 43 | readonly files: readonly SkillFile[]; | ||
| 44 | } | ||
| 45 | |||
| 46 | // --------------------------------------------------------------------------- | ||
| 47 | // Default install locations | ||
| 48 | // --------------------------------------------------------------------------- | ||
| 49 | |||
| 50 | export const defaultSkillsDir = (): string => `${homedir()}/.agents/skills`; | ||
| 51 | export const defaultClaudeSkillsDir = (): string => `${homedir()}/.claude/skills`; | ||
| 52 | |||
| 53 | // --------------------------------------------------------------------------- | ||
| 54 | // Owner shadowing: a `user` skill hides an `org` skill of the same name. | ||
| 55 | // --------------------------------------------------------------------------- | ||
| 56 | |||
| 57 | /** Resolve the set of skills every agent actually sees: at most one entry per | ||
| 58 | * name, preferring the `user` (personal) copy over an `org` (workspace) one. */ | ||
| 59 | export const resolveEffectiveSkills = <S extends { readonly owner: Owner; readonly name: string }>( | ||
| 60 | skills: readonly S[], | ||
| 61 | ): readonly S[] => { | ||
| 62 | const byName = new Map<string, S>(); | ||
| 63 | for (const skill of skills) { | ||
| 64 | const existing = byName.get(skill.name); | ||
| 65 | if (!existing || (existing.owner === "org" && skill.owner === "user")) { | ||
| 66 | byName.set(skill.name, skill); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | return Array.from(byName.values()); | ||
| 70 | }; | ||
| 71 | |||
| 72 | // --------------------------------------------------------------------------- | ||
| 73 | // `list` — table formatting (pure). | ||
| 74 | // --------------------------------------------------------------------------- | ||
| 75 | |||
| 76 | export const formatSkillsTable = (skills: readonly SkillSummary[]): readonly string[] => { | ||
| 77 | if (skills.length === 0) { | ||
| 78 | return ["No skills found."]; | ||
| 79 | } | ||
| 80 | |||
| 81 | const header = { | ||
| 82 | owner: "OWNER", | ||
| 83 | name: "NAME", | ||
| 84 | description: "DESCRIPTION", | ||
| 85 | files: "FILES", | ||
| 86 | updated: "UPDATED", | ||
| 87 | }; | ||
| 88 | const rows = skills.map((skill) => ({ | ||
| 89 | owner: skill.owner, | ||
| 90 | name: skill.name, | ||
| 91 | description: skill.description, | ||
| 92 | files: String(skill.files.length), | ||
| 93 | updated: new Date(skill.updatedAt).toISOString(), | ||
| 94 | })); | ||
| 95 | |||
| 96 | const widthOf = (key: "owner" | "name" | "files"): number => | ||
| 97 | Math.max(header[key].length, ...rows.map((row) => row[key].length)); | ||
| 98 | const ownerWidth = widthOf("owner"); | ||
| 99 | const nameWidth = widthOf("name"); | ||
| 100 | const filesWidth = widthOf("files"); | ||
| 101 | |||
| 102 | const line = (row: (typeof rows)[number] | typeof header): string => | ||
| 103 | `${row.owner.padEnd(ownerWidth)} ${row.name.padEnd(nameWidth)} ${row.description} ${row.files.padEnd(filesWidth)} ${row.updated}`; | ||
| 104 | |||
| 105 | return [line(header), ...rows.map(line)]; | ||
| 106 | }; | ||
| 107 | |||
| 108 | // --------------------------------------------------------------------------- | ||
| 109 | // Marker file — `.executor-skill.json`, written into every skill directory | ||
| 110 | // this CLI manages. Its presence (and matching `origin`) is the only thing | ||
| 111 | // that authorizes `pull` to overwrite or delete a directory. | ||
| 112 | // --------------------------------------------------------------------------- | ||
| 113 | |||
| 114 | export const SKILL_MARKER_FILENAME = ".executor-skill.json"; | ||
| 115 | |||
| 116 | export interface SkillMarker { | ||
| 117 | readonly origin: string; | ||
| 118 | readonly owner: Owner; | ||
| 119 | readonly name: string; | ||
| 120 | /** File path (relative to the skill root) -> digest, as last written. */ | ||
| 121 | readonly digests: Readonly<Record<string, string>>; | ||
| 122 | } | ||
| 123 | |||
| 124 | const isOwner = (value: unknown): value is Owner => value === "org" || value === "user"; | ||
| 125 | |||
| 126 | /** Parse a marker file's contents. Returns `undefined` for anything that | ||
| 127 | * isn't a well-formed marker — including a directory some other tool made, | ||
| 128 | * which must never be treated as ours. */ | ||
| 129 | export const parseSkillMarker = (raw: string): SkillMarker | undefined => { | ||
| 130 | let parsed: unknown; | ||
| 131 | try { | ||
| 132 | parsed = JSON.parse(raw); | ||
| 133 | } catch { | ||
| 134 | return undefined; | ||
| 135 | } | ||
| 136 | if (typeof parsed !== "object" || parsed === null) return undefined; | ||
| 137 | const candidate = parsed as Record<string, unknown>; | ||
| 138 | if (typeof candidate.origin !== "string") return undefined; | ||
| 139 | if (!isOwner(candidate.owner)) return undefined; | ||
| 140 | if (typeof candidate.name !== "string") return undefined; | ||
| 141 | if (typeof candidate.digests !== "object" || candidate.digests === null) return undefined; | ||
| 142 | |||
| 143 | const digests: Record<string, string> = {}; | ||
| 144 | for (const [path, digest] of Object.entries(candidate.digests as Record<string, unknown>)) { | ||
| 145 | if (typeof digest !== "string") return undefined; | ||
| 146 | digests[path] = digest; | ||
| 147 | } | ||
| 148 | |||
| 149 | return { origin: candidate.origin, owner: candidate.owner, name: candidate.name, digests }; | ||
| 150 | }; | ||
| 151 | |||
| 152 | export const serializeSkillMarker = (marker: SkillMarker): string => | ||
| 153 | `${JSON.stringify(marker, null, 2)}\n`; | ||
| 154 | |||
| 155 | export const skillMarkerFor = (input: { | ||
| 156 | readonly origin: string; | ||
| 157 | readonly skill: Pick<SkillSummary, "owner" | "name">; | ||
| 158 | readonly files: readonly SkillFileEntry[]; | ||
| 159 | }): SkillMarker => ({ | ||
| 160 | origin: input.origin, | ||
| 161 | owner: input.skill.owner, | ||
| 162 | name: input.skill.name, | ||
| 163 | digests: Object.fromEntries(input.files.map((file) => [file.path, file.digest])), | ||
| 164 | }); | ||
| 165 | |||
| 166 | // --------------------------------------------------------------------------- | ||
| 167 | // `pull` — planning (pure). Decides add/update/unchanged/remove/skip per | ||
| 168 | // skill directory without touching the network or filesystem. | ||
| 169 | // --------------------------------------------------------------------------- | ||
| 170 | |||
| 171 | export type SkillPullActionKind = "add" | "update" | "unchanged" | "remove" | "skip"; | ||
| 172 | |||
| 173 | export interface SkillPullAction { | ||
| 174 | readonly name: string; | ||
| 175 | readonly kind: SkillPullActionKind; | ||
| 176 | readonly reason?: string; | ||
| 177 | } | ||
| 178 | |||
| 179 | /** What `pull` found on disk for one directory entry under the target root. */ | ||
| 180 | export interface ExistingSkillEntry { | ||
| 181 | readonly name: string; | ||
| 182 | /** `undefined` when there's no marker file, or it doesn't parse: an | ||
| 183 | * unmanaged directory that `pull` must never overwrite or delete. */ | ||
| 184 | readonly marker: SkillMarker | undefined; | ||
| 185 | } | ||
| 186 | |||
| 187 | const digestsEqual = ( | ||
| 188 | a: Readonly<Record<string, string>>, | ||
| 189 | b: Readonly<Record<string, string>>, | ||
| 190 | ): boolean => { | ||
| 191 | const aKeys = Object.keys(a); | ||
| 192 | const bKeys = Object.keys(b); | ||
| 193 | if (aKeys.length !== bKeys.length) return false; | ||
| 194 | return aKeys.every((key) => a[key] === b[key]); | ||
| 195 | }; | ||
| 196 | |||
| 197 | /** Plan one target root's worth of `pull` actions. Call once per root (the | ||
| 198 | * main `--dir` and, unless `--no-claude`, the `~/.claude/skills` mirror). */ | ||
| 199 | export const planSkillsPull = (input: { | ||
| 200 | readonly origin: string; | ||
| 201 | /** Effective (already-shadowed) skills this owner scope can see. */ | ||
| 202 | readonly skills: readonly SkillSummary[]; | ||
| 203 | /** What's already on disk under the target root. */ | ||
| 204 | readonly existing: readonly ExistingSkillEntry[]; | ||
| 205 | }): readonly SkillPullAction[] => { | ||
| 206 | const actions: SkillPullAction[] = []; | ||
| 207 | const skillNames = new Set(input.skills.map((skill) => skill.name)); | ||
| 208 | const existingByName = new Map(input.existing.map((entry) => [entry.name, entry])); | ||
| 209 | |||
| 210 | for (const skill of input.skills) { | ||
| 211 | const existing = existingByName.get(skill.name); | ||
| 212 | const desiredDigests = Object.fromEntries(skill.files.map((file) => [file.path, file.digest])); | ||
| 213 | |||
| 214 | if (!existing) { | ||
| 215 | actions.push({ name: skill.name, kind: "add" }); | ||
| 216 | continue; | ||
| 217 | } | ||
| 218 | if (!existing.marker) { | ||
| 219 | actions.push({ | ||
| 220 | name: skill.name, | ||
| 221 | kind: "skip", | ||
| 222 | reason: `${skill.name}/ exists without a ${SKILL_MARKER_FILENAME} marker`, | ||
| 223 | }); | ||
| 224 | continue; | ||
| 225 | } | ||
| 226 | if (existing.marker.origin !== input.origin) { | ||
| 227 | actions.push({ | ||
| 228 | name: skill.name, | ||
| 229 | kind: "skip", | ||
| 230 | reason: `${skill.name}/ is managed by a different server (${existing.marker.origin})`, | ||
| 231 | }); | ||
| 232 | continue; | ||
| 233 | } | ||
| 234 | actions.push({ | ||
| 235 | name: skill.name, | ||
| 236 | kind: digestsEqual(existing.marker.digests, desiredDigests) ? "unchanged" : "update", | ||
| 237 | }); | ||
| 238 | } | ||
| 239 | |||
| 240 | for (const existing of input.existing) { | ||
| 241 | if (skillNames.has(existing.name)) continue; | ||
| 242 | if (!existing.marker) continue; // never touch a directory we don't own | ||
| 243 | if (existing.marker.origin !== input.origin) continue; // owned by another server | ||
| 244 | actions.push({ name: existing.name, kind: "remove", reason: "no longer exists upstream" }); | ||
| 245 | } | ||
| 246 | |||
| 247 | return actions; | ||
| 248 | }; | ||
| 249 | |||
| 250 | export interface SkillPullSummary { | ||
| 251 | readonly added: number; | ||
| 252 | readonly updated: number; | ||
| 253 | readonly unchanged: number; | ||
| 254 | readonly removed: number; | ||
| 255 | readonly skipped: number; | ||
| 256 | } | ||
| 257 | |||
| 258 | export const summarizeSkillPullActions = ( | ||
| 259 | actions: readonly SkillPullAction[], | ||
| 260 | ): SkillPullSummary => ({ | ||
| 261 | added: actions.filter((action) => action.kind === "add").length, | ||
| 262 | updated: actions.filter((action) => action.kind === "update").length, | ||
| 263 | unchanged: actions.filter((action) => action.kind === "unchanged").length, | ||
| 264 | removed: actions.filter((action) => action.kind === "remove").length, | ||
| 265 | skipped: actions.filter((action) => action.kind === "skip").length, | ||
| 266 | }); | ||
| 267 | |||
| 268 | export const formatSkillPullSummaryLine = (summary: SkillPullSummary): string => | ||
| 269 | `${summary.added} added, ${summary.updated} updated, ${summary.removed} removed, ${summary.skipped} skipped` + | ||
| 270 | (summary.unchanged > 0 ? ` (${summary.unchanged} unchanged)` : ""); | ||
| 271 | |||
| 272 | // --------------------------------------------------------------------------- | ||
| 273 | // Filesystem I/O — reading what's on disk and applying a plan. Kept small and | ||
| 274 | // separate from the planning above so tests can exercise the pure logic | ||
| 275 | // without a real (or even temp) filesystem; only the handful of tests that | ||
| 276 | // need real directory semantics use a temp dir. | ||
| 277 | // --------------------------------------------------------------------------- | ||
| 278 | |||
| 279 | /** List the immediate subdirectories of `root` and read each one's marker, | ||
| 280 | * if any. Returns `[]` when `root` doesn't exist yet. */ | ||
| 281 | export const readExistingSkillEntries = ( | ||
| 282 | root: string, | ||
| 283 | ): Effect.Effect<readonly ExistingSkillEntry[], PlatformError, FileSystem.FileSystem | Path.Path> => | ||
| 284 | Effect.gen(function* () { | ||
| 285 | const fs = yield* FileSystem.FileSystem; | ||
| 286 | const path = yield* Path.Path; | ||
| 287 | |||
| 288 | const rootExists = yield* fs.exists(root); | ||
| 289 | if (!rootExists) return []; | ||
| 290 | |||
| 291 | const entries = yield* fs.readDirectory(root); | ||
| 292 | const results: ExistingSkillEntry[] = []; | ||
| 293 | for (const name of entries) { | ||
| 294 | const entryPath = path.join(root, name); | ||
| 295 | const info = yield* fs.stat(entryPath); | ||
| 296 | if (info.type !== "Directory") continue; | ||
| 297 | |||
| 298 | const markerPath = path.join(entryPath, SKILL_MARKER_FILENAME); | ||
| 299 | const hasMarker = yield* fs.exists(markerPath); | ||
| 300 | if (!hasMarker) { | ||
| 301 | results.push({ name, marker: undefined }); | ||
| 302 | continue; | ||
| 303 | } | ||
| 304 | const raw = yield* fs.readFileString(markerPath).pipe(Effect.orElseSucceed(() => "")); | ||
| 305 | results.push({ name, marker: parseSkillMarker(raw) }); | ||
| 306 | } | ||
| 307 | return results; | ||
| 308 | }); | ||
| 309 | |||
| 310 | /** Write one skill's files (and its marker) into `<root>/<name>`, creating | ||
| 311 | * parent directories for nested file paths as needed. */ | ||
| 312 | export const writeSkillDirectory = (input: { | ||
| 313 | readonly root: string; | ||
| 314 | readonly origin: string; | ||
| 315 | readonly skill: SkillDetail; | ||
| 316 | }): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => | ||
| 317 | Effect.gen(function* () { | ||
| 318 | const fs = yield* FileSystem.FileSystem; | ||
| 319 | const path = yield* Path.Path; | ||
| 320 | const skillDir = path.join(input.root, input.skill.name); | ||
| 321 | |||
| 322 | yield* fs.makeDirectory(skillDir, { recursive: true }); | ||
| 323 | for (const file of input.skill.files) { | ||
| 324 | const filePath = path.join(skillDir, file.path); | ||
| 325 | const parent = path.dirname(filePath); | ||
| 326 | if (parent !== skillDir) { | ||
| 327 | yield* fs.makeDirectory(parent, { recursive: true }); | ||
| 328 | } | ||
| 329 | yield* fs.writeFileString(filePath, file.content); | ||
| 330 | } | ||
| 331 | |||
| 332 | const marker = skillMarkerFor({ | ||
| 333 | origin: input.origin, | ||
| 334 | skill: input.skill, | ||
| 335 | files: input.skill.files, | ||
| 336 | }); | ||
| 337 | yield* fs.writeFileString( | ||
| 338 | path.join(skillDir, SKILL_MARKER_FILENAME), | ||
| 339 | serializeSkillMarker(marker), | ||
| 340 | ); | ||
| 341 | }); | ||
| 342 | |||
| 343 | /** Remove `<root>/<name>` entirely. Only called for `"remove"` actions, which | ||
| 344 | * `planSkillsPull` only ever produces for marker-bearing directories whose | ||
| 345 | * marker origin matches the current server. */ | ||
| 346 | export const removeSkillDirectory = ( | ||
| 347 | root: string, | ||
| 348 | name: string, | ||
| 349 | ): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => | ||
| 350 | Effect.gen(function* () { | ||
| 351 | const fs = yield* FileSystem.FileSystem; | ||
| 352 | const path = yield* Path.Path; | ||
| 353 | yield* fs.remove(path.join(root, name), { recursive: true, force: true }); | ||
| 354 | }); | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | CREATE TABLE "skill" ( | ||
| 2 | "name" varchar(255) NOT NULL, | ||
| 3 | "description" text NOT NULL, | ||
| 4 | "frontmatter" json NOT NULL, | ||
| 5 | "files" json NOT NULL, | ||
| 6 | "created_at" timestamp NOT NULL, | ||
| 7 | "updated_at" timestamp NOT NULL, | ||
| 8 | "row_id" varchar(255) PRIMARY KEY NOT NULL, | ||
| 9 | "tenant" varchar(255) NOT NULL, | ||
| 10 | "owner" varchar(255) NOT NULL, | ||
| 11 | "subject" varchar(255) NOT NULL | ||
| 12 | ); | ||
| 13 | --> statement-breakpoint | ||
| 14 | CREATE UNIQUE INDEX "skill_uidx" ON "skill" USING btree ("tenant","owner","subject","name"); | ||
| @@ -0,0 +1,1611 @@ | |||
| 1 | { | ||
| 2 | "id": "6782ceae-ca96-4a99-b64e-1841949f2366", | ||
| 3 | "prevId": "42251aa3-ae24-4010-ac65-9f41e26cdc20", | ||
| 4 | "version": "7", | ||
| 5 | "dialect": "postgresql", | ||
| 6 | "tables": { | ||
| 7 | "public.accounts": { | ||
| 8 | "name": "accounts", | ||
| 9 | "schema": "", | ||
| 10 | "columns": { | ||
| 11 | "id": { | ||
| 12 | "name": "id", | ||
| 13 | "type": "text", | ||
| 14 | "primaryKey": true, | ||
| 15 | "notNull": true | ||
| 16 | }, | ||
| 17 | "created_at": { | ||
| 18 | "name": "created_at", | ||
| 19 | "type": "timestamp with time zone", | ||
| 20 | "primaryKey": false, | ||
| 21 | "notNull": true, | ||
| 22 | "default": "now()" | ||
| 23 | } | ||
| 24 | }, | ||
| 25 | "indexes": {}, | ||
| 26 | "foreignKeys": {}, | ||
| 27 | "compositePrimaryKeys": {}, | ||
| 28 | "uniqueConstraints": {}, | ||
| 29 | "policies": {}, | ||
| 30 | "checkConstraints": {}, | ||
| 31 | "isRLSEnabled": false | ||
| 32 | }, | ||
| 33 | "public.memberships": { | ||
| 34 | "name": "memberships", | ||
| 35 | "schema": "", | ||
| 36 | "columns": { | ||
| 37 | "account_id": { | ||
| 38 | "name": "account_id", | ||
| 39 | "type": "text", | ||
| 40 | "primaryKey": false, | ||
| 41 | "notNull": true | ||
| 42 | }, | ||
| 43 | "organization_id": { | ||
| 44 | "name": "organization_id", | ||
| 45 | "type": "text", | ||
| 46 | "primaryKey": false, | ||
| 47 | "notNull": true | ||
| 48 | }, | ||
| 49 | "created_at": { | ||
| 50 | "name": "created_at", | ||
| 51 | "type": "timestamp with time zone", | ||
| 52 | "primaryKey": false, | ||
| 53 | "notNull": true, | ||
| 54 | "default": "now()" | ||
| 55 | } | ||
| 56 | }, | ||
| 57 | "indexes": {}, | ||
| 58 | "foreignKeys": { | ||
| 59 | "memberships_account_id_accounts_id_fk": { | ||
| 60 | "name": "memberships_account_id_accounts_id_fk", | ||
| 61 | "tableFrom": "memberships", | ||
| 62 | "tableTo": "accounts", | ||
| 63 | "columnsFrom": ["account_id"], | ||
| 64 | "columnsTo": ["id"], | ||
| 65 | "onDelete": "cascade", | ||
| 66 | "onUpdate": "no action" | ||
| 67 | }, | ||
| 68 | "memberships_organization_id_organizations_id_fk": { | ||
| 69 | "name": "memberships_organization_id_organizations_id_fk", | ||
| 70 | "tableFrom": "memberships", | ||
| 71 | "tableTo": "organizations", | ||
| 72 | "columnsFrom": ["organization_id"], | ||
| 73 | "columnsTo": ["id"], | ||
| 74 | "onDelete": "cascade", | ||
| 75 | "onUpdate": "no action" | ||
| 76 | } | ||
| 77 | }, | ||
| 78 | "compositePrimaryKeys": { | ||
| 79 | "memberships_account_id_organization_id_pk": { | ||
| 80 | "name": "memberships_account_id_organization_id_pk", | ||
| 81 | "columns": ["account_id", "organization_id"] | ||
| 82 | } | ||
| 83 | }, | ||
| 84 | "uniqueConstraints": {}, | ||
| 85 | "policies": {}, | ||
| 86 | "checkConstraints": {}, | ||
| 87 | "isRLSEnabled": false | ||
| 88 | }, | ||
| 89 | "public.organizations": { | ||
| 90 | "name": "organizations", | ||
| 91 | "schema": "", | ||
| 92 | "columns": { | ||
| 93 | "id": { | ||
| 94 | "name": "id", | ||
| 95 | "type": "text", | ||
| 96 | "primaryKey": true, | ||
| 97 | "notNull": true | ||
| 98 | }, | ||
| 99 | "name": { | ||
| 100 | "name": "name", | ||
| 101 | "type": "text", | ||
| 102 | "primaryKey": false, | ||
| 103 | "notNull": true | ||
| 104 | }, | ||
| 105 | "slug": { | ||
| 106 | "name": "slug", | ||
| 107 | "type": "text", | ||
| 108 | "primaryKey": false, | ||
| 109 | "notNull": true | ||
| 110 | }, | ||
| 111 | "created_at": { | ||
| 112 | "name": "created_at", | ||
| 113 | "type": "timestamp with time zone", | ||
| 114 | "primaryKey": false, | ||
| 115 | "notNull": true, | ||
| 116 | "default": "now()" | ||
| 117 | } | ||
| 118 | }, | ||
| 119 | "indexes": { | ||
| 120 | "organizations_slug_unique": { | ||
| 121 | "name": "organizations_slug_unique", | ||
| 122 | "columns": [ | ||
| 123 | { | ||
| 124 | "expression": "slug", | ||
| 125 | "isExpression": false, | ||
| 126 | "asc": true, | ||
| 127 | "nulls": "last" | ||
| 128 | } | ||
| 129 | ], | ||
| 130 | "isUnique": true, | ||
| 131 | "concurrently": false, | ||
| 132 | "method": "btree", | ||
| 133 | "with": {} | ||
| 134 | } | ||
| 135 | }, | ||
| 136 | "foreignKeys": {}, | ||
| 137 | "compositePrimaryKeys": {}, | ||
| 138 | "uniqueConstraints": {}, | ||
| 139 | "policies": {}, | ||
| 140 | "checkConstraints": {}, | ||
| 141 | "isRLSEnabled": false | ||
| 142 | }, | ||
| 143 | "public.artifact": { | ||
| 144 | "name": "artifact", | ||
| 145 | "schema": "", | ||
| 146 | "columns": { | ||
| 147 | "id": { | ||
| 148 | "name": "id", | ||
| 149 | "type": "varchar(255)", | ||
| 150 | "primaryKey": false, | ||
| 151 | "notNull": true | ||
| 152 | }, | ||
| 153 | "title": { | ||
| 154 | "name": "title", | ||
| 155 | "type": "text", | ||
| 156 | "primaryKey": false, | ||
| 157 | "notNull": true | ||
| 158 | }, | ||
| 159 | "description": { | ||
| 160 | "name": "description", | ||
| 161 | "type": "text", | ||
| 162 | "primaryKey": false, | ||
| 163 | "notNull": false | ||
| 164 | }, | ||
| 165 | "code": { | ||
| 166 | "name": "code", | ||
| 167 | "type": "text", | ||
| 168 | "primaryKey": false, | ||
| 169 | "notNull": true | ||
| 170 | }, | ||
| 171 | "bindings": { | ||
| 172 | "name": "bindings", | ||
| 173 | "type": "json", | ||
| 174 | "primaryKey": false, | ||
| 175 | "notNull": false | ||
| 176 | }, | ||
| 177 | "preview": { | ||
| 178 | "name": "preview", | ||
| 179 | "type": "text", | ||
| 180 | "primaryKey": false, | ||
| 181 | "notNull": false | ||
| 182 | }, | ||
| 183 | "created_at": { | ||
| 184 | "name": "created_at", | ||
| 185 | "type": "timestamp", | ||
| 186 | "primaryKey": false, | ||
| 187 | "notNull": true | ||
| 188 | }, | ||
| 189 | "updated_at": { | ||
| 190 | "name": "updated_at", | ||
| 191 | "type": "timestamp", | ||
| 192 | "primaryKey": false, | ||
| 193 | "notNull": true | ||
| 194 | }, | ||
| 195 | "row_id": { | ||
| 196 | "name": "row_id", | ||
| 197 | "type": "varchar(255)", | ||
| 198 | "primaryKey": true, | ||
| 199 | "notNull": true | ||
| 200 | }, | ||
| 201 | "tenant": { | ||
| 202 | "name": "tenant", | ||
| 203 | "type": "varchar(255)", | ||
| 204 | "primaryKey": false, | ||
| 205 | "notNull": true | ||
| 206 | }, | ||
| 207 | "owner": { | ||
| 208 | "name": "owner", | ||
| 209 | "type": "varchar(255)", | ||
| 210 | "primaryKey": false, | ||
| 211 | "notNull": true | ||
| 212 | }, | ||
| 213 | "subject": { | ||
| 214 | "name": "subject", | ||
| 215 | "type": "varchar(255)", | ||
| 216 | "primaryKey": false, | ||
| 217 | "notNull": true | ||
| 218 | } | ||
| 219 | }, | ||
| 220 | "indexes": { | ||
| 221 | "artifact_uidx": { | ||
| 222 | "name": "artifact_uidx", | ||
| 223 | "columns": [ | ||
| 224 | { | ||
| 225 | "expression": "tenant", | ||
| 226 | "isExpression": false, | ||
| 227 | "asc": true, | ||
| 228 | "nulls": "last" | ||
| 229 | }, | ||
| 230 | { | ||
| 231 | "expression": "owner", | ||
| 232 | "isExpression": false, | ||
| 233 | "asc": true, | ||
| 234 | "nulls": "last" | ||
| 235 | }, | ||
| 236 | { | ||
| 237 | "expression": "subject", | ||
| 238 | "isExpression": false, | ||
| 239 | "asc": true, | ||
| 240 | "nulls": "last" | ||
| 241 | }, | ||
| 242 | { | ||
| 243 | "expression": "id", | ||
| 244 | "isExpression": false, | ||
| 245 | "asc": true, | ||
| 246 | "nulls": "last" | ||
| 247 | } | ||
| 248 | ], | ||
| 249 | "isUnique": true, | ||
| 250 | "concurrently": false, | ||
| 251 | "method": "btree", | ||
| 252 | "with": {} | ||
| 253 | } | ||
| 254 | }, | ||
| 255 | "foreignKeys": {}, | ||
| 256 | "compositePrimaryKeys": {}, | ||
| 257 | "uniqueConstraints": {}, | ||
| 258 | "policies": {}, | ||
| 259 | "checkConstraints": {}, | ||
| 260 | "isRLSEnabled": false | ||
| 261 | }, | ||
| 262 | "public.blob": { | ||
| 263 | "name": "blob", | ||
| 264 | "schema": "", | ||
| 265 | "columns": { | ||
| 266 | "namespace": { | ||
| 267 | "name": "namespace", | ||
| 268 | "type": "varchar(255)", | ||
| 269 | "primaryKey": false, | ||
| 270 | "notNull": true | ||
| 271 | }, | ||
| 272 | "key": { | ||
| 273 | "name": "key", | ||
| 274 | "type": "varchar(255)", | ||
| 275 | "primaryKey": false, | ||
| 276 | "notNull": true | ||
| 277 | }, | ||
| 278 | "value": { | ||
| 279 | "name": "value", | ||
| 280 | "type": "text", | ||
| 281 | "primaryKey": false, | ||
| 282 | "notNull": true | ||
| 283 | }, | ||
| 284 | "row_id": { | ||
| 285 | "name": "row_id", | ||
| 286 | "type": "varchar(255)", | ||
| 287 | "primaryKey": true, | ||
| 288 | "notNull": true | ||
| 289 | }, | ||
| 290 | "id": { | ||
| 291 | "name": "id", | ||
| 292 | "type": "varchar(255)", | ||
| 293 | "primaryKey": false, | ||
| 294 | "notNull": true | ||
| 295 | } | ||
| 296 | }, | ||
| 297 | "indexes": { | ||
| 298 | "blob_id_uidx": { | ||
| 299 | "name": "blob_id_uidx", | ||
| 300 | "columns": [ | ||
| 301 | { | ||
| 302 | "expression": "id", | ||
| 303 | "isExpression": false, | ||
| 304 | "asc": true, | ||
| 305 | "nulls": "last" | ||
| 306 | } | ||
| 307 | ], | ||
| 308 | "isUnique": true, | ||
| 309 | "concurrently": false, | ||
| 310 | "method": "btree", | ||
| 311 | "with": {} | ||
| 312 | } | ||
| 313 | }, | ||
| 314 | "foreignKeys": {}, | ||
| 315 | "compositePrimaryKeys": {}, | ||
| 316 | "uniqueConstraints": {}, | ||
| 317 | "policies": {}, | ||
| 318 | "checkConstraints": {}, | ||
| 319 | "isRLSEnabled": false | ||
| 320 | }, | ||
| 321 | "public.connection": { | ||
| 322 | "name": "connection", | ||
| 323 | "schema": "", | ||
| 324 | "columns": { | ||
| 325 | "integration": { | ||
| 326 | "name": "integration", | ||
| 327 | "type": "varchar(255)", | ||
| 328 | "primaryKey": false, | ||
| 329 | "notNull": true | ||
| 330 | }, | ||
| 331 | "name": { | ||
| 332 | "name": "name", | ||
| 333 | "type": "varchar(255)", | ||
| 334 | "primaryKey": false, | ||
| 335 | "notNull": true | ||
| 336 | }, | ||
| 337 | "template": { | ||
| 338 | "name": "template", | ||
| 339 | "type": "text", | ||
| 340 | "primaryKey": false, | ||
| 341 | "notNull": true | ||
| 342 | }, | ||
| 343 | "provider": { | ||
| 344 | "name": "provider", | ||
| 345 | "type": "text", | ||
| 346 | "primaryKey": false, | ||
| 347 | "notNull": true | ||
| 348 | }, | ||
| 349 | "item_ids": { | ||
| 350 | "name": "item_ids", | ||
| 351 | "type": "json", | ||
| 352 | "primaryKey": false, | ||
| 353 | "notNull": true | ||
| 354 | }, | ||
| 355 | "credential_write": { | ||
| 356 | "name": "credential_write", | ||
| 357 | "type": "json", | ||
| 358 | "primaryKey": false, | ||
| 359 | "notNull": false | ||
| 360 | }, | ||
| 361 | "identity_label": { | ||
| 362 | "name": "identity_label", | ||
| 363 | "type": "text", | ||
| 364 | "primaryKey": false, | ||
| 365 | "notNull": false | ||
| 366 | }, | ||
| 367 | "description": { | ||
| 368 | "name": "description", | ||
| 369 | "type": "text", | ||
| 370 | "primaryKey": false, | ||
| 371 | "notNull": false | ||
| 372 | }, | ||
| 373 | "last_health": { | ||
| 374 | "name": "last_health", | ||
| 375 | "type": "json", | ||
| 376 | "primaryKey": false, | ||
| 377 | "notNull": false | ||
| 378 | }, | ||
| 379 | "tools_synced_at": { | ||
| 380 | "name": "tools_synced_at", | ||
| 381 | "type": "bigint", | ||
| 382 | "primaryKey": false, | ||
| 383 | "notNull": false | ||
| 384 | }, | ||
| 385 | "oauth_client": { | ||
| 386 | "name": "oauth_client", | ||
| 387 | "type": "text", | ||
| 388 | "primaryKey": false, | ||
| 389 | "notNull": false | ||
| 390 | }, | ||
| 391 | "oauth_client_owner": { | ||
| 392 | "name": "oauth_client_owner", | ||
| 393 | "type": "text", | ||
| 394 | "primaryKey": false, | ||
| 395 | "notNull": false | ||
| 396 | }, | ||
| 397 | "refresh_item_id": { | ||
| 398 | "name": "refresh_item_id", | ||
| 399 | "type": "text", | ||
| 400 | "primaryKey": false, | ||
| 401 | "notNull": false | ||
| 402 | }, | ||
| 403 | "expires_at": { | ||
| 404 | "name": "expires_at", | ||
| 405 | "type": "bigint", | ||
| 406 | "primaryKey": false, | ||
| 407 | "notNull": false | ||
| 408 | }, | ||
| 409 | "oauth_scope": { | ||
| 410 | "name": "oauth_scope", | ||
| 411 | "type": "text", | ||
| 412 | "primaryKey": false, | ||
| 413 | "notNull": false | ||
| 414 | }, | ||
| 415 | "oauth_token_url": { | ||
| 416 | "name": "oauth_token_url", | ||
| 417 | "type": "text", | ||
| 418 | "primaryKey": false, | ||
| 419 | "notNull": false | ||
| 420 | }, | ||
| 421 | "provider_state": { | ||
| 422 | "name": "provider_state", | ||
| 423 | "type": "json", | ||
| 424 | "primaryKey": false, | ||
| 425 | "notNull": false | ||
| 426 | }, | ||
| 427 | "created_at": { | ||
| 428 | "name": "created_at", | ||
| 429 | "type": "timestamp", | ||
| 430 | "primaryKey": false, | ||
| 431 | "notNull": true | ||
| 432 | }, | ||
| 433 | "updated_at": { | ||
| 434 | "name": "updated_at", | ||
| 435 | "type": "timestamp", | ||
| 436 | "primaryKey": false, | ||
| 437 | "notNull": true | ||
| 438 | }, | ||
| 439 | "row_id": { | ||
| 440 | "name": "row_id", | ||
| 441 | "type": "varchar(255)", | ||
| 442 | "primaryKey": true, | ||
| 443 | "notNull": true | ||
| 444 | }, | ||
| 445 | "tenant": { | ||
| 446 | "name": "tenant", | ||
| 447 | "type": "varchar(255)", | ||
| 448 | "primaryKey": false, | ||
| 449 | "notNull": true | ||
| 450 | }, | ||
| 451 | "owner": { | ||
| 452 | "name": "owner", | ||
| 453 | "type": "varchar(255)", | ||
| 454 | "primaryKey": false, | ||
| 455 | "notNull": true | ||
| 456 | }, | ||
| 457 | "subject": { | ||
| 458 | "name": "subject", | ||
| 459 | "type": "varchar(255)", | ||
| 460 | "primaryKey": false, | ||
| 461 | "notNull": true | ||
| 462 | } | ||
| 463 | }, | ||
| 464 | "indexes": { | ||
| 465 | "connection_uidx": { | ||
| 466 | "name": "connection_uidx", | ||
| 467 | "columns": [ | ||
| 468 | { | ||
| 469 | "expression": "tenant", | ||
| 470 | "isExpression": false, | ||
| 471 | "asc": true, | ||
| 472 | "nulls": "last" | ||
| 473 | }, | ||
| 474 | { | ||
| 475 | "expression": "owner", | ||
| 476 | "isExpression": false, | ||
| 477 | "asc": true, | ||
| 478 | "nulls": "last" | ||
| 479 | }, | ||
| 480 | { | ||
| 481 | "expression": "subject", | ||
| 482 | "isExpression": false, | ||
| 483 | "asc": true, | ||
| 484 | "nulls": "last" | ||
| 485 | }, | ||
| 486 | { | ||
| 487 | "expression": "integration", | ||
| 488 | "isExpression": false, | ||
| 489 | "asc": true, | ||
| 490 | "nulls": "last" | ||
| 491 | }, | ||
| 492 | { | ||
| 493 | "expression": "name", | ||
| 494 | "isExpression": false, | ||
| 495 | "asc": true, | ||
| 496 | "nulls": "last" | ||
| 497 | } | ||
| 498 | ], | ||
| 499 | "isUnique": true, | ||
| 500 | "concurrently": false, | ||
| 501 | "method": "btree", | ||
| 502 | "with": {} | ||
| 503 | } | ||
| 504 | }, | ||
| 505 | "foreignKeys": {}, | ||
| 506 | "compositePrimaryKeys": {}, | ||
| 507 | "uniqueConstraints": {}, | ||
| 508 | "policies": {}, | ||
| 509 | "checkConstraints": {}, | ||
| 510 | "isRLSEnabled": false | ||
| 511 | }, | ||
| 512 | "public.definition": { | ||
| 513 | "name": "definition", | ||
| 514 | "schema": "", | ||
| 515 | "columns": { | ||
| 516 | "integration": { | ||
| 517 | "name": "integration", | ||
| 518 | "type": "varchar(255)", | ||
| 519 | "primaryKey": false, | ||
| 520 | "notNull": true | ||
| 521 | }, | ||
| 522 | "connection": { | ||
| 523 | "name": "connection", | ||
| 524 | "type": "varchar(255)", | ||
| 525 | "primaryKey": false, | ||
| 526 | "notNull": true | ||
| 527 | }, | ||
| 528 | "plugin_id": { | ||
| 529 | "name": "plugin_id", | ||
| 530 | "type": "text", | ||
| 531 | "primaryKey": false, | ||
| 532 | "notNull": true | ||
| 533 | }, | ||
| 534 | "name": { | ||
| 535 | "name": "name", | ||
| 536 | "type": "text", | ||
| 537 | "primaryKey": false, | ||
| 538 | "notNull": true | ||
| 539 | }, | ||
| 540 | "schema": { | ||
| 541 | "name": "schema", | ||
| 542 | "type": "json", | ||
| 543 | "primaryKey": false, | ||
| 544 | "notNull": true | ||
| 545 | }, | ||
| 546 | "created_at": { | ||
| 547 | "name": "created_at", | ||
| 548 | "type": "timestamp", | ||
| 549 | "primaryKey": false, | ||
| 550 | "notNull": true | ||
| 551 | }, | ||
| 552 | "row_id": { | ||
| 553 | "name": "row_id", | ||
| 554 | "type": "varchar(255)", | ||
| 555 | "primaryKey": true, | ||
| 556 | "notNull": true | ||
| 557 | }, | ||
| 558 | "tenant": { | ||
| 559 | "name": "tenant", | ||
| 560 | "type": "varchar(255)", | ||
| 561 | "primaryKey": false, | ||
| 562 | "notNull": true | ||
| 563 | }, | ||
| 564 | "owner": { | ||
| 565 | "name": "owner", | ||
| 566 | "type": "varchar(255)", | ||
| 567 | "primaryKey": false, | ||
| 568 | "notNull": true | ||
| 569 | }, | ||
| 570 | "subject": { | ||
| 571 | "name": "subject", | ||
| 572 | "type": "varchar(255)", | ||
| 573 | "primaryKey": false, | ||
| 574 | "notNull": true | ||
| 575 | } | ||
| 576 | }, | ||
| 577 | "indexes": { | ||
| 578 | "definition_uidx": { | ||
| 579 | "name": "definition_uidx", | ||
| 580 | "columns": [ | ||
| 581 | { | ||
| 582 | "expression": "tenant", | ||
| 583 | "isExpression": false, | ||
| 584 | "asc": true, | ||
| 585 | "nulls": "last" | ||
| 586 | }, | ||
| 587 | { | ||
| 588 | "expression": "owner", | ||
| 589 | "isExpression": false, | ||
| 590 | "asc": true, | ||
| 591 | "nulls": "last" | ||
| 592 | }, | ||
| 593 | { | ||
| 594 | "expression": "subject", | ||
| 595 | "isExpression": false, | ||
| 596 | "asc": true, | ||
| 597 | "nulls": "last" | ||
| 598 | }, | ||
| 599 | { | ||
| 600 | "expression": "integration", | ||
| 601 | "isExpression": false, | ||
| 602 | "asc": true, | ||
| 603 | "nulls": "last" | ||
| 604 | }, | ||
| 605 | { | ||
| 606 | "expression": "connection", | ||
| 607 | "isExpression": false, | ||
| 608 | "asc": true, | ||
| 609 | "nulls": "last" | ||
| 610 | }, | ||
| 611 | { | ||
| 612 | "expression": "name", | ||
| 613 | "isExpression": false, | ||
| 614 | "asc": true, | ||
| 615 | "nulls": "last" | ||
| 616 | } | ||
| 617 | ], | ||
| 618 | "isUnique": true, | ||
| 619 | "concurrently": false, | ||
| 620 | "method": "btree", | ||
| 621 | "with": {} | ||
| 622 | } | ||
| 623 | }, | ||
| 624 | "foreignKeys": {}, | ||
| 625 | "compositePrimaryKeys": {}, | ||
| 626 | "uniqueConstraints": {}, | ||
| 627 | "policies": {}, | ||
| 628 | "checkConstraints": {}, | ||
| 629 | "isRLSEnabled": false | ||
| 630 | }, | ||
| 631 | "public.integration": { | ||
| 632 | "name": "integration", | ||
| 633 | "schema": "", | ||
| 634 | "columns": { | ||
| 635 | "slug": { | ||
| 636 | "name": "slug", | ||
| 637 | "type": "varchar(255)", | ||
| 638 | "primaryKey": false, | ||
| 639 | "notNull": true | ||
| 640 | }, | ||
| 641 | "plugin_id": { | ||
| 642 | "name": "plugin_id", | ||
| 643 | "type": "text", | ||
| 644 | "primaryKey": false, | ||
| 645 | "notNull": true | ||
| 646 | }, | ||
| 647 | "name": { | ||
| 648 | "name": "name", | ||
| 649 | "type": "text", | ||
| 650 | "primaryKey": false, | ||
| 651 | "notNull": false | ||
| 652 | }, | ||
| 653 | "description": { | ||
| 654 | "name": "description", | ||
| 655 | "type": "text", | ||
| 656 | "primaryKey": false, | ||
| 657 | "notNull": false | ||
| 658 | }, | ||
| 659 | "config": { | ||
| 660 | "name": "config", | ||
| 661 | "type": "json", | ||
| 662 | "primaryKey": false, | ||
| 663 | "notNull": false | ||
| 664 | }, | ||
| 665 | "health_check": { | ||
| 666 | "name": "health_check", | ||
| 667 | "type": "json", | ||
| 668 | "primaryKey": false, | ||
| 669 | "notNull": false | ||
| 670 | }, | ||
| 671 | "config_revised_at": { | ||
| 672 | "name": "config_revised_at", | ||
| 673 | "type": "bigint", | ||
| 674 | "primaryKey": false, | ||
| 675 | "notNull": false | ||
| 676 | }, | ||
| 677 | "can_remove": { | ||
| 678 | "name": "can_remove", | ||
| 679 | "type": "boolean", | ||
| 680 | "primaryKey": false, | ||
| 681 | "notNull": true, | ||
| 682 | "default": true | ||
| 683 | }, | ||
| 684 | "can_refresh": { | ||
| 685 | "name": "can_refresh", | ||
| 686 | "type": "boolean", | ||
| 687 | "primaryKey": false, | ||
| 688 | "notNull": true, | ||
| 689 | "default": false | ||
| 690 | }, | ||
| 691 | "created_at": { | ||
| 692 | "name": "created_at", | ||
| 693 | "type": "timestamp", | ||
| 694 | "primaryKey": false, | ||
| 695 | "notNull": true | ||
| 696 | }, | ||
| 697 | "updated_at": { | ||
| 698 | "name": "updated_at", | ||
| 699 | "type": "timestamp", | ||
| 700 | "primaryKey": false, | ||
| 701 | "notNull": true | ||
| 702 | }, | ||
| 703 | "row_id": { | ||
| 704 | "name": "row_id", | ||
| 705 | "type": "varchar(255)", | ||
| 706 | "primaryKey": true, | ||
| 707 | "notNull": true | ||
| 708 | }, | ||
| 709 | "tenant": { | ||
| 710 | "name": "tenant", | ||
| 711 | "type": "varchar(255)", | ||
| 712 | "primaryKey": false, | ||
| 713 | "notNull": true | ||
| 714 | } | ||
| 715 | }, | ||
| 716 | "indexes": { | ||
| 717 | "integration_uidx": { | ||
| 718 | "name": "integration_uidx", | ||
| 719 | "columns": [ | ||
| 720 | { | ||
| 721 | "expression": "tenant", | ||
| 722 | "isExpression": false, | ||
| 723 | "asc": true, | ||
| 724 | "nulls": "last" | ||
| 725 | }, | ||
| 726 | { | ||
| 727 | "expression": "slug", | ||
| 728 | "isExpression": false, | ||
| 729 | "asc": true, | ||
| 730 | "nulls": "last" | ||
| 731 | } | ||
| 732 | ], | ||
| 733 | "isUnique": true, | ||
| 734 | "concurrently": false, | ||
| 735 | "method": "btree", | ||
| 736 | "with": {} | ||
| 737 | } | ||
| 738 | }, | ||
| 739 | "foreignKeys": {}, | ||
| 740 | "compositePrimaryKeys": {}, | ||
| 741 | "uniqueConstraints": {}, | ||
| 742 | "policies": {}, | ||
| 743 | "checkConstraints": {}, | ||
| 744 | "isRLSEnabled": false | ||
| 745 | }, | ||
| 746 | "public.oauth_client": { | ||
| 747 | "name": "oauth_client", | ||
| 748 | "schema": "", | ||
| 749 | "columns": { | ||
| 750 | "slug": { | ||
| 751 | "name": "slug", | ||
| 752 | "type": "varchar(255)", | ||
| 753 | "primaryKey": false, | ||
| 754 | "notNull": true | ||
| 755 | }, | ||
| 756 | "authorization_url": { | ||
| 757 | "name": "authorization_url", | ||
| 758 | "type": "text", | ||
| 759 | "primaryKey": false, | ||
| 760 | "notNull": true | ||
| 761 | }, | ||
| 762 | "token_url": { | ||
| 763 | "name": "token_url", | ||
| 764 | "type": "text", | ||
| 765 | "primaryKey": false, | ||
| 766 | "notNull": true | ||
| 767 | }, | ||
| 768 | "grant": { | ||
| 769 | "name": "grant", | ||
| 770 | "type": "text", | ||
| 771 | "primaryKey": false, | ||
| 772 | "notNull": true | ||
| 773 | }, | ||
| 774 | "client_id": { | ||
| 775 | "name": "client_id", | ||
| 776 | "type": "text", | ||
| 777 | "primaryKey": false, | ||
| 778 | "notNull": true | ||
| 779 | }, | ||
| 780 | "client_secret_item_id": { | ||
| 781 | "name": "client_secret_item_id", | ||
| 782 | "type": "text", | ||
| 783 | "primaryKey": false, | ||
| 784 | "notNull": false | ||
| 785 | }, | ||
| 786 | "credential_write": { | ||
| 787 | "name": "credential_write", | ||
| 788 | "type": "json", | ||
| 789 | "primaryKey": false, | ||
| 790 | "notNull": false | ||
| 791 | }, | ||
| 792 | "token_endpoint_auth_method": { | ||
| 793 | "name": "token_endpoint_auth_method", | ||
| 794 | "type": "text", | ||
| 795 | "primaryKey": false, | ||
| 796 | "notNull": false | ||
| 797 | }, | ||
| 798 | "resource": { | ||
| 799 | "name": "resource", | ||
| 800 | "type": "text", | ||
| 801 | "primaryKey": false, | ||
| 802 | "notNull": false | ||
| 803 | }, | ||
| 804 | "origin_kind": { | ||
| 805 | "name": "origin_kind", | ||
| 806 | "type": "text", | ||
| 807 | "primaryKey": false, | ||
| 808 | "notNull": false | ||
| 809 | }, | ||
| 810 | "origin_integration": { | ||
| 811 | "name": "origin_integration", | ||
| 812 | "type": "text", | ||
| 813 | "primaryKey": false, | ||
| 814 | "notNull": false | ||
| 815 | }, | ||
| 816 | "origin_issuer": { | ||
| 817 | "name": "origin_issuer", | ||
| 818 | "type": "text", | ||
| 819 | "primaryKey": false, | ||
| 820 | "notNull": false | ||
| 821 | }, | ||
| 822 | "origin_redirect_uri": { | ||
| 823 | "name": "origin_redirect_uri", | ||
| 824 | "type": "text", | ||
| 825 | "primaryKey": false, | ||
| 826 | "notNull": false | ||
| 827 | }, | ||
| 828 | "created_at": { | ||
| 829 | "name": "created_at", | ||
| 830 | "type": "timestamp", | ||
| 831 | "primaryKey": false, | ||
| 832 | "notNull": true | ||
| 833 | }, | ||
| 834 | "row_id": { | ||
| 835 | "name": "row_id", | ||
| 836 | "type": "varchar(255)", | ||
| 837 | "primaryKey": true, | ||
| 838 | "notNull": true | ||
| 839 | }, | ||
| 840 | "tenant": { | ||
| 841 | "name": "tenant", | ||
| 842 | "type": "varchar(255)", | ||
| 843 | "primaryKey": false, | ||
| 844 | "notNull": true | ||
| 845 | }, | ||
| 846 | "owner": { | ||
| 847 | "name": "owner", | ||
| 848 | "type": "varchar(255)", | ||
| 849 | "primaryKey": false, | ||
| 850 | "notNull": true | ||
| 851 | }, | ||
| 852 | "subject": { | ||
| 853 | "name": "subject", | ||
| 854 | "type": "varchar(255)", | ||
| 855 | "primaryKey": false, | ||
| 856 | "notNull": true | ||
| 857 | } | ||
| 858 | }, | ||
| 859 | "indexes": { | ||
| 860 | "oauth_client_uidx": { | ||
| 861 | "name": "oauth_client_uidx", | ||
| 862 | "columns": [ | ||
| 863 | { | ||
| 864 | "expression": "tenant", | ||
| 865 | "isExpression": false, | ||
| 866 | "asc": true, | ||
| 867 | "nulls": "last" | ||
| 868 | }, | ||
| 869 | { | ||
| 870 | "expression": "owner", | ||
| 871 | "isExpression": false, | ||
| 872 | "asc": true, | ||
| 873 | "nulls": "last" | ||
| 874 | }, | ||
| 875 | { | ||
| 876 | "expression": "subject", | ||
| 877 | "isExpression": false, | ||
| 878 | "asc": true, | ||
| 879 | "nulls": "last" | ||
| 880 | }, | ||
| 881 | { | ||
| 882 | "expression": "slug", | ||
| 883 | "isExpression": false, | ||
| 884 | "asc": true, | ||
| 885 | "nulls": "last" | ||
| 886 | } | ||
| 887 | ], | ||
| 888 | "isUnique": true, | ||
| 889 | "concurrently": false, | ||
| 890 | "method": "btree", | ||
| 891 | "with": {} | ||
| 892 | } | ||
| 893 | }, | ||
| 894 | "foreignKeys": {}, | ||
| 895 | "compositePrimaryKeys": {}, | ||
| 896 | "uniqueConstraints": {}, | ||
| 897 | "policies": {}, | ||
| 898 | "checkConstraints": {}, | ||
| 899 | "isRLSEnabled": false | ||
| 900 | }, | ||
| 901 | "public.oauth_session": { | ||
| 902 | "name": "oauth_session", | ||
| 903 | "schema": "", | ||
| 904 | "columns": { | ||
| 905 | "state": { | ||
| 906 | "name": "state", | ||
| 907 | "type": "varchar(255)", | ||
| 908 | "primaryKey": false, | ||
| 909 | "notNull": true | ||
| 910 | }, | ||
| 911 | "client_slug": { | ||
| 912 | "name": "client_slug", | ||
| 913 | "type": "text", | ||
| 914 | "primaryKey": false, | ||
| 915 | "notNull": true | ||
| 916 | }, | ||
| 917 | "integration": { | ||
| 918 | "name": "integration", | ||
| 919 | "type": "text", | ||
| 920 | "primaryKey": false, | ||
| 921 | "notNull": true | ||
| 922 | }, | ||
| 923 | "name": { | ||
| 924 | "name": "name", | ||
| 925 | "type": "text", | ||
| 926 | "primaryKey": false, | ||
| 927 | "notNull": true | ||
| 928 | }, | ||
| 929 | "template": { | ||
| 930 | "name": "template", | ||
| 931 | "type": "text", | ||
| 932 | "primaryKey": false, | ||
| 933 | "notNull": true | ||
| 934 | }, | ||
| 935 | "redirect_url": { | ||
| 936 | "name": "redirect_url", | ||
| 937 | "type": "text", | ||
| 938 | "primaryKey": false, | ||
| 939 | "notNull": true | ||
| 940 | }, | ||
| 941 | "pkce_verifier": { | ||
| 942 | "name": "pkce_verifier", | ||
| 943 | "type": "text", | ||
| 944 | "primaryKey": false, | ||
| 945 | "notNull": false | ||
| 946 | }, | ||
| 947 | "identity_label": { | ||
| 948 | "name": "identity_label", | ||
| 949 | "type": "text", | ||
| 950 | "primaryKey": false, | ||
| 951 | "notNull": false | ||
| 952 | }, | ||
| 953 | "payload": { | ||
| 954 | "name": "payload", | ||
| 955 | "type": "json", | ||
| 956 | "primaryKey": false, | ||
| 957 | "notNull": true | ||
| 958 | }, | ||
| 959 | "expires_at": { | ||
| 960 | "name": "expires_at", | ||
| 961 | "type": "bigint", | ||
| 962 | "primaryKey": false, | ||
| 963 | "notNull": true | ||
| 964 | }, | ||
| 965 | "created_at": { | ||
| 966 | "name": "created_at", | ||
| 967 | "type": "timestamp", | ||
| 968 | "primaryKey": false, | ||
| 969 | "notNull": true | ||
| 970 | }, | ||
| 971 | "row_id": { | ||
| 972 | "name": "row_id", | ||
| 973 | "type": "varchar(255)", | ||
| 974 | "primaryKey": true, | ||
| 975 | "notNull": true | ||
| 976 | }, | ||
| 977 | "tenant": { | ||
| 978 | "name": "tenant", | ||
| 979 | "type": "varchar(255)", | ||
| 980 | "primaryKey": false, | ||
| 981 | "notNull": true | ||
| 982 | }, | ||
| 983 | "owner": { | ||
| 984 | "name": "owner", | ||
| 985 | "type": "varchar(255)", | ||
| 986 | "primaryKey": false, | ||
| 987 | "notNull": true | ||
| 988 | }, | ||
| 989 | "subject": { | ||
| 990 | "name": "subject", | ||
| 991 | "type": "varchar(255)", | ||
| 992 | "primaryKey": false, | ||
| 993 | "notNull": true | ||
| 994 | } | ||
| 995 | }, | ||
| 996 | "indexes": { | ||
| 997 | "oauth_session_uidx": { | ||
| 998 | "name": "oauth_session_uidx", | ||
| 999 | "columns": [ | ||
| 1000 | { | ||
| 1001 | "expression": "tenant", | ||
| 1002 | "isExpression": false, | ||
| 1003 | "asc": true, | ||
| 1004 | "nulls": "last" | ||
| 1005 | }, | ||
| 1006 | { | ||
| 1007 | "expression": "state", | ||
| 1008 | "isExpression": false, | ||
| 1009 | "asc": true, | ||
| 1010 | "nulls": "last" | ||
| 1011 | } | ||
| 1012 | ], | ||
| 1013 | "isUnique": true, | ||
| 1014 | "concurrently": false, | ||
| 1015 | "method": "btree", | ||
| 1016 | "with": {} | ||
| 1017 | } | ||
| 1018 | }, | ||
| 1019 | "foreignKeys": {}, | ||
| 1020 | "compositePrimaryKeys": {}, | ||
| 1021 | "uniqueConstraints": {}, | ||
| 1022 | "policies": {}, | ||
| 1023 | "checkConstraints": {}, | ||
| 1024 | "isRLSEnabled": false | ||
| 1025 | }, | ||
| 1026 | "public.plugin_storage": { | ||
| 1027 | "name": "plugin_storage", | ||
| 1028 | "schema": "", | ||
| 1029 | "columns": { | ||
| 1030 | "plugin_id": { | ||
| 1031 | "name": "plugin_id", | ||
| 1032 | "type": "varchar(255)", | ||
| 1033 | "primaryKey": false, | ||
| 1034 | "notNull": true | ||
| 1035 | }, | ||
| 1036 | "collection": { | ||
| 1037 | "name": "collection", | ||
| 1038 | "type": "varchar(255)", | ||
| 1039 | "primaryKey": false, | ||
| 1040 | "notNull": true | ||
| 1041 | }, | ||
| 1042 | "key": { | ||
| 1043 | "name": "key", | ||
| 1044 | "type": "varchar(255)", | ||
| 1045 | "primaryKey": false, | ||
| 1046 | "notNull": true | ||
| 1047 | }, | ||
| 1048 | "data": { | ||
| 1049 | "name": "data", | ||
| 1050 | "type": "json", | ||
| 1051 | "primaryKey": false, | ||
| 1052 | "notNull": true | ||
| 1053 | }, | ||
| 1054 | "created_at": { | ||
| 1055 | "name": "created_at", | ||
| 1056 | "type": "timestamp", | ||
| 1057 | "primaryKey": false, | ||
| 1058 | "notNull": true | ||
| 1059 | }, | ||
| 1060 | "updated_at": { | ||
| 1061 | "name": "updated_at", | ||
| 1062 | "type": "timestamp", | ||
| 1063 | "primaryKey": false, | ||
| 1064 | "notNull": true | ||
| 1065 | }, | ||
| 1066 | "row_id": { | ||
| 1067 | "name": "row_id", | ||
| 1068 | "type": "varchar(255)", | ||
| 1069 | "primaryKey": true, | ||
| 1070 | "notNull": true | ||
| 1071 | }, | ||
| 1072 | "tenant": { | ||
| 1073 | "name": "tenant", | ||
| 1074 | "type": "varchar(255)", | ||
| 1075 | "primaryKey": false, | ||
| 1076 | "notNull": true | ||
| 1077 | }, | ||
| 1078 | "owner": { | ||
| 1079 | "name": "owner", | ||
| 1080 | "type": "varchar(255)", | ||
| 1081 | "primaryKey": false, | ||
| 1082 | "notNull": true | ||
| 1083 | }, | ||
| 1084 | "subject": { | ||
| 1085 | "name": "subject", | ||
| 1086 | "type": "varchar(255)", | ||
| 1087 | "primaryKey": false, | ||
| 1088 | "notNull": true | ||
| 1089 | } | ||
| 1090 | }, | ||
| 1091 | "indexes": { | ||
| 1092 | "plugin_storage_uidx": { | ||
| 1093 | "name": "plugin_storage_uidx", | ||
| 1094 | "columns": [ | ||
| 1095 | { | ||
| 1096 | "expression": "tenant", | ||
| 1097 | "isExpression": false, | ||
| 1098 | "asc": true, | ||
| 1099 | "nulls": "last" | ||
| 1100 | }, | ||
| 1101 | { | ||
| 1102 | "expression": "owner", | ||
| 1103 | "isExpression": false, | ||
| 1104 | "asc": true, | ||
| 1105 | "nulls": "last" | ||
| 1106 | }, | ||
| 1107 | { | ||
| 1108 | "expression": "subject", | ||
| 1109 | "isExpression": false, | ||
| 1110 | "asc": true, | ||
| 1111 | "nulls": "last" | ||
| 1112 | }, | ||
| 1113 | { | ||
| 1114 | "expression": "plugin_id", | ||
| 1115 | "isExpression": false, | ||
| 1116 | "asc": true, | ||
| 1117 | "nulls": "last" | ||
| 1118 | }, | ||
| 1119 | { | ||
| 1120 | "expression": "collection", | ||
| 1121 | "isExpression": false, | ||
| 1122 | "asc": true, | ||
| 1123 | "nulls": "last" | ||
| 1124 | }, | ||
| 1125 | { | ||
| 1126 | "expression": "key", | ||
| 1127 | "isExpression": false, | ||
| 1128 | "asc": true, | ||
| 1129 | "nulls": "last" | ||
| 1130 | } | ||
| 1131 | ], | ||
| 1132 | "isUnique": true, | ||
| 1133 | "concurrently": false, | ||
| 1134 | "method": "btree", | ||
| 1135 | "with": {} | ||
| 1136 | } | ||
| 1137 | }, | ||
| 1138 | "foreignKeys": {}, | ||
| 1139 | "compositePrimaryKeys": {}, | ||
| 1140 | "uniqueConstraints": {}, | ||
| 1141 | "policies": {}, | ||
| 1142 | "checkConstraints": {}, | ||
| 1143 | "isRLSEnabled": false | ||
| 1144 | }, | ||
| 1145 | "public.private_executor_cloud_settings": { | ||
| 1146 | "name": "private_executor_cloud_settings", | ||
| 1147 | "schema": "", | ||
| 1148 | "columns": { | ||
| 1149 | "id": { | ||
| 1150 | "name": "id", | ||
| 1151 | "type": "varchar(255)", | ||
| 1152 | "primaryKey": true, | ||
| 1153 | "notNull": true | ||
| 1154 | }, | ||
| 1155 | "version": { | ||
| 1156 | "name": "version", | ||
| 1157 | "type": "varchar(255)", | ||
| 1158 | "primaryKey": false, | ||
| 1159 | "notNull": true, | ||
| 1160 | "default": "'1.0.0'" | ||
| 1161 | } | ||
| 1162 | }, | ||
| 1163 | "indexes": {}, | ||
| 1164 | "foreignKeys": {}, | ||
| 1165 | "compositePrimaryKeys": {}, | ||
| 1166 | "uniqueConstraints": {}, | ||
| 1167 | "policies": {}, | ||
| 1168 | "checkConstraints": {}, | ||
| 1169 | "isRLSEnabled": false | ||
| 1170 | }, | ||
| 1171 | "public.skill": { | ||
| 1172 | "name": "skill", | ||
| 1173 | "schema": "", | ||
| 1174 | "columns": { | ||
| 1175 | "name": { | ||
| 1176 | "name": "name", | ||
| 1177 | "type": "varchar(255)", | ||
| 1178 | "primaryKey": false, | ||
| 1179 | "notNull": true | ||
| 1180 | }, | ||
| 1181 | "description": { | ||
| 1182 | "name": "description", | ||
| 1183 | "type": "text", | ||
| 1184 | "primaryKey": false, | ||
| 1185 | "notNull": true | ||
| 1186 | }, | ||
| 1187 | "frontmatter": { | ||
| 1188 | "name": "frontmatter", | ||
| 1189 | "type": "json", | ||
| 1190 | "primaryKey": false, | ||
| 1191 | "notNull": true | ||
| 1192 | }, | ||
| 1193 | "files": { | ||
| 1194 | "name": "files", | ||
| 1195 | "type": "json", | ||
| 1196 | "primaryKey": false, | ||
| 1197 | "notNull": true | ||
| 1198 | }, | ||
| 1199 | "created_at": { | ||
| 1200 | "name": "created_at", | ||
| 1201 | "type": "timestamp", | ||
| 1202 | "primaryKey": false, | ||
| 1203 | "notNull": true | ||
| 1204 | }, | ||
| 1205 | "updated_at": { | ||
| 1206 | "name": "updated_at", | ||
| 1207 | "type": "timestamp", | ||
| 1208 | "primaryKey": false, | ||
| 1209 | "notNull": true | ||
| 1210 | }, | ||
| 1211 | "row_id": { | ||
| 1212 | "name": "row_id", | ||
| 1213 | "type": "varchar(255)", | ||
| 1214 | "primaryKey": true, | ||
| 1215 | "notNull": true | ||
| 1216 | }, | ||
| 1217 | "tenant": { | ||
| 1218 | "name": "tenant", | ||
| 1219 | "type": "varchar(255)", | ||
| 1220 | "primaryKey": false, | ||
| 1221 | "notNull": true | ||
| 1222 | }, | ||
| 1223 | "owner": { | ||
| 1224 | "name": "owner", | ||
| 1225 | "type": "varchar(255)", | ||
| 1226 | "primaryKey": false, | ||
| 1227 | "notNull": true | ||
| 1228 | }, | ||
| 1229 | "subject": { | ||
| 1230 | "name": "subject", | ||
| 1231 | "type": "varchar(255)", | ||
| 1232 | "primaryKey": false, | ||
| 1233 | "notNull": true | ||
| 1234 | } | ||
| 1235 | }, | ||
| 1236 | "indexes": { | ||
| 1237 | "skill_uidx": { | ||
| 1238 | "name": "skill_uidx", | ||
| 1239 | "columns": [ | ||
| 1240 | { | ||
| 1241 | "expression": "tenant", | ||
| 1242 | "isExpression": false, | ||
| 1243 | "asc": true, | ||
| 1244 | "nulls": "last" | ||
| 1245 | }, | ||
| 1246 | { | ||
| 1247 | "expression": "owner", | ||
| 1248 | "isExpression": false, | ||
| 1249 | "asc": true, | ||
| 1250 | "nulls": "last" | ||
| 1251 | }, | ||
| 1252 | { | ||
| 1253 | "expression": "subject", | ||
| 1254 | "isExpression": false, | ||
| 1255 | "asc": true, | ||
| 1256 | "nulls": "last" | ||
| 1257 | }, | ||
| 1258 | { | ||
| 1259 | "expression": "name", | ||
| 1260 | "isExpression": false, | ||
| 1261 | "asc": true, | ||
| 1262 | "nulls": "last" | ||
| 1263 | } | ||
| 1264 | ], | ||
| 1265 | "isUnique": true, | ||
| 1266 | "concurrently": false, | ||
| 1267 | "method": "btree", | ||
| 1268 | "with": {} | ||
| 1269 | } | ||
| 1270 | }, | ||
| 1271 | "foreignKeys": {}, | ||
| 1272 | "compositePrimaryKeys": {}, | ||
| 1273 | "uniqueConstraints": {}, | ||
| 1274 | "policies": {}, | ||
| 1275 | "checkConstraints": {}, | ||
| 1276 | "isRLSEnabled": false | ||
| 1277 | }, | ||
| 1278 | "public.subject": { | ||
| 1279 | "name": "subject", | ||
| 1280 | "schema": "", | ||
| 1281 | "columns": { | ||
| 1282 | "external_id": { | ||
| 1283 | "name": "external_id", | ||
| 1284 | "type": "varchar(255)", | ||
| 1285 | "primaryKey": false, | ||
| 1286 | "notNull": true | ||
| 1287 | }, | ||
| 1288 | "created_at": { | ||
| 1289 | "name": "created_at", | ||
| 1290 | "type": "timestamp", | ||
| 1291 | "primaryKey": false, | ||
| 1292 | "notNull": true | ||
| 1293 | }, | ||
| 1294 | "last_seen_at": { | ||
| 1295 | "name": "last_seen_at", | ||
| 1296 | "type": "bigint", | ||
| 1297 | "primaryKey": false, | ||
| 1298 | "notNull": false | ||
| 1299 | }, | ||
| 1300 | "status": { | ||
| 1301 | "name": "status", | ||
| 1302 | "type": "text", | ||
| 1303 | "primaryKey": false, | ||
| 1304 | "notNull": false | ||
| 1305 | }, | ||
| 1306 | "row_id": { | ||
| 1307 | "name": "row_id", | ||
| 1308 | "type": "varchar(255)", | ||
| 1309 | "primaryKey": true, | ||
| 1310 | "notNull": true | ||
| 1311 | }, | ||
| 1312 | "tenant": { | ||
| 1313 | "name": "tenant", | ||
| 1314 | "type": "varchar(255)", | ||
| 1315 | "primaryKey": false, | ||
| 1316 | "notNull": true | ||
| 1317 | } | ||
| 1318 | }, | ||
| 1319 | "indexes": { | ||
| 1320 | "subject_uidx": { | ||
| 1321 | "name": "subject_uidx", | ||
| 1322 | "columns": [ | ||
| 1323 | { | ||
| 1324 | "expression": "tenant", | ||
| 1325 | "isExpression": false, | ||
| 1326 | "asc": true, | ||
| 1327 | "nulls": "last" | ||
| 1328 | }, | ||
| 1329 | { | ||
| 1330 | "expression": "external_id", | ||
| 1331 | "isExpression": false, | ||
| 1332 | "asc": true, | ||
| 1333 | "nulls": "last" | ||
| 1334 | } | ||
| 1335 | ], | ||
| 1336 | "isUnique": true, | ||
| 1337 | "concurrently": false, | ||
| 1338 | "method": "btree", | ||
| 1339 | "with": {} | ||
| 1340 | } | ||
| 1341 | }, | ||
| 1342 | "foreignKeys": {}, | ||
| 1343 | "compositePrimaryKeys": {}, | ||
| 1344 | "uniqueConstraints": {}, | ||
| 1345 | "policies": {}, | ||
| 1346 | "checkConstraints": {}, | ||
| 1347 | "isRLSEnabled": false | ||
| 1348 | }, | ||
| 1349 | "public.tool": { | ||
| 1350 | "name": "tool", | ||
| 1351 | "schema": "", | ||
| 1352 | "columns": { | ||
| 1353 | "integration": { | ||
| 1354 | "name": "integration", | ||
| 1355 | "type": "varchar(255)", | ||
| 1356 | "primaryKey": false, | ||
| 1357 | "notNull": true | ||
| 1358 | }, | ||
| 1359 | "connection": { | ||
| 1360 | "name": "connection", | ||
| 1361 | "type": "varchar(255)", | ||
| 1362 | "primaryKey": false, | ||
| 1363 | "notNull": true | ||
| 1364 | }, | ||
| 1365 | "plugin_id": { | ||
| 1366 | "name": "plugin_id", | ||
| 1367 | "type": "text", | ||
| 1368 | "primaryKey": false, | ||
| 1369 | "notNull": true | ||
| 1370 | }, | ||
| 1371 | "name": { | ||
| 1372 | "name": "name", | ||
| 1373 | "type": "varchar(255)", | ||
| 1374 | "primaryKey": false, | ||
| 1375 | "notNull": true | ||
| 1376 | }, | ||
| 1377 | "description": { | ||
| 1378 | "name": "description", | ||
| 1379 | "type": "text", | ||
| 1380 | "primaryKey": false, | ||
| 1381 | "notNull": true | ||
| 1382 | }, | ||
| 1383 | "input_schema": { | ||
| 1384 | "name": "input_schema", | ||
| 1385 | "type": "json", | ||
| 1386 | "primaryKey": false, | ||
| 1387 | "notNull": false | ||
| 1388 | }, | ||
| 1389 | "output_schema": { | ||
| 1390 | "name": "output_schema", | ||
| 1391 | "type": "json", | ||
| 1392 | "primaryKey": false, | ||
| 1393 | "notNull": false | ||
| 1394 | }, | ||
| 1395 | "annotations": { | ||
| 1396 | "name": "annotations", | ||
| 1397 | "type": "json", | ||
| 1398 | "primaryKey": false, | ||
| 1399 | "notNull": false | ||
| 1400 | }, | ||
| 1401 | "created_at": { | ||
| 1402 | "name": "created_at", | ||
| 1403 | "type": "timestamp", | ||
| 1404 | "primaryKey": false, | ||
| 1405 | "notNull": true | ||
| 1406 | }, | ||
| 1407 | "updated_at": { | ||
| 1408 | "name": "updated_at", | ||
| 1409 | "type": "timestamp", | ||
| 1410 | "primaryKey": false, | ||
| 1411 | "notNull": true | ||
| 1412 | }, | ||
| 1413 | "row_id": { | ||
| 1414 | "name": "row_id", | ||
| 1415 | "type": "varchar(255)", | ||
| 1416 | "primaryKey": true, | ||
| 1417 | "notNull": true | ||
| 1418 | }, | ||
| 1419 | "tenant": { | ||
| 1420 | "name": "tenant", | ||
| 1421 | "type": "varchar(255)", | ||
| 1422 | "primaryKey": false, | ||
| 1423 | "notNull": true | ||
| 1424 | }, | ||
| 1425 | "owner": { | ||
| 1426 | "name": "owner", | ||
| 1427 | "type": "varchar(255)", | ||
| 1428 | "primaryKey": false, | ||
| 1429 | "notNull": true | ||
| 1430 | }, | ||
| 1431 | "subject": { | ||
| 1432 | "name": "subject", | ||
| 1433 | "type": "varchar(255)", | ||
| 1434 | "primaryKey": false, | ||
| 1435 | "notNull": true | ||
| 1436 | } | ||
| 1437 | }, | ||
| 1438 | "indexes": { | ||
| 1439 | "tool_uidx": { | ||
| 1440 | "name": "tool_uidx", | ||
| 1441 | "columns": [ | ||
| 1442 | { | ||
| 1443 | "expression": "tenant", | ||
| 1444 | "isExpression": false, | ||
| 1445 | "asc": true, | ||
| 1446 | "nulls": "last" | ||
| 1447 | }, | ||
| 1448 | { | ||
| 1449 | "expression": "owner", | ||
| 1450 | "isExpression": false, | ||
| 1451 | "asc": true, | ||
| 1452 | "nulls": "last" | ||
| 1453 | }, | ||
| 1454 | { | ||
| 1455 | "expression": "subject", | ||
| 1456 | "isExpression": false, | ||
| 1457 | "asc": true, | ||
| 1458 | "nulls": "last" | ||
| 1459 | }, | ||
| 1460 | { | ||
| 1461 | "expression": "integration", | ||
| 1462 | "isExpression": false, | ||
| 1463 | "asc": true, | ||
| 1464 | "nulls": "last" | ||
| 1465 | }, | ||
| 1466 | { | ||
| 1467 | "expression": "connection", | ||
| 1468 | "isExpression": false, | ||
| 1469 | "asc": true, | ||
| 1470 | "nulls": "last" | ||
| 1471 | }, | ||
| 1472 | { | ||
| 1473 | "expression": "name", | ||
| 1474 | "isExpression": false, | ||
| 1475 | "asc": true, | ||
| 1476 | "nulls": "last" | ||
| 1477 | } | ||
| 1478 | ], | ||
| 1479 | "isUnique": true, | ||
| 1480 | "concurrently": false, | ||
| 1481 | "method": "btree", | ||
| 1482 | "with": {} | ||
| 1483 | } | ||
| 1484 | }, | ||
| 1485 | "foreignKeys": {}, | ||
| 1486 | "compositePrimaryKeys": {}, | ||
| 1487 | "uniqueConstraints": {}, | ||
| 1488 | "policies": {}, | ||
| 1489 | "checkConstraints": {}, | ||
| 1490 | "isRLSEnabled": false | ||
| 1491 | }, | ||
| 1492 | "public.tool_policy": { | ||
| 1493 | "name": "tool_policy", | ||
| 1494 | "schema": "", | ||
| 1495 | "columns": { | ||
| 1496 | "id": { | ||
| 1497 | "name": "id", | ||
| 1498 | "type": "varchar(255)", | ||
| 1499 | "primaryKey": false, | ||
| 1500 | "notNull": true | ||
| 1501 | }, | ||
| 1502 | "pattern": { | ||
| 1503 | "name": "pattern", | ||
| 1504 | "type": "text", | ||
| 1505 | "primaryKey": false, | ||
| 1506 | "notNull": true | ||
| 1507 | }, | ||
| 1508 | "action": { | ||
| 1509 | "name": "action", | ||
| 1510 | "type": "text", | ||
| 1511 | "primaryKey": false, | ||
| 1512 | "notNull": true | ||
| 1513 | }, | ||
| 1514 | "position": { | ||
| 1515 | "name": "position", | ||
| 1516 | "type": "text", | ||
| 1517 | "primaryKey": false, | ||
| 1518 | "notNull": true | ||
| 1519 | }, | ||
| 1520 | "created_at": { | ||
| 1521 | "name": "created_at", | ||
| 1522 | "type": "timestamp", | ||
| 1523 | "primaryKey": false, | ||
| 1524 | "notNull": true | ||
| 1525 | }, | ||
| 1526 | "updated_at": { | ||
| 1527 | "name": "updated_at", | ||
| 1528 | "type": "timestamp", | ||
| 1529 | "primaryKey": false, | ||
| 1530 | "notNull": true | ||
| 1531 | }, | ||
| 1532 | "row_id": { | ||
| 1533 | "name": "row_id", | ||
| 1534 | "type": "varchar(255)", | ||
| 1535 | "primaryKey": true, | ||
| 1536 | "notNull": true | ||
| 1537 | }, | ||
| 1538 | "tenant": { | ||
| 1539 | "name": "tenant", | ||
| 1540 | "type": "varchar(255)", | ||
| 1541 | "primaryKey": false, | ||
| 1542 | "notNull": true | ||
| 1543 | }, | ||
| 1544 | "owner": { | ||
| 1545 | "name": "owner", | ||
| 1546 | "type": "varchar(255)", | ||
| 1547 | "primaryKey": false, | ||
| 1548 | "notNull": true | ||
| 1549 | }, | ||
| 1550 | "subject": { | ||
| 1551 | "name": "subject", | ||
| 1552 | "type": "varchar(255)", | ||
| 1553 | "primaryKey": false, | ||
| 1554 | "notNull": true | ||
| 1555 | } | ||
| 1556 | }, | ||
| 1557 | "indexes": { | ||
| 1558 | "tool_policy_uidx": { | ||
| 1559 | "name": "tool_policy_uidx", | ||
| 1560 | "columns": [ | ||
| 1561 | { | ||
| 1562 | "expression": "tenant", | ||
| 1563 | "isExpression": false, | ||
| 1564 | "asc": true, | ||
| 1565 | "nulls": "last" | ||
| 1566 | }, | ||
| 1567 | { | ||
| 1568 | "expression": "owner", | ||
| 1569 | "isExpression": false, | ||
| 1570 | "asc": true, | ||
| 1571 | "nulls": "last" | ||
| 1572 | }, | ||
| 1573 | { | ||
| 1574 | "expression": "subject", | ||
| 1575 | "isExpression": false, | ||
| 1576 | "asc": true, | ||
| 1577 | "nulls": "last" | ||
| 1578 | }, | ||
| 1579 | { | ||
| 1580 | "expression": "id", | ||
| 1581 | "isExpression": false, | ||
| 1582 | "asc": true, | ||
| 1583 | "nulls": "last" | ||
| 1584 | } | ||
| 1585 | ], | ||
| 1586 | "isUnique": true, | ||
| 1587 | "concurrently": false, | ||
| 1588 | "method": "btree", | ||
| 1589 | "with": {} | ||
| 1590 | } | ||
| 1591 | }, | ||
| 1592 | "foreignKeys": {}, | ||
| 1593 | "compositePrimaryKeys": {}, | ||
| 1594 | "uniqueConstraints": {}, | ||
| 1595 | "policies": {}, | ||
| 1596 | "checkConstraints": {}, | ||
| 1597 | "isRLSEnabled": false | ||
| 1598 | } | ||
| 1599 | }, | ||
| 1600 | "enums": {}, | ||
| 1601 | "schemas": {}, | ||
| 1602 | "sequences": {}, | ||
| 1603 | "roles": {}, | ||
| 1604 | "policies": {}, | ||
| 1605 | "views": {}, | ||
| 1606 | "_meta": { | ||
| 1607 | "columns": {}, | ||
| 1608 | "schemas": {}, | ||
| 1609 | "tables": {} | ||
| 1610 | } | ||
| 1611 | } | ||
| @@ -127,6 +127,13 @@ · 126 unmodified lines | |||
| 127 | "when": 1788287088210, | 127 | "when": 1788287088210, |
| 128 | "tag": "0017_lush_thunderbolts", | 128 | "tag": "0017_lush_thunderbolts", |
| 129 | "breakpoints": true | 129 | "breakpoints": true |
| 130 | }, | ||
| 131 | { | ||
| 132 | "idx": 18, | ||
| 133 | "version": "7", | ||
| 134 | "when": 1789347558605, | ||
| 135 | "tag": "0018_eager_jack_power", | ||
| 136 | "breakpoints": true | ||
| 130 | } | 137 | } |
| 131 | ] | 138 | ] |
| 132 | } | 139 | } |
| @@ -251,6 +251,26 @@ export const artifact = pgTable( · 250 unmodified lines | |||
| 251 | (table) => [uniqueIndex("artifact_uidx").on(table.tenant, table.owner, table.subject, table.id)], | 251 | (table) => [uniqueIndex("artifact_uidx").on(table.tenant, table.owner, table.subject, table.id)], |
| 252 | ); | 252 | ); |
| 253 | 253 | ||
| 254 | export const skill = pgTable( | ||
| 255 | "skill", | ||
| 256 | { | ||
| 257 | name: varchar("name", { length: 255 }).notNull(), | ||
| 258 | description: text("description").notNull(), | ||
| 259 | frontmatter: json("frontmatter").notNull(), | ||
| 260 | files: json("files").notNull(), | ||
| 261 | created_at: timestamp("created_at").notNull(), | ||
| 262 | updated_at: timestamp("updated_at").notNull(), | ||
| 263 | row_id: varchar("row_id", { length: 255 }) | ||
| 264 | .primaryKey() | ||
| 265 | .notNull() | ||
| 266 | .$defaultFn(() => createId()), | ||
| 267 | tenant: varchar("tenant", { length: 255 }).notNull(), | ||
| 268 | owner: varchar("owner", { length: 255 }).notNull(), | ||
| 269 | subject: varchar("subject", { length: 255 }).notNull(), | ||
| 270 | }, | ||
| 271 | (table) => [uniqueIndex("skill_uidx").on(table.tenant, table.owner, table.subject, table.name)], | ||
| 272 | ); | ||
| 273 | |||
| 254 | export const plugin_storage = pgTable( | 274 | export const plugin_storage = pgTable( |
| 255 | "plugin_storage", | 275 | "plugin_storage", |
| 256 | { | 276 | { |
| @@ -36,6 +36,7 @@ import { · 35 unmodified lines | |||
| 36 | oauth_client, | 36 | oauth_client, |
| 37 | oauth_session, | 37 | oauth_session, |
| 38 | plugin_storage, | 38 | plugin_storage, |
| 39 | skill, | ||
| 39 | subject, | 40 | subject, |
| 40 | tool, | 41 | tool, |
| 41 | tool_policy, | 42 | tool_policy, |
| @@ -158,6 +159,18 @@ const seedTenant = async (db: DrizzleDb, tenant: string, tag: string) => { · 116 unmodified lines | |||
| 158 | subject: "s", | 159 | subject: "s", |
| 159 | }); | 160 | }); |
| 160 | 161 | ||
| 162 | await db.insert(skill).values({ | ||
| 163 | name: `skill-${tag}`, | ||
| 164 | description: "House style", | ||
| 165 | frontmatter: { name: `skill-${tag}`, description: "House style" }, | ||
| 166 | files: [], | ||
| 167 | created_at: now, | ||
| 168 | updated_at: now, | ||
| 169 | tenant, | ||
| 170 | owner: "o", | ||
| 171 | subject: "s", | ||
| 172 | }); | ||
| 173 | |||
| 161 | const orgNs = `o:${tenant}/plugin`; | 174 | const orgNs = `o:${tenant}/plugin`; |
| 162 | const userNs = `u:${tenant}:subject/plugin`; | 175 | const userNs = `u:${tenant}:subject/plugin`; |
| 163 | await db.insert(blob).values({ | 176 | await db.insert(blob).values({ |
| @@ -185,6 +198,7 @@ const TENANT_TABLES = [ · 21 unmodified lines | |||
| 185 | plugin_storage, | 198 | plugin_storage, |
| 186 | subject, | 199 | subject, |
| 187 | artifact, | 200 | artifact, |
| 201 | skill, | ||
| 188 | ] as const; | 202 | ] as const; |
| 189 | 203 | ||
| 190 | // Tables that are NOT purged by org id, each with the reason it is exempt. Any | 204 | // Tables that are NOT purged by org id, each with the reason it is exempt. Any |
| @@ -24,6 +24,7 @@ import { · 23 unmodified lines | |||
| 24 | oauth_client, | 24 | oauth_client, |
| 25 | oauth_session, | 25 | oauth_session, |
| 26 | plugin_storage, | 26 | plugin_storage, |
| 27 | skill, | ||
| 27 | subject, | 28 | subject, |
| 28 | tool, | 29 | tool, |
| 29 | tool_policy, | 30 | tool_policy, |
| @@ -52,6 +53,7 @@ export const purgeOrganizationData = (db: DrizzleDb, organizationId: string): Pr · 22 unmodified lines | |||
| 52 | await tx.delete(plugin_storage).where(eq(plugin_storage.tenant, organizationId)); | 53 | await tx.delete(plugin_storage).where(eq(plugin_storage.tenant, organizationId)); |
| 53 | await tx.delete(subject).where(eq(subject.tenant, organizationId)); | 54 | await tx.delete(subject).where(eq(subject.tenant, organizationId)); |
| 54 | await tx.delete(artifact).where(eq(artifact.tenant, organizationId)); | 55 | await tx.delete(artifact).where(eq(artifact.tenant, organizationId)); |
| 56 | await tx.delete(skill).where(eq(skill.tenant, organizationId)); | ||
| 55 | 57 | ||
| 56 | // Secrets, OAuth tokens, and cached specs live in `blob`, namespaced by | 58 | // Secrets, OAuth tokens, and cached specs live in `blob`, namespaced by |
| 57 | // owner: `o:<org>/<plugin>` (org scope) and `u:<org>:<subject>/<plugin>` | 59 | // owner: `o:<org>/<plugin>` (org scope) and `u:<org>:<subject>/<plugin>` |
| @@ -383,6 +383,7 @@ export class McpSessionDOSqlite extends McpAgentSessionDOBase<Env, CloudSessionD · 382 unmodified lines | |||
| 383 | engine, | 383 | engine, |
| 384 | description, | 384 | description, |
| 385 | artifacts: executor.artifacts, | 385 | artifacts: executor.artifacts, |
| 386 | skills: executor.skills, | ||
| 386 | connections: executor.connections, | 387 | connections: executor.connections, |
| 387 | // Artifacts are on by default, opt-out per connection. A session | 388 | // Artifacts are on by default, opt-out per connection. A session |
| 388 | // persisted without a value restores to the default, same as a fresh | 389 | // persisted without a value restores to the default, same as a fresh |
| @@ -192,6 +192,12 @@ const methodAttrs = (envelope: JsonRpcEnvelope): Record<string, unknown> => { · 191 unmodified lines | |||
| 192 | onSome: ({ uri }) => (uri ? { "mcp.resource.uri": uri } : {}), | 192 | onSome: ({ uri }) => (uri ? { "mcp.resource.uri": uri } : {}), |
| 193 | }), | 193 | }), |
| 194 | ), | 194 | ), |
| 195 | Match.when("skills/get", () => | ||
| 196 | Option.match(decodeUriParams(params), { | ||
| 197 | onNone: () => ({}) as Record<string, unknown>, | ||
| 198 | onSome: ({ uri }) => (uri ? { "mcp.resource.uri": uri } : {}), | ||
| 199 | }), | ||
| 200 | ), | ||
| 195 | Match.when("prompts/get", () => | 201 | Match.when("prompts/get", () => |
| 196 | Option.match(decodeNamedParams(params), { | 202 | Option.match(decodeNamedParams(params), { |
| 197 | onNone: () => ({}) as Record<string, unknown>, | 203 | onNone: () => ({}) as Record<string, unknown>, |
| @@ -16,6 +16,7 @@ import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport · 15 unmodified lines | |||
| 16 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport } from './../../../packages/react/src/routes/users' | 16 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRouteImport } from './../../../packages/react/src/routes/users' |
| 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools' | 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools' |
| 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits' | 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits' |
| 19 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport } from './../../../packages/react/src/routes/skills' | ||
| 19 | import { Route as SecretsRouteImport } from './routes/app/secrets' | 20 | import { Route as SecretsRouteImport } from './routes/app/secrets' |
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' | 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' |
| 21 | import { Route as OrgRouteImport } from './routes/app/org' | 22 | import { Route as OrgRouteImport } from './routes/app/org' |
| @@ -23,12 +24,14 @@ import { Route as BillingRouteImport } from './routes/app/billing' · 1 unmodified line | |||
| 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' | 24 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' |
| 24 | import { Route as ApiKeysRouteImport } from './routes/app/api-keys' | 25 | import { Route as ApiKeysRouteImport } from './routes/app/api-keys' |
| 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' | 26 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' |
| 27 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport } from './../../../packages/react/src/routes/skills.new' | ||
| 26 | import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId' | 28 | import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId' |
| 27 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../../packages/react/src/routes/integrations.browse' | 29 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../../packages/react/src/routes/integrations.browse' |
| 28 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' | 30 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' |
| 29 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' | 31 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' |
| 30 | import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans' | 32 | import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans' |
| 31 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' | 33 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' |
| 34 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport } from './../../../packages/react/src/routes/skills.$skillOwner.$skillName' | ||
| 32 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' | 35 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' |
| 33 | 36 | ||
| 34 | const SetupMcpRoute = SetupMcpRouteImport.update({ | 37 | const SetupMcpRoute = SetupMcpRouteImport.update({ |
| @@ -70,6 +73,12 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute = · 35 unmodified lines | |||
| 70 | path: '/{-$orgSlug}/toolkits', | 73 | path: '/{-$orgSlug}/toolkits', |
| 71 | getParentRoute: () => rootRouteImport, | 74 | getParentRoute: () => rootRouteImport, |
| 72 | } as any) | 75 | } as any) |
| 76 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute = | ||
| 77 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport.update({ | ||
| 78 | id: '/{-$orgSlug}/skills', | ||
| 79 | path: '/{-$orgSlug}/skills', | ||
| 80 | getParentRoute: () => rootRouteImport, | ||
| 81 | } as any) | ||
| 73 | const SecretsRoute = SecretsRouteImport.update({ | 82 | const SecretsRoute = SecretsRouteImport.update({ |
| 74 | id: '/{-$orgSlug}/secrets', | 83 | id: '/{-$orgSlug}/secrets', |
| 75 | path: '/{-$orgSlug}/secrets', | 84 | path: '/{-$orgSlug}/secrets', |
| @@ -111,6 +120,13 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute · 35 unmodified lines | |||
| 111 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute, | 120 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute, |
| 112 | } as any, | 121 | } as any, |
| 113 | ) | 122 | ) |
| 123 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute = | ||
| 124 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport.update({ | ||
| 125 | id: '/new', | ||
| 126 | path: '/new', | ||
| 127 | getParentRoute: () => | ||
| 128 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute, | ||
| 129 | } as any) | ||
| 114 | const ResumeDotexecutionIdRoute = ResumeDotexecutionIdRouteImport.update({ | 130 | const ResumeDotexecutionIdRoute = ResumeDotexecutionIdRouteImport.update({ |
| 115 | id: '/{-$orgSlug}/resume/$executionId', | 131 | id: '/{-$orgSlug}/resume/$executionId', |
| 116 | path: '/{-$orgSlug}/resume/$executionId', | 132 | path: '/{-$orgSlug}/resume/$executionId', |
| @@ -154,6 +170,15 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute · 37 unmodified lines | |||
| 154 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, | 170 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, |
| 155 | } as any, | 171 | } as any, |
| 156 | ) | 172 | ) |
| 173 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute = | ||
| 174 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport.update( | ||
| 175 | { | ||
| 176 | id: '/$skillOwner/$skillName', | ||
| 177 | path: '/$skillOwner/$skillName', | ||
| 178 | getParentRoute: () => | ||
| 179 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute, | ||
| 180 | } as any, | ||
| 181 | ) | ||
| 157 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute = | 182 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute = |
| 158 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update( | 183 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update( |
| 159 | { | 184 | { |
| @@ -173,6 +198,7 @@ export interface FileRoutesByFullPath { · 13 unmodified lines | |||
| 173 | '/{-$orgSlug}/org': typeof OrgRoute | 198 | '/{-$orgSlug}/org': typeof OrgRoute |
| 174 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 199 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 175 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 200 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 201 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 176 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 202 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 177 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 203 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 178 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute | 204 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute |
| @@ -183,8 +209,10 @@ export interface FileRoutesByFullPath { · 4 unmodified lines | |||
| 183 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 209 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 184 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 210 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 185 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 211 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 212 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 186 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 213 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 187 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 214 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 215 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 188 | } | 216 | } |
| 189 | export interface FileRoutesByTo { | 217 | export interface FileRoutesByTo { |
| 190 | '/create-org': typeof CreateOrgRoute | 218 | '/create-org': typeof CreateOrgRoute |
| @@ -196,6 +224,7 @@ export interface FileRoutesByTo { · 5 unmodified lines | |||
| 196 | '/{-$orgSlug}/org': typeof OrgRoute | 224 | '/{-$orgSlug}/org': typeof OrgRoute |
| 197 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 225 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 198 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 226 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 227 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 199 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 228 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 200 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 229 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 201 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute | 230 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute |
| @@ -206,8 +235,10 @@ export interface FileRoutesByTo { · 4 unmodified lines | |||
| 206 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 235 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 207 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 236 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 208 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 237 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 238 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 209 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 239 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 210 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 240 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 241 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 211 | } | 242 | } |
| 212 | export interface FileRoutesById { | 243 | export interface FileRoutesById { |
| 213 | __root__: typeof rootRouteImport | 244 | __root__: typeof rootRouteImport |
| @@ -220,6 +251,7 @@ export interface FileRoutesById { · 6 unmodified lines | |||
| 220 | '/{-$orgSlug}/org': typeof OrgRoute | 251 | '/{-$orgSlug}/org': typeof OrgRoute |
| 221 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 252 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 222 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 253 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 254 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 223 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 255 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 224 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 256 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 225 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute | 257 | '/{-$orgSlug}/users': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute |
| @@ -230,8 +262,10 @@ export interface FileRoutesById { · 4 unmodified lines | |||
| 230 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 262 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 231 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 263 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 232 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 264 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 265 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 233 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 266 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 234 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 267 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 268 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 235 | } | 269 | } |
| 236 | export interface FileRouteTypes { | 270 | export interface FileRouteTypes { |
| 237 | fileRoutesByFullPath: FileRoutesByFullPath | 271 | fileRoutesByFullPath: FileRoutesByFullPath |
| @@ -245,6 +279,7 @@ export interface FileRouteTypes { · 7 unmodified lines | |||
| 245 | | '/{-$orgSlug}/org' | 279 | | '/{-$orgSlug}/org' |
| 246 | | '/{-$orgSlug}/policies' | 280 | | '/{-$orgSlug}/policies' |
| 247 | | '/{-$orgSlug}/secrets' | 281 | | '/{-$orgSlug}/secrets' |
| 282 | | '/{-$orgSlug}/skills' | ||
| 248 | | '/{-$orgSlug}/toolkits' | 283 | | '/{-$orgSlug}/toolkits' |
| 249 | | '/{-$orgSlug}/tools' | 284 | | '/{-$orgSlug}/tools' |
| 250 | | '/{-$orgSlug}/users' | 285 | | '/{-$orgSlug}/users' |
| @@ -255,8 +290,10 @@ export interface FileRouteTypes { · 4 unmodified lines | |||
| 255 | | '/{-$orgSlug}/integrations/$namespace' | 290 | | '/{-$orgSlug}/integrations/$namespace' |
| 256 | | '/{-$orgSlug}/integrations/browse' | 291 | | '/{-$orgSlug}/integrations/browse' |
| 257 | | '/{-$orgSlug}/resume/$executionId' | 292 | | '/{-$orgSlug}/resume/$executionId' |
| 293 | | '/{-$orgSlug}/skills/new' | ||
| 258 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 294 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 259 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 295 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 296 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 260 | fileRoutesByTo: FileRoutesByTo | 297 | fileRoutesByTo: FileRoutesByTo |
| 261 | to: | 298 | to: |
| 262 | | '/create-org' | 299 | | '/create-org' |
| @@ -268,6 +305,7 @@ export interface FileRouteTypes { · 5 unmodified lines | |||
| 268 | | '/{-$orgSlug}/org' | 305 | | '/{-$orgSlug}/org' |
| 269 | | '/{-$orgSlug}/policies' | 306 | | '/{-$orgSlug}/policies' |
| 270 | | '/{-$orgSlug}/secrets' | 307 | | '/{-$orgSlug}/secrets' |
| 308 | | '/{-$orgSlug}/skills' | ||
| 271 | | '/{-$orgSlug}/toolkits' | 309 | | '/{-$orgSlug}/toolkits' |
| 272 | | '/{-$orgSlug}/tools' | 310 | | '/{-$orgSlug}/tools' |
| 273 | | '/{-$orgSlug}/users' | 311 | | '/{-$orgSlug}/users' |
| @@ -278,8 +316,10 @@ export interface FileRouteTypes { · 4 unmodified lines | |||
| 278 | | '/{-$orgSlug}/integrations/$namespace' | 316 | | '/{-$orgSlug}/integrations/$namespace' |
| 279 | | '/{-$orgSlug}/integrations/browse' | 317 | | '/{-$orgSlug}/integrations/browse' |
| 280 | | '/{-$orgSlug}/resume/$executionId' | 318 | | '/{-$orgSlug}/resume/$executionId' |
| 319 | | '/{-$orgSlug}/skills/new' | ||
| 281 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 320 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 282 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 321 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 322 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 283 | id: | 323 | id: |
| 284 | | '__root__' | 324 | | '__root__' |
| 285 | | '/create-org' | 325 | | '/create-org' |
| @@ -291,6 +331,7 @@ export interface FileRouteTypes { · 5 unmodified lines | |||
| 291 | | '/{-$orgSlug}/org' | 331 | | '/{-$orgSlug}/org' |
| 292 | | '/{-$orgSlug}/policies' | 332 | | '/{-$orgSlug}/policies' |
| 293 | | '/{-$orgSlug}/secrets' | 333 | | '/{-$orgSlug}/secrets' |
| 334 | | '/{-$orgSlug}/skills' | ||
| 294 | | '/{-$orgSlug}/toolkits' | 335 | | '/{-$orgSlug}/toolkits' |
| 295 | | '/{-$orgSlug}/tools' | 336 | | '/{-$orgSlug}/tools' |
| 296 | | '/{-$orgSlug}/users' | 337 | | '/{-$orgSlug}/users' |
| @@ -301,8 +342,10 @@ export interface FileRouteTypes { · 4 unmodified lines | |||
| 301 | | '/{-$orgSlug}/integrations/$namespace' | 342 | | '/{-$orgSlug}/integrations/$namespace' |
| 302 | | '/{-$orgSlug}/integrations/browse' | 343 | | '/{-$orgSlug}/integrations/browse' |
| 303 | | '/{-$orgSlug}/resume/$executionId' | 344 | | '/{-$orgSlug}/resume/$executionId' |
| 345 | | '/{-$orgSlug}/skills/new' | ||
| 304 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 346 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 305 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 347 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 348 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 306 | fileRoutesById: FileRoutesById | 349 | fileRoutesById: FileRoutesById |
| 307 | } | 350 | } |
| 308 | export interface RootRouteChildren { | 351 | export interface RootRouteChildren { |
| @@ -315,6 +358,7 @@ export interface RootRouteChildren { · 6 unmodified lines | |||
| 315 | OrgRoute: typeof OrgRoute | 358 | OrgRoute: typeof OrgRoute |
| 316 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 359 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 317 | SecretsRoute: typeof SecretsRoute | 360 | SecretsRoute: typeof SecretsRoute |
| 361 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 318 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 362 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 319 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 363 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 320 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute | 364 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesUsersRoute |
| @@ -378,6 +422,13 @@ declare module '@tanstack/react-router' { · 57 unmodified lines | |||
| 378 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport | 422 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport |
| 379 | parentRoute: typeof rootRouteImport | 423 | parentRoute: typeof rootRouteImport |
| 380 | } | 424 | } |
| 425 | '/{-$orgSlug}/skills': { | ||
| 426 | id: '/{-$orgSlug}/skills' | ||
| 427 | path: '/{-$orgSlug}/skills' | ||
| 428 | fullPath: '/{-$orgSlug}/skills' | ||
| 429 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport | ||
| 430 | parentRoute: typeof rootRouteImport | ||
| 431 | } | ||
| 381 | '/{-$orgSlug}/secrets': { | 432 | '/{-$orgSlug}/secrets': { |
| 382 | id: '/{-$orgSlug}/secrets' | 433 | id: '/{-$orgSlug}/secrets' |
| 383 | path: '/{-$orgSlug}/secrets' | 434 | path: '/{-$orgSlug}/secrets' |
| @@ -427,6 +478,13 @@ declare module '@tanstack/react-router' { · 43 unmodified lines | |||
| 427 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport | 478 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport |
| 428 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute | 479 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute |
| 429 | } | 480 | } |
| 481 | '/{-$orgSlug}/skills/new': { | ||
| 482 | id: '/{-$orgSlug}/skills/new' | ||
| 483 | path: '/new' | ||
| 484 | fullPath: '/{-$orgSlug}/skills/new' | ||
| 485 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport | ||
| 486 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute | ||
| 487 | } | ||
| 430 | '/{-$orgSlug}/resume/$executionId': { | 488 | '/{-$orgSlug}/resume/$executionId': { |
| 431 | id: '/{-$orgSlug}/resume/$executionId' | 489 | id: '/{-$orgSlug}/resume/$executionId' |
| 432 | path: '/{-$orgSlug}/resume/$executionId' | 490 | path: '/{-$orgSlug}/resume/$executionId' |
| @@ -469,6 +527,13 @@ declare module '@tanstack/react-router' { · 36 unmodified lines | |||
| 469 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport | 527 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport |
| 470 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute | 528 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute |
| 471 | } | 529 | } |
| 530 | '/{-$orgSlug}/skills/$skillOwner/$skillName': { | ||
| 531 | id: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 532 | path: '/$skillOwner/$skillName' | ||
| 533 | fullPath: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 534 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport | ||
| 535 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute | ||
| 536 | } | ||
| 472 | '/{-$orgSlug}/integrations/add/$pluginKey': { | 537 | '/{-$orgSlug}/integrations/add/$pluginKey': { |
| 473 | id: '/{-$orgSlug}/integrations/add/$pluginKey' | 538 | id: '/{-$orgSlug}/integrations/add/$pluginKey' |
| 474 | path: '/{-$orgSlug}/integrations/add/$pluginKey' | 539 | path: '/{-$orgSlug}/integrations/add/$pluginKey' |
| @@ -494,6 +559,24 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren = · 19 unmodified lines | |||
| 494 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, | 559 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, |
| 495 | ) | 560 | ) |
| 496 | 561 | ||
| 562 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren { | ||
| 563 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 564 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 565 | } | ||
| 566 | |||
| 567 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren = | ||
| 568 | { | ||
| 569 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute: | ||
| 570 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute, | ||
| 571 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: | ||
| 572 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute, | ||
| 573 | } | ||
| 574 | |||
| 575 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren = | ||
| 576 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute._addFileChildren( | ||
| 577 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren, | ||
| 578 | ) | ||
| 579 | |||
| 497 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { | 580 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { |
| 498 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 581 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 499 | } | 582 | } |
| @@ -521,6 +604,8 @@ const rootRouteChildren: RootRouteChildren = { · 21 unmodified lines | |||
| 521 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: | 604 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: |
| 522 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, | 605 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, |
| 523 | SecretsRoute: SecretsRoute, | 606 | SecretsRoute: SecretsRoute, |
| 607 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute: | ||
| 608 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren, | ||
| 524 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: | 609 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: |
| 525 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren, | 610 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren, |
| 526 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: | 611 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: |
| @@ -164,6 +164,7 @@ export class McpSessionDO extends McpAgentSessionDOBase<CloudflareEnv, CfSession · 163 unmodified lines | |||
| 164 | const mcpServer = yield* createExecutorMcpServer({ | 164 | const mcpServer = yield* createExecutorMcpServer({ |
| 165 | engine, | 165 | engine, |
| 166 | artifacts: executor.artifacts, | 166 | artifacts: executor.artifacts, |
| 167 | skills: executor.skills, | ||
| 167 | connections: executor.connections, | 168 | connections: executor.connections, |
| 168 | // Artifacts are on by default, opt-out per connection. A session | 169 | // Artifacts are on by default, opt-out per connection. A session |
| 169 | // persisted without a value restores to the default, same as a fresh | 170 | // persisted without a value restores to the default, same as a fresh |
| @@ -12,15 +12,18 @@ import { Route as rootRouteImport } from './routes/__root' · 11 unmodified lines | |||
| 12 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index' | 12 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index' |
| 13 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools' | 13 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools' |
| 14 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits' | 14 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits' |
| 15 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport } from './../../../packages/react/src/routes/skills' | ||
| 15 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets' | 16 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets' |
| 16 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' | 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' |
| 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' | 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' |
| 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' | 19 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' |
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport } from './../../../packages/react/src/routes/skills.new' | ||
| 19 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' | 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' |
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../../packages/react/src/routes/integrations.browse' | 22 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../../packages/react/src/routes/integrations.browse' |
| 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' | 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' |
| 22 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' | 24 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' |
| 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' | 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' |
| 26 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport } from './../../../packages/react/src/routes/skills.$skillOwner.$skillName' | ||
| 24 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' | 27 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' |
| 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' | 28 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' |
| 26 | 29 | ||
| @@ -42,6 +45,12 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute = · 15 unmodified lines | |||
| 42 | path: '/{-$orgSlug}/toolkits', | 45 | path: '/{-$orgSlug}/toolkits', |
| 43 | getParentRoute: () => rootRouteImport, | 46 | getParentRoute: () => rootRouteImport, |
| 44 | } as any) | 47 | } as any) |
| 48 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute = | ||
| 49 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport.update({ | ||
| 50 | id: '/{-$orgSlug}/skills', | ||
| 51 | path: '/{-$orgSlug}/skills', | ||
| 52 | getParentRoute: () => rootRouteImport, | ||
| 53 | } as any) | ||
| 45 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute = | 54 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute = |
| 46 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport.update({ | 55 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport.update({ |
| 47 | id: '/{-$orgSlug}/secrets', | 56 | id: '/{-$orgSlug}/secrets', |
| @@ -69,6 +78,13 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute · 21 unmodified lines | |||
| 69 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute, | 78 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute, |
| 70 | } as any, | 79 | } as any, |
| 71 | ) | 80 | ) |
| 81 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute = | ||
| 82 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport.update({ | ||
| 83 | id: '/new', | ||
| 84 | path: '/new', | ||
| 85 | getParentRoute: () => | ||
| 86 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute, | ||
| 87 | } as any) | ||
| 72 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute = | 88 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute = |
| 73 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport.update( | 89 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport.update( |
| 74 | { | 90 | { |
| @@ -110,6 +126,15 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute · 35 unmodified lines | |||
| 110 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, | 126 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, |
| 111 | } as any, | 127 | } as any, |
| 112 | ) | 128 | ) |
| 129 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute = | ||
| 130 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport.update( | ||
| 131 | { | ||
| 132 | id: '/$skillOwner/$skillName', | ||
| 133 | path: '/$skillOwner/$skillName', | ||
| 134 | getParentRoute: () => | ||
| 135 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute, | ||
| 136 | } as any, | ||
| 137 | ) | ||
| 113 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = | 138 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = |
| 114 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( | 139 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( |
| 115 | { | 140 | { |
| @@ -131,6 +156,7 @@ export interface FileRoutesByFullPath { · 15 unmodified lines | |||
| 131 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren | 156 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren |
| 132 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 157 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 133 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute | 158 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute |
| 159 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 134 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 160 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 135 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 161 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 136 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 162 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| @@ -139,14 +165,17 @@ export interface FileRoutesByFullPath { · 2 unmodified lines | |||
| 139 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 165 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 140 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 166 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 141 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 167 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 168 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 142 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 169 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 143 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 170 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 144 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 171 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 172 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 145 | } | 173 | } |
| 146 | export interface FileRoutesByTo { | 174 | export interface FileRoutesByTo { |
| 147 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren | 175 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren |
| 148 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 176 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 149 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute | 177 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute |
| 178 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 150 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 179 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 151 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 180 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 152 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 181 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| @@ -155,15 +184,18 @@ export interface FileRoutesByTo { · 2 unmodified lines | |||
| 155 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 184 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 156 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 185 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 157 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 186 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 187 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 158 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 188 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 159 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 189 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 160 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 190 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 191 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 161 | } | 192 | } |
| 162 | export interface FileRoutesById { | 193 | export interface FileRoutesById { |
| 163 | __root__: typeof rootRouteImport | 194 | __root__: typeof rootRouteImport |
| 164 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren | 195 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren |
| 165 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 196 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 166 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute | 197 | '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute |
| 198 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 167 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 199 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 168 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 200 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 169 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 201 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| @@ -172,9 +204,11 @@ export interface FileRoutesById { · 2 unmodified lines | |||
| 172 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 204 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 173 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute | 205 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotbrowseRoute |
| 174 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 206 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 207 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 175 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 208 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 176 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 209 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 177 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 210 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 211 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 178 | } | 212 | } |
| 179 | export interface FileRouteTypes { | 213 | export interface FileRouteTypes { |
| 180 | fileRoutesByFullPath: FileRoutesByFullPath | 214 | fileRoutesByFullPath: FileRoutesByFullPath |
| @@ -182,6 +216,7 @@ export interface FileRouteTypes { · 1 unmodified line | |||
| 182 | | '/{-$orgSlug}/artifacts' | 216 | | '/{-$orgSlug}/artifacts' |
| 183 | | '/{-$orgSlug}/policies' | 217 | | '/{-$orgSlug}/policies' |
| 184 | | '/{-$orgSlug}/secrets' | 218 | | '/{-$orgSlug}/secrets' |
| 219 | | '/{-$orgSlug}/skills' | ||
| 185 | | '/{-$orgSlug}/toolkits' | 220 | | '/{-$orgSlug}/toolkits' |
| 186 | | '/{-$orgSlug}/tools' | 221 | | '/{-$orgSlug}/tools' |
| 187 | | '/{-$orgSlug}/' | 222 | | '/{-$orgSlug}/' |
| @@ -190,14 +225,17 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 190 | | '/{-$orgSlug}/integrations/$namespace' | 225 | | '/{-$orgSlug}/integrations/$namespace' |
| 191 | | '/{-$orgSlug}/integrations/browse' | 226 | | '/{-$orgSlug}/integrations/browse' |
| 192 | | '/{-$orgSlug}/resume/$executionId' | 227 | | '/{-$orgSlug}/resume/$executionId' |
| 228 | | '/{-$orgSlug}/skills/new' | ||
| 193 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 229 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 194 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 230 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 195 | | '/{-$orgSlug}/plugins/$pluginId/$' | 231 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 232 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 196 | fileRoutesByTo: FileRoutesByTo | 233 | fileRoutesByTo: FileRoutesByTo |
| 197 | to: | 234 | to: |
| 198 | | '/{-$orgSlug}/artifacts' | 235 | | '/{-$orgSlug}/artifacts' |
| 199 | | '/{-$orgSlug}/policies' | 236 | | '/{-$orgSlug}/policies' |
| 200 | | '/{-$orgSlug}/secrets' | 237 | | '/{-$orgSlug}/secrets' |
| 238 | | '/{-$orgSlug}/skills' | ||
| 201 | | '/{-$orgSlug}/toolkits' | 239 | | '/{-$orgSlug}/toolkits' |
| 202 | | '/{-$orgSlug}/tools' | 240 | | '/{-$orgSlug}/tools' |
| 203 | | '/{-$orgSlug}' | 241 | | '/{-$orgSlug}' |
| @@ -206,14 +244,17 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 206 | | '/{-$orgSlug}/integrations/$namespace' | 244 | | '/{-$orgSlug}/integrations/$namespace' |
| 207 | | '/{-$orgSlug}/integrations/browse' | 245 | | '/{-$orgSlug}/integrations/browse' |
| 208 | | '/{-$orgSlug}/resume/$executionId' | 246 | | '/{-$orgSlug}/resume/$executionId' |
| 247 | | '/{-$orgSlug}/skills/new' | ||
| 209 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 248 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 210 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 249 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 211 | | '/{-$orgSlug}/plugins/$pluginId/$' | 250 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 251 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 212 | id: | 252 | id: |
| 213 | | '__root__' | 253 | | '__root__' |
| 214 | | '/{-$orgSlug}/artifacts' | 254 | | '/{-$orgSlug}/artifacts' |
| 215 | | '/{-$orgSlug}/policies' | 255 | | '/{-$orgSlug}/policies' |
| 216 | | '/{-$orgSlug}/secrets' | 256 | | '/{-$orgSlug}/secrets' |
| 257 | | '/{-$orgSlug}/skills' | ||
| 217 | | '/{-$orgSlug}/toolkits' | 258 | | '/{-$orgSlug}/toolkits' |
| 218 | | '/{-$orgSlug}/tools' | 259 | | '/{-$orgSlug}/tools' |
| 219 | | '/{-$orgSlug}/' | 260 | | '/{-$orgSlug}/' |
| @@ -222,15 +263,18 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 222 | | '/{-$orgSlug}/integrations/$namespace' | 263 | | '/{-$orgSlug}/integrations/$namespace' |
| 223 | | '/{-$orgSlug}/integrations/browse' | 264 | | '/{-$orgSlug}/integrations/browse' |
| 224 | | '/{-$orgSlug}/resume/$executionId' | 265 | | '/{-$orgSlug}/resume/$executionId' |
| 266 | | '/{-$orgSlug}/skills/new' | ||
| 225 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 267 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 226 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 268 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 227 | | '/{-$orgSlug}/plugins/$pluginId/$' | 269 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 270 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 228 | fileRoutesById: FileRoutesById | 271 | fileRoutesById: FileRoutesById |
| 229 | } | 272 | } |
| 230 | export interface RootRouteChildren { | 273 | export interface RootRouteChildren { |
| 231 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren | 274 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren |
| 232 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute | 275 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute |
| 233 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute | 276 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute |
| 277 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren | ||
| 234 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 278 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 235 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 279 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 236 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 280 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| @@ -265,6 +309,13 @@ declare module '@tanstack/react-router' { · 28 unmodified lines | |||
| 265 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport | 309 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport |
| 266 | parentRoute: typeof rootRouteImport | 310 | parentRoute: typeof rootRouteImport |
| 267 | } | 311 | } |
| 312 | '/{-$orgSlug}/skills': { | ||
| 313 | id: '/{-$orgSlug}/skills' | ||
| 314 | path: '/{-$orgSlug}/skills' | ||
| 315 | fullPath: '/{-$orgSlug}/skills' | ||
| 316 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteImport | ||
| 317 | parentRoute: typeof rootRouteImport | ||
| 318 | } | ||
| 268 | '/{-$orgSlug}/secrets': { | 319 | '/{-$orgSlug}/secrets': { |
| 269 | id: '/{-$orgSlug}/secrets' | 320 | id: '/{-$orgSlug}/secrets' |
| 270 | path: '/{-$orgSlug}/secrets' | 321 | path: '/{-$orgSlug}/secrets' |
| @@ -293,6 +344,13 @@ declare module '@tanstack/react-router' { · 22 unmodified lines | |||
| 293 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport | 344 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport |
| 294 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute | 345 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute |
| 295 | } | 346 | } |
| 347 | '/{-$orgSlug}/skills/new': { | ||
| 348 | id: '/{-$orgSlug}/skills/new' | ||
| 349 | path: '/new' | ||
| 350 | fullPath: '/{-$orgSlug}/skills/new' | ||
| 351 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRouteImport | ||
| 352 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute | ||
| 353 | } | ||
| 296 | '/{-$orgSlug}/resume/$executionId': { | 354 | '/{-$orgSlug}/resume/$executionId': { |
| 297 | id: '/{-$orgSlug}/resume/$executionId' | 355 | id: '/{-$orgSlug}/resume/$executionId' |
| 298 | path: '/{-$orgSlug}/resume/$executionId' | 356 | path: '/{-$orgSlug}/resume/$executionId' |
| @@ -328,6 +386,13 @@ declare module '@tanstack/react-router' { · 29 unmodified lines | |||
| 328 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport | 386 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport |
| 329 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute | 387 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute |
| 330 | } | 388 | } |
| 389 | '/{-$orgSlug}/skills/$skillOwner/$skillName': { | ||
| 390 | id: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 391 | path: '/$skillOwner/$skillName' | ||
| 392 | fullPath: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 393 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport | ||
| 394 | parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute | ||
| 395 | } | ||
| 331 | '/{-$orgSlug}/plugins/$pluginId/$': { | 396 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 332 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 397 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 333 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 398 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -360,6 +425,24 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren = · 26 unmodified lines | |||
| 360 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, | 425 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, |
| 361 | ) | 426 | ) |
| 362 | 427 | ||
| 428 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren { | ||
| 429 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute | ||
| 430 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 431 | } | ||
| 432 | |||
| 433 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren = | ||
| 434 | { | ||
| 435 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute: | ||
| 436 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotnewRoute, | ||
| 437 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: | ||
| 438 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute, | ||
| 439 | } | ||
| 440 | |||
| 441 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren = | ||
| 442 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute._addFileChildren( | ||
| 443 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteChildren, | ||
| 444 | ) | ||
| 445 | |||
| 363 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { | 446 | interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { |
| 364 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 447 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 365 | } | 448 | } |
| @@ -382,6 +465,8 @@ const rootRouteChildren: RootRouteChildren = { · 16 unmodified lines | |||
| 382 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, | 465 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, |
| 383 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: | 466 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: |
| 384 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute, | 467 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute, |
| 468 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRoute: | ||
| 469 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSkillsRouteWithChildren, | ||
| 385 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: | 470 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: |
| 386 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren, | 471 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren, |
| 387 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: | 472 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: |
| @@ -0,0 +1,14 @@ | |||
| 1 | CREATE TABLE `skill` ( | ||
| 2 | `name` text NOT NULL, | ||
| 3 | `description` text NOT NULL, | ||
| 4 | `frontmatter` text NOT NULL, | ||
| 5 | `files` text NOT NULL, | ||
| 6 | `created_at` integer NOT NULL, | ||
| 7 | `updated_at` integer NOT NULL, | ||
| 8 | `row_id` text PRIMARY KEY NOT NULL, | ||
| 9 | `tenant` text NOT NULL, | ||
| 10 | `owner` text NOT NULL, | ||
| 11 | `subject` text NOT NULL | ||
| 12 | ); | ||
| 13 | --> statement-breakpoint | ||
| 14 | CREATE UNIQUE INDEX `skill_uidx` ON `skill` (`tenant`,`owner`,`subject`,`name`); | ||
| @@ -0,0 +1,1034 @@ | |||
| 1 | { | ||
| 2 | "version": "6", | ||
| 3 | "dialect": "sqlite", | ||
| 4 | "id": "e5c498c3-9c8a-4bcb-b781-e4a48afac4cc", | ||
| 5 | "prevId": "89a4fd1b-f0f6-4482-a991-0db78c859f76", | ||
| 6 | "tables": { | ||
| 7 | "blob": { | ||
| 8 | "name": "blob", | ||
| 9 | "columns": { | ||
| 10 | "namespace": { | ||
| 11 | "name": "namespace", | ||
| 12 | "type": "text", | ||
| 13 | "primaryKey": false, | ||
| 14 | "notNull": true, | ||
| 15 | "autoincrement": false | ||
| 16 | }, | ||
| 17 | "key": { | ||
| 18 | "name": "key", | ||
| 19 | "type": "text", | ||
| 20 | "primaryKey": false, | ||
| 21 | "notNull": true, | ||
| 22 | "autoincrement": false | ||
| 23 | }, | ||
| 24 | "value": { | ||
| 25 | "name": "value", | ||
| 26 | "type": "text", | ||
| 27 | "primaryKey": false, | ||
| 28 | "notNull": true, | ||
| 29 | "autoincrement": false | ||
| 30 | }, | ||
| 31 | "row_id": { | ||
| 32 | "name": "row_id", | ||
| 33 | "type": "text", | ||
| 34 | "primaryKey": true, | ||
| 35 | "notNull": true, | ||
| 36 | "autoincrement": false | ||
| 37 | }, | ||
| 38 | "id": { | ||
| 39 | "name": "id", | ||
| 40 | "type": "text", | ||
| 41 | "primaryKey": false, | ||
| 42 | "notNull": true, | ||
| 43 | "autoincrement": false | ||
| 44 | } | ||
| 45 | }, | ||
| 46 | "indexes": { | ||
| 47 | "blob_id_uidx": { | ||
| 48 | "name": "blob_id_uidx", | ||
| 49 | "columns": ["id"], | ||
| 50 | "isUnique": true | ||
| 51 | } | ||
| 52 | }, | ||
| 53 | "foreignKeys": {}, | ||
| 54 | "compositePrimaryKeys": {}, | ||
| 55 | "uniqueConstraints": {}, | ||
| 56 | "checkConstraints": {} | ||
| 57 | }, | ||
| 58 | "connection": { | ||
| 59 | "name": "connection", | ||
| 60 | "columns": { | ||
| 61 | "integration": { | ||
| 62 | "name": "integration", | ||
| 63 | "type": "text", | ||
| 64 | "primaryKey": false, | ||
| 65 | "notNull": true, | ||
| 66 | "autoincrement": false | ||
| 67 | }, | ||
| 68 | "name": { | ||
| 69 | "name": "name", | ||
| 70 | "type": "text", | ||
| 71 | "primaryKey": false, | ||
| 72 | "notNull": true, | ||
| 73 | "autoincrement": false | ||
| 74 | }, | ||
| 75 | "template": { | ||
| 76 | "name": "template", | ||
| 77 | "type": "text", | ||
| 78 | "primaryKey": false, | ||
| 79 | "notNull": true, | ||
| 80 | "autoincrement": false | ||
| 81 | }, | ||
| 82 | "provider": { | ||
| 83 | "name": "provider", | ||
| 84 | "type": "text", | ||
| 85 | "primaryKey": false, | ||
| 86 | "notNull": true, | ||
| 87 | "autoincrement": false | ||
| 88 | }, | ||
| 89 | "item_ids": { | ||
| 90 | "name": "item_ids", | ||
| 91 | "type": "text", | ||
| 92 | "primaryKey": false, | ||
| 93 | "notNull": true, | ||
| 94 | "autoincrement": false | ||
| 95 | }, | ||
| 96 | "credential_write": { | ||
| 97 | "name": "credential_write", | ||
| 98 | "type": "text", | ||
| 99 | "primaryKey": false, | ||
| 100 | "notNull": false, | ||
| 101 | "autoincrement": false | ||
| 102 | }, | ||
| 103 | "identity_label": { | ||
| 104 | "name": "identity_label", | ||
| 105 | "type": "text", | ||
| 106 | "primaryKey": false, | ||
| 107 | "notNull": false, | ||
| 108 | "autoincrement": false | ||
| 109 | }, | ||
| 110 | "oauth_client": { | ||
| 111 | "name": "oauth_client", | ||
| 112 | "type": "text", | ||
| 113 | "primaryKey": false, | ||
| 114 | "notNull": false, | ||
| 115 | "autoincrement": false | ||
| 116 | }, | ||
| 117 | "oauth_client_owner": { | ||
| 118 | "name": "oauth_client_owner", | ||
| 119 | "type": "text", | ||
| 120 | "primaryKey": false, | ||
| 121 | "notNull": false, | ||
| 122 | "autoincrement": false | ||
| 123 | }, | ||
| 124 | "refresh_item_id": { | ||
| 125 | "name": "refresh_item_id", | ||
| 126 | "type": "text", | ||
| 127 | "primaryKey": false, | ||
| 128 | "notNull": false, | ||
| 129 | "autoincrement": false | ||
| 130 | }, | ||
| 131 | "expires_at": { | ||
| 132 | "name": "expires_at", | ||
| 133 | "type": "blob", | ||
| 134 | "primaryKey": false, | ||
| 135 | "notNull": false, | ||
| 136 | "autoincrement": false | ||
| 137 | }, | ||
| 138 | "oauth_scope": { | ||
| 139 | "name": "oauth_scope", | ||
| 140 | "type": "text", | ||
| 141 | "primaryKey": false, | ||
| 142 | "notNull": false, | ||
| 143 | "autoincrement": false | ||
| 144 | }, | ||
| 145 | "oauth_token_url": { | ||
| 146 | "name": "oauth_token_url", | ||
| 147 | "type": "text", | ||
| 148 | "primaryKey": false, | ||
| 149 | "notNull": false, | ||
| 150 | "autoincrement": false | ||
| 151 | }, | ||
| 152 | "provider_state": { | ||
| 153 | "name": "provider_state", | ||
| 154 | "type": "text", | ||
| 155 | "primaryKey": false, | ||
| 156 | "notNull": false, | ||
| 157 | "autoincrement": false | ||
| 158 | }, | ||
| 159 | "created_at": { | ||
| 160 | "name": "created_at", | ||
| 161 | "type": "integer", | ||
| 162 | "primaryKey": false, | ||
| 163 | "notNull": true, | ||
| 164 | "autoincrement": false | ||
| 165 | }, | ||
| 166 | "updated_at": { | ||
| 167 | "name": "updated_at", | ||
| 168 | "type": "integer", | ||
| 169 | "primaryKey": false, | ||
| 170 | "notNull": true, | ||
| 171 | "autoincrement": false | ||
| 172 | }, | ||
| 173 | "row_id": { | ||
| 174 | "name": "row_id", | ||
| 175 | "type": "text", | ||
| 176 | "primaryKey": true, | ||
| 177 | "notNull": true, | ||
| 178 | "autoincrement": false | ||
| 179 | }, | ||
| 180 | "tenant": { | ||
| 181 | "name": "tenant", | ||
| 182 | "type": "text", | ||
| 183 | "primaryKey": false, | ||
| 184 | "notNull": true, | ||
| 185 | "autoincrement": false | ||
| 186 | }, | ||
| 187 | "owner": { | ||
| 188 | "name": "owner", | ||
| 189 | "type": "text", | ||
| 190 | "primaryKey": false, | ||
| 191 | "notNull": true, | ||
| 192 | "autoincrement": false | ||
| 193 | }, | ||
| 194 | "subject": { | ||
| 195 | "name": "subject", | ||
| 196 | "type": "text", | ||
| 197 | "primaryKey": false, | ||
| 198 | "notNull": true, | ||
| 199 | "autoincrement": false | ||
| 200 | } | ||
| 201 | }, | ||
| 202 | "indexes": { | ||
| 203 | "connection_uidx": { | ||
| 204 | "name": "connection_uidx", | ||
| 205 | "columns": ["tenant", "owner", "subject", "integration", "name"], | ||
| 206 | "isUnique": true | ||
| 207 | } | ||
| 208 | }, | ||
| 209 | "foreignKeys": {}, | ||
| 210 | "compositePrimaryKeys": {}, | ||
| 211 | "uniqueConstraints": {}, | ||
| 212 | "checkConstraints": {} | ||
| 213 | }, | ||
| 214 | "definition": { | ||
| 215 | "name": "definition", | ||
| 216 | "columns": { | ||
| 217 | "integration": { | ||
| 218 | "name": "integration", | ||
| 219 | "type": "text", | ||
| 220 | "primaryKey": false, | ||
| 221 | "notNull": true, | ||
| 222 | "autoincrement": false | ||
| 223 | }, | ||
| 224 | "connection": { | ||
| 225 | "name": "connection", | ||
| 226 | "type": "text", | ||
| 227 | "primaryKey": false, | ||
| 228 | "notNull": true, | ||
| 229 | "autoincrement": false | ||
| 230 | }, | ||
| 231 | "plugin_id": { | ||
| 232 | "name": "plugin_id", | ||
| 233 | "type": "text", | ||
| 234 | "primaryKey": false, | ||
| 235 | "notNull": true, | ||
| 236 | "autoincrement": false | ||
| 237 | }, | ||
| 238 | "name": { | ||
| 239 | "name": "name", | ||
| 240 | "type": "text", | ||
| 241 | "primaryKey": false, | ||
| 242 | "notNull": true, | ||
| 243 | "autoincrement": false | ||
| 244 | }, | ||
| 245 | "schema": { | ||
| 246 | "name": "schema", | ||
| 247 | "type": "text", | ||
| 248 | "primaryKey": false, | ||
| 249 | "notNull": true, | ||
| 250 | "autoincrement": false | ||
| 251 | }, | ||
| 252 | "created_at": { | ||
| 253 | "name": "created_at", | ||
| 254 | "type": "integer", | ||
| 255 | "primaryKey": false, | ||
| 256 | "notNull": true, | ||
| 257 | "autoincrement": false | ||
| 258 | }, | ||
| 259 | "row_id": { | ||
| 260 | "name": "row_id", | ||
| 261 | "type": "text", | ||
| 262 | "primaryKey": true, | ||
| 263 | "notNull": true, | ||
| 264 | "autoincrement": false | ||
| 265 | }, | ||
| 266 | "tenant": { | ||
| 267 | "name": "tenant", | ||
| 268 | "type": "text", | ||
| 269 | "primaryKey": false, | ||
| 270 | "notNull": true, | ||
| 271 | "autoincrement": false | ||
| 272 | }, | ||
| 273 | "owner": { | ||
| 274 | "name": "owner", | ||
| 275 | "type": "text", | ||
| 276 | "primaryKey": false, | ||
| 277 | "notNull": true, | ||
| 278 | "autoincrement": false | ||
| 279 | }, | ||
| 280 | "subject": { | ||
| 281 | "name": "subject", | ||
| 282 | "type": "text", | ||
| 283 | "primaryKey": false, | ||
| 284 | "notNull": true, | ||
| 285 | "autoincrement": false | ||
| 286 | } | ||
| 287 | }, | ||
| 288 | "indexes": { | ||
| 289 | "definition_uidx": { | ||
| 290 | "name": "definition_uidx", | ||
| 291 | "columns": ["tenant", "owner", "subject", "integration", "connection", "name"], | ||
| 292 | "isUnique": true | ||
| 293 | } | ||
| 294 | }, | ||
| 295 | "foreignKeys": {}, | ||
| 296 | "compositePrimaryKeys": {}, | ||
| 297 | "uniqueConstraints": {}, | ||
| 298 | "checkConstraints": {} | ||
| 299 | }, | ||
| 300 | "integration": { | ||
| 301 | "name": "integration", | ||
| 302 | "columns": { | ||
| 303 | "slug": { | ||
| 304 | "name": "slug", | ||
| 305 | "type": "text", | ||
| 306 | "primaryKey": false, | ||
| 307 | "notNull": true, | ||
| 308 | "autoincrement": false | ||
| 309 | }, | ||
| 310 | "plugin_id": { | ||
| 311 | "name": "plugin_id", | ||
| 312 | "type": "text", | ||
| 313 | "primaryKey": false, | ||
| 314 | "notNull": true, | ||
| 315 | "autoincrement": false | ||
| 316 | }, | ||
| 317 | "description": { | ||
| 318 | "name": "description", | ||
| 319 | "type": "text", | ||
| 320 | "primaryKey": false, | ||
| 321 | "notNull": true, | ||
| 322 | "autoincrement": false | ||
| 323 | }, | ||
| 324 | "config": { | ||
| 325 | "name": "config", | ||
| 326 | "type": "text", | ||
| 327 | "primaryKey": false, | ||
| 328 | "notNull": false, | ||
| 329 | "autoincrement": false | ||
| 330 | }, | ||
| 331 | "can_remove": { | ||
| 332 | "name": "can_remove", | ||
| 333 | "type": "integer", | ||
| 334 | "primaryKey": false, | ||
| 335 | "notNull": true, | ||
| 336 | "autoincrement": false, | ||
| 337 | "default": 1 | ||
| 338 | }, | ||
| 339 | "can_refresh": { | ||
| 340 | "name": "can_refresh", | ||
| 341 | "type": "integer", | ||
| 342 | "primaryKey": false, | ||
| 343 | "notNull": true, | ||
| 344 | "autoincrement": false, | ||
| 345 | "default": 0 | ||
| 346 | }, | ||
| 347 | "created_at": { | ||
| 348 | "name": "created_at", | ||
| 349 | "type": "integer", | ||
| 350 | "primaryKey": false, | ||
| 351 | "notNull": true, | ||
| 352 | "autoincrement": false | ||
| 353 | }, | ||
| 354 | "updated_at": { | ||
| 355 | "name": "updated_at", | ||
| 356 | "type": "integer", | ||
| 357 | "primaryKey": false, | ||
| 358 | "notNull": true, | ||
| 359 | "autoincrement": false | ||
| 360 | }, | ||
| 361 | "row_id": { | ||
| 362 | "name": "row_id", | ||
| 363 | "type": "text", | ||
| 364 | "primaryKey": true, | ||
| 365 | "notNull": true, | ||
| 366 | "autoincrement": false | ||
| 367 | }, | ||
| 368 | "tenant": { | ||
| 369 | "name": "tenant", | ||
| 370 | "type": "text", | ||
| 371 | "primaryKey": false, | ||
| 372 | "notNull": true, | ||
| 373 | "autoincrement": false | ||
| 374 | } | ||
| 375 | }, | ||
| 376 | "indexes": { | ||
| 377 | "integration_uidx": { | ||
| 378 | "name": "integration_uidx", | ||
| 379 | "columns": ["tenant", "slug"], | ||
| 380 | "isUnique": true | ||
| 381 | } | ||
| 382 | }, | ||
| 383 | "foreignKeys": {}, | ||
| 384 | "compositePrimaryKeys": {}, | ||
| 385 | "uniqueConstraints": {}, | ||
| 386 | "checkConstraints": {} | ||
| 387 | }, | ||
| 388 | "oauth_client": { | ||
| 389 | "name": "oauth_client", | ||
| 390 | "columns": { | ||
| 391 | "slug": { | ||
| 392 | "name": "slug", | ||
| 393 | "type": "text", | ||
| 394 | "primaryKey": false, | ||
| 395 | "notNull": true, | ||
| 396 | "autoincrement": false | ||
| 397 | }, | ||
| 398 | "authorization_url": { | ||
| 399 | "name": "authorization_url", | ||
| 400 | "type": "text", | ||
| 401 | "primaryKey": false, | ||
| 402 | "notNull": true, | ||
| 403 | "autoincrement": false | ||
| 404 | }, | ||
| 405 | "token_url": { | ||
| 406 | "name": "token_url", | ||
| 407 | "type": "text", | ||
| 408 | "primaryKey": false, | ||
| 409 | "notNull": true, | ||
| 410 | "autoincrement": false | ||
| 411 | }, | ||
| 412 | "grant": { | ||
| 413 | "name": "grant", | ||
| 414 | "type": "text", | ||
| 415 | "primaryKey": false, | ||
| 416 | "notNull": true, | ||
| 417 | "autoincrement": false | ||
| 418 | }, | ||
| 419 | "client_id": { | ||
| 420 | "name": "client_id", | ||
| 421 | "type": "text", | ||
| 422 | "primaryKey": false, | ||
| 423 | "notNull": true, | ||
| 424 | "autoincrement": false | ||
| 425 | }, | ||
| 426 | "client_secret_item_id": { | ||
| 427 | "name": "client_secret_item_id", | ||
| 428 | "type": "text", | ||
| 429 | "primaryKey": false, | ||
| 430 | "notNull": false, | ||
| 431 | "autoincrement": false | ||
| 432 | }, | ||
| 433 | "credential_write": { | ||
| 434 | "name": "credential_write", | ||
| 435 | "type": "text", | ||
| 436 | "primaryKey": false, | ||
| 437 | "notNull": false, | ||
| 438 | "autoincrement": false | ||
| 439 | }, | ||
| 440 | "token_endpoint_auth_method": { | ||
| 441 | "name": "token_endpoint_auth_method", | ||
| 442 | "type": "text", | ||
| 443 | "primaryKey": false, | ||
| 444 | "notNull": false, | ||
| 445 | "autoincrement": false | ||
| 446 | }, | ||
| 447 | "resource": { | ||
| 448 | "name": "resource", | ||
| 449 | "type": "text", | ||
| 450 | "primaryKey": false, | ||
| 451 | "notNull": false, | ||
| 452 | "autoincrement": false | ||
| 453 | }, | ||
| 454 | "origin_kind": { | ||
| 455 | "name": "origin_kind", | ||
| 456 | "type": "text", | ||
| 457 | "primaryKey": false, | ||
| 458 | "notNull": false, | ||
| 459 | "autoincrement": false | ||
| 460 | }, | ||
| 461 | "origin_integration": { | ||
| 462 | "name": "origin_integration", | ||
| 463 | "type": "text", | ||
| 464 | "primaryKey": false, | ||
| 465 | "notNull": false, | ||
| 466 | "autoincrement": false | ||
| 467 | }, | ||
| 468 | "origin_issuer": { | ||
| 469 | "name": "origin_issuer", | ||
| 470 | "type": "text", | ||
| 471 | "primaryKey": false, | ||
| 472 | "notNull": false, | ||
| 473 | "autoincrement": false | ||
| 474 | }, | ||
| 475 | "origin_redirect_uri": { | ||
| 476 | "name": "origin_redirect_uri", | ||
| 477 | "type": "text", | ||
| 478 | "primaryKey": false, | ||
| 479 | "notNull": false, | ||
| 480 | "autoincrement": false | ||
| 481 | }, | ||
| 482 | "created_at": { | ||
| 483 | "name": "created_at", | ||
| 484 | "type": "integer", | ||
| 485 | "primaryKey": false, | ||
| 486 | "notNull": true, | ||
| 487 | "autoincrement": false | ||
| 488 | }, | ||
| 489 | "row_id": { | ||
| 490 | "name": "row_id", | ||
| 491 | "type": "text", | ||
| 492 | "primaryKey": true, | ||
| 493 | "notNull": true, | ||
| 494 | "autoincrement": false | ||
| 495 | }, | ||
| 496 | "tenant": { | ||
| 497 | "name": "tenant", | ||
| 498 | "type": "text", | ||
| 499 | "primaryKey": false, | ||
| 500 | "notNull": true, | ||
| 501 | "autoincrement": false | ||
| 502 | }, | ||
| 503 | "owner": { | ||
| 504 | "name": "owner", | ||
| 505 | "type": "text", | ||
| 506 | "primaryKey": false, | ||
| 507 | "notNull": true, | ||
| 508 | "autoincrement": false | ||
| 509 | }, | ||
| 510 | "subject": { | ||
| 511 | "name": "subject", | ||
| 512 | "type": "text", | ||
| 513 | "primaryKey": false, | ||
| 514 | "notNull": true, | ||
| 515 | "autoincrement": false | ||
| 516 | } | ||
| 517 | }, | ||
| 518 | "indexes": { | ||
| 519 | "oauth_client_uidx": { | ||
| 520 | "name": "oauth_client_uidx", | ||
| 521 | "columns": ["tenant", "owner", "subject", "slug"], | ||
| 522 | "isUnique": true | ||
| 523 | } | ||
| 524 | }, | ||
| 525 | "foreignKeys": {}, | ||
| 526 | "compositePrimaryKeys": {}, | ||
| 527 | "uniqueConstraints": {}, | ||
| 528 | "checkConstraints": {} | ||
| 529 | }, | ||
| 530 | "oauth_session": { | ||
| 531 | "name": "oauth_session", | ||
| 532 | "columns": { | ||
| 533 | "state": { | ||
| 534 | "name": "state", | ||
| 535 | "type": "text", | ||
| 536 | "primaryKey": false, | ||
| 537 | "notNull": true, | ||
| 538 | "autoincrement": false | ||
| 539 | }, | ||
| 540 | "client_slug": { | ||
| 541 | "name": "client_slug", | ||
| 542 | "type": "text", | ||
| 543 | "primaryKey": false, | ||
| 544 | "notNull": true, | ||
| 545 | "autoincrement": false | ||
| 546 | }, | ||
| 547 | "integration": { | ||
| 548 | "name": "integration", | ||
| 549 | "type": "text", | ||
| 550 | "primaryKey": false, | ||
| 551 | "notNull": true, | ||
| 552 | "autoincrement": false | ||
| 553 | }, | ||
| 554 | "name": { | ||
| 555 | "name": "name", | ||
| 556 | "type": "text", | ||
| 557 | "primaryKey": false, | ||
| 558 | "notNull": true, | ||
| 559 | "autoincrement": false | ||
| 560 | }, | ||
| 561 | "template": { | ||
| 562 | "name": "template", | ||
| 563 | "type": "text", | ||
| 564 | "primaryKey": false, | ||
| 565 | "notNull": true, | ||
| 566 | "autoincrement": false | ||
| 567 | }, | ||
| 568 | "redirect_url": { | ||
| 569 | "name": "redirect_url", | ||
| 570 | "type": "text", | ||
| 571 | "primaryKey": false, | ||
| 572 | "notNull": true, | ||
| 573 | "autoincrement": false | ||
| 574 | }, | ||
| 575 | "pkce_verifier": { | ||
| 576 | "name": "pkce_verifier", | ||
| 577 | "type": "text", | ||
| 578 | "primaryKey": false, | ||
| 579 | "notNull": false, | ||
| 580 | "autoincrement": false | ||
| 581 | }, | ||
| 582 | "identity_label": { | ||
| 583 | "name": "identity_label", | ||
| 584 | "type": "text", | ||
| 585 | "primaryKey": false, | ||
| 586 | "notNull": false, | ||
| 587 | "autoincrement": false | ||
| 588 | }, | ||
| 589 | "payload": { | ||
| 590 | "name": "payload", | ||
| 591 | "type": "text", | ||
| 592 | "primaryKey": false, | ||
| 593 | "notNull": true, | ||
| 594 | "autoincrement": false | ||
| 595 | }, | ||
| 596 | "expires_at": { | ||
| 597 | "name": "expires_at", | ||
| 598 | "type": "blob", | ||
| 599 | "primaryKey": false, | ||
| 600 | "notNull": true, | ||
| 601 | "autoincrement": false | ||
| 602 | }, | ||
| 603 | "created_at": { | ||
| 604 | "name": "created_at", | ||
| 605 | "type": "integer", | ||
| 606 | "primaryKey": false, | ||
| 607 | "notNull": true, | ||
| 608 | "autoincrement": false | ||
| 609 | }, | ||
| 610 | "row_id": { | ||
| 611 | "name": "row_id", | ||
| 612 | "type": "text", | ||
| 613 | "primaryKey": true, | ||
| 614 | "notNull": true, | ||
| 615 | "autoincrement": false | ||
| 616 | }, | ||
| 617 | "tenant": { | ||
| 618 | "name": "tenant", | ||
| 619 | "type": "text", | ||
| 620 | "primaryKey": false, | ||
| 621 | "notNull": true, | ||
| 622 | "autoincrement": false | ||
| 623 | }, | ||
| 624 | "owner": { | ||
| 625 | "name": "owner", | ||
| 626 | "type": "text", | ||
| 627 | "primaryKey": false, | ||
| 628 | "notNull": true, | ||
| 629 | "autoincrement": false | ||
| 630 | }, | ||
| 631 | "subject": { | ||
| 632 | "name": "subject", | ||
| 633 | "type": "text", | ||
| 634 | "primaryKey": false, | ||
| 635 | "notNull": true, | ||
| 636 | "autoincrement": false | ||
| 637 | } | ||
| 638 | }, | ||
| 639 | "indexes": { | ||
| 640 | "oauth_session_uidx": { | ||
| 641 | "name": "oauth_session_uidx", | ||
| 642 | "columns": ["tenant", "state"], | ||
| 643 | "isUnique": true | ||
| 644 | } | ||
| 645 | }, | ||
| 646 | "foreignKeys": {}, | ||
| 647 | "compositePrimaryKeys": {}, | ||
| 648 | "uniqueConstraints": {}, | ||
| 649 | "checkConstraints": {} | ||
| 650 | }, | ||
| 651 | "plugin_storage": { | ||
| 652 | "name": "plugin_storage", | ||
| 653 | "columns": { | ||
| 654 | "plugin_id": { | ||
| 655 | "name": "plugin_id", | ||
| 656 | "type": "text", | ||
| 657 | "primaryKey": false, | ||
| 658 | "notNull": true, | ||
| 659 | "autoincrement": false | ||
| 660 | }, | ||
| 661 | "collection": { | ||
| 662 | "name": "collection", | ||
| 663 | "type": "text", | ||
| 664 | "primaryKey": false, | ||
| 665 | "notNull": true, | ||
| 666 | "autoincrement": false | ||
| 667 | }, | ||
| 668 | "key": { | ||
| 669 | "name": "key", | ||
| 670 | "type": "text", | ||
| 671 | "primaryKey": false, | ||
| 672 | "notNull": true, | ||
| 673 | "autoincrement": false | ||
| 674 | }, | ||
| 675 | "data": { | ||
| 676 | "name": "data", | ||
| 677 | "type": "text", | ||
| 678 | "primaryKey": false, | ||
| 679 | "notNull": true, | ||
| 680 | "autoincrement": false | ||
| 681 | }, | ||
| 682 | "created_at": { | ||
| 683 | "name": "created_at", | ||
| 684 | "type": "integer", | ||
| 685 | "primaryKey": false, | ||
| 686 | "notNull": true, | ||
| 687 | "autoincrement": false | ||
| 688 | }, | ||
| 689 | "updated_at": { | ||
| 690 | "name": "updated_at", | ||
| 691 | "type": "integer", | ||
| 692 | "primaryKey": false, | ||
| 693 | "notNull": true, | ||
| 694 | "autoincrement": false | ||
| 695 | }, | ||
| 696 | "row_id": { | ||
| 697 | "name": "row_id", | ||
| 698 | "type": "text", | ||
| 699 | "primaryKey": true, | ||
| 700 | "notNull": true, | ||
| 701 | "autoincrement": false | ||
| 702 | }, | ||
| 703 | "tenant": { | ||
| 704 | "name": "tenant", | ||
| 705 | "type": "text", | ||
| 706 | "primaryKey": false, | ||
| 707 | "notNull": true, | ||
| 708 | "autoincrement": false | ||
| 709 | }, | ||
| 710 | "owner": { | ||
| 711 | "name": "owner", | ||
| 712 | "type": "text", | ||
| 713 | "primaryKey": false, | ||
| 714 | "notNull": true, | ||
| 715 | "autoincrement": false | ||
| 716 | }, | ||
| 717 | "subject": { | ||
| 718 | "name": "subject", | ||
| 719 | "type": "text", | ||
| 720 | "primaryKey": false, | ||
| 721 | "notNull": true, | ||
| 722 | "autoincrement": false | ||
| 723 | } | ||
| 724 | }, | ||
| 725 | "indexes": { | ||
| 726 | "plugin_storage_uidx": { | ||
| 727 | "name": "plugin_storage_uidx", | ||
| 728 | "columns": ["tenant", "owner", "subject", "plugin_id", "collection", "key"], | ||
| 729 | "isUnique": true | ||
| 730 | } | ||
| 731 | }, | ||
| 732 | "foreignKeys": {}, | ||
| 733 | "compositePrimaryKeys": {}, | ||
| 734 | "uniqueConstraints": {}, | ||
| 735 | "checkConstraints": {} | ||
| 736 | }, | ||
| 737 | "skill": { | ||
| 738 | "name": "skill", | ||
| 739 | "columns": { | ||
| 740 | "name": { | ||
| 741 | "name": "name", | ||
| 742 | "type": "text", | ||
| 743 | "primaryKey": false, | ||
| 744 | "notNull": true, | ||
| 745 | "autoincrement": false | ||
| 746 | }, | ||
| 747 | "description": { | ||
| 748 | "name": "description", | ||
| 749 | "type": "text", | ||
| 750 | "primaryKey": false, | ||
| 751 | "notNull": true, | ||
| 752 | "autoincrement": false | ||
| 753 | }, | ||
| 754 | "frontmatter": { | ||
| 755 | "name": "frontmatter", | ||
| 756 | "type": "text", | ||
| 757 | "primaryKey": false, | ||
| 758 | "notNull": true, | ||
| 759 | "autoincrement": false | ||
| 760 | }, | ||
| 761 | "files": { | ||
| 762 | "name": "files", | ||
| 763 | "type": "text", | ||
| 764 | "primaryKey": false, | ||
| 765 | "notNull": true, | ||
| 766 | "autoincrement": false | ||
| 767 | }, | ||
| 768 | "created_at": { | ||
| 769 | "name": "created_at", | ||
| 770 | "type": "integer", | ||
| 771 | "primaryKey": false, | ||
| 772 | "notNull": true, | ||
| 773 | "autoincrement": false | ||
| 774 | }, | ||
| 775 | "updated_at": { | ||
| 776 | "name": "updated_at", | ||
| 777 | "type": "integer", | ||
| 778 | "primaryKey": false, | ||
| 779 | "notNull": true, | ||
| 780 | "autoincrement": false | ||
| 781 | }, | ||
| 782 | "row_id": { | ||
| 783 | "name": "row_id", | ||
| 784 | "type": "text", | ||
| 785 | "primaryKey": true, | ||
| 786 | "notNull": true, | ||
| 787 | "autoincrement": false | ||
| 788 | }, | ||
| 789 | "tenant": { | ||
| 790 | "name": "tenant", | ||
| 791 | "type": "text", | ||
| 792 | "primaryKey": false, | ||
| 793 | "notNull": true, | ||
| 794 | "autoincrement": false | ||
| 795 | }, | ||
| 796 | "owner": { | ||
| 797 | "name": "owner", | ||
| 798 | "type": "text", | ||
| 799 | "primaryKey": false, | ||
| 800 | "notNull": true, | ||
| 801 | "autoincrement": false | ||
| 802 | }, | ||
| 803 | "subject": { | ||
| 804 | "name": "subject", | ||
| 805 | "type": "text", | ||
| 806 | "primaryKey": false, | ||
| 807 | "notNull": true, | ||
| 808 | "autoincrement": false | ||
| 809 | } | ||
| 810 | }, | ||
| 811 | "indexes": { | ||
| 812 | "skill_uidx": { | ||
| 813 | "name": "skill_uidx", | ||
| 814 | "columns": ["tenant", "owner", "subject", "name"], | ||
| 815 | "isUnique": true | ||
| 816 | } | ||
| 817 | }, | ||
| 818 | "foreignKeys": {}, | ||
| 819 | "compositePrimaryKeys": {}, | ||
| 820 | "uniqueConstraints": {}, | ||
| 821 | "checkConstraints": {} | ||
| 822 | }, | ||
| 823 | "tool": { | ||
| 824 | "name": "tool", | ||
| 825 | "columns": { | ||
| 826 | "integration": { | ||
| 827 | "name": "integration", | ||
| 828 | "type": "text", | ||
| 829 | "primaryKey": false, | ||
| 830 | "notNull": true, | ||
| 831 | "autoincrement": false | ||
| 832 | }, | ||
| 833 | "connection": { | ||
| 834 | "name": "connection", | ||
| 835 | "type": "text", | ||
| 836 | "primaryKey": false, | ||
| 837 | "notNull": true, | ||
| 838 | "autoincrement": false | ||
| 839 | }, | ||
| 840 | "plugin_id": { | ||
| 841 | "name": "plugin_id", | ||
| 842 | "type": "text", | ||
| 843 | "primaryKey": false, | ||
| 844 | "notNull": true, | ||
| 845 | "autoincrement": false | ||
| 846 | }, | ||
| 847 | "name": { | ||
| 848 | "name": "name", | ||
| 849 | "type": "text", | ||
| 850 | "primaryKey": false, | ||
| 851 | "notNull": true, | ||
| 852 | "autoincrement": false | ||
| 853 | }, | ||
| 854 | "description": { | ||
| 855 | "name": "description", | ||
| 856 | "type": "text", | ||
| 857 | "primaryKey": false, | ||
| 858 | "notNull": true, | ||
| 859 | "autoincrement": false | ||
| 860 | }, | ||
| 861 | "input_schema": { | ||
| 862 | "name": "input_schema", | ||
| 863 | "type": "text", | ||
| 864 | "primaryKey": false, | ||
| 865 | "notNull": false, | ||
| 866 | "autoincrement": false | ||
| 867 | }, | ||
| 868 | "output_schema": { | ||
| 869 | "name": "output_schema", | ||
| 870 | "type": "text", | ||
| 871 | "primaryKey": false, | ||
| 872 | "notNull": false, | ||
| 873 | "autoincrement": false | ||
| 874 | }, | ||
| 875 | "annotations": { | ||
| 876 | "name": "annotations", | ||
| 877 | "type": "text", | ||
| 878 | "primaryKey": false, | ||
| 879 | "notNull": false, | ||
| 880 | "autoincrement": false | ||
| 881 | }, | ||
| 882 | "created_at": { | ||
| 883 | "name": "created_at", | ||
| 884 | "type": "integer", | ||
| 885 | "primaryKey": false, | ||
| 886 | "notNull": true, | ||
| 887 | "autoincrement": false | ||
| 888 | }, | ||
| 889 | "updated_at": { | ||
| 890 | "name": "updated_at", | ||
| 891 | "type": "integer", | ||
| 892 | "primaryKey": false, | ||
| 893 | "notNull": true, | ||
| 894 | "autoincrement": false | ||
| 895 | }, | ||
| 896 | "row_id": { | ||
| 897 | "name": "row_id", | ||
| 898 | "type": "text", | ||
| 899 | "primaryKey": true, | ||
| 900 | "notNull": true, | ||
| 901 | "autoincrement": false | ||
| 902 | }, | ||
| 903 | "tenant": { | ||
| 904 | "name": "tenant", | ||
| 905 | "type": "text", | ||
| 906 | "primaryKey": false, | ||
| 907 | "notNull": true, | ||
| 908 | "autoincrement": false | ||
| 909 | }, | ||
| 910 | "owner": { | ||
| 911 | "name": "owner", | ||
| 912 | "type": "text", | ||
| 913 | "primaryKey": false, | ||
| 914 | "notNull": true, | ||
| 915 | "autoincrement": false | ||
| 916 | }, | ||
| 917 | "subject": { | ||
| 918 | "name": "subject", | ||
| 919 | "type": "text", | ||
| 920 | "primaryKey": false, | ||
| 921 | "notNull": true, | ||
| 922 | "autoincrement": false | ||
| 923 | } | ||
| 924 | }, | ||
| 925 | "indexes": { | ||
| 926 | "tool_uidx": { | ||
| 927 | "name": "tool_uidx", | ||
| 928 | "columns": ["tenant", "owner", "subject", "integration", "connection", "name"], | ||
| 929 | "isUnique": true | ||
| 930 | } | ||
| 931 | }, | ||
| 932 | "foreignKeys": {}, | ||
| 933 | "compositePrimaryKeys": {}, | ||
| 934 | "uniqueConstraints": {}, | ||
| 935 | "checkConstraints": {} | ||
| 936 | }, | ||
| 937 | "tool_policy": { | ||
| 938 | "name": "tool_policy", | ||
| 939 | "columns": { | ||
| 940 | "id": { | ||
| 941 | "name": "id", | ||
| 942 | "type": "text", | ||
| 943 | "primaryKey": false, | ||
| 944 | "notNull": true, | ||
| 945 | "autoincrement": false | ||
| 946 | }, | ||
| 947 | "pattern": { | ||
| 948 | "name": "pattern", | ||
| 949 | "type": "text", | ||
| 950 | "primaryKey": false, | ||
| 951 | "notNull": true, | ||
| 952 | "autoincrement": false | ||
| 953 | }, | ||
| 954 | "action": { | ||
| 955 | "name": "action", | ||
| 956 | "type": "text", | ||
| 957 | "primaryKey": false, | ||
| 958 | "notNull": true, | ||
| 959 | "autoincrement": false | ||
| 960 | }, | ||
| 961 | "position": { | ||
| 962 | "name": "position", | ||
| 963 | "type": "text", | ||
| 964 | "primaryKey": false, | ||
| 965 | "notNull": true, | ||
| 966 | "autoincrement": false | ||
| 967 | }, | ||
| 968 | "created_at": { | ||
| 969 | "name": "created_at", | ||
| 970 | "type": "integer", | ||
| 971 | "primaryKey": false, | ||
| 972 | "notNull": true, | ||
| 973 | "autoincrement": false | ||
| 974 | }, | ||
| 975 | "updated_at": { | ||
| 976 | "name": "updated_at", | ||
| 977 | "type": "integer", | ||
| 978 | "primaryKey": false, | ||
| 979 | "notNull": true, | ||
| 980 | "autoincrement": false | ||
| 981 | }, | ||
| 982 | "row_id": { | ||
| 983 | "name": "row_id", | ||
| 984 | "type": "text", | ||
| 985 | "primaryKey": true, | ||
| 986 | "notNull": true, | ||
| 987 | "autoincrement": false | ||
| 988 | }, | ||
| 989 | "tenant": { | ||
| 990 | "name": "tenant", | ||
| 991 | "type": "text", | ||
| 992 | "primaryKey": false, | ||
| 993 | "notNull": true, | ||
| 994 | "autoincrement": false | ||
| 995 | }, | ||
| 996 | "owner": { | ||
| 997 | "name": "owner", | ||
| 998 | "type": "text", | ||
| 999 | "primaryKey": false, | ||
| 1000 | "notNull": true, | ||
| 1001 | "autoincrement": false | ||
| 1002 | }, | ||
| 1003 | "subject": { | ||
| 1004 | "name": "subject", | ||
| 1005 | "type": "text", | ||
| 1006 | "primaryKey": false, | ||
| 1007 | "notNull": true, | ||
| 1008 | "autoincrement": false | ||
| 1009 | } | ||
| 1010 | }, | ||
| 1011 | "indexes": { | ||
| 1012 | "tool_policy_uidx": { | ||
| 1013 | "name": "tool_policy_uidx", | ||
| 1014 | "columns": ["tenant", "owner", "subject", "id"], | ||
| 1015 | "isUnique": true | ||
| 1016 | } | ||
| 1017 | }, | ||
| 1018 | "foreignKeys": {}, | ||
| 1019 | "compositePrimaryKeys": {}, | ||
| 1020 | "uniqueConstraints": {}, | ||
| 1021 | "checkConstraints": {} | ||
| 1022 | } | ||
| 1023 | }, | ||
| 1024 | "views": {}, | ||
| 1025 | "enums": {}, | ||
| 1026 | "_meta": { | ||
| 1027 | "schemas": {}, | ||
| 1028 | "tables": {}, | ||
| 1029 | "columns": {} | ||
| 1030 | }, | ||
| 1031 | "internal": { | ||
| 1032 | "indexes": {} | ||
| 1033 | } | ||
| 1034 | } | ||
| @@ -50,6 +50,13 @@ · 49 unmodified lines | |||
| 50 | "when": 1788255902609, | 50 | "when": 1788255902609, |
| 51 | "tag": "0006_bored_landau", | 51 | "tag": "0006_bored_landau", |
| 52 | "breakpoints": true | 52 | "breakpoints": true |
| 53 | }, | ||
| 54 | { | ||
| 55 | "idx": 7, | ||
| 56 | "version": "6", | ||
| 57 | "when": 1789348020175, | ||
| 58 | "tag": "0007_same_vengeance", | ||
| 59 | "breakpoints": true | ||
| 53 | } | 60 | } |
| 54 | ] | 61 | ] |
| 55 | } | 62 | } |
| @@ -188,6 +188,23 @@ export const tool_policy = sqliteTable( · 187 unmodified lines | |||
| 188 | ], | 188 | ], |
| 189 | ); | 189 | ); |
| 190 | 190 | ||
| 191 | export const skill = sqliteTable( | ||
| 192 | "skill", | ||
| 193 | { | ||
| 194 | name: text("name").notNull(), | ||
| 195 | description: text("description").notNull(), | ||
| 196 | frontmatter: text("frontmatter").notNull(), | ||
| 197 | files: text("files").notNull(), | ||
| 198 | created_at: integer("created_at").notNull(), | ||
| 199 | updated_at: integer("updated_at").notNull(), | ||
| 200 | row_id: text("row_id").primaryKey().notNull(), | ||
| 201 | tenant: text("tenant").notNull(), | ||
| 202 | owner: text("owner").notNull(), | ||
| 203 | subject: text("subject").notNull(), | ||
| 204 | }, | ||
| 205 | (table) => [uniqueIndex("skill_uidx").on(table.tenant, table.owner, table.subject, table.name)], | ||
| 206 | ); | ||
| 207 | |||
| 191 | export const plugin_storage = sqliteTable( | 208 | export const plugin_storage = sqliteTable( |
| 192 | "plugin_storage", | 209 | "plugin_storage", |
| 193 | { | 210 | { |
| @@ -122,6 +122,7 @@ export const createServerHandlers = async (token: string): Promise<ServerHandler · 121 unmodified lines | |||
| 122 | defaultConfig: { | 122 | defaultConfig: { |
| 123 | engine, | 123 | engine, |
| 124 | artifacts: executor.artifacts, | 124 | artifacts: executor.artifacts, |
| 125 | skills: executor.skills, | ||
| 125 | connections: executor.connections, | 126 | connections: executor.connections, |
| 126 | ...appsConfig, | 127 | ...appsConfig, |
| 127 | }, | 128 | }, |
| @@ -131,6 +132,7 @@ export const createServerHandlers = async (token: string): Promise<ServerHandler · 3 unmodified lines | |||
| 131 | config: { | 132 | config: { |
| 132 | engine, | 133 | engine, |
| 133 | artifacts: executor.artifacts, | 134 | artifacts: executor.artifacts, |
| 135 | skills: executor.skills, | ||
| 134 | connections: executor.connections, | 136 | connections: executor.connections, |
| 135 | ...appsConfig, | 137 | ...appsConfig, |
| 136 | }, | 138 | }, |
| @@ -156,6 +158,7 @@ export const createServerHandlers = async (token: string): Promise<ServerHandler · 19 unmodified lines | |||
| 156 | config: { | 158 | config: { |
| 157 | engine: toolkitEngine, | 159 | engine: toolkitEngine, |
| 158 | artifacts: handle.executor.artifacts, | 160 | artifacts: handle.executor.artifacts, |
| 161 | skills: handle.executor.skills, | ||
| 159 | connections: handle.executor.connections, | 162 | connections: handle.executor.connections, |
| 160 | ...appsConfig, | 163 | ...appsConfig, |
| 161 | }, | 164 | }, |
| @@ -612,6 +612,7 @@ · 611 unmodified lines | |||
| 612 | "fractional-indexing": "^3.2.0", | 612 | "fractional-indexing": "^3.2.0", |
| 613 | "oauth4webapi": "^3.8.5", | 613 | "oauth4webapi": "^3.8.5", |
| 614 | "tldts": "^7.0.28", | 614 | "tldts": "^7.0.28", |
| 615 | "yaml": "^2.9.0", | ||
| 615 | }, | 616 | }, |
| 616 | "devDependencies": { | 617 | "devDependencies": { |
| 617 | "@effect/atom-react": "catalog:", | 618 | "@effect/atom-react": "catalog:", |
| @@ -5827,7 +5828,7 @@ · 5209 unmodified lines | |||
| 5827 | 5828 | ||
| 5828 | "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], | 5829 | "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], |
| 5829 | 5830 | ||
| 5830 | "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], | 5831 | "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], |
| 5831 | 5832 | ||
| 5832 | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], | 5833 | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], |
| 5833 | 5834 | ||
| @@ -5999,8 +6000,6 @@ · 165 unmodified lines | |||
| 5999 | 6000 | ||
| 6000 | "@executor-js/emulate/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], | 6001 | "@executor-js/emulate/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], |
| 6001 | 6002 | ||
| 6002 | "@executor-js/emulate/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], | ||
| 6003 | |||
| 6004 | "@executor-js/example-all-plugins/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], | 6003 | "@executor-js/example-all-plugins/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], |
| 6005 | 6004 | ||
| 6006 | "@executor-js/example-promise-sdk/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], | 6005 | "@executor-js/example-promise-sdk/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], |
| @@ -6113,6 +6112,8 @@ · 106 unmodified lines | |||
| 6113 | 6112 | ||
| 6114 | "@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], | 6113 | "@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], |
| 6115 | 6114 | ||
| 6115 | "@opentelemetry/configuration/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], | ||
| 6116 | |||
| 6116 | "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], | 6117 | "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], |
| 6117 | 6118 | ||
| 6118 | "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], | 6119 | "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], |
| @@ -6401,8 +6402,6 @@ · 282 unmodified lines | |||
| 6401 | 6402 | ||
| 6402 | "agents/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], | 6403 | "agents/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], |
| 6403 | 6404 | ||
| 6404 | "agents/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], | ||
| 6405 | |||
| 6406 | "agents/yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="], | 6405 | "agents/yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="], |
| 6407 | 6406 | ||
| 6408 | "ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], | 6407 | "ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], |
| @@ -6503,6 +6502,8 @@ · 94 unmodified lines | |||
| 6503 | 6502 | ||
| 6504 | "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], | 6503 | "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], |
| 6505 | 6504 | ||
| 6505 | "effect/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], | ||
| 6506 | |||
| 6506 | "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], | 6507 | "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], |
| 6507 | 6508 | ||
| 6508 | "electron-builder/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], | 6509 | "electron-builder/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], |
| @@ -6597,6 +6598,8 @@ · 88 unmodified lines | |||
| 6597 | 6598 | ||
| 6598 | "knip/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], | 6599 | "knip/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], |
| 6599 | 6600 | ||
| 6601 | "knip/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], | ||
| 6602 | |||
| 6600 | "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], | 6603 | "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], |
| 6601 | 6604 | ||
| 6602 | "macos-version/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], | 6605 | "macos-version/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], |
| @@ -0,0 +1,142 @@ | |||
| 1 | // Cross-target: Agent Skills, end to end. | ||
| 2 | // | ||
| 3 | // A user saves a SKILL.md directory to their workspace through the console's | ||
| 4 | // API. The product promise under test is that every agent connected over MCP | ||
| 5 | // sees it next — in the `skills` tool's description (the catalog), in its index, | ||
| 6 | // and as loadable instructions with the bundled files reachable one at a time. | ||
| 7 | // Delete it, and the agent stops seeing it. The console and the agent share one | ||
| 8 | // store, not two caches. | ||
| 9 | import { randomBytes } from "node:crypto"; | ||
| 10 | |||
| 11 | import { expect } from "@effect/vitest"; | ||
| 12 | import { Effect } from "effect"; | ||
| 13 | import { composePluginApi } from "@executor-js/api/server"; | ||
| 14 | import { SkillName } from "@executor-js/sdk/shared"; | ||
| 15 | |||
| 16 | import { scenario } from "../src/scenario"; | ||
| 17 | import { Api, Mcp, Target } from "../src/services"; | ||
| 18 | |||
| 19 | const api = composePluginApi([] as const); | ||
| 20 | |||
| 21 | /** Selfhost shares one workspace across scenarios, so every name is unique to | ||
| 22 | * this run and assertions look for "mine", never "the only one". */ | ||
| 23 | const uniqueSuffix = () => randomBytes(4).toString("hex"); | ||
| 24 | |||
| 25 | const skillMarkdown = (name: string, marker: string) => | ||
| 26 | [ | ||
| 27 | "---", | ||
| 28 | `name: ${name}`, | ||
| 29 | `description: Release-notes house style. Use when drafting release notes (${marker}).`, | ||
| 30 | "metadata:", | ||
| 31 | ' version: "1.0"', | ||
| 32 | "---", | ||
| 33 | "", | ||
| 34 | "# Release notes", | ||
| 35 | "", | ||
| 36 | `Lead with the user-visible change. Marker: ${marker}.`, | ||
| 37 | "See `references/tone.md` for the voice.", | ||
| 38 | ].join("\n"); | ||
| 39 | |||
| 40 | scenario( | ||
| 41 | "Skills · a skill saved to the workspace is what a connected agent loads next", | ||
| 42 | { timeout: 120_000 }, | ||
| 43 | Effect.gen(function* () { | ||
| 44 | const target = yield* Target; | ||
| 45 | const mcp = yield* Mcp; | ||
| 46 | const { client: apiClient } = yield* Api; | ||
| 47 | |||
| 48 | const identity = yield* target.newIdentity(); | ||
| 49 | const client = yield* apiClient(api, identity); | ||
| 50 | const session = mcp.session(identity); | ||
| 51 | |||
| 52 | const suffix = uniqueSuffix(); | ||
| 53 | const name = SkillName.make(`release-notes-${suffix}`); | ||
| 54 | const marker = `skill-ok-${suffix}`; | ||
| 55 | const toneMarker = `tone-ok-${suffix}`; | ||
| 56 | |||
| 57 | yield* Effect.gen(function* () { | ||
| 58 | // Save through the same endpoint the console's editor uses. | ||
| 59 | const saved = yield* client.skills.save({ | ||
| 60 | payload: { | ||
| 61 | owner: "user", | ||
| 62 | files: [ | ||
| 63 | { path: "SKILL.md", content: skillMarkdown(name, marker) }, | ||
| 64 | { path: "references/tone.md", content: `# Tone\n\nPlain and direct. ${toneMarker}` }, | ||
| 65 | ], | ||
| 66 | }, | ||
| 67 | }); | ||
| 68 | expect(saved.name, "the name comes from the frontmatter").toBe(name); | ||
| 69 | expect( | ||
| 70 | saved.files.map((file) => file.path), | ||
| 71 | "SKILL.md leads the stored manifest", | ||
| 72 | ).toEqual(["SKILL.md", "references/tone.md"]); | ||
| 73 | for (const file of saved.files) { | ||
| 74 | expect(file.digest, `${file.path} carries a sha256 digest`).toMatch( | ||
| 75 | /^sha256:[0-9a-f]{64}$/, | ||
| 76 | ); | ||
| 77 | } | ||
| 78 | |||
| 79 | // A fresh MCP session sees the skill in the tool's own description — the | ||
| 80 | // catalog a model reads before it asks for anything. | ||
| 81 | const tools = yield* session.describeTools(); | ||
| 82 | const skillsTool = tools.find((tool) => tool.name === "skills"); | ||
| 83 | expect(skillsTool?.description, "the skills tool advertises the saved skill").toContain( | ||
| 84 | `\`${name}\``, | ||
| 85 | ); | ||
| 86 | |||
| 87 | const index = yield* session.call("skills", {}); | ||
| 88 | expect(index.text, "the index lists the saved skill next to Executor's docs").toContain( | ||
| 89 | `\`${name}\``, | ||
| 90 | ); | ||
| 91 | expect(index.text).toContain("`execute`"); | ||
| 92 | |||
| 93 | const loaded = yield* session.call("skills", { name }); | ||
| 94 | expect(loaded.ok, `loading the skill succeeds: ${loaded.text}`).toBe(true); | ||
| 95 | expect(loaded.text, "the body is served with its frontmatter stripped").toContain( | ||
| 96 | `Marker: ${marker}`, | ||
| 97 | ); | ||
| 98 | expect(loaded.text).not.toContain("description: Release-notes"); | ||
| 99 | expect(loaded.text, "bundled files are listed, not inlined").toContain( | ||
| 100 | "<file>references/tone.md</file>", | ||
| 101 | ); | ||
| 102 | expect(loaded.text).not.toContain(toneMarker); | ||
| 103 | |||
| 104 | const tone = yield* session.call("skills", { name, file: "references/tone.md" }); | ||
| 105 | expect(tone.ok, `reading a bundled file succeeds: ${tone.text}`).toBe(true); | ||
| 106 | expect(tone.text, "the bundled file's content is returned verbatim").toContain(toneMarker); | ||
| 107 | |||
| 108 | // Delete in the console; the agent's next call misses. | ||
| 109 | yield* client.skills.remove({ params: { owner: "user", name } }); | ||
| 110 | const gone = yield* session.call("skills", { name }); | ||
| 111 | expect(gone.ok, "a removed skill no longer loads").toBe(false); | ||
| 112 | expect(gone.text).toContain(`No skill named "${name}"`); | ||
| 113 | }).pipe( | ||
| 114 | Effect.ensuring( | ||
| 115 | client.skills.remove({ params: { owner: "user", name } }).pipe(Effect.ignore), | ||
| 116 | ), | ||
| 117 | ); | ||
| 118 | }), | ||
| 119 | ); | ||
| 120 | |||
| 121 | scenario( | ||
| 122 | "Skills · a malformed SKILL.md is refused with the reason", | ||
| 123 | {}, | ||
| 124 | Effect.gen(function* () { | ||
| 125 | const target = yield* Target; | ||
| 126 | const { client: apiClient } = yield* Api; | ||
| 127 | const identity = yield* target.newIdentity(); | ||
| 128 | const client = yield* apiClient(api, identity); | ||
| 129 | |||
| 130 | const result = yield* client.skills | ||
| 131 | .save({ | ||
| 132 | payload: { | ||
| 133 | owner: "user", | ||
| 134 | files: [{ path: "SKILL.md", content: "---\nname: Not Valid\ndescription: x\n---\n" }], | ||
| 135 | }, | ||
| 136 | }) | ||
| 137 | .pipe(Effect.result); | ||
| 138 | expect(result._tag, "the save is refused").toBe("Failure"); | ||
| 139 | if (result._tag !== "Failure") return; | ||
| 140 | expect(String(result.failure._tag), "as an invalid-skill error").toBe("InvalidSkillError"); | ||
| 141 | }), | ||
| 142 | ); | ||
| @@ -12,15 +12,18 @@ import { Route as rootRouteImport } from './routes/__root' · 11 unmodified lines | |||
| 12 | import { Route as DotDotDotDotDotDotReactSrcRoutesIndexRouteImport } from './../../react/src/routes/index' | 12 | import { Route as DotDotDotDotDotDotReactSrcRoutesIndexRouteImport } from './../../react/src/routes/index' |
| 13 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolsRouteImport } from './../../react/src/routes/tools' | 13 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolsRouteImport } from './../../react/src/routes/tools' |
| 14 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsRouteImport } from './../../react/src/routes/toolkits' | 14 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsRouteImport } from './../../react/src/routes/toolkits' |
| 15 | import { Route as DotDotDotDotDotDotReactSrcRoutesSkillsRouteImport } from './../../react/src/routes/skills' | ||
| 15 | import { Route as SecretsRouteImport } from './routes/app/secrets' | 16 | import { Route as SecretsRouteImport } from './routes/app/secrets' |
| 16 | import { Route as DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport } from './../../react/src/routes/policies' | 17 | import { Route as DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport } from './../../react/src/routes/policies' |
| 17 | import { Route as DotDotDotDotDotDotReactSrcRoutesArtifactsRouteImport } from './../../react/src/routes/artifacts' | 18 | import { Route as DotDotDotDotDotDotReactSrcRoutesArtifactsRouteImport } from './../../react/src/routes/artifacts' |
| 18 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../react/src/routes/toolkits.$toolkitSlug' | 19 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../react/src/routes/toolkits.$toolkitSlug' |
| 20 | import { Route as DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRouteImport } from './../../react/src/routes/skills.new' | ||
| 19 | import { Route as DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../react/src/routes/resume.$executionId' | 21 | import { Route as DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../react/src/routes/resume.$executionId' |
| 20 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../react/src/routes/integrations.browse' | 22 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRouteImport } from './../../react/src/routes/integrations.browse' |
| 21 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../react/src/routes/integrations.$namespace' | 23 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../react/src/routes/integrations.$namespace' |
| 22 | import { Route as DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../react/src/routes/connect.$integrationSlug' | 24 | import { Route as DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../react/src/routes/connect.$integrationSlug' |
| 23 | import { Route as DotDotDotDotDotDotReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../react/src/routes/artifacts.$artifactId' | 25 | import { Route as DotDotDotDotDotDotReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../react/src/routes/artifacts.$artifactId' |
| 26 | import { Route as DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport } from './../../react/src/routes/skills.$skillOwner.$skillName' | ||
| 24 | import { Route as DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../react/src/routes/plugins.$pluginId.$' | 27 | import { Route as DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../react/src/routes/plugins.$pluginId.$' |
| 25 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../react/src/routes/integrations.add.$pluginKey' | 28 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../react/src/routes/integrations.add.$pluginKey' |
| 26 | 29 | ||
| @@ -42,6 +45,12 @@ const DotDotDotDotDotDotReactSrcRoutesToolkitsRoute = · 15 unmodified lines | |||
| 42 | path: '/{-$orgSlug}/toolkits', | 45 | path: '/{-$orgSlug}/toolkits', |
| 43 | getParentRoute: () => rootRouteImport, | 46 | getParentRoute: () => rootRouteImport, |
| 44 | } as any) | 47 | } as any) |
| 48 | const DotDotDotDotDotDotReactSrcRoutesSkillsRoute = | ||
| 49 | DotDotDotDotDotDotReactSrcRoutesSkillsRouteImport.update({ | ||
| 50 | id: '/{-$orgSlug}/skills', | ||
| 51 | path: '/{-$orgSlug}/skills', | ||
| 52 | getParentRoute: () => rootRouteImport, | ||
| 53 | } as any) | ||
| 45 | const SecretsRoute = SecretsRouteImport.update({ | 54 | const SecretsRoute = SecretsRouteImport.update({ |
| 46 | id: '/{-$orgSlug}/secrets', | 55 | id: '/{-$orgSlug}/secrets', |
| 47 | path: '/{-$orgSlug}/secrets', | 56 | path: '/{-$orgSlug}/secrets', |
| @@ -65,6 +74,12 @@ const DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute = · 17 unmodified lines | |||
| 65 | path: '/$toolkitSlug', | 74 | path: '/$toolkitSlug', |
| 66 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesToolkitsRoute, | 75 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesToolkitsRoute, |
| 67 | } as any) | 76 | } as any) |
| 77 | const DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute = | ||
| 78 | DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRouteImport.update({ | ||
| 79 | id: '/new', | ||
| 80 | path: '/new', | ||
| 81 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesSkillsRoute, | ||
| 82 | } as any) | ||
| 68 | const DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute = | 83 | const DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute = |
| 69 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport.update({ | 84 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport.update({ |
| 70 | id: '/{-$orgSlug}/resume/$executionId', | 85 | id: '/{-$orgSlug}/resume/$executionId', |
| @@ -95,6 +110,14 @@ const DotDotDotDotDotDotReactSrcRoutesArtifactsDotartifactIdRoute = · 24 unmodified lines | |||
| 95 | path: '/$artifactId', | 110 | path: '/$artifactId', |
| 96 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesArtifactsRoute, | 111 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesArtifactsRoute, |
| 97 | } as any) | 112 | } as any) |
| 113 | const DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute = | ||
| 114 | DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport.update( | ||
| 115 | { | ||
| 116 | id: '/$skillOwner/$skillName', | ||
| 117 | path: '/$skillOwner/$skillName', | ||
| 118 | getParentRoute: () => DotDotDotDotDotDotReactSrcRoutesSkillsRoute, | ||
| 119 | } as any, | ||
| 120 | ) | ||
| 98 | const DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute = | 121 | const DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute = |
| 99 | DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update({ | 122 | DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update({ |
| 100 | id: '/{-$orgSlug}/plugins/$pluginId/$', | 123 | id: '/{-$orgSlug}/plugins/$pluginId/$', |
| @@ -114,6 +137,7 @@ export interface FileRoutesByFullPath { · 13 unmodified lines | |||
| 114 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren | 137 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren |
| 115 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute | 138 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute |
| 116 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 139 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 140 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren | ||
| 117 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 141 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 118 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 142 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 119 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 143 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| @@ -122,14 +146,17 @@ export interface FileRoutesByFullPath { · 2 unmodified lines | |||
| 122 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 146 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 123 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute | 147 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute |
| 124 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 148 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 149 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute | ||
| 125 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 150 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 126 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 151 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 127 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 152 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 153 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 128 | } | 154 | } |
| 129 | export interface FileRoutesByTo { | 155 | export interface FileRoutesByTo { |
| 130 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren | 156 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren |
| 131 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute | 157 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute |
| 132 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 158 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 159 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren | ||
| 133 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 160 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 134 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 161 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 135 | '/{-$orgSlug}': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 162 | '/{-$orgSlug}': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| @@ -138,15 +165,18 @@ export interface FileRoutesByTo { · 2 unmodified lines | |||
| 138 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 165 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 139 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute | 166 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute |
| 140 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 167 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 168 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute | ||
| 141 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 169 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 142 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 170 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 143 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 171 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 172 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 144 | } | 173 | } |
| 145 | export interface FileRoutesById { | 174 | export interface FileRoutesById { |
| 146 | __root__: typeof rootRouteImport | 175 | __root__: typeof rootRouteImport |
| 147 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren | 176 | '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren |
| 148 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute | 177 | '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute |
| 149 | '/{-$orgSlug}/secrets': typeof SecretsRoute | 178 | '/{-$orgSlug}/secrets': typeof SecretsRoute |
| 179 | '/{-$orgSlug}/skills': typeof DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren | ||
| 150 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 180 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 151 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 181 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 152 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 182 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| @@ -155,9 +185,11 @@ export interface FileRoutesById { · 2 unmodified lines | |||
| 155 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 185 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 156 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute | 186 | '/{-$orgSlug}/integrations/browse': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotbrowseRoute |
| 157 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 187 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 188 | '/{-$orgSlug}/skills/new': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute | ||
| 158 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 189 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 159 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 190 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| 160 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute | 191 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute |
| 192 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 161 | } | 193 | } |
| 162 | export interface FileRouteTypes { | 194 | export interface FileRouteTypes { |
| 163 | fileRoutesByFullPath: FileRoutesByFullPath | 195 | fileRoutesByFullPath: FileRoutesByFullPath |
| @@ -165,6 +197,7 @@ export interface FileRouteTypes { · 1 unmodified line | |||
| 165 | | '/{-$orgSlug}/artifacts' | 197 | | '/{-$orgSlug}/artifacts' |
| 166 | | '/{-$orgSlug}/policies' | 198 | | '/{-$orgSlug}/policies' |
| 167 | | '/{-$orgSlug}/secrets' | 199 | | '/{-$orgSlug}/secrets' |
| 200 | | '/{-$orgSlug}/skills' | ||
| 168 | | '/{-$orgSlug}/toolkits' | 201 | | '/{-$orgSlug}/toolkits' |
| 169 | | '/{-$orgSlug}/tools' | 202 | | '/{-$orgSlug}/tools' |
| 170 | | '/{-$orgSlug}/' | 203 | | '/{-$orgSlug}/' |
| @@ -173,14 +206,17 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 173 | | '/{-$orgSlug}/integrations/$namespace' | 206 | | '/{-$orgSlug}/integrations/$namespace' |
| 174 | | '/{-$orgSlug}/integrations/browse' | 207 | | '/{-$orgSlug}/integrations/browse' |
| 175 | | '/{-$orgSlug}/resume/$executionId' | 208 | | '/{-$orgSlug}/resume/$executionId' |
| 209 | | '/{-$orgSlug}/skills/new' | ||
| 176 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 210 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 177 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 211 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 178 | | '/{-$orgSlug}/plugins/$pluginId/$' | 212 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 213 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 179 | fileRoutesByTo: FileRoutesByTo | 214 | fileRoutesByTo: FileRoutesByTo |
| 180 | to: | 215 | to: |
| 181 | | '/{-$orgSlug}/artifacts' | 216 | | '/{-$orgSlug}/artifacts' |
| 182 | | '/{-$orgSlug}/policies' | 217 | | '/{-$orgSlug}/policies' |
| 183 | | '/{-$orgSlug}/secrets' | 218 | | '/{-$orgSlug}/secrets' |
| 219 | | '/{-$orgSlug}/skills' | ||
| 184 | | '/{-$orgSlug}/toolkits' | 220 | | '/{-$orgSlug}/toolkits' |
| 185 | | '/{-$orgSlug}/tools' | 221 | | '/{-$orgSlug}/tools' |
| 186 | | '/{-$orgSlug}' | 222 | | '/{-$orgSlug}' |
| @@ -189,14 +225,17 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 189 | | '/{-$orgSlug}/integrations/$namespace' | 225 | | '/{-$orgSlug}/integrations/$namespace' |
| 190 | | '/{-$orgSlug}/integrations/browse' | 226 | | '/{-$orgSlug}/integrations/browse' |
| 191 | | '/{-$orgSlug}/resume/$executionId' | 227 | | '/{-$orgSlug}/resume/$executionId' |
| 228 | | '/{-$orgSlug}/skills/new' | ||
| 192 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 229 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 193 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 230 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 194 | | '/{-$orgSlug}/plugins/$pluginId/$' | 231 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 232 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 195 | id: | 233 | id: |
| 196 | | '__root__' | 234 | | '__root__' |
| 197 | | '/{-$orgSlug}/artifacts' | 235 | | '/{-$orgSlug}/artifacts' |
| 198 | | '/{-$orgSlug}/policies' | 236 | | '/{-$orgSlug}/policies' |
| 199 | | '/{-$orgSlug}/secrets' | 237 | | '/{-$orgSlug}/secrets' |
| 238 | | '/{-$orgSlug}/skills' | ||
| 200 | | '/{-$orgSlug}/toolkits' | 239 | | '/{-$orgSlug}/toolkits' |
| 201 | | '/{-$orgSlug}/tools' | 240 | | '/{-$orgSlug}/tools' |
| 202 | | '/{-$orgSlug}/' | 241 | | '/{-$orgSlug}/' |
| @@ -205,15 +244,18 @@ export interface FileRouteTypes { · 2 unmodified lines | |||
| 205 | | '/{-$orgSlug}/integrations/$namespace' | 244 | | '/{-$orgSlug}/integrations/$namespace' |
| 206 | | '/{-$orgSlug}/integrations/browse' | 245 | | '/{-$orgSlug}/integrations/browse' |
| 207 | | '/{-$orgSlug}/resume/$executionId' | 246 | | '/{-$orgSlug}/resume/$executionId' |
| 247 | | '/{-$orgSlug}/skills/new' | ||
| 208 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 248 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 209 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 249 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 210 | | '/{-$orgSlug}/plugins/$pluginId/$' | 250 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 251 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 211 | fileRoutesById: FileRoutesById | 252 | fileRoutesById: FileRoutesById |
| 212 | } | 253 | } |
| 213 | export interface RootRouteChildren { | 254 | export interface RootRouteChildren { |
| 214 | DotDotDotDotDotDotReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren | 255 | DotDotDotDotDotDotReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren |
| 215 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute | 256 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute |
| 216 | SecretsRoute: typeof SecretsRoute | 257 | SecretsRoute: typeof SecretsRoute |
| 258 | DotDotDotDotDotDotReactSrcRoutesSkillsRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren | ||
| 217 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 259 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 218 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 260 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 219 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 261 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| @@ -248,6 +290,13 @@ declare module '@tanstack/react-router' { · 28 unmodified lines | |||
| 248 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteImport | 290 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteImport |
| 249 | parentRoute: typeof rootRouteImport | 291 | parentRoute: typeof rootRouteImport |
| 250 | } | 292 | } |
| 293 | '/{-$orgSlug}/skills': { | ||
| 294 | id: '/{-$orgSlug}/skills' | ||
| 295 | path: '/{-$orgSlug}/skills' | ||
| 296 | fullPath: '/{-$orgSlug}/skills' | ||
| 297 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsRouteImport | ||
| 298 | parentRoute: typeof rootRouteImport | ||
| 299 | } | ||
| 251 | '/{-$orgSlug}/secrets': { | 300 | '/{-$orgSlug}/secrets': { |
| 252 | id: '/{-$orgSlug}/secrets' | 301 | id: '/{-$orgSlug}/secrets' |
| 253 | path: '/{-$orgSlug}/secrets' | 302 | path: '/{-$orgSlug}/secrets' |
| @@ -276,6 +325,13 @@ declare module '@tanstack/react-router' { · 22 unmodified lines | |||
| 276 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport | 325 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport |
| 277 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRoute | 326 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRoute |
| 278 | } | 327 | } |
| 328 | '/{-$orgSlug}/skills/new': { | ||
| 329 | id: '/{-$orgSlug}/skills/new' | ||
| 330 | path: '/new' | ||
| 331 | fullPath: '/{-$orgSlug}/skills/new' | ||
| 332 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRouteImport | ||
| 333 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsRoute | ||
| 334 | } | ||
| 279 | '/{-$orgSlug}/resume/$executionId': { | 335 | '/{-$orgSlug}/resume/$executionId': { |
| 280 | id: '/{-$orgSlug}/resume/$executionId' | 336 | id: '/{-$orgSlug}/resume/$executionId' |
| 281 | path: '/{-$orgSlug}/resume/$executionId' | 337 | path: '/{-$orgSlug}/resume/$executionId' |
| @@ -311,6 +367,13 @@ declare module '@tanstack/react-router' { · 29 unmodified lines | |||
| 311 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsDotartifactIdRouteImport | 367 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsDotartifactIdRouteImport |
| 312 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRoute | 368 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesArtifactsRoute |
| 313 | } | 369 | } |
| 370 | '/{-$orgSlug}/skills/$skillOwner/$skillName': { | ||
| 371 | id: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 372 | path: '/$skillOwner/$skillName' | ||
| 373 | fullPath: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 374 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRouteImport | ||
| 375 | parentRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsRoute | ||
| 376 | } | ||
| 314 | '/{-$orgSlug}/plugins/$pluginId/$': { | 377 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 315 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 378 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 316 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 379 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -343,6 +406,24 @@ const DotDotDotDotDotDotReactSrcRoutesArtifactsRouteWithChildren = · 26 unmodified lines | |||
| 343 | DotDotDotDotDotDotReactSrcRoutesArtifactsRouteChildren, | 406 | DotDotDotDotDotDotReactSrcRoutesArtifactsRouteChildren, |
| 344 | ) | 407 | ) |
| 345 | 408 | ||
| 409 | interface DotDotDotDotDotDotReactSrcRoutesSkillsRouteChildren { | ||
| 410 | DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute | ||
| 411 | DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: typeof DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute | ||
| 412 | } | ||
| 413 | |||
| 414 | const DotDotDotDotDotDotReactSrcRoutesSkillsRouteChildren: DotDotDotDotDotDotReactSrcRoutesSkillsRouteChildren = | ||
| 415 | { | ||
| 416 | DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute: | ||
| 417 | DotDotDotDotDotDotReactSrcRoutesSkillsDotnewRoute, | ||
| 418 | DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute: | ||
| 419 | DotDotDotDotDotDotReactSrcRoutesSkillsDotskillOwnerDotskillNameRoute, | ||
| 420 | } | ||
| 421 | |||
| 422 | const DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren = | ||
| 423 | DotDotDotDotDotDotReactSrcRoutesSkillsRoute._addFileChildren( | ||
| 424 | DotDotDotDotDotDotReactSrcRoutesSkillsRouteChildren, | ||
| 425 | ) | ||
| 426 | |||
| 346 | interface DotDotDotDotDotDotReactSrcRoutesToolkitsRouteChildren { | 427 | interface DotDotDotDotDotDotReactSrcRoutesToolkitsRouteChildren { |
| 347 | DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 428 | DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| 348 | } | 429 | } |
| @@ -364,6 +445,8 @@ const rootRouteChildren: RootRouteChildren = { · 15 unmodified lines | |||
| 364 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute: | 445 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute: |
| 365 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute, | 446 | DotDotDotDotDotDotReactSrcRoutesPoliciesRoute, |
| 366 | SecretsRoute: SecretsRoute, | 447 | SecretsRoute: SecretsRoute, |
| 448 | DotDotDotDotDotDotReactSrcRoutesSkillsRoute: | ||
| 449 | DotDotDotDotDotDotReactSrcRoutesSkillsRouteWithChildren, | ||
| 367 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: | 450 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: |
| 368 | DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren, | 451 | DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren, |
| 369 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: | 452 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: |
| @@ -177,6 +177,7 @@ function SidebarContent(props: { · 176 unmodified lines | |||
| 177 | const isPolicies = props.pathname === "/policies"; | 177 | const isPolicies = props.pathname === "/policies"; |
| 178 | const isToolkits = props.pathname === "/toolkits" || props.pathname.startsWith("/toolkits/"); | 178 | const isToolkits = props.pathname === "/toolkits" || props.pathname.startsWith("/toolkits/"); |
| 179 | const isArtifacts = props.pathname === "/artifacts" || props.pathname.startsWith("/artifacts/"); | 179 | const isArtifacts = props.pathname === "/artifacts" || props.pathname.startsWith("/artifacts/"); |
| 180 | const isSkills = props.pathname === "/skills" || props.pathname.startsWith("/skills/"); | ||
| 180 | 181 | ||
| 181 | return ( | 182 | return ( |
| 182 | <> | 183 | <> |
| @@ -216,6 +217,12 @@ function SidebarContent(props: { · 33 unmodified lines | |||
| 216 | active={isToolkits} | 217 | active={isToolkits} |
| 217 | onNavigate={props.onNavigate} | 218 | onNavigate={props.onNavigate} |
| 218 | /> | 219 | /> |
| 220 | <NavItem | ||
| 221 | to="/{-$orgSlug}/skills" | ||
| 222 | label="Skills" | ||
| 223 | active={isSkills} | ||
| 224 | onNavigate={props.onNavigate} | ||
| 225 | /> | ||
| 219 | <NavItem | 226 | <NavItem |
| 220 | to="/{-$orgSlug}/artifacts" | 227 | to="/{-$orgSlug}/artifacts" |
| 221 | label="Artifacts" | 228 | label="Artifacts" |
| @@ -18,7 +18,7 @@ const ORG_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,47}$/; · 17 unmodified lines | |||
| 18 | * - App planes: api, mcp, .well-known (cloud `app-paths.ts`, selfhost | 18 | * - App planes: api, mcp, .well-known (cloud `app-paths.ts`, selfhost |
| 19 | * envelope, cloudflare `run_worker_first`) | 19 | * envelope, cloudflare `run_worker_first`) |
| 20 | * - Console routes: connect, integrations, policies, secrets, tools, users, | 20 | * - Console routes: connect, integrations, policies, secrets, tools, users, |
| 21 | * toolkits, artifacts, resume, plugins (the shared | 21 | * toolkits, artifacts, skills, resume, plugins (the shared |
| 22 | * contract, i.e. every entry in `CONSOLE_ROUTE_PATHS`), | 22 | * contract, i.e. every entry in `CONSOLE_ROUTE_PATHS`), |
| 23 | * plus host extras: | 23 | * plus host extras: |
| 24 | * api-keys, org, billing, create-org, setup-mcp (cloud), | 24 | * api-keys, org, billing, create-org, setup-mcp (cloud), |
| @@ -50,6 +50,7 @@ export const RESERVED_ORG_SLUGS: ReadonlySet<string> = new Set([ · 25 unmodified lines | |||
| 50 | "tools", | 50 | "tools", |
| 51 | "toolkits", | 51 | "toolkits", |
| 52 | "artifacts", | 52 | "artifacts", |
| 53 | "skills", | ||
| 53 | "resume", | 54 | "resume", |
| 54 | "plugins", | 55 | "plugins", |
| 55 | "api-keys", | 56 | "api-keys", |
| @@ -9,6 +9,7 @@ import { ExecutionsApi } from "./executions/api"; · 8 unmodified lines | |||
| 9 | import { OAuthApi } from "./oauth/api"; | 9 | import { OAuthApi } from "./oauth/api"; |
| 10 | import { PoliciesApi } from "./policies/api"; | 10 | import { PoliciesApi } from "./policies/api"; |
| 11 | import { ArtifactsApi } from "./artifacts/api"; | 11 | import { ArtifactsApi } from "./artifacts/api"; |
| 12 | import { SkillsApi } from "./skills/api"; | ||
| 12 | 13 | ||
| 13 | export const CoreExecutorApi = HttpApi.make("executor") | 14 | export const CoreExecutorApi = HttpApi.make("executor") |
| 14 | .add(ToolsApi) | 15 | .add(ToolsApi) |
| @@ -19,6 +20,7 @@ export const CoreExecutorApi = HttpApi.make("executor") · 4 unmodified lines | |||
| 19 | .add(OAuthApi) | 20 | .add(OAuthApi) |
| 20 | .add(PoliciesApi) | 21 | .add(PoliciesApi) |
| 21 | .add(ArtifactsApi) | 22 | .add(ArtifactsApi) |
| 23 | .add(SkillsApi) | ||
| 22 | .annotateMerge( | 24 | .annotateMerge( |
| 23 | OpenApi.annotations({ | 25 | OpenApi.annotations({ |
| 24 | title: "Executor API", | 26 | title: "Executor API", |
| @@ -8,6 +8,7 @@ import { ExecutionsHandlers } from "./executions"; · 7 unmodified lines | |||
| 8 | import { OAuthHandlers } from "./oauth"; | 8 | import { OAuthHandlers } from "./oauth"; |
| 9 | import { PoliciesHandlers } from "./policies"; | 9 | import { PoliciesHandlers } from "./policies"; |
| 10 | import { ArtifactsHandlers } from "./artifacts"; | 10 | import { ArtifactsHandlers } from "./artifacts"; |
| 11 | import { SkillsHandlers } from "./skills"; | ||
| 11 | 12 | ||
| 12 | export { ToolsHandlers } from "./tools"; | 13 | export { ToolsHandlers } from "./tools"; |
| 13 | export { IntegrationsHandlers } from "./integrations"; | 14 | export { IntegrationsHandlers } from "./integrations"; |
| @@ -17,6 +18,7 @@ export { ExecutionsHandlers } from "./executions"; · 3 unmodified lines | |||
| 17 | export { OAuthHandlers } from "./oauth"; | 18 | export { OAuthHandlers } from "./oauth"; |
| 18 | export { PoliciesHandlers } from "./policies"; | 19 | export { PoliciesHandlers } from "./policies"; |
| 19 | export { ArtifactsHandlers } from "./artifacts"; | 20 | export { ArtifactsHandlers } from "./artifacts"; |
| 21 | export { SkillsHandlers } from "./skills"; | ||
| 20 | 22 | ||
| 21 | export const CoreHandlers = Layer.mergeAll( | 23 | export const CoreHandlers = Layer.mergeAll( |
| 22 | ToolsHandlers, | 24 | ToolsHandlers, |
| @@ -27,4 +29,5 @@ export const CoreHandlers = Layer.mergeAll( · 4 unmodified lines | |||
| 27 | OAuthHandlers, | 29 | OAuthHandlers, |
| 28 | PoliciesHandlers, | 30 | PoliciesHandlers, |
| 29 | ArtifactsHandlers, | 31 | ArtifactsHandlers, |
| 32 | SkillsHandlers, | ||
| 30 | ); | 33 | ); |
| @@ -0,0 +1,60 @@ | |||
| 1 | import { HttpApiBuilder } from "effect/unstable/httpapi"; | ||
| 2 | import { Effect } from "effect"; | ||
| 3 | import type { Skill, SkillSummary } from "@executor-js/sdk"; | ||
| 4 | |||
| 5 | import { ExecutorApi } from "../api"; | ||
| 6 | import { ExecutorService } from "../services"; | ||
| 7 | import { capture } from "@executor-js/api"; | ||
| 8 | |||
| 9 | const summaryToResponse = (skill: SkillSummary) => ({ | ||
| 10 | owner: skill.owner, | ||
| 11 | name: skill.name, | ||
| 12 | description: skill.description, | ||
| 13 | frontmatter: skill.frontmatter, | ||
| 14 | files: skill.files, | ||
| 15 | createdAt: skill.createdAt.getTime(), | ||
| 16 | updatedAt: skill.updatedAt.getTime(), | ||
| 17 | }); | ||
| 18 | |||
| 19 | const skillToResponse = (skill: Skill) => ({ | ||
| 20 | ...summaryToResponse(skill), | ||
| 21 | files: skill.files, | ||
| 22 | }); | ||
| 23 | |||
| 24 | export const SkillsHandlers = HttpApiBuilder.group(ExecutorApi, "skills", (handlers) => | ||
| 25 | handlers | ||
| 26 | .handle("list", () => | ||
| 27 | capture( | ||
| 28 | Effect.gen(function* () { | ||
| 29 | const executor = yield* ExecutorService; | ||
| 30 | const skills = yield* executor.skills.list(); | ||
| 31 | return skills.map(summaryToResponse); | ||
| 32 | }), | ||
| 33 | ), | ||
| 34 | ) | ||
| 35 | .handle("get", ({ params }) => | ||
| 36 | capture( | ||
| 37 | Effect.gen(function* () { | ||
| 38 | const executor = yield* ExecutorService; | ||
| 39 | return skillToResponse(yield* executor.skills.get(params)); | ||
| 40 | }), | ||
| 41 | ), | ||
| 42 | ) | ||
| 43 | .handle("save", ({ payload }) => | ||
| 44 | capture( | ||
| 45 | Effect.gen(function* () { | ||
| 46 | const executor = yield* ExecutorService; | ||
| 47 | return skillToResponse(yield* executor.skills.save(payload)); | ||
| 48 | }), | ||
| 49 | ), | ||
| 50 | ) | ||
| 51 | .handle("remove", ({ params }) => | ||
| 52 | capture( | ||
| 53 | Effect.gen(function* () { | ||
| 54 | const executor = yield* ExecutorService; | ||
| 55 | yield* executor.skills.remove(params); | ||
| 56 | return { removed: true }; | ||
| 57 | }), | ||
| 58 | ), | ||
| 59 | ), | ||
| 60 | ); | ||
| @@ -38,6 +38,7 @@ export { · 37 unmodified lines | |||
| 38 | } from "./oauth-popup"; | 38 | } from "./oauth-popup"; |
| 39 | export { PoliciesApi } from "./policies/api"; | 39 | export { PoliciesApi } from "./policies/api"; |
| 40 | export { ArtifactsApi } from "./artifacts/api"; | 40 | export { ArtifactsApi } from "./artifacts/api"; |
| 41 | export { SkillsApi } from "./skills/api"; | ||
| 41 | export { | 42 | export { |
| 42 | AccountApi, | 43 | AccountApi, |
| 43 | AccountHttpApi, | 44 | AccountHttpApi, |
| @@ -70,6 +70,7 @@ export const makeMcpBuildServer = · 69 unmodified lines | |||
| 70 | createExecutorMcpServer({ | 70 | createExecutorMcpServer({ |
| 71 | engine, | 71 | engine, |
| 72 | artifacts: executor.artifacts, | 72 | artifacts: executor.artifacts, |
| 73 | skills: executor.skills, | ||
| 73 | connections: executor.connections, | 74 | connections: executor.connections, |
| 74 | ...(hostOptions?.loadAppShellHtml | 75 | ...(hostOptions?.loadAppShellHtml |
| 75 | ? { loadAppShellHtml: hostOptions.loadAppShellHtml } | 76 | ? { loadAppShellHtml: hostOptions.loadAppShellHtml } |
| @@ -0,0 +1,88 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // Skills HTTP API — Agent Skills (SKILL.md directories) saved to the workspace. | ||
| 3 | // | ||
| 4 | // A skill is identified by `(owner, name)`: `org` skills are shared with the | ||
| 5 | // whole workspace, `user` skills are personal. The name comes from the SKILL.md | ||
| 6 | // frontmatter, so `save` takes only the owner and the files. Reads return what | ||
| 7 | // the bound owner scope may see, exactly like connections. | ||
| 8 | // --------------------------------------------------------------------------- | ||
| 9 | |||
| 10 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; | ||
| 11 | import { Schema } from "effect"; | ||
| 12 | import { | ||
| 13 | InternalError, | ||
| 14 | InvalidSkillError, | ||
| 15 | OrgWriteDeniedError, | ||
| 16 | Owner, | ||
| 17 | SkillName, | ||
| 18 | SkillNotFoundError, | ||
| 19 | } from "@executor-js/sdk/shared"; | ||
| 20 | |||
| 21 | const SkillParams = { owner: Owner, name: SkillName }; | ||
| 22 | |||
| 23 | const SkillFileEntryResponse = Schema.Struct({ | ||
| 24 | path: Schema.String, | ||
| 25 | size: Schema.Number, | ||
| 26 | digest: Schema.String, | ||
| 27 | }); | ||
| 28 | |||
| 29 | const SkillFileResponse = Schema.Struct({ | ||
| 30 | ...SkillFileEntryResponse.fields, | ||
| 31 | content: Schema.String, | ||
| 32 | }); | ||
| 33 | |||
| 34 | /** What a list returns: the manifest without file contents. */ | ||
| 35 | export const SkillSummaryResponse = Schema.Struct({ | ||
| 36 | owner: Owner, | ||
| 37 | name: SkillName, | ||
| 38 | description: Schema.String, | ||
| 39 | frontmatter: Schema.Record(Schema.String, Schema.Unknown), | ||
| 40 | files: Schema.Array(SkillFileEntryResponse), | ||
| 41 | createdAt: Schema.Number, | ||
| 42 | updatedAt: Schema.Number, | ||
| 43 | }); | ||
| 44 | |||
| 45 | export const SkillResponse = Schema.Struct({ | ||
| 46 | ...SkillSummaryResponse.fields, | ||
| 47 | files: Schema.Array(SkillFileResponse), | ||
| 48 | }); | ||
| 49 | |||
| 50 | const SkillFileInputSchema = Schema.Struct({ | ||
| 51 | path: Schema.String, | ||
| 52 | content: Schema.String, | ||
| 53 | }); | ||
| 54 | |||
| 55 | /** Create or replace: the skill's name is read from `SKILL.md`. */ | ||
| 56 | const SaveSkillPayload = Schema.Struct({ | ||
| 57 | owner: Owner, | ||
| 58 | files: Schema.Array(SkillFileInputSchema), | ||
| 59 | }); | ||
| 60 | |||
| 61 | export const SkillsApi = HttpApiGroup.make("skills") | ||
| 62 | .add( | ||
| 63 | HttpApiEndpoint.get("list", "/skills", { | ||
| 64 | success: Schema.Array(SkillSummaryResponse), | ||
| 65 | error: InternalError, | ||
| 66 | }), | ||
| 67 | ) | ||
| 68 | .add( | ||
| 69 | HttpApiEndpoint.get("get", "/skills/:owner/:name", { | ||
| 70 | params: SkillParams, | ||
| 71 | success: SkillResponse, | ||
| 72 | error: [InternalError, SkillNotFoundError], | ||
| 73 | }), | ||
| 74 | ) | ||
| 75 | .add( | ||
| 76 | HttpApiEndpoint.put("save", "/skills", { | ||
| 77 | payload: SaveSkillPayload, | ||
| 78 | success: SkillResponse, | ||
| 79 | error: [InternalError, InvalidSkillError, OrgWriteDeniedError], | ||
| 80 | }), | ||
| 81 | ) | ||
| 82 | .add( | ||
| 83 | HttpApiEndpoint.delete("remove", "/skills/:owner/:name", { | ||
| 84 | params: SkillParams, | ||
| 85 | success: Schema.Struct({ removed: Schema.Boolean }), | ||
| 86 | error: [InternalError, OrgWriteDeniedError], | ||
| 87 | }), | ||
| 88 | ); | ||
| @@ -104,7 +104,8 @@ · 103 unmodified lines | |||
| 104 | "@standard-schema/spec": "^1.1.0", | 104 | "@standard-schema/spec": "^1.1.0", |
| 105 | "fractional-indexing": "^3.2.0", | 105 | "fractional-indexing": "^3.2.0", |
| 106 | "oauth4webapi": "^3.8.5", | 106 | "oauth4webapi": "^3.8.5", |
| 107 | "tldts": "^7.0.28" | 107 | "tldts": "^7.0.28", |
| 108 | "yaml": "^2.9.0" | ||
| 108 | }, | 109 | }, |
| 109 | "devDependencies": { | 110 | "devDependencies": { |
| 110 | "@effect/atom-react": "catalog:", | 111 | "@effect/atom-react": "catalog:", |
| @@ -416,6 +416,29 @@ export const coreTables = defineTables({ · 415 unmodified lines | |||
| 416 | ["tenant", "owner", "subject", "id"], | 416 | ["tenant", "owner", "subject", "id"], |
| 417 | ), | 417 | ), |
| 418 | 418 | ||
| 419 | // An Agent Skill (https://agentskills.io) saved to the workspace: a SKILL.md | ||
| 420 | // plus any bundled files, served to every connected agent through the MCP | ||
| 421 | // `skills` tool and the MCP Skills Extension. Owner-scoped like a connection: | ||
| 422 | // `org` rows are shared with the whole workspace, `user` rows are personal. | ||
| 423 | skill: ownedExecutorTable( | ||
| 424 | "skill", | ||
| 425 | { | ||
| 426 | // The frontmatter `name`; also the identity within the owner partition. | ||
| 427 | name: keyColumn("name"), | ||
| 428 | // The frontmatter `description`, denormalized so lists never parse files. | ||
| 429 | description: textColumn("description"), | ||
| 430 | // The SKILL.md frontmatter, verbatim, as a JSON object. The MCP Skills | ||
| 431 | // Extension serves it field-for-field, unknown keys included. | ||
| 432 | frontmatter: jsonColumn("frontmatter"), | ||
| 433 | // Every file of the skill, SKILL.md included: | ||
| 434 | // `[{ path, content, size, digest }]`. Text only in v1. | ||
| 435 | files: jsonColumn("files"), | ||
| 436 | created_at: dateColumn("created_at"), | ||
| 437 | updated_at: dateColumn("updated_at"), | ||
| 438 | }, | ||
| 439 | ["tenant", "owner", "subject", "name"], | ||
| 440 | ), | ||
| 441 | |||
| 419 | // Host-owned plugin storage (shared `plugin_storage` table, owner-scoped). | 442 | // Host-owned plugin storage (shared `plugin_storage` table, owner-scoped). |
| 420 | plugin_storage: ownedExecutorTable( | 443 | plugin_storage: ownedExecutorTable( |
| 421 | "plugin_storage", | 444 | "plugin_storage", |
| @@ -492,6 +515,17 @@ export const ARTIFACT_SUMMARY_COLUMNS = [ · 70 unmodified lines | |||
| 492 | ] as const satisfies readonly (keyof ArtifactRow)[]; | 515 | ] as const satisfies readonly (keyof ArtifactRow)[]; |
| 493 | /** The artifact-row projection {@link ARTIFACT_SUMMARY_COLUMNS} selects. */ | 516 | /** The artifact-row projection {@link ARTIFACT_SUMMARY_COLUMNS} selects. */ |
| 494 | export type ArtifactSummaryRow = Pick<ArtifactRow, (typeof ARTIFACT_SUMMARY_COLUMNS)[number]>; | 517 | export type ArtifactSummaryRow = Pick<ArtifactRow, (typeof ARTIFACT_SUMMARY_COLUMNS)[number]>; |
| 518 | export type SkillRow = FumaRow<CoreSchema["skill"]>; | ||
| 519 | /** The skill-row projection lists select: everything but the file contents. */ | ||
| 520 | export const SKILL_SUMMARY_COLUMNS = [ | ||
| 521 | "owner", | ||
| 522 | "name", | ||
| 523 | "description", | ||
| 524 | "frontmatter", | ||
| 525 | "files", | ||
| 526 | "created_at", | ||
| 527 | "updated_at", | ||
| 528 | ] as const satisfies readonly (keyof SkillRow)[]; | ||
| 495 | export type PluginStorageRow = FumaRow<CoreSchema["plugin_storage"]>; | 529 | export type PluginStorageRow = FumaRow<CoreSchema["plugin_storage"]>; |
| 496 | export type BlobRow = FumaRow<CoreSchema["blob"]>; | 530 | export type BlobRow = FumaRow<CoreSchema["blob"]>; |
| 497 | 531 | ||
| @@ -8,6 +8,7 @@ import { · 7 unmodified lines | |||
| 8 | IntegrationSlug, | 8 | IntegrationSlug, |
| 9 | Owner, | 9 | Owner, |
| 10 | ProviderKey, | 10 | ProviderKey, |
| 11 | SkillName, | ||
| 11 | ToolAddress, | 12 | ToolAddress, |
| 12 | } from "./ids"; | 13 | } from "./ids"; |
| 13 | 14 | ||
| @@ -279,6 +280,42 @@ export class ArtifactNotFoundError extends Schema.TaggedErrorClass<ArtifactNotFo · 265 unmodified lines | |||
| 279 | } | 280 | } |
| 280 | } | 281 | } |
| 281 | 282 | ||
| 283 | // --------------------------------------------------------------------------- | ||
| 284 | // Skills | ||
| 285 | // --------------------------------------------------------------------------- | ||
| 286 | |||
| 287 | /** No skill with this owner + name is visible to the bound owner scope. */ | ||
| 288 | export class SkillNotFoundError extends Schema.TaggedErrorClass<SkillNotFoundError>()( | ||
| 289 | "SkillNotFoundError", | ||
| 290 | { owner: Owner, name: SkillName }, | ||
| 291 | { httpApiStatus: 404 }, | ||
| 292 | ) { | ||
| 293 | override get message(): string { | ||
| 294 | return `Skill not found: ${this.owner}/${this.name}`; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | /** The uploaded skill does not conform to the Agent Skills specification (bad | ||
| 299 | * frontmatter, an invalid name, a missing SKILL.md, a file path outside the | ||
| 300 | * skill, or a size over the limit). `reason` is written for the author. */ | ||
| 301 | export class InvalidSkillError | ||
| 302 | extends Schema.TaggedErrorClass<InvalidSkillError>()( | ||
| 303 | "InvalidSkillError", | ||
| 304 | { reason: Schema.String }, | ||
| 305 | { httpApiStatus: 400 }, | ||
| 306 | ) | ||
| 307 | implements UserActionableError | ||
| 308 | { | ||
| 309 | readonly __executorUserActionable = true; | ||
| 310 | readonly code = "invalid_skill"; | ||
| 311 | get userMessage(): string { | ||
| 312 | return this.reason; | ||
| 313 | } | ||
| 314 | override get message(): string { | ||
| 315 | return `Invalid skill: ${this.reason}`; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 282 | // --------------------------------------------------------------------------- | 319 | // --------------------------------------------------------------------------- |
| 283 | // Union — the failure channel of `execute`. | 320 | // Union — the failure channel of `execute`. |
| 284 | // --------------------------------------------------------------------------- | 321 | // --------------------------------------------------------------------------- |
| @@ -300,4 +337,6 @@ export type ExecutorError = · 15 unmodified lines | |||
| 300 | | ExecuteError | 337 | | ExecuteError |
| 301 | | IntegrationNotFoundError | 338 | | IntegrationNotFoundError |
| 302 | | IntegrationRemovalNotAllowedError | 339 | | IntegrationRemovalNotAllowedError |
| 303 | | ArtifactNotFoundError; | 340 | | ArtifactNotFoundError |
| 341 | | SkillNotFoundError | ||
| 342 | | InvalidSkillError; | ||
| @@ -10,6 +10,7 @@ import { · 9 unmodified lines | |||
| 10 | Option, | 10 | Option, |
| 11 | Predicate, | 11 | Predicate, |
| 12 | Ref, | 12 | Ref, |
| 13 | Result, | ||
| 13 | Schema, | 14 | Schema, |
| 14 | Semaphore, | 15 | Semaphore, |
| 15 | } from "effect"; | 16 | } from "effect"; |
| @@ -97,6 +98,17 @@ import { · 81 unmodified lines | |||
| 97 | type SetArtifactPreviewInput, | 98 | type SetArtifactPreviewInput, |
| 98 | } from "./artifact"; | 99 | } from "./artifact"; |
| 99 | import { | 100 | import { |
| 101 | rowToSkill, | ||
| 102 | toSkillSummary, | ||
| 103 | prepareSkillFiles, | ||
| 104 | type SaveSkillInput, | ||
| 105 | type Skill, | ||
| 106 | type SkillRef, | ||
| 107 | type SkillSummary, | ||
| 108 | } from "./skill"; | ||
| 109 | import { | ||
| 110 | SkillNotFoundError, | ||
| 111 | InvalidSkillError, | ||
| 100 | ArtifactNotFoundError, | 112 | ArtifactNotFoundError, |
| 101 | ConnectionAlreadyExistsError, | 113 | ConnectionAlreadyExistsError, |
| 102 | ConnectionNotFoundError, | 114 | ConnectionNotFoundError, |
| @@ -115,6 +127,7 @@ import { · 12 unmodified lines | |||
| 115 | } from "./errors"; | 127 | } from "./errors"; |
| 116 | import { | 128 | import { |
| 117 | ArtifactId, | 129 | ArtifactId, |
| 130 | SkillName, | ||
| 118 | AuthTemplateSlug, | 131 | AuthTemplateSlug, |
| 119 | ConnectionAddress, | 132 | ConnectionAddress, |
| 120 | ConnectionName, | 133 | ConnectionName, |
| @@ -518,6 +531,22 @@ export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = { · 397 unmodified lines | |||
| 518 | ) => Effect.Effect<void, ArtifactNotFoundError | StorageFailure>; | 531 | ) => Effect.Effect<void, ArtifactNotFoundError | StorageFailure>; |
| 519 | }; | 532 | }; |
| 520 | 533 | ||
| 534 | /** | ||
| 535 | * Agent Skills saved to the workspace, visible to the bound owner scope: this | ||
| 536 | * subject's personal skills plus the org's shared ones. | ||
| 537 | */ | ||
| 538 | readonly skills: { | ||
| 539 | /** Newest first, manifests only — file contents stay out of lists. */ | ||
| 540 | readonly list: () => Effect.Effect<readonly SkillSummary[], StorageFailure>; | ||
| 541 | readonly get: (ref: SkillRef) => Effect.Effect<Skill, SkillNotFoundError | StorageFailure>; | ||
| 542 | /** Create, or replace in place the skill with the same `(owner, name)`. | ||
| 543 | * The name is read from the SKILL.md frontmatter. */ | ||
| 544 | readonly save: ( | ||
| 545 | input: SaveSkillInput, | ||
| 546 | ) => Effect.Effect<Skill, InvalidSkillError | OrgWriteDeniedError | StorageFailure>; | ||
| 547 | readonly remove: (ref: SkillRef) => Effect.Effect<void, OrgWriteDeniedError | StorageFailure>; | ||
| 548 | }; | ||
| 549 | |||
| 521 | /** | 550 | /** |
| 522 | * Approvals recorded for artifact-originated calls that paused on a human. | 551 | * Approvals recorded for artifact-originated calls that paused on a human. |
| 523 | * | 552 | * |
| @@ -6118,6 +6147,85 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 5594 unmodified lines | |||
| 6118 | const artifactsRemove = (input: RemoveArtifactInput): Effect.Effect<void, StorageFailure> => | 6147 | const artifactsRemove = (input: RemoveArtifactInput): Effect.Effect<void, StorageFailure> => |
| 6119 | core.deleteMany("artifact", { where: artifactById(input.id) }); | 6148 | core.deleteMany("artifact", { where: artifactById(input.id) }); |
| 6120 | 6149 | ||
| 6150 | // ------------------------------------------------------------------ | ||
| 6151 | // Skills | ||
| 6152 | // ------------------------------------------------------------------ | ||
| 6153 | |||
| 6154 | // Reads take no explicit owner beyond the ref: the owner policy already | ||
| 6155 | // narrows to the rows this binding may see (org rows plus this subject's | ||
| 6156 | // own), so `(owner, name)` is enough to pin one visible row. | ||
| 6157 | const skillByRef = | ||
| 6158 | (ref: SkillRef): CoreWhere => | ||
| 6159 | (b: AnyCb) => | ||
| 6160 | b.and(b("owner", "=", ref.owner), b("name", "=", ref.name)); | ||
| 6161 | |||
| 6162 | const skillsList = (): Effect.Effect<readonly SkillSummary[], StorageFailure> => | ||
| 6163 | core | ||
| 6164 | .findMany("skill", { | ||
| 6165 | orderBy: [ | ||
| 6166 | ["updated_at", "desc"], | ||
| 6167 | ["name", "asc"], | ||
| 6168 | ], | ||
| 6169 | }) | ||
| 6170 | .pipe(Effect.map((rows) => rows.map((row) => toSkillSummary(rowToSkill(row))))); | ||
| 6171 | |||
| 6172 | const skillsGet = (ref: SkillRef): Effect.Effect<Skill, SkillNotFoundError | StorageFailure> => | ||
| 6173 | Effect.gen(function* () { | ||
| 6174 | const row = yield* core.findFirst("skill", { where: skillByRef(ref) }); | ||
| 6175 | if (!row) { | ||
| 6176 | return yield* new SkillNotFoundError({ | ||
| 6177 | owner: ref.owner, | ||
| 6178 | name: SkillName.make(ref.name), | ||
| 6179 | }); | ||
| 6180 | } | ||
| 6181 | return rowToSkill(row); | ||
| 6182 | }); | ||
| 6183 | |||
| 6184 | const skillsSave = ( | ||
| 6185 | input: SaveSkillInput, | ||
| 6186 | ): Effect.Effect<Skill, InvalidSkillError | OrgWriteDeniedError | StorageFailure> => | ||
| 6187 | Effect.gen(function* () { | ||
| 6188 | yield* guardOrgWrite(input.owner); | ||
| 6189 | yield* requireUserSubject(input.owner); | ||
| 6190 | const prepared = yield* Effect.promise(() => prepareSkillFiles(input.files)); | ||
| 6191 | if (Result.isFailure(prepared)) return yield* prepared.failure; | ||
| 6192 | const { parsed, files } = prepared.success; | ||
| 6193 | const now = new Date(); | ||
| 6194 | const ref: SkillRef = { owner: input.owner, name: parsed.name }; | ||
| 6195 | const set = { | ||
| 6196 | description: parsed.description, | ||
| 6197 | frontmatter: parsed.frontmatter, | ||
| 6198 | files, | ||
| 6199 | updated_at: now, | ||
| 6200 | }; | ||
| 6201 | const existing = yield* core.findFirst("skill", { where: skillByRef(ref) }); | ||
| 6202 | if (existing) { | ||
| 6203 | yield* core.updateMany("skill", { where: skillByRef(ref), set }); | ||
| 6204 | return rowToSkill({ ...existing, ...set }); | ||
| 6205 | } | ||
| 6206 | const keys = yield* Effect.try({ | ||
| 6207 | try: () => ownedKeys(input.owner), | ||
| 6208 | catch: (cause) => storageFailureFromUnknown("invalid owner", cause), | ||
| 6209 | }); | ||
| 6210 | const created = yield* core.create("skill", { | ||
| 6211 | tenant: keys.tenant, | ||
| 6212 | owner: keys.owner, | ||
| 6213 | subject: keys.subject, | ||
| 6214 | name: parsed.name, | ||
| 6215 | ...set, | ||
| 6216 | created_at: now, | ||
| 6217 | }); | ||
| 6218 | return rowToSkill(created); | ||
| 6219 | }); | ||
| 6220 | |||
| 6221 | const skillsRemove = ( | ||
| 6222 | ref: SkillRef, | ||
| 6223 | ): Effect.Effect<void, OrgWriteDeniedError | StorageFailure> => | ||
| 6224 | Effect.gen(function* () { | ||
| 6225 | yield* guardOrgWrite(ref.owner); | ||
| 6226 | yield* core.deleteMany("skill", { where: skillByRef(ref) }); | ||
| 6227 | }); | ||
| 6228 | |||
| 6121 | // ------------------------------------------------------------------ | 6229 | // ------------------------------------------------------------------ |
| 6122 | // Elicitation | 6230 | // Elicitation |
| 6123 | // ------------------------------------------------------------------ | 6231 | // ------------------------------------------------------------------ |
| @@ -7082,6 +7190,12 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 958 unmodified lines | |||
| 7082 | remove: artifactsRemove, | 7190 | remove: artifactsRemove, |
| 7083 | setPreview: artifactsSetPreview, | 7191 | setPreview: artifactsSetPreview, |
| 7084 | }, | 7192 | }, |
| 7193 | skills: { | ||
| 7194 | list: skillsList, | ||
| 7195 | get: skillsGet, | ||
| 7196 | save: skillsSave, | ||
| 7197 | remove: skillsRemove, | ||
| 7198 | }, | ||
| 7085 | pendingApprovals, | 7199 | pendingApprovals, |
| 7086 | execute, | 7200 | execute, |
| 7087 | close, | 7201 | close, |
| @@ -63,6 +63,11 @@ export type PolicyId = typeof PolicyId.Type; · 62 unmodified lines | |||
| 63 | export const ArtifactId = Schema.String.pipe(Schema.brand("ArtifactId")); | 63 | export const ArtifactId = Schema.String.pipe(Schema.brand("ArtifactId")); |
| 64 | export type ArtifactId = typeof ArtifactId.Type; | 64 | export type ArtifactId = typeof ArtifactId.Type; |
| 65 | 65 | ||
| 66 | /** An Agent Skill's name — the `name` in its SKILL.md frontmatter, unique within | ||
| 67 | * its owner partition. Lowercase letters, digits, and single hyphens. */ | ||
| 68 | export const SkillName = Schema.String.pipe(Schema.brand("SkillName")); | ||
| 69 | export type SkillName = typeof SkillName.Type; | ||
| 70 | |||
| 66 | /** | 71 | /** |
| 67 | * The isolation partition (the org/workspace). Owns the catalog and namespaces | 72 | * The isolation partition (the org/workspace). Owns the catalog and namespaces |
| 68 | * every connection. The executor is bound to one; `owner: "org"` files at this | 73 | * every connection. The executor is bound to one; `owner: "org"` files at this |
| @@ -59,6 +59,7 @@ export { · 58 unmodified lines | |||
| 59 | ElicitationId, | 59 | ElicitationId, |
| 60 | PolicyId, | 60 | PolicyId, |
| 61 | ArtifactId, | 61 | ArtifactId, |
| 62 | SkillName, | ||
| 62 | Tenant, | 63 | Tenant, |
| 63 | Subject, | 64 | Subject, |
| 64 | Owner, | 65 | Owner, |
| @@ -81,6 +82,8 @@ export { · 16 unmodified lines | |||
| 81 | CredentialProviderNotRegisteredError, | 82 | CredentialProviderNotRegisteredError, |
| 82 | CredentialResolutionError, | 83 | CredentialResolutionError, |
| 83 | ArtifactNotFoundError, | 84 | ArtifactNotFoundError, |
| 85 | SkillNotFoundError, | ||
| 86 | InvalidSkillError, | ||
| 84 | isUserActionableError, | 87 | isUserActionableError, |
| 85 | type ExecuteError, | 88 | type ExecuteError, |
| 86 | type ExecutorError, | 89 | type ExecutorError, |
| @@ -220,6 +223,35 @@ export { · 133 unmodified lines | |||
| 220 | } from "./artifact"; | 223 | } from "./artifact"; |
| 221 | export { sanitizeArtifactPreviewMarkup, ARTIFACT_PREVIEW_MARKUP_LIMIT } from "./artifact-preview"; | 224 | export { sanitizeArtifactPreviewMarkup, ARTIFACT_PREVIEW_MARKUP_LIMIT } from "./artifact-preview"; |
| 222 | 225 | ||
| 226 | // Agent Skills — SKILL.md directories saved to the workspace. | ||
| 227 | export { | ||
| 228 | rowToSkill, | ||
| 229 | toSkillSummary, | ||
| 230 | parseSkillMarkdown, | ||
| 231 | prepareSkillFiles, | ||
| 232 | digestSkillContent, | ||
| 233 | skillBody, | ||
| 234 | skillRootUri, | ||
| 235 | skillFileUri, | ||
| 236 | parseSkillUri, | ||
| 237 | isValidSkillName, | ||
| 238 | isValidSkillFilePath, | ||
| 239 | SKILL_MD_PATH, | ||
| 240 | SKILL_MAX_FILES, | ||
| 241 | SKILL_MAX_TOTAL_BYTES, | ||
| 242 | SKILL_RESERVED_NAMES, | ||
| 243 | SKILL_URI_SCHEME, | ||
| 244 | type Skill, | ||
| 245 | type SkillSummary, | ||
| 246 | type SkillFile, | ||
| 247 | type SkillFileEntry, | ||
| 248 | type SkillFileInput, | ||
| 249 | type SkillRef, | ||
| 250 | type SaveSkillInput, | ||
| 251 | type ParsedSkillMarkdown, | ||
| 252 | type ParsedSkillUri, | ||
| 253 | } from "./skill"; | ||
| 254 | |||
| 223 | // Elicitation. | 255 | // Elicitation. |
| 224 | export { | 256 | export { |
| 225 | ElicitationMeta, | 257 | ElicitationMeta, |
| @@ -11,6 +11,7 @@ · 10 unmodified lines | |||
| 11 | // Branded ids + the owner literal. | 11 | // Branded ids + the owner literal. |
| 12 | export { | 12 | export { |
| 13 | ArtifactId, | 13 | ArtifactId, |
| 14 | SkillName, | ||
| 14 | AuthTemplateSlug, | 15 | AuthTemplateSlug, |
| 15 | ConnectionAddress, | 16 | ConnectionAddress, |
| 16 | ConnectionName, | 17 | ConnectionName, |
| @@ -66,6 +67,8 @@ export { · 49 unmodified lines | |||
| 66 | CredentialProviderNotRegisteredError, | 67 | CredentialProviderNotRegisteredError, |
| 67 | CredentialResolutionError, | 68 | CredentialResolutionError, |
| 68 | ArtifactNotFoundError, | 69 | ArtifactNotFoundError, |
| 70 | SkillNotFoundError, | ||
| 71 | InvalidSkillError, | ||
| 69 | isUserActionableError, | 72 | isUserActionableError, |
| 70 | type ExecuteError, | 73 | type ExecuteError, |
| 71 | type ExecutorError, | 74 | type ExecutorError, |
| @@ -120,6 +123,32 @@ export type { · 48 unmodified lines | |||
| 120 | } from "./artifact"; | 123 | } from "./artifact"; |
| 121 | export { ARTIFACT_PREVIEW_MARKUP_LIMIT } from "./artifact-preview"; | 124 | export { ARTIFACT_PREVIEW_MARKUP_LIMIT } from "./artifact-preview"; |
| 122 | 125 | ||
| 126 | // Agent Skills — the pure parts the console and CLI share with the server. | ||
| 127 | export { | ||
| 128 | parseSkillMarkdown, | ||
| 129 | isValidSkillName, | ||
| 130 | isValidSkillFilePath, | ||
| 131 | skillRootUri, | ||
| 132 | skillFileUri, | ||
| 133 | parseSkillUri, | ||
| 134 | SKILL_MD_PATH, | ||
| 135 | SKILL_MAX_FILES, | ||
| 136 | SKILL_MAX_TOTAL_BYTES, | ||
| 137 | SKILL_RESERVED_NAMES, | ||
| 138 | SKILL_URI_SCHEME, | ||
| 139 | } from "./skill"; | ||
| 140 | export type { | ||
| 141 | Skill, | ||
| 142 | SkillSummary, | ||
| 143 | SkillFile, | ||
| 144 | SkillFileEntry, | ||
| 145 | SkillFileInput, | ||
| 146 | SkillRef, | ||
| 147 | SaveSkillInput, | ||
| 148 | ParsedSkillMarkdown, | ||
| 149 | ParsedSkillUri, | ||
| 150 | } from "./skill"; | ||
| 151 | |||
| 123 | // Schema-side views + onboarding autodetect. | 152 | // Schema-side views + onboarding autodetect. |
| 124 | export { ToolSchemaView, IntegrationDetectionResult } from "./types"; | 153 | export { ToolSchemaView, IntegrationDetectionResult } from "./types"; |
| 125 | 154 | ||
| @@ -0,0 +1,384 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // Agent Skills — the SKILL.md standard (https://agentskills.io/specification) | ||
| 3 | // saved into the workspace. Row → public projection, the operation inputs, and | ||
| 4 | // the pure validation that turns a set of uploaded files into a skill. | ||
| 5 | // | ||
| 6 | // A skill IS a directory: a SKILL.md with YAML frontmatter (`name`, | ||
| 7 | // `description`, a few optional fields) followed by markdown instructions, plus | ||
| 8 | // any bundled files (`references/`, `scripts/`, `assets/`). We store the whole | ||
| 9 | // directory as one row so the MCP host can serve it back file-by-file with | ||
| 10 | // digests, which is what the MCP Skills Extension (SEP-2640) requires. | ||
| 11 | // | ||
| 12 | // Owner-scoped like a connection: an `org` skill is shared with everyone in the | ||
| 13 | // workspace, a `user` skill is personal. Identity is `(owner, name)`. | ||
| 14 | // --------------------------------------------------------------------------- | ||
| 15 | |||
| 16 | import { Option, Result, Schema } from "effect"; | ||
| 17 | import { parse as parseYaml } from "yaml"; | ||
| 18 | |||
| 19 | import type { SkillRow } from "./core-schema"; | ||
| 20 | import { InvalidSkillError } from "./errors"; | ||
| 21 | import { SkillName, type Owner } from "./ids"; | ||
| 22 | |||
| 23 | /** One file as uploaded: a relative POSIX path and its UTF-8 text. */ | ||
| 24 | export interface SkillFileInput { | ||
| 25 | readonly path: string; | ||
| 26 | readonly content: string; | ||
| 27 | } | ||
| 28 | |||
| 29 | /** One file in a skill's manifest: what the MCP Skills Extension lists. */ | ||
| 30 | export interface SkillFileEntry { | ||
| 31 | /** Relative to the skill root, `/`-separated. `SKILL.md` for the root file. */ | ||
| 32 | readonly path: string; | ||
| 33 | /** Byte length of the UTF-8 content. */ | ||
| 34 | readonly size: number; | ||
| 35 | /** `sha256:<64 lowercase hex>` over the same bytes. */ | ||
| 36 | readonly digest: string; | ||
| 37 | } | ||
| 38 | |||
| 39 | export type SkillFile = SkillFileEntry & { readonly content: string }; | ||
| 40 | |||
| 41 | export interface SkillSummary { | ||
| 42 | readonly owner: Owner; | ||
| 43 | readonly name: SkillName; | ||
| 44 | readonly description: string; | ||
| 45 | /** The SKILL.md frontmatter, verbatim, every field the author wrote. */ | ||
| 46 | readonly frontmatter: Readonly<Record<string, unknown>>; | ||
| 47 | /** The manifest — paths, sizes, digests — without content. */ | ||
| 48 | readonly files: readonly SkillFileEntry[]; | ||
| 49 | readonly createdAt: Date; | ||
| 50 | readonly updatedAt: Date; | ||
| 51 | } | ||
| 52 | |||
| 53 | export interface Skill extends Omit<SkillSummary, "files"> { | ||
| 54 | readonly files: readonly SkillFile[]; | ||
| 55 | } | ||
| 56 | |||
| 57 | /** Save (create or replace in place) the skill the files describe. The name | ||
| 58 | * comes from the SKILL.md frontmatter, never from the caller. */ | ||
| 59 | export interface SaveSkillInput { | ||
| 60 | readonly owner: Owner; | ||
| 61 | readonly files: readonly SkillFileInput[]; | ||
| 62 | } | ||
| 63 | |||
| 64 | export interface SkillRef { | ||
| 65 | readonly owner: Owner; | ||
| 66 | readonly name: string; | ||
| 67 | } | ||
| 68 | |||
| 69 | // --------------------------------------------------------------------------- | ||
| 70 | // Limits and reserved names | ||
| 71 | // --------------------------------------------------------------------------- | ||
| 72 | |||
| 73 | export const SKILL_MD_PATH = "SKILL.md"; | ||
| 74 | /** Files per skill, SKILL.md included. */ | ||
| 75 | export const SKILL_MAX_FILES = 64; | ||
| 76 | /** Total UTF-8 bytes across every file of one skill. */ | ||
| 77 | export const SKILL_MAX_TOTAL_BYTES = 1024 * 1024; | ||
| 78 | export const SKILL_NAME_MAX_LENGTH = 64; | ||
| 79 | export const SKILL_DESCRIPTION_MAX_LENGTH = 1024; | ||
| 80 | export const SKILL_COMPATIBILITY_MAX_LENGTH = 500; | ||
| 81 | |||
| 82 | /** Names the MCP `skills` tool already answers with Executor's own docs; a | ||
| 83 | * workspace skill under one of them would be unreachable by name. */ | ||
| 84 | export const SKILL_RESERVED_NAMES: ReadonlySet<string> = new Set([ | ||
| 85 | "execute", | ||
| 86 | "create-artifact", | ||
| 87 | "artifact-style", | ||
| 88 | ]); | ||
| 89 | |||
| 90 | const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; | ||
| 91 | |||
| 92 | /** Whether `name` satisfies the specification's `name` rules. */ | ||
| 93 | export const isValidSkillName = (name: string): boolean => | ||
| 94 | name.length >= 1 && name.length <= SKILL_NAME_MAX_LENGTH && SKILL_NAME_PATTERN.test(name); | ||
| 95 | |||
| 96 | // --------------------------------------------------------------------------- | ||
| 97 | // Frontmatter | ||
| 98 | // --------------------------------------------------------------------------- | ||
| 99 | |||
| 100 | export interface ParsedSkillMarkdown { | ||
| 101 | readonly name: SkillName; | ||
| 102 | readonly description: string; | ||
| 103 | readonly frontmatter: Readonly<Record<string, unknown>>; | ||
| 104 | /** The markdown after the closing `---`, trimmed. */ | ||
| 105 | readonly body: string; | ||
| 106 | } | ||
| 107 | |||
| 108 | const invalid = (reason: string) => Result.fail(new InvalidSkillError({ reason })); | ||
| 109 | |||
| 110 | const isRecord = (value: unknown): value is Record<string, unknown> => | ||
| 111 | typeof value === "object" && value !== null && !Array.isArray(value); | ||
| 112 | |||
| 113 | /** Locate the frontmatter block: `---` on the first line, `---` alone on a | ||
| 114 | * later line. Returns the YAML between them and the body after. */ | ||
| 115 | const splitFrontmatter = ( | ||
| 116 | markdown: string, | ||
| 117 | ): Option.Option<{ readonly yaml: string; readonly body: string }> => { | ||
| 118 | const text = markdown.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n"); | ||
| 119 | const lines = text.split("\n"); | ||
| 120 | if (lines[0]?.trim() !== "---") return Option.none(); | ||
| 121 | const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---"); | ||
| 122 | if (closing === -1) return Option.none(); | ||
| 123 | return Option.some({ | ||
| 124 | yaml: lines.slice(1, closing).join("\n"), | ||
| 125 | body: lines | ||
| 126 | .slice(closing + 1) | ||
| 127 | .join("\n") | ||
| 128 | .trim(), | ||
| 129 | }); | ||
| 130 | }; | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Split a SKILL.md into frontmatter and body and validate the required fields. | ||
| 134 | * | ||
| 135 | * Strict on what the specification makes strict (`name` shape, `description` | ||
| 136 | * presence and length, `compatibility` length, `metadata` shape) and open on | ||
| 137 | * everything else: unknown keys are kept verbatim, because the MCP Skills | ||
| 138 | * Extension promises hosts the frontmatter exactly as the author wrote it. | ||
| 139 | */ | ||
| 140 | export const parseSkillMarkdown = ( | ||
| 141 | markdown: string, | ||
| 142 | ): Result.Result<ParsedSkillMarkdown, InvalidSkillError> => { | ||
| 143 | const split = splitFrontmatter(markdown); | ||
| 144 | if (Option.isNone(split)) { | ||
| 145 | return invalid( | ||
| 146 | "SKILL.md must begin with a `---` line, followed by YAML frontmatter and a closing `---` line.", | ||
| 147 | ); | ||
| 148 | } | ||
| 149 | const { yaml, body } = split.value; | ||
| 150 | |||
| 151 | const parsed = Result.try({ | ||
| 152 | try: () => parseYaml(yaml) as unknown, | ||
| 153 | catch: () => new InvalidSkillError({ reason: "SKILL.md frontmatter is not valid YAML." }), | ||
| 154 | }); | ||
| 155 | if (Result.isFailure(parsed)) return Result.fail(parsed.failure); | ||
| 156 | const frontmatter = parsed.success; | ||
| 157 | if (!isRecord(frontmatter)) { | ||
| 158 | return invalid("SKILL.md frontmatter must be a YAML mapping of fields."); | ||
| 159 | } | ||
| 160 | |||
| 161 | const name = frontmatter.name; | ||
| 162 | if (typeof name !== "string" || !isValidSkillName(name)) { | ||
| 163 | return invalid( | ||
| 164 | "Frontmatter `name` must be 1–64 characters of lowercase letters, digits, and single hyphens, and cannot start or end with a hyphen.", | ||
| 165 | ); | ||
| 166 | } | ||
| 167 | if (SKILL_RESERVED_NAMES.has(name)) { | ||
| 168 | return invalid(`\`${name}\` is reserved for Executor's built-in docs; choose another name.`); | ||
| 169 | } | ||
| 170 | const description = frontmatter.description; | ||
| 171 | if (typeof description !== "string" || description.trim().length === 0) { | ||
| 172 | return invalid("Frontmatter `description` is required and must be a non-empty string."); | ||
| 173 | } | ||
| 174 | if (description.length > SKILL_DESCRIPTION_MAX_LENGTH) { | ||
| 175 | return invalid( | ||
| 176 | `Frontmatter \`description\` must be at most ${SKILL_DESCRIPTION_MAX_LENGTH} characters.`, | ||
| 177 | ); | ||
| 178 | } | ||
| 179 | if ("compatibility" in frontmatter) { | ||
| 180 | const compatibility = frontmatter.compatibility; | ||
| 181 | if ( | ||
| 182 | typeof compatibility !== "string" || | ||
| 183 | compatibility.length === 0 || | ||
| 184 | compatibility.length > SKILL_COMPATIBILITY_MAX_LENGTH | ||
| 185 | ) { | ||
| 186 | return invalid( | ||
| 187 | `Frontmatter \`compatibility\` must be a string of 1–${SKILL_COMPATIBILITY_MAX_LENGTH} characters.`, | ||
| 188 | ); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | if ("license" in frontmatter && typeof frontmatter.license !== "string") { | ||
| 192 | return invalid("Frontmatter `license` must be a string."); | ||
| 193 | } | ||
| 194 | if ("allowed-tools" in frontmatter && typeof frontmatter["allowed-tools"] !== "string") { | ||
| 195 | return invalid("Frontmatter `allowed-tools` must be a space-separated string."); | ||
| 196 | } | ||
| 197 | if ("metadata" in frontmatter) { | ||
| 198 | const metadata = frontmatter.metadata; | ||
| 199 | if (!isRecord(metadata) || Object.values(metadata).some((v) => typeof v !== "string")) { | ||
| 200 | return invalid("Frontmatter `metadata` must be a mapping of string keys to string values."); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | return Result.succeed({ | ||
| 205 | name: SkillName.make(name), | ||
| 206 | description: description.trim(), | ||
| 207 | frontmatter, | ||
| 208 | body, | ||
| 209 | }); | ||
| 210 | }; | ||
| 211 | |||
| 212 | // --------------------------------------------------------------------------- | ||
| 213 | // Files | ||
| 214 | // --------------------------------------------------------------------------- | ||
| 215 | |||
| 216 | const encoder = new TextEncoder(); | ||
| 217 | |||
| 218 | const toHex = (bytes: ArrayBuffer): string => | ||
| 219 | Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, "0")).join(""); | ||
| 220 | |||
| 221 | /** `sha256:<hex>` of the UTF-8 encoding of `content` — the digest shape the | ||
| 222 | * MCP Skills Extension specifies. Web Crypto, so it runs on Node, Bun, and | ||
| 223 | * workerd alike. */ | ||
| 224 | export const digestSkillContent = async (content: string): Promise<string> => | ||
| 225 | `sha256:${toHex(await crypto.subtle.digest("SHA-256", encoder.encode(content)))}`; | ||
| 226 | |||
| 227 | export const skillContentByteLength = (content: string): number => | ||
| 228 | encoder.encode(content).byteLength; | ||
| 229 | |||
| 230 | // eslint-disable-next-line no-control-regex -- the point is to refuse control characters in paths | ||
| 231 | const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/; | ||
| 232 | |||
| 233 | /** A relative POSIX path inside a skill: no leading `/`, no `.`/`..` segments, | ||
| 234 | * no empty segments, no backslashes, no control characters. */ | ||
| 235 | export const isValidSkillFilePath = (path: string): boolean => { | ||
| 236 | if (path.length === 0 || path.length > 512) return false; | ||
| 237 | if (path.includes("\\") || CONTROL_CHARACTERS.test(path)) return false; | ||
| 238 | const segments = path.split("/"); | ||
| 239 | return segments.every((segment) => segment !== "" && segment !== "." && segment !== ".."); | ||
| 240 | }; | ||
| 241 | |||
| 242 | const compareSkillPaths = (a: SkillFileInput, b: SkillFileInput): number => | ||
| 243 | a.path === SKILL_MD_PATH | ||
| 244 | ? -1 | ||
| 245 | : b.path === SKILL_MD_PATH | ||
| 246 | ? 1 | ||
| 247 | : a.path < b.path | ||
| 248 | ? -1 | ||
| 249 | : a.path > b.path | ||
| 250 | ? 1 | ||
| 251 | : 0; | ||
| 252 | |||
| 253 | export interface PreparedSkill { | ||
| 254 | readonly parsed: ParsedSkillMarkdown; | ||
| 255 | /** SKILL.md first, then the rest in path order. */ | ||
| 256 | readonly files: readonly SkillFile[]; | ||
| 257 | } | ||
| 258 | |||
| 259 | /** | ||
| 260 | * Validate an uploaded file set and produce the parsed SKILL.md plus the | ||
| 261 | * normalized, digested file list — exactly what gets stored. | ||
| 262 | * | ||
| 263 | * Async only for the digests; everything else is pure. | ||
| 264 | */ | ||
| 265 | export const prepareSkillFiles = async ( | ||
| 266 | inputs: readonly SkillFileInput[], | ||
| 267 | ): Promise<Result.Result<PreparedSkill, InvalidSkillError>> => { | ||
| 268 | if (inputs.length === 0) return invalid("A skill needs at least a SKILL.md file."); | ||
| 269 | if (inputs.length > SKILL_MAX_FILES) { | ||
| 270 | return invalid(`A skill can have at most ${SKILL_MAX_FILES} files.`); | ||
| 271 | } | ||
| 272 | const seen = new Set<string>(); | ||
| 273 | for (const file of inputs) { | ||
| 274 | if (!isValidSkillFilePath(file.path)) { | ||
| 275 | return invalid(`File path "${file.path}" is not a relative path inside the skill.`); | ||
| 276 | } | ||
| 277 | if (seen.has(file.path)) return invalid(`File path "${file.path}" appears more than once.`); | ||
| 278 | seen.add(file.path); | ||
| 279 | } | ||
| 280 | const skillMd = inputs.find((file) => file.path === SKILL_MD_PATH); | ||
| 281 | if (!skillMd) return invalid("A skill must contain a SKILL.md file at its root."); | ||
| 282 | const parsed = parseSkillMarkdown(skillMd.content); | ||
| 283 | if (Result.isFailure(parsed)) return Result.fail(parsed.failure); | ||
| 284 | |||
| 285 | let total = 0; | ||
| 286 | const files: SkillFile[] = []; | ||
| 287 | for (const file of [...inputs].sort(compareSkillPaths)) { | ||
| 288 | const size = skillContentByteLength(file.content); | ||
| 289 | total += size; | ||
| 290 | if (total > SKILL_MAX_TOTAL_BYTES) { | ||
| 291 | return invalid(`A skill's files can total at most ${SKILL_MAX_TOTAL_BYTES} bytes.`); | ||
| 292 | } | ||
| 293 | files.push({ | ||
| 294 | path: file.path, | ||
| 295 | content: file.content, | ||
| 296 | size, | ||
| 297 | digest: await digestSkillContent(file.content), | ||
| 298 | }); | ||
| 299 | } | ||
| 300 | return Result.succeed({ parsed: parsed.success, files }); | ||
| 301 | }; | ||
| 302 | |||
| 303 | // --------------------------------------------------------------------------- | ||
| 304 | // URIs — the MCP Skills Extension form. `skill://<owner>/<name>/<path>`; the | ||
| 305 | // final skill-path segment is the name, as the extension requires, and the | ||
| 306 | // owner is the server-chosen prefix that keeps a personal and a workspace skill | ||
| 307 | // of the same name distinct. | ||
| 308 | // --------------------------------------------------------------------------- | ||
| 309 | |||
| 310 | export const SKILL_URI_SCHEME = "skill://"; | ||
| 311 | |||
| 312 | export const skillRootUri = (ref: SkillRef): string => | ||
| 313 | `${SKILL_URI_SCHEME}${ref.owner}/${ref.name}`; | ||
| 314 | |||
| 315 | export const skillFileUri = (ref: SkillRef, path: string): string => `${skillRootUri(ref)}/${path}`; | ||
| 316 | |||
| 317 | export interface ParsedSkillUri extends SkillRef { | ||
| 318 | /** Empty for the skill root directory. */ | ||
| 319 | readonly path: string; | ||
| 320 | } | ||
| 321 | |||
| 322 | /** Parse `skill://<owner>/<name>[/<path>]` back into its parts, or `None`. */ | ||
| 323 | export const parseSkillUri = (uri: string): Option.Option<ParsedSkillUri> => { | ||
| 324 | if (!uri.startsWith(SKILL_URI_SCHEME)) return Option.none(); | ||
| 325 | const [owner, name, ...pathSegments] = uri.slice(SKILL_URI_SCHEME.length).split("/"); | ||
| 326 | if ((owner !== "org" && owner !== "user") || name === undefined || !isValidSkillName(name)) { | ||
| 327 | return Option.none(); | ||
| 328 | } | ||
| 329 | const path = pathSegments.join("/"); | ||
| 330 | if (path.length > 0 && !isValidSkillFilePath(path)) return Option.none(); | ||
| 331 | return Option.some({ owner, name, path }); | ||
| 332 | }; | ||
| 333 | |||
| 334 | // --------------------------------------------------------------------------- | ||
| 335 | // Row projections | ||
| 336 | // --------------------------------------------------------------------------- | ||
| 337 | |||
| 338 | // The stored `files` column is decoded through this; it is the same shape as | ||
| 339 | // `SkillFile`, kept as a schema here because the row is the only place bytes | ||
| 340 | // arrive untyped. | ||
| 341 | const StoredSkillFile = Schema.Struct({ | ||
| 342 | path: Schema.String, | ||
| 343 | content: Schema.String, | ||
| 344 | size: Schema.Number, | ||
| 345 | digest: Schema.String, | ||
| 346 | }); | ||
| 347 | const decodeFiles = Schema.decodeUnknownOption(Schema.Array(StoredSkillFile)); | ||
| 348 | const decodeJsonString = Schema.decodeUnknownOption(Schema.UnknownFromJsonString); | ||
| 349 | |||
| 350 | /** JSON arrives as an object on Postgres and as a string on SQLite. */ | ||
| 351 | const jsonFromColumn = (value: unknown): Option.Option<unknown> => | ||
| 352 | typeof value === "string" ? decodeJsonString(value) : Option.some(value); | ||
| 353 | |||
| 354 | const filesFromColumn = (value: unknown): readonly SkillFile[] => | ||
| 355 | Option.flatMap(jsonFromColumn(value), decodeFiles).pipe(Option.getOrElse(() => [])); | ||
| 356 | |||
| 357 | const frontmatterFromColumn = (value: unknown): Readonly<Record<string, unknown>> => | ||
| 358 | Option.filter(jsonFromColumn(value), isRecord).pipe(Option.getOrElse(() => ({}))); | ||
| 359 | |||
| 360 | const asDate = (value: Date | number | string): Date => | ||
| 361 | value instanceof Date ? value : new Date(value); | ||
| 362 | |||
| 363 | export const rowToSkill = (row: SkillRow): Skill => ({ | ||
| 364 | owner: row.owner as Owner, | ||
| 365 | name: SkillName.make(row.name), | ||
| 366 | description: row.description, | ||
| 367 | frontmatter: frontmatterFromColumn(row.frontmatter), | ||
| 368 | files: filesFromColumn(row.files), | ||
| 369 | createdAt: asDate(row.created_at), | ||
| 370 | updatedAt: asDate(row.updated_at), | ||
| 371 | }); | ||
| 372 | |||
| 373 | export const toSkillSummary = (skill: Skill): SkillSummary => ({ | ||
| 374 | ...skill, | ||
| 375 | files: skill.files.map(({ path, size, digest }) => ({ path, size, digest })), | ||
| 376 | }); | ||
| 377 | |||
| 378 | /** The markdown body of a stored skill's SKILL.md (frontmatter stripped). */ | ||
| 379 | export const skillBody = (skill: Skill): string => { | ||
| 380 | const skillMd = skill.files.find((file) => file.path === SKILL_MD_PATH); | ||
| 381 | if (!skillMd) return ""; | ||
| 382 | const parsed = parseSkillMarkdown(skillMd.content); | ||
| 383 | return Result.isSuccess(parsed) ? parsed.success.body : skillMd.content; | ||
| 384 | }; | ||
| @@ -0,0 +1,199 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import { Effect, Option, Predicate, Result } from "effect"; | ||
| 3 | |||
| 4 | import { makeTestExecutor } from "./testing"; | ||
| 5 | import { | ||
| 6 | parseSkillMarkdown, | ||
| 7 | parseSkillUri, | ||
| 8 | prepareSkillFiles, | ||
| 9 | skillBody, | ||
| 10 | skillFileUri, | ||
| 11 | } from "./skill"; | ||
| 12 | |||
| 13 | // The `executor.skills` surface against the real SQLite test db, plus the pure | ||
| 14 | // SKILL.md validation it is built on. Skills are owner-scoped rows with no | ||
| 15 | // plugin involvement, so the default test executor (one tenant + one bound | ||
| 16 | // subject) is the whole fixture. | ||
| 17 | |||
| 18 | const SKILL_MD = [ | ||
| 19 | "---", | ||
| 20 | "name: pdf-processing", | ||
| 21 | "description: Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 22 | "license: Apache-2.0", | ||
| 23 | "metadata:", | ||
| 24 | " author: example-org", | ||
| 25 | ' version: "1.0"', | ||
| 26 | "unknown-field: kept verbatim", | ||
| 27 | "---", | ||
| 28 | "", | ||
| 29 | "# PDF processing", | ||
| 30 | "", | ||
| 31 | "Run `scripts/extract.py`.", | ||
| 32 | ].join("\n"); | ||
| 33 | |||
| 34 | const files = (skillMd = SKILL_MD) => [ | ||
| 35 | { path: "SKILL.md", content: skillMd }, | ||
| 36 | { path: "scripts/extract.py", content: "print('hi')\n" }, | ||
| 37 | ]; | ||
| 38 | |||
| 39 | describe("parseSkillMarkdown", () => { | ||
| 40 | it("splits frontmatter from body and keeps unknown fields", () => { | ||
| 41 | const parsed = parseSkillMarkdown(SKILL_MD); | ||
| 42 | expect(Result.isSuccess(parsed)).toBe(true); | ||
| 43 | if (Result.isFailure(parsed)) return; | ||
| 44 | expect(parsed.success.name).toBe("pdf-processing"); | ||
| 45 | expect(parsed.success.description).toBe( | ||
| 46 | "Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 47 | ); | ||
| 48 | expect(parsed.success.frontmatter["unknown-field"]).toBe("kept verbatim"); | ||
| 49 | expect(parsed.success.frontmatter.metadata).toEqual({ author: "example-org", version: "1.0" }); | ||
| 50 | expect(parsed.success.body.startsWith("# PDF processing")).toBe(true); | ||
| 51 | }); | ||
| 52 | |||
| 53 | it.each([ | ||
| 54 | ["no frontmatter", "# Just markdown"], | ||
| 55 | ["unclosed frontmatter", "---\nname: x\ndescription: y\n"], | ||
| 56 | ["uppercase name", "---\nname: PDF\ndescription: y\n---\n"], | ||
| 57 | ["leading hyphen", "---\nname: -pdf\ndescription: y\n---\n"], | ||
| 58 | ["double hyphen", "---\nname: pdf--x\ndescription: y\n---\n"], | ||
| 59 | ["missing description", "---\nname: pdf\n---\n"], | ||
| 60 | ["reserved name", "---\nname: execute\ndescription: y\n---\n"], | ||
| 61 | ["non-string metadata value", "---\nname: pdf\ndescription: y\nmetadata:\n n: 1\n---\n"], | ||
| 62 | ])("rejects %s", (_label, markdown) => { | ||
| 63 | expect(Result.isFailure(parseSkillMarkdown(markdown))).toBe(true); | ||
| 64 | }); | ||
| 65 | }); | ||
| 66 | |||
| 67 | describe("prepareSkillFiles", () => { | ||
| 68 | it("digests every file and puts SKILL.md first", async () => { | ||
| 69 | const prepared = await prepareSkillFiles([...files()].reverse()); | ||
| 70 | expect(Result.isSuccess(prepared)).toBe(true); | ||
| 71 | if (Result.isFailure(prepared)) return; | ||
| 72 | expect(prepared.success.files.map((f) => f.path)).toEqual(["SKILL.md", "scripts/extract.py"]); | ||
| 73 | for (const file of prepared.success.files) { | ||
| 74 | expect(file.digest).toMatch(/^sha256:[0-9a-f]{64}$/); | ||
| 75 | expect(file.size).toBe(new TextEncoder().encode(file.content).byteLength); | ||
| 76 | } | ||
| 77 | }); | ||
| 78 | |||
| 79 | it.each([ | ||
| 80 | ["a path escaping the skill", [{ path: "../SKILL.md", content: "" }]], | ||
| 81 | ["an absolute path", [{ path: "/SKILL.md", content: "" }]], | ||
| 82 | ["no SKILL.md", [{ path: "README.md", content: "" }]], | ||
| 83 | ["a duplicate path", [...files(), { path: "scripts/extract.py", content: "" }]], | ||
| 84 | ])("rejects %s", async (_label, inputs) => { | ||
| 85 | expect(Result.isFailure(await prepareSkillFiles(inputs))).toBe(true); | ||
| 86 | }); | ||
| 87 | }); | ||
| 88 | |||
| 89 | describe("skill URIs", () => { | ||
| 90 | it("round-trips owner, name, and path", () => { | ||
| 91 | const uri = skillFileUri({ owner: "org", name: "pdf-processing" }, "references/FORMS.md"); | ||
| 92 | expect(uri).toBe("skill://org/pdf-processing/references/FORMS.md"); | ||
| 93 | expect(parseSkillUri(uri)).toEqual( | ||
| 94 | Option.some({ owner: "org", name: "pdf-processing", path: "references/FORMS.md" }), | ||
| 95 | ); | ||
| 96 | }); | ||
| 97 | |||
| 98 | it("rejects other schemes, unknown owners, and escaping paths", () => { | ||
| 99 | expect(Option.isNone(parseSkillUri("ui://executor/shell.html"))).toBe(true); | ||
| 100 | expect(Option.isNone(parseSkillUri("skill://team/pdf/SKILL.md"))).toBe(true); | ||
| 101 | expect(Option.isNone(parseSkillUri("skill://org/pdf/../x"))).toBe(true); | ||
| 102 | }); | ||
| 103 | }); | ||
| 104 | |||
| 105 | describe("executor.skills", () => { | ||
| 106 | it.effect("list is empty when nothing is saved", () => | ||
| 107 | Effect.gen(function* () { | ||
| 108 | const executor = yield* makeTestExecutor(); | ||
| 109 | expect(yield* executor.skills.list()).toEqual([]); | ||
| 110 | }), | ||
| 111 | ); | ||
| 112 | |||
| 113 | it.effect("save stores the directory under the frontmatter name", () => | ||
| 114 | Effect.gen(function* () { | ||
| 115 | const executor = yield* makeTestExecutor(); | ||
| 116 | const saved = yield* executor.skills.save({ owner: "user", files: files() }); | ||
| 117 | expect(saved.owner).toBe("user"); | ||
| 118 | expect(saved.name).toBe("pdf-processing"); | ||
| 119 | expect(saved.files.map((f) => f.path)).toEqual(["SKILL.md", "scripts/extract.py"]); | ||
| 120 | expect(skillBody(saved).startsWith("# PDF processing")).toBe(true); | ||
| 121 | |||
| 122 | const fetched = yield* executor.skills.get({ owner: "user", name: "pdf-processing" }); | ||
| 123 | expect(fetched).toEqual(saved); | ||
| 124 | |||
| 125 | // Lists carry the manifest, never the contents. | ||
| 126 | const [summary] = yield* executor.skills.list(); | ||
| 127 | expect(summary?.files).toEqual( | ||
| 128 | saved.files.map(({ path, size, digest }) => ({ path, size, digest })), | ||
| 129 | ); | ||
| 130 | expect(summary?.frontmatter["unknown-field"]).toBe("kept verbatim"); | ||
| 131 | }), | ||
| 132 | ); | ||
| 133 | |||
| 134 | it.effect("save replaces an existing skill of the same owner and name in place", () => | ||
| 135 | Effect.gen(function* () { | ||
| 136 | const executor = yield* makeTestExecutor(); | ||
| 137 | yield* executor.skills.save({ owner: "user", files: files() }); | ||
| 138 | const updated = yield* executor.skills.save({ | ||
| 139 | owner: "user", | ||
| 140 | files: [{ path: "SKILL.md", content: SKILL_MD.replace("Extract text", "Extract tables") }], | ||
| 141 | }); | ||
| 142 | expect(updated.description).toContain("Extract tables"); | ||
| 143 | expect(updated.files).toHaveLength(1); | ||
| 144 | expect(yield* executor.skills.list()).toHaveLength(1); | ||
| 145 | }), | ||
| 146 | ); | ||
| 147 | |||
| 148 | it.effect("a personal and a workspace skill may share a name", () => | ||
| 149 | Effect.gen(function* () { | ||
| 150 | const executor = yield* makeTestExecutor(); | ||
| 151 | yield* executor.skills.save({ owner: "user", files: files() }); | ||
| 152 | yield* executor.skills.save({ owner: "org", files: files() }); | ||
| 153 | const listed = yield* executor.skills.list(); | ||
| 154 | expect(listed.map((s) => s.owner).sort()).toEqual(["org", "user"]); | ||
| 155 | }), | ||
| 156 | ); | ||
| 157 | |||
| 158 | it.effect("an invalid skill is refused with a reason", () => | ||
| 159 | Effect.gen(function* () { | ||
| 160 | const executor = yield* makeTestExecutor(); | ||
| 161 | const result = yield* executor.skills | ||
| 162 | .save({ owner: "user", files: [{ path: "SKILL.md", content: "# no frontmatter" }] }) | ||
| 163 | .pipe(Effect.result); | ||
| 164 | expect(Result.isFailure(result)).toBe(true); | ||
| 165 | if (Result.isSuccess(result)) return; | ||
| 166 | expect(Predicate.isTagged("InvalidSkillError")(result.failure)).toBe(true); | ||
| 167 | }), | ||
| 168 | ); | ||
| 169 | |||
| 170 | it.effect("get and remove target one (owner, name)", () => | ||
| 171 | Effect.gen(function* () { | ||
| 172 | const executor = yield* makeTestExecutor(); | ||
| 173 | yield* executor.skills.save({ owner: "user", files: files() }); | ||
| 174 | yield* executor.skills.save({ owner: "org", files: files() }); | ||
| 175 | yield* executor.skills.remove({ owner: "user", name: "pdf-processing" }); | ||
| 176 | const remaining = yield* executor.skills.list(); | ||
| 177 | expect(remaining.map((s) => s.owner)).toEqual(["org"]); | ||
| 178 | const missing = yield* executor.skills | ||
| 179 | .get({ owner: "user", name: "pdf-processing" }) | ||
| 180 | .pipe(Effect.result); | ||
| 181 | expect(Result.isFailure(missing)).toBe(true); | ||
| 182 | }), | ||
| 183 | ); | ||
| 184 | |||
| 185 | it.effect("org writes are refused when workspace writes are denied", () => | ||
| 186 | Effect.gen(function* () { | ||
| 187 | const executor = yield* makeTestExecutor({ orgWrites: "denied" }); | ||
| 188 | const result = yield* executor.skills | ||
| 189 | .save({ owner: "org", files: files() }) | ||
| 190 | .pipe(Effect.result); | ||
| 191 | expect(Result.isFailure(result)).toBe(true); | ||
| 192 | if (Result.isSuccess(result)) return; | ||
| 193 | expect(Predicate.isTagged("OrgWriteDeniedError")(result.failure)).toBe(true); | ||
| 194 | // Personal skills are untouched by the workspace gate. | ||
| 195 | const personal = yield* executor.skills.save({ owner: "user", files: files() }); | ||
| 196 | expect(personal.owner).toBe("user"); | ||
| 197 | }), | ||
| 198 | ); | ||
| 199 | }); | ||
| @@ -68,6 +68,16 @@ import { · 67 unmodified lines | |||
| 68 | } from "./create-artifact"; | 68 | } from "./create-artifact"; |
| 69 | import { TOOL_CALL_CONTRACT_MESSAGE } from "./tool-call-code"; | 69 | import { TOOL_CALL_CONTRACT_MESSAGE } from "./tool-call-code"; |
| 70 | import { resolveArtifactAction } from "./artifact-action"; | 70 | import { resolveArtifactAction } from "./artifact-action"; |
| 71 | import { | ||
| 72 | MCP_SKILLS_EXTENSION_ID, | ||
| 73 | registerWorkspaceSkills, | ||
| 74 | renderSkillsToolDescription, | ||
| 75 | renderWorkspaceSkill, | ||
| 76 | renderWorkspaceSkillsIndex, | ||
| 77 | resolveWorkspaceSkill, | ||
| 78 | type McpSkillsPort, | ||
| 79 | } from "./workspace-skills"; | ||
| 80 | import type { Skill as WorkspaceSkill, SkillSummary } from "@executor-js/sdk"; | ||
| 71 | import { | 81 | import { |
| 72 | extractArtifactRoles, | 82 | extractArtifactRoles, |
| 73 | resolveArtifactBindings, | 83 | resolveArtifactBindings, |
| @@ -206,6 +216,14 @@ type SharedMcpServerConfig = { · 132 unmodified lines | |||
| 206 | * whole `Executor` across this boundary. | 216 | * whole `Executor` across this boundary. |
| 207 | */ | 217 | */ |
| 208 | readonly artifacts?: McpArtifactsPort; | 218 | readonly artifacts?: McpArtifactsPort; |
| 219 | /** | ||
| 220 | * The Agent Skills saved in the caller's workspace (personal + org-shared). | ||
| 221 | * Structurally satisfied by `executor.skills`. Present, the `skills` tool | ||
| 222 | * serves them next to Executor's own docs and the server declares the MCP | ||
| 223 | * Skills Extension; absent (stdio-only hosts, tests), the surface is exactly | ||
| 224 | * the docs-only one it always was. | ||
| 225 | */ | ||
| 226 | readonly skills?: McpSkillsPort; | ||
| 209 | /** | 227 | /** |
| 210 | * The caller's saved connections, for binding an artifact's integration roles | 228 | * The caller's saved connections, for binding an artifact's integration roles |
| 211 | * at create time. Structurally satisfied by `executor.connections`; hosts pass | 229 | * at create time. Structurally satisfied by `executor.connections`; hosts pass |
| @@ -841,6 +859,76 @@ const skillsResult = ( · 629 unmodified lines | |||
| 841 | return { content: [{ type: "text", text }] }; | 859 | return { content: [{ type: "text", text }] }; |
| 842 | }; | 860 | }; |
| 843 | 861 | ||
| 862 | const textResult = (text: string, isError = false): McpToolResult => ({ | ||
| 863 | content: [{ type: "text", text }], | ||
| 864 | ...(isError ? { isError: true } : {}), | ||
| 865 | }); | ||
| 866 | |||
| 867 | // The `skills` tool when the host serves workspace skills too. Built-in docs | ||
| 868 | // keep their exact behavior and win on a name clash (their names are reserved at | ||
| 869 | // save time, so the clash cannot happen for a skill saved through Executor). | ||
| 870 | // The index lists both sections; a miss lists both too, so the model retries | ||
| 871 | // with a name that exists rather than the same miss. | ||
| 872 | // | ||
| 873 | // `file` reads one bundled file of a workspace skill — the third tier of | ||
| 874 | // progressive disclosure. It is meaningless for a built-in doc, which has no | ||
| 875 | // files, and says so instead of silently returning the body. | ||
| 876 | const workspaceSkillsResult = ( | ||
| 877 | input: { readonly name: string | undefined; readonly file: string | undefined }, | ||
| 878 | executeInventory: string, | ||
| 879 | catalog: readonly Skill[], | ||
| 880 | port: McpSkillsPort, | ||
| 881 | ): Effect.Effect<McpToolResult> => | ||
| 882 | Effect.gen(function* () { | ||
| 883 | const skills: readonly SkillSummary[] = yield* port | ||
| 884 | .list() | ||
| 885 | .pipe(Effect.catchCause(() => Effect.succeed<readonly SkillSummary[]>([]))); | ||
| 886 | const index = () => `${renderSkillsIndex(catalog)}\n\n${renderWorkspaceSkillsIndex(skills)}`; | ||
| 887 | const name = input.name?.trim(); | ||
| 888 | const file = input.file?.trim(); | ||
| 889 | if (!name) return textResult(index()); | ||
| 890 | |||
| 891 | const builtIn = findSkill(name, catalog); | ||
| 892 | if (builtIn) { | ||
| 893 | if (file) { | ||
| 894 | return textResult( | ||
| 895 | `\`${name}\` is one of Executor's own docs and has no bundled files. Call \`skills({ name: "${name}" })\` without \`file\`.`, | ||
| 896 | true, | ||
| 897 | ); | ||
| 898 | } | ||
| 899 | return skillsResult(name, executeInventory, catalog); | ||
| 900 | } | ||
| 901 | |||
| 902 | const summary = resolveWorkspaceSkill(name, skills); | ||
| 903 | if (!summary) { | ||
| 904 | return textResult( | ||
| 905 | `No skill named "${name}". The skills this server can serve are listed below — a skill on your own disk or in your harness is not reachable from here.\n\n${index()}`, | ||
| 906 | true, | ||
| 907 | ); | ||
| 908 | } | ||
| 909 | const skill: WorkspaceSkill | null = yield* port | ||
| 910 | .get({ owner: summary.owner, name: summary.name }) | ||
| 911 | .pipe(Effect.catchCause(() => Effect.succeed(null))); | ||
| 912 | if (!skill) { | ||
| 913 | return textResult( | ||
| 914 | `Skill "${name}" could not be loaded right now. Retry, or call with no name to list what is available.`, | ||
| 915 | true, | ||
| 916 | ); | ||
| 917 | } | ||
| 918 | if (file) { | ||
| 919 | const found = skill.files.find((entry) => entry.path === file); | ||
| 920 | if (!found) { | ||
| 921 | const paths = skill.files.map((entry) => `- \`${entry.path}\``).join("\n"); | ||
| 922 | return textResult( | ||
| 923 | `Skill "${skill.name}" has no file "${file}". Its files are:\n${paths}`, | ||
| 924 | true, | ||
| 925 | ); | ||
| 926 | } | ||
| 927 | return textResult(found.content); | ||
| 928 | } | ||
| 929 | return textResult(renderWorkspaceSkill(skill)); | ||
| 930 | }); | ||
| 931 | |||
| 844 | /** Pull the live integration inventory block out of the built execute | 932 | /** Pull the live integration inventory block out of the built execute |
| 845 | * description (it runs from its header to the end), so the `skills` tool can | 933 | * description (it runs from its header to the end), so the `skills` tool can |
| 846 | * re-use it without rebuilding the inventory from the executor. */ | 934 | * re-use it without rebuilding the inventory from the executor. */ |
| @@ -1225,7 +1313,13 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 378 unmodified lines | |||
| 1225 | // `ui://executor/shell.html`; it stays advertised even when no | 1313 | // `ui://executor/shell.html`; it stays advertised even when no |
| 1226 | // shell loader is configured so the capability set doesn't vary | 1314 | // shell loader is configured so the capability set doesn't vary |
| 1227 | // per host. | 1315 | // per host. |
| 1228 | capabilities: { resources: {}, tools: {} }, | 1316 | capabilities: { |
| 1317 | resources: {}, | ||
| 1318 | tools: {}, | ||
| 1319 | // The MCP Skills Extension (SEP-2640) rides on resources, so it | ||
| 1320 | // is declared only when a skills source is wired in. | ||
| 1321 | ...(config.skills ? { extensions: { [MCP_SKILLS_EXTENSION_ID]: {} } } : {}), | ||
| 1322 | }, | ||
| 1229 | jsonSchemaValidator: new CfWorkerJsonSchemaValidator(), | 1323 | jsonSchemaValidator: new CfWorkerJsonSchemaValidator(), |
| 1230 | }, | 1324 | }, |
| 1231 | ), | 1325 | ), |
| @@ -1562,34 +1656,88 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 330 unmodified lines | |||
| 1562 | }), | 1656 | }), |
| 1563 | ); | 1657 | ); |
| 1564 | 1658 | ||
| 1659 | // The catalog in the description is read once per session: it is what the | ||
| 1660 | // model sees before it asks, and a description that changed under a | ||
| 1661 | // client's cached tool list would be worse than one that is a few minutes | ||
| 1662 | // stale. Every CALL reads live. | ||
| 1663 | const workspaceSkills = config.skills; | ||
| 1664 | const skillsCatalogAtBuild: readonly SkillSummary[] = workspaceSkills | ||
| 1665 | ? yield* workspaceSkills.list().pipe( | ||
| 1666 | Effect.catchCause(() => Effect.succeed<readonly SkillSummary[]>([])), | ||
| 1667 | Effect.withSpan("mcp.host.list_workspace_skills"), | ||
| 1668 | ) | ||
| 1669 | : []; | ||
| 1670 | |||
| 1565 | yield* Effect.sync(() => | 1671 | yield* Effect.sync(() => |
| 1566 | server.registerTool( | 1672 | workspaceSkills |
| 1567 | "skills", | 1673 | ? server.registerTool( |
| 1568 | { | 1674 | "skills", |
| 1569 | description: [ | 1675 | { |
| 1570 | "Documentation for THIS server's own tools. Not a general skill reader: it serves a short, fixed set of how-to docs about using `execute` and artifacts here, and it cannot reach your harness's skills, a SKILL.md on disk, or any user- or project-authored skill. The argument is a name from its own catalog, never a path or an outside skill's id.", | 1676 | description: renderSkillsToolDescription(skillsCatalogAtBuild), |
| 1571 | "These docs hold the long-form guidance that would otherwise bloat another tool's always-loaded description.", | 1677 | inputSchema: { |
| 1572 | 'Call `skills({ name: "execute" })` for the full guide to writing code for the `execute` tool (search the catalog, call tools, emit results, resume paused runs).', | 1678 | name: z |
| 1573 | "Call with no name to list the few docs available.", | 1679 | .string() |
| 1574 | ].join("\n"), | 1680 | .optional() |
| 1575 | inputSchema: { | 1681 | .describe( |
| 1576 | name: z | 1682 | 'A skill from the catalog, by `name` or `owner/name`, or one of Executor\'s own docs such as "execute". Omit to list everything.', |
| 1577 | .string() | 1683 | ), |
| 1578 | .optional() | 1684 | file: z |
| 1579 | .describe( | 1685 | .string() |
| 1580 | 'A doc from this server\'s own catalog, e.g. "execute" — not a path or an outside skill name. Omit to list the catalog.', | 1686 | .optional() |
| 1687 | .describe( | ||
| 1688 | 'A bundled file of the named workspace skill to read, as the relative path the skill lists (e.g. "references/guide.md").', | ||
| 1689 | ), | ||
| 1690 | }, | ||
| 1691 | }, | ||
| 1692 | ({ name, file }, extra) => | ||
| 1693 | runToolEffect( | ||
| 1694 | workspaceSkillsResult( | ||
| 1695 | { name, file }, | ||
| 1696 | executeInventory, | ||
| 1697 | skillCatalog, | ||
| 1698 | workspaceSkills, | ||
| 1699 | ), | ||
| 1700 | extra, | ||
| 1581 | ), | 1701 | ), |
| 1582 | }, | 1702 | ) |
| 1583 | }, | 1703 | : server.registerTool( |
| 1584 | ({ name }, extra) => | 1704 | "skills", |
| 1585 | runToolEffect(Effect.succeed(skillsResult(name, executeInventory, skillCatalog)), extra), | 1705 | { |
| 1586 | ), | 1706 | description: [ |
| 1707 | "Documentation for THIS server's own tools. Not a general skill reader: it serves a short, fixed set of how-to docs about using `execute` and artifacts here, and it cannot reach your harness's skills, a SKILL.md on disk, or any user- or project-authored skill. The argument is a name from its own catalog, never a path or an outside skill's id.", | ||
| 1708 | "These docs hold the long-form guidance that would otherwise bloat another tool's always-loaded description.", | ||
| 1709 | 'Call `skills({ name: "execute" })` for the full guide to writing code for the `execute` tool (search the catalog, call tools, emit results, resume paused runs).', | ||
| 1710 | "Call with no name to list the few docs available.", | ||
| 1711 | ].join("\n"), | ||
| 1712 | inputSchema: { | ||
| 1713 | name: z | ||
| 1714 | .string() | ||
| 1715 | .optional() | ||
| 1716 | .describe( | ||
| 1717 | 'A doc from this server\'s own catalog, e.g. "execute" — not a path or an outside skill name. Omit to list the catalog.', | ||
| 1718 | ), | ||
| 1719 | }, | ||
| 1720 | }, | ||
| 1721 | ({ name }, extra) => | ||
| 1722 | runToolEffect( | ||
| 1723 | Effect.succeed(skillsResult(name, executeInventory, skillCatalog)), | ||
| 1724 | extra, | ||
| 1725 | ), | ||
| 1726 | ), | ||
| 1587 | ).pipe( | 1727 | ).pipe( |
| 1588 | Effect.withSpan("mcp.host.register_tool", { | 1728 | Effect.withSpan("mcp.host.register_tool", { |
| 1589 | attributes: { "mcp.tool.name": "skills" }, | 1729 | attributes: { "mcp.tool.name": "skills" }, |
| 1590 | }), | 1730 | }), |
| 1591 | ); | 1731 | ); |
| 1592 | 1732 | ||
| 1733 | if (workspaceSkills) { | ||
| 1734 | yield* Effect.sync(() => | ||
| 1735 | registerWorkspaceSkills(server, workspaceSkills, (effect) => | ||
| 1736 | Effect.runPromiseWith(context)(anchor(effect)), | ||
| 1737 | ), | ||
| 1738 | ).pipe(Effect.withSpan("mcp.host.register_workspace_skills")); | ||
| 1739 | } | ||
| 1740 | |||
| 1593 | yield* Effect.sync(() => { | 1741 | yield* Effect.sync(() => { |
| 1594 | if (elicitationMode.mode === "native") { | 1742 | if (elicitationMode.mode === "native") { |
| 1595 | return undefined; | 1743 | return undefined; |
| @@ -0,0 +1,296 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import { Effect } from "effect"; | ||
| 3 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | ||
| 4 | import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; | ||
| 5 | import * as z from "zod/v4"; | ||
| 6 | import type * as Cause from "effect/Cause"; | ||
| 7 | |||
| 8 | import { SkillName, SkillNotFoundError, type Skill, type SkillSummary } from "@executor-js/sdk"; | ||
| 9 | import type { ExecutionEngine } from "@executor-js/execution"; | ||
| 10 | |||
| 11 | import { createExecutorMcpServer } from "./tool-server"; | ||
| 12 | import { | ||
| 13 | MCP_SKILLS_EXTENSION_ID, | ||
| 14 | resolveWorkspaceSkill, | ||
| 15 | type McpSkillsPort, | ||
| 16 | } from "./workspace-skills"; | ||
| 17 | |||
| 18 | // Workspace skills on the MCP surface: the `skills` tool grows a workspace | ||
| 19 | // section and a `file` argument, and the server speaks the MCP Skills | ||
| 20 | // Extension (SEP-2640). Both read the same port, faked here as a static list. | ||
| 21 | |||
| 22 | const makeStubEngine = <E extends Cause.YieldableError = never>(): ExecutionEngine<E> => ({ | ||
| 23 | execute: () => Effect.succeed({ result: "default" }), | ||
| 24 | executeWithPause: () => Effect.succeed({ status: "completed", result: { result: "default" } }), | ||
| 25 | resume: () => Effect.succeed(null), | ||
| 26 | getPausedExecution: () => Effect.succeed(null), | ||
| 27 | pausedExecutionCount: () => Effect.succeed(0), | ||
| 28 | hasPausedExecutions: () => Effect.succeed(false), | ||
| 29 | getDescription: Effect.succeed("test executor"), | ||
| 30 | shutdown: Effect.void, | ||
| 31 | }); | ||
| 32 | |||
| 33 | const SKILL_MD = [ | ||
| 34 | "---", | ||
| 35 | "name: pdf-processing", | ||
| 36 | "description: Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 37 | "metadata:", | ||
| 38 | ' version: "1.0"', | ||
| 39 | "---", | ||
| 40 | "", | ||
| 41 | "# PDF processing", | ||
| 42 | "", | ||
| 43 | "Read `references/FORMS.md` before filling forms.", | ||
| 44 | ].join("\n"); | ||
| 45 | |||
| 46 | const skill = (owner: "org" | "user", name = "pdf-processing"): Skill => ({ | ||
| 47 | owner, | ||
| 48 | name: SkillName.make(name), | ||
| 49 | description: "Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 50 | frontmatter: { | ||
| 51 | name, | ||
| 52 | description: "Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 53 | metadata: { version: "1.0" }, | ||
| 54 | }, | ||
| 55 | files: [ | ||
| 56 | { | ||
| 57 | path: "SKILL.md", | ||
| 58 | content: SKILL_MD.replace("pdf-processing", name), | ||
| 59 | size: 111, | ||
| 60 | digest: `sha256:${"a".repeat(64)}`, | ||
| 61 | }, | ||
| 62 | { | ||
| 63 | path: "references/FORMS.md", | ||
| 64 | content: "# Forms\n\nFill every field.", | ||
| 65 | size: 26, | ||
| 66 | digest: `sha256:${"b".repeat(64)}`, | ||
| 67 | }, | ||
| 68 | ], | ||
| 69 | createdAt: new Date(0), | ||
| 70 | updatedAt: new Date(0), | ||
| 71 | }); | ||
| 72 | |||
| 73 | const summaryOf = (s: Skill): SkillSummary => ({ | ||
| 74 | ...s, | ||
| 75 | files: s.files.map(({ path, size, digest }) => ({ path, size, digest })), | ||
| 76 | }); | ||
| 77 | |||
| 78 | const portOf = (skills: readonly Skill[]): McpSkillsPort => ({ | ||
| 79 | list: () => Effect.succeed(skills.map(summaryOf)), | ||
| 80 | get: (ref) => { | ||
| 81 | const found = skills.find((s) => s.owner === ref.owner && s.name === ref.name); | ||
| 82 | return found | ||
| 83 | ? Effect.succeed(found) | ||
| 84 | : Effect.fail(new SkillNotFoundError({ owner: ref.owner, name: SkillName.make(ref.name) })); | ||
| 85 | }, | ||
| 86 | }); | ||
| 87 | |||
| 88 | const withClient = async ( | ||
| 89 | skills: McpSkillsPort | undefined, | ||
| 90 | fn: (client: Client) => Promise<void>, | ||
| 91 | ) => { | ||
| 92 | const mcpServer = await Effect.runPromise( | ||
| 93 | createExecutorMcpServer({ engine: makeStubEngine(), ...(skills ? { skills } : {}) }), | ||
| 94 | ); | ||
| 95 | const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); | ||
| 96 | const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); | ||
| 97 | await mcpServer.connect(serverTransport); | ||
| 98 | await client.connect(clientTransport); | ||
| 99 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: test helper must close MCP transports after async client assertions | ||
| 100 | try { | ||
| 101 | await fn(client); | ||
| 102 | } finally { | ||
| 103 | await clientTransport.close(); | ||
| 104 | await serverTransport.close(); | ||
| 105 | } | ||
| 106 | }; | ||
| 107 | |||
| 108 | const textOf = (result: Awaited<ReturnType<Client["callTool"]>>): string => | ||
| 109 | (result.content as Array<{ type: string; text: string }>)[0].text; | ||
| 110 | |||
| 111 | const SkillsListResult = z.object({ | ||
| 112 | skills: z.array( | ||
| 113 | z.object({ | ||
| 114 | uri: z.string(), | ||
| 115 | frontmatter: z.record(z.string(), z.unknown()), | ||
| 116 | resources: z.array(z.object({ uri: z.string(), digest: z.string(), size: z.number() })), | ||
| 117 | }), | ||
| 118 | ), | ||
| 119 | }); | ||
| 120 | const SkillsGetResult = z.object({ skill: SkillsListResult.shape.skills.element }); | ||
| 121 | |||
| 122 | describe("resolveWorkspaceSkill", () => { | ||
| 123 | const both = [summaryOf(skill("org")), summaryOf(skill("user"))]; | ||
| 124 | it("prefers the personal skill on a bare-name clash", () => { | ||
| 125 | expect(resolveWorkspaceSkill("pdf-processing", both)?.owner).toBe("user"); | ||
| 126 | }); | ||
| 127 | it("honours an explicit owner", () => { | ||
| 128 | expect(resolveWorkspaceSkill("org/pdf-processing", both)?.owner).toBe("org"); | ||
| 129 | expect(resolveWorkspaceSkill("user/nope", both)).toBeUndefined(); | ||
| 130 | }); | ||
| 131 | }); | ||
| 132 | |||
| 133 | describe("skills tool without a skills source", () => { | ||
| 134 | it("keeps the docs-only description and input", async () => { | ||
| 135 | await withClient(undefined, async (client) => { | ||
| 136 | const { tools } = await client.listTools(); | ||
| 137 | const tool = tools.find((t) => t.name === "skills"); | ||
| 138 | expect(tool?.description).toContain("Not a general skill reader"); | ||
| 139 | expect(Object.keys(tool?.inputSchema.properties ?? {})).toEqual(["name"]); | ||
| 140 | expect(client.getServerCapabilities()?.extensions).toBeUndefined(); | ||
| 141 | }); | ||
| 142 | }); | ||
| 143 | }); | ||
| 144 | |||
| 145 | describe("skills tool with workspace skills", () => { | ||
| 146 | it("advertises the catalog in the description", async () => { | ||
| 147 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 148 | const { tools } = await client.listTools(); | ||
| 149 | const tool = tools.find((t) => t.name === "skills"); | ||
| 150 | expect(tool?.description).toContain("`pdf-processing` (workspace)"); | ||
| 151 | expect(tool?.description).toContain("Use when the user mentions PDFs"); | ||
| 152 | expect(Object.keys(tool?.inputSchema.properties ?? {}).sort()).toEqual(["file", "name"]); | ||
| 153 | }); | ||
| 154 | }); | ||
| 155 | |||
| 156 | it("lists built-in docs and workspace skills in the index", async () => { | ||
| 157 | await withClient(portOf([skill("user")]), async (client) => { | ||
| 158 | const text = textOf(await client.callTool({ name: "skills", arguments: {} })); | ||
| 159 | expect(text).toContain("`execute`"); | ||
| 160 | expect(text).toContain("`pdf-processing` (personal)"); | ||
| 161 | }); | ||
| 162 | }); | ||
| 163 | |||
| 164 | it("returns the body with frontmatter stripped and lists bundled files", async () => { | ||
| 165 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 166 | const result = await client.callTool({ | ||
| 167 | name: "skills", | ||
| 168 | arguments: { name: "pdf-processing" }, | ||
| 169 | }); | ||
| 170 | const text = textOf(result); | ||
| 171 | expect(result.isError).toBeFalsy(); | ||
| 172 | expect(text).toContain('<skill_content name="pdf-processing" owner="org">'); | ||
| 173 | expect(text).toContain("# PDF processing"); | ||
| 174 | expect(text).not.toContain("description: Extract text"); | ||
| 175 | expect(text).toContain("<file>references/FORMS.md</file>"); | ||
| 176 | expect(text).not.toContain("Fill every field"); | ||
| 177 | expect(result.structuredContent).toBeUndefined(); | ||
| 178 | }); | ||
| 179 | }); | ||
| 180 | |||
| 181 | it("reads a bundled file by path and reports a missing one", async () => { | ||
| 182 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 183 | const hit = await client.callTool({ | ||
| 184 | name: "skills", | ||
| 185 | arguments: { name: "org/pdf-processing", file: "references/FORMS.md" }, | ||
| 186 | }); | ||
| 187 | expect(textOf(hit)).toBe("# Forms\n\nFill every field."); | ||
| 188 | const miss = await client.callTool({ | ||
| 189 | name: "skills", | ||
| 190 | arguments: { name: "pdf-processing", file: "references/NOPE.md" }, | ||
| 191 | }); | ||
| 192 | expect(miss.isError).toBe(true); | ||
| 193 | expect(textOf(miss)).toContain("- `references/FORMS.md`"); | ||
| 194 | }); | ||
| 195 | }); | ||
| 196 | |||
| 197 | it("still serves the built-in docs and refuses `file` on them", async () => { | ||
| 198 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 199 | const doc = await client.callTool({ name: "skills", arguments: { name: "execute" } }); | ||
| 200 | expect(textOf(doc)).toContain("## Workflow"); | ||
| 201 | const bad = await client.callTool({ | ||
| 202 | name: "skills", | ||
| 203 | arguments: { name: "execute", file: "x.md" }, | ||
| 204 | }); | ||
| 205 | expect(bad.isError).toBe(true); | ||
| 206 | }); | ||
| 207 | }); | ||
| 208 | |||
| 209 | it("names both sections on a miss", async () => { | ||
| 210 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 211 | const result = await client.callTool({ name: "skills", arguments: { name: "nope" } }); | ||
| 212 | expect(result.isError).toBe(true); | ||
| 213 | expect(textOf(result)).toContain('No skill named "nope"'); | ||
| 214 | expect(textOf(result)).toContain("`execute`"); | ||
| 215 | expect(textOf(result)).toContain("`pdf-processing`"); | ||
| 216 | }); | ||
| 217 | }); | ||
| 218 | }); | ||
| 219 | |||
| 220 | describe("MCP Skills Extension", () => { | ||
| 221 | it("declares the extension and answers skills/list with digest manifests", async () => { | ||
| 222 | await withClient(portOf([skill("org"), skill("user", "release-notes")]), async (client) => { | ||
| 223 | expect(client.getServerCapabilities()?.extensions).toEqual({ | ||
| 224 | [MCP_SKILLS_EXTENSION_ID]: {}, | ||
| 225 | }); | ||
| 226 | const result = await client.request({ method: "skills/list", params: {} }, SkillsListResult); | ||
| 227 | expect(result.skills.map((s) => s.uri)).toEqual([ | ||
| 228 | "skill://org/pdf-processing/SKILL.md", | ||
| 229 | "skill://user/release-notes/SKILL.md", | ||
| 230 | ]); | ||
| 231 | const [first] = result.skills; | ||
| 232 | expect(first?.frontmatter).toEqual({ | ||
| 233 | name: "pdf-processing", | ||
| 234 | description: "Extract text from PDFs. Use when the user mentions PDFs.", | ||
| 235 | metadata: { version: "1.0" }, | ||
| 236 | }); | ||
| 237 | expect(first?.resources).toEqual([ | ||
| 238 | { | ||
| 239 | uri: "skill://org/pdf-processing/SKILL.md", | ||
| 240 | digest: `sha256:${"a".repeat(64)}`, | ||
| 241 | size: 111, | ||
| 242 | }, | ||
| 243 | { | ||
| 244 | uri: "skill://org/pdf-processing/references/FORMS.md", | ||
| 245 | digest: `sha256:${"b".repeat(64)}`, | ||
| 246 | size: 26, | ||
| 247 | }, | ||
| 248 | ]); | ||
| 249 | }); | ||
| 250 | }); | ||
| 251 | |||
| 252 | it("answers skills/get by SKILL.md URI and -32602 for anything else", async () => { | ||
| 253 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 254 | const result = await client.request( | ||
| 255 | { method: "skills/get", params: { uri: "skill://org/pdf-processing/SKILL.md" } }, | ||
| 256 | SkillsGetResult, | ||
| 257 | ); | ||
| 258 | expect(result.skill.uri).toBe("skill://org/pdf-processing/SKILL.md"); | ||
| 259 | await expect( | ||
| 260 | client.request( | ||
| 261 | { method: "skills/get", params: { uri: "skill://user/pdf-processing/SKILL.md" } }, | ||
| 262 | SkillsGetResult, | ||
| 263 | ), | ||
| 264 | ).rejects.toMatchObject({ code: -32602 }); | ||
| 265 | await expect( | ||
| 266 | client.request( | ||
| 267 | { | ||
| 268 | method: "skills/get", | ||
| 269 | params: { uri: "skill://org/pdf-processing/references/FORMS.md" }, | ||
| 270 | }, | ||
| 271 | SkillsGetResult, | ||
| 272 | ), | ||
| 273 | ).rejects.toMatchObject({ code: -32602 }); | ||
| 274 | }); | ||
| 275 | }); | ||
| 276 | |||
| 277 | it("serves every file as a skill:// resource and lists one entry per skill", async () => { | ||
| 278 | await withClient(portOf([skill("org")]), async (client) => { | ||
| 279 | const listed = await client.listResources(); | ||
| 280 | expect(listed.resources.map((r) => r.uri)).toEqual(["skill://org/pdf-processing/SKILL.md"]); | ||
| 281 | expect(listed.resources[0]?.mimeType).toBe("text/markdown"); | ||
| 282 | |||
| 283 | const read = await client.readResource({ | ||
| 284 | uri: "skill://org/pdf-processing/references/FORMS.md", | ||
| 285 | }); | ||
| 286 | expect(read.contents[0]).toMatchObject({ | ||
| 287 | uri: "skill://org/pdf-processing/references/FORMS.md", | ||
| 288 | mimeType: "text/markdown", | ||
| 289 | text: "# Forms\n\nFill every field.", | ||
| 290 | }); | ||
| 291 | await expect( | ||
| 292 | client.readResource({ uri: "skill://org/pdf-processing/missing.md" }), | ||
| 293 | ).rejects.toMatchObject({ code: -32602 }); | ||
| 294 | }); | ||
| 295 | }); | ||
| 296 | }); | ||
| @@ -0,0 +1,268 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // Workspace skills on the MCP surface. | ||
| 3 | // | ||
| 4 | // A workspace skill is an Agent Skill (a SKILL.md directory, see | ||
| 5 | // https://agentskills.io) saved to Executor by a user, personally or for the | ||
| 6 | // whole org. Every agent connected to this server should be able to find and | ||
| 7 | // load it, so it is served two ways: | ||
| 8 | // | ||
| 9 | // 1. Through the `skills` tool, alongside Executor's own how-to docs. Every | ||
| 10 | // MCP client can call a tool today, so this is the channel that works | ||
| 11 | // everywhere. The tool description carries the catalog (name + description) | ||
| 12 | // so the model knows what exists before it asks. | ||
| 13 | // 2. Through the MCP Skills Extension (SEP-2640): the server declares | ||
| 14 | // `io.modelcontextprotocol/skills`, answers `skills/list` and `skills/get` | ||
| 15 | // with digest manifests, and serves every file as a `skill://` resource. | ||
| 16 | // Clients that adopt the standard pick skills up with no tool call at all. | ||
| 17 | // | ||
| 18 | // Both channels read the same live source (`executor.skills`), so a skill saved | ||
| 19 | // in the console is visible to an already-connected agent on its next call. | ||
| 20 | // --------------------------------------------------------------------------- | ||
| 21 | |||
| 22 | import { Effect, Option } from "effect"; | ||
| 23 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| 24 | import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| 25 | import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; | ||
| 26 | import * as z from "zod/v4"; | ||
| 27 | |||
| 28 | import { | ||
| 29 | parseSkillUri, | ||
| 30 | skillBody, | ||
| 31 | skillFileUri, | ||
| 32 | SKILL_MD_PATH, | ||
| 33 | type Owner, | ||
| 34 | type Skill, | ||
| 35 | type SkillFileEntry, | ||
| 36 | type SkillRef, | ||
| 37 | type SkillSummary, | ||
| 38 | } from "@executor-js/sdk"; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * The two reads this surface needs. Structurally satisfied by | ||
| 42 | * `executor.skills`; hosts pass that so the whole `Executor` never crosses this | ||
| 43 | * boundary. Both are LIVE: nothing here caches skill content. | ||
| 44 | */ | ||
| 45 | export type McpSkillsPort = { | ||
| 46 | readonly list: () => Effect.Effect<readonly SkillSummary[], unknown>; | ||
| 47 | readonly get: (ref: SkillRef) => Effect.Effect<Skill, unknown>; | ||
| 48 | }; | ||
| 49 | |||
| 50 | /** The extension id SEP-2640 assigns, as it appears under `capabilities.extensions`. */ | ||
| 51 | export const MCP_SKILLS_EXTENSION_ID = "io.modelcontextprotocol/skills"; | ||
| 52 | |||
| 53 | // --------------------------------------------------------------------------- | ||
| 54 | // Naming | ||
| 55 | // --------------------------------------------------------------------------- | ||
| 56 | |||
| 57 | /** How a workspace skill is referred to in the `skills` tool: `owner/name`. */ | ||
| 58 | export const workspaceSkillLabel = (ref: SkillRef): string => `${ref.owner}/${ref.name}`; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Resolve what the model typed into one workspace skill. | ||
| 62 | * | ||
| 63 | * Accepts the bare `name` or the explicit `owner/name`. A bare name that exists | ||
| 64 | * both personally and for the org resolves to the personal one: that is the | ||
| 65 | * same shadowing rule local skill directories use (the nearer scope wins), and | ||
| 66 | * the explicit form is always there to reach the other. | ||
| 67 | */ | ||
| 68 | export const resolveWorkspaceSkill = ( | ||
| 69 | input: string, | ||
| 70 | skills: readonly SkillSummary[], | ||
| 71 | ): SkillSummary | undefined => { | ||
| 72 | const slash = input.indexOf("/"); | ||
| 73 | if (slash !== -1) { | ||
| 74 | const owner = input.slice(0, slash); | ||
| 75 | const name = input.slice(slash + 1); | ||
| 76 | return skills.find((skill) => skill.owner === owner && skill.name === name); | ||
| 77 | } | ||
| 78 | const matches = skills.filter((skill) => skill.name === input); | ||
| 79 | return matches.find((skill) => skill.owner === "user") ?? matches[0]; | ||
| 80 | }; | ||
| 81 | |||
| 82 | // --------------------------------------------------------------------------- | ||
| 83 | // Rendering for the `skills` tool | ||
| 84 | // --------------------------------------------------------------------------- | ||
| 85 | |||
| 86 | const ownerWord = (owner: Owner): string => (owner === "org" ? "workspace" : "personal"); | ||
| 87 | |||
| 88 | /** One-line catalog entry, the shape both the index and the description use. */ | ||
| 89 | const catalogLine = (skill: SkillSummary): string => | ||
| 90 | `- \`${skill.name}\` (${ownerWord(skill.owner)}) — ${skill.description}`; | ||
| 91 | |||
| 92 | /** How many workspace skills the tool description names before it truncates. | ||
| 93 | * The description is loaded into every session's prompt, so it stays bounded | ||
| 94 | * no matter how many skills a workspace saves; the index is the full list. */ | ||
| 95 | export const SKILL_CATALOG_DESCRIPTION_LIMIT = 40; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * The `skills` tool description when workspace skills can be served. Replaces | ||
| 99 | * the docs-only wording: this IS now a skill reader for the skills saved in | ||
| 100 | * Executor, and it says so, while still marking the boundary for skills that | ||
| 101 | * live on the agent's own disk (which it still cannot reach). | ||
| 102 | */ | ||
| 103 | export const renderSkillsToolDescription = (skills: readonly SkillSummary[]): string => { | ||
| 104 | const shown = skills.slice(0, SKILL_CATALOG_DESCRIPTION_LIMIT); | ||
| 105 | const hidden = skills.length - shown.length; | ||
| 106 | const catalog = | ||
| 107 | skills.length === 0 | ||
| 108 | ? ["No workspace skills are saved yet; the catalog below is Executor's docs only."] | ||
| 109 | : [ | ||
| 110 | "Skills saved in this Executor workspace — load one when a task matches its description:", | ||
| 111 | ...shown.map(catalogLine), | ||
| 112 | ...(hidden > 0 ? [`- …and ${hidden} more; call with no name for the full index.`] : []), | ||
| 113 | ]; | ||
| 114 | return [ | ||
| 115 | "Skills for THIS server: Executor's own how-to docs (`execute`, artifacts) plus the Agent Skills saved in this Executor workspace. It cannot reach a SKILL.md on your own disk or your harness's skills — only what is listed here.", | ||
| 116 | 'Call `skills({ name: "<name>" })` to load a skill\'s instructions; the result lists its bundled files, which you read with `skills({ name, file: "<path>" })`. A name that exists both personally and for the workspace resolves to the personal one; use `owner/name` (`org/<name>` or `user/<name>`) to be explicit.', | ||
| 117 | 'Call `skills({ name: "execute" })` for the full guide to writing code for the `execute` tool. Call with no name to list everything.', | ||
| 118 | "", | ||
| 119 | ...catalog, | ||
| 120 | ].join("\n"); | ||
| 121 | }; | ||
| 122 | |||
| 123 | /** The workspace section appended to the `skills` index. */ | ||
| 124 | export const renderWorkspaceSkillsIndex = (skills: readonly SkillSummary[]): string => | ||
| 125 | skills.length === 0 | ||
| 126 | ? "No skills are saved in this Executor workspace yet. Add one in the console under Skills and it appears here for every connected agent." | ||
| 127 | : [ | ||
| 128 | 'Skills saved in this Executor workspace. Load one with `skills({ name: "<name>" })`; read a bundled file with `skills({ name, file: "<path>" })`.', | ||
| 129 | "", | ||
| 130 | ...skills.map(catalogLine), | ||
| 131 | ].join("\n"); | ||
| 132 | |||
| 133 | const bundledFiles = (skill: Skill): readonly SkillFileEntry[] => | ||
| 134 | skill.files.filter((file) => file.path !== SKILL_MD_PATH); | ||
| 135 | |||
| 136 | /** | ||
| 137 | * What the model receives when it loads a workspace skill: the SKILL.md body | ||
| 138 | * with the frontmatter stripped (name and description were already in the | ||
| 139 | * catalog), wrapped in identifying tags, followed by the list of bundled files | ||
| 140 | * and how to read one. The files themselves are NOT inlined — that is the | ||
| 141 | * progressive disclosure the standard asks for. | ||
| 142 | */ | ||
| 143 | export const renderWorkspaceSkill = (skill: Skill): string => { | ||
| 144 | const files = bundledFiles(skill); | ||
| 145 | const resources = | ||
| 146 | files.length === 0 | ||
| 147 | ? [] | ||
| 148 | : [ | ||
| 149 | "", | ||
| 150 | "<skill_resources>", | ||
| 151 | ...files.map((file) => ` <file>${file.path}</file>`), | ||
| 152 | "</skill_resources>", | ||
| 153 | `Read a bundled file with \`skills({ name: "${workspaceSkillLabel(skill)}", file: "<path>" })\`. Relative paths in the instructions above are relative to the skill's root.`, | ||
| 154 | ]; | ||
| 155 | return [ | ||
| 156 | `<skill_content name="${skill.name}" owner="${skill.owner}">`, | ||
| 157 | skillBody(skill), | ||
| 158 | ...resources, | ||
| 159 | "</skill_content>", | ||
| 160 | ].join("\n"); | ||
| 161 | }; | ||
| 162 | |||
| 163 | // --------------------------------------------------------------------------- | ||
| 164 | // MCP Skills Extension (SEP-2640) | ||
| 165 | // --------------------------------------------------------------------------- | ||
| 166 | |||
| 167 | /** MIME type for a skill file, by extension. Markdown is what the standard is | ||
| 168 | * made of; everything else is served as plain text. */ | ||
| 169 | export const skillFileMimeType = (path: string): string => | ||
| 170 | path.endsWith(".md") ? "text/markdown" : "text/plain"; | ||
| 171 | |||
| 172 | /** One `skills/list` / `skills/get` entry: the SKILL.md URI, the verbatim | ||
| 173 | * frontmatter, and the complete digest manifest. */ | ||
| 174 | export const skillEntry = (skill: SkillSummary) => ({ | ||
| 175 | uri: skillFileUri(skill, SKILL_MD_PATH), | ||
| 176 | frontmatter: skill.frontmatter, | ||
| 177 | resources: skill.files.map((file) => ({ | ||
| 178 | uri: skillFileUri(skill, file.path), | ||
| 179 | digest: file.digest, | ||
| 180 | size: file.size, | ||
| 181 | })), | ||
| 182 | }); | ||
| 183 | |||
| 184 | const SkillsListRequestSchema = z.object({ | ||
| 185 | method: z.literal("skills/list"), | ||
| 186 | params: z.object({ cursor: z.string().optional() }).loose().optional(), | ||
| 187 | }); | ||
| 188 | |||
| 189 | const SkillsGetRequestSchema = z.object({ | ||
| 190 | method: z.literal("skills/get"), | ||
| 191 | params: z.object({ uri: z.string() }).loose(), | ||
| 192 | }); | ||
| 193 | |||
| 194 | const SKILL_RESOURCE_TEMPLATE = "skill://{owner}/{name}/{+path}"; | ||
| 195 | |||
| 196 | /** Runs an Effect at an SDK callback edge with the server's captured context. */ | ||
| 197 | export type RunAtEdge = <A>(effect: Effect.Effect<A, unknown>) => Promise<A>; | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Register the extension's methods and the `skill://` resource space on a | ||
| 201 | * server that has already declared the `resources` capability and the | ||
| 202 | * extension under `capabilities.extensions`. | ||
| 203 | * | ||
| 204 | * `skills/list` never paginates: a workspace's catalog is small by | ||
| 205 | * construction (each entry is one saved row), and an unpaginated listing is | ||
| 206 | * what every client handles. | ||
| 207 | */ | ||
| 208 | export const registerWorkspaceSkills = ( | ||
| 209 | server: McpServer, | ||
| 210 | port: McpSkillsPort, | ||
| 211 | run: RunAtEdge, | ||
| 212 | ): void => { | ||
| 213 | const list = () => run(port.list()); | ||
| 214 | // The SDK turns a thrown McpError into the JSON-RPC error the method | ||
| 215 | // requires (-32602 for an unknown skill or file), so the not-found path is a | ||
| 216 | // failed Effect run through the same edge as every other read. | ||
| 217 | const notFound = (uri: string) => | ||
| 218 | run( | ||
| 219 | Effect.fail(new McpError(ErrorCode.InvalidParams, `Unknown skill resource: ${uri}`)), | ||
| 220 | ) as Promise<never>; | ||
| 221 | |||
| 222 | server.server.setRequestHandler(SkillsListRequestSchema, async () => ({ | ||
| 223 | skills: (await list()).map(skillEntry), | ||
| 224 | })); | ||
| 225 | |||
| 226 | server.server.setRequestHandler(SkillsGetRequestSchema, async ({ params }) => { | ||
| 227 | const parsed = parseSkillUri(params.uri); | ||
| 228 | if (Option.isNone(parsed) || parsed.value.path !== SKILL_MD_PATH) return notFound(params.uri); | ||
| 229 | const { owner, name } = parsed.value; | ||
| 230 | const skill = (await list()).find((entry) => entry.owner === owner && entry.name === name); | ||
| 231 | if (!skill) return notFound(params.uri); | ||
| 232 | return { skill: skillEntry(skill) }; | ||
| 233 | }); | ||
| 234 | |||
| 235 | server.registerResource( | ||
| 236 | "Workspace skills", | ||
| 237 | new ResourceTemplate(SKILL_RESOURCE_TEMPLATE, { | ||
| 238 | // `resources/list` shows one entry per skill — its SKILL.md — so a | ||
| 239 | // resource browser sees the skills, not every bundled file. | ||
| 240 | list: async () => ({ | ||
| 241 | resources: (await list()).map((skill) => ({ | ||
| 242 | uri: skillFileUri(skill, SKILL_MD_PATH), | ||
| 243 | name: skill.name, | ||
| 244 | description: skill.description, | ||
| 245 | mimeType: skillFileMimeType(SKILL_MD_PATH), | ||
| 246 | })), | ||
| 247 | }), | ||
| 248 | }), | ||
| 249 | { | ||
| 250 | description: "Agent Skills saved in this Executor workspace, one file per resource.", | ||
| 251 | mimeType: "text/markdown", | ||
| 252 | }, | ||
| 253 | async (uri) => { | ||
| 254 | const href = uri.toString(); | ||
| 255 | const parsed = parseSkillUri(href); | ||
| 256 | if (Option.isNone(parsed) || parsed.value.path === "") return notFound(href); | ||
| 257 | const { owner, name, path } = parsed.value; | ||
| 258 | const skill = await run( | ||
| 259 | port.get({ owner, name }).pipe(Effect.catchCause(() => Effect.succeed(null))), | ||
| 260 | ); | ||
| 261 | const file = skill?.files.find((entry) => entry.path === path); | ||
| 262 | if (!file) return notFound(href); | ||
| 263 | return { | ||
| 264 | contents: [{ uri: href, mimeType: skillFileMimeType(path), text: file.content }], | ||
| 265 | }; | ||
| 266 | }, | ||
| 267 | ); | ||
| 268 | }; | ||
| @@ -12,6 +12,7 @@ import { · 11 unmodified lines | |||
| 12 | type OAuthGrant, | 12 | type OAuthGrant, |
| 13 | type Owner, | 13 | type Owner, |
| 14 | type ProviderItemId, | 14 | type ProviderItemId, |
| 15 | type SkillName, | ||
| 15 | type TokenEndpointAuthMethod, | 16 | type TokenEndpointAuthMethod, |
| 16 | type ToolAddress, | 17 | type ToolAddress, |
| 17 | } from "@executor-js/sdk/shared"; | 18 | } from "@executor-js/sdk/shared"; |
| @@ -173,6 +174,32 @@ export const artifactAtom = Atom.family((artifactId: ArtifactId) => · 155 unmodified lines | |||
| 173 | }), | 174 | }), |
| 174 | ); | 175 | ); |
| 175 | 176 | ||
| 177 | // --------------------------------------------------------------------------- | ||
| 178 | // Agent Skills — SKILL.md directories saved to the workspace, owner-scoped the | ||
| 179 | // same way connections are. | ||
| 180 | // --------------------------------------------------------------------------- | ||
| 181 | |||
| 182 | export const skillsAtom = ExecutorApiClient.query("skills", "list", { | ||
| 183 | timeToLive: "30 seconds", | ||
| 184 | reactivityKeys: [ReactivityKey.skills], | ||
| 185 | }); | ||
| 186 | |||
| 187 | /** | ||
| 188 | * One skill WITH the content of every file it bundles. `skills.list` returns | ||
| 189 | * the manifest only (path, size, digest), so the detail and edit views have to | ||
| 190 | * fetch the row rather than derive it from the list. | ||
| 191 | * | ||
| 192 | * `Atom.family` (not a bare arrow) because the pages rebuild the `{owner,name}` | ||
| 193 | * key object on every render — a fresh atom per render would refetch in a loop. | ||
| 194 | */ | ||
| 195 | export const skillAtom = Atom.family((ref: { readonly owner: Owner; readonly name: SkillName }) => | ||
| 196 | ExecutorApiClient.query("skills", "get", { | ||
| 197 | params: { owner: ref.owner, name: ref.name }, | ||
| 198 | timeToLive: "30 seconds", | ||
| 199 | reactivityKeys: [ReactivityKey.skills], | ||
| 200 | }), | ||
| 201 | ); | ||
| 202 | |||
| 176 | // --------------------------------------------------------------------------- | 203 | // --------------------------------------------------------------------------- |
| 177 | // Mutation atoms — reactivityKeys must be passed at call site (effect-atom | 204 | // Mutation atoms — reactivityKeys must be passed at call site (effect-atom |
| 178 | // does not accept them at definition time). See `reactivity-keys.tsx` for the | 205 | // does not accept them at definition time). See `reactivity-keys.tsx` for the |
| @@ -281,6 +308,13 @@ export const removeArtifact = ExecutorApiClient.mutation("artifacts", "remove"); · 102 unmodified lines | |||
| 281 | */ | 308 | */ |
| 282 | export const setArtifactPreview = ExecutorApiClient.mutation("artifacts", "setPreview"); | 309 | export const setArtifactPreview = ExecutorApiClient.mutation("artifacts", "setPreview"); |
| 283 | 310 | ||
| 311 | /** Create or replace a skill in place. The name is read from the uploaded | ||
| 312 | * SKILL.md, never sent separately, so a renamed frontmatter saves a NEW skill. | ||
| 313 | * Pass `reactivityKeys: skillWriteKeys` at the call site. */ | ||
| 314 | export const saveSkill = ExecutorApiClient.mutation("skills", "save"); | ||
| 315 | |||
| 316 | export const removeSkill = ExecutorApiClient.mutation("skills", "remove"); | ||
| 317 | |||
| 284 | export const resumeExecution = ExecutorApiClient.mutation("executions", "resume"); | 318 | export const resumeExecution = ExecutorApiClient.mutation("executions", "resume"); |
| 285 | 319 | ||
| 286 | /** Run codemode source (`POST /executions`). Used by the per-tool Run/Test panel | 320 | /** Run codemode source (`POST /executions`). Used by the per-tool Run/Test panel |
| @@ -28,6 +28,8 @@ export const ReactivityKey = { · 27 unmodified lines | |||
| 28 | policies: "policies", | 28 | policies: "policies", |
| 29 | /** Saved generative-UI artifacts. */ | 29 | /** Saved generative-UI artifacts. */ |
| 30 | artifacts: "artifacts", | 30 | artifacts: "artifacts", |
| 31 | /** Agent Skills (SKILL.md directories) saved to the workspace. */ | ||
| 32 | skills: "skills", | ||
| 31 | /** Registered OAuth clients (apps). */ | 33 | /** Registered OAuth clients (apps). */ |
| 32 | oauthClients: "oauth-clients", | 34 | oauthClients: "oauth-clients", |
| 33 | /** An integration's declared health check (the operation/identity-field spec). */ | 35 | /** An integration's declared health check (the operation/identity-field spec). */ |
| @@ -80,6 +82,11 @@ export const policyWriteKeys = [ReactivityKey.policies, ReactivityKey.tools] as · 46 unmodified lines | |||
| 80 | * resource — nothing else reads them — so they invalidate only themselves. */ | 82 | * resource — nothing else reads them — so they invalidate only themselves. */ |
| 81 | export const artifactWriteKeys = [ReactivityKey.artifacts] as const; | 83 | export const artifactWriteKeys = [ReactivityKey.artifacts] as const; |
| 82 | 84 | ||
| 85 | /** Mutations that save or delete a skill. Nothing else in the console reads | ||
| 86 | * skills (the MCP host serves them straight from storage), so they invalidate | ||
| 87 | * only themselves. */ | ||
| 88 | export const skillWriteKeys = [ReactivityKey.skills] as const; | ||
| 89 | |||
| 83 | /** Cloud-only: org membership mutations. */ | 90 | /** Cloud-only: org membership mutations. */ |
| 84 | export const orgMemberWriteKeys = [ReactivityKey.orgMembers] as const; | 91 | export const orgMemberWriteKeys = [ReactivityKey.orgMembers] as const; |
| 85 | 92 | ||
| @@ -46,6 +46,9 @@ export const CONSOLE_ROUTE_PATHS = [ · 45 unmodified lines | |||
| 46 | "/toolkits/$toolkitSlug", | 46 | "/toolkits/$toolkitSlug", |
| 47 | "/artifacts", | 47 | "/artifacts", |
| 48 | "/artifacts/$artifactId", | 48 | "/artifacts/$artifactId", |
| 49 | "/skills", | ||
| 50 | "/skills/new", | ||
| 51 | "/skills/$skillOwner/$skillName", | ||
| 49 | "/resume/$executionId", | 52 | "/resume/$executionId", |
| 50 | "/plugins/$pluginId/$", | 53 | "/plugins/$pluginId/$", |
| 51 | ] as const; | 54 | ] as const; |
| @@ -91,6 +94,14 @@ export const consoleRoutes = (options: ConsoleRoutesOptions): Array<VirtualRoute · 39 unmodified lines | |||
| 91 | ["/toolkits/$toolkitSlug", route("/toolkits/$toolkitSlug", file("toolkits.$toolkitSlug.tsx"))], | 94 | ["/toolkits/$toolkitSlug", route("/toolkits/$toolkitSlug", file("toolkits.$toolkitSlug.tsx"))], |
| 92 | ["/artifacts", route("/artifacts", file("artifacts.tsx"))], | 95 | ["/artifacts", route("/artifacts", file("artifacts.tsx"))], |
| 93 | ["/artifacts/$artifactId", route("/artifacts/$artifactId", file("artifacts.$artifactId.tsx"))], | 96 | ["/artifacts/$artifactId", route("/artifacts/$artifactId", file("artifacts.$artifactId.tsx"))], |
| 97 | // A skill is addressed by `(owner, name)`, so both segments are in the URL: | ||
| 98 | // a personal and a workspace skill can share a name and stay distinct. | ||
| 99 | ["/skills", route("/skills", file("skills.tsx"))], | ||
| 100 | ["/skills/new", route("/skills/new", file("skills.new.tsx"))], | ||
| 101 | [ | ||
| 102 | "/skills/$skillOwner/$skillName", | ||
| 103 | route("/skills/$skillOwner/$skillName", file("skills.$skillOwner.$skillName.tsx")), | ||
| 104 | ], | ||
| 94 | ["/resume/$executionId", route("/resume/$executionId", file("resume.$executionId.tsx"))], | 105 | ["/resume/$executionId", route("/resume/$executionId", file("resume.$executionId.tsx"))], |
| 95 | ["/plugins/$pluginId/$", route("/plugins/$pluginId/$", file("plugins.$pluginId.$.tsx"))], | 106 | ["/plugins/$pluginId/$", route("/plugins/$pluginId/$", file("plugins.$pluginId.$.tsx"))], |
| 96 | ]; | 107 | ]; |
| @@ -52,6 +52,7 @@ export const defaultShellNavItems: ReadonlyArray<ShellNavItem> = [ · 51 unmodified lines | |||
| 52 | { to: "/secrets", label: "Providers" }, | 52 | { to: "/secrets", label: "Providers" }, |
| 53 | { to: "/policies", label: "Policies" }, | 53 | { to: "/policies", label: "Policies" }, |
| 54 | { to: "/toolkits", label: "Toolkits" }, | 54 | { to: "/toolkits", label: "Toolkits" }, |
| 55 | { to: "/skills", label: "Skills" }, | ||
| 55 | { to: "/artifacts", label: "Artifacts" }, | 56 | { to: "/artifacts", label: "Artifacts" }, |
| 56 | ]; | 57 | ]; |
| 57 | 58 | ||
| @@ -0,0 +1,285 @@ | |||
| 1 | import { useMemo } from "react"; | ||
| 2 | import { useAtomRefresh, useAtomSet, useAtomValue } from "@effect/atom-react"; | ||
| 3 | import { Link, useNavigate } from "@tanstack/react-router"; | ||
| 4 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Result from "effect/Result"; | ||
| 7 | import { toast } from "sonner"; | ||
| 8 | import { | ||
| 9 | parseSkillMarkdown, | ||
| 10 | skillFileUri, | ||
| 11 | SKILL_MD_PATH, | ||
| 12 | type Owner, | ||
| 13 | type SkillName, | ||
| 14 | } from "@executor-js/sdk/shared"; | ||
| 15 | |||
| 16 | import { removeSkill, skillAtom } from "../api/atoms"; | ||
| 17 | import { messageFromExit } from "../api/error-reporting"; | ||
| 18 | import { skillWriteKeys } from "../api/reactivity-keys"; | ||
| 19 | import { | ||
| 20 | AlertDialog, | ||
| 21 | AlertDialogAction, | ||
| 22 | AlertDialogCancel, | ||
| 23 | AlertDialogContent, | ||
| 24 | AlertDialogDescription, | ||
| 25 | AlertDialogFooter, | ||
| 26 | AlertDialogHeader, | ||
| 27 | AlertDialogTitle, | ||
| 28 | AlertDialogTrigger, | ||
| 29 | } from "../components/alert-dialog"; | ||
| 30 | import { Button } from "../components/button"; | ||
| 31 | import { CopyButton } from "../components/copy-button"; | ||
| 32 | import { ErrorState } from "../components/error-state"; | ||
| 33 | import { Markdown } from "../components/markdown"; | ||
| 34 | import { PageContainer, PageHeader } from "../components/page"; | ||
| 35 | import { isAsyncResultLoading } from "../lib/async-result"; | ||
| 36 | import { useExecutorDocumentTitle } from "../lib/document-title"; | ||
| 37 | import { formatRelativeTime } from "../lib/relative-time"; | ||
| 38 | import { SkillOwnerTag } from "./skills"; | ||
| 39 | |||
| 40 | interface SkillFileRow { | ||
| 41 | readonly path: string; | ||
| 42 | readonly size: number; | ||
| 43 | readonly digest: string; | ||
| 44 | readonly content: string; | ||
| 45 | } | ||
| 46 | |||
| 47 | const BackLink = () => ( | ||
| 48 | <Button asChild variant="ghost" size="sm" className="-ml-3 mb-4 text-muted-foreground"> | ||
| 49 | <Link to="/{-$orgSlug}/skills">Skills</Link> | ||
| 50 | </Button> | ||
| 51 | ); | ||
| 52 | |||
| 53 | /** A URL that names no skill this host could ever have (a bad owner segment). */ | ||
| 54 | export function SkillMissingPage() { | ||
| 55 | useExecutorDocumentTitle("Skill"); | ||
| 56 | return ( | ||
| 57 | <PageContainer> | ||
| 58 | <BackLink /> | ||
| 59 | <p className="text-sm text-muted-foreground"> | ||
| 60 | That isn't a skill address. A skill lives at{" "} | ||
| 61 | <span className="font-mono text-foreground">/skills/user/<name></span> or{" "} | ||
| 62 | <span className="font-mono text-foreground">/skills/org/<name></span>. | ||
| 63 | </p> | ||
| 64 | </PageContainer> | ||
| 65 | ); | ||
| 66 | } | ||
| 67 | |||
| 68 | /** A labelled row of machine metadata — mono value, quiet mono key. */ | ||
| 69 | function MetaRow(props: { readonly label: string; readonly children: React.ReactNode }) { | ||
| 70 | return ( | ||
| 71 | <div className="flex gap-4 py-2"> | ||
| 72 | <dt className="w-40 shrink-0 font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 73 | {props.label} | ||
| 74 | </dt> | ||
| 75 | <dd className="min-w-0 flex-1 text-sm text-foreground">{props.children}</dd> | ||
| 76 | </div> | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | |||
| 80 | /** One bundled file: path and size always visible, content behind a disclosure | ||
| 81 | * so a skill with a dozen references still reads as a list. */ | ||
| 82 | function SkillFileEntry(props: { readonly file: SkillFileRow }) { | ||
| 83 | const { file } = props; | ||
| 84 | return ( | ||
| 85 | <details className="group/skill-file px-4 py-2.5"> | ||
| 86 | <summary className="flex cursor-pointer list-none items-center justify-between gap-3 outline-none focus-visible:underline"> | ||
| 87 | <span className="truncate font-mono text-[13px] text-foreground">{file.path}</span> | ||
| 88 | <span className="shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground"> | ||
| 89 | {file.size} B | ||
| 90 | </span> | ||
| 91 | </summary> | ||
| 92 | <pre className="mt-2.5 max-h-96 overflow-auto rounded-md border border-border bg-muted/40 p-3 font-mono text-[12px] leading-relaxed whitespace-pre-wrap text-foreground"> | ||
| 93 | {file.content} | ||
| 94 | </pre> | ||
| 95 | </details> | ||
| 96 | ); | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | ||
| 100 | * What an agent gets. The console is where a person decides whether a skill is | ||
| 101 | * doing its job, and they cannot judge that without seeing the two addresses | ||
| 102 | * their agent will actually use. | ||
| 103 | */ | ||
| 104 | function AgentAddressing(props: { readonly owner: Owner; readonly name: string }) { | ||
| 105 | const toolCall = `skills({ name: "${props.name}" })`; | ||
| 106 | const uri = skillFileUri({ owner: props.owner, name: props.name }, SKILL_MD_PATH); | ||
| 107 | return ( | ||
| 108 | <section> | ||
| 109 | <h2 className="mb-3 font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 110 | Agents see this as | ||
| 111 | </h2> | ||
| 112 | <div className="divide-y divide-border overflow-hidden rounded-lg border border-border bg-card"> | ||
| 113 | {[toolCall, uri].map((value) => ( | ||
| 114 | <div key={value} className="flex items-center justify-between gap-3 px-4 py-2.5"> | ||
| 115 | <code className="truncate font-mono text-[12px] text-foreground">{value}</code> | ||
| 116 | <CopyButton value={value} /> | ||
| 117 | </div> | ||
| 118 | ))} | ||
| 119 | </div> | ||
| 120 | <p className="mt-2 text-xs leading-relaxed text-muted-foreground"> | ||
| 121 | The tool call works on every MCP client today. The{" "} | ||
| 122 | <span className="font-mono">skill://</span> resource is for clients that speak the MCP | ||
| 123 | Skills extension. | ||
| 124 | </p> | ||
| 125 | </section> | ||
| 126 | ); | ||
| 127 | } | ||
| 128 | |||
| 129 | export function SkillDetailPage(props: { readonly owner: Owner; readonly name: SkillName }) { | ||
| 130 | // Rebuilt every render, so it MUST go through the atom family — see | ||
| 131 | // `skillAtom`. `useMemo` keeps the key object stable for the hook deps too. | ||
| 132 | const ref = useMemo(() => ({ owner: props.owner, name: props.name }), [props.owner, props.name]); | ||
| 133 | const skill = useAtomValue(skillAtom(ref)); | ||
| 134 | const refresh = useAtomRefresh(skillAtom(ref)); | ||
| 135 | const doRemove = useAtomSet(removeSkill, { mode: "promiseExit" }); | ||
| 136 | const navigate = useNavigate(); | ||
| 137 | |||
| 138 | useExecutorDocumentTitle(props.name || "Skill"); | ||
| 139 | |||
| 140 | const handleRemove = async () => { | ||
| 141 | const exit = await doRemove({ params: ref, reactivityKeys: skillWriteKeys }); | ||
| 142 | if (Exit.isFailure(exit)) { | ||
| 143 | toast.error(messageFromExit(exit, "Couldn't delete the skill. Try again.")); | ||
| 144 | return; | ||
| 145 | } | ||
| 146 | toast.success("Skill deleted"); | ||
| 147 | // `params` is omitted so the router keeps the active org slug. | ||
| 148 | await navigate({ to: "/{-$orgSlug}/skills" }); | ||
| 149 | }; | ||
| 150 | |||
| 151 | return ( | ||
| 152 | <PageContainer> | ||
| 153 | <BackLink /> | ||
| 154 | |||
| 155 | {isAsyncResultLoading(skill) ? ( | ||
| 156 | <p className="text-sm text-muted-foreground">Loading skill…</p> | ||
| 157 | ) : ( | ||
| 158 | AsyncResult.match(skill, { | ||
| 159 | onInitial: () => <p className="text-sm text-muted-foreground">Loading skill…</p>, | ||
| 160 | onFailure: () => ( | ||
| 161 | <ErrorState | ||
| 162 | message="This skill isn't available. It may have been deleted." | ||
| 163 | onRetry={refresh} | ||
| 164 | /> | ||
| 165 | ), | ||
| 166 | onSuccess: ({ value }) => { | ||
| 167 | const files = value.files as ReadonlyArray<SkillFileRow>; | ||
| 168 | const skillMd = files.find((file) => file.path === SKILL_MD_PATH); | ||
| 169 | const parsed = skillMd ? parseSkillMarkdown(skillMd.content) : undefined; | ||
| 170 | const body = | ||
| 171 | parsed && Result.isSuccess(parsed) ? parsed.success.body : (skillMd?.content ?? ""); | ||
| 172 | // Name and description have their own place in the header; the rest | ||
| 173 | // of the frontmatter is whatever the author wrote and is shown | ||
| 174 | // verbatim, because that is what the MCP host hands to agents. | ||
| 175 | const extraFrontmatter = Object.entries(value.frontmatter).filter( | ||
| 176 | ([key]) => key !== "name" && key !== "description", | ||
| 177 | ); | ||
| 178 | |||
| 179 | return ( | ||
| 180 | <div className="space-y-10"> | ||
| 181 | <PageHeader | ||
| 182 | className="mb-0" | ||
| 183 | title={<span className="font-mono text-[1.6rem]">{value.name}</span>} | ||
| 184 | description={value.description} | ||
| 185 | actions={ | ||
| 186 | <> | ||
| 187 | <Button asChild variant="outline" size="sm"> | ||
| 188 | <Link | ||
| 189 | to="/{-$orgSlug}/skills/$skillOwner/$skillName" | ||
| 190 | params={{ skillOwner: value.owner, skillName: value.name }} | ||
| 191 | search={{ edit: true }} | ||
| 192 | > | ||
| 193 | Edit | ||
| 194 | </Link> | ||
| 195 | </Button> | ||
| 196 | <AlertDialog> | ||
| 197 | <AlertDialogTrigger asChild> | ||
| 198 | <Button | ||
| 199 | type="button" | ||
| 200 | variant="outline" | ||
| 201 | size="sm" | ||
| 202 | className="text-muted-foreground hover:text-destructive" | ||
| 203 | > | ||
| 204 | Delete | ||
| 205 | </Button> | ||
| 206 | </AlertDialogTrigger> | ||
| 207 | <AlertDialogContent size="sm"> | ||
| 208 | <AlertDialogHeader> | ||
| 209 | <AlertDialogTitle>Delete {value.name}?</AlertDialogTitle> | ||
| 210 | <AlertDialogDescription> | ||
| 211 | This removes the skill and every file in it. Agents will no longer | ||
| 212 | find it by name. | ||
| 213 | </AlertDialogDescription> | ||
| 214 | </AlertDialogHeader> | ||
| 215 | <AlertDialogFooter> | ||
| 216 | <AlertDialogCancel>Cancel</AlertDialogCancel> | ||
| 217 | <AlertDialogAction | ||
| 218 | variant="destructive" | ||
| 219 | onClick={() => void handleRemove()} | ||
| 220 | > | ||
| 221 | Delete Skill | ||
| 222 | </AlertDialogAction> | ||
| 223 | </AlertDialogFooter> | ||
| 224 | </AlertDialogContent> | ||
| 225 | </AlertDialog> | ||
| 226 | </> | ||
| 227 | } | ||
| 228 | > | ||
| 229 | <div className="mt-3 flex items-center gap-2"> | ||
| 230 | <SkillOwnerTag owner={value.owner} /> | ||
| 231 | <span className="font-mono text-[11px] text-muted-foreground"> | ||
| 232 | Updated {formatRelativeTime(value.updatedAt)} | ||
| 233 | </span> | ||
| 234 | </div> | ||
| 235 | </PageHeader> | ||
| 236 | |||
| 237 | {extraFrontmatter.length > 0 ? ( | ||
| 238 | <section> | ||
| 239 | <h2 className="mb-1 font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 240 | Frontmatter | ||
| 241 | </h2> | ||
| 242 | <dl className="divide-y divide-border"> | ||
| 243 | {extraFrontmatter.map(([key, fieldValue]) => ( | ||
| 244 | <MetaRow key={key} label={key}> | ||
| 245 | <span className="font-mono text-[12px] break-words"> | ||
| 246 | {typeof fieldValue === "string" | ||
| 247 | ? fieldValue | ||
| 248 | : JSON.stringify(fieldValue)} | ||
| 249 | </span> | ||
| 250 | </MetaRow> | ||
| 251 | ))} | ||
| 252 | </dl> | ||
| 253 | </section> | ||
| 254 | ) : null} | ||
| 255 | |||
| 256 | <section> | ||
| 257 | <h2 className="mb-3 font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 258 | Instructions | ||
| 259 | </h2> | ||
| 260 | <div className="rounded-lg border border-border bg-card px-5 py-4 text-sm leading-relaxed"> | ||
| 261 | <Markdown>{body}</Markdown> | ||
| 262 | </div> | ||
| 263 | </section> | ||
| 264 | |||
| 265 | <section> | ||
| 266 | <h2 className="mb-3 flex items-center font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 267 | Files | ||
| 268 | <span className="ml-2 font-normal tabular-nums">{files.length}</span> | ||
| 269 | </h2> | ||
| 270 | <div className="divide-y divide-border overflow-hidden rounded-lg border border-border bg-card"> | ||
| 271 | {files.map((file) => ( | ||
| 272 | <SkillFileEntry key={file.path} file={file} /> | ||
| 273 | ))} | ||
| 274 | </div> | ||
| 275 | </section> | ||
| 276 | |||
| 277 | <AgentAddressing owner={value.owner} name={value.name} /> | ||
| 278 | </div> | ||
| 279 | ); | ||
| 280 | }, | ||
| 281 | }) | ||
| 282 | )} | ||
| 283 | </PageContainer> | ||
| 284 | ); | ||
| 285 | } | ||
| @@ -0,0 +1,458 @@ | |||
| 1 | import { useEffect, useMemo, useRef, useState } from "react"; | ||
| 2 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; | ||
| 3 | import { Link, useNavigate } from "@tanstack/react-router"; | ||
| 4 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Result from "effect/Result"; | ||
| 8 | import * as Schema from "effect/Schema"; | ||
| 9 | import { | ||
| 10 | isValidSkillFilePath, | ||
| 11 | parseSkillMarkdown, | ||
| 12 | SKILL_MD_PATH, | ||
| 13 | type Owner, | ||
| 14 | type SkillName, | ||
| 15 | } from "@executor-js/sdk/shared"; | ||
| 16 | |||
| 17 | import { saveSkill, skillAtom } from "../api/atoms"; | ||
| 18 | import { messageFromExit } from "../api/error-reporting"; | ||
| 19 | import { useOrganizationId } from "../api/organization-context"; | ||
| 20 | import { skillWriteKeys } from "../api/reactivity-keys"; | ||
| 21 | import { Button } from "../components/button"; | ||
| 22 | import { FieldLabel } from "../components/field"; | ||
| 23 | import { Input } from "../components/input"; | ||
| 24 | import { PageContainer, PageHeader } from "../components/page"; | ||
| 25 | import { Textarea } from "../components/textarea"; | ||
| 26 | import { FormErrorAlert } from "../lib/integration-add"; | ||
| 27 | import { useExecutorDocumentTitle } from "../lib/document-title"; | ||
| 28 | import { | ||
| 29 | connectionOwnerOptionsForAccess, | ||
| 30 | ConnectionOwnerDropdown, | ||
| 31 | defaultConnectionOwnerForHost, | ||
| 32 | normalizeConnectionOwner, | ||
| 33 | } from "../plugins/connection-owner"; | ||
| 34 | import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav"; | ||
| 35 | |||
| 36 | /** A skill that already parses, so the validation panel is green from the first | ||
| 37 | * keystroke and the author edits a working example instead of guessing. */ | ||
| 38 | const NEW_SKILL_TEMPLATE = `--- | ||
| 39 | name: my-skill | ||
| 40 | description: What this skill does and when an agent should load it. | ||
| 41 | --- | ||
| 42 | |||
| 43 | # My skill | ||
| 44 | |||
| 45 | Write the instructions an agent should follow here. | ||
| 46 | |||
| 47 | ## Steps | ||
| 48 | |||
| 49 | 1. ... | ||
| 50 | `; | ||
| 51 | |||
| 52 | /** Files above this are almost never instructions, and the whole skill is | ||
| 53 | * capped at 1 MiB anyway — better to say which file was dropped than to fail | ||
| 54 | * the save with a total-size error. */ | ||
| 55 | const MAX_IMPORT_FILE_BYTES = 512 * 1024; | ||
| 56 | |||
| 57 | const SkillErrorReason = Schema.Struct({ reason: Schema.String }); | ||
| 58 | const decodeReason = Schema.decodeUnknownOption(SkillErrorReason); | ||
| 59 | |||
| 60 | /** `InvalidSkillError` carries the actionable sentence in `reason`; its | ||
| 61 | * `message` prefixes it with "Invalid skill:", which the alert's placement | ||
| 62 | * already says. Everything else (org-write denied, transport) uses `message`. */ | ||
| 63 | const saveErrorMessage = (exit: Exit.Exit<unknown, unknown>): string => | ||
| 64 | Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeReason), { | ||
| 65 | onNone: () => messageFromExit(exit, "Couldn't save the skill. Try again."), | ||
| 66 | onSome: ({ reason }) => reason, | ||
| 67 | }); | ||
| 68 | |||
| 69 | interface ExtraFileRow { | ||
| 70 | /** Stable across edits so a row keeps focus while its path is retyped. */ | ||
| 71 | readonly id: number; | ||
| 72 | readonly path: string; | ||
| 73 | readonly content: string; | ||
| 74 | } | ||
| 75 | |||
| 76 | interface EditorSeed { | ||
| 77 | readonly owner: Owner | null; | ||
| 78 | readonly skillMd: string; | ||
| 79 | readonly extras: readonly ExtraFileRow[]; | ||
| 80 | /** The `(owner, name)` this editor opened, when it is editing one. */ | ||
| 81 | readonly original: { readonly owner: Owner; readonly name: string } | null; | ||
| 82 | } | ||
| 83 | |||
| 84 | export function SkillEditorPage(props: { | ||
| 85 | readonly editing?: { readonly owner: Owner; readonly name: SkillName } | undefined; | ||
| 86 | }) { | ||
| 87 | if (props.editing === undefined) { | ||
| 88 | return ( | ||
| 89 | <SkillEditorForm | ||
| 90 | seed={{ owner: null, skillMd: NEW_SKILL_TEMPLATE, extras: [], original: null }} | ||
| 91 | /> | ||
| 92 | ); | ||
| 93 | } | ||
| 94 | return <SkillEditorLoader editing={props.editing} />; | ||
| 95 | } | ||
| 96 | |||
| 97 | /** Editing needs the stored file CONTENTS, which only `skills.get` returns. The | ||
| 98 | * form is mounted once the row lands so its state can be seeded directly, | ||
| 99 | * rather than syncing an effect against a changing fetch. */ | ||
| 100 | function SkillEditorLoader(props: { | ||
| 101 | readonly editing: { readonly owner: Owner; readonly name: SkillName }; | ||
| 102 | }) { | ||
| 103 | const ref = useMemo( | ||
| 104 | () => ({ owner: props.editing.owner, name: props.editing.name }), | ||
| 105 | [props.editing.owner, props.editing.name], | ||
| 106 | ); | ||
| 107 | const skill = useAtomValue(skillAtom(ref)); | ||
| 108 | |||
| 109 | if (!AsyncResult.isSuccess(skill)) { | ||
| 110 | return ( | ||
| 111 | <PageContainer> | ||
| 112 | <p className="text-sm text-muted-foreground"> | ||
| 113 | {AsyncResult.isFailure(skill) ? "This skill isn't available." : "Loading skill…"} | ||
| 114 | </p> | ||
| 115 | </PageContainer> | ||
| 116 | ); | ||
| 117 | } | ||
| 118 | |||
| 119 | const files = skill.value.files as ReadonlyArray<{ | ||
| 120 | readonly path: string; | ||
| 121 | readonly content: string; | ||
| 122 | }>; | ||
| 123 | const skillMd = files.find((file) => file.path === SKILL_MD_PATH); | ||
| 124 | return ( | ||
| 125 | <SkillEditorForm | ||
| 126 | seed={{ | ||
| 127 | owner: skill.value.owner, | ||
| 128 | skillMd: skillMd?.content ?? NEW_SKILL_TEMPLATE, | ||
| 129 | extras: files | ||
| 130 | .filter((file) => file.path !== SKILL_MD_PATH) | ||
| 131 | .map((file, index) => ({ id: index, path: file.path, content: file.content })), | ||
| 132 | original: { owner: skill.value.owner, name: skill.value.name }, | ||
| 133 | }} | ||
| 134 | /> | ||
| 135 | ); | ||
| 136 | } | ||
| 137 | |||
| 138 | function SkillEditorForm(props: { readonly seed: EditorSeed }) { | ||
| 139 | const { seed } = props; | ||
| 140 | const editing = seed.original !== null; | ||
| 141 | useExecutorDocumentTitle(editing ? `Edit ${seed.original?.name ?? ""}` : "New skill"); | ||
| 142 | |||
| 143 | const organizationId = useOrganizationId(); | ||
| 144 | const canCreateWorkspaceConnections = useCanCreateWorkspaceConnections(); | ||
| 145 | const ownerOptions = connectionOwnerOptionsForAccess( | ||
| 146 | organizationId, | ||
| 147 | canCreateWorkspaceConnections, | ||
| 148 | ); | ||
| 149 | const [owner, setOwner] = useState<Owner>( | ||
| 150 | seed.owner ?? defaultConnectionOwnerForHost(organizationId), | ||
| 151 | ); | ||
| 152 | const [skillMd, setSkillMd] = useState(seed.skillMd); | ||
| 153 | const [extras, setExtras] = useState<readonly ExtraFileRow[]>(seed.extras); | ||
| 154 | const [saving, setSaving] = useState(false); | ||
| 155 | const [error, setError] = useState<string | null>(null); | ||
| 156 | const [importNotice, setImportNotice] = useState<string | null>(null); | ||
| 157 | const nextRowId = useRef(seed.extras.length); | ||
| 158 | const folderInput = useRef<HTMLInputElement | null>(null); | ||
| 159 | const navigate = useNavigate(); | ||
| 160 | |||
| 161 | // `webkitdirectory` is what turns a file input into a folder picker. React's | ||
| 162 | // typings don't declare it, so it is set on the element rather than in JSX. | ||
| 163 | useEffect(() => { | ||
| 164 | folderInput.current?.setAttribute("webkitdirectory", ""); | ||
| 165 | }, []); | ||
| 166 | |||
| 167 | // Always keep the selection valid against the options this host offers — on | ||
| 168 | // local there is exactly one, and the picker hides itself. | ||
| 169 | const activeOwner = normalizeConnectionOwner(owner, ownerOptions); | ||
| 170 | const parsed = useMemo(() => parseSkillMarkdown(skillMd), [skillMd]); | ||
| 171 | const parsedName = Result.isSuccess(parsed) ? parsed.success.name : null; | ||
| 172 | // Identity is `(owner, name)` and `save` reads the name out of the file, so | ||
| 173 | // retitling the frontmatter or moving the owner writes a SECOND skill and | ||
| 174 | // leaves this one alone. Say that before the save, not after. | ||
| 175 | const forksExisting = | ||
| 176 | seed.original !== null && | ||
| 177 | parsedName !== null && | ||
| 178 | (parsedName !== seed.original.name || activeOwner !== seed.original.owner); | ||
| 179 | |||
| 180 | const doSave = useAtomSet(saveSkill, { mode: "promiseExit" }); | ||
| 181 | |||
| 182 | const addRow = () => { | ||
| 183 | setExtras((rows) => [...rows, { id: nextRowId.current++, path: "", content: "" }]); | ||
| 184 | }; | ||
| 185 | |||
| 186 | const updateRow = (id: number, patch: Partial<Omit<ExtraFileRow, "id">>) => { | ||
| 187 | setExtras((rows) => rows.map((row) => (row.id === id ? { ...row, ...patch } : row))); | ||
| 188 | }; | ||
| 189 | |||
| 190 | const removeRow = (id: number) => { | ||
| 191 | setExtras((rows) => rows.filter((row) => row.id !== id)); | ||
| 192 | }; | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Read a picked folder into the editor. | ||
| 196 | * | ||
| 197 | * A folder pick reports every file as `<folder>/<path>`, so the wrapping | ||
| 198 | * segment is stripped — otherwise SKILL.md would land at `my-skill/SKILL.md` | ||
| 199 | * and the save would reject a skill with no root file. Binary and oversized | ||
| 200 | * files are dropped rather than mangled into UTF-8, and dotfiles go with them | ||
| 201 | * (`.DS_Store` is in every folder a Mac has ever opened). | ||
| 202 | */ | ||
| 203 | const importFolder = async (picked: FileList) => { | ||
| 204 | const chosen = Array.from(picked); | ||
| 205 | const relative = chosen.map((file) => file.webkitRelativePath || file.name); | ||
| 206 | const topSegments = new Set(relative.map((path) => path.split("/")[0])); | ||
| 207 | const strip = topSegments.size === 1 && relative.every((path) => path.includes("/")); | ||
| 208 | |||
| 209 | const imported: Array<{ path: string; content: string }> = []; | ||
| 210 | const skipped: string[] = []; | ||
| 211 | for (const [index, file] of chosen.entries()) { | ||
| 212 | const raw = relative[index] ?? file.name; | ||
| 213 | const path = strip ? raw.split("/").slice(1).join("/") : raw; | ||
| 214 | if ( | ||
| 215 | path === "" || | ||
| 216 | !isValidSkillFilePath(path) || | ||
| 217 | path.split("/").some((segment) => segment.startsWith(".")) || | ||
| 218 | file.size > MAX_IMPORT_FILE_BYTES | ||
| 219 | ) { | ||
| 220 | skipped.push(raw); | ||
| 221 | continue; | ||
| 222 | } | ||
| 223 | const content = await file.text(); | ||
| 224 | // A NUL byte is the cheap, reliable tell that this was never text. | ||
| 225 | if (content.includes("\u0000")) { | ||
| 226 | skipped.push(raw); | ||
| 227 | continue; | ||
| 228 | } | ||
| 229 | imported.push({ path, content }); | ||
| 230 | } | ||
| 231 | |||
| 232 | const rootFile = imported.find((file) => file.path === SKILL_MD_PATH); | ||
| 233 | if (rootFile) setSkillMd(rootFile.content); | ||
| 234 | setExtras( | ||
| 235 | imported | ||
| 236 | .filter((file) => file.path !== SKILL_MD_PATH) | ||
| 237 | .map((file) => ({ id: nextRowId.current++, path: file.path, content: file.content })), | ||
| 238 | ); | ||
| 239 | |||
| 240 | const parts = [`Imported ${imported.length} ${imported.length === 1 ? "file" : "files"}.`]; | ||
| 241 | if (!rootFile) parts.push("No SKILL.md in that folder — write one below."); | ||
| 242 | if (skipped.length > 0) { | ||
| 243 | parts.push( | ||
| 244 | `Skipped ${skipped.length} (binary, hidden, or over 512 KiB): ${skipped.slice(0, 5).join(", ")}${skipped.length > 5 ? "…" : ""}.`, | ||
| 245 | ); | ||
| 246 | } | ||
| 247 | setImportNotice(parts.join(" ")); | ||
| 248 | }; | ||
| 249 | |||
| 250 | const submit = async () => { | ||
| 251 | if (!Result.isSuccess(parsed) || saving) return; | ||
| 252 | setSaving(true); | ||
| 253 | setError(null); | ||
| 254 | const exit = await doSave({ | ||
| 255 | payload: { | ||
| 256 | owner: activeOwner, | ||
| 257 | files: [ | ||
| 258 | { path: SKILL_MD_PATH, content: skillMd }, | ||
| 259 | ...extras | ||
| 260 | .filter((row) => row.path.trim() !== "") | ||
| 261 | .map((row) => ({ path: row.path.trim(), content: row.content })), | ||
| 262 | ], | ||
| 263 | }, | ||
| 264 | reactivityKeys: skillWriteKeys, | ||
| 265 | }); | ||
| 266 | setSaving(false); | ||
| 267 | if (Exit.isFailure(exit)) { | ||
| 268 | setError(saveErrorMessage(exit)); | ||
| 269 | return; | ||
| 270 | } | ||
| 271 | // `params` names the saved row, not the one the editor opened — the save | ||
| 272 | // may have written a different name than the URL carried. | ||
| 273 | await navigate({ | ||
| 274 | to: "/{-$orgSlug}/skills/$skillOwner/$skillName", | ||
| 275 | params: { skillOwner: exit.value.owner, skillName: exit.value.name }, | ||
| 276 | search: {}, | ||
| 277 | }); | ||
| 278 | }; | ||
| 279 | |||
| 280 | return ( | ||
| 281 | <PageContainer> | ||
| 282 | <Button asChild variant="ghost" size="sm" className="-ml-3 mb-4 text-muted-foreground"> | ||
| 283 | <Link to="/{-$orgSlug}/skills">Skills</Link> | ||
| 284 | </Button> | ||
| 285 | |||
| 286 | <PageHeader | ||
| 287 | title={editing ? "Edit skill" : "New skill"} | ||
| 288 | description={ | ||
| 289 | editing | ||
| 290 | ? "Change the instructions or the files that travel with them. Agents pick the new version up on their next load." | ||
| 291 | : "Write a SKILL.md, or import a folder you already have. Every agent connected to this workspace can load it by name." | ||
| 292 | } | ||
| 293 | actions={ | ||
| 294 | <Button | ||
| 295 | type="button" | ||
| 296 | size="sm" | ||
| 297 | loading={saving} | ||
| 298 | disabled={!Result.isSuccess(parsed)} | ||
| 299 | onClick={() => void submit()} | ||
| 300 | > | ||
| 301 | Save Skill | ||
| 302 | </Button> | ||
| 303 | } | ||
| 304 | /> | ||
| 305 | |||
| 306 | <div className="space-y-8"> | ||
| 307 | {error ? <FormErrorAlert message={error} /> : null} | ||
| 308 | |||
| 309 | <ConnectionOwnerDropdown | ||
| 310 | value={activeOwner} | ||
| 311 | options={ownerOptions} | ||
| 312 | onChange={setOwner} | ||
| 313 | label="Shared with" | ||
| 314 | help="Personal skills are yours alone. Workspace skills load for every agent in this workspace." | ||
| 315 | className="max-w-xs space-y-1.5" | ||
| 316 | /> | ||
| 317 | |||
| 318 | <section className="space-y-2"> | ||
| 319 | <div className="flex items-end justify-between gap-3"> | ||
| 320 | <div> | ||
| 321 | <FieldLabel className="text-[11px]">SKILL.md</FieldLabel> | ||
| 322 | <p className="mt-1 text-xs text-muted-foreground"> | ||
| 323 | YAML frontmatter with <span className="font-mono">name</span> and{" "} | ||
| 324 | <span className="font-mono">description</span>, then the instructions. | ||
| 325 | </p> | ||
| 326 | </div> | ||
| 327 | <Button | ||
| 328 | type="button" | ||
| 329 | variant="outline" | ||
| 330 | size="sm" | ||
| 331 | onClick={() => folderInput.current?.click()} | ||
| 332 | > | ||
| 333 | Import Folder | ||
| 334 | </Button> | ||
| 335 | <Input | ||
| 336 | ref={folderInput} | ||
| 337 | type="file" | ||
| 338 | multiple | ||
| 339 | className="hidden" | ||
| 340 | onChange={(event) => { | ||
| 341 | const picked = event.target.files; | ||
| 342 | if (picked && picked.length > 0) void importFolder(picked); | ||
| 343 | // Clear it so picking the SAME folder again still fires. | ||
| 344 | event.target.value = ""; | ||
| 345 | }} | ||
| 346 | /> | ||
| 347 | </div> | ||
| 348 | {importNotice ? <p className="text-xs text-muted-foreground">{importNotice}</p> : null} | ||
| 349 | <Textarea | ||
| 350 | value={skillMd} | ||
| 351 | spellCheck={false} | ||
| 352 | onChange={(event) => setSkillMd(event.target.value)} | ||
| 353 | className="min-h-96 font-mono text-[12px] leading-relaxed" | ||
| 354 | aria-label="SKILL.md" | ||
| 355 | /> | ||
| 356 | <ValidationPanel parsed={parsed} forksExisting={forksExisting} original={seed.original} /> | ||
| 357 | </section> | ||
| 358 | |||
| 359 | <section className="space-y-2"> | ||
| 360 | <div className="flex items-end justify-between gap-3"> | ||
| 361 | <div> | ||
| 362 | <FieldLabel className="text-[11px]">Additional files</FieldLabel> | ||
| 363 | <p className="mt-1 text-xs text-muted-foreground"> | ||
| 364 | References and scripts the instructions point at, by relative path. | ||
| 365 | </p> | ||
| 366 | </div> | ||
| 367 | <Button type="button" variant="outline" size="sm" onClick={addRow}> | ||
| 368 | Add File | ||
| 369 | </Button> | ||
| 370 | </div> | ||
| 371 | {extras.length === 0 ? ( | ||
| 372 | <p className="rounded-lg border border-dashed border-border px-4 py-6 text-center text-xs text-muted-foreground"> | ||
| 373 | No additional files. A SKILL.md on its own is a complete skill. | ||
| 374 | </p> | ||
| 375 | ) : ( | ||
| 376 | <ul className="space-y-3"> | ||
| 377 | {extras.map((row) => ( | ||
| 378 | <li key={row.id} className="rounded-lg border border-border bg-card p-3"> | ||
| 379 | <div className="flex items-center gap-2"> | ||
| 380 | <Input | ||
| 381 | value={row.path} | ||
| 382 | spellCheck={false} | ||
| 383 | placeholder="references/api.md" | ||
| 384 | onChange={(event) => updateRow(row.id, { path: event.target.value })} | ||
| 385 | className="font-mono text-[12px]" | ||
| 386 | aria-label="File path" | ||
| 387 | /> | ||
| 388 | <Button | ||
| 389 | type="button" | ||
| 390 | variant="ghost" | ||
| 391 | size="sm" | ||
| 392 | className="text-muted-foreground hover:text-destructive" | ||
| 393 | onClick={() => removeRow(row.id)} | ||
| 394 | > | ||
| 395 | Remove | ||
| 396 | </Button> | ||
| 397 | </div> | ||
| 398 | <Textarea | ||
| 399 | value={row.content} | ||
| 400 | spellCheck={false} | ||
| 401 | onChange={(event) => updateRow(row.id, { content: event.target.value })} | ||
| 402 | className="mt-2 min-h-32 font-mono text-[12px] leading-relaxed" | ||
| 403 | aria-label={`Contents of ${row.path || "file"}`} | ||
| 404 | /> | ||
| 405 | </li> | ||
| 406 | ))} | ||
| 407 | </ul> | ||
| 408 | )} | ||
| 409 | </section> | ||
| 410 | </div> | ||
| 411 | </PageContainer> | ||
| 412 | ); | ||
| 413 | } | ||
| 414 | |||
| 415 | /** What the server will read out of the file, shown while it is being typed — | ||
| 416 | * the frontmatter is the only part of a SKILL.md with rules, and finding out | ||
| 417 | * it is wrong at save time is the slowest possible way to learn it. */ | ||
| 418 | function ValidationPanel(props: { | ||
| 419 | readonly parsed: ReturnType<typeof parseSkillMarkdown>; | ||
| 420 | readonly forksExisting: boolean; | ||
| 421 | readonly original: { readonly owner: Owner; readonly name: string } | null; | ||
| 422 | }) { | ||
| 423 | if (Result.isFailure(props.parsed)) { | ||
| 424 | return ( | ||
| 425 | <div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2"> | ||
| 426 | <p className="text-[12px] text-destructive">{props.parsed.failure.reason}</p> | ||
| 427 | </div> | ||
| 428 | ); | ||
| 429 | } | ||
| 430 | const { name, description } = props.parsed.success; | ||
| 431 | return ( | ||
| 432 | <div className="rounded-lg border border-border bg-muted/40 px-3 py-2.5"> | ||
| 433 | <dl className="space-y-1"> | ||
| 434 | <div className="flex gap-3"> | ||
| 435 | <dt className="w-24 shrink-0 font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 436 | Name | ||
| 437 | </dt> | ||
| 438 | <dd className="font-mono text-[12px] text-foreground">{name}</dd> | ||
| 439 | </div> | ||
| 440 | <div className="flex gap-3"> | ||
| 441 | <dt className="w-24 shrink-0 font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 442 | Description | ||
| 443 | </dt> | ||
| 444 | <dd className="min-w-0 flex-1 text-[12px] text-muted-foreground">{description}</dd> | ||
| 445 | </div> | ||
| 446 | </dl> | ||
| 447 | {props.forksExisting && props.original ? ( | ||
| 448 | <p className="mt-2 border-t border-border pt-2 text-[12px] text-destructive"> | ||
| 449 | Saving this creates a new skill. The original,{" "} | ||
| 450 | <span className="font-mono"> | ||
| 451 | {props.original.owner}/{props.original.name} | ||
| 452 | </span> | ||
| 453 | , stays as it is. | ||
| 454 | </p> | ||
| 455 | ) : null} | ||
| 456 | </div> | ||
| 457 | ); | ||
| 458 | } | ||
| @@ -0,0 +1,159 @@ | |||
| 1 | import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; | ||
| 2 | import { Link } from "@tanstack/react-router"; | ||
| 3 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; | ||
| 4 | import type { Owner } from "@executor-js/sdk/shared"; | ||
| 5 | |||
| 6 | import { skillsAtom } from "../api/atoms"; | ||
| 7 | import { useOrganizationId } from "../api/organization-context"; | ||
| 8 | import { Button } from "../components/button"; | ||
| 9 | import { ErrorState } from "../components/error-state"; | ||
| 10 | import { PageContainer, PageHeader } from "../components/page"; | ||
| 11 | import { isAsyncResultLoading } from "../lib/async-result"; | ||
| 12 | import { useExecutorDocumentTitle } from "../lib/document-title"; | ||
| 13 | import { formatRelativeTime } from "../lib/relative-time"; | ||
| 14 | import { connectionOwnerOptionsForHost } from "../plugins/connection-owner"; | ||
| 15 | |||
| 16 | /** The wire row `skills.list` returns — the manifest, without file contents. */ | ||
| 17 | interface SkillSummaryRow { | ||
| 18 | readonly owner: Owner; | ||
| 19 | readonly name: string; | ||
| 20 | readonly description: string; | ||
| 21 | readonly files: ReadonlyArray<{ readonly path: string }>; | ||
| 22 | readonly updatedAt: number; | ||
| 23 | } | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Personal / Workspace / Local, in the host's own words. | ||
| 27 | * | ||
| 28 | * A skill is owner-scoped exactly like a connection, so it says "Workspace" | ||
| 29 | * where connections say "Workspace" and "Local" on a host that has one bucket — | ||
| 30 | * two vocabularies for the same `Owner` would be a lie about what it means. | ||
| 31 | */ | ||
| 32 | export function useSkillOwnerLabel(): (owner: Owner) => string { | ||
| 33 | const options = connectionOwnerOptionsForHost(useOrganizationId()); | ||
| 34 | return (owner) => | ||
| 35 | options.find((option) => option.owner === owner)?.label ?? | ||
| 36 | (owner === "org" ? "Workspace" : "Personal"); | ||
| 37 | } | ||
| 38 | |||
| 39 | /** The owner badge: a mono chip, since the owner is metadata about the skill. */ | ||
| 40 | export function SkillOwnerTag(props: { readonly owner: Owner }) { | ||
| 41 | const label = useSkillOwnerLabel(); | ||
| 42 | return ( | ||
| 43 | <span className="rounded-sm border border-border bg-secondary px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground"> | ||
| 44 | {label(props.owner)} | ||
| 45 | </span> | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | const LoadingState = () => ( | ||
| 50 | <div className="flex items-center gap-2 py-8"> | ||
| 51 | <div className="size-1.5 animate-pulse rounded-full bg-muted-foreground/30" /> | ||
| 52 | <p className="text-sm text-muted-foreground">Loading skills…</p> | ||
| 53 | </div> | ||
| 54 | ); | ||
| 55 | |||
| 56 | /** Skills are the one workspace resource a PERSON authors, so the empty state | ||
| 57 | * says what one is and what adding it does, then points at the button. */ | ||
| 58 | const EmptyState = () => ( | ||
| 59 | <div | ||
| 60 | data-slot="skill-empty" | ||
| 61 | className="rounded-lg border border-dashed border-border px-6 py-16 text-center" | ||
| 62 | > | ||
| 63 | <p className="mx-auto max-w-md text-sm leading-relaxed text-muted-foreground"> | ||
| 64 | No skills yet. A skill is a SKILL.md file — instructions an agent loads on demand, plus any | ||
| 65 | files it references. Add one and every agent connected to this workspace can find it by name. | ||
| 66 | </p> | ||
| 67 | </div> | ||
| 68 | ); | ||
| 69 | |||
| 70 | function SkillRow(props: { readonly skill: SkillSummaryRow }) { | ||
| 71 | const { skill } = props; | ||
| 72 | return ( | ||
| 73 | <li className="group/skill-row relative isolate flex items-start justify-between gap-4 px-4 py-3.5 transition-colors duration-150 focus-within:bg-accent/40 hover:bg-accent/40"> | ||
| 74 | <div className="min-w-0 flex-1"> | ||
| 75 | <div className="flex items-center gap-2"> | ||
| 76 | {/* The whole row is the target; the link overlays it so the row keeps | ||
| 77 | one accessible name and one focus stop. */} | ||
| 78 | <h3 className="truncate font-mono text-sm text-foreground"> | ||
| 79 | <Link | ||
| 80 | to="/{-$orgSlug}/skills/$skillOwner/$skillName" | ||
| 81 | params={{ skillOwner: skill.owner, skillName: skill.name }} | ||
| 82 | className="outline-none after:absolute after:inset-0 after:content-[''] focus-visible:underline" | ||
| 83 | > | ||
| 84 | {skill.name} | ||
| 85 | </Link> | ||
| 86 | </h3> | ||
| 87 | <SkillOwnerTag owner={skill.owner} /> | ||
| 88 | </div> | ||
| 89 | <p className="mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground"> | ||
| 90 | {skill.description} | ||
| 91 | </p> | ||
| 92 | </div> | ||
| 93 | <div className="flex shrink-0 flex-col items-end gap-1 font-mono text-[11px] text-muted-foreground"> | ||
| 94 | <span className="tabular-nums"> | ||
| 95 | {skill.files.length} {skill.files.length === 1 ? "file" : "files"} | ||
| 96 | </span> | ||
| 97 | <span>{formatRelativeTime(skill.updatedAt)}</span> | ||
| 98 | </div> | ||
| 99 | </li> | ||
| 100 | ); | ||
| 101 | } | ||
| 102 | |||
| 103 | export function SkillsPage() { | ||
| 104 | useExecutorDocumentTitle("Skills"); | ||
| 105 | const skills = useAtomValue(skillsAtom); | ||
| 106 | const refresh = useAtomRefresh(skillsAtom); | ||
| 107 | |||
| 108 | return ( | ||
| 109 | <PageContainer> | ||
| 110 | <PageHeader | ||
| 111 | title="Skills" | ||
| 112 | description="SKILL.md instructions your agents can load on demand. Save one here and every agent connected to this workspace discovers it — personally, or shared with everyone." | ||
| 113 | actions={ | ||
| 114 | <Button asChild size="sm"> | ||
| 115 | <Link to="/{-$orgSlug}/skills/new">Add Skill</Link> | ||
| 116 | </Button> | ||
| 117 | } | ||
| 118 | /> | ||
| 119 | |||
| 120 | {isAsyncResultLoading(skills) ? ( | ||
| 121 | <LoadingState /> | ||
| 122 | ) : ( | ||
| 123 | AsyncResult.match(skills, { | ||
| 124 | onInitial: () => <LoadingState />, | ||
| 125 | onFailure: () => <ErrorState message="Failed to load skills" onRetry={refresh} />, | ||
| 126 | onSuccess: ({ value }) => { | ||
| 127 | // Workspace skills first, then by name: the shared set is the one a | ||
| 128 | // reader is checking against, and name order makes it scannable. | ||
| 129 | const rows = [...value].sort((a, b) => | ||
| 130 | a.owner === b.owner ? a.name.localeCompare(b.name) : a.owner === "org" ? -1 : 1, | ||
| 131 | ); | ||
| 132 | return ( | ||
| 133 | <section> | ||
| 134 | <h2 className="mb-3 flex items-center font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> | ||
| 135 | Saved skills | ||
| 136 | {rows.length > 0 ? ( | ||
| 137 | <span className="ml-2 font-normal tabular-nums">{rows.length}</span> | ||
| 138 | ) : null} | ||
| 139 | </h2> | ||
| 140 | {rows.length === 0 ? ( | ||
| 141 | <EmptyState /> | ||
| 142 | ) : ( | ||
| 143 | <ul | ||
| 144 | data-slot="skill-list" | ||
| 145 | className="divide-y divide-border overflow-hidden rounded-lg border border-border bg-card" | ||
| 146 | > | ||
| 147 | {rows.map((skill) => ( | ||
| 148 | <SkillRow key={`${skill.owner}/${skill.name}`} skill={skill} /> | ||
| 149 | ))} | ||
| 150 | </ul> | ||
| 151 | )} | ||
| 152 | </section> | ||
| 153 | ); | ||
| 154 | }, | ||
| 155 | }) | ||
| 156 | )} | ||
| 157 | </PageContainer> | ||
| 158 | ); | ||
| 159 | } | ||
| @@ -13,15 +13,18 @@ import { Route as DotIndexRouteImport } from './index' · 12 unmodified lines | |||
| 13 | import { Route as DotUsersRouteImport } from './users' | 13 | import { Route as DotUsersRouteImport } from './users' |
| 14 | import { Route as DotToolsRouteImport } from './tools' | 14 | import { Route as DotToolsRouteImport } from './tools' |
| 15 | import { Route as DotToolkitsRouteImport } from './toolkits' | 15 | import { Route as DotToolkitsRouteImport } from './toolkits' |
| 16 | import { Route as DotSkillsRouteImport } from './skills' | ||
| 16 | import { Route as DotSecretsRouteImport } from './secrets' | 17 | import { Route as DotSecretsRouteImport } from './secrets' |
| 17 | import { Route as DotPoliciesRouteImport } from './policies' | 18 | import { Route as DotPoliciesRouteImport } from './policies' |
| 18 | import { Route as DotArtifactsRouteImport } from './artifacts' | 19 | import { Route as DotArtifactsRouteImport } from './artifacts' |
| 19 | import { Route as DotToolkitsDottoolkitSlugRouteImport } from './toolkits.$toolkitSlug' | 20 | import { Route as DotToolkitsDottoolkitSlugRouteImport } from './toolkits.$toolkitSlug' |
| 21 | import { Route as DotSkillsDotnewRouteImport } from './skills.new' | ||
| 20 | import { Route as DotResumeDotexecutionIdRouteImport } from './resume.$executionId' | 22 | import { Route as DotResumeDotexecutionIdRouteImport } from './resume.$executionId' |
| 21 | import { Route as DotIntegrationsDotbrowseRouteImport } from './integrations.browse' | 23 | import { Route as DotIntegrationsDotbrowseRouteImport } from './integrations.browse' |
| 22 | import { Route as DotIntegrationsDotnamespaceRouteImport } from './integrations.$namespace' | 24 | import { Route as DotIntegrationsDotnamespaceRouteImport } from './integrations.$namespace' |
| 23 | import { Route as DotConnectDotintegrationSlugRouteImport } from './connect.$integrationSlug' | 25 | import { Route as DotConnectDotintegrationSlugRouteImport } from './connect.$integrationSlug' |
| 24 | import { Route as DotArtifactsDotartifactIdRouteImport } from './artifacts.$artifactId' | 26 | import { Route as DotArtifactsDotartifactIdRouteImport } from './artifacts.$artifactId' |
| 27 | import { Route as DotSkillsDotskillOwnerDotskillNameRouteImport } from './skills.$skillOwner.$skillName' | ||
| 25 | import { Route as DotPluginsDotpluginIdDotsplatRouteImport } from './plugins.$pluginId.$' | 28 | import { Route as DotPluginsDotpluginIdDotsplatRouteImport } from './plugins.$pluginId.$' |
| 26 | import { Route as DotIntegrationsDotaddDotpluginKeyRouteImport } from './integrations.add.$pluginKey' | 29 | import { Route as DotIntegrationsDotaddDotpluginKeyRouteImport } from './integrations.add.$pluginKey' |
| 27 | 30 | ||
| @@ -45,6 +48,11 @@ const DotToolkitsRoute = DotToolkitsRouteImport.update({ · 17 unmodified lines | |||
| 45 | path: '/{-$orgSlug}/toolkits', | 48 | path: '/{-$orgSlug}/toolkits', |
| 46 | getParentRoute: () => rootRouteImport, | 49 | getParentRoute: () => rootRouteImport, |
| 47 | } as any) | 50 | } as any) |
| 51 | const DotSkillsRoute = DotSkillsRouteImport.update({ | ||
| 52 | id: '/{-$orgSlug}/skills', | ||
| 53 | path: '/{-$orgSlug}/skills', | ||
| 54 | getParentRoute: () => rootRouteImport, | ||
| 55 | } as any) | ||
| 48 | const DotSecretsRoute = DotSecretsRouteImport.update({ | 56 | const DotSecretsRoute = DotSecretsRouteImport.update({ |
| 49 | id: '/{-$orgSlug}/secrets', | 57 | id: '/{-$orgSlug}/secrets', |
| 50 | path: '/{-$orgSlug}/secrets', | 58 | path: '/{-$orgSlug}/secrets', |
| @@ -66,6 +74,11 @@ const DotToolkitsDottoolkitSlugRoute = · 15 unmodified lines | |||
| 66 | path: '/$toolkitSlug', | 74 | path: '/$toolkitSlug', |
| 67 | getParentRoute: () => DotToolkitsRoute, | 75 | getParentRoute: () => DotToolkitsRoute, |
| 68 | } as any) | 76 | } as any) |
| 77 | const DotSkillsDotnewRoute = DotSkillsDotnewRouteImport.update({ | ||
| 78 | id: '/new', | ||
| 79 | path: '/new', | ||
| 80 | getParentRoute: () => DotSkillsRoute, | ||
| 81 | } as any) | ||
| 69 | const DotResumeDotexecutionIdRoute = DotResumeDotexecutionIdRouteImport.update({ | 82 | const DotResumeDotexecutionIdRoute = DotResumeDotexecutionIdRouteImport.update({ |
| 70 | id: '/{-$orgSlug}/resume/$executionId', | 83 | id: '/{-$orgSlug}/resume/$executionId', |
| 71 | path: '/{-$orgSlug}/resume/$executionId', | 84 | path: '/{-$orgSlug}/resume/$executionId', |
| @@ -95,6 +108,12 @@ const DotArtifactsDotartifactIdRoute = · 23 unmodified lines | |||
| 95 | path: '/$artifactId', | 108 | path: '/$artifactId', |
| 96 | getParentRoute: () => DotArtifactsRoute, | 109 | getParentRoute: () => DotArtifactsRoute, |
| 97 | } as any) | 110 | } as any) |
| 111 | const DotSkillsDotskillOwnerDotskillNameRoute = | ||
| 112 | DotSkillsDotskillOwnerDotskillNameRouteImport.update({ | ||
| 113 | id: '/$skillOwner/$skillName', | ||
| 114 | path: '/$skillOwner/$skillName', | ||
| 115 | getParentRoute: () => DotSkillsRoute, | ||
| 116 | } as any) | ||
| 98 | const DotPluginsDotpluginIdDotsplatRoute = | 117 | const DotPluginsDotpluginIdDotsplatRoute = |
| 99 | DotPluginsDotpluginIdDotsplatRouteImport.update({ | 118 | DotPluginsDotpluginIdDotsplatRouteImport.update({ |
| 100 | id: '/{-$orgSlug}/plugins/$pluginId/$', | 119 | id: '/{-$orgSlug}/plugins/$pluginId/$', |
| @@ -112,6 +131,7 @@ export interface FileRoutesByFullPath { · 11 unmodified lines | |||
| 112 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren | 131 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren |
| 113 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute | 132 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute |
| 114 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute | 133 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute |
| 134 | '/{-$orgSlug}/skills': typeof DotSkillsRouteWithChildren | ||
| 115 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 135 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 116 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 136 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 117 | '/{-$orgSlug}/users': typeof DotUsersRoute | 137 | '/{-$orgSlug}/users': typeof DotUsersRoute |
| @@ -121,14 +141,17 @@ export interface FileRoutesByFullPath { · 3 unmodified lines | |||
| 121 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 141 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 122 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute | 142 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute |
| 123 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 143 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 144 | '/{-$orgSlug}/skills/new': typeof DotSkillsDotnewRoute | ||
| 124 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 145 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| 125 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute | 146 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute |
| 126 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute | 147 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute |
| 148 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotSkillsDotskillOwnerDotskillNameRoute | ||
| 127 | } | 149 | } |
| 128 | export interface FileRoutesByTo { | 150 | export interface FileRoutesByTo { |
| 129 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren | 151 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren |
| 130 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute | 152 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute |
| 131 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute | 153 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute |
| 154 | '/{-$orgSlug}/skills': typeof DotSkillsRouteWithChildren | ||
| 132 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 155 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 133 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 156 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 134 | '/{-$orgSlug}/users': typeof DotUsersRoute | 157 | '/{-$orgSlug}/users': typeof DotUsersRoute |
| @@ -138,15 +161,18 @@ export interface FileRoutesByTo { · 3 unmodified lines | |||
| 138 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 161 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 139 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute | 162 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute |
| 140 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 163 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 164 | '/{-$orgSlug}/skills/new': typeof DotSkillsDotnewRoute | ||
| 141 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 165 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| 142 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute | 166 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute |
| 143 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute | 167 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute |
| 168 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotSkillsDotskillOwnerDotskillNameRoute | ||
| 144 | } | 169 | } |
| 145 | export interface FileRoutesById { | 170 | export interface FileRoutesById { |
| 146 | __root__: typeof rootRouteImport | 171 | __root__: typeof rootRouteImport |
| 147 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren | 172 | '/{-$orgSlug}/artifacts': typeof DotArtifactsRouteWithChildren |
| 148 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute | 173 | '/{-$orgSlug}/policies': typeof DotPoliciesRoute |
| 149 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute | 174 | '/{-$orgSlug}/secrets': typeof DotSecretsRoute |
| 175 | '/{-$orgSlug}/skills': typeof DotSkillsRouteWithChildren | ||
| 150 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 176 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 151 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 177 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 152 | '/{-$orgSlug}/users': typeof DotUsersRoute | 178 | '/{-$orgSlug}/users': typeof DotUsersRoute |
| @@ -156,9 +182,11 @@ export interface FileRoutesById { · 3 unmodified lines | |||
| 156 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 182 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 157 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute | 183 | '/{-$orgSlug}/integrations/browse': typeof DotIntegrationsDotbrowseRoute |
| 158 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 184 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 185 | '/{-$orgSlug}/skills/new': typeof DotSkillsDotnewRoute | ||
| 159 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 186 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| 160 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute | 187 | '/{-$orgSlug}/integrations/add/$pluginKey': typeof DotIntegrationsDotaddDotpluginKeyRoute |
| 161 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute | 188 | '/{-$orgSlug}/plugins/$pluginId/$': typeof DotPluginsDotpluginIdDotsplatRoute |
| 189 | '/{-$orgSlug}/skills/$skillOwner/$skillName': typeof DotSkillsDotskillOwnerDotskillNameRoute | ||
| 162 | } | 190 | } |
| 163 | export interface FileRouteTypes { | 191 | export interface FileRouteTypes { |
| 164 | fileRoutesByFullPath: FileRoutesByFullPath | 192 | fileRoutesByFullPath: FileRoutesByFullPath |
| @@ -166,6 +194,7 @@ export interface FileRouteTypes { · 1 unmodified line | |||
| 166 | | '/{-$orgSlug}/artifacts' | 194 | | '/{-$orgSlug}/artifacts' |
| 167 | | '/{-$orgSlug}/policies' | 195 | | '/{-$orgSlug}/policies' |
| 168 | | '/{-$orgSlug}/secrets' | 196 | | '/{-$orgSlug}/secrets' |
| 197 | | '/{-$orgSlug}/skills' | ||
| 169 | | '/{-$orgSlug}/toolkits' | 198 | | '/{-$orgSlug}/toolkits' |
| 170 | | '/{-$orgSlug}/tools' | 199 | | '/{-$orgSlug}/tools' |
| 171 | | '/{-$orgSlug}/users' | 200 | | '/{-$orgSlug}/users' |
| @@ -175,14 +204,17 @@ export interface FileRouteTypes { · 3 unmodified lines | |||
| 175 | | '/{-$orgSlug}/integrations/$namespace' | 204 | | '/{-$orgSlug}/integrations/$namespace' |
| 176 | | '/{-$orgSlug}/integrations/browse' | 205 | | '/{-$orgSlug}/integrations/browse' |
| 177 | | '/{-$orgSlug}/resume/$executionId' | 206 | | '/{-$orgSlug}/resume/$executionId' |
| 207 | | '/{-$orgSlug}/skills/new' | ||
| 178 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 208 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 179 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 209 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 180 | | '/{-$orgSlug}/plugins/$pluginId/$' | 210 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 211 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 181 | fileRoutesByTo: FileRoutesByTo | 212 | fileRoutesByTo: FileRoutesByTo |
| 182 | to: | 213 | to: |
| 183 | | '/{-$orgSlug}/artifacts' | 214 | | '/{-$orgSlug}/artifacts' |
| 184 | | '/{-$orgSlug}/policies' | 215 | | '/{-$orgSlug}/policies' |
| 185 | | '/{-$orgSlug}/secrets' | 216 | | '/{-$orgSlug}/secrets' |
| 217 | | '/{-$orgSlug}/skills' | ||
| 186 | | '/{-$orgSlug}/toolkits' | 218 | | '/{-$orgSlug}/toolkits' |
| 187 | | '/{-$orgSlug}/tools' | 219 | | '/{-$orgSlug}/tools' |
| 188 | | '/{-$orgSlug}/users' | 220 | | '/{-$orgSlug}/users' |
| @@ -192,14 +224,17 @@ export interface FileRouteTypes { · 3 unmodified lines | |||
| 192 | | '/{-$orgSlug}/integrations/$namespace' | 224 | | '/{-$orgSlug}/integrations/$namespace' |
| 193 | | '/{-$orgSlug}/integrations/browse' | 225 | | '/{-$orgSlug}/integrations/browse' |
| 194 | | '/{-$orgSlug}/resume/$executionId' | 226 | | '/{-$orgSlug}/resume/$executionId' |
| 227 | | '/{-$orgSlug}/skills/new' | ||
| 195 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 228 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 196 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 229 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 197 | | '/{-$orgSlug}/plugins/$pluginId/$' | 230 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 231 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 198 | id: | 232 | id: |
| 199 | | '__root__' | 233 | | '__root__' |
| 200 | | '/{-$orgSlug}/artifacts' | 234 | | '/{-$orgSlug}/artifacts' |
| 201 | | '/{-$orgSlug}/policies' | 235 | | '/{-$orgSlug}/policies' |
| 202 | | '/{-$orgSlug}/secrets' | 236 | | '/{-$orgSlug}/secrets' |
| 237 | | '/{-$orgSlug}/skills' | ||
| 203 | | '/{-$orgSlug}/toolkits' | 238 | | '/{-$orgSlug}/toolkits' |
| 204 | | '/{-$orgSlug}/tools' | 239 | | '/{-$orgSlug}/tools' |
| 205 | | '/{-$orgSlug}/users' | 240 | | '/{-$orgSlug}/users' |
| @@ -209,15 +244,18 @@ export interface FileRouteTypes { · 3 unmodified lines | |||
| 209 | | '/{-$orgSlug}/integrations/$namespace' | 244 | | '/{-$orgSlug}/integrations/$namespace' |
| 210 | | '/{-$orgSlug}/integrations/browse' | 245 | | '/{-$orgSlug}/integrations/browse' |
| 211 | | '/{-$orgSlug}/resume/$executionId' | 246 | | '/{-$orgSlug}/resume/$executionId' |
| 247 | | '/{-$orgSlug}/skills/new' | ||
| 212 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 248 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| 213 | | '/{-$orgSlug}/integrations/add/$pluginKey' | 249 | | '/{-$orgSlug}/integrations/add/$pluginKey' |
| 214 | | '/{-$orgSlug}/plugins/$pluginId/$' | 250 | | '/{-$orgSlug}/plugins/$pluginId/$' |
| 251 | | '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 215 | fileRoutesById: FileRoutesById | 252 | fileRoutesById: FileRoutesById |
| 216 | } | 253 | } |
| 217 | export interface RootRouteChildren { | 254 | export interface RootRouteChildren { |
| 218 | DotArtifactsRoute: typeof DotArtifactsRouteWithChildren | 255 | DotArtifactsRoute: typeof DotArtifactsRouteWithChildren |
| 219 | DotPoliciesRoute: typeof DotPoliciesRoute | 256 | DotPoliciesRoute: typeof DotPoliciesRoute |
| 220 | DotSecretsRoute: typeof DotSecretsRoute | 257 | DotSecretsRoute: typeof DotSecretsRoute |
| 258 | DotSkillsRoute: typeof DotSkillsRouteWithChildren | ||
| 221 | DotToolkitsRoute: typeof DotToolkitsRouteWithChildren | 259 | DotToolkitsRoute: typeof DotToolkitsRouteWithChildren |
| 222 | DotToolsRoute: typeof DotToolsRoute | 260 | DotToolsRoute: typeof DotToolsRoute |
| 223 | DotUsersRoute: typeof DotUsersRoute | 261 | DotUsersRoute: typeof DotUsersRoute |
| @@ -260,6 +298,13 @@ declare module '@tanstack/react-router' { · 36 unmodified lines | |||
| 260 | preLoaderRoute: typeof DotToolkitsRouteImport | 298 | preLoaderRoute: typeof DotToolkitsRouteImport |
| 261 | parentRoute: typeof rootRouteImport | 299 | parentRoute: typeof rootRouteImport |
| 262 | } | 300 | } |
| 301 | '/{-$orgSlug}/skills': { | ||
| 302 | id: '/{-$orgSlug}/skills' | ||
| 303 | path: '/{-$orgSlug}/skills' | ||
| 304 | fullPath: '/{-$orgSlug}/skills' | ||
| 305 | preLoaderRoute: typeof DotSkillsRouteImport | ||
| 306 | parentRoute: typeof rootRouteImport | ||
| 307 | } | ||
| 263 | '/{-$orgSlug}/secrets': { | 308 | '/{-$orgSlug}/secrets': { |
| 264 | id: '/{-$orgSlug}/secrets' | 309 | id: '/{-$orgSlug}/secrets' |
| 265 | path: '/{-$orgSlug}/secrets' | 310 | path: '/{-$orgSlug}/secrets' |
| @@ -288,6 +333,13 @@ declare module '@tanstack/react-router' { · 22 unmodified lines | |||
| 288 | preLoaderRoute: typeof DotToolkitsDottoolkitSlugRouteImport | 333 | preLoaderRoute: typeof DotToolkitsDottoolkitSlugRouteImport |
| 289 | parentRoute: typeof DotToolkitsRoute | 334 | parentRoute: typeof DotToolkitsRoute |
| 290 | } | 335 | } |
| 336 | '/{-$orgSlug}/skills/new': { | ||
| 337 | id: '/{-$orgSlug}/skills/new' | ||
| 338 | path: '/new' | ||
| 339 | fullPath: '/{-$orgSlug}/skills/new' | ||
| 340 | preLoaderRoute: typeof DotSkillsDotnewRouteImport | ||
| 341 | parentRoute: typeof DotSkillsRoute | ||
| 342 | } | ||
| 291 | '/{-$orgSlug}/resume/$executionId': { | 343 | '/{-$orgSlug}/resume/$executionId': { |
| 292 | id: '/{-$orgSlug}/resume/$executionId' | 344 | id: '/{-$orgSlug}/resume/$executionId' |
| 293 | path: '/{-$orgSlug}/resume/$executionId' | 345 | path: '/{-$orgSlug}/resume/$executionId' |
| @@ -323,6 +375,13 @@ declare module '@tanstack/react-router' { · 29 unmodified lines | |||
| 323 | preLoaderRoute: typeof DotArtifactsDotartifactIdRouteImport | 375 | preLoaderRoute: typeof DotArtifactsDotartifactIdRouteImport |
| 324 | parentRoute: typeof DotArtifactsRoute | 376 | parentRoute: typeof DotArtifactsRoute |
| 325 | } | 377 | } |
| 378 | '/{-$orgSlug}/skills/$skillOwner/$skillName': { | ||
| 379 | id: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 380 | path: '/$skillOwner/$skillName' | ||
| 381 | fullPath: '/{-$orgSlug}/skills/$skillOwner/$skillName' | ||
| 382 | preLoaderRoute: typeof DotSkillsDotskillOwnerDotskillNameRouteImport | ||
| 383 | parentRoute: typeof DotSkillsRoute | ||
| 384 | } | ||
| 326 | '/{-$orgSlug}/plugins/$pluginId/$': { | 385 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 327 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 386 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 328 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 387 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -352,6 +411,21 @@ const DotArtifactsRouteWithChildren = DotArtifactsRoute._addFileChildren( · 23 unmodified lines | |||
| 352 | DotArtifactsRouteChildren, | 411 | DotArtifactsRouteChildren, |
| 353 | ) | 412 | ) |
| 354 | 413 | ||
| 414 | interface DotSkillsRouteChildren { | ||
| 415 | DotSkillsDotnewRoute: typeof DotSkillsDotnewRoute | ||
| 416 | DotSkillsDotskillOwnerDotskillNameRoute: typeof DotSkillsDotskillOwnerDotskillNameRoute | ||
| 417 | } | ||
| 418 | |||
| 419 | const DotSkillsRouteChildren: DotSkillsRouteChildren = { | ||
| 420 | DotSkillsDotnewRoute: DotSkillsDotnewRoute, | ||
| 421 | DotSkillsDotskillOwnerDotskillNameRoute: | ||
| 422 | DotSkillsDotskillOwnerDotskillNameRoute, | ||
| 423 | } | ||
| 424 | |||
| 425 | const DotSkillsRouteWithChildren = DotSkillsRoute._addFileChildren( | ||
| 426 | DotSkillsRouteChildren, | ||
| 427 | ) | ||
| 428 | |||
| 355 | interface DotToolkitsRouteChildren { | 429 | interface DotToolkitsRouteChildren { |
| 356 | DotToolkitsDottoolkitSlugRoute: typeof DotToolkitsDottoolkitSlugRoute | 430 | DotToolkitsDottoolkitSlugRoute: typeof DotToolkitsDottoolkitSlugRoute |
| 357 | } | 431 | } |
| @@ -368,6 +442,7 @@ const rootRouteChildren: RootRouteChildren = { · 10 unmodified lines | |||
| 368 | DotArtifactsRoute: DotArtifactsRouteWithChildren, | 442 | DotArtifactsRoute: DotArtifactsRouteWithChildren, |
| 369 | DotPoliciesRoute: DotPoliciesRoute, | 443 | DotPoliciesRoute: DotPoliciesRoute, |
| 370 | DotSecretsRoute: DotSecretsRoute, | 444 | DotSecretsRoute: DotSecretsRoute, |
| 445 | DotSkillsRoute: DotSkillsRouteWithChildren, | ||
| 371 | DotToolkitsRoute: DotToolkitsRouteWithChildren, | 446 | DotToolkitsRoute: DotToolkitsRouteWithChildren, |
| 372 | DotToolsRoute: DotToolsRoute, | 447 | DotToolsRoute: DotToolsRoute, |
| 373 | DotUsersRoute: DotUsersRoute, | 448 | DotUsersRoute: DotUsersRoute, |
| @@ -0,0 +1,36 @@ | |||
| 1 | import { Owner, SkillName } from "@executor-js/sdk/shared"; | ||
| 2 | |||
| 3 | import { SkillDetailPage, SkillMissingPage } from "../pages/skill-detail"; | ||
| 4 | import { SkillEditorPage } from "../pages/skill-editor"; | ||
| 5 | import { SkillsPage } from "../pages/skills"; | ||
| 6 | |||
| 7 | /** The URL's owner segment, or `null` when it is neither `org` nor `user`. */ | ||
| 8 | const parseOwner = (value: string): Owner | null => | ||
| 9 | value === "org" || value === "user" ? value : null; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * The one component every skills route renders. `/skills/new` and | ||
| 13 | * `/skills/$skillOwner/$skillName` generate as CHILDREN of `/skills` and the | ||
| 14 | * parent renders no `<Outlet/>`, so the parent resolves which view to show — | ||
| 15 | * the shape `artifacts-route.tsx` established. | ||
| 16 | */ | ||
| 17 | export function SkillsRoute(props: { | ||
| 18 | readonly skillOwner?: string | undefined; | ||
| 19 | readonly skillName?: string | undefined; | ||
| 20 | /** `?edit` on a skill URL. Ignored on the list and on `/skills/new`. */ | ||
| 21 | readonly editing?: boolean | undefined; | ||
| 22 | /** True on `/skills/new`, which carries no params to recognize it by. */ | ||
| 23 | readonly creating?: boolean | undefined; | ||
| 24 | }) { | ||
| 25 | if (props.skillOwner === undefined || props.skillName === undefined) { | ||
| 26 | return props.creating ? <SkillEditorPage /> : <SkillsPage />; | ||
| 27 | } | ||
| 28 | |||
| 29 | const owner = parseOwner(props.skillOwner); | ||
| 30 | // Only a hand-typed URL gets here with an owner that is neither `org` nor | ||
| 31 | // `user`. There is no row to ask for, so say so without a fetch. | ||
| 32 | if (owner === null) return <SkillMissingPage />; | ||
| 33 | |||
| 34 | const ref = { owner, name: SkillName.make(props.skillName) } as const; | ||
| 35 | return props.editing ? <SkillEditorPage editing={ref} /> : <SkillDetailPage {...ref} />; | ||
| 36 | } | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | import { createFileRoute } from "@tanstack/react-router"; | ||
| 2 | |||
| 3 | import { SkillsRoute } from "./skills-route"; | ||
| 4 | |||
| 5 | export const Route = createFileRoute("/{-$orgSlug}/skills/$skillOwner/$skillName")({ | ||
| 6 | component: SkillDetailRouteComponent, | ||
| 7 | }); | ||
| 8 | |||
| 9 | function SkillDetailRouteComponent() { | ||
| 10 | const { skillOwner, skillName } = Route.useParams(); | ||
| 11 | const { edit } = Route.useSearch(); | ||
| 12 | return <SkillsRoute skillOwner={skillOwner} skillName={skillName} editing={edit} />; | ||
| 13 | } | ||
| @@ -0,0 +1,11 @@ | |||
| 1 | import { createFileRoute } from "@tanstack/react-router"; | ||
| 2 | |||
| 3 | import { SkillsRoute } from "./skills-route"; | ||
| 4 | |||
| 5 | export const Route = createFileRoute("/{-$orgSlug}/skills/new")({ | ||
| 6 | component: NewSkillRouteComponent, | ||
| 7 | }); | ||
| 8 | |||
| 9 | function NewSkillRouteComponent() { | ||
| 10 | return <SkillsRoute creating />; | ||
| 11 | } | ||
| @@ -0,0 +1,37 @@ | |||
| 1 | import { createFileRoute, useLocation, useParams } from "@tanstack/react-router"; | ||
| 2 | |||
| 3 | import { SkillsRoute } from "./skills-route"; | ||
| 4 | |||
| 5 | /** `?edit` puts the skill URL into its editor. Declared on the PARENT because | ||
| 6 | * `/skills/new` and `/skills/$skillOwner/$skillName` are generated as its | ||
| 7 | * children and inherit its search contract. */ | ||
| 8 | interface SkillsSearch { | ||
| 9 | readonly edit?: true; | ||
| 10 | } | ||
| 11 | |||
| 12 | export const Route = createFileRoute("/{-$orgSlug}/skills")({ | ||
| 13 | component: SkillsRouteComponent, | ||
| 14 | validateSearch: (search: Record<string, unknown>): SkillsSearch => | ||
| 15 | search.edit === undefined ? {} : { edit: true }, | ||
| 16 | }); | ||
| 17 | |||
| 18 | function SkillsRouteComponent() { | ||
| 19 | // Both child routes generate UNDER this one and this component renders no | ||
| 20 | // <Outlet/>, so the parent has to serve all three URLs — the shape | ||
| 21 | // `artifacts.tsx` established. `/skills/new` carries no params at all, so the | ||
| 22 | // pathname is the only thing that tells it apart from the list. | ||
| 23 | const { skillOwner, skillName } = useParams({ strict: false }) as { | ||
| 24 | skillOwner?: string; | ||
| 25 | skillName?: string; | ||
| 26 | }; | ||
| 27 | const pathname = useLocation({ select: (location) => location.pathname }); | ||
| 28 | const { edit } = Route.useSearch(); | ||
| 29 | return ( | ||
| 30 | <SkillsRoute | ||
| 31 | skillOwner={skillOwner} | ||
| 32 | skillName={skillName} | ||
| 33 | editing={edit} | ||
| 34 | creating={pathname.endsWith("/skills/new")} | ||
| 35 | /> | ||
| 36 | ); | ||
| 37 | } | ||