@kud/foxhop-cli 1.0.3 → 1.1.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 +119 -16
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { defineCommand, runMain } from "citty";
5
5
  import { spawn } from "child_process";
6
+ import { existsSync as existsSync3 } from "fs";
6
7
 
7
8
  // src/config.ts
8
9
  import { mkdirSync, readFileSync, writeFileSync, existsSync } from "fs";
@@ -52,6 +53,10 @@ var SCHEMA = {
52
53
  enum: ["recent", "first", "pinned"],
53
54
  default: "recent",
54
55
  description: "Which tab to focus when several match"
56
+ },
57
+ favorite: {
58
+ type: "boolean",
59
+ description: "Pin this target to the top of the list"
55
60
  }
56
61
  }
57
62
  }
@@ -112,6 +117,34 @@ var removeTarget = (name) => {
112
117
  if (removed) writeConfig({ targets: filtered });
113
118
  return { targets: filtered, removed };
114
119
  };
120
+ var toggleFavorite = (name) => {
121
+ const { targets } = readConfig();
122
+ let favorite = false;
123
+ let found = false;
124
+ const next = targets.map((target) => {
125
+ if (target.name !== name) return target;
126
+ found = true;
127
+ favorite = !target.favorite;
128
+ const { favorite: _was, ...rest } = target;
129
+ return favorite ? { ...rest, favorite: true } : rest;
130
+ });
131
+ if (found) writeConfig({ targets: next });
132
+ return { favorite, found };
133
+ };
134
+ var deriveTarget = (url) => {
135
+ let host = url.trim();
136
+ try {
137
+ host = new URL(url).hostname;
138
+ } catch {
139
+ host = url.replace(/^[a-z]+:\/\//i, "").split("/")[0] || url;
140
+ }
141
+ const labels = host.split(".").filter(Boolean);
142
+ const generic = /* @__PURE__ */ new Set(["www", "app", "web", "m", "my", "go"]);
143
+ const candidates = labels.length > 1 ? labels.slice(0, -1) : labels;
144
+ const name = (candidates.find((label) => !generic.has(label)) ?? candidates[0] ?? host).toLowerCase();
145
+ const title = name.charAt(0).toUpperCase() + name.slice(1);
146
+ return { name, match: host, title };
147
+ };
115
148
 
116
149
  // src/client.ts
117
150
  import net from "net";
@@ -245,6 +278,14 @@ var sync = (node, cli, dir = defaultScriptsDir()) => {
245
278
  }
246
279
  return { dir, written: targets.length, removed: removed.length };
247
280
  };
281
+ var clearScripts = (dir = defaultScriptsDir()) => {
282
+ if (!existsSync2(dir)) return { dir, written: 0, removed: 0 };
283
+ const removed = readdirSync(dir).filter((file) => file.startsWith("focus-") && file.endsWith(".sh")).filter((file) => isGenerated(join4(dir, file)));
284
+ for (const file of removed) rmSync(join4(dir, file));
285
+ const icon = join4(dir, ICON_FILE);
286
+ if (existsSync2(icon)) rmSync(icon);
287
+ return { dir, written: 0, removed: removed.length };
288
+ };
248
289
 
249
290
  // src/cli.ts
250
291
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -260,6 +301,11 @@ var runOpen = (args) => {
260
301
  };
261
302
  var foreground = () => runOpen(["-a", browserApp()]);
262
303
  var openUrl = (url) => runOpen([url]);
304
+ var autoSync = () => {
305
+ if (existsSync3(defaultScriptsDir())) {
306
+ sync(process.execPath, fileURLToPath2(import.meta.url));
307
+ }
308
+ };
263
309
  var focus = defineCommand({
264
310
  meta: {
265
311
  name: "focus",
@@ -344,8 +390,13 @@ var list = defineCommand({
344
390
  );
345
391
  return;
346
392
  }
347
- for (const target of targets) {
348
- console.log(`${target.name.padEnd(16)} ${target.match}`);
393
+ const ordered = [
394
+ ...targets.filter((target) => target.favorite),
395
+ ...targets.filter((target) => !target.favorite)
396
+ ];
397
+ for (const target of ordered) {
398
+ const star = target.favorite ? "\u2605 " : " ";
399
+ console.log(`${star}${target.name.padEnd(16)} ${target.match}`);
349
400
  }
350
401
  }
351
402
  });
@@ -398,9 +449,24 @@ var syncCommand = defineCommand({
398
449
  type: "string",
399
450
  description: `Output directory (default: ${defaultScriptsDir()})`
400
451
  },
452
+ clean: {
453
+ type: "boolean",
454
+ description: "Remove all generated scripts instead of writing them"
455
+ },
401
456
  json: { type: "boolean", description: "Output the result as JSON" }
402
457
  },
403
458
  run: ({ args }) => {
459
+ if (args.clean) {
460
+ const cleared = clearScripts(args.dir);
461
+ if (args.json) {
462
+ process.stdout.write(JSON.stringify(cleared) + "\n");
463
+ return;
464
+ }
465
+ console.log(
466
+ `foxhop: removed ${cleared.removed} script(s) from ${cleared.dir}`
467
+ );
468
+ return;
469
+ }
404
470
  const result = sync(
405
471
  process.execPath,
406
472
  fileURLToPath2(import.meta.url),
@@ -419,20 +485,25 @@ var syncCommand = defineCommand({
419
485
  }
420
486
  });
421
487
  var add = defineCommand({
422
- meta: { name: "add", description: "Add or update a target in tabs.json" },
488
+ meta: {
489
+ name: "add",
490
+ description: "Add or update a target \u2014 name/match/title derive from the URL"
491
+ },
423
492
  args: {
424
- name: {
493
+ url: {
425
494
  type: "positional",
426
495
  required: true,
427
- description: "Target id (used by `foxhop focus <name>`)"
496
+ description: "URL of the tab (e.g. https://gemini.google.com)"
497
+ },
498
+ name: {
499
+ type: "string",
500
+ description: "Override the derived id (used by `foxhop focus <name>`)"
428
501
  },
502
+ title: { type: "string", description: "Override the derived label" },
429
503
  match: {
430
504
  type: "string",
431
- required: true,
432
- description: "Substring matched against tab URLs"
505
+ description: "Override the derived match (default: the URL hostname)"
433
506
  },
434
- url: { type: "string", description: "URL opened when no tab matches" },
435
- title: { type: "string", description: "Human-friendly label" },
436
507
  strategy: {
437
508
  type: "string",
438
509
  description: "hostname | prefix | exact | search"
@@ -440,18 +511,25 @@ var add = defineCommand({
440
511
  pick: {
441
512
  type: "string",
442
513
  description: "recent | first | pinned (which tab when several match)"
443
- }
514
+ },
515
+ favorite: { type: "boolean", description: "Pin to the top of the list" }
444
516
  },
445
517
  run: ({ args }) => {
518
+ const url = String(args.url);
519
+ const derived = deriveTarget(url);
520
+ const name = args.name ?? derived.name;
521
+ const existing = findTarget(readConfig(), name);
446
522
  upsertTarget({
447
- name: String(args.name),
448
- match: String(args.match),
449
- url: args.url,
450
- title: args.title,
523
+ name,
524
+ match: args.match ?? derived.match,
525
+ url,
526
+ title: args.title ?? derived.title,
451
527
  strategy: args.strategy,
452
- pick: args.pick
528
+ pick: args.pick,
529
+ favorite: args.favorite || existing?.favorite ? true : void 0
453
530
  });
454
- console.log(`foxhop: saved "${args.name}"`);
531
+ console.log(`foxhop: saved "${name}"`);
532
+ autoSync();
455
533
  }
456
534
  });
457
535
  var remove = defineCommand({
@@ -470,6 +548,30 @@ var remove = defineCommand({
470
548
  process.exit(1);
471
549
  }
472
550
  console.log(`foxhop: removed "${args.name}"`);
551
+ autoSync();
552
+ }
553
+ });
554
+ var fav = defineCommand({
555
+ meta: {
556
+ name: "fav",
557
+ description: "Toggle a target's favourite (pins it to the top of the list)"
558
+ },
559
+ args: {
560
+ name: {
561
+ type: "positional",
562
+ required: true,
563
+ description: "Target id to toggle"
564
+ }
565
+ },
566
+ run: ({ args }) => {
567
+ const { favorite, found } = toggleFavorite(String(args.name));
568
+ if (!found) {
569
+ console.error(`foxhop: no target named "${args.name}"`);
570
+ process.exit(1);
571
+ }
572
+ console.log(
573
+ `foxhop: "${args.name}" ${favorite ? "favourited \u2605" : "unfavourited"}`
574
+ );
473
575
  }
474
576
  });
475
577
  var NAME = "foxhop";
@@ -479,6 +581,7 @@ var subCommands = {
479
581
  tabs,
480
582
  add,
481
583
  remove,
584
+ fav,
482
585
  init,
483
586
  sync: syncCommand,
484
587
  install: installCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/foxhop-cli",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "foxhop CLI and native messaging host — focus specific Firefox tabs from macOS",
5
5
  "type": "module",
6
6
  "license": "MIT",