@moderniser/plugin 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,13 +1,8 @@
1
1
  export declare const API: () => string;
2
- export declare class ApiError extends Error {
3
- status: number;
4
- constructor(status: number, message: string);
5
- }
6
2
  export declare function setStoredKey(key: string): void;
7
3
  export declare function storedKey(): string | undefined;
8
- export declare function api<T>(path: string, init?: RequestInit): Promise<T>;
9
- export declare const get: <T>(path: string) => Promise<T>;
10
- export declare const post: <T>(path: string, body: unknown) => Promise<T>;
11
- export declare const patch: <T>(path: string, body: unknown) => Promise<T>;
12
- export declare const del: <T>(path: string) => Promise<T>;
13
- export declare const json: (value: unknown) => string;
4
+ export declare function resolveKey(): string | undefined;
5
+ export declare const COMPANY_HEADER = "x-moderniser-company";
6
+ export declare function setStoredCompany(companyId: string): void;
7
+ export declare function resolveCompany(): string | undefined;
8
+ export declare function companyHeaders(): Record<string, string>;
package/dist/index.js CHANGED
@@ -2,19 +2,10 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // packages/plugin/src/client.ts
5
- import { readFileSync } from "node:fs";
5
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { join } from "node:path";
8
8
  var API = () => (process.env.MODERNISER_API_URL ?? "https://v2.moderniser.ai/api").replace(/\/+$/, "");
9
- var BASE = API;
10
-
11
- class ApiError extends Error {
12
- status;
13
- constructor(status, message) {
14
- super(message);
15
- this.status = status;
16
- }
17
- }
18
9
  var stored;
19
10
  function setStoredKey(key) {
20
11
  stored = key;
@@ -31,61 +22,56 @@ function fromAuthFile() {
31
22
  if (fileKey !== undefined)
32
23
  return fileKey ?? undefined;
33
24
  fileKey = null;
34
- let raw;
35
- try {
36
- raw = readFileSync(authFilePath(), "utf8");
37
- } catch {
38
- return;
39
- }
40
25
  try {
41
- const entry = JSON.parse(raw)["moderniser"];
42
- if (!entry || typeof entry !== "object")
43
- return;
44
- const { type, key } = entry;
45
- if (type !== "api" || typeof key !== "string" || key.length === 0)
46
- return;
47
- fileKey = key;
48
- return key;
49
- } catch {
50
- return;
51
- }
26
+ const parsed = JSON.parse(readFileSync(authFilePath(), "utf8"));
27
+ const entry = parsed?.moderniser;
28
+ if (typeof entry === "object" && entry !== null && entry.type === "api" && typeof entry.key === "string" && entry.key.length > 0) {
29
+ fileKey = entry.key;
30
+ }
31
+ } catch {}
32
+ return fileKey ?? undefined;
52
33
  }
53
- function token() {
54
- const value = process.env.MODERNISER_API_KEY ?? stored ?? fromAuthFile();
55
- if (!value) {
56
- throw new Error(`Not signed in to Moderniser. Run: moderniser providers login
57
- ` + "(or set MODERNISER_API_KEY to a token from Settings in the web app).");
58
- }
59
- return value;
34
+ function resolveKey() {
35
+ return process.env.MODERNISER_API_KEY ?? stored ?? fromAuthFile();
36
+ }
37
+ var COMPANY_HEADER = "x-moderniser-company";
38
+ function companyFilePath() {
39
+ const data = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share");
40
+ return join(data, "moderniser", "company.json");
60
41
  }
61
- async function api(path, init = {}) {
62
- const bearer = token();
63
- let res;
42
+ var storedCompany;
43
+ function setStoredCompany(companyId) {
44
+ storedCompany = companyId;
45
+ fileCompany = companyId;
64
46
  try {
65
- res = await fetch(`${BASE()}${path}`, {
66
- ...init,
67
- headers: {
68
- "Content-Type": "application/json",
69
- Authorization: `Bearer ${bearer}`,
70
- ...init.headers
71
- },
72
- signal: AbortSignal.timeout(30000)
73
- });
74
- } catch (error) {
75
- throw new ApiError(0, `Could not reach Moderniser at ${BASE()}: ${error.message}`);
76
- }
77
- if (res.status === 204)
78
- return;
79
- const body = await res.json().catch(() => ({}));
80
- if (!res.ok) {
81
- throw new ApiError(res.status, body.error ?? `Request failed with ${res.status}`);
82
- }
83
- return body;
47
+ const path = companyFilePath();
48
+ mkdirSync(join(path, ".."), { recursive: true });
49
+ const temp = `${path}.${process.pid}.tmp`;
50
+ writeFileSync(temp, JSON.stringify({ companyId }, null, 2) + `
51
+ `, { mode: 384 });
52
+ renameSync(temp, path);
53
+ } catch {}
54
+ }
55
+ var fileCompany;
56
+ function companyFromFile() {
57
+ if (fileCompany !== undefined)
58
+ return fileCompany ?? undefined;
59
+ fileCompany = null;
60
+ try {
61
+ const parsed = JSON.parse(readFileSync(companyFilePath(), "utf8"));
62
+ if (typeof parsed?.companyId === "string" && parsed.companyId.length > 0) {
63
+ fileCompany = parsed.companyId;
64
+ }
65
+ } catch {}
66
+ return fileCompany ?? undefined;
67
+ }
68
+ function resolveCompany() {
69
+ return process.env.MODERNISER_COMPANY ?? storedCompany ?? companyFromFile();
70
+ }
71
+ function companyHeaders() {
72
+ const company = resolveCompany();
73
+ return company ? { [COMPANY_HEADER]: company } : {};
84
74
  }
85
- var get = (path) => api(path);
86
- var post = (path, body) => api(path, { method: "POST", body: JSON.stringify(body) });
87
- var patch = (path, body) => api(path, { method: "PATCH", body: JSON.stringify(body) });
88
- var json = (value) => JSON.stringify(value, null, 2);
89
75
 
90
76
  // packages/plugin/src/auth.ts
91
77
  async function start() {
@@ -119,7 +105,7 @@ async function poll(deviceCode, expiresIn, intervalSeconds) {
119
105
  continue;
120
106
  }
121
107
  if (body.token)
122
- return body.token;
108
+ return { token: body.token, companyId: body.companyId };
123
109
  if (body.error === "authorization_pending")
124
110
  continue;
125
111
  if (body.error === "slow_down") {
@@ -141,14 +127,16 @@ function moderniserAuth() {
141
127
  const started = await start();
142
128
  return {
143
129
  url: started.verificationUrl,
144
- instructions: `Check that your browser shows this code: ${started.userCode}`,
130
+ instructions: `Check that your browser shows this code: ${started.userCode}. After signing in for the first time, restart to pick up the Moderniser tools.`,
145
131
  method: "auto",
146
132
  async callback() {
147
- const token2 = await poll(started.deviceCode, started.expiresIn, started.interval);
148
- if (!token2)
133
+ const collected = await poll(started.deviceCode, started.expiresIn, started.interval);
134
+ if (!collected)
149
135
  return { type: "failed" };
150
- setStoredKey(token2);
151
- return { type: "success", key: token2 };
136
+ setStoredKey(collected.token);
137
+ if (collected.companyId)
138
+ setStoredCompany(collected.companyId);
139
+ return { type: "success", key: collected.token };
152
140
  }
153
141
  };
154
142
  }
@@ -176,7 +164,7 @@ function moderniserContext() {
176
164
  "- Task: one unit of work a team would plan together.",
177
165
  "- Subtask: one concrete change in one codebase, small enough to finish and check off.",
178
166
  "",
179
- "When the user says project, task, board or ticket, they mean Moderniser's — " + "reach for the board tools (list_projects first, then create_task / " + "update_task / create_subtask / …). Do not answer by scaffolding files or " + "writing a spec document instead, unless a document is what they actually asked for."
167
+ "When the user says project, task, board or ticket, they mean Moderniser's — " + "reach for the board tools (moderniser_list_projects first, then " + "moderniser_create_task / moderniser_update_task / moderniser_create_subtask / …). " + "Do not answer by scaffolding files or writing a spec document instead, " + "unless a document is what they actually asked for."
180
168
  ];
181
169
  const workspace = process.env.MODERNISER_WORKSPACE_NAME;
182
170
  const company = process.env.MODERNISER_COMPANY_NAME;
@@ -194,212 +182,6 @@ function moderniserContext() {
194
182
  `);
195
183
  }
196
184
 
197
- // packages/plugin/src/tools-boards.ts
198
- import { tool } from "@opencode-ai/plugin";
199
- var z = tool.schema;
200
- var STATUS = ["todo", "in_progress", "in_review", "blocked", "done"];
201
- var TYPE = ["feature", "bug", "improvement"];
202
- var FILES_TO_MODIFY = "Paths you have actually opened, written exactly as you reached them from the folder this session started in — 'orders/src/Order.java', not an absolute path and not a path you have rewritten. When a task spans two codebases the leading folder is the only thing telling a reader which one a change is in. Leave this empty rather than guessing.";
203
- function commentPath(taskId, subtaskId) {
204
- if (Boolean(taskId) === Boolean(subtaskId)) {
205
- throw new Error("Pass exactly one of taskId or subtaskId");
206
- }
207
- return taskId ? `/projects/tasks/${taskId}/comments` : `/projects/subtasks/${subtaskId}/comments`;
208
- }
209
- var boardTools = {
210
- list_projects: tool({
211
- description: "List the projects belonging to the caller's company. Call this first — every task belongs to a project, and you need a project id before you can create one.",
212
- args: {},
213
- async execute() {
214
- return json((await get("/projects")).projects);
215
- }
216
- }),
217
- create_project: tool({
218
- description: "Create a project. A project is an area of work, and it may span several codebases — a checkout modernisation touches the storefront and the system that receives the order. Check list_projects first, since a duplicate fragments the board.",
219
- args: {
220
- name: z.string().describe("Short name, e.g. 'Checkout modernisation'"),
221
- description: z.string().describe("What this covers and why it is being worked on").optional(),
222
- keyPrefix: z.string().regex(/^[A-Z]{3}$/).describe("Three uppercase letters used to key tasks, e.g. 'CAD' gives CAD-1. Omit it and one is derived from the name.").optional()
223
- },
224
- async execute(args) {
225
- return json((await post("/projects", args)).project);
226
- }
227
- }),
228
- list_tasks: tool({
229
- description: "List the tasks in a project with their status. Use this to see what already exists before creating more.",
230
- args: {
231
- projectId: z.string().describe("Project id from list_projects"),
232
- status: z.string().describe(`Only tasks in this status. Defaults: ${STATUS.join(", ")}; a project may define its own`).optional()
233
- },
234
- async execute({ projectId, status }) {
235
- const query = status ? `?status=${status}` : "";
236
- return json((await get(`/projects/${projectId}/tasks${query}`)).tasks);
237
- }
238
- }),
239
- get_task: tool({
240
- description: "Fetch one task with all of its subtasks. Use this before updating anything, so you are editing against the current state rather than what you remember.",
241
- args: { taskId: z.string().describe("Task id") },
242
- async execute(args) {
243
- return json((await get(`/projects/tasks/${args.taskId}`)).task);
244
- }
245
- }),
246
- create_task: tool({
247
- description: "Create a task: one unit of work a team would plan together, such as 'Upgrade to SAP Commerce 2211'. Read the relevant code before writing the description — a task that describes the actual state of the code is worth keeping, one written from assumption is not. Break it into subtasks afterwards with create_subtask.",
248
- args: {
249
- projectId: z.string().describe("Project id from list_projects"),
250
- title: z.string().max(300).describe("Imperative and specific: 'Replace deprecated FlexibleSearch calls'"),
251
- description: z.string().describe("What needs doing and why, including what you found in the code — current version, affected modules, known blockers"),
252
- type: z.string().describe(`Task type. Defaults to feature; usual values: ${TYPE.join(", ")}; a project may define its own`).optional()
253
- },
254
- async execute({ projectId, ...body }) {
255
- return json((await post(`/projects/${projectId}/tasks`, body)).task);
256
- }
257
- }),
258
- update_task: tool({
259
- description: "Update a task's title, description, type or status. Move it to in_progress when work starts. Prefer in_review over done when you have finished but a person has not checked it — done is a human's word. Use blocked when you cannot proceed, and say why in a comment.",
260
- args: {
261
- taskId: z.string().describe("Task id"),
262
- title: z.string().max(300).describe("New title").optional(),
263
- description: z.string().describe("New description").optional(),
264
- type: z.string().describe(`New type (usual values: ${TYPE.join(", ")}; a project may define its own)`).optional(),
265
- status: z.string().describe(`New status (defaults: ${STATUS.join(", ")}; a project may define its own — a 400 lists the real ones)`).optional()
266
- },
267
- async execute({ taskId, ...body }) {
268
- return json((await patch(`/projects/tasks/${taskId}`, body)).task);
269
- }
270
- }),
271
- list_subtasks: tool({
272
- description: "List the subtasks of a task, in order.",
273
- args: { taskId: z.string().describe("Task id") },
274
- async execute(args) {
275
- return json((await get(`/projects/tasks/${args.taskId}/subtasks`)).subtasks);
276
- }
277
- }),
278
- create_subtask: tool({
279
- description: "Add a subtask: one concrete change a developer could pick up and finish, in one codebase. Populate filesToModify with paths you have actually opened; leave it empty rather than filling it with guesses. Work that genuinely spans two codebases is two subtasks, which is also what keeps 'done' honest.",
280
- args: {
281
- taskId: z.string().describe("Task id"),
282
- title: z.string().max(300).describe("One specific change"),
283
- description: z.string().describe("What to change and how you know it needs changing"),
284
- filesToModify: z.array(z.string()).describe(FILES_TO_MODIFY).optional(),
285
- implementationNotes: z.string().describe("Anything that saves the next person a search: the symbol to grep for, the call sites, the order to do things in").optional(),
286
- order: z.number().int().describe("Position in the list. Lower runs first.").optional()
287
- },
288
- async execute({ taskId, ...body }) {
289
- return json((await post(`/projects/tasks/${taskId}/subtasks`, body)).subtask);
290
- }
291
- }),
292
- update_subtask: tool({
293
- description: "Update a subtask. Use this to mark one done as you finish it, so the board reflects reality while the work is happening rather than after.",
294
- args: {
295
- subtaskId: z.string().describe("Subtask id"),
296
- title: z.string().max(300).describe("New title").optional(),
297
- description: z.string().describe("New description").optional(),
298
- status: z.string().describe(`New status (defaults: ${STATUS.join(", ")}; a project may define its own — a 400 lists the real ones)`).optional(),
299
- filesToModify: z.array(z.string()).describe(FILES_TO_MODIFY).optional(),
300
- implementationNotes: z.string().describe("Notes for whoever picks this up").optional(),
301
- order: z.number().int().describe("Position in the list").optional()
302
- },
303
- async execute({ subtaskId, ...body }) {
304
- return json((await patch(`/projects/subtasks/${subtaskId}`, body)).subtask);
305
- }
306
- }),
307
- list_comments: tool({
308
- description: "Read the comment thread on a task or a subtask. Do this before acting on work someone else planned — a correction from a human usually lives here rather than in the description. Pass exactly one of taskId or subtaskId.",
309
- args: {
310
- taskId: z.string().describe("Task id").optional(),
311
- subtaskId: z.string().describe("Subtask id").optional()
312
- },
313
- async execute(args) {
314
- const path = commentPath(args.taskId, args.subtaskId);
315
- return json((await get(path)).comments);
316
- }
317
- }),
318
- add_comment: tool({
319
- description: "Leave a comment on a task or subtask. Use it to record what you found, what you changed, or why you departed from the plan — not to restate the description. Comments you write are labelled as coming from the agent. Pass exactly one of taskId or subtaskId.",
320
- args: {
321
- body: z.string().max(1e4).describe("The comment"),
322
- taskId: z.string().describe("Task id").optional(),
323
- subtaskId: z.string().describe("Subtask id").optional()
324
- },
325
- async execute(args) {
326
- const path = commentPath(args.taskId, args.subtaskId);
327
- return json((await post(path, { body: args.body, source: "agent" })).comment);
328
- }
329
- })
330
- };
331
-
332
- // packages/plugin/src/tools-context-hub.ts
333
- import { tool as tool2 } from "@opencode-ai/plugin";
334
- var z2 = tool2.schema;
335
- var contextHubTools = {
336
- search_context_hub: tool2({
337
- description: "Search the company's internal documentation. Run this before answering a question about how something works here — the wiki usually explains the intent that the code only implies, and it is where conventions, past decisions and known traps are written down. Searches by meaning as well as by keyword, so a question phrased differently to the page still finds it. Results carry a score and a staleness flag; a stale page is past its review date and may be wrong. Follow up with read_context_hub_page for the full text.",
338
- args: {
339
- query: z2.string().describe("What you want to know. A phrase or question works better than a single word."),
340
- mode: z2.enum(["hybrid", "keyword", "semantic"]).describe("hybrid (default) searches by meaning and by keyword. Use keyword when looking for an exact identifier, class name or error string.").optional(),
341
- spaceId: z2.string().describe("Optional: restrict to one space, from list_context_hub_spaces").optional(),
342
- tag: z2.string().describe("Optional: restrict to pages carrying this tag").optional(),
343
- limit: z2.number().int().describe("Maximum results, default 20, maximum 50").optional()
344
- },
345
- async execute(args) {
346
- const params = new URLSearchParams({ q: args.query });
347
- if (args.mode)
348
- params.set("mode", args.mode);
349
- if (args.spaceId)
350
- params.set("spaceId", args.spaceId);
351
- if (args.tag)
352
- params.set("tag", args.tag);
353
- if (args.limit)
354
- params.set("limit", String(args.limit));
355
- return json((await get(`/context-hub/search?${params}`)).results);
356
- }
357
- }),
358
- list_context_hub_spaces: tool2({
359
- description: "List the spaces in the Context Hub. A space groups related pages — typically one per system, team or imported wiki. Use it to narrow a search when a keyword is too common.",
360
- args: {},
361
- async execute() {
362
- return json((await get("/context-hub/spaces")).spaces);
363
- }
364
- }),
365
- list_context_hub_pages: tool2({
366
- description: "List the pages in a space, with titles and when they were last updated. Bodies are not included — read_context_hub_page fetches those.",
367
- args: { spaceId: z2.string().describe("Space id from list_context_hub_spaces") },
368
- async execute(args) {
369
- return json((await get(`/context-hub/spaces/${args.spaceId}/pages`)).pages);
370
- }
371
- }),
372
- read_context_hub_page: tool2({
373
- description: "Read one page in full. Cite what you found from it when it changes your answer, so the person can check the source — the wiki can be out of date, and saying where a claim came from is what lets them notice.",
374
- args: { pageId: z2.string().describe("Page id from search or list") },
375
- async execute(args) {
376
- return json((await get(`/context-hub/pages/${args.pageId}`)).page);
377
- }
378
- }),
379
- create_context_hub_page: tool2({
380
- description: "Write a new page. Use this when you have worked something out that the next person would otherwise have to rediscover — a migration procedure, why an approach was rejected, how two systems actually talk to each other. Search first: adding a second page on a subject that already has one makes both less trustworthy.",
381
- args: {
382
- spaceId: z2.string().describe("Space to create the page in"),
383
- title: z2.string().max(300).describe("Specific and searchable: 'Upgrading SAP Commerce 1905 to 2211'"),
384
- content: z2.string().describe("Markdown body")
385
- },
386
- async execute({ spaceId, ...body }) {
387
- return json((await post(`/context-hub/spaces/${spaceId}/pages`, body)).page);
388
- }
389
- }),
390
- update_context_hub_page: tool2({
391
- description: "Replace a page's title or body. The content you send replaces what is there — read the page first and return the whole document with your changes folded in, or you will delete the parts you did not mention.",
392
- args: {
393
- pageId: z2.string().describe("Page id"),
394
- title: z2.string().max(300).describe("New title").optional(),
395
- content: z2.string().describe("The complete new markdown body, not a fragment").optional()
396
- },
397
- async execute({ pageId, ...body }) {
398
- return json((await patch(`/context-hub/pages/${pageId}`, body)).page);
399
- }
400
- })
401
- };
402
-
403
185
  // packages/plugin/src/index.ts
404
186
  var API2 = () => (process.env.MODERNISER_API_URL ?? "https://v2.moderniser.ai/api").replace(/\/+$/, "");
405
187
  var KEY = () => process.env.MODERNISER_API_KEY;
@@ -416,7 +198,7 @@ async function call(path, body) {
416
198
  try {
417
199
  return await fetch(`${API2()}${path}`, {
418
200
  method: "POST",
419
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
201
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}`, ...companyHeaders() },
420
202
  body: JSON.stringify(body),
421
203
  signal: AbortSignal.timeout(1e4)
422
204
  });
@@ -424,27 +206,62 @@ async function call(path, body) {
424
206
  return null;
425
207
  }
426
208
  }
209
+ function positiveInt(value) {
210
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
211
+ }
212
+ async function gatewayProduct() {
213
+ const key = KEY() ?? storedKey();
214
+ if (!key)
215
+ return {};
216
+ try {
217
+ const res = await fetch(`${GATEWAY()}/models`, {
218
+ headers: { Authorization: `Bearer ${key}`, ...companyHeaders() },
219
+ signal: AbortSignal.timeout(5000)
220
+ });
221
+ if (!res.ok)
222
+ return {};
223
+ const entry = (await res.json())?.data?.[0];
224
+ if (!entry)
225
+ return {};
226
+ const efforts = Array.isArray(entry.efforts) ? entry.efforts.filter((e) => typeof e === "string" && e.length > 0) : undefined;
227
+ return {
228
+ contextWindow: positiveInt(entry.context_window),
229
+ maxOutputTokens: positiveInt(entry.max_output_tokens),
230
+ efforts: efforts?.length ? efforts : undefined
231
+ };
232
+ } catch {
233
+ return {};
234
+ }
235
+ }
427
236
  var registered = new Set;
428
237
  var reported = new Map;
429
238
  var ModerniserPlugin = async () => {
430
239
  return {
431
240
  config: async (config) => {
432
241
  const modalities = { input: ["text", "image"], output: ["text"] };
242
+ const product = await gatewayProduct();
243
+ const efforts = (product.efforts ?? ["high"]).filter((effort) => effort !== "default");
433
244
  const models = {
434
- emm: { name: "Enterprise Modernisation Model", tool_call: true, reasoning: true, modalities },
435
- "emm-high": {
436
- name: "Enterprise Modernisation Model (deep reasoning)",
245
+ emm: {
246
+ name: "Enterprise Modernisation Model",
437
247
  tool_call: true,
438
248
  reasoning: true,
439
- modalities
249
+ modalities,
250
+ variants: Object.fromEntries(efforts.map((effort) => [effort, { reasoning_effort: effort }])),
251
+ ...product.contextWindow && product.maxOutputTokens ? { limit: { context: product.contextWindow, output: product.maxOutputTokens } } : {}
440
252
  }
441
253
  };
442
254
  const key = KEY();
255
+ const company = companyHeaders();
443
256
  config.provider ??= {};
444
257
  config.provider[PROVIDER_ID] ??= {
445
258
  name: "Moderniser",
446
259
  npm: "@ai-sdk/openai-compatible",
447
- options: { baseURL: GATEWAY(), ...key ? { apiKey: key } : {} },
260
+ options: {
261
+ baseURL: GATEWAY(),
262
+ ...key ? { apiKey: key } : {},
263
+ ...Object.keys(company).length ? { headers: company } : {}
264
+ },
448
265
  models
449
266
  };
450
267
  config.enabled_providers ??= [PROVIDER_ID];
@@ -453,8 +270,17 @@ var ModerniserPlugin = async () => {
453
270
  config.model ??= requested || DEFAULT_MODEL;
454
271
  config.small_model ??= config.model;
455
272
  }
273
+ const mcpKey = resolveKey();
274
+ if (mcpKey) {
275
+ config.mcp ??= {};
276
+ config.mcp[PROVIDER_ID] ??= {
277
+ type: "remote",
278
+ url: `${API2()}/mcp`,
279
+ headers: { Authorization: `Bearer ${mcpKey}`, ...companyHeaders() },
280
+ oauth: false
281
+ };
282
+ }
456
283
  },
457
- tool: { ...boardTools, ...contextHubTools },
458
284
  "experimental.chat.system.transform": async (_input, output) => {
459
285
  output.system.push(moderniserContext());
460
286
  },
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@moderniser/plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
- "description": "Moderniser plugin for opencode: points inference at the gateway, carries the board and Context Hub tools, and reports session telemetry",
5
+ "description": "Moderniser plugin for opencode: points inference at the gateway, declares the Moderniser remote MCP server, and reports session telemetry",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -1,2 +0,0 @@
1
- import { type ToolDefinition } from "@opencode-ai/plugin";
2
- export declare const boardTools: Record<string, ToolDefinition>;
@@ -1,2 +0,0 @@
1
- import { type ToolDefinition } from "@opencode-ai/plugin";
2
- export declare const contextHubTools: Record<string, ToolDefinition>;