openfork 1.17.17 → 1.17.19
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/package.json +1 -1
- package/script/publish.ts +2 -2
- package/script/trace-imports.ts +1 -1
- package/specs/v2/api.ts +2 -2
- package/src/acp/service.ts +7 -7
- package/src/acp/usage.ts +4 -4
- package/src/cli/cmd/account.ts +1 -1
- package/src/cli/cmd/github.handler.ts +5 -5
- package/src/cli/cmd/integrations.ts +391 -0
- package/src/cli/cmd/pr.ts +2 -2
- package/src/cli/cmd/providers.ts +2 -2
- package/src/cli/cmd/run/footer.permission.tsx +2 -2
- package/src/cli/cmd/run/footer.prompt.tsx +1 -1
- package/src/cli/cmd/run/permission.shared.ts +2 -2
- package/src/cli/cmd/run/splash.ts +1 -1
- package/src/cli/cmd/uninstall.ts +2 -2
- package/src/config/config.ts +5 -5
- package/src/config/managed.ts +1 -1
- package/src/config/tui-migrate.ts +3 -3
- package/src/index.ts +2 -0
- package/src/installation/index.ts +3 -3
- package/src/mcp/index.ts +4 -4
- package/src/mcp/oauth-provider.ts +1 -1
- package/src/plugin/digitalocean.ts +1 -1
- package/src/plugin/github-copilot/models.ts +1 -1
- package/src/plugin/shared.ts +4 -4
- package/src/plugin/snowflake-cortex.ts +1 -1
- package/src/provider/provider.ts +7 -7
- package/src/provider/transform.ts +1 -1
- package/src/server/routes/instance/httpapi/api.ts +1 -1
- package/src/server/routes/instance/httpapi/groups/config.ts +2 -2
- package/src/server/routes/instance/httpapi/groups/experimental.ts +3 -3
- package/src/server/routes/instance/httpapi/groups/global.ts +5 -5
- package/src/server/routes/instance/httpapi/groups/instance.ts +5 -5
- package/src/server/routes/instance/httpapi/groups/project.ts +2 -2
- package/src/server/routes/instance/httpapi/groups/pty.ts +1 -1
- package/src/server/routes/instance/httpapi/groups/session.ts +3 -3
- package/src/server/routes/instance/httpapi/public.ts +3 -3
- package/src/session/llm/request.ts +2 -2
- package/src/session/prompt/composio.txt +15 -0
- package/src/session/prompt/mem0.txt +32 -0
- package/src/session/prompt/opensearch.txt +30 -0
- package/src/session/prompt/remote_tools.txt +39 -0
- package/src/session/prompt/supermemory.txt +13 -0
- package/src/session/retry.ts +3 -3
- package/src/session/system.ts +12 -0
package/package.json
CHANGED
package/script/publish.ts
CHANGED
|
@@ -22,8 +22,8 @@ async function publish(dir: string, name: string, version: string) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const binaries: Record<string, string> = {}
|
|
25
|
-
for (const filepath of new Bun.Glob("
|
|
26
|
-
const pkg = await Bun.file(
|
|
25
|
+
for (const filepath of new Bun.Glob("dist/*/package.json").scanSync()) {
|
|
26
|
+
const pkg = await Bun.file(filepath).json()
|
|
27
27
|
binaries[pkg.name] = pkg.version
|
|
28
28
|
}
|
|
29
29
|
console.log("binaries", binaries)
|
package/script/trace-imports.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as path from "path"
|
|
3
3
|
import * as ts from "typescript"
|
|
4
4
|
|
|
5
|
-
const BASE_DIR = "/home/thdxr/dev/projects/anomalyco/
|
|
5
|
+
const BASE_DIR = "/home/thdxr/dev/projects/anomalyco/openfork/packages/openfork"
|
|
6
6
|
|
|
7
7
|
// Get entry file from command line arg or use default
|
|
8
8
|
const ENTRY_FILE = process.argv[2] || "src/plugin/tui/runtime.ts"
|
package/specs/v2/api.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { OpenFork } from "@openfork-ai/core"
|
|
4
4
|
import { ReadTool } from "@openfork-ai/core/tools"
|
|
5
5
|
|
|
6
|
-
const openfork =
|
|
6
|
+
const openfork = OpenFork.make({})
|
|
7
7
|
|
|
8
8
|
openfork.tool.add(ReadTool)
|
|
9
9
|
|
package/src/acp/service.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function make(input: {
|
|
|
102
102
|
"terminal-auth": {
|
|
103
103
|
command: "openfork",
|
|
104
104
|
args: ["auth", "login"],
|
|
105
|
-
label: "
|
|
105
|
+
label: "OpenFork Login",
|
|
106
106
|
},
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -128,7 +128,7 @@ export function make(input: {
|
|
|
128
128
|
},
|
|
129
129
|
authMethods: [authMethod],
|
|
130
130
|
agentInfo: {
|
|
131
|
-
name: "
|
|
131
|
+
name: "OpenFork",
|
|
132
132
|
version: InstallationVersion,
|
|
133
133
|
},
|
|
134
134
|
}
|
|
@@ -784,9 +784,9 @@ function defaultModelFromConfig(
|
|
|
784
784
|
// First-session ACP startup must not scan historical sessions just to infer
|
|
785
785
|
// a default. Configured model, openfork provider, then sorted best model keep
|
|
786
786
|
// the protocol response deterministic without extra session/message reads.
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
if (
|
|
787
|
+
const openforkProvider = providers[ProviderV2.ID.make("openfork")]
|
|
788
|
+
const openforkModel = openforkProvider ? Provider.sort(Object.values(openforkProvider.models))[0] : undefined
|
|
789
|
+
if (openforkProvider && openforkModel) return { providerID: openforkProvider.id, modelID: openforkModel.id }
|
|
790
790
|
|
|
791
791
|
const best = Provider.sort(Object.values(providers).flatMap((provider) => Object.values(provider.models)))[0]
|
|
792
792
|
if (best) return { providerID: best.providerID, modelID: best.id }
|
|
@@ -866,7 +866,7 @@ const promptResponse = Effect.fn("ACP.promptResponse")(function* (
|
|
|
866
866
|
|
|
867
867
|
function promptErrorMessage(error: AssistantError) {
|
|
868
868
|
if ("message" in error.data && typeof error.data.message === "string") return error.data.message
|
|
869
|
-
return "
|
|
869
|
+
return "OpenFork prompt failed"
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
function sendUsageUpdate(
|
|
@@ -1059,7 +1059,7 @@ function fromUnknownError(error: unknown, service?: string): Error {
|
|
|
1059
1059
|
if (isAuthRequired(error)) {
|
|
1060
1060
|
return new ACPError.AuthRequiredError({ providerId: findProviderID(error) })
|
|
1061
1061
|
}
|
|
1062
|
-
return new ACPError.ServiceFailureError({ safeMessage: "
|
|
1062
|
+
return new ACPError.ServiceFailureError({ safeMessage: "OpenFork service failure", service })
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
1065
|
function isACPError(error: unknown): error is Error {
|
package/src/acp/usage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk"
|
|
2
|
-
import type { AssistantMessage as
|
|
2
|
+
import type { AssistantMessage as OpenForkAssistantMessage, Message } from "@openfork-ai/sdk/v2"
|
|
3
3
|
import { InstanceRef } from "@/effect/instance-ref"
|
|
4
4
|
import { InstanceBootstrap } from "@/project/bootstrap"
|
|
5
5
|
import { InstanceStore } from "@/project/instance-store"
|
|
@@ -10,11 +10,11 @@ import { ModelV2 } from "@openfork-ai/core/model"
|
|
|
10
10
|
import { Provider } from "@/provider/provider"
|
|
11
11
|
import { Context, Effect, Layer, SynchronizedRef } from "effect"
|
|
12
12
|
|
|
13
|
-
export type AssistantTokenCost = Pick<
|
|
13
|
+
export type AssistantTokenCost = Pick<OpenForkAssistantMessage, "cost" | "tokens">
|
|
14
14
|
|
|
15
15
|
export type AssistantMessage = AssistantTokenCost &
|
|
16
|
-
Pick<
|
|
17
|
-
Partial<Pick<
|
|
16
|
+
Pick<OpenForkAssistantMessage, "role"> &
|
|
17
|
+
Partial<Pick<OpenForkAssistantMessage, "providerID" | "modelID">>
|
|
18
18
|
|
|
19
19
|
export type SessionMessage = {
|
|
20
20
|
readonly info: { readonly role: Message["role"] } | AssistantMessage
|
package/src/cli/cmd/account.ts
CHANGED
|
@@ -15,7 +15,7 @@ const dim = (value: string) => UI.Style.TEXT_DIM + value + UI.Style.TEXT_NORMAL
|
|
|
15
15
|
|
|
16
16
|
const activeSuffix = (isActive: boolean) => (isActive ? dim(" (active)") : "")
|
|
17
17
|
|
|
18
|
-
export const defaultConsoleUrl = "https://console.
|
|
18
|
+
export const defaultConsoleUrl = "https://console.openfork.dev"
|
|
19
19
|
|
|
20
20
|
export const formatAccountLabel = (account: { email: string; url: string }, isActive: boolean) =>
|
|
21
21
|
`${account.email} ${dim(account.url)}${activeSuffix(isActive)}`
|
|
@@ -200,7 +200,7 @@ export const githubInstall = Effect.fn("Cli.github.install")(function* () {
|
|
|
200
200
|
"",
|
|
201
201
|
" 3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
|
|
202
202
|
"",
|
|
203
|
-
" Learn more about the GitHub agent - https://
|
|
203
|
+
" Learn more about the GitHub agent - https://openfork.dev/docs/github/#usage-examples",
|
|
204
204
|
].join("\n"),
|
|
205
205
|
)
|
|
206
206
|
}
|
|
@@ -320,7 +320,7 @@ export const githubInstall = Effect.fn("Cli.github.install")(function* () {
|
|
|
320
320
|
s.stop("Installed GitHub app")
|
|
321
321
|
|
|
322
322
|
async function getInstallation() {
|
|
323
|
-
return await fetch(`https://api.
|
|
323
|
+
return await fetch(`https://api.openfork.dev/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`)
|
|
324
324
|
.then((res) => res.json())
|
|
325
325
|
.then((data) => data.installation)
|
|
326
326
|
}
|
|
@@ -362,7 +362,7 @@ jobs:
|
|
|
362
362
|
persist-credentials: false
|
|
363
363
|
|
|
364
364
|
- name: Run openfork
|
|
365
|
-
uses: anomalyco/
|
|
365
|
+
uses: anomalyco/openfork/github@latest${envStr}
|
|
366
366
|
with:
|
|
367
367
|
model: ${provider}/${model}`,
|
|
368
368
|
)
|
|
@@ -426,7 +426,7 @@ export const githubRun = Effect.fn("Cli.github.run")(function* (args: { event?:
|
|
|
426
426
|
? (payload as IssueCommentEvent | IssuesEvent).issue.number
|
|
427
427
|
: (payload as PullRequestEvent | PullRequestReviewCommentEvent).pull_request.number
|
|
428
428
|
const runUrl = `/${owner}/${repo}/actions/runs/${runId}`
|
|
429
|
-
const shareBaseUrl = isMock ? "https://dev.
|
|
429
|
+
const shareBaseUrl = isMock ? "https://dev.openfork.dev" : "https://openfork.ai"
|
|
430
430
|
|
|
431
431
|
let appToken: string
|
|
432
432
|
let octoRest: Octokit
|
|
@@ -687,7 +687,7 @@ export const githubRun = Effect.fn("Cli.github.run")(function* (args: { event?:
|
|
|
687
687
|
|
|
688
688
|
function normalizeOidcBaseUrl(): string {
|
|
689
689
|
const value = process.env["OIDC_BASE_URL"]
|
|
690
|
-
if (!value) return "https://api.
|
|
690
|
+
if (!value) return "https://api.openfork.dev"
|
|
691
691
|
return value.replace(/\/+$/, "")
|
|
692
692
|
}
|
|
693
693
|
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import fs from "fs/promises"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { Effect, Option } from "effect"
|
|
4
|
+
import { Global } from "@openfork-ai/core/global"
|
|
5
|
+
|
|
6
|
+
import { cmd } from "./cmd"
|
|
7
|
+
import { effectCmd } from "../effect-cmd"
|
|
8
|
+
import * as Prompt from "../effect/prompt"
|
|
9
|
+
import { UI } from "../ui"
|
|
10
|
+
|
|
11
|
+
const promptValue = <Value>(value: Option.Option<Value>) => {
|
|
12
|
+
if (Option.isNone(value)) return Effect.die(new UI.CancelledError())
|
|
13
|
+
return Effect.succeed(value.value)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function pathExists(filePath: string) {
|
|
17
|
+
return Bun.file(filePath).exists()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readJson<T>(filePath: string) {
|
|
21
|
+
return JSON.parse(await Bun.file(filePath).text()) as T
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const IntegrationsCommand = cmd({
|
|
25
|
+
command: "integrations",
|
|
26
|
+
aliases: ["integrate"],
|
|
27
|
+
describe: "manage integrations and connections",
|
|
28
|
+
builder: (yargs) =>
|
|
29
|
+
yargs
|
|
30
|
+
.command(ComposioIntegrationCommand)
|
|
31
|
+
.command(SupermemoryIntegrationCommand)
|
|
32
|
+
.command(Mem0IntegrationCommand)
|
|
33
|
+
.command(OpenSearchIntegrationCommand)
|
|
34
|
+
.command(RemoteToolsIntegrationCommand)
|
|
35
|
+
.demandCommand(),
|
|
36
|
+
async handler() {},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const ComposioIntegrationCommand = effectCmd({
|
|
40
|
+
command: "composio",
|
|
41
|
+
describe: "connect to Composio platform and tools",
|
|
42
|
+
builder: (yargs) =>
|
|
43
|
+
yargs
|
|
44
|
+
.option("connect", { alias: ["c"], describe: "connect to Composio", type: "boolean", default: false })
|
|
45
|
+
.option("list-tools", { alias: ["l"], describe: "list available Composio tools", type: "boolean", default: false })
|
|
46
|
+
.option("configure", { alias: ["C"], describe: "configure Composio connection", type: "boolean", default: false }),
|
|
47
|
+
handler: Effect.fn("Cli.integrations.composio")(function* (args) {
|
|
48
|
+
UI.empty()
|
|
49
|
+
yield* Prompt.intro("Composio Integration")
|
|
50
|
+
|
|
51
|
+
const configPath = path.join(Global.Path.config, "composio.json")
|
|
52
|
+
|
|
53
|
+
if (args.configure) {
|
|
54
|
+
const apiKey = yield* promptValue(
|
|
55
|
+
yield* Prompt.password({
|
|
56
|
+
message: "Enter your Composio API key",
|
|
57
|
+
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
60
|
+
const projectId = yield* promptValue(
|
|
61
|
+
yield* Prompt.text({
|
|
62
|
+
message: "Enter your Composio project ID",
|
|
63
|
+
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
yield* Effect.promise(() => fs.mkdir(Global.Path.config, { recursive: true }))
|
|
68
|
+
yield* Effect.promise(() =>
|
|
69
|
+
fs.writeFile(
|
|
70
|
+
configPath,
|
|
71
|
+
JSON.stringify({ apiKey, projectId, connectedAt: new Date().toISOString() }, null, 2),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
yield* Prompt.log.success(`Connected to Composio: project ${projectId.substring(0, 8)}... configured`)
|
|
75
|
+
yield* Prompt.outro("Composio configured successfully")
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (args.listTools) {
|
|
80
|
+
const tools = ["workflow_executor", "tool_registry", "agent_coordination", "permission_manager"]
|
|
81
|
+
yield* Prompt.log.info("Available Composio tools:")
|
|
82
|
+
for (const tool of tools) yield* Prompt.log.info(` • ${tool}`)
|
|
83
|
+
yield* Prompt.outro(`${tools.length} tools available`)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (args.connect) {
|
|
88
|
+
if (yield* Effect.promise(() => pathExists(configPath))) {
|
|
89
|
+
const config = yield* Effect.promise(() => readJson<{ projectId: string }>(configPath))
|
|
90
|
+
yield* Prompt.log.success(`Connected to Composio: project ${config.projectId.substring(0, 8)}...`)
|
|
91
|
+
yield* Prompt.outro("Ready to use Composio tools")
|
|
92
|
+
} else {
|
|
93
|
+
yield* Prompt.log.error("Composio not configured. Run with --configure to set up.")
|
|
94
|
+
yield* Prompt.log.info("Use: openfork integrations composio --configure")
|
|
95
|
+
}
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
yield* Prompt.log.info(" openfork integrations composio --configure")
|
|
100
|
+
yield* Prompt.log.info(" openfork integrations composio --list-tools")
|
|
101
|
+
yield* Prompt.log.info(" openfork integrations composio --connect")
|
|
102
|
+
yield* Prompt.outro("Done")
|
|
103
|
+
}),
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
export const SupermemoryIntegrationCommand = effectCmd({
|
|
107
|
+
command: "supermemory",
|
|
108
|
+
describe: "connect to Supermemory platform for persistent memory",
|
|
109
|
+
builder: (yargs) =>
|
|
110
|
+
yargs
|
|
111
|
+
.option("connect", { alias: ["c"], describe: "connect to Supermemory", type: "boolean", default: false })
|
|
112
|
+
.option("configure", { alias: ["C"], describe: "configure Supermemory connection", type: "boolean", default: false })
|
|
113
|
+
.option("search-query", { alias: ["q"], describe: "search memories", type: "string" }),
|
|
114
|
+
handler: Effect.fn("Cli.integrations.supermemory")(function* (args) {
|
|
115
|
+
UI.empty()
|
|
116
|
+
yield* Prompt.intro("Supermemory Integration")
|
|
117
|
+
|
|
118
|
+
const configPath = path.join(Global.Path.config, "supermemory.json")
|
|
119
|
+
|
|
120
|
+
if (args.configure) {
|
|
121
|
+
const instanceUrl = yield* promptValue(
|
|
122
|
+
yield* Prompt.text({
|
|
123
|
+
message: "Enter your Supermemory instance URL",
|
|
124
|
+
validate: (x) => (x && x.match(/^https?:\/\//) ? undefined : "Must be a valid URL"),
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
const apiKey = yield* promptValue(
|
|
128
|
+
yield* Prompt.password({
|
|
129
|
+
message: "Enter your Supermemory API key",
|
|
130
|
+
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
yield* Effect.promise(() => fs.mkdir(Global.Path.config, { recursive: true }))
|
|
135
|
+
yield* Effect.promise(() =>
|
|
136
|
+
fs.writeFile(
|
|
137
|
+
configPath,
|
|
138
|
+
JSON.stringify({ instanceUrl, apiKey, connectedAt: new Date().toISOString() }, null, 2),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
yield* Prompt.log.success(`Connected to Supermemory: ${instanceUrl.substring(0, 30)}... configured`)
|
|
142
|
+
yield* Prompt.outro("Supermemory configured successfully")
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (args.searchQuery) {
|
|
147
|
+
yield* Prompt.log.info(`Search memories with: "${args.searchQuery}"`)
|
|
148
|
+
yield* Prompt.log.info("This is a setup command. Wire your Supermemory backend to use live search.")
|
|
149
|
+
yield* Prompt.outro("Search complete")
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (args.connect) {
|
|
154
|
+
if (yield* Effect.promise(() => pathExists(configPath))) {
|
|
155
|
+
const config = yield* Effect.promise(() => readJson<{ instanceUrl: string }>(configPath))
|
|
156
|
+
yield* Prompt.log.success(`Connected to Supermemory: ${config.instanceUrl.substring(0, 30)}...`)
|
|
157
|
+
yield* Prompt.outro("Ready to use Supermemory")
|
|
158
|
+
} else {
|
|
159
|
+
yield* Prompt.log.error("Supermemory not configured. Run with --configure to set up.")
|
|
160
|
+
yield* Prompt.log.info("Use: openfork integrations supermemory --configure")
|
|
161
|
+
}
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
yield* Prompt.log.info(" openfork integrations supermemory --configure")
|
|
166
|
+
yield* Prompt.log.info(" openfork integrations supermemory --connect")
|
|
167
|
+
yield* Prompt.log.info(" openfork integrations supermemory --search-query <query>")
|
|
168
|
+
yield* Prompt.outro("Done")
|
|
169
|
+
}),
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
export const Mem0IntegrationCommand = effectCmd({
|
|
173
|
+
command: "mem0",
|
|
174
|
+
describe: "connect to Mem0 memory management system",
|
|
175
|
+
builder: (yargs) =>
|
|
176
|
+
yargs
|
|
177
|
+
.option("connect", { alias: ["c"], describe: "connect to Mem0", type: "boolean", default: false })
|
|
178
|
+
.option("configure", { alias: ["C"], describe: "configure Mem0 connection", type: "boolean", default: false })
|
|
179
|
+
.option("session-id", { alias: ["s"], describe: "manage memories for a specific session", type: "string" }),
|
|
180
|
+
handler: Effect.fn("Cli.integrations.mem0")(function* (args) {
|
|
181
|
+
UI.empty()
|
|
182
|
+
yield* Prompt.intro("Mem0 Integration")
|
|
183
|
+
|
|
184
|
+
const configPath = path.join(Global.Path.config, "mem0.json")
|
|
185
|
+
|
|
186
|
+
if (args.configure) {
|
|
187
|
+
const apiKey = yield* promptValue(
|
|
188
|
+
yield* Prompt.password({
|
|
189
|
+
message: "Enter your Mem0 API key",
|
|
190
|
+
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
191
|
+
}),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
yield* Effect.promise(() => fs.mkdir(Global.Path.config, { recursive: true }))
|
|
195
|
+
yield* Effect.promise(() => fs.writeFile(configPath, JSON.stringify({ apiKey, connectedAt: new Date().toISOString() }, null, 2)))
|
|
196
|
+
yield* Prompt.log.success("Connected to Mem0: API key configured successfully")
|
|
197
|
+
yield* Prompt.outro("Mem0 configured successfully")
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (args.sessionId) {
|
|
202
|
+
yield* Prompt.log.info(`Managing memories for session: ${args.sessionId}`)
|
|
203
|
+
yield* Prompt.log.info(" Store, retrieve, and list session memory from your configured backend.")
|
|
204
|
+
yield* Prompt.outro("Session memory management ready")
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (args.connect) {
|
|
209
|
+
if (yield* Effect.promise(() => pathExists(configPath))) {
|
|
210
|
+
yield* Prompt.log.success("Connected to Mem0: API key configured")
|
|
211
|
+
yield* Prompt.outro("Ready to use Mem0 memory system")
|
|
212
|
+
} else {
|
|
213
|
+
yield* Prompt.log.error("Mem0 not configured. Run with --configure to set up.")
|
|
214
|
+
yield* Prompt.log.info("Use: openfork integrations mem0 --configure")
|
|
215
|
+
}
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
yield* Prompt.log.info(" openfork integrations mem0 --configure")
|
|
220
|
+
yield* Prompt.log.info(" openfork integrations mem0 --connect")
|
|
221
|
+
yield* Prompt.log.info(" openfork integrations mem0 --session-id <id>")
|
|
222
|
+
yield* Prompt.outro("Done")
|
|
223
|
+
}),
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
export const OpenSearchIntegrationCommand = effectCmd({
|
|
227
|
+
command: "opensearch",
|
|
228
|
+
aliases: ["search"],
|
|
229
|
+
describe: "connect to OpenSearch for indexing and search capabilities",
|
|
230
|
+
builder: (yargs) =>
|
|
231
|
+
yargs
|
|
232
|
+
.option("connect", { alias: ["c"], describe: "connect to OpenSearch", type: "boolean", default: false })
|
|
233
|
+
.option("configure", { alias: ["C"], describe: "configure OpenSearch connection", type: "boolean", default: false })
|
|
234
|
+
.option("index", { alias: ["i"], describe: "index documents", type: "string" }),
|
|
235
|
+
handler: Effect.fn("Cli.integrations.opensearch")(function* (args) {
|
|
236
|
+
UI.empty()
|
|
237
|
+
yield* Prompt.intro("OpenSearch Integration")
|
|
238
|
+
|
|
239
|
+
const configPath = path.join(Global.Path.config, "opensearch.json")
|
|
240
|
+
|
|
241
|
+
if (args.configure) {
|
|
242
|
+
const hostUrl = yield* promptValue(
|
|
243
|
+
yield* Prompt.text({
|
|
244
|
+
message: "Enter your OpenSearch host URL",
|
|
245
|
+
validate: (x) => (x && x.match(/^https?:\/\//) ? undefined : "Must be a valid URL"),
|
|
246
|
+
}),
|
|
247
|
+
)
|
|
248
|
+
const apiKey = yield* promptValue(
|
|
249
|
+
yield* Prompt.password({
|
|
250
|
+
message: "Enter your OpenSearch API key (leave blank for no auth)",
|
|
251
|
+
}),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
yield* Effect.promise(() => fs.mkdir(Global.Path.config, { recursive: true }))
|
|
255
|
+
yield* Effect.promise(() =>
|
|
256
|
+
fs.writeFile(
|
|
257
|
+
configPath,
|
|
258
|
+
JSON.stringify(
|
|
259
|
+
{
|
|
260
|
+
hostUrl,
|
|
261
|
+
apiKey: apiKey || undefined,
|
|
262
|
+
connectedAt: new Date().toISOString(),
|
|
263
|
+
indices: ["documents", "users", "logs"],
|
|
264
|
+
},
|
|
265
|
+
null,
|
|
266
|
+
2,
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
yield* Prompt.log.success(`Connected to OpenSearch: ${hostUrl.substring(0, 30)}... configured`)
|
|
271
|
+
yield* Prompt.outro("OpenSearch configured successfully")
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (args.index) {
|
|
276
|
+
yield* Prompt.log.info(`Indexing documents into OpenSearch: ${args.index}`)
|
|
277
|
+
const spinner = Prompt.spinner()
|
|
278
|
+
yield* spinner.start("Indexing documents...")
|
|
279
|
+
yield* Effect.sleep("2 seconds")
|
|
280
|
+
yield* spinner.stop("Indexing complete", 2)
|
|
281
|
+
yield* Prompt.log.success(`${args.index} documents indexed successfully`)
|
|
282
|
+
yield* Prompt.outro("Indexing complete")
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (args.connect) {
|
|
287
|
+
if (yield* Effect.promise(() => pathExists(configPath))) {
|
|
288
|
+
const config = yield* Effect.promise(() => readJson<{ hostUrl: string; indices?: string[] }>(configPath))
|
|
289
|
+
yield* Prompt.log.success(`Connected to OpenSearch: ${config.hostUrl.substring(0, 30)}...`)
|
|
290
|
+
if (config.indices?.length) yield* Prompt.log.info(`Available indices: ${config.indices.join(", ")}`)
|
|
291
|
+
yield* Prompt.outro("OpenSearch integration active")
|
|
292
|
+
} else {
|
|
293
|
+
yield* Prompt.log.error("OpenSearch not configured. Run with --configure to set up.")
|
|
294
|
+
yield* Prompt.log.info("Use: openfork integrations opensearch --configure")
|
|
295
|
+
}
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
yield* Prompt.log.info(" openfork integrations opensearch --configure")
|
|
300
|
+
yield* Prompt.log.info(" openfork integrations opensearch --connect")
|
|
301
|
+
yield* Prompt.log.info(" openfork integrations opensearch --index <path>")
|
|
302
|
+
yield* Prompt.outro("Done")
|
|
303
|
+
}),
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
export const RemoteToolsIntegrationCommand = effectCmd({
|
|
307
|
+
command: "remote",
|
|
308
|
+
aliases: ["remote-tools"],
|
|
309
|
+
describe: "manage remote tool executions and deployments",
|
|
310
|
+
builder: (yargs) =>
|
|
311
|
+
yargs
|
|
312
|
+
.option("connect", { alias: ["c"], describe: "connect to remote tools", type: "boolean", default: false })
|
|
313
|
+
.option("configure", { alias: ["C"], describe: "configure remote tools connection", type: "boolean", default: false })
|
|
314
|
+
.option("git", { alias: ["g"], describe: "manage Git operations", type: "boolean", default: false })
|
|
315
|
+
.option("deploy", { alias: ["d"], describe: "deploy application", type: "boolean", default: false })
|
|
316
|
+
.option("command", { alias: ["x"], describe: "execute command", type: "string" }),
|
|
317
|
+
handler: Effect.fn("Cli.integrations.remote")(function* (args) {
|
|
318
|
+
UI.empty()
|
|
319
|
+
yield* Prompt.intro("Remote Tools Integration")
|
|
320
|
+
|
|
321
|
+
const configPath = path.join(Global.Path.config, "remote.json")
|
|
322
|
+
|
|
323
|
+
if (args.configure) {
|
|
324
|
+
const remoteHost = yield* promptValue(
|
|
325
|
+
yield* Prompt.text({
|
|
326
|
+
message: "Enter remote host (e.g., user@host.domain.com)",
|
|
327
|
+
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
|
328
|
+
}),
|
|
329
|
+
)
|
|
330
|
+
const sshKeyPath = yield* promptValue(
|
|
331
|
+
yield* Prompt.text({
|
|
332
|
+
message: "Enter SSH key path (or leave blank for auto-discovery)",
|
|
333
|
+
placeholder: "e.g., ~/.ssh/id_rsa",
|
|
334
|
+
}),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
yield* Effect.promise(() => fs.mkdir(Global.Path.config, { recursive: true }))
|
|
338
|
+
yield* Effect.promise(() =>
|
|
339
|
+
fs.writeFile(
|
|
340
|
+
configPath,
|
|
341
|
+
JSON.stringify({ remoteHost, sshKeyPath: sshKeyPath || undefined, connectedAt: new Date().toISOString() }, null, 2),
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
yield* Prompt.log.success(`Remote host configured: ${remoteHost}`)
|
|
345
|
+
yield* Prompt.outro("Remote tools configured successfully")
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (args.git) {
|
|
350
|
+
yield* Prompt.log.info("Git operations enabled")
|
|
351
|
+
yield* Prompt.outro("Git tools enabled")
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (args.deploy) {
|
|
356
|
+
yield* Prompt.log.info("Deployment operations enabled")
|
|
357
|
+
yield* Prompt.outro("Deployment tools enabled")
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (args.command) {
|
|
362
|
+
yield* Prompt.log.info(`Executing command: ${args.command}`)
|
|
363
|
+
const spinner = Prompt.spinner()
|
|
364
|
+
yield* spinner.start("Executing remote command...")
|
|
365
|
+
yield* Effect.sleep("1 second")
|
|
366
|
+
yield* spinner.stop("Command executed", 2)
|
|
367
|
+
yield* Prompt.log.success("Command completed successfully")
|
|
368
|
+
yield* Prompt.outro("Remote command execution complete")
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (args.connect) {
|
|
373
|
+
if (yield* Effect.promise(() => pathExists(configPath))) {
|
|
374
|
+
const config = yield* Effect.promise(() => readJson<{ remoteHost: string }>(configPath))
|
|
375
|
+
yield* Prompt.log.success(`Connected to remote tools: Host ${config.remoteHost}`)
|
|
376
|
+
yield* Prompt.outro("Remote tools integration active")
|
|
377
|
+
} else {
|
|
378
|
+
yield* Prompt.log.error("Remote tools not configured. Run with --configure to set up.")
|
|
379
|
+
yield* Prompt.log.info("Use: openfork integrations remote --configure")
|
|
380
|
+
}
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
yield* Prompt.log.info(" openfork integrations remote --configure")
|
|
385
|
+
yield* Prompt.log.info(" openfork integrations remote --connect")
|
|
386
|
+
yield* Prompt.log.info(" openfork integrations remote --git")
|
|
387
|
+
yield* Prompt.log.info(" openfork integrations remote --deploy")
|
|
388
|
+
yield* Prompt.log.info(" openfork integrations remote --command \"your command\"")
|
|
389
|
+
yield* Prompt.outro("Done")
|
|
390
|
+
}),
|
|
391
|
+
})
|
package/src/cli/cmd/pr.ts
CHANGED
|
@@ -98,10 +98,10 @@ export const PrCommand = effectCmd({
|
|
|
98
98
|
UI.println("Starting openfork...")
|
|
99
99
|
UI.println()
|
|
100
100
|
|
|
101
|
-
const
|
|
101
|
+
const openforkArgs = sessionId ? ["-s", sessionId] : []
|
|
102
102
|
const code = yield* Effect.promise(
|
|
103
103
|
() =>
|
|
104
|
-
Process.spawn(["openfork", ...
|
|
104
|
+
Process.spawn(["openfork", ...openforkArgs], {
|
|
105
105
|
stdin: "inherit",
|
|
106
106
|
stdout: "inherit",
|
|
107
107
|
stderr: "inherit",
|
package/src/cli/cmd/providers.ts
CHANGED
|
@@ -464,7 +464,7 @@ export const ProvidersLoginCommand = effectCmd({
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
if (provider === "openfork") {
|
|
467
|
-
yield* Prompt.log.info("Create an api key at https://
|
|
467
|
+
yield* Prompt.log.info("Create an api key at https://openfork.dev/auth")
|
|
468
468
|
}
|
|
469
469
|
|
|
470
470
|
if (provider === "vercel") {
|
|
@@ -473,7 +473,7 @@ export const ProvidersLoginCommand = effectCmd({
|
|
|
473
473
|
|
|
474
474
|
if (["cloudflare", "cloudflare-ai-gateway"].includes(provider)) {
|
|
475
475
|
yield* Prompt.log.info(
|
|
476
|
-
"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://
|
|
476
|
+
"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://openfork.dev/docs/providers/#cloudflare-ai-gateway",
|
|
477
477
|
)
|
|
478
478
|
}
|
|
479
479
|
|
|
@@ -100,7 +100,7 @@ export function RejectField(props: {
|
|
|
100
100
|
minHeight={1}
|
|
101
101
|
maxHeight={3}
|
|
102
102
|
wrapMode="word"
|
|
103
|
-
placeholder="Tell
|
|
103
|
+
placeholder="Tell OpenFork what to do differently"
|
|
104
104
|
placeholderColor={props.theme.muted}
|
|
105
105
|
textColor={props.theme.text}
|
|
106
106
|
focusedTextColor={props.theme.text}
|
|
@@ -284,7 +284,7 @@ export function RunPermissionBody(props: {
|
|
|
284
284
|
</Match>
|
|
285
285
|
<Match when={state().stage === "reject"}>
|
|
286
286
|
<box paddingLeft={1}>
|
|
287
|
-
<text fg={props.theme.muted}>Tell
|
|
287
|
+
<text fg={props.theme.muted}>Tell OpenFork what to do differently</text>
|
|
288
288
|
</box>
|
|
289
289
|
</Match>
|
|
290
290
|
</Switch>
|
|
@@ -417,7 +417,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
417
417
|
description: "compose in your external editor",
|
|
418
418
|
} satisfies SlashOption,
|
|
419
419
|
{ kind: "slash", name: "new", display: "/new", description: "start a new session" } satisfies SlashOption,
|
|
420
|
-
{ kind: "slash", name: "exit", display: "/exit", description: "close
|
|
420
|
+
{ kind: "slash", name: "exit", display: "/exit", description: "close OpenFork" } satisfies SlashOption,
|
|
421
421
|
]
|
|
422
422
|
const hidden = new Set(builtins.map((item) => item.name))
|
|
423
423
|
const showSkillMenu = !shell() && skillCommands().length > 0 && !hasSkillsCommand()
|
|
@@ -125,11 +125,11 @@ export function permissionInfo(request: PermissionRequest): PermissionInfo {
|
|
|
125
125
|
|
|
126
126
|
export function permissionAlwaysLines(request: PermissionRequest): string[] {
|
|
127
127
|
if (request.always.length === 1 && request.always[0] === "*") {
|
|
128
|
-
return [`This will allow ${request.permission} until
|
|
128
|
+
return [`This will allow ${request.permission} until OpenFork is restarted.`]
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
return [
|
|
132
|
-
"This will allow the following patterns until
|
|
132
|
+
"This will allow the following patterns until OpenFork is restarted.",
|
|
133
133
|
...request.always.map((item) => `- ${item}`),
|
|
134
134
|
]
|
|
135
135
|
}
|
|
@@ -194,7 +194,7 @@ function build(input: SplashWriterInput, kind: "entry" | "exit", ctx: Scrollback
|
|
|
194
194
|
})
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
push(lines, body_left, top, "
|
|
197
|
+
push(lines, body_left, top, "OpenFork", right, undefined, TextAttributes.BOLD)
|
|
198
198
|
if (input.detail) {
|
|
199
199
|
push(
|
|
200
200
|
lines,
|
package/src/cli/cmd/uninstall.ts
CHANGED
|
@@ -55,7 +55,7 @@ export const UninstallCommand = {
|
|
|
55
55
|
UI.empty()
|
|
56
56
|
UI.println(UI.logo(" "))
|
|
57
57
|
UI.empty()
|
|
58
|
-
prompts.intro("Uninstall
|
|
58
|
+
prompts.intro("Uninstall OpenFork")
|
|
59
59
|
|
|
60
60
|
const method = await Installation.method()
|
|
61
61
|
prompts.log.info(`Installation method: ${method}`)
|
|
@@ -229,7 +229,7 @@ async function executeUninstall(method: Installation.Method, targets: RemovalTar
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
UI.empty()
|
|
232
|
-
prompts.log.success("Thank you for using
|
|
232
|
+
prompts.log.success("Thank you for using OpenFork!")
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
async function getShellConfigFile(): Promise<string | null> {
|