@bretwardjames/tw-bridge 0.6.0 → 0.7.1

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
@@ -1006,6 +1006,34 @@ function completeTask(existing, keepTags) {
1006
1006
  );
1007
1007
  return result.status === 0;
1008
1008
  }
1009
+ function findTaskByBackendId(backend, backendId) {
1010
+ const existing = getExistingTasks(backend);
1011
+ return existing.get(backendId) ?? null;
1012
+ }
1013
+ function startTask(uuid) {
1014
+ const result = spawnSync2(
1015
+ "task",
1016
+ ["rc.confirmation=off", uuid, "start"],
1017
+ {
1018
+ encoding: "utf-8",
1019
+ stdio: ["pipe", "pipe", "pipe"],
1020
+ env: { ...process.env, TW_BRIDGE_REVERSE_SYNC: "1" }
1021
+ }
1022
+ );
1023
+ return result.status === 0;
1024
+ }
1025
+ function doneTask(uuid) {
1026
+ const result = spawnSync2(
1027
+ "task",
1028
+ ["rc.confirmation=off", uuid, "done"],
1029
+ {
1030
+ encoding: "utf-8",
1031
+ stdio: ["pipe", "pipe", "pipe"],
1032
+ env: { ...process.env, TW_BRIDGE_REVERSE_SYNC: "1" }
1033
+ }
1034
+ );
1035
+ return result.status === 0;
1036
+ }
1009
1037
  function updateTaskDescription(existing, newDescription) {
1010
1038
  if (existing.description === newDescription) return false;
1011
1039
  const result = spawnSync2(
@@ -1074,6 +1102,8 @@ var commands = {
1074
1102
  add: addBackend,
1075
1103
  install,
1076
1104
  sync,
1105
+ start: startCmd,
1106
+ done: doneCmd,
1077
1107
  meeting: meetingCmd,
1078
1108
  timewarrior: timewarriorCmd,
1079
1109
  which,
@@ -1087,6 +1117,8 @@ async function main() {
1087
1117
  console.log(" add Add a new backend instance");
1088
1118
  console.log(" install Install Taskwarrior hooks and shell integration");
1089
1119
  console.log(" sync Pull tasks from all backends");
1120
+ console.log(" start Start a task by backend ID (e.g., tw-bridge start ghp#123)");
1121
+ console.log(" done Complete a task by backend ID (e.g., tw-bridge done ghp#123)");
1090
1122
  console.log(" meeting Track meetings in Timewarrior (no task created)");
1091
1123
  console.log(" timewarrior Manage Timewarrior integration");
1092
1124
  console.log(" which Print the context for the current directory");
@@ -1502,6 +1534,89 @@ async function sync() {
1502
1534
  await syncBackend(name, config.backends[name], config);
1503
1535
  }
1504
1536
  }
1537
+ function parseBackendRef(ref) {
1538
+ const match = ref.match(/^([^#]+)#(.+)$/);
1539
+ if (!match) return null;
1540
+ return { backend: match[1], id: match[2] };
1541
+ }
1542
+ async function resolveTaskByRef(ref) {
1543
+ const config = loadConfig();
1544
+ const repo = parseFlag("--repo");
1545
+ let backendName;
1546
+ if (repo) {
1547
+ const repoName = repo.includes("/") ? repo.split("/").pop() : repo;
1548
+ backendName = Object.keys(config.backends).find((name) => {
1549
+ const b = config.backends[name];
1550
+ if (b.adapter !== ref.backend && name !== ref.backend) return false;
1551
+ const project = b.config?.project;
1552
+ const cwdBase = b.config?.cwd ? path6.basename(b.config.cwd) : void 0;
1553
+ return project?.toLowerCase() === repoName.toLowerCase() || cwdBase?.toLowerCase() === repoName.toLowerCase();
1554
+ });
1555
+ }
1556
+ if (!backendName) {
1557
+ backendName = Object.keys(config.backends).find(
1558
+ (name) => name === ref.backend || config.backends[name].adapter === ref.backend
1559
+ );
1560
+ }
1561
+ if (!backendName) {
1562
+ console.error(`No backend found matching "${ref.backend}"${repo ? ` for repo "${repo}"` : ""}.`);
1563
+ console.error(`Configured backends: ${Object.keys(config.backends).join(", ")}`);
1564
+ process.exit(1);
1565
+ }
1566
+ let task = findTaskByBackendId(backendName, ref.id);
1567
+ if (!task) {
1568
+ console.log(`Task #${ref.id} not in Taskwarrior yet, syncing ${backendName}...`);
1569
+ await syncBackend(backendName, config.backends[backendName], config);
1570
+ task = findTaskByBackendId(backendName, ref.id);
1571
+ }
1572
+ if (!task) {
1573
+ console.error(`Task #${ref.id} not found in backend "${backendName}" after sync.`);
1574
+ process.exit(1);
1575
+ }
1576
+ return task;
1577
+ }
1578
+ async function startCmd() {
1579
+ const ref = process.argv[3];
1580
+ if (!ref || ref.startsWith("--")) {
1581
+ console.error("Usage: tw-bridge start <backend>#<id> (e.g., tw-bridge start ghp#123)");
1582
+ process.exit(1);
1583
+ }
1584
+ const parsed = parseBackendRef(ref);
1585
+ if (!parsed) {
1586
+ console.error(`Invalid reference "${ref}". Expected format: backend#id (e.g., ghp#123)`);
1587
+ process.exit(1);
1588
+ }
1589
+ const task = await resolveTaskByRef(parsed);
1590
+ if (task.start) {
1591
+ console.log(`Task #${parsed.id} is already started (${task.uuid.slice(0, 8)})`);
1592
+ return;
1593
+ }
1594
+ if (startTask(task.uuid)) {
1595
+ console.log(`Started: [#${parsed.id}] ${task.description} (${task.uuid.slice(0, 8)})`);
1596
+ } else {
1597
+ console.error(`Failed to start task ${task.uuid}`);
1598
+ process.exit(1);
1599
+ }
1600
+ }
1601
+ async function doneCmd() {
1602
+ const ref = process.argv[3];
1603
+ if (!ref || ref.startsWith("--")) {
1604
+ console.error("Usage: tw-bridge done <backend>#<id> (e.g., tw-bridge done ghp#123)");
1605
+ process.exit(1);
1606
+ }
1607
+ const parsed = parseBackendRef(ref);
1608
+ if (!parsed) {
1609
+ console.error(`Invalid reference "${ref}". Expected format: backend#id (e.g., ghp#123)`);
1610
+ process.exit(1);
1611
+ }
1612
+ const task = await resolveTaskByRef(parsed);
1613
+ if (doneTask(task.uuid)) {
1614
+ console.log(`Completed: [#${parsed.id}] ${task.description} (${task.uuid.slice(0, 8)})`);
1615
+ } else {
1616
+ console.error(`Failed to complete task ${task.uuid}`);
1617
+ process.exit(1);
1618
+ }
1619
+ }
1505
1620
  async function syncBackend(name, backend, config) {
1506
1621
  const adapter = await resolveAdapter(
1507
1622
  { backend: name },
@@ -1068,6 +1068,7 @@ async function main() {
1068
1068
  } else if (wasStopped || wasCompleted) {
1069
1069
  handleTimewarriorStop(config, oldTask);
1070
1070
  }
1071
+ if (process.env.TW_BRIDGE_REVERSE_SYNC) return;
1071
1072
  const adapter = await resolveAdapter(newTask, config);
1072
1073
  if (!adapter) return;
1073
1074
  if (wasStarted && adapter.onStart) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bretwardjames/tw-bridge",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Taskwarrior backend bridge — unified sync and hooks for multiple task management platforms",
5
5
  "type": "module",
6
6
  "license": "MIT",