@fungi.computer/truffle 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +542 -2
- package/computerd-loopback.cjs +16 -0
- package/dist/auth-cli.d.ts +46 -0
- package/dist/auth-cli.js +373 -0
- package/dist/fungi-account-command.d.ts +18 -0
- package/dist/fungi-account-command.js +95 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +23 -0
- 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 +27487 -0
- package/dist/internal/sandbox-metafile.json +6726 -0
- package/dist/linear-auth.d.ts +4 -0
- package/dist/linear-auth.js +72 -0
- package/dist/local-archive-handoff.d.ts +12 -0
- package/dist/local-archive-handoff.js +21 -0
- package/dist/local-extensions.d.ts +143 -0
- package/dist/local-extensions.js +624 -0
- package/dist/local-image-fit.d.ts +30 -0
- package/dist/local-image-fit.js +105 -0
- package/dist/local-login.d.ts +55 -0
- package/dist/local-login.js +167 -0
- package/dist/local-models.d.ts +16 -0
- package/dist/local-models.js +82 -0
- package/dist/local-session-archives.d.ts +8 -0
- package/dist/local-session-archives.js +152 -0
- package/dist/local-skill-source.d.ts +21 -0
- package/dist/local-skill-source.js +433 -0
- package/dist/log-event.d.ts +9 -0
- package/dist/log-event.js +13 -0
- package/dist/private-bounded-response.d.ts +2 -0
- package/dist/private-bounded-response.js +41 -0
- package/dist/private-bun-shiitake-host.d.ts +11 -0
- package/dist/private-bun-shiitake-host.js +66 -0
- package/dist/private-file-credential-store.d.ts +58 -0
- package/dist/private-file-credential-store.js +331 -0
- package/dist/private-files-patch.d.ts +4 -0
- package/dist/private-files-patch.js +104 -0
- package/dist/private-fungi-account.d.ts +252 -0
- package/dist/private-fungi-account.js +1329 -0
- package/dist/private-fungi-auth-error.d.ts +5 -0
- package/dist/private-fungi-auth-error.js +7 -0
- package/dist/private-fungi-mcp-connection.d.ts +26 -0
- package/dist/private-fungi-mcp-connection.js +162 -0
- package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
- package/dist/private-fungi-mcp-endpoint.js +15 -0
- 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 +14 -0
- package/dist/private-fungi-mcp-selection.js +49 -0
- package/dist/private-fungi-model-api.d.ts +6 -0
- package/dist/private-fungi-model-api.js +125 -0
- package/dist/private-fungi-model-catalog.d.ts +16 -0
- package/dist/private-fungi-model-catalog.js +86 -0
- package/dist/private-fungi-model-provider.d.ts +6 -0
- package/dist/private-fungi-model-provider.js +51 -0
- package/dist/private-fungi-pending-logouts.d.ts +34 -0
- package/dist/private-fungi-pending-logouts.js +135 -0
- 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 +58 -0
- package/dist/private-invocation-computer.js +498 -0
- package/dist/private-invocation-files.d.ts +30 -0
- package/dist/private-invocation-files.js +520 -0
- package/dist/private-local-acp-stdio.d.ts +17 -0
- package/dist/private-local-acp-stdio.js +145 -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 +129 -0
- package/dist/private-local-auth-rpc.js +396 -0
- 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 +49 -0
- package/dist/private-local-computer-attach.js +230 -0
- package/dist/private-local-computer-backend.d.ts +282 -0
- package/dist/private-local-computer-backend.js +402 -0
- package/dist/private-local-computer-processes.d.ts +202 -0
- package/dist/private-local-computer-processes.js +613 -0
- 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-default-model.d.ts +11 -0
- package/dist/private-local-default-model.js +45 -0
- package/dist/private-local-delegation.d.ts +36 -0
- package/dist/private-local-delegation.js +133 -0
- package/dist/private-local-effective-model.d.ts +27 -0
- package/dist/private-local-effective-model.js +17 -0
- package/dist/private-local-exec-environment.d.ts +16 -0
- package/dist/private-local-exec-environment.js +11 -0
- package/dist/private-local-exec-launch.d.ts +41 -0
- package/dist/private-local-exec-launch.js +51 -0
- package/dist/private-local-exec-path.d.ts +6 -0
- package/dist/private-local-exec-path.js +40 -0
- package/dist/private-local-git-exclude.d.ts +2 -0
- package/dist/private-local-git-exclude.js +33 -0
- package/dist/private-local-http-dispatch.d.ts +5 -0
- package/dist/private-local-http-dispatch.js +14 -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 +13 -0
- package/dist/private-local-mail.js +77 -0
- 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 +192 -0
- package/dist/private-local-runtime-host.js +1177 -0
- 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 +170 -0
- package/dist/private-local-service-contract.js +1 -0
- package/dist/private-local-service-discovery.d.ts +217 -0
- package/dist/private-local-service-discovery.js +322 -0
- package/dist/private-local-service-ids.d.ts +3 -0
- package/dist/private-local-service-ids.js +8 -0
- 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 +22 -0
- package/dist/private-local-service-listener.js +407 -0
- 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 +135 -0
- package/dist/private-local-service-owner.js +1369 -0
- package/dist/private-local-service-process.d.ts +1 -0
- package/dist/private-local-service-process.js +8 -0
- 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 +46 -0
- package/dist/private-local-service.js +606 -0
- package/dist/private-local-session-discovery.d.ts +66 -0
- package/dist/private-local-session-discovery.js +278 -0
- package/dist/private-local-shiitake-host.d.ts +44 -0
- package/dist/private-local-shiitake-host.js +143 -0
- 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 +16 -0
- package/dist/private-local-worker-server.js +144 -0
- 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 +39 -0
- package/dist/private-sqlite-owner.js +169 -0
- package/dist/private-team-computers.d.ts +38 -0
- package/dist/private-team-computers.js +110 -0
- package/dist/private-workerd-sandbox.d.ts +3 -0
- package/dist/private-workerd-sandbox.js +347 -0
- 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 +24 -0
- package/dist/team-apps.js +128 -0
- package/dist/tracker-git-credential.d.ts +15 -0
- package/dist/tracker-git-credential.js +87 -0
- 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/build.json +4037 -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-apply-patch +0 -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 +161 -3
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { open, opendir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const maximumSkillCount = 64;
|
|
6
|
+
const maximumCatalogBytes = 16_000;
|
|
7
|
+
const maximumFileBytes = 128 * 1_024;
|
|
8
|
+
const maximumDirectoryEntries = 512;
|
|
9
|
+
const maximumSupportingDepth = 16;
|
|
10
|
+
const maximumDiscoveryDepth = 16;
|
|
11
|
+
const utf8 = new TextEncoder();
|
|
12
|
+
const nameSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(64)
|
|
16
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
17
|
+
const frontmatterSchema = z.object({
|
|
18
|
+
name: z.string().optional(),
|
|
19
|
+
description: z.string().optional(),
|
|
20
|
+
"disable-model-invocation": z
|
|
21
|
+
.unknown()
|
|
22
|
+
.default(false)
|
|
23
|
+
.transform((value) => value === true),
|
|
24
|
+
});
|
|
25
|
+
const fileSchema = z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(4_096)
|
|
29
|
+
.refine((file) => !isAbsolute(file) &&
|
|
30
|
+
!file.includes("\\") &&
|
|
31
|
+
!file.includes("\0") &&
|
|
32
|
+
file
|
|
33
|
+
.split("/")
|
|
34
|
+
.every((part) => part !== "" && part !== "." && part !== ".."), "Skill file must remain a relative path inside its skill directory");
|
|
35
|
+
function absent(cause) {
|
|
36
|
+
return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
37
|
+
}
|
|
38
|
+
function contained(root, path) {
|
|
39
|
+
const within = relative(root, path);
|
|
40
|
+
if (isAbsolute(within) ||
|
|
41
|
+
within === ".." ||
|
|
42
|
+
within.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`))
|
|
43
|
+
throw new Error("Skill path escapes its directory");
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
function inside(root, path) {
|
|
47
|
+
const within = relative(root, path);
|
|
48
|
+
return within === "" ||
|
|
49
|
+
isAbsolute(within) ||
|
|
50
|
+
within === ".." ||
|
|
51
|
+
within.startsWith(`..${sep}`)
|
|
52
|
+
? undefined
|
|
53
|
+
: within;
|
|
54
|
+
}
|
|
55
|
+
/** The model's view of a skill file: invocation-relative, `~/…`, else absolute. */
|
|
56
|
+
function displayLocation(location, roots) {
|
|
57
|
+
const project = inside(roots.cwd, location);
|
|
58
|
+
if (project !== undefined)
|
|
59
|
+
return project;
|
|
60
|
+
const personal = inside(roots.home, location);
|
|
61
|
+
return personal === undefined ? location : join("~", personal);
|
|
62
|
+
}
|
|
63
|
+
async function readBounded(path, signal) {
|
|
64
|
+
signal.throwIfAborted();
|
|
65
|
+
const handle = await open(path, "r");
|
|
66
|
+
try {
|
|
67
|
+
if (!(await handle.stat()).isFile())
|
|
68
|
+
throw new Error("Skill path is not a regular file");
|
|
69
|
+
const bytes = Buffer.alloc(maximumFileBytes + 1);
|
|
70
|
+
let size = 0;
|
|
71
|
+
while (size < bytes.length) {
|
|
72
|
+
signal.throwIfAborted();
|
|
73
|
+
const page = await handle.read(bytes, size, bytes.length - size, size);
|
|
74
|
+
if (page.bytesRead === 0)
|
|
75
|
+
break;
|
|
76
|
+
size += page.bytesRead;
|
|
77
|
+
}
|
|
78
|
+
if (size > maximumFileBytes)
|
|
79
|
+
throw new Error("Skill file exceeds the 128 KiB bound");
|
|
80
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, size));
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await handle.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
class SkillSourceError extends Error {
|
|
87
|
+
}
|
|
88
|
+
function diagnostic(path, reason, message, cause = new Error(message)) {
|
|
89
|
+
return { path, reason, message, cause };
|
|
90
|
+
}
|
|
91
|
+
function explanation(cause, fallback) {
|
|
92
|
+
return cause instanceof SkillSourceError ? cause.message : fallback;
|
|
93
|
+
}
|
|
94
|
+
function parseSkill(content, directory) {
|
|
95
|
+
const normalized = content.replace(/\r\n?/gu, "\n");
|
|
96
|
+
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/u.exec(normalized);
|
|
97
|
+
if (!match)
|
|
98
|
+
throw new SkillSourceError("SKILL.md requires YAML frontmatter");
|
|
99
|
+
let frontmatter;
|
|
100
|
+
try {
|
|
101
|
+
frontmatter = parseYaml(match[1] ?? "");
|
|
102
|
+
}
|
|
103
|
+
catch (cause) {
|
|
104
|
+
throw new SkillSourceError("Skill frontmatter is not valid YAML", {
|
|
105
|
+
cause,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const result = frontmatterSchema.safeParse(frontmatter);
|
|
109
|
+
if (!result.success)
|
|
110
|
+
throw new SkillSourceError("Skill frontmatter has invalid field types", {
|
|
111
|
+
cause: result.error,
|
|
112
|
+
});
|
|
113
|
+
const metadata = result.data;
|
|
114
|
+
if (metadata.description === undefined || metadata.description.trim() === "")
|
|
115
|
+
throw new SkillSourceError("description is required");
|
|
116
|
+
const name = metadata.name === ""
|
|
117
|
+
? basename(directory)
|
|
118
|
+
: (metadata.name ?? basename(directory));
|
|
119
|
+
return {
|
|
120
|
+
metadata: { ...metadata, name, description: metadata.description },
|
|
121
|
+
body: normalized.slice(match[0].length),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function discoveryRoots(cwd) {
|
|
125
|
+
const project = [join(cwd, ".fungi/skills")];
|
|
126
|
+
let ancestor = cwd;
|
|
127
|
+
for (;;) {
|
|
128
|
+
project.push(join(ancestor, ".agents/skills"));
|
|
129
|
+
try {
|
|
130
|
+
await stat(join(ancestor, ".git"));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
catch (cause) {
|
|
134
|
+
if (!absent(cause))
|
|
135
|
+
throw cause;
|
|
136
|
+
}
|
|
137
|
+
const parent = dirname(ancestor);
|
|
138
|
+
if (parent === ancestor)
|
|
139
|
+
break;
|
|
140
|
+
ancestor = parent;
|
|
141
|
+
}
|
|
142
|
+
return project;
|
|
143
|
+
}
|
|
144
|
+
async function configuredRoots(path, base, home, signal, diagnostics) {
|
|
145
|
+
const settingsSchema = z.object({
|
|
146
|
+
skills: z.array(z.string().trim().min(1)).max(64).default([]),
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
const settings = settingsSchema.parse(JSON.parse(await readBounded(path, signal)));
|
|
150
|
+
return settings.skills.map((folder) => folder === "~"
|
|
151
|
+
? home
|
|
152
|
+
: folder.startsWith("~/")
|
|
153
|
+
? resolve(home, folder.slice(2))
|
|
154
|
+
: resolve(base, folder));
|
|
155
|
+
}
|
|
156
|
+
catch (cause) {
|
|
157
|
+
signal.throwIfAborted();
|
|
158
|
+
if (!absent(cause))
|
|
159
|
+
diagnostics.push(diagnostic(path, "invalid", "Local settings must be valid JSON with a skills list of extra folders", cause));
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function describeOrigin(origin) {
|
|
164
|
+
return origin.kind === "extension"
|
|
165
|
+
? `extension ${origin.extensionId}`
|
|
166
|
+
: origin.kind;
|
|
167
|
+
}
|
|
168
|
+
/** Walk labeled roots in order; the first skill with a name wins. */
|
|
169
|
+
async function scanRoots(roots, displayRoots, signal, diagnostics) {
|
|
170
|
+
const skills = [];
|
|
171
|
+
const owners = new Map();
|
|
172
|
+
const paths = new Set();
|
|
173
|
+
const visitedRoots = new Set();
|
|
174
|
+
for (const { path, origin, named } of roots) {
|
|
175
|
+
signal.throwIfAborted();
|
|
176
|
+
let root;
|
|
177
|
+
try {
|
|
178
|
+
root = await realpath(path);
|
|
179
|
+
}
|
|
180
|
+
catch (cause) {
|
|
181
|
+
signal.throwIfAborted();
|
|
182
|
+
if (!absent(cause) || named)
|
|
183
|
+
diagnostics.push({
|
|
184
|
+
path,
|
|
185
|
+
reason: "unavailable",
|
|
186
|
+
message: "Skill root is unavailable",
|
|
187
|
+
cause,
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (visitedRoots.has(root))
|
|
192
|
+
continue;
|
|
193
|
+
visitedRoots.add(root);
|
|
194
|
+
const visited = new Set();
|
|
195
|
+
let entries = 0;
|
|
196
|
+
let found = 0;
|
|
197
|
+
const before = diagnostics.length;
|
|
198
|
+
const walk = async (directory, depth) => {
|
|
199
|
+
signal.throwIfAborted();
|
|
200
|
+
if (depth > maximumDiscoveryDepth) {
|
|
201
|
+
diagnostics.push(diagnostic(directory, "bound", "Skill discovery exceeds its 16-level depth bound"));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (visited.has(directory))
|
|
205
|
+
return;
|
|
206
|
+
visited.add(directory);
|
|
207
|
+
const skillPath = join(directory, "SKILL.md");
|
|
208
|
+
let location;
|
|
209
|
+
try {
|
|
210
|
+
location = contained(directory, await realpath(skillPath));
|
|
211
|
+
}
|
|
212
|
+
catch (cause) {
|
|
213
|
+
signal.throwIfAborted();
|
|
214
|
+
if (!absent(cause)) {
|
|
215
|
+
diagnostics.push({
|
|
216
|
+
path: skillPath,
|
|
217
|
+
reason: "invalid",
|
|
218
|
+
message: "Skill file escapes its directory or cannot be read",
|
|
219
|
+
cause,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (location !== undefined) {
|
|
225
|
+
found++;
|
|
226
|
+
try {
|
|
227
|
+
if (paths.has(location))
|
|
228
|
+
return;
|
|
229
|
+
paths.add(location);
|
|
230
|
+
const { metadata } = parseSkill(await readBounded(location, signal), directory);
|
|
231
|
+
if (!nameSchema.safeParse(metadata.name).success ||
|
|
232
|
+
metadata.name !== basename(directory))
|
|
233
|
+
diagnostics.push(diagnostic(skillPath, "warning", "Skill name should match its directory, use lowercase letters, digits and single hyphens, and be at most 64 characters"));
|
|
234
|
+
if (metadata.description.length > 1_024)
|
|
235
|
+
diagnostics.push(diagnostic(skillPath, "warning", "Skill description exceeds the recommended 1024 characters"));
|
|
236
|
+
const winner = owners.get(metadata.name);
|
|
237
|
+
if (winner !== undefined) {
|
|
238
|
+
diagnostics.push(diagnostic(skillPath, "collision", `${describeOrigin(winner)} skill ${metadata.name} wins over ${describeOrigin(origin)}`));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
owners.set(metadata.name, origin);
|
|
242
|
+
if (metadata["disable-model-invocation"]) {
|
|
243
|
+
diagnostics.push(diagnostic(skillPath, "skipped", "Skill disables model invocation"));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const skill = {
|
|
247
|
+
name: metadata.name,
|
|
248
|
+
description: metadata.description,
|
|
249
|
+
location,
|
|
250
|
+
displayLocation: displayLocation(location, displayRoots),
|
|
251
|
+
origin,
|
|
252
|
+
};
|
|
253
|
+
if (skills.length >= maximumSkillCount ||
|
|
254
|
+
utf8.encode(JSON.stringify([...skills, skill])).byteLength >
|
|
255
|
+
maximumCatalogBytes) {
|
|
256
|
+
diagnostics.push(diagnostic(skillPath, "bound", "Skill catalog exceeds its 64-skill or 16 KB bound"));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
skills.push(skill);
|
|
260
|
+
}
|
|
261
|
+
catch (cause) {
|
|
262
|
+
signal.throwIfAborted();
|
|
263
|
+
diagnostics.push({
|
|
264
|
+
path: skillPath,
|
|
265
|
+
reason: "invalid",
|
|
266
|
+
message: explanation(cause, "Skill metadata or file is invalid"),
|
|
267
|
+
cause,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const children = [];
|
|
273
|
+
for await (const entry of await opendir(directory)) {
|
|
274
|
+
signal.throwIfAborted();
|
|
275
|
+
if (++entries > maximumDirectoryEntries)
|
|
276
|
+
throw new SkillSourceError("Skill discovery exceeds its 512-entry bound");
|
|
277
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
278
|
+
if (entry.isDirectory() || entry.isSymbolicLink())
|
|
279
|
+
diagnostics.push(diagnostic(join(directory, entry.name), "skipped", "Dot directories and node_modules are not searched"));
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
children.push(entry.name);
|
|
283
|
+
}
|
|
284
|
+
for (const child of children.sort()) {
|
|
285
|
+
const candidate = join(directory, child);
|
|
286
|
+
try {
|
|
287
|
+
const canonical = contained(root, await realpath(candidate));
|
|
288
|
+
if ((await stat(canonical)).isDirectory())
|
|
289
|
+
await walk(canonical, depth + 1);
|
|
290
|
+
}
|
|
291
|
+
catch (cause) {
|
|
292
|
+
signal.throwIfAborted();
|
|
293
|
+
if (cause instanceof SkillSourceError)
|
|
294
|
+
throw cause;
|
|
295
|
+
diagnostics.push(diagnostic(join(candidate, "SKILL.md"), "invalid", "Skill entry escapes its root or is unavailable", cause));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
try {
|
|
300
|
+
await walk(root, 0);
|
|
301
|
+
if (found === 0 && diagnostics.length === before)
|
|
302
|
+
diagnostics.push(diagnostic(path, "empty", entries === 0
|
|
303
|
+
? "Skill root is empty"
|
|
304
|
+
: "Skill root contains no SKILL.md files"));
|
|
305
|
+
}
|
|
306
|
+
catch (cause) {
|
|
307
|
+
signal.throwIfAborted();
|
|
308
|
+
diagnostics.push({
|
|
309
|
+
path,
|
|
310
|
+
reason: cause instanceof SkillSourceError ? "bound" : "unavailable",
|
|
311
|
+
message: explanation(cause, "Skill root cannot be searched"),
|
|
312
|
+
cause,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { skills, diagnostics };
|
|
317
|
+
}
|
|
318
|
+
/** What an extension's skills folder offers, for the install prompt. */
|
|
319
|
+
export function describeExtensionSkills(folder, signal) {
|
|
320
|
+
// The install prompt lists names only, so display paths are never shown.
|
|
321
|
+
return scanRoots([
|
|
322
|
+
{
|
|
323
|
+
path: folder.directory,
|
|
324
|
+
origin: { kind: "extension", extensionId: folder.extensionId },
|
|
325
|
+
named: true,
|
|
326
|
+
},
|
|
327
|
+
], { cwd: folder.directory, home: folder.directory }, signal, []);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Discover skills without widening the invocation Files jail. Folders are
|
|
331
|
+
* searched project first, then personal, then installed extensions' (lowest);
|
|
332
|
+
* the first skill with a name wins, so your own skill beats an extension's.
|
|
333
|
+
* `extensions` is already filtered by the extension owner's activation: a
|
|
334
|
+
* folder that is not passed here contributes nothing. Local installed
|
|
335
|
+
* extensions only; Hub-hosted extensions are not covered.
|
|
336
|
+
*/
|
|
337
|
+
export function createLocalSkillSource(options) {
|
|
338
|
+
const cwd = resolve(options.cwd);
|
|
339
|
+
const home = resolve(options.home);
|
|
340
|
+
// Skill files are canonical paths, so compare them with canonical roots.
|
|
341
|
+
// A root that does not exist has no symlinks to resolve; any other failure
|
|
342
|
+
// (permissions, loops) propagates to the caller.
|
|
343
|
+
const canonical = async (path) => {
|
|
344
|
+
try {
|
|
345
|
+
return await realpath(path);
|
|
346
|
+
}
|
|
347
|
+
catch (cause) {
|
|
348
|
+
if (absent(cause))
|
|
349
|
+
return path;
|
|
350
|
+
throw cause;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const catalog = async (signal) => {
|
|
354
|
+
const displayRoots = {
|
|
355
|
+
cwd: await canonical(cwd),
|
|
356
|
+
home: await canonical(home),
|
|
357
|
+
};
|
|
358
|
+
const diagnostics = [];
|
|
359
|
+
const projectExtras = await configuredRoots(join(cwd, ".fungi/settings.json"), cwd, home, signal, diagnostics);
|
|
360
|
+
const personalExtras = await configuredRoots(join(home, ".fungi/settings.json"), join(home, ".fungi"), home, signal, diagnostics);
|
|
361
|
+
const project = { kind: "project" };
|
|
362
|
+
const personal = { kind: "personal" };
|
|
363
|
+
const roots = [
|
|
364
|
+
...(await discoveryRoots(cwd)).map((path) => ({
|
|
365
|
+
path,
|
|
366
|
+
origin: project,
|
|
367
|
+
named: false,
|
|
368
|
+
})),
|
|
369
|
+
...projectExtras.map((path) => ({ path, origin: project, named: true })),
|
|
370
|
+
...[join(home, ".fungi/skills"), join(home, ".agents/skills")].map((path) => ({ path, origin: personal, named: false })),
|
|
371
|
+
...personalExtras.map((path) => ({
|
|
372
|
+
path,
|
|
373
|
+
origin: personal,
|
|
374
|
+
named: true,
|
|
375
|
+
})),
|
|
376
|
+
...(options.extensions ?? []).map(({ extensionId, directory }) => ({
|
|
377
|
+
path: directory,
|
|
378
|
+
origin: { kind: "extension", extensionId },
|
|
379
|
+
named: true,
|
|
380
|
+
})),
|
|
381
|
+
];
|
|
382
|
+
return scanRoots(roots, displayRoots, signal, diagnostics);
|
|
383
|
+
};
|
|
384
|
+
return {
|
|
385
|
+
catalog,
|
|
386
|
+
read: async (name, file, signal) => {
|
|
387
|
+
z.string().min(1).parse(name);
|
|
388
|
+
const found = (await catalog(signal)).skills.find((skill) => skill.name === name);
|
|
389
|
+
if (!found)
|
|
390
|
+
throw new Error(`Unknown or hidden skill: ${name}`);
|
|
391
|
+
const baseDirectory = dirname(found.location);
|
|
392
|
+
const selected = file === undefined ? "SKILL.md" : fileSchema.parse(file);
|
|
393
|
+
const path = contained(baseDirectory, await realpath(join(baseDirectory, selected)));
|
|
394
|
+
const content = await readBounded(path, signal);
|
|
395
|
+
if (selected !== "SKILL.md")
|
|
396
|
+
return {
|
|
397
|
+
name,
|
|
398
|
+
file: selected,
|
|
399
|
+
content,
|
|
400
|
+
baseDirectory,
|
|
401
|
+
supportingFiles: [],
|
|
402
|
+
};
|
|
403
|
+
const parsed = parseSkill(content, baseDirectory);
|
|
404
|
+
if (parsed.metadata["disable-model-invocation"])
|
|
405
|
+
throw new Error(`Hidden skill: ${name}`);
|
|
406
|
+
const supportingFiles = [];
|
|
407
|
+
let entries = 0;
|
|
408
|
+
const walk = async (directory, depth) => {
|
|
409
|
+
if (depth > maximumSupportingDepth)
|
|
410
|
+
throw new Error("Skill supporting files exceed depth bound");
|
|
411
|
+
for await (const entry of await opendir(directory)) {
|
|
412
|
+
signal.throwIfAborted();
|
|
413
|
+
if (++entries > maximumDirectoryEntries)
|
|
414
|
+
throw new Error("Skill supporting files exceed entry bound");
|
|
415
|
+
const path = contained(baseDirectory, await realpath(join(directory, entry.name)));
|
|
416
|
+
// Directory symlinks are not traversed; direct reads still enforce realpath containment.
|
|
417
|
+
if (entry.isDirectory())
|
|
418
|
+
await walk(path, depth + 1);
|
|
419
|
+
else if ((await stat(path)).isFile() && path !== found.location)
|
|
420
|
+
supportingFiles.push(relative(baseDirectory, path));
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
await walk(baseDirectory, 0);
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
file: selected,
|
|
427
|
+
content: parsed.body,
|
|
428
|
+
baseDirectory,
|
|
429
|
+
supportingFiles: supportingFiles.sort(),
|
|
430
|
+
};
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type LocalFailureMetadata = Readonly<{
|
|
2
|
+
source: "local-runtime";
|
|
3
|
+
operation: string;
|
|
4
|
+
failureKind: "Error" | "NonErrorThrown";
|
|
5
|
+
}>;
|
|
6
|
+
/** The only persistable projection of a caught Local failure. */
|
|
7
|
+
export declare function localFailureMetadata(operation: string, cause: unknown): LocalFailureMetadata;
|
|
8
|
+
/** Retain the original Local failure without printing exception or input text. */
|
|
9
|
+
export declare function logEvent(operation: string, cause: unknown): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Redacted from "effect/Redacted";
|
|
2
|
+
/** The only persistable projection of a caught Local failure. */
|
|
3
|
+
export function localFailureMetadata(operation, cause) {
|
|
4
|
+
return {
|
|
5
|
+
source: "local-runtime",
|
|
6
|
+
operation,
|
|
7
|
+
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Retain the original Local failure without printing exception or input text. */
|
|
11
|
+
export function logEvent(operation, cause) {
|
|
12
|
+
globalThis.console.error(JSON.stringify(localFailureMetadata(operation, cause)), Redacted.make(cause));
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Local HTTP response budget, checked before text/JSON/schema decoding. */
|
|
2
|
+
export async function readBoundedResponse(response, maximumBytes, description) {
|
|
3
|
+
const declared = response.headers.get("content-length");
|
|
4
|
+
if (declared !== null) {
|
|
5
|
+
const declaredBytes = Number(declared);
|
|
6
|
+
if (!Number.isSafeInteger(declaredBytes) ||
|
|
7
|
+
declaredBytes < 0 ||
|
|
8
|
+
declaredBytes > maximumBytes)
|
|
9
|
+
throw new Error(`${description} response is too large`);
|
|
10
|
+
}
|
|
11
|
+
if (response.body === null)
|
|
12
|
+
throw new Error(`${description} returned no body`);
|
|
13
|
+
const stream = response.body.pipeThrough(new TransformStream());
|
|
14
|
+
const reader = stream.getReader();
|
|
15
|
+
const chunks = [];
|
|
16
|
+
let size = 0;
|
|
17
|
+
try {
|
|
18
|
+
for (;;) {
|
|
19
|
+
const next = await reader.read();
|
|
20
|
+
if (next.done)
|
|
21
|
+
break;
|
|
22
|
+
const bytes = next.value;
|
|
23
|
+
size += bytes.byteLength;
|
|
24
|
+
if (size > maximumBytes) {
|
|
25
|
+
await reader.cancel();
|
|
26
|
+
throw new Error(`${description} response is too large`);
|
|
27
|
+
}
|
|
28
|
+
chunks.push(bytes);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
reader.releaseLock();
|
|
33
|
+
}
|
|
34
|
+
const joined = new Uint8Array(size);
|
|
35
|
+
let offset = 0;
|
|
36
|
+
for (const chunk of chunks) {
|
|
37
|
+
joined.set(chunk, offset);
|
|
38
|
+
offset += chunk.byteLength;
|
|
39
|
+
}
|
|
40
|
+
return new TextDecoder().decode(joined);
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RpcTarget } from "@fungi.computer/capnweb";
|
|
2
|
+
import type { PrivateLocalShiitakeServer } from "./private-local-shiitake-host.js";
|
|
3
|
+
import { type PrivateLocalShiitakeWorkerBatch, type PrivateLocalShiitakeWorkerInput } from "./private-local-shiitake-host.js";
|
|
4
|
+
/** Retained native Bun listener, now owned beside the persistent Local service. */
|
|
5
|
+
export declare function openPrivateLocalBunServer(fetch: (request: Request) => Response | Promise<Response>, listenAddress?: string, auth?: Readonly<{
|
|
6
|
+
path: string;
|
|
7
|
+
admit: (request: Request) => boolean;
|
|
8
|
+
createTarget: () => RpcTarget;
|
|
9
|
+
}>): PrivateLocalShiitakeServer;
|
|
10
|
+
/** Retained static worker server; persistent service callers use the manager. */
|
|
11
|
+
export declare function openPrivateLocalBunShiitakeWorkerBatch(inputs: readonly PrivateLocalShiitakeWorkerInput[], listenAddress: string): Promise<PrivateLocalShiitakeWorkerBatch>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
import { serve } from "bun";
|
|
3
|
+
import { dispatchPrivateLocalRequest } from "./private-local-http-dispatch.js";
|
|
4
|
+
import { newBunWebSocketRpcHandler, } from "@fungi.computer/capnweb";
|
|
5
|
+
import { openPrivateLocalShiitakeWorkerBatch, } from "./private-local-shiitake-host.js";
|
|
6
|
+
function parsePrivateLoopbackListener(value) {
|
|
7
|
+
const url = new URL(value);
|
|
8
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
9
|
+
const port = Number(url.port);
|
|
10
|
+
if (url.protocol !== "http:" ||
|
|
11
|
+
!loopback ||
|
|
12
|
+
url.username !== "" ||
|
|
13
|
+
url.password !== "" ||
|
|
14
|
+
url.pathname !== "/" ||
|
|
15
|
+
url.search !== "" ||
|
|
16
|
+
url.hash !== "" ||
|
|
17
|
+
!Number.isInteger(port) ||
|
|
18
|
+
port < 1 ||
|
|
19
|
+
port > 65_535)
|
|
20
|
+
throw new Error("Private local Shiitake listener requires loopback HTTP with an explicit port");
|
|
21
|
+
return {
|
|
22
|
+
hostname: url.hostname === "127.0.0.1" ? "127.0.0.1" : "::1",
|
|
23
|
+
port,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Retained native Bun listener, now owned beside the persistent Local service. */
|
|
27
|
+
export function openPrivateLocalBunServer(fetch, listenAddress, auth) {
|
|
28
|
+
const listener = listenAddress === undefined
|
|
29
|
+
? { hostname: "127.0.0.1", port: 0 }
|
|
30
|
+
: parsePrivateLoopbackListener(listenAddress);
|
|
31
|
+
// A model retry can leave SSE quiet, and command admission/cancellation can
|
|
32
|
+
// await owner cleanup. Bun's socket-idle clock must not sever either while
|
|
33
|
+
// the handler is pending. Restore its ordinary idle limit for finite replies.
|
|
34
|
+
const dispatch = async (request, server) => {
|
|
35
|
+
return dispatchPrivateLocalRequest(request, server, fetch);
|
|
36
|
+
};
|
|
37
|
+
const server = auth === undefined
|
|
38
|
+
? serve({ ...listener, fetch: dispatch })
|
|
39
|
+
: serve({
|
|
40
|
+
...listener,
|
|
41
|
+
websocket: newBunWebSocketRpcHandler(auth.createTarget),
|
|
42
|
+
fetch(request, server) {
|
|
43
|
+
if (new URL(request.url).pathname === auth.path) {
|
|
44
|
+
if (!auth.admit(request))
|
|
45
|
+
return new Response(null, { status: 403 });
|
|
46
|
+
if (request.headers.get("upgrade")?.toLowerCase() !== "websocket")
|
|
47
|
+
return new Response(null, { status: 426 });
|
|
48
|
+
if (server.upgrade(request, { data: undefined }))
|
|
49
|
+
return;
|
|
50
|
+
return new Response(null, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
return dispatch(request, server);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
baseUrl: server.url.origin,
|
|
57
|
+
close: () => server.stop(true),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Retained static worker server; persistent service callers use the manager. */
|
|
61
|
+
export async function openPrivateLocalBunShiitakeWorkerBatch(inputs, listenAddress) {
|
|
62
|
+
// Validate the native listener before any Local owner opens its store.
|
|
63
|
+
parsePrivateLoopbackListener(listenAddress);
|
|
64
|
+
return openPrivateLocalShiitakeWorkerBatch(inputs, (fetch) => openPrivateLocalBunServer(fetch, listenAddress));
|
|
65
|
+
}
|
|
66
|
+
/// <reference types="bun-types" />
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type MachineEnrollmentRecord, type LocalMachineEnrollment } from "./private-local-mesh-record.js";
|
|
2
|
+
import type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from "@earendil-works/pi-ai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export declare const maximumCredentialStringLength: number;
|
|
5
|
+
export declare const oauthCredentialSchema: z.ZodObject<{
|
|
6
|
+
type: z.ZodLiteral<"oauth">;
|
|
7
|
+
refresh: z.ZodString;
|
|
8
|
+
access: z.ZodString;
|
|
9
|
+
expires: z.ZodNumber;
|
|
10
|
+
}, z.core.$catchall<z.ZodJSONSchema>>;
|
|
11
|
+
export type AtomicWrite = (path: string, content: string, options: Readonly<{
|
|
12
|
+
encoding: BufferEncoding;
|
|
13
|
+
fsync: boolean;
|
|
14
|
+
mode: number;
|
|
15
|
+
}>) => Promise<void>;
|
|
16
|
+
/** The signed-in credential that must still own an enrollment origin. */
|
|
17
|
+
export type MachineEnrollmentOwner = Readonly<{
|
|
18
|
+
providerId: string;
|
|
19
|
+
/** Throws unless this credential owns the origin being edited. */
|
|
20
|
+
admit: (current: Credential | undefined) => void;
|
|
21
|
+
}>;
|
|
22
|
+
/** A host-private Pi credential store selected by one absolute filesystem path. */
|
|
23
|
+
export type PrivateCredentialStore = CredentialStore & {
|
|
24
|
+
readMachineEnrollments(): Promise<MachineEnrollmentRecord[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Edit one origin's enrollments under the file lock. An empty result removes
|
|
27
|
+
* the record. When the origin belongs to a signed-in credential, `owner`
|
|
28
|
+
* admits that credential under the same lock before anything is written.
|
|
29
|
+
*/
|
|
30
|
+
modifyMachineEnrollments(origin: string, edit: (current: readonly LocalMachineEnrollment[]) => LocalMachineEnrollment[], owner?: MachineEnrollmentOwner): Promise<void>;
|
|
31
|
+
removeMachineEnrollments(origin: string): Promise<boolean>;
|
|
32
|
+
/** Stable host identity supplied to Pi's ChatGPT sign-in, never a credential. */
|
|
33
|
+
getDeviceId(): Promise<string>;
|
|
34
|
+
/** Revoke or validate current custody under the same lock, then delete it atomically. */
|
|
35
|
+
remove<T>(providerId: string, beforeRemove: (credential: Credential) => Promise<T>, options?: AuthOperationOptions): Promise<T | undefined>;
|
|
36
|
+
};
|
|
37
|
+
/** Resolve the host-owned default path without consulting project or Agent Home state. */
|
|
38
|
+
export declare function resolveDefaultCredentialPath(home: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Internal constructor permits one focused write-failure test while the public
|
|
41
|
+
* package surface remains the Pi CredentialStore factory below.
|
|
42
|
+
*/
|
|
43
|
+
export declare class FileCredentialStore implements PrivateCredentialStore {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(path: string, writeAtomic?: AtomicWrite);
|
|
46
|
+
/** Keep the installation UUID beside credentials, across logout and restart. */
|
|
47
|
+
getDeviceId(): Promise<string>;
|
|
48
|
+
read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined>;
|
|
49
|
+
list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]>;
|
|
50
|
+
modify(providerId: string, callback: (current: Credential | undefined) => Promise<Credential | undefined>, options?: AuthOperationOptions): Promise<Credential | undefined>;
|
|
51
|
+
delete(providerId: string, options?: AuthOperationOptions): Promise<void>;
|
|
52
|
+
readMachineEnrollments(): Promise<MachineEnrollmentRecord[]>;
|
|
53
|
+
modifyMachineEnrollments(origin: string, edit: (current: readonly LocalMachineEnrollment[]) => LocalMachineEnrollment[], owner?: MachineEnrollmentOwner): Promise<void>;
|
|
54
|
+
removeMachineEnrollments(origin: string): Promise<boolean>;
|
|
55
|
+
remove<T>(providerId: string, beforeRemove: (credential: Credential) => Promise<T>, options?: AuthOperationOptions): Promise<T | undefined>;
|
|
56
|
+
}
|
|
57
|
+
/** Create the one private Pi CredentialStore selected by the local host. */
|
|
58
|
+
export declare function createPrivateCredentialStore(path: string): PrivateCredentialStore;
|