@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 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 Pull tasks from all backends");
304
- console.log(" which Print the context for the current directory");
305
- console.log(" config Show current configuration");
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() {
@@ -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) {
@@ -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": "/home/bretwardjames/IdeaProjects/ghp",
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.1.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": "./dist/cli.js"
19
+ "tw-bridge": "dist/cli.js"
20
20
  },
21
21
  "files": [
22
22
  "dist",