flagstaff 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/conforms.d.ts +31 -0
- package/dist/conforms.js +72 -0
- package/dist/plugin.js +2 -71
- package/dist/plugin.schema.json +1 -0
- package/dist/schema.json +1 -1
- package/package.json +16 -10
package/README.md
CHANGED
|
@@ -18,13 +18,18 @@
|
|
|
18
18
|
<img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License: MIT" />
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
Docs: <a href="https://burgee.interlace.tools/docs/packages/flagstaff">https://burgee.interlace.tools/docs/packages/flagstaff</a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
21
25
|
ora animates a spinner and, off a terminal, prints frames anyway — `\r` after `\r` into the
|
|
22
26
|
log an agent reads back. Ink fixes the terminal by shipping React and a layout engine.
|
|
23
27
|
**flagstaff** is the staff the flag flies from: a frame loop that hoists a component, holds
|
|
24
28
|
it, changes it and lowers it, and a **static projection** that is what every mode but the
|
|
25
29
|
terminal gets — one line per state on a pipe, one event per transition under `--json`,
|
|
26
|
-
plain text for a screen reader. Plugins are data. No layout engine.
|
|
27
|
-
|
|
30
|
+
plain text for a screen reader. Plugins are data. No layout engine. Four dependencies, all
|
|
31
|
+
from this repository: [roundel](../roundel/README.md), [paratext](../paratext/README.md),
|
|
32
|
+
[linegauge](../linegauge/README.md) and [closeout](../closeout/README.md).
|
|
28
33
|
|
|
29
34
|
A **flagstaff** is the simplest part of the whole apparatus and the only one that is always
|
|
30
35
|
in view: a flag is hoisted on it, held there, changed, and lowered when it is done. That is a
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON Schema walker the plugin host validates with, in its own module so the lock that asks
|
|
3
|
+
* whether the family schema describes every host (`scripts/plugin-schema-coverage-lock.test.ts`)
|
|
4
|
+
* walks with the same code a plugin is refused by — not a second walker that could disagree.
|
|
5
|
+
* Not a published subpath: nothing outside this package imports it.
|
|
6
|
+
*/
|
|
7
|
+
export interface JsonSchema {
|
|
8
|
+
type?: string;
|
|
9
|
+
const?: unknown;
|
|
10
|
+
required?: string[];
|
|
11
|
+
properties?: Record<string, JsonSchema>;
|
|
12
|
+
additionalProperties?: JsonSchema | boolean;
|
|
13
|
+
items?: JsonSchema;
|
|
14
|
+
minItems?: number;
|
|
15
|
+
minLength?: number;
|
|
16
|
+
minimum?: number;
|
|
17
|
+
maximum?: number;
|
|
18
|
+
pattern?: string;
|
|
19
|
+
$ref?: string;
|
|
20
|
+
}
|
|
21
|
+
export type Problem = string | undefined;
|
|
22
|
+
/** A schema document: the node the walk starts at, carrying the `$defs` its `$ref`s name. */
|
|
23
|
+
export type Root = JsonSchema & {
|
|
24
|
+
$defs?: Record<string, JsonSchema>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The subset of JSON Schema the family schema uses, walked by hand: a validator is a dependency
|
|
28
|
+
* the package will not carry. `root` is the document `$ref`s resolve against — flagstaff's own
|
|
29
|
+
* fragment at run time, the whole family schema when a lock asks whether it describes a plugin.
|
|
30
|
+
*/
|
|
31
|
+
export declare function check(value: unknown, node: JsonSchema, path: string, root?: Root): Problem;
|
package/dist/conforms.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const DEFS_PREFIX = '#/$defs/';
|
|
2
|
+
function typeOf(value) {
|
|
3
|
+
if (Array.isArray(value))
|
|
4
|
+
return 'array';
|
|
5
|
+
if (value === null)
|
|
6
|
+
return 'null';
|
|
7
|
+
if (typeof value === 'number')
|
|
8
|
+
return Number.isInteger(value) ? 'integer' : 'number';
|
|
9
|
+
return typeof value;
|
|
10
|
+
}
|
|
11
|
+
function checkScalar(value, node, path) {
|
|
12
|
+
if (node.const !== undefined && value !== node.const)
|
|
13
|
+
return `${path}: expected ${JSON.stringify(node.const)}, got ${JSON.stringify(value)}`;
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
if (node.minLength !== undefined && value.length < node.minLength)
|
|
16
|
+
return `${path}: must not be empty`;
|
|
17
|
+
if (node.pattern !== undefined && !new RegExp(node.pattern).test(value))
|
|
18
|
+
return `${path}: ${JSON.stringify(value)} does not match ${node.pattern}`;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'number' && node.minimum !== undefined && value < node.minimum)
|
|
21
|
+
return `${path}: must be at least ${node.minimum}`;
|
|
22
|
+
if (typeof value === 'number' && node.maximum !== undefined && value > node.maximum)
|
|
23
|
+
return `${path}: must be at most ${node.maximum}`;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function checkArray(value, node, path, root) {
|
|
27
|
+
if (node.minItems !== undefined && value.length < node.minItems)
|
|
28
|
+
return `${path}: needs at least ${node.minItems} item(s)`;
|
|
29
|
+
const { items } = node;
|
|
30
|
+
if (items === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
for (const [i, item] of value.entries()) {
|
|
33
|
+
const problem = check(item, items, `${path}[${i}]`, root);
|
|
34
|
+
if (problem !== undefined)
|
|
35
|
+
return problem;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const isSchema = (x) => typeof x === 'object' && x !== null;
|
|
40
|
+
function checkObject(record, node, path, root) {
|
|
41
|
+
for (const key of node.required ?? []) {
|
|
42
|
+
if (!(key in record))
|
|
43
|
+
return `${path}.${key}: required`;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, child] of Object.entries(record)) {
|
|
46
|
+
const rule = node.properties?.[key] ?? (isSchema(node.additionalProperties) ? node.additionalProperties : undefined);
|
|
47
|
+
if (rule === undefined) {
|
|
48
|
+
if (node.additionalProperties === false)
|
|
49
|
+
return `${path}.${key}: not allowed`;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const problem = check(child, rule, `${path}.${key}`, root);
|
|
53
|
+
if (problem !== undefined)
|
|
54
|
+
return problem;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
export function check(value, node, path, root = node) {
|
|
59
|
+
if (node.$ref !== undefined) {
|
|
60
|
+
const def = root.$defs?.[node.$ref.slice(DEFS_PREFIX.length)];
|
|
61
|
+
return def === undefined ? `${path}: unknown $ref ${node.$ref}` : check(value, def, path, root);
|
|
62
|
+
}
|
|
63
|
+
const actual = typeOf(value);
|
|
64
|
+
if (node.type !== undefined && actual !== node.type && !(node.type === 'number' && actual === 'integer')) {
|
|
65
|
+
return `${path}: expected ${node.type}, got ${actual}`;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value))
|
|
68
|
+
return checkArray(value, node, path, root);
|
|
69
|
+
if (actual === 'object')
|
|
70
|
+
return checkObject(value, node, path, root);
|
|
71
|
+
return checkScalar(value, node, path);
|
|
72
|
+
}
|
package/dist/plugin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { builtins } from './builtins.js';
|
|
2
|
-
import
|
|
2
|
+
import { check } from './conforms.js';
|
|
3
|
+
import schema from './plugin.schema.json' with { type: 'json' };
|
|
3
4
|
export const CONTRACT = 1;
|
|
4
5
|
export class PluginError extends Error {
|
|
5
6
|
code;
|
|
@@ -11,77 +12,7 @@ export class PluginError extends Error {
|
|
|
11
12
|
this.name = 'PluginError';
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
const DEFS_PREFIX = '#/$defs/';
|
|
15
15
|
const ROOT = schema;
|
|
16
|
-
function typeOf(value) {
|
|
17
|
-
if (Array.isArray(value))
|
|
18
|
-
return 'array';
|
|
19
|
-
if (value === null)
|
|
20
|
-
return 'null';
|
|
21
|
-
if (typeof value === 'number')
|
|
22
|
-
return Number.isInteger(value) ? 'integer' : 'number';
|
|
23
|
-
return typeof value;
|
|
24
|
-
}
|
|
25
|
-
function checkScalar(value, node, path) {
|
|
26
|
-
if (node.const !== undefined && value !== node.const)
|
|
27
|
-
return `${path}: expected ${JSON.stringify(node.const)}, got ${JSON.stringify(value)}`;
|
|
28
|
-
if (typeof value === 'string') {
|
|
29
|
-
if (node.minLength !== undefined && value.length < node.minLength)
|
|
30
|
-
return `${path}: must not be empty`;
|
|
31
|
-
if (node.pattern !== undefined && !new RegExp(node.pattern).test(value))
|
|
32
|
-
return `${path}: ${JSON.stringify(value)} does not match ${node.pattern}`;
|
|
33
|
-
}
|
|
34
|
-
if (typeof value === 'number' && node.minimum !== undefined && value < node.minimum)
|
|
35
|
-
return `${path}: must be at least ${node.minimum}`;
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
function checkArray(value, node, path) {
|
|
39
|
-
if (node.minItems !== undefined && value.length < node.minItems)
|
|
40
|
-
return `${path}: needs at least ${node.minItems} item(s)`;
|
|
41
|
-
const { items } = node;
|
|
42
|
-
if (items === undefined)
|
|
43
|
-
return undefined;
|
|
44
|
-
for (const [i, item] of value.entries()) {
|
|
45
|
-
const problem = check(item, items, `${path}[${i}]`);
|
|
46
|
-
if (problem !== undefined)
|
|
47
|
-
return problem;
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
const isSchema = (x) => typeof x === 'object' && x !== null;
|
|
52
|
-
function checkObject(record, node, path) {
|
|
53
|
-
for (const key of node.required ?? []) {
|
|
54
|
-
if (!(key in record))
|
|
55
|
-
return `${path}.${key}: required`;
|
|
56
|
-
}
|
|
57
|
-
for (const [key, child] of Object.entries(record)) {
|
|
58
|
-
const rule = node.properties?.[key] ?? (isSchema(node.additionalProperties) ? node.additionalProperties : undefined);
|
|
59
|
-
if (rule === undefined) {
|
|
60
|
-
if (node.additionalProperties === false)
|
|
61
|
-
return `${path}.${key}: not allowed`;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const problem = check(child, rule, `${path}.${key}`);
|
|
65
|
-
if (problem !== undefined)
|
|
66
|
-
return problem;
|
|
67
|
-
}
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
function check(value, node, path) {
|
|
71
|
-
if (node.$ref !== undefined) {
|
|
72
|
-
const def = ROOT.$defs[node.$ref.slice(DEFS_PREFIX.length)];
|
|
73
|
-
return def === undefined ? `${path}: unknown $ref ${node.$ref}` : check(value, def, path);
|
|
74
|
-
}
|
|
75
|
-
const actual = typeOf(value);
|
|
76
|
-
if (node.type !== undefined && actual !== node.type && !(node.type === 'number' && actual === 'integer')) {
|
|
77
|
-
return `${path}: expected ${node.type}, got ${actual}`;
|
|
78
|
-
}
|
|
79
|
-
if (Array.isArray(value))
|
|
80
|
-
return checkArray(value, node, path);
|
|
81
|
-
if (actual === 'object')
|
|
82
|
-
return checkObject(value, node, path);
|
|
83
|
-
return checkScalar(value, node, path);
|
|
84
|
-
}
|
|
85
16
|
const NO_STATIC = /\.(?:spinners|components)\.([^.]+)\.static: required$/;
|
|
86
17
|
export function validate(plugin) {
|
|
87
18
|
const problem = check(plugin, ROOT, 'plugin');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"propertyNames":{"enum":["error","warn","ok","hint","muted","command","flag","value","heading","ground"],"description":"roundel's ten semantic tokens, and nothing else — `roundel`'s own validate() refuses any other name."}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}}}}
|
package/dist/schema.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/ofri-peretz/burgee/blob/main/packages/flagstaff/src/schema.json","title":"flagstaff plugin","description":"A plugin is one plain object. Everything in it is data that can be read without running it; the only functions allowed are a component's `static` (required) and `frame` (optional). A spinner or component without a static projection is refused at register().","type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"propertyNames":{"enum":["error","warn","ok","hint","muted","command","flag","value","heading","ground"],"description":"roundel's ten semantic tokens, and nothing else — `roundel`'s own validate() refuses any other name."}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}},"capabilities":{"$ref":"#/$defs/capabilities"},"widths":{"$ref":"#/$defs/widths"}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}},"capability":{"type":"object","required":["name","osc","when","encode","fallback"],"additionalProperties":false,"description":"One paratext capability: what it says to the terminal, when the terminal is believed to understand it, and what prints when it does not. No functions, so it travels through JSON. `encode` and `fallback` are templates: `{field}` is the field's value, `{field|base64}` is it base64-encoded, and `[ … ]` is emitted only when every field inside it has a value.","properties":{"name":{"type":"string","minLength":1,"description":"How callers name it. Registering an existing name replaces it — how a caller corrects a guess we got wrong."},"osc":{"description":"The OSC code this speaks, or 'BEL' for the bell and for protocols that are not OSC at all, such as Kitty's.","oneOf":[{"type":"integer","minimum":0},{"const":"BEL"}]},"when":{"type":"object","description":"When the terminal is believed to understand it. Every clause must hold; `termProgram` and `envAny` are ORs within themselves. Guesses — no terminal answers 'do you do OSC 1337' — and therefore data a caller can replace.","additionalProperties":false,"properties":{"tty":{"type":"boolean","description":"Refuse a pipe. Almost always true: a file that receives OSC gets control bytes in it."},"termProgram":{"type":"array","items":{"type":"string"},"description":"Any one of these TERM_PROGRAM values."},"envAny":{"type":"array","items":{"type":"string"},"description":"Any one of these environment variables merely being set, as VTE announces itself."},"term":{"type":"string","description":"An exact TERM — Kitty is xterm-kitty."}}},"encode":{"type":"string","minLength":1,"description":"The bytes, as a template, for a terminal that does understand."},"fallback":{"type":"string","description":"What to print when it does not — PRINCIPLES rule 6. '' is a legitimate answer, a window title having nothing to say in a log; absence is not, which is why this is required and may be ''."}},"examples":[{"name":"kitty-image","osc":"BEL","when":{"tty":true,"term":"xterm-kitty"},"encode":"\u001b_Ga=T,f=100;{base64}\u001b\\","fallback":"{caption}"}]},"capabilities":{"type":"object","description":"paratext capabilities by name — the OSC section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/capability"}},"capabilityDocument":{"description":"What paratext's `check()` accepts. Two shapes, and only one of them survives 1.0.","oneOf":[{"description":"The family shape: a plugin carrying its capabilities under `capabilities`.","type":"object","required":["name","capabilities"],"properties":{"name":{"type":"string","minLength":1},"contract":{"type":"integer","minimum":1},"capabilities":{"$ref":"#/$defs/capabilities"}}},{"deprecated":true,"description":"Deprecated: one capability as the whole document, the shape paratext's schema had before this one absorbed it. Accepted for one minor release, removed at 1.0 — `check()` validates it and says so.","$ref":"#/$defs/capability"}]},"widthRange":{"type":"array","description":"One inclusive code-point range, as [low, high]. A single code point is written [n, n].","items":{"type":"integer","minimum":0,"maximum":1114111},"minItems":2,"maxItems":2},"widthOverride":{"type":"object","description":"A linegauge width override: the column count a named set of code-point ranges occupies, for a terminal that disagrees with the Unicode tables. Data only — no function, so it can be written in a config file, diffed, and printed by `linegauge check` without running anyone code.","required":["ranges","columns","why"],"additionalProperties":false,"properties":{"ranges":{"type":"array","description":"The code points this override applies to.","items":{"$ref":"#/$defs/widthRange"},"minItems":1},"columns":{"type":"integer","description":"Columns each cluster in those ranges occupies: 0 for a zero-width mark, 1 narrow, 2 wide.","minimum":0,"maximum":2},"why":{"type":"string","description":"The terminal or the reason. Required, because a width table with no provenance is one nobody can audit when it turns out to be wrong — which is the normal outcome for ambiguous width.","minLength":1}}},"widths":{"type":"object","description":"linegauge width overrides by name — the measurement section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/widthOverride"}}}}
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/ofri-peretz/burgee/blob/main/packages/flagstaff/src/schema.json","title":"flagstaff plugin","description":"A plugin is one plain object. Everything in it is data that can be read without running it; the only functions allowed are a component's `static` (required) and `frame` (optional). A spinner or component without a static projection is refused at register().","type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"propertyNames":{"enum":["error","warn","ok","hint","muted","command","flag","value","heading","ground"],"description":"roundel's ten semantic tokens, and nothing else — `roundel`'s own validate() refuses any other name."}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}},"capabilities":{"$ref":"#/$defs/capabilities"},"widths":{"$ref":"#/$defs/widths"},"resolvers":{"type":"object","description":"bellpull resolvers by name: extra directories searched for an executable, before PATH (negative rank) or after it (positive). Two resolvers with one name shadow; two names both apply, in rank order.","additionalProperties":{"$ref":"#/$defs/resolver"}},"widgets":{"type":"object","description":"caique prompt widgets by kind. The six built-in kinds — text, confirm, select, multiselect, password, path — cannot be replaced: a plugin adds kinds of its own.","additionalProperties":{"$ref":"#/$defs/widget"}},"handlers":{"type":"array","description":"closeout exit handlers. The phase decides the order they run in, not their position in this list.","items":{"$ref":"#/$defs/handler"}},"sources":{"type":"object","description":"seniority configuration sources by name, ranked against the built-in layers: a flag is 0 and a declared default is 40, and a plugin source sits strictly between.","additionalProperties":{"$ref":"#/$defs/source"}},"commands":{"type":"array","description":"burgee commands this plugin contributes. Each is read by exactly the code a program's own command is, and a burgee plugin must declare `contract`.","items":{"$ref":"#/$defs/command"}},"hooks":{"type":"object","description":"burgee lifecycle hooks. preRun opens around a command, and exactly one of postRun or onError closes.","additionalProperties":false,"properties":{"preRun":{"$ref":"#/$defs/hook"},"postRun":{"$ref":"#/$defs/hook"},"onError":{"$ref":"#/$defs/hook"}}},"enforce":{"type":"string","pattern":"^(pre|post)$","description":"burgee: order this plugin's hooks before (`pre`) or after (`post`) the others."}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}},"capability":{"type":"object","required":["name","osc","when","encode","fallback"],"additionalProperties":false,"description":"One paratext capability: what it says to the terminal, when the terminal is believed to understand it, and what prints when it does not. No functions, so it travels through JSON. `encode` and `fallback` are templates: `{field}` is the field's value, `{field|base64}` is it base64-encoded, and `[ … ]` is emitted only when every field inside it has a value.","properties":{"name":{"type":"string","minLength":1,"description":"How callers name it. Registering an existing name replaces it — how a caller corrects a guess we got wrong."},"osc":{"description":"The OSC code this speaks, or 'BEL' for the bell and for protocols that are not OSC at all, such as Kitty's.","oneOf":[{"type":"integer","minimum":0},{"const":"BEL"}]},"when":{"type":"object","description":"When the terminal is believed to understand it. Every clause must hold; `termProgram` and `envAny` are ORs within themselves. Guesses — no terminal answers 'do you do OSC 1337' — and therefore data a caller can replace.","additionalProperties":false,"properties":{"tty":{"type":"boolean","description":"Refuse a pipe. Almost always true: a file that receives OSC gets control bytes in it."},"termProgram":{"type":"array","items":{"type":"string"},"description":"Any one of these TERM_PROGRAM values."},"envAny":{"type":"array","items":{"type":"string"},"description":"Any one of these environment variables merely being set, as VTE announces itself."},"term":{"type":"string","description":"An exact TERM — Kitty is xterm-kitty."}}},"encode":{"type":"string","minLength":1,"description":"The bytes, as a template, for a terminal that does understand."},"fallback":{"type":"string","description":"What to print when it does not — PRINCIPLES rule 6. '' is a legitimate answer, a window title having nothing to say in a log; absence is not, which is why this is required and may be ''."}},"examples":[{"name":"kitty-image","osc":"BEL","when":{"tty":true,"term":"xterm-kitty"},"encode":"\u001b_Ga=T,f=100;{base64}\u001b\\","fallback":"{caption}"}]},"capabilities":{"type":"object","description":"paratext capabilities by name — the OSC section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/capability"}},"capabilityDocument":{"description":"What paratext's `check()` accepts. Two shapes, and only one of them survives 1.0.","oneOf":[{"description":"The family shape: a plugin carrying its capabilities under `capabilities`.","type":"object","required":["name","capabilities"],"properties":{"name":{"type":"string","minLength":1},"contract":{"type":"integer","minimum":1},"capabilities":{"$ref":"#/$defs/capabilities"}}},{"deprecated":true,"description":"Deprecated: one capability as the whole document, the shape paratext's schema had before this one absorbed it. Accepted for one minor release, removed at 1.0 — `check()` validates it and says so.","$ref":"#/$defs/capability"}]},"widthRange":{"type":"array","description":"One inclusive code-point range, as [low, high]. A single code point is written [n, n].","items":{"type":"integer","minimum":0,"maximum":1114111},"minItems":2,"maxItems":2},"widthOverride":{"type":"object","description":"A linegauge width override: the column count a named set of code-point ranges occupies, for a terminal that disagrees with the Unicode tables. Data only — no function, so it can be written in a config file, diffed, and printed by `linegauge check` without running anyone code.","required":["ranges","columns","why"],"additionalProperties":false,"properties":{"ranges":{"type":"array","description":"The code points this override applies to.","items":{"$ref":"#/$defs/widthRange"},"minItems":1},"columns":{"type":"integer","description":"Columns each cluster in those ranges occupies: 0 for a zero-width mark, 1 narrow, 2 wide.","minimum":0,"maximum":2},"why":{"type":"string","description":"The terminal or the reason. Required, because a width table with no provenance is one nobody can audit when it turns out to be wrong — which is the normal outcome for ambiguous width.","minLength":1}}},"widths":{"type":"object","description":"linegauge width overrides by name — the measurement section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/widthOverride"}},"resolver":{"type":"object","required":["rank","paths"],"description":"One bellpull resolver: where to look for an executable, and when.","properties":{"rank":{"type":"number","description":"Search order against PATH: negative searches before it, positive after."},"paths":{"type":"array","minItems":1,"description":"Directories to search. `{VAR}` is substituted from the environment; each must be absolute, or start with a `{VAR}` that holds an absolute path.","items":{"type":"string","minLength":1,"pattern":"^(/|\\{|[A-Za-z]:[\\\\/]|\\\\\\\\)"}},"extensions":{"type":"array","items":{"type":"string"},"description":"Extensions to try on Windows, in place of PATHEXT."},"when":{"type":"object","description":"When the resolver applies. Leave it out and it always does.","properties":{"platform":{"type":"array","items":{"type":"string"},"description":"process.platform values it applies on."},"envAny":{"type":"array","items":{"type":"string"},"description":"It applies when any of these environment variables is set."}}}}},"widget":{"type":"object","required":["static"],"description":"One caique widget: how a prompt kind of the plugin's own is drawn.","properties":{"static":{"description":"(spec) => string. Required: what a pipe, an agent or a screen reader gets instead of the interactive prompt."},"frame":{"description":"(t, spec) => string. Optional: the animated form; leave it out and the widget is line-mode only."},"sample":{"type":"object","required":["running","done"],"description":"Two named states of plain data that `caique check` renders the widget with."}}},"handler":{"type":"object","required":["name","run"],"description":"One closeout exit handler.","properties":{"name":{"type":"string","minLength":1,"description":"How the handler is named in a report, including the one the deadline prints when it does not return."},"phase":{"type":"string","pattern":"^(flush|release)$","description":"When it runs. Defaults to `release`; `restore` is closeout's own last phase and a plugin may not use it."},"run":{"description":"(info) => void | Promise<void>. Required: the cleanup itself."}}},"source":{"type":"object","required":["rank"],"description":"One seniority source. Exactly one of `values` (static) or `read(runtime)` (fetched) gives its answer.","properties":{"rank":{"type":"integer","minimum":1,"maximum":39,"description":"Precedence, strictly between the flag (0) and the declared default (40): a source may not beat what the user typed, nor sink below the default."},"values":{"type":"object","description":"Option name to value."},"read":{"description":"(runtime) => { values, location? } | undefined."},"location":{"type":"string","description":"The file, URL or variable set a person would go and look at."}}},"option":{"type":"object","required":["type"],"description":"One burgee option, keyed by its camelCase name; the flag is its kebab-case form.","properties":{"type":{"type":"string","pattern":"^(string|boolean|number)$"},"description":{"type":"string"},"short":{"type":"string","minLength":1},"required":{"type":"boolean"},"env":{"type":"string"},"choices":{"type":"array","items":{"type":"string"}},"dependsOn":{"type":"array","items":{"type":"string"},"description":"Options that must be set with this one; each must be another option of the same command."},"exclusive":{"type":"array","items":{"type":"string"},"description":"Options that may not be set with this one; each must be another option of the same command."},"deprecated":{"description":"What replaces this option, e.g. '--force'. `true` alone, or an empty string, is refused: a deprecation names its replacement."}}},"command":{"type":"object","required":["path"],"description":"One burgee command node, declared exactly as a program's own command is.","properties":{"path":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"The words a user types. May not repeat a path the program or an earlier plugin already declares."},"description":{"type":"string"},"options":{"type":"object","additionalProperties":{"$ref":"#/$defs/option"}},"effects":{"type":"string","pattern":"^(read_only|idempotent|non_idempotent|withheld)$","description":"What running it does to the world. Required on any command that runs; `withheld` serves it to people and keeps it out of the MCP tool list."},"deprecated":{"description":"What replaces this command. `true` alone, or an empty string, is refused."},"run":{"description":"(ctx) => unknown. What the command does; its return value is the `--json` envelope's data."},"load":{"description":"() => Promise<module>. A lazy form of `run`, imported on dispatch."}}},"hook":{"type":"object","required":["handler"],"description":"One burgee hook.","properties":{"filter":{"type":"object","description":"`{ command: RegExp }`: fire only for commands whose path matches."},"handler":{"description":"(ctx) => void | Promise<void>. Required."}}}}}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagstaff",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "The staff the flag flies from. A terminal frame loop with a static projection for agents and screen readers, and a plugin host for spinners, progress, boxes and tables. Drop-in paths for ora, log-update, boxen and cli-table3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
|
+
"description": "The staff the flag flies from. A terminal frame loop with a static projection for agents and screen readers, and a plugin host for spinners, progress, boxes and tables. Drop-in paths for ora, log-update, boxen and cli-table3. No dependency outside the burgee family.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": ["./dist/cli.js"],
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
10
10
|
},
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"THIRD-PARTY.md"
|
|
87
87
|
],
|
|
88
88
|
"scripts": {
|
|
89
|
-
"build": "tsc -p tsconfig.build.json && node ../../scripts/strip-comments.mjs dist",
|
|
89
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/schema-to-dist.mjs && node ../../scripts/strip-comments.mjs dist",
|
|
90
90
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
91
91
|
"test": "vitest run --passWithNoTests",
|
|
92
92
|
"coverage": "vitest run --coverage.enabled",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"url": "git+https://github.com/ofri-peretz/burgee.git",
|
|
98
98
|
"directory": "packages/flagstaff"
|
|
99
99
|
},
|
|
100
|
-
"homepage": "https://
|
|
100
|
+
"homepage": "https://burgee.interlace.tools/docs/packages/flagstaff",
|
|
101
101
|
"bugs": {
|
|
102
102
|
"url": "https://github.com/ofri-peretz/burgee/issues"
|
|
103
103
|
},
|
|
@@ -112,13 +112,19 @@
|
|
|
112
112
|
"log-update",
|
|
113
113
|
"boxen",
|
|
114
114
|
"cli-table3",
|
|
115
|
-
"plugins"
|
|
115
|
+
"plugins",
|
|
116
|
+
"plugin",
|
|
117
|
+
"extensible",
|
|
118
|
+
"agent",
|
|
119
|
+
"ai-agent",
|
|
120
|
+
"non-tty",
|
|
121
|
+
"json"
|
|
116
122
|
],
|
|
117
123
|
"dependencies": {
|
|
118
|
-
"closeout": "^0.
|
|
119
|
-
"linegauge": "^0.4.
|
|
120
|
-
"paratext": "^0.5.
|
|
121
|
-
"roundel": "^0.4.
|
|
124
|
+
"closeout": "^0.4.0",
|
|
125
|
+
"linegauge": "^0.4.3",
|
|
126
|
+
"paratext": "^0.5.3",
|
|
127
|
+
"roundel": "^0.4.1"
|
|
122
128
|
},
|
|
123
129
|
"devDependencies": {
|
|
124
130
|
"fast-check": "^4.10.1",
|