@meetopenbot/linear 0.0.3 → 0.0.5

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.
@@ -0,0 +1,38 @@
1
+ export function sanitizeMcpToolArgs(args) {
2
+ if (args === null || args === undefined)
3
+ return args;
4
+ if (typeof args !== "object" || Array.isArray(args))
5
+ return args;
6
+ const result = {};
7
+ for (const [key, value] of Object.entries(args)) {
8
+ if (value === "")
9
+ continue;
10
+ if (Array.isArray(value) && value.length === 0)
11
+ continue;
12
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
13
+ const nested = sanitizeMcpToolArgs(value);
14
+ if (nested &&
15
+ typeof nested === "object" &&
16
+ !Array.isArray(nested) &&
17
+ Object.keys(nested).length > 0) {
18
+ result[key] = nested;
19
+ }
20
+ continue;
21
+ }
22
+ result[key] = value;
23
+ }
24
+ return result;
25
+ }
26
+ export function wrapMcpTools(tools) {
27
+ const wrapped = { ...tools };
28
+ for (const [name, tool] of Object.entries(tools)) {
29
+ if (!tool.execute)
30
+ continue;
31
+ const originalExecute = tool.execute.bind(tool);
32
+ wrapped[name] = {
33
+ ...tool,
34
+ execute: (input, options) => originalExecute(sanitizeMcpToolArgs(input), options),
35
+ };
36
+ }
37
+ return wrapped;
38
+ }
@@ -0,0 +1,40 @@
1
+ const REGISTRY_URL = "https://raw.githubusercontent.com/meetopenbot/openbot-registry/main/registry.json";
2
+ const OPENAI_PROVIDER = "openai";
3
+ const freeInputModelField = () => ({
4
+ type: "string",
5
+ override: true,
6
+ description: "OpenAI model in provider/model-id format (e.g. openai/gpt-4o-mini).",
7
+ default: "openai/gpt-4o-mini",
8
+ });
9
+ /** Registry-backed model field for plugin configSchema (`enum` + labeled `options`). */
10
+ export async function resolveModelConfigField() {
11
+ try {
12
+ const res = await fetch(REGISTRY_URL, {
13
+ headers: { Accept: "application/json" },
14
+ signal: AbortSignal.timeout(15_000),
15
+ });
16
+ if (!res.ok)
17
+ return freeInputModelField();
18
+ const registry = (await res.json());
19
+ const provider = registry.providers?.[OPENAI_PROVIDER];
20
+ const models = provider?.models ?? [];
21
+ if (models.length === 0)
22
+ return freeInputModelField();
23
+ const defaultModel = models.find((model) => model.id === "gpt-4o-mini")?.id ?? models[0].id;
24
+ return {
25
+ type: "string",
26
+ override: true,
27
+ description: "OpenAI model from the OpenBot registry.",
28
+ default: `${OPENAI_PROVIDER}/${defaultModel}`,
29
+ enum: models.map((model) => `${OPENAI_PROVIDER}/${model.id}`),
30
+ options: models.map((model) => ({
31
+ label: `${provider?.label ?? "OpenAI"} — ${model.label}`,
32
+ value: `${OPENAI_PROVIDER}/${model.id}`,
33
+ description: model.description,
34
+ })),
35
+ };
36
+ }
37
+ catch {
38
+ return freeInputModelField();
39
+ }
40
+ }
package/dist/model.js ADDED
@@ -0,0 +1,24 @@
1
+ import { createOpenAI } from "@ai-sdk/openai";
2
+ import { CREDITS_API_KEY_PLACEHOLDER, INTEGRATIONS_TOKEN_HEADER, creditsProviderBaseUrl, resolveCreditsAuthConfig, shouldUseCreditsAuth, } from "./credits-auth.js";
3
+ function normalizeOpenAiModelId(model) {
4
+ return model.includes("/") ? model.split("/").slice(1).join("/") : model;
5
+ }
6
+ export function resolveOpenAiModel(model, options) {
7
+ const modelId = normalizeOpenAiModelId(model);
8
+ const useCredits = shouldUseCreditsAuth(options);
9
+ if (useCredits) {
10
+ const config = resolveCreditsAuthConfig();
11
+ if (!config) {
12
+ throw new Error("OpenBot Credits is not configured. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN.");
13
+ }
14
+ const baseURL = creditsProviderBaseUrl(config);
15
+ const headers = { [INTEGRATIONS_TOKEN_HEADER]: config.token };
16
+ const apiKey = config.token || CREDITS_API_KEY_PLACEHOLDER;
17
+ return createOpenAI({ baseURL, apiKey, headers })(modelId);
18
+ }
19
+ const apiKey = options?.openaiApiKey?.trim();
20
+ if (!apiKey) {
21
+ throw new Error("OpenAI API key is required in BYOK mode. Add `OPENAI_API_KEY` under workspace settings or switch `authMode` to `credits` on cloud.");
22
+ }
23
+ return createOpenAI({ apiKey })(modelId);
24
+ }
@@ -0,0 +1,72 @@
1
+ export const MAX_THREAD_CONTEXT_LINES = 20;
2
+ export function threadEventsToContextLines(events, agentId) {
3
+ const lines = [];
4
+ for (const raw of events) {
5
+ if (!raw || typeof raw !== "object")
6
+ continue;
7
+ const event = raw;
8
+ if (event.meta?.parentToolCallId)
9
+ continue;
10
+ switch (event.type) {
11
+ case "agent:invoke": {
12
+ const role = event.data?.role ?? "user";
13
+ const content = typeof event.data?.content === "string"
14
+ ? event.data.content.trim()
15
+ : "";
16
+ if (role === "user" && content) {
17
+ lines.push({ role: "user", content });
18
+ }
19
+ break;
20
+ }
21
+ case "agent:output": {
22
+ if (event.meta?.agentId !== agentId)
23
+ break;
24
+ const content = typeof event.data?.content === "string"
25
+ ? event.data.content.trim()
26
+ : "";
27
+ if (!content)
28
+ break;
29
+ const last = lines[lines.length - 1];
30
+ if (last?.role === "assistant") {
31
+ last.content += content;
32
+ }
33
+ else {
34
+ lines.push({ role: "assistant", content });
35
+ }
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ if (lines.length > MAX_THREAD_CONTEXT_LINES) {
41
+ return lines.slice(-MAX_THREAD_CONTEXT_LINES);
42
+ }
43
+ return lines;
44
+ }
45
+ export function formatThreadContext(lines) {
46
+ if (lines.length === 0)
47
+ return "";
48
+ const jsonl = lines.map((line) => JSON.stringify(line)).join("\n");
49
+ return `Previous conversation context (most recent last):\n${jsonl}`;
50
+ }
51
+ export async function loadThreadContext(storage, args) {
52
+ if (!args.channelId)
53
+ return "";
54
+ try {
55
+ const events = await storage.getEvents({
56
+ channelId: args.channelId,
57
+ threadId: args.threadId,
58
+ });
59
+ let lines = threadEventsToContextLines(events, args.agentId);
60
+ const currentMessage = args.currentMessage?.trim();
61
+ if (currentMessage) {
62
+ const last = lines[lines.length - 1];
63
+ if (last?.role === "user" && last.content === currentMessage) {
64
+ lines = lines.slice(0, -1);
65
+ }
66
+ }
67
+ return formatThreadContext(lines);
68
+ }
69
+ catch {
70
+ return "";
71
+ }
72
+ }
package/package.json CHANGED
@@ -1,41 +1,30 @@
1
1
  {
2
2
  "name": "@meetopenbot/linear",
3
- "version": "0.0.3",
4
- "description": "Linear OAuth connect flow and MCP-backed agent for issues, projects, and comments",
3
+ "version": "0.0.5",
4
+ "description": "Linear MCP-backed specialist agent for OpenBot",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
- "files": [
9
- "dist",
10
- "src",
11
- "README.md"
12
- ],
13
- "scripts": {
14
- "build": "tsc",
15
- "prepublishOnly": "npm run build"
8
+ "exports": {
9
+ ".": "./dist/index.js"
16
10
  },
17
11
  "publishConfig": {
18
12
  "access": "public"
19
13
  },
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/meetopenbot/plugin-linear.git"
23
- },
24
- "keywords": [
25
- "openbot",
26
- "plugin",
27
- "linear",
28
- "oauth"
14
+ "files": [
15
+ "dist"
29
16
  ],
17
+ "scripts": {
18
+ "build": "tsc"
19
+ },
30
20
  "dependencies": {
31
21
  "@ai-sdk/mcp": "^2.0.14",
32
22
  "@ai-sdk/openai": "^4.0.15",
33
23
  "@meetopenbot/plugin-sdk": "^0.1.8",
34
- "ai": "^7.0.29",
35
- "mcp-server-linear": "^1.6.0"
24
+ "ai": "^7.0.29"
36
25
  },
37
26
  "devDependencies": {
38
- "@types/node": "^25.9.1",
39
- "typescript": "^6.0.3"
27
+ "@types/node": "^20.10.1",
28
+ "typescript": "^5.9.3"
40
29
  }
41
30
  }
package/dist/api.d.ts DELETED
@@ -1,94 +0,0 @@
1
- /**
2
- * Thin Linear GraphQL client over fetch. We use raw GraphQL instead of
3
- * @linear/sdk so list queries resolve relations (state, assignee, team) in a
4
- * single request and the plugin stays dependency-light.
5
- */
6
- export declare const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
7
- export type LinearAuth = {
8
- kind: 'apiKey';
9
- token: string;
10
- } | {
11
- kind: 'oauth';
12
- token: string;
13
- };
14
- export declare class LinearApiError extends Error {
15
- readonly status?: number | undefined;
16
- constructor(message: string, status?: number | undefined);
17
- }
18
- export declare function linearRequest<T = Record<string, unknown>>(auth: LinearAuth, query: string, variables?: Record<string, unknown>): Promise<T>;
19
- export declare const ISSUE_FIELDS = "\n id\n identifier\n title\n description\n priority\n priorityLabel\n url\n createdAt\n updatedAt\n dueDate\n state { id name type }\n assignee { id name displayName }\n team { id key name }\n project { id name }\n labels { nodes { id name } }\n";
20
- export interface LinearIssue {
21
- id: string;
22
- identifier: string;
23
- title: string;
24
- description?: string | null;
25
- priority: number;
26
- priorityLabel: string;
27
- url: string;
28
- createdAt: string;
29
- updatedAt: string;
30
- dueDate?: string | null;
31
- state?: {
32
- id: string;
33
- name: string;
34
- type: string;
35
- } | null;
36
- assignee?: {
37
- id: string;
38
- name: string;
39
- displayName: string;
40
- } | null;
41
- team?: {
42
- id: string;
43
- key: string;
44
- name: string;
45
- } | null;
46
- project?: {
47
- id: string;
48
- name: string;
49
- } | null;
50
- labels?: {
51
- nodes: Array<{
52
- id: string;
53
- name: string;
54
- }>;
55
- } | null;
56
- }
57
- /** Compact issue representation returned to the model/tool caller. */
58
- export declare function formatIssue(issue: LinearIssue): {
59
- id: string;
60
- identifier: string;
61
- title: string;
62
- description: string | undefined;
63
- state: string | undefined;
64
- stateType: string | undefined;
65
- priority: string;
66
- assignee: string | undefined;
67
- team: string | undefined;
68
- project: string | undefined;
69
- labels: string[] | undefined;
70
- dueDate: string | undefined;
71
- url: string;
72
- updatedAt: string;
73
- };
74
- export declare function fetchViewer(auth: LinearAuth): Promise<{
75
- viewer: {
76
- id: string;
77
- name: string;
78
- displayName: string;
79
- email: string;
80
- };
81
- organization: {
82
- id: string;
83
- name: string;
84
- urlKey: string;
85
- };
86
- }>;
87
- /** Resolve a team by UUID, key (e.g. "ENG"), or exact name. */
88
- export declare function resolveTeam(auth: LinearAuth, team: string): Promise<{
89
- id: string;
90
- key: string;
91
- name: string;
92
- } | {
93
- id: string;
94
- }>;
package/dist/api.js DELETED
@@ -1,93 +0,0 @@
1
- /**
2
- * Thin Linear GraphQL client over fetch. We use raw GraphQL instead of
3
- * @linear/sdk so list queries resolve relations (state, assignee, team) in a
4
- * single request and the plugin stays dependency-light.
5
- */
6
- export const LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';
7
- export class LinearApiError extends Error {
8
- status;
9
- constructor(message, status) {
10
- super(message);
11
- this.status = status;
12
- this.name = 'LinearApiError';
13
- }
14
- }
15
- export async function linearRequest(auth, query, variables) {
16
- const response = await fetch(LINEAR_GRAPHQL_URL, {
17
- method: 'POST',
18
- headers: {
19
- 'Content-Type': 'application/json',
20
- // Personal API keys are sent bare; OAuth tokens use the Bearer scheme.
21
- Authorization: auth.kind === 'apiKey' ? auth.token : `Bearer ${auth.token}`,
22
- },
23
- body: JSON.stringify({ query, variables }),
24
- });
25
- if (!response.ok) {
26
- const body = await response.text().catch(() => '');
27
- if (response.status === 401) {
28
- throw new LinearApiError('Linear rejected the credentials (401). Reconnect with `linear_connect` or update the API key.', 401);
29
- }
30
- throw new LinearApiError(`Linear API request failed (${response.status}): ${body.slice(0, 500)}`, response.status);
31
- }
32
- const payload = (await response.json());
33
- if (payload.errors?.length) {
34
- throw new LinearApiError(payload.errors.map((e) => e.message).join('; '));
35
- }
36
- if (!payload.data) {
37
- throw new LinearApiError('Linear API returned no data.');
38
- }
39
- return payload.data;
40
- }
41
- export const ISSUE_FIELDS = `
42
- id
43
- identifier
44
- title
45
- description
46
- priority
47
- priorityLabel
48
- url
49
- createdAt
50
- updatedAt
51
- dueDate
52
- state { id name type }
53
- assignee { id name displayName }
54
- team { id key name }
55
- project { id name }
56
- labels { nodes { id name } }
57
- `;
58
- /** Compact issue representation returned to the model/tool caller. */
59
- export function formatIssue(issue) {
60
- return {
61
- id: issue.id,
62
- identifier: issue.identifier,
63
- title: issue.title,
64
- description: issue.description ?? undefined,
65
- state: issue.state?.name,
66
- stateType: issue.state?.type,
67
- priority: issue.priorityLabel,
68
- assignee: issue.assignee?.displayName ?? issue.assignee?.name,
69
- team: issue.team?.key,
70
- project: issue.project?.name,
71
- labels: issue.labels?.nodes.map((l) => l.name),
72
- dueDate: issue.dueDate ?? undefined,
73
- url: issue.url,
74
- updatedAt: issue.updatedAt,
75
- };
76
- }
77
- export async function fetchViewer(auth) {
78
- const data = await linearRequest(auth, `query { viewer { id name displayName email } organization { id name urlKey } }`);
79
- return data;
80
- }
81
- /** Resolve a team by UUID, key (e.g. "ENG"), or exact name. */
82
- export async function resolveTeam(auth, team) {
83
- const uuidLike = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
84
- if (uuidLike.test(team))
85
- return { id: team };
86
- const data = await linearRequest(auth, `query { teams(first: 100) { nodes { id key name } } }`);
87
- const match = data.teams.nodes.find((t) => t.key.toLowerCase() === team.toLowerCase() || t.name.toLowerCase() === team.toLowerCase());
88
- if (!match) {
89
- const available = data.teams.nodes.map((t) => t.key).join(', ');
90
- throw new LinearApiError(`No Linear team matches "${team}". Available team keys: ${available}`);
91
- }
92
- return match;
93
- }
package/dist/auth.d.ts DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Credential resolution for the Linear plugin.
3
- *
4
- * Priority:
5
- * 1. `apiKey` in plugin config (AGENT.md) — personal API key.
6
- * 2. `LINEAR_API_KEY` workspace variable / env — personal API key.
7
- * 3. `LINEAR_ACCESS_TOKEN` workspace variable — OAuth token saved by
8
- * `linear_connect`, refreshed automatically via `LINEAR_REFRESH_TOKEN`.
9
- */
10
- import type { PluginContext } from '@meetopenbot/plugin-sdk';
11
- import type { LinearAuth } from './api.js';
12
- import { type OAuthTokens } from './oauth.js';
13
- export declare const VAR_API_KEY = "LINEAR_API_KEY";
14
- export declare const VAR_ACCESS_TOKEN = "LINEAR_ACCESS_TOKEN";
15
- export declare const VAR_REFRESH_TOKEN = "LINEAR_REFRESH_TOKEN";
16
- export declare const VAR_TOKEN_EXPIRES_AT = "LINEAR_TOKEN_EXPIRES_AT";
17
- /** Stored by the runtime's install-time OAuth flow; used to refresh tokens. */
18
- export declare const VAR_CLIENT_ID = "LINEAR_CLIENT_ID";
19
- export interface LinearPluginConfig {
20
- apiKey?: string;
21
- clientId?: string;
22
- clientSecret?: string;
23
- oauthPort?: number;
24
- scopes?: string;
25
- }
26
- export declare function readConfig(context: PluginContext): LinearPluginConfig;
27
- export declare function saveTokens(context: PluginContext, tokens: OAuthTokens): Promise<void>;
28
- export declare function clearTokens(context: PluginContext): Promise<void>;
29
- export declare class NotConnectedError extends Error {
30
- constructor();
31
- }
32
- /** Resolve usable Linear credentials, refreshing the OAuth token if needed. */
33
- export declare function resolveAuth(context: PluginContext): Promise<LinearAuth>;
package/dist/auth.js DELETED
@@ -1,91 +0,0 @@
1
- /**
2
- * Credential resolution for the Linear plugin.
3
- *
4
- * Priority:
5
- * 1. `apiKey` in plugin config (AGENT.md) — personal API key.
6
- * 2. `LINEAR_API_KEY` workspace variable / env — personal API key.
7
- * 3. `LINEAR_ACCESS_TOKEN` workspace variable — OAuth token saved by
8
- * `linear_connect`, refreshed automatically via `LINEAR_REFRESH_TOKEN`.
9
- */
10
- import { refreshAccessToken } from './oauth.js';
11
- export const VAR_API_KEY = 'LINEAR_API_KEY';
12
- export const VAR_ACCESS_TOKEN = 'LINEAR_ACCESS_TOKEN';
13
- export const VAR_REFRESH_TOKEN = 'LINEAR_REFRESH_TOKEN';
14
- export const VAR_TOKEN_EXPIRES_AT = 'LINEAR_TOKEN_EXPIRES_AT';
15
- /** Stored by the runtime's install-time OAuth flow; used to refresh tokens. */
16
- export const VAR_CLIENT_ID = 'LINEAR_CLIENT_ID';
17
- /** Refresh this long before the reported expiry. */
18
- const REFRESH_MARGIN_MS = 5 * 60 * 1000;
19
- export function readConfig(context) {
20
- const config = (context.config ?? {});
21
- return {
22
- apiKey: typeof config.apiKey === 'string' && config.apiKey.trim() ? config.apiKey.trim() : undefined,
23
- clientId: typeof config.clientId === 'string' && config.clientId.trim() ? config.clientId.trim() : undefined,
24
- clientSecret: typeof config.clientSecret === 'string' && config.clientSecret.trim() ? config.clientSecret.trim() : undefined,
25
- oauthPort: typeof config.oauthPort === 'number' ? config.oauthPort : 4137,
26
- scopes: typeof config.scopes === 'string' && config.scopes.trim()
27
- ? config.scopes.trim()
28
- : 'read,write,issues:create,comments:create',
29
- };
30
- }
31
- function variableValue(variables, key) {
32
- const entry = variables[key];
33
- if (typeof entry === 'string')
34
- return entry || undefined;
35
- return entry?.value || undefined;
36
- }
37
- export async function saveTokens(context, tokens) {
38
- await context.storage.createVariable({ key: VAR_ACCESS_TOKEN, value: tokens.accessToken, secret: true });
39
- if (tokens.refreshToken) {
40
- await context.storage.createVariable({ key: VAR_REFRESH_TOKEN, value: tokens.refreshToken, secret: true });
41
- }
42
- if (tokens.expiresAt) {
43
- await context.storage.createVariable({ key: VAR_TOKEN_EXPIRES_AT, value: String(tokens.expiresAt), secret: false });
44
- }
45
- }
46
- export async function clearTokens(context) {
47
- for (const key of [VAR_ACCESS_TOKEN, VAR_REFRESH_TOKEN, VAR_TOKEN_EXPIRES_AT]) {
48
- await context.storage.deleteVariable({ key }).catch(() => { });
49
- }
50
- }
51
- export class NotConnectedError extends Error {
52
- constructor() {
53
- super('Linear is not connected. Use the `linear_connect` tool to connect with OAuth, or set an API key in the plugin config / LINEAR_API_KEY variable.');
54
- this.name = 'NotConnectedError';
55
- }
56
- }
57
- /** Resolve usable Linear credentials, refreshing the OAuth token if needed. */
58
- export async function resolveAuth(context) {
59
- const config = readConfig(context);
60
- if (config.apiKey)
61
- return { kind: 'apiKey', token: config.apiKey };
62
- const variables = (await context.storage.getVariables().catch(() => ({})));
63
- const apiKey = variableValue(variables, VAR_API_KEY) ?? process.env[VAR_API_KEY];
64
- if (apiKey)
65
- return { kind: 'apiKey', token: apiKey };
66
- const accessToken = variableValue(variables, VAR_ACCESS_TOKEN) ?? process.env[VAR_ACCESS_TOKEN];
67
- if (!accessToken)
68
- throw new NotConnectedError();
69
- const expiresAtRaw = variableValue(variables, VAR_TOKEN_EXPIRES_AT) ?? process.env[VAR_TOKEN_EXPIRES_AT];
70
- const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : undefined;
71
- const refreshToken = variableValue(variables, VAR_REFRESH_TOKEN) ?? process.env[VAR_REFRESH_TOKEN];
72
- // Client id from plugin config, or the variable stored by the runtime's
73
- // install-time OAuth flow (openbot.one plugins page).
74
- const clientId = config.clientId ?? variableValue(variables, VAR_CLIENT_ID) ?? process.env[VAR_CLIENT_ID];
75
- const needsRefresh = expiresAt !== undefined && Number.isFinite(expiresAt) && Date.now() > expiresAt - REFRESH_MARGIN_MS;
76
- if (needsRefresh && refreshToken && clientId) {
77
- try {
78
- const tokens = await refreshAccessToken({
79
- refreshToken,
80
- clientId,
81
- clientSecret: config.clientSecret,
82
- });
83
- await saveTokens(context, tokens);
84
- return { kind: 'oauth', token: tokens.accessToken };
85
- }
86
- catch {
87
- // Fall through and try the stored token; a 401 will tell the user to reconnect.
88
- }
89
- }
90
- return { kind: 'oauth', token: accessToken };
91
- }
package/dist/config.d.ts DELETED
@@ -1,41 +0,0 @@
1
- import type { Storage } from "@meetopenbot/plugin-sdk";
2
- import { type OAuthTokens } from "./oauth.js";
3
- export declare const VAR_API_KEY = "LINEAR_API_KEY";
4
- export declare const VAR_ACCESS_TOKEN = "LINEAR_ACCESS_TOKEN";
5
- export declare const VAR_REFRESH_TOKEN = "LINEAR_REFRESH_TOKEN";
6
- export declare const VAR_TOKEN_EXPIRES_AT = "LINEAR_TOKEN_EXPIRES_AT";
7
- export declare const VAR_CLIENT_ID = "LINEAR_CLIENT_ID";
8
- export declare const GO_BACK_TO_OPENBOT_URL = "https://openbot.one/settings/agents";
9
- export declare const LINEAR_OPENBOT_CLIENT_ID = "1263fc2dbbf08efa7b2c4b1b2b2582be";
10
- export type LinearPluginConfig = {
11
- clientId?: string;
12
- clientSecret?: string;
13
- apiKey?: string;
14
- oauthPort?: number;
15
- scopes?: string;
16
- openaiApiKey?: string;
17
- model?: string;
18
- /** Public runtime base URL, e.g. https://my-host.com (used for OAuth webhook callback). */
19
- webhookBaseUrl?: string;
20
- };
21
- export type LinearCredentials = {
22
- accessToken: string;
23
- openaiApiKey: string;
24
- model: string;
25
- clientId?: string;
26
- clientSecret?: string;
27
- };
28
- export declare function readLinearConfig(config: Record<string, unknown>): LinearPluginConfig;
29
- export declare function resolveWebhookBaseUrl(config: LinearPluginConfig, publicBaseUrl?: string): string | undefined;
30
- export declare function saveTokens(storage: Storage, tokens: OAuthTokens): Promise<void>;
31
- export declare function clearTokens(storage: Storage): Promise<void>;
32
- export declare function formatMissingCredentials(missing: Array<"accessToken" | "openaiApiKey">): string;
33
- export declare function resolveLinearCredentials(config: LinearPluginConfig, storage: Storage, options?: {
34
- requireOpenAi?: boolean;
35
- }): Promise<{
36
- ok: true;
37
- credentials: LinearCredentials;
38
- } | {
39
- ok: false;
40
- missing: Array<"accessToken" | "openaiApiKey">;
41
- }>;
package/dist/index.d.ts DELETED
@@ -1,54 +0,0 @@
1
- import { type PluginContext, type ToolDefinition } from "@meetopenbot/plugin-sdk";
2
- declare const _default: {
3
- id: string;
4
- name: string;
5
- description: string;
6
- configSchema: {
7
- type: "object";
8
- properties: {
9
- clientId: {
10
- type: "string";
11
- description: string;
12
- default: string;
13
- };
14
- clientSecret: {
15
- type: "string";
16
- description: string;
17
- format: "password";
18
- };
19
- apiKey: {
20
- type: "string";
21
- description: string;
22
- format: "password";
23
- };
24
- oauthPort: {
25
- type: "number";
26
- description: string;
27
- default: number;
28
- };
29
- scopes: {
30
- type: "string";
31
- description: string;
32
- default: string;
33
- };
34
- webhookBaseUrl: {
35
- type: "string";
36
- description: string;
37
- format: "url";
38
- };
39
- openaiApiKey: {
40
- type: "string";
41
- description: string;
42
- format: "password";
43
- };
44
- model: {
45
- type: "string";
46
- description: string;
47
- default: string;
48
- };
49
- };
50
- };
51
- toolDefinitions: Record<string, ToolDefinition>;
52
- factory: (pluginContext: PluginContext) => (builder: import("melony").MelonyBuilder<import("@meetopenbot/plugin-sdk").OpenBotState, import("@meetopenbot/plugin-sdk").OpenBotEvent>) => void;
53
- };
54
- export default _default;
@@ -1,14 +0,0 @@
1
- import type { LinearIssue } from "./linear-issues.js";
2
- export type RunLinearAgentArgs = {
3
- prompt: string;
4
- openaiApiKey: string;
5
- accessToken: string;
6
- model?: string;
7
- };
8
- export type LinearAgentResult = {
9
- text: string;
10
- issues: LinearIssue[];
11
- usedTools: boolean;
12
- toolErrors: string[];
13
- };
14
- export declare function runLinearAgent(args: RunLinearAgentArgs): Promise<LinearAgentResult>;