@obsfx/trekker 1.6.0 → 1.7.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 (2) hide show
  1. package/dist/index.js +166 -235
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -952,6 +952,7 @@ function migrateHistoryTable(sqlite) {
952
952
  }
953
953
  }
954
954
  function rebuildSearchIndex() {
955
+ getDb();
955
956
  const sqlite = getSqliteInstance();
956
957
  if (!sqlite) {
957
958
  throw new Error("Database not initialized");
@@ -962,6 +963,13 @@ function rebuildSearchIndex() {
962
963
  function getSqliteInstance() {
963
964
  return sqliteInstance;
964
965
  }
966
+ function requireSqliteInstance() {
967
+ getDb();
968
+ if (!sqliteInstance) {
969
+ throw new Error("Database not initialized");
970
+ }
971
+ return sqliteInstance;
972
+ }
965
973
  function closeDb() {
966
974
  if (sqliteInstance) {
967
975
  sqliteInstance.close();
@@ -994,6 +1002,24 @@ var EPIC_STATUSES = [
994
1002
  "completed",
995
1003
  "archived"
996
1004
  ];
1005
+ var DEFAULT_PRIORITY = 2;
1006
+ var DEFAULT_TASK_STATUS = "todo";
1007
+ var DEFAULT_EPIC_STATUS = "todo";
1008
+ var PAGINATION_DEFAULTS = {
1009
+ LIST_PAGE_SIZE: 50,
1010
+ SEARCH_PAGE_SIZE: 20,
1011
+ HISTORY_PAGE_SIZE: 50,
1012
+ DEFAULT_PAGE: 1
1013
+ };
1014
+ var VALID_SORT_FIELDS = [
1015
+ "created",
1016
+ "updated",
1017
+ "title",
1018
+ "priority",
1019
+ "status"
1020
+ ];
1021
+ var LIST_ENTITY_TYPES = ["epic", "task", "subtask"];
1022
+ var SEARCH_ENTITY_TYPES = ["epic", "task", "subtask", "comment"];
997
1023
  var PREFIX_MAP = {
998
1024
  task: "TREK",
999
1025
  epic: "EPIC",
@@ -1556,6 +1582,24 @@ function error(message, details) {
1556
1582
  }
1557
1583
  }
1558
1584
  }
1585
+ function handleCommandError(err) {
1586
+ error(err instanceof Error ? err.message : String(err));
1587
+ process.exit(1);
1588
+ }
1589
+ function handleNotFound(entityType, id) {
1590
+ error(`${entityType} not found: ${id}`);
1591
+ process.exit(1);
1592
+ }
1593
+ function outputResult(data, formatter, successMessage) {
1594
+ if (isToonMode()) {
1595
+ output(data);
1596
+ } else {
1597
+ if (successMessage) {
1598
+ success(successMessage);
1599
+ }
1600
+ console.log(formatter(data));
1601
+ }
1602
+ }
1559
1603
  function info(message) {
1560
1604
  if (!toonMode) {
1561
1605
  console.log(message);
@@ -1721,8 +1765,8 @@ function createEpic(input) {
1721
1765
  projectId: project.id,
1722
1766
  title: input.title,
1723
1767
  description: input.description ?? null,
1724
- status: input.status ?? "todo",
1725
- priority: input.priority ?? 2,
1768
+ status: input.status ?? DEFAULT_EPIC_STATUS,
1769
+ priority: input.priority ?? DEFAULT_PRIORITY,
1726
1770
  createdAt: now,
1727
1771
  updatedAt: now
1728
1772
  };
@@ -1842,6 +1886,35 @@ function validateRequired(value, fieldName) {
1842
1886
  throw new Error(`${fieldName} is required`);
1843
1887
  }
1844
1888
  }
1889
+ function validatePagination(limit, page) {
1890
+ if (isNaN(limit) || limit < 1) {
1891
+ throw new Error("Invalid limit value");
1892
+ }
1893
+ if (isNaN(page) || page < 1) {
1894
+ throw new Error("Invalid page value");
1895
+ }
1896
+ }
1897
+ function validateListEntityTypes(types) {
1898
+ for (const t of types) {
1899
+ if (!LIST_ENTITY_TYPES.includes(t)) {
1900
+ throw new Error(`Invalid type: ${t}. Valid types: ${LIST_ENTITY_TYPES.join(", ")}`);
1901
+ }
1902
+ }
1903
+ }
1904
+ function validateSearchEntityTypes(types) {
1905
+ for (const t of types) {
1906
+ if (!SEARCH_ENTITY_TYPES.includes(t)) {
1907
+ throw new Error(`Invalid type: ${t}. Valid types: ${SEARCH_ENTITY_TYPES.join(", ")}`);
1908
+ }
1909
+ }
1910
+ }
1911
+ function validatePriorities(priorities) {
1912
+ for (const p of priorities) {
1913
+ if (isNaN(p) || p < 0 || p > 5) {
1914
+ throw new Error(`Invalid priority: ${p}. Valid priorities: 0-5`);
1915
+ }
1916
+ }
1917
+ }
1845
1918
 
1846
1919
  // src/commands/epic.ts
1847
1920
  var epicCommand = new Command3("epic").description("Manage epics");
@@ -1854,46 +1927,28 @@ epicCommand.command("create").description("Create a new epic").requiredOption("-
1854
1927
  priority: parsePriority(options.priority),
1855
1928
  status: parseStatus(options.status, "epic")
1856
1929
  });
1857
- if (isToonMode()) {
1858
- output(epic);
1859
- } else {
1860
- success(`Epic created: ${epic.id}`);
1861
- console.log(formatEpic(epic));
1862
- }
1930
+ outputResult(epic, formatEpic, `Epic created: ${epic.id}`);
1863
1931
  } catch (err) {
1864
- error(err instanceof Error ? err.message : String(err));
1865
- process.exit(1);
1932
+ handleCommandError(err);
1866
1933
  }
1867
1934
  });
1868
1935
  epicCommand.command("list").description("List all epics").option("-s, --status <status>", "Filter by status").action((options) => {
1869
1936
  try {
1870
1937
  const status = parseStatus(options.status, "epic");
1871
1938
  const epics2 = listEpics(status);
1872
- if (isToonMode()) {
1873
- output(epics2);
1874
- } else {
1875
- console.log(formatEpicList(epics2));
1876
- }
1939
+ outputResult(epics2, formatEpicList);
1877
1940
  } catch (err) {
1878
- error(err instanceof Error ? err.message : String(err));
1879
- process.exit(1);
1941
+ handleCommandError(err);
1880
1942
  }
1881
1943
  });
1882
1944
  epicCommand.command("show <epic-id>").description("Show epic details").action((epicId) => {
1883
1945
  try {
1884
1946
  const epic = getEpic(epicId);
1885
- if (!epic) {
1886
- error(`Epic not found: ${epicId}`);
1887
- process.exit(1);
1888
- }
1889
- if (isToonMode()) {
1890
- output(epic);
1891
- } else {
1892
- console.log(formatEpic(epic));
1893
- }
1947
+ if (!epic)
1948
+ return handleNotFound("Epic", epicId);
1949
+ outputResult(epic, formatEpic);
1894
1950
  } catch (err) {
1895
- error(err instanceof Error ? err.message : String(err));
1896
- process.exit(1);
1951
+ handleCommandError(err);
1897
1952
  }
1898
1953
  });
1899
1954
  epicCommand.command("update <epic-id>").description("Update an epic").option("-t, --title <title>", "New title").option("-d, --description <description>", "New description").option("-p, --priority <priority>", "New priority (0-5)").option("-s, --status <status>", "New status").action((epicId, options) => {
@@ -1904,15 +1959,9 @@ epicCommand.command("update <epic-id>").description("Update an epic").option("-t
1904
1959
  priority: parsePriority(options.priority),
1905
1960
  status: parseStatus(options.status, "epic")
1906
1961
  });
1907
- if (isToonMode()) {
1908
- output(epic);
1909
- } else {
1910
- success(`Epic updated: ${epic.id}`);
1911
- console.log(formatEpic(epic));
1912
- }
1962
+ outputResult(epic, formatEpic, `Epic updated: ${epic.id}`);
1913
1963
  } catch (err) {
1914
- error(err instanceof Error ? err.message : String(err));
1915
- process.exit(1);
1964
+ handleCommandError(err);
1916
1965
  }
1917
1966
  });
1918
1967
  epicCommand.command("delete <epic-id>").description("Delete an epic").action((epicId) => {
@@ -1920,8 +1969,7 @@ epicCommand.command("delete <epic-id>").description("Delete an epic").action((ep
1920
1969
  deleteEpic(epicId);
1921
1970
  success(`Epic deleted: ${epicId}`);
1922
1971
  } catch (err) {
1923
- error(err instanceof Error ? err.message : String(err));
1924
- process.exit(1);
1972
+ handleCommandError(err);
1925
1973
  }
1926
1974
  });
1927
1975
  epicCommand.command("complete <epic-id>").description("Complete an epic and archive all its tasks and subtasks").action((epicId) => {
@@ -1934,8 +1982,7 @@ epicCommand.command("complete <epic-id>").description("Complete an epic and arch
1934
1982
  console.log(`Archived ${result.archived.tasks} task(s) and ${result.archived.subtasks} subtask(s)`);
1935
1983
  }
1936
1984
  } catch (err) {
1937
- error(err instanceof Error ? err.message : String(err));
1938
- process.exit(1);
1985
+ handleCommandError(err);
1939
1986
  }
1940
1987
  });
1941
1988
 
@@ -1971,8 +2018,8 @@ function createTask(input) {
1971
2018
  parentTaskId: input.parentTaskId ?? null,
1972
2019
  title: input.title,
1973
2020
  description: input.description ?? null,
1974
- priority: input.priority ?? 2,
1975
- status: input.status ?? "todo",
2021
+ priority: input.priority ?? DEFAULT_PRIORITY,
2022
+ status: input.status ?? DEFAULT_TASK_STATUS,
1976
2023
  tags: input.tags ?? null,
1977
2024
  createdAt: now,
1978
2025
  updatedAt: now
@@ -2060,15 +2107,9 @@ taskCommand.command("create").description("Create a new task").requiredOption("-
2060
2107
  tags: options.tags,
2061
2108
  epicId: options.epic
2062
2109
  });
2063
- if (isToonMode()) {
2064
- output(task);
2065
- } else {
2066
- success(`Task created: ${task.id}`);
2067
- console.log(formatTask(task));
2068
- }
2110
+ outputResult(task, formatTask, `Task created: ${task.id}`);
2069
2111
  } catch (err) {
2070
- error(err instanceof Error ? err.message : String(err));
2071
- process.exit(1);
2112
+ handleCommandError(err);
2072
2113
  }
2073
2114
  });
2074
2115
  taskCommand.command("list").description("List all tasks").option("-s, --status <status>", "Filter by status").option("-e, --epic <epic-id>", "Filter by epic").action((options) => {
@@ -2079,31 +2120,19 @@ taskCommand.command("list").description("List all tasks").option("-s, --status <
2079
2120
  epicId: options.epic,
2080
2121
  parentTaskId: null
2081
2122
  });
2082
- if (isToonMode()) {
2083
- output(tasks2);
2084
- } else {
2085
- console.log(formatTaskList(tasks2));
2086
- }
2123
+ outputResult(tasks2, formatTaskList);
2087
2124
  } catch (err) {
2088
- error(err instanceof Error ? err.message : String(err));
2089
- process.exit(1);
2125
+ handleCommandError(err);
2090
2126
  }
2091
2127
  });
2092
2128
  taskCommand.command("show <task-id>").description("Show task details").action((taskId) => {
2093
2129
  try {
2094
2130
  const task = getTask(taskId);
2095
- if (!task) {
2096
- error(`Task not found: ${taskId}`);
2097
- process.exit(1);
2098
- }
2099
- if (isToonMode()) {
2100
- output(task);
2101
- } else {
2102
- console.log(formatTask(task));
2103
- }
2131
+ if (!task)
2132
+ return handleNotFound("Task", taskId);
2133
+ outputResult(task, formatTask);
2104
2134
  } catch (err) {
2105
- error(err instanceof Error ? err.message : String(err));
2106
- process.exit(1);
2135
+ handleCommandError(err);
2107
2136
  }
2108
2137
  });
2109
2138
  taskCommand.command("update <task-id>").description("Update a task").option("-t, --title <title>", "New title").option("-d, --description <description>", "New description").option("-p, --priority <priority>", "New priority (0-5)").option("-s, --status <status>", "New status").option("--tags <tags>", "New tags (comma-separated)").option("-e, --epic <epic-id>", "New epic ID").option("--no-epic", "Remove from epic").action((taskId, options) => {
@@ -2115,9 +2144,8 @@ taskCommand.command("update <task-id>").description("Update a task").option("-t,
2115
2144
  updateInput.description = options.description;
2116
2145
  if (options.priority !== undefined)
2117
2146
  updateInput.priority = parsePriority(options.priority);
2118
- if (options.status !== undefined) {
2147
+ if (options.status !== undefined)
2119
2148
  updateInput.status = parseStatus(options.status, "task");
2120
- }
2121
2149
  if (options.tags !== undefined)
2122
2150
  updateInput.tags = options.tags;
2123
2151
  if (options.epic === false) {
@@ -2126,15 +2154,9 @@ taskCommand.command("update <task-id>").description("Update a task").option("-t,
2126
2154
  updateInput.epicId = options.epic;
2127
2155
  }
2128
2156
  const task = updateTask(taskId, updateInput);
2129
- if (isToonMode()) {
2130
- output(task);
2131
- } else {
2132
- success(`Task updated: ${task.id}`);
2133
- console.log(formatTask(task));
2134
- }
2157
+ outputResult(task, formatTask, `Task updated: ${task.id}`);
2135
2158
  } catch (err) {
2136
- error(err instanceof Error ? err.message : String(err));
2137
- process.exit(1);
2159
+ handleCommandError(err);
2138
2160
  }
2139
2161
  });
2140
2162
  taskCommand.command("delete <task-id>").description("Delete a task").action((taskId) => {
@@ -2142,8 +2164,7 @@ taskCommand.command("delete <task-id>").description("Delete a task").action((tas
2142
2164
  deleteTask(taskId);
2143
2165
  success(`Task deleted: ${taskId}`);
2144
2166
  } catch (err) {
2145
- error(err instanceof Error ? err.message : String(err));
2146
- process.exit(1);
2167
+ handleCommandError(err);
2147
2168
  }
2148
2169
  });
2149
2170
 
@@ -2154,10 +2175,8 @@ subtaskCommand.command("create <parent-task-id>").description("Create a new subt
2154
2175
  try {
2155
2176
  validateRequired(options.title, "Title");
2156
2177
  const parent = getTask(parentTaskId);
2157
- if (!parent) {
2158
- error(`Parent task not found: ${parentTaskId}`);
2159
- process.exit(1);
2160
- }
2178
+ if (!parent)
2179
+ return handleNotFound("Parent task", parentTaskId);
2161
2180
  const subtask = createTask({
2162
2181
  title: options.title,
2163
2182
  description: options.description,
@@ -2166,24 +2185,16 @@ subtaskCommand.command("create <parent-task-id>").description("Create a new subt
2166
2185
  parentTaskId,
2167
2186
  epicId: parent.epicId ?? undefined
2168
2187
  });
2169
- if (isToonMode()) {
2170
- output(subtask);
2171
- } else {
2172
- success(`Subtask created: ${subtask.id}`);
2173
- console.log(formatTask(subtask));
2174
- }
2188
+ outputResult(subtask, formatTask, `Subtask created: ${subtask.id}`);
2175
2189
  } catch (err) {
2176
- error(err instanceof Error ? err.message : String(err));
2177
- process.exit(1);
2190
+ handleCommandError(err);
2178
2191
  }
2179
2192
  });
2180
2193
  subtaskCommand.command("list <parent-task-id>").description("List all subtasks of a task").action((parentTaskId) => {
2181
2194
  try {
2182
2195
  const parent = getTask(parentTaskId);
2183
- if (!parent) {
2184
- error(`Parent task not found: ${parentTaskId}`);
2185
- process.exit(1);
2186
- }
2196
+ if (!parent)
2197
+ return handleNotFound("Parent task", parentTaskId);
2187
2198
  const subtasks = listSubtasks(parentTaskId);
2188
2199
  if (isToonMode()) {
2189
2200
  output(subtasks);
@@ -2196,17 +2207,14 @@ subtaskCommand.command("list <parent-task-id>").description("List all subtasks o
2196
2207
  }
2197
2208
  }
2198
2209
  } catch (err) {
2199
- error(err instanceof Error ? err.message : String(err));
2200
- process.exit(1);
2210
+ handleCommandError(err);
2201
2211
  }
2202
2212
  });
2203
2213
  subtaskCommand.command("update <subtask-id>").description("Update a subtask").option("-t, --title <title>", "New title").option("-d, --description <description>", "New description").option("-p, --priority <priority>", "New priority (0-5)").option("-s, --status <status>", "New status").action((subtaskId, options) => {
2204
2214
  try {
2205
2215
  const subtask = getTask(subtaskId);
2206
- if (!subtask) {
2207
- error(`Subtask not found: ${subtaskId}`);
2208
- process.exit(1);
2209
- }
2216
+ if (!subtask)
2217
+ return handleNotFound("Subtask", subtaskId);
2210
2218
  if (!subtask.parentTaskId) {
2211
2219
  error(`${subtaskId} is not a subtask. Use 'trekker task update' instead.`);
2212
2220
  process.exit(1);
@@ -2218,28 +2226,19 @@ subtaskCommand.command("update <subtask-id>").description("Update a subtask").op
2218
2226
  updateInput.description = options.description;
2219
2227
  if (options.priority !== undefined)
2220
2228
  updateInput.priority = parsePriority(options.priority);
2221
- if (options.status !== undefined) {
2229
+ if (options.status !== undefined)
2222
2230
  updateInput.status = parseStatus(options.status, "task");
2223
- }
2224
2231
  const updated = updateTask(subtaskId, updateInput);
2225
- if (isToonMode()) {
2226
- output(updated);
2227
- } else {
2228
- success(`Subtask updated: ${updated.id}`);
2229
- console.log(formatTask(updated));
2230
- }
2232
+ outputResult(updated, formatTask, `Subtask updated: ${updated.id}`);
2231
2233
  } catch (err) {
2232
- error(err instanceof Error ? err.message : String(err));
2233
- process.exit(1);
2234
+ handleCommandError(err);
2234
2235
  }
2235
2236
  });
2236
2237
  subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").action((subtaskId) => {
2237
2238
  try {
2238
2239
  const subtask = getTask(subtaskId);
2239
- if (!subtask) {
2240
- error(`Subtask not found: ${subtaskId}`);
2241
- process.exit(1);
2242
- }
2240
+ if (!subtask)
2241
+ return handleNotFound("Subtask", subtaskId);
2243
2242
  if (!subtask.parentTaskId) {
2244
2243
  error(`${subtaskId} is not a subtask. Use 'trekker task delete' instead.`);
2245
2244
  process.exit(1);
@@ -2247,8 +2246,7 @@ subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").ac
2247
2246
  deleteTask(subtaskId);
2248
2247
  success(`Subtask deleted: ${subtaskId}`);
2249
2248
  } catch (err) {
2250
- error(err instanceof Error ? err.message : String(err));
2251
- process.exit(1);
2249
+ handleCommandError(err);
2252
2250
  }
2253
2251
  });
2254
2252
 
@@ -2321,15 +2319,9 @@ commentCommand.command("add <task-id>").description("Add a comment to a task").r
2321
2319
  author: options.author,
2322
2320
  content: options.content
2323
2321
  });
2324
- if (isToonMode()) {
2325
- output(comment);
2326
- } else {
2327
- success(`Comment added: ${comment.id}`);
2328
- console.log(formatComment(comment));
2329
- }
2322
+ outputResult(comment, formatComment, `Comment added: ${comment.id}`);
2330
2323
  } catch (err) {
2331
- error(err instanceof Error ? err.message : String(err));
2332
- process.exit(1);
2324
+ handleCommandError(err);
2333
2325
  }
2334
2326
  });
2335
2327
  commentCommand.command("list <task-id>").description("List all comments on a task").action((taskId) => {
@@ -2346,8 +2338,7 @@ commentCommand.command("list <task-id>").description("List all comments on a tas
2346
2338
  }
2347
2339
  }
2348
2340
  } catch (err) {
2349
- error(err instanceof Error ? err.message : String(err));
2350
- process.exit(1);
2341
+ handleCommandError(err);
2351
2342
  }
2352
2343
  });
2353
2344
  commentCommand.command("update <comment-id>").description("Update a comment").requiredOption("-c, --content <content>", "New comment content").action((commentId, options) => {
@@ -2356,15 +2347,9 @@ commentCommand.command("update <comment-id>").description("Update a comment").re
2356
2347
  const comment = updateComment(commentId, {
2357
2348
  content: options.content
2358
2349
  });
2359
- if (isToonMode()) {
2360
- output(comment);
2361
- } else {
2362
- success(`Comment updated: ${comment.id}`);
2363
- console.log(formatComment(comment));
2364
- }
2350
+ outputResult(comment, formatComment, `Comment updated: ${comment.id}`);
2365
2351
  } catch (err) {
2366
- error(err instanceof Error ? err.message : String(err));
2367
- process.exit(1);
2352
+ handleCommandError(err);
2368
2353
  }
2369
2354
  });
2370
2355
  commentCommand.command("delete <comment-id>").description("Delete a comment").action((commentId) => {
@@ -2372,8 +2357,7 @@ commentCommand.command("delete <comment-id>").description("Delete a comment").ac
2372
2357
  deleteComment(commentId);
2373
2358
  success(`Comment deleted: ${commentId}`);
2374
2359
  } catch (err) {
2375
- error(err instanceof Error ? err.message : String(err));
2376
- process.exit(1);
2360
+ handleCommandError(err);
2377
2361
  }
2378
2362
  });
2379
2363
 
@@ -2467,8 +2451,7 @@ depCommand.command("add <task-id> <depends-on-id>").description("Add a dependenc
2467
2451
  success(`Dependency added: ${taskId} \u2192 depends on ${dependsOnId}`);
2468
2452
  }
2469
2453
  } catch (err) {
2470
- error(err instanceof Error ? err.message : String(err));
2471
- process.exit(1);
2454
+ handleCommandError(err);
2472
2455
  }
2473
2456
  });
2474
2457
  depCommand.command("remove <task-id> <depends-on-id>").description("Remove a dependency").action((taskId, dependsOnId) => {
@@ -2476,8 +2459,7 @@ depCommand.command("remove <task-id> <depends-on-id>").description("Remove a dep
2476
2459
  removeDependency(taskId, dependsOnId);
2477
2460
  success(`Dependency removed: ${taskId} \u2192 ${dependsOnId}`);
2478
2461
  } catch (err) {
2479
- error(err instanceof Error ? err.message : String(err));
2480
- process.exit(1);
2462
+ handleCommandError(err);
2481
2463
  }
2482
2464
  });
2483
2465
  depCommand.command("list <task-id>").description("List dependencies for a task").action((taskId) => {
@@ -2495,8 +2477,7 @@ Blocks:`);
2495
2477
  console.log(formatDependencyList(blocks, "blocks"));
2496
2478
  }
2497
2479
  } catch (err) {
2498
- error(err instanceof Error ? err.message : String(err));
2499
- process.exit(1);
2480
+ handleCommandError(err);
2500
2481
  }
2501
2482
  });
2502
2483
 
@@ -2910,13 +2891,9 @@ import { Command as Command10 } from "commander";
2910
2891
 
2911
2892
  // src/services/search.ts
2912
2893
  function search(query, options) {
2913
- getDb();
2914
- const sqlite = getSqliteInstance();
2915
- if (!sqlite) {
2916
- throw new Error("Database not initialized");
2917
- }
2918
- const limit = options?.limit ?? 20;
2919
- const page = options?.page ?? 1;
2894
+ const sqlite = requireSqliteInstance();
2895
+ const limit = options?.limit ?? PAGINATION_DEFAULTS.SEARCH_PAGE_SIZE;
2896
+ const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
2920
2897
  const offset = (page - 1) * limit;
2921
2898
  const conditions = ["search_index MATCH ?"];
2922
2899
  const params = [query];
@@ -2975,37 +2952,21 @@ var searchCommand = new Command10("search").description("Search across epics, ta
2975
2952
  if (options.rebuildIndex) {
2976
2953
  rebuildSearchIndex();
2977
2954
  }
2978
- const types = options.type ? options.type.split(",").map((t) => t.trim()) : undefined;
2979
2955
  const limit = parseInt(options.limit, 10);
2980
2956
  const page = parseInt(options.page, 10);
2981
- if (isNaN(limit) || limit < 1) {
2982
- throw new Error("Invalid limit value");
2983
- }
2984
- if (isNaN(page) || page < 1) {
2985
- throw new Error("Invalid page value");
2986
- }
2987
- const validTypes = ["epic", "task", "subtask", "comment"];
2988
- if (types) {
2989
- for (const t of types) {
2990
- if (!validTypes.includes(t)) {
2991
- throw new Error(`Invalid type: ${t}. Valid types: ${validTypes.join(", ")}`);
2992
- }
2993
- }
2994
- }
2957
+ validatePagination(limit, page);
2958
+ const types = options.type ? options.type.split(",").map((t) => t.trim()) : undefined;
2959
+ if (types)
2960
+ validateSearchEntityTypes(types);
2995
2961
  const result = search(query, {
2996
2962
  types,
2997
2963
  status: options.status,
2998
2964
  limit,
2999
2965
  page
3000
2966
  });
3001
- if (isToonMode()) {
3002
- output(result);
3003
- } else {
3004
- console.log(formatSearchResults(result));
3005
- }
2967
+ outputResult(result, formatSearchResults);
3006
2968
  } catch (err) {
3007
- error(err instanceof Error ? err.message : String(err));
3008
- process.exit(1);
2969
+ handleCommandError(err);
3009
2970
  }
3010
2971
  });
3011
2972
  function formatSearchResults(result) {
@@ -3044,13 +3005,9 @@ import { Command as Command11 } from "commander";
3044
3005
 
3045
3006
  // src/services/history.ts
3046
3007
  function getHistory(options) {
3047
- getDb();
3048
- const sqlite = getSqliteInstance();
3049
- if (!sqlite) {
3050
- throw new Error("Database not initialized");
3051
- }
3052
- const limit = options?.limit ?? 50;
3053
- const page = options?.page ?? 1;
3008
+ const sqlite = requireSqliteInstance();
3009
+ const limit = options?.limit ?? PAGINATION_DEFAULTS.HISTORY_PAGE_SIZE;
3010
+ const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
3054
3011
  const offset = (page - 1) * limit;
3055
3012
  const conditions = [];
3056
3013
  const params = [];
@@ -3244,15 +3201,10 @@ var import_customParseFormat2 = __toESM(require_customParseFormat(), 1);
3244
3201
  import { Command as Command12 } from "commander";
3245
3202
 
3246
3203
  // src/services/list.ts
3247
- var VALID_SORT_FIELDS = ["created", "updated", "title", "priority", "status"];
3248
3204
  function listAll(options) {
3249
- getDb();
3250
- const sqlite = getSqliteInstance();
3251
- if (!sqlite) {
3252
- throw new Error("Database not initialized");
3253
- }
3254
- const limit = options?.limit ?? 50;
3255
- const page = options?.page ?? 1;
3205
+ const sqlite = requireSqliteInstance();
3206
+ const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
3207
+ const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
3256
3208
  const offset = (page - 1) * limit;
3257
3209
  const conditions = [];
3258
3210
  const params = [];
@@ -3339,52 +3291,29 @@ function parseSort(sortStr) {
3339
3291
  import_dayjs2.default.extend(import_customParseFormat2.default);
3340
3292
  var listCommand = new Command12("list").description("List all epics, tasks, and subtasks").option("--type <types>", "Filter by type: epic,task,subtask (comma-separated)").option("--status <statuses>", "Filter by status (comma-separated)").option("--priority <levels>", "Filter by priority: 0-5 (comma-separated)").option("--since <date>", "Created after date (YYYY-MM-DD)").option("--until <date>", "Created before date (YYYY-MM-DD)").option("--sort <fields>", "Sort by fields (field:direction, comma-separated)", "created:desc").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
3341
3293
  try {
3294
+ const limit = parseInt(options.limit, 10);
3295
+ const page = parseInt(options.page, 10);
3296
+ validatePagination(limit, page);
3342
3297
  const types = options.type ? options.type.split(",").map((t) => t.trim()) : undefined;
3298
+ if (types)
3299
+ validateListEntityTypes(types);
3343
3300
  const statuses = options.status ? options.status.split(",").map((s) => s.trim()) : undefined;
3344
3301
  const priorities = options.priority ? options.priority.split(",").map((p) => parseInt(p.trim(), 10)) : undefined;
3345
- const limit = parseInt(options.limit, 10);
3346
- const page = parseInt(options.page, 10);
3347
- if (isNaN(limit) || limit < 1) {
3348
- throw new Error("Invalid limit value");
3349
- }
3350
- if (isNaN(page) || page < 1) {
3351
- throw new Error("Invalid page value");
3352
- }
3353
- const validTypes = ["epic", "task", "subtask"];
3354
- if (types) {
3355
- for (const t of types) {
3356
- if (!validTypes.includes(t)) {
3357
- throw new Error(`Invalid type: ${t}. Valid types: ${validTypes.join(", ")}`);
3358
- }
3359
- }
3360
- }
3361
- if (priorities) {
3362
- for (const p of priorities) {
3363
- if (isNaN(p) || p < 0 || p > 5) {
3364
- throw new Error(`Invalid priority: ${p}. Valid priorities: 0-5`);
3365
- }
3366
- }
3367
- }
3302
+ if (priorities)
3303
+ validatePriorities(priorities);
3368
3304
  let sort;
3369
3305
  try {
3370
3306
  sort = parseSort(options.sort);
3371
3307
  } catch (err) {
3372
3308
  throw new Error(`Invalid sort: ${err instanceof Error ? err.message : String(err)}`);
3373
3309
  }
3374
- let since;
3375
- let until;
3376
- if (options.since) {
3377
- since = parseDate2(options.since);
3378
- if (!since) {
3379
- throw new Error("Invalid since date. Use YYYY-MM-DD format.");
3380
- }
3310
+ const since = parseDate2(options.since);
3311
+ if (options.since && !since) {
3312
+ throw new Error("Invalid since date. Use YYYY-MM-DD format.");
3381
3313
  }
3382
- if (options.until) {
3383
- const parsedUntil = import_dayjs2.default(options.until, "YYYY-MM-DD", true);
3384
- if (!parsedUntil.isValid()) {
3385
- throw new Error("Invalid until date. Use YYYY-MM-DD format.");
3386
- }
3387
- until = parsedUntil.endOf("day").toDate();
3314
+ const until = parseUntilDate(options.until);
3315
+ if (options.until && !until) {
3316
+ throw new Error("Invalid until date. Use YYYY-MM-DD format.");
3388
3317
  }
3389
3318
  const result = listAll({
3390
3319
  types,
@@ -3396,22 +3325,22 @@ var listCommand = new Command12("list").description("List all epics, tasks, and
3396
3325
  limit,
3397
3326
  page
3398
3327
  });
3399
- if (isToonMode()) {
3400
- output(result);
3401
- } else {
3402
- console.log(formatListResults(result));
3403
- }
3328
+ outputResult(result, formatListResults);
3404
3329
  } catch (err) {
3405
- error(err instanceof Error ? err.message : String(err));
3406
- process.exit(1);
3330
+ handleCommandError(err);
3407
3331
  }
3408
3332
  });
3409
3333
  function parseDate2(dateStr) {
3334
+ if (!dateStr)
3335
+ return;
3410
3336
  const parsed = import_dayjs2.default(dateStr, "YYYY-MM-DD", true);
3411
- if (!parsed.isValid()) {
3337
+ return parsed.isValid() ? parsed.toDate() : undefined;
3338
+ }
3339
+ function parseUntilDate(dateStr) {
3340
+ if (!dateStr)
3412
3341
  return;
3413
- }
3414
- return parsed.toDate();
3342
+ const parsed = import_dayjs2.default(dateStr, "YYYY-MM-DD", true);
3343
+ return parsed.isValid() ? parsed.endOf("day").toDate() : undefined;
3415
3344
  }
3416
3345
  function formatListResults(result) {
3417
3346
  const lines = [];
@@ -3443,7 +3372,7 @@ function formatItem(item) {
3443
3372
  // package.json
3444
3373
  var package_default = {
3445
3374
  name: "@obsfx/trekker",
3446
- version: "1.6.0",
3375
+ version: "1.7.0",
3447
3376
  description: "A CLI-based issue tracker built for AI coding agents. Stores tasks, epics, and dependencies in a local SQLite database.",
3448
3377
  type: "module",
3449
3378
  main: "dist/index.js",
@@ -3457,6 +3386,8 @@ var package_default = {
3457
3386
  scripts: {
3458
3387
  build: "bun build src/index.ts --outdir dist --target bun --external commander --external drizzle-orm",
3459
3388
  dev: "bun run src/index.ts",
3389
+ test: "bun test",
3390
+ "test:watch": "bun test --watch",
3460
3391
  "db:generate": "drizzle-kit generate",
3461
3392
  "db:migrate": "drizzle-kit migrate",
3462
3393
  "release:patch": "npm version patch && npm run build && npm publish --access public",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obsfx/trekker",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "A CLI-based issue tracker built for AI coding agents. Stores tasks, epics, and dependencies in a local SQLite database.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,6 +14,8 @@
14
14
  "scripts": {
15
15
  "build": "bun build src/index.ts --outdir dist --target bun --external commander --external drizzle-orm",
16
16
  "dev": "bun run src/index.ts",
17
+ "test": "bun test",
18
+ "test:watch": "bun test --watch",
17
19
  "db:generate": "drizzle-kit generate",
18
20
  "db:migrate": "drizzle-kit migrate",
19
21
  "release:patch": "npm version patch && npm run build && npm publish --access public",