@caplets/core 0.18.9 → 0.19.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/dist/attach/options.d.ts +10 -0
- package/dist/attach/server.d.ts +5 -0
- package/dist/caplet-files-bundle.d.ts +415 -0
- package/dist/caplet-files.d.ts +3 -266
- package/dist/caplet-source/bundle.d.ts +7 -0
- package/dist/caplet-source/filesystem.d.ts +7 -0
- package/dist/caplet-source/filesystem.js +2 -0
- package/dist/caplet-source/index.d.ts +4 -0
- package/dist/caplet-source/parse.d.ts +35 -0
- package/dist/caplet-source/types.d.ts +9 -0
- package/dist/caplet-source.js +11231 -0
- package/dist/cli/commands.d.ts +6 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/setup-caplet.d.ts +12 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli.d.ts +7 -1
- package/dist/cloud/apply.d.ts +36 -0
- package/dist/cloud/client.d.ts +30 -0
- package/dist/cloud/presence.d.ts +29 -0
- package/dist/cloud/project-root.d.ts +2 -0
- package/dist/cloud/runtime-adapter.d.ts +23 -0
- package/dist/cloud/runtime-http.d.ts +6 -0
- package/dist/cloud/sync.d.ts +10 -0
- package/dist/cloud-auth/client.d.ts +42 -0
- package/dist/cloud-auth/errors.d.ts +11 -0
- package/dist/cloud-auth/open-url.d.ts +7 -0
- package/dist/cloud-auth/store.d.ts +35 -0
- package/dist/cloud-auth/types.d.ts +66 -0
- package/dist/{completion-RqzHpHRY.js → completion-brgziz4L.js} +17 -1
- package/dist/config-runtime.d.ts +174 -0
- package/dist/config-runtime.js +392 -0
- package/dist/config.d.ts +42 -0
- package/dist/filesystem-Kkg32TOJ.js +66 -0
- package/dist/generated-tool-input-schema.js +161 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.js +2999 -266
- package/dist/native/options.d.ts +22 -3
- package/dist/native/remote.d.ts +2 -1
- package/dist/native/service.d.ts +7 -3
- package/dist/native.js +2 -430
- package/dist/project-binding/attach.d.ts +46 -0
- package/dist/project-binding/errors.d.ts +17 -0
- package/dist/project-binding/gitignore.d.ts +5 -0
- package/dist/project-binding/mutagen.d.ts +65 -0
- package/dist/project-binding/routes.d.ts +9 -0
- package/dist/project-binding/session.d.ts +82 -0
- package/dist/project-binding/sync-filter.d.ts +19 -0
- package/dist/project-binding/sync-size.d.ts +27 -0
- package/dist/project-binding/transport.d.ts +21 -0
- package/dist/project-binding/types.d.ts +31 -0
- package/dist/project-binding/workspaces.d.ts +60 -0
- package/dist/remote/options.d.ts +42 -0
- package/dist/remote/selection.d.ts +26 -0
- package/dist/runtime-plan/features.d.ts +7 -0
- package/dist/runtime-plan/index.d.ts +4 -0
- package/dist/runtime-plan/planner.d.ts +5 -0
- package/dist/runtime-plan/resources.d.ts +11 -0
- package/dist/runtime-plan/types.d.ts +82 -0
- package/dist/runtime-plan.js +275 -0
- package/dist/{generated-tool-input-schema--kVuUNc5.js → schemas-BZ6BBrh7.js} +1 -161
- package/dist/serve/daemon/config.d.ts +8 -0
- package/dist/serve/daemon/index.d.ts +16 -0
- package/dist/serve/daemon/paths.d.ts +3 -0
- package/dist/serve/daemon/platform-darwin.d.ts +2 -0
- package/dist/serve/daemon/platform-linux.d.ts +2 -0
- package/dist/serve/daemon/platform-windows.d.ts +2 -0
- package/dist/serve/daemon/platform.d.ts +9 -0
- package/dist/serve/daemon/process.d.ts +5 -0
- package/dist/serve/daemon/types.d.ts +86 -0
- package/dist/serve/http.d.ts +8 -0
- package/dist/serve/index.d.ts +5 -1
- package/dist/serve/native-session.d.ts +19 -0
- package/dist/serve/options.d.ts +1 -0
- package/dist/server/options.d.ts +1 -1
- package/dist/{options-DnOUjft1.js → service-BXcE4Rv8.js} +9891 -2182
- package/dist/setup/hash.d.ts +3 -0
- package/dist/setup/local-store.d.ts +34 -0
- package/dist/setup/runner.d.ts +40 -0
- package/dist/setup/types.d.ts +52 -0
- package/dist/tools.d.ts +16 -1
- package/dist/validation-BBG4skZf.js +153 -0
- package/package.json +20 -4
package/dist/index.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { $ as ElicitResultSchema, $t as markdownStructuredContent, A as capabilityDescription, At as objectFromShape, B as assertClientRequestTaskCapability, C as migrateCredentials, Ct as getLiteralValue, D as CapletsEngine, Dt as isSchemaOptional, E as fingerprintProjectRoot, Et as getSchemaDescription, F as deleteTokenBundle, Ft as loadGlobalConfig, G as toJsonSchemaCompat, Gt as resolveConfigPath, H as AjvJsonSchemaValidator, Ht as defaultConfigBaseDir, I as isTokenBundleExpired, It as loadLocalOverlayConfigWithSources, J as CompleteRequestSchema, Jt as discoverCapletFiles, K as CallToolRequestSchema, Kt as resolveProjectCapletsRoot, L as readTokenBundle, Lt as loadProjectConfig, M as runOAuthFlow, Mt as safeParseAsync, N as startGenericOAuthFlow, Nt as loadConfig, O as handleServerTool, Ot as isZ4Schema, P as startOAuthFlow, Pt as loadConfigWithSources, Qt as markdownCallToolResultContent, R as ReadBuffer, Rt as parseConfig, S as cloudAuthPath, St as isJSONRPCResultResponse, T as findProjectRoot, Tt as getParseErrorMessage, U as Protocol, Ut as defaultStateBaseDir, V as assertToolsCallTaskCapability, Vt as defaultCacheBaseDir, W as mergeCapabilities, Wt as resolveCapletsRoot, X as CreateMessageResultWithToolsSchema, Xt as loadCapletFilesFromMap, Y as CreateMessageResultSchema, Yt as validateCapletFile, Z as CreateTaskResultSchema, Zt as hasRenderableStructuredContent, _ as controlUrlForBase, _t as assertCompleteRequestPrompt, a as createSdkRemoteCapletsClient, at as JSONRPCMessageSchema, b as resolveCapletsServer, bt as isJSONRPCErrorResponse, ct as ListResourceTemplatesRequestSchema, d as PROJECT_BINDING_ERROR_CODES, dt as ListToolsRequestSchema, et as EmptyResultSchema, f as ProjectBindingError, ft as LoggingLevelSchema, g as resolveCapletsRemote, gt as SetLevelRequestSchema, h as CloudAuthClient, ht as SUPPORTED_PROTOCOL_VERSIONS, it as InitializedNotificationSchema, j as runGenericOAuthFlow, jt as safeParse, k as ServerRegistry, kt as normalizeObjectSchema, lt as ListResourcesRequestSchema, m as projectBindingRecovery, mt as ReadResourceRequestSchema, n as buildProjectSyncManifest, nt as GetPromptRequestSchema, ot as LATEST_PROTOCOL_VERSION, p as projectBindingError, pt as McpError, q as CallToolResultSchema, qt as resolveProjectConfigPath, rt as InitializeRequestSchema, st as ListPromptsRequestSchema, t as createNativeCapletsService, tt as ErrorCode, u as resolveRemoteSelection, ut as ListRootsResultSchema, v as parseServerBaseUrl, vt as assertCompleteRequestResourceTemplate, w as redactedCloudAuthStatus, wt as getObjectShape, x as CloudAuthStore, xt as isJSONRPCRequest, y as resolveCapletsMode, yt as isInitializeRequest, z as serializeMessage } from "./service-BXcE4Rv8.js";
|
|
2
|
+
import { f as redactSecrets, i as SERVER_ID_PATTERN, l as CAPLETS_ERROR_CODES, p as toSafeError, u as CapletsError } from "./validation-BBG4skZf.js";
|
|
3
|
+
import { _ as record, b as unknown, d as literal, m as object, n as ZodOptional, o as array, p as number, r as _enum, s as boolean, v as string, x as url } from "./schemas-BZ6BBrh7.js";
|
|
4
|
+
import { generatedToolInputSchema, generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema.js";
|
|
5
|
+
import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands$1, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-brgziz4L.js";
|
|
6
|
+
import { n as normalizeCapletSourcePath, t as FilesystemCapletSource } from "./filesystem-Kkg32TOJ.js";
|
|
7
|
+
import { parseConfig as parseConfig$1 } from "./config-runtime.js";
|
|
4
8
|
import fs, { accessSync, chmodSync, closeSync, constants, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
5
|
-
import path, { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
6
|
-
import childProcess, { execFile, execFileSync } from "node:child_process";
|
|
9
|
+
import path, { basename, dirname, isAbsolute, join, parse, posix, relative, resolve, sep, win32 } from "node:path";
|
|
10
|
+
import childProcess, { execFile, execFileSync, spawn } from "node:child_process";
|
|
11
|
+
import { homedir, tmpdir } from "node:os";
|
|
7
12
|
import process$1, { stdin, stdout } from "node:process";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
13
|
import { Readable } from "node:stream";
|
|
10
14
|
import { STATUS_CODES, createServer } from "node:http";
|
|
11
|
-
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
15
|
+
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
12
16
|
import { EventEmitter } from "node:events";
|
|
13
17
|
import { promisify, stripVTControlCharacters } from "node:util";
|
|
14
18
|
import { createInterface } from "node:readline/promises";
|
|
19
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
15
20
|
import { Http2ServerRequest, constants as constants$1 } from "node:http2";
|
|
16
21
|
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
|
|
17
22
|
/**
|
|
@@ -1322,7 +1327,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
1322
1327
|
} };
|
|
1323
1328
|
//#endregion
|
|
1324
1329
|
//#region package.json
|
|
1325
|
-
var version = "0.
|
|
1330
|
+
var version = "0.19.0";
|
|
1326
1331
|
//#endregion
|
|
1327
1332
|
//#region src/serve/session.ts
|
|
1328
1333
|
var CapletsMcpSession = class {
|
|
@@ -4741,11 +4746,14 @@ function renderLocalPaths(fields, outputDir) {
|
|
|
4741
4746
|
});
|
|
4742
4747
|
}
|
|
4743
4748
|
function localPathRelativeToOutput(path, outputDir) {
|
|
4744
|
-
const absolutePath = resolve(path);
|
|
4745
|
-
const rendered = relative(outputDir,
|
|
4749
|
+
const absolutePath = displayPath(resolve(path));
|
|
4750
|
+
const rendered = relative(outputDir, absolutePath);
|
|
4746
4751
|
if (rendered.startsWith("../..") || rendered.startsWith("..\\..")) return absolutePath;
|
|
4747
4752
|
return rendered === "" ? "." : rendered;
|
|
4748
4753
|
}
|
|
4754
|
+
function displayPath(path) {
|
|
4755
|
+
return path;
|
|
4756
|
+
}
|
|
4749
4757
|
function rejectUnsafeDestinationParents(path) {
|
|
4750
4758
|
const parent = dirname(resolve(path));
|
|
4751
4759
|
const root = parse(parent).root;
|
|
@@ -4755,10 +4763,22 @@ function rejectUnsafeDestinationParents(path) {
|
|
|
4755
4763
|
current = join(current, segment);
|
|
4756
4764
|
const stats = lstatIfExists$1(current);
|
|
4757
4765
|
if (!stats) return;
|
|
4758
|
-
if (stats.isSymbolicLink())
|
|
4766
|
+
if (stats.isSymbolicLink()) {
|
|
4767
|
+
if (isDarwinSystemAliasSymlink$1(current)) continue;
|
|
4768
|
+
throw new CapletsError("CONFIG_EXISTS", `Output parent path ${current} is a symlink; remove it before writing`);
|
|
4769
|
+
}
|
|
4759
4770
|
if (!stats.isDirectory()) throw new CapletsError("CONFIG_EXISTS", `Output parent path ${current} is not a directory; choose a file path`);
|
|
4760
4771
|
}
|
|
4761
4772
|
}
|
|
4773
|
+
function isDarwinSystemAliasSymlink$1(path) {
|
|
4774
|
+
if (process.platform !== "darwin") return false;
|
|
4775
|
+
if (path !== "/var" && path !== "/tmp") return false;
|
|
4776
|
+
try {
|
|
4777
|
+
return realpathSync(path) === `/private${path}`;
|
|
4778
|
+
} catch {
|
|
4779
|
+
return false;
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4762
4782
|
function lstatIfExists$1(path) {
|
|
4763
4783
|
try {
|
|
4764
4784
|
return lstatSync(path);
|
|
@@ -5009,6 +5029,344 @@ function starterConfig() {
|
|
|
5009
5029
|
}, null, 2);
|
|
5010
5030
|
}
|
|
5011
5031
|
//#endregion
|
|
5032
|
+
//#region src/project-binding/workspaces.ts
|
|
5033
|
+
const DEFAULT_STALE_LEASE_TTL_MS = 120 * 1e3;
|
|
5034
|
+
const DEFAULT_INACTIVE_WORKSPACE_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
5035
|
+
const DEFAULT_SOFT_DISK_CAP_BYTES = 10 * 1024 * 1024 * 1024;
|
|
5036
|
+
function projectBindingWorkspaceRoot(options = {}) {
|
|
5037
|
+
if (options.root) return options.root;
|
|
5038
|
+
const platform = options.platform ?? process.platform;
|
|
5039
|
+
const home = options.homedir ?? homedir();
|
|
5040
|
+
const env = options.env ?? process.env;
|
|
5041
|
+
if (platform === "win32") {
|
|
5042
|
+
const base = env.LOCALAPPDATA && win32.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : win32.join(home, "AppData", "Local");
|
|
5043
|
+
return win32.join(base, "Caplets", "State", "workspaces");
|
|
5044
|
+
}
|
|
5045
|
+
const base = env.XDG_STATE_HOME && posix.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : posix.join(home, ".local", "state");
|
|
5046
|
+
return posix.join(base, "caplets", "workspaces");
|
|
5047
|
+
}
|
|
5048
|
+
function projectBindingWorkspacePaths(projectFingerprint, options = {}) {
|
|
5049
|
+
assertPathSegment(projectFingerprint, "project fingerprint");
|
|
5050
|
+
const pathJoin = pathJoinFor(options.platform);
|
|
5051
|
+
const root = pathJoin(projectBindingWorkspaceRoot(options), projectFingerprint);
|
|
5052
|
+
const leases = pathJoin(root, "leases");
|
|
5053
|
+
const setup = pathJoin(root, "setup");
|
|
5054
|
+
return {
|
|
5055
|
+
projectFingerprint,
|
|
5056
|
+
root,
|
|
5057
|
+
project: pathJoin(root, "project"),
|
|
5058
|
+
metadata: pathJoin(root, "metadata.json"),
|
|
5059
|
+
leases,
|
|
5060
|
+
setup,
|
|
5061
|
+
setupReceipts: pathJoin(setup, "receipts.json"),
|
|
5062
|
+
lease(bindingId) {
|
|
5063
|
+
assertPathSegment(bindingId, "binding ID");
|
|
5064
|
+
return pathJoin(leases, `${bindingId}.json`);
|
|
5065
|
+
}
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5068
|
+
var ProjectBindingWorkspaceStore = class {
|
|
5069
|
+
options;
|
|
5070
|
+
root;
|
|
5071
|
+
now;
|
|
5072
|
+
staleLeaseTtlMs;
|
|
5073
|
+
inactiveWorkspaceTtlMs;
|
|
5074
|
+
softDiskCapBytes;
|
|
5075
|
+
workspaceSizeBytes;
|
|
5076
|
+
constructor(options = {}) {
|
|
5077
|
+
this.options = options;
|
|
5078
|
+
this.root = projectBindingWorkspaceRoot(options);
|
|
5079
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
5080
|
+
this.staleLeaseTtlMs = options.staleLeaseTtlMs ?? DEFAULT_STALE_LEASE_TTL_MS;
|
|
5081
|
+
this.inactiveWorkspaceTtlMs = options.inactiveWorkspaceTtlMs ?? DEFAULT_INACTIVE_WORKSPACE_TTL_MS;
|
|
5082
|
+
this.softDiskCapBytes = options.softDiskCapBytes ?? DEFAULT_SOFT_DISK_CAP_BYTES;
|
|
5083
|
+
this.workspaceSizeBytes = options.workspaceSizeBytes ?? ((paths) => directorySize(paths.root));
|
|
5084
|
+
}
|
|
5085
|
+
paths(projectFingerprint) {
|
|
5086
|
+
return projectBindingWorkspacePaths(projectFingerprint, {
|
|
5087
|
+
...this.options,
|
|
5088
|
+
root: this.root
|
|
5089
|
+
});
|
|
5090
|
+
}
|
|
5091
|
+
async ensureWorkspace(input) {
|
|
5092
|
+
const paths = this.paths(input.projectFingerprint);
|
|
5093
|
+
const now = this.now().toISOString();
|
|
5094
|
+
const existing = this.readMetadata(input.projectFingerprint);
|
|
5095
|
+
const metadata = {
|
|
5096
|
+
projectFingerprint: input.projectFingerprint,
|
|
5097
|
+
projectRoot: input.projectRoot,
|
|
5098
|
+
createdAt: input.createdAt ?? existing?.createdAt ?? now,
|
|
5099
|
+
lastActiveAt: input.lastActiveAt ?? now
|
|
5100
|
+
};
|
|
5101
|
+
mkdirSync(paths.project, { recursive: true });
|
|
5102
|
+
mkdirSync(paths.leases, { recursive: true });
|
|
5103
|
+
mkdirSync(paths.setup, { recursive: true });
|
|
5104
|
+
writeJson$1(paths.metadata, metadata);
|
|
5105
|
+
return paths;
|
|
5106
|
+
}
|
|
5107
|
+
async writeLease(lease) {
|
|
5108
|
+
const paths = this.paths(lease.projectFingerprint);
|
|
5109
|
+
mkdirSync(paths.leases, { recursive: true });
|
|
5110
|
+
writeJson$1(paths.lease(lease.bindingId), lease);
|
|
5111
|
+
if (lease.active) {
|
|
5112
|
+
const metadata = this.readMetadata(lease.projectFingerprint);
|
|
5113
|
+
if (metadata) writeJson$1(paths.metadata, {
|
|
5114
|
+
...metadata,
|
|
5115
|
+
lastActiveAt: lease.updatedAt
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
async listLeases(projectFingerprint) {
|
|
5120
|
+
return this.leasesFor(this.paths(projectFingerprint)).map((entry) => entry.lease);
|
|
5121
|
+
}
|
|
5122
|
+
async writeSetupReceipts(projectFingerprint, receipts) {
|
|
5123
|
+
const paths = this.paths(projectFingerprint);
|
|
5124
|
+
mkdirSync(paths.setup, { recursive: true });
|
|
5125
|
+
writeJson$1(paths.setupReceipts, receipts);
|
|
5126
|
+
}
|
|
5127
|
+
async cleanup() {
|
|
5128
|
+
const expiredLeases = [];
|
|
5129
|
+
const deletedWorkspaces = [];
|
|
5130
|
+
const retainedWorkspaces = [];
|
|
5131
|
+
const candidates = [];
|
|
5132
|
+
for (const paths of this.workspacePaths()) {
|
|
5133
|
+
const leases = this.leasesFor(paths);
|
|
5134
|
+
for (const entry of leases) if (!entry.lease.active && this.isStaleLease(entry.lease)) {
|
|
5135
|
+
rmSync(entry.path, { force: true });
|
|
5136
|
+
expiredLeases.push(entry.path);
|
|
5137
|
+
}
|
|
5138
|
+
if (this.leasesFor(paths).some((entry) => entry.lease.active)) {
|
|
5139
|
+
retainedWorkspaces.push(paths.root);
|
|
5140
|
+
continue;
|
|
5141
|
+
}
|
|
5142
|
+
const metadata = this.readMetadata(paths.projectFingerprint);
|
|
5143
|
+
const lastActiveMs = metadata ? Date.parse(metadata.lastActiveAt) : workspaceMtime(paths.root);
|
|
5144
|
+
const sizeBytes = this.workspaceSizeBytes(paths);
|
|
5145
|
+
candidates.push({
|
|
5146
|
+
paths,
|
|
5147
|
+
lastActiveMs,
|
|
5148
|
+
sizeBytes
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
5151
|
+
for (const candidate of candidates) if (this.isInactiveWorkspace(candidate.lastActiveMs)) {
|
|
5152
|
+
rmSync(candidate.paths.root, {
|
|
5153
|
+
recursive: true,
|
|
5154
|
+
force: true
|
|
5155
|
+
});
|
|
5156
|
+
deletedWorkspaces.push(candidate.paths.root);
|
|
5157
|
+
}
|
|
5158
|
+
const remaining = candidates.filter((candidate) => !deletedWorkspaces.includes(candidate.paths.root)).sort((first, second) => first.lastActiveMs - second.lastActiveMs);
|
|
5159
|
+
let totalBytes = remaining.reduce((sum, candidate) => sum + candidate.sizeBytes, 0);
|
|
5160
|
+
for (const candidate of remaining) {
|
|
5161
|
+
if (totalBytes <= this.softDiskCapBytes) {
|
|
5162
|
+
retainedWorkspaces.push(candidate.paths.root);
|
|
5163
|
+
continue;
|
|
5164
|
+
}
|
|
5165
|
+
rmSync(candidate.paths.root, {
|
|
5166
|
+
recursive: true,
|
|
5167
|
+
force: true
|
|
5168
|
+
});
|
|
5169
|
+
deletedWorkspaces.push(candidate.paths.root);
|
|
5170
|
+
totalBytes -= candidate.sizeBytes;
|
|
5171
|
+
}
|
|
5172
|
+
return {
|
|
5173
|
+
expiredLeases,
|
|
5174
|
+
deletedWorkspaces,
|
|
5175
|
+
retainedWorkspaces
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
5178
|
+
readMetadata(projectFingerprint) {
|
|
5179
|
+
const path = this.paths(projectFingerprint).metadata;
|
|
5180
|
+
if (!existsSync(path)) return void 0;
|
|
5181
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
5182
|
+
}
|
|
5183
|
+
workspacePaths() {
|
|
5184
|
+
if (!existsSync(this.root)) return [];
|
|
5185
|
+
return readdirSync(this.root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => this.paths(entry.name));
|
|
5186
|
+
}
|
|
5187
|
+
leasesFor(paths) {
|
|
5188
|
+
if (!existsSync(paths.leases)) return [];
|
|
5189
|
+
return readdirSync(paths.leases, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => {
|
|
5190
|
+
const path = join(paths.leases, entry.name);
|
|
5191
|
+
return {
|
|
5192
|
+
path,
|
|
5193
|
+
lease: JSON.parse(readFileSync(path, "utf8"))
|
|
5194
|
+
};
|
|
5195
|
+
});
|
|
5196
|
+
}
|
|
5197
|
+
isStaleLease(lease) {
|
|
5198
|
+
const parsedExpiresAt = lease.expiresAt ? Date.parse(lease.expiresAt) : NaN;
|
|
5199
|
+
return (Number.isFinite(parsedExpiresAt) ? parsedExpiresAt : Date.parse(lease.updatedAt) + this.staleLeaseTtlMs) <= this.now().getTime();
|
|
5200
|
+
}
|
|
5201
|
+
isInactiveWorkspace(lastActiveMs) {
|
|
5202
|
+
return lastActiveMs + this.inactiveWorkspaceTtlMs <= this.now().getTime();
|
|
5203
|
+
}
|
|
5204
|
+
};
|
|
5205
|
+
function writeJson$1(path, value) {
|
|
5206
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
5207
|
+
}
|
|
5208
|
+
function directorySize(path) {
|
|
5209
|
+
if (!existsSync(path)) return 0;
|
|
5210
|
+
const stat = statSync(path);
|
|
5211
|
+
if (stat.isFile()) return stat.size;
|
|
5212
|
+
if (!stat.isDirectory()) return 0;
|
|
5213
|
+
return readdirSync(path).reduce((sum, entry) => sum + directorySize(join(path, entry)), 0);
|
|
5214
|
+
}
|
|
5215
|
+
function workspaceMtime(path) {
|
|
5216
|
+
return existsSync(path) ? statSync(path).mtimeMs : 0;
|
|
5217
|
+
}
|
|
5218
|
+
function pathJoinFor(platform = process.platform) {
|
|
5219
|
+
return platform === "win32" ? win32.join : join;
|
|
5220
|
+
}
|
|
5221
|
+
function assertPathSegment(value, label) {
|
|
5222
|
+
if (!value || value.includes("/") || value.includes("\\") || value.includes("\0") || value === "." || value === "..") throw new Error(`Invalid ${label}: ${value}`);
|
|
5223
|
+
}
|
|
5224
|
+
//#endregion
|
|
5225
|
+
//#region src/cli/doctor.ts
|
|
5226
|
+
async function doctorJsonReport(options = {}) {
|
|
5227
|
+
const env = options.env ?? process.env;
|
|
5228
|
+
const root = findProjectRoot(options.cwd ?? process.cwd());
|
|
5229
|
+
const projectFingerprint = fingerprintProjectRoot(root);
|
|
5230
|
+
const paths = projectBindingWorkspacePaths(projectFingerprint, { env });
|
|
5231
|
+
const server = resolveServerSection(env);
|
|
5232
|
+
const remote = resolveRemoteSection(env);
|
|
5233
|
+
const credentials = await (options.cloudAuthStore ?? new CloudAuthStore({ env })).load();
|
|
5234
|
+
return {
|
|
5235
|
+
server,
|
|
5236
|
+
remote,
|
|
5237
|
+
projectBinding: {
|
|
5238
|
+
state: "not_attached",
|
|
5239
|
+
projectRoot: root,
|
|
5240
|
+
projectFingerprint,
|
|
5241
|
+
workspacePath: paths.project,
|
|
5242
|
+
authMode: credentials ? "hosted_cloud" : remote.configured ? "self_hosted_remote" : "unconfigured",
|
|
5243
|
+
selectedWorkspace: credentials?.workspaceSlug ?? credentials?.workspaceId ?? remote.workspace ?? null,
|
|
5244
|
+
webSocketUrl: remote.webSocketUrl,
|
|
5245
|
+
lease: null,
|
|
5246
|
+
lastUpgradeError: null,
|
|
5247
|
+
recoveryCommand: credentials || remote.configured ? "caplets attach --once" : "caplets cloud auth login"
|
|
5248
|
+
},
|
|
5249
|
+
sync: {
|
|
5250
|
+
state: options.syncStatus?.state ?? "idle",
|
|
5251
|
+
diagnosticCode: options.syncStatus?.diagnosticCode ?? null,
|
|
5252
|
+
mutagenBinary: options.syncStatus?.mutagenBinary ?? "mutagen",
|
|
5253
|
+
mutagenVersion: options.syncStatus?.mutagenVersion ?? null,
|
|
5254
|
+
lastCommand: options.syncStatus?.lastCommand ?? null
|
|
5255
|
+
},
|
|
5256
|
+
daemon: {
|
|
5257
|
+
configured: false,
|
|
5258
|
+
running: false
|
|
5259
|
+
},
|
|
5260
|
+
cloudAuth: redactedCloudAuthStatus(credentials)
|
|
5261
|
+
};
|
|
5262
|
+
}
|
|
5263
|
+
async function formatDoctorReport(options = {}) {
|
|
5264
|
+
const report = await doctorJsonReport(options);
|
|
5265
|
+
return `${[
|
|
5266
|
+
"Server hosting",
|
|
5267
|
+
` Configured: ${yesNo(Boolean(report.server.configured))}`,
|
|
5268
|
+
...report.server.configured ? [` Base URL: ${report.server.baseUrl}`] : [],
|
|
5269
|
+
"",
|
|
5270
|
+
"Remote client",
|
|
5271
|
+
` Configured: ${yesNo(Boolean(report.remote.configured))}`,
|
|
5272
|
+
...report.remote.configured ? [
|
|
5273
|
+
` MCP URL: ${report.remote.mcpUrl}`,
|
|
5274
|
+
` Control URL: ${report.remote.controlUrl}`,
|
|
5275
|
+
` Health URL: ${report.remote.healthUrl}`,
|
|
5276
|
+
` WebSocket URL: ${report.remote.webSocketUrl}`,
|
|
5277
|
+
` Auth: ${report.remote.auth}`
|
|
5278
|
+
] : [],
|
|
5279
|
+
"",
|
|
5280
|
+
"Project Binding",
|
|
5281
|
+
` State: ${report.projectBinding.state}`,
|
|
5282
|
+
` Project root: ${report.projectBinding.projectRoot}`,
|
|
5283
|
+
` Project fingerprint: ${report.projectBinding.projectFingerprint}`,
|
|
5284
|
+
` Workspace path: ${report.projectBinding.workspacePath}`,
|
|
5285
|
+
` Auth mode: ${report.projectBinding.authMode}`,
|
|
5286
|
+
` Selected Workspace: ${report.projectBinding.selectedWorkspace ?? "none"}`,
|
|
5287
|
+
` Binding Session: ${report.projectBinding.state}`,
|
|
5288
|
+
` Recovery: ${report.projectBinding.recoveryCommand}`,
|
|
5289
|
+
"",
|
|
5290
|
+
"Project sync",
|
|
5291
|
+
` State: ${report.sync.state}`,
|
|
5292
|
+
` Mutagen: ${report.sync.mutagenVersion ?? report.sync.mutagenBinary}`,
|
|
5293
|
+
...report.sync.diagnosticCode ? [` Diagnostic: ${report.sync.diagnosticCode}`] : [],
|
|
5294
|
+
"",
|
|
5295
|
+
"Daemon",
|
|
5296
|
+
` Running: ${yesNo(Boolean(report.daemon.running))}`,
|
|
5297
|
+
"",
|
|
5298
|
+
"Cloud Auth",
|
|
5299
|
+
` Authenticated: ${yesNo(Boolean(report.cloudAuth.authenticated))}`,
|
|
5300
|
+
...report.cloudAuth.cloudUrl ? [` Cloud URL: ${report.cloudAuth.cloudUrl}`] : [],
|
|
5301
|
+
...report.cloudAuth.workspaceSlug || report.cloudAuth.workspaceId ? [` Selected Workspace: ${report.cloudAuth.workspaceSlug ?? report.cloudAuth.workspaceId}`] : []
|
|
5302
|
+
].join("\n")}\n`;
|
|
5303
|
+
}
|
|
5304
|
+
function resolveServerSection(env) {
|
|
5305
|
+
try {
|
|
5306
|
+
const server = resolveCapletsServer({}, env);
|
|
5307
|
+
return {
|
|
5308
|
+
configured: true,
|
|
5309
|
+
baseUrl: server.baseUrl.href,
|
|
5310
|
+
mcpUrl: server.mcpUrl.href,
|
|
5311
|
+
controlUrl: server.controlUrl.href,
|
|
5312
|
+
healthUrl: server.healthUrl.href,
|
|
5313
|
+
auth: server.auth.enabled ? "basic" : "none"
|
|
5314
|
+
};
|
|
5315
|
+
} catch {
|
|
5316
|
+
return { configured: false };
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
function resolveRemoteSection(env) {
|
|
5320
|
+
try {
|
|
5321
|
+
const remote = resolveCapletsRemote({}, env);
|
|
5322
|
+
return {
|
|
5323
|
+
configured: true,
|
|
5324
|
+
baseUrl: remote.baseUrl.href,
|
|
5325
|
+
mcpUrl: remote.mcpUrl.href,
|
|
5326
|
+
controlUrl: remote.controlUrl.href,
|
|
5327
|
+
healthUrl: remote.healthUrl.href,
|
|
5328
|
+
webSocketUrl: remote.projectBindingWebSocketUrl.href,
|
|
5329
|
+
auth: remote.auth.type,
|
|
5330
|
+
tokenPresent: remote.auth.type === "bearer",
|
|
5331
|
+
workspace: remote.workspace ?? null
|
|
5332
|
+
};
|
|
5333
|
+
} catch {
|
|
5334
|
+
return { configured: false };
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
function yesNo(value) {
|
|
5338
|
+
return value ? "yes" : "no";
|
|
5339
|
+
}
|
|
5340
|
+
//#endregion
|
|
5341
|
+
//#region src/cloud-auth/open-url.ts
|
|
5342
|
+
async function openBrowserUrl(url, options = {}) {
|
|
5343
|
+
if (options.opener) return await options.opener(url);
|
|
5344
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
5345
|
+
const args = process.platform === "win32" ? [
|
|
5346
|
+
"/c",
|
|
5347
|
+
"start",
|
|
5348
|
+
"",
|
|
5349
|
+
url
|
|
5350
|
+
] : [url];
|
|
5351
|
+
return await new Promise((resolve) => {
|
|
5352
|
+
const child = spawn(command, args, {
|
|
5353
|
+
detached: true,
|
|
5354
|
+
stdio: "ignore"
|
|
5355
|
+
});
|
|
5356
|
+
child.once("error", () => resolve({
|
|
5357
|
+
opened: false,
|
|
5358
|
+
command
|
|
5359
|
+
}));
|
|
5360
|
+
child.once("spawn", () => {
|
|
5361
|
+
child.unref();
|
|
5362
|
+
resolve({
|
|
5363
|
+
opened: true,
|
|
5364
|
+
command
|
|
5365
|
+
});
|
|
5366
|
+
});
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
//#endregion
|
|
5012
5370
|
//#region src/cli/install.ts
|
|
5013
5371
|
function installCaplets(repo, options = {}) {
|
|
5014
5372
|
const source = resolveInstallSource(repo);
|
|
@@ -5118,10 +5476,22 @@ function rejectUnsafeInstallParents(path) {
|
|
|
5118
5476
|
current = join(current, segment);
|
|
5119
5477
|
const stats = lstatIfExists(current);
|
|
5120
5478
|
if (!stats) return;
|
|
5121
|
-
if (stats.isSymbolicLink())
|
|
5479
|
+
if (stats.isSymbolicLink()) {
|
|
5480
|
+
if (isDarwinSystemAliasSymlink(current)) continue;
|
|
5481
|
+
throw new CapletsError("CONFIG_EXISTS", `Install destination parent ${current} is a symlink; remove it before installing`);
|
|
5482
|
+
}
|
|
5122
5483
|
if (!stats.isDirectory()) throw new CapletsError("CONFIG_EXISTS", `Install destination parent ${current} is not a directory; choose another destination`);
|
|
5123
5484
|
}
|
|
5124
5485
|
}
|
|
5486
|
+
function isDarwinSystemAliasSymlink(path) {
|
|
5487
|
+
if (process.platform !== "darwin") return false;
|
|
5488
|
+
if (path !== "/var" && path !== "/tmp") return false;
|
|
5489
|
+
try {
|
|
5490
|
+
return realpathSync(path) === `/private${path}`;
|
|
5491
|
+
} catch {
|
|
5492
|
+
return false;
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5125
5495
|
function rejectUnsafeInstallDestination(plan, force) {
|
|
5126
5496
|
const stats = lstatIfExists(plan.destination);
|
|
5127
5497
|
if (!stats) return;
|
|
@@ -5270,6 +5640,348 @@ function nearestExistingParent(path) {
|
|
|
5270
5640
|
return nearestExistingParent(parent);
|
|
5271
5641
|
}
|
|
5272
5642
|
//#endregion
|
|
5643
|
+
//#region src/setup/hash.ts
|
|
5644
|
+
function capletSetupContentHash(caplet) {
|
|
5645
|
+
return createHash("sha256").update(stableJson(stableCapletForHash(caplet))).digest("hex");
|
|
5646
|
+
}
|
|
5647
|
+
function stableJson(value) {
|
|
5648
|
+
return JSON.stringify(sortJson(value));
|
|
5649
|
+
}
|
|
5650
|
+
function stableCapletForHash(caplet) {
|
|
5651
|
+
return {
|
|
5652
|
+
server: caplet.server,
|
|
5653
|
+
name: caplet.name,
|
|
5654
|
+
description: caplet.description,
|
|
5655
|
+
backend: caplet.backend,
|
|
5656
|
+
tags: caplet.tags,
|
|
5657
|
+
body: caplet.body,
|
|
5658
|
+
setup: caplet.setup,
|
|
5659
|
+
backendConfig: Object.fromEntries(Object.entries(caplet).filter(([key]) => ![
|
|
5660
|
+
"server",
|
|
5661
|
+
"name",
|
|
5662
|
+
"description",
|
|
5663
|
+
"backend",
|
|
5664
|
+
"tags",
|
|
5665
|
+
"body",
|
|
5666
|
+
"setup",
|
|
5667
|
+
"disabled"
|
|
5668
|
+
].includes(key)))
|
|
5669
|
+
};
|
|
5670
|
+
}
|
|
5671
|
+
function sortJson(value) {
|
|
5672
|
+
if (Array.isArray(value)) return value.map(sortJson);
|
|
5673
|
+
if (!value || typeof value !== "object") return value;
|
|
5674
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)]));
|
|
5675
|
+
}
|
|
5676
|
+
//#endregion
|
|
5677
|
+
//#region src/setup/types.ts
|
|
5678
|
+
const setupTargetKinds = [
|
|
5679
|
+
"local_host",
|
|
5680
|
+
"remote_host",
|
|
5681
|
+
"hosted_sandbox"
|
|
5682
|
+
];
|
|
5683
|
+
function isSetupTargetKind(value) {
|
|
5684
|
+
return setupTargetKinds.includes(value);
|
|
5685
|
+
}
|
|
5686
|
+
//#endregion
|
|
5687
|
+
//#region src/setup/local-store.ts
|
|
5688
|
+
const DEFAULT_PROJECT_FINGERPRINT = "default";
|
|
5689
|
+
var LocalSetupStore = class {
|
|
5690
|
+
root;
|
|
5691
|
+
now;
|
|
5692
|
+
maxAttempts;
|
|
5693
|
+
retentionDays;
|
|
5694
|
+
constructor(options = {}) {
|
|
5695
|
+
this.root = options.baseDir ?? join(defaultCacheBaseDir(), "caplets", "setup");
|
|
5696
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
5697
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
5698
|
+
this.retentionDays = options.retentionDays ?? 7;
|
|
5699
|
+
}
|
|
5700
|
+
async getApproval(...args) {
|
|
5701
|
+
const [projectFingerprint, capletId, contentHash, targetKind] = args.length === 3 ? [
|
|
5702
|
+
DEFAULT_PROJECT_FINGERPRINT,
|
|
5703
|
+
args[0],
|
|
5704
|
+
args[1],
|
|
5705
|
+
args[2]
|
|
5706
|
+
] : args;
|
|
5707
|
+
assertSetupTargetKind$1(targetKind);
|
|
5708
|
+
return this.approvals().find((approval) => approval.projectFingerprint === projectFingerprint && approval.capletId === capletId && approval.contentHash === contentHash && approval.targetKind === targetKind);
|
|
5709
|
+
}
|
|
5710
|
+
async approve(input) {
|
|
5711
|
+
const approval = {
|
|
5712
|
+
...input,
|
|
5713
|
+
projectFingerprint: input.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT
|
|
5714
|
+
};
|
|
5715
|
+
assertSetupTargetKind$1(approval.targetKind);
|
|
5716
|
+
const approvals = this.approvals().filter((existing) => existing.projectFingerprint !== approval.projectFingerprint || existing.capletId !== approval.capletId || existing.contentHash !== approval.contentHash || existing.targetKind !== approval.targetKind);
|
|
5717
|
+
approvals.push(approval);
|
|
5718
|
+
mkdirSync(this.root, { recursive: true });
|
|
5719
|
+
writeFileSync(this.approvalsPath(), `${JSON.stringify(approvals, null, 2)}\n`, { mode: 384 });
|
|
5720
|
+
return approval;
|
|
5721
|
+
}
|
|
5722
|
+
async recordAttempt(attempt) {
|
|
5723
|
+
assertSetupTargetKind$1(attempt.targetKind);
|
|
5724
|
+
const projectFingerprint = attempt.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT;
|
|
5725
|
+
const attempts = this.prunedAttempts([...this.attempts(projectFingerprint, attempt.capletId), {
|
|
5726
|
+
...attempt,
|
|
5727
|
+
projectFingerprint
|
|
5728
|
+
}]);
|
|
5729
|
+
mkdirSync(this.attemptsDir(projectFingerprint), { recursive: true });
|
|
5730
|
+
writeFileSync(this.attemptsPath(projectFingerprint, attempt.capletId), attempts.map((entry) => JSON.stringify(entry)).join("\n") + "\n", { mode: 384 });
|
|
5731
|
+
}
|
|
5732
|
+
async listAttempts(...args) {
|
|
5733
|
+
const [projectFingerprint, capletId] = args.length === 1 ? [DEFAULT_PROJECT_FINGERPRINT, args[0]] : args;
|
|
5734
|
+
return this.attempts(projectFingerprint, capletId);
|
|
5735
|
+
}
|
|
5736
|
+
retention() {
|
|
5737
|
+
return {
|
|
5738
|
+
maxAttempts: this.maxAttempts,
|
|
5739
|
+
days: this.retentionDays
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
approvals() {
|
|
5743
|
+
const path = this.approvalsPath();
|
|
5744
|
+
if (!existsSync(path)) return [];
|
|
5745
|
+
return JSON.parse(readFileSync(path, "utf8")).map((approval) => ({
|
|
5746
|
+
...approval,
|
|
5747
|
+
projectFingerprint: approval.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT
|
|
5748
|
+
}));
|
|
5749
|
+
}
|
|
5750
|
+
prunedAttempts(attempts) {
|
|
5751
|
+
const cutoffMs = this.now().getTime() - this.retentionDays * 24 * 60 * 60 * 1e3;
|
|
5752
|
+
return attempts.filter((attempt) => new Date(attempt.finishedAt).getTime() >= cutoffMs).slice(-this.maxAttempts);
|
|
5753
|
+
}
|
|
5754
|
+
approvalsPath() {
|
|
5755
|
+
return join(this.root, "approvals.json");
|
|
5756
|
+
}
|
|
5757
|
+
attempts(projectFingerprint, capletId) {
|
|
5758
|
+
const path = this.attemptsPath(projectFingerprint, capletId);
|
|
5759
|
+
if (!existsSync(path)) return [];
|
|
5760
|
+
return readFileSync(path, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5761
|
+
}
|
|
5762
|
+
attemptsDir(projectFingerprint) {
|
|
5763
|
+
return join(this.root, "projects", safeFileName(projectFingerprint), "attempts");
|
|
5764
|
+
}
|
|
5765
|
+
attemptsPath(projectFingerprint, capletId) {
|
|
5766
|
+
return join(this.attemptsDir(projectFingerprint), `${safeFileName(capletId)}.jsonl`);
|
|
5767
|
+
}
|
|
5768
|
+
};
|
|
5769
|
+
function safeFileName(value) {
|
|
5770
|
+
return value.replace(/[^a-zA-Z0-9._-]/gu, "_");
|
|
5771
|
+
}
|
|
5772
|
+
function assertSetupTargetKind$1(value) {
|
|
5773
|
+
if (!isSetupTargetKind(value)) throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
|
|
5774
|
+
}
|
|
5775
|
+
//#endregion
|
|
5776
|
+
//#region src/setup/runner.ts
|
|
5777
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
5778
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 2e5;
|
|
5779
|
+
async function runCapletSetup(options) {
|
|
5780
|
+
assertSetupTargetKind(options.targetKind);
|
|
5781
|
+
if (!options.approved) throw new CapletsError("REQUEST_INVALID", "Setup approval is required before commands run");
|
|
5782
|
+
const attempts = [];
|
|
5783
|
+
const commands = options.setup.commands ?? [];
|
|
5784
|
+
const verify = options.setup.verify ?? [];
|
|
5785
|
+
for (const phase of ["commands", "verify"]) for (const command of phase === "commands" ? commands : verify) {
|
|
5786
|
+
const attempt = await runSetupCommand(options, phase, command);
|
|
5787
|
+
attempts.push(attempt);
|
|
5788
|
+
await options.store.recordAttempt(attempt);
|
|
5789
|
+
if (attempt.status !== "succeeded") return attempts;
|
|
5790
|
+
}
|
|
5791
|
+
return attempts;
|
|
5792
|
+
}
|
|
5793
|
+
async function runSetupCommand(options, phase, command) {
|
|
5794
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
5795
|
+
const startedAt = now();
|
|
5796
|
+
const argv = [command.command, ...command.args ?? []];
|
|
5797
|
+
const env = {
|
|
5798
|
+
...process.env,
|
|
5799
|
+
...command.env
|
|
5800
|
+
};
|
|
5801
|
+
const timeoutMs = command.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
5802
|
+
const maxOutputBytes = command.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
5803
|
+
const cwd = resolveCwd(command.cwd);
|
|
5804
|
+
assertProjectWorkspaceSetupAllowed({
|
|
5805
|
+
cwd,
|
|
5806
|
+
projectBindingRequired: options.projectBindingRequired === true,
|
|
5807
|
+
projectWorkspacePath: options.projectWorkspacePath
|
|
5808
|
+
});
|
|
5809
|
+
const result = await (options.spawn ?? spawnCommand)(command.command, command.args ?? [], {
|
|
5810
|
+
cwd,
|
|
5811
|
+
env,
|
|
5812
|
+
timeoutMs,
|
|
5813
|
+
maxOutputBytes
|
|
5814
|
+
});
|
|
5815
|
+
const finishedAt = now();
|
|
5816
|
+
const redacted = redactsSecrets(command.env);
|
|
5817
|
+
const stdout = redactOutput(result.stdout, command.env);
|
|
5818
|
+
const stderr = redactOutput(result.stderr, command.env);
|
|
5819
|
+
return {
|
|
5820
|
+
attemptId: randomUUID(),
|
|
5821
|
+
projectFingerprint: options.projectFingerprint ?? "default",
|
|
5822
|
+
capletId: options.capletId,
|
|
5823
|
+
contentHash: options.contentHash,
|
|
5824
|
+
...options.setupHash === void 0 ? {} : { setupHash: options.setupHash },
|
|
5825
|
+
targetKind: options.targetKind,
|
|
5826
|
+
...options.runtimeFeatures === void 0 ? {} : { runtimeFeatures: options.runtimeFeatures },
|
|
5827
|
+
actor: options.actor,
|
|
5828
|
+
status: result.exitCode === 0 && !result.signal ? "succeeded" : "failed",
|
|
5829
|
+
phase,
|
|
5830
|
+
commandLabel: command.label,
|
|
5831
|
+
argv,
|
|
5832
|
+
...result.exitCode === void 0 ? {} : { exitCode: result.exitCode },
|
|
5833
|
+
...result.signal === void 0 ? {} : { signal: result.signal },
|
|
5834
|
+
durationMs: result.durationMs,
|
|
5835
|
+
startedAt: startedAt.toISOString(),
|
|
5836
|
+
finishedAt: finishedAt.toISOString(),
|
|
5837
|
+
stdout: capBytes(stdout, maxOutputBytes),
|
|
5838
|
+
stderr: capBytes(stderr, maxOutputBytes),
|
|
5839
|
+
redacted,
|
|
5840
|
+
retention: options.store.retention()
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
function assertProjectWorkspaceSetupAllowed(input) {
|
|
5844
|
+
if (input.projectBindingRequired || !input.cwd || !input.projectWorkspacePath) return;
|
|
5845
|
+
const workspacePath = resolve(input.projectWorkspacePath);
|
|
5846
|
+
const cwd = resolve(input.cwd);
|
|
5847
|
+
if (cwd === workspacePath || cwd.startsWith(`${workspacePath}/`)) throw new CapletsError("REQUEST_INVALID", "Non-project setup cannot run inside project workspace without projectBinding.required");
|
|
5848
|
+
}
|
|
5849
|
+
async function spawnCommand(command, args, options) {
|
|
5850
|
+
const startedAt = Date.now();
|
|
5851
|
+
return await new Promise((resolvePromise, reject) => {
|
|
5852
|
+
const child = spawn(command, args, {
|
|
5853
|
+
cwd: options.cwd,
|
|
5854
|
+
env: options.env,
|
|
5855
|
+
shell: false,
|
|
5856
|
+
stdio: [
|
|
5857
|
+
"ignore",
|
|
5858
|
+
"pipe",
|
|
5859
|
+
"pipe"
|
|
5860
|
+
]
|
|
5861
|
+
});
|
|
5862
|
+
const chunks = {
|
|
5863
|
+
stdout: "",
|
|
5864
|
+
stderr: ""
|
|
5865
|
+
};
|
|
5866
|
+
const timer = setTimeout(() => {
|
|
5867
|
+
child.kill("SIGTERM");
|
|
5868
|
+
}, options.timeoutMs);
|
|
5869
|
+
child.on("error", (error) => {
|
|
5870
|
+
clearTimeout(timer);
|
|
5871
|
+
reject(error);
|
|
5872
|
+
});
|
|
5873
|
+
child.stdout?.setEncoding("utf8");
|
|
5874
|
+
child.stderr?.setEncoding("utf8");
|
|
5875
|
+
child.stdout?.on("data", (chunk) => {
|
|
5876
|
+
chunks.stdout = capBytes(chunks.stdout + chunk, options.maxOutputBytes);
|
|
5877
|
+
});
|
|
5878
|
+
child.stderr?.on("data", (chunk) => {
|
|
5879
|
+
chunks.stderr = capBytes(chunks.stderr + chunk, options.maxOutputBytes);
|
|
5880
|
+
});
|
|
5881
|
+
child.on("close", (exitCode, signal) => {
|
|
5882
|
+
clearTimeout(timer);
|
|
5883
|
+
resolvePromise({
|
|
5884
|
+
exitCode: exitCode ?? void 0,
|
|
5885
|
+
signal: signal ?? void 0,
|
|
5886
|
+
stdout: chunks.stdout,
|
|
5887
|
+
stderr: chunks.stderr,
|
|
5888
|
+
durationMs: Date.now() - startedAt
|
|
5889
|
+
});
|
|
5890
|
+
});
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
function resolveCwd(cwd) {
|
|
5894
|
+
if (!cwd) return void 0;
|
|
5895
|
+
if (!isAbsolute(cwd)) throw new CapletsError("CONFIG_INVALID", "Setup command cwd must be absolute");
|
|
5896
|
+
return resolve(cwd);
|
|
5897
|
+
}
|
|
5898
|
+
function redactsSecrets(env) {
|
|
5899
|
+
return Object.entries(env ?? {}).some(([key, value]) => isSecretKey(key) && value.length > 0);
|
|
5900
|
+
}
|
|
5901
|
+
function redactOutput(output, env) {
|
|
5902
|
+
let redacted = output;
|
|
5903
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
5904
|
+
if (!isSecretKey(key) || !value) continue;
|
|
5905
|
+
redacted = redacted.split(value).join("[REDACTED]");
|
|
5906
|
+
}
|
|
5907
|
+
return redacted;
|
|
5908
|
+
}
|
|
5909
|
+
function isSecretKey(key) {
|
|
5910
|
+
return /TOKEN|SECRET|PASSWORD|KEY|AUTH/iu.test(key);
|
|
5911
|
+
}
|
|
5912
|
+
function capBytes(value, maxBytes) {
|
|
5913
|
+
if (Buffer.byteLength(value) <= maxBytes) return value;
|
|
5914
|
+
return Buffer.from(value).subarray(0, maxBytes).toString("utf8");
|
|
5915
|
+
}
|
|
5916
|
+
function assertSetupTargetKind(value) {
|
|
5917
|
+
if (!isSetupTargetKind(value)) throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
|
|
5918
|
+
}
|
|
5919
|
+
//#endregion
|
|
5920
|
+
//#region src/cli/setup-caplet.ts
|
|
5921
|
+
async function runCapletSetupCli(capletId, options = {}) {
|
|
5922
|
+
const targetKind = resolveSetupTarget(options);
|
|
5923
|
+
if (targetKind === "hosted_sandbox") throw new CapletsError("REQUEST_INVALID", "Cloud setup runs through the Caplets Cloud API, not the local CLI runner");
|
|
5924
|
+
const config = loadConfig(options.configPath ?? resolveConfigPath(), options.projectConfigPath ?? resolveProjectConfigPath());
|
|
5925
|
+
const caplet = Object.values({
|
|
5926
|
+
...config.mcpServers,
|
|
5927
|
+
...config.openapiEndpoints,
|
|
5928
|
+
...config.graphqlEndpoints,
|
|
5929
|
+
...config.httpApis,
|
|
5930
|
+
...config.cliTools,
|
|
5931
|
+
...config.capletSets
|
|
5932
|
+
}).find((entry) => entry.server === capletId);
|
|
5933
|
+
if (!caplet) throw new CapletsError("CONFIG_INVALID", `Unknown Caplet ID: ${capletId}`);
|
|
5934
|
+
if (!caplet.setup || !caplet.setup.commands?.length && !caplet.setup.verify?.length) return `No setup metadata is defined for ${caplet.name} (${caplet.server}).\n`;
|
|
5935
|
+
const contentHash = capletSetupContentHash(caplet);
|
|
5936
|
+
const projectFingerprint = "default";
|
|
5937
|
+
const store = new LocalSetupStore(options.baseDir ? { baseDir: options.baseDir } : {});
|
|
5938
|
+
const existingApproval = await store.getApproval(projectFingerprint, caplet.server, contentHash, targetKind);
|
|
5939
|
+
const actor = options.yes ? "cli-yes" : "cli-interactive";
|
|
5940
|
+
if (!existingApproval && !options.yes) return [
|
|
5941
|
+
`Setup approval required for ${caplet.name} (${caplet.server}).`,
|
|
5942
|
+
`Content hash: ${contentHash}`,
|
|
5943
|
+
`Target: ${targetKind}`,
|
|
5944
|
+
"",
|
|
5945
|
+
"Commands:",
|
|
5946
|
+
...formatCommands(caplet.setup.commands ?? []),
|
|
5947
|
+
"Verify:",
|
|
5948
|
+
...formatCommands(caplet.setup.verify ?? []),
|
|
5949
|
+
"",
|
|
5950
|
+
`Run caplets setup ${caplet.server} --yes to approve and execute these exact steps.`,
|
|
5951
|
+
""
|
|
5952
|
+
].join("\n");
|
|
5953
|
+
if (options.yes && !existingApproval) await store.approve({
|
|
5954
|
+
projectFingerprint,
|
|
5955
|
+
capletId: caplet.server,
|
|
5956
|
+
contentHash,
|
|
5957
|
+
targetKind,
|
|
5958
|
+
approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5959
|
+
actor
|
|
5960
|
+
});
|
|
5961
|
+
const attempts = await runCapletSetup({
|
|
5962
|
+
projectFingerprint,
|
|
5963
|
+
capletId: caplet.server,
|
|
5964
|
+
contentHash,
|
|
5965
|
+
targetKind,
|
|
5966
|
+
setup: caplet.setup,
|
|
5967
|
+
actor,
|
|
5968
|
+
approved: true,
|
|
5969
|
+
store,
|
|
5970
|
+
...options.spawn ? { spawn: options.spawn } : {}
|
|
5971
|
+
});
|
|
5972
|
+
const failed = attempts.find((attempt) => attempt.status === "failed");
|
|
5973
|
+
if (failed) throw new CapletsError("SERVER_UNAVAILABLE", `Setup failed for ${caplet.server}: ${failed.commandLabel}`, { attempts });
|
|
5974
|
+
return `Completed setup for ${caplet.name} (${caplet.server}).\n`;
|
|
5975
|
+
}
|
|
5976
|
+
function resolveSetupTarget(options) {
|
|
5977
|
+
if (options.target) return options.target;
|
|
5978
|
+
return options.remote ? "remote_host" : "local_host";
|
|
5979
|
+
}
|
|
5980
|
+
function formatCommands(commands) {
|
|
5981
|
+
if (commands.length === 0) return [" none"];
|
|
5982
|
+
return commands.map((command) => ` - ${command.label}: ${[command.command, ...command.args ?? []].join(" ")}`);
|
|
5983
|
+
}
|
|
5984
|
+
//#endregion
|
|
5273
5985
|
//#region src/cli/setup.ts
|
|
5274
5986
|
const execFileAsync = promisify(execFile);
|
|
5275
5987
|
const setupIntegrationIds = [
|
|
@@ -5307,6 +6019,11 @@ function formatSetupMenu() {
|
|
|
5307
6019
|
].join("\n");
|
|
5308
6020
|
}
|
|
5309
6021
|
async function runSetup(integration, options = {}) {
|
|
6022
|
+
if (!setupIntegrationIds.includes(integration)) return await runCapletSetupCli(integration, {
|
|
6023
|
+
...options.yes === void 0 ? {} : { yes: options.yes },
|
|
6024
|
+
target: resolveSetupTargetKind(options),
|
|
6025
|
+
...options.remote === void 0 ? {} : { remote: options.remote }
|
|
6026
|
+
});
|
|
5310
6027
|
const result = await executeSetup(integration, options);
|
|
5311
6028
|
if (options.format === "json") return `${JSON.stringify(result, null, 2)}\n`;
|
|
5312
6029
|
return formatSetupResult(result);
|
|
@@ -5350,6 +6067,7 @@ async function executeSetup(integration, options) {
|
|
|
5350
6067
|
integration: id,
|
|
5351
6068
|
name: definition.name,
|
|
5352
6069
|
mode: options.remote ? "remote" : "local",
|
|
6070
|
+
targetKind: resolveSetupTargetKind(options),
|
|
5353
6071
|
dryRun: Boolean(options.dryRun),
|
|
5354
6072
|
actions,
|
|
5355
6073
|
nextSteps: definition.nextSteps
|
|
@@ -5497,7 +6215,7 @@ async function defaultSetupCommandRunner(command, args) {
|
|
|
5497
6215
|
};
|
|
5498
6216
|
}
|
|
5499
6217
|
function formatSetupResult(result) {
|
|
5500
|
-
const lines = [`${result.dryRun ? "Dry run" : "Completed"} ${result.name} setup (${result.mode})`, ""];
|
|
6218
|
+
const lines = [`${result.dryRun ? "Dry run" : "Completed"} ${result.name} setup (${result.mode}, ${result.targetKind})`, ""];
|
|
5501
6219
|
for (const action of result.actions) {
|
|
5502
6220
|
if (action.command) lines.push(`- ${action.status}: ${action.command}`);
|
|
5503
6221
|
if (action.path) lines.push(`- ${action.status}: wrote ${action.path}`);
|
|
@@ -5516,97 +6234,174 @@ function nonEmpty$1(value) {
|
|
|
5516
6234
|
const trimmed = value?.trim();
|
|
5517
6235
|
return trimmed ? trimmed : void 0;
|
|
5518
6236
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
this.#baseUrl = options.baseUrl;
|
|
5527
|
-
this.#requestInit = options.requestInit;
|
|
5528
|
-
this.#fetch = options.fetch ?? fetch;
|
|
6237
|
+
function resolveSetupTargetKind(options) {
|
|
6238
|
+
if (options.target !== void 0) {
|
|
6239
|
+
if (isSetupTargetKind(options.target)) return options.target;
|
|
6240
|
+
if (options.target === "local") return "local_host";
|
|
6241
|
+
if (options.target === "remote") return "remote_host";
|
|
6242
|
+
if (options.target === "cloud" || options.target === "hosted_worker") return "hosted_sandbox";
|
|
6243
|
+
throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
|
|
5529
6244
|
}
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
if (
|
|
5548
|
-
|
|
5549
|
-
if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
|
|
5550
|
-
return payload.result;
|
|
6245
|
+
return options.remote ? "remote_host" : "local_host";
|
|
6246
|
+
}
|
|
6247
|
+
//#endregion
|
|
6248
|
+
//#region src/serve/options.ts
|
|
6249
|
+
const HTTP_ONLY_OPTIONS = [
|
|
6250
|
+
"host",
|
|
6251
|
+
"port",
|
|
6252
|
+
"path",
|
|
6253
|
+
"user",
|
|
6254
|
+
"password",
|
|
6255
|
+
"allowUnauthenticatedHttp",
|
|
6256
|
+
"trustProxy"
|
|
6257
|
+
];
|
|
6258
|
+
function resolveServeOptions(raw, env = process.env) {
|
|
6259
|
+
const transport = parseTransport(raw.transport ?? "stdio");
|
|
6260
|
+
if (transport === "stdio") {
|
|
6261
|
+
const invalid = HTTP_ONLY_OPTIONS.filter((key) => raw[key] !== void 0);
|
|
6262
|
+
if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
|
|
6263
|
+
return { transport };
|
|
5551
6264
|
}
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
const
|
|
5555
|
-
|
|
5556
|
-
|
|
6265
|
+
const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
|
|
6266
|
+
const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
|
|
6267
|
+
const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
|
|
6268
|
+
const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
|
|
6269
|
+
const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
|
|
6270
|
+
const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
|
|
6271
|
+
const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
6272
|
+
if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "HTTP Basic Auth requires a password; pass --password or set CAPLETS_SERVER_PASSWORD.");
|
|
6273
|
+
const loopback = isLoopbackHost(host);
|
|
6274
|
+
const auth = password === void 0 ? {
|
|
6275
|
+
enabled: false,
|
|
6276
|
+
user
|
|
6277
|
+
} : {
|
|
6278
|
+
enabled: true,
|
|
6279
|
+
user,
|
|
6280
|
+
password
|
|
6281
|
+
};
|
|
6282
|
+
if (!loopback && !auth.enabled && raw.allowUnauthenticatedHttp !== true) throw new CapletsError("REQUEST_INVALID", "Unauthenticated HTTP serving on non-loopback hosts requires --allow-unauthenticated-http.");
|
|
6283
|
+
return {
|
|
6284
|
+
transport,
|
|
6285
|
+
host,
|
|
6286
|
+
port,
|
|
6287
|
+
path,
|
|
6288
|
+
...serverUrl ? { publicOrigin: serverUrl.origin } : {},
|
|
6289
|
+
auth,
|
|
6290
|
+
warnUnauthenticatedNetwork: !loopback && !auth.enabled,
|
|
6291
|
+
loopback,
|
|
6292
|
+
trustProxy: raw.trustProxy === true
|
|
6293
|
+
};
|
|
5557
6294
|
}
|
|
5558
|
-
function
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
return safe.toString();
|
|
6295
|
+
function resolveDaemonServeOptions(raw, env = process.env) {
|
|
6296
|
+
if (raw.transport !== void 0 && raw.transport !== "http") throw new CapletsError("REQUEST_INVALID", "Daemonized serve requires --transport http.");
|
|
6297
|
+
return resolveServeOptions({
|
|
6298
|
+
...raw,
|
|
6299
|
+
transport: "http"
|
|
6300
|
+
}, env);
|
|
5565
6301
|
}
|
|
5566
|
-
|
|
5567
|
-
|
|
6302
|
+
function isLoopbackHost(host) {
|
|
6303
|
+
const normalized = host.toLocaleLowerCase();
|
|
6304
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
6305
|
+
}
|
|
6306
|
+
function parseServeServerUrl(value) {
|
|
5568
6307
|
try {
|
|
5569
|
-
|
|
6308
|
+
return parseServerBaseUrl(value);
|
|
5570
6309
|
} catch (error) {
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
if (!isRecord(payload)) throw invalidRemoteControlResponse();
|
|
5574
|
-
if (payload.ok === true) {
|
|
5575
|
-
if (!("result" in payload)) throw invalidRemoteControlResponse();
|
|
5576
|
-
return {
|
|
5577
|
-
ok: true,
|
|
5578
|
-
result: payload.result
|
|
5579
|
-
};
|
|
5580
|
-
}
|
|
5581
|
-
if (payload.ok === false) {
|
|
5582
|
-
const error = payload.error;
|
|
5583
|
-
if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
|
|
5584
|
-
if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
|
|
5585
|
-
const errorResponse = {
|
|
5586
|
-
ok: false,
|
|
5587
|
-
error: {
|
|
5588
|
-
code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
|
|
5589
|
-
message: error.message
|
|
5590
|
-
}
|
|
5591
|
-
};
|
|
5592
|
-
if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
|
|
5593
|
-
return errorResponse;
|
|
6310
|
+
if (error instanceof CapletsError && error.message.includes("must use https except loopback development URLs")) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL must use https except loopback development URLs; use --host, --port, and --path separately for non-loopback HTTP bind addresses.");
|
|
6311
|
+
throw error;
|
|
5594
6312
|
}
|
|
5595
|
-
throw invalidRemoteControlResponse();
|
|
5596
6313
|
}
|
|
5597
|
-
function
|
|
5598
|
-
|
|
6314
|
+
function parseTransport(value) {
|
|
6315
|
+
if (value === "stdio" || value === "http") return value;
|
|
6316
|
+
throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
|
|
5599
6317
|
}
|
|
5600
|
-
function
|
|
5601
|
-
|
|
6318
|
+
function parsePort(value) {
|
|
6319
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
6320
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new CapletsError("REQUEST_INVALID", `Expected --port to be a valid TCP port, got ${value}`);
|
|
6321
|
+
return parsed;
|
|
5602
6322
|
}
|
|
5603
|
-
function
|
|
5604
|
-
|
|
6323
|
+
function normalizeHttpPath(value) {
|
|
6324
|
+
if (!value.startsWith("/")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must start with /");
|
|
6325
|
+
if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
|
|
6326
|
+
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
5605
6327
|
}
|
|
5606
|
-
function
|
|
5607
|
-
return
|
|
6328
|
+
function serverUrlHost(url) {
|
|
6329
|
+
return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
6330
|
+
}
|
|
6331
|
+
function nonEmpty(value, label) {
|
|
6332
|
+
if (value === void 0) return;
|
|
6333
|
+
const trimmed = value.trim();
|
|
6334
|
+
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
6335
|
+
return trimmed;
|
|
6336
|
+
}
|
|
6337
|
+
function hasEnv(value) {
|
|
6338
|
+
return value !== void 0 && value.trim() !== "";
|
|
6339
|
+
}
|
|
6340
|
+
//#endregion
|
|
6341
|
+
//#region src/attach/options.ts
|
|
6342
|
+
async function resolveAttachServeOptions(raw = {}, env = process.env) {
|
|
6343
|
+
const selection = await resolveRemoteSelection(raw, env);
|
|
6344
|
+
return {
|
|
6345
|
+
...resolveServeOptions(raw, env),
|
|
6346
|
+
projectRoot: raw.projectRoot ?? process.cwd(),
|
|
6347
|
+
selection
|
|
6348
|
+
};
|
|
5608
6349
|
}
|
|
5609
6350
|
//#endregion
|
|
6351
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
6352
|
+
/**
|
|
6353
|
+
* Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout.
|
|
6354
|
+
*
|
|
6355
|
+
* This transport is only available in Node.js environments.
|
|
6356
|
+
*/
|
|
6357
|
+
var StdioServerTransport = class {
|
|
6358
|
+
constructor(_stdin = process$1.stdin, _stdout = process$1.stdout) {
|
|
6359
|
+
this._stdin = _stdin;
|
|
6360
|
+
this._stdout = _stdout;
|
|
6361
|
+
this._readBuffer = new ReadBuffer();
|
|
6362
|
+
this._started = false;
|
|
6363
|
+
this._ondata = (chunk) => {
|
|
6364
|
+
this._readBuffer.append(chunk);
|
|
6365
|
+
this.processReadBuffer();
|
|
6366
|
+
};
|
|
6367
|
+
this._onerror = (error) => {
|
|
6368
|
+
this.onerror?.(error);
|
|
6369
|
+
};
|
|
6370
|
+
}
|
|
6371
|
+
/**
|
|
6372
|
+
* Starts listening for messages on stdin.
|
|
6373
|
+
*/
|
|
6374
|
+
async start() {
|
|
6375
|
+
if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
6376
|
+
this._started = true;
|
|
6377
|
+
this._stdin.on("data", this._ondata);
|
|
6378
|
+
this._stdin.on("error", this._onerror);
|
|
6379
|
+
}
|
|
6380
|
+
processReadBuffer() {
|
|
6381
|
+
while (true) try {
|
|
6382
|
+
const message = this._readBuffer.readMessage();
|
|
6383
|
+
if (message === null) break;
|
|
6384
|
+
this.onmessage?.(message);
|
|
6385
|
+
} catch (error) {
|
|
6386
|
+
this.onerror?.(error);
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6389
|
+
async close() {
|
|
6390
|
+
this._stdin.off("data", this._ondata);
|
|
6391
|
+
this._stdin.off("error", this._onerror);
|
|
6392
|
+
if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
|
|
6393
|
+
this._readBuffer.clear();
|
|
6394
|
+
this.onclose?.();
|
|
6395
|
+
}
|
|
6396
|
+
send(message) {
|
|
6397
|
+
return new Promise((resolve) => {
|
|
6398
|
+
const json = serializeMessage(message);
|
|
6399
|
+
if (this._stdout.write(json)) resolve();
|
|
6400
|
+
else this._stdout.once("drain", resolve);
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6403
|
+
};
|
|
6404
|
+
//#endregion
|
|
5610
6405
|
//#region ../../node_modules/.pnpm/hono@4.12.23/node_modules/hono/dist/compose.js
|
|
5611
6406
|
var compose = (middleware, onError, onNotFound) => {
|
|
5612
6407
|
return (context, next) => {
|
|
@@ -9337,7 +10132,7 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
9337
10132
|
id: null
|
|
9338
10133
|
}, 400);
|
|
9339
10134
|
const nextSessionId = randomUUID();
|
|
9340
|
-
const session = await createHttpSession(engine, nextSessionId, options, async (closedSessionId) => {
|
|
10135
|
+
const session = await createHttpSession(io.sessionFactory ?? (() => new CapletsMcpSession(engine)), nextSessionId, options, async (closedSessionId) => {
|
|
9341
10136
|
const closed = sessions.get(closedSessionId);
|
|
9342
10137
|
sessions.delete(closedSessionId);
|
|
9343
10138
|
if (closed) await closed.server.close();
|
|
@@ -9363,6 +10158,11 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
9363
10158
|
}
|
|
9364
10159
|
return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.publicOrigin, options.trustProxy, (name) => c.req.header(name))));
|
|
9365
10160
|
});
|
|
10161
|
+
app.get(routePath(paths.control, "project-bindings/connect"), basicAuth(options.auth), (c) => c.json({ error: "websocket_upgrade_required" }, 426));
|
|
10162
|
+
app.get(routePath(paths.control, "project-bindings/:bindingId/status"), basicAuth(options.auth), (c) => c.json({
|
|
10163
|
+
bindingId: c.req.param("bindingId"),
|
|
10164
|
+
state: "not_attached"
|
|
10165
|
+
}));
|
|
9366
10166
|
app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
|
|
9367
10167
|
const flowId = c.req.param("flowId");
|
|
9368
10168
|
const result = await dispatchRemoteCliRequest({
|
|
@@ -9428,6 +10228,28 @@ async function serveHttp(options, engineOptions = {}, writeErr = (value) => proc
|
|
|
9428
10228
|
writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
|
|
9429
10229
|
}), app, engine, writeErr);
|
|
9430
10230
|
}
|
|
10231
|
+
async function serveHttpWithSessionFactory(options, createSession, writeErr = (value) => process.stderr.write(value)) {
|
|
10232
|
+
const engine = new CapletsEngine({});
|
|
10233
|
+
const app = createHttpServeApp(options, engine, {
|
|
10234
|
+
writeErr,
|
|
10235
|
+
sessionFactory: createSession,
|
|
10236
|
+
control: { projectCapletsRoot: resolveProjectCapletsRoot() }
|
|
10237
|
+
});
|
|
10238
|
+
const paths = servicePaths(options.path);
|
|
10239
|
+
const origin = `http://${formatHost(options.host)}:${options.port}`;
|
|
10240
|
+
const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
|
|
10241
|
+
installHttpSignalHandlers(serve({
|
|
10242
|
+
fetch: app.fetch,
|
|
10243
|
+
hostname: options.host,
|
|
10244
|
+
port: options.port
|
|
10245
|
+
}, () => {
|
|
10246
|
+
writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
|
|
10247
|
+
writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
|
|
10248
|
+
writeErr(`Control endpoint: ${origin}${paths.control}\n`);
|
|
10249
|
+
writeErr(`Health check: ${origin}${paths.health}\n`);
|
|
10250
|
+
writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
|
|
10251
|
+
}), app, engine, writeErr);
|
|
10252
|
+
}
|
|
9431
10253
|
function projectCapletsRootForEngineOptions(engineOptions) {
|
|
9432
10254
|
return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
|
|
9433
10255
|
}
|
|
@@ -9445,13 +10267,13 @@ function servicePaths(base) {
|
|
|
9445
10267
|
health: routePath(base, "healthz")
|
|
9446
10268
|
};
|
|
9447
10269
|
}
|
|
9448
|
-
async function createHttpSession(
|
|
10270
|
+
async function createHttpSession(createServer, sessionId, options, onClose) {
|
|
9449
10271
|
const transport = new StreamableHTTPTransport({
|
|
9450
10272
|
sessionIdGenerator: () => sessionId,
|
|
9451
10273
|
onsessionclosed: onClose,
|
|
9452
10274
|
...options.loopback ? dnsRebindingOptions(options) : {}
|
|
9453
10275
|
});
|
|
9454
|
-
const server =
|
|
10276
|
+
const server = await createServer();
|
|
9455
10277
|
await server.connect(transport);
|
|
9456
10278
|
return {
|
|
9457
10279
|
server,
|
|
@@ -9517,145 +10339,657 @@ function formatHost(host) {
|
|
|
9517
10339
|
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
9518
10340
|
}
|
|
9519
10341
|
//#endregion
|
|
9520
|
-
//#region src/serve/
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
return { transport };
|
|
10342
|
+
//#region src/serve/native-session.ts
|
|
10343
|
+
var NativeCapletsMcpSession = class {
|
|
10344
|
+
service;
|
|
10345
|
+
server;
|
|
10346
|
+
tools = /* @__PURE__ */ new Map();
|
|
10347
|
+
unsubscribe;
|
|
10348
|
+
closed = false;
|
|
10349
|
+
constructor(service, options = {}) {
|
|
10350
|
+
this.service = service;
|
|
10351
|
+
this.server = options.server ?? new McpServer({
|
|
10352
|
+
name: "caplets",
|
|
10353
|
+
version
|
|
10354
|
+
});
|
|
10355
|
+
this.unsubscribe = service.onToolsChanged((tools) => this.reconcileTools(tools));
|
|
10356
|
+
this.reconcileTools(service.listTools());
|
|
9536
10357
|
}
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
10358
|
+
async connect(transport) {
|
|
10359
|
+
await this.server.connect(transport);
|
|
10360
|
+
}
|
|
10361
|
+
async close() {
|
|
10362
|
+
if (this.closed) return;
|
|
10363
|
+
this.closed = true;
|
|
10364
|
+
this.unsubscribe();
|
|
10365
|
+
this.tools.clear();
|
|
10366
|
+
await this.server.close();
|
|
10367
|
+
await this.service.close();
|
|
10368
|
+
}
|
|
10369
|
+
reconcileTools(next) {
|
|
10370
|
+
const enabled = new Map(next.map((tool) => [tool.caplet, tool]));
|
|
10371
|
+
for (const [id, registered] of this.tools) {
|
|
10372
|
+
const tool = enabled.get(id);
|
|
10373
|
+
if (!tool) {
|
|
10374
|
+
registered.remove();
|
|
10375
|
+
this.tools.delete(id);
|
|
10376
|
+
continue;
|
|
10377
|
+
}
|
|
10378
|
+
registered.update(this.definition(tool));
|
|
10379
|
+
}
|
|
10380
|
+
for (const tool of enabled.values()) if (!this.tools.has(tool.caplet)) this.tools.set(tool.caplet, this.server.registerTool(tool.caplet, this.definition(tool), async (request) => await this.service.execute(tool.caplet, request)));
|
|
10381
|
+
}
|
|
10382
|
+
definition(tool) {
|
|
10383
|
+
return {
|
|
10384
|
+
title: tool.title,
|
|
10385
|
+
description: tool.description,
|
|
10386
|
+
inputSchema: isRecord$2(tool.inputSchema) ? jsonSchemaToZodShape(tool.inputSchema) : void 0
|
|
10387
|
+
};
|
|
10388
|
+
}
|
|
10389
|
+
};
|
|
10390
|
+
function jsonSchemaToZodShape(schema) {
|
|
10391
|
+
const properties = isRecord$2(schema.properties) ? schema.properties : {};
|
|
10392
|
+
const shape = {};
|
|
10393
|
+
for (const [key, value] of Object.entries(properties)) shape[key] = jsonSchemaPropertyToZod(value);
|
|
10394
|
+
return shape;
|
|
10395
|
+
}
|
|
10396
|
+
function jsonSchemaPropertyToZod(value) {
|
|
10397
|
+
if (!isRecord$2(value)) return unknown().optional();
|
|
10398
|
+
if (Array.isArray(value.enum) && value.enum.every((item) => typeof item === "string")) {
|
|
10399
|
+
const values = value.enum;
|
|
10400
|
+
if (values.length > 0) return _enum(values).optional();
|
|
10401
|
+
}
|
|
10402
|
+
switch (value.type) {
|
|
10403
|
+
case "string": return string().optional();
|
|
10404
|
+
case "number":
|
|
10405
|
+
case "integer": return number().optional();
|
|
10406
|
+
case "boolean": return boolean().optional();
|
|
10407
|
+
case "array": return array(unknown()).optional();
|
|
10408
|
+
case "object": return record(string(), unknown()).optional();
|
|
10409
|
+
default: return unknown().optional();
|
|
10410
|
+
}
|
|
10411
|
+
}
|
|
10412
|
+
function isRecord$2(value) {
|
|
10413
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10414
|
+
}
|
|
10415
|
+
//#endregion
|
|
10416
|
+
//#region src/attach/server.ts
|
|
10417
|
+
async function attachResolvedCaplets(options, io = {}) {
|
|
10418
|
+
if (options.transport === "stdio") {
|
|
10419
|
+
const service = createAttachNativeService(options, io);
|
|
10420
|
+
const session = new NativeCapletsMcpSession(service);
|
|
10421
|
+
await service.reload();
|
|
10422
|
+
await session.connect(new StdioServerTransport());
|
|
10423
|
+
return;
|
|
10424
|
+
}
|
|
10425
|
+
await serveHttpWithSessionFactory(options, async () => {
|
|
10426
|
+
const service = createAttachNativeService(options, io);
|
|
10427
|
+
await service.reload();
|
|
10428
|
+
return new NativeCapletsMcpSession(service);
|
|
10429
|
+
}, io.writeErr);
|
|
10430
|
+
}
|
|
10431
|
+
function createAttachNativeService(options, io) {
|
|
10432
|
+
return createNativeCapletsService({
|
|
10433
|
+
mode: options.selection.kind === "hosted_cloud" ? "cloud" : "remote",
|
|
10434
|
+
server: {
|
|
10435
|
+
url: options.selection.remote.baseUrl.toString(),
|
|
10436
|
+
...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {}
|
|
10437
|
+
},
|
|
10438
|
+
remote: {
|
|
10439
|
+
...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {},
|
|
10440
|
+
...options.selection.kind === "hosted_cloud" ? { cloud: {
|
|
10441
|
+
url: options.selection.cloudPresence.url.toString(),
|
|
10442
|
+
accessToken: options.selection.cloudPresence.accessToken,
|
|
10443
|
+
workspaceId: options.selection.cloudPresence.workspaceId,
|
|
10444
|
+
projectRoot: options.projectRoot
|
|
10445
|
+
} } : {}
|
|
10446
|
+
},
|
|
10447
|
+
remoteClientFactory: (resolved) => createSdkRemoteCapletsClient({
|
|
10448
|
+
...resolved,
|
|
10449
|
+
requestInit: options.selection.remote.requestInit,
|
|
10450
|
+
auth: nativeAuthFromRemoteAuth(options.selection.remote.auth),
|
|
10451
|
+
url: options.selection.remote.mcpUrl,
|
|
10452
|
+
...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {}
|
|
10453
|
+
}),
|
|
10454
|
+
...io.writeErr ? { writeErr: io.writeErr } : {}
|
|
10455
|
+
});
|
|
10456
|
+
}
|
|
10457
|
+
function nativeAuthFromRemoteAuth(auth) {
|
|
10458
|
+
if (auth.type === "basic") return {
|
|
9550
10459
|
enabled: true,
|
|
9551
|
-
user,
|
|
9552
|
-
password
|
|
10460
|
+
user: auth.user,
|
|
10461
|
+
password: auth.password
|
|
10462
|
+
};
|
|
10463
|
+
if (auth.type === "none") return {
|
|
10464
|
+
enabled: false,
|
|
10465
|
+
user: auth.user
|
|
9553
10466
|
};
|
|
9554
|
-
if (!loopback && !auth.enabled && raw.allowUnauthenticatedHttp !== true) throw new CapletsError("REQUEST_INVALID", "Unauthenticated HTTP serving on non-loopback hosts requires --allow-unauthenticated-http.");
|
|
9555
10467
|
return {
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
port,
|
|
9559
|
-
path,
|
|
9560
|
-
...serverUrl ? { publicOrigin: serverUrl.origin } : {},
|
|
9561
|
-
auth,
|
|
9562
|
-
warnUnauthenticatedNetwork: !loopback && !auth.enabled,
|
|
9563
|
-
loopback,
|
|
9564
|
-
trustProxy: raw.trustProxy === true
|
|
10468
|
+
enabled: false,
|
|
10469
|
+
user: "caplets"
|
|
9565
10470
|
};
|
|
9566
10471
|
}
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
10472
|
+
//#endregion
|
|
10473
|
+
//#region src/project-binding/gitignore.ts
|
|
10474
|
+
const CAPLETS_GITIGNORE_CONTENT = "*\n!.gitignore\n";
|
|
10475
|
+
function bootstrapProjectBindingGitignore(projectRoot) {
|
|
10476
|
+
const capletsDir = join(projectRoot, ".caplets");
|
|
10477
|
+
const gitignorePath = join(capletsDir, ".gitignore");
|
|
10478
|
+
if (!existsSync(projectRoot)) return {
|
|
10479
|
+
path: gitignorePath,
|
|
10480
|
+
changed: false
|
|
10481
|
+
};
|
|
10482
|
+
mkdirSync(capletsDir, { recursive: true });
|
|
10483
|
+
if (!existsSync(gitignorePath)) {
|
|
10484
|
+
writeFileSync(gitignorePath, CAPLETS_GITIGNORE_CONTENT, { mode: 384 });
|
|
10485
|
+
return {
|
|
10486
|
+
path: gitignorePath,
|
|
10487
|
+
changed: true
|
|
10488
|
+
};
|
|
9577
10489
|
}
|
|
10490
|
+
const existing = readFileSync(gitignorePath, "utf8");
|
|
10491
|
+
if (existing.startsWith(CAPLETS_GITIGNORE_CONTENT)) return {
|
|
10492
|
+
path: gitignorePath,
|
|
10493
|
+
changed: false
|
|
10494
|
+
};
|
|
10495
|
+
writeFileSync(gitignorePath, `${CAPLETS_GITIGNORE_CONTENT}${existing}`, { mode: 384 });
|
|
10496
|
+
return {
|
|
10497
|
+
path: gitignorePath,
|
|
10498
|
+
changed: true
|
|
10499
|
+
};
|
|
9578
10500
|
}
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new CapletsError("REQUEST_INVALID", `Expected --port to be a valid TCP port, got ${value}`);
|
|
9586
|
-
return parsed;
|
|
9587
|
-
}
|
|
9588
|
-
function normalizeHttpPath(value) {
|
|
9589
|
-
if (!value.startsWith("/")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must start with /");
|
|
9590
|
-
if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
|
|
9591
|
-
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
9592
|
-
}
|
|
9593
|
-
function serverUrlHost(url) {
|
|
9594
|
-
return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
9595
|
-
}
|
|
9596
|
-
function nonEmpty(value, label) {
|
|
9597
|
-
if (value === void 0) return;
|
|
9598
|
-
const trimmed = value.trim();
|
|
9599
|
-
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
9600
|
-
return trimmed;
|
|
9601
|
-
}
|
|
9602
|
-
function hasEnv(value) {
|
|
9603
|
-
return value !== void 0 && value.trim() !== "";
|
|
10501
|
+
//#endregion
|
|
10502
|
+
//#region src/project-binding/transport.ts
|
|
10503
|
+
function defaultProjectBindingWebSocketFactory(url, protocols) {
|
|
10504
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
10505
|
+
if (!WebSocketCtor) throw new Error("WebSocket is not available in this runtime.");
|
|
10506
|
+
return new WebSocketCtor(url, protocols);
|
|
9604
10507
|
}
|
|
9605
10508
|
//#endregion
|
|
9606
|
-
//#region
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
10509
|
+
//#region src/project-binding/session.ts
|
|
10510
|
+
async function runProjectBindingSession(input) {
|
|
10511
|
+
const fetchImpl = input.fetch ?? input.remote.fetch ?? fetch;
|
|
10512
|
+
const webSocketFactory = input.webSocketFactory ?? defaultProjectBindingWebSocketFactory;
|
|
10513
|
+
const heartbeatIntervalMs = input.heartbeatIntervalMs ?? 15e3;
|
|
10514
|
+
const projectFingerprint = fingerprintProjectRoot(input.projectRoot);
|
|
10515
|
+
const requestInit = input.remote.requestInit;
|
|
10516
|
+
input.onEvent?.({
|
|
10517
|
+
type: "state",
|
|
10518
|
+
state: "attaching"
|
|
10519
|
+
});
|
|
10520
|
+
const created = await postJson(fetchImpl, sessionUrl(input.remote), requestInit, {
|
|
10521
|
+
projectRoot: input.projectRoot,
|
|
10522
|
+
projectFingerprint,
|
|
10523
|
+
workspaceId: input.remote.workspace ?? "default"
|
|
10524
|
+
});
|
|
10525
|
+
const bindingId = created.binding.bindingId;
|
|
10526
|
+
const sessionId = created.sessionId;
|
|
10527
|
+
let state = created.binding.state ?? "attaching";
|
|
10528
|
+
let syncState = created.binding.syncState ?? "pending";
|
|
10529
|
+
let ended = false;
|
|
10530
|
+
let heartbeatTimer;
|
|
10531
|
+
const publicWebSocketUrl = input.remote.projectBindingWebSocketUrl.toString();
|
|
10532
|
+
const socketUrl = bindingSocketUrl(input.remote, bindingId, sessionId, projectFingerprint);
|
|
10533
|
+
const socketProtocols = bindingSocketProtocols(input.remote);
|
|
10534
|
+
const emitReady = (requestId) => {
|
|
10535
|
+
input.onEvent?.({
|
|
10536
|
+
type: "ready",
|
|
10537
|
+
bindingId,
|
|
10538
|
+
sessionId,
|
|
10539
|
+
projectRoot: input.projectRoot,
|
|
10540
|
+
projectFingerprint,
|
|
10541
|
+
webSocketUrl: publicWebSocketUrl,
|
|
10542
|
+
...requestId ? { requestId } : {}
|
|
10543
|
+
});
|
|
10544
|
+
};
|
|
10545
|
+
const heartbeat = async (socket) => {
|
|
10546
|
+
const payload = {
|
|
10547
|
+
type: "heartbeat",
|
|
10548
|
+
bindingId,
|
|
10549
|
+
sessionId,
|
|
10550
|
+
state,
|
|
10551
|
+
syncState
|
|
9621
10552
|
};
|
|
9622
|
-
|
|
9623
|
-
|
|
10553
|
+
if (socket?.readyState === 1) socket.send(JSON.stringify(payload));
|
|
10554
|
+
await postJson(fetchImpl, heartbeatUrl(input.remote, bindingId), requestInit, {
|
|
10555
|
+
sessionId,
|
|
10556
|
+
state,
|
|
10557
|
+
syncState
|
|
10558
|
+
}).catch(() => void 0);
|
|
10559
|
+
input.onEvent?.({
|
|
10560
|
+
type: "heartbeat",
|
|
10561
|
+
bindingId,
|
|
10562
|
+
sessionId,
|
|
10563
|
+
state
|
|
10564
|
+
});
|
|
10565
|
+
};
|
|
10566
|
+
const connect = async (attempt) => {
|
|
10567
|
+
const socket = webSocketFactory(socketUrl, socketProtocols);
|
|
10568
|
+
await waitForOpen(socket, input.signal);
|
|
10569
|
+
if (input.signal?.aborted) {
|
|
10570
|
+
closeSocket(socket, 1e3, "aborted");
|
|
10571
|
+
return;
|
|
10572
|
+
}
|
|
10573
|
+
const closePromise = new Promise((resolve) => {
|
|
10574
|
+
listen(socket, "message", (event) => {
|
|
10575
|
+
const message = parseSocketMessage(event.data);
|
|
10576
|
+
if (!message) return;
|
|
10577
|
+
if (message.type === "state") {
|
|
10578
|
+
state = message.state;
|
|
10579
|
+
syncState = message.syncState;
|
|
10580
|
+
input.onEvent?.({
|
|
10581
|
+
type: "state",
|
|
10582
|
+
state,
|
|
10583
|
+
...message.requestId ? { requestId: message.requestId } : {}
|
|
10584
|
+
});
|
|
10585
|
+
return;
|
|
10586
|
+
}
|
|
10587
|
+
if (message.type === "ready") {
|
|
10588
|
+
state = "ready";
|
|
10589
|
+
syncState = message.syncState;
|
|
10590
|
+
emitReady(message.requestId);
|
|
10591
|
+
return;
|
|
10592
|
+
}
|
|
10593
|
+
if (message.type === "blocked") {
|
|
10594
|
+
state = "blocked";
|
|
10595
|
+
input.onEvent?.({
|
|
10596
|
+
type: "ended",
|
|
10597
|
+
bindingId,
|
|
10598
|
+
sessionId,
|
|
10599
|
+
reason: message.reason
|
|
10600
|
+
});
|
|
10601
|
+
resolve({
|
|
10602
|
+
reconnect: false,
|
|
10603
|
+
reason: message.reason.message
|
|
10604
|
+
});
|
|
10605
|
+
return;
|
|
10606
|
+
}
|
|
10607
|
+
input.onEvent?.({
|
|
10608
|
+
type: "ended",
|
|
10609
|
+
bindingId,
|
|
10610
|
+
sessionId,
|
|
10611
|
+
reason: message.reason
|
|
10612
|
+
});
|
|
10613
|
+
resolve({
|
|
10614
|
+
reconnect: false,
|
|
10615
|
+
reason: message.reason.message
|
|
10616
|
+
});
|
|
10617
|
+
});
|
|
10618
|
+
listen(socket, "close", (event) => {
|
|
10619
|
+
resolve({
|
|
10620
|
+
reconnect: !input.signal?.aborted && attempt < 1,
|
|
10621
|
+
reason: event.reason ?? `WebSocket closed${event.code ? ` (${event.code})` : ""}.`
|
|
10622
|
+
});
|
|
10623
|
+
});
|
|
10624
|
+
listen(socket, "error", () => {
|
|
10625
|
+
resolve({
|
|
10626
|
+
reconnect: !input.signal?.aborted && attempt < 1,
|
|
10627
|
+
reason: "WebSocket error."
|
|
10628
|
+
});
|
|
10629
|
+
});
|
|
10630
|
+
});
|
|
10631
|
+
heartbeatTimer = setInterval(() => {
|
|
10632
|
+
heartbeat(socket);
|
|
10633
|
+
}, heartbeatIntervalMs);
|
|
10634
|
+
await heartbeat(socket);
|
|
10635
|
+
const closed = await Promise.race([closePromise, waitForAbort(input.signal)]);
|
|
10636
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10637
|
+
heartbeatTimer = void 0;
|
|
10638
|
+
closeSocket(socket, 1e3, "Binding Session closed.");
|
|
10639
|
+
if (closed === "abort") return;
|
|
10640
|
+
if (closed.reconnect) {
|
|
10641
|
+
input.onEvent?.({
|
|
10642
|
+
type: "reconnecting",
|
|
10643
|
+
bindingId,
|
|
10644
|
+
sessionId,
|
|
10645
|
+
attempt: attempt + 1,
|
|
10646
|
+
reason: closed.reason
|
|
10647
|
+
});
|
|
10648
|
+
await connect(attempt + 1);
|
|
10649
|
+
}
|
|
10650
|
+
};
|
|
10651
|
+
try {
|
|
10652
|
+
await connect(0);
|
|
10653
|
+
} finally {
|
|
10654
|
+
ended = true;
|
|
10655
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10656
|
+
const reason = input.signal?.aborted ? {
|
|
10657
|
+
code: "interrupted",
|
|
10658
|
+
message: "Binding Session ended."
|
|
10659
|
+
} : {
|
|
10660
|
+
code: "completed",
|
|
10661
|
+
message: "Binding Session completed."
|
|
9624
10662
|
};
|
|
10663
|
+
await endRemoteSession(fetchImpl, input.remote, requestInit, bindingId, sessionId, reason);
|
|
10664
|
+
input.onEvent?.({
|
|
10665
|
+
type: "ended",
|
|
10666
|
+
bindingId,
|
|
10667
|
+
sessionId,
|
|
10668
|
+
reason
|
|
10669
|
+
});
|
|
9625
10670
|
}
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
10671
|
+
return {
|
|
10672
|
+
ok: true,
|
|
10673
|
+
bindingId,
|
|
10674
|
+
sessionId,
|
|
10675
|
+
projectRoot: input.projectRoot,
|
|
10676
|
+
projectFingerprint,
|
|
10677
|
+
webSocketUrl: publicWebSocketUrl,
|
|
10678
|
+
ended
|
|
10679
|
+
};
|
|
10680
|
+
}
|
|
10681
|
+
async function postJson(fetchImpl, url, requestInit, body) {
|
|
10682
|
+
const headers = new Headers(requestInit.headers);
|
|
10683
|
+
headers.set("content-type", "application/json");
|
|
10684
|
+
const response = await fetchImpl(url, {
|
|
10685
|
+
...requestInit,
|
|
10686
|
+
method: "POST",
|
|
10687
|
+
headers,
|
|
10688
|
+
body: JSON.stringify(body)
|
|
10689
|
+
});
|
|
10690
|
+
if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Project Binding request failed (${response.status}).`);
|
|
10691
|
+
return await response.json().catch(() => ({}));
|
|
10692
|
+
}
|
|
10693
|
+
async function endRemoteSession(fetchImpl, remote, requestInit, bindingId, sessionId, reason) {
|
|
10694
|
+
const headers = new Headers(requestInit.headers);
|
|
10695
|
+
headers.set("content-type", "application/json");
|
|
10696
|
+
await fetchImpl(endSessionUrl(remote, bindingId), {
|
|
10697
|
+
...requestInit,
|
|
10698
|
+
method: "DELETE",
|
|
10699
|
+
headers,
|
|
10700
|
+
body: JSON.stringify({
|
|
10701
|
+
sessionId,
|
|
10702
|
+
terminalReason: reason
|
|
10703
|
+
})
|
|
10704
|
+
}).catch(() => void 0);
|
|
10705
|
+
}
|
|
10706
|
+
function sessionUrl(remote) {
|
|
10707
|
+
return controlProjectBindingUrl(remote, "sessions");
|
|
10708
|
+
}
|
|
10709
|
+
function heartbeatUrl(remote, bindingId) {
|
|
10710
|
+
return controlProjectBindingUrl(remote, `${encodeURIComponent(bindingId)}/heartbeat`);
|
|
10711
|
+
}
|
|
10712
|
+
function endSessionUrl(remote, bindingId) {
|
|
10713
|
+
return controlProjectBindingUrl(remote, `${encodeURIComponent(bindingId)}/session`);
|
|
10714
|
+
}
|
|
10715
|
+
function controlProjectBindingUrl(remote, suffix) {
|
|
10716
|
+
const url = new URL(remote.projectBindingWebSocketUrl);
|
|
10717
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
10718
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
10719
|
+
url.pathname = url.pathname.replace(/\/connect$/u, `/${suffix}`);
|
|
10720
|
+
url.search = "";
|
|
10721
|
+
url.hash = "";
|
|
10722
|
+
return url;
|
|
10723
|
+
}
|
|
10724
|
+
function bindingSocketUrl(remote, bindingId, sessionId, projectFingerprint) {
|
|
10725
|
+
const url = new URL(remote.projectBindingWebSocketUrl);
|
|
10726
|
+
url.searchParams.set("bindingId", bindingId);
|
|
10727
|
+
url.searchParams.set("sessionId", sessionId);
|
|
10728
|
+
url.searchParams.set("projectFingerprint", projectFingerprint);
|
|
10729
|
+
return url.toString();
|
|
10730
|
+
}
|
|
10731
|
+
function bindingSocketProtocols(remote) {
|
|
10732
|
+
if (remote.auth.type !== "bearer") return void 0;
|
|
10733
|
+
return ["caplets.project-binding.v1", `caplets.bearer.${Buffer$1.from(remote.auth.token).toString("base64url")}`];
|
|
10734
|
+
}
|
|
10735
|
+
function parseSocketMessage(data) {
|
|
10736
|
+
const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? new TextDecoder().decode(data) : void 0;
|
|
10737
|
+
if (!text) return void 0;
|
|
10738
|
+
const parsed = JSON.parse(text);
|
|
10739
|
+
return typeof parsed.type === "string" ? parsed : void 0;
|
|
10740
|
+
}
|
|
10741
|
+
async function waitForOpen(socket, signal) {
|
|
10742
|
+
if (socket.readyState === 1) return;
|
|
10743
|
+
await Promise.race([new Promise((resolve, reject) => {
|
|
10744
|
+
listen(socket, "open", () => resolve(), { once: true });
|
|
10745
|
+
listen(socket, "error", () => reject(new CapletsError("SERVER_UNAVAILABLE", "Project Binding WebSocket failed to open.")), { once: true });
|
|
10746
|
+
}), waitForAbort(signal).then(() => void 0)]);
|
|
10747
|
+
}
|
|
10748
|
+
function waitForAbort(signal) {
|
|
10749
|
+
if (signal?.aborted) return Promise.resolve("abort");
|
|
10750
|
+
return new Promise((resolve) => {
|
|
10751
|
+
signal?.addEventListener("abort", () => resolve("abort"), { once: true });
|
|
10752
|
+
});
|
|
10753
|
+
}
|
|
10754
|
+
function listen(socket, type, listener, options) {
|
|
10755
|
+
if (socket.addEventListener) {
|
|
10756
|
+
socket.addEventListener(type, listener, options);
|
|
10757
|
+
return;
|
|
9634
10758
|
}
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
10759
|
+
const key = `on${type}`;
|
|
10760
|
+
const existing = socket[key];
|
|
10761
|
+
const wrapper = (event) => {
|
|
10762
|
+
existing?.(event);
|
|
10763
|
+
listener(event);
|
|
10764
|
+
if (options?.once && socket[key] === wrapper) socket[key] = existing ?? null;
|
|
10765
|
+
};
|
|
10766
|
+
socket[key] = wrapper;
|
|
10767
|
+
}
|
|
10768
|
+
function closeSocket(socket, code, reason) {
|
|
10769
|
+
try {
|
|
10770
|
+
socket.close(code, reason);
|
|
10771
|
+
} catch {}
|
|
10772
|
+
}
|
|
10773
|
+
//#endregion
|
|
10774
|
+
//#region src/project-binding/sync-size.ts
|
|
10775
|
+
const DEFAULT_SYNC_LIMITS = {
|
|
10776
|
+
free: {
|
|
10777
|
+
maxSingleFileBytes: 25 * 1024 * 1024,
|
|
10778
|
+
maxProjectBytes: 250 * 1024 * 1024
|
|
10779
|
+
},
|
|
10780
|
+
plus: {
|
|
10781
|
+
maxSingleFileBytes: 100 * 1024 * 1024,
|
|
10782
|
+
maxProjectBytes: 1024 * 1024 * 1024
|
|
10783
|
+
},
|
|
10784
|
+
pro: {
|
|
10785
|
+
maxSingleFileBytes: 250 * 1024 * 1024,
|
|
10786
|
+
maxProjectBytes: 5 * 1024 * 1024 * 1024
|
|
10787
|
+
},
|
|
10788
|
+
enterprise: {
|
|
10789
|
+
maxSingleFileBytes: 250 * 1024 * 1024,
|
|
10790
|
+
maxProjectBytes: 5 * 1024 * 1024 * 1024
|
|
10791
|
+
},
|
|
10792
|
+
self_hosted: {
|
|
10793
|
+
maxSingleFileBytes: 250 * 1024 * 1024,
|
|
10794
|
+
maxProjectBytes: 5 * 1024 * 1024 * 1024
|
|
10795
|
+
}
|
|
10796
|
+
};
|
|
10797
|
+
function enforceProjectSyncSizeLimits(input) {
|
|
10798
|
+
const limits = {
|
|
10799
|
+
...DEFAULT_SYNC_LIMITS[input.tier],
|
|
10800
|
+
...input.limits
|
|
10801
|
+
};
|
|
10802
|
+
const totalBytes = input.files.reduce((total, file) => total + file.sizeBytes, 0);
|
|
10803
|
+
const largestFileBytes = Math.max(0, ...input.files.map((file) => file.sizeBytes));
|
|
10804
|
+
if (largestFileBytes > limits.maxSingleFileBytes || totalBytes > limits.maxProjectBytes) return {
|
|
10805
|
+
ok: false,
|
|
10806
|
+
code: "sync_size_limit_exceeded",
|
|
10807
|
+
totalBytes,
|
|
10808
|
+
maxSingleFileBytes: limits.maxSingleFileBytes,
|
|
10809
|
+
maxProjectBytes: limits.maxProjectBytes,
|
|
10810
|
+
largestFileBytes,
|
|
10811
|
+
recoveryCommand: "Add exclusions to .capletsignore or upgrade the workspace plan."
|
|
10812
|
+
};
|
|
10813
|
+
return {
|
|
10814
|
+
ok: true,
|
|
10815
|
+
totalBytes,
|
|
10816
|
+
maxSingleFileBytes: limits.maxSingleFileBytes,
|
|
10817
|
+
maxProjectBytes: limits.maxProjectBytes
|
|
10818
|
+
};
|
|
10819
|
+
}
|
|
10820
|
+
//#endregion
|
|
10821
|
+
//#region src/project-binding/attach.ts
|
|
10822
|
+
async function resolveAttachOptions(raw = {}, env = process.env) {
|
|
10823
|
+
return await resolveAttachOptionsForRun(raw, env);
|
|
10824
|
+
}
|
|
10825
|
+
async function resolveAttachOptionsForRun(raw = {}, env = process.env) {
|
|
10826
|
+
const selection = await resolveRemoteSelection({
|
|
10827
|
+
...raw.remoteUrl !== void 0 ? { remoteUrl: raw.remoteUrl } : {},
|
|
10828
|
+
...raw.user !== void 0 ? { user: raw.user } : {},
|
|
10829
|
+
...raw.password !== void 0 ? { password: raw.password } : {},
|
|
10830
|
+
...raw.token !== void 0 ? { token: raw.token } : {},
|
|
10831
|
+
...raw.workspace !== void 0 ? { workspace: raw.workspace } : {},
|
|
10832
|
+
...raw.fetch !== void 0 ? { fetch: raw.fetch } : {}
|
|
10833
|
+
}, env);
|
|
10834
|
+
return {
|
|
10835
|
+
projectRoot: raw.projectRoot ?? process.cwd(),
|
|
10836
|
+
json: raw.json === true,
|
|
10837
|
+
verbose: raw.verbose === true,
|
|
10838
|
+
once: raw.once === true,
|
|
10839
|
+
remote: selection.remote,
|
|
10840
|
+
authMode: selection.kind,
|
|
10841
|
+
...selection.kind === "hosted_cloud" ? { selectedWorkspace: selection.selectedWorkspace } : raw.workspace ? { selectedWorkspace: raw.workspace } : {}
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
async function attachProjectOnce(raw = {}, env = process.env) {
|
|
10845
|
+
const resolved = await resolveAttachOptionsForRun({
|
|
10846
|
+
...raw,
|
|
10847
|
+
once: true
|
|
10848
|
+
}, env);
|
|
10849
|
+
bootstrapProjectBindingGitignore(resolved.projectRoot);
|
|
10850
|
+
preflightProjectSync(resolved.projectRoot, hostedTier(env));
|
|
10851
|
+
const response = await (resolved.remote.fetch ?? fetch)(projectBindingProbeUrl(resolved.remote), {
|
|
10852
|
+
...resolved.remote.requestInit,
|
|
10853
|
+
method: "GET"
|
|
10854
|
+
});
|
|
10855
|
+
if (response.status !== 101 && !await isWebSocketUpgradeRequired(response)) throw new CapletsError("SERVER_UNAVAILABLE", `Project Binding WebSocket unavailable at ${resolved.remote.projectBindingWebSocketUrl}. Run caplets doctor for details.`);
|
|
10856
|
+
return {
|
|
10857
|
+
ok: true,
|
|
10858
|
+
projectRoot: resolved.projectRoot,
|
|
10859
|
+
webSocketUrl: resolved.remote.projectBindingWebSocketUrl.toString()
|
|
10860
|
+
};
|
|
10861
|
+
}
|
|
10862
|
+
async function attachProjectSession(raw = {}, env = process.env, options = {}) {
|
|
10863
|
+
const resolved = await resolveAttachOptionsForRun(raw, env);
|
|
10864
|
+
bootstrapProjectBindingGitignore(resolved.projectRoot);
|
|
10865
|
+
preflightProjectSync(resolved.projectRoot, hostedTier(env));
|
|
10866
|
+
return await runProjectBindingSession({
|
|
10867
|
+
projectRoot: resolved.projectRoot,
|
|
10868
|
+
remote: resolved.remote,
|
|
10869
|
+
fetch: resolved.remote.fetch,
|
|
10870
|
+
signal: options.signal,
|
|
10871
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
10872
|
+
webSocketFactory: options.webSocketFactory,
|
|
10873
|
+
onEvent: options.onEvent
|
|
10874
|
+
});
|
|
10875
|
+
}
|
|
10876
|
+
function projectBindingProbeUrl(remote) {
|
|
10877
|
+
const url = new URL(remote.projectBindingWebSocketUrl);
|
|
10878
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
10879
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
10880
|
+
return url;
|
|
10881
|
+
}
|
|
10882
|
+
async function isWebSocketUpgradeRequired(response) {
|
|
10883
|
+
if (response.status !== 426) return false;
|
|
10884
|
+
if (!(response.headers.get("content-type") ?? "").includes("application/json")) return false;
|
|
10885
|
+
return (await response.json().catch(() => void 0))?.error === "websocket_upgrade_required";
|
|
10886
|
+
}
|
|
10887
|
+
function preflightProjectSync(projectRoot, tier) {
|
|
10888
|
+
if (!existsSync(projectRoot)) return;
|
|
10889
|
+
const size = enforceProjectSyncSizeLimits({
|
|
10890
|
+
tier,
|
|
10891
|
+
files: buildProjectSyncManifest({ projectRoot }).files
|
|
10892
|
+
});
|
|
10893
|
+
if (!size.ok) throw new ProjectBindingError({
|
|
10894
|
+
code: size.code,
|
|
10895
|
+
message: "Project sync size exceeds the selected workspace policy.",
|
|
10896
|
+
recoveryCommand: size.recoveryCommand
|
|
10897
|
+
});
|
|
10898
|
+
}
|
|
10899
|
+
function hostedTier(env) {
|
|
10900
|
+
const value = env.CAPLETS_CLOUD_TIER?.toLowerCase();
|
|
10901
|
+
return value === "plus" || value === "pro" || value === "enterprise" ? value : "free";
|
|
10902
|
+
}
|
|
10903
|
+
//#endregion
|
|
10904
|
+
//#region src/remote-control/client.ts
|
|
10905
|
+
var RemoteControlClient = class {
|
|
10906
|
+
#baseUrl;
|
|
10907
|
+
#requestInit;
|
|
10908
|
+
#fetch;
|
|
10909
|
+
constructor(options) {
|
|
10910
|
+
this.#baseUrl = options.baseUrl;
|
|
10911
|
+
this.#requestInit = options.requestInit;
|
|
10912
|
+
this.#fetch = options.fetch ?? fetch;
|
|
10913
|
+
}
|
|
10914
|
+
async request(command, args) {
|
|
10915
|
+
const controlUrl = controlUrlForBase(this.#baseUrl);
|
|
10916
|
+
let response;
|
|
10917
|
+
try {
|
|
10918
|
+
response = await this.#fetch(controlUrl, {
|
|
10919
|
+
...this.#requestInit,
|
|
10920
|
+
method: "POST",
|
|
10921
|
+
headers: mergeJsonHeaders(this.#requestInit.headers),
|
|
10922
|
+
body: JSON.stringify({
|
|
10923
|
+
command,
|
|
10924
|
+
arguments: args
|
|
10925
|
+
})
|
|
10926
|
+
});
|
|
9640
10927
|
} catch (error) {
|
|
9641
|
-
this
|
|
10928
|
+
throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
|
|
9642
10929
|
}
|
|
10930
|
+
if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets remote authentication failed. Check CAPLETS_REMOTE_USER and CAPLETS_REMOTE_PASSWORD.");
|
|
10931
|
+
if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
|
|
10932
|
+
const payload = await parseRemoteCliResponse(response);
|
|
10933
|
+
if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
|
|
10934
|
+
return payload.result;
|
|
9643
10935
|
}
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
10936
|
+
};
|
|
10937
|
+
function mergeJsonHeaders(headers) {
|
|
10938
|
+
const merged = new Headers(headers);
|
|
10939
|
+
merged.set("content-type", "application/json");
|
|
10940
|
+
return merged;
|
|
10941
|
+
}
|
|
10942
|
+
function safeBaseUrl(baseUrl) {
|
|
10943
|
+
const safe = new URL(baseUrl.href);
|
|
10944
|
+
safe.username = "";
|
|
10945
|
+
safe.password = "";
|
|
10946
|
+
safe.search = "";
|
|
10947
|
+
safe.hash = "";
|
|
10948
|
+
return safe.toString();
|
|
10949
|
+
}
|
|
10950
|
+
async function parseRemoteCliResponse(response) {
|
|
10951
|
+
let payload;
|
|
10952
|
+
try {
|
|
10953
|
+
payload = await response.json();
|
|
10954
|
+
} catch (error) {
|
|
10955
|
+
throw invalidRemoteControlResponse(error);
|
|
9650
10956
|
}
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
10957
|
+
if (!isRecord$1(payload)) throw invalidRemoteControlResponse();
|
|
10958
|
+
if (payload.ok === true) {
|
|
10959
|
+
if (!("result" in payload)) throw invalidRemoteControlResponse();
|
|
10960
|
+
return {
|
|
10961
|
+
ok: true,
|
|
10962
|
+
result: payload.result
|
|
10963
|
+
};
|
|
9657
10964
|
}
|
|
9658
|
-
|
|
10965
|
+
if (payload.ok === false) {
|
|
10966
|
+
const error = payload.error;
|
|
10967
|
+
if (!isRecord$1(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
|
|
10968
|
+
if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
|
|
10969
|
+
const errorResponse = {
|
|
10970
|
+
ok: false,
|
|
10971
|
+
error: {
|
|
10972
|
+
code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
|
|
10973
|
+
message: error.message
|
|
10974
|
+
}
|
|
10975
|
+
};
|
|
10976
|
+
if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
|
|
10977
|
+
return errorResponse;
|
|
10978
|
+
}
|
|
10979
|
+
throw invalidRemoteControlResponse();
|
|
10980
|
+
}
|
|
10981
|
+
function invalidRemoteControlResponse(cause) {
|
|
10982
|
+
return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
|
|
10983
|
+
}
|
|
10984
|
+
function isRecord$1(value) {
|
|
10985
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10986
|
+
}
|
|
10987
|
+
function isCapletsErrorCode(value) {
|
|
10988
|
+
return CAPLETS_ERROR_CODES.includes(value);
|
|
10989
|
+
}
|
|
10990
|
+
function redactRemoteMessage(message) {
|
|
10991
|
+
return String(redactSecrets(message)).replace(/\b(authorization\s*:\s*(?:basic|bearer)\s+)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:access_)?token=)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh(?:_token)?)\s*[=:]\s*)[^\s,;]+/giu, "$1[REDACTED]");
|
|
10992
|
+
}
|
|
9659
10993
|
//#endregion
|
|
9660
10994
|
//#region src/serve/stdio.ts
|
|
9661
10995
|
async function serveStdio(options = {}) {
|
|
@@ -9671,20 +11005,402 @@ async function serveStdio(options = {}) {
|
|
|
9671
11005
|
await engine.close();
|
|
9672
11006
|
}
|
|
9673
11007
|
};
|
|
9674
|
-
let sigintHandler;
|
|
9675
|
-
let sigtermHandler;
|
|
9676
|
-
if (options.signalHandling !== false) {
|
|
9677
|
-
sigintHandler = () => void close().finally(() => process.exit(130));
|
|
9678
|
-
sigtermHandler = () => void close().finally(() => process.exit(143));
|
|
9679
|
-
process.once("SIGINT", sigintHandler);
|
|
9680
|
-
process.once("SIGTERM", sigtermHandler);
|
|
9681
|
-
}
|
|
11008
|
+
let sigintHandler;
|
|
11009
|
+
let sigtermHandler;
|
|
11010
|
+
if (options.signalHandling !== false) {
|
|
11011
|
+
sigintHandler = () => void close().finally(() => process.exit(130));
|
|
11012
|
+
sigtermHandler = () => void close().finally(() => process.exit(143));
|
|
11013
|
+
process.once("SIGINT", sigintHandler);
|
|
11014
|
+
process.once("SIGTERM", sigtermHandler);
|
|
11015
|
+
}
|
|
11016
|
+
try {
|
|
11017
|
+
await session.connect(new StdioServerTransport());
|
|
11018
|
+
} finally {
|
|
11019
|
+
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
11020
|
+
if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
|
|
11021
|
+
await close();
|
|
11022
|
+
}
|
|
11023
|
+
}
|
|
11024
|
+
//#endregion
|
|
11025
|
+
//#region src/serve/daemon/config.ts
|
|
11026
|
+
function readDaemonConfig(paths) {
|
|
11027
|
+
return readJson(paths.configFile);
|
|
11028
|
+
}
|
|
11029
|
+
function writeDaemonConfig(paths, serve, command, now = /* @__PURE__ */ new Date()) {
|
|
11030
|
+
const config = {
|
|
11031
|
+
instance: "default",
|
|
11032
|
+
serve,
|
|
11033
|
+
command,
|
|
11034
|
+
paths,
|
|
11035
|
+
updatedAt: now.toISOString()
|
|
11036
|
+
};
|
|
11037
|
+
writeJson(paths.configFile, config);
|
|
11038
|
+
return config;
|
|
11039
|
+
}
|
|
11040
|
+
function readDaemonState(paths) {
|
|
11041
|
+
return readJson(paths.stateFile);
|
|
11042
|
+
}
|
|
11043
|
+
function writeDaemonState(paths, state, now = /* @__PURE__ */ new Date()) {
|
|
11044
|
+
const next = {
|
|
11045
|
+
instance: "default",
|
|
11046
|
+
...state,
|
|
11047
|
+
updatedAt: state.updatedAt ?? now.toISOString()
|
|
11048
|
+
};
|
|
11049
|
+
writeJson(paths.stateFile, next);
|
|
11050
|
+
return next;
|
|
11051
|
+
}
|
|
11052
|
+
function redactDaemonStatus(status) {
|
|
11053
|
+
return redactDaemonValue(status);
|
|
11054
|
+
}
|
|
11055
|
+
function redactDaemonValue(value) {
|
|
11056
|
+
if (Array.isArray(value)) return redactDaemonArray(value);
|
|
11057
|
+
if (!value || typeof value !== "object") return value;
|
|
11058
|
+
const redacted = {};
|
|
11059
|
+
for (const [key, nested] of Object.entries(value)) redacted[key] = /password|token|secret|authorization|credential/iu.test(key) ? "[REDACTED]" : redactDaemonValue(nested);
|
|
11060
|
+
return redacted;
|
|
11061
|
+
}
|
|
11062
|
+
function redactDaemonArray(value) {
|
|
11063
|
+
const redacted = [];
|
|
11064
|
+
let redactNext = false;
|
|
11065
|
+
for (const item of value) {
|
|
11066
|
+
if (redactNext) {
|
|
11067
|
+
redacted.push("[REDACTED]");
|
|
11068
|
+
redactNext = false;
|
|
11069
|
+
continue;
|
|
11070
|
+
}
|
|
11071
|
+
redacted.push(redactDaemonValue(item));
|
|
11072
|
+
if (typeof item === "string" && /--(?:password|token|secret|authorization|credential)$/iu.test(item)) redactNext = true;
|
|
11073
|
+
}
|
|
11074
|
+
return redacted;
|
|
11075
|
+
}
|
|
11076
|
+
function readJson(path) {
|
|
11077
|
+
if (!existsSync(path)) return void 0;
|
|
11078
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
11079
|
+
}
|
|
11080
|
+
function writeJson(path, value) {
|
|
11081
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
11082
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
11083
|
+
}
|
|
11084
|
+
//#endregion
|
|
11085
|
+
//#region src/serve/daemon/platform-darwin.ts
|
|
11086
|
+
function buildLaunchdUserAgentDescriptor(paths, command) {
|
|
11087
|
+
const label = "dev.caplets.serve.default";
|
|
11088
|
+
return {
|
|
11089
|
+
kind: "launchd-user-agent",
|
|
11090
|
+
label,
|
|
11091
|
+
path: `${paths.configFile}.plist`,
|
|
11092
|
+
plist: [
|
|
11093
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
11094
|
+
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">",
|
|
11095
|
+
"<plist version=\"1.0\">",
|
|
11096
|
+
"<dict>",
|
|
11097
|
+
" <key>Label</key>",
|
|
11098
|
+
` <string>${label}</string>`,
|
|
11099
|
+
" <key>ProgramArguments</key>",
|
|
11100
|
+
" <array>",
|
|
11101
|
+
` <string>${escapeXml(command.executable)}</string>`,
|
|
11102
|
+
...command.args.map((arg) => ` <string>${escapeXml(arg)}</string>`),
|
|
11103
|
+
" </array>",
|
|
11104
|
+
" <key>RunAtLoad</key>",
|
|
11105
|
+
" <true/>",
|
|
11106
|
+
" <key>KeepAlive</key>",
|
|
11107
|
+
" <true/>",
|
|
11108
|
+
" <key>StandardOutPath</key>",
|
|
11109
|
+
` <string>${escapeXml(paths.stdoutLog)}</string>`,
|
|
11110
|
+
" <key>StandardErrorPath</key>",
|
|
11111
|
+
` <string>${escapeXml(paths.stderrLog)}</string>`,
|
|
11112
|
+
"</dict>",
|
|
11113
|
+
"</plist>",
|
|
11114
|
+
""
|
|
11115
|
+
].join("\n")
|
|
11116
|
+
};
|
|
11117
|
+
}
|
|
11118
|
+
//#endregion
|
|
11119
|
+
//#region src/serve/daemon/platform-linux.ts
|
|
11120
|
+
function buildLinuxServiceDescriptor(paths, command, serviceAvailable = true) {
|
|
11121
|
+
if (!serviceAvailable) return {
|
|
11122
|
+
kind: "manual",
|
|
11123
|
+
reason: "Linux systemd user service is not available; run the daemon command manually.",
|
|
11124
|
+
command
|
|
11125
|
+
};
|
|
11126
|
+
return {
|
|
11127
|
+
kind: "systemd-user",
|
|
11128
|
+
unitName: "caplets-serve-default.service",
|
|
11129
|
+
path: `${paths.configFile}.service`,
|
|
11130
|
+
unit: [
|
|
11131
|
+
"[Unit]",
|
|
11132
|
+
"Description=Caplets HTTP daemon (default)",
|
|
11133
|
+
"After=network.target",
|
|
11134
|
+
"",
|
|
11135
|
+
"[Service]",
|
|
11136
|
+
"Type=simple",
|
|
11137
|
+
`ExecStart=${shellJoin([command.executable, ...command.args])}`,
|
|
11138
|
+
"Restart=on-failure",
|
|
11139
|
+
`StandardOutput=append:${paths.stdoutLog}`,
|
|
11140
|
+
`StandardError=append:${paths.stderrLog}`,
|
|
11141
|
+
"",
|
|
11142
|
+
"[Install]",
|
|
11143
|
+
"WantedBy=default.target",
|
|
11144
|
+
""
|
|
11145
|
+
].join("\n")
|
|
11146
|
+
};
|
|
11147
|
+
}
|
|
11148
|
+
function shellJoin(args) {
|
|
11149
|
+
return args.map((arg) => /^[A-Za-z0-9_./:=@-]+$/u.test(arg) ? arg : `'${arg.replaceAll("'", "'\\''")}'`).join(" ");
|
|
11150
|
+
}
|
|
11151
|
+
//#endregion
|
|
11152
|
+
//#region src/serve/daemon/platform-windows.ts
|
|
11153
|
+
function buildWindowsScheduledTaskDescriptor(command) {
|
|
11154
|
+
const taskName = "Caplets Serve Default";
|
|
11155
|
+
return {
|
|
11156
|
+
kind: "windows-scheduled-task",
|
|
11157
|
+
taskName,
|
|
11158
|
+
commands: {
|
|
11159
|
+
register: `schtasks /Create /TN "${taskName}" /SC ONLOGON /TR "${commandLine([command.executable, ...command.args])}" /F`,
|
|
11160
|
+
unregister: `schtasks /Delete /TN "${taskName}" /F`,
|
|
11161
|
+
query: `schtasks /Query /TN "${taskName}"`
|
|
11162
|
+
}
|
|
11163
|
+
};
|
|
11164
|
+
}
|
|
11165
|
+
function commandLine(args) {
|
|
11166
|
+
return args.map((arg) => arg.includes(" ") ? `\\"${arg}\\"` : arg).join(" ");
|
|
11167
|
+
}
|
|
11168
|
+
//#endregion
|
|
11169
|
+
//#region src/serve/daemon/platform.ts
|
|
11170
|
+
function buildDaemonPlatformDescriptor(options) {
|
|
11171
|
+
const platform = options.platform ?? process.platform;
|
|
11172
|
+
if (platform === "darwin") return buildLaunchdUserAgentDescriptor(options.paths, options.command);
|
|
11173
|
+
if (platform === "linux") return buildLinuxServiceDescriptor(options.paths, options.command, options.serviceAvailable);
|
|
11174
|
+
if (platform === "win32") return buildWindowsScheduledTaskDescriptor(options.command);
|
|
11175
|
+
return {
|
|
11176
|
+
kind: "manual",
|
|
11177
|
+
reason: `Automatic user service descriptors are not available on ${platform}.`,
|
|
11178
|
+
command: options.command
|
|
11179
|
+
};
|
|
11180
|
+
}
|
|
11181
|
+
function escapeXml(value) {
|
|
11182
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
11183
|
+
}
|
|
11184
|
+
//#endregion
|
|
11185
|
+
//#region src/serve/daemon/process.ts
|
|
11186
|
+
function daemonServeCommand(options) {
|
|
11187
|
+
return {
|
|
11188
|
+
executable: process.argv[1] ?? "caplets",
|
|
11189
|
+
args: daemonServeArgs(options)
|
|
11190
|
+
};
|
|
11191
|
+
}
|
|
11192
|
+
function daemonServeArgs(options) {
|
|
11193
|
+
const args = [
|
|
11194
|
+
"serve",
|
|
11195
|
+
"--transport",
|
|
11196
|
+
"http",
|
|
11197
|
+
"--host",
|
|
11198
|
+
options.host,
|
|
11199
|
+
"--port",
|
|
11200
|
+
String(options.port),
|
|
11201
|
+
"--path",
|
|
11202
|
+
options.path,
|
|
11203
|
+
"--user",
|
|
11204
|
+
options.auth.user
|
|
11205
|
+
];
|
|
11206
|
+
if (options.auth.enabled) args.push("--password", options.auth.password);
|
|
11207
|
+
if (options.warnUnauthenticatedNetwork) args.push("--allow-unauthenticated-http");
|
|
11208
|
+
if (options.trustProxy) args.push("--trust-proxy");
|
|
11209
|
+
return args;
|
|
11210
|
+
}
|
|
11211
|
+
function createNodeDaemonProcessRunner() {
|
|
11212
|
+
return {
|
|
11213
|
+
async isRunning(pid) {
|
|
11214
|
+
try {
|
|
11215
|
+
process.kill(pid, 0);
|
|
11216
|
+
return true;
|
|
11217
|
+
} catch {
|
|
11218
|
+
return false;
|
|
11219
|
+
}
|
|
11220
|
+
},
|
|
11221
|
+
async start(command) {
|
|
11222
|
+
mkdirSync(dirname(command.stdoutLog), { recursive: true });
|
|
11223
|
+
mkdirSync(dirname(command.stderrLog), { recursive: true });
|
|
11224
|
+
const stdout = openSync(command.stdoutLog, "a");
|
|
11225
|
+
const stderr = openSync(command.stderrLog, "a");
|
|
11226
|
+
try {
|
|
11227
|
+
const child = spawn(process.execPath, [process.argv[1] ?? "caplets", ...command.args], {
|
|
11228
|
+
detached: true,
|
|
11229
|
+
stdio: [
|
|
11230
|
+
"ignore",
|
|
11231
|
+
stdout,
|
|
11232
|
+
stderr
|
|
11233
|
+
],
|
|
11234
|
+
env: process.env
|
|
11235
|
+
});
|
|
11236
|
+
child.unref();
|
|
11237
|
+
return child.pid ?? 0;
|
|
11238
|
+
} finally {
|
|
11239
|
+
closeSync(stdout);
|
|
11240
|
+
closeSync(stderr);
|
|
11241
|
+
}
|
|
11242
|
+
},
|
|
11243
|
+
async stop(pid) {
|
|
11244
|
+
try {
|
|
11245
|
+
process.kill(pid, "SIGTERM");
|
|
11246
|
+
} catch {
|
|
11247
|
+
return;
|
|
11248
|
+
}
|
|
11249
|
+
}
|
|
11250
|
+
};
|
|
11251
|
+
}
|
|
11252
|
+
//#endregion
|
|
11253
|
+
//#region src/serve/daemon/paths.ts
|
|
11254
|
+
function resolveServeDaemonPaths(options = {}) {
|
|
11255
|
+
const platform = options.platform ?? process.platform;
|
|
11256
|
+
const home = options.home ?? homedir();
|
|
11257
|
+
const env = options.env ?? process.env;
|
|
11258
|
+
const path = platform === "win32" ? win32 : posix;
|
|
11259
|
+
const configBase = defaultConfigBaseDir(env, home, platform);
|
|
11260
|
+
const stateBase = defaultStateBaseDir(env, home, platform);
|
|
11261
|
+
if (platform === "win32") {
|
|
11262
|
+
const stateDir = path.join(stateBase, "Caplets", "State", "serve", "default");
|
|
11263
|
+
const logDir = path.join(stateDir, "logs");
|
|
11264
|
+
return {
|
|
11265
|
+
instance: "default",
|
|
11266
|
+
stateDir,
|
|
11267
|
+
logDir,
|
|
11268
|
+
stateFile: path.join(stateDir, "state.json"),
|
|
11269
|
+
pidFile: path.join(stateDir, "server.pid"),
|
|
11270
|
+
stdoutLog: path.join(logDir, "stdout.log"),
|
|
11271
|
+
stderrLog: path.join(logDir, "stderr.log"),
|
|
11272
|
+
configFile: path.join(configBase, "Caplets", "serve", "default.json")
|
|
11273
|
+
};
|
|
11274
|
+
}
|
|
11275
|
+
const stateDir = path.join(stateBase, "caplets", "serve", "default");
|
|
11276
|
+
const logDir = path.join(stateDir, "logs");
|
|
11277
|
+
return {
|
|
11278
|
+
instance: "default",
|
|
11279
|
+
stateDir,
|
|
11280
|
+
logDir,
|
|
11281
|
+
stateFile: path.join(stateDir, "state.json"),
|
|
11282
|
+
pidFile: path.join(stateDir, "server.pid"),
|
|
11283
|
+
stdoutLog: path.join(logDir, "stdout.log"),
|
|
11284
|
+
stderrLog: path.join(logDir, "stderr.log"),
|
|
11285
|
+
configFile: path.join(configBase, "caplets", "serve", "default.json")
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
11288
|
+
//#endregion
|
|
11289
|
+
//#region src/serve/daemon/index.ts
|
|
11290
|
+
async function startDaemon(raw = {}, options = {}) {
|
|
11291
|
+
const paths = resolveServeDaemonPaths(options);
|
|
11292
|
+
const processRunner = options.process ?? createNodeDaemonProcessRunner();
|
|
11293
|
+
const existing = await daemonStatus({
|
|
11294
|
+
...options,
|
|
11295
|
+
process: processRunner
|
|
11296
|
+
});
|
|
11297
|
+
if (existing.running) throw new CapletsError("REQUEST_INVALID", "Caplets HTTP daemon is already running.");
|
|
11298
|
+
const serve = resolveDaemonServeOptions(raw, options.env ?? process.env);
|
|
11299
|
+
const command = daemonServeCommand(serve);
|
|
11300
|
+
mkdirSync(paths.logDir, { recursive: true });
|
|
11301
|
+
const config = writeDaemonConfig(paths, serve, command);
|
|
11302
|
+
const pid = await processRunner.start({
|
|
11303
|
+
args: command.args,
|
|
11304
|
+
stdoutLog: paths.stdoutLog,
|
|
11305
|
+
stderrLog: paths.stderrLog,
|
|
11306
|
+
configFile: paths.configFile
|
|
11307
|
+
});
|
|
11308
|
+
writeFileSync(paths.pidFile, `${pid}\n`);
|
|
11309
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11310
|
+
return { status: redactDaemonStatus({
|
|
11311
|
+
...writeDaemonState(paths, {
|
|
11312
|
+
running: true,
|
|
11313
|
+
pid,
|
|
11314
|
+
startedAt: now,
|
|
11315
|
+
enabled: existing.enabled,
|
|
11316
|
+
updatedAt: now
|
|
11317
|
+
}),
|
|
11318
|
+
paths,
|
|
11319
|
+
config
|
|
11320
|
+
}) };
|
|
11321
|
+
}
|
|
11322
|
+
async function stopDaemon(options = {}) {
|
|
11323
|
+
const paths = resolveServeDaemonPaths(options);
|
|
11324
|
+
const processRunner = options.process ?? createNodeDaemonProcessRunner();
|
|
11325
|
+
const existing = await daemonStatus({
|
|
11326
|
+
...options,
|
|
11327
|
+
process: processRunner
|
|
11328
|
+
});
|
|
11329
|
+
if (existing.running && existing.pid !== void 0) await processRunner.stop(existing.pid);
|
|
11330
|
+
rmSync(paths.pidFile, { force: true });
|
|
11331
|
+
return { status: redactDaemonStatus({
|
|
11332
|
+
...writeDaemonState(paths, {
|
|
11333
|
+
running: false,
|
|
11334
|
+
enabled: existing.enabled
|
|
11335
|
+
}),
|
|
11336
|
+
paths,
|
|
11337
|
+
...configProperty(readDaemonConfig(paths))
|
|
11338
|
+
}) };
|
|
11339
|
+
}
|
|
11340
|
+
async function restartDaemon(raw = {}, options = {}) {
|
|
11341
|
+
await stopDaemon(options);
|
|
11342
|
+
return startDaemon(raw, options);
|
|
11343
|
+
}
|
|
11344
|
+
async function daemonStatus(options = {}) {
|
|
11345
|
+
const paths = resolveServeDaemonPaths(options);
|
|
11346
|
+
const processRunner = options.process ?? createNodeDaemonProcessRunner();
|
|
11347
|
+
const config = readDaemonConfig(paths);
|
|
11348
|
+
const storedState = readDaemonState(paths);
|
|
11349
|
+
const pid = readPid(paths.pidFile) ?? storedState?.pid;
|
|
11350
|
+
const running = pid === void 0 ? false : await processRunner.isRunning(pid);
|
|
11351
|
+
if (!running) rmSync(paths.pidFile, { force: true });
|
|
11352
|
+
return redactDaemonStatus({
|
|
11353
|
+
...writeDaemonState(paths, {
|
|
11354
|
+
running,
|
|
11355
|
+
...running && pid !== void 0 ? { pid } : {},
|
|
11356
|
+
...running && storedState?.startedAt ? { startedAt: storedState.startedAt } : {},
|
|
11357
|
+
enabled: storedState?.enabled ?? false
|
|
11358
|
+
}),
|
|
11359
|
+
paths,
|
|
11360
|
+
...config ? { config } : {}
|
|
11361
|
+
});
|
|
11362
|
+
}
|
|
11363
|
+
async function enableDaemon(options = {}) {
|
|
11364
|
+
return setDaemonEnabled(true, options);
|
|
11365
|
+
}
|
|
11366
|
+
async function disableDaemon(options = {}) {
|
|
11367
|
+
return setDaemonEnabled(false, options);
|
|
11368
|
+
}
|
|
11369
|
+
async function setDaemonEnabled(enabled, options) {
|
|
11370
|
+
const paths = resolveServeDaemonPaths(options);
|
|
11371
|
+
const config = readDaemonConfig(paths);
|
|
11372
|
+
const command = config?.command ?? daemonServeCommand(resolveDaemonServeOptions({}, options.env));
|
|
11373
|
+
const descriptor = buildDaemonPlatformDescriptor({
|
|
11374
|
+
...options.platform !== void 0 ? { platform: options.platform } : {},
|
|
11375
|
+
...options.serviceAvailable !== void 0 ? { serviceAvailable: options.serviceAvailable } : {},
|
|
11376
|
+
paths,
|
|
11377
|
+
command
|
|
11378
|
+
});
|
|
11379
|
+
const current = await daemonStatus(options);
|
|
11380
|
+
return {
|
|
11381
|
+
enabled,
|
|
11382
|
+
descriptor,
|
|
11383
|
+
status: redactDaemonStatus({
|
|
11384
|
+
...writeDaemonState(paths, {
|
|
11385
|
+
running: current.running,
|
|
11386
|
+
...current.running && current.pid !== void 0 ? { pid: current.pid } : {},
|
|
11387
|
+
...current.running && current.startedAt ? { startedAt: current.startedAt } : {},
|
|
11388
|
+
enabled
|
|
11389
|
+
}),
|
|
11390
|
+
paths,
|
|
11391
|
+
...configProperty(config)
|
|
11392
|
+
})
|
|
11393
|
+
};
|
|
11394
|
+
}
|
|
11395
|
+
function configProperty(config) {
|
|
11396
|
+
return config ? { config } : {};
|
|
11397
|
+
}
|
|
11398
|
+
function readPid(path) {
|
|
9682
11399
|
try {
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
await close();
|
|
11400
|
+
const value = Number(readFileSync(path, "utf8").trim());
|
|
11401
|
+
return Number.isInteger(value) && value > 0 ? value : void 0;
|
|
11402
|
+
} catch {
|
|
11403
|
+
return;
|
|
9688
11404
|
}
|
|
9689
11405
|
}
|
|
9690
11406
|
//#endregion
|
|
@@ -9730,6 +11446,61 @@ async function runCli(args, io = {}) {
|
|
|
9730
11446
|
function normalizeCompletionWords(words) {
|
|
9731
11447
|
return words.map((word) => word === "__CAPLETS_TRAILING_SPACE__" ? "" : word);
|
|
9732
11448
|
}
|
|
11449
|
+
function addServeDaemonCommand(parent, name, description, action) {
|
|
11450
|
+
parent.command(name).description(description).option("--transport <transport>", "server transport: http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--json", "print JSON output").action(function(options) {
|
|
11451
|
+
return action({
|
|
11452
|
+
...this.parent?.opts(),
|
|
11453
|
+
...options
|
|
11454
|
+
});
|
|
11455
|
+
});
|
|
11456
|
+
}
|
|
11457
|
+
function cloudAuthStore(env) {
|
|
11458
|
+
return new CloudAuthStore({ env });
|
|
11459
|
+
}
|
|
11460
|
+
function cloudAuthStatus(credentials) {
|
|
11461
|
+
return redactedCloudAuthStatus(credentials);
|
|
11462
|
+
}
|
|
11463
|
+
function isProjectBindingWebSocketUnavailable(error) {
|
|
11464
|
+
return error instanceof CapletsError && error.code === "SERVER_UNAVAILABLE" && error.message.includes("Project Binding WebSocket unavailable");
|
|
11465
|
+
}
|
|
11466
|
+
function isProjectBindingCliError(error) {
|
|
11467
|
+
return error instanceof ProjectBindingError;
|
|
11468
|
+
}
|
|
11469
|
+
function serveRawOptions(options) {
|
|
11470
|
+
return {
|
|
11471
|
+
...options.transport !== void 0 ? { transport: options.transport } : {},
|
|
11472
|
+
...options.host !== void 0 ? { host: options.host } : {},
|
|
11473
|
+
...options.port !== void 0 ? { port: options.port } : {},
|
|
11474
|
+
...options.path !== void 0 ? { path: options.path } : {},
|
|
11475
|
+
...options.user !== void 0 ? { user: options.user } : {},
|
|
11476
|
+
...options.password !== void 0 ? { password: options.password } : {},
|
|
11477
|
+
...options.allowUnauthenticatedHttp !== void 0 ? { allowUnauthenticatedHttp: options.allowUnauthenticatedHttp } : {},
|
|
11478
|
+
...options.trustProxy !== void 0 ? { trustProxy: options.trustProxy } : {}
|
|
11479
|
+
};
|
|
11480
|
+
}
|
|
11481
|
+
async function waitForCloudLogin(client, loginId, env) {
|
|
11482
|
+
const timeoutMs = numberEnv(env.CAPLETS_CLOUD_AUTH_TIMEOUT_MS, 12e4);
|
|
11483
|
+
const intervalMs = numberEnv(env.CAPLETS_CLOUD_AUTH_POLL_INTERVAL_MS, 1500);
|
|
11484
|
+
const started = Date.now();
|
|
11485
|
+
while (Date.now() - started <= timeoutMs) {
|
|
11486
|
+
const result = await client.pollLogin(loginId);
|
|
11487
|
+
if (result.status !== "pending" && result.status !== "workspace_selection_required") return result;
|
|
11488
|
+
await sleep(intervalMs);
|
|
11489
|
+
}
|
|
11490
|
+
return {
|
|
11491
|
+
status: "expired",
|
|
11492
|
+
message: "Cloud Auth login timed out."
|
|
11493
|
+
};
|
|
11494
|
+
}
|
|
11495
|
+
function numberEnv(value, fallback) {
|
|
11496
|
+
if (value === void 0) return fallback;
|
|
11497
|
+
const parsed = Number(value);
|
|
11498
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
11499
|
+
}
|
|
11500
|
+
async function sleep(ms) {
|
|
11501
|
+
if (ms <= 0) return;
|
|
11502
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
11503
|
+
}
|
|
9733
11504
|
function createProgram(io = {}) {
|
|
9734
11505
|
const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
|
|
9735
11506
|
const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
|
|
@@ -9744,11 +11515,11 @@ function createProgram(io = {}) {
|
|
|
9744
11515
|
writeErr,
|
|
9745
11516
|
outputError: (value, write) => write(value)
|
|
9746
11517
|
});
|
|
9747
|
-
program.command(cliCommands.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
|
|
11518
|
+
program.command(cliCommands$1.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
|
|
9748
11519
|
if (!completionShells.includes(shell)) throw new CapletsError("REQUEST_INVALID", "completion shell must be bash, zsh, fish, powershell, or cmd");
|
|
9749
11520
|
writeOut(completionScript(shell));
|
|
9750
11521
|
});
|
|
9751
|
-
program.command(cliCommands.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
|
|
11522
|
+
program.command(cliCommands$1.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
|
|
9752
11523
|
const shell = completionShells.includes(options.shell) ? options.shell : "bash";
|
|
9753
11524
|
const remote = remoteClientForCli(io);
|
|
9754
11525
|
const configPath = currentConfigPath();
|
|
@@ -9781,7 +11552,7 @@ function createProgram(io = {}) {
|
|
|
9781
11552
|
}
|
|
9782
11553
|
if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
|
|
9783
11554
|
});
|
|
9784
|
-
program.command(cliCommands.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
|
|
11555
|
+
const serve = program.command(cliCommands$1.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
|
|
9785
11556
|
const resolved = resolveServeOptions(options);
|
|
9786
11557
|
const configPath = currentConfigPath();
|
|
9787
11558
|
await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
|
|
@@ -9789,7 +11560,233 @@ function createProgram(io = {}) {
|
|
|
9789
11560
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
9790
11561
|
}, writeErr)))(resolved);
|
|
9791
11562
|
});
|
|
9792
|
-
|
|
11563
|
+
const daemonOptions = () => ({
|
|
11564
|
+
env,
|
|
11565
|
+
...io.daemon
|
|
11566
|
+
});
|
|
11567
|
+
addServeDaemonCommand(serve, "start", "Start the default Caplets HTTP daemon.", async (options) => {
|
|
11568
|
+
const result = await startDaemon(serveRawOptions(options), daemonOptions());
|
|
11569
|
+
const serveConfig = result.status.config?.serve;
|
|
11570
|
+
writeOut(`Started Caplets HTTP daemon on ${serveConfig?.host ?? "127.0.0.1"}:${serveConfig?.port ?? 5387}.\n`);
|
|
11571
|
+
if (options.json) writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
|
|
11572
|
+
});
|
|
11573
|
+
addServeDaemonCommand(serve, "stop", "Stop the default Caplets HTTP daemon.", async (options) => {
|
|
11574
|
+
const result = await stopDaemon(daemonOptions());
|
|
11575
|
+
if (options.json) {
|
|
11576
|
+
writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
|
|
11577
|
+
return;
|
|
11578
|
+
}
|
|
11579
|
+
writeOut("Stopped Caplets HTTP daemon.\n");
|
|
11580
|
+
});
|
|
11581
|
+
addServeDaemonCommand(serve, "status", "Show the default Caplets HTTP daemon status.", async (options) => {
|
|
11582
|
+
const status = await daemonStatus(daemonOptions());
|
|
11583
|
+
if (options.json) {
|
|
11584
|
+
writeOut(`${JSON.stringify(status, null, 2)}\n`);
|
|
11585
|
+
return;
|
|
11586
|
+
}
|
|
11587
|
+
writeOut(status.running ? `Caplets HTTP daemon is running${status.pid ? ` (pid ${status.pid})` : ""}.\n` : "Caplets HTTP daemon is stopped.\n");
|
|
11588
|
+
});
|
|
11589
|
+
addServeDaemonCommand(serve, "restart", "Restart the default Caplets HTTP daemon.", async (options) => {
|
|
11590
|
+
const result = await restartDaemon(serveRawOptions(options), daemonOptions());
|
|
11591
|
+
if (options.json) {
|
|
11592
|
+
writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
|
|
11593
|
+
return;
|
|
11594
|
+
}
|
|
11595
|
+
const serveConfig = result.status.config?.serve;
|
|
11596
|
+
writeOut(`Restarted Caplets HTTP daemon on ${serveConfig?.host ?? "127.0.0.1"}:${serveConfig?.port ?? 5387}.\n`);
|
|
11597
|
+
});
|
|
11598
|
+
addServeDaemonCommand(serve, "enable", "Enable the default Caplets HTTP daemon at login.", async (options) => {
|
|
11599
|
+
const result = await enableDaemon(daemonOptions());
|
|
11600
|
+
if (options.json) {
|
|
11601
|
+
writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
11602
|
+
return;
|
|
11603
|
+
}
|
|
11604
|
+
writeOut(`Enabled Caplets HTTP daemon at login (${result.descriptor.kind}).\n`);
|
|
11605
|
+
});
|
|
11606
|
+
addServeDaemonCommand(serve, "disable", "Disable the default Caplets HTTP daemon at login.", async (options) => {
|
|
11607
|
+
const result = await disableDaemon(daemonOptions());
|
|
11608
|
+
if (options.json) {
|
|
11609
|
+
writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
11610
|
+
return;
|
|
11611
|
+
}
|
|
11612
|
+
writeOut(`Disabled Caplets HTTP daemon at login (${result.descriptor.kind}).\n`);
|
|
11613
|
+
});
|
|
11614
|
+
program.command(cliCommands$1.attach).description("Start a remote-backed Caplets MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-url <url>", "remote Caplets service base URL").option("--user <user>", "remote Basic Auth username").option("--password <password>", "remote Basic Auth password").option("--token <token>", "remote bearer token").option("--workspace <workspace>", "hosted Cloud workspace ID or slug").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--json", "print JSON status events").option("--verbose", "print detailed attach diagnostics").option("--once", "validate Project Binding once and exit").option("--project-root <path>", "test-only project root override").action(async (options) => {
|
|
11615
|
+
try {
|
|
11616
|
+
const attachOptions = {
|
|
11617
|
+
...options,
|
|
11618
|
+
...io.fetch ? { fetch: io.fetch } : {}
|
|
11619
|
+
};
|
|
11620
|
+
if (!options.once) {
|
|
11621
|
+
const resolved = await resolveAttachServeOptions(attachOptions, env);
|
|
11622
|
+
await (io.attachServe ?? ((serveOptions) => attachResolvedCaplets(serveOptions, { writeErr })))(resolved);
|
|
11623
|
+
return;
|
|
11624
|
+
}
|
|
11625
|
+
const result = await attachProjectOnce(attachOptions, env);
|
|
11626
|
+
if (options.json) {
|
|
11627
|
+
writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
11628
|
+
return;
|
|
11629
|
+
}
|
|
11630
|
+
writeOut(`Project Binding available at ${result.webSocketUrl}.\n`);
|
|
11631
|
+
} catch (error) {
|
|
11632
|
+
if (options.json && isProjectBindingWebSocketUnavailable(error)) {
|
|
11633
|
+
writeOut(`${JSON.stringify({
|
|
11634
|
+
ok: false,
|
|
11635
|
+
error: {
|
|
11636
|
+
code: "PROJECT_BINDING_WEBSOCKET_UNAVAILABLE",
|
|
11637
|
+
message: error instanceof Error ? error.message : String(error)
|
|
11638
|
+
}
|
|
11639
|
+
}, null, 2)}\n`);
|
|
11640
|
+
setExitCode(1);
|
|
11641
|
+
return;
|
|
11642
|
+
}
|
|
11643
|
+
if (options.json && isProjectBindingCliError(error)) {
|
|
11644
|
+
writeOut(`${JSON.stringify({
|
|
11645
|
+
ok: false,
|
|
11646
|
+
error: {
|
|
11647
|
+
code: error.projectBindingCode,
|
|
11648
|
+
message: error.message,
|
|
11649
|
+
recoveryCommand: error.recoveryCommand,
|
|
11650
|
+
requestId: error.requestId
|
|
11651
|
+
}
|
|
11652
|
+
}, null, 2)}\n`);
|
|
11653
|
+
setExitCode(1);
|
|
11654
|
+
return;
|
|
11655
|
+
}
|
|
11656
|
+
if (options.json && error instanceof CapletsError) {
|
|
11657
|
+
writeOut(`${JSON.stringify({
|
|
11658
|
+
ok: false,
|
|
11659
|
+
error: {
|
|
11660
|
+
code: error.code,
|
|
11661
|
+
message: error.message
|
|
11662
|
+
}
|
|
11663
|
+
}, null, 2)}\n`);
|
|
11664
|
+
setExitCode(1);
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
throw error;
|
|
11668
|
+
}
|
|
11669
|
+
});
|
|
11670
|
+
const cloudAuth = program.command(cliCommands$1.cloud).description("Manage hosted Caplets Cloud.").command("auth").description("Authenticate this Caplets client to hosted Caplets Cloud.");
|
|
11671
|
+
cloudAuth.command("login").description("Log in to hosted Caplets Cloud.").option("--cloud-url <url>", "hosted Caplets Cloud URL").option("--workspace <workspace>", "workspace ID or slug to select").option("--device-name <name>", "device label for this Cloud Auth credential").option("--no-open", "print the login URL without opening a browser").option("--json", "print JSON output").action(async (options) => {
|
|
11672
|
+
const client = new CloudAuthClient({
|
|
11673
|
+
cloudUrl: options.cloudUrl ?? env.CAPLETS_CLOUD_URL ?? "https://cloud.caplets.dev",
|
|
11674
|
+
...io.fetch ? { fetch: io.fetch } : {}
|
|
11675
|
+
});
|
|
11676
|
+
const started = await client.startLogin({
|
|
11677
|
+
requestedWorkspace: options.workspace,
|
|
11678
|
+
deviceName: options.deviceName ?? env.CAPLETS_DEVICE_NAME ?? "Caplets CLI"
|
|
11679
|
+
});
|
|
11680
|
+
if (options.open !== false) await openBrowserUrl(started.loginUrl);
|
|
11681
|
+
if (!options.json) {
|
|
11682
|
+
writeOut(`Open ${started.loginUrl}\n`);
|
|
11683
|
+
writeOut(`Enter code ${started.userCode} if prompted.\n`);
|
|
11684
|
+
}
|
|
11685
|
+
const completed = await waitForCloudLogin(client, started.loginId, env);
|
|
11686
|
+
if (completed.status !== "completed") throw new CapletsError("AUTH_FAILED", `Cloud Auth login ${completed.status}.`);
|
|
11687
|
+
const exchanged = await client.exchangeToken({
|
|
11688
|
+
loginId: started.loginId,
|
|
11689
|
+
oneTimeCode: completed.oneTimeCode
|
|
11690
|
+
});
|
|
11691
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11692
|
+
const credentials = {
|
|
11693
|
+
version: 2,
|
|
11694
|
+
cloudUrl: exchanged.cloudUrl,
|
|
11695
|
+
workspaceId: exchanged.workspaceId,
|
|
11696
|
+
...exchanged.workspaceSlug ? { workspaceSlug: exchanged.workspaceSlug } : {},
|
|
11697
|
+
accessToken: exchanged.accessToken,
|
|
11698
|
+
refreshToken: exchanged.refreshToken ?? "",
|
|
11699
|
+
expiresAt: exchanged.expiresAt,
|
|
11700
|
+
scope: exchanged.scope,
|
|
11701
|
+
tokenType: exchanged.tokenType,
|
|
11702
|
+
credentialFamilyId: exchanged.credentialFamilyId,
|
|
11703
|
+
deviceName: exchanged.deviceName ?? options.deviceName ?? "Caplets CLI",
|
|
11704
|
+
createdAt: now,
|
|
11705
|
+
lastRefreshAt: now
|
|
11706
|
+
};
|
|
11707
|
+
await cloudAuthStore(env).save(credentials);
|
|
11708
|
+
const status = cloudAuthStatus(credentials);
|
|
11709
|
+
if (options.json) {
|
|
11710
|
+
writeOut(`${JSON.stringify(status, null, 2)}\n`);
|
|
11711
|
+
return;
|
|
11712
|
+
}
|
|
11713
|
+
writeOut(`Authenticated to ${credentials.cloudUrl} as workspace ${credentials.workspaceSlug ?? credentials.workspaceId}.\n`);
|
|
11714
|
+
});
|
|
11715
|
+
cloudAuth.command("status").description("Show hosted Caplets Cloud authentication status.").option("--json", "print JSON output").action(async (options) => {
|
|
11716
|
+
const credentials = await cloudAuthStore(env).load();
|
|
11717
|
+
if (options.json) {
|
|
11718
|
+
writeOut(`${JSON.stringify(cloudAuthStatus(credentials), null, 2)}\n`);
|
|
11719
|
+
return;
|
|
11720
|
+
}
|
|
11721
|
+
writeOut(credentials ? `Authenticated to ${credentials.cloudUrl} as workspace ${credentials.workspaceSlug ?? credentials.workspaceId}.\n` : "Not authenticated to hosted Caplets Cloud.\n");
|
|
11722
|
+
});
|
|
11723
|
+
cloudAuth.command("logout").description("Log out of hosted Caplets Cloud.").action(async () => {
|
|
11724
|
+
const store = cloudAuthStore(env);
|
|
11725
|
+
const credentials = await store.load();
|
|
11726
|
+
if (credentials?.refreshToken) await new CloudAuthClient({
|
|
11727
|
+
cloudUrl: credentials.cloudUrl,
|
|
11728
|
+
...io.fetch ? { fetch: io.fetch } : {}
|
|
11729
|
+
}).logout(credentials.refreshToken).catch(() => void 0);
|
|
11730
|
+
await store.clear();
|
|
11731
|
+
writeOut("Logged out of hosted Caplets Cloud.\n");
|
|
11732
|
+
});
|
|
11733
|
+
cloudAuth.command("workspaces").description("List hosted Caplets Cloud workspaces.").option("--json", "print JSON output").action(async (options) => {
|
|
11734
|
+
const credentials = await cloudAuthStore(env).load();
|
|
11735
|
+
const workspaces = credentials?.accessToken ? (await new CloudAuthClient({
|
|
11736
|
+
cloudUrl: credentials.cloudUrl,
|
|
11737
|
+
...io.fetch ? { fetch: io.fetch } : {}
|
|
11738
|
+
}).workspaces(credentials.accessToken).catch(() => ({ workspaces: [{
|
|
11739
|
+
workspaceId: credentials.workspaceId,
|
|
11740
|
+
...credentials.workspaceSlug ? { slug: credentials.workspaceSlug } : {}
|
|
11741
|
+
}] }))).workspaces.map((workspace) => ({
|
|
11742
|
+
...workspace,
|
|
11743
|
+
selected: workspace.workspaceId === credentials.workspaceId || workspace.slug === credentials.workspaceSlug
|
|
11744
|
+
})) : [];
|
|
11745
|
+
if (options.json) {
|
|
11746
|
+
writeOut(`${JSON.stringify({ workspaces }, null, 2)}\n`);
|
|
11747
|
+
return;
|
|
11748
|
+
}
|
|
11749
|
+
if (workspaces.length === 0) {
|
|
11750
|
+
writeOut("No hosted Caplets Cloud workspaces available. Run caplets cloud auth login.\n");
|
|
11751
|
+
return;
|
|
11752
|
+
}
|
|
11753
|
+
for (const workspace of workspaces) writeOut(`${workspace.selected ? "* " : " "}${workspace.slug ?? workspace.workspaceId}\n`);
|
|
11754
|
+
});
|
|
11755
|
+
cloudAuth.command("switch").description("Switch the hosted Caplets Cloud Selected Workspace.").argument("<workspace>", "workspace ID or slug").option("--json", "print JSON output").action(async (workspace, options) => {
|
|
11756
|
+
const store = cloudAuthStore(env);
|
|
11757
|
+
const credentials = await store.load();
|
|
11758
|
+
if (!credentials) throw new CapletsError("AUTH_REQUIRED", "Run caplets cloud auth login first.");
|
|
11759
|
+
const switched = await new CloudAuthClient({
|
|
11760
|
+
cloudUrl: credentials.cloudUrl,
|
|
11761
|
+
...io.fetch ? { fetch: io.fetch } : {}
|
|
11762
|
+
}).switchWorkspace({
|
|
11763
|
+
accessToken: credentials.accessToken,
|
|
11764
|
+
refreshToken: credentials.refreshToken,
|
|
11765
|
+
workspace,
|
|
11766
|
+
deviceName: credentials.deviceName
|
|
11767
|
+
});
|
|
11768
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11769
|
+
const next = {
|
|
11770
|
+
...credentials,
|
|
11771
|
+
workspaceId: switched.workspaceId,
|
|
11772
|
+
workspaceSlug: switched.workspaceSlug,
|
|
11773
|
+
accessToken: switched.accessToken,
|
|
11774
|
+
refreshToken: switched.refreshToken ?? credentials.refreshToken,
|
|
11775
|
+
expiresAt: switched.expiresAt,
|
|
11776
|
+
scope: switched.scope,
|
|
11777
|
+
tokenType: switched.tokenType,
|
|
11778
|
+
credentialFamilyId: switched.credentialFamilyId,
|
|
11779
|
+
lastRefreshAt: now,
|
|
11780
|
+
selectedWorkspaceSwitchedAt: now
|
|
11781
|
+
};
|
|
11782
|
+
await store.save(next);
|
|
11783
|
+
if (options.json) {
|
|
11784
|
+
writeOut(`${JSON.stringify(cloudAuthStatus(next), null, 2)}\n`);
|
|
11785
|
+
return;
|
|
11786
|
+
}
|
|
11787
|
+
writeOut(`Selected workspace ${next.workspaceSlug ?? next.workspaceId}.\n`);
|
|
11788
|
+
});
|
|
11789
|
+
program.command(cliCommands$1.init).description("Create a starter Caplets config file.").option("--project", "create the project Caplets config").option("-g, --global", "create the user Caplets config").option("--remote", "create the remote Caplets config").option("--force", "overwrite an existing config file").action(async (options) => {
|
|
9793
11790
|
const target = parseMutationTarget(options);
|
|
9794
11791
|
if (target === "remote") {
|
|
9795
11792
|
writeOut(`Created remote Caplets config at ${(await requireRemoteClientForTarget(io).request("init", { force: Boolean(options.force) })).path}\n`);
|
|
@@ -9801,7 +11798,7 @@ function createProgram(io = {}) {
|
|
|
9801
11798
|
});
|
|
9802
11799
|
writeOut(`Created ${localMutationTargetLabel(target, io)}Caplets config at ${path}\n`);
|
|
9803
11800
|
});
|
|
9804
|
-
program.command(cliCommands.setup).description("Install or configure an agent integration for Caplets.").argument("[integration]", "integration: codex, claude-code, opencode, pi, or mcp-client").option("--remote", "configure for a remote Caplets server").option("--server-url <url>", "remote Caplets service base URL").option("--output <path>", "config path to write for generic MCP setup").option("--dry-run", "print actions without running commands or writing files").option("--format <format>", "output format: plain or json", parseSetupFormat).action(async (integration, options) => {
|
|
11801
|
+
program.command(cliCommands$1.setup).description("Install or configure an agent integration for Caplets.").argument("[integration]", "integration: codex, claude-code, opencode, pi, or mcp-client").option("--remote", "configure for a remote Caplets server").option("--server-url <url>", "remote Caplets service base URL").option("--output <path>", "config path to write for generic MCP setup").option("--dry-run", "print actions without running commands or writing files").option("--yes", "approve Caplet setup commands for the exact current content hash").option("--target <target>", "Caplet setup target: local, remote, or cloud", parseSetupTarget).option("--format <format>", "output format: plain or json", parseSetupFormat).action(async (integration, options) => {
|
|
9805
11802
|
if (!integration) {
|
|
9806
11803
|
writeOut(formatSetupMenu());
|
|
9807
11804
|
return;
|
|
@@ -9813,7 +11810,14 @@ function createProgram(io = {}) {
|
|
|
9813
11810
|
if (io.runSetupCommand) setupOptions.runCommand = io.runSetupCommand;
|
|
9814
11811
|
writeOut(await runSetup(integration, setupOptions));
|
|
9815
11812
|
});
|
|
9816
|
-
program.command(cliCommands.
|
|
11813
|
+
program.command(cliCommands$1.doctor).description("Diagnose Caplets local, remote, and project-sync configuration.").option("--json", "print JSON output").action(async (options) => {
|
|
11814
|
+
if (options.json) {
|
|
11815
|
+
writeOut(`${JSON.stringify(await doctorJsonReport({ env }), null, 2)}\n`);
|
|
11816
|
+
return;
|
|
11817
|
+
}
|
|
11818
|
+
writeOut(await formatDoctorReport({ env }));
|
|
11819
|
+
});
|
|
11820
|
+
program.command(cliCommands$1.list).description("List configured Caplets.").option("--all", "include disabled Caplets").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
|
|
9817
11821
|
const includeDisabled = Boolean(options.all);
|
|
9818
11822
|
const remote = remoteClientForCli(io);
|
|
9819
11823
|
if (remote) {
|
|
@@ -9835,7 +11839,7 @@ function createProgram(io = {}) {
|
|
|
9835
11839
|
}
|
|
9836
11840
|
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
9837
11841
|
});
|
|
9838
|
-
program.command(cliCommands.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
|
|
11842
|
+
program.command(cliCommands$1.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
|
|
9839
11843
|
const target = parseMutationTarget(options);
|
|
9840
11844
|
if (target === "remote") {
|
|
9841
11845
|
const result = await requireRemoteClientForTarget(io).request("install", {
|
|
@@ -9853,7 +11857,7 @@ function createProgram(io = {}) {
|
|
|
9853
11857
|
});
|
|
9854
11858
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${localMutationTargetLabel(target, io)}${caplet.destination}\n`);
|
|
9855
11859
|
});
|
|
9856
|
-
const add = program.command(cliCommands.add).description("Add generated Caplet files.");
|
|
11860
|
+
const add = program.command(cliCommands$1.add).description("Add generated Caplet files.");
|
|
9857
11861
|
add.command("cli").description("Add a CLI tools Caplet.").argument("<id>", "Caplet ID/display seed").option("--repo <path>", "repository path to inspect").option("--include <items>", "comma-separated generators to include: git,gh,package").option("--command <name>", "single CLI command template to generate").option("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9858
11862
|
const target = parseMutationTarget(options);
|
|
9859
11863
|
if (target === "remote") {
|
|
@@ -9938,7 +11942,7 @@ function createProgram(io = {}) {
|
|
|
9938
11942
|
});
|
|
9939
11943
|
writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}HTTP`, result);
|
|
9940
11944
|
});
|
|
9941
|
-
program.command(cliCommands.inspect).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
11945
|
+
program.command(cliCommands$1.inspect).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
9942
11946
|
await executeOperation(caplet, { operation: "inspect" }, {
|
|
9943
11947
|
writeOut,
|
|
9944
11948
|
writeErr,
|
|
@@ -9949,7 +11953,7 @@ function createProgram(io = {}) {
|
|
|
9949
11953
|
format: options.format
|
|
9950
11954
|
});
|
|
9951
11955
|
});
|
|
9952
|
-
program.command(cliCommands.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
11956
|
+
program.command(cliCommands$1.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
9953
11957
|
await executeOperation(caplet, { operation: "check_backend" }, {
|
|
9954
11958
|
writeOut,
|
|
9955
11959
|
writeErr,
|
|
@@ -9960,7 +11964,7 @@ function createProgram(io = {}) {
|
|
|
9960
11964
|
format: options.format
|
|
9961
11965
|
});
|
|
9962
11966
|
});
|
|
9963
|
-
program.command(cliCommands.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
11967
|
+
program.command(cliCommands$1.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
9964
11968
|
await executeOperation(caplet, { operation: "list_tools" }, {
|
|
9965
11969
|
writeOut,
|
|
9966
11970
|
writeErr,
|
|
@@ -9971,7 +11975,7 @@ function createProgram(io = {}) {
|
|
|
9971
11975
|
format: options.format
|
|
9972
11976
|
});
|
|
9973
11977
|
});
|
|
9974
|
-
program.command(cliCommands.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
|
|
11978
|
+
program.command(cliCommands$1.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
|
|
9975
11979
|
await executeOperation(caplet, options.limit === void 0 ? {
|
|
9976
11980
|
operation: "search_tools",
|
|
9977
11981
|
query
|
|
@@ -9989,7 +11993,7 @@ function createProgram(io = {}) {
|
|
|
9989
11993
|
format: options.format
|
|
9990
11994
|
});
|
|
9991
11995
|
});
|
|
9992
|
-
program.command(cliCommands.getTool).description("Print one downstream tool schema.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
|
|
11996
|
+
program.command(cliCommands$1.getTool).description("Print one downstream tool schema.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
|
|
9993
11997
|
const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
|
|
9994
11998
|
await executeOperation(caplet, {
|
|
9995
11999
|
operation: "get_tool",
|
|
@@ -10004,7 +12008,7 @@ function createProgram(io = {}) {
|
|
|
10004
12008
|
format: options.format
|
|
10005
12009
|
});
|
|
10006
12010
|
});
|
|
10007
|
-
program.command(cliCommands.callTool).description("Call one downstream tool.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
|
|
12011
|
+
program.command(cliCommands$1.callTool).description("Call one downstream tool.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
|
|
10008
12012
|
const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
|
|
10009
12013
|
await executeOperation(caplet, {
|
|
10010
12014
|
operation: "call_tool",
|
|
@@ -10021,7 +12025,7 @@ function createProgram(io = {}) {
|
|
|
10021
12025
|
format: options.format
|
|
10022
12026
|
});
|
|
10023
12027
|
});
|
|
10024
|
-
program.command(cliCommands.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
|
|
12028
|
+
program.command(cliCommands$1.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
|
|
10025
12029
|
operation: "list_resources",
|
|
10026
12030
|
limit: options.limit
|
|
10027
12031
|
}, {
|
|
@@ -10033,7 +12037,7 @@ function createProgram(io = {}) {
|
|
|
10033
12037
|
remote: remoteClientForCli(io),
|
|
10034
12038
|
format: options.format
|
|
10035
12039
|
}));
|
|
10036
|
-
program.command(cliCommands.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
12040
|
+
program.command(cliCommands$1.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
10037
12041
|
operation: "search_resources",
|
|
10038
12042
|
query
|
|
10039
12043
|
} : {
|
|
@@ -10049,7 +12053,7 @@ function createProgram(io = {}) {
|
|
|
10049
12053
|
remote: remoteClientForCli(io),
|
|
10050
12054
|
format: options.format
|
|
10051
12055
|
}));
|
|
10052
|
-
program.command(cliCommands.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
|
|
12056
|
+
program.command(cliCommands$1.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
|
|
10053
12057
|
operation: "list_resource_templates",
|
|
10054
12058
|
limit: options.limit
|
|
10055
12059
|
}, {
|
|
@@ -10061,7 +12065,7 @@ function createProgram(io = {}) {
|
|
|
10061
12065
|
remote: remoteClientForCli(io),
|
|
10062
12066
|
format: options.format
|
|
10063
12067
|
}));
|
|
10064
|
-
program.command(cliCommands.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
|
|
12068
|
+
program.command(cliCommands$1.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
|
|
10065
12069
|
operation: "read_resource",
|
|
10066
12070
|
uri
|
|
10067
12071
|
}, {
|
|
@@ -10073,7 +12077,7 @@ function createProgram(io = {}) {
|
|
|
10073
12077
|
remote: remoteClientForCli(io),
|
|
10074
12078
|
format: options.format
|
|
10075
12079
|
}));
|
|
10076
|
-
program.command(cliCommands.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
|
|
12080
|
+
program.command(cliCommands$1.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
|
|
10077
12081
|
operation: "list_prompts",
|
|
10078
12082
|
limit: options.limit
|
|
10079
12083
|
}, {
|
|
@@ -10085,7 +12089,7 @@ function createProgram(io = {}) {
|
|
|
10085
12089
|
remote: remoteClientForCli(io),
|
|
10086
12090
|
format: options.format
|
|
10087
12091
|
}));
|
|
10088
|
-
program.command(cliCommands.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
12092
|
+
program.command(cliCommands$1.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
10089
12093
|
operation: "search_prompts",
|
|
10090
12094
|
query
|
|
10091
12095
|
} : {
|
|
@@ -10101,7 +12105,7 @@ function createProgram(io = {}) {
|
|
|
10101
12105
|
remote: remoteClientForCli(io),
|
|
10102
12106
|
format: options.format
|
|
10103
12107
|
}));
|
|
10104
|
-
program.command(cliCommands.getPrompt).description("Get one MCP prompt by name.").argument("<caplet-or-target>", "MCP Caplet ID or qualified <caplet.prompt> target").argument("[prompt]", "prompt name when caplet is provided separately").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, promptArgument, options) => {
|
|
12108
|
+
program.command(cliCommands$1.getPrompt).description("Get one MCP prompt by name.").argument("<caplet-or-target>", "MCP Caplet ID or qualified <caplet.prompt> target").argument("[prompt]", "prompt name when caplet is provided separately").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, promptArgument, options) => {
|
|
10105
12109
|
const { caplet, tool: prompt } = parseQualifiedTarget(capletOrTarget, promptArgument);
|
|
10106
12110
|
await executeOperation(caplet, {
|
|
10107
12111
|
operation: "get_prompt",
|
|
@@ -10117,7 +12121,7 @@ function createProgram(io = {}) {
|
|
|
10117
12121
|
format: options.format
|
|
10118
12122
|
});
|
|
10119
12123
|
});
|
|
10120
|
-
program.command(cliCommands.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
|
|
12124
|
+
program.command(cliCommands$1.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
|
|
10121
12125
|
operation: "complete",
|
|
10122
12126
|
ref: completionRefFromOptions(options),
|
|
10123
12127
|
argument: {
|
|
@@ -10133,7 +12137,7 @@ function createProgram(io = {}) {
|
|
|
10133
12137
|
remote: remoteClientForCli(io),
|
|
10134
12138
|
format: options.format
|
|
10135
12139
|
}));
|
|
10136
|
-
const config = program.command(cliCommands.config).description("Inspect Caplets config locations.");
|
|
12140
|
+
const config = program.command(cliCommands$1.config).description("Inspect Caplets config locations.");
|
|
10137
12141
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
10138
12142
|
writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
|
|
10139
12143
|
});
|
|
@@ -10145,7 +12149,7 @@ function createProgram(io = {}) {
|
|
|
10145
12149
|
}
|
|
10146
12150
|
writeOut(formatConfigPaths(paths, options.format ?? "plain"));
|
|
10147
12151
|
});
|
|
10148
|
-
const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
|
|
12152
|
+
const auth = program.command(cliCommands$1.auth).description("Manage OAuth credentials for remote servers.");
|
|
10149
12153
|
auth.command("login").description("Authenticate a configured remote OAuth server.").argument("<server>", "configured server ID").option("--project", "authenticate using the project Caplets config").option("-g, --global", "authenticate using the user Caplets config").option("--remote", "authenticate using the remote server auth store").option("--no-open", "print the authorization URL without opening a browser").action(async (serverId, options) => {
|
|
10150
12154
|
const target = await resolveAuthTarget(serverId, options, io);
|
|
10151
12155
|
if (target === "remote") {
|
|
@@ -10348,6 +12352,10 @@ function parseSetupFormat(value) {
|
|
|
10348
12352
|
if (value === "plain" || value === "json") return value;
|
|
10349
12353
|
throw new CapletsError("REQUEST_INVALID", "setup format must be plain or json");
|
|
10350
12354
|
}
|
|
12355
|
+
function parseSetupTarget(value) {
|
|
12356
|
+
if (value === "local" || value === "remote" || value === "cloud") return value;
|
|
12357
|
+
throw new CapletsError("REQUEST_INVALID", "setup target must be local, remote, or cloud");
|
|
12358
|
+
}
|
|
10351
12359
|
function parseQualifiedTarget(capletOrTarget, toolArgument) {
|
|
10352
12360
|
if (toolArgument !== void 0) {
|
|
10353
12361
|
if (capletOrTarget.length === 0 || toolArgument.length === 0) throw new CapletsError("REQUEST_INVALID", "Expected target in the form <caplet> <tool> or <caplet>.<tool>");
|
|
@@ -10385,22 +12393,22 @@ function localShadowedCompletionTarget(words, config) {
|
|
|
10385
12393
|
const target = words[1];
|
|
10386
12394
|
if (!command || !target || target.startsWith("-")) return;
|
|
10387
12395
|
const qualifiedCommands = new Set([
|
|
10388
|
-
cliCommands.getTool,
|
|
10389
|
-
cliCommands.callTool,
|
|
10390
|
-
cliCommands.getPrompt
|
|
12396
|
+
cliCommands$1.getTool,
|
|
12397
|
+
cliCommands$1.callTool,
|
|
12398
|
+
cliCommands$1.getPrompt
|
|
10391
12399
|
]);
|
|
10392
12400
|
const capletCommands = new Set([
|
|
10393
|
-
cliCommands.inspect,
|
|
10394
|
-
cliCommands.checkBackend,
|
|
10395
|
-
cliCommands.listTools,
|
|
10396
|
-
cliCommands.searchTools,
|
|
10397
|
-
cliCommands.listResources,
|
|
10398
|
-
cliCommands.searchResources,
|
|
10399
|
-
cliCommands.listResourceTemplates,
|
|
10400
|
-
cliCommands.readResource,
|
|
10401
|
-
cliCommands.listPrompts,
|
|
10402
|
-
cliCommands.searchPrompts,
|
|
10403
|
-
cliCommands.complete
|
|
12401
|
+
cliCommands$1.inspect,
|
|
12402
|
+
cliCommands$1.checkBackend,
|
|
12403
|
+
cliCommands$1.listTools,
|
|
12404
|
+
cliCommands$1.searchTools,
|
|
12405
|
+
cliCommands$1.listResources,
|
|
12406
|
+
cliCommands$1.searchResources,
|
|
12407
|
+
cliCommands$1.listResourceTemplates,
|
|
12408
|
+
cliCommands$1.readResource,
|
|
12409
|
+
cliCommands$1.listPrompts,
|
|
12410
|
+
cliCommands$1.searchPrompts,
|
|
12411
|
+
cliCommands$1.complete
|
|
10404
12412
|
]);
|
|
10405
12413
|
const caplet = qualifiedCommands.has(command) ? target.slice(0, target.includes(".") ? target.indexOf(".") : target.length) : capletCommands.has(command) ? target : void 0;
|
|
10406
12414
|
return caplet && hasEnabledCaplet(config, caplet) ? caplet : void 0;
|
|
@@ -10940,4 +12948,729 @@ function writeAddResult(writeOut, label, result) {
|
|
|
10940
12948
|
writeOut(result.text);
|
|
10941
12949
|
}
|
|
10942
12950
|
//#endregion
|
|
10943
|
-
|
|
12951
|
+
//#region src/caplet-source/bundle.ts
|
|
12952
|
+
var BundleCapletSource = class {
|
|
12953
|
+
files;
|
|
12954
|
+
constructor(files) {
|
|
12955
|
+
this.files = /* @__PURE__ */ new Map();
|
|
12956
|
+
for (const file of files) {
|
|
12957
|
+
const path = normalizeCapletSourcePath(file.path);
|
|
12958
|
+
if (!path) throw new CapletsError("CONFIG_INVALID", `Invalid bundle file path ${file.path}`);
|
|
12959
|
+
if (this.files.has(path)) throw new CapletsError("CONFIG_INVALID", `Duplicate bundle file path ${path}`);
|
|
12960
|
+
this.files.set(path, {
|
|
12961
|
+
path,
|
|
12962
|
+
content: file.content
|
|
12963
|
+
});
|
|
12964
|
+
}
|
|
12965
|
+
}
|
|
12966
|
+
async listFiles() {
|
|
12967
|
+
return [...this.files.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
12968
|
+
}
|
|
12969
|
+
async readFile(path) {
|
|
12970
|
+
const normalized = normalizeCapletSourcePath(path);
|
|
12971
|
+
if (!normalized) return;
|
|
12972
|
+
return this.files.get(normalized);
|
|
12973
|
+
}
|
|
12974
|
+
};
|
|
12975
|
+
//#endregion
|
|
12976
|
+
//#region src/runtime-plan/features.ts
|
|
12977
|
+
function inferRuntimeFeatures(caplet) {
|
|
12978
|
+
const provenance = [];
|
|
12979
|
+
for (const feature of explicitFeatures(caplet)) provenance.push({
|
|
12980
|
+
feature,
|
|
12981
|
+
source: "explicit",
|
|
12982
|
+
matched: "runtime.features"
|
|
12983
|
+
});
|
|
12984
|
+
for (const command of commandRecords(caplet)) {
|
|
12985
|
+
const text = [command.command, ...command.args].join(" ");
|
|
12986
|
+
const dockerMatch = matchDocker(command.command, command.args);
|
|
12987
|
+
if (dockerMatch) provenance.push({
|
|
12988
|
+
feature: "docker",
|
|
12989
|
+
source: command.source,
|
|
12990
|
+
matched: dockerMatch,
|
|
12991
|
+
command: text
|
|
12992
|
+
});
|
|
12993
|
+
const browserMatch = matchBrowser(command.command, command.args);
|
|
12994
|
+
if (browserMatch) provenance.push({
|
|
12995
|
+
feature: "browser",
|
|
12996
|
+
source: command.source,
|
|
12997
|
+
matched: browserMatch,
|
|
12998
|
+
command: text
|
|
12999
|
+
});
|
|
13000
|
+
}
|
|
13001
|
+
return {
|
|
13002
|
+
features: orderedFeatures([...new Set(provenance.map((entry) => entry.feature))]),
|
|
13003
|
+
provenance
|
|
13004
|
+
};
|
|
13005
|
+
}
|
|
13006
|
+
function explicitFeatures(caplet) {
|
|
13007
|
+
const runtime = caplet.runtime;
|
|
13008
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return [];
|
|
13009
|
+
const features = runtime.features;
|
|
13010
|
+
return Array.isArray(features) ? features.filter((feature) => feature === "docker" || feature === "browser") : [];
|
|
13011
|
+
}
|
|
13012
|
+
function commandRecords(caplet) {
|
|
13013
|
+
return [
|
|
13014
|
+
...setupCommands(caplet.setup, "setup.commands"),
|
|
13015
|
+
...setupCommands(caplet.setup, "setup.verify", true),
|
|
13016
|
+
...mcpCommands(caplet),
|
|
13017
|
+
...cliCommands(caplet)
|
|
13018
|
+
];
|
|
13019
|
+
}
|
|
13020
|
+
function setupCommands(setup, source, verify = false) {
|
|
13021
|
+
if (!setup || typeof setup !== "object" || Array.isArray(setup)) return [];
|
|
13022
|
+
const values = setup[verify ? "verify" : "commands"];
|
|
13023
|
+
if (!Array.isArray(values)) return [];
|
|
13024
|
+
return values.flatMap((value) => commandRecordFrom(value, source));
|
|
13025
|
+
}
|
|
13026
|
+
function mcpCommands(caplet) {
|
|
13027
|
+
if (caplet.backend !== "mcp" || typeof caplet.command !== "string") return [];
|
|
13028
|
+
return [{
|
|
13029
|
+
source: "mcp.command",
|
|
13030
|
+
command: caplet.command,
|
|
13031
|
+
args: stringArray(caplet.args)
|
|
13032
|
+
}];
|
|
13033
|
+
}
|
|
13034
|
+
function cliCommands(caplet) {
|
|
13035
|
+
if (caplet.backend !== "cli") return [];
|
|
13036
|
+
const records = [];
|
|
13037
|
+
if (typeof caplet.command === "string") records.push({
|
|
13038
|
+
source: "cli.command",
|
|
13039
|
+
command: caplet.command,
|
|
13040
|
+
args: stringArray(caplet.args)
|
|
13041
|
+
});
|
|
13042
|
+
const actions = caplet.actions;
|
|
13043
|
+
if (actions && typeof actions === "object" && !Array.isArray(actions)) for (const action of Object.values(actions)) records.push(...commandRecordFrom(action, "cli.action"));
|
|
13044
|
+
return records;
|
|
13045
|
+
}
|
|
13046
|
+
function commandRecordFrom(value, source) {
|
|
13047
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
13048
|
+
const command = value.command;
|
|
13049
|
+
if (typeof command !== "string") return [];
|
|
13050
|
+
return [{
|
|
13051
|
+
source,
|
|
13052
|
+
command,
|
|
13053
|
+
args: stringArray(value.args)
|
|
13054
|
+
}];
|
|
13055
|
+
}
|
|
13056
|
+
function stringArray(value) {
|
|
13057
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
13058
|
+
}
|
|
13059
|
+
function matchDocker(command, args) {
|
|
13060
|
+
const text = [command, ...args].join(" ").toLowerCase();
|
|
13061
|
+
if (text.includes("docker-mcp")) return "docker-mcp";
|
|
13062
|
+
if (/\bdocker(?:-compose)?\b/u.test(text)) return command === "docker" ? command : text;
|
|
13063
|
+
return text.includes("docker mcp") ? "docker mcp" : void 0;
|
|
13064
|
+
}
|
|
13065
|
+
function matchBrowser(command, args) {
|
|
13066
|
+
const text = [command, ...args].join(" ").toLowerCase();
|
|
13067
|
+
if (text.includes("@playwright/mcp")) return "@playwright/mcp";
|
|
13068
|
+
if (text.includes("playwright install")) return "playwright install";
|
|
13069
|
+
if (text.includes("playwright")) return "playwright";
|
|
13070
|
+
if (text.includes("browser-use")) return "browser-use";
|
|
13071
|
+
if (text.includes("puppeteer")) return "puppeteer";
|
|
13072
|
+
if (text.includes("chromium")) return "chromium";
|
|
13073
|
+
return /\bchrome\b/u.test(text) ? "chrome" : void 0;
|
|
13074
|
+
}
|
|
13075
|
+
function orderedFeatures(features) {
|
|
13076
|
+
return ["docker", "browser"].filter((feature) => features.includes(feature));
|
|
13077
|
+
}
|
|
13078
|
+
//#endregion
|
|
13079
|
+
//#region src/runtime-plan/resources.ts
|
|
13080
|
+
const defaults = {
|
|
13081
|
+
small: {
|
|
13082
|
+
class: "small",
|
|
13083
|
+
cpu: 1,
|
|
13084
|
+
memoryMb: 1024,
|
|
13085
|
+
diskMb: 4096
|
|
13086
|
+
},
|
|
13087
|
+
medium: {
|
|
13088
|
+
class: "medium",
|
|
13089
|
+
cpu: 2,
|
|
13090
|
+
memoryMb: 4096,
|
|
13091
|
+
diskMb: 8192
|
|
13092
|
+
},
|
|
13093
|
+
standard: {
|
|
13094
|
+
class: "standard",
|
|
13095
|
+
cpu: 2,
|
|
13096
|
+
memoryMb: 4096,
|
|
13097
|
+
diskMb: 8192
|
|
13098
|
+
},
|
|
13099
|
+
large: {
|
|
13100
|
+
class: "large",
|
|
13101
|
+
cpu: 4,
|
|
13102
|
+
memoryMb: 8192,
|
|
13103
|
+
diskMb: 20480
|
|
13104
|
+
},
|
|
13105
|
+
heavy: {
|
|
13106
|
+
class: "heavy",
|
|
13107
|
+
cpu: 8,
|
|
13108
|
+
memoryMb: 16384,
|
|
13109
|
+
diskMb: 40960
|
|
13110
|
+
}
|
|
13111
|
+
};
|
|
13112
|
+
const rank = {
|
|
13113
|
+
small: 0,
|
|
13114
|
+
standard: 1,
|
|
13115
|
+
medium: 1,
|
|
13116
|
+
large: 2,
|
|
13117
|
+
heavy: 3
|
|
13118
|
+
};
|
|
13119
|
+
function resolveRuntimeResources(inputOrCaplet, features, policy) {
|
|
13120
|
+
const caplet = inputOrCaplet;
|
|
13121
|
+
const input = features === void 0 ? inputOrCaplet : {
|
|
13122
|
+
backend: typeof caplet.backend === "string" ? caplet.backend : void 0,
|
|
13123
|
+
features,
|
|
13124
|
+
explicitClass: explicitResourceClass$1(caplet),
|
|
13125
|
+
setupRequired: Boolean(caplet.setup),
|
|
13126
|
+
policy
|
|
13127
|
+
};
|
|
13128
|
+
const requested = input.explicitClass ?? defaultResourceClass(input);
|
|
13129
|
+
const capped = capClass(requested, input.policy?.maxClass);
|
|
13130
|
+
return {
|
|
13131
|
+
...defaults[capped] ?? defaults.standard,
|
|
13132
|
+
...requested !== capped ? { cappedByPolicy: input.policy?.maxClass } : {}
|
|
13133
|
+
};
|
|
13134
|
+
}
|
|
13135
|
+
function defaultResourceClass(input) {
|
|
13136
|
+
const hasDocker = input.features.includes("docker");
|
|
13137
|
+
const hasBrowser = input.features.includes("browser");
|
|
13138
|
+
if (hasDocker && hasBrowser) return "heavy";
|
|
13139
|
+
if (hasDocker || hasBrowser) return "large";
|
|
13140
|
+
if (input.backend === "cli" || input.backend === "mcp" || input.setupRequired) return "medium";
|
|
13141
|
+
return "small";
|
|
13142
|
+
}
|
|
13143
|
+
function explicitResourceClass$1(caplet) {
|
|
13144
|
+
const runtime = caplet.runtime;
|
|
13145
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return void 0;
|
|
13146
|
+
const resources = runtime.resources;
|
|
13147
|
+
if (!resources || typeof resources !== "object" || Array.isArray(resources)) return void 0;
|
|
13148
|
+
const value = resources.class;
|
|
13149
|
+
return typeof value === "string" ? value : void 0;
|
|
13150
|
+
}
|
|
13151
|
+
function capClass(requested, maxClass) {
|
|
13152
|
+
if (!maxClass) return requested;
|
|
13153
|
+
return (rank[requested] ?? 0) > (rank[maxClass] ?? 0) ? maxClass : requested;
|
|
13154
|
+
}
|
|
13155
|
+
//#endregion
|
|
13156
|
+
//#region src/runtime-plan/planner.ts
|
|
13157
|
+
function planCapletRuntimeRoutes(caplets, options = {}) {
|
|
13158
|
+
return caplets.map((caplet) => planSingleCaplet(caplet, options));
|
|
13159
|
+
}
|
|
13160
|
+
function planCapletRuntimeRoute(caplet, options = {}) {
|
|
13161
|
+
return planCapletRuntimeRoutes([caplet], options)[0] ?? planSingleCaplet(caplet, options);
|
|
13162
|
+
}
|
|
13163
|
+
function planSingleCaplet(caplet, options) {
|
|
13164
|
+
const route = classifyCapletRuntimeRoute(caplet);
|
|
13165
|
+
const setupRequired = Boolean(caplet.setup);
|
|
13166
|
+
const projectBindingRequired = projectBindingRequiredFor(caplet);
|
|
13167
|
+
const features = inferRuntimeFeatures(caplet);
|
|
13168
|
+
const runtime = {
|
|
13169
|
+
features: features.features,
|
|
13170
|
+
featureProvenance: features.provenance,
|
|
13171
|
+
resources: resolveRuntimeResources({
|
|
13172
|
+
backend: typeof caplet.backend === "string" ? caplet.backend : void 0,
|
|
13173
|
+
features: features.features,
|
|
13174
|
+
explicitClass: explicitResourceClass(caplet),
|
|
13175
|
+
policy: options.resourcePolicy,
|
|
13176
|
+
setupRequired
|
|
13177
|
+
})
|
|
13178
|
+
};
|
|
13179
|
+
return {
|
|
13180
|
+
id: String(caplet.server ?? ""),
|
|
13181
|
+
backend: typeof caplet.backend === "string" ? caplet.backend : "unknown",
|
|
13182
|
+
route,
|
|
13183
|
+
setupRequired,
|
|
13184
|
+
authRequired: authRequired$1("auth" in caplet ? caplet.auth : void 0),
|
|
13185
|
+
...route === "process" || route === "project_bound_process" ? { setupTarget: setupTargetFor(options.deployment) } : {},
|
|
13186
|
+
projectBindingRequired,
|
|
13187
|
+
runtime,
|
|
13188
|
+
caplet
|
|
13189
|
+
};
|
|
13190
|
+
}
|
|
13191
|
+
function classifyCapletRuntimeRoute(caplet) {
|
|
13192
|
+
if (projectBindingRequiredFor(caplet)) return "project_bound_process";
|
|
13193
|
+
if (caplet.setup) return "process";
|
|
13194
|
+
if (caplet.backend === "cli") return "process";
|
|
13195
|
+
if (caplet.backend === "mcp") return caplet.transport === "stdio" || Boolean(caplet.command) ? "process" : "worker_safe";
|
|
13196
|
+
if (caplet.backend === "openapi" || caplet.backend === "graphql" || caplet.backend === "http") return "worker_safe";
|
|
13197
|
+
if (caplet.backend === "caplets") return "worker_safe";
|
|
13198
|
+
return "local_only";
|
|
13199
|
+
}
|
|
13200
|
+
function setupTargetFor(deployment) {
|
|
13201
|
+
if (deployment === "local") return "local_host";
|
|
13202
|
+
return deployment === "self_hosted" ? "remote_host" : "hosted_sandbox";
|
|
13203
|
+
}
|
|
13204
|
+
function authRequired$1(auth) {
|
|
13205
|
+
return auth !== null && typeof auth === "object" && "type" in auth && auth.type !== "none";
|
|
13206
|
+
}
|
|
13207
|
+
function projectBindingRequiredFor(caplet) {
|
|
13208
|
+
const projectBinding = caplet.projectBinding;
|
|
13209
|
+
return Boolean(projectBinding) && typeof projectBinding === "object" && !Array.isArray(projectBinding) && projectBinding.required === true;
|
|
13210
|
+
}
|
|
13211
|
+
function explicitResourceClass(caplet) {
|
|
13212
|
+
const runtime = caplet.runtime;
|
|
13213
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return void 0;
|
|
13214
|
+
const resources = runtime.resources;
|
|
13215
|
+
if (!resources || typeof resources !== "object" || Array.isArray(resources)) return void 0;
|
|
13216
|
+
const value = resources.class;
|
|
13217
|
+
return value === "small" || value === "medium" || value === "standard" || value === "large" || value === "heavy" ? value : void 0;
|
|
13218
|
+
}
|
|
13219
|
+
//#endregion
|
|
13220
|
+
//#region src/caplet-source/parse.ts
|
|
13221
|
+
async function parseCapletSource(source) {
|
|
13222
|
+
const files = await source.listFiles();
|
|
13223
|
+
let loaded;
|
|
13224
|
+
try {
|
|
13225
|
+
loaded = loadCapletFilesFromMap({ files });
|
|
13226
|
+
} catch (error) {
|
|
13227
|
+
return {
|
|
13228
|
+
ok: false,
|
|
13229
|
+
resolvedCaplets: [],
|
|
13230
|
+
warnings: [],
|
|
13231
|
+
errors: [{ message: errorMessage(error) }]
|
|
13232
|
+
};
|
|
13233
|
+
}
|
|
13234
|
+
if (!loaded) return {
|
|
13235
|
+
ok: false,
|
|
13236
|
+
resolvedCaplets: [],
|
|
13237
|
+
warnings: [],
|
|
13238
|
+
errors: [{ message: "Caplet source must include at least one CAPLET.md or top-level Markdown Caplet file." }]
|
|
13239
|
+
};
|
|
13240
|
+
let config;
|
|
13241
|
+
try {
|
|
13242
|
+
config = parseConfig$1({
|
|
13243
|
+
version: 1,
|
|
13244
|
+
...loaded.config
|
|
13245
|
+
});
|
|
13246
|
+
} catch (error) {
|
|
13247
|
+
return {
|
|
13248
|
+
ok: false,
|
|
13249
|
+
resolvedCaplets: [],
|
|
13250
|
+
warnings: [],
|
|
13251
|
+
errors: [{ message: errorMessage(error) }]
|
|
13252
|
+
};
|
|
13253
|
+
}
|
|
13254
|
+
const configCaplets = capletsFromConfig(config);
|
|
13255
|
+
const plansById = new Map(planCapletRuntimeRoutes(configCaplets, { deployment: "hosted" }).map((plan) => [plan.id, plan]));
|
|
13256
|
+
const caplets = configCaplets.map((caplet) => {
|
|
13257
|
+
const plan = plansById.get(caplet.server);
|
|
13258
|
+
return {
|
|
13259
|
+
id: caplet.server,
|
|
13260
|
+
name: caplet.name,
|
|
13261
|
+
description: caplet.description,
|
|
13262
|
+
backend: caplet.backend,
|
|
13263
|
+
sourcePath: loaded.paths[caplet.server] ?? "CAPLET.md",
|
|
13264
|
+
setupRequired: Boolean(caplet.setup),
|
|
13265
|
+
authRequired: authRequired("auth" in caplet ? caplet.auth : void 0),
|
|
13266
|
+
projectBindingRequired: plan?.projectBindingRequired ?? false,
|
|
13267
|
+
runtime: {
|
|
13268
|
+
...plan?.runtime ?? {
|
|
13269
|
+
features: [],
|
|
13270
|
+
featureProvenance: [],
|
|
13271
|
+
resources: {
|
|
13272
|
+
class: "standard",
|
|
13273
|
+
cpu: 2,
|
|
13274
|
+
memoryMb: 4096,
|
|
13275
|
+
diskMb: 8192
|
|
13276
|
+
}
|
|
13277
|
+
},
|
|
13278
|
+
route: plan?.route ?? "local_only",
|
|
13279
|
+
...plan?.setupTarget === void 0 ? {} : { setupTarget: plan.setupTarget }
|
|
13280
|
+
},
|
|
13281
|
+
localReferences: localReferencePaths(caplet).map((path) => ({
|
|
13282
|
+
path,
|
|
13283
|
+
exists: false
|
|
13284
|
+
})),
|
|
13285
|
+
config: caplet
|
|
13286
|
+
};
|
|
13287
|
+
});
|
|
13288
|
+
for (const caplet of caplets) for (const reference of caplet.localReferences) reference.exists = Boolean(await source.readFile(reference.path));
|
|
13289
|
+
const errors = caplets.flatMap((caplet) => caplet.localReferences.filter((reference) => !reference.exists).map((reference) => ({
|
|
13290
|
+
path: caplet.sourcePath,
|
|
13291
|
+
message: `Referenced file ${reference.path} was not found.`
|
|
13292
|
+
})));
|
|
13293
|
+
return {
|
|
13294
|
+
ok: errors.length === 0,
|
|
13295
|
+
config,
|
|
13296
|
+
resolvedCaplets: errors.length === 0 ? caplets : [],
|
|
13297
|
+
warnings: [],
|
|
13298
|
+
errors
|
|
13299
|
+
};
|
|
13300
|
+
}
|
|
13301
|
+
function capletsFromConfig(config) {
|
|
13302
|
+
return [
|
|
13303
|
+
...Object.values(config.mcpServers),
|
|
13304
|
+
...Object.values(config.openapiEndpoints),
|
|
13305
|
+
...Object.values(config.graphqlEndpoints),
|
|
13306
|
+
...Object.values(config.httpApis),
|
|
13307
|
+
...Object.values(config.cliTools),
|
|
13308
|
+
...Object.values(config.capletSets)
|
|
13309
|
+
];
|
|
13310
|
+
}
|
|
13311
|
+
function localReferencePaths(caplet) {
|
|
13312
|
+
if (caplet.backend === "openapi") return filterLocalReferences([caplet.specPath]);
|
|
13313
|
+
if (caplet.backend === "graphql") return filterLocalReferences([caplet.schemaPath, ...Object.values(caplet.operations ?? {}).map((operation) => operation.documentPath)]);
|
|
13314
|
+
if (caplet.backend === "caplets") return filterLocalReferences([caplet.configPath]);
|
|
13315
|
+
return [];
|
|
13316
|
+
}
|
|
13317
|
+
function filterLocalReferences(values) {
|
|
13318
|
+
return values.filter((value) => typeof value === "string" && value.length > 0 && !hasEnvReference(value) && !/^[a-z][a-z0-9+.-]*:/iu.test(value));
|
|
13319
|
+
}
|
|
13320
|
+
function authRequired(auth) {
|
|
13321
|
+
return auth !== null && typeof auth === "object" && "type" in auth && auth.type !== "none";
|
|
13322
|
+
}
|
|
13323
|
+
function hasEnvReference(value) {
|
|
13324
|
+
return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/u.test(value);
|
|
13325
|
+
}
|
|
13326
|
+
function errorMessage(error) {
|
|
13327
|
+
return error instanceof Error ? error.message : String(error);
|
|
13328
|
+
}
|
|
13329
|
+
//#endregion
|
|
13330
|
+
//#region src/project-binding/types.ts
|
|
13331
|
+
const PROJECT_BINDING_STATES = [
|
|
13332
|
+
"not_attached",
|
|
13333
|
+
"attaching",
|
|
13334
|
+
"syncing",
|
|
13335
|
+
"ready",
|
|
13336
|
+
"degraded",
|
|
13337
|
+
"blocked",
|
|
13338
|
+
"offline",
|
|
13339
|
+
"cleaning_up",
|
|
13340
|
+
"ended",
|
|
13341
|
+
"expired"
|
|
13342
|
+
];
|
|
13343
|
+
const PROJECT_BINDING_SYNC_STATES = [
|
|
13344
|
+
"not_started",
|
|
13345
|
+
"pending",
|
|
13346
|
+
"syncing",
|
|
13347
|
+
"idle",
|
|
13348
|
+
"failed"
|
|
13349
|
+
];
|
|
13350
|
+
//#endregion
|
|
13351
|
+
//#region src/project-binding/routes.ts
|
|
13352
|
+
const PROJECT_BINDINGS_CONTROL_PATH = "/control/project-bindings";
|
|
13353
|
+
const PROJECT_BINDING_CONNECT_PATH = `${PROJECT_BINDINGS_CONTROL_PATH}/connect`;
|
|
13354
|
+
function projectBindingConnectPath() {
|
|
13355
|
+
return PROJECT_BINDING_CONNECT_PATH;
|
|
13356
|
+
}
|
|
13357
|
+
function projectBindingStatusPath(bindingId) {
|
|
13358
|
+
return `${PROJECT_BINDINGS_CONTROL_PATH}/${encodeURIComponent(bindingId)}/status`;
|
|
13359
|
+
}
|
|
13360
|
+
function projectBindingConnectUrl(baseUrl) {
|
|
13361
|
+
return withBasePath(baseUrl, projectBindingConnectPath()).toString();
|
|
13362
|
+
}
|
|
13363
|
+
function projectBindingStatusUrl(baseUrl, bindingId) {
|
|
13364
|
+
return withBasePath(baseUrl, projectBindingStatusPath(bindingId)).toString();
|
|
13365
|
+
}
|
|
13366
|
+
function withBasePath(baseUrl, path) {
|
|
13367
|
+
const url = new URL(baseUrl);
|
|
13368
|
+
url.pathname = `${trimTrailingSlash(url.pathname)}${path}`;
|
|
13369
|
+
url.search = "";
|
|
13370
|
+
url.hash = "";
|
|
13371
|
+
return url;
|
|
13372
|
+
}
|
|
13373
|
+
function trimTrailingSlash(pathname) {
|
|
13374
|
+
if (pathname === "/") return "";
|
|
13375
|
+
return pathname.replace(/\/+$/u, "");
|
|
13376
|
+
}
|
|
13377
|
+
//#endregion
|
|
13378
|
+
//#region src/project-binding/mutagen.ts
|
|
13379
|
+
const readyStatuses = new Set([
|
|
13380
|
+
"watching",
|
|
13381
|
+
"ready",
|
|
13382
|
+
"ok"
|
|
13383
|
+
]);
|
|
13384
|
+
const syncingStatuses = new Set([
|
|
13385
|
+
"connecting",
|
|
13386
|
+
"halted on root",
|
|
13387
|
+
"reconciling",
|
|
13388
|
+
"scanning",
|
|
13389
|
+
"staging",
|
|
13390
|
+
"syncing",
|
|
13391
|
+
"transitioning",
|
|
13392
|
+
"watching for changes"
|
|
13393
|
+
]);
|
|
13394
|
+
function planMutagenVersionCommand(mutagenBinary = "mutagen") {
|
|
13395
|
+
return {
|
|
13396
|
+
command: mutagenBinary,
|
|
13397
|
+
args: ["version"]
|
|
13398
|
+
};
|
|
13399
|
+
}
|
|
13400
|
+
function planMutagenSyncCreateCommand(input, mutagenBinary = "mutagen") {
|
|
13401
|
+
return {
|
|
13402
|
+
command: mutagenBinary,
|
|
13403
|
+
args: [
|
|
13404
|
+
"sync",
|
|
13405
|
+
"create",
|
|
13406
|
+
input.localProjectRoot,
|
|
13407
|
+
input.serverProjectRoot,
|
|
13408
|
+
"--name",
|
|
13409
|
+
mutagenSyncName(input.bindingId)
|
|
13410
|
+
]
|
|
13411
|
+
};
|
|
13412
|
+
}
|
|
13413
|
+
function planMutagenSyncListCommand(mutagenBinary = "mutagen") {
|
|
13414
|
+
return {
|
|
13415
|
+
command: mutagenBinary,
|
|
13416
|
+
args: [
|
|
13417
|
+
"sync",
|
|
13418
|
+
"list",
|
|
13419
|
+
"--template",
|
|
13420
|
+
"json"
|
|
13421
|
+
]
|
|
13422
|
+
};
|
|
13423
|
+
}
|
|
13424
|
+
function planMutagenSyncTerminateCommand(bindingId, mutagenBinary = "mutagen") {
|
|
13425
|
+
return {
|
|
13426
|
+
command: mutagenBinary,
|
|
13427
|
+
args: [
|
|
13428
|
+
"sync",
|
|
13429
|
+
"terminate",
|
|
13430
|
+
mutagenSyncName(bindingId)
|
|
13431
|
+
]
|
|
13432
|
+
};
|
|
13433
|
+
}
|
|
13434
|
+
function mutagenSyncName(bindingId) {
|
|
13435
|
+
return `caplets-${bindingId}`;
|
|
13436
|
+
}
|
|
13437
|
+
var ManagedMutagenProjectSync = class {
|
|
13438
|
+
mutagenBinary;
|
|
13439
|
+
#runner;
|
|
13440
|
+
#snapshot;
|
|
13441
|
+
constructor(options = {}) {
|
|
13442
|
+
this.mutagenBinary = options.mutagenBinary ?? "mutagen";
|
|
13443
|
+
this.#runner = options.runner ?? defaultMutagenProcessRunner;
|
|
13444
|
+
this.#snapshot = {
|
|
13445
|
+
state: "idle",
|
|
13446
|
+
publicMessage: "Project sync is idle.",
|
|
13447
|
+
mutagenBinary: this.mutagenBinary
|
|
13448
|
+
};
|
|
13449
|
+
}
|
|
13450
|
+
async start(input) {
|
|
13451
|
+
this.#setState({
|
|
13452
|
+
state: "starting",
|
|
13453
|
+
bindingId: input.bindingId,
|
|
13454
|
+
publicMessage: "Project sync is starting."
|
|
13455
|
+
});
|
|
13456
|
+
const versionResult = await this.#run(planMutagenVersionCommand(this.mutagenBinary));
|
|
13457
|
+
if (versionResult.blocked) return this.snapshot();
|
|
13458
|
+
this.#snapshot.mutagenVersion = parseMutagenVersionOutput(versionResult.result.stdout ?? "").version;
|
|
13459
|
+
if ((await this.#run(planMutagenSyncCreateCommand(input, this.mutagenBinary))).blocked) return this.snapshot();
|
|
13460
|
+
this.#setState({
|
|
13461
|
+
state: "syncing",
|
|
13462
|
+
bindingId: input.bindingId,
|
|
13463
|
+
publicMessage: "Project sync is starting."
|
|
13464
|
+
});
|
|
13465
|
+
return this.snapshot();
|
|
13466
|
+
}
|
|
13467
|
+
async refresh(input) {
|
|
13468
|
+
const listResult = await this.#run(planMutagenSyncListCommand(this.mutagenBinary), input.bindingId);
|
|
13469
|
+
if (listResult.blocked) return this.snapshot();
|
|
13470
|
+
let session;
|
|
13471
|
+
try {
|
|
13472
|
+
session = findMutagenSyncSession(listResult.result.stdout, mutagenSyncName(input.bindingId));
|
|
13473
|
+
} catch {
|
|
13474
|
+
this.#block(input.bindingId, "project_sync_status_unavailable");
|
|
13475
|
+
return this.snapshot();
|
|
13476
|
+
}
|
|
13477
|
+
if (!session) {
|
|
13478
|
+
this.#block(input.bindingId, "project_sync_status_unavailable");
|
|
13479
|
+
return this.snapshot();
|
|
13480
|
+
}
|
|
13481
|
+
const normalizedStatus = session.status.toLocaleLowerCase();
|
|
13482
|
+
if (readyStatuses.has(normalizedStatus)) {
|
|
13483
|
+
this.#setState({
|
|
13484
|
+
state: "ready",
|
|
13485
|
+
bindingId: input.bindingId,
|
|
13486
|
+
publicMessage: "Project sync is ready."
|
|
13487
|
+
});
|
|
13488
|
+
return this.snapshot();
|
|
13489
|
+
}
|
|
13490
|
+
if (syncingStatuses.has(normalizedStatus)) {
|
|
13491
|
+
this.#setState({
|
|
13492
|
+
state: "syncing",
|
|
13493
|
+
bindingId: input.bindingId,
|
|
13494
|
+
publicMessage: "Project sync is catching up."
|
|
13495
|
+
});
|
|
13496
|
+
return this.snapshot();
|
|
13497
|
+
}
|
|
13498
|
+
this.#block(input.bindingId, mapTextToDiagnosticCode(session.status));
|
|
13499
|
+
return this.snapshot();
|
|
13500
|
+
}
|
|
13501
|
+
async stop(input) {
|
|
13502
|
+
if ((await this.#run(planMutagenSyncTerminateCommand(input.bindingId, this.mutagenBinary), input.bindingId)).blocked) return this.snapshot();
|
|
13503
|
+
this.#setState({
|
|
13504
|
+
state: "stopped",
|
|
13505
|
+
bindingId: input.bindingId,
|
|
13506
|
+
publicMessage: "Project sync has stopped."
|
|
13507
|
+
});
|
|
13508
|
+
return this.snapshot();
|
|
13509
|
+
}
|
|
13510
|
+
snapshot() {
|
|
13511
|
+
const snapshot = { ...this.#snapshot };
|
|
13512
|
+
if (this.#snapshot.lastCommand) snapshot.lastCommand = {
|
|
13513
|
+
...this.#snapshot.lastCommand,
|
|
13514
|
+
args: [...this.#snapshot.lastCommand.args]
|
|
13515
|
+
};
|
|
13516
|
+
return snapshot;
|
|
13517
|
+
}
|
|
13518
|
+
async #run(plan, bindingId = this.#snapshot.bindingId) {
|
|
13519
|
+
try {
|
|
13520
|
+
const result = normalizeProcessResult(await this.#runner(plan.command, [...plan.args]));
|
|
13521
|
+
this.#snapshot.lastCommand = {
|
|
13522
|
+
...plan,
|
|
13523
|
+
...result,
|
|
13524
|
+
args: [...plan.args]
|
|
13525
|
+
};
|
|
13526
|
+
if (result.exitCode !== 0) {
|
|
13527
|
+
this.#block(bindingId, "project_sync_process_exit");
|
|
13528
|
+
return { blocked: true };
|
|
13529
|
+
}
|
|
13530
|
+
return {
|
|
13531
|
+
blocked: false,
|
|
13532
|
+
result
|
|
13533
|
+
};
|
|
13534
|
+
} catch (error) {
|
|
13535
|
+
const errorResult = {
|
|
13536
|
+
stdout: "",
|
|
13537
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
13538
|
+
};
|
|
13539
|
+
const exitCode = errorExitCode(error);
|
|
13540
|
+
if (exitCode !== void 0) errorResult.exitCode = exitCode;
|
|
13541
|
+
this.#snapshot.lastCommand = commandStatus(plan, errorResult);
|
|
13542
|
+
this.#block(bindingId, mapErrorToDiagnosticCode(error));
|
|
13543
|
+
return { blocked: true };
|
|
13544
|
+
}
|
|
13545
|
+
}
|
|
13546
|
+
#block(bindingId, diagnosticCode) {
|
|
13547
|
+
this.#setState({
|
|
13548
|
+
state: "blocked",
|
|
13549
|
+
bindingId,
|
|
13550
|
+
diagnosticCode,
|
|
13551
|
+
publicMessage: "Project sync is blocked."
|
|
13552
|
+
});
|
|
13553
|
+
}
|
|
13554
|
+
#setState(next) {
|
|
13555
|
+
const snapshot = {
|
|
13556
|
+
...this.#snapshot,
|
|
13557
|
+
state: next.state,
|
|
13558
|
+
publicMessage: next.publicMessage,
|
|
13559
|
+
mutagenBinary: this.mutagenBinary
|
|
13560
|
+
};
|
|
13561
|
+
if (next.bindingId !== void 0) snapshot.bindingId = next.bindingId;
|
|
13562
|
+
if (next.diagnosticCode !== void 0) snapshot.diagnosticCode = next.diagnosticCode;
|
|
13563
|
+
else delete snapshot.diagnosticCode;
|
|
13564
|
+
this.#snapshot = snapshot;
|
|
13565
|
+
}
|
|
13566
|
+
};
|
|
13567
|
+
function mutagenProjectSyncDoctorData(snapshot) {
|
|
13568
|
+
const doctorData = { state: snapshot.state };
|
|
13569
|
+
if (snapshot.diagnosticCode !== void 0) doctorData.diagnosticCode = snapshot.diagnosticCode;
|
|
13570
|
+
if (snapshot.mutagenBinary !== void 0) doctorData.mutagenBinary = snapshot.mutagenBinary;
|
|
13571
|
+
if (snapshot.mutagenVersion !== void 0) doctorData.mutagenVersion = snapshot.mutagenVersion;
|
|
13572
|
+
if (snapshot.lastCommand !== void 0) doctorData.lastCommand = snapshot.lastCommand;
|
|
13573
|
+
return doctorData;
|
|
13574
|
+
}
|
|
13575
|
+
function parseMutagenVersionOutput(output) {
|
|
13576
|
+
return { version: output.match(/Mutagen version\s+([^\s]+)/u)?.[1] ?? "unknown" };
|
|
13577
|
+
}
|
|
13578
|
+
async function defaultMutagenProcessRunner(command, args) {
|
|
13579
|
+
return new Promise((resolve, reject) => {
|
|
13580
|
+
const child = spawn(command, args, { stdio: [
|
|
13581
|
+
"ignore",
|
|
13582
|
+
"pipe",
|
|
13583
|
+
"pipe"
|
|
13584
|
+
] });
|
|
13585
|
+
let stdout = "";
|
|
13586
|
+
let stderr = "";
|
|
13587
|
+
child.stdout.setEncoding("utf8");
|
|
13588
|
+
child.stderr.setEncoding("utf8");
|
|
13589
|
+
child.stdout.on("data", (chunk) => {
|
|
13590
|
+
stdout += chunk;
|
|
13591
|
+
});
|
|
13592
|
+
child.stderr.on("data", (chunk) => {
|
|
13593
|
+
stderr += chunk;
|
|
13594
|
+
});
|
|
13595
|
+
child.on("error", reject);
|
|
13596
|
+
child.on("close", (exitCode) => {
|
|
13597
|
+
resolve(exitCode === null ? {
|
|
13598
|
+
stdout,
|
|
13599
|
+
stderr
|
|
13600
|
+
} : {
|
|
13601
|
+
stdout,
|
|
13602
|
+
stderr,
|
|
13603
|
+
exitCode
|
|
13604
|
+
});
|
|
13605
|
+
});
|
|
13606
|
+
});
|
|
13607
|
+
}
|
|
13608
|
+
function normalizeProcessResult(result) {
|
|
13609
|
+
return {
|
|
13610
|
+
stdout: result.stdout ?? "",
|
|
13611
|
+
stderr: result.stderr ?? "",
|
|
13612
|
+
exitCode: result.exitCode ?? 0
|
|
13613
|
+
};
|
|
13614
|
+
}
|
|
13615
|
+
function commandStatus(plan, result) {
|
|
13616
|
+
const status = {
|
|
13617
|
+
...plan,
|
|
13618
|
+
args: [...plan.args],
|
|
13619
|
+
stdout: result.stdout ?? "",
|
|
13620
|
+
stderr: result.stderr ?? ""
|
|
13621
|
+
};
|
|
13622
|
+
if (result.exitCode !== void 0) status.exitCode = result.exitCode;
|
|
13623
|
+
return status;
|
|
13624
|
+
}
|
|
13625
|
+
function mapErrorToDiagnosticCode(error) {
|
|
13626
|
+
const text = errorText(error);
|
|
13627
|
+
if (errorCode(error) === "ENOENT" || /\bnot found\b|enoent|no such file/u.test(text)) return "project_sync_binary_missing";
|
|
13628
|
+
return mapTextToDiagnosticCode(text, errorExitCode(error));
|
|
13629
|
+
}
|
|
13630
|
+
function mapTextToDiagnosticCode(text, exitCode) {
|
|
13631
|
+
const normalized = text.toLocaleLowerCase();
|
|
13632
|
+
if (/auth|credential|permission denied|unauthorized|forbidden/u.test(normalized)) return "project_sync_auth_failed";
|
|
13633
|
+
if (/already exists|conflict|duplicate|in use/u.test(normalized)) return "project_sync_conflict";
|
|
13634
|
+
if (exitCode !== void 0 || /exit|failed|terminated/u.test(normalized)) return "project_sync_process_exit";
|
|
13635
|
+
return "project_sync_status_unavailable";
|
|
13636
|
+
}
|
|
13637
|
+
function errorText(error) {
|
|
13638
|
+
return error instanceof Error ? error.message.toLocaleLowerCase() : String(error).toLocaleLowerCase();
|
|
13639
|
+
}
|
|
13640
|
+
function errorCode(error) {
|
|
13641
|
+
return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
13642
|
+
}
|
|
13643
|
+
function errorExitCode(error) {
|
|
13644
|
+
return typeof error === "object" && error !== null && "exitCode" in error ? error.exitCode : void 0;
|
|
13645
|
+
}
|
|
13646
|
+
function findMutagenSyncSession(stdout, name) {
|
|
13647
|
+
const parsed = JSON.parse(stdout);
|
|
13648
|
+
for (const entry of collectCandidateSessions(parsed)) if (entry.name === name) return entry;
|
|
13649
|
+
}
|
|
13650
|
+
function collectCandidateSessions(value) {
|
|
13651
|
+
if (Array.isArray(value)) return value.flatMap(collectCandidateSessions);
|
|
13652
|
+
if (!isRecord(value)) return [];
|
|
13653
|
+
const ownSession = sessionFromRecord(value);
|
|
13654
|
+
const nested = [
|
|
13655
|
+
"synchronizations",
|
|
13656
|
+
"sessions",
|
|
13657
|
+
"syncs"
|
|
13658
|
+
].flatMap((key) => collectCandidateSessions(value[key]));
|
|
13659
|
+
return ownSession ? [ownSession, ...nested] : nested;
|
|
13660
|
+
}
|
|
13661
|
+
function sessionFromRecord(value) {
|
|
13662
|
+
const name = stringProperty(value, "name") ?? stringProperty(value, "Name");
|
|
13663
|
+
const status = stringProperty(value, "status") ?? stringProperty(value, "Status") ?? stringProperty(value, "sessionStatus") ?? stringProperty(value, "SessionStatus");
|
|
13664
|
+
return name && status ? {
|
|
13665
|
+
name,
|
|
13666
|
+
status
|
|
13667
|
+
} : void 0;
|
|
13668
|
+
}
|
|
13669
|
+
function stringProperty(value, key) {
|
|
13670
|
+
return typeof value[key] === "string" ? value[key] : void 0;
|
|
13671
|
+
}
|
|
13672
|
+
function isRecord(value) {
|
|
13673
|
+
return typeof value === "object" && value !== null;
|
|
13674
|
+
}
|
|
13675
|
+
//#endregion
|
|
13676
|
+
export { BundleCapletSource, CapletsRuntime, CloudAuthClient, CloudAuthStore, DEFAULT_SYNC_LIMITS, FilesystemCapletSource, LocalSetupStore, ManagedMutagenProjectSync, PROJECT_BINDINGS_CONTROL_PATH, PROJECT_BINDING_CONNECT_PATH, PROJECT_BINDING_ERROR_CODES, PROJECT_BINDING_STATES, PROJECT_BINDING_SYNC_STATES, ProjectBindingError, ProjectBindingWorkspaceStore, ServerRegistry, attachProjectOnce, attachProjectSession, buildProjectSyncManifest, capabilityDescription, capletSetupContentHash, classifyCapletRuntimeRoute, cloudAuthPath, createProgram, defaultProjectBindingWebSocketFactory, enforceProjectSyncSizeLimits, generatedToolInputSchema, handleServerTool, hasRenderableStructuredContent, loadConfig, markdownCallToolResultContent, markdownStructuredContent, migrateCredentials, mutagenProjectSyncDoctorData, mutagenSyncName, openBrowserUrl, parseCapletSource, parseConfig, parseMutagenVersionOutput as parseManagedMutagenVersionOutput, planCapletRuntimeRoute, planCapletRuntimeRoutes, planMutagenSyncCreateCommand, planMutagenSyncListCommand, planMutagenSyncTerminateCommand, planMutagenVersionCommand, projectBindingConnectPath, projectBindingConnectUrl, projectBindingError, projectBindingRecovery, projectBindingStatusPath, projectBindingStatusUrl, projectBindingWorkspacePaths, projectBindingWorkspaceRoot, redactedCloudAuthStatus, resolveAttachOptions, runCapletSetup, runCli, runProjectBindingSession, serveCaplets, serveHttp, serveResolvedCaplets, serveStdio, stableJson };
|