burgee 0.0.0 → 0.2.0

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.
Files changed (114) hide show
  1. package/README.md +28 -1
  2. package/dist/agent.d.ts +19 -0
  3. package/dist/agent.js +25 -0
  4. package/dist/brand.d.ts +186 -0
  5. package/dist/brand.js +232 -0
  6. package/dist/cli.d.ts +51 -0
  7. package/dist/cli.js +105 -0
  8. package/dist/commander-argument.d.ts +22 -0
  9. package/dist/commander-argument.js +72 -0
  10. package/dist/commander-command.d.ts +345 -0
  11. package/dist/commander-command.js +1605 -0
  12. package/dist/commander-error.d.ts +10 -0
  13. package/dist/commander-error.js +20 -0
  14. package/dist/commander-help.d.ts +67 -0
  15. package/dist/commander-help.js +319 -0
  16. package/dist/commander-option.d.ts +58 -0
  17. package/dist/commander-option.js +164 -0
  18. package/dist/commander-suggest.d.ts +2 -0
  19. package/dist/commander-suggest.js +59 -0
  20. package/dist/commander.d.ts +18 -0
  21. package/dist/commander.js +12 -0
  22. package/dist/completions.d.ts +39 -0
  23. package/dist/completions.js +224 -0
  24. package/dist/config.d.ts +28 -0
  25. package/dist/config.js +98 -0
  26. package/dist/contrast.d.ts +70 -0
  27. package/dist/contrast.js +93 -0
  28. package/dist/execute.d.ts +97 -0
  29. package/dist/execute.js +433 -0
  30. package/dist/exit-code.d.ts +18 -0
  31. package/dist/exit-code.js +12 -0
  32. package/dist/help.d.ts +22 -0
  33. package/dist/help.js +158 -0
  34. package/dist/index.d.ts +16 -1
  35. package/dist/index.js +9 -2
  36. package/dist/manifest.d.ts +192 -0
  37. package/dist/manifest.js +55 -0
  38. package/dist/mcp.d.ts +40 -0
  39. package/dist/mcp.js +111 -0
  40. package/dist/names.d.ts +5 -0
  41. package/dist/names.js +6 -0
  42. package/dist/pkg.d.ts +5 -0
  43. package/dist/pkg.js +21 -0
  44. package/dist/precedence.d.ts +55 -0
  45. package/dist/precedence.js +100 -0
  46. package/dist/runtime.d.ts +27 -0
  47. package/dist/runtime.js +17 -0
  48. package/dist/schema.d.ts +71 -0
  49. package/dist/schema.js +108 -0
  50. package/dist/testing-helpers.d.ts +62 -0
  51. package/dist/testing-helpers.js +110 -0
  52. package/dist/testing.d.ts +10 -0
  53. package/dist/testing.js +3 -0
  54. package/dist/validate.d.ts +27 -0
  55. package/dist/validate.js +133 -0
  56. package/dist/yargs-burgee.d.ts +50 -0
  57. package/dist/yargs-burgee.js +104 -0
  58. package/dist/yargs-cliui.d.ts +56 -0
  59. package/dist/yargs-cliui.js +421 -0
  60. package/dist/yargs-command.d.ts +82 -0
  61. package/dist/yargs-command.js +414 -0
  62. package/dist/yargs-completion.d.ts +41 -0
  63. package/dist/yargs-completion.js +271 -0
  64. package/dist/yargs-factory.d.ts +193 -0
  65. package/dist/yargs-factory.js +1606 -0
  66. package/dist/yargs-helpers.d.ts +6 -0
  67. package/dist/yargs-helpers.js +2 -0
  68. package/dist/yargs-middleware.d.ts +32 -0
  69. package/dist/yargs-middleware.js +81 -0
  70. package/dist/yargs-parser.d.ts +41 -0
  71. package/dist/yargs-parser.js +929 -0
  72. package/dist/yargs-shim.d.ts +54 -0
  73. package/dist/yargs-shim.js +84 -0
  74. package/dist/yargs-usage.d.ts +42 -0
  75. package/dist/yargs-usage.js +479 -0
  76. package/dist/yargs-utils.d.ts +33 -0
  77. package/dist/yargs-utils.js +209 -0
  78. package/dist/yargs-validation.d.ts +26 -0
  79. package/dist/yargs-validation.js +261 -0
  80. package/dist/yargs-y18n.d.ts +21 -0
  81. package/dist/yargs-y18n.js +117 -0
  82. package/dist/yargs.d.ts +6 -0
  83. package/dist/yargs.js +8 -0
  84. package/locales/be.json +46 -0
  85. package/locales/cs.json +51 -0
  86. package/locales/de.json +46 -0
  87. package/locales/en.json +55 -0
  88. package/locales/es.json +46 -0
  89. package/locales/fi.json +49 -0
  90. package/locales/fr.json +53 -0
  91. package/locales/he.json +55 -0
  92. package/locales/hi.json +49 -0
  93. package/locales/hu.json +46 -0
  94. package/locales/id.json +50 -0
  95. package/locales/it.json +46 -0
  96. package/locales/ja.json +51 -0
  97. package/locales/ka.json +55 -0
  98. package/locales/ko.json +49 -0
  99. package/locales/nb.json +44 -0
  100. package/locales/nl.json +49 -0
  101. package/locales/nn.json +44 -0
  102. package/locales/pirate.json +13 -0
  103. package/locales/pl.json +49 -0
  104. package/locales/pt.json +45 -0
  105. package/locales/pt_BR.json +48 -0
  106. package/locales/ru.json +51 -0
  107. package/locales/th.json +46 -0
  108. package/locales/tr.json +48 -0
  109. package/locales/uk_UA.json +51 -0
  110. package/locales/uz.json +52 -0
  111. package/locales/zh_CN.json +48 -0
  112. package/locales/zh_TW.json +51 -0
  113. package/package.json +61 -8
  114. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -8,6 +8,33 @@ belongs to — a flag of identity, not of instruction. That is what this framewo
8
8
  a command-line program: a command declares itself once, and every surface is that
9
9
  declaration read by a different reader.
10
10
 
11
+ ```js
12
+ // cli.mjs — the whole CLI
13
+ import { defineCommand, run } from 'burgee';
14
+
15
+ run(defineCommand({
16
+ name: 'greet',
17
+ description: 'Greet someone by name',
18
+ options: { name: { type: 'string', required: true, description: 'who to greet' } },
19
+ run: ({ options }) => ({ greeting: `hello, ${options.name}` }),
20
+ }));
21
+ ```
22
+
23
+ ```console
24
+ $ node cli.mjs --name ada
25
+ greeting: hello, ada
26
+
27
+ $ node cli.mjs --json --name ada
28
+ {"ok":true,"data":{"greeting":"hello, ada"}}
29
+
30
+ $ node cli.mjs # exit 2
31
+ error: missing required option --name
32
+ hint: pass --name <value>
33
+ ```
34
+
35
+ One file. No build step, no config file, no directory convention. A test enforces
36
+ that on every commit.
37
+
11
38
  ```
12
39
  defineCommand() ──▶ manifest ──┬──▶ human help
13
40
  ├──▶ --json one stable envelope
@@ -30,6 +57,6 @@ It stays a library you import in one file: no build step, no config, no director
30
57
  convention, no scaffold. A test enforces that.
31
58
 
32
59
  Roadmap, architecture and the 79-requirement floor:
33
- <https://github.com/ofri-peretz/cli>
60
+ <https://github.com/ofri-peretz/burgee>
34
61
 
35
62
  MIT © Interlace
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Agent detection, not just `isTTY` (N12). An agent may well have a terminal; what it
3
+ * does not have is a person. Non-interactive is the default under a detected agent;
4
+ * `FORCE_TTY=1` overrides. The variables mirror what `@vercel/detect-agent` probes; the
5
+ * list is data, and `AI_AGENT` is the generic escape hatch any agent can set.
6
+ */
7
+ export interface AgentProbe {
8
+ /** Environment variable whose presence names the agent. */
9
+ variable: string;
10
+ agent: string;
11
+ }
12
+ export declare const AGENT_PROBES: readonly AgentProbe[];
13
+ export interface Detection {
14
+ /** The agent named by the environment, if any; `AI_AGENT`'s own value when it names one. */
15
+ agent?: string;
16
+ /** Prompts and other blocking interaction are allowed. */
17
+ interactive: boolean;
18
+ }
19
+ export declare function detectAgent(env: Record<string, string | undefined>, tty: boolean, probes?: readonly AgentProbe[]): Detection;
package/dist/agent.js ADDED
@@ -0,0 +1,25 @@
1
+ export const AGENT_PROBES = [
2
+ { variable: 'AI_AGENT', agent: 'generic' },
3
+ { variable: 'CLAUDECODE', agent: 'claude-code' },
4
+ { variable: 'CURSOR_AGENT', agent: 'cursor' },
5
+ { variable: 'CODEX_THREAD_ID', agent: 'codex' },
6
+ { variable: 'GEMINI_CLI', agent: 'gemini' },
7
+ ];
8
+ function agentName(hit, value) {
9
+ if (hit.variable !== 'AI_AGENT' || value === '1')
10
+ return hit.agent;
11
+ return value;
12
+ }
13
+ export function detectAgent(env, tty, probes = AGENT_PROBES) {
14
+ let agent;
15
+ for (const probe of probes) {
16
+ const value = env[probe.variable];
17
+ if (value !== undefined && value !== '') {
18
+ agent = agentName(probe, value);
19
+ break;
20
+ }
21
+ }
22
+ const forced = env['FORCE_TTY'] === '1';
23
+ const interactive = forced || (tty && agent === undefined);
24
+ return agent === undefined ? { interactive } : { agent, interactive };
25
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * burgee/brand — a brand declares itself once; every identity surface is that
3
+ * declaration read by a different reader.
4
+ *
5
+ * The same thesis as the rest of the package, applied to identity instead of
6
+ * argv. You declare a field and a mark; out come the flag, the favicon, the OG
7
+ * card and the article cover, every one a projection of that declaration, so
8
+ * none of them can drift from the others.
9
+ *
10
+ * THE FLAG. A burgee is the swallowtail flag a boat flies to say which club it
11
+ * belongs to — a flag of identity, not of instruction. This one is composed the
12
+ * way real club burgees are: a field, and one charge upon it. The field is a
13
+ * gradient run BACKWARDS along the axis the two Interlace bars are stacked on,
14
+ * so the leading bar sits over the following bar's colour and the reverse. The
15
+ * charge is the Interlace mark itself.
16
+ *
17
+ * WHY THE MIDPOINT STOP IS LOAD-BEARING. Deep rock on deep juniper measures
18
+ * 1.16:1 — invisible. The middle stop drops the field to near-black exactly
19
+ * where the charge sits, lifting the two bars to 3.50:1 and 3.02:1, both
20
+ * clearing the 3:1 floor WCAG sets for a graphical object. Remove that stop and
21
+ * the mark disappears. It is contrast, not decoration.
22
+ *
23
+ * NOT in the core entry point. `import { defineCommand } from "burgee"` must
24
+ * stay one import of one file with no build step; this is a separate subpath and
25
+ * costs that path nothing.
26
+ *
27
+ * DETERMINISM. No timestamps, no randomness, coordinates rounded to 2dp. The one
28
+ * id in the output — a gradient cannot be anonymous — is derived from the
29
+ * declaration itself, so the same brand always produces the same bytes and two
30
+ * different brands can share a page without colliding.
31
+ */
32
+ /** A point in the mark space. */
33
+ export type Point = readonly [number, number];
34
+ export declare const BURGEE_FLAG: readonly Point[];
35
+ export declare const BURGEE_ANGLE: number;
36
+ /** The charge occupies this fraction of the flag, and sits here within it. */
37
+ export declare const CHARGE: {
38
+ readonly scale: 0.4;
39
+ readonly x: 42;
40
+ readonly y: 50;
41
+ };
42
+ /**
43
+ * The gradient axis: the direction the two bars are stacked on, traversed
44
+ * backwards — hoist-top to fly-bottom, in mark-space coordinates.
45
+ */
46
+ export declare const FIELD_AXIS: {
47
+ readonly x1: 25;
48
+ readonly y1: 6.7;
49
+ readonly x2: 75;
50
+ readonly y2: 93.3;
51
+ };
52
+ /** One gradient stop: how far along the axis, and what colour. */
53
+ export interface FieldStop {
54
+ offset: number;
55
+ color: string;
56
+ }
57
+ /** One band of the outline. */
58
+ export interface Bordure {
59
+ color: string;
60
+ /** Visible thickness, in mark-space units. */
61
+ width: number;
62
+ }
63
+ export interface BurgeeColors {
64
+ /** Leading bar, hoist side. */
65
+ lead: string;
66
+ /** Following bar, fly side. */
67
+ follow: string;
68
+ }
69
+ export interface BurgeeBrand {
70
+ /** Accessible name for the flag. Without one the flag is decorative. */
71
+ name?: string;
72
+ /**
73
+ * The charge, as a two-colour bar pair. Ignored when {@link BurgeeBrand.charge}
74
+ * is given — that is the escape hatch for a CLI bringing its own glyph.
75
+ */
76
+ mark: BurgeeColors;
77
+ /**
78
+ * Your own charge instead of the bars: SVG markup drawn in a 0 0 100 100 box,
79
+ * which burgee places and scales for you. Everything else — the swallowtail,
80
+ * the reversed field, the sizes — still comes from this one declaration.
81
+ *
82
+ * The markup is emitted verbatim, so it is yours to trust: this runs at build
83
+ * time on a file you wrote, not on anything a user supplies at runtime.
84
+ */
85
+ charge?: string;
86
+ /**
87
+ * The field, as gradient stops along {@link FIELD_AXIS}. One stop is a flat
88
+ * field. Keep a dark stop under the charge or the mark will not read.
89
+ */
90
+ field: readonly FieldStop[];
91
+ /**
92
+ * The outline, outermost band first.
93
+ *
94
+ * One band is enough when you control the ground. Two is what you want when
95
+ * you do not: no single flat colour clears 3:1 against both a near-black and a
96
+ * white page, so a dark outer band and a light inner band are given, and
97
+ * whichever one the ground does not match is the one carrying the silhouette.
98
+ * That is one asset that holds its outline anywhere — which a favicon, having
99
+ * no stylesheet to read a theme from, actually needs.
100
+ *
101
+ * `width` is the visible thickness of each band in mark-space units.
102
+ */
103
+ bordure?: Bordure | readonly Bordure[];
104
+ }
105
+ /** The midpoint a field falls through when none is given. Near-black. */
106
+ export declare const DEFAULT_GROUND = "#0a0a0a";
107
+ /**
108
+ * The field two colours imply.
109
+ *
110
+ * Reversed on purpose: the leading colour goes at the FAR end, so the leading
111
+ * half of the charge sits against the following colour and the reverse. Through
112
+ * a dark midpoint, because the charge sits at the centre and two saturated
113
+ * colours of similar weight cannot be told apart — the reason this is a default
114
+ * rather than something each caller re-derives.
115
+ */
116
+ export declare function opposedField(colors: BurgeeColors, ground?: string): FieldStop[];
117
+ /** The flag silhouette, as SVG path data. */
118
+ export declare function burgeeFlagPath(): string;
119
+ /**
120
+ * A stable id for the field gradient, derived from the declaration.
121
+ *
122
+ * A gradient is the one thing in SVG that cannot be anonymous. Deriving the id
123
+ * from the brand keeps output byte-identical across runs, and keeps two
124
+ * different brands from colliding when they share a page. Two instances of the
125
+ * SAME brand do share an id, which is harmless — the definitions are identical —
126
+ * and a React caller can pass its own `useId()` value instead.
127
+ */
128
+ export declare function fieldId(brand: BurgeeBrand): string;
129
+ /**
130
+ * Where the charge sits, as an SVG transform. Exported because consumers that
131
+ * hand-write the flag (a React component, say) must place it identically, and
132
+ * recomputing it at the call site is how the two drift apart.
133
+ */
134
+ export declare function chargeTransform(scale?: number): string;
135
+ /** The angle the charge is rotated by, as an SVG transform. */
136
+ export declare function chargeRotation(): string;
137
+ /** Place any charge markup where the charge belongs, at the charge's scale. */
138
+ export declare function placeCharge(markup: string, scale?: number): string;
139
+ /** The two Interlace bars, rotated and placed as the charge. */
140
+ export declare function chargeGroup(colors: BurgeeColors, scale?: number): string;
141
+ /** Field, charge and optional bordure — everything inside the viewBox. */
142
+ export declare function burgeeBody(brand: BurgeeBrand, id?: string): string;
143
+ export interface CardOptions {
144
+ width?: number;
145
+ height?: number;
146
+ /** Large line. Defaults to the brand name. */
147
+ title?: string;
148
+ /** Small line under it; wraps. */
149
+ subtitle?: string;
150
+ theme?: 'light' | 'dark';
151
+ background?: string;
152
+ foreground?: string;
153
+ muted?: string;
154
+ }
155
+ /** Everything one brand declaration projects into. */
156
+ export interface Burgee {
157
+ /** The flag alone, square, at any size. */
158
+ flag(size?: number): string;
159
+ /** Favicon master. One file serves both themes — the flag carries its own field. */
160
+ favicon(size?: number): string;
161
+ /** Social card, 1200×630. */
162
+ og(options?: CardOptions): string;
163
+ /** Article cover, 1000×420. */
164
+ cover(options?: CardOptions): string;
165
+ /** Flag and wordmark, laid out horizontally. */
166
+ lockup(options?: CardOptions): string;
167
+ /** The gradient id this brand emits, for callers that need to match it. */
168
+ fieldId(): string;
169
+ }
170
+ /**
171
+ * Declare a brand. Everything on the returned object is a projection of it.
172
+ *
173
+ * ```js
174
+ * const brand = defineBurgee({
175
+ * name: 'burgee',
176
+ * mark: { lead: '#a84c17', follow: '#0a6b47' },
177
+ * field: [
178
+ * { offset: 0, color: '#0a6b47' },
179
+ * { offset: 0.5, color: '#0a0a0a' },
180
+ * { offset: 1, color: '#a84c17' },
181
+ * ],
182
+ * });
183
+ * writeFileSync('icon.svg', brand.favicon());
184
+ * ```
185
+ */
186
+ export declare function defineBurgee(brand: BurgeeBrand): Burgee;
package/dist/brand.js ADDED
@@ -0,0 +1,232 @@
1
+ const MARK = { SPAN: 100, CENTRE: 50 };
2
+ const INTEGER_EPSILON = 0.005;
3
+ const HOIST = { x: 10, top: 20, bottom: 80 };
4
+ const FLY = { x: 94, top: 32, bottom: 68 };
5
+ const NOTCH = { x: 66, y: 50 };
6
+ export const BURGEE_FLAG = [
7
+ [HOIST.x, HOIST.top],
8
+ [FLY.x, FLY.top],
9
+ [NOTCH.x, NOTCH.y],
10
+ [FLY.x, FLY.bottom],
11
+ [HOIST.x, HOIST.bottom],
12
+ ];
13
+ const BAR = { width: 52, height: 24, radius: 12 };
14
+ const LEAD_BAR = { x: 15, y: 24 };
15
+ const FOLLOW_BAR = { x: 33, y: 52 };
16
+ const INTERLACE_ROTATION_DEGREES = 30;
17
+ export const BURGEE_ANGLE = -INTERLACE_ROTATION_DEGREES;
18
+ export const CHARGE = { scale: 0.4, x: 42, y: 50 };
19
+ export const FIELD_AXIS = { x1: 25, y1: 6.7, x2: 75, y2: 93.3 };
20
+ export const DEFAULT_GROUND = '#0a0a0a';
21
+ const MIDPOINT = 0.5;
22
+ export function opposedField(colors, ground = DEFAULT_GROUND) {
23
+ return [
24
+ { offset: 0, color: colors.follow },
25
+ { offset: MIDPOINT, color: ground },
26
+ { offset: 1, color: colors.lead },
27
+ ];
28
+ }
29
+ function round(n) {
30
+ return Math.abs(n - Math.round(n)) < INTEGER_EPSILON ? String(Math.round(n)) : n.toFixed(2);
31
+ }
32
+ function toPath(points) {
33
+ return `M${points.map(([x, y]) => `${round(x)} ${round(y)}`).join(' L')} Z`;
34
+ }
35
+ export function burgeeFlagPath() {
36
+ return toPath(BURGEE_FLAG);
37
+ }
38
+ function escape(s) {
39
+ return s
40
+ .replace(/&/g, '&amp;')
41
+ .replace(/</g, '&lt;')
42
+ .replace(/>/g, '&gt;')
43
+ .replace(/"/g, '&quot;')
44
+ .replace(/'/g, '&#39;');
45
+ }
46
+ const HASH_SEED = 5381;
47
+ const HASH_SHIFT = 5;
48
+ const HASH_RADIX = 36;
49
+ export function fieldId(brand) {
50
+ const source = JSON.stringify([brand.field, brand.mark, brand.bordure, brand.charge]);
51
+ let h = HASH_SEED;
52
+ for (let i = 0; i < source.length; i++) {
53
+ h = ((h << HASH_SHIFT) + h + (source.codePointAt(i) ?? 0)) >>> 0;
54
+ }
55
+ return `burgee-${h.toString(HASH_RADIX)}`;
56
+ }
57
+ function gradient(brand, id) {
58
+ const stops = brand.field
59
+ .map((s) => `<stop offset="${round(s.offset)}" stop-color="${s.color}"/>`)
60
+ .join('');
61
+ return (`<linearGradient id="${id}" gradientUnits="userSpaceOnUse"` +
62
+ ` x1="${FIELD_AXIS.x1}" y1="${FIELD_AXIS.y1}" x2="${FIELD_AXIS.x2}" y2="${FIELD_AXIS.y2}">` +
63
+ `${stops}</linearGradient>`);
64
+ }
65
+ export function chargeTransform(scale = CHARGE.scale) {
66
+ const tx = round(CHARGE.x - MARK.CENTRE * scale);
67
+ const ty = round(CHARGE.y - MARK.CENTRE * scale);
68
+ return `translate(${tx} ${ty}) scale(${round(scale)})`;
69
+ }
70
+ export function chargeRotation() {
71
+ return `rotate(${BURGEE_ANGLE} ${MARK.CENTRE} ${MARK.CENTRE})`;
72
+ }
73
+ export function placeCharge(markup, scale = CHARGE.scale) {
74
+ return `<g transform="${chargeTransform(scale)}">${markup}</g>`;
75
+ }
76
+ export function chargeGroup(colors, scale = CHARGE.scale) {
77
+ const bar = (at, fill) => `<rect x="${at.x}" y="${at.y}" width="${BAR.width}" height="${BAR.height}"` +
78
+ ` rx="${BAR.radius}" fill="${fill}"/>`;
79
+ return (`<g transform="${chargeTransform(scale)}">` +
80
+ `<g transform="${chargeRotation()}">` +
81
+ `${bar(LEAD_BAR, colors.lead)}${bar(FOLLOW_BAR, colors.follow)}</g></g>`);
82
+ }
83
+ function bordureBands(brand) {
84
+ if (brand.bordure === undefined)
85
+ return '';
86
+ const bands = Array.isArray(brand.bordure)
87
+ ? [...brand.bordure]
88
+ : [brand.bordure];
89
+ if (bands.length === 0)
90
+ return '';
91
+ let total = bands.reduce((sum, band) => sum + band.width, 0);
92
+ const path = burgeeFlagPath();
93
+ const drawn = [];
94
+ for (const band of bands) {
95
+ drawn.push(`<path d="${path}" fill="none" stroke="${band.color}"` +
96
+ ` stroke-width="${round(total * 2)}" stroke-linejoin="round"/>`);
97
+ total -= band.width;
98
+ }
99
+ return drawn.join('');
100
+ }
101
+ export function burgeeBody(brand, id = fieldId(brand)) {
102
+ const charge = brand.charge === undefined ? chargeGroup(brand.mark) : placeCharge(brand.charge);
103
+ return (`${gradient(brand, id)}${bordureBands(brand)}` +
104
+ `<path d="${burgeeFlagPath()}" fill="url(#${id})"/>${charge}`);
105
+ }
106
+ const FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';
107
+ const TRACKING_TIGHTEN = 0.03;
108
+ const LAYOUT = {
109
+ GAP: 0.28,
110
+ TITLE: 0.42,
111
+ SUBTITLE: 0.13,
112
+ ADVANCE: 0.62,
113
+ TITLE_CENTRE: 0.36,
114
+ SUBTITLE_LEAD: 1.6,
115
+ LINE_HEIGHT: 1.4,
116
+ TEXT_COLUMN: 0.46,
117
+ TRACKING: -TRACKING_TIGHTEN,
118
+ };
119
+ const SIZES = {
120
+ OG: { width: 1200, height: 630, mark: 260 },
121
+ COVER: { width: 1000, height: 420, mark: 180 },
122
+ LOCKUP: { width: 480, height: 120, mark: 84 },
123
+ FAVICON: 512,
124
+ };
125
+ const GROUND = {
126
+ dark: { background: '#12201b', foreground: '#f0f3f6', muted: '#9aada5' },
127
+ light: { background: '#efe9dd', foreground: '#0a0a0a', muted: '#5c6058' },
128
+ };
129
+ function flagGroup(brand, x, y, size) {
130
+ const scale = size / MARK.SPAN;
131
+ return (`<g transform="translate(${round(x)} ${round(y)}) scale(${round(scale)})">` +
132
+ `${burgeeBody(brand)}</g>`);
133
+ }
134
+ function wrap(text, columns) {
135
+ if (text === '')
136
+ return [];
137
+ const lines = [];
138
+ let line = '';
139
+ for (const word of text.split(/\s+/)) {
140
+ const candidate = line === '' ? word : `${line} ${word}`;
141
+ if (candidate.length > columns && line !== '') {
142
+ lines.push(line);
143
+ line = word;
144
+ }
145
+ else {
146
+ line = candidate;
147
+ }
148
+ }
149
+ if (line !== '')
150
+ lines.push(line);
151
+ return lines;
152
+ }
153
+ const advance = (chars, size) => chars * size * LAYOUT.ADVANCE;
154
+ function textElement({ x, y, size, fill, bold, text }) {
155
+ const weight = bold ? ` font-weight="700" letter-spacing="${round(size * LAYOUT.TRACKING)}"` : '';
156
+ return (`<text x="${round(x)}" y="${round(y)}" font-family="${FONT}" font-size="${round(size)}"` +
157
+ `${weight} fill="${fill}">${escape(text)}</text>`);
158
+ }
159
+ function renderCard(brand, options, size) {
160
+ const theme = options.theme ?? 'dark';
161
+ const ground = GROUND[theme];
162
+ const background = options.background ?? ground.background;
163
+ const foreground = options.foreground ?? ground.foreground;
164
+ const muted = options.muted ?? ground.muted;
165
+ const width = options.width ?? size.width;
166
+ const height = options.height ?? size.height;
167
+ const title = options.title ?? brand.name ?? '';
168
+ const subtitle = options.subtitle ?? '';
169
+ const gap = size.mark * LAYOUT.GAP;
170
+ const titleSize = size.mark * LAYOUT.TITLE;
171
+ const subSize = size.mark * LAYOUT.SUBTITLE;
172
+ const lineHeight = subSize * LAYOUT.LINE_HEIGHT;
173
+ const columns = Math.floor((width * LAYOUT.TEXT_COLUMN) / (subSize * LAYOUT.ADVANCE));
174
+ const lines = wrap(subtitle, columns);
175
+ const textWidth = Math.max(advance(title.length, titleSize), ...lines.map((line) => advance(line.length, subSize)));
176
+ const left = (width - (size.mark + gap + textWidth)) / 2;
177
+ const centreY = height / 2;
178
+ const textX = left + size.mark + gap;
179
+ const blockHeight = titleSize +
180
+ (lines.length === 0 ? 0 : subSize * LAYOUT.SUBTITLE_LEAD + (lines.length - 1) * lineHeight);
181
+ const titleY = lines.length === 0
182
+ ? centreY + titleSize * LAYOUT.TITLE_CENTRE
183
+ : centreY - blockHeight / 2 + titleSize;
184
+ const label = escape([title, subtitle].filter(Boolean).join(' — ') || brand.name || 'burgee');
185
+ const parts = [
186
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"` +
187
+ ` viewBox="0 0 ${width} ${height}" role="img" aria-label="${label}">`,
188
+ `<rect width="${width}" height="${height}" fill="${background}"/>`,
189
+ flagGroup(brand, left, centreY - size.mark / 2, size.mark),
190
+ ];
191
+ if (title) {
192
+ parts.push(textElement({
193
+ x: textX,
194
+ y: titleY,
195
+ size: titleSize,
196
+ fill: foreground,
197
+ bold: true,
198
+ text: title,
199
+ }));
200
+ }
201
+ lines.forEach((line, i) => {
202
+ parts.push(textElement({
203
+ x: textX,
204
+ y: titleY + subSize * LAYOUT.SUBTITLE_LEAD + i * lineHeight,
205
+ size: subSize,
206
+ fill: muted,
207
+ bold: false,
208
+ text: line,
209
+ }));
210
+ });
211
+ parts.push('</svg>');
212
+ return parts.join('\n');
213
+ }
214
+ function renderFlag(brand, size) {
215
+ const label = brand.name ? ` role="img" aria-label="${escape(brand.name)}"` : ' aria-hidden="true"';
216
+ return [
217
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${MARK.SPAN} ${MARK.SPAN}"` +
218
+ ` width="${size}" height="${size}"${label}>`,
219
+ ` ${burgeeBody(brand)}`,
220
+ '</svg>',
221
+ ].join('\n');
222
+ }
223
+ export function defineBurgee(brand) {
224
+ return {
225
+ flag: (size = MARK.SPAN) => renderFlag(brand, size),
226
+ favicon: (size = SIZES.FAVICON) => renderFlag(brand, size),
227
+ og: (options = {}) => renderCard(brand, options, SIZES.OG),
228
+ cover: (options = {}) => renderCard(brand, options, SIZES.COVER),
229
+ lockup: (options = {}) => renderCard(brand, options, SIZES.LOCKUP),
230
+ fieldId: () => fieldId(brand),
231
+ };
232
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ export declare const brandCommand: import("./execute.js").Command<{
2
+ readonly lead: {
3
+ readonly type: "string";
4
+ readonly required: true;
5
+ readonly description: "leading colour, hex. Your primary; it leads the charge upper-left";
6
+ };
7
+ readonly follow: {
8
+ readonly type: "string";
9
+ readonly required: true;
10
+ readonly description: "following colour, hex. Your secondary; it follows lower-right";
11
+ };
12
+ readonly name: {
13
+ readonly type: "string";
14
+ readonly description: "brand name, used as the accessible label and card title";
15
+ };
16
+ readonly ground: {
17
+ readonly type: "string";
18
+ readonly default: "#0a0a0a";
19
+ readonly description: "the field’s dark midpoint, which is what keeps the charge legible";
20
+ };
21
+ readonly charge: {
22
+ readonly type: "string";
23
+ readonly description: "path to an SVG whose contents replace the bars, drawn in a 0 0 100 100 box";
24
+ };
25
+ readonly bordure: {
26
+ readonly type: "string";
27
+ readonly description: "outline colour, hex. Omit for no outline";
28
+ };
29
+ readonly 'bordure-width': {
30
+ readonly type: "string";
31
+ readonly default: "1.5";
32
+ readonly description: "outline width";
33
+ };
34
+ readonly tagline: {
35
+ readonly type: "string";
36
+ readonly description: "one line under the name on the card and cover";
37
+ };
38
+ readonly out: {
39
+ readonly type: "string";
40
+ readonly description: "directory to write into. Omit to print the flag only";
41
+ };
42
+ readonly on: {
43
+ readonly type: "string";
44
+ readonly description: "page colour(s) the flag will fly on, comma separated. Checked for contrast";
45
+ };
46
+ readonly 'allow-low-contrast': {
47
+ readonly type: "boolean";
48
+ readonly description: "emit anyway when a contrast check fails. Says so in the output";
49
+ };
50
+ }>;
51
+ export declare const program: import("./manifest.js").Manifest;
package/dist/cli.js ADDED
@@ -0,0 +1,105 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { DEFAULT_GROUND, defineBurgee, opposedField } from './brand.js';
4
+ import { auditBurgee, report } from './contrast.js';
5
+ import { defineCommand, defineProgram, run } from './execute.js';
6
+ const MASTER = 512;
7
+ const CONTRAST_HINT = 'hint: darken the --ground stop under the charge, or pass --allow-low-contrast';
8
+ const DEFAULT_BORDURE_WIDTH = '1.5';
9
+ function surfaces(brand, tagline) {
10
+ const burgee = defineBurgee(brand);
11
+ const subtitle = tagline === '' ? {} : { subtitle: tagline };
12
+ return [
13
+ { file: 'flag.svg', svg: burgee.flag(MASTER) },
14
+ { file: 'icon.svg', svg: burgee.favicon() },
15
+ { file: 'og.svg', svg: burgee.og(subtitle) },
16
+ { file: 'cover.svg', svg: burgee.cover(subtitle) },
17
+ { file: 'lockup.svg', svg: burgee.lockup({ theme: 'dark' }) },
18
+ { file: 'lockup-light.svg', svg: burgee.lockup({ theme: 'light' }) },
19
+ ];
20
+ }
21
+ export const brandCommand = defineCommand({
22
+ name: 'brand',
23
+ description: 'Generate a burgee — flag, favicon, social card, cover and lockup — from two colours',
24
+ options: {
25
+ lead: {
26
+ type: 'string',
27
+ required: true,
28
+ description: 'leading colour, hex. Your primary; it leads the charge upper-left',
29
+ },
30
+ follow: {
31
+ type: 'string',
32
+ required: true,
33
+ description: 'following colour, hex. Your secondary; it follows lower-right',
34
+ },
35
+ name: { type: 'string', description: 'brand name, used as the accessible label and card title' },
36
+ ground: {
37
+ type: 'string',
38
+ default: DEFAULT_GROUND,
39
+ description: 'the field’s dark midpoint, which is what keeps the charge legible',
40
+ },
41
+ charge: {
42
+ type: 'string',
43
+ description: 'path to an SVG whose contents replace the bars, drawn in a 0 0 100 100 box',
44
+ },
45
+ bordure: { type: 'string', description: 'outline colour, hex. Omit for no outline' },
46
+ 'bordure-width': { type: 'string', default: DEFAULT_BORDURE_WIDTH, description: 'outline width' },
47
+ tagline: { type: 'string', description: 'one line under the name on the card and cover' },
48
+ out: { type: 'string', description: 'directory to write into. Omit to print the flag only' },
49
+ 'on': {
50
+ type: 'string',
51
+ description: 'page colour(s) the flag will fly on, comma separated. Checked for contrast',
52
+ },
53
+ 'allow-low-contrast': {
54
+ type: 'boolean',
55
+ description: 'emit anyway when a contrast check fails. Says so in the output',
56
+ },
57
+ },
58
+ run: ({ options }) => {
59
+ const lead = options.lead ?? '';
60
+ const follow = options.follow ?? '';
61
+ const colors = { lead, follow };
62
+ const charge = options.charge === undefined
63
+ ? {}
64
+ : { charge: readFileSync(options.charge, 'utf8').replace(/<\/?svg[^>]*>/g, '').trim() };
65
+ const bordure = options.bordure === undefined
66
+ ? {}
67
+ : {
68
+ bordure: {
69
+ color: options.bordure,
70
+ width: Number(options['bordure-width'] ?? DEFAULT_BORDURE_WIDTH),
71
+ },
72
+ };
73
+ const brand = {
74
+ ...(options.name === undefined ? {} : { name: options.name }),
75
+ mark: colors,
76
+ field: opposedField(colors, options.ground ?? DEFAULT_GROUND),
77
+ ...charge,
78
+ ...bordure,
79
+ };
80
+ const grounds = (options.on ?? '')
81
+ .split(',')
82
+ .map((g) => g.trim())
83
+ .filter((g) => g !== '');
84
+ const findings = auditBurgee(brand, grounds);
85
+ const failed = findings.filter((f) => !f.passes);
86
+ if (failed.length > 0 && options['allow-low-contrast'] !== true) {
87
+ throw new Error(`contrast below WCAG AA:\n${report(failed)}\n${CONTRAST_HINT}`);
88
+ }
89
+ const written = surfaces(brand, options.tagline ?? '');
90
+ const contrast = findings.map((f) => ({ what: f.what, ratio: f.ratio, passes: f.passes }));
91
+ if (options.out === undefined) {
92
+ return { flag: defineBurgee(brand).flag(MASTER), files: [], contrast };
93
+ }
94
+ mkdirSync(options.out, { recursive: true });
95
+ for (const s of written)
96
+ writeFileSync(join(options.out, s.file), `${s.svg}\n`);
97
+ return { out: options.out, files: written.map((s) => s.file), contrast };
98
+ },
99
+ });
100
+ export const program = defineProgram({
101
+ name: 'burgee',
102
+ description: 'The agent-native CLI framework, and the tools that come with it',
103
+ commands: [brandCommand],
104
+ });
105
+ run(program);
@@ -0,0 +1,22 @@
1
+ export type ParseArg = (value: string, previous: unknown) => unknown;
2
+ export declare class Argument {
3
+ description: string;
4
+ variadic: boolean;
5
+ parseArg: ParseArg | undefined;
6
+ defaultValue: unknown;
7
+ defaultValueDescription: string | undefined;
8
+ argChoices: string[] | undefined;
9
+ required: boolean;
10
+ _name: string;
11
+ /** `<required>`, `[optional]`, bare = required; a trailing `...` makes it variadic. */
12
+ constructor(name: string, description?: string);
13
+ name(): string;
14
+ _collectValue(value: unknown, previous: unknown): unknown[];
15
+ default(value: unknown, description?: string): this;
16
+ argParser(fn?: ParseArg): this;
17
+ choices(values: readonly string[]): this;
18
+ argRequired(): this;
19
+ argOptional(): this;
20
+ }
21
+ /** `<name>` / `[name]` / `<name...>` for usage strings. */
22
+ export declare function humanReadableArgName(arg: Argument): string;