@koda-sl/baker-cli 0.417.0 → 0.418.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 CHANGED
@@ -865,7 +865,7 @@ baker ads x targeting-constants --constant tv_shows --locale en-US
865
865
 
866
866
  ### ChatGPT Ads (`baker ads chatgpt`)
867
867
 
868
- Read the company's ChatGPT Ads account (OpenAI's ads inside ChatGPT conversations) and how it performs, and stage changes to it that apply when the chat is published. The company connects one account in the dashboard (Brain → Integrations → ChatGPT Ads) with an API key from its Ads Manager; the key stays in Baker and never reaches the CLI, so there is no account id to pass.
868
+ Read the company's ChatGPT Ads account (OpenAI's ads inside ChatGPT conversations) and how it performs, and stage changes to it that apply when the chat is published. The company connects one account with an API key from its Ads Manager, pasted in the chat's connect card (`request_connection` `{ platform: "chatgpt-ads" }`) or in Brain → Integrations → ChatGPT Ads; the key stays in Baker and never reaches the CLI, so there is no account id to pass.
869
869
 
870
870
  | Subcommand | What it returns |
871
871
  |---|---|
@@ -3730,12 +3730,13 @@ Permissions enforced server-side:
3730
3730
 
3731
3731
  ---
3732
3732
 
3733
- ### `baker mcp connected | list | add | remove`
3733
+ ### `baker mcp connected | catalog | list | add | remove`
3734
3734
 
3735
3735
  See everything a chat can reach — managed integrations, custom servers, and connected platforms — and manage **custom MCP servers** by pointing the agent at any remote HTTPS MCP endpoint. Registered tools appear as `mcp__<name>__*` on the next chat message.
3736
3736
 
3737
3737
  ```bash
3738
3738
  baker mcp connected
3739
+ baker mcp catalog --search notion
3739
3740
  baker mcp list
3740
3741
  baker mcp add --name weather --url https://mcp.example.com/mcp
3741
3742
  baker mcp add --name acme --url https://acme.dev/mcp --header "Authorization: Bearer sk-…"
@@ -3744,6 +3745,7 @@ baker mcp remove --name weather
3744
3745
  ```
3745
3746
 
3746
3747
  - `connected` returns three lists. `managed` and `custom` are MCP tools the agent calls directly; `platforms` are the accounts the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager, LinkedIn Ads), each with its login and the resources it may read. A platform has **no** `mcp__*` tools — it is read through its own command family — so it is listed here to keep the agent from concluding a working connection is missing.
3748
+ - `catalog --search <name> [--limit 1-20]` searches the managed apps a user can connect when a vendor has no MCP server of its own. Each app reports its slug (`app`), how the user connects it (`sign-in` or `key`; apps that need no sign-in cannot be connected and are left out) — never a managed copy of one of Baker's own connections: those come back under `platforms`, to ask for with `{ platform }` and whether this chat already reaches it (`connected`).
3747
3749
  - `--url` must be the **full MCP endpoint** (commonly ending in `/mcp`), HTTPS and public; loopback / private / cloud-metadata hosts are rejected. A bare domain frequently accepts the OAuth sign-in and then serves no tools, so every address is handshake-checked after it changes: `connected` and `list` report `usable: false` plus a `problem` (and a `suggestedAddress` when the server advertises a better endpoint) for a server that isn't serving tools, or `lastCheck: failed` when the last check only hit an outage — that one may work anyway, and a later turn re-checks it automatically. Repointing a server at a different host clears its saved sign-in: a token belongs to the host that issued it, so the server has to be connected again.
3748
3750
  - Scope is two independent choices — audience (just you / everyone) × level (this company / whole org):
3749
3751
  - `--scope company` (default) — everyone in this company.
package/dist/cli.js CHANGED
@@ -34614,7 +34614,7 @@ var chatgptAdsConversionReportRequestSchema = z38.object({
34614
34614
  var CHATGPT_ADS_TRUNCATED_HEADER = "X-Baker-Truncated";
34615
34615
 
34616
34616
  // src/commands/ads/chatgpt/shared.ts
34617
- var CONNECT_EXPLANATION = "ChatGPT Ads is connected from the dashboard, with the API key from the company's ChatGPT Ads Manager (Settings \u2192 API keys): Brain \u2192 Integrations \u2192 ChatGPT Ads. Tell the user exactly that, and never ask them to paste the key in this chat \u2014 `request_connection` does not cover ChatGPT Ads. Carry on with the rest of the task and say what stays unavailable until it is connected.";
34617
+ var CONNECT_EXPLANATION = 'Ask for ChatGPT Ads with request_connection `{ platform: "chatgpt-ads" }`: the card asks for the API key from the company\'s ChatGPT Ads Manager (Settings \u2192 API keys), and the user pastes it there \u2014 never ask for it in this chat. If they skip it, carry on with the rest of the task and say what stays unavailable until it is connected.';
34618
34618
  var OPENAI_ADS_DOCS = "https://developers.openai.com/ads";
34619
34619
  var CHATGPT_ADS_REFERENCE = {
34620
34620
  account: `${OPENAI_ADS_DOCS}/api-reference/ad-account.md`,
@@ -34691,7 +34691,7 @@ function chatgptFailure(err, reference = CHATGPT_ADS_REFERENCE.overview) {
34691
34691
  message: err.message,
34692
34692
  fix: {
34693
34693
  action: "authenticate",
34694
- explanation: `${err.message} Tell the user; never ask them to paste the key in this chat.`
34694
+ explanation: `${err.message} Open request_connection \`{ platform: "chatgpt-ads" }\` so they can replace the key in the card; never ask them to paste it in this chat.`
34695
34695
  },
34696
34696
  retryable: false
34697
34697
  }
@@ -93825,9 +93825,60 @@ Subcommands:
93825
93825
  }
93826
93826
  });
93827
93827
 
93828
+ // ../api/src/mcp-catalog.ts
93829
+ import { z as z65 } from "zod";
93830
+ var MCP_CATALOG_DEFAULT_LIMIT = 8;
93831
+ var MCP_CATALOG_DESCRIPTION_MAX = 160;
93832
+ var mcpCatalogRequestSchema = z65.object({
93833
+ search: z65.string().trim().min(1, "Provide --search").max(100),
93834
+ limit: z65.coerce.number().int().min(1).max(20).default(MCP_CATALOG_DEFAULT_LIMIT)
93835
+ });
93836
+ var mcpCatalogConnectSchema = z65.enum(["sign-in", "key"]);
93837
+ var mcpCatalogEntrySchema = z65.object({
93838
+ /** The toolkit slug — what request_connection `{ app }` takes. */
93839
+ app: z65.string(),
93840
+ name: z65.string(),
93841
+ description: z65.string().max(MCP_CATALOG_DESCRIPTION_MAX),
93842
+ connect: mcpCatalogConnectSchema,
93843
+ connected: z65.boolean()
93844
+ });
93845
+ var mcpCatalogResponseSchema = z65.object({
93846
+ apps: z65.array(mcpCatalogEntrySchema),
93847
+ /** The company has managed apps switched off: nothing here can be connected, so `apps` is empty. */
93848
+ managedDisabled: z65.boolean().optional(),
93849
+ /** Baker's own connections this search matched — ask for these with `{ platform }`, never a managed copy. */
93850
+ platforms: z65.array(z65.string()).optional()
93851
+ });
93852
+
93828
93853
  // src/commands/mcp/index.ts
93829
93854
  import { defineCommand as defineCommand211 } from "citty";
93830
93855
 
93856
+ // src/commands/mcp/catalog.ts
93857
+ var PREFER_VENDOR_HINT = 'The vendor\'s own MCP server comes before any app here. If you have not yet, WebSearch "<vendor> MCP server" and WebFetch its official docs for the remote https address and how it signs in, then ask for it with request_connection `{ server: { name, url, auth, keyHeader } }`. Only when the vendor has none, ask for an app from this catalog with request_connection `{ app: "<slug>", reason }` \u2014 the user connects it in the chat and its tools load right after, in this same turn.';
93858
+ var ALREADY_CONNECTED_HINT = "An app marked connected is already reachable \u2014 use its mcp__composio_* tools; do not ask again.";
93859
+ var NO_MATCH_HINT = "Nothing in the catalog matches. WebSearch for the vendor's official remote MCP server (its docs, its developer site) before telling the user it can't be connected \u2014 if one exists, ask for it with request_connection `{ server }`. Only when the web shows no official remote server either, say plainly this tool can't be connected yet and offer a Task.";
93860
+ var MANAGED_DISABLED_HINT = "Managed apps are switched off for this company, so none can be connected here. Use the vendor's own MCP server \u2014 WebSearch \"<vendor> MCP server\" and WebFetch its official docs, then request_connection `{ server }`; when it has none, say plainly this tool can't be connected yet and offer a Task.";
93861
+ function firstPartyHint(platforms) {
93862
+ const asks = platforms.map((p) => `request_connection \`{ platform: "${p}" }\``).join(" or ");
93863
+ return `This is one of Baker's own connections \u2014 ask for it with ${asks}. It is the one Baker's commands, staging and publish work with, so never connect it as a catalog app or as an MCP server, even one the vendor publishes or the user names. If it is already connected, read it with its own baker command family instead.`;
93864
+ }
93865
+ function catalogHints(apps, { managedDisabled = false, platforms = [] } = {}) {
93866
+ if (platforms.length > 0) {
93867
+ return [firstPartyHint(platforms)];
93868
+ }
93869
+ if (managedDisabled) {
93870
+ return [MANAGED_DISABLED_HINT];
93871
+ }
93872
+ const hints2 = [PREFER_VENDOR_HINT];
93873
+ if (apps.some((a) => a.connected)) {
93874
+ hints2.push(ALREADY_CONNECTED_HINT);
93875
+ }
93876
+ if (apps.length === 0) {
93877
+ hints2.push(NO_MATCH_HINT);
93878
+ }
93879
+ return hints2;
93880
+ }
93881
+
93831
93882
  // src/commands/mcp/platforms.ts
93832
93883
  function readsKey(label) {
93833
93884
  const [first = "", ...rest] = label.split(" ");
@@ -93937,7 +93988,7 @@ A tool or platform the user names that is NOT listed anywhere here is simply not
93937
93988
  );
93938
93989
  }
93939
93990
  hints2.push(
93940
- "Not listed anywhere = not connected. Managed app (Slack, Attio, \u2026) \u2192 the user connects it in the dashboard: Brain \u2192 Integrations \u2192 More apps. Platform (HubSpot, Google Ads, GA4, \u2026) \u2192 Brain \u2192 Integrations \u2192 Platforms. Custom HTTPS MCP endpoint \u2192 `baker mcp add`."
93991
+ "Not listed anywhere = not connected \u2014 connect it from the chat with request_connection, never by sending the user to the dashboard: a Baker platform (HubSpot, Google Ads, GA4, \u2026) \u2192 { platform }; anything else \u2192 the vendor's own MCP server FIRST: use the address the user gave you, or WebSearch '<vendor> MCP server' and WebFetch its official docs for the remote https address and how it signs in, then { server: { name, url, auth, keyHeader } }; only when the vendor has none \u2192 `baker mcp catalog --search <name>`, then { app }. Research before you ask the user anything. What they connect loads into this same turn."
93941
93992
  );
93942
93993
  writeJson({
93943
93994
  ok: true,
@@ -93949,6 +94000,61 @@ A tool or platform the user names that is NOT listed anywhere here is simply not
93949
94000
  }
93950
94001
  }
93951
94002
  });
94003
+ var catalogArgs = {
94004
+ search: { type: "string", description: "App or vendor name, e.g. notion, clarity, pipedrive", required: true },
94005
+ limit: { type: "string", description: "Max apps to return, 1-20 (default 8)", required: false }
94006
+ };
94007
+ registerSchema({
94008
+ command: "mcp.catalog",
94009
+ description: "Search the managed apps you can ask the user to connect when a vendor has no MCP server of its own. Each app says how the user connects it (sign-in | key) and whether this chat already reaches it. Example: baker mcp catalog --search notion",
94010
+ args: catalogArgs
94011
+ });
94012
+ var catalogCommand2 = defineCommand211({
94013
+ meta: {
94014
+ name: "catalog",
94015
+ description: `Managed apps you can ask the user to connect when a vendor has no MCP server of its own.
94016
+
94017
+ For a tool \`baker mcp connected\` does not list whose vendor has no official remote MCP server:
94018
+ baker mcp catalog --search notion
94019
+ baker mcp catalog --search "microsoft clarity" --limit 3
94020
+
94021
+ Each app: app (slug for request_connection), name, description, connect (sign-in | key), connected.
94022
+ Prefer the vendor's own MCP server when it publishes one; a catalog app is the fallback.`
94023
+ },
94024
+ args: catalogArgs,
94025
+ run: async ({ args: args8 }) => {
94026
+ const parsed = mcpCatalogRequestSchema.safeParse({ search: args8.search, limit: args8.limit });
94027
+ if (!parsed.success) {
94028
+ writeJson({
94029
+ ok: false,
94030
+ error: { code: "VALIDATION_ERROR", message: parsed.error.issues[0]?.message ?? "Invalid arguments" }
94031
+ });
94032
+ process.exit(1);
94033
+ }
94034
+ try {
94035
+ const user = actingUserId();
94036
+ const data = await apiGet("/api/mcp/catalog", {
94037
+ search: parsed.data.search,
94038
+ limit: String(parsed.data.limit),
94039
+ ...user ? { actingUserId: user } : {}
94040
+ });
94041
+ writeJson({
94042
+ ok: true,
94043
+ data: {
94044
+ apps: data.apps,
94045
+ ...data.platforms?.length ? { platforms: data.platforms } : {},
94046
+ ...data.managedDisabled ? { managedDisabled: true } : {}
94047
+ },
94048
+ hints: catalogHints(data.apps, {
94049
+ managedDisabled: data.managedDisabled === true,
94050
+ platforms: data.platforms ?? []
94051
+ })
94052
+ });
94053
+ } catch (err) {
94054
+ fail10(err);
94055
+ }
94056
+ }
94057
+ });
93952
94058
  registerSchema({
93953
94059
  command: "mcp.list",
93954
94060
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
@@ -93995,6 +94101,8 @@ var addCommand2 = defineCommand211({
93995
94101
  meta: {
93996
94102
  name: "add",
93997
94103
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
94104
+ For a server the USER asks for, use request_connection { server } instead: they confirm who can use it,
94105
+ type any key into the card (never the chat), and its tools load in this same turn.
93998
94106
 
93999
94107
  Scope: company (default \u2014 every chat in the company) | org (all companies in the org; needs an admin key) | user (only the current message sender).
94000
94108
 
@@ -94075,6 +94183,7 @@ Managed here are **company-scoped** servers; user- and org-scoped servers are ma
94075
94183
 
94076
94184
  Start here:
94077
94185
  baker mcp connected # everything this chat can reach: managed integrations, custom servers, connected platforms
94186
+ baker mcp catalog --search notion # managed apps you can connect when a vendor has no MCP server of its own
94078
94187
 
94079
94188
  Examples:
94080
94189
  baker mcp add --name weather --url https://mcp.example.com/mcp
@@ -94084,6 +94193,7 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
94084
94193
  },
94085
94194
  subCommands: {
94086
94195
  connected: connectedCommand,
94196
+ catalog: catalogCommand2,
94087
94197
  list: listCommand18,
94088
94198
  add: addCommand2,
94089
94199
  remove: removeCommand6
@@ -94094,8 +94204,8 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
94094
94204
  import { defineCommand as defineCommand217 } from "citty";
94095
94205
 
94096
94206
  // ../api/src/media-sources.ts
94097
- import { z as z65 } from "zod";
94098
- var mediaSourcePlatformSchema = z65.enum([
94207
+ import { z as z66 } from "zod";
94208
+ var mediaSourcePlatformSchema = z66.enum([
94099
94209
  "instagram",
94100
94210
  "tiktok",
94101
94211
  "youtube",
@@ -94103,55 +94213,55 @@ var mediaSourcePlatformSchema = z65.enum([
94103
94213
  "facebook",
94104
94214
  "google_drive"
94105
94215
  ]);
94106
- var mediaSourceRefreshIntervalSchema = z65.enum(["never", "daily", "weekly", "monthly"]);
94107
- var mediaSourceSyncStatusSchema = z65.enum(["idle", "fetching", "processing", "error"]);
94108
- var mediaSourceHalfSchema = z65.object({
94109
- sourceId: z65.string(),
94216
+ var mediaSourceRefreshIntervalSchema = z66.enum(["never", "daily", "weekly", "monthly"]);
94217
+ var mediaSourceSyncStatusSchema = z66.enum(["idle", "fetching", "processing", "error"]);
94218
+ var mediaSourceHalfSchema = z66.object({
94219
+ sourceId: z66.string(),
94110
94220
  syncStatus: mediaSourceSyncStatusSchema,
94111
- storedCount: z65.number().optional(),
94112
- pendingItems: z65.number().optional(),
94113
- failedItems: z65.number().optional(),
94114
- lastSyncedAt: z65.number().optional(),
94115
- errorMessage: z65.string().optional()
94116
- });
94117
- var mediaSourceSummarySchema = z65.object({
94118
- key: z65.string(),
94221
+ storedCount: z66.number().optional(),
94222
+ pendingItems: z66.number().optional(),
94223
+ failedItems: z66.number().optional(),
94224
+ lastSyncedAt: z66.number().optional(),
94225
+ errorMessage: z66.string().optional()
94226
+ });
94227
+ var mediaSourceSummarySchema = z66.object({
94228
+ key: z66.string(),
94119
94229
  platform: mediaSourcePlatformSchema,
94120
- platformLabel: z65.string(),
94121
- handle: z65.string(),
94122
- label: z65.string(),
94123
- profileUrl: z65.string(),
94124
- profileImageUrl: z65.string().optional(),
94230
+ platformLabel: z66.string(),
94231
+ handle: z66.string(),
94232
+ label: z66.string(),
94233
+ profileUrl: z66.string(),
94234
+ profileImageUrl: z66.string().optional(),
94125
94235
  refreshInterval: mediaSourceRefreshIntervalSchema,
94126
94236
  /** Absent for a platform that feeds no images (YouTube). */
94127
94237
  images: mediaSourceHalfSchema.optional(),
94128
94238
  videos: mediaSourceHalfSchema.optional(),
94129
- createdAt: z65.number()
94239
+ createdAt: z66.number()
94130
94240
  });
94131
- var mediaSourceProfileSchema = z65.object({
94241
+ var mediaSourceProfileSchema = z66.object({
94132
94242
  platform: mediaSourcePlatformSchema,
94133
- handle: z65.string(),
94134
- displayName: z65.string(),
94135
- avatarUrl: z65.string(),
94136
- bio: z65.string(),
94137
- followers: z65.number().optional(),
94138
- postCount: z65.number().optional(),
94139
- isPrivate: z65.boolean(),
94140
- profileUrl: z65.string(),
94141
- channelId: z65.string().optional()
94142
- });
94143
- var mediaSourcesAddRequestSchema = z65.object({
94243
+ handle: z66.string(),
94244
+ displayName: z66.string(),
94245
+ avatarUrl: z66.string(),
94246
+ bio: z66.string(),
94247
+ followers: z66.number().optional(),
94248
+ postCount: z66.number().optional(),
94249
+ isPrivate: z66.boolean(),
94250
+ profileUrl: z66.string(),
94251
+ channelId: z66.string().optional()
94252
+ });
94253
+ var mediaSourcesAddRequestSchema = z66.object({
94144
94254
  /** A profile URL or a Drive folder link, or an @handle together with `platform`. */
94145
- input: z65.string().trim().min(1, "Pass the account's profile URL or @handle"),
94255
+ input: z66.string().trim().min(1, "Pass the account's profile URL or @handle"),
94146
94256
  /** Needed only when `input` is a bare handle; a URL names its own platform. */
94147
94257
  platform: mediaSourcePlatformSchema.optional(),
94148
94258
  /** Auto-sync cadence. Omitted = weekly, for every platform. */
94149
94259
  every: mediaSourceRefreshIntervalSchema.optional()
94150
94260
  });
94151
- var mediaSourceKeySchema = z65.string().trim().regex(/^(instagram|tiktok|youtube|linkedin|facebook|google_drive):.+$/, "Pass a source key like tiktok:nike");
94152
- var mediaSourcesSyncRequestSchema = z65.object({ key: mediaSourceKeySchema });
94153
- var mediaSourcesRemoveRequestSchema = z65.object({ key: mediaSourceKeySchema });
94154
- var mediaSourcesScheduleRequestSchema = z65.object({
94261
+ var mediaSourceKeySchema = z66.string().trim().regex(/^(instagram|tiktok|youtube|linkedin|facebook|google_drive):.+$/, "Pass a source key like tiktok:nike");
94262
+ var mediaSourcesSyncRequestSchema = z66.object({ key: mediaSourceKeySchema });
94263
+ var mediaSourcesRemoveRequestSchema = z66.object({ key: mediaSourceKeySchema });
94264
+ var mediaSourcesScheduleRequestSchema = z66.object({
94155
94265
  key: mediaSourceKeySchema,
94156
94266
  every: mediaSourceRefreshIntervalSchema
94157
94267
  });