@manifest-network/manifest-mcp-fred 0.10.0 → 0.12.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/http/auth-token-service.js.map +1 -1
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/auth.js.map +1 -1
- package/dist/http/fred.d.ts +12 -0
- package/dist/http/fred.d.ts.map +1 -1
- package/dist/http/fred.js +5 -0
- package/dist/http/fred.js.map +1 -1
- package/dist/http/provider.d.ts.map +1 -1
- package/dist/http/provider.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +8 -1
- package/dist/manifest.js.map +1 -1
- package/dist/server/fetch-gate.d.ts +34 -0
- package/dist/server/fetch-gate.d.ts.map +1 -0
- package/dist/server/fetch-gate.js +41 -0
- package/dist/server/fetch-gate.js.map +1 -0
- package/dist/server/progress.js.map +1 -1
- package/dist/server/register-prompts.d.ts.map +1 -1
- package/dist/server/register-prompts.js.map +1 -1
- package/dist/server/register-resources.d.ts.map +1 -1
- package/dist/server/register-resources.js.map +1 -1
- package/dist/server/register-tools.d.ts +8 -0
- package/dist/server/register-tools.d.ts.map +1 -1
- package/dist/server/register-tools.js +10 -10
- package/dist/server/register-tools.js.map +1 -1
- package/dist/tools/appStatus.js.map +1 -1
- package/dist/tools/browseCatalog.d.ts.map +1 -1
- package/dist/tools/browseCatalog.js.map +1 -1
- package/dist/tools/buildManifestPreview.d.ts.map +1 -1
- package/dist/tools/buildManifestPreview.js.map +1 -1
- package/dist/tools/checkDeploymentReadiness.d.ts.map +1 -1
- package/dist/tools/checkDeploymentReadiness.js.map +1 -1
- package/dist/tools/deployApp.d.ts +3 -16
- package/dist/tools/deployApp.d.ts.map +1 -1
- package/dist/tools/deployApp.js +22 -116
- package/dist/tools/deployApp.js.map +1 -1
- package/dist/tools/deployManifest.d.ts +52 -0
- package/dist/tools/deployManifest.d.ts.map +1 -0
- package/dist/tools/deployManifest.js +172 -0
- package/dist/tools/deployManifest.js.map +1 -0
- package/dist/tools/fetchActiveLease.d.ts +1 -2
- package/dist/tools/fetchActiveLease.d.ts.map +1 -1
- package/dist/tools/fetchActiveLease.js.map +1 -1
- package/dist/tools/getLogs.js.map +1 -1
- package/dist/tools/resolveLeaseProvider.d.ts.map +1 -1
- package/dist/tools/resolveLeaseProvider.js.map +1 -1
- package/dist/tools/restartApp.js.map +1 -1
- package/dist/tools/updateApp.js.map +1 -1
- package/dist/tools/waitForAppReady.d.ts.map +1 -1
- package/dist/tools/waitForAppReady.js.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-token-service.js","names":[],"sources":["../../src/http/auth-token-service.ts"],"sourcesContent":["import {\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n AuthTimestampTracker,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './auth.js';\n\n/**\n * Wallet-bound builder for ADR-036 provider auth tokens.\n *\n * Owns the stateful pieces (timestamp serialization, signArbitrary binding) so\n * that `FredMCPServer` can depend on a single collaborator rather than hand-\n * rolling the flow at each tool call site. The underlying stateless builders\n * (`createSignMessage`, `createAuthToken`, etc.) remain in `./auth.ts` for\n * library callers who need them without our specific wallet wiring.\n *\n * The `signArbitrary` requirement is enforced lazily — a wallet without\n * ADR-036 support still lets the server boot and serve non-auth-gated paths;\n * only provider-auth tool calls throw `INVALID_CONFIG`.\n */\nexport class AuthTokenService {\n private readonly timestamps = new AuthTimestampTracker();\n\n constructor(private readonly walletProvider: WalletProvider) {}\n\n async providerToken(address: string, leaseUuid: string): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createSignMessage(address, leaseUuid, timestamp);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n );\n }\n\n async leaseDataToken(\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createLeaseDataSignMessage(\n leaseUuid,\n metaHashHex,\n timestamp,\n );\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n metaHashHex,\n );\n }\n\n private requireSignArbitrary(): NonNullable<WalletProvider['signArbitrary']> {\n if (!this.walletProvider.signArbitrary) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Wallet does not support signArbitrary (ADR-036). Required for provider authentication. Use a wallet provider that implements signArbitrary.',\n );\n }\n return this.walletProvider.signArbitrary.bind(this.walletProvider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,IAAa,mBAAb,MAA8B;CAG5B,YAAY,gBAAiD;
|
|
1
|
+
{"version":3,"file":"auth-token-service.js","names":[],"sources":["../../src/http/auth-token-service.ts"],"sourcesContent":["import {\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n AuthTimestampTracker,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './auth.js';\n\n/**\n * Wallet-bound builder for ADR-036 provider auth tokens.\n *\n * Owns the stateful pieces (timestamp serialization, signArbitrary binding) so\n * that `FredMCPServer` can depend on a single collaborator rather than hand-\n * rolling the flow at each tool call site. The underlying stateless builders\n * (`createSignMessage`, `createAuthToken`, etc.) remain in `./auth.ts` for\n * library callers who need them without our specific wallet wiring.\n *\n * The `signArbitrary` requirement is enforced lazily — a wallet without\n * ADR-036 support still lets the server boot and serve non-auth-gated paths;\n * only provider-auth tool calls throw `INVALID_CONFIG`.\n */\nexport class AuthTokenService {\n private readonly timestamps = new AuthTimestampTracker();\n\n constructor(private readonly walletProvider: WalletProvider) {}\n\n async providerToken(address: string, leaseUuid: string): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createSignMessage(address, leaseUuid, timestamp);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n );\n }\n\n async leaseDataToken(\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createLeaseDataSignMessage(\n leaseUuid,\n metaHashHex,\n timestamp,\n );\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n metaHashHex,\n );\n }\n\n private requireSignArbitrary(): NonNullable<WalletProvider['signArbitrary']> {\n if (!this.walletProvider.signArbitrary) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Wallet does not support signArbitrary (ADR-036). Required for provider authentication. Use a wallet provider that implements signArbitrary.',\n );\n }\n return this.walletProvider.signArbitrary.bind(this.walletProvider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,IAAa,mBAAb,MAA8B;CAG5B,YAAY,gBAAiD;EAAhC,KAAA,iBAAA;EAF7B,KAAiB,aAAa,IAAI,qBAAqB;CAEO;CAE9D,MAAM,cAAc,SAAiB,WAAoC;EACvE,MAAM,gBAAgB,KAAK,qBAAqB;EAChD,MAAM,YAAY,MAAM,KAAK,WAAW,KAAK;EAE7C,MAAM,EAAE,SAAS,cAAc,MAAM,cAAc,SADnC,kBAAkB,SAAS,WAAW,SACY,CAAC;EACnE,OAAO,gBACL,SACA,WACA,WACA,QAAQ,OACR,SACF;CACF;CAEA,MAAM,eACJ,SACA,WACA,aACiB;EACjB,MAAM,gBAAgB,KAAK,qBAAqB;EAChD,MAAM,YAAY,MAAM,KAAK,WAAW,KAAK;EAM7C,MAAM,EAAE,SAAS,cAAc,MAAM,cAAc,SALnC,2BACd,WACA,aACA,SAEgE,CAAC;EACnE,OAAO,gBACL,SACA,WACA,WACA,QAAQ,OACR,WACA,WACF;CACF;CAEA,uBAA6E;EAC3E,IAAI,CAAC,KAAK,eAAe,eACvB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,6IACF;EAEF,OAAO,KAAK,eAAe,cAAc,KAAK,KAAK,cAAc;CACnE;AACF"}
|
package/dist/http/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/http/auth.ts"],"mappings":";;AAYA;;;;;;;;;cAAa,oBAAA;EAAA,QACH,IAAA;EAAA,QACA,KAAA;EAER,IAAA,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/http/auth.ts"],"mappings":";;AAYA;;;;;;;;;cAAa,oBAAA;EAAA,QACH,IAAA;EAAA,QACA,KAAA;EAER,IAAA,IAAQ,OAAO;AAAA;;;;;AAiCE;AAKnB;;;;;;;iBARgB,iBAAA,CACd,MAAA,UACA,SAAA,UACA,SAAA;AAAA,iBAKc,0BAAA,CACd,SAAA,UACA,WAAA,UACA,SAAA;AAAA,UAKe,gBAAA;EAAA,SACN,MAAA;EAAA,SACA,UAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,SACA,SAAA;AAAA;AAAA,iBAGK,eAAA,CACd,MAAA,UACA,SAAA,UACA,SAAA,UACA,MAAA,UACA,SAAA,UACA,WAAA"}
|
package/dist/http/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","names":[],"sources":["../../src/http/auth.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\n\n/**\n * Produces unix-second timestamps guaranteed unique across calls.\n *\n * ADR-036 signing is deterministic, so tokens sharing the same timestamp\n * produce identical signatures. The provider's replay tracker rejects\n * duplicate signatures on protected endpoints (connection, restart, update).\n * We wait for the wall clock to advance rather than drifting into the future\n * (the provider enforces a 30 s max token age and 10 s max-future-skew).\n * A promise queue serializes concurrent callers.\n */\nexport class AuthTimestampTracker {\n private last = 0;\n private queue: Promise<number> = Promise.resolve(0);\n\n next(): Promise<number> {\n const result = this.queue.then(async () => {\n let now = Math.floor(Date.now() / 1000);\n while (now <= this.last) {\n // Cap sleep at 1 s so forward clock jumps (e.g. NTP) are\n // picked up quickly instead of waiting the full precomputed delay.\n const sleepMs = Math.min((this.last - now + 1) * 1000, 1000);\n await new Promise((resolve) => setTimeout(resolve, sleepMs));\n now = Math.floor(Date.now() / 1000);\n }\n this.last = now;\n return now;\n });\n this.queue = result.catch(() => this.last);\n return result;\n }\n}\n\n/**\n * Build the ADR-036 sign message for a generic provider/Fred call.\n *\n * NOTE (security): the message currently scopes a token to a tenant + lease +\n * timestamp, but not to a specific HTTP operation. If the provider's replay\n * tracker is per-endpoint rather than global, a token issued for a read\n * endpoint (e.g. status) could be replayed against a mutating endpoint\n * (e.g. restart, update) within the 30 s replay window. Tightening this\n * requires a coordinated server change to also validate an operation scope —\n * do not change this format unilaterally without updating the provider/Fred\n * verifier in lockstep, or every auth call will fail.\n */\nexport function createSignMessage(\n tenant: string,\n leaseUuid: string,\n timestamp: number,\n): string {\n return `${tenant}:${leaseUuid}:${timestamp}`;\n}\n\nexport function createLeaseDataSignMessage(\n leaseUuid: string,\n metaHashHex: string,\n timestamp: number,\n): string {\n return `manifest lease data ${leaseUuid} ${metaHashHex} ${timestamp}`;\n}\n\nexport interface AuthTokenPayload {\n readonly tenant: string;\n readonly lease_uuid: string;\n readonly timestamp: number;\n readonly pub_key: string;\n readonly signature: string;\n readonly meta_hash?: string;\n}\n\nexport function createAuthToken(\n tenant: string,\n leaseUuid: string,\n timestamp: number,\n pubKey: string,\n signature: string,\n metaHashHex?: string,\n): string {\n const payload: AuthTokenPayload = {\n tenant,\n lease_uuid: leaseUuid,\n timestamp,\n pub_key: pubKey,\n signature,\n ...(metaHashHex !== undefined && { meta_hash: metaHashHex }),\n };\n return toBase64(new TextEncoder().encode(JSON.stringify(payload)));\n}\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,uBAAb,MAAkC;;
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../../src/http/auth.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\n\n/**\n * Produces unix-second timestamps guaranteed unique across calls.\n *\n * ADR-036 signing is deterministic, so tokens sharing the same timestamp\n * produce identical signatures. The provider's replay tracker rejects\n * duplicate signatures on protected endpoints (connection, restart, update).\n * We wait for the wall clock to advance rather than drifting into the future\n * (the provider enforces a 30 s max token age and 10 s max-future-skew).\n * A promise queue serializes concurrent callers.\n */\nexport class AuthTimestampTracker {\n private last = 0;\n private queue: Promise<number> = Promise.resolve(0);\n\n next(): Promise<number> {\n const result = this.queue.then(async () => {\n let now = Math.floor(Date.now() / 1000);\n while (now <= this.last) {\n // Cap sleep at 1 s so forward clock jumps (e.g. NTP) are\n // picked up quickly instead of waiting the full precomputed delay.\n const sleepMs = Math.min((this.last - now + 1) * 1000, 1000);\n await new Promise((resolve) => setTimeout(resolve, sleepMs));\n now = Math.floor(Date.now() / 1000);\n }\n this.last = now;\n return now;\n });\n this.queue = result.catch(() => this.last);\n return result;\n }\n}\n\n/**\n * Build the ADR-036 sign message for a generic provider/Fred call.\n *\n * NOTE (security): the message currently scopes a token to a tenant + lease +\n * timestamp, but not to a specific HTTP operation. If the provider's replay\n * tracker is per-endpoint rather than global, a token issued for a read\n * endpoint (e.g. status) could be replayed against a mutating endpoint\n * (e.g. restart, update) within the 30 s replay window. Tightening this\n * requires a coordinated server change to also validate an operation scope —\n * do not change this format unilaterally without updating the provider/Fred\n * verifier in lockstep, or every auth call will fail.\n */\nexport function createSignMessage(\n tenant: string,\n leaseUuid: string,\n timestamp: number,\n): string {\n return `${tenant}:${leaseUuid}:${timestamp}`;\n}\n\nexport function createLeaseDataSignMessage(\n leaseUuid: string,\n metaHashHex: string,\n timestamp: number,\n): string {\n return `manifest lease data ${leaseUuid} ${metaHashHex} ${timestamp}`;\n}\n\nexport interface AuthTokenPayload {\n readonly tenant: string;\n readonly lease_uuid: string;\n readonly timestamp: number;\n readonly pub_key: string;\n readonly signature: string;\n readonly meta_hash?: string;\n}\n\nexport function createAuthToken(\n tenant: string,\n leaseUuid: string,\n timestamp: number,\n pubKey: string,\n signature: string,\n metaHashHex?: string,\n): string {\n const payload: AuthTokenPayload = {\n tenant,\n lease_uuid: leaseUuid,\n timestamp,\n pub_key: pubKey,\n signature,\n ...(metaHashHex !== undefined && { meta_hash: metaHashHex }),\n };\n return toBase64(new TextEncoder().encode(JSON.stringify(payload)));\n}\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,uBAAb,MAAkC;;EAChC,KAAQ,OAAO;EACf,KAAQ,QAAyB,QAAQ,QAAQ,CAAC;;CAElD,OAAwB;EACtB,MAAM,SAAS,KAAK,MAAM,KAAK,YAAY;GACzC,IAAI,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;GACtC,OAAO,OAAO,KAAK,MAAM;IAGvB,MAAM,UAAU,KAAK,KAAK,KAAK,OAAO,MAAM,KAAK,KAAM,GAAI;IAC3D,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,OAAO,CAAC;IAC3D,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;GACpC;GACA,KAAK,OAAO;GACZ,OAAO;EACT,CAAC;EACD,KAAK,QAAQ,OAAO,YAAY,KAAK,IAAI;EACzC,OAAO;CACT;AACF;;;;;;;;;;;;;AAcA,SAAgB,kBACd,QACA,WACA,WACQ;CACR,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG;AACnC;AAEA,SAAgB,2BACd,WACA,aACA,WACQ;CACR,OAAO,uBAAuB,UAAU,GAAG,YAAY,GAAG;AAC5D;AAWA,SAAgB,gBACd,QACA,WACA,WACA,QACA,WACA,aACQ;CACR,MAAM,UAA4B;EAChC;EACA,YAAY;EACZ;EACA,SAAS;EACT;EACA,GAAI,gBAAgB,KAAA,KAAa,EAAE,WAAW,YAAY;CAC5D;CACA,OAAO,SAAS,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,OAAO,CAAC,CAAC;AACnE"}
|
package/dist/http/fred.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ interface PollOptions {
|
|
|
87
87
|
* or read `chainState` to distinguish from provider-reported terminal states.
|
|
88
88
|
*/
|
|
89
89
|
interface TerminalChainStateContext {
|
|
90
|
+
readonly lease_uuid?: string;
|
|
90
91
|
readonly providerUuid?: string;
|
|
91
92
|
readonly providerUrl?: string;
|
|
92
93
|
}
|
|
@@ -95,6 +96,17 @@ declare class TerminalChainStateError extends ProviderApiError {
|
|
|
95
96
|
readonly leaseUuid: string;
|
|
96
97
|
readonly providerUuid?: string;
|
|
97
98
|
readonly providerUrl?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Structured context for downstream classifiers (e.g. agent-core's
|
|
101
|
+
* classify-deploy-error). `lease_uuid` is always present so callers can name
|
|
102
|
+
* the affected lease without re-deriving it from the message; provider keys
|
|
103
|
+
* appear once `withContext` enriches the error.
|
|
104
|
+
*/
|
|
105
|
+
readonly details: {
|
|
106
|
+
readonly lease_uuid: string;
|
|
107
|
+
readonly provider_uuid?: string;
|
|
108
|
+
readonly provider_url?: string;
|
|
109
|
+
};
|
|
98
110
|
constructor(leaseUuid: string, chainState: TerminalChainLeaseState, context?: TerminalChainStateContext);
|
|
99
111
|
/**
|
|
100
112
|
* Returns a new instance with the same lease/state and the supplied context,
|
package/dist/http/fred.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fred.d.ts","names":[],"sources":["../../src/http/fred.ts"],"mappings":";;;;cAaa,QAAA;AAAA,UAEI,gBAAA;EAAA,SACN,IAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,GAAQ,
|
|
1
|
+
{"version":3,"file":"fred.d.ts","names":[],"sources":["../../src/http/fred.ts"],"mappings":"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"}
|
package/dist/http/fred.js
CHANGED
|
@@ -68,6 +68,11 @@ var TerminalChainStateError = class TerminalChainStateError extends ProviderApiE
|
|
|
68
68
|
this.leaseUuid = leaseUuid;
|
|
69
69
|
this.providerUuid = context?.providerUuid;
|
|
70
70
|
this.providerUrl = context?.providerUrl;
|
|
71
|
+
this.details = {
|
|
72
|
+
lease_uuid: context?.lease_uuid ?? leaseUuid,
|
|
73
|
+
provider_uuid: context?.providerUuid,
|
|
74
|
+
provider_url: context?.providerUrl
|
|
75
|
+
};
|
|
71
76
|
Object.setPrototypeOf(this, TerminalChainStateError.prototype);
|
|
72
77
|
}
|
|
73
78
|
/**
|
package/dist/http/fred.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fred.js","names":[],"sources":["../../src/http/fred.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\nimport {\n LeaseState,\n leaseStateFromJSON,\n logger,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n checkedFetch,\n ProviderApiError,\n parseJsonResponse,\n validateProviderUrl,\n} from './provider.js';\n\nexport const MAX_TAIL = 1000;\n\nexport interface FredInstanceInfo {\n readonly name: string;\n readonly status: string;\n readonly ports?: Record<string, number>;\n readonly fqdn?: string;\n}\n\nexport interface FredServiceStatus {\n readonly instances: readonly FredInstanceInfo[];\n}\n\nexport interface FredLeaseStatus {\n readonly state: LeaseState;\n readonly provision_status?: string;\n readonly phase?: string;\n readonly steps?: Record<string, string>;\n readonly instances?: readonly FredInstanceInfo[];\n readonly endpoints?: Record<string, string>;\n readonly last_error?: string;\n readonly fail_count?: number;\n readonly created_at?: string;\n readonly services?: Record<string, FredServiceStatus>;\n}\n\n/** Raw wire shape before LeaseState conversion */\ninterface RawLeaseStatus extends Omit<FredLeaseStatus, 'state'> {\n readonly state: string;\n}\n\nexport async function getLeaseStatus(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/status`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n const raw = await parseJsonResponse<RawLeaseStatus>(res, url);\n const state = leaseStateFromJSON(raw.state);\n if (state === LeaseState.UNRECOGNIZED) {\n logger.warn(\n `[getLeaseStatus] Unrecognized lease state \"${raw.state}\" for lease ${leaseUuid}. ` +\n 'The provider may be running a newer version than the client supports.',\n );\n }\n return { ...raw, state };\n}\n\nexport interface FredLeaseLogs {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly logs: Record<string, string>;\n}\n\nexport async function getLeaseLogs(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseLogs> {\n const validated = validateProviderUrl(providerUrl);\n const cappedTail = tail !== undefined ? Math.min(tail, MAX_TAIL) : undefined;\n const qs = cappedTail !== undefined ? `?tail=${cappedTail}` : '';\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/logs${qs}`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseLogs>(res, url);\n}\n\nexport interface FredLeaseProvision {\n readonly status: string;\n readonly fail_count: number;\n /**\n * Set only when the most recent provisioning attempt failed. The Fred\n * provider omits the field on success, so the optional marker matches\n * the wire shape (and matches the same field on FredLeaseStatus above).\n */\n readonly last_error?: string;\n}\n\nexport async function getLeaseProvision(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseProvision> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/provision`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseProvision>(res, url);\n}\n\nexport interface FredActionResponse {\n readonly status: string;\n}\n\nexport async function restartLease(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/restart`;\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport async function updateLease(\n providerUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/update`;\n // The provider expects JSON with a base64-encoded payload (Go []byte field).\n const b64 = toBase64(payload);\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ payload: b64 }),\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport interface FredLeaseRelease {\n readonly version: number;\n readonly image: string;\n readonly status: string;\n readonly created_at: string;\n readonly error?: string;\n readonly manifest?: string;\n}\n\nexport interface FredLeaseReleases {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly releases: readonly FredLeaseRelease[];\n}\n\nexport async function getLeaseReleases(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseReleases> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/releases`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseReleases>(res, url);\n}\n\nexport interface FredLeaseInfo {\n readonly host: string;\n readonly ports?: Record<string, unknown>;\n}\n\nexport async function getLeaseInfo(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseInfo> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/info`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseInfo>(res, url);\n}\n\nexport type TerminalChainLeaseState = 'closed' | 'rejected' | 'expired';\n\nexport interface TerminalChainState {\n readonly state: TerminalChainLeaseState;\n}\n\nexport interface PollOptions {\n readonly intervalMs?: number;\n readonly timeoutMs?: number;\n readonly abortSignal?: AbortSignal;\n readonly onProgress?: (status: FredLeaseStatus) => void;\n /** Runs once per iteration before the provider is queried. Non-null return throws; errors propagate. */\n readonly checkChainState?: () => Promise<TerminalChainState | null>;\n}\n\nconst CHAIN_STATE_TO_LEASE_STATE: Record<TerminalChainLeaseState, LeaseState> =\n {\n closed: LeaseState.LEASE_STATE_CLOSED,\n rejected: LeaseState.LEASE_STATE_REJECTED,\n expired: LeaseState.LEASE_STATE_EXPIRED,\n };\n\nfunction leaseStateName(state: LeaseState): string {\n return LeaseState[state] ?? String(state);\n}\n\n/**\n * Thrown by pollLeaseUntilReady when the caller's checkChainState callback\n * reports a terminal lease state on-chain. Extends ProviderApiError so\n * existing catchers keep working; use `instanceof TerminalChainStateError`\n * or read `chainState` to distinguish from provider-reported terminal states.\n */\nexport interface TerminalChainStateContext {\n readonly providerUuid?: string;\n readonly providerUrl?: string;\n}\n\nexport class TerminalChainStateError extends ProviderApiError {\n public readonly chainState: TerminalChainLeaseState;\n public readonly leaseUuid: string;\n public readonly providerUuid?: string;\n public readonly providerUrl?: string;\n\n constructor(\n leaseUuid: string,\n chainState: TerminalChainLeaseState,\n context?: TerminalChainStateContext,\n ) {\n const mapped = CHAIN_STATE_TO_LEASE_STATE[chainState];\n super(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(mapped)} on chain`,\n );\n this.name = 'TerminalChainStateError';\n this.chainState = chainState;\n this.leaseUuid = leaseUuid;\n this.providerUuid = context?.providerUuid;\n this.providerUrl = context?.providerUrl;\n Object.setPrototypeOf(this, TerminalChainStateError.prototype);\n }\n\n /**\n * Returns a new instance with the same lease/state and the supplied context,\n * preserving the original stack trace so debugging points to where the\n * terminal state was first detected.\n */\n withContext(context: TerminalChainStateContext): TerminalChainStateError {\n const enriched = new TerminalChainStateError(\n this.leaseUuid,\n this.chainState,\n context,\n );\n if (this.stack) enriched.stack = this.stack;\n return enriched;\n }\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n signal.throwIfAborted();\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(\n signal.reason ??\n new DOMException('The operation was aborted', 'AbortError'),\n );\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport async function pollLeaseUntilReady(\n providerUrl: string,\n leaseUuid: string,\n authToken: string | (() => Promise<string>),\n opts: PollOptions = {},\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const {\n intervalMs = 3_000,\n timeoutMs = 120_000,\n abortSignal,\n onProgress,\n checkChainState,\n } = opts;\n const deadline = Date.now() + timeoutMs;\n let lastState: LeaseState | undefined;\n\n while (Date.now() < deadline) {\n abortSignal?.throwIfAborted();\n if (checkChainState) {\n const chainState = await checkChainState();\n if (chainState) {\n throw new TerminalChainStateError(leaseUuid, chainState.state);\n }\n abortSignal?.throwIfAborted();\n }\n const token =\n typeof authToken === 'function' ? await authToken() : authToken;\n abortSignal?.throwIfAborted();\n const status = await getLeaseStatus(providerUrl, leaseUuid, token, fetchFn);\n lastState = status.state;\n onProgress?.(status);\n switch (status.state) {\n case LeaseState.LEASE_STATE_ACTIVE:\n return status;\n case LeaseState.LEASE_STATE_PENDING:\n break;\n case LeaseState.LEASE_STATE_CLOSED:\n case LeaseState.LEASE_STATE_REJECTED:\n case LeaseState.LEASE_STATE_EXPIRED:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(status.state)}`,\n );\n default:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} returned unexpected state ${leaseStateName(status.state)}`,\n );\n }\n await abortableSleep(intervalMs, abortSignal);\n }\n\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} poll timed out after ${timeoutMs}ms (last state: ${lastState !== undefined ? leaseStateName(lastState) : 'unknown'})`,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fred.js","names":[],"sources":["../../src/http/fred.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\nimport {\n LeaseState,\n leaseStateFromJSON,\n logger,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n checkedFetch,\n ProviderApiError,\n parseJsonResponse,\n validateProviderUrl,\n} from './provider.js';\n\nexport const MAX_TAIL = 1000;\n\nexport interface FredInstanceInfo {\n readonly name: string;\n readonly status: string;\n readonly ports?: Record<string, number>;\n readonly fqdn?: string;\n}\n\nexport interface FredServiceStatus {\n readonly instances: readonly FredInstanceInfo[];\n}\n\nexport interface FredLeaseStatus {\n readonly state: LeaseState;\n readonly provision_status?: string;\n readonly phase?: string;\n readonly steps?: Record<string, string>;\n readonly instances?: readonly FredInstanceInfo[];\n readonly endpoints?: Record<string, string>;\n readonly last_error?: string;\n readonly fail_count?: number;\n readonly created_at?: string;\n readonly services?: Record<string, FredServiceStatus>;\n}\n\n/** Raw wire shape before LeaseState conversion */\ninterface RawLeaseStatus extends Omit<FredLeaseStatus, 'state'> {\n readonly state: string;\n}\n\nexport async function getLeaseStatus(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/status`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n const raw = await parseJsonResponse<RawLeaseStatus>(res, url);\n const state = leaseStateFromJSON(raw.state);\n if (state === LeaseState.UNRECOGNIZED) {\n logger.warn(\n `[getLeaseStatus] Unrecognized lease state \"${raw.state}\" for lease ${leaseUuid}. ` +\n 'The provider may be running a newer version than the client supports.',\n );\n }\n return { ...raw, state };\n}\n\nexport interface FredLeaseLogs {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly logs: Record<string, string>;\n}\n\nexport async function getLeaseLogs(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseLogs> {\n const validated = validateProviderUrl(providerUrl);\n const cappedTail = tail !== undefined ? Math.min(tail, MAX_TAIL) : undefined;\n const qs = cappedTail !== undefined ? `?tail=${cappedTail}` : '';\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/logs${qs}`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseLogs>(res, url);\n}\n\nexport interface FredLeaseProvision {\n readonly status: string;\n readonly fail_count: number;\n /**\n * Set only when the most recent provisioning attempt failed. The Fred\n * provider omits the field on success, so the optional marker matches\n * the wire shape (and matches the same field on FredLeaseStatus above).\n */\n readonly last_error?: string;\n}\n\nexport async function getLeaseProvision(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseProvision> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/provision`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseProvision>(res, url);\n}\n\nexport interface FredActionResponse {\n readonly status: string;\n}\n\nexport async function restartLease(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/restart`;\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport async function updateLease(\n providerUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/update`;\n // The provider expects JSON with a base64-encoded payload (Go []byte field).\n const b64 = toBase64(payload);\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ payload: b64 }),\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport interface FredLeaseRelease {\n readonly version: number;\n readonly image: string;\n readonly status: string;\n readonly created_at: string;\n readonly error?: string;\n readonly manifest?: string;\n}\n\nexport interface FredLeaseReleases {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly releases: readonly FredLeaseRelease[];\n}\n\nexport async function getLeaseReleases(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseReleases> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/releases`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseReleases>(res, url);\n}\n\nexport interface FredLeaseInfo {\n readonly host: string;\n readonly ports?: Record<string, unknown>;\n}\n\nexport async function getLeaseInfo(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseInfo> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/info`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseInfo>(res, url);\n}\n\nexport type TerminalChainLeaseState = 'closed' | 'rejected' | 'expired';\n\nexport interface TerminalChainState {\n readonly state: TerminalChainLeaseState;\n}\n\nexport interface PollOptions {\n readonly intervalMs?: number;\n readonly timeoutMs?: number;\n readonly abortSignal?: AbortSignal;\n readonly onProgress?: (status: FredLeaseStatus) => void;\n /** Runs once per iteration before the provider is queried. Non-null return throws; errors propagate. */\n readonly checkChainState?: () => Promise<TerminalChainState | null>;\n}\n\nconst CHAIN_STATE_TO_LEASE_STATE: Record<TerminalChainLeaseState, LeaseState> =\n {\n closed: LeaseState.LEASE_STATE_CLOSED,\n rejected: LeaseState.LEASE_STATE_REJECTED,\n expired: LeaseState.LEASE_STATE_EXPIRED,\n };\n\nfunction leaseStateName(state: LeaseState): string {\n return LeaseState[state] ?? String(state);\n}\n\n/**\n * Thrown by pollLeaseUntilReady when the caller's checkChainState callback\n * reports a terminal lease state on-chain. Extends ProviderApiError so\n * existing catchers keep working; use `instanceof TerminalChainStateError`\n * or read `chainState` to distinguish from provider-reported terminal states.\n */\nexport interface TerminalChainStateContext {\n readonly lease_uuid?: string;\n readonly providerUuid?: string;\n readonly providerUrl?: string;\n}\n\nexport class TerminalChainStateError extends ProviderApiError {\n public readonly chainState: TerminalChainLeaseState;\n public readonly leaseUuid: string;\n public readonly providerUuid?: string;\n public readonly providerUrl?: string;\n /**\n * Structured context for downstream classifiers (e.g. agent-core's\n * classify-deploy-error). `lease_uuid` is always present so callers can name\n * the affected lease without re-deriving it from the message; provider keys\n * appear once `withContext` enriches the error.\n */\n public readonly details: {\n readonly lease_uuid: string;\n readonly provider_uuid?: string;\n readonly provider_url?: string;\n };\n\n constructor(\n leaseUuid: string,\n chainState: TerminalChainLeaseState,\n context?: TerminalChainStateContext,\n ) {\n const mapped = CHAIN_STATE_TO_LEASE_STATE[chainState];\n super(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(mapped)} on chain`,\n );\n this.name = 'TerminalChainStateError';\n this.chainState = chainState;\n this.leaseUuid = leaseUuid;\n this.providerUuid = context?.providerUuid;\n this.providerUrl = context?.providerUrl;\n this.details = {\n lease_uuid: context?.lease_uuid ?? leaseUuid,\n provider_uuid: context?.providerUuid,\n provider_url: context?.providerUrl,\n };\n Object.setPrototypeOf(this, TerminalChainStateError.prototype);\n }\n\n /**\n * Returns a new instance with the same lease/state and the supplied context,\n * preserving the original stack trace so debugging points to where the\n * terminal state was first detected.\n */\n withContext(context: TerminalChainStateContext): TerminalChainStateError {\n const enriched = new TerminalChainStateError(\n this.leaseUuid,\n this.chainState,\n context,\n );\n if (this.stack) enriched.stack = this.stack;\n return enriched;\n }\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n signal.throwIfAborted();\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(\n signal.reason ??\n new DOMException('The operation was aborted', 'AbortError'),\n );\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport async function pollLeaseUntilReady(\n providerUrl: string,\n leaseUuid: string,\n authToken: string | (() => Promise<string>),\n opts: PollOptions = {},\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const {\n intervalMs = 3_000,\n timeoutMs = 120_000,\n abortSignal,\n onProgress,\n checkChainState,\n } = opts;\n const deadline = Date.now() + timeoutMs;\n let lastState: LeaseState | undefined;\n\n while (Date.now() < deadline) {\n abortSignal?.throwIfAborted();\n if (checkChainState) {\n const chainState = await checkChainState();\n if (chainState) {\n throw new TerminalChainStateError(leaseUuid, chainState.state);\n }\n abortSignal?.throwIfAborted();\n }\n const token =\n typeof authToken === 'function' ? await authToken() : authToken;\n abortSignal?.throwIfAborted();\n const status = await getLeaseStatus(providerUrl, leaseUuid, token, fetchFn);\n lastState = status.state;\n onProgress?.(status);\n switch (status.state) {\n case LeaseState.LEASE_STATE_ACTIVE:\n return status;\n case LeaseState.LEASE_STATE_PENDING:\n break;\n case LeaseState.LEASE_STATE_CLOSED:\n case LeaseState.LEASE_STATE_REJECTED:\n case LeaseState.LEASE_STATE_EXPIRED:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(status.state)}`,\n );\n default:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} returned unexpected state ${leaseStateName(status.state)}`,\n );\n }\n await abortableSleep(intervalMs, abortSignal);\n }\n\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} poll timed out after ${timeoutMs}ms (last state: ${lastState !== undefined ? leaseStateName(lastState) : 'unknown'})`,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/http/provider.ts"],"mappings":";cAAa,gBAAA,SAAyB,
|
|
1
|
+
{"version":3,"file":"provider.d.ts","names":[],"sources":["../../src/http/provider.ts"],"mappings":";cAAa,gBAAA,SAAyB,KAAK;EAAA,SACzB,MAAA;cAEJ,MAAA,UAAgB,OAAA;AAAA;AAAA,iBAUd,mBAAA,CAAoB,GAAW;AAAA,iBAwBzB,YAAA,CACpB,GAAA,UACA,IAAA,GAAO,WAAA,EACP,SAAA,WACA,OAAA,UAAgB,UAAA,CAAW,KAAA,GAC1B,OAAA,CAAQ,QAAA;AAAA,iBAyEW,iBAAA,IACpB,GAAA,EAAK,QAAA,EACL,GAAA,WACC,OAAA,CAAQ,CAAA;AAAA,UAcM,sBAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;EAAA,SACA,MAAA;IAAA,SACE,KAAA;MAAA,SAAmB,MAAA;IAAA;EAAA;AAAA;AAAA,iBAIV,iBAAA,CACpB,cAAA,UACA,SAAA,WACA,OAAA,UAAiB,UAAA,CAAW,KAAA,GAC3B,OAAA,CAAQ,sBAAA;AAAA,UAOM,YAAA;EAAA,SACN,cAAA;EAAA,SACA,YAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,GAAQ,MAAM;EAAA,SACd,IAAA;AAAA;AAAA,UAGM,wBAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA,GAAQ,MAAA;EAAA,SACR,SAAA,YAAqB,YAAY;AAAA;AAAA,UAG3B,iBAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA,GAAQ,MAAA;EAAA,SACR,SAAA,YAAqB,YAAA;EAAA,SACrB,QAAA;EAAA,SACA,QAAA,GAAW,MAAA;EAAA,SACX,QAAA,GAAW,MAAA,SAAe,wBAAA;AAAA;AAAA,UAGpB,uBAAA;EAAA,SACN,UAAA;EAAA,SACA,MAAA;EAAA,SACA,aAAA;EAAA,SACA,UAAA,EAAY,iBAAiB;AAAA;AAAA,iBAGlB,sBAAA,CACpB,cAAA,UACA,SAAA,UACA,SAAA,UACA,OAAA,UAAiB,UAAA,CAAW,KAAA,GAC3B,OAAA,CAAQ,uBAAA;AAAA,iBAcW,eAAA,CACpB,cAAA,UACA,SAAA,UACA,OAAA,EAAS,UAAA,EACT,SAAA,UACA,OAAA,UAAiB,UAAA,CAAW,KAAA,EAC5B,WAAA,GAAc,WAAA,GACb,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":[],"sources":["../../src/http/provider.ts"],"sourcesContent":["export class ProviderApiError extends Error {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'ProviderApiError';\n this.status = status;\n Object.setPrototypeOf(this, ProviderApiError.prototype);\n }\n}\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function validateProviderUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new ProviderApiError(0, `Invalid provider URL: ${url}`);\n }\n\n if (parsed.protocol === 'https:') {\n return url.replace(/\\/+$/, '');\n }\n\n if (parsed.protocol === 'http:' && LOCALHOST_HOSTS.has(parsed.hostname)) {\n return url.replace(/\\/+$/, '');\n }\n\n throw new ProviderApiError(\n 0,\n `Provider URL must use HTTPS (or HTTP for localhost): ${url}`,\n );\n}\n\nconst DEFAULT_FETCH_TIMEOUT_MS = 30_000;\n\nexport async function checkedFetch(\n url: string,\n init?: RequestInit,\n timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS,\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n): Promise<Response> {\n const callerSignal = init?.signal ?? undefined;\n\n // Compose the caller's signal with an internal timeout so callers cannot\n // accidentally disable the safety net by supplying their own signal.\n const composed = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let callerAbortHandler: (() => void) | undefined;\n\n if (callerSignal?.aborted) {\n composed.abort(callerSignal.reason);\n } else if (callerSignal) {\n callerAbortHandler = () => {\n // Clear the timer so a delayed fetch rejection can't be misclassified\n // as a timeout after the caller already cancelled.\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n composed.abort(callerSignal.reason);\n };\n callerSignal.addEventListener('abort', callerAbortHandler, { once: true });\n }\n if (timeoutMs > 0 && !composed.signal.aborted) {\n timer = setTimeout(() => {\n // Defensive: if the caller already aborted, don't flip timedOut.\n if (composed.signal.aborted) return;\n timedOut = true;\n composed.abort();\n }, timeoutMs);\n }\n\n let res: Response;\n try {\n // Don't even dispatch fetch if the caller's signal is already aborted.\n composed.signal.throwIfAborted();\n res = await fetchFn(url, { ...init, signal: composed.signal });\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timedOut) {\n throw new ProviderApiError(\n 0,\n `Request to ${url} timed out after ${timeoutMs}ms`,\n );\n }\n // Surface the caller's original abort reason (e.g. `new Error('cancelled')`)\n // rather than the fetch-internal DOMException AbortError.\n throw composed.signal.reason;\n }\n if (composed.signal.aborted && !timedOut) throw composed.signal.reason;\n throw new ProviderApiError(\n 0,\n `Network request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n if (callerAbortHandler && callerSignal) {\n callerSignal.removeEventListener('abort', callerAbortHandler);\n }\n }\n if (!res.ok) {\n const body = await res\n .text()\n .catch(\n (readErr: unknown) =>\n `[body read failed: ${readErr instanceof Error ? readErr.message : String(readErr)}]`,\n );\n throw new ProviderApiError(res.status, body || `HTTP ${res.status}`);\n }\n return res;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n url: string,\n): Promise<T> {\n const text = await res.text();\n try {\n return JSON.parse(text) as T;\n } catch (parseErr) {\n const reason =\n parseErr instanceof Error ? parseErr.message : 'parse failed';\n throw new ProviderApiError(\n res.status,\n `Invalid JSON from ${url} (${reason}): ${text.slice(0, 200)}`,\n );\n }\n}\n\nexport interface ProviderHealthResponse {\n readonly status: string;\n readonly provider_uuid: string;\n readonly checks?: {\n readonly chain?: { readonly status: string };\n };\n}\n\nexport async function getProviderHealth(\n providerApiUrl: string,\n timeoutMs = 5_000,\n fetchFn?: typeof globalThis.fetch,\n): Promise<ProviderHealthResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/health`;\n const res = await checkedFetch(url, undefined, timeoutMs, fetchFn);\n return await parseJsonResponse<ProviderHealthResponse>(res, url);\n}\n\nexport interface InstanceInfo {\n readonly instance_index: number;\n readonly container_id: string;\n readonly image: string;\n readonly status: string;\n readonly ports?: Record<string, unknown>;\n readonly fqdn?: string;\n}\n\nexport interface ServiceConnectionDetails {\n readonly host?: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n}\n\nexport interface ConnectionDetails {\n readonly host: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n readonly protocol?: string;\n readonly metadata?: Record<string, string>;\n readonly services?: Record<string, ServiceConnectionDetails>;\n}\n\nexport interface LeaseConnectionResponse {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly connection: ConnectionDetails;\n}\n\nexport async function getLeaseConnectionInfo(\n providerApiUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<LeaseConnectionResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/connection`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<LeaseConnectionResponse>(res, url);\n}\n\nexport async function uploadLeaseData(\n providerApiUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n abortSignal?: AbortSignal,\n): Promise<void> {\n const validated = validateProviderUrl(providerApiUrl);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: payload,\n };\n if (abortSignal) {\n init.signal = abortSignal;\n }\n await checkedFetch(\n `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/data`,\n init,\n undefined,\n fetchFn,\n );\n}\n"],"mappings":";AAAA,IAAa,mBAAb,MAAa,yBAAyB,MAAM;CAG1C,YAAY,QAAgB,SAAiB;
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../../src/http/provider.ts"],"sourcesContent":["export class ProviderApiError extends Error {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'ProviderApiError';\n this.status = status;\n Object.setPrototypeOf(this, ProviderApiError.prototype);\n }\n}\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function validateProviderUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new ProviderApiError(0, `Invalid provider URL: ${url}`);\n }\n\n if (parsed.protocol === 'https:') {\n return url.replace(/\\/+$/, '');\n }\n\n if (parsed.protocol === 'http:' && LOCALHOST_HOSTS.has(parsed.hostname)) {\n return url.replace(/\\/+$/, '');\n }\n\n throw new ProviderApiError(\n 0,\n `Provider URL must use HTTPS (or HTTP for localhost): ${url}`,\n );\n}\n\nconst DEFAULT_FETCH_TIMEOUT_MS = 30_000;\n\nexport async function checkedFetch(\n url: string,\n init?: RequestInit,\n timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS,\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n): Promise<Response> {\n const callerSignal = init?.signal ?? undefined;\n\n // Compose the caller's signal with an internal timeout so callers cannot\n // accidentally disable the safety net by supplying their own signal.\n const composed = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let callerAbortHandler: (() => void) | undefined;\n\n if (callerSignal?.aborted) {\n composed.abort(callerSignal.reason);\n } else if (callerSignal) {\n callerAbortHandler = () => {\n // Clear the timer so a delayed fetch rejection can't be misclassified\n // as a timeout after the caller already cancelled.\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n composed.abort(callerSignal.reason);\n };\n callerSignal.addEventListener('abort', callerAbortHandler, { once: true });\n }\n if (timeoutMs > 0 && !composed.signal.aborted) {\n timer = setTimeout(() => {\n // Defensive: if the caller already aborted, don't flip timedOut.\n if (composed.signal.aborted) return;\n timedOut = true;\n composed.abort();\n }, timeoutMs);\n }\n\n let res: Response;\n try {\n // Don't even dispatch fetch if the caller's signal is already aborted.\n composed.signal.throwIfAborted();\n res = await fetchFn(url, { ...init, signal: composed.signal });\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timedOut) {\n throw new ProviderApiError(\n 0,\n `Request to ${url} timed out after ${timeoutMs}ms`,\n );\n }\n // Surface the caller's original abort reason (e.g. `new Error('cancelled')`)\n // rather than the fetch-internal DOMException AbortError.\n throw composed.signal.reason;\n }\n if (composed.signal.aborted && !timedOut) throw composed.signal.reason;\n throw new ProviderApiError(\n 0,\n `Network request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n if (callerAbortHandler && callerSignal) {\n callerSignal.removeEventListener('abort', callerAbortHandler);\n }\n }\n if (!res.ok) {\n const body = await res\n .text()\n .catch(\n (readErr: unknown) =>\n `[body read failed: ${readErr instanceof Error ? readErr.message : String(readErr)}]`,\n );\n throw new ProviderApiError(res.status, body || `HTTP ${res.status}`);\n }\n return res;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n url: string,\n): Promise<T> {\n const text = await res.text();\n try {\n return JSON.parse(text) as T;\n } catch (parseErr) {\n const reason =\n parseErr instanceof Error ? parseErr.message : 'parse failed';\n throw new ProviderApiError(\n res.status,\n `Invalid JSON from ${url} (${reason}): ${text.slice(0, 200)}`,\n );\n }\n}\n\nexport interface ProviderHealthResponse {\n readonly status: string;\n readonly provider_uuid: string;\n readonly checks?: {\n readonly chain?: { readonly status: string };\n };\n}\n\nexport async function getProviderHealth(\n providerApiUrl: string,\n timeoutMs = 5_000,\n fetchFn?: typeof globalThis.fetch,\n): Promise<ProviderHealthResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/health`;\n const res = await checkedFetch(url, undefined, timeoutMs, fetchFn);\n return await parseJsonResponse<ProviderHealthResponse>(res, url);\n}\n\nexport interface InstanceInfo {\n readonly instance_index: number;\n readonly container_id: string;\n readonly image: string;\n readonly status: string;\n readonly ports?: Record<string, unknown>;\n readonly fqdn?: string;\n}\n\nexport interface ServiceConnectionDetails {\n readonly host?: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n}\n\nexport interface ConnectionDetails {\n readonly host: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n readonly protocol?: string;\n readonly metadata?: Record<string, string>;\n readonly services?: Record<string, ServiceConnectionDetails>;\n}\n\nexport interface LeaseConnectionResponse {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly connection: ConnectionDetails;\n}\n\nexport async function getLeaseConnectionInfo(\n providerApiUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<LeaseConnectionResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/connection`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<LeaseConnectionResponse>(res, url);\n}\n\nexport async function uploadLeaseData(\n providerApiUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n abortSignal?: AbortSignal,\n): Promise<void> {\n const validated = validateProviderUrl(providerApiUrl);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: payload,\n };\n if (abortSignal) {\n init.signal = abortSignal;\n }\n await checkedFetch(\n `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/data`,\n init,\n undefined,\n fetchFn,\n );\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { appStatus } from "./tools/appStatus.js";
|
|
|
6
6
|
import { browseCatalog, mapWithConcurrency } from "./tools/browseCatalog.js";
|
|
7
7
|
import { BuildManifestPreviewInput, BuildManifestPreviewResult, ManifestPreviewServiceInput, buildManifestPreview } from "./tools/buildManifestPreview.js";
|
|
8
8
|
import { CheckDeploymentReadinessInput, CheckDeploymentReadinessResult, SkuSummary, checkDeploymentReadiness } from "./tools/checkDeploymentReadiness.js";
|
|
9
|
-
import {
|
|
9
|
+
import { DeployAppResult, DeployManifestInput, DeployManifestOptions, SkuSelector, deployManifest } from "./tools/deployManifest.js";
|
|
10
|
+
import { DeployAppInput, ServiceConfig, deployApp } from "./tools/deployApp.js";
|
|
10
11
|
import { fetchActiveLease } from "./tools/fetchActiveLease.js";
|
|
11
12
|
import { getAppLogs } from "./tools/getLogs.js";
|
|
12
13
|
import { resolveProviderUrl } from "./tools/resolveLeaseProvider.js";
|
|
@@ -29,5 +30,5 @@ declare class FredMCPServer {
|
|
|
29
30
|
}
|
|
30
31
|
declare function createMnemonicFredServer(config: MnemonicServerConfig): Promise<FredMCPServer>;
|
|
31
32
|
//#endregion
|
|
32
|
-
export { AuthTimestampTracker, type AuthTokenPayload, type BuildManifestOptions, type BuildManifestPreviewInput, type BuildManifestPreviewResult, type CheckDeploymentReadinessInput, type CheckDeploymentReadinessResult, type ConnectionDetails, type DeployAppInput, type DeployAppResult, type FredActionResponse, type FredInstanceInfo, type FredLeaseInfo, type FredLeaseLogs, type FredLeaseProvision, type FredLeaseRelease, type FredLeaseReleases, type FredLeaseStatus, FredMCPServer, type FredServiceStatus, INFRASTRUCTURE_ERROR_CODES, type InstanceInfo, type LeaseConnectionResponse, MAX_TAIL, type ManifestFormat, ManifestMCPError, ManifestMCPErrorCode, type ManifestMCPServerOptions, type ManifestPreviewServiceInput, type ManifestValidationResult, type PollOptions, ProviderApiError, type ProviderHealthResponse, type ServiceConfig, type ServiceConnectionDetails, type SkuSummary, type TerminalChainLeaseState, type TerminalChainState, type TerminalChainStateContext, TerminalChainStateError, type WaitForAppReadyOptions, type WaitForAppReadyResult, appStatus, browseCatalog, buildManifest, buildManifestPreview, buildStackManifest, checkDeploymentReadiness, checkedFetch, createAuthToken, createLeaseDataSignMessage, createMnemonicFredServer, createSignMessage, deployApp, deriveAppNameFromImage, fetchActiveLease, getAppLogs, getLeaseConnectionInfo, getLeaseInfo, getLeaseLogs, getLeaseProvision, getLeaseReleases, getLeaseStatus, getProviderHealth, getServiceNames, isStackManifest, mapWithConcurrency, mergeManifest, metaHashHex, normalizePorts, parseStackManifest, pollLeaseUntilReady, resolveProviderUrl, restartApp, restartLease, updateApp, updateLease, uploadLeaseData, validateManifest, validateProviderUrl, validateServiceName, waitForAppReady };
|
|
33
|
+
export { AuthTimestampTracker, type AuthTokenPayload, type BuildManifestOptions, type BuildManifestPreviewInput, type BuildManifestPreviewResult, type CheckDeploymentReadinessInput, type CheckDeploymentReadinessResult, type ConnectionDetails, type DeployAppInput, type DeployAppResult, type DeployManifestInput, type DeployManifestOptions, type FredActionResponse, type FredInstanceInfo, type FredLeaseInfo, type FredLeaseLogs, type FredLeaseProvision, type FredLeaseRelease, type FredLeaseReleases, type FredLeaseStatus, FredMCPServer, type FredServiceStatus, INFRASTRUCTURE_ERROR_CODES, type InstanceInfo, type LeaseConnectionResponse, MAX_TAIL, type ManifestFormat, ManifestMCPError, ManifestMCPErrorCode, type ManifestMCPServerOptions, type ManifestPreviewServiceInput, type ManifestValidationResult, type PollOptions, ProviderApiError, type ProviderHealthResponse, type ServiceConfig, type ServiceConnectionDetails, type SkuSelector, type SkuSummary, type TerminalChainLeaseState, type TerminalChainState, type TerminalChainStateContext, TerminalChainStateError, type WaitForAppReadyOptions, type WaitForAppReadyResult, appStatus, browseCatalog, buildManifest, buildManifestPreview, buildStackManifest, checkDeploymentReadiness, checkedFetch, createAuthToken, createLeaseDataSignMessage, createMnemonicFredServer, createSignMessage, deployApp, deployManifest, deriveAppNameFromImage, fetchActiveLease, getAppLogs, getLeaseConnectionInfo, getLeaseInfo, getLeaseLogs, getLeaseProvision, getLeaseReleases, getLeaseStatus, getProviderHealth, getServiceNames, isStackManifest, mapWithConcurrency, mergeManifest, metaHashHex, normalizePorts, parseStackManifest, pollLeaseUntilReady, resolveProviderUrl, restartApp, restartLease, updateApp, updateLease, uploadLeaseData, validateManifest, validateProviderUrl, validateServiceName, waitForAppReady };
|
|
33
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAyHa,aAAA;EAAA,QACH,SAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,UAAA;cAEI,OAAA,EAAS,0BAAA;EAiDrB,SAAA,IAAa,MAAA;EAIb,gBAAA,IAAoB,mBAAA;EAIpB,UAAA;AAAA;AAAA,iBAKc,wBAAA,CACd,MAAA,EAAQ,oBAAA,GACP,OAAA,CAAQ,aAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthTimestampTracker, createAuthToken, createLeaseDataSignMessage, createSignMessage } from "./http/auth.js";
|
|
2
2
|
import { AuthTokenService } from "./http/auth-token-service.js";
|
|
3
|
+
import { resolveGuardedFetch } from "./server/fetch-gate.js";
|
|
3
4
|
import { registerPrompts } from "./server/register-prompts.js";
|
|
4
5
|
import { registerResources } from "./server/register-resources.js";
|
|
5
6
|
import { ProviderApiError, checkedFetch, getLeaseConnectionInfo, getProviderHealth, uploadLeaseData, validateProviderUrl } from "./http/provider.js";
|
|
@@ -10,6 +11,7 @@ import { browseCatalog, mapWithConcurrency } from "./tools/browseCatalog.js";
|
|
|
10
11
|
import { buildManifest, buildStackManifest, deriveAppNameFromImage, getServiceNames, isStackManifest, mergeManifest, metaHashHex, normalizePorts, parseStackManifest, validateManifest, validateServiceName } from "./manifest.js";
|
|
11
12
|
import { buildManifestPreview } from "./tools/buildManifestPreview.js";
|
|
12
13
|
import { checkDeploymentReadiness } from "./tools/checkDeploymentReadiness.js";
|
|
14
|
+
import { deployManifest } from "./tools/deployManifest.js";
|
|
13
15
|
import { deployApp } from "./tools/deployApp.js";
|
|
14
16
|
import { fetchActiveLease } from "./tools/fetchActiveLease.js";
|
|
15
17
|
import { getAppLogs } from "./tools/getLogs.js";
|
|
@@ -34,11 +36,13 @@ var FredMCPServer = class {
|
|
|
34
36
|
resources: {},
|
|
35
37
|
prompts: {}
|
|
36
38
|
} });
|
|
39
|
+
const fetchFn = resolveGuardedFetch(typeof process !== "undefined" ? process.env.MANIFEST_FRED_FETCH_GUARDED : void 0, typeof process !== "undefined" && !!process.versions?.node);
|
|
37
40
|
registerTools({
|
|
38
41
|
mcpServer: this.mcpServer,
|
|
39
42
|
clientManager: this.clientManager,
|
|
40
43
|
walletProvider: this.walletProvider,
|
|
41
|
-
authTokens: this.authTokens
|
|
44
|
+
authTokens: this.authTokens,
|
|
45
|
+
fetchFn
|
|
42
46
|
});
|
|
43
47
|
registerResources({
|
|
44
48
|
mcpServer: this.mcpServer,
|
|
@@ -61,6 +65,6 @@ function createMnemonicFredServer(config) {
|
|
|
61
65
|
return createMnemonicServer(config, FredMCPServer);
|
|
62
66
|
}
|
|
63
67
|
//#endregion
|
|
64
|
-
export { AuthTimestampTracker, FredMCPServer, INFRASTRUCTURE_ERROR_CODES, MAX_TAIL, ManifestMCPError, ManifestMCPErrorCode, ProviderApiError, TerminalChainStateError, appStatus, browseCatalog, buildManifest, buildManifestPreview, buildStackManifest, checkDeploymentReadiness, checkedFetch, createAuthToken, createLeaseDataSignMessage, createMnemonicFredServer, createSignMessage, deployApp, deriveAppNameFromImage, fetchActiveLease, getAppLogs, getLeaseConnectionInfo, getLeaseInfo, getLeaseLogs, getLeaseProvision, getLeaseReleases, getLeaseStatus, getProviderHealth, getServiceNames, isStackManifest, mapWithConcurrency, mergeManifest, metaHashHex, normalizePorts, parseStackManifest, pollLeaseUntilReady, resolveProviderUrl, restartApp, restartLease, updateApp, updateLease, uploadLeaseData, validateManifest, validateProviderUrl, validateServiceName, waitForAppReady };
|
|
68
|
+
export { AuthTimestampTracker, FredMCPServer, INFRASTRUCTURE_ERROR_CODES, MAX_TAIL, ManifestMCPError, ManifestMCPErrorCode, ProviderApiError, TerminalChainStateError, appStatus, browseCatalog, buildManifest, buildManifestPreview, buildStackManifest, checkDeploymentReadiness, checkedFetch, createAuthToken, createLeaseDataSignMessage, createMnemonicFredServer, createSignMessage, deployApp, deployManifest, deriveAppNameFromImage, fetchActiveLease, getAppLogs, getLeaseConnectionInfo, getLeaseInfo, getLeaseLogs, getLeaseProvision, getLeaseReleases, getLeaseStatus, getProviderHealth, getServiceNames, isStackManifest, mapWithConcurrency, mergeManifest, metaHashHex, normalizePorts, parseStackManifest, pollLeaseUntilReady, resolveProviderUrl, restartApp, restartLease, updateApp, updateLease, uploadLeaseData, validateManifest, validateProviderUrl, validateServiceName, waitForAppReady };
|
|
65
69
|
|
|
66
70
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { WalletProvider } from '@manifest-network/manifest-mcp-core';\nimport {\n CosmosClientManager,\n createMnemonicServer,\n createValidatedConfig,\n type ManifestMCPServerOptions,\n type MnemonicServerConfig,\n VERSION,\n} from '@manifest-network/manifest-mcp-core';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { AuthTokenService } from './http/auth-token-service.js';\nimport { registerPrompts } from './server/register-prompts.js';\nimport { registerResources } from './server/register-resources.js';\nimport { registerTools } from './server/register-tools.js';\n\nexport type { ManifestMCPServerOptions } from '@manifest-network/manifest-mcp-core';\nexport {\n INFRASTRUCTURE_ERROR_CODES,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nexport {\n AuthTimestampTracker,\n type AuthTokenPayload,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './http/auth.js';\nexport {\n type FredActionResponse,\n type FredInstanceInfo,\n type FredLeaseInfo,\n type FredLeaseLogs,\n type FredLeaseProvision,\n type FredLeaseRelease,\n type FredLeaseReleases,\n type FredLeaseStatus,\n type FredServiceStatus,\n getLeaseInfo,\n getLeaseLogs,\n getLeaseProvision,\n getLeaseReleases,\n getLeaseStatus,\n MAX_TAIL,\n type PollOptions,\n pollLeaseUntilReady,\n restartLease,\n type TerminalChainLeaseState,\n type TerminalChainState,\n type TerminalChainStateContext,\n TerminalChainStateError,\n updateLease,\n} from './http/fred.js';\nexport {\n type ConnectionDetails,\n checkedFetch,\n getLeaseConnectionInfo,\n getProviderHealth,\n type InstanceInfo,\n type LeaseConnectionResponse,\n ProviderApiError,\n type ProviderHealthResponse,\n type ServiceConnectionDetails,\n uploadLeaseData,\n validateProviderUrl,\n} from './http/provider.js';\nexport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n deriveAppNameFromImage,\n getServiceNames,\n isStackManifest,\n type ManifestFormat,\n type ManifestValidationResult,\n mergeManifest,\n metaHashHex,\n normalizePorts,\n parseStackManifest,\n validateManifest,\n validateServiceName,\n} from './manifest.js';\nexport { appStatus } from './tools/appStatus.js';\nexport { browseCatalog, mapWithConcurrency } from './tools/browseCatalog.js';\nexport {\n type BuildManifestPreviewInput,\n type BuildManifestPreviewResult,\n buildManifestPreview,\n type ManifestPreviewServiceInput,\n} from './tools/buildManifestPreview.js';\nexport {\n type CheckDeploymentReadinessInput,\n type CheckDeploymentReadinessResult,\n checkDeploymentReadiness,\n type SkuSummary,\n} from './tools/checkDeploymentReadiness.js';\nexport {\n type DeployAppInput,\n type DeployAppResult,\n deployApp,\n type ServiceConfig,\n} from './tools/deployApp.js';\nexport { fetchActiveLease } from './tools/fetchActiveLease.js';\nexport { getAppLogs } from './tools/getLogs.js';\nexport { resolveProviderUrl } from './tools/resolveLeaseProvider.js';\nexport { restartApp } from './tools/restartApp.js';\nexport { updateApp } from './tools/updateApp.js';\nexport {\n type WaitForAppReadyOptions,\n type WaitForAppReadyResult,\n waitForAppReady,\n} from './tools/waitForAppReady.js';\n\nexport class FredMCPServer {\n private mcpServer: McpServer;\n private clientManager: CosmosClientManager;\n private walletProvider: WalletProvider;\n private authTokens: AuthTokenService;\n\n constructor(options: ManifestMCPServerOptions) {\n const config = createValidatedConfig(options.config);\n this.walletProvider = options.walletProvider;\n this.clientManager = CosmosClientManager.getInstance(\n config,\n this.walletProvider,\n );\n this.authTokens = new AuthTokenService(this.walletProvider);\n\n this.mcpServer = new McpServer(\n {\n name: '@manifest-network/manifest-mcp-fred',\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n resources: {},\n prompts: {},\n },\n },\n );\n\n registerTools({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n authTokens: this.authTokens,\n });\n registerResources({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n });\n registerPrompts(this.mcpServer);\n }\n\n getServer(): Server {\n return this.mcpServer.server;\n }\n\n getClientManager(): CosmosClientManager {\n return this.clientManager;\n }\n\n disconnect(): void {\n this.clientManager.disconnect();\n }\n}\n\nexport function createMnemonicFredServer(\n config: MnemonicServerConfig,\n): Promise<FredMCPServer> {\n return createMnemonicServer(config, FredMCPServer);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { WalletProvider } from '@manifest-network/manifest-mcp-core';\nimport {\n CosmosClientManager,\n createMnemonicServer,\n createValidatedConfig,\n type ManifestMCPServerOptions,\n type MnemonicServerConfig,\n VERSION,\n} from '@manifest-network/manifest-mcp-core';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { AuthTokenService } from './http/auth-token-service.js';\nimport { resolveGuardedFetch } from './server/fetch-gate.js';\nimport { registerPrompts } from './server/register-prompts.js';\nimport { registerResources } from './server/register-resources.js';\nimport { registerTools } from './server/register-tools.js';\n\nexport type { ManifestMCPServerOptions } from '@manifest-network/manifest-mcp-core';\nexport {\n INFRASTRUCTURE_ERROR_CODES,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nexport {\n AuthTimestampTracker,\n type AuthTokenPayload,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './http/auth.js';\nexport {\n type FredActionResponse,\n type FredInstanceInfo,\n type FredLeaseInfo,\n type FredLeaseLogs,\n type FredLeaseProvision,\n type FredLeaseRelease,\n type FredLeaseReleases,\n type FredLeaseStatus,\n type FredServiceStatus,\n getLeaseInfo,\n getLeaseLogs,\n getLeaseProvision,\n getLeaseReleases,\n getLeaseStatus,\n MAX_TAIL,\n type PollOptions,\n pollLeaseUntilReady,\n restartLease,\n type TerminalChainLeaseState,\n type TerminalChainState,\n type TerminalChainStateContext,\n TerminalChainStateError,\n updateLease,\n} from './http/fred.js';\nexport {\n type ConnectionDetails,\n checkedFetch,\n getLeaseConnectionInfo,\n getProviderHealth,\n type InstanceInfo,\n type LeaseConnectionResponse,\n ProviderApiError,\n type ProviderHealthResponse,\n type ServiceConnectionDetails,\n uploadLeaseData,\n validateProviderUrl,\n} from './http/provider.js';\nexport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n deriveAppNameFromImage,\n getServiceNames,\n isStackManifest,\n type ManifestFormat,\n type ManifestValidationResult,\n mergeManifest,\n metaHashHex,\n normalizePorts,\n parseStackManifest,\n validateManifest,\n validateServiceName,\n} from './manifest.js';\nexport { appStatus } from './tools/appStatus.js';\nexport { browseCatalog, mapWithConcurrency } from './tools/browseCatalog.js';\nexport {\n type BuildManifestPreviewInput,\n type BuildManifestPreviewResult,\n buildManifestPreview,\n type ManifestPreviewServiceInput,\n} from './tools/buildManifestPreview.js';\nexport {\n type CheckDeploymentReadinessInput,\n type CheckDeploymentReadinessResult,\n checkDeploymentReadiness,\n type SkuSummary,\n} from './tools/checkDeploymentReadiness.js';\nexport {\n type DeployAppInput,\n type DeployAppResult,\n deployApp,\n type ServiceConfig,\n} from './tools/deployApp.js';\nexport {\n type DeployManifestInput,\n type DeployManifestOptions,\n deployManifest,\n type SkuSelector,\n} from './tools/deployManifest.js';\nexport { fetchActiveLease } from './tools/fetchActiveLease.js';\nexport { getAppLogs } from './tools/getLogs.js';\nexport { resolveProviderUrl } from './tools/resolveLeaseProvider.js';\nexport { restartApp } from './tools/restartApp.js';\nexport { updateApp } from './tools/updateApp.js';\nexport {\n type WaitForAppReadyOptions,\n type WaitForAppReadyResult,\n waitForAppReady,\n} from './tools/waitForAppReady.js';\n\nexport class FredMCPServer {\n private mcpServer: McpServer;\n private clientManager: CosmosClientManager;\n private walletProvider: WalletProvider;\n private authTokens: AuthTokenService;\n\n constructor(options: ManifestMCPServerOptions) {\n const config = createValidatedConfig(options.config);\n this.walletProvider = options.walletProvider;\n this.clientManager = CosmosClientManager.getInstance(\n config,\n this.walletProvider,\n );\n this.authTokens = new AuthTokenService(this.walletProvider);\n\n this.mcpServer = new McpServer(\n {\n name: '@manifest-network/manifest-mcp-fred',\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n resources: {},\n prompts: {},\n },\n },\n );\n\n // SSRF guard (ENG-268). Fred fetches provider/Fred APIs at URLs sourced\n // from on-chain SKU records, so a malicious provider could point them at\n // an internal host. Route all outbound HTTP through an SSRF-guarded fetch\n // by default; operators opt out with MANIFEST_FRED_FETCH_GUARDED=0.\n const fetchFn = resolveGuardedFetch(\n typeof process !== 'undefined'\n ? process.env.MANIFEST_FRED_FETCH_GUARDED\n : undefined,\n typeof process !== 'undefined' && !!process.versions?.node,\n );\n\n registerTools({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n authTokens: this.authTokens,\n fetchFn,\n });\n registerResources({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n });\n registerPrompts(this.mcpServer);\n }\n\n getServer(): Server {\n return this.mcpServer.server;\n }\n\n getClientManager(): CosmosClientManager {\n return this.clientManager;\n }\n\n disconnect(): void {\n this.clientManager.disconnect();\n }\n}\n\nexport function createMnemonicFredServer(\n config: MnemonicServerConfig,\n): Promise<FredMCPServer> {\n return createMnemonicServer(config, FredMCPServer);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyHA,IAAa,gBAAb,MAA2B;CAMzB,YAAY,SAAmC;EAC7C,MAAM,SAAS,sBAAsB,QAAQ,MAAM;EACnD,KAAK,iBAAiB,QAAQ;EAC9B,KAAK,gBAAgB,oBAAoB,YACvC,QACA,KAAK,cACP;EACA,KAAK,aAAa,IAAI,iBAAiB,KAAK,cAAc;EAE1D,KAAK,YAAY,IAAI,UACnB;GACE,MAAM;GACN,SAAS;EACX,GACA,EACE,cAAc;GACZ,OAAO,CAAC;GACR,WAAW,CAAC;GACZ,SAAS,CAAC;EACZ,EACF,CACF;EAMA,MAAM,UAAU,oBACd,OAAO,YAAY,cACf,QAAQ,IAAI,8BACZ,KAAA,GACJ,OAAO,YAAY,eAAe,CAAC,CAAC,QAAQ,UAAU,IACxD;EAEA,cAAc;GACZ,WAAW,KAAK;GAChB,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACrB,YAAY,KAAK;GACjB;EACF,CAAC;EACD,kBAAkB;GAChB,WAAW,KAAK;GAChB,eAAe,KAAK;GACpB,gBAAgB,KAAK;EACvB,CAAC;EACD,gBAAgB,KAAK,SAAS;CAChC;CAEA,YAAoB;EAClB,OAAO,KAAK,UAAU;CACxB;CAEA,mBAAwC;EACtC,OAAO,KAAK;CACd;CAEA,aAAmB;EACjB,KAAK,cAAc,WAAW;CAChC;AACF;AAEA,SAAgB,yBACd,QACwB;CACxB,OAAO,qBAAqB,QAAQ,aAAa;AACnD"}
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../src/manifest.ts"],"mappings":";UAAiB,oBAAA;EACf,KAAA;EACA,KAAA,EAAO,MAAA,SAAe,MAAA;EACtB,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,IAAA;EACA,MAAA;EACA,MAAA,GAAS,MAAA;EACT,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;AAAA;AAAA,iBAWhB,sBAAA,CAAuB,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../src/manifest.ts"],"mappings":";UAAiB,oBAAA;EACf,KAAA;EACA,KAAA,EAAO,MAAA,SAAe,MAAA;EACtB,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,IAAA;EACA,MAAA;EACA,MAAA,GAAS,MAAA;EACT,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;AAAA;AAAA,iBAWhB,sBAAA,CAAuB,KAAa;AAAA,iBAkCpC,mBAAA,CAAoB,IAAY;AAAA,iBAIhC,aAAA,CACd,IAAA,EAAM,oBAAA,GACL,MAAM;AAAA,iBAsBO,cAAA,CACd,IAAA,WACC,MAAM,SAAS,MAAA;AAAA,iBA+BF,kBAAA,CAAmB,IAAA;EACjC,QAAA,EAAU,MAAA,SAAe,oBAAA;AAAA;EACrB,QAAA,EAAU,MAAA;AAAA;AAAA,iBAoBA,aAAA,CACd,WAAA,EAAa,MAAA,mBACb,eAAA,WACC,MAAM;AAAA,iBA6DO,eAAA,CACd,QAAA,YACC,QAAA;EAAc,QAAA,EAAU,MAAM,SAAS,MAAA;AAAA;AAAA,iBA2B1B,kBAAA,CAAmB,IAAA;EACjC,QAAA,EAAU,MAAM,SAAS,MAAA;AAAA;AAAA,iBAuBX,eAAA,CAAgB,QAAiB;;AA1OG;AAkCpD;;;;iBAmNsB,WAAA,CAAY,YAAA,WAAuB,OAAO;AAAA,KAOpD,cAAA;AAAA,UAEK,wBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA,EAAQ,cAAc;AAAA;;;AAzNxB;AAsBT;;;;;iBAugBgB,gBAAA,CAAiB,QAAA,YAAoB,wBAAwB"}
|
package/dist/manifest.js
CHANGED
|
@@ -183,7 +183,14 @@ function validateService(service, scope, inStack, errors) {
|
|
|
183
183
|
}
|
|
184
184
|
if (!("image" in service)) errors.push(`${scope}.image: required`);
|
|
185
185
|
else if (typeof service.image !== "string" || service.image.length === 0) errors.push(`${scope}.image: must be a non-empty string`);
|
|
186
|
-
|
|
186
|
+
const seenLower = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const key of Object.keys(service)) {
|
|
188
|
+
const lower = key.toLowerCase();
|
|
189
|
+
const prev = seenLower.get(lower);
|
|
190
|
+
if (prev !== void 0) errors.push(`${scope || "manifest"}: keys "${prev}" and "${key}" collide case-insensitively (the provider matches fields case-insensitively)`);
|
|
191
|
+
else seenLower.set(lower, key);
|
|
192
|
+
if (!ALLOWED_TOP_LEVEL_KEYS.has(key)) errors.push(`${scope}.${key}: unknown field`);
|
|
193
|
+
}
|
|
187
194
|
if ("ports" in service) if (!isPlainObject(service.ports)) errors.push(`${scope}.ports: must be an object`);
|
|
188
195
|
else for (const key of Object.keys(service.ports)) {
|
|
189
196
|
const match = key.match(PORT_KEY_RE);
|
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["export interface BuildManifestOptions {\n image: string;\n ports: Record<string, Record<string, never>>;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n init?: boolean;\n expose?: string[];\n labels?: Record<string, string>;\n depends_on?: Record<string, { condition: string }>;\n}\n\nimport {\n DNS_LABEL_RE,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\n\nconst MAX_NAME_LENGTH = 32;\n\nexport function deriveAppNameFromImage(image: string): string {\n // Strip registry prefix (everything before the last /)\n const lastSlash = image.lastIndexOf('/');\n let name = lastSlash >= 0 ? image.slice(lastSlash + 1) : image;\n\n // Strip digest (@sha256:...)\n const atIdx = name.indexOf('@');\n if (atIdx >= 0) {\n name = name.slice(0, atIdx);\n }\n\n // Strip tag unconditionally\n const colonIdx = name.indexOf(':');\n if (colonIdx >= 0) {\n name = name.slice(0, colonIdx);\n }\n\n // Normalize: lowercase, replace non-alphanumeric with hyphens\n name = name.toLowerCase().replace(/[^a-z0-9]/g, '-');\n\n // Collapse consecutive hyphens\n name = name.replace(/-{2,}/g, '-');\n\n // Trim leading/trailing hyphens\n name = name.replace(/^-+|-+$/g, '');\n\n // Truncate\n if (name.length > MAX_NAME_LENGTH) {\n name = name.slice(0, MAX_NAME_LENGTH).replace(/-+$/, '');\n }\n\n return name;\n}\n\nexport function validateServiceName(name: string): boolean {\n return DNS_LABEL_RE.test(name);\n}\n\nexport function buildManifest(\n opts: BuildManifestOptions,\n): Record<string, unknown> {\n const manifest: Record<string, unknown> = {\n image: opts.image,\n ports: opts.ports,\n };\n if (opts.env) manifest.env = opts.env;\n if (opts.command) manifest.command = opts.command;\n if (opts.args) manifest.args = opts.args;\n if (opts.user) manifest.user = opts.user;\n if (opts.tmpfs) manifest.tmpfs = opts.tmpfs;\n if (opts.health_check) manifest.health_check = opts.health_check;\n if (opts.stop_grace_period)\n manifest.stop_grace_period = opts.stop_grace_period;\n if (opts.init !== undefined) manifest.init = opts.init;\n if (opts.expose) manifest.expose = opts.expose;\n if (opts.labels) manifest.labels = opts.labels;\n if (opts.depends_on) manifest.depends_on = opts.depends_on;\n return manifest;\n}\n\nconst VALID_PROTOCOLS = new Set(['tcp', 'udp']);\n\nexport function normalizePorts(\n port: string,\n): Record<string, Record<string, never>> {\n const result: Record<string, Record<string, never>> = {};\n for (const raw of port.split(',')) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n const slashIdx = trimmed.indexOf('/');\n const portStr = slashIdx >= 0 ? trimmed.slice(0, slashIdx) : trimmed;\n const protocol = slashIdx >= 0 ? trimmed.slice(slashIdx + 1) : 'tcp';\n const portNum = parseInt(portStr, 10);\n if (\n Number.isNaN(portNum) ||\n portNum < 1 ||\n portNum > 65535 ||\n String(portNum) !== portStr\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid port: \"${portStr}\". Port must be a number between 1 and 65535.`,\n );\n }\n if (!VALID_PROTOCOLS.has(protocol)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid protocol: \"${protocol}\". Must be \"tcp\" or \"udp\".`,\n );\n }\n result[`${portNum}/${protocol}`] = {};\n }\n return result;\n}\n\nexport function buildStackManifest(opts: {\n services: Record<string, BuildManifestOptions>;\n}): { services: Record<string, unknown> } {\n const stack: Record<string, unknown> = {};\n for (const [name, serviceOpts] of Object.entries(opts.services)) {\n stack[name] = buildManifest(serviceOpts);\n }\n return { services: stack };\n}\n\nconst CARRY_FORWARD_KEYS = [\n 'user',\n 'tmpfs',\n 'command',\n 'args',\n 'health_check',\n 'stop_grace_period',\n 'init',\n 'expose',\n 'depends_on',\n] as const;\n\nexport function mergeManifest(\n newManifest: Record<string, unknown>,\n oldManifestJson: string,\n): Record<string, unknown> {\n let old: Record<string, unknown>;\n try {\n const parsed = JSON.parse(oldManifestJson);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'existing_manifest must be a JSON object',\n );\n }\n old = parsed as Record<string, unknown>;\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `existing_manifest contains invalid JSON: ${err.message}`,\n );\n }\n throw err;\n }\n\n const merged: Record<string, unknown> = { ...newManifest };\n\n // env: old defaults, new overrides\n if (old.env || merged.env) {\n merged.env = {\n ...(old.env as Record<string, string> | undefined),\n ...(merged.env as Record<string, string> | undefined),\n };\n }\n\n // ports: union\n if (old.ports || merged.ports) {\n merged.ports = {\n ...(old.ports as Record<string, unknown> | undefined),\n ...(merged.ports as Record<string, unknown> | undefined),\n };\n }\n\n // labels: old defaults, new overrides\n if (old.labels || merged.labels) {\n merged.labels = {\n ...(old.labels as Record<string, string> | undefined),\n ...(merged.labels as Record<string, string> | undefined),\n };\n }\n\n // Carry forward from old if not present in new\n for (const key of CARRY_FORWARD_KEYS) {\n if (!(key in merged) && key in old) {\n merged[key] = old[key];\n }\n }\n\n return merged;\n}\n\nexport function isStackManifest(\n manifest: unknown,\n): manifest is { services: Record<string, Record<string, unknown>> } {\n if (\n manifest === null ||\n typeof manifest !== 'object' ||\n Array.isArray(manifest)\n ) {\n return false;\n }\n const services = (manifest as Record<string, unknown>).services;\n if (\n services === null ||\n typeof services !== 'object' ||\n Array.isArray(services)\n ) {\n return false;\n }\n const entries = Object.values(services as Record<string, unknown>);\n if (entries.length === 0) return false;\n return entries.every(\n (v) =>\n v !== null &&\n typeof v === 'object' &&\n !Array.isArray(v) &&\n 'image' in (v as Record<string, unknown>),\n );\n}\n\nexport function parseStackManifest(json: string): {\n services: Record<string, Record<string, unknown>>;\n} {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Stack manifest contains invalid JSON: ${err.message}`,\n );\n }\n throw err;\n }\n if (!isStackManifest(parsed)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Not a valid stack manifest: expected { services: { ... } } where each service has an \"image\" key',\n );\n }\n return parsed;\n}\n\nexport function getServiceNames(manifest: unknown): string[] {\n if (!isStackManifest(manifest)) return [];\n return Object.keys(manifest.services);\n}\n\n/**\n * Computes the lowercase hex SHA-256 of the manifest JSON. The result must\n * match the `meta_hash` recorded on-chain — Fred rejects uploads whose body\n * hash does not match. Callers are responsible for serializing exactly the\n * bytes that will be uploaded.\n */\nexport async function metaHashHex(manifestJson: string): Promise<string> {\n const encoded = new TextEncoder().encode(manifestJson);\n const hashBuffer = await crypto.subtle.digest('SHA-256', encoded);\n const bytes = new Uint8Array(hashBuffer);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport type ManifestFormat = 'single' | 'stack';\n\nexport interface ManifestValidationResult {\n readonly valid: boolean;\n readonly errors: readonly string[];\n readonly format: ManifestFormat | null;\n}\n\nconst ALLOWED_TOP_LEVEL_KEYS = new Set<string>([\n 'image',\n 'ports',\n 'env',\n 'command',\n 'args',\n 'labels',\n 'health_check',\n 'tmpfs',\n 'user',\n 'depends_on',\n 'stop_grace_period',\n 'init',\n 'expose',\n]);\n\nconst HEALTH_CHECK_KEYS = new Set<string>([\n 'test',\n 'interval',\n 'timeout',\n 'retries',\n 'start_period',\n]);\n\nconst PORT_KEY_RE = /^([1-9][0-9]{0,4})\\/(tcp|udp)$/i;\nconst EXPOSE_PORT_RE = /^([1-9][0-9]{0,4})$/;\nconst ENV_NAME_BLOCKED_RE = /^(path|ld_|fred_|docker_)/i;\nconst TMPFS_BLOCKED = new Set<string>(['/', '/tmp', '/run']);\nconst TMPFS_BLOCKED_PREFIXES = ['/proc', '/sys', '/dev'];\nconst HEALTH_CHECK_TYPES = new Set<string>(['CMD', 'CMD-SHELL', 'NONE']);\nconst DEPENDS_ON_CONDITIONS = new Set<string>([\n 'service_started',\n 'service_healthy',\n]);\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v);\n}\n\nfunction validatePort(portStr: string): boolean {\n const n = Number(portStr);\n return Number.isInteger(n) && n >= 1 && n <= 65535;\n}\n\nfunction validateService(\n service: unknown,\n scope: string,\n inStack: boolean,\n errors: string[],\n): void {\n if (!isPlainObject(service)) {\n errors.push(`${scope}: must be a JSON object`);\n return;\n }\n\n // image (required, non-empty string)\n if (!('image' in service)) {\n errors.push(`${scope}.image: required`);\n } else if (typeof service.image !== 'string' || service.image.length === 0) {\n errors.push(`${scope}.image: must be a non-empty string`);\n }\n\n // unknown keys\n for (const key of Object.keys(service)) {\n if (!ALLOWED_TOP_LEVEL_KEYS.has(key)) {\n errors.push(`${scope}.${key}: unknown field`);\n }\n }\n\n // ports\n if ('ports' in service) {\n if (!isPlainObject(service.ports)) {\n errors.push(`${scope}.ports: must be an object`);\n } else {\n for (const key of Object.keys(service.ports)) {\n // PORT_KEY_RE permits up to 5 digits (so 1-99999); the docs and the\n // error message limit ports to 1-65535. validatePort closes the gap\n // so 70000/tcp doesn't silently pass pre-flight.\n const match = key.match(PORT_KEY_RE);\n if (!match || !validatePort(match[1])) {\n errors.push(\n `${scope}.ports[\"${key}\"]: must be in \"port/protocol\" format with port 1-65535 and protocol tcp|udp`,\n );\n }\n }\n }\n }\n\n // env: name validation\n if ('env' in service) {\n if (!isPlainObject(service.env)) {\n errors.push(`${scope}.env: must be an object`);\n } else {\n for (const [name, value] of Object.entries(service.env)) {\n if (name.length === 0) {\n errors.push(`${scope}.env: variable name cannot be empty`);\n } else if (name.includes('=') || name.includes('\\0')) {\n errors.push(\n `${scope}.env[\"${name}\"]: name cannot contain '=' or NUL`,\n );\n } else if (ENV_NAME_BLOCKED_RE.test(name)) {\n errors.push(\n `${scope}.env[\"${name}\"]: blocked variable name (PATH, LD_*, FRED_*, DOCKER_* are reserved)`,\n );\n }\n if (typeof value !== 'string') {\n errors.push(`${scope}.env[\"${name}\"]: value must be a string`);\n }\n }\n }\n }\n\n // labels: fred.* prefix is reserved\n if ('labels' in service) {\n if (!isPlainObject(service.labels)) {\n errors.push(`${scope}.labels: must be an object`);\n } else {\n for (const key of Object.keys(service.labels)) {\n if (key.startsWith('fred.')) {\n errors.push(\n `${scope}.labels[\"${key}\"]: reserved prefix 'fred.' is not allowed`,\n );\n }\n }\n }\n }\n\n // tmpfs\n if ('tmpfs' in service) {\n if (!Array.isArray(service.tmpfs)) {\n errors.push(`${scope}.tmpfs: must be an array of strings`);\n } else {\n if (service.tmpfs.length > 4) {\n errors.push(\n `${scope}.tmpfs: too many mounts (${service.tmpfs.length}), maximum is 4`,\n );\n }\n const seen = new Set<string>();\n for (const path of service.tmpfs) {\n if (typeof path !== 'string') {\n errors.push(`${scope}.tmpfs: entries must be strings`);\n continue;\n }\n if (!path.startsWith('/')) {\n errors.push(`${scope}.tmpfs[\"${path}\"]: must be an absolute path`);\n }\n if (TMPFS_BLOCKED.has(path)) {\n errors.push(\n `${scope}.tmpfs[\"${path}\"]: path is managed by the backend`,\n );\n }\n for (const prefix of TMPFS_BLOCKED_PREFIXES) {\n if (path === prefix || path.startsWith(`${prefix}/`)) {\n errors.push(\n `${scope}.tmpfs[\"${path}\"]: path is under sensitive path ${prefix}`,\n );\n }\n }\n if (seen.has(path)) {\n errors.push(`${scope}.tmpfs[\"${path}\"]: duplicate mount`);\n }\n seen.add(path);\n }\n }\n }\n\n // user\n if ('user' in service) {\n if (typeof service.user !== 'string') {\n errors.push(`${scope}.user: must be a string`);\n } else if (service.user.length > 0) {\n const u = service.user;\n if (/\\s/.test(u)) {\n errors.push(`${scope}.user: cannot contain whitespace`);\n } else {\n const colon = u.indexOf(':');\n if (colon === 0 || colon === u.length - 1) {\n errors.push(`${scope}.user: user/group parts cannot be empty`);\n }\n }\n }\n }\n\n // health_check\n if ('health_check' in service) {\n if (!isPlainObject(service.health_check)) {\n errors.push(`${scope}.health_check: must be an object`);\n } else {\n const hc = service.health_check;\n for (const key of Object.keys(hc)) {\n if (!HEALTH_CHECK_KEYS.has(key)) {\n errors.push(`${scope}.health_check.${key}: unknown field`);\n }\n }\n if (!('test' in hc)) {\n errors.push(`${scope}.health_check.test: required`);\n } else if (\n !Array.isArray(hc.test) ||\n hc.test.length === 0 ||\n !hc.test.every((s) => typeof s === 'string')\n ) {\n errors.push(`${scope}.health_check.test: must be a non-empty string[]`);\n } else {\n const head = hc.test[0];\n if (!HEALTH_CHECK_TYPES.has(head)) {\n errors.push(\n `${scope}.health_check.test[0]: must be CMD, CMD-SHELL, or NONE`,\n );\n } else if (head !== 'NONE' && hc.test.length < 2) {\n errors.push(\n `${scope}.health_check.test: ${head} requires at least one argument after the type`,\n );\n } else if (head === 'NONE' && hc.test.length > 1) {\n errors.push(\n `${scope}.health_check.test: NONE accepts no further arguments`,\n );\n }\n }\n if (\n 'retries' in hc &&\n (typeof hc.retries !== 'number' ||\n !Number.isInteger(hc.retries) ||\n hc.retries < 0)\n ) {\n errors.push(\n `${scope}.health_check.retries: must be a non-negative integer`,\n );\n }\n }\n }\n\n // depends_on: only valid in stack\n if ('depends_on' in service) {\n if (!isPlainObject(service.depends_on)) {\n errors.push(`${scope}.depends_on: must be an object`);\n } else {\n const entries = Object.entries(service.depends_on);\n if (entries.length > 0 && !inStack) {\n errors.push(\n `${scope}.depends_on: only allowed inside a stack manifest (services map)`,\n );\n }\n for (const [name, cond] of entries) {\n if (!isPlainObject(cond)) {\n errors.push(`${scope}.depends_on[\"${name}\"]: must be an object`);\n continue;\n }\n for (const k of Object.keys(cond)) {\n if (k !== 'condition') {\n errors.push(`${scope}.depends_on[\"${name}\"].${k}: unknown field`);\n }\n }\n if (\n typeof cond.condition !== 'string' ||\n !DEPENDS_ON_CONDITIONS.has(cond.condition)\n ) {\n errors.push(\n `${scope}.depends_on[\"${name}\"].condition: must be \"service_started\" or \"service_healthy\"`,\n );\n }\n }\n }\n }\n\n // expose\n if ('expose' in service) {\n if (!Array.isArray(service.expose)) {\n errors.push(`${scope}.expose: must be an array of port strings`);\n } else {\n const seen = new Set<string>();\n for (const p of service.expose) {\n if (typeof p !== 'string' || !EXPOSE_PORT_RE.test(p)) {\n errors.push(\n `${scope}.expose[\"${String(p)}\"]: must be a port number string (1-65535)`,\n );\n } else if (!validatePort(p)) {\n errors.push(`${scope}.expose[\"${p}\"]: port out of range`);\n }\n if (seen.has(String(p))) {\n errors.push(`${scope}.expose[\"${String(p)}\"]: duplicate`);\n }\n seen.add(String(p));\n }\n }\n }\n\n // init / stop_grace_period: minimal type checks (range validation is runtime).\n if ('init' in service && typeof service.init !== 'boolean') {\n errors.push(`${scope}.init: must be a boolean`);\n }\n if ('stop_grace_period' in service) {\n const v = service.stop_grace_period;\n if (typeof v !== 'string' && typeof v !== 'number') {\n errors.push(\n `${scope}.stop_grace_period: must be a duration string or integer nanoseconds`,\n );\n }\n }\n}\n\n/**\n * Validates a parsed manifest object against the documented Fred rules.\n * Pre-flight only: catches the constraints documented in the public spec\n * (env-name blocklist, label prefix, port format, tmpfs limits, RFC 1123\n * service names, depends_on placement, unknown fields). The provider does\n * the canonical validation server-side; this helper exists so agents can\n * reject obviously-broken manifests before paying for a lease.\n */\nexport function validateManifest(manifest: unknown): ManifestValidationResult {\n const errors: string[] = [];\n\n if (!isPlainObject(manifest)) {\n return {\n valid: false,\n errors: ['manifest must be a JSON object'],\n format: null,\n };\n }\n\n if (isStackManifest(manifest)) {\n // Stack manifest — only `services` is allowed at the top level.\n for (const key of Object.keys(manifest)) {\n if (key !== 'services') {\n errors.push(`${key}: unknown top-level field for stack manifest`);\n }\n }\n const serviceNames = Object.keys(manifest.services);\n if (serviceNames.length === 0) {\n errors.push('services: at least one service is required');\n }\n for (const name of serviceNames) {\n if (!validateServiceName(name)) {\n errors.push(\n `services[\"${name}\"]: must be a valid RFC 1123 DNS label (1-63 chars, lowercase alphanumeric + hyphens)`,\n );\n }\n validateService(\n manifest.services[name],\n `services[\"${name}\"]`,\n true,\n errors,\n );\n }\n // Cross-service: depends_on must only reference defined services and not\n // self. Set lookup keeps the cross-check linear in total dep edges\n // instead of O(services * deps * services).\n const serviceNameSet = new Set(serviceNames);\n for (const [name, svc] of Object.entries(manifest.services)) {\n if (isPlainObject(svc) && isPlainObject(svc.depends_on)) {\n for (const dep of Object.keys(svc.depends_on)) {\n if (dep === name) {\n errors.push(\n `services[\"${name}\"].depends_on[\"${dep}\"]: a service cannot depend on itself`,\n );\n } else if (!serviceNameSet.has(dep)) {\n errors.push(\n `services[\"${name}\"].depends_on[\"${dep}\"]: references undefined service`,\n );\n }\n }\n }\n }\n return {\n valid: errors.length === 0,\n errors,\n format: 'stack',\n };\n }\n\n // Single-service manifest.\n validateService(manifest, '', false, errors);\n return {\n valid: errors.length === 0,\n errors,\n format: 'single',\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manifest.js","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["export interface BuildManifestOptions {\n image: string;\n ports: Record<string, Record<string, never>>;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n init?: boolean;\n expose?: string[];\n labels?: Record<string, string>;\n depends_on?: Record<string, { condition: string }>;\n}\n\nimport {\n DNS_LABEL_RE,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\n\nconst MAX_NAME_LENGTH = 32;\n\nexport function deriveAppNameFromImage(image: string): string {\n // Strip registry prefix (everything before the last /)\n const lastSlash = image.lastIndexOf('/');\n let name = lastSlash >= 0 ? image.slice(lastSlash + 1) : image;\n\n // Strip digest (@sha256:...)\n const atIdx = name.indexOf('@');\n if (atIdx >= 0) {\n name = name.slice(0, atIdx);\n }\n\n // Strip tag unconditionally\n const colonIdx = name.indexOf(':');\n if (colonIdx >= 0) {\n name = name.slice(0, colonIdx);\n }\n\n // Normalize: lowercase, replace non-alphanumeric with hyphens\n name = name.toLowerCase().replace(/[^a-z0-9]/g, '-');\n\n // Collapse consecutive hyphens\n name = name.replace(/-{2,}/g, '-');\n\n // Trim leading/trailing hyphens\n name = name.replace(/^-+|-+$/g, '');\n\n // Truncate\n if (name.length > MAX_NAME_LENGTH) {\n name = name.slice(0, MAX_NAME_LENGTH).replace(/-+$/, '');\n }\n\n return name;\n}\n\nexport function validateServiceName(name: string): boolean {\n return DNS_LABEL_RE.test(name);\n}\n\nexport function buildManifest(\n opts: BuildManifestOptions,\n): Record<string, unknown> {\n const manifest: Record<string, unknown> = {\n image: opts.image,\n ports: opts.ports,\n };\n if (opts.env) manifest.env = opts.env;\n if (opts.command) manifest.command = opts.command;\n if (opts.args) manifest.args = opts.args;\n if (opts.user) manifest.user = opts.user;\n if (opts.tmpfs) manifest.tmpfs = opts.tmpfs;\n if (opts.health_check) manifest.health_check = opts.health_check;\n if (opts.stop_grace_period)\n manifest.stop_grace_period = opts.stop_grace_period;\n if (opts.init !== undefined) manifest.init = opts.init;\n if (opts.expose) manifest.expose = opts.expose;\n if (opts.labels) manifest.labels = opts.labels;\n if (opts.depends_on) manifest.depends_on = opts.depends_on;\n return manifest;\n}\n\nconst VALID_PROTOCOLS = new Set(['tcp', 'udp']);\n\nexport function normalizePorts(\n port: string,\n): Record<string, Record<string, never>> {\n const result: Record<string, Record<string, never>> = {};\n for (const raw of port.split(',')) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n const slashIdx = trimmed.indexOf('/');\n const portStr = slashIdx >= 0 ? trimmed.slice(0, slashIdx) : trimmed;\n const protocol = slashIdx >= 0 ? trimmed.slice(slashIdx + 1) : 'tcp';\n const portNum = parseInt(portStr, 10);\n if (\n Number.isNaN(portNum) ||\n portNum < 1 ||\n portNum > 65535 ||\n String(portNum) !== portStr\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid port: \"${portStr}\". Port must be a number between 1 and 65535.`,\n );\n }\n if (!VALID_PROTOCOLS.has(protocol)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid protocol: \"${protocol}\". Must be \"tcp\" or \"udp\".`,\n );\n }\n result[`${portNum}/${protocol}`] = {};\n }\n return result;\n}\n\nexport function buildStackManifest(opts: {\n services: Record<string, BuildManifestOptions>;\n}): { services: Record<string, unknown> } {\n const stack: Record<string, unknown> = {};\n for (const [name, serviceOpts] of Object.entries(opts.services)) {\n stack[name] = buildManifest(serviceOpts);\n }\n return { services: stack };\n}\n\nconst CARRY_FORWARD_KEYS = [\n 'user',\n 'tmpfs',\n 'command',\n 'args',\n 'health_check',\n 'stop_grace_period',\n 'init',\n 'expose',\n 'depends_on',\n] as const;\n\nexport function mergeManifest(\n newManifest: Record<string, unknown>,\n oldManifestJson: string,\n): Record<string, unknown> {\n let old: Record<string, unknown>;\n try {\n const parsed = JSON.parse(oldManifestJson);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'existing_manifest must be a JSON object',\n );\n }\n old = parsed as Record<string, unknown>;\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `existing_manifest contains invalid JSON: ${err.message}`,\n );\n }\n throw err;\n }\n\n const merged: Record<string, unknown> = { ...newManifest };\n\n // env: old defaults, new overrides\n if (old.env || merged.env) {\n merged.env = {\n ...(old.env as Record<string, string> | undefined),\n ...(merged.env as Record<string, string> | undefined),\n };\n }\n\n // ports: union\n if (old.ports || merged.ports) {\n merged.ports = {\n ...(old.ports as Record<string, unknown> | undefined),\n ...(merged.ports as Record<string, unknown> | undefined),\n };\n }\n\n // labels: old defaults, new overrides\n if (old.labels || merged.labels) {\n merged.labels = {\n ...(old.labels as Record<string, string> | undefined),\n ...(merged.labels as Record<string, string> | undefined),\n };\n }\n\n // Carry forward from old if not present in new\n for (const key of CARRY_FORWARD_KEYS) {\n if (!(key in merged) && key in old) {\n merged[key] = old[key];\n }\n }\n\n return merged;\n}\n\nexport function isStackManifest(\n manifest: unknown,\n): manifest is { services: Record<string, Record<string, unknown>> } {\n if (\n manifest === null ||\n typeof manifest !== 'object' ||\n Array.isArray(manifest)\n ) {\n return false;\n }\n const services = (manifest as Record<string, unknown>).services;\n if (\n services === null ||\n typeof services !== 'object' ||\n Array.isArray(services)\n ) {\n return false;\n }\n const entries = Object.values(services as Record<string, unknown>);\n if (entries.length === 0) return false;\n return entries.every(\n (v) =>\n v !== null &&\n typeof v === 'object' &&\n !Array.isArray(v) &&\n 'image' in (v as Record<string, unknown>),\n );\n}\n\nexport function parseStackManifest(json: string): {\n services: Record<string, Record<string, unknown>>;\n} {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Stack manifest contains invalid JSON: ${err.message}`,\n );\n }\n throw err;\n }\n if (!isStackManifest(parsed)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Not a valid stack manifest: expected { services: { ... } } where each service has an \"image\" key',\n );\n }\n return parsed;\n}\n\nexport function getServiceNames(manifest: unknown): string[] {\n if (!isStackManifest(manifest)) return [];\n return Object.keys(manifest.services);\n}\n\n/**\n * Computes the lowercase hex SHA-256 of the manifest JSON. The result must\n * match the `meta_hash` recorded on-chain — Fred rejects uploads whose body\n * hash does not match. Callers are responsible for serializing exactly the\n * bytes that will be uploaded.\n */\nexport async function metaHashHex(manifestJson: string): Promise<string> {\n const encoded = new TextEncoder().encode(manifestJson);\n const hashBuffer = await crypto.subtle.digest('SHA-256', encoded);\n const bytes = new Uint8Array(hashBuffer);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport type ManifestFormat = 'single' | 'stack';\n\nexport interface ManifestValidationResult {\n readonly valid: boolean;\n readonly errors: readonly string[];\n readonly format: ManifestFormat | null;\n}\n\nconst ALLOWED_TOP_LEVEL_KEYS = new Set<string>([\n 'image',\n 'ports',\n 'env',\n 'command',\n 'args',\n 'labels',\n 'health_check',\n 'tmpfs',\n 'user',\n 'depends_on',\n 'stop_grace_period',\n 'init',\n 'expose',\n]);\n\nconst HEALTH_CHECK_KEYS = new Set<string>([\n 'test',\n 'interval',\n 'timeout',\n 'retries',\n 'start_period',\n]);\n\nconst PORT_KEY_RE = /^([1-9][0-9]{0,4})\\/(tcp|udp)$/i;\nconst EXPOSE_PORT_RE = /^([1-9][0-9]{0,4})$/;\nconst ENV_NAME_BLOCKED_RE = /^(path|ld_|fred_|docker_)/i;\nconst TMPFS_BLOCKED = new Set<string>(['/', '/tmp', '/run']);\nconst TMPFS_BLOCKED_PREFIXES = ['/proc', '/sys', '/dev'];\nconst HEALTH_CHECK_TYPES = new Set<string>(['CMD', 'CMD-SHELL', 'NONE']);\nconst DEPENDS_ON_CONDITIONS = new Set<string>([\n 'service_started',\n 'service_healthy',\n]);\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v);\n}\n\nfunction validatePort(portStr: string): boolean {\n const n = Number(portStr);\n return Number.isInteger(n) && n >= 1 && n <= 65535;\n}\n\nfunction validateService(\n service: unknown,\n scope: string,\n inStack: boolean,\n errors: string[],\n): void {\n if (!isPlainObject(service)) {\n errors.push(`${scope}: must be a JSON object`);\n return;\n }\n\n // image (required, non-empty string)\n if (!('image' in service)) {\n errors.push(`${scope}.image: required`);\n } else if (typeof service.image !== 'string' || service.image.length === 0) {\n errors.push(`${scope}.image: must be a non-empty string`);\n }\n\n // unknown keys + case-folded collisions (Go encoding/json matches fields\n // case-insensitively; V8 keeps `image` and `IMAGE` as two keys).\n const seenLower = new Map<string, string>();\n for (const key of Object.keys(service)) {\n const lower = key.toLowerCase();\n const prev = seenLower.get(lower);\n if (prev !== undefined) {\n // `scope` is '' for a single-service manifest; use a non-empty label so\n // the message doesn't read with a stray leading colon.\n errors.push(\n `${scope || 'manifest'}: keys \"${prev}\" and \"${key}\" collide case-insensitively (the provider matches fields case-insensitively)`,\n );\n } else {\n seenLower.set(lower, key);\n }\n if (!ALLOWED_TOP_LEVEL_KEYS.has(key)) {\n errors.push(`${scope}.${key}: unknown field`);\n }\n }\n\n // ports\n if ('ports' in service) {\n if (!isPlainObject(service.ports)) {\n errors.push(`${scope}.ports: must be an object`);\n } else {\n for (const key of Object.keys(service.ports)) {\n // PORT_KEY_RE permits up to 5 digits (so 1-99999); the docs and the\n // error message limit ports to 1-65535. validatePort closes the gap\n // so 70000/tcp doesn't silently pass pre-flight.\n const match = key.match(PORT_KEY_RE);\n if (!match || !validatePort(match[1])) {\n errors.push(\n `${scope}.ports[\"${key}\"]: must be in \"port/protocol\" format with port 1-65535 and protocol tcp|udp`,\n );\n }\n }\n }\n }\n\n // env: name validation\n if ('env' in service) {\n if (!isPlainObject(service.env)) {\n errors.push(`${scope}.env: must be an object`);\n } else {\n for (const [name, value] of Object.entries(service.env)) {\n if (name.length === 0) {\n errors.push(`${scope}.env: variable name cannot be empty`);\n } else if (name.includes('=') || name.includes('\\0')) {\n errors.push(\n `${scope}.env[\"${name}\"]: name cannot contain '=' or NUL`,\n );\n } else if (ENV_NAME_BLOCKED_RE.test(name)) {\n errors.push(\n `${scope}.env[\"${name}\"]: blocked variable name (PATH, LD_*, FRED_*, DOCKER_* are reserved)`,\n );\n }\n if (typeof value !== 'string') {\n errors.push(`${scope}.env[\"${name}\"]: value must be a string`);\n }\n }\n }\n }\n\n // labels: fred.* prefix is reserved\n if ('labels' in service) {\n if (!isPlainObject(service.labels)) {\n errors.push(`${scope}.labels: must be an object`);\n } else {\n for (const key of Object.keys(service.labels)) {\n if (key.startsWith('fred.')) {\n errors.push(\n `${scope}.labels[\"${key}\"]: reserved prefix 'fred.' is not allowed`,\n );\n }\n }\n }\n }\n\n // tmpfs\n if ('tmpfs' in service) {\n if (!Array.isArray(service.tmpfs)) {\n errors.push(`${scope}.tmpfs: must be an array of strings`);\n } else {\n if (service.tmpfs.length > 4) {\n errors.push(\n `${scope}.tmpfs: too many mounts (${service.tmpfs.length}), maximum is 4`,\n );\n }\n const seen = new Set<string>();\n for (const path of service.tmpfs) {\n if (typeof path !== 'string') {\n errors.push(`${scope}.tmpfs: entries must be strings`);\n continue;\n }\n if (!path.startsWith('/')) {\n errors.push(`${scope}.tmpfs[\"${path}\"]: must be an absolute path`);\n }\n if (TMPFS_BLOCKED.has(path)) {\n errors.push(\n `${scope}.tmpfs[\"${path}\"]: path is managed by the backend`,\n );\n }\n for (const prefix of TMPFS_BLOCKED_PREFIXES) {\n if (path === prefix || path.startsWith(`${prefix}/`)) {\n errors.push(\n `${scope}.tmpfs[\"${path}\"]: path is under sensitive path ${prefix}`,\n );\n }\n }\n if (seen.has(path)) {\n errors.push(`${scope}.tmpfs[\"${path}\"]: duplicate mount`);\n }\n seen.add(path);\n }\n }\n }\n\n // user\n if ('user' in service) {\n if (typeof service.user !== 'string') {\n errors.push(`${scope}.user: must be a string`);\n } else if (service.user.length > 0) {\n const u = service.user;\n if (/\\s/.test(u)) {\n errors.push(`${scope}.user: cannot contain whitespace`);\n } else {\n const colon = u.indexOf(':');\n if (colon === 0 || colon === u.length - 1) {\n errors.push(`${scope}.user: user/group parts cannot be empty`);\n }\n }\n }\n }\n\n // health_check\n if ('health_check' in service) {\n if (!isPlainObject(service.health_check)) {\n errors.push(`${scope}.health_check: must be an object`);\n } else {\n const hc = service.health_check;\n for (const key of Object.keys(hc)) {\n if (!HEALTH_CHECK_KEYS.has(key)) {\n errors.push(`${scope}.health_check.${key}: unknown field`);\n }\n }\n if (!('test' in hc)) {\n errors.push(`${scope}.health_check.test: required`);\n } else if (\n !Array.isArray(hc.test) ||\n hc.test.length === 0 ||\n !hc.test.every((s) => typeof s === 'string')\n ) {\n errors.push(`${scope}.health_check.test: must be a non-empty string[]`);\n } else {\n const head = hc.test[0];\n if (!HEALTH_CHECK_TYPES.has(head)) {\n errors.push(\n `${scope}.health_check.test[0]: must be CMD, CMD-SHELL, or NONE`,\n );\n } else if (head !== 'NONE' && hc.test.length < 2) {\n errors.push(\n `${scope}.health_check.test: ${head} requires at least one argument after the type`,\n );\n } else if (head === 'NONE' && hc.test.length > 1) {\n errors.push(\n `${scope}.health_check.test: NONE accepts no further arguments`,\n );\n }\n }\n if (\n 'retries' in hc &&\n (typeof hc.retries !== 'number' ||\n !Number.isInteger(hc.retries) ||\n hc.retries < 0)\n ) {\n errors.push(\n `${scope}.health_check.retries: must be a non-negative integer`,\n );\n }\n }\n }\n\n // depends_on: only valid in stack\n if ('depends_on' in service) {\n if (!isPlainObject(service.depends_on)) {\n errors.push(`${scope}.depends_on: must be an object`);\n } else {\n const entries = Object.entries(service.depends_on);\n if (entries.length > 0 && !inStack) {\n errors.push(\n `${scope}.depends_on: only allowed inside a stack manifest (services map)`,\n );\n }\n for (const [name, cond] of entries) {\n if (!isPlainObject(cond)) {\n errors.push(`${scope}.depends_on[\"${name}\"]: must be an object`);\n continue;\n }\n for (const k of Object.keys(cond)) {\n if (k !== 'condition') {\n errors.push(`${scope}.depends_on[\"${name}\"].${k}: unknown field`);\n }\n }\n if (\n typeof cond.condition !== 'string' ||\n !DEPENDS_ON_CONDITIONS.has(cond.condition)\n ) {\n errors.push(\n `${scope}.depends_on[\"${name}\"].condition: must be \"service_started\" or \"service_healthy\"`,\n );\n }\n }\n }\n }\n\n // expose\n if ('expose' in service) {\n if (!Array.isArray(service.expose)) {\n errors.push(`${scope}.expose: must be an array of port strings`);\n } else {\n const seen = new Set<string>();\n for (const p of service.expose) {\n if (typeof p !== 'string' || !EXPOSE_PORT_RE.test(p)) {\n errors.push(\n `${scope}.expose[\"${String(p)}\"]: must be a port number string (1-65535)`,\n );\n } else if (!validatePort(p)) {\n errors.push(`${scope}.expose[\"${p}\"]: port out of range`);\n }\n if (seen.has(String(p))) {\n errors.push(`${scope}.expose[\"${String(p)}\"]: duplicate`);\n }\n seen.add(String(p));\n }\n }\n }\n\n // init / stop_grace_period: minimal type checks (range validation is runtime).\n if ('init' in service && typeof service.init !== 'boolean') {\n errors.push(`${scope}.init: must be a boolean`);\n }\n if ('stop_grace_period' in service) {\n const v = service.stop_grace_period;\n if (typeof v !== 'string' && typeof v !== 'number') {\n errors.push(\n `${scope}.stop_grace_period: must be a duration string or integer nanoseconds`,\n );\n }\n }\n}\n\n/**\n * Validates a parsed manifest object against the documented Fred rules.\n * Pre-flight only: catches the constraints documented in the public spec\n * (env-name blocklist, label prefix, port format, tmpfs limits, RFC 1123\n * service names, depends_on placement, unknown fields). The provider does\n * the canonical validation server-side; this helper exists so agents can\n * reject obviously-broken manifests before paying for a lease.\n */\nexport function validateManifest(manifest: unknown): ManifestValidationResult {\n const errors: string[] = [];\n\n if (!isPlainObject(manifest)) {\n return {\n valid: false,\n errors: ['manifest must be a JSON object'],\n format: null,\n };\n }\n\n if (isStackManifest(manifest)) {\n // Stack manifest — only `services` is allowed at the top level.\n for (const key of Object.keys(manifest)) {\n if (key !== 'services') {\n errors.push(`${key}: unknown top-level field for stack manifest`);\n }\n }\n const serviceNames = Object.keys(manifest.services);\n if (serviceNames.length === 0) {\n errors.push('services: at least one service is required');\n }\n for (const name of serviceNames) {\n if (!validateServiceName(name)) {\n errors.push(\n `services[\"${name}\"]: must be a valid RFC 1123 DNS label (1-63 chars, lowercase alphanumeric + hyphens)`,\n );\n }\n validateService(\n manifest.services[name],\n `services[\"${name}\"]`,\n true,\n errors,\n );\n }\n // Cross-service: depends_on must only reference defined services and not\n // self. Set lookup keeps the cross-check linear in total dep edges\n // instead of O(services * deps * services).\n const serviceNameSet = new Set(serviceNames);\n for (const [name, svc] of Object.entries(manifest.services)) {\n if (isPlainObject(svc) && isPlainObject(svc.depends_on)) {\n for (const dep of Object.keys(svc.depends_on)) {\n if (dep === name) {\n errors.push(\n `services[\"${name}\"].depends_on[\"${dep}\"]: a service cannot depend on itself`,\n );\n } else if (!serviceNameSet.has(dep)) {\n errors.push(\n `services[\"${name}\"].depends_on[\"${dep}\"]: references undefined service`,\n );\n }\n }\n }\n }\n return {\n valid: errors.length === 0,\n errors,\n format: 'stack',\n };\n }\n\n // Single-service manifest.\n validateService(manifest, '', false, errors);\n return {\n valid: errors.length === 0,\n errors,\n format: 'single',\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/server/fetch-gate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Env var gating fred's SSRF-guarded fetch. Default ON; operators opt out
|
|
4
|
+
* with `MANIFEST_FRED_FETCH_GUARDED=0`. Independent of the agent server's
|
|
5
|
+
* `MANIFEST_AGENT_FETCH_GUARDED` so each standalone server can be toggled
|
|
6
|
+
* on its own (ENG-268).
|
|
7
|
+
*/
|
|
8
|
+
declare const FRED_FETCH_GUARDED_ENV = "MANIFEST_FRED_FETCH_GUARDED";
|
|
9
|
+
/**
|
|
10
|
+
* Decide which `fetch` the fred MCP server injects into its tool layer.
|
|
11
|
+
*
|
|
12
|
+
* - Guard ON (default) + Node runtime → an SSRF-guarded fetch (blocks
|
|
13
|
+
* provider URLs that resolve to private/reserved ranges; closes the
|
|
14
|
+
* DNS-rebinding window at connect time).
|
|
15
|
+
* - Guard ON + non-Node runtime → `undefined` (so the HTTP layer falls back
|
|
16
|
+
* to `globalThis.fetch`), with a warning — `createGuardedFetch` is
|
|
17
|
+
* Node-only and would otherwise throw.
|
|
18
|
+
* - Guard OFF (`MANIFEST_FRED_FETCH_GUARDED=0`) → `undefined` (unguarded
|
|
19
|
+
* `globalThis.fetch` fallback).
|
|
20
|
+
* - Unrecognized env value → throws `INVALID_CONFIG` (never silently
|
|
21
|
+
* disables the guard).
|
|
22
|
+
*
|
|
23
|
+
* Returning `undefined` rather than `globalThis.fetch` keeps the injection
|
|
24
|
+
* a no-op for the HTTP layer's existing `fetchFn = globalThis.fetch`
|
|
25
|
+
* default, so library consumers that pass their own `fetchFn` are
|
|
26
|
+
* unaffected.
|
|
27
|
+
*
|
|
28
|
+
* @param envValue - Raw `MANIFEST_FRED_FETCH_GUARDED` value.
|
|
29
|
+
* @param isNode - Whether the current runtime is Node.js.
|
|
30
|
+
*/
|
|
31
|
+
declare function resolveGuardedFetch(envValue: string | undefined, isNode: boolean): typeof globalThis.fetch | undefined;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FRED_FETCH_GUARDED_ENV, resolveGuardedFetch };
|
|
34
|
+
//# sourceMappingURL=fetch-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-gate.d.ts","names":[],"sources":["../../src/server/fetch-gate.ts"],"mappings":";;AAWA;;;;AAAmC;cAAtB,sBAAA;;;;;;;;;AA2Ba;;;;;;;;;;;;;;iBAHV,mBAAA,CACd,QAAA,sBACA,MAAA,mBACQ,UAAA,CAAW,KAAK"}
|