@hasna/connectors 0.3.16 → 0.4.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.
- package/bin/index.js +71 -1
- package/bin/mcp.js +71 -1
- package/bin/serve.js +70 -0
- package/connectors/connect-asana/.env.example +11 -0
- package/connectors/connect-asana/CLAUDE.md +128 -0
- package/connectors/connect-asana/README.md +193 -0
- package/connectors/connect-asana/package.json +52 -0
- package/connectors/connect-asana/src/api/client.ts +119 -0
- package/connectors/connect-asana/src/api/index.ts +319 -0
- package/connectors/connect-asana/src/cli/index.ts +731 -0
- package/connectors/connect-asana/src/index.ts +19 -0
- package/connectors/connect-asana/src/types/index.ts +270 -0
- package/connectors/connect-asana/src/utils/config.ts +171 -0
- package/connectors/connect-asana/src/utils/output.ts +119 -0
- package/connectors/connect-asana/tsconfig.json +16 -0
- package/connectors/connect-clickup/.env.example +11 -0
- package/connectors/connect-clickup/CLAUDE.md +128 -0
- package/connectors/connect-clickup/README.md +193 -0
- package/connectors/connect-clickup/package.json +52 -0
- package/connectors/connect-clickup/src/api/client.ts +116 -0
- package/connectors/connect-clickup/src/api/index.ts +400 -0
- package/connectors/connect-clickup/src/cli/index.ts +625 -0
- package/connectors/connect-clickup/src/index.ts +19 -0
- package/connectors/connect-clickup/src/types/index.ts +591 -0
- package/connectors/connect-clickup/src/utils/config.ts +157 -0
- package/connectors/connect-clickup/src/utils/output.ts +119 -0
- package/connectors/connect-clickup/tsconfig.json +16 -0
- package/connectors/connect-confluence/.env.example +11 -0
- package/connectors/connect-confluence/CLAUDE.md +272 -0
- package/connectors/connect-confluence/README.md +193 -0
- package/connectors/connect-confluence/package.json +53 -0
- package/connectors/connect-confluence/scripts/release.ts +179 -0
- package/connectors/connect-confluence/src/api/client.ts +213 -0
- package/connectors/connect-confluence/src/api/example.ts +48 -0
- package/connectors/connect-confluence/src/api/index.ts +51 -0
- package/connectors/connect-confluence/src/cli/index.ts +254 -0
- package/connectors/connect-confluence/src/index.ts +103 -0
- package/connectors/connect-confluence/src/types/index.ts +237 -0
- package/connectors/connect-confluence/src/utils/auth.ts +274 -0
- package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
- package/connectors/connect-confluence/src/utils/config.ts +326 -0
- package/connectors/connect-confluence/src/utils/output.ts +175 -0
- package/connectors/connect-confluence/src/utils/settings.ts +114 -0
- package/connectors/connect-confluence/src/utils/storage.ts +198 -0
- package/connectors/connect-confluence/tsconfig.json +16 -0
- package/connectors/connect-jira/.env.example +11 -0
- package/connectors/connect-jira/CLAUDE.md +128 -0
- package/connectors/connect-jira/README.md +193 -0
- package/connectors/connect-jira/package.json +53 -0
- package/connectors/connect-jira/src/api/client.ts +131 -0
- package/connectors/connect-jira/src/api/index.ts +266 -0
- package/connectors/connect-jira/src/cli/index.ts +653 -0
- package/connectors/connect-jira/src/index.ts +23 -0
- package/connectors/connect-jira/src/types/index.ts +448 -0
- package/connectors/connect-jira/src/utils/config.ts +179 -0
- package/connectors/connect-jira/src/utils/output.ts +119 -0
- package/connectors/connect-jira/tsconfig.json +16 -0
- package/connectors/connect-linear/CLAUDE.md +88 -0
- package/connectors/connect-linear/README.md +201 -0
- package/connectors/connect-linear/package.json +45 -0
- package/connectors/connect-linear/src/api/client.ts +62 -0
- package/connectors/connect-linear/src/api/index.ts +46 -0
- package/connectors/connect-linear/src/api/issues.ts +247 -0
- package/connectors/connect-linear/src/api/projects.ts +179 -0
- package/connectors/connect-linear/src/api/teams.ts +125 -0
- package/connectors/connect-linear/src/api/users.ts +112 -0
- package/connectors/connect-linear/src/cli/index.ts +560 -0
- package/connectors/connect-linear/src/index.ts +27 -0
- package/connectors/connect-linear/src/types/index.ts +275 -0
- package/connectors/connect-linear/src/utils/config.ts +249 -0
- package/connectors/connect-linear/src/utils/output.ts +119 -0
- package/connectors/connect-linear/tsconfig.json +16 -0
- package/connectors/connect-slack/.env.example +7 -0
- package/connectors/connect-slack/CLAUDE.md +69 -0
- package/connectors/connect-slack/README.md +150 -0
- package/connectors/connect-slack/package.json +44 -0
- package/connectors/connect-slack/src/api/channels.ts +112 -0
- package/connectors/connect-slack/src/api/client.ts +97 -0
- package/connectors/connect-slack/src/api/index.ts +42 -0
- package/connectors/connect-slack/src/api/messages.ts +127 -0
- package/connectors/connect-slack/src/api/users.ts +110 -0
- package/connectors/connect-slack/src/cli/index.ts +494 -0
- package/connectors/connect-slack/src/index.ts +21 -0
- package/connectors/connect-slack/src/types/index.ts +263 -0
- package/connectors/connect-slack/src/utils/config.ts +297 -0
- package/connectors/connect-slack/src/utils/output.ts +119 -0
- package/connectors/connect-slack/tsconfig.json +16 -0
- package/connectors/connect-telegram/.env.example +2 -0
- package/connectors/connect-telegram/package.json +49 -0
- package/connectors/connect-todoist/.env.example +11 -0
- package/connectors/connect-todoist/CLAUDE.md +104 -0
- package/connectors/connect-todoist/README.md +193 -0
- package/connectors/connect-todoist/package.json +52 -0
- package/connectors/connect-todoist/src/api/client.ts +117 -0
- package/connectors/connect-todoist/src/api/index.ts +188 -0
- package/connectors/connect-todoist/src/cli/index.ts +990 -0
- package/connectors/connect-todoist/src/index.ts +21 -0
- package/connectors/connect-todoist/src/types/index.ts +240 -0
- package/connectors/connect-todoist/src/utils/config.ts +157 -0
- package/connectors/connect-todoist/src/utils/output.ts +119 -0
- package/connectors/connect-todoist/tsconfig.json +16 -0
- package/connectors/connect-trello/.env.example +11 -0
- package/connectors/connect-trello/CLAUDE.md +128 -0
- package/connectors/connect-trello/README.md +193 -0
- package/connectors/connect-trello/package.json +53 -0
- package/connectors/connect-trello/src/api/client.ts +128 -0
- package/connectors/connect-trello/src/api/index.ts +278 -0
- package/connectors/connect-trello/src/cli/index.ts +737 -0
- package/connectors/connect-trello/src/index.ts +21 -0
- package/connectors/connect-trello/src/types/index.ts +314 -0
- package/connectors/connect-trello/src/utils/config.ts +182 -0
- package/connectors/connect-trello/src/utils/output.ts +119 -0
- package/connectors/connect-trello/tsconfig.json +16 -0
- package/connectors/connect-whatsapp/.env.example +11 -0
- package/connectors/connect-whatsapp/CLAUDE.md +113 -0
- package/connectors/connect-whatsapp/README.md +193 -0
- package/connectors/connect-whatsapp/package.json +53 -0
- package/connectors/connect-whatsapp/src/api/client.ts +133 -0
- package/connectors/connect-whatsapp/src/api/index.ts +365 -0
- package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
- package/connectors/connect-whatsapp/src/index.ts +25 -0
- package/connectors/connect-whatsapp/src/types/index.ts +502 -0
- package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
- package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
- package/connectors/connect-whatsapp/tsconfig.json +16 -0
- package/dist/index.js +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Linear API Types
|
|
2
|
+
|
|
3
|
+
export type OutputFormat = 'json' | 'table' | 'pretty';
|
|
4
|
+
|
|
5
|
+
// Configuration
|
|
6
|
+
export interface LinearConfig {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CliConfig {
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
defaultTeamId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// API Error
|
|
17
|
+
export class LinearApiError extends Error {
|
|
18
|
+
constructor(
|
|
19
|
+
message: string,
|
|
20
|
+
public readonly code: string,
|
|
21
|
+
public readonly statusCode?: number
|
|
22
|
+
) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'LinearApiError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// GraphQL response wrapper
|
|
29
|
+
export interface GraphQLResponse<T> {
|
|
30
|
+
data?: T;
|
|
31
|
+
errors?: Array<{
|
|
32
|
+
message: string;
|
|
33
|
+
locations?: Array<{ line: number; column: number }>;
|
|
34
|
+
path?: string[];
|
|
35
|
+
extensions?: Record<string, unknown>;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// User types
|
|
40
|
+
export interface LinearUser {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
displayName: string;
|
|
44
|
+
email: string;
|
|
45
|
+
avatarUrl?: string;
|
|
46
|
+
active: boolean;
|
|
47
|
+
admin: boolean;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Team types
|
|
53
|
+
export interface LinearTeam {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
key: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
icon?: string;
|
|
59
|
+
color?: string;
|
|
60
|
+
private: boolean;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Issue state types
|
|
66
|
+
export interface LinearWorkflowState {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
color: string;
|
|
70
|
+
type: 'backlog' | 'unstarted' | 'started' | 'completed' | 'canceled';
|
|
71
|
+
position: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Issue label types
|
|
75
|
+
export interface LinearLabel {
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
color: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Issue priority
|
|
83
|
+
export type LinearPriority = 0 | 1 | 2 | 3 | 4; // 0 = no priority, 1 = urgent, 2 = high, 3 = normal, 4 = low
|
|
84
|
+
|
|
85
|
+
// Issue types
|
|
86
|
+
export interface LinearIssue {
|
|
87
|
+
id: string;
|
|
88
|
+
identifier: string;
|
|
89
|
+
title: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
priority: LinearPriority;
|
|
92
|
+
priorityLabel: string;
|
|
93
|
+
estimate?: number;
|
|
94
|
+
sortOrder: number;
|
|
95
|
+
boardOrder: number;
|
|
96
|
+
startedAt?: string;
|
|
97
|
+
completedAt?: string;
|
|
98
|
+
canceledAt?: string;
|
|
99
|
+
autoClosedAt?: string;
|
|
100
|
+
autoArchivedAt?: string;
|
|
101
|
+
dueDate?: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
updatedAt: string;
|
|
104
|
+
archivedAt?: string;
|
|
105
|
+
url: string;
|
|
106
|
+
number: number;
|
|
107
|
+
team?: LinearTeam;
|
|
108
|
+
state?: LinearWorkflowState;
|
|
109
|
+
assignee?: LinearUser;
|
|
110
|
+
creator?: LinearUser;
|
|
111
|
+
labels?: { nodes: LinearLabel[] };
|
|
112
|
+
project?: LinearProject;
|
|
113
|
+
parent?: LinearIssue;
|
|
114
|
+
children?: { nodes: LinearIssue[] };
|
|
115
|
+
comments?: { nodes: LinearComment[] };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Comment types
|
|
119
|
+
export interface LinearComment {
|
|
120
|
+
id: string;
|
|
121
|
+
body: string;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
updatedAt: string;
|
|
124
|
+
user?: LinearUser;
|
|
125
|
+
issue?: LinearIssue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Project types
|
|
129
|
+
export interface LinearProject {
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
description?: string;
|
|
133
|
+
icon?: string;
|
|
134
|
+
color?: string;
|
|
135
|
+
state: string;
|
|
136
|
+
progress: number;
|
|
137
|
+
startDate?: string;
|
|
138
|
+
targetDate?: string;
|
|
139
|
+
createdAt: string;
|
|
140
|
+
updatedAt: string;
|
|
141
|
+
lead?: LinearUser;
|
|
142
|
+
teams?: { nodes: LinearTeam[] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Cycle types
|
|
146
|
+
export interface LinearCycle {
|
|
147
|
+
id: string;
|
|
148
|
+
number: number;
|
|
149
|
+
name?: string;
|
|
150
|
+
description?: string;
|
|
151
|
+
startsAt: string;
|
|
152
|
+
endsAt: string;
|
|
153
|
+
completedAt?: string;
|
|
154
|
+
progress: number;
|
|
155
|
+
createdAt: string;
|
|
156
|
+
updatedAt: string;
|
|
157
|
+
team?: LinearTeam;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Pagination
|
|
161
|
+
export interface PageInfo {
|
|
162
|
+
hasNextPage: boolean;
|
|
163
|
+
hasPreviousPage: boolean;
|
|
164
|
+
startCursor?: string;
|
|
165
|
+
endCursor?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface Connection<T> {
|
|
169
|
+
nodes: T[];
|
|
170
|
+
pageInfo: PageInfo;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// API Response types
|
|
174
|
+
export interface IssuesResponse {
|
|
175
|
+
issues: Connection<LinearIssue>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface IssueResponse {
|
|
179
|
+
issue: LinearIssue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface TeamsResponse {
|
|
183
|
+
teams: Connection<LinearTeam>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface TeamResponse {
|
|
187
|
+
team: LinearTeam;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface UsersResponse {
|
|
191
|
+
users: Connection<LinearUser>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface UserResponse {
|
|
195
|
+
user: LinearUser;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ViewerResponse {
|
|
199
|
+
viewer: LinearUser;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface ProjectsResponse {
|
|
203
|
+
projects: Connection<LinearProject>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface ProjectResponse {
|
|
207
|
+
project: LinearProject;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Request options
|
|
211
|
+
export interface ListOptions {
|
|
212
|
+
first?: number;
|
|
213
|
+
after?: string;
|
|
214
|
+
orderBy?: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface IssueListOptions extends ListOptions {
|
|
218
|
+
teamId?: string;
|
|
219
|
+
projectId?: string;
|
|
220
|
+
assigneeId?: string;
|
|
221
|
+
stateId?: string;
|
|
222
|
+
filter?: IssueFilter;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface IssueFilter {
|
|
226
|
+
team?: { id?: { eq?: string } };
|
|
227
|
+
project?: { id?: { eq?: string } };
|
|
228
|
+
assignee?: { id?: { eq?: string } };
|
|
229
|
+
state?: { name?: { eq?: string }; type?: { eq?: string } };
|
|
230
|
+
priority?: { eq?: number };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface CreateIssueInput {
|
|
234
|
+
title: string;
|
|
235
|
+
description?: string;
|
|
236
|
+
teamId: string;
|
|
237
|
+
projectId?: string;
|
|
238
|
+
assigneeId?: string;
|
|
239
|
+
priority?: LinearPriority;
|
|
240
|
+
stateId?: string;
|
|
241
|
+
labelIds?: string[];
|
|
242
|
+
estimate?: number;
|
|
243
|
+
dueDate?: string;
|
|
244
|
+
parentId?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface UpdateIssueInput {
|
|
248
|
+
title?: string;
|
|
249
|
+
description?: string;
|
|
250
|
+
projectId?: string;
|
|
251
|
+
assigneeId?: string;
|
|
252
|
+
priority?: LinearPriority;
|
|
253
|
+
stateId?: string;
|
|
254
|
+
labelIds?: string[];
|
|
255
|
+
estimate?: number;
|
|
256
|
+
dueDate?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Mutation response types
|
|
260
|
+
export interface IssuePayload {
|
|
261
|
+
success: boolean;
|
|
262
|
+
issue?: LinearIssue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface CreateIssueResponse {
|
|
266
|
+
issueCreate: IssuePayload;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface UpdateIssueResponse {
|
|
270
|
+
issueUpdate: IssuePayload;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface ArchiveIssueResponse {
|
|
274
|
+
issueArchive: IssuePayload;
|
|
275
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import type { CliConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
const CONNECTOR_NAME = 'connect-linear';
|
|
7
|
+
const DEFAULT_PROFILE = 'default';
|
|
8
|
+
const CURRENT_PROFILE_FILE = 'current_profile';
|
|
9
|
+
const PROFILES_DIR = 'profiles';
|
|
10
|
+
|
|
11
|
+
// Store for --profile flag override
|
|
12
|
+
let profileOverride: string | undefined;
|
|
13
|
+
|
|
14
|
+
// Config directory: ~/.connect/connect-linear/
|
|
15
|
+
const BASE_CONFIG_DIR = join(homedir(), '.connect', CONNECTOR_NAME);
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// Profile Management
|
|
19
|
+
// ============================================
|
|
20
|
+
|
|
21
|
+
export function setProfileOverride(profile: string | undefined): void {
|
|
22
|
+
profileOverride = profile;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getProfileOverride(): string | undefined {
|
|
26
|
+
return profileOverride;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureBaseConfigDir(): void {
|
|
30
|
+
if (!existsSync(BASE_CONFIG_DIR)) {
|
|
31
|
+
mkdirSync(BASE_CONFIG_DIR, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getProfilesDir(): string {
|
|
36
|
+
return join(BASE_CONFIG_DIR, PROFILES_DIR);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getCurrentProfileFile(): string {
|
|
40
|
+
return join(BASE_CONFIG_DIR, CURRENT_PROFILE_FILE);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the current active profile name
|
|
45
|
+
*/
|
|
46
|
+
export function getCurrentProfile(): string {
|
|
47
|
+
if (profileOverride) {
|
|
48
|
+
return profileOverride;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ensureBaseConfigDir();
|
|
52
|
+
|
|
53
|
+
const profilesDir = getProfilesDir();
|
|
54
|
+
if (!existsSync(profilesDir)) {
|
|
55
|
+
mkdirSync(profilesDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const currentProfileFile = getCurrentProfileFile();
|
|
59
|
+
if (existsSync(currentProfileFile)) {
|
|
60
|
+
try {
|
|
61
|
+
const profile = readFileSync(currentProfileFile, 'utf-8').trim();
|
|
62
|
+
if (profile && profileExists(profile)) {
|
|
63
|
+
return profile;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Fall through to default
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return DEFAULT_PROFILE;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set the current active profile
|
|
75
|
+
*/
|
|
76
|
+
export function setCurrentProfile(profile: string): void {
|
|
77
|
+
ensureBaseConfigDir();
|
|
78
|
+
|
|
79
|
+
if (!profileExists(profile) && profile !== DEFAULT_PROFILE) {
|
|
80
|
+
throw new Error(`Profile "${profile}" does not exist. Create it first with "profile create ${profile}"`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
writeFileSync(getCurrentProfileFile(), profile);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if a profile exists
|
|
88
|
+
*/
|
|
89
|
+
export function profileExists(profile: string): boolean {
|
|
90
|
+
const profileDir = join(getProfilesDir(), profile);
|
|
91
|
+
return existsSync(profileDir);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* List all available profiles
|
|
96
|
+
*/
|
|
97
|
+
export function listProfiles(): string[] {
|
|
98
|
+
ensureBaseConfigDir();
|
|
99
|
+
|
|
100
|
+
const profilesDir = getProfilesDir();
|
|
101
|
+
if (!existsSync(profilesDir)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return readdirSync(profilesDir, { withFileTypes: true })
|
|
106
|
+
.filter(dirent => dirent.isDirectory())
|
|
107
|
+
.map(dirent => dirent.name)
|
|
108
|
+
.sort();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a new profile
|
|
113
|
+
*/
|
|
114
|
+
export function createProfile(profile: string): void {
|
|
115
|
+
ensureBaseConfigDir();
|
|
116
|
+
|
|
117
|
+
if (profileExists(profile)) {
|
|
118
|
+
throw new Error(`Profile "${profile}" already exists`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validate profile name
|
|
122
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
|
|
123
|
+
throw new Error('Profile name can only contain letters, numbers, hyphens, and underscores');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const profileDir = join(getProfilesDir(), profile);
|
|
127
|
+
mkdirSync(profileDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Delete a profile
|
|
132
|
+
*/
|
|
133
|
+
export function deleteProfile(profile: string): void {
|
|
134
|
+
if (profile === DEFAULT_PROFILE) {
|
|
135
|
+
throw new Error('Cannot delete the default profile');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!profileExists(profile)) {
|
|
139
|
+
throw new Error(`Profile "${profile}" does not exist`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const currentProfile = getCurrentProfile();
|
|
143
|
+
if (currentProfile === profile) {
|
|
144
|
+
setCurrentProfile(DEFAULT_PROFILE);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const profileDir = join(getProfilesDir(), profile);
|
|
148
|
+
rmSync(profileDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the config directory for the current profile
|
|
153
|
+
*/
|
|
154
|
+
function getProfileDir(): string {
|
|
155
|
+
ensureBaseConfigDir();
|
|
156
|
+
|
|
157
|
+
const profilesDir = getProfilesDir();
|
|
158
|
+
if (!existsSync(profilesDir)) {
|
|
159
|
+
mkdirSync(profilesDir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const profile = getCurrentProfile();
|
|
163
|
+
const profileDir = join(profilesDir, profile);
|
|
164
|
+
|
|
165
|
+
if (!existsSync(profileDir)) {
|
|
166
|
+
mkdirSync(profileDir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return profileDir;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getConfigDir(): string {
|
|
173
|
+
return getProfileDir();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function getBaseConfigDir(): string {
|
|
177
|
+
return BASE_CONFIG_DIR;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function ensureConfigDir(): void {
|
|
181
|
+
getProfileDir();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ============================================
|
|
185
|
+
// Config Management
|
|
186
|
+
// ============================================
|
|
187
|
+
|
|
188
|
+
export function loadConfig(): CliConfig {
|
|
189
|
+
ensureConfigDir();
|
|
190
|
+
const configFile = join(getProfileDir(), 'config.json');
|
|
191
|
+
|
|
192
|
+
if (!existsSync(configFile)) {
|
|
193
|
+
return {};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const content = readFileSync(configFile, 'utf-8');
|
|
198
|
+
return JSON.parse(content);
|
|
199
|
+
} catch {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function saveConfig(config: CliConfig): void {
|
|
205
|
+
ensureConfigDir();
|
|
206
|
+
const configFile = join(getProfileDir(), 'config.json');
|
|
207
|
+
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================
|
|
211
|
+
// API Key Management
|
|
212
|
+
// ============================================
|
|
213
|
+
|
|
214
|
+
export function getApiKey(): string | undefined {
|
|
215
|
+
return process.env.LINEAR_API_KEY || loadConfig().apiKey;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function setApiKey(apiKey: string): void {
|
|
219
|
+
const config = loadConfig();
|
|
220
|
+
config.apiKey = apiKey;
|
|
221
|
+
saveConfig(config);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getDefaultTeamId(): string | undefined {
|
|
225
|
+
return loadConfig().defaultTeamId;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function setDefaultTeamId(teamId: string): void {
|
|
229
|
+
const config = loadConfig();
|
|
230
|
+
config.defaultTeamId = teamId;
|
|
231
|
+
saveConfig(config);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================
|
|
235
|
+
// Utility Functions
|
|
236
|
+
// ============================================
|
|
237
|
+
|
|
238
|
+
export function clearConfig(): void {
|
|
239
|
+
saveConfig({});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function isAuthenticated(): boolean {
|
|
243
|
+
const apiKey = getApiKey();
|
|
244
|
+
return apiKey !== undefined && apiKey !== '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getActiveProfileName(): string {
|
|
248
|
+
return getCurrentProfile();
|
|
249
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export type OutputFormat = 'json' | 'table' | 'pretty';
|
|
4
|
+
|
|
5
|
+
export function formatOutput(data: unknown, format: OutputFormat = 'pretty'): string {
|
|
6
|
+
switch (format) {
|
|
7
|
+
case 'json':
|
|
8
|
+
return JSON.stringify(data, null, 2);
|
|
9
|
+
case 'table':
|
|
10
|
+
return formatAsTable(data);
|
|
11
|
+
case 'pretty':
|
|
12
|
+
default:
|
|
13
|
+
return formatPretty(data);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatAsTable(data: unknown): string {
|
|
18
|
+
if (!Array.isArray(data)) {
|
|
19
|
+
data = [data];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const items = data as Record<string, unknown>[];
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
return 'No data';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const firstItem = items[0];
|
|
28
|
+
if (!firstItem || typeof firstItem !== 'object') {
|
|
29
|
+
return 'No data';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const keys = Object.keys(firstItem);
|
|
33
|
+
const colWidths = keys.map(key => {
|
|
34
|
+
const maxValue = Math.max(
|
|
35
|
+
key.length,
|
|
36
|
+
...items.map(item => String(item[key] ?? '').length)
|
|
37
|
+
);
|
|
38
|
+
return Math.min(maxValue, 40);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const header = keys.map((key, i) => key.padEnd(colWidths[i] ?? 10)).join(' | ');
|
|
42
|
+
const separator = colWidths.map(w => '-'.repeat(w)).join('-+-');
|
|
43
|
+
|
|
44
|
+
const rows = items.map(item =>
|
|
45
|
+
keys.map((key, i) => {
|
|
46
|
+
const value = String(item[key] ?? '');
|
|
47
|
+
const width = colWidths[i] ?? 10;
|
|
48
|
+
return value.length > width
|
|
49
|
+
? value.substring(0, width - 3) + '...'
|
|
50
|
+
: value.padEnd(width);
|
|
51
|
+
}).join(' | ')
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return [header, separator, ...rows].join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatPretty(data: unknown): string {
|
|
58
|
+
if (Array.isArray(data)) {
|
|
59
|
+
return data.map((item, i) => `${chalk.cyan(`[${i + 1}]`)} ${formatPrettyItem(item)}`).join('\n\n');
|
|
60
|
+
}
|
|
61
|
+
return formatPrettyItem(data);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatPrettyItem(item: unknown, indent = 0): string {
|
|
65
|
+
if (item === null || item === undefined) {
|
|
66
|
+
return chalk.gray('null');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof item !== 'object') {
|
|
70
|
+
return String(item);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const spaces = ' '.repeat(indent);
|
|
74
|
+
const entries = Object.entries(item as Record<string, unknown>);
|
|
75
|
+
|
|
76
|
+
return entries
|
|
77
|
+
.map(([key, value]) => {
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
if (value.length === 0) {
|
|
80
|
+
return `${spaces}${chalk.blue(key)}: ${chalk.gray('[]')}`;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value[0] === 'object') {
|
|
83
|
+
return `${spaces}${chalk.blue(key)}:\n${value.map(v => formatPrettyItem(v, indent + 1)).join('\n')}`;
|
|
84
|
+
}
|
|
85
|
+
return `${spaces}${chalk.blue(key)}: ${value.join(', ')}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof value === 'object' && value !== null) {
|
|
89
|
+
return `${spaces}${chalk.blue(key)}:\n${formatPrettyItem(value, indent + 1)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${spaces}${chalk.blue(key)}: ${chalk.white(String(value))}`;
|
|
93
|
+
})
|
|
94
|
+
.join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function success(message: string): void {
|
|
98
|
+
console.log(chalk.green('OK'), message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function error(message: string): void {
|
|
102
|
+
console.error(chalk.red('Error:'), message);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function warn(message: string): void {
|
|
106
|
+
console.warn(chalk.yellow('Warning:'), message);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function info(message: string): void {
|
|
110
|
+
console.log(chalk.blue('Info:'), message);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function heading(message: string): void {
|
|
114
|
+
console.log(chalk.bold.cyan(`\n${message}\n`));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function print(data: unknown, format: OutputFormat = 'pretty'): void {
|
|
118
|
+
console.log(formatOutput(data, format));
|
|
119
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"types": ["bun-types"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist", "bin"]
|
|
16
|
+
}
|