@koda-sl/baker-cli 0.438.0 → 0.439.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
@@ -4077,11 +4077,12 @@ baker accounts campaigns --last-months 6 --output md # per campaign: reached,
4077
4077
  baker accounts influenced --model awareness --threshold 10 --output csv # another rule, this call only
4078
4078
  baker accounts warm --min 0 # every New/Rising company, below the threshold too
4079
4079
  baker accounts crm status # deals from HubSpot or sent by the client; what arrived, linked Google Sheets
4080
+ baker accounts crm sheet # create and link the deals sheet in the client's Google Drive
4080
4081
  baker accounts crm check --file deals.csv # judge a deals or company CSV offline, nothing sent
4081
4082
  baker accounts crm contract # the send-your-CRM contract, as Markdown for a developer
4082
4083
  ```
4083
4084
 
4084
- A deal is **influenced** when its company reached the company's influence rule — by default 3+ LinkedIn ad engagements (Awareness: 7+ impressions; Intent: 3+ clicks) — in the 12 months up to and including the month the deal was created (`--basis pipeline`, deals not won) or closed won (`--basis revenue`). It is a labelled rule, not credit: campaign rows overlap, and money is reported per currency, never summed across currencies. Deals whose company match is only a guess come back under `lowConfidence` and stay out of `summary` until the match is confirmed in Measure → Accounts. Without the deals set up there — HubSpot's pipelines and won/lost stages, or Another CRM — `influenced` and `campaigns` exit 1 with `ACCOUNTS_NOT_SET_UP`. A CRM Baker cannot read sends its companies and deals to `POST /v1/accounts/crm` (a `crm_ingest` key, `X-Baker-Mode: test` to rehearse, up to 500 records per request, records naming a person refused), is uploaded as a CSV in Measure → Accounts, or lives in a Google Sheet linked there that Baker reads every night (a row deleted from the sheet leaves Baker). A file is a deals export or a company list — a company needs only its website or LinkedIn page; `crm check` runs the same rules offline and `crm contract` prints the whole contract. The CLI never sends deals or links a sheet. `--model engagement|awareness|intent` and `--threshold N` on `influenced`, `campaigns` and `warm` read with another rule for that call only; the saved setting is unchanged. A **warm** account is New or Rising, reached the company's influence threshold this period on the rule's metric (`--min N` overrides, `--min 0` drops it), and has no open deal, no won deal and no deal lost in the last 6 months; `engagement` and `warm` rows carry `dealStatus` (`won` > `open` > `lost` > `none`), `engagement --output csv` adds `lastMonth` and `previousImpressions/Clicks/Engagements`, and `campaigns` / `company` carry each campaign's `campaignName` (null when LinkedIn could not name it). `company` returns `months` (the company's totals), `byCampaign` (the same numbers per campaign — overlapping, never add them) and its `deals` with `touchValue` and `influenced`. `warm --output csv` is the file `baker ads linkedin audiences create --type company-list --list-file` takes.
4085
+ A deal is **influenced** when its company reached the company's influence rule — by default 3+ LinkedIn ad engagements (Awareness: 7+ impressions; Intent: 3+ clicks) — in the 12 months up to and including the month the deal was created (`--basis pipeline`, deals not won) or closed won (`--basis revenue`). It is a labelled rule, not credit: campaign rows overlap, and money is reported per currency, never summed across currencies. Deals whose company match is only a guess come back under `lowConfidence` and stay out of `summary` until the match is confirmed in Measure → Accounts. Without the deals set up there — HubSpot's pipelines and won/lost stages, or Another CRM — `influenced` and `campaigns` exit 1 with `ACCOUNTS_NOT_SET_UP`. A CRM Baker cannot read sends its companies and deals to `POST /v1/accounts/crm` (a `crm_ingest` key, `X-Baker-Mode: test` to rehearse, up to 500 records per request, records naming a person refused), is uploaded as a CSV in Measure → Accounts, or lives in a Google Sheet linked there that Baker reads every night (a row deleted from the sheet leaves Baker). A file is a deals export or a company list — a company needs only its website or LinkedIn page; `crm check` runs the same rules offline and `crm contract` prints the whole contract. The CLI never sends deals or links a sheet of the client's; `crm sheet` creates the empty deals sheet in their Google Drive and links it, applied at once (asking again returns the same sheet). `--model engagement|awareness|intent` and `--threshold N` on `influenced`, `campaigns` and `warm` read with another rule for that call only; the saved setting is unchanged. A **warm** account is New or Rising, reached the company's influence threshold this period on the rule's metric (`--min N` overrides, `--min 0` drops it), and has no open deal, no won deal and no deal lost in the last 6 months; `engagement` and `warm` rows carry `dealStatus` (`won` > `open` > `lost` > `none`), `engagement --output csv` adds `lastMonth` and `previousImpressions/Clicks/Engagements`, and `campaigns` / `company` carry each campaign's `campaignName` (null when LinkedIn could not name it). `company` returns `months` (the company's totals), `byCampaign` (the same numbers per campaign — overlapping, never add them) and its `deals` with `touchValue` and `influenced`. `warm --output csv` is the file `baker ads linkedin audiences create --type company-list --list-file` takes.
4085
4086
 
4086
4087
  ---
4087
4088
 
package/dist/cli.js CHANGED
@@ -22438,6 +22438,20 @@ var accountsCrmStatusResponseSchema = z2.object({
22438
22438
  contents: crmContentsSchema.nullable()
22439
22439
  })
22440
22440
  });
22441
+ var accountsCrmSheetRequestSchema = z2.object({
22442
+ /** The Google Drive connection to make it with, when the company has more than one. */
22443
+ account: z2.string().optional()
22444
+ });
22445
+ var accountsCrmSheetResponseSchema = z2.object({
22446
+ ok: z2.literal(true),
22447
+ data: z2.object({
22448
+ /** False when the company already had the sheet: it is handed back, never made twice. */
22449
+ created: z2.boolean(),
22450
+ spreadsheetName: z2.string(),
22451
+ url: z2.string(),
22452
+ tabs: z2.array(z2.string())
22453
+ })
22454
+ });
22441
22455
  var CRM_PUSH_EXAMPLE = {
22442
22456
  companies: [{ id: "acct-001", name: "Acme Inc", domain: "acme.com", linkedinPage: "acme-inc" }],
22443
22457
  deals: [
@@ -33213,12 +33227,68 @@ var contractCommand = defineCommand({
33213
33227
  process.stdout.write(crmPushBrief(crmPushUrl(env.BAKER_PUBLIC_API_URL, env.BAKER_API_URL)));
33214
33228
  }
33215
33229
  });
33230
+ var SHEET_ARGS = {
33231
+ account: {
33232
+ type: "string",
33233
+ description: "The Google Drive connection to create it with, when the company has more than one",
33234
+ required: false
33235
+ }
33236
+ };
33237
+ registerSchema({
33238
+ command: "accounts.crm.sheet",
33239
+ description: `Create the deals sheet LinkedIn's Account insights reads, for a company running LinkedIn campaigns whose CRM Baker cannot read (anything but HubSpot). Makes "Deals and companies for Baker" in the client's Google Drive \u2014 a Deals tab and a Companies tab headed by the columns Baker reads \u2014 and links both, so Baker reads it every night; chooses Another CRM first when nobody has said where the deals are. Applies at once (it is a real file) and goes on the chat's change list. Empty: nothing counts until the client fills it or pastes their CRM export into it. Asking again returns the sheet already linked. Needs a Google Drive connection and a chat owned by an admin of the company; refused when HubSpot is where the deals come from. Returns {created, spreadsheetName, url, tabs}.`,
33240
+ args: SHEET_ARGS
33241
+ });
33242
+ function sheetFailureFix(code, message) {
33243
+ if (code === "NOT_FOUND" && message.startsWith("No Google Drive account is connected")) {
33244
+ return {
33245
+ action: "request_connection",
33246
+ explanation: 'Google Drive is not connected, and the sheet is made in it. Don\'t send the user to Settings or end the turn \u2014 call the `request_connection` tool with { platform: "google-drive", reason: "so I can create the sheet of your deals that LinkedIn Account insights reads" }; once they connect, run `baker accounts crm sheet` again. If they decline or the call fails, carry on with the rest of the work and leave a Task saying the deals sheet is still to create.'
33247
+ };
33248
+ }
33249
+ if (message.startsWith("Only an admin")) {
33250
+ return {
33251
+ action: "continue_without",
33252
+ explanation: "Carry on with the rest of the work and leave a Task for an admin: create the deals sheet for LinkedIn Account insights."
33253
+ };
33254
+ }
33255
+ return null;
33256
+ }
33257
+ var sheetCommand = defineCommand({
33258
+ meta: {
33259
+ name: "sheet",
33260
+ description: "Start here when the client runs LinkedIn ads and their deals are not in HubSpot: create and link the Google Sheet of deals Account insights reads. Example: baker accounts crm sheet"
33261
+ },
33262
+ args: SHEET_ARGS,
33263
+ run: async ({ args: args8 }) => {
33264
+ try {
33265
+ const account = typeof args8.account === "string" && args8.account.trim() !== "" ? args8.account.trim() : void 0;
33266
+ const response = await apiPost("/api/accounts/crm/sheet", account ? { account } : {});
33267
+ writeJson({
33268
+ ...response,
33269
+ hints: [
33270
+ `Give the user the link (${response.data.url}). The client fills the Deals tab \u2014 or pastes their CRM's deals export into it \u2014 and keeps it up to date; Baker reads it every night, and "Read now" in Measure \u2192 Sources \u2192 Deals reads it at once. A website on each deal is enough to match it to the companies the LinkedIn ads reach.`,
33271
+ "Check what arrived later with `baker accounts crm status`."
33272
+ ]
33273
+ });
33274
+ } catch (err) {
33275
+ if (err instanceof ApiError) {
33276
+ const fix = sheetFailureFix(err.code, err.message);
33277
+ if (fix) {
33278
+ writeJson({ ok: false, error: { code: err.code, message: err.message, fix } });
33279
+ process.exit(1);
33280
+ }
33281
+ }
33282
+ failApi(err);
33283
+ }
33284
+ }
33285
+ });
33216
33286
  var crmCommand = defineCommand({
33217
33287
  meta: {
33218
33288
  name: "crm",
33219
- description: "A CRM Baker cannot read (Salesforce, Pipedrive, a Google Sheet, their own): where the deals and companies stand, check a file before it is sent, and the contract a developer sends to. Nothing here sends or links anything."
33289
+ description: "A CRM Baker cannot read (Salesforce, Pipedrive, a Google Sheet, their own): where the deals and companies stand, create the deals sheet Baker reads, check a file before it is sent, and the contract a developer sends to. Nothing here sends a deal."
33220
33290
  },
33221
- subCommands: { status: statusCommand, check: checkCommand, contract: contractCommand }
33291
+ subCommands: { status: statusCommand, sheet: sheetCommand, check: checkCommand, contract: contractCommand }
33222
33292
  });
33223
33293
 
33224
33294
  // src/commands/accounts/report.ts