@hasna/todos 0.11.41 → 0.11.42

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/cli/index.js CHANGED
@@ -3083,6 +3083,19 @@ var init_migrations = __esm(() => {
3083
3083
  );
3084
3084
  CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at);
3085
3085
  INSERT OR IGNORE INTO _migrations (id) VALUES (54);
3086
+ `,
3087
+ `
3088
+ CREATE TABLE IF NOT EXISTS saved_search_views (
3089
+ id TEXT PRIMARY KEY,
3090
+ name TEXT NOT NULL UNIQUE,
3091
+ description TEXT,
3092
+ scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
3093
+ filters TEXT NOT NULL DEFAULT '{}',
3094
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
3095
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
3096
+ );
3097
+ CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope);
3098
+ INSERT OR IGNORE INTO _migrations (id) VALUES (55);
3086
3099
  `
3087
3100
  ];
3088
3101
  });
@@ -3259,6 +3272,17 @@ function ensureSchema(db) {
3259
3272
  PRIMARY KEY (handoff_id, agent_id)
3260
3273
  )`);
3261
3274
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at)");
3275
+ ensureTable("saved_search_views", `
3276
+ CREATE TABLE saved_search_views (
3277
+ id TEXT PRIMARY KEY,
3278
+ name TEXT NOT NULL UNIQUE,
3279
+ description TEXT,
3280
+ scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
3281
+ filters TEXT NOT NULL DEFAULT '{}',
3282
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
3283
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
3284
+ )`);
3285
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope)");
3262
3286
  ensureTable("task_relationships", `
3263
3287
  CREATE TABLE task_relationships (
3264
3288
  id TEXT PRIMARY KEY,
@@ -9432,6 +9456,101 @@ var init_comments = __esm(() => {
9432
9456
  init_tasks();
9433
9457
  });
9434
9458
 
9459
+ // src/lib/local-fields.ts
9460
+ function normalizeList(values) {
9461
+ return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
9462
+ }
9463
+ function metadataFields(task) {
9464
+ const value = task.metadata[LOCAL_FIELDS_KEY];
9465
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
9466
+ }
9467
+ function sameCustomValue(actual, expected) {
9468
+ return JSON.stringify(actual) === JSON.stringify(expected);
9469
+ }
9470
+ function hasOwnField(fields, key) {
9471
+ return Object.prototype.hasOwnProperty.call(fields, key);
9472
+ }
9473
+ function getTaskLocalFields(taskId, db) {
9474
+ const d = db || getDatabase();
9475
+ const task = getTask(taskId, d);
9476
+ if (!task)
9477
+ throw new TaskNotFoundError(taskId);
9478
+ const fields = metadataFields(task);
9479
+ return {
9480
+ labels: normalizeList(fields.labels),
9481
+ priority: task.priority,
9482
+ severity: typeof fields.severity === "string" ? fields.severity : null,
9483
+ owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
9484
+ area: typeof fields.area === "string" ? fields.area : null,
9485
+ custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
9486
+ };
9487
+ }
9488
+ function setTaskLocalFields(taskId, input, db) {
9489
+ const d = db || getDatabase();
9490
+ const task = getTask(taskId, d);
9491
+ if (!task)
9492
+ throw new TaskNotFoundError(taskId);
9493
+ const currentFields = getTaskLocalFields(taskId, d);
9494
+ const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
9495
+ const custom = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
9496
+ const nextFields = {
9497
+ labels,
9498
+ priority: input.priority || task.priority,
9499
+ severity: input.severity !== undefined ? input.severity : currentFields.severity,
9500
+ owner: input.owner !== undefined ? input.owner : currentFields.owner,
9501
+ area: input.area !== undefined ? input.area : currentFields.area,
9502
+ custom
9503
+ };
9504
+ const nextMetadata = {
9505
+ ...task.metadata,
9506
+ [LOCAL_FIELDS_KEY]: nextFields
9507
+ };
9508
+ const previousLabels = new Set(currentFields.labels);
9509
+ const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
9510
+ const updates = {
9511
+ version: task.version,
9512
+ priority: input.priority,
9513
+ tags: nextTags,
9514
+ metadata: nextMetadata
9515
+ };
9516
+ if (input.owner !== undefined)
9517
+ updates.assigned_to = nextFields.owner;
9518
+ return updateTask(taskId, updates, d);
9519
+ }
9520
+ function queryTasksByLocalFields(query, db) {
9521
+ const d = db || getDatabase();
9522
+ const tasks = listTasks({
9523
+ priority: query.priority,
9524
+ tags: query.labels,
9525
+ limit: 1e4
9526
+ }, d);
9527
+ const matches = tasks.filter((task) => {
9528
+ const fields = getTaskLocalFields(task.id, d);
9529
+ if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
9530
+ return false;
9531
+ if (query.severity && fields.severity !== query.severity)
9532
+ return false;
9533
+ if (query.owner && fields.owner !== query.owner)
9534
+ return false;
9535
+ if (query.area && fields.area !== query.area)
9536
+ return false;
9537
+ if (query.custom) {
9538
+ for (const [key, expected] of Object.entries(query.custom)) {
9539
+ if (!sameCustomValue(fields.custom[key], expected))
9540
+ return false;
9541
+ }
9542
+ }
9543
+ return true;
9544
+ });
9545
+ return matches.slice(0, query.limit || 100);
9546
+ }
9547
+ var LOCAL_FIELDS_KEY = "local_fields";
9548
+ var init_local_fields = __esm(() => {
9549
+ init_database();
9550
+ init_tasks();
9551
+ init_types();
9552
+ });
9553
+
9435
9554
  // src/lib/search.ts
9436
9555
  var exports_search = {};
9437
9556
  __export(exports_search, {
@@ -9547,6 +9666,347 @@ var init_search = __esm(() => {
9547
9666
  init_database();
9548
9667
  });
9549
9668
 
9669
+ // src/lib/saved-search-views.ts
9670
+ var exports_saved_search_views = {};
9671
+ __export(exports_saved_search_views, {
9672
+ saveSearchView: () => saveSearchView,
9673
+ runSearchView: () => runSearchView,
9674
+ runSavedSearch: () => runSavedSearch,
9675
+ normalizeScope: () => normalizeScope,
9676
+ listSearchViews: () => listSearchViews,
9677
+ getSearchView: () => getSearchView,
9678
+ deleteSearchView: () => deleteSearchView
9679
+ });
9680
+ function parseFilters(value) {
9681
+ if (!value)
9682
+ return {};
9683
+ try {
9684
+ const parsed = JSON.parse(value);
9685
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
9686
+ } catch {
9687
+ return {};
9688
+ }
9689
+ }
9690
+ function rowToSavedSearchView(row) {
9691
+ return {
9692
+ ...row,
9693
+ scope: normalizeScope(row.scope),
9694
+ filters: parseFilters(row.filters)
9695
+ };
9696
+ }
9697
+ function normalizeScope(scope) {
9698
+ if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
9699
+ return scope;
9700
+ }
9701
+ return "tasks";
9702
+ }
9703
+ function normalizeName(name) {
9704
+ const normalized = name.trim();
9705
+ if (!normalized)
9706
+ throw new Error("Saved view name is required");
9707
+ return normalized;
9708
+ }
9709
+ function normalizeLimit(limit) {
9710
+ if (!Number.isFinite(limit) || !limit || limit <= 0)
9711
+ return 100;
9712
+ return Math.min(Math.trunc(limit), 1000);
9713
+ }
9714
+ function valuesList(value) {
9715
+ if (value === undefined)
9716
+ return [];
9717
+ return (Array.isArray(value) ? value : [value]).map((item) => item.trim()).filter(Boolean);
9718
+ }
9719
+ function addStatusFilter(sql, params, column, value) {
9720
+ const values = valuesList(value);
9721
+ if (values.length === 0)
9722
+ return sql;
9723
+ params.push(...values);
9724
+ return `${sql} AND ${column} IN (${values.map(() => "?").join(",")})`;
9725
+ }
9726
+ function addDateFilter(sql, params, column, value) {
9727
+ if (!value)
9728
+ return sql;
9729
+ params.push(value);
9730
+ return `${sql} AND ${column} > ?`;
9731
+ }
9732
+ function likePattern(query) {
9733
+ const trimmed = query?.trim();
9734
+ if (!trimmed || trimmed === "*")
9735
+ return null;
9736
+ return `%${trimmed}%`;
9737
+ }
9738
+ function parseJsonObject(value) {
9739
+ if (!value)
9740
+ return {};
9741
+ if (typeof value === "object" && !Array.isArray(value))
9742
+ return value;
9743
+ if (typeof value !== "string")
9744
+ return {};
9745
+ try {
9746
+ const parsed = JSON.parse(value);
9747
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
9748
+ } catch {
9749
+ return {};
9750
+ }
9751
+ }
9752
+ function rowToTaskRun(row) {
9753
+ return { ...row, metadata: parseJsonObject(row.metadata) };
9754
+ }
9755
+ function taskMatchesSavedFilters(task, filters, db) {
9756
+ if (filters.plan_id && task.plan_id !== filters.plan_id)
9757
+ return false;
9758
+ if (filters.tags && !filters.tags.every((tag) => task.tags.includes(tag)))
9759
+ return false;
9760
+ if (filters.depends_on) {
9761
+ const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(task.id, filters.depends_on);
9762
+ if (!row)
9763
+ return false;
9764
+ }
9765
+ if (filters.blocks) {
9766
+ const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(filters.blocks, task.id);
9767
+ if (!row)
9768
+ return false;
9769
+ }
9770
+ return true;
9771
+ }
9772
+ function searchTaskEntities(filters, db) {
9773
+ let tasks;
9774
+ if (filters.local_fields) {
9775
+ const localMatches = queryTasksByLocalFields({ ...filters.local_fields, limit: 1e4 }, db);
9776
+ const allowedIds = new Set(localMatches.map((task) => task.id));
9777
+ tasks = searchTasks({
9778
+ query: filters.query,
9779
+ project_id: filters.project_id,
9780
+ task_list_id: filters.task_list_id,
9781
+ status: filters.status,
9782
+ priority: filters.priority,
9783
+ assigned_to: filters.assigned_to,
9784
+ agent_id: filters.agent_id,
9785
+ created_after: filters.created_after,
9786
+ updated_after: filters.updated_after,
9787
+ has_dependencies: filters.has_dependencies,
9788
+ is_blocked: filters.is_blocked
9789
+ }, undefined, undefined, db).filter((task) => allowedIds.has(task.id));
9790
+ } else {
9791
+ tasks = searchTasks({
9792
+ query: filters.query,
9793
+ project_id: filters.project_id,
9794
+ task_list_id: filters.task_list_id,
9795
+ status: filters.status,
9796
+ priority: filters.priority,
9797
+ assigned_to: filters.assigned_to,
9798
+ agent_id: filters.agent_id,
9799
+ created_after: filters.created_after,
9800
+ updated_after: filters.updated_after,
9801
+ has_dependencies: filters.has_dependencies,
9802
+ is_blocked: filters.is_blocked
9803
+ }, undefined, undefined, db);
9804
+ }
9805
+ return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
9806
+ }
9807
+ function searchProjects(filters, db) {
9808
+ const params = [];
9809
+ let sql = "SELECT * FROM projects WHERE 1=1";
9810
+ if (filters.project_id) {
9811
+ sql += " AND id = ?";
9812
+ params.push(filters.project_id);
9813
+ }
9814
+ const pattern = likePattern(filters.query);
9815
+ if (pattern) {
9816
+ sql += " AND (name LIKE ? OR description LIKE ? OR path LIKE ?)";
9817
+ params.push(pattern, pattern, pattern);
9818
+ }
9819
+ sql = addDateFilter(sql, params, "created_at", filters.created_after);
9820
+ sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
9821
+ sql += " ORDER BY name LIMIT ?";
9822
+ params.push(normalizeLimit(filters.limit));
9823
+ return db.query(sql).all(...params);
9824
+ }
9825
+ function searchPlans(filters, db) {
9826
+ const params = [];
9827
+ let sql = "SELECT * FROM plans WHERE 1=1";
9828
+ if (filters.project_id) {
9829
+ sql += " AND project_id = ?";
9830
+ params.push(filters.project_id);
9831
+ }
9832
+ if (filters.task_list_id) {
9833
+ sql += " AND task_list_id = ?";
9834
+ params.push(filters.task_list_id);
9835
+ }
9836
+ if (filters.agent_id) {
9837
+ sql += " AND agent_id = ?";
9838
+ params.push(filters.agent_id);
9839
+ }
9840
+ sql = addStatusFilter(sql, params, "status", filters.status);
9841
+ const pattern = likePattern(filters.query);
9842
+ if (pattern) {
9843
+ sql += " AND (name LIKE ? OR description LIKE ?)";
9844
+ params.push(pattern, pattern);
9845
+ }
9846
+ sql = addDateFilter(sql, params, "created_at", filters.created_after);
9847
+ sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
9848
+ sql += " ORDER BY updated_at DESC, created_at DESC LIMIT ?";
9849
+ params.push(normalizeLimit(filters.limit));
9850
+ return db.query(sql).all(...params);
9851
+ }
9852
+ function searchRuns(filters, db) {
9853
+ const params = [];
9854
+ let sql = `SELECT r.* FROM task_runs r
9855
+ JOIN tasks t ON t.id = r.task_id
9856
+ WHERE 1=1`;
9857
+ if (filters.project_id) {
9858
+ sql += " AND t.project_id = ?";
9859
+ params.push(filters.project_id);
9860
+ }
9861
+ if (filters.task_list_id) {
9862
+ sql += " AND t.task_list_id = ?";
9863
+ params.push(filters.task_list_id);
9864
+ }
9865
+ if (filters.plan_id) {
9866
+ sql += " AND t.plan_id = ?";
9867
+ params.push(filters.plan_id);
9868
+ }
9869
+ if (filters.task_id) {
9870
+ sql += " AND r.task_id = ?";
9871
+ params.push(filters.task_id);
9872
+ }
9873
+ if (filters.agent_id) {
9874
+ sql += " AND r.agent_id = ?";
9875
+ params.push(filters.agent_id);
9876
+ }
9877
+ sql = addStatusFilter(sql, params, "r.status", filters.status);
9878
+ const pattern = likePattern(filters.query);
9879
+ if (pattern) {
9880
+ sql += " AND (r.title LIKE ? OR r.summary LIKE ? OR t.title LIKE ?)";
9881
+ params.push(pattern, pattern, pattern);
9882
+ }
9883
+ sql = addDateFilter(sql, params, "r.created_at", filters.created_after);
9884
+ sql = addDateFilter(sql, params, "r.updated_at", filters.updated_after);
9885
+ sql += " ORDER BY r.started_at DESC, r.created_at DESC LIMIT ?";
9886
+ params.push(normalizeLimit(filters.limit));
9887
+ return db.query(sql).all(...params).map(rowToTaskRun);
9888
+ }
9889
+ function searchComments(filters, db) {
9890
+ const params = [];
9891
+ let sql = `SELECT c.* FROM task_comments c
9892
+ JOIN tasks t ON t.id = c.task_id
9893
+ WHERE 1=1`;
9894
+ if (filters.project_id) {
9895
+ sql += " AND t.project_id = ?";
9896
+ params.push(filters.project_id);
9897
+ }
9898
+ if (filters.task_list_id) {
9899
+ sql += " AND t.task_list_id = ?";
9900
+ params.push(filters.task_list_id);
9901
+ }
9902
+ if (filters.plan_id) {
9903
+ sql += " AND t.plan_id = ?";
9904
+ params.push(filters.plan_id);
9905
+ }
9906
+ if (filters.task_id) {
9907
+ sql += " AND c.task_id = ?";
9908
+ params.push(filters.task_id);
9909
+ }
9910
+ if (filters.agent_id) {
9911
+ sql += " AND c.agent_id = ?";
9912
+ params.push(filters.agent_id);
9913
+ }
9914
+ const pattern = likePattern(filters.query);
9915
+ if (pattern) {
9916
+ sql += " AND (c.content LIKE ? OR t.title LIKE ?)";
9917
+ params.push(pattern, pattern);
9918
+ }
9919
+ sql = addDateFilter(sql, params, "c.created_at", filters.created_after);
9920
+ sql += " ORDER BY c.created_at DESC, c.id LIMIT ?";
9921
+ params.push(normalizeLimit(filters.limit));
9922
+ return db.query(sql).all(...params);
9923
+ }
9924
+ function toResults(entityType, rows) {
9925
+ return rows.map((entity) => ({ entity_type: entityType, entity }));
9926
+ }
9927
+ function runSavedSearch(filters = {}, scope = "tasks", db) {
9928
+ const d = db || getDatabase();
9929
+ const normalizedScope = normalizeScope(scope);
9930
+ const scopes = normalizedScope === "all" ? ["tasks", "projects", "plans", "runs", "comments"] : [normalizedScope];
9931
+ const results = [];
9932
+ for (const item of scopes) {
9933
+ if (item === "tasks")
9934
+ results.push(...toResults("tasks", searchTaskEntities(filters, d)));
9935
+ if (item === "projects")
9936
+ results.push(...toResults("projects", searchProjects(filters, d)));
9937
+ if (item === "plans")
9938
+ results.push(...toResults("plans", searchPlans(filters, d)));
9939
+ if (item === "runs")
9940
+ results.push(...toResults("runs", searchRuns(filters, d)));
9941
+ if (item === "comments")
9942
+ results.push(...toResults("comments", searchComments(filters, d)));
9943
+ }
9944
+ return {
9945
+ scope: normalizedScope,
9946
+ filters,
9947
+ count: results.length,
9948
+ results: results.slice(0, normalizeLimit(filters.limit))
9949
+ };
9950
+ }
9951
+ function saveSearchView(input, db) {
9952
+ const d = db || getDatabase();
9953
+ const name = normalizeName(input.name);
9954
+ const timestamp = now();
9955
+ const existing = getSearchView(name, d);
9956
+ if (existing) {
9957
+ d.run(`UPDATE saved_search_views
9958
+ SET description = ?, scope = ?, filters = ?, updated_at = ?
9959
+ WHERE id = ?`, [
9960
+ input.description ?? existing.description,
9961
+ normalizeScope(input.scope ?? existing.scope),
9962
+ JSON.stringify(input.filters ?? existing.filters),
9963
+ timestamp,
9964
+ existing.id
9965
+ ]);
9966
+ return getSearchView(existing.id, d);
9967
+ }
9968
+ const id = uuid();
9969
+ d.run(`INSERT INTO saved_search_views (id, name, description, scope, filters, created_at, updated_at)
9970
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
9971
+ id,
9972
+ name,
9973
+ input.description ?? null,
9974
+ normalizeScope(input.scope),
9975
+ JSON.stringify(input.filters ?? {}),
9976
+ timestamp,
9977
+ timestamp
9978
+ ]);
9979
+ return getSearchView(id, d);
9980
+ }
9981
+ function getSearchView(idOrName, db) {
9982
+ const d = db || getDatabase();
9983
+ const row = d.query("SELECT * FROM saved_search_views WHERE id = ? OR name = ?").get(idOrName, idOrName);
9984
+ return row ? rowToSavedSearchView(row) : null;
9985
+ }
9986
+ function listSearchViews(scope, db) {
9987
+ const d = db || getDatabase();
9988
+ const normalizedScope = scope ? normalizeScope(scope) : null;
9989
+ const rows = normalizedScope ? d.query("SELECT * FROM saved_search_views WHERE scope = ? ORDER BY name").all(normalizedScope) : d.query("SELECT * FROM saved_search_views ORDER BY name").all();
9990
+ return rows.map(rowToSavedSearchView);
9991
+ }
9992
+ function deleteSearchView(idOrName, db) {
9993
+ const d = db || getDatabase();
9994
+ const result = d.run("DELETE FROM saved_search_views WHERE id = ? OR name = ?", [idOrName, idOrName]);
9995
+ return result.changes > 0;
9996
+ }
9997
+ function runSearchView(idOrName, db) {
9998
+ const d = db || getDatabase();
9999
+ const view = getSearchView(idOrName, d);
10000
+ if (!view)
10001
+ throw new Error(`Saved search view not found: ${idOrName}`);
10002
+ return { ...runSavedSearch(view.filters, view.scope, d), view };
10003
+ }
10004
+ var init_saved_search_views = __esm(() => {
10005
+ init_database();
10006
+ init_local_fields();
10007
+ init_search();
10008
+ });
10009
+
9550
10010
  // src/lib/claude-tasks.ts
9551
10011
  import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
9552
10012
  import { join as join5 } from "path";
@@ -10786,7 +11246,7 @@ function packageSource(version) {
10786
11246
  function emptyCounts() {
10787
11247
  return Object.fromEntries(dataKeys.map((key) => [key, 0]));
10788
11248
  }
10789
- function parseJsonObject(value) {
11249
+ function parseJsonObject2(value) {
10790
11250
  if (!value)
10791
11251
  return {};
10792
11252
  if (typeof value === "object" && !Array.isArray(value))
@@ -10832,22 +11292,25 @@ function rowToTask3(row) {
10832
11292
  return {
10833
11293
  ...row,
10834
11294
  tags: parseJsonArray(row.tags),
10835
- metadata: parseJsonObject(row.metadata),
11295
+ metadata: parseJsonObject2(row.metadata),
10836
11296
  requires_approval: Boolean(row.requires_approval)
10837
11297
  };
10838
11298
  }
10839
11299
  function rowToTaskList2(row) {
10840
- return { ...row, metadata: parseJsonObject(row.metadata) };
11300
+ return { ...row, metadata: parseJsonObject2(row.metadata) };
10841
11301
  }
10842
11302
  function rowWithMetadata(row) {
10843
- return { ...row, metadata: parseJsonObject(row.metadata) };
11303
+ return { ...row, metadata: parseJsonObject2(row.metadata) };
10844
11304
  }
10845
11305
  function rowToRunEvent(row) {
10846
- return { ...row, data: parseJsonObject(row.data) };
11306
+ return { ...row, data: parseJsonObject2(row.data) };
10847
11307
  }
10848
11308
  function rowToCommit2(row) {
10849
11309
  return { ...row, files_changed: row.files_changed ? parseJsonArray(row.files_changed) : null };
10850
11310
  }
11311
+ function rowToSavedView(row) {
11312
+ return { ...row, filters: parseJsonObject2(row.filters) };
11313
+ }
10851
11314
  function bridgeStats(data) {
10852
11315
  return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
10853
11316
  }
@@ -10871,7 +11334,8 @@ function createLocalBridgeBundle(options = {}, db) {
10871
11334
  task_files: queryByTaskIds(d, "SELECT * FROM task_files WHERE task_id IN (__TASK_IDS__) ORDER BY path, id", taskIds),
10872
11335
  task_commits: queryByTaskIds(d, "SELECT * FROM task_commits WHERE task_id IN (__TASK_IDS__) ORDER BY created_at, id", taskIds).map(rowToCommit2),
10873
11336
  task_git_refs: queryByTaskIds(d, "SELECT * FROM task_git_refs WHERE task_id IN (__TASK_IDS__) ORDER BY created_at, id", taskIds).map(rowWithMetadata),
10874
- task_verifications: queryByTaskIds(d, "SELECT * FROM task_verifications WHERE task_id IN (__TASK_IDS__) ORDER BY run_at, id", taskIds)
11337
+ task_verifications: queryByTaskIds(d, "SELECT * FROM task_verifications WHERE task_id IN (__TASK_IDS__) ORDER BY run_at, id", taskIds),
11338
+ saved_views: (options.project_id ? d.query("SELECT * FROM saved_search_views WHERE json_extract(filters, '$.project_id') = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM saved_search_views ORDER BY name").all()).map(rowToSavedView)
10875
11339
  };
10876
11340
  const artifactContents = data.run_artifacts.map((artifact) => exportStoredArtifactContent({
10877
11341
  id: artifact.id,
@@ -10910,6 +11374,8 @@ function validateLocalBridgeBundle(value) {
10910
11374
  issues.push("data must be an object");
10911
11375
  } else {
10912
11376
  for (const key of dataKeys) {
11377
+ if (key === "saved_views" && data[key] === undefined)
11378
+ continue;
10913
11379
  if (!Array.isArray(data[key]))
10914
11380
  issues.push(`data.${key} must be an array`);
10915
11381
  }
@@ -11082,7 +11548,8 @@ function importLocalBridgeBundle(bundle, options = {}, db) {
11082
11548
  const conflictStrategy = options.conflictStrategy ?? "skip";
11083
11549
  const data = {
11084
11550
  ...bundle.data,
11085
- tasks: sortedTasks(bundle.data.tasks)
11551
+ tasks: sortedTasks(bundle.data.tasks),
11552
+ saved_views: bundle.data.saved_views ?? []
11086
11553
  };
11087
11554
  for (const key of dataKeys) {
11088
11555
  for (const row of data[key]) {
@@ -11153,7 +11620,8 @@ var init_local_bridge = __esm(() => {
11153
11620
  "task_files",
11154
11621
  "task_commits",
11155
11622
  "task_git_refs",
11156
- "task_verifications"
11623
+ "task_verifications",
11624
+ "saved_views"
11157
11625
  ];
11158
11626
  insertColumns = {
11159
11627
  projects: ["id", "name", "path", "description", "task_list_id", "task_prefix", "task_counter", "created_at", "updated_at", "machine_id", "synced_at"],
@@ -11225,7 +11693,8 @@ var init_local_bridge = __esm(() => {
11225
11693
  task_files: ["id", "task_id", "path", "status", "agent_id", "note", "created_at", "updated_at", "machine_id"],
11226
11694
  task_commits: ["id", "task_id", "sha", "message", "author", "files_changed", "committed_at", "created_at"],
11227
11695
  task_git_refs: ["id", "task_id", "ref_type", "name", "url", "provider", "metadata", "created_at", "updated_at"],
11228
- task_verifications: ["id", "task_id", "command", "status", "output_summary", "artifact_path", "agent_id", "run_at", "created_at"]
11696
+ task_verifications: ["id", "task_id", "command", "status", "output_summary", "artifact_path", "agent_id", "run_at", "created_at"],
11697
+ saved_views: ["id", "name", "description", "scope", "filters", "created_at", "updated_at"]
11229
11698
  };
11230
11699
  tableByKey = {
11231
11700
  projects: "projects",
@@ -11241,9 +11710,10 @@ var init_local_bridge = __esm(() => {
11241
11710
  task_files: "task_files",
11242
11711
  task_commits: "task_commits",
11243
11712
  task_git_refs: "task_git_refs",
11244
- task_verifications: "task_verifications"
11713
+ task_verifications: "task_verifications",
11714
+ saved_views: "saved_search_views"
11245
11715
  };
11246
- jsonColumns = new Set(["metadata", "tags", "data", "files_changed"]);
11716
+ jsonColumns = new Set(["metadata", "tags", "data", "files_changed", "filters"]);
11247
11717
  });
11248
11718
 
11249
11719
  // src/lib/local-encryption.ts
@@ -11818,7 +12288,8 @@ function emptyCounts2() {
11818
12288
  task_files: 0,
11819
12289
  task_commits: 0,
11820
12290
  task_git_refs: 0,
11821
- task_verifications: 0
12291
+ task_verifications: 0,
12292
+ saved_views: 0
11822
12293
  };
11823
12294
  }
11824
12295
  function bridgeToMarkdownPayload(bundle) {
@@ -12069,6 +12540,61 @@ __export(exports_project_commands, {
12069
12540
  });
12070
12541
  import chalk4 from "chalk";
12071
12542
  import { basename as basename3, resolve as resolve9 } from "path";
12543
+ function collectOption(value, previous = []) {
12544
+ return [...previous, value];
12545
+ }
12546
+ function splitList(value) {
12547
+ if (!value)
12548
+ return;
12549
+ const values = Array.isArray(value) ? value : [value];
12550
+ const items = values.flatMap((item) => item.split(",").map((part) => part.trim()).filter(Boolean));
12551
+ return items.length > 0 ? items : undefined;
12552
+ }
12553
+ function parseJsonObjectOption(value, label) {
12554
+ if (!value)
12555
+ return;
12556
+ try {
12557
+ const parsed = JSON.parse(value);
12558
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
12559
+ return parsed;
12560
+ } catch {}
12561
+ throw new Error(`${label} must be a JSON object`);
12562
+ }
12563
+ function buildSearchFilters(query, opts, projectId) {
12564
+ const filterPatch = parseJsonObjectOption(opts.filter, "--filter");
12565
+ const customFields = parseJsonObjectOption(opts.fieldCustom, "--field-custom");
12566
+ const labels = splitList(opts.fieldLabel);
12567
+ const tags = splitList(opts.tag);
12568
+ const statuses = splitList(opts.status)?.map(normalizeStatus);
12569
+ const filters = {
12570
+ query: query || opts.query,
12571
+ project_id: opts.allProjects ? undefined : projectId,
12572
+ status: statuses,
12573
+ priority: splitList(opts.priority),
12574
+ assigned_to: opts.assigned,
12575
+ agent_id: opts.agentId,
12576
+ task_list_id: opts.taskList,
12577
+ plan_id: opts.plan,
12578
+ task_id: opts.task,
12579
+ tags,
12580
+ created_after: opts.createdAfter,
12581
+ updated_after: opts.since || opts.updatedAfter,
12582
+ has_dependencies: opts.hasDeps ? true : undefined,
12583
+ is_blocked: opts.blocked ? true : undefined,
12584
+ depends_on: opts.dependsOn,
12585
+ blocks: opts.blocks,
12586
+ limit: opts.limit ? Number(opts.limit) : undefined,
12587
+ local_fields: labels || opts.fieldOwner || opts.fieldArea || opts.fieldSeverity || customFields ? {
12588
+ labels,
12589
+ owner: opts.fieldOwner,
12590
+ area: opts.fieldArea,
12591
+ severity: opts.fieldSeverity,
12592
+ custom: customFields
12593
+ } : undefined,
12594
+ ...filterPatch
12595
+ };
12596
+ return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== undefined));
12597
+ }
12072
12598
  function registerProjectCommands(program2) {
12073
12599
  program2.command("project-bootstrap [path]").description("Discover a local workspace and initialize project task state").option("--name <name>", "Project display name").option("--task-list <slug>", "Default task list slug").option("--dry-run", "Show discovery without writing local state").action(async (inputPath, opts) => {
12074
12600
  const globalOpts = program2.opts();
@@ -12121,35 +12647,126 @@ function registerProjectCommands(program2) {
12121
12647
  console.log(chalk4.green("Comment added."));
12122
12648
  }
12123
12649
  });
12124
- program2.command("search <query>").description("Search tasks").option("--status <status>", "Filter by status").option("--priority <p>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--since <date>", "Only tasks updated after this date (ISO)").option("--blocked", "Only blocked tasks (incomplete dependencies)").option("--has-deps", "Only tasks with dependencies").action((query, opts) => {
12650
+ program2.command("search <query>").description("Search local tasks, or run/save a cross-entity search view").option("--status <status>", "Filter by status").option("--priority <p>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--agent-id <agent>", "Filter by creator/run/comment agent").option("--task-list <id>", "Filter by task list").option("--plan <id>", "Filter by plan").option("--task <id>", "Filter runs/comments by task").option("--tag <tag>", "Filter by task tag (repeatable or comma-separated)", collectOption, []).option("--field-label <label>", "Filter by local field label (repeatable or comma-separated)", collectOption, []).option("--field-owner <owner>", "Filter by local field owner").option("--field-area <area>", "Filter by local field area").option("--field-severity <severity>", "Filter by local field severity").option("--field-custom <json>", "Filter by local custom fields as JSON").option("--since <date>", "Only tasks updated after this date (ISO)").option("--created-after <date>", "Only records created after this date (ISO)").option("--blocked", "Only blocked tasks (incomplete dependencies)").option("--has-deps", "Only tasks with dependencies").option("--depends-on <id>", "Only tasks that depend on a task").option("--blocks <id>", "Only tasks that block a task").option("--scope <scope>", "Search scope: tasks, projects, plans, runs, comments, all", "tasks").option("--limit <n>", "Maximum results", "100").option("--filter <json>", "Merge an advanced saved-search filter JSON object").option("--save-as <name>", "Save this search as a named view").option("--description <text>", "Saved view description").option("--all-projects", "Do not auto-scope the search to the current project").action((query, opts) => {
12125
12651
  const globalOpts = program2.opts();
12126
- const projectId = autoProject(globalOpts);
12127
- const searchOpts = { query, project_id: projectId };
12128
- if (opts.status)
12129
- searchOpts.status = normalizeStatus(opts.status);
12130
- if (opts.priority)
12131
- searchOpts.priority = opts.priority;
12132
- if (opts.assigned)
12133
- searchOpts.assigned_to = opts.assigned;
12134
- if (opts.since)
12135
- searchOpts.updated_after = opts.since;
12136
- if (opts.blocked)
12137
- searchOpts.is_blocked = true;
12138
- if (opts.hasDeps)
12139
- searchOpts.has_dependencies = true;
12140
- const tasks = searchTasks(searchOpts);
12141
- if (globalOpts.json) {
12142
- output(tasks, true);
12143
- return;
12652
+ try {
12653
+ const projectId = opts.allProjects ? undefined : autoProject(globalOpts);
12654
+ const scope = normalizeScope(opts.scope);
12655
+ const searchOpts = buildSearchFilters(query, opts, projectId);
12656
+ if (opts.saveAs) {
12657
+ const view = saveSearchView({
12658
+ name: opts.saveAs,
12659
+ description: opts.description,
12660
+ scope,
12661
+ filters: searchOpts
12662
+ });
12663
+ output(view, Boolean(globalOpts.json));
12664
+ if (!globalOpts.json)
12665
+ console.log(chalk4.green(`Saved view ${view.name}.`));
12666
+ return;
12667
+ }
12668
+ if (scope !== "tasks") {
12669
+ const result = runSavedSearch(searchOpts, scope);
12670
+ if (globalOpts.json) {
12671
+ output(result, true);
12672
+ return;
12673
+ }
12674
+ if (result.count === 0) {
12675
+ console.log(chalk4.dim(`No ${scope} results matching "${query}".`));
12676
+ return;
12677
+ }
12678
+ console.log(chalk4.bold(`${result.count} ${scope} result(s) for "${query}":
12679
+ `));
12680
+ for (const item of result.results) {
12681
+ const entity = item.entity;
12682
+ console.log(`${chalk4.cyan(item.entity_type)} ${entity.id?.slice?.(0, 8) || ""} ${entity.name || entity.title || entity.content || entity.summary || ""}`);
12683
+ }
12684
+ return;
12685
+ }
12686
+ const tasks = runSavedSearch(searchOpts, "tasks").results.map((item) => item.entity);
12687
+ if (globalOpts.json) {
12688
+ output(tasks, true);
12689
+ return;
12690
+ }
12691
+ if (tasks.length === 0) {
12692
+ console.log(chalk4.dim(`No tasks matching "${query}".`));
12693
+ return;
12694
+ }
12695
+ console.log(chalk4.bold(`${tasks.length} result(s) for "${query}":
12696
+ `));
12697
+ for (const t of tasks) {
12698
+ console.log(formatTaskLine(t));
12699
+ }
12700
+ } catch (e) {
12701
+ handleError(e);
12144
12702
  }
12145
- if (tasks.length === 0) {
12146
- console.log(chalk4.dim(`No tasks matching "${query}".`));
12147
- return;
12703
+ });
12704
+ const views = program2.command("views").description("Manage local saved search views");
12705
+ views.command("save <name>").description("Save a local search view").option("--query <query>", "Search query").option("--scope <scope>", "Search scope: tasks, projects, plans, runs, comments, all", "tasks").option("--description <text>", "Description").option("--status <status>", "Filter by status").option("--priority <p>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--agent-id <agent>", "Filter by creator/run/comment agent").option("--task-list <id>", "Filter by task list").option("--plan <id>", "Filter by plan").option("--task <id>", "Filter runs/comments by task").option("--tag <tag>", "Filter by task tag (repeatable or comma-separated)", collectOption, []).option("--field-label <label>", "Filter by local field label (repeatable or comma-separated)", collectOption, []).option("--field-owner <owner>", "Filter by local field owner").option("--field-area <area>", "Filter by local field area").option("--field-severity <severity>", "Filter by local field severity").option("--field-custom <json>", "Filter by local custom fields as JSON").option("--since <date>", "Only records updated after this date (ISO)").option("--created-after <date>", "Only records created after this date (ISO)").option("--blocked", "Only blocked tasks").option("--has-deps", "Only tasks with dependencies").option("--depends-on <id>", "Only tasks that depend on a task").option("--blocks <id>", "Only tasks that block a task").option("--limit <n>", "Maximum results", "100").option("--filter <json>", "Merge an advanced saved-search filter JSON object").option("--all-projects", "Do not auto-scope the view to the current project").action((name, opts) => {
12706
+ const globalOpts = program2.opts();
12707
+ try {
12708
+ const projectId = opts.allProjects ? undefined : autoProject(globalOpts);
12709
+ const view = saveSearchView({
12710
+ name,
12711
+ description: opts.description,
12712
+ scope: normalizeScope(opts.scope),
12713
+ filters: buildSearchFilters(opts.query, opts, projectId)
12714
+ });
12715
+ output(view, Boolean(globalOpts.json));
12716
+ if (!globalOpts.json)
12717
+ console.log(chalk4.green(`Saved view ${view.name}.`));
12718
+ } catch (e) {
12719
+ handleError(e);
12148
12720
  }
12149
- console.log(chalk4.bold(`${tasks.length} result(s) for "${query}":
12721
+ });
12722
+ views.command("list").description("List local saved search views").option("--scope <scope>", "Filter by scope").action((opts) => {
12723
+ const globalOpts = program2.opts();
12724
+ try {
12725
+ const rows = listSearchViews(opts.scope ? normalizeScope(opts.scope) : undefined);
12726
+ output(rows, Boolean(globalOpts.json));
12727
+ if (!globalOpts.json) {
12728
+ if (rows.length === 0) {
12729
+ console.log(chalk4.dim("No saved search views."));
12730
+ return;
12731
+ }
12732
+ for (const row of rows)
12733
+ console.log(`${chalk4.cyan(row.name)} ${chalk4.dim(`[${row.scope}]`)} ${JSON.stringify(row.filters)}`);
12734
+ }
12735
+ } catch (e) {
12736
+ handleError(e);
12737
+ }
12738
+ });
12739
+ views.command("run <name>").description("Run a local saved search view").action((name) => {
12740
+ const globalOpts = program2.opts();
12741
+ try {
12742
+ const result = runSearchView(name);
12743
+ if (globalOpts.json) {
12744
+ output(result, true);
12745
+ return;
12746
+ }
12747
+ console.log(chalk4.bold(`${result.count} result(s) for view "${result.view?.name || name}":
12150
12748
  `));
12151
- for (const t of tasks) {
12152
- console.log(formatTaskLine(t));
12749
+ for (const item of result.results) {
12750
+ if (item.entity_type === "tasks") {
12751
+ console.log(formatTaskLine(item.entity));
12752
+ continue;
12753
+ }
12754
+ const entity = item.entity;
12755
+ console.log(`${chalk4.cyan(item.entity_type)} ${entity.id?.slice?.(0, 8) || ""} ${entity.name || entity.title || entity.content || entity.summary || ""}`);
12756
+ }
12757
+ } catch (e) {
12758
+ handleError(e);
12759
+ }
12760
+ });
12761
+ views.command("delete <name>").description("Delete a local saved search view").action((name) => {
12762
+ const globalOpts = program2.opts();
12763
+ try {
12764
+ const deleted = deleteSearchView(name);
12765
+ output({ deleted }, Boolean(globalOpts.json));
12766
+ if (!globalOpts.json)
12767
+ console.log(deleted ? chalk4.green(`Deleted view ${name}.`) : chalk4.dim(`View not found: ${name}`));
12768
+ } catch (e) {
12769
+ handleError(e);
12153
12770
  }
12154
12771
  });
12155
12772
  program2.command("deps <id>").description("Manage task dependencies").option("--needs <dep-id>", "Add dependency (this task needs dep-id)").option("--remove <dep-id>", "Remove dependency").option("--graph", "Show the dependency graph instead of direct edges").option("--direction <direction>", "Graph direction: up, down, or both", "both").action(async (id, opts) => {
@@ -12576,7 +13193,7 @@ var init_project_commands = __esm(() => {
12576
13193
  init_database();
12577
13194
  init_projects();
12578
13195
  init_comments();
12579
- init_search();
13196
+ init_saved_search_views();
12580
13197
  init_sync();
12581
13198
  init_config();
12582
13199
  init_helpers();
@@ -18614,101 +19231,6 @@ var init_task_dedupe = __esm(() => {
18614
19231
  PY_STACK_RE = /File\s+"([^"]+)",\s+line\s+\d+,\s+in\s+([^\s]+)/i;
18615
19232
  });
18616
19233
 
18617
- // src/lib/local-fields.ts
18618
- function normalizeList(values) {
18619
- return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
18620
- }
18621
- function metadataFields(task) {
18622
- const value = task.metadata[LOCAL_FIELDS_KEY];
18623
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18624
- }
18625
- function sameCustomValue(actual, expected) {
18626
- return JSON.stringify(actual) === JSON.stringify(expected);
18627
- }
18628
- function hasOwnField(fields, key) {
18629
- return Object.prototype.hasOwnProperty.call(fields, key);
18630
- }
18631
- function getTaskLocalFields(taskId, db) {
18632
- const d = db || getDatabase();
18633
- const task = getTask(taskId, d);
18634
- if (!task)
18635
- throw new TaskNotFoundError(taskId);
18636
- const fields = metadataFields(task);
18637
- return {
18638
- labels: normalizeList(fields.labels),
18639
- priority: task.priority,
18640
- severity: typeof fields.severity === "string" ? fields.severity : null,
18641
- owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
18642
- area: typeof fields.area === "string" ? fields.area : null,
18643
- custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
18644
- };
18645
- }
18646
- function setTaskLocalFields(taskId, input, db) {
18647
- const d = db || getDatabase();
18648
- const task = getTask(taskId, d);
18649
- if (!task)
18650
- throw new TaskNotFoundError(taskId);
18651
- const currentFields = getTaskLocalFields(taskId, d);
18652
- const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
18653
- const custom = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
18654
- const nextFields = {
18655
- labels,
18656
- priority: input.priority || task.priority,
18657
- severity: input.severity !== undefined ? input.severity : currentFields.severity,
18658
- owner: input.owner !== undefined ? input.owner : currentFields.owner,
18659
- area: input.area !== undefined ? input.area : currentFields.area,
18660
- custom
18661
- };
18662
- const nextMetadata = {
18663
- ...task.metadata,
18664
- [LOCAL_FIELDS_KEY]: nextFields
18665
- };
18666
- const previousLabels = new Set(currentFields.labels);
18667
- const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
18668
- const updates = {
18669
- version: task.version,
18670
- priority: input.priority,
18671
- tags: nextTags,
18672
- metadata: nextMetadata
18673
- };
18674
- if (input.owner !== undefined)
18675
- updates.assigned_to = nextFields.owner;
18676
- return updateTask(taskId, updates, d);
18677
- }
18678
- function queryTasksByLocalFields(query, db) {
18679
- const d = db || getDatabase();
18680
- const tasks = listTasks({
18681
- priority: query.priority,
18682
- tags: query.labels,
18683
- limit: 1e4
18684
- }, d);
18685
- const matches = tasks.filter((task) => {
18686
- const fields = getTaskLocalFields(task.id, d);
18687
- if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
18688
- return false;
18689
- if (query.severity && fields.severity !== query.severity)
18690
- return false;
18691
- if (query.owner && fields.owner !== query.owner)
18692
- return false;
18693
- if (query.area && fields.area !== query.area)
18694
- return false;
18695
- if (query.custom) {
18696
- for (const [key, expected] of Object.entries(query.custom)) {
18697
- if (!sameCustomValue(fields.custom[key], expected))
18698
- return false;
18699
- }
18700
- }
18701
- return true;
18702
- });
18703
- return matches.slice(0, query.limit || 100);
18704
- }
18705
- var LOCAL_FIELDS_KEY = "local_fields";
18706
- var init_local_fields = __esm(() => {
18707
- init_database();
18708
- init_tasks();
18709
- init_types();
18710
- });
18711
-
18712
19234
  // src/lib/activity-timeline.ts
18713
19235
  var exports_activity_timeline = {};
18714
19236
  __export(exports_activity_timeline, {
@@ -19552,7 +20074,7 @@ __export(exports_query_commands, {
19552
20074
  registerQueryCommands: () => registerQueryCommands
19553
20075
  });
19554
20076
  import chalk7 from "chalk";
19555
- function parseJsonObjectOption(value, label) {
20077
+ function parseJsonObjectOption2(value, label) {
19556
20078
  if (!value)
19557
20079
  return;
19558
20080
  try {
@@ -20904,7 +21426,7 @@ Repairs`));
20904
21426
  fields.command("set <task-id>").description("Set local fields for a task").option("--labels <labels>", "Comma-separated labels").option("--priority <priority>", "Priority: low, medium, high, critical").option("--severity <severity>", "Local severity, for example s0, s1, s2").option("--owner <owner>", "Local owner or responsible agent").option("--area <area>", "Local area or component").option("--custom <json>", "Custom fields as a JSON object").option("--field <pairs...>", "Custom key=value pairs").option("--replace-custom", "Replace custom fields instead of merging").option("-j, --json", "Output as JSON").action((taskId, opts) => {
20905
21427
  const globalOpts = program2.opts();
20906
21428
  try {
20907
- const custom = mergeCustomFields(parseJsonObjectOption(opts.custom, "--custom"), parseFieldPairs(opts.field));
21429
+ const custom = mergeCustomFields(parseJsonObjectOption2(opts.custom, "--custom"), parseFieldPairs(opts.field));
20908
21430
  const input = {
20909
21431
  labels: parseCsvOption(opts.labels),
20910
21432
  priority: parsePriority(opts.priority),
@@ -20934,7 +21456,7 @@ Repairs`));
20934
21456
  severity: opts.severity,
20935
21457
  owner: opts.owner,
20936
21458
  area: opts.area,
20937
- custom: mergeCustomFields(parseJsonObjectOption(opts.custom, "--custom"), parseFieldPairs(opts.field)),
21459
+ custom: mergeCustomFields(parseJsonObjectOption2(opts.custom, "--custom"), parseFieldPairs(opts.field)),
20938
21460
  limit: Number(opts.limit)
20939
21461
  };
20940
21462
  const tasks = queryTasksByLocalFields(query);
@@ -21018,7 +21540,7 @@ Repairs`));
21018
21540
  source_type: opts.sourceType,
21019
21541
  source_name: opts.sourceName || opts.file,
21020
21542
  source_url: opts.sourceUrl,
21021
- metadata: parseJsonObjectOption(opts.metadata, "--metadata"),
21543
+ metadata: parseJsonObjectOption2(opts.metadata, "--metadata"),
21022
21544
  project_id: autoProject(globalOpts) || undefined,
21023
21545
  priority: opts.priority,
21024
21546
  tags: opts.tags ? String(opts.tags).split(",").map((tag) => tag.trim()).filter(Boolean) : undefined,
@@ -25810,6 +26332,10 @@ var init_token_utils = __esm(() => {
25810
26332
  "request_task_review",
25811
26333
  "reschedule_task",
25812
26334
  "search_tasks",
26335
+ "save_search_view",
26336
+ "list_search_views",
26337
+ "run_search_view",
26338
+ "delete_search_view",
25813
26339
  "standup",
25814
26340
  "set_task_contract",
25815
26341
  "task_context",
@@ -27601,6 +28127,76 @@ ${lines.join(`
27601
28127
  }
27602
28128
  });
27603
28129
  }
28130
+ if (shouldRegisterTool("save_search_view")) {
28131
+ server.tool("save_search_view", "Save a local search view for tasks, projects, plans, runs, comments, or all records.", {
28132
+ name: exports_external.string().describe("Saved view name"),
28133
+ query: exports_external.string().optional().describe("Search query"),
28134
+ scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional(),
28135
+ description: exports_external.string().optional(),
28136
+ project_id: exports_external.string().optional(),
28137
+ status: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
28138
+ priority: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
28139
+ assigned_to: exports_external.string().optional(),
28140
+ agent_id: exports_external.string().optional(),
28141
+ tags: exports_external.array(exports_external.string()).optional(),
28142
+ limit: exports_external.number().optional()
28143
+ }, async ({ name, scope, description, ...filters }) => {
28144
+ try {
28145
+ const { saveSearchView: saveSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
28146
+ const view = saveSearchView2({
28147
+ name,
28148
+ description,
28149
+ scope,
28150
+ filters: {
28151
+ ...filters,
28152
+ project_id: filters.project_id ? resolveId(filters.project_id, "projects") : undefined
28153
+ }
28154
+ });
28155
+ return { content: [{ type: "text", text: JSON.stringify(view, null, 2) }] };
28156
+ } catch (e) {
28157
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
28158
+ }
28159
+ });
28160
+ }
28161
+ if (shouldRegisterTool("list_search_views")) {
28162
+ server.tool("list_search_views", "List local saved search views.", {
28163
+ scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional()
28164
+ }, async ({ scope }) => {
28165
+ try {
28166
+ const { listSearchViews: listSearchViews2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
28167
+ const views = listSearchViews2(scope);
28168
+ return { content: [{ type: "text", text: JSON.stringify(views, null, 2) }] };
28169
+ } catch (e) {
28170
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
28171
+ }
28172
+ });
28173
+ }
28174
+ if (shouldRegisterTool("run_search_view")) {
28175
+ server.tool("run_search_view", "Run a local saved search view and return stable JSON results.", {
28176
+ name: exports_external.string().describe("Saved view name or id")
28177
+ }, async ({ name }) => {
28178
+ try {
28179
+ const { runSearchView: runSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
28180
+ const result = runSearchView2(name);
28181
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
28182
+ } catch (e) {
28183
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
28184
+ }
28185
+ });
28186
+ }
28187
+ if (shouldRegisterTool("delete_search_view")) {
28188
+ server.tool("delete_search_view", "Delete a local saved search view.", {
28189
+ name: exports_external.string().describe("Saved view name or id")
28190
+ }, async ({ name }) => {
28191
+ try {
28192
+ const { deleteSearchView: deleteSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
28193
+ const deleted = deleteSearchView2(name);
28194
+ return { content: [{ type: "text", text: JSON.stringify({ deleted }, null, 2) }] };
28195
+ } catch (e) {
28196
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
28197
+ }
28198
+ });
28199
+ }
27604
28200
  }
27605
28201
  var init_task_project_tools = __esm(() => {
27606
28202
  init_zod();
@@ -28949,6 +29545,10 @@ function registerTaskMetaTools(server, ctx) {
28949
29545
  reschedule_task: "reschedule_task \u2014 Update deadline. Params: task_id, deadline, version",
28950
29546
  prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
28951
29547
  search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
29548
+ save_search_view: "save_search_view \u2014 Save a local search view across tasks, projects, plans, runs, comments, or all records. Params: name, query, scope, description, project_id, status, priority, assigned_to, agent_id, tags, limit",
29549
+ list_search_views: "list_search_views \u2014 List local saved search views. Params: scope",
29550
+ run_search_view: "run_search_view \u2014 Run a local saved search view and return stable JSON results. Params: name",
29551
+ delete_search_view: "delete_search_view \u2014 Delete a local saved search view. Params: name",
28952
29552
  get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
28953
29553
  get_next_task: "get_next_task \u2014 Get the next available task without claiming it. Params: agent_id, project_id, task_list_id, plan_id, tags",
28954
29554
  claim_next_task: "claim_next_task \u2014 Atomically claim and start the next available task. Params: agent_id, project_id, task_list_id, plan_id, tags, steal_stale, stale_minutes",
@@ -29281,7 +29881,7 @@ __export(exports_verification_providers, {
29281
29881
  discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
29282
29882
  });
29283
29883
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
29284
- function normalizeName(name) {
29884
+ function normalizeName2(name) {
29285
29885
  const normalized = name.trim().toLowerCase();
29286
29886
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
29287
29887
  throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
@@ -29300,10 +29900,10 @@ function timeoutMs(value) {
29300
29900
  return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
29301
29901
  }
29302
29902
  function getProvider(name) {
29303
- return loadConfig().verification_providers?.[normalizeName(name)] || null;
29903
+ return loadConfig().verification_providers?.[normalizeName2(name)] || null;
29304
29904
  }
29305
29905
  function upsertVerificationProvider(input) {
29306
- const name = normalizeName(input.name);
29906
+ const name = normalizeName2(input.name);
29307
29907
  const config = loadConfig();
29308
29908
  const existing = config.verification_providers?.[name];
29309
29909
  const timestamp = new Date().toISOString();
@@ -29333,7 +29933,7 @@ function listVerificationProviders() {
29333
29933
  return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
29334
29934
  }
29335
29935
  function removeVerificationProvider(name) {
29336
- const normalized = normalizeName(name);
29936
+ const normalized = normalizeName2(name);
29337
29937
  const config = loadConfig();
29338
29938
  if (!config.verification_providers?.[normalized])
29339
29939
  return false;