@martini-film/cli 0.3.0 → 0.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 +165 -44
- package/dist/api.js +57 -17
- package/dist/api.js.map +1 -1
- package/dist/approve.js +2 -1
- package/dist/approve.js.map +1 -1
- package/dist/cli.js +429 -144
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.js +141 -25
- package/dist/commands/assets.js.map +1 -1
- package/dist/commands/call.js +99 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/context.js +15 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/files.js +100 -7
- package/dist/commands/files.js.map +1 -1
- package/dist/commands/guide.js +67 -0
- package/dist/commands/guide.js.map +1 -0
- package/dist/commands/init.js +196 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.js +37 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/tools.js +63 -78
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/use.js +143 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/config.js +82 -6
- package/dist/config.js.map +1 -1
- package/dist/generated/guide.json +104 -0
- package/dist/generated/manifest.json +10 -2
- package/dist/guideCache.js +204 -0
- package/dist/guideCache.js.map +1 -0
- package/dist/help.js +375 -32
- package/dist/help.js.map +1 -1
- package/dist/manifest.js +27 -0
- package/dist/manifest.js.map +1 -1
- package/dist/manifestCache.js +196 -0
- package/dist/manifestCache.js.map +1 -0
- package/dist/output.js +210 -28
- package/dist/output.js.map +1 -1
- package/dist/schema.js +99 -14
- package/dist/schema.js.map +1 -1
- package/dist/wait.js +31 -1
- package/dist/wait.js.map +1 -1
- package/package.json +2 -2
package/dist/manifest.js
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import manifestJson from './generated/manifest.json' with { type: 'json' };
|
|
2
|
+
export const TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{1,80}$/;
|
|
2
3
|
export function loadManifest() {
|
|
3
4
|
return manifestJson;
|
|
4
5
|
}
|
|
5
6
|
export function findTool(name, manifest = loadManifest()) {
|
|
6
7
|
return manifest.tools.find(tool => tool.name === name);
|
|
7
8
|
}
|
|
9
|
+
export function compareManifests(build, server) {
|
|
10
|
+
const built = new Map(build.tools.map(tool => [tool.name, tool]));
|
|
11
|
+
const served = new Map(server.tools.map(tool => [tool.name, tool]));
|
|
12
|
+
const sorted = (names) => [...names].sort((a, b) => a.localeCompare(b));
|
|
13
|
+
return {
|
|
14
|
+
serverOnly: sorted([...served.keys()].filter(name => !built.has(name))),
|
|
15
|
+
buildOnly: sorted([...built.keys()].filter(name => !served.has(name))),
|
|
16
|
+
changed: sorted([...served.values()]
|
|
17
|
+
.filter(tool => {
|
|
18
|
+
const local = built.get(tool.name);
|
|
19
|
+
return local !== undefined && canonicalJson(local.inputSchema) !== canonicalJson(tool.inputSchema);
|
|
20
|
+
})
|
|
21
|
+
.map(tool => tool.name)),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function canonicalJson(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
27
|
+
if (value && typeof value === 'object') {
|
|
28
|
+
const entries = Object.entries(value)
|
|
29
|
+
.filter(([, item]) => item !== undefined)
|
|
30
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
31
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
|
|
32
|
+
}
|
|
33
|
+
return JSON.stringify(value) ?? 'null';
|
|
34
|
+
}
|
|
8
35
|
export function groupTools(tools) {
|
|
9
36
|
const groups = new Map();
|
|
10
37
|
for (const tool of tools) {
|
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { cacheDir, isRunKey } from './config.js';
|
|
5
|
+
export const MANIFEST_REFRESH_ENV = 'MARTINI_MANIFEST_REFRESH';
|
|
6
|
+
const CACHE_FORMAT = 1;
|
|
7
|
+
function digest(value, length) {
|
|
8
|
+
return createHash('sha256').update(value).digest('hex').slice(0, length);
|
|
9
|
+
}
|
|
10
|
+
export function keyFingerprint(apiKey) {
|
|
11
|
+
return digest(apiKey, 16);
|
|
12
|
+
}
|
|
13
|
+
export function credentialCachePath(kind, env, apiUrl, apiKey) {
|
|
14
|
+
return join(cacheDir(env), `${kind}-${digest(apiUrl, 12)}-${keyFingerprint(apiKey)}.json`);
|
|
15
|
+
}
|
|
16
|
+
export function manifestCachePath(env, apiUrl, apiKey) {
|
|
17
|
+
return credentialCachePath('manifest', env, apiUrl, apiKey);
|
|
18
|
+
}
|
|
19
|
+
export function refreshDisabled(env) {
|
|
20
|
+
const value = env[MANIFEST_REFRESH_ENV]?.trim().toLowerCase();
|
|
21
|
+
return value === '0' || value === 'false' || value === 'no' || value === 'off';
|
|
22
|
+
}
|
|
23
|
+
export function openManifest(options) {
|
|
24
|
+
const { baked, apiUrl, apiKey, env, warn } = options;
|
|
25
|
+
const now = options.now ?? Date.now;
|
|
26
|
+
const runKey = isRunKey(apiKey);
|
|
27
|
+
const cachePath = apiKey && !runKey ? manifestCachePath(env, apiUrl, apiKey) : null;
|
|
28
|
+
const fingerprint = apiKey ? keyFingerprint(apiKey) : '';
|
|
29
|
+
let cached = cachePath ? readCache(cachePath, apiUrl, fingerprint, warn) : null;
|
|
30
|
+
let bakedConfirmed = false;
|
|
31
|
+
return {
|
|
32
|
+
get state() {
|
|
33
|
+
if (cached) {
|
|
34
|
+
return {
|
|
35
|
+
manifest: cached.manifest,
|
|
36
|
+
source: 'cache',
|
|
37
|
+
baked,
|
|
38
|
+
server: cached.manifest,
|
|
39
|
+
fetchedAt: cached.fetchedAt,
|
|
40
|
+
cachePath,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { manifest: baked, source: 'baked', baked, server: bakedConfirmed ? baked : null, cachePath };
|
|
44
|
+
},
|
|
45
|
+
async refresh({ force = false } = {}) {
|
|
46
|
+
if (runKey)
|
|
47
|
+
return 'skipped';
|
|
48
|
+
if (refreshDisabled(env)) {
|
|
49
|
+
if (force) {
|
|
50
|
+
warn('MANIFEST_REFRESH_DISABLED', `${MANIFEST_REFRESH_ENV}=${env[MANIFEST_REFRESH_ENV]} turns refreshes off, so the tool list was not fetched. Unset it to refresh.`);
|
|
51
|
+
}
|
|
52
|
+
return 'skipped';
|
|
53
|
+
}
|
|
54
|
+
const inUse = cached ? `the cached list from ${cached.fetchedAt}` : "this build's list";
|
|
55
|
+
let answer;
|
|
56
|
+
try {
|
|
57
|
+
answer = await options.fetchManifest(force ? undefined : (cached?.manifest.hash ?? baked.hash));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
warn('MANIFEST_REFRESH_FAILED', `Could not refresh the tool list from ${apiUrl} (${messageOf(error)}); commands use ${inUse}.`);
|
|
61
|
+
return 'failed';
|
|
62
|
+
}
|
|
63
|
+
if (answer.notModified) {
|
|
64
|
+
if (!cached)
|
|
65
|
+
bakedConfirmed = true;
|
|
66
|
+
return 'unchanged';
|
|
67
|
+
}
|
|
68
|
+
const manifest = usableManifest(answer.body, answer.etag);
|
|
69
|
+
if (!manifest) {
|
|
70
|
+
warn('MANIFEST_REFRESH_FAILED', `The server answered GET /v1/tools without a usable tool list; commands use ${inUse}.`);
|
|
71
|
+
return 'failed';
|
|
72
|
+
}
|
|
73
|
+
cached = {
|
|
74
|
+
format: CACHE_FORMAT,
|
|
75
|
+
apiUrl,
|
|
76
|
+
keyFingerprint: fingerprint,
|
|
77
|
+
fetchedAt: new Date(now()).toISOString(),
|
|
78
|
+
manifest,
|
|
79
|
+
};
|
|
80
|
+
if (cachePath)
|
|
81
|
+
writeCache(cachePath, cached, warn);
|
|
82
|
+
return 'updated';
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function isRecord(value) {
|
|
87
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
function isUsableSchema(schema, depth = 0) {
|
|
90
|
+
if (!isRecord(schema))
|
|
91
|
+
return false;
|
|
92
|
+
if (depth > 16)
|
|
93
|
+
return true;
|
|
94
|
+
const { properties, required, items, additionalProperties } = schema;
|
|
95
|
+
if (properties !== undefined &&
|
|
96
|
+
(!isRecord(properties) || !Object.values(properties).every(child => isUsableSchema(child, depth + 1))))
|
|
97
|
+
return false;
|
|
98
|
+
if (required !== undefined && (!Array.isArray(required) || !required.every(item => typeof item === 'string')))
|
|
99
|
+
return false;
|
|
100
|
+
if (items !== undefined && !isUsableSchema(items, depth + 1))
|
|
101
|
+
return false;
|
|
102
|
+
if (isRecord(additionalProperties) && !isUsableSchema(additionalProperties, depth + 1))
|
|
103
|
+
return false;
|
|
104
|
+
for (const key of ['anyOf', 'oneOf', 'allOf']) {
|
|
105
|
+
const variants = schema[key];
|
|
106
|
+
if (variants !== undefined &&
|
|
107
|
+
(!Array.isArray(variants) || !variants.every(child => isUsableSchema(child, depth + 1))))
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function isUsableTool(tool) {
|
|
113
|
+
if (!isRecord(tool) || typeof tool.name !== 'string' || typeof tool.description !== 'string')
|
|
114
|
+
return false;
|
|
115
|
+
if (!isRecord(tool.annotations))
|
|
116
|
+
return false;
|
|
117
|
+
return isUsableSchema(tool.inputSchema);
|
|
118
|
+
}
|
|
119
|
+
function usableManifest(body, etag) {
|
|
120
|
+
if (!isRecord(body))
|
|
121
|
+
return null;
|
|
122
|
+
const hash = typeof body.hash === 'string' && body.hash ? body.hash : etag;
|
|
123
|
+
if (!hash || !Array.isArray(body.tools) || body.tools.length === 0 || !body.tools.every(isUsableTool))
|
|
124
|
+
return null;
|
|
125
|
+
return {
|
|
126
|
+
version: 1,
|
|
127
|
+
hash,
|
|
128
|
+
generatedAt: typeof body.generatedAt === 'string' ? body.generatedAt : '',
|
|
129
|
+
tools: body.tools,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function messageOf(error) {
|
|
133
|
+
return error instanceof Error ? error.message : String(error);
|
|
134
|
+
}
|
|
135
|
+
function readCache(path, apiUrl, fingerprint, warn) {
|
|
136
|
+
let text;
|
|
137
|
+
try {
|
|
138
|
+
text = readFileSync(path, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const code = error.code;
|
|
142
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
143
|
+
return null;
|
|
144
|
+
warn('MANIFEST_CACHE_IGNORED', `Could not read the cached tool list ${path} (${messageOf(error)}); commands use this build's list.`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
let entry;
|
|
148
|
+
try {
|
|
149
|
+
entry = JSON.parse(text);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
entry = null;
|
|
153
|
+
}
|
|
154
|
+
if (isRecord(entry) && entry.format !== undefined && entry.format !== CACHE_FORMAT)
|
|
155
|
+
return null;
|
|
156
|
+
const manifest = isRecord(entry) ? usableManifest(entry.manifest, null) : null;
|
|
157
|
+
if (!isRecord(entry) ||
|
|
158
|
+
!manifest ||
|
|
159
|
+
entry.apiUrl !== apiUrl ||
|
|
160
|
+
entry.keyFingerprint !== fingerprint ||
|
|
161
|
+
typeof entry.fetchedAt !== 'string') {
|
|
162
|
+
try {
|
|
163
|
+
rmSync(path, { force: true });
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
}
|
|
167
|
+
warn('MANIFEST_CACHE_IGNORED', `The cached tool list ${path} was not valid and has been removed; commands use this build's list until the next refresh.`);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return { format: CACHE_FORMAT, apiUrl, keyFingerprint: fingerprint, fetchedAt: entry.fetchedAt, manifest };
|
|
171
|
+
}
|
|
172
|
+
export function writePrivateFileAtomic(path, text) {
|
|
173
|
+
const temp = `${path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
|
|
174
|
+
try {
|
|
175
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
176
|
+
writeFileSync(temp, text, { mode: 0o600 });
|
|
177
|
+
renameSync(temp, path);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
try {
|
|
181
|
+
rmSync(temp, { force: true });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function writeCache(path, entry, warn) {
|
|
189
|
+
try {
|
|
190
|
+
writePrivateFileAtomic(path, JSON.stringify(entry));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
warn('MANIFEST_CACHE_WRITE_FAILED', `Could not save the tool list to ${path} (${messageOf(error)}); only this command uses it.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=manifestCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifestCache.js","sourceRoot":"","sources":["../src/manifestCache.ts"],"names":[],"mappings":"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"}
|
package/dist/output.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writeFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { extname } from 'node:path';
|
|
3
|
+
import { CliApiError, isMediaBlock as isMediaContentBlock } from './api.js';
|
|
3
4
|
import { ArgumentError } from './schema.js';
|
|
4
5
|
import { WaitTimeoutError } from './wait.js';
|
|
5
6
|
export const EXIT = {
|
|
@@ -19,11 +20,17 @@ function writeError(options, text) {
|
|
|
19
20
|
(options.stderr ?? (line => process.stderr.write(line)))(text);
|
|
20
21
|
}
|
|
21
22
|
export function printResult(value, options = {}) {
|
|
23
|
+
if (options.out && isMediaOutPath(options.out)) {
|
|
24
|
+
const { out, ...rest } = options;
|
|
25
|
+
printWarning('OUT_NOT_MEDIA', `This result is JSON, not media, so nothing was written to ${out}; it is printed here.`, rest);
|
|
26
|
+
printResult(value, rest);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
if (options.out) {
|
|
23
30
|
const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2) + '\n';
|
|
24
31
|
writeFileSync(options.out, text);
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
const { out: _out, ...receiptOptions } = options;
|
|
33
|
+
printResult({ path: options.out, bytes: Buffer.byteLength(text), summary: summarize(value) }, receiptOptions);
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
29
36
|
if (options.quiet)
|
|
@@ -36,7 +43,43 @@ export function printResult(value, options = {}) {
|
|
|
36
43
|
const printable = withoutMediaBytes(value);
|
|
37
44
|
write(options, (tty ? JSON.stringify(printable, null, 2) : JSON.stringify(printable)) + '\n');
|
|
38
45
|
}
|
|
39
|
-
export const MEDIA_BYTES_HINT = 'The bytes are not printed.
|
|
46
|
+
export const MEDIA_BYTES_HINT = 'The bytes are not printed. Save them with --out <file.jpg|.png|.webp|.mp3|.wav> (a result with one media block; --raw does the same for any file name) or --out-dir <dir> (every block, read-only tools); --out <file.json> writes this JSON with the base64 kept.';
|
|
47
|
+
export const MEDIA_EXTENSIONS = {
|
|
48
|
+
'image/jpeg': 'jpg',
|
|
49
|
+
'image/png': 'png',
|
|
50
|
+
'image/webp': 'webp',
|
|
51
|
+
'audio/mpeg': 'mp3',
|
|
52
|
+
'audio/wav': 'wav',
|
|
53
|
+
};
|
|
54
|
+
const MEDIA_OUT_EXTENSIONS = new Set([
|
|
55
|
+
'jpg',
|
|
56
|
+
'jpeg',
|
|
57
|
+
'png',
|
|
58
|
+
'webp',
|
|
59
|
+
'gif',
|
|
60
|
+
'mp3',
|
|
61
|
+
'wav',
|
|
62
|
+
'm4a',
|
|
63
|
+
'aac',
|
|
64
|
+
'flac',
|
|
65
|
+
'ogg',
|
|
66
|
+
'mp4',
|
|
67
|
+
'mov',
|
|
68
|
+
'webm',
|
|
69
|
+
]);
|
|
70
|
+
function extensionOf(path) {
|
|
71
|
+
return extname(path).slice(1).toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
export function isMediaOutPath(path) {
|
|
74
|
+
return MEDIA_OUT_EXTENSIONS.has(extensionOf(path));
|
|
75
|
+
}
|
|
76
|
+
export function mismatchedExtension(path, contentType) {
|
|
77
|
+
const expected = MEDIA_EXTENSIONS[contentType.split(';')[0].trim().toLowerCase()];
|
|
78
|
+
const actual = extensionOf(path);
|
|
79
|
+
if (!expected || !MEDIA_OUT_EXTENSIONS.has(actual))
|
|
80
|
+
return null;
|
|
81
|
+
return actual === expected || (expected === 'jpg' && actual === 'jpeg') ? null : expected;
|
|
82
|
+
}
|
|
40
83
|
function isMediaBlock(block) {
|
|
41
84
|
return block !== null && typeof block === 'object' && typeof block.data === 'string';
|
|
42
85
|
}
|
|
@@ -61,6 +104,11 @@ function withoutMediaBytes(value) {
|
|
|
61
104
|
: block),
|
|
62
105
|
};
|
|
63
106
|
}
|
|
107
|
+
export function noteMediaStubs(blocks, options = {}) {
|
|
108
|
+
if (blocks.some(block => isMediaContentBlock(block) && !('data' in block))) {
|
|
109
|
+
printWarning('MEDIA_NOT_PRINTED', MEDIA_BYTES_HINT, options);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
64
112
|
export function assertRawDestination(options) {
|
|
65
113
|
if (options.out)
|
|
66
114
|
return;
|
|
@@ -71,8 +119,19 @@ export function assertRawDestination(options) {
|
|
|
71
119
|
export function printBytes(bytes, options) {
|
|
72
120
|
if (options.out) {
|
|
73
121
|
writeFileSync(options.out, bytes);
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
const expected = options.contentType ? mismatchedExtension(options.out, options.contentType) : null;
|
|
123
|
+
if (expected) {
|
|
124
|
+
printWarning('EXTENSION_MISMATCH', `${options.out} holds ${options.contentType} bytes; a viewer that trusts the extension may refuse it. Name the file .${expected}.`, options);
|
|
125
|
+
}
|
|
126
|
+
const { out: _out, contentType, meta, fields, ...receiptOptions } = options;
|
|
127
|
+
const receipt = {
|
|
128
|
+
path: options.out,
|
|
129
|
+
bytes: bytes.byteLength,
|
|
130
|
+
...(contentType ? { contentType } : {}),
|
|
131
|
+
...(meta !== null && meta !== undefined ? { result: meta } : {}),
|
|
132
|
+
};
|
|
133
|
+
const trimmed = fields?.length ? selectFields(receipt, fields) : receipt;
|
|
134
|
+
printResult(fields?.length && isEmptyProjection(trimmed) ? receipt : trimmed, receiptOptions);
|
|
76
135
|
return;
|
|
77
136
|
}
|
|
78
137
|
assertRawDestination(options);
|
|
@@ -83,21 +142,121 @@ export function printWarning(code, message, options = {}) {
|
|
|
83
142
|
return;
|
|
84
143
|
writeError(options, JSON.stringify({ warning: { code, message } }) + '\n');
|
|
85
144
|
}
|
|
86
|
-
export function
|
|
87
|
-
if (
|
|
88
|
-
return;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
145
|
+
export function isEmptyProjection(result) {
|
|
146
|
+
if (result === null || typeof result !== 'object')
|
|
147
|
+
return false;
|
|
148
|
+
return Object.keys(result).length === 0 || !holdsValue(result);
|
|
149
|
+
}
|
|
150
|
+
export function unmatchedFields(result, fields) {
|
|
151
|
+
return fields.filter(path => !holdsValue(project(result, path.split('.'))));
|
|
152
|
+
}
|
|
153
|
+
function holdsValue(value) {
|
|
154
|
+
if (value === undefined)
|
|
155
|
+
return false;
|
|
156
|
+
if (Array.isArray(value))
|
|
157
|
+
return value.length === 0 || value.some(item => item !== null && holdsValue(item));
|
|
158
|
+
if (value !== null && typeof value === 'object') {
|
|
159
|
+
const values = Object.values(value);
|
|
160
|
+
return values.length === 0 || values.some(holdsValue);
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
export function noFieldsMatchedMessage(fields, knownFields = []) {
|
|
92
165
|
const shape = knownFields.length
|
|
93
|
-
? `The result's top-level fields are: ${knownFields.join(', ')} (
|
|
166
|
+
? `The result's top-level fields are: ${knownFields.join(', ')} (--help on this command lists the nested ones).`
|
|
94
167
|
: 'Run once without --fields to see the shape.';
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
168
|
+
return `--fields ${fields.join(',')} matched nothing in the result. Paths are dotted and case-sensitive (asset.imageUrl, nodes.nodeId); a field that is a map keyed by id takes a * step, quoted for the shell ('assets.*.status'). ${shape}`;
|
|
169
|
+
}
|
|
170
|
+
export function warnUnmatchedFields(result, fields, options = {}, schema) {
|
|
171
|
+
if (fields.length < 2)
|
|
172
|
+
return;
|
|
173
|
+
const unmatched = unmatchedFields(result, fields);
|
|
174
|
+
const missing = unmatched.filter(path => !isDeclaredPath(path, schema));
|
|
175
|
+
if (missing.length === 0 || unmatched.length === fields.length)
|
|
176
|
+
return;
|
|
177
|
+
printWarning('FIELDS_NOT_FOUND', `--fields ${missing.join(',')} matched nothing in the result; the other paths are printed. Check the spelling; --help on this command lists the result fields it declares.`, options);
|
|
178
|
+
}
|
|
179
|
+
function walkMapPath(path, schema) {
|
|
180
|
+
const out = [];
|
|
181
|
+
const everyRecord = [];
|
|
182
|
+
let undeclaredMapStep = false;
|
|
183
|
+
let node = schema;
|
|
184
|
+
for (const segment of path.split('.')) {
|
|
185
|
+
while (node && node.type === 'array' && node.items)
|
|
186
|
+
node = node.items;
|
|
187
|
+
if (node && segment !== '*' && isIdKeyedMap(node)) {
|
|
188
|
+
let values = node.additionalProperties;
|
|
189
|
+
while (values && values.type === 'array' && values.items)
|
|
190
|
+
values = values.items;
|
|
191
|
+
everyRecord.push('*');
|
|
192
|
+
if (values?.properties?.[segment] === undefined) {
|
|
193
|
+
undeclaredMapStep = true;
|
|
194
|
+
out.push(segment);
|
|
195
|
+
everyRecord.push(segment);
|
|
196
|
+
node = values;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
out.push('*');
|
|
200
|
+
node = values;
|
|
201
|
+
}
|
|
202
|
+
out.push(segment);
|
|
203
|
+
everyRecord.push(segment);
|
|
204
|
+
if (!node)
|
|
205
|
+
continue;
|
|
206
|
+
node =
|
|
207
|
+
segment === '*'
|
|
208
|
+
? typeof node.additionalProperties === 'object'
|
|
209
|
+
? node.additionalProperties
|
|
210
|
+
: undefined
|
|
211
|
+
: node.properties?.[segment];
|
|
212
|
+
}
|
|
213
|
+
return { path: out.join('.'), everyRecord: everyRecord.join('.'), undeclaredMapStep };
|
|
214
|
+
}
|
|
215
|
+
export function expandMapPaths(paths, schema) {
|
|
216
|
+
if (!schema)
|
|
217
|
+
return [...paths];
|
|
218
|
+
return paths.map(path => walkMapPath(path, schema).path);
|
|
219
|
+
}
|
|
220
|
+
export function hasUndeclaredMapStep(paths, schema) {
|
|
221
|
+
return schema !== undefined && paths.some(path => walkMapPath(path, schema).undeclaredMapStep);
|
|
222
|
+
}
|
|
223
|
+
export function resolveMapPaths(result, paths, schema) {
|
|
224
|
+
if (!schema)
|
|
225
|
+
return [...paths];
|
|
226
|
+
return paths.map(path => {
|
|
227
|
+
const walked = walkMapPath(path, schema);
|
|
228
|
+
if (!walked.undeclaredMapStep)
|
|
229
|
+
return walked.path;
|
|
230
|
+
return isEmptyProjection(selectFields(result, [walked.path])) ? walked.everyRecord : walked.path;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
export function isDeclaredPath(path, schema) {
|
|
234
|
+
let node = schema;
|
|
235
|
+
for (const segment of path.split('.')) {
|
|
236
|
+
while (node && node.type === 'array' && node.items)
|
|
237
|
+
node = node.items;
|
|
238
|
+
if (!node)
|
|
239
|
+
return false;
|
|
240
|
+
const variants = node.anyOf ?? node.oneOf ?? [node];
|
|
241
|
+
const next = variants
|
|
242
|
+
.map((variant) => {
|
|
243
|
+
if (segment === '*')
|
|
244
|
+
return typeof variant.additionalProperties === 'object' ? variant.additionalProperties : undefined;
|
|
245
|
+
return variant.properties?.[segment];
|
|
246
|
+
})
|
|
247
|
+
.find(found => found !== undefined);
|
|
248
|
+
if (next === undefined)
|
|
249
|
+
return false;
|
|
250
|
+
node = next;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
function isIdKeyedMap(schema) {
|
|
255
|
+
const values = schema.additionalProperties;
|
|
256
|
+
return (Object.keys(schema.properties ?? {}).length === 0 &&
|
|
257
|
+
values !== null &&
|
|
258
|
+
typeof values === 'object' &&
|
|
259
|
+
Object.keys(values.properties ?? {}).length > 0);
|
|
101
260
|
}
|
|
102
261
|
export function selectFields(value, paths) {
|
|
103
262
|
if (paths.length === 0)
|
|
@@ -128,13 +287,20 @@ function project(value, segments) {
|
|
|
128
287
|
}
|
|
129
288
|
return Object.keys(out).length ? out : undefined;
|
|
130
289
|
}
|
|
131
|
-
if (!Object.prototype.hasOwnProperty.call(value, head))
|
|
132
|
-
return undefined;
|
|
290
|
+
if (!Object.prototype.hasOwnProperty.call(value, head)) {
|
|
291
|
+
return isIdKeyedData(value) ? project(value, ['*', ...segments]) : undefined;
|
|
292
|
+
}
|
|
133
293
|
const inner = project(value[head], rest);
|
|
134
294
|
return inner === undefined ? undefined : { [head]: inner };
|
|
135
295
|
}
|
|
136
296
|
return undefined;
|
|
137
297
|
}
|
|
298
|
+
const ID_KEY = /^(?:[a-z]+-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
299
|
+
function isIdKeyedData(value) {
|
|
300
|
+
const entries = Object.entries(value);
|
|
301
|
+
return (entries.length > 0 &&
|
|
302
|
+
entries.every(([key, item]) => ID_KEY.test(key) && item !== null && typeof item === 'object' && !Array.isArray(item)));
|
|
303
|
+
}
|
|
138
304
|
function merge(a, b) {
|
|
139
305
|
if (a === undefined)
|
|
140
306
|
return b;
|
|
@@ -179,7 +345,11 @@ export function exitCodeFor(error) {
|
|
|
179
345
|
/^(INVALID_API_KEY|API_KEY_REVOKED|API_KEY_EXPIRED|API_KEY_DEFAULTS_MISSING)$/.test(error.code)) {
|
|
180
346
|
return EXIT.auth;
|
|
181
347
|
}
|
|
182
|
-
if (error.code === 'INSUFFICIENT_CREDITS' ||
|
|
348
|
+
if (error.code === 'INSUFFICIENT_CREDITS' ||
|
|
349
|
+
error.code === 'INSUFFICIENT_OLIVES' ||
|
|
350
|
+
error.status === 402 ||
|
|
351
|
+
error.code === 'PLAN_OVER_BUDGET' ||
|
|
352
|
+
error.code === 'PLAN_UNPRICED') {
|
|
183
353
|
return EXIT.insufficientCredits;
|
|
184
354
|
}
|
|
185
355
|
if (/_NOT_READY$/.test(error.code) ||
|
|
@@ -187,7 +357,10 @@ export function exitCodeFor(error) {
|
|
|
187
357
|
error.code === 'RUN_NOT_ACTIVE' ||
|
|
188
358
|
error.code === 'LOGIN_PENDING')
|
|
189
359
|
return EXIT.notReady;
|
|
190
|
-
if (error.code === 'INVALID_ARGUMENTS' ||
|
|
360
|
+
if (error.code === 'INVALID_ARGUMENTS' ||
|
|
361
|
+
error.code === 'INVALID_REQUEST' ||
|
|
362
|
+
error.code === 'TOOL_NOT_FOUND' ||
|
|
363
|
+
error.code === 'NO_FIELDS_MATCHED') {
|
|
191
364
|
return EXIT.usage;
|
|
192
365
|
}
|
|
193
366
|
return EXIT.toolError;
|
|
@@ -196,6 +369,8 @@ export function exitCodeFor(error) {
|
|
|
196
369
|
}
|
|
197
370
|
const RUN_CREDENTIAL_GONE_HINT = 'This execution’s run credential is expired or revoked. Stop and report it; do not run martini login. Retrying or resuming the run provisions a new one.';
|
|
198
371
|
export function hintFor(error, context = {}) {
|
|
372
|
+
if (error instanceof CliApiError && error.hint)
|
|
373
|
+
return error.hint;
|
|
199
374
|
if (error instanceof WaitTimeoutError) {
|
|
200
375
|
return `Still running; nothing was lost. Resume with: ${error.resumeCommand()} — give --timeout the seconds your shell step allows (a video generation takes minutes).`;
|
|
201
376
|
}
|
|
@@ -224,6 +399,10 @@ export function hintFor(error, context = {}) {
|
|
|
224
399
|
: 'Run keys work only while their workflow run is executing.';
|
|
225
400
|
case 'INSUFFICIENT_CREDITS':
|
|
226
401
|
return 'Top up olives in Martini, or lower the batch.';
|
|
402
|
+
case 'PLAN_OVER_BUDGET':
|
|
403
|
+
return 'The quote is on stdout. Ask the person before raising --max-olives, or make the batch smaller.';
|
|
404
|
+
case 'NO_FIELDS_MATCHED':
|
|
405
|
+
return 'Nothing was changed; run the command again with paths the result has.';
|
|
227
406
|
case 'FFPROBE_MISSING':
|
|
228
407
|
return 'Install ffmpeg (it ships ffprobe) and make sure it is on PATH.';
|
|
229
408
|
default:
|
|
@@ -232,12 +411,17 @@ export function hintFor(error, context = {}) {
|
|
|
232
411
|
return undefined;
|
|
233
412
|
}
|
|
234
413
|
}
|
|
235
|
-
if (error instanceof ArgumentError)
|
|
236
|
-
return
|
|
414
|
+
if (error instanceof ArgumentError) {
|
|
415
|
+
return error.hint ?? (context.command ? `Run martini ${context.command} --help.` : 'Run martini --help.');
|
|
416
|
+
}
|
|
237
417
|
return undefined;
|
|
238
418
|
}
|
|
239
419
|
export function printError(error, options = {}) {
|
|
240
420
|
const code = exitCodeFor(error);
|
|
421
|
+
const hint = hintFor(error, {
|
|
422
|
+
inRun: options.inRun ?? false,
|
|
423
|
+
...(options.command ? { command: options.command } : {}),
|
|
424
|
+
});
|
|
241
425
|
const payload = {
|
|
242
426
|
error: {
|
|
243
427
|
code: error instanceof CliApiError ? error.code : error instanceof ArgumentError ? 'USAGE' : 'CLI_ERROR',
|
|
@@ -245,9 +429,7 @@ export function printError(error, options = {}) {
|
|
|
245
429
|
...(error instanceof CliApiError && error.tool ? { tool: error.tool } : {}),
|
|
246
430
|
...(error instanceof WaitTimeoutError ? { pending: error.pending } : {}),
|
|
247
431
|
...(error instanceof WaitTimeoutError && error.settled.length ? { settled: error.settled } : {}),
|
|
248
|
-
...(
|
|
249
|
-
? { hint: hintFor(error, { inRun: options.inRun ?? false }) }
|
|
250
|
-
: {}),
|
|
432
|
+
...(hint ? { hint } : {}),
|
|
251
433
|
exitCode: code,
|
|
252
434
|
},
|
|
253
435
|
};
|