@khangal.j/fireside-cli 0.0.6 → 0.0.7
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/dist/index.js +157 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
27
|
+
var import_promises3 = require("fs/promises");
|
|
28
28
|
var import_commander = require("commander");
|
|
29
29
|
var import_picocolors = __toESM(require("picocolors"));
|
|
30
30
|
|
|
@@ -487,6 +487,51 @@ function openBrowser(url) {
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
// src/lib/project-config.ts
|
|
491
|
+
var import_promises2 = require("fs/promises");
|
|
492
|
+
var import_node_path2 = require("path");
|
|
493
|
+
var PROJECT_CONFIG_FILENAME = ".fireside.json";
|
|
494
|
+
async function loadProjectConfig(startDir = process.cwd()) {
|
|
495
|
+
const { root } = (0, import_node_path2.parse)(startDir);
|
|
496
|
+
let dir = startDir;
|
|
497
|
+
for (; ; ) {
|
|
498
|
+
const candidate = (0, import_node_path2.join)(dir, PROJECT_CONFIG_FILENAME);
|
|
499
|
+
try {
|
|
500
|
+
const raw = await (0, import_promises2.readFile)(candidate, "utf8");
|
|
501
|
+
return { config: JSON.parse(raw), path: candidate };
|
|
502
|
+
} catch (error) {
|
|
503
|
+
const code = error.code;
|
|
504
|
+
if (error instanceof SyntaxError) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`Invalid ${PROJECT_CONFIG_FILENAME} at ${candidate}: ${error.message}`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if (code !== "ENOENT") {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (dir === root) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const parent = (0, import_node_path2.dirname)(dir);
|
|
517
|
+
if (parent === dir) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
dir = parent;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async function loadDefaultProject(startDir) {
|
|
524
|
+
const loaded = await loadProjectConfig(startDir);
|
|
525
|
+
const project = loaded?.config.project;
|
|
526
|
+
return typeof project === "string" && project.trim() ? project.trim() : void 0;
|
|
527
|
+
}
|
|
528
|
+
async function writeProjectConfig(dir, config) {
|
|
529
|
+
const path = (0, import_node_path2.join)(dir, PROJECT_CONFIG_FILENAME);
|
|
530
|
+
await (0, import_promises2.writeFile)(path, `${JSON.stringify(config, null, 2)}
|
|
531
|
+
`);
|
|
532
|
+
return path;
|
|
533
|
+
}
|
|
534
|
+
|
|
490
535
|
// src/index.ts
|
|
491
536
|
function printInfo(message) {
|
|
492
537
|
console.log(`${import_picocolors.default.cyan("[i]")} ${message}`);
|
|
@@ -843,7 +888,7 @@ function printTaskHandoff(taskEntry, taskHandoff) {
|
|
|
843
888
|
}
|
|
844
889
|
async function readTextFile(filePath, label) {
|
|
845
890
|
try {
|
|
846
|
-
return await (0,
|
|
891
|
+
return await (0, import_promises3.readFile)(filePath, "utf8");
|
|
847
892
|
} catch (error) {
|
|
848
893
|
const message = error instanceof Error ? error.message : `Unable to read ${label} file.`;
|
|
849
894
|
throw new Error(`Failed to read ${label} file: ${message}`);
|
|
@@ -927,6 +972,23 @@ async function loadCliContextWithCurrentUser(baseUrl) {
|
|
|
927
972
|
user: await getCurrentUser(cliContext.baseUrl, cliContext.accessToken)
|
|
928
973
|
};
|
|
929
974
|
}
|
|
975
|
+
async function resolveProjectSelector(explicit, allProjects) {
|
|
976
|
+
if (allProjects) {
|
|
977
|
+
return void 0;
|
|
978
|
+
}
|
|
979
|
+
if (explicit) {
|
|
980
|
+
return explicit;
|
|
981
|
+
}
|
|
982
|
+
return loadDefaultProject();
|
|
983
|
+
}
|
|
984
|
+
function findTaskMatches(entries, taskId) {
|
|
985
|
+
const trimmed = taskId.trim();
|
|
986
|
+
const exact = entries.filter((entry) => entry.task.id === trimmed);
|
|
987
|
+
if (exact.length || trimmed.length < 4) {
|
|
988
|
+
return exact;
|
|
989
|
+
}
|
|
990
|
+
return entries.filter((entry) => entry.task.id.startsWith(trimmed));
|
|
991
|
+
}
|
|
930
992
|
async function loadProjectBoardsResults(baseUrl, accessToken, projectSelector) {
|
|
931
993
|
const projects = await listProjects(baseUrl, accessToken);
|
|
932
994
|
const selectedProjects = projectSelector ? [resolveByIdOrTitle(projects, projectSelector, "project")] : projects;
|
|
@@ -938,14 +1000,19 @@ async function loadProjectBoardsResults(baseUrl, accessToken, projectSelector) {
|
|
|
938
1000
|
);
|
|
939
1001
|
}
|
|
940
1002
|
async function resolveTaskEntry(baseUrl, accessToken, taskId, projectSelector) {
|
|
941
|
-
const
|
|
942
|
-
|
|
1003
|
+
const scoped = await resolveProjectSelector(projectSelector, false);
|
|
1004
|
+
let matches = findTaskMatches(
|
|
1005
|
+
flattenTaskEntries(
|
|
1006
|
+
await loadProjectBoardsResults(baseUrl, accessToken, scoped)
|
|
1007
|
+
),
|
|
1008
|
+
taskId
|
|
943
1009
|
);
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1010
|
+
if (!matches.length && scoped && !projectSelector) {
|
|
1011
|
+
matches = findTaskMatches(
|
|
1012
|
+
flattenTaskEntries(
|
|
1013
|
+
await loadProjectBoardsResults(baseUrl, accessToken, void 0)
|
|
1014
|
+
),
|
|
1015
|
+
taskId
|
|
949
1016
|
);
|
|
950
1017
|
}
|
|
951
1018
|
if (!matches.length) {
|
|
@@ -1092,7 +1159,7 @@ function printAssignedTasks(tasks, projectFilter) {
|
|
|
1092
1159
|
}
|
|
1093
1160
|
}
|
|
1094
1161
|
var program = new import_commander.Command();
|
|
1095
|
-
program.name("fireside").description("Fireside CLI").version("0.0.
|
|
1162
|
+
program.name("fireside").description("Fireside CLI").version("0.0.7").configureOutput({
|
|
1096
1163
|
outputError: (message, write) => write(import_picocolors.default.red(message))
|
|
1097
1164
|
}).showHelpAfterError();
|
|
1098
1165
|
addBaseUrlOption(
|
|
@@ -1161,16 +1228,17 @@ addBaseUrlOption(
|
|
|
1161
1228
|
})
|
|
1162
1229
|
);
|
|
1163
1230
|
addBaseUrlOption(
|
|
1164
|
-
program.command("my-stuff").description("List tasks currently assigned to you").option("--json", "Print assigned tasks as compact JSON").option("--raw", "With --json, print the full unabridged shape (larger)").option("-p, --project <project>", "Filter by project id or title").action(
|
|
1231
|
+
program.command("my-stuff").description("List tasks currently assigned to you").option("--json", "Print assigned tasks as compact JSON").option("--raw", "With --json, print the full unabridged shape (larger)").option("-p, --project <project>", "Filter by project id or title").option("--all-projects", "Ignore the default project; span all projects").action(
|
|
1165
1232
|
async (options) => {
|
|
1166
1233
|
const state = await requireAuthState();
|
|
1167
1234
|
const configState = await loadConfigState();
|
|
1168
1235
|
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
options.project
|
|
1236
|
+
const projectFilter = await resolveProjectSelector(
|
|
1237
|
+
options.project,
|
|
1238
|
+
options.allProjects
|
|
1173
1239
|
);
|
|
1240
|
+
const tasks = await listAssignedTasks(baseUrl, state.accessToken);
|
|
1241
|
+
const filteredTasks = filterAssignedTasksByProject(tasks, projectFilter);
|
|
1174
1242
|
if (options.json) {
|
|
1175
1243
|
if (options.raw) {
|
|
1176
1244
|
console.log(JSON.stringify(filteredTasks, null, 2));
|
|
@@ -1195,13 +1263,50 @@ addBaseUrlOption(
|
|
|
1195
1263
|
printProjects(projects);
|
|
1196
1264
|
})
|
|
1197
1265
|
);
|
|
1266
|
+
var configCommand = program.command("config").description("Manage CLI configuration");
|
|
1267
|
+
addBaseUrlOption(
|
|
1268
|
+
configCommand.command("show").description("Show resolved defaults (base URL, default project)").action(async (options) => {
|
|
1269
|
+
const configState = await loadConfigState();
|
|
1270
|
+
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
1271
|
+
const loaded = await loadProjectConfig();
|
|
1272
|
+
printKeyValue("Base URL", baseUrl);
|
|
1273
|
+
if (loaded?.config.project) {
|
|
1274
|
+
printKeyValue("Default project", loaded.config.project);
|
|
1275
|
+
printKeyValue(" from", loaded.path);
|
|
1276
|
+
} else {
|
|
1277
|
+
printKeyValue("Default project", import_picocolors.default.dim("Not set"));
|
|
1278
|
+
}
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1281
|
+
addBaseUrlOption(
|
|
1282
|
+
configCommand.command("use-project <project>").description(
|
|
1283
|
+
`Set the default project for this directory (writes ${PROJECT_CONFIG_FILENAME})`
|
|
1284
|
+
).action(async (project, options) => {
|
|
1285
|
+
const { accessToken, baseUrl } = await loadCliContext(options.baseUrl);
|
|
1286
|
+
const projects = await listProjects(baseUrl, accessToken);
|
|
1287
|
+
const resolved = resolveByIdOrTitle(projects, project, "project");
|
|
1288
|
+
const path = await writeProjectConfig(process.cwd(), {
|
|
1289
|
+
project: resolved.title
|
|
1290
|
+
});
|
|
1291
|
+
printSuccess(`Default project set to ${import_picocolors.default.cyan(resolved.title)}.`);
|
|
1292
|
+
printKeyValue("Wrote", path);
|
|
1293
|
+
})
|
|
1294
|
+
);
|
|
1198
1295
|
var tasksCommand = program.command("tasks").description("Interact with tasks");
|
|
1199
1296
|
addBaseUrlOption(
|
|
1200
|
-
tasksCommand.command("list").description(
|
|
1297
|
+
tasksCommand.command("list").description(
|
|
1298
|
+
"List tasks assigned to you in the default project (.fireside.json)"
|
|
1299
|
+
).option("--json", "Print tasks as compact JSON").option("--raw", "With --json, print the full unabridged shape (larger)").option("-p, --project <project>", "Filter by project id or title").option("--board <board>", "Filter by board id or title").option("--column <column>", "Filter by column id or title").option("--all", "Show everyone's tasks across all projects").option("--everyone", "Include tasks not assigned to you").option("--all-projects", "Ignore the default project; span all projects").action(
|
|
1201
1300
|
async (options) => {
|
|
1202
1301
|
const { accessToken, baseUrl } = await loadCliContext(options.baseUrl);
|
|
1302
|
+
const includeEveryone = Boolean(options.all || options.everyone);
|
|
1303
|
+
const spanAllProjects = Boolean(options.all || options.allProjects);
|
|
1304
|
+
const projectSelector = await resolveProjectSelector(
|
|
1305
|
+
options.project,
|
|
1306
|
+
spanAllProjects
|
|
1307
|
+
);
|
|
1203
1308
|
let taskEntries = flattenTaskEntries(
|
|
1204
|
-
await loadProjectBoardsResults(baseUrl, accessToken,
|
|
1309
|
+
await loadProjectBoardsResults(baseUrl, accessToken, projectSelector)
|
|
1205
1310
|
);
|
|
1206
1311
|
if (options.board) {
|
|
1207
1312
|
taskEntries = taskEntries.filter(
|
|
@@ -1213,6 +1318,14 @@ addBaseUrlOption(
|
|
|
1213
1318
|
(taskEntry) => matchIdOrTitle(taskEntry.column, options.column)
|
|
1214
1319
|
);
|
|
1215
1320
|
}
|
|
1321
|
+
if (!includeEveryone) {
|
|
1322
|
+
const currentUser = await getCurrentUser(baseUrl, accessToken);
|
|
1323
|
+
taskEntries = taskEntries.filter(
|
|
1324
|
+
(taskEntry) => taskEntry.task.assignees.some(
|
|
1325
|
+
(assignee) => assignee.id === currentUser.id
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1216
1329
|
if (options.json) {
|
|
1217
1330
|
if (options.raw) {
|
|
1218
1331
|
console.log(
|
|
@@ -1245,10 +1358,14 @@ addBaseUrlOption(
|
|
|
1245
1358
|
tasksCommand.command("columns").description("List a board's columns (id, role, title) without tasks").option("--json", "Print columns as JSON").option("-p, --project <project>", "Filter by project id or title").option("--board <board>", "Board id or title").action(
|
|
1246
1359
|
async (options) => {
|
|
1247
1360
|
const { accessToken, baseUrl } = await loadCliContext(options.baseUrl);
|
|
1361
|
+
const projectSelector = await resolveProjectSelector(
|
|
1362
|
+
options.project,
|
|
1363
|
+
options.allProjects
|
|
1364
|
+
);
|
|
1248
1365
|
const [projectBoardsResult] = await loadProjectBoardsResults(
|
|
1249
1366
|
baseUrl,
|
|
1250
1367
|
accessToken,
|
|
1251
|
-
|
|
1368
|
+
projectSelector
|
|
1252
1369
|
);
|
|
1253
1370
|
if (!projectBoardsResult) {
|
|
1254
1371
|
printWarning("No project found.");
|
|
@@ -1321,7 +1438,10 @@ addBaseUrlOption(
|
|
|
1321
1438
|
)
|
|
1322
1439
|
);
|
|
1323
1440
|
addBaseUrlOption(
|
|
1324
|
-
tasksCommand.command("create").description("Create a task").
|
|
1441
|
+
tasksCommand.command("create").description("Create a task").option(
|
|
1442
|
+
"-p, --project <project>",
|
|
1443
|
+
"Project id or title (defaults to .fireside.json)"
|
|
1444
|
+
).requiredOption("-t, --title <title>", "Task title").requiredOption("-c, --column <column>", "Column id or title").option("--board <board>", "Board id or title").option("--description <description>", "Task description").option("--description-file <path>", "Read task description from a file").option("--due-date <date>", "Due date in YYYY-MM-DD format").option(
|
|
1325
1445
|
"-a, --assignee <member>",
|
|
1326
1446
|
"Assign a member by @username, email, id, or me",
|
|
1327
1447
|
collectOptionValue,
|
|
@@ -1329,10 +1449,16 @@ addBaseUrlOption(
|
|
|
1329
1449
|
).option("--json", "Print the created task as JSON").action(
|
|
1330
1450
|
async (options) => {
|
|
1331
1451
|
const { accessToken, baseUrl, user } = await loadCliContextWithCurrentUser(options.baseUrl);
|
|
1452
|
+
const projectSelector = options.project ?? await loadDefaultProject();
|
|
1453
|
+
if (!projectSelector) {
|
|
1454
|
+
throw new Error(
|
|
1455
|
+
`No project. Pass -p <project> or add ${PROJECT_CONFIG_FILENAME} (\`fireside config use-project <project>\`).`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1332
1458
|
const [projectBoardsResult] = await loadProjectBoardsResults(
|
|
1333
1459
|
baseUrl,
|
|
1334
1460
|
accessToken,
|
|
1335
|
-
|
|
1461
|
+
projectSelector
|
|
1336
1462
|
);
|
|
1337
1463
|
const board = resolveBoard(
|
|
1338
1464
|
projectBoardsResult.boardsData.boards,
|
|
@@ -1379,7 +1505,10 @@ addBaseUrlOption(
|
|
|
1379
1505
|
)
|
|
1380
1506
|
);
|
|
1381
1507
|
addBaseUrlOption(
|
|
1382
|
-
tasksCommand.command("bulk-create").description("Create many tasks at once from a file").
|
|
1508
|
+
tasksCommand.command("bulk-create").description("Create many tasks at once from a file").option(
|
|
1509
|
+
"-p, --project <project>",
|
|
1510
|
+
"Project id or title (defaults to .fireside.json)"
|
|
1511
|
+
).requiredOption(
|
|
1383
1512
|
"--file <path>",
|
|
1384
1513
|
"Tasks file: JSON array, `{ tasks: [...] }`, or one title per line"
|
|
1385
1514
|
).option("--board <board>", "Board id or title").option(
|
|
@@ -1399,6 +1528,12 @@ addBaseUrlOption(
|
|
|
1399
1528
|
).option("--dry-run", "Resolve and preview tasks without creating them").option("--json", "Print results as JSON").action(
|
|
1400
1529
|
async (options) => {
|
|
1401
1530
|
const { accessToken, baseUrl, user } = await loadCliContextWithCurrentUser(options.baseUrl);
|
|
1531
|
+
const projectSelector = options.project ?? await loadDefaultProject();
|
|
1532
|
+
if (!projectSelector) {
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
`No project. Pass -p <project> or add ${PROJECT_CONFIG_FILENAME} (\`fireside config use-project <project>\`).`
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1402
1537
|
const rows = parseBulkTaskRows(
|
|
1403
1538
|
await readTextFile(options.file, "tasks")
|
|
1404
1539
|
);
|
|
@@ -1408,7 +1543,7 @@ addBaseUrlOption(
|
|
|
1408
1543
|
const [projectBoardsResult] = await loadProjectBoardsResults(
|
|
1409
1544
|
baseUrl,
|
|
1410
1545
|
accessToken,
|
|
1411
|
-
|
|
1546
|
+
projectSelector
|
|
1412
1547
|
);
|
|
1413
1548
|
const board = resolveBoard(
|
|
1414
1549
|
projectBoardsResult.boardsData.boards,
|