expandai 0.0.0 → 0.0.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 +241 -6
- package/build/dts/ApiClient.d.ts +231 -0
- package/build/dts/ApiClient.d.ts.map +1 -0
- package/build/dts/Authentication.d.ts +23 -0
- package/build/dts/Authentication.d.ts.map +1 -0
- package/build/dts/BrowserOAuth.d.ts +99 -0
- package/build/dts/BrowserOAuth.d.ts.map +1 -0
- package/build/dts/Commands/Fetch.d.ts +30 -0
- package/build/dts/Commands/Fetch.d.ts.map +1 -0
- package/build/dts/Commands/Hook.d.ts +17 -0
- package/build/dts/Commands/Hook.d.ts.map +1 -0
- package/build/dts/Commands/Mcp.d.ts +11 -0
- package/build/dts/Commands/Mcp.d.ts.map +1 -0
- package/build/dts/Commands/McpRun.d.ts +5 -0
- package/build/dts/Commands/McpRun.d.ts.map +1 -0
- package/build/dts/Commands/Search.d.ts +21 -0
- package/build/dts/Commands/Search.d.ts.map +1 -0
- package/build/dts/Commands/Skill.d.ts +12 -0
- package/build/dts/Commands/Skill.d.ts.map +1 -0
- package/build/dts/Commands/Uninstall.d.ts +9 -0
- package/build/dts/Commands/Uninstall.d.ts.map +1 -0
- package/build/dts/Config.d.ts +105 -0
- package/build/dts/Config.d.ts.map +1 -0
- package/build/dts/DeviceAuth.d.ts +87 -0
- package/build/dts/DeviceAuth.d.ts.map +1 -0
- package/build/dts/Hook/Claude.d.ts +13 -0
- package/build/dts/Hook/Claude.d.ts.map +1 -0
- package/build/dts/Hook/Errors.d.ts +20 -0
- package/build/dts/Hook/Errors.d.ts.map +1 -0
- package/build/dts/Hook/Opencode.d.ts +13 -0
- package/build/dts/Hook/Opencode.d.ts.map +1 -0
- package/build/dts/Hook/SnapshotNote.d.ts +8 -0
- package/build/dts/Hook/SnapshotNote.d.ts.map +1 -0
- package/build/dts/Install/Claude/Hook.d.ts +60 -0
- package/build/dts/Install/Claude/Hook.d.ts.map +1 -0
- package/build/dts/Install/Claude/Mcp.d.ts +45 -0
- package/build/dts/Install/Claude/Mcp.d.ts.map +1 -0
- package/build/dts/Install/Claude/Skill.d.ts +24 -0
- package/build/dts/Install/Claude/Skill.d.ts.map +1 -0
- package/build/dts/Install/Codex/Mcp.d.ts +39 -0
- package/build/dts/Install/Codex/Mcp.d.ts.map +1 -0
- package/build/dts/Install/Codex/Skill.d.ts +20 -0
- package/build/dts/Install/Codex/Skill.d.ts.map +1 -0
- package/build/dts/Install/Cursor/Mcp.d.ts +45 -0
- package/build/dts/Install/Cursor/Mcp.d.ts.map +1 -0
- package/build/dts/Install/Cursor/Skill.d.ts +27 -0
- package/build/dts/Install/Cursor/Skill.d.ts.map +1 -0
- package/build/dts/Install/Domain.d.ts +73 -0
- package/build/dts/Install/Domain.d.ts.map +1 -0
- package/build/dts/Install/Hook.d.ts +30 -0
- package/build/dts/Install/Hook.d.ts.map +1 -0
- package/build/dts/Install/Mcp.d.ts +28 -0
- package/build/dts/Install/Mcp.d.ts.map +1 -0
- package/build/dts/Install/Opencode/Hook.d.ts +23 -0
- package/build/dts/Install/Opencode/Hook.d.ts.map +1 -0
- package/build/dts/Install/Opencode/Mcp.d.ts +47 -0
- package/build/dts/Install/Opencode/Mcp.d.ts.map +1 -0
- package/build/dts/Install/Opencode/Skill.d.ts +24 -0
- package/build/dts/Install/Opencode/Skill.d.ts.map +1 -0
- package/build/dts/Install/Skill.d.ts +32 -0
- package/build/dts/Install/Skill.d.ts.map +1 -0
- package/build/dts/Install/Uninstall.d.ts +32 -0
- package/build/dts/Install/Uninstall.d.ts.map +1 -0
- package/build/dts/Mcp/ExpandDocs.d.ts +7 -0
- package/build/dts/Mcp/ExpandDocs.d.ts.map +1 -0
- package/build/dts/Mcp/RequestHeaders.d.ts +15 -0
- package/build/dts/Mcp/RequestHeaders.d.ts.map +1 -0
- package/build/dts/Mcp/Server.d.ts +10 -0
- package/build/dts/Mcp/Server.d.ts.map +1 -0
- package/build/dts/Mcp/Toolkit.d.ts +3 -0
- package/build/dts/Mcp/Toolkit.d.ts.map +1 -0
- package/build/dts/Mcp/tools/Citations.d.ts +237 -0
- package/build/dts/Mcp/tools/Citations.d.ts.map +1 -0
- package/build/dts/Mcp/tools/Fetch.d.ts +167 -0
- package/build/dts/Mcp/tools/Fetch.d.ts.map +1 -0
- package/build/dts/Snippets.d.ts +19 -0
- package/build/dts/Snippets.d.ts.map +1 -0
- package/build/dts/index.d.ts +2 -0
- package/build/dts/index.d.ts.map +1 -0
- package/build/dts/main.d.ts +3 -0
- package/build/dts/main.d.ts.map +1 -0
- package/build/esm/ApiClient.js +182 -0
- package/build/esm/ApiClient.js.map +1 -0
- package/build/esm/Authentication.js +112 -0
- package/build/esm/Authentication.js.map +1 -0
- package/build/esm/BrowserOAuth.js +254 -0
- package/build/esm/BrowserOAuth.js.map +1 -0
- package/build/esm/Commands/Fetch.js +95 -0
- package/build/esm/Commands/Fetch.js.map +1 -0
- package/build/esm/Commands/Hook.js +20 -0
- package/build/esm/Commands/Hook.js.map +1 -0
- package/build/esm/Commands/Mcp.js +19 -0
- package/build/esm/Commands/Mcp.js.map +1 -0
- package/build/esm/Commands/McpRun.js +15 -0
- package/build/esm/Commands/McpRun.js.map +1 -0
- package/build/esm/Commands/Search.js +43 -0
- package/build/esm/Commands/Search.js.map +1 -0
- package/build/esm/Commands/Skill.js +17 -0
- package/build/esm/Commands/Skill.js.map +1 -0
- package/build/esm/Commands/Uninstall.js +28 -0
- package/build/esm/Commands/Uninstall.js.map +1 -0
- package/build/esm/Config.js +145 -0
- package/build/esm/Config.js.map +1 -0
- package/build/esm/DeviceAuth.js +127 -0
- package/build/esm/DeviceAuth.js.map +1 -0
- package/build/esm/Hook/Claude.js +74 -0
- package/build/esm/Hook/Claude.js.map +1 -0
- package/build/esm/Hook/Errors.js +18 -0
- package/build/esm/Hook/Errors.js.map +1 -0
- package/build/esm/Hook/Opencode.js +50 -0
- package/build/esm/Hook/Opencode.js.map +1 -0
- package/build/esm/Hook/SnapshotNote.js +18 -0
- package/build/esm/Hook/SnapshotNote.js.map +1 -0
- package/build/esm/Install/Claude/Hook.js +89 -0
- package/build/esm/Install/Claude/Hook.js.map +1 -0
- package/build/esm/Install/Claude/Mcp.js +71 -0
- package/build/esm/Install/Claude/Mcp.js.map +1 -0
- package/build/esm/Install/Claude/Skill.js +34 -0
- package/build/esm/Install/Claude/Skill.js.map +1 -0
- package/build/esm/Install/Codex/Mcp.js +92 -0
- package/build/esm/Install/Codex/Mcp.js.map +1 -0
- package/build/esm/Install/Codex/Skill.js +30 -0
- package/build/esm/Install/Codex/Skill.js.map +1 -0
- package/build/esm/Install/Cursor/Mcp.js +71 -0
- package/build/esm/Install/Cursor/Mcp.js.map +1 -0
- package/build/esm/Install/Cursor/Skill.js +37 -0
- package/build/esm/Install/Cursor/Skill.js.map +1 -0
- package/build/esm/Install/Domain.js +105 -0
- package/build/esm/Install/Domain.js.map +1 -0
- package/build/esm/Install/Hook.js +38 -0
- package/build/esm/Install/Hook.js.map +1 -0
- package/build/esm/Install/Mcp.js +40 -0
- package/build/esm/Install/Mcp.js.map +1 -0
- package/build/esm/Install/Opencode/Hook.js +44 -0
- package/build/esm/Install/Opencode/Hook.js.map +1 -0
- package/build/esm/Install/Opencode/Mcp.js +73 -0
- package/build/esm/Install/Opencode/Mcp.js.map +1 -0
- package/build/esm/Install/Opencode/Skill.js +34 -0
- package/build/esm/Install/Opencode/Skill.js.map +1 -0
- package/build/esm/Install/Opencode/expandai-webfetch.js +38 -0
- package/build/esm/Install/Skill.js +41 -0
- package/build/esm/Install/Skill.js.map +1 -0
- package/build/esm/Install/Uninstall.js +49 -0
- package/build/esm/Install/Uninstall.js.map +1 -0
- package/build/esm/Mcp/ExpandDocs.js +64 -0
- package/build/esm/Mcp/ExpandDocs.js.map +1 -0
- package/build/esm/Mcp/RequestHeaders.js +13 -0
- package/build/esm/Mcp/RequestHeaders.js.map +1 -0
- package/build/esm/Mcp/Server.js +11 -0
- package/build/esm/Mcp/Server.js.map +1 -0
- package/build/esm/Mcp/Toolkit.js +16 -0
- package/build/esm/Mcp/Toolkit.js.map +1 -0
- package/build/esm/Mcp/tools/Citations.js +92 -0
- package/build/esm/Mcp/tools/Citations.js.map +1 -0
- package/build/esm/Mcp/tools/Fetch.js +141 -0
- package/build/esm/Mcp/tools/Fetch.js.map +1 -0
- package/build/esm/Snippets.js +18 -0
- package/build/esm/Snippets.js.map +1 -0
- package/build/esm/index.js +2 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/main.js +97 -0
- package/build/esm/main.js.map +1 -0
- package/build/skills/expandai/SKILL.md +72 -0
- package/package.json +72 -10
- package/src/ApiClient.ts +257 -0
- package/src/Authentication.ts +164 -0
- package/src/BrowserOAuth.ts +361 -0
- package/src/Commands/Fetch.ts +157 -0
- package/src/Commands/Hook.ts +48 -0
- package/src/Commands/Mcp.ts +30 -0
- package/src/Commands/McpRun.ts +25 -0
- package/src/Commands/Search.ts +92 -0
- package/src/Commands/Skill.ts +30 -0
- package/src/Commands/Uninstall.ts +38 -0
- package/src/Config.ts +195 -0
- package/src/DeviceAuth.ts +193 -0
- package/src/Hook/Claude.ts +81 -0
- package/src/Hook/Errors.ts +19 -0
- package/src/Hook/Opencode.ts +58 -0
- package/src/Hook/SnapshotNote.ts +18 -0
- package/src/Install/Claude/Hook.ts +133 -0
- package/src/Install/Claude/Mcp.ts +82 -0
- package/src/Install/Claude/Skill.ts +44 -0
- package/src/Install/Codex/Mcp.ts +109 -0
- package/src/Install/Codex/Skill.ts +40 -0
- package/src/Install/Cursor/Mcp.ts +82 -0
- package/src/Install/Cursor/Skill.ts +47 -0
- package/src/Install/Domain.ts +148 -0
- package/src/Install/Hook.ts +50 -0
- package/src/Install/Mcp.ts +71 -0
- package/src/Install/Opencode/Hook.ts +55 -0
- package/src/Install/Opencode/Mcp.ts +84 -0
- package/src/Install/Opencode/Skill.ts +44 -0
- package/src/Install/Opencode/expandai-webfetch.d.ts +23 -0
- package/src/Install/Opencode/expandai-webfetch.js +38 -0
- package/src/Install/Skill.ts +57 -0
- package/src/Install/Uninstall.ts +82 -0
- package/src/Mcp/ExpandDocs.ts +71 -0
- package/src/Mcp/RequestHeaders.ts +16 -0
- package/src/Mcp/Server.ts +14 -0
- package/src/Mcp/Toolkit.ts +21 -0
- package/src/Mcp/tools/Citations.ts +110 -0
- package/src/Mcp/tools/Fetch.ts +177 -0
- package/src/Snippets.ts +32 -0
- package/src/index.ts +1 -0
- package/src/main.ts +146 -0
- package/bun.lockb +0 -0
- package/index.ts +0 -1
- package/tsconfig.json +0 -27
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Search.js","sourceRoot":"","sources":["../../../src/Commands/Search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Args, Command } from '@effect/cli';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { Skill } from '../Install/Skill.js';
|
|
4
|
+
const hostArg = Args.choice([
|
|
5
|
+
['claude-code', 'claude-code'],
|
|
6
|
+
['cursor', 'cursor'],
|
|
7
|
+
['opencode', 'opencode'],
|
|
8
|
+
['codex', 'codex'],
|
|
9
|
+
]);
|
|
10
|
+
const scopeArg = Args.choice([
|
|
11
|
+
['global', 'global'],
|
|
12
|
+
['project', 'project'],
|
|
13
|
+
]);
|
|
14
|
+
const InstallCommand = Command.make('install', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Install the expandai skill into the chosen host + scope'), Command.withHandler(({ host, scope }) => Effect.flatMap(Skill, (skill) => skill.install(host, scope))));
|
|
15
|
+
const UninstallCommand = Command.make('uninstall', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Remove the expandai skill from the chosen host + scope'), Command.withHandler(({ host, scope }) => Effect.flatMap(Skill, (skill) => skill.uninstall(host, scope))));
|
|
16
|
+
export const SkillCommand = Command.make('skill').pipe(Command.withDescription('Manage the expandai agent skill'), Command.withSubcommands([InstallCommand, UninstallCommand]));
|
|
17
|
+
//# sourceMappingURL=Skill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skill.js","sourceRoot":"","sources":["../../../src/Commands/Skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,CAAC,aAAa,EAAE,aAAsB,CAAC;IACvC,CAAC,QAAQ,EAAE,QAAiB,CAAC;IAC7B,CAAC,UAAU,EAAE,UAAmB,CAAC;IACjC,CAAC,OAAO,EAAE,OAAgB,CAAC;CAC3B,CAAC,CAAA;AAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC,QAAQ,EAAE,QAAiB,CAAC;IAC7B,CAAC,SAAS,EAAE,SAAkB,CAAC;CAC/B,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CACtF,OAAO,CAAC,eAAe,CAAC,yDAAyD,CAAC,EAClF,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CACtG,CAAA;AAED,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAC1F,OAAO,CAAC,eAAe,CAAC,wDAAwD,CAAC,EACjF,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CACxG,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CACrD,OAAO,CAAC,eAAe,CAAC,iCAAiC,CAAC,EAC1D,OAAO,CAAC,eAAe,CAAC,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAC3D,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Args, Command } from '@effect/cli';
|
|
2
|
+
import { Effect, Option } from 'effect';
|
|
3
|
+
import { Uninstall } from '../Install/Uninstall.js';
|
|
4
|
+
const targetArg = Args.choice([
|
|
5
|
+
['all', 'all'],
|
|
6
|
+
['hook', 'hook'],
|
|
7
|
+
['skill', 'skill'],
|
|
8
|
+
['mcp', 'mcp'],
|
|
9
|
+
]).pipe(Args.optional);
|
|
10
|
+
const hostArg = Args.choice([
|
|
11
|
+
['claude-code', 'claude-code'],
|
|
12
|
+
['cursor', 'cursor'],
|
|
13
|
+
['opencode', 'opencode'],
|
|
14
|
+
['codex', 'codex'],
|
|
15
|
+
]).pipe(Args.optional);
|
|
16
|
+
const scopeArg = Args.choice([
|
|
17
|
+
['global', 'global'],
|
|
18
|
+
['project', 'project'],
|
|
19
|
+
]).pipe(Args.optional);
|
|
20
|
+
export const UninstallCommand = Command.make('uninstall', { target: targetArg, host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Remove expandai hooks, skills, and MCP server entries. Defaults to `all` across every host and scope when target/host/scope are omitted.'), Command.withHandler(Effect.fnUntraced(function* ({ target, host, scope }) {
|
|
21
|
+
const uninstall = yield* Uninstall;
|
|
22
|
+
yield* uninstall.uninstall({
|
|
23
|
+
target: Option.getOrUndefined(target),
|
|
24
|
+
host: Option.getOrUndefined(host),
|
|
25
|
+
scope: Option.getOrUndefined(scope),
|
|
26
|
+
});
|
|
27
|
+
})));
|
|
28
|
+
//# sourceMappingURL=Uninstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Uninstall.js","sourceRoot":"","sources":["../../../src/Commands/Uninstall.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAEnD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7B,CAAC,KAAK,EAAE,KAAc,CAAC;IACvB,CAAC,MAAM,EAAE,MAAe,CAAC;IACzB,CAAC,OAAO,EAAE,OAAgB,CAAC;IAC3B,CAAC,KAAK,EAAE,KAAc,CAAC;CACvB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAEtB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,CAAC,aAAa,EAAE,aAAsB,CAAC;IACvC,CAAC,QAAQ,EAAE,QAAiB,CAAC;IAC7B,CAAC,UAAU,EAAE,UAAmB,CAAC;IACjC,CAAC,OAAO,EAAE,OAAgB,CAAC;CAC3B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAEtB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC,QAAQ,EAAE,QAAiB,CAAC;IAC7B,CAAC,SAAS,EAAE,SAAkB,CAAC;CAC/B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAEtB,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CACpH,OAAO,CAAC,eAAe,CACtB,0IAA0I,CAC1I,EACD,OAAO,CAAC,WAAW,CAClB,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE;IACnD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,SAAS,CAAA;IAClC,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC;KACnC,CAAC,CAAA;AACH,CAAC,CAAC,CACF,CACD,CAAA"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI configuration. Persisted at `$XDG_CONFIG_HOME/expandai/config.json`
|
|
3
|
+
* (default `~/.config/expandai/config.json`). The `EXPAND_API_KEY` and
|
|
4
|
+
* `EXPAND_API_URL` environment variables override the file.
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { FileSystem, Path } from '@effect/platform';
|
|
8
|
+
import { NodeFileSystem, NodePath } from '@effect/platform-node';
|
|
9
|
+
import { DEFAULT_PLAYGROUND_HOST } from '@expandai/sdk/Playground';
|
|
10
|
+
import { Config, DateTime, Effect, Option, Schema } from 'effect';
|
|
11
|
+
/** Production API endpoint used when neither `EXPAND_API_URL` nor the config file overrides it. */
|
|
12
|
+
export const DEFAULT_API_URL = 'https://api.expand.ai';
|
|
13
|
+
export const ApiKey = Schema.Redacted(Schema.NonEmptyTrimmedString.pipe(Schema.brand('ApiKey')));
|
|
14
|
+
export const ApiKeyConfig = Schema.TaggedStruct('ApiKey', { apiKey: ApiKey });
|
|
15
|
+
export const OAuthConfig = Schema.TaggedStruct('OAuth', {
|
|
16
|
+
accessToken: Schema.Redacted(Schema.String),
|
|
17
|
+
refreshToken: Schema.optional(Schema.Redacted(Schema.String)),
|
|
18
|
+
expiresAt: Schema.DateTimeUtc,
|
|
19
|
+
});
|
|
20
|
+
export class ExpandCliConfig extends Schema.Class('ExpandCliConfig')({
|
|
21
|
+
apiUrl: Schema.optional(Schema.URL),
|
|
22
|
+
authMethod: Schema.optional(Schema.Union(ApiKeyConfig, OAuthConfig)),
|
|
23
|
+
}) {
|
|
24
|
+
static Json = Schema.parseJson(ExpandCliConfig);
|
|
25
|
+
}
|
|
26
|
+
export class ConfigNotWritable extends Schema.TaggedError()('ConfigNotWritable', {
|
|
27
|
+
path: Schema.String,
|
|
28
|
+
}) {
|
|
29
|
+
get message() {
|
|
30
|
+
return `Cannot write to ${this.path}. Check filesystem permissions on the config directory.`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class ConfigParseError extends Schema.TaggedError()('ConfigParseError', {
|
|
34
|
+
path: Schema.String,
|
|
35
|
+
error: Schema.Defect,
|
|
36
|
+
}) {
|
|
37
|
+
get message() {
|
|
38
|
+
return `Could not parse ${this.path}. Delete the file and run \`expandai login\` to recreate it.`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class NotAuthenticated extends Schema.TaggedError()('NotAuthenticated', {}) {
|
|
42
|
+
get message() {
|
|
43
|
+
return 'Not authenticated. Run `expandai login` to authenticate.';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class OAuthTokenExpired extends Schema.TaggedError()('OAuthTokenExpired', {
|
|
47
|
+
expiredAt: Schema.DateTimeUtc,
|
|
48
|
+
}) {
|
|
49
|
+
get message() {
|
|
50
|
+
return `Your OAuth token expired at ${DateTime.formatIso(this.expiredAt)}. Run \`expandai login\` to re-authenticate.`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const Environment = Effect.gen(function* () {
|
|
54
|
+
const apiKey = yield* Schema.Config('EXPAND_API_KEY', ApiKey).pipe(Config.option);
|
|
55
|
+
const apiUrl = yield* Config.url('EXPAND_API_URL').pipe(Config.option);
|
|
56
|
+
return ExpandCliConfig.make({
|
|
57
|
+
apiUrl: Option.getOrUndefined(apiUrl),
|
|
58
|
+
authMethod: Option.getOrUndefined(Option.map(apiKey, (apiKey) => ({ _tag: 'ApiKey', apiKey }))),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
export class ConfigManager extends Effect.Service()('@expandai/cli/Config/ConfigManager', {
|
|
62
|
+
dependencies: [NodeFileSystem.layer, NodePath.layer],
|
|
63
|
+
effect: Effect.gen(function* () {
|
|
64
|
+
const fs = yield* FileSystem.FileSystem;
|
|
65
|
+
const path = yield* Path.Path;
|
|
66
|
+
const configPath = yield* Config.string('XDG_CONFIG_HOME').pipe(Config.withDefault(path.join(homedir(), '.config')), Config.map((dir) => path.join(dir, 'expandai', 'config.json')));
|
|
67
|
+
const EnvironmentFromConfigFile = Effect.gen(function* () {
|
|
68
|
+
const exists = yield* fs.exists(configPath);
|
|
69
|
+
if (!exists) {
|
|
70
|
+
return ExpandCliConfig.make({});
|
|
71
|
+
}
|
|
72
|
+
const string = yield* fs.readFileString(configPath);
|
|
73
|
+
return yield* Schema.decode(ExpandCliConfig.Json)(string).pipe(Effect.mapError((error) => new ConfigParseError({ path: configPath, error })));
|
|
74
|
+
});
|
|
75
|
+
const loadConfig = Effect.fn('loadConfig')(function* () {
|
|
76
|
+
const env = yield* Environment;
|
|
77
|
+
const file = yield* EnvironmentFromConfigFile;
|
|
78
|
+
const apiUrl = {
|
|
79
|
+
env: Option.fromNullable(env.apiUrl),
|
|
80
|
+
file: Option.fromNullable(file.apiUrl),
|
|
81
|
+
};
|
|
82
|
+
const authMethod = {
|
|
83
|
+
env: Option.fromNullable(env.authMethod),
|
|
84
|
+
file: Option.fromNullable(file.authMethod),
|
|
85
|
+
};
|
|
86
|
+
yield* Effect.logWarning(`EXPAND_API_URL overrides apiUrl from ${configPath}`).pipe(Effect.when(() => Option.zipWith(apiUrl.env, apiUrl.file, (env, file) => env.toString() !== file.toString()).pipe(Option.getOrElse(() => false))));
|
|
87
|
+
yield* Effect.logWarning(`EXPAND_API_KEY overrides the auth method stored in ${configPath}`).pipe(Effect.when(() => Option.isSome(authMethod.env) && Option.isSome(authMethod.file)));
|
|
88
|
+
return yield* Option.match(Option.orElse(authMethod.env, () => authMethod.file), {
|
|
89
|
+
onNone: () => Effect.fail(new NotAuthenticated()),
|
|
90
|
+
onSome: (authMethod) => Effect.succeed({
|
|
91
|
+
apiUrl: Option.firstSomeOf([apiUrl.env, apiUrl.file]).pipe(Option.getOrElse(() => new URL(DEFAULT_API_URL))),
|
|
92
|
+
authMethod,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const saveConfig = Effect.fn('saveConfig')(function* (config) {
|
|
97
|
+
yield* fs
|
|
98
|
+
.makeDirectory(path.dirname(configPath), { recursive: true })
|
|
99
|
+
.pipe(Effect.mapError(() => new ConfigNotWritable({ path: configPath })));
|
|
100
|
+
const encoded = yield* Schema.encode(ExpandCliConfig.Json)(config);
|
|
101
|
+
yield* fs
|
|
102
|
+
.writeFileString(configPath, encoded, { mode: 0o600 })
|
|
103
|
+
.pipe(Effect.mapError(() => new ConfigNotWritable({ path: configPath })));
|
|
104
|
+
// `mode` only applies on creation; chmod handles a pre-existing file.
|
|
105
|
+
// Failures are non-fatal (e.g. chmod is a no-op on Windows, or the file
|
|
106
|
+
// is owned by another user) but surfaced at debug level so they're
|
|
107
|
+
// inspectable when DEBUG=true.
|
|
108
|
+
yield* fs.chmod(configPath, 0o600).pipe(Effect.tapErrorCause((cause) => Effect.logDebug(`chmod 0o600 ${configPath} failed (file mode may not enforce 0600)`).pipe(Effect.annotateLogs({ cause }))), Effect.ignore);
|
|
109
|
+
});
|
|
110
|
+
const updateConfig = Effect.fn('updateConfig')(function* (patch) {
|
|
111
|
+
const existing = yield* EnvironmentFromConfigFile.pipe(Effect.orElseSucceed(() => ExpandCliConfig.make({})));
|
|
112
|
+
yield* saveConfig(ExpandCliConfig.make({ ...existing, ...patch }));
|
|
113
|
+
});
|
|
114
|
+
const clearConfig = Effect.fn('clearConfig')(function* () {
|
|
115
|
+
yield* saveConfig(ExpandCliConfig.make({}));
|
|
116
|
+
});
|
|
117
|
+
const getApiUrl = Effect.fn('getApiUrl')(function* () {
|
|
118
|
+
const env = yield* Environment;
|
|
119
|
+
const file = yield* EnvironmentFromConfigFile;
|
|
120
|
+
const apiUrl = {
|
|
121
|
+
env: Option.fromNullable(env.apiUrl),
|
|
122
|
+
file: Option.fromNullable(file.apiUrl),
|
|
123
|
+
};
|
|
124
|
+
return Option.firstSomeOf([apiUrl.env, apiUrl.file]).pipe(Option.getOrElse(() => new URL(DEFAULT_API_URL)));
|
|
125
|
+
});
|
|
126
|
+
// Public playground host for citation links. Reads `EXPAND_PLAYGROUND_HOST` — the same env
|
|
127
|
+
// var the API / cdp-server bake into `meta.playground` — so local/staging set one host for
|
|
128
|
+
// both server and CLI. The fetch path prefers the server-resolved `meta.playground` origin;
|
|
129
|
+
// only the snapshot-search path (no `meta.playground`) falls back to this.
|
|
130
|
+
const getPlaygroundHost = Effect.fn('getPlaygroundHost')(function* () {
|
|
131
|
+
return yield* Config.string('EXPAND_PLAYGROUND_HOST').pipe(Config.withDefault(DEFAULT_PLAYGROUND_HOST));
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
configPath,
|
|
135
|
+
loadConfig,
|
|
136
|
+
saveConfig,
|
|
137
|
+
updateConfig,
|
|
138
|
+
clearConfig,
|
|
139
|
+
getApiUrl,
|
|
140
|
+
getPlaygroundHost,
|
|
141
|
+
};
|
|
142
|
+
}),
|
|
143
|
+
}) {
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=Config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Config.js","sourceRoot":"","sources":["../../src/Config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from '@effect/platform';
|
|
2
|
+
import { NodeHttpClient } from '@effect/platform-node';
|
|
3
|
+
import { DateTime, Duration, Effect, Inspectable, Match, Option, Redacted, Ref, Schema, String } from 'effect';
|
|
4
|
+
import { ConfigManager, OAuthConfig } from './Config.js';
|
|
5
|
+
const FormattedUserCode = Schema.transform(Schema.String, Schema.String, {
|
|
6
|
+
strict: true,
|
|
7
|
+
decode: (raw) => {
|
|
8
|
+
const upper = String.toUpperCase(raw);
|
|
9
|
+
return upper.length > 4 ? `${String.takeLeft(upper, 4)}-${String.takeRight(upper, upper.length - 4)}` : upper;
|
|
10
|
+
},
|
|
11
|
+
encode: (formatted) => formatted.replace('-', ''),
|
|
12
|
+
});
|
|
13
|
+
const DurationFromSeconds = Schema.transform(Schema.Number, Schema.DurationFromSelf, {
|
|
14
|
+
strict: true,
|
|
15
|
+
decode: Duration.seconds,
|
|
16
|
+
encode: Duration.toSeconds,
|
|
17
|
+
});
|
|
18
|
+
export class DeviceCodeResponse extends Schema.Class('DeviceCodeResponse')({
|
|
19
|
+
device_code: Schema.String,
|
|
20
|
+
user_code: FormattedUserCode,
|
|
21
|
+
verification_uri: Schema.String,
|
|
22
|
+
verification_uri_complete: Schema.String,
|
|
23
|
+
expires_in: DurationFromSeconds,
|
|
24
|
+
interval: DurationFromSeconds,
|
|
25
|
+
}) {
|
|
26
|
+
}
|
|
27
|
+
export class TokenResponse extends Schema.Class('TokenResponse')({
|
|
28
|
+
access_token: Schema.String,
|
|
29
|
+
expires_in: DurationFromSeconds,
|
|
30
|
+
token_type: Schema.String,
|
|
31
|
+
scope: Schema.optional(Schema.String),
|
|
32
|
+
}) {
|
|
33
|
+
}
|
|
34
|
+
export class DeviceAuthRequestFailed extends Schema.TaggedError()('DeviceAuthRequestFailed', {
|
|
35
|
+
endpoint: Schema.String,
|
|
36
|
+
error: Schema.Defect,
|
|
37
|
+
status: Schema.optional(Schema.Number),
|
|
38
|
+
}) {
|
|
39
|
+
get message() {
|
|
40
|
+
return `Device auth request to ${this.endpoint} failed${this.status ? ` (${this.status})` : ''}: ${Inspectable.format(this.error)}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class DeviceAuthorizationDenied extends Schema.TaggedError()('DeviceAuthorizationDenied', {}) {
|
|
44
|
+
get message() {
|
|
45
|
+
return 'Authorization was denied by the user';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class DeviceAuthorizationExpired extends Schema.TaggedError()('DeviceAuthorizationExpired', {}) {
|
|
49
|
+
get message() {
|
|
50
|
+
return 'Device authorization code expired';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const CLIENT_ID = 'expandai-cli';
|
|
54
|
+
export class DeviceAuthService extends Effect.Service()('@expandai/cli/DeviceAuthService', {
|
|
55
|
+
dependencies: [NodeHttpClient.layer, ConfigManager.Default],
|
|
56
|
+
effect: Effect.gen(function* () {
|
|
57
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
58
|
+
const configManager = yield* ConfigManager;
|
|
59
|
+
const apiUrl = yield* configManager.getApiUrl();
|
|
60
|
+
const requestDeviceCode = Effect.fn('DeviceAuthService.requestDeviceCode')(function* () {
|
|
61
|
+
const DEVICE_AUTH_CODE_ENDPOINT = '/api/auth/device/code';
|
|
62
|
+
const request = yield* HttpClientRequest.post(new URL(DEVICE_AUTH_CODE_ENDPOINT, apiUrl)).pipe(HttpClientRequest.bodyJson({ client_id: CLIENT_ID }));
|
|
63
|
+
return yield* httpClient.execute(request).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(DeviceCodeResponse)), Effect.mapError((error) => new DeviceAuthRequestFailed({
|
|
64
|
+
endpoint: DEVICE_AUTH_CODE_ENDPOINT,
|
|
65
|
+
error,
|
|
66
|
+
})));
|
|
67
|
+
});
|
|
68
|
+
const pollForToken = Effect.fn('DeviceAuthService.pollForToken')(function* (deviceCode, interval, expiresIn) {
|
|
69
|
+
const DEVICE_AUTH_TOKEN_ENDPOINT = '/api/auth/device/token';
|
|
70
|
+
// RFC 8628 §3.5: on `slow_down`, the polling interval MUST be increased by 5
|
|
71
|
+
// seconds for this and all subsequent requests.
|
|
72
|
+
const SLOW_DOWN_BUMP = Duration.seconds(5);
|
|
73
|
+
const intervalRef = yield* Ref.make(interval);
|
|
74
|
+
const PollErrorBody = Schema.Struct({
|
|
75
|
+
error: Schema.String,
|
|
76
|
+
error_description: Schema.optional(Schema.String),
|
|
77
|
+
});
|
|
78
|
+
// better-auth's `deviceAuthorization` plugin accepts JSON; RFC 8628 §3.4
|
|
79
|
+
// would normally require `application/x-www-form-urlencoded`. If we ever
|
|
80
|
+
// target a non-better-auth server, switch to `HttpClientRequest.bodyUrlParams`.
|
|
81
|
+
const singlePoll = HttpClientRequest.post(new URL(DEVICE_AUTH_TOKEN_ENDPOINT, apiUrl)).pipe(HttpClientRequest.bodyJson({
|
|
82
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
83
|
+
device_code: deviceCode,
|
|
84
|
+
client_id: CLIENT_ID,
|
|
85
|
+
}), Effect.flatMap(httpClient.execute), Effect.flatMap(HttpClientResponse.matchStatus({
|
|
86
|
+
'200': (response) => HttpClientResponse.schemaBodyJson(TokenResponse)(response).pipe(Effect.map(Option.some)),
|
|
87
|
+
orElse: (response) => HttpClientResponse.schemaBodyJson(PollErrorBody)(response).pipe(Effect.flatMap(({ error, error_description }) => Match.value(error).pipe(Match.when('authorization_pending', () => Ref.get(intervalRef).pipe(Effect.flatMap(Effect.sleep), Effect.as(Option.none()))), Match.when('slow_down', () => Ref.updateAndGet(intervalRef, (current) => Duration.sum(current, SLOW_DOWN_BUMP)).pipe(Effect.flatMap(Effect.sleep), Effect.as(Option.none()))), Match.when('access_denied', () => Effect.fail(new DeviceAuthorizationDenied())), Match.when('expired_token', () => Effect.fail(new DeviceAuthorizationExpired())), Match.orElse(() => Effect.fail(new DeviceAuthRequestFailed({
|
|
88
|
+
endpoint: DEVICE_AUTH_TOKEN_ENDPOINT,
|
|
89
|
+
status: response.status,
|
|
90
|
+
error: error_description ?? error,
|
|
91
|
+
})))))),
|
|
92
|
+
})), Effect.catchTags({
|
|
93
|
+
RequestError: (error) => new DeviceAuthRequestFailed({ endpoint: DEVICE_AUTH_TOKEN_ENDPOINT, error }),
|
|
94
|
+
ResponseError: (error) => new DeviceAuthRequestFailed({ endpoint: DEVICE_AUTH_TOKEN_ENDPOINT, error }),
|
|
95
|
+
HttpBodyError: (error) => new DeviceAuthRequestFailed({ endpoint: DEVICE_AUTH_TOKEN_ENDPOINT, error }),
|
|
96
|
+
ParseError: (error) => new DeviceAuthRequestFailed({ endpoint: DEVICE_AUTH_TOKEN_ENDPOINT, error }),
|
|
97
|
+
}));
|
|
98
|
+
const result = yield* Effect.repeat(singlePoll, { until: Option.isSome }).pipe(Effect.timeoutFail({
|
|
99
|
+
duration: expiresIn,
|
|
100
|
+
onTimeout: () => new DeviceAuthorizationExpired(),
|
|
101
|
+
}));
|
|
102
|
+
return yield* Option.match(result, {
|
|
103
|
+
onNone: () => new DeviceAuthorizationExpired(),
|
|
104
|
+
onSome: Effect.succeed,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// INFO: better-auth's `deviceAuthorization` plugin issues access tokens
|
|
108
|
+
// with no refresh flow — `/api/auth/device/token` only accepts
|
|
109
|
+
// `grant_type=urn:ietf:params:oauth:grant-type:device_code`. Refresh is
|
|
110
|
+
// not part of the auth surface we're using, so we don't persist the
|
|
111
|
+
// refresh_token at all; ApiClient pre-empts the post-expiry 401 with
|
|
112
|
+
// `OAuthTokenExpired` and asks the user to re-login.
|
|
113
|
+
const saveToken = Effect.fn('DeviceAuthService.saveToken')(function* (tokenResponse) {
|
|
114
|
+
const now = yield* DateTime.now;
|
|
115
|
+
const expiresAt = DateTime.addDuration(now, tokenResponse.expires_in);
|
|
116
|
+
yield* configManager.updateConfig({
|
|
117
|
+
authMethod: OAuthConfig.make({
|
|
118
|
+
accessToken: Redacted.make(tokenResponse.access_token),
|
|
119
|
+
expiresAt,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
return { requestDeviceCode, pollForToken, saveToken };
|
|
124
|
+
}),
|
|
125
|
+
}) {
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=DeviceAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceAuth.js","sourceRoot":"","sources":["../../src/DeviceAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
* Runner for `expandai hook claude-code`. Reads a Claude Code PreToolUse
|
|
9
|
+
* payload from stdin, fetches the requested URL via expand.ai, and writes
|
|
10
|
+
* a `hookSpecificOutput` payload that substitutes the markdown for what
|
|
11
|
+
* WebFetch would have produced.
|
|
12
|
+
*
|
|
13
|
+
* Substitution mechanism: `permissionDecision: "deny"` blocks the WebFetch,
|
|
14
|
+
* and the page markdown is delivered via `additionalContext` (the documented
|
|
15
|
+
* channel for injecting page content). `permissionDecisionReason` carries
|
|
16
|
+
* a short label only — the agent SDK has no documented cap on it, but
|
|
17
|
+
* `additionalContext` is the semantically correct field for the body.
|
|
18
|
+
*/
|
|
19
|
+
const WEBFETCH_TOOL_NAME = 'WebFetch';
|
|
20
|
+
class WebFetchToolInput extends Schema.Class('WebFetchToolInput')({
|
|
21
|
+
url: Schema.String,
|
|
22
|
+
prompt: Schema.optional(Schema.String),
|
|
23
|
+
}) {
|
|
24
|
+
}
|
|
25
|
+
class PreToolUseHookPayload extends Schema.Class('PreToolUseHookPayload')({
|
|
26
|
+
hook_event_name: Schema.Literal('PreToolUse'),
|
|
27
|
+
tool_name: Schema.String,
|
|
28
|
+
tool_input: WebFetchToolInput,
|
|
29
|
+
}) {
|
|
30
|
+
}
|
|
31
|
+
const decodeHookPayload = Schema.decode(Schema.parseJson(PreToolUseHookPayload));
|
|
32
|
+
const encodeHookOutput = Schema.encode(Schema.parseJson(Schema.Unknown));
|
|
33
|
+
const readStdin = NodeStream.toString(() => process.stdin, {
|
|
34
|
+
onFailure: (error) => new StdinReadError({ error }),
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
});
|
|
37
|
+
export class ClaudeHook extends Effect.Service()('@expandai/cli/Hook/Claude', {
|
|
38
|
+
dependencies: [ApiClient.Default],
|
|
39
|
+
effect: Effect.gen(function* () {
|
|
40
|
+
const apiClient = yield* ApiClient;
|
|
41
|
+
const runProtocol = Effect.fn('ClaudeHook.runProtocol')(function* () {
|
|
42
|
+
const raw = yield* readStdin.pipe(Effect.mapError((error) => new HookProtocolFailed({ error })));
|
|
43
|
+
const payload = yield* decodeHookPayload(raw).pipe(Effect.mapError((error) => new HookProtocolFailed({ error })));
|
|
44
|
+
// Defensive — settings.json matcher already scopes us to WebFetch.
|
|
45
|
+
if (payload.tool_name !== WEBFETCH_TOOL_NAME)
|
|
46
|
+
return;
|
|
47
|
+
const url = payload.tool_input.url;
|
|
48
|
+
yield* Effect.gen(function* () {
|
|
49
|
+
// Object mode (`/v1/fetch/json`) over markdown mode so the response carries
|
|
50
|
+
// `meta.snapshotId` — the handle the agent uses to search this same capture
|
|
51
|
+
// via `fetch_search` instead of fetching the URL again.
|
|
52
|
+
const result = yield* apiClient.fetchFetchResult({
|
|
53
|
+
params: {},
|
|
54
|
+
payload: { url, include: { markdown: true } },
|
|
55
|
+
});
|
|
56
|
+
if (String.isEmpty(result.markdown)) {
|
|
57
|
+
return yield* new MarkdownUnavailable({ url });
|
|
58
|
+
}
|
|
59
|
+
const output = {
|
|
60
|
+
hookSpecificOutput: {
|
|
61
|
+
hookEventName: 'PreToolUse',
|
|
62
|
+
permissionDecision: 'deny',
|
|
63
|
+
permissionDecisionReason: `Fetched via expand.ai — page markdown injected as additionalContext.`,
|
|
64
|
+
additionalContext: withSnapshotNote(result.markdown, result.meta.snapshotId, result.meta.playground),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
yield* Console.log(yield* encodeHookOutput(output));
|
|
68
|
+
}).pipe(Effect.mapError((error) => new HookProtocolFailed({ error, url })));
|
|
69
|
+
});
|
|
70
|
+
return { runProtocol };
|
|
71
|
+
}),
|
|
72
|
+
}) {
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=Claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Claude.js","sourceRoot":"","sources":["../../../src/Hook/Claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
export class StdinReadError extends Schema.TaggedError()('StdinReadError', {
|
|
3
|
+
error: Schema.Defect,
|
|
4
|
+
}) {
|
|
5
|
+
get message() {
|
|
6
|
+
return 'Failed to read hook payload from stdin.';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class HookProtocolFailed extends Schema.TaggedError()('HookProtocolFailed', {
|
|
10
|
+
error: Schema.Defect,
|
|
11
|
+
url: Schema.optional(Schema.String),
|
|
12
|
+
}) {
|
|
13
|
+
get message() {
|
|
14
|
+
const inner = this.error instanceof Error ? this.error.message : 'Hook protocol failed.';
|
|
15
|
+
return this.url ? `${inner} (url: ${this.url})` : inner;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=Errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Errors.js","sourceRoot":"","sources":["../../../src/Hook/Errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,MAAM,OAAO,cAAe,SAAQ,MAAM,CAAC,WAAW,EAAkB,CAAC,gBAAgB,EAAE;IAC1F,KAAK,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC;IACD,IAAI,OAAO;QACV,OAAO,yCAAyC,CAAA;IACjD,CAAC;CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,MAAM,CAAC,WAAW,EAAsB,CAAC,oBAAoB,EAAE;IACtG,KAAK,EAAE,MAAM,CAAC,MAAM;IACpB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;CACnC,CAAC;IACD,IAAI,OAAO;QACV,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAA;QACxF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,UAAU,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA;IACxD,CAAC;CACD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
* Runner for `expandai hook opencode`. Reads `{ url }` from stdin (sent by
|
|
9
|
+
* the OpenCode plugin), fetches via expand.ai, and writes `{ markdown }`
|
|
10
|
+
* to stdout for the plugin to splice back into webfetch's result.
|
|
11
|
+
*/
|
|
12
|
+
class ProtocolInput extends Schema.Class('OpencodeWebfetchInput')({
|
|
13
|
+
url: Schema.String,
|
|
14
|
+
}) {
|
|
15
|
+
}
|
|
16
|
+
const decodeProtocolInput = Schema.decode(Schema.parseJson(ProtocolInput));
|
|
17
|
+
const encodeProtocolOutput = Schema.encode(Schema.parseJson(Schema.Unknown));
|
|
18
|
+
const readStdin = NodeStream.toString(() => process.stdin, {
|
|
19
|
+
onFailure: (error) => new StdinReadError({ error }),
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
});
|
|
22
|
+
export class OpencodeHook extends Effect.Service()('@expandai/cli/Hook/Opencode', {
|
|
23
|
+
dependencies: [ApiClient.Default],
|
|
24
|
+
effect: Effect.gen(function* () {
|
|
25
|
+
const apiClient = yield* ApiClient;
|
|
26
|
+
const runProtocol = Effect.fn('OpencodeHook.runProtocol')(function* () {
|
|
27
|
+
const raw = yield* readStdin.pipe(Effect.mapError((error) => new HookProtocolFailed({ error })));
|
|
28
|
+
const payload = yield* decodeProtocolInput(raw).pipe(Effect.mapError((error) => new HookProtocolFailed({ error })));
|
|
29
|
+
const url = payload.url;
|
|
30
|
+
yield* Effect.gen(function* () {
|
|
31
|
+
// Object mode (`/v1/fetch/json`) over markdown mode so the response carries
|
|
32
|
+
// `meta.snapshotId` — the handle the agent uses to search this same capture
|
|
33
|
+
// via `fetch_search` instead of fetching the URL again. The note rides inside
|
|
34
|
+
// `markdown`, so the installed plugin needs no protocol change.
|
|
35
|
+
const result = yield* apiClient.fetchFetchResult({
|
|
36
|
+
params: {},
|
|
37
|
+
payload: { url, include: { markdown: true } },
|
|
38
|
+
});
|
|
39
|
+
if (String.isEmpty(result.markdown)) {
|
|
40
|
+
return yield* new MarkdownUnavailable({ url });
|
|
41
|
+
}
|
|
42
|
+
const markdown = withSnapshotNote(result.markdown, result.meta.snapshotId, result.meta.playground);
|
|
43
|
+
yield* Console.log(yield* encodeProtocolOutput({ markdown }));
|
|
44
|
+
}).pipe(Effect.mapError((error) => new HookProtocolFailed({ error, url })));
|
|
45
|
+
});
|
|
46
|
+
return { runProtocol };
|
|
47
|
+
}),
|
|
48
|
+
}) {
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=Opencode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Opencode.js","sourceRoot":"","sources":["../../../src/Hook/Opencode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { String } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* Prepends a short note to fetched markdown carrying the snapshot handle and the public
|
|
4
|
+
* playground link, and telling the agent how to search the already-captured page by handle
|
|
5
|
+
* (rather than fetching the URL again) and cite specific passages back to the user. Both the
|
|
6
|
+
* Claude Code and OpenCode hooks inject this so the surfaced guidance stays identical.
|
|
7
|
+
*/
|
|
8
|
+
export function withSnapshotNote(markdown, snapshotId, playgroundUrl) {
|
|
9
|
+
const note = String.stripMargin(`
|
|
10
|
+
|> Fetched via expand.ai (snapshot \`${snapshotId}\`). View or share this capture at
|
|
11
|
+
|> ${playgroundUrl}
|
|
12
|
+
|> To extract specific passages, search the snapshot by handle — call the \`fetch_search\`
|
|
13
|
+
|> tool with \`snapshotId: "${snapshotId}"\`, or run \`expandai search ${snapshotId} "<query>"\`.
|
|
14
|
+
|> Each snippet returns a \`citationUrl\` you can hand the user to audit the exact source.
|
|
15
|
+
|> Do not fetch this URL again.`);
|
|
16
|
+
return `${note}\n\n---\n\n${markdown}`;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=SnapshotNote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SnapshotNote.js","sourceRoot":"","sources":["../../../src/Hook/SnapshotNote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,UAAkB,EAAE,aAAqB;IAC3F,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC;yCACQ,UAAU;OAC5C,aAAa;;gCAEY,UAAU,iCAAiC,UAAU;;kCAEnD,CAAC,CAAA;IAClC,OAAO,GAAG,IAAI,cAAc,QAAQ,EAAE,CAAA;AACvC,CAAC"}
|