@tvbs-ai/news-rd 0.1.2 → 0.3.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 (3) hide show
  1. package/README.md +25 -4
  2. package/dist/cli.js +294 -74
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,7 @@ TVBS News Rundown AI CLI — query rundowns, news candidates, Google Trends, and
6
6
 
7
7
  ```bash
8
8
  npm install -g @tvbs-ai/news-rd
9
- # or
9
+ # or run directly
10
10
  npx @tvbs-ai/news-rd --help
11
11
  ```
12
12
 
@@ -25,6 +25,7 @@ news-rd <group> <command> [args] [options]
25
25
  | `news` | 候選新聞與趨勢關鍵字 |
26
26
  | `prompt` | Prompt 模板、變數與編譯 |
27
27
  | `token` | JWT Token 管理 |
28
+ | `completion` | Shell auto-completion |
28
29
 
29
30
  ### Examples
30
31
 
@@ -55,7 +56,8 @@ news-rd prompt compile 2026-03-10 0600
55
56
  | `--base-url URL` | API base URL | `https://news-rundown.tvbs.ai` |
56
57
  | `--token TOKEN` | JWT token (or `RD_TOKEN` env) | — |
57
58
  | `--json` | Raw JSON output | — |
58
- | `--help` | Show help | — |
59
+ | `--version, -v` | Show version | — |
60
+ | `--help, -h` | Show help | — |
59
61
 
60
62
  ### Environment Variables
61
63
 
@@ -64,14 +66,33 @@ news-rd prompt compile 2026-03-10 0600
64
66
  | `RD_BASE_URL` | API base URL |
65
67
  | `RD_TOKEN` | JWT authentication token |
66
68
 
69
+ ### Shell Completion
70
+
71
+ ```bash
72
+ # Bash
73
+ news-rd completion bash >> ~/.bashrc && source ~/.bashrc
74
+
75
+ # Zsh
76
+ news-rd completion zsh >> ~/.zshrc && source ~/.zshrc
77
+ ```
78
+
79
+ ## Update
80
+
81
+ The CLI automatically checks for new versions and shows a hint after command output. To update:
82
+
83
+ ```bash
84
+ npm install -g @tvbs-ai/news-rd@latest
85
+ ```
86
+
67
87
  ## Requirements
68
88
 
69
89
  - Node.js >= 18 (uses built-in fetch)
70
90
  - A running TVBS News Rundown AI server
71
91
 
72
- ## Publishing
92
+ ## Documentation
73
93
 
74
- See [CLI 發佈指南](../../docs/cli-publishing.md) for version bumping and npm publishing workflow.
94
+ - [使用教學](../../docs/cli-guide.md) 完整的操作教學(中文)
95
+ - [發佈指南](../../docs/cli-publishing.md) — 版本更新與 npm 發佈流程
75
96
 
76
97
  ## License
77
98
 
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- var args = process.argv.slice(2);
4
+ var CLI_VERSION = "0.2.0";
5
+ var rawArgs = process.argv.slice(2);
5
6
  function parseArgs() {
6
7
  const options = {
7
8
  baseUrl: process.env.RD_BASE_URL || "https://news-rundown.tvbs.ai",
@@ -11,57 +12,93 @@ function parseArgs() {
11
12
  const positional = [];
12
13
  let group = "";
13
14
  let command = "";
14
- for (let i = 0; i < args.length; i++) {
15
- const arg = args[i];
16
- switch (arg) {
17
- case "--base-url":
18
- options.baseUrl = args[++i] || options.baseUrl;
19
- break;
20
- case "--token":
21
- options.token = args[++i] || "";
22
- break;
23
- case "--json":
24
- options.json = true;
25
- break;
26
- case "--category":
27
- options.category = args[++i];
28
- break;
29
- case "--content-type":
30
- options.contentType = args[++i];
31
- break;
32
- case "--limit":
33
- options.limit = args[++i];
34
- break;
35
- case "--version":
36
- options.version = args[++i];
37
- break;
38
- case "--variables":
39
- options.variables = args[++i];
40
- break;
41
- case "--help":
42
- case "-h":
43
- if (group) {
44
- showGroupHelp(group);
45
- } else {
46
- showHelp();
47
- }
48
- process.exit(0);
49
- break;
50
- default:
51
- if (!group) {
52
- group = arg;
53
- } else if (!command) {
54
- command = arg;
55
- } else {
56
- positional.push(arg);
57
- }
15
+ for (let i = 0; i < rawArgs.length; i++) {
16
+ const arg = rawArgs[i];
17
+ if (arg.startsWith("-")) {
18
+ switch (arg) {
19
+ case "--base-url":
20
+ options.baseUrl = rawArgs[++i] || options.baseUrl;
21
+ break;
22
+ case "--token":
23
+ options.token = rawArgs[++i] || "";
24
+ break;
25
+ case "--json":
26
+ options.json = true;
27
+ break;
28
+ case "--category":
29
+ options.category = rawArgs[++i];
30
+ break;
31
+ case "--content-type":
32
+ options.contentType = rawArgs[++i];
33
+ break;
34
+ case "--limit":
35
+ options.limit = rawArgs[++i];
36
+ break;
37
+ case "--version":
38
+ if (!group) {
39
+ console.log(`@tvbs-ai/news-rd v${CLI_VERSION}`);
40
+ process.exit(0);
41
+ }
42
+ options.version = rawArgs[++i];
43
+ break;
44
+ case "-v":
45
+ console.log(`@tvbs-ai/news-rd v${CLI_VERSION}`);
46
+ process.exit(0);
47
+ case "--variables":
48
+ options.variables = rawArgs[++i];
49
+ break;
50
+ case "--help":
51
+ case "-h":
52
+ break;
53
+ default:
54
+ console.error(`Unknown option: ${arg}`);
55
+ process.exit(1);
56
+ }
57
+ continue;
58
+ }
59
+ if (!group) {
60
+ group = arg;
61
+ } else if (!command) {
62
+ command = arg;
63
+ } else {
64
+ positional.push(arg);
58
65
  }
59
66
  }
67
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
68
+ if (group) {
69
+ showGroupHelp(group);
70
+ } else {
71
+ showHelp();
72
+ }
73
+ process.exit(0);
74
+ }
60
75
  return { group, command, positional, options };
61
76
  }
77
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
78
+ var VALID_TIMESLOTS = ["0600", "0700", "0800", "1400", "1500", "1600"];
79
+ function requireDateTimeslot(positional, group, command) {
80
+ const [date, timeslot] = positional;
81
+ if (!date || !timeslot) {
82
+ console.error(`Usage: news-rd ${group} ${command} <date> <timeslot>`);
83
+ console.error(` date: YYYY-MM-DD (e.g. 2026-03-10)`);
84
+ console.error(` timeslot: ${VALID_TIMESLOTS.join(" | ")}`);
85
+ process.exit(1);
86
+ }
87
+ if (!DATE_RE.test(date)) {
88
+ console.error(`Invalid date format: "${date}"`);
89
+ console.error(` Expected YYYY-MM-DD (e.g. 2026-03-10)`);
90
+ process.exit(1);
91
+ }
92
+ if (!VALID_TIMESLOTS.includes(timeslot)) {
93
+ console.error(`Invalid timeslot: "${timeslot}"`);
94
+ console.error(` Available: ${VALID_TIMESLOTS.join(", ")}`);
95
+ process.exit(1);
96
+ }
97
+ return [date, timeslot];
98
+ }
62
99
  function showHelp() {
63
100
  console.log(`
64
- tvbs-news-rd \u2014 TVBS News Rundown AI CLI
101
+ @tvbs-ai/news-rd v${CLI_VERSION} \u2014 TVBS News Rundown AI CLI
65
102
 
66
103
  Usage: news-rd <group> <command> [args] [options]
67
104
 
@@ -71,11 +108,13 @@ Groups:
71
108
  news \u5019\u9078\u65B0\u805E\u8207\u8DA8\u52E2\u95DC\u9375\u5B57
72
109
  prompt Prompt \u6A21\u677F\u3001\u8B8A\u6578\u8207\u7DE8\u8B6F
73
110
  token JWT Token \u7BA1\u7406
111
+ completion Shell auto-completion
74
112
 
75
113
  Global Options:
76
114
  --base-url URL API base URL (default: https://news-rundown.tvbs.ai)
77
115
  --token TOKEN JWT token (or RD_TOKEN env)
78
116
  --json Raw JSON output
117
+ --version, -v Show version
79
118
  --help, -h Show help
80
119
 
81
120
  Run 'news-rd <group> --help' for group-specific commands.
@@ -146,10 +185,25 @@ Examples:
146
185
  token \u2014 JWT Token \u7BA1\u7406
147
186
 
148
187
  Commands:
149
- generate Generate a new admin JWT token (requires Clerk session)
188
+ generate Generate a new admin JWT token
189
+
190
+ Note: Token generation requires a Clerk session cookie.
191
+ The easiest way is to use the Web UI \u2014 log in and click "API Token"
192
+ in the header. The CLI command is for scripting with an existing session.
150
193
 
151
194
  Examples:
152
195
  news-rd token generate
196
+ `,
197
+ completion: `
198
+ completion \u2014 Shell auto-completion
199
+
200
+ Commands:
201
+ bash Output bash completion script
202
+ zsh Output zsh completion script
203
+
204
+ Setup:
205
+ news-rd completion bash >> ~/.bashrc && source ~/.bashrc
206
+ news-rd completion zsh >> ~/.zshrc && source ~/.zshrc
153
207
  `
154
208
  };
155
209
  function showGroupHelp(group) {
@@ -223,8 +277,7 @@ async function apiCall(path, options, method = "GET") {
223
277
  console.error(` # or`);
224
278
  console.error(` export RD_TOKEN=YOUR_TOKEN
225
279
  `);
226
- console.error(` To generate a token, log in to the web UI and click "API Token",`);
227
- console.error(` or run: news-rd token generate (requires Clerk session).
280
+ console.error(` To generate a token, log in to the web UI and click "API Token".
228
281
  `);
229
282
  } else {
230
283
  console.error(` ${errorMsg}
@@ -259,14 +312,6 @@ function printTable(rows, columns) {
259
312
  console.log(cols.map((c, i) => String(row[c] ?? "").padEnd(widths[i])).join(" "));
260
313
  }
261
314
  }
262
- function requireDateTimeslot(positional, group, command) {
263
- const [date, timeslot] = positional;
264
- if (!date || !timeslot) {
265
- console.error(`Usage: news-rd ${group} ${command} <date> <timeslot>`);
266
- process.exit(1);
267
- }
268
- return [date, timeslot];
269
- }
270
315
  async function handleConfig(command, _positional, options) {
271
316
  switch (command) {
272
317
  case "timeslots": {
@@ -275,12 +320,12 @@ async function handleConfig(command, _positional, options) {
275
320
  printJson(data);
276
321
  return;
277
322
  }
278
- printTable(data.timeslots, ["slot_code", "description", "include_google_trends"]);
323
+ printTable(data.timeslots, ["slot_code", "description"]);
279
324
  return;
280
325
  }
281
326
  default:
282
327
  showGroupHelp("config");
283
- process.exit(1);
328
+ process.exit(0);
284
329
  }
285
330
  }
286
331
  async function handleRundown(command, positional, options) {
@@ -292,10 +337,30 @@ async function handleRundown(command, positional, options) {
292
337
  printJson(data);
293
338
  return;
294
339
  }
295
- console.log(`Rundown: ${data.rundown_id}`);
296
340
  const items = data.rundown.items || [];
297
- console.log(`Items: ${items.length}`);
298
- printJson(data);
341
+ console.log(`Rundown: ${data.rundown_id}`);
342
+ console.log(`Items: ${items.length}
343
+ `);
344
+ printTable(items.map((item, i) => {
345
+ const newsItem = item.news_item;
346
+ const scoring = newsItem?.scoring || {};
347
+ const bonus = scoring.rundown_bonus || {};
348
+ return {
349
+ "#": item.sequence ?? i + 1,
350
+ type: item.item_type,
351
+ slug: newsItem?.slug ?? "-",
352
+ category: newsItem?.category ?? "-",
353
+ content_type: newsItem?.content_type ?? "-",
354
+ final: newsItem ? scoring.rundown_final_score?.toFixed?.(1) ?? "-" : "-",
355
+ summary: newsItem ? scoring.summary_score?.toFixed?.(1) ?? "-" : "-",
356
+ visual: newsItem ? scoring.visual_score?.toFixed?.(1) ?? "-" : "-",
357
+ trends: newsItem ? bonus.google_trends_score ?? 0 : "-",
358
+ excl: newsItem ? bonus.exclusivity_score ?? 0 : "-",
359
+ radar: newsItem ? bonus.trending_keywords_score ?? 0 : "-",
360
+ topic: newsItem ? bonus.featured_topic_score ?? 0 : "-",
361
+ title: newsItem ? String(newsItem.title ?? "").substring(0, 40) : item.placeholder?.caption ?? "-"
362
+ };
363
+ }), ["#", "type", "slug", "category", "content_type", "final", "summary", "visual", "trends", "excl", "radar", "topic", "title"]);
299
364
  return;
300
365
  }
301
366
  case "history": {
@@ -316,7 +381,7 @@ async function handleRundown(command, positional, options) {
316
381
  }
317
382
  default:
318
383
  showGroupHelp("rundown");
319
- process.exit(1);
384
+ process.exit(0);
320
385
  }
321
386
  }
322
387
  async function handleNews(command, positional, options) {
@@ -336,13 +401,23 @@ async function handleNews(command, positional, options) {
336
401
  }
337
402
  console.log(`Statistics: total=${data.statistics.total}, with_slug=${data.statistics.with_slug}, without_slug=${data.statistics.without_slug}
338
403
  `);
339
- printTable(data.candidates.map((c) => ({
340
- score: c.final_score?.toFixed(1),
341
- title: String(c.title).substring(0, 50),
342
- category: c.category,
343
- slug: c.slug || "-",
344
- video_id: c.video_id
345
- })), ["score", "category", "title", "slug", "video_id"]);
404
+ printTable(data.candidates.map((c) => {
405
+ const bonus = c.bonus_scoring || {};
406
+ return {
407
+ slug: c.slug || "-",
408
+ category: c.category,
409
+ content_type: c.content_type || "-",
410
+ publish_time: c.publish_time ? String(c.publish_time).substring(0, 16) : "-",
411
+ final: c.final_score?.toFixed(1),
412
+ summary: c.summary_score?.toFixed(1) ?? "-",
413
+ visual: c.visual_score?.toFixed(1) ?? "-",
414
+ trends: bonus.google_trends_score ?? 0,
415
+ excl: bonus.exclusivity_score ?? 0,
416
+ radar: bonus.trending_keywords_score ?? 0,
417
+ topic: bonus.featured_topic_score ?? 0,
418
+ title: String(c.title).substring(0, 40)
419
+ };
420
+ }), ["slug", "category", "content_type", "publish_time", "final", "summary", "visual", "trends", "excl", "radar", "topic", "title"]);
346
421
  return;
347
422
  }
348
423
  case "trends": {
@@ -373,7 +448,7 @@ ${window} keywords (${trends.keywords.length}):`);
373
448
  }
374
449
  default:
375
450
  showGroupHelp("news");
376
- process.exit(1);
451
+ process.exit(0);
377
452
  }
378
453
  }
379
454
  async function handlePrompt(command, positional, options) {
@@ -460,7 +535,7 @@ ${data.compiled_prompt}
460
535
  }
461
536
  default:
462
537
  showGroupHelp("prompt");
463
- process.exit(1);
538
+ process.exit(0);
464
539
  }
465
540
  }
466
541
  async function handleToken(command, _positional, options) {
@@ -479,15 +554,156 @@ Set env: export RD_TOKEN="${data.token}"`);
479
554
  }
480
555
  default:
481
556
  showGroupHelp("token");
482
- process.exit(1);
557
+ process.exit(0);
558
+ }
559
+ }
560
+ function handleCompletion(command) {
561
+ switch (command) {
562
+ case "bash":
563
+ console.log(generateBashCompletion());
564
+ return;
565
+ case "zsh":
566
+ console.log(generateZshCompletion());
567
+ return;
568
+ default:
569
+ showGroupHelp("completion");
570
+ process.exit(0);
571
+ }
572
+ }
573
+ function generateBashCompletion() {
574
+ return `# bash completion for news-rd
575
+ _news_rd_completions() {
576
+ local cur prev groups
577
+ cur="\${COMP_WORDS[COMP_CWORD]}"
578
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
579
+ groups="config rundown news prompt token completion"
580
+
581
+ case "\${COMP_WORDS[1]}" in
582
+ config)
583
+ COMPREPLY=($(compgen -W "timeslots" -- "$cur"))
584
+ ;;
585
+ rundown)
586
+ COMPREPLY=($(compgen -W "get history" -- "$cur"))
587
+ ;;
588
+ news)
589
+ COMPREPLY=($(compgen -W "candidates trends keywords" -- "$cur"))
590
+ ;;
591
+ prompt)
592
+ COMPREPLY=($(compgen -W "list show variables resolve compile" -- "$cur"))
593
+ ;;
594
+ token)
595
+ COMPREPLY=($(compgen -W "generate" -- "$cur"))
596
+ ;;
597
+ completion)
598
+ COMPREPLY=($(compgen -W "bash zsh" -- "$cur"))
599
+ ;;
600
+ *)
601
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
602
+ COMPREPLY=($(compgen -W "$groups --help --version --json" -- "$cur"))
603
+ fi
604
+ ;;
605
+ esac
606
+
607
+ case "$prev" in
608
+ --base-url|--token|--category|--content-type|--limit|--version|--variables)
609
+ COMPREPLY=()
610
+ ;;
611
+ get|history|candidates|trends|keywords|resolve|compile)
612
+ # Suggest timeslots after date
613
+ if [[ \${COMP_CWORD} -ge 4 ]]; then
614
+ COMPREPLY=($(compgen -W "0600 0700 0800 1400 1500 1600" -- "$cur"))
615
+ fi
616
+ ;;
617
+ esac
618
+ }
619
+ complete -F _news_rd_completions news-rd`;
620
+ }
621
+ function generateZshCompletion() {
622
+ return `# zsh completion for news-rd
623
+ #compdef news-rd
624
+
625
+ _news_rd() {
626
+ local -a groups
627
+ groups=(
628
+ 'config:\u6642\u6BB5\u8207\u74B0\u5883\u8A2D\u5B9A'
629
+ 'rundown:Rundown \u67E5\u8A62\u8207\u7248\u672C\u6BD4\u5C0D'
630
+ 'news:\u5019\u9078\u65B0\u805E\u8207\u8DA8\u52E2\u95DC\u9375\u5B57'
631
+ 'prompt:Prompt \u6A21\u677F\u3001\u8B8A\u6578\u8207\u7DE8\u8B6F'
632
+ 'token:JWT Token \u7BA1\u7406'
633
+ 'completion:Shell auto-completion'
634
+ )
635
+
636
+ local -a timeslots
637
+ timeslots=(0600 0700 0800 1400 1500 1600)
638
+
639
+ _arguments -C \\
640
+ '--base-url[API base URL]:url:' \\
641
+ '--token[JWT token]:token:' \\
642
+ '--json[Raw JSON output]' \\
643
+ '--version[Show version]' \\
644
+ '--help[Show help]' \\
645
+ '1:group:->group' \\
646
+ '*::arg:->args'
647
+
648
+ case $state in
649
+ group)
650
+ _describe 'group' groups
651
+ ;;
652
+ args)
653
+ case \${words[1]} in
654
+ config)
655
+ _values 'command' 'timeslots[List timeslots]'
656
+ ;;
657
+ rundown)
658
+ _values 'command' 'get[Get rundown]' 'history[Version history]'
659
+ ;;
660
+ news)
661
+ _values 'command' 'candidates[Scored candidates]' 'trends[Google Trends]' 'keywords[NewsRadar keywords]'
662
+ ;;
663
+ prompt)
664
+ _values 'command' 'list[List prompts]' 'show[Get template]' 'variables[List variables]' 'resolve[Resolve variables]' 'compile[Compiled prompt]'
665
+ ;;
666
+ token)
667
+ _values 'command' 'generate[Generate JWT token]'
668
+ ;;
669
+ completion)
670
+ _values 'command' 'bash[Bash completion]' 'zsh[Zsh completion]'
671
+ ;;
672
+ esac
673
+ ;;
674
+ esac
675
+ }
676
+
677
+ _news_rd "$@"`;
678
+ }
679
+ async function checkForUpdates() {
680
+ try {
681
+ const controller = new AbortController();
682
+ const timeout = setTimeout(() => controller.abort(), 3e3);
683
+ const res = await fetch(
684
+ "https://registry.npmjs.org/@tvbs-ai/news-rd/latest",
685
+ { signal: controller.signal }
686
+ );
687
+ clearTimeout(timeout);
688
+ if (!res.ok) return;
689
+ const data = await res.json();
690
+ const latest = data.version;
691
+ if (latest && latest !== CLI_VERSION) {
692
+ console.error(`
693
+ \u65B0\u7248\u672C\u53EF\u7528: v${latest} (\u76EE\u524D v${CLI_VERSION})`);
694
+ console.error(` \u66F4\u65B0\u6307\u4EE4: npm install -g @tvbs-ai/news-rd@latest
695
+ `);
696
+ }
697
+ } catch {
483
698
  }
484
699
  }
485
700
  async function main() {
486
701
  const { group, command, positional, options } = parseArgs();
487
702
  if (!group) {
488
703
  showHelp();
489
- process.exit(1);
704
+ process.exit(0);
490
705
  }
706
+ const updateCheck = checkForUpdates();
491
707
  try {
492
708
  switch (group) {
493
709
  case "config":
@@ -505,6 +721,9 @@ async function main() {
505
721
  case "token":
506
722
  await handleToken(command, positional, options);
507
723
  break;
724
+ case "completion":
725
+ handleCompletion(command);
726
+ break;
508
727
  default:
509
728
  console.error(`Unknown group: ${group}`);
510
729
  showHelp();
@@ -514,5 +733,6 @@ async function main() {
514
733
  console.error(error instanceof Error ? error.message : String(error));
515
734
  process.exit(1);
516
735
  }
736
+ await updateCheck;
517
737
  }
518
738
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tvbs-ai/news-rd",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "TVBS News Rundown AI CLI — query rundowns, candidates, trends, and prompts",
5
5
  "type": "module",
6
6
  "bin": {