npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@buyong/pi-ollama-cloud

v0.15.1

Published

Ollama Cloud provider, web search and fetch tools, and usage status for Pi.

Downloads

452

Readme

@buyong/pi-ollama-cloud

English | 한국어

Connects Pi to the Ollama Cloud API without a local Ollama server. The extension provides the cloud model list, web search and page reading, usage reports, and per-session rotation across several accounts.

Source and releases live in buYoung/pi-packages; the npm name is unchanged. The package is a fork of fgrehm/pi-ollama-cloud and keeps the Git history and MIT license of the former buYoung/pi-ollama-cloud repository.

Install and first use

You need Node.js 22.19 or later, Pi, and an Ollama Cloud API key. The package is developed and tested with Pi 1.3.0, and CI also checks compatibility with the latest Pi.

If you installed from Git, or still use commands or import paths from 0.13 or earlier, first follow Move from earlier versions, so that only one copy of the extension loads. Otherwise install from npm:

pi install npm:@buyong/pi-ollama-cloud

To install for one project only, run pi install npm:@buyong/pi-ollama-cloud --local instead. Project resources follow Pi's project trust setting.

Start Pi, then:

  1. In /login, choose Use an API key → Ollama Cloud and enter your key, or set OLLAMA_API_KEY in the environment that runs Pi. Skip this step if a key is already registered. Do not put keys in a repository or in settings examples.
  2. In /model, select an ollama-cloud model.
  3. Send a short question and check the reply. This and later model replies and web tools use real API quota. /ollama shows per-account usage and settings.

To use more accounts, add them with + Add account in /ollama; see Use several accounts.

Update

pi update npm:@buyong/pi-ollama-cloud

pi update without a target updates Pi itself. To update every installed extension, run pi update --extensions. An installation pinned to an exact version does not move to a new version on its own; run pi install again with the new version.

Tools and commands

| Name | Purpose | | --- | --- | | ollama_web_search | Ollama Cloud web search. Required: query. Optional: max_results (default 5, maximum 10), expand, offset, refresh | | ollama_web_fetch | Reads a web page's title, body, and links. Required: url. Optional: full, offset, refresh | | /ollama | Opens the settings screen in the TUI. Without a TUI, prints the settings and an account summary. | | /ollama usage | Shows balance, quota, status, and known reset dates for every account, plus request statistics for the current session's account. | | /ollama status [on\|off] | Turns the usage display in the TUI footer on or off. Without an argument, switches the current state. Saved to the global settings. | | /ollama webtools [on\|off] | Turns the web tools on or off. Without an argument, switches the current state. Saved to the global settings. |

Without a UI, as in print and JSON modes, /ollama writes its summary, usage, settings-save results, and errors to stderr. The JSON-mode event stream on stdout carries no command messages.

The web tools use the same account as model requests: the API key of the account assigned to the current session.

  • ollama_web_search: Each result shows a body preview of up to 500 characters by default. To read more of one result, repeat the same query and max_results with expand=<result number, starting at 1>. To continue, keep query, max_results, and expand and pass each returned nextOffset as offset; the result also names this next call. offset without expand is a position in the result-list text.
  • ollama_web_fetch: Returns the page body in 3000-character pieces by default. full=true is not unlimited; it requests the largest piece that fits the output limit. While body text remains, the result includes nextOffset; pass it as offset with the same url until no nextOffset is returned.
  • The combined content and details follow Pi's output limit (51,200 bytes and 2,000 lines in Pi 1.3.0). Titles, URLs, links, and body text in details are length-limited previews as well.
  • offset is a UTF-16 position in a JavaScript string. Pass nextOffset as is rather than calculating it. Reading on makes no extra API call while the cached entry is within its lifetime (24 hours by default) and has not been evicted. After the entry expires or is evicted, or another call refreshes it with refresh=true, the search or page is fetched again and its text can change.
  • Both tools accept refresh=true to bypass the cache. # live query in a result means a new request or a shared in-flight request; # from cache means a cached result.

Use several accounts

With several accounts registered, each session keeps one account and each new session gets the next account. When an account reaches its usage limit, the session moves to the next account.

Before adding accounts: the extension cannot tell apart several keys from the same Ollama account, and those keys share one usage limit. Register only accounts that each person legitimately owns. Ollama can treat creating extra accounts to avoid limits as abuse. For shared use by several people, consider the Team plan.

Accounts

  • Default account (default): Pi's ollama-cloud credential. Set it with /login or OLLAMA_API_KEY; Pi needs it to mark the models as available. Remove it with /logout.
  • Additional accounts: Stored in ~/.pi/agent/ollama-cloud-accounts.json, which follows Pi's agent directory if you change it. The format matches Pi's auth.json, and the file is read and written through Pi's credential storage, so it has the same 0600 permissions and locking. A key can be written as a literal value or as a $ENV, ${ENV}, or !command reference.
  • An account ID has 1 to 32 characters: lowercase letters, digits, -, and _, starting with a letter or digit. default, ollama-cloud, and Pi's built-in provider names (such as anthropic) are not allowed.

In the TUI, + Add account in /ollama asks for the key or reference first, then the account ID. An empty ID becomes an unused one such as account-1. Keys being typed and keys in the account list show only their first 6 characters, even if you paste a key into the name field by mistake. After saving, the extension checks the key with /api/balance and does not save it if Ollama rejects it or it duplicates a registered key. Without a TUI, edit the file directly; the next request reads the changed file.

{
  "work": { "type": "api_key", "key": "$OLLAMA_WORK_API_KEY" },
  "personal": { "type": "api_key", "key": "!op read op://Private/ollama/credential" }
}

Pi 1.3.0 caches !command results until the process exits. If the command returns a new key, restart Pi to read it; /reload does not run the command again.

Per-session rotation

  • A session's first Ollama Cloud request (a model reply or a web tool) assigns the next account in turn. The order is default, then the order in ollama-cloud-accounts.json; after the last account it starts over.
  • The assignment is recorded in the session file. Restarting Pi, /resume, or /reload keeps the same account for the same session. /new and forks are new sessions and get the next account.
  • Compaction requests and web tools in the same session use that account too. Several Pi processes and subagents share the rotation order and account state through ~/.pi/agent/ollama-cloud-account-state.json. That file stores key fingerprints, limit times, and balance responses, never the keys.
  • To switch the current session's account yourself, select an account in /ollama and choose Use for this session.

If the state file is locked or saving fails, the change is kept in the current process only. It is saved with the next update if no other process changed the file in the meantime; otherwise the file's state wins.

Account switching and limits

If one of these errors arrives before a reply starts, the extension marks the account and sends the same request to the next account. The session moves to the new account and shows a notice once.

| Response | Handling | | --- | --- | | 429 session, weekly, or monthly usage limit | The account is skipped until the reset time reported by /api/balance. Without a reported time, it is retried after 30 minutes (session), 6 hours (weekly), or 24 hours (monthly). | | 401 key rejected | The account is skipped until you run Clear limit markers in /ollama or its key changes. The next request reads $ENV and ${ENV} references again, so if the variable now gives a different key, the account is used again. A !command reference keeps its cached result until Pi restarts (see Accounts). | | 403 model rejected | The account is skipped for that model only, for 6 hours. A 403 on a web tool request does not switch accounts. |

If the concurrent-request limit (429 "too many concurrent requests") hits before a reply starts, the extension resends on the same account instead of switching. When several sessions or subagents share one account, a slot frees only when another request finishes. It waits about 1, 2, 4, 8, and 16 seconds (each scaled randomly by 0.5 to 1.5), resending up to five times, and shows a notice once when it first waits. If no slot frees, Pi's own retries take over. Cancelling during a wait stops immediately.

Other rate limits (429), server errors, and errors after a reply starts do not switch accounts; Pi's own retries handle them. If no account is usable, the extension returns an error with each account's reason and quota exceeded, which Pi does not retry automatically.

Purchased-credit protection

Ollama spends included usage first and then draws on purchased credits. Because requests keep succeeding without a limit error while purchased credits remain, the extension stops sending requests on an account that holds purchased credits once its included usage drops to 3% or less. This applies even with a single account.

  • Before each account's first request, the extension queries /api/balance once. Accounts without purchased credits are checked again every 15 minutes to notice a new purchase; accounts with purchased credits are checked every 30 seconds to 10 minutes, depending on how much is left.
  • If a balance check rejects the key (401), the next check of that account reads referenced keys again. New environment-variable values apply without /reload; !command references follow the Pi process cache described above.
  • Usage between two checks cannot be stopped. The less included usage remains, the more often the extension checks, to keep that gap small.
  • An account whose balance has never been checked successfully is not blocked; it shows available (balance not verified).
  • An account with confirmed purchased credits is blocked when a balance recheck fails and either less than 10% of included usage remains or the last successful check is 30 minutes old or more. The balance check is retried after 1 minute, and requests resume once it succeeds.

Settings screen

Run /ollama in the TUI to open the settings screen. Move with the up and down arrows, change an item with Enter or Space, and close with Esc.

A usage report appears right below the title. For each account it shows a quota bar, the included balance and reset date, and purchased credits; for the current session's account (the first account before assignment) it also shows the 7-day request count. The report refreshes only while the screen is open and stops querying when you close it.

  • The balance (/api/balance) of every account is queried every 5 seconds. The next query is scheduled after the previous one finishes, so requests do not overlap.
  • Request statistics (/api/usage) have a low rate limit and are queried once every 15 seconds. External observation shows a 429 above about 5 requests per minute.
  • If a balance query gets a 429, it is retried after 30 seconds and the report says so.

| Item | Behavior | | --- | --- | | Usage status bar | Turns the footer usage display (usageStatus) on or off. Saved to the global settings file. | | Web tools | Turns ollama_web_search and ollama_web_fetch on or off. Saved to the global settings file; locked while PI_OLLAMA_WEB_TOOLS turns them off. | | Account rows | Show the account ID (* marks the current session's account), the first 6 characters of the key, the latest usage, and the status. Usage refreshes with the report. Press Enter to choose Use for this session, Disable/Enable, Clear limit markers, or Remove account (additional accounts only, after confirmation). | | + Add account | Enter a key or a $ENV or !command reference, then the account ID (empty becomes account-N). Keys show only their first 6 characters. |

The default account cannot be removed here; remove it with /logout.

Settings

The global settings file is ~/.pi/agent/ollama-cloud.json, and the project settings file is .pi/ollama-cloud.json. The global file follows Pi's agent directory if you change it.

{
  "webTools": true,
  "usageStatus": false
}

Project settings take precedence over global settings. The PI_OLLAMA_WEB_TOOLS environment variable takes precedence over both files; 0, false, no, off, or an empty value (in any letter case) turns the web tools off, and /ollama cannot turn them back on. Any other value turns them on. After editing a settings file, restart Pi or run /reload.

Changes made in /ollama to the footer usage display and the web tools apply immediately and are saved to the global settings file; other keys in the file are kept. If project settings set the same item, or PI_OLLAMA_WEB_TOOLS turns the web tools on, /ollama tells you when you change it, and that value takes precedence again at the next start. If the global settings file is not valid JSON, it is not overwritten and the change applies to this run only.

The footer usage display is off by default. When on, it refreshes every 5 minutes only while an ollama-cloud model is in use in the TUI, and the end of a conversation turn respects the same 5-minute interval. Turning the display off, switching to another provider, or exiting Pi cancels pending queries and the timer. Outside the TUI, no background queries run. With several accounts, the display shows the current session's account by name (default before assignment). The results also feed purchased-credit protection. /ollama usage and the usage report on the settings screen are explicit queries, so they call the API even while the display is off.

Web tool cache

The default file is ~/.pi/agent/cache/pi-ollama-cloud/cache.json, with 0600 permissions.

| Environment variable | Default | Meaning | | --- | --- | --- | | PI_OLLAMA_SEARCH_TTL_HOURS | 24 | How long successful results are kept | | PI_OLLAMA_SEARCH_FAIL_TTL_MINUTES | 15 | How long cacheable page failures are kept | | PI_OLLAMA_SEARCH_CACHE_PATH | cache/pi-ollama-cloud/cache.json in the agent directory | Cache file location | | PI_OLLAMA_SEARCH_MAX_ENTRIES | 500 | Maximum entries, separately for searches and pages | | PI_OLLAMA_SEARCH_SNIPPET_CHARS | 500 | Search result preview length | | PI_OLLAMA_SEARCH_CHUNK_CHARS | 3000 | Page body piece length |

Authentication errors, rate limits, network errors, server errors, and malformed responses are not stored as cached failures. When a refresh=true request fails with one of these errors, the earlier entry is removed as well, so the next request retries normally.

The cache is limited to 8 MiB in memory (measured as serialized data) and, on disk, 256 KiB per entry and 8 MiB in total. The memory figure is not a limit on process heap or RSS. When the total limit is reached, whole old entries are removed instead of truncating text. An entry larger than 256 KiB stays in memory only, so another process or a later session fetches it again.

Identical requests in flight at the same time that use the same cache and credentials share one result. Cancelling one call ends only that call; the request stops once every sharing call is cancelled. When several processes use the cache file, saves merge changes under a lock. After another process saves, entries that exist only in memory and not on disk can be dropped.

The cache stores full page text and URLs, and sessions that use the same cache path reuse them. Do not put secrets in URL query strings. If a write fails or the lock wait times out, the entry may not reach the disk.

Models and thinking levels

  • The provider name is ollama-cloud, and the endpoint is https://ollama.com/v1.
  • Models and their capabilities come from /v1/models and /api/show; only models that support tool calls are registered.
  • Pi refreshes the model list at startup, when /model opens, and on pi update --models. Without credentials, the extension uses the model list bundled in the package and makes no network request.
  • Thinking levels come from valid thinking.values in /api/show first, then from the models.dev data bundled in the package, then from the defaults off, low, medium, high, and xhigh (xhigh is sent as max). The server's default level never replaces the level you choose. For models confirmed to keep reasoning when asked to stop (the gpt-oss family and minimax-m2.7), off is hidden.
  • An unknown context length defaults to 128,000 tokens; models without a measured output limit default to 32,768 tokens.
  • A healthy catalog refreshes every 4 hours. A partial or complete failure, or an empty list, keeps the last good list and its success time and retries after 15 minutes by default (PI_OLLAMA_MODEL_FAIL_RETRY_MINUTES). A forced refresh skips the interval.
  • The model list is stored in Pi's model store. ~/.pi/agent/cache/pi-ollama-cloud/catalog-state.json holds only failure, source, and retry information. When a stored list is restored, the package's current prices, output limits, and compatibility rules are applied again.
  • The package bundles first-run model, price, and thinking data and measured output limits. This data fills in values the API does not provide, so unlike the live model list it changes only with a new release.

Price limitations

Prices are estimates based on the standard per-million-token rates on the Ollama pricing page. They do not guarantee actual charges or credit deductions, and Off-Peak discount hours are not included. Models without a known price show unknown (not free) in the development CLI's model metadata view. Pi's numeric cost fields can contain 0, but that does not mean free.

Price data is fixed in the package and changes only with a new release. Unconfirmed :cloud aliases and date tags do not inherit another ID's price or output limit. If the API does not report cached-token usage, cache discounts are not reflected in /cost either. The cacheWrite cost field is always 0.

Limits and support

This extension does not go through a local server as ollama launch pi does, and it does not provide local models. Because the provider names differ (ollama and ollama-cloud), you can use both approaches together. Do not load more than one copy of this extension, though, for example a global and a --local installation, or a Git and an npm copy.

Each model detail request has a 10-second limit. If only some detail requests fail, the whole last good catalog is kept. If a model outputs raw markup instead of structured tool calls, retry with another model and report it in an issue with keys and personal information removed from the reproduction details.

Move from earlier versions

From a Git installation to npm

npm installations need no change. For a Git installation, find the package entry in pi list, remove it, and install from npm:

pi remove git:github.com/buYoung/pi-ollama-cloud
pi install npm:@buyong/pi-ollama-cloud

For a project installation, add --local to both commands. If you used another URL form or pinned a version, remove the exact entry shown by pi list. For a copy cloned by hand into ~/.pi/agent/extensions/, save any local changes first, then move it out of the auto-discovery path. Do not load Git, local, and npm copies at the same time.

After the switch, the ollama-cloud credential, settings files, cache paths, and public tool names stay the same. pi install git:github.com/buYoung/pi-packages installs the repository root, not this app; to run a source checkout, use the commands in Load in Pi directly.

Removed commands and files

| Used in | Removed item | Use instead | | --- | --- | --- | | 0.13 and earlier | /ollama-cloud-usage, /ollama-usage-status, /ollama-webtools | /ollama usage, /ollama status, /ollama webtools | | 0.13 and earlier | Root compatibility files such as index.ts, usage.ts, and utils.ts (removed in 0.14.0) | usage.ts: src/usage/api.ts (see Use the usage API from another extension). index.ts: the package directory or src/index.ts (see Load in Pi directly). Other root files such as utils.ts were internal and have no public replacement. | | Before 0.8.0 | /ollama-cloud-refresh and the model cache ~/.pi/agent/cache/ollama-cloud-models.json | Automatic refresh and pi update --models. The old cache file is no longer read. |

Use the usage API from another extension

Other extensions, such as a custom status display, can import this package's usage functions. The runtime and bundled data all live under src/. The package ships TypeScript sources, so import with the .ts extension.

import { fetchUsage, fetchUsageStats, formatUsage, formatUsageStatusColored } from "@buyong/pi-ollama-cloud/src/usage/api.ts";
import { getCloudApiKey } from "@buyong/pi-ollama-cloud/src/auth/context.ts";
import type { UsageData } from "@buyong/pi-ollama-cloud/src/usage/api.ts";

| Function | Behavior | | --- | --- | | fetchUsage(apiKey, signal?) | Queries /api/balance. Supports both the older quota-window format and balance-based responses. | | fetchUsageStats(apiKey, range?, signal?) | Queries /api/usage request statistics. | | formatUsage(data, stats?, options?) | Builds a multi-line usage string. options.title replaces the first line. | | formatUsageSummary(data) | Builds a one-line summary, such as 5h 40% used · purchased $5.00. | | formatUsageStatusColored(theme, data) | Builds the string for the TUI status display. | | getCloudApiKey(ctx) | Checks Pi's provider credential first, then falls back to OLLAMA_API_KEY. | | isUsageResponse(data), isUsageStats(data) | Check the response shape. |

UsageData.included.period.until holds the reset boundary reported by the API. The status display idea came from pi-ollama-cloud-usage-tracker, but this extension uses an API key and /api/balance instead of browser cookies.

Development and verification

This section is for contributors working in a source checkout. Run from the monorepo root with Node.js 22.19 or later and [email protected].

pnpm install --frozen-lockfile
pnpm --filter @buyong/pi-ollama-cloud check
pnpm --filter @buyong/pi-ollama-cloud test:unit
verification_dir="$(mktemp -d)"
pnpm --filter @buyong/pi-ollama-cloud exec tsx scripts/cli/verify.ts --offline --report "$verification_dir/offline.json"
pnpm --dir apps/pi-ollama-cloud pack --pack-destination "$verification_dir/package"

check runs lint and type checks without changing files. Unit tests and --offline verification make no requests to the real service. --offline verification also packs and unpacks the real pnpm archive, prepares only the production dependencies already installed, and compares registration, tools, cancellation, reload, and request payloads between the source and the archive. The last command writes the archive to $verification_dir/package so you can inspect it. Confirm exit code 0 and that every check in the report passed. Tests, the CLI, Inquirer, and verification records are not shipped, and there is no compile or bundle step.

Interactive check CLI

Run in an interactive terminal at the monorepo root. This command is for a source checkout, not an npm installation.

pnpm --filter @buyong/pi-ollama-cloud cli

Choose from the @inquirer/prompts menu; the menu labels are in Korean:

  • 모델 목록 조회 (list models): Queries Ollama's GET https://ollama.com/v1/models and shows the model IDs and count. It never substitutes a fixed list and works without an API key.
  • 모델을 선택해 1+1 채팅 (chat 1+1 with a selected model): Pick a model from the API list; the CLI sends 1+1 once through the same model setup code as the extension and Pi's model runtime, and shows the text reply.
  • 모델 ID를 직접 입력해 1+1 채팅 (chat 1+1 with a typed model ID): Accepts a valid ID that the list does not show, such as glm-5.3-flash:cloud. The CLI checks it with /api/show, then registers and sends the ID exactly as typed; unsupported models are reported before the call. Names in the API list do not always match the accepted aliases.
  • 모델 메타데이터·출처 조회 (공개 API) (model metadata and sources): Shows capabilities and thinking values from the public API, and whether each value comes from bundled data or a default.
  • 기능별 검증 (오프라인) / 전체 기능·배포물 검증 (오프라인) (check by feature / check everything and the archive): Runs simulated checks of authentication, models, cache, renderers, cancellation, and the generation pipeline. Diagnostics run in a process separate from the menu.
  • 종료 (exit): Ends the menu. Ctrl+C also stops and exits.

Chats use real API quota. Like the existing generation and verification scripts, the CLI uses the ollama-cloud API key stored in Pi's auth.json first, then OLLAMA_API_KEY. Keys are never printed to the screen or written to files. Stored literal, $ENV, ${ENV}, and !command values are resolved with Pi's official resolver. Pi 1.3.0 runs !command synchronously for up to 10 seconds, so cancellation can be delayed that long.

A chat registers the selected model's configuration in Pi's model runtime temporarily, and just before sending it checks that the model ID in the request body matches your input. The key is passed only in runtime memory; no session file or personal setting is saved. Requests are limited to 120 seconds, and output to the smaller of the model limit and 4096 tokens. Errors are shown and the menu returns; temporary directories are cleaned up. A successful chat confirms inference through the model setup path. The extension's registration, commands, tools, and events are checked separately by the source and archive checks and by loading it in Pi as shown below.

For help, run pnpm --filter @buyong/pi-ollama-cloud cli --help. The CLI and @inquirer/prompts are development tools and are not part of the runtime package.

Load in Pi directly

Pi finds the entry point through pi.extensions: ["./src/index.ts"] in package.json; no root index.ts is needed. If you loaded it with a file path such as -e .../index.ts, change it to the package directory or src/index.ts.

To load it apart from your personal settings, run from the app directory:

cd apps/pi-ollama-cloud
node --version
pnpm exec pi --version
package_dir="$(pwd -P)"
pi_cli="$package_dir/node_modules/.bin/pi"
verification_dir="$(mktemp -d)"
mkdir -p "$verification_dir/agent" "$verification_dir/work"
(
  cd "$verification_dir/work" || exit 1
  PI_CODING_AGENT_DIR="$verification_dir/agent" PI_OFFLINE=1 \
    "$pi_cli" --no-extensions --no-skills --no-prompt-templates --no-themes \
    --no-context-files --no-mcp -e "$package_dir"
)

In Pi, run /ollama (open the settings screen, then close it with Esc), /ollama webtools off, /ollama webtools on, and /reload, and confirm that the tool state changes without errors. The two webtools commands save values to ollama-cloud.json in the isolated agent directory. This check covers startup and command behavior only; it does not show that model or API calls succeed. The isolated directory is not a security boundary that blocks the code's file or network access.

Live-service checks need an API key and use quota. CI's Smoke pi-ollama-cloud checks model replies, thinking off, and web tools with the packed extension. To check the web tools for real on your machine, run pnpm run smoke:web-tools in the app directory. It makes one web search, one page fetch, and one balance query for purchased-credit protection, checks the tool result size limit and cache reuse, and does not retry failures.

Bundled data updates, release approval, and tagging are described in the maintenance guide (Korean).

License

MIT License. The original author's attribution is kept.