@meetopenbot/linear 0.0.2 → 0.0.4
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/README.md +17 -60
- package/dist/cloud-mode.js +10 -0
- package/dist/config.js +26 -107
- package/dist/credits-auth.js +53 -0
- package/dist/index.js +60 -261
- package/dist/linear-agent.js +142 -61
- package/dist/linear-mcp.js +9 -21
- package/dist/mcp-errors.js +40 -0
- package/dist/mcp-tool-args.js +38 -0
- package/dist/model-registry.js +40 -0
- package/dist/model.js +24 -0
- package/dist/thread-context.js +72 -0
- package/package.json +12 -23
- package/dist/api.d.ts +0 -17
- package/dist/api.js +0 -29
- package/dist/auth.d.ts +0 -33
- package/dist/auth.js +0 -91
- package/dist/config.d.ts +0 -39
- package/dist/index.d.ts +0 -53
- package/dist/linear-agent.d.ts +0 -14
- package/dist/linear-issues.d.ts +0 -33
- package/dist/linear-issues.js +0 -136
- package/dist/linear-mcp.d.ts +0 -4
- package/dist/oauth-pending.d.ts +0 -13
- package/dist/oauth-pending.js +0 -40
- package/dist/oauth.d.ts +0 -95
- package/dist/oauth.js +0 -372
- package/dist/tools.d.ts +0 -17
- package/dist/tools.js +0 -290
- package/src/config.ts +0 -228
- package/src/index.ts +0 -449
- package/src/linear-agent.ts +0 -107
- package/src/linear-issues.ts +0 -188
- package/src/linear-mcp.ts +0 -33
- package/src/oauth-pending.ts +0 -68
- package/src/oauth.ts +0 -563
|
@@ -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.
|
|
4
|
-
"description": "Linear
|
|
3
|
+
"version": "0.0.4",
|
|
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
|
-
"
|
|
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
|
-
"
|
|
21
|
-
"
|
|
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": "^
|
|
39
|
-
"typescript": "^
|
|
27
|
+
"@types/node": "^20.10.1",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
40
29
|
}
|
|
41
30
|
}
|
package/dist/api.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Linear GraphQL helpers used by the OAuth connect flow.
|
|
3
|
-
*/
|
|
4
|
-
export declare const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
|
|
5
|
-
export declare function fetchViewer(accessToken: string): Promise<{
|
|
6
|
-
viewer: {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
displayName: string;
|
|
10
|
-
email: string;
|
|
11
|
-
};
|
|
12
|
-
organization: {
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
urlKey: string;
|
|
16
|
-
};
|
|
17
|
-
}>;
|
package/dist/api.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Linear GraphQL helpers used by the OAuth connect flow.
|
|
3
|
-
*/
|
|
4
|
-
export const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
|
|
5
|
-
async function linearGraphql(accessToken, query, variables) {
|
|
6
|
-
const response = await fetch(LINEAR_GRAPHQL_URL, {
|
|
7
|
-
method: "POST",
|
|
8
|
-
headers: {
|
|
9
|
-
"Content-Type": "application/json",
|
|
10
|
-
Authorization: `Bearer ${accessToken}`,
|
|
11
|
-
},
|
|
12
|
-
body: JSON.stringify({ query, variables }),
|
|
13
|
-
});
|
|
14
|
-
if (!response.ok) {
|
|
15
|
-
const body = await response.text().catch(() => "");
|
|
16
|
-
throw new Error(`Linear API request failed (${response.status}): ${body.slice(0, 500)}`);
|
|
17
|
-
}
|
|
18
|
-
const payload = (await response.json());
|
|
19
|
-
if (payload.errors?.length) {
|
|
20
|
-
throw new Error(payload.errors.map((e) => e.message).join("; "));
|
|
21
|
-
}
|
|
22
|
-
if (!payload.data) {
|
|
23
|
-
throw new Error("Linear API returned no data.");
|
|
24
|
-
}
|
|
25
|
-
return payload.data;
|
|
26
|
-
}
|
|
27
|
-
export async function fetchViewer(accessToken) {
|
|
28
|
-
return linearGraphql(accessToken, `query { viewer { id name displayName email } organization { id name urlKey } }`);
|
|
29
|
-
}
|
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,39 +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 type LinearPluginConfig = {
|
|
9
|
-
clientId?: string;
|
|
10
|
-
clientSecret?: string;
|
|
11
|
-
apiKey?: string;
|
|
12
|
-
oauthPort?: number;
|
|
13
|
-
scopes?: string;
|
|
14
|
-
openaiApiKey?: string;
|
|
15
|
-
model?: string;
|
|
16
|
-
/** Public runtime base URL, e.g. https://my-host.com (used for OAuth webhook callback). */
|
|
17
|
-
webhookBaseUrl?: string;
|
|
18
|
-
};
|
|
19
|
-
export type LinearCredentials = {
|
|
20
|
-
accessToken: string;
|
|
21
|
-
openaiApiKey: string;
|
|
22
|
-
model: string;
|
|
23
|
-
clientId?: string;
|
|
24
|
-
clientSecret?: string;
|
|
25
|
-
};
|
|
26
|
-
export declare function readLinearConfig(config: Record<string, unknown>): LinearPluginConfig;
|
|
27
|
-
export declare function resolveWebhookBaseUrl(config: LinearPluginConfig, publicBaseUrl?: string): string | undefined;
|
|
28
|
-
export declare function saveTokens(storage: Storage, tokens: OAuthTokens): Promise<void>;
|
|
29
|
-
export declare function clearTokens(storage: Storage): Promise<void>;
|
|
30
|
-
export declare function formatMissingCredentials(missing: Array<"accessToken" | "openaiApiKey">): string;
|
|
31
|
-
export declare function resolveLinearCredentials(config: LinearPluginConfig, storage: Storage, options?: {
|
|
32
|
-
requireOpenAi?: boolean;
|
|
33
|
-
}): Promise<{
|
|
34
|
-
ok: true;
|
|
35
|
-
credentials: LinearCredentials;
|
|
36
|
-
} | {
|
|
37
|
-
ok: false;
|
|
38
|
-
missing: Array<"accessToken" | "openaiApiKey">;
|
|
39
|
-
}>;
|
package/dist/index.d.ts
DELETED
|
@@ -1,53 +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
|
-
};
|
|
13
|
-
clientSecret: {
|
|
14
|
-
type: "string";
|
|
15
|
-
description: string;
|
|
16
|
-
format: "password";
|
|
17
|
-
};
|
|
18
|
-
apiKey: {
|
|
19
|
-
type: "string";
|
|
20
|
-
description: string;
|
|
21
|
-
format: "password";
|
|
22
|
-
};
|
|
23
|
-
oauthPort: {
|
|
24
|
-
type: "number";
|
|
25
|
-
description: string;
|
|
26
|
-
default: number;
|
|
27
|
-
};
|
|
28
|
-
scopes: {
|
|
29
|
-
type: "string";
|
|
30
|
-
description: string;
|
|
31
|
-
default: string;
|
|
32
|
-
};
|
|
33
|
-
webhookBaseUrl: {
|
|
34
|
-
type: "string";
|
|
35
|
-
description: string;
|
|
36
|
-
format: "url";
|
|
37
|
-
};
|
|
38
|
-
openaiApiKey: {
|
|
39
|
-
type: "string";
|
|
40
|
-
description: string;
|
|
41
|
-
format: "password";
|
|
42
|
-
};
|
|
43
|
-
model: {
|
|
44
|
-
type: "string";
|
|
45
|
-
description: string;
|
|
46
|
-
default: string;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
toolDefinitions: Record<string, ToolDefinition>;
|
|
51
|
-
factory: (pluginContext: PluginContext) => (builder: import("melony").MelonyBuilder<import("@meetopenbot/plugin-sdk").OpenBotState, import("@meetopenbot/plugin-sdk").OpenBotEvent>) => void;
|
|
52
|
-
};
|
|
53
|
-
export default _default;
|
package/dist/linear-agent.d.ts
DELETED
|
@@ -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>;
|
package/dist/linear-issues.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { RenderUIWidgetData } from "@meetopenbot/plugin-sdk";
|
|
2
|
-
export type LinearIssue = {
|
|
3
|
-
id: string;
|
|
4
|
-
identifier: string;
|
|
5
|
-
title: string;
|
|
6
|
-
url: string;
|
|
7
|
-
state?: {
|
|
8
|
-
name: string;
|
|
9
|
-
type: string;
|
|
10
|
-
};
|
|
11
|
-
assignee?: {
|
|
12
|
-
name: string;
|
|
13
|
-
};
|
|
14
|
-
team?: {
|
|
15
|
-
name: string;
|
|
16
|
-
key: string;
|
|
17
|
-
};
|
|
18
|
-
project?: {
|
|
19
|
-
name: string;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
export declare const ISSUES_LIST_WIDGET_ID = "linear-issues-list";
|
|
23
|
-
export declare function buildIssuesListWidget(issues: LinearIssue[], options?: {
|
|
24
|
-
title?: string;
|
|
25
|
-
description?: string;
|
|
26
|
-
}): RenderUIWidgetData;
|
|
27
|
-
export declare function extractIssuesFromToolResults(toolResults: Array<{
|
|
28
|
-
toolName: string;
|
|
29
|
-
output: unknown;
|
|
30
|
-
}>): LinearIssue[];
|
|
31
|
-
export declare function isListIssuesPrompt(message: string): boolean;
|
|
32
|
-
export declare function isAssignedIssuesPrompt(message: string): boolean;
|
|
33
|
-
export declare function issuesListTitle(prompt: string): string;
|