@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.
Files changed (2) hide show
  1. package/dist/index.js +157 -22
  2. 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 import_promises2 = require("fs/promises");
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, import_promises2.readFile)(filePath, "utf8");
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 allEntries = flattenTaskEntries(
942
- await loadProjectBoardsResults(baseUrl, accessToken, projectSelector)
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
- const trimmed = taskId.trim();
945
- let matches = allEntries.filter((taskEntry) => taskEntry.task.id === trimmed);
946
- if (!matches.length && trimmed.length >= 4) {
947
- matches = allEntries.filter(
948
- (taskEntry) => taskEntry.task.id.startsWith(trimmed)
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.6").configureOutput({
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 tasks = await listAssignedTasks(baseUrl, state.accessToken);
1170
- const filteredTasks = filterAssignedTasksByProject(
1171
- tasks,
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("List accessible tasks").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").action(
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, options.project)
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
- options.project
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").requiredOption("-p, --project <project>", "Project id or title").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(
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
- options.project
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").requiredOption("-p, --project <project>", "Project id or title").requiredOption(
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
- options.project
1546
+ projectSelector
1412
1547
  );
1413
1548
  const board = resolveBoard(
1414
1549
  projectBoardsResult.boardsData.boards,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khangal.j/fireside-cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Fireside CLI",
5
5
  "license": "MIT",
6
6
  "private": false,