cli.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """命令行装配。
  2. 读侧(forms/word/search/dist/get)不需要凭据;写侧(ensure-model/channels/grant/write)
  3. 需要,且写入前有确认闸门。登录是独立的 wikipali-login,本入口不接触密码。
  4. """
  5. import argparse
  6. import sys
  7. import cmd_read
  8. import cmd_site
  9. import cmd_write
  10. from client import DEFAULT_BATCH
  11. from errors import WpError
  12. def build_parser():
  13. parser = argparse.ArgumentParser(
  14. prog='wikipali',
  15. description='WikiPali 客户端:检索、阅读、以 AI 模型身份写入',
  16. formatter_class=argparse.RawDescriptionHelpFormatter,
  17. epilog='凭据在 ~/.wikipali/credentials.json(0600)。登录请跑 wikipali-login。',
  18. )
  19. parser.add_argument('--api', help='本次调用使用的 API 地址(序号/简称/完整 url),不写回凭据文件')
  20. sub = parser.add_subparsers(dest='command', required=True)
  21. def add(name, help_text, needs_json=True):
  22. p = sub.add_parser(name, help=help_text)
  23. if needs_json:
  24. p.add_argument('--json', action='store_true', help='输出原始 JSON')
  25. return p
  26. # -- 站点与状态 --------------------------------------------------------
  27. p = add('endpoint', '查看 / 切换 API 地址', needs_json=False)
  28. p.add_argument('target', nargs='?', help='序号、简称(www/www.cc/next/next.cc/staging/local)或完整 url')
  29. p.add_argument('-l', '--list', action='store_true',
  30. help='只列出不提示选择(在终端里跑而又不想被追问时用)')
  31. p.set_defaults(func=cmd_site.cmd_endpoint)
  32. p = add('whoami', '显示当前凭据状态', needs_json=False)
  33. p.add_argument('--check', action='store_true', help='额外向服务端校验用户 token')
  34. p.set_defaults(func=cmd_site.cmd_whoami)
  35. # -- 读 ----------------------------------------------------------------
  36. p = add('forms', '展开词形——一切检索的前置步骤')
  37. p.add_argument('word', help='词根或任意变格形')
  38. p.add_argument('--limit', type=int, default=3, help='显示几个候选词根,默认 3')
  39. p.set_defaults(func=cmd_read.cmd_forms)
  40. p = add('word', '词典释义与形态分析,用来确认选对了词根')
  41. p.add_argument('word')
  42. p.add_argument('--lang', default='zh', help='释义语言,默认 zh')
  43. p.add_argument('--limit', type=int, default=3, help='最多显示几个词条')
  44. p.add_argument('--dicts', type=int, default=3, help='每个词条显示几部词典')
  45. p.set_defaults(func=cmd_read.cmd_word)
  46. p = add('search', '按词形检索段落')
  47. p.add_argument('forms', nargs='*', help='逗号或空格分隔的词形;或用 --lemma 自动展开')
  48. p.add_argument('--lemma', help='给词根,自动先展开成全部词形再检索')
  49. p.add_argument('--bold', action='store_true', help='只要黑体命中(注释书标出的词条)')
  50. p.add_argument('--book', help='限定书(用 dist 输出里的 --book 值)')
  51. p.add_argument('--tags', help='限定范围,如 vinaya 或 vinaya,mūla;vinaya,aṭṭhakathā')
  52. p.add_argument('--limit', type=int, default=50, help='本页条数,默认 50')
  53. p.add_argument('--offset', type=int, default=0)
  54. p.add_argument('--width', type=int, default=200, help='每条摘要的字符数')
  55. p.set_defaults(func=cmd_read.cmd_search)
  56. p = add('dist', '出处分布:命中散布在哪些书、各多少、什么层次')
  57. p.add_argument('forms', nargs='*')
  58. p.add_argument('--lemma')
  59. p.add_argument('--tags')
  60. p.add_argument('--limit', type=int, default=25, help='最多列几部书')
  61. p.set_defaults(func=cmd_read.cmd_dist)
  62. p = add('get', '按坐标取文,如 wikipali get 216:35 216:36')
  63. p.add_argument('coords', nargs='+', help='book:paragraph,可给多个')
  64. p.add_argument('--channel', action='append',
  65. help='channel uid,可重复;缺省取巴利原文')
  66. p.add_argument('--limit', type=int, default=200, help='每次请求最多取几句')
  67. p.set_defaults(func=cmd_read.cmd_get)
  68. p = add('toc', '章节目录')
  69. p.add_argument('coord', help='book:paragraph,任意段号即可,服务端会找到所属丛书')
  70. p.add_argument('--depth', type=int, default=4, help='最多显示到第几层,默认 4')
  71. p.add_argument('--all', action='store_true', help='显示整套丛书而不只当前这本')
  72. p.set_defaults(func=cmd_read.cmd_toc)
  73. p = add('chapter', '先报体量,再按需取整章')
  74. p.add_argument('coord', help='book:paragraph,正文段也行,会自动向上找章节')
  75. p.add_argument('--fetch', action='store_true', help='确认要读全文时加这个;不加只报体量')
  76. p.add_argument('--channel', action='append', help='channel uid,可重复;缺省取巴利原文')
  77. p.add_argument('--warn-at', type=int, default=8000, help='超过多少字符就提示,默认 8000')
  78. p.add_argument('--text', action='store_true', help='输出纯文本而非 html(黑体转成 **)')
  79. p.add_argument('--via', choices=['tipitaka-content', 'chapter-content'],
  80. default='tipitaka-content',
  81. help='取文走哪个端点。默认 tipitaka-content;chapter-content 返回逐句的'
  82. '多版本结构,写入侧需要它')
  83. p.add_argument('--limit', type=int, default=200)
  84. p.set_defaults(func=cmd_read.cmd_chapter)
  85. p = add('versions', '某坐标有哪些译本,以及没有哪些')
  86. p.add_argument('coord', help='book:paragraph')
  87. p.set_defaults(func=cmd_read.cmd_versions)
  88. p = add('count', '词频合计(词次,不是段落数)')
  89. p.add_argument('words', nargs='+', help='一个或多个词/词根')
  90. p.set_defaults(func=cmd_read.cmd_count)
  91. p = add('terms', '术语表:权威译名对照')
  92. p.add_argument('keyword', nargs='?', help='按巴利词形过滤;省略则列全表')
  93. p.add_argument('--lang', default='zh-Hans')
  94. p.add_argument('--view', default='community')
  95. p.add_argument('--limit', type=int, default=30)
  96. p.add_argument('--refresh', action='store_true', help='强制重新拉取(全表有缓存)')
  97. p.set_defaults(func=cmd_read.cmd_terms)
  98. p = add('related', '本文 ↔ 义注 ↔ 复注的段落对应')
  99. p.add_argument('coord', help='book:paragraph')
  100. p.set_defaults(func=cmd_read.cmd_related)
  101. p = add('articles', '列出 / 搜索文章(二手研究)')
  102. p.add_argument('keyword', nargs='?', help='标题关键词')
  103. p.add_argument('--lang', help='按语言过滤')
  104. p.add_argument('--view', default='public')
  105. p.add_argument('--limit', type=int, default=20)
  106. p.add_argument('--offset', type=int, default=0)
  107. p.set_defaults(func=cmd_read.cmd_articles)
  108. p = add('article', '读一篇文章的全文')
  109. p.add_argument('uid')
  110. p.add_argument('--chars', type=int, default=4000, help='最多输出多少字符,0 为不截断')
  111. p.set_defaults(func=cmd_read.cmd_article)
  112. p = add('anthology', '文集:不给 uid 列表,给 uid 看目录')
  113. p.add_argument('uid', nargs='?')
  114. p.add_argument('--view', default='public')
  115. p.add_argument('--limit', type=int, default=30)
  116. p.add_argument('--offset', type=int, default=0)
  117. p.set_defaults(func=cmd_read.cmd_anthology)
  118. p = add('books', '分类目录:按 tag 找书,如「长部的复注有哪些」')
  119. p.add_argument('keyword', nargs='?', help='按书名/toc 过滤')
  120. p.add_argument('--tags', help='按 tag 筛,逗号分隔是**且**,如 dīghanikāya,ṭīkā')
  121. p.add_argument('--tag-list', action='store_true', help='列出全部 tag 及各自的书数')
  122. p.add_argument('--show-tags', action='store_true', help='每本书都列出它的 tag')
  123. p.add_argument('--limit', type=int, default=40)
  124. p.add_argument('--refresh', action='store_true', help='强制重新拉取书目清单(有本地缓存)')
  125. p.set_defaults(func=cmd_read.cmd_books)
  126. # -- 写 ----------------------------------------------------------------
  127. p = add('ensure-model', '幂等地建立模型记录并取模型身份 token', needs_json=False)
  128. p.add_argument('--name', help='模型标识,如 claude-opus-5(会成为句子作者署名)')
  129. p.add_argument('--model', help='底层模型 id')
  130. p.add_argument('--url', dest='url', help='模型服务地址')
  131. p.add_argument('--description', help='描述')
  132. p.add_argument('--privacy', choices=['private', 'public'], default='private')
  133. p.set_defaults(func=cmd_write.cmd_ensure_model)
  134. p = add('revoke', '撤销该模型已签出的全部 token', needs_json=False)
  135. p.add_argument('--uid', help='模型 uid,缺省用缓存里的')
  136. p.add_argument('-y', '--yes', action='store_true')
  137. p.set_defaults(func=cmd_write.cmd_revoke)
  138. p = add('channels', '列出当前账号可编辑的 channel')
  139. p.add_argument('--search', help='按名字过滤')
  140. p.set_defaults(func=cmd_write.cmd_channels)
  141. p = add('grant', '为某个 channel 签发 access token 并缓存', needs_json=False)
  142. p.add_argument('channel', nargs='?', help='channel uid / 列表序号 / 名字片段;省略则交互选择')
  143. p.add_argument('--book', type=int, default=0, help='限定 book,0 表示不限(默认)')
  144. p.add_argument('--force', action='store_true', help='即使缓存未过期也重新签发')
  145. p.set_defaults(func=cmd_write.cmd_grant)
  146. p = add('write', '写入句子', needs_json=False)
  147. p.add_argument('file', help='句子 JSON 文件,- 表示从 stdin 读')
  148. p.add_argument('--channel', help='目标 channel(uid / 序号 / 名字片段)')
  149. p.add_argument('--book', type=int, help='access token 的 book 范围,缺省按句子推断')
  150. p.add_argument('--batch', type=int, default=DEFAULT_BATCH, help=f'每批条数,默认 {DEFAULT_BATCH}')
  151. p.add_argument('--content-type', default='markdown')
  152. p.add_argument('--preview', type=int, default=5, help='确认时预览几条')
  153. p.add_argument('--dry-run', action='store_true', help='只做校验与回显,不发请求')
  154. p.add_argument('-y', '--yes', action='store_true', help='跳过交互确认(非交互环境必须显式给)')
  155. p.set_defaults(func=cmd_write.cmd_write)
  156. return parser
  157. def main(argv=None):
  158. args = build_parser().parse_args(argv)
  159. try:
  160. return args.func(args)
  161. except WpError as exc:
  162. print(f'错误:{exc}', file=sys.stderr)
  163. return 1
  164. except KeyboardInterrupt:
  165. print('\n已中断。', file=sys.stderr)
  166. return 130