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/Install/Codex/Mcp.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os'
|
|
2
|
-
import { Config, Console, Effect, Match, Record, Schema } from 'effect'
|
|
3
|
-
import { parse, stringify, type TomlTableWithoutBigInt } from 'smol-toml'
|
|
4
|
-
import { CliInvocation, ConfigIo, McpParseError, McpWriteError, SERVER_KEY } from '../Domain.js'
|
|
5
|
-
import type { InstallScope } from '../Mcp.js'
|
|
6
|
-
|
|
7
|
-
export class Entry extends Schema.Class<Entry>('CodexMcpEntry')({
|
|
8
|
-
command: Schema.NonEmptyTrimmedString,
|
|
9
|
-
args: Schema.Array(Schema.String),
|
|
10
|
-
env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })),
|
|
11
|
-
enabled: Schema.Boolean,
|
|
12
|
-
}) {}
|
|
13
|
-
|
|
14
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
15
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const getMcpServers = Effect.fn(function* (config: TomlTableWithoutBigInt) {
|
|
19
|
-
const servers = config.mcp_servers
|
|
20
|
-
return isRecord(servers) ? servers : {}
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
function toStringifyValue(value: TomlTableWithoutBigInt) {
|
|
24
|
-
// smol-toml's stringify type accepts any; keep the unsafe boundary local.
|
|
25
|
-
return value as unknown as Record<string, unknown>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Codex MCP descriptor — Codex stores MCP servers in TOML config under
|
|
30
|
-
* `[mcp_servers.<name>]`.
|
|
31
|
-
*
|
|
32
|
-
* - global → `~/.codex/config.toml`
|
|
33
|
-
* - project → `<cwd>/.codex/config.toml`
|
|
34
|
-
*/
|
|
35
|
-
export class CodexMcp extends Effect.Service<CodexMcp>()('@expandai/cli/Install/Codex/Mcp', {
|
|
36
|
-
dependencies: [ConfigIo.Default, CliInvocation.Default],
|
|
37
|
-
effect: Effect.gen(function* () {
|
|
38
|
-
const io = yield* ConfigIo
|
|
39
|
-
const cli = yield* CliInvocation
|
|
40
|
-
const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
|
|
41
|
-
const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
|
|
42
|
-
|
|
43
|
-
const pathFor = Effect.fn(function* (scope: InstallScope) {
|
|
44
|
-
return yield* Match.value(scope).pipe(
|
|
45
|
-
Match.when('global', () => Effect.succeed(`${home}/.codex/config.toml`)),
|
|
46
|
-
Match.when('project', () => Effect.succeed(`${cwd}/.codex/config.toml`)),
|
|
47
|
-
Match.exhaustive,
|
|
48
|
-
)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const readConfig = Effect.fn(function* (file: string) {
|
|
52
|
-
const text = yield* io.readText(file)
|
|
53
|
-
return yield* Effect.try({
|
|
54
|
-
try: () => parse(text, { integersAsBigInt: false }),
|
|
55
|
-
catch: (error) => new McpParseError({ path: file, error }),
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const writeConfig = Effect.fn(function* (file: string, value: TomlTableWithoutBigInt) {
|
|
60
|
-
const encoded = yield* Effect.try({
|
|
61
|
-
try: () => stringify(toStringifyValue(value)),
|
|
62
|
-
catch: (error) => new McpWriteError({ path: file, error }),
|
|
63
|
-
})
|
|
64
|
-
yield* io.writeText(file, encoded).pipe(Effect.mapError((error) => new McpWriteError({ path: file, error })))
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const install = Effect.fn(function* (scope: InstallScope) {
|
|
68
|
-
const file = yield* pathFor(scope)
|
|
69
|
-
const config = yield* readConfig(file)
|
|
70
|
-
const servers = yield* getMcpServers(config)
|
|
71
|
-
|
|
72
|
-
const invocation = yield* cli.resolve()
|
|
73
|
-
const entryModel = new Entry({
|
|
74
|
-
command: invocation.command,
|
|
75
|
-
args: [...invocation.args, 'mcp', 'run'],
|
|
76
|
-
env: { EXPANDAI_HARNESS: 'codex' },
|
|
77
|
-
enabled: true,
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const entry: TomlTableWithoutBigInt = {
|
|
81
|
-
command: entryModel.command,
|
|
82
|
-
args: [...entryModel.args],
|
|
83
|
-
env: { ...entryModel.env },
|
|
84
|
-
enabled: entryModel.enabled,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
yield* writeConfig(file, {
|
|
88
|
-
...config,
|
|
89
|
-
mcp_servers: Record.set(servers, SERVER_KEY, entry),
|
|
90
|
-
})
|
|
91
|
-
yield* Console.log(`Installed expandai MCP server into ${file}`)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const uninstall = Effect.fn(function* (scope: InstallScope) {
|
|
95
|
-
const file = yield* pathFor(scope)
|
|
96
|
-
const config = yield* readConfig(file)
|
|
97
|
-
const servers = yield* getMcpServers(config)
|
|
98
|
-
if (!(SERVER_KEY in servers)) {
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
yield* writeConfig(file, { ...config, mcp_servers: Record.remove(servers, SERVER_KEY) })
|
|
102
|
-
yield* Console.log(`Removed expandai MCP server from ${file}`)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const isInstalled = Effect.fn(function* (scope: InstallScope) {
|
|
106
|
-
const file = yield* pathFor(scope)
|
|
107
|
-
const config = yield* readConfig(file)
|
|
108
|
-
const servers = yield* getMcpServers(config)
|
|
109
|
-
return SERVER_KEY in servers
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
return { install, uninstall, isInstalled, pathFor } as const
|
|
113
|
-
}),
|
|
114
|
-
}) {}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os'
|
|
2
|
-
import { Config, Effect, Match } from 'effect'
|
|
3
|
-
import { SkillIo } from '../Domain.js'
|
|
4
|
-
import type { InstallScope } from '../Mcp.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Codex skills use the open agent skills locations:
|
|
8
|
-
*
|
|
9
|
-
* - global → `~/.agents/skills/<name>/`
|
|
10
|
-
* - project → `<cwd>/.agents/skills/<name>/`
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const SKILL_NAME = 'expandai'
|
|
14
|
-
|
|
15
|
-
export class CodexSkill extends Effect.Service<CodexSkill>()('@expandai/cli/Install/Codex/Skill', {
|
|
16
|
-
dependencies: [SkillIo.Default],
|
|
17
|
-
effect: Effect.gen(function* () {
|
|
18
|
-
const io = yield* SkillIo
|
|
19
|
-
const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
|
|
20
|
-
const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
|
|
21
|
-
|
|
22
|
-
const pathFor = Effect.fn(function* (scope: InstallScope) {
|
|
23
|
-
return yield* Match.value(scope).pipe(
|
|
24
|
-
Match.when('global', () => Effect.succeed(`${home}/.agents/skills/${SKILL_NAME}`)),
|
|
25
|
-
Match.when('project', () => Effect.succeed(`${cwd}/.agents/skills/${SKILL_NAME}`)),
|
|
26
|
-
Match.exhaustive,
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const install = Effect.fn(function* (scope: InstallScope) {
|
|
31
|
-
return yield* io.install(yield* pathFor(scope))
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const uninstall = Effect.fn(function* (scope: InstallScope) {
|
|
35
|
-
return yield* io.uninstall(yield* pathFor(scope))
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const isCurrent = Effect.fn(function* (scope: InstallScope) {
|
|
39
|
-
return yield* io.isCurrent(yield* pathFor(scope))
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
return { install, uninstall, isCurrent, pathFor } as const
|
|
43
|
-
}),
|
|
44
|
-
}) {}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os'
|
|
2
|
-
import { Config, Console, Effect, Match, Record, Schema } from 'effect'
|
|
3
|
-
import { CliInvocation, ConfigIo, McpParseError, McpServersConfigSchema, McpWriteError, SERVER_KEY } from '../Domain.js'
|
|
4
|
-
import type { InstallScope } from '../Mcp.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Cursor MCP descriptor — same `mcpServers` shape as Claude Code, different
|
|
8
|
-
* paths.
|
|
9
|
-
*
|
|
10
|
-
* - global → `~/.cursor/mcp.json`
|
|
11
|
-
* - project → `<cwd>/.cursor/mcp.json`
|
|
12
|
-
*/
|
|
13
|
-
export class Entry extends Schema.Class<Entry>('CursorMcpEntry')({
|
|
14
|
-
type: Schema.Literal('stdio'),
|
|
15
|
-
command: Schema.NonEmptyTrimmedString,
|
|
16
|
-
args: Schema.Array(Schema.String),
|
|
17
|
-
env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })),
|
|
18
|
-
}) {}
|
|
19
|
-
|
|
20
|
-
export class CursorMcp extends Effect.Service<CursorMcp>()('@expandai/cli/Install/Cursor/Mcp', {
|
|
21
|
-
dependencies: [ConfigIo.Default, CliInvocation.Default],
|
|
22
|
-
effect: Effect.gen(function* () {
|
|
23
|
-
const io = yield* ConfigIo
|
|
24
|
-
const cli = yield* CliInvocation
|
|
25
|
-
const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
|
|
26
|
-
const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
|
|
27
|
-
|
|
28
|
-
const pathFor = Effect.fn(function* (scope: InstallScope) {
|
|
29
|
-
return yield* Match.value(scope).pipe(
|
|
30
|
-
Match.when('global', () => Effect.succeed(`${home}/.cursor/mcp.json`)),
|
|
31
|
-
Match.when('project', () => Effect.succeed(`${cwd}/.cursor/mcp.json`)),
|
|
32
|
-
Match.exhaustive,
|
|
33
|
-
)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const readConfig = Effect.fn(function* (file: string) {
|
|
37
|
-
return yield* io
|
|
38
|
-
.read(file, McpServersConfigSchema)
|
|
39
|
-
.pipe(Effect.mapError((error) => new McpParseError({ path: file, error })))
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const writeConfig = Effect.fn(function* (file: string, value: unknown) {
|
|
43
|
-
yield* io
|
|
44
|
-
.write(file, value, McpServersConfigSchema)
|
|
45
|
-
.pipe(Effect.mapError((error) => new McpWriteError({ path: file, error })))
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const install = Effect.fn(function* (scope: InstallScope) {
|
|
49
|
-
const file = yield* pathFor(scope)
|
|
50
|
-
const config = yield* readConfig(file)
|
|
51
|
-
const invocation = yield* cli.resolve()
|
|
52
|
-
yield* writeConfig(file, {
|
|
53
|
-
...config,
|
|
54
|
-
mcpServers: {
|
|
55
|
-
...config.mcpServers,
|
|
56
|
-
[SERVER_KEY]: new Entry({
|
|
57
|
-
type: 'stdio',
|
|
58
|
-
command: invocation.command,
|
|
59
|
-
args: [...invocation.args, 'mcp', 'run'],
|
|
60
|
-
env: { EXPANDAI_HARNESS: 'cursor' },
|
|
61
|
-
}),
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
yield* Console.log(`Installed expandai MCP server into ${file}`)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const uninstall = Effect.fn(function* (scope: InstallScope) {
|
|
68
|
-
const file = yield* pathFor(scope)
|
|
69
|
-
const config = yield* readConfig(file)
|
|
70
|
-
if (!(SERVER_KEY in config.mcpServers)) {
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
yield* writeConfig(file, { ...config, mcpServers: Record.remove(config.mcpServers, SERVER_KEY) })
|
|
74
|
-
yield* Console.log(`Removed expandai MCP server from ${file}`)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
const isInstalled = Effect.fn(function* (scope: InstallScope) {
|
|
78
|
-
const file = yield* pathFor(scope)
|
|
79
|
-
const config = yield* readConfig(file)
|
|
80
|
-
return SERVER_KEY in config.mcpServers
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
return { install, uninstall, isInstalled, pathFor } as const
|
|
84
|
-
}),
|
|
85
|
-
}) {}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os'
|
|
2
|
-
import { Config, Effect, Match } from 'effect'
|
|
3
|
-
import { SkillIo } from '../Domain.js'
|
|
4
|
-
import type { InstallScope } from '../Mcp.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Cursor skills are auto-loaded from these locations (per
|
|
8
|
-
* cursor.com/docs/skills):
|
|
9
|
-
*
|
|
10
|
-
* - global → `~/.cursor/skills/<name>/`
|
|
11
|
-
* - project → `<cwd>/.cursor/skills/<name>/`
|
|
12
|
-
*
|
|
13
|
-
* Cursor also compat-loads `.claude/skills/`, `.codex/skills/`, `.agents/skills/`
|
|
14
|
-
* at both project and user scope, but we ship to Cursor's native paths.
|
|
15
|
-
*
|
|
16
|
-
* Format is the same SKILL.md + YAML frontmatter open standard as Claude Code
|
|
17
|
-
* and OpenCode, so the bundled `skills/expandai/` directory copies in unchanged.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export const SKILL_NAME = 'expandai'
|
|
21
|
-
|
|
22
|
-
export class CursorSkill extends Effect.Service<CursorSkill>()('@expandai/cli/Install/Cursor/Skill', {
|
|
23
|
-
dependencies: [SkillIo.Default],
|
|
24
|
-
effect: Effect.gen(function* () {
|
|
25
|
-
const io = yield* SkillIo
|
|
26
|
-
const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
|
|
27
|
-
const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
|
|
28
|
-
|
|
29
|
-
const pathFor = Effect.fn(function* (scope: InstallScope) {
|
|
30
|
-
return yield* Match.value(scope).pipe(
|
|
31
|
-
Match.when('global', () => Effect.succeed(`${home}/.cursor/skills/${SKILL_NAME}`)),
|
|
32
|
-
Match.when('project', () => Effect.succeed(`${cwd}/.cursor/skills/${SKILL_NAME}`)),
|
|
33
|
-
Match.exhaustive,
|
|
34
|
-
)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const install = Effect.fn(function* (scope: InstallScope) {
|
|
38
|
-
return yield* io.install(yield* pathFor(scope))
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const uninstall = Effect.fn(function* (scope: InstallScope) {
|
|
42
|
-
return yield* io.uninstall(yield* pathFor(scope))
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const isCurrent = Effect.fn(function* (scope: InstallScope) {
|
|
46
|
-
return yield* io.isCurrent(yield* pathFor(scope))
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
return { install, uninstall, isCurrent, pathFor } as const
|
|
50
|
-
}),
|
|
51
|
-
}) {}
|
package/src/Install/Domain.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { FileSystem, Path } from '@effect/platform'
|
|
2
|
-
import { NodeFileSystem, NodePath } from '@effect/platform-node'
|
|
3
|
-
import { Array, Config, Effect, Inspectable, Layer, Schema, String } from 'effect'
|
|
4
|
-
|
|
5
|
-
export class McpParseError extends Schema.TaggedError<McpParseError>()('McpParseError', {
|
|
6
|
-
path: Schema.String,
|
|
7
|
-
error: Schema.Defect,
|
|
8
|
-
}) {
|
|
9
|
-
get message() {
|
|
10
|
-
return `Failed to parse MCP config at ${this.path}: ${Inspectable.format(this.error)}`
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class McpWriteError extends Schema.TaggedError<McpWriteError>()('McpWriteError', {
|
|
15
|
-
path: Schema.String,
|
|
16
|
-
error: Schema.Defect,
|
|
17
|
-
}) {
|
|
18
|
-
get message() {
|
|
19
|
-
return `Failed to write MCP config at ${this.path}: ${Inspectable.format(this.error)}`
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const Host = Schema.Literal('claude-code', 'cursor', 'opencode', 'codex')
|
|
24
|
-
export type Host = typeof Host.Type
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The agent harness invoking the CLI — every install target plus 'unknown'
|
|
28
|
-
* for uninstrumented callers. Installers bake `EXPANDAI_HARNESS=<host>` into
|
|
29
|
-
* MCP/hook configs; telemetry reads it back.
|
|
30
|
-
*/
|
|
31
|
-
export const Harness = Schema.Literal(...Host.literals, 'unknown')
|
|
32
|
-
export type Harness = typeof Harness.Type
|
|
33
|
-
|
|
34
|
-
export const InstallScope = Schema.Literal('project', 'global')
|
|
35
|
-
export type InstallScope = typeof InstallScope.Type
|
|
36
|
-
|
|
37
|
-
const McpServerMap = Schema.Record({ key: Schema.String, value: Schema.Unknown })
|
|
38
|
-
|
|
39
|
-
export const McpServersConfigSchema = Schema.parseJson(
|
|
40
|
-
Schema.Struct({ mcpServers: Schema.optionalWith(McpServerMap, { default: () => ({}) }) }, McpServerMap),
|
|
41
|
-
{ space: 2 },
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
export const OpencodeConfigSchema = Schema.parseJson(
|
|
45
|
-
Schema.Struct({ mcp: Schema.optionalWith(McpServerMap, { default: () => ({}) }) }, McpServerMap),
|
|
46
|
-
{ space: 2 },
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
export const SERVER_KEY = 'expandai'
|
|
50
|
-
|
|
51
|
-
/** How a host config should launch the CLI: `command` plus leading args. */
|
|
52
|
-
export interface CliCommand {
|
|
53
|
-
readonly command: string
|
|
54
|
-
readonly args: ReadonlyArray<string>
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const DIRECT_CLI: CliCommand = { command: 'expandai', args: [] }
|
|
58
|
-
const NPX_CLI: CliCommand = { command: 'npx', args: ['-y', 'expandai'] }
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Renders a `CliCommand` as the single shell string Claude Code stores for hook
|
|
62
|
-
* entries. MCP descriptors keep `command`/`args` separate because they exec
|
|
63
|
-
* directly; hooks are run through a shell, so they need one flat line.
|
|
64
|
-
*/
|
|
65
|
-
export function cliShellCommand(cli: CliCommand, args: ReadonlyArray<string>): string {
|
|
66
|
-
return [cli.command, ...cli.args, ...args].join(' ')
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Resolves how installed host configs should invoke the expandai CLI. MCP
|
|
71
|
-
* entries exec their `command` directly (no shell), so the fallback decision
|
|
72
|
-
* happens at install time: prefer the `expandai` binary when it is on PATH,
|
|
73
|
-
* otherwise write an `npx -y expandai` invocation so installs performed via
|
|
74
|
-
* `npx expandai …` keep working without a global CLI install.
|
|
75
|
-
*/
|
|
76
|
-
const makeCliInvocation = Effect.gen(function* () {
|
|
77
|
-
const fs = yield* FileSystem.FileSystem
|
|
78
|
-
const path = yield* Path.Path
|
|
79
|
-
const searchPath = yield* Config.string('PATH').pipe(Config.withDefault(''))
|
|
80
|
-
|
|
81
|
-
const resolve = Effect.fn('CliInvocation.resolve')(function* () {
|
|
82
|
-
// ':' is the POSIX PATH delimiter — the install flow already assumes
|
|
83
|
-
// POSIX conventions (HOME-based config paths, shell hooks).
|
|
84
|
-
const onPath = yield* Effect.exists(Array.filter(searchPath.split(':'), String.isNonEmpty), (dir) =>
|
|
85
|
-
fs.exists(path.join(dir, 'expandai')).pipe(Effect.orElseSucceed(() => false)),
|
|
86
|
-
)
|
|
87
|
-
return onPath ? DIRECT_CLI : NPX_CLI
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
return { resolve } as const
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
export class CliInvocation extends Effect.Service<CliInvocation>()('@expandai/cli/Install/CliInvocation', {
|
|
94
|
-
dependencies: [NodeFileSystem.layer, NodePath.layer],
|
|
95
|
-
effect: makeCliInvocation,
|
|
96
|
-
}) {
|
|
97
|
-
/** Dependency-open layer so fixtures can supply their own file system and PATH. */
|
|
98
|
-
static readonly layer = Layer.effect(CliInvocation, makeCliInvocation.pipe(Effect.map(CliInvocation.make)))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export const readConfigText = Effect.fn(function* (file: string) {
|
|
102
|
-
const fs = yield* FileSystem.FileSystem
|
|
103
|
-
const exists = yield* fs.exists(file)
|
|
104
|
-
if (!exists) {
|
|
105
|
-
return '{}'
|
|
106
|
-
}
|
|
107
|
-
return yield* fs.readFileString(file)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
export const writeConfigText = Effect.fn(function* (file: string, text: string) {
|
|
111
|
-
const fs = yield* FileSystem.FileSystem
|
|
112
|
-
const path = yield* Path.Path
|
|
113
|
-
yield* fs.makeDirectory(path.dirname(file), { recursive: true })
|
|
114
|
-
yield* fs.writeFileString(file, `${text}\n`)
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Captures `FileSystem` + `Path` once and exposes string-encoded JSON read/
|
|
119
|
-
* write methods that don't re-yield them on every call. Use this from inside
|
|
120
|
-
* an `Effect.Service` gen block so the install/uninstall methods don't have
|
|
121
|
-
* to re-thread `provideService` calls.
|
|
122
|
-
*/
|
|
123
|
-
export class ConfigIo extends Effect.Service<ConfigIo>()('@expandai/cli/Install/ConfigIo', {
|
|
124
|
-
dependencies: [NodeFileSystem.layer, NodePath.layer],
|
|
125
|
-
effect: Effect.gen(function* () {
|
|
126
|
-
const fs = yield* FileSystem.FileSystem
|
|
127
|
-
const path = yield* Path.Path
|
|
128
|
-
|
|
129
|
-
const read = Effect.fn('ConfigIo.read')(function* <A>(file: string, schema: Schema.Schema<A, string>) {
|
|
130
|
-
const text = yield* readConfigText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs))
|
|
131
|
-
return yield* Schema.decode(schema)(text)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
const write = Effect.fn('ConfigIo.write')(function* <A>(
|
|
135
|
-
file: string,
|
|
136
|
-
value: unknown,
|
|
137
|
-
schema: Schema.Schema<A, string>,
|
|
138
|
-
) {
|
|
139
|
-
const encoded = yield* Schema.encodeUnknown(schema)(value)
|
|
140
|
-
yield* writeConfigText(file, encoded).pipe(
|
|
141
|
-
Effect.provideService(FileSystem.FileSystem, fs),
|
|
142
|
-
Effect.provideService(Path.Path, path),
|
|
143
|
-
)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const exists = Effect.fn('ConfigIo.exists')(function* (file: string) {
|
|
147
|
-
return yield* fs.exists(file)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// Raw text I/O for formats `ConfigIo.read`/`write` can't handle through a
|
|
151
|
-
// `Schema.parseJson(...)` schema (e.g. Codex's TOML). Missing file reads
|
|
152
|
-
// as an empty string — callers attach their own parser-specific default.
|
|
153
|
-
const readText = Effect.fn('ConfigIo.readText')(function* (file: string) {
|
|
154
|
-
const present = yield* fs.exists(file)
|
|
155
|
-
return present ? yield* fs.readFileString(file) : ''
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
const writeText = Effect.fn('ConfigIo.writeText')(function* (file: string, text: string) {
|
|
159
|
-
yield* writeConfigText(file, text).pipe(
|
|
160
|
-
Effect.provideService(FileSystem.FileSystem, fs),
|
|
161
|
-
Effect.provideService(Path.Path, path),
|
|
162
|
-
)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
return { read, write, exists, readText, writeText } as const
|
|
166
|
-
}),
|
|
167
|
-
}) {}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Captures `FileSystem` + `Path` once and exposes skill directory copy/remove
|
|
171
|
-
* operations. The bundled `SKILL.md` lives at `<cli>/skills/expandai/` (or
|
|
172
|
-
* `<build>/skills/expandai/` in production); this service computes that path
|
|
173
|
-
* from its own `import.meta.url`, so per-host skill descriptors only need to
|
|
174
|
-
* provide `pathFor` and call `io.install(target)` / `io.uninstall(target)`.
|
|
175
|
-
*/
|
|
176
|
-
export class SkillIo extends Effect.Service<SkillIo>()('@expandai/cli/Install/SkillIo', {
|
|
177
|
-
dependencies: [NodeFileSystem.layer, NodePath.layer],
|
|
178
|
-
effect: Effect.gen(function* () {
|
|
179
|
-
const fs = yield* FileSystem.FileSystem
|
|
180
|
-
const path = yield* Path.Path
|
|
181
|
-
|
|
182
|
-
const sourceDir = path.resolve(
|
|
183
|
-
path.dirname(yield* path.fromFileUrl(new URL(import.meta.url))),
|
|
184
|
-
'..',
|
|
185
|
-
'..',
|
|
186
|
-
'skills',
|
|
187
|
-
'expandai',
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
const install = Effect.fn('SkillIo.install')(function* (target: string) {
|
|
191
|
-
yield* fs.makeDirectory(path.dirname(target), { recursive: true })
|
|
192
|
-
yield* fs.copy(sourceDir, target, { overwrite: true })
|
|
193
|
-
return target
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
const uninstall = Effect.fn('SkillIo.uninstall')(function* (target: string) {
|
|
197
|
-
const exists = yield* fs.exists(target)
|
|
198
|
-
if (exists) {
|
|
199
|
-
yield* fs.remove(target, { recursive: true })
|
|
200
|
-
}
|
|
201
|
-
return target
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Whether the installed skill matches the bundled one. Presence alone is not
|
|
206
|
-
* enough: an upgraded CLI ships new skill text, and a harness whose install is
|
|
207
|
-
* gated on "the file exists" would keep serving the old instructions forever.
|
|
208
|
-
*/
|
|
209
|
-
const isCurrent = Effect.fn('SkillIo.isCurrent')(function* (target: string) {
|
|
210
|
-
const targetFile = path.join(target, 'SKILL.md')
|
|
211
|
-
if (!(yield* fs.exists(targetFile))) return false
|
|
212
|
-
const [installed, bundled] = yield* Effect.all([
|
|
213
|
-
fs.readFileString(targetFile),
|
|
214
|
-
fs.readFileString(path.join(sourceDir, 'SKILL.md')),
|
|
215
|
-
])
|
|
216
|
-
return installed === bundled
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
return { install, uninstall, isCurrent } as const
|
|
220
|
-
}),
|
|
221
|
-
}) {}
|
package/src/Install/Hook.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Effect, Match, Schema } from 'effect'
|
|
2
|
-
import { ClaudeHook } from './Claude/Hook.js'
|
|
3
|
-
import { type InstallScope, Mcp } from './Mcp.js'
|
|
4
|
-
import { OpencodeHook } from './Opencode/Hook.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Hook install dispatcher. Bundles MCP install + per-host hook config:
|
|
8
|
-
*
|
|
9
|
-
* - claude-code: writes the matcher into `~/.claude/settings.json` (global)
|
|
10
|
-
* or `<cwd>/.claude/settings.json` (project), then registers expandai as
|
|
11
|
-
* an MCP server at the matching scope.
|
|
12
|
-
* - opencode: writes the webfetch plugin file into `.opencode/plugins/`
|
|
13
|
-
* (project) or `~/.config/opencode/plugins/` (global), plus registers
|
|
14
|
-
* expandai as an MCP server.
|
|
15
|
-
*
|
|
16
|
-
* Cursor has no hook system per opencode.ai/docs and Cursor's docs, so it's
|
|
17
|
-
* absent from this Host union (compare with `Mcp.Host` which includes Cursor).
|
|
18
|
-
*/
|
|
19
|
-
export const HookHost = Schema.Literal('claude-code', 'opencode')
|
|
20
|
-
export type HookHost = typeof HookHost.Type
|
|
21
|
-
|
|
22
|
-
export class HookInstaller extends Effect.Service<HookInstaller>()('@expandai/cli/Install/Hook', {
|
|
23
|
-
dependencies: [Mcp.Default, ClaudeHook.Default, OpencodeHook.Default],
|
|
24
|
-
effect: Effect.gen(function* () {
|
|
25
|
-
const mcp = yield* Mcp
|
|
26
|
-
const claudeHook = yield* ClaudeHook
|
|
27
|
-
const opencodeHook = yield* OpencodeHook
|
|
28
|
-
|
|
29
|
-
const install = Effect.fn('HookInstaller.install')(function* (host: HookHost, scope: InstallScope) {
|
|
30
|
-
// Bundle: MCP install first, then host-specific hook config.
|
|
31
|
-
yield* mcp.install(host, scope)
|
|
32
|
-
yield* Match.value(host).pipe(
|
|
33
|
-
Match.when('claude-code', () => claudeHook.install(scope)),
|
|
34
|
-
Match.when('opencode', () => opencodeHook.install(scope)),
|
|
35
|
-
Match.exhaustive,
|
|
36
|
-
)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const uninstall = Effect.fn('HookInstaller.uninstall')(function* (host: HookHost, scope: InstallScope) {
|
|
40
|
-
yield* Match.value(host).pipe(
|
|
41
|
-
Match.when('claude-code', () => claudeHook.uninstall(scope)),
|
|
42
|
-
Match.when('opencode', () => opencodeHook.uninstall(scope)),
|
|
43
|
-
Match.exhaustive,
|
|
44
|
-
)
|
|
45
|
-
yield* mcp.uninstall(host, scope)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
return { install, uninstall } as const
|
|
49
|
-
}),
|
|
50
|
-
}) {}
|
package/src/Install/Mcp.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Effect, Match } from 'effect'
|
|
2
|
-
import { ClaudeMcp } from './Claude/Mcp.js'
|
|
3
|
-
import { CodexMcp } from './Codex/Mcp.js'
|
|
4
|
-
import { CursorMcp } from './Cursor/Mcp.js'
|
|
5
|
-
import type { Host, InstallScope } from './Domain.js'
|
|
6
|
-
import { OpencodeMcp } from './Opencode/Mcp.js'
|
|
7
|
-
|
|
8
|
-
export { Host, InstallScope, McpParseError, McpWriteError } from './Domain.js'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* MCP install dispatcher. Per-host descriptors live in `Install/<Host>/Mcp.ts`
|
|
12
|
-
* and contribute their path resolution + entry shape; this service handles
|
|
13
|
-
* the file I/O (read existing config, merge, write back) and routes by host.
|
|
14
|
-
*
|
|
15
|
-
* - claude-code (~/.claude.json | <cwd>/.mcp.json) → mcpServers shape
|
|
16
|
-
* - cursor (~/.cursor/mcp.json | <cwd>/.cursor/mcp.json) → mcpServers
|
|
17
|
-
* - opencode (~/.config/opencode/opencode.json | <cwd>/opencode.json) → opencode `mcp` shape
|
|
18
|
-
* - codex (~/.codex/config.toml | <cwd>/.codex/config.toml) → Codex TOML `mcp_servers`
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
export class Mcp extends Effect.Service<Mcp>()('@expandai/cli/Install/Mcp', {
|
|
22
|
-
dependencies: [ClaudeMcp.Default, CursorMcp.Default, OpencodeMcp.Default, CodexMcp.Default],
|
|
23
|
-
effect: Effect.gen(function* () {
|
|
24
|
-
const claude = yield* ClaudeMcp
|
|
25
|
-
const cursor = yield* CursorMcp
|
|
26
|
-
const opencode = yield* OpencodeMcp
|
|
27
|
-
const codex = yield* CodexMcp
|
|
28
|
-
|
|
29
|
-
const pathFor = Effect.fn('Mcp.pathFor')(function* (host: Host, scope: InstallScope) {
|
|
30
|
-
return yield* Match.value(host).pipe(
|
|
31
|
-
Match.when('claude-code', () => claude.pathFor(scope)),
|
|
32
|
-
Match.when('cursor', () => cursor.pathFor(scope)),
|
|
33
|
-
Match.when('opencode', () => opencode.pathFor(scope)),
|
|
34
|
-
Match.when('codex', () => codex.pathFor(scope)),
|
|
35
|
-
Match.exhaustive,
|
|
36
|
-
)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const install = Effect.fn('Mcp.install')(function* (host: Host, scope: InstallScope) {
|
|
40
|
-
yield* Match.value(host).pipe(
|
|
41
|
-
Match.when('claude-code', () => claude.install(scope)),
|
|
42
|
-
Match.when('cursor', () => cursor.install(scope)),
|
|
43
|
-
Match.when('opencode', () => opencode.install(scope)),
|
|
44
|
-
Match.when('codex', () => codex.install(scope)),
|
|
45
|
-
Match.exhaustive,
|
|
46
|
-
)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const uninstall = Effect.fn('Mcp.uninstall')(function* (host: Host, scope: InstallScope) {
|
|
50
|
-
yield* Match.value(host).pipe(
|
|
51
|
-
Match.when('claude-code', () => claude.uninstall(scope)),
|
|
52
|
-
Match.when('cursor', () => cursor.uninstall(scope)),
|
|
53
|
-
Match.when('opencode', () => opencode.uninstall(scope)),
|
|
54
|
-
Match.when('codex', () => codex.uninstall(scope)),
|
|
55
|
-
Match.exhaustive,
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const isInstalled = Effect.fn('Mcp.isInstalled')(function* (host: Host, scope: InstallScope) {
|
|
60
|
-
return yield* Match.value(host).pipe(
|
|
61
|
-
Match.when('claude-code', () => claude.isInstalled(scope)),
|
|
62
|
-
Match.when('cursor', () => cursor.isInstalled(scope)),
|
|
63
|
-
Match.when('opencode', () => opencode.isInstalled(scope)),
|
|
64
|
-
Match.when('codex', () => codex.isInstalled(scope)),
|
|
65
|
-
Match.exhaustive,
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
return { install, uninstall, isInstalled, pathFor } as const
|
|
70
|
-
}),
|
|
71
|
-
}) {}
|