@metrio-ai/cli 1.1.0 → 1.3.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 +46 -0
- package/dist/commands/sync.js +489 -0
- package/dist/commands/verify.js +288 -0
- package/dist/index.js +12 -0
- package/dist/manage/output.js +6 -1
- package/dist/manage/run.js +33 -1
- package/dist/sync/push.js +329 -0
- package/dist/sync/remote.js +79 -0
- package/dist/sync/render.js +181 -0
- package/dist/sync/status.js +85 -0
- package/dist/sync/workspace.js +430 -0
- package/dist/verify/assert.js +117 -0
- package/dist/verify/cases.js +302 -0
- package/dist/verify/execute.js +109 -0
- package/dist/verify/jsonpath.js +58 -0
- package/dist/verify/report.js +86 -0
- package/package.json +3 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.VERIFY_HELP = void 0;
|
|
37
|
+
exports.parseVerifyArgs = parseVerifyArgs;
|
|
38
|
+
exports.resolveTargets = resolveTargets;
|
|
39
|
+
exports.runVerifyCommand = runVerifyCommand;
|
|
40
|
+
/**
|
|
41
|
+
* `metrio verify` — runs cases.yaml against prompts and workflows and checks every reply.
|
|
42
|
+
* Execution uses the project API key, like `chat` and `run-batch`. Targets come from the
|
|
43
|
+
* workspace (.metrio/state.json) or from -p / -w with --cases. Every cases.yaml is
|
|
44
|
+
* validated before the first request.
|
|
45
|
+
*/
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const client_1 = require("@metrio-ai/client");
|
|
49
|
+
const errors_1 = require("../errors");
|
|
50
|
+
const shared_1 = require("./shared");
|
|
51
|
+
const sync_1 = require("./sync");
|
|
52
|
+
const workspace_1 = require("../sync/workspace");
|
|
53
|
+
const cases_1 = require("../verify/cases");
|
|
54
|
+
const execute_1 = require("../verify/execute");
|
|
55
|
+
const report_1 = require("../verify/report");
|
|
56
|
+
exports.VERIFY_HELP = `metrio verify - run cases.yaml and check every reply
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
metrio verify [paths…] [options] Workspace prompts/<name>/cases.yaml and
|
|
60
|
+
workflows/<id>/cases.yaml
|
|
61
|
+
metrio verify -p <prompt-id> --cases <file> One prompt, any cases file
|
|
62
|
+
metrio verify -w <workflow-id> --cases <file> One workflow, any cases file
|
|
63
|
+
|
|
64
|
+
In a workspace (see "metrio pull --help") each cases.yaml runs against the resource
|
|
65
|
+
recorded in .metrio/state.json. Paths narrow the run: a resource directory, prompts/,
|
|
66
|
+
workflows/ or the root.
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
--version active|pushed|<n> active (default): the live version; pushed: the version
|
|
70
|
+
the last "metrio push" created; <n>: prompt version serial
|
|
71
|
+
or workflow version
|
|
72
|
+
(a number needs a single prompt or workflow path)
|
|
73
|
+
--concurrency <n> Cases in flight (default 4)
|
|
74
|
+
--json Print results as JSON instead of the text report
|
|
75
|
+
--junit <file> Also write a JUnit XML report
|
|
76
|
+
--fail-fast Start no new case after the first failure
|
|
77
|
+
--timezone <tz> IANA timezone sent with every case
|
|
78
|
+
-e, --env <file> Env file (default: .env in the current dir)
|
|
79
|
+
--api-key <key> Project API key (default: METRIOAI_API_KEY); it must belong
|
|
80
|
+
to the workspace's project
|
|
81
|
+
--base-url <url> API base URL
|
|
82
|
+
|
|
83
|
+
cases.yaml:
|
|
84
|
+
defaults:
|
|
85
|
+
channel: { User: { customer_id: "123" } } # workflows only
|
|
86
|
+
variables: { region: TW }
|
|
87
|
+
cases:
|
|
88
|
+
- name: regular order
|
|
89
|
+
message: "我要訂 3 箱蘋果" # or messages: [...] or messages_file: path
|
|
90
|
+
variables: { region: HK } # merged key by key over defaults
|
|
91
|
+
expect: # optional; every listed assertion must hold
|
|
92
|
+
contains: ["蘋果"] # reply text; also not_contains, regex, equals
|
|
93
|
+
json: # $.content $.structContent $.state $.tags, and
|
|
94
|
+
"$.state.order.qty": { equals: 3 } # $.json (reply text parsed as JSON);
|
|
95
|
+
"$.tags": { contains: order } # matchers: equals, exists, contains
|
|
96
|
+
path_includes: [classify, create_order] # workflows: ordered node subsequence
|
|
97
|
+
terminal_node: create_order # workflows
|
|
98
|
+
response_type: text # workflows: text | struct | none
|
|
99
|
+
max_latency_ms: 8000
|
|
100
|
+
max_tokens: 4000 # input + output
|
|
101
|
+
max_llm_calls: 3 # workflows
|
|
102
|
+
|
|
103
|
+
Agent loop: pull → edit → push → verify --version pushed → push --activate
|
|
104
|
+
|
|
105
|
+
Exit codes: 0 all passed, 1 a case failed / errored / was skipped, 2 invalid cases or options.
|
|
106
|
+
`;
|
|
107
|
+
function positive(raw, flag) {
|
|
108
|
+
const n = Number(raw);
|
|
109
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
110
|
+
throw new errors_1.CliError(`${flag} must be a positive integer, got "${raw}"`);
|
|
111
|
+
return n;
|
|
112
|
+
}
|
|
113
|
+
function parseVerifyArgs(argv) {
|
|
114
|
+
const o = { paths: [], version: 'active', concurrency: 4, json: false, failFast: false };
|
|
115
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
116
|
+
const arg = argv[i];
|
|
117
|
+
const value = () => {
|
|
118
|
+
const v = argv[i + 1];
|
|
119
|
+
if (v === undefined)
|
|
120
|
+
throw new errors_1.CliError(`${arg} needs a value`);
|
|
121
|
+
i += 1;
|
|
122
|
+
return v;
|
|
123
|
+
};
|
|
124
|
+
switch (arg) {
|
|
125
|
+
case '--version': {
|
|
126
|
+
const v = value();
|
|
127
|
+
o.version = v === 'active' || v === 'pushed' ? v : positive(v, '--version');
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case '--concurrency':
|
|
131
|
+
o.concurrency = positive(value(), arg);
|
|
132
|
+
break;
|
|
133
|
+
case '--json':
|
|
134
|
+
o.json = true;
|
|
135
|
+
break;
|
|
136
|
+
case '--junit':
|
|
137
|
+
o.junit = value();
|
|
138
|
+
break;
|
|
139
|
+
case '--fail-fast':
|
|
140
|
+
o.failFast = true;
|
|
141
|
+
break;
|
|
142
|
+
case '-p':
|
|
143
|
+
case '--prompt-id':
|
|
144
|
+
o.promptId = positive(value(), arg);
|
|
145
|
+
break;
|
|
146
|
+
case '-w':
|
|
147
|
+
case '--workflow-id': {
|
|
148
|
+
const v = value().trim();
|
|
149
|
+
if (!v)
|
|
150
|
+
throw new errors_1.CliError(`${arg} cannot be empty`);
|
|
151
|
+
o.workflowId = v;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case '--cases':
|
|
155
|
+
o.cases = value();
|
|
156
|
+
break;
|
|
157
|
+
case '--timezone':
|
|
158
|
+
o.timezone = value();
|
|
159
|
+
break;
|
|
160
|
+
case '-e':
|
|
161
|
+
case '--env':
|
|
162
|
+
o.envFile = value();
|
|
163
|
+
break;
|
|
164
|
+
case '--api-key':
|
|
165
|
+
o.apiKey = value();
|
|
166
|
+
break;
|
|
167
|
+
case '--base-url':
|
|
168
|
+
o.baseUrl = value();
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
if (arg.startsWith('-'))
|
|
172
|
+
throw new errors_1.CliError(`Unknown option ${arg}. Run "metrio verify --help".`);
|
|
173
|
+
o.paths.push(arg);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const direct = o.promptId !== undefined || o.workflowId !== undefined;
|
|
177
|
+
if (o.promptId !== undefined && o.workflowId !== undefined)
|
|
178
|
+
throw new errors_1.CliError('Use -p or -w, not both');
|
|
179
|
+
if (direct && !o.cases)
|
|
180
|
+
throw new errors_1.CliError('-p / -w need --cases <file>');
|
|
181
|
+
if (!direct && o.cases)
|
|
182
|
+
throw new errors_1.CliError('--cases needs -p <prompt-id> or -w <workflow-id>');
|
|
183
|
+
if (direct && o.paths.length)
|
|
184
|
+
throw new errors_1.CliError('Workspace paths cannot be combined with -p / -w');
|
|
185
|
+
if (direct && o.version === 'pushed') {
|
|
186
|
+
throw new errors_1.CliError('--version pushed reads .metrio/state.json; with -p / -w use active or a version number');
|
|
187
|
+
}
|
|
188
|
+
return o;
|
|
189
|
+
}
|
|
190
|
+
function directTarget(o) {
|
|
191
|
+
const kind = o.promptId !== undefined ? 'prompt' : 'workflow';
|
|
192
|
+
const file = o.cases;
|
|
193
|
+
let raw;
|
|
194
|
+
try {
|
|
195
|
+
if (!fs.statSync(file).isFile())
|
|
196
|
+
throw new Error('not a file');
|
|
197
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
throw new errors_1.CliError(`Cannot read cases file ${file}: ${err.message}`);
|
|
201
|
+
}
|
|
202
|
+
const ref = kind === 'prompt' ? o.promptId : o.workflowId;
|
|
203
|
+
return {
|
|
204
|
+
label: `${kind} ${ref}`,
|
|
205
|
+
kind,
|
|
206
|
+
ref,
|
|
207
|
+
version: typeof o.version === 'number' ? o.version : undefined,
|
|
208
|
+
cases: (0, cases_1.parseCasesYaml)(raw, { kind, file, baseDir: path.dirname(path.resolve(file)) }),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function resolveTargets(o) {
|
|
212
|
+
if (o.cases)
|
|
213
|
+
return [directTarget(o)];
|
|
214
|
+
const root = (0, workspace_1.findRoot)();
|
|
215
|
+
if (!root)
|
|
216
|
+
throw new errors_1.CliError('Not in a metrio workspace (no metrio.yaml). Run it inside one, or use -p / -w with --cases.');
|
|
217
|
+
const state = (0, workspace_1.readState)(root, (0, workspace_1.readProject)(root));
|
|
218
|
+
const listed = (0, workspace_1.listLocal)(root, ['prompt', 'workflow']);
|
|
219
|
+
// Filter by path first so an unrelated resource's cases.yaml is never read.
|
|
220
|
+
const inScope = (0, sync_1.filterEntries)(listed, root, o.paths);
|
|
221
|
+
const read = (rs) => rs.map((r) => ({ ...r, cases: (0, workspace_1.readResourceFile)(root, r.key, 'cases.yaml') }));
|
|
222
|
+
for (const p of o.paths) {
|
|
223
|
+
if (!read((0, sync_1.filterEntries)(listed, root, [p])).some((r) => r.cases !== undefined))
|
|
224
|
+
throw new errors_1.CliError(`${p}: no cases.yaml found`);
|
|
225
|
+
}
|
|
226
|
+
const selected = read(inScope).filter((r) => r.cases !== undefined);
|
|
227
|
+
if (!selected.length) {
|
|
228
|
+
throw new errors_1.CliError('No cases.yaml in this workspace. Add prompts/<name>/cases.yaml or workflows/<id>/cases.yaml.');
|
|
229
|
+
}
|
|
230
|
+
if (typeof o.version === 'number' && selected.length > 1) {
|
|
231
|
+
throw new errors_1.CliError(`--version <n> pins one resource; name a single prompt or workflow path (matched: ${selected.map((r) => r.key).join(', ')})`);
|
|
232
|
+
}
|
|
233
|
+
return selected.map((r) => {
|
|
234
|
+
const entry = state.resources[r.key];
|
|
235
|
+
if (!entry)
|
|
236
|
+
throw new errors_1.CliError(`${r.key} is not in .metrio/state.json yet; run "metrio push" first`);
|
|
237
|
+
const kind = r.kind;
|
|
238
|
+
let version;
|
|
239
|
+
if (typeof o.version === 'number')
|
|
240
|
+
version = o.version;
|
|
241
|
+
else if (o.version === 'pushed') {
|
|
242
|
+
version = kind === 'prompt' ? entry.pushed_serial : entry.pushed_workflow_version;
|
|
243
|
+
if (version === undefined) {
|
|
244
|
+
throw new errors_1.CliError(`${r.key}: no pushed version recorded in .metrio/state.json; run "metrio push"; a resource created by its first push (e.g. a new workflow) has none, so use --version active for it`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const ref = kind === 'prompt' ? Number(entry.id) : entry.id;
|
|
248
|
+
if (kind === 'prompt' && (!Number.isInteger(ref) || ref <= 0)) {
|
|
249
|
+
throw new errors_1.CliError(`${r.key}: prompt id "${entry.id}" in .metrio/state.json is not a positive integer`);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
label: r.key,
|
|
253
|
+
kind,
|
|
254
|
+
ref,
|
|
255
|
+
version,
|
|
256
|
+
cases: (0, cases_1.parseCasesYaml)(r.cases, { kind, file: `${r.key}/cases.yaml`, baseDir: path.join(root, r.key), root }),
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async function runVerifyCommand(argv) {
|
|
261
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
262
|
+
process.stdout.write(exports.VERIFY_HELP);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const o = parseVerifyArgs(argv);
|
|
266
|
+
const targets = resolveTargets(o);
|
|
267
|
+
(0, shared_1.loadEnv)(o.envFile);
|
|
268
|
+
const client = new client_1.MetrioAI({ apiKey: (0, shared_1.resolveApiKey)(o.apiKey), baseUrl: (0, shared_1.resolveBaseUrl)(o.baseUrl) });
|
|
269
|
+
const results = await (0, execute_1.runAll)(client, targets, {
|
|
270
|
+
concurrency: o.concurrency,
|
|
271
|
+
failFast: o.failFast,
|
|
272
|
+
timezone: o.timezone,
|
|
273
|
+
onResult: (r, done, total) => process.stderr.write((0, sync_1.safe)(`[${done}/${total}] ${r.resource} › ${r.case} ${r.status}\n`)),
|
|
274
|
+
});
|
|
275
|
+
process.stdout.write(o.json ? (0, report_1.formatJson)(results) : (0, report_1.formatHuman)(results));
|
|
276
|
+
if (o.junit) {
|
|
277
|
+
try {
|
|
278
|
+
fs.writeFileSync(path.resolve(o.junit), (0, report_1.formatJunit)(results));
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
process.stderr.write(`Error: cannot write --junit file ${o.junit} (${err.message})\n`);
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const s = (0, report_1.summarize)(results);
|
|
286
|
+
if (s.passed !== s.total)
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,9 @@ const chat_1 = require("./commands/chat");
|
|
|
18
18
|
const prompt_1 = require("./commands/prompt");
|
|
19
19
|
const workflow_1 = require("./commands/workflow");
|
|
20
20
|
const tools_1 = require("./commands/tools");
|
|
21
|
+
const verify_1 = require("./commands/verify");
|
|
21
22
|
const run_1 = require("./manage/run");
|
|
23
|
+
const sync_1 = require("./commands/sync");
|
|
22
24
|
const registry_1 = require("./manage/registry");
|
|
23
25
|
exports.HELP = `metrio - MetrioAI CLI
|
|
24
26
|
|
|
@@ -30,6 +32,7 @@ Run (project API key, see "metrio chat --help"):
|
|
|
30
32
|
workflow run-batch Run several workflow cases in one process
|
|
31
33
|
prompt run-batch Run several prompt cases in one process (metrio prompt --help)
|
|
32
34
|
prompt create-version Create a prompt version from a file
|
|
35
|
+
verify [paths…] Run cases.yaml and check replies ("metrio verify --help")
|
|
33
36
|
|
|
34
37
|
Account (console OAuth):
|
|
35
38
|
login Sign in to the Metrio console in your browser
|
|
@@ -40,6 +43,7 @@ Manage (console OAuth; run "metrio <group> --help"):
|
|
|
40
43
|
project | prompt | version | skill | workflow | schedule | mcp | run | admin | model-tiers
|
|
41
44
|
e.g. metrio version create 12 -f system.md
|
|
42
45
|
metrio skill get 5 -o skill.md
|
|
46
|
+
pull | diff | push Sync prompts, skills and workflows with local files ("metrio pull --help")
|
|
43
47
|
tools List the console tools your login can call
|
|
44
48
|
call <tool> Call any console tool with JSON args (-f file | --args json)
|
|
45
49
|
|
|
@@ -81,6 +85,14 @@ async function main(argv) {
|
|
|
81
85
|
return runMixedGroup('workflow', argv);
|
|
82
86
|
case 'prompt':
|
|
83
87
|
return runMixedGroup('prompt', argv);
|
|
88
|
+
case 'pull':
|
|
89
|
+
return (0, sync_1.runPullCommand)(argv.slice(1));
|
|
90
|
+
case 'diff':
|
|
91
|
+
return (0, sync_1.runDiffCommand)(argv.slice(1));
|
|
92
|
+
case 'push':
|
|
93
|
+
return (0, sync_1.runPushCommand)(argv.slice(1));
|
|
94
|
+
case 'verify':
|
|
95
|
+
return (0, verify_1.runVerifyCommand)(argv.slice(1));
|
|
84
96
|
case 'login':
|
|
85
97
|
return (0, auth_1.runLoginCommand)(argv.slice(1));
|
|
86
98
|
case 'logout':
|
package/dist/manage/output.js
CHANGED
|
@@ -110,7 +110,12 @@ async function writeOutput(data, opts) {
|
|
|
110
110
|
process.stdout.write(record.text.endsWith('\n') ? record.text : `${record.text}\n`);
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
|
-
const
|
|
113
|
+
const listKey = Array.isArray(record.items)
|
|
114
|
+
? 'items'
|
|
115
|
+
: 'pagination' in record
|
|
116
|
+
? Object.keys(record).find((k) => Array.isArray(record[k]))
|
|
117
|
+
: undefined;
|
|
118
|
+
const rows = Array.isArray(data) ? data : listKey ? record[listKey] : undefined;
|
|
114
119
|
if (rows) {
|
|
115
120
|
process.stdout.write(rows.length ? `${renderTable(rows)}\n` : '(none)\n');
|
|
116
121
|
const p = asRecord(record.pagination);
|
package/dist/manage/run.js
CHANGED
|
@@ -7,6 +7,7 @@ const input_1 = require("./input");
|
|
|
7
7
|
const output_1 = require("./output");
|
|
8
8
|
const project_1 = require("./project");
|
|
9
9
|
const registry_1 = require("./registry");
|
|
10
|
+
const workspace_1 = require("../sync/workspace");
|
|
10
11
|
const schema_1 = require("./schema");
|
|
11
12
|
function parse(rest) {
|
|
12
13
|
const parsed = { tokens: [], json: false, yes: false };
|
|
@@ -66,6 +67,27 @@ function isCommandPrefix(argv) {
|
|
|
66
67
|
const words = argv.filter((a) => !a.startsWith('-'));
|
|
67
68
|
return registry_1.COMMANDS.some((c) => words.length < c.path.length && words.every((w, i) => c.path[i] === w));
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A prompt positional may name a tracked directory (prompts/<dir> or <dir>) instead of the
|
|
72
|
+
* numeric id — only for the workspace's own project, since ids are per project.
|
|
73
|
+
*/
|
|
74
|
+
function promptRef(raw, project) {
|
|
75
|
+
if (/^\d+$/.test(raw))
|
|
76
|
+
return raw;
|
|
77
|
+
const root = (0, workspace_1.findRoot)();
|
|
78
|
+
const dir = raw.replace(/\/+$/, '');
|
|
79
|
+
const key = dir.startsWith('prompts/') ? dir : `prompts/${dir}`;
|
|
80
|
+
// A broken workspace surfaces its own (informative) CliError.
|
|
81
|
+
const workspaceProject = root ? (0, workspace_1.readProject)(root) : undefined;
|
|
82
|
+
const entry = root ? (0, workspace_1.readState)(root, workspaceProject).resources[key] : undefined;
|
|
83
|
+
if (!entry || entry.kind !== 'prompt') {
|
|
84
|
+
throw new errors_1.CliError(`Unknown prompt "${raw}": use the numeric id, or a prompts/<dir> tracked in .metrio/state.json`);
|
|
85
|
+
}
|
|
86
|
+
if (project !== undefined && project !== workspaceProject) {
|
|
87
|
+
throw new errors_1.CliError(`"${raw}" is a directory of project ${workspaceProject}, but this command targets project ${String(project)}`);
|
|
88
|
+
}
|
|
89
|
+
return entry.id;
|
|
90
|
+
}
|
|
69
91
|
/** Runs one registry command; argv starts with the group, e.g. ['version', 'create', '12', '-f', 's.md']. */
|
|
70
92
|
async function runManageCommand(argv, readStdin) {
|
|
71
93
|
const match = (0, registry_1.findCommand)(argv);
|
|
@@ -107,7 +129,14 @@ async function runManageCommand(argv, readStdin) {
|
|
|
107
129
|
put('project_slug', opts.project, 'project');
|
|
108
130
|
for (const [k, v] of Object.entries(values))
|
|
109
131
|
put(k, v, `--${(0, schema_1.kebab)(k)}`);
|
|
110
|
-
|
|
132
|
+
// A prompt directory resolves against the final project_slug, so it waits for it.
|
|
133
|
+
let promptDir;
|
|
134
|
+
expected.forEach((arg, i) => {
|
|
135
|
+
if (arg.prop === 'prompt_external_id' && !/^\d+$/.test(positionals[i]))
|
|
136
|
+
promptDir = { prop: arg.prop, raw: positionals[i], label: arg.label };
|
|
137
|
+
else
|
|
138
|
+
put(arg.prop, (0, schema_1.coercePositional)(positionals[i], props[arg.prop], arg.label), arg.label);
|
|
139
|
+
});
|
|
111
140
|
if ('project_slug' in props && !opts.project && args.project_slug === undefined) {
|
|
112
141
|
try {
|
|
113
142
|
const project = (0, project_1.resolveProject)(undefined);
|
|
@@ -119,6 +148,9 @@ async function runManageCommand(argv, readStdin) {
|
|
|
119
148
|
throw err;
|
|
120
149
|
}
|
|
121
150
|
}
|
|
151
|
+
if (promptDir) {
|
|
152
|
+
put(promptDir.prop, (0, schema_1.coercePositional)(promptRef(promptDir.raw, args.project_slug), props[promptDir.prop], promptDir.label), promptDir.label);
|
|
153
|
+
}
|
|
122
154
|
if ((schema.required ?? []).includes('project_slug') && args.project_slug === undefined) {
|
|
123
155
|
throw new errors_1.CliError('No project. Pass --project <slug>, set METRIO_PROJECT, or add "project: <slug>" to metrio.yaml.');
|
|
124
156
|
}
|