expandai 0.0.4 → 0.0.6
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 +2 -2
- package/build/{esm/Install/Opencode → assets/opencode}/expandai-webfetch.js +22 -4
- package/build/dts/Mcp/RequestHeaders.d.ts +0 -1
- package/build/dts/Mcp/Server.d.ts +1 -2
- package/build/dts/index.d.ts +0 -1
- package/build/esm/index.js +193 -2
- package/build/esm/main.js +691 -200
- package/build/skills/expandai/SKILL.md +100 -8
- package/package.json +20 -12
- package/build/dts/ApiClient.d.ts +0 -165
- package/build/dts/ApiClient.d.ts.map +0 -1
- package/build/dts/Auth/Domain.d.ts +0 -95
- package/build/dts/Auth/Domain.d.ts.map +0 -1
- package/build/dts/Auth/ManualAuthorization.d.ts +0 -107
- package/build/dts/Auth/ManualAuthorization.d.ts.map +0 -1
- package/build/dts/Auth/PendingInit.d.ts +0 -35
- package/build/dts/Auth/PendingInit.d.ts.map +0 -1
- package/build/dts/Auth/Pkce.d.ts +0 -10
- package/build/dts/Auth/Pkce.d.ts.map +0 -1
- package/build/dts/Auth/SetupGrantAuthorization.d.ts +0 -34
- package/build/dts/Auth/SetupGrantAuthorization.d.ts.map +0 -1
- package/build/dts/Authentication.d.ts +0 -24
- package/build/dts/Authentication.d.ts.map +0 -1
- package/build/dts/BrowserOAuth.d.ts +0 -99
- package/build/dts/BrowserOAuth.d.ts.map +0 -1
- package/build/dts/CommandPath.d.ts +0 -2
- package/build/dts/CommandPath.d.ts.map +0 -1
- package/build/dts/Commands/Fetch.d.ts +0 -30
- package/build/dts/Commands/Fetch.d.ts.map +0 -1
- package/build/dts/Commands/Hook.d.ts +0 -17
- package/build/dts/Commands/Hook.d.ts.map +0 -1
- package/build/dts/Commands/Init.d.ts +0 -55
- package/build/dts/Commands/Init.d.ts.map +0 -1
- package/build/dts/Commands/Mcp.d.ts +0 -11
- package/build/dts/Commands/Mcp.d.ts.map +0 -1
- package/build/dts/Commands/McpRun.d.ts +0 -5
- package/build/dts/Commands/McpRun.d.ts.map +0 -1
- package/build/dts/Commands/Search.d.ts +0 -21
- package/build/dts/Commands/Search.d.ts.map +0 -1
- package/build/dts/Commands/Skill.d.ts +0 -12
- package/build/dts/Commands/Skill.d.ts.map +0 -1
- package/build/dts/Commands/Status.d.ts +0 -27
- package/build/dts/Commands/Status.d.ts.map +0 -1
- package/build/dts/Commands/Telemetry.d.ts +0 -7
- package/build/dts/Commands/Telemetry.d.ts.map +0 -1
- package/build/dts/Commands/Uninstall.d.ts +0 -9
- package/build/dts/Commands/Uninstall.d.ts.map +0 -1
- package/build/dts/Config.d.ts +0 -196
- package/build/dts/Config.d.ts.map +0 -1
- package/build/dts/Hook/Claude.d.ts +0 -13
- package/build/dts/Hook/Claude.d.ts.map +0 -1
- package/build/dts/Hook/Errors.d.ts +0 -20
- package/build/dts/Hook/Errors.d.ts.map +0 -1
- package/build/dts/Hook/Opencode.d.ts +0 -13
- package/build/dts/Hook/Opencode.d.ts.map +0 -1
- package/build/dts/Hook/SnapshotNote.d.ts +0 -12
- package/build/dts/Hook/SnapshotNote.d.ts.map +0 -1
- package/build/dts/Init/ClaudeAdapter.d.ts +0 -61
- package/build/dts/Init/ClaudeAdapter.d.ts.map +0 -1
- package/build/dts/Init/ClaudeProfile.d.ts +0 -77
- package/build/dts/Init/ClaudeProfile.d.ts.map +0 -1
- package/build/dts/Init/Domain.d.ts +0 -139
- package/build/dts/Init/Domain.d.ts.map +0 -1
- package/build/dts/Init/HarnessAdapter.d.ts +0 -33
- package/build/dts/Init/HarnessAdapter.d.ts.map +0 -1
- package/build/dts/Init/HarnessDetector.d.ts +0 -19
- package/build/dts/Init/HarnessDetector.d.ts.map +0 -1
- package/build/dts/Init/HarnessRegistry.d.ts +0 -50
- package/build/dts/Init/HarnessRegistry.d.ts.map +0 -1
- package/build/dts/Init/MutationTransaction.d.ts +0 -96
- package/build/dts/Init/MutationTransaction.d.ts.map +0 -1
- package/build/dts/Init/Service.d.ts +0 -72
- package/build/dts/Init/Service.d.ts.map +0 -1
- package/build/dts/Init/StandardAdapters.d.ts +0 -42
- package/build/dts/Init/StandardAdapters.d.ts.map +0 -1
- package/build/dts/Init/Status.d.ts +0 -149
- package/build/dts/Init/Status.d.ts.map +0 -1
- package/build/dts/Install/Claude/Hook.d.ts +0 -88
- package/build/dts/Install/Claude/Hook.d.ts.map +0 -1
- package/build/dts/Install/Claude/Mcp.d.ts +0 -48
- package/build/dts/Install/Claude/Mcp.d.ts.map +0 -1
- package/build/dts/Install/Claude/Skill.d.ts +0 -23
- package/build/dts/Install/Claude/Skill.d.ts.map +0 -1
- package/build/dts/Install/Codex/Mcp.d.ts +0 -45
- package/build/dts/Install/Codex/Mcp.d.ts.map +0 -1
- package/build/dts/Install/Codex/Skill.d.ts +0 -22
- package/build/dts/Install/Codex/Skill.d.ts.map +0 -1
- package/build/dts/Install/Cursor/Mcp.d.ts +0 -45
- package/build/dts/Install/Cursor/Mcp.d.ts.map +0 -1
- package/build/dts/Install/Cursor/Skill.d.ts +0 -29
- package/build/dts/Install/Cursor/Skill.d.ts.map +0 -1
- package/build/dts/Install/Domain.d.ts +0 -103
- package/build/dts/Install/Domain.d.ts.map +0 -1
- package/build/dts/Install/Hook.d.ts +0 -30
- package/build/dts/Install/Hook.d.ts.map +0 -1
- package/build/dts/Install/Mcp.d.ts +0 -28
- package/build/dts/Install/Mcp.d.ts.map +0 -1
- package/build/dts/Install/Opencode/Hook.d.ts +0 -24
- package/build/dts/Install/Opencode/Hook.d.ts.map +0 -1
- package/build/dts/Install/Opencode/Mcp.d.ts +0 -47
- package/build/dts/Install/Opencode/Mcp.d.ts.map +0 -1
- package/build/dts/Install/Opencode/Skill.d.ts +0 -26
- package/build/dts/Install/Opencode/Skill.d.ts.map +0 -1
- package/build/dts/Install/Skill.d.ts +0 -32
- package/build/dts/Install/Skill.d.ts.map +0 -1
- package/build/dts/Install/Uninstall.d.ts +0 -32
- package/build/dts/Install/Uninstall.d.ts.map +0 -1
- package/build/dts/MarkdownAnsi.d.ts +0 -29
- package/build/dts/MarkdownAnsi.d.ts.map +0 -1
- package/build/dts/Mcp/ExpandDocs.d.ts +0 -19
- package/build/dts/Mcp/ExpandDocs.d.ts.map +0 -1
- package/build/dts/Mcp/RequestHeaders.d.ts.map +0 -1
- package/build/dts/Mcp/Server.d.ts.map +0 -1
- package/build/dts/Mcp/Toolkit.d.ts +0 -3
- package/build/dts/Mcp/Toolkit.d.ts.map +0 -1
- package/build/dts/Mcp/tools/Citations.d.ts +0 -266
- package/build/dts/Mcp/tools/Citations.d.ts.map +0 -1
- package/build/dts/Mcp/tools/Fetch.d.ts +0 -168
- package/build/dts/Mcp/tools/Fetch.d.ts.map +0 -1
- package/build/dts/SecureFile.d.ts +0 -23
- package/build/dts/SecureFile.d.ts.map +0 -1
- package/build/dts/Snippets.d.ts +0 -18
- package/build/dts/Snippets.d.ts.map +0 -1
- package/build/dts/Telemetry.d.ts +0 -30
- package/build/dts/Telemetry.d.ts.map +0 -1
- package/build/dts/Version.d.ts +0 -2
- package/build/dts/Version.d.ts.map +0 -1
- package/build/dts/Xdg.d.ts +0 -11
- package/build/dts/Xdg.d.ts.map +0 -1
- package/build/dts/index.d.ts.map +0 -1
- package/build/dts/main.d.ts +0 -3
- package/build/dts/main.d.ts.map +0 -1
- package/build/esm/ApiClient.js +0 -305
- package/build/esm/ApiClient.js.map +0 -1
- package/build/esm/Auth/Domain.js +0 -56
- package/build/esm/Auth/Domain.js.map +0 -1
- package/build/esm/Auth/ManualAuthorization.js +0 -253
- package/build/esm/Auth/ManualAuthorization.js.map +0 -1
- package/build/esm/Auth/PendingInit.js +0 -267
- package/build/esm/Auth/PendingInit.js.map +0 -1
- package/build/esm/Auth/Pkce.js +0 -23
- package/build/esm/Auth/Pkce.js.map +0 -1
- package/build/esm/Auth/SetupGrantAuthorization.js +0 -91
- package/build/esm/Auth/SetupGrantAuthorization.js.map +0 -1
- package/build/esm/Authentication.js +0 -143
- package/build/esm/Authentication.js.map +0 -1
- package/build/esm/BrowserOAuth.js +0 -251
- package/build/esm/BrowserOAuth.js.map +0 -1
- package/build/esm/CommandPath.js +0 -31
- package/build/esm/CommandPath.js.map +0 -1
- package/build/esm/Commands/Fetch.js +0 -119
- package/build/esm/Commands/Fetch.js.map +0 -1
- package/build/esm/Commands/Hook.js +0 -20
- package/build/esm/Commands/Hook.js.map +0 -1
- package/build/esm/Commands/Init.js +0 -371
- package/build/esm/Commands/Init.js.map +0 -1
- package/build/esm/Commands/Mcp.js +0 -19
- package/build/esm/Commands/Mcp.js.map +0 -1
- package/build/esm/Commands/McpRun.js +0 -15
- package/build/esm/Commands/McpRun.js.map +0 -1
- package/build/esm/Commands/Search.js +0 -46
- package/build/esm/Commands/Search.js.map +0 -1
- package/build/esm/Commands/Skill.js +0 -17
- package/build/esm/Commands/Skill.js.map +0 -1
- package/build/esm/Commands/Status.js +0 -186
- package/build/esm/Commands/Status.js.map +0 -1
- package/build/esm/Commands/Telemetry.js +0 -23
- package/build/esm/Commands/Telemetry.js.map +0 -1
- package/build/esm/Commands/Uninstall.js +0 -28
- package/build/esm/Commands/Uninstall.js.map +0 -1
- package/build/esm/Config.js +0 -619
- package/build/esm/Config.js.map +0 -1
- package/build/esm/Hook/Claude.js +0 -74
- package/build/esm/Hook/Claude.js.map +0 -1
- package/build/esm/Hook/Errors.js +0 -18
- package/build/esm/Hook/Errors.js.map +0 -1
- package/build/esm/Hook/Opencode.js +0 -50
- package/build/esm/Hook/Opencode.js.map +0 -1
- package/build/esm/Hook/SnapshotNote.js +0 -65
- package/build/esm/Hook/SnapshotNote.js.map +0 -1
- package/build/esm/Init/ClaudeAdapter.js +0 -137
- package/build/esm/Init/ClaudeAdapter.js.map +0 -1
- package/build/esm/Init/ClaudeProfile.js +0 -344
- package/build/esm/Init/ClaudeProfile.js.map +0 -1
- package/build/esm/Init/Domain.js +0 -118
- package/build/esm/Init/Domain.js.map +0 -1
- package/build/esm/Init/HarnessAdapter.js +0 -21
- package/build/esm/Init/HarnessAdapter.js.map +0 -1
- package/build/esm/Init/HarnessDetector.js +0 -65
- package/build/esm/Init/HarnessDetector.js.map +0 -1
- package/build/esm/Init/HarnessRegistry.js +0 -58
- package/build/esm/Init/HarnessRegistry.js.map +0 -1
- package/build/esm/Init/MutationTransaction.js +0 -494
- package/build/esm/Init/MutationTransaction.js.map +0 -1
- package/build/esm/Init/Service.js +0 -319
- package/build/esm/Init/Service.js.map +0 -1
- package/build/esm/Init/StandardAdapters.js +0 -116
- package/build/esm/Init/StandardAdapters.js.map +0 -1
- package/build/esm/Init/Status.js +0 -215
- package/build/esm/Init/Status.js.map +0 -1
- package/build/esm/Install/Claude/Hook.js +0 -77
- package/build/esm/Install/Claude/Hook.js.map +0 -1
- package/build/esm/Install/Claude/Mcp.js +0 -101
- package/build/esm/Install/Claude/Mcp.js.map +0 -1
- package/build/esm/Install/Claude/Skill.js +0 -56
- package/build/esm/Install/Claude/Skill.js.map +0 -1
- package/build/esm/Install/Codex/Mcp.js +0 -97
- package/build/esm/Install/Codex/Mcp.js.map +0 -1
- package/build/esm/Install/Codex/Skill.js +0 -33
- package/build/esm/Install/Codex/Skill.js.map +0 -1
- package/build/esm/Install/Cursor/Mcp.js +0 -74
- package/build/esm/Install/Cursor/Mcp.js.map +0 -1
- package/build/esm/Install/Cursor/Skill.js +0 -40
- package/build/esm/Install/Cursor/Skill.js.map +0 -1
- package/build/esm/Install/Domain.js +0 -162
- package/build/esm/Install/Domain.js.map +0 -1
- package/build/esm/Install/Hook.js +0 -38
- package/build/esm/Install/Hook.js.map +0 -1
- package/build/esm/Install/Mcp.js +0 -40
- package/build/esm/Install/Mcp.js.map +0 -1
- package/build/esm/Install/Opencode/Hook.js +0 -44
- package/build/esm/Install/Opencode/Hook.js.map +0 -1
- package/build/esm/Install/Opencode/Mcp.js +0 -76
- package/build/esm/Install/Opencode/Mcp.js.map +0 -1
- package/build/esm/Install/Opencode/Skill.js +0 -37
- package/build/esm/Install/Opencode/Skill.js.map +0 -1
- package/build/esm/Install/Skill.js +0 -41
- package/build/esm/Install/Skill.js.map +0 -1
- package/build/esm/Install/Uninstall.js +0 -49
- package/build/esm/Install/Uninstall.js.map +0 -1
- package/build/esm/MarkdownAnsi.js +0 -142
- package/build/esm/MarkdownAnsi.js.map +0 -1
- package/build/esm/Mcp/ExpandDocs.js +0 -56
- package/build/esm/Mcp/ExpandDocs.js.map +0 -1
- package/build/esm/Mcp/RequestHeaders.js +0 -13
- package/build/esm/Mcp/RequestHeaders.js.map +0 -1
- package/build/esm/Mcp/Server.js +0 -12
- package/build/esm/Mcp/Server.js.map +0 -1
- package/build/esm/Mcp/Toolkit.js +0 -16
- package/build/esm/Mcp/Toolkit.js.map +0 -1
- package/build/esm/Mcp/tools/Citations.js +0 -193
- package/build/esm/Mcp/tools/Citations.js.map +0 -1
- package/build/esm/Mcp/tools/Fetch.js +0 -165
- package/build/esm/Mcp/tools/Fetch.js.map +0 -1
- package/build/esm/SecureFile.js +0 -36
- package/build/esm/SecureFile.js.map +0 -1
- package/build/esm/Snippets.js +0 -29
- package/build/esm/Snippets.js.map +0 -1
- package/build/esm/Telemetry.js +0 -157
- package/build/esm/Telemetry.js.map +0 -1
- package/build/esm/Version.js +0 -4
- package/build/esm/Version.js.map +0 -1
- package/build/esm/Xdg.js +0 -15
- package/build/esm/Xdg.js.map +0 -1
- package/build/esm/index.js.map +0 -1
- package/build/esm/main.js.map +0 -1
- package/src/ApiClient.ts +0 -450
- package/src/Auth/Domain.ts +0 -63
- package/src/Auth/ManualAuthorization.ts +0 -296
- package/src/Auth/PendingInit.ts +0 -338
- package/src/Auth/Pkce.ts +0 -26
- package/src/Auth/SetupGrantAuthorization.ts +0 -116
- package/src/Authentication.ts +0 -202
- package/src/BrowserOAuth.ts +0 -359
- package/src/CommandPath.ts +0 -30
- package/src/Commands/Fetch.ts +0 -188
- package/src/Commands/Hook.ts +0 -48
- package/src/Commands/Init.ts +0 -489
- package/src/Commands/Mcp.ts +0 -30
- package/src/Commands/McpRun.ts +0 -25
- package/src/Commands/Search.ts +0 -97
- package/src/Commands/Skill.ts +0 -30
- package/src/Commands/Status.ts +0 -228
- package/src/Commands/Telemetry.ts +0 -42
- package/src/Commands/Uninstall.ts +0 -38
- package/src/Config.ts +0 -840
- package/src/Hook/Claude.ts +0 -81
- package/src/Hook/Errors.ts +0 -19
- package/src/Hook/Opencode.ts +0 -58
- package/src/Hook/SnapshotNote.ts +0 -66
- package/src/Init/ClaudeAdapter.ts +0 -191
- package/src/Init/ClaudeProfile.ts +0 -453
- package/src/Init/Domain.ts +0 -153
- package/src/Init/HarnessAdapter.ts +0 -35
- package/src/Init/HarnessDetector.ts +0 -77
- package/src/Init/HarnessRegistry.ts +0 -87
- package/src/Init/MutationTransaction.ts +0 -747
- package/src/Init/Service.ts +0 -385
- package/src/Init/StandardAdapters.ts +0 -141
- package/src/Init/Status.ts +0 -253
- package/src/Install/Claude/Hook.ts +0 -123
- package/src/Install/Claude/Mcp.ts +0 -129
- package/src/Install/Claude/Skill.ts +0 -77
- package/src/Install/Codex/Mcp.ts +0 -114
- package/src/Install/Codex/Skill.ts +0 -44
- package/src/Install/Cursor/Mcp.ts +0 -85
- package/src/Install/Cursor/Skill.ts +0 -51
- package/src/Install/Domain.ts +0 -221
- package/src/Install/Hook.ts +0 -50
- package/src/Install/Mcp.ts +0 -71
- package/src/Install/Opencode/Hook.ts +0 -55
- package/src/Install/Opencode/Mcp.ts +0 -87
- package/src/Install/Opencode/Skill.ts +0 -48
- package/src/Install/Opencode/expandai-webfetch.d.ts +0 -23
- package/src/Install/Opencode/expandai-webfetch.js +0 -56
- package/src/Install/Skill.ts +0 -57
- package/src/Install/Uninstall.ts +0 -82
- package/src/MarkdownAnsi.ts +0 -162
- package/src/Mcp/ExpandDocs.ts +0 -62
- package/src/Mcp/RequestHeaders.ts +0 -16
- package/src/Mcp/Server.ts +0 -15
- package/src/Mcp/Toolkit.ts +0 -21
- package/src/Mcp/tools/Citations.ts +0 -219
- package/src/Mcp/tools/Fetch.ts +0 -203
- package/src/SecureFile.ts +0 -70
- package/src/Snippets.ts +0 -39
- package/src/Telemetry.ts +0 -186
- package/src/Version.ts +0 -3
- package/src/Xdg.ts +0 -18
- package/src/index.ts +0 -1
- package/src/main.ts +0 -305
package/src/Hook/Claude.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { NodeStream } from '@effect/platform-node'
|
|
2
|
-
import { Console, Effect, Schema, String } from 'effect'
|
|
3
|
-
import { ApiClient } from '../ApiClient.js'
|
|
4
|
-
import { MarkdownUnavailable } from '../Commands/Fetch.js'
|
|
5
|
-
import { HookProtocolFailed, StdinReadError } from './Errors.js'
|
|
6
|
-
import { withSnapshotNote } from './SnapshotNote.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Runner for `expandai hook claude-code`. Reads a Claude Code PreToolUse
|
|
10
|
-
* payload from stdin, fetches the requested URL via expand.ai, and writes
|
|
11
|
-
* a `hookSpecificOutput` payload that substitutes the markdown for what
|
|
12
|
-
* WebFetch would have produced.
|
|
13
|
-
*
|
|
14
|
-
* Substitution mechanism: `permissionDecision: "deny"` blocks the WebFetch,
|
|
15
|
-
* and the page markdown is delivered via `additionalContext` (the documented
|
|
16
|
-
* channel for injecting page content). `permissionDecisionReason` carries
|
|
17
|
-
* a short label only — the agent SDK has no documented cap on it, but
|
|
18
|
-
* `additionalContext` is the semantically correct field for the body.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const WEBFETCH_TOOL_NAME = 'WebFetch'
|
|
22
|
-
|
|
23
|
-
class WebFetchToolInput extends Schema.Class<WebFetchToolInput>('WebFetchToolInput')({
|
|
24
|
-
url: Schema.String,
|
|
25
|
-
prompt: Schema.optional(Schema.String),
|
|
26
|
-
}) {}
|
|
27
|
-
|
|
28
|
-
class PreToolUseHookPayload extends Schema.Class<PreToolUseHookPayload>('PreToolUseHookPayload')({
|
|
29
|
-
hook_event_name: Schema.Literal('PreToolUse'),
|
|
30
|
-
tool_name: Schema.String,
|
|
31
|
-
tool_input: WebFetchToolInput,
|
|
32
|
-
}) {}
|
|
33
|
-
|
|
34
|
-
const decodeHookPayload = Schema.decode(Schema.parseJson(PreToolUseHookPayload))
|
|
35
|
-
const encodeHookOutput = Schema.encode(Schema.parseJson(Schema.Unknown))
|
|
36
|
-
|
|
37
|
-
const readStdin = NodeStream.toString(() => process.stdin, {
|
|
38
|
-
onFailure: (error) => new StdinReadError({ error }),
|
|
39
|
-
encoding: 'utf8',
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
export class ClaudeHook extends Effect.Service<ClaudeHook>()('@expandai/cli/Hook/Claude', {
|
|
43
|
-
dependencies: [ApiClient.Default],
|
|
44
|
-
effect: Effect.gen(function* () {
|
|
45
|
-
const apiClient = yield* ApiClient
|
|
46
|
-
|
|
47
|
-
const runProtocol = Effect.fn('ClaudeHook.runProtocol')(function* () {
|
|
48
|
-
const raw = yield* readStdin.pipe(Effect.mapError((error) => new HookProtocolFailed({ error })))
|
|
49
|
-
const payload = yield* decodeHookPayload(raw).pipe(Effect.mapError((error) => new HookProtocolFailed({ error })))
|
|
50
|
-
|
|
51
|
-
// Defensive — settings.json matcher already scopes us to WebFetch.
|
|
52
|
-
if (payload.tool_name !== WEBFETCH_TOOL_NAME) return
|
|
53
|
-
|
|
54
|
-
const url = payload.tool_input.url
|
|
55
|
-
yield* Effect.gen(function* () {
|
|
56
|
-
// Object mode (`/v1/fetch/json`) over markdown mode so the response carries
|
|
57
|
-
// `meta.snapshotId` — the handle the agent uses to search this same capture
|
|
58
|
-
// via `fetch_search` instead of fetching the URL again.
|
|
59
|
-
const result = yield* apiClient.fetchFetchResult({
|
|
60
|
-
params: {},
|
|
61
|
-
payload: { url, include: { markdown: true } },
|
|
62
|
-
})
|
|
63
|
-
if (String.isEmpty(result.markdown)) {
|
|
64
|
-
return yield* new MarkdownUnavailable({ url })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const output = {
|
|
68
|
-
hookSpecificOutput: {
|
|
69
|
-
hookEventName: 'PreToolUse',
|
|
70
|
-
permissionDecision: 'deny',
|
|
71
|
-
permissionDecisionReason: `Fetched by expand.ai instead — the captured page follows, with evidence ids for citing it. Do not retry this URL.`,
|
|
72
|
-
additionalContext: withSnapshotNote(result.markdown, result.meta.snapshotId, result.meta.playground),
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
yield* Console.log(yield* encodeHookOutput(output))
|
|
76
|
-
}).pipe(Effect.mapError((error) => new HookProtocolFailed({ error, url })))
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
return { runProtocol } as const
|
|
80
|
-
}),
|
|
81
|
-
}) {}
|
package/src/Hook/Errors.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Schema } from 'effect'
|
|
2
|
-
|
|
3
|
-
export class StdinReadError extends Schema.TaggedError<StdinReadError>()('StdinReadError', {
|
|
4
|
-
error: Schema.Defect,
|
|
5
|
-
}) {
|
|
6
|
-
get message() {
|
|
7
|
-
return 'Failed to read hook payload from stdin.'
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class HookProtocolFailed extends Schema.TaggedError<HookProtocolFailed>()('HookProtocolFailed', {
|
|
12
|
-
error: Schema.Defect,
|
|
13
|
-
url: Schema.optional(Schema.String),
|
|
14
|
-
}) {
|
|
15
|
-
get message() {
|
|
16
|
-
const inner = this.error instanceof Error ? this.error.message : 'Hook protocol failed.'
|
|
17
|
-
return this.url ? `${inner} (url: ${this.url})` : inner
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/Hook/Opencode.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { NodeStream } from '@effect/platform-node'
|
|
2
|
-
import { Console, Effect, Schema, String } from 'effect'
|
|
3
|
-
import { ApiClient } from '../ApiClient.js'
|
|
4
|
-
import { MarkdownUnavailable } from '../Commands/Fetch.js'
|
|
5
|
-
import { HookProtocolFailed, StdinReadError } from './Errors.js'
|
|
6
|
-
import { withSnapshotNote } from './SnapshotNote.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Runner for `expandai hook opencode`. Reads `{ url }` from stdin (sent by
|
|
10
|
-
* the OpenCode plugin), fetches via expand.ai, and writes `{ markdown }`
|
|
11
|
-
* to stdout for the plugin to splice back into webfetch's result.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
class ProtocolInput extends Schema.Class<ProtocolInput>('OpencodeWebfetchInput')({
|
|
15
|
-
url: Schema.String,
|
|
16
|
-
}) {}
|
|
17
|
-
|
|
18
|
-
const decodeProtocolInput = Schema.decode(Schema.parseJson(ProtocolInput))
|
|
19
|
-
const encodeProtocolOutput = Schema.encode(Schema.parseJson(Schema.Unknown))
|
|
20
|
-
|
|
21
|
-
const readStdin = NodeStream.toString(() => process.stdin, {
|
|
22
|
-
onFailure: (error) => new StdinReadError({ error }),
|
|
23
|
-
encoding: 'utf8',
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
export class OpencodeHook extends Effect.Service<OpencodeHook>()('@expandai/cli/Hook/Opencode', {
|
|
27
|
-
dependencies: [ApiClient.Default],
|
|
28
|
-
effect: Effect.gen(function* () {
|
|
29
|
-
const apiClient = yield* ApiClient
|
|
30
|
-
|
|
31
|
-
const runProtocol = Effect.fn('OpencodeHook.runProtocol')(function* () {
|
|
32
|
-
const raw = yield* readStdin.pipe(Effect.mapError((error) => new HookProtocolFailed({ error })))
|
|
33
|
-
const payload = yield* decodeProtocolInput(raw).pipe(
|
|
34
|
-
Effect.mapError((error) => new HookProtocolFailed({ error })),
|
|
35
|
-
)
|
|
36
|
-
const url = payload.url
|
|
37
|
-
|
|
38
|
-
yield* Effect.gen(function* () {
|
|
39
|
-
// Object mode (`/v1/fetch/json`) over markdown mode so the response carries
|
|
40
|
-
// `meta.snapshotId` — the handle the agent uses to search this same capture
|
|
41
|
-
// via `fetch_search` instead of fetching the URL again. The note rides inside
|
|
42
|
-
// `markdown`, so the installed plugin needs no protocol change.
|
|
43
|
-
const result = yield* apiClient.fetchFetchResult({
|
|
44
|
-
params: {},
|
|
45
|
-
payload: { url, include: { markdown: true } },
|
|
46
|
-
})
|
|
47
|
-
if (String.isEmpty(result.markdown)) {
|
|
48
|
-
return yield* new MarkdownUnavailable({ url })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const markdown = withSnapshotNote(result.markdown, result.meta.snapshotId, result.meta.playground)
|
|
52
|
-
yield* Console.log(yield* encodeProtocolOutput({ markdown }))
|
|
53
|
-
}).pipe(Effect.mapError((error) => new HookProtocolFailed({ error, url })))
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return { runProtocol } as const
|
|
57
|
-
}),
|
|
58
|
-
}) {}
|
package/src/Hook/SnapshotNote.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { String } from 'effect'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Prepends a short note to fetched markdown carrying the snapshot handle, the public
|
|
5
|
-
* playground link, and the citation contract. Both the Claude Code and OpenCode hooks
|
|
6
|
-
* inject this so the surfaced guidance stays identical.
|
|
7
|
-
*
|
|
8
|
-
* The citation rules are stated here rather than only in the onboarding guide or
|
|
9
|
-
* `SKILL.md`: the guide is read once during setup, and a skill may not be loaded in the
|
|
10
|
-
* session that happens to fetch. This note is the only instruction guaranteed to be in
|
|
11
|
-
* context at the moment the page arrives, so an answer that cites nothing — or cites the
|
|
12
|
-
* bare capture URL, which is not auditable — traces back to what is written here.
|
|
13
|
-
*/
|
|
14
|
-
const FRONT_MATTER_PLAYGROUND = /^playground:\s*"?([^"\n]+)"?\s*$/mu
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Citation guidance for `expandai fetch` text output consumed by an agent. The plain
|
|
18
|
-
* command is a third fetch path alongside the hook and the MCP tools — an agent that
|
|
19
|
-
* shells out to it used to receive evidence ids with nothing telling it to cite them,
|
|
20
|
-
* and answered uncited. Humans at a TTY do not need this, so callers gate on that.
|
|
21
|
-
*
|
|
22
|
-
* Returns the markdown unchanged when no playground URL is present, rather than
|
|
23
|
-
* emitting an instruction the reader cannot follow.
|
|
24
|
-
*/
|
|
25
|
-
export function withCitationFooter(markdown: string): string {
|
|
26
|
-
const playgroundUrl = FRONT_MATTER_PLAYGROUND.exec(markdown)?.[1]?.trim()
|
|
27
|
-
if (playgroundUrl === undefined || playgroundUrl.length === 0) return markdown
|
|
28
|
-
const footer = String.stripMargin(`
|
|
29
|
-
|---
|
|
30
|
-
|
|
|
31
|
-
|> Cite what you take from this page. Evidence ids are stamped next to the text they
|
|
32
|
-
|> belong to, written as \`{1199}\`. End your answer with:
|
|
33
|
-
|>
|
|
34
|
-
|> **Sources**
|
|
35
|
-
|> [1] [<page title>](${playgroundUrl}?id=<evidence id>)
|
|
36
|
-
|>
|
|
37
|
-
|> One marker per sentence, numbered in order. Never invent an evidence id, and never
|
|
38
|
-
|> reuse an id from different text. A link without \`?id=\` is not a citation, so if the
|
|
39
|
-
|> sentence you want carries no id, quote one that does or say the capture has none —
|
|
40
|
-
|> never fall back to linking the original page.`)
|
|
41
|
-
return `${markdown.trimEnd()}\n\n${footer}\n`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function withSnapshotNote(markdown: string, snapshotId: string, playgroundUrl: string): string {
|
|
45
|
-
const note = String.stripMargin(`
|
|
46
|
-
|> Fetched via expand.ai (snapshot \`${snapshotId}\`). Capture: ${playgroundUrl}
|
|
47
|
-
|>
|
|
48
|
-
|> Cite every claim you take from this page. The body below stamps an evidence id next
|
|
49
|
-
|> to the text it belongs to, written as \`{1199}\`. For each sentence you use, take the
|
|
50
|
-
|> id nearest that text and end your answer with:
|
|
51
|
-
|>
|
|
52
|
-
|> **Sources**
|
|
53
|
-
|> [1] [<page title>](${playgroundUrl}?id=<evidence id>)
|
|
54
|
-
|>
|
|
55
|
-
|> Number the markers \`[1]\`, \`[2]\`, … in the order they appear, one per sentence.
|
|
56
|
-
|> Never invent an evidence id, and never reuse an id from different text.
|
|
57
|
-
|> A bare capture link without \`?id=\` is not a citation — the reader cannot
|
|
58
|
-
|> check the sentence against the page. If the sentence you want carries no id, quote
|
|
59
|
-
|> one that does, or say the capture has none. Never fall back to the original page URL.
|
|
60
|
-
|>
|
|
61
|
-
|> To extract specific passages, search the snapshot by handle — call the \`fetch_search\`
|
|
62
|
-
|> tool with \`snapshotId: "${snapshotId}"\`, or run \`expandai search ${snapshotId} "<query>"\`.
|
|
63
|
-
|> Each snippet returns a \`citationUrl\` that already carries the right \`?id=\`.
|
|
64
|
-
|> Do not fetch this URL again.`)
|
|
65
|
-
return `${note}\n\n---\n\n${markdown}`
|
|
66
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { Command, CommandExecutor } from '@effect/platform'
|
|
2
|
-
import { NodeCommandExecutor } from '@effect/platform-node'
|
|
3
|
-
import { Config, Effect, Layer, Schema, Stream } from 'effect'
|
|
4
|
-
import { CLI_VERSION } from '../Version.js'
|
|
5
|
-
import { CLAUDE_PROFILE_VERSION, ClaudeProfile, type ClaudeProfileInspection } from './ClaudeProfile.js'
|
|
6
|
-
import type { InitScope } from './Domain.js'
|
|
7
|
-
import { HarnessConfigurationError, type HarnessInspection, HarnessInspectionError } from './HarnessAdapter.js'
|
|
8
|
-
import { MutationTransaction } from './MutationTransaction.js'
|
|
9
|
-
|
|
10
|
-
export class IncompatibleClaudeProfileVersionError extends Schema.TaggedError<IncompatibleClaudeProfileVersionError>()(
|
|
11
|
-
'IncompatibleClaudeProfileVersionError',
|
|
12
|
-
{
|
|
13
|
-
executingVersion: Schema.String,
|
|
14
|
-
requiredVersion: Schema.String,
|
|
15
|
-
},
|
|
16
|
-
) {
|
|
17
|
-
get message() {
|
|
18
|
-
return `Executing CLI ${this.executingVersion} is incompatible with Claude profile ${this.requiredVersion}`
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ClaudeProfilePolicyBlockedError extends Schema.TaggedError<ClaudeProfilePolicyBlockedError>()(
|
|
23
|
-
'ClaudeProfilePolicyBlockedError',
|
|
24
|
-
{
|
|
25
|
-
policyBlock: Schema.String,
|
|
26
|
-
},
|
|
27
|
-
) {
|
|
28
|
-
get message() {
|
|
29
|
-
return `Claude Code policy blocks profile configuration: ${this.policyBlock}`
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class ClaudeAdapterOperationError extends Schema.TaggedError<ClaudeAdapterOperationError>()(
|
|
34
|
-
'ClaudeAdapterOperationError',
|
|
35
|
-
{
|
|
36
|
-
code: Schema.Literal('inspection_failed', 'configuration_failed'),
|
|
37
|
-
},
|
|
38
|
-
) {
|
|
39
|
-
get message() {
|
|
40
|
-
return this.code === 'inspection_failed'
|
|
41
|
-
? 'Claude Code profile inspection failed'
|
|
42
|
-
: 'Claude Code profile configuration failed'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const PORTABLE_PROBE_TIMEOUT = '5 seconds'
|
|
47
|
-
|
|
48
|
-
interface ClaudeRuntimeProbeContract {
|
|
49
|
-
readonly executingVersion: string
|
|
50
|
-
readonly portableCommandAvailable: Effect.Effect<boolean, unknown>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const makeClaudeRuntimeProbe = Effect.gen(function* () {
|
|
54
|
-
const commandExecutor = yield* CommandExecutor.CommandExecutor
|
|
55
|
-
const searchPath = yield* Config.string('PATH').pipe(Config.withDefault(''))
|
|
56
|
-
const portableCommandAvailable = Effect.scoped(
|
|
57
|
-
Effect.gen(function* () {
|
|
58
|
-
const command = Command.make('expandai', '--version').pipe(
|
|
59
|
-
// Version probing must not persist telemetry state or emit a request in
|
|
60
|
-
// the child. Keep the remaining environment intact while making PATH
|
|
61
|
-
// injectable for fixture verification.
|
|
62
|
-
Command.env({ ...process.env, EXPANDAI_NO_TELEMETRY: '1', PATH: searchPath }),
|
|
63
|
-
Command.stdout('pipe'),
|
|
64
|
-
Command.stderr('pipe'),
|
|
65
|
-
)
|
|
66
|
-
const child = yield* commandExecutor.start(command)
|
|
67
|
-
const [stdout, , exitCode] = yield* Effect.all(
|
|
68
|
-
[child.stdout.pipe(Stream.decodeText(), Stream.mkString), child.stderr.pipe(Stream.runDrain), child.exitCode],
|
|
69
|
-
{ concurrency: 'unbounded' },
|
|
70
|
-
)
|
|
71
|
-
return exitCode === 0 && stdout.trim() === CLI_VERSION
|
|
72
|
-
}),
|
|
73
|
-
).pipe(
|
|
74
|
-
// A shim on PATH that never exits would otherwise hang setup forever. This
|
|
75
|
-
// probe only decides a "restart your harness" hint, so a slow answer is
|
|
76
|
-
// worth no more than a missing one. `Effect.scoped` above reaps the child.
|
|
77
|
-
Effect.timeoutFail({
|
|
78
|
-
duration: PORTABLE_PROBE_TIMEOUT,
|
|
79
|
-
onTimeout: () => new Error('expandai --version timed out'),
|
|
80
|
-
}),
|
|
81
|
-
Effect.orElseSucceed(() => false),
|
|
82
|
-
)
|
|
83
|
-
return { executingVersion: CLI_VERSION, portableCommandAvailable } satisfies ClaudeRuntimeProbeContract
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
/** Runtime facts kept separate from persisted harness commands. */
|
|
87
|
-
export class ClaudeRuntimeProbe extends Effect.Service<ClaudeRuntimeProbe>()('@expandai/cli/Init/ClaudeRuntimeProbe', {
|
|
88
|
-
dependencies: [NodeCommandExecutor.layer],
|
|
89
|
-
effect: makeClaudeRuntimeProbe,
|
|
90
|
-
}) {
|
|
91
|
-
/**
|
|
92
|
-
* Not `DefaultWithoutDependencies`: that layer always reports `CLI_VERSION` and
|
|
93
|
-
* always runs the real command probe. Tests need to pin an arbitrary executing
|
|
94
|
-
* version and a precomputed probe result, which only a fixture layer can supply.
|
|
95
|
-
*/
|
|
96
|
-
static testLayer(options: { readonly executingVersion: string; readonly portableCommandAvailable: boolean }) {
|
|
97
|
-
return Layer.succeed(
|
|
98
|
-
ClaudeRuntimeProbe,
|
|
99
|
-
ClaudeRuntimeProbe.make({
|
|
100
|
-
executingVersion: options.executingVersion,
|
|
101
|
-
portableCommandAvailable: Effect.succeed(options.portableCommandAvailable),
|
|
102
|
-
}),
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function projectInspection(
|
|
108
|
-
inspection: ClaudeProfileInspection,
|
|
109
|
-
portableCommandAvailable: boolean,
|
|
110
|
-
changed: boolean,
|
|
111
|
-
): HarnessInspection {
|
|
112
|
-
const policyBlock = inspection.policyBlocks[0]
|
|
113
|
-
const blocked = policyBlock !== undefined
|
|
114
|
-
const reloadRequired = inspection.configured && !portableCommandAvailable
|
|
115
|
-
return {
|
|
116
|
-
configured: inspection.configured,
|
|
117
|
-
changed,
|
|
118
|
-
components: inspection.components,
|
|
119
|
-
integrationVersion: inspection.profileVersion,
|
|
120
|
-
...(policyBlock === undefined ? {} : { policyBlock }),
|
|
121
|
-
runtimeState: blocked ? 'blocked' : reloadRequired ? 'reload_required' : 'unverified',
|
|
122
|
-
nextAction: blocked
|
|
123
|
-
? 'resolve-claude-policy-block'
|
|
124
|
-
: reloadRequired
|
|
125
|
-
? 'restart-claude-code-current-path-unavailable'
|
|
126
|
-
: inspection.configured
|
|
127
|
-
? 'invoke-native-webfetch'
|
|
128
|
-
: 'configure-claude-code',
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const makeClaudeAdapter = Effect.gen(function* () {
|
|
133
|
-
const profile = yield* ClaudeProfile
|
|
134
|
-
const transaction = yield* MutationTransaction
|
|
135
|
-
const runtime = yield* ClaudeRuntimeProbe
|
|
136
|
-
|
|
137
|
-
const inspect = Effect.fn('ClaudeAdapter.inspect')(function* (scope: InitScope) {
|
|
138
|
-
return yield* Effect.gen(function* () {
|
|
139
|
-
const inspection = yield* profile.inspect(scope)
|
|
140
|
-
const portableCommandAvailable = yield* runtime.portableCommandAvailable
|
|
141
|
-
return projectInspection(inspection, portableCommandAvailable, false)
|
|
142
|
-
}).pipe(
|
|
143
|
-
Effect.catchAllDefect(() => new ClaudeAdapterOperationError({ code: 'inspection_failed' })),
|
|
144
|
-
Effect.mapError(
|
|
145
|
-
() =>
|
|
146
|
-
new HarnessInspectionError({
|
|
147
|
-
harness: 'claude-code',
|
|
148
|
-
scope,
|
|
149
|
-
error: new ClaudeAdapterOperationError({ code: 'inspection_failed' }),
|
|
150
|
-
}),
|
|
151
|
-
),
|
|
152
|
-
)
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
const configure = Effect.fn('ClaudeAdapter.configure')(function* (scope: InitScope) {
|
|
156
|
-
return yield* Effect.gen(function* () {
|
|
157
|
-
if (runtime.executingVersion !== CLAUDE_PROFILE_VERSION) {
|
|
158
|
-
return yield* new IncompatibleClaudeProfileVersionError({
|
|
159
|
-
executingVersion: runtime.executingVersion,
|
|
160
|
-
requiredVersion: CLAUDE_PROFILE_VERSION,
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
const portableCommandAvailable = yield* runtime.portableCommandAvailable
|
|
164
|
-
const plan = yield* profile.plan(scope)
|
|
165
|
-
const policyBlock = plan.inspection.policyBlocks[0]
|
|
166
|
-
if (policyBlock !== undefined) return yield* new ClaudeProfilePolicyBlockedError({ policyBlock })
|
|
167
|
-
yield* transaction.run(plan.operations)
|
|
168
|
-
return projectInspection(plan.resultInspection, portableCommandAvailable, plan.changed)
|
|
169
|
-
}).pipe(
|
|
170
|
-
Effect.catchAllDefect(() => new ClaudeAdapterOperationError({ code: 'configuration_failed' })),
|
|
171
|
-
Effect.mapError(
|
|
172
|
-
(error) =>
|
|
173
|
-
new HarnessConfigurationError({
|
|
174
|
-
harness: 'claude-code',
|
|
175
|
-
scope,
|
|
176
|
-
error:
|
|
177
|
-
error instanceof IncompatibleClaudeProfileVersionError || error instanceof ClaudeProfilePolicyBlockedError
|
|
178
|
-
? error
|
|
179
|
-
: new ClaudeAdapterOperationError({ code: 'configuration_failed' }),
|
|
180
|
-
}),
|
|
181
|
-
),
|
|
182
|
-
)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return { harness: 'claude-code' as const, inspect, configure } as const
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
export class ClaudeAdapter extends Effect.Service<ClaudeAdapter>()('@expandai/cli/Init/ClaudeAdapter', {
|
|
189
|
-
dependencies: [ClaudeProfile.Default, MutationTransaction.Default, ClaudeRuntimeProbe.Default],
|
|
190
|
-
effect: makeClaudeAdapter,
|
|
191
|
-
}) {}
|