@taurgis/bonsai 3.2.0 → 4.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/README.md +9 -6
- package/agents/agents/salesforce-docs-researcher.agent.md +17 -4
- package/agents/instructions/salesforce-research.instructions.md +2 -1
- package/bin/cli.mjs +13 -18
- package/dist/base-command.d.ts +59 -12
- package/dist/base-command.js +179 -51
- package/dist/commands/config/base.js +12 -2
- package/dist/commands/config/get.js +8 -12
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/list.js +11 -17
- package/dist/commands/config/set.d.ts +2 -1
- package/dist/commands/config/set.js +35 -28
- package/dist/commands/config/unset.d.ts +2 -1
- package/dist/commands/config/unset.js +26 -25
- package/dist/commands/fetch.d.ts +7 -15
- package/dist/commands/fetch.js +61 -401
- package/dist/commands/import.d.ts +4 -9
- package/dist/commands/import.js +56 -284
- package/dist/commands/inspect.d.ts +7 -1
- package/dist/commands/inspect.js +112 -52
- package/dist/commands/list.d.ts +18 -3
- package/dist/commands/list.js +121 -46
- package/dist/commands/prune.d.ts +6 -1
- package/dist/commands/prune.js +89 -78
- package/dist/commands/status.d.ts +3 -2
- package/dist/commands/status.js +62 -79
- package/dist/hooks/command-not-found/suggest.d.ts +2 -1
- package/dist/hooks/command-not-found/suggest.js +78 -41
- package/dist/lib/argv.d.ts +29 -3
- package/dist/lib/argv.js +147 -54
- package/dist/lib/batch.d.ts +41 -0
- package/dist/lib/batch.js +39 -0
- package/dist/lib/cache-view.d.ts +21 -0
- package/dist/lib/cache-view.js +30 -0
- package/dist/lib/cli-emit.d.ts +28 -0
- package/dist/lib/cli-emit.js +44 -0
- package/dist/lib/cli-error-policy.d.ts +57 -0
- package/dist/lib/cli-error-policy.js +74 -0
- package/dist/lib/cli-flag-manifest.d.ts +12 -0
- package/dist/lib/cli-flag-manifest.js +15 -0
- package/dist/lib/cli-presentation.d.ts +41 -0
- package/dist/lib/cli-presentation.js +53 -0
- package/dist/lib/cli-result-types.d.ts +112 -0
- package/dist/lib/cli-result-types.js +2 -0
- package/dist/lib/config/index.d.ts +23 -5
- package/dist/lib/config/index.js +60 -5
- package/dist/lib/config/io.d.ts +9 -0
- package/dist/lib/config/io.js +71 -21
- package/dist/lib/config/schema.d.ts +2 -0
- package/dist/lib/config/schema.js +7 -0
- package/dist/lib/config-persist.d.ts +28 -0
- package/dist/lib/config-persist.js +29 -0
- package/dist/lib/envelope.d.ts +41 -3
- package/dist/lib/envelope.js +154 -4
- package/dist/lib/help-preflight.d.ts +5 -8
- package/dist/lib/help-preflight.js +13 -32
- package/dist/lib/json-meta.d.ts +0 -2
- package/dist/lib/json-meta.js +21 -25
- package/dist/lib/limit-flag.d.ts +11 -1
- package/dist/lib/limit-flag.js +19 -6
- package/dist/lib/parse-error-ux.d.ts +5 -0
- package/dist/lib/parse-error-ux.js +73 -0
- package/dist/lib/prune-flags.d.ts +30 -0
- package/dist/lib/prune-flags.js +86 -0
- package/dist/lib/research/artifact-index.d.ts +11 -2
- package/dist/lib/research/artifact-index.js +14 -5
- package/dist/lib/research/artifact.js +20 -2
- package/dist/lib/research/browser.d.ts +53 -2
- package/dist/lib/research/browser.js +202 -58
- package/dist/lib/research/capture.d.ts +24 -2
- package/dist/lib/research/capture.js +24 -4
- package/dist/lib/research/cli-io.d.ts +40 -0
- package/dist/lib/research/cli-io.js +27 -0
- package/dist/lib/research/compress.d.ts +7 -0
- package/dist/lib/research/compress.js +7 -3
- package/dist/lib/research/docs/capabilities.d.ts +15 -0
- package/dist/lib/research/docs/capabilities.js +5 -0
- package/dist/lib/research/docs/clean-dom.d.ts +2 -0
- package/dist/lib/research/docs/clean-dom.js +2 -0
- package/dist/lib/research/docs/code-blocks.d.ts +2 -0
- package/dist/lib/research/docs/code-blocks.js +2 -0
- package/dist/lib/research/docs/detect.d.ts +4 -0
- package/dist/lib/research/docs/detect.js +4 -0
- package/dist/lib/research/docs/machine-readable.d.ts +18 -0
- package/dist/lib/research/docs/machine-readable.js +10 -0
- package/dist/lib/research/docs/markdown-source.d.ts +20 -0
- package/dist/lib/research/docs/markdown-source.js +19 -2
- package/dist/lib/research/docs/quality-gates.d.ts +5 -0
- package/dist/lib/research/docs/quality-gates.js +4 -0
- package/dist/lib/research/docs/rsc.d.ts +9 -2
- package/dist/lib/research/docs/rsc.js +5 -2
- package/dist/lib/research/docs/section-artifacts.d.ts +12 -0
- package/dist/lib/research/docs/section-artifacts.js +12 -0
- package/dist/lib/research/docs/sections.d.ts +18 -0
- package/dist/lib/research/docs/sections.js +17 -1
- package/dist/lib/research/docs/source-map.d.ts +28 -0
- package/dist/lib/research/docs/source-map.js +27 -0
- package/dist/lib/research/docs/validate.d.ts +35 -2
- package/dist/lib/research/docs/validate.js +47 -2
- package/dist/lib/research/extract.d.ts +16 -0
- package/dist/lib/research/extract.js +12 -1
- package/dist/lib/research/fetch-command-service.d.ts +36 -0
- package/dist/lib/research/fetch-command-service.js +367 -0
- package/dist/lib/research/fetch-result.d.ts +114 -0
- package/dist/lib/research/fetch-result.js +212 -0
- package/dist/lib/research/fetcher.d.ts +46 -8
- package/dist/lib/research/fetcher.js +61 -31
- package/dist/lib/research/freshness.d.ts +54 -11
- package/dist/lib/research/freshness.js +91 -31
- package/dist/lib/research/import-command-service.d.ts +56 -0
- package/dist/lib/research/import-command-service.js +402 -0
- package/dist/lib/research/keywords.js +5 -1
- package/dist/lib/research/markdown.d.ts +12 -1
- package/dist/lib/research/markdown.js +13 -5
- package/dist/lib/research/metadata-filters.d.ts +18 -0
- package/dist/lib/research/metadata-filters.js +37 -0
- package/dist/lib/research/metadata-flags.d.ts +34 -0
- package/dist/lib/research/metadata-flags.js +45 -0
- package/dist/lib/research/persist-artifact.d.ts +23 -0
- package/dist/lib/research/persist-artifact.js +27 -0
- package/dist/lib/research/prompt-injection.d.ts +9 -0
- package/dist/lib/research/prompt-injection.js +59 -14
- package/dist/lib/research/proxy.d.ts +14 -8
- package/dist/lib/research/proxy.js +14 -10
- package/dist/lib/research/resolve-target.d.ts +2 -0
- package/dist/lib/research/resolve-target.js +1 -1
- package/dist/lib/research/revalidate.d.ts +49 -20
- package/dist/lib/research/revalidate.js +80 -15
- package/dist/lib/research/schema.d.ts +7 -0
- package/dist/lib/research/schema.js +2 -0
- package/dist/lib/research/secret-scan.d.ts +6 -0
- package/dist/lib/research/secret-scan.js +8 -0
- package/dist/lib/research/secure-write.d.ts +4 -0
- package/dist/lib/research/secure-write.js +31 -2
- package/dist/lib/research/storage.d.ts +9 -8
- package/dist/lib/research/storage.js +34 -28
- package/dist/lib/research/summarize.d.ts +15 -0
- package/dist/lib/research/summarize.js +11 -0
- package/dist/lib/research/url.d.ts +9 -6
- package/dist/lib/research/url.js +33 -21
- package/dist/lib/text.d.ts +19 -8
- package/dist/lib/text.js +32 -9
- package/dist/lib/value-flags.d.ts +14 -0
- package/dist/lib/value-flags.js +28 -0
- package/dist/sites/coveo.d.ts +10 -0
- package/dist/sites/coveo.js +10 -0
- package/dist/sites/index.d.ts +16 -0
- package/dist/sites/index.js +16 -4
- package/dist/sites/markdown-twin.d.ts +31 -0
- package/dist/sites/markdown-twin.js +56 -0
- package/dist/sites/salesforce/fetch-page.d.ts +8 -2
- package/dist/sites/salesforce/fetch-page.js +14 -3
- package/dist/sites/salesforce/help-tooling-markdown.d.ts +30 -0
- package/dist/sites/salesforce/help-tooling-markdown.js +74 -0
- package/dist/sites/salesforce/index.d.ts +1 -0
- package/dist/sites/salesforce/index.js +1 -0
- package/dist/sites/salesforce-developer/fetch-page.d.ts +6 -1
- package/dist/sites/salesforce-developer/fetch-page.js +6 -1
- package/dist/sites/salesforce-developer/index.d.ts +1 -0
- package/dist/sites/salesforce-developer/index.js +1 -0
- package/dist/sites/salesforce-developer/route-markdown.d.ts +18 -6
- package/dist/sites/salesforce-developer/route-markdown.js +29 -67
- package/dist/sites/salesforce-doc-fetch.d.ts +39 -5
- package/dist/sites/salesforce-doc-fetch.js +52 -7
- package/dist/sites/salesforce-dom-probe.d.ts +20 -2
- package/dist/sites/salesforce-dom-probe.js +20 -2
- package/dist/sites/types.d.ts +14 -8
- package/dist/sites/types.js +3 -0
- package/oclif.manifest.json +216 -102
- package/package.json +20 -7
- package/agents/agents/official-docs-researcher.agent.md +0 -87
- package/agents/instructions/web-research.instructions.md +0 -44
- package/agents/skills/web-research/SKILL.md +0 -118
package/dist/lib/envelope.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EXIT_RUNTIME_FAILURE } from './cli-error-policy.js';
|
|
2
|
+
import { pluralize } from './text.js';
|
|
1
3
|
/** Map oclif parse-time errors to stable Bonsai codes when the throw site did not set one. */
|
|
2
4
|
export function stableErrorCodeFrom(err) {
|
|
3
5
|
const e = err;
|
|
@@ -21,10 +23,23 @@ export function stableErrorCodeFrom(err) {
|
|
|
21
23
|
/^Flag --\S+ expects (a value$|one of these values:)/.test(e.message)) {
|
|
22
24
|
return 'MISSING_FLAG_VALUE';
|
|
23
25
|
}
|
|
26
|
+
// Repeating a single-value flag (e.g. `--topic a --topic b`) is also a plain CLIError with no
|
|
27
|
+
// dedicated class — oclif's parser rejects it before any flag-specific validation runs.
|
|
28
|
+
if (typeof e?.message === 'string' && /^Flag --\S+ can only be specified once$/.test(e.message)) {
|
|
29
|
+
return 'DUPLICATE_FLAG';
|
|
30
|
+
}
|
|
24
31
|
return undefined;
|
|
25
32
|
}
|
|
26
|
-
/**
|
|
33
|
+
/** oclif wraps custom flag parse failures as `Parsing --name \n\t…\nSee more help with --help`. */
|
|
34
|
+
const PARSING_WRAPPER = /^Parsing --\S+ \n\t([\s\S]*?)(?:\nSee more help with --help)?$/;
|
|
35
|
+
/**
|
|
36
|
+
* Strip oclif's generic help suffix and the `Parsing --flag` wrapper so agents and humans see the
|
|
37
|
+
* actionable message we threw (e.g. `Limit must be between 1 and 100.`) instead of the wrapper.
|
|
38
|
+
*/
|
|
27
39
|
export function normalizeCliErrorMessage(message) {
|
|
40
|
+
const wrapped = message.match(PARSING_WRAPPER);
|
|
41
|
+
if (wrapped?.[1])
|
|
42
|
+
return wrapped[1];
|
|
28
43
|
const suffix = '\nSee more help with --help';
|
|
29
44
|
return message.endsWith(suffix) ? message.slice(0, -suffix.length) : message;
|
|
30
45
|
}
|
|
@@ -52,6 +67,125 @@ export function formatErrorForJson(err) {
|
|
|
52
67
|
lines.push(`Reference: ${err.ref}`);
|
|
53
68
|
return lines.join('\n');
|
|
54
69
|
}
|
|
70
|
+
/** Stable CLIError fields for batch failure rows (status/inspect/fetch). */
|
|
71
|
+
export function cliErrorFields(err, fallbackCode = 'INVALID_URL') {
|
|
72
|
+
return {
|
|
73
|
+
code: typeof err.code === 'string' && err.code ? err.code : fallbackCode,
|
|
74
|
+
message: typeof err.message === 'string' ? err.message : String(err),
|
|
75
|
+
suggestions: err.suggestions,
|
|
76
|
+
ref: err.ref,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Overlay a batch failure onto a success envelope while keeping per-row `data`.
|
|
81
|
+
* Shared by multi-URL status/inspect (CACHE_MISS) and fetch/row errors.
|
|
82
|
+
*/
|
|
83
|
+
function enrichBatchFailureEnvelope(envelope, data, opts) {
|
|
84
|
+
const list = Array.isArray(data) ? data : [data];
|
|
85
|
+
const failures = list.map(opts.pick).filter(Boolean);
|
|
86
|
+
if (failures.length === 0)
|
|
87
|
+
return envelope;
|
|
88
|
+
const first = failures[0];
|
|
89
|
+
const code = typeof opts.code === 'function' ? opts.code(first, failures) : opts.code;
|
|
90
|
+
const suggestions = opts.suggestions?.(failures);
|
|
91
|
+
const ref = opts.ref?.(first);
|
|
92
|
+
const stderr = formatErrorForJson({
|
|
93
|
+
message: opts.message(first, failures),
|
|
94
|
+
code,
|
|
95
|
+
suggestions,
|
|
96
|
+
ref,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
...envelope,
|
|
100
|
+
ok: false,
|
|
101
|
+
exitCode: opts.exitCode ?? 1,
|
|
102
|
+
stderr,
|
|
103
|
+
code,
|
|
104
|
+
suggestions,
|
|
105
|
+
ref,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* CACHE_MISS overlay. Message wording is fixed so status/inspect cannot drift.
|
|
110
|
+
*/
|
|
111
|
+
export function enrichCacheMissEnvelope(envelope, data, bin) {
|
|
112
|
+
return enrichBatchFailureEnvelope(envelope, data, {
|
|
113
|
+
pick: (row) => {
|
|
114
|
+
const r = row;
|
|
115
|
+
return r?.status === 'miss' ? r : null;
|
|
116
|
+
},
|
|
117
|
+
code: 'CACHE_MISS',
|
|
118
|
+
exitCode: EXIT_RUNTIME_FAILURE,
|
|
119
|
+
message: (first, failures) => {
|
|
120
|
+
const url = first.normalizedUrl ?? '';
|
|
121
|
+
const otherCount = failures.length - 1;
|
|
122
|
+
return otherCount > 0
|
|
123
|
+
? `Cache miss for ${url} and ${otherCount} other ${pluralize(otherCount, 'URL', 'URLs')}`
|
|
124
|
+
: `Cache miss for ${url}`;
|
|
125
|
+
},
|
|
126
|
+
// A URL that is already a secondary source of a different cached artifact must not be pointed
|
|
127
|
+
// at a plain fetch — that would create an unrelated duplicate entry instead of finding the
|
|
128
|
+
// note that already covers it.
|
|
129
|
+
suggestions: (failures) => failures.map((m) => m.partOfExistingNote
|
|
130
|
+
? `Find it with: ${bin} list --url "${m.normalizedUrl}"`
|
|
131
|
+
: `Fetch and cache it first: ${bin} ${m.normalizedUrl}`),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Per-row `.error` overlay for multi-URL batches (fetch, status, inspect).
|
|
136
|
+
* Keeps prior successes in `data` and surfaces the first row's code/message.
|
|
137
|
+
*/
|
|
138
|
+
export function enrichRowErrorEnvelope(envelope, data) {
|
|
139
|
+
return enrichBatchFailureEnvelope(envelope, data, {
|
|
140
|
+
pick: (row) => {
|
|
141
|
+
if (!row || typeof row !== 'object')
|
|
142
|
+
return null;
|
|
143
|
+
return row.error ?? null;
|
|
144
|
+
},
|
|
145
|
+
code: (first) => first.code ?? 'FETCH_FAILED',
|
|
146
|
+
message: (first, failures) => {
|
|
147
|
+
const otherCount = failures.length - 1;
|
|
148
|
+
const suffix = otherCount > 0
|
|
149
|
+
? `\n…and ${otherCount} other URL ${pluralize(otherCount, 'failure', 'failures')}`
|
|
150
|
+
: '';
|
|
151
|
+
return (first.message ?? '') + suffix;
|
|
152
|
+
},
|
|
153
|
+
suggestions: (failures) => failures[0]?.suggestions,
|
|
154
|
+
ref: (first) => first.ref,
|
|
155
|
+
exitCode: EXIT_RUNTIME_FAILURE,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Partial prune unlink failure: candidates remain in `data`, envelope reports the stable code.
|
|
160
|
+
*/
|
|
161
|
+
export function enrichPrunePartialEnvelope(envelope, data) {
|
|
162
|
+
if (!isPruneCounts(data))
|
|
163
|
+
return envelope;
|
|
164
|
+
// A dry run never deletes anything, so prunedCount is always 0 by design — that is a preview,
|
|
165
|
+
// not a partial failure, and must never be reported as one.
|
|
166
|
+
if (data.dryRun)
|
|
167
|
+
return envelope;
|
|
168
|
+
if (data.candidateCount <= 0 || data.prunedCount >= data.candidateCount)
|
|
169
|
+
return envelope;
|
|
170
|
+
const failed = data.candidateCount - data.prunedCount;
|
|
171
|
+
const code = 'PRUNE_PARTIAL_FAILURE';
|
|
172
|
+
const message = `Failed to delete ${failed} of ${data.candidateCount} cache ${failed === 1 ? 'entry' : 'entries'}.`;
|
|
173
|
+
return {
|
|
174
|
+
...envelope,
|
|
175
|
+
ok: false,
|
|
176
|
+
exitCode: EXIT_RUNTIME_FAILURE,
|
|
177
|
+
code,
|
|
178
|
+
stderr: formatErrorForJson({ message, code }),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function isPruneCounts(data) {
|
|
182
|
+
if (!data || typeof data !== 'object')
|
|
183
|
+
return false;
|
|
184
|
+
const d = data;
|
|
185
|
+
return (typeof d.prunedCount === 'number' &&
|
|
186
|
+
typeof d.candidateCount === 'number' &&
|
|
187
|
+
typeof d.dryRun === 'boolean');
|
|
188
|
+
}
|
|
55
189
|
/**
|
|
56
190
|
* Builds the standard Bonsai CLI JSON envelope structure.
|
|
57
191
|
* This is the single source of truth for the output envelope schema.
|
|
@@ -75,8 +209,24 @@ export function buildEnvelope(parts) {
|
|
|
75
209
|
return envelope;
|
|
76
210
|
}
|
|
77
211
|
/**
|
|
78
|
-
*
|
|
212
|
+
* Usage/not-found style error envelope: message + Code (+ Try this) in `stderr`, stable `code`,
|
|
213
|
+
* and top-level `suggestions` when present. Shared by argv preflight, help-preflight, and the
|
|
214
|
+
* command_not_found hook so those paths cannot drift.
|
|
79
215
|
*/
|
|
80
|
-
export function
|
|
81
|
-
return
|
|
216
|
+
export function buildCliErrorEnvelope(opts) {
|
|
217
|
+
return buildEnvelope({
|
|
218
|
+
command: opts.command,
|
|
219
|
+
ok: false,
|
|
220
|
+
exitCode: opts.exitCode ?? 2,
|
|
221
|
+
stderr: formatErrorForJson({
|
|
222
|
+
message: opts.message,
|
|
223
|
+
code: opts.code,
|
|
224
|
+
suggestions: opts.suggestions,
|
|
225
|
+
ref: opts.ref,
|
|
226
|
+
}),
|
|
227
|
+
data: null,
|
|
228
|
+
code: opts.code,
|
|
229
|
+
suggestions: opts.suggestions?.length ? opts.suggestions : undefined,
|
|
230
|
+
ref: opts.ref,
|
|
231
|
+
});
|
|
82
232
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} | {
|
|
1
|
+
import { EXIT_USAGE } from './cli-error-policy.js';
|
|
2
|
+
/** Always an envelope — human and JSON share one formatted stderr (Code + Try this). */
|
|
3
|
+
export type UnknownHelpResult = {
|
|
4
|
+
exitCode: typeof EXIT_USAGE;
|
|
6
5
|
envelope: Record<string, unknown>;
|
|
7
|
-
|
|
8
|
-
kind: 'json';
|
|
6
|
+
json: boolean;
|
|
9
7
|
};
|
|
10
8
|
/**
|
|
11
9
|
* oclif resolves help before its command_not_found hook, so `bonsai config gett --help` otherwise
|
|
12
10
|
* falls back to a terse framework error. Catch that narrow case and reuse the normal typo guidance.
|
|
13
11
|
*/
|
|
14
12
|
export declare function tryUnknownHelpOutput(argv: readonly string[], root: string): Promise<UnknownHelpResult | null>;
|
|
15
|
-
export {};
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { Config } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import { positionalArgvTokens } from './argv.js';
|
|
3
|
+
import { EXIT_USAGE } from './cli-error-policy.js';
|
|
4
|
+
import { VALUE_TAKING_FLAG_TOKENS } from './cli-flag-manifest.js';
|
|
5
|
+
import { buildCliErrorEnvelope } from './envelope.js';
|
|
3
6
|
import { buildCommandNotFoundDetails } from '../hooks/command-not-found/suggest.js';
|
|
4
7
|
function commandId(parts) {
|
|
5
8
|
return parts.join(':');
|
|
6
9
|
}
|
|
7
|
-
function positionalTokens(argv) {
|
|
8
|
-
const tokens = [];
|
|
9
|
-
for (const arg of argv) {
|
|
10
|
-
if (arg === '--')
|
|
11
|
-
break;
|
|
12
|
-
if (arg === '--help' || arg === '--json')
|
|
13
|
-
continue;
|
|
14
|
-
if (arg.startsWith('-'))
|
|
15
|
-
continue;
|
|
16
|
-
tokens.push(arg);
|
|
17
|
-
}
|
|
18
|
-
return tokens;
|
|
19
|
-
}
|
|
20
10
|
function commandPrefixLength(tokens, config) {
|
|
21
11
|
let longest = 0;
|
|
22
12
|
for (let n = 1; n <= tokens.length; n++) {
|
|
@@ -51,7 +41,7 @@ function hasValidHelpTarget(tokens, config) {
|
|
|
51
41
|
export async function tryUnknownHelpOutput(argv, root) {
|
|
52
42
|
if (!argv.includes('--help'))
|
|
53
43
|
return null;
|
|
54
|
-
const tokens =
|
|
44
|
+
const tokens = positionalArgvTokens(argv, VALUE_TAKING_FLAG_TOKENS);
|
|
55
45
|
if (tokens.length === 0)
|
|
56
46
|
return null;
|
|
57
47
|
const config = await Config.load({ root });
|
|
@@ -59,23 +49,14 @@ export async function tryUnknownHelpOutput(argv, root) {
|
|
|
59
49
|
return null;
|
|
60
50
|
const attemptedId = commandId(tokens);
|
|
61
51
|
const details = buildCommandNotFoundDetails(attemptedId, [...argv], config);
|
|
62
|
-
if (argv.includes('--json')) {
|
|
63
|
-
return {
|
|
64
|
-
kind: 'json',
|
|
65
|
-
exitCode: 2,
|
|
66
|
-
envelope: buildEnvelope({
|
|
67
|
-
command: details.command,
|
|
68
|
-
ok: false,
|
|
69
|
-
exitCode: 2,
|
|
70
|
-
stderr: `${details.message}\nCode: ${details.code}`,
|
|
71
|
-
data: null,
|
|
72
|
-
code: details.code,
|
|
73
|
-
}),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
52
|
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
53
|
+
exitCode: EXIT_USAGE,
|
|
54
|
+
json: argv.includes('--json'),
|
|
55
|
+
envelope: buildCliErrorEnvelope({
|
|
56
|
+
command: details.command,
|
|
57
|
+
message: details.message,
|
|
58
|
+
code: details.code,
|
|
59
|
+
suggestions: details.suggestions,
|
|
60
|
+
}),
|
|
80
61
|
};
|
|
81
62
|
}
|
package/dist/lib/json-meta.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** True when argv asks for help or version together with --json. */
|
|
2
|
-
export declare function isJsonMetaRequest(argv: readonly string[]): boolean;
|
|
3
1
|
/**
|
|
4
2
|
* When `--json` is combined with `--help` or `--version`, oclif would print human text to stdout.
|
|
5
3
|
* Render the same content inside the standard Bonsai JSON envelope instead.
|
package/dist/lib/json-meta.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Config, Help, toConfiguredId } from '@oclif/core';
|
|
2
|
+
import { positionalArgvTokens } from './argv.js';
|
|
3
|
+
import { EXIT_OK, EXIT_RUNTIME_FAILURE } from './cli-error-policy.js';
|
|
4
|
+
import { VALUE_TAKING_FLAG_TOKENS } from './cli-flag-manifest.js';
|
|
2
5
|
import { buildEnvelope } from './envelope.js';
|
|
3
6
|
/** Strip ANSI color codes so JSON help text stays machine-stable. */
|
|
4
7
|
function stripAnsi(text) {
|
|
5
|
-
// eslint-disable-next-line no-control-regex
|
|
6
8
|
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
7
9
|
}
|
|
8
10
|
/** True when argv asks for help or version together with --json. */
|
|
9
|
-
|
|
11
|
+
function isJsonMetaRequest(argv) {
|
|
10
12
|
if (!argv.includes('--json'))
|
|
11
13
|
return false;
|
|
12
14
|
return argv.includes('--help') || argv.includes('--version');
|
|
@@ -21,27 +23,21 @@ class CaptureHelp extends Help {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
function helpSubject(argv) {
|
|
25
|
-
for (const arg of argv) {
|
|
26
|
-
if (arg === '--' || arg === '--help' || arg === 'help')
|
|
27
|
-
continue;
|
|
28
|
-
if (arg.startsWith('-'))
|
|
29
|
-
return;
|
|
30
|
-
return arg;
|
|
31
|
-
}
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
26
|
function envelopeCommandId(config, argv) {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
27
|
+
const tokens = positionalArgvTokens(argv, VALUE_TAKING_FLAG_TOKENS);
|
|
28
|
+
if (tokens.length === 0)
|
|
37
29
|
return config.bin;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
// Longest matching command id wins (`config:get` over topic `config`).
|
|
31
|
+
for (let n = tokens.length; n >= 1; n--) {
|
|
32
|
+
const id = tokens.slice(0, n).join(':');
|
|
33
|
+
const command = config.findCommand(id);
|
|
34
|
+
if (command?.id)
|
|
35
|
+
return toConfiguredId(command.id, config);
|
|
36
|
+
}
|
|
37
|
+
const topic = config.findTopic(tokens[0]);
|
|
42
38
|
if (topic)
|
|
43
39
|
return topic.name;
|
|
44
|
-
return
|
|
40
|
+
return tokens[0];
|
|
45
41
|
}
|
|
46
42
|
/**
|
|
47
43
|
* When `--json` is combined with `--help` or `--version`, oclif would print human text to stdout.
|
|
@@ -54,11 +50,11 @@ export async function tryJsonMetaOutput(argv, root) {
|
|
|
54
50
|
const config = await Config.load({ root });
|
|
55
51
|
if (argv.includes('--version')) {
|
|
56
52
|
return {
|
|
57
|
-
exitCode:
|
|
53
|
+
exitCode: EXIT_OK,
|
|
58
54
|
envelope: buildEnvelope({
|
|
59
55
|
command: config.bin,
|
|
60
56
|
ok: true,
|
|
61
|
-
exitCode:
|
|
57
|
+
exitCode: EXIT_OK,
|
|
62
58
|
stderr: '',
|
|
63
59
|
data: {
|
|
64
60
|
version: config.version,
|
|
@@ -71,11 +67,11 @@ export async function tryJsonMetaOutput(argv, root) {
|
|
|
71
67
|
await help.showHelp([...argv]);
|
|
72
68
|
const helpText = stripAnsi(help.lines.join('\n').trimEnd());
|
|
73
69
|
return {
|
|
74
|
-
exitCode:
|
|
70
|
+
exitCode: EXIT_OK,
|
|
75
71
|
envelope: buildEnvelope({
|
|
76
72
|
command: envelopeCommandId(config, argv),
|
|
77
73
|
ok: true,
|
|
78
|
-
exitCode:
|
|
74
|
+
exitCode: EXIT_OK,
|
|
79
75
|
stderr: '',
|
|
80
76
|
data: { help: helpText },
|
|
81
77
|
}),
|
|
@@ -84,11 +80,11 @@ export async function tryJsonMetaOutput(argv, root) {
|
|
|
84
80
|
catch (err) {
|
|
85
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
86
82
|
return {
|
|
87
|
-
exitCode:
|
|
83
|
+
exitCode: EXIT_RUNTIME_FAILURE,
|
|
88
84
|
envelope: buildEnvelope({
|
|
89
85
|
command: 'bonsai',
|
|
90
86
|
ok: false,
|
|
91
|
-
exitCode:
|
|
87
|
+
exitCode: EXIT_RUNTIME_FAILURE,
|
|
92
88
|
stderr: message,
|
|
93
89
|
data: null,
|
|
94
90
|
code: 'META_RENDER_FAILED',
|
package/dist/lib/limit-flag.d.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Build an oclif integer `--limit` flag capped at `maxLimit`.
|
|
3
|
+
*
|
|
4
|
+
* @param maxLimit - Inclusive upper bound for accepted values.
|
|
5
|
+
* @param defaultValue - Default when the flag is omitted.
|
|
6
|
+
* @param description - Help text for the flag.
|
|
7
|
+
* @returns An oclif custom flag definition.
|
|
8
|
+
* @throws {Errors.CLIError} With code `INVALID_LIMIT` and exit {@link EXIT_USAGE} on bad input
|
|
9
|
+
* (thrown by oclif during parse, not by this factory itself).
|
|
10
|
+
*/
|
|
11
|
+
export declare function limitFlag(maxLimit: number, defaultValue: number, description: string): import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
package/dist/lib/limit-flag.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
import { Errors, Flags } from '@oclif/core';
|
|
2
|
-
|
|
2
|
+
import { EXIT_USAGE } from './cli-error-policy.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build an oclif integer `--limit` flag capped at `maxLimit`.
|
|
5
|
+
*
|
|
6
|
+
* @param maxLimit - Inclusive upper bound for accepted values.
|
|
7
|
+
* @param defaultValue - Default when the flag is omitted.
|
|
8
|
+
* @param description - Help text for the flag.
|
|
9
|
+
* @returns An oclif custom flag definition.
|
|
10
|
+
* @throws {Errors.CLIError} With code `INVALID_LIMIT` and exit {@link EXIT_USAGE} on bad input
|
|
11
|
+
* (thrown by oclif during parse, not by this factory itself).
|
|
12
|
+
*/
|
|
13
|
+
export function limitFlag(maxLimit, defaultValue, description) {
|
|
3
14
|
return Flags.custom({
|
|
4
15
|
parse: async (input) => {
|
|
5
16
|
if (!/^\d+$/.test(input)) {
|
|
6
|
-
throw new Errors.CLIError(`Limit must be an integer between 1 and ${
|
|
7
|
-
exit:
|
|
17
|
+
throw new Errors.CLIError(`Limit must be an integer between 1 and ${maxLimit}.`, {
|
|
18
|
+
exit: EXIT_USAGE,
|
|
8
19
|
code: 'INVALID_LIMIT',
|
|
20
|
+
suggestions: [`Use an integer from 1 to ${maxLimit}.`],
|
|
9
21
|
});
|
|
10
22
|
}
|
|
11
23
|
const value = Number(input);
|
|
12
|
-
if (value < 1 || value >
|
|
13
|
-
throw new Errors.CLIError(`Limit must be between 1 and ${
|
|
14
|
-
exit:
|
|
24
|
+
if (value < 1 || value > maxLimit) {
|
|
25
|
+
throw new Errors.CLIError(`Limit must be between 1 and ${maxLimit}.`, {
|
|
26
|
+
exit: EXIT_USAGE,
|
|
15
27
|
code: 'INVALID_LIMIT',
|
|
28
|
+
suggestions: [`Use a value from 1 to ${maxLimit}.`],
|
|
16
29
|
});
|
|
17
30
|
}
|
|
18
31
|
return value;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { normalizeCliErrorMessage } from './envelope.js';
|
|
2
|
+
import { closestMatch, closestOptionValues, maxFuzzyDistance } from './text.js';
|
|
3
|
+
/** FlagInvalidOptionError body after {@link normalizeCliErrorMessage}. */
|
|
4
|
+
const EXPECTED_OPTION = /^Expected --([^=]+)=(\S+) to be one of: (.+)$/;
|
|
5
|
+
function asEnrichable(err) {
|
|
6
|
+
if (!err || typeof err !== 'object')
|
|
7
|
+
return null;
|
|
8
|
+
if (typeof err.message !== 'string')
|
|
9
|
+
return null;
|
|
10
|
+
return err;
|
|
11
|
+
}
|
|
12
|
+
function suggestableFlagNames(err) {
|
|
13
|
+
const defined = err.parse?.input?.flags;
|
|
14
|
+
if (!defined)
|
|
15
|
+
return [];
|
|
16
|
+
const names = [];
|
|
17
|
+
for (const [name, flag] of Object.entries(defined)) {
|
|
18
|
+
names.push(`--${name}`);
|
|
19
|
+
for (const alias of flag.aliases ?? [])
|
|
20
|
+
names.push(`--${alias}`);
|
|
21
|
+
if (flag.char)
|
|
22
|
+
names.push(`-${flag.char}`);
|
|
23
|
+
}
|
|
24
|
+
// enableJsonFlag is framework-level — it may be absent from parse.input.flags.
|
|
25
|
+
if (!names.includes('--json'))
|
|
26
|
+
names.push('--json');
|
|
27
|
+
return names;
|
|
28
|
+
}
|
|
29
|
+
function collectTips(err) {
|
|
30
|
+
const lines = [];
|
|
31
|
+
const suggestions = [];
|
|
32
|
+
if (err.flags?.length) {
|
|
33
|
+
const candidates = suggestableFlagNames(err);
|
|
34
|
+
for (const bad of err.flags) {
|
|
35
|
+
const match = closestMatch(bad, candidates, maxFuzzyDistance(bad.replace(/^-*/, '')));
|
|
36
|
+
if (!match || match === bad)
|
|
37
|
+
continue;
|
|
38
|
+
lines.push(`Did you mean ${match}?`);
|
|
39
|
+
suggestions.push(match);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const option = err.message.match(EXPECTED_OPTION);
|
|
43
|
+
if (option) {
|
|
44
|
+
const [, flagName, input, optionsCsv] = option;
|
|
45
|
+
if (flagName && input && optionsCsv) {
|
|
46
|
+
const options = optionsCsv
|
|
47
|
+
.split(', ')
|
|
48
|
+
.map((part) => part.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
const values = closestOptionValues(input, options);
|
|
51
|
+
if (values.length > 0) {
|
|
52
|
+
lines.push(`Did you mean ${values.join(' or ')}?`);
|
|
53
|
+
suggestions.push(...values.map((value) => `--${flagName} ${value}`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { lines, suggestions };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Improve oclif parse-time errors in place: unwrap wrapper text, then suggest flag/option typos.
|
|
61
|
+
* Accepts `unknown` because `catch` can receive non-Error throws (bare strings, etc.).
|
|
62
|
+
*/
|
|
63
|
+
export function enrichParseError(err) {
|
|
64
|
+
const enrichable = asEnrichable(err);
|
|
65
|
+
if (!enrichable)
|
|
66
|
+
return;
|
|
67
|
+
enrichable.message = normalizeCliErrorMessage(enrichable.message);
|
|
68
|
+
const { lines, suggestions } = collectTips(enrichable);
|
|
69
|
+
if (lines.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
enrichable.message = [enrichable.message, ...lines].join('\n');
|
|
72
|
+
enrichable.suggestions = [...(enrichable.suggestions ?? []), ...suggestions];
|
|
73
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure prune-flag policy. Returns a usage/safety error descriptor, or null when flags are valid.
|
|
3
|
+
* Kept outside the command class so the branching stays testable without oclif and so
|
|
4
|
+
* `validatePruneFlags` stays a thin apply-error loop.
|
|
5
|
+
*/
|
|
6
|
+
export interface PruneFlagError {
|
|
7
|
+
message: string;
|
|
8
|
+
code: 'INVALID_FLAG_VALUE' | 'MISSING_FILTER' | 'READ_ONLY_MODE' | 'CONFLICTING_FLAGS' | 'SAFETY_CHECK_REQUIRED' | 'INVALID_DURATION';
|
|
9
|
+
suggestions?: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Flag bag passed into {@link pruneFlagError}. */
|
|
12
|
+
export interface PruneFlagInput {
|
|
13
|
+
olderThan?: string;
|
|
14
|
+
inactive?: string;
|
|
15
|
+
artifactType?: string;
|
|
16
|
+
url?: string;
|
|
17
|
+
topic?: string;
|
|
18
|
+
tags?: string[];
|
|
19
|
+
dryRun: boolean;
|
|
20
|
+
yes: boolean;
|
|
21
|
+
readOnly: boolean;
|
|
22
|
+
bin: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Return the first prune usage/safety error, or `null` when flags are valid.
|
|
26
|
+
*
|
|
27
|
+
* @param input - Parsed prune flags and binary name.
|
|
28
|
+
* @returns Error descriptor for `this.error`, or `null`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pruneFlagError(input: PruneFlagInput): PruneFlagError | null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { emptyUrlFilterError } from './research/url.js';
|
|
2
|
+
import { durationFlagError } from './research/freshness.js';
|
|
3
|
+
import { emptyTopicFilterError, emptyTagsFilterError } from './research/metadata-filters.js';
|
|
4
|
+
function missingFilterError(input) {
|
|
5
|
+
// Treat an explicitly passed empty string as "filter was attempted" so `--older-than ''`
|
|
6
|
+
// is not misreported as MISSING_FILTER (durationError rejects it as INVALID_DURATION).
|
|
7
|
+
// `tags` is the one array-valued flag here: oclif never parses a bare `--tags` into `[]` (a
|
|
8
|
+
// multiple string flag requires a value per occurrence), so a non-empty array is the correct
|
|
9
|
+
// "was this filter attempted" test, mirroring the `!== undefined` check used for every scalar flag.
|
|
10
|
+
if (input.olderThan !== undefined ||
|
|
11
|
+
input.inactive !== undefined ||
|
|
12
|
+
input.artifactType !== undefined ||
|
|
13
|
+
input.url !== undefined ||
|
|
14
|
+
input.topic !== undefined ||
|
|
15
|
+
(input.tags?.length ?? 0) > 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
message: 'Must specify at least one pruning filter: --older-than, --inactive, --artifact-type, --url, --topic, or --tags.',
|
|
20
|
+
code: 'MISSING_FILTER',
|
|
21
|
+
suggestions: [`Preview age-based pruning: ${input.bin} prune --older-than 30d --dry-run`],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Mutation-gate checks. READ_ONLY before --dry-run/--yes conflict so
|
|
26
|
+
* `--dry-run --yes --read-only` reports the more specific READ_ONLY_MODE code.
|
|
27
|
+
*/
|
|
28
|
+
function mutationSafetyError(input) {
|
|
29
|
+
if (input.readOnly && input.yes) {
|
|
30
|
+
return {
|
|
31
|
+
message: '--yes cannot be used while read-only mode is active (--read-only/--plan or BONSAI_READ_ONLY/BONSAI_PLAN_MODE): mutations are disabled.',
|
|
32
|
+
code: 'READ_ONLY_MODE',
|
|
33
|
+
suggestions: [`Preview instead: ${input.bin} prune --dry-run`],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (input.dryRun && input.yes) {
|
|
37
|
+
return {
|
|
38
|
+
message: '--dry-run and --yes are mutually exclusive: --dry-run previews without deleting, --yes confirms deletion. Choose one.',
|
|
39
|
+
code: 'CONFLICTING_FLAGS',
|
|
40
|
+
suggestions: [
|
|
41
|
+
`Preview without deleting: ${input.bin} prune --dry-run`,
|
|
42
|
+
`Or confirm deletion: ${input.bin} prune --yes`,
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Read-only mode implicitly previews, so the usual "pick one" safety check is redundant then.
|
|
47
|
+
if (!input.readOnly && !input.dryRun && !input.yes) {
|
|
48
|
+
const olderThanPart = input.olderThan ? ` --older-than ${input.olderThan}` : '';
|
|
49
|
+
const urlPart = input.url ? ` --url "${input.url}"` : '';
|
|
50
|
+
const topicPart = input.topic ? ` --topic "${input.topic}"` : '';
|
|
51
|
+
const tagsPart = (input.tags ?? []).map((tag) => ` --tags "${tag}"`).join('');
|
|
52
|
+
return {
|
|
53
|
+
message: 'Safety check: use --yes to confirm pruning, or --dry-run to preview files that would be deleted.',
|
|
54
|
+
code: 'SAFETY_CHECK_REQUIRED',
|
|
55
|
+
suggestions: [
|
|
56
|
+
`Preview first: ${input.bin} prune --dry-run${olderThanPart}${urlPart}${topicPart}${tagsPart}`,
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function durationError(input) {
|
|
63
|
+
for (const msg of [
|
|
64
|
+
durationFlagError('--older-than', input.olderThan),
|
|
65
|
+
durationFlagError('--inactive', input.inactive),
|
|
66
|
+
]) {
|
|
67
|
+
if (msg)
|
|
68
|
+
return { message: msg, code: 'INVALID_DURATION' };
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Return the first prune usage/safety error, or `null` when flags are valid.
|
|
74
|
+
*
|
|
75
|
+
* @param input - Parsed prune flags and binary name.
|
|
76
|
+
* @returns Error descriptor for `this.error`, or `null`.
|
|
77
|
+
*/
|
|
78
|
+
export function pruneFlagError(input) {
|
|
79
|
+
const flagValueErr = emptyUrlFilterError(input.url) ??
|
|
80
|
+
emptyTopicFilterError(input.topic) ??
|
|
81
|
+
emptyTagsFilterError(input.tags);
|
|
82
|
+
if (flagValueErr)
|
|
83
|
+
return { message: flagValueErr, code: 'INVALID_FLAG_VALUE' };
|
|
84
|
+
// Duration before missing-filter so `--older-than ''` reports INVALID_DURATION, not MISSING_FILTER.
|
|
85
|
+
return durationError(input) ?? missingFilterError(input) ?? mutationSafetyError(input);
|
|
86
|
+
}
|
|
@@ -3,16 +3,25 @@ export interface IndexedArtifact {
|
|
|
3
3
|
artifact: ResearchArtifact;
|
|
4
4
|
filePath: string;
|
|
5
5
|
}
|
|
6
|
+
export interface LoadIndexedArtifactsOptions {
|
|
7
|
+
/**
|
|
8
|
+
* When false, skip writing `.search-index.json` (read-only/plan mode). Results are still
|
|
9
|
+
* computed from disk; only the derived sidecar is withheld. Defaults to true.
|
|
10
|
+
*/
|
|
11
|
+
persist?: boolean;
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* Returns the indexed view of every active artifact in one research dir, using the sidecar index
|
|
8
15
|
* to skip unchanged files. Re-reads only files whose signature changed, then persists the refreshed
|
|
9
16
|
* index (best-effort; a write failure never breaks callers). Corrupt/unreadable files are skipped —
|
|
10
17
|
* the real read paths in storage.ts handle archiving them.
|
|
18
|
+
*
|
|
19
|
+
* Pass `{ persist: false }` under read-only/plan mode so the derived sidecar is never written.
|
|
11
20
|
*/
|
|
12
|
-
export declare function loadIndexedArtifactsForDir(researchDir: string): IndexedArtifact[];
|
|
21
|
+
export declare function loadIndexedArtifactsForDir(researchDir: string, options?: LoadIndexedArtifactsOptions): IndexedArtifact[];
|
|
13
22
|
/**
|
|
14
23
|
* Multi-root variant: scans the `research/` dir of each data dir in order and deduplicates by cache
|
|
15
24
|
* key so a project entry shadows the same key in the global cache. Mirrors the dedup semantics of
|
|
16
25
|
* {@link scanCacheDirs} but reads through the per-root sidecar index.
|
|
17
26
|
*/
|
|
18
|
-
export declare function loadIndexedArtifacts(dataDirs: string[]): IndexedArtifact[];
|
|
27
|
+
export declare function loadIndexedArtifacts(dataDirs: string[], options?: LoadIndexedArtifactsOptions): IndexedArtifact[];
|