@maudecode/kanvil 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/apps/server/dist/{auth-Bm6cIseE.mjs → auth-D43XyHdK.mjs} +266 -68
  2. package/apps/server/dist/client.mjs +2 -2
  3. package/apps/server/dist/{config-CrJuk5mY.mjs → config-CQVdVhXv.mjs} +1 -1
  4. package/apps/server/dist/index.mjs +167 -45
  5. package/apps/server/dist/migrate.mjs +1 -1
  6. package/apps/server/dist/recover.mjs +3 -3
  7. package/apps/server/dist/{src-D80LNZKP.mjs → src-D70GmZ5R.mjs} +170 -28
  8. package/apps/server/drizzle/0039_intake_credential.sql +2 -0
  9. package/apps/server/drizzle/0040_upload_urls.sql +20 -0
  10. package/apps/server/drizzle/0041_project_repositories.sql +2 -0
  11. package/apps/server/drizzle/meta/0039_snapshot.json +7020 -0
  12. package/apps/server/drizzle/meta/0040_snapshot.json +7170 -0
  13. package/apps/server/drizzle/meta/0041_snapshot.json +7183 -0
  14. package/apps/server/drizzle/meta/_journal.json +21 -0
  15. package/apps/web/dist/assets/{CompositeItem-Tueq2VKR.js → CompositeItem-DLTRsada.js} +1 -1
  16. package/apps/web/dist/assets/{Trans-BgU0eWAX.js → Trans-DqX-qhvQ.js} +1 -1
  17. package/apps/web/dist/assets/admin-settings-BQzdZII5.js +1 -0
  18. package/apps/web/dist/assets/{archive-restore-CSpoH6mS.js → archive-restore-BO1qng5Y.js} +1 -1
  19. package/apps/web/dist/assets/az-AZ-SXqDBPA2.js +1 -0
  20. package/apps/web/dist/assets/{az-DUcDY8Bp.js → az-CEW7-a7J.js} +1 -1
  21. package/apps/web/dist/assets/configuration-D0zvwrXt.js +1 -0
  22. package/apps/web/dist/assets/{corner-left-up-C3OGlXIB.js → corner-left-up-Be7Z9Uhf.js} +1 -1
  23. package/apps/web/dist/assets/{de-89C1V8Ut.js → de-B-oGVMYL.js} +1 -1
  24. package/apps/web/dist/assets/de-DE-CB7aQ6b-.js +1 -0
  25. package/apps/web/dist/assets/default-CCI1vZxa.js +56 -0
  26. package/apps/web/dist/assets/delivery-settings-BrwSrFIN.js +1 -0
  27. package/apps/web/dist/assets/{el-D6CICkhi.js → el-Cp69kezM.js} +1 -1
  28. package/apps/web/dist/assets/el-GR-D1ntYXIt.js +1 -0
  29. package/apps/web/dist/assets/{ellipsis-lJJU8zB7.js → ellipsis-B0AE8ax3.js} +1 -1
  30. package/apps/web/dist/assets/{es-B20Izib2.js → es-CwOMVSTx.js} +1 -1
  31. package/apps/web/dist/assets/es-ES-CllsaxcB.js +1 -0
  32. package/apps/web/dist/assets/{forms--87R7VQW.js → forms-CkVpTx29.js} +1 -1
  33. package/apps/web/dist/assets/{fr-DVlGnGsu.js → fr-Cy4co9ic.js} +1 -1
  34. package/apps/web/dist/assets/fr-FR-DxTw6LgI.js +1 -0
  35. package/apps/web/dist/assets/{hi-D1F5Pi_I.js → hi-DbO9TKwg.js} +1 -1
  36. package/apps/web/dist/assets/hi-IN-CaQga-0l.js +1 -0
  37. package/apps/web/dist/assets/{highlight-ygDJPvRy.js → highlight-zMg1BK-r.js} +1 -1
  38. package/apps/web/dist/assets/{id-LESXKW7G.js → id-B6NKsmCm.js} +1 -1
  39. package/apps/web/dist/assets/id-ID-DBfwAAqU.js +1 -0
  40. package/apps/web/dist/assets/index-CZuFd4LM.js +17 -0
  41. package/apps/web/dist/assets/index-DPT5LAs1.css +2 -0
  42. package/apps/web/dist/assets/invitations-Dp40BfHj.js +1 -0
  43. package/apps/web/dist/assets/it-IT-BckuH9Nl.js +1 -0
  44. package/apps/web/dist/assets/{it-CgRGAs6u.js → it-ilGYCq2T.js} +1 -1
  45. package/apps/web/dist/assets/item-DQR89bKm.js +1 -0
  46. package/apps/web/dist/assets/{ja-B7arhzgX.js → ja-D-hJRTgP.js} +1 -1
  47. package/apps/web/dist/assets/ja-JP-BAGgJKZ4.js +1 -0
  48. package/apps/web/dist/assets/ko-KR-srIEsTP5.js +1 -0
  49. package/apps/web/dist/assets/{ko-Ro_UonyA.js → ko-ajkkoJ_d.js} +1 -1
  50. package/apps/web/dist/assets/label-settings-ChnKkA97.js +1 -0
  51. package/apps/web/dist/assets/{markdown-blocks-toSPlHSc.js → markdown-blocks-CpzrwyQG.js} +1 -1
  52. package/apps/web/dist/assets/member-directory-XK0ZEGUq.js +1 -0
  53. package/apps/web/dist/assets/{mk-DR3kmrIp.js → mk-C_sMyEHX.js} +1 -1
  54. package/apps/web/dist/assets/mk-MK-BTa1NMAF.js +1 -0
  55. package/apps/web/dist/assets/{nl-D3uRJJLT.js → nl-D9-P4UPR.js} +1 -1
  56. package/apps/web/dist/assets/nl-NL-CQduhDqs.js +1 -0
  57. package/apps/web/dist/assets/{pl-CtzDOLdV.js → pl-CiQM7fSB.js} +1 -1
  58. package/apps/web/dist/assets/pl-PL-BdZMf7Ni.js +1 -0
  59. package/apps/web/dist/assets/project-lifecycle-VYecJuiP.js +1 -0
  60. package/apps/web/dist/assets/project-settings-Bs6YEpo9.js +1 -0
  61. package/apps/web/dist/assets/{pt-BR-FarXHHuf.js → pt-BR-CP7dhuqF.js} +1 -1
  62. package/apps/web/dist/assets/pt-BR-DZEXJNHg.js +1 -0
  63. package/apps/web/dist/assets/public-project-DUfWDao9.js +1 -0
  64. package/apps/web/dist/assets/{rotate-ccw-Byx7O3RH.js → rotate-ccw-Cxh9ec2s.js} +1 -1
  65. package/apps/web/dist/assets/{ru-DHMEVxvB.js → ru-DxJ6vF4E.js} +1 -1
  66. package/apps/web/dist/assets/ru-RU-CZKEe7P8.js +1 -0
  67. package/apps/web/dist/assets/search-DfbsqO7M.js +85 -0
  68. package/apps/web/dist/assets/{send-DlGV2XQS.js → send-DrJl0BKm.js} +1 -1
  69. package/apps/web/dist/assets/settings-BN8uIiBG.js +1 -0
  70. package/apps/web/dist/assets/{sliders-horizontal-CEHGGft_.js → sliders-horizontal-DM47MLe7.js} +1 -1
  71. package/apps/web/dist/assets/switch-CJTVGtE9.js +1 -0
  72. package/apps/web/dist/assets/{table-2AEfwytk.js → table-BLLjFPWD.js} +1 -1
  73. package/apps/web/dist/assets/ticket-detail-BTy_3JeU.js +2 -0
  74. package/apps/web/dist/assets/ticket-editor-Bb_lyxnJ.js +2 -0
  75. package/apps/web/dist/assets/ticket-list-Botp5qf5.js +6 -0
  76. package/apps/web/dist/assets/ticket-list-controls-Dmkno4pm.js +1 -0
  77. package/apps/web/dist/assets/{tr-CjdSDu0z.js → tr-9IkdRLIk.js} +1 -1
  78. package/apps/web/dist/assets/tr-TR-Y8wBLO8I.js +1 -0
  79. package/apps/web/dist/assets/{uk-C8od6u_H.js → uk-Btampsdw.js} +1 -1
  80. package/apps/web/dist/assets/uk-UA-CVpasGUS.js +1 -0
  81. package/apps/web/dist/assets/{upload-BeTAGU_W.js → upload-HARy4VC_.js} +1 -1
  82. package/apps/web/dist/assets/{useQueries-BtRgTciJ.js → useQueries-CTeEF--U.js} +1 -1
  83. package/apps/web/dist/assets/{vi-BwdBtgfS.js → vi-BVZzOn_n.js} +1 -1
  84. package/apps/web/dist/assets/vi-VN-BMy-iJD-.js +1 -0
  85. package/apps/web/dist/assets/visual-editor-DKBPjqV-.js +208 -0
  86. package/apps/web/dist/assets/workspace-settings-DqzBNw60.js +1 -0
  87. package/apps/web/dist/assets/{zh-CN-DM-UoFmL.js → zh-CN-BoUvF2do.js} +1 -1
  88. package/apps/web/dist/assets/zh-CN-Cds_v0R8.js +1 -0
  89. package/apps/web/dist/assets/{zh-TW-q2nrwFjt.js → zh-TW-CEIDGTWe.js} +1 -1
  90. package/apps/web/dist/assets/zh-TW-DCCYakXz.js +1 -0
  91. package/apps/web/dist/index.html +9 -9
  92. package/docs/api.md +35 -5
  93. package/docs/cli.md +1 -1
  94. package/docs/mobile.md +67 -4
  95. package/docs/releasing.md +6 -4
  96. package/package.json +1 -1
  97. package/apps/web/dist/assets/admin-settings-Ckis3XJG.js +0 -1
  98. package/apps/web/dist/assets/az-AZ-bFxpMMZN.js +0 -1
  99. package/apps/web/dist/assets/de-DE-BeXI30Ll.js +0 -1
  100. package/apps/web/dist/assets/default-Dnep8Yat.js +0 -56
  101. package/apps/web/dist/assets/delivery-settings-cMldZ9K7.js +0 -1
  102. package/apps/web/dist/assets/el-GR-SgRHlWhY.js +0 -1
  103. package/apps/web/dist/assets/es-ES-BF1J5vap.js +0 -1
  104. package/apps/web/dist/assets/fr-FR-D_sldwf6.js +0 -1
  105. package/apps/web/dist/assets/hi-IN-CfOw4TWf.js +0 -1
  106. package/apps/web/dist/assets/id-ID-CLKtHO7Q.js +0 -1
  107. package/apps/web/dist/assets/index-Bn-MADyj.css +0 -2
  108. package/apps/web/dist/assets/index-Ds3IHeIi.js +0 -17
  109. package/apps/web/dist/assets/invitations-C5WbdDUQ.js +0 -1
  110. package/apps/web/dist/assets/it-IT-eVZNW5qX.js +0 -1
  111. package/apps/web/dist/assets/item-BD1Poxfs.js +0 -1
  112. package/apps/web/dist/assets/ja-JP-D96njn-P.js +0 -1
  113. package/apps/web/dist/assets/ko-KR-Bash_ieA.js +0 -1
  114. package/apps/web/dist/assets/label-settings-BaikCfal.js +0 -1
  115. package/apps/web/dist/assets/member-directory-BjUy1KuK.js +0 -1
  116. package/apps/web/dist/assets/mk-MK-BWQWGIRx.js +0 -1
  117. package/apps/web/dist/assets/nl-NL-D1tvo0Gd.js +0 -1
  118. package/apps/web/dist/assets/pl-PL-DNgGUEcQ.js +0 -1
  119. package/apps/web/dist/assets/project-lifecycle-BBHcRi6p.js +0 -1
  120. package/apps/web/dist/assets/project-settings-BaYuxYjg.js +0 -1
  121. package/apps/web/dist/assets/pt-BR-CZsUZQ6P.js +0 -1
  122. package/apps/web/dist/assets/public-project-DGf3nkg2.js +0 -1
  123. package/apps/web/dist/assets/ru-RU-4ew92YE_.js +0 -1
  124. package/apps/web/dist/assets/search-DwsPEC4E.js +0 -85
  125. package/apps/web/dist/assets/settings-C_sN8Y1h.js +0 -1
  126. package/apps/web/dist/assets/switch-B_rsZYBf.js +0 -1
  127. package/apps/web/dist/assets/ticket-detail-C2l8nwF-.js +0 -2
  128. package/apps/web/dist/assets/ticket-editor-B0UB98HS.js +0 -2
  129. package/apps/web/dist/assets/ticket-list-D_MHudj1.js +0 -6
  130. package/apps/web/dist/assets/ticket-list-controls-jKg4i-NA.js +0 -1
  131. package/apps/web/dist/assets/tr-TR-CM381XCw.js +0 -1
  132. package/apps/web/dist/assets/uk-UA-AeOiWCVo.js +0 -1
  133. package/apps/web/dist/assets/vi-VN-td08xXv9.js +0 -1
  134. package/apps/web/dist/assets/visual-editor-CzB_qwvB.js +0 -208
  135. package/apps/web/dist/assets/workspace-settings-DOr9j8X1.js +0 -1
  136. package/apps/web/dist/assets/zh-CN-D3dhEYLS.js +0 -1
  137. package/apps/web/dist/assets/zh-TW-CfhOEFo8.js +0 -1
@@ -1,7 +1,7 @@
1
- import { a as auth_schema_exports, d as user$1, i as apikey, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CrJuk5mY.mjs";
2
- import { A as hasAdminRole, B as memberSchema, Bt as searchSettingsSchema, C as errorSchema, Ft as instanceUpdateSchema, G as notificationReadInput, H as notificationIdsInput, I as isOperation, It as photoInputSchema, L as isPresetColor, Lt as photoResultSchema, M as importProviderDefinitions, Mt as adminInstanceSchema, Pt as brokerSettingsSchema, R as isPublicOperation, Rt as s3SettingsSchema, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, at as publicOperations, c as credentialInput, g as defaultWorkflow, ht as scopes, j as importLimits, mt as roles, ot as pushDeviceInput, q as okSchema, s as closedStates, t as ApiError, wt as ticketExportFormat, xt as stateLabels } from "./src-D80LNZKP.mjs";
1
+ import { a as auth_schema_exports, d as user$1, i as apikey, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CQVdVhXv.mjs";
2
+ import { B as isPublicOperation, Bt as instanceUpdateSchema, G as notificationIdsInput, Gt as searchSettingsSchema, H as memberSchema, Ht as photoResultSchema, J as notificationReadInput, K as notificationPreferencesInput, Lt as adminInstanceSchema, M as hasAdminRole, N as importLimits, Ot as ticketExportFormat, P as importProviderDefinitions, Q as operations, R as isOperation, T as errorSchema, Tt as stateLabels, Ut as s3SettingsSchema, Vt as photoInputSchema, X as okSchema, _t as roles, ct as publicOperations, l as closedStates, lt as pushDeviceInput, q as notificationPreferencesSchema, t as ApiError, u as credentialInput, v as defaultWorkflow, vt as scopes, z as isPresetColor, zt as brokerSettingsSchema } from "./src-D70GmZ5R.mjs";
3
3
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
4
- import { $ as stagedUploads, A as requireSession, B as attachmentContent, C as defaultSenders, D as createAccess, E as createHttpSender, F as decodeImage, G as events, H as blobObjects, I as imageResponse, J as notificationPreferences, L as stagedUploadCutoff, M as createTicketSearch, N as createTicketIndex, O as createWriteLimiter, P as createBlobStorage, Q as pushDevices, R as createObjectStore, S as receiveRepositoryWebhook, T as DeliveryError, U as deliveryCheckpoints, V as attachments, W as eventClock, X as profilePhotos, Y as notifications, Z as projects, _ as one, a as deliverySecrets, at as matchesOIDCBinding, b as requireRow, c as ownerBlocker, d as createDomain, et as storageBackends, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaces, j as readCalendarFeed, k as requireInstanceAdmin, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as tickets, o as otherActiveAdministrator, p as fail, q as members, r as signInProviders, rt as workspaceMembers, s as ownedWorkspaces, t as createAuth, tt as subscriptions, u as assertVersion, v as reachable, w as deliverBatch, x as processImports, y as recipientColumns, z as createPollingJob } from "./auth-Bm6cIseE.mjs";
4
+ import { $ as pushDevices, A as requireInstanceAdmin, B as createPollingJob, C as defaultSenders, D as receiveUpload, E as createHttpSender, F as createBlobStorage, G as eventClock, H as attachments, I as decodeImage, J as members, K as events, L as imageResponse, M as readCalendarFeed, N as createTicketSearch, O as createAccess, P as createTicketIndex, Q as projects, R as stagedUploadCutoff, S as receiveRepositoryWebhook, T as DeliveryError, U as blobObjects, V as attachmentContent, W as deliveryCheckpoints, X as notifications, Y as notificationPreferences, Z as profilePhotos, _ as one, a as deliverySecrets, at as workspaces, b as requireRow, c as ownerBlocker, d as createDomain, et as stagedUploads, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaceMembers, j as requireSession, k as createWriteLimiter, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as subscriptions, o as otherActiveAdministrator, ot as matchesOIDCBinding, p as fail, r as signInProviders, rt as tickets, s as ownedWorkspaces, t as createAuth, tt as storageBackends, u as assertVersion, v as reachable, w as deliverBatch, x as processImports, y as recipientColumns, z as createObjectStore } from "./auth-D43XyHdK.mjs";
5
5
  import { serve } from "@hono/node-server";
6
6
  import pg from "pg";
7
7
  import { drizzle } from "drizzle-orm/node-postgres";
@@ -9,8 +9,8 @@ import { and, asc, count, desc, eq, gt, ilike, inArray, isNotNull, isNull, notIn
9
9
  import { z } from "zod";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
12
- import { Expo } from "expo-server-sdk";
13
12
  import { isDeepStrictEqual } from "node:util";
13
+ import { Expo } from "expo-server-sdk";
14
14
  import { EventEmitter } from "node:events";
15
15
  import { connect } from "amqplib";
16
16
  import { OpenAPIHono, createRoute, z as z$1 } from "@hono/zod-openapi";
@@ -359,7 +359,7 @@ function createCleanup() {
359
359
  }
360
360
  //#endregion
361
361
  //#region src/version.ts
362
- const applicationVersion = "0.5.1";
362
+ const applicationVersion = "0.7.0";
363
363
  //#endregion
364
364
  //#region src/realtime.ts
365
365
  async function createRealtime(config) {
@@ -3121,49 +3121,138 @@ const defaultAdapters = {
3121
3121
  };
3122
3122
  //#endregion
3123
3123
  //#region src/mcp.ts
3124
+ const operationNames = Object.keys(operations).filter(isOperation);
3125
+ const coreNames = operationNames.filter((name) => operations[name].mcp === "core");
3126
+ const instructions = "Ticket keys look like KAN-12, and a key's prefix or the project's linked git remote works as projectId. To work a ticket: claim_ticket, keep its status current with update_ticket, add_comment on what you did, add_evidence, then set status done and release_claim. Leave tickets with automationReady false to people. The core tools are listed. Reach every other operation with find_operation, then read_operation for reads or run_operation for writes. In replies, a missing key means null or empty; lists return summary rows unless view is full.";
3127
+ const defaultView = "summary";
3128
+ const toolInput = (name) => {
3129
+ const input = operations[name].input;
3130
+ return "view" in input.shape ? input.safeExtend({ view: z.enum([defaultView, "full"]).default(defaultView) }) : input;
3131
+ };
3132
+ const echoedFields = (name) => {
3133
+ const { shape } = toolInput(name);
3134
+ const { changes } = shape;
3135
+ return ["description", "acceptanceCriteria"].filter((field) => field in shape || changes instanceof z.ZodObject && field in changes.shape);
3136
+ };
3137
+ const isEmpty = (value) => value === null || typeof value === "object" && Object.keys(value).length === 0;
3138
+ const compact = (value, echoed) => {
3139
+ if (Array.isArray(value)) return value.map((item) => compact(item, echoed));
3140
+ if (typeof value !== "object" || value === null) return value;
3141
+ const dropped = "acceptanceCriteria" in value ? echoed : [];
3142
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !dropped.includes(key)).map(([key, field]) => [key, compact(field, echoed)]).filter(([, field]) => !isEmpty(field)));
3143
+ };
3144
+ const known = (name) => {
3145
+ if (!isOperation(name)) throw new ApiError(404, "not_found", `No operation is named ${name}`);
3146
+ return name;
3147
+ };
3148
+ const summary = (name) => ({
3149
+ name,
3150
+ description: operations[name].description,
3151
+ access: operations[name].method === "GET" ? "read" : "write"
3152
+ });
3153
+ function findOperation({ query = "", name }) {
3154
+ if (name !== void 0) {
3155
+ const operation = known(name), spec = operations[operation];
3156
+ return {
3157
+ ...summary(operation),
3158
+ method: spec.method,
3159
+ scope: spec.scope,
3160
+ inputSchema: z.toJSONSchema(toolInput(operation), { io: "input" })
3161
+ };
3162
+ }
3163
+ const terms = query.toLowerCase().split(/\s+/);
3164
+ return { operations: operationNames.map(summary).filter((row) => terms.every((term) => `${row.name} ${row.description}`.toLowerCase().includes(term))) };
3165
+ }
3166
+ const reply = async (name, work) => {
3167
+ try {
3168
+ return {
3169
+ content: [{
3170
+ type: "text",
3171
+ text: `${name} completed.`
3172
+ }],
3173
+ structuredContent: { result: await work() }
3174
+ };
3175
+ } catch (error) {
3176
+ return {
3177
+ isError: true,
3178
+ content: [{
3179
+ type: "text",
3180
+ text: JSON.stringify(errorEnvelope(error).body)
3181
+ }]
3182
+ };
3183
+ }
3184
+ };
3185
+ const runnerInput = {
3186
+ name: z.string().max(80).describe("Operation name from find_operation."),
3187
+ input: z.record(z.string(), z.unknown()).default({}).describe("The operation's input.")
3188
+ };
3124
3189
  function createMcp(auth, domain, access, config, limitWrite) {
3125
- const serve = (request, identity, limited = false) => createMcpHandler(() => {
3126
- const server = new McpServer({
3127
- name: "Kanvil",
3128
- version: applicationVersion
3190
+ const serve = (request, identity, limited = false) => {
3191
+ const execute = async (name, input) => {
3192
+ if (limited && operations[name].method !== "GET") limitWrite(identity.ownerId);
3193
+ const value = "view" in operations[name].input.shape ? {
3194
+ view: defaultView,
3195
+ ...input
3196
+ } : input;
3197
+ return compact(await domain.execute(name, value, identity), echoedFields(name));
3198
+ };
3199
+ const runner = (write) => (args) => reply(args.name, () => {
3200
+ const name = known(args.name);
3201
+ if (operations[name].method !== "GET" !== write) throw new ApiError(400, "invalid_request", `${name} ${write ? "only reads; use read_operation" : "writes; use run_operation"}`);
3202
+ return execute(name, args.input);
3129
3203
  });
3130
- for (const name of Object.keys(operations).filter(isOperation)) {
3131
- const spec = operations[name];
3132
- const inputSchema = spec.input.shape;
3133
- server.registerTool(name, {
3134
- description: spec.description,
3135
- inputSchema,
3136
- outputSchema: { result: spec.output },
3204
+ return createMcpHandler(() => {
3205
+ const server = new McpServer({
3206
+ name: "Kanvil",
3207
+ version: applicationVersion
3208
+ }, { instructions });
3209
+ for (const name of coreNames) {
3210
+ const spec = operations[name];
3211
+ const inputSchema = toolInput(name).shape;
3212
+ server.registerTool(name, {
3213
+ description: spec.description,
3214
+ inputSchema,
3215
+ annotations: {
3216
+ readOnlyHint: spec.method === "GET",
3217
+ destructiveHint: false,
3218
+ idempotentHint: spec.method === "GET",
3219
+ openWorldHint: false
3220
+ }
3221
+ }, (input) => reply(name, () => execute(name, input)));
3222
+ }
3223
+ server.registerTool("find_operation", {
3224
+ description: "Find an operation that is not a listed tool. `query` matches names and descriptions and marks each match as read or write; `name` returns one operation with its input JSON Schema, method, and scope.",
3225
+ inputSchema: {
3226
+ query: z.string().max(200).optional(),
3227
+ name: z.string().max(80).optional()
3228
+ },
3137
3229
  annotations: {
3138
- readOnlyHint: spec.method === "GET",
3139
- destructiveHint: /^(delete|remove|recover)_/.test(name),
3140
- idempotentHint: spec.method === "GET",
3230
+ readOnlyHint: true,
3231
+ idempotentHint: true,
3141
3232
  openWorldHint: false
3142
3233
  }
3143
- }, async (input) => {
3144
- try {
3145
- if (limited && spec.method !== "GET") limitWrite(identity.ownerId);
3146
- const result = await domain.execute(name, input, identity);
3147
- return {
3148
- content: [{
3149
- type: "text",
3150
- text: `${name} completed.`
3151
- }],
3152
- structuredContent: { result }
3153
- };
3154
- } catch (error) {
3155
- return {
3156
- isError: true,
3157
- content: [{
3158
- type: "text",
3159
- text: JSON.stringify(errorEnvelope(error).body)
3160
- }]
3161
- };
3234
+ }, (input) => reply("find_operation", () => findOperation(input)));
3235
+ server.registerTool("read_operation", {
3236
+ description: "Run a read operation found with find_operation.",
3237
+ inputSchema: runnerInput,
3238
+ annotations: {
3239
+ readOnlyHint: true,
3240
+ idempotentHint: true,
3241
+ openWorldHint: false
3162
3242
  }
3163
- });
3164
- }
3165
- return server;
3166
- }, { legacy: "stateless" }).fetch(request);
3243
+ }, runner(false));
3244
+ server.registerTool("run_operation", {
3245
+ description: "Run a write operation found with find_operation.",
3246
+ inputSchema: runnerInput,
3247
+ annotations: {
3248
+ readOnlyHint: false,
3249
+ destructiveHint: true,
3250
+ openWorldHint: false
3251
+ }
3252
+ }, runner(true));
3253
+ return server;
3254
+ }, { legacy: "stateless" }).fetch(request);
3255
+ };
3167
3256
  const oauth = requireMcpAuth(auth, async (request, claims) => {
3168
3257
  if (typeof claims.sub !== "string") throw new ApiError(401, "unauthorized", "A user subject is required");
3169
3258
  const granted = typeof claims.scope === "string" ? claims.scope.split(" ") : [];
@@ -3319,13 +3408,14 @@ async function initializeApplication(database, config, adapters, cleanup) {
3319
3408
  } }, { status: 400 });
3320
3409
  } });
3321
3410
  app.use("*", secureHeaders());
3322
- app.use("*", bodyLimit({
3411
+ const limitBody = bodyLimit({
3323
3412
  maxSize: 15e6,
3324
3413
  onError: () => Response.json({ error: {
3325
3414
  code: "payload_too_large",
3326
3415
  message: "The request exceeds the upload limit"
3327
3416
  } }, { status: 413 })
3328
- }));
3417
+ });
3418
+ app.use("*", (c, next) => c.req.path.startsWith("/api/uploads/") ? next() : limitBody(c, next));
3329
3419
  app.onError((error) => {
3330
3420
  if (error instanceof APIError && error.statusCode >= 400 && error.statusCode < 500) return Response.json({ error: {
3331
3421
  code: typeof error.body?.code === "string" ? error.body.code : "authentication_error",
@@ -3421,6 +3511,33 @@ async function initializeApplication(database, config, adapters, cleanup) {
3421
3511
  "Referrer-Policy": "no-referrer"
3422
3512
  } });
3423
3513
  });
3514
+ app.openAPIRegistry.registerPath({
3515
+ method: "put",
3516
+ path: "/api/uploads/{token}",
3517
+ request: {
3518
+ params: z$1.object({ token: z$1.string() }),
3519
+ body: { content: { "application/octet-stream": { schema: z$1.string() } } }
3520
+ },
3521
+ responses: {
3522
+ 200: { description: "The staged upload, or the attachment when the URL names a ticket" },
3523
+ 400: { description: "The body or its Content-Length is not the declared size" },
3524
+ 404: { description: "Unknown upload URL" },
3525
+ 410: { description: "The upload URL was already used or expired" }
3526
+ }
3527
+ });
3528
+ app.put("/api/uploads/:token", async (c) => Response.json(await receiveUpload(domain, c.req.param("token"), c.req.header("content-length"), async (limit) => {
3529
+ const chunks = [], reader = c.req.raw.body?.getReader();
3530
+ if (!reader) return Buffer.alloc(0);
3531
+ let total = 0;
3532
+ while (total <= limit) {
3533
+ const { done, value } = await reader.read();
3534
+ if (done) return Buffer.concat(chunks);
3535
+ chunks.push(value);
3536
+ total += value.length;
3537
+ }
3538
+ await reader.cancel();
3539
+ return Buffer.concat(chunks);
3540
+ })));
3424
3541
  app.openAPIRegistry.registerPath({
3425
3542
  method: "post",
3426
3543
  path: "/api/repository-hooks/{connectionId}",
@@ -3436,10 +3553,15 @@ async function initializeApplication(database, config, adapters, cleanup) {
3436
3553
  const result = await receiveRepositoryWebhook(domain, repositories, secrets, c.req.param("connectionId"), c.req.raw.headers, await c.req.text());
3437
3554
  return c.json(result, result.outcome === "duplicate" ? 200 : 202);
3438
3555
  });
3556
+ const intakeRoutes = [
3557
+ ["POST", /^\/api\/v1\/projects\/[^/]+\/tickets$/],
3558
+ ["POST", /^\/api\/v1\/projects\/[^/]+\/tickets\/[^/]+\/attachments$/],
3559
+ ["GET", /^\/api\/v1\/projects\/[^/]+\/tickets\/[^/]+\/attachments$/]
3560
+ ];
3439
3561
  app.use("/api/v1/*", async (c, next) => {
3440
3562
  access.checkOrigin(c.req.raw);
3441
3563
  const actor = await access.authenticate(c.req.raw);
3442
- if (intakeOnly(actor) && !(c.req.method === "POST" && /^\/api\/v1\/projects\/[^/]+\/tickets$/.test(c.req.path))) fail(403, "insufficient_scope", "Intake credentials can only create tickets");
3564
+ if (intakeOnly(actor) && !intakeRoutes.some(([method, path]) => c.req.method === method && path.test(c.req.path))) fail(403, "insufficient_scope", "Intake credentials can only create tickets and attach files to them");
3443
3565
  c.set("actor", actor);
3444
3566
  if (!actor.machine && ![
3445
3567
  "GET",
@@ -1,4 +1,4 @@
1
- import { n as createDatabase, t as readConfig } from "./config-CrJuk5mY.mjs";
1
+ import { n as createDatabase, t as readConfig } from "./config-CQVdVhXv.mjs";
2
2
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
3
3
  //#region src/cli/migrate.ts
4
4
  const database = createDatabase(readConfig());
@@ -1,6 +1,6 @@
1
- import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CrJuk5mY.mjs";
2
- import { A as hasAdminRole } from "./src-D80LNZKP.mjs";
3
- import { K as instanceSettings, p as fail, t as createAuth } from "./auth-Bm6cIseE.mjs";
1
+ import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CQVdVhXv.mjs";
2
+ import { M as hasAdminRole } from "./src-D70GmZ5R.mjs";
3
+ import { p as fail, q as instanceSettings, t as createAuth } from "./auth-D43XyHdK.mjs";
4
4
  import { and, eq, isNull, sql } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { parseArgs } from "node:util";