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

@gainsight-hub/developer-studio-cli

v1.8.2

Published

Gainsight Developer Studio CLI - development tools for widgets, connectors, and pages for the Gainsight CC Widget Catalog

Downloads

3,169

Readme

@gainsight-hub/developer-studio-cli

Command-line tools for building Gainsight CC Widget Catalog widgets, connectors, and pages.

npm version node

Install

Requires Node.js >= 18 and a CC Widget Catalog account with developer access.

npm install -g @gainsight-hub/developer-studio-cli
gsds --version

Quick start

gsds init acme-widgets && cd acme-widgets
gsds login PAIRING-CODE-FROM-GAINSIGHT
gsds create --name revenue-overview --framework react --category Analytics
gsds preview

Then open CC — your local widgets appear in the picker while gsds preview runs.

Authentication

Get a pairing code from your Gainsight community's CLI Access page (Integrations → Developer Studio → CLI Access) and redeem it within its 1-minute TTL. The resulting session token lasts ~8 hours, is tenant-scoped, and is required by both gsds preview and gsds connector test.

gsds login <pairing_code>   # redeem a code
gsds login --status         # report session state
gsds logout                 # clear the stored session

There is no refresh — re-run gsds login when the session expires. On 401, check gsds login --status. gsds profile marks an expired session (expired), and one whose stored credential can't be read (unreadable), so you can see which tenant to log back into.

Named profiles (multiple tenants)

gsds login stores a session and makes it current, and every other command (preview, logout, connector test) uses it automatically. Logging into a second tenant with a plain gsds login keeps the first session too — each is kept as its own profile, keyed by tenant, and the one you just logged into becomes current.

gsds profile              # list stored profiles, marks the current one and any needing a re-login
gsds profile use <name>   # switch which one is current

Use --profile <name> on login, preview, logout, or connector test only when you want to name a profile explicitly (e.g. logging into the same tenant twice under different names) or target a non-current profile for a single command without switching it.

Commands

| Command | Purpose | |---|---| | gsds init <name> | Scaffold a project, or backfill missing files into an existing widget project (e.g. cloned from widgets-repository-template), migrating a legacy widget_registry.json to extensions_registry.json if that's the only registry present, and adding dedupe.exclude: ["@angular/*"] to a gsds.json that predates it entirely. <name> can be . to scaffold the current directory in place (the project name falls back to the directory's own name). --force for any other non-empty directory. Never overwrites a file it finds | | gsds create | Scaffold a widget. --name --framework --category | | gsds list | List widgets, connectors, scripts, stylesheets. --json | | gsds preview | Dev server + CC pairing. --port --widget <name>... | | gsds build | Build widgets, validate/normalize the registries. --validate to fail on drift | | gsds connector test [name] | Run a connector against your tenant | | gsds script / gsds style | Manage sitewide assets: new link ls rm | | gsds profile / gsds profile use <name> | List stored login profiles / switch the current one. --json | | gsds update | Check npm for a newer CLI version now and install it, after confirmation (supplements the automatic check below) |

Frameworks: react, vue, angular, vanilla, html.

gsds create

Interactive by default. In a non-TTY (CI, agent-driven) all three flags are required, and the command fails fast naming what's missing.

Also triggers a registry rebuild so a newly shared dependency is externalized immediately (see below) — but only rebuilds the new widget plus any widget whose externals actually changed this run, not every widget in the project. A registry-rebuild failure only fails create if it's attributable to the widget just created (names it, or names no widget at all); a failure that clearly names a different, already-known widget just warns.

gsds preview

Requires an active session. Each widget needs a dev script in its package.json to appear in the picker; the package manager is resolved from the widget's own package.json packageManager field first, falling back to lockfile detection when that's absent. Widget-service brokers the CC connection via the registered preview session — no token to paste.

gsds build

Builds each widget, then validates and normalizes extensions_registry.json and connectors_registry.json in place. Both are hand-authored sources of truth, not generated output: gsds build never derives widget entries from a widgets/* folder scan, and a widget with no entry is invisible to the platform. Your entries' field order and array order are preserved, so a build with nothing to change produces no diff. Every widget entry in extensions_registry.json must declare a non-empty title and category; the build fails otherwise and names the offending entry. Installs each widget's dependencies automatically first (concurrently) if missing — no manual npm install needed per widget; a widget whose install fails is skipped without blocking the rest.

A leftover per-widget widget.json or connectors.json (from before both registries became the sole source of truth) is never read. gsds build warns naming the widget, and the advice depends on which case you are in:

  • the widget is in extensions_registry.json — the file is redundant, safe to delete.
  • the widget is not in extensions_registry.json — that widget.json is its only remaining metadata. Migrate it into the registry; deleting it loses the widget.

A widgets/<name>/ folder with no registry entry at all is also warned about, since the platform will never serve it.

--validate compares the registries against source and exits 1 on drift — useful as a CI gate. It does not rebuild dist/, so run plain gsds build before committing. It still checks importMaps for drift when every shared dependency's version can be resolved without installing anything; when it can't, it skips that one comparison rather than false-failing.

Shared dependency deduplication

Any package in two or more widgets' package.json dependencies is shared, and gsds build externalizes it out of those widgets' bundles automatically — no author action beyond declaring the dependency. The registry then gets an importMaps entry pointing each shared package at https://esm.sh/<pkg>@<version>, so widgets on the same page load one copy from the browser's module cache instead of one apiece.

A package only one widget uses stays bundled in that widget. Externalizing it would cost a network hop and buy nothing, so nothing changes until a second widget declares it.

Mechanics worth knowing:

  • gsds build may rebuild widgets. When a widget's set of externalized packages changes, gsds build patches the const externalPackages: string[] = [...] declaration in its vite.config.ts in place (or angular.json's externalDependencies for Angular) and its bundle is rebuilt so the change takes effect. Nothing else in either file is touched.

  • A version conflict is a hard error. If two widgets sharing a dependency resolve it to different installed versions, the build fails and names every widget/version pair. There is one import map per registry, so one version has to win; align the versions (or remove the dependency from one widget) and re-run.

  • devDependencies never participate — build-time-only packages never reach the browser.

  • Angular widgets are patched through angular.json's architect.build.options.externalDependencies; every other key in that file is left as you wrote it. @angular/* packages should never be externalized — they ship unlinked partial-Ivy code, and served raw from esm.sh they skip the Angular Linker entirely, breaking at runtime with a JIT-compiler error. There is no working fix for this via import maps today (confirmed: no CDN serves a linked build, Module Federation sidesteps the problem by linking locally rather than publishing a shared linked artifact, and bundling @angular/compiler locally as a JIT fallback doesn't work either — it and the externally-served @angular/core never share a module graph). Non-Angular dependencies (rxjs, tslib, or any third-party library two Angular widgets happen to share) dedupe normally.

  • Widgets scaffolded before this feature have a vite.config.ts with no externalPackages declaration. gsds build auto-migrates it into the current shape the first time the widget actually shares a dependency with another widget — nothing to do. It only falls back to a warning (naming the widget, no file touched) when the file's shape can't be confidently patched (a custom rollupOptions, or a build block laid out differently than the standard template); copy the externals block from a freshly scaffolded widget's vite.config.ts in that case. A widget whose dependencies nobody else shares is left alone with no warning either way — there's nothing to gain from opting in.

  • HTML widgets have no package.json and never participate.

  • A package (or scope) can be excluded project-wide via dedupe.exclude in gsds.json:

    { "dedupe": { "exclude": ["@angular/*", "styled-components"] } }

    A trailing /* excludes an entire scope. gsds init seeds this list with @angular/* for the Angular reason above — dedupe has no hardcoded exclusions of its own; every project's protection lives entirely in its own gsds.json. A gsds.json that predates this feature (no dedupe key at all) gets @angular/* backfilled automatically the next time gsds init runs against that project (never touches a gsds.json that already has a dedupe key, even an empty exclude list). Add further entries for anything else that's safe to bundle per-widget but unsafe to force into a single shared copy, such as a CSS-in-JS library whose independently-built copies don't share a class-name registry, causing style collisions at runtime with no build-time error.

    dedupe.exclude is the only opt-out — there is no single flag to disable dedupe project-wide. That's by design: dedupe only ever activates for a package two or more widgets actually declare (see above), so excluding the specific packages you don't want shared has the same effect as turning the feature off, without silently reintroducing duplicate bundles the moment a genuinely shareable dependency shows up later.

Automatic updates

gsds keeps itself up to date on its own. Roughly every 8 hours, the next gsds command you run first checks the latest dist-tag on the npm registry, and if a newer version is published it runs npm install -g @gainsight-hub/developer-studio-cli@<version> without asking you to confirm. You'll see two lines when that happens:

  ℹ  Updating gsds to v1.4.0...
  ✔  Updated to v1.4.0 - restarting on the new version...

The update applies to the command you just typed, not the next one. Once the install finishes, gsds hands the invocation over to the newly installed binary — it re-runs itself from disk with your original arguments and exits with whatever that run returns. So a gsds login that updates on the way in performs the login on the new version, not the old one. This matters for more than freshness: it means the command never runs half on the old code and half against a tree that npm has just rewritten underneath it.

You should not notice the handover beyond the two lines above. Output, exit codes, and Ctrl-C all behave as usual, including for long-running commands like gsds preview.

Only routine upgrades install this way. The automatic path takes a newer version within your current major and never a prerelease. A major bump (1.x → 2.0.0), or a prerelease that has been moved onto the latest tag, is deliberately left for gsds update, so a human sees a potentially breaking version before it lands.

Between checks there is no network call at all: the check state lives in ~/.config/gsds/update-check.json, and the check is skipped outright until 8 hours have elapsed. The timestamp is recorded whatever the outcome (updated, nothing to update, or the check failed), so a registry outage costs one attempt per interval rather than one per command. That file also records the last outcome and the last version installed, which is what to look at first if a machine ends up on an unexpected version.

Parallel gsds invocations (a CI matrix, a Makefile, agent fan-out) coordinate through a lock file next to that cache, so several of them starting at once cannot all launch npm install -g against the same global prefix — one runs the check, the rest skip it.

The check can never fail your command. A network error, a missing npm, an unwritable config directory, or a failed install is swallowed (at most a one-line warning) and your command then runs exactly as it would have — on the current version, with no handover.

Opting out: GSDS_DISABLE_AUTO_UPDATE=1

Set GSDS_DISABLE_AUTO_UPDATE=1 to skip the automatic check entirely — no registry call, no install, no cache write. Use it anywhere an unattended global npm install would do damage or simply fail:

  • CI pipelines, where the CLI version should be pinned by the lockfile or install step, not changed underneath a build.
  • Sandboxed or offline runners with no registry access, or a read-only HOME.
  • Mid-build or agent-driven invocations, where a global install racing the build is a hazard.
export GSDS_DISABLE_AUTO_UPDATE=1

It is an escape hatch for those environments, not a general "never update" switch for a normal development machine — with it set you're responsible for running gsds update yourself.

gsds update

Runs the same check immediately and on demand, which is what makes it still useful alongside the automatic one: use it to pull a just-published version now instead of waiting up to 8 hours for the next scheduled check — and it is the only way to take a major-version or prerelease upgrade, which the automatic path skips on purpose.

It compares the running version against the latest dist-tag on the npm registry, then offers to run npm install -g @gainsight-hub/developer-studio-cli@<version> for you. Unlike the automatic check, this command installs nothing without an explicit y at the prompt.

Three things worth knowing:

  • It does nothing in CI or any non-interactive shell. With no TTY (or CI=true) there is no one to confirm a global install, so it prints the npm install -g command for you to run yourself and exits 0 without installing.
  • The new version applies to the next gsds invocation. This command's process has already loaded its own code and does not hand over to the new one the way the automatic check does.
  • It installs with npm regardless of how you originally installed gsds. If you used pnpm, yarn, bun, or Volta, the npm-installed copy may not be the one on your PATH — re-run the update through your actual package manager instead.

A failed check (registry unreachable, or a response that doesn't contain a usable version) exits 1 and installs nothing.

gsds connector test

gsds connector test [name] [--payload @body.json] [--query k=v] [--path-param k=v] [--verbose] [--json]

Resolution is local-first, in two steps: a name is looked up in connectors_registry.json — the root file the platform actually ingests — then against the connector persisted on your tenant. The .gsds/ capture files and --json output record source: local | remote; stderr's source: line names which of the two won.

--payload takes a file reference only (@body.json), and the file must contain valid JSON. --verbose prints the rendered upstream request. Composite connectors cannot be tested.

Runs are captured to .gsds/ (gitignored by gsds init), with sensitive headers redacted.

gsds script / gsds style

Sitewide assets injected on every rendered page, or scoped with --page. new scaffolds a local file, link registers an external CDN URL (must end in .js / .css).

Flags on new / link: --attr k=v, --page <name>, --placement head|bodyStart|bodyEnd (scripts only), --name <slug> (link only). rm --force deletes a non-empty folder.

Automation / AI use

Every interactive path has a flag equivalent and every human output has --json.

gsds create --name revenue-overview --framework react --category Analytics
gsds preview --widget revenue-overview
gsds list --json | jq -r '.widgets[] | select(.previewable) | .type' | xargs gsds preview --widget
gsds connector test revenue-feed --query limit=5 --json

gsds list --json emits an object keyed widgets / connectors / scripts / stylesheets, not a bare array. Commands exit 0 on success and 1 on any failure — treat non-zero as "failed" rather than branching on the value.

Files on disk

Committed, per project: gsds.json (project-root marker), extensions_registry.json, connectors_registry.json, AGENTS.md, README.md, .gitignore.

Gitignored: .gsds/ (connector-test capture output).

Per user: session token stored in the OS keychain (macOS Keychain, Windows Credential Store, or Linux Secret Service / keyutils via @napi-rs/keyring) under service gainsight-developer-studio-cli, one keychain account per profile (default unless --profile <name> was used). On platforms where the keychain is unavailable — no supported provider present, headless CI, or a keychain error — the CLI falls back to ~/.config/gsds/credentials.json (credentials.<profile>.json for a named profile, mode 0600) and warns once on stderr. Set GSDS_DISABLE_KEYCHAIN=1 to force the file-based fallback. Which profile is current is tracked in ~/.config/gsds/current-profile.

Outbound requests

Every request the CLI makes — login redemption, preview sessions, connector tests, and update checks — sends User-Agent: gsds-cli/<version> (node/<version>; <platform>-<arch>). This exists so the platform can see which CLI versions are actually in use and which platforms need support. It carries no user, project, or session identifiers.

Registry data model

Schemas and platform semantics live in the developer portal, not here:

  • LLM-optimized: https://developer-portal.gainsight.com/docs/llms-full.txt
  • Human-readable: https://developer-portal.gainsight.com/docs

What gsds build reads: each widget's entry in extensions_registry.json (requires title + category; exactly one of source or content), the root connectors_registry.json (connectors and/or composite_connectors, permalinks unique within the file), and each widget's package.json dependencies (to compute shared dependencies). Script and stylesheet entries live only in extensions_registry.json and are managed via gsds script / gsds style.

What gsds build writes into a widget: the externalPackages array in vite.config.ts, or, for Angular widgets, architect.build.options.externalDependencies in angular.json. Both are patched in place — commit the result, don't hand-edit either value.

Troubleshooting

no gsds.json found — run from the project root, or gsds init <name> (or gsds init . for the current directory) first.

session is invalid or expired — ~8 h TTL. Re-run gsds login. Affects preview and connector test.

Pairing code expired — 1-minute TTL. Mint a fresh one from CLI Access and redeem immediately.

Missing required field: category — add a category string to the widget's entry in extensions_registry.json that the error names.

Script "x" points at missing file — a registry entry references a deleted file, and gsds build won't regenerate while inconsistent. Restore the file, or run gsds script rm <name> / gsds style rm <name>. gsds create warns on the same drift without failing so you can still scaffold new widgets while cleaning up the registry.

--payload rejected — file reference only: --payload @body.json, containing valid JSON.

version mismatch across widgets — two widgets share a dependency but resolve different installed versions. The error names each widget and version; align them and re-run gsds build.

excluded from shared-dependency deduplication — that widget's vite.config.ts/angular.json couldn't be auto-migrated (only happens once the widget actually shares a dependency with another and the file's shape can't be confidently patched). Copy the externals block from a newly scaffolded widget's vite.config.ts.

Port in use — gsds preview --port 5200.

Widget missing from the picker — its package.json needs a dev script.

Versioning

Semver. Breaking changes to commands, flags, --json shapes, or the on-disk contract require a major bump. Exit codes are currently only 0 / 1; finer-grained codes may land in a minor.