@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.
- package/README.md +1 -1
- package/dist/base-command.d.ts +22 -0
- package/dist/base-command.js +99 -0
- package/dist/commands/add.d.ts +14 -0
- package/dist/commands/add.js +70 -0
- package/dist/commands/affected.d.ts +23 -0
- package/dist/commands/affected.js +323 -0
- package/dist/commands/audit.d.ts +33 -0
- package/dist/commands/audit.js +594 -0
- package/dist/commands/back.d.ts +6 -0
- package/dist/commands/back.js +29 -0
- package/dist/commands/checkout.d.ts +14 -0
- package/dist/commands/checkout.js +124 -0
- package/dist/commands/commit.d.ts +11 -0
- package/dist/commands/commit.js +107 -0
- package/dist/commands/context.d.ts +6 -0
- package/dist/commands/context.js +32 -0
- package/dist/commands/contexts.d.ts +7 -0
- package/dist/commands/contexts.js +88 -0
- package/dist/commands/deps.d.ts +10 -0
- package/dist/commands/deps.js +100 -0
- package/dist/commands/entity/add.d.ts +16 -0
- package/dist/commands/entity/add.js +103 -0
- package/dist/commands/entity/clone-all.d.ts +17 -0
- package/dist/commands/entity/clone-all.js +127 -0
- package/dist/commands/entity/clone.d.ts +15 -0
- package/dist/commands/entity/clone.js +106 -0
- package/dist/commands/entity/list.d.ts +11 -0
- package/dist/commands/entity/list.js +80 -0
- package/dist/commands/entity/remove.d.ts +12 -0
- package/dist/commands/entity/remove.js +54 -0
- package/dist/commands/extract.d.ts +35 -0
- package/dist/commands/extract.js +483 -0
- package/dist/commands/focus.d.ts +19 -0
- package/dist/commands/focus.js +137 -0
- package/dist/commands/graph.d.ts +18 -0
- package/dist/commands/graph.js +273 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +75 -0
- package/dist/commands/insights.d.ts +21 -0
- package/dist/commands/insights.js +465 -0
- package/dist/commands/patterns.d.ts +40 -0
- package/dist/commands/patterns.js +405 -0
- package/dist/commands/pull.d.ts +11 -0
- package/dist/commands/pull.js +121 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.js +97 -0
- package/dist/commands/quick-setup.d.ts +20 -0
- package/dist/commands/quick-setup.js +417 -0
- package/dist/commands/recent.d.ts +9 -0
- package/dist/commands/recent.js +51 -0
- package/dist/commands/related.d.ts +23 -0
- package/dist/commands/related.js +255 -0
- package/dist/commands/repos.d.ts +17 -0
- package/dist/commands/repos.js +184 -0
- package/dist/commands/stack.d.ts +10 -0
- package/dist/commands/stack.js +78 -0
- package/dist/commands/status.d.ts +13 -0
- package/dist/commands/status.js +193 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.js +139 -0
- package/dist/commands/ticket/focus.d.ts +20 -0
- package/dist/commands/ticket/focus.js +217 -0
- package/dist/commands/ticket/get.d.ts +15 -0
- package/dist/commands/ticket/get.js +168 -0
- package/dist/commands/ticket/hint.d.ts +16 -0
- package/dist/commands/ticket/hint.js +147 -0
- package/dist/commands/ticket/index.d.ts +10 -0
- package/dist/commands/ticket/index.js +60 -0
- package/dist/commands/ticket/list.d.ts +13 -0
- package/dist/commands/ticket/list.js +120 -0
- package/dist/commands/ticket/sync.d.ts +14 -0
- package/dist/commands/ticket/sync.js +85 -0
- package/dist/commands/ticket/update.d.ts +17 -0
- package/dist/commands/ticket/update.js +142 -0
- package/dist/commands/unfocus.d.ts +6 -0
- package/dist/commands/unfocus.js +19 -0
- package/dist/commands/used-by.d.ts +13 -0
- package/dist/commands/used-by.js +110 -0
- package/dist/commands/workspace.d.ts +22 -0
- package/dist/commands/workspace.js +372 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +16 -0
- package/dist/models/entity.model.d.ts +234 -0
- package/dist/models/entity.model.js +1 -0
- package/dist/models/ticket.model.d.ts +117 -0
- package/dist/models/ticket.model.js +43 -0
- package/dist/services/auth.service.d.ts +15 -0
- package/dist/services/auth.service.js +26 -0
- package/dist/services/config.service.d.ts +34 -0
- package/dist/services/config.service.js +234 -0
- package/dist/services/entity.service.d.ts +20 -0
- package/dist/services/entity.service.js +127 -0
- package/dist/services/focus.service.d.ts +71 -0
- package/dist/services/focus.service.js +614 -0
- package/dist/services/git.service.d.ts +39 -0
- package/dist/services/git.service.js +188 -0
- package/dist/services/gut-api.service.d.ts +53 -0
- package/dist/services/gut-api.service.js +99 -0
- package/dist/services/ticket.service.d.ts +84 -0
- package/dist/services/ticket.service.js +207 -0
- package/dist/utils/display.d.ts +26 -0
- package/dist/utils/display.js +145 -0
- package/dist/utils/filesystem.d.ts +32 -0
- package/dist/utils/filesystem.js +198 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.js +192 -0
- package/oclif.manifest.json +2008 -0
- 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;
|