expandai 0.0.2 → 0.0.3
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 +42 -5
- package/build/dts/ApiClient.d.ts +128 -194
- package/build/dts/ApiClient.d.ts.map +1 -1
- package/build/dts/Auth/Domain.d.ts +95 -0
- package/build/dts/Auth/Domain.d.ts.map +1 -0
- package/build/dts/Auth/ManualAuthorization.d.ts +107 -0
- package/build/dts/Auth/ManualAuthorization.d.ts.map +1 -0
- package/build/dts/Auth/PendingInit.d.ts +35 -0
- package/build/dts/Auth/PendingInit.d.ts.map +1 -0
- package/build/dts/Auth/Pkce.d.ts +10 -0
- package/build/dts/Auth/Pkce.d.ts.map +1 -0
- package/build/dts/Auth/SetupGrantAuthorization.d.ts +34 -0
- package/build/dts/Auth/SetupGrantAuthorization.d.ts.map +1 -0
- package/build/dts/Authentication.d.ts +10 -9
- package/build/dts/Authentication.d.ts.map +1 -1
- package/build/dts/BrowserOAuth.d.ts +7 -7
- package/build/dts/BrowserOAuth.d.ts.map +1 -1
- package/build/dts/CommandPath.d.ts +2 -0
- package/build/dts/CommandPath.d.ts.map +1 -0
- package/build/dts/Commands/Fetch.d.ts.map +1 -1
- package/build/dts/Commands/Hook.d.ts +5 -5
- package/build/dts/Commands/Init.d.ts +55 -0
- package/build/dts/Commands/Init.d.ts.map +1 -0
- package/build/dts/Commands/Mcp.d.ts +6 -6
- package/build/dts/Commands/Search.d.ts.map +1 -1
- package/build/dts/Commands/Skill.d.ts +4 -4
- package/build/dts/Commands/Status.d.ts +27 -0
- package/build/dts/Commands/Status.d.ts.map +1 -0
- package/build/dts/Commands/Telemetry.d.ts +7 -0
- package/build/dts/Commands/Telemetry.d.ts.map +1 -0
- package/build/dts/Commands/Uninstall.d.ts +4 -4
- package/build/dts/Config.d.ts +108 -17
- package/build/dts/Config.d.ts.map +1 -1
- package/build/dts/Hook/SnapshotNote.d.ts +8 -4
- package/build/dts/Hook/SnapshotNote.d.ts.map +1 -1
- package/build/dts/Init/ClaudeAdapter.d.ts +61 -0
- package/build/dts/Init/ClaudeAdapter.d.ts.map +1 -0
- package/build/dts/Init/ClaudeProfile.d.ts +78 -0
- package/build/dts/Init/ClaudeProfile.d.ts.map +1 -0
- package/build/dts/Init/Domain.d.ts +139 -0
- package/build/dts/Init/Domain.d.ts.map +1 -0
- package/build/dts/Init/HarnessAdapter.d.ts +33 -0
- package/build/dts/Init/HarnessAdapter.d.ts.map +1 -0
- package/build/dts/Init/HarnessDetector.d.ts +19 -0
- package/build/dts/Init/HarnessDetector.d.ts.map +1 -0
- package/build/dts/Init/HarnessRegistry.d.ts +50 -0
- package/build/dts/Init/HarnessRegistry.d.ts.map +1 -0
- package/build/dts/Init/MutationTransaction.d.ts +96 -0
- package/build/dts/Init/MutationTransaction.d.ts.map +1 -0
- package/build/dts/Init/Service.d.ts +72 -0
- package/build/dts/Init/Service.d.ts.map +1 -0
- package/build/dts/Init/StandardAdapters.d.ts +42 -0
- package/build/dts/Init/StandardAdapters.d.ts.map +1 -0
- package/build/dts/Init/Status.d.ts +149 -0
- package/build/dts/Init/Status.d.ts.map +1 -0
- package/build/dts/Install/Claude/Hook.d.ts +85 -25
- package/build/dts/Install/Claude/Hook.d.ts.map +1 -1
- package/build/dts/Install/Claude/Mcp.d.ts +21 -18
- package/build/dts/Install/Claude/Mcp.d.ts.map +1 -1
- package/build/dts/Install/Claude/Skill.d.ts +14 -15
- package/build/dts/Install/Claude/Skill.d.ts.map +1 -1
- package/build/dts/Install/Codex/Mcp.d.ts +14 -8
- package/build/dts/Install/Codex/Mcp.d.ts.map +1 -1
- package/build/dts/Install/Codex/Skill.d.ts +4 -2
- package/build/dts/Install/Codex/Skill.d.ts.map +1 -1
- package/build/dts/Install/Cursor/Mcp.d.ts +13 -13
- package/build/dts/Install/Cursor/Mcp.d.ts.map +1 -1
- package/build/dts/Install/Cursor/Skill.d.ts +4 -2
- package/build/dts/Install/Cursor/Skill.d.ts.map +1 -1
- package/build/dts/Install/Domain.d.ts +35 -5
- package/build/dts/Install/Domain.d.ts.map +1 -1
- package/build/dts/Install/Hook.d.ts +4 -4
- package/build/dts/Install/Hook.d.ts.map +1 -1
- package/build/dts/Install/Mcp.d.ts +5 -5
- package/build/dts/Install/Mcp.d.ts.map +1 -1
- package/build/dts/Install/Opencode/Hook.d.ts +4 -3
- package/build/dts/Install/Opencode/Hook.d.ts.map +1 -1
- package/build/dts/Install/Opencode/Mcp.d.ts +11 -11
- package/build/dts/Install/Opencode/Mcp.d.ts.map +1 -1
- package/build/dts/Install/Opencode/Skill.d.ts +4 -2
- package/build/dts/Install/Opencode/Skill.d.ts.map +1 -1
- package/build/dts/Install/Skill.d.ts +3 -3
- package/build/dts/Install/Uninstall.d.ts +6 -6
- package/build/dts/Install/Uninstall.d.ts.map +1 -1
- package/build/dts/MarkdownAnsi.d.ts +29 -0
- package/build/dts/MarkdownAnsi.d.ts.map +1 -0
- package/build/dts/Mcp/ExpandDocs.d.ts +17 -5
- package/build/dts/Mcp/ExpandDocs.d.ts.map +1 -1
- package/build/dts/Mcp/Server.d.ts +1 -1
- package/build/dts/Mcp/Server.d.ts.map +1 -1
- package/build/dts/Mcp/tools/Citations.d.ts +100 -71
- package/build/dts/Mcp/tools/Citations.d.ts.map +1 -1
- package/build/dts/Mcp/tools/Fetch.d.ts +16 -15
- package/build/dts/Mcp/tools/Fetch.d.ts.map +1 -1
- package/build/dts/SecureFile.d.ts +23 -0
- package/build/dts/SecureFile.d.ts.map +1 -0
- package/build/dts/Snippets.d.ts +11 -12
- package/build/dts/Snippets.d.ts.map +1 -1
- package/build/dts/Telemetry.d.ts +30 -0
- package/build/dts/Telemetry.d.ts.map +1 -0
- package/build/dts/Version.d.ts +2 -0
- package/build/dts/Version.d.ts.map +1 -0
- package/build/dts/Xdg.d.ts +11 -0
- package/build/dts/Xdg.d.ts.map +1 -0
- package/build/esm/ApiClient.js +133 -10
- package/build/esm/ApiClient.js.map +1 -1
- package/build/esm/Auth/Domain.js +56 -0
- package/build/esm/Auth/Domain.js.map +1 -0
- package/build/esm/Auth/ManualAuthorization.js +253 -0
- package/build/esm/Auth/ManualAuthorization.js.map +1 -0
- package/build/esm/Auth/PendingInit.js +267 -0
- package/build/esm/Auth/PendingInit.js.map +1 -0
- package/build/esm/Auth/Pkce.js +23 -0
- package/build/esm/Auth/Pkce.js.map +1 -0
- package/build/esm/Auth/SetupGrantAuthorization.js +91 -0
- package/build/esm/Auth/SetupGrantAuthorization.js.map +1 -0
- package/build/esm/Authentication.js +38 -7
- package/build/esm/Authentication.js.map +1 -1
- package/build/esm/BrowserOAuth.js +7 -10
- package/build/esm/BrowserOAuth.js.map +1 -1
- package/build/esm/CommandPath.js +31 -0
- package/build/esm/CommandPath.js.map +1 -0
- package/build/esm/Commands/Fetch.js +30 -6
- package/build/esm/Commands/Fetch.js.map +1 -1
- package/build/esm/Commands/Init.js +371 -0
- package/build/esm/Commands/Init.js.map +1 -0
- package/build/esm/Commands/Search.js +6 -3
- package/build/esm/Commands/Search.js.map +1 -1
- package/build/esm/Commands/Status.js +186 -0
- package/build/esm/Commands/Status.js.map +1 -0
- package/build/esm/Commands/Telemetry.js +23 -0
- package/build/esm/Commands/Telemetry.js.map +1 -0
- package/build/esm/Config.js +502 -28
- package/build/esm/Config.js.map +1 -1
- package/build/esm/Hook/Claude.js +1 -1
- package/build/esm/Hook/Claude.js.map +1 -1
- package/build/esm/Hook/SnapshotNote.js +54 -7
- package/build/esm/Hook/SnapshotNote.js.map +1 -1
- package/build/esm/Init/ClaudeAdapter.js +137 -0
- package/build/esm/Init/ClaudeAdapter.js.map +1 -0
- package/build/esm/Init/ClaudeProfile.js +380 -0
- package/build/esm/Init/ClaudeProfile.js.map +1 -0
- package/build/esm/Init/Domain.js +118 -0
- package/build/esm/Init/Domain.js.map +1 -0
- package/build/esm/Init/HarnessAdapter.js +21 -0
- package/build/esm/Init/HarnessAdapter.js.map +1 -0
- package/build/esm/Init/HarnessDetector.js +65 -0
- package/build/esm/Init/HarnessDetector.js.map +1 -0
- package/build/esm/Init/HarnessRegistry.js +58 -0
- package/build/esm/Init/HarnessRegistry.js.map +1 -0
- package/build/esm/Init/MutationTransaction.js +494 -0
- package/build/esm/Init/MutationTransaction.js.map +1 -0
- package/build/esm/Init/Service.js +319 -0
- package/build/esm/Init/Service.js.map +1 -0
- package/build/esm/Init/StandardAdapters.js +116 -0
- package/build/esm/Init/StandardAdapters.js.map +1 -0
- package/build/esm/Init/Status.js +215 -0
- package/build/esm/Init/Status.js.map +1 -0
- package/build/esm/Install/Claude/Hook.js +182 -50
- package/build/esm/Install/Claude/Hook.js.map +1 -1
- package/build/esm/Install/Claude/Mcp.js +56 -26
- package/build/esm/Install/Claude/Mcp.js.map +1 -1
- package/build/esm/Install/Claude/Skill.js +49 -27
- package/build/esm/Install/Claude/Skill.js.map +1 -1
- package/build/esm/Install/Codex/Mcp.js +9 -4
- package/build/esm/Install/Codex/Mcp.js.map +1 -1
- package/build/esm/Install/Codex/Skill.js +4 -1
- package/build/esm/Install/Codex/Skill.js.map +1 -1
- package/build/esm/Install/Cursor/Mcp.js +7 -4
- package/build/esm/Install/Cursor/Mcp.js.map +1 -1
- package/build/esm/Install/Cursor/Skill.js +4 -1
- package/build/esm/Install/Cursor/Skill.js.map +1 -1
- package/build/esm/Install/Domain.js +60 -3
- package/build/esm/Install/Domain.js.map +1 -1
- package/build/esm/Install/Hook.js.map +1 -1
- package/build/esm/Install/Mcp.js +1 -1
- package/build/esm/Install/Mcp.js.map +1 -1
- package/build/esm/Install/Opencode/Hook.js +1 -1
- package/build/esm/Install/Opencode/Hook.js.map +1 -1
- package/build/esm/Install/Opencode/Mcp.js +6 -3
- package/build/esm/Install/Opencode/Mcp.js.map +1 -1
- package/build/esm/Install/Opencode/Skill.js +4 -1
- package/build/esm/Install/Opencode/Skill.js.map +1 -1
- package/build/esm/Install/Opencode/expandai-webfetch.js +27 -9
- package/build/esm/Install/Skill.js.map +1 -1
- package/build/esm/Install/Uninstall.js +2 -2
- package/build/esm/Install/Uninstall.js.map +1 -1
- package/build/esm/MarkdownAnsi.js +142 -0
- package/build/esm/MarkdownAnsi.js.map +1 -0
- package/build/esm/Mcp/ExpandDocs.js +44 -52
- package/build/esm/Mcp/ExpandDocs.js.map +1 -1
- package/build/esm/Mcp/RequestHeaders.js.map +1 -1
- package/build/esm/Mcp/Server.js +2 -1
- package/build/esm/Mcp/Server.js.map +1 -1
- package/build/esm/Mcp/tools/Citations.js +109 -8
- package/build/esm/Mcp/tools/Citations.js.map +1 -1
- package/build/esm/Mcp/tools/Fetch.js +36 -12
- package/build/esm/Mcp/tools/Fetch.js.map +1 -1
- package/build/esm/SecureFile.js +36 -0
- package/build/esm/SecureFile.js.map +1 -0
- package/build/esm/Snippets.js +12 -1
- package/build/esm/Snippets.js.map +1 -1
- package/build/esm/Telemetry.js +157 -0
- package/build/esm/Telemetry.js.map +1 -0
- package/build/esm/Version.js +4 -0
- package/build/esm/Version.js.map +1 -0
- package/build/esm/Xdg.js +15 -0
- package/build/esm/Xdg.js.map +1 -0
- package/build/esm/main.js +122 -14
- package/build/esm/main.js.map +1 -1
- package/build/skills/expandai/SKILL.md +26 -3
- package/package.json +4 -4
- package/src/ApiClient.ts +206 -13
- package/src/Auth/Domain.ts +63 -0
- package/src/Auth/ManualAuthorization.ts +296 -0
- package/src/Auth/PendingInit.ts +338 -0
- package/src/Auth/Pkce.ts +26 -0
- package/src/Auth/SetupGrantAuthorization.ts +116 -0
- package/src/Authentication.ts +49 -11
- package/src/BrowserOAuth.ts +10 -12
- package/src/CommandPath.ts +30 -0
- package/src/Commands/Fetch.ts +42 -11
- package/src/Commands/Init.ts +489 -0
- package/src/Commands/Search.ts +8 -3
- package/src/Commands/Status.ts +228 -0
- package/src/Commands/Telemetry.ts +42 -0
- package/src/Config.ts +695 -50
- package/src/Hook/Claude.ts +1 -1
- package/src/Hook/SnapshotNote.ts +55 -7
- package/src/Init/ClaudeAdapter.ts +191 -0
- package/src/Init/ClaudeProfile.ts +496 -0
- package/src/Init/Domain.ts +153 -0
- package/src/Init/HarnessAdapter.ts +35 -0
- package/src/Init/HarnessDetector.ts +77 -0
- package/src/Init/HarnessRegistry.ts +87 -0
- package/src/Init/MutationTransaction.ts +747 -0
- package/src/Init/Service.ts +385 -0
- package/src/Init/StandardAdapters.ts +141 -0
- package/src/Init/Status.ts +253 -0
- package/src/Install/Claude/Hook.ts +234 -81
- package/src/Install/Claude/Mcp.ts +80 -33
- package/src/Install/Claude/Skill.ts +70 -37
- package/src/Install/Codex/Mcp.ts +14 -9
- package/src/Install/Codex/Skill.ts +9 -5
- package/src/Install/Cursor/Mcp.ts +12 -9
- package/src/Install/Cursor/Skill.ts +9 -5
- package/src/Install/Domain.ts +77 -4
- package/src/Install/Hook.ts +3 -3
- package/src/Install/Mcp.ts +6 -6
- package/src/Install/Opencode/Hook.ts +5 -5
- package/src/Install/Opencode/Mcp.ts +11 -8
- package/src/Install/Opencode/Skill.ts +9 -5
- package/src/Install/Opencode/expandai-webfetch.js +27 -9
- package/src/Install/Skill.ts +3 -3
- package/src/Install/Uninstall.ts +3 -3
- package/src/MarkdownAnsi.ts +162 -0
- package/src/Mcp/ExpandDocs.ts +46 -55
- package/src/Mcp/Server.ts +2 -1
- package/src/Mcp/tools/Citations.ts +117 -8
- package/src/Mcp/tools/Fetch.ts +40 -14
- package/src/SecureFile.ts +70 -0
- package/src/Snippets.ts +19 -12
- package/src/Telemetry.ts +186 -0
- package/src/Version.ts +3 -0
- package/src/Xdg.ts +18 -0
- package/src/main.ts +171 -12
- package/build/dts/DeviceAuth.d.ts +0 -87
- package/build/dts/DeviceAuth.d.ts.map +0 -1
- package/build/esm/DeviceAuth.js +0 -127
- package/build/esm/DeviceAuth.js.map +0 -1
- package/src/DeviceAuth.ts +0 -193
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { Command, Options, Prompt } from '@effect/cli'
|
|
2
|
+
import { Terminal } from '@effect/platform'
|
|
3
|
+
import { Array, Console, Effect, Either, Option, Schema } from 'effect'
|
|
4
|
+
import { Authentication } from '../Authentication.js'
|
|
5
|
+
import {
|
|
6
|
+
HARNESS_SELECTION_MESSAGE,
|
|
7
|
+
InitDetectionResult,
|
|
8
|
+
InitErrorEnvelope,
|
|
9
|
+
InitHarness,
|
|
10
|
+
type InitHarnesses,
|
|
11
|
+
InitRequest,
|
|
12
|
+
InitResult,
|
|
13
|
+
InitScope,
|
|
14
|
+
NO_HARNESS_DETECTED_MESSAGE,
|
|
15
|
+
} from '../Init/Domain.js'
|
|
16
|
+
import { HarnessDetector } from '../Init/HarnessDetector.js'
|
|
17
|
+
import { InitService, InitServiceError } from '../Init/Service.js'
|
|
18
|
+
|
|
19
|
+
const harness = Options.text('harness').pipe(
|
|
20
|
+
Options.optional,
|
|
21
|
+
Options.withDescription('Agent harness running this setup, used as a detection hint'),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const harnesses = Options.text('harnesses').pipe(
|
|
25
|
+
Options.optional,
|
|
26
|
+
Options.withDescription('Comma-separated harnesses to configure. Omit to configure every detected harness.'),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const detect = Options.boolean('detect').pipe(
|
|
30
|
+
Options.withDescription('Report the harnesses setup would configure, and change nothing'),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const scope = Options.text('scope').pipe(Options.optional, Options.withDescription('Configuration scope'))
|
|
34
|
+
|
|
35
|
+
const setupToken = Options.text('setup-token').pipe(
|
|
36
|
+
Options.optional,
|
|
37
|
+
Options.withDescription('Short-lived, single-use setup token from the Expand dashboard'),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const json = Options.boolean('json').pipe(Options.withDescription('Emit one machine-readable JSON result'))
|
|
41
|
+
|
|
42
|
+
const config = { harness, harnesses, detect, scope, setupToken, json }
|
|
43
|
+
|
|
44
|
+
const InitErrorJson = Schema.parseJson(InitErrorEnvelope)
|
|
45
|
+
const InitResultJson = Schema.parseJson(InitResult)
|
|
46
|
+
const InitDetectionJson = Schema.parseJson(InitDetectionResult)
|
|
47
|
+
|
|
48
|
+
const INVALID_ARGUMENTS_MESSAGE =
|
|
49
|
+
'Agent init requires --scope project|global and --json, or --detect --json to only report detected harnesses. Optional: --harnesses claude-code,cursor,opencode,codex to configure an explicit subset, --harness <name> as a current-harness hint, and --setup-token <token> from a dashboard setup prompt. After the user approves in the browser, run the same command again — there is no code to pass back.'
|
|
50
|
+
|
|
51
|
+
export const SUPPORTED_HARNESS_LIST = 'claude-code, cursor, opencode, codex'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Says that the *harness itself* is unsupported, rather than that the flag was
|
|
55
|
+
* malformed. An agent told only "invalid arguments" alongside a list of four
|
|
56
|
+
* valid names will pick one of them and misreport the user's environment.
|
|
57
|
+
*/
|
|
58
|
+
export const UNSUPPORTED_HARNESS_MESSAGE = `Expand does not support that agent harness. Supported harnesses are ${SUPPORTED_HARNESS_LIST}. Do not substitute a different harness: tell the user their agent is not supported yet.`
|
|
59
|
+
|
|
60
|
+
const INVALID_SETUP_TOKEN_MESSAGE =
|
|
61
|
+
'That setup token is malformed. Copy it exactly as the dashboard issued it, or create a new one; do not retype or truncate it.'
|
|
62
|
+
|
|
63
|
+
const INVALID_HARNESS_SELECTION_MESSAGE = `--harnesses takes a comma-separated list drawn from ${SUPPORTED_HARNESS_LIST}, for example --harnesses claude-code,cursor. Every entry must be one of those names.`
|
|
64
|
+
|
|
65
|
+
/** Mirrors the `SetupToken` schema so a typo is reported as a bad token, not bad flags. */
|
|
66
|
+
const SETUP_TOKEN_PATTERN = /^[A-Za-z0-9._~-]{20,256}$/u
|
|
67
|
+
|
|
68
|
+
/** Distinct codes so an agent can fix the specific argument instead of guessing. */
|
|
69
|
+
function argumentErrorJson(code: string, message: string): string {
|
|
70
|
+
return Schema.encodeSync(InitErrorJson)(
|
|
71
|
+
new InitErrorEnvelope({ schemaVersion: 1, status: 'error', code, message, recoverable: true }),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function invalidArgumentsJson(): string {
|
|
76
|
+
return argumentErrorJson('invalid_arguments', INVALID_ARGUMENTS_MESSAGE)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function errorJson(code: string, message: string, recoverable: boolean): string {
|
|
80
|
+
return Schema.encodeSync(InitErrorJson)(
|
|
81
|
+
new InitErrorEnvelope({ schemaVersion: 1, status: 'error', code, message, recoverable }),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function emitError(code: string, message: string, recoverable: boolean, exitCode: number) {
|
|
86
|
+
return Console.error(errorJson(code, message, recoverable)).pipe(
|
|
87
|
+
Effect.zipRight(Effect.sync(() => void (process.exitCode = exitCode))),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface InitCommandOptions {
|
|
92
|
+
readonly detect: boolean
|
|
93
|
+
readonly harness: Option.Option<string>
|
|
94
|
+
readonly harnesses: Option.Option<string>
|
|
95
|
+
readonly json: boolean
|
|
96
|
+
readonly scope: Option.Option<string>
|
|
97
|
+
readonly setupToken: Option.Option<string>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Decodes `--harnesses claude-code,cursor`. Returns `undefined` for any
|
|
102
|
+
* unknown or empty entry: silently dropping one would configure less than the
|
|
103
|
+
* user asked for, which is the failure mode this flag exists to prevent.
|
|
104
|
+
*/
|
|
105
|
+
export function parseHarnessSelection(value: string): InitHarnesses | undefined {
|
|
106
|
+
const entries = value.split(',').map((entry) => entry.trim())
|
|
107
|
+
const decoded: InitHarness[] = []
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const harness = Schema.decodeUnknownOption(InitHarness)(entry)
|
|
110
|
+
if (Option.isNone(harness)) return undefined
|
|
111
|
+
if (!decoded.includes(harness.value)) decoded.push(harness.value)
|
|
112
|
+
}
|
|
113
|
+
return Array.isNonEmptyArray(decoded) ? decoded : undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface OnboardingExecutionModeInput {
|
|
117
|
+
readonly argv: readonly string[]
|
|
118
|
+
readonly environment: Readonly<Record<string, string | undefined>>
|
|
119
|
+
readonly stdinIsTty: boolean
|
|
120
|
+
readonly stdoutIsTty: boolean
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface InitExecutionModeInput extends OnboardingExecutionModeInput {}
|
|
124
|
+
|
|
125
|
+
function enabledEnvironmentFlag(value: string | undefined): boolean {
|
|
126
|
+
return value !== undefined && value !== '' && value !== '0' && value !== 'false'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Selects a safe onboarding surface before either CLI tree parses the command. */
|
|
130
|
+
export function resolveOnboardingExecutionMode(
|
|
131
|
+
input: OnboardingExecutionModeInput,
|
|
132
|
+
command: 'init' | 'status',
|
|
133
|
+
): 'human' | 'machine' | undefined {
|
|
134
|
+
if (input.argv[2] !== command) return undefined
|
|
135
|
+
const args = input.argv.slice(3)
|
|
136
|
+
const agentSignal =
|
|
137
|
+
input.environment.EXPANDAI_CLI_CONTEXT === 'agent' ||
|
|
138
|
+
enabledEnvironmentFlag(input.environment.CLAUDECODE) ||
|
|
139
|
+
input.environment.CURSOR_TRACE_ID !== undefined ||
|
|
140
|
+
input.environment.CODEX_RUN_ID !== undefined
|
|
141
|
+
const machineSafe = !input.stdinIsTty || !input.stdoutIsTty || args.includes('--json') || agentSignal
|
|
142
|
+
return machineSafe ? 'machine' : 'human'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Selects the safe init surface before either CLI tree parses the command. */
|
|
146
|
+
export function resolveInitExecutionMode(input: InitExecutionModeInput): 'human' | 'machine' | undefined {
|
|
147
|
+
return resolveOnboardingExecutionMode(input, 'init')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseMachineArguments(args: readonly string[]): InitCommandOptions | undefined {
|
|
151
|
+
function parse(index: number, parsed: InitCommandOptions): InitCommandOptions | undefined {
|
|
152
|
+
if (index >= args.length) return parsed
|
|
153
|
+
const token = args[index]
|
|
154
|
+
if (token === '--json' && !parsed.json) {
|
|
155
|
+
return parse(index + 1, { ...parsed, json: true })
|
|
156
|
+
}
|
|
157
|
+
if (token === '--detect' && !parsed.detect) {
|
|
158
|
+
return parse(index + 1, { ...parsed, detect: true })
|
|
159
|
+
}
|
|
160
|
+
if (token !== '--setup-token' && token !== '--harness' && token !== '--harnesses' && token !== '--scope')
|
|
161
|
+
return undefined
|
|
162
|
+
const value = args[index + 1]
|
|
163
|
+
if (value === undefined || value.startsWith('-')) return undefined
|
|
164
|
+
if (token === '--setup-token' && Option.isNone(parsed.setupToken)) {
|
|
165
|
+
return parse(index + 2, { ...parsed, setupToken: Option.some(value) })
|
|
166
|
+
}
|
|
167
|
+
if (token === '--harness' && Option.isNone(parsed.harness)) {
|
|
168
|
+
return parse(index + 2, { ...parsed, harness: Option.some(value) })
|
|
169
|
+
}
|
|
170
|
+
if (token === '--harnesses' && Option.isNone(parsed.harnesses)) {
|
|
171
|
+
return parse(index + 2, { ...parsed, harnesses: Option.some(value) })
|
|
172
|
+
}
|
|
173
|
+
if (token === '--scope' && Option.isNone(parsed.scope)) {
|
|
174
|
+
return parse(index + 2, { ...parsed, scope: Option.some(value) })
|
|
175
|
+
}
|
|
176
|
+
return undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return parse(0, {
|
|
180
|
+
detect: false,
|
|
181
|
+
harness: Option.none(),
|
|
182
|
+
harnesses: Option.none(),
|
|
183
|
+
json: false,
|
|
184
|
+
scope: Option.none(),
|
|
185
|
+
setupToken: Option.none(),
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Returns a safe JSON error before Effect CLI can render ordinary parser help. */
|
|
190
|
+
export function preflightAgentInitArgv(argv: readonly string[]): string | undefined {
|
|
191
|
+
if (argv[2] !== 'init') return undefined
|
|
192
|
+
const args = argv.slice(3)
|
|
193
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) return undefined
|
|
194
|
+
const parsed = parseMachineArguments(args)
|
|
195
|
+
if (parsed === undefined) return invalidArgumentsJson()
|
|
196
|
+
if (!parsed.json) return invalidArgumentsJson()
|
|
197
|
+
if (Option.isSome(parsed.harness) && Option.isNone(Schema.decodeUnknownOption(InitHarness)(parsed.harness.value))) {
|
|
198
|
+
return argumentErrorJson('unsupported_harness', UNSUPPORTED_HARNESS_MESSAGE)
|
|
199
|
+
}
|
|
200
|
+
if (Option.isSome(parsed.harnesses) && parseHarnessSelection(parsed.harnesses.value) === undefined) {
|
|
201
|
+
return argumentErrorJson('invalid_harness_selection', INVALID_HARNESS_SELECTION_MESSAGE)
|
|
202
|
+
}
|
|
203
|
+
// Detection reports and exits, so it needs no scope and no credentials.
|
|
204
|
+
if (parsed.detect) return undefined
|
|
205
|
+
if (!Option.contains(parsed.scope, 'project') && !Option.contains(parsed.scope, 'global')) {
|
|
206
|
+
return invalidArgumentsJson()
|
|
207
|
+
}
|
|
208
|
+
if (Option.isSome(parsed.setupToken) && !SETUP_TOKEN_PATTERN.test(parsed.setupToken.value)) {
|
|
209
|
+
return argumentErrorJson('invalid_setup_token', INVALID_SETUP_TOKEN_MESSAGE)
|
|
210
|
+
}
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function renderHumanResult(result: typeof InitResult.Type): string {
|
|
215
|
+
if (result.status === 'authorization_required') {
|
|
216
|
+
// The approval page asks the user to type this code and compare it against
|
|
217
|
+
// their terminal, so the terminal has to print it.
|
|
218
|
+
return `Expand authorization required\n${result.userMessage}\nOpen: ${result.authorization.url.toString()}\nEnter code: ${result.authorization.userCode}`
|
|
219
|
+
}
|
|
220
|
+
const configured = result.installations.map((installation) => installation.harness).join(', ')
|
|
221
|
+
const failures = result.failures ?? []
|
|
222
|
+
// A run that configured some harnesses and not others must not print
|
|
223
|
+
// "complete" — the heading is the part a user skims.
|
|
224
|
+
const heading = failures.length > 0 ? 'Expand setup incomplete' : 'Expand setup complete'
|
|
225
|
+
const failed = failures.map((failure) => `${failure.harness} (${failure.code})`).join(', ')
|
|
226
|
+
return [
|
|
227
|
+
heading,
|
|
228
|
+
result.userMessage,
|
|
229
|
+
`Configured: ${configured}`,
|
|
230
|
+
...(failures.length > 0 ? [`Failed: ${failed}`] : []),
|
|
231
|
+
].join('\n')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const runInitService = Effect.fnUntraced(function* (request: InitRequest, jsonOutput: boolean) {
|
|
235
|
+
const initService = yield* InitService
|
|
236
|
+
const result = yield* Effect.either(initService.run(request))
|
|
237
|
+
if (Either.isLeft(result)) {
|
|
238
|
+
const error = result.left
|
|
239
|
+
if (error instanceof InitServiceError) {
|
|
240
|
+
yield* emitError(error.code, error.message, error.recoverable, 1)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
yield* emitError('init_failed', 'Expand init could not be completed safely.', true, 1)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
if (!jsonOutput) {
|
|
247
|
+
yield* Console.log(renderHumanResult(result.right))
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
const encoded = yield* Schema.encode(InitResultJson)(result.right).pipe(
|
|
251
|
+
Effect.catchAll(() => emitError('invalid_result', 'Expand init returned an invalid result.', false, 1)),
|
|
252
|
+
)
|
|
253
|
+
if (typeof encoded === 'string') yield* Console.log(encoded)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
/** Emits what setup would configure without authorizing or writing anything. */
|
|
257
|
+
const runAgentDetect = Effect.fnUntraced(function* (hint: Option.Option<InitHarness>) {
|
|
258
|
+
const detector = yield* HarnessDetector
|
|
259
|
+
const detected = yield* detector.detect({ ...(Option.isSome(hint) ? { hint: hint.value } : {}) })
|
|
260
|
+
const current = yield* detector.detectCurrent()
|
|
261
|
+
const encoded = yield* Schema.encode(InitDetectionJson)(
|
|
262
|
+
new InitDetectionResult({
|
|
263
|
+
schemaVersion: 1,
|
|
264
|
+
status: 'detected',
|
|
265
|
+
harnesses: detected,
|
|
266
|
+
...(Option.isSome(current) ? { current: current.value } : {}),
|
|
267
|
+
userMessage: detected.length === 0 ? NO_HARNESS_DETECTED_MESSAGE : HARNESS_SELECTION_MESSAGE,
|
|
268
|
+
}),
|
|
269
|
+
).pipe(Effect.catchAll(() => emitError('invalid_result', 'Expand init returned an invalid result.', false, 1)))
|
|
270
|
+
if (typeof encoded === 'string') yield* Console.log(encoded)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const runAgentInit = Effect.fnUntraced(function* ({
|
|
274
|
+
detect: detectOnly,
|
|
275
|
+
harness,
|
|
276
|
+
harnesses: harnessSelection,
|
|
277
|
+
json,
|
|
278
|
+
scope,
|
|
279
|
+
setupToken,
|
|
280
|
+
}: InitCommandOptions) {
|
|
281
|
+
if (!json) {
|
|
282
|
+
yield* emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
const harnessHint = Option.flatMap(harness, (value) => Schema.decodeUnknownOption(InitHarness)(value))
|
|
286
|
+
if (Option.isSome(harness) && Option.isNone(harnessHint)) {
|
|
287
|
+
yield* emitError('unsupported_harness', UNSUPPORTED_HARNESS_MESSAGE, true, 2)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
if (detectOnly) {
|
|
291
|
+
yield* runAgentDetect(harnessHint)
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (!Option.contains(scope, 'project') && !Option.contains(scope, 'global')) {
|
|
295
|
+
yield* emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
const selected = Option.map(harnessSelection, parseHarnessSelection)
|
|
299
|
+
if (Option.isSome(selected) && selected.value === undefined) {
|
|
300
|
+
yield* emitError('invalid_harness_selection', INVALID_HARNESS_SELECTION_MESSAGE, true, 2)
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
const detector = yield* HarnessDetector
|
|
304
|
+
// An explicit selection is the user's answer to "which harnesses?" — it is
|
|
305
|
+
// authoritative, and detection must not widen it.
|
|
306
|
+
const harnesses =
|
|
307
|
+
Option.isSome(selected) && selected.value !== undefined
|
|
308
|
+
? selected.value
|
|
309
|
+
: yield* detector.detect({ ...(Option.isSome(harnessHint) ? { hint: harnessHint.value } : {}) })
|
|
310
|
+
if (harnesses.length === 0) {
|
|
311
|
+
yield* emitError(
|
|
312
|
+
'no_harness_detected',
|
|
313
|
+
'No supported agent harness was detected. Pass --harness with the agent currently running setup.',
|
|
314
|
+
true,
|
|
315
|
+
2,
|
|
316
|
+
)
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const request = yield* Schema.decodeUnknown(InitRequest)({
|
|
321
|
+
harnesses,
|
|
322
|
+
scope: Option.getOrUndefined(scope),
|
|
323
|
+
...(Option.isSome(setupToken) ? { setupToken: setupToken.value } : {}),
|
|
324
|
+
}).pipe(
|
|
325
|
+
Effect.catchAll(() =>
|
|
326
|
+
emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2).pipe(Effect.as(undefined)),
|
|
327
|
+
),
|
|
328
|
+
)
|
|
329
|
+
if (request === undefined) return
|
|
330
|
+
|
|
331
|
+
yield* runInitService(request, true)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
export class InitTerminalInteraction extends Effect.Service<InitTerminalInteraction>()(
|
|
335
|
+
'@expandai/cli/Commands/InitTerminalInteraction',
|
|
336
|
+
{
|
|
337
|
+
effect: Effect.gen(function* () {
|
|
338
|
+
const terminal = yield* Terminal.Terminal
|
|
339
|
+
const chooseScope = Effect.fnUntraced(function* () {
|
|
340
|
+
return yield* Prompt.select({
|
|
341
|
+
message: 'Where should Expand be available?',
|
|
342
|
+
// Global leads: most people want Expand in every project, and it is
|
|
343
|
+
// the scope the hosted setup guide asks agents to offer first.
|
|
344
|
+
choices: [
|
|
345
|
+
{ title: 'All projects for this user', value: 'global' as const },
|
|
346
|
+
{ title: 'This project', value: 'project' as const },
|
|
347
|
+
],
|
|
348
|
+
}).pipe(Effect.provideService(Terminal.Terminal, terminal))
|
|
349
|
+
})
|
|
350
|
+
/**
|
|
351
|
+
* Setup writes to every harness the user confirms here and to no other.
|
|
352
|
+
* Detected harnesses start selected so the common "configure everything"
|
|
353
|
+
* case is one keystroke, but the list is always shown before anything
|
|
354
|
+
* on disk changes.
|
|
355
|
+
*/
|
|
356
|
+
const chooseHarnesses = Effect.fnUntraced(function* (detected: ReadonlyArray<InitHarness>) {
|
|
357
|
+
return yield* Prompt.multiSelect({
|
|
358
|
+
message: 'Which harnesses should Expand configure?',
|
|
359
|
+
choices: detected.map((harness) => ({ title: harness, value: harness, selected: true })),
|
|
360
|
+
}).pipe(Effect.provideService(Terminal.Terminal, terminal))
|
|
361
|
+
})
|
|
362
|
+
return { chooseScope, chooseHarnesses } as const
|
|
363
|
+
}),
|
|
364
|
+
},
|
|
365
|
+
) {}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Human counterpart to `runAgentDetect`: the same detection, rendered for a
|
|
369
|
+
* terminal instead of as JSON, and writing nothing either way.
|
|
370
|
+
*/
|
|
371
|
+
const runHumanDetect = Effect.fnUntraced(function* (hint: Option.Option<InitHarness>) {
|
|
372
|
+
const detector = yield* HarnessDetector
|
|
373
|
+
const detected = yield* detector.detect({ ...(Option.isSome(hint) ? { hint: hint.value } : {}) })
|
|
374
|
+
const current = yield* detector.detectCurrent()
|
|
375
|
+
if (detected.length === 0) {
|
|
376
|
+
yield* Console.log('No supported agent harness was detected. Nothing was changed.')
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
yield* Console.log(
|
|
380
|
+
[
|
|
381
|
+
'Detected harnesses. Nothing was changed.',
|
|
382
|
+
...detected.map(
|
|
383
|
+
(harness) => ` ${harness}${Option.isSome(current) && current.value === harness ? ' (this one)' : ''}`,
|
|
384
|
+
),
|
|
385
|
+
'',
|
|
386
|
+
'Run `expandai init` to configure them.',
|
|
387
|
+
].join('\n'),
|
|
388
|
+
)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
export const runHumanInit = Effect.fnUntraced(function* (options: InitCommandOptions) {
|
|
392
|
+
if (options.json) {
|
|
393
|
+
yield* runAgentInit(options)
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
const decodedHarness = Option.flatMap(options.harness, (value) => Schema.decodeUnknownOption(InitHarness)(value))
|
|
397
|
+
if (Option.isSome(options.harness) && Option.isNone(decodedHarness)) {
|
|
398
|
+
yield* emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2)
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
// `--detect` documents itself as changing nothing, and the guide tells agents
|
|
402
|
+
// the same. Only `runAgentInit` ever read the flag, and humans do not pass
|
|
403
|
+
// `--json`, so `expandai init --detect` in a terminal performed a real install.
|
|
404
|
+
// Answered before the scope prompt, because asking is itself a side effect —
|
|
405
|
+
// and `--harnesses`/`--scope` are irrelevant to detection.
|
|
406
|
+
if (options.detect) {
|
|
407
|
+
yield* runHumanDetect(decodedHarness)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
const interaction = yield* InitTerminalInteraction
|
|
411
|
+
const scopeInput = Option.isSome(options.scope) ? options.scope.value : yield* interaction.chooseScope()
|
|
412
|
+
const decodedScope = Schema.decodeUnknownOption(InitScope)(scopeInput)
|
|
413
|
+
if (Option.isNone(decodedScope)) {
|
|
414
|
+
yield* emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2)
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
const selected = Option.map(options.harnesses, parseHarnessSelection)
|
|
418
|
+
if (Option.isSome(selected) && selected.value === undefined) {
|
|
419
|
+
yield* emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2)
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
// An explicit `--harnesses` is the answer, not a hint, so detection is skipped
|
|
423
|
+
// entirely — it exists only to build the question. Running it first rejected
|
|
424
|
+
// `--harnesses codex` on a machine with no Codex config, which is exactly when
|
|
425
|
+
// someone passes the flag. The machine path already treats it as authoritative.
|
|
426
|
+
const explicit = Option.isSome(selected) ? selected.value : undefined
|
|
427
|
+
const harnesses =
|
|
428
|
+
explicit ??
|
|
429
|
+
(yield* Effect.gen(function* () {
|
|
430
|
+
const detector = yield* HarnessDetector
|
|
431
|
+
const detected = yield* detector.detect({
|
|
432
|
+
...(Option.isSome(decodedHarness) ? { hint: decodedHarness.value } : {}),
|
|
433
|
+
})
|
|
434
|
+
return detected.length === 0 ? undefined : yield* interaction.chooseHarnesses(detected)
|
|
435
|
+
}))
|
|
436
|
+
if (harnesses === undefined) {
|
|
437
|
+
yield* emitError(
|
|
438
|
+
'no_harness_detected',
|
|
439
|
+
'No supported agent harness was detected. Re-run with --harness for the agent you want to configure.',
|
|
440
|
+
true,
|
|
441
|
+
2,
|
|
442
|
+
)
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
if (!Array.isNonEmptyReadonlyArray(harnesses)) {
|
|
446
|
+
yield* Console.log('No harnesses selected. Nothing was changed.')
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
const request = yield* Schema.decodeUnknown(InitRequest)({
|
|
450
|
+
harnesses,
|
|
451
|
+
scope: decodedScope.value,
|
|
452
|
+
...(Option.isSome(options.setupToken) ? { setupToken: options.setupToken.value } : {}),
|
|
453
|
+
}).pipe(
|
|
454
|
+
Effect.catchAll(() =>
|
|
455
|
+
emitError('invalid_arguments', INVALID_ARGUMENTS_MESSAGE, true, 2).pipe(Effect.as(undefined)),
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
if (request === undefined) return
|
|
459
|
+
|
|
460
|
+
if (Option.isNone(options.setupToken)) {
|
|
461
|
+
const authentication = yield* Authentication
|
|
462
|
+
const credentialHealth = yield* Effect.either(authentication.hasLocallyUsableCredentials())
|
|
463
|
+
if (Either.isLeft(credentialHealth)) {
|
|
464
|
+
yield* emitError('credential_health_failed', 'Stored credentials could not be validated safely.', false, 1)
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
if (!credentialHealth.right) {
|
|
468
|
+
const login = yield* Effect.either(authentication.loginWithBrowser())
|
|
469
|
+
if (Either.isLeft(login)) {
|
|
470
|
+
yield* emitError('authorization_failed', 'Browser authorization could not be completed.', true, 1)
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
yield* runInitService(request, options.json)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const initDescription = 'Authorize Expand and configure the complete supported harness profile'
|
|
480
|
+
|
|
481
|
+
export const HumanInitCommand = Command.make('init', config).pipe(
|
|
482
|
+
Command.withDescription(initDescription),
|
|
483
|
+
Command.withHandler(runHumanInit),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
export const AgentInitCommand = Command.make('init', config).pipe(
|
|
487
|
+
Command.withDescription(initDescription),
|
|
488
|
+
Command.withHandler(runAgentInit),
|
|
489
|
+
)
|
package/src/Commands/Search.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Args, Command, Options } from '@effect/cli'
|
|
2
|
+
import { resolvePlaygroundHost } from '@expandai/sdk/Playground'
|
|
2
3
|
import { Console, Effect, Layer, Match, Schema } from 'effect'
|
|
3
4
|
import { ApiClient } from '../ApiClient.js'
|
|
4
5
|
import { ConfigManager } from '../Config.js'
|
|
6
|
+
import { CitedSnapshotSearchResult, enrichSnapshotSearchResult } from '../Mcp/tools/Citations.js'
|
|
5
7
|
import { renderSnippets } from '../Snippets.js'
|
|
6
8
|
|
|
7
9
|
export class SnapshotSnippetsUnavailable extends Schema.TaggedError<SnapshotSnippetsUnavailable>()(
|
|
@@ -72,15 +74,18 @@ export const SearchCommand: Command.Command<'search', never, unknown, SearchComm
|
|
|
72
74
|
search: { query, maxResults, minScore },
|
|
73
75
|
include: { markdown: true, json: true },
|
|
74
76
|
})
|
|
77
|
+
const cited = enrichSnapshotSearchResult(result, host, 'cli-recovery')
|
|
75
78
|
|
|
76
79
|
const output = yield* Match.value(format).pipe(
|
|
77
80
|
Match.when('text', () => {
|
|
78
|
-
const snippets =
|
|
81
|
+
const snippets = cited.search.snippets
|
|
79
82
|
return snippets.length === 0
|
|
80
83
|
? Effect.fail(new SnapshotSnippetsUnavailable({ snapshotId, query }))
|
|
81
|
-
: Effect.succeed(
|
|
84
|
+
: Effect.succeed(
|
|
85
|
+
`[invocation: cli-recovery]\n${renderSnippets(snapshotId, resolvePlaygroundHost(result.playground, host), snippets)}`,
|
|
86
|
+
)
|
|
82
87
|
}),
|
|
83
|
-
Match.when('json', () => Schema.encode(Schema.parseJson(
|
|
88
|
+
Match.when('json', () => Schema.encode(Schema.parseJson(CitedSnapshotSearchResult, { space: 2 }))(cited)),
|
|
84
89
|
Match.exhaustive,
|
|
85
90
|
)
|
|
86
91
|
|