@metrio-ai/cli 1.0.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/LICENSE +29 -0
- package/README.md +38 -0
- package/dist/auth/credentials.js +81 -0
- package/dist/auth/login.js +143 -0
- package/dist/auth/oauth.js +44 -0
- package/dist/commands/auth.js +74 -0
- package/dist/commands/chat.js +235 -0
- package/dist/commands/prompt-create-version.js +211 -0
- package/dist/commands/prompt.js +284 -0
- package/dist/commands/shared.js +328 -0
- package/dist/commands/tools.js +114 -0
- package/dist/commands/workflow.js +666 -0
- package/dist/console/client.js +108 -0
- package/dist/errors.js +27 -0
- package/dist/index.js +105 -0
- package/package.json +41 -0
|
@@ -0,0 +1,328 @@
|
|
|
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.readMessagesFile = readMessagesFile;
|
|
37
|
+
exports.buildMessages = buildMessages;
|
|
38
|
+
exports.loadEnv = loadEnv;
|
|
39
|
+
exports.resolveApiKey = resolveApiKey;
|
|
40
|
+
exports.resolveBaseUrl = resolveBaseUrl;
|
|
41
|
+
exports.parseJsonFlag = parseJsonFlag;
|
|
42
|
+
exports.resolvePromptId = resolvePromptId;
|
|
43
|
+
exports.parseJsonlObjects = parseJsonlObjects;
|
|
44
|
+
exports.caseIdFor = caseIdFor;
|
|
45
|
+
exports.readCasesSource = readCasesSource;
|
|
46
|
+
exports.mapWithConcurrency = mapWithConcurrency;
|
|
47
|
+
exports.emitBatchResults = emitBatchResults;
|
|
48
|
+
/**
|
|
49
|
+
* Shared CLI plumbing: env-file loading, credential resolution, and the
|
|
50
|
+
* conversion from the user-facing messages file format into the SDK's message
|
|
51
|
+
* shape. Every `metrio` subcommand builds on these.
|
|
52
|
+
*/
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
/** Maps common file extensions to a MetrioAI file content type and MIME type. */
|
|
56
|
+
const FILE_TYPES = {
|
|
57
|
+
'.png': { type: 'image', mime: 'image/png' },
|
|
58
|
+
'.jpg': { type: 'image', mime: 'image/jpeg' },
|
|
59
|
+
'.jpeg': { type: 'image', mime: 'image/jpeg' },
|
|
60
|
+
'.gif': { type: 'image', mime: 'image/gif' },
|
|
61
|
+
'.webp': { type: 'image', mime: 'image/webp' },
|
|
62
|
+
'.pdf': { type: 'pdf', mime: 'application/pdf' },
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Minimal `.env` loader so the CLI stays dependency-free. When `required` is
|
|
66
|
+
* true (an explicit --env file), a missing file is an error; otherwise the
|
|
67
|
+
* default `.env` is loaded if present and silently skipped if not.
|
|
68
|
+
*/
|
|
69
|
+
function loadDotEnv(envPath, required) {
|
|
70
|
+
if (!fs.existsSync(envPath)) {
|
|
71
|
+
if (required)
|
|
72
|
+
throw new Error(`Env file not found: ${envPath}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let contents;
|
|
76
|
+
try {
|
|
77
|
+
contents = fs.readFileSync(envPath, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// An explicit --env file must be readable; for the default .env, fall back
|
|
81
|
+
// to whatever is already in the environment instead of crashing.
|
|
82
|
+
if (required) {
|
|
83
|
+
throw new Error(`Cannot read env file: ${envPath} (${err.message})`);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const lines = contents.split('\n');
|
|
88
|
+
for (const rawLine of lines) {
|
|
89
|
+
const line = rawLine.trim();
|
|
90
|
+
if (!line || line.startsWith('#'))
|
|
91
|
+
continue;
|
|
92
|
+
const eq = line.indexOf('=');
|
|
93
|
+
if (eq === -1)
|
|
94
|
+
continue;
|
|
95
|
+
const key = line.slice(0, eq).trim();
|
|
96
|
+
let value = line.slice(eq + 1).trim();
|
|
97
|
+
// Strip surrounding single or double quotes.
|
|
98
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
99
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
100
|
+
value = value.slice(1, -1);
|
|
101
|
+
}
|
|
102
|
+
// Existing environment variables take precedence over the .env file.
|
|
103
|
+
if (process.env[key] === undefined) {
|
|
104
|
+
process.env[key] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Reads and validates the messages file, returning the raw input entries. */
|
|
109
|
+
function readMessagesFile(file) {
|
|
110
|
+
let raw;
|
|
111
|
+
try {
|
|
112
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new Error(`Cannot read messages file: ${file} (${err.message})`);
|
|
116
|
+
}
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new Error(`Messages file is not valid JSON: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(parsed)) {
|
|
125
|
+
throw new Error('Messages file must contain a JSON array of messages');
|
|
126
|
+
}
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
/** Builds a file content block by reading and base64-encoding the file. */
|
|
130
|
+
function buildFileContent(input, baseDir) {
|
|
131
|
+
const filePath = path.resolve(baseDir, input.file);
|
|
132
|
+
let data;
|
|
133
|
+
try {
|
|
134
|
+
data = fs.readFileSync(filePath);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
throw new Error(`Cannot read attached file: ${filePath} (${err.message})`);
|
|
138
|
+
}
|
|
139
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
140
|
+
const known = FILE_TYPES[ext];
|
|
141
|
+
const type = input.fileType ?? known?.type ?? 'binary';
|
|
142
|
+
const mime = input.mime ?? known?.mime ?? 'application/octet-stream';
|
|
143
|
+
return { type, mime, data: data.toString('base64') };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Converts user-friendly input messages into the SDK's message shape. A single
|
|
147
|
+
* input entry may expand into two messages when it carries both text and a file.
|
|
148
|
+
*/
|
|
149
|
+
function buildMessages(inputs, baseDir) {
|
|
150
|
+
const messages = [];
|
|
151
|
+
inputs.forEach((input, index) => {
|
|
152
|
+
if (!input || typeof input !== 'object') {
|
|
153
|
+
throw new Error(`Message #${index} must be an object`);
|
|
154
|
+
}
|
|
155
|
+
if (input.role !== 'system' && input.role !== 'user' && input.role !== 'assistant') {
|
|
156
|
+
throw new Error(`Message #${index} has invalid role: ${JSON.stringify(input.role)}`);
|
|
157
|
+
}
|
|
158
|
+
const text = input.content ?? input.text;
|
|
159
|
+
const hasText = typeof text === 'string' && text.trim() !== '';
|
|
160
|
+
const hasFile = typeof input.file === 'string' && input.file !== '';
|
|
161
|
+
if (!hasText && !hasFile) {
|
|
162
|
+
throw new Error(`Message #${index} must have "content" text and/or a "file"`);
|
|
163
|
+
}
|
|
164
|
+
if (hasText) {
|
|
165
|
+
messages.push({ role: input.role, content: { type: 'text', text: text } });
|
|
166
|
+
}
|
|
167
|
+
if (hasFile) {
|
|
168
|
+
messages.push({ role: input.role, content: buildFileContent(input, baseDir) });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return messages;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Loads the env file. An explicit --env file must exist; the default `.env`
|
|
175
|
+
* is loaded if present and silently skipped otherwise.
|
|
176
|
+
*/
|
|
177
|
+
function loadEnv(envFile) {
|
|
178
|
+
const envPath = envFile ? path.resolve(envFile) : path.join(process.cwd(), '.env');
|
|
179
|
+
loadDotEnv(envPath, envFile !== undefined);
|
|
180
|
+
}
|
|
181
|
+
/** Resolves the API key from a flag then the environment, throwing if absent. */
|
|
182
|
+
function resolveApiKey(flagKey) {
|
|
183
|
+
const apiKey = flagKey ||
|
|
184
|
+
process.env.METRIOAI_API_KEY ||
|
|
185
|
+
process.env.METRIO_API_KEY ||
|
|
186
|
+
process.env.METRIO_PROJECT_API_KEY;
|
|
187
|
+
if (!apiKey) {
|
|
188
|
+
throw new Error('No API key found. Set METRIOAI_API_KEY in .env or pass --api-key.');
|
|
189
|
+
}
|
|
190
|
+
return apiKey;
|
|
191
|
+
}
|
|
192
|
+
/** Resolves the base URL from a flag then the environment (undefined = default). */
|
|
193
|
+
function resolveBaseUrl(flagUrl) {
|
|
194
|
+
return flagUrl || process.env.METRIOAI_BASE_URL || process.env.METRIO_BASE_URL;
|
|
195
|
+
}
|
|
196
|
+
/** Parses a JSON CLI flag, throwing a friendly error on malformed input. */
|
|
197
|
+
function parseJsonFlag(raw, flag) {
|
|
198
|
+
try {
|
|
199
|
+
return JSON.parse(raw);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
throw new Error(`${flag} must be valid JSON: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Resolves the prompt id to run: the `--prompt-id`/`-p` flag if given,
|
|
207
|
+
* otherwise `METRIO_PROMPT_ID` or `METRIOAI_PROMPT_ID` from the environment
|
|
208
|
+
* (checked in that order). Throws if neither source provides one, or if the
|
|
209
|
+
* environment value is not a positive integer.
|
|
210
|
+
*/
|
|
211
|
+
function resolvePromptId(flagId) {
|
|
212
|
+
if (flagId !== undefined)
|
|
213
|
+
return flagId;
|
|
214
|
+
const raw = process.env.METRIO_PROMPT_ID ?? process.env.METRIOAI_PROMPT_ID;
|
|
215
|
+
if (raw !== undefined && raw.trim() !== '') {
|
|
216
|
+
const id = Number(raw);
|
|
217
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
218
|
+
throw new Error(`METRIO_PROMPT_ID must be a positive integer, got "${raw}"`);
|
|
219
|
+
}
|
|
220
|
+
return id;
|
|
221
|
+
}
|
|
222
|
+
throw new Error('Missing prompt id. Pass --prompt-id or set METRIO_PROMPT_ID in your env file.');
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Parses a JSONL cases file into objects. Blank lines and `//` comment lines
|
|
226
|
+
* are ignored so case files stay hand-editable; ignored lines still count
|
|
227
|
+
* toward the line numbers used for error messages and generated ids.
|
|
228
|
+
*
|
|
229
|
+
* Field validation belongs to each command — this only guarantees every entry
|
|
230
|
+
* is a JSON object and knows where it came from.
|
|
231
|
+
*/
|
|
232
|
+
function parseJsonlObjects(raw) {
|
|
233
|
+
const entries = [];
|
|
234
|
+
raw.split('\n').forEach((rawLine, index) => {
|
|
235
|
+
const line = rawLine.trim();
|
|
236
|
+
if (!line || line.startsWith('//'))
|
|
237
|
+
return;
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(line);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
throw new Error(`Case on line ${index + 1} is not valid JSON: ${err.message}`);
|
|
244
|
+
}
|
|
245
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
246
|
+
throw new Error(`Case on line ${index + 1} must be a JSON object`);
|
|
247
|
+
}
|
|
248
|
+
entries.push({ line: index + 1, value: parsed });
|
|
249
|
+
});
|
|
250
|
+
if (entries.length === 0) {
|
|
251
|
+
throw new Error('Cases file contains no cases');
|
|
252
|
+
}
|
|
253
|
+
return entries;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The id a case is known by: its own `id` when it has a non-empty one,
|
|
257
|
+
* otherwise `case-<line>` so every output row stays addressable.
|
|
258
|
+
*/
|
|
259
|
+
function caseIdFor(value, line) {
|
|
260
|
+
return typeof value.id === 'string' && value.id !== '' ? value.id : `case-${line}`;
|
|
261
|
+
}
|
|
262
|
+
/** Reads all of stdin as UTF-8 — backs `--cases -`. */
|
|
263
|
+
async function readStdin() {
|
|
264
|
+
const chunks = [];
|
|
265
|
+
for await (const chunk of process.stdin)
|
|
266
|
+
chunks.push(Buffer.from(chunk));
|
|
267
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
268
|
+
}
|
|
269
|
+
/** Reads a `--cases` argument, where `-` means stdin. */
|
|
270
|
+
async function readCasesSource(casesFile) {
|
|
271
|
+
if (casesFile === '-') {
|
|
272
|
+
return { raw: await readStdin(), baseDir: process.cwd() };
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
return {
|
|
276
|
+
raw: fs.readFileSync(casesFile, 'utf8'),
|
|
277
|
+
baseDir: path.dirname(path.resolve(casesFile)),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw new Error(`Cannot read cases file: ${casesFile} (${err.message})`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Runs `worker` over `items` with at most `limit` in flight, resolving to
|
|
286
|
+
* results in input order — batch output is diffed across runs, so completion
|
|
287
|
+
* order must not leak into it.
|
|
288
|
+
*/
|
|
289
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
290
|
+
const results = new Array(items.length);
|
|
291
|
+
let cursor = 0;
|
|
292
|
+
const runners = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
|
|
293
|
+
while (cursor < items.length) {
|
|
294
|
+
const index = cursor++;
|
|
295
|
+
results[index] = await worker(items[index], index);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
await Promise.all(runners);
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Emits a batch's result rows as JSONL — to `--out` when given, otherwise to
|
|
303
|
+
* stdout — then reports the tally on stderr and sets the exit code.
|
|
304
|
+
*
|
|
305
|
+
* The batch has already run by this point, so a failed `--out` write falls
|
|
306
|
+
* back to stdout rather than throwing the results away. A failing case is a
|
|
307
|
+
* result, not a crash: every row is still emitted, and only the exit code
|
|
308
|
+
* (1 when any case failed) marks the batch as unsuccessful.
|
|
309
|
+
*/
|
|
310
|
+
function emitBatchResults(rows, out) {
|
|
311
|
+
const body = `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`;
|
|
312
|
+
if (out) {
|
|
313
|
+
try {
|
|
314
|
+
fs.writeFileSync(path.resolve(out), body);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
process.stderr.write(`Warning: could not write --out file ${out} (${err.message}); printing results to stdout instead\n`);
|
|
318
|
+
process.stdout.write(body);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
process.stdout.write(body);
|
|
323
|
+
}
|
|
324
|
+
const failed = rows.filter((row) => !row.ok).length;
|
|
325
|
+
process.stderr.write(`${rows.length} cases: ${rows.length - failed} ok, ${failed} failed\n`);
|
|
326
|
+
if (failed > 0)
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
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.runToolsCommand = runToolsCommand;
|
|
37
|
+
exports.runCallCommand = runCallCommand;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const client_1 = require("../console/client");
|
|
40
|
+
const errors_1 = require("../errors");
|
|
41
|
+
const TOOLS_HELP = `metrio tools - list the console tools your login can call
|
|
42
|
+
|
|
43
|
+
Usage: metrio tools [--json]
|
|
44
|
+
`;
|
|
45
|
+
const CALL_HELP = `metrio call - call one console tool
|
|
46
|
+
|
|
47
|
+
Usage: metrio call <tool> [-f <args.json> | -f - | --args '<json>']
|
|
48
|
+
|
|
49
|
+
Arguments are a JSON object matching the tool's schema (see "metrio tools --json").
|
|
50
|
+
With no arguments, {} is sent.
|
|
51
|
+
`;
|
|
52
|
+
async function runToolsCommand(argv) {
|
|
53
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
54
|
+
process.stdout.write(TOOLS_HELP);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const asJson = argv.includes('--json');
|
|
58
|
+
const unknown = argv.find((a) => a !== '--json');
|
|
59
|
+
if (unknown)
|
|
60
|
+
throw new errors_1.CliError(`Unknown argument: ${unknown}`);
|
|
61
|
+
const tools = await new client_1.ConsoleClient(await (0, client_1.resolveConsoleAuth)()).listTools();
|
|
62
|
+
if (asJson) {
|
|
63
|
+
process.stdout.write(`${JSON.stringify(tools, null, 2)}\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const nameWidth = Math.max(...tools.map((t) => t.name.length), 0) + 2;
|
|
67
|
+
const scopeWidth = Math.max(...tools.map((t) => t.scope.length), 0) + 2;
|
|
68
|
+
for (const tool of tools) {
|
|
69
|
+
const summary = tool.description.split('\n')[0];
|
|
70
|
+
process.stdout.write(`${tool.name.padEnd(nameWidth)}${tool.scope.padEnd(scopeWidth)}${summary}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function parseArgsObject(raw, source) {
|
|
74
|
+
let value;
|
|
75
|
+
try {
|
|
76
|
+
value = JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new errors_1.CliError(`${source} must be valid JSON: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
82
|
+
throw new errors_1.CliError(`${source} must be a JSON object`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
async function runCallCommand(argv, readStdin = () => fs.readFileSync(0, 'utf8')) {
|
|
87
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
88
|
+
process.stdout.write(CALL_HELP);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const [name, ...rest] = argv;
|
|
92
|
+
if (!name || name.startsWith('-'))
|
|
93
|
+
throw new errors_1.CliError('Missing tool name. Usage: metrio call <tool> [-f args.json]');
|
|
94
|
+
let file;
|
|
95
|
+
let inline;
|
|
96
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
97
|
+
const arg = rest[i];
|
|
98
|
+
if ((arg === '-f' || arg === '--file') && rest[i + 1] !== undefined)
|
|
99
|
+
file = rest[(i += 1)];
|
|
100
|
+
else if (arg === '--args' && rest[i + 1] !== undefined)
|
|
101
|
+
inline = rest[(i += 1)];
|
|
102
|
+
else
|
|
103
|
+
throw new errors_1.CliError(`Unknown argument: ${arg}`);
|
|
104
|
+
}
|
|
105
|
+
if (file !== undefined && inline !== undefined)
|
|
106
|
+
throw new errors_1.CliError('Use either -f or --args, not both');
|
|
107
|
+
const args = file !== undefined
|
|
108
|
+
? parseArgsObject(file === '-' ? readStdin() : fs.readFileSync(file, 'utf8'), file === '-' ? 'stdin' : file)
|
|
109
|
+
: inline !== undefined
|
|
110
|
+
? parseArgsObject(inline, '--args')
|
|
111
|
+
: {};
|
|
112
|
+
const data = await new client_1.ConsoleClient(await (0, client_1.resolveConsoleAuth)()).callTool(name, args);
|
|
113
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
114
|
+
}
|