@sellable/install 0.1.217 → 0.1.219

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
@@ -35,9 +35,8 @@ Campaign creation, foundation memory, content capture/ideation, and post
35
35
  drafting run inside Claude Code or Codex, where the Sellable MCP tools and
36
36
  approval flows are available.
37
37
 
38
- Install is auth-free by default. The normal path is first-run login: launch a
39
- Sellable workflow in Claude Code or Codex and the agent handles Sellable
40
- sign-in with a browser magic-link handoff.
38
+ Install is auth-free by default. If you do not pass a token, the agent handles
39
+ Sellable sign-in on the first campaign run with a magic-link handoff.
41
40
 
42
41
  The installer uses package stdio MCP by default:
43
42
 
@@ -65,15 +64,14 @@ verification in one path:
65
64
  curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
66
65
  ```
67
66
 
68
- For scripted fallback after browser login, paste the command shown by Sellable:
67
+ For CI/scripted installs, get a Sellable API token from:
69
68
 
70
- ```bash
71
- sellable auth set <token> --workspace-id <workspace_id>
69
+ ```text
70
+ https://app.sellable.dev/settings
72
71
  ```
73
72
 
74
- For CI/env-only installs, operators can still pass `--token` / `SELLABLE_TOKEN`
75
- plus `--workspace-id` / `SELLABLE_WORKSPACE_ID`. Do not use env vars as the
76
- primary human setup path.
73
+ Then pass it with `--token` / `SELLABLE_TOKEN` plus `--workspace-id` /
74
+ `SELLABLE_WORKSPACE_ID`.
77
75
 
78
76
  Auth is stored once at:
79
77
 
@@ -419,26 +419,13 @@ async function loadAuthIfPresent(opts) {
419
419
  return opts;
420
420
  }
421
421
 
422
- function redactTokensForLog(value) {
423
- if (Array.isArray(value)) {
424
- return value.map((item) => redactTokensForLog(item));
425
- }
426
- if (value && typeof value === "object") {
427
- return Object.fromEntries(
428
- Object.entries(value).map(([key, item]) => [
429
- key,
430
- key.toLowerCase().includes("token") && typeof item === "string"
431
- ? redact(item)
432
- : redactTokensForLog(item),
433
- ])
434
- );
435
- }
436
- return value;
437
- }
438
-
439
422
  function writeJson(path, data, opts) {
440
423
  if (VERBOSE) {
441
- const redacted = JSON.stringify(redactTokensForLog(data), null, 2);
424
+ const redacted = JSON.stringify(
425
+ { ...data, token: redact(data.token) },
426
+ null,
427
+ 2
428
+ );
442
429
  logVerbose(`${C.grey}Writing ${path}: ${redacted}${C.reset}`);
443
430
  }
444
431
  if (opts.dryRun) return;
@@ -455,56 +442,6 @@ function readExisting(path) {
455
442
  }
456
443
  }
457
444
 
458
- function mergeAuthConfig(raw, auth) {
459
- const existing =
460
- raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
461
- const authFields = {
462
- token: auth.token,
463
- activeWorkspaceId: auth.activeWorkspaceId,
464
- apiUrl: auth.apiUrl,
465
- };
466
-
467
- if (existing.activeEnv && existing.environments) {
468
- const activeEnv = existing.activeEnv;
469
- const envConfig = existing.environments[activeEnv];
470
- if (
471
- !envConfig ||
472
- typeof envConfig !== "object" ||
473
- Array.isArray(envConfig)
474
- ) {
475
- throw new Error(
476
- `Unknown active environment '${activeEnv}' in ${authPath()}`
477
- );
478
- }
479
- return {
480
- ...existing,
481
- environments: {
482
- ...existing.environments,
483
- [activeEnv]: {
484
- ...envConfig,
485
- ...authFields,
486
- },
487
- },
488
- };
489
- }
490
-
491
- return {
492
- ...existing,
493
- ...authFields,
494
- };
495
- }
496
-
497
- function writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun }) {
498
- const configPath = authPath();
499
- const raw = readExisting(configPath) || {};
500
- const config = mergeAuthConfig(raw, {
501
- token,
502
- activeWorkspaceId: workspaceId || null,
503
- apiUrl,
504
- });
505
- writeJson(configPath, config, { dryRun });
506
- }
507
-
508
445
  function sellableHostEnvPath() {
509
446
  return join(homedir(), ".local", "sellable", "app-sellable-dev", ".env");
510
447
  }
@@ -802,9 +739,9 @@ const CREATE_CAMPAIGN_ALLOWED_TOOLS = [
802
739
  "mcp__sellable__save_rubrics",
803
740
  "mcp__sellable__get_campaign_table_schema",
804
741
  "mcp__sellable__select_campaign_cells",
742
+ "mcp__sellable__record_campaign_review_batch",
805
743
  "mcp__sellable__queue_campaign_cells",
806
744
  "mcp__sellable__wait_for_campaign_processing",
807
- "mcp__sellable__fill_campaign_horizon",
808
745
  "mcp__sellable__start_campaign_message_preparation",
809
746
  "mcp__sellable__get_campaign_message_preparation_status",
810
747
  "mcp__sellable__cancel_campaign_message_preparation",
@@ -3728,31 +3665,18 @@ async function main() {
3728
3665
  }
3729
3666
  const token = rawArgs[2];
3730
3667
  const authSetFlags = rawArgs.slice(3);
3731
- let dryRun = false;
3732
- let workspaceId = "";
3733
- for (let i = 0; i < authSetFlags.length; i += 1) {
3734
- const flag = authSetFlags[i];
3735
- if (flag === "--dry-run") {
3736
- dryRun = true;
3737
- continue;
3738
- }
3739
- if (flag === "--workspace-id") {
3740
- const value = authSetFlags[i + 1];
3741
- if (!value || value.startsWith("--")) {
3742
- console.error("Missing value for --workspace-id");
3743
- process.exit(2);
3744
- }
3745
- workspaceId = value;
3746
- i += 1;
3747
- continue;
3748
- }
3749
- console.error(`Unknown auth set option: ${flag}`);
3668
+ const dryRun = authSetFlags.includes("--dry-run");
3669
+ const unknownAuthSetFlag = authSetFlags.find(
3670
+ (arg) => arg !== "--dry-run"
3671
+ );
3672
+ if (unknownAuthSetFlag) {
3673
+ console.error(`Unknown auth set option: ${unknownAuthSetFlag}`);
3750
3674
  process.exit(2);
3751
3675
  }
3752
3676
  if (!token) {
3753
3677
  console.error(
3754
- "Usage: sellable auth set <token> [--workspace-id <id>]\n" +
3755
- "Use this only when the Sellable browser login page shows a manual fallback command."
3678
+ "Usage: sellable auth set <token>\n" +
3679
+ "Get the token from the Sellable browser confirm page (after clicking the magic link)."
3756
3680
  );
3757
3681
  process.exit(2);
3758
3682
  }
@@ -3764,22 +3688,23 @@ async function main() {
3764
3688
  );
3765
3689
  process.exit(2);
3766
3690
  }
3767
- // Bypass writeAuth() because auth-set is a first-run browser fallback:
3768
- // the workspace id is optional for legacy fallback pages, and existing
3769
- // config metadata must be preserved.
3691
+ // Bypass writeAuth() its workspaceId guard early-returns on missing
3692
+ // workspaceId, but on the auth-set path we don't have one yet (next MCP
3693
+ // call hydrates it). Write the same shape directly.
3770
3694
  // Skip installSelfShim() — by definition the user has the shim already
3771
3695
  // (they invoked `sellable auth set` from it).
3772
3696
  const apiUrl = process.env.SELLABLE_API_URL || DEFAULT_API_URL;
3773
- writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun });
3697
+ writeJson(
3698
+ authPath(),
3699
+ { token, activeWorkspaceId: null, apiUrl },
3700
+ { dryRun }
3701
+ );
3774
3702
  if (dryRun) {
3775
3703
  console.log(`Dry run: token would be saved to ${authPath()}`);
3776
3704
  } else {
3777
3705
  console.log(`✓ Token saved to ${authPath()}`);
3778
3706
  }
3779
3707
  console.log(` apiUrl: ${apiUrl}`);
3780
- if (workspaceId) {
3781
- console.log(` activeWorkspaceId: ${workspaceId}`);
3782
- }
3783
3708
  console.log(` Continue in your agent:`);
3784
3709
  console.log(` Claude Code: /sellable:create-campaign`);
3785
3710
  console.log(` Claude Code: /sellable:foundation`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.217",
3
+ "version": "0.1.219",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {
@@ -44,9 +44,9 @@ allowed-tools:
44
44
  - mcp__sellable__save_rubrics
45
45
  - mcp__sellable__get_campaign_table_schema
46
46
  - mcp__sellable__select_campaign_cells
47
+ - mcp__sellable__record_campaign_review_batch
47
48
  - mcp__sellable__queue_campaign_cells
48
49
  - mcp__sellable__wait_for_campaign_processing
49
- - mcp__sellable__fill_campaign_horizon
50
50
  - mcp__sellable__start_campaign_message_preparation
51
51
  - mcp__sellable__get_campaign_message_preparation_status
52
52
  - mcp__sellable__cancel_campaign_message_preparation
@@ -105,16 +105,15 @@ most one direct `enrich_with_prospeo` sample when row evidence is too thin.
105
105
  After filter approval, the browser should move to Filter Leads with
106
106
  `currentStep: "apply-icp-rubric"` and show template waiting/approval copy until
107
107
  the template is approved.
108
+ If the bounded filter run later returns `0/N` passes, do not immediately find
109
+ new leads. Load `references/sample-validation-loop.md`, run the zero-pass
110
+ rule-relaxability audit, then choose exactly one recovery: revise saved
111
+ rubrics, record a fresh same-source review batch, or change lead source. Change
112
+ source only when safe relaxation and same-source sampling would still fail or
113
+ would pass bad-fit rows.
108
114
  The default path stays the existing first campaign-table execution slice:
109
115
  review the normal `reviewBatchLimit:15`, approve reviewed draft rows, then move
110
116
  to Settings/sequence/final greenlight. Only call
111
- `fill_campaign_horizon` for explicit source-cleanup horizon-fill requests, such
112
- as "fill sends from Signal Discovery but not John Cutler posts." Run
113
- `fill_campaign_horizon({ action:"audit", ... })` first, then apply with the
114
- returned `stateRevision`. It imports/prepares at most 300 eligible non-excluded
115
- source rows in the first pass, caps prep batches at 100, skips existing rows
116
- from excluded post/author sources, and does not launch the campaign. If the
117
- user only asks for generic extra sends with no source cleanup, use
118
117
  `start_campaign_message_preparation` when the user explicitly asks for more
119
118
  prepared messages, a send count, or language like "fill up/load sends for these
120
119
  senders." Treat those requests as capacity-fill preparation: calculate the
@@ -128,9 +127,8 @@ count `checkedRows` as enriched rows; it is only the table cursor. Use
128
127
  messages", set `targetPreparedMessages:X`, omit `maxRowsToCheck`, and keep
129
128
  `approvalMode:"mark_ready"`. The backend calibrates on at least 100 actually
130
129
  enriched rows, estimates the row budget from observed rubric/pass yield, caps
131
- `maxRowsToCheck` at 300, then continues in batches capped at 100 newly checked
132
- rows. It will not pull another row batch while the current checked batch still
133
- has queueable or active cells. If the user says "approve X messages", use
130
+ `maxRowsToCheck` at 2500, then adapts later batches up to 250 rows while
131
+ recalculating yield. If the user says "approve X messages", use
134
132
  `approvalMode:"approve"` but still do not launch. If the user says "schedule X
135
133
  sends" or asks to fill sender sends, use `approvalMode:"approve"` to approve
136
134
  exactly the bounded X-message cohort during preparation, then continue through
@@ -893,11 +891,7 @@ updates.
893
891
 
894
892
  1. Call `mcp__sellable__get_auth_status({})`.
895
893
  2. If auth is not OK with `error.type === "config"` or `error.type === "auth"`,
896
- the user has not signed in yet. Run first-run login through the FTUX
897
- magic-link handoff. If a browser page or tool guidance gives the user a
898
- manual fallback, it must be
899
- `sellable auth set <token> --workspace-id <workspace_id>`. Do not instruct
900
- the user to hand-edit JSON auth config.
894
+ the user has not signed in yet. Run the FTUX magic-link handoff:
901
895
 
902
896
  a. Say to the user verbatim:
903
897
 
@@ -1085,7 +1079,11 @@ updates.
1085
1079
  rubrics and Message Drafting runs.
1086
1080
  After rubrics save, keep Filter Rules visible for approval; after approval,
1087
1081
  move to Filter Leads with `currentStep: "apply-icp-rubric"` and wait there
1088
- while Message Drafting finishes or the template is approved.
1082
+ while Message Drafting finishes or the template is approved. After template
1083
+ approval and bounded scoring, a `0/N` pass result must run the
1084
+ sample-validation zero-pass rule-relaxability audit before any lead-source
1085
+ revision; the three allowed recoveries are rubric revision, fresh
1086
+ same-source sample, or source revision.
1089
1087
  If filters are skipped, launch Message Drafting before moving to
1090
1088
  Messages/message review; updating `currentStep` to `messages` is not proof
1091
1089
  that the background worker started. Queue the bounded campaign-table