@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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/finch.js +140 -148
  3. 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 launch`, `mcp grant`, `creator`, `credentials`, `intent`, `operation show`, `purchase`, `task`, and `skill`. Run `finch --help` for the exact command list as one JSON value.
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 handoff manifest agree. `finch mcp launch codex` then reuses or creates an ordinary Finch MCP grant parented to that Session and starts a new Codex process. The grant bearer exists only in that child process environment; it is never printed, put in argv, or written to Codex credential files. No separate browser OAuth step is required. A new Codex process is required because an already-running Harness cannot inherit a newly created child environment.
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: { const: "84532" },
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 handoff = jsonObject(client.handoff);
36584
+ const install = jsonObject(client.install);
36585
+ const authorize = jsonObject(client.authorize);
36543
36586
  const verify = jsonObject(client.verify);
36544
- if (manifest.schemaVersion !== 2 || manifest.manifestId !== "finch.remote-mcp-clients" || mcp.serverName !== "finch" || mcp.url !== mcpUrl || mcp.transport !== "streamable_http" || mcp.authorization !== "agent_cli_session_handoff" || source.repository !== "https://github.com/FinchipAI/Finch-Site" || source.path !== "apps/mcp/src/client-compatibility.ts" || client.id !== "codex" || client.supportLevel !== "verified" || handoff.kind !== "finch_cli" || handoff.executable !== "finch" || JSON.stringify(handoff.arguments) !== JSON.stringify(["mcp", "launch", "codex"]) || verify.kind !== "harness_ui" || verify.surface !== "mcp_tools" || client.reload !== "new_session") {
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: "agent_cli_session_handoff",
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 browserHandoffAuthority() {
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), "Browser handoff Session");
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.browserHandoffAuthority();
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.browserHandoffAuthority();
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 !== "agent_cli_session_handoff" || JSON.stringify(mcp.supportedClients) !== JSON.stringify(["codex"])) {
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.1.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 === "launch" && third === "codex" && arguments_.length === 3) {
40277
- return result("mcp.launch.codex", await method(application.launchMcpHarness, "mcp launch codex").call(application, "codex"));
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 launch codex",
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 launch codex": "finch mcp launch codex",
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 as spawn2 } from "node:child_process";
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_) => spawn2(
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" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
56436
- const args = process.platform === "win32" ? ["/d", "/s", "/c", "start", "", url2] : [url2];
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finchtech/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Finch Agent CLI for wallet-owned marketplace and MCP workflows.",
5
5
  "type": "module",
6
6
  "bin": {