flagstaff

Changelog

Every release of flagstaff, newest first, from its CHANGELOG.md — what changed and the pull request it came from.

0.4.4

Patch Changes

  • #627 4a629a9 Thanks @ofri-peretz! - Lint with every published Interlace ESLint plugin, and fix what the upgrade surfaced.

    • caique: the inquirer theme merge skips __proto__, constructor and prototype keys, so a theme object cannot swap the merged object's prototype.
    • burgee: last-element reads use .at(-1).
    • burgee, closeout, flagstaff, roundel: helpers that capture nothing from their enclosing function move to module scope.
    • seniority: suppression comments name the no-dynamic-require rule that now reports the config loader's dynamic require.

    No public API or output changes.

  • Updated dependencies [4a629a9]:

    • closeout@0.5.4
    • roundel@0.5.4

0.4.3

Patch Changes

  • #604 0e7b1e8 Thanks @ofri-peretz! - Each README links to its migration guides under the docs link: "Migrating from: chalk", "ora · log-update · boxen · cli-table3", and so on. That puts a path from the npm page to the guide for the library you are replacing. No code changes.
  • Updated dependencies [0e7b1e8]:
    • roundel@0.5.3
    • linegauge@0.5.4
    • closeout@0.5.3
    • paratext@0.7.2

0.4.2

Patch Changes

  • #474 1955419 Thanks @ofri-peretz! - burgee plugins can hook two more stages. parse runs before the command is resolved: it receives argv and may return a replacement, which is how an alias plugin maps d to deploy. shutdown runs once as the program exits, whether the command succeeded or failed. The family schema.json shipped in every package now describes both stages.

  • #522 f4be6a8 Thanks @ofri-peretz! - require('bellpull/cross-spawn'), require('flagstaff/cli-table3'), require('burgee/yargs'), require('seniority/dotenv') and require('seniority/rc') now return what the incumbent's require() does — the function, the class, the factory, the object — instead of an ES module namespace. Each exports its default as 'module.exports', which is what Node hands a CommonJS caller, and which yargs' own entry already does. const spawn = require('…'); spawn(…) threw before.

  • #519 77ff1cb Thanks @ofri-peretz! - The drop-ins now export their incumbents' type names, so a TypeScript program migrates by its import alone: roundel/chalk gains chalk's Color, ForegroundColor, BackgroundColor, Modifiers and Options; flagstaff/ora gains Spinner, PrefixTextGenerator and SuffixTextGenerator; flagstaff/boxen gains Options, CustomBorderStyle and Boxes; flagstaff/log-update, linegauge, linegauge/wrap and closeout/exit-hook gain Options; burgee/yargs/parser gains Arguments, Options and Configuration. Types only — no runtime bytes.

  • Updated dependencies [866b972, 1955419, 1b002e0, 77ff1cb, dc1b1a7, 0592441]:

    • paratext@0.7.0
    • closeout@0.5.2
    • linegauge@0.5.2
    • roundel@0.5.2

0.4.1

Patch Changes

  • #508 1aae1e2 Thanks @ofri-peretz! - <package> --help and --version answer instead of crashing. The bin took its first argument as the plugin file to import, so roundel --help failed with Cannot find module '…/--help' and exit 1. -h/--help now print usage and exit 0, -V/--version print the version and exit 0, and any other flag where the plugin file belongs is a usage error, exit 2.
  • Updated dependencies [1aae1e2]:
    • closeout@0.5.1
    • linegauge@0.5.1
    • paratext@0.6.1
    • roundel@0.5.1

0.4.0

Minor Changes

  • #507 b8e97dc Thanks @ofri-peretz! - Runs on Node 20 and 22, not just 24+: engines.node is now ^20.19.0 || >=22.13.0. Those are the first releases where require(esm) loads without a warning, so the CommonJS require() path keeps working. Every package's test suite runs on exactly 20.19.0 and 22.13.0, on Linux, macOS and Windows. caique's prompts no longer call Promise.withResolvers, which Node 20 doesn't have.

Patch Changes

  • Updated dependencies [9800b43, b8e97dc]:
    • closeout@0.5.0
    • linegauge@0.5.0
    • paratext@0.6.0
    • roundel@0.5.0

0.3.7

Patch Changes

  • #494 f7f6d4b Thanks @ofri-peretz! - Each package's homepage and README docs link now point at its own documentation site, https://<package>.interlace.tools, instead of a page on burgee's site. The old burgee.interlace.tools/docs/packages/<package> URLs answer with a 301 to the new host, so nothing already linked breaks. closeout's README override example also resolves to the current release again (npm:closeout@^0.4; the 0.4.0 release left it at ^0.3).
  • Updated dependencies [f7f6d4b]:
    • roundel@0.4.3
    • linegauge@0.4.4
    • closeout@0.4.1
    • paratext@0.5.4

0.3.6

Patch Changes

  • Updated dependencies [69563d1, 2dc573f]:
    • closeout@0.4.0
    • paratext@0.5.3
    • linegauge@0.4.3

0.3.5

Patch Changes

  • #454 b4584e7 Thanks @ofri-peretz! - Every package's npm homepage now points at its page on the docs site, https://burgee.interlace.tools/docs/packages/<name>, and each README links it under the header. The keywords add what people and models search for: burgee gains cli-framework, argument-parser, subcommands, json-schema, mcp-server, model-context-protocol, ai-agent, llm, shell-completion, typescript, zero-dependency, commander-alternative and yargs-alternative; the other eight gain agent, ai-agent, non-tty, json and zero-dependency where the package does that — zero-dependency only on the six that install nothing at all.

    burgee's README gains a short FAQ (commander alternative, agent use, MCP, dependencies) and states the compatibility counts the oracle holds — 1,360 / 1,360 of commander's tests and 804 / 804 of yargs' — where it had said 1,215 and 1,185. caique's README no longer calls a released package pre-release.

  • #442 bdaf364 Thanks @ofri-peretz! - Every package now lists plugin, plugins and extensible in its npm keywords, because every package takes plugins through one shared contract.

    A plugin is a plain object, validated against the schema.json that ships in every package, and checked with the package's own check command. Each package reads its own key and ignores the rest, so one object can extend any subset of the family. The plugins page has a nine-layer example that every package's check accepts in CI.

  • #435 7888524 Thanks @ofri-peretz! - schema.json now describes every plugin host in the family.

    The one schema each package ships as its plugin contract used to cover only four hosts: roundel's tokens, flagstaff's glyphs, spinners, borders and components, paratext's capabilities, and linegauge's widths. Five hosts validated their keys in their own code, but the file an author (or a model) writes against said nothing about them. It now describes all of them:

    • bellpull resolvers, including the absolute-path rule on paths
    • caique widgets
    • closeout handlers, including the phases a plugin may use
    • seniority sources, including the rank bounds
    • burgee commands, hooks and enforce

    Where the schema can express a rule, it gives the same verdict as the host's own validator, and a test holds the two together. Function-valued fields (static, run, read, handler) are described and required, but not typed, because JSON Schema can't say "function".

    flagstaff now validates a plugin against only its own keys, not the whole family schema. It no longer refuses a plugin over another host's key, which lets one plugin object contribute to several hosts. Its entry points are also 4.7–5.9 KB lighter for it.

  • #445 dac303e Thanks @ofri-peretz! - Every package now declares sideEffects truthfully, so bundlers can drop what you don't import.

    Six packages declared nothing, so no bundler could drop any of their modules. A named import from the root now bundles to the same bytes as the same import from its subpath:

    importbeforeafter
    import { explain } from 'seniority'2,939 B1,067 B
    import { decide } from 'caique'1,235 B734 B
    import { strip } from 'linegauge'1,102 B940 B
    import { once } from 'closeout'353 B235 B

    flagstaff and roundel used to declare false, but each ships a check command whose file runs when loaded. Each now lists that file, which is the true statement. paratext also lists the two modules that register its built-in capabilities when they load.

  • #457 44edb2f Thanks @ofri-peretz! - flagstaff's npm description no longer says "Zero dependencies" (it takes four, all in-family); caique's README badge names both of its dependencies.

  • Updated dependencies [b4584e7, bdaf364, 7888524, dac303e]:

    • closeout@0.3.1
    • linegauge@0.4.1
    • paratext@0.5.1
    • roundel@0.4.1

0.3.4

Patch Changes

  • #430 4d1b2b3 Thanks @ofri-peretz! - check now reports every refusal with its code and its fix, wherever it was raised.

    Some plugin files register themselves on import: they call register() at the top of the module and export the result. Until now, when such a file was refused, the error was thrown inside check's import(), before the only try that turns a PluginError into E_PLUGIN_SCHEMA: … plus a fix: line. The author got the bare message on stderr, with no code and no fix. Now the whole of check runs inside that one handler, so every refusal comes out the same way on every host.

  • #421 db3c59e Thanks @ofri-peretz! - linegauge hosts plugins — widths, and it is the ninth of nine.

    scripts/extension-surface-lock.test.ts has carried linegauge: { plugin: false } since it was written, and the row was empty honestly: a width function is not obviously extensible, and an extension point invented to fill a table is worse than a gap that says so.

    What makes widths real is that the package already admits the problem. width.ts says ambiguous-width characters are "counted narrow, which is what a terminal does unless it has been told it is rendering an East Asian locale" — and that covers only the ambiguity Unicode sanctions. A Nerd Font putting a two-column icon in the Private Use Area, a code point added by a Unicode release newer than the table compiled into this build, a font drawing U+2500 wide: each is a real, local disagreement with the built-in answer, and until now a user had no way to settle it short of patching the package.

    export default {
      name: "nerd-font",
      widths: {
        icons: {
          ranges: [[0xe000, 0xf8ff]],
          columns: 2,
          why: "Nerd Font patches two-column icons into the PUA; measured in WezTerm",
        },
      },
    };

    Three fields of plain data, so a plugin can arrive as JSON, be diffed, be generated and be printed without running its author's code (R7). why is required, which no other $def in the family does: a width table with no provenance cannot be audited when it turns out to be wrong, and wrong is the normal outcome for ambiguous width.

    A later registration wins over an earlier one and over the built-in tables, which is the point — the built-in answer is right for most terminals and the user is the authority on theirs. An override applies before the zero-width and emoji rules, or it would be decorative. A program with no plugin pays one length === 0 per cluster, and the ASCII fast path never reaches it.

    The family schema gains widthRange, widthOverride and widths, and because it is one byte-identical file across every host, every host that validates against it grows by about 1.3 KB — five flagstaff budgets and two paratext ones moved for a definition only linegauge reads. That trade is the design's and is recorded as D-108 rather than absorbed.

  • Updated dependencies [4d1b2b3, db3c59e, db3c59e]:

    • closeout@0.3.0
    • linegauge@0.4.0
    • paratext@0.5.0
    • roundel@0.4.0

0.3.3

Patch Changes

  • #404 c70ff9c Thanks @ofri-peretz! - flagstaff/cli-table3's Cell carries ColSpanCell and RowSpanCell, the way cli-table3's own src/cell.js publishes them — module.exports = Cell; module.exports.ColSpanCell = …; module.exports.RowSpanCell = …. A caller who reaches Cell.RowSpanCell on the incumbent now reaches it here. Both names were already named exports of the module; this is a second spelling of the incumbent's shape, and nothing new is published.

    With it, cli-table3's internal suite reads 103 / 104 against a control of 103 / 104 — the target matches the reference exactly, up from 90.

0.3.2

Patch Changes

  • #398 ee35904 Thanks @ofri-peretz! - A __proto__ key in a caller's corpus stays a style.

    fromCliSpinners and fromCliBoxes built their maps by assigning map[key] = value over the caller's JSON. JSON can carry the key __proto__, and that assignment hands it to the prototype setter rather than defining a property: the entry vanished from the plugin and whatever it held became the fallback that every other lookupSpinner and lookupBorder inherited. Both now build with Object.fromEntries, which defines an own property for every key, and the returned map keeps Object.prototype.

    The registry's deepFrozen copy and caique's prompt binding write the same shape from a loop they cannot turn into an expression, and use Object.defineProperty instead.

    flagstaff/import is 80 bytes lighter for it — 838 B to 758 B — because two loops became two expressions.

0.3.1

Patch Changes

  • #375 98ac9a3 Thanks @ofri-peretz! - flagstaff/cli-table3 — cli-table3's internal surface now hangs off the default export, and the informational internals column moves 0 / 104 to 90 / 104 against a control of 103 / 104.

    No behaviour changed and the gated row is 29 / 29 before and after. All 104 internal cases were failing as X is not a function: the compat oracle reaches a target's internals through a CommonJS shim whose body is module.exports = loaded?.default ?? loaded, and that unwrap hands the suite the Table class rather than the namespace where Cell, strlen, computeWidths and fifteen more already lived. Object.assign(Table, { … }) at the foot of the module publishes them the way cli-table3's own src/cell.js publishes ColSpanCell and RowSpanCell — a second spelling of names this subpath already exported, plus six that were private only because nothing had asked.

    Two ceilings are recorded with the measurement in compat-oracle/src/hosts.ts rather than chased: 13 cases in table-layout-test.js that resolve Cell to Table because the shim collapses four internal modules onto one entry, and the 94 cases of cell-test.js, which never register in the control run either.

    ./cli-table3's byte ratchet rises 29,000 to 29,300 for the 240 B this costs, with the reasoning in weight.test.ts. It is a ceiling moving in the loosening direction and is the owner's to reverse.

  • #373 a1f1d40 Thanks @ofri-peretz! - Stage 2's artifact is now spec.md, the name Anthropic's AI-Native SDLC playbook gives it, so the source comments and README sections that cite a package's own design document point at spec.md rather than design.md.

    No behaviour changes. The published tarballs do move, by two bytes per surviving reference — design.md is nine characters and spec.md is seven — so the four packages carrying a weight band were re-measured against it: linegauge 83,538 to 83,536; paratext 66,343 to 66,341; closeout 84,455 to 84,453; bellpull 86,113 to 86,107.

  • Updated dependencies [f3224f4, 2f6cb16, a1f1d40]:

    • paratext@0.4.0
    • closeout@0.2.1
    • linegauge@0.3.1

0.3.0

Minor Changes

  • #340 c123029 Thanks @ofri-peretz! - flagstaff/box and flagstaff/table render a path or a url as a terminal hyperlink, and flagstaff is the first package in the family to build on paratext (paratext R12).

    A table cell may be { text, href } and a box may be given { href }. On a terminal believed to understand OSC 8 the text becomes a real hyperlink; on a pipe, in a log, under TERM=dumb, and for a screen reader it reads src/index.ts (file:///repo/src/index.ts) — the destination survives rather than being dropped with the escape, and no control byte reaches a file. Neither the guess nor the sequence is flagstaff's: both come from paratext/link, and flagstaff passes it a runtime instead of re-deciding.

    flagstaff/cli-table3's hyperlink() now builds its sequence the same way. It still emits unconditionally and byte for byte what upstream emits — that is the drop-in contract, and cli-table3 still grades 29 / 29 — but the escape itself is no longer written out a second time in this package. src/link.test.ts locks that: no published file here spells an OSC 8 sequence of its own.

    paratext/link rather than paratext: 2,410 B and no registry against 20,221 B and registerBuiltins() at import, measured in paratext's own dist/. flagstaff/table grew 1,675 B and flagstaff/box 1,502 B; the two budgets in weight.test.ts moved with them and the reasoning is recorded there.

Patch Changes

  • #332 3ea38c3 Thanks @ofri-peretz! - flagstaff no longer carries its own copy of "put the cursor back however the process dies". src/cursor.ts is deleted and its three consumers — the ora façade, the log-update façade and the loop's tty projection — reach closeout instead, which owns restore-cursor and signal-exit and grades 6 / 6 against restore-cursor's own suite. That module's own header argued there is exactly one correct implementation of this and that a second copy is a second place to get it wrong; it was the second copy. There was a third, in caique.

    The two façades use closeout/restore-cursor, whose contract is the one their incumbents grade: the stream is a property of the process — stderr if it is a terminal, else stdout — decided when you call, and written at exit whatever isTTY says by then. The projection uses closeout's onExit in the restore phase, because it draws on the stream the Runtime handed it and must not learn that process exists. closeout/exit-hook is deliberately not used: it is faithful to its own incumbent, which never registers SIGHUP, so a closing terminal would not have reached the restore.

    A defect went with it. The deleted module held a process-wide cursorRestoreInstalled flag — first caller installs the net, every later caller gets a no-op. That reads like a guard against a duplicate restore. It was a lost one: the second surface's writer was never registered, so a program with a hoisted frame on stdout and a spinner on stderr hid two cursors and put back one, leaving stderr's hidden. Measured on the previous build at stderr { hide: 1, show: 0 }. Registering per caller fixes it, and src/cursor-net.test.ts grades both halves — every hidden stream restored, and the one redundant (idempotent) show that two surfaces on a single stream now write.

    No compatibility row moves: ora 99 / 99, log-update 99 / 99, boxen 84 / 84. None of those suites kills the process, which is why the guarantee is graded by flagstaff's own signal cases against the built dist/ in a child that is really signalled.

    flagstaff now depends on closeout. The per-entry weight measurements fall — . −1,666 B, ./loop −1,666 B, ./ora −1,591 B, ./log-update −1,591 B — and nothing got lighter: the walk stops at a bare specifier, so the code left the measurement while staying in the program. Installed bytes go up, not down.

  • #326 88f7ba6 Thanks @ofri-peretz! - One Runtime seam per package, and one file in each that names the process (Y9).

    roundel/src/runtime.ts and flagstaff/src/runtime.ts each declare a Runtime — the slice of the world that package actually needs — and a processRuntime() that is the only place the real process is named. Six files stop naming it: roundel/chalk, and flagstaff's cli, ora, boxen, cursor and log-update.

    Nothing about the ports' behaviour moved, and the shape of each seam is what holds that. roundel's returns a literal, because chalk's contract is to detect the terminal once at import; flagstaff's hands back the live process narrowed to the interface, because its incumbents read the process at call time — boxen takes stdout.columns every time a box is drawn, so a box drawn after a resize still uses the new width, and ora still hooks the real stream objects and still looks up kill when it re-signals a swallowed Ctrl+C. The compatibility rows are unchanged: chalk 58/58, ora 99/99, log-update 99/99, boxen 84/84, restore-cursor 6/6.

  • #294 3f92a60 Thanks @ofri-peretz! - paratext validates against the family plugin schema, with its shape under capabilities.

    paratext shipped its own schema.json whose root was one capability, so the family had three plugin schemas where the contract says one (PRINCIPLES 14, plugin-contract R2). The capability shape is now $defs/capability of the shared file, reached through a capabilities key beside spinners, tokens and components, and packages/*/src/schema.json hashes to one value. flagstaff and roundel ship the same bytes: their published ./schema.json gains the capability definitions and nothing about what they validate changes.

    check() follows the schema's $defs/capabilityDocument and takes either shape:

    • a plugin carrying its capabilities under capabilities, which is where they live from now on, and whose problems are reported at capabilities.<key>;
    • deprecated — one capability written as the whole document, which is what a 0.2 capability file looks like. It still validates, and check() returns a deprecated: line saying to move it under capabilities. paratext 1.0 stops accepting it (.sdlc/PLAN.md D2).

    refusals() and isDeprecation() are exported to tell the two kinds of line apart; the lines that are not deprecations are the ones that block, and the ones register() throws on. register(capability) is unchanged: it takes one capability, not a document, so it neither reports nor accepts the document-level deprecation.

  • #339 f295630 Thanks @ofri-peretz! - schema.json constrains token names, because it was promising something no host honours.

    tokens was described as any name to a #rrggbb colour. roundel's validate() accepts ten semantic names — error, warn, ok, hint, muted, command, flag, value, heading, ground — and throws on everything else. So a plugin author doing exactly what their own E_PLUGIN_SCHEMA error tells them, comparing their object against roundel/schema.json, got a green from the schema and "accent" is not a token from register(). Measured 2026-09-16 with { accent: '[#336699](https://github.com/ofri-peretz/burgee/issues/336699)' }.

    The schema now carries propertyNames.enum, and scripts/plugin-contract-lock.test.ts pins the enum and the runtime set to each other from both sides, so neither can grow a name the other does not know.

    Every host ships a byte-identical copy of this file (plugin-schema-lock.test.ts asserts it), which is why nine packages are listed. Only the key roundel owns is constrained: describing widgets, handlers, sources, resolvers or commands in a file all eight hosts share is what made flagstaff start validating caique's key last time (PluginError: plugin.widgets.later: expected object, got boolean), and those stay in plugin-schema-lock's UNDESCRIBED list with that reason.

    linegauge is in the list for a different change: ceilings.json's R9 block now records the bar as D1's tree-inclusive ceiling — 83,538 against 170,342, a ratio of 0.4904 — and keeps the superseded get-east-asian-width bar beside it with the count of entries that cleared it.

  • Updated dependencies [c8acb28, fc640dd, 3f92a60, 3ea38c3, fc640dd, 3ea38c3, c8acb28, 88f7ba6, c8acb28, 3f92a60, fc640dd, 3ea38c3, 0f00f72, f295630]:

    • closeout@0.2.0
    • linegauge@0.3.0
    • roundel@0.3.1
    • paratext@0.3.0

0.2.1

Patch Changes

0.2.0

Minor Changes

  • #86 e927678 Thanks @ofri-peretz! - Add flagstaff/boxen: boxen 8's API, graded 84 / 84 by boxen's own test suite.

    Every one of boxen's 84 cases is a snapshot of the exact characters the box comes out as, so matching the drawing byte for byte is the compatibility claim rather than a way of avoiding one — a user leaving boxen cares about one thing, whether the box still looks the same.

    borderStyle (all eight cli-boxes styles, a style object, or none), borderColor, backgroundColor, dimBorder, title/titleAlignment, textAlignment, padding, margin, width, height, float, fullscreen, and the _borderStyles re-export.

    Eight dependencies folded in. boxen reaches string-width, wrap-ansi, cli-boxes, ansi-align, widest-line, camelcase, chalk and type-fest; this reaches width.js and wrap.js — both already shipped for flagstaff/ora and flagstaff/log-update — plus roundel/chalk. 43.0 KB in two packages, against boxen 8.0.1's 151.4 KB in fourteen.

    It carries cli-boxes' table itself rather than reading the plugin registry: _borderStyles is boxen's public surface, and a façade whose drawing changed when somebody registered a plugin would be reinterpreting its host. Named borders through the registry stay flagstaff/box's job.

  • #116 3daa412 Thanks @ofri-peretz! - Add flagstaff/cli-table3: cli-table3 0.6.5's API, graded 29 / 29 by cli-table3's own test suite.

    The full option surface — head, chars, style, colWidths, rowHeights, colAligns, rowAligns, truncate, wordWrap, wrapOnWordBoundary, per-cell colSpan, rowSpan, hAlign, vAlign, href, and the debug channel with table.messages and Table.reset(). It extends Array, because cli-table3 does and its callers push rows onto it.

    One module, not four. Upstream is table.js, layout-manager.js, cell.js and utils.js, and 201 of its 234 cases test those files directly. Those are reported beside the number and never gate it — passing them would mean copying cli-table3's file layout rather than matching its behaviour, which is the one thing a façade owes its users.

    42.3 KB in two packages, against cli-table3 0.6.5's 161.7 KB in seven. It reaches width.js, already shipped for the other three façades, plus roundel/chalk for the two default styles. It carries its own wrapping rather than sharing wrap.js: cli-table3 splits on /(\s+)/ and counts with its own strlen, which a wrap-ansi port does not reproduce.

Patch Changes

  • #149 0d2520b Thanks @ofri-peretz! - Correct the published weight comparisons against boxen and cli-table3, both of which overstated the incumbent. boxen 8.0.1 is 132,414 B across nineteen packages, not the 151,351 in fourteen the README and weight rules claimed; cli-table3 0.6.5 is 105,983 across seven, not 161,690. ora and log-update reproduce to the byte and are unchanged.

  • #82 61bd11b Thanks @ofri-peretz! - One door into the registry, and a check that grades what it actually found.

    registered() handed out the live registry behind a Readonly<Registry> type that freezes property bindings, not the Maps behind them — so registered().spinners.set(…) put a spinner with no static projection where spinner() would find it, and .clear() removed the built-ins. It now returns a copy over the frozen objects register() stores, which makes U3's "a contribution without a static projection is refused at the door" a property of the code rather than advice. Registering also copies: edit your plugin object afterwards and the registry does not change.

    flagstaff check opens with a census of the contributions it found and closes with the verdict, so ok is never printed before the rendering that would justify it. A plugin whose keys are misspelled — the schema allows unknown keys on purpose, for the rest of the family — is now E_NO_CONTRIBUTION and exit 1 with the unknown keys named, rather than ok and exit 0. Each component block states the state it was rendered with: a component may declare sample: { running, done }, and without one the assumed { phase } shape is said out loud instead of silently invented. A static that throws is E_COMPONENT_THREW with a fix and the modes it broke in, rather than an uncaught crash after an ok.

    roundel is bumped with it because the plugin schema is hosted in both packages and both publish it: packages/roundel/src/schema.json gained the same sample key, and plugin-schema-lock.test.ts requires the two to be byte-identical. Without a roundel release the copies would agree in git and disagree in the registry — the contract's own "byte-identical in every tarball" rule holding in the repository and breaking where anyone would actually read it. This is the first contract change since roundel became a plugin host, so the pairing is worth establishing now rather than after the second one.

    Both of those codes are now members of the exported PluginErrorCode, which is the union every refusal in the family comes from. They were bare string literals inside cli.ts, so a second host could have spelled either one its own way and nothing would have noticed — the plugin contract's "one error vocabulary" held only as long as nobody tested it. refuse() takes PluginErrorCode rather than string, and a repo lock reads each host's declaration out of its source and refuses any E_… literal that is not in it. The union is a type, so this costs no bytes on any subpath.

  • #117 ec43c47 Thanks @ofri-peretz! - register() lists a plugin name once instead of accumulating it — every other contribution already landed in a Map, so re-registering replaced entries while the name list grew, and flagstaff check and the docs gallery both project that list.

  • #75 f2eaa6a Thanks @ofri-peretz! - hoist() puts the cursor back when a signal ends the process. close() restored it, and close() does not run for a signal with no listener — so Ctrl+C during a frame left the terminal with no cursor at all. The loop now shares the cursor.js both façades use.

  • #182 28a838f Thanks @ofri-peretz! - linegauge is real: width and wrap move out of flagstaff into the foundation package that was reserved for them (F1, the move only). The default export is width, call-compatible with string-width's default. flagstaff imports them and deletes both files; its 227 tests pass unchanged, and B4's bundled bytes are identical to the byte — the code went to a different file, not away.

  • Updated dependencies [61bd11b, 8586f58, 28a838f, 7c5eeb0]:

    • roundel@0.2.0
    • linegauge@0.1.0

0.1.0

Minor Changes

  • #45 4e376fe Thanks @ofri-peretz! - The first working release: hoist() with a static projection per mode, register() over schema.json, the dots and line spinners as a plugin, and flagstaff check.

  • #62 5e34676 Thanks @ofri-peretz! - flagstaff/ora — ora 9's whole API, graded 99 / 99 by ora's own test suite. One import changes; the seventeen packages ora ships become two, 113,577 B of JavaScript becomes 55,641 B (49%), and the spinner corpus, the display width, the log symbols, the cursor control — restored on SIGINT, SIGTERM and SIGHUP as well as a clean exit — and the stdin discarder come with it. Both sides counted the same way from the entry point: shipped .js plus the .json a module imports, package.json excluded.

Patch Changes

  • #66 b43e1d3 Thanks @ofri-peretz! - The four remaining built-ins: progress, tasks, box and table, each on its own subpath. box() and table() are also plain string functions for the callers who want the string. Every one answers the static projection separately — a progress bar is 12/30 files · 40% off a terminal, a table is header: value pairs — because a stripped drawing is not information.

  • #68 8666f70 Thanks @ofri-peretz! - flagstaff/log-update restores the cursor when the process is signalled, not only when it exits normally. It shipped with process.once('exit', …) and nothing else — which node does not run when a signal with no listener terminates the process — so Ctrl+C mid-frame left the terminal with no cursor. That is the same defect flagstaff/ora fixed before it shipped, so the fix is now one module, src/cursor.ts, that both façades import: signal-exit's 22.0 KB in 1.4 KB, with the re-raise and its listenerCount guard, so a program that installed its own SIGINT handler is still delivered exactly one signal and is never overruled. Graded per façade against the built dist/ in a child process that is really signalled.

  • #66 b43e1d3 Thanks @ofri-peretz! - flagstaff/import — fromCliSpinners() and fromCliBoxes() turn the corpora you already have into ordinary plugins, through the same register() and the same schema. Neither corpus is bundled; 838 B, reaching nothing. The plugin contract gains borders, and the built-in border styles move into the built-ins plugin with the spinners, so box() can draw with a style a plugin registered.

  • #66 b43e1d3 Thanks @ofri-peretz! - flagstaff/log-update — log-update 8's API, graded 99 / 99 by log-update's own suite, which renders every frame through a real terminal emulator. Sixteen packages and 113.4 KB become none and 28.7 KB: the subpath reaches no package at all. Brings wrap(), an ANSI-aware wrapper graded differentially against wrap-ansi, which box and table share.

  • #76 847f79a Thanks @ofri-peretz! - Export flagstaff/schema.json.

    PluginError's fix for E_PLUGIN_SCHEMA tells a plugin author to "compare the object against flagstaff/schema.json", and that specifier did not resolve — following the advice got ERR_PACKAGE_PATH_NOT_EXPORTED. The file already shipped in the tarball; only the exports entry was missing.

  • #66 b43e1d3 Thanks @ofri-peretz! - The static projection writes only what is new. A component whose projection is a growing list — tasks, whose static is every task that has settled — reprinted every line already in the log on each change; it now writes the lines past the common prefix. An empty projection writes nothing at all, rather than a blank line. Found by generating the docs gallery from the components themselves.

  • Updated dependencies [183ebc9, c65bad8]:

    • roundel@0.1.0

On this page