@koda-sl/baker-cli 0.417.0-dev.fc1af4da8 → 0.418.0-dev.6af3f5093
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 +54 -21
- package/dist/{chunk-SCA25GLY.js → chunk-3FJU7BNQ.js} +4 -43
- package/dist/chunk-3FJU7BNQ.js.map +1 -0
- package/dist/{chunk-QEE2SJDW.js → chunk-6V6B7RPR.js} +4 -4
- package/dist/{chunk-ASON7WRD.js → chunk-AKX7S6ZW.js} +12 -4
- package/dist/chunk-AKX7S6ZW.js.map +1 -0
- package/dist/{chunk-INZVJHAI.js → chunk-HTPY7G2Y.js} +3 -3
- package/dist/{chunk-NIFD47XY.js → chunk-LMMZ3FAC.js} +3 -3
- package/dist/{chunk-T2MNTMZ7.js → chunk-OEI3NLGH.js} +11 -3
- package/dist/chunk-OEI3NLGH.js.map +1 -0
- package/dist/cli.js +8832 -7683
- package/dist/cli.js.map +1 -1
- package/dist/{client-EXF5LY5N.js → client-6CLQVK65.js} +4 -4
- package/dist/engine/index.js +3 -3
- package/dist/{env-E565GJBV.js → env-ZJLFGNH5.js} +4 -2
- package/dist/output-BTVIL27G.js +22 -0
- package/dist/{shared-OVJIDVGI.js → shared-C5EZVLDS.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-ASON7WRD.js.map +0 -1
- package/dist/chunk-SCA25GLY.js.map +0 -1
- package/dist/chunk-T2MNTMZ7.js.map +0 -1
- package/dist/output-XPNSPPDN.js +0 -18
- /package/dist/{chunk-QEE2SJDW.js.map → chunk-6V6B7RPR.js.map} +0 -0
- /package/dist/{chunk-INZVJHAI.js.map → chunk-HTPY7G2Y.js.map} +0 -0
- /package/dist/{chunk-NIFD47XY.js.map → chunk-LMMZ3FAC.js.map} +0 -0
- /package/dist/{client-EXF5LY5N.js.map → client-6CLQVK65.js.map} +0 -0
- /package/dist/{env-E565GJBV.js.map → env-ZJLFGNH5.js.map} +0 -0
- /package/dist/{output-XPNSPPDN.js.map → output-BTVIL27G.js.map} +0 -0
- /package/dist/{shared-OVJIDVGI.js.map → shared-C5EZVLDS.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1109,10 +1109,23 @@ baker ads linkedin analytics --level campaign --campaign-id 1234 \
|
|
|
1109
1109
|
# "Top companies seeing this campaign" (ABM feedback loop) — org names auto-resolved
|
|
1110
1110
|
baker ads linkedin top-companies --campaign-id 1234 --last-days 30
|
|
1111
1111
|
|
|
1112
|
+
# "Which companies are our ads reaching?" — one row per company over the whole period,
|
|
1113
|
+
# most engaged first ({org, name, impressions, clicks, engagements, costLocal}).
|
|
1114
|
+
# Defaults: last 12 calendar months, the only selected ad account, top 100 (--full for all).
|
|
1115
|
+
baker ads linkedin companies --account-id 1234
|
|
1116
|
+
baker ads linkedin companies --by month --min-impressions 50 # months[] per company
|
|
1117
|
+
baker ads linkedin companies --campaign-id 1,2 --by campaign # campaigns[] per company
|
|
1118
|
+
baker ads linkedin companies --with-domains --stats --output csv # + domain/domainSource; read-quality stats
|
|
1119
|
+
|
|
1112
1120
|
# "Who are these org URNs?" — resolve org URNs to company names (probe leakage)
|
|
1113
1121
|
baker ads linkedin resolve --urns urn:li:organization:17719,urn:li:organization:19022
|
|
1114
1122
|
baker ads linkedin resolve --ids 17719,19022 --output csv
|
|
1115
1123
|
|
|
1124
|
+
# "What are these companies' website domains?" — for matching against CRM companies.
|
|
1125
|
+
# Per org: vanityName (LinkedIn page), website, domain, domainSource (website | subdomain | none);
|
|
1126
|
+
# ids LinkedIn doesn't know land in `errors`. Shortlinks are never followed.
|
|
1127
|
+
baker ads linkedin resolve --ids 1035,1337 --with-domains
|
|
1128
|
+
|
|
1116
1129
|
# "Who is in our audience by industry/seniority/function/title — all at once?"
|
|
1117
1130
|
baker ads linkedin demographics --campaign-id 1234 --last-days 30
|
|
1118
1131
|
|
|
@@ -1181,6 +1194,7 @@ creatives # default ACTIVE intended-status
|
|
|
1181
1194
|
|
|
1182
1195
|
analytics # see "Smart defaults" above (3-axis: level × intent × pivot)
|
|
1183
1196
|
demographics # sweep all firmographic pivots in one call
|
|
1197
|
+
companies # which companies the ads reached, one row per company (--by month|campaign, --with-domains)
|
|
1184
1198
|
top-companies # pivot=MEMBER_COMPANY shortcut (ABM)
|
|
1185
1199
|
conversation # pivot=CONVERSATION_NODE_BUTTON shortcut (Sponsored Messaging)
|
|
1186
1200
|
|
|
@@ -1200,6 +1214,7 @@ audit --format md # deliverable-ready markdown table
|
|
|
1200
1214
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
1201
1215
|
campaigns create|update|pause|resume|archive|duplicate
|
|
1202
1216
|
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
1217
|
+
campaigns exclude-companies <ad-set-id> --org <ids> # stop an ad set reaching these companies
|
|
1203
1218
|
account url-params # UTMs for every ad in the whole account
|
|
1204
1219
|
url-params [--ad-sets] # read what is tagged now, and what lands twice
|
|
1205
1220
|
creatives create|update|pause|resume|duplicate
|
|
@@ -1240,9 +1255,16 @@ baker ads linkedin account url-params --param "utm_source=linkedin&utm_medium=pa
|
|
|
1240
1255
|
baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
|
|
1241
1256
|
baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
|
|
1242
1257
|
|
|
1243
|
-
# Matched audiences (CSV with header; email columns SHA-256 hashed locally)
|
|
1258
|
+
# Matched audiences (CSV with header; email columns SHA-256 hashed locally).
|
|
1259
|
+
# Company lists take companyName,domain,organizationUrn — organizationUrn matches the company page exactly
|
|
1244
1260
|
baker ads linkedin audiences create --name "ABM Tier-1" --type company-list --list-file accounts.csv
|
|
1245
1261
|
|
|
1262
|
+
# Stop an ad set reaching customers / open deals — add-only; at publish Baker re-reads the ad set
|
|
1263
|
+
# and merges into its targeting as it stands then. --org takes company ids, ≤200 per call;
|
|
1264
|
+
# a longer list goes into a company-list audience passed as --segment
|
|
1265
|
+
baker ads linkedin campaigns exclude-companies 123456 --org 1035,1441
|
|
1266
|
+
baker ads linkedin campaigns exclude-companies 123456 --segment urn:li:dmpSegment:42
|
|
1267
|
+
|
|
1246
1268
|
# Conversions with playbook-default windows
|
|
1247
1269
|
baker ads linkedin conversions create --name "Demo booked" --type LEAD --method INSIGHT_TAG \
|
|
1248
1270
|
--post-click-window 30 --view-window 7
|
|
@@ -1304,7 +1326,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
|
|
|
1304
1326
|
- OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
|
|
1305
1327
|
- Account scoping: backend rejects calls against an account not in the picker selection.
|
|
1306
1328
|
- Pinned API version: `Linkedin-Version: 202606` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window. The floor is `TRACKING_PARAM_DYNAMIC_MIN_API_VERSION` (`CREATIVE_NAME` needs 202606), asserted in `ads/linkedin/utils.test.ts`.
|
|
1307
|
-
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, bid-pricing/forecast 6h, audience-size 1h.
|
|
1329
|
+
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, org domains 7d, bid-pricing/forecast 6h, audience-size 1h.
|
|
1308
1330
|
- `--no-cache` on any command skips both cache layers (the CLI's own and the server's).
|
|
1309
1331
|
|
|
1310
1332
|
#### LinkedIn-specific gotchas
|
|
@@ -3790,24 +3812,7 @@ baker history list --category ads --full # ad writes with raw metadata
|
|
|
3790
3812
|
|
|
3791
3813
|
---
|
|
3792
3814
|
|
|
3793
|
-
### `baker
|
|
3794
|
-
|
|
3795
|
-
Credentials are a company's stored API keys, tokens and passwords. The CLI never returns a value: each credential has a placeholder, `{{credential:NAME}}`, that a request inside a Baker Session can carry in any header, URL or body. The Session's HTTPS proxy fills in the value only for the hosts the credential is bound to, and puts the placeholder back in responses.
|
|
3796
|
-
|
|
3797
|
-
```bash
|
|
3798
|
-
baker credentials list # name, placeholder, allowedHosts, label
|
|
3799
|
-
baker credentials list --full # + source, lastUsedAt
|
|
3800
|
-
printf %s "$KEY" | baker credentials put stripe-key --host api.stripe.com --label "Stripe key"
|
|
3801
|
-
baker credentials delete stripe-key
|
|
3802
|
-
```
|
|
3803
|
-
|
|
3804
|
-
- `put` reads the value from **stdin only** — a `--value` flag or a positional value is refused, because argv is visible to other processes. One trailing newline is trimmed; empty stdin is refused. `--host` is repeatable (or comma-separated) and accepts `*.example.com` wildcards.
|
|
3805
|
-
- `put` and `delete` act only on credentials the agent created. Credentials a person added are managed in the dashboard (Settings → Credentials).
|
|
3806
|
-
- The company must have credentials switched on; otherwise every command answers `FORBIDDEN`.
|
|
3807
|
-
|
|
3808
|
-
---
|
|
3809
|
-
|
|
3810
|
-
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot deals properties | fields` · `baker hubspot meetings list | view`
|
|
3815
|
+
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot companies search` · `baker hubspot deals list | properties | fields` · `baker hubspot meetings list | view`
|
|
3811
3816
|
|
|
3812
3817
|
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there, the leads those forms received, and the workflows, deal pipelines, and contact records a lead runs through afterwards. Fetched from HubSpot live on every call; nothing is cached and nothing is ever written back.
|
|
3813
3818
|
|
|
@@ -3829,6 +3834,8 @@ baker hubspot pipelines list # where a lead lands, and the st
|
|
|
3829
3834
|
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
3830
3835
|
baker hubspot contacts lookup lead@example.com # was this one lead picked up?
|
|
3831
3836
|
baker hubspot contacts properties # what a HubSpot-read source can watch, with each value
|
|
3837
|
+
baker hubspot companies search --domains acme.com,globex.io --with-deals # CRM accounts by domain, with their deals
|
|
3838
|
+
baker hubspot deals list --pipeline default --modified-since 2026-01-01 # deals: amount, stage, closed won
|
|
3832
3839
|
baker hubspot meetings list # every calendar + whether booking redirects
|
|
3833
3840
|
baker hubspot meetings view <slug> # booking-form fields for one calendar
|
|
3834
3841
|
baker hubspot meetings view <slug> --as-node # the form.external blob for a flow node
|
|
@@ -3839,7 +3846,9 @@ baker hubspot meetings view <slug> --as-node # the form.external blob for a f
|
|
|
3839
3846
|
**`forms submissions`:** positional `<formId>`; `--days <1-365>` (default 30) sets the window; `--full` adds each submission's field values. The default response is counts and dates only — `total`, `inWindow`, `lastSubmittedAt`, and per-day counts — because submissions carry the lead's own contact details. `truncated: true` means the page budget ran out before the window was covered, so `inWindow` is a floor rather than an exact count.
|
|
3840
3847
|
**`workflows list` flags:** `--search <text>`, `--enabled-only`. Disabled workflows are listed too — a form wired to one that is off looks connected and does nothing.
|
|
3841
3848
|
**`workflows view`:** positional `<workflowId>` (the id from `workflows list`, and the one in HubSpot's own URL — the command translates it to HubSpot's separate internal flow id for you). Returns the enrolment criteria and whether it re-enrols (`null` = HubSpot does not report it for that criteria type, so it is unknown rather than off), plus every step with the property it writes. On a `LIST_BRANCH` step, branches come back in **evaluation order** (`order: 1` wins over `order: 2`) — a broad branch above a narrower one silently swallows the records the narrow one was written for, and HubSpot reports nothing. A `STATIC_BRANCH` matches on a value rather than top down, so its `order` is display only. `steps` is **not** in execution order; follow `nextActionId`. `--full` adds the raw payload, the only place the branch filter criteria (which property is compared to which value) can be read.
|
|
3842
|
-
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot.
|
|
3849
|
+
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot, each with `isClosed` and `isWon` (closed with probability 1).
|
|
3850
|
+
**`companies search` flags:** `--domains <a.com,b.com>` (up to 100; a URL or `www.` is reduced to the bare domain), `--names <"Acme, Inc.;Globex">` (semicolon-separated), `--with-deals`, `--limit <1-500>` (default 100), `--output json|csv|jsonl|md`. Rows: `{ id, name, domain, linkedinPage, deals? }`; `notFound` lists the searched domains no CRM company carries; `truncated: true` means more matched than `--limit`.
|
|
3851
|
+
**`deals list` flags:** `--pipeline <id,id>`, `--modified-since <YYYY-MM-DD>`, `--limit <1-1000>` (default 200), `--after <cursor>`, `--output json|csv|jsonl|md`. Rows, oldest change first: `{ id, name, amount, currency, pipelineId, stageId, stageLabel, isClosed, isWon, createdAt, closedAt, modifiedAt, companyIds }` — `amount: null` is none entered. `next` is the `--after` for the following page; `restartFrom` appears when HubSpot's 10,000-result search ceiling is reached (read on with `--modified-since <restartFrom>`); `companiesReadable: false` means the connection cannot read companies, so no deal carries `companyIds`.
|
|
3843
3852
|
**`contacts summary` flags:** `--days <1-365>` (default 30) over contact creation date. Returns counts only — total contacts, how many have an owner, and the lifecycle-stage split — never a name or an email. `truncated: true` means the page budget ran out, so the counts are floors.
|
|
3844
3853
|
**`contacts lookup`:** positional `<email>`; returns that contact's lifecycle stage, owner, the account (company) it was filed under, and associated deals with their stage. `dealsReadable: false` / `companyReadable: false` mean the connection does not cover deals or accounts, so an empty `deals` or a null `company` is unknown rather than absent. `found: false` means no contact with that email — the lead never reached the CRM, which is a finding rather than an error.
|
|
3845
3854
|
**`contacts properties`:** no flags; returns every contact property with a closed list of values — `{ name, label, groupName, options: [{ value, label }] }` — the ones a HubSpot-read source can watch. Names are HubSpot's internal ones; hand them to `baker analytics sources --create-hubspot --property <name> --value <value>` as printed. No personal data.
|
|
@@ -3871,6 +3880,30 @@ No write surface against HubSpot itself — a form's fields, redirect, or consen
|
|
|
3871
3880
|
|
|
3872
3881
|
---
|
|
3873
3882
|
|
|
3883
|
+
### `baker accounts influenced | engagement | warm | company | campaigns | crm`
|
|
3884
|
+
|
|
3885
|
+
Account insights: which companies the LinkedIn ads are warming, and which CRM deals they influenced. Reads Baker's nightly copy of LinkedIn and the CRM, so it is fast and covers up to 24 months; nothing is changed anywhere. Only for companies with Account insights switched on — otherwise every command exits 1 with `ACCOUNTS_NOT_ENABLED` and an `error.fix` pointing at the live read (`baker ads linkedin companies`, plus `baker hubspot companies search`). `--start`/`--end` must be real calendar days within 24 calendar months; a bad flag or an unsupported `--output` exits 1 with `INVALID_ARGS`.
|
|
3886
|
+
|
|
3887
|
+
```bash
|
|
3888
|
+
baker accounts influenced # influenced pipeline, last 12 calendar months
|
|
3889
|
+
baker accounts influenced --basis revenue --start 2026-01-01 --end 2026-06-30
|
|
3890
|
+
baker accounts influenced --full # every deal in the period, influenced or not
|
|
3891
|
+
baker accounts engagement --last-months 3 --limit 50 # companies reached, New/Rising, CRM match and deals
|
|
3892
|
+
baker accounts engagement --campaign-id 123456 --output csv
|
|
3893
|
+
baker accounts warm --output csv > warm.csv # companyName,domain,organizationUrn
|
|
3894
|
+
baker accounts company acme.com # or a LinkedIn company id / urn:li:organization:N
|
|
3895
|
+
baker accounts campaigns --last-months 6 --output md # per campaign: reached, engaged, influenced pipeline (--basis revenue)
|
|
3896
|
+
baker accounts influenced --model awareness --threshold 10 --output csv # another rule, this call only
|
|
3897
|
+
baker accounts warm --min 0 # every New/Rising company, below the threshold too
|
|
3898
|
+
baker accounts crm status # deals from HubSpot or sent by the client; what arrived, linked Google Sheets
|
|
3899
|
+
baker accounts crm check --file deals.csv # judge a deals or company CSV offline, nothing sent
|
|
3900
|
+
baker accounts crm contract # the send-your-CRM contract, as Markdown for a developer
|
|
3901
|
+
```
|
|
3902
|
+
|
|
3903
|
+
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.
|
|
3904
|
+
|
|
3905
|
+
---
|
|
3906
|
+
|
|
3874
3907
|
### `baker chats list | view | transcript | diff`
|
|
3875
3908
|
|
|
3876
3909
|
Read-only inspection of **other Sessions (chats) on the same account** — so an agent can reuse prior work ("like we did in that other chat, but for X"). Same-account only; never crosses a company boundary and has no write surface.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debugLogSetting
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AKX7S6ZW.js";
|
|
4
4
|
|
|
5
5
|
// src/debugLog.ts
|
|
6
6
|
import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
|
|
@@ -33,45 +33,6 @@ function redactSecrets(input) {
|
|
|
33
33
|
}
|
|
34
34
|
return out;
|
|
35
35
|
}
|
|
36
|
-
var CREDENTIAL_VALUE = "[credential value]";
|
|
37
|
-
var CREDENTIAL_PUT_PATH = "/api/credentials/put";
|
|
38
|
-
function withoutCredentialValue(url, body) {
|
|
39
|
-
let path;
|
|
40
|
-
try {
|
|
41
|
-
path = new URL(url).pathname;
|
|
42
|
-
} catch {
|
|
43
|
-
path = url;
|
|
44
|
-
}
|
|
45
|
-
if (path !== CREDENTIAL_PUT_PATH || body === null || typeof body !== "object" || !("value" in body)) {
|
|
46
|
-
return body;
|
|
47
|
-
}
|
|
48
|
-
return { ...body, value: CREDENTIAL_VALUE };
|
|
49
|
-
}
|
|
50
|
-
var PUT_FLAGS_WITH_VALUES = /* @__PURE__ */ new Set(["--host", "--label"]);
|
|
51
|
-
function argvWithoutCredentialValue(argv) {
|
|
52
|
-
if (argv[0] !== "credentials" || argv[1] !== "put") {
|
|
53
|
-
return argv;
|
|
54
|
-
}
|
|
55
|
-
let nameSeen = false;
|
|
56
|
-
let keepNext = false;
|
|
57
|
-
let redactNext = false;
|
|
58
|
-
return argv.map((arg, index) => {
|
|
59
|
-
if (index < 2) return arg;
|
|
60
|
-
if (keepNext || redactNext) {
|
|
61
|
-
const kept = keepNext ? arg : CREDENTIAL_VALUE;
|
|
62
|
-
keepNext = false;
|
|
63
|
-
redactNext = false;
|
|
64
|
-
return kept;
|
|
65
|
-
}
|
|
66
|
-
if (arg.startsWith("--value=")) return `--value=${CREDENTIAL_VALUE}`;
|
|
67
|
-
redactNext = arg === "--value";
|
|
68
|
-
keepNext = PUT_FLAGS_WITH_VALUES.has(arg);
|
|
69
|
-
if (arg.startsWith("-")) return arg;
|
|
70
|
-
const isName = !nameSeen;
|
|
71
|
-
nameSeen = true;
|
|
72
|
-
return isName ? arg : CREDENTIAL_VALUE;
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
36
|
function resolveTarget() {
|
|
76
37
|
const raw = debugLogSetting();
|
|
77
38
|
if (!raw) {
|
|
@@ -137,7 +98,7 @@ function errorToPlain(err) {
|
|
|
137
98
|
var invocationStartedAt;
|
|
138
99
|
function logInvocation(argv) {
|
|
139
100
|
invocationStartedAt = Date.now();
|
|
140
|
-
append({ kind: "invocation", argv
|
|
101
|
+
append({ kind: "invocation", argv, cwd: process.cwd() });
|
|
141
102
|
}
|
|
142
103
|
function logExit(code) {
|
|
143
104
|
append({
|
|
@@ -166,7 +127,7 @@ function debugLogHttp(entry) {
|
|
|
166
127
|
method: entry.method,
|
|
167
128
|
url: entry.url,
|
|
168
129
|
durationMs: entry.durationMs,
|
|
169
|
-
request:
|
|
130
|
+
request: entry.requestBody,
|
|
170
131
|
status: entry.status,
|
|
171
132
|
ok: entry.ok,
|
|
172
133
|
response: entry.responseBody,
|
|
@@ -212,4 +173,4 @@ export {
|
|
|
212
173
|
readBodyForLog,
|
|
213
174
|
installStreamTaps
|
|
214
175
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
176
|
+
//# sourceMappingURL=chunk-3FJU7BNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/debugLog.ts"],"sourcesContent":["// Full-fidelity debug capture for the whole CLI.\n//\n// Set BAKER_DEBUG_LOG to turn it on:\n// BAKER_DEBUG_LOG=1 -> appends to ./baker-cli-debug.jsonl\n// BAKER_DEBUG_LOG=/tmp/baker.jsonl -> appends to that file\n// BAKER_DEBUG_LOG=0|false|off|no -> disabled (same as unset)\n//\n// When enabled, every HTTP request/response (both the REST client and the\n// canvas engine client), every byte written to stdout/stderr, and the command\n// invocation are appended as JSONL entries. Known secret shapes are redacted\n// before anything is written (best-effort — see redactSecrets). Logging is\n// always fail-open: a logging error is swallowed so it can never break a\n// command. The file rotates once past MAX_BYTES so an always-on capture cannot\n// fill the disk.\n\nimport { appendFileSync, mkdirSync, renameSync, statSync } from \"node:fs\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { debugLogSetting } from \"./env.ts\";\n\nconst DEFAULT_FILE = \"baker-cli-debug.jsonl\";\nconst BOOLEAN_ON = new Set([\"1\", \"true\", \"on\", \"yes\"]);\nconst BOOLEAN_OFF = new Set([\"0\", \"false\", \"off\", \"no\"]);\n\n// Rotate once the active file passes this size (one older generation, `<file>.1`,\n// is kept). Bounds growth for the always-on sandbox capture without a per-write stat.\nconst MAX_BYTES = 64 * 1024 * 1024;\n\n// Best-effort redaction. Response/request bodies are stored as escaped strings, so\n// key/value matching is unreliable there; instead we scrub the token *values*\n// themselves (quote-agnostic) plus the auth header and Baker keys. This is not\n// exhaustive — a secret in an unrecognized shape can still be written. See README.\nconst REDACTIONS: ReadonlyArray<readonly [RegExp, string]> = [\n [/Bearer\\s+[A-Za-z0-9._-]+/g, \"Bearer [redacted]\"], // Authorization headers\n // Both prefixes, captured rather than listed twice: `bkr_` does not contain\n // the literal `bk_`, so the company-key rule matched no part of the read-only\n // credential a Session review runs with.\n [/\\b(bkr?)_[A-Za-z0-9]+/g, \"$1_[redacted]\"], // Baker API keys\n [/eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/g, \"[redacted-jwt]\"], // JWTs / id tokens\n [/ya29\\.[A-Za-z0-9._-]+/g, \"[redacted-google-token]\"], // Google OAuth access tokens\n [/1\\/\\/[A-Za-z0-9._-]+/g, \"[redacted-google-refresh]\"], // Google refresh tokens\n [/EAA[A-Za-z0-9]{20,}/g, \"[redacted-meta-token]\"], // Meta/Facebook access tokens\n];\n\nexport function redactSecrets(input: string): string {\n let out = input;\n for (const [pattern, replacement] of REDACTIONS) {\n out = out.replace(pattern, replacement);\n }\n return out;\n}\n\nfunction resolveTarget(): string | undefined {\n const raw = debugLogSetting();\n if (!raw) {\n return undefined;\n }\n const lower = raw.toLowerCase();\n if (BOOLEAN_OFF.has(lower)) {\n return undefined;\n }\n const target = BOOLEAN_ON.has(lower) ? DEFAULT_FILE : raw;\n return isAbsolute(target) ? target : resolve(process.cwd(), target);\n}\n\nexport function isDebugLogEnabled(): boolean {\n return resolveTarget() !== undefined;\n}\n\nlet sessionId: string | undefined;\nfunction getSessionId(): string {\n if (!sessionId) {\n sessionId = `${process.pid}-${Date.now()}`;\n }\n return sessionId;\n}\n\n// Per-resolved-path bookkeeping so the hot path skips redundant syscalls: the\n// directory is ensured once and the current size is read once (via statSync),\n// then tracked in-process. Resets when the resolved path changes.\nlet trackedPath: string | undefined;\nlet bytesInFile = 0;\n\nfunction ensureReady(filePath: string): void {\n if (trackedPath !== filePath) {\n trackedPath = filePath;\n mkdirSync(dirname(filePath), { recursive: true });\n try {\n bytesInFile = statSync(filePath).size;\n } catch {\n bytesInFile = 0;\n }\n }\n}\n\nfunction append(entry: Record<string, unknown>): void {\n const filePath = resolveTarget();\n if (!filePath) {\n return;\n }\n try {\n const payload = { ts: new Date().toISOString(), session: getSessionId(), ...entry };\n const line = `${redactSecrets(JSON.stringify(payload))}\\n`;\n ensureReady(filePath);\n const size = Buffer.byteLength(line);\n if (bytesInFile + size > MAX_BYTES) {\n renameSync(filePath, `${filePath}.1`);\n bytesInFile = 0;\n }\n appendFileSync(filePath, line);\n bytesInFile += size;\n } catch {\n // Fail-open: debug logging must never break a command.\n }\n}\n\nfunction errorToPlain(err: unknown): Record<string, unknown> {\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n return { name: err.name, message: err.message, ...(typeof code === \"string\" ? { code } : {}) };\n }\n return { message: String(err) };\n}\n\nlet invocationStartedAt: number | undefined;\n\nexport function logInvocation(argv: string[]): void {\n invocationStartedAt = Date.now();\n append({ kind: \"invocation\", argv, cwd: process.cwd() });\n}\n\n/**\n * Close an invocation with its outcome and its wall time.\n *\n * Until this existed the log could say what was called and what every HTTP hop\n * cost, and nothing at all about whether the command succeeded or how long the\n * whole thing took — so \"this verb is slow\" and \"this verb fails half the time\"\n * were both unanswerable from the one record that sees every call. `durationMs`\n * is measured from `logInvocation`, which runs before any command does.\n */\nexport function logExit(code: number): void {\n append({\n kind: \"exit\",\n code,\n ...(invocationStartedAt === undefined ? {} : { durationMs: Date.now() - invocationStartedAt }),\n });\n}\n\nlet exitHookInstalled = false;\n\n/**\n * Write the exit record however the process ends.\n *\n * `process.on(\"exit\")` is the only hook that sees all of them — a clean return, a\n * `process.exit` from the error handler, an unhandled rejection — and it is\n * synchronous-only, which is exactly what `appendFileSync` needs. Installed once,\n * and only when logging is on, so the disabled path adds no listener at all.\n */\nexport function installExitRecord(): void {\n if (exitHookInstalled || !isDebugLogEnabled()) {\n return;\n }\n exitHookInstalled = true;\n process.on(\"exit\", (code) => {\n logExit(typeof code === \"number\" ? code : 0);\n });\n}\n\nexport type HttpLogEntry = {\n source: \"cli\" | \"canvas\";\n method: string;\n url: string;\n requestBody?: unknown;\n status?: number;\n ok?: boolean;\n responseBody?: string;\n durationMs?: number;\n error?: unknown;\n};\n\nexport function debugLogHttp(entry: HttpLogEntry): void {\n if (!isDebugLogEnabled()) {\n return;\n }\n append({\n kind: \"http\",\n source: entry.source,\n method: entry.method,\n url: entry.url,\n durationMs: entry.durationMs,\n request: entry.requestBody,\n status: entry.status,\n ok: entry.ok,\n response: entry.responseBody,\n error: entry.error === undefined ? undefined : errorToPlain(entry.error),\n });\n}\n\n// Reads a response body for logging without consuming it for the real caller.\nexport async function readBodyForLog(response: Response): Promise<string | undefined> {\n if (!isDebugLogEnabled()) {\n return undefined;\n }\n try {\n return await response.clone().text();\n } catch {\n return undefined;\n }\n}\n\n// Method syntax (bivariant) so a real `process.stdout`/`stderr` and a plain\n// test double both satisfy it.\ntype Writable = { write(...args: unknown[]): boolean };\n\n// Wraps a stream's `write` so every chunk is mirrored into the debug log while\n// the original write semantics (args + return value) are preserved. A single\n// streaming TextDecoder per stream decodes byte chunks so a multi-byte character\n// split across two writes is reassembled instead of garbled.\nexport function tapStream(name: string, stream: Writable): void {\n const original = stream.write.bind(stream) as (...args: unknown[]) => boolean;\n const decoder = new TextDecoder(\"utf-8\");\n stream.write = ((chunk: unknown, ...rest: unknown[]): boolean => {\n try {\n const text =\n typeof chunk === \"string\"\n ? chunk\n : chunk instanceof Uint8Array\n ? decoder.decode(chunk, { stream: true })\n : String(chunk);\n append({ kind: \"output\", stream: name, text });\n } catch {\n // ignore capture failures\n }\n return original(chunk, ...rest);\n }) as typeof stream.write;\n}\n\nlet tapsInstalled = false;\n\n// Mirrors everything written to stdout/stderr into the debug log. Installed once,\n// only when enabled, so the common (disabled) path leaves the streams untouched.\nexport function installStreamTaps(): void {\n if (tapsInstalled || !isDebugLogEnabled()) {\n return;\n }\n tapsInstalled = true;\n tapStream(\"stdout\", process.stdout);\n tapStream(\"stderr\", process.stderr);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
handleConnectionError,
|
|
3
3
|
needsConnectionFix,
|
|
4
4
|
writeAdsJson
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OEI3NLGH.js";
|
|
6
6
|
import {
|
|
7
7
|
ApiError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LMMZ3FAC.js";
|
|
9
9
|
import {
|
|
10
10
|
getEnv
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AKX7S6ZW.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/ads/meta/shared.ts
|
|
14
14
|
var DAY_MS = 864e5;
|
|
@@ -133,4 +133,4 @@ export {
|
|
|
133
133
|
resolveEffectiveStatus,
|
|
134
134
|
statusFilterHints
|
|
135
135
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-6V6B7RPR.js.map
|
|
@@ -1280,12 +1280,19 @@ function debugLogSetting() {
|
|
|
1280
1280
|
const raw = process.env.BAKER_DEBUG_LOG?.trim();
|
|
1281
1281
|
return raw ? raw : void 0;
|
|
1282
1282
|
}
|
|
1283
|
+
var MissingChatIdError = class extends Error {
|
|
1284
|
+
code = "MISSING_CHAT_ID";
|
|
1285
|
+
constructor() {
|
|
1286
|
+
super(
|
|
1287
|
+
"BAKER_CHAT_ID is not set. This command stages changes against a chat \u2014 run it from a chat-attached environment."
|
|
1288
|
+
);
|
|
1289
|
+
this.name = "MissingChatIdError";
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1283
1292
|
function requireChatId() {
|
|
1284
1293
|
const env = getEnv();
|
|
1285
1294
|
if (!env.BAKER_CHAT_ID) {
|
|
1286
|
-
throw new
|
|
1287
|
-
"BAKER_CHAT_ID is not set. This command stages changes against a chat \u2014 run it from a chat-attached environment."
|
|
1288
|
-
);
|
|
1295
|
+
throw new MissingChatIdError();
|
|
1289
1296
|
}
|
|
1290
1297
|
return env.BAKER_CHAT_ID;
|
|
1291
1298
|
}
|
|
@@ -1353,6 +1360,7 @@ export {
|
|
|
1353
1360
|
requireConnection,
|
|
1354
1361
|
optionalChatId,
|
|
1355
1362
|
debugLogSetting,
|
|
1363
|
+
MissingChatIdError,
|
|
1356
1364
|
requireChatId,
|
|
1357
1365
|
resolveChatId,
|
|
1358
1366
|
hasFrameVisionKey,
|
|
@@ -1364,4 +1372,4 @@ export {
|
|
|
1364
1372
|
captureVimeoCookie,
|
|
1365
1373
|
childEnvWith
|
|
1366
1374
|
};
|
|
1367
|
-
//# sourceMappingURL=chunk-
|
|
1375
|
+
//# sourceMappingURL=chunk-AKX7S6ZW.js.map
|