@bretwardjames/tw-bridge 0.7.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/cli.js +130 -7
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1091,9 +1091,12 @@ function stopEntry(key) {
1091
1091
  spawnSync3("timew", ["start", ...remaining], { stdio: "pipe" });
1092
1092
  }
1093
1093
  }
1094
- function getActiveMeetings() {
1094
+ function getActiveEntries(prefix) {
1095
1095
  const tracking = loadTracking();
1096
- return Object.entries(tracking).filter(([key]) => key.startsWith("meeting:")).map(([key, tags]) => ({ key, tags }));
1096
+ return Object.entries(tracking).filter(([key]) => key.startsWith(prefix)).map(([key, tags]) => ({ key, tags }));
1097
+ }
1098
+ function getActiveMeetings() {
1099
+ return getActiveEntries("meeting:");
1097
1100
  }
1098
1101
 
1099
1102
  // src/cli.ts
@@ -1104,6 +1107,7 @@ var commands = {
1104
1107
  sync,
1105
1108
  start: startCmd,
1106
1109
  done: doneCmd,
1110
+ track: trackCmd,
1107
1111
  meeting: meetingCmd,
1108
1112
  timewarrior: timewarriorCmd,
1109
1113
  which,
@@ -1119,7 +1123,8 @@ async function main() {
1119
1123
  console.log(" sync Pull tasks from all backends");
1120
1124
  console.log(" start Start a task by backend ID (e.g., tw-bridge start ghp#123)");
1121
1125
  console.log(" done Complete a task by backend ID (e.g., tw-bridge done ghp#123)");
1122
- console.log(" meeting Track meetings in Timewarrior (no task created)");
1126
+ console.log(" track Track non-task time (e.g., tw-bridge track emails --project ghp)");
1127
+ console.log(" meeting Track meetings (alias for: track start <name> --type meeting)");
1123
1128
  console.log(" timewarrior Manage Timewarrior integration");
1124
1129
  console.log(" which Print the context for the current directory");
1125
1130
  console.log(" config Show current configuration");
@@ -1247,6 +1252,110 @@ function installTimewExtension() {
1247
1252
  Installed Timewarrior extension: ${extTarget} -> ${extSource}`);
1248
1253
  console.log(" Usage: timew bridge [task-time|wall-time] [project-filter]");
1249
1254
  }
1255
+ function sanitizeName(name) {
1256
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1257
+ }
1258
+ async function trackCmd() {
1259
+ const sub = process.argv[3];
1260
+ if (!sub || sub === "--help") {
1261
+ console.log("Usage: tw-bridge track <subcommand>\n");
1262
+ console.log("Subcommands:");
1263
+ console.log(" start <activity> [--project <tag>] [--switch] Start tracking an activity");
1264
+ console.log(" stop [activity] Stop an activity (or all if no name)");
1265
+ console.log(" list Show active non-task tracking");
1266
+ console.log("\nActivities are tracked in Timewarrior only \u2014 no Taskwarrior task is created.");
1267
+ console.log("By default, activities run in parallel with active tasks.");
1268
+ console.log("Use --switch to pause active tasks instead.");
1269
+ console.log("\nExamples:");
1270
+ console.log(" tw-bridge track start emails --project ghp");
1271
+ console.log(" tw-bridge track start comms");
1272
+ console.log(" tw-bridge track stop emails");
1273
+ return;
1274
+ }
1275
+ const config = loadConfig();
1276
+ if (!config.timewarrior?.enabled) {
1277
+ console.error("Timewarrior is not enabled. Run: tw-bridge timewarrior enable");
1278
+ process.exit(1);
1279
+ }
1280
+ if (sub === "start") {
1281
+ const switchMode = process.argv.includes("--switch") || process.argv.includes("-s");
1282
+ const projectFlag = parseFlag("--project");
1283
+ const nameArgs = [];
1284
+ const args = process.argv.slice(4);
1285
+ for (let i = 0; i < args.length; i++) {
1286
+ if (args[i] === "--project") {
1287
+ i++;
1288
+ continue;
1289
+ }
1290
+ if (args[i].startsWith("--") || args[i] === "-s") continue;
1291
+ nameArgs.push(args[i]);
1292
+ }
1293
+ const nameArg = nameArgs.join(" ");
1294
+ if (!nameArg) {
1295
+ console.error("Usage: tw-bridge track start <activity>");
1296
+ process.exit(1);
1297
+ }
1298
+ const tag = sanitizeName(nameArg);
1299
+ const key = `track:${tag}`;
1300
+ const tags = [tag];
1301
+ const project = projectFlag ?? detectProjectContext();
1302
+ if (project) tags.push(project);
1303
+ if (switchMode) {
1304
+ startSwitch(key, tags);
1305
+ } else {
1306
+ startParallel(key, tags);
1307
+ }
1308
+ console.log(`Tracking: ${nameArg}`);
1309
+ console.log(` Tags: ${tags.join(" ")}`);
1310
+ if (!switchMode) {
1311
+ console.log(" Mode: parallel (active tasks continue tracking)");
1312
+ } else {
1313
+ console.log(" Mode: switch (active tasks paused)");
1314
+ }
1315
+ return;
1316
+ }
1317
+ if (sub === "stop") {
1318
+ const nameArg = process.argv.slice(4).join(" ").trim();
1319
+ const active = getActiveEntries("track:");
1320
+ if (active.length === 0) {
1321
+ console.log("No active tracking.");
1322
+ return;
1323
+ }
1324
+ if (nameArg) {
1325
+ const tag = sanitizeName(nameArg);
1326
+ const key = `track:${tag}`;
1327
+ const match = active.find((m) => m.key === key);
1328
+ if (!match) {
1329
+ console.error(`No active tracking matching "${nameArg}".`);
1330
+ console.error(`Active: ${active.map((m) => m.key.replace("track:", "")).join(", ")}`);
1331
+ process.exit(1);
1332
+ }
1333
+ stopEntry(key);
1334
+ console.log(`Stopped tracking: ${nameArg}`);
1335
+ } else {
1336
+ for (const m of active) {
1337
+ stopEntry(m.key);
1338
+ }
1339
+ console.log(`Stopped ${active.length} activity(s): ${active.map((m) => m.key.replace("track:", "")).join(", ")}`);
1340
+ }
1341
+ return;
1342
+ }
1343
+ if (sub === "list") {
1344
+ const active = getActiveEntries("track:");
1345
+ if (active.length === 0) {
1346
+ console.log("No active tracking.");
1347
+ return;
1348
+ }
1349
+ console.log("Active tracking:");
1350
+ for (const m of active) {
1351
+ const name = m.key.replace("track:", "");
1352
+ console.log(` ${name} (${m.tags.join(" ")})`);
1353
+ }
1354
+ return;
1355
+ }
1356
+ console.error(`Unknown subcommand: ${sub}`);
1357
+ process.exit(1);
1358
+ }
1250
1359
  function sanitizeMeetingName(name) {
1251
1360
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1252
1361
  }
@@ -1541,11 +1650,25 @@ function parseBackendRef(ref) {
1541
1650
  }
1542
1651
  async function resolveTaskByRef(ref) {
1543
1652
  const config = loadConfig();
1544
- const backendName = Object.keys(config.backends).find(
1545
- (name) => name === ref.backend || config.backends[name].adapter === ref.backend
1546
- );
1653
+ const repo = parseFlag("--repo");
1654
+ let backendName;
1655
+ if (repo) {
1656
+ const repoName = repo.includes("/") ? repo.split("/").pop() : repo;
1657
+ backendName = Object.keys(config.backends).find((name) => {
1658
+ const b = config.backends[name];
1659
+ if (b.adapter !== ref.backend && name !== ref.backend) return false;
1660
+ const project = b.config?.project;
1661
+ const cwdBase = b.config?.cwd ? path6.basename(b.config.cwd) : void 0;
1662
+ return project?.toLowerCase() === repoName.toLowerCase() || cwdBase?.toLowerCase() === repoName.toLowerCase();
1663
+ });
1664
+ }
1665
+ if (!backendName) {
1666
+ backendName = Object.keys(config.backends).find(
1667
+ (name) => name === ref.backend || config.backends[name].adapter === ref.backend
1668
+ );
1669
+ }
1547
1670
  if (!backendName) {
1548
- console.error(`No backend found matching "${ref.backend}".`);
1671
+ console.error(`No backend found matching "${ref.backend}"${repo ? ` for repo "${repo}"` : ""}.`);
1549
1672
  console.error(`Configured backends: ${Object.keys(config.backends).join(", ")}`);
1550
1673
  process.exit(1);
1551
1674
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bretwardjames/tw-bridge",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Taskwarrior backend bridge — unified sync and hooks for multiple task management platforms",
5
5
  "type": "module",
6
6
  "license": "MIT",