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,112 @@
|
|
|
1
|
+
import { Command, Prompt } from '@effect/cli';
|
|
2
|
+
import { Console, DateTime, Duration, Effect, Either, Match, Schema, String } from 'effect';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { BrowserOAuthService } from './BrowserOAuth.js';
|
|
5
|
+
import { ApiKey, ApiKeyConfig, ConfigManager } from './Config.js';
|
|
6
|
+
import { Uninstall } from './Install/Uninstall.js';
|
|
7
|
+
/** Render a user-facing description of the current auth state. Pure given DateTime.now. */
|
|
8
|
+
export const formatAuthStatus = Effect.fn('Authentication.formatAuthStatus')(function* (authMethod) {
|
|
9
|
+
return yield* Match.value(authMethod).pipe(Match.tag('ApiKey', () => Effect.succeed('Authenticated via API key.')), Match.tag('OAuth', ({ expiresAt }) => Effect.gen(function* () {
|
|
10
|
+
const now = yield* DateTime.now;
|
|
11
|
+
const expired = DateTime.lessThan(expiresAt, now);
|
|
12
|
+
const detail = expired
|
|
13
|
+
? `expired ${Duration.format(DateTime.distanceDuration(expiresAt, now))} ago — run \`expandai login\``
|
|
14
|
+
: `expires in ${Duration.format(DateTime.distanceDuration(now, expiresAt))}`;
|
|
15
|
+
return `Authenticated via OAuth (${detail}).`;
|
|
16
|
+
})), Match.exhaustive);
|
|
17
|
+
});
|
|
18
|
+
export class Authentication extends Effect.Service()('@expandai/cli/Authentication', {
|
|
19
|
+
dependencies: [ConfigManager.Default, BrowserOAuthService.Default, Uninstall.Default],
|
|
20
|
+
effect: Effect.gen(function* () {
|
|
21
|
+
const configManager = yield* ConfigManager;
|
|
22
|
+
const browserOAuth = yield* BrowserOAuthService;
|
|
23
|
+
const uninstall = yield* Uninstall;
|
|
24
|
+
const apiKeyLogin = Effect.fn('Authentication.apiKeyLogin')(function* () {
|
|
25
|
+
const apiKey = yield* Prompt.password({ message: 'Enter your API Key' });
|
|
26
|
+
yield* configManager.updateConfig({
|
|
27
|
+
authMethod: ApiKeyConfig.make({ apiKey: yield* Schema.validate(ApiKey)(apiKey) }),
|
|
28
|
+
});
|
|
29
|
+
yield* Console.log('API key saved successfully!');
|
|
30
|
+
});
|
|
31
|
+
const oauthLogin = Effect.fn('Authentication.oauthLogin')(function* () {
|
|
32
|
+
yield* Console.log(String.stripMargin(`
|
|
33
|
+
|
|
|
34
|
+
|Starting browser authorization...`));
|
|
35
|
+
yield* browserOAuth.login((authorizeUrl) => Effect.gen(function* () {
|
|
36
|
+
yield* Console.log(String.stripMargin(`
|
|
37
|
+
|
|
|
38
|
+
|Open this URL in a browser:
|
|
39
|
+
| ${authorizeUrl.toString()}
|
|
40
|
+
|`));
|
|
41
|
+
const shouldOpen = yield* Prompt.confirm({ message: 'Open browser now?', initial: true });
|
|
42
|
+
if (shouldOpen) {
|
|
43
|
+
yield* Effect.tryPromise(() => open(authorizeUrl.toString())).pipe(Effect.catchAll(() => Console.log('Could not open browser automatically — use the URL above.')));
|
|
44
|
+
}
|
|
45
|
+
yield* Console.log(String.stripMargin(`
|
|
46
|
+
|
|
|
47
|
+
|Waiting for browser authorization...`));
|
|
48
|
+
}));
|
|
49
|
+
yield* Console.log(String.stripMargin(`
|
|
50
|
+
|
|
|
51
|
+
|Successfully authenticated!`));
|
|
52
|
+
});
|
|
53
|
+
const login = Effect.fn('Authentication.login')(function* () {
|
|
54
|
+
const existing = yield* Effect.either(configManager.loadConfig());
|
|
55
|
+
if (Either.isRight(existing)) {
|
|
56
|
+
const status = yield* formatAuthStatus(existing.right.authMethod);
|
|
57
|
+
const proceed = yield* Prompt.confirm({
|
|
58
|
+
message: String.stripMargin(`
|
|
59
|
+
|${status}
|
|
60
|
+
|Do you want to re-authenticate?`),
|
|
61
|
+
});
|
|
62
|
+
if (!proceed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const method = yield* Prompt.select({
|
|
67
|
+
message: 'How would you like to authenticate?',
|
|
68
|
+
choices: [
|
|
69
|
+
{ title: 'Browser Login', value: 'oauth', description: 'Authenticate via browser' },
|
|
70
|
+
{ title: 'API Key', value: 'api-key', description: 'Paste your API key' },
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
yield* Match.value(method).pipe(Match.when('api-key', () => apiKeyLogin()), Match.when('oauth', () => oauthLogin()), Match.exhaustive);
|
|
74
|
+
});
|
|
75
|
+
const whoami = Effect.fn('Authentication.whoami')(function* () {
|
|
76
|
+
const { authMethod } = yield* configManager.loadConfig();
|
|
77
|
+
const text = yield* formatAuthStatus(authMethod);
|
|
78
|
+
yield* Console.log(text);
|
|
79
|
+
});
|
|
80
|
+
const logout = Effect.fn('Authentication.logout')(function* () {
|
|
81
|
+
yield* configManager.clearConfig();
|
|
82
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
83
|
+
yield* Effect.logInfo(String.stripMargin(`
|
|
84
|
+
|Skipping install cleanup — \`expandai logout\` is not interactive in this context.
|
|
85
|
+
|Run \`expandai uninstall\` to remove hooks, skills, and MCP entries.`));
|
|
86
|
+
yield* Console.log('Logged out.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const cleanup = yield* Prompt.multiSelect({
|
|
90
|
+
message: 'Remove installed expandai integrations?',
|
|
91
|
+
choices: [
|
|
92
|
+
{ title: 'Global hooks', value: { target: 'hook', scope: 'global' } },
|
|
93
|
+
{ title: 'Project hooks', value: { target: 'hook', scope: 'project' } },
|
|
94
|
+
{ title: 'Global skills', value: { target: 'skill', scope: 'global' } },
|
|
95
|
+
{ title: 'Project skills', value: { target: 'skill', scope: 'project' } },
|
|
96
|
+
{ title: 'Global MCP entries', value: { target: 'mcp', scope: 'global' } },
|
|
97
|
+
{ title: 'Project MCP entries', value: { target: 'mcp', scope: 'project' } },
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
if (cleanup.length > 0) {
|
|
101
|
+
yield* Effect.forEach(cleanup, (item) => uninstall.uninstall(item), { discard: true });
|
|
102
|
+
}
|
|
103
|
+
yield* Console.log('Logged out.');
|
|
104
|
+
});
|
|
105
|
+
return { login, whoami, logout };
|
|
106
|
+
}),
|
|
107
|
+
}) {
|
|
108
|
+
}
|
|
109
|
+
export const LoginCommand = Command.make('login').pipe(Command.withDescription('Authenticate with ExpandAI'), Command.withHandler(() => Effect.flatMap(Authentication, (auth) => auth.login())));
|
|
110
|
+
export const WhoamiCommand = Command.make('whoami').pipe(Command.withDescription('Show current authentication status'), Command.withHandler(() => Effect.flatMap(Authentication, (auth) => auth.whoami())));
|
|
111
|
+
export const LogoutCommand = Command.make('logout').pipe(Command.withDescription('Clear stored credentials and optionally remove installed integrations'), Command.withHandler(() => Effect.flatMap(Authentication, (auth) => auth.logout())));
|
|
112
|
+
//# sourceMappingURL=Authentication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Authentication.js","sourceRoot":"","sources":["../../src/Authentication.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from '@effect/platform';
|
|
4
|
+
import { NodeHttpClient } from '@effect/platform-node';
|
|
5
|
+
import { DateTime, Duration, Effect, Inspectable, Redacted, Schema } from 'effect';
|
|
6
|
+
import { ConfigManager, OAuthConfig } from './Config.js';
|
|
7
|
+
const CLIENT_ID = 'expandai-cli';
|
|
8
|
+
const OAUTH_AUTHORIZE_ENDPOINT = '/api/auth/oauth2/authorize';
|
|
9
|
+
const OAUTH_TOKEN_ENDPOINT = '/api/auth/oauth2/token';
|
|
10
|
+
const OAUTH_SCOPE = 'openid profile email offline_access';
|
|
11
|
+
const LOOPBACK_HOST = '127.0.0.1';
|
|
12
|
+
const OAUTH_LOGIN_TIMEOUT = Duration.minutes(5);
|
|
13
|
+
const DurationFromSeconds = Schema.transform(Schema.Number, Schema.DurationFromSelf, {
|
|
14
|
+
strict: true,
|
|
15
|
+
decode: Duration.seconds,
|
|
16
|
+
encode: Duration.toSeconds,
|
|
17
|
+
});
|
|
18
|
+
export class TokenResponse extends Schema.Class('BrowserOAuthTokenResponse')({
|
|
19
|
+
access_token: Schema.String,
|
|
20
|
+
refresh_token: Schema.optional(Schema.String),
|
|
21
|
+
expires_in: DurationFromSeconds,
|
|
22
|
+
token_type: Schema.String,
|
|
23
|
+
scope: Schema.optional(Schema.String),
|
|
24
|
+
}) {
|
|
25
|
+
}
|
|
26
|
+
export class BrowserOAuthRequestFailed extends Schema.TaggedError()('BrowserOAuthRequestFailed', {
|
|
27
|
+
endpoint: Schema.String,
|
|
28
|
+
error: Schema.Defect,
|
|
29
|
+
status: Schema.optional(Schema.Number),
|
|
30
|
+
}) {
|
|
31
|
+
get message() {
|
|
32
|
+
return `OAuth request to ${this.endpoint} failed${this.status ? ` (${this.status})` : ''}: ${Inspectable.format(this.error)}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class BrowserOAuthCallbackError extends Schema.TaggedError()('BrowserOAuthCallbackError', {
|
|
36
|
+
error: Schema.String,
|
|
37
|
+
errorDescription: Schema.optional(Schema.String),
|
|
38
|
+
}) {
|
|
39
|
+
get message() {
|
|
40
|
+
return this.errorDescription ? `${this.error}: ${this.errorDescription}` : this.error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class BrowserOAuthMissingCode extends Schema.TaggedError()('BrowserOAuthMissingCode', {}) {
|
|
44
|
+
get message() {
|
|
45
|
+
return 'OAuth callback did not include an authorization code.';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class BrowserOAuthStateMismatch extends Schema.TaggedError()('BrowserOAuthStateMismatch', {}) {
|
|
49
|
+
get message() {
|
|
50
|
+
return 'OAuth callback state did not match the login request.';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class BrowserOAuthLoopbackFailed extends Schema.TaggedError()('BrowserOAuthLoopbackFailed', {
|
|
54
|
+
error: Schema.Defect,
|
|
55
|
+
}) {
|
|
56
|
+
get message() {
|
|
57
|
+
return `Could not start OAuth loopback server: ${Inspectable.format(this.error)}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class BrowserOAuthTimedOut extends Schema.TaggedError()('BrowserOAuthTimedOut', {}) {
|
|
61
|
+
get message() {
|
|
62
|
+
return 'OAuth browser login timed out before the local callback completed.';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function base64Url(bytes) {
|
|
66
|
+
return Buffer.from(bytes).toString('base64').replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
|
|
67
|
+
}
|
|
68
|
+
function createHighEntropySecret() {
|
|
69
|
+
return base64Url(randomBytes(32));
|
|
70
|
+
}
|
|
71
|
+
export const createCodeChallenge = Effect.fn('BrowserOAuth.createCodeChallenge')(function* (codeVerifier) {
|
|
72
|
+
return base64Url(createHash('sha256').update(codeVerifier).digest());
|
|
73
|
+
});
|
|
74
|
+
export const buildAuthorizeUrl = Effect.fn('BrowserOAuth.buildAuthorizeUrl')(function* ({ apiUrl, redirectUri, state, codeChallenge, }) {
|
|
75
|
+
const authorizeUrl = new URL(OAUTH_AUTHORIZE_ENDPOINT, apiUrl);
|
|
76
|
+
authorizeUrl.searchParams.set('response_type', 'code');
|
|
77
|
+
authorizeUrl.searchParams.set('client_id', CLIENT_ID);
|
|
78
|
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri.toString());
|
|
79
|
+
authorizeUrl.searchParams.set('scope', OAUTH_SCOPE);
|
|
80
|
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
81
|
+
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
|
|
82
|
+
authorizeUrl.searchParams.set('state', state);
|
|
83
|
+
return authorizeUrl;
|
|
84
|
+
});
|
|
85
|
+
function respond(response, status, body) {
|
|
86
|
+
response.writeHead(status, { 'content-type': 'text/html; charset=utf-8' });
|
|
87
|
+
response.end(body);
|
|
88
|
+
}
|
|
89
|
+
function validateCallback(request, expectedState) {
|
|
90
|
+
const callbackUrl = new URL(request.url ?? '/', `http://${LOOPBACK_HOST}`);
|
|
91
|
+
const error = callbackUrl.searchParams.get('error');
|
|
92
|
+
if (error !== null) {
|
|
93
|
+
throw new BrowserOAuthCallbackError({
|
|
94
|
+
error,
|
|
95
|
+
errorDescription: callbackUrl.searchParams.get('error_description') ?? undefined,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (callbackUrl.searchParams.get('state') !== expectedState) {
|
|
99
|
+
throw new BrowserOAuthStateMismatch();
|
|
100
|
+
}
|
|
101
|
+
const code = callbackUrl.searchParams.get('code');
|
|
102
|
+
if (code === null || code.length === 0) {
|
|
103
|
+
throw new BrowserOAuthMissingCode();
|
|
104
|
+
}
|
|
105
|
+
return code;
|
|
106
|
+
}
|
|
107
|
+
function toCallbackFailure(error) {
|
|
108
|
+
if (error instanceof BrowserOAuthCallbackError ||
|
|
109
|
+
error instanceof BrowserOAuthMissingCode ||
|
|
110
|
+
error instanceof BrowserOAuthStateMismatch) {
|
|
111
|
+
return error;
|
|
112
|
+
}
|
|
113
|
+
return new BrowserOAuthCallbackError({ error: 'server_error', errorDescription: Inspectable.format(error) });
|
|
114
|
+
}
|
|
115
|
+
function closeServer(server) {
|
|
116
|
+
return Effect.async((resume) => {
|
|
117
|
+
server.close((error) => {
|
|
118
|
+
if (error) {
|
|
119
|
+
resume(Effect.void);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
resume(Effect.void);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const startLoopbackServer = Effect.fn('BrowserOAuth.startLoopbackServer')(function* (expectedState) {
|
|
127
|
+
return yield* Effect.async((resume) => {
|
|
128
|
+
let settled = false;
|
|
129
|
+
let resolveCode = () => undefined;
|
|
130
|
+
let rejectCode = () => undefined;
|
|
131
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
132
|
+
resolveCode = resolve;
|
|
133
|
+
rejectCode = (error) => reject(error);
|
|
134
|
+
});
|
|
135
|
+
const server = createServer((request, response) => {
|
|
136
|
+
const requestUrl = new URL(request.url ?? '/', `http://${LOOPBACK_HOST}`);
|
|
137
|
+
if (requestUrl.pathname !== '/callback') {
|
|
138
|
+
respond(response, 404, 'Not found');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (settled) {
|
|
142
|
+
respond(response, 409, 'OAuth callback already handled.');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
settled = true;
|
|
146
|
+
try {
|
|
147
|
+
const code = validateCallback(request, expectedState);
|
|
148
|
+
respond(response, 200, 'Authentication complete. You can close this tab.');
|
|
149
|
+
resolveCode(code);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
respond(response, 400, 'Authentication failed. Return to the terminal and try again.');
|
|
153
|
+
rejectCode(toCallbackFailure(error));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
server.once('error', (error) => {
|
|
157
|
+
resume(Effect.fail(new BrowserOAuthLoopbackFailed({ error })));
|
|
158
|
+
});
|
|
159
|
+
server.listen(0, LOOPBACK_HOST, () => {
|
|
160
|
+
const address = server.address();
|
|
161
|
+
if (address === null || typeof address === 'string') {
|
|
162
|
+
resume(Effect.fail(new BrowserOAuthLoopbackFailed({ error: 'loopback server did not report a port' })));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
resume(Effect.succeed({
|
|
166
|
+
redirectUri: new URL(`http://${LOOPBACK_HOST}:${address.port}/callback`),
|
|
167
|
+
waitForCode: Effect.tryPromise({
|
|
168
|
+
try: () => codePromise,
|
|
169
|
+
catch: toCallbackFailure,
|
|
170
|
+
}),
|
|
171
|
+
close: closeServer(server),
|
|
172
|
+
}));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
export class BrowserOAuthService extends Effect.Service()('@expandai/cli/BrowserOAuthService', {
|
|
177
|
+
dependencies: [NodeHttpClient.layer, ConfigManager.Default],
|
|
178
|
+
effect: Effect.gen(function* () {
|
|
179
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
180
|
+
const configManager = yield* ConfigManager;
|
|
181
|
+
const exchangeToken = Effect.fn('BrowserOAuthService.exchangeToken')(function* (form) {
|
|
182
|
+
const apiUrl = yield* configManager.getApiUrl();
|
|
183
|
+
const resource = apiUrl.toString();
|
|
184
|
+
const request = HttpClientRequest.post(new URL(OAUTH_TOKEN_ENDPOINT, apiUrl)).pipe(HttpClientRequest.bodyUrlParams([...form, ['resource', resource]]));
|
|
185
|
+
return yield* httpClient.execute(request).pipe(Effect.flatMap(HttpClientResponse.matchStatus({
|
|
186
|
+
'200': (response) => HttpClientResponse.schemaBodyJson(TokenResponse)(response),
|
|
187
|
+
orElse: (response) => response.text.pipe(Effect.flatMap((body) => Effect.fail(new BrowserOAuthRequestFailed({
|
|
188
|
+
endpoint: OAUTH_TOKEN_ENDPOINT,
|
|
189
|
+
status: response.status,
|
|
190
|
+
error: body,
|
|
191
|
+
})))),
|
|
192
|
+
})), Effect.catchTags({
|
|
193
|
+
RequestError: (error) => Effect.fail(new BrowserOAuthRequestFailed({ endpoint: OAUTH_TOKEN_ENDPOINT, error })),
|
|
194
|
+
ResponseError: (error) => Effect.fail(new BrowserOAuthRequestFailed({ endpoint: OAUTH_TOKEN_ENDPOINT, error })),
|
|
195
|
+
ParseError: (error) => Effect.fail(new BrowserOAuthRequestFailed({ endpoint: OAUTH_TOKEN_ENDPOINT, error })),
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
const exchangeCode = Effect.fn('BrowserOAuthService.exchangeCode')(function* ({ code, redirectUri, codeVerifier, }) {
|
|
199
|
+
return yield* exchangeToken([
|
|
200
|
+
['grant_type', 'authorization_code'],
|
|
201
|
+
['client_id', CLIENT_ID],
|
|
202
|
+
['code', code],
|
|
203
|
+
['redirect_uri', redirectUri.toString()],
|
|
204
|
+
['code_verifier', codeVerifier],
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
207
|
+
const refreshToken = Effect.fn('BrowserOAuthService.refreshToken')(function* (refreshToken) {
|
|
208
|
+
return yield* exchangeToken([
|
|
209
|
+
['grant_type', 'refresh_token'],
|
|
210
|
+
['client_id', CLIENT_ID],
|
|
211
|
+
['refresh_token', refreshToken],
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
const saveToken = Effect.fn('BrowserOAuthService.saveToken')(function* (tokenResponse) {
|
|
215
|
+
const now = yield* DateTime.now;
|
|
216
|
+
const expiresAt = DateTime.addDuration(now, tokenResponse.expires_in);
|
|
217
|
+
yield* configManager.updateConfig({
|
|
218
|
+
authMethod: OAuthConfig.make({
|
|
219
|
+
accessToken: Redacted.make(tokenResponse.access_token),
|
|
220
|
+
refreshToken: tokenResponse.refresh_token === undefined ? undefined : Redacted.make(tokenResponse.refresh_token),
|
|
221
|
+
expiresAt,
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
const login = Effect.fn('BrowserOAuthService.login')(function* (openAuthorizeUrl) {
|
|
226
|
+
const codeVerifier = createHighEntropySecret();
|
|
227
|
+
const state = createHighEntropySecret();
|
|
228
|
+
const codeChallenge = yield* createCodeChallenge(codeVerifier);
|
|
229
|
+
const loopback = yield* startLoopbackServer(state);
|
|
230
|
+
const apiUrl = yield* configManager.getApiUrl();
|
|
231
|
+
const authorizeUrl = yield* buildAuthorizeUrl({
|
|
232
|
+
apiUrl,
|
|
233
|
+
redirectUri: loopback.redirectUri,
|
|
234
|
+
state,
|
|
235
|
+
codeChallenge,
|
|
236
|
+
});
|
|
237
|
+
const code = yield* openAuthorizeUrl(authorizeUrl).pipe(Effect.zipRight(loopback.waitForCode), Effect.timeoutFail({
|
|
238
|
+
duration: OAUTH_LOGIN_TIMEOUT,
|
|
239
|
+
onTimeout: () => new BrowserOAuthTimedOut(),
|
|
240
|
+
}), Effect.ensuring(loopback.close));
|
|
241
|
+
const token = yield* exchangeCode({ code, redirectUri: loopback.redirectUri, codeVerifier });
|
|
242
|
+
if (token === undefined) {
|
|
243
|
+
return yield* new BrowserOAuthRequestFailed({
|
|
244
|
+
endpoint: OAUTH_TOKEN_ENDPOINT,
|
|
245
|
+
error: 'OAuth token endpoint returned an empty response body',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
yield* saveToken(token);
|
|
249
|
+
});
|
|
250
|
+
return { exchangeCode, refreshToken, saveToken, login };
|
|
251
|
+
}),
|
|
252
|
+
}) {
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=BrowserOAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserOAuth.js","sourceRoot":"","sources":["../../src/BrowserOAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Args, Command, HelpDoc, Options } from '@effect/cli';
|
|
2
|
+
import { DEFAULT_PLAYGROUND_HOST, resolvePlaygroundHost } from '@expandai/sdk/Playground';
|
|
3
|
+
import { Console, Effect, Match, Option, Schema, String } from 'effect';
|
|
4
|
+
import { ApiClient } from '../ApiClient.js';
|
|
5
|
+
import { renderSnippets } from '../Snippets.js';
|
|
6
|
+
export class MarkdownUnavailable extends Schema.TaggedError()('MarkdownUnavailable', {
|
|
7
|
+
url: Schema.String,
|
|
8
|
+
}) {
|
|
9
|
+
get message() {
|
|
10
|
+
return `No markdown content was returned for ${this.url}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class SnippetsUnavailable extends Schema.TaggedError()('SnippetsUnavailable', {
|
|
14
|
+
url: Schema.String,
|
|
15
|
+
query: Schema.String,
|
|
16
|
+
}) {
|
|
17
|
+
get message() {
|
|
18
|
+
return `No snippets were returned for ${this.url} matching '${this.query}'`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// `Options.text('format').pipe(Options.withDefault('text'))` is optional, so
|
|
22
|
+
// when the user types `expandai fetch --format` (no value) the parser falls
|
|
23
|
+
// through to the default and `--format` is fed to the URL positional. WHATWG's
|
|
24
|
+
// URL parser accepts `https://--format` as a host-only URL, so without this
|
|
25
|
+
// guard the bogus value reaches the API. Reject flag-looking input up front,
|
|
26
|
+
// and require a hostname with a dot (or `localhost`) so `fetch help` etc. fail
|
|
27
|
+
// here with a clear message instead of via the API's schema decoder.
|
|
28
|
+
//
|
|
29
|
+
// `Args.mapEffect` is preferred over `Args.withSchema` because the latter
|
|
30
|
+
// renders failures with `ParseResult.TreeFormatter`, which dumps the full
|
|
31
|
+
// schema-decode trace.
|
|
32
|
+
export const parseUrlArg = Effect.fn('Fetch.parseUrlArg')(function* (raw) {
|
|
33
|
+
if (raw.startsWith('-')) {
|
|
34
|
+
return yield* Effect.fail(HelpDoc.p(`Missing argument <url> (received '${raw}')`));
|
|
35
|
+
}
|
|
36
|
+
const candidate = raw.startsWith('http://') || raw.startsWith('https://') ? raw : `https://${raw}`;
|
|
37
|
+
const parsed = yield* Effect.try({
|
|
38
|
+
try: () => new URL(candidate),
|
|
39
|
+
catch: () => HelpDoc.p(`Invalid URL '${raw}'`),
|
|
40
|
+
});
|
|
41
|
+
if (!parsed.hostname.includes('.') && parsed.hostname !== 'localhost') {
|
|
42
|
+
return yield* Effect.fail(HelpDoc.p(`URL must contain a valid domain (got '${raw}')`));
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
});
|
|
46
|
+
const url = Args.text({ name: 'url' }).pipe(Args.mapEffect(parseUrlArg));
|
|
47
|
+
const format = Options.text('format').pipe(Options.withDefault('text'), Options.withDescription('Output format (text or json)'), Options.withSchema(Schema.Literal('text', 'json')));
|
|
48
|
+
const search = Options.text('search').pipe(Options.optional, Options.withDescription('Semantic search query. Returns relevant snippets instead of markdown.'));
|
|
49
|
+
const maxResults = Options.integer('max-results').pipe(Options.withDefault(5), Options.withDescription('Maximum snippets to return when using --search (default: 5)'), Options.withSchema(Schema.Int.pipe(Schema.greaterThanOrEqualTo(1), Schema.lessThanOrEqualTo(50))));
|
|
50
|
+
const minScore = Options.float('min-score').pipe(Options.withDefault(0.6), Options.withDescription('Minimum snippet relevance score from 0 to 1 when using --search (default: 0.6)'), Options.withSchema(Schema.Number.pipe(Schema.greaterThanOrEqualTo(0), Schema.lessThanOrEqualTo(1))));
|
|
51
|
+
const fetchConfig = {
|
|
52
|
+
url,
|
|
53
|
+
format,
|
|
54
|
+
search,
|
|
55
|
+
maxResults,
|
|
56
|
+
minScore,
|
|
57
|
+
};
|
|
58
|
+
export const FetchCommand = Command.make('fetch', fetchConfig).pipe(Command.withDescription('Fetch content from a URL as markdown or snippets'), Command.withHandler(Effect.fnUntraced(function* ({ format, maxResults, minScore, search, url }) {
|
|
59
|
+
const client = yield* ApiClient;
|
|
60
|
+
const query = Option.getOrUndefined(search);
|
|
61
|
+
const fetchRequest = query === undefined
|
|
62
|
+
? {
|
|
63
|
+
params: {},
|
|
64
|
+
payload: {
|
|
65
|
+
url: url.toString(),
|
|
66
|
+
include: { markdown: true },
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
: {
|
|
70
|
+
params: {},
|
|
71
|
+
payload: {
|
|
72
|
+
url: url.toString(),
|
|
73
|
+
include: { markdown: true, json: true },
|
|
74
|
+
search: {
|
|
75
|
+
query,
|
|
76
|
+
maxResults,
|
|
77
|
+
minScore,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const output = format === 'text' && query === undefined
|
|
82
|
+
? yield* client
|
|
83
|
+
.fetchFetch(fetchRequest)
|
|
84
|
+
.pipe(Effect.flatMap((markdown) => String.isNonEmpty(markdown)
|
|
85
|
+
? Effect.succeed(markdown)
|
|
86
|
+
: Effect.fail(new MarkdownUnavailable({ url: url.toString() }))))
|
|
87
|
+
: yield* client.fetchFetchResult(fetchRequest).pipe(Effect.flatMap((fetched) => Match.value(format).pipe(Match.when('text', () => {
|
|
88
|
+
const snippets = fetched.data?.search?.snippets ?? [];
|
|
89
|
+
return snippets.length === 0
|
|
90
|
+
? Effect.fail(new SnippetsUnavailable({ url: url.toString(), query: query ?? '' }))
|
|
91
|
+
: Effect.succeed(renderSnippets(fetched.meta.snapshotId, resolvePlaygroundHost(fetched.meta.playground, DEFAULT_PLAYGROUND_HOST), snippets));
|
|
92
|
+
}), Match.when('json', () => Schema.encode(Schema.parseJson(Schema.Unknown, { space: 2 }))(fetched)), Match.exhaustive)));
|
|
93
|
+
yield* Console.log(output);
|
|
94
|
+
}, Effect.provide(ApiClient.Default))));
|
|
95
|
+
//# sourceMappingURL=Fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fetch.js","sourceRoot":"","sources":["../../../src/Commands/Fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Args, Command } from '@effect/cli';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { ClaudeHook } from '../Hook/Claude.js';
|
|
4
|
+
import { OpencodeHook } from '../Hook/Opencode.js';
|
|
5
|
+
import { HookInstaller } from '../Install/Hook.js';
|
|
6
|
+
const ClaudeRunCommand = Command.make('claude-code').pipe(Command.withDescription('Run the Claude Code WebFetch hook protocol over stdio'), Command.withHandler(() => Effect.flatMap(ClaudeHook, (hook) => hook.runProtocol())));
|
|
7
|
+
const OpencodeRunCommand = Command.make('opencode').pipe(Command.withDescription('Run the OpenCode webfetch plugin protocol over stdio'), Command.withHandler(() => Effect.flatMap(OpencodeHook, (hook) => hook.runProtocol())));
|
|
8
|
+
const hostArg = Args.choice([
|
|
9
|
+
['claude-code', 'claude-code'],
|
|
10
|
+
['opencode', 'opencode'],
|
|
11
|
+
]);
|
|
12
|
+
const scopeArg = Args.choice([
|
|
13
|
+
['global', 'global'],
|
|
14
|
+
['project', 'project'],
|
|
15
|
+
]);
|
|
16
|
+
const InstallSubCommand = Command.make('install', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Install the expandai MCP server + webfetch hook for the chosen host + scope'), Command.withHandler(({ host, scope }) => Effect.flatMap(HookInstaller, (installer) => installer.install(host, scope))));
|
|
17
|
+
const UninstallSubCommand = Command.make('uninstall', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Remove the expandai MCP server + webfetch hook for the chosen host + scope'), Command.withHandler(({ host, scope }) => Effect.flatMap(HookInstaller, (installer) => installer.uninstall(host, scope))));
|
|
18
|
+
export const HumanHookCommand = Command.make('hook').pipe(Command.withDescription('expandai hooks for AI coding assistants'), Command.withSubcommands([ClaudeRunCommand, OpencodeRunCommand, InstallSubCommand, UninstallSubCommand]));
|
|
19
|
+
export const AgentHookCommand = Command.make('hook').pipe(Command.withDescription('expandai hooks for AI coding assistants'), Command.withSubcommands([ClaudeRunCommand, OpencodeRunCommand]));
|
|
20
|
+
//# sourceMappingURL=Hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Hook.js","sourceRoot":"","sources":["../../../src/Commands/Hook.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,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CACxD,OAAO,CAAC,eAAe,CAAC,uDAAuD,CAAC,EAChF,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CACnF,CAAA;AAED,MAAM,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CACvD,OAAO,CAAC,eAAe,CAAC,sDAAsD,CAAC,EAC/E,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CACrF,CAAA;AAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,CAAC,aAAa,EAAE,aAAsB,CAAC;IACvC,CAAC,UAAU,EAAE,UAAmB,CAAC;CACjC,CAAC,CAAA;AACF,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,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CACzF,OAAO,CAAC,eAAe,CAAC,6EAA6E,CAAC,EACtG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CACvC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAC5E,CACD,CAAA;AAED,MAAM,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAC7F,OAAO,CAAC,eAAe,CAAC,4EAA4E,CAAC,EACrG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CACvC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAC9E,CACD,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACxD,OAAO,CAAC,eAAe,CAAC,yCAAyC,CAAC,EAClE,OAAO,CAAC,eAAe,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,CAAC,CAAC,CACvG,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACxD,OAAO,CAAC,eAAe,CAAC,yCAAyC,CAAC,EAClE,OAAO,CAAC,eAAe,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC,CAC/D,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Args, Command } from '@effect/cli';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { Mcp } from '../Install/Mcp.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
|
+
export const InstallSubCommand = Command.make('install', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Install the expandai MCP server into the chosen host + scope (e.g. `claude-code global`)'), Command.withHandler(Effect.fnUntraced(function* ({ host, scope }) {
|
|
15
|
+
const mcp = yield* Mcp;
|
|
16
|
+
yield* mcp.install(host, scope);
|
|
17
|
+
})));
|
|
18
|
+
export const UninstallSubCommand = Command.make('uninstall', { host: hostArg, scope: scopeArg }).pipe(Command.withDescription('Remove the expandai MCP server from the chosen host + scope'), Command.withHandler(({ host, scope }) => Effect.flatMap(Mcp, (mcp) => mcp.uninstall(host, scope))));
|
|
19
|
+
//# sourceMappingURL=Mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Mcp.js","sourceRoot":"","sources":["../../../src/Commands/Mcp.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,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAEvC,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,MAAM,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAChG,OAAO,CAAC,eAAe,CAAC,0FAA0F,CAAC,EACnH,OAAO,CAAC,WAAW,CAClB,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;IAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;IACtB,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAChC,CAAC,CAAC,CACF,CACD,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CACpG,OAAO,CAAC,eAAe,CAAC,6DAA6D,CAAC,EACtF,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAClG,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { McpServer } from '@effect/ai';
|
|
2
|
+
import { Command, Options } from '@effect/cli';
|
|
3
|
+
import { NodeSink, NodeStream } from '@effect/platform-node';
|
|
4
|
+
import { Effect, Layer } from 'effect';
|
|
5
|
+
import { makeServerLayer, ServerInfo } from '../Mcp/Server.js';
|
|
6
|
+
const includeDocsOption = Options.boolean('docs').pipe(Options.withDefault(false), Options.withDescription('Expose documentation resources alongside tools'));
|
|
7
|
+
export const McpRunCommand = Command.make('run', { includeDocs: includeDocsOption }).pipe(Command.withDescription('Run the expandai MCP server over stdio'), Command.withHandler(Effect.fnUntraced(function* ({ includeDocs }) {
|
|
8
|
+
const McpLayer = McpServer.layerStdio({
|
|
9
|
+
...ServerInfo,
|
|
10
|
+
stdin: NodeStream.stdin,
|
|
11
|
+
stdout: NodeSink.stdout,
|
|
12
|
+
}).pipe(Layer.provide(makeServerLayer({ includeDocs })));
|
|
13
|
+
return yield* Layer.launch(McpLayer);
|
|
14
|
+
})));
|
|
15
|
+
//# sourceMappingURL=McpRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpRun.js","sourceRoot":"","sources":["../../../src/Commands/McpRun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE9D,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CACrD,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAC1B,OAAO,CAAC,eAAe,CAAC,gDAAgD,CAAC,CACzE,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC,CAAC,IAAI,CACxF,OAAO,CAAC,eAAe,CAAC,wCAAwC,CAAC,EACjE,OAAO,CAAC,WAAW,CAClB,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE;IAC3C,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC;QACrC,GAAG,UAAU;QACb,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;KACvB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;IAExD,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACrC,CAAC,CAAC,CACF,CACD,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Args, Command, Options } from '@effect/cli';
|
|
2
|
+
import { Console, Effect, Layer, Match, Schema } from 'effect';
|
|
3
|
+
import { ApiClient } from '../ApiClient.js';
|
|
4
|
+
import { ConfigManager } from '../Config.js';
|
|
5
|
+
import { renderSnippets } from '../Snippets.js';
|
|
6
|
+
export class SnapshotSnippetsUnavailable extends Schema.TaggedError()('SnapshotSnippetsUnavailable', {
|
|
7
|
+
snapshotId: Schema.String,
|
|
8
|
+
query: Schema.String,
|
|
9
|
+
}) {
|
|
10
|
+
get message() {
|
|
11
|
+
return `No snippets in snapshot ${this.snapshotId} matched '${this.query}'`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const snapshotId = Args.text({ name: 'snapshotId' }).pipe(Args.withDescription('Snapshot handle from a previous fetch (meta.snapshotId, or the WebFetch hook note)'));
|
|
15
|
+
const query = Args.text({ name: 'query' }).pipe(Args.withDescription('Semantic search query'));
|
|
16
|
+
const format = Options.text('format').pipe(Options.withDefault('text'), Options.withDescription('Output format (text or json)'), Options.withSchema(Schema.Literal('text', 'json')));
|
|
17
|
+
const maxResults = Options.integer('max-results').pipe(Options.withDefault(5), Options.withDescription('Maximum snippets to return (default: 5)'), Options.withSchema(Schema.Int.pipe(Schema.greaterThanOrEqualTo(1), Schema.lessThanOrEqualTo(50))));
|
|
18
|
+
const minScore = Options.float('min-score').pipe(Options.withDefault(0.6), Options.withDescription('Minimum snippet relevance score from 0 to 1 (default: 0.6)'), Options.withSchema(Schema.Number.pipe(Schema.greaterThanOrEqualTo(0), Schema.lessThanOrEqualTo(1))));
|
|
19
|
+
const searchConfig = {
|
|
20
|
+
snapshotId,
|
|
21
|
+
query,
|
|
22
|
+
format,
|
|
23
|
+
maxResults,
|
|
24
|
+
minScore,
|
|
25
|
+
};
|
|
26
|
+
export const SearchCommand = Command.make('search', searchConfig).pipe(Command.withDescription('Search a previously fetched page by its snapshot handle — no new capture'), Command.withHandler(Effect.fnUntraced(function* ({ format, maxResults, minScore, query, snapshotId }) {
|
|
27
|
+
const client = yield* ApiClient;
|
|
28
|
+
const configManager = yield* ConfigManager;
|
|
29
|
+
const host = yield* configManager.getPlaygroundHost();
|
|
30
|
+
const result = yield* client.fetchFetchSearch({
|
|
31
|
+
snapshotId,
|
|
32
|
+
search: { query, maxResults, minScore },
|
|
33
|
+
include: { markdown: true, json: true },
|
|
34
|
+
});
|
|
35
|
+
const output = yield* Match.value(format).pipe(Match.when('text', () => {
|
|
36
|
+
const snippets = result.search.snippets;
|
|
37
|
+
return snippets.length === 0
|
|
38
|
+
? Effect.fail(new SnapshotSnippetsUnavailable({ snapshotId, query }))
|
|
39
|
+
: Effect.succeed(renderSnippets(snapshotId, host, snippets));
|
|
40
|
+
}), Match.when('json', () => Schema.encode(Schema.parseJson(Schema.Unknown, { space: 2 }))(result)), Match.exhaustive);
|
|
41
|
+
yield* Console.log(output);
|
|
42
|
+
}, Effect.provide(Layer.mergeAll(ApiClient.Default, ConfigManager.Default)))));
|
|
43
|
+
//# sourceMappingURL=Search.js.map
|