b3-dict 0.0.0-stage → 0.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 (80) hide show
  1. package/README.md +50 -2
  2. package/dist/app.d.ts +8 -0
  3. package/dist/app.js +83 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +96 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/components/answer-view.d.ts +6 -0
  9. package/dist/components/answer-view.js +27 -0
  10. package/dist/components/answer-view.js.map +1 -0
  11. package/dist/components/banner.d.ts +5 -0
  12. package/dist/components/banner.js +12 -0
  13. package/dist/components/banner.js.map +1 -0
  14. package/dist/components/cursor.d.ts +3 -0
  15. package/dist/components/cursor.js +6 -0
  16. package/dist/components/cursor.js.map +1 -0
  17. package/dist/components/gutter-line.d.ts +7 -0
  18. package/dist/components/gutter-line.js +12 -0
  19. package/dist/components/gutter-line.js.map +1 -0
  20. package/dist/components/lookup-list.d.ts +12 -0
  21. package/dist/components/lookup-list.js +23 -0
  22. package/dist/components/lookup-list.js.map +1 -0
  23. package/dist/components/message-block.d.ts +3 -0
  24. package/dist/components/message-block.js +11 -0
  25. package/dist/components/message-block.js.map +1 -0
  26. package/dist/components/query-input.d.ts +7 -0
  27. package/dist/components/query-input.js +21 -0
  28. package/dist/components/query-input.js.map +1 -0
  29. package/dist/lib/args.d.ts +15 -0
  30. package/dist/lib/args.js +41 -0
  31. package/dist/lib/args.js.map +1 -0
  32. package/dist/lib/ask.d.ts +4 -0
  33. package/dist/lib/ask.js +54 -0
  34. package/dist/lib/ask.js.map +1 -0
  35. package/dist/lib/config.d.ts +6 -0
  36. package/dist/lib/config.js +39 -0
  37. package/dist/lib/config.js.map +1 -0
  38. package/dist/lib/db.d.ts +27 -0
  39. package/dist/lib/db.js +98 -0
  40. package/dist/lib/db.js.map +1 -0
  41. package/dist/lib/envelope.d.ts +37 -0
  42. package/dist/lib/envelope.js +74 -0
  43. package/dist/lib/envelope.js.map +1 -0
  44. package/dist/lib/lookup.d.ts +25 -0
  45. package/dist/lib/lookup.js +41 -0
  46. package/dist/lib/lookup.js.map +1 -0
  47. package/dist/lib/messages.d.ts +63 -0
  48. package/dist/lib/messages.js +131 -0
  49. package/dist/lib/messages.js.map +1 -0
  50. package/dist/lib/model.d.ts +138 -0
  51. package/dist/lib/model.js +172 -0
  52. package/dist/lib/model.js.map +1 -0
  53. package/dist/lib/paths.d.ts +8 -0
  54. package/dist/lib/paths.js +22 -0
  55. package/dist/lib/paths.js.map +1 -0
  56. package/dist/lib/policy.d.ts +22 -0
  57. package/dist/lib/policy.js +92 -0
  58. package/dist/lib/policy.js.map +1 -0
  59. package/dist/lib/render-text.d.ts +2 -0
  60. package/dist/lib/render-text.js +28 -0
  61. package/dist/lib/render-text.js.map +1 -0
  62. package/dist/lib/theme.d.ts +14 -0
  63. package/dist/lib/theme.js +16 -0
  64. package/dist/lib/theme.js.map +1 -0
  65. package/dist/lib/time.d.ts +1 -0
  66. package/dist/lib/time.js +23 -0
  67. package/dist/lib/time.js.map +1 -0
  68. package/dist/lib/types.d.ts +47 -0
  69. package/dist/lib/types.js +7 -0
  70. package/dist/lib/types.js.map +1 -0
  71. package/dist/screens/home.d.ts +14 -0
  72. package/dist/screens/home.js +45 -0
  73. package/dist/screens/home.js.map +1 -0
  74. package/dist/screens/list.d.ts +9 -0
  75. package/dist/screens/list.js +13 -0
  76. package/dist/screens/list.js.map +1 -0
  77. package/dist/screens/setup.d.ts +5 -0
  78. package/dist/screens/setup.js +11 -0
  79. package/dist/screens/setup.js.map +1 -0
  80. package/package.json +48 -4
package/README.md CHANGED
@@ -1,3 +1,51 @@
1
- # Temporary Holding Version
1
+ # B3 Dict
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An AI-first dictionary for the terminal. Ask about English words, idioms, or usage in mixed Vietnamese/English; the answer comes back in Vietnamese. Freeform questions, not a bilingual lookup table: "is draggable a real word?", "take for granted nghĩa là gì?", or paste a joke and ask how to read it.
4
+
5
+ ## Getting Started
6
+
7
+ Needs Node 22 or newer and an API key from [OpenRouter](https://openrouter.ai/keys) or [OpenAI](https://platform.openai.com/api-keys).
8
+
9
+ ```bash
10
+ npm install
11
+ cp .env.example .env # then paste your key into .env
12
+ npm run build
13
+ npm link # puts `b3dict` on your PATH
14
+
15
+ b3dict # interactive: type a question, press enter
16
+ b3dict "take for granted" # one-shot: print the answer and exit
17
+ b3dict "Is draggable an English word?"
18
+ b3dict "take for granted" --fresh # skip the stored answer, ask again
19
+ b3dict "draggable" --json # raw answer envelope
20
+ ```
21
+
22
+ Answers are stored in `data/b3-dict.db`, so asking the same thing twice costs nothing the second time. `.env` and `data/` live in this folder no matter where you run `b3dict` from. Installed from npm instead of a checkout, they move to `~/.local/share/b3-dict` (or `$XDG_DATA_HOME/b3-dict`); set `B3_DICT_HOME` to put them anywhere else.
23
+
24
+ In the interactive screen, paste works for multiline questions. Once an answer is up: `r` asks again, `s` saves it to the wordbook, `esc` goes back, `q` quits. Press `↓` from the question box to reach History (everything you've asked) and Wordbook (what you starred); `enter` reopens a stored answer without calling the model, `s` stars or unstars the row. From the empty question box, `esc` or `ctrl+c` quits.
25
+
26
+ ## Development
27
+
28
+ ```bash
29
+ npm run typecheck # Typecheck
30
+ npm run lint # Lint
31
+ npm test # Run tests
32
+ npm run dev -- "<query>" # Run from source without building
33
+ npm run typecheck && npm run lint && npm test && npm run build # Before creating a PR
34
+ ```
35
+
36
+ ## Architecture
37
+
38
+ useReducer-based Model → Update → View. State in a single reducer, components dispatch actions, side effects in useEffect. Mirrors Bubbletea's discipline in a React idiom.
39
+
40
+ ## Documentation
41
+
42
+ - [CLAUDE.md](./CLAUDE.md) — Development guide
43
+ - [GLOSSARY.md](./GLOSSARY.md) — Domain vocabulary
44
+ - [docs/PRD.md](./docs/PRD.md) — Product requirements
45
+ - [docs/USER-STORIES.md](./docs/USER-STORIES.md) — User stories and acceptance criteria
46
+ - [docs/INK-TUI-UI-UX-GUIDELINE.md](./docs/INK-TUI-UI-UX-GUIDELINE.md) — Ink TUI design system and component specs
47
+ - [docs/INK-TUI-TECH-SPECS.md](./docs/INK-TUI-TECH-SPECS.md) — Technical stack and configuration
48
+
49
+ ## License
50
+
51
+ ISC
package/dist/app.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type LookupDeps } from './lib/lookup.js';
2
+ import type { Config, ConfigProblem, Result } from './lib/types.js';
3
+ export type AppProps = {
4
+ config: Result<Config, ConfigProblem>;
5
+ envPath: string;
6
+ deps?: Omit<LookupDeps, 'onRetry'>;
7
+ };
8
+ export declare function App({ config, envPath, deps }: AppProps): import("react").JSX.Element;
package/dist/app.js ADDED
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, useApp, useInput } from 'ink';
3
+ import { useEffect, useReducer } from 'react';
4
+ import { Banner } from './components/banner.js';
5
+ import { countLookups, getLookup, listLookups, setSaved } from './lib/db.js';
6
+ import { parseStored } from './lib/envelope.js';
7
+ import { lookup } from './lib/lookup.js';
8
+ import { TUI } from './lib/messages.js';
9
+ import { init, intentFor, update } from './lib/model.js';
10
+ import { Home } from './screens/home.js';
11
+ import { List } from './screens/list.js';
12
+ import { Setup } from './screens/setup.js';
13
+ // Reads a list for the screen. A notice rides along when the step before it went wrong.
14
+ function showList(deps, which, index, notice) {
15
+ const rows = listLookups(deps.db, which);
16
+ if (!rows.ok)
17
+ return { type: 'listShown', which, rows: [], index: 0, notice: TUI.listFailed(rows.error) };
18
+ return { type: 'listShown', which, rows: rows.data, index, ...(notice && { notice }) };
19
+ }
20
+ // Intents that read or write the DB. All synchronous, so each returns the message to dispatch.
21
+ function perform(intent, deps) {
22
+ switch (intent.type) {
23
+ case 'openList':
24
+ return showList(deps, intent.which, intent.index);
25
+ case 'openEntry': {
26
+ const { query, origin } = intent;
27
+ const row = getLookup(deps.db, intent.id);
28
+ if (!row.ok)
29
+ return { type: 'openFailed', query, detail: row.error, origin };
30
+ if (!row.data)
31
+ return { type: 'openFailed', query, detail: `no lookup with id ${intent.id}`, origin };
32
+ const answer = parseStored(row.data.answer_json);
33
+ return answer.ok
34
+ ? { type: 'entryOpened', entry: row.data, answer: answer.data, origin }
35
+ : { type: 'openFailed', query: row.data.query_text, detail: answer.error, origin };
36
+ }
37
+ case 'toggleSave': {
38
+ const saved = setSaved(deps.db, intent.id, intent.saved, deps.now());
39
+ if (intent.reload) {
40
+ return showList(deps, intent.reload.which, intent.reload.index, saved.ok ? undefined : TUI.saveFailed(saved.error));
41
+ }
42
+ return saved.ok ? { type: 'entrySaved', entry: saved.data } : { type: 'saveFailed', detail: saved.error };
43
+ }
44
+ }
45
+ }
46
+ // The shell: one reducer holds the state, key presses become messages, and the side effects
47
+ // (asking the model, reading lists, flipping the saved flag) happen here.
48
+ export function App({ config, envPath, deps }) {
49
+ const { exit } = useApp();
50
+ const [model, dispatch] = useReducer(update, config, init);
51
+ const { view } = model;
52
+ useInput((input, key) => {
53
+ const intent = intentFor(view, input, key);
54
+ if (!intent)
55
+ return;
56
+ if (intent.type === 'quit') {
57
+ exit();
58
+ }
59
+ else if (intent.type === 'openList' || intent.type === 'openEntry' || intent.type === 'toggleSave') {
60
+ if (deps)
61
+ dispatch(perform(intent, deps));
62
+ }
63
+ else {
64
+ dispatch(intent);
65
+ }
66
+ });
67
+ const request = view.screen === 'asking' ? view.request : undefined;
68
+ useEffect(() => {
69
+ if (!request || !config.ok || !deps)
70
+ return;
71
+ const requestId = request.id;
72
+ void lookup({ query: request.query, route: config.data.route, model: config.data.model, fresh: request.fresh }, { ...deps, onRetry: (event) => dispatch({ type: 'retrying', requestId, event }) }).then((result) => dispatch(result.ok
73
+ ? { type: 'answered', requestId, outcome: result.data }
74
+ : { type: 'failed', requestId, failure: result.error }));
75
+ // One lookup per request id. The request object is recreated on every render, and config
76
+ // and deps never change after mount, so the id is the only dependency that matters.
77
+ // eslint-disable-next-line react-hooks/exhaustive-deps
78
+ }, [request?.id]);
79
+ // One COUNT per Home render; renders happen on key presses, so this is cheap.
80
+ const counted = view.screen === 'home' && deps ? countLookups(deps.db) : undefined;
81
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Banner, { config: config.ok ? config.data : undefined }), view.screen === 'setup' && _jsx(Setup, { problem: view.problem, envPath: envPath }), view.screen === 'list' && deps && _jsx(List, { view: view, now: deps.now() }), view.screen !== 'setup' && view.screen !== 'list' && config.ok && (_jsx(Home, { view: view, config: config.data, counts: counted?.ok ? counted.data : undefined, notice: counted && !counted.ok ? TUI.countFailed(counted.error) : undefined }))] }));
82
+ }
83
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAiB,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAmB,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAyB,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAW3C,wFAAwF;AACxF,SAAS,QAAQ,CAAC,IAAU,EAAE,KAAe,EAAE,KAAa,EAAE,MAAe;IAC3E,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1G,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,+FAA+F;AAC/F,SAAS,OAAO,CAAC,MAA+C,EAAE,IAAU;IAC1E,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,UAAU;YACb,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACpD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;YACjC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;YAC7E,IAAI,CAAC,GAAG,CAAC,IAAI;gBAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;YACtG,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjD,OAAO,MAAM,CAAC,EAAE;gBACd,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE;gBACvE,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QACvF,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACrE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACtH,CAAC;YACD,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5G,CAAC;IACH,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,0EAA0E;AAC1E,MAAM,UAAU,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAY;IACrD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAEvB,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,IAAI,EAAE,CAAC;QACT,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACrG,IAAI,IAAI;gBAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI;YAAE,OAAO;QAC5C,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;QAC7B,KAAK,MAAM,CACT,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,EAClG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,CAClF,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAChB,QAAQ,CACN,MAAM,CAAC,EAAE;YACP,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;YACvD,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,CACzD,CACF,CAAC;QACF,yFAAyF;QACzF,oFAAoF;QACpF,uDAAuD;IACzD,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAElB,8EAA8E;IAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnF,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,MAAM,IAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,GAAI,EACtD,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,KAAK,IAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAI,EAC7E,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,IAAI,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAI,EACvE,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,EAAE,IAAI,CACjE,KAAC,IAAI,IACH,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,CAAC,IAAI,EACnB,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAC9C,MAAM,EAAE,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,GAC3E,CACH,IACG,CACP,CAAC;AACJ,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { setTimeout as sleep } from 'node:timers/promises';
4
+ import { nanoid } from 'nanoid';
5
+ import { parseCommand } from './lib/args.js';
6
+ import { createAsk } from './lib/ask.js';
7
+ import { resolveConfig } from './lib/config.js';
8
+ import { openDb } from './lib/db.js';
9
+ import { lookup } from './lib/lookup.js';
10
+ import { CLI, configProblemText, failureText, HELP, retryText, TUI } from './lib/messages.js';
11
+ import { DB_PATH, ENV_PATH, PACKAGE_JSON_PATH } from './lib/paths.js';
12
+ import { renderText } from './lib/render-text.js';
13
+ import { EXIT, messageOf } from './lib/types.js';
14
+ const out = (text) => process.stdout.write(`${text}\n`);
15
+ const warn = (text) => process.stderr.write(`${text}\n`);
16
+ const depsFor = (config, db) => ({
17
+ db,
18
+ ask: createAsk(config),
19
+ sleep: (ms) => sleep(ms),
20
+ now: () => new Date(),
21
+ newId: () => nanoid(),
22
+ });
23
+ async function main(argv) {
24
+ const command = parseCommand(argv);
25
+ if (!command.ok) {
26
+ warn(`✗ ${command.error}\n\n${HELP}`);
27
+ return EXIT.usage;
28
+ }
29
+ switch (command.data.mode) {
30
+ case 'help':
31
+ out(HELP);
32
+ return EXIT.ok;
33
+ case 'version':
34
+ out(JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8')).version);
35
+ return EXIT.ok;
36
+ }
37
+ // Variables already in the environment win over .env.
38
+ if (existsSync(ENV_PATH))
39
+ process.loadEnvFile(ENV_PATH);
40
+ return command.data.mode === 'tui'
41
+ ? runTui(resolveConfig(process.env))
42
+ : runOneShot(command.data, resolveConfig(process.env, command.data.model));
43
+ }
44
+ async function runOneShot({ query, fresh, json, model }, config) {
45
+ if (!config.ok) {
46
+ warn(configProblemText(config.error, ENV_PATH));
47
+ return EXIT.noKey;
48
+ }
49
+ const db = openDb(DB_PATH);
50
+ if (!db.ok) {
51
+ warn(CLI.dbOpenFailed(DB_PATH, db.error));
52
+ return EXIT.unexpected;
53
+ }
54
+ try {
55
+ const result = await lookup(
56
+ // An explicit --model asks to hear from that model, so it never settles for a stored answer.
57
+ { query, route: config.data.route, model: config.data.model, fresh: fresh || model !== undefined }, { ...depsFor(config.data, db.data), onRetry: (event) => warn(`${retryText(event)} (${event.detail})`) });
58
+ if (!result.ok) {
59
+ warn(failureText(result.error, config.data, query));
60
+ return result.error.reason === 'unexpected' ? EXIT.unexpected : EXIT.provider;
61
+ }
62
+ // --json prints the envelope exactly as stored, so a pipe sees the same bytes history holds.
63
+ out(json ? result.data.entry.answer_json : renderText(result.data.answer, query));
64
+ return EXIT.ok;
65
+ }
66
+ finally {
67
+ db.data.close();
68
+ }
69
+ }
70
+ async function runTui(config) {
71
+ if (!process.stdin.isTTY) {
72
+ warn(`✗ ${TUI.needsTerminal}`);
73
+ return EXIT.usage;
74
+ }
75
+ const db = config.ok ? openDb(DB_PATH) : undefined;
76
+ if (db && !db.ok) {
77
+ warn(CLI.dbOpenFailed(DB_PATH, db.error));
78
+ return EXIT.unexpected;
79
+ }
80
+ // Loaded here so a one-shot lookup never pays for Ink and React.
81
+ const [{ render }, { createElement }, { App }] = await Promise.all([import('ink'), import('react'), import('./app.js')]);
82
+ const deps = config.ok && db?.ok ? depsFor(config.data, db.data) : undefined;
83
+ const app = render(createElement(App, { config, envPath: ENV_PATH, deps }));
84
+ await app.waitUntilExit();
85
+ if (db?.ok)
86
+ db.data.close();
87
+ // A lookup may still be in flight after the user quits; don't let it hold the process open.
88
+ process.exit(config.ok ? EXIT.ok : EXIT.noKey);
89
+ }
90
+ main(process.argv.slice(2)).then((code) => {
91
+ process.exitCode = code;
92
+ }, (error) => {
93
+ warn(CLI.unknownError(messageOf(error)));
94
+ process.exitCode = EXIT.unexpected;
95
+ });
96
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type Answer } from '../lib/envelope.js';
2
+ export declare function AnswerView({ answer, query, saved }: {
3
+ answer: Answer;
4
+ query: string;
5
+ saved: boolean;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Fragment } from 'react';
4
+ import { bareIpa, displayTerm } from '../lib/envelope.js';
5
+ import { TUI } from '../lib/messages.js';
6
+ import { C } from '../lib/theme.js';
7
+ import { GutterLine } from './gutter-line.js';
8
+ // A gutter row set in from the sense it belongs to. Padding, not spaces, so wrapped text stays aligned.
9
+ const Indented = ({ by = 2, children }) => (_jsx(GutterLine, { children: _jsx(Box, { paddingLeft: by, children: children }) }));
10
+ const Star = ({ saved }) => (saved ? _jsx(Text, { color: C.green, children: " \u2605" }) : null);
11
+ // The full answer for the TUI. Like the plain-text renderer, each kind reads only its own fields.
12
+ export function AnswerView({ answer, query, saved }) {
13
+ const term = displayTerm(answer, query);
14
+ switch (answer.kind) {
15
+ case 'word-check':
16
+ return (_jsxs(_Fragment, { children: [_jsxs(GutterLine, { children: [_jsx(Text, { color: C.label, bold: true, children: term }), _jsx(Text, { color: C.overlay1, children: " \u2014 " }), _jsx(Text, { color: answer.isWord ? C.green : C.red, children: answer.isWord ? TUI.isWord : TUI.notWord }), _jsx(Star, { saved: saved })] }), answer.verdictVi && (_jsx(GutterLine, { children: _jsx(Text, { color: C.label, children: answer.verdictVi }) })), !answer.isWord &&
17
+ (answer.suggestions ?? []).map((s) => (_jsx(GutterLine, { children: _jsxs(Text, { color: C.label, children: [_jsxs(Text, { color: C.green, children: ["\u2192 ", s.term] }), ": ", s.reasonVi] }) }, s.term)))] }));
18
+ case 'meaning':
19
+ return (_jsxs(_Fragment, { children: [_jsxs(GutterLine, { children: [_jsx(Text, { color: C.label, bold: true, children: term }), _jsx(Star, { saved: saved })] }), answer.ipa && (_jsx(GutterLine, { children: _jsxs(Text, { color: C.overlay1, children: ["/", bareIpa(answer.ipa), "/"] }) })), (answer.entries ?? []).map((entry, index) => (_jsx(EntryLines, { entry: entry }, index)))] }));
20
+ case 'interpretation':
21
+ return (_jsxs(_Fragment, { children: [_jsxs(GutterLine, { children: [_jsxs(Text, { color: C.overlay1, children: ["\"", (answer.subject ?? query).trim().replace(/\s*\n\s*/g, ' / '), "\""] }), _jsx(Star, { saved: saved })] }), _jsx(GutterLine, {}), _jsx(GutterLine, { children: _jsx(Text, { color: C.label, children: (answer.explanationVi ?? '').trim() }) }), (answer.nuances ?? []).map((nuance) => (_jsx(GutterLine, { children: _jsxs(Text, { color: C.overlay1, children: ["\u2022 ", nuance] }) }, nuance)))] }));
22
+ }
23
+ }
24
+ function EntryLines({ entry }) {
25
+ return (_jsxs(_Fragment, { children: [_jsx(GutterLine, {}), _jsx(GutterLine, { children: _jsxs(Text, { color: C.label, children: [_jsxs(Text, { color: C.sapphire, children: ["[", entry.partOfSpeech, entry.register ? ` · ${entry.register}` : '', "]"] }), ' ', entry.glossVi] }) }), entry.synonyms.length > 0 && (_jsx(Indented, { children: _jsxs(Text, { color: C.overlay1, children: ["\u2248 ", entry.synonyms.join(', ')] }) })), entry.examples.map((example) => (_jsxs(Fragment, { children: [_jsx(Indented, { children: _jsxs(Text, { color: C.label, children: ["\"", example.text, "\""] }) }), _jsx(Indented, { by: 3, children: _jsxs(Text, { color: C.overlay1, children: ["\u2014 ", example.glossVi] }) })] }, example.text)))] }));
26
+ }
27
+ //# sourceMappingURL=answer-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-view.js","sourceRoot":"","sources":["../../src/components/answer-view.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Config } from '../lib/types.js';
2
+ export declare const shortModel: (model: string) => string;
3
+ export declare function Banner({ config }: {
4
+ config?: Config;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { TUI } from '../lib/messages.js';
4
+ import { C } from '../lib/theme.js';
5
+ const ART = ['█▄▄ ▀▀█ █▀▄ █ █▀▀ ▀█▀', '█▄█ ▄██ █▄▀ █ █▄▄ █'];
6
+ // "qwen/qwen3-235b-a22b-2507" reads better without its vendor prefix.
7
+ export const shortModel = (model) => model.slice(model.lastIndexOf('/') + 1);
8
+ export function Banner({ config }) {
9
+ const where = config ? ` · ${config.route} · ${shortModel(config.model)}` : '';
10
+ return (_jsxs(Box, { flexDirection: "column", children: [ART.map((line) => (_jsx(Text, { color: C.mauve, bold: true, children: line }, line))), _jsxs(Text, { children: [TUI.subtitle, where] }), _jsx(Text, { color: C.overlay1, children: '─'.repeat(46) })] }));
11
+ }
12
+ //# sourceMappingURL=banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.js","sourceRoot":"","sources":["../../src/components/banner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAGpC,MAAM,GAAG,GAAG,CAAC,yBAAyB,EAAE,wBAAwB,CAAC,CAAC;AAElE,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAE7F,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,EAAuB;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,MAAM,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACxB,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACjB,KAAC,IAAI,IAAY,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,kBAClC,IAAI,IADI,IAAI,CAER,CACR,CAAC,EACF,MAAC,IAAI,eACF,GAAG,CAAC,QAAQ,EACZ,KAAK,IACD,EACP,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,YAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAQ,IAC5C,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const Cursor: ({ active }: {
2
+ active: boolean;
3
+ }) => import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { C } from '../lib/theme.js';
4
+ // The two-column row marker every list and input shares: ❯ on the active row, blank elsewhere.
5
+ export const Cursor = ({ active }) => (_jsx(Text, { color: C.mauve, bold: true, children: active ? '❯ ' : ' ' }));
6
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/components/cursor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEpC,+FAA+F;AAC/F,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,EAAuB,EAAE,EAAE,CAAC,CACzD,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,kBACvB,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAChB,CACR,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function GutterLine({ children }: {
3
+ children?: ReactNode;
4
+ }): import("react").JSX.Element;
5
+ export declare function Gutter({ children }: {
6
+ children: ReactNode;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { C } from '../lib/theme.js';
4
+ // One row inside a gutter block. The │ is the box's left border, so text that wraps keeps its gutter.
5
+ export function GutterLine({ children }) {
6
+ return (_jsx(Box, { borderStyle: "single", borderColor: C.overlay1, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 2, children: children ?? _jsx(Text, { children: " " }) }));
7
+ }
8
+ // The one gutter block a screen has: ┌, its rows, └.
9
+ export function Gutter({ children }) {
10
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: C.overlay1, children: "\u250C" }), children, _jsx(Text, { color: C.overlay1, children: "\u2514" })] }));
11
+ }
12
+ //# sourceMappingURL=gutter-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gutter-line.js","sourceRoot":"","sources":["../../src/components/gutter-line.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEpC,sGAAsG;AACtG,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAA4B;IAC/D,OAAO,CACL,KAAC,GAAG,IACF,WAAW,EAAC,QAAQ,EACpB,WAAW,EAAE,CAAC,CAAC,QAAQ,EACvB,SAAS,EAAE,KAAK,EAChB,WAAW,EAAE,KAAK,EAClB,YAAY,EAAE,KAAK,EACnB,WAAW,EAAE,CAAC,YAEb,QAAQ,IAAI,KAAC,IAAI,oBAAS,GACvB,CACP,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,EAA2B;IAC1D,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,uBAAU,EAChC,QAAQ,EACT,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,uBAAU,IAC7B,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ListKind, ListRow } from '../lib/db.js';
2
+ export declare const MAX_VISIBLE = 12;
3
+ export declare const oneLine: (text: string) => string;
4
+ type Props = {
5
+ rows: ListRow[];
6
+ index: number;
7
+ which: ListKind;
8
+ now: Date;
9
+ maxVisible?: number;
10
+ };
11
+ export declare function LookupList({ rows, index, which, now, maxVisible }: Props): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { TUI } from '../lib/messages.js';
4
+ import { C } from '../lib/theme.js';
5
+ import { relativeTime } from '../lib/time.js';
6
+ import { Cursor } from './cursor.js';
7
+ import { GutterLine } from './gutter-line.js';
8
+ export const MAX_VISIBLE = 12;
9
+ const QUERY_WIDTH = 34;
10
+ const KIND_WIDTH = 16;
11
+ // A multiline question becomes one line; Ink truncates it to the column with an ellipsis.
12
+ export const oneLine = (text) => text.trim().replace(/\s*\n\s*/g, ' ');
13
+ // The scrolling list History and Wordbook share. The window slides so the cursor stays inside it.
14
+ export function LookupList({ rows, index, which, now, maxVisible = MAX_VISIBLE }) {
15
+ const start = Math.max(0, Math.min(index - maxVisible + 1, rows.length - maxVisible));
16
+ const end = Math.min(rows.length, start + maxVisible);
17
+ return (_jsxs(_Fragment, { children: [start > 0 && (_jsx(GutterLine, { children: _jsx(Text, { color: C.overlay0, children: TUI.moreAbove(start) }) })), rows.slice(start, end).map((row, offset) => {
18
+ const active = start + offset === index;
19
+ const when = which === 'wordbook' ? TUI.savedAgo(relativeTime(row.saved_at ?? row.created_at, now)) : relativeTime(row.created_at, now);
20
+ return (_jsxs(GutterLine, { children: [_jsx(Cursor, { active: active }), _jsx(Text, { color: C.green, children: row.saved ? '★ ' : ' ' }), _jsx(Box, { width: QUERY_WIDTH, children: _jsx(Text, { color: active ? C.mauve : C.label, bold: active, wrap: "truncate-end", children: oneLine(row.query_text) }) }), _jsx(Box, { width: KIND_WIDTH, marginLeft: 1, children: _jsx(Text, { color: C.overlay0, children: row.kind }) }), _jsx(Text, { color: C.overlay0, children: when })] }, row.id));
21
+ }), end < rows.length && (_jsx(GutterLine, { children: _jsx(Text, { color: C.overlay0, children: TUI.moreBelow(rows.length - end) }) }))] }));
22
+ }
23
+ //# sourceMappingURL=lookup-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookup-list.js","sourceRoot":"","sources":["../../src/components/lookup-list.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function MessageBlock({ text }: {
2
+ text: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { C } from '../lib/theme.js';
4
+ import { GutterLine } from './gutter-line.js';
5
+ // A multi-line message inside the gutter: the first line is the red headline, the rest explain.
6
+ // Blank lines in the text stay blank rows. Setup problems and failed lookups both read this way.
7
+ export function MessageBlock({ text }) {
8
+ const [headline, ...rest] = text.split('\n');
9
+ return (_jsxs(_Fragment, { children: [_jsx(GutterLine, { children: _jsx(Text, { color: C.red, bold: true, children: headline }) }), rest.map((line, index) => (_jsx(GutterLine, { children: line === '' ? undefined : _jsx(Text, { color: C.label, children: line }) }, index)))] }));
10
+ }
11
+ //# sourceMappingURL=message-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-block.js","sourceRoot":"","sources":["../../src/components/message-block.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,gGAAgG;AAChG,iGAAiG;AACjG,MAAM,UAAU,YAAY,CAAC,EAAE,IAAI,EAAoB;IACrD,MAAM,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,CACL,8BACE,KAAC,UAAU,cACT,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,kBACrB,QAAQ,GACJ,GACI,EACZ,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CACzB,KAAC,UAAU,cAAc,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,KAAK,YAAG,IAAI,GAAQ,IAArE,KAAK,CAA8E,CACrG,CAAC,IACD,CACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Draft } from '../lib/model.js';
2
+ export declare function QueryInput(props: ({
3
+ draft: Draft;
4
+ active?: boolean;
5
+ }) | {
6
+ text: string;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { C } from '../lib/theme.js';
4
+ import { Cursor } from './cursor.js';
5
+ // Draws the question. With a draft it's the live input and shows the cursor; with plain text it's a
6
+ // read-only echo. Key handling lives in the app, not here.
7
+ export function QueryInput(props) {
8
+ const live = 'draft' in props && props.active !== false;
9
+ const text = 'draft' in props ? props.draft.text : props.text;
10
+ const cursor = live && 'draft' in props ? props.draft.cursor : -1;
11
+ let offset = 0;
12
+ const lines = text.split('\n').map((line, index) => {
13
+ const start = offset;
14
+ offset += line.length + 1;
15
+ const at = cursor - start;
16
+ const hasCursor = at >= 0 && at <= line.length;
17
+ return (_jsxs(Box, { children: [_jsx(Cursor, { active: index === 0 }), _jsx(Text, { color: live ? C.inputing : C.label, children: hasCursor ? (_jsxs(_Fragment, { children: [line.slice(0, at), _jsx(Text, { inverse: true, children: line[at] ?? ' ' }), line.slice(at + 1)] })) : (line || ' ') })] }, index));
18
+ });
19
+ return _jsx(Box, { flexDirection: "column", children: lines });
20
+ }
21
+ //# sourceMappingURL=query-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-input.js","sourceRoot":"","sources":["../../src/components/query-input.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,oGAAoG;AACpG,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,KAA8D;IACvF,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC;IACxD,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAElE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1B,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK,CAAC;QAC1B,MAAM,SAAS,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC;QAC/C,OAAO,CACL,MAAC,GAAG,eACF,KAAC,MAAM,IAAC,MAAM,EAAE,KAAK,KAAK,CAAC,GAAI,EAC/B,KAAC,IAAI,IAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,YACrC,SAAS,CAAC,CAAC,CAAC,CACX,8BACG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAClB,KAAC,IAAI,IAAC,OAAO,kBAAE,IAAI,CAAC,EAAE,CAAC,IAAI,GAAG,GAAQ,EACrC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,IAClB,CACJ,CAAC,CAAC,CAAC,CACF,IAAI,IAAI,GAAG,CACZ,GACI,KAZC,KAAK,CAaT,CACP,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,KAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,YAAE,KAAK,GAAO,CAAC;AACnD,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Result } from './types.js';
2
+ export type Command = {
3
+ mode: 'help';
4
+ } | {
5
+ mode: 'version';
6
+ } | {
7
+ mode: 'tui';
8
+ } | {
9
+ mode: 'ask';
10
+ query: string;
11
+ fresh: boolean;
12
+ json: boolean;
13
+ model?: string;
14
+ };
15
+ export declare function parseCommand(argv: string[]): Result<Command>;
@@ -0,0 +1,41 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { CLI } from './messages.js';
3
+ import { err, messageOf, ok } from './types.js';
4
+ // argv without the node binary and script path. The error is a Vietnamese line for stderr.
5
+ export function parseCommand(argv) {
6
+ let parsed;
7
+ try {
8
+ parsed = parseArgs({
9
+ args: argv,
10
+ allowPositionals: true,
11
+ options: {
12
+ fresh: { type: 'boolean' },
13
+ json: { type: 'boolean' },
14
+ model: { type: 'string' },
15
+ help: { type: 'boolean', short: 'h' },
16
+ version: { type: 'boolean', short: 'V' },
17
+ },
18
+ });
19
+ }
20
+ catch (e) {
21
+ const message = messageOf(e);
22
+ const unknown = /^Unknown option '([^']+)'/.exec(message);
23
+ return err(unknown ? CLI.unknownOption(unknown[1]) : CLI.badArguments(message));
24
+ }
25
+ const { values, positionals } = parsed;
26
+ if (values.help)
27
+ return ok({ mode: 'help' });
28
+ if (values.version)
29
+ return ok({ mode: 'version' });
30
+ if (argv.length === 0)
31
+ return ok({ mode: 'tui' });
32
+ // Unquoted words are joined, so `b3dict take for granted` works like the quoted form.
33
+ const query = positionals.join(' ');
34
+ if (query.trim() === '')
35
+ return err(CLI.missingQuery);
36
+ const model = values.model?.trim();
37
+ if (values.model !== undefined && !model)
38
+ return err(CLI.missingModel);
39
+ return ok({ mode: 'ask', query, fresh: values.fresh ?? false, json: values.json ?? false, model });
40
+ }
41
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/lib/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAe,MAAM,YAAY,CAAC;AAQ7D,2FAA2F;AAC3F,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC;YACjB,IAAI,EAAE,IAAI;YACV,gBAAgB,EAAE,IAAI;YACtB,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;gBACzB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;gBACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;aACzC;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,OAAO,GAAG,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IACvC,IAAI,MAAM,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IACnD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAElD,sFAAsF;IACtF,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEvE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACrG,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Ask } from './lookup.js';
2
+ import { type Config } from './types.js';
3
+ export declare const MAX_OUTPUT_TOKENS = 2500;
4
+ export declare function createAsk(config: Config): Ask;