@arach/pomo 0.1.0 → 0.2.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.
- package/README.md +9 -3
- package/bin/pomo.js +128 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,9 +30,12 @@ pomo status
|
|
|
30
30
|
Timer status [--json] · start · pause · toggle · reset · skip
|
|
31
31
|
session <focus|short|long> · duration <minutes>
|
|
32
32
|
Intent intent <text…> · intent clear
|
|
33
|
-
Audio audio <url> · audio <play|pause|stop|next|prev>
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Audio audio <url> · audio <play|pause|stop|next|prev>
|
|
34
|
+
audio session <focus|break|long> <favorite#|url|clear> · volume <0-100>
|
|
35
|
+
Video video <show|hide|toggle|page|player|browser>
|
|
36
|
+
Favorites fav · fav add <url> [title…] · fav rename <n> <title…>
|
|
37
|
+
fav url <n> <url> · fav move <from> <to>
|
|
38
|
+
fav set <json-file|json|-> · fav play <n> · fav remove <n> · fav clear
|
|
36
39
|
Window show · hide · hud · menu · face <name> · settings · stats
|
|
37
40
|
Login login · login import [--browser b] [--profile p] · login profiles
|
|
38
41
|
login account <n> · logout
|
|
@@ -46,7 +49,10 @@ Run `pomo` with no arguments for a live status; `pomo help` for the full list.
|
|
|
46
49
|
```sh
|
|
47
50
|
pomo intent "Writing the launch post"
|
|
48
51
|
pomo audio "https://youtube.com/watch?v=jfKfPfyJRdk"
|
|
52
|
+
pomo audio session focus 1
|
|
49
53
|
pomo fav play 1
|
|
54
|
+
pomo fav move 4 1
|
|
55
|
+
pomo fav set ./playlist.json
|
|
50
56
|
pomo status --json | jq .remainingSeconds
|
|
51
57
|
```
|
|
52
58
|
|
package/bin/pomo.js
CHANGED
|
@@ -13,6 +13,7 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSy
|
|
|
13
13
|
import { homedir, tmpdir } from 'node:os';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { createInterface } from 'node:readline';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
16
17
|
|
|
17
18
|
const REPO = 'arach/pomo';
|
|
18
19
|
const STATE_FILE = join(homedir(), 'Library', 'Application Support', 'Pomo', 'state.json');
|
|
@@ -31,11 +32,29 @@ function requireMac() {
|
|
|
31
32
|
if (process.platform !== 'darwin') die('this CLI only works on macOS.');
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Which Pomo.app should handle a `pomo://` command. With many stale bundles
|
|
37
|
+
* registered (old worktrees, mounted DMGs, cached builds), bare `open pomo://`
|
|
38
|
+
* routes unpredictably to "some older version". So:
|
|
39
|
+
* 1. honour an explicit POMO_APP env override, else
|
|
40
|
+
* 2. when this CLI lives inside the repo, prefer its sibling dev build, so
|
|
41
|
+
* `pomo` drives the copy you're hacking on — not whatever LaunchServices
|
|
42
|
+
* happens to pick.
|
|
43
|
+
* Returns null for a plain npm install (no sibling app) → default routing.
|
|
44
|
+
*/
|
|
45
|
+
function targetApp() {
|
|
46
|
+
if (process.env.POMO_APP) return process.env.POMO_APP;
|
|
47
|
+
const devApp = join(fileURLToPath(import.meta.url), '../../../macos/dist/Pomo.app');
|
|
48
|
+
return existsSync(devApp) ? devApp : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
/** Fire a pomo:// command at the app via `open`. */
|
|
35
52
|
function send(path) {
|
|
36
53
|
requireMac();
|
|
54
|
+
const url = `pomo://${path}`;
|
|
55
|
+
const app = targetApp();
|
|
37
56
|
try {
|
|
38
|
-
execFileSync('open', [
|
|
57
|
+
execFileSync('open', app ? ['-a', app, url] : [url], { stdio: 'ignore' });
|
|
39
58
|
} catch {
|
|
40
59
|
die(`couldn't reach Pomo. Is it installed? Try: pomo install`);
|
|
41
60
|
}
|
|
@@ -97,6 +116,20 @@ function printStatus(args) {
|
|
|
97
116
|
console.log(` hud ${s.hudVisible ? 'visible' : 'hidden'} · face ${s.watchface}`);
|
|
98
117
|
|
|
99
118
|
const favs = s.favorites || [];
|
|
119
|
+
const sessionAudio = s.sessionAudioURLs || {};
|
|
120
|
+
const sessionLabels = [
|
|
121
|
+
['focus', 'focus'],
|
|
122
|
+
['shortBreak', 'break'],
|
|
123
|
+
['longBreak', 'long'],
|
|
124
|
+
];
|
|
125
|
+
const assigned = sessionLabels.filter(([key]) => sessionAudio[key]);
|
|
126
|
+
if (assigned.length) {
|
|
127
|
+
console.log(' session audio');
|
|
128
|
+
assigned.forEach(([key, label]) => {
|
|
129
|
+
const fav = favs.find((f) => f.url === sessionAudio[key]);
|
|
130
|
+
console.log(` ${label.padEnd(5)} ${fav ? fav.title : sessionAudio[key]}`);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
100
133
|
if (favs.length) {
|
|
101
134
|
console.log(' favorites');
|
|
102
135
|
favs.forEach((f, i) => console.log(` ${i + 1}. ${f.title}`));
|
|
@@ -301,6 +334,51 @@ function favorites(args) {
|
|
|
301
334
|
const title = args.slice(2).join(' ') || undefined;
|
|
302
335
|
return send(`favorite/add${query({ url, title })}`);
|
|
303
336
|
}
|
|
337
|
+
case 'rename': {
|
|
338
|
+
const n = parseInt(args[1], 10);
|
|
339
|
+
const title = args.slice(2).join(' ');
|
|
340
|
+
if (!Number.isInteger(n) || !title) die('usage: pomo fav rename <n> <title…>');
|
|
341
|
+
return send(`favorite/update/${n}${query({ title })}`);
|
|
342
|
+
}
|
|
343
|
+
case 'url': {
|
|
344
|
+
const n = parseInt(args[1], 10);
|
|
345
|
+
const url = args[2];
|
|
346
|
+
if (!Number.isInteger(n) || !url) die('usage: pomo fav url <n> <url>');
|
|
347
|
+
return send(`favorite/update/${n}${query({ url })}`);
|
|
348
|
+
}
|
|
349
|
+
case 'move': {
|
|
350
|
+
const from = parseInt(args[1], 10);
|
|
351
|
+
const to = parseInt(args[2], 10);
|
|
352
|
+
if (!Number.isInteger(from) || !Number.isInteger(to)) die('usage: pomo fav move <from> <to>');
|
|
353
|
+
return send(`favorite/move/${from}/${to}`);
|
|
354
|
+
}
|
|
355
|
+
case 'set':
|
|
356
|
+
case 'replace': {
|
|
357
|
+
const source = args[1];
|
|
358
|
+
if (!source) die('usage: pomo fav set <json-file|json|->');
|
|
359
|
+
const raw = source === '-'
|
|
360
|
+
? readFileSync(0, 'utf8')
|
|
361
|
+
: existsSync(source)
|
|
362
|
+
? readFileSync(source, 'utf8')
|
|
363
|
+
: args.slice(1).join(' ');
|
|
364
|
+
let items;
|
|
365
|
+
try {
|
|
366
|
+
items = JSON.parse(raw);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
die(`invalid favorites JSON: ${error.message}`);
|
|
369
|
+
}
|
|
370
|
+
if (!Array.isArray(items)) die('favorites JSON must be an array of { "title": "...", "url": "..." } objects');
|
|
371
|
+
const normalized = items.map((item, index) => {
|
|
372
|
+
if (!item || typeof item !== 'object') die(`favorite ${index + 1} must be an object`);
|
|
373
|
+
const url = String(item.url || '').trim();
|
|
374
|
+
if (!url) die(`favorite ${index + 1} is missing url`);
|
|
375
|
+
const title = String(item.title || '').trim();
|
|
376
|
+
return { title: title || url, url };
|
|
377
|
+
});
|
|
378
|
+
return send(`favorite/set${query({ items: JSON.stringify(normalized) })}`);
|
|
379
|
+
}
|
|
380
|
+
case 'clear':
|
|
381
|
+
return send('favorite/clear');
|
|
304
382
|
case 'play': {
|
|
305
383
|
const n = parseInt(args[1], 10);
|
|
306
384
|
if (!Number.isInteger(n)) die('usage: pomo fav play <n>');
|
|
@@ -316,6 +394,43 @@ function favorites(args) {
|
|
|
316
394
|
}
|
|
317
395
|
}
|
|
318
396
|
|
|
397
|
+
function sessionKey(input) {
|
|
398
|
+
switch ((input || '').toLowerCase()) {
|
|
399
|
+
case 'focus':
|
|
400
|
+
case 'work':
|
|
401
|
+
return 'focus';
|
|
402
|
+
case 'short':
|
|
403
|
+
case 'break':
|
|
404
|
+
case 'shortbreak':
|
|
405
|
+
case 'short-break':
|
|
406
|
+
return 'break';
|
|
407
|
+
case 'long':
|
|
408
|
+
case 'longbreak':
|
|
409
|
+
case 'long-break':
|
|
410
|
+
return 'long';
|
|
411
|
+
default:
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function sessionAudio(args) {
|
|
417
|
+
const type = sessionKey(args[0]);
|
|
418
|
+
if (!type) die('usage: pomo audio session <focus|break|long> <favorite#|url|clear>');
|
|
419
|
+
|
|
420
|
+
const value = args[1];
|
|
421
|
+
if (!value) die('usage: pomo audio session <focus|break|long> <favorite#|url|clear>');
|
|
422
|
+
if (value.toLowerCase() === 'clear') return send(`audio/session/${type}/clear`);
|
|
423
|
+
|
|
424
|
+
let url = value;
|
|
425
|
+
if (/^\d+$/.test(value)) {
|
|
426
|
+
const favorite = (readState().favorites || [])[Number(value) - 1];
|
|
427
|
+
if (!favorite) die(`no favorite #${value}`);
|
|
428
|
+
url = favorite.url;
|
|
429
|
+
}
|
|
430
|
+
if (!/^https?:\/\//i.test(url)) die('session audio must be a favorite number, URL, or clear');
|
|
431
|
+
return send(`audio/session/${type}${query({ url })}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
319
434
|
// ─── help ─────────────────────────────────────────────────────────────────
|
|
320
435
|
|
|
321
436
|
function help() {
|
|
@@ -336,14 +451,20 @@ Intent
|
|
|
336
451
|
Audio / video
|
|
337
452
|
audio <url> play a YouTube/stream link
|
|
338
453
|
audio <play|pause|stop|next|prev>
|
|
454
|
+
audio session <focus|break|long> <favorite#|url|clear>
|
|
339
455
|
volume <0-100>
|
|
340
|
-
video <show|hide|toggle|browser>
|
|
456
|
+
video <show|hide|toggle|page|player|browser>
|
|
341
457
|
|
|
342
458
|
Favorites
|
|
343
459
|
fav list saved stations
|
|
344
460
|
fav add <url> [title…]
|
|
461
|
+
fav rename <n> <title…>
|
|
462
|
+
fav url <n> <url>
|
|
463
|
+
fav move <from> <to>
|
|
464
|
+
fav set <json-file|json|->
|
|
345
465
|
fav play <n>
|
|
346
466
|
fav remove <n>
|
|
467
|
+
fav clear
|
|
347
468
|
|
|
348
469
|
Window & app
|
|
349
470
|
show | hide | hud summon / dismiss / toggle the HUD
|
|
@@ -414,10 +535,11 @@ switch (cmd) {
|
|
|
414
535
|
|
|
415
536
|
case 'audio': {
|
|
416
537
|
const a = rest[0] || '';
|
|
417
|
-
if (
|
|
538
|
+
if (a === 'session' || a === 'for') sessionAudio(rest.slice(1));
|
|
539
|
+
else if (/^https?:\/\//i.test(a)) send(`audio${query({ url: a })}`);
|
|
418
540
|
else if (['play', 'pause', 'stop', 'next', 'prev', 'previous'].includes(a.toLowerCase()))
|
|
419
541
|
send(`audio/${a.toLowerCase()}`);
|
|
420
|
-
else die('usage: pomo audio <url|play|pause|stop|next|prev>');
|
|
542
|
+
else die('usage: pomo audio <url|play|pause|stop|next|prev|session>');
|
|
421
543
|
break;
|
|
422
544
|
}
|
|
423
545
|
|
|
@@ -428,8 +550,8 @@ switch (cmd) {
|
|
|
428
550
|
|
|
429
551
|
case 'video': {
|
|
430
552
|
const sub = (rest[0] || 'toggle').toLowerCase();
|
|
431
|
-
if (!['show', 'hide', 'toggle', 'browser', 'open'].includes(sub))
|
|
432
|
-
die('usage: pomo video <show|hide|toggle|browser>');
|
|
553
|
+
if (!['show', 'hide', 'toggle', 'page', 'full', 'original', 'expand', 'player', 'bare', 'screen', 'collapse', 'browser', 'open'].includes(sub))
|
|
554
|
+
die('usage: pomo video <show|hide|toggle|page|player|browser>');
|
|
433
555
|
send(`video/${sub}`);
|
|
434
556
|
break;
|
|
435
557
|
}
|
package/package.json
CHANGED