@sellable/install 0.1.325 → 0.1.327
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 +5 -41
- package/agents/registry.json +2 -2
- package/bin/sellable-install.mjs +97 -321
- package/lib/runtime-verify.mjs +31 -2
- package/package.json +2 -3
- package/skill-templates/create-campaign.md +3 -3
- package/skill-templates/create-evergreen-campaigns.md +16 -16
- package/skill-templates/find-leads.md +57 -0
- package/skill-templates/refill-sends-v2.md +6 -6
- package/skill-templates/refill-sends.md +353 -91
- package/HERMES-SLACK-PROFILE-SCOPING.md +0 -169
- package/lib/codex-plugin-selection.mjs +0 -353
package/bin/sellable-install.mjs
CHANGED
|
@@ -22,14 +22,6 @@ import {
|
|
|
22
22
|
REQUIRED_SELLABLE_MCP_TOOLS,
|
|
23
23
|
verifySellableMcpRuntime,
|
|
24
24
|
} from "../lib/runtime-verify.mjs";
|
|
25
|
-
import {
|
|
26
|
-
activateCodexCandidateSelection,
|
|
27
|
-
installCodexCandidateCache,
|
|
28
|
-
readCodexPluginSelection,
|
|
29
|
-
resolveCodexPluginSelectionPaths,
|
|
30
|
-
restoreCodexPluginSelection,
|
|
31
|
-
snapshotCodexPluginSelection,
|
|
32
|
-
} from "../lib/codex-plugin-selection.mjs";
|
|
33
25
|
|
|
34
26
|
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
35
27
|
const DEFAULT_SERVER_PACKAGE =
|
|
@@ -48,7 +40,7 @@ function getInstallVersion() {
|
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
const CODEX_PLUGIN_VERSION = "0.1.
|
|
43
|
+
const CODEX_PLUGIN_VERSION = "0.1.54";
|
|
52
44
|
const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
53
45
|
"0.1.8",
|
|
54
46
|
"0.1.9",
|
|
@@ -87,8 +79,6 @@ const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
|
87
79
|
"0.1.51",
|
|
88
80
|
"0.1.52",
|
|
89
81
|
"0.1.53",
|
|
90
|
-
"0.1.54",
|
|
91
|
-
"0.1.55",
|
|
92
82
|
];
|
|
93
83
|
const RAW_INSTALL_PACKAGE_SPEC =
|
|
94
84
|
process.env.SELLABLE_INSTALL_PACKAGE_SPEC || "@sellable/install@latest";
|
|
@@ -180,8 +170,6 @@ Commands:
|
|
|
180
170
|
Save the ask-first prompt-sharing preference.
|
|
181
171
|
setup claude-permissions --allow-common-setup
|
|
182
172
|
Add common Sellable Bash permissions to Claude settings.
|
|
183
|
-
codex plugin-selection snapshot|install-cache|activate|restore|status
|
|
184
|
-
Approval-gated Phase 98 exact-cache selection controls.
|
|
185
173
|
hermes profile bootstrap Create a profile-local Hermes config, Sellable auth config,
|
|
186
174
|
configs dir, skills, and optional Slack .env.
|
|
187
175
|
uninstall Remove Sellable host config and installed artifacts.
|
|
@@ -191,8 +179,6 @@ Options:
|
|
|
191
179
|
--server <mode> package, local, or hosted. Default: package
|
|
192
180
|
--token <token> Sellable API token. Also reads SELLABLE_TOKEN.
|
|
193
181
|
--workspace-id <id> Sellable workspace id. Also reads SELLABLE_WORKSPACE_ID.
|
|
194
|
-
--require-workspace-lock For Hermes customer profiles, fail closed unless
|
|
195
|
-
SELLABLE_LOCK_WORKSPACE_ID is present in MCP env.
|
|
196
182
|
--api-url <url> Sellable API URL. Default: ${DEFAULT_API_URL}
|
|
197
183
|
--sellable-config-path <path>
|
|
198
184
|
Profile-scoped Sellable config path. Also reads SELLABLE_CONFIG_PATH.
|
|
@@ -213,6 +199,7 @@ Options:
|
|
|
213
199
|
Auth:
|
|
214
200
|
Install is auth-free by default. Sign in happens on the first run of
|
|
215
201
|
/sellable:create-campaign, /sellable:create-evergreen-campaigns,
|
|
202
|
+
/sellable:find-leads,
|
|
216
203
|
/sellable:foundation, /sellable:content, /sellable:create-post,
|
|
217
204
|
/sellable:refresh-sender-engagement, or /sellable:refill-sends in Claude Code,
|
|
218
205
|
Codex, or Hermes,
|
|
@@ -231,20 +218,9 @@ Hermes profile bootstrap:
|
|
|
231
218
|
|
|
232
219
|
Bootstrap writes <profileRoot>/config.yaml, <profileRoot>/sellable/config.json,
|
|
233
220
|
<profileRoot>/sellable/configs, Sellable Hermes skills, and optional
|
|
234
|
-
<profileRoot>/.env Slack token
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
customer-scoped Sellable credentials and customer-scoped Slack credentials for
|
|
238
|
-
customer profiles; shared admin tokens are weaker isolation and should remain
|
|
239
|
-
internal-only. Bootstrap writes SELLABLE_REQUIRE_WORKSPACE_LOCK=1 whenever a
|
|
240
|
-
workspace id is supplied, so a customer profile fails closed if the workspace
|
|
241
|
-
lock is later removed or not yet provisioned.
|
|
242
|
-
|
|
243
|
-
Hermes shared Slack dispatcher:
|
|
244
|
-
Shared listener setup and route-registry validation are internal Sellable
|
|
245
|
-
Admin responsibilities. This customer-facing installer only bootstraps a
|
|
246
|
-
profile-local Sellable runtime after Sellable Admin provisions the profile
|
|
247
|
-
and dispatcher route.
|
|
221
|
+
<profileRoot>/.env Slack token keys. Use customer-scoped Sellable credentials
|
|
222
|
+
for customer profiles; a shared admin token is weaker isolation and should
|
|
223
|
+
remain internal-only.
|
|
248
224
|
`;
|
|
249
225
|
}
|
|
250
226
|
|
|
@@ -1119,6 +1095,35 @@ const CREATE_CAMPAIGN_ALLOWED_TOOLS = [
|
|
|
1119
1095
|
"mcp__sellable__start_campaign",
|
|
1120
1096
|
];
|
|
1121
1097
|
|
|
1098
|
+
const FIND_LEADS_ALLOWED_TOOLS = [
|
|
1099
|
+
"mcp__sellable__get_auth_status",
|
|
1100
|
+
"mcp__sellable__start_cli_login",
|
|
1101
|
+
"mcp__sellable__wait_for_cli_login",
|
|
1102
|
+
"mcp__sellable__get_active_workspace",
|
|
1103
|
+
"mcp__sellable__list_workspaces",
|
|
1104
|
+
"mcp__sellable__set_active_workspace",
|
|
1105
|
+
"mcp__sellable__get_subskill_prompt",
|
|
1106
|
+
"mcp__sellable__get_subskill_asset",
|
|
1107
|
+
"mcp__sellable__search_subskill_prompts",
|
|
1108
|
+
"mcp__sellable__get_provider_prompt",
|
|
1109
|
+
"mcp__sellable__bootstrap_find_leads",
|
|
1110
|
+
"mcp__sellable__get_find_leads_run",
|
|
1111
|
+
"mcp__sellable__update_find_leads_run",
|
|
1112
|
+
"mcp__sellable__wait_for_find_leads_run",
|
|
1113
|
+
"mcp__sellable__cancel_find_leads",
|
|
1114
|
+
"mcp__sellable__reissue_find_leads_watch_link",
|
|
1115
|
+
"mcp__sellable__preflight_find_leads_provider",
|
|
1116
|
+
"mcp__sellable__lookup_sales_nav_filter",
|
|
1117
|
+
"mcp__sellable__search_sales_nav",
|
|
1118
|
+
"mcp__sellable__search_prospeo",
|
|
1119
|
+
"mcp__sellable__search_signals",
|
|
1120
|
+
"mcp__sellable__select_promising_posts",
|
|
1121
|
+
"mcp__sellable__import_leads",
|
|
1122
|
+
"mcp__sellable__wait_for_lead_list_ready",
|
|
1123
|
+
"mcp__sellable__get_rows_minimal",
|
|
1124
|
+
"mcp__sellable__export_table_csv",
|
|
1125
|
+
];
|
|
1126
|
+
|
|
1122
1127
|
const CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS = [
|
|
1123
1128
|
...CREATE_CAMPAIGN_ALLOWED_TOOLS,
|
|
1124
1129
|
"mcp__sellable__setup_evergreen_campaigns",
|
|
@@ -1672,7 +1677,7 @@ Treat host capabilities as concrete functions, not prose conventions:
|
|
|
1672
1677
|
import and before dispatching Message Drafting only.
|
|
1673
1678
|
- \`launch_message_drafting\`: Claude Code uses \`Task\` with \`subagent_type\`
|
|
1674
1679
|
\`post-find-leads-message-scout\` when listed; Codex uses the returned
|
|
1675
|
-
compatibility agent or a generic \`gpt-5.
|
|
1680
|
+
compatibility agent or a generic \`gpt-5.5\` / \`xhigh\` Message Drafting agent.
|
|
1676
1681
|
|
|
1677
1682
|
If a required interactive question function or MCP loader is missing, stop and
|
|
1678
1683
|
explain the Sellable install/reload problem. Source and filter work use
|
|
@@ -1934,6 +1939,17 @@ then retry \`get_subskill_prompt\`.
|
|
|
1934
1939
|
`, host, "create-campaign");
|
|
1935
1940
|
}
|
|
1936
1941
|
|
|
1942
|
+
function findLeadsSkillMd(host = "shared") {
|
|
1943
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1944
|
+
const templatePath = join(here, "..", "skill-templates", "find-leads.md");
|
|
1945
|
+
if (!existsSync(templatePath)) {
|
|
1946
|
+
throw new Error(
|
|
1947
|
+
"Find Leads skill template is missing or stale. Reinstall @sellable/install and retry."
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
return stampInstalledHost(readFileSync(templatePath, "utf8"), host, "find-leads");
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1937
1953
|
function createAbTestSkillMd(host = "shared") {
|
|
1938
1954
|
try {
|
|
1939
1955
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2050,20 +2066,6 @@ then retry \`get_subskill_prompt\`.
|
|
|
2050
2066
|
`, host, "create-evergreen-campaigns");
|
|
2051
2067
|
}
|
|
2052
2068
|
|
|
2053
|
-
const REFILL_SENDS_FALLBACK_CONTRACT = `<!-- REFILL_CONTRACT_GENERATED:START -->
|
|
2054
|
-
contractVersion: 2.0.0
|
|
2055
|
-
cacheVersion: refill-contract-v1
|
|
2056
|
-
contractHash: e54ba0f80bc534fd70fd8c10c1caab109fe9a91c205cfc423f5f6735cd53fdc1
|
|
2057
|
-
scope: workspace_id, target_date, sender_selectors, campaign_selectors, approval_mode, contract_identity, installed_identity
|
|
2058
|
-
gates: approval_packet, one_primitive, fresh_reread, normalized_accounting, prep_circuit
|
|
2059
|
-
forbidden: direct_send, raw_scheduler_write, campaign_create, campaign_archive_delete, threshold_lowering, unselected_source_mutation, unapproved_campaign_start, sender_config_write
|
|
2060
|
-
terminal: projected_full, concrete_blocker, deadline_reached, iteration_limit, operator_stopped
|
|
2061
|
-
ready_buffer_exists_is_not_complete
|
|
2062
|
-
mcpPackageVersion: installed MCP package spec in host MCP config
|
|
2063
|
-
installPackageVersion: ${getInstallVersion()}
|
|
2064
|
-
codexPluginVersion: ${CODEX_PLUGIN_VERSION}
|
|
2065
|
-
<!-- REFILL_CONTRACT_GENERATED:END -->`;
|
|
2066
|
-
|
|
2067
2069
|
function refillSendsSkillMd(host = "shared") {
|
|
2068
2070
|
try {
|
|
2069
2071
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2073,10 +2075,7 @@ function refillSendsSkillMd(host = "shared") {
|
|
|
2073
2075
|
"skill-templates",
|
|
2074
2076
|
"refill-sends.md"
|
|
2075
2077
|
);
|
|
2076
|
-
if (
|
|
2077
|
-
process.env.SELLABLE_FORCE_REFILL_TEMPLATE_MISSING !== "1" &&
|
|
2078
|
-
existsSync(templatePath)
|
|
2079
|
-
) {
|
|
2078
|
+
if (existsSync(templatePath)) {
|
|
2080
2079
|
return stampInstalledHost(
|
|
2081
2080
|
addOrReplaceAllowedTools(
|
|
2082
2081
|
readFileSync(templatePath, "utf8"),
|
|
@@ -2099,14 +2098,6 @@ ${allowedToolsYaml(REFILL_SENDS_ALLOWED_TOOLS)}
|
|
|
2099
2098
|
|
|
2100
2099
|
# Sellable Refill Sends
|
|
2101
2100
|
|
|
2102
|
-
${REFILL_SENDS_FALLBACK_CONTRACT}
|
|
2103
|
-
|
|
2104
|
-
fallbackSource: embedded
|
|
2105
|
-
|
|
2106
|
-
Load \`refill-sends-workflow\`, then \`core/flow.v1.json\`, then
|
|
2107
|
-
\`core/contract.v2.json\`, each through the Sellable MCP prompt tools until
|
|
2108
|
-
\`hasMore:false\`. Fail closed on unsupported major or stale contract hash.
|
|
2109
|
-
|
|
2110
2101
|
Use this as the customer-facing entrypoint for the Sellable \`refill-sends\`
|
|
2111
2102
|
workflow. It supports \`--yolo\` and optional repeated \`--sender <name-or-id>\`
|
|
2112
2103
|
selectors. It also supports \`--target-date YYYY-MM-DD\` to fill one exact
|
|
@@ -2136,9 +2127,9 @@ Desktop, then start a new thread.
|
|
|
2136
2127
|
|
|
2137
2128
|
1. Call \`mcp__sellable__get_auth_status({})\`.
|
|
2138
2129
|
2. Resolve the target workspace id from the user request, automation config, or install-time workspace mapping. Pass \`workspaceId\` on every scheduled or \`--yolo\` refill tool call. Missing \`workspaceId\` in scheduled or \`--yolo\` mode is a blocker; return \`WORKSPACE_REQUIRED\` instead of running against shared config state. Do not solve automation workspace uncertainty by changing the shared active workspace.
|
|
2139
|
-
3. Normalize invocation arguments with \`mcp__sellable__refill_sends({ yolo, executionMode, requireWorkspace, workspaceId, senders, senderIds, senderNames,
|
|
2130
|
+
3. Normalize invocation arguments with \`mcp__sellable__refill_sends({ yolo, executionMode, requireWorkspace, workspaceId, senders, senderIds, senderNames, targetDate, untilDate })\` when the host exposes typed MCP tools. Type shape: \`refill_sends({ yolo?: boolean, executionMode?: "manual" | "scheduled" | "yolo", requireWorkspace?: boolean, workspaceId?: string, senders?: string[], senderIds?: string[], senderNames?: string[], horizonSendDays?: number, untilDate?: "YYYY-MM-DD", targetDate?: "YYYY-MM-DD" })\`.
|
|
2140
2131
|
4. Clover scheduled automation example: \`mcp__sellable__refill_sends({ yolo:false, executionMode:"scheduled", requireWorkspace:true, workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })\`, then \`mcp__sellable__get_refill_target_plan({ intent:"plain", approvalMode:"mark_ready", workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })\`.
|
|
2141
|
-
5. Call \`mcp__sellable__get_refill_target_plan({ workspaceId, senders, senderIds, senderNames,
|
|
2132
|
+
5. Call \`mcp__sellable__get_refill_target_plan({ workspaceId, senders, senderIds, senderNames, targetDate, untilDate })\` before any mutation. Render \`target.eligibleSenderLedger\`, \`target.senderRefillPlans[]\`, and \`target.globalActionQueue\` as the structured packet. Preserve the labels \`Need to prepare\`, \`Goal\`, \`Already sent\`, \`Scheduled\`, \`Ready and waiting to be scheduled\`, and \`Still need\`. In \`--yolo\`, \`mcp__sellable__refill_sends\` maintains a run-local \`refreshedPaidInmailSenderIds\` set, refreshes stale/missing paid InMail facts for each selected paid-InMail sender at most once, reruns \`get_refill_target_plan\`, and returns \`autoPaidInmailRefresh\` plus the post-refresh \`targetPlan\` before choosing the next prep/source-copy/bounded-approval/read-only wait action. Refill ladder: approve generated rows only through the explicit bounded approval gate, process existing same-campaign unenriched/unprepared rows before source work, then same-source copy, then provider-aligned source-more. New source/provider switches change reply-rate baseline and are manual alternates, not \`--yolo\` side effects. Do not present paid-credit refresh as the next operator action after \`refill_sends\` returns that post-refresh plan. For exact-date proof, \`mcp__sellable__get_scheduler_fill_capacity\` is read-only and tells how many cells the scheduler will try to place for a sender/action/date. When ready rows cover the target but scheduled rows do not, \`mcp__sellable__run_scheduler_sweep({ workspaceId, action:"run" })\` may request immediate placement within existing gates and returns a receipt, but it never sends or bypasses limits. If the returned target is already complete, stop with the no-op receipt. If ready rows cover the target but scheduled rows do not, keep the run open in the read-only scheduler wait/re-read loop until projected coverage fills, a concrete non-scheduler blocker appears, or Christian explicitly stops/statuses the run.
|
|
2142
2133
|
6. Load the canonical prompt and deterministic flow asset via
|
|
2143
2134
|
\`mcp__sellable__get_subskill_prompt({ subskillName: "refill-sends" })\`,
|
|
2144
2135
|
then load \`mcp__sellable__get_subskill_prompt({ subskillName: "refill-sends-workflow" })\`,
|
|
@@ -2149,7 +2140,8 @@ Desktop, then start a new thread.
|
|
|
2149
2140
|
facts are present, refresh exact selected sender facts once, rerun
|
|
2150
2141
|
\`get_refill_target_plan\`, and only then enter \`wait_for_scheduler\` if
|
|
2151
2142
|
freshness is clean.
|
|
2152
|
-
7. Follow the canonical prompt exactly. Do not
|
|
2143
|
+
7. Follow the canonical prompt exactly. Do not launch, start, archive, delete,
|
|
2144
|
+
or write scheduler rows as part of refill sends.
|
|
2153
2145
|
|
|
2154
2146
|
## MCP Prompt Fallback
|
|
2155
2147
|
|
|
@@ -2670,6 +2662,12 @@ will change or after they approve the settings update.
|
|
|
2670
2662
|
|
|
2671
2663
|
function codexPluginSkills() {
|
|
2672
2664
|
return [
|
|
2665
|
+
{
|
|
2666
|
+
dir: "sellable-find-leads",
|
|
2667
|
+
displayName: "Sellable Find Leads",
|
|
2668
|
+
description: "Find prospects and build a live exportable lead list",
|
|
2669
|
+
skillMd: findLeadsSkillMd("codex"),
|
|
2670
|
+
},
|
|
2673
2671
|
{
|
|
2674
2672
|
dir: "sellable-create-campaign",
|
|
2675
2673
|
displayName: "Sellable Create Campaign",
|
|
@@ -2910,6 +2908,14 @@ then retry \`get_subskill_prompt\`.
|
|
|
2910
2908
|
|
|
2911
2909
|
function hermesSkillDefinitions() {
|
|
2912
2910
|
return [
|
|
2911
|
+
{
|
|
2912
|
+
commandName: "find-leads",
|
|
2913
|
+
dir: hermesCommandName("find-leads"),
|
|
2914
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2915
|
+
findLeadsSkillMd("hermes"),
|
|
2916
|
+
"find-leads"
|
|
2917
|
+
),
|
|
2918
|
+
},
|
|
2913
2919
|
{
|
|
2914
2920
|
commandName: "create-campaign",
|
|
2915
2921
|
dir: hermesCommandName("create-campaign"),
|
|
@@ -3093,8 +3099,8 @@ function generateCodexAgentToml(agent) {
|
|
|
3093
3099
|
const codex = agent.codex;
|
|
3094
3100
|
return `name = ${quoteToml(agent.name)}
|
|
3095
3101
|
description = ${quoteToml(codex.description)}
|
|
3096
|
-
model = ${quoteToml(codex.model || "gpt-5.
|
|
3097
|
-
model_reasoning_effort = ${quoteToml(codex.modelReasoningEffort || "
|
|
3102
|
+
model = ${quoteToml(codex.model || "gpt-5.5")}
|
|
3103
|
+
model_reasoning_effort = ${quoteToml(codex.modelReasoningEffort || "xhigh")}
|
|
3098
3104
|
sandbox_mode = ${quoteToml(codex.sandboxMode || "read-only")}
|
|
3099
3105
|
nickname_candidates = ${tomlArray(codex.nicknameCandidates || [agent.displayName])}
|
|
3100
3106
|
developer_instructions = ${tomlMultilineString(agent.prompt)}
|
|
@@ -3160,6 +3166,15 @@ ${stripFrontmatter(command.skillMd)}
|
|
|
3160
3166
|
|
|
3161
3167
|
function claudeCommands() {
|
|
3162
3168
|
return [
|
|
3169
|
+
{
|
|
3170
|
+
name: "find-leads",
|
|
3171
|
+
title: "Find Leads",
|
|
3172
|
+
filename: join("sellable", "find-leads.md"),
|
|
3173
|
+
description: "Find prospects and build a live exportable Sellable lead list.",
|
|
3174
|
+
argumentHint: "[target audience, provider, or lead-list goal]",
|
|
3175
|
+
skillMd: findLeadsSkillMd("claude"),
|
|
3176
|
+
allowedTools: ["AskUserQuestion", ...FIND_LEADS_ALLOWED_TOOLS],
|
|
3177
|
+
},
|
|
3163
3178
|
{
|
|
3164
3179
|
name: "create-campaign",
|
|
3165
3180
|
title: "Create Campaign",
|
|
@@ -3554,12 +3569,6 @@ function mcpEnvForHost(host, opts) {
|
|
|
3554
3569
|
if (opts.sellableConfigsDir) {
|
|
3555
3570
|
env.SELLABLE_CONFIGS_DIR = opts.sellableConfigsDir;
|
|
3556
3571
|
}
|
|
3557
|
-
if (opts.lockWorkspaceId) {
|
|
3558
|
-
env.SELLABLE_LOCK_WORKSPACE_ID = opts.lockWorkspaceId;
|
|
3559
|
-
}
|
|
3560
|
-
if (opts.requireWorkspaceLock) {
|
|
3561
|
-
env.SELLABLE_REQUIRE_WORKSPACE_LOCK = "1";
|
|
3562
|
-
}
|
|
3563
3572
|
return env;
|
|
3564
3573
|
}
|
|
3565
3574
|
|
|
@@ -3844,12 +3853,6 @@ function parseHermesProfileBootstrapArgs(argv) {
|
|
|
3844
3853
|
apiUrl: process.env.SELLABLE_API_URL || DEFAULT_API_URL,
|
|
3845
3854
|
slackBotToken: process.env.SLACK_BOT_TOKEN || "",
|
|
3846
3855
|
slackAppToken: process.env.SLACK_APP_TOKEN || "",
|
|
3847
|
-
slackHomeChannel: process.env.SLACK_HOME_CHANNEL || "",
|
|
3848
|
-
slackHomeChannelName: process.env.SLACK_HOME_CHANNEL_NAME || "",
|
|
3849
|
-
slackAllowedChannels: process.env.SLACK_ALLOWED_CHANNELS || "",
|
|
3850
|
-
slackFreeResponseChannels: process.env.SLACK_FREE_RESPONSE_CHANNELS || "",
|
|
3851
|
-
slackAllowedUsers: process.env.SLACK_ALLOWED_USERS || "",
|
|
3852
|
-
slackRequireMention: process.env.SLACK_REQUIRE_MENTION || "",
|
|
3853
3856
|
server: process.env.SELLABLE_INSTALL_SERVER || "package",
|
|
3854
3857
|
mcpPackage: DEFAULT_SERVER_PACKAGE,
|
|
3855
3858
|
localCommand: process.env.SELLABLE_MCP_LOCAL_COMMAND || "",
|
|
@@ -3858,7 +3861,6 @@ function parseHermesProfileBootstrapArgs(argv) {
|
|
|
3858
3861
|
force: false,
|
|
3859
3862
|
overwriteEnv: false,
|
|
3860
3863
|
allowDuplicateSlackTokens: false,
|
|
3861
|
-
requireWorkspaceLock: false,
|
|
3862
3864
|
verbose: false,
|
|
3863
3865
|
};
|
|
3864
3866
|
|
|
@@ -3883,12 +3885,6 @@ function parseHermesProfileBootstrapArgs(argv) {
|
|
|
3883
3885
|
else if (arg === "--api-url") opts.apiUrl = next();
|
|
3884
3886
|
else if (arg === "--slack-bot-token") opts.slackBotToken = next();
|
|
3885
3887
|
else if (arg === "--slack-app-token") opts.slackAppToken = next();
|
|
3886
|
-
else if (arg === "--slack-home-channel") opts.slackHomeChannel = next();
|
|
3887
|
-
else if (arg === "--slack-home-channel-name") opts.slackHomeChannelName = next();
|
|
3888
|
-
else if (arg === "--slack-allowed-channels") opts.slackAllowedChannels = next();
|
|
3889
|
-
else if (arg === "--slack-free-response-channels") opts.slackFreeResponseChannels = next();
|
|
3890
|
-
else if (arg === "--slack-allowed-users") opts.slackAllowedUsers = next();
|
|
3891
|
-
else if (arg === "--slack-require-mention") opts.slackRequireMention = next();
|
|
3892
3888
|
else if (arg === "--server") opts.server = next();
|
|
3893
3889
|
else if (arg === "--mcp-package") {
|
|
3894
3890
|
opts.mcpPackage = normalizeWindowsPackageSpec(next());
|
|
@@ -3897,9 +3893,7 @@ function parseHermesProfileBootstrapArgs(argv) {
|
|
|
3897
3893
|
else if (arg === "--json") opts.json = true;
|
|
3898
3894
|
else if (arg === "--force") opts.force = true;
|
|
3899
3895
|
else if (arg === "--overwrite-env") opts.overwriteEnv = true;
|
|
3900
|
-
else if (arg === "--
|
|
3901
|
-
opts.requireWorkspaceLock = true;
|
|
3902
|
-
} else if (arg === "--allow-duplicate-slack-tokens") {
|
|
3896
|
+
else if (arg === "--allow-duplicate-slack-tokens") {
|
|
3903
3897
|
opts.allowDuplicateSlackTokens = true;
|
|
3904
3898
|
} else if (arg === "--verbose") opts.verbose = true;
|
|
3905
3899
|
else {
|
|
@@ -3992,111 +3986,6 @@ function renderEnvFile({ entries, passthrough }) {
|
|
|
3992
3986
|
return `${lines.join("\n")}${lines.length ? "\n" : ""}`;
|
|
3993
3987
|
}
|
|
3994
3988
|
|
|
3995
|
-
function normalizeSlackChannelIds(value, key) {
|
|
3996
|
-
const raw = String(value || "").trim();
|
|
3997
|
-
if (!raw) return "";
|
|
3998
|
-
const ids = raw
|
|
3999
|
-
.split(",")
|
|
4000
|
-
.map((item) => item.trim())
|
|
4001
|
-
.filter(Boolean);
|
|
4002
|
-
if (ids.length === 0) return "";
|
|
4003
|
-
|
|
4004
|
-
for (const id of ids) {
|
|
4005
|
-
if (
|
|
4006
|
-
id === "*" ||
|
|
4007
|
-
/^all$/i.test(id) ||
|
|
4008
|
-
id.startsWith("#") ||
|
|
4009
|
-
/\s/.test(id) ||
|
|
4010
|
-
!/^[A-Z][A-Z0-9]{8,}$/.test(id)
|
|
4011
|
-
) {
|
|
4012
|
-
throw new Error(
|
|
4013
|
-
`${key} must be comma-separated Slack channel IDs, not names, wildcards, or free text. Bad value: ${id}`
|
|
4014
|
-
);
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
return [...new Set(ids)].join(",");
|
|
4018
|
-
}
|
|
4019
|
-
|
|
4020
|
-
function normalizeSlackSingleChannelId(value, key) {
|
|
4021
|
-
const normalized = normalizeSlackChannelIds(value, key);
|
|
4022
|
-
if (normalized.includes(",")) {
|
|
4023
|
-
throw new Error(`${key} must be a single Slack channel ID`);
|
|
4024
|
-
}
|
|
4025
|
-
return normalized;
|
|
4026
|
-
}
|
|
4027
|
-
|
|
4028
|
-
function normalizeSlackUserIds(value, key) {
|
|
4029
|
-
const raw = String(value || "").trim();
|
|
4030
|
-
if (!raw) return "";
|
|
4031
|
-
const ids = raw
|
|
4032
|
-
.split(",")
|
|
4033
|
-
.map((item) => item.trim())
|
|
4034
|
-
.filter(Boolean);
|
|
4035
|
-
if (ids.length === 0) return "";
|
|
4036
|
-
|
|
4037
|
-
for (const id of ids) {
|
|
4038
|
-
if (!/^[UW][A-Z0-9]{8,}$/.test(id)) {
|
|
4039
|
-
throw new Error(`${key} must be comma-separated Slack user IDs. Bad value: ${id}`);
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
return [...new Set(ids)].join(",");
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
function normalizeSlackChannelName(value, key) {
|
|
4046
|
-
const raw = String(value || "").trim();
|
|
4047
|
-
if (!raw) return "";
|
|
4048
|
-
if (raw.startsWith("#") || /[\s,]/.test(raw)) {
|
|
4049
|
-
throw new Error(`${key} must be a Slack channel name without #, spaces, or commas`);
|
|
4050
|
-
}
|
|
4051
|
-
return raw;
|
|
4052
|
-
}
|
|
4053
|
-
|
|
4054
|
-
function normalizeSlackBoolean(value, key) {
|
|
4055
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
4056
|
-
if (!raw) return "";
|
|
4057
|
-
if (raw !== "true" && raw !== "false") {
|
|
4058
|
-
throw new Error(`${key} must be true or false`);
|
|
4059
|
-
}
|
|
4060
|
-
return raw;
|
|
4061
|
-
}
|
|
4062
|
-
|
|
4063
|
-
function buildSlackProfileEnv(parsed) {
|
|
4064
|
-
const homeChannel = normalizeSlackSingleChannelId(
|
|
4065
|
-
parsed.slackHomeChannel,
|
|
4066
|
-
"SLACK_HOME_CHANNEL"
|
|
4067
|
-
);
|
|
4068
|
-
const explicitAllowedChannels = normalizeSlackChannelIds(
|
|
4069
|
-
parsed.slackAllowedChannels,
|
|
4070
|
-
"SLACK_ALLOWED_CHANNELS"
|
|
4071
|
-
);
|
|
4072
|
-
const allowedChannels = explicitAllowedChannels || homeChannel;
|
|
4073
|
-
|
|
4074
|
-
return {
|
|
4075
|
-
SLACK_BOT_TOKEN: parsed.slackBotToken.trim(),
|
|
4076
|
-
SLACK_APP_TOKEN: parsed.slackAppToken.trim(),
|
|
4077
|
-
SLACK_HOME_CHANNEL: homeChannel,
|
|
4078
|
-
SLACK_HOME_CHANNEL_NAME: normalizeSlackChannelName(
|
|
4079
|
-
parsed.slackHomeChannelName,
|
|
4080
|
-
"SLACK_HOME_CHANNEL_NAME"
|
|
4081
|
-
),
|
|
4082
|
-
// Customer Hermes profiles must fail closed to their channel. If the caller
|
|
4083
|
-
// supplies only a home channel, we treat that as the allowlist too.
|
|
4084
|
-
SLACK_ALLOWED_CHANNELS: allowedChannels,
|
|
4085
|
-
SLACK_FREE_RESPONSE_CHANNELS: normalizeSlackChannelIds(
|
|
4086
|
-
parsed.slackFreeResponseChannels,
|
|
4087
|
-
"SLACK_FREE_RESPONSE_CHANNELS"
|
|
4088
|
-
),
|
|
4089
|
-
SLACK_ALLOWED_USERS: normalizeSlackUserIds(
|
|
4090
|
-
parsed.slackAllowedUsers,
|
|
4091
|
-
"SLACK_ALLOWED_USERS"
|
|
4092
|
-
),
|
|
4093
|
-
SLACK_REQUIRE_MENTION: normalizeSlackBoolean(
|
|
4094
|
-
parsed.slackRequireMention,
|
|
4095
|
-
"SLACK_REQUIRE_MENTION"
|
|
4096
|
-
),
|
|
4097
|
-
};
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
3989
|
function findDuplicateSlackTokenProfiles(profileRoot, slackTokens) {
|
|
4101
3990
|
const duplicates = [];
|
|
4102
3991
|
const profilesRoot = dirname(profileRoot);
|
|
@@ -4149,16 +4038,10 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4149
4038
|
const sellableConfigsDir = join(profileRoot, "sellable", "configs");
|
|
4150
4039
|
const envPath = join(profileRoot, ".env");
|
|
4151
4040
|
const { token, source: tokenSource } = readBootstrapToken(parsed);
|
|
4152
|
-
const slackEnv = buildSlackProfileEnv(parsed);
|
|
4153
4041
|
const slackTokens = {
|
|
4154
|
-
SLACK_BOT_TOKEN:
|
|
4155
|
-
SLACK_APP_TOKEN:
|
|
4042
|
+
SLACK_BOT_TOKEN: parsed.slackBotToken.trim(),
|
|
4043
|
+
SLACK_APP_TOKEN: parsed.slackAppToken.trim(),
|
|
4156
4044
|
};
|
|
4157
|
-
const slackEnvKeys = Object.entries(slackEnv)
|
|
4158
|
-
.filter(([, value]) => value)
|
|
4159
|
-
.map(([key]) => key);
|
|
4160
|
-
const requireWorkspaceLock =
|
|
4161
|
-
parsed.requireWorkspaceLock || Boolean(parsed.workspaceId);
|
|
4162
4045
|
const opts = {
|
|
4163
4046
|
...parsed,
|
|
4164
4047
|
host: "hermes",
|
|
@@ -4168,8 +4051,6 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4168
4051
|
token,
|
|
4169
4052
|
tokenSource,
|
|
4170
4053
|
workspaceId: parsed.workspaceId,
|
|
4171
|
-
lockWorkspaceId: parsed.workspaceId,
|
|
4172
|
-
requireWorkspaceLock,
|
|
4173
4054
|
apiUrl: parsed.apiUrl,
|
|
4174
4055
|
};
|
|
4175
4056
|
|
|
@@ -4194,17 +4075,6 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4194
4075
|
apiUrl: opts.apiUrl,
|
|
4195
4076
|
activeWorkspaceId: parsed.workspaceId || null,
|
|
4196
4077
|
activeWorkspaceName: parsed.workspaceName || null,
|
|
4197
|
-
workspaceLock: parsed.workspaceId
|
|
4198
|
-
? {
|
|
4199
|
-
enabled: true,
|
|
4200
|
-
workspaceId: parsed.workspaceId,
|
|
4201
|
-
required: requireWorkspaceLock,
|
|
4202
|
-
}
|
|
4203
|
-
: {
|
|
4204
|
-
enabled: false,
|
|
4205
|
-
workspaceId: null,
|
|
4206
|
-
required: requireWorkspaceLock,
|
|
4207
|
-
},
|
|
4208
4078
|
tokenPresent: Boolean(token),
|
|
4209
4079
|
tokenSource,
|
|
4210
4080
|
tokenFingerprint: tokenFingerprint(token),
|
|
@@ -4213,29 +4083,11 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4213
4083
|
keys: Object.entries(slackTokens)
|
|
4214
4084
|
.filter(([, value]) => value)
|
|
4215
4085
|
.map(([key]) => key),
|
|
4216
|
-
envKeys: slackEnvKeys,
|
|
4217
4086
|
fingerprints: Object.fromEntries(
|
|
4218
4087
|
Object.entries(slackTokens)
|
|
4219
4088
|
.filter(([, value]) => value)
|
|
4220
4089
|
.map(([key, value]) => [key, tokenFingerprint(value)])
|
|
4221
4090
|
),
|
|
4222
|
-
scope: {
|
|
4223
|
-
homeChannel: slackEnv.SLACK_HOME_CHANNEL || null,
|
|
4224
|
-
homeChannelName: slackEnv.SLACK_HOME_CHANNEL_NAME || null,
|
|
4225
|
-
allowedChannels: slackEnv.SLACK_ALLOWED_CHANNELS
|
|
4226
|
-
? slackEnv.SLACK_ALLOWED_CHANNELS.split(",")
|
|
4227
|
-
: [],
|
|
4228
|
-
freeResponseChannels: slackEnv.SLACK_FREE_RESPONSE_CHANNELS
|
|
4229
|
-
? slackEnv.SLACK_FREE_RESPONSE_CHANNELS.split(",")
|
|
4230
|
-
: [],
|
|
4231
|
-
allowedUsersCount: slackEnv.SLACK_ALLOWED_USERS
|
|
4232
|
-
? slackEnv.SLACK_ALLOWED_USERS.split(",").length
|
|
4233
|
-
: 0,
|
|
4234
|
-
requireMention:
|
|
4235
|
-
slackEnv.SLACK_REQUIRE_MENTION === ""
|
|
4236
|
-
? null
|
|
4237
|
-
: slackEnv.SLACK_REQUIRE_MENTION === "true",
|
|
4238
|
-
},
|
|
4239
4091
|
},
|
|
4240
4092
|
created: [],
|
|
4241
4093
|
updated: [],
|
|
@@ -4280,7 +4132,7 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4280
4132
|
if (existsSync(envPath)) {
|
|
4281
4133
|
envState = parseEnvLines(readFileSync(envPath, "utf8"));
|
|
4282
4134
|
}
|
|
4283
|
-
for (const [key, value] of Object.entries(
|
|
4135
|
+
for (const [key, value] of Object.entries(slackTokens)) {
|
|
4284
4136
|
if (!value) continue;
|
|
4285
4137
|
const existing = envState.entries.get(key);
|
|
4286
4138
|
if (existing && existing !== value && !opts.overwriteEnv) {
|
|
@@ -4340,8 +4192,8 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4340
4192
|
mkdirSync(sellableConfigsDir, { recursive: true, mode: 0o700 });
|
|
4341
4193
|
writeHermesProfileSellableConfig(sellableConfigPath, nextAuth, opts);
|
|
4342
4194
|
installHermes(opts);
|
|
4343
|
-
if (Object.values(
|
|
4344
|
-
for (const [key, value] of Object.entries(
|
|
4195
|
+
if (Object.values(slackTokens).some(Boolean)) {
|
|
4196
|
+
for (const [key, value] of Object.entries(slackTokens)) {
|
|
4345
4197
|
if (value) envState.entries.set(key, value);
|
|
4346
4198
|
}
|
|
4347
4199
|
writeFile(envPath, renderEnvFile(envState), opts, 0o600);
|
|
@@ -4356,31 +4208,16 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4356
4208
|
hermesConfigPath(opts)
|
|
4357
4209
|
);
|
|
4358
4210
|
(skillsExisted ? summary.updated : summary.created).push(hermesSkillsRoot(opts));
|
|
4359
|
-
if (Object.values(
|
|
4211
|
+
if (Object.values(slackTokens).some(Boolean)) {
|
|
4360
4212
|
(envExisted ? summary.updated : summary.created).push(envPath);
|
|
4361
4213
|
} else {
|
|
4362
|
-
summary.skipped.push("Slack env: no Slack tokens
|
|
4363
|
-
}
|
|
4364
|
-
if (slackEnv.SLACK_HOME_CHANNEL && !parsed.slackAllowedChannels.trim()) {
|
|
4365
|
-
summary.warnings.push(
|
|
4366
|
-
"Slack scope defaulted SLACK_ALLOWED_CHANNELS to SLACK_HOME_CHANNEL for fail-closed profile listening."
|
|
4367
|
-
);
|
|
4368
|
-
}
|
|
4369
|
-
if (slackEnv.SLACK_BOT_TOKEN && !slackEnv.SLACK_APP_TOKEN) {
|
|
4370
|
-
summary.warnings.push(
|
|
4371
|
-
"Slack bot token is present without SLACK_APP_TOKEN; outbound Slack sends may work, but native Hermes Socket Mode listening still requires an xapp app-level token with connections:write."
|
|
4372
|
-
);
|
|
4214
|
+
summary.skipped.push("Slack env: no Slack tokens supplied");
|
|
4373
4215
|
}
|
|
4374
4216
|
if (!token) {
|
|
4375
4217
|
summary.warnings.push(
|
|
4376
4218
|
`Manual auth pending. Run: sellable auth set <token> --workspace-id <workspace_id> --sellable-config-path ${sellableConfigPath}`
|
|
4377
4219
|
);
|
|
4378
4220
|
}
|
|
4379
|
-
if (requireWorkspaceLock && !parsed.workspaceId) {
|
|
4380
|
-
summary.warnings.push(
|
|
4381
|
-
"Sellable MCP will fail closed until sellable-admin provisions SELLABLE_LOCK_WORKSPACE_ID for this customer profile."
|
|
4382
|
-
);
|
|
4383
|
-
}
|
|
4384
4221
|
|
|
4385
4222
|
if (opts.json) {
|
|
4386
4223
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -5383,7 +5220,8 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
5383
5220
|
console.log("");
|
|
5384
5221
|
|
|
5385
5222
|
if (hasClaude) {
|
|
5386
|
-
|
|
5223
|
+
printAgentBox("Using Claude Code?", "claude", [
|
|
5224
|
+
{ label: "Find Leads", command: "/sellable:find-leads" },
|
|
5387
5225
|
{ label: "Campaign", command: "/sellable:create-campaign" },
|
|
5388
5226
|
{ label: "Evergreen", command: "/sellable:create-evergreen-campaigns" },
|
|
5389
5227
|
{ label: "Foundation", command: "/sellable:foundation" },
|
|
@@ -5396,7 +5234,8 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
5396
5234
|
console.log("");
|
|
5397
5235
|
}
|
|
5398
5236
|
if (hasCodex) {
|
|
5399
|
-
|
|
5237
|
+
printAgentBox("Using Codex?", "codex", [
|
|
5238
|
+
{ label: "Find Leads", command: "$sellable:find-leads" },
|
|
5400
5239
|
{ label: "Campaign", command: "$sellable:create-campaign" },
|
|
5401
5240
|
{ label: "Evergreen", command: "$sellable:create-evergreen-campaigns" },
|
|
5402
5241
|
{ label: "Foundation", command: "$sellable:foundation" },
|
|
@@ -5408,7 +5247,8 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
5408
5247
|
console.log("");
|
|
5409
5248
|
}
|
|
5410
5249
|
if (hasHermes) {
|
|
5411
|
-
|
|
5250
|
+
printAgentBox("Using Hermes?", "hermes", [
|
|
5251
|
+
{ label: "Find Leads", command: "/sellable-find-leads" },
|
|
5412
5252
|
{ label: "Campaign", command: "/sellable-create-campaign" },
|
|
5413
5253
|
{ label: "A/B Test", command: "/sellable-create-ab-test" },
|
|
5414
5254
|
{ label: "Evergreen", command: "/sellable-create-evergreen-campaigns" },
|
|
@@ -5778,22 +5618,25 @@ async function main() {
|
|
|
5778
5618
|
if (workspaceId) {
|
|
5779
5619
|
console.log(` activeWorkspaceId: ${workspaceId}`);
|
|
5780
5620
|
}
|
|
5781
|
-
|
|
5782
|
-
|
|
5621
|
+
console.log(` Continue in your agent:`);
|
|
5622
|
+
console.log(` Claude Code: /sellable:find-leads`);
|
|
5623
|
+
console.log(` Claude Code: /sellable:create-campaign`);
|
|
5783
5624
|
console.log(` Claude Code: /sellable:create-evergreen-campaigns`);
|
|
5784
5625
|
console.log(` Claude Code: /sellable:foundation`);
|
|
5785
5626
|
console.log(` Claude Code: /sellable:content`);
|
|
5786
5627
|
console.log(` Claude Code: /sellable:create-post`);
|
|
5787
5628
|
console.log(` Claude Code: /sellable:refresh-sender-engagement`);
|
|
5788
5629
|
console.log(` Claude Code: /sellable:refill-sends`);
|
|
5789
|
-
|
|
5630
|
+
console.log(` Codex: $sellable:find-leads`);
|
|
5631
|
+
console.log(` Codex: $sellable:create-campaign`);
|
|
5790
5632
|
console.log(` Codex: $sellable:create-evergreen-campaigns`);
|
|
5791
5633
|
console.log(` Codex: $sellable:foundation`);
|
|
5792
5634
|
console.log(` Codex: $sellable:content`);
|
|
5793
5635
|
console.log(` Codex: $sellable:create-post`);
|
|
5794
5636
|
console.log(` Codex: $sellable:refresh-sender-engagement`);
|
|
5795
5637
|
console.log(` Codex: $sellable:refill-sends`);
|
|
5796
|
-
|
|
5638
|
+
console.log(` Hermes: /sellable-find-leads`);
|
|
5639
|
+
console.log(` Hermes: /sellable-create-campaign`);
|
|
5797
5640
|
console.log(` Hermes: /sellable-create-evergreen-campaigns`);
|
|
5798
5641
|
console.log(` Hermes: /sellable-foundation`);
|
|
5799
5642
|
console.log(` Hermes: /sellable-content`);
|
|
@@ -5850,73 +5693,6 @@ async function main() {
|
|
|
5850
5693
|
printCreateCommandHint();
|
|
5851
5694
|
process.exit(0);
|
|
5852
5695
|
}
|
|
5853
|
-
if (rawArgs[0] === "codex" && rawArgs[1] === "plugin-selection") {
|
|
5854
|
-
const command = rawArgs[2];
|
|
5855
|
-
const flags = new Map();
|
|
5856
|
-
for (let index = 3; index < rawArgs.length; index += 2) {
|
|
5857
|
-
const flag = rawArgs[index];
|
|
5858
|
-
const value = rawArgs[index + 1];
|
|
5859
|
-
if (!flag?.startsWith("--") || !value || value.startsWith("--")) {
|
|
5860
|
-
throw new Error(`Invalid Codex plugin-selection option: ${flag ?? "<missing>"}`);
|
|
5861
|
-
}
|
|
5862
|
-
flags.set(flag, value);
|
|
5863
|
-
}
|
|
5864
|
-
const required = (name) => {
|
|
5865
|
-
const value = flags.get(name);
|
|
5866
|
-
if (!value) throw new Error(`Missing value for ${name}`);
|
|
5867
|
-
return value;
|
|
5868
|
-
};
|
|
5869
|
-
const paths = resolveCodexPluginSelectionPaths({ home: homedir() });
|
|
5870
|
-
let result;
|
|
5871
|
-
if (command === "status") {
|
|
5872
|
-
result = readCodexPluginSelection({ paths, stableVersion: flags.get("--stable-version") });
|
|
5873
|
-
} else if (command === "snapshot") {
|
|
5874
|
-
result = snapshotCodexPluginSelection({
|
|
5875
|
-
paths,
|
|
5876
|
-
snapshotRoot: required("--snapshot-root"),
|
|
5877
|
-
});
|
|
5878
|
-
} else if (command === "install-cache") {
|
|
5879
|
-
result = installCodexCandidateCache({
|
|
5880
|
-
paths,
|
|
5881
|
-
sourcePluginRoot: required("--source"),
|
|
5882
|
-
version: required("--version"),
|
|
5883
|
-
});
|
|
5884
|
-
} else if (command === "activate") {
|
|
5885
|
-
result = activateCodexCandidateSelection({
|
|
5886
|
-
paths,
|
|
5887
|
-
snapshotManifestPath: required("--snapshot"),
|
|
5888
|
-
expectedSnapshotHash: required("--snapshot-hash"),
|
|
5889
|
-
candidateVersion: required("--candidate-version"),
|
|
5890
|
-
expectedCandidateHash: required("--candidate-hash"),
|
|
5891
|
-
approvalId: required("--approval-id"),
|
|
5892
|
-
approvedAt: required("--approved-at"),
|
|
5893
|
-
restoreBy: required("--restore-by"),
|
|
5894
|
-
now: required("--now"),
|
|
5895
|
-
});
|
|
5896
|
-
} else if (command === "restore") {
|
|
5897
|
-
result = restoreCodexPluginSelection({
|
|
5898
|
-
paths,
|
|
5899
|
-
snapshotManifestPath: required("--snapshot"),
|
|
5900
|
-
expectedSnapshotHash: required("--snapshot-hash"),
|
|
5901
|
-
activationPath: required("--activation"),
|
|
5902
|
-
now: required("--now"),
|
|
5903
|
-
recoveryReason: flags.get("--recovery-reason") || undefined,
|
|
5904
|
-
taskEvidence:
|
|
5905
|
-
flags.has("--task-id") && flags.has("--resolved-candidate-hash")
|
|
5906
|
-
? {
|
|
5907
|
-
taskId: flags.get("--task-id"),
|
|
5908
|
-
resolvedCandidateHash: flags.get("--resolved-candidate-hash"),
|
|
5909
|
-
}
|
|
5910
|
-
: undefined,
|
|
5911
|
-
});
|
|
5912
|
-
} else {
|
|
5913
|
-
throw new Error(
|
|
5914
|
-
"Usage: sellable codex plugin-selection snapshot|install-cache|activate|restore|status"
|
|
5915
|
-
);
|
|
5916
|
-
}
|
|
5917
|
-
console.log(JSON.stringify(result, null, 2));
|
|
5918
|
-
process.exit(0);
|
|
5919
|
-
}
|
|
5920
5696
|
if (
|
|
5921
5697
|
rawArgs[0] === "hermes" &&
|
|
5922
5698
|
rawArgs[1] === "profile" &&
|