@hyperdrive.bot/gut 0.1.6 → 0.1.9

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 (111) hide show
  1. package/README.md +1 -1
  2. package/dist/base-command.d.ts +22 -0
  3. package/dist/base-command.js +99 -0
  4. package/dist/commands/add.d.ts +14 -0
  5. package/dist/commands/add.js +70 -0
  6. package/dist/commands/affected.d.ts +23 -0
  7. package/dist/commands/affected.js +323 -0
  8. package/dist/commands/audit.d.ts +33 -0
  9. package/dist/commands/audit.js +594 -0
  10. package/dist/commands/back.d.ts +6 -0
  11. package/dist/commands/back.js +29 -0
  12. package/dist/commands/checkout.d.ts +14 -0
  13. package/dist/commands/checkout.js +124 -0
  14. package/dist/commands/commit.d.ts +11 -0
  15. package/dist/commands/commit.js +107 -0
  16. package/dist/commands/context.d.ts +6 -0
  17. package/dist/commands/context.js +32 -0
  18. package/dist/commands/contexts.d.ts +7 -0
  19. package/dist/commands/contexts.js +88 -0
  20. package/dist/commands/deps.d.ts +10 -0
  21. package/dist/commands/deps.js +100 -0
  22. package/dist/commands/entity/add.d.ts +16 -0
  23. package/dist/commands/entity/add.js +103 -0
  24. package/dist/commands/entity/clone-all.d.ts +17 -0
  25. package/dist/commands/entity/clone-all.js +127 -0
  26. package/dist/commands/entity/clone.d.ts +15 -0
  27. package/dist/commands/entity/clone.js +106 -0
  28. package/dist/commands/entity/list.d.ts +11 -0
  29. package/dist/commands/entity/list.js +80 -0
  30. package/dist/commands/entity/remove.d.ts +12 -0
  31. package/dist/commands/entity/remove.js +54 -0
  32. package/dist/commands/extract.d.ts +35 -0
  33. package/dist/commands/extract.js +483 -0
  34. package/dist/commands/focus.d.ts +19 -0
  35. package/dist/commands/focus.js +137 -0
  36. package/dist/commands/graph.d.ts +18 -0
  37. package/dist/commands/graph.js +273 -0
  38. package/dist/commands/init.d.ts +11 -0
  39. package/dist/commands/init.js +75 -0
  40. package/dist/commands/insights.d.ts +21 -0
  41. package/dist/commands/insights.js +465 -0
  42. package/dist/commands/patterns.d.ts +40 -0
  43. package/dist/commands/patterns.js +405 -0
  44. package/dist/commands/pull.d.ts +11 -0
  45. package/dist/commands/pull.js +121 -0
  46. package/dist/commands/push.d.ts +11 -0
  47. package/dist/commands/push.js +97 -0
  48. package/dist/commands/quick-setup.d.ts +20 -0
  49. package/dist/commands/quick-setup.js +417 -0
  50. package/dist/commands/recent.d.ts +9 -0
  51. package/dist/commands/recent.js +51 -0
  52. package/dist/commands/related.d.ts +23 -0
  53. package/dist/commands/related.js +255 -0
  54. package/dist/commands/repos.d.ts +17 -0
  55. package/dist/commands/repos.js +184 -0
  56. package/dist/commands/stack.d.ts +10 -0
  57. package/dist/commands/stack.js +78 -0
  58. package/dist/commands/status.d.ts +13 -0
  59. package/dist/commands/status.js +193 -0
  60. package/dist/commands/sync.d.ts +11 -0
  61. package/dist/commands/sync.js +139 -0
  62. package/dist/commands/ticket/focus.d.ts +20 -0
  63. package/dist/commands/ticket/focus.js +217 -0
  64. package/dist/commands/ticket/get.d.ts +15 -0
  65. package/dist/commands/ticket/get.js +168 -0
  66. package/dist/commands/ticket/hint.d.ts +16 -0
  67. package/dist/commands/ticket/hint.js +147 -0
  68. package/dist/commands/ticket/index.d.ts +10 -0
  69. package/dist/commands/ticket/index.js +60 -0
  70. package/dist/commands/ticket/list.d.ts +13 -0
  71. package/dist/commands/ticket/list.js +120 -0
  72. package/dist/commands/ticket/sync.d.ts +14 -0
  73. package/dist/commands/ticket/sync.js +85 -0
  74. package/dist/commands/ticket/update.d.ts +17 -0
  75. package/dist/commands/ticket/update.js +142 -0
  76. package/dist/commands/unfocus.d.ts +6 -0
  77. package/dist/commands/unfocus.js +19 -0
  78. package/dist/commands/used-by.d.ts +13 -0
  79. package/dist/commands/used-by.js +110 -0
  80. package/dist/commands/workspace.d.ts +22 -0
  81. package/dist/commands/workspace.js +372 -0
  82. package/dist/index.d.ts +14 -0
  83. package/dist/index.js +16 -0
  84. package/dist/models/entity.model.d.ts +234 -0
  85. package/dist/models/entity.model.js +1 -0
  86. package/dist/models/ticket.model.d.ts +117 -0
  87. package/dist/models/ticket.model.js +43 -0
  88. package/dist/services/auth.service.d.ts +15 -0
  89. package/dist/services/auth.service.js +26 -0
  90. package/dist/services/config.service.d.ts +34 -0
  91. package/dist/services/config.service.js +234 -0
  92. package/dist/services/entity.service.d.ts +20 -0
  93. package/dist/services/entity.service.js +127 -0
  94. package/dist/services/focus.service.d.ts +71 -0
  95. package/dist/services/focus.service.js +614 -0
  96. package/dist/services/git.service.d.ts +39 -0
  97. package/dist/services/git.service.js +188 -0
  98. package/dist/services/gut-api.service.d.ts +53 -0
  99. package/dist/services/gut-api.service.js +99 -0
  100. package/dist/services/ticket.service.d.ts +84 -0
  101. package/dist/services/ticket.service.js +207 -0
  102. package/dist/utils/display.d.ts +26 -0
  103. package/dist/utils/display.js +145 -0
  104. package/dist/utils/filesystem.d.ts +32 -0
  105. package/dist/utils/filesystem.js +198 -0
  106. package/dist/utils/index.d.ts +13 -0
  107. package/dist/utils/index.js +14 -0
  108. package/dist/utils/validation.d.ts +22 -0
  109. package/dist/utils/validation.js +192 -0
  110. package/oclif.manifest.json +2008 -0
  111. package/package.json +11 -2
@@ -0,0 +1,188 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ export class GitService {
4
+ async add(repoPath, files) {
5
+ const args = ['add', ...files];
6
+ await this.exec(args, { cwd: repoPath });
7
+ }
8
+ async addRemote(repoPath, name, url) {
9
+ await this.exec(['remote', 'add', name, url], { cwd: repoPath });
10
+ }
11
+ async checkout(repoPath, branch) {
12
+ await this.exec(['checkout', branch], { cwd: repoPath });
13
+ }
14
+ async clone(url, destination, depth) {
15
+ const args = ['clone', url, destination];
16
+ if (depth) {
17
+ args.push('--depth', depth.toString());
18
+ }
19
+ await this.exec(args);
20
+ }
21
+ async commit(repoPath, message, options) {
22
+ const args = ['commit'];
23
+ if (options?.all) {
24
+ args.push('-a');
25
+ }
26
+ if (options?.amend) {
27
+ args.push('--amend');
28
+ }
29
+ args.push('-m', message);
30
+ await this.exec(args, { cwd: repoPath });
31
+ }
32
+ async createBranch(repoPath, branch, checkout = true) {
33
+ if (checkout) {
34
+ await this.exec(['checkout', '-b', branch], { cwd: repoPath });
35
+ }
36
+ else {
37
+ await this.exec(['branch', branch], { cwd: repoPath });
38
+ }
39
+ }
40
+ async exec(args, options) {
41
+ return new Promise((resolve, reject) => {
42
+ const gitProcess = spawn('git', args, {
43
+ cwd: options?.cwd || process.cwd(),
44
+ env: options?.env || process.env,
45
+ });
46
+ let stdout = '';
47
+ let stderr = '';
48
+ gitProcess.stdout.on('data', data => {
49
+ stdout += data.toString();
50
+ });
51
+ gitProcess.stderr.on('data', data => {
52
+ stderr += data.toString();
53
+ });
54
+ gitProcess.on('close', code => {
55
+ if (code === 0) {
56
+ resolve(stdout);
57
+ }
58
+ else {
59
+ reject(new Error(`Git command failed: ${stderr || stdout}`));
60
+ }
61
+ });
62
+ gitProcess.on('error', error => {
63
+ reject(error);
64
+ });
65
+ });
66
+ }
67
+ execSync(args, options) {
68
+ const result = spawnSync('git', args, {
69
+ cwd: options?.cwd || process.cwd(),
70
+ encoding: 'utf8',
71
+ env: options?.env || process.env,
72
+ });
73
+ if (result.error) {
74
+ throw result.error;
75
+ }
76
+ if (result.status !== 0) {
77
+ throw new Error(`Git command failed: ${result.stderr || result.stdout}`);
78
+ }
79
+ return result.stdout;
80
+ }
81
+ async fetch(repoPath) {
82
+ await this.exec(['fetch'], { cwd: repoPath });
83
+ }
84
+ async getCurrentBranch(repoPath) {
85
+ const branch = await this.exec(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoPath });
86
+ return branch.trim();
87
+ }
88
+ async getRemoteUrl(repoPath, remote = 'origin') {
89
+ try {
90
+ const url = await this.exec(['remote', 'get-url', remote], { cwd: repoPath });
91
+ return url.trim();
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ async getStatus(repoPath) {
98
+ const branch = await this.getCurrentBranch(repoPath);
99
+ const statusOutput = await this.exec(['status', '--porcelain'], { cwd: repoPath });
100
+ const changes = [];
101
+ const untracked = [];
102
+ for (const line of statusOutput.split('\n')) {
103
+ if (line.startsWith('??')) {
104
+ untracked.push(line.slice(3));
105
+ }
106
+ else if (line.trim()) {
107
+ changes.push(line);
108
+ }
109
+ }
110
+ // Get ahead/behind info
111
+ let ahead = 0;
112
+ let behind = 0;
113
+ try {
114
+ const trackingBranch = await this.exec(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], { cwd: repoPath });
115
+ if (trackingBranch.trim()) {
116
+ const aheadBehind = await this.exec(['rev-list', '--left-right', '--count', `${trackingBranch.trim()}...HEAD`], { cwd: repoPath });
117
+ const [behindStr, aheadStr] = aheadBehind.trim().split('\t');
118
+ behind = Number.parseInt(behindStr, 10) || 0;
119
+ ahead = Number.parseInt(aheadStr, 10) || 0;
120
+ }
121
+ }
122
+ catch {
123
+ // No tracking branch
124
+ }
125
+ return {
126
+ ahead,
127
+ behind,
128
+ branch,
129
+ changes,
130
+ entity: path.basename(repoPath),
131
+ hasChanges: changes.length > 0 || untracked.length > 0,
132
+ path: repoPath,
133
+ untracked,
134
+ };
135
+ }
136
+ async hasChanges(repoPath) {
137
+ const status = await this.getStatus(repoPath);
138
+ return status.hasChanges;
139
+ }
140
+ async hasRemote(repoPath, remote = 'origin') {
141
+ try {
142
+ const remotes = await this.exec(['remote'], { cwd: repoPath });
143
+ return remotes.split('\n').includes(remote);
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ async init(repoPath) {
150
+ await this.exec(['init'], { cwd: repoPath });
151
+ }
152
+ async isRepository(repoPath) {
153
+ try {
154
+ await this.exec(['rev-parse', '--git-dir'], { cwd: repoPath });
155
+ return true;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ async pull(repoPath, options) {
162
+ const args = ['pull'];
163
+ if (options?.rebase) {
164
+ args.push('--rebase');
165
+ }
166
+ if (options?.noFf) {
167
+ args.push('--no-ff');
168
+ }
169
+ if (options?.strategy) {
170
+ args.push('--strategy', options.strategy);
171
+ }
172
+ await this.exec(args, { cwd: repoPath });
173
+ }
174
+ async push(repoPath, options) {
175
+ const args = ['push'];
176
+ if (options?.force) {
177
+ args.push('--force');
178
+ }
179
+ if (options?.tags) {
180
+ args.push('--tags');
181
+ }
182
+ if (options?.setUpstream) {
183
+ const branch = await this.getCurrentBranch(repoPath);
184
+ args.push('--set-upstream', 'origin', branch);
185
+ }
186
+ await this.exec(args, { cwd: repoPath });
187
+ }
188
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Gut API Service with AWS SigV4 authentication
3
+ *
4
+ * Extends @devsquad/cli-auth SigV4ApiClient for gut-specific API methods.
5
+ */
6
+ import { SigV4ApiClient } from '@hyperdrive.bot/cli-auth';
7
+ import type { FocusTicketResponse, GutTicket, GutTicketStatus, HintResponse, ListTicketsResponse, SyncResponse } from '../models/ticket.model.js';
8
+ /**
9
+ * Update ticket request
10
+ */
11
+ export interface UpdateTicketRequest {
12
+ confidence?: number;
13
+ phase?: string;
14
+ status?: GutTicketStatus;
15
+ }
16
+ /**
17
+ * Gut API Service with SigV4 authentication
18
+ */
19
+ export declare class GutApiService extends SigV4ApiClient {
20
+ constructor(domain?: string);
21
+ /**
22
+ * List tickets with optional status filter
23
+ */
24
+ listTickets(options?: {
25
+ limit?: number;
26
+ nextToken?: string;
27
+ status?: GutTicketStatus;
28
+ }): Promise<ListTicketsResponse>;
29
+ /**
30
+ * Get a ticket by ID
31
+ */
32
+ getTicket(ticketId: string): Promise<GutTicket | null>;
33
+ /**
34
+ * Get focus context for a ticket
35
+ */
36
+ focusTicket(ticketId: string): Promise<FocusTicketResponse>;
37
+ /**
38
+ * Add a hint to a ticket
39
+ */
40
+ addHint(ticketId: string, hint: string, givenBy: string): Promise<HintResponse>;
41
+ /**
42
+ * Sync ticket with external source
43
+ */
44
+ syncTicket(ticketId: string, direction?: 'pull' | 'push'): Promise<SyncResponse>;
45
+ /**
46
+ * Update a ticket
47
+ */
48
+ updateTicket(ticketId: string, updates: UpdateTicketRequest): Promise<GutTicket>;
49
+ /**
50
+ * Test API connection
51
+ */
52
+ testConnection(): Promise<Record<string, unknown>>;
53
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Gut API Service with AWS SigV4 authentication
3
+ *
4
+ * Extends @devsquad/cli-auth SigV4ApiClient for gut-specific API methods.
5
+ */
6
+ import { SigV4ApiClient } from '@hyperdrive.bot/cli-auth';
7
+ import { readFileSync } from 'fs';
8
+ import { dirname, join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { GUT_AUTH_CONFIG } from './auth.service.js';
11
+ // Get CLI version from package.json
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ let CLI_VERSION = '0.1.0';
15
+ try {
16
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
17
+ CLI_VERSION = packageJson.version;
18
+ }
19
+ catch {
20
+ // Use default version if package.json can't be read
21
+ }
22
+ /**
23
+ * Gut API Service with SigV4 authentication
24
+ */
25
+ export class GutApiService extends SigV4ApiClient {
26
+ constructor(domain) {
27
+ super({
28
+ apiUrlOverride: process.env.GUT_API_ENDPOINT,
29
+ authConfig: GUT_AUTH_CONFIG,
30
+ cliName: 'gut-cli',
31
+ cliVersion: CLI_VERSION,
32
+ domain,
33
+ regionOverride: process.env.GUT_AWS_REGION,
34
+ });
35
+ }
36
+ // ============================================================================
37
+ // Ticket Methods
38
+ // ============================================================================
39
+ /**
40
+ * List tickets with optional status filter
41
+ */
42
+ async listTickets(options) {
43
+ const params = new URLSearchParams();
44
+ if (options?.status)
45
+ params.set('status', options.status);
46
+ if (options?.limit)
47
+ params.set('limit', String(options.limit));
48
+ if (options?.nextToken)
49
+ params.set('nextToken', options.nextToken);
50
+ const queryString = params.toString();
51
+ const endpoint = `/gut/tickets${queryString ? `?${queryString}` : ''}`;
52
+ return this.makeSignedRequest('GET', endpoint);
53
+ }
54
+ /**
55
+ * Get a ticket by ID
56
+ */
57
+ async getTicket(ticketId) {
58
+ try {
59
+ return await this.makeSignedRequest('GET', `/gut/tickets/${encodeURIComponent(ticketId)}`);
60
+ }
61
+ catch (error) {
62
+ const err = error;
63
+ if (err.response?.status === 404) {
64
+ return null;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+ /**
70
+ * Get focus context for a ticket
71
+ */
72
+ async focusTicket(ticketId) {
73
+ return this.makeSignedRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/focus`);
74
+ }
75
+ /**
76
+ * Add a hint to a ticket
77
+ */
78
+ async addHint(ticketId, hint, givenBy) {
79
+ return this.makeSignedRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/hints`, { hint, givenBy });
80
+ }
81
+ /**
82
+ * Sync ticket with external source
83
+ */
84
+ async syncTicket(ticketId, direction = 'push') {
85
+ return this.makeSignedRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/sync?direction=${direction}`);
86
+ }
87
+ /**
88
+ * Update a ticket
89
+ */
90
+ async updateTicket(ticketId, updates) {
91
+ return this.makeSignedRequest('PATCH', `/gut/tickets/${encodeURIComponent(ticketId)}`, updates);
92
+ }
93
+ /**
94
+ * Test API connection
95
+ */
96
+ async testConnection() {
97
+ return this.makeSignedRequest('GET', '/gut/health');
98
+ }
99
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Ticket Service
3
+ *
4
+ * Handles API calls to the gut backend for ticket operations.
5
+ * Uses AWS SigV4 signing when authenticated, or falls back to token-based auth.
6
+ */
7
+ import type { FocusTicketResponse, GutTicket, GutTicketStatus, HintResponse, ListTicketsResponse, SyncResponse } from '../models/ticket.model.js';
8
+ import type { ConfigService } from './config.service.js';
9
+ import { type UpdateTicketRequest } from './gut-api.service.js';
10
+ /**
11
+ * Ticket service configuration for unauthenticated mode
12
+ */
13
+ interface TicketServiceConfig {
14
+ apiEndpoint: string;
15
+ region: string;
16
+ tenantId: string;
17
+ }
18
+ /**
19
+ * Ticket service for gut API operations
20
+ *
21
+ * This service supports two modes:
22
+ * 1. Authenticated mode: Uses SigV4 signing via GutApiService
23
+ * 2. Unauthenticated mode: Uses simple fetch with optional bearer token
24
+ */
25
+ export declare class TicketService {
26
+ private readonly apiService;
27
+ private readonly config;
28
+ private readonly configService;
29
+ private readonly useAuth;
30
+ constructor(configService: ConfigService);
31
+ /**
32
+ * List tickets with optional status filter
33
+ */
34
+ listTickets(options?: {
35
+ limit?: number;
36
+ nextToken?: string;
37
+ status?: GutTicketStatus;
38
+ }): Promise<ListTicketsResponse>;
39
+ /**
40
+ * Get a ticket by ID
41
+ */
42
+ getTicket(ticketId: string): Promise<GutTicket | null>;
43
+ /**
44
+ * Get focus context for a ticket
45
+ */
46
+ focusTicket(ticketId: string): Promise<FocusTicketResponse>;
47
+ /**
48
+ * Add a hint to a ticket
49
+ */
50
+ addHint(ticketId: string, hint: string, givenBy: string): Promise<HintResponse>;
51
+ /**
52
+ * Sync ticket with external source
53
+ */
54
+ syncTicket(ticketId: string, direction?: 'pull' | 'push'): Promise<SyncResponse>;
55
+ /**
56
+ * Update a ticket
57
+ */
58
+ updateTicket(ticketId: string, updates: UpdateTicketRequest): Promise<GutTicket>;
59
+ /**
60
+ * Download manifest for a ticket
61
+ */
62
+ downloadManifest(manifestUrl: string, outputPath: string): Promise<void>;
63
+ /**
64
+ * Check if API is configured
65
+ */
66
+ isConfigured(): boolean;
67
+ /**
68
+ * Check if using authenticated mode
69
+ */
70
+ isAuthenticated(): boolean;
71
+ /**
72
+ * Get current configuration (for debugging)
73
+ */
74
+ getConfig(): TicketServiceConfig;
75
+ /**
76
+ * Make a fetch request (for unauthenticated mode)
77
+ */
78
+ private makeFetchRequest;
79
+ /**
80
+ * Load configuration from environment or config file
81
+ */
82
+ private loadConfig;
83
+ }
84
+ export {};
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Ticket Service
3
+ *
4
+ * Handles API calls to the gut backend for ticket operations.
5
+ * Uses AWS SigV4 signing when authenticated, or falls back to token-based auth.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { GutApiService } from './gut-api.service.js';
10
+ /**
11
+ * Ticket service for gut API operations
12
+ *
13
+ * This service supports two modes:
14
+ * 1. Authenticated mode: Uses SigV4 signing via GutApiService
15
+ * 2. Unauthenticated mode: Uses simple fetch with optional bearer token
16
+ */
17
+ export class TicketService {
18
+ apiService = null;
19
+ config;
20
+ configService;
21
+ useAuth = false;
22
+ constructor(configService) {
23
+ this.configService = configService;
24
+ // Load configuration from environment or config file
25
+ this.config = this.loadConfig();
26
+ // Try to use authenticated mode if credentials are available
27
+ try {
28
+ this.apiService = new GutApiService();
29
+ this.useAuth = true;
30
+ }
31
+ catch {
32
+ // No credentials available, use unauthenticated mode
33
+ this.useAuth = false;
34
+ }
35
+ }
36
+ /**
37
+ * List tickets with optional status filter
38
+ */
39
+ async listTickets(options) {
40
+ if (this.useAuth && this.apiService) {
41
+ return this.apiService.listTickets(options);
42
+ }
43
+ // Fallback to unauthenticated mode
44
+ const params = new URLSearchParams();
45
+ if (options?.status)
46
+ params.set('status', options.status);
47
+ if (options?.limit)
48
+ params.set('limit', String(options.limit));
49
+ if (options?.nextToken)
50
+ params.set('nextToken', options.nextToken);
51
+ const queryString = params.toString();
52
+ const endpoint = `/gut/tickets${queryString ? `?${queryString}` : ''}`;
53
+ return this.makeFetchRequest('GET', endpoint);
54
+ }
55
+ /**
56
+ * Get a ticket by ID
57
+ */
58
+ async getTicket(ticketId) {
59
+ if (this.useAuth && this.apiService) {
60
+ return this.apiService.getTicket(ticketId);
61
+ }
62
+ // Fallback to unauthenticated mode
63
+ try {
64
+ const response = await this.makeFetchRequest('GET', `/gut/tickets/${encodeURIComponent(ticketId)}`);
65
+ return response;
66
+ }
67
+ catch (error) {
68
+ if (error.message.includes('404')) {
69
+ return null;
70
+ }
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * Get focus context for a ticket
76
+ */
77
+ async focusTicket(ticketId) {
78
+ if (this.useAuth && this.apiService) {
79
+ return this.apiService.focusTicket(ticketId);
80
+ }
81
+ // Fallback to unauthenticated mode
82
+ const response = await this.makeFetchRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/focus`);
83
+ return response;
84
+ }
85
+ /**
86
+ * Add a hint to a ticket
87
+ */
88
+ async addHint(ticketId, hint, givenBy) {
89
+ if (this.useAuth && this.apiService) {
90
+ return this.apiService.addHint(ticketId, hint, givenBy);
91
+ }
92
+ // Fallback to unauthenticated mode
93
+ const response = await this.makeFetchRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/hints`, { hint, givenBy });
94
+ return response;
95
+ }
96
+ /**
97
+ * Sync ticket with external source
98
+ */
99
+ async syncTicket(ticketId, direction = 'push') {
100
+ if (this.useAuth && this.apiService) {
101
+ return this.apiService.syncTicket(ticketId, direction);
102
+ }
103
+ // Fallback to unauthenticated mode
104
+ const response = await this.makeFetchRequest('POST', `/gut/tickets/${encodeURIComponent(ticketId)}/sync?direction=${direction}`);
105
+ return response;
106
+ }
107
+ /**
108
+ * Update a ticket
109
+ */
110
+ async updateTicket(ticketId, updates) {
111
+ if (this.useAuth && this.apiService) {
112
+ return this.apiService.updateTicket(ticketId, updates);
113
+ }
114
+ // Fallback to unauthenticated mode
115
+ const response = await this.makeFetchRequest('PATCH', `/gut/tickets/${encodeURIComponent(ticketId)}`, updates);
116
+ return response;
117
+ }
118
+ /**
119
+ * Download manifest for a ticket
120
+ */
121
+ async downloadManifest(manifestUrl, outputPath) {
122
+ const response = await fetch(manifestUrl);
123
+ if (!response.ok) {
124
+ throw new Error(`Failed to download manifest: ${response.statusText}`);
125
+ }
126
+ const content = await response.text();
127
+ fs.writeFileSync(outputPath, content);
128
+ }
129
+ /**
130
+ * Check if API is configured
131
+ */
132
+ isConfigured() {
133
+ return this.useAuth || Boolean(this.config.apiEndpoint && this.config.tenantId);
134
+ }
135
+ /**
136
+ * Check if using authenticated mode
137
+ */
138
+ isAuthenticated() {
139
+ return this.useAuth;
140
+ }
141
+ /**
142
+ * Get current configuration (for debugging)
143
+ */
144
+ getConfig() {
145
+ return { ...this.config };
146
+ }
147
+ /**
148
+ * Make a fetch request (for unauthenticated mode)
149
+ */
150
+ async makeFetchRequest(method, endpoint, body) {
151
+ const url = `${this.config.apiEndpoint}${endpoint}`;
152
+ const headers = {
153
+ 'Content-Type': 'application/json',
154
+ 'x-tenant-id': this.config.tenantId
155
+ };
156
+ // Add authorization header if available from environment
157
+ const authToken = process.env.GUT_AUTH_TOKEN;
158
+ if (authToken) {
159
+ headers['Authorization'] = `Bearer ${authToken}`;
160
+ }
161
+ const response = await fetch(url, {
162
+ body: body ? JSON.stringify(body) : undefined,
163
+ headers,
164
+ method,
165
+ });
166
+ if (!response.ok) {
167
+ const errorBody = await response.text();
168
+ throw new Error(`API request failed (${response.status}): ${errorBody}`);
169
+ }
170
+ return response.json();
171
+ }
172
+ /**
173
+ * Load configuration from environment or config file
174
+ */
175
+ loadConfig() {
176
+ // Try environment variables first
177
+ const apiEndpoint = process.env.GUT_API_ENDPOINT;
178
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
179
+ const tenantId = process.env.GUT_TENANT_ID;
180
+ if (apiEndpoint && tenantId) {
181
+ return { apiEndpoint, region, tenantId };
182
+ }
183
+ // Try loading from .gut/api.json config file
184
+ const workspaceRoot = this.configService.getWorkspaceRoot();
185
+ const apiConfigPath = path.join(workspaceRoot, '.gut', 'api.json');
186
+ if (fs.existsSync(apiConfigPath)) {
187
+ try {
188
+ const configContent = fs.readFileSync(apiConfigPath, 'utf8');
189
+ const config = JSON.parse(configContent);
190
+ return {
191
+ apiEndpoint: config.apiEndpoint || 'https://api.devsquad.com',
192
+ region: config.region || region,
193
+ tenantId: config.tenantId || 'default'
194
+ };
195
+ }
196
+ catch {
197
+ console.warn('Warning: Could not parse .gut/api.json');
198
+ }
199
+ }
200
+ // Default fallback
201
+ return {
202
+ apiEndpoint: process.env.GUT_API_ENDPOINT || 'https://api.devsquad.com',
203
+ region,
204
+ tenantId: process.env.GUT_TENANT_ID || 'default'
205
+ };
206
+ }
207
+ }
@@ -0,0 +1,26 @@
1
+ import Table, { type TableConstructorOptions } from 'cli-table3';
2
+ import { type Ora } from 'ora';
3
+ export declare const DisplayUtils: {
4
+ createSpinner(text?: string): Ora;
5
+ createTable(options?: TableConstructorOptions): Table.Table;
6
+ error(message: string): void;
7
+ formatCommand(command: string): string;
8
+ formatDuration(ms: number): string;
9
+ formatEntityType(type: string): string;
10
+ formatGitStatus(status: string): string;
11
+ formatList(items: string[], maxItems?: number): string;
12
+ formatPath(path: string): string;
13
+ formatRelativeTime(date: Date): string;
14
+ formatSize(bytes: number): string;
15
+ formatTimestamp(date: Date): string;
16
+ highlight(text: string, pattern: RegExp | string): string;
17
+ info(message: string): void;
18
+ pluralize(count: number, singular: string, plural?: string): string;
19
+ printFooter(message?: string, width?: number): void;
20
+ printHeader(title: string, width?: number): void;
21
+ progressBar(current: number, total: number, width?: number): string;
22
+ success(message: string): void;
23
+ truncate(str: string, maxLength: number): string;
24
+ warning(message: string): void;
25
+ };
26
+ export default DisplayUtils;