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

tagwire

v0.1.0

Published

A small semantic tag protocol and TypeScript codec for Node.js and browsers.

Readme

tagwire

A small semantic tag protocol and TypeScript codec for Node.js and browsers. Convert one structured value into one tag, and back, with no runtime dependencies.

import { parse, serialize } from "tagwire";

const tag = {
  name: "skill",
  attributes: { path: "~/.skills/media-generation" },
  content: "媒体生成技能",
};

const text = serialize(tag);
// <skill path="~/.skills/media-generation">媒体生成技能</skill>

parse(text); // Same name, attributes, and content as tag.

The tag name expresses meaning, attributes carry metadata, and the body carries readable text. Applications define their own vocabulary:

<camera-motion>镜头上摇</camera-motion>
<duration unit="s">6</duration>
<skill path="~/.skills/media-generation">媒体生成技能</skill>
<node id="project-a/node-x">节点 x</node>

These are four separate values, not one valid tagwire input. Ordinary text, multiple tags, and nested tags are outside the format:

parse("Please use <skill>Media generation</skill>."); // Throws.
parse("<skill>A</skill><skill>B</skill>"); // Throws.
parse("<skill><name>A</name></skill>"); // Throws.

Install and use

npm install tagwire

The package publishes ESM JavaScript and TypeScript declarations. Node.js 18 or later and browser projects with an ESM-aware bundler can use the imports above. The built module also works directly in a browser when its dist directory is served by your application:

<script type="module">
  import { parse } from "/vendor/tagwire/dist/index.js";
  console.log(parse('<duration unit="s">6</duration>'));
</script>

The codec uses no Node.js, DOM, filesystem, or network APIs. It does not render capsules, scan messages, load skills, or resolve references and paths. For example, ~ remains a literal character.

Protocol

This README is the protocol specification. tagwire uses a deliberately restricted XML-like syntax; it is not a general XML parser or an existing industry standard.

Structured representation

interface Tag {
  readonly name: string;
  readonly attributes: Readonly<Record<string, string>>;
  readonly content: string;
}

All three fields are required. Use {} for no attributes and "" for empty content. Attribute values and content are literal, unescaped strings. The serializer performs escaping; callers must not pre-escape their values.

Attributes are record objects, including objects with a null prototype; only their own enumerable string-keyed properties are metadata. Arrays and built-in containers such as Map, Set, and Date are rejected.

The protocol does not reserve business tag names or define an allowlist. Applications own each tag's meaning, permitted attributes, required fields, value validation, reference resolution, and rendering. Recognizing valid tagwire syntax does not validate those application rules or authorize an action.

Accepted text

| Rule | Contract | | --- | --- | | Document | Exactly one complete tag. Leading and trailing XML whitespace are accepted and excluded from the result. | | Names | Tag and attribute names match [a-z][a-z0-9]*(?:-[a-z0-9]+)*: lowercase ASCII, optionally separated by single hyphens. Names are case-sensitive. | | Attributes | String values enclosed in either single or double quotes. Attribute names must be unique. XML whitespace separates attributes and may surround =. | | Content | Plain text, including empty or multiline text. No child tags. | | Empty tag | <skill/> and <skill></skill> both produce empty content. | | Whitespace | XML whitespace is space, tab, LF, or CR. Literal whitespace inside attribute values and content is preserved; there is no XML newline or attribute-value normalization. | | Escaping | The five predefined entities &amp;, &lt;, &gt;, &quot;, and &apos; are supported, as are decimal &#65; and hexadecimal &#x41; character references. Hexadecimal uses lowercase x; hex digits may use either case. Entities decode exactly once. | | Characters | Literal and decoded characters must satisfy XML 1.0's character range: U+0009, U+000A, U+000D, U+0020–U+D7FF, U+E000–U+FFFD, or U+10000–U+10FFFF. Unpaired UTF-16 surrogates are invalid. | | Rejected syntax | Unknown or malformed entities, raw &, raw < inside values or content, raw ]]> in content, duplicate attributes, and mismatched closing names. | | Unsupported constructs | XML declarations, processing instructions, DTDs, comments, CDATA, namespace-qualified names such as ns:tag, and nested elements. No namespace resolution is performed; xmlns is ordinary metadata. |

Escape markup when it is literal content:

<note>Use &lt;skill&gt; as an example, not as a child element.</note>

Its parsed content is Use <skill> as an example, not as a child element. Markdown and backticks have no special meaning in this protocol.

Canonical serialization and round trips

serialize validates its input and emits one canonical representation:

  • Attributes use double quotes and are sorted by ASCII name order.
  • Every tag has an explicit closing tag, including empty content.
  • Content escapes &, <, and >.
  • Attribute values additionally escape ", tab, and LF. CR is emitted as &#13; in both attribute values and content.

parse(serialize(tag)) preserves the tag's name, attribute key/value pairs, and content. Attribute insertion order is not meaningful. serialize(parse(text)) may normalize quoting, attribute order, entity spelling, outer whitespace, and empty-tag syntax; byte-for-byte source preservation is not promised.

For example, &amp;lt; decodes to the literal string &lt;, not to <. Serializing that string emits &amp;lt; again.

API and errors

import {
  parse,
  serialize,
  TagwireError,
  type Tag,
  type TagwireErrorCode,
} from "tagwire";

// parse(text: string): Tag
// serialize(tag: Tag): string

Invalid inputs throw TagwireError, an Error subclass with a stable code and an optional offset. Parse errors in text include a zero-based UTF-16 offset into the original input. Input-type errors and serialization errors have no offset. Error messages are descriptive, not a machine-readable contract.

| Code | Meaning | | --- | --- | | INVALID_INPUT | Wrong input type or structured shape. | | INVALID_NAME | Tag or attribute name violates the name grammar. | | INVALID_SYNTAX | Text violates the tag syntax, including extra content or nested elements. | | INVALID_ENTITY | Unsupported or malformed entity/reference. | | INVALID_CHARACTER | A literal or decoded character falls outside the allowed range. | | DUPLICATE_ATTRIBUTE | The same attribute occurs more than once. |

try {
  const tag = parse(receivedText);
  // Apply your application's tag/attribute rules before using this value.
} catch (error) {
  if (!(error instanceof TagwireError)) throw error;
  console.error(error.code, error.offset, error.message);
}

Unknown business names are valid syntax: parse("<ticket>Support request</ticket>") succeeds. There is no recovery into ordinary text or partially parsed output. The caller decides how to present or handle invalid inputs.

AI integration

For AI assistants working in a consuming project, explicitly link this README from that project's AGENTS.md or equivalent instructions. Installing an npm package does not guarantee automatic discovery of its documentation. Keep application-specific vocabulary and permissions in the consuming project's own instructions.

For a model reading serialized tags, provide the meanings and allowed uses of the tags it may encounter. Meaningful names and readable content help interpretation, but a tag alone neither loads a referenced resource nor executes an instruction.

Develop and publish

pnpm install
pnpm check

pnpm check runs lint, type checking, build, and tests. Individual scripts are pnpm build, pnpm test, pnpm typecheck, and pnpm format. The tests exercise the protocol boundaries and round-trip guarantees.

The package is licensed under MIT. Publishing is manual. Confirm the package version and metadata, then create and inspect the actual release archive:

npm pack
tar -tzf tagwire-*.tgz

prepack runs the checks before creating the archive. Install the resulting tarball into a consuming project to verify its public ESM entry point and declarations.

Log in to the public npm registry and confirm the publishing account:

npm login --registry=https://registry.npmjs.org/
npm whoami --registry=https://registry.npmjs.org/

Publish the inspected archive with npm publish ./tagwire-<version>.tgz --access public --registry=https://registry.npmjs.org/, replacing <version> with the archive's version. Follow npm's authentication prompts. There is no GitHub publishing workflow.