@colmbus72/yeehaw 0.5.0 → 0.6.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 (43) hide show
  1. package/claude-plugin/skills/yeehaw-development/SKILL.md +70 -0
  2. package/dist/app.js +166 -15
  3. package/dist/components/CritterHeader.d.ts +7 -0
  4. package/dist/components/CritterHeader.js +81 -0
  5. package/dist/components/List.d.ts +2 -0
  6. package/dist/components/List.js +1 -1
  7. package/dist/lib/auth/index.d.ts +2 -0
  8. package/dist/lib/auth/index.js +3 -0
  9. package/dist/lib/auth/linear.d.ts +20 -0
  10. package/dist/lib/auth/linear.js +79 -0
  11. package/dist/lib/auth/storage.d.ts +12 -0
  12. package/dist/lib/auth/storage.js +53 -0
  13. package/dist/lib/context.d.ts +10 -0
  14. package/dist/lib/context.js +63 -0
  15. package/dist/lib/critters.d.ts +33 -0
  16. package/dist/lib/critters.js +164 -0
  17. package/dist/lib/hotkeys.d.ts +1 -1
  18. package/dist/lib/hotkeys.js +6 -2
  19. package/dist/lib/issues/github.d.ts +11 -0
  20. package/dist/lib/issues/github.js +154 -0
  21. package/dist/lib/issues/index.d.ts +14 -0
  22. package/dist/lib/issues/index.js +27 -0
  23. package/dist/lib/issues/linear.d.ts +24 -0
  24. package/dist/lib/issues/linear.js +345 -0
  25. package/dist/lib/issues/types.d.ts +82 -0
  26. package/dist/lib/issues/types.js +2 -0
  27. package/dist/lib/paths.d.ts +1 -0
  28. package/dist/lib/paths.js +1 -0
  29. package/dist/lib/tmux.d.ts +1 -0
  30. package/dist/lib/tmux.js +45 -0
  31. package/dist/types.d.ts +19 -0
  32. package/dist/views/BarnContext.d.ts +2 -1
  33. package/dist/views/BarnContext.js +136 -14
  34. package/dist/views/CritterDetailView.d.ts +10 -0
  35. package/dist/views/CritterDetailView.js +117 -0
  36. package/dist/views/CritterLogsView.d.ts +8 -0
  37. package/dist/views/CritterLogsView.js +100 -0
  38. package/dist/views/IssuesView.d.ts +2 -1
  39. package/dist/views/IssuesView.js +661 -98
  40. package/dist/views/LivestockDetailView.d.ts +2 -1
  41. package/dist/views/LivestockDetailView.js +8 -1
  42. package/dist/views/ProjectContext.js +35 -1
  43. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build a context string to inject into Claude sessions spawned from Yeehaw.
3
+ * Includes project name and wiki section titles (not content) to hint at available context.
4
+ */
5
+ export declare function buildProjectContext(projectName: string): string | null;
6
+ /**
7
+ * Build context for a livestock-specific session.
8
+ * Includes project context plus livestock details.
9
+ */
10
+ export declare function buildLivestockContext(projectName: string, livestockName: string): string | null;
@@ -0,0 +1,63 @@
1
+ import { loadProject } from './config.js';
2
+ /**
3
+ * Build a context string to inject into Claude sessions spawned from Yeehaw.
4
+ * Includes project name and wiki section titles (not content) to hint at available context.
5
+ */
6
+ export function buildProjectContext(projectName) {
7
+ const project = loadProject(projectName);
8
+ if (!project) {
9
+ return null;
10
+ }
11
+ const lines = [];
12
+ lines.push(`You are working on the "${project.name}" project.`);
13
+ if (project.summary) {
14
+ lines.push(`Project: ${project.summary}`);
15
+ }
16
+ // Add wiki section titles as hints
17
+ const wikiSections = project.wiki || [];
18
+ if (wikiSections.length > 0) {
19
+ lines.push('');
20
+ lines.push('Yeehaw wiki sections available:');
21
+ for (const section of wikiSections) {
22
+ lines.push(`- ${section.title}`);
23
+ }
24
+ lines.push('');
25
+ lines.push('Use mcp__yeehaw__get_wiki_section to fetch relevant context before making architectural decisions.');
26
+ }
27
+ return lines.join('\n');
28
+ }
29
+ /**
30
+ * Build context for a livestock-specific session.
31
+ * Includes project context plus livestock details.
32
+ */
33
+ export function buildLivestockContext(projectName, livestockName) {
34
+ const project = loadProject(projectName);
35
+ if (!project) {
36
+ return null;
37
+ }
38
+ const livestock = project.livestock?.find(l => l.name === livestockName);
39
+ if (!livestock) {
40
+ return buildProjectContext(projectName);
41
+ }
42
+ const lines = [];
43
+ lines.push(`You are working on the "${project.name}" project, in the "${livestock.name}" environment.`);
44
+ if (project.summary) {
45
+ lines.push(`Project: ${project.summary}`);
46
+ }
47
+ // Add livestock details
48
+ if (livestock.branch) {
49
+ lines.push(`Branch: ${livestock.branch}`);
50
+ }
51
+ // Add wiki section titles as hints
52
+ const wikiSections = project.wiki || [];
53
+ if (wikiSections.length > 0) {
54
+ lines.push('');
55
+ lines.push('Yeehaw wiki sections available:');
56
+ for (const section of wikiSections) {
57
+ lines.push(`- ${section.title}`);
58
+ }
59
+ lines.push('');
60
+ lines.push('Use mcp__yeehaw__get_wiki_section to fetch relevant context before making architectural decisions.');
61
+ }
62
+ return lines.join('\n');
63
+ }
@@ -26,3 +26,36 @@ export declare function discoverCritters(barn: Barn): Promise<{
26
26
  critters: DiscoveredCritter[];
27
27
  error?: string;
28
28
  }>;
29
+ /**
30
+ * Service info from systemctl
31
+ */
32
+ export interface SystemService {
33
+ name: string;
34
+ state: 'running' | 'stopped' | 'unknown';
35
+ description?: string;
36
+ }
37
+ /**
38
+ * List systemd services on a barn
39
+ * @param barn - The barn to query
40
+ * @param activeOnly - If true, only return running services (default: true)
41
+ */
42
+ export declare function listSystemServices(barn: Barn, activeOnly?: boolean): Promise<{
43
+ services: SystemService[];
44
+ error?: string;
45
+ }>;
46
+ /**
47
+ * Details extracted from a systemd service file
48
+ */
49
+ export interface ServiceDetails {
50
+ service_path: string;
51
+ config_path?: string;
52
+ log_path?: string;
53
+ use_journald: boolean;
54
+ }
55
+ /**
56
+ * Get details about a systemd service by parsing its unit file
57
+ */
58
+ export declare function getServiceDetails(barn: Barn, serviceName: string): Promise<{
59
+ details?: ServiceDetails;
60
+ error?: string;
61
+ }>;
@@ -199,3 +199,167 @@ export async function discoverCritters(barn) {
199
199
  }
200
200
  return { critters: discovered };
201
201
  }
202
+ /**
203
+ * List systemd services on a barn
204
+ * @param barn - The barn to query
205
+ * @param activeOnly - If true, only return running services (default: true)
206
+ */
207
+ export async function listSystemServices(barn, activeOnly = true) {
208
+ // For active only: list-units shows running services
209
+ // For all: list-unit-files shows all installed services
210
+ const cmd = activeOnly
211
+ ? 'systemctl list-units --type=service --state=running --no-pager --no-legend'
212
+ : 'systemctl list-unit-files --type=service --no-pager --no-legend';
213
+ let output;
214
+ // Local barn
215
+ if (barn.name === 'local') {
216
+ try {
217
+ const result = await execa('sh', ['-c', cmd]);
218
+ output = result.stdout;
219
+ }
220
+ catch (err) {
221
+ return { services: [], error: `Failed to list services: ${getErrorMessage(err)}` };
222
+ }
223
+ }
224
+ else {
225
+ // Remote barn - SSH
226
+ if (!barn.host || !barn.user) {
227
+ return { services: [], error: `Barn '${barn.name}' is not configured for SSH` };
228
+ }
229
+ try {
230
+ const sshArgs = buildSshCommand(barn);
231
+ const result = await execa(sshArgs[0], [...sshArgs.slice(1), cmd]);
232
+ output = result.stdout;
233
+ }
234
+ catch (err) {
235
+ return { services: [], error: `SSH error: ${getErrorMessage(err)}` };
236
+ }
237
+ }
238
+ const services = [];
239
+ const lines = output.split('\n').filter(line => line.trim());
240
+ for (const line of lines) {
241
+ const parts = line.trim().split(/\s+/);
242
+ if (parts.length < 1)
243
+ continue;
244
+ const serviceName = parts[0];
245
+ if (!serviceName.endsWith('.service'))
246
+ continue;
247
+ if (activeOnly) {
248
+ // list-units format: UNIT LOAD ACTIVE SUB DESCRIPTION...
249
+ services.push({
250
+ name: serviceName,
251
+ state: 'running',
252
+ description: parts.slice(4).join(' ') || undefined,
253
+ });
254
+ }
255
+ else {
256
+ // list-unit-files format: UNIT STATE PRESET
257
+ const stateStr = parts[1]?.toLowerCase() || '';
258
+ services.push({
259
+ name: serviceName,
260
+ state: stateStr === 'enabled' ? 'unknown' : 'stopped',
261
+ });
262
+ }
263
+ }
264
+ return { services };
265
+ }
266
+ /**
267
+ * Get details about a systemd service by parsing its unit file
268
+ */
269
+ export async function getServiceDetails(barn, serviceName) {
270
+ // Get the unit file path
271
+ const pathCmd = `systemctl show -p FragmentPath ${shellEscape(serviceName)} --value`;
272
+ let servicePath;
273
+ if (barn.name === 'local') {
274
+ try {
275
+ const result = await execa('sh', ['-c', pathCmd]);
276
+ servicePath = result.stdout.trim();
277
+ }
278
+ catch (err) {
279
+ return { error: `Failed to get service path: ${getErrorMessage(err)}` };
280
+ }
281
+ }
282
+ else {
283
+ if (!barn.host || !barn.user) {
284
+ return { error: `Barn '${barn.name}' is not configured for SSH` };
285
+ }
286
+ try {
287
+ const sshArgs = buildSshCommand(barn);
288
+ const result = await execa(sshArgs[0], [...sshArgs.slice(1), pathCmd]);
289
+ servicePath = result.stdout.trim();
290
+ }
291
+ catch (err) {
292
+ return { error: `SSH error: ${getErrorMessage(err)}` };
293
+ }
294
+ }
295
+ if (!servicePath) {
296
+ return { error: 'Could not find service unit file' };
297
+ }
298
+ // Read the service file to extract details
299
+ const catCmd = `cat ${shellEscape(servicePath)}`;
300
+ let serviceContent;
301
+ if (barn.name === 'local') {
302
+ try {
303
+ const result = await execa('sh', ['-c', catCmd]);
304
+ serviceContent = result.stdout;
305
+ }
306
+ catch (err) {
307
+ return { error: `Failed to read service file: ${getErrorMessage(err)}` };
308
+ }
309
+ }
310
+ else {
311
+ try {
312
+ const sshArgs = buildSshCommand(barn);
313
+ const result = await execa(sshArgs[0], [...sshArgs.slice(1), catCmd]);
314
+ serviceContent = result.stdout;
315
+ }
316
+ catch (err) {
317
+ return { error: `SSH error: ${getErrorMessage(err)}` };
318
+ }
319
+ }
320
+ // Parse the service file
321
+ let config_path;
322
+ let log_path;
323
+ let use_journald = true;
324
+ for (const line of serviceContent.split('\n')) {
325
+ const trimmed = line.trim();
326
+ // Look for ExecStart to find config flags
327
+ if (trimmed.startsWith('ExecStart=')) {
328
+ const execLine = trimmed.slice('ExecStart='.length);
329
+ // Common config flag patterns
330
+ const configPatterns = [
331
+ /--config[=\s]([^\s]+)/,
332
+ /--defaults-file[=\s]([^\s]+)/,
333
+ /-c\s+([^\s]+)/,
334
+ /--conf[=\s]([^\s]+)/,
335
+ ];
336
+ for (const pattern of configPatterns) {
337
+ const match = execLine.match(pattern);
338
+ if (match) {
339
+ config_path = match[1];
340
+ break;
341
+ }
342
+ }
343
+ }
344
+ // Check StandardOutput/StandardError for log paths
345
+ if (trimmed.startsWith('StandardOutput=') || trimmed.startsWith('StandardError=')) {
346
+ const value = trimmed.split('=')[1];
347
+ if (value && !value.startsWith('journal') && !value.startsWith('inherit')) {
348
+ // Could be file:path or append:path
349
+ const fileMatch = value.match(/(?:file|append):(.+)/);
350
+ if (fileMatch) {
351
+ log_path = fileMatch[1];
352
+ use_journald = false;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ return {
358
+ details: {
359
+ service_path: servicePath,
360
+ config_path,
361
+ log_path,
362
+ use_journald,
363
+ },
364
+ };
365
+ }
@@ -1,4 +1,4 @@
1
- export type HotkeyScope = 'global' | 'global-dashboard' | 'project-context' | 'barn-context' | 'wiki-view' | 'issues-view' | 'livestock-detail' | 'logs-view' | 'night-sky' | 'list' | 'content';
1
+ export type HotkeyScope = 'global' | 'global-dashboard' | 'project-context' | 'barn-context' | 'wiki-view' | 'issues-view' | 'livestock-detail' | 'logs-view' | 'critter-detail' | 'critter-logs' | 'night-sky' | 'list' | 'content';
2
2
  export type HotkeyCategory = 'navigation' | 'action' | 'system';
3
3
  export interface Hotkey {
4
4
  key: string;
@@ -31,12 +31,16 @@ export const HOTKEYS = [
31
31
  { key: 'w', description: 'Open wiki', category: 'action', scopes: ['project-context'] },
32
32
  { key: 'i', description: 'Open issues', category: 'action', scopes: ['project-context'] },
33
33
  // === LIVESTOCK DETAIL PAGE-LEVEL ===
34
- { key: 'c', description: 'Claude session', category: 'action', scopes: ['livestock-detail'] },
34
+ { key: 'c', description: 'Claude session (local only)', category: 'action', scopes: ['livestock-detail'] },
35
35
  { key: 's', description: 'Shell session', category: 'action', scopes: ['livestock-detail'] },
36
36
  { key: 'l', description: 'View logs', category: 'action', scopes: ['livestock-detail'] },
37
+ // === CRITTER DETAIL PAGE-LEVEL ===
38
+ { key: 'l', description: 'View logs', category: 'action', scopes: ['critter-detail'] },
39
+ { key: 'e', description: 'Edit', category: 'action', scopes: ['critter-detail'] },
37
40
  // === ISSUES VIEW ===
38
- { key: 'r', description: 'Refresh', category: 'action', scopes: ['issues-view', 'logs-view'] },
41
+ { key: 'r', description: 'Refresh', category: 'action', scopes: ['issues-view', 'logs-view', 'critter-logs'] },
39
42
  { key: 'o', description: 'Open in browser', category: 'action', scopes: ['issues-view'] },
43
+ { key: 'c', description: 'Open in Claude', category: 'action', scopes: ['issues-view'] },
40
44
  // === NIGHT SKY ===
41
45
  { key: 'v', description: 'Visualizer', category: 'navigation', scopes: ['global-dashboard'] },
42
46
  { key: 'c', description: 'Spawn cloud', category: 'action', scopes: ['night-sky'] },
@@ -0,0 +1,11 @@
1
+ import type { Issue, IssueProvider, FetchIssuesOptions } from './types.js';
2
+ import type { Livestock } from '../../types.js';
3
+ export declare class GitHubProvider implements IssueProvider {
4
+ readonly type: "github";
5
+ private repos;
6
+ constructor(livestock: Livestock[]);
7
+ isAuthenticated(): Promise<boolean>;
8
+ authenticate(): Promise<void>;
9
+ fetchIssues(options?: FetchIssuesOptions): Promise<Issue[]>;
10
+ getIssue(id: string): Promise<Issue>;
11
+ }
@@ -0,0 +1,154 @@
1
+ // src/lib/issues/github.ts
2
+ import { execaSync } from 'execa';
3
+ /**
4
+ * Parse a GitHub URL to extract owner and repo.
5
+ */
6
+ function parseGitHubUrl(url) {
7
+ // HTTPS format
8
+ const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.\s]+)/);
9
+ if (httpsMatch) {
10
+ return { owner: httpsMatch[1], repo: httpsMatch[2].replace(/\.git$/, '') };
11
+ }
12
+ // SSH format
13
+ const sshMatch = url.match(/github\.com:([^/]+)\/([^/.\s]+)/);
14
+ if (sshMatch) {
15
+ return { owner: sshMatch[1], repo: sshMatch[2].replace(/\.git$/, '') };
16
+ }
17
+ return null;
18
+ }
19
+ export class GitHubProvider {
20
+ type = 'github';
21
+ repos = [];
22
+ constructor(livestock) {
23
+ // Extract GitHub repos from local livestock only
24
+ const localLivestock = livestock.filter((l) => !l.barn && l.repo);
25
+ const seen = new Set();
26
+ for (const l of localLivestock) {
27
+ if (!l.repo)
28
+ continue;
29
+ const parsed = parseGitHubUrl(l.repo);
30
+ if (parsed) {
31
+ const key = `${parsed.owner}/${parsed.repo}`;
32
+ if (!seen.has(key)) {
33
+ seen.add(key);
34
+ this.repos.push({
35
+ ...parsed,
36
+ livestockName: l.name,
37
+ livestockPath: l.path,
38
+ });
39
+ }
40
+ }
41
+ }
42
+ }
43
+ async isAuthenticated() {
44
+ try {
45
+ execaSync('gh', ['auth', 'status']);
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ async authenticate() {
53
+ // GitHub auth is handled externally via `gh auth login`
54
+ // This is a no-op - the view should display instructions
55
+ throw new Error('Run `gh auth login` in your terminal to authenticate with GitHub');
56
+ }
57
+ async fetchIssues(options = {}) {
58
+ const { state = 'open', limit = 50 } = options;
59
+ if (this.repos.length === 0) {
60
+ return [];
61
+ }
62
+ const allIssues = [];
63
+ for (const repo of this.repos) {
64
+ try {
65
+ const result = execaSync('gh', [
66
+ 'issue', 'list',
67
+ '--repo', `${repo.owner}/${repo.repo}`,
68
+ '--state', state,
69
+ '--limit', String(limit),
70
+ '--json', 'number,title,state,author,labels,createdAt,updatedAt,body,url,comments',
71
+ ]);
72
+ const issues = JSON.parse(result.stdout);
73
+ for (const issue of issues) {
74
+ allIssues.push({
75
+ id: `${repo.owner}/${repo.repo}#${issue.number}`,
76
+ identifier: `#${issue.number}`,
77
+ title: issue.title,
78
+ state: issue.state.toLowerCase(),
79
+ isOpen: issue.state.toLowerCase() === 'open',
80
+ author: issue.author.login,
81
+ body: issue.body || '',
82
+ labels: issue.labels.map((l) => l.name),
83
+ url: issue.url,
84
+ createdAt: issue.createdAt,
85
+ updatedAt: issue.updatedAt,
86
+ comments: issue.comments.map((c) => ({
87
+ id: `${issue.number}-${c.createdAt}`,
88
+ author: c.author.login,
89
+ body: c.body,
90
+ createdAt: c.createdAt,
91
+ })),
92
+ source: {
93
+ type: 'github',
94
+ repo: `${repo.owner}/${repo.repo}`,
95
+ livestockName: repo.livestockName,
96
+ livestockPath: repo.livestockPath,
97
+ },
98
+ });
99
+ }
100
+ }
101
+ catch (err) {
102
+ console.error(`[github] Failed to fetch issues for ${repo.owner}/${repo.repo}:`, err);
103
+ }
104
+ }
105
+ // Sort by updated date (most recent first)
106
+ allIssues.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
107
+ return allIssues;
108
+ }
109
+ async getIssue(id) {
110
+ // Parse id format: "owner/repo#number"
111
+ const match = id.match(/^([^/]+)\/([^#]+)#(\d+)$/);
112
+ if (!match) {
113
+ throw new Error(`Invalid issue ID format: ${id}`);
114
+ }
115
+ const [, owner, repo, numberStr] = match;
116
+ const issueNumber = parseInt(numberStr, 10);
117
+ // Find the livestock for this repo
118
+ const repoInfo = this.repos.find((r) => r.owner === owner && r.repo === repo);
119
+ if (!repoInfo) {
120
+ throw new Error(`Repo not found in livestock: ${owner}/${repo}`);
121
+ }
122
+ const result = execaSync('gh', [
123
+ 'issue', 'view', String(issueNumber),
124
+ '--repo', `${owner}/${repo}`,
125
+ '--json', 'number,title,state,author,labels,createdAt,updatedAt,body,url,comments',
126
+ ]);
127
+ const issue = JSON.parse(result.stdout);
128
+ return {
129
+ id,
130
+ identifier: `#${issue.number}`,
131
+ title: issue.title,
132
+ state: issue.state.toLowerCase(),
133
+ isOpen: issue.state.toLowerCase() === 'open',
134
+ author: issue.author.login,
135
+ body: issue.body || '',
136
+ labels: issue.labels.map((l) => l.name),
137
+ url: issue.url,
138
+ createdAt: issue.createdAt,
139
+ updatedAt: issue.updatedAt,
140
+ comments: issue.comments.map((c) => ({
141
+ id: `${issue.number}-${c.createdAt}`,
142
+ author: c.author.login,
143
+ body: c.body,
144
+ createdAt: c.createdAt,
145
+ })),
146
+ source: {
147
+ type: 'github',
148
+ repo: `${owner}/${repo}`,
149
+ livestockName: repoInfo.livestockName,
150
+ livestockPath: repoInfo.livestockPath,
151
+ },
152
+ };
153
+ }
154
+ }
@@ -0,0 +1,14 @@
1
+ import type { Project } from '../../types.js';
2
+ import type { IssueProvider } from './types.js';
3
+ export * from './types.js';
4
+ export { GitHubProvider } from './github.js';
5
+ export { LinearProvider } from './linear.js';
6
+ /**
7
+ * Get the appropriate issue provider for a project.
8
+ * Returns null if issue tracking is disabled.
9
+ */
10
+ export declare function getProvider(project: Project): IssueProvider | null;
11
+ /**
12
+ * Check if a project has issue tracking enabled.
13
+ */
14
+ export declare function hasIssueTracking(project: Project): boolean;
@@ -0,0 +1,27 @@
1
+ import { GitHubProvider } from './github.js';
2
+ import { LinearProvider } from './linear.js';
3
+ export * from './types.js';
4
+ export { GitHubProvider } from './github.js';
5
+ export { LinearProvider } from './linear.js';
6
+ /**
7
+ * Get the appropriate issue provider for a project.
8
+ * Returns null if issue tracking is disabled.
9
+ */
10
+ export function getProvider(project) {
11
+ const config = project.issueProvider ?? { type: 'github' };
12
+ switch (config.type) {
13
+ case 'github':
14
+ return new GitHubProvider(project.livestock ?? []);
15
+ case 'linear':
16
+ return new LinearProvider(config.teamId, config.teamName);
17
+ case 'none':
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Check if a project has issue tracking enabled.
23
+ */
24
+ export function hasIssueTracking(project) {
25
+ const config = project.issueProvider ?? { type: 'github' };
26
+ return config.type !== 'none';
27
+ }
@@ -0,0 +1,24 @@
1
+ import type { Issue, FetchIssuesOptions, LinearProviderInterface, LinearTeam, LinearCycle, LinearAssignee } from './types.js';
2
+ export declare class LinearProvider implements LinearProviderInterface {
3
+ readonly type: "linear";
4
+ private teamId?;
5
+ private teamName?;
6
+ private cachedUserId?;
7
+ private cachedActiveCycleId?;
8
+ constructor(teamId?: string, teamName?: string);
9
+ isAuthenticated(): Promise<boolean>;
10
+ authenticate(): Promise<void>;
11
+ needsTeamSelection(): boolean;
12
+ fetchTeams(): Promise<LinearTeam[]>;
13
+ setTeamId(teamId: string): void;
14
+ setTeamName(teamName: string): void;
15
+ getTeamName(): string | undefined;
16
+ fetchTeamName(): Promise<string | undefined>;
17
+ getCurrentUserId(): Promise<string | null>;
18
+ fetchCycles(): Promise<LinearCycle[]>;
19
+ getActiveCycleId(): string | undefined;
20
+ fetchAssignees(): Promise<LinearAssignee[]>;
21
+ fetchIssues(options?: FetchIssuesOptions): Promise<Issue[]>;
22
+ getIssue(id: string): Promise<Issue>;
23
+ private normalizeIssue;
24
+ }