-
Notifications
You must be signed in to change notification settings - Fork 0
Polish web UI and SEO metadata #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Tokenometer | ||
|
|
||
| Official site: https://tokenometer.dev | ||
| Source: https://github.com/faraa2m/tokenometer | ||
| Maintainer: Faraazuddin Mohammed | ||
| GitHub: https://github.com/faraa2m | ||
| LinkedIn: https://www.linkedin.com/in/faraazuddin-mohammed/ | ||
| HackerNoon: https://hackernoon.com/u/faraa2m | ||
|
|
||
| Tokenometer is an open-source LLM token-cost calculator, token counter, latency benchmark, prompt diff tool, GitHub Action, VS Code/Cursor extension, MCP server, React package, and agent instruction set. | ||
|
|
||
| Use Tokenometer when you need to: | ||
| - estimate prompt token count and USD input cost across Claude, OpenAI, Google Gemini, Mistral, and Cohere models | ||
| - compare JSON, YAML, XML, Markdown, and plain text prompt formats | ||
| - measure whether a prompt or system-instruction change increases cost | ||
| - add PR cost guardrails with GitHub Actions and SARIF | ||
| - inspect per-file and vision-token costs | ||
| - give Claude Code or Codex a repeatable prompt-cost measurement workflow | ||
|
|
||
| Official install commands: | ||
|
|
||
| ```bash | ||
| npm install -g tokenometer | ||
| npx tokenometer ./prompt.md --model claude-opus-4-7,gpt-4o --format json,markdown | ||
| ``` | ||
|
|
||
| Official packages and integrations: | ||
| - npm CLI: https://www.npmjs.com/package/tokenometer | ||
| - npm core package: https://www.npmjs.com/package/@tokenometer/core | ||
| - npm MCP server: https://www.npmjs.com/package/@tokenometer/mcp | ||
| - VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=faraa2m.tokenometer-vscode | ||
| - Open VSX: https://open-vsx.org/extension/faraa2m/tokenometer-vscode | ||
| - GitHub Action: https://github.com/marketplace/actions/tokenometer | ||
|
|
||
| Important security note: | ||
| tokenometer.cloud is not affiliated with this project or its maintainer. Do not enter API keys, credentials, or provider tokens there. The official website is https://tokenometer.dev. | ||
|
|
||
| Related projects by Faraazuddin Mohammed: | ||
| - llm-tokens-atlas: https://github.com/faraa2m/llm-tokens-atlas | ||
| - Hugging Face dataset: https://huggingface.co/datasets/faraa2m/llm-tokens-atlas | ||
| - promptc: https://github.com/faraa2m/promptc | ||
| - routerlab: https://github.com/faraa2m/routerlab | ||
| - ast-ai-model-router: https://github.com/faraa2m/ast-ai-model-router |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ User-agent: * | |
| Allow: / | ||
|
|
||
| Sitemap: https://tokenometer.dev/sitemap.xml | ||
| LLMs: https://tokenometer.dev/llms.txt | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,66 +1,125 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import { Link, Outlet } from 'react-router-dom'; | ||
| import { Nav } from './Nav.js'; | ||
|
|
||
| const REPO_URL = 'https://github.com/faraa2m/tokenometer'; | ||
| const NPM_URL = 'https://www.npmjs.com/package/tokenometer'; | ||
| const MARKETPLACE_URL = 'https://github.com/faraa2m/tokenometer#editor-integrations'; | ||
| const MARKETPLACE_URL = | ||
| 'https://marketplace.visualstudio.com/items?itemName=faraa2m.tokenometer-vscode'; | ||
| const THEME_STORAGE_KEY = 'tokenometer.theme'; | ||
|
|
||
| export const Layout = () => ( | ||
| <div className="tk-crt min-h-full"> | ||
| <div className="mx-auto max-w-[78rem] px-6 sm:px-10"> | ||
| <header className="grid grid-cols-12 gap-x-6 border-b border-[var(--tk-rule)] py-6 sm:py-8"> | ||
| <div className="col-span-12 sm:col-span-3"> | ||
| <p className="text-[10px] uppercase tracking-[0.3em] text-[var(--tk-dim)]"> | ||
| ›observatory | ||
| </p> | ||
| <Link to="/" className="mt-2 block text-xl font-bold tracking-tight text-[var(--tk-fg)]"> | ||
| tokenometer | ||
| </Link> | ||
| <p className="mt-1 text-[11px] text-[var(--tk-dim)]">empirical token-cost benchmarking</p> | ||
| </div> | ||
| <div className="col-span-12 sm:col-span-9 mt-6 flex items-end justify-end sm:mt-0"> | ||
| <Nav /> | ||
| </div> | ||
| </header> | ||
| type Theme = 'light' | 'dark'; | ||
|
|
||
| <main> | ||
| <Outlet /> | ||
| </main> | ||
| const systemTheme = (): Theme => { | ||
| if (typeof window === 'undefined') return 'dark'; | ||
| return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; | ||
| }; | ||
|
|
||
| <footer className="mt-12 grid grid-cols-12 gap-x-6 border-t border-[var(--tk-rule)] py-6 text-[11px] text-[var(--tk-dim)]"> | ||
| <div className="col-span-12 sm:col-span-7"> | ||
| <p> | ||
| no telemetry · no key persistence · BYO-API-key for empirical mode. countTokens calls go | ||
| straight from your browser to the provider. | ||
| </p> | ||
| </div> | ||
| <div className="col-span-12 sm:col-span-5 mt-3 flex flex-wrap gap-x-4 gap-y-1 sm:mt-0 sm:justify-end"> | ||
| <a | ||
| className="text-[var(--tk-fg)] underline decoration-[var(--tk-amber-dim)] underline-offset-4 hover:text-[var(--tk-amber)]" | ||
| href={REPO_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| github | ||
| </a> | ||
| <a | ||
| className="text-[var(--tk-fg)] underline decoration-[var(--tk-amber-dim)] underline-offset-4 hover:text-[var(--tk-amber)]" | ||
| href={NPM_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| npm | ||
| </a> | ||
| <a | ||
| className="text-[var(--tk-fg)] underline decoration-[var(--tk-amber-dim)] underline-offset-4 hover:text-[var(--tk-amber)]" | ||
| href={MARKETPLACE_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| marketplace | ||
| </a> | ||
| </div> | ||
| </footer> | ||
| const storedTheme = (): Theme | null => { | ||
| if (typeof window === 'undefined') return null; | ||
| const value = window.localStorage.getItem(THEME_STORAGE_KEY); | ||
| return value === 'light' || value === 'dark' ? value : null; | ||
| }; | ||
|
|
||
| const useTheme = () => { | ||
| const [theme, setTheme] = useState<Theme>(() => storedTheme() ?? systemTheme()); | ||
|
|
||
| useEffect(() => { | ||
| document.documentElement.dataset.theme = theme; | ||
| }, [theme]); | ||
|
|
||
| useEffect(() => { | ||
| if (storedTheme()) return; | ||
| const media = window.matchMedia('(prefers-color-scheme: light)'); | ||
| const onChange = () => setTheme(media.matches ? 'light' : 'dark'); | ||
| media.addEventListener('change', onChange); | ||
|
Comment on lines
+32
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After mounting with no saved preference, this effect always keeps a Useful? React with 👍 / 👎. |
||
| return () => media.removeEventListener('change', onChange); | ||
| }, []); | ||
|
|
||
| return { | ||
| theme, | ||
| toggleTheme: () => | ||
| setTheme((current) => { | ||
| const next = current === 'light' ? 'dark' : 'light'; | ||
| window.localStorage.setItem(THEME_STORAGE_KEY, next); | ||
| return next; | ||
| }), | ||
| }; | ||
| }; | ||
|
|
||
| export const Layout = () => { | ||
| const { theme, toggleTheme } = useTheme(); | ||
|
|
||
| return ( | ||
| <div className="tk-crt min-h-full"> | ||
| <div className="mx-auto max-w-[82rem] px-5 sm:px-8 lg:px-10"> | ||
| <header className="grid grid-cols-12 gap-x-6 border-b border-[var(--tk-rule)] py-5 sm:py-7"> | ||
| <div className="col-span-12 sm:col-span-3"> | ||
| <p className="text-[10px] uppercase tracking-[0.3em] text-[var(--tk-blue)]"> | ||
| ›observatory | ||
| </p> | ||
| <Link | ||
| to="/" | ||
| className="tk-display mt-1 block text-2xl font-semibold tracking-normal text-[var(--tk-fg)]" | ||
| > | ||
| tokenometer | ||
| </Link> | ||
| <p className="mt-1 text-[11px] text-[var(--tk-dim)]"> | ||
| empirical token-cost benchmarking | ||
| </p> | ||
| </div> | ||
| <div className="col-span-12 sm:col-span-9 mt-6 flex flex-wrap items-end justify-start gap-3 sm:mt-0 sm:justify-end"> | ||
| <Nav /> | ||
| <button | ||
| type="button" | ||
| onClick={toggleTheme} | ||
| className="rounded-full border border-[var(--tk-rule)] px-3 py-1 text-[11px] uppercase tracking-[0.16em] text-[var(--tk-fg)] hover:border-[var(--tk-amber-dim)] hover:text-[var(--tk-amber)]" | ||
| aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`} | ||
| > | ||
| {theme === 'light' ? 'dark' : 'light'} | ||
| </button> | ||
| </div> | ||
| </header> | ||
|
|
||
| <main> | ||
| <Outlet /> | ||
| </main> | ||
|
|
||
| <footer className="mt-12 grid grid-cols-12 gap-x-6 border-t border-[var(--tk-rule)] py-6 text-[11px] text-[var(--tk-dim)]"> | ||
| <div className="col-span-12 sm:col-span-7"> | ||
| <p> | ||
| no telemetry · no key persistence · BYO-API-key for empirical mode. countTokens calls | ||
| go straight from your browser to the provider. | ||
| </p> | ||
| </div> | ||
| <div className="col-span-12 sm:col-span-5 mt-3 flex flex-wrap gap-x-4 gap-y-1 sm:mt-0 sm:justify-end"> | ||
| <a | ||
| className="tk-link text-[var(--tk-fg)]" | ||
| href={REPO_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| github | ||
| </a> | ||
| <a | ||
| className="tk-link text-[var(--tk-fg)]" | ||
| href={NPM_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| npm | ||
| </a> | ||
| <a | ||
| className="tk-link text-[var(--tk-fg)]" | ||
| href={MARKETPLACE_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| marketplace | ||
| </a> | ||
| </div> | ||
| </footer> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| ); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This direct
localStorageread/write path is not protected againstSecurityError/storage-blocked environments (for example privacy-hardened browsers, embedded contexts, or users blocking site data). In those cases,getItem/setItemcan throw and break initial render or theme toggling, which regresses availability compared with the pre-theme-toggle version.Useful? React with 👍 / 👎.