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

@neocompose/cli

v0.31.1

Published

Neo Compose native project-source CLI with bidirectional sync.

Readme

neo — Neo Compose project-source CLI

neo synchronizes a Git-friendly Neo Compose project working copy with the web editor. Working-copy format 4 is Neo-native: .neo defines the project model and authored values, .neoflow defines dialogue flows, and managed image and audio files stay as ordinary binaries.

The current agent authoring guide is in cli/skills/neocompose-cli/SKILL.md. Feature specs in neo-compose-specs/new-features document design intent and edge cases; the released compiler, contract, and tests are the executable source of truth.

Source architecture

The CLI source separates authoring, document projection, and synchronization:

project-source/   parse, lower, and emit .neo/.neoflow plus managed binaries
project-manifest/ semantic bridge between authored source and project documents
project-sync/     diff, merge, conflict, migration, and CAS state handling

The flow is project source -> ProjectSchemaManifest -> project documents -> sync. ProjectSchemaManifest carries an independently versioned serialized contract so source syntax and server documents can evolve without coupling their version numbers. The current workspace format is still format 4; the manifest's numeric contract version is not a second working-copy format. Dependencies point downward: project-source may use both lower layers, while project-sync never imports source-authoring or manifest code.

Run

npm i -g @neocompose/cli       # published package; installs `neo` on PATH
node cli/bin/neo.mjs <command> # repository development

Set up a working copy with explicit IDs in automation:

neo login --profile editor
neo init --project <id> --version <id> --dir neo

Interactive terminals may select the project, version, and directory. Neo does not install, discover, or require .NET. neo doctor validates the format-4 browser-safe compiler, VS Code analysis contract, tracked source, and managed-file capabilities.

Format-4 working copy

neo/
  neo.json                         # formatVersion: 4
  Project.neo                      # project defaults/settings
  Root.neo                         # protected root shape; editable root values
  Classes/
  Interfaces/
  Enums/
  Relations.neo                    # generic project relations, when present
  Templates/
  Localization.neo
  LocalizationStatuses/
  Files/
    Images.neo                     # ImageRegistry Images
    AudioClips.neo                 # AudioClipRegistry AudioClips
    Images/                        # tracked image bytes
    AudioClips/                    # tracked audio bytes
  DialogueGroups/
  Dialogues/*.neoflow
  Migrations/*.neo
  **/*.spec.neo                   # colocated local-only NeoScript tests
  .neo/
    state.json                     # private merge/CAS bases; never hand-edit
    conflicts/
    test-build/                    # local test compilation metadata

Production .neo, .neoflow, and supported managed binaries are tracked. Colocated .spec.neo files are test-only: they never enter status, source identity, pull/reset, or push. .neo/ is ignored private state. There is no schema .csproj, C# authoring tree, Roslyn compiler, generated authoring DLL, per-function sidecar tree, or checked-in dialogue JSON.

Only formatVersion: 4 is accepted. Format 4 is a clean break; it does not read or convert a format-3 C# checkout. Push intended changes with the older CLI first, preserve any local source you need, then reconstruct from the authoritative server:

neo init --project <id> --version <id> --dir neo

For an existing format-4 workspace, neo pull --reset regenerates canonical source and managed binaries. Normal neo pull performs a stable-ID-aware three-way merge, preserves source placement when possible, and uses a durable transaction cursor to fetch only records changed since the previous sync. Undo/reset metadata invalidates that cursor and safely falls back to a full snapshot.

In Unity projects, neo.json may use unityConfigPath instead of storing projectId and versionId. The referenced NeoComposeConfig.asset then owns those IDs and receives branch/version switches.

Native Neo authoring

Classes, interfaces, and enums are top-level declarations. Schema functions, getters, and setters live inline and compile as NeoScript; they do not have separate source files.

@id("enum-item-rarity-id")
enum ItemRarity {
  @id("rarity-common-id")
  Common = "Common text",
}

@id("class-inventory-item-id")
@storage(allowed: .Immutable)
abstract class InventoryItem<
  @id("inventory-context-generic-id")
  TContext extends SomeClass
> {
  @id("item-name-id")
  @settings(localizable: true, searchKey: true)
  virtual string Name = "";

  @id("item-rarity-id")
  abstract ItemRarity Rarity;

  @id("item-stack-size-id")
  @settings(min: 1, max: 999)
  int StackSize = 1;

  @id("item-display-name-id")
  string DisplayName {
    get {
      return $"{Name} ({Rarity})";
    }
  }

  @id("item-use-id")
  abstract void Use(TContext context);
}

The source identifier is the persisted name. @settings and @storage are typed by their declaration context; invalid fields and enum cases are diagnostics. Swift-like .EnumCase syntax works whenever the expected enum type is known. Focused annotations such as @id, @locked, @hidden, and @relations carry cross-cutting behavior. Never author @system; it is reserved for platform-owned system records. @hidden marks a class the member selector and classes tree leave out; absent means visible.

Lists take repeatable @index and @column annotations naming a field on the entry class. An index requires a non-localizable string or a single-select enum; a column may also target the reserved __other__ key, which carries the default layout for every field a subclass adds.

@id("outpost-list-id")
@settings(kind: .Unordered)
@index(member: Slug, unique: true)
@column(member: Slug, width: 158, frozen: true)
@column(member: Notes, hidden: true)
public List<Outpost> Outposts = [];

Stable IDs preserve identity across rename, reorder, and file moves. Omit an @id only to create something. A successful real push assigns the ID and rewrites source; dry-run, validation failure, upload failure, and CAS failure leave source byte-for-byte unchanged. Collection item identity uses the same native annotation:

Tags = [
  @id("story-tag-item-id")
  "story",
];

Root values and class-owned values

Root.neo contains the compiler-owned Root root = new() { ... } envelope. Its member names, types, IDs, lock annotations, and storage settings are protected, while the root.Assets, root.Save, and root.Session value initializers are editable.

Reusable values are stored static members on ordinary project classes. There is no ValueRegistry or generated Values namespace.

@id("assets-class-id")
@storage(allowed: .Immutable)
class Assets {
  @id("capitol-member-id")
  static Outpost Capitol = new {
    Name = "Capitol",
    Image = Images.Capitol.Slice(0),
  };

  @id("home-getter-id")
  static Outpost Home {
    get {
      return Assets.Capitol;
    }
  }
}

The member ID anchors the stored binding; do not add a second ID to the static initializer. Nested class and collection rows remain independently identified.

Defaults materialize once when a value is created. Existing instances do not stay linked to later class-default changes. Logical lookup/dialogue references use the native typed intrinsic:

Reference(Assets.Capitol)
Reference(root.Assets.Cosmetics.Pants)
Reference<PantsAsset>(key: "pants.long")
Reference<Dialogue>(id: "dialogue-id")

Project.neo, typed texture/audio templates, localization configuration and statuses, priority groups/options, dialogue groups, and direct relations use the same top-level typed declaration grammar. The compiler derives structural row identities, storage placement, generic stamps, localized-text links, and other persistence details that are not authorable source.

Files

Images and audio are declared in typed registries and referenced directly:

ImageRegistry Images = new() {
  @id("sword-image-file-id")
  @settings(template: PixelArt)
  NeoImage Sword = new("Files/Images/Sword.png");
}

AudioClipRegistry AudioClips = new() {
  @id("sword-hit-audio-file-id")
  @settings(template: SoundEffect)
  NeoAudioClip SwordHit = new("Files/AudioClips/SwordHit.wav");
}

Use Images.Sword.Slice(0) for a sprite and AudioClips.SwordHit for an audio member. Dropping a supported binary into Files/Images/ or Files/AudioClips/ creates a provisional symbol; status/diff report it, and a successful push writes the explicit declaration and assigned ID. neo files add can create that pending declaration before push.

Pull and push compare server-verified SHA-256 digests. Divergent local/remote bytes keep the local file and place the verified remote side under .neo/conflicts/files/<file-id>/ until explicitly resolved.

NeoFlow dialogue authoring

Each .neoflow file contains exactly one sealed, non-generic class directly derived from the system Dialogue type and exactly one required trigger override. Dialogue functions and lexical value bindings are ordinary typed Neo code. Main-locale prose is inline; other locales and workflow metadata remain in localized-text records.

Graph constructors use NeoFlow-only trailing bodies. => Node and a final return Node; are equivalent destinations; there is no authorable to: argument. Terminal text/actions nodes may omit a destination, while triggers, options, and outcomes require one.

@id("capitol-dialogue-id")
@settings(name: "Capitol: cold boot", saveOptionChoices: true)
sealed class CapitolColdBoot : Dialogue {
  @primary Outpost capitol = Assets.Capitol;

  @id("can-start-function-id")
  bool CanStart() {
    return capitol.Name == "Capitol";
  }

  @id("capitol-trigger-id")
  override Trigger Trigger = new(
    group: CapitolDialogues.High,
    when: [
      @id("can-start-condition-use-id")
      CanStart,
    ]
  ) => Welcome;

  @id("welcome-node-id")
  Text Welcome = new(name: "Welcome!") {
    """
    Welcome to {capitol.Name}.
    """

    @id("continue-option-id")
    Option Continue = new() {
      """
      Tell me more.
      """
      return Finish;
    }
  }

  @id("finish-node-id")
  Text Finish = new() {
    """
    Until next time.
    """
  }
}

Persisted condition uses, action invocations, mutations, pauses, options, and outcomes each have their own stable @id. References made through lexical bindings and transitive function calls derive the same linked values as the web editor. Run neo dialogue dryrun <ref> after dialogue edits to traverse option paths against a fresh save and catch runtime storage-ownership errors.

Synchronization workflow

neo pull
# edit .neo, .neoflow, and managed binaries
neo status
neo diff
neo dialogue dryrun <ref>
neo push --dry-run
neo push

A pull followed immediately by status or push dry-run must be a semantic no-op. If it reports authoring changes, stop and report a round-trip bug rather than pushing.

Pull merges source concepts and collections by stable identity, never by index. Conflicts are explicit and block compilation. Edit source to the desired result, or use neo resolve --mine|--theirs as a whole-side convenience. There is no force-CAS bypass. Accept a required compatibility bump only after reviewing it with neo push --accept-bump.

One push recompiles changed NeoScript and schema-affected callers at the trusted server boundary and atomically commits semantic record changes, localized main-locale edits, and staged files under CAS. Normal push sends the semantic diff plus a deterministic source hash; neo push --dry-run performs the complete source-to-record rehearsal. Client AST, compiled IR, placement stamps, and derived links are not trusted.

Create classes, stored values, file declarations, and dialogues by editing their tracked .neo or .neoflow source directly. Add managed binaries under Files/Images/ or Files/AudioClips/; neo push discovers and stages them with the source transaction. The CLI intentionally has no parallel scaffold or low-level content-write commands.

NeoScript unit tests

Place *.spec.neo files anywhere beside project source. They see the complete locally prepared production graph—including unpushed declarations, values, and function bodies—plus a test-only typed prelude. Production source never sees test globals.

describe("Player.TakeDamage", () => {
  test("rejects negative damage", () => {
    var player = new Player();
    expect(() => { player.TakeDamage(-1); }).toThrow("negative");
  });
});

Run all tests, narrow by file/directory, or filter the full test name with a JavaScript regular expression:

neo test
neo test Classes/Player.spec.neo
neo test -t "TakeDamage"
neo test --reporter=json --outputFile=.neo/test-results.json

The JSON output file is written atomically. Human and JSON reports break timing into total, test-code, and framework-startup durations; human output also gives shared startup its own row instead of charging it to the first test.

Test compilation metadata and the experimental verified prepared-project cache live in .neo/test-build/; neither they nor test files are server records. Production source, managed files, configuration, workspace state, CLI version, and compiler revision invalidate the prepared project, while editing only a .spec.neo file reuses it. Missing, corrupt, or checksum-mismatched cache entries are rebuilt. A workspace must have been initialized or pulled once so .neo/state.json contains the offline project facts, but running tests performs no login or network request.

Native functions cannot execute in the local evaluator and therefore fail closed unless mocked. Use mock.native(Member), mock.function(Member), or spyOn(Member), then configure returns, returnsOnce, throws, throwsOnce, doesNothing, or implementation. Mocks and call history are isolated per test.

To gate pushes, add either or both commands to neo.json:

{
  "prePushHook": "neo test",
  "prePushDryRunHook": "neo test --reporter=json --outputFile=.neo/test-results.json"
}

The real-push hook is intentionally not reused for dry runs: arbitrary hook commands may have side effects. Each applicable hook runs only after local candidate validation and before authentication/network activity; a non-zero exit aborts. neo push --no-verify is an explicit local bypass, not a server authorization boundary.

Commands

| Command | Purpose | | ----------------------------------------------------- | ------------------------------------------------------------------------- | | --version | Print the installed CLI package version. | | login | Authenticate the selected profile. | | whoami | Inspect the selected profile. | | logout [--api <url>] | Delete the stored credential for one API origin and revoke its session. | | init --project <id> [--version <id>] | Create a format-4 working copy and perform its first reset pull. | | doctor | Validate format/compiler/editor/source/file contracts; no .NET discovery. | | pull [--force\|--reset] [--regenerate-source-names] | Merge remote changes or regenerate canonical source, names, and binaries. | | status [--json] | Compile source and summarize semantic records and upload intent. | | diff [--json] | Show semantic records, spans, digests, and upload intent. | | push [--dry-run] [--accept-bump] [--no-verify] | Validate or commit one atomic compare-and-swap transaction. | | test [file-or-dir ...] [-t pattern] | Compile the unpushed local candidate and run *.spec.neo tests. | | dev [--push] | Watch remote/local changes; pushing remains opt-in. | | resolve --mine\|--theirs | Resolve all current source/binary conflicts with one side. | | script check\|compile\|eval | Compile or preview NeoScript with project context; never commit writes. | | dialogue dryrun <ref> | Simulate a locally authored dialogue candidate. | | loc | Inspect or edit localization data not represented in main-locale source. | | migrate new\|list\|check\|run\|prune | Author and manage tracked NeoScript migrations. | | branch | Manage copy-on-write branches. | | merge | Merge branch changes. | | release | Cut, publish, archive, or restore releases. | | channel | Manage release channels. | | history | Inspect and deliberately rewrite retained history. | | export unity | Export the runtime Unity projection. |

With the deliberate exception of neo loc, content authoring goes through tracked native source and neo push. Localization commands can mutate authoritative localization records that .neo does not fully represent; run neo pull immediately afterward before further source edits or push.

Tokens use the OS credential store when available and a protected local file only as fallback. CI can use NEO_COMPOSE_TOKEN or --token-stdin. The editor profile cannot publish releases; server authorization remains the security boundary.

Regenerating dialogue source names

Normal pull preserves source filenames and symbols by stable record ID. Force and reset pulls always derive readable dialogue names from authoritative records. To deliberately refresh those names during a normal merge, run:

neo pull --regenerate-source-names
neo status
neo push --dry-run

Custom linked values prefer a non-empty string member named Name, using PascalCase at dialogue scope and camelCase inside nodes.

The reset leaves the regenerated names as local changes. IDs are unchanged, and nothing is written to the server until neo push succeeds.

Packaging

node cli/build.mjs bundles the Node orchestrator and browser-safe Neo language/compiler/editor assets. The npm package contains no C# SDK, Roslyn application, DLL, or .NET runtime metadata. Package checks reject those artifacts and verify that the shared compiler assets and skill are present.

Useful checks while developing:

npm run typecheck
npx vitest run cli
npm --prefix cli run test:package

Run the repository's full verification suite before release, package and exercise the VS Code extension, verify the Hello World and live neowyn format-4 no-op workflows, and run npm run doctor last.