@ornncompute/cli 0.2.24 → 1.0.34

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 (90) hide show
  1. package/README.md +351 -426
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +305 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +681 -0
  6. package/src/admin-mutations.mjs +187 -0
  7. package/src/admin-node-diagnostics.mjs +412 -0
  8. package/src/admin-node-lifecycle.mjs +905 -0
  9. package/src/admin-shell.mjs +456 -0
  10. package/src/api-client.mjs +81 -28
  11. package/src/atomic-output.mjs +112 -0
  12. package/src/auth-store.mjs +182 -11
  13. package/src/capability-catalog.mjs +7 -0
  14. package/src/catalog-shell.mjs +1363 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +179 -62
  18. package/src/evidence-commands.mjs +1354 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/gpu-catalog.mjs +21 -0
  21. package/src/identity-file.mjs +28 -0
  22. package/src/interaction-runtime.mjs +310 -0
  23. package/src/kubernetes-commands.mjs +2235 -0
  24. package/src/launch-mode-handlers.mjs +119 -0
  25. package/src/machine-reference.mjs +285 -0
  26. package/src/mount-commands.mjs +1148 -0
  27. package/src/session-commands.mjs +565 -0
  28. package/src/slurm-commands.mjs +274 -0
  29. package/src/startup.mjs +34 -11
  30. package/src/storage-commands.mjs +1164 -0
  31. package/src/update.mjs +172 -24
  32. package/src/vm-commands.mjs +4308 -0
  33. package/src/vpn-secret-output.mjs +150 -0
  34. package/src/wait-runtime.mjs +231 -0
  35. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +96 -0
  36. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  37. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  38. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  39. package/vendor/capabilities/capabilities/admin.js +89 -0
  40. package/vendor/capabilities/capabilities/cli.js +156 -0
  41. package/vendor/capabilities/capabilities/clusters.js +150 -1
  42. package/vendor/capabilities/capabilities/commerce-market.js +330 -0
  43. package/vendor/capabilities/capabilities/compute.js +522 -0
  44. package/vendor/capabilities/capabilities/evidence.js +145 -0
  45. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  46. package/vendor/capabilities/capabilities/fleet.js +77 -25
  47. package/vendor/capabilities/capabilities/identity.js +114 -3
  48. package/vendor/capabilities/capabilities/listings.js +141 -144
  49. package/vendor/capabilities/capabilities/market-shared.js +44 -0
  50. package/vendor/capabilities/capabilities/markets.js +449 -0
  51. package/vendor/capabilities/capabilities/mounts.js +176 -0
  52. package/vendor/capabilities/capabilities/networks.js +1264 -0
  53. package/vendor/capabilities/capabilities/observability.js +38 -8
  54. package/vendor/capabilities/capabilities/tokens.js +32 -17
  55. package/vendor/capabilities/capabilities/volumes.js +178 -0
  56. package/vendor/capabilities/capability.js +21 -14
  57. package/vendor/capabilities/cli-catalog.js +67 -0
  58. package/vendor/capabilities/cli-migrations.js +261 -0
  59. package/vendor/capabilities/cli.js +7 -0
  60. package/vendor/capabilities/software-catalog.js +36 -0
  61. package/vendor/capabilities/spec-catalog.js +28 -5
  62. package/vendor/capabilities/supplier-directory.js +20 -0
  63. package/src/catalog-dispatch.mjs +0 -605
  64. package/src/color.mjs +0 -71
  65. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  66. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  67. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  68. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  69. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  70. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  71. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  72. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  73. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  74. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  75. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  76. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  77. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  78. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  79. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  80. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  81. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  82. package/vendor/capabilities/capability.d.ts +0 -93
  83. package/vendor/capabilities/catalog.d.ts +0 -3
  84. package/vendor/capabilities/catalog.js +0 -26
  85. package/vendor/capabilities/index.d.ts +0 -16
  86. package/vendor/capabilities/index.js +0 -16
  87. package/vendor/capabilities/names.d.ts +0 -6
  88. package/vendor/capabilities/role.d.ts +0 -6
  89. package/vendor/capabilities/role.js +0 -17
  90. package/vendor/capabilities/spec-catalog.d.ts +0 -78
@@ -1,142 +0,0 @@
1
- import { z } from "zod";
2
- export declare const gtmCompanyListInput: z.ZodObject<{
3
- q: z.ZodOptional<z.ZodString>;
4
- limit: z.ZodOptional<z.ZodNumber>;
5
- }, z.core.$strip>;
6
- export declare const gtmCompanyListCapability: import("../capability.ts").Capability<unknown, unknown>;
7
- export declare const gtmCompanyWriteInput: z.ZodObject<{
8
- confirm: z.ZodOptional<z.ZodBoolean>;
9
- name: z.ZodString;
10
- ownerTags: z.ZodDefault<z.ZodString>;
11
- notes: z.ZodOptional<z.ZodString>;
12
- companyId: z.ZodOptional<z.ZodNumber>;
13
- }, z.core.$strip>;
14
- export declare const gtmCompanyWriteCapability: import("../capability.ts").Capability<unknown, unknown>;
15
- export declare const gtmDealListInput: z.ZodObject<{
16
- q: z.ZodOptional<z.ZodString>;
17
- limit: z.ZodOptional<z.ZodNumber>;
18
- }, z.core.$strip>;
19
- export declare const gtmDealListCapability: import("../capability.ts").Capability<unknown, unknown>;
20
- export declare const gtmDealWriteInput: z.ZodObject<{
21
- dealId: z.ZodOptional<z.ZodNumber>;
22
- listingId: z.ZodOptional<z.ZodString>;
23
- customer: z.ZodOptional<z.ZodString>;
24
- owner: z.ZodOptional<z.ZodString>;
25
- confirm: z.ZodOptional<z.ZodBoolean>;
26
- }, z.core.$strip>;
27
- export declare const gtmDealWriteCapability: import("../capability.ts").Capability<unknown, unknown>;
28
- export declare const gtmCommentListInput: z.ZodObject<{
29
- dealId: z.ZodOptional<z.ZodNumber>;
30
- companyId: z.ZodOptional<z.ZodNumber>;
31
- taskId: z.ZodOptional<z.ZodNumber>;
32
- field: z.ZodOptional<z.ZodEnum<{
33
- name: "name";
34
- notes: "notes";
35
- owner_tags: "owner_tags";
36
- tickets: "tickets";
37
- }>>;
38
- }, z.core.$strip>;
39
- export declare const gtmCommentListCapability: import("../capability.ts").Capability<unknown, unknown>;
40
- export declare const gtmCommentWriteInput: z.ZodObject<{
41
- commentId: z.ZodOptional<z.ZodNumber>;
42
- parentId: z.ZodOptional<z.ZodNumber>;
43
- body: z.ZodString;
44
- bid: z.ZodDefault<z.ZodBoolean>;
45
- confirm: z.ZodOptional<z.ZodBoolean>;
46
- dealId: z.ZodOptional<z.ZodNumber>;
47
- companyId: z.ZodOptional<z.ZodNumber>;
48
- taskId: z.ZodOptional<z.ZodNumber>;
49
- field: z.ZodOptional<z.ZodEnum<{
50
- name: "name";
51
- notes: "notes";
52
- owner_tags: "owner_tags";
53
- tickets: "tickets";
54
- }>>;
55
- }, z.core.$strip>;
56
- export declare const gtmCommentWriteCapability: import("../capability.ts").Capability<unknown, unknown>;
57
- export declare const gtmWorkspaceShowInput: z.ZodObject<{}, z.core.$strip>;
58
- export declare const gtmWorkspaceShowCapability: import("../capability.ts").Capability<unknown, unknown>;
59
- export declare const gtmTaskListInput: z.ZodObject<{
60
- q: z.ZodOptional<z.ZodString>;
61
- limit: z.ZodOptional<z.ZodNumber>;
62
- }, z.core.$strip>;
63
- export declare const gtmTaskListCapability: import("../capability.ts").Capability<unknown, unknown>;
64
- export declare const gtmTaskWriteInput: z.ZodObject<{
65
- taskId: z.ZodOptional<z.ZodNumber>;
66
- title: z.ZodString;
67
- body: z.ZodDefault<z.ZodString>;
68
- companyId: z.ZodNumber;
69
- dealId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
70
- assignee: z.ZodString;
71
- status: z.ZodOptional<z.ZodEnum<{
72
- open: "open";
73
- done: "done";
74
- }>>;
75
- confirm: z.ZodOptional<z.ZodBoolean>;
76
- }, z.core.$strip>;
77
- export declare const gtmTaskWriteCapability: import("../capability.ts").Capability<unknown, unknown>;
78
- export declare const gtmCompanyDeleteInput: z.ZodObject<{
79
- [x: string]: z.ZodNumber | z.ZodOptional<z.ZodBoolean>;
80
- confirm: z.ZodOptional<z.ZodBoolean>;
81
- }, z.core.$strip>;
82
- export declare const gtmCompanyDeleteCapability: import("../capability.ts").Capability<unknown, unknown>;
83
- export declare const gtmCommentDeleteInput: z.ZodObject<{
84
- [x: string]: z.ZodNumber | z.ZodOptional<z.ZodBoolean>;
85
- confirm: z.ZodOptional<z.ZodBoolean>;
86
- }, z.core.$strip>;
87
- export declare const gtmCommentDeleteCapability: import("../capability.ts").Capability<unknown, unknown>;
88
- export declare const gtmTaskDeleteInput: z.ZodObject<{
89
- [x: string]: z.ZodNumber | z.ZodOptional<z.ZodBoolean>;
90
- confirm: z.ZodOptional<z.ZodBoolean>;
91
- }, z.core.$strip>;
92
- export declare const gtmTaskDeleteCapability: import("../capability.ts").Capability<unknown, unknown>;
93
- /**
94
- * Listing verbs. GTM never touches a Commerce table directly: these call
95
- * Commerce's own listing routes through the gateway, exactly as the staff app
96
- * does. Nothing here writes a gtm_ row, and nothing in the CRM constrains a
97
- * listing.
98
- */
99
- export declare const gtmListingListInput: z.ZodObject<{
100
- gpuType: z.ZodOptional<z.ZodString>;
101
- state: z.ZodOptional<z.ZodEnum<{
102
- delisted: "delisted";
103
- available: "available";
104
- internal: "internal";
105
- }>>;
106
- limit: z.ZodOptional<z.ZodNumber>;
107
- cursor: z.ZodOptional<z.ZodString>;
108
- }, z.core.$strip>;
109
- export declare const gtmListingListCapability: import("../capability.ts").Capability<unknown, unknown>;
110
- export declare const gtmListingUpdateInput: z.ZodObject<{
111
- listingId: z.ZodString;
112
- buyNowRatePerGpuHour: z.ZodOptional<z.ZodString>;
113
- costPerGpuHour: z.ZodOptional<z.ZodString>;
114
- monthToMonth: z.ZodOptional<z.ZodBoolean>;
115
- allowsDepositPlan: z.ZodOptional<z.ZodBoolean>;
116
- downPaymentPct: z.ZodOptional<z.ZodString>;
117
- capacityNodes: z.ZodOptional<z.ZodNumber>;
118
- state: z.ZodOptional<z.ZodEnum<{
119
- delisted: "delisted";
120
- available: "available";
121
- internal: "internal";
122
- }>>;
123
- confirm: z.ZodOptional<z.ZodBoolean>;
124
- }, z.core.$strip>;
125
- export declare const gtmListingUpdateCapability: import("../capability.ts").Capability<unknown, unknown>;
126
- export declare const gtmTaskActivityListInput: z.ZodObject<{
127
- taskId: z.ZodNumber;
128
- }, z.core.$strip>;
129
- export declare const gtmTaskActivityListCapability: import("../capability.ts").Capability<unknown, unknown>;
130
- /**
131
- * Bulk owner-tag reassignment. This is the one GTM edit that is genuinely
132
- * N-at-a-time — a book of companies moving to a new owner — and doing it one
133
- * confirmation at a time is both slow and easy to half-finish. Every other
134
- * bulk case is a loop over the single-record verbs.
135
- */
136
- export declare const gtmCompanyBulkRetagInput: z.ZodObject<{
137
- companyIds: z.ZodArray<z.ZodNumber>;
138
- ownerTags: z.ZodString;
139
- confirm: z.ZodOptional<z.ZodBoolean>;
140
- }, z.core.$strip>;
141
- export declare const gtmCompanyBulkRetagCapability: import("../capability.ts").Capability<unknown, unknown>;
142
- export declare const staffGTMCapabilities: import("../capability.ts").Capability<unknown, unknown>[];
@@ -1,521 +0,0 @@
1
- import { z } from "zod";
2
- import { defineStaffMcpCapability, preview } from "../capability.js";
3
- import { seg } from "./staff-shared.js";
4
- const companyFields = {
5
- name: z.string().min(1),
6
- ownerTags: z.string().default(""),
7
- /** The editable company summary that sits above the comment thread. */
8
- notes: z.string().optional(),
9
- };
10
- /** Narrowing every list accepts, so an agent need not hold the whole table. */
11
- const listFilterFields = {
12
- q: z.string().min(1).optional(),
13
- limit: z.number().int().positive().max(500).optional(),
14
- };
15
- function listQuery(parsed) {
16
- const parts = [];
17
- if (parsed.q)
18
- parts.push(`q=${encodeURIComponent(parsed.q)}`);
19
- if (parsed.limit)
20
- parts.push(`limit=${parsed.limit}`);
21
- return parts.length ? `?${parts.join("&")}` : "";
22
- }
23
- export const gtmCompanyListInput = z.object({ ...listFilterFields });
24
- export const gtmCompanyListCapability = defineStaffMcpCapability({
25
- id: "gtm.company.list",
26
- domain: "gtm",
27
- roles: ["reviewer", "admin"],
28
- description: "GTM staff only: list plaintext CRM companies, their owner tags and summary notes. Narrow with q, cap with limit. A company's running discussion is its comment thread.",
29
- mutation: "read",
30
- http: { method: "GET", path: "/internal/v1/gtm/companies" },
31
- input: gtmCompanyListInput,
32
- execute: (ctx, input) => ctx.fetch(`/internal/v1/gtm/companies${listQuery(gtmCompanyListInput.parse(input))}`, {
33
- method: "GET",
34
- }),
35
- });
36
- export const gtmCompanyWriteInput = z.object({
37
- companyId: z.number().int().positive().optional(),
38
- ...companyFields,
39
- confirm: z.boolean().optional(),
40
- });
41
- export const gtmCompanyWriteCapability = defineStaffMcpCapability({
42
- id: "gtm.company.write",
43
- domain: "gtm",
44
- roles: ["reviewer", "admin"],
45
- description: "GTM staff only: create a plaintext CRM company, or replace its name, owner tags and summary notes when companyId is supplied. Requires confirm: true.",
46
- mutation: "preview-confirm",
47
- http: { method: "POST", path: "/internal/v1/gtm/companies" },
48
- input: gtmCompanyWriteInput,
49
- execute: async (ctx, input) => {
50
- const parsed = gtmCompanyWriteInput.parse(input);
51
- const body = { name: parsed.name, owner_tags: parsed.ownerTags };
52
- if (parsed.notes !== undefined)
53
- body.notes = parsed.notes;
54
- if (!ctx.confirmed) {
55
- return preview("gtm.company.write", { companyId: parsed.companyId, ...body });
56
- }
57
- return ctx.fetch(parsed.companyId
58
- ? `/internal/v1/gtm/companies/${seg(String(parsed.companyId))}`
59
- : "/internal/v1/gtm/companies", { method: parsed.companyId ? "PATCH" : "POST", body });
60
- },
61
- });
62
- export const gtmDealListInput = z.object({ ...listFilterFields });
63
- export const gtmDealListCapability = defineStaffMcpCapability({
64
- id: "gtm.deal.list",
65
- domain: "gtm",
66
- roles: ["reviewer", "admin"],
67
- description: "GTM staff only: list manually entered deals. Each deal has only customer, Ornn owner, supplier, and GPU type. Narrow with q, cap with limit.",
68
- mutation: "read",
69
- http: { method: "GET", path: "/internal/v1/gtm/deals" },
70
- input: gtmDealListInput,
71
- execute: (ctx, input) => ctx.fetch(`/internal/v1/gtm/deals${listQuery(gtmDealListInput.parse(input))}`, {
72
- method: "GET",
73
- }),
74
- });
75
- export const gtmDealWriteInput = z.object({
76
- dealId: z.number().int().positive().optional(),
77
- /** Required to create: a deal is that Commerce listing's GTM record. Cannot change. */
78
- listingId: z.string().uuid().optional(),
79
- /** Optional extras, unspecified until filled in. "" clears one. */
80
- customer: z.string().optional(),
81
- owner: z.string().optional(),
82
- confirm: z.boolean().optional(),
83
- });
84
- export const gtmDealWriteCapability = defineStaffMcpCapability({
85
- id: "gtm.deal.write",
86
- domain: "gtm",
87
- roles: ["reviewer", "admin"],
88
- description: 'GTM staff only: a deal is the GTM record of one Commerce listing, and its comments hang off it. Create one with listingId (one deal per listing; a second is a conflict), or update one with dealId. customer and owner are optional; on update only the fields you pass change, and "" clears one. The listing cannot change. A bid is a comment with bid: true (see comment_write); the newest one is the current bid. Requires confirm: true.',
89
- mutation: "preview-confirm",
90
- http: { method: "POST", path: "/internal/v1/gtm/deals" },
91
- input: gtmDealWriteInput,
92
- execute: async (ctx, input) => {
93
- const parsed = gtmDealWriteInput.parse(input);
94
- if (parsed.dealId === undefined && parsed.listingId === undefined) {
95
- throw new Error("listingId is required to create a deal");
96
- }
97
- if (parsed.dealId !== undefined && parsed.listingId !== undefined) {
98
- throw new Error("a deal's listing cannot change; omit listingId when updating");
99
- }
100
- // Only what the caller passed is sent: omitted is unspecified on create
101
- // and untouched on update; "" clears.
102
- const body = {};
103
- for (const [key, value] of [
104
- ["listing_id", parsed.listingId],
105
- ["customer", parsed.customer],
106
- ["owner", parsed.owner],
107
- ]) {
108
- if (value !== undefined)
109
- body[key] = value;
110
- }
111
- if (!ctx.confirmed) {
112
- return preview("gtm.deal.write", { dealId: parsed.dealId, ...body });
113
- }
114
- return ctx.fetch(parsed.dealId
115
- ? `/internal/v1/gtm/deals/${seg(String(parsed.dealId))}`
116
- : "/internal/v1/gtm/deals", { method: parsed.dealId ? "PATCH" : "POST", body });
117
- },
118
- });
119
- /** Company fields that carry their own side thread. */
120
- const companyField = z.enum(["name", "owner_tags", "notes", "tickets"]);
121
- /** A thread hangs off one deal, one company (or one of its fields), or one ticket. */
122
- const commentThread = {
123
- dealId: z.number().int().positive().optional(),
124
- companyId: z.number().int().positive().optional(),
125
- taskId: z.number().int().positive().optional(),
126
- /** With companyId: that field's side thread instead of the main one. */
127
- field: companyField.optional(),
128
- };
129
- export const gtmCommentListInput = z.object(commentThread);
130
- export const gtmCommentListCapability = defineStaffMcpCapability({
131
- id: "gtm.comment.list",
132
- domain: "gtm",
133
- roles: ["reviewer", "admin"],
134
- description: "GTM staff only: list the plaintext comment thread for one deal, one company (or one company field with field), or one ticket (taskId), replies included. A reply carries parent_id.",
135
- mutation: "read",
136
- http: { method: "GET", path: "/internal/v1/gtm/deals/{dealId}/comments" },
137
- input: gtmCommentListInput,
138
- execute: (ctx, input) => {
139
- const parsed = gtmCommentListInput.parse(input);
140
- const query = parsed.field ? `?field=${encodeURIComponent(parsed.field)}` : "";
141
- return ctx.fetch(commentThreadPath(parsed) + query, { method: "GET" });
142
- },
143
- });
144
- export const gtmCommentWriteInput = z.object({
145
- ...commentThread,
146
- commentId: z.number().int().positive().optional(),
147
- /** Set to reply to an existing comment. Threads are one level deep. */
148
- parentId: z.number().int().positive().optional(),
149
- body: z.string().min(1),
150
- bid: z.boolean().default(false),
151
- confirm: z.boolean().optional(),
152
- });
153
- export const gtmCommentWriteCapability = defineStaffMcpCapability({
154
- id: "gtm.comment.write",
155
- domain: "gtm",
156
- roles: ["reviewer", "admin"],
157
- description: "GTM staff only: append a plaintext comment to a deal, a company (or one company field with field), or a ticket (taskId); reply to one with parentId, or replace one when commentId is supplied. bid=true adds only the BID tag and changes no other behavior. Mention a teammate by writing @name in the body. Requires confirm: true.",
158
- mutation: "preview-confirm",
159
- http: { method: "POST", path: "/internal/v1/gtm/deals/{dealId}/comments" },
160
- input: gtmCommentWriteInput,
161
- execute: async (ctx, input) => {
162
- const parsed = gtmCommentWriteInput.parse(input);
163
- if (!parsed.commentId && !parsed.dealId && !parsed.companyId && !parsed.taskId) {
164
- throw new Error("dealId, companyId or taskId is required when creating a comment");
165
- }
166
- const body = {
167
- body: parsed.body,
168
- tag: parsed.bid ? "BID" : null,
169
- };
170
- if (parsed.parentId) {
171
- body.parent_id = parsed.parentId;
172
- }
173
- if (parsed.field) {
174
- body.field = parsed.field;
175
- }
176
- if (!ctx.confirmed) {
177
- return preview("gtm.comment.write", {
178
- dealId: parsed.dealId,
179
- companyId: parsed.companyId,
180
- taskId: parsed.taskId,
181
- commentId: parsed.commentId,
182
- ...body,
183
- });
184
- }
185
- return ctx.fetch(parsed.commentId
186
- ? `/internal/v1/gtm/comments/${seg(String(parsed.commentId))}`
187
- : commentThreadPath(parsed), { method: parsed.commentId ? "PATCH" : "POST", body });
188
- },
189
- });
190
- /** A comment thread hangs off exactly one subject. */
191
- function commentThreadPath(parsed) {
192
- if (parsed.companyId) {
193
- return `/internal/v1/gtm/companies/${seg(String(parsed.companyId))}/comments`;
194
- }
195
- if (parsed.taskId) {
196
- return `/internal/v1/gtm/tasks/${seg(String(parsed.taskId))}/comments`;
197
- }
198
- if (parsed.dealId) {
199
- return `/internal/v1/gtm/deals/${seg(String(parsed.dealId))}/comments`;
200
- }
201
- throw new Error("dealId, companyId or taskId is required");
202
- }
203
- export const gtmWorkspaceShowInput = z.object({});
204
- export const gtmWorkspaceShowCapability = defineStaffMcpCapability({
205
- id: "gtm.workspace.show",
206
- domain: "gtm",
207
- roles: ["reviewer", "admin"],
208
- description: "GTM staff only: show companies tagged to the signed-in user, deals they own, mentions of them (answered once they comment in that thread), and tickets assigned to them.",
209
- mutation: "read",
210
- http: { method: "GET", path: "/internal/v1/gtm/me" },
211
- input: gtmWorkspaceShowInput,
212
- execute: (ctx) => ctx.fetch("/internal/v1/gtm/me", { method: "GET" }),
213
- });
214
- export const gtmTaskListInput = z.object({ ...listFilterFields });
215
- export const gtmTaskListCapability = defineStaffMcpCapability({
216
- id: "gtm.task.list",
217
- domain: "gtm",
218
- roles: ["reviewer", "admin"],
219
- description: "GTM staff only: list isolated CRM tickets tied to companies. Narrow with q, cap with limit.",
220
- mutation: "read",
221
- http: { method: "GET", path: "/internal/v1/gtm/tasks" },
222
- input: gtmTaskListInput,
223
- execute: (ctx, input) => ctx.fetch(`/internal/v1/gtm/tasks${listQuery(gtmTaskListInput.parse(input))}`, {
224
- method: "GET",
225
- }),
226
- });
227
- export const gtmTaskWriteInput = z
228
- .object({
229
- taskId: z.number().int().positive().optional(),
230
- title: z.string().min(1),
231
- body: z.string().default(""),
232
- companyId: z.number().int().positive(),
233
- dealId: z.number().int().positive().nullable().optional(),
234
- assignee: z.string().min(1),
235
- status: z.enum(["open", "done"]).optional(),
236
- confirm: z.boolean().optional(),
237
- })
238
- // Creating defaults to open; updating replaces the whole ticket, so an
239
- // omitted status would be sent as empty and rejected by Commerce. Catch it
240
- // here instead of after the caller has confirmed.
241
- .refine((value) => value.taskId === undefined || value.status !== undefined, {
242
- message: "status is required when updating a ticket",
243
- path: ["status"],
244
- });
245
- export const gtmTaskWriteCapability = defineStaffMcpCapability({
246
- id: "gtm.task.write",
247
- domain: "gtm",
248
- roles: ["reviewer", "admin"],
249
- description: "GTM staff only: create a company ticket, or replace one when taskId is supplied. Requires confirm: true.",
250
- mutation: "preview-confirm",
251
- http: { method: "POST", path: "/internal/v1/gtm/tasks" },
252
- input: gtmTaskWriteInput,
253
- execute: async (ctx, input) => {
254
- const parsed = gtmTaskWriteInput.parse(input);
255
- const body = {
256
- title: parsed.title,
257
- body: parsed.body,
258
- company_id: parsed.companyId,
259
- deal_id: parsed.dealId ?? null,
260
- assignee: parsed.assignee,
261
- status: parsed.status,
262
- };
263
- if (!ctx.confirmed) {
264
- return preview("gtm.task.write", { taskId: parsed.taskId, ...body });
265
- }
266
- return ctx.fetch(parsed.taskId
267
- ? `/internal/v1/gtm/tasks/${seg(String(parsed.taskId))}`
268
- : "/internal/v1/gtm/tasks", { method: parsed.taskId ? "PATCH" : "POST", body });
269
- },
270
- });
271
- /**
272
- * Delete verbs. Each takes the record id, previews first, and maps onto the
273
- * matching DELETE route. Deleting a deal removes its comments with it; a
274
- * company refuses while deals or tickets still name it.
275
- */
276
- function gtmDeleteCapability(entity, idKey, route, consequence) {
277
- const input = z.object({
278
- [idKey]: z.number().int().positive(),
279
- confirm: z.boolean().optional(),
280
- });
281
- const capability = defineStaffMcpCapability({
282
- id: `gtm.${entity}.delete`,
283
- domain: "gtm",
284
- roles: ["reviewer", "admin"],
285
- description: `GTM staff only: delete one ${entity}. ${consequence} Requires confirm: true.`,
286
- mutation: "preview-confirm",
287
- http: { method: "DELETE", path: `${route}/{${idKey}}` },
288
- input,
289
- execute: async (ctx, raw) => {
290
- const parsed = input.parse(raw);
291
- const id = parsed[idKey];
292
- if (!ctx.confirmed) {
293
- return preview(`gtm.${entity}.delete`, { [idKey]: id });
294
- }
295
- return ctx.fetch(`${route}/${seg(String(id))}`, { method: "DELETE" });
296
- },
297
- });
298
- return { input, capability };
299
- }
300
- const companyDelete = gtmDeleteCapability("company", "companyId", "/internal/v1/gtm/companies", "Refused while the company still has deals or tickets.");
301
- export const gtmCompanyDeleteInput = companyDelete.input;
302
- export const gtmCompanyDeleteCapability = companyDelete.capability;
303
- const commentDelete = gtmDeleteCapability("comment", "commentId", "/internal/v1/gtm/comments", "Removes only that comment.");
304
- export const gtmCommentDeleteInput = commentDelete.input;
305
- export const gtmCommentDeleteCapability = commentDelete.capability;
306
- const taskDelete = gtmDeleteCapability("task", "taskId", "/internal/v1/gtm/tasks", "Removes only that ticket.");
307
- export const gtmTaskDeleteInput = taskDelete.input;
308
- export const gtmTaskDeleteCapability = taskDelete.capability;
309
- /**
310
- * Listing verbs. GTM never touches a Commerce table directly: these call
311
- * Commerce's own listing routes through the gateway, exactly as the staff app
312
- * does. Nothing here writes a gtm_ row, and nothing in the CRM constrains a
313
- * listing.
314
- */
315
- export const gtmListingListInput = z.object({
316
- gpuType: z.string().optional(),
317
- state: z.enum(["available", "internal", "delisted"]).optional(),
318
- /** Commerce serves at most 100 per page; pass nextCursor back for the rest. */
319
- limit: z.number().int().positive().max(100).optional(),
320
- cursor: z.string().optional(),
321
- });
322
- export const gtmListingListCapability = defineStaffMcpCapability({
323
- id: "gtm.listing.list",
324
- domain: "gtm",
325
- roles: ["reviewer", "admin"],
326
- description: "GTM staff only: list Commerce listings (each can carry one GTM deal), optionally filtered by GPU type or state. Returns at most 100 per call with nextCursor; pass it back as cursor for the next page. Includes provider (staff-only). Read-only; this is Commerce's catalog, not CRM data.",
327
- mutation: "read",
328
- http: { method: "GET", path: "/internal/v1/listings" },
329
- input: gtmListingListInput,
330
- execute: async (ctx, input) => {
331
- const parsed = gtmListingListInput.parse(input);
332
- const parts = [`limit=${encodeURIComponent(String(parsed.limit ?? 50))}`];
333
- if (parsed.gpuType)
334
- parts.push(`q=${encodeURIComponent(parsed.gpuType)}`);
335
- if (parsed.state)
336
- parts.push(`state=${encodeURIComponent(parsed.state)}`);
337
- if (parsed.cursor)
338
- parts.push(`cursor=${encodeURIComponent(parsed.cursor)}`);
339
- const page = (await ctx.fetch(`/internal/v1/listings?${parts.join("&")}`, {
340
- method: "GET",
341
- }));
342
- return {
343
- listings: (page.listings ?? []).map(staffListing),
344
- nextCursor: page.nextCursor ?? null,
345
- };
346
- },
347
- });
348
- export const gtmListingUpdateInput = z.object({
349
- listingId: z.string().uuid(),
350
- buyNowRatePerGpuHour: z.string().optional(),
351
- costPerGpuHour: z.string().optional(),
352
- /** true = month to month, false = the fixed window the listing already carries. */
353
- monthToMonth: z.boolean().optional(),
354
- allowsDepositPlan: z.boolean().optional(),
355
- downPaymentPct: z.string().optional(),
356
- capacityNodes: z.number().int().positive().optional(),
357
- state: z.enum(["available", "internal", "delisted"]).optional(),
358
- confirm: z.boolean().optional(),
359
- });
360
- export const gtmListingUpdateCapability = defineStaffMcpCapability({
361
- id: "gtm.listing.update",
362
- domain: "gtm",
363
- roles: ["reviewer", "admin"],
364
- description: "GTM staff only: update a Commerce listing's commercial terms — buy-now rate, cost, term and deposit plan — plus capacity or state. Setting a buy-now rate also enables buy-now, otherwise the rate would be inert. Commerce owns the record; this is its own PATCH, not a CRM write. Requires confirm: true.",
365
- mutation: "preview-confirm",
366
- http: { method: "PATCH", path: "/internal/v1/listings/{listingId}" },
367
- input: gtmListingUpdateInput,
368
- execute: async (ctx, input) => {
369
- const parsed = gtmListingUpdateInput.parse(input);
370
- const body = listingPatchBody(parsed);
371
- if (Object.keys(body).length === 0) {
372
- throw new Error("supply at least one field to update");
373
- }
374
- if (!ctx.confirmed) {
375
- return preview("gtm.listing.update", { listingId: parsed.listingId, ...body });
376
- }
377
- return patchListing(ctx, parsed.listingId, body);
378
- },
379
- });
380
- /**
381
- * Strip true operator identity before it reaches an MCP client. Facility and
382
- * region are allowed; operator ids and provider names are not. Every listing
383
- * this module returns -- list, update, and the apply result -- goes through it.
384
- */
385
- /**
386
- * GTM tools are staff-only, behind the reviewer boundary, so they keep the
387
- * listing's provider -- a deal's supplier is its listing's. The operator
388
- * record's own id and legal name still stay out.
389
- */
390
- function staffListing(raw) {
391
- const listing = raw;
392
- const { operatorId: _operatorId, operatorName: _operatorName, ...safe } = listing;
393
- return safe;
394
- }
395
- /**
396
- * The listing PATCH is a compare-and-set: it refuses without the version the
397
- * row was read at. Read it here so a caller never has to guess, and a listing
398
- * changed in between fails loudly rather than being clobbered.
399
- */
400
- async function patchListing(ctx, listingId, body) {
401
- const current = (await ctx.fetch(`/internal/v1/listings/${seg(listingId)}`, {
402
- method: "GET",
403
- }));
404
- if (typeof current.version !== "number") {
405
- throw new Error("listing did not report a version to update against");
406
- }
407
- const updated = await ctx.fetch(`/internal/v1/listings/${seg(listingId)}`, {
408
- method: "PATCH",
409
- body: { ...body, version: current.version },
410
- });
411
- return staffListing(updated);
412
- }
413
- function listingPatchBody(parsed) {
414
- const body = {};
415
- if (parsed.buyNowRatePerGpuHour !== undefined) {
416
- body.buyNowRatePerGpuHour = parsed.buyNowRatePerGpuHour;
417
- // A rate with buy-now still disabled would change nothing a buyer sees.
418
- body.buyNowEnabled = true;
419
- }
420
- if (parsed.costPerGpuHour !== undefined)
421
- body.costPerGpuHour = parsed.costPerGpuHour;
422
- if (parsed.monthToMonth !== undefined)
423
- body.monthToMonth = parsed.monthToMonth;
424
- if (parsed.allowsDepositPlan !== undefined)
425
- body.allowsDepositPlan = parsed.allowsDepositPlan;
426
- if (parsed.downPaymentPct !== undefined)
427
- body.downPaymentPct = parsed.downPaymentPct;
428
- if (parsed.capacityNodes !== undefined)
429
- body.capacityNodes = parsed.capacityNodes;
430
- if (parsed.state !== undefined)
431
- body.state = parsed.state;
432
- return body;
433
- }
434
- export const gtmTaskActivityListInput = z.object({
435
- taskId: z.number().int().positive(),
436
- });
437
- export const gtmTaskActivityListCapability = defineStaffMcpCapability({
438
- id: "gtm.task.activity.list",
439
- domain: "gtm",
440
- roles: ["reviewer", "admin"],
441
- description: "GTM staff only: read one ticket's history — its creation and the field changes recorded against it, oldest first. Its comments are gtm.comment.list with taskId.",
442
- mutation: "read",
443
- http: { method: "GET", path: "/internal/v1/gtm/tasks/{taskId}/activity" },
444
- input: gtmTaskActivityListInput,
445
- execute: (ctx, input) => {
446
- const parsed = gtmTaskActivityListInput.parse(input);
447
- return ctx.fetch(`/internal/v1/gtm/tasks/${seg(String(parsed.taskId))}/activity`, {
448
- method: "GET",
449
- });
450
- },
451
- });
452
- /**
453
- * Bulk owner-tag reassignment. This is the one GTM edit that is genuinely
454
- * N-at-a-time — a book of companies moving to a new owner — and doing it one
455
- * confirmation at a time is both slow and easy to half-finish. Every other
456
- * bulk case is a loop over the single-record verbs.
457
- */
458
- export const gtmCompanyBulkRetagInput = z.object({
459
- companyIds: z.array(z.number().int().positive()).min(1).max(200),
460
- ownerTags: z.string(),
461
- confirm: z.boolean().optional(),
462
- });
463
- export const gtmCompanyBulkRetagCapability = defineStaffMcpCapability({
464
- id: "gtm.company.bulk_retag",
465
- domain: "gtm",
466
- roles: ["reviewer", "admin"],
467
- description: "GTM staff only: set the same owner tags on many companies at once, for moving a book to a new owner. Names, notes and comments are untouched. The preview lists every company it would change. Requires confirm: true.",
468
- mutation: "preview-confirm",
469
- http: { method: "PATCH", path: "/internal/v1/gtm/companies/{companyId}" },
470
- input: gtmCompanyBulkRetagInput,
471
- execute: async (ctx, input) => {
472
- const parsed = gtmCompanyBulkRetagInput.parse(input);
473
- const ids = [...new Set(parsed.companyIds)];
474
- if (!ctx.confirmed) {
475
- return preview("gtm.company.bulk_retag", { companyIds: ids, ownerTags: parsed.ownerTags });
476
- }
477
- // PATCH replaces the company's name as well, so the current names are read
478
- // once up front rather than per row. Omitting notes preserves them.
479
- // Commerce owns no bulk route, so partial progress is reported rather than
480
- // rolled back; re-running is safe.
481
- const all = (await ctx.fetch("/internal/v1/gtm/companies", { method: "GET" }));
482
- const names = new Map(all.map((company) => [company.id, company.name]));
483
- const updated = [];
484
- const failed = [];
485
- for (const companyId of ids) {
486
- const name = names.get(companyId);
487
- if (name === undefined) {
488
- failed.push({ companyId, error: "no such company" });
489
- continue;
490
- }
491
- try {
492
- updated.push(await ctx.fetch(`/internal/v1/gtm/companies/${seg(String(companyId))}`, {
493
- method: "PATCH",
494
- body: { name, owner_tags: parsed.ownerTags },
495
- }));
496
- }
497
- catch (error) {
498
- failed.push({ companyId, error: error instanceof Error ? error.message : String(error) });
499
- }
500
- }
501
- return { updated, failed };
502
- },
503
- });
504
- export const staffGTMCapabilities = [
505
- gtmCompanyListCapability,
506
- gtmCompanyWriteCapability,
507
- gtmCompanyDeleteCapability,
508
- gtmCompanyBulkRetagCapability,
509
- gtmDealListCapability,
510
- gtmDealWriteCapability,
511
- gtmCommentListCapability,
512
- gtmCommentWriteCapability,
513
- gtmCommentDeleteCapability,
514
- gtmTaskListCapability,
515
- gtmTaskWriteCapability,
516
- gtmTaskDeleteCapability,
517
- gtmTaskActivityListCapability,
518
- gtmListingListCapability,
519
- gtmListingUpdateCapability,
520
- gtmWorkspaceShowCapability,
521
- ];