@hasna/todos 0.11.41 → 0.11.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -9
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1331 -264
- package/dist/cli-mcp-parity.d.ts +1 -1
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +73 -6
- package/dist/db/builtin-templates.d.ts +17 -0
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +785 -168
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts +2 -0
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/saved-search-views.d.ts +60 -0
- package/dist/lib/saved-search-views.d.ts.map +1 -0
- package/dist/lib/todos-md.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1485 -99
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/templates.d.ts.map +1 -1
- package/dist/mcp.js +7 -1
- package/dist/registry.js +122 -8
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +24 -0
- package/dist/storage.js +24 -0
- package/package.json +1 -1
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,
|
|
@@ -8884,9 +8908,83 @@ var init_plans = __esm(() => {
|
|
|
8884
8908
|
// src/db/builtin-templates.ts
|
|
8885
8909
|
var exports_builtin_templates = {};
|
|
8886
8910
|
__export(exports_builtin_templates, {
|
|
8911
|
+
writeBuiltinTemplateFiles: () => writeBuiltinTemplateFiles,
|
|
8912
|
+
listBuiltinTemplates: () => listBuiltinTemplates,
|
|
8887
8913
|
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
8914
|
+
getBuiltinTemplate: () => getBuiltinTemplate,
|
|
8915
|
+
exportBuiltinTemplateFiles: () => exportBuiltinTemplateFiles,
|
|
8916
|
+
exportBuiltinTemplate: () => exportBuiltinTemplate,
|
|
8917
|
+
BUILTIN_TEMPLATE_LIBRARY_VERSION: () => BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
8918
|
+
BUILTIN_TEMPLATE_LIBRARY_SOURCE: () => BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
8888
8919
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
8889
8920
|
});
|
|
8921
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
8922
|
+
import { join as join5 } from "path";
|
|
8923
|
+
function templateMetadata(template) {
|
|
8924
|
+
return {
|
|
8925
|
+
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
8926
|
+
library_version: template.version,
|
|
8927
|
+
category: template.category,
|
|
8928
|
+
template_file: `${template.name}.json`,
|
|
8929
|
+
local_only: true,
|
|
8930
|
+
marketplace_free: true
|
|
8931
|
+
};
|
|
8932
|
+
}
|
|
8933
|
+
function listBuiltinTemplates() {
|
|
8934
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
8935
|
+
...template,
|
|
8936
|
+
variables: template.variables.map((variable) => ({ ...variable })),
|
|
8937
|
+
tasks: template.tasks.map((task) => ({ ...task, tags: task.tags ? [...task.tags] : undefined }))
|
|
8938
|
+
}));
|
|
8939
|
+
}
|
|
8940
|
+
function getBuiltinTemplate(name) {
|
|
8941
|
+
return listBuiltinTemplates().find((template) => template.name === name) ?? null;
|
|
8942
|
+
}
|
|
8943
|
+
function exportBuiltinTemplate(name) {
|
|
8944
|
+
const template = getBuiltinTemplate(name);
|
|
8945
|
+
if (!template)
|
|
8946
|
+
throw new Error(`Built-in template not found: ${name}`);
|
|
8947
|
+
return {
|
|
8948
|
+
name: template.name,
|
|
8949
|
+
title_pattern: `${template.name}: {${template.variables[0]?.name ?? "name"}}`,
|
|
8950
|
+
description: template.description,
|
|
8951
|
+
priority: "medium",
|
|
8952
|
+
tags: [template.category, "local-template"],
|
|
8953
|
+
variables: template.variables,
|
|
8954
|
+
project_id: null,
|
|
8955
|
+
plan_id: null,
|
|
8956
|
+
metadata: templateMetadata(template),
|
|
8957
|
+
tasks: template.tasks.map((task) => ({
|
|
8958
|
+
position: task.position,
|
|
8959
|
+
title_pattern: task.title_pattern,
|
|
8960
|
+
description: task.description ?? null,
|
|
8961
|
+
priority: task.priority ?? "medium",
|
|
8962
|
+
tags: task.tags ?? [template.category],
|
|
8963
|
+
task_type: task.task_type ?? null,
|
|
8964
|
+
condition: task.condition ?? null,
|
|
8965
|
+
include_template_id: task.include_template_id ?? null,
|
|
8966
|
+
depends_on_positions: task.depends_on_positions ?? task.depends_on ?? [],
|
|
8967
|
+
metadata: task.metadata ?? { category: template.category }
|
|
8968
|
+
}))
|
|
8969
|
+
};
|
|
8970
|
+
}
|
|
8971
|
+
function exportBuiltinTemplateFiles() {
|
|
8972
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
8973
|
+
filename: `${template.name}.json`,
|
|
8974
|
+
template: exportBuiltinTemplate(template.name)
|
|
8975
|
+
}));
|
|
8976
|
+
}
|
|
8977
|
+
function writeBuiltinTemplateFiles(directory) {
|
|
8978
|
+
mkdirSync4(directory, { recursive: true });
|
|
8979
|
+
const files = [];
|
|
8980
|
+
for (const entry of exportBuiltinTemplateFiles()) {
|
|
8981
|
+
const path = join5(directory, entry.filename);
|
|
8982
|
+
writeFileSync2(path, `${JSON.stringify(entry.template, null, 2)}
|
|
8983
|
+
`, "utf-8");
|
|
8984
|
+
files.push(path);
|
|
8985
|
+
}
|
|
8986
|
+
return { directory, written: files.length, files };
|
|
8987
|
+
}
|
|
8890
8988
|
function initBuiltinTemplates(db) {
|
|
8891
8989
|
const d = db || getDatabase();
|
|
8892
8990
|
const existing = listTemplates(d);
|
|
@@ -8912,7 +9010,9 @@ function initBuiltinTemplates(db) {
|
|
|
8912
9010
|
name: bt.name,
|
|
8913
9011
|
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
8914
9012
|
description: bt.description,
|
|
9013
|
+
tags: [bt.category, "local-template"],
|
|
8915
9014
|
variables: bt.variables,
|
|
9015
|
+
metadata: templateMetadata(bt),
|
|
8916
9016
|
tasks
|
|
8917
9017
|
}, d);
|
|
8918
9018
|
created++;
|
|
@@ -8920,14 +9020,141 @@ function initBuiltinTemplates(db) {
|
|
|
8920
9020
|
}
|
|
8921
9021
|
return { created, skipped, names };
|
|
8922
9022
|
}
|
|
8923
|
-
var BUILTIN_TEMPLATES;
|
|
9023
|
+
var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21", BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library", BUILTIN_TEMPLATES;
|
|
8924
9024
|
var init_builtin_templates = __esm(() => {
|
|
8925
9025
|
init_database();
|
|
8926
9026
|
init_templates();
|
|
8927
9027
|
BUILTIN_TEMPLATES = [
|
|
9028
|
+
{
|
|
9029
|
+
name: "bug-fix",
|
|
9030
|
+
description: "Reproduce, diagnose, fix, test, and release a defect.",
|
|
9031
|
+
category: "bug-fix",
|
|
9032
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9033
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
9034
|
+
tasks: [
|
|
9035
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical", tags: ["bug", "repro"] },
|
|
9036
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", tags: ["bug", "diagnosis"], depends_on_positions: [0] },
|
|
9037
|
+
{ position: 2, title_pattern: "Write regression test for {bug}", priority: "high", tags: ["bug", "test"], depends_on_positions: [1] },
|
|
9038
|
+
{ position: 3, title_pattern: "Implement fix for {bug}", priority: "critical", tags: ["bug", "implementation"], depends_on_positions: [2] },
|
|
9039
|
+
{ position: 4, title_pattern: "Run full verification for {bug}", priority: "high", tags: ["bug", "verification"], depends_on_positions: [3] },
|
|
9040
|
+
{ position: 5, title_pattern: "Publish and smoke test fix for {bug}", priority: "high", tags: ["bug", "release"], depends_on_positions: [4] }
|
|
9041
|
+
]
|
|
9042
|
+
},
|
|
9043
|
+
{
|
|
9044
|
+
name: "feature-implementation",
|
|
9045
|
+
description: "Plan, build, test, document, and release a product feature.",
|
|
9046
|
+
category: "feature",
|
|
9047
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9048
|
+
variables: [
|
|
9049
|
+
{ name: "feature", required: true, description: "Feature name" },
|
|
9050
|
+
{ name: "scope", required: false, default: "medium", description: "Implementation size or risk" }
|
|
9051
|
+
],
|
|
9052
|
+
tasks: [
|
|
9053
|
+
{ position: 0, title_pattern: "Define acceptance criteria for {feature}", priority: "high", tags: ["feature", "spec"] },
|
|
9054
|
+
{ position: 1, title_pattern: "Design {scope} implementation plan for {feature}", priority: "high", tags: ["feature", "design"], depends_on_positions: [0] },
|
|
9055
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", tags: ["feature", "implementation"], depends_on_positions: [1] },
|
|
9056
|
+
{ position: 3, title_pattern: "Add tests for {feature}", priority: "high", tags: ["feature", "test"], depends_on_positions: [2] },
|
|
9057
|
+
{ position: 4, title_pattern: "Update docs for {feature}", priority: "medium", tags: ["feature", "docs"], depends_on_positions: [2] },
|
|
9058
|
+
{ position: 5, title_pattern: "Run release checks for {feature}", priority: "high", tags: ["feature", "verification"], depends_on_positions: [3, 4] }
|
|
9059
|
+
]
|
|
9060
|
+
},
|
|
9061
|
+
{
|
|
9062
|
+
name: "security-review",
|
|
9063
|
+
description: "Threat model, test, remediate, and report on security posture.",
|
|
9064
|
+
category: "security",
|
|
9065
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9066
|
+
variables: [{ name: "target", required: true, description: "System, package, or change under review" }],
|
|
9067
|
+
tasks: [
|
|
9068
|
+
{ position: 0, title_pattern: "Map trust boundaries for {target}", priority: "critical", tags: ["security", "threat-model"] },
|
|
9069
|
+
{ position: 1, title_pattern: "Scan {target} for vulnerabilities and secret exposure", priority: "critical", tags: ["security", "scan"], depends_on_positions: [0] },
|
|
9070
|
+
{ position: 2, title_pattern: "Review authz, data access, and dependency risks in {target}", priority: "critical", tags: ["security", "review"], depends_on_positions: [0] },
|
|
9071
|
+
{ position: 3, title_pattern: "Fix critical security findings in {target}", priority: "critical", tags: ["security", "fix"], depends_on_positions: [1, 2] },
|
|
9072
|
+
{ position: 4, title_pattern: "Retest {target} security fixes", priority: "high", tags: ["security", "verification"], depends_on_positions: [3] },
|
|
9073
|
+
{ position: 5, title_pattern: "Write local security review report for {target}", priority: "medium", tags: ["security", "report"], depends_on_positions: [4] }
|
|
9074
|
+
]
|
|
9075
|
+
},
|
|
9076
|
+
{
|
|
9077
|
+
name: "release",
|
|
9078
|
+
description: "Prepare, verify, publish, install, and smoke test a package release.",
|
|
9079
|
+
category: "release",
|
|
9080
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9081
|
+
variables: [
|
|
9082
|
+
{ name: "package", required: true, description: "Package name" },
|
|
9083
|
+
{ name: "version", required: false, default: "patch", description: "Release version or bump type" }
|
|
9084
|
+
],
|
|
9085
|
+
tasks: [
|
|
9086
|
+
{ position: 0, title_pattern: "Prepare {package} {version} release notes", priority: "medium", tags: ["release", "docs"] },
|
|
9087
|
+
{ position: 1, title_pattern: "Run full tests for {package}", priority: "critical", tags: ["release", "test"] },
|
|
9088
|
+
{ position: 2, title_pattern: "Run build and release verification for {package}", priority: "critical", tags: ["release", "verification"], depends_on_positions: [1] },
|
|
9089
|
+
{ position: 3, title_pattern: "Scan {package} release diff for secrets", priority: "critical", tags: ["release", "security"], depends_on_positions: [2] },
|
|
9090
|
+
{ position: 4, title_pattern: "Publish {package} {version}", priority: "high", tags: ["release", "publish"], depends_on_positions: [3] },
|
|
9091
|
+
{ position: 5, title_pattern: "Install and smoke test published {package}", priority: "high", tags: ["release", "smoke"], depends_on_positions: [4] }
|
|
9092
|
+
]
|
|
9093
|
+
},
|
|
9094
|
+
{
|
|
9095
|
+
name: "migration",
|
|
9096
|
+
description: "Plan, test, apply, and verify a schema or data migration.",
|
|
9097
|
+
category: "migration",
|
|
9098
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9099
|
+
variables: [{ name: "migration", required: true, description: "Migration name or objective" }],
|
|
9100
|
+
tasks: [
|
|
9101
|
+
{ position: 0, title_pattern: "Design migration plan for {migration}", priority: "critical", tags: ["migration", "plan"] },
|
|
9102
|
+
{ position: 1, title_pattern: "Write rollback plan for {migration}", priority: "critical", tags: ["migration", "rollback"], depends_on_positions: [0] },
|
|
9103
|
+
{ position: 2, title_pattern: "Implement migration {migration}", priority: "critical", tags: ["migration", "implementation"], depends_on_positions: [0] },
|
|
9104
|
+
{ position: 3, title_pattern: "Test migration {migration} on fixture data", priority: "high", tags: ["migration", "test"], depends_on_positions: [2] },
|
|
9105
|
+
{ position: 4, title_pattern: "Run migration {migration} verification and drift checks", priority: "high", tags: ["migration", "verification"], depends_on_positions: [1, 3] },
|
|
9106
|
+
{ position: 5, title_pattern: "Document migration {migration} evidence", priority: "medium", tags: ["migration", "docs"], depends_on_positions: [4] }
|
|
9107
|
+
]
|
|
9108
|
+
},
|
|
9109
|
+
{
|
|
9110
|
+
name: "incident",
|
|
9111
|
+
description: "Triage, mitigate, repair, verify, and retrospect an incident.",
|
|
9112
|
+
category: "incident",
|
|
9113
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9114
|
+
variables: [{ name: "incident", required: true, description: "Incident summary" }],
|
|
9115
|
+
tasks: [
|
|
9116
|
+
{ position: 0, title_pattern: "Triage incident: {incident}", priority: "critical", tags: ["incident", "triage"] },
|
|
9117
|
+
{ position: 1, title_pattern: "Mitigate customer impact for {incident}", priority: "critical", tags: ["incident", "mitigation"], depends_on_positions: [0] },
|
|
9118
|
+
{ position: 2, title_pattern: "Diagnose root cause for {incident}", priority: "critical", tags: ["incident", "diagnosis"], depends_on_positions: [0] },
|
|
9119
|
+
{ position: 3, title_pattern: "Implement durable repair for {incident}", priority: "critical", tags: ["incident", "repair"], depends_on_positions: [2] },
|
|
9120
|
+
{ position: 4, title_pattern: "Verify recovery from {incident}", priority: "high", tags: ["incident", "verification"], depends_on_positions: [1, 3] },
|
|
9121
|
+
{ position: 5, title_pattern: "Write retrospective for {incident}", priority: "medium", tags: ["incident", "retro"], depends_on_positions: [4] }
|
|
9122
|
+
]
|
|
9123
|
+
},
|
|
9124
|
+
{
|
|
9125
|
+
name: "docs-refresh",
|
|
9126
|
+
description: "Audit, update, validate, and publish documentation changes.",
|
|
9127
|
+
category: "docs",
|
|
9128
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9129
|
+
variables: [{ name: "area", required: true, description: "Documentation area or product surface" }],
|
|
9130
|
+
tasks: [
|
|
9131
|
+
{ position: 0, title_pattern: "Audit current docs for {area}", priority: "medium", tags: ["docs", "audit"] },
|
|
9132
|
+
{ position: 1, title_pattern: "Update examples and commands for {area}", priority: "medium", tags: ["docs", "examples"], depends_on_positions: [0] },
|
|
9133
|
+
{ position: 2, title_pattern: "Validate docs snippets for {area}", priority: "high", tags: ["docs", "verification"], depends_on_positions: [1] },
|
|
9134
|
+
{ position: 3, title_pattern: "Refresh screenshots or generated artifacts for {area}", priority: "medium", tags: ["docs", "assets"], depends_on_positions: [1] },
|
|
9135
|
+
{ position: 4, title_pattern: "Publish docs refresh for {area}", priority: "medium", tags: ["docs", "release"], depends_on_positions: [2, 3] }
|
|
9136
|
+
]
|
|
9137
|
+
},
|
|
9138
|
+
{
|
|
9139
|
+
name: "qa",
|
|
9140
|
+
description: "Build a focused QA plan, execute checks, file defects, and sign off.",
|
|
9141
|
+
category: "qa",
|
|
9142
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9143
|
+
variables: [{ name: "target", required: true, description: "Feature, release, or workflow under QA" }],
|
|
9144
|
+
tasks: [
|
|
9145
|
+
{ position: 0, title_pattern: "Create QA matrix for {target}", priority: "high", tags: ["qa", "plan"] },
|
|
9146
|
+
{ position: 1, title_pattern: "Run happy-path QA for {target}", priority: "high", tags: ["qa", "manual"], depends_on_positions: [0] },
|
|
9147
|
+
{ position: 2, title_pattern: "Run edge-case QA for {target}", priority: "high", tags: ["qa", "edge-cases"], depends_on_positions: [0] },
|
|
9148
|
+
{ position: 3, title_pattern: "File and dedupe QA defects for {target}", priority: "medium", tags: ["qa", "bugs"], depends_on_positions: [1, 2] },
|
|
9149
|
+
{ position: 4, title_pattern: "Verify QA fixes for {target}", priority: "high", tags: ["qa", "verification"], depends_on_positions: [3] },
|
|
9150
|
+
{ position: 5, title_pattern: "Record QA signoff for {target}", priority: "medium", tags: ["qa", "signoff"], depends_on_positions: [4] }
|
|
9151
|
+
]
|
|
9152
|
+
},
|
|
8928
9153
|
{
|
|
8929
9154
|
name: "open-source-project",
|
|
8930
9155
|
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
9156
|
+
category: "open-source",
|
|
9157
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
8931
9158
|
variables: [
|
|
8932
9159
|
{ name: "name", required: true, description: "Service name" },
|
|
8933
9160
|
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
@@ -8947,45 +9174,6 @@ var init_builtin_templates = __esm(() => {
|
|
|
8947
9174
|
{ position: 11, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
8948
9175
|
{ position: 12, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [11] }
|
|
8949
9176
|
]
|
|
8950
|
-
},
|
|
8951
|
-
{
|
|
8952
|
-
name: "bug-fix",
|
|
8953
|
-
description: "Standard bug fix workflow",
|
|
8954
|
-
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
8955
|
-
tasks: [
|
|
8956
|
-
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical" },
|
|
8957
|
-
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", depends_on_positions: [0] },
|
|
8958
|
-
{ position: 2, title_pattern: "Implement fix for {bug}", priority: "critical", depends_on_positions: [1] },
|
|
8959
|
-
{ position: 3, title_pattern: "Write regression test for {bug}", priority: "high", depends_on_positions: [2] },
|
|
8960
|
-
{ position: 4, title_pattern: "Publish fix and verify in production", priority: "high", depends_on_positions: [3] }
|
|
8961
|
-
]
|
|
8962
|
-
},
|
|
8963
|
-
{
|
|
8964
|
-
name: "feature",
|
|
8965
|
-
description: "Standard feature development workflow",
|
|
8966
|
-
variables: [{ name: "feature", required: true }, { name: "scope", required: false, default: "medium" }],
|
|
8967
|
-
tasks: [
|
|
8968
|
-
{ position: 0, title_pattern: "Write spec for {feature}", priority: "high" },
|
|
8969
|
-
{ position: 1, title_pattern: "Design implementation approach for {feature}", priority: "high", depends_on_positions: [0] },
|
|
8970
|
-
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", depends_on_positions: [1] },
|
|
8971
|
-
{ position: 3, title_pattern: "Write tests for {feature}", priority: "high", depends_on_positions: [2] },
|
|
8972
|
-
{ position: 4, title_pattern: "Code review for {feature}", priority: "medium", depends_on_positions: [3] },
|
|
8973
|
-
{ position: 5, title_pattern: "Update docs for {feature}", priority: "medium", depends_on_positions: [2] },
|
|
8974
|
-
{ position: 6, title_pattern: "Deploy {feature}", priority: "high", depends_on_positions: [4] }
|
|
8975
|
-
]
|
|
8976
|
-
},
|
|
8977
|
-
{
|
|
8978
|
-
name: "security-audit",
|
|
8979
|
-
description: "Security audit workflow",
|
|
8980
|
-
variables: [{ name: "target", required: true }],
|
|
8981
|
-
tasks: [
|
|
8982
|
-
{ position: 0, title_pattern: "Scan {target} for vulnerabilities", priority: "critical" },
|
|
8983
|
-
{ position: 1, title_pattern: "Review {target} security findings", priority: "critical", depends_on_positions: [0] },
|
|
8984
|
-
{ position: 2, title_pattern: "Fix critical issues in {target}", priority: "critical", depends_on_positions: [1] },
|
|
8985
|
-
{ position: 3, title_pattern: "Retest {target} after fixes", priority: "high", depends_on_positions: [2] },
|
|
8986
|
-
{ position: 4, title_pattern: "Write security report for {target}", priority: "medium", depends_on_positions: [3] },
|
|
8987
|
-
{ position: 5, title_pattern: "Close audit for {target}", priority: "low", depends_on_positions: [4] }
|
|
8988
|
-
]
|
|
8989
9177
|
}
|
|
8990
9178
|
];
|
|
8991
9179
|
});
|
|
@@ -9253,7 +9441,7 @@ function registerPlanTemplateCommands(program2) {
|
|
|
9253
9441
|
console.log(` ${chalk3.dim(t.id.slice(0, 8))} ${chalk3.bold(t.name)} ${chalk3.cyan(`"${t.title_pattern}"`)} ${chalk3.yellow(t.priority)}${vars}`);
|
|
9254
9442
|
}
|
|
9255
9443
|
});
|
|
9256
|
-
program2.command("template-init").alias("templates-init").description("Initialize
|
|
9444
|
+
program2.command("template-init").alias("templates-init").description("Initialize the bundled local template library").action(async () => {
|
|
9257
9445
|
const globalOpts = program2.opts();
|
|
9258
9446
|
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
9259
9447
|
const result = initBuiltinTemplates2();
|
|
@@ -9267,6 +9455,52 @@ function registerPlanTemplateCommands(program2) {
|
|
|
9267
9455
|
console.log(chalk3.green(`Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.`));
|
|
9268
9456
|
}
|
|
9269
9457
|
});
|
|
9458
|
+
program2.command("template-library").alias("templates-library").description("List, show, or write the bundled local template library as editable JSON files").option("--show <name>", "Show one bundled template as JSON").option("--write <dir>", "Write all bundled templates to editable JSON files").action(async (opts) => {
|
|
9459
|
+
const globalOpts = program2.opts();
|
|
9460
|
+
const {
|
|
9461
|
+
exportBuiltinTemplate: exportBuiltinTemplate2,
|
|
9462
|
+
listBuiltinTemplates: listBuiltinTemplates2,
|
|
9463
|
+
writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2
|
|
9464
|
+
} = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
9465
|
+
try {
|
|
9466
|
+
if (opts.show) {
|
|
9467
|
+
const template = exportBuiltinTemplate2(opts.show);
|
|
9468
|
+
output(template, true);
|
|
9469
|
+
return;
|
|
9470
|
+
}
|
|
9471
|
+
if (opts.write) {
|
|
9472
|
+
const result = writeBuiltinTemplateFiles2(opts.write);
|
|
9473
|
+
if (globalOpts.json) {
|
|
9474
|
+
output(result, true);
|
|
9475
|
+
return;
|
|
9476
|
+
}
|
|
9477
|
+
console.log(chalk3.green(`Wrote ${result.written} editable template file(s) to ${result.directory}`));
|
|
9478
|
+
for (const file of result.files)
|
|
9479
|
+
console.log(chalk3.dim(` ${file}`));
|
|
9480
|
+
return;
|
|
9481
|
+
}
|
|
9482
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
9483
|
+
name: template.name,
|
|
9484
|
+
description: template.description,
|
|
9485
|
+
category: template.category,
|
|
9486
|
+
version: template.version,
|
|
9487
|
+
variables: template.variables,
|
|
9488
|
+
task_count: template.tasks.length
|
|
9489
|
+
}));
|
|
9490
|
+
if (globalOpts.json) {
|
|
9491
|
+
output(templates, true);
|
|
9492
|
+
return;
|
|
9493
|
+
}
|
|
9494
|
+
console.log(chalk3.bold(`${templates.length} bundled local template(s):
|
|
9495
|
+
`));
|
|
9496
|
+
for (const template of templates) {
|
|
9497
|
+
console.log(` ${chalk3.bold(template.name)} ${chalk3.dim(`[${template.category}]`)} ${chalk3.yellow(`${template.task_count} tasks`)}`);
|
|
9498
|
+
console.log(chalk3.dim(` ${template.description}`));
|
|
9499
|
+
}
|
|
9500
|
+
} catch (e) {
|
|
9501
|
+
handleError(e);
|
|
9502
|
+
}
|
|
9503
|
+
});
|
|
9270
9504
|
program2.command("template-preview <id>").alias("templates-preview").description("Preview a template without creating tasks \u2014 shows resolved titles, deps, and priorities").option("--var <vars...>", "Variable substitution in key=value format (e.g. --var name=invoices)").action(async (id, opts) => {
|
|
9271
9505
|
const globalOpts = program2.opts();
|
|
9272
9506
|
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
@@ -9432,6 +9666,101 @@ var init_comments = __esm(() => {
|
|
|
9432
9666
|
init_tasks();
|
|
9433
9667
|
});
|
|
9434
9668
|
|
|
9669
|
+
// src/lib/local-fields.ts
|
|
9670
|
+
function normalizeList(values) {
|
|
9671
|
+
return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
9672
|
+
}
|
|
9673
|
+
function metadataFields(task) {
|
|
9674
|
+
const value = task.metadata[LOCAL_FIELDS_KEY];
|
|
9675
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9676
|
+
}
|
|
9677
|
+
function sameCustomValue(actual, expected) {
|
|
9678
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
9679
|
+
}
|
|
9680
|
+
function hasOwnField(fields, key) {
|
|
9681
|
+
return Object.prototype.hasOwnProperty.call(fields, key);
|
|
9682
|
+
}
|
|
9683
|
+
function getTaskLocalFields(taskId, db) {
|
|
9684
|
+
const d = db || getDatabase();
|
|
9685
|
+
const task = getTask(taskId, d);
|
|
9686
|
+
if (!task)
|
|
9687
|
+
throw new TaskNotFoundError(taskId);
|
|
9688
|
+
const fields = metadataFields(task);
|
|
9689
|
+
return {
|
|
9690
|
+
labels: normalizeList(fields.labels),
|
|
9691
|
+
priority: task.priority,
|
|
9692
|
+
severity: typeof fields.severity === "string" ? fields.severity : null,
|
|
9693
|
+
owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
|
|
9694
|
+
area: typeof fields.area === "string" ? fields.area : null,
|
|
9695
|
+
custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
|
|
9696
|
+
};
|
|
9697
|
+
}
|
|
9698
|
+
function setTaskLocalFields(taskId, input, db) {
|
|
9699
|
+
const d = db || getDatabase();
|
|
9700
|
+
const task = getTask(taskId, d);
|
|
9701
|
+
if (!task)
|
|
9702
|
+
throw new TaskNotFoundError(taskId);
|
|
9703
|
+
const currentFields = getTaskLocalFields(taskId, d);
|
|
9704
|
+
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
9705
|
+
const custom = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
9706
|
+
const nextFields = {
|
|
9707
|
+
labels,
|
|
9708
|
+
priority: input.priority || task.priority,
|
|
9709
|
+
severity: input.severity !== undefined ? input.severity : currentFields.severity,
|
|
9710
|
+
owner: input.owner !== undefined ? input.owner : currentFields.owner,
|
|
9711
|
+
area: input.area !== undefined ? input.area : currentFields.area,
|
|
9712
|
+
custom
|
|
9713
|
+
};
|
|
9714
|
+
const nextMetadata = {
|
|
9715
|
+
...task.metadata,
|
|
9716
|
+
[LOCAL_FIELDS_KEY]: nextFields
|
|
9717
|
+
};
|
|
9718
|
+
const previousLabels = new Set(currentFields.labels);
|
|
9719
|
+
const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
|
|
9720
|
+
const updates = {
|
|
9721
|
+
version: task.version,
|
|
9722
|
+
priority: input.priority,
|
|
9723
|
+
tags: nextTags,
|
|
9724
|
+
metadata: nextMetadata
|
|
9725
|
+
};
|
|
9726
|
+
if (input.owner !== undefined)
|
|
9727
|
+
updates.assigned_to = nextFields.owner;
|
|
9728
|
+
return updateTask(taskId, updates, d);
|
|
9729
|
+
}
|
|
9730
|
+
function queryTasksByLocalFields(query, db) {
|
|
9731
|
+
const d = db || getDatabase();
|
|
9732
|
+
const tasks = listTasks({
|
|
9733
|
+
priority: query.priority,
|
|
9734
|
+
tags: query.labels,
|
|
9735
|
+
limit: 1e4
|
|
9736
|
+
}, d);
|
|
9737
|
+
const matches = tasks.filter((task) => {
|
|
9738
|
+
const fields = getTaskLocalFields(task.id, d);
|
|
9739
|
+
if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
|
|
9740
|
+
return false;
|
|
9741
|
+
if (query.severity && fields.severity !== query.severity)
|
|
9742
|
+
return false;
|
|
9743
|
+
if (query.owner && fields.owner !== query.owner)
|
|
9744
|
+
return false;
|
|
9745
|
+
if (query.area && fields.area !== query.area)
|
|
9746
|
+
return false;
|
|
9747
|
+
if (query.custom) {
|
|
9748
|
+
for (const [key, expected] of Object.entries(query.custom)) {
|
|
9749
|
+
if (!sameCustomValue(fields.custom[key], expected))
|
|
9750
|
+
return false;
|
|
9751
|
+
}
|
|
9752
|
+
}
|
|
9753
|
+
return true;
|
|
9754
|
+
});
|
|
9755
|
+
return matches.slice(0, query.limit || 100);
|
|
9756
|
+
}
|
|
9757
|
+
var LOCAL_FIELDS_KEY = "local_fields";
|
|
9758
|
+
var init_local_fields = __esm(() => {
|
|
9759
|
+
init_database();
|
|
9760
|
+
init_tasks();
|
|
9761
|
+
init_types();
|
|
9762
|
+
});
|
|
9763
|
+
|
|
9435
9764
|
// src/lib/search.ts
|
|
9436
9765
|
var exports_search = {};
|
|
9437
9766
|
__export(exports_search, {
|
|
@@ -9547,17 +9876,358 @@ var init_search = __esm(() => {
|
|
|
9547
9876
|
init_database();
|
|
9548
9877
|
});
|
|
9549
9878
|
|
|
9879
|
+
// src/lib/saved-search-views.ts
|
|
9880
|
+
var exports_saved_search_views = {};
|
|
9881
|
+
__export(exports_saved_search_views, {
|
|
9882
|
+
saveSearchView: () => saveSearchView,
|
|
9883
|
+
runSearchView: () => runSearchView,
|
|
9884
|
+
runSavedSearch: () => runSavedSearch,
|
|
9885
|
+
normalizeScope: () => normalizeScope,
|
|
9886
|
+
listSearchViews: () => listSearchViews,
|
|
9887
|
+
getSearchView: () => getSearchView,
|
|
9888
|
+
deleteSearchView: () => deleteSearchView
|
|
9889
|
+
});
|
|
9890
|
+
function parseFilters(value) {
|
|
9891
|
+
if (!value)
|
|
9892
|
+
return {};
|
|
9893
|
+
try {
|
|
9894
|
+
const parsed = JSON.parse(value);
|
|
9895
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
9896
|
+
} catch {
|
|
9897
|
+
return {};
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
function rowToSavedSearchView(row) {
|
|
9901
|
+
return {
|
|
9902
|
+
...row,
|
|
9903
|
+
scope: normalizeScope(row.scope),
|
|
9904
|
+
filters: parseFilters(row.filters)
|
|
9905
|
+
};
|
|
9906
|
+
}
|
|
9907
|
+
function normalizeScope(scope) {
|
|
9908
|
+
if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
|
|
9909
|
+
return scope;
|
|
9910
|
+
}
|
|
9911
|
+
return "tasks";
|
|
9912
|
+
}
|
|
9913
|
+
function normalizeName(name) {
|
|
9914
|
+
const normalized = name.trim();
|
|
9915
|
+
if (!normalized)
|
|
9916
|
+
throw new Error("Saved view name is required");
|
|
9917
|
+
return normalized;
|
|
9918
|
+
}
|
|
9919
|
+
function normalizeLimit(limit) {
|
|
9920
|
+
if (!Number.isFinite(limit) || !limit || limit <= 0)
|
|
9921
|
+
return 100;
|
|
9922
|
+
return Math.min(Math.trunc(limit), 1000);
|
|
9923
|
+
}
|
|
9924
|
+
function valuesList(value) {
|
|
9925
|
+
if (value === undefined)
|
|
9926
|
+
return [];
|
|
9927
|
+
return (Array.isArray(value) ? value : [value]).map((item) => item.trim()).filter(Boolean);
|
|
9928
|
+
}
|
|
9929
|
+
function addStatusFilter(sql, params, column, value) {
|
|
9930
|
+
const values = valuesList(value);
|
|
9931
|
+
if (values.length === 0)
|
|
9932
|
+
return sql;
|
|
9933
|
+
params.push(...values);
|
|
9934
|
+
return `${sql} AND ${column} IN (${values.map(() => "?").join(",")})`;
|
|
9935
|
+
}
|
|
9936
|
+
function addDateFilter(sql, params, column, value) {
|
|
9937
|
+
if (!value)
|
|
9938
|
+
return sql;
|
|
9939
|
+
params.push(value);
|
|
9940
|
+
return `${sql} AND ${column} > ?`;
|
|
9941
|
+
}
|
|
9942
|
+
function likePattern(query) {
|
|
9943
|
+
const trimmed = query?.trim();
|
|
9944
|
+
if (!trimmed || trimmed === "*")
|
|
9945
|
+
return null;
|
|
9946
|
+
return `%${trimmed}%`;
|
|
9947
|
+
}
|
|
9948
|
+
function parseJsonObject(value) {
|
|
9949
|
+
if (!value)
|
|
9950
|
+
return {};
|
|
9951
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
9952
|
+
return value;
|
|
9953
|
+
if (typeof value !== "string")
|
|
9954
|
+
return {};
|
|
9955
|
+
try {
|
|
9956
|
+
const parsed = JSON.parse(value);
|
|
9957
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
9958
|
+
} catch {
|
|
9959
|
+
return {};
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
function rowToTaskRun(row) {
|
|
9963
|
+
return { ...row, metadata: parseJsonObject(row.metadata) };
|
|
9964
|
+
}
|
|
9965
|
+
function taskMatchesSavedFilters(task, filters, db) {
|
|
9966
|
+
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
9967
|
+
return false;
|
|
9968
|
+
if (filters.tags && !filters.tags.every((tag) => task.tags.includes(tag)))
|
|
9969
|
+
return false;
|
|
9970
|
+
if (filters.depends_on) {
|
|
9971
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(task.id, filters.depends_on);
|
|
9972
|
+
if (!row)
|
|
9973
|
+
return false;
|
|
9974
|
+
}
|
|
9975
|
+
if (filters.blocks) {
|
|
9976
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(filters.blocks, task.id);
|
|
9977
|
+
if (!row)
|
|
9978
|
+
return false;
|
|
9979
|
+
}
|
|
9980
|
+
return true;
|
|
9981
|
+
}
|
|
9982
|
+
function searchTaskEntities(filters, db) {
|
|
9983
|
+
let tasks;
|
|
9984
|
+
if (filters.local_fields) {
|
|
9985
|
+
const localMatches = queryTasksByLocalFields({ ...filters.local_fields, limit: 1e4 }, db);
|
|
9986
|
+
const allowedIds = new Set(localMatches.map((task) => task.id));
|
|
9987
|
+
tasks = searchTasks({
|
|
9988
|
+
query: filters.query,
|
|
9989
|
+
project_id: filters.project_id,
|
|
9990
|
+
task_list_id: filters.task_list_id,
|
|
9991
|
+
status: filters.status,
|
|
9992
|
+
priority: filters.priority,
|
|
9993
|
+
assigned_to: filters.assigned_to,
|
|
9994
|
+
agent_id: filters.agent_id,
|
|
9995
|
+
created_after: filters.created_after,
|
|
9996
|
+
updated_after: filters.updated_after,
|
|
9997
|
+
has_dependencies: filters.has_dependencies,
|
|
9998
|
+
is_blocked: filters.is_blocked
|
|
9999
|
+
}, undefined, undefined, db).filter((task) => allowedIds.has(task.id));
|
|
10000
|
+
} else {
|
|
10001
|
+
tasks = searchTasks({
|
|
10002
|
+
query: filters.query,
|
|
10003
|
+
project_id: filters.project_id,
|
|
10004
|
+
task_list_id: filters.task_list_id,
|
|
10005
|
+
status: filters.status,
|
|
10006
|
+
priority: filters.priority,
|
|
10007
|
+
assigned_to: filters.assigned_to,
|
|
10008
|
+
agent_id: filters.agent_id,
|
|
10009
|
+
created_after: filters.created_after,
|
|
10010
|
+
updated_after: filters.updated_after,
|
|
10011
|
+
has_dependencies: filters.has_dependencies,
|
|
10012
|
+
is_blocked: filters.is_blocked
|
|
10013
|
+
}, undefined, undefined, db);
|
|
10014
|
+
}
|
|
10015
|
+
return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
|
|
10016
|
+
}
|
|
10017
|
+
function searchProjects(filters, db) {
|
|
10018
|
+
const params = [];
|
|
10019
|
+
let sql = "SELECT * FROM projects WHERE 1=1";
|
|
10020
|
+
if (filters.project_id) {
|
|
10021
|
+
sql += " AND id = ?";
|
|
10022
|
+
params.push(filters.project_id);
|
|
10023
|
+
}
|
|
10024
|
+
const pattern = likePattern(filters.query);
|
|
10025
|
+
if (pattern) {
|
|
10026
|
+
sql += " AND (name LIKE ? OR description LIKE ? OR path LIKE ?)";
|
|
10027
|
+
params.push(pattern, pattern, pattern);
|
|
10028
|
+
}
|
|
10029
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
10030
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
10031
|
+
sql += " ORDER BY name LIMIT ?";
|
|
10032
|
+
params.push(normalizeLimit(filters.limit));
|
|
10033
|
+
return db.query(sql).all(...params);
|
|
10034
|
+
}
|
|
10035
|
+
function searchPlans(filters, db) {
|
|
10036
|
+
const params = [];
|
|
10037
|
+
let sql = "SELECT * FROM plans WHERE 1=1";
|
|
10038
|
+
if (filters.project_id) {
|
|
10039
|
+
sql += " AND project_id = ?";
|
|
10040
|
+
params.push(filters.project_id);
|
|
10041
|
+
}
|
|
10042
|
+
if (filters.task_list_id) {
|
|
10043
|
+
sql += " AND task_list_id = ?";
|
|
10044
|
+
params.push(filters.task_list_id);
|
|
10045
|
+
}
|
|
10046
|
+
if (filters.agent_id) {
|
|
10047
|
+
sql += " AND agent_id = ?";
|
|
10048
|
+
params.push(filters.agent_id);
|
|
10049
|
+
}
|
|
10050
|
+
sql = addStatusFilter(sql, params, "status", filters.status);
|
|
10051
|
+
const pattern = likePattern(filters.query);
|
|
10052
|
+
if (pattern) {
|
|
10053
|
+
sql += " AND (name LIKE ? OR description LIKE ?)";
|
|
10054
|
+
params.push(pattern, pattern);
|
|
10055
|
+
}
|
|
10056
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
10057
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
10058
|
+
sql += " ORDER BY updated_at DESC, created_at DESC LIMIT ?";
|
|
10059
|
+
params.push(normalizeLimit(filters.limit));
|
|
10060
|
+
return db.query(sql).all(...params);
|
|
10061
|
+
}
|
|
10062
|
+
function searchRuns(filters, db) {
|
|
10063
|
+
const params = [];
|
|
10064
|
+
let sql = `SELECT r.* FROM task_runs r
|
|
10065
|
+
JOIN tasks t ON t.id = r.task_id
|
|
10066
|
+
WHERE 1=1`;
|
|
10067
|
+
if (filters.project_id) {
|
|
10068
|
+
sql += " AND t.project_id = ?";
|
|
10069
|
+
params.push(filters.project_id);
|
|
10070
|
+
}
|
|
10071
|
+
if (filters.task_list_id) {
|
|
10072
|
+
sql += " AND t.task_list_id = ?";
|
|
10073
|
+
params.push(filters.task_list_id);
|
|
10074
|
+
}
|
|
10075
|
+
if (filters.plan_id) {
|
|
10076
|
+
sql += " AND t.plan_id = ?";
|
|
10077
|
+
params.push(filters.plan_id);
|
|
10078
|
+
}
|
|
10079
|
+
if (filters.task_id) {
|
|
10080
|
+
sql += " AND r.task_id = ?";
|
|
10081
|
+
params.push(filters.task_id);
|
|
10082
|
+
}
|
|
10083
|
+
if (filters.agent_id) {
|
|
10084
|
+
sql += " AND r.agent_id = ?";
|
|
10085
|
+
params.push(filters.agent_id);
|
|
10086
|
+
}
|
|
10087
|
+
sql = addStatusFilter(sql, params, "r.status", filters.status);
|
|
10088
|
+
const pattern = likePattern(filters.query);
|
|
10089
|
+
if (pattern) {
|
|
10090
|
+
sql += " AND (r.title LIKE ? OR r.summary LIKE ? OR t.title LIKE ?)";
|
|
10091
|
+
params.push(pattern, pattern, pattern);
|
|
10092
|
+
}
|
|
10093
|
+
sql = addDateFilter(sql, params, "r.created_at", filters.created_after);
|
|
10094
|
+
sql = addDateFilter(sql, params, "r.updated_at", filters.updated_after);
|
|
10095
|
+
sql += " ORDER BY r.started_at DESC, r.created_at DESC LIMIT ?";
|
|
10096
|
+
params.push(normalizeLimit(filters.limit));
|
|
10097
|
+
return db.query(sql).all(...params).map(rowToTaskRun);
|
|
10098
|
+
}
|
|
10099
|
+
function searchComments(filters, db) {
|
|
10100
|
+
const params = [];
|
|
10101
|
+
let sql = `SELECT c.* FROM task_comments c
|
|
10102
|
+
JOIN tasks t ON t.id = c.task_id
|
|
10103
|
+
WHERE 1=1`;
|
|
10104
|
+
if (filters.project_id) {
|
|
10105
|
+
sql += " AND t.project_id = ?";
|
|
10106
|
+
params.push(filters.project_id);
|
|
10107
|
+
}
|
|
10108
|
+
if (filters.task_list_id) {
|
|
10109
|
+
sql += " AND t.task_list_id = ?";
|
|
10110
|
+
params.push(filters.task_list_id);
|
|
10111
|
+
}
|
|
10112
|
+
if (filters.plan_id) {
|
|
10113
|
+
sql += " AND t.plan_id = ?";
|
|
10114
|
+
params.push(filters.plan_id);
|
|
10115
|
+
}
|
|
10116
|
+
if (filters.task_id) {
|
|
10117
|
+
sql += " AND c.task_id = ?";
|
|
10118
|
+
params.push(filters.task_id);
|
|
10119
|
+
}
|
|
10120
|
+
if (filters.agent_id) {
|
|
10121
|
+
sql += " AND c.agent_id = ?";
|
|
10122
|
+
params.push(filters.agent_id);
|
|
10123
|
+
}
|
|
10124
|
+
const pattern = likePattern(filters.query);
|
|
10125
|
+
if (pattern) {
|
|
10126
|
+
sql += " AND (c.content LIKE ? OR t.title LIKE ?)";
|
|
10127
|
+
params.push(pattern, pattern);
|
|
10128
|
+
}
|
|
10129
|
+
sql = addDateFilter(sql, params, "c.created_at", filters.created_after);
|
|
10130
|
+
sql += " ORDER BY c.created_at DESC, c.id LIMIT ?";
|
|
10131
|
+
params.push(normalizeLimit(filters.limit));
|
|
10132
|
+
return db.query(sql).all(...params);
|
|
10133
|
+
}
|
|
10134
|
+
function toResults(entityType, rows) {
|
|
10135
|
+
return rows.map((entity) => ({ entity_type: entityType, entity }));
|
|
10136
|
+
}
|
|
10137
|
+
function runSavedSearch(filters = {}, scope = "tasks", db) {
|
|
10138
|
+
const d = db || getDatabase();
|
|
10139
|
+
const normalizedScope = normalizeScope(scope);
|
|
10140
|
+
const scopes = normalizedScope === "all" ? ["tasks", "projects", "plans", "runs", "comments"] : [normalizedScope];
|
|
10141
|
+
const results = [];
|
|
10142
|
+
for (const item of scopes) {
|
|
10143
|
+
if (item === "tasks")
|
|
10144
|
+
results.push(...toResults("tasks", searchTaskEntities(filters, d)));
|
|
10145
|
+
if (item === "projects")
|
|
10146
|
+
results.push(...toResults("projects", searchProjects(filters, d)));
|
|
10147
|
+
if (item === "plans")
|
|
10148
|
+
results.push(...toResults("plans", searchPlans(filters, d)));
|
|
10149
|
+
if (item === "runs")
|
|
10150
|
+
results.push(...toResults("runs", searchRuns(filters, d)));
|
|
10151
|
+
if (item === "comments")
|
|
10152
|
+
results.push(...toResults("comments", searchComments(filters, d)));
|
|
10153
|
+
}
|
|
10154
|
+
return {
|
|
10155
|
+
scope: normalizedScope,
|
|
10156
|
+
filters,
|
|
10157
|
+
count: results.length,
|
|
10158
|
+
results: results.slice(0, normalizeLimit(filters.limit))
|
|
10159
|
+
};
|
|
10160
|
+
}
|
|
10161
|
+
function saveSearchView(input, db) {
|
|
10162
|
+
const d = db || getDatabase();
|
|
10163
|
+
const name = normalizeName(input.name);
|
|
10164
|
+
const timestamp = now();
|
|
10165
|
+
const existing = getSearchView(name, d);
|
|
10166
|
+
if (existing) {
|
|
10167
|
+
d.run(`UPDATE saved_search_views
|
|
10168
|
+
SET description = ?, scope = ?, filters = ?, updated_at = ?
|
|
10169
|
+
WHERE id = ?`, [
|
|
10170
|
+
input.description ?? existing.description,
|
|
10171
|
+
normalizeScope(input.scope ?? existing.scope),
|
|
10172
|
+
JSON.stringify(input.filters ?? existing.filters),
|
|
10173
|
+
timestamp,
|
|
10174
|
+
existing.id
|
|
10175
|
+
]);
|
|
10176
|
+
return getSearchView(existing.id, d);
|
|
10177
|
+
}
|
|
10178
|
+
const id = uuid();
|
|
10179
|
+
d.run(`INSERT INTO saved_search_views (id, name, description, scope, filters, created_at, updated_at)
|
|
10180
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
10181
|
+
id,
|
|
10182
|
+
name,
|
|
10183
|
+
input.description ?? null,
|
|
10184
|
+
normalizeScope(input.scope),
|
|
10185
|
+
JSON.stringify(input.filters ?? {}),
|
|
10186
|
+
timestamp,
|
|
10187
|
+
timestamp
|
|
10188
|
+
]);
|
|
10189
|
+
return getSearchView(id, d);
|
|
10190
|
+
}
|
|
10191
|
+
function getSearchView(idOrName, db) {
|
|
10192
|
+
const d = db || getDatabase();
|
|
10193
|
+
const row = d.query("SELECT * FROM saved_search_views WHERE id = ? OR name = ?").get(idOrName, idOrName);
|
|
10194
|
+
return row ? rowToSavedSearchView(row) : null;
|
|
10195
|
+
}
|
|
10196
|
+
function listSearchViews(scope, db) {
|
|
10197
|
+
const d = db || getDatabase();
|
|
10198
|
+
const normalizedScope = scope ? normalizeScope(scope) : null;
|
|
10199
|
+
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();
|
|
10200
|
+
return rows.map(rowToSavedSearchView);
|
|
10201
|
+
}
|
|
10202
|
+
function deleteSearchView(idOrName, db) {
|
|
10203
|
+
const d = db || getDatabase();
|
|
10204
|
+
const result = d.run("DELETE FROM saved_search_views WHERE id = ? OR name = ?", [idOrName, idOrName]);
|
|
10205
|
+
return result.changes > 0;
|
|
10206
|
+
}
|
|
10207
|
+
function runSearchView(idOrName, db) {
|
|
10208
|
+
const d = db || getDatabase();
|
|
10209
|
+
const view = getSearchView(idOrName, d);
|
|
10210
|
+
if (!view)
|
|
10211
|
+
throw new Error(`Saved search view not found: ${idOrName}`);
|
|
10212
|
+
return { ...runSavedSearch(view.filters, view.scope, d), view };
|
|
10213
|
+
}
|
|
10214
|
+
var init_saved_search_views = __esm(() => {
|
|
10215
|
+
init_database();
|
|
10216
|
+
init_local_fields();
|
|
10217
|
+
init_search();
|
|
10218
|
+
});
|
|
10219
|
+
|
|
9550
10220
|
// src/lib/claude-tasks.ts
|
|
9551
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as
|
|
9552
|
-
import { join as
|
|
10221
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
10222
|
+
import { join as join6 } from "path";
|
|
9553
10223
|
function getTaskListDir(taskListId) {
|
|
9554
|
-
return
|
|
10224
|
+
return join6(HOME, ".claude", "tasks", taskListId);
|
|
9555
10225
|
}
|
|
9556
10226
|
function readClaudeTask(dir, filename) {
|
|
9557
|
-
return readJsonFile(
|
|
10227
|
+
return readJsonFile(join6(dir, filename));
|
|
9558
10228
|
}
|
|
9559
10229
|
function writeClaudeTask(dir, task) {
|
|
9560
|
-
writeJsonFile(
|
|
10230
|
+
writeJsonFile(join6(dir, `${task.id}.json`), task);
|
|
9561
10231
|
}
|
|
9562
10232
|
function toClaudeStatus(status) {
|
|
9563
10233
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -9569,14 +10239,14 @@ function toSqliteStatus(status) {
|
|
|
9569
10239
|
return status;
|
|
9570
10240
|
}
|
|
9571
10241
|
function readPrefixCounter(dir) {
|
|
9572
|
-
const path =
|
|
10242
|
+
const path = join6(dir, ".prefix-counter");
|
|
9573
10243
|
if (!existsSync5(path))
|
|
9574
10244
|
return 0;
|
|
9575
10245
|
const val = parseInt(readFileSync3(path, "utf-8").trim(), 10);
|
|
9576
10246
|
return isNaN(val) ? 0 : val;
|
|
9577
10247
|
}
|
|
9578
10248
|
function writePrefixCounter(dir, value) {
|
|
9579
|
-
|
|
10249
|
+
writeFileSync3(join6(dir, ".prefix-counter"), String(value));
|
|
9580
10250
|
}
|
|
9581
10251
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
9582
10252
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -9612,7 +10282,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
9612
10282
|
const existingByTodosId = new Map;
|
|
9613
10283
|
const files = listJsonFiles(dir);
|
|
9614
10284
|
for (const f of files) {
|
|
9615
|
-
const path =
|
|
10285
|
+
const path = join6(dir, f);
|
|
9616
10286
|
const ct = readClaudeTask(dir, f);
|
|
9617
10287
|
if (ct?.metadata?.["todos_id"]) {
|
|
9618
10288
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -9719,7 +10389,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
9719
10389
|
}
|
|
9720
10390
|
for (const f of files) {
|
|
9721
10391
|
try {
|
|
9722
|
-
const filePath =
|
|
10392
|
+
const filePath = join6(dir, f);
|
|
9723
10393
|
const ct = readClaudeTask(dir, f);
|
|
9724
10394
|
if (!ct)
|
|
9725
10395
|
continue;
|
|
@@ -9793,26 +10463,26 @@ var init_claude_tasks = __esm(() => {
|
|
|
9793
10463
|
|
|
9794
10464
|
// src/lib/agent-tasks.ts
|
|
9795
10465
|
import { existsSync as existsSync6 } from "fs";
|
|
9796
|
-
import { join as
|
|
10466
|
+
import { join as join7 } from "path";
|
|
9797
10467
|
function getTodosGlobalDir2() {
|
|
9798
|
-
const newDir =
|
|
9799
|
-
const legacyDir =
|
|
10468
|
+
const newDir = join7(HOME, ".hasna", "todos");
|
|
10469
|
+
const legacyDir = join7(HOME, ".todos");
|
|
9800
10470
|
if (!existsSync6(newDir) && existsSync6(legacyDir))
|
|
9801
10471
|
return legacyDir;
|
|
9802
10472
|
return newDir;
|
|
9803
10473
|
}
|
|
9804
10474
|
function agentBaseDir(agent) {
|
|
9805
10475
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
9806
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
10476
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join7(getTodosGlobalDir2(), "agents");
|
|
9807
10477
|
}
|
|
9808
10478
|
function getTaskListDir2(agent, taskListId) {
|
|
9809
|
-
return
|
|
10479
|
+
return join7(agentBaseDir(agent), agent, taskListId);
|
|
9810
10480
|
}
|
|
9811
10481
|
function readAgentTask(dir, filename) {
|
|
9812
|
-
return readJsonFile(
|
|
10482
|
+
return readJsonFile(join7(dir, filename));
|
|
9813
10483
|
}
|
|
9814
10484
|
function writeAgentTask(dir, task) {
|
|
9815
|
-
writeJsonFile(
|
|
10485
|
+
writeJsonFile(join7(dir, `${task.id}.json`), task);
|
|
9816
10486
|
}
|
|
9817
10487
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
9818
10488
|
return {
|
|
@@ -9846,7 +10516,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
9846
10516
|
const existingByTodosId = new Map;
|
|
9847
10517
|
const files = listJsonFiles(dir);
|
|
9848
10518
|
for (const f of files) {
|
|
9849
|
-
const path =
|
|
10519
|
+
const path = join7(dir, f);
|
|
9850
10520
|
const at = readAgentTask(dir, f);
|
|
9851
10521
|
if (at?.metadata?.["todos_id"]) {
|
|
9852
10522
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -9939,7 +10609,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
9939
10609
|
}
|
|
9940
10610
|
for (const f of files) {
|
|
9941
10611
|
try {
|
|
9942
|
-
const filePath =
|
|
10612
|
+
const filePath = join7(dir, f);
|
|
9943
10613
|
const at = readAgentTask(dir, f);
|
|
9944
10614
|
if (!at)
|
|
9945
10615
|
continue;
|
|
@@ -10350,7 +11020,7 @@ __export(exports_extract, {
|
|
|
10350
11020
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
10351
11021
|
});
|
|
10352
11022
|
import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
10353
|
-
import { relative as relative3, resolve as resolve7, join as
|
|
11023
|
+
import { relative as relative3, resolve as resolve7, join as join8 } from "path";
|
|
10354
11024
|
function tagToPriority(tag) {
|
|
10355
11025
|
switch (tag) {
|
|
10356
11026
|
case "BUG":
|
|
@@ -10422,7 +11092,7 @@ function extractTodos(options, db) {
|
|
|
10422
11092
|
const files = collectFiles(basePath, extensions);
|
|
10423
11093
|
const allComments = [];
|
|
10424
11094
|
for (const file of files) {
|
|
10425
|
-
const fullPath = statSync3(basePath).isFile() ? basePath :
|
|
11095
|
+
const fullPath = statSync3(basePath).isFile() ? basePath : join8(basePath, file);
|
|
10426
11096
|
try {
|
|
10427
11097
|
const source = readFileSync5(fullPath, "utf-8");
|
|
10428
11098
|
const relPath = statSync3(basePath).isFile() ? relative3(resolve7(basePath, ".."), fullPath) : file;
|
|
@@ -10549,8 +11219,8 @@ var init_extract = __esm(() => {
|
|
|
10549
11219
|
|
|
10550
11220
|
// src/lib/artifact-store.ts
|
|
10551
11221
|
import { createHash as createHash2 } from "crypto";
|
|
10552
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
10553
|
-
import { dirname as dirname6, join as
|
|
11222
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
11223
|
+
import { dirname as dirname6, join as join9, resolve as resolve8 } from "path";
|
|
10554
11224
|
import { tmpdir } from "os";
|
|
10555
11225
|
function isInMemoryDb2(path) {
|
|
10556
11226
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -10562,15 +11232,15 @@ function artifactStoreRoot() {
|
|
|
10562
11232
|
return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
10563
11233
|
const dbPath = getDatabasePath();
|
|
10564
11234
|
if (isInMemoryDb2(dbPath))
|
|
10565
|
-
return
|
|
10566
|
-
return
|
|
11235
|
+
return join9(tmpdir(), "hasna-todos-artifacts");
|
|
11236
|
+
return join9(dirname6(resolve8(dbPath)), "artifacts");
|
|
10567
11237
|
}
|
|
10568
11238
|
function artifactStorePath(relativePath) {
|
|
10569
11239
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
10570
11240
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
10571
11241
|
throw new Error("Invalid artifact store path");
|
|
10572
11242
|
}
|
|
10573
|
-
return
|
|
11243
|
+
return join9(artifactStoreRoot(), normalized);
|
|
10574
11244
|
}
|
|
10575
11245
|
function sha256(buffer) {
|
|
10576
11246
|
return createHash2("sha256").update(buffer).digest("hex");
|
|
@@ -10628,11 +11298,11 @@ function storeArtifactContent(input) {
|
|
|
10628
11298
|
redactionStatus = "redacted";
|
|
10629
11299
|
}
|
|
10630
11300
|
const storedSha = sha256(storedBuffer);
|
|
10631
|
-
const relativePath =
|
|
11301
|
+
const relativePath = join9("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
10632
11302
|
const destination = artifactStorePath(relativePath);
|
|
10633
11303
|
if (!existsSync8(destination)) {
|
|
10634
|
-
|
|
10635
|
-
|
|
11304
|
+
mkdirSync5(dirname6(destination), { recursive: true });
|
|
11305
|
+
writeFileSync4(destination, storedBuffer);
|
|
10636
11306
|
}
|
|
10637
11307
|
const createdAt = input.created_at || new Date().toISOString();
|
|
10638
11308
|
const retentionDays = input.retention_days ?? null;
|
|
@@ -10749,8 +11419,8 @@ function importStoredArtifactContent(content) {
|
|
|
10749
11419
|
};
|
|
10750
11420
|
}
|
|
10751
11421
|
const destination = artifactStorePath(content.relative_path);
|
|
10752
|
-
|
|
10753
|
-
|
|
11422
|
+
mkdirSync5(dirname6(destination), { recursive: true });
|
|
11423
|
+
writeFileSync4(destination, buffer);
|
|
10754
11424
|
return {
|
|
10755
11425
|
id: content.artifact_id,
|
|
10756
11426
|
path: content.relative_path,
|
|
@@ -10786,7 +11456,7 @@ function packageSource(version) {
|
|
|
10786
11456
|
function emptyCounts() {
|
|
10787
11457
|
return Object.fromEntries(dataKeys.map((key) => [key, 0]));
|
|
10788
11458
|
}
|
|
10789
|
-
function
|
|
11459
|
+
function parseJsonObject2(value) {
|
|
10790
11460
|
if (!value)
|
|
10791
11461
|
return {};
|
|
10792
11462
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -10832,22 +11502,25 @@ function rowToTask3(row) {
|
|
|
10832
11502
|
return {
|
|
10833
11503
|
...row,
|
|
10834
11504
|
tags: parseJsonArray(row.tags),
|
|
10835
|
-
metadata:
|
|
11505
|
+
metadata: parseJsonObject2(row.metadata),
|
|
10836
11506
|
requires_approval: Boolean(row.requires_approval)
|
|
10837
11507
|
};
|
|
10838
11508
|
}
|
|
10839
11509
|
function rowToTaskList2(row) {
|
|
10840
|
-
return { ...row, metadata:
|
|
11510
|
+
return { ...row, metadata: parseJsonObject2(row.metadata) };
|
|
10841
11511
|
}
|
|
10842
11512
|
function rowWithMetadata(row) {
|
|
10843
|
-
return { ...row, metadata:
|
|
11513
|
+
return { ...row, metadata: parseJsonObject2(row.metadata) };
|
|
10844
11514
|
}
|
|
10845
11515
|
function rowToRunEvent(row) {
|
|
10846
|
-
return { ...row, data:
|
|
11516
|
+
return { ...row, data: parseJsonObject2(row.data) };
|
|
10847
11517
|
}
|
|
10848
11518
|
function rowToCommit2(row) {
|
|
10849
11519
|
return { ...row, files_changed: row.files_changed ? parseJsonArray(row.files_changed) : null };
|
|
10850
11520
|
}
|
|
11521
|
+
function rowToSavedView(row) {
|
|
11522
|
+
return { ...row, filters: parseJsonObject2(row.filters) };
|
|
11523
|
+
}
|
|
10851
11524
|
function bridgeStats(data) {
|
|
10852
11525
|
return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
|
|
10853
11526
|
}
|
|
@@ -10871,7 +11544,8 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
10871
11544
|
task_files: queryByTaskIds(d, "SELECT * FROM task_files WHERE task_id IN (__TASK_IDS__) ORDER BY path, id", taskIds),
|
|
10872
11545
|
task_commits: queryByTaskIds(d, "SELECT * FROM task_commits WHERE task_id IN (__TASK_IDS__) ORDER BY created_at, id", taskIds).map(rowToCommit2),
|
|
10873
11546
|
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)
|
|
11547
|
+
task_verifications: queryByTaskIds(d, "SELECT * FROM task_verifications WHERE task_id IN (__TASK_IDS__) ORDER BY run_at, id", taskIds),
|
|
11548
|
+
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
11549
|
};
|
|
10876
11550
|
const artifactContents = data.run_artifacts.map((artifact) => exportStoredArtifactContent({
|
|
10877
11551
|
id: artifact.id,
|
|
@@ -10910,6 +11584,8 @@ function validateLocalBridgeBundle(value) {
|
|
|
10910
11584
|
issues.push("data must be an object");
|
|
10911
11585
|
} else {
|
|
10912
11586
|
for (const key of dataKeys) {
|
|
11587
|
+
if (key === "saved_views" && data[key] === undefined)
|
|
11588
|
+
continue;
|
|
10913
11589
|
if (!Array.isArray(data[key]))
|
|
10914
11590
|
issues.push(`data.${key} must be an array`);
|
|
10915
11591
|
}
|
|
@@ -11082,7 +11758,8 @@ function importLocalBridgeBundle(bundle, options = {}, db) {
|
|
|
11082
11758
|
const conflictStrategy = options.conflictStrategy ?? "skip";
|
|
11083
11759
|
const data = {
|
|
11084
11760
|
...bundle.data,
|
|
11085
|
-
tasks: sortedTasks(bundle.data.tasks)
|
|
11761
|
+
tasks: sortedTasks(bundle.data.tasks),
|
|
11762
|
+
saved_views: bundle.data.saved_views ?? []
|
|
11086
11763
|
};
|
|
11087
11764
|
for (const key of dataKeys) {
|
|
11088
11765
|
for (const row of data[key]) {
|
|
@@ -11153,7 +11830,8 @@ var init_local_bridge = __esm(() => {
|
|
|
11153
11830
|
"task_files",
|
|
11154
11831
|
"task_commits",
|
|
11155
11832
|
"task_git_refs",
|
|
11156
|
-
"task_verifications"
|
|
11833
|
+
"task_verifications",
|
|
11834
|
+
"saved_views"
|
|
11157
11835
|
];
|
|
11158
11836
|
insertColumns = {
|
|
11159
11837
|
projects: ["id", "name", "path", "description", "task_list_id", "task_prefix", "task_counter", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
@@ -11225,7 +11903,8 @@ var init_local_bridge = __esm(() => {
|
|
|
11225
11903
|
task_files: ["id", "task_id", "path", "status", "agent_id", "note", "created_at", "updated_at", "machine_id"],
|
|
11226
11904
|
task_commits: ["id", "task_id", "sha", "message", "author", "files_changed", "committed_at", "created_at"],
|
|
11227
11905
|
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"]
|
|
11906
|
+
task_verifications: ["id", "task_id", "command", "status", "output_summary", "artifact_path", "agent_id", "run_at", "created_at"],
|
|
11907
|
+
saved_views: ["id", "name", "description", "scope", "filters", "created_at", "updated_at"]
|
|
11229
11908
|
};
|
|
11230
11909
|
tableByKey = {
|
|
11231
11910
|
projects: "projects",
|
|
@@ -11241,9 +11920,10 @@ var init_local_bridge = __esm(() => {
|
|
|
11241
11920
|
task_files: "task_files",
|
|
11242
11921
|
task_commits: "task_commits",
|
|
11243
11922
|
task_git_refs: "task_git_refs",
|
|
11244
|
-
task_verifications: "task_verifications"
|
|
11923
|
+
task_verifications: "task_verifications",
|
|
11924
|
+
saved_views: "saved_search_views"
|
|
11245
11925
|
};
|
|
11246
|
-
jsonColumns = new Set(["metadata", "tags", "data", "files_changed"]);
|
|
11926
|
+
jsonColumns = new Set(["metadata", "tags", "data", "files_changed", "filters"]);
|
|
11247
11927
|
});
|
|
11248
11928
|
|
|
11249
11929
|
// src/lib/local-encryption.ts
|
|
@@ -11818,7 +12498,8 @@ function emptyCounts2() {
|
|
|
11818
12498
|
task_files: 0,
|
|
11819
12499
|
task_commits: 0,
|
|
11820
12500
|
task_git_refs: 0,
|
|
11821
|
-
task_verifications: 0
|
|
12501
|
+
task_verifications: 0,
|
|
12502
|
+
saved_views: 0
|
|
11822
12503
|
};
|
|
11823
12504
|
}
|
|
11824
12505
|
function bridgeToMarkdownPayload(bundle) {
|
|
@@ -12069,6 +12750,61 @@ __export(exports_project_commands, {
|
|
|
12069
12750
|
});
|
|
12070
12751
|
import chalk4 from "chalk";
|
|
12071
12752
|
import { basename as basename3, resolve as resolve9 } from "path";
|
|
12753
|
+
function collectOption(value, previous = []) {
|
|
12754
|
+
return [...previous, value];
|
|
12755
|
+
}
|
|
12756
|
+
function splitList(value) {
|
|
12757
|
+
if (!value)
|
|
12758
|
+
return;
|
|
12759
|
+
const values = Array.isArray(value) ? value : [value];
|
|
12760
|
+
const items = values.flatMap((item) => item.split(",").map((part) => part.trim()).filter(Boolean));
|
|
12761
|
+
return items.length > 0 ? items : undefined;
|
|
12762
|
+
}
|
|
12763
|
+
function parseJsonObjectOption(value, label) {
|
|
12764
|
+
if (!value)
|
|
12765
|
+
return;
|
|
12766
|
+
try {
|
|
12767
|
+
const parsed = JSON.parse(value);
|
|
12768
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
12769
|
+
return parsed;
|
|
12770
|
+
} catch {}
|
|
12771
|
+
throw new Error(`${label} must be a JSON object`);
|
|
12772
|
+
}
|
|
12773
|
+
function buildSearchFilters(query, opts, projectId) {
|
|
12774
|
+
const filterPatch = parseJsonObjectOption(opts.filter, "--filter");
|
|
12775
|
+
const customFields = parseJsonObjectOption(opts.fieldCustom, "--field-custom");
|
|
12776
|
+
const labels = splitList(opts.fieldLabel);
|
|
12777
|
+
const tags = splitList(opts.tag);
|
|
12778
|
+
const statuses = splitList(opts.status)?.map(normalizeStatus);
|
|
12779
|
+
const filters = {
|
|
12780
|
+
query: query || opts.query,
|
|
12781
|
+
project_id: opts.allProjects ? undefined : projectId,
|
|
12782
|
+
status: statuses,
|
|
12783
|
+
priority: splitList(opts.priority),
|
|
12784
|
+
assigned_to: opts.assigned,
|
|
12785
|
+
agent_id: opts.agentId,
|
|
12786
|
+
task_list_id: opts.taskList,
|
|
12787
|
+
plan_id: opts.plan,
|
|
12788
|
+
task_id: opts.task,
|
|
12789
|
+
tags,
|
|
12790
|
+
created_after: opts.createdAfter,
|
|
12791
|
+
updated_after: opts.since || opts.updatedAfter,
|
|
12792
|
+
has_dependencies: opts.hasDeps ? true : undefined,
|
|
12793
|
+
is_blocked: opts.blocked ? true : undefined,
|
|
12794
|
+
depends_on: opts.dependsOn,
|
|
12795
|
+
blocks: opts.blocks,
|
|
12796
|
+
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
12797
|
+
local_fields: labels || opts.fieldOwner || opts.fieldArea || opts.fieldSeverity || customFields ? {
|
|
12798
|
+
labels,
|
|
12799
|
+
owner: opts.fieldOwner,
|
|
12800
|
+
area: opts.fieldArea,
|
|
12801
|
+
severity: opts.fieldSeverity,
|
|
12802
|
+
custom: customFields
|
|
12803
|
+
} : undefined,
|
|
12804
|
+
...filterPatch
|
|
12805
|
+
};
|
|
12806
|
+
return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== undefined));
|
|
12807
|
+
}
|
|
12072
12808
|
function registerProjectCommands(program2) {
|
|
12073
12809
|
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
12810
|
const globalOpts = program2.opts();
|
|
@@ -12121,35 +12857,126 @@ function registerProjectCommands(program2) {
|
|
|
12121
12857
|
console.log(chalk4.green("Comment added."));
|
|
12122
12858
|
}
|
|
12123
12859
|
});
|
|
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) => {
|
|
12860
|
+
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) => {
|
|
12861
|
+
const globalOpts = program2.opts();
|
|
12862
|
+
try {
|
|
12863
|
+
const projectId = opts.allProjects ? undefined : autoProject(globalOpts);
|
|
12864
|
+
const scope = normalizeScope(opts.scope);
|
|
12865
|
+
const searchOpts = buildSearchFilters(query, opts, projectId);
|
|
12866
|
+
if (opts.saveAs) {
|
|
12867
|
+
const view = saveSearchView({
|
|
12868
|
+
name: opts.saveAs,
|
|
12869
|
+
description: opts.description,
|
|
12870
|
+
scope,
|
|
12871
|
+
filters: searchOpts
|
|
12872
|
+
});
|
|
12873
|
+
output(view, Boolean(globalOpts.json));
|
|
12874
|
+
if (!globalOpts.json)
|
|
12875
|
+
console.log(chalk4.green(`Saved view ${view.name}.`));
|
|
12876
|
+
return;
|
|
12877
|
+
}
|
|
12878
|
+
if (scope !== "tasks") {
|
|
12879
|
+
const result = runSavedSearch(searchOpts, scope);
|
|
12880
|
+
if (globalOpts.json) {
|
|
12881
|
+
output(result, true);
|
|
12882
|
+
return;
|
|
12883
|
+
}
|
|
12884
|
+
if (result.count === 0) {
|
|
12885
|
+
console.log(chalk4.dim(`No ${scope} results matching "${query}".`));
|
|
12886
|
+
return;
|
|
12887
|
+
}
|
|
12888
|
+
console.log(chalk4.bold(`${result.count} ${scope} result(s) for "${query}":
|
|
12889
|
+
`));
|
|
12890
|
+
for (const item of result.results) {
|
|
12891
|
+
const entity = item.entity;
|
|
12892
|
+
console.log(`${chalk4.cyan(item.entity_type)} ${entity.id?.slice?.(0, 8) || ""} ${entity.name || entity.title || entity.content || entity.summary || ""}`);
|
|
12893
|
+
}
|
|
12894
|
+
return;
|
|
12895
|
+
}
|
|
12896
|
+
const tasks = runSavedSearch(searchOpts, "tasks").results.map((item) => item.entity);
|
|
12897
|
+
if (globalOpts.json) {
|
|
12898
|
+
output(tasks, true);
|
|
12899
|
+
return;
|
|
12900
|
+
}
|
|
12901
|
+
if (tasks.length === 0) {
|
|
12902
|
+
console.log(chalk4.dim(`No tasks matching "${query}".`));
|
|
12903
|
+
return;
|
|
12904
|
+
}
|
|
12905
|
+
console.log(chalk4.bold(`${tasks.length} result(s) for "${query}":
|
|
12906
|
+
`));
|
|
12907
|
+
for (const t of tasks) {
|
|
12908
|
+
console.log(formatTaskLine(t));
|
|
12909
|
+
}
|
|
12910
|
+
} catch (e) {
|
|
12911
|
+
handleError(e);
|
|
12912
|
+
}
|
|
12913
|
+
});
|
|
12914
|
+
const views = program2.command("views").description("Manage local saved search views");
|
|
12915
|
+
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) => {
|
|
12916
|
+
const globalOpts = program2.opts();
|
|
12917
|
+
try {
|
|
12918
|
+
const projectId = opts.allProjects ? undefined : autoProject(globalOpts);
|
|
12919
|
+
const view = saveSearchView({
|
|
12920
|
+
name,
|
|
12921
|
+
description: opts.description,
|
|
12922
|
+
scope: normalizeScope(opts.scope),
|
|
12923
|
+
filters: buildSearchFilters(opts.query, opts, projectId)
|
|
12924
|
+
});
|
|
12925
|
+
output(view, Boolean(globalOpts.json));
|
|
12926
|
+
if (!globalOpts.json)
|
|
12927
|
+
console.log(chalk4.green(`Saved view ${view.name}.`));
|
|
12928
|
+
} catch (e) {
|
|
12929
|
+
handleError(e);
|
|
12930
|
+
}
|
|
12931
|
+
});
|
|
12932
|
+
views.command("list").description("List local saved search views").option("--scope <scope>", "Filter by scope").action((opts) => {
|
|
12933
|
+
const globalOpts = program2.opts();
|
|
12934
|
+
try {
|
|
12935
|
+
const rows = listSearchViews(opts.scope ? normalizeScope(opts.scope) : undefined);
|
|
12936
|
+
output(rows, Boolean(globalOpts.json));
|
|
12937
|
+
if (!globalOpts.json) {
|
|
12938
|
+
if (rows.length === 0) {
|
|
12939
|
+
console.log(chalk4.dim("No saved search views."));
|
|
12940
|
+
return;
|
|
12941
|
+
}
|
|
12942
|
+
for (const row of rows)
|
|
12943
|
+
console.log(`${chalk4.cyan(row.name)} ${chalk4.dim(`[${row.scope}]`)} ${JSON.stringify(row.filters)}`);
|
|
12944
|
+
}
|
|
12945
|
+
} catch (e) {
|
|
12946
|
+
handleError(e);
|
|
12947
|
+
}
|
|
12948
|
+
});
|
|
12949
|
+
views.command("run <name>").description("Run a local saved search view").action((name) => {
|
|
12950
|
+
const globalOpts = program2.opts();
|
|
12951
|
+
try {
|
|
12952
|
+
const result = runSearchView(name);
|
|
12953
|
+
if (globalOpts.json) {
|
|
12954
|
+
output(result, true);
|
|
12955
|
+
return;
|
|
12956
|
+
}
|
|
12957
|
+
console.log(chalk4.bold(`${result.count} result(s) for view "${result.view?.name || name}":
|
|
12958
|
+
`));
|
|
12959
|
+
for (const item of result.results) {
|
|
12960
|
+
if (item.entity_type === "tasks") {
|
|
12961
|
+
console.log(formatTaskLine(item.entity));
|
|
12962
|
+
continue;
|
|
12963
|
+
}
|
|
12964
|
+
const entity = item.entity;
|
|
12965
|
+
console.log(`${chalk4.cyan(item.entity_type)} ${entity.id?.slice?.(0, 8) || ""} ${entity.name || entity.title || entity.content || entity.summary || ""}`);
|
|
12966
|
+
}
|
|
12967
|
+
} catch (e) {
|
|
12968
|
+
handleError(e);
|
|
12969
|
+
}
|
|
12970
|
+
});
|
|
12971
|
+
views.command("delete <name>").description("Delete a local saved search view").action((name) => {
|
|
12125
12972
|
const globalOpts = program2.opts();
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
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;
|
|
12144
|
-
}
|
|
12145
|
-
if (tasks.length === 0) {
|
|
12146
|
-
console.log(chalk4.dim(`No tasks matching "${query}".`));
|
|
12147
|
-
return;
|
|
12148
|
-
}
|
|
12149
|
-
console.log(chalk4.bold(`${tasks.length} result(s) for "${query}":
|
|
12150
|
-
`));
|
|
12151
|
-
for (const t of tasks) {
|
|
12152
|
-
console.log(formatTaskLine(t));
|
|
12973
|
+
try {
|
|
12974
|
+
const deleted = deleteSearchView(name);
|
|
12975
|
+
output({ deleted }, Boolean(globalOpts.json));
|
|
12976
|
+
if (!globalOpts.json)
|
|
12977
|
+
console.log(deleted ? chalk4.green(`Deleted view ${name}.`) : chalk4.dim(`View not found: ${name}`));
|
|
12978
|
+
} catch (e) {
|
|
12979
|
+
handleError(e);
|
|
12153
12980
|
}
|
|
12154
12981
|
});
|
|
12155
12982
|
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) => {
|
|
@@ -12415,8 +13242,8 @@ function registerProjectCommands(program2) {
|
|
|
12415
13242
|
const projectId = autoProject(globalOpts);
|
|
12416
13243
|
const writeOutput = async (content) => {
|
|
12417
13244
|
if (opts.output) {
|
|
12418
|
-
const { writeFileSync:
|
|
12419
|
-
|
|
13245
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
13246
|
+
writeFileSync5(resolve9(opts.output), content.endsWith(`
|
|
12420
13247
|
`) ? content : `${content}
|
|
12421
13248
|
`);
|
|
12422
13249
|
} else {
|
|
@@ -12576,7 +13403,7 @@ var init_project_commands = __esm(() => {
|
|
|
12576
13403
|
init_database();
|
|
12577
13404
|
init_projects();
|
|
12578
13405
|
init_comments();
|
|
12579
|
-
|
|
13406
|
+
init_saved_search_views();
|
|
12580
13407
|
init_sync();
|
|
12581
13408
|
init_config();
|
|
12582
13409
|
init_helpers();
|
|
@@ -14205,8 +15032,8 @@ var exports_doctor = {};
|
|
|
14205
15032
|
__export(exports_doctor, {
|
|
14206
15033
|
runTodosDoctor: () => runTodosDoctor
|
|
14207
15034
|
});
|
|
14208
|
-
import { chmodSync, copyFileSync, existsSync as existsSync9, mkdirSync as
|
|
14209
|
-
import { basename as basename4, dirname as dirname7, join as
|
|
15035
|
+
import { chmodSync, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync6, statSync as statSync5 } from "fs";
|
|
15036
|
+
import { basename as basename4, dirname as dirname7, join as join10 } from "path";
|
|
14210
15037
|
function tableExists(db, table) {
|
|
14211
15038
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
14212
15039
|
}
|
|
@@ -14363,13 +15190,13 @@ function createBackup(dbPath) {
|
|
|
14363
15190
|
if (!existsSync9(dbPath))
|
|
14364
15191
|
return;
|
|
14365
15192
|
const stamp = now().replace(/[:.]/g, "-");
|
|
14366
|
-
const backupDir =
|
|
15193
|
+
const backupDir = join10(dirname7(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
|
|
14367
15194
|
const files = [];
|
|
14368
|
-
|
|
15195
|
+
mkdirSync6(backupDir, { recursive: true });
|
|
14369
15196
|
for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
14370
15197
|
if (!existsSync9(source))
|
|
14371
15198
|
continue;
|
|
14372
|
-
const target =
|
|
15199
|
+
const target = join10(backupDir, basename4(source));
|
|
14373
15200
|
copyFileSync(source, target);
|
|
14374
15201
|
files.push(target);
|
|
14375
15202
|
}
|
|
@@ -14595,7 +15422,7 @@ var init_doctor = __esm(() => {
|
|
|
14595
15422
|
});
|
|
14596
15423
|
|
|
14597
15424
|
// src/server/routes.ts
|
|
14598
|
-
import { join as
|
|
15425
|
+
import { join as join11, resolve as resolve11, sep } from "path";
|
|
14599
15426
|
function parseFieldsParam(url) {
|
|
14600
15427
|
const fieldsParam = url.searchParams.get("fields");
|
|
14601
15428
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -15295,7 +16122,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
15295
16122
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
15296
16123
|
return null;
|
|
15297
16124
|
if (path !== "/") {
|
|
15298
|
-
const filePath =
|
|
16125
|
+
const filePath = join11(ctx.dashboardDir, path);
|
|
15299
16126
|
const resolvedFile = resolve11(filePath);
|
|
15300
16127
|
const resolvedBase = resolve11(ctx.dashboardDir);
|
|
15301
16128
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
@@ -15305,7 +16132,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
15305
16132
|
if (res2)
|
|
15306
16133
|
return res2;
|
|
15307
16134
|
}
|
|
15308
|
-
const indexPath =
|
|
16135
|
+
const indexPath = join11(ctx.dashboardDir, "index.html");
|
|
15309
16136
|
const res = serveStaticFile2(indexPath);
|
|
15310
16137
|
if (res)
|
|
15311
16138
|
return res;
|
|
@@ -15335,26 +16162,26 @@ __export(exports_serve, {
|
|
|
15335
16162
|
MIME_TYPES: () => MIME_TYPES
|
|
15336
16163
|
});
|
|
15337
16164
|
import { existsSync as existsSync10 } from "fs";
|
|
15338
|
-
import { join as
|
|
16165
|
+
import { join as join12, dirname as dirname8, extname } from "path";
|
|
15339
16166
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15340
16167
|
function resolveDashboardDir() {
|
|
15341
16168
|
const candidates = [];
|
|
15342
16169
|
try {
|
|
15343
16170
|
const scriptDir = dirname8(fileURLToPath2(import.meta.url));
|
|
15344
|
-
candidates.push(
|
|
15345
|
-
candidates.push(
|
|
16171
|
+
candidates.push(join12(scriptDir, "..", "dashboard", "dist"));
|
|
16172
|
+
candidates.push(join12(scriptDir, "..", "..", "dashboard", "dist"));
|
|
15346
16173
|
} catch {}
|
|
15347
16174
|
if (process.argv[1]) {
|
|
15348
16175
|
const mainDir = dirname8(process.argv[1]);
|
|
15349
|
-
candidates.push(
|
|
15350
|
-
candidates.push(
|
|
16176
|
+
candidates.push(join12(mainDir, "..", "dashboard", "dist"));
|
|
16177
|
+
candidates.push(join12(mainDir, "..", "..", "dashboard", "dist"));
|
|
15351
16178
|
}
|
|
15352
|
-
candidates.push(
|
|
16179
|
+
candidates.push(join12(process.cwd(), "dashboard", "dist"));
|
|
15353
16180
|
for (const candidate of candidates) {
|
|
15354
16181
|
if (existsSync10(candidate))
|
|
15355
16182
|
return candidate;
|
|
15356
16183
|
}
|
|
15357
|
-
return
|
|
16184
|
+
return join12(process.cwd(), "dashboard", "dist");
|
|
15358
16185
|
}
|
|
15359
16186
|
function getProvidedApiKey(req) {
|
|
15360
16187
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -17130,14 +17957,14 @@ __export(exports_config_serve_commands, {
|
|
|
17130
17957
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
17131
17958
|
});
|
|
17132
17959
|
import chalk6 from "chalk";
|
|
17133
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
17134
|
-
import { dirname as dirname9, join as
|
|
17960
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
17961
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
17135
17962
|
function registerConfigServeCommands(program2) {
|
|
17136
17963
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
17137
17964
|
const globalOpts = program2.opts();
|
|
17138
17965
|
const home = process.env["HOME"] || "~";
|
|
17139
|
-
const newPath =
|
|
17140
|
-
const legacyPath =
|
|
17966
|
+
const newPath = join13(home, ".hasna", "todos", "config.json");
|
|
17967
|
+
const legacyPath = join13(home, ".todos", "config.json");
|
|
17141
17968
|
const configPath = !existsSync11(newPath) && existsSync11(legacyPath) ? legacyPath : newPath;
|
|
17142
17969
|
if (opts.get) {
|
|
17143
17970
|
const config2 = loadConfig();
|
|
@@ -17176,8 +18003,8 @@ function registerConfigServeCommands(program2) {
|
|
|
17176
18003
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
17177
18004
|
const dir = dirname9(configPath);
|
|
17178
18005
|
if (!existsSync11(dir))
|
|
17179
|
-
|
|
17180
|
-
|
|
18006
|
+
mkdirSync7(dir, { recursive: true });
|
|
18007
|
+
writeFileSync5(configPath, JSON.stringify(config2, null, 2));
|
|
17181
18008
|
if (globalOpts.json) {
|
|
17182
18009
|
output({ key, value: parsedValue }, true);
|
|
17183
18010
|
} else {
|
|
@@ -18614,101 +19441,6 @@ var init_task_dedupe = __esm(() => {
|
|
|
18614
19441
|
PY_STACK_RE = /File\s+"([^"]+)",\s+line\s+\d+,\s+in\s+([^\s]+)/i;
|
|
18615
19442
|
});
|
|
18616
19443
|
|
|
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
19444
|
// src/lib/activity-timeline.ts
|
|
18713
19445
|
var exports_activity_timeline = {};
|
|
18714
19446
|
__export(exports_activity_timeline, {
|
|
@@ -19552,7 +20284,7 @@ __export(exports_query_commands, {
|
|
|
19552
20284
|
registerQueryCommands: () => registerQueryCommands
|
|
19553
20285
|
});
|
|
19554
20286
|
import chalk7 from "chalk";
|
|
19555
|
-
function
|
|
20287
|
+
function parseJsonObjectOption2(value, label) {
|
|
19556
20288
|
if (!value)
|
|
19557
20289
|
return;
|
|
19558
20290
|
try {
|
|
@@ -20083,8 +20815,8 @@ Repairs`));
|
|
|
20083
20815
|
const db = getDatabase();
|
|
20084
20816
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
20085
20817
|
const { statSync: statSync6 } = await import("fs");
|
|
20086
|
-
const { join:
|
|
20087
|
-
const dbPath = process.env["TODOS_DB_PATH"] ||
|
|
20818
|
+
const { join: join14 } = await import("path");
|
|
20819
|
+
const dbPath = process.env["TODOS_DB_PATH"] || join14(process.env["HOME"] || "~", ".todos", "todos.db");
|
|
20088
20820
|
let size = "unknown";
|
|
20089
20821
|
try {
|
|
20090
20822
|
size = `${(statSync6(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -20904,7 +21636,7 @@ Repairs`));
|
|
|
20904
21636
|
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
21637
|
const globalOpts = program2.opts();
|
|
20906
21638
|
try {
|
|
20907
|
-
const custom = mergeCustomFields(
|
|
21639
|
+
const custom = mergeCustomFields(parseJsonObjectOption2(opts.custom, "--custom"), parseFieldPairs(opts.field));
|
|
20908
21640
|
const input = {
|
|
20909
21641
|
labels: parseCsvOption(opts.labels),
|
|
20910
21642
|
priority: parsePriority(opts.priority),
|
|
@@ -20934,7 +21666,7 @@ Repairs`));
|
|
|
20934
21666
|
severity: opts.severity,
|
|
20935
21667
|
owner: opts.owner,
|
|
20936
21668
|
area: opts.area,
|
|
20937
|
-
custom: mergeCustomFields(
|
|
21669
|
+
custom: mergeCustomFields(parseJsonObjectOption2(opts.custom, "--custom"), parseFieldPairs(opts.field)),
|
|
20938
21670
|
limit: Number(opts.limit)
|
|
20939
21671
|
};
|
|
20940
21672
|
const tasks = queryTasksByLocalFields(query);
|
|
@@ -21018,7 +21750,7 @@ Repairs`));
|
|
|
21018
21750
|
source_type: opts.sourceType,
|
|
21019
21751
|
source_name: opts.sourceName || opts.file,
|
|
21020
21752
|
source_url: opts.sourceUrl,
|
|
21021
|
-
metadata:
|
|
21753
|
+
metadata: parseJsonObjectOption2(opts.metadata, "--metadata"),
|
|
21022
21754
|
project_id: autoProject(globalOpts) || undefined,
|
|
21023
21755
|
priority: opts.priority,
|
|
21024
21756
|
tags: opts.tags ? String(opts.tags).split(",").map((tag) => tag.trim()).filter(Boolean) : undefined,
|
|
@@ -25810,6 +26542,10 @@ var init_token_utils = __esm(() => {
|
|
|
25810
26542
|
"request_task_review",
|
|
25811
26543
|
"reschedule_task",
|
|
25812
26544
|
"search_tasks",
|
|
26545
|
+
"save_search_view",
|
|
26546
|
+
"list_search_views",
|
|
26547
|
+
"run_search_view",
|
|
26548
|
+
"delete_search_view",
|
|
25813
26549
|
"standup",
|
|
25814
26550
|
"set_task_contract",
|
|
25815
26551
|
"task_context",
|
|
@@ -25999,10 +26735,12 @@ var init_token_utils = __esm(() => {
|
|
|
25999
26735
|
"export_template",
|
|
26000
26736
|
"import_template",
|
|
26001
26737
|
"init_templates",
|
|
26738
|
+
"list_template_library",
|
|
26002
26739
|
"list_templates",
|
|
26003
26740
|
"preview_template",
|
|
26004
26741
|
"template_history",
|
|
26005
|
-
"update_template"
|
|
26742
|
+
"update_template",
|
|
26743
|
+
"write_template_library"
|
|
26006
26744
|
],
|
|
26007
26745
|
webhooks: ["create_webhook", "delete_webhook", "list_webhooks"],
|
|
26008
26746
|
machines: [
|
|
@@ -27601,6 +28339,76 @@ ${lines.join(`
|
|
|
27601
28339
|
}
|
|
27602
28340
|
});
|
|
27603
28341
|
}
|
|
28342
|
+
if (shouldRegisterTool("save_search_view")) {
|
|
28343
|
+
server.tool("save_search_view", "Save a local search view for tasks, projects, plans, runs, comments, or all records.", {
|
|
28344
|
+
name: exports_external.string().describe("Saved view name"),
|
|
28345
|
+
query: exports_external.string().optional().describe("Search query"),
|
|
28346
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional(),
|
|
28347
|
+
description: exports_external.string().optional(),
|
|
28348
|
+
project_id: exports_external.string().optional(),
|
|
28349
|
+
status: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
28350
|
+
priority: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
28351
|
+
assigned_to: exports_external.string().optional(),
|
|
28352
|
+
agent_id: exports_external.string().optional(),
|
|
28353
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
28354
|
+
limit: exports_external.number().optional()
|
|
28355
|
+
}, async ({ name, scope, description, ...filters }) => {
|
|
28356
|
+
try {
|
|
28357
|
+
const { saveSearchView: saveSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
28358
|
+
const view = saveSearchView2({
|
|
28359
|
+
name,
|
|
28360
|
+
description,
|
|
28361
|
+
scope,
|
|
28362
|
+
filters: {
|
|
28363
|
+
...filters,
|
|
28364
|
+
project_id: filters.project_id ? resolveId(filters.project_id, "projects") : undefined
|
|
28365
|
+
}
|
|
28366
|
+
});
|
|
28367
|
+
return { content: [{ type: "text", text: JSON.stringify(view, null, 2) }] };
|
|
28368
|
+
} catch (e) {
|
|
28369
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28370
|
+
}
|
|
28371
|
+
});
|
|
28372
|
+
}
|
|
28373
|
+
if (shouldRegisterTool("list_search_views")) {
|
|
28374
|
+
server.tool("list_search_views", "List local saved search views.", {
|
|
28375
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional()
|
|
28376
|
+
}, async ({ scope }) => {
|
|
28377
|
+
try {
|
|
28378
|
+
const { listSearchViews: listSearchViews2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
28379
|
+
const views = listSearchViews2(scope);
|
|
28380
|
+
return { content: [{ type: "text", text: JSON.stringify(views, null, 2) }] };
|
|
28381
|
+
} catch (e) {
|
|
28382
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28383
|
+
}
|
|
28384
|
+
});
|
|
28385
|
+
}
|
|
28386
|
+
if (shouldRegisterTool("run_search_view")) {
|
|
28387
|
+
server.tool("run_search_view", "Run a local saved search view and return stable JSON results.", {
|
|
28388
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
28389
|
+
}, async ({ name }) => {
|
|
28390
|
+
try {
|
|
28391
|
+
const { runSearchView: runSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
28392
|
+
const result = runSearchView2(name);
|
|
28393
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
28394
|
+
} catch (e) {
|
|
28395
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28396
|
+
}
|
|
28397
|
+
});
|
|
28398
|
+
}
|
|
28399
|
+
if (shouldRegisterTool("delete_search_view")) {
|
|
28400
|
+
server.tool("delete_search_view", "Delete a local saved search view.", {
|
|
28401
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
28402
|
+
}, async ({ name }) => {
|
|
28403
|
+
try {
|
|
28404
|
+
const { deleteSearchView: deleteSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
28405
|
+
const deleted = deleteSearchView2(name);
|
|
28406
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted }, null, 2) }] };
|
|
28407
|
+
} catch (e) {
|
|
28408
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
28409
|
+
}
|
|
28410
|
+
});
|
|
28411
|
+
}
|
|
27604
28412
|
}
|
|
27605
28413
|
var init_task_project_tools = __esm(() => {
|
|
27606
28414
|
init_zod();
|
|
@@ -28949,6 +29757,10 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
28949
29757
|
reschedule_task: "reschedule_task \u2014 Update deadline. Params: task_id, deadline, version",
|
|
28950
29758
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
28951
29759
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
29760
|
+
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",
|
|
29761
|
+
list_search_views: "list_search_views \u2014 List local saved search views. Params: scope",
|
|
29762
|
+
run_search_view: "run_search_view \u2014 Run a local saved search view and return stable JSON results. Params: name",
|
|
29763
|
+
delete_search_view: "delete_search_view \u2014 Delete a local saved search view. Params: name",
|
|
28952
29764
|
get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
|
|
28953
29765
|
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
29766
|
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 +30093,7 @@ __export(exports_verification_providers, {
|
|
|
29281
30093
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
29282
30094
|
});
|
|
29283
30095
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
29284
|
-
function
|
|
30096
|
+
function normalizeName2(name) {
|
|
29285
30097
|
const normalized = name.trim().toLowerCase();
|
|
29286
30098
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
29287
30099
|
throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
|
|
@@ -29300,10 +30112,10 @@ function timeoutMs(value) {
|
|
|
29300
30112
|
return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
|
|
29301
30113
|
}
|
|
29302
30114
|
function getProvider(name) {
|
|
29303
|
-
return loadConfig().verification_providers?.[
|
|
30115
|
+
return loadConfig().verification_providers?.[normalizeName2(name)] || null;
|
|
29304
30116
|
}
|
|
29305
30117
|
function upsertVerificationProvider(input) {
|
|
29306
|
-
const name =
|
|
30118
|
+
const name = normalizeName2(input.name);
|
|
29307
30119
|
const config = loadConfig();
|
|
29308
30120
|
const existing = config.verification_providers?.[name];
|
|
29309
30121
|
const timestamp = new Date().toISOString();
|
|
@@ -29333,7 +30145,7 @@ function listVerificationProviders() {
|
|
|
29333
30145
|
return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
|
|
29334
30146
|
}
|
|
29335
30147
|
function removeVerificationProvider(name) {
|
|
29336
|
-
const normalized =
|
|
30148
|
+
const normalized = normalizeName2(name);
|
|
29337
30149
|
const config = loadConfig();
|
|
29338
30150
|
if (!config.verification_providers?.[normalized])
|
|
29339
30151
|
return false;
|
|
@@ -32070,6 +32882,259 @@ var init_agents2 = __esm(() => {
|
|
|
32070
32882
|
init_database();
|
|
32071
32883
|
});
|
|
32072
32884
|
|
|
32885
|
+
// src/mcp/tools/templates.ts
|
|
32886
|
+
function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatError }) {
|
|
32887
|
+
if (shouldRegisterTool("create_template")) {
|
|
32888
|
+
server.tool("create_template", "Create a reusable task template. Optionally include a tasks array to define a multi-task template with dependencies and variable placeholders ({name} syntax). Use variables to define typed variable definitions with defaults and required flags.", {
|
|
32889
|
+
name: exports_external.string(),
|
|
32890
|
+
title_pattern: exports_external.string(),
|
|
32891
|
+
description: exports_external.string().optional(),
|
|
32892
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32893
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
32894
|
+
project_id: exports_external.string().optional(),
|
|
32895
|
+
plan_id: exports_external.string().optional(),
|
|
32896
|
+
variables: exports_external.array(exports_external.object({
|
|
32897
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
32898
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
32899
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
32900
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
32901
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
32902
|
+
tasks: exports_external.array(exports_external.object({
|
|
32903
|
+
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
32904
|
+
description: exports_external.string().optional(),
|
|
32905
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32906
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
32907
|
+
task_type: exports_external.string().optional(),
|
|
32908
|
+
depends_on: exports_external.array(exports_external.number()).optional().describe("Position indices (0-based) of tasks this task depends on"),
|
|
32909
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
32910
|
+
})).optional().describe("Multi-task template: ordered list of tasks to create together with dependencies")
|
|
32911
|
+
}, async (params) => {
|
|
32912
|
+
try {
|
|
32913
|
+
const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32914
|
+
const t = createTemplate2(params);
|
|
32915
|
+
const withTasks = getTemplateWithTasks2(t.id);
|
|
32916
|
+
const taskCount = withTasks?.tasks.length ?? 0;
|
|
32917
|
+
const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
|
|
32918
|
+
return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
|
|
32919
|
+
} catch (e) {
|
|
32920
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32921
|
+
}
|
|
32922
|
+
});
|
|
32923
|
+
}
|
|
32924
|
+
if (shouldRegisterTool("list_templates")) {
|
|
32925
|
+
server.tool("list_templates", "List all task templates", {}, async () => {
|
|
32926
|
+
try {
|
|
32927
|
+
const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32928
|
+
const templates = listTemplates2();
|
|
32929
|
+
if (templates.length === 0)
|
|
32930
|
+
return { content: [{ type: "text", text: "No templates." }] };
|
|
32931
|
+
const text = templates.map((t) => {
|
|
32932
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
32933
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
32934
|
+
}).join(`
|
|
32935
|
+
`);
|
|
32936
|
+
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
32937
|
+
${text}` }] };
|
|
32938
|
+
} catch (e) {
|
|
32939
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32940
|
+
}
|
|
32941
|
+
});
|
|
32942
|
+
}
|
|
32943
|
+
if (shouldRegisterTool("create_task_from_template")) {
|
|
32944
|
+
server.tool("create_task_from_template", "Create task(s) from a template. For multi-task templates, creates all tasks with dependencies wired. Supports {variable} substitution in titles/descriptions.", {
|
|
32945
|
+
template_id: exports_external.string(),
|
|
32946
|
+
title: exports_external.string().optional().describe("Override title (single-task templates only)"),
|
|
32947
|
+
description: exports_external.string().optional().describe("Override description (single-task templates only)"),
|
|
32948
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
32949
|
+
assigned_to: exports_external.string().optional(),
|
|
32950
|
+
project_id: exports_external.string().optional(),
|
|
32951
|
+
task_list_id: exports_external.string().optional(),
|
|
32952
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders in multi-task templates")
|
|
32953
|
+
}, async (params) => {
|
|
32954
|
+
try {
|
|
32955
|
+
const { taskFromTemplate: taskFromTemplate2, getTemplateWithTasks: getTemplateWithTasks2, tasksFromTemplate: tasksFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32956
|
+
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
32957
|
+
const resolvedProjectId = params.project_id ? resolveId(params.project_id, "projects") : undefined;
|
|
32958
|
+
const resolvedTaskListId = params.task_list_id ? resolveId(params.task_list_id, "task_lists") : undefined;
|
|
32959
|
+
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
32960
|
+
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
32961
|
+
const effectiveProjectId = resolvedProjectId || templateWithTasks.project_id || undefined;
|
|
32962
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, resolvedTaskListId);
|
|
32963
|
+
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
32964
|
+
`);
|
|
32965
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
32966
|
+
${text}` }] };
|
|
32967
|
+
}
|
|
32968
|
+
const input = taskFromTemplate2(resolvedTemplateId, {
|
|
32969
|
+
title: params.title,
|
|
32970
|
+
description: params.description,
|
|
32971
|
+
priority: params.priority,
|
|
32972
|
+
assigned_to: params.assigned_to,
|
|
32973
|
+
project_id: resolvedProjectId,
|
|
32974
|
+
task_list_id: resolvedTaskListId
|
|
32975
|
+
});
|
|
32976
|
+
const task = createTask(input);
|
|
32977
|
+
return { content: [{ type: "text", text: `Task created from template:
|
|
32978
|
+
${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
32979
|
+
} catch (e) {
|
|
32980
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32981
|
+
}
|
|
32982
|
+
});
|
|
32983
|
+
}
|
|
32984
|
+
if (shouldRegisterTool("delete_template")) {
|
|
32985
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
32986
|
+
try {
|
|
32987
|
+
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
32988
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
32989
|
+
const deleted = deleteTemplate2(resolvedId);
|
|
32990
|
+
return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
|
|
32991
|
+
} catch (e) {
|
|
32992
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
32993
|
+
}
|
|
32994
|
+
});
|
|
32995
|
+
}
|
|
32996
|
+
if (shouldRegisterTool("update_template")) {
|
|
32997
|
+
server.tool("update_template", "Update a task template's name, title pattern, description, priority, tags, or other fields.", {
|
|
32998
|
+
id: exports_external.string(),
|
|
32999
|
+
name: exports_external.string().optional(),
|
|
33000
|
+
title_pattern: exports_external.string().optional(),
|
|
33001
|
+
description: exports_external.string().optional(),
|
|
33002
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
33003
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
33004
|
+
project_id: exports_external.string().optional(),
|
|
33005
|
+
plan_id: exports_external.string().optional()
|
|
33006
|
+
}, async ({ id, ...updates }) => {
|
|
33007
|
+
try {
|
|
33008
|
+
const { updateTemplate: updateTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33009
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
33010
|
+
const t = updateTemplate2(resolvedId, updates);
|
|
33011
|
+
if (!t)
|
|
33012
|
+
return { content: [{ type: "text", text: `Template not found: ${id}` }], isError: true };
|
|
33013
|
+
return { content: [{ type: "text", text: `Template updated: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}` }] };
|
|
33014
|
+
} catch (e) {
|
|
33015
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33016
|
+
}
|
|
33017
|
+
});
|
|
33018
|
+
}
|
|
33019
|
+
if (shouldRegisterTool("init_templates")) {
|
|
33020
|
+
server.tool("init_templates", "Initialize the bundled local template library. Skips templates that already exist by name.", {}, async () => {
|
|
33021
|
+
try {
|
|
33022
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33023
|
+
const result = initBuiltinTemplates2();
|
|
33024
|
+
if (result.created === 0)
|
|
33025
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
33026
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
33027
|
+
} catch (e) {
|
|
33028
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33029
|
+
}
|
|
33030
|
+
});
|
|
33031
|
+
}
|
|
33032
|
+
if (shouldRegisterTool("list_template_library")) {
|
|
33033
|
+
server.tool("list_template_library", "List the bundled marketplace-free local template library without mutating the database.", {}, async () => {
|
|
33034
|
+
try {
|
|
33035
|
+
const { listBuiltinTemplates: listBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33036
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
33037
|
+
name: template.name,
|
|
33038
|
+
description: template.description,
|
|
33039
|
+
category: template.category,
|
|
33040
|
+
version: template.version,
|
|
33041
|
+
variables: template.variables,
|
|
33042
|
+
task_count: template.tasks.length
|
|
33043
|
+
}));
|
|
33044
|
+
return { content: [{ type: "text", text: JSON.stringify(templates, null, 2) }] };
|
|
33045
|
+
} catch (e) {
|
|
33046
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33047
|
+
}
|
|
33048
|
+
});
|
|
33049
|
+
}
|
|
33050
|
+
if (shouldRegisterTool("write_template_library")) {
|
|
33051
|
+
server.tool("write_template_library", "Write the bundled local template library to editable JSON files for review or import.", { directory: exports_external.string() }, async ({ directory }) => {
|
|
33052
|
+
try {
|
|
33053
|
+
const { writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
33054
|
+
return { content: [{ type: "text", text: JSON.stringify(writeBuiltinTemplateFiles2(directory), null, 2) }] };
|
|
33055
|
+
} catch (e) {
|
|
33056
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33057
|
+
}
|
|
33058
|
+
});
|
|
33059
|
+
}
|
|
33060
|
+
if (shouldRegisterTool("preview_template")) {
|
|
33061
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
33062
|
+
template_id: exports_external.string(),
|
|
33063
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
33064
|
+
}, async (params) => {
|
|
33065
|
+
try {
|
|
33066
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33067
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
33068
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
33069
|
+
const lines = preview.tasks.map((t) => {
|
|
33070
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
33071
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
33072
|
+
});
|
|
33073
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
33074
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
33075
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
33076
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
33077
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
33078
|
+
${lines.join(`
|
|
33079
|
+
`)}` }] };
|
|
33080
|
+
} catch (e) {
|
|
33081
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33082
|
+
}
|
|
33083
|
+
});
|
|
33084
|
+
}
|
|
33085
|
+
if (shouldRegisterTool("export_template")) {
|
|
33086
|
+
server.tool("export_template", "Export a template as a full JSON object (template + tasks + variables). Useful for sharing or backup.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
33087
|
+
try {
|
|
33088
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33089
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
33090
|
+
const json2 = exportTemplate2(resolvedId);
|
|
33091
|
+
return { content: [{ type: "text", text: JSON.stringify(json2, null, 2) }] };
|
|
33092
|
+
} catch (e) {
|
|
33093
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33094
|
+
}
|
|
33095
|
+
});
|
|
33096
|
+
}
|
|
33097
|
+
if (shouldRegisterTool("import_template")) {
|
|
33098
|
+
server.tool("import_template", "Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.", { json: exports_external.string().describe("JSON string of the template export") }, async ({ json: json2 }) => {
|
|
33099
|
+
try {
|
|
33100
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33101
|
+
const parsed = JSON.parse(json2);
|
|
33102
|
+
const t = importTemplate2(parsed);
|
|
33103
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
33104
|
+
} catch (e) {
|
|
33105
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33106
|
+
}
|
|
33107
|
+
});
|
|
33108
|
+
}
|
|
33109
|
+
if (shouldRegisterTool("template_history")) {
|
|
33110
|
+
server.tool("template_history", "Show version history of a template. Each update creates a snapshot of the previous state.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
33111
|
+
try {
|
|
33112
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
33113
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
33114
|
+
const template = getTemplate2(resolvedId);
|
|
33115
|
+
if (!template)
|
|
33116
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
33117
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
33118
|
+
if (versions.length === 0)
|
|
33119
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
33120
|
+
const lines = versions.map((v) => {
|
|
33121
|
+
const snap = JSON.parse(v.snapshot);
|
|
33122
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
33123
|
+
});
|
|
33124
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
33125
|
+
${lines.join(`
|
|
33126
|
+
`)}` }] };
|
|
33127
|
+
} catch (e) {
|
|
33128
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
33129
|
+
}
|
|
33130
|
+
});
|
|
33131
|
+
}
|
|
33132
|
+
}
|
|
33133
|
+
var init_templates2 = __esm(() => {
|
|
33134
|
+
init_zod();
|
|
33135
|
+
init_tasks();
|
|
33136
|
+
});
|
|
33137
|
+
|
|
32073
33138
|
// src/mcp/index.ts
|
|
32074
33139
|
var exports_mcp = {};
|
|
32075
33140
|
__export(exports_mcp, {
|
|
@@ -32238,6 +33303,7 @@ var init_mcp = __esm(() => {
|
|
|
32238
33303
|
init_code_tools();
|
|
32239
33304
|
init_machines2();
|
|
32240
33305
|
init_agents2();
|
|
33306
|
+
init_templates2();
|
|
32241
33307
|
init_package_version();
|
|
32242
33308
|
init_token_utils();
|
|
32243
33309
|
if (hasVersionFlag()) {
|
|
@@ -32268,6 +33334,7 @@ var init_mcp = __esm(() => {
|
|
|
32268
33334
|
registerTaskRelTools(server, toolContext);
|
|
32269
33335
|
registerCodeTools(server, toolContext);
|
|
32270
33336
|
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
33337
|
+
registerTemplateTools(server, toolContext);
|
|
32271
33338
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
32272
33339
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
32273
33340
|
main().catch(async (err) => {
|
|
@@ -32287,15 +33354,15 @@ __export(exports_mcp_hooks_commands, {
|
|
|
32287
33354
|
});
|
|
32288
33355
|
import chalk8 from "chalk";
|
|
32289
33356
|
import { execSync as execSync3 } from "child_process";
|
|
32290
|
-
import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as
|
|
32291
|
-
import { dirname as dirname10, join as
|
|
33357
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, chmodSync as chmodSync2 } from "fs";
|
|
33358
|
+
import { dirname as dirname10, join as join14 } from "path";
|
|
32292
33359
|
function getMcpBinaryPath() {
|
|
32293
33360
|
try {
|
|
32294
33361
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
32295
33362
|
if (p)
|
|
32296
33363
|
return p;
|
|
32297
33364
|
} catch {}
|
|
32298
|
-
const bunBin =
|
|
33365
|
+
const bunBin = join14(HOME2, ".bun", "bin", "todos-mcp");
|
|
32299
33366
|
if (existsSync13(bunBin))
|
|
32300
33367
|
return bunBin;
|
|
32301
33368
|
return "todos-mcp";
|
|
@@ -32312,8 +33379,8 @@ function readJsonFile2(path) {
|
|
|
32312
33379
|
function writeJsonFile2(path, data) {
|
|
32313
33380
|
const dir = dirname10(path);
|
|
32314
33381
|
if (!existsSync13(dir))
|
|
32315
|
-
|
|
32316
|
-
|
|
33382
|
+
mkdirSync8(dir, { recursive: true });
|
|
33383
|
+
writeFileSync6(path, JSON.stringify(data, null, 2) + `
|
|
32317
33384
|
`);
|
|
32318
33385
|
}
|
|
32319
33386
|
function readTomlFile(path) {
|
|
@@ -32324,8 +33391,8 @@ function readTomlFile(path) {
|
|
|
32324
33391
|
function writeTomlFile(path, content) {
|
|
32325
33392
|
const dir = dirname10(path);
|
|
32326
33393
|
if (!existsSync13(dir))
|
|
32327
|
-
|
|
32328
|
-
|
|
33394
|
+
mkdirSync8(dir, { recursive: true });
|
|
33395
|
+
writeFileSync6(path, content);
|
|
32329
33396
|
}
|
|
32330
33397
|
function removeTomlBlock(content, blockName) {
|
|
32331
33398
|
const lines = content.split(`
|
|
@@ -32389,7 +33456,7 @@ function unregisterClaude(_global) {
|
|
|
32389
33456
|
}
|
|
32390
33457
|
}
|
|
32391
33458
|
function registerCodex(binPath) {
|
|
32392
|
-
const configPath =
|
|
33459
|
+
const configPath = join14(HOME2, ".codex", "config.toml");
|
|
32393
33460
|
let content = readTomlFile(configPath);
|
|
32394
33461
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
32395
33462
|
const block = `
|
|
@@ -32403,7 +33470,7 @@ args = []
|
|
|
32403
33470
|
console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
|
|
32404
33471
|
}
|
|
32405
33472
|
function unregisterCodex() {
|
|
32406
|
-
const configPath =
|
|
33473
|
+
const configPath = join14(HOME2, ".codex", "config.toml");
|
|
32407
33474
|
let content = readTomlFile(configPath);
|
|
32408
33475
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
32409
33476
|
console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -32415,7 +33482,7 @@ function unregisterCodex() {
|
|
|
32415
33482
|
console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
|
|
32416
33483
|
}
|
|
32417
33484
|
function registerGemini(binPath) {
|
|
32418
|
-
const configPath =
|
|
33485
|
+
const configPath = join14(HOME2, ".gemini", "settings.json");
|
|
32419
33486
|
const config = readJsonFile2(configPath);
|
|
32420
33487
|
if (!config["mcpServers"]) {
|
|
32421
33488
|
config["mcpServers"] = {};
|
|
@@ -32429,7 +33496,7 @@ function registerGemini(binPath) {
|
|
|
32429
33496
|
console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
|
|
32430
33497
|
}
|
|
32431
33498
|
function unregisterGemini() {
|
|
32432
|
-
const configPath =
|
|
33499
|
+
const configPath = join14(HOME2, ".gemini", "settings.json");
|
|
32433
33500
|
const config = readJsonFile2(configPath);
|
|
32434
33501
|
const servers = config["mcpServers"];
|
|
32435
33502
|
if (!servers || !("todos" in servers)) {
|
|
@@ -32486,9 +33553,9 @@ function registerMcpHooksCommands(program2) {
|
|
|
32486
33553
|
if (p)
|
|
32487
33554
|
todosBin = p;
|
|
32488
33555
|
} catch {}
|
|
32489
|
-
const hooksDir =
|
|
33556
|
+
const hooksDir = join14(process.cwd(), ".claude", "hooks");
|
|
32490
33557
|
if (!existsSync13(hooksDir))
|
|
32491
|
-
|
|
33558
|
+
mkdirSync8(hooksDir, { recursive: true });
|
|
32492
33559
|
const hookScript = `#!/usr/bin/env bash
|
|
32493
33560
|
# Auto-generated by: todos hooks install
|
|
32494
33561
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -32512,11 +33579,11 @@ esac
|
|
|
32512
33579
|
|
|
32513
33580
|
exit 0
|
|
32514
33581
|
`;
|
|
32515
|
-
const hookPath =
|
|
32516
|
-
|
|
33582
|
+
const hookPath = join14(hooksDir, "todos-sync.sh");
|
|
33583
|
+
writeFileSync6(hookPath, hookScript);
|
|
32517
33584
|
execSync3(`chmod +x "${hookPath}"`);
|
|
32518
33585
|
console.log(chalk8.green(`Hook script created: ${hookPath}`));
|
|
32519
|
-
const settingsPath =
|
|
33586
|
+
const settingsPath = join14(process.cwd(), ".claude", "settings.json");
|
|
32520
33587
|
const settings = readJsonFile2(settingsPath);
|
|
32521
33588
|
if (!settings["hooks"]) {
|
|
32522
33589
|
settings["hooks"] = {};
|
|
@@ -33265,12 +34332,12 @@ Artifacts:`));
|
|
|
33265
34332
|
console.log(chalk8.yellow("Hook already installed."));
|
|
33266
34333
|
return;
|
|
33267
34334
|
}
|
|
33268
|
-
|
|
34335
|
+
writeFileSync6(hookPath, existing + `
|
|
33269
34336
|
${marker}
|
|
33270
34337
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
33271
34338
|
`);
|
|
33272
34339
|
} else {
|
|
33273
|
-
|
|
34340
|
+
writeFileSync6(hookPath, `#!/usr/bin/env bash
|
|
33274
34341
|
${marker}
|
|
33275
34342
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
33276
34343
|
`);
|
|
@@ -33302,7 +34369,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
33302
34369
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
33303
34370
|
(await import("fs")).unlinkSync(hookPath);
|
|
33304
34371
|
} else {
|
|
33305
|
-
|
|
34372
|
+
writeFileSync6(hookPath, cleaned + `
|
|
33306
34373
|
`);
|
|
33307
34374
|
}
|
|
33308
34375
|
console.log(chalk8.green("Post-commit hook removed."));
|
|
@@ -33467,9 +34534,9 @@ __export(exports_machines2, {
|
|
|
33467
34534
|
});
|
|
33468
34535
|
import chalk10 from "chalk";
|
|
33469
34536
|
import { execSync as execSync4 } from "child_process";
|
|
33470
|
-
import { writeFileSync as
|
|
34537
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
33471
34538
|
import { tmpdir as tmpdir2 } from "os";
|
|
33472
|
-
import { join as
|
|
34539
|
+
import { join as join15 } from "path";
|
|
33473
34540
|
function getOrCreateLocalMachineName() {
|
|
33474
34541
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
33475
34542
|
}
|
|
@@ -33663,8 +34730,8 @@ Warning: No primary machine set.`));
|
|
|
33663
34730
|
if (opts.push) {
|
|
33664
34731
|
try {
|
|
33665
34732
|
const localTasks = listTasks3();
|
|
33666
|
-
const tmpFile =
|
|
33667
|
-
|
|
34733
|
+
const tmpFile = join15(tmpdir2(), `todos-export-${uuid()}.json`);
|
|
34734
|
+
writeFileSync7(tmpFile, JSON.stringify(localTasks, null, 2));
|
|
33668
34735
|
execSync4(`scp ${tmpFile} ${ssh}:/tmp/todos-import.json`, { timeout: 15000 });
|
|
33669
34736
|
const importCmd = `ssh ${ssh} 'node -e "const fs=require(\\'fs\\');const tasks=JSON.parse(fs.readFileSync(\\'/tmp/todos-import.json\\',\\'utf-8\\'));console.log(JSON.stringify(tasks.length))"'`;
|
|
33670
34737
|
const count = execSync4(importCmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|