@fungi.computer/truffle 0.1.1 → 0.2.0
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 +282 -34
- package/dist/auth-cli.d.ts +10 -0
- package/dist/auth-cli.js +36 -14
- package/dist/fungi-account-command.d.ts +1 -1
- package/dist/fungi-account-command.js +6 -3
- package/dist/index.d.ts +10 -2
- package/dist/index.js +8 -2
- package/dist/internal/image-fit-metafile.json +106 -0
- package/dist/internal/local-image-fit-worker.mjs +960 -0
- package/dist/internal/private-workerd-sandbox-worker.mjs +11 -12
- package/dist/internal/sandbox-metafile.json +7 -7
- package/dist/local-archive-handoff.d.ts +12 -0
- package/dist/local-archive-handoff.js +21 -0
- package/dist/local-extensions.d.ts +58 -12
- package/dist/local-extensions.js +195 -55
- package/dist/local-image-fit.d.ts +30 -0
- package/dist/local-image-fit.js +105 -0
- package/dist/local-models.d.ts +4 -2
- package/dist/local-models.js +11 -6
- package/dist/local-session-archives.d.ts +8 -0
- package/dist/local-session-archives.js +152 -0
- package/dist/local-skill-source.d.ts +18 -3
- package/dist/local-skill-source.js +216 -140
- package/dist/log-event.d.ts +7 -0
- package/dist/log-event.js +8 -4
- package/dist/private-bounded-response.d.ts +2 -0
- package/dist/private-bounded-response.js +41 -0
- package/dist/private-file-credential-store.d.ts +28 -3
- package/dist/private-file-credential-store.js +57 -9
- package/dist/private-files-patch.d.ts +2 -1
- package/dist/private-files-patch.js +21 -3
- package/dist/private-fungi-account.d.ts +40 -23
- package/dist/private-fungi-account.js +201 -21
- package/dist/private-fungi-mcp-connection.d.ts +4 -0
- package/dist/private-fungi-mcp-connection.js +25 -6
- package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
- package/dist/private-fungi-mcp-selection-schema.js +15 -0
- package/dist/private-fungi-mcp-selection.d.ts +6 -6
- package/dist/private-fungi-mcp-selection.js +29 -16
- package/dist/private-fungi-model-api.js +4 -4
- package/dist/private-fungi-model-catalog.d.ts +10 -0
- package/dist/private-fungi-model-catalog.js +10 -2
- package/dist/private-fungi-model-provider.d.ts +1 -1
- package/dist/private-fungi-model-provider.js +6 -1
- package/dist/private-fungi-pending-logouts.d.ts +1 -1
- package/dist/private-fungi-pending-logouts.js +1 -2
- package/dist/private-fungi-service-logout.d.ts +44 -0
- package/dist/private-fungi-service-logout.js +59 -0
- package/dist/private-invocation-computer.d.ts +24 -3
- package/dist/private-invocation-computer.js +101 -62
- package/dist/private-invocation-files.d.ts +16 -1
- package/dist/private-invocation-files.js +154 -83
- package/dist/private-local-acp-stdio.js +3 -0
- package/dist/private-local-admission-gate.d.ts +14 -0
- package/dist/private-local-admission-gate.js +52 -0
- package/dist/private-local-agent-control.d.ts +101 -0
- package/dist/private-local-agent-control.js +30 -0
- package/dist/private-local-agent-default-model.d.ts +12 -0
- package/dist/private-local-agent-default-model.js +31 -0
- package/dist/private-local-auth-rpc.d.ts +67 -7
- package/dist/private-local-auth-rpc.js +140 -17
- package/dist/private-local-bwrap.d.ts +13 -0
- package/dist/private-local-bwrap.js +70 -0
- package/dist/private-local-client-version.d.ts +5 -0
- package/dist/private-local-client-version.js +43 -0
- package/dist/private-local-computer-attach.d.ts +3 -3
- package/dist/private-local-computer-attach.js +17 -3
- package/dist/private-local-computer-processes.d.ts +6 -3
- package/dist/private-local-computer-processes.js +55 -31
- package/dist/private-local-config-root.d.ts +21 -0
- package/dist/private-local-config-root.js +19 -0
- package/dist/private-local-containment.d.ts +51 -0
- package/dist/private-local-containment.js +161 -0
- package/dist/private-local-delegation.d.ts +10 -3
- package/dist/private-local-delegation.js +70 -15
- package/dist/private-local-effective-model.d.ts +9 -1
- package/dist/private-local-effective-model.js +3 -0
- package/dist/private-local-exec-environment.d.ts +16 -0
- package/dist/private-local-exec-environment.js +11 -0
- package/dist/private-local-linux-sandbox.d.ts +8 -0
- package/dist/private-local-linux-sandbox.js +67 -0
- package/dist/private-local-mail.d.ts +4 -2
- package/dist/private-local-mail.js +41 -33
- package/dist/private-local-mesh-enrollment.d.ts +34 -0
- package/dist/private-local-mesh-enrollment.js +188 -0
- package/dist/private-local-mesh-links.d.ts +21 -0
- package/dist/private-local-mesh-links.js +229 -0
- package/dist/private-local-mesh-record.d.ts +120 -0
- package/dist/private-local-mesh-record.js +55 -0
- package/dist/private-local-process-identity.d.ts +20 -0
- package/dist/private-local-process-identity.js +40 -0
- package/dist/private-local-runtime-host.d.ts +64 -13
- package/dist/private-local-runtime-host.js +361 -172
- package/dist/private-local-sandbox-policy.d.ts +21 -0
- package/dist/private-local-sandbox-policy.js +121 -0
- package/dist/private-local-sandbox.d.ts +37 -0
- package/dist/private-local-sandbox.js +114 -0
- package/dist/private-local-seatbelt.d.ts +24 -0
- package/dist/private-local-seatbelt.js +68 -0
- package/dist/private-local-service-contract.d.ts +91 -11
- package/dist/private-local-service-discovery.d.ts +129 -13
- package/dist/private-local-service-discovery.js +148 -47
- package/dist/private-local-service-lease.d.ts +16 -0
- package/dist/private-local-service-lease.js +105 -0
- package/dist/private-local-service-listener.d.ts +9 -2
- package/dist/private-local-service-listener.js +260 -64
- package/dist/private-local-service-log.d.ts +128 -0
- package/dist/private-local-service-log.js +321 -0
- package/dist/private-local-service-owner.d.ts +87 -1519
- package/dist/private-local-service-owner.js +1069 -71
- package/dist/private-local-service-process.js +3 -1
- package/dist/private-local-service-termination.d.ts +7 -0
- package/dist/private-local-service-termination.js +67 -0
- package/dist/private-local-service.d.ts +31 -2
- package/dist/private-local-service.js +402 -126
- package/dist/private-local-session-discovery.d.ts +52 -5
- package/dist/private-local-session-discovery.js +132 -11
- package/dist/private-local-shiitake-host.js +2 -2
- package/dist/private-local-startup-model.d.ts +51 -0
- package/dist/private-local-startup-model.js +86 -0
- package/dist/private-local-stop-all.d.ts +27 -0
- package/dist/private-local-stop-all.js +26 -0
- package/dist/private-local-ui-attachment.d.ts +19 -0
- package/dist/private-local-ui-attachment.js +71 -0
- package/dist/private-local-worker-server.d.ts +1 -0
- package/dist/private-local-worker-server.js +13 -5
- package/dist/private-mcp-form-contract.d.ts +186 -0
- package/dist/private-mcp-form-contract.js +53 -0
- package/dist/private-mcp-form-elicitation.d.ts +18 -0
- package/dist/private-mcp-form-elicitation.js +97 -0
- package/dist/private-mcp-form-session.d.ts +172 -0
- package/dist/private-mcp-form-session.js +148 -0
- package/dist/private-openai-claims.d.ts +30 -0
- package/dist/private-openai-claims.js +69 -0
- package/dist/private-oyster-mcp-auth.d.ts +13 -0
- package/dist/private-oyster-mcp-auth.js +57 -0
- package/dist/private-provider-rejection.d.ts +15 -0
- package/dist/private-provider-rejection.js +24 -0
- package/dist/private-sqlite-owner.d.ts +34 -2
- package/dist/private-sqlite-owner.js +103 -37
- package/dist/private-team-computers.d.ts +4 -4
- package/dist/private-team-computers.js +11 -2
- package/dist/seatbelt/safe-base.sbpl +79 -0
- package/dist/subscription-activation.d.ts +9 -0
- package/dist/subscription-activation.js +62 -0
- package/dist/subscription-identity.d.ts +23 -0
- package/dist/subscription-identity.js +124 -0
- package/dist/subscription-turn.d.ts +17 -0
- package/dist/subscription-turn.js +143 -0
- package/dist/team-apps.d.ts +2 -2
- package/dist/team-apps.js +7 -2
- package/native/bin/LICENSE.bubblewrap +502 -0
- package/native/bin/LICENSE.libcap +112 -0
- package/native/bin/LICENSE.sandbox-runtime +201 -0
- package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
- package/native/bin/bwrap-build-source/Dockerfile +48 -0
- package/native/bin/bwrap-build-source/README.md +96 -0
- package/native/bin/bwrap-build-source/build.mjs +52 -0
- package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
- package/native/bin/bwrap-build-source/pin.mjs +21 -0
- package/native/bin/bwrap-build-source/record.mjs +74 -0
- package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
- package/native/bin/bwrap-build-source/vendor/README.md +23 -0
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
- package/native/bin/bwrap-build.json +44 -0
- package/native/bin/fungi-bwrap +0 -0
- package/native/bin/fungi-bwrap.sha256 +1 -0
- package/native/bin/fungi-seccomp.bpf +0 -0
- package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
- package/package.json +44 -31
- package/dist/private-local-model-settings.d.ts +0 -5
- package/dist/private-local-model-settings.js +0 -31
- package/dist/private-local-subagents.d.ts +0 -27
- package/dist/private-local-subagents.js +0 -77
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { FilesPatch, FilesPort } from "@fungi.computer/knapsack";
|
|
2
|
+
import { type LocalSandboxLaunch } from "./private-invocation-computer.js";
|
|
2
3
|
/** Advertise only a packaged native engine; never install tools during a model call. */
|
|
3
|
-
export declare function createPrivateFilesPatch(root: string, read: FilesPort["read"]): Promise<FilesPatch | undefined>;
|
|
4
|
+
export declare function createPrivateFilesPatch(root: string, read: FilesPort["read"], sandbox?: LocalSandboxLaunch): Promise<FilesPatch | undefined>;
|
|
@@ -5,7 +5,7 @@ import process from "node:process";
|
|
|
5
5
|
import * as ChildProcess from "effect/process/ChildProcess";
|
|
6
6
|
import { parsePatch } from "diff";
|
|
7
7
|
import * as Stream from "effect/Stream";
|
|
8
|
-
import { runPrivateInvocationProcess } from "./private-invocation-computer.js";
|
|
8
|
+
import { runPrivateInvocationProcess, } from "./private-invocation-computer.js";
|
|
9
9
|
function patchExecutable() {
|
|
10
10
|
if (process.env.FUNGI_BUNDLED_CLI) {
|
|
11
11
|
const name = `@fungi.computer/spore-native-${process.platform}-${process.arch}`;
|
|
@@ -33,6 +33,8 @@ async function unifiedDiffContext(patch, read, context) {
|
|
|
33
33
|
if (path === undefined || hunk === undefined || path === "/dev/null")
|
|
34
34
|
return "\nInclude *** Update File: path/to/file before @@ to identify the target.\n";
|
|
35
35
|
const current = await read({ path, offset: 0, limit: maximumPatchBytes }, context);
|
|
36
|
+
if ("image" in current)
|
|
37
|
+
throw new Error(`${path} is an image, not text`);
|
|
36
38
|
const lines = current.content.split(/\r?\n/u);
|
|
37
39
|
const from = Math.min(Math.max(0, hunk.oldStart - 3), Math.max(0, lines.length - 7));
|
|
38
40
|
const nearby = lines
|
|
@@ -46,7 +48,7 @@ async function unifiedDiffContext(patch, read, context) {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
/** Advertise only a packaged native engine; never install tools during a model call. */
|
|
49
|
-
export async function createPrivateFilesPatch(root, read) {
|
|
51
|
+
export async function createPrivateFilesPatch(root, read, sandbox) {
|
|
50
52
|
const executable = patchExecutable();
|
|
51
53
|
try {
|
|
52
54
|
await access(executable, constants.X_OK);
|
|
@@ -61,9 +63,25 @@ export async function createPrivateFilesPatch(root, read) {
|
|
|
61
63
|
const bytes = new TextEncoder().encode(patch);
|
|
62
64
|
if (bytes.byteLength > maximumPatchBytes)
|
|
63
65
|
throw new Error("Patch exceeds the 128 KiB input bound");
|
|
64
|
-
const
|
|
66
|
+
const launch = sandbox?.(root, [executable]);
|
|
67
|
+
const env = { ...process.env, ...launch?.environment };
|
|
68
|
+
if (launch !== undefined) {
|
|
69
|
+
delete env.XDG_RUNTIME_DIR;
|
|
70
|
+
delete env.DBUS_SESSION_BUS_ADDRESS;
|
|
71
|
+
}
|
|
72
|
+
const command = ChildProcess.make(launch?.executable ?? executable, launch?.args ?? [], {
|
|
65
73
|
cwd: root,
|
|
74
|
+
env,
|
|
75
|
+
extendEnv: false,
|
|
66
76
|
stdin: Stream.make(bytes),
|
|
77
|
+
additionalFds: launch?.seccompFilter === undefined
|
|
78
|
+
? undefined
|
|
79
|
+
: {
|
|
80
|
+
fd3: {
|
|
81
|
+
type: "input",
|
|
82
|
+
stream: Stream.make(launch.seccompFilter),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
67
85
|
});
|
|
68
86
|
const result = await runPrivateInvocationProcess(command, context.signal, 30_000);
|
|
69
87
|
if (result.status === "deadline_exceeded")
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type AuthProvider, type FetchLike, type OAuthClientProvider, type StoredOAuthClientInformation } from "@modelcontextprotocol/client";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { Credential, CredentialStore, OAuthAuth } from "@earendil-works/pi-ai";
|
|
4
|
+
import { type LocalMachineEnrollmentAccount } from "./private-local-mesh-record.js";
|
|
5
|
+
import { type FungiAccountLogoutResult } from "./private-fungi-service-logout.js";
|
|
4
6
|
/**
|
|
5
7
|
* One folder attached to a Team Computer. The absolute folder path and the
|
|
6
8
|
* device's private key never leave this file; the Hub holds only the public
|
|
@@ -35,10 +37,18 @@ export type PrivateFungiAccountOAuthProviderOptions = {
|
|
|
35
37
|
redirectUrl?: string;
|
|
36
38
|
openBrowser?: (url: URL) => void;
|
|
37
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Enrollment uses the account file and lock, without Pi's selected-payer view.
|
|
42
|
+
* Fungi enrollments are partitioned by issuer in the origin-keyed enrollment
|
|
43
|
+
* records: only the signed-in issuer's partition is ever read for dialing, so
|
|
44
|
+
* a switch never lends one issuer's Team or device identifiers to another,
|
|
45
|
+
* and signing in to an issuer again finds its own partition.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createLocalMachineEnrollmentAccount(credentialPath: string): LocalMachineEnrollmentAccount;
|
|
38
48
|
/** Hand a Fungi sign-in page to the platform browser; callers show the URL. */
|
|
39
49
|
export declare function launchPlatformBrowser(url: URL): void;
|
|
40
50
|
/** Persisted OAuth state uses Local's existing owner-only Pi credential file. */
|
|
41
|
-
export declare function privateFungiAccountOAuthProvider(
|
|
51
|
+
export declare function privateFungiAccountOAuthProvider(input?: PrivateFungiAccountOAuthProviderOptions): OAuthClientProvider & {
|
|
42
52
|
saveClientInformation(information: StoredOAuthClientInformation): Promise<void>;
|
|
43
53
|
expectedState(): string | undefined;
|
|
44
54
|
authorizationUrl(): URL | undefined;
|
|
@@ -58,14 +68,13 @@ export type PrivateFungiAccountLoginOptions = {
|
|
|
58
68
|
/** Opens the Hub pages; the default launches the platform browser. */
|
|
59
69
|
openBrowser?: (url: URL) => void;
|
|
60
70
|
};
|
|
61
|
-
export type PrivateFungiAccountLogoutResult =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
name: string;
|
|
66
|
-
url: string;
|
|
67
|
-
}>[];
|
|
71
|
+
export type PrivateFungiAccountLogoutResult = FungiAccountLogoutResult;
|
|
72
|
+
/** An in-process account change the service owner reacts to. */
|
|
73
|
+
export type FungiAccountChange = Readonly<{
|
|
74
|
+
type: "signed-out";
|
|
68
75
|
}>;
|
|
76
|
+
/** Installed only by the service holding this home's owner lease. */
|
|
77
|
+
export declare function registerFungiAccountOwner(credentialPath: string, changed: (event: FungiAccountChange) => Promise<void>): () => void;
|
|
69
78
|
export type FungiAccountStatus = {
|
|
70
79
|
issuer: string;
|
|
71
80
|
accessExpiresAt: Date;
|
|
@@ -73,14 +82,22 @@ export type FungiAccountStatus = {
|
|
|
73
82
|
teamCount?: number;
|
|
74
83
|
};
|
|
75
84
|
/** Non-secret status of the shared Fungi account. */
|
|
76
|
-
export declare function readFungiAccountStatus(
|
|
85
|
+
export declare function readFungiAccountStatus(input?: {
|
|
77
86
|
credentialPath?: string;
|
|
78
87
|
}): Promise<FungiAccountStatus | undefined>;
|
|
79
88
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
89
|
+
* Ask the running Local account owner to remove this origin's login and revoke
|
|
90
|
+
* its refresh grant. Without a service, clear offline; never start one for
|
|
91
|
+
* logout. A login for another origin is kept. Explicit standalone stores have
|
|
92
|
+
* no service home; clients with a custom service directory name it explicitly.
|
|
82
93
|
*/
|
|
83
|
-
export declare function signOutFungiAccount(rawEndpoint?: string | URL,
|
|
94
|
+
export declare function signOutFungiAccount(rawEndpoint?: string | URL, input?: {
|
|
95
|
+
credentialPath?: string;
|
|
96
|
+
serviceDirectory?: string;
|
|
97
|
+
fetch?: FetchLike;
|
|
98
|
+
}): Promise<PrivateFungiAccountLogoutResult>;
|
|
99
|
+
/** Store mutation for the service owner or a verified no-service caller. */
|
|
100
|
+
export declare function signOutOwnedFungiAccount(rawEndpoint?: string | URL, input?: {
|
|
84
101
|
credentialPath?: string;
|
|
85
102
|
fetch?: FetchLike;
|
|
86
103
|
}): Promise<PrivateFungiAccountLogoutResult>;
|
|
@@ -101,20 +118,20 @@ export declare function fungiMcpTeamEndpoint(rawEndpoint: string | URL): {
|
|
|
101
118
|
teamId: string;
|
|
102
119
|
};
|
|
103
120
|
/** The machine's attached folders, as Local keeps them. */
|
|
104
|
-
export declare function readFungiMcpLocalBackends(
|
|
121
|
+
export declare function readFungiMcpLocalBackends(input?: Readonly<{
|
|
105
122
|
credentialPath?: string;
|
|
106
123
|
}>): Promise<Readonly<{
|
|
107
124
|
issuer: string | undefined;
|
|
108
125
|
backends: LocalBackendRecord[];
|
|
109
126
|
}>>;
|
|
110
127
|
/** Replace the attached folder records in the owner-only credential file. */
|
|
111
|
-
export declare function writeFungiMcpLocalBackends(update: (current: readonly LocalBackendRecord[]) => LocalBackendRecord[],
|
|
128
|
+
export declare function writeFungiMcpLocalBackends(update: (current: readonly LocalBackendRecord[]) => LocalBackendRecord[], input?: Readonly<{
|
|
112
129
|
credentialPath?: string;
|
|
113
130
|
}>): Promise<void>;
|
|
114
131
|
/** Complete first-party registration and OAuth authorization in a foreground TUI. */
|
|
115
|
-
export declare function signInFungiAccount(rawEndpoint: string | URL,
|
|
132
|
+
export declare function signInFungiAccount(rawEndpoint: string | URL, input?: PrivateFungiAccountLoginOptions): Promise<void>;
|
|
116
133
|
/** Switch only to a fresh membership; failure preserves the prior selected Team. */
|
|
117
|
-
export declare function switchFungiAccountTeam(teamId: string,
|
|
134
|
+
export declare function switchFungiAccountTeam(teamId: string, input?: {
|
|
118
135
|
credentialPath?: string;
|
|
119
136
|
fetch?: FetchLike;
|
|
120
137
|
}): Promise<void>;
|
|
@@ -123,7 +140,7 @@ export type FungiAccountTeams = Readonly<{
|
|
|
123
140
|
selectedTeamId?: string;
|
|
124
141
|
}>;
|
|
125
142
|
/** Fresh memberships for the saved account, with the Team that pays for models. */
|
|
126
|
-
export declare function readFungiAccountTeams(
|
|
143
|
+
export declare function readFungiAccountTeams(input?: {
|
|
127
144
|
credentialPath?: string;
|
|
128
145
|
fetch?: FetchLike;
|
|
129
146
|
}): Promise<FungiAccountTeams>;
|
|
@@ -192,7 +209,7 @@ export type FungiTeamBalance = Readonly<{
|
|
|
192
209
|
kind: "not-available";
|
|
193
210
|
reason: "no-team" | "not-a-member" | "sign-in-again" | "sign-in-not-admitted";
|
|
194
211
|
}>;
|
|
195
|
-
export declare function readFungiTeamBalance(
|
|
212
|
+
export declare function readFungiTeamBalance(input?: {
|
|
196
213
|
credentialPath?: string;
|
|
197
214
|
fetch?: FetchLike;
|
|
198
215
|
}): Promise<FungiTeamBalance>;
|
|
@@ -201,7 +218,7 @@ export declare function readFungiTeamBalance(options?: {
|
|
|
201
218
|
* when the saved access token actually needs it: a plain read never takes
|
|
202
219
|
* the lock or rewrites the file, so a still-valid credential stays untouched.
|
|
203
220
|
*/
|
|
204
|
-
export declare function freshFungiAccess(rawHub: string | URL,
|
|
221
|
+
export declare function freshFungiAccess(rawHub: string | URL, input?: {
|
|
205
222
|
credentialPath?: string;
|
|
206
223
|
fetch?: FetchLike;
|
|
207
224
|
signal?: AbortSignal;
|
|
@@ -209,13 +226,13 @@ export declare function freshFungiAccess(rawHub: string | URL, options?: {
|
|
|
209
226
|
rejectedAccess?: string;
|
|
210
227
|
}): Promise<string>;
|
|
211
228
|
/** Retry an explicitly refused request once; the cloned body is replayable. */
|
|
212
|
-
export declare function fungiAccountFetch(rawHub: string | URL,
|
|
229
|
+
export declare function fungiAccountFetch(rawHub: string | URL, input?: {
|
|
213
230
|
credentialPath?: string;
|
|
214
231
|
fetch?: FetchLike;
|
|
215
232
|
signal?: AbortSignal;
|
|
216
233
|
}): typeof globalThis.fetch;
|
|
217
234
|
/** The SDK owns exactly one retry; this hook rotates only the refused credential. */
|
|
218
|
-
export declare function fungiMcpAccountAuth(endpoint: URL,
|
|
235
|
+
export declare function fungiMcpAccountAuth(endpoint: URL, input?: {
|
|
219
236
|
credentialPath?: string;
|
|
220
237
|
fetch?: FetchLike;
|
|
221
238
|
signal?: AbortSignal;
|
|
@@ -223,12 +240,12 @@ export declare function fungiMcpAccountAuth(endpoint: URL, options?: {
|
|
|
223
240
|
/** Selected payer metadata belongs to the shared account, never a model request body. */
|
|
224
241
|
export declare function fungiAccountTeam(credential: Credential): FungiAccountTeam;
|
|
225
242
|
/** Pi uses its own locked modify; this owner supplies the same refresh as MCP. */
|
|
226
|
-
export declare function createFungiAccountOAuth(
|
|
243
|
+
export declare function createFungiAccountOAuth(input?: {
|
|
227
244
|
credentialPath?: string;
|
|
228
245
|
fetch?: FetchLike;
|
|
229
246
|
}): OAuthAuth;
|
|
230
247
|
/** Pi deletion clears Local custody and reports unconfirmed remote revocation. */
|
|
231
|
-
export declare function createFungiAccountCredentialStore(
|
|
248
|
+
export declare function createFungiAccountCredentialStore(input?: {
|
|
232
249
|
credentialPath?: string;
|
|
233
250
|
fetch?: FetchLike;
|
|
234
251
|
}): CredentialStore;
|
|
@@ -4,12 +4,16 @@ import { createServer, } from "node:http";
|
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import process from "node:process";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
7
8
|
import { auth, Client, discoverOAuthServerInfo, refreshAuthorization, OAuthError, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
|
|
8
9
|
import { z } from "zod";
|
|
9
10
|
import { createPrivateCredentialStore } from "./private-file-credential-store.js";
|
|
11
|
+
import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
|
|
10
12
|
import { parseFungiMcpEndpoint } from "./private-fungi-mcp-endpoint.js";
|
|
11
13
|
import { logEvent } from "./log-event.js";
|
|
14
|
+
import { machineEnrollmentAuthoritySchema, machineEnrollmentRecordKind, } from "./private-local-mesh-record.js";
|
|
12
15
|
import { fungiMcpClientInfo, fungiMcpReachableFetch, rejectedFungiMcpAccess, } from "./private-fungi-mcp-connection.js";
|
|
16
|
+
import { signOutFungiThroughLocalService, } from "./private-fungi-service-logout.js";
|
|
13
17
|
const credentialId = "fungi";
|
|
14
18
|
const localScopes = "openid profile email offline_access models:invoke git:tracker billing:read";
|
|
15
19
|
const callbackHost = "127.0.0.1";
|
|
@@ -46,7 +50,8 @@ const teamChoiceSchema = z.object({
|
|
|
46
50
|
handle: z.string().min(1).max(256),
|
|
47
51
|
name: z.string().min(1).max(256),
|
|
48
52
|
});
|
|
49
|
-
const savedCredentialSchema = z
|
|
53
|
+
const savedCredentialSchema = z
|
|
54
|
+
.looseObject({
|
|
50
55
|
type: z.literal("oauth"),
|
|
51
56
|
access: z.string(),
|
|
52
57
|
refresh: z.string(),
|
|
@@ -59,6 +64,14 @@ const savedCredentialSchema = z.looseObject({
|
|
|
59
64
|
localBackends: z.array(localBackendRecordSchema).max(16).optional(),
|
|
60
65
|
team: teamChoiceSchema.optional(),
|
|
61
66
|
teamCount: z.number().int().min(0).max(1000).optional(),
|
|
67
|
+
})
|
|
68
|
+
.transform((credential) => {
|
|
69
|
+
// Preview credentials saved device enrollments here without an issuer
|
|
70
|
+
// binding, private keys included. They cannot be attributed to an issuer,
|
|
71
|
+
// so they are never read and the next write of this credential drops them.
|
|
72
|
+
const current = { ...credential };
|
|
73
|
+
delete current.machineEnrollments;
|
|
74
|
+
return current;
|
|
62
75
|
});
|
|
63
76
|
function jsonValue(value) {
|
|
64
77
|
const serialized = JSON.stringify(value);
|
|
@@ -87,6 +100,63 @@ async function readCredential(path) {
|
|
|
87
100
|
throw new Error("Fungi Local credential data is invalid");
|
|
88
101
|
return result.data;
|
|
89
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Enrollment uses the account file and lock, without Pi's selected-payer view.
|
|
105
|
+
* Fungi enrollments are partitioned by issuer in the origin-keyed enrollment
|
|
106
|
+
* records: only the signed-in issuer's partition is ever read for dialing, so
|
|
107
|
+
* a switch never lends one issuer's Team or device identifiers to another,
|
|
108
|
+
* and signing in to an issuer again finds its own partition.
|
|
109
|
+
*/
|
|
110
|
+
export function createLocalMachineEnrollmentAccount(credentialPath) {
|
|
111
|
+
return {
|
|
112
|
+
read: async () => {
|
|
113
|
+
const saved = await readCredential(credentialPath);
|
|
114
|
+
const records = await credentialStore(credentialPath).readMachineEnrollments();
|
|
115
|
+
const issuer = saved?.issuer !== undefined && saved.access !== ""
|
|
116
|
+
? saved.issuer
|
|
117
|
+
: undefined;
|
|
118
|
+
const partition = records.find((record) => record.origin === issuer &&
|
|
119
|
+
machineEnrollmentRecordKind(record) === "oauth");
|
|
120
|
+
return [
|
|
121
|
+
...(issuer !== undefined
|
|
122
|
+
? [
|
|
123
|
+
{
|
|
124
|
+
kind: "oauth",
|
|
125
|
+
origin: issuer,
|
|
126
|
+
enrollments: partition?.enrollments ?? [],
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
: []),
|
|
130
|
+
...records.flatMap((record) => machineEnrollmentRecordKind(record) === "oyster"
|
|
131
|
+
? [
|
|
132
|
+
{
|
|
133
|
+
kind: "oyster",
|
|
134
|
+
origin: record.origin,
|
|
135
|
+
enrollments: record.enrollments,
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
: []),
|
|
139
|
+
];
|
|
140
|
+
},
|
|
141
|
+
update: async (rawAuthority, edit) => {
|
|
142
|
+
const authority = machineEnrollmentAuthoritySchema.parse(rawAuthority);
|
|
143
|
+
if (authority.kind === "oyster") {
|
|
144
|
+
await credentialStore(credentialPath).modifyMachineEnrollments(authority.origin, edit);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await credentialStore(credentialPath).modifyMachineEnrollments(authority.origin, edit, {
|
|
148
|
+
providerId: credentialId,
|
|
149
|
+
admit: (current) => {
|
|
150
|
+
const parsed = savedCredentialSchema.safeParse(current);
|
|
151
|
+
if (!parsed.success ||
|
|
152
|
+
parsed.data.access === "" ||
|
|
153
|
+
parsed.data.issuer !== authority.origin)
|
|
154
|
+
throw new Error("Fungi sign-in changed before machine enrollment was persisted");
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
90
160
|
async function updateCredential(patch, path) {
|
|
91
161
|
const store = credentialStore(path);
|
|
92
162
|
await store.modify(credentialId, (current) => {
|
|
@@ -128,7 +198,11 @@ function respond(response, status, message) {
|
|
|
128
198
|
response.end(`<!doctype html><meta charset="utf-8"><title>Fungi Local</title><p>${message}</p>`);
|
|
129
199
|
}
|
|
130
200
|
/** Persisted OAuth state uses Local's existing owner-only Pi credential file. */
|
|
131
|
-
export function privateFungiAccountOAuthProvider(
|
|
201
|
+
export function privateFungiAccountOAuthProvider(input = {}) {
|
|
202
|
+
const options = {
|
|
203
|
+
...input,
|
|
204
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
205
|
+
};
|
|
132
206
|
const interactive = options.interactive ?? false;
|
|
133
207
|
const credentialPath = options.credentialPath;
|
|
134
208
|
const issuer = options.issuer ?? options.endpoint?.origin;
|
|
@@ -278,6 +352,17 @@ export function privateFungiAccountOAuthProvider(options = {}) {
|
|
|
278
352
|
function validateEndpoint(value) {
|
|
279
353
|
return parseFungiMcpEndpoint(value);
|
|
280
354
|
}
|
|
355
|
+
const accountOwners = new Map();
|
|
356
|
+
/** Installed only by the service holding this home's owner lease. */
|
|
357
|
+
export function registerFungiAccountOwner(credentialPath, changed) {
|
|
358
|
+
const path = resolve(credentialPath);
|
|
359
|
+
if (accountOwners.has(path))
|
|
360
|
+
throw new Error("Fungi account already has an in-process owner");
|
|
361
|
+
accountOwners.set(path, changed);
|
|
362
|
+
return () => {
|
|
363
|
+
accountOwners.delete(path);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
281
366
|
function logoutResult(kind, issuer, unconfirmedComputers = []) {
|
|
282
367
|
const status = kind === "not-logged-in"
|
|
283
368
|
? `Fungi was not logged in${issuer ? ` to ${issuer}` : ""}.`
|
|
@@ -413,6 +498,10 @@ async function removeSavedLogin(saved, options) {
|
|
|
413
498
|
}
|
|
414
499
|
return value;
|
|
415
500
|
});
|
|
501
|
+
// Publish after the committed removal, before remote revocation can stall.
|
|
502
|
+
await accountOwners.get(resolve(options.credentialPath))?.({
|
|
503
|
+
type: "signed-out",
|
|
504
|
+
});
|
|
416
505
|
if (latest === undefined || latest.refresh === "")
|
|
417
506
|
return logoutResult("not-logged-in", saved.issuer, unconfirmed);
|
|
418
507
|
let revoked = await revokeSavedGrant(latest, options.fetch);
|
|
@@ -430,7 +519,11 @@ async function removeSavedLogin(saved, options) {
|
|
|
430
519
|
return logoutResult(revoked ? "revoked" : "removed", latest.issuer, unconfirmed);
|
|
431
520
|
}
|
|
432
521
|
/** Non-secret status of the shared Fungi account. */
|
|
433
|
-
export async function readFungiAccountStatus(
|
|
522
|
+
export async function readFungiAccountStatus(input = {}) {
|
|
523
|
+
const options = {
|
|
524
|
+
...input,
|
|
525
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
526
|
+
};
|
|
434
527
|
const saved = await readCredential(options.credentialPath);
|
|
435
528
|
if (saved?.issuer === undefined || saved.access === "")
|
|
436
529
|
return undefined;
|
|
@@ -445,16 +538,60 @@ export async function readFungiAccountStatus(options = {}) {
|
|
|
445
538
|
return status;
|
|
446
539
|
}
|
|
447
540
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
541
|
+
* Ask the running Local account owner to remove this origin's login and revoke
|
|
542
|
+
* its refresh grant. Without a service, clear offline; never start one for
|
|
543
|
+
* logout. A login for another origin is kept. Explicit standalone stores have
|
|
544
|
+
* no service home; clients with a custom service directory name it explicitly.
|
|
450
545
|
*/
|
|
451
|
-
export async function signOutFungiAccount(rawEndpoint,
|
|
546
|
+
export async function signOutFungiAccount(rawEndpoint, input = {}) {
|
|
547
|
+
const credentialPath = resolve(input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath);
|
|
548
|
+
if (accountOwners.has(credentialPath))
|
|
549
|
+
return await signOutOwnedFungiAccount(rawEndpoint, {
|
|
550
|
+
...input,
|
|
551
|
+
credentialPath,
|
|
552
|
+
});
|
|
553
|
+
// The ordinary CLI supplies this canonical path, including for an explicitly
|
|
554
|
+
// selected home. Do not reselect ambient HOME after that boundary.
|
|
555
|
+
const root = capturePrivateLocalConfigRoot(dirname(dirname(dirname(credentialPath))));
|
|
556
|
+
const directory = input.serviceDirectory ??
|
|
557
|
+
(root.credentialPath === credentialPath
|
|
558
|
+
? root.serviceDirectory
|
|
559
|
+
: undefined);
|
|
560
|
+
if (directory !== undefined) {
|
|
561
|
+
return await signOutFungiThroughLocalService(rawEndpoint, {
|
|
562
|
+
...input,
|
|
563
|
+
credentialPath,
|
|
564
|
+
directory,
|
|
565
|
+
clearOffline: signOutOwnedFungiAccount,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
// Explicit standalone credential stores have no service home. A client using
|
|
569
|
+
// a custom service directory supplies it above; the server checks the path.
|
|
570
|
+
return await signOutOwnedFungiAccount(rawEndpoint, {
|
|
571
|
+
...input,
|
|
572
|
+
credentialPath,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
/** Store mutation for the service owner or a verified no-service caller. */
|
|
576
|
+
export async function signOutOwnedFungiAccount(rawEndpoint, input = {}) {
|
|
577
|
+
const options = {
|
|
578
|
+
...input,
|
|
579
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
580
|
+
};
|
|
452
581
|
const saved = await readCredential(options.credentialPath);
|
|
453
582
|
const origin = rawEndpoint === undefined
|
|
454
583
|
? saved?.issuer
|
|
455
584
|
: validateEndpoint(rawEndpoint).origin;
|
|
456
|
-
|
|
457
|
-
|
|
585
|
+
const removedMachine = origin !== undefined &&
|
|
586
|
+
(await credentialStore(options.credentialPath).removeMachineEnrollments(origin));
|
|
587
|
+
if (saved === undefined || saved.issuer !== origin) {
|
|
588
|
+
if (removedMachine)
|
|
589
|
+
await accountOwners.get(resolve(options.credentialPath))?.({
|
|
590
|
+
type: "signed-out",
|
|
591
|
+
});
|
|
592
|
+
// Local custody ended; this is not a claim of remote device revocation.
|
|
593
|
+
return logoutResult(removedMachine ? "removed" : "not-logged-in", origin);
|
|
594
|
+
}
|
|
458
595
|
return removeSavedLogin(saved, options);
|
|
459
596
|
}
|
|
460
597
|
/**
|
|
@@ -523,7 +660,11 @@ export function fungiMcpTeamEndpoint(rawEndpoint) {
|
|
|
523
660
|
return { endpoint, teamId };
|
|
524
661
|
}
|
|
525
662
|
/** The machine's attached folders, as Local keeps them. */
|
|
526
|
-
export async function readFungiMcpLocalBackends(
|
|
663
|
+
export async function readFungiMcpLocalBackends(input = {}) {
|
|
664
|
+
const options = {
|
|
665
|
+
...input,
|
|
666
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
667
|
+
};
|
|
527
668
|
const saved = await readCredential(options.credentialPath);
|
|
528
669
|
return {
|
|
529
670
|
issuer: saved?.issuer,
|
|
@@ -531,7 +672,11 @@ export async function readFungiMcpLocalBackends(options = {}) {
|
|
|
531
672
|
};
|
|
532
673
|
}
|
|
533
674
|
/** Replace the attached folder records in the owner-only credential file. */
|
|
534
|
-
export async function writeFungiMcpLocalBackends(update,
|
|
675
|
+
export async function writeFungiMcpLocalBackends(update, input = {}) {
|
|
676
|
+
const options = {
|
|
677
|
+
...input,
|
|
678
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
679
|
+
};
|
|
535
680
|
await credentialStore(options.credentialPath).modify(credentialId, (current) => {
|
|
536
681
|
const saved = savedCredentialSchema.parse(current);
|
|
537
682
|
if (saved.access === "")
|
|
@@ -585,7 +730,11 @@ async function retryPendingLogout(endpoint, options) {
|
|
|
585
730
|
}
|
|
586
731
|
}
|
|
587
732
|
/** Complete first-party registration and OAuth authorization in a foreground TUI. */
|
|
588
|
-
export async function signInFungiAccount(rawEndpoint,
|
|
733
|
+
export async function signInFungiAccount(rawEndpoint, input = {}) {
|
|
734
|
+
const options = {
|
|
735
|
+
...input,
|
|
736
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
737
|
+
};
|
|
589
738
|
options.signal?.throwIfAborted();
|
|
590
739
|
const endpoint = validateEndpoint(rawEndpoint);
|
|
591
740
|
// Check the Hub before offering a browser handoff or replacing a saved login.
|
|
@@ -664,12 +813,11 @@ export async function signInFungiAccount(rawEndpoint, options = {}) {
|
|
|
664
813
|
const redirectUrl = `${callbackOrigin}${callbackPath}`;
|
|
665
814
|
const providerOptions = {
|
|
666
815
|
interactive: true,
|
|
816
|
+
credentialPath: options.credentialPath,
|
|
667
817
|
endpoint,
|
|
668
818
|
redirectUrl,
|
|
669
819
|
openBrowser: launchBrowser,
|
|
670
820
|
};
|
|
671
|
-
if (options.credentialPath !== undefined)
|
|
672
|
-
providerOptions.credentialPath = options.credentialPath;
|
|
673
821
|
const provider = privateFungiAccountOAuthProvider(providerOptions);
|
|
674
822
|
// The browser is opened only after this handler is attached.
|
|
675
823
|
server.on("request", (request, response) => {
|
|
@@ -835,7 +983,11 @@ async function chooseFungiAccountTeam(rawHub, options) {
|
|
|
835
983
|
await saveSelectedTeam(issuer, team, teams.length, options.credentialPath);
|
|
836
984
|
}
|
|
837
985
|
/** Switch only to a fresh membership; failure preserves the prior selected Team. */
|
|
838
|
-
export async function switchFungiAccountTeam(teamId,
|
|
986
|
+
export async function switchFungiAccountTeam(teamId, input = {}) {
|
|
987
|
+
const options = {
|
|
988
|
+
...input,
|
|
989
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
990
|
+
};
|
|
839
991
|
const saved = await readCredential(options.credentialPath);
|
|
840
992
|
if (saved?.issuer === undefined)
|
|
841
993
|
throw new Error("Sign in to Fungi to choose a Team");
|
|
@@ -846,7 +998,11 @@ export async function switchFungiAccountTeam(teamId, options = {}) {
|
|
|
846
998
|
await saveSelectedTeam(saved.issuer, selected, teams.length, options.credentialPath);
|
|
847
999
|
}
|
|
848
1000
|
/** Fresh memberships for the saved account, with the Team that pays for models. */
|
|
849
|
-
export async function readFungiAccountTeams(
|
|
1001
|
+
export async function readFungiAccountTeams(input = {}) {
|
|
1002
|
+
const options = {
|
|
1003
|
+
...input,
|
|
1004
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1005
|
+
};
|
|
850
1006
|
const saved = await readCredential(options.credentialPath);
|
|
851
1007
|
if (saved?.issuer === undefined || saved.access === "")
|
|
852
1008
|
throw new Error("Sign in to Fungi to list your Teams");
|
|
@@ -881,7 +1037,11 @@ const teamBalanceSchema = z.object({
|
|
|
881
1037
|
reserved: moneySnapshotSchema,
|
|
882
1038
|
available: moneySnapshotSchema,
|
|
883
1039
|
});
|
|
884
|
-
export async function readFungiTeamBalance(
|
|
1040
|
+
export async function readFungiTeamBalance(input = {}) {
|
|
1041
|
+
const options = {
|
|
1042
|
+
...input,
|
|
1043
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1044
|
+
};
|
|
885
1045
|
const saved = await readCredential(options.credentialPath);
|
|
886
1046
|
if (saved?.issuer === undefined || saved.access === "")
|
|
887
1047
|
throw new Error("Sign in to Fungi to read your Team balance");
|
|
@@ -973,7 +1133,11 @@ function cachedFungiAccess(cached, issuer, rejectedAccess) {
|
|
|
973
1133
|
* when the saved access token actually needs it: a plain read never takes
|
|
974
1134
|
* the lock or rewrites the file, so a still-valid credential stays untouched.
|
|
975
1135
|
*/
|
|
976
|
-
export async function freshFungiAccess(rawHub,
|
|
1136
|
+
export async function freshFungiAccess(rawHub, input = {}) {
|
|
1137
|
+
const options = {
|
|
1138
|
+
...input,
|
|
1139
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1140
|
+
};
|
|
977
1141
|
const issuer = validateEndpoint(rawHub).origin;
|
|
978
1142
|
options.signal?.throwIfAborted();
|
|
979
1143
|
const cached = await readCredential(options.credentialPath);
|
|
@@ -995,7 +1159,11 @@ export async function freshFungiAccess(rawHub, options = {}) {
|
|
|
995
1159
|
return savedCredentialSchema.parse(current).access;
|
|
996
1160
|
}
|
|
997
1161
|
/** Retry an explicitly refused request once; the cloned body is replayable. */
|
|
998
|
-
export function fungiAccountFetch(rawHub,
|
|
1162
|
+
export function fungiAccountFetch(rawHub, input = {}) {
|
|
1163
|
+
const options = {
|
|
1164
|
+
...input,
|
|
1165
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1166
|
+
};
|
|
999
1167
|
const issuer = validateEndpoint(rawHub).origin;
|
|
1000
1168
|
const transport = ownFungiFetch(issuer, options.fetch, options.signal);
|
|
1001
1169
|
const requestWithRetry = async (input, init) => {
|
|
@@ -1040,7 +1208,11 @@ export function fungiAccountFetch(rawHub, options = {}) {
|
|
|
1040
1208
|
});
|
|
1041
1209
|
}
|
|
1042
1210
|
/** The SDK owns exactly one retry; this hook rotates only the refused credential. */
|
|
1043
|
-
export function fungiMcpAccountAuth(endpoint,
|
|
1211
|
+
export function fungiMcpAccountAuth(endpoint, input = {}) {
|
|
1212
|
+
const options = {
|
|
1213
|
+
...input,
|
|
1214
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1215
|
+
};
|
|
1044
1216
|
let lastToken;
|
|
1045
1217
|
return {
|
|
1046
1218
|
token: async () => {
|
|
@@ -1063,7 +1235,11 @@ export function fungiAccountTeam(credential) {
|
|
|
1063
1235
|
return saved.team;
|
|
1064
1236
|
}
|
|
1065
1237
|
/** Pi uses its own locked modify; this owner supplies the same refresh as MCP. */
|
|
1066
|
-
export function createFungiAccountOAuth(
|
|
1238
|
+
export function createFungiAccountOAuth(input = {}) {
|
|
1239
|
+
const options = {
|
|
1240
|
+
...input,
|
|
1241
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1242
|
+
};
|
|
1067
1243
|
return {
|
|
1068
1244
|
name: "Fungi",
|
|
1069
1245
|
loginLabel: "Sign in to Fungi",
|
|
@@ -1112,7 +1288,11 @@ export function createFungiAccountOAuth(options = {}) {
|
|
|
1112
1288
|
};
|
|
1113
1289
|
}
|
|
1114
1290
|
/** Pi deletion clears Local custody and reports unconfirmed remote revocation. */
|
|
1115
|
-
export function createFungiAccountCredentialStore(
|
|
1291
|
+
export function createFungiAccountCredentialStore(input = {}) {
|
|
1292
|
+
const options = {
|
|
1293
|
+
...input,
|
|
1294
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
1295
|
+
};
|
|
1116
1296
|
const store = credentialStore(options.credentialPath);
|
|
1117
1297
|
return {
|
|
1118
1298
|
async read(id, operation) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AuthProvider, type FetchLike, type OAuthClientProvider } from "@modelcontextprotocol/client";
|
|
2
2
|
import { type CapabilitySource } from "@fungi.computer/mycelium";
|
|
3
|
+
import { type RequestMcpFormApproval } from "./private-mcp-form-elicitation.js";
|
|
3
4
|
/** The MCP client identity Local reports to the Hub. */
|
|
4
5
|
export declare const fungiMcpClientInfo: Readonly<{
|
|
5
6
|
name: "fungi-local";
|
|
@@ -10,9 +11,12 @@ export declare function fungiMcpReachableFetch(endpoint: URL, fetchImpl?: FetchL
|
|
|
10
11
|
/** Host-held selection and credentials; neither enters the model's arguments. */
|
|
11
12
|
export type PrivateFungiMcpConnectionOptions = Readonly<{
|
|
12
13
|
endpoint: URL;
|
|
14
|
+
authentication?: "oyster-key" | undefined;
|
|
13
15
|
authProvider: AuthProvider | OAuthClientProvider;
|
|
14
16
|
/** The maintained transport's fetch seam, also used by the local caller proof. */
|
|
15
17
|
fetch?: FetchLike;
|
|
18
|
+
/** Trusted human interaction; never supplied by model arguments. */
|
|
19
|
+
requestApproval?: RequestMcpFormApproval | undefined;
|
|
16
20
|
}>;
|
|
17
21
|
/**
|
|
18
22
|
* Add the existing Fungi MCP execute door to a Local run's capability scope.
|