@metrio-ai/cli 1.3.0 → 1.4.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/README.md +3 -1
- package/dist/manage/output.js +3 -1
- package/dist/manage/registry.js +39 -4
- package/dist/manage/run.js +25 -7
- package/dist/verify/report.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,8 +29,10 @@ export METRIO_PROJECT=my-project # or --project, or "project: my-project" in me
|
|
|
29
29
|
|
|
30
30
|
metrio prompt list
|
|
31
31
|
metrio version get 34 -o system.md # the system prompt as a file
|
|
32
|
-
metrio version create 12 -f system.md --model-name gpt-5
|
|
32
|
+
metrio version create 12 -f system.md --model-provider openai --model-name gpt-5
|
|
33
33
|
metrio version get 34 -o v.yaml && metrio version update 34 -f v.yaml # round-trip
|
|
34
|
+
metrio version get 34 -o greeter.md --frontmatter # settings as YAML frontmatter + the prompt verbatim
|
|
35
|
+
metrio version create 12 -f greeter.md --frontmatter # one file carries the whole version
|
|
34
36
|
metrio skill get 5 -o skill.md # frontmatter (name, description, references) + body
|
|
35
37
|
metrio skill update 5 -f skill.md
|
|
36
38
|
metrio workflow version create intake -f definition.json --activate
|
package/dist/manage/output.js
CHANGED
|
@@ -87,7 +87,9 @@ async function writeFile(data, opts, file) {
|
|
|
87
87
|
const text = opts.textField ? record[opts.textField] : undefined;
|
|
88
88
|
if (typeof text !== 'string')
|
|
89
89
|
throw new errors_1.CliError(`-o ${file}: this result has no text body; use .json or .yaml`);
|
|
90
|
-
const front = Object.fromEntries((opts.frontmatterKeys ?? [])
|
|
90
|
+
const front = Object.fromEntries((opts.frontmatterKeys ?? [])
|
|
91
|
+
.filter((k) => record[k] !== undefined && (record[k] !== null || (opts.frontmatterKeepNull ?? []).includes(k)))
|
|
92
|
+
.map((k) => [k, record[k]]));
|
|
91
93
|
const head = Object.keys(front).length ? `---\n${(0, yaml_1.stringify)(front)}---\n\n` : '';
|
|
92
94
|
body = `${head}${text}`;
|
|
93
95
|
}
|
package/dist/manage/registry.js
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MANAGE_GROUPS = exports.COMMANDS = void 0;
|
|
3
|
+
exports.MANAGE_GROUPS = exports.COMMANDS = exports.READ_ONLY_FRONTMATTER = void 0;
|
|
4
4
|
exports.findCommand = findCommand;
|
|
5
5
|
exports.groupHelp = groupHelp;
|
|
6
6
|
exports.commandHelp = commandHelp;
|
|
7
7
|
const P = (prop, label) => ({ prop, label });
|
|
8
|
+
/** Identity of the version a file came from: written for reference, never sent back. */
|
|
9
|
+
exports.READ_ONLY_FRONTMATTER = ['version_id', 'serial'];
|
|
10
|
+
const VERSION_FRONTMATTER = [
|
|
11
|
+
...exports.READ_ONLY_FRONTMATTER, 'description', 'tier_key', 'model_provider', 'model_name',
|
|
12
|
+
'output_format', 'model_settings', 'model_fallbacks', 'output_schema',
|
|
13
|
+
];
|
|
14
|
+
/** get_version → the create_version/update_version field names the frontmatter uses. */
|
|
15
|
+
function versionFrontmatterView(r) {
|
|
16
|
+
const tier = r.model_tier?.tier_key;
|
|
17
|
+
// A file describes the whole version: "no schema" / "no fallbacks" are written explicitly, so
|
|
18
|
+
// update -f clears them instead of keeping what the target version had (omission keeps).
|
|
19
|
+
const view = {
|
|
20
|
+
...r,
|
|
21
|
+
version_id: r.id,
|
|
22
|
+
output_schema: r.output_schema ?? null,
|
|
23
|
+
model_fallbacks: r.model_fallbacks ?? [],
|
|
24
|
+
};
|
|
25
|
+
if (typeof tier === 'string' && tier) {
|
|
26
|
+
view.tier_key = tier;
|
|
27
|
+
delete view.model_provider;
|
|
28
|
+
delete view.model_name;
|
|
29
|
+
}
|
|
30
|
+
return view;
|
|
31
|
+
}
|
|
32
|
+
const VERSION_FILE = {
|
|
33
|
+
optionalFrontmatterKeys: VERSION_FRONTMATTER,
|
|
34
|
+
frontmatterView: versionFrontmatterView,
|
|
35
|
+
frontmatterKeepNull: ['output_schema'],
|
|
36
|
+
};
|
|
8
37
|
const PROMPT = P('prompt_external_id', '<prompt-id>');
|
|
9
38
|
const VERSION_ID = P('version_id', '<version-id>');
|
|
10
39
|
const SKILL = P('skill_id', '<skill-id>');
|
|
@@ -28,9 +57,9 @@ exports.COMMANDS = [
|
|
|
28
57
|
{ path: ['prompt', 'set-mcp-tools'], tool: 'set_prompt_mcp_tools', summary: 'Set the MCP tool allowlist (-f tools.yaml)', args: [PROMPT] },
|
|
29
58
|
{ path: ['prompt', 'diff'], tool: 'diff_prompt', summary: 'Diff two versions', args: [PROMPT, P('from_serial', '<from>'), P('to_serial', '<to>')] },
|
|
30
59
|
{ path: ['version', 'list'], tool: 'list_versions', summary: 'List versions of a prompt', args: [PROMPT] },
|
|
31
|
-
{ path: ['version', 'get'], tool: 'get_version', summary: 'Get a version (-o v.yaml | system.md)', args: [VERSION_ID], textField: 'system_prompt' },
|
|
32
|
-
{ path: ['version', 'create'], tool: 'create_version', summary: 'Create a version (-f system.md | v.yaml)', args: [PROMPT], textField: 'system_prompt' },
|
|
33
|
-
{ path: ['version', 'update'], tool: 'update_version', summary: 'Update a version (-f system.md | v.yaml)', args: [VERSION_ID], textField: 'system_prompt' },
|
|
60
|
+
{ path: ['version', 'get'], tool: 'get_version', summary: 'Get a version (-o v.yaml | system.md [--frontmatter])', args: [VERSION_ID], textField: 'system_prompt', ...VERSION_FILE },
|
|
61
|
+
{ path: ['version', 'create'], tool: 'create_version', summary: 'Create a version (-f system.md [--frontmatter] | v.yaml)', args: [PROMPT], textField: 'system_prompt', ...VERSION_FILE },
|
|
62
|
+
{ path: ['version', 'update'], tool: 'update_version', summary: 'Update a version (-f system.md [--frontmatter] | v.yaml)', args: [VERSION_ID], textField: 'system_prompt', ...VERSION_FILE },
|
|
34
63
|
{ path: ['version', 'create-batch'], tool: 'create_versions', summary: 'Create versions for several prompts (-f versions.jsonl)', wrap: 'versions' },
|
|
35
64
|
{ path: ['skill', 'list'], tool: 'list_skills', summary: 'List skills' },
|
|
36
65
|
{ path: ['skill', 'get'], tool: 'get_skill', summary: 'Get a skill (-o skill.md)', args: [SKILL], textField: 'content', frontmatterKeys: ['name', 'description', 'references'] },
|
|
@@ -107,10 +136,16 @@ function commandHelp(spec) {
|
|
|
107
136
|
];
|
|
108
137
|
if (spec.destructive)
|
|
109
138
|
lines.push(' --yes Confirm the delete');
|
|
139
|
+
if (spec.optionalFrontmatterKeys) {
|
|
140
|
+
lines.push(' --frontmatter .md files carry the version settings as YAML frontmatter (see below)');
|
|
141
|
+
}
|
|
110
142
|
if (spec.textField)
|
|
111
143
|
lines.push('', `A .md/.txt file fills ${spec.textField}.`);
|
|
112
144
|
if (spec.frontmatterKeys)
|
|
113
145
|
lines.push(`Its YAML frontmatter may set: ${spec.frontmatterKeys.join(', ')}.`);
|
|
146
|
+
if (spec.optionalFrontmatterKeys) {
|
|
147
|
+
lines.push(`With --frontmatter, a .md file starts with YAML frontmatter (${spec.optionalFrontmatterKeys.join(', ')})`, `and the rest is ${spec.textField} verbatim. ${exports.READ_ONLY_FRONTMATTER.join(' and ')} are written by -o and ignored by -f.`, 'Without --frontmatter the whole file is the text, so a prompt that starts with --- stays intact.');
|
|
148
|
+
}
|
|
114
149
|
if (spec.wrap)
|
|
115
150
|
lines.push('', `A file without a "${spec.wrap}" key is sent as ${spec.wrap}.`);
|
|
116
151
|
if (spec.downloadField)
|
package/dist/manage/run.js
CHANGED
|
@@ -10,7 +10,7 @@ const registry_1 = require("./registry");
|
|
|
10
10
|
const workspace_1 = require("../sync/workspace");
|
|
11
11
|
const schema_1 = require("./schema");
|
|
12
12
|
function parse(rest) {
|
|
13
|
-
const parsed = { tokens: [], json: false, yes: false };
|
|
13
|
+
const parsed = { tokens: [], json: false, yes: false, frontmatter: false };
|
|
14
14
|
const take = (i, name) => {
|
|
15
15
|
const v = rest[i + 1];
|
|
16
16
|
if (v === undefined)
|
|
@@ -38,6 +38,8 @@ function parse(rest) {
|
|
|
38
38
|
parsed.json = true;
|
|
39
39
|
else if (arg === '--yes')
|
|
40
40
|
parsed.yes = true;
|
|
41
|
+
else if (arg === '--frontmatter')
|
|
42
|
+
parsed.frontmatter = true;
|
|
41
43
|
else if (arg.startsWith('--')) {
|
|
42
44
|
const eq = arg.indexOf('=');
|
|
43
45
|
parsed.tokens.push(eq === -1 ? { flag: arg.slice(2) } : { flag: arg.slice(2, eq), value: arg.slice(eq + 1) });
|
|
@@ -47,13 +49,17 @@ function parse(rest) {
|
|
|
47
49
|
}
|
|
48
50
|
return parsed;
|
|
49
51
|
}
|
|
50
|
-
function fromFile(spec, file, readStdin) {
|
|
51
|
-
const loaded = (0, input_1.loadInputFile)(file, readStdin, { frontmatter: !!spec.frontmatterKeys, stdinText: !!spec.textField });
|
|
52
|
+
function fromFile(spec, file, frontmatter, readStdin) {
|
|
53
|
+
const loaded = (0, input_1.loadInputFile)(file, readStdin, { frontmatter: !!spec.frontmatterKeys || frontmatter, stdinText: !!spec.textField });
|
|
52
54
|
const cmd = `metrio ${spec.path.join(' ')}`;
|
|
53
55
|
if (loaded.kind === 'text') {
|
|
54
56
|
if (!spec.textField)
|
|
55
57
|
throw new errors_1.CliError(`${cmd} does not take a text file; use .json or .yaml`);
|
|
56
|
-
|
|
58
|
+
const front = { ...(loaded.frontmatter ?? {}) };
|
|
59
|
+
if (frontmatter)
|
|
60
|
+
for (const key of registry_1.READ_ONLY_FRONTMATTER)
|
|
61
|
+
delete front[key];
|
|
62
|
+
return { ...front, [spec.textField]: loaded.value };
|
|
57
63
|
}
|
|
58
64
|
if (loaded.kind === 'array') {
|
|
59
65
|
if (!spec.wrap)
|
|
@@ -103,6 +109,9 @@ async function runManageCommand(argv, readStdin) {
|
|
|
103
109
|
}
|
|
104
110
|
const { spec, rest } = match;
|
|
105
111
|
const opts = parse(rest);
|
|
112
|
+
if (opts.frontmatter && !spec.optionalFrontmatterKeys) {
|
|
113
|
+
throw new errors_1.CliError(`--frontmatter only applies to metrio version get | create | update, not metrio ${spec.path.join(' ')}`);
|
|
114
|
+
}
|
|
106
115
|
const client = new client_1.ConsoleClient(await (0, client_1.resolveConsoleAuth)());
|
|
107
116
|
const tool = (await client.listTools()).find((t) => t.name === spec.tool);
|
|
108
117
|
if (!tool) {
|
|
@@ -123,12 +132,18 @@ async function runManageCommand(argv, readStdin) {
|
|
|
123
132
|
sources[key] = source;
|
|
124
133
|
};
|
|
125
134
|
if (opts.file)
|
|
126
|
-
for (const [k, v] of Object.entries(fromFile(spec, opts.file, readStdin)))
|
|
135
|
+
for (const [k, v] of Object.entries(fromFile(spec, opts.file, opts.frontmatter, readStdin)))
|
|
127
136
|
put(k, v, `file ${opts.file}`);
|
|
128
137
|
if (opts.project && 'project_slug' in props)
|
|
129
138
|
put('project_slug', opts.project, 'project');
|
|
130
139
|
for (const [k, v] of Object.entries(values))
|
|
131
140
|
put(k, v, `--${(0, schema_1.kebab)(k)}`);
|
|
141
|
+
// A version uses a tier OR a specific model, and the console lets tier_key win when both are
|
|
142
|
+
// sent; so a model chosen by flag must drop a tier that came from the file, or the flag is ignored.
|
|
143
|
+
if (('model_provider' in values || 'model_name' in values) && !('tier_key' in values) && 'tier_key' in args) {
|
|
144
|
+
delete args.tier_key;
|
|
145
|
+
delete sources.tier_key;
|
|
146
|
+
}
|
|
132
147
|
// A prompt directory resolves against the final project_slug, so it waits for it.
|
|
133
148
|
let promptDir;
|
|
134
149
|
expected.forEach((arg, i) => {
|
|
@@ -163,11 +178,14 @@ async function runManageCommand(argv, readStdin) {
|
|
|
163
178
|
throw new errors_1.CliError('Re-run with --yes to confirm.', 2);
|
|
164
179
|
}
|
|
165
180
|
const data = await client.callTool(spec.tool, accepted, { retry: tool.scope === 'read' });
|
|
166
|
-
|
|
181
|
+
const textOut = !!opts.outFile && /\.(md|markdown|txt)$/i.test(opts.outFile);
|
|
182
|
+
const withFrontmatter = opts.frontmatter && textOut && isRecord(data) && spec.frontmatterView;
|
|
183
|
+
await (0, output_1.writeOutput)(withFrontmatter ? spec.frontmatterView(data) : data, {
|
|
167
184
|
json: opts.json,
|
|
168
185
|
outFile: opts.outFile,
|
|
169
186
|
textField: spec.textField,
|
|
170
|
-
frontmatterKeys: spec.frontmatterKeys,
|
|
187
|
+
frontmatterKeys: spec.frontmatterKeys ?? (opts.frontmatter ? spec.optionalFrontmatterKeys : undefined),
|
|
188
|
+
frontmatterKeepNull: withFrontmatter ? spec.frontmatterKeepNull : undefined,
|
|
171
189
|
downloadField: spec.downloadField,
|
|
172
190
|
});
|
|
173
191
|
if (isRecord(data) && data.valid === false) {
|
package/dist/verify/report.js
CHANGED
|
@@ -30,7 +30,8 @@ function formatHuman(results) {
|
|
|
30
30
|
lines.push(r.resource);
|
|
31
31
|
}
|
|
32
32
|
const stats = [
|
|
33
|
-
|
|
33
|
+
// Workflow runs report fractional milliseconds; the text report shows whole ones.
|
|
34
|
+
r.latencyMs !== undefined ? `${Math.round(r.latencyMs)}ms` : '',
|
|
34
35
|
r.tokens !== undefined ? `${r.tokens} tokens` : '',
|
|
35
36
|
r.version !== undefined ? `v${r.version}` : '',
|
|
36
37
|
].filter(Boolean).join(', ');
|