@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.
@@ -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;
@@ -1,136 +0,0 @@
1
- const OPEN_URL_ACTION_ID = "open_url";
2
- export const ISSUES_LIST_WIDGET_ID = "linear-issues-list";
3
- function mapStateStatus(stateType) {
4
- switch (stateType) {
5
- case "started":
6
- return "in_progress";
7
- case "completed":
8
- return "done";
9
- case "canceled":
10
- return "cancelled";
11
- case "backlog":
12
- case "unstarted":
13
- return "pending";
14
- default:
15
- return undefined;
16
- }
17
- }
18
- export function buildIssuesListWidget(issues, options) {
19
- return {
20
- kind: "list",
21
- widgetId: ISSUES_LIST_WIDGET_ID,
22
- title: options?.title ?? "Linear issues",
23
- description: options?.description ??
24
- (issues.length === 0
25
- ? "No issues matched this query."
26
- : `${issues.length} issue${issues.length === 1 ? "" : "s"}`),
27
- items: issues.map((issue) => {
28
- const details = [
29
- issue.state?.name,
30
- issue.assignee?.name ? `Assignee: ${issue.assignee.name}` : undefined,
31
- issue.team?.name,
32
- issue.project?.name,
33
- ]
34
- .filter(Boolean)
35
- .join(" · ");
36
- return {
37
- id: issue.id,
38
- label: `${issue.identifier}: ${issue.title}`,
39
- description: details || undefined,
40
- badge: issue.state?.name,
41
- status: mapStateStatus(issue.state?.type),
42
- actions: issue.url
43
- ? [
44
- {
45
- id: OPEN_URL_ACTION_ID,
46
- label: "Open",
47
- variant: "secondary",
48
- value: { url: issue.url, target: "_blank" },
49
- },
50
- ]
51
- : undefined,
52
- metadata: {
53
- identifier: issue.identifier,
54
- url: issue.url,
55
- },
56
- };
57
- }),
58
- };
59
- }
60
- function extractTextPayload(value) {
61
- if (typeof value === "string")
62
- return value;
63
- if (!value || typeof value !== "object")
64
- return undefined;
65
- const record = value;
66
- if (typeof record.text === "string")
67
- return record.text;
68
- if (Array.isArray(record.content)) {
69
- for (const part of record.content) {
70
- if (part &&
71
- typeof part === "object" &&
72
- part.type === "text" &&
73
- typeof part.text === "string") {
74
- return part.text;
75
- }
76
- }
77
- }
78
- return undefined;
79
- }
80
- function parseIssuesPayload(payload) {
81
- if (!payload || typeof payload !== "object")
82
- return [];
83
- const root = payload;
84
- const issuesNode = root.issues ??
85
- root.data?.issues;
86
- if (!issuesNode?.nodes || !Array.isArray(issuesNode.nodes))
87
- return [];
88
- return issuesNode.nodes
89
- .filter((node) => {
90
- if (!node || typeof node !== "object")
91
- return false;
92
- const issue = node;
93
- return (typeof issue.id === "string" &&
94
- typeof issue.identifier === "string" &&
95
- typeof issue.title === "string");
96
- })
97
- .map((issue) => ({
98
- ...issue,
99
- url: typeof issue.url === "string" ? issue.url : "",
100
- }));
101
- }
102
- export function extractIssuesFromToolResults(toolResults) {
103
- const byId = new Map();
104
- for (const toolResult of toolResults) {
105
- if (!toolResult.toolName.includes("search_issues"))
106
- continue;
107
- const text = extractTextPayload(toolResult.output);
108
- if (!text)
109
- continue;
110
- try {
111
- for (const issue of parseIssuesPayload(JSON.parse(text))) {
112
- byId.set(issue.id, issue);
113
- }
114
- }
115
- catch {
116
- // Ignore malformed tool payloads.
117
- }
118
- }
119
- return [...byId.values()];
120
- }
121
- export function isListIssuesPrompt(message) {
122
- const normalized = message.trim().toLowerCase();
123
- if (!normalized)
124
- return false;
125
- return (/\b(list|show|get|fetch|what are|available)\b/.test(normalized) &&
126
- /\bissues?\b/.test(normalized));
127
- }
128
- export function isAssignedIssuesPrompt(message) {
129
- const normalized = message.trim().toLowerCase();
130
- return (/\b(assigned to me|my issues|issues for me|issues assigned)\b/.test(normalized) || /\bissues?\s+assigned\b/.test(normalized));
131
- }
132
- export function issuesListTitle(prompt) {
133
- return isAssignedIssuesPrompt(prompt)
134
- ? "Issues assigned to you"
135
- : "Linear issues";
136
- }
@@ -1,4 +0,0 @@
1
- export type LinearMcpClientArgs = {
2
- accessToken: string;
3
- };
4
- export declare function createLinearMcpClient(args: LinearMcpClientArgs): Promise<import("@ai-sdk/mcp").MCPClient>;
@@ -1,13 +0,0 @@
1
- import type { Storage } from "@meetopenbot/plugin-sdk";
2
- export declare const VAR_OAUTH_PENDING = "LINEAR_OAUTH_PENDING";
3
- export interface PendingOAuthSession {
4
- state: string;
5
- codeVerifier: string;
6
- clientId: string;
7
- clientSecret?: string;
8
- redirectUri: string;
9
- expiresAt: number;
10
- }
11
- export declare function savePendingOAuthSession(storage: Storage, session: PendingOAuthSession): Promise<void>;
12
- export declare function loadPendingOAuthSession(storage: Storage): Promise<PendingOAuthSession | null>;
13
- export declare function clearPendingOAuthSession(storage: Storage): Promise<void>;
@@ -1,40 +0,0 @@
1
- export const VAR_OAUTH_PENDING = "LINEAR_OAUTH_PENDING";
2
- function variableValue(variables, key) {
3
- const entry = variables[key];
4
- if (typeof entry === "string")
5
- return entry || undefined;
6
- return entry?.value || undefined;
7
- }
8
- export async function savePendingOAuthSession(storage, session) {
9
- await storage.createVariable({
10
- key: VAR_OAUTH_PENDING,
11
- value: JSON.stringify(session),
12
- secret: true,
13
- });
14
- }
15
- export async function loadPendingOAuthSession(storage) {
16
- const variables = (await storage.getVariables().catch(() => ({})));
17
- const raw = variableValue(variables, VAR_OAUTH_PENDING) ??
18
- process.env[VAR_OAUTH_PENDING];
19
- if (!raw)
20
- return null;
21
- try {
22
- const session = JSON.parse(raw);
23
- if (typeof session.state !== "string" ||
24
- typeof session.codeVerifier !== "string" ||
25
- typeof session.clientId !== "string" ||
26
- typeof session.redirectUri !== "string" ||
27
- typeof session.expiresAt !== "number") {
28
- return null;
29
- }
30
- if (Date.now() > session.expiresAt)
31
- return null;
32
- return session;
33
- }
34
- catch {
35
- return null;
36
- }
37
- }
38
- export async function clearPendingOAuthSession(storage) {
39
- await storage.deleteVariable({ key: VAR_OAUTH_PENDING }).catch(() => { });
40
- }
package/dist/oauth.d.ts DELETED
@@ -1,95 +0,0 @@
1
- /**
2
- * Linear OAuth 2.0 (authorization code + PKCE).
3
- *
4
- * Two callback modes:
5
- * - Loopback: temporary localhost server (local runtimes).
6
- * - Webhook: redirect to `https://<host>/api/webhooks/linear` (cloud/remote).
7
- */
8
- import type { Storage } from "@meetopenbot/plugin-sdk";
9
- export declare const LINEAR_AUTHORIZE_URL = "https://linear.app/oauth/authorize";
10
- export declare const LINEAR_TOKEN_URL = "https://api.linear.app/oauth/token";
11
- export declare const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
12
- export declare const OAUTH_WEBHOOK_PROVIDER = "linear";
13
- export interface OAuthTokens {
14
- accessToken: string;
15
- refreshToken?: string;
16
- /** Epoch ms when the access token expires, if Linear reported expiry. */
17
- expiresAt?: number;
18
- scope?: string;
19
- }
20
- export interface StartOAuthFlowArgs {
21
- clientId: string;
22
- clientSecret?: string;
23
- port: number;
24
- scopes: string;
25
- /** Called once the code has been exchanged successfully. */
26
- onSuccess: (tokens: OAuthTokens) => Promise<void>;
27
- onError?: (error: Error) => void;
28
- /** How long the callback server stays alive, in ms. */
29
- timeoutMs?: number;
30
- }
31
- export interface OAuthFlowHandle {
32
- authorizeUrl: string;
33
- redirectUri: string;
34
- /** Resolves with tokens on success, null on timeout/cancel. */
35
- completion: Promise<OAuthTokens | null>;
36
- cancel: () => void;
37
- }
38
- export interface StartWebhookOAuthFlowArgs {
39
- storage: Storage;
40
- clientId: string;
41
- clientSecret?: string;
42
- scopes: string;
43
- webhookBaseUrl: string;
44
- timeoutMs?: number;
45
- }
46
- export declare function oauthHtmlPage(title: string, body: string, ok: boolean): string;
47
- export declare function buildOAuthRedirectUri(webhookBaseUrl: string): string;
48
- export declare function buildAuthorizeUrl(args: {
49
- clientId: string;
50
- redirectUri: string;
51
- scopes: string;
52
- state: string;
53
- codeChallenge: string;
54
- }): string;
55
- export declare function exchangeAuthorizationCode(args: {
56
- code: string;
57
- redirectUri: string;
58
- clientId: string;
59
- clientSecret?: string;
60
- codeVerifier: string;
61
- }): Promise<OAuthTokens>;
62
- export declare function refreshAccessToken(args: {
63
- refreshToken: string;
64
- clientId: string;
65
- clientSecret?: string;
66
- }): Promise<OAuthTokens>;
67
- export declare function startWebhookOAuthFlow(args: StartWebhookOAuthFlowArgs): Promise<OAuthFlowHandle>;
68
- export type WebhookOAuthCallbackResult = {
69
- kind: "oauth";
70
- status: number;
71
- html: string;
72
- tokens: OAuthTokens | null;
73
- state?: string;
74
- } | {
75
- kind: "ignore";
76
- };
77
- export declare function handleWebhookOAuthCallback(args: {
78
- storage: Storage;
79
- query: Record<string, unknown>;
80
- onSuccess: (tokens: OAuthTokens) => Promise<void>;
81
- }): Promise<WebhookOAuthCallbackResult>;
82
- export declare function startOAuthFlow(args: StartOAuthFlowArgs): OAuthFlowHandle;
83
- export declare function fetchViewer(accessToken: string): Promise<{
84
- viewer: {
85
- id: string;
86
- name: string;
87
- displayName: string;
88
- email: string;
89
- };
90
- organization: {
91
- id: string;
92
- name: string;
93
- urlKey: string;
94
- };
95
- }>;