burgee 0.5.0 → 0.6.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.
- package/dist/execute.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/dist/config.d.ts +0 -28
- package/dist/config.js +0 -98
- package/dist/precedence.d.ts +0 -55
- package/dist/precedence.js +0 -100
package/dist/execute.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
+
import { ConfigError, explain, resolve as resolveLayers } from 'seniority';
|
|
3
4
|
import { detectAgent } from './agent.js';
|
|
4
5
|
import { ExitCode, isExitCode } from './exit-code.js';
|
|
5
6
|
import { renderHelp } from './help.js';
|
|
@@ -7,7 +8,6 @@ import { Manifest } from './manifest.js';
|
|
|
7
8
|
import { serveMcp } from './mcp.js';
|
|
8
9
|
import { camel, kebab } from './names.js';
|
|
9
10
|
import { nearestPackage } from './pkg.js';
|
|
10
|
-
import { ConfigError, explain, resolve as resolveLayers } from './precedence.js';
|
|
11
11
|
import { commandSchemaOf, machineJson, schemaOf, summaryOf } from './schema.js';
|
|
12
12
|
import { checkDefinition, checkRelations, coerce, UsageError } from './validate.js';
|
|
13
13
|
function helpFields(c) {
|
|
@@ -159,7 +159,7 @@ function packageLayer(pkg, name) {
|
|
|
159
159
|
return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
|
|
160
160
|
}
|
|
161
161
|
async function configLayers(name, values, io) {
|
|
162
|
-
const { discover } = await import('
|
|
162
|
+
const { discover } = await import('seniority');
|
|
163
163
|
const explicit = values['config'];
|
|
164
164
|
const disabled = values['noConfig'] === true;
|
|
165
165
|
const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ export { camel, checkDefinition, kebab, UsageError } from './validate.js';
|
|
|
11
11
|
export { AGENT_PROBES, detectAgent, type AgentProbe, type Detection } from './agent.js';
|
|
12
12
|
export { renderHelp, type HelpOptions, type HelpTheme, type HelpToken } from './help.js';
|
|
13
13
|
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf, type Invoke, type ServeOptions, type Tool, type ToolAnnotations } from './mcp.js';
|
|
14
|
-
export { ConfigError, envName, explain, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from '
|
|
14
|
+
export { ConfigError, envName, explain, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from 'seniority';
|
|
15
15
|
export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf, type CommandSchema, type JsonSchema, type ProgramSchema, type SchemaSummary } from './schema.js';
|
|
16
16
|
export { definePlugin, Manifest, type ArgumentSpec, type CommandNode, type Effects, type Example, type Hook, type LazyModule, type OptionSpec, type ActionRequiredSpec, type Plugin, type Relation, type RunContext, type StandardResult, type StandardSchemaV1, } from './manifest.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,6 @@ export { camel, checkDefinition, kebab, UsageError } from './validate.js';
|
|
|
4
4
|
export { AGENT_PROBES, detectAgent } from './agent.js';
|
|
5
5
|
export { renderHelp } from './help.js';
|
|
6
6
|
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
|
|
7
|
-
export { ConfigError, envName, explain, resolve, screaming } from '
|
|
7
|
+
export { ConfigError, envName, explain, resolve, screaming } from 'seniority';
|
|
8
8
|
export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf } from './schema.js';
|
|
9
9
|
export { definePlugin, Manifest, } from './manifest.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "burgee",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "An agent-native CLI framework, drop-in compatible with commander and yargs. One declaration; help, --json, --schema, --mcp and completions all projected from it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"provenance": true
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"roundel": "^0.3.0"
|
|
99
|
+
"roundel": "^0.3.0",
|
|
100
|
+
"seniority": "^0.1.0"
|
|
100
101
|
},
|
|
101
102
|
"devDependencies": {
|
|
102
103
|
"vitest": "^5.0.0"
|
package/dist/config.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type Layer } from './precedence.js';
|
|
2
|
-
export interface Discovery {
|
|
3
|
-
name: string;
|
|
4
|
-
cwd: string;
|
|
5
|
-
env: Record<string, string | undefined>;
|
|
6
|
-
/** `--config <path>`. */
|
|
7
|
-
explicit?: string;
|
|
8
|
-
/** `--no-config`. */
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface Loaded extends Layer {
|
|
12
|
-
/** The files merged, outermost first — what `--explain` shows. */
|
|
13
|
-
chain: string[];
|
|
14
|
-
}
|
|
15
|
-
/** Objects merge recursively; anything else, the later value wins. */
|
|
16
|
-
export declare function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown>;
|
|
17
|
-
/** Load a file and everything it extends, outermost first, the file's own keys winning. */
|
|
18
|
-
export declare function loadWithExtends(path: string, seen?: string[]): Promise<Loaded>;
|
|
19
|
-
/** The discovery order as candidate paths, first hit wins; each entry says why it was tried. */
|
|
20
|
-
export declare function candidates(d: Discovery): {
|
|
21
|
-
path: string;
|
|
22
|
-
reason: string;
|
|
23
|
-
}[];
|
|
24
|
-
/**
|
|
25
|
-
* Discover and load. `package.json#<name>` is not a file here — the engine reads the
|
|
26
|
-
* owning package.json itself and treats the field as its own layer, below config.
|
|
27
|
-
*/
|
|
28
|
-
export declare function discover(d: Discovery): Promise<Loaded | undefined>;
|
package/dist/config.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { ConfigError } from './precedence.js';
|
|
6
|
-
const EXTENSIONS = ['.json', '.mjs', '.js', '.cjs'];
|
|
7
|
-
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
8
|
-
export function deepMerge(base, over) {
|
|
9
|
-
const out = new Map(Object.entries(base));
|
|
10
|
-
for (const [k, v] of Object.entries(over)) {
|
|
11
|
-
const prev = out.get(k);
|
|
12
|
-
out.set(k, isObject(prev) && isObject(v) ? deepMerge(prev, v) : v);
|
|
13
|
-
}
|
|
14
|
-
return Object.fromEntries(out);
|
|
15
|
-
}
|
|
16
|
-
async function loadFile(path) {
|
|
17
|
-
if (path.endsWith('.json')) {
|
|
18
|
-
try {
|
|
19
|
-
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
20
|
-
if (!isObject(parsed))
|
|
21
|
-
throw new ConfigError(`${path} must contain an object`);
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
catch (cause) {
|
|
25
|
-
if (cause instanceof ConfigError)
|
|
26
|
-
throw cause;
|
|
27
|
-
throw new ConfigError(`${path} is not valid JSON`, cause instanceof Error ? cause.message : undefined);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const mod = (await import(pathToFileURL(path).href));
|
|
31
|
-
const value = typeof mod.default === 'function' ? await mod.default() : mod.default;
|
|
32
|
-
if (!isObject(value))
|
|
33
|
-
throw new ConfigError(`${path} must export an object (or a function returning one)`);
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
function resolveExtends(spec, from) {
|
|
37
|
-
if (spec.startsWith('.') || isAbsolute(spec))
|
|
38
|
-
return resolve(dirname(from), spec);
|
|
39
|
-
try {
|
|
40
|
-
return createRequire(from).resolve(spec);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
throw new ConfigError(`${from} extends "${spec}", which cannot be resolved`, 'use a relative path or an installed package');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function extendsList(value) {
|
|
47
|
-
if (typeof value === 'string')
|
|
48
|
-
return [value];
|
|
49
|
-
return Array.isArray(value) ? value.map(String) : [];
|
|
50
|
-
}
|
|
51
|
-
export async function loadWithExtends(path, seen = []) {
|
|
52
|
-
if (seen.includes(path))
|
|
53
|
-
throw new ConfigError(`config extends itself: ${[...seen, path].join(' → ')}`);
|
|
54
|
-
const own = await loadFile(path);
|
|
55
|
-
const parents = own['extends'];
|
|
56
|
-
const specs = extendsList(parents);
|
|
57
|
-
let data = {};
|
|
58
|
-
const chain = [];
|
|
59
|
-
for (const spec of specs) {
|
|
60
|
-
const parent = await loadWithExtends(resolveExtends(spec, path), [...seen, path]);
|
|
61
|
-
data = deepMerge(data, parent.data);
|
|
62
|
-
chain.push(...parent.chain);
|
|
63
|
-
}
|
|
64
|
-
const { extends: _ignored, ...rest } = own;
|
|
65
|
-
return { path, data: deepMerge(data, rest), chain: [...chain, path] };
|
|
66
|
-
}
|
|
67
|
-
const userConfigDir = (env) => {
|
|
68
|
-
const base = env['XDG_CONFIG_HOME'] ?? (env['HOME'] === undefined ? undefined : join(env['HOME'], '.config'));
|
|
69
|
-
return base;
|
|
70
|
-
};
|
|
71
|
-
export function candidates(d) {
|
|
72
|
-
const out = [];
|
|
73
|
-
const fromEnv = d.env[`${d.name.toUpperCase().replaceAll('-', '_')}_CONFIG`];
|
|
74
|
-
if (fromEnv !== undefined)
|
|
75
|
-
out.push({ path: resolve(d.cwd, fromEnv), reason: `${d.name.toUpperCase().replaceAll('-', '_')}_CONFIG` });
|
|
76
|
-
for (const ext of EXTENSIONS)
|
|
77
|
-
out.push({ path: join(d.cwd, `${d.name}.config${ext}`), reason: 'current directory' });
|
|
78
|
-
const user = userConfigDir(d.env);
|
|
79
|
-
if (user !== undefined)
|
|
80
|
-
out.push({ path: join(user, d.name, 'config.json'), reason: 'user config directory' });
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
export async function discover(d) {
|
|
84
|
-
if (d.disabled === true)
|
|
85
|
-
return undefined;
|
|
86
|
-
if (d.explicit !== undefined) {
|
|
87
|
-
const path = resolve(d.cwd, d.explicit);
|
|
88
|
-
if (!existsSync(path))
|
|
89
|
-
throw new ConfigError(`config file not found: ${path}`, 'check --config, or drop it to use discovery');
|
|
90
|
-
return await loadWithExtends(path);
|
|
91
|
-
}
|
|
92
|
-
const found = candidates(d)
|
|
93
|
-
.map((c) => c.path)
|
|
94
|
-
.find((path) => existsSync(path));
|
|
95
|
-
if (found === undefined)
|
|
96
|
-
return undefined;
|
|
97
|
-
return await loadWithExtends(found);
|
|
98
|
-
}
|
package/dist/precedence.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One precedence order, fixed and not configurable (commander-env V1–V3, V5):
|
|
3
|
-
*
|
|
4
|
-
* flag > env > config file > package.json field > default
|
|
5
|
-
*
|
|
6
|
-
* `resolve` is pure — it takes the layers and returns the values with their provenance —
|
|
7
|
-
* which is what makes `--explain` trustworthy and `meta.provenance` cheap. Env applies
|
|
8
|
-
* only to the options the running command declares (yargs #873), never to a sibling's.
|
|
9
|
-
*/
|
|
10
|
-
import { type OptionSpec } from './manifest.js';
|
|
11
|
-
export type Source = 'flag' | 'env' | 'config' | 'package' | 'default';
|
|
12
|
-
export interface Provenance {
|
|
13
|
-
source: Source;
|
|
14
|
-
/** The env name, the config file, or `package.json` — where a person would look. */
|
|
15
|
-
location?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface Candidate {
|
|
18
|
-
source: Source;
|
|
19
|
-
location: string;
|
|
20
|
-
/** `undefined` when the layer had nothing for this option. */
|
|
21
|
-
value: unknown;
|
|
22
|
-
}
|
|
23
|
-
export interface Layer {
|
|
24
|
-
path: string;
|
|
25
|
-
data: Record<string, unknown>;
|
|
26
|
-
}
|
|
27
|
-
export interface Layers {
|
|
28
|
-
/** What the user typed: only options present on the command line. */
|
|
29
|
-
flags: Record<string, unknown>;
|
|
30
|
-
env: Record<string, string | undefined>;
|
|
31
|
-
/** With a prefix, an option without `env:` reads `PREFIX_OPTION_NAME` (R2). */
|
|
32
|
-
envPrefix?: string;
|
|
33
|
-
config?: Layer;
|
|
34
|
-
/** The `package.json` field named after the program, when present. */
|
|
35
|
-
pkg?: Layer;
|
|
36
|
-
}
|
|
37
|
-
export interface Resolution {
|
|
38
|
-
values: Record<string, unknown>;
|
|
39
|
-
provenance: Record<string, Provenance>;
|
|
40
|
-
candidates: Record<string, Candidate[]>;
|
|
41
|
-
}
|
|
42
|
-
/** A value that cannot be used as configured: exit CONFIG (3), never a stack (E1, E3). */
|
|
43
|
-
export declare class ConfigError extends Error {
|
|
44
|
-
readonly hint?: string | undefined;
|
|
45
|
-
constructor(message: string, hint?: string | undefined);
|
|
46
|
-
}
|
|
47
|
-
/** `region` → `REGION`, `dryRun` → `DRY_RUN`, `log-level` → `LOG_LEVEL` (yargs #2005: never camel-cased back). */
|
|
48
|
-
export declare function screaming(name: string): string;
|
|
49
|
-
export declare function envName(name: string, spec: OptionSpec, prefix: string | undefined): string | undefined;
|
|
50
|
-
/** Booleans from env accept one spelling each way (R7). */
|
|
51
|
-
export declare function envBoolean(raw: string): boolean | undefined;
|
|
52
|
-
/** Every declared option, resolved through the layers; a missing required one is left undefined for the caller to report. */
|
|
53
|
-
export declare function resolve(specs: Record<string, OptionSpec>, layers: Layers): Resolution;
|
|
54
|
-
/** `--explain <option>`: the winning source and every candidate it beat, or that was unset (V3). */
|
|
55
|
-
export declare function explain(name: string, resolution: Resolution): string;
|
package/dist/precedence.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
export class ConfigError extends Error {
|
|
2
|
-
hint;
|
|
3
|
-
constructor(message, hint) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.hint = hint;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
export function screaming(name) {
|
|
9
|
-
return name
|
|
10
|
-
.replaceAll(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
11
|
-
.replaceAll('-', '_')
|
|
12
|
-
.toUpperCase();
|
|
13
|
-
}
|
|
14
|
-
export function envName(name, spec, prefix) {
|
|
15
|
-
if (spec.env !== undefined)
|
|
16
|
-
return spec.env;
|
|
17
|
-
return prefix === undefined ? undefined : `${prefix}_${screaming(name)}`;
|
|
18
|
-
}
|
|
19
|
-
const TRUE = new Set(['1', 'true', 'yes']);
|
|
20
|
-
const FALSE = new Set(['0', 'false', 'no']);
|
|
21
|
-
export function envBoolean(raw) {
|
|
22
|
-
const v = raw.trim().toLowerCase();
|
|
23
|
-
if (TRUE.has(v))
|
|
24
|
-
return true;
|
|
25
|
-
if (FALSE.has(v))
|
|
26
|
-
return false;
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
function fromEnv(name, spec, layers) {
|
|
30
|
-
const variable = envName(name, spec, layers.envPrefix);
|
|
31
|
-
if (variable === undefined)
|
|
32
|
-
return undefined;
|
|
33
|
-
if (spec.type === 'boolean' && layers.envPrefix !== undefined) {
|
|
34
|
-
const negated = `${layers.envPrefix}_NO_${screaming(name)}`;
|
|
35
|
-
if (layers.env[negated] !== undefined) {
|
|
36
|
-
throw new ConfigError(`${negated} is not supported`, `set ${variable}=false instead`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const raw = layers.env[variable];
|
|
40
|
-
if (raw === undefined)
|
|
41
|
-
return { source: 'env', location: variable, value: undefined };
|
|
42
|
-
if (spec.type !== 'boolean')
|
|
43
|
-
return { source: 'env', location: variable, value: raw };
|
|
44
|
-
const parsed = envBoolean(raw);
|
|
45
|
-
if (parsed === undefined)
|
|
46
|
-
throw new ConfigError(`${variable}="${raw}" is not a boolean`, `use ${variable}=true or ${variable}=false`);
|
|
47
|
-
return { source: 'env', location: variable, value: parsed };
|
|
48
|
-
}
|
|
49
|
-
function candidatesFor(name, spec, layers) {
|
|
50
|
-
const out = [{ source: 'flag', location: `--${name}`, value: layers.flags[name] }];
|
|
51
|
-
const env = fromEnv(name, spec, layers);
|
|
52
|
-
if (env !== undefined)
|
|
53
|
-
out.push(env);
|
|
54
|
-
if (layers.config !== undefined)
|
|
55
|
-
out.push({ source: 'config', location: layers.config.path, value: layers.config.data[name] });
|
|
56
|
-
if (layers.pkg !== undefined)
|
|
57
|
-
out.push({ source: 'package', location: layers.pkg.path, value: layers.pkg.data[name] });
|
|
58
|
-
out.push({ source: 'default', location: 'default', value: spec.default });
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
export function resolve(specs, layers) {
|
|
62
|
-
const values = new Map();
|
|
63
|
-
const provenance = new Map();
|
|
64
|
-
const candidates = new Map();
|
|
65
|
-
for (const [name, spec] of Object.entries(specs)) {
|
|
66
|
-
const list = candidatesFor(name, spec, layers);
|
|
67
|
-
candidates.set(name, list);
|
|
68
|
-
const winner = list.find((c) => c.value !== undefined);
|
|
69
|
-
if (winner === undefined)
|
|
70
|
-
continue;
|
|
71
|
-
values.set(name, winner.value);
|
|
72
|
-
provenance.set(name, winner.source === 'default' ? { source: 'default' } : { source: winner.source, location: winner.location });
|
|
73
|
-
}
|
|
74
|
-
return { values: Object.fromEntries(values), provenance: Object.fromEntries(provenance), candidates: Object.fromEntries(candidates) };
|
|
75
|
-
}
|
|
76
|
-
const describe = (c) => {
|
|
77
|
-
switch (c.source) {
|
|
78
|
-
case 'flag':
|
|
79
|
-
return `flag ${c.location}`;
|
|
80
|
-
case 'env':
|
|
81
|
-
return `env ${c.location}`;
|
|
82
|
-
case 'config':
|
|
83
|
-
return `config file ${c.location}`;
|
|
84
|
-
case 'package':
|
|
85
|
-
return `package.json field in ${c.location}`;
|
|
86
|
-
case 'default':
|
|
87
|
-
return 'default';
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
export function explain(name, resolution) {
|
|
91
|
-
const list = resolution.candidates[name];
|
|
92
|
-
if (list === undefined)
|
|
93
|
-
return `${name} is not an option of this command\n`;
|
|
94
|
-
const winner = list.find((c) => c.value !== undefined);
|
|
95
|
-
const head = winner === undefined ? `${name} is unset` : `${name} = ${JSON.stringify(winner.value)} from ${describe(winner)}`;
|
|
96
|
-
const rest = list
|
|
97
|
-
.filter((c) => c !== winner)
|
|
98
|
-
.map((c) => `${describe(c)} ${c.value === undefined ? '(unset)' : JSON.stringify(c.value)}`);
|
|
99
|
-
return `${head}\n${rest.length === 0 ? '' : ` candidates: ${rest.join(', ')}\n`}`;
|
|
100
|
-
}
|