qwerty-cli 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (49) hide show
  1. package/dist/ConfigEditor-GXFVIJP3.js +2 -0
  2. package/dist/ConfigEditor-GXFVIJP3.js.map +1 -0
  3. package/dist/DictBrowser-SZVB5W25.js +2 -0
  4. package/dist/DictBrowser-SZVB5W25.js.map +1 -0
  5. package/dist/HelpScreen-OUP5G5UG.js +2 -0
  6. package/dist/HelpScreen-OUP5G5UG.js.map +1 -0
  7. package/dist/PracticeScreen-LLUTKFXL.js +2 -0
  8. package/dist/PracticeScreen-LLUTKFXL.js.map +1 -0
  9. package/dist/StatsViewer-EY2N2LP3.js +2 -0
  10. package/dist/StatsViewer-EY2N2LP3.js.map +1 -0
  11. package/dist/WordLookup-UPEDLVKF.js +2 -0
  12. package/dist/WordLookup-UPEDLVKF.js.map +1 -0
  13. package/dist/chunk-2GTGXODM.js +2 -0
  14. package/dist/chunk-2GTGXODM.js.map +1 -0
  15. package/dist/chunk-2MRNI465.js +2 -0
  16. package/dist/chunk-2MRNI465.js.map +1 -0
  17. package/dist/chunk-6KRVNT2S.js +4 -0
  18. package/dist/chunk-6KRVNT2S.js.map +1 -0
  19. package/dist/chunk-6QICLHIY.js +2 -0
  20. package/dist/chunk-6QICLHIY.js.map +1 -0
  21. package/dist/chunk-ELWVQGDK.js +2 -0
  22. package/dist/chunk-ELWVQGDK.js.map +1 -0
  23. package/dist/chunk-MPE25TTQ.js +2 -0
  24. package/dist/chunk-MPE25TTQ.js.map +1 -0
  25. package/dist/chunk-QEX27D7F.js +2 -0
  26. package/dist/chunk-QEX27D7F.js.map +1 -0
  27. package/dist/chunk-RF5SVFBO.js +3 -0
  28. package/dist/chunk-RF5SVFBO.js.map +1 -0
  29. package/dist/chunk-TP77EGJ2.js +2 -0
  30. package/dist/chunk-TP77EGJ2.js.map +1 -0
  31. package/dist/chunk-UPA4JFCH.js +2 -0
  32. package/dist/chunk-UPA4JFCH.js.map +1 -0
  33. package/dist/chunk-UPYHZMDS.js +2 -0
  34. package/dist/chunk-UPYHZMDS.js.map +1 -0
  35. package/dist/cli.js +1 -3654
  36. package/dist/cli.js.map +1 -1
  37. package/dist/config.impl-IYJ4ZUPE.js +2 -0
  38. package/dist/config.impl-IYJ4ZUPE.js.map +1 -0
  39. package/dist/dict.impl-Y66SRRZL.js +4 -0
  40. package/dist/dict.impl-Y66SRRZL.js.map +1 -0
  41. package/dist/menu.impl-L5KAWNMC.js +2 -0
  42. package/dist/menu.impl-L5KAWNMC.js.map +1 -0
  43. package/dist/practice.impl-NYUJO5ER.js +2 -0
  44. package/dist/practice.impl-NYUJO5ER.js.map +1 -0
  45. package/dist/stats.impl-IXVF3Q5Y.js +7 -0
  46. package/dist/stats.impl-IXVF3Q5Y.js.map +1 -0
  47. package/dist/word.impl-C4AYZ3NC.js +2 -0
  48. package/dist/word.impl-C4AYZ3NC.js.map +1 -0
  49. package/package.json +2 -1
@@ -0,0 +1,2 @@
1
+ import{a as i,b as e,c as s,d as r}from"./chunk-ELWVQGDK.js";import"./chunk-6KRVNT2S.js";import c from"chalk";async function l(){let o=await i();console.log(JSON.stringify(o,null,2))}async function d(o){let t=await i(),n=s(t,o);if(n===void 0){console.error(c.red(`Key not found: ${o}`)),process.exitCode=1;return}console.log(typeof n=="string"?n:JSON.stringify(n))}async function m(o,t){let n=await i(),f=r(n,o,t);await e(f),console.log(c.green(`Set ${o} = ${t}`))}export{d as configGet,l as configList,m as configSet};
2
+ //# sourceMappingURL=config.impl-IYJ4ZUPE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/config.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { loadConfig, saveConfig, getByPath, setByPath } from '../infra/config-store.js';\n\nexport async function configList(): Promise<void> {\n const cfg = await loadConfig();\n console.log(JSON.stringify(cfg, null, 2));\n}\n\nexport async function configGet(key: string): Promise<void> {\n const cfg = await loadConfig();\n const v = getByPath(cfg, key);\n if (v === undefined) {\n console.error(chalk.red(`Key not found: ${key}`));\n process.exitCode = 1;\n return;\n }\n console.log(typeof v === 'string' ? v : JSON.stringify(v));\n}\n\nexport async function configSet(key: string, value: string): Promise<void> {\n const cfg = await loadConfig();\n const next = setByPath(cfg, key, value);\n await saveConfig(next);\n console.log(chalk.green(`Set ${key} = ${value}`));\n}\n"],"mappings":"yFAAA,OAAOA,MAAW,QAGlB,eAAsBC,GAA4B,CAChD,IAAMC,EAAM,MAAMC,EAAW,EAC7B,QAAQ,IAAI,KAAK,UAAUD,EAAK,KAAM,CAAC,CAAC,CAC1C,CAEA,eAAsBE,EAAUC,EAA4B,CAC1D,IAAMH,EAAM,MAAMC,EAAW,EACvBG,EAAIC,EAAUL,EAAKG,CAAG,EAC5B,GAAIC,IAAM,OAAW,CACnB,QAAQ,MAAME,EAAM,IAAI,kBAAkBH,CAAG,EAAE,CAAC,EAChD,QAAQ,SAAW,EACnB,MACF,CACA,QAAQ,IAAI,OAAOC,GAAM,SAAWA,EAAI,KAAK,UAAUA,CAAC,CAAC,CAC3D,CAEA,eAAsBG,EAAUJ,EAAaK,EAA8B,CACzE,IAAMR,EAAM,MAAMC,EAAW,EACvBQ,EAAOC,EAAUV,EAAKG,EAAKK,CAAK,EACtC,MAAMG,EAAWF,CAAI,EACrB,QAAQ,IAAIH,EAAM,MAAM,OAAOH,CAAG,MAAMK,CAAK,EAAE,CAAC,CAClD","names":["chalk","configList","cfg","loadConfig","configGet","key","v","getByPath","chalk","configSet","value","next","setByPath","saveConfig"]}
@@ -0,0 +1,4 @@
1
+ import{a as d,b as m,d as s,f as y}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{b as g,c as l}from"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import i from"chalk";function h(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(2)} MB`}function u(e){let t=0,o=e.replace(/\x1b\[[0-9;]*m/g,"");for(let r of o){let a=r.codePointAt(0);t+=a>11904&&a<64256?2:1}return t}async function f(e){let t=await Promise.all(e.map(async n=>({entry:n,local:await s(n.id)}))),o={local:3,id:Math.max(2,...t.map(n=>n.entry.id.length)),name:Math.max(4,...t.map(n=>n.entry.name.length)),category:Math.max(8,...t.map(n=>n.entry.category.length)),length:Math.max(5,...t.map(n=>String(n.entry.length).length))},r=(n,c)=>n+" ".repeat(Math.max(0,c-u(n))),a=[r(" ",o.local),i.bold(r("ID",o.id)),i.bold(r("Name",o.name)),i.bold(r("Category",o.category)),i.bold(r("Words",o.length)),i.bold("Description")].join(" ");console.log(a);for(let{entry:n,local:c}of t){let p=c?i.green(" \u2713"):" ";console.log([r(p,o.local),r(n.id,o.id),r(n.name,o.name),i.dim(r(n.category,o.category)),r(String(n.length),o.length),i.dim(n.description)].join(" "))}}async function $(e){let t=await l(),o=e.category?t.filter(r=>r.category===e.category):t;if(e.localOnly){let r=await Promise.all(o.map(a=>s(a.id)));o=o.filter((a,n)=>r[n])}await f(o),console.log(i.dim(`
2
+ ${o.length} dictionaries`))}async function P(e,t){let o=await l(),r=g(o,e,t);if(r.length===0){console.log(i.yellow(`No dictionaries matched "${e}"`));return}await f(r),console.log(i.dim(`
3
+ ${r.length} matches`))}async function D(e){try{let{words:t,size:o}=await d(e);console.log(i.green(`Saved ${t.length} words (${h(o)}) \u2192 dict ${i.bold(e)}`))}catch(t){console.error(i.red(t.message)),process.exitCode=1}}async function M(e,t){try{let{words:o,size:r}=await m(e,t.id);console.log(i.green(`Imported ${o.length} words (${h(r)}) as ${i.bold(t.id)}`))}catch(o){console.error(i.red(o.message)),process.exitCode=1}}async function B(e){let t=await y(e);console.log(t?i.green(`Removed ${e}`):i.yellow(`Nothing to remove for ${e}`))}export{M as dictImport,$ as dictList,D as dictPull,B as dictRemove,P as dictSearch};
4
+ //# sourceMappingURL=dict.impl-Y66SRRZL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/dict.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { filterRegistry, type DictionaryEntry } from '../domain/dictionary.js';\nimport { loadRegistry } from '../infra/registry-store.js';\nimport {\n pullDictionary,\n importDictionary,\n isLocallyAvailable,\n removeDictionary,\n} from '../infra/dict-downloader.js';\n\nfunction fmtBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n return `${(n / 1024 / 1024).toFixed(2)} MB`;\n}\n\n// Naive: count CJK as width-2 for alignment.\nfunction visibleWidth(s: string): number {\n let w = 0;\n const plain = s.replace(/\\x1b\\[[0-9;]*m/g, '');\n for (const ch of plain) {\n const code = ch.codePointAt(0)!;\n w += code > 0x2e80 && code < 0xfb00 ? 2 : 1;\n }\n return w;\n}\n\nasync function renderTable(entries: DictionaryEntry[]): Promise<void> {\n const flagged = await Promise.all(\n entries.map(async (e) => ({ entry: e, local: await isLocallyAvailable(e.id) })),\n );\n const widths = {\n local: 3,\n id: Math.max(2, ...flagged.map((f) => f.entry.id.length)),\n name: Math.max(4, ...flagged.map((f) => f.entry.name.length)),\n category: Math.max(8, ...flagged.map((f) => f.entry.category.length)),\n length: Math.max(5, ...flagged.map((f) => String(f.entry.length).length)),\n };\n const pad = (s: string, n: number) => s + ' '.repeat(Math.max(0, n - visibleWidth(s)));\n const header = [\n pad(' ', widths.local),\n chalk.bold(pad('ID', widths.id)),\n chalk.bold(pad('Name', widths.name)),\n chalk.bold(pad('Category', widths.category)),\n chalk.bold(pad('Words', widths.length)),\n chalk.bold('Description'),\n ].join(' ');\n console.log(header);\n for (const { entry, local } of flagged) {\n const mark = local ? chalk.green(' ✓') : ' ';\n console.log(\n [\n pad(mark, widths.local),\n pad(entry.id, widths.id),\n pad(entry.name, widths.name),\n chalk.dim(pad(entry.category, widths.category)),\n pad(String(entry.length), widths.length),\n chalk.dim(entry.description),\n ].join(' '),\n );\n }\n}\n\nexport async function dictList(opts: { category?: string; localOnly?: boolean }): Promise<void> {\n const reg = await loadRegistry();\n let entries = opts.category ? reg.filter((e) => e.category === opts.category) : reg;\n if (opts.localOnly) {\n const flags = await Promise.all(entries.map((e) => isLocallyAvailable(e.id)));\n entries = entries.filter((_, i) => flags[i]);\n }\n await renderTable(entries);\n console.log(chalk.dim(`\\n${entries.length} dictionaries`));\n}\n\nexport async function dictSearch(\n keyword: string,\n opts: { category?: string; language?: string },\n): Promise<void> {\n const reg = await loadRegistry();\n const matches = filterRegistry(reg, keyword, opts);\n if (matches.length === 0) {\n console.log(chalk.yellow(`No dictionaries matched \"${keyword}\"`));\n return;\n }\n await renderTable(matches);\n console.log(chalk.dim(`\\n${matches.length} matches`));\n}\n\nexport async function dictPull(id: string): Promise<void> {\n try {\n const { words, size } = await pullDictionary(id);\n console.log(\n chalk.green(`Saved ${words.length} words (${fmtBytes(size)}) → dict ${chalk.bold(id)}`),\n );\n } catch (err) {\n console.error(chalk.red((err as Error).message));\n process.exitCode = 1;\n }\n}\n\nexport async function dictImport(file: string, opts: { id: string }): Promise<void> {\n try {\n const { words, size } = await importDictionary(file, opts.id);\n console.log(\n chalk.green(`Imported ${words.length} words (${fmtBytes(size)}) as ${chalk.bold(opts.id)}`),\n );\n } catch (err) {\n console.error(chalk.red((err as Error).message));\n process.exitCode = 1;\n }\n}\n\nexport async function dictRemove(id: string): Promise<void> {\n const removed = await removeDictionary(id);\n if (removed) console.log(chalk.green(`Removed ${id}`));\n else console.log(chalk.yellow(`Nothing to remove for ${id}`));\n}\n"],"mappings":"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","names":["chalk","fmtBytes","n","visibleWidth","s","w","plain","ch","code","renderTable","entries","flagged","e","isLocallyAvailable","widths","f","pad","header","chalk","entry","local","mark","dictList","opts","reg","loadRegistry","flags","_","i","dictSearch","keyword","matches","filterRegistry","dictPull","id","words","size","pullDictionary","err","dictImport","file","importDictionary","dictRemove","removed","removeDictionary"]}
@@ -0,0 +1,2 @@
1
+ import{a as i,b as a,c as m,d as s}from"./chunk-RF5SVFBO.js";import{h as n,j as e}from"./chunk-UPYHZMDS.js";import"./chunk-2GTGXODM.js";import{a as r}from"./chunk-ELWVQGDK.js";import"./chunk-2MRNI465.js";import{e as o}from"./chunk-QEX27D7F.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{render as c}from"ink";import{createElement as g}from"react";async function E(){i();let t=await r();n();let{waitUntilExit:p}=c(g(a,{initial:{name:"main"},initialCfg:t}),{patchConsole:!1,exitOnCtrlC:!1});await p(),m();let{lang:f,t:l}=o(t.language);s(e(),l,f)}export{E as runMainMenuImpl};
2
+ //# sourceMappingURL=menu.impl-L5KAWNMC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/menu.impl.ts"],"sourcesContent":["import { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\n\nexport async function runMainMenuImpl(): Promise<void> {\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, the menu leaks under\n // the exit report when the user quits.\n enterAltScreen();\n\n const cfg = await loadConfig();\n startSession();\n const { waitUntilExit } = render(\n createElement(App, { initial: { name: 'main' }, initialCfg: cfg }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n ensureMainScreen();\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"4SAAA,OAAS,UAAAA,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAQ9B,eAAsBC,GAAiC,CAIrDC,EAAe,EAEf,IAAMC,EAAM,MAAMC,EAAW,EAC7BC,EAAa,EACb,GAAM,CAAE,cAAAC,CAAc,EAAIC,EACxBC,EAAcC,EAAK,CAAE,QAAS,CAAE,KAAM,MAAO,EAAG,WAAYN,CAAI,CAAC,EACjE,CAAE,aAAc,GAAO,YAAa,EAAM,CAC5C,EACA,MAAMG,EAAc,EACpBI,EAAiB,EACjB,GAAM,CAAE,KAAAC,EAAM,EAAAC,CAAE,EAAIC,EAAYV,EAAI,QAAQ,EAC5CW,EAAmBC,EAAc,EAAGH,EAAGD,CAAI,CAC7C","names":["render","createElement","runMainMenuImpl","enterAltScreen","cfg","loadConfig","start","waitUntilExit","render","createElement","App","ensureMainScreen","lang","t","pickStrings","printSessionReport","report"]}
@@ -0,0 +1,2 @@
1
+ import{a as c,b as l,c as f,d as u}from"./chunk-RF5SVFBO.js";import{h as m,j as p}from"./chunk-UPYHZMDS.js";import"./chunk-2GTGXODM.js";import{a}from"./chunk-ELWVQGDK.js";import"./chunk-2MRNI465.js";import{e as d}from"./chunk-QEX27D7F.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import s from"chalk";import{render as S}from"ink";import{createElement as T}from"react";var g=["order","dictation","review","random","loop"];function b(r){return g.includes(r)}async function k(r,o){if(!process.stdout.isTTY){console.error(s.red("Practice requires an interactive TTY.")),process.exitCode=1;return}let e=await a(),n=r??e.defaultDict;if(!n){console.error(s.red("No dictionary specified. Pass an id or set config.defaultDict.")),process.exitCode=1;return}let i=o.mode??e.defaultMode;if(!b(i)){console.error(s.red(`Invalid mode "${i}". Valid: ${g.join(", ")}`)),process.exitCode=1;return}let h=Math.max(0,Number(o.chapter??1)-1),t=o.stealth===!0||e.stealth==="default";t||c(),m();let{waitUntilExit:x}=S(T(l,{initial:{name:"practice",params:{dictId:n,chapterIndex:h,mode:i,stealth:t}},initialCfg:e,inline:t}),{patchConsole:!1,exitOnCtrlC:!1});await x(),t&&process.stdout.isTTY?process.stdout.write("\x1B[3F\x1B[0J"):f();let{lang:C,t:M}=d(e.language);u(p(),M,C)}export{k as runPractice};
2
+ //# sourceMappingURL=practice.impl-NYUJO5ER.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/practice.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\nimport type { Mode } from '../domain/chapters.js';\n\nconst MODES: Mode[] = ['order', 'dictation', 'review', 'random', 'loop'];\n\nfunction isMode(v: string): v is Mode {\n return (MODES as string[]).includes(v);\n}\n\nexport async function runPractice(\n dictIdArg: string | undefined,\n options: { chapter?: string | number; mode?: string; stealth?: boolean },\n): Promise<void> {\n if (!process.stdout.isTTY) {\n console.error(chalk.red('Practice requires an interactive TTY.'));\n process.exitCode = 1;\n return;\n }\n const cfg = await loadConfig();\n const dictId = dictIdArg ?? cfg.defaultDict;\n if (!dictId) {\n console.error(chalk.red('No dictionary specified. Pass an id or set config.defaultDict.'));\n process.exitCode = 1;\n return;\n }\n const mode = options.mode ?? cfg.defaultMode;\n if (!isMode(mode)) {\n console.error(chalk.red(`Invalid mode \"${mode}\". Valid: ${MODES.join(', ')}`));\n process.exitCode = 1;\n return;\n }\n const chapterIndex = Math.max(0, Number(options.chapter ?? 1) - 1);\n const stealth = options.stealth === true || cfg.stealth === 'default';\n\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, when the user exits,\n // the main screen still has the menu/practice content from frame 0\n // showing under the exit report. Stealth uses inline mode, skip.\n if (!stealth) enterAltScreen();\n\n startSession();\n const { waitUntilExit } = render(\n createElement(App, {\n initial: { name: 'practice', params: { dictId, chapterIndex, mode, stealth } },\n initialCfg: cfg,\n inline: stealth,\n }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n if (stealth && process.stdout.isTTY) {\n // Wipe the 3-row inline stealth frame so the shell prompt comes back clean.\n process.stdout.write('\\x1b[3F\\x1b[0J');\n } else {\n ensureMainScreen();\n }\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"uSAAA,OAAOA,MAAW,QAClB,OAAS,UAAAC,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAS9B,IAAMC,EAAgB,CAAC,QAAS,YAAa,SAAU,SAAU,MAAM,EAEvE,SAASC,EAAOC,EAAsB,CACpC,OAAQF,EAAmB,SAASE,CAAC,CACvC,CAEA,eAAsBC,EACpBC,EACAC,EACe,CACf,GAAI,CAAC,QAAQ,OAAO,MAAO,CACzB,QAAQ,MAAMC,EAAM,IAAI,uCAAuC,CAAC,EAChE,QAAQ,SAAW,EACnB,MACF,CACA,IAAMC,EAAM,MAAMC,EAAW,EACvBC,EAASL,GAAaG,EAAI,YAChC,GAAI,CAACE,EAAQ,CACX,QAAQ,MAAMH,EAAM,IAAI,gEAAgE,CAAC,EACzF,QAAQ,SAAW,EACnB,MACF,CACA,IAAMI,EAAOL,EAAQ,MAAQE,EAAI,YACjC,GAAI,CAACN,EAAOS,CAAI,EAAG,CACjB,QAAQ,MAAMJ,EAAM,IAAI,iBAAiBI,CAAI,aAAaV,EAAM,KAAK,IAAI,CAAC,EAAE,CAAC,EAC7E,QAAQ,SAAW,EACnB,MACF,CACA,IAAMW,EAAe,KAAK,IAAI,EAAG,OAAON,EAAQ,SAAW,CAAC,EAAI,CAAC,EAC3DO,EAAUP,EAAQ,UAAY,IAAQE,EAAI,UAAY,UAMvDK,GAASC,EAAe,EAE7BC,EAAa,EACb,GAAM,CAAE,cAAAC,CAAc,EAAIC,EACxBC,EAAcC,EAAK,CACjB,QAAS,CAAE,KAAM,WAAY,OAAQ,CAAE,OAAAT,EAAQ,aAAAE,EAAc,KAAAD,EAAM,QAAAE,CAAQ,CAAE,EAC7E,WAAYL,EACZ,OAAQK,CACV,CAAC,EACD,CAAE,aAAc,GAAO,YAAa,EAAM,CAC5C,EACA,MAAMG,EAAc,EAChBH,GAAW,QAAQ,OAAO,MAE5B,QAAQ,OAAO,MAAM,gBAAgB,EAErCO,EAAiB,EAEnB,GAAM,CAAE,KAAAC,EAAM,EAAAC,CAAE,EAAIC,EAAYf,EAAI,QAAQ,EAC5CgB,EAAmBC,EAAc,EAAGH,EAAGD,CAAI,CAC7C","names":["chalk","render","createElement","MODES","isMode","v","runPractice","dictIdArg","options","chalk","cfg","loadConfig","dictId","mode","chapterIndex","stealth","enterAltScreen","start","waitUntilExit","render","createElement","App","ensureMainScreen","lang","t","pickStrings","printSessionReport","report"]}
@@ -0,0 +1,7 @@
1
+ import{b as $,c as g,d as u,e as y,f as a,h}from"./chunk-MPE25TTQ.js";import{a as m,d as p}from"./chunk-UPA4JFCH.js";import"./chunk-6KRVNT2S.js";import t from"chalk";async function T(d){let l=Math.max(1,Number(d.days)||14),w=Math.max(1,Number(d.top)||10),r=await $(),M=await m();if(r.length===0){console.log(t.yellow("No practice history yet. Run `qwerty practice <dict>` to get started."));return}let e=h(r,l),k=y(r),n=r.reduce((o,s)=>o+s.wordCount,0),b=r.reduce((o,s)=>o+s.errors,0),i=r.reduce((o,s)=>o+s.durationMs,0),f=r.reduce((o,s)=>o+(s.wordCount-Object.keys(s.perWordErrors).length),0),S=i>0?Math.round(n/(i/6e4)*10)/10:0,v=n===0?1:f/n;console.log(t.bold(`
2
+ Lifetime`)),console.log(` ${t.dim("sessions")} ${r.length} ${t.dim("words")} ${n} ${t.dim("errors")} ${b}`),console.log(` ${t.dim("avg wpm")} ${S} ${t.dim("avg accuracy")} ${Math.round(v*1e3)/10}% ${t.dim("streak")} ${t.bold(k)}d`),console.log(t.bold(`
3
+ Last ${l} days`)),console.log(` ${t.dim("wpm ")} ${a(e.map(o=>o.wpm))} ${t.dim("max")} ${Math.round(Math.max(...e.map(o=>o.wpm)))}`),console.log(` ${t.dim("accuracy")} ${a(e.map(o=>o.accuracy*100))} ${t.dim("range")} ${Math.round(Math.min(...e.map(o=>o.accuracy*100)))}-${Math.round(Math.max(...e.map(o=>o.accuracy*100)))}%`),console.log(` ${t.dim("sessions")} ${a(e.map(o=>o.sessions))}`);let x=r.slice(-5).reverse();console.log(t.bold(`
4
+ Last 5 sessions`));for(let o of x){let s=g(o),E=Math.round(u(o)*1e3)/10;console.log(` ${t.dim(o.ts.replace("T"," ").slice(0,16))} ${t.cyan(o.dictId.padEnd(14))} ch${String(o.chapter+1).padStart(3)} ${o.mode.padEnd(9)} ${String(o.wordCount).padStart(3)}w ${o.errors}err ${s}wpm ${E}%`)}let c=p(M,w);if(c.length>0){console.log(t.bold(`
5
+ Top ${c.length} mistakes`));for(let[o,s]of c)console.log(` ${t.red(String(s.count).padStart(3))} ${t.bold(o.padEnd(20))} ${t.dim(s.dictIds.join(", "))}`)}else console.log(t.bold(`
6
+ Top mistakes`)),console.log(t.dim(" none \u2014 keep going"));console.log()}export{T as runStats};
7
+ //# sourceMappingURL=stats.impl-IXVF3Q5Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/stats.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n dailyBuckets,\n sparkline,\n dailyStreak,\n} from '../domain/stats.js';\nimport { loadMistakes, topN } from '../domain/mistakes.js';\n\nexport async function runStats(opts: { days: string; top: string }): Promise<void> {\n const days = Math.max(1, Number(opts.days) || 14);\n const topCount = Math.max(1, Number(opts.top) || 10);\n const sessions = await loadSessions();\n const book = await loadMistakes();\n\n if (sessions.length === 0) {\n console.log(\n chalk.yellow('No practice history yet. Run `qwerty practice <dict>` to get started.'),\n );\n return;\n }\n\n const buckets = dailyBuckets(sessions, days);\n const streak = dailyStreak(sessions);\n const totalWords = sessions.reduce((a, s) => a + s.wordCount, 0);\n const totalErrors = sessions.reduce((a, s) => a + s.errors, 0);\n const totalMs = sessions.reduce((a, s) => a + s.durationMs, 0);\n const firstTryWords = sessions.reduce(\n (a, s) => a + (s.wordCount - Object.keys(s.perWordErrors).length),\n 0,\n );\n const overallWpm = totalMs > 0 ? Math.round((totalWords / (totalMs / 60000)) * 10) / 10 : 0;\n const overallAcc = totalWords === 0 ? 1 : firstTryWords / totalWords;\n\n console.log(chalk.bold('\\nLifetime'));\n console.log(\n ` ${chalk.dim('sessions')} ${sessions.length} ${chalk.dim('words')} ${totalWords} ${chalk.dim('errors')} ${totalErrors}`,\n );\n console.log(\n ` ${chalk.dim('avg wpm')} ${overallWpm} ${chalk.dim('avg accuracy')} ${Math.round(overallAcc * 1000) / 10}% ${chalk.dim('streak')} ${chalk.bold(streak)}d`,\n );\n\n console.log(chalk.bold(`\\nLast ${days} days`));\n console.log(\n ` ${chalk.dim('wpm ')} ${sparkline(buckets.map((b) => b.wpm))} ${chalk.dim('max')} ${Math.round(Math.max(...buckets.map((b) => b.wpm)))}`,\n );\n console.log(\n ` ${chalk.dim('accuracy')} ${sparkline(buckets.map((b) => b.accuracy * 100))} ${chalk.dim('range')} ${Math.round(Math.min(...buckets.map((b) => b.accuracy * 100)))}-${Math.round(Math.max(...buckets.map((b) => b.accuracy * 100)))}%`,\n );\n console.log(` ${chalk.dim('sessions')} ${sparkline(buckets.map((b) => b.sessions))}`);\n\n const recent = sessions.slice(-5).reverse();\n console.log(chalk.bold('\\nLast 5 sessions'));\n for (const s of recent) {\n const wpm = computeWPM(s);\n const acc = Math.round(accuracy(s) * 1000) / 10;\n console.log(\n ` ${chalk.dim(s.ts.replace('T', ' ').slice(0, 16))} ${chalk.cyan(s.dictId.padEnd(14))} ch${String(s.chapter + 1).padStart(3)} ${s.mode.padEnd(9)} ${String(s.wordCount).padStart(3)}w ${s.errors}err ${wpm}wpm ${acc}%`,\n );\n }\n\n const top = topN(book, topCount);\n if (top.length > 0) {\n console.log(chalk.bold(`\\nTop ${top.length} mistakes`));\n for (const [word, entry] of top) {\n console.log(\n ` ${chalk.red(String(entry.count).padStart(3))} ${chalk.bold(word.padEnd(20))} ${chalk.dim(entry.dictIds.join(', '))}`,\n );\n }\n } else {\n console.log(chalk.bold('\\nTop mistakes'));\n console.log(chalk.dim(' none — keep going'));\n }\n console.log();\n}\n"],"mappings":"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","names":["chalk","runStats","opts","days","topCount","sessions","loadSessions","book","loadMistakes","chalk","buckets","dailyBuckets","streak","dailyStreak","totalWords","a","totalErrors","totalMs","firstTryWords","overallWpm","overallAcc","sparkline","b","recent","s","wpm","computeWPM","acc","accuracy","top","topN","word","entry"]}
@@ -0,0 +1,2 @@
1
+ import{c as w}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as h}from"./chunk-UPA4JFCH.js";import{d as g}from"./chunk-6QICLHIY.js";import{a as u}from"./chunk-6KRVNT2S.js";import i from"chalk";import{readdir as k}from"fs/promises";async function j(){try{return(await k(u.dictsDir)).filter(s=>s.endsWith(".json")&&!s.endsWith(".meta.json")).map(s=>s.replace(/\.json$/,""))}catch{return[]}}async function N(c,s){let p=c.toLowerCase(),a=await j();if(a.length===0){console.log(i.yellow("No local dictionaries. Run `qwerty dict pull <id>` first."));return}let l=[];for(let o of a){let t=await w(o);if(t)for(let n of t){let e=n.name.toLowerCase();(s.exact?e===p:e.includes(p))&&l.push({dictId:o,word:n})}}if(l.length===0){console.log(i.yellow(`No matches for "${c}" in ${a.length} local dictionaries`));return}let d=new Map;for(let o of l){let t=d.get(o.word.name)??[];t.push(o),d.set(o.word.name,t)}let y=await h();for(let[o,t]of d){let n=t[0].word;console.log(),console.log(i.bold.white(o));let e=n.usphone?`US /${n.usphone}/`:"",m=n.ukphone?`UK /${n.ukphone}/`:"";(e||m)&&console.log(i.dim(` ${[e,m].filter(Boolean).join(" ")}`));for(let r of n.trans??[])console.log(i.cyan(` \xB7 ${r}`));let $=await Promise.all(t.map(async r=>(await g(r.dictId))?.name??r.dictId));console.log(i.dim(` in: ${$.join(", ")}`));let f=y[o];f&&console.log(i.dim(` mistakes: ${f.count} (last ${f.lastSeen.slice(0,10)})`))}console.log()}export{N as runWordLookup};
2
+ //# sourceMappingURL=word.impl-C4AYZ3NC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/word.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { readdir } from 'node:fs/promises';\nimport { paths } from '../infra/paths.js';\nimport { loadLocalDictionary } from '../infra/dict-downloader.js';\nimport { loadMistakes } from '../domain/mistakes.js';\nimport { findEntry } from '../infra/registry-store.js';\nimport type { Word } from '../domain/dictionary.js';\n\nasync function listLocalDictIds(): Promise<string[]> {\n try {\n const files = await readdir(paths.dictsDir);\n return files\n .filter((f) => f.endsWith('.json') && !f.endsWith('.meta.json'))\n .map((f) => f.replace(/\\.json$/, ''));\n } catch {\n return [];\n }\n}\n\nexport async function runWordLookup(keyword: string, opts: { exact?: boolean }): Promise<void> {\n const q = keyword.toLowerCase();\n const ids = await listLocalDictIds();\n if (ids.length === 0) {\n console.log(chalk.yellow('No local dictionaries. Run `qwerty dict pull <id>` first.'));\n return;\n }\n\n type Hit = { dictId: string; word: Word };\n const hits: Hit[] = [];\n for (const id of ids) {\n const words = await loadLocalDictionary(id);\n if (!words) continue;\n for (const w of words) {\n const wl = w.name.toLowerCase();\n const match = opts.exact ? wl === q : wl.includes(q);\n if (match) hits.push({ dictId: id, word: w });\n }\n }\n\n if (hits.length === 0) {\n console.log(\n chalk.yellow(`No matches for \"${keyword}\" in ${ids.length} local dictionaries`),\n );\n return;\n }\n\n // Group hits by word name; one block per distinct word.\n const byName = new Map<string, Hit[]>();\n for (const h of hits) {\n const arr = byName.get(h.word.name) ?? [];\n arr.push(h);\n byName.set(h.word.name, arr);\n }\n\n const book = await loadMistakes();\n for (const [name, group] of byName) {\n const first = group[0]!.word;\n console.log();\n console.log(chalk.bold.white(name));\n const us = first.usphone ? `US /${first.usphone}/` : '';\n const uk = first.ukphone ? `UK /${first.ukphone}/` : '';\n if (us || uk) console.log(chalk.dim(` ${[us, uk].filter(Boolean).join(' ')}`));\n for (const t of first.trans ?? []) console.log(chalk.cyan(` · ${t}`));\n const sources = await Promise.all(\n group.map(async (h) => {\n const reg = await findEntry(h.dictId);\n return reg?.name ?? h.dictId;\n }),\n );\n console.log(chalk.dim(` in: ${sources.join(', ')}`));\n const mistake = book[name];\n if (mistake) {\n console.log(\n chalk.dim(` mistakes: ${mistake.count} (last ${mistake.lastSeen.slice(0, 10)})`),\n );\n }\n }\n console.log();\n}\n"],"mappings":"4LAAA,OAAOA,MAAW,QAClB,OAAS,WAAAC,MAAe,cAOxB,eAAeC,GAAsC,CACnD,GAAI,CAEF,OADc,MAAMC,EAAQC,EAAM,QAAQ,GAEvC,OAAQC,GAAMA,EAAE,SAAS,OAAO,GAAK,CAACA,EAAE,SAAS,YAAY,CAAC,EAC9D,IAAKA,GAAMA,EAAE,QAAQ,UAAW,EAAE,CAAC,CACxC,MAAQ,CACN,MAAO,CAAC,CACV,CACF,CAEA,eAAsBC,EAAcC,EAAiBC,EAA0C,CAC7F,IAAMC,EAAIF,EAAQ,YAAY,EACxBG,EAAM,MAAMR,EAAiB,EACnC,GAAIQ,EAAI,SAAW,EAAG,CACpB,QAAQ,IAAIC,EAAM,OAAO,2DAA2D,CAAC,EACrF,MACF,CAGA,IAAMC,EAAc,CAAC,EACrB,QAAWC,KAAMH,EAAK,CACpB,IAAMI,EAAQ,MAAMC,EAAoBF,CAAE,EAC1C,GAAKC,EACL,QAAWE,KAAKF,EAAO,CACrB,IAAMG,EAAKD,EAAE,KAAK,YAAY,GAChBR,EAAK,MAAQS,IAAOR,EAAIQ,EAAG,SAASR,CAAC,IACxCG,EAAK,KAAK,CAAE,OAAQC,EAAI,KAAMG,CAAE,CAAC,CAC9C,CACF,CAEA,GAAIJ,EAAK,SAAW,EAAG,CACrB,QAAQ,IACND,EAAM,OAAO,mBAAmBJ,CAAO,QAAQG,EAAI,MAAM,qBAAqB,CAChF,EACA,MACF,CAGA,IAAMQ,EAAS,IAAI,IACnB,QAAWC,KAAKP,EAAM,CACpB,IAAMQ,EAAMF,EAAO,IAAIC,EAAE,KAAK,IAAI,GAAK,CAAC,EACxCC,EAAI,KAAKD,CAAC,EACVD,EAAO,IAAIC,EAAE,KAAK,KAAMC,CAAG,CAC7B,CAEA,IAAMC,EAAO,MAAMC,EAAa,EAChC,OAAW,CAACC,EAAMC,CAAK,IAAKN,EAAQ,CAClC,IAAMO,EAAQD,EAAM,CAAC,EAAG,KACxB,QAAQ,IAAI,EACZ,QAAQ,IAAIb,EAAM,KAAK,MAAMY,CAAI,CAAC,EAClC,IAAMG,EAAKD,EAAM,QAAU,OAAOA,EAAM,OAAO,IAAM,GAC/CE,EAAKF,EAAM,QAAU,OAAOA,EAAM,OAAO,IAAM,IACjDC,GAAMC,IAAI,QAAQ,IAAIhB,EAAM,IAAI,KAAK,CAACe,EAAIC,CAAE,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,EAChF,QAAWC,KAAKH,EAAM,OAAS,CAAC,EAAG,QAAQ,IAAId,EAAM,KAAK,UAAOiB,CAAC,EAAE,CAAC,EACrE,IAAMC,EAAU,MAAM,QAAQ,IAC5BL,EAAM,IAAI,MAAOL,IACH,MAAMW,EAAUX,EAAE,MAAM,IACxB,MAAQA,EAAE,MACvB,CACH,EACA,QAAQ,IAAIR,EAAM,IAAI,SAASkB,EAAQ,KAAK,IAAI,CAAC,EAAE,CAAC,EACpD,IAAME,EAAUV,EAAKE,CAAI,EACrBQ,GACF,QAAQ,IACNpB,EAAM,IAAI,eAAeoB,EAAQ,KAAK,UAAUA,EAAQ,SAAS,MAAM,EAAG,EAAE,CAAC,GAAG,CAClF,CAEJ,CACA,QAAQ,IAAI,CACd","names":["chalk","readdir","listLocalDictIds","readdir","paths","f","runWordLookup","keyword","opts","q","ids","chalk","hits","id","words","loadLocalDictionary","w","wl","byName","h","arr","book","loadMistakes","name","group","first","us","uk","t","sources","findEntry","mistake"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwerty-cli",
3
- "version": "0.0.1-alpha.7",
3
+ "version": "0.0.1-alpha.9",
4
4
  "description": "Terminal clone of qwerty-learner: typing practice for English vocabulary, with chapters, dictation, mistake book, and audio.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,6 +33,7 @@
33
33
  ],
34
34
  "license": "MIT",
35
35
  "dependencies": {
36
+ "boxen": "^7.1.1",
36
37
  "chalk": "^5.3.0",
37
38
  "commander": "^12.1.0",
38
39
  "ink": "^5.0.1",