@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.
- package/dist/cli.js +126 -17
- 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
|
-
|
|
348
|
-
|
|
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: {
|
|
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
|
-
|
|
493
|
+
url: {
|
|
425
494
|
type: "positional",
|
|
426
|
-
required:
|
|
427
|
-
description: "
|
|
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
|
-
|
|
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
|
|
448
|
-
match:
|
|
449
|
-
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 "${
|
|
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
|