@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 +7 -9
- package/bin/sellable-install.mjs +22 -97
- package/package.json +1 -1
- package/skill-templates/create-campaign.md +15 -17
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.
|
|
39
|
-
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
|
|
67
|
+
For CI/scripted installs, get a Sellable API token from:
|
|
69
68
|
|
|
70
|
-
```
|
|
71
|
-
sellable
|
|
69
|
+
```text
|
|
70
|
+
https://app.sellable.dev/settings
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
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
|
|
3755
|
-
"
|
|
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()
|
|
3768
|
-
// the
|
|
3769
|
-
//
|
|
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
|
-
|
|
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
|
@@ -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
|
|
132
|
-
|
|
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
|
|
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
|