@bretwardjames/tw-bridge 0.1.0 → 0.3.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/dist/cli.js +80 -3
- package/dist/hooks/on-modify.js +32 -2
- package/examples/config.json +5 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -290,6 +290,7 @@ var commands = {
|
|
|
290
290
|
add: addBackend,
|
|
291
291
|
install,
|
|
292
292
|
sync,
|
|
293
|
+
timewarrior: timewarriorCmd,
|
|
293
294
|
which,
|
|
294
295
|
config: showConfig
|
|
295
296
|
};
|
|
@@ -300,9 +301,10 @@ async function main() {
|
|
|
300
301
|
console.log("Commands:");
|
|
301
302
|
console.log(" add Add a new backend instance");
|
|
302
303
|
console.log(" install Install Taskwarrior hooks and shell integration");
|
|
303
|
-
console.log(" sync
|
|
304
|
-
console.log("
|
|
305
|
-
console.log("
|
|
304
|
+
console.log(" sync Pull tasks from all backends");
|
|
305
|
+
console.log(" timewarrior Manage Timewarrior integration");
|
|
306
|
+
console.log(" which Print the context for the current directory");
|
|
307
|
+
console.log(" config Show current configuration");
|
|
306
308
|
return;
|
|
307
309
|
}
|
|
308
310
|
const handler = commands[command];
|
|
@@ -393,8 +395,83 @@ async function install() {
|
|
|
393
395
|
console.log("urgency.user.tag.in_review.coefficient=-2.0");
|
|
394
396
|
console.log("urgency.user.tag.ready_for_beta.coefficient=-4.0");
|
|
395
397
|
console.log("urgency.user.tag.in_beta.coefficient=-6.0");
|
|
398
|
+
if (fs2.existsSync(STANDARD_TIMEW_HOOK)) {
|
|
399
|
+
console.log("\nTimewarrior hook detected. To enable tw-bridge time tracking:");
|
|
400
|
+
console.log(" tw-bridge timewarrior enable-overlaps");
|
|
401
|
+
}
|
|
396
402
|
installShellFunction();
|
|
397
403
|
}
|
|
404
|
+
var STANDARD_TIMEW_HOOK = path3.join(HOOKS_DIR, "on-modify.timewarrior");
|
|
405
|
+
async function timewarriorCmd() {
|
|
406
|
+
const sub = process.argv[3];
|
|
407
|
+
if (!sub || sub === "--help") {
|
|
408
|
+
console.log("Usage: tw-bridge timewarrior <subcommand>\n");
|
|
409
|
+
console.log("Subcommands:");
|
|
410
|
+
console.log(" enable Enable Timewarrior tracking");
|
|
411
|
+
console.log(" enable-overlaps Enable with overlapping intervals (for parallel work)");
|
|
412
|
+
console.log(" disable Disable Timewarrior tracking");
|
|
413
|
+
console.log(" status Show current Timewarrior configuration");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const config = loadConfig();
|
|
417
|
+
if (sub === "status") {
|
|
418
|
+
const tw = config.timewarrior;
|
|
419
|
+
if (!tw?.enabled) {
|
|
420
|
+
console.log("Timewarrior: disabled");
|
|
421
|
+
} else {
|
|
422
|
+
console.log(`Timewarrior: enabled (overlaps: ${tw.allow_overlaps ? "yes" : "no"})`);
|
|
423
|
+
}
|
|
424
|
+
const hookExists = fs2.existsSync(STANDARD_TIMEW_HOOK);
|
|
425
|
+
const hookDisabled = fs2.existsSync(STANDARD_TIMEW_HOOK + ".disabled");
|
|
426
|
+
if (hookExists) {
|
|
427
|
+
console.log(`Standard hook: active (${STANDARD_TIMEW_HOOK})`);
|
|
428
|
+
if (tw?.enabled) {
|
|
429
|
+
console.log(" Warning: may cause double-tracking. Run `tw-bridge timewarrior enable` to fix.");
|
|
430
|
+
}
|
|
431
|
+
} else if (hookDisabled) {
|
|
432
|
+
console.log("Standard hook: disabled");
|
|
433
|
+
} else {
|
|
434
|
+
console.log("Standard hook: not found");
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (sub === "enable" || sub === "enable-overlaps") {
|
|
439
|
+
const allowOverlaps = sub === "enable-overlaps";
|
|
440
|
+
config.timewarrior = {
|
|
441
|
+
enabled: true,
|
|
442
|
+
allow_overlaps: allowOverlaps
|
|
443
|
+
};
|
|
444
|
+
const configPath = saveConfig(config);
|
|
445
|
+
console.log(`Timewarrior tracking enabled (overlaps: ${allowOverlaps ? "yes" : "no"})`);
|
|
446
|
+
console.log(`Config: ${configPath}`);
|
|
447
|
+
if (fs2.existsSync(STANDARD_TIMEW_HOOK)) {
|
|
448
|
+
const disabled = STANDARD_TIMEW_HOOK + ".disabled";
|
|
449
|
+
fs2.renameSync(STANDARD_TIMEW_HOOK, disabled);
|
|
450
|
+
console.log(`
|
|
451
|
+
Disabled standard hook: ${STANDARD_TIMEW_HOOK} -> .disabled`);
|
|
452
|
+
console.log("tw-bridge will handle Timewarrior tracking directly.");
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (sub === "disable") {
|
|
457
|
+
config.timewarrior = {
|
|
458
|
+
enabled: false,
|
|
459
|
+
allow_overlaps: false
|
|
460
|
+
};
|
|
461
|
+
const configPath = saveConfig(config);
|
|
462
|
+
console.log("Timewarrior tracking disabled");
|
|
463
|
+
console.log(`Config: ${configPath}`);
|
|
464
|
+
const disabled = STANDARD_TIMEW_HOOK + ".disabled";
|
|
465
|
+
if (fs2.existsSync(disabled)) {
|
|
466
|
+
fs2.renameSync(disabled, STANDARD_TIMEW_HOOK);
|
|
467
|
+
console.log(`
|
|
468
|
+
Restored standard hook: ${STANDARD_TIMEW_HOOK}`);
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
console.error(`Unknown subcommand: ${sub}`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
398
475
|
var SHELL_FUNCTION = `
|
|
399
476
|
# tw-bridge: auto-context task wrapper
|
|
400
477
|
task() {
|
package/dist/hooks/on-modify.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/hooks/on-modify.ts
|
|
4
4
|
import fs2 from "fs";
|
|
5
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
6
|
|
|
6
7
|
// src/config.ts
|
|
7
8
|
import fs from "fs";
|
|
@@ -176,17 +177,46 @@ async function resolveAdapter(task, config) {
|
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
// src/hooks/on-modify.ts
|
|
180
|
+
function timewTags(task) {
|
|
181
|
+
const tags = [];
|
|
182
|
+
if (task.backend) tags.push(task.backend);
|
|
183
|
+
if (task.backend_id) tags.push(`#${task.backend_id}`);
|
|
184
|
+
if (task.project) tags.push(task.project);
|
|
185
|
+
for (const tag of task.tags ?? []) {
|
|
186
|
+
if (!tag.includes("_")) tags.push(tag);
|
|
187
|
+
}
|
|
188
|
+
return [...new Set(tags)];
|
|
189
|
+
}
|
|
190
|
+
function handleTimewarrior(config, oldTask, newTask, wasStarted, wasStopped, wasCompleted) {
|
|
191
|
+
const twConfig = config.timewarrior;
|
|
192
|
+
if (!twConfig?.enabled) return;
|
|
193
|
+
const overlap = twConfig.allow_overlaps ?? false;
|
|
194
|
+
if (wasStarted) {
|
|
195
|
+
const tags = timewTags(newTask);
|
|
196
|
+
const args = ["start", ...tags];
|
|
197
|
+
if (overlap) args.splice(1, 0, ":overlap");
|
|
198
|
+
spawnSync2("timew", args, { stdio: "pipe" });
|
|
199
|
+
} else if (wasStopped || wasCompleted) {
|
|
200
|
+
const tags = timewTags(oldTask);
|
|
201
|
+
if (overlap) {
|
|
202
|
+
spawnSync2("timew", ["stop", ...tags], { stdio: "pipe" });
|
|
203
|
+
} else {
|
|
204
|
+
spawnSync2("timew", ["stop"], { stdio: "pipe" });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
179
208
|
async function main() {
|
|
180
209
|
const input = fs2.readFileSync("/dev/stdin", "utf-8").trim().split("\n");
|
|
181
210
|
const oldTask = JSON.parse(input[0]);
|
|
182
211
|
const newTask = JSON.parse(input[1]);
|
|
183
212
|
process.stdout.write(JSON.stringify(newTask) + "\n");
|
|
184
213
|
const config = loadConfig();
|
|
185
|
-
const adapter = await resolveAdapter(newTask, config);
|
|
186
|
-
if (!adapter) return;
|
|
187
214
|
const wasStarted = !oldTask.start && !!newTask.start;
|
|
188
215
|
const wasStopped = !!oldTask.start && !newTask.start && newTask.status === "pending";
|
|
189
216
|
const wasCompleted = oldTask.status !== "completed" && newTask.status === "completed";
|
|
217
|
+
handleTimewarrior(config, oldTask, newTask, wasStarted, wasStopped, wasCompleted);
|
|
218
|
+
const adapter = await resolveAdapter(newTask, config);
|
|
219
|
+
if (!adapter) return;
|
|
190
220
|
let ttyFd = null;
|
|
191
221
|
try {
|
|
192
222
|
if (wasStarted && adapter.onStart) {
|
package/examples/config.json
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"timewarrior": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"allow_overlaps": true
|
|
5
|
+
},
|
|
2
6
|
"backends": {
|
|
3
7
|
"ghp": {
|
|
4
8
|
"adapter": "ghp",
|
|
@@ -6,7 +10,7 @@
|
|
|
6
10
|
"tags": ["ghp"]
|
|
7
11
|
},
|
|
8
12
|
"config": {
|
|
9
|
-
"cwd": "/
|
|
13
|
+
"cwd": "/path/to/your/repo",
|
|
10
14
|
"project": "ghp"
|
|
11
15
|
}
|
|
12
16
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bretwardjames/tw-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Taskwarrior backend bridge — unified sync and hooks for multiple task management platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/bretwardjames/tw-bridge.git"
|
|
9
|
+
"url": "git+https://github.com/bretwardjames/tw-bridge.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"taskwarrior",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sync"
|
|
17
17
|
],
|
|
18
18
|
"bin": {
|
|
19
|
-
"tw-bridge": "
|
|
19
|
+
"tw-bridge": "dist/cli.js"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|