@finchtech/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/finch.js +140 -148
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@ Install the public npm package with `npm install -g @finchtech/cli`, then run `f
|
|
|
4
4
|
|
|
5
5
|
The Finch Agent CLI stores wallet keys, AgentCLI Session bearers, and MCP grant bearers only in an owner-only `FINCHTECH_HOME`, which defaults to `~/.finchtech`; secrets are never accepted as command arguments or emitted to stdout.
|
|
6
6
|
|
|
7
|
-
The command groups are `wallet`, `login/status/logout`, `site`, `mcp doctor`, `mcp
|
|
7
|
+
The command groups are `wallet`, `login/status/logout`, `site`, `mcp doctor`, `mcp authorize`, `mcp grant`, `creator`, `credentials`, `intent`, `operation show`, `purchase`, `task`, and `skill`. Run `finch --help` for the exact command list as one JSON value.
|
|
8
8
|
|
|
9
9
|
`finch site open` creates a sixty-second one-use handoff from the active AgentCLI Session and opens the exact configured Finch origin in the local default Browser. `finch site login <REQUEST_URL>` binds a website-created five-minute request once, then opens the same confirmation page. Both commands require the wallet, CLI, and graphical Browser on the user's machine. A launch failure returns `BROWSER_OPEN_FAILED` without printing the secret completion URL. The Browser receives only a normal seven-day Finch Web Session; wallet signing, broadcast, and market confirmation remain local CLI actions.
|
|
10
10
|
|
|
11
|
-
`finch mcp doctor` is read-only. It verifies that the active CLI authority, AgentCLI Session, local wallet, MCP challenge, and source-controlled
|
|
11
|
+
`finch mcp doctor` is read-only. It verifies that the active CLI authority, AgentCLI Session, local wallet, MCP challenge, OAuth metadata, and source-controlled persistent-Harness manifest agree. The Agent Harness owns Remote MCP configuration, OAuth credentials, and refresh; the CLI does not configure or launch Codex and never reads Harness OAuth material. During the Harness's native OAuth flow, the authorization page supplies the exact `finch mcp authorize <REQUEST_URL>` command. That command uses only the active local AgentCLI Session, verifies the callback against the registered redirect and state, and opens it without returning its one-use code. It never uses a Browser Session, Social account, or extension wallet as Agent authority. Configure `https://www.finchtech.ai/mcp` once through the Harness, then use MCP `identity_actor_get` together with `finch status` to verify exact Account, wallet, and environment alignment before custody.
|
|
12
12
|
|
|
13
13
|
Ordinary installs require no profile file or environment selection. Network commands use the reviewed built-in PROD authority at `https://www.finchtech.ai` and its source-controlled mainnet deployments. The public command tree has no profile, environment, origin, or chain selector, and conflicting local deployment state fails closed. `intent approve` signs only the shared publication-v2 or Local-purchase-v1 EIP-712 authorization; it cannot sign arbitrary transaction calldata.
|
|
14
14
|
|
package/dist/finch.js
CHANGED
|
@@ -3738,7 +3738,7 @@ var init_testnet_purchase_v1 = __esm({
|
|
|
3738
3738
|
});
|
|
3739
3739
|
testnetPurchaseTransactionResponseSchema = strictObjectSchema({
|
|
3740
3740
|
operationId: canonicalUuidSchema,
|
|
3741
|
-
chainId: {
|
|
3741
|
+
chainId: { type: "string", enum: ["84532", "8453"] },
|
|
3742
3742
|
deploymentHash: hex32Schema,
|
|
3743
3743
|
paymentTokenAddress: evmAddressSchema,
|
|
3744
3744
|
totalPaidAtomic: positiveIntegerStringSchema,
|
|
@@ -28380,6 +28380,7 @@ function getAgentServiceDeployment(environment3) {
|
|
|
28380
28380
|
);
|
|
28381
28381
|
return Object.freeze({
|
|
28382
28382
|
chainId: value.network.chainId,
|
|
28383
|
+
networkName: value.network.name,
|
|
28383
28384
|
protocolAddress: value.contracts.protocol,
|
|
28384
28385
|
protocolImplementationAddress: value.contracts.protocolImplementation ?? null,
|
|
28385
28386
|
protocolProxyAdminAddress: value.contracts.protocolProxyAdmin ?? null,
|
|
@@ -36387,7 +36388,7 @@ function parseRemoteError(response, value) {
|
|
|
36387
36388
|
}
|
|
36388
36389
|
return envelope;
|
|
36389
36390
|
}
|
|
36390
|
-
var import_ajv, CLI_MERCHANT_OPERATION_IDS, CliHttpError, ajv, validatorCache, AgentCliHttpClient;
|
|
36391
|
+
var import_ajv, CLI_MERCHANT_OPERATION_IDS, CliHttpError, ajv, validatorCache, oauthAuthorizationResponseSchema, oauthErrorResponseSchema, AgentCliHttpClient;
|
|
36391
36392
|
var init_api2 = __esm({
|
|
36392
36393
|
"src/api.ts"() {
|
|
36393
36394
|
"use strict";
|
|
@@ -36433,6 +36434,29 @@ var init_api2 = __esm({
|
|
|
36433
36434
|
ajv.addFormat("date-time", (value) => Number.isFinite(Date.parse(value)));
|
|
36434
36435
|
ajv.addFormat("uri", (value) => URL.canParse(value));
|
|
36435
36436
|
validatorCache = /* @__PURE__ */ new WeakMap();
|
|
36437
|
+
oauthAuthorizationResponseSchema = {
|
|
36438
|
+
type: "object",
|
|
36439
|
+
additionalProperties: false,
|
|
36440
|
+
required: ["authorized", "accountId", "walletAddress", "clientName", "scopes", "callbackUrl"],
|
|
36441
|
+
properties: {
|
|
36442
|
+
authorized: { const: true },
|
|
36443
|
+
accountId: { type: "string", format: "uuid" },
|
|
36444
|
+
walletAddress: { type: "string", pattern: "^0x[0-9a-f]{40}$" },
|
|
36445
|
+
clientName: { type: "string", minLength: 1, maxLength: 128 },
|
|
36446
|
+
scopes: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", minLength: 1 } },
|
|
36447
|
+
callbackUrl: { type: "string", format: "uri" }
|
|
36448
|
+
}
|
|
36449
|
+
};
|
|
36450
|
+
oauthErrorResponseSchema = {
|
|
36451
|
+
type: "object",
|
|
36452
|
+
additionalProperties: false,
|
|
36453
|
+
required: ["error", "error_description"],
|
|
36454
|
+
properties: {
|
|
36455
|
+
error: { type: "string", minLength: 1 },
|
|
36456
|
+
error_description: { type: "string", minLength: 1 },
|
|
36457
|
+
trace_id: { type: "string", format: "uuid" }
|
|
36458
|
+
}
|
|
36459
|
+
};
|
|
36436
36460
|
AgentCliHttpClient = class {
|
|
36437
36461
|
origin;
|
|
36438
36462
|
fetchImplementation;
|
|
@@ -36527,6 +36551,24 @@ var init_api2 = __esm({
|
|
|
36527
36551
|
throw new CliHttpError("CLI_RESPONSE_INVALID", challenge2.status);
|
|
36528
36552
|
}
|
|
36529
36553
|
await challenge2.body?.cancel().catch(() => void 0);
|
|
36554
|
+
const resourceResponse = await this.publicFetch("/.well-known/oauth-protected-resource");
|
|
36555
|
+
if (resourceResponse.status !== 200) {
|
|
36556
|
+
await resourceResponse.body?.cancel().catch(() => void 0);
|
|
36557
|
+
throw new CliHttpError("CLI_REMOTE_UNAVAILABLE", resourceResponse.status);
|
|
36558
|
+
}
|
|
36559
|
+
const resource = jsonObject(await readBoundedJson(resourceResponse));
|
|
36560
|
+
if (resource.resource !== `${this.origin}/` || JSON.stringify(resource.authorization_servers) !== JSON.stringify([this.origin])) {
|
|
36561
|
+
throw new CliHttpError("CLI_RESPONSE_INVALID", resourceResponse.status);
|
|
36562
|
+
}
|
|
36563
|
+
const authorizationResponse = await this.publicFetch("/.well-known/oauth-authorization-server");
|
|
36564
|
+
if (authorizationResponse.status !== 200) {
|
|
36565
|
+
await authorizationResponse.body?.cancel().catch(() => void 0);
|
|
36566
|
+
throw new CliHttpError("CLI_REMOTE_UNAVAILABLE", authorizationResponse.status);
|
|
36567
|
+
}
|
|
36568
|
+
const authorization = jsonObject(await readBoundedJson(authorizationResponse));
|
|
36569
|
+
if (authorization.issuer !== this.origin || authorization.authorization_endpoint !== `${this.origin}/oauth/authorize` || authorization.token_endpoint !== `${this.origin}/oauth/token` || authorization.registration_endpoint !== `${this.origin}/oauth/register` || JSON.stringify(authorization.response_types_supported) !== JSON.stringify(["code"]) || JSON.stringify(authorization.grant_types_supported) !== JSON.stringify(["authorization_code", "refresh_token"]) || JSON.stringify(authorization.token_endpoint_auth_methods_supported) !== JSON.stringify(["none"]) || JSON.stringify(authorization.code_challenge_methods_supported) !== JSON.stringify(["S256"])) {
|
|
36570
|
+
throw new CliHttpError("CLI_RESPONSE_INVALID", authorizationResponse.status);
|
|
36571
|
+
}
|
|
36530
36572
|
const manifestResponse = await this.publicFetch("/mcp/client-compatibility.json");
|
|
36531
36573
|
if (manifestResponse.status !== 200) {
|
|
36532
36574
|
await manifestResponse.body?.cancel().catch(() => void 0);
|
|
@@ -36539,15 +36581,16 @@ var init_api2 = __esm({
|
|
|
36539
36581
|
throw new CliHttpError("CLI_RESPONSE_INVALID", manifestResponse.status);
|
|
36540
36582
|
}
|
|
36541
36583
|
const client = jsonObject(manifest.clients[0]);
|
|
36542
|
-
const
|
|
36584
|
+
const install = jsonObject(client.install);
|
|
36585
|
+
const authorize = jsonObject(client.authorize);
|
|
36543
36586
|
const verify = jsonObject(client.verify);
|
|
36544
|
-
if (manifest.schemaVersion !==
|
|
36587
|
+
if (manifest.schemaVersion !== 3 || manifest.manifestId !== "finch.remote-mcp-clients" || mcp.serverName !== "finch" || mcp.url !== mcpUrl || mcp.transport !== "streamable_http" || mcp.authorization !== "oauth" || source.repository !== "https://github.com/FinchipAI/Finch-Site" || source.path !== "apps/mcp/src/client-compatibility.ts" || client.id !== "codex" || client.supportLevel !== "verified" || install.kind !== "native_cli" || install.executable !== "codex" || JSON.stringify(install.arguments) !== JSON.stringify(["mcp", "add", "finch", "--url", mcpUrl]) || authorize.kind !== "native_cli" || authorize.executable !== "codex" || JSON.stringify(authorize.arguments) !== JSON.stringify(["mcp", "login", "finch"]) || verify.kind !== "harness_ui" || verify.surface !== "mcp_tools" || client.reload !== "new_session") {
|
|
36545
36588
|
throw new CliHttpError("CLI_RESPONSE_INVALID", manifestResponse.status);
|
|
36546
36589
|
}
|
|
36547
36590
|
return Object.freeze({
|
|
36548
36591
|
url: mcpUrl,
|
|
36549
36592
|
transport: "streamable_http",
|
|
36550
|
-
authorization: "
|
|
36593
|
+
authorization: "oauth",
|
|
36551
36594
|
compatibilityManifestUrl,
|
|
36552
36595
|
supportedClients: Object.freeze(["codex"])
|
|
36553
36596
|
});
|
|
@@ -36680,6 +36723,26 @@ var init_api2 = __esm({
|
|
|
36680
36723
|
bearer
|
|
36681
36724
|
});
|
|
36682
36725
|
}
|
|
36726
|
+
authorizeMcp(requestPath, bearer) {
|
|
36727
|
+
let parsed;
|
|
36728
|
+
try {
|
|
36729
|
+
parsed = new URL(requestPath, this.origin);
|
|
36730
|
+
} catch {
|
|
36731
|
+
throw new TypeError("MCP authorization request path is invalid.");
|
|
36732
|
+
}
|
|
36733
|
+
if (parsed.origin !== this.origin || parsed.pathname !== "/oauth/authorize" || parsed.hash !== "" || requestPath !== `${parsed.pathname}${parsed.search}` || parsed.search === "") {
|
|
36734
|
+
throw new TypeError("MCP authorization request path is invalid.");
|
|
36735
|
+
}
|
|
36736
|
+
return this.request({
|
|
36737
|
+
method: "post",
|
|
36738
|
+
path: requestPath,
|
|
36739
|
+
successStatus: 200,
|
|
36740
|
+
responseSchema: oauthAuthorizationResponseSchema,
|
|
36741
|
+
statusOnlyErrorSchema: oauthErrorResponseSchema,
|
|
36742
|
+
bearer,
|
|
36743
|
+
body: {}
|
|
36744
|
+
});
|
|
36745
|
+
}
|
|
36683
36746
|
createChallenge(input) {
|
|
36684
36747
|
const operation4 = IDENTITY_V1_OPERATIONS.challenge;
|
|
36685
36748
|
return this.request({
|
|
@@ -38857,7 +38920,6 @@ var init_application = __esm({
|
|
|
38857
38920
|
taskProtocolSender;
|
|
38858
38921
|
skillIntentSender;
|
|
38859
38922
|
skillLocalFlow;
|
|
38860
|
-
mcpHarness;
|
|
38861
38923
|
browserLauncher;
|
|
38862
38924
|
constructor(dependencies) {
|
|
38863
38925
|
this.api = dependencies.api;
|
|
@@ -38872,7 +38934,6 @@ var init_application = __esm({
|
|
|
38872
38934
|
this.taskProtocolSender = dependencies.taskProtocolSender;
|
|
38873
38935
|
this.skillIntentSender = dependencies.skillIntentSender;
|
|
38874
38936
|
this.skillLocalFlow = dependencies.skillLocalFlow;
|
|
38875
|
-
this.mcpHarness = dependencies.mcpHarness;
|
|
38876
38937
|
this.browserLauncher = dependencies.browserLauncher;
|
|
38877
38938
|
}
|
|
38878
38939
|
async authorizedProfile() {
|
|
@@ -39052,11 +39113,11 @@ var init_application = __esm({
|
|
|
39052
39113
|
await required4(this.state.activateSession, "state.activateSession").call(this.state, response);
|
|
39053
39114
|
return response;
|
|
39054
39115
|
}
|
|
39055
|
-
async
|
|
39116
|
+
async verifiedAgentCliAuthority() {
|
|
39056
39117
|
const profile = await this.authorizedProfile();
|
|
39057
39118
|
const session = await this.networkSession(profile);
|
|
39058
39119
|
const wallet = await required4(this.state.readWalletAddress, "state.readWalletAddress").call(this.state);
|
|
39059
|
-
const remote = asObject(await required4(this.api.getSession, "api.getSession").call(this.api, session.rawBearer), "
|
|
39120
|
+
const remote = asObject(await required4(this.api.getSession, "api.getSession").call(this.api, session.rawBearer), "AgentCLI Session");
|
|
39060
39121
|
if (session.walletAddress.toLowerCase() !== wallet.walletAddress.toLowerCase() || remote.accountId !== session.accountId || String(remote.walletAddress).toLowerCase() !== wallet.walletAddress.toLowerCase() || remote.environment !== profile.environment || remote.audience !== "agent_cli") {
|
|
39061
39122
|
throw new CliAuthorityError();
|
|
39062
39123
|
}
|
|
@@ -39081,7 +39142,7 @@ var init_application = __esm({
|
|
|
39081
39142
|
});
|
|
39082
39143
|
}
|
|
39083
39144
|
async openSite() {
|
|
39084
|
-
const authority = await this.
|
|
39145
|
+
const authority = await this.verifiedAgentCliAuthority();
|
|
39085
39146
|
return this.openBrowserHandoff(authority);
|
|
39086
39147
|
}
|
|
39087
39148
|
async loginSite(requestUrl) {
|
|
@@ -39096,9 +39157,67 @@ var init_application = __esm({
|
|
|
39096
39157
|
if (parsed.origin !== profile.origin || requestUrl !== `${profile.origin}${parsed.pathname}` || parsed.search !== "" || parsed.hash !== "" || match === null) {
|
|
39097
39158
|
throw new TypeError("Browser login request origin or path does not match the active profile origin.");
|
|
39098
39159
|
}
|
|
39099
|
-
const authority = await this.
|
|
39160
|
+
const authority = await this.verifiedAgentCliAuthority();
|
|
39100
39161
|
return this.openBrowserHandoff({ ...authority, requestId: match[1] });
|
|
39101
39162
|
}
|
|
39163
|
+
async authorizeMcp(requestUrl) {
|
|
39164
|
+
const profile = await this.authorizedProfile();
|
|
39165
|
+
let request;
|
|
39166
|
+
try {
|
|
39167
|
+
request = new URL(requestUrl);
|
|
39168
|
+
} catch {
|
|
39169
|
+
throw new TypeError("MCP OAuth request URL is invalid.");
|
|
39170
|
+
}
|
|
39171
|
+
const redirectValues = request.searchParams.getAll("redirect_uri");
|
|
39172
|
+
const stateValues = request.searchParams.getAll("state");
|
|
39173
|
+
const scopeValues = request.searchParams.getAll("scope");
|
|
39174
|
+
if (request.origin !== profile.origin || request.pathname !== "/oauth/authorize" || request.username !== "" || request.password !== "" || request.hash !== "" || requestUrl !== `${profile.origin}${request.pathname}${request.search}` || redirectValues.length !== 1 || stateValues.length > 1 || scopeValues.length !== 1) {
|
|
39175
|
+
throw new TypeError("MCP OAuth request URL does not match the active profile authority.");
|
|
39176
|
+
}
|
|
39177
|
+
let registeredRedirect;
|
|
39178
|
+
try {
|
|
39179
|
+
registeredRedirect = new URL(redirectValues[0]);
|
|
39180
|
+
} catch {
|
|
39181
|
+
throw new TypeError("MCP OAuth redirect URI is invalid.");
|
|
39182
|
+
}
|
|
39183
|
+
if (registeredRedirect.username !== "" || registeredRedirect.password !== "" || registeredRedirect.hash !== "") {
|
|
39184
|
+
throw new TypeError("MCP OAuth redirect URI is invalid.");
|
|
39185
|
+
}
|
|
39186
|
+
const authority = await this.verifiedAgentCliAuthority();
|
|
39187
|
+
const response = asObject(await required4(this.api.authorizeMcp, "api.authorizeMcp").call(this.api, `${request.pathname}${request.search}`, authority.session.rawBearer), "MCP authorization");
|
|
39188
|
+
if (response.authorized !== true || response.accountId !== authority.session.accountId || String(response.walletAddress).toLowerCase() !== authority.walletAddress || typeof response.clientName !== "string" || response.clientName.length < 1 || response.clientName.length > 128 || !Array.isArray(response.scopes) || response.scopes.some((scope) => typeof scope !== "string" || scope.length < 1) || typeof response.callbackUrl !== "string") {
|
|
39189
|
+
throw new CliAuthorityError();
|
|
39190
|
+
}
|
|
39191
|
+
const requestedScopes = scopeValues[0].split(" ").filter(Boolean).sort();
|
|
39192
|
+
const returnedScopes = [...response.scopes].sort();
|
|
39193
|
+
if (requestedScopes.length < 1 || requestedScopes.length !== returnedScopes.length || requestedScopes.some((scope, index2) => scope !== returnedScopes[index2])) {
|
|
39194
|
+
throw new CliAuthorityError();
|
|
39195
|
+
}
|
|
39196
|
+
let callback;
|
|
39197
|
+
try {
|
|
39198
|
+
callback = new URL(response.callbackUrl);
|
|
39199
|
+
} catch {
|
|
39200
|
+
throw new TypeError("MCP OAuth callback is invalid.");
|
|
39201
|
+
}
|
|
39202
|
+
const codes = callback.searchParams.getAll("code");
|
|
39203
|
+
if (codes.length !== 1 || !/^[A-Za-z0-9_-]{43}$/u.test(codes[0])) {
|
|
39204
|
+
throw new TypeError("MCP OAuth callback is invalid.");
|
|
39205
|
+
}
|
|
39206
|
+
const expectedCallback = new URL(registeredRedirect.href);
|
|
39207
|
+
expectedCallback.searchParams.set("code", codes[0]);
|
|
39208
|
+
if (stateValues.length === 1) expectedCallback.searchParams.set("state", stateValues[0]);
|
|
39209
|
+
if (callback.href !== expectedCallback.href) {
|
|
39210
|
+
throw new TypeError("MCP OAuth callback does not match the registered redirect and state.");
|
|
39211
|
+
}
|
|
39212
|
+
await required4(this.browserLauncher, "browser launcher").open(callback.href);
|
|
39213
|
+
return Object.freeze({
|
|
39214
|
+
authorized: true,
|
|
39215
|
+
clientName: response.clientName,
|
|
39216
|
+
walletAddress: authority.walletAddress,
|
|
39217
|
+
environment: profile.environment,
|
|
39218
|
+
scopes: Object.freeze([...returnedScopes])
|
|
39219
|
+
});
|
|
39220
|
+
}
|
|
39102
39221
|
async createWallet() {
|
|
39103
39222
|
const privateKey3 = generatePrivateKey();
|
|
39104
39223
|
const walletAddress2 = privateKeyToAccount(privateKey3).address.toLowerCase();
|
|
@@ -39255,7 +39374,7 @@ var init_application = __esm({
|
|
|
39255
39374
|
throw new TypeError("MCP doctor Session authority does not match the active wallet and profile.");
|
|
39256
39375
|
}
|
|
39257
39376
|
const mcp = await required4(this.api.inspectRemoteMcp, "api.inspectRemoteMcp").call(this.api);
|
|
39258
|
-
if (mcp.url !== `${profile.origin}/mcp` || mcp.authorization !== "
|
|
39377
|
+
if (mcp.url !== `${profile.origin}/mcp` || mcp.authorization !== "oauth" || JSON.stringify(mcp.supportedClients) !== JSON.stringify(["codex"])) {
|
|
39259
39378
|
throw new TypeError("MCP doctor discovery does not match the authoritative profile.");
|
|
39260
39379
|
}
|
|
39261
39380
|
return Object.freeze({
|
|
@@ -39270,64 +39389,6 @@ var init_application = __esm({
|
|
|
39270
39389
|
mcp
|
|
39271
39390
|
});
|
|
39272
39391
|
}
|
|
39273
|
-
async launchMcpHarness(client) {
|
|
39274
|
-
if (client !== "codex") throw new TypeError("Only the verified Codex MCP handoff is supported.");
|
|
39275
|
-
const profile = await this.authorizedProfile();
|
|
39276
|
-
const session = await this.networkSession(profile);
|
|
39277
|
-
const wallet = await required4(this.state.readWalletAddress, "state.readWalletAddress").call(this.state);
|
|
39278
|
-
const remoteSession = asObject(await required4(this.api.getSession, "api.getSession").call(this.api, session.rawBearer), "MCP handoff Session");
|
|
39279
|
-
if (remoteSession.sessionId !== session.sessionId || remoteSession.accountId !== session.accountId || remoteSession.walletAddress !== session.walletAddress || wallet.walletAddress !== session.walletAddress || remoteSession.environment !== session.environment || remoteSession.audience !== "agent_cli" || typeof remoteSession.sessionExpiresAt !== "string" || Date.parse(remoteSession.sessionExpiresAt) <= Date.now()) {
|
|
39280
|
-
throw new TypeError("MCP handoff Session authority does not match the active wallet and profile.");
|
|
39281
|
-
}
|
|
39282
|
-
const mcp = await required4(this.api.inspectRemoteMcp, "api.inspectRemoteMcp").call(this.api);
|
|
39283
|
-
if (mcp.url !== `${profile.origin}/mcp` || mcp.authorization !== "agent_cli_session_handoff" || !mcp.supportedClients.includes(client)) {
|
|
39284
|
-
throw new TypeError("MCP handoff manifest does not match the authoritative profile.");
|
|
39285
|
-
}
|
|
39286
|
-
const readGrant = required4(this.state.readMcpGrantForScopes, "state.readMcpGrantForScopes");
|
|
39287
|
-
let grant = await readGrant.call(this.state, {
|
|
39288
|
-
scopes: identityScopeValues,
|
|
39289
|
-
accountId: session.accountId,
|
|
39290
|
-
environment: session.environment
|
|
39291
|
-
});
|
|
39292
|
-
if (grant === null) {
|
|
39293
|
-
const parentExpiry = Date.parse(remoteSession.sessionExpiresAt);
|
|
39294
|
-
const expiry = Math.min(Date.now() + 12 * 60 * 60 * 1e3, parentExpiry - 1e3);
|
|
39295
|
-
if (expiry <= Date.now() + 6e4) {
|
|
39296
|
-
throw new TypeError("The AgentCLI Session expires too soon to launch Finch MCP. Log in again.");
|
|
39297
|
-
}
|
|
39298
|
-
await this.createMcpGrant(identityScopeValues, new Date(expiry).toISOString());
|
|
39299
|
-
grant = await readGrant.call(this.state, {
|
|
39300
|
-
scopes: identityScopeValues,
|
|
39301
|
-
accountId: session.accountId,
|
|
39302
|
-
environment: session.environment
|
|
39303
|
-
});
|
|
39304
|
-
if (grant === null) throw new TypeError("The new MCP handoff grant is unavailable in protected local state.");
|
|
39305
|
-
}
|
|
39306
|
-
parseCanonicalUuid(grant.grantId, "MCP handoff grantId");
|
|
39307
|
-
if (!/^[A-Za-z0-9_-]{43}$/u.test(grant.rawBearer)) {
|
|
39308
|
-
throw new TypeError("The selected MCP handoff grant is invalid.");
|
|
39309
|
-
}
|
|
39310
|
-
const remoteGrant = asObject(await required4(this.api.showGrant, "api.showGrant").call(this.api, grant.grantId, session.rawBearer), "MCP handoff grant");
|
|
39311
|
-
if (remoteGrant.grantId !== grant.grantId || remoteGrant.audience !== "finch_mcp" || remoteGrant.environment !== session.environment || remoteGrant.status !== "active" || remoteGrant.revokedAt !== null || remoteGrant.expiresAt !== grant.expiresAt || remoteGrant.parentSessionExpiresAt !== remoteSession.sessionExpiresAt || JSON.stringify(remoteGrant.scopes) !== JSON.stringify(identityScopeValues) || Date.parse(grant.expiresAt) <= Date.now()) {
|
|
39312
|
-
throw new TypeError("MCP handoff grant does not inherit the active parent Session authority.");
|
|
39313
|
-
}
|
|
39314
|
-
const launched = await required4(this.mcpHarness, "MCP Harness").launch({
|
|
39315
|
-
client,
|
|
39316
|
-
mcpUrl: mcp.url,
|
|
39317
|
-
bearer: grant.rawBearer
|
|
39318
|
-
});
|
|
39319
|
-
return Object.freeze({
|
|
39320
|
-
client,
|
|
39321
|
-
launched: true,
|
|
39322
|
-
browserAuthorization: false,
|
|
39323
|
-
exitCode: launched.exitCode,
|
|
39324
|
-
environment: session.environment,
|
|
39325
|
-
accountId: session.accountId,
|
|
39326
|
-
walletAddress: session.walletAddress,
|
|
39327
|
-
grantId: grant.grantId,
|
|
39328
|
-
grantExpiresAt: grant.expiresAt
|
|
39329
|
-
});
|
|
39330
|
-
}
|
|
39331
39392
|
async assertNoBlockingRecoveries() {
|
|
39332
39393
|
const recoveries = await this.state.listBlockingRecoveries?.call(this.state) ?? [];
|
|
39333
39394
|
if (recoveries.length > 0) {
|
|
@@ -40109,7 +40170,7 @@ var init_package = __esm({
|
|
|
40109
40170
|
"package.json"() {
|
|
40110
40171
|
package_default = {
|
|
40111
40172
|
name: "@finchtech/cli",
|
|
40112
|
-
version: "0.
|
|
40173
|
+
version: "0.2.0",
|
|
40113
40174
|
description: "Finch Agent CLI for wallet-owned marketplace and MCP workflows.",
|
|
40114
40175
|
type: "module",
|
|
40115
40176
|
bin: {
|
|
@@ -40273,8 +40334,8 @@ async function routeCommand(arguments_, application) {
|
|
|
40273
40334
|
if (first === "mcp" && second === "doctor" && arguments_.length === 2) {
|
|
40274
40335
|
return result("mcp.doctor", await method(application.doctorMcp, "mcp doctor").call(application));
|
|
40275
40336
|
}
|
|
40276
|
-
if (first === "mcp" && second === "
|
|
40277
|
-
return result("mcp.
|
|
40337
|
+
if (first === "mcp" && second === "authorize" && arguments_.length === 3 && third !== void 0) {
|
|
40338
|
+
return result("mcp.authorize", await method(application.authorizeMcp, "mcp authorize").call(application, third));
|
|
40278
40339
|
}
|
|
40279
40340
|
if (first === "mcp" && second === "grant" && third === "create") {
|
|
40280
40341
|
const scopes = exactOption(arguments_, "--scopes").split(",");
|
|
@@ -40595,7 +40656,7 @@ var init_command_router = __esm({
|
|
|
40595
40656
|
"site open",
|
|
40596
40657
|
"site login",
|
|
40597
40658
|
"mcp doctor",
|
|
40598
|
-
"mcp
|
|
40659
|
+
"mcp authorize",
|
|
40599
40660
|
"mcp grant create",
|
|
40600
40661
|
"mcp grant show",
|
|
40601
40662
|
"mcp grant revoke",
|
|
@@ -40661,7 +40722,7 @@ var init_command_router = __esm({
|
|
|
40661
40722
|
"site open": "finch site open",
|
|
40662
40723
|
"site login": "finch site login <REQUEST_URL>",
|
|
40663
40724
|
"mcp doctor": "finch mcp doctor",
|
|
40664
|
-
"mcp
|
|
40725
|
+
"mcp authorize": "finch mcp authorize <REQUEST_URL>",
|
|
40665
40726
|
"mcp grant create": "finch mcp grant create --scopes <CSV> --expires-at <ISO-8601>",
|
|
40666
40727
|
"mcp grant show": "finch mcp grant show <GRANT_ID>",
|
|
40667
40728
|
"mcp grant revoke": "finch mcp grant revoke <GRANT_ID>",
|
|
@@ -56346,81 +56407,14 @@ var init_skill_intent_sender = __esm({
|
|
|
56346
56407
|
}
|
|
56347
56408
|
});
|
|
56348
56409
|
|
|
56349
|
-
// src/mcp-handoff.ts
|
|
56350
|
-
import { spawn } from "node:child_process";
|
|
56351
|
-
function canonicalMcpUrl(value) {
|
|
56352
|
-
const url2 = new URL(value);
|
|
56353
|
-
const localHttp = url2.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url2.hostname);
|
|
56354
|
-
if (url2.protocol !== "https:" && !localHttp || url2.username !== "" || url2.password !== "" || url2.pathname !== "/mcp" || url2.search !== "" || url2.hash !== "") {
|
|
56355
|
-
throw new TypeError("Finch MCP handoff URL is invalid.");
|
|
56356
|
-
}
|
|
56357
|
-
return url2.toString();
|
|
56358
|
-
}
|
|
56359
|
-
var MCP_HANDOFF_TOKEN_ENV, runInteractiveProcess, InstalledCodexMcpHarness;
|
|
56360
|
-
var init_mcp_handoff = __esm({
|
|
56361
|
-
"src/mcp-handoff.ts"() {
|
|
56362
|
-
"use strict";
|
|
56363
|
-
MCP_HANDOFF_TOKEN_ENV = "FINCHTECH_MCP_HANDOFF_TOKEN";
|
|
56364
|
-
runInteractiveProcess = async (input) => new Promise((resolve4, reject5) => {
|
|
56365
|
-
const child = spawn(input.executable, [...input.arguments], {
|
|
56366
|
-
env: input.environment,
|
|
56367
|
-
shell: false,
|
|
56368
|
-
stdio: "inherit"
|
|
56369
|
-
});
|
|
56370
|
-
child.once("error", reject5);
|
|
56371
|
-
child.once("exit", (code, signal) => {
|
|
56372
|
-
if (signal !== null || code === null) {
|
|
56373
|
-
reject5(new TypeError("Codex MCP handoff terminated without an exit code."));
|
|
56374
|
-
return;
|
|
56375
|
-
}
|
|
56376
|
-
resolve4(Object.freeze({ exitCode: code }));
|
|
56377
|
-
});
|
|
56378
|
-
});
|
|
56379
|
-
InstalledCodexMcpHarness = class {
|
|
56380
|
-
baseEnvironment;
|
|
56381
|
-
run;
|
|
56382
|
-
platform;
|
|
56383
|
-
constructor(baseEnvironment = process.env, run = runInteractiveProcess, platform = process.platform) {
|
|
56384
|
-
this.baseEnvironment = { ...baseEnvironment };
|
|
56385
|
-
this.run = run;
|
|
56386
|
-
this.platform = platform;
|
|
56387
|
-
}
|
|
56388
|
-
async launch(input) {
|
|
56389
|
-
if (input.client !== "codex" || !/^[A-Za-z0-9_-]{43}$/u.test(input.bearer)) {
|
|
56390
|
-
throw new TypeError("Codex MCP handoff authority is invalid.");
|
|
56391
|
-
}
|
|
56392
|
-
const mcpUrl = canonicalMcpUrl(input.mcpUrl);
|
|
56393
|
-
const codexArguments = Object.freeze([
|
|
56394
|
-
"-c",
|
|
56395
|
-
`mcp_servers.finch.url="${mcpUrl}"`,
|
|
56396
|
-
"-c",
|
|
56397
|
-
`mcp_servers.finch.bearer_token_env_var="${MCP_HANDOFF_TOKEN_ENV}"`,
|
|
56398
|
-
"-c",
|
|
56399
|
-
"mcp_servers.finch.enabled=true"
|
|
56400
|
-
]);
|
|
56401
|
-
const windows = this.platform === "win32";
|
|
56402
|
-
const result2 = await this.run(Object.freeze({
|
|
56403
|
-
executable: windows ? this.baseEnvironment.ComSpec ?? "cmd.exe" : "codex",
|
|
56404
|
-
arguments: windows ? Object.freeze(["/d", "/s", "/c", "codex", ...codexArguments]) : codexArguments,
|
|
56405
|
-
environment: Object.freeze({ ...this.baseEnvironment, [MCP_HANDOFF_TOKEN_ENV]: input.bearer })
|
|
56406
|
-
}));
|
|
56407
|
-
if (!Number.isInteger(result2.exitCode) || result2.exitCode !== 0) {
|
|
56408
|
-
throw new TypeError(`Codex exited with status ${String(result2.exitCode)}.`);
|
|
56409
|
-
}
|
|
56410
|
-
return Object.freeze({ exitCode: result2.exitCode });
|
|
56411
|
-
}
|
|
56412
|
-
};
|
|
56413
|
-
}
|
|
56414
|
-
});
|
|
56415
|
-
|
|
56416
56410
|
// src/browser-launcher.ts
|
|
56417
|
-
import { spawn
|
|
56411
|
+
import { spawn } from "node:child_process";
|
|
56418
56412
|
var launchBrowserCommand, InstalledBrowserLauncher;
|
|
56419
56413
|
var init_browser_launcher = __esm({
|
|
56420
56414
|
"src/browser-launcher.ts"() {
|
|
56421
56415
|
"use strict";
|
|
56422
56416
|
init_cli_error();
|
|
56423
|
-
launchBrowserCommand = (command, arguments_) =>
|
|
56417
|
+
launchBrowserCommand = (command, arguments_) => spawn(
|
|
56424
56418
|
command,
|
|
56425
56419
|
[...arguments_],
|
|
56426
56420
|
{ stdio: "ignore", windowsHide: true }
|
|
@@ -56432,8 +56426,8 @@ var init_browser_launcher = __esm({
|
|
|
56432
56426
|
launch;
|
|
56433
56427
|
async open(url2) {
|
|
56434
56428
|
await new Promise((resolve4, reject5) => {
|
|
56435
|
-
const command = process.platform === "win32" ? "
|
|
56436
|
-
const args = process.platform === "win32" ? ["
|
|
56429
|
+
const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
56430
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url2] : [url2];
|
|
56437
56431
|
const child = this.launch(command, args);
|
|
56438
56432
|
child.once("error", () => reject5(new BrowserOpenError()));
|
|
56439
56433
|
child.once("close", (code) => code === 0 ? resolve4() : reject5(new BrowserOpenError()));
|
|
@@ -56560,7 +56554,6 @@ async function runInstalledCli(arguments_, environment3 = process.env, authoriti
|
|
|
56560
56554
|
taskProtocolSender: rpcUrls.task === void 0 ? void 0 : new InstalledTaskProtocolSender(rpcUrls.task),
|
|
56561
56555
|
skillIntentSender,
|
|
56562
56556
|
skillLocalFlow: profile === void 0 || skillIntentSender === void 0 ? void 0 : new SkillLocalFlow(api, skillIntentSender, signer, void 0, void 0, state),
|
|
56563
|
-
mcpHarness: authorities.mcpHarness ?? new InstalledCodexMcpHarness(environment3),
|
|
56564
56557
|
browserLauncher: new InstalledBrowserLauncher()
|
|
56565
56558
|
});
|
|
56566
56559
|
return routeCommand(arguments_, application);
|
|
@@ -56580,7 +56573,6 @@ var init_main = __esm({
|
|
|
56580
56573
|
init_task_protocol_sender();
|
|
56581
56574
|
init_skill_intent_sender();
|
|
56582
56575
|
init_skill_local_flow();
|
|
56583
|
-
init_mcp_handoff();
|
|
56584
56576
|
init_browser_launcher();
|
|
56585
56577
|
}
|
|
56586
56578
|
});
|