@sellable/install 0.1.307 → 0.1.309
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
|
@@ -122,6 +122,13 @@ It creates:
|
|
|
122
122
|
/srv/hermes/profiles/acme/.env # only when Slack token inputs are supplied
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
For profile paths under a gateway root such as
|
|
126
|
+
`/srv/hermes/profiles/acme`, bootstrap also registers
|
|
127
|
+
`/srv/hermes/profiles/acme/skills/sellable` in the gateway root
|
|
128
|
+
`/srv/hermes/config.yaml` as `skills.external_dirs`. Keep this behavior:
|
|
129
|
+
Hermes Desktop command autocomplete scans the gateway/root skill config, not
|
|
130
|
+
only the selected profile's local skill tree.
|
|
131
|
+
|
|
125
132
|
If no token is supplied, bootstrap still creates a pending
|
|
126
133
|
`sellable/config.json` so the profile path is concrete from the first MCP
|
|
127
134
|
launch. Finish auth with:
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
writeFileSync,
|
|
14
14
|
} from "node:fs";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
16
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
17
17
|
import { stdout as output } from "node:process";
|
|
18
18
|
import { createInterface } from "node:readline/promises";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
@@ -2087,9 +2087,9 @@ Desktop, then start a new thread.
|
|
|
2087
2087
|
|
|
2088
2088
|
1. Call \`mcp__sellable__get_auth_status({})\`.
|
|
2089
2089
|
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.
|
|
2090
|
-
3. Normalize invocation arguments with \`mcp__sellable__refill_sends({ yolo, executionMode, requireWorkspace, workspaceId, senders, senderIds, senderNames,
|
|
2090
|
+
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" })\`.
|
|
2091
2091
|
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" })\`.
|
|
2092
|
-
5. Call \`mcp__sellable__get_refill_target_plan({ workspaceId, senders, senderIds, senderNames,
|
|
2092
|
+
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.
|
|
2093
2093
|
6. Load the canonical prompt and deterministic flow asset via
|
|
2094
2094
|
\`mcp__sellable__get_subskill_prompt({ subskillName: "refill-sends" })\`,
|
|
2095
2095
|
then load \`mcp__sellable__get_subskill_prompt({ subskillName: "refill-sends-workflow" })\`,
|
|
@@ -3663,6 +3663,19 @@ function hermesSkillsRoot(opts = {}) {
|
|
|
3663
3663
|
return join(hermesHome(opts), "skills", "sellable");
|
|
3664
3664
|
}
|
|
3665
3665
|
|
|
3666
|
+
function hermesGatewayRoot(opts = {}) {
|
|
3667
|
+
const profileRoot = hermesHome(opts);
|
|
3668
|
+
const profilesRoot = dirname(profileRoot);
|
|
3669
|
+
if (basename(profilesRoot) !== "profiles") return null;
|
|
3670
|
+
return dirname(profilesRoot);
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
function hermesGatewayConfigPath(opts = {}) {
|
|
3674
|
+
const root = hermesGatewayRoot(opts);
|
|
3675
|
+
if (!root) return null;
|
|
3676
|
+
return join(root, "config.yaml");
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3666
3679
|
function hermesLikelyInstalled() {
|
|
3667
3680
|
return (
|
|
3668
3681
|
Boolean(process.env.HERMES_HOME?.trim()) ||
|
|
@@ -3742,6 +3755,38 @@ function writeHermesMcpServer(opts) {
|
|
|
3742
3755
|
return configPath;
|
|
3743
3756
|
}
|
|
3744
3757
|
|
|
3758
|
+
function writeHermesGatewaySkillDirectory(opts) {
|
|
3759
|
+
const configPath = hermesGatewayConfigPath(opts);
|
|
3760
|
+
if (!configPath || configPath === hermesConfigPath(opts)) return null;
|
|
3761
|
+
|
|
3762
|
+
const skillDir = hermesSkillsRoot(opts);
|
|
3763
|
+
const raw = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
3764
|
+
const config = parseHermesConfig(raw, configPath);
|
|
3765
|
+
// Hermes Desktop slash autocomplete scans the gateway/root HERMES_HOME. A
|
|
3766
|
+
// profile can execute its own skills without this, but Desktop will not show
|
|
3767
|
+
// /sellable-* commands until the root scanner sees the profile skill dir.
|
|
3768
|
+
const skills =
|
|
3769
|
+
config.skills && typeof config.skills === "object" && !Array.isArray(config.skills)
|
|
3770
|
+
? config.skills
|
|
3771
|
+
: {};
|
|
3772
|
+
const current = Array.isArray(skills.external_dirs)
|
|
3773
|
+
? skills.external_dirs
|
|
3774
|
+
: skills.external_dirs
|
|
3775
|
+
? [skills.external_dirs]
|
|
3776
|
+
: [];
|
|
3777
|
+
const externalDirs = current.includes(skillDir) ? current : [...current, skillDir];
|
|
3778
|
+
const nextConfig = {
|
|
3779
|
+
...config,
|
|
3780
|
+
skills: {
|
|
3781
|
+
...skills,
|
|
3782
|
+
external_dirs: externalDirs,
|
|
3783
|
+
},
|
|
3784
|
+
};
|
|
3785
|
+
|
|
3786
|
+
writeHermesConfig(configPath, nextConfig, opts);
|
|
3787
|
+
return { configPath, skillDir };
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3745
3790
|
function installHermesSkills(opts) {
|
|
3746
3791
|
const root = hermesSkillsRoot(opts);
|
|
3747
3792
|
for (const skill of hermesSkillDefinitions()) {
|
|
@@ -3762,8 +3807,15 @@ function installHermes(opts) {
|
|
|
3762
3807
|
try {
|
|
3763
3808
|
skillsRoot = installHermesSkills(opts);
|
|
3764
3809
|
const configPath = writeHermesMcpServer(opts);
|
|
3810
|
+
const gateway = writeHermesGatewaySkillDirectory(opts);
|
|
3765
3811
|
logVerbose(`${C.grey}+ Hermes config updated: ${configPath}${C.reset}`);
|
|
3766
|
-
return {
|
|
3812
|
+
return {
|
|
3813
|
+
installed: true,
|
|
3814
|
+
configPath,
|
|
3815
|
+
skillsRoot,
|
|
3816
|
+
gatewayConfigPath: gateway?.configPath || null,
|
|
3817
|
+
gatewayExternalSkillsDir: gateway?.skillDir || null,
|
|
3818
|
+
};
|
|
3767
3819
|
} catch (err) {
|
|
3768
3820
|
if (skillsRoot && !opts.dryRun) {
|
|
3769
3821
|
rmSync(skillsRoot, { recursive: true, force: true });
|
|
@@ -4001,6 +4053,7 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4001
4053
|
sellableConfigPath,
|
|
4002
4054
|
sellableConfigsDir,
|
|
4003
4055
|
skillsRoot: hermesSkillsRoot(opts),
|
|
4056
|
+
gatewayConfigPath: hermesGatewayConfigPath(opts),
|
|
4004
4057
|
},
|
|
4005
4058
|
auth: {
|
|
4006
4059
|
apiUrl: opts.apiUrl,
|
|
@@ -4085,6 +4138,8 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4085
4138
|
}
|
|
4086
4139
|
|
|
4087
4140
|
const hermesConfigExisted = existsSync(hermesConfigPath(opts));
|
|
4141
|
+
const gatewayConfigPath = hermesGatewayConfigPath(opts);
|
|
4142
|
+
const gatewayConfigExisted = gatewayConfigPath ? existsSync(gatewayConfigPath) : false;
|
|
4088
4143
|
const skillsExisted = existsSync(hermesSkillsRoot(opts));
|
|
4089
4144
|
const authExisted = existsSync(sellableConfigPath);
|
|
4090
4145
|
const configsDirExisted = existsSync(sellableConfigsDir);
|
|
@@ -4138,6 +4193,11 @@ function runHermesProfileBootstrap(argv) {
|
|
|
4138
4193
|
(hermesConfigExisted ? summary.updated : summary.created).push(
|
|
4139
4194
|
hermesConfigPath(opts)
|
|
4140
4195
|
);
|
|
4196
|
+
if (gatewayConfigPath) {
|
|
4197
|
+
(gatewayConfigExisted ? summary.updated : summary.created).push(
|
|
4198
|
+
gatewayConfigPath
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4141
4201
|
(skillsExisted ? summary.updated : summary.created).push(hermesSkillsRoot(opts));
|
|
4142
4202
|
if (Object.values(slackTokens).some(Boolean)) {
|
|
4143
4203
|
(envExisted ? summary.updated : summary.created).push(envPath);
|
package/lib/runtime-verify.mjs
CHANGED
package/package.json
CHANGED
|
@@ -37,21 +37,16 @@ active workspace readback. Scheduled or autonomous usage must carry
|
|
|
37
37
|
`workspaceId` on every tool call. If the workspace is missing or ambiguous, stop
|
|
38
38
|
with `WORKSPACE_REQUIRED`; do not switch the shared active workspace.
|
|
39
39
|
|
|
40
|
-
Date selectors are sender-local and mutually exclusive by precedence:
|
|
41
|
-
`targetDate > untilDate > horizonSendDays`. Use `targetDate` for one exact sender-local date, `untilDate` for an inclusive through-date, and `horizonSendDays` for the next N sender-local send days. If none is provided, the planner uses the default scheduler-forward 48-hour behavior.
|
|
42
|
-
|
|
43
40
|
For a real refill run:
|
|
44
41
|
|
|
45
42
|
```text
|
|
46
|
-
refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode
|
|
47
|
-
refill_sends_v2({ workspaceId, targetDate:"2026-07-07" })
|
|
48
|
-
refill_sends_v2({ workspaceId, untilDate:"2026-07-09" })
|
|
43
|
+
refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode? })
|
|
49
44
|
```
|
|
50
45
|
|
|
51
46
|
For read-only inspection:
|
|
52
47
|
|
|
53
48
|
```text
|
|
54
|
-
refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds
|
|
49
|
+
refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
|
|
55
50
|
```
|
|
56
51
|
|
|
57
52
|
To resume an in-progress run, pass the handle back exactly:
|
|
@@ -60,11 +55,6 @@ To resume an in-progress run, pass the handle back exactly:
|
|
|
60
55
|
refill_sends_v2({ workspaceId, runId, fence })
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
The original run's date window is stored in the run record. On resume, the
|
|
64
|
-
stored window is reused and conflicting resume input is blocked; do not add,
|
|
65
|
-
change, or remove `targetDate`, `untilDate`, or `horizonSendDays` while passing
|
|
66
|
-
an existing `{ runId, fence }`.
|
|
67
|
-
|
|
68
58
|
If a stale handle loses the lease, the tool reports the holder status and the
|
|
69
59
|
approximately 10 minute lockout window. Reinvoke with the current handle or wait
|
|
70
60
|
for lease expiry; never guess a fence.
|
|
@@ -135,12 +125,9 @@ host guard, heartbeats the run lease, and may return:
|
|
|
135
125
|
```
|
|
136
126
|
|
|
137
127
|
Reinvoke with that handle. The approximately five minute scheduler budget is at
|
|
138
|
-
the run level, not one tool call.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
window-closed or loaded-awaiting scheduler report must include remaining-ready
|
|
142
|
-
count, exact expected pickup time, and the resume handle; never treat bare
|
|
143
|
-
"awaiting scheduler" copy as a final answer.
|
|
128
|
+
the run level, not one tool call. A window-closed or loaded-awaiting scheduler
|
|
129
|
+
report must include remaining-ready count, exact expected pickup time, and the
|
|
130
|
+
resume handle; never treat bare "awaiting scheduler" copy as a final answer.
|
|
144
131
|
Scheduler-run receipt interpretation still applies to terminal progress:
|
|
145
132
|
`cellsConsidered is allocation-attempt count`, not total ready supply, while
|
|
146
133
|
`readyCellsFound` is ready inventory found before prefilters. Inspect
|
|
@@ -87,12 +87,6 @@ Accepted invocation flags in the same user request:
|
|
|
87
87
|
preserves natural-language arguments better than shell-style flags.
|
|
88
88
|
- `targetDate: YYYY-MM-DD`: exact-date selector alternative when the host
|
|
89
89
|
preserves natural-language arguments better than shell-style flags.
|
|
90
|
-
- `horizonSendDays: N`: compatibility selector for the next N sender-local send
|
|
91
|
-
days when no exact or through-date selector is present.
|
|
92
|
-
|
|
93
|
-
Date selector precedence is `targetDate > untilDate > horizonSendDays`. Use
|
|
94
|
-
`targetDate` for one exact sender-local date, `untilDate` for an inclusive
|
|
95
|
-
through-date, and `horizonSendDays` for the next N sender-local send days.
|
|
96
90
|
|
|
97
91
|
When the host can call typed MCP tools, start with:
|
|
98
92
|
|
|
@@ -278,9 +272,7 @@ same sender/action/date; it tells the MCP how many cells the product scheduler
|
|
|
278
272
|
will try to place and does not import, approve, schedule, refresh credits, or
|
|
279
273
|
mutate.
|
|
280
274
|
When the refill loop has ready rows and needs scheduler pickup now, use
|
|
281
|
-
`run_scheduler_sweep
|
|
282
|
-
`untilDate` or `horizonSendDays` requests, sweep each planner-selected
|
|
283
|
-
`targetDate` with the same explicit `workspaceId`. The sweep can place cells
|
|
275
|
+
`run_scheduler_sweep` with the same explicit `workspaceId`; it can place cells
|
|
284
276
|
within existing scheduler gates and returns the receipt, but it never sends or
|
|
285
277
|
bypasses limits.
|
|
286
278
|
Scheduler-run receipt interpretation: `cellsConsidered is allocation-attempt
|