@sellable/install 0.1.218 → 0.1.220
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 +9 -7
- package/bin/sellable-install.mjs +99 -21
- package/lib/runtime-verify.mjs +2 -0
- package/package.json +1 -1
- package/skill-templates/create-campaign.md +52 -24
package/README.md
CHANGED
|
@@ -35,8 +35,9 @@ 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
|
|
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.
|
|
40
41
|
|
|
41
42
|
The installer uses package stdio MCP by default:
|
|
42
43
|
|
|
@@ -64,14 +65,15 @@ verification in one path:
|
|
|
64
65
|
curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
For
|
|
68
|
+
For scripted fallback after browser login, paste the command shown by Sellable:
|
|
68
69
|
|
|
69
|
-
```
|
|
70
|
-
|
|
70
|
+
```bash
|
|
71
|
+
sellable auth set <token> --workspace-id <workspace_id>
|
|
71
72
|
```
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
`SELLABLE_WORKSPACE_ID`.
|
|
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.
|
|
75
77
|
|
|
76
78
|
Auth is stored once at:
|
|
77
79
|
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -605,13 +605,26 @@ async function loadAuthIfPresent(opts) {
|
|
|
605
605
|
return opts;
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
+
function redactTokensForLog(value) {
|
|
609
|
+
if (Array.isArray(value)) {
|
|
610
|
+
return value.map((item) => redactTokensForLog(item));
|
|
611
|
+
}
|
|
612
|
+
if (value && typeof value === "object") {
|
|
613
|
+
return Object.fromEntries(
|
|
614
|
+
Object.entries(value).map(([key, item]) => [
|
|
615
|
+
key,
|
|
616
|
+
key.toLowerCase().includes("token") && typeof item === "string"
|
|
617
|
+
? redact(item)
|
|
618
|
+
: redactTokensForLog(item),
|
|
619
|
+
])
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
624
|
+
|
|
608
625
|
function writeJson(path, data, opts) {
|
|
609
626
|
if (VERBOSE) {
|
|
610
|
-
const redacted = JSON.stringify(
|
|
611
|
-
{ ...data, token: redact(data.token) },
|
|
612
|
-
null,
|
|
613
|
-
2
|
|
614
|
-
);
|
|
627
|
+
const redacted = JSON.stringify(redactTokensForLog(data), null, 2);
|
|
615
628
|
logVerbose(`${C.grey}Writing ${path}: ${redacted}${C.reset}`);
|
|
616
629
|
}
|
|
617
630
|
if (opts.dryRun) return;
|
|
@@ -628,6 +641,56 @@ function readExisting(path) {
|
|
|
628
641
|
}
|
|
629
642
|
}
|
|
630
643
|
|
|
644
|
+
function mergeAuthConfig(raw, auth) {
|
|
645
|
+
const existing =
|
|
646
|
+
raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
647
|
+
const authFields = {
|
|
648
|
+
token: auth.token,
|
|
649
|
+
activeWorkspaceId: auth.activeWorkspaceId,
|
|
650
|
+
apiUrl: auth.apiUrl,
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
if (existing.activeEnv && existing.environments) {
|
|
654
|
+
const activeEnv = existing.activeEnv;
|
|
655
|
+
const envConfig = existing.environments[activeEnv];
|
|
656
|
+
if (
|
|
657
|
+
!envConfig ||
|
|
658
|
+
typeof envConfig !== "object" ||
|
|
659
|
+
Array.isArray(envConfig)
|
|
660
|
+
) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
`Unknown active environment '${activeEnv}' in ${authPath()}`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
...existing,
|
|
667
|
+
environments: {
|
|
668
|
+
...existing.environments,
|
|
669
|
+
[activeEnv]: {
|
|
670
|
+
...envConfig,
|
|
671
|
+
...authFields,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
...existing,
|
|
679
|
+
...authFields,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun }) {
|
|
684
|
+
const configPath = authPath();
|
|
685
|
+
const raw = readExisting(configPath) || {};
|
|
686
|
+
const config = mergeAuthConfig(raw, {
|
|
687
|
+
token,
|
|
688
|
+
activeWorkspaceId: workspaceId || null,
|
|
689
|
+
apiUrl,
|
|
690
|
+
});
|
|
691
|
+
writeJson(configPath, config, { dryRun });
|
|
692
|
+
}
|
|
693
|
+
|
|
631
694
|
function sellableHostEnvPath() {
|
|
632
695
|
return join(homedir(), ".local", "sellable", "app-sellable-dev", ".env");
|
|
633
696
|
}
|
|
@@ -925,8 +988,11 @@ const CREATE_CAMPAIGN_ALLOWED_TOOLS = [
|
|
|
925
988
|
"mcp__sellable__save_rubrics",
|
|
926
989
|
"mcp__sellable__get_campaign_table_schema",
|
|
927
990
|
"mcp__sellable__select_campaign_cells",
|
|
991
|
+
"mcp__sellable__record_campaign_review_batch",
|
|
928
992
|
"mcp__sellable__queue_campaign_cells",
|
|
929
993
|
"mcp__sellable__wait_for_campaign_processing",
|
|
994
|
+
"mcp__sellable__refill_campaign_sends",
|
|
995
|
+
"mcp__sellable__resolve_campaign_fill_route",
|
|
930
996
|
"mcp__sellable__fill_campaign_horizon",
|
|
931
997
|
"mcp__sellable__start_campaign_message_preparation",
|
|
932
998
|
"mcp__sellable__get_campaign_message_preparation_status",
|
|
@@ -3909,18 +3975,31 @@ async function main() {
|
|
|
3909
3975
|
}
|
|
3910
3976
|
const token = rawArgs[2];
|
|
3911
3977
|
const authSetFlags = rawArgs.slice(3);
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3978
|
+
let dryRun = false;
|
|
3979
|
+
let workspaceId = "";
|
|
3980
|
+
for (let i = 0; i < authSetFlags.length; i += 1) {
|
|
3981
|
+
const flag = authSetFlags[i];
|
|
3982
|
+
if (flag === "--dry-run") {
|
|
3983
|
+
dryRun = true;
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3986
|
+
if (flag === "--workspace-id") {
|
|
3987
|
+
const value = authSetFlags[i + 1];
|
|
3988
|
+
if (!value || value.startsWith("--")) {
|
|
3989
|
+
console.error("Missing value for --workspace-id");
|
|
3990
|
+
process.exit(2);
|
|
3991
|
+
}
|
|
3992
|
+
workspaceId = value;
|
|
3993
|
+
i += 1;
|
|
3994
|
+
continue;
|
|
3995
|
+
}
|
|
3996
|
+
console.error(`Unknown auth set option: ${flag}`);
|
|
3918
3997
|
process.exit(2);
|
|
3919
3998
|
}
|
|
3920
3999
|
if (!token) {
|
|
3921
4000
|
console.error(
|
|
3922
|
-
"Usage: sellable auth set <token
|
|
3923
|
-
"
|
|
4001
|
+
"Usage: sellable auth set <token> [--workspace-id <id>]\n" +
|
|
4002
|
+
"Use this only when the Sellable browser login page shows a manual fallback command."
|
|
3924
4003
|
);
|
|
3925
4004
|
process.exit(2);
|
|
3926
4005
|
}
|
|
@@ -3932,23 +4011,22 @@ async function main() {
|
|
|
3932
4011
|
);
|
|
3933
4012
|
process.exit(2);
|
|
3934
4013
|
}
|
|
3935
|
-
// Bypass writeAuth()
|
|
3936
|
-
//
|
|
3937
|
-
//
|
|
4014
|
+
// Bypass writeAuth() because auth-set is a first-run browser fallback:
|
|
4015
|
+
// the workspace id is optional for legacy fallback pages, and existing
|
|
4016
|
+
// config metadata must be preserved.
|
|
3938
4017
|
// Skip installSelfShim() — by definition the user has the shim already
|
|
3939
4018
|
// (they invoked `sellable auth set` from it).
|
|
3940
4019
|
const apiUrl = process.env.SELLABLE_API_URL || DEFAULT_API_URL;
|
|
3941
|
-
|
|
3942
|
-
authPath(),
|
|
3943
|
-
{ token, activeWorkspaceId: null, apiUrl },
|
|
3944
|
-
{ dryRun }
|
|
3945
|
-
);
|
|
4020
|
+
writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun });
|
|
3946
4021
|
if (dryRun) {
|
|
3947
4022
|
console.log(`Dry run: token would be saved to ${authPath()}`);
|
|
3948
4023
|
} else {
|
|
3949
4024
|
console.log(`✓ Token saved to ${authPath()}`);
|
|
3950
4025
|
}
|
|
3951
4026
|
console.log(` apiUrl: ${apiUrl}`);
|
|
4027
|
+
if (workspaceId) {
|
|
4028
|
+
console.log(` activeWorkspaceId: ${workspaceId}`);
|
|
4029
|
+
}
|
|
3952
4030
|
console.log(` Continue in your agent:`);
|
|
3953
4031
|
console.log(` Claude Code: /sellable:create-campaign`);
|
|
3954
4032
|
console.log(` Claude Code: /sellable:foundation`);
|
package/lib/runtime-verify.mjs
CHANGED
|
@@ -28,6 +28,8 @@ export const REQUIRED_SELLABLE_MCP_TOOLS = [
|
|
|
28
28
|
"get_campaign_messages_preview",
|
|
29
29
|
"attach_sequence",
|
|
30
30
|
"attach_recommended_sequence",
|
|
31
|
+
"refill_campaign_sends",
|
|
32
|
+
"resolve_campaign_fill_route",
|
|
31
33
|
"start_campaign_message_preparation",
|
|
32
34
|
"get_campaign_message_preparation_status",
|
|
33
35
|
"cancel_campaign_message_preparation",
|
package/package.json
CHANGED
|
@@ -44,8 +44,11 @@ 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
|
|
50
|
+
- mcp__sellable__refill_campaign_sends
|
|
51
|
+
- mcp__sellable__resolve_campaign_fill_route
|
|
49
52
|
- mcp__sellable__fill_campaign_horizon
|
|
50
53
|
- mcp__sellable__start_campaign_message_preparation
|
|
51
54
|
- mcp__sellable__get_campaign_message_preparation_status
|
|
@@ -105,37 +108,54 @@ most one direct `enrich_with_prospeo` sample when row evidence is too thin.
|
|
|
105
108
|
After filter approval, the browser should move to Filter Leads with
|
|
106
109
|
`currentStep: "apply-icp-rubric"` and show template waiting/approval copy until
|
|
107
110
|
the template is approved.
|
|
111
|
+
If the bounded filter run later returns `0/N` passes, do not immediately find
|
|
112
|
+
new leads. Load `references/sample-validation-loop.md`, run the zero-pass
|
|
113
|
+
rule-relaxability audit, then choose exactly one recovery: revise saved
|
|
114
|
+
rubrics, record a fresh same-source review batch, or change lead source. Change
|
|
115
|
+
source only when safe relaxation and same-source sampling would still fail or
|
|
116
|
+
would pass bad-fit rows.
|
|
108
117
|
The default path stays the existing first campaign-table execution slice:
|
|
109
118
|
review the normal `reviewBatchLimit:15`, approve reviewed draft rows, then move
|
|
110
|
-
to Settings/sequence/final greenlight.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
to Settings/sequence/final greenlight. For any plain post-mint fill request
|
|
120
|
+
such as "fill campaigns", "fill up", "refill sends", "max out sends", or
|
|
121
|
+
"load sends", first call `refill_campaign_sends({ mode:"plan", intent:"plain" })`.
|
|
122
|
+
If the user did not include `--yolo`, report the plan and stop. If the user did
|
|
123
|
+
include `--yolo`, call plan first, inspect blockers, then call
|
|
124
|
+
`refill_campaign_sends({ mode:"apply", yolo:true, planRevision, actionIds })`
|
|
125
|
+
using only the selected immutable action IDs from the fresh plan. Plain fill is
|
|
126
|
+
not an alias for `fill_campaign_horizon` or campaign creation. Keep
|
|
127
|
+
`resolve_campaign_fill_route`, `fill_campaign_horizon`, and
|
|
128
|
+
`start_campaign_message_preparation` as fallback/lower-level diagnostics only
|
|
129
|
+
when `refill_campaign_sends` is unavailable or when the refill command itself
|
|
130
|
+
returns an evergreen subplan. `fill_campaign_horizon` is evergreen-only and
|
|
131
|
+
must not be used for regular campaign refill. When more leads are needed, the
|
|
132
|
+
plan must recommend same-campaign source-ladder replenishment; do not create
|
|
133
|
+
warm-post-engager side campaigns, on-demand campaigns, or unrelated campaigns.
|
|
134
|
+
If fallback `resolve_campaign_fill_route` returns `route:"ask_create"`, ask
|
|
135
|
+
whether to create a normal campaign or evergreen campaigns; campaign creation is
|
|
136
|
+
never the default response to plain fill.
|
|
137
|
+
Treat active fills as capacity-fill preparation: calculate the bounded target
|
|
138
|
+
from sender capacity when needed, then let the preparation job queue pending
|
|
139
|
+
`Enrich Prospect` cells, wait for ICP/rubric and Generate Message cells to
|
|
140
|
+
cascade, and mark ready or approve only the target cohort. Do not count
|
|
141
|
+
`checkedRows` as enriched rows; it is only the table cursor. Use
|
|
125
142
|
`progress.enrichedRows`, `progress.needsEnrichRows`, `activeCellCount`,
|
|
126
|
-
`preparedMessages`, `approvedRows`,
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
`preparedMessages`, `approvedRows`, active preparation jobs, sender-health
|
|
144
|
+
blockers, target, estimated row budget remaining, and `stopReason` to explain
|
|
145
|
+
progress. If the user says "prepare/generate X messages", set
|
|
146
|
+
`targetPreparedMessages:X`, omit `maxRowsToCheck`, and keep
|
|
129
147
|
`approvalMode:"mark_ready"`. The backend calibrates on at least 100 actually
|
|
130
148
|
enriched rows, estimates the row budget from observed rubric/pass yield, caps
|
|
131
149
|
`maxRowsToCheck` at 300, then continues in batches capped at 100 newly checked
|
|
132
150
|
rows. It will not pull another row batch while the current checked batch still
|
|
133
151
|
has queueable or active cells. If the user says "approve X messages", use
|
|
134
152
|
`approvalMode:"approve"` but still do not launch. If the user says "schedule X
|
|
135
|
-
sends" or asks to fill sender sends, use `approvalMode:"approve"`
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
sends" or asks to fill sender sends, use `approvalMode:"approve"` only when
|
|
154
|
+
the user explicitly asked for approval, approve exactly the bounded X-message
|
|
155
|
+
cohort during preparation, then re-read scheduled counts; if scheduler-owned
|
|
156
|
+
cells are not present, report prepared/approved/ready awaiting scheduler
|
|
157
|
+
instead of success. Final launch remains a separate explicit user greenlight
|
|
158
|
+
and must verify that bounded cohort and must not broad approve-all.
|
|
139
159
|
When approving reviewed draft rows in the campaign table, resolve the actual
|
|
140
160
|
visible `Approved` cells with `select_campaign_cells({ columnRole: "approved",
|
|
141
161
|
rowSelector: { type: "rowIds", rowIds } })` and `update_cell` those returned
|
|
@@ -893,7 +913,11 @@ updates.
|
|
|
893
913
|
|
|
894
914
|
1. Call `mcp__sellable__get_auth_status({})`.
|
|
895
915
|
2. If auth is not OK with `error.type === "config"` or `error.type === "auth"`,
|
|
896
|
-
the user has not signed in yet. Run the FTUX
|
|
916
|
+
the user has not signed in yet. Run first-run login through the FTUX
|
|
917
|
+
magic-link handoff. If a browser page or tool guidance gives the user a
|
|
918
|
+
manual fallback, it must be
|
|
919
|
+
`sellable auth set <token> --workspace-id <workspace_id>`. Do not instruct
|
|
920
|
+
the user to hand-edit JSON auth config.
|
|
897
921
|
|
|
898
922
|
a. Say to the user verbatim:
|
|
899
923
|
|
|
@@ -1081,7 +1105,11 @@ updates.
|
|
|
1081
1105
|
rubrics and Message Drafting runs.
|
|
1082
1106
|
After rubrics save, keep Filter Rules visible for approval; after approval,
|
|
1083
1107
|
move to Filter Leads with `currentStep: "apply-icp-rubric"` and wait there
|
|
1084
|
-
while Message Drafting finishes or the template is approved.
|
|
1108
|
+
while Message Drafting finishes or the template is approved. After template
|
|
1109
|
+
approval and bounded scoring, a `0/N` pass result must run the
|
|
1110
|
+
sample-validation zero-pass rule-relaxability audit before any lead-source
|
|
1111
|
+
revision; the three allowed recoveries are rubric revision, fresh
|
|
1112
|
+
same-source sample, or source revision.
|
|
1085
1113
|
If filters are skipped, launch Message Drafting before moving to
|
|
1086
1114
|
Messages/message review; updating `currentStep` to `messages` is not proof
|
|
1087
1115
|
that the background worker started. Queue the bounded campaign-table
|