gaunt-sloth-assistant 0.7.2 → 0.8.0

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 (66) hide show
  1. package/.gsloth.config_.json +2 -2
  2. package/.gsloth.config_.mjs +18 -0
  3. package/.gsloth.config_claude.mjs +18 -0
  4. package/.gsloth.config_vertex.json +6 -0
  5. package/README.md +8 -1
  6. package/assets/release-notes/v0_7_3.md +64 -0
  7. package/assets/release-notes/v0_8_0.md +13 -0
  8. package/dist/builtInToolsConfig.d.ts +6 -0
  9. package/dist/builtInToolsConfig.js +73 -0
  10. package/dist/builtInToolsConfig.js.map +1 -0
  11. package/dist/commands/prCommand.js +14 -1
  12. package/dist/commands/prCommand.js.map +1 -1
  13. package/dist/config.d.ts +18 -7
  14. package/dist/config.js +1 -41
  15. package/dist/config.js.map +1 -1
  16. package/dist/constants.d.ts +3 -0
  17. package/dist/constants.js +3 -0
  18. package/dist/constants.js.map +1 -1
  19. package/dist/core/Invocation.d.ts +1 -1
  20. package/dist/core/Invocation.js +27 -9
  21. package/dist/core/Invocation.js.map +1 -1
  22. package/dist/filePathUtils.js +1 -2
  23. package/dist/filePathUtils.js.map +1 -1
  24. package/dist/globalConfigUtils.d.ts +28 -0
  25. package/dist/globalConfigUtils.js +61 -0
  26. package/dist/globalConfigUtils.js.map +1 -0
  27. package/dist/helpers/jira/jiraClient.d.ts +4 -0
  28. package/dist/helpers/jira/jiraClient.js +75 -0
  29. package/dist/helpers/jira/jiraClient.js.map +1 -0
  30. package/dist/helpers/jira/jiraLogWork.d.ts +2 -0
  31. package/dist/helpers/jira/jiraLogWork.js +53 -0
  32. package/dist/helpers/jira/jiraLogWork.js.map +1 -0
  33. package/dist/mcp/OAuthClientProviderImpl.d.ts +36 -0
  34. package/dist/mcp/OAuthClientProviderImpl.js +197 -0
  35. package/dist/mcp/OAuthClientProviderImpl.js.map +1 -0
  36. package/dist/providers/jiraIssueProvider.js +9 -52
  37. package/dist/providers/jiraIssueProvider.js.map +1 -1
  38. package/dist/tools/gthJiraLogWorkTool.d.ts +3 -0
  39. package/dist/tools/gthJiraLogWorkTool.js +37 -0
  40. package/dist/tools/gthJiraLogWorkTool.js.map +1 -0
  41. package/dist/tools/gthStatusUpdateTool.d.ts +2 -0
  42. package/dist/tools/gthStatusUpdateTool.js +16 -0
  43. package/dist/tools/gthStatusUpdateTool.js.map +1 -0
  44. package/docs/CONFIGURATION.md +37 -2
  45. package/docs/DEVELOPMENT.md +26 -0
  46. package/eslint.config.js +7 -1
  47. package/package.json +3 -1
  48. package/src/builtInToolsConfig.ts +99 -0
  49. package/src/commands/prCommand.ts +28 -3
  50. package/src/config.ts +20 -62
  51. package/src/constants.ts +3 -0
  52. package/src/core/Invocation.ts +30 -11
  53. package/src/filePathUtils.ts +1 -3
  54. package/src/globalConfigUtils.ts +71 -0
  55. package/src/helpers/jira/jiraClient.ts +99 -0
  56. package/src/helpers/jira/jiraLogWork.ts +81 -0
  57. package/src/mcp/OAuthClientProviderImpl.ts +236 -0
  58. package/src/providers/jiraIssueProvider.ts +13 -70
  59. package/src/tools/gthJiraLogWorkTool.ts +51 -0
  60. package/src/tools/gthStatusUpdateTool.ts +18 -0
  61. package/vitest-it.config.ts +1 -1
  62. package/vitest.config.ts +1 -0
  63. package/dist/tools/statusUpdate.d.ts +0 -1
  64. package/dist/tools/statusUpdate.js +0 -12
  65. package/dist/tools/statusUpdate.js.map +0 -1
  66. package/src/tools/statusUpdate.ts +0 -15
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Gets the global .gsloth directory path in the user's home directory
3
+ * @returns The resolved path to the global .gsloth directory
4
+ */
5
+ export declare function getGlobalGslothDir(): string;
6
+ /**
7
+ * Ensures the global .gsloth directory exists in the user's home directory
8
+ * Creates it if it doesn't exist
9
+ * @returns The resolved path to the global .gsloth directory
10
+ */
11
+ export declare function ensureGlobalGslothDir(): string;
12
+ /**
13
+ * Gets the global auth directory path
14
+ * @returns The resolved path to the global auth directory
15
+ */
16
+ export declare function getGlobalAuthDir(): string;
17
+ /**
18
+ * Ensures the global auth directory exists
19
+ * Creates it if it doesn't exist
20
+ * @returns The resolved path to the global auth directory
21
+ */
22
+ export declare function ensureGlobalAuthDir(): string;
23
+ /**
24
+ * Gets the path for a specific OAuth provider's storage file
25
+ * @param serverUrl The server URL or identifier for the OAuth provider
26
+ * @returns The resolved path where the OAuth data should be stored
27
+ */
28
+ export declare function getOAuthStoragePath(serverUrl: string): string;
@@ -0,0 +1,61 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { GSLOTH_DIR, GSLOTH_AUTH } from '#src/constants.js';
5
+ /**
6
+ * Gets the global .gsloth directory path in the user's home directory
7
+ * @returns The resolved path to the global .gsloth directory
8
+ */
9
+ export function getGlobalGslothDir() {
10
+ return resolve(homedir(), GSLOTH_DIR);
11
+ }
12
+ /**
13
+ * Ensures the global .gsloth directory exists in the user's home directory
14
+ * Creates it if it doesn't exist
15
+ * @returns The resolved path to the global .gsloth directory
16
+ */
17
+ export function ensureGlobalGslothDir() {
18
+ const globalDir = getGlobalGslothDir();
19
+ if (!existsSync(globalDir)) {
20
+ mkdirSync(globalDir, { recursive: true });
21
+ }
22
+ return globalDir;
23
+ }
24
+ /**
25
+ * Gets the global auth directory path
26
+ * @returns The resolved path to the global auth directory
27
+ */
28
+ export function getGlobalAuthDir() {
29
+ const globalDir = getGlobalGslothDir();
30
+ return resolve(globalDir, GSLOTH_AUTH);
31
+ }
32
+ /**
33
+ * Ensures the global auth directory exists
34
+ * Creates it if it doesn't exist
35
+ * @returns The resolved path to the global auth directory
36
+ */
37
+ export function ensureGlobalAuthDir() {
38
+ // First ensure parent directory exists
39
+ ensureGlobalGslothDir();
40
+ const authDir = getGlobalAuthDir();
41
+ if (!existsSync(authDir)) {
42
+ mkdirSync(authDir, { recursive: true });
43
+ }
44
+ return authDir;
45
+ }
46
+ /**
47
+ * Gets the path for a specific OAuth provider's storage file
48
+ * @param serverUrl The server URL or identifier for the OAuth provider
49
+ * @returns The resolved path where the OAuth data should be stored
50
+ */
51
+ export function getOAuthStoragePath(serverUrl) {
52
+ const authDir = ensureGlobalAuthDir();
53
+ // Create a safe filename from the server URL
54
+ const safeFilename = serverUrl
55
+ .replace(/https?:\/\//, '')
56
+ .replace(/[^a-zA-Z0-9.-]/g, '_')
57
+ .replace(/_+/g, '_')
58
+ .toLowerCase();
59
+ return resolve(authDir, `${safeFilename}.json`);
60
+ }
61
+ //# sourceMappingURL=globalConfigUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalConfigUtils.js","sourceRoot":"","sources":["../src/globalConfigUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;IAEvC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;IACvC,OAAO,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,uCAAuC;IACvC,qBAAqB,EAAE,CAAC;IAExB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IAEnC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,6CAA6C;IAC7C,MAAM,YAAY,GAAG,SAAS;SAC3B,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,WAAW,EAAE,CAAC;IAEjB,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,YAAY,OAAO,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { JiraConfig } from '#src/providers/types.js';
2
+ export declare function getJiraCredentials(config: Partial<JiraConfig> | null): JiraConfig;
3
+ export declare function getJiraHeaders(config: JiraConfig): Record<string, string>;
4
+ export declare function jiraRequest<T>(config: JiraConfig, endpoint: string, options?: RequestInit, showProgress?: boolean): Promise<T>;
@@ -0,0 +1,75 @@
1
+ import { env } from '#src/systemUtils.js';
2
+ import { ProgressIndicator } from '#src/utils.js';
3
+ export function getJiraCredentials(config) {
4
+ if (!config) {
5
+ throw new Error('No Jira config provided');
6
+ }
7
+ const username = env.JIRA_USERNAME || config.username;
8
+ if (!username) {
9
+ throw new Error('Missing JIRA username. The username can be defined as JIRA_USERNAME environment variable or as "username" in config.');
10
+ }
11
+ const token = env.JIRA_API_PAT_TOKEN || config.token;
12
+ if (!token) {
13
+ throw new Error('Missing JIRA PAT token. The token can be defined as JIRA_API_PAT_TOKEN environment variable or as "token" in config.');
14
+ }
15
+ const cloudId = env.JIRA_CLOUD_ID || config.cloudId;
16
+ if (!cloudId) {
17
+ throw new Error('Missing JIRA Cloud ID. The Cloud ID can be defined as JIRA_CLOUD_ID environment variable or as "cloudId" in config.');
18
+ }
19
+ return {
20
+ username,
21
+ token,
22
+ cloudId,
23
+ displayUrl: config.displayUrl,
24
+ };
25
+ }
26
+ export function getJiraHeaders(config) {
27
+ const credentials = `${config.username}:${config.token}`;
28
+ const encodedCredentials = Buffer.from(credentials).toString('base64');
29
+ const authHeader = `Basic ${encodedCredentials}`;
30
+ return {
31
+ Authorization: authHeader,
32
+ Accept: 'application/json; charset=utf-8',
33
+ 'Accept-Language': 'en-US,en;q=0.9',
34
+ 'Content-Type': 'application/json',
35
+ };
36
+ }
37
+ export async function jiraRequest(config, endpoint, options = {}, showProgress = true) {
38
+ const apiUrl = `https://api.atlassian.com/ex/jira/${config.cloudId}${endpoint}`;
39
+ const headers = getJiraHeaders(config);
40
+ let progressIndicator;
41
+ if (showProgress) {
42
+ progressIndicator = new ProgressIndicator(`${options.method || 'GET'} ${apiUrl.replace(/^https?:\/\//, '')}`);
43
+ }
44
+ try {
45
+ const response = await fetch(apiUrl, {
46
+ ...options,
47
+ headers: {
48
+ ...headers,
49
+ ...options.headers,
50
+ },
51
+ });
52
+ if (progressIndicator) {
53
+ progressIndicator.stop();
54
+ }
55
+ if (!response.ok) {
56
+ let errorMessage = `Failed to fetch from Jira: ${response.statusText}`;
57
+ try {
58
+ const errorData = await response.json();
59
+ errorMessage += ` - ${JSON.stringify(errorData)}`;
60
+ }
61
+ catch {
62
+ // If we can't parse JSON error, use the basic message
63
+ }
64
+ throw new Error(errorMessage);
65
+ }
66
+ return response.json();
67
+ }
68
+ catch (error) {
69
+ if (progressIndicator) {
70
+ progressIndicator.stop();
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+ //# sourceMappingURL=jiraClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jiraClient.js","sourceRoot":"","sources":["../../../src/helpers/jira/jiraClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { JiraConfig } from '#src/providers/types.js';
2
+ export default function jiraLogWork(config: Partial<JiraConfig> | null, jiraId: string, timeInSeconds: number, comment?: string, startedAt?: Date): Promise<string>;
@@ -0,0 +1,53 @@
1
+ import { getJiraCredentials, jiraRequest } from './jiraClient.js';
2
+ import { displayError, displaySuccess } from '#src/consoleUtils.js';
3
+ export default async function jiraLogWork(config, jiraId, timeInSeconds, comment = 'Work logged', startedAt = new Date()) {
4
+ try {
5
+ // Use provided config or empty config (will use environment variables)
6
+ const credentials = getJiraCredentials(config);
7
+ const bodyData = {
8
+ comment: {
9
+ content: [
10
+ {
11
+ content: [
12
+ {
13
+ text: comment,
14
+ type: 'text',
15
+ },
16
+ ],
17
+ type: 'paragraph',
18
+ },
19
+ ],
20
+ type: 'doc',
21
+ version: 1,
22
+ },
23
+ started: startedAt.toISOString().replace('Z', '+0000'),
24
+ timeSpentSeconds: timeInSeconds,
25
+ };
26
+ /**
27
+ * https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-worklogs/#api-rest-api-3-issue-issueidorkey-worklog-post
28
+ * Needs:
29
+ * Classic RECOMMENDED:write:jira-work
30
+ *
31
+ * OR
32
+ *
33
+ * write:issue-worklog:jira, write:issue-worklog.property:jira, read:avatar:jira, read:group:jira,
34
+ * read:issue-worklog:jira, read:project-role:jira, read:user:jira, read:issue-worklog.property:jira
35
+ */
36
+ await jiraRequest(credentials, `/rest/api/3/issue/${jiraId}/worklog`, {
37
+ method: 'POST',
38
+ body: JSON.stringify(bodyData),
39
+ });
40
+ const hours = Math.floor(timeInSeconds / 3600);
41
+ const minutes = Math.floor((timeInSeconds % 3600) / 60);
42
+ const timeStr = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
43
+ const successMessage = `✅ Logged ${timeStr} to ${jiraId}`;
44
+ displaySuccess(successMessage);
45
+ return successMessage;
46
+ }
47
+ catch (error) {
48
+ const errorMessage = `Failed to log work to Jira: ${error instanceof Error ? error.message : String(error)}`;
49
+ displayError(errorMessage);
50
+ return errorMessage;
51
+ }
52
+ }
53
+ //# sourceMappingURL=jiraLogWork.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jiraLogWork.js","sourceRoot":"","sources":["../../../src/helpers/jira/jiraLogWork.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAkBpE,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACvC,MAAkC,EAClC,MAAc,EACd,aAAqB,EACrB,UAAkB,aAAa,EAC/B,YAAkB,IAAI,IAAI,EAAE;IAE5B,IAAI,CAAC;QACH,uEAAuE;QACvE,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAE/C,MAAM,QAAQ,GAAuB;YACnC,OAAO,EAAE;gBACP,OAAO,EAAE;oBACP;wBACE,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,OAAO;gCACb,IAAI,EAAE,MAAM;6BACb;yBACF;wBACD,IAAI,EAAE,WAAW;qBAClB;iBACF;gBACD,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,CAAC;aACX;YACD,OAAO,EAAE,SAAS,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;YACtD,gBAAgB,EAAE,aAAa;SAChC,CAAC;QAEF;;;;;;;;;WASG;QACH,MAAM,WAAW,CAAC,WAAW,EAAE,qBAAqB,MAAM,UAAU,EAAE;YACpE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;SAC/B,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;QAEpE,MAAM,cAAc,GAAG,YAAY,OAAO,OAAO,MAAM,EAAE,CAAC;QAC1D,cAAc,CAAC,cAAc,CAAC,CAAC;QAC/B,OAAO,cAAc,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,YAAY,GAAG,+BACnB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CAAC;QACH,YAAY,CAAC,YAAY,CAAC,CAAC;QAC3B,OAAO,YAAY,CAAC;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
2
+ import type { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
3
+ import { StreamableHTTPConnection } from '@langchain/mcp-adapters';
4
+ import http from 'http';
5
+ interface OAuthClientProviderConfig {
6
+ redirectUrl: string;
7
+ serverUrl: string;
8
+ }
9
+ /**
10
+ * Please note most of these "unused" methods are part of {@link OAuthClientProvider}
11
+ */
12
+ export declare class OAuthClientProviderImpl implements OAuthClientProvider {
13
+ private config;
14
+ private innerState;
15
+ private storagePath;
16
+ constructor(config: OAuthClientProviderConfig);
17
+ private loadStorageData;
18
+ private saveStorageData;
19
+ state(): string | Promise<string>;
20
+ get redirectUrl(): string;
21
+ get clientMetadata(): OAuthClientMetadata;
22
+ saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void>;
23
+ clientInformation(): Promise<OAuthClientInformationFull | undefined>;
24
+ saveTokens(tokens: OAuthTokens): Promise<void>;
25
+ tokens(): OAuthTokens | undefined;
26
+ saveCodeVerifier(codeVerifier: string): Promise<void>;
27
+ codeVerifier(): Promise<string>;
28
+ redirectToAuthorization(authUrl: URL): Promise<void>;
29
+ }
30
+ export declare function createAuthProviderAndAuthenticate(mcpServer: StreamableHTTPConnection): Promise<OAuthClientProviderImpl>;
31
+ export declare function createOAuthRedirectServer(path: string, portParam?: number): Promise<{
32
+ port: number;
33
+ server: http.Server;
34
+ codePromise: Promise<string>;
35
+ }>;
36
+ export {};
@@ -0,0 +1,197 @@
1
+ import { auth } from '@modelcontextprotocol/sdk/client/auth.js';
2
+ import express from 'express';
3
+ import * as crypto from 'crypto';
4
+ import { platform } from 'node:os';
5
+ import { execSync } from 'node:child_process';
6
+ import { displayInfo } from '#src/consoleUtils.js';
7
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
8
+ import { getOAuthStoragePath } from '#src/globalConfigUtils.js';
9
+ /**
10
+ * Please note most of these "unused" methods are part of {@link OAuthClientProvider}
11
+ */
12
+ export class OAuthClientProviderImpl {
13
+ config;
14
+ innerState;
15
+ storagePath;
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.innerState = crypto.randomUUID();
19
+ if (!this.config.redirectUrl) {
20
+ throw new Error('No redirect URL provided');
21
+ }
22
+ if (!this.config.serverUrl) {
23
+ throw new Error('No server URL provided');
24
+ }
25
+ this.storagePath = getOAuthStoragePath(this.config.serverUrl);
26
+ }
27
+ loadStorageData() {
28
+ if (existsSync(this.storagePath)) {
29
+ try {
30
+ const data = readFileSync(this.storagePath, 'utf-8');
31
+ return JSON.parse(data);
32
+ }
33
+ catch (error) {
34
+ displayInfo('Failed to load OAuth storage data:' + error);
35
+ return {};
36
+ }
37
+ }
38
+ return {};
39
+ }
40
+ saveStorageData(data) {
41
+ try {
42
+ writeFileSync(this.storagePath, JSON.stringify(data, null, 2), 'utf-8');
43
+ }
44
+ catch (error) {
45
+ console.error('Failed to save OAuth storage data:', error);
46
+ }
47
+ }
48
+ state() {
49
+ return this.innerState;
50
+ }
51
+ // noinspection JSUnusedGlobalSymbols
52
+ get redirectUrl() {
53
+ return this.config.redirectUrl;
54
+ }
55
+ // noinspection JSUnusedGlobalSymbols
56
+ get clientMetadata() {
57
+ return {
58
+ redirect_uris: [this.config.redirectUrl],
59
+ client_name: 'Gaunt Sloth Assistant',
60
+ client_uri: 'https://github.com/andruhon/gaunt-sloth-assistant',
61
+ software_id: '1dd38b83-946b-4631-8855-66ee467bfd68',
62
+ scope: 'mcp:read mcp:write',
63
+ token_endpoint_auth_method: 'none',
64
+ grant_types: ['authorization_code', 'refresh_token'],
65
+ response_types: ['code'],
66
+ };
67
+ }
68
+ // noinspection JSUnusedGlobalSymbols
69
+ saveClientInformation(clientInformation) {
70
+ const data = this.loadStorageData();
71
+ data.clientInformation = clientInformation;
72
+ this.saveStorageData(data);
73
+ return Promise.resolve();
74
+ }
75
+ // noinspection JSUnusedGlobalSymbols
76
+ async clientInformation() {
77
+ const data = this.loadStorageData();
78
+ return Promise.resolve(data.clientInformation);
79
+ }
80
+ // noinspection JSUnusedGlobalSymbols
81
+ async saveTokens(tokens) {
82
+ const data = this.loadStorageData();
83
+ data.tokens = tokens;
84
+ this.saveStorageData(data);
85
+ }
86
+ // noinspection JSUnusedGlobalSymbols
87
+ tokens() {
88
+ const data = this.loadStorageData();
89
+ return data.tokens;
90
+ }
91
+ // noinspection JSUnusedGlobalSymbols
92
+ saveCodeVerifier(codeVerifier) {
93
+ const data = this.loadStorageData();
94
+ data.codeVerifier = codeVerifier;
95
+ this.saveStorageData(data);
96
+ return Promise.resolve();
97
+ }
98
+ // noinspection JSUnusedGlobalSymbols
99
+ codeVerifier() {
100
+ const data = this.loadStorageData();
101
+ if (!data.codeVerifier) {
102
+ throw new Error('No code verifier stored');
103
+ }
104
+ return Promise.resolve(data.codeVerifier);
105
+ }
106
+ // noinspection JSUnusedGlobalSymbols
107
+ async redirectToAuthorization(authUrl) {
108
+ displayInfo('Auth url: ' + authUrl.toString());
109
+ try {
110
+ const url = authUrl.toString();
111
+ displayInfo('Trying to open browser');
112
+ // Handle different platforms
113
+ const platformName = platform();
114
+ if (platformName === 'win32') {
115
+ // Windows
116
+ execSync(`start "" "${url}"`);
117
+ }
118
+ else if (platformName === 'darwin') {
119
+ // macOS
120
+ execSync(`open "${url}"`);
121
+ }
122
+ else {
123
+ // Linux and others
124
+ execSync(`xdg-open "${url}"`);
125
+ }
126
+ }
127
+ catch (error) {
128
+ displayInfo(`Failed to open browser: ${error}`);
129
+ displayInfo(`Please open ${authUrl.toString()} in your browser`);
130
+ }
131
+ }
132
+ }
133
+ export async function createAuthProviderAndAuthenticate(mcpServer) {
134
+ const { port, server, codePromise } = await createOAuthRedirectServer('/oauth-callback');
135
+ const authProvider = new OAuthClientProviderImpl({
136
+ redirectUrl: `http://127.0.0.1:${port}/oauth-callback`,
137
+ serverUrl: mcpServer.url,
138
+ });
139
+ const outcome = await auth(authProvider, { serverUrl: mcpServer.url });
140
+ if (outcome == 'REDIRECT') {
141
+ const authorizationCode = await codePromise;
142
+ await auth(authProvider, { serverUrl: mcpServer.url, authorizationCode });
143
+ }
144
+ else if (outcome == 'AUTHORIZED') {
145
+ try {
146
+ server.close();
147
+ }
148
+ catch { }
149
+ displayInfo('Authorized');
150
+ }
151
+ else {
152
+ throw new Error(`Unexpected Auth outcome: ${outcome}`);
153
+ }
154
+ return authProvider;
155
+ }
156
+ export function createOAuthRedirectServer(path, portParam = 0) {
157
+ const redirectApp = express();
158
+ return new Promise((resolve, reject) => {
159
+ const codePromise = new Promise((resolveCode, rejectCode) => {
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ redirectApp.get(path, (req, res) => {
162
+ const code = req.query.code;
163
+ if (!code) {
164
+ res.status(400).send('Error: No auth code received');
165
+ rejectCode('Error: No auth code received');
166
+ return;
167
+ }
168
+ res.send(`<div style="height: 80vh;text-align: center;display: flex;justify-content: center;align-items: center;">
169
+ <div>
170
+ <h1>Auth successful!</h1>
171
+ You may close this window and return to the Gaunt Sloth.
172
+ <div>
173
+ <img src="data:image/bmp;base64,Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAnD98ZBtDTQ8sey9YOQobRA8nZx9OdyxahzZrMwERdCtShjZqLQUVLwgZKwUWKQYXsFCRq06KciJPnEh/n0GEmjuClDl9lzl/uFGglz56dChOm0h0SBInLgQVKwgZKgUVgzZchDRfqEeLsUmavFWjvV6joEGFizFvsU+Yw2CowWCiu1qdbCRNKgEQMAofMAcebSRIgC9bt1KdqEqNnD6BokGJqEaOvVmjqEWMu12ftFeYwGCjpUOMUxc8KQIRLAQWhjVmo0qGs1CYqEmOqEmPmjyArk2UrEyQr02TsU2WwV6pvF2eo0CKlDl+OgsiJwQSijZthS9qnDuAo0GFqESMmz2BijZtpUOLnT2CuVicyWmtsVCUjDh0p0KQbiVYJAQSkzx3kzZ7iS5rv2mn3Y3Gq0+Pgi1laCFOfyxmmTx/slCVmT55eihemzyGjTd6NwgfgSxokzl9qkuY4pLR+Lvg1H3EjTRzZB1IjTV3cCNWmjmAgC1diCxpjC1ulDd8RhAtdihbljl/vmK1v2WuuWGgtVWdjDVwgS5ldCVZcydckjl5kzh3ynGwxHOooT6DSA8sQQwkpUeTyGrBlTp8eCNZkzh1rEeSmDx9ZB9JcyNYkzh2tlek5pjT8q3bqU2PPQokJgEPhTVxy2nBkDd6kDV2qEaOwGOvvWGxuViqoUSNlDd6nj6FvmKq13zKnkiJMgUZKwUWPg4poUaOkzp+kTh7nkCExG60033Jym7C03zKsVOdiC9tjjR10W7IeThrKAAOMAUYJgUULgUZdCZdqkqYu1ersk+fvFyww2q4z3fDqUmXhy5ulTp/yWi/Txo6JgEPLgYZLQUXKQUVJwkaSxExq1GTxWW3t1SpsU+gv1mvnUOLjTJ3izh3dTRkJwEQKgQWLQYaLAgcLQUYLQYZLAERezZaiTtmXyFJgjp0ijt1iTlqOAsjNQ0pKQASJwUVKQYYLgUZLAQZLAgcLgUZMAUYZyVRfCthPQoiIQAJOgcgYyBLKAIRLAUZNwsoMQcfKAQV"
174
+ width="16" height="16">
175
+ </div>
176
+ </div>
177
+ </div>`);
178
+ resolveCode(code);
179
+ if (server) {
180
+ displayInfo('Cleaning auth redirect server...');
181
+ server.close();
182
+ }
183
+ });
184
+ });
185
+ const server = redirectApp.listen(portParam, () => {
186
+ const addressInfo = server.address();
187
+ const port = addressInfo.port;
188
+ displayInfo(`OAuth callback server listening at ${port}`);
189
+ resolve({ port, server, codePromise });
190
+ });
191
+ server.on('error', (err) => {
192
+ // If the server fails to start, reject the outer promise.
193
+ reject(err);
194
+ });
195
+ });
196
+ }
197
+ //# sourceMappingURL=OAuthClientProviderImpl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthClientProviderImpl.js","sourceRoot":"","sources":["../../src/mcp/OAuthClientProviderImpl.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { display, displayError, displayWarning } from '#src/consoleUtils.js';
2
- import { env } from '#src/systemUtils.js';
3
- import { ProgressIndicator } from '#src/utils.js';
2
+ import { getJiraCredentials, jiraRequest } from '#src/helpers/jira/jiraClient.js';
4
3
  /**
5
4
  * Gets Jira issue using Atlassian REST API v3 with Personal Access Token
6
5
  *
@@ -19,28 +18,9 @@ export async function get(config, issueId) {
19
18
  displayWarning('No issue ID provided');
20
19
  return null;
21
20
  }
22
- // Get username from environment variable or config
23
- const username = env.JIRA_USERNAME || config.username;
24
- if (!username) {
25
- throw new Error('Missing JIRA username. The username can be defined as JIRA_USERNAME environment variable or as "username" in config.');
26
- }
27
- // Get token from environment variable or config
28
- const token = env.JIRA_API_PAT_TOKEN || config.token;
29
- if (!token) {
30
- throw new Error('Missing JIRA PAT token. The token can be defined as JIRA_API_PAT_TOKEN environment variable or as "token" in config.');
31
- }
32
- // Get cloud ID from environment variable or config
33
- const cloudId = env.JIRA_CLOUD_ID || config.cloudId;
34
- if (!cloudId) {
35
- throw new Error('Missing JIRA Cloud ID. The Cloud ID can be defined as JIRA_CLOUD_ID environment variable or as "cloudId" in config.');
36
- }
21
+ const credentials = getJiraCredentials(config);
37
22
  try {
38
- const issue = await getJiraIssue({
39
- ...config,
40
- username,
41
- token,
42
- cloudId,
43
- }, issueId);
23
+ const issue = await getJiraIssue(credentials, issueId);
44
24
  if (!issue) {
45
25
  return null;
46
26
  }
@@ -63,43 +43,20 @@ export async function get(config, issueId) {
63
43
  * @param jiraKey Jira issue ID
64
44
  * @returns Jira issue response
65
45
  */
66
- async function getJiraIssue(config, jiraKey) {
46
+ async function getJiraIssue(credentials, jiraKey) {
67
47
  // Jira Cloud ID can be found by authenticated user at https://company.atlassian.net/_edge/tenant_info
68
48
  // According to doc https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get permissions to read this resource:
69
49
  // https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-get
70
50
  // either Classic (RECOMMENDED) read:jira-work
71
- // or Granular read:issue-meta:jira, read:issue-security-level:jira, read:issue.vote:jira, read:issue.changelog:jira, read:avatar:jira, read:issue:jira, read:status:jira, read:user:jira, read:field-configuration:jira
72
- const apiUrl = `https://api.atlassian.com/ex/jira/${config.cloudId}/rest/api/2/issue/${jiraKey}`;
73
- if (config.displayUrl) {
74
- display(`Loading Jira issue ${config.displayUrl}${jiraKey}`);
51
+ // or Granular read:issue-meta:jira, read:issue-security-level:jira, read:issue.vote:jira, read:issue.changelog:jira,
52
+ // read:avatar:jira, read:issue:jira, read:status:jira, read:user:jira, read:field-configuration:jira
53
+ if (credentials.displayUrl) {
54
+ display(`Loading Jira issue ${credentials.displayUrl}${jiraKey}`);
75
55
  }
76
56
  // This filter will be necessary for V3: `&expand=renderedFields` to convert ADF to HTML
77
57
  const filters = '?fields=summary,description'; // Limit JSON to summary and description
78
- // Encode credentials for Basic Authentication header
79
- const credentials = `${config.username}:${config.token}`;
80
- const encodedCredentials = Buffer.from(credentials).toString('base64');
81
- const authHeader = `Basic ${encodedCredentials}`;
82
- // Define request headers
83
- const headers = {
84
- Authorization: authHeader,
85
- Accept: 'application/json; charset=utf-8',
86
- 'Accept-Language': 'en-US,en;q=0.9', // Prevents errors in other languages
87
- };
88
- const progressIndicator = new ProgressIndicator(`Retrieving jira from api ${apiUrl.replace(/^https?:\/\//, '')}`);
89
- const response = await fetch(apiUrl + filters, {
58
+ return jiraRequest(credentials, `/rest/api/2/issue/${jiraKey}${filters}`, {
90
59
  method: 'GET',
91
- headers: headers,
92
60
  });
93
- progressIndicator.stop();
94
- if (!response?.ok) {
95
- try {
96
- const errorData = await response.json();
97
- throw new Error(`Failed to fetch Jira issue: ${response.statusText} - ${JSON.stringify(errorData)}`);
98
- }
99
- catch (_e) {
100
- throw new Error(`Failed to fetch Jira issue: ${response?.statusText}`);
101
- }
102
- }
103
- return response.json();
104
61
  }
105
62
  //# sourceMappingURL=jiraIssueProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jiraIssueProvider.js","sourceRoot":"","sources":["../../src/providers/jiraIssueProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jiraIssueProvider.js","sourceRoot":"","sources":["../../src/providers/jiraIssueProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAYlF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,MAAkC,EAClC,OAA2B;IAE3B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,cAAc,CAAC,yBAAyB,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,cAAc,CAAC,sBAAsB,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;QACrC,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC;QAE7C,OAAO,eAAe,OAAO,cAAc,OAAO,qBAAqB,WAAW,EAAE,CAAC;IACvF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CACV,6BAA6B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtF,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,YAAY,CACzB,WAAsF,EACtF,OAAe;IAEf,sGAAsG;IAEtG,0KAA0K;IAC1K,sHAAsH;IACtH,8CAA8C;IAC9C,qHAAqH;IACrH,qGAAqG;IAErG,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC;QAC3B,OAAO,CAAC,sBAAsB,WAAW,CAAC,UAAU,GAAG,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,wFAAwF;IACxF,MAAM,OAAO,GAAG,6BAA6B,CAAC,CAAC,wCAAwC;IAEvF,OAAO,WAAW,CAAoB,WAAW,EAAE,qBAAqB,OAAO,GAAG,OAAO,EAAE,EAAE;QAC3F,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type StructuredToolInterface } from '@langchain/core/tools';
2
+ import { SlothConfig } from '#src/config.js';
3
+ export declare function get(config: SlothConfig): StructuredToolInterface<import("@langchain/core/tools").ToolSchemaBase, any, any>;
@@ -0,0 +1,37 @@
1
+ import { tool } from '@langchain/core/tools';
2
+ import { z } from 'zod';
3
+ import { displayWarning } from '#src/consoleUtils.js';
4
+ import jiraLogWork from '#src/helpers/jira/jiraLogWork.js';
5
+ // Define the input schema for the tool
6
+ const gthJiraLogWorkSchema = z.object({
7
+ jiraId: z.string().describe('The Jira issue ID (e.g., "PROJ-123")'),
8
+ timeInSeconds: z.number().describe('Time spent in seconds'),
9
+ comment: z.string().optional().describe('Work log comment'),
10
+ startedAt: z.string().optional().describe('ISO 8601 date string for when work started'),
11
+ });
12
+ const toolDefinition = {
13
+ name: 'gth_jira_log_work',
14
+ description: `Gaunt Sloth Jira Log Work Tool. Log work time to a Jira issue. Requires Jira configuration with credentials.
15
+ Example: gth_jira_log_work({ jiraId: "PROJ-123", timeInSeconds: 3600, comment: "Implemented feature X" })`,
16
+ schema: gthJiraLogWorkSchema,
17
+ };
18
+ function getToolImpl(config) {
19
+ let toolImpl = async ({ jiraId, timeInSeconds, comment = 'Work logged', startedAt, }) => {
20
+ const jiraConfig = config || {};
21
+ const startDate = startedAt ? new Date(startedAt) : new Date();
22
+ return await jiraLogWork(jiraConfig, jiraId, timeInSeconds, comment, startDate);
23
+ };
24
+ return tool(toolImpl, toolDefinition);
25
+ }
26
+ // Export a default instance that uses environment variables
27
+ export function get(config) {
28
+ if (!config.prebuiltToolsConfig?.jira && config.requirementsProviderConfig?.jira) {
29
+ displayWarning('config.prebuiltToolsConfig.jira is not defined. Using config.requirementsProviderConfig.jira.');
30
+ }
31
+ let jiraConfig = config.prebuiltToolsConfig?.jira || config.requirementsProviderConfig?.jira;
32
+ if (!jiraConfig) {
33
+ throw new Error('gth_jira_log_work is added to preBuiltTools, but no Jira config is provided.');
34
+ }
35
+ return getToolImpl(jiraConfig);
36
+ }
37
+ //# sourceMappingURL=gthJiraLogWorkTool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gthJiraLogWorkTool.js","sourceRoot":"","sources":["../../src/tools/gthJiraLogWorkTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,WAAW,MAAM,kCAAkC,CAAC;AAI3D,uCAAuC;AACvC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IACnE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;IAC3D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAC3D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;CACxF,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG;IACrB,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE;0GAC2F;IACxG,MAAM,EAAE,oBAAoB;CAC7B,CAAC;AAEF,SAAS,WAAW,CAAC,MAA4B;IAC/C,IAAI,QAAQ,GAAG,KAAK,EAAE,EACpB,MAAM,EACN,aAAa,EACb,OAAO,GAAG,aAAa,EACvB,SAAS,GAC4B,EAAmB,EAAE;QAC1D,MAAM,UAAU,GAAG,MAAM,IAAI,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAE/D,OAAO,MAAM,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAClF,CAAC,CAAC;IACF,OAAO,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AACxC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,GAAG,CAAC,MAAmB;IACrC,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,IAAI,MAAM,CAAC,0BAA0B,EAAE,IAAI,EAAE,CAAC;QACjF,cAAc,CACZ,+FAA+F,CAChG,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,GAAG,MAAM,CAAC,mBAAmB,EAAE,IAAI,IAAI,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC;IAC7F,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { SlothConfig } from '#src/config.js';
2
+ export declare function get(_: SlothConfig): import("@langchain/core/tools").DynamicTool<void>;