opentasks 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +174 -49
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +451 -15
- package/dist/cli.js.map +1 -1
- package/dist/client/client.d.ts +54 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +148 -4
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/core/discover.d.ts.map +1 -1
- package/dist/core/discover.js +7 -2
- package/dist/core/discover.js.map +1 -1
- package/dist/core/hash.d.ts.map +1 -1
- package/dist/core/hash.js +7 -0
- package/dist/core/hash.js.map +1 -1
- package/dist/core/id.d.ts +1 -1
- package/dist/core/id.d.ts.map +1 -1
- package/dist/core/id.js +2 -0
- package/dist/core/id.js.map +1 -1
- package/dist/core/worktree.js +4 -4
- package/dist/core/worktree.js.map +1 -1
- package/dist/daemon/events.d.ts +82 -0
- package/dist/daemon/events.d.ts.map +1 -0
- package/dist/daemon/events.js +71 -0
- package/dist/daemon/events.js.map +1 -0
- package/dist/daemon/flush.d.ts.map +1 -1
- package/dist/daemon/flush.js +12 -2
- package/dist/daemon/flush.js.map +1 -1
- package/dist/daemon/health-counters.d.ts +38 -0
- package/dist/daemon/health-counters.d.ts.map +1 -0
- package/dist/daemon/health-counters.js +42 -0
- package/dist/daemon/health-counters.js.map +1 -0
- package/dist/daemon/idempotency.d.ts +50 -0
- package/dist/daemon/idempotency.d.ts.map +1 -0
- package/dist/daemon/idempotency.js +104 -0
- package/dist/daemon/idempotency.js.map +1 -0
- package/dist/daemon/ipc.d.ts +32 -2
- package/dist/daemon/ipc.d.ts.map +1 -1
- package/dist/daemon/ipc.js +54 -3
- package/dist/daemon/ipc.js.map +1 -1
- package/dist/daemon/lifecycle.d.ts.map +1 -1
- package/dist/daemon/lifecycle.js +145 -9
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/location-state.d.ts +60 -1
- package/dist/daemon/location-state.d.ts.map +1 -1
- package/dist/daemon/location-state.js +136 -31
- package/dist/daemon/location-state.js.map +1 -1
- package/dist/daemon/methods/context-files.d.ts.map +1 -1
- package/dist/daemon/methods/context-files.js +4 -3
- package/dist/daemon/methods/context-files.js.map +1 -1
- package/dist/daemon/methods/events.d.ts +22 -0
- package/dist/daemon/methods/events.d.ts.map +1 -0
- package/dist/daemon/methods/events.js +30 -0
- package/dist/daemon/methods/events.js.map +1 -0
- package/dist/daemon/methods/graph.d.ts +7 -0
- package/dist/daemon/methods/graph.d.ts.map +1 -1
- package/dist/daemon/methods/graph.js +24 -14
- package/dist/daemon/methods/graph.js.map +1 -1
- package/dist/daemon/methods/lifecycle.d.ts +16 -0
- package/dist/daemon/methods/lifecycle.d.ts.map +1 -1
- package/dist/daemon/methods/lifecycle.js +3 -1
- package/dist/daemon/methods/lifecycle.js.map +1 -1
- package/dist/daemon/methods/location.js.map +1 -1
- package/dist/daemon/methods/sync.d.ts +48 -2
- package/dist/daemon/methods/sync.d.ts.map +1 -1
- package/dist/daemon/methods/sync.js +106 -3
- package/dist/daemon/methods/sync.js.map +1 -1
- package/dist/daemon/methods/tools.d.ts.map +1 -1
- package/dist/daemon/methods/tools.js +42 -8
- package/dist/daemon/methods/tools.js.map +1 -1
- package/dist/daemon/methods/watch.d.ts +24 -0
- package/dist/daemon/methods/watch.d.ts.map +1 -1
- package/dist/daemon/methods/watch.js +178 -88
- package/dist/daemon/methods/watch.js.map +1 -1
- package/dist/graph/EdgeTypeRegistry.d.ts.map +1 -1
- package/dist/graph/EdgeTypeRegistry.js +32 -0
- package/dist/graph/EdgeTypeRegistry.js.map +1 -1
- package/dist/graph/FederatedGraph.d.ts.map +1 -1
- package/dist/graph/GraphologyAdapter.d.ts.map +1 -1
- package/dist/graph/GraphologyAdapter.js.map +1 -1
- package/dist/graph/HydratingFederatedGraph.d.ts.map +1 -1
- package/dist/graph/HydratingFederatedGraph.js +1 -1
- package/dist/graph/HydratingFederatedGraph.js.map +1 -1
- package/dist/graph/attempts.d.ts +50 -0
- package/dist/graph/attempts.d.ts.map +1 -0
- package/dist/graph/attempts.js +51 -0
- package/dist/graph/attempts.js.map +1 -0
- package/dist/graph/coordination.d.ts +4 -2
- package/dist/graph/coordination.d.ts.map +1 -1
- package/dist/graph/coordination.js +77 -85
- package/dist/graph/coordination.js.map +1 -1
- package/dist/graph/expansion.d.ts.map +1 -1
- package/dist/graph/expansion.js +1 -1
- package/dist/graph/expansion.js.map +1 -1
- package/dist/graph/git-graph-syncer.d.ts +24 -0
- package/dist/graph/git-graph-syncer.d.ts.map +1 -1
- package/dist/graph/git-graph-syncer.js +95 -28
- package/dist/graph/git-graph-syncer.js.map +1 -1
- package/dist/graph/provider-store.d.ts +12 -1
- package/dist/graph/provider-store.d.ts.map +1 -1
- package/dist/graph/provider-store.js +35 -8
- package/dist/graph/provider-store.js.map +1 -1
- package/dist/graph/query.d.ts.map +1 -1
- package/dist/graph/query.js +18 -2
- package/dist/graph/query.js.map +1 -1
- package/dist/graph/store.d.ts +18 -2
- package/dist/graph/store.d.ts.map +1 -1
- package/dist/graph/store.js +43 -0
- package/dist/graph/store.js.map +1 -1
- package/dist/graph/sync.js +1 -1
- package/dist/graph/sync.js.map +1 -1
- package/dist/graph/types.d.ts +9 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/types.js.map +1 -1
- package/dist/graph/validation.d.ts.map +1 -1
- package/dist/graph/validation.js +20 -2
- package/dist/graph/validation.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/materialization/archiver.d.ts.map +1 -1
- package/dist/materialization/archiver.js.map +1 -1
- package/dist/materialization/snapshot.d.ts.map +1 -1
- package/dist/materialization/snapshot.js +1 -1
- package/dist/materialization/snapshot.js.map +1 -1
- package/dist/materialization/types.d.ts +7 -5
- package/dist/materialization/types.d.ts.map +1 -1
- package/dist/materialization/types.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +308 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/providers/beads.d.ts.map +1 -1
- package/dist/providers/beads.js +2 -0
- package/dist/providers/beads.js.map +1 -1
- package/dist/providers/claude-tasks.d.ts.map +1 -1
- package/dist/providers/claude-tasks.js.map +1 -1
- package/dist/providers/global.js +1 -1
- package/dist/providers/global.js.map +1 -1
- package/dist/providers/location.d.ts.map +1 -1
- package/dist/providers/location.js.map +1 -1
- package/dist/providers/map-client-factory.d.ts.map +1 -1
- package/dist/providers/map-client-factory.js +1 -1
- package/dist/providers/map-client-factory.js.map +1 -1
- package/dist/providers/map-connector.js +1 -1
- package/dist/providers/map-connector.js.map +1 -1
- package/dist/providers/map-event-bridge.d.ts.map +1 -1
- package/dist/providers/map-event-bridge.js +1 -0
- package/dist/providers/map-event-bridge.js.map +1 -1
- package/dist/providers/map.d.ts.map +1 -1
- package/dist/providers/map.js +5 -2
- package/dist/providers/map.js.map +1 -1
- package/dist/providers/native.d.ts.map +1 -1
- package/dist/providers/native.js +21 -8
- package/dist/providers/native.js.map +1 -1
- package/dist/providers/registry.js +1 -1
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/sudocode.d.ts.map +1 -1
- package/dist/providers/sudocode.js +5 -11
- package/dist/providers/sudocode.js.map +1 -1
- package/dist/providers/traits/Reconcilable.d.ts.map +1 -1
- package/dist/providers/traits/TaskManageable.d.ts +15 -1
- package/dist/providers/traits/TaskManageable.d.ts.map +1 -1
- package/dist/providers/traits/TaskManageable.js +26 -0
- package/dist/providers/traits/TaskManageable.js.map +1 -1
- package/dist/providers/traits/Watchable.d.ts +6 -0
- package/dist/providers/traits/Watchable.d.ts.map +1 -1
- package/dist/providers/traits/Watchable.js.map +1 -1
- package/dist/providers/types.d.ts +1 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/schema/edges.d.ts +5 -1
- package/dist/schema/edges.d.ts.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/nodes.d.ts +55 -2
- package/dist/schema/nodes.d.ts.map +1 -1
- package/dist/schema/storage.d.ts +2 -0
- package/dist/schema/storage.d.ts.map +1 -1
- package/dist/schema/validation.d.ts +6 -2
- package/dist/schema/validation.d.ts.map +1 -1
- package/dist/schema/validation.js +18 -1
- package/dist/schema/validation.js.map +1 -1
- package/dist/sessionlog/agent/registry.js.map +1 -1
- package/dist/sessionlog/agent/session-types.d.ts.map +1 -1
- package/dist/sessionlog/agent/types.d.ts.map +1 -1
- package/dist/sessionlog/commands/clean.js +1 -1
- package/dist/sessionlog/commands/clean.js.map +1 -1
- package/dist/sessionlog/commands/disable.d.ts.map +1 -1
- package/dist/sessionlog/commands/disable.js.map +1 -1
- package/dist/sessionlog/commands/doctor.js +1 -1
- package/dist/sessionlog/commands/doctor.js.map +1 -1
- package/dist/sessionlog/commands/enable.js +1 -1
- package/dist/sessionlog/commands/enable.js.map +1 -1
- package/dist/sessionlog/commands/resume.js +1 -1
- package/dist/sessionlog/commands/resume.js.map +1 -1
- package/dist/sessionlog/commands/rewind.d.ts.map +1 -1
- package/dist/sessionlog/commands/rewind.js +1 -1
- package/dist/sessionlog/commands/rewind.js.map +1 -1
- package/dist/sessionlog/commands/status.d.ts.map +1 -1
- package/dist/sessionlog/commands/status.js.map +1 -1
- package/dist/sessionlog/hooks/lifecycle.d.ts.map +1 -1
- package/dist/sessionlog/hooks/lifecycle.js +3 -3
- package/dist/sessionlog/hooks/lifecycle.js.map +1 -1
- package/dist/sessionlog/security/redaction.js +1 -1
- package/dist/sessionlog/security/redaction.js.map +1 -1
- package/dist/sessionlog/store/checkpoint-store.d.ts.map +1 -1
- package/dist/sessionlog/store/checkpoint-store.js +2 -2
- package/dist/sessionlog/store/checkpoint-store.js.map +1 -1
- package/dist/sessionlog/store/native-store.d.ts.map +1 -1
- package/dist/sessionlog/store/native-store.js.map +1 -1
- package/dist/sessionlog/store/session-store.d.ts.map +1 -1
- package/dist/sessionlog/store/session-store.js +1 -1
- package/dist/sessionlog/store/session-store.js.map +1 -1
- package/dist/sessionlog/strategy/attribution.js +2 -2
- package/dist/sessionlog/strategy/attribution.js.map +1 -1
- package/dist/sessionlog/strategy/common.d.ts +1 -1
- package/dist/sessionlog/strategy/common.d.ts.map +1 -1
- package/dist/sessionlog/strategy/common.js +3 -3
- package/dist/sessionlog/strategy/common.js.map +1 -1
- package/dist/sessionlog/strategy/content-overlap.d.ts.map +1 -1
- package/dist/sessionlog/strategy/content-overlap.js +1 -1
- package/dist/sessionlog/strategy/content-overlap.js.map +1 -1
- package/dist/sessionlog/strategy/manual-commit.d.ts.map +1 -1
- package/dist/sessionlog/strategy/manual-commit.js +6 -16
- package/dist/sessionlog/strategy/manual-commit.js.map +1 -1
- package/dist/sessionlog/utils/preview-rewind.d.ts.map +1 -1
- package/dist/sessionlog/utils/preview-rewind.js +1 -1
- package/dist/sessionlog/utils/preview-rewind.js.map +1 -1
- package/dist/sessionlog/utils/trailers.d.ts.map +1 -1
- package/dist/sessionlog/utils/trailers.js +0 -1
- package/dist/sessionlog/utils/trailers.js.map +1 -1
- package/dist/storage/interface.d.ts +64 -0
- package/dist/storage/interface.d.ts.map +1 -1
- package/dist/storage/interface.js.map +1 -1
- package/dist/storage/jsonl.js +2 -2
- package/dist/storage/jsonl.js.map +1 -1
- package/dist/storage/sqlite-schema.d.ts +8 -2
- package/dist/storage/sqlite-schema.d.ts.map +1 -1
- package/dist/storage/sqlite-schema.js +10 -1
- package/dist/storage/sqlite-schema.js.map +1 -1
- package/dist/storage/sqlite.d.ts +16 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +134 -2
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/link.js +1 -1
- package/dist/tools/link.js.map +1 -1
- package/dist/tools/query.d.ts.map +1 -1
- package/dist/tools/query.js +8 -29
- package/dist/tools/query.js.map +1 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +115 -5
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/types.d.ts +84 -3
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/package.json +32 -7
- package/skills/opentasks/SKILL.md +226 -0
- package/skills/opentasks/dependency-management.md +119 -0
- package/skills/opentasks/feedback-and-review.md +100 -0
- package/skills/opentasks/linking-external-data.md +103 -0
- package/skills/opentasks/spec-to-implementation.md +171 -0
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import * as path from 'node:path';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
11
13
|
import { generateLocationIdentity } from './core/location.js';
|
|
12
14
|
import { ensureGlobalStoreInitialized } from './core/init.js';
|
|
13
15
|
import { createConnection, checkAllConnectionHealth } from './core/connections.js';
|
|
@@ -16,17 +18,24 @@ import { mergeJsonl, installMergeDriver } from './core/merge-driver.js';
|
|
|
16
18
|
import { discoverLocations } from './core/discover.js';
|
|
17
19
|
import { OpenTasksClient } from './client/client.js';
|
|
18
20
|
import { createDaemonWithStore, createMultiLocationDaemonFromGit, checkExistingDaemon } from './daemon/index.js';
|
|
21
|
+
import { VERSION } from './version.js';
|
|
19
22
|
const OPENTASKS_DIR = '.opentasks';
|
|
23
|
+
const OPENSWARM_OPENTASKS_DIR = '.openswarm/opentasks';
|
|
20
24
|
const SWARM_OPENTASKS_DIR = '.swarm/opentasks';
|
|
21
25
|
const CONFIG_FILE = 'config.json';
|
|
22
26
|
/**
|
|
23
27
|
* Resolve the project-level opentasks directory.
|
|
24
|
-
* Priority: OPENTASKS_PROJECT_DIR env
|
|
28
|
+
* Priority: OPENTASKS_PROJECT_DIR env > .openswarm/opentasks > .swarm/opentasks
|
|
29
|
+
* (legacy) > .opentasks. The swarmkit namespace is migrating .swarm → .openswarm;
|
|
30
|
+
* both are honored so mixed-version repos keep working, preferring the new name.
|
|
25
31
|
*/
|
|
26
32
|
function resolveProjectDir(baseDir = process.cwd()) {
|
|
27
33
|
const envDir = process.env.OPENTASKS_PROJECT_DIR;
|
|
28
34
|
if (envDir)
|
|
29
35
|
return path.resolve(baseDir, envDir);
|
|
36
|
+
const openswarmDir = path.resolve(baseDir, OPENSWARM_OPENTASKS_DIR);
|
|
37
|
+
if (fs.existsSync(openswarmDir))
|
|
38
|
+
return openswarmDir;
|
|
30
39
|
const swarmDir = path.resolve(baseDir, SWARM_OPENTASKS_DIR);
|
|
31
40
|
if (fs.existsSync(swarmDir))
|
|
32
41
|
return swarmDir;
|
|
@@ -34,12 +43,12 @@ function resolveProjectDir(baseDir = process.cwd()) {
|
|
|
34
43
|
}
|
|
35
44
|
function printHelp() {
|
|
36
45
|
console.log(`
|
|
37
|
-
opentasks
|
|
46
|
+
opentasks v${VERSION}
|
|
38
47
|
|
|
39
48
|
Usage:
|
|
40
49
|
opentasks <command> [options]
|
|
41
50
|
|
|
42
|
-
Tool commands (
|
|
51
|
+
Tool commands (auto-start the daemon if needed; --no-autostart to opt out):
|
|
43
52
|
link --from <id> --to <id> --type <type> [--remove] [--metadata <json>]
|
|
44
53
|
query <json> Query the graph (pass QueryParams as JSON)
|
|
45
54
|
annotate <json> Manage feedback (pass AnnotateParams as JSON)
|
|
@@ -48,9 +57,20 @@ Tool commands (require running daemon):
|
|
|
48
57
|
update <id> [options] Update a node
|
|
49
58
|
delete <id> [--hard] Delete a node
|
|
50
59
|
context-summary [options] Get context summary breadcrumbs for session continuity
|
|
60
|
+
claim <id> --agent <id> [--duration <ms>] [--force] Atomically claim a task (lease)
|
|
61
|
+
claim-next --agent <id> [--duration <ms>] Claim the next ready, unclaimed task
|
|
62
|
+
release <id> --agent <id> [--fence <n>] Release a claim
|
|
63
|
+
renew <id> --agent <id> [--duration <ms>] [--fence <n>] Renew/extend a claim's lease
|
|
64
|
+
|
|
65
|
+
Views (compact, human-readable; --json for raw, --limit <n>, default ${DEFAULT_LIST_LIMIT}):
|
|
66
|
+
ready [--tags a,b] Unblocked tasks ready to work on
|
|
67
|
+
list [--type <t>] [--status <s>] [--tags a,b] [--all] Tasks (closed hidden unless --all)
|
|
68
|
+
blocked Open tasks waiting on an unresolved blocker
|
|
69
|
+
tree <id> A task and its blocker dependency tree
|
|
70
|
+
cleanup [--older-than-days <n>] [--dry-run] Archive terminal tasks older than n days (default 30)
|
|
51
71
|
|
|
52
72
|
Create options:
|
|
53
|
-
--status <s> Status (
|
|
73
|
+
--status <s> Status (default: open for tasks)
|
|
54
74
|
--content <text> Markdown content
|
|
55
75
|
--uri <uri> External URI (for external nodes)
|
|
56
76
|
--source <src> Source system (for external nodes)
|
|
@@ -87,6 +107,11 @@ Setup commands:
|
|
|
87
107
|
discover [options] Find nearby opentasks locations
|
|
88
108
|
merge-driver <O> <A> <B> JSONL merge driver (for git)
|
|
89
109
|
|
|
110
|
+
Global options (any command):
|
|
111
|
+
--global Target the shared store at ~/.opentasks (use from
|
|
112
|
+
anywhere; same as OPENTASKS_GLOBAL=1)
|
|
113
|
+
--no-autostart Don't auto-start a daemon (OPENTASKS_NO_AUTOSTART=1)
|
|
114
|
+
|
|
90
115
|
Archive commands (require running daemon with archival enabled):
|
|
91
116
|
archive list [--graph <id>] [--source <src>] List archived sessions
|
|
92
117
|
archive restore <uri> Restore a node from archive
|
|
@@ -133,7 +158,9 @@ function writeConfig(opentasksDir, config) {
|
|
|
133
158
|
function cmdInit(args) {
|
|
134
159
|
const nameIndex = args.indexOf('--name');
|
|
135
160
|
const name = nameIndex !== -1 ? args[nameIndex + 1] : undefined;
|
|
136
|
-
|
|
161
|
+
// `--global` is stripped + routed via OPENTASKS_PROJECT_DIR in main(); detect
|
|
162
|
+
// it from the resolved target dir so the global init message still fires.
|
|
163
|
+
const isGlobal = resolveProjectDir() === path.join(os.homedir(), '.opentasks');
|
|
137
164
|
// Global init delegates to the shared utility
|
|
138
165
|
if (isGlobal) {
|
|
139
166
|
ensureGlobalStoreInitialized(name ?? 'global');
|
|
@@ -443,12 +470,87 @@ function cmdDiscover(args) {
|
|
|
443
470
|
// ============================================================================
|
|
444
471
|
// Daemon Commands
|
|
445
472
|
// ============================================================================
|
|
473
|
+
/** Resolve the path to this CLI entrypoint (dist/cli.js) for re-spawning. */
|
|
474
|
+
function cliEntrypoint() {
|
|
475
|
+
return fileURLToPath(import.meta.url);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Resolve the directory the daemon actually uses for its lock + socket.
|
|
479
|
+
*
|
|
480
|
+
* In a git repo the daemon runs in multi-location mode with its home at
|
|
481
|
+
* `<git-common-dir>/opentasks` (shared across worktrees); otherwise it's the
|
|
482
|
+
* location's own `.opentasks`. This must match `cmdDaemonStart`'s single-vs-multi
|
|
483
|
+
* decision, or the CLI's `checkExistingDaemon` probes look in the wrong place and
|
|
484
|
+
* never detect a running git-repo daemon (auto-start "times out", `daemon status`
|
|
485
|
+
* reports not_running, `daemon stop` no-ops — even though the daemon is up).
|
|
486
|
+
*/
|
|
487
|
+
export function daemonHomeFor(opentasksDir) {
|
|
488
|
+
const gitCommonDir = getGitCommonDir(path.dirname(opentasksDir));
|
|
489
|
+
return gitCommonDir ? path.join(gitCommonDir, 'opentasks') : opentasksDir;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Spawn a detached daemon process (`daemon start --foreground`) and let it
|
|
493
|
+
* outlive this process. The child inherits cwd + env so it resolves the same
|
|
494
|
+
* project as the parent.
|
|
495
|
+
*/
|
|
496
|
+
function spawnDetachedDaemon() {
|
|
497
|
+
const child = spawn(process.execPath, [cliEntrypoint(), 'daemon', 'start', '--foreground'], {
|
|
498
|
+
detached: true,
|
|
499
|
+
stdio: 'ignore',
|
|
500
|
+
});
|
|
501
|
+
child.unref();
|
|
502
|
+
}
|
|
503
|
+
/** Sleep helper for poll loops. */
|
|
504
|
+
function delay(ms) {
|
|
505
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Poll until the daemon for `opentasksDir` reports running, or throw on timeout.
|
|
509
|
+
*/
|
|
510
|
+
async function waitForDaemon(opentasksDir, timeoutMs = 10_000) {
|
|
511
|
+
const daemonHome = daemonHomeFor(opentasksDir);
|
|
512
|
+
const start = Date.now();
|
|
513
|
+
for (;;) {
|
|
514
|
+
const existing = await checkExistingDaemon(daemonHome);
|
|
515
|
+
if (existing.running)
|
|
516
|
+
return;
|
|
517
|
+
if (Date.now() - start >= timeoutMs) {
|
|
518
|
+
throw new Error(`Daemon did not start within ${timeoutMs}ms`);
|
|
519
|
+
}
|
|
520
|
+
await delay(100);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Ensure a daemon is running for `opentasksDir`, auto-starting one if needed
|
|
525
|
+
* (4.1). Auto-start is on by default; opt out with `--no-autostart` or
|
|
526
|
+
* `OPENTASKS_NO_AUTOSTART=1`. When auto-start is disabled and no daemon is
|
|
527
|
+
* running, this is a no-op — the subsequent client connect surfaces a clear
|
|
528
|
+
* "not connected" error.
|
|
529
|
+
*/
|
|
530
|
+
async function ensureDaemonRunning(opentasksDir, options = { autostart: true }) {
|
|
531
|
+
const daemonHome = daemonHomeFor(opentasksDir);
|
|
532
|
+
const existing = await checkExistingDaemon(daemonHome);
|
|
533
|
+
if (existing.running)
|
|
534
|
+
return;
|
|
535
|
+
if (!options.autostart)
|
|
536
|
+
return;
|
|
537
|
+
process.stderr.write('opentasks: no daemon running — starting one…\n');
|
|
538
|
+
spawnDetachedDaemon();
|
|
539
|
+
try {
|
|
540
|
+
await waitForDaemon(opentasksDir);
|
|
541
|
+
const started = await checkExistingDaemon(daemonHome);
|
|
542
|
+
process.stderr.write(`opentasks: daemon started (pid ${started.pid ?? '?'})\n`);
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
process.stderr.write(`opentasks: daemon auto-start failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
446
548
|
/**
|
|
447
549
|
* Start the daemon process.
|
|
448
550
|
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
551
|
+
* Detaches by default and returns once the daemon is accepting connections.
|
|
552
|
+
* Pass `--foreground` to run it in the foreground (used by the detached child
|
|
553
|
+
* and by callers that manage the lifecycle themselves).
|
|
452
554
|
*/
|
|
453
555
|
async function cmdDaemonStart(args) {
|
|
454
556
|
const opentasksDir = resolveProjectDir();
|
|
@@ -460,8 +562,9 @@ async function cmdDaemonStart(args) {
|
|
|
460
562
|
fs.writeFileSync(graphPath, '', 'utf-8');
|
|
461
563
|
}
|
|
462
564
|
}
|
|
463
|
-
// Check if already running
|
|
464
|
-
const
|
|
565
|
+
// Check if already running (probe the daemon's real home — .git/opentasks in a repo)
|
|
566
|
+
const daemonHome = daemonHomeFor(opentasksDir);
|
|
567
|
+
const existing = await checkExistingDaemon(daemonHome);
|
|
465
568
|
if (existing.running) {
|
|
466
569
|
console.log(JSON.stringify({
|
|
467
570
|
status: 'already_running',
|
|
@@ -470,9 +573,24 @@ async function cmdDaemonStart(args) {
|
|
|
470
573
|
}));
|
|
471
574
|
return;
|
|
472
575
|
}
|
|
576
|
+
// Detach by default (4.2): re-spawn ourselves in the foreground, wait until
|
|
577
|
+
// the daemon is accepting connections, then report and return. `--foreground`
|
|
578
|
+
// runs the blocking daemon loop in-process.
|
|
579
|
+
if (!args.includes('--foreground')) {
|
|
580
|
+
spawnDetachedDaemon();
|
|
581
|
+
await waitForDaemon(opentasksDir);
|
|
582
|
+
const started = await checkExistingDaemon(daemonHome);
|
|
583
|
+
console.log(JSON.stringify({
|
|
584
|
+
status: 'started',
|
|
585
|
+
detached: true,
|
|
586
|
+
pid: started.pid,
|
|
587
|
+
socketPath: started.socketPath,
|
|
588
|
+
}));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
473
591
|
// Detect multi-location (git worktree) vs single-location mode
|
|
474
592
|
const gitCommonDir = getGitCommonDir(path.dirname(opentasksDir));
|
|
475
|
-
const version =
|
|
593
|
+
const version = VERSION;
|
|
476
594
|
let daemon;
|
|
477
595
|
if (gitCommonDir) {
|
|
478
596
|
daemon = createMultiLocationDaemonFromGit({
|
|
@@ -511,7 +629,7 @@ async function cmdDaemonStart(args) {
|
|
|
511
629
|
*/
|
|
512
630
|
async function cmdDaemonStop() {
|
|
513
631
|
const opentasksDir = resolveProjectDir();
|
|
514
|
-
const existing = await checkExistingDaemon(opentasksDir);
|
|
632
|
+
const existing = await checkExistingDaemon(daemonHomeFor(opentasksDir));
|
|
515
633
|
if (!existing.running) {
|
|
516
634
|
console.log(JSON.stringify({ status: 'not_running' }));
|
|
517
635
|
return;
|
|
@@ -523,7 +641,7 @@ async function cmdDaemonStop() {
|
|
|
523
641
|
client.disconnect();
|
|
524
642
|
console.log(JSON.stringify({ status: 'stopped', ...result }));
|
|
525
643
|
}
|
|
526
|
-
catch (
|
|
644
|
+
catch (_error) {
|
|
527
645
|
client.disconnect();
|
|
528
646
|
// If the connection was refused or reset, the daemon may have already stopped
|
|
529
647
|
console.log(JSON.stringify({ status: 'stopped', note: 'daemon may have already exited' }));
|
|
@@ -534,7 +652,7 @@ async function cmdDaemonStop() {
|
|
|
534
652
|
*/
|
|
535
653
|
async function cmdDaemonStatus() {
|
|
536
654
|
const opentasksDir = resolveProjectDir();
|
|
537
|
-
const existing = await checkExistingDaemon(opentasksDir);
|
|
655
|
+
const existing = await checkExistingDaemon(daemonHomeFor(opentasksDir));
|
|
538
656
|
if (!existing.running) {
|
|
539
657
|
console.log(JSON.stringify({ status: 'not_running', locationPath: opentasksDir }));
|
|
540
658
|
return;
|
|
@@ -582,6 +700,172 @@ async function runToolCommand(fn) {
|
|
|
582
700
|
process.exit(1);
|
|
583
701
|
}
|
|
584
702
|
}
|
|
703
|
+
// ============================================================================
|
|
704
|
+
// Human-readable commands (token-light sugar over `query`) — P4 4.4
|
|
705
|
+
// ============================================================================
|
|
706
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
707
|
+
const TERMINAL_STATUSES = new Set(['closed', 'failed', 'abandoned']);
|
|
708
|
+
/** One compact line per task — id, status, priority, truncated title. */
|
|
709
|
+
function printTaskRows(rows, total, limit) {
|
|
710
|
+
if (rows.length === 0) {
|
|
711
|
+
console.log('(none)');
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const shown = rows.slice(0, limit);
|
|
715
|
+
for (const r of shown) {
|
|
716
|
+
const id = (r.id ?? '').padEnd(10).slice(0, 10);
|
|
717
|
+
const status = (r.status ?? '').padEnd(11).slice(0, 11);
|
|
718
|
+
const pri = r.priority !== undefined ? `P${r.priority}` : ' ';
|
|
719
|
+
const raw = r.title ?? '';
|
|
720
|
+
const title = raw.length > 64 ? raw.slice(0, 63) + '…' : raw;
|
|
721
|
+
console.log(`${id} ${status} ${pri} ${title}`);
|
|
722
|
+
}
|
|
723
|
+
const omitted = total - shown.length;
|
|
724
|
+
if (omitted > 0) {
|
|
725
|
+
console.log(`… ${omitted} more (showing ${shown.length} of ${total}; use --limit / --all)`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/** Run `fn` with a connected client; report errors as JSON; always disconnect. */
|
|
729
|
+
async function withClient(fn) {
|
|
730
|
+
const client = new OpenTasksClient();
|
|
731
|
+
try {
|
|
732
|
+
await fn(client);
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
console.error(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
736
|
+
process.exitCode = 1;
|
|
737
|
+
}
|
|
738
|
+
finally {
|
|
739
|
+
client.disconnect();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
export async function cmdReady(args) {
|
|
743
|
+
const limit = Number(getFlag(args, '--limit') ?? DEFAULT_LIST_LIMIT);
|
|
744
|
+
const tags = getFlag(args, '--tags')?.split(',').map((s) => s.trim());
|
|
745
|
+
const json = hasFlag(args, '--json');
|
|
746
|
+
await withClient(async (client) => {
|
|
747
|
+
const result = (await client.query({ ready: { limit, tags } }));
|
|
748
|
+
const items = result.items ?? [];
|
|
749
|
+
if (json) {
|
|
750
|
+
console.log(JSON.stringify(items, null, 2));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
printTaskRows(items, result.total ?? items.length, limit);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
export async function cmdList(args) {
|
|
757
|
+
const type = getFlag(args, '--type');
|
|
758
|
+
const status = getFlag(args, '--status');
|
|
759
|
+
const tags = getFlag(args, '--tags')?.split(',').map((s) => s.trim());
|
|
760
|
+
const all = hasFlag(args, '--all');
|
|
761
|
+
const limit = Number(getFlag(args, '--limit') ?? DEFAULT_LIST_LIMIT);
|
|
762
|
+
const json = hasFlag(args, '--json');
|
|
763
|
+
await withClient(async (client) => {
|
|
764
|
+
const nodes = { archived: false };
|
|
765
|
+
if (type)
|
|
766
|
+
nodes.type = type;
|
|
767
|
+
if (status)
|
|
768
|
+
nodes.status = status;
|
|
769
|
+
if (tags)
|
|
770
|
+
nodes.tags = tags;
|
|
771
|
+
const result = (await client.query({ nodes }));
|
|
772
|
+
let items = result.items ?? [];
|
|
773
|
+
// Hide terminal (closed/failed/abandoned) tasks by default; --all or an
|
|
774
|
+
// explicit --status overrides.
|
|
775
|
+
if (!all && !status) {
|
|
776
|
+
items = items.filter((i) => !TERMINAL_STATUSES.has(i.status ?? ''));
|
|
777
|
+
}
|
|
778
|
+
if (json) {
|
|
779
|
+
console.log(JSON.stringify(items.slice(0, limit), null, 2));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
printTaskRows(items, items.length, limit);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
export async function cmdBlocked(args) {
|
|
786
|
+
const limit = Number(getFlag(args, '--limit') ?? DEFAULT_LIST_LIMIT);
|
|
787
|
+
const json = hasFlag(args, '--json');
|
|
788
|
+
await withClient(async (client) => {
|
|
789
|
+
const [active, ready] = await Promise.all([
|
|
790
|
+
client.query({ nodes: { type: 'task', archived: false } }),
|
|
791
|
+
client.query({ ready: { limit: 100_000 } }),
|
|
792
|
+
]);
|
|
793
|
+
const readyIds = new Set((ready.items ?? []).map((i) => i.id));
|
|
794
|
+
// An open/blocked task that isn't in the ready set is waiting on a blocker.
|
|
795
|
+
const blocked = (active.items ?? []).filter((i) => (i.status === 'open' || i.status === 'blocked') && !readyIds.has(i.id));
|
|
796
|
+
if (json) {
|
|
797
|
+
console.log(JSON.stringify(blocked, null, 2));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
printTaskRows(blocked, blocked.length, limit);
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
export async function cmdCleanup(args) {
|
|
804
|
+
const ttlDays = Number(getFlag(args, '--older-than-days') ?? 30);
|
|
805
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
806
|
+
const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1000;
|
|
807
|
+
await withClient(async (client) => {
|
|
808
|
+
// Terminal, not-yet-archived tasks are the cleanup candidates.
|
|
809
|
+
const result = (await client.query({
|
|
810
|
+
nodes: { type: 'task', status: ['closed', 'failed', 'abandoned'], archived: false },
|
|
811
|
+
}));
|
|
812
|
+
const candidates = result.items ?? [];
|
|
813
|
+
// NodeSummary carries no timestamp, so fetch each candidate to check age.
|
|
814
|
+
const stale = [];
|
|
815
|
+
for (const c of candidates) {
|
|
816
|
+
const full = (await client.getNode(c.id));
|
|
817
|
+
const ts = full?.updated_at ?? full?.created_at;
|
|
818
|
+
if (ts && new Date(ts).getTime() <= cutoff) {
|
|
819
|
+
stale.push({ id: c.id, title: c.title, status: c.status, ts });
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (dryRun) {
|
|
823
|
+
console.log(JSON.stringify({ dryRun: true, olderThanDays: ttlDays, wouldArchive: stale.length, tasks: stale }, null, 2));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
for (const t of stale) {
|
|
827
|
+
await client.updateNode(t.id, { archived: true });
|
|
828
|
+
}
|
|
829
|
+
console.log(JSON.stringify({ archived: stale.length, olderThanDays: ttlDays }, null, 2));
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
export async function cmdTree(args) {
|
|
833
|
+
const id = args.find((a) => !a.startsWith('--'));
|
|
834
|
+
if (!id) {
|
|
835
|
+
console.error('Usage: opentasks tree <id> [--json]');
|
|
836
|
+
process.exit(1);
|
|
837
|
+
}
|
|
838
|
+
const json = hasFlag(args, '--json');
|
|
839
|
+
await withClient(async (client) => {
|
|
840
|
+
const root = (await client.getNode(id));
|
|
841
|
+
if (!root) {
|
|
842
|
+
console.error(JSON.stringify({ error: `Not found: ${id}` }));
|
|
843
|
+
process.exitCode = 1;
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
// Walk the blocker tree (what this task depends on), depth-limited + cycle-safe.
|
|
847
|
+
const lines = [];
|
|
848
|
+
const seen = new Set();
|
|
849
|
+
async function walk(node, depth) {
|
|
850
|
+
const indent = ' '.repeat(depth);
|
|
851
|
+
const marker = depth === 0 ? '' : '↳ ';
|
|
852
|
+
lines.push(`${indent}${marker}${node.id} [${node.status ?? '?'}] ${node.title ?? ''}`);
|
|
853
|
+
if (seen.has(node.id) || depth >= 6)
|
|
854
|
+
return;
|
|
855
|
+
seen.add(node.id);
|
|
856
|
+
const blockers = (await client.blockers(node.id));
|
|
857
|
+
for (const b of blockers) {
|
|
858
|
+
await walk(b, depth + 1);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
await walk(root, 0);
|
|
862
|
+
if (json) {
|
|
863
|
+
console.log(JSON.stringify({ root: id, tree: lines }, null, 2));
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
console.log(lines.join('\n'));
|
|
867
|
+
});
|
|
868
|
+
}
|
|
585
869
|
export async function cmdLink(args) {
|
|
586
870
|
const fromId = getFlag(args, '--from');
|
|
587
871
|
const toId = getFlag(args, '--to');
|
|
@@ -634,6 +918,82 @@ export async function cmdAnnotate(args) {
|
|
|
634
918
|
return result;
|
|
635
919
|
});
|
|
636
920
|
}
|
|
921
|
+
export async function cmdClaim(args) {
|
|
922
|
+
const id = args[0];
|
|
923
|
+
const agentId = getFlag(args, '--agent');
|
|
924
|
+
if (!id || id.startsWith('--') || !agentId) {
|
|
925
|
+
console.error('Usage: opentasks claim <id> --agent <agentId> [--duration <ms>] [--force]');
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
const durationStr = getFlag(args, '--duration');
|
|
929
|
+
const claim = { id, agentId };
|
|
930
|
+
if (durationStr)
|
|
931
|
+
claim.durationMs = Number(durationStr);
|
|
932
|
+
if (hasFlag(args, '--force'))
|
|
933
|
+
claim.force = true;
|
|
934
|
+
const client = new OpenTasksClient();
|
|
935
|
+
await runToolCommand(async () => {
|
|
936
|
+
const result = await client.task({ claim });
|
|
937
|
+
client.disconnect();
|
|
938
|
+
return result;
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
export async function cmdClaimNext(args) {
|
|
942
|
+
const agentId = getFlag(args, '--agent');
|
|
943
|
+
if (!agentId) {
|
|
944
|
+
console.error('Usage: opentasks claim-next --agent <agentId> [--duration <ms>]');
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
const durationStr = getFlag(args, '--duration');
|
|
948
|
+
const claimNext = { agentId };
|
|
949
|
+
if (durationStr)
|
|
950
|
+
claimNext.durationMs = Number(durationStr);
|
|
951
|
+
const client = new OpenTasksClient();
|
|
952
|
+
await runToolCommand(async () => {
|
|
953
|
+
const result = await client.task({ claimNext });
|
|
954
|
+
client.disconnect();
|
|
955
|
+
return result;
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
export async function cmdRelease(args) {
|
|
959
|
+
const id = args[0];
|
|
960
|
+
const agentId = getFlag(args, '--agent');
|
|
961
|
+
if (!id || id.startsWith('--') || !agentId) {
|
|
962
|
+
console.error('Usage: opentasks release <id> --agent <agentId> [--fence <n>]');
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
const fenceStr = getFlag(args, '--fence');
|
|
966
|
+
const release = { id, agentId };
|
|
967
|
+
if (fenceStr)
|
|
968
|
+
release.fence = Number(fenceStr);
|
|
969
|
+
const client = new OpenTasksClient();
|
|
970
|
+
await runToolCommand(async () => {
|
|
971
|
+
const result = await client.task({ release });
|
|
972
|
+
client.disconnect();
|
|
973
|
+
return result;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
export async function cmdRenew(args) {
|
|
977
|
+
const id = args[0];
|
|
978
|
+
const agentId = getFlag(args, '--agent');
|
|
979
|
+
if (!id || id.startsWith('--') || !agentId) {
|
|
980
|
+
console.error('Usage: opentasks renew <id> --agent <agentId> [--duration <ms>] [--fence <n>]');
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
const durationStr = getFlag(args, '--duration');
|
|
984
|
+
const fenceStr = getFlag(args, '--fence');
|
|
985
|
+
const renew = { id, agentId };
|
|
986
|
+
if (durationStr)
|
|
987
|
+
renew.durationMs = Number(durationStr);
|
|
988
|
+
if (fenceStr)
|
|
989
|
+
renew.fence = Number(fenceStr);
|
|
990
|
+
const client = new OpenTasksClient();
|
|
991
|
+
await runToolCommand(async () => {
|
|
992
|
+
const result = await client.task({ renew });
|
|
993
|
+
client.disconnect();
|
|
994
|
+
return result;
|
|
995
|
+
});
|
|
996
|
+
}
|
|
637
997
|
export async function cmdCreate(args) {
|
|
638
998
|
const type = getFlag(args, '--type');
|
|
639
999
|
const title = getFlag(args, '--title');
|
|
@@ -652,6 +1012,8 @@ export async function cmdCreate(args) {
|
|
|
652
1012
|
const metadataStr = getFlag(args, '--metadata');
|
|
653
1013
|
if (status)
|
|
654
1014
|
params.status = status;
|
|
1015
|
+
else if (type === 'task')
|
|
1016
|
+
params.status = 'open';
|
|
655
1017
|
if (content)
|
|
656
1018
|
params.content = content;
|
|
657
1019
|
if (uri)
|
|
@@ -887,6 +1249,13 @@ async function cmdMcp(args) {
|
|
|
887
1249
|
scopeStr = args[++i];
|
|
888
1250
|
}
|
|
889
1251
|
}
|
|
1252
|
+
// Auto-start the project daemon so a fresh MCP session works with zero manual
|
|
1253
|
+
// daemon management (4.1). Skip when an explicit `--socket` is given (the
|
|
1254
|
+
// daemon is externally managed) or auto-start is disabled. Notices go to
|
|
1255
|
+
// stderr so they don't corrupt the stdio JSON-RPC channel.
|
|
1256
|
+
if (!socketPath && process.env.OPENTASKS_NO_AUTOSTART !== '1') {
|
|
1257
|
+
await ensureDaemonRunning(resolveProjectDir(), { autostart: true });
|
|
1258
|
+
}
|
|
890
1259
|
const { startMCPServer, ALL_SCOPES } = await import('./mcp/index.js');
|
|
891
1260
|
const scopes = scopeStr === 'all'
|
|
892
1261
|
? [...ALL_SCOPES]
|
|
@@ -901,13 +1270,52 @@ async function cmdMcp(args) {
|
|
|
901
1270
|
function padRight(str, len) {
|
|
902
1271
|
return str.length >= len ? str + ' ' : str + ' '.repeat(len - str.length);
|
|
903
1272
|
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Commands that talk to the daemon. For these, `main` ensures a daemon is
|
|
1275
|
+
* running first (auto-starting one unless opted out).
|
|
1276
|
+
*/
|
|
1277
|
+
const DAEMON_COMMANDS = new Set([
|
|
1278
|
+
'link',
|
|
1279
|
+
'query',
|
|
1280
|
+
'annotate',
|
|
1281
|
+
'create',
|
|
1282
|
+
'get',
|
|
1283
|
+
'update',
|
|
1284
|
+
'delete',
|
|
1285
|
+
'context-summary',
|
|
1286
|
+
'claim',
|
|
1287
|
+
'claim-next',
|
|
1288
|
+
'release',
|
|
1289
|
+
'renew',
|
|
1290
|
+
'ready',
|
|
1291
|
+
'list',
|
|
1292
|
+
'tree',
|
|
1293
|
+
'blocked',
|
|
1294
|
+
'cleanup',
|
|
1295
|
+
]);
|
|
904
1296
|
async function main() {
|
|
905
|
-
const
|
|
1297
|
+
const rawArgs = process.argv.slice(2);
|
|
1298
|
+
// Global flags, stripped so per-command arg parsing isn't disturbed.
|
|
1299
|
+
const autostart = !rawArgs.includes('--no-autostart') && process.env.OPENTASKS_NO_AUTOSTART !== '1';
|
|
1300
|
+
const globalMode = rawArgs.includes('--global') || process.env.OPENTASKS_GLOBAL === '1';
|
|
1301
|
+
const args = rawArgs.filter((a) => a !== '--no-autostart' && a !== '--global');
|
|
906
1302
|
const command = args[0];
|
|
907
1303
|
if (!command || command === 'help' || command === '--help') {
|
|
908
1304
|
printHelp();
|
|
909
1305
|
process.exit(0);
|
|
910
1306
|
}
|
|
1307
|
+
// --global / OPENTASKS_GLOBAL: target the use-from-anywhere store at
|
|
1308
|
+
// ~/.opentasks. Routing both the auto-start (resolveProjectDir) and the
|
|
1309
|
+
// client's socket discovery (findOpenTasksDir) through OPENTASKS_PROJECT_DIR
|
|
1310
|
+
// keeps them in agreement. ensureGlobalStoreInitialized creates the dir so the
|
|
1311
|
+
// existence check downstream passes even on first use. (4.7)
|
|
1312
|
+
if (globalMode) {
|
|
1313
|
+
process.env.OPENTASKS_PROJECT_DIR = ensureGlobalStoreInitialized();
|
|
1314
|
+
}
|
|
1315
|
+
// Auto-start the daemon for commands that need it (4.1).
|
|
1316
|
+
if (DAEMON_COMMANDS.has(command)) {
|
|
1317
|
+
await ensureDaemonRunning(resolveProjectDir(), { autostart });
|
|
1318
|
+
}
|
|
911
1319
|
switch (command) {
|
|
912
1320
|
// Tool commands (async, require daemon)
|
|
913
1321
|
case 'link':
|
|
@@ -934,6 +1342,34 @@ async function main() {
|
|
|
934
1342
|
case 'context-summary':
|
|
935
1343
|
await cmdContextSummary(args.slice(1));
|
|
936
1344
|
break;
|
|
1345
|
+
case 'claim':
|
|
1346
|
+
await cmdClaim(args.slice(1));
|
|
1347
|
+
break;
|
|
1348
|
+
case 'claim-next':
|
|
1349
|
+
await cmdClaimNext(args.slice(1));
|
|
1350
|
+
break;
|
|
1351
|
+
case 'release':
|
|
1352
|
+
await cmdRelease(args.slice(1));
|
|
1353
|
+
break;
|
|
1354
|
+
case 'renew':
|
|
1355
|
+
await cmdRenew(args.slice(1));
|
|
1356
|
+
break;
|
|
1357
|
+
// Human-readable commands (sugar over query)
|
|
1358
|
+
case 'ready':
|
|
1359
|
+
await cmdReady(args.slice(1));
|
|
1360
|
+
break;
|
|
1361
|
+
case 'list':
|
|
1362
|
+
await cmdList(args.slice(1));
|
|
1363
|
+
break;
|
|
1364
|
+
case 'blocked':
|
|
1365
|
+
await cmdBlocked(args.slice(1));
|
|
1366
|
+
break;
|
|
1367
|
+
case 'tree':
|
|
1368
|
+
await cmdTree(args.slice(1));
|
|
1369
|
+
break;
|
|
1370
|
+
case 'cleanup':
|
|
1371
|
+
await cmdCleanup(args.slice(1));
|
|
1372
|
+
break;
|
|
937
1373
|
// Setup commands (sync, no daemon needed)
|
|
938
1374
|
case 'init': {
|
|
939
1375
|
const initArgs = args.slice(1);
|