@taurgis/bonsai 3.2.0 → 3.2.2
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 +7 -4
- package/agents/agents/salesforce-docs-researcher.agent.md +14 -1
- package/agents/instructions/salesforce-research.instructions.md +2 -1
- package/bin/cli.mjs +9 -17
- package/dist/base-command.d.ts +22 -12
- package/dist/base-command.js +86 -49
- package/dist/commands/config/base.js +11 -2
- package/dist/commands/config/get.js +8 -12
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/list.js +10 -16
- package/dist/commands/config/set.d.ts +2 -1
- package/dist/commands/config/set.js +34 -27
- package/dist/commands/config/unset.d.ts +2 -1
- package/dist/commands/config/unset.js +25 -24
- package/dist/commands/fetch.d.ts +7 -15
- package/dist/commands/fetch.js +57 -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 +103 -52
- package/dist/commands/list.d.ts +13 -1
- package/dist/commands/list.js +79 -32
- package/dist/commands/prune.d.ts +4 -1
- package/dist/commands/prune.js +55 -75
- package/dist/commands/status.d.ts +3 -2
- package/dist/commands/status.js +52 -79
- package/dist/hooks/command-not-found/suggest.d.ts +1 -0
- package/dist/hooks/command-not-found/suggest.js +66 -39
- package/dist/lib/argv.d.ts +24 -3
- package/dist/lib/argv.js +124 -53
- 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 +5 -0
- package/dist/lib/cli-flag-manifest.js +8 -0
- package/dist/lib/cli-presentation.d.ts +39 -0
- package/dist/lib/cli-presentation.js +50 -0
- package/dist/lib/cli-result-types.d.ts +88 -0
- package/dist/lib/cli-result-types.js +2 -0
- package/dist/lib/config/index.d.ts +21 -3
- package/dist/lib/config/index.js +58 -3
- 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 +147 -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 +28 -0
- package/dist/lib/prune-flags.js +74 -0
- package/dist/lib/research/artifact-index.d.ts +11 -2
- package/dist/lib/research/artifact-index.js +7 -4
- package/dist/lib/research/browser.d.ts +41 -2
- package/dist/lib/research/browser.js +139 -50
- 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 +33 -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 +310 -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 +58 -30
- 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 +381 -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/persist-artifact.d.ts +23 -0
- package/dist/lib/research/persist-artifact.js +27 -0
- package/dist/lib/research/prompt-injection.d.ts +8 -0
- package/dist/lib/research/prompt-injection.js +42 -13
- package/dist/lib/research/proxy.d.ts +14 -8
- package/dist/lib/research/proxy.js +14 -10
- 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 +5 -1
- package/dist/lib/research/storage.js +8 -3
- 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 +7 -2
- package/dist/lib/text.js +13 -2
- 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 +104 -98
- package/package.json +19 -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/commands/status.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../base-command.js';
|
|
3
|
-
import {
|
|
3
|
+
import { finalizeBatch, isBatchReadFailure, urlValidationErrorRow } from '../lib/batch.js';
|
|
4
4
|
import { getArtifactPath } from '../lib/research/storage.js';
|
|
5
|
-
import {
|
|
5
|
+
import { durationFlagError, evaluateFreshnessWithMaxAge } from '../lib/research/freshness.js';
|
|
6
|
+
import { CLI_FLAG_DESCRIPTIONS } from '../lib/cli-presentation.js';
|
|
7
|
+
import { batchSeparator, cacheMissHint, formatCacheTargetHeader } from '../lib/cache-view.js';
|
|
6
8
|
import { colors } from '../lib/color.js';
|
|
7
|
-
function resolveFreshness(cached, currentTime, ttl, maxAge) {
|
|
8
|
-
|
|
9
|
-
return isMaxAgeExpired
|
|
10
|
-
? 'stale_expired'
|
|
11
|
-
: evaluateFreshness(cached.metadata, currentTime, ttl || cached.metadata.ttl);
|
|
9
|
+
function resolveFreshness(cached, currentTime, ttl, maxAge, tier) {
|
|
10
|
+
return evaluateFreshnessWithMaxAge(cached, currentTime, { ttl, maxAge, tier });
|
|
12
11
|
}
|
|
13
|
-
function describeCacheStatus(cached, currentTime, ttl, maxAge) {
|
|
12
|
+
function describeCacheStatus(cached, currentTime, ttl, maxAge, tier) {
|
|
14
13
|
if (!cached) {
|
|
15
14
|
return { status: 'miss', freshness: 'none', action: 'would_fetch' };
|
|
16
15
|
}
|
|
17
|
-
const freshness = resolveFreshness(cached, currentTime, ttl, maxAge);
|
|
16
|
+
const freshness = resolveFreshness(cached, currentTime, ttl, maxAge, tier);
|
|
18
17
|
return freshness === 'fresh'
|
|
19
18
|
? { status: 'hit', freshness, action: 'would_return_cached' }
|
|
20
19
|
: { status: 'stale', freshness, action: 'would_revalidate' };
|
|
21
20
|
}
|
|
21
|
+
/** Report cache hit/miss and freshness without writing. */
|
|
22
22
|
export default class ResearchStatus extends BaseCommand {
|
|
23
23
|
static id = 'status';
|
|
24
|
-
static summary = 'Check
|
|
25
|
-
static description = '
|
|
24
|
+
static summary = 'Check cache status without fetching or writing';
|
|
25
|
+
static description = 'Report cache hit/miss state, freshness, and the action the URL shorthand would take.';
|
|
26
26
|
static examples = [
|
|
27
27
|
{
|
|
28
|
-
description: 'check
|
|
28
|
+
description: 'check whether a URL is cached',
|
|
29
29
|
command: '<%= config.bin %> <%= command.id %> https://example.com/docs',
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
|
-
description: '
|
|
32
|
+
description: 'evaluate status with a specific tier and TTL',
|
|
33
33
|
command: '<%= config.bin %> <%= command.id %> https://example.com/docs --tier volatile --ttl 2h',
|
|
34
34
|
},
|
|
35
35
|
];
|
|
@@ -37,41 +37,27 @@ export default class ResearchStatus extends BaseCommand {
|
|
|
37
37
|
static args = {
|
|
38
38
|
url: Args.string({
|
|
39
39
|
required: true,
|
|
40
|
-
description: '
|
|
40
|
+
description: 'URL to check',
|
|
41
41
|
}),
|
|
42
42
|
};
|
|
43
43
|
static flags = {
|
|
44
|
+
// No default: omitting --tier evaluates against the artifact's stored tier. A default of
|
|
45
|
+
// `standard` would silently re-grade stable/volatile entries and lie about planned action.
|
|
44
46
|
tier: Flags.option({
|
|
45
|
-
description:
|
|
47
|
+
description: CLI_FLAG_DESCRIPTIONS.statusFreshnessTierPolicy,
|
|
46
48
|
options: ['stable', 'standard', 'volatile'],
|
|
47
|
-
default: 'standard',
|
|
48
49
|
})(),
|
|
49
50
|
ttl: Flags.string({
|
|
50
51
|
char: 'l',
|
|
51
|
-
description:
|
|
52
|
+
description: CLI_FLAG_DESCRIPTIONS.statusTtl,
|
|
52
53
|
}),
|
|
53
54
|
'max-age': Flags.string({
|
|
54
|
-
description:
|
|
55
|
+
description: CLI_FLAG_DESCRIPTIONS.maxAge,
|
|
55
56
|
}),
|
|
56
57
|
};
|
|
57
58
|
static stdoutIsPrimaryData = true;
|
|
58
|
-
/** Enrich cache-miss envelopes with the same CACHE_MISS code and suggestions as inspect. */
|
|
59
59
|
toSuccessJson(data) {
|
|
60
|
-
|
|
61
|
-
const list = Array.isArray(data) ? data : [data];
|
|
62
|
-
const misses = list.filter((d) => d && d.status === 'miss');
|
|
63
|
-
if (misses.length === 0)
|
|
64
|
-
return envelope;
|
|
65
|
-
const firstMiss = misses[0];
|
|
66
|
-
const normalizedUrl = firstMiss.normalizedUrl ?? '';
|
|
67
|
-
const suggestions = misses.map((m) => `Fetch and cache it first: ${this.config.bin} ${m.normalizedUrl}`);
|
|
68
|
-
const stderr = formatErrorForJson({
|
|
69
|
-
message: `Cache miss for ${normalizedUrl}` +
|
|
70
|
-
(misses.length > 1 ? ` and ${misses.length - 1} other URLs` : ''),
|
|
71
|
-
code: 'CACHE_MISS',
|
|
72
|
-
suggestions,
|
|
73
|
-
});
|
|
74
|
-
return { ...envelope, ok: false, exitCode: 1, stderr, code: 'CACHE_MISS', suggestions };
|
|
60
|
+
return this.batchReadSuccessJson(data);
|
|
75
61
|
}
|
|
76
62
|
async run() {
|
|
77
63
|
const urls = this.parsedArgv;
|
|
@@ -82,60 +68,22 @@ export default class ResearchStatus extends BaseCommand {
|
|
|
82
68
|
if (msg)
|
|
83
69
|
this.error(msg, { exit: 2, code: 'INVALID_DURATION' });
|
|
84
70
|
}
|
|
85
|
-
const results = [];
|
|
86
|
-
let hasMiss = false;
|
|
87
71
|
const currentTime = new Date();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (res.status === 'miss') {
|
|
91
|
-
hasMiss = true;
|
|
92
|
-
}
|
|
93
|
-
results.push(res);
|
|
94
|
-
}
|
|
95
|
-
if (hasMiss) {
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
return urls.length === 1 ? results[0] : results;
|
|
72
|
+
const results = this.mapUrlsAllowingBatchErrors(urls, (url) => this.checkSingleStatus(url, currentTime, { ttl, maxAge, tier }), urlValidationErrorRow);
|
|
73
|
+
return finalizeBatch(results, isBatchReadFailure);
|
|
99
74
|
}
|
|
100
|
-
checkSingleStatus(url, currentTime,
|
|
75
|
+
checkSingleStatus(url, currentTime, policy) {
|
|
101
76
|
const target = this.resolveResearchTargetOrFail(url);
|
|
102
77
|
const { cacheKey, located, normalizedUrl, roots } = target;
|
|
103
78
|
const cached = located?.artifact ?? null;
|
|
104
|
-
const result = describeCacheStatus(cached, currentTime, ttl, maxAge);
|
|
79
|
+
const result = describeCacheStatus(cached, currentTime, policy.ttl, policy.maxAge, policy.tier);
|
|
105
80
|
const artifactPath = located?.path ?? getArtifactPath(roots.writeRoot, cacheKey);
|
|
106
81
|
if (!this.jsonEnabled()) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
miss: colors.red,
|
|
111
|
-
};
|
|
112
|
-
const freshnessColorMap = {
|
|
113
|
-
fresh: colors.green,
|
|
114
|
-
stale_grace: colors.yellow,
|
|
115
|
-
stale_expired: colors.red,
|
|
116
|
-
none: colors.gray,
|
|
117
|
-
};
|
|
118
|
-
const actionColorMap = {
|
|
119
|
-
would_return_cached: colors.green,
|
|
120
|
-
would_revalidate: colors.yellow,
|
|
121
|
-
would_fetch: colors.red,
|
|
122
|
-
};
|
|
123
|
-
const statusColor = statusColorMap[result.status] || ((t) => t);
|
|
124
|
-
const freshnessColor = freshnessColorMap[result.freshness] || ((t) => t);
|
|
125
|
-
const actionColor = actionColorMap[result.action] || ((t) => t);
|
|
126
|
-
this.log(`${colors.cyan('URL:'.padEnd(25))} ${colors.bold(normalizedUrl)}`);
|
|
127
|
-
this.log(`${colors.cyan('Cache Key:'.padEnd(25))} ${colors.bold(cacheKey)}`);
|
|
128
|
-
this.log(`${colors.cyan('Cache Path:'.padEnd(25))} ${colors.gray(artifactPath)}`);
|
|
129
|
-
this.log(`${colors.cyan('Status:'.padEnd(25))} ${statusColor(result.status)}`);
|
|
130
|
-
this.log(`${colors.cyan('Freshness:'.padEnd(25))} ${freshnessColor(result.freshness)}`);
|
|
131
|
-
this.log(`${colors.cyan('Action:'.padEnd(25))} ${actionColor(result.action)}`);
|
|
132
|
-
if (showSeparator) {
|
|
133
|
-
this.log('-'.repeat(40));
|
|
82
|
+
this.logStatusTable(target, artifactPath, result);
|
|
83
|
+
if (result.status === 'miss') {
|
|
84
|
+
this.warn(cacheMissHint(this.config.bin, normalizedUrl));
|
|
134
85
|
}
|
|
135
86
|
}
|
|
136
|
-
if (result.status === 'miss' && !this.jsonEnabled()) {
|
|
137
|
-
this.warn(`Cache miss — run: ${this.config.bin} ${normalizedUrl}`);
|
|
138
|
-
}
|
|
139
87
|
return {
|
|
140
88
|
cacheKey,
|
|
141
89
|
cachePath: artifactPath,
|
|
@@ -145,4 +93,29 @@ export default class ResearchStatus extends BaseCommand {
|
|
|
145
93
|
action: result.action,
|
|
146
94
|
};
|
|
147
95
|
}
|
|
96
|
+
logStatusTable(target, artifactPath, result) {
|
|
97
|
+
const colorOf = (map, key) => map[key] ?? ((t) => t);
|
|
98
|
+
const statusColor = colorOf({ hit: colors.green, stale: colors.yellow, miss: colors.red }, result.status);
|
|
99
|
+
const freshnessColor = colorOf({
|
|
100
|
+
fresh: colors.green,
|
|
101
|
+
stale_grace: colors.yellow,
|
|
102
|
+
stale_expired: colors.red,
|
|
103
|
+
none: colors.gray,
|
|
104
|
+
}, result.freshness);
|
|
105
|
+
const actionColor = colorOf({
|
|
106
|
+
would_return_cached: colors.green,
|
|
107
|
+
would_revalidate: colors.yellow,
|
|
108
|
+
would_fetch: colors.red,
|
|
109
|
+
}, result.action);
|
|
110
|
+
for (const line of formatCacheTargetHeader(target, [
|
|
111
|
+
['Status', statusColor(result.status)],
|
|
112
|
+
['Freshness', freshnessColor(result.freshness)],
|
|
113
|
+
['Action', actionColor(result.action)],
|
|
114
|
+
], artifactPath)) {
|
|
115
|
+
this.log(line);
|
|
116
|
+
}
|
|
117
|
+
const sep = batchSeparator(this.parsedArgv.length > 1);
|
|
118
|
+
if (sep)
|
|
119
|
+
this.log(sep);
|
|
120
|
+
}
|
|
148
121
|
}
|
|
@@ -4,6 +4,7 @@ export declare function buildCommandNotFoundDetails(id: string, argv: string[] |
|
|
|
4
4
|
command: string;
|
|
5
5
|
jsonMode: boolean;
|
|
6
6
|
message: string;
|
|
7
|
+
suggestions?: string[];
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* Typo-aware "command not found" handler. oclif fires this before printing its own terse error;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toConfiguredId } from '@oclif/core';
|
|
2
2
|
import { closestMatch, maxFuzzyDistance } from '../../lib/text.js';
|
|
3
|
-
import {
|
|
3
|
+
import { writeCommandNotFoundJson } from '../../lib/cli-emit.js';
|
|
4
4
|
import { looksLikeSchemelessUrl } from '../../lib/research/url.js';
|
|
5
5
|
/**
|
|
6
6
|
* The nearest visible command to a typo, plus how many leading segments of the attempted id name
|
|
@@ -10,13 +10,13 @@ import { looksLikeSchemelessUrl } from '../../lib/research/url.js';
|
|
|
10
10
|
* `lisst:extra` resolves to nothing, but its prefix `lisst` resolves to `list`. The matched prefix
|
|
11
11
|
* length doubles as the command-segment count, so a folded-in positional arg is dropped from display.
|
|
12
12
|
*/
|
|
13
|
-
function findSuggestion(segments,
|
|
13
|
+
function findSuggestion(segments, commandIds) {
|
|
14
14
|
for (let n = segments.length; n >= 1; n--) {
|
|
15
15
|
const attempted = segments.slice(0, n).join(':');
|
|
16
|
-
const suggestion = closestMatch(attempted,
|
|
16
|
+
const suggestion = closestMatch(attempted, commandIds, maxFuzzyDistance(attempted));
|
|
17
17
|
if (suggestion === attempted &&
|
|
18
18
|
n < segments.length &&
|
|
19
|
-
|
|
19
|
+
commandIds.some((id) => id.startsWith(`${attempted}:`))) {
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
if (suggestion)
|
|
@@ -35,12 +35,12 @@ function topicChainLength(segments, config) {
|
|
|
35
35
|
i++;
|
|
36
36
|
return i + 1;
|
|
37
37
|
}
|
|
38
|
-
function exactZeroArgCommandPrefix(segments,
|
|
38
|
+
function exactZeroArgCommandPrefix(segments, commandIds, config) {
|
|
39
39
|
for (let n = segments.length - 1; n >= 1; n--) {
|
|
40
40
|
const candidate = segments.slice(0, n).join(':');
|
|
41
|
-
if (!
|
|
41
|
+
if (!commandIds.includes(candidate))
|
|
42
42
|
continue;
|
|
43
|
-
if (
|
|
43
|
+
if (commandIds.some((id) => id.startsWith(`${candidate}:`)))
|
|
44
44
|
continue;
|
|
45
45
|
const command = config.commands.find((entry) => entry.id === candidate);
|
|
46
46
|
const args = Object.keys(command?.args ?? {});
|
|
@@ -65,43 +65,54 @@ function exactZeroArgCommandPrefix(segments, visibleIds, config) {
|
|
|
65
65
|
function bareUrlInput(id) {
|
|
66
66
|
return looksLikeSchemelessUrl(id) ? id : null;
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The id's colon-joined segments when every one individually looks like a URL missing its scheme,
|
|
70
|
+
* else null. Covers the multi-URL batch-fetch typo (`bonsai example.com example.org` folds into one
|
|
71
|
+
* id, "example.com:example.org") that {@link bareUrlInput} misses: parsed as a single URL, the colon
|
|
72
|
+
* between two hostnames reads as an invalid port, so the whole-string check fails even though each
|
|
73
|
+
* segment on its own is a plain scheme-less host.
|
|
74
|
+
*/
|
|
75
|
+
function bareUrlSegments(id) {
|
|
76
|
+
const segments = id.split(':');
|
|
77
|
+
return segments.length > 1 && segments.every((segment) => looksLikeSchemelessUrl(segment))
|
|
78
|
+
? segments
|
|
79
|
+
: null;
|
|
80
|
+
}
|
|
68
81
|
function isJsonMode(argv) {
|
|
69
|
-
return argv?.includes('--json') ?? false;
|
|
82
|
+
return (argv?.includes('--json') ?? false) || process.argv.includes('--json');
|
|
70
83
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
/** Shared "did you mean https://…?" response for one or several scheme-less URLs. */
|
|
85
|
+
function missingUrlSchemeDetails(urls, argv, config) {
|
|
86
|
+
const attempted = urls.join(' ');
|
|
87
|
+
const suggestion = `${config.bin} ${urls.map((url) => `https://${url}`).join(' ')}`;
|
|
88
|
+
return {
|
|
89
|
+
code: 'MISSING_URL_SCHEME',
|
|
90
|
+
command: attempted,
|
|
91
|
+
jsonMode: isJsonMode(argv),
|
|
92
|
+
message: [
|
|
93
|
+
`${attempted} is not a ${config.bin} command.`,
|
|
94
|
+
`Run ${config.bin} help for a list of available commands.`,
|
|
95
|
+
`Did you mean ${suggestion}? URLs need an http:// or https:// scheme.`,
|
|
96
|
+
].join('\n'),
|
|
97
|
+
suggestions: [suggestion],
|
|
98
|
+
};
|
|
83
99
|
}
|
|
84
100
|
export function buildCommandNotFoundDetails(id, argv, config) {
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
// Match against every loaded command id — including hidden ones that stay invokable
|
|
102
|
+
// (`fetch`, plugin internals). Root `--help` hides them; typo recovery should not.
|
|
103
|
+
const commandIds = config.commandIDs;
|
|
87
104
|
// A scheme-less URL is the most common "not a command" mistake for this CLI, so steer the user to
|
|
88
105
|
// the `bonsai <url>` shorthand with a scheme before falling back to nearest-command matching (which
|
|
89
106
|
// never finds a command for a hostname). The correction is shown, never auto-run (clig.dev).
|
|
107
|
+
// Flag-only argv never reaches here — normalizeArgv early-exits those as MISSING_COMMAND.
|
|
90
108
|
const bareUrl = bareUrlInput(id);
|
|
91
|
-
if (bareUrl)
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
message: [
|
|
97
|
-
`${bareUrl} is not a ${config.bin} command.`,
|
|
98
|
-
`Run ${config.bin} help for a list of available commands.`,
|
|
99
|
-
`Did you mean ${config.bin} https://${bareUrl}? URLs need an http:// or https:// scheme.`,
|
|
100
|
-
].join('\n'),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
109
|
+
if (bareUrl)
|
|
110
|
+
return missingUrlSchemeDetails([bareUrl], argv, config);
|
|
111
|
+
const bareUrls = bareUrlSegments(id);
|
|
112
|
+
if (bareUrls)
|
|
113
|
+
return missingUrlSchemeDetails(bareUrls, argv, config);
|
|
103
114
|
const segments = id.split(':');
|
|
104
|
-
const unexpected = exactZeroArgCommandPrefix(segments,
|
|
115
|
+
const unexpected = exactZeroArgCommandPrefix(segments, commandIds, config);
|
|
105
116
|
if (unexpected) {
|
|
106
117
|
const command = toConfiguredId(unexpected.commandId, config);
|
|
107
118
|
return {
|
|
@@ -114,7 +125,7 @@ export function buildCommandNotFoundDetails(id, argv, config) {
|
|
|
114
125
|
].join('\n'),
|
|
115
126
|
};
|
|
116
127
|
}
|
|
117
|
-
const { suggestion, commandSegments } = findSuggestion(segments,
|
|
128
|
+
const { suggestion, commandSegments } = findSuggestion(segments, commandIds);
|
|
118
129
|
// Show only the segments that name the command, never the positional args oclif folded into the id
|
|
119
130
|
// (which would otherwise glue the arg on and turn `://` into ` //`).
|
|
120
131
|
const displaySegments = commandSegments || topicChainLength(segments, config);
|
|
@@ -123,6 +134,9 @@ export function buildCommandNotFoundDetails(id, argv, config) {
|
|
|
123
134
|
// is the floor, and a concrete "Did you mean …?" correction — when we have one — goes below it.
|
|
124
135
|
const lines = [`${attempted} is not a ${config.bin} command.`];
|
|
125
136
|
lines.push(`Run ${config.bin} help for a list of available commands.`);
|
|
137
|
+
const suggestions = suggestion
|
|
138
|
+
? [`${config.bin} ${toConfiguredId(suggestion, config)}`]
|
|
139
|
+
: undefined;
|
|
126
140
|
if (suggestion)
|
|
127
141
|
lines.push(`Did you mean ${toConfiguredId(suggestion, config)}?`);
|
|
128
142
|
return {
|
|
@@ -130,6 +144,7 @@ export function buildCommandNotFoundDetails(id, argv, config) {
|
|
|
130
144
|
command: attempted,
|
|
131
145
|
jsonMode: isJsonMode(argv),
|
|
132
146
|
message: lines.join('\n'),
|
|
147
|
+
suggestions,
|
|
133
148
|
};
|
|
134
149
|
}
|
|
135
150
|
/**
|
|
@@ -140,8 +155,20 @@ export function buildCommandNotFoundDetails(id, argv, config) {
|
|
|
140
155
|
*/
|
|
141
156
|
const hook = async function (opts) {
|
|
142
157
|
const details = buildCommandNotFoundDetails(opts.id, opts.argv, opts.config);
|
|
143
|
-
if (details.jsonMode)
|
|
144
|
-
return
|
|
145
|
-
|
|
158
|
+
if (details.jsonMode) {
|
|
159
|
+
return writeCommandNotFoundJson({
|
|
160
|
+
command: details.command,
|
|
161
|
+
message: details.message,
|
|
162
|
+
code: details.code,
|
|
163
|
+
suggestions: details.suggestions,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Hook context typings omit `suggestions` even though CLIError accepts them; cast keeps the
|
|
167
|
+
// human pretty-print "Try this:" lines aligned with the JSON envelope.
|
|
168
|
+
this.error(details.message, {
|
|
169
|
+
exit: 2,
|
|
170
|
+
code: details.code,
|
|
171
|
+
suggestions: details.suggestions,
|
|
172
|
+
});
|
|
146
173
|
};
|
|
147
174
|
export default hook;
|
package/dist/lib/argv.d.ts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
+
/** Result of argv normalization before oclif command dispatch. */
|
|
1
2
|
export interface NormalizationResult {
|
|
2
3
|
/** The normalized argv array to set on process.argv. */
|
|
3
4
|
argv: string[];
|
|
4
|
-
/**
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Exit before oclif runs. Used for flag-only / bare `--json` usage errors so human and JSON
|
|
7
|
+
* callers share one path (no "leave a flag token for command_not_found" hack).
|
|
8
|
+
*/
|
|
9
|
+
earlyExit?: {
|
|
6
10
|
exitCode: number;
|
|
7
11
|
envelope: Record<string, unknown>;
|
|
12
|
+
json: boolean;
|
|
8
13
|
};
|
|
9
14
|
}
|
|
10
|
-
|
|
15
|
+
/** Options for {@link normalizeArgv}. */
|
|
16
|
+
export interface ArgvNormalizeOptions {
|
|
17
|
+
/** Tokens that consume the next argv value (`--topic`, `-t`, …). From cli-flag-manifest. */
|
|
18
|
+
valueTakingFlags: ReadonlySet<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalize raw process argv for oclif. `valueTakingFlags` must come from the composition root
|
|
22
|
+
* (`cli-flag-manifest`) — this module stays free of command registry imports.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeArgv(rawArgv: string[], options: ArgvNormalizeOptions): NormalizationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Non-flag positional tokens from a (typically already-normalized) argv. Skips `--help`/`--json`
|
|
27
|
+
* and any dash-prefixed flags so multi-segment command ids (`config get`) stay intact. Shared by
|
|
28
|
+
* help-preflight and json-meta — both run after `normalizeArgv` has already folded `help X` →
|
|
29
|
+
* `X --help` and `-h` → `--help`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function positionalArgvTokens(argv: readonly string[], valueTakingFlags: ReadonlySet<string>): string[];
|
package/dist/lib/argv.js
CHANGED
|
@@ -1,52 +1,92 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EXIT_USAGE } from './cli-error-policy.js';
|
|
2
|
+
import { buildCliErrorEnvelope } from './envelope.js';
|
|
3
|
+
/** Shared MISSING_COMMAND copy for flag-only argv preflight. */
|
|
4
|
+
function missingCommandDetails(bin, swallowed) {
|
|
5
|
+
if (swallowed) {
|
|
6
|
+
return {
|
|
7
|
+
code: 'MISSING_COMMAND',
|
|
8
|
+
message: [
|
|
9
|
+
`Missing URL or command. ${swallowed.flag} consumed ${swallowed.url} as its value, so there was no URL left to fetch.`,
|
|
10
|
+
`Run ${bin} --help for usage.`,
|
|
11
|
+
].join('\n'),
|
|
12
|
+
suggestions: [
|
|
13
|
+
`Pass the URL as the command (flags after): ${bin} ${swallowed.url}`,
|
|
14
|
+
`Or a named command: ${bin} list`,
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
code: 'MISSING_COMMAND',
|
|
20
|
+
message: `Missing URL or command. Run ${bin} --help for usage.`,
|
|
21
|
+
suggestions: [`Pass a URL: ${bin} https://example.com`, `Or a command: ${bin} list`],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Boolean flags registered on every command via BaseCommand.baseFlags. oclif only merges those after
|
|
26
|
+
* it picks a command, so `bonsai --read-only list` would otherwise treat `--read-only` as the
|
|
27
|
+
* command id. Strip and re-append them after the command/URL, same pattern as `--json`.
|
|
28
|
+
*/
|
|
29
|
+
const GLOBAL_BOOLEAN_FLAGS = new Set(['--read-only', '--plan']);
|
|
2
30
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
31
|
+
* When a value-taking flag is the only "command" and its value looks like a URL, the user almost
|
|
32
|
+
* certainly put the URL where the flag value belongs (e.g. `bonsai --tags https://example.com`).
|
|
5
33
|
*/
|
|
6
|
-
function
|
|
34
|
+
function findSwallowedUrlFlag(argv, valueTakingFlags) {
|
|
35
|
+
if (!argv?.length)
|
|
36
|
+
return null;
|
|
37
|
+
for (let i = 0; i < argv.length - 1; i++) {
|
|
38
|
+
const flag = argv[i];
|
|
39
|
+
if (!valueTakingFlags.has(flag))
|
|
40
|
+
continue;
|
|
41
|
+
const value = argv[i + 1];
|
|
42
|
+
if (value.startsWith('-'))
|
|
43
|
+
continue;
|
|
44
|
+
if (looksLikeUrl(value))
|
|
45
|
+
return { flag, url: value };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function missingUsageExit(json, argv, valueTakingFlags) {
|
|
50
|
+
const details = missingCommandDetails('bonsai', findSwallowedUrlFlag(argv, valueTakingFlags));
|
|
7
51
|
return {
|
|
8
|
-
exitCode:
|
|
9
|
-
|
|
52
|
+
exitCode: EXIT_USAGE,
|
|
53
|
+
json,
|
|
54
|
+
envelope: buildCliErrorEnvelope({
|
|
10
55
|
command: 'bonsai',
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
data: null,
|
|
56
|
+
message: details.message,
|
|
57
|
+
code: details.code,
|
|
58
|
+
suggestions: details.suggestions,
|
|
15
59
|
}),
|
|
16
60
|
};
|
|
17
61
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'--artifact-type',
|
|
34
|
-
'--capture-method',
|
|
35
|
-
'--older-than',
|
|
36
|
-
'--inactive',
|
|
37
|
-
'--domain',
|
|
38
|
-
'--remote',
|
|
39
|
-
'--limit',
|
|
40
|
-
]);
|
|
41
|
-
export function normalizeArgv(rawArgv) {
|
|
42
|
-
const onlyJsonFlags = rawArgv.length > 0 && rawArgv.every((arg) => arg === '--json');
|
|
43
|
-
if (onlyJsonFlags) {
|
|
44
|
-
return {
|
|
45
|
-
// bin/cli.mjs exits before applying argv when exitWithJson is set.
|
|
46
|
-
argv: ['--json'],
|
|
47
|
-
exitWithJson: missingUsageJsonExit(),
|
|
48
|
-
};
|
|
62
|
+
/**
|
|
63
|
+
* Index of the first positional command/URL token, skipping flags (and value-flag operands)
|
|
64
|
+
* plus root meta tokens that never name a command (`--help`, `--json`, globals).
|
|
65
|
+
*/
|
|
66
|
+
function firstPositionalIndex(argv, valueTakingFlags) {
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const token = argv[i];
|
|
69
|
+
if (token === '--help' || token === '--json' || GLOBAL_BOOLEAN_FLAGS.has(token))
|
|
70
|
+
continue;
|
|
71
|
+
if (token.startsWith('-')) {
|
|
72
|
+
if (valueTakingFlags.has(token))
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
return i;
|
|
49
77
|
}
|
|
78
|
+
return -1;
|
|
79
|
+
}
|
|
80
|
+
/** True when argv selects a command, URL, or root `--version` action (not flag-only usage). */
|
|
81
|
+
function hasCommandToken(argv, valueTakingFlags) {
|
|
82
|
+
return argv.includes('--version') || firstPositionalIndex(argv, valueTakingFlags) !== -1;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalize raw process argv for oclif. `valueTakingFlags` must come from the composition root
|
|
86
|
+
* (`cli-flag-manifest`) — this module stays free of command registry imports.
|
|
87
|
+
*/
|
|
88
|
+
export function normalizeArgv(rawArgv, options) {
|
|
89
|
+
const { valueTakingFlags } = options;
|
|
50
90
|
// oclif's JSON flag is command-scoped, so `bonsai list --json` works but
|
|
51
91
|
// `bonsai --json list` is otherwise parsed as an unknown command named
|
|
52
92
|
// "--json". Collect every --json, append one copy after the command/URL, and
|
|
@@ -60,22 +100,20 @@ export function normalizeArgv(rawArgv) {
|
|
|
60
100
|
}
|
|
61
101
|
const helpRequested = tokens.includes('--help');
|
|
62
102
|
let core = tokens.filter((arg) => arg !== '--help');
|
|
103
|
+
// Relocate base boolean flags so they survive command resolution (see GLOBAL_BOOLEAN_FLAGS).
|
|
104
|
+
const relocatedGlobals = [];
|
|
105
|
+
core = core.filter((arg) => {
|
|
106
|
+
if (!GLOBAL_BOOLEAN_FLAGS.has(arg))
|
|
107
|
+
return true;
|
|
108
|
+
if (!relocatedGlobals.includes(arg))
|
|
109
|
+
relocatedGlobals.push(arg);
|
|
110
|
+
return false;
|
|
111
|
+
});
|
|
63
112
|
// Treat URL-shaped tokens as the `fetch` shorthand. Match both `https://...` and scheme-only
|
|
64
113
|
// forms like `javascript:` or `data:` so fetch can reject unsupported protocols instead of oclif
|
|
65
114
|
// reporting a misleading "command not found". If the invocation begins with a flag, allow common
|
|
66
115
|
// flag-before-argument usage such as `bonsai --format detailed https://example.com`.
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < core.length; i++) {
|
|
69
|
-
const token = core[i];
|
|
70
|
-
if (token.startsWith('-')) {
|
|
71
|
-
if (FLAGS_WITH_VALUES.has(token)) {
|
|
72
|
-
i++; // skip the value
|
|
73
|
-
}
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
firstNonFlagArgIndex = i;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
116
|
+
const firstNonFlagArgIndex = firstPositionalIndex(core, valueTakingFlags);
|
|
79
117
|
const rootFetchShape = firstNonFlagArgIndex !== -1 && looksLikeUrl(core[firstNonFlagArgIndex]);
|
|
80
118
|
if (rootFetchShape) {
|
|
81
119
|
const url = core[firstNonFlagArgIndex];
|
|
@@ -88,10 +126,43 @@ export function normalizeArgv(rawArgv) {
|
|
|
88
126
|
}
|
|
89
127
|
if (helpRequested)
|
|
90
128
|
core.push('--help');
|
|
129
|
+
core.push(...relocatedGlobals);
|
|
91
130
|
if (jsonMode)
|
|
92
131
|
core.push('--json');
|
|
132
|
+
// Flag-only argv (`--json`, `--read-only`, `--tags https://…`) never resolves a command.
|
|
133
|
+
// Exit here so command_not_found does not special-case dash-prefixed ids.
|
|
134
|
+
// Empty argv and `--help`/`--version` stay with oclif.
|
|
135
|
+
if (!helpRequested && core.length > 0 && !hasCommandToken(core, valueTakingFlags)) {
|
|
136
|
+
return {
|
|
137
|
+
argv: jsonMode ? ['--json'] : [],
|
|
138
|
+
earlyExit: missingUsageExit(jsonMode, rawArgv, valueTakingFlags),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
93
141
|
return { argv: core };
|
|
94
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Non-flag positional tokens from a (typically already-normalized) argv. Skips `--help`/`--json`
|
|
145
|
+
* and any dash-prefixed flags so multi-segment command ids (`config get`) stay intact. Shared by
|
|
146
|
+
* help-preflight and json-meta — both run after `normalizeArgv` has already folded `help X` →
|
|
147
|
+
* `X --help` and `-h` → `--help`.
|
|
148
|
+
*/
|
|
149
|
+
export function positionalArgvTokens(argv, valueTakingFlags) {
|
|
150
|
+
const tokens = [];
|
|
151
|
+
for (let i = 0; i < argv.length; i++) {
|
|
152
|
+
const arg = argv[i];
|
|
153
|
+
if (arg === '--')
|
|
154
|
+
break;
|
|
155
|
+
if (arg === '--help' || arg === '--json')
|
|
156
|
+
continue;
|
|
157
|
+
if (arg.startsWith('-')) {
|
|
158
|
+
if (!arg.includes('=') && valueTakingFlags.has(arg))
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
tokens.push(arg);
|
|
163
|
+
}
|
|
164
|
+
return tokens;
|
|
165
|
+
}
|
|
95
166
|
function looksLikeUrl(arg) {
|
|
96
167
|
return arg.includes('://') || /^[a-z][a-z0-9+.-]*:/i.test(arg);
|
|
97
168
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapse a multi-URL command's per-URL rows into the CLI's single-vs-array return
|
|
3
|
+
* contract, and mark {@link EXIT_RUNTIME_FAILURE} when any row is a failure outcome.
|
|
4
|
+
*
|
|
5
|
+
* @param results - Per-URL result rows.
|
|
6
|
+
* @param isFailure - Predicate marking a row as a failure outcome.
|
|
7
|
+
* @returns The sole row when there is one URL; otherwise the full array.
|
|
8
|
+
*/
|
|
9
|
+
export declare function finalizeBatch<T>(results: T[], isFailure: (row: T) => boolean): T | T[];
|
|
10
|
+
/**
|
|
11
|
+
* status/inspect batch rows: a miss or a validation error row is a failure outcome.
|
|
12
|
+
*
|
|
13
|
+
* @param row - Batch row with a `status` field.
|
|
14
|
+
* @returns `true` when the row should fail the batch exit code.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isBatchReadFailure(row: {
|
|
17
|
+
status: string;
|
|
18
|
+
}): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Sparse validation-failure row for multi-URL status/inspect. Only the fields agents need —
|
|
21
|
+
* no fake cacheKey/freshness/metadata padding.
|
|
22
|
+
*
|
|
23
|
+
* @param url - Input URL that failed validation.
|
|
24
|
+
* @param err - Error-like object with optional code/message/suggestions/ref.
|
|
25
|
+
* @returns Sparse error row for the batch result array.
|
|
26
|
+
*/
|
|
27
|
+
export declare function urlValidationErrorRow(url: string, err: {
|
|
28
|
+
code?: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
suggestions?: string[];
|
|
31
|
+
ref?: string;
|
|
32
|
+
}): {
|
|
33
|
+
status: "error";
|
|
34
|
+
normalizedUrl: string;
|
|
35
|
+
error: {
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
suggestions?: string[];
|
|
39
|
+
ref?: string;
|
|
40
|
+
};
|
|
41
|
+
};
|