@kud/foxhop-cli 1.0.3 → 1.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +126 -17
  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
- required: true,
427
- description: "Target id (used by `foxhop focus <name>`)"
495
+ required: false,
496
+ description: "URL of the tab (e.g. https://gemini.google.com) \u2014 or use --match"
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,31 @@ 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 = args.url ? String(args.url) : void 0;
519
+ const matchArg = args.match ? String(args.match) : void 0;
520
+ const source = url ?? matchArg;
521
+ if (!source) {
522
+ console.error("foxhop: provide a URL or --match");
523
+ process.exit(1);
524
+ }
525
+ const derived = deriveTarget(source);
526
+ const name = args.name ?? derived.name;
527
+ const existing = findTarget(readConfig(), name);
446
528
  upsertTarget({
447
- name: String(args.name),
448
- match: String(args.match),
449
- url: args.url,
450
- title: args.title,
529
+ name,
530
+ match: matchArg ?? derived.match,
531
+ url,
532
+ title: args.title ?? derived.title,
451
533
  strategy: args.strategy,
452
- pick: args.pick
534
+ pick: args.pick,
535
+ favorite: args.favorite || existing?.favorite ? true : void 0
453
536
  });
454
- console.log(`foxhop: saved "${args.name}"`);
537
+ console.log(`foxhop: saved "${name}"`);
538
+ autoSync();
455
539
  }
456
540
  });
457
541
  var remove = defineCommand({
@@ -470,6 +554,30 @@ var remove = defineCommand({
470
554
  process.exit(1);
471
555
  }
472
556
  console.log(`foxhop: removed "${args.name}"`);
557
+ autoSync();
558
+ }
559
+ });
560
+ var fav = defineCommand({
561
+ meta: {
562
+ name: "fav",
563
+ description: "Toggle a target's favourite (pins it to the top of the list)"
564
+ },
565
+ args: {
566
+ name: {
567
+ type: "positional",
568
+ required: true,
569
+ description: "Target id to toggle"
570
+ }
571
+ },
572
+ run: ({ args }) => {
573
+ const { favorite, found } = toggleFavorite(String(args.name));
574
+ if (!found) {
575
+ console.error(`foxhop: no target named "${args.name}"`);
576
+ process.exit(1);
577
+ }
578
+ console.log(
579
+ `foxhop: "${args.name}" ${favorite ? "favourited \u2605" : "unfavourited"}`
580
+ );
473
581
  }
474
582
  });
475
583
  var NAME = "foxhop";
@@ -479,6 +587,7 @@ var subCommands = {
479
587
  tabs,
480
588
  add,
481
589
  remove,
590
+ fav,
482
591
  init,
483
592
  sync: syncCommand,
484
593
  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.1",
4
4
  "description": "foxhop CLI and native messaging host — focus specific Firefox tabs from macOS",
5
5
  "type": "module",
6
6
  "license": "MIT",