@khangal.j/fireside-cli 0.0.1
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 +33 -0
- package/dist/index.js +480 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Fireside CLI
|
|
2
|
+
|
|
3
|
+
Node.js CLI for Fireside.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @khangal.j/fireside-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
fireside login --base-url http://localhost:3000
|
|
15
|
+
fireside status
|
|
16
|
+
fireside hello
|
|
17
|
+
fireside my-stuff
|
|
18
|
+
fireside projects list
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Local State
|
|
22
|
+
|
|
23
|
+
The CLI stores local files under the `fireside` config directory.
|
|
24
|
+
|
|
25
|
+
- macOS: `~/Library/Application Support/fireside/{config,auth}.json`
|
|
26
|
+
- Linux: `$XDG_CONFIG_HOME/fireside/{config,auth}.json` or `~/.config/fireside/{config,auth}.json`
|
|
27
|
+
- Windows: `%APPDATA%\\fireside\\{config,auth}.json`
|
|
28
|
+
|
|
29
|
+
`config.json` stores settings like `baseUrl`. `auth.json` stores the local session token.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Node.js 20+
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
var import_commander = require("commander");
|
|
6
|
+
|
|
7
|
+
// src/lib/api.ts
|
|
8
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
9
|
+
var CLI_CLIENT_ID = "fireside-cli";
|
|
10
|
+
async function readResponseBody(response) {
|
|
11
|
+
const contentType = response.headers.get("content-type") || "";
|
|
12
|
+
if (contentType.includes("application/json")) {
|
|
13
|
+
return await response.json();
|
|
14
|
+
}
|
|
15
|
+
return await response.text();
|
|
16
|
+
}
|
|
17
|
+
function getErrorMessage(body, fallbackMessage) {
|
|
18
|
+
if (typeof body === "string" && body.trim()) {
|
|
19
|
+
return body;
|
|
20
|
+
}
|
|
21
|
+
if (body && typeof body === "object") {
|
|
22
|
+
const candidate = body;
|
|
23
|
+
return candidate.error_description || candidate.message || candidate.error || fallbackMessage;
|
|
24
|
+
}
|
|
25
|
+
return fallbackMessage;
|
|
26
|
+
}
|
|
27
|
+
async function requestJson(url, init) {
|
|
28
|
+
const response = await fetch(url, init);
|
|
29
|
+
const body = await readResponseBody(response);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
getErrorMessage(body, `Request failed with status ${response.status}.`)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (!body || typeof body !== "object") {
|
|
36
|
+
throw new Error("Expected a JSON response.");
|
|
37
|
+
}
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
function getAuthHeaders(accessToken) {
|
|
41
|
+
return {
|
|
42
|
+
Authorization: `Bearer ${accessToken}`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function delay(milliseconds) {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
47
|
+
}
|
|
48
|
+
async function createDeviceCode(baseUrl) {
|
|
49
|
+
return requestJson(`${baseUrl}/api/auth/device/code`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
"content-type": "application/json"
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
client_id: CLI_CLIENT_ID,
|
|
56
|
+
scope: "openid profile email"
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function pollForAccessToken(baseUrl, deviceCode, intervalSeconds = 5) {
|
|
61
|
+
let pollingIntervalSeconds = intervalSeconds;
|
|
62
|
+
for (; ; ) {
|
|
63
|
+
await delay(pollingIntervalSeconds * 1e3);
|
|
64
|
+
const response = await fetch(`${baseUrl}/api/auth/device/token`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"content-type": "application/json",
|
|
68
|
+
"user-agent": "fireside-cli"
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
72
|
+
device_code: deviceCode,
|
|
73
|
+
client_id: CLI_CLIENT_ID
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
const body = await readResponseBody(response);
|
|
77
|
+
if (response.ok && typeof body === "object" && body.access_token) {
|
|
78
|
+
return body.access_token;
|
|
79
|
+
}
|
|
80
|
+
const errorCode = typeof body === "object" && body && typeof body.error === "string" ? body.error : void 0;
|
|
81
|
+
switch (errorCode) {
|
|
82
|
+
case "authorization_pending":
|
|
83
|
+
continue;
|
|
84
|
+
case "slow_down":
|
|
85
|
+
pollingIntervalSeconds += 5;
|
|
86
|
+
continue;
|
|
87
|
+
case "access_denied":
|
|
88
|
+
throw new Error("Access denied by user.");
|
|
89
|
+
case "expired_token":
|
|
90
|
+
throw new Error("Device code expired. Run the login command again.");
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(
|
|
93
|
+
getErrorMessage(
|
|
94
|
+
body,
|
|
95
|
+
`Token request failed with status ${response.status}.`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function getCurrentUser(baseUrl, accessToken) {
|
|
102
|
+
const data = await requestJson(`${baseUrl}/api/me`, {
|
|
103
|
+
headers: getAuthHeaders(accessToken)
|
|
104
|
+
});
|
|
105
|
+
return data.user;
|
|
106
|
+
}
|
|
107
|
+
async function listProjects(baseUrl, accessToken) {
|
|
108
|
+
return requestJson(`${baseUrl}/api/projects`, {
|
|
109
|
+
headers: getAuthHeaders(accessToken)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function listAssignedTasks(baseUrl, accessToken) {
|
|
113
|
+
return requestJson(`${baseUrl}/api/my-stuff`, {
|
|
114
|
+
headers: getAuthHeaders(accessToken)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function getHello(baseUrl, accessToken) {
|
|
118
|
+
const response = await fetch(`${baseUrl}/api/hello`, {
|
|
119
|
+
headers: getAuthHeaders(accessToken)
|
|
120
|
+
});
|
|
121
|
+
const body = await readResponseBody(response);
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
getErrorMessage(body, `Request failed with status ${response.status}.`)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (typeof body !== "string") {
|
|
128
|
+
throw new Error("Expected a text response.");
|
|
129
|
+
}
|
|
130
|
+
return body;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/lib/auth-state.ts
|
|
134
|
+
var import_promises = require("fs/promises");
|
|
135
|
+
var import_node_os = require("os");
|
|
136
|
+
var import_node_path = require("path");
|
|
137
|
+
var DEFAULT_BASE_URL = "http://localhost:3000";
|
|
138
|
+
var legacyMigrationPromise = null;
|
|
139
|
+
function normalizeBaseUrl(baseUrl) {
|
|
140
|
+
return baseUrl.trim().replace(/\/+$/, "");
|
|
141
|
+
}
|
|
142
|
+
function getConfigDirectory(appName = "fireside") {
|
|
143
|
+
switch (process.platform) {
|
|
144
|
+
case "darwin":
|
|
145
|
+
return (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support", appName);
|
|
146
|
+
case "win32":
|
|
147
|
+
return (0, import_node_path.join)(
|
|
148
|
+
process.env.APPDATA || (0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Roaming"),
|
|
149
|
+
appName
|
|
150
|
+
);
|
|
151
|
+
default:
|
|
152
|
+
return (0, import_node_path.join)(
|
|
153
|
+
process.env.XDG_CONFIG_HOME || (0, import_node_path.join)((0, import_node_os.homedir)(), ".config"),
|
|
154
|
+
appName
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function getAuthStateFilePath() {
|
|
159
|
+
return (0, import_node_path.join)(getConfigDirectory(), "auth.json");
|
|
160
|
+
}
|
|
161
|
+
function getConfigStateFilePath() {
|
|
162
|
+
return (0, import_node_path.join)(getConfigDirectory(), "config.json");
|
|
163
|
+
}
|
|
164
|
+
function getLegacyAuthStateFilePath() {
|
|
165
|
+
return (0, import_node_path.join)(getConfigDirectory("fireside-cli"), "auth.json");
|
|
166
|
+
}
|
|
167
|
+
async function fileExists(filePath) {
|
|
168
|
+
try {
|
|
169
|
+
await (0, import_promises.access)(filePath);
|
|
170
|
+
return true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function ensureConfigDirectory(filePath) {
|
|
176
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), {
|
|
177
|
+
recursive: true,
|
|
178
|
+
mode: 448
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async function writeStateFile(filePath, value) {
|
|
182
|
+
await ensureConfigDirectory(filePath);
|
|
183
|
+
await (0, import_promises.writeFile)(filePath, `${JSON.stringify(value, null, 2)}
|
|
184
|
+
`, {
|
|
185
|
+
mode: 384
|
|
186
|
+
});
|
|
187
|
+
await (0, import_promises.chmod)(filePath, 384);
|
|
188
|
+
}
|
|
189
|
+
async function readStateFile(filePath) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = await (0, import_promises.readFile)(filePath, "utf8");
|
|
192
|
+
return JSON.parse(raw);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error.code === "ENOENT") {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function isAuthState(value) {
|
|
201
|
+
if (!value || typeof value !== "object") {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const candidate = value;
|
|
205
|
+
return typeof candidate.accessToken === "string" && typeof candidate.createdAt === "string";
|
|
206
|
+
}
|
|
207
|
+
function isConfigState(value) {
|
|
208
|
+
if (!value || typeof value !== "object") {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const candidate = value;
|
|
212
|
+
return typeof candidate.baseUrl === "string";
|
|
213
|
+
}
|
|
214
|
+
function isLegacyAuthState(value) {
|
|
215
|
+
return isAuthState(value) && isConfigState(value);
|
|
216
|
+
}
|
|
217
|
+
async function migrateLegacyState() {
|
|
218
|
+
const legacyState = await readStateFile(getLegacyAuthStateFilePath());
|
|
219
|
+
if (legacyState === null) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!isLegacyAuthState(legacyState)) {
|
|
223
|
+
throw new Error("Invalid legacy auth state file format.");
|
|
224
|
+
}
|
|
225
|
+
const authStateFilePath = getAuthStateFilePath();
|
|
226
|
+
const configStateFilePath = getConfigStateFilePath();
|
|
227
|
+
if (!await fileExists(authStateFilePath)) {
|
|
228
|
+
await writeStateFile(authStateFilePath, {
|
|
229
|
+
accessToken: legacyState.accessToken,
|
|
230
|
+
createdAt: legacyState.createdAt
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (!await fileExists(configStateFilePath)) {
|
|
234
|
+
await writeStateFile(configStateFilePath, {
|
|
235
|
+
baseUrl: normalizeBaseUrl(legacyState.baseUrl)
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
await (0, import_promises.rm)(getLegacyAuthStateFilePath(), { force: true });
|
|
239
|
+
}
|
|
240
|
+
async function ensureLegacyStateMigrated() {
|
|
241
|
+
legacyMigrationPromise ??= migrateLegacyState();
|
|
242
|
+
await legacyMigrationPromise;
|
|
243
|
+
}
|
|
244
|
+
async function resolveBaseUrl(baseUrl, configState) {
|
|
245
|
+
const resolvedConfigState = configState === void 0 ? await loadConfigState() : configState;
|
|
246
|
+
return normalizeBaseUrl(
|
|
247
|
+
baseUrl || process.env.FIRESIDE_BASE_URL || resolvedConfigState?.baseUrl || DEFAULT_BASE_URL
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async function loadAuthState() {
|
|
251
|
+
await ensureLegacyStateMigrated();
|
|
252
|
+
const parsed = await readStateFile(getAuthStateFilePath());
|
|
253
|
+
if (parsed === null) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
if (!isAuthState(parsed)) {
|
|
257
|
+
throw new Error("Invalid auth state file format.");
|
|
258
|
+
}
|
|
259
|
+
return parsed;
|
|
260
|
+
}
|
|
261
|
+
async function loadConfigState() {
|
|
262
|
+
await ensureLegacyStateMigrated();
|
|
263
|
+
const parsed = await readStateFile(getConfigStateFilePath());
|
|
264
|
+
if (parsed === null) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (!isConfigState(parsed)) {
|
|
268
|
+
throw new Error("Invalid config state file format.");
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
baseUrl: normalizeBaseUrl(parsed.baseUrl)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
async function saveAuthState(state) {
|
|
275
|
+
await ensureLegacyStateMigrated();
|
|
276
|
+
await writeStateFile(getAuthStateFilePath(), state);
|
|
277
|
+
}
|
|
278
|
+
async function saveConfigState(state) {
|
|
279
|
+
await ensureLegacyStateMigrated();
|
|
280
|
+
await writeStateFile(getConfigStateFilePath(), {
|
|
281
|
+
baseUrl: normalizeBaseUrl(state.baseUrl)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async function clearAuthState() {
|
|
285
|
+
await ensureLegacyStateMigrated();
|
|
286
|
+
await (0, import_promises.rm)(getAuthStateFilePath(), { force: true });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/lib/browser.ts
|
|
290
|
+
var import_node_child_process = require("child_process");
|
|
291
|
+
function getBrowserOpenCommand(url) {
|
|
292
|
+
switch (process.platform) {
|
|
293
|
+
case "darwin":
|
|
294
|
+
return {
|
|
295
|
+
command: "open",
|
|
296
|
+
args: [url]
|
|
297
|
+
};
|
|
298
|
+
case "win32":
|
|
299
|
+
return {
|
|
300
|
+
command: "cmd",
|
|
301
|
+
args: ["/c", "start", "", url]
|
|
302
|
+
};
|
|
303
|
+
default:
|
|
304
|
+
return {
|
|
305
|
+
command: "xdg-open",
|
|
306
|
+
args: [url]
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function openBrowser(url) {
|
|
311
|
+
const { command, args } = getBrowserOpenCommand(url);
|
|
312
|
+
try {
|
|
313
|
+
const child = (0, import_node_child_process.spawn)(command, args, {
|
|
314
|
+
detached: process.platform !== "win32",
|
|
315
|
+
stdio: "ignore"
|
|
316
|
+
});
|
|
317
|
+
child.unref();
|
|
318
|
+
return true;
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/index.ts
|
|
325
|
+
function formatUserCodeForDisplay(userCode) {
|
|
326
|
+
return userCode.match(/.{1,4}/g)?.join("-") || userCode;
|
|
327
|
+
}
|
|
328
|
+
function addBaseUrlOption(command) {
|
|
329
|
+
return command.option(
|
|
330
|
+
"-b, --base-url <url>",
|
|
331
|
+
"Fireside base URL",
|
|
332
|
+
process.env.FIRESIDE_BASE_URL
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
async function requireAuthState() {
|
|
336
|
+
const state = await loadAuthState();
|
|
337
|
+
if (!state) {
|
|
338
|
+
throw new Error("Not signed in. Run `fireside login` first.");
|
|
339
|
+
}
|
|
340
|
+
return state;
|
|
341
|
+
}
|
|
342
|
+
function printProjects(projects) {
|
|
343
|
+
if (!projects.length) {
|
|
344
|
+
console.log("No projects found.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
for (const project of projects) {
|
|
348
|
+
console.log(`${project.title} (${project.id})`);
|
|
349
|
+
console.log(` Color: ${project.color}`);
|
|
350
|
+
console.log(` Members: ${project.members.length}`);
|
|
351
|
+
console.log(` ${project.description}`);
|
|
352
|
+
console.log("");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function formatDueDate(dueDate) {
|
|
356
|
+
if (!dueDate) {
|
|
357
|
+
return "No due date";
|
|
358
|
+
}
|
|
359
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
360
|
+
month: "short",
|
|
361
|
+
day: "numeric",
|
|
362
|
+
year: "numeric"
|
|
363
|
+
}).format(/* @__PURE__ */ new Date(`${dueDate}T00:00:00`));
|
|
364
|
+
}
|
|
365
|
+
function printAssignedTasks(baseUrl, tasks) {
|
|
366
|
+
if (!tasks.length) {
|
|
367
|
+
console.log("Nothing assigned right now.");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const task of tasks) {
|
|
371
|
+
console.log(`${task.title} (${task.id})`);
|
|
372
|
+
console.log(
|
|
373
|
+
` ${task.projectTitle} / ${task.boardTitle} / ${task.columnTitle}`
|
|
374
|
+
);
|
|
375
|
+
console.log(` Due: ${formatDueDate(task.dueDate)}`);
|
|
376
|
+
console.log(
|
|
377
|
+
` Assignees: ${task.assignees.map((assignee) => assignee.name).join(", ") || "None"}`
|
|
378
|
+
);
|
|
379
|
+
if (task.description) {
|
|
380
|
+
console.log(` ${task.description}`);
|
|
381
|
+
}
|
|
382
|
+
console.log(
|
|
383
|
+
` ${baseUrl}/projects/${encodeURIComponent(task.projectId)}/boards/tasks/${encodeURIComponent(task.id)}`
|
|
384
|
+
);
|
|
385
|
+
console.log("");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
var program = new import_commander.Command();
|
|
389
|
+
program.name("fireside").description("Fireside CLI").version("0.0.1").showHelpAfterError();
|
|
390
|
+
addBaseUrlOption(
|
|
391
|
+
program.command("login").description("Authenticate with Fireside using device authorization").option("--no-open", "Do not open the browser automatically").action(async (options) => {
|
|
392
|
+
const configState = await loadConfigState();
|
|
393
|
+
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
394
|
+
const deviceCode = await createDeviceCode(baseUrl);
|
|
395
|
+
const verificationUrl = deviceCode.verification_uri;
|
|
396
|
+
console.log(`Base URL: ${baseUrl}`);
|
|
397
|
+
console.log(`Open this URL: ${verificationUrl}`);
|
|
398
|
+
console.log(
|
|
399
|
+
`Enter this code in the browser: ${formatUserCodeForDisplay(deviceCode.user_code)}`
|
|
400
|
+
);
|
|
401
|
+
if (options.open) {
|
|
402
|
+
const opened = openBrowser(verificationUrl);
|
|
403
|
+
if (opened) {
|
|
404
|
+
console.log("Opened the browser for approval.");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
console.log("Waiting for approval...");
|
|
408
|
+
const accessToken = await pollForAccessToken(
|
|
409
|
+
baseUrl,
|
|
410
|
+
deviceCode.device_code,
|
|
411
|
+
deviceCode.interval
|
|
412
|
+
);
|
|
413
|
+
await saveConfigState({ baseUrl });
|
|
414
|
+
await saveAuthState({
|
|
415
|
+
accessToken,
|
|
416
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
417
|
+
});
|
|
418
|
+
const user = await getCurrentUser(baseUrl, accessToken);
|
|
419
|
+
console.log(`Signed in as ${user.email}.`);
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
program.command("logout").description("Remove the local CLI session").action(async () => {
|
|
423
|
+
const state = await loadAuthState();
|
|
424
|
+
if (!state) {
|
|
425
|
+
console.log("Already signed out.");
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
await clearAuthState();
|
|
429
|
+
console.log("Removed the local CLI session.");
|
|
430
|
+
});
|
|
431
|
+
addBaseUrlOption(
|
|
432
|
+
program.command("status").description("Show the current authenticated CLI user").action(async (options) => {
|
|
433
|
+
const state = await requireAuthState();
|
|
434
|
+
const configState = await loadConfigState();
|
|
435
|
+
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
436
|
+
const user = await getCurrentUser(baseUrl, state.accessToken);
|
|
437
|
+
console.log(`Base URL: ${baseUrl}`);
|
|
438
|
+
console.log(`Signed in as: ${user.name} <${user.email}>`);
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
addBaseUrlOption(
|
|
442
|
+
program.command("hello").description("Call the sample authenticated API endpoint").action(async (options) => {
|
|
443
|
+
const state = await requireAuthState();
|
|
444
|
+
const configState = await loadConfigState();
|
|
445
|
+
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
446
|
+
const message = await getHello(baseUrl, state.accessToken);
|
|
447
|
+
console.log(message);
|
|
448
|
+
})
|
|
449
|
+
);
|
|
450
|
+
addBaseUrlOption(
|
|
451
|
+
program.command("my-stuff").description("List tasks currently assigned to you").action(async (options) => {
|
|
452
|
+
const state = await requireAuthState();
|
|
453
|
+
const configState = await loadConfigState();
|
|
454
|
+
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
455
|
+
const tasks = await listAssignedTasks(baseUrl, state.accessToken);
|
|
456
|
+
printAssignedTasks(baseUrl, tasks);
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
addBaseUrlOption(
|
|
460
|
+
program.command("projects").description("Interact with project APIs").command("list").description("List accessible projects").action(async function() {
|
|
461
|
+
const state = await requireAuthState();
|
|
462
|
+
const configState = await loadConfigState();
|
|
463
|
+
const baseUrl = await resolveBaseUrl(
|
|
464
|
+
this.optsWithGlobals().baseUrl,
|
|
465
|
+
configState
|
|
466
|
+
);
|
|
467
|
+
const projects = await listProjects(baseUrl, state.accessToken);
|
|
468
|
+
printProjects(projects);
|
|
469
|
+
})
|
|
470
|
+
);
|
|
471
|
+
async function main() {
|
|
472
|
+
try {
|
|
473
|
+
await program.parseAsync(process.argv);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
const message = error instanceof Error ? error.message : "Unknown CLI error.";
|
|
476
|
+
console.error(message);
|
|
477
|
+
process.exitCode = 1;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
void main();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khangal.j/fireside-cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Fireside CLI",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"fireside": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup src/index.ts --format cjs --platform node --target node20 --out-dir dist --clean",
|
|
24
|
+
"prepublishOnly": "pnpm build",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"start": "node dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"commander": "^14.0.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.5.0"
|
|
33
|
+
}
|
|
34
|
+
}
|